Este artigo aponta o seguinte problema de segurança com o MySQL:
USERS
id | email | password_reset_token
1 | [email protected] | QTlXww)uV!Hdg3U6aGwKV2FPvAqzVgPx
2 | [email protected] | CZor5t7WbX#LTeqiG3v@6f3@z#)BfK*n
Aqui temos bons tokens aleatórios que um usuário deve provar que possui para redefinir sua conta. Mas o usuário consegue enviar um token de redefinição de 0
, então executamos esta consulta:
SELECT * FROM `users` WHERE `email` = '[email protected]' AND `password_reset_token` = 0
O MySQL converte o token VARCHAR
em um INT
para fazer a comparação . Ele considera a VARCHAR
que não começa com um número igual a 0. Portanto, um invasor pode corresponder a qualquer string não numérica e assumir o controle da conta .
Mesmo que o token de reinicialização inicie alguns números, a situação não é muito melhor. O MySQL ignora todos os caracteres, exceto os números iniciais, ao fazer essa comparação, portanto, considera 12blahblah3blah4
igual a 12
, o que torna a adivinhação exponencialmente mais fácil.
Posso configurar o MySQL para não fazer esse tipo de typecasting? Se, por exemplo, fosse lançado INT
em VARCHAR
vez de vice-versa, esse ataque não funcionaria.
Observação
Se a consulta for executada com '0'
em vez de 0
, isso não funcionará. O artigo discute essa vulnerabilidade em termos da aceitação de XML do Ruby on Rails, onde uma type=integer
propriedade convenceu o Rails a enviar um inteiro real na consulta.
Esse bug foi corrigido no Rails; agora ele converte todos os parâmetros de solicitação em strings, portanto, nunca criaria uma consulta com um número inteiro. Mas ainda acho que o MySQL deve ser configurável para evitar isso.
O problema, porém, é que você está fornecendo um valor numérico, sem aspas, então o MySQL tenta converter o outro lado da comparação como um
DOUBLE
, para compará-lo com o argumento numérico que você forneceu ... o que deixa você com um 0 isso é equivalente a 0.Se você fornecer o 0 em um contexto de string, isso será avaliado como falso.
Portanto, uma opção que você poderia usar para evitar esse comportamento seria converter seu valor de entrada como um
CHAR
... o que, na maioria das vezes, já é ... mas, se não, será avaliado conforme o esperado.update , para responder mais completamente à pergunta:
Não, não parece haver nenhuma maneira de evitar isso por meio de configuração - apenas por meio de um elenco explícito de sua string de aparência numérica potencial - para uma string, que você disse que não pode ser realizada desde que você está usando um ORM. O local mais provável, se existisse algo assim, seria SQL Server Mode , e nenhuma dessas opções modifica esse comportamento quando se trata de comparações.
Há um pouco de discussão sobre conversões implícitas como esta no Bug #63112 :
Observe que o escopo deste relatório de bug não é estritamente limitado a conversão implícita em contexto externo, e a menção
UUID()
aqui é um pouco distrativa, poisUUID()
pode retornar strings começando com dígitos e converter para outros números ... mas acho que serve para confirmar que não existe alternativa, pois nada é apresentado além de "não use tipos diferentes para comparação se quiser evitar um elenco", que já sabemos. Isto continua:Em relação à sua sugestão de que, se a conversão implícita for feita, o número deve se tornar uma string em vez da string se tornar um número ... Posso ver seu ponto, até certo ponto, para comparações de igualdade estrita ... mas há muitos outros operadores além
=
e usando essa lógica, surge toda uma nova série de problemas...Correto! A string '100' é "menor que" a string '2' em sentido alfabético... da mesma forma que a string 'AXX' é "menor que" a string 'B'. Portanto, essa alternativa certamente tem suas desvantagens.
Existe, no entanto, uma terceira alternativa, que parece ser a lógica mais precisa (embora eu aceite uma objeção construtiva): Se uma string que começa com um caractere não-dígito é convertida para a
DOUBLE
(ou qualquer outro tipo numérico, nesse caso ), parece que o resultado mais correto dessa conversão seria NULL .Infelizmente, esta não é a abordagem do MySQL. Parece que isso pode ser corrigido na fonte, possivelmente
sql/field.cc
onde o '0' retornado por my_strntod () parece ser simplesmente retornado ao chamador após lançar o aviso se ocorreu um erro, mas tentando tal hack tão profundo na fonte está além da minha especialidade.Primeiro, se sua consulta final estava sendo gerada dessa forma, parece implicar em pegar a entrada direta do usuário e colá-la em um modelo de consulta, o que em si é o problema da injeção de SQL. Além disso, mesmo se você estivesse fazendo isso, o modelo de consulta precisaria estar entre aspas para que as solicitações legítimas de entradas charish fossem sintaticamente válidas.
Qualquer que seja a linguagem que seu aplicativo esteja usando, ruby ou não, procure como eles fazem consultas parametrizadas e instruções preparadas. A API cuidará de todo o escape adequado para você.