Énoncé
A friend of yours has created a web application that allows you to check the availability of your locally hosted services. He assured you that it is secure and even allowed you to run it as a test user!
Prove him wrong by reading the
flag.txt
file on the server.~ The flag can be found in the file:
/tmp/flag.txt
Authors: Owne, Brumens
Aperçu
Dans un premier temps, une application dont le code source était accessible a été identifiée. Elle prend en entrée deux valeurs : cmd et token. La valeur cmd est censé correspondre à l’ip ou nom de domaine de la machine à ping.
Résolution
Pour résoudre ce dojo, il va falloir faire en sorte d’obtenir l’accès dev, puis d’utiliser l’accès dev pour RCE. Une fois RCE, il suffira de lire le fichier situé dans /tmp/flag.txt
.
SQL Like Injection
La première chose à faire avant de pouvoir exploiter l’injection de commandes est de passer par la méthode PreProd_Sanitize()
, plutôt que la méthode Prod_Sanitize()
. En effet la méthode Prod_Sanitize()
assaini correctement l’entrée cmd, ce qui rend l’injection de commandes impossible. Pour que cmd soit assaini par la méthode que nous voulons, il va falloir passer par l’utilisateur dev, comme indiqué dans le code source :
def Run(self):
if self.user == "dev":
cmd_sanitize = self.PreProd_Sanitize(self.command)
else:
cmd_sanitize = self.Prod_Sanitize(self.command)
# At the moment we don't have internet access.
# We should only ping localhost to avoid server timeout
result = subprocess.run(["/bin/ash", "-c", f"ping -c 1 {cmd_sanitize}"], capture_output=True, text=True)
if result.returncode == 0:
return result.stdout
else:
return result.stderr
Si on regarde à quoi correspond user, on s’aperçoit que cela correspond à la première valeur retournée par une requête SQL :
# Get user that holds the given token
r = cursor.execute('SELECT username FROM users WHERE token LIKE ?', (token,))
try:
user = r.fetchone()[0]
except:
user = "test"
command = Command(cmd, user)
Cette requête va récupérer les utilisateurs qui possèdent le token envoyé en entrée. Pour que l’utilisateur corresponde à dev, il faudrait donc normalement avoir le token qui lui appartient. Cependant l’instruction LIKE
est utilisé pour vérifier le token, on peut alors récupérer un utilisateur possédant un token, qui valide ce genre de comparaison : _%
. Le symbole _
peut être remplacé par n’importe quel caractère, mais un seul caractère uniquement, alors que le symbole %
peut être remplacé par un nombre incalculable de caractères. Les deux combinés valident n’importe quel token.
Si le premier token stocké dans la DB correspond à celui de dev, on aura alors ce que l’on veut :
Maintenant qu’on est dev, on va pouvoir passer à l’injection de commandes.
OS Command Injection
Dans cette partie, on va voir comment rendre une injection de commandes qui semble presque inutile, utile. Une fois qu’on est dev, le programme utilise une autre méthode pour assainir cmd :
def PreProd_Sanitize(self, s:str) -> str:
"""My homemade secure sanitize function"""
if not s:
return "''"
if re.search(r'[a-zA-Z_*^@%+=:,./-]', s) is None:
return s
return "'" + s.replace("'", "'\"'\"'") + "'"
Cette méthode va assainir toute les entrées qui match la regex : r'[a-zA-Z_*^@%+=:,./-]
. On peut malheureusement pas échapper les simples quotes, comme on pourrait le penser (en tout cas je n’ai pas trouvé comment). Cependant, on peut essayer de lire le fichier flag.txt
sans matcher la regex. Pour faire ça, il va falloir être ingénieux. Pour lire le fichier voulu, il va falloir le faire sans les caractères alphabétiques, ce qui complique grandement la tâche. Dans un premier temps, on peut enchaîner l’exécution de commandes grâce au caractère ;
qui n’est pas présent dans la regex. Pour rendre le ping valide et éviter d’avoir un terminal illisible on peut utiliser ce genre de payload : 0;
. Ping sur 0 correspond à ping sur localhost, ensuite on sépare la commande ping de notre future commande pour lire le fichier flag.txt
avec ;
. Ensuite, il va falloir appeler /bin/ash
sans utiliser de lettres ou de slash. On peut faire ça avec $0
puisque cela correspond au premier argument ici /bin/ash
, ensuite on met un caractère qui ne match pas la regex sinon on aura un timeout. On obtient maintenant le payload : 0;$0 1
, qu’on teste :
On voit bien que $0
équivaut bien à /bin/ash
ici comme indiqué précédemment. Ensuite pour avoir quelque chose du genre /bin/ash flag.txt
, il va falloir indiquer à /bin/ash
d’exécuter le fichier, toujours sans matcher la regex. Pour cela on peut utiliser le point d’interrogation. Car dans un contexte de correspondance de fichiers, ?
est un joker (wildcard) qui représente exactement un caractère. Il peut être utilisé pour rechercher ou manipuler des fichiers ou des chaînes qui ont un caractère à une certaine position, peu importe lequel. Il suffit alors ensuite de mettre autant de ?
que de caractères présents dans flag.txt
et le tour est joué ! Le payload final correspond maintenant à : 0;$0 ????????
.
Exploitation
Résultat :
Flag : FLAG{W3lc0me_T0_Th3_Oth3r_S1de!}
Bonus : Il était aussi possible d’exécuter des commandes en transformant les caractères en octal. Par exemple avec
; $'\143\141\164' $'\146\154\141\147\056\164\170\164'
qui correspond à; cat flag.txt
.