Injeção de SQL que contorna mysql_real_escape_string ()

644

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?

Richard Knop
fonte
34
@ThiefMaster - Eu prefiro não dar detalhado erros como usuário inválido / senha inválida ... ele diz comerciantes de força bruta que eles têm um ID de usuário válido, e é apenas a senha que precisa adivinhar
Mark Baker
18
É horrível do ponto de vista da usabilidade. Às vezes, você não pode usar seu apelido / nome de usuário / endereço de e-mail principal e esquecer isso depois de algum tempo ou o site exclui sua conta por inatividade. Então, é extremamente irritante se você continuar tentando senhas e talvez até bloquear seu IP, mesmo que seja apenas seu nome de usuário inválido.
ThiefMaster
50
Por favor, não use 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 .
tereško 3/12/12
13
@machineaddict, desde 5.5 (lançado recentemente) as mysql_*funções já produzem E_DEPRECATEDaviso. A ext/mysqlextensão não é mantida por mais de 10 anos. Você é realmente tão ilusório?
tereško
13
@machineaddict Acabaram de remover essa extensão no PHP 7.0 e ainda não é 2050.
GGG

Respostas:

379

Considere a seguinte consulta:

$iId = mysql_real_escape_string("1 OR 1=1");    
$sSql = "SELECT * FROM table WHERE id = $iId";

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:

$iId = (int)"1 OR 1=1";
$sSql = "SELECT * FROM table WHERE id = $iId";
Wesley van Opdorp
fonte
9
Mas isso não seria um problema real, porque mysql_query()não executa várias instruções, não?
Pekka
11
@ Pekka, Embora o exemplo usual seja DROP TABLE, na prática o atacante é mais provável SELECT passwd FROM users. Neste último caso, a segunda consulta geralmente é executada pelo uso de uma UNIONcláusula.
Jacco 21/05
58
(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 entrada
Zerkms 24/07/12
28
Isso é mais um mau uso da função do que qualquer outra coisa. Afinal, é nomeado mysql_real_escape_string, não mysql_real_escape_integer. Não é para ser usado com campos inteiros.
NullUserException 9/10/12
11
@ircmaxell, mas a resposta é totalmente enganosa. Obviamente, a pergunta está perguntando sobre o conteúdo entre aspas. "As cotações não estão lá" não é a resposta para esta pergunta.
Pacerier
629

A resposta curta é sim, sim, há uma maneira de se locomovermysql_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 ...

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

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

  1. Selecionando um conjunto de caracteres

    mysql_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 no ie ASCII 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, gbke sjis. Vamos selecionar gbkaqui.

    Agora, é muito importante observar o uso SET NAMESdaqui. Isso define o conjunto de caracteres NO SERVIDOR . Se usássemos a chamada para a função da API C mysql_set_charset(), estaríamos bem (nas versões do MySQL desde 2006). Mas mais sobre por que em um minuto ...

  2. A carga útil

    A carga útil que vamos usar para esta injeção começa com a sequência de bytes 0xbf27. Em gbk, esse é um caractere multibyte inválido; dentro latin1, é a corda ¿'. Observe que em latin1 e gbk , 0x27por si só, é um 'caractere literal .

    Escolhemos essa carga útil, porque, se addslashes()a utilizássemos, inseriríamos um ASCII , \ou seja 0x5c, antes do 'caractere. Então, terminamos com 0xbf5c27, que gbké uma sequência de dois caracteres: 0xbf5cseguida por 0x27. Ou, em outras palavras, um caractere válido seguido por um sem escape '. Mas não estamos usando addslashes(). Então, para o próximo passo ...

  3. mysql_real_escape_string ()

    A chamada da API C mysql_real_escape_string()é diferente por addslashes()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 usando latin1a conexão, porque nunca dissemos o contrário. Dissemos ao servidor que estamos usando gbk, 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 $varno gbkconjunto de caracteres, veríamos:

    OR 'OR 1 = 1 / *

    Qual é exatamente o que o ataque exige.

  4. A pergunta

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

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

Parabéns, você acabou de atacar com sucesso um programa usando mysql_real_escape_string()...

O mal

Fica pior. PDOO padrão é emular instruções preparadas com o MySQL. Isso significa que, no lado do cliente, ele basicamente faz um sprintf mysql_real_escape_string()(na biblioteca C), o que significa que o seguinte resultará em uma injeção bem-sucedida:

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

Agora, é importante notar que você pode evitar isso desativando as instruções preparadas emuladas:

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

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 de SET 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 PDOnão expusemos a API C mysql_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ãoutf8mb4 é 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 é utf8que 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_ESCAPESmodo SQL, que (entre outras coisas) altera a operação do mysql_real_escape_string(). Com esse modo ativado, 0x27será substituído por em 0x2727vez de 0x5c27e, 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 (ou 0xbf27seja, ainda é 0xbf27etc.) - 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:

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");

Porque 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 definimos corretamente o conjunto de caracteres para que o cliente e o servidor correspondam.

$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 desativamos as instruções preparadas emuladas.

$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 definimos o conjunto de caracteres 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 o MySQLi faz verdadeiras declarações preparadas o tempo todo.

Empacotando

Se vocês:

  • Use Versões Modernas do MySQL (final de 5.1, todas as 5.5, 5.6, etc.) Parâmetro de charset DSN do AND mysql_set_charset() / $mysqli->set_charset()/ PDO (no PHP ≥ 5.3.6)

OU

  • Não use um conjunto de caracteres vulneráveis ​​para codificação de conexão (você usa apenas utf8/ latin1/ ascii/ etc)

Você é 100% seguro.

Caso contrário, você estará vulnerável mesmo usandomysql_real_escape_string() ...

ircmaxell
fonte
3
O DOP emulando instruções de preparação para o MySQL, sério? Não vejo nenhuma razão para isso, já que o driver suporta nativamente. Não?
Netcoder
16
Faz. Dizem na documentação que não. Mas no código fonte, é claramente visível e fácil de corrigir. Eu atribuo isso à incompetência dos desenvolvedores.
Theodore R. Smith
5
@ TheodoreR.Smith: Não é tão fácil de consertar. Estou trabalhando para alterar o padrão, mas ele falha em uma carga de testes quando alternado. Portanto, é uma mudança maior do que parece. Eu ainda estou esperando para tê-lo concluído até 5.5 ...
ircmaxell
14
@shadyyx: Não, a vulnerabilidade descrita no artigo 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 ...
ircmaxell
5
@shadyyx: Quanto a passar tanta graça em $ _GET ... ?var=%BF%27+OR+1=1+%2F%2Ana URL, $var = $_GET['var'];no código, e Bob é seu tio.
cHao 27/12/12
183

TL; DR

mysql_real_escape_string()não fornecerá nenhuma proteção (além disso, poderá prejudicar seus dados) se:

  • O NO_BACKSLASH_ESCAPESmodo SQL do MySQL está ativado (o que pode ser, a menos que você selecione explicitamente outro modo SQL sempre que conectar ); e

  • seus literais de seqüência de caracteres SQL são citados usando "caracteres de aspas duplas .

Isso foi registrado como bug # 72458 e foi corrigido no MySQL v5.7.6 (consulte a seção " The Saving Grace ", abaixo).

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 ...

mysql_query('SET SQL_MODE="NO_BACKSLASH_ESCAPES"'); // could already be set
$var = mysql_real_escape_string('" OR 1=1 -- ');
mysql_query('SELECT * FROM test WHERE name = "'.$var.'" LIMIT 1');

Isso retornará todos os registros da testtabela. Uma dissecção:

  1. Selecionando um modo SQL

    mysql_query('SET SQL_MODE="NO_BACKSLASH_ESCAPES"');

    Conforme documentado em Literais de String :

    Existem várias maneiras de incluir caracteres de aspas em uma string:

    • Um " '" dentro de uma sequência citada com " '" pode ser escrito como " ''".

    • Um " "" dentro de uma sequência citada com " "" pode ser escrito como " """.

    • Preceda o caractere de citação com um caractere de escape (“ \”).

    • Um " '" dentro de uma string citada com " "" não precisa de tratamento especial e não precisa ser duplicado ou escapado. Da mesma forma, “ "” dentro de uma sequência citada com “ '” não precisa de tratamento especial.

    Se o modo SQL do servidor incluir NO_BACKSLASH_ESCAPES, a terceira dessas opções - que é a abordagem usual adotada por mysql_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.

  2. A carga útil

    " OR 1=1 -- 

    A carga útil inicia essa injeção literalmente com o "personagem. Nenhuma codificação específica. Sem caracteres especiais. Sem bytes estranhos.

  3. mysql_real_escape_string ()

    $var = mysql_real_escape_string('" OR 1=1 -- ');

    Felizmente, mysql_real_escape_string()verifica o modo SQL e ajusta seu comportamento de acordo. Veja libmysql.c:

    ulong STDCALL
    mysql_real_escape_string(MYSQL *mysql, char *to,const char *from,
                 ulong length)
    {
      if (mysql->server_status & SERVER_STATUS_NO_BACKSLASH_ESCAPES)
        return escape_quotes_for_mysql(mysql->charset, to, 0, from, length);
      return escape_string_for_mysql(mysql->charset, to, 0, from, length);
    }

    Portanto, uma função subjacente diferente escape_quotes_for_mysql(), é invocada se o NO_BACKSLASH_ESCAPESmodo 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 . Veja charset.c:

    /*
      Escape apostrophes by doubling them up
    
    // [ deletia 839-845 ]
    
      DESCRIPTION
        This escapes the contents of a string by doubling up any apostrophes that
        it contains. This is used when the NO_BACKSLASH_ESCAPES SQL_MODE is in
        effect on the server.
    
    // [ deletia 852-858 ]
    */
    
    size_t escape_quotes_for_mysql(CHARSET_INFO *charset_info,
                                   char *to, size_t to_length,
                                   const char *from, size_t length)
    {
    // [ deletia 865-892 ]
    
        if (*from == '\'')
        {
          if (to + 2 > to_end)
          {
            overflow= TRUE;
            break;
          }
          *to++= '\'';
          *to++= '\'';
        }

    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 $varpermanece exatamente o mesmo que o argumento de que foi fornecido a mysql_real_escape_string()-é como se há como escapar teve lugar em tudo .

  4. A pergunta

    mysql_query('SELECT * FROM test WHERE name = "'.$var.'" LIMIT 1');

    Algo de formalidade, a consulta renderizada é:

    SELECT * FROM test WHERE name = "" OR 1=1 -- " LIMIT 1

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 pode mysqli::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, no NO_BACKSLASH_ESCAPESmodo, 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_ESCAPESpode 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 como addslashes().

Além disso, o modo SQL de uma nova conexão é definido pelo servidor de acordo com sua configuração (que um SUPERusuá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_ESCAPESou cite literais de string do MySQL usando o caractere de aspas simples, esse bug não poderá criar sua cabeça feia: respectivamente escape_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_ESCAPEStambém ative o ANSI_QUOTESmodo, 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 chamam mysql_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 :

Funcionalidade adicionada ou alterada

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) :

mysql_set_charset($charset);
mysql_query("SET SQL_MODE=''");
$var = mysql_real_escape_string('" OR 1=1 /*');
mysql_query('SELECT * FROM test WHERE name = "'.$var.'" LIMIT 1');

... porque selecionamos explicitamente um modo SQL que não inclui NO_BACKSLASH_ESCAPES.

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

... porque estamos citando nossa string literal com aspas simples.

$stmt = $pdo->prepare('SELECT * FROM test WHERE name = ? LIMIT 1');
$stmt->execute(["' OR 1=1 /*"]);

... 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) .

$var  = $pdo->quote("' OR 1=1 /*");
$stmt = $pdo->query("SELECT * FROM test WHERE name = $var LIMIT 1");

... 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.

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

... porque as instruções preparadas do MySQLi são seguras.

Empacotando

Assim, se você:

  • use instruções preparadas nativas

OU

  • use MySQL v5.7.6 ou posterior

OU

  • em Além de empregar uma das soluções em resumo ircmaxell de, o uso de pelo menos um dos seguintes:

    • DOP;
    • literais de string com aspas simples; ou
    • um modo SQL explicitamente definido que não inclui NO_BACKSLASH_ESCAPES

... então você deve estar completamente seguro (vulnerabilidades fora do escopo da cadeia de caracteres que escapam).

eggyal
fonte
10
Portanto, TL; DR seria como "existe um modo de servidor mysql NO_BACKSLASH_ESCAPES que pode causar uma injeção se você não estiver usando aspas simples.
Seu senso comum
Não consigo acessar bugs.mysql.com/bug.php?id=72458 ; Acabei de receber uma página de acesso negado. Está sendo escondido do público devido a um problema de segurança? Além disso, entendo corretamente a partir desta resposta que você é o descobridor da vulnerabilidade? Se sim, parabéns.
Mark Amery
1
As pessoas não deveriam estar usando "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 incluir ANSI_QUOTESno modo de corrigir o quebrantamento citando O desrespeito aberto de normas, porém, é um problema maior que pode exigir medidas mais severas..)
Chao
2
@ DanAllen: minha resposta foi um pouco mais ampla, pois você pode evitar esse bug específico através da 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.
eggyal
1
@eggyall: Nosso sistema conta com o segundo exemplo seguro acima. Existem erros em que o mysql_real_escape_string foi omitido. Corrigir os que estão no modo de emergência parece ser o caminho prudente, esperando que não fiquemos nuked antes das correções. Minha lógica é converter para declarações preparadas, será um processo muito mais longo que terá que vir depois. A razão pela qual as instruções preparadas é mais segura é o fato de que os erros não criam vulnerabilidades? Em outras palavras, o segundo exemplo corretamente implementado é tão seguro quanto as instruções preparadas?
DanAllen
18

Bem, não há realmente nada que possa passar por isso, a não ser %curinga. Poderia ser perigoso se você estivesse usando a LIKEdeclaraçã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);

Slava
fonte
2
+1, mas os curingas são para a cláusula LIKE, não para uma igualdade simples.
Dor
7
Em que medida você considera uma substituição simples do more efficientque 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.)
MatBailie
7
@Slava: você está efetivamente limitando nomes de usuário e senhas apenas a caracteres de palavras. A maioria das pessoas que sabe alguma coisa sobre segurança consideraria uma má ideia, pois diminui consideravelmente o espaço de pesquisa. É claro que eles também consideram uma má idéia armazenar senhas de texto não criptografado no banco de dados, mas não precisamos estar agravando o problema. :)
Chao
2
@cHao, minha sugestão diz respeito apenas a logins. Obviamente, você não precisa filtrar senhas. Desculpe, isso não está claramente indicado na minha resposta. Mas na verdade isso pode ser uma boa ideia. Usar "espaço de árvore ignorante em pedra" em vez de difícil de lembrar e digitar "a4üua3! @V \" ä90; 8f "seria muito mais difícil de usar. Mesmo usando um dicionário de, diga 3000 palavras para ajudá-lo, sabendo você usou exatamente 4 palavras - que ainda seria cerca de 3,3 * 10 ^ 12 combinações :).
Slava
2
@Slava: Eu já vi essa ideia antes; consulte xkcd.com/936 . O problema é que a matemática não é suficiente. Seu exemplo de senha de 17 caracteres teria 96 ^ 17 possibilidades, e isso se você esquecer os tremas e se limitar a ASCII imprimível. Isso é cerca de 4,5x10 ^ 33. Estamos falando literalmente um bilhão de trilhões de vezes mais de trabalho com força bruta. Mesmo uma senha ASCII de 8 caracteres teria 7,2x10 x 15 possibilidades - 3 mil vezes mais.
precisa saber é