Como posso limpar a entrada do usuário com PHP?

1124

Existe uma função catchall em algum lugar que funcione bem para higienizar a entrada do usuário para injeção de SQL e ataques XSS, enquanto ainda permite certos tipos de tags HTML?

Brent
fonte
42
Atualmente, para evitar a injeção de sql, use o DOP ou o MySQLi.
Francisco Presencia 08/04
76
Usar DOP ou MySQLi não é suficiente. Se você construir suas instruções SQL com dados não confiáveis, como select * from users where name='$name', então não importa se você usa o PDO ou MySQLi ou MySQL. Você ainda está em perigo. Você deve usar consultas parametrizadas ou, se necessário, usar mecanismos de escape em seus dados, mas isso é muito menos preferível.
Andy Lester
26
@AndyLester Você está sugerindo que alguém usa DOP sem declarações preparadas? :)
64
Estou dizendo que "Usar DOP ou MySQLi" não é informação suficiente para explicar aos iniciantes como usá-los com segurança. Você e eu sabemos que as declarações preparadas são importantes, mas não presumo que todos que leem esta pergunta o conheçam. Foi por isso que adicionei as instruções explícitas.
Andy Lester
30
O comentário de Andy é totalmente válido. Eu converti meu site mysql para DOP recentemente, pensando que agora eu estava de alguma forma seguro contra ataques de injeção. Foi somente durante o processo que percebi que algumas das minhas instruções sql ainda eram construídas usando a entrada do usuário. Eu então corrigi isso usando instruções preparadas. Para um novato completo, não está totalmente claro que haja uma distinção, pois muitos especialistas lançam o comentário sobre o uso da DOP, mas não especificam a necessidade de declarações preparadas. A suposição é que isso é óbvio. Mas não para um novato.
GhostRider 25/05

Respostas:

1184

É um equívoco comum que a entrada do usuário possa ser filtrada. O PHP ainda possui um "recurso" (agora obsoleto), chamado magic-quotes , que se baseia nessa idéia. Não faz sentido. Esqueça a filtragem (ou limpeza, ou como as pessoas chamam).

O que você deve fazer, para evitar problemas, é bastante simples: sempre que você incorpora uma cadeia de caracteres a um código estrangeiro, deve escapar dela, de acordo com as regras desse idioma. Por exemplo, se você incorporar uma string em algum SQL direcionado ao MySQL, deverá escapar da string com a função do MySQL para essa finalidade ( mysqli_real_escape_string). (Ou, no caso de bancos de dados, usar instruções preparadas é uma abordagem melhor, quando possível.)

Outro exemplo é o HTML: se você incorporar cadeias de caracteres na marcação HTML, deverá escapar dela htmlspecialchars. Isso significa que cada declaração echoou printdeclaração deve ser usada htmlspecialchars.

Um terceiro exemplo pode ser os comandos do shell: se você deseja incorporar seqüências de caracteres (como argumentos) a comandos externos e chamá-las com exec, deve usar escapeshellcmde escapeshellarg.

E assim por diante ...

O único caso em que você precisa filtrar ativamente os dados é se você está aceitando entrada pré-formatada. Por exemplo, se você permitir que seus usuários publiquem a marcação HTML, planeja exibir no site. No entanto, é prudente evitar isso a todo custo, pois, independentemente de quão bem você o filtre, sempre será uma falha de segurança em potencial.

Troelskn
fonte
245
"Isso significa que cada declaração de eco ou impressão deve usar htmlspecialchars" - é claro, você quer dizer "toda ... declaração de saída do usuário"; htmlspecialchars () - ifying "echo 'Olá, mundo!';" seria uma loucura;)
Bobby Jack
10
Há um caso em que acho que a filtragem é a solução certa: UTF-8. Você não deseja seqüências UTF-8 inválidas em todo o aplicativo (pode haver uma recuperação de erro diferente dependendo do caminho do código), e o UTF-8 pode ser filtrado (ou rejeitado) facilmente.
21911 Kornel
6
@jbyrd - não, o LIKE usa uma linguagem regexp especializada. Você terá que escapar da sua string de entrada duas vezes - uma vez para a regexp e outra para a codificação da string mysql. É código dentro de código dentro de código.
troelskn
6
Neste momento mysql_real_escape_stringestá obsoleto. Atualmente, é considerado uma boa prática usar instruções preparadas para impedir a injeção de SQL. Então mude para MySQLi ou PDO.
Marcel Korpel
4
Porque você limita a superfície de ataque. Se você limpar previamente (quando entrar), precisará ter certeza de que não há outros buracos no aplicativo onde dados ruins possam entrar. Considerando que, se você fizer isso tarde, sua função de saída não precisará "confiar" em que ele recebe dados seguros - simplesmente assume que tudo é inseguro.
troelskn
217

Não tente impedir a injeção de SQL limpando os dados de entrada.

Em vez disso, não permita que dados sejam usados ​​na criação do seu código SQL . Use Instruções Preparadas (ou seja, usando parâmetros em uma consulta de modelo) que usa variáveis ​​ligadas. É a única maneira de garantir a injeção de SQL.

Por favor, consulte o meu site http://bobby-tables.com/ para obter mais informações sobre como evitar a injeção de SQL.

Andy Lester
fonte
18
Ou visite a documentação oficial e aprenda DOP e declarações preparadas. Pequena curva de aprendizado, mas se você conhece SQL muito bem, não terá problemas para se adaptar.
um codificador
2
Para o caso específico de injeção SQL, esta é a resposta correta!
Scott Arciszewski
4
Observe que as instruções preparadas não adicionam segurança, as consultas parametrizadas. Eles são muito fáceis de usar juntos em PHP.
Basic
Não é a única maneira garantida. Hex a entrada e unhex na consulta também impedirão. Além disso, ataques hexadecimais não são possíveis se você usar o hexágono certo.
Ramon Bakker
E se você estiver inserindo algo especializado, como endereços de email ou nomes de usuário?
Abraham Brookes
79

Não. Você não pode filtrar dados genericamente sem qualquer contexto para o que serve. Às vezes, você deseja aceitar uma consulta SQL como entrada e, às vezes, deseja aceitar HTML como entrada.

Você precisa filtrar a entrada em uma lista de permissões - verifique se os dados correspondem a alguma especificação do que você espera. Então você precisa escapar antes de usá-lo, dependendo do contexto em que o está usando.

O processo de escape de dados para SQL - para impedir a injeção de SQL - é muito diferente do processo de escape de dados para (X) HTML, para impedir o XSS.

Daniel Papasian
fonte
52

O PHP tem agora as novas funções agradáveis ​​filter_input, que por exemplo o libertam de encontrar 'o melhor regex de e-mail' agora que existe um tipo FILTER_VALIDATE_EMAIL embutido

Minha própria classe de filtro (usa JavaScript para destacar campos defeituosos) pode ser iniciada por uma solicitação ajax ou por uma postagem de formulário normal. (veja o exemplo abaixo)

/**
 *  Pork.FormValidator
 *  Validates arrays or properties by setting up simple arrays. 
 *  Note that some of the regexes are for dutch input!
 *  Example:
 * 
 *  $validations = array('name' => 'anything','email' => 'email','alias' => 'anything','pwd'=>'anything','gsm' => 'phone','birthdate' => 'date');
 *  $required = array('name', 'email', 'alias', 'pwd');
 *  $sanitize = array('alias');
 *
 *  $validator = new FormValidator($validations, $required, $sanitize);
 *                  
 *  if($validator->validate($_POST))
 *  {
 *      $_POST = $validator->sanitize($_POST);
 *      // now do your saving, $_POST has been sanitized.
 *      die($validator->getScript()."<script type='text/javascript'>alert('saved changes');</script>");
 *  }
 *  else
 *  {
 *      die($validator->getScript());
 *  }   
 *  
 * To validate just one element:
 * $validated = new FormValidator()->validate('blah@bla.', 'email');
 * 
 * To sanitize just one element:
 * $sanitized = new FormValidator()->sanitize('<b>blah</b>', 'string');
 * 
 * @package pork
 * @author SchizoDuckie
 * @copyright SchizoDuckie 2008
 * @version 1.0
 * @access public
 */
class FormValidator
{
    public static $regexes = Array(
            'date' => "^[0-9]{1,2}[-/][0-9]{1,2}[-/][0-9]{4}\$",
            'amount' => "^[-]?[0-9]+\$",
            'number' => "^[-]?[0-9,]+\$",
            'alfanum' => "^[0-9a-zA-Z ,.-_\\s\?\!]+\$",
            'not_empty' => "[a-z0-9A-Z]+",
            'words' => "^[A-Za-z]+[A-Za-z \\s]*\$",
            'phone' => "^[0-9]{10,11}\$",
            'zipcode' => "^[1-9][0-9]{3}[a-zA-Z]{2}\$",
            'plate' => "^([0-9a-zA-Z]{2}[-]){2}[0-9a-zA-Z]{2}\$",
            'price' => "^[0-9.,]*(([.,][-])|([.,][0-9]{2}))?\$",
            '2digitopt' => "^\d+(\,\d{2})?\$",
            '2digitforce' => "^\d+\,\d\d\$",
            'anything' => "^[\d\D]{1,}\$"
    );
    private $validations, $sanatations, $mandatories, $errors, $corrects, $fields;


    public function __construct($validations=array(), $mandatories = array(), $sanatations = array())
    {
        $this->validations = $validations;
        $this->sanitations = $sanitations;
        $this->mandatories = $mandatories;
        $this->errors = array();
        $this->corrects = array();
    }

    /**
     * Validates an array of items (if needed) and returns true or false
     *
     */
    public function validate($items)
    {
        $this->fields = $items;
        $havefailures = false;
        foreach($items as $key=>$val)
        {
            if((strlen($val) == 0 || array_search($key, $this->validations) === false) && array_search($key, $this->mandatories) === false) 
            {
                $this->corrects[] = $key;
                continue;
            }
            $result = self::validateItem($val, $this->validations[$key]);
            if($result === false) {
                $havefailures = true;
                $this->addError($key, $this->validations[$key]);
            }
            else
            {
                $this->corrects[] = $key;
            }
        }

        return(!$havefailures);
    }

    /**
     *
     *  Adds unvalidated class to thos elements that are not validated. Removes them from classes that are.
     */
    public function getScript() {
        if(!empty($this->errors))
        {
            $errors = array();
            foreach($this->errors as $key=>$val) { $errors[] = "'INPUT[name={$key}]'"; }

            $output = '$$('.implode(',', $errors).').addClass("unvalidated");'; 
            $output .= "new FormValidator().showMessage();";
        }
        if(!empty($this->corrects))
        {
            $corrects = array();
            foreach($this->corrects as $key) { $corrects[] = "'INPUT[name={$key}]'"; }
            $output .= '$$('.implode(',', $corrects).').removeClass("unvalidated");';   
        }
        $output = "<script type='text/javascript'>{$output} </script>";
        return($output);
    }


    /**
     *
     * Sanitizes an array of items according to the $this->sanitations
     * sanitations will be standard of type string, but can also be specified.
     * For ease of use, this syntax is accepted:
     * $sanitations = array('fieldname', 'otherfieldname'=>'float');
     */
    public function sanitize($items)
    {
        foreach($items as $key=>$val)
        {
            if(array_search($key, $this->sanitations) === false && !array_key_exists($key, $this->sanitations)) continue;
            $items[$key] = self::sanitizeItem($val, $this->validations[$key]);
        }
        return($items);
    }


    /**
     *
     * Adds an error to the errors array.
     */ 
    private function addError($field, $type='string')
    {
        $this->errors[$field] = $type;
    }

    /**
     *
     * Sanitize a single var according to $type.
     * Allows for static calling to allow simple sanitization
     */
    public static function sanitizeItem($var, $type)
    {
        $flags = NULL;
        switch($type)
        {
            case 'url':
                $filter = FILTER_SANITIZE_URL;
            break;
            case 'int':
                $filter = FILTER_SANITIZE_NUMBER_INT;
            break;
            case 'float':
                $filter = FILTER_SANITIZE_NUMBER_FLOAT;
                $flags = FILTER_FLAG_ALLOW_FRACTION | FILTER_FLAG_ALLOW_THOUSAND;
            break;
            case 'email':
                $var = substr($var, 0, 254);
                $filter = FILTER_SANITIZE_EMAIL;
            break;
            case 'string':
            default:
                $filter = FILTER_SANITIZE_STRING;
                $flags = FILTER_FLAG_NO_ENCODE_QUOTES;
            break;

        }
        $output = filter_var($var, $filter, $flags);        
        return($output);
    }

    /** 
     *
     * Validates a single var according to $type.
     * Allows for static calling to allow simple validation.
     *
     */
    public static function validateItem($var, $type)
    {
        if(array_key_exists($type, self::$regexes))
        {
            $returnval =  filter_var($var, FILTER_VALIDATE_REGEXP, array("options"=> array("regexp"=>'!'.self::$regexes[$type].'!i'))) !== false;
            return($returnval);
        }
        $filter = false;
        switch($type)
        {
            case 'email':
                $var = substr($var, 0, 254);
                $filter = FILTER_VALIDATE_EMAIL;    
            break;
            case 'int':
                $filter = FILTER_VALIDATE_INT;
            break;
            case 'boolean':
                $filter = FILTER_VALIDATE_BOOLEAN;
            break;
            case 'ip':
                $filter = FILTER_VALIDATE_IP;
            break;
            case 'url':
                $filter = FILTER_VALIDATE_URL;
            break;
        }
        return ($filter === false) ? false : filter_var($var, $filter) !== false ? true : false;
    }       



}

Obviamente, lembre-se de que você precisa executar sua consulta sql também, dependendo do tipo de banco de dados que você está usando (mysql_real_escape_string () é inútil para um servidor sql, por exemplo). Você provavelmente deseja lidar com isso automaticamente em sua camada de aplicativo apropriada, como um ORM. Além disso, como mencionado acima: para enviar para html use as outras funções dedicadas em php como htmlspecialchars;)

Para permitir realmente a entrada HTML com classes e / ou tags despojadas, dependa de um dos pacotes de validação xss dedicados. NÃO ESCREVA SEUS PRÓPRIOS REGEXES PARA COMPARTILHAR HTML!

SchizoDuckie
fonte
18
Parece que pode ser um script útil para validar entradas, mas é completamente irrelevante para a pergunta.
Rjmunro
43

Não, não há.

Em primeiro lugar, a injeção SQL é um problema de filtragem de entrada e o XSS é uma saída que foge da saída - portanto, você nem executaria essas duas operações ao mesmo tempo no ciclo de vida do código.

Regras básicas básicas

  • Para consulta SQL, vincule parâmetros (como no PDO) ou use uma função de escape nativa do driver para variáveis ​​de consulta (como mysql_real_escape_string())
  • Use strip_tags()para filtrar HTML indesejado
  • Escape de todas as outras saídas com htmlspecialchars()e esteja atento aos segundo e terceiro parâmetros aqui.
Peter Bailey
fonte
1
Portanto, você só usa strip_tags () ou htmlspecialchars () quando sabe que a entrada possui HTML do qual deseja se livrar ou escapar, respectivamente - você não está usando para fins de segurança, certo? Além disso, quando você faz a ligação, o que isso faz para coisas como Bobby Tables? "Robert '); DROP TABLE Students; -" Isso simplesmente escapa às aspas?
Robert Mark Bram
2
Se você possui dados de usuários que entram em um banco de dados e depois são exibidos em páginas da Web, geralmente não são lidos muito mais do que estão escritos? Para mim, faz mais sentido filtrá-lo uma vez (como entrada) antes de armazená-lo, em vez de precisar filtrá-lo toda vez que você o exibe. Estou faltando alguma coisa ou um monte de gente votou em sobrecarga de desempenho desnecessário nesta e na resposta aceita?
precisa saber é o seguinte
2
Melhor resposta para mim. É curto e aborda bem a questão, se você me perguntar. É possível atacar o PHP de alguma forma via $ _POST ou $ _GET com alguma injeção ou isso é impossível?
Jo # Smo
ah sim, as matrizes $ post e $ get aceitam todos os caracteres, mas alguns desses caracteres podem ser usados ​​contra você se for permitido que o personagem seja enumerado na página php postada. portanto, se você não escapar dos caracteres encapsulados (como ", 'e`), isso poderá abrir um vetor de ataque. o caractere `geralmente é perdido e pode ser usado para formar hacks de execução de linha de comando. mas não o ajudará com hacks de firewall de aplicativos da Web.
drtechno
22

Para resolver o problema do XSS, consulte o HTML Purifier . É bastante configurável e tem um histórico decente.

Quanto aos ataques de injeção SQL, verifique a entrada do usuário e execute-a através de mysql_real_escape_string (). Porém, a função não derrota todos os ataques de injeção, portanto, é importante que você verifique os dados antes de jogá-los na cadeia de caracteres da consulta.

Uma solução melhor é usar instruções preparadas. A biblioteca PDO e a extensão mysqli suportam isso.

jasonbar
fonte
não há "melhor maneira" de fazer algo como desinfetar a entrada. Use alguma biblioteca, o purificador de html é bom. Essas bibliotecas já foram lançadas muitas vezes. Por isso, é muito mais balas do que qualquer coisa ocê pode chegar a si mesmo
paan
Consulte também bioinformatics.org/phplabware/internal_utilities/htmLawed . Do meu WordPress compreensão usa uma versão mais antiga, core.trac.wordpress.org/browser/tags/2.9.2/wp-includes/kses.php
Steve argila
O problema com o wordpress é que não é necessariamente um ataque de injeção de php-sql que causa violações no banco de dados. A falta de plugins programados que armazenam dados que uma consulta xml revela segredos é mais problemática.
drtechno 01/10/19
17

Um truque que pode ajudar na circunstância específica em que você tem uma página /mypage?id=53e usa o id em uma cláusula WHERE é garantir que o id definitivamente seja um número inteiro, assim:

if (isset($_GET['id'])) {
  $id = $_GET['id'];
  settype($id, 'integer');
  $result = mysql_query("SELECT * FROM mytable WHERE id = '$id'");
  # now use the result
}

Mas é claro que isso apenas interrompe um ataque específico, portanto, leia todas as outras respostas. (E sim, eu sei que o código acima não é ótimo, mas mostra a defesa específica.)

Hamish Downer
fonte
11
I usar $ id = intval ($ id) em vez :)
Duc Tran
A transmissão de números inteiros é uma boa maneira de garantir que apenas dados numéricos sejam inseridos.
teste
1
$id = (int)$_GET['id']e $que = sprintf('SELECT ... WHERE id="%d"', $id)é bom também
vladkras 21/11
16

Métodos para higienizar a entrada do usuário com PHP:

  • Use versões modernas do MySQL e PHP.

  • Defina charset explicitamente:

    • $ mysqli-> set_charset ("utf8");
      manual
    • $ pdo = novo PDO ('mysql: host = localhost; dbname = testdb; charset = UTF8', $ usuário, $ senha);
      manual
    • $ pdo-> exec ("definir nomes utf8");
      manual
    • $ pdo = nova DOP (
      "mysql: host = $ host; dbname = $ db", $ user, $ pass, 
      matriz (
      DOP :: ATTR_ERRMODE => DOP :: ERRMODE_EXCEPTION,
      DOP :: MYSQL_ATTR_INIT_COMMAND => "SET NAMES utf8"
      )
      );
      manual
    • mysql_set_charset ('utf8')
      [obsoleto no PHP 5.5.0, removido no PHP 7.0.0].
  • Use conjuntos de caracteres seguros:

    • Selecione utf8, latin1, ascii .., não use conjuntos de caracteres vulneráveis ​​big5, cp932, gb2312, gbk, sjis.
  • Use a função espacializada:

    • Instruções preparadas para o MySQLi:
      $ stmt = $ mysqli-> prepare ('SELECT * FROM teste WHERE nome =? LIMIT 1'); 
      $ param = "'OR 1 = 1 / *";
      $ stmt-> bind_param ('s', $ param);
      $ stmt-> execute ();
    • PDO :: quote () - coloca aspas ao redor da string de entrada (se necessário) e escapa caracteres especiais dentro da string de entrada, usando um estilo de aspas apropriado ao driver subjacente:

      $ pdo = novo PDO ('mysql: host = localhost; dbname = testdb; charset = UTF8', $ usuário, $ senha); conjunto explícito do conjunto de caracteres
      $ pdo-> setAttribute (PDO :: ATTR_EMULATE_PREPARES, false); desabilite a emulação de instruções preparadas para evitar fallback para emulações que o MySQL não pode preparar de forma nativa (para impedir a injeção)
      $ var = $ pdo-> quote ("'OR 1 = 1 / *"); não apenas escapa ao literal, mas também o aspas (em caracteres de aspas simples) $ stmt = $ pdo-> query ("SELECT * FROM teste WHERE nome = $ var LIMIT 1");

    • Declarações Preparadas para DOP : As instruções preparadas contra o MySQLi suportam mais drivers de banco de dados e parâmetros nomeados:

      $ pdo = novo PDO ('mysql: host = localhost; dbname = testdb; charset = UTF8', $ usuário, $ senha); conjunto explícito do conjunto de caracteres
      $ pdo-> setAttribute (PDO :: ATTR_EMULATE_PREPARES, false); desabilite a emulação de instruções preparadas para evitar fallback para emulações de instruções que o MySQL não pode preparar nativamente (para impedir a injeção) $ stmt = $ pdo-> prepare ('SELECT * FROM teste WHERE nome =? LIMIT 1'); $ stmt-> execute (["'OR 1 = 1 / *"]);

    • mysql_real_escape_string [obsoleto no PHP 5.5.0, removido no PHP 7.0.0].
    • mysqli_real_escape_string Escapa caracteres especiais em uma string para uso em uma instrução SQL, levando em consideração o conjunto de caracteres atual da conexão. Mas recomendado usar instruções preparadas porque elas não são simplesmente seqüências de escape, uma instrução apresenta um plano de execução de consulta completo, incluindo quais tabelas e índices ela usaria, é uma maneira otimizada.
    • Use aspas simples ('') em torno de suas variáveis ​​na sua consulta.
  • Verifique se a variável contém o que você está esperando:

    • Se você está esperando um número inteiro, use:
      ctype_digit - Verifique se há caracteres numéricos; 
      $ valor = (int) $ valor;
      $ valor = intval (valor $);
      $ var = filter_var ('0755', FILTER_VALIDATE_INT, $ opções);
    • Para Strings use:
      is_string () - Descubra se o tipo de uma variável é string

      Use a Função de Filtro filter_var () - filtra uma variável com um filtro especificado:
      $ email = filter_var ($ email, FILTER_SANITIZE_EMAIL); 
      $ newstr = filter_var ($ str, FILTER_SANITIZE_STRING);
      filtros mais predefinidos
    • filter_input () - obtém uma variável externa específica por nome e, opcionalmente, a filtra:
      $ search_html = filter_input (INPUT_GET, 'search', FILTER_SANITIZE_SPECIAL_CHARS);
    • preg_match () - Executa uma correspondência de expressão regular;
    • Escreva sua própria função de validação.
Mark Martin
fonte
11

O que você está descrevendo aqui são dois problemas separados:

  1. Higienização / filtragem de dados de entrada do usuário.
  2. Saída de escape.

1) A entrada do usuário sempre deve ser considerada ruim.

Usar instruções preparadas ou / e filtrar com mysql_real_escape_string é definitivamente uma obrigação. O PHP também possui filter_input, o que é um bom ponto de partida.

2) Esse é um tópico amplo e depende do contexto dos dados que estão sendo impressos. Para HTML, existem soluções como o htmlpurifier por aí. Como regra geral, sempre escape qualquer coisa que você produz.

Ambos os problemas são grandes demais para serem analisados ​​em uma única postagem, mas há muitas que entram em mais detalhes:

Métodos Saída PHP

Saída PHP mais segura

Andrew
fonte
9

Se você estiver usando o PostgreSQL, a entrada do PHP pode ser escapada com pg_escape_string ()

 $username = pg_escape_string($_POST['username']);

Na documentação ( http://php.net/manual/es/function.pg-escape-string.php ):

pg_escape_string () escapa uma string para consultar o banco de dados. Retorna uma string de escape no formato PostgreSQL sem aspas.

Alejandro Silva
fonte
1
pg_escape_literal () é a função recomendada para o PostgreSQL.
Cryptic #
8

Não existe uma função abrangente, porque há várias preocupações a serem abordadas.

  1. Injeção de SQL - Hoje, geralmente, todo projeto PHP deve usar instruções preparadas por meio de PHP Data Objects (PDO) como uma prática recomendada, evitando erros de aspas perdidas e uma solução completa contra injeção. . É também a maneira mais flexível e segura de acessar seu banco de dados.

    Confira o tutorial do PDO (o único apropriado) para praticamente tudo o que você precisa saber sobre o DOP. (Agradecimentos sinceros ao principal colaborador do SO, @YourCommonSense, por este excelente recurso sobre o assunto.)

  2. XSS - higienizar dados a caminho de ...

    • O Purificador HTML já existe há muito tempo e ainda é atualizado ativamente. Você pode usá-lo para limpar informações maliciosas, enquanto ainda permite uma lista de permissões generosa e configurável. Funciona muito bem com muitos editores WYSIWYG, mas pode ser pesado para alguns casos de uso.

    • Em outros casos, onde não queremos aceitar HTML / Javascript, achei essa função simples útil (e passou por várias auditorias no XSS):

      /* Prevent XSS input */ function sanitizeXSS () { $_GET = filter_input_array(INPUT_GET, FILTER_SANITIZE_STRING); $_POST = filter_input_array(INPUT_POST, FILTER_SANITIZE_STRING); $_REQUEST = (array)$_POST + (array)$_GET + (array)$_REQUEST; }

  3. XSS - higienize dados na saída ... a menos que você garanta que os dados foram higienizados adequadamente antes de adicioná-los ao seu banco de dados, será necessário higienizá-los antes de exibi-los para o usuário, podemos aproveitar essas funções úteis do PHP:

    • Ao ligar echoou printexibir valores fornecidos pelo usuário, use htmlspecialchars, a menos que os dados tenham sido higienizados adequadamente e que sejam permitidos exibir HTML.
    • json_encode é uma maneira segura de fornecer valores fornecidos pelo usuário, de PHP a Javascript
  4. Você chama comandos shell externos usando exec()ou system()functions ou para o backtickoperador? Nesse caso, além do SQL Injection & XSS, você pode ter uma preocupação adicional a tratar, os usuários executando comandos maliciosos no seu servidor . Você precisa usar escapeshellcmdse quiser escapar de todo o comando OU escapeshellargpara escapar de argumentos individuais.

webaholik
fonte
poderia ser usado mb_encode_numericentity? Uma vez que codifica tudo?
drtechno 1/10/19
@drtechno - mb_encode_numericentityé discutido no htmlspecialcharslink # 3 XSS
webaholik
5

A maneira mais fácil de evitar erros na limpeza de dados de entrada e escape é usar a estrutura PHP como Symfony , Nette etc. ou parte dessa estrutura (mecanismo de modelagem, camada de banco de dados, ORM).

Mecanismos de modelagem como Twig ou Latte têm saída com escape por padrão - você não precisa resolver manualmente se tiver escapado adequadamente da saída, dependendo do contexto (parte HTML ou Javascript da página da web).

O Framework limpa automaticamente as entradas e você não deve usar as variáveis ​​$ _POST, $ _GET ou $ _SESSION diretamente, mas através de mecanismos como roteamento, manipulação de sessões etc.

E para a camada de banco de dados (modelo), existem estruturas ORM como Doctrine ou wrappers em torno do PDO como Nette Database.

Você pode ler mais sobre isso aqui - O que é uma estrutura de software?

Ondřej Šotek
fonte
3

Só queria acrescentar que, no assunto de saída de escape, se você usar o php DOMDocument para fazer sua saída de html, ela escapará automaticamente no contexto certo. Um atributo (value = "") e o texto interno de um <span> não são iguais. Para estar seguro contra o XSS, leia o seguinte: Folha de dicas de prevenção do OWASP XSS

user138720
fonte
2

Você nunca limpa a entrada.

Você sempre limpa a saída.

As transformações que você aplica aos dados para torná-lo seguro para inclusão em uma instrução SQL são completamente diferentes daquelas que você solicita para inclusão em HTML são completamente diferentes daquelas que você solicita para inclusão em Javascript são completamente diferentes daquelas que solicita para inclusão em LDIF. completamente diferentes daqueles que você aplica à inclusão em CSS são completamente diferentes daqueles que você aplica à inclusão em um E-mail ....

Por todos os meios, valide a entrada - decida se você deve aceitá-la para processamento adicional ou se é inaceitável para o usuário. Mas não aplique nenhuma alteração na representação dos dados até que esteja prestes a sair do PHP.

Há muito tempo, alguém tentou inventar um mecanismo único para todos os dados de escape e acabamos com " magic_quotes " que não escapavam adequadamente aos dados de todos os destinos de saída e resultavam em instalações diferentes, exigindo código diferente para funcionar.

symcbean
fonte
Um problema disso é que nem sempre é um ataque ao banco de dados e todas as entradas do usuário devem ser protegidas do sistema. não apenas um tipo de idioma. Portanto, em seus sites, quando você enumera seus dados $ _POST, mesmo com o uso de binding, eles podem escapar o suficiente para executar o shell ou mesmo outro código php.
drtechno 1/10/19
"não é sempre um ataque de banco de dados": "As transformadas você se aplicam a dados para torná-lo seguro para inclusão em uma instrução SQL são completamente diferentes daquelas ...."
symcbean
"todas as entradas do usuário devem ser protegidas do sistema": não, o sistema deve ser protegido das entradas do usuário.
Symcbean #
bem, fiquei sem palavras, mas sim, a entrada precisa ser impedida de afetar a operação do sistema. para esclarecer isso ...
drtechno 04/10/19
Tanto a entrada como a saída devem ser higienizadas.
Tajni
1

Nunca confie nos dados do usuário.

function clean_input($data) {
  $data = trim($data);
  $data = stripslashes($data);
  $data = htmlspecialchars($data);
  return $data;
}

A trim()função remove espaços em branco e outros caracteres predefinidos dos dois lados de uma sequência.

A stripslashes()função remove barras invertidas

A htmlspecialchars()função converte alguns caracteres predefinidos em entidades HTML.

Os caracteres predefinidos são:

& (ampersand) becomes &amp;
" (double quote) becomes &quot;
' (single quote) becomes &#039;
< (less than) becomes &lt;
> (greater than) becomes &gt;
Erik Thiart
fonte
1
Do que isso protegeria? Isso é para XSS? Por que é chamado clean_inputentão? Por que você iria querer cortar barras?
Dharman
5
AVISO: Isso não torna magicamente os dados do usuário seguros. Esta função danifica desnecessariamente seus dados sem se proteger de nada. NÃO USE ISSO!
Dharman
Sua afirmação é falsa.
Erik Thiart 9/11/19
0

Existe a extensão do filtro ( howto-link , manual ), que funciona muito bem com todas as variáveis ​​GPC. Não é algo mágico, você ainda precisará usá-lo.

Até
fonte