Testimonial
Description
As the leader of the Revivalists you are determined to take down the KORP, you and the best of your faction's hackers have set out to deface the official KORP website to send them a message that the revolution is closing in.
Analysis
client.go
package client
import (
"context"
"fmt"
"htbchal/pb"
"strings"
"sync"
"google.golang.org/grpc"
)
var (
grpcClient *Client
mutex *sync.Mutex
)
func init() {
grpcClient = nil
mutex = &sync.Mutex{}
}
type Client struct {
pb.RickyServiceClient
}
func GetClient() (*Client, error) {
mutex.Lock()
defer mutex.Unlock()
if grpcClient == nil {
conn, err := grpc.Dial(fmt.Sprintf("127.0.0.1%s", ":50045"), grpc.WithInsecure())
if err != nil {
return nil, err
}
grpcClient = &Client{pb.NewRickyServiceClient(conn)}
}
return grpcClient, nil
}
func (c *Client) SendTestimonial(customer, testimonial string) error {
ctx := context.Background()
// Filter bad characters.
for _, char := range []string{"/", "\\", ":", "*", "?", "\"", "<", ">", "|", "."} {
customer = strings.ReplaceAll(customer, char, "")
}
_, err := c.SubmitTestimonial(ctx, &pb.TestimonialSubmission{Customer: customer, Testimonial: testimonial})
return err
}
func (c *Client) SendTestimonialNoFilters(customer, testimonial string) error {
ctx := context.Background()
_, err := c.SubmitTestimonial(ctx, &pb.TestimonialSubmission{Customer: customer, Testimonial: testimonial})
return err
}ptypes.proto
syntax = "proto3";
option go_package = "/pb";
service RickyService {
rpc SubmitTestimonial(TestimonialSubmission) returns (GenericReply) {}
}
message TestimonialSubmission {
string customer = 1;
string testimonial = 2;
}
message GenericReply {
string message = 1;
}index.templ
package home
import (
"htbchal/view/layout"
"io/fs"
"fmt"
"os"
)
templ Index() {
@layout.App(true) {
<nav class="navbar navbar-expand-lg navbar-dark bg-black">
<div class="container-fluid">
<a class="navbar-brand" href="/">The Fray</a>
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarNav"
aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarNav">
<ul class="navbar-nav ml-auto">
<li class="nav-item active">
<a class="nav-link" href="/">Home</a>
</li>
<li class="nav-item">
<a class="nav-link" href="javascript:void();">Factions</a>
</li>
<li class="nav-item">
<a class="nav-link" href="javascript:void();">Trials</a>
</li>
<li class="nav-item">
<a class="nav-link" href="javascript:void();">Contact</a>
</li>
</ul>
</div>
</div>
</nav>
<div class="container">
<section class="jumbotron text-center">
<div class="container mt-5">
<h1 class="display-4">Welcome to The Fray</h1>
<p class="lead">Assemble your faction and prove youre the last one standing!</p>
<a href="javascript:void();" class="btn btn-primary btn-lg">Get Started</a>
</div>
</section>
<section class="container mt-5">
<h2 class="text-center mb-4">What Others Say</h2>
<div class="row">
@Testimonials()
</div>
</section>
<div class="row mt-5 mb-5">
<div class="col-md">
<h2 class="text-center mb-4">Submit Your Testimonial</h2>
<form method="get" action="/">
<div class="form-group">
<label class="mt-2" for="testimonialText">Your Testimonial</label>
<textarea class="form-control mt-2" id="testimonialText" rows="3" name="testimonial"></textarea>
</div>
<div class="form-group">
<label class="mt-2" for="testifierName">Your Name</label>
<input type="text" class="form-control mt-2" id="testifierName" name="customer"/>
</div>
<button type="submit" class="btn btn-primary mt-4">Submit Testimonial</button>
</form>
</div>
</div>
</div>
<footer class="bg-black text-white text-center py-3">
<p>© 2024 The Fray. All Rights Reserved.</p>
</footer>
}
}
func GetTestimonials() []string {
fsys := os.DirFS("public/testimonials")
files, err := fs.ReadDir(fsys, ".")
if err != nil {
return []string{fmt.Sprintf("Error reading testimonials: %v", err)}
}
var res []string
for _, file := range files {
fileContent, _ := fs.ReadFile(fsys, file.Name())
res = append(res, string(fileContent))
}
return res
}
templ Testimonials() {
for _, item := range GetTestimonials() {
<div class="col-md-4">
<div class="card mb-4">
<div class="card-body">
<p class="card-text">"{item}"</p>
<p class="text-muted">- Anonymous Testifier</p>
</div>
</div>
</div>
}
}main.go
package main
import (
"embed"
"htbchal/handler"
"htbchal/pb"
"log"
"net"
"net/http"
"github.com/go-chi/chi/v5"
"google.golang.org/grpc"
)
//go:embed public
var FS embed.FS
func main() {
router := chi.NewMux()
router.Handle("/*", http.StripPrefix("/", http.FileServer(http.FS(FS))))
router.Get("/", handler.MakeHandler(handler.HandleHomeIndex))
go startGRPC()
log.Fatal(http.ListenAndServe(":1337", router))
}
type server struct {
pb.RickyServiceServer
}
func startGRPC() error {
lis, err := net.Listen("tcp", ":50045")
if err != nil {
log.Fatal(err)
}
s := grpc.NewServer()
pb.RegisterRickyServiceServer(s, &server{})
if err := s.Serve(lis); err != nil {
log.Fatal(err)
}
return nil
}
There's many things to consider in this (somewhat) simple app.
There are 2 services, HTTP and GRPC.
Both are exposed to outside world
HTTP does validation and sends request to GRPC
Nothing stops us by directly interacting with GRPC
LFI in GRPC
err := os.WriteFile(fmt.Sprintf("public/testimonials/%s", req.Customer), []byte(req.Testimonial), 0644)HTTP filters our name, but GRPC doesnt. This introduces LFI vulnerability.
We can write files anywhere, but yet not read.
GRPC doesnt support direct interaction.
Error:
Failed to list services: server does not support the reflection APIBut since we have access to proto files, we are able to make it interactive.
Application is deployed using air
☁️ Air - Live reload for Go apps
Live reload will regenerate files is it detects change.
Because air will regenerate files on change we can send GRPC server such file that it will render contents when accessed. There's only 1 view so we will change index.templ with our code and execute it.
To interact with GRPC server I used grpcurl.
We need to send request to RickyService.SubmitTestimonial to overwrite the file. Since we are overwriting index.templ I mainly copy pasted same thing, deleted some markup and changed what golang should render.
Convert the template to oneliner string: Cyberchef Recipe

Solution
customer:
../../view/home/index.templ(Filename to write to)testimonial: Oneliner template (What data to write)
83.136.248.119:57007: GRPC_SERVER:PORT
RickyService.SubmitTestimonial: Endpoint

Flag: HTB{w34kly_t35t3d_t3mplate5}
Last updated