Làm cách nào tôi có thể vệ sinh đầu vào của người dùng bằng PHP?


1124

Có một chức năng bắt ở đâu đó hoạt động tốt để vệ sinh đầu vào của người dùng cho các cuộc tấn công SQL và XSS, trong khi vẫn cho phép một số loại thẻ HTML nhất định không?


42
Ngày nay, để tránh tiêm sql, hãy sử dụng PDO hoặc MySQLi.
Francisco Presencia

76
Sử dụng PDO hoặc MySQLi là không đủ. Nếu bạn xây dựng các câu lệnh SQL của mình với dữ liệu không đáng tin cậy select * from users where name='$name', thì sẽ không có vấn đề gì nếu bạn sử dụng PDO hoặc MySQLi hoặc MySQL. Bạn vẫn đang gặp nguy hiểm. Bạn phải sử dụng các truy vấn được tham số hóa hoặc, nếu bạn phải, sử dụng các cơ chế thoát trên dữ liệu của mình, nhưng điều đó ít được ưu tiên hơn.
Andy Lester

26
@AndyLester Bạn đang ám chỉ rằng ai đó sử dụng PDO mà không có tuyên bố chuẩn bị? :)

64
Tôi đang nói rằng "Sử dụng PDO hoặc MySQLi" không đủ thông tin để giải thích cho người mới về cách sử dụng chúng một cách an toàn. Bạn và tôi biết rằng các tuyên bố đã chuẩn bị có vấn đề, nhưng tôi không cho rằng mọi người đọc câu hỏi này sẽ biết nó. Đó là lý do tại sao tôi thêm các hướng dẫn rõ ràng.
Andy Lester

30
Nhận xét của Andy là hoàn toàn hợp lệ. Tôi đã chuyển đổi trang web mysql của mình sang PDO gần đây với suy nghĩ rằng bây giờ tôi đã an toàn trước các cuộc tấn công tiêm chích. Chỉ trong quá trình tôi nhận ra rằng một số câu lệnh sql của tôi vẫn được xây dựng bằng cách sử dụng đầu vào của người dùng. Sau đó tôi đã sửa nó bằng cách sử dụng các báo cáo đã chuẩn bị. Đối với một người mới hoàn toàn, không hoàn toàn rõ ràng rằng có một sự khác biệt khi nhiều chuyên gia đưa ra nhận xét về việc sử dụng PDO nhưng không chỉ định sự cần thiết cho các tuyên bố đã chuẩn bị. Giả định rằng điều này là rõ ràng. Nhưng không đến một người mới.
GhostRider

Câu trả lời:


1184

Đó là một quan niệm sai lầm phổ biến rằng đầu vào của người dùng có thể được lọc. PHP thậm chí còn có một "tính năng" (hiện không dùng nữa), được gọi là trích dẫn ma thuật , dựa trên ý tưởng này. Đó là vô nghĩa. Hãy quên việc lọc (hoặc làm sạch, hoặc bất cứ điều gì mọi người gọi nó).

Những gì bạn nên làm, để tránh các vấn đề, khá đơn giản: bất cứ khi nào bạn nhúng một chuỗi trong mã nước ngoài, bạn phải thoát nó, theo các quy tắc của ngôn ngữ đó. Ví dụ: nếu bạn nhúng một chuỗi trong một số SQL nhắm mục tiêu vào MySQL, bạn phải thoát khỏi chuỗi có chức năng của MySQL cho mục đích này ( mysqli_real_escape_string). (Hoặc, trong trường hợp cơ sở dữ liệu, sử dụng các câu lệnh được chuẩn bị là cách tiếp cận tốt hơn, khi có thể.)

Một ví dụ khác là HTML: Nếu bạn nhúng các chuỗi trong đánh dấu HTML, bạn phải thoát nó bằng htmlspecialchars. Điều này có nghĩa là mọi đơn echohoặc printtuyên bố nên sử dụng htmlspecialchars.

Một ví dụ thứ ba có thể là các lệnh shell: Nếu bạn định nhúng các chuỗi (chẳng hạn như đối số) vào các lệnh bên ngoài và gọi chúng bằng exec, thì bạn phải sử dụng escapeshellcmdescapeshellarg.

Vân vân và vân vân ...

Các chỉ trường hợp bạn cần phải chủ động lọc dữ liệu, là nếu bạn chấp nhận đầu vào dạng sẵn. Ví dụ: nếu bạn cho phép người dùng của mình đăng đánh dấu HTML, bạn dự định hiển thị trên trang web. Tuy nhiên, bạn nên khôn ngoan để tránh điều này bằng mọi giá, vì cho dù bạn có lọc nó tốt đến đâu, nó sẽ luôn là một lỗ hổng bảo mật tiềm năng.


245
"Điều này có nghĩa là mỗi câu lệnh echo hoặc print nên sử dụng htmlspecialchars" - tất nhiên, ý bạn là "mọi ... câu lệnh xuất ra đầu vào của người dùng"; htmlspecialchars () - ifying "echo 'Xin chào, thế giới!';" sẽ là điên rồ;)
Bobby Jack

10
Có một trường hợp mà tôi nghĩ rằng lọc là giải pháp phù hợp: UTF-8. Bạn không muốn các chuỗi UTF-8 không hợp lệ trên tất cả ứng dụng của mình (bạn có thể nhận được phục hồi lỗi khác nhau tùy theo đường dẫn mã) và UTF-8 có thể được lọc (hoặc từ chối) một cách dễ dàng.
Kornel

6
@jbyrd - không, THÍCH sử dụng ngôn ngữ regrec chuyên dụng. Bạn sẽ phải thoát chuỗi đầu vào của mình hai lần - một lần cho biểu thức chính quy và một lần cho mã hóa chuỗi mysql. Đó là mã trong mã trong mã.
troelskn

6
Tại thời điểm này mysql_real_escape_stringlà không tán thành. Ngày nay, nó được coi là thực hành tốt để sử dụng các câu lệnh được chuẩn bị để ngăn chặn việc tiêm SQL. Vì vậy, chuyển sang MySQLi hoặc PDO.
Marcel Korpel

4
Bởi vì bạn hạn chế bề mặt tấn công. Nếu bạn vệ sinh sớm (khi nhập liệu), bạn phải chắc chắn rằng không có lỗ hổng nào khác trong ứng dụng mà dữ liệu xấu có thể xâm nhập. Trong khi đó, nếu bạn làm điều đó muộn thì chức năng đầu ra của bạn sẽ không phải "tin tưởng" rằng nó được cung cấp dữ liệu an toàn - đơn giản là nó cho rằng mọi thứ đều không an toàn.
troelskn

217

Đừng cố gắng ngăn SQL tiêm bằng cách vệ sinh dữ liệu đầu vào.

Thay vào đó, không cho phép dữ liệu được sử dụng trong việc tạo mã SQL của bạn . Sử dụng Báo cáo đã chuẩn bị (nghĩa là sử dụng tham số trong truy vấn mẫu) sử dụng các biến bị ràng buộc. Đây là cách duy nhất để được bảo đảm chống lại việc tiêm SQL.

Vui lòng xem trang web của tôi http://bobby-tables.com/ để biết thêm về cách ngăn chặn việc tiêm SQL.


18
Hoặc truy cập tài liệu chính thức và tìm hiểu PDO và các báo cáo được chuẩn bị. Đường cong học tập nhỏ, nhưng nếu bạn biết SQL khá tốt, bạn sẽ không gặp khó khăn gì trong việc thích nghi.
một lập trình viên

2
Đối với trường hợp cụ thể của SQL Injection, đây là câu trả lời đúng!
Scott Arciszewski

4
Lưu ý rằng các câu lệnh được chuẩn bị không thêm bất kỳ bảo mật nào, các truy vấn được tham số hóa làm. Chúng tình cờ rất dễ sử dụng cùng nhau trong PHP.
Cơ bản

Nó không phải là cách duy nhất được đảm bảo. Hex đầu vào và không chính xác trong truy vấn cũng sẽ ngăn chặn. Ngoài ra các cuộc tấn công hex là không thể nếu bạn sử dụng hexing phải.
Ramon Bakker

Điều gì xảy ra nếu bạn nhập một cái gì đó chuyên biệt, như địa chỉ email hoặc tên người dùng?
Abraham Brookes

79

Không. Bạn không thể lọc dữ liệu một cách khái quát mà không có bất kỳ bối cảnh nào. Đôi khi bạn muốn lấy một truy vấn SQL làm đầu vào và đôi khi bạn muốn lấy HTML làm đầu vào.

Bạn cần lọc đầu vào trên danh sách trắng - đảm bảo rằng dữ liệu phù hợp với một số đặc điểm kỹ thuật của những gì bạn mong đợi. Sau đó, bạn cần phải thoát nó trước khi bạn sử dụng nó, tùy thuộc vào bối cảnh bạn đang sử dụng nó.

Quá trình thoát dữ liệu cho SQL - để ngăn chặn SQL tiêm - rất khác với quy trình thoát dữ liệu cho (X) HTML, để ngăn XSS.


52

PHP hiện có các hàm filter_input đẹp mới, ví dụ như giải phóng bạn khỏi việc tìm kiếm 'regex e-mail cuối cùng' bây giờ có loại FILTER_VALIDATE_EMAIL tích hợp

Lớp bộ lọc của riêng tôi (sử dụng JavaScript để làm nổi bật các trường bị lỗi) có thể được bắt đầu bằng yêu cầu ajax hoặc bài đăng mẫu thông thường. (xem ví dụ dưới đây)

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



}

Tất nhiên, hãy nhớ rằng bạn cũng cần thực hiện thoát truy vấn sql của mình tùy thuộc vào loại db bạn đang sử dụng (ví dụ: mysql_real_escape_opes () là vô dụng đối với máy chủ sql). Bạn có thể muốn xử lý việc này tự động ở lớp ứng dụng thích hợp của bạn như ORM. Ngoài ra, như đã đề cập ở trên: để xuất ra html, hãy sử dụng các hàm dành riêng cho php khác như htmlspecialchars;)

Để thực sự cho phép nhập HTML với các lớp và / hoặc thẻ giống như bị tước phụ thuộc vào một trong các gói xác thực xss chuyên dụng. KHÔNG VIẾT ĐĂNG KÝ CỦA RIÊNG BẠN ĐỂ PARSE HTML!


18
Điều này có vẻ như nó có thể là một kịch bản tiện dụng để xác nhận đầu vào, nhưng nó hoàn toàn không liên quan đến câu hỏi.
rjmunro

43

Không có.

Trước hết, SQL tiêm là một vấn đề lọc đầu vào và XSS là đầu ra thoát một - vì vậy bạn thậm chí sẽ không thực hiện hai thao tác này cùng một lúc trong vòng đời mã.

Quy tắc cơ bản của ngón tay cái

  • Đối với truy vấn SQL, liên kết các tham số (như với PDO) hoặc sử dụng chức năng thoát gốc trình điều khiển cho các biến truy vấn (chẳng hạn như mysql_real_escape_string())
  • Sử dụng strip_tags()để lọc HTML không mong muốn
  • Thoát tất cả các đầu ra khác với htmlspecialchars()và chú ý đến các tham số thứ 2 và thứ 3 tại đây.

1
Vì vậy, bạn chỉ sử dụng Strip_tags () hoặc htmlspecialchars () khi bạn biết rằng đầu vào có HTML mà bạn muốn loại bỏ hoặc thoát tương ứng - bạn không sử dụng nó cho bất kỳ mục đích bảo mật nào phải không? Ngoài ra, khi bạn thực hiện liên kết, nó làm gì cho những thứ như Bảng Bobby? "Robert '); DROP TABLE Học sinh; -" Nó chỉ thoát khỏi dấu ngoặc kép?
Robert Mark Bram

2
Nếu bạn có dữ liệu người dùng sẽ đi vào cơ sở dữ liệu và sau đó sẽ được hiển thị trên các trang web, thì nó có thường được đọc nhiều hơn so với dữ liệu được viết không? Đối với tôi, việc lọc nó một lần (như đầu vào) sẽ có ý nghĩa hơn trước khi bạn lưu trữ nó, thay vì phải lọc nó mỗi khi bạn hiển thị nó. Tôi có thiếu điều gì không hoặc đã có một loạt người bỏ phiếu cho hiệu suất không cần thiết trong phần này và câu trả lời được chấp nhận?
jbo5112

2
Câu trả lời tốt nhất cho tôi. Nó ngắn và giải quyết tốt câu hỏi nếu bạn hỏi tôi. Có thể tấn công PHP bằng cách nào đó thông qua $ _POST hoặc $ _GET bằng một số mũi tiêm hay điều này là không thể?
Jo Smo

ồ đúng, các mảng $ post và $ get chấp nhận tất cả các ký tự, nhưng một số ký tự đó có thể được sử dụng để chống lại bạn nếu ký tự được phép liệt kê trong trang php đã đăng. vì vậy nếu bạn không thoát khỏi các ký tự đóng gói (như ", 'và`) thì nó có thể mở ra một vectơ tấn công. Ký tự `thường bị bỏ qua và có thể được sử dụng để tạo ra các hack thực thi dòng lệnh. nhưng sẽ không giúp bạn với việc hack tường lửa ứng dụng web.
drtechno

22

Để giải quyết vấn đề XSS, hãy xem Bộ lọc HTML . Nó là cấu hình khá và có một hồ sơ theo dõi khá.

Đối với các cuộc tấn công tiêm nhiễm SQL, hãy đảm bảo bạn kiểm tra đầu vào của người dùng và sau đó chạy nó mặc dù mysql_real_escape_opes (). Tuy nhiên, chức năng sẽ không đánh bại tất cả các cuộc tấn công tiêm chích, vì vậy điều quan trọng là bạn phải kiểm tra dữ liệu trước khi đưa nó vào chuỗi truy vấn của mình.

Một giải pháp tốt hơn là sử dụng các báo cáo chuẩn bị. Các thư viện PDO và mở rộng mysqli hỗ trợ này.


không có "cách tốt nhất" để làm một cái gì đó như vệ sinh đầu vào .. Sử dụng một số thư viện, bộ lọc html là tốt. Những thư viện này đã bị dồn nén nhiều lần. Vì vậy, nó có khả năng chống đạn cao hơn nhiều so với bất cứ thứ gì bạn có thể tự mình nghĩ ra
paan

Xem thêm bioinformatics.org/phplabware/iternal_utilities/htmLawed . Theo hiểu biết của tôi, WordPress sử dụng phiên bản cũ hơn, core.trac.wordpress.org/browser/tags/2.9.2/wp-includes/kses.php
Steve Clay

Vấn đề với wordpress là nó không nhất thiết là một cuộc tấn công tiêm php-sql gây ra vi phạm cơ sở dữ liệu. Bỏ lỡ các plugin được lập trình lưu trữ dữ liệu mà truy vấn xml tiết lộ bí mật có vấn đề hơn.
drtechno


17

Một mẹo có thể giúp trong trường hợp cụ thể nơi bạn có một trang thích /mypage?id=53và bạn sử dụng id trong mệnh đề WHERE là đảm bảo id chắc chắn là một số nguyên, như vậy:

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

Nhưng tất nhiên chỉ cắt bỏ một cuộc tấn công cụ thể, vì vậy hãy đọc tất cả các câu trả lời khác. (Và vâng tôi biết rằng đoạn mã trên không tuyệt vời, nhưng nó cho thấy sự phòng thủ cụ thể.)


11
Thay vào đó, tôi sử dụng $ id = intval ($ id) :)
Đức Trần

Đúc số nguyên là một cách tốt để đảm bảo chỉ có dữ liệu số được chèn vào.
kiểm tra

1
$id = (int)$_GET['id']$que = sprintf('SELECT ... WHERE id="%d"', $id)cũng tốt
vladkras

16

Các phương pháp vệ sinh đầu vào của người dùng bằng PHP:

  • Sử dụng các phiên bản hiện đại của MySQL và PHP.

  • Đặt bộ ký tự rõ ràng:

    • $ mysqli-> set_charset ("utf8");
      hướng dẫn sử dụng
    • $ pdo = new PDO ('mysql: host = localhost; dbname = testdb; charset = UTF8', $ user, $ password);
      hướng dẫn sử dụng
    • $ pdo-> exec ("đặt tên utf8");
      hướng dẫn sử dụng
    • $ pdo = PDO mới (
      "mysql: host = $ host; dbname = $ db", $ user, $ pass, 
      mảng(
      PDO :: ATTR_ERRMODE => PDO :: ERRMODE_EXCEPTION,
      PDO :: MYSQL_ATTR_INIT_COMMAND => "THIẾT LẬP TÊN utf8"
      )
      );
      hướng dẫn sử dụng
    • mysql_set_charset ('utf8')
      [không dùng nữa trong PHP 5.5.0, đã bị xóa trong PHP 7.0.0].
  • Sử dụng bộ ký tự an toàn:

    • Chọn utf8, latin1, ascii .., không sử dụng bảng mã dễ bị tổn thương big5, cp932, gb2312, gbk, sjis.
  • Sử dụng chức năng không gian:

    • Báo cáo chuẩn bị của MySQLi:
      $ stmt = $ mysqli-> chuẩn bị ('CHỌN * TỪ bài kiểm tra WHERE tên =? GIỚI HẠN 1'); 
      $ param = "'HOẶC 1 = 1 / *";
      $ stmt-> bind_param ('s', $ param);
      $ stmt-> thực thi ();
    • PDO :: quote () - đặt dấu ngoặc kép quanh chuỗi đầu vào (nếu cần) và thoát các ký tự đặc biệt trong chuỗi đầu vào, sử dụng kiểu trích dẫn phù hợp với trình điều khiển bên dưới:

      $ pdo = new PDO ('mysql: host = localhost; dbname = testdb; charset = UTF8', $ user, $ password); bộ rõ ràng bộ ký tự
      $ pdo-> setAttribution (PDO :: ATTR_EMULATE_PREPARES, false); vô hiệu hóa các câu lệnh được chuẩn bị để mô phỏng dự phòng để mô phỏng các câu lệnh mà MySQL không thể chuẩn bị một cách tự nhiên (để ngăn chặn việc tiêm)
      $ var = $ pdo-> quote ("'OR 1 = 1 / *"); không chỉ thoát khỏi nghĩa đen mà còn trích dẫn nó (bằng các ký tự trích dẫn đơn) $ stmt = $ pdo-> truy vấn ("CHỌN * TỪ kiểm tra WHERE tên = $ var LIMIT 1");

    • Báo cáo được chuẩn bị PDO : so với các câu lệnh được chuẩn bị của MySQLi hỗ trợ nhiều trình điều khiển cơ sở dữ liệu hơn và các tham số được đặt tên:

      $ pdo = new PDO ('mysql: host = localhost; dbname = testdb; charset = UTF8', $ user, $ password); bộ rõ ràng bộ ký tự
      $ pdo-> setAttribution (PDO :: ATTR_EMULATE_PREPARES, false); vô hiệu hóa các câu lệnh đã được chuẩn bị để ngăn chặn dự phòng cho các câu lệnh mô phỏng mà MySQL không thể chuẩn bị nguyên bản (để ngăn ngừa tiêm) $ stmt = $ pdo-> chuẩn bị ('CHỌN * TỪ bài kiểm tra WHERE tên =? GIỚI HẠN 1'); $ stmt-> thực thi (["'HOẶC 1 = 1 / *"]);

    • mysql_real_escape_opes [không dùng nữa trong PHP 5.5.0, đã bị xóa trong PHP 7.0.0].
    • mysqli_real_escape_opes Thoát các ký tự đặc biệt trong một chuỗi để sử dụng trong câu lệnh SQL, có tính đến bộ ký tự hiện tại của kết nối. Nhưng nên sử dụng Báo cáo đã chuẩn bị bởi vì chúng không chỉ đơn giản là thoát chuỗi, một câu lệnh đi kèm với một kế hoạch thực hiện truy vấn hoàn chỉnh, bao gồm các bảng và chỉ mục mà nó sẽ sử dụng, đó là một cách tối ưu hóa.
    • Sử dụng dấu ngoặc đơn ('') xung quanh các biến của bạn trong truy vấn của bạn.
  • Kiểm tra biến chứa những gì bạn đang mong đợi:

    • Nếu bạn đang mong đợi một số nguyên, hãy sử dụng:
      ctype_digit - Kiểm tra (các) ký tự số; 
      $ value = (int) $ value;
      $ value = intval ($ value);
      $ var = filter_var ('0755', FILTER_VALIDATE_INT, $ tùy chọn);
    • Đối với chuỗi sử dụng:
      is_opes () - Tìm xem loại biến có phải là chuỗi không

      Sử dụng chức năng lọc Filter_var () - lọc một biến với bộ lọc được chỉ định:
      $ email = filter_var ($ email, FILTER_SANITIZE_EMAIL); 
      $ newstr = filter_var ($ str, FILTER_SANITIZE_STRING);
      nhiều bộ lọc được xác định trước
    • filter_input () - Nhận một biến ngoài cụ thể theo tên và tùy ý lọc nó:
      $ search_html = filter_input (INPUT_GET, 'search', FILTER_SANITIZEinksECIAL_CHARS);
    • preg_match () - Thực hiện khớp biểu thức chính quy;
    • Viết chức năng xác nhận của riêng bạn.

11

Những gì bạn đang mô tả ở đây là hai vấn đề riêng biệt:

  1. Vệ sinh / lọc dữ liệu đầu vào của người dùng.
  2. Thoát khỏi đầu ra.

1) Đầu vào của người dùng phải luôn luôn được coi là xấu.

Sử dụng các câu lệnh đã chuẩn bị, hoặc / và lọc với mysql_real_escape_opes chắc chắn là điều bắt buộc. PHP cũng có bộ lọc được xây dựng trong đó là một nơi tốt để bắt đầu.

2) Đây là một chủ đề lớn và nó phụ thuộc vào ngữ cảnh của dữ liệu được xuất ra. Đối với HTML, có các giải pháp như htmlpurifier ngoài kia. như một quy tắc của ngón tay cái, luôn luôn thoát khỏi bất cứ điều gì bạn xuất ra.

Cả hai vấn đề đều quá lớn để đi sâu vào một bài viết, nhưng có rất nhiều bài viết đi sâu vào chi tiết hơn:

Phương thức đầu ra PHP

Đầu ra PHP an toàn hơn


9

Nếu bạn đang sử dụng PostgreSQL, đầu vào từ PHP có thể được thoát bằng pg_escape_opes ()

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

Từ tài liệu ( http://php.net/manual/es/feft.pg-escape-opes.php ):

pg_escape_opes () thoát một chuỗi để truy vấn cơ sở dữ liệu. Nó trả về một chuỗi thoát trong định dạng PostgreSQL mà không có dấu ngoặc kép.


1
pg_escape_literal () là hàm được khuyến nghị sử dụng cho PostgreSQL.
mật mã ツ

8

Không có chức năng bắt, bởi vì có nhiều mối quan tâm được giải quyết.

  1. SQL Injection - Ngày nay, nói chung, mọi dự án PHP nên sử dụng các câu lệnh được chuẩn bị thông qua Đối tượng dữ liệu PHP (PDO) như một cách thực hành tốt nhất, ngăn ngừa lỗi từ một trích dẫn đi lạc cũng như một giải pháp đầy đủ tính năng chống lại việc tiêm . Đó cũng là cách linh hoạt và an toàn nhất để truy cập cơ sở dữ liệu của bạn.

    Hãy xem hướng dẫn PDO (Chỉ thích hợp) cho hầu hết mọi thứ bạn cần biết về PDO. (Xin chân thành cảm ơn cộng tác viên SO hàng đầu, @YourCommonSense, vì tài nguyên tuyệt vời này về chủ đề này.)

  2. XSS - Vệ sinh dữ liệu trên đường trong ...

    • Bộ lọc HTML đã có từ lâu và vẫn được cập nhật tích cực. Bạn có thể sử dụng nó để vệ sinh đầu vào độc hại, trong khi vẫn cho phép danh sách trắng các thẻ có thể định cấu hình và có thể định cấu hình. Hoạt động tuyệt vời với nhiều trình soạn thảo WYSIWYG, nhưng nó có thể nặng đối với một số trường hợp sử dụng.

    • Trong các trường hợp khác, ở đó chúng tôi không muốn chấp nhận HTML / Javascript, tôi đã thấy chức năng đơn giản này hữu ích (và đã vượt qua nhiều lần kiểm tra đối với 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 - Vệ sinh dữ liệu trên đường ra ... trừ khi bạn đảm bảo dữ liệu được vệ sinh đúng cách trước khi thêm vào cơ sở dữ liệu của mình, bạn sẽ cần vệ sinh dữ liệu trước khi hiển thị cho người dùng của mình, chúng tôi có thể tận dụng các chức năng PHP hữu ích này:

    • Khi bạn gọi echohoặc printđể hiển thị các giá trị do người dùng cung cấp, hãy sử dụng htmlspecialcharstrừ khi dữ liệu được vệ sinh an toàn đúng cách và được phép hiển thị HTML.
    • json_encode là một cách an toàn để cung cấp các giá trị do người dùng cung cấp từ PHP sang Javascript
  4. Bạn có gọi các lệnh shell bên ngoài bằng cách sử dụng exec()hoặc các system()hàm hoặc đểbacktick toán tử không? Nếu vậy, ngoài SQL Injection & XSS, bạn có thể có thêm mối quan tâm để giải quyết, người dùng đang chạy các lệnh độc hại trên máy chủ của bạn . Bạn cần sử dụng escapeshellcmdnếu bạn muốn thoát toàn bộ lệnh HOẶC escapeshellargđể thoát các đối số riêng lẻ.


mb_encode_numericentity có thể được sử dụng thay thế không? Vì nó mã hóa mọi thứ?
drtechno

@drtechno - mb_encode_numericentityđược thảo luận trong htmlspecialcharsliên kết trên # 3 XSS
webaholik

5

Cách dễ nhất để tránh những sai lầm trong việc vệ sinh dữ liệu đầu vào và thoát dữ liệu là sử dụng khung công tác PHP như Symfony , Nette , v.v. hoặc một phần của khung đó (công cụ tạo khuôn mẫu, lớp cơ sở dữ liệu, ORM).

Công cụ tạo khuôn mẫu như Twig hoặc Latte có đầu ra thoát theo mặc định - bạn không phải giải quyết thủ công nếu bạn đã thoát đúng đầu ra của mình tùy thuộc vào ngữ cảnh (phần HTML hoặc Javascript của trang web).

Framework tự động vệ sinh đầu vào và bạn không nên sử dụng trực tiếp các biến $ _POST, $ _GET hoặc $ _SESSION, nhưng thông qua cơ chế như định tuyến, xử lý phiên, v.v.

Và đối với lớp cơ sở dữ liệu (mô hình), có các khung ORM như Doctrine hoặc các hàm bao quanh PDO như Nette Database.

Bạn có thể đọc thêm về nó ở đây - Khung phần mềm là gì?


3

Chỉ muốn thêm rằng về chủ đề thoát đầu ra, nếu bạn sử dụng php DOMDocument để tạo đầu ra html của mình, nó sẽ tự động thoát trong ngữ cảnh phù hợp. Một thuộc tính (value = "") và văn bản bên trong của <span> không bằng nhau. Để an toàn trước XSS, hãy đọc bài này: Bảng cheat phòng chống OWASP XSS


2

Bạn không bao giờ vệ sinh đầu vào.

Bạn luôn vệ sinh đầu ra.

Các biến đổi bạn áp dụng cho dữ liệu để đảm bảo an toàn khi đưa vào câu lệnh SQL hoàn toàn khác với các biến đổi bạn áp dụng để đưa vào HTML hoàn toàn khác với các biến đổi bạn áp dụng để đưa vào Javascript hoàn toàn khác với các biến đổi bạn áp dụng để đưa vào LDIF là hoàn toàn khác với những gì bạn áp dụng để đưa vào CSS hoàn toàn khác với những gì bạn áp dụng để đưa vào Email ....

Bằng mọi cách xác nhận đầu vào - quyết định xem bạn có nên chấp nhận nó để xử lý thêm hay nói với người dùng rằng nó không được chấp nhận. Nhưng đừng áp dụng bất kỳ thay đổi nào đối với việc thể hiện dữ liệu cho đến khi nó sắp rời khỏi vùng đất PHP.

Cách đây rất lâu, ai đó đã cố gắng phát minh ra một cơ chế phù hợp với tất cả các cơ chế để thoát dữ liệu và chúng tôi đã kết thúc với " magic_quotes ", không thoát dữ liệu đúng cho tất cả các mục tiêu đầu ra và dẫn đến việc cài đặt khác nhau đòi hỏi mã khác nhau để hoạt động.


một vấn đề với điều đó là nó không phải luôn luôn là một cuộc tấn công cơ sở dữ liệu và tất cả đầu vào của người dùng cần được bảo vệ khỏi hệ thống. không chỉ một loại ngôn ngữ. Vì vậy, trên các trang web của bạn, khi bạn liệt kê dữ liệu $ _POST của mình, ngay cả khi sử dụng liên kết, nó có thể thoát ra đủ để thực thi shell hoặc thậm chí mã php khác.
drtechno

"Nó không phải luôn luôn là một cuộc tấn công cơ sở dữ liệu": "Các biến đổi bạn áp dụng cho dữ liệu để đảm bảo an toàn khi đưa vào một câu lệnh SQL hoàn toàn khác với các ...."
symcbean

"Tất cả đầu vào của người dùng nên được bảo vệ khỏi hệ thống": không có hệ thống nào được bảo vệ khỏi đầu vào của người dùng.
symcbean

Tôi đã hết lời, nhưng đúng vậy, đầu vào cần phải được ngăn chặn để ảnh hưởng đến hoạt động của hệ thống. để làm rõ điều này ...
drtechno

Cả đầu vào và đầu ra nên được vệ sinh.
Tajni

1

Không bao giờ tin tưởng dữ liệu người dùng.

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

Các trim()Loại bỏ chức năng khoảng trắng và ký tự xác định trước khác từ cả hai phía của một chuỗi.

Các stripslashes()chức năng loại bỏ những dấu xồ nguợc

Các htmlspecialchars()chức năng chuyển đổi một số ký tự được xác định trước cho các đối tượng HTML.

Các ký tự được xác định trước là:

& (ampersand) becomes &amp;
" (double quote) becomes &quot;
' (single quote) becomes &#039;
< (less than) becomes &lt;
> (greater than) becomes &gt;

1
Điều này sẽ bảo vệ cái gì? Đây có phải là cho XSS? Tại sao nó được gọi là clean_inputsau đó? Tại sao bạn muốn tước dấu gạch chéo?
Dharman

5
CẢNH BÁO: Điều này không kỳ diệu làm cho dữ liệu người dùng an toàn. Chức năng này sẽ làm hỏng dữ liệu của bạn một cách không cần thiết mà không bảo vệ khỏi bất cứ điều gì. ĐỪNG SỬ DỤNG NÓ!
Dharman

Tuyên bố của bạn là sai.
Erik Thiart

0

Có phần mở rộng bộ lọc ( howto-link , thủ công ), hoạt động khá tốt với tất cả các biến GPC. Tuy nhiên, đây không phải là một thứ ma thuật, bạn vẫn sẽ phải sử dụng nó.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.