Jak mogę zdezynfekować dane wejściowe użytkownika za pomocą PHP?

1124

Czy jest gdzieś funkcja catchall, która działa dobrze w odkażaniu danych wejściowych użytkownika dla iniekcji SQL i ataków XSS, jednocześnie dopuszczając pewne typy tagów HTML?

Brent
źródło
42
W dzisiejszych czasach, aby uniknąć iniekcji sql, użyj PDO lub MySQLi.
Francisco Presencia
76
Używanie PDO lub MySQLi nie wystarczy. Jeśli budujesz swoje instrukcje SQL z niezaufanymi danymi, na przykład select * from users where name='$name', nie ma znaczenia, czy używasz PDO, MySQLi czy MySQL. Nadal jesteś w niebezpieczeństwie. Musisz użyć sparametryzowanych zapytań lub, jeśli musisz, użyć mechanizmów zmiany znaczenia na swoich danych, ale jest to o wiele mniej preferowane.
Andy Lester,
26
@AndyLester Czy sugerujesz, że ktoś używa PDO bez przygotowanych wyciągów? :)
63
Mówię, że „Użyj PDO lub MySQLi” nie jest wystarczającą informacją, aby wyjaśnić nowicjuszom, jak bezpiecznie z nich korzystać. Ty i ja wiemy, że przygotowane wypowiedzi mają znaczenie, ale nie zakładam, że każdy, kto przeczyta to pytanie, będzie o tym wiedział. Dlatego dodałem wyraźne instrukcje.
Andy Lester
30
Komentarz Andy'ego jest całkowicie poprawny. Niedawno przekonwertowałem moją stronę mysql na PDO, myśląc, że jestem w jakiś sposób bezpieczny przed atakami iniekcyjnymi. Dopiero podczas tego procesu zdałem sobie sprawę, że niektóre z moich instrukcji SQL nadal były budowane przy użyciu danych wprowadzanych przez użytkownika. Następnie naprawiłem to za pomocą przygotowanych instrukcji. Dla kompletnego nowicjusza nie jest do końca jasne, że istnieje rozróżnienie, ponieważ wielu ekspertów odrzuca komentarz na temat używania PDO, ale nie określa potrzeby przygotowywania oświadczeń. Zakłada się, że jest to oczywiste. Ale nie nowicjuszowi.
GhostRider

Odpowiedzi:

1183

Często nieporozumieniem jest to, że dane wejściowe użytkownika mogą być filtrowane. PHP ma nawet (obecnie przestarzałą) „funkcję”, zwaną magicznymi cytatami , która opiera się na tym pomyśle. To jest nonsens. Zapomnij o filtrowaniu (lub czyszczeniu lub jakkolwiek to nazywają ludzie).

To, co należy zrobić, aby uniknąć problemów, jest dość proste: za każdym razem, gdy osadzasz ciąg znaków w obcym kodzie, musisz uciec przed nim, zgodnie z regułami tego języka. Na przykład, jeśli osadzisz ciąg w jakimś SQL kierowanym do MySQL, w tym celu musisz uciec przed ciągiem za pomocą funkcji MySQL ( mysqli_real_escape_string). (Lub, w przypadku baz danych, stosowanie przygotowanych instrukcji jest lepszym podejściem, jeśli to możliwe.)

Innym przykładem jest HTML: jeśli osadzasz ciągi znaków w znacznikach HTML, musisz uciec przed nim htmlspecialchars. Oznacza to, że każda pojedyncza instrukcja echolub printinstrukcja powinna być używana htmlspecialchars.

Trzecim przykładem mogą być polecenia powłoki: jeśli zamierzasz osadzić ciągi (takie jak argumenty) w poleceniach zewnętrznych i wywołać je za pomocą exec, musisz użyć escapeshellcmdi escapeshellarg.

I tak dalej i tak dalej ...

Jedyny przypadek, w którym trzeba aktywnie filtrowanie danych, jeśli jest przyjmowanie Preformatowany wejście. Na przykład, jeśli pozwolisz swoim użytkownikom publikować znaczniki HTML, które planujesz wyświetlać w witrynie. Należy jednak rozsądnie unikać tego za wszelką cenę, ponieważ bez względu na to, jak dobrze go filtrujesz, zawsze będzie to potencjalna dziura w zabezpieczeniach.

troelskn
źródło
245
„Oznacza to, że każda instrukcja echa lub instrukcja drukowania powinna używać htmlspecialchars” - oczywiście masz na myśli „każda ... instrukcja wypisująca dane wejściowe użytkownika”; htmlspecialchars () - ifying „echo 'Hello, world!';" byłby szalony;)
Bobby Jack
10
Jest jeden przypadek, w którym myślę, że filtrowanie jest właściwym rozwiązaniem: UTF-8. Nie chcesz nieprawidłowych sekwencji UTF-8 w całej aplikacji (możesz uzyskać różne odzyskiwanie po błędzie w zależności od ścieżki kodu), a UTF-8 można łatwo filtrować (lub odrzucać).
Kornel,
6
@jbyrd - nie, LIKE używa specjalistycznego języka wyrażeń regularnych. Będziesz musiał dwa razy uciec od łańcucha wejściowego - raz dla wyrażenia regularnego i raz dla kodowania ciągu mysql. To kod w kodzie w kodzie.
troelskn
6
W tej chwili mysql_real_escape_stringjest przestarzałe. Obecnie dobrą praktyką jest używanie przygotowanych instrukcji, aby zapobiec wstrzykiwaniu SQL. Więc przełącz się na MySQLi lub PDO.
Marcel Korpel
4
Ponieważ ograniczasz powierzchnię ataku. Jeśli wcześnie zdezynfekujesz (po wprowadzeniu), musisz mieć pewność, że w aplikacji nie ma innych dziur, przez które mogłyby przedostać się złe dane. Podczas gdy robisz to późno, funkcja wyjściowa nie musi „ufać”, że dane są bezpieczne - po prostu zakłada, że ​​wszystko jest niebezpieczne.
troelskn
217

Nie próbuj zapobiegać iniekcji SQL poprzez odkażanie danych wejściowych.

Zamiast tego nie zezwalaj na wykorzystywanie danych podczas tworzenia kodu SQL . Użyj przygotowanych instrukcji (tj. Używając parametrów w zapytaniu szablonowym), które korzystają ze zmiennych powiązanych. Jest to jedyny sposób na zabezpieczenie przed wstrzyknięciem SQL.

Więcej informacji na temat zapobiegania wstrzykiwaniu SQL można znaleźć na mojej stronie http://bobby-tables.com/ .

Andy Lester
źródło
18
Lub odwiedź oficjalną dokumentację i dowiedz się o PDO i przygotowanych oświadczeniach. Mała krzywa uczenia się, ale jeśli dobrze znasz SQL, nie będziesz mieć problemów z adaptacją.
koder
2
Dla konkretnego przypadku z SQL Injection, to jest poprawna odpowiedź!
Scott Arciszewski
4
Zauważ, że przygotowane instrukcje nie dodają żadnych zabezpieczeń, ale sparametryzowane zapytania. Są po prostu bardzo łatwe w użyciu razem w PHP.
Podstawowy
To nie jedyny gwarantowany sposób. Hex wejściowy i unhex w zapytaniu również zapobiegnie. Również ataki heksadecymalne nie są możliwe, jeśli użyjesz heksowania z prawej
Ramon Bakker,
Co jeśli wpisujesz coś specjalistycznego, na przykład adresy e-mail lub nazwy użytkowników?
Abraham Brookes,
78

Nie. Nie można generalnie filtrować danych bez kontekstu, do czego one służą. Czasami chcesz wziąć zapytanie SQL jako dane wejściowe, a czasem HTML jako dane wejściowe.

Musisz filtrować dane wejściowe na białej liście - upewnij się, że dane są zgodne z pewną specyfikacją tego, czego oczekujesz. Następnie musisz uciec przed użyciem, w zależności od kontekstu, w którym go używasz.

Proces ucieczki danych dla SQL - aby zapobiec wstrzykiwaniu SQL - bardzo różni się od procesu ucieczki danych dla (X) HTML, aby zapobiec XSS.

Daniel Papasian
źródło
52

PHP ma teraz nowe fajne funkcje filter_input, które na przykład uwalniają cię od szukania „ostatecznego wyrażenia regularnego e-mail” teraz, gdy istnieje wbudowany typ FILTER_VALIDATE_EMAIL

Moja własna klasa filtrów (wykorzystuje JavaScript do wyróżnienia błędnych pól) może zostać zainicjowana przez żądanie ajax lub zwykły formularz. (patrz przykład poniżej)

/**
 *  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;
    }       



}

Oczywiście należy pamiętać, że należy wykonać także ucieczkę zapytania SQL w zależności od używanego typu bazy danych (mysql_real_escape_string () jest na przykład bezużyteczny dla serwera SQL). Prawdopodobnie chcesz obsłużyć to automatycznie na odpowiedniej warstwie aplikacji, takiej jak ORM. Ponadto, jak wspomniano powyżej: do wysyłania do html użyj innych funkcji dedykowanych php, takich jak htmlspecialchars;)

Rzeczywiste zezwolenie na wprowadzanie HTML z podobnymi klasami i / lub znacznikami zależy od jednego z dedykowanych pakietów sprawdzania poprawności xss. NIE PISUJ SWOICH WŁASNYCH REGEXESÓW DO SKŁADANIA HTML!

SchizoDuckie
źródło
17
Wygląda na to, że może to być przydatny skrypt do sprawdzania poprawności danych wejściowych, ale jest całkowicie nieistotny dla pytania.
rjmunro
43

Nie, nie ma.

Po pierwsze, wstrzyknięcie SQL stanowi problem z filtrowaniem danych wejściowych, a XSS jest wyjściem unikającym danych wyjściowych - abyś nawet nie wykonał tych dwóch operacji w tym samym czasie w cyklu życia kodu.

Podstawowe zasady praktyczne

  • W przypadku zapytania SQL należy powiązać parametry (jak w przypadku PDO) lub użyć funkcji zmiany znaczenia natywnego dla sterownika dla zmiennych zapytania (takich jak mysql_real_escape_string())
  • Służy strip_tags()do filtrowania niechcianego kodu HTML
  • Ucieknij przed wszystkimi innymi htmlspecialchars()danymi wyjściowymi i pamiętaj o drugim i trzecim parametrze tutaj.
Peter Bailey
źródło
1
Więc używasz strip_tags () lub htmlspecialchars (), gdy wiesz, że dane wejściowe zawierają HTML, którego chcesz się odpowiednio pozbyć lub uciec - nie używasz go do celów bezpieczeństwa, prawda? Ponadto, kiedy wykonujesz powiązanie, co to robi w przypadku takich rzeczy jak Bobby Tables? „Robert”); DROP TABLE Studenci; - „Czy to po prostu unika cytatów?
Robert Mark Bram
2
Jeśli masz dane użytkownika, które trafią do bazy danych, a później zostaną wyświetlone na stronach internetowych, czy zwykle nie czyta o wiele więcej niż jest napisane? Dla mnie sensowniej jest filtrować go raz (jako dane wejściowe) przed jego zapisaniem, zamiast filtrować go za każdym razem, gdy go wyświetlasz. Czy coś pomijam, czy grupa ludzi głosowała za niepotrzebnym kosztem wydajności w tej i zaakceptowanej odpowiedzi?
jbo5112
2
Najlepsza odpowiedź dla mnie. Jest krótki i dobrze odpowiada na pytanie, jeśli mnie o to poprosisz. Czy możliwe jest zaatakowanie PHP w jakiś sposób za pomocą $ _POST lub $ _GET przy pomocy zastrzyku, czy jest to niemożliwe?
Jo Smo
och tak, tablice $ post i $ get akceptują wszystkie znaki, ale niektóre z tych znaków mogą być użyte przeciwko tobie, jeśli można je wyliczyć na opublikowanej stronie php. więc jeśli nie unikniesz znaków enkapsulujących (takich jak „,” i „), może to otworzyć wektor ataku. znak„ często jest pomijany i można go użyć do utworzenia hakowania w wierszu poleceń. ale nie pomoże ci
włamać się do
22

Aby rozwiązać problem XSS, spójrz na HTML Purifier . Jest dość konfigurowalny i ma przyzwoitą historię.

Jeśli chodzi o ataki typu SQL injection, sprawdź dane wejściowe użytkownika, a następnie uruchom je za pomocą mysql_real_escape_string (). Ta funkcja nie pokona jednak wszystkich ataków polegających na wstrzyknięciu, dlatego ważne jest, aby sprawdzić dane przed zrzuceniem ich do ciągu zapytania.

Lepszym rozwiązaniem jest użycie przygotowanych wyciągów. Biblioteki PDO oraz mysqli rozszerzenie wsparcia nich.

Jasonbar
źródło
nie ma „najlepszego sposobu” na wykonanie czegoś takiego jak dezynfekcja danych wejściowych. Użyj biblioteki, oczyszczacz HTML jest dobry. Biblioteki te były wielokrotnie rozbudowywane. Jest więc o wiele bardziej kuloodporny niż wszystko, co możesz sam
wymyślić
Zobacz także bioinformatics.org/phplabware/internal_utilities/htmPrawo . Z mojego zrozumienia, WordPress używa starszej wersji, core.trac.wordpress.org/browser/tags/2.9.2/wp-include/kses.php
Steve Clay
Problem z wordpress polega na tym, że niekoniecznie jest to atak wstrzykiwania php-sql, który powoduje naruszenie bazy danych. Brak zaprogramowanych wtyczek, które przechowują dane ujawnione przez zapytanie XML, jest bardziej problematyczny.
drtechno
17

Jedną sztuczką, która może pomóc w konkretnych okolicznościach, w których masz stronę podobną do /mypage?id=53i używasz id w klauzuli WHERE, jest upewnienie się, że id zdecydowanie jest liczbą całkowitą, tak jak:

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

Ale oczywiście eliminuje to tylko jeden konkretny atak, więc przeczytaj pozostałe odpowiedzi. (I tak, wiem, że powyższy kod nie jest świetny, ale pokazuje konkretną obronę.)

Hamish Downer
źródło
11
Zamiast tego używam $ id = intval ($ id) :)
Duc Tran
Rzucanie liczb całkowitych jest dobrym sposobem na upewnienie się, że wstawiane są tylko dane liczbowe.
test
1
$id = (int)$_GET['id']i $que = sprintf('SELECT ... WHERE id="%d"', $id)jest też dobry
vladkras,
16

Metody dezynfekcji danych wejściowych użytkownika za pomocą PHP:

  • Używaj nowoczesnych wersji MySQL i PHP.

  • Ustaw jawnie zestaw znaków:

    • $ mysqli-> set_charset ("utf8");
      podręcznik
    • $ pdo = nowy PDO ('mysql: host = localhost; dbname = testdb; charset = UTF8', $ user, $ password);
      podręcznik
    • $ pdo-> exec ("set names utf8");
      podręcznik
    • $ pdo = nowy PDO (
      „mysql: host = $ host; dbname = $ db”, $ user, $ pass, 
      szyk(
      PDO :: ATTR_ERRMODE => PDO :: ERRMODE_EXCEPTION,
      PDO :: MYSQL_ATTR_INIT_COMMAND => „SET NAMES utf8”
      )
      );
      podręcznik
    • mysql_set_charset ('utf8')
      [przestarzałe w PHP 5.5.0, usunięte w PHP 7.0.0].
  • Używaj bezpiecznych zestawów znaków:

    • Wybierz utf8, latin1, ascii .., nie używaj wrażliwych zestawów znaków big5, cp932, gb2312, gbk, sjis.
  • Użyj funkcji przestrzennej:

    • MySQLi przygotowało instrukcje:
      $ stmt = $ mysqli-> przygotuj ('WYBIERZ * Z testu GDZIE nazwa =? LIMIT 1'); 
      $ param = "'OR 1 = 1 / *";
      $ stmt-> bind_param ('s', $ param);
      $ stmt-> execute ();
    • PDO :: quote () - umieszcza cudzysłowy wokół ciągu wejściowego (jeśli jest wymagany) i zmienia znaki specjalne w ciągu wejściowym, używając stylu cytowania odpowiedniego dla sterownika:

      $ pdo = nowy PDO ('mysql: host = localhost; dbname = testdb; charset = UTF8', $ user, $ password); jawnie ustaw zestaw znaków
      $ pdo-> setAttribute (PDO :: ATTR_EMULATE_PREPARES, false); wyłącz emulowanie przygotowanych instrukcji, aby uniknąć powrotu do emulacji instrukcji, których MySQL nie może przygotować natywnie (aby zapobiec wstrzyknięciu)
      $ var = $ pdo-> quote ("OR 1 = 1 / *"); nie tylko unika literału, ale także cytuje go (w postaci pojedynczych cudzysłowów) $ stmt = $ pdo-> zapytanie („WYBIERZ * Z testu GDZIE nazwa = $ var LIMIT 1”);

    • Instrukcje przygotowane przez PDO : instrukcje przygotowane przez MySQLi obsługują więcej sterowników baz danych i nazwane parametry:

      $ pdo = nowy PDO ('mysql: host = localhost; dbname = testdb; charset = UTF8', $ user, $ password); jawnie ustaw zestaw znaków
      $ pdo-> setAttribute (PDO :: ATTR_EMULATE_PREPARES, false); wyłącz emulowanie przygotowanych instrukcji, aby uniknąć powrotu do emulacji instrukcji, których MySQL nie może przygotować natywnie (aby zapobiec wstrzyknięciu) $ stmt = $ pdo-> przygotuj ('WYBIERZ * Z testu GDZIE nazwa =? LIMIT 1'); $ stmt-> execute ([”OR 1 = 1 / *”]);

    • mysql_real_escape_string [przestarzałe w PHP 5.5.0, usunięte w PHP 7.0.0].
    • mysqli_real_escape_string Ucieka znaki specjalne w ciągu znaków do użycia w instrukcji SQL, biorąc pod uwagę aktualny zestaw znaków połączenia. Zalecane jest jednak stosowanie gotowych instrukcji, ponieważ nie są one po prostu ciągami znaków ucieczki, instrukcja zawiera kompletny plan wykonania zapytań, w tym których tabel i indeksów użyłaby, jest to zoptymalizowany sposób.
    • Używaj pojedynczych cudzysłowów ('') wokół zmiennych w zapytaniu.
  • Sprawdź, czy zmienna zawiera to, czego oczekujesz:

    • Jeśli oczekujesz liczby całkowitej, użyj:
      ctype_digit - Sprawdź znaki numeryczne; 
      wartość $ = (int) $ wartość;
      wartość $ = intval (wartość $);
      $ var = filter_var ('0755', FILTER_VALIDATE_INT, $ options);
    • Do użycia ciągów:
      is_string () - Sprawdź, czy typ zmiennej jest łańcuchem

      Użyj funkcji filtrowania filter_var () - filtruje zmienną o określonym filtrze:
      $ email = filter_var ($ email, FILTER_SANITIZE_EMAIL); 
      $ newstr = filter_var ($ str, FILTER_SANITIZE_STRING);
      więcej predefiniowanych filtrów
    • filter_input () - Pobiera określoną zmienną zewnętrzną według nazwy i opcjonalnie ją filtruje:
      $ search_html = filtr_wejściowy (INPUT_GET, 'szukaj', FILTER_SANITIZE_SPECIAL_CHARS);
    • preg_match () - Wykonuje dopasowanie wyrażenia regularnego;
    • Napisz własną funkcję sprawdzania poprawności.
Mark Martin
źródło
11

Opisujesz tutaj dwa osobne problemy:

  1. Odkażanie / filtrowanie danych wejściowych użytkownika.
  2. Wyjście wyjściowe.

1) Należy zawsze zakładać, że dane wprowadzone przez użytkownika są złe.

Używanie przygotowanych instrukcji lub filtrowanie za pomocą mysql_real_escape_string jest zdecydowanie koniecznością. PHP ma również wbudowane filter_input, co jest dobrym miejscem do rozpoczęcia.

2) Jest to duży temat i zależy od kontekstu danych wyjściowych. W przypadku HTML istnieją rozwiązania takie jak htmlpurifier. jako ogólna zasada, zawsze unikaj wszystkiego, co wysyłasz.

Oba problemy są o wiele za duże, aby je omówić w jednym poście, ale istnieje wiele postów, które są bardziej szczegółowe:

Metody Wyjście PHP

Bezpieczniejsze wyjście PHP

Andrzej
źródło
9

Jeśli korzystasz z PostgreSQL, dane wejściowe z PHP można zmienić za pomocą pg_escape_string ()

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

Z dokumentacji ( http://php.net/manual/es/function.pg-escape-string.php ):

pg_escape_string () ucieka z ciągu znaków do zapytania do bazy danych. Zwraca łańcuch znaków w formacie PostgreSQL bez cudzysłowów.

Alejandro Silva
źródło
1
pg_escape_literal () to zalecana funkcja do użycia w PostgreSQL.
tajemniczy
8

Nie ma żadnej funkcji catchall, ponieważ należy rozwiązać wiele problemów.

  1. SQL Injection - Dzisiaj, ogólnie rzecz biorąc, każdy projekt PHP powinien używać przygotowanych instrukcji za pośrednictwem PHP Data Objects (PDO) jako najlepszej praktyki, zapobiegając błędom z błędnego cytatu, a także w pełni funkcjonalnego rozwiązania przeciwko iniekcji . Jest to również najbardziej elastyczny i bezpieczny sposób dostępu do bazy danych.

    Sprawdź (Jedyny właściwy) samouczek PDO, aby uzyskać prawie wszystko, co musisz wiedzieć o PDO. (Szczere podziękowania dla najlepszego współtwórcy SO, @YourCommonSense, za ten wspaniały zasób na ten temat.)

  2. XSS - dezynfekuj dane w drodze w ...

    • Oczyszczacz HTML istnieje już od dawna i jest ciągle aktywnie aktualizowany. Możesz go użyć do oczyszczenia złośliwych danych wejściowych, jednocześnie pozwalając na hojną i konfigurowalną białą listę tagów. Działa świetnie z wieloma edytorami WYSIWYG, ale w niektórych przypadkach może być ciężki.

    • W innych przypadkach, w których wcale nie chcemy akceptować HTML / JavaScript, ta prosta funkcja jest przydatna (i przeszedłem wiele kontroli w stosunku do 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 - dezynfekuj dane po wyjściu ... chyba że zagwarantujesz, że dane zostały odpowiednio zdezynfekowane przed dodaniem ich do bazy danych, musisz je zdezynfekować przed wyświetleniem ich użytkownikowi, możemy wykorzystać te przydatne funkcje PHP:

    • Gdy dzwonisz echolub printwyświetlasz wartości dostarczone przez użytkownika, używaj, htmlspecialcharschyba że dane zostały odpowiednio zdezynfekowane, bezpieczne i mogą wyświetlać HTML.
    • json_encode to bezpieczny sposób na dostarczanie wartości dostarczanych przez użytkowników z PHP na Javascript
  4. Czy wywołujesz zewnętrzne polecenia powłoki za pomocą exec()lub system()funkcji, czy do backtickoperatora? Jeśli tak, oprócz SQL Injection i XSS możesz mieć dodatkowe obawy dotyczące rozwiązania problemu przez użytkowników wykonujących złośliwe polecenia na twoim serwerze . Musisz użyć, escapeshellcmdjeśli chcesz uniknąć całej komendy LUB, escapeshellargaby uniknąć pojedynczych argumentów.

webaholik
źródło
czy zamiast tego można użyć mb_encode_numericentity? Skoro wszystko koduje?
drtechno
@drtechno - mb_encode_numericentityjest omawiany w htmlspecialcharslinku na # 3 XSS
webaholik
5

Najłatwiejszym sposobem uniknięcia błędów w odkażaniu danych wejściowych i ucieczce danych jest użycie frameworka PHP takiego jak Symfony , Nette itp. Lub jego części (silnik szablonów, warstwa bazy danych, ORM).

Silnik szablonów, taki jak Twig lub Latte, ma domyślnie włączanie ucieczki danych wyjściowych - nie musisz rozwiązywać ręcznie, jeśli odpowiednio uciekłeś od danych wyjściowych w zależności od kontekstu (część strony HTML lub JavaScript).

Framework automatycznie odkaża dane wejściowe i nie należy używać zmiennych $ _POST, $ _GET lub $ _SESSION bezpośrednio, ale poprzez mechanizm taki jak routing, obsługa sesji itp.

A dla warstwy bazy danych (modelu) istnieją frameworki ORM, takie jak Doctrine, lub owijarki wokół PDO, takie jak Nette Database.

Możesz przeczytać więcej na ten temat tutaj - Co to jest struktura oprogramowania?

Ondřej Šotek
źródło
3

Chciałem tylko dodać, że jeśli chodzi o ucieczkę danych wyjściowych, jeśli użyjesz php DOMDocument do utworzenia pliku wyjściowego HTML, automatycznie ucieknie we właściwym kontekście. Atrybut (wartość = "") i tekst wewnętrzny <span> nie są równe. Aby zabezpieczyć się przed XSS, przeczytaj to: OWASP XSS Prevention Cheat Sheet

użytkownik138720
źródło
2

Nigdy nie dezynfekujesz danych wejściowych.

Zawsze odkażasz wyjście.

Transformacje, które stosuje się do danych, aby można je było bezpiecznie uwzględnić w instrukcji SQL, są całkowicie różne od tych, które ubiegasz się o włączenie do HTML, są całkowicie różne od tych, o które wnioskujesz o włączenie do Javascript, są całkowicie różne od tych, o które ubiegasz się o włączenie do LDIF. zupełnie inne niż te, które stosuje się do włączenia do CSS, są całkowicie różne od tych, które stosuje się do włączenia do e-maila ...

Za wszelką cenę wejścia validate - zdecydować, czy należy przyjąć ją do dalszego przetwarzania lub poinformować użytkownika, że jest nie do przyjęcia. Ale nie stosuj żadnych zmian w reprezentacji danych, dopóki nie opuści ziemi PHP.

Dawno, dawno temu ktoś próbował wymyślić uniwersalny mechanizm dla wszystkich mechanizmów ucieczki danych, a my otrzymaliśmymagic_quotes ”, które nie poprawiły właściwie danych dla wszystkich celów wyjściowych i spowodowały, że instalacja wymagała innego kodu do działania.

symcbean
źródło
Jednym z problemów jest to, że nie zawsze jest to atak bazy danych, a wszystkie dane wejściowe użytkownika powinny być chronione przed systemem. nie tylko jeden typ języka. Tak więc w twoich witrynach, kiedy wyliczysz swoje dane $ _POST, nawet przy użyciu wiązania, może uciekać na tyle, aby wykonać powłokę lub nawet inny kod php.
drtechno
„nie zawsze jest to atak bazy danych”: „Przekształcenia, które stosujesz do danych, aby uczynić je bezpiecznymi do włączenia do instrukcji SQL, są zupełnie inne niż te…”
symcbean
„wszystkie dane wejściowe użytkownika powinny być chronione przed systemem”: nie, system powinien być chroniony przed danymi wejściowymi użytkownika.
symcbean
No cóż, zabrakło mi słów, ale tak, należy zapobiegać wpływowi danych wejściowych na działanie systemu. wyjaśnić to ...
drtechno
Zarówno wejście, jak i wyjście powinny zostać zdezynfekowane.
Tajni
1

Nigdy nie ufaj danym użytkownika.

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

W trim()usuwa białe znaki funkcyjne i inne predefiniowane znaki po obu stronach łańcucha.

stripslashes()Funkcja usuwa backslashy

Ta htmlspecialchars()funkcja przekształca niektóre predefiniowane znaki w encje HTML.

Predefiniowane znaki to:

& (ampersand) becomes &amp;
" (double quote) becomes &quot;
' (single quote) becomes &#039;
< (less than) becomes &lt;
> (greater than) becomes &gt;
Erik Thiart
źródło
1
Przed czym to chroni? Czy to dla XSS? Dlaczego więc się nazywa clean_input? Dlaczego miałbyś chcieć usuwać ukośniki?
Dharman
4
OSTRZEŻENIE: To nie magicznie czyni dane użytkownika bezpiecznymi. Ta funkcja niepotrzebnie uszkodzi twoje dane bez ochrony przed czymkolwiek. NIE UŻYWAJ TEGO!
Dharman
Twoje oświadczenie jest fałszywe.
Erik Thiart,
0

Istnieje rozszerzenie filtra ( howto-link , manual ), które działa całkiem dobrze ze wszystkimi zmiennymi GPC. Nie jest to jednak magiczne zrób to wszystko, nadal będziesz musiał z niego korzystać.

Do
źródło