Existe uma possibilidade de injeção de SQL, mesmo ao usar a mysql_real_escape_string()
função?
Considere esta situação de exemplo. SQL é construído em PHP assim:
$login = mysql_real_escape_string(GetFromPost('login'));
$password = mysql_real_escape_string(GetFromPost('password'));
$sql = "SELECT * FROM table WHERE login='$login' AND password='$password'";
Eu ouvi muitas pessoas me dizerem que códigos como esse ainda são perigosos e possíveis de invadir, mesmo com a mysql_real_escape_string()
função usada. Mas não consigo pensar em nenhuma possível exploração?
Injeções clássicas como esta:
aaa' OR 1=1 --
não funciona.
Você conhece alguma injeção possível que passaria pelo código PHP acima?
mysql_*
funções no novo código . Eles não são mais mantidos e o processo de descontinuação foi iniciado. Veja a caixa vermelha ? Aprenda sobre as instruções preparadas e use o DOP ou o MySQLi - este artigo o ajudará a decidir quais. Se você escolher a DOP, aqui está um bom tutorial .mysql_*
funções já produzemE_DEPRECATED
aviso. Aext/mysql
extensão não é mantida por mais de 10 anos. Você é realmente tão ilusório?Respostas:
Considere a seguinte consulta:
mysql_real_escape_string()
não irá protegê-lo contra isso. O fato de você usar aspas simples (' '
) em torno de suas variáveis na sua consulta é o que protege você contra isso. O seguinte também é uma opção:fonte
mysql_query()
não executa várias instruções, não?DROP TABLE
, na prática o atacante é mais provávelSELECT passwd FROM users
. Neste último caso, a segunda consulta geralmente é executada pelo uso de umaUNION
cláusula.(int)mysql_real_escape_string
- isso não faz sentido. Não difere de(int)
todo. E eles produzirão o mesmo resultado para cada entradamysql_real_escape_string
, nãomysql_real_escape_integer
. Não é para ser usado com campos inteiros.A resposta curta é sim, sim, há uma maneira de se locomover
mysql_real_escape_string()
.PARA CASOS DE OURO MUITO OBSCURO !!!
A resposta longa não é tão fácil. É baseado em um ataque demonstrado aqui .
O ataque
Então, vamos começar mostrando o ataque ...
Em certas circunstâncias, isso retornará mais de uma linha. Vamos dissecar o que está acontecendo aqui:
Selecionando um conjunto de caracteres
Para este ataque ao trabalho, precisamos da codificação que o servidor está esperando na conexão tanto para codificar
'
como no ie ASCII0x27
e ter algum personagem cujo byte final é um ASCII\
ie0x5c
. Como se vê, há 5 tais codificações suportadas no MySQL 5.6 por padrão:big5
,cp932
,gb2312
,gbk
esjis
. Vamos selecionargbk
aqui.Agora, é muito importante observar o uso
SET NAMES
daqui. Isso define o conjunto de caracteres NO SERVIDOR . Se usássemos a chamada para a função da API Cmysql_set_charset()
, estaríamos bem (nas versões do MySQL desde 2006). Mas mais sobre por que em um minuto ...A carga útil
A carga útil que vamos usar para esta injeção começa com a sequência de bytes
0xbf27
. Emgbk
, esse é um caractere multibyte inválido; dentrolatin1
, é a corda¿'
. Observe que emlatin1
egbk
,0x27
por si só, é um'
caractere literal .Escolhemos essa carga útil, porque, se
addslashes()
a utilizássemos, inseriríamos um ASCII ,\
ou seja0x5c
, antes do'
caractere. Então, terminamos com0xbf5c27
, quegbk
é uma sequência de dois caracteres:0xbf5c
seguida por0x27
. Ou, em outras palavras, um caractere válido seguido por um sem escape'
. Mas não estamos usandoaddslashes()
. Então, para o próximo passo ...mysql_real_escape_string ()
A chamada da API C
mysql_real_escape_string()
é diferente poraddslashes()
conhecer o conjunto de caracteres da conexão. Portanto, ele pode executar o escape corretamente para o conjunto de caracteres que o servidor está esperando. No entanto, até o momento, o cliente pensa que ainda estamos usandolatin1
a conexão, porque nunca dissemos o contrário. Dissemos ao servidor que estamos usandogbk
, mas o cliente ainda pensa que élatin1
.Portanto, a chamada para
mysql_real_escape_string()
insere a barra invertida, e temos um'
caractere livre suspenso em nosso conteúdo "escapado"! Na verdade, se estivéssemos a olhar para$var
nogbk
conjunto de caracteres, veríamos:Qual é exatamente o que o ataque exige.
A pergunta
Esta parte é apenas uma formalidade, mas aqui está a consulta renderizada:
Parabéns, você acabou de atacar com sucesso um programa usando
mysql_real_escape_string()
...O mal
Fica pior.
PDO
O padrão é emular instruções preparadas com o MySQL. Isso significa que, no lado do cliente, ele basicamente faz um sprintfmysql_real_escape_string()
(na biblioteca C), o que significa que o seguinte resultará em uma injeção bem-sucedida:Agora, é importante notar que você pode evitar isso desativando as instruções preparadas emuladas:
Isso geralmente resultará em uma verdadeira declaração preparada (isto é, os dados sendo enviados em um pacote separado da consulta). No entanto, esteja ciente de que o PDO silenciosamente recorrerá a emulações de instruções que o MySQL não pode preparar de forma nativa: aquelas que podem ser listadas no manual, mas tome cuidado para selecionar a versão apropriada do servidor).
O feio
Eu disse desde o início que poderíamos ter evitado tudo isso se tivéssemos usado em
mysql_set_charset('gbk')
vez deSET NAMES gbk
. E isso é verdade, desde que você esteja usando uma versão do MySQL desde 2006.Se você estiver usando uma versão do MySQL mais cedo, em seguida, um bug no
mysql_real_escape_string()
significava que caracteres de vários bytes inválidos, como os da nossa carga foram tratados como bytes únicas para efeitos escapar mesmo se o cliente tinha sido correctamente informado sobre a codificação de conexão e assim por este ataque faria ainda tem sucesso. O bug foi corrigido no MySQL 4.1.20 , 5.0.22 e 5.1.11 .Mas a pior parte é que
PDO
não expusemos a API Cmysql_set_charset()
até a 5.3.6; portanto, nas versões anteriores, não é possível impedir esse ataque para todos os comandos possíveis! Agora está exposto como um parâmetro DSN .A Graça Salvadora
Como dissemos no início, para que esse ataque funcione, a conexão com o banco de dados deve ser codificada usando um conjunto de caracteres vulneráveis. não
utf8mb4
é vulnerável e ainda pode suportar todos os caracteres Unicode: portanto, você pode optar por usá-lo - mas ele só está disponível desde o MySQL 5.5.3. Uma alternativa éutf8
que também não é vulnerável e pode suportar todo o Plano Multilíngue Básico Unicode .Como alternativa, você pode ativar o
NO_BACKSLASH_ESCAPES
modo SQL, que (entre outras coisas) altera a operação domysql_real_escape_string()
. Com esse modo ativado,0x27
será substituído por em0x2727
vez de0x5c27
e, portanto, o processo de escape não poderá criar caracteres válidos em nenhuma das codificações vulneráveis onde elas não existiam anteriormente (ou0xbf27
seja, ainda é0xbf27
etc.) - para que o servidor ainda rejeite a string como inválida . No entanto, consulte a resposta do @ eggyal para uma vulnerabilidade diferente que pode surgir ao usar este modo SQL.Exemplos seguros
Os seguintes exemplos são seguros:
Porque o servidor está esperando
utf8
...Porque definimos corretamente o conjunto de caracteres para que o cliente e o servidor correspondam.
Porque desativamos as instruções preparadas emuladas.
Porque definimos o conjunto de caracteres corretamente.
Porque o MySQLi faz verdadeiras declarações preparadas o tempo todo.
Empacotando
Se vocês:
mysql_set_charset()
/$mysqli->set_charset()
/ PDO (no PHP ≥ 5.3.6)OU
utf8
/latin1
/ascii
/ etc)Você é 100% seguro.
Caso contrário, você estará vulnerável mesmo usando
mysql_real_escape_string()
...fonte
addslashes
. I base esta vulnerabilidade em que um. Tente você mesmo. Vá para o MySQL 5.0 e execute esta exploração e veja por si mesmo. Tanto quanto colocar isso em PUT / GET / POST, é TRIVIAL. Os dados de entrada são apenas fluxos de bytes.char(0xBF)
é apenas uma maneira legível de gerar um byte. Demonstrei essa vulnerabilidade ao vivo em frente a várias conferências. Confie em mim ... Mas se não, tente você mesmo. Funciona ...?var=%BF%27+OR+1=1+%2F%2A
na URL,$var = $_GET['var'];
no código, e Bob é seu tio.Este é outro caso (talvez menos?) Obscuro da BORDA !!!
Em homenagem à excelente resposta de @ ircmaxell (realmente, isso deveria ser lisonjeiro e não plágio!), Adotarei seu formato:
O ataque
Começando com uma demonstração ...
Isso retornará todos os registros da
test
tabela. Uma dissecção:Selecionando um modo SQL
Conforme documentado em Literais de String :
Se o modo SQL do servidor incluir
NO_BACKSLASH_ESCAPES
, a terceira dessas opções - que é a abordagem usual adotada pormysql_real_escape_string()
- não estará disponível: uma das duas primeiras opções deve ser usada. Observe que o efeito do quarto marcador é que é necessário conhecer necessariamente o caractere que será usado para citar o literal, a fim de evitar a confusão dos dados.A carga útil
A carga útil inicia essa injeção literalmente com o
"
personagem. Nenhuma codificação específica. Sem caracteres especiais. Sem bytes estranhos.mysql_real_escape_string ()
Felizmente,
mysql_real_escape_string()
verifica o modo SQL e ajusta seu comportamento de acordo. Vejalibmysql.c
:Portanto, uma função subjacente diferente
escape_quotes_for_mysql()
, é invocada se oNO_BACKSLASH_ESCAPES
modo SQL estiver em uso. Como mencionado acima, essa função precisa saber qual caractere será usado para citar o literal para repeti-lo sem fazer com que o outro caractere de citação seja repetido literalmente.No entanto, essa função assume arbitrariamente que a seqüência de caracteres será citada usando o
'
caractere de aspas simples . Vejacharset.c
:Portanto, deixa os
"
caracteres de aspas duplas intocados (e dobra todos os'
caracteres de aspas simples ), independentemente do caractere real usado para citar o literal ! No nosso caso$var
permanece exatamente o mesmo que o argumento de que foi fornecido amysql_real_escape_string()
-é como se há como escapar teve lugar em tudo .A pergunta
Algo de formalidade, a consulta renderizada é:
Como meu amigo aprendeu disse: parabéns, você acabou de atacar com sucesso um programa usando
mysql_real_escape_string()
...O mal
mysql_set_charset()
não pode ajudar, pois isso não tem nada a ver com conjuntos de caracteres; nem podemysqli::real_escape_string()
, já que é apenas um invólucro diferente em torno dessa mesma função.O problema, se ainda não óbvio, é que a chamada para
mysql_real_escape_string()
não saber com qual caractere o literal será citado, pois isso é deixado para o desenvolvedor decidir posteriormente. Portanto, noNO_BACKSLASH_ESCAPES
modo, não há literalmente nenhuma maneira de que essa função possa escapar com segurança de todas as entradas para uso com aspas arbitrárias (pelo menos, não sem caracteres duplicados que não exijam duplicação e, portanto, alterando seus dados).O feio
Fica pior.
NO_BACKSLASH_ESCAPES
pode não ser tão incomum na natureza devido à necessidade de seu uso para compatibilidade com o SQL padrão (por exemplo, consulte a seção 5.3 da especificação SQL-92 , ou seja, a<quote symbol> ::= <quote><quote>
produção gramatical e a falta de qualquer significado especial dado à barra invertida). Além disso, seu uso foi explicitamente recomendado como uma solução alternativa para o bug (há muito corrigido) descrito pela publicação da ircmaxell. Quem sabe, alguns DBAs podem até configurá-lo para estar ativado por padrão como meio de desencorajar o uso de métodos de escape incorretos comoaddslashes()
.Além disso, o modo SQL de uma nova conexão é definido pelo servidor de acordo com sua configuração (que um
SUPER
usuário pode alterar a qualquer momento); portanto, para ter certeza do comportamento do servidor, você sempre deve especificar explicitamente o modo desejado após a conexão.A Graça Salvadora
Desde que você sempre defina explicitamente o modo SQL para não incluir
NO_BACKSLASH_ESCAPES
ou cite literais de string do MySQL usando o caractere de aspas simples, esse bug não poderá criar sua cabeça feia: respectivamenteescape_quotes_for_mysql()
não serão usados, ou sua suposição sobre quais caracteres de aspas requerem repetição será esteja correto.Por esse motivo, recomendo que qualquer pessoa que esteja usando
NO_BACKSLASH_ESCAPES
também ative oANSI_QUOTES
modo, pois forçará o uso habitual de literais de cadeia de caracteres com aspas simples. Observe que isso não impede a injeção de SQL no caso de literais de aspas duplas serem usados - apenas reduz a probabilidade de que isso aconteça (porque as consultas normais e não maliciosas falhariam).No PDO, tanto a sua função equivalente
PDO::quote()
quanto o emulador de instrução preparado chamammysql_handle_quoter()
- o que faz exatamente isso: garante que o literal escapado seja citado entre aspas simples, para que você possa ter certeza de que o DOP está sempre imune a esse bug.No MySQL v5.7.6, esse bug foi corrigido. Veja o log de alterações :
Exemplos seguros
Tomados em conjunto com o bug explicado pela ircmaxell, os exemplos a seguir são totalmente seguros (assumindo que um esteja usando o MySQL posterior a 4.1.20, 5.0.22, 5.1.11; ou que não esteja usando uma codificação de conexão GBK / Big5) :
... porque selecionamos explicitamente um modo SQL que não inclui
NO_BACKSLASH_ESCAPES
.... porque estamos citando nossa string literal com aspas simples.
... porque as instruções preparadas pelo PDO são imunes a esta vulnerabilidade (e a ircmaxell também, desde que você esteja usando o PHP≥5.3.6 e o conjunto de caracteres tenha sido definido corretamente no DSN; ou que a emulação de instruções preparadas tenha sido desativada) .
... porque a
quote()
função do PDO não apenas escapa ao literal, mas também cita (em'
caracteres de aspas simples ); note que, para evitar o bug do ircmaxell, neste caso, você deve estar usando o PHP≥5.3.6 e definiu corretamente o conjunto de caracteres no DSN.... porque as instruções preparadas do MySQLi são seguras.
Empacotando
Assim, se você:
OU
OU
em Além de empregar uma das soluções em resumo ircmaxell de, o uso de pelo menos um dos seguintes:
NO_BACKSLASH_ESCAPES
... então você deve estar completamente seguro (vulnerabilidades fora do escopo da cadeia de caracteres que escapam).
fonte
"
strings em primeiro lugar. SQL diz que é para identificadores. Mas eh ... apenas outro exemplo do MySQL dizendo "padrões de parafuso, farei o que eu quiser". (Felizmente, você pode incluirANSI_QUOTES
no modo de corrigir o quebrantamento citando O desrespeito aberto de normas, porém, é um problema maior que pode exigir medidas mais severas..)quote()
função do DOP - mas as instruções preparadas são uma maneira muito mais segura e apropriada de evitar a injeção em geral. Obviamente, se você concatenou diretamente variáveis não escapadas em seu SQL, certamente estará vulnerável à injeção, independentemente dos métodos usados posteriormente.Bem, não há realmente nada que possa passar por isso, a não ser
%
curinga. Poderia ser perigoso se você estivesse usando aLIKE
declaração como invasor, poderia colocar o mesmo%
login se não filtrar isso e precisaria apenas forçar uma senha de qualquer usuário. As pessoas geralmente sugerem o uso de instruções preparadas para torná-lo 100% seguro, pois os dados não podem interferir na consulta em si dessa maneira. Mas, para consultas tão simples, provavelmente seria mais eficiente fazer algo como$login = preg_replace('/[^a-zA-Z0-9_]/', '', $login);
fonte
more efficient
que usar declarações preparadas? (As instruções preparadas sempre funcionam, a biblioteca pode ser rapidamente corrigida em caso de ataques, não expõe erros humanos [como digitar incorretamente a cadeia de substituição completa] e tem benefícios significativos de desempenho se a instrução for reutilizada.)