Mod ctfauth | Tjctf 2022
Fichier(s)
Nécessaires
- Python3 + Ida + Ghidra
Flag
CTF{http_headers_amiright}
Solution détaillée
Partie 1: Découverte du challenge
Ce challenge mélange un peu de Web et une majorité de reverse. Deux fichiers sont fournis,
- Une configuration apache2
- Un fichier .so
- Le lien d’un site web (Hébergé en local pour le writeup)
Voici ce que nous avons quand nous nous rendons sur le site :
<html><body><h1>It works!</h1></body></html>
Rien de très pertinent , jetons un œil au fichier de configuration httpd.conf :
DocumentRoot "/usr/local/apache2/htdocs"
<Directory "/usr/local/apache2/htdocs">
    AllowOverride None
    Header set Cache-Control no-cache
    Require all granted
</Directory>
<Directory "/usr/local/apache2/htdocs/secret">
    AllowOverride None
    Header set Cache-Control no-cache
    Require ctfauth
</Directory>
Ça c’est intéressant ! On apprend l’existence de /secret
Malheuresement , l’accés est bloqué !
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
<html><head>
<title>403 Forbidden</title>
</head><body>
<h1>Forbidden</h1>
<p>You don't have permission to access this resource.</p>
</body></html>
Tous ceci est logique , on remarque que l’accès est limité par un module : Require ctfauth
En remontant dans le fichier :
LoadModule ctfauth_module modules/ctfauth.so
Voilà , maintenant nous savons que le .so est un module apache2 permettant de gérer l’accès ou non à la page en question !
Partie 2: Mise en place d’un environnement de travail.
Pour continuer ce challenge, j’ai utilisé un serveur en local grâce à Docker et celui va me permettre par la suite de tester les manipulations et modification que je vais faire .
Grâce à ce serveur, je pourrais accéder aux logs et donc avoir plus d’informations sur mon avancé .
Voici le DockerFile:
FROM httpd:2.4
COPY ./httpd.conf /usr/local/apache2/conf/httpd.conf
COPY ./ctfauth3.so /usr/local/apache2/modules/ctfauth.so
RUN \
	mkdir /usr/local/apache2/htdocs/secret; \
	echo '<html><body><h1>flag{randomflag}</h1></body></html>' > /usr/local/apache2/htdocs/secret/secret.html;
EXPOSE 80
Partie 3: Compréhension du plugin.
Ouvrons le fichier ctfauth.so avec Ida
On retrouve une fonction qui gère l’authentification :
Voici le retour de fin :
if ( *v13 == v17 )
{
  ap_log_rerror_("./ctfauth.c", 87LL, 0xFFFFFFFFLL, 4LL, 0LL, a1, "CTF: Looks good!");
  free(v13);
  result = 1LL;
}
else
{
  ap_log_rerror_("./ctfauth.c", 82LL, 0xFFFFFFFFLL, 4LL, 0LL, a1, "CTF: Token doesn't match!");
  free(v13);
  result = 0LL;
}
return result;
Reprenons depuis le début de la fonction :
v3 = (const char *)apr_table_get(v2, "X-CTF-User");
if ( !v3 )
{
  ap_log_rerror_("./ctfauth.c", 38LL, 0xFFFFFFFFLL, 4LL, 0LL, a1, "CTF: No username detected");
  return 0LL;
}
v4 = v3;
if ( strcmp(v3, "ctf") )
{
  ap_log_rerror_("./ctfauth.c", 42LL, 0xFFFFFFFFLL, 4LL, 0LL, a1, "CTF: Incorrect username");
  return 0LL;
}
Le début est Trivial, on voit tout de suite qu’un Header X-CTF-User égal à ctf est nécessaire
v5 = apr_table_get(*(_QWORD *)(a1 + 232), "X-CTF-Authorization");
v6 = 0;
v7 = v5 == 0;
if ( !v5 )
{
  ap_log_rerror_("./ctfauth.c", 49LL, 0xFFFFFFFFLL, 4LL, 0LL, a1, "CTF: Missing authorization header");
  return 0LL;
}
v8 = 6LL;
v9 = "Token ";
v10 = (const char *)v5;
do
{
  if ( !v8 )
    break;
  v6 = *v10 < (unsigned int)*v9;
  v7 = *v10++ == *v9++;
  --v8;
}
while ( v7 );
if ( (!v6 && !v7) != v6 )
{
  ap_log_rerror_(
    "./ctfauth.c",
    54LL,
    0xFFFFFFFFLL,
    4LL,
    0LL,
    a1,
    "CTF: Incorrect header format for authorization header");
  return 0LL;
}
Un autre Header est requis. X-CTF-Authorization et après avoir passé un petit moment un bien comprendre le pseudo-code , le seul prérequis ici est que la valeur commence par Token 
Par la suite on a :
v11 = v5 + 6;
v12 = apr_base64_decode_len(v5 + 6, v10);
v13 = malloc(v12);
if ( (unsigned int)apr_base64_decode(v13, v11) != 16 )
{
 ap_log_rerror_(
   "./ctfauth.c",
   67LL,
   0xFFFFFFFFLL,
   4LL,
   0LL,
   a1,
   "CTF: Incorrect decoded length for authorization header (expected: %d bytes)");
 return 0LL;
}
Ici , on a une vérification de la taille de ce qui suit Token 
apr_base64_decode nous indique que le Header est de la forme :
X-CTF-Authorization: Token BASE64
et que la taille de la base64 décodée est égale à 16
Enfin , on a :
apr_md5_init(v16);
 apr_md5_update(v16, "GuardingTheGatesFromEvilCTFPlayers", 34LL);
 v14 = strlen(v4);
 apr_md5_update(v16, v4, v14);
 apr_md5_update(v16, "GuardingTheGatesFromEvilCTFPlayers", 34LL);
 apr_md5_final(&v17, v16);
 if ( *v13 == v17 )
 {
   ap_log_rerror_("./ctfauth.c", 87LL, 0xFFFFFFFFLL, 4LL, 0LL, a1, "CTF: Looks good!");
   free(v13);
   result = 1LL;
 }
 else
 {
   ap_log_rerror_("./ctfauth.c", 82LL, 0xFFFFFFFFLL, 4LL, 0LL, a1, "CTF: Token doesn't match!");
   free(v13);
   result = 0LL;
 }
 return result;
lire cette documentation
Ici , un hash MD5 de
GuardingTheGatesFromEvilCTFPlayers + v4 + GuardingTheGatesFromEvilCTFPlayers
avec:
v4 = v3 = (const char *)apr_table_get(v2, "X-CTF-User"); = 'ctf' 
Finalement , la valeur du Token serai:
base64(md5('GuardingTheGatesFromEvilCTFPlayerscfGuardingTheGatesFromEvilCTFPlayers')[16])
Malheureusement , cette technique n’a pas fonctionné et je n’ai toujours pas la réponse à ce soucis .
Contournement du Token:
Comme expliqué précédement , le serveur sur le Docker donne accès à des logs :
[Fri Jun 10 16:17:07.819589 2022] [:warn] [pid 99:tid 140199342372608] [client 172.17.0.1:33192] CTF: No username detected
[Fri Jun 10 16:17:07.819623 2022] [authz_core:error] [pid 99:tid 140199342372608] [client 172.17.0.1:33192] AH01630: client denied by server configuration: /usr/local/apache2/htdocs/secret
172.17.0.1 - - [10/Jun/2022:16:17:07 +0000] "GET /secret HTTP/1.1" 403 199
172.17.0.1 - - [10/Jun/2022:16:17:57 +0000] "-" 408 -
172.17.0.1 - - [10/Jun/2022:16:18:06 +0000] "-" 408 -
Utilisons cette fois Ghidra
Le code :
ap_log_rerror_("./ctfauth.c",0x52,0xffffffff,4,0,param_1,"CTF: Token doesn\'t match!",
est effectué durant la dernière vérification , ainsi : le bon hash md5 de comparaison est déjà calculé et présent en mémoire . De plus ; celui-ci est constant !
On voit ici  dans l’assembleur que la valeur du md5 est push dans le registre RDI

Par chance , le code d’affichage d’un token invalide et ce stockage dans RSI sont très proche .
On peut vérifier que ce registre RSI n’est pas modifié !
On peut donc patcher le binaire pour remplacer CTF: Token doesn\'t match! par RSI

Enfin , on peut envoyer un token aléatoire dans les headers et attendre le retour des logs du serveur !

Bingo ! Nous avons maintenant le MD5
import base64
a = b'm\xd8Q\b\x14\x1bzXE\x01\xb5\x86\x1b,\x83X)\xb6.p\x8f\x7f'
print(base64.b64encode(a[:16]).decode())
Output:
root@DESKTOP-HNQJECB: /c/mod_ctfauth
➜   python3 find.py
bdhRCBQbelhFAbWGGyyDWA==
Il ne nous reste plus qu’à tous envoyer et le tour est joué :
<p>Great job! Here's your flag</p>
<pre>CTF{http_headers_amiright}</pre>