Pergunta

Vamos dizer que eu tenho 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 DOP diz:

Os parâmetros para instruções preparadas não precisam ser citados; as alças motorista lo para você.

É que realmente tudo necessidade I fazer para injeções evitar SQL? É realmente assim tão fácil?

Você pode assumir MySQL se ele faz a diferença. Além disso, estou realmente apenas curioso sobre o uso de declarações preparadas contra injeção de SQL. Neste contexto, eu não me importo com XSS ou outras vulnerabilidades possíveis.

Foi útil?

Solução

A resposta curta é NÃO , DOP prepara não vai defendê-lo de todos os possíveis ataques de injeção de SQL. Para certos borda casos obscuros.

Estou adaptando esta resposta para falar sobre DOP ...

A resposta longa não é tão fácil. É baseado um ataque demonstrado aqui .

O Ataque

Então, vamos começar por mostrar o ataque ...

$pdo->query('SET NAMES gbk');
$var = "\xbf\x27 OR 1=1 /*";
$query = 'SELECT * FROM test WHERE name = ? LIMIT 1';
$stmt = $pdo->prepare($query);
$stmt->execute(array($var));

Em certas circunstâncias, que irá retornar mais de 1 linha. Vamos dissecar o que está acontecendo aqui:

  1. Selecionar um conjunto de caracteres

    $pdo->query('SET NAMES gbk');
    

    Para este ataque ao trabalho, precisamos da codificação que o servidor está esperando na conexão tanto para codificar ' como em ASCII ou seja 0x27 e ter algum personagem cujo byte final é um ASCII \ ie 0x5c . Como se vê, há 5 tais codificações suportadas no MySQL 5.6 por padrão: big5, cp932, gb2312, gbk e sjis. Vamos selecionar gbk aqui.

    Agora, é muito importante observar o uso de SET NAMES aqui. Isso define o conjunto de caracteres no servidor . Não há outra maneira de fazê-lo, mas nós vamos chegar lá em breve.

  2. O Payload

    A carga útil que vamos utilizar para esta injeção começa com o 0xbf27 sequência de bytes. Em gbk, que é um personagem multibyte inválido; em latin1, é o ¿' string. Note-se que em latin1 e gbk, 0x27 por si só é um personagem ' literal.

    Nós escolhemos esta carga útil porque, se nós chamamos addslashes() sobre ele, nós inserir um ASCII \ ou seja 0x5c, antes do caractere '. Por isso, ia acabar com 0xbf5c27, que em gbk é uma seqüência de dois caracteres: 0xbf5c seguido por 0x27. Ou, em outras palavras, um válido caracteres seguido por um ' unescaped. Mas nós não estamos usando addslashes(). Assim, para a próxima etapa ...

  3. $ stmt-> execute ()

    O que é importante perceber aqui é que PDO por padrão não faz verdadeiras declarações preparadas . Ele emula-los (para MySQL). Portanto, DOP constrói internamente a string de consulta, chamando mysql_real_escape_string() (a função API MySQL C) em cada valor da cadeia ligada.

    A chamada C API para difere mysql_real_escape_string() de addslashes() na medida em que conhece o conjunto de caracteres de conexão. Assim, ele pode executar a escapar corretamente para o conjunto de caracteres que o servidor está esperando. No entanto, até este ponto, o cliente pensa que ainda estamos usando latin1 para a conexão, porque nós nunca disse o contrário. Fizemos dizer ao servidor estamos usando gbk, mas o cliente ainda acha que é latin1.

    Portanto, a chamada para mysql_real_escape_string() insere a barra invertida, e nós temos um personagem livre ' pendurado no nosso "escapou" conteúdo! Na verdade, se estivéssemos a olhar para $var no conjunto de caracteres gbk, veríamos:

    縗' OR 1=1 /*

    O que é exatamente o que o ataque requer.

  4. A consulta

    Esta parte é apenas uma formalidade, mas aqui está a consulta prestados:

    SELECT * FROM test WHERE name = '縗' OR 1=1 /*' LIMIT 1
    

Parabéns, você acabou com sucesso atacaram um programa usando DOP declarações preparadas ...

A simples correção

Agora, é importante notar que você pode evitar isso, desativando st emulado preparadoatements:

$pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);

Esta vontade normalmente resultado em uma declaração preparada verdadeira (ou seja, os dados que estão sendo enviados através de um pacote separado a partir da consulta). No entanto, estar ciente de que DOP silenciosamente fallback para emular declarações que o MySQL não pode se preparar de forma nativa: aqueles que podem são listados no manual, mas cuidado para selecionar a versão do servidor apropriado).

a correção correta

O problema aqui é que nós não chamar mysql_set_charset() o C da API em vez de SET NAMES. Se o fizéssemos, estaríamos multa prevista estamos usando uma versão MySQL desde 2006.

Se você estiver usando uma versão do MySQL mais cedo, em seguida, um bug em 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 escapando mesmo se o cliente tinha sido correctamente informado sobre a codificação de conexão e por isso este ataque ainda teria sucesso. O bug foi corrigido no MySQL 4.1.20 , 5.0.22 5.1.11 .

Mas a pior parte é que PDO não expor a API C para mysql_set_charset() até 5.3.6, por isso em versões anteriores que não pode evitar este ataque para cada comando possível! É agora exposto como um DSN parâmetro , que deve ser usado em vez de SET NAMES ...

O Saving Grace

Como dissemos no início, para este ataque funcione a conexão banco de dados deve ser codificados usando um conjunto de caracteres vulnerável. utf8mb4 é não vulneráveis ?? e ainda pode suportar todas de caracteres Unicode: assim que você poderia optar por usar que em vez, mas foi apenas disponível desde o MySQL 5.5.3. Uma alternativa é utf8 , que também é não é vulnerável e pode suportar todo o Unicode Basic Multilingual Plane .

Como alternativa, você pode habilitar o href="http://dev.mysql.com/doc/en/sql-mode.html#sqlmode_no_backslash_escapes" rel="noreferrer"> NO_BACKSLASH_ESCAPES modo SQL , que (entre outras coisas) altera o funcionamento de mysql_real_escape_string(). Com este modo ativado, 0x27 será substituído por 0x2727 em vez de 0x5c27 e, assim, o processo de escapar não pode criar caracteres válidos em qualquer uma das codificações vulneráveis ??onde eles não existiam anteriormente (ie 0xbf27 ainda é 0xbf27 etc. ) -então o servidor ainda irá rejeitar a cadeia como inválido. No entanto, consulte de @ eggyal resposta para uma vulnerabilidade diferente que podem surgir de usar este modo SQL (embora não com DOP).

Exemplos seguro

Os exemplos a seguir são seguros:

mysql_query('SET NAMES utf8');
$var = mysql_real_escape_string("\xbf\x27 OR 1=1 /*");
mysql_query("SELECT * FROM test WHERE name = '$var' LIMIT 1");

Como o servidor está esperando utf8 ...

mysql_set_charset('gbk');
$var = mysql_real_escape_string("\xbf\x27 OR 1=1 /*");
mysql_query("SELECT * FROM test WHERE name = '$var' LIMIT 1");

Porque nós definir corretamente o conjunto de caracteres para que o cliente eo jogo servidor.

$pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
$pdo->query('SET NAMES gbk');
$stmt = $pdo->prepare('SELECT * FROM test WHERE name = ? LIMIT 1');
$stmt->execute(array("\xbf\x27 OR 1=1 /*"));

Porque nós desligado declarações preparadas emulados.

$pdo = new PDO('mysql:host=localhost;dbname=testdb;charset=gbk', $user, $password);
$stmt = $pdo->prepare('SELECT * FROM test WHERE name = ? LIMIT 1');
$stmt->execute(array("\xbf\x27 OR 1=1 /*"));

Porque nós definimos o caractereActer definido corretamente.

$mysqli->query('SET NAMES gbk');
$stmt = $mysqli->prepare('SELECT * FROM test WHERE name = ? LIMIT 1');
$param = "\xbf\x27 OR 1=1 /*";
$stmt->bind_param('s', $param);
$stmt->execute();

Porque MySQLi faz verdadeiras declarações preparadas o tempo todo.

Wrapping Up

Se você:

  • Versões uso moderno do MySQL (final de 5,1, todos 5.5, 5.6, etc) e parâmetro DSN charset do DOP (em PHP = 5.3.6)

ou

  • Não use um conjunto de caracteres vulneráveis ??para a codificação de conexão (você só pode usar utf8 / latin1 / ascii / etc)

ou

  • Ative o modo SQL NO_BACKSLASH_ESCAPES

Você está 100% seguro.

Caso contrário, você está vulneráveis ?? mesmo que você está usando DOP elaborou demonstrações ...

Adenda

Eu tenho lentamente trabalhando em um patch para alterar o padrão para não emular prepara para uma futura versão do PHP. O problema que eu estou correndo em é que um monte de testes quebrar quando eu faço isso. Um problema é que emulado prepara só vai jogar erros de sintaxe em execução, mas é verdade prepara irá lançar erros na preparação. De modo que pode causar problemas (e é parte da razão testes são Borking).

Outras dicas

declarações preparadas / consultas parametrizadas são geralmente suficiente para evitar 1ª ordem injeção em que a declaração * . Se você usar desmarcado sql dinâmico em qualquer outro lugar em sua aplicação você ainda são vulneráveis ??a 2ª ordem injeção.

2ª ordem dados meio de injeção foi ciclada através do banco de dados uma vez antes de ser incluído em uma consulta, e é muito mais difícil de retirar. AFAIK, você quase nunca vê verdadeira engenharia 2ª ataques ordem, como é geralmente mais fácil para os atacantes sócio-engenheiro seu caminho, mas às vezes você tem 2º erros ordem surgir por causa da extras benigna personagens ' ou similar.

Você pode realizar um segundo ataque de injeção de ordem quando você pode causar um valor a ser armazenado em um banco de dados que é usado mais tarde como um literal em uma consulta. Como exemplo, digamos que você insira as seguintes informações como o seu novo nome de usuário ao criar uma conta em um site (assumindo MySQL DB para esta pergunta):

' + (SELECT UserName + '_' + Password FROM Users LIMIT 1) + '

Se não há outras restrições sobre o nome de utilizador, uma declaração preparada ainda certificar-se de que a consulta acima incorporado não é executado no momento da inserção, e armazenar o valor corretamente no banco de dados. No entanto, imaginar que mais tarde o aplicativo recupera seu nome de usuário do banco de dados, e usa concatenação para incluir esse valor uma nova consulta. Você pode começar a ver a senha de outra pessoa. Desde os primeiros nomes na tabela de usuários tendem a ser os administradores, você também pode ter acabado de dar afastado a fazenda. (Note também: esta é mais uma razão para não armazenar senhas em texto simples)

Vemos, então, que as declarações preparadas são suficientes para uma única consulta, mas por si só são não suficiente para proteger contra ataques de injeção SQL em um aplicativo inteiro, porque eles não têm um mecanismo para impor que todo o acesso a um banco de dados dentro do aplicativo usa o código seguro. No entanto, usado como parte de um bom design do aplicativo - que pode incluir práticas como a revisão de código ou análise estática, ou o uso de um ORM, camada de dados, ou camada de serviço que os limites SQL dinâmico - declarações preparadas a principal ferramenta para resolver o problema de injeção SQL. Se você seguir bons princípios de aplicação, de modo que o seu acesso a dados é separado do resto da sua programa, torna-se fácil de aplicar ou auditoria que cada consulta usa corretamente parametrização. Neste caso, a injecção SQL (ambas primeira e segunda ordem) é completamente evitada.


* Acontece que o MySQL / PHP são (ok, foram) apenas mudo sobre a manipulação de parâmetros quando caracteres largos estão envolvidos, e ainda há um raras caso descrito no outro altamente votou resposta aqui que pode permitir a injeção de deslizamento através de uma consulta parametrizada.

Não, eles não são sempre.

Depende se você permitir a entrada do usuário para ser colocado dentro da própria consulta. Por exemplo:

$dbh = new PDO("blahblah");

$tableToUse = $_GET['userTable'];

$stmt = $dbh->prepare('SELECT * FROM ' . $tableToUse . ' where username = :username');
$stmt->execute( array(':username' => $_REQUEST['username']) );

seria vulnerável a injeções SQL e usando instruções preparadas neste exemplo não vai funcionar, porque a entrada do usuário é usado como um identificador, não como dados. A resposta aqui seria usar algum tipo de filtragem / validação como:

$dbh = new PDO("blahblah");

$tableToUse = $_GET['userTable'];
$allowedTables = array('users','admins','moderators');
if (!in_array($tableToUse,$allowedTables))    
 $tableToUse = 'users';

$stmt = $dbh->prepare('SELECT * FROM ' . $tableToUse . ' where username = :username');
$stmt->execute( array(':username' => $_REQUEST['username']) );

Nota: você não pode usar PDO para os dados de ligação que vai fora de DDL (Data Definition Language), ou seja, isso não funciona:

$stmt = $dbh->prepare('SELECT * FROM foo ORDER BY :userSuppliedData');

A razão pela qual o acima não funciona é porque DESC e ASC não são dados . DOP só pode escapar para o dados . Em segundo lugar, você não pode sequer colocar aspas ' em torno dele. A única maneira de permitir que o usuário escolheu a classificação é para filtrar manualmente e verifique se ele é ou DESC ou ASC.

Sim, é suficiente. A forma como ataques do tipo injeção de trabalho, é de alguma forma obter um intérprete (o banco de dados) para avaliar alguma coisa, que deveria ter sido dados, como se fosse código. Isso só é possível se você misturar código e dados no mesmo meio (Ex. Quando você construir uma consulta como uma string).

parametrizado consultas funcionam enviando o código e os dados separadamente, de modo que seria não ser possível encontrar um buraco em que.

Você ainda pode estar vulnerável a outros ataques do tipo injeção embora. Por exemplo, se você usar os dados em uma página HTML, você pode estar sujeito a ataques do tipo XSS.

Sem isso não é suficiente (em alguns casos específicos)! Por padrão usos DOP emulado instruções preparadas quando usando o MySQL como um driver de banco de dados. Você deve sempre desativar emulado instruções preparadas quando usando o MySQL e DOP:

$dbh->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);

Outra coisa que sempre deve ser feito definir a codificação correta do banco de dados:

$dbh = new PDO('mysql:dbname=dbtest;host=127.0.0.1;charset=utf8', 'user', 'pass');

Veja também esta questão relacionada: Como posso evitar SQL injeção em PHP?

Além disso, note que isso só é sobre o lado do banco de dados das coisas que você ainda tem que prestar atenção a si mesmo ao exibir os dados. Por exemplo. usando htmlspecialchars() novamente com a codificação correta e citando estilo.

Pessoalmente eu sempre executar alguma forma de saneamento sobre os dados pela primeira vez como você pode entrada usuário nunca confiança, no entanto, quando usando marcadores de posição / ligação de parâmetro os dados introduzidos são enviados para o servidor separadamente para a instrução SQL e, em seguida, binded juntos. A chave aqui é que esta liga os dados fornecidos para um tipo específico e uma utilização específica e elimina qualquer oportunidade de mudar a lógica da instrução SQL.

eaven se você estiver indo para evitar sql injeção de front-end, usando HTML ou js cheques, você tem que considerar que as verificações de front-end são "bypassable".

Você pode desativar js ou editar um padrão com uma ferramenta de desenvolvimento de front-end (construído com Firefox ou o Chrome hoje em dia).

Assim, a fim de evitar a injeção de SQL, seria ideal para higienizar data de entrada backend dentro de seu controlador.

Eu gostaria de sugerir-lhe para usar filter_input () função PHP nativa, a fim de valores sanitize GET e INPUT.

Se você quiser ir em frente com segurança, para consultas de banco de dados sensíveis, eu gostaria de sugerir-lhe para usar a expressão regular para o formato de dados de validação. preg_match () irá ajudá-lo neste caso! Mas tome cuidado! motor de regex não é tão leve. Use-o apenas se necessário, caso contrário, suas performances de aplicação irá diminuir.

segurança tem um custo, mas não perca seu desempenho!

Fácil exemplo:

Se você quiser verifique se um valor, recebido do GET é um número, a menos de 99 if (preg_match! ( '/ [0-9] {1,2} /')) {...} é heavyer de

if (isset($value) && intval($value)) <99) {...}

Assim, a resposta final é: "Será que instruções preparadas Sem DOP não evitar todo o tipo de injeção de SQL!"; Ela não impede que valores inesperados, apenas concatenação inesperado

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top