+1

Evitando SQL Injection com Type Cast no PHP

criado por Ibrahim S. M. Brumate em 19/01/2011 4:52pm
Desenvolvi uma função que desagrada aos "boas praticas" de plantão pelo
fato de que ela se aproveita das aspas e do MySQL para retornar erros.
Porém eu vejo diferente!(A quem diga que diferente é um eufemismo para cada um no seu quadrado rss).

Eu não fiz um script para que a query atribua aspa simples onde se deve ter um número inteiro. Fiz um script para que o PHP interprete o tipo da variável enviada via GET ou POST e force sua tipagem. Ou seja se o valor enviado pelo formulário for uma string o retorno é uma 'string'. Se for um double ou integer o valor é um X.X ou X (onde X pode ser um número entre 0 e 9) e assim por diante.

Desta forma você trata o valor enviado contra injections tipando-os da forma correta. Abaixo vou dar exemplos de query com a minha função.

$sql = "SELECT * FROM tabela WHERE nome = {$nome} AND id = {$id}";

Vejam que no exemplo acima onde nome é uma string eu não coloquei aspa simples.
Isto porque quem vai fazer isto por mim é a função antiInjection que força a tipagem de valores fazendo com que toda string contenha aspas.
Sendo assim usando a minha função o resultado da query seria este:

$sql = "SELECT * FROM tb WHERE nome = 'Ibrahim' AND id = 1";

"Ok mas e no caso do 0 UNION que foi seu exemplo de injection para valores numéricos?"

Como estamos falando de forçar tipagem, podemos ver que 0 UNION é uma string e não um inteiro. Portanto com a minha função ficaria da seguinte forma:

$sql = "SELECT * FROM tb WHERE nome = 'Ibrahim' AND id = '0 UNION ...'";

Ai é que a briga é feia. Quem usa JAVA por exemplo diz que o correto é validar o tipo do valor informado na variável antes de chegar a query.
Eu digo que isso pode ser feito também forçando a tipagem de valores enviados ao invés de validar. Assim o MySQL que já tem as tipagens como elas devem ser vão fazer esta validação retornando o erro.

Por isso eu disse que o MySQL poderia nos ajudar com a responsabilidade
de tratar os dados. Veja que desta forma não sou obrigado a dizer via PHP qual o tipo de cada variável, ganhando tempo ao adicionar novos campos a base de dados e formulário. Tipando a mão toda vez que surge um novo campo eu tenho também que informar a função o tipo do valor que foi adicionado.

Tratando com a minha função, com apenas um parâmetro é possível forçar a tipagem dos valores enviados via POST ou GET obtendo assim Int's, Float's, Array's Tipados e String's sem problemas. Esta é a minha idéia que aproveita então a automaticidade do PHP para resolver os problemas de Injection.

Agora chega de mais delongas e vamos a função:

<?php
function antiInjection ($var,$q='') {
    //Verifica se o parâmetro é um array
    if (!is_array($var)) {
        //identifico o tipo da variável e trato a string
        switch (gettype($var)) {
            case 'double':
            case 'integer':
                $return = $var;
                break;
            case 'string':
                /*Verifico quantas vírgulas tem na string.
                  Se for mais de uma trato como string normal,
                  caso contrário trato como String Numérica*/
                $temp = (substr_count($var,',')==1) ? str_replace(',','*;*',$var) : $var;
                //aqui eu verifico se existe valor para não adicionar aspas desnecessariamente    
                if (!empty($temp)) {
                    if (is_numeric(str_replace('*;*','.',$temp))) {
                        $temp = str_replace('*;*','.',$temp);
                        $return = strstr($temp,'.') ? floatval($temp) : intval($temp);
                    } elseif (get_magic_quotes_gpc()) {
                              //aqui eu verifico o parametro q para o caso de ser necessário utilizar LIKE com %
                              $return = (empty($q)) ? '\''.str_replace('*;*',',',$temp).'\'' : '\'%'.str_replace('*;*',',',$temp).'%\'';
                    } else {
                        //aqui eu verifico o parametro q para o caso de ser necessário utilizar LIKE com %
                        $return = (empty($q)) ? '\''.addslashes(str_replace('*;*',',',$temp)).'\'' : '\'%'.addslashes(str_replace('*;*',',',$temp)).'%\'';
                    }
                } else {
                    $return = $temp;
                }
                break;
            default:
                /*Abaixo eu coloquei uma msg de erro para poder tratar
                  antes de realizar a query caso seja enviado um valor
                  que nao condiz com nenhum dos tipos tratatos desta
                  função. Porém você pode usar o retorno como preferir*/
                $return = 'Erro: O Tipo da Variável é Inválido!';
        }
        //Retorna o valor tipado
        return $return;
    } else {
        //Retorna os valores tipados de um array
        return array_map('antiInjection',$var);
    }
}
?>

Observe que para string além de acrescentar as aspas eu uso o addslashes para evitar injections. O exemplo de teste da função
vai abaixo:

<?php
$x = array('16','1,6','1,2,1',1.3,30,'12 teste');

echo '<pre>';
echo var_dump(antiInjection($x)).'<br />'.gettype($x).'<br />'.gettype(antiInjection($x));
?>

ou ainda se quiser testar com um form considerando os inputs
nome e valor:

<?php
$x = array_map('antiInjection',$_POST);
echo "{$x['nome']}<br />{$x['valor']}";
?>

A função está bem simples. Notem que utilizei o separador *;* para poder identificar se a string é uma string mesmo ou um float. Caso tenha uma virgula apenas na string eu verifico se ela é numérica. Se for eu vejo se é float ou inteiro e faço a tipagem. Se não for uma string numérica eu transformo o *;* em virgula novamente para manter a integridade da string.

"Porque não simplesmente transformou a virgula em ponto?"

Porque em uma string eu posso ter mais de um ponto. E a volta dos pontos
a virgula faria com que a string fosse alterada e todos os pontos virassem virgula, mesmo os que deveriam ser pontos mesmo.

Coloquei também um segundo parâmetro para que a função possa ser utilizada caso você tenha de definir uma query com LIKE '%texto%' por exemplo.

A função também pode ser adaptada para PHP OO dentre outros.

Não fiz este artigo para arrumar confusão com ninguém e não sou
o senhor da razão. Apenas espero ter deixado claro a intenção da função e a minha que é ajudar as pessoas que tem esta dúvida.

Abraços.

Comentários:

Mostrando 1 - 8 de 8 comentários
Isso ai Marcos Regis. Não houve confusão. O amigo apenas explicou novamente oque já está no artigo. Basta raciocinar. Se o typecast automatico do PHP fosse inteligente, não haveria a necessidade deste artigo. Como quando se passa algo via GET ou POST o TYPECAST automatico do PHP simplesmente não converte nada e trata tudo como uma string, eu coloquei no artigo uma forma para que o mesmo trate corretamente. Como disse, uma forma diferente de fazer com que o TYPECAST automatico do PHP trabalhe identificando o verdadeiro valor enviado, já que o PHP não é uma liguagem fortemente tipada que exija que seu valor seja declarado antes de utilizar a mesma. Obrigado pela contribuição, links e opnião.

Abraços.
27/12/2011 5:15pm (~7 anos atrás)

Marcos Regis disse:
Ibrahim, creio que você está confundindo os assuntos.
TYPECASTING é a capacidade da linguagem de converter um tipo em outro tipo.
Não confunda com tipagem fraca e tipagem forte.
Como PHP é fracamente tipado, por "default" qualquer valor definido via requisição é sempre uma string. Não entendi isso de identificar corretamente valores vindos de GET e POST. O PHP faz sua parte como deveria.
Tudo que vem por requisição é por padrão "string".
Seu TYPECASTING é automático (implícito) como expliquei no exemplo.
Em linguagens mais fortemente tipadas você precisa indicar qual vai ser o tipo dela antes de usá-la como tal.

Pra quem quer entender melhor do que estamos falando
http://en.wikipedia.org/wiki/Type_conversion
http://php.net/manual/pt_BR/language.types.type-juggling.php



27/12/2011 12:40pm (~7 anos atrás)

Marcos Regis, experimente o typecasting automatico do php em valores enviados por um formulário HTML via $_POST ou $_GET, pra você ver como ele funciona "bem". Veja se por um acaso o var_dump identifica os valores corretamente.

Como comentei posts abaixo, quis mostrar apenas mais uma forma de se fazer. Claro que com PDO fica 100%. Porém com PDO muitos amigos já fizeram artigos e se acha muita coisa no google. Type Cast no PHP não.

Mas agradeço mais uma vez a opnião de todos =)
23/12/2011 11:52pm (~7 anos atrás)

Ok, boa a preocupação com o famoso e polemico injection. Não acompanho o php brasil a anos, vejo que ele está meio 'paradão', legal ver que existem pessoas interessadas em escrever artigos. Parabéns!

Como todos eu concordo, isso deve ser usado para fins didáticos.

Este assunto ainda existe, e muito por ai, e muitos poucos falam sobre a PDO. Por este ser um topico recente em data, lembro aos amigos do PHP brasil que o tempo passou, e o PDO está ai para ser usado.
23/12/2011 2:29pm (~7 anos atrás)

Marcos Regis disse:
PHP tem TypeCasting SIM. E pior o TYPECASTING dele é automático.
Ex.:
$i=12345.00; // float
$i=$i."AA"; // string BB12345.00AA
$i=(int) $i; // integer 12345
O que PHP não tem suporte é o uso de TYPECASTING em assinatura de funções para tipos não escalares (PHP 5).
Conforme todos mencionaram, o artigo foi muito esclarecedor para aqueles que ainda não entendem o porquê deve-se preocupar com esse assunto.
E ainda esclarece que usar funções mysql_* já deveria ter sido abolido da linguagem em detrimento das funções que suporte bind como mysqli e PDO.
PDO inclusive é o padrão de praticamente todos os Frameworks.

22/12/2011 7:22pm (~7 anos atrás)

Hélio disse:
Independente de tudo, a intenção é importante. Mas neste caso, é completamente desnecessário e só deve ser vir pra estudo.
22/12/2011 5:01pm (~7 anos atrás)

Então Diego, na verdade minha idéia foi disponibilizar um Anti-Injection via Type Cast, coisa que o PHP não tem. E se ele não tem, toda e qualquer forma de faze-la será uma "gambiarra" mesmo. Cada um faz/utiliza a forma de Anti-Injection que achar melhor. Em momento algum eu declarei que utilizar Type Cast é melhor que Bind. Apenas dei mais uma opção a quem quiser utilizar. Se você não quer ou não ve a necessidade de usar, não a utilize.

Muito Obrigado por opinar.

Abraços.
21/12/2011 11:25pm (~7 anos atrás)

Diego disse:
Muito boa a sua iniciatava, porém não vi necessidade de criar toda essa gambiarra na função. Quer previnir Injection no PHP? Binda as variáveis antes, simples, funcional no pog.
Utilize o PDO para fazer a persistencia ou se você utiliza algum framework MVC, grande maioria já vem com opção se bindar se não vem, mude de framework.
20/12/2011 8:25am (~7 anos atrás)

Novo Comentário:

(Você pode usar tags como <b>, <i> ou <code>. URLs serão convertidas para links automaticamente.)