Digamos que eu tenha um código como este:
$dbh = new PDO("blahblah");
$stmt = $dbh->prepare('SELECT * FROM users where username = :username');
$stmt->execute( array(':username' => $_REQUEST['username']) );
A documentação da DOP diz:
Os parâmetros para instruções preparadas não precisam ser citados; o driver lida com você.
Isso é realmente tudo o que preciso fazer para evitar injeções de SQL? é realmente tão fácil?
Você pode assumir o MySQL se isso fizer diferença. Além disso, estou realmente curioso sobre o uso de instruções preparadas contra a injeção de SQL. Nesse contexto, não me importo com o XSS ou outras possíveis vulnerabilidades.
php
security
pdo
sql-injection
Mark Biek
fonte
fonte
Respostas:
A resposta curta é NÃO , a preparação do DOP não o defenderá de todos os possíveis ataques de injeção de SQL. Para certos casos obscuros.
Estou adaptando esta resposta para falar sobre o DOP ...
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 . Há outra maneira de fazer isso, mas chegaremos lá em breve.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 ...$ stmt-> execute ()
O importante a ser percebido aqui é que o DOP por padrão NÃO faz declarações preparadas verdadeiras. Emula-os (para MySQL). Portanto, o PDO constrói internamente a string de consulta, chamando
mysql_real_escape_string()
(a função API do MySQL C) em cada valor da string vinculada.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 as declarações preparadas do DOP ...
A correção simples
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).
A correção correta
O problema aqui é que não chamamos as APIs C em
mysql_set_charset()
vez deSET NAMES
. Se o fizéssemos, ficaríamos bem desde que usássemos 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 , que deve ser usado em vez deSET NAMES
...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 da @ eggyal para uma vulnerabilidade diferente que pode surgir ao usar esse modo SQL (embora não com o PDO).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:
OU
utf8
/latin1
/ascii
/ etc)OU
NO_BACKSLASH_ESCAPES
modo SQLVocê é 100% seguro.
Caso contrário, você estará vulnerável, mesmo usando declarações preparadas para DOP ...
Termo aditivo
Eu tenho trabalhado lentamente em um patch para alterar o padrão para não emular os preparativos para uma versão futura do PHP. O problema que estou enfrentando é que muitos testes são interrompidos quando faço isso. Um problema é que as preparações emuladas só lançam erros de sintaxe na execução, mas as preparações verdadeiras lançam erros na preparação. Portanto, isso pode causar problemas (e faz parte do motivo pelo qual os testes estão funcionando).
fonte
mysql_real_escape_string
não tratava adequadamente os casos em que a conexão estava definida corretamente como BIG5 / GBK. Então, na verdade, mesmo invocar omysql_set_charset()
mysql <5.0.22 seria vulnerável a esse bug! Então, não, este post é ainda aplicável a 5.0.22 (porque mysql_real_escape_string só é charset afastado para chamadas a partirmysql_set_charset()
, que é o que este post está falando sobre ignorando) ...NO_BACKSLASH_ESCAPES
também pode introduzir novas vulnerabilidades: stackoverflow.com/a/23277864/1014813Instruções preparadas / consultas parametrizadas são geralmente suficientes para impedir a injeção de 1ª ordem nessa instrução * . Se você usar sql dinâmico desmarcado em qualquer outro lugar do seu aplicativo, ainda estará vulnerável à injeção de 2ª ordem .
A injeção de 2ª ordem significa que os dados foram percorridos no banco de dados uma vez antes de serem incluídos em uma consulta e são muito mais difíceis de executar. AFAIK, você quase nunca vê ataques reais de 2ª ordem de engenharia, pois geralmente é mais fácil para os invasores fazerem a engenharia social, mas às vezes há bugs de segunda ordem surgindo devido a
'
caracteres benignos ou similares.Você pode realizar um ataque de injeção de 2ª ordem quando pode fazer com que um valor seja armazenado em um banco de dados que posteriormente será usado como literal em uma consulta. Como exemplo, digamos que você insira as seguintes informações como seu novo nome de usuário ao criar uma conta em um site (assumindo o MySQL DB para esta pergunta):
Se não houver outras restrições no nome de usuário, uma instrução preparada ainda garantirá que a consulta incorporada acima não seja executada no momento da inserção e armazene o valor corretamente no banco de dados. No entanto, imagine que mais tarde o aplicativo recupere seu nome de usuário do banco de dados e use a concatenação de cadeias para incluir esse valor em uma nova consulta. Você pode ver a senha de outra pessoa. Como os primeiros nomes na tabela de usuários tendem a ser administradores, você também pode ter acabado de ceder o farm. (Observe também: este é mais um motivo para não armazenar senhas em texto sem formatação!)
Vemos, então, que as instruções preparadas são suficientes para uma única consulta, mas, por si só, não são suficientes para se proteger contra ataques de injeção de sql em todo o aplicativo, porque eles não possuem um mecanismo para impor todo o acesso a um banco de dados dentro de um aplicativo. código. No entanto, usado como parte de um bom design de aplicativo - que pode incluir práticas como revisão de código ou análise estática, ou uso de um ORM, camada de dados ou camada de serviço que limita o sql dinâmico - as instruções preparadas são a principal ferramenta para resolver a injeção de sql problema.Se você seguir bons princípios de design de aplicativos, de modo que o acesso a dados seja separado do restante do programa, será fácil impor ou auditar que todas as consultas usem corretamente a parametrização. Nesse caso, a injeção de sql (primeira e segunda ordem) é completamente evitada.
* Acontece que o MySql / PHP é (bem, era) apenas idiota em lidar com parâmetros quando caracteres largos estão envolvidos, e ainda há um caso raro descrito na outra resposta altamente votada aqui que pode permitir que a injeção deslize através de um parâmetro inquerir.
fonte
Não, eles nem sempre são.
Depende se você permite que a entrada do usuário seja colocada na própria consulta. Por exemplo:
estaria vulnerável a injeções de SQL e o uso de instruções preparadas neste exemplo não funcionará, porque a entrada do usuário é usada como um identificador, não como dados. A resposta certa aqui seria usar algum tipo de filtragem / validação como:
Nota: você não pode usar o PDO para vincular dados que ficam fora do DDL (Data Definition Language), ou seja, isso não funciona:
A razão pela qual o exposto acima não funciona é porque
DESC
eASC
não são dados . O PDO pode escapar apenas para dados . Em segundo lugar, você não pode nem colocar'
aspas. A única maneira de permitir a classificação escolhida pelo usuário é filtrar manualmente e verificar se éDESC
ou nãoASC
.fonte
SELECT * FROM 'table'
pode estar errado como deveria estarSELECT * FROM `table`
ou sem barras invertidas. Então, algumas coisas comoORDER BY DESC
ondeDESC
vem o usuário não pode ser simplesmente escapou. Portanto, os cenários práticos são bastante ilimitados.switch
para derivar o nome da tabela.Sim, é suficiente. A maneira como os ataques do tipo injeção funcionam, é conseguir que um intérprete (O banco de dados) avalie algo, que deveria ter sido dados, como se fosse código. Isso só é possível se você misturar código e dados na mesma mídia (por exemplo, quando você constrói uma consulta como uma string).
As consultas parametrizadas funcionam enviando o código e os dados separadamente, para que nunca seja possível encontrar um buraco nisso.
Você ainda pode estar vulnerável a outros ataques do tipo injeção. Por exemplo, se você usar os dados em uma página HTML, poderá estar sujeito a ataques do tipo XSS.
fonte
Não, isso não é suficiente (em alguns casos específicos)! Por padrão, o PDO usa instruções preparadas emuladas ao usar o MySQL como um driver de banco de dados. Você sempre deve desativar instruções preparadas emuladas ao usar o MySQL e o PDO:
Outra coisa que sempre deve ser feita é definir a codificação correta do banco de dados:
Consulte também esta pergunta relacionada: Como posso evitar a injeção de SQL no PHP?
Observe também que isso é apenas sobre o lado do banco de dados das coisas que você ainda precisa observar quando exibir os dados. Por exemplo, usando
htmlspecialchars()
novamente com o estilo correto de codificação e citação.fonte
Pessoalmente, eu sempre executaria alguma forma de saneamento nos dados, pois você nunca pode confiar na entrada do usuário; no entanto, ao usar espaços reservados / ligação de parâmetro, os dados introduzidos são enviados ao servidor separadamente para a instrução sql e depois unidos. A chave aqui é que isso vincula os dados fornecidos a um tipo e uso específicos e elimina qualquer oportunidade de alterar a lógica da instrução SQL.
fonte
Eaven, se você estiver impedindo o front-end de injeção de sql, usando verificações de html ou js, deverá considerar que as verificações de front-end são "ignoráveis".
Você pode desativar o js ou editar um padrão com uma ferramenta de desenvolvimento front-end (incorporada atualmente ao Firefox ou Chrome).
Portanto, para evitar a injeção de SQL, seria correto higienizar o back-end da data de entrada dentro do seu controlador.
Gostaria de sugerir que você use a função PHP nativa filter_input () para limpar os valores GET e INPUT.
Se você quiser ir em frente com segurança, para consultas sensatas ao banco de dados, gostaria de sugerir que você use expressões regulares para validar o formato dos dados. preg_match () irá ajudá-lo neste caso! Mas tome cuidado! O mecanismo Regex não é tão leve. Use-o apenas se necessário, caso contrário, o desempenho do aplicativo diminuirá.
A segurança tem um custo, mas não desperdice seu desempenho!
Exemplo fácil:
se você quiser checar se um valor recebido de GET é um número, menor que 99 se (! preg_match ('/ [0-9] {1,2} /')) {...} é mais pesado de
Portanto, a resposta final é: "Não! As declarações preparadas para DOP não impedem todo tipo de injeção de sql"; Não impede valores inesperados, apenas concatenação inesperada
fonte