ฉันจะฆ่าเชื้ออินพุตของผู้ใช้ด้วย PHP ได้อย่างไร


1124

มีฟังก์ชั่น catchall บางแห่งที่ทำงานได้ดีสำหรับฆ่าเชื้ออินพุตของผู้ใช้สำหรับการฉีด SQL และการโจมตี XSS ในขณะที่ยังอนุญาตแท็ก HTML บางประเภทอยู่?


42
ทุกวันนี้เพื่อหลีกเลี่ยงการฉีด sql ให้ใช้ PDO หรือ MySQLi
Francisco Presencia

76
การใช้ PDO หรือ MySQLi ไม่เพียงพอ หากคุณสร้างคำสั่ง SQL ของคุณด้วยข้อมูลที่ไม่น่าเชื่อถือเช่นselect * from users where name='$name'นั้นไม่สำคัญว่าคุณจะใช้ PDO หรือ MySQLi หรือ MySQL คุณยังอยู่ในอันตราย คุณต้องใช้การสืบค้นแบบ Parametrized หรือหากคุณต้องใช้การหลบหลีกกลไกในข้อมูลของคุณ แต่นั่นเป็นวิธีที่ดีกว่า
Andy Lester

26
@AndyLester คุณหมายถึงว่ามีคนใช้ PDO โดยไม่ต้องมีคำสั่งที่เตรียมไว้หรือไม่ :)

64
ฉันกำลังบอกว่า "ใช้ PDO หรือ MySQLi" ไม่ได้มีข้อมูลเพียงพอที่จะอธิบายให้สามเณรเกี่ยวกับวิธีการใช้อย่างปลอดภัย คุณและฉันรู้ว่าข้อความที่เตรียมไว้มีความสำคัญ แต่ฉันไม่คิดว่าทุกคนที่อ่านคำถามนี้จะรู้ นั่นคือเหตุผลที่ฉันเพิ่มคำแนะนำที่ชัดเจน
Andy Lester

30
ความคิดเห็นของ Andy นั้นถูกต้องทั้งหมด ฉันเปลี่ยนเว็บไซต์ mysql เป็น PDO เมื่อเร็ว ๆ นี้ฉันคิดว่าตอนนี้ฉันปลอดภัยจากการโจมตีของการฉีด เป็นเพียงระหว่างกระบวนการที่ฉันรู้ว่าคำสั่ง sql บางส่วนของฉันยังคงถูกสร้างโดยใช้อินพุตของผู้ใช้ ฉันแก้ไขแล้วโดยใช้คำสั่งที่เตรียมไว้ สำหรับผู้เริ่มต้นที่สมบูรณ์นั้นยังไม่ชัดเจนว่ามีความแตกต่างเนื่องจากผู้เชี่ยวชาญหลายคนแสดงความคิดเห็นเกี่ยวกับการใช้ PDO แต่ไม่ได้ระบุความจำเป็นในการเตรียมข้อความ สมมุติว่าสิ่งนี้ชัดเจน แต่ไม่ถึงสามเณร
GhostRider

คำตอบ:


1184

เป็นความเข้าใจผิดทั่วไปที่ผู้ใช้สามารถกรองได้ PHP ยังมี "ฟีเจอร์" (เลิกใช้แล้ว) ซึ่งเรียกว่าเมจิกอัญประกาศซึ่งสร้างจากแนวคิดนี้ มันไร้สาระ ลืมเกี่ยวกับการกรอง (หรือทำความสะอาดหรือสิ่งที่ผู้คนเรียก)

สิ่งที่คุณควรทำเพื่อหลีกเลี่ยงปัญหานั้นค่อนข้างง่าย: เมื่อใดก็ตามที่คุณฝังสตริงภายในโค้ดต่างประเทศคุณต้องหลบหนีตามกฎของภาษานั้น ตัวอย่างเช่นหากคุณฝังสตริงใน SQL ที่กำหนดเป้าหมายไปยัง MySQL คุณจะต้องหลีกเลี่ยงสตริงด้วยฟังก์ชันของ MySQL เพื่อจุดประสงค์นี้ ( mysqli_real_escape_string) (หรือในกรณีของฐานข้อมูลการใช้คำสั่งที่เตรียมไว้เป็นวิธีที่ดีกว่าหากเป็นไปได้)

อีกตัวอย่างหนึ่งคือ HTML: ถ้าคุณฝังสตริงใน HTML htmlspecialcharsมาร์กอัปคุณต้องหนีด้วย ซึ่งหมายความว่าทุกคนเดียวechoหรือคำสั่งที่ควรใช้printhtmlspecialchars

ตัวอย่างที่สามอาจจะเป็นคำสั่งเชลล์: ถ้าคุณกำลังจะฝังสตริง (เช่นข้อโต้แย้ง) ไปที่คำสั่งภายนอกและเรียกพวกเขาด้วยexecแล้วคุณจะต้องใช้และescapeshellcmdescapeshellarg

และอื่น ๆ และอื่น ๆ ...

เฉพาะกรณีที่คุณจำเป็นต้องกรองข้อมูลอย่างแข็งขันคือถ้าคุณยอมรับ preformatted การป้อนข้อมูล ตัวอย่างเช่นหากคุณให้ผู้ใช้โพสต์มาร์กอัพ HTML แสดงว่าคุณวางแผนที่จะแสดงบนเว็บไซต์ อย่างไรก็ตามคุณควรหลีกเลี่ยงค่าใช้จ่ายนี้เนื่องจากไม่ว่าคุณจะกรองได้ดีเพียงใดก็ตามมันจะเป็นช่องโหว่ด้านความปลอดภัย


245
"ซึ่งหมายความว่าทุกคำสั่ง echo หรือคำสั่ง print ควรใช้ htmlspecialchars" - แน่นอนคุณหมายถึง "คำสั่ง ... ทุกคำสั่งที่ส่งออกอินพุตของผู้ใช้"; htmlspecialchars () - ifying "echo 'Hello, world!';" จะบ้า;)
บ๊อบบี้แจ็ค

10
มีกรณีหนึ่งที่ฉันคิดว่าการกรองเป็นวิธีแก้ปัญหาที่ถูกต้อง: UTF-8 คุณไม่ต้องการลำดับ UTF-8 ที่ไม่ถูกต้องทั่วทั้งแอปพลิเคชันของคุณ (คุณอาจได้รับข้อผิดพลาดการกู้คืนที่แตกต่างกันขึ้นอยู่กับเส้นทางของโค้ด) และสามารถกรอง UTF-8 (หรือถูกปฏิเสธ) ได้อย่างง่ายดาย
Kornel

6
@jbyrd - ไม่ LIKE ใช้ภาษา regexp แบบพิเศษ คุณจะต้องหลีกเลี่ยงสตริงอินพุตของคุณสองครั้ง - หนึ่งครั้งสำหรับ regexp และอีกครั้งสำหรับการเข้ารหัสสตริง mysql มันเป็นรหัสภายในรหัสภายในรหัส
troelskn

6
ในขณะmysql_real_escape_stringนี้เลิกใช้แล้ว ในปัจจุบันถือว่าเป็นแนวปฏิบัติที่ดีในการใช้ข้อความที่เตรียมไว้เพื่อป้องกันการฉีด SQL ดังนั้นเปลี่ยนเป็น MySQLi หรือ PDO
Marcel Korpel

4
เพราะคุณ จำกัด พื้นผิวการโจมตี หากคุณฆ่าเชื้อตั้งแต่เนิ่นๆ (เมื่อป้อนข้อมูล) คุณต้องแน่ใจว่าไม่มีช่องโหว่ใด ๆ ในแอปพลิเคชันที่สามารถป้อนข้อมูลที่ไม่ถูกต้องได้ ในขณะที่ถ้าคุณทำช้าฟังก์ชั่นการส่งออกของคุณไม่จำเป็นต้อง "เชื่อใจ" ว่าได้รับข้อมูลที่ปลอดภัย - เพียงแค่สมมติว่าทุกอย่างไม่ปลอดภัย
troelskn

217

อย่าพยายามป้องกันการฉีด SQL ด้วยการฆ่าเชื้อข้อมูลอินพุต

แต่ไม่อนุญาตให้มีข้อมูลที่จะใช้ในการสร้างรหัส SQL ของคุณ ใช้งบที่เตรียมไว้ (เช่นการใช้พารามิเตอร์ในแบบสอบถามแม่แบบ) ที่ใช้ตัวแปรที่ถูกผูกไว้ เป็นวิธีเดียวที่จะรับประกันการฉีด SQL

โปรดดูเว็บไซต์ของฉันhttp://bobby-tables.com/สำหรับข้อมูลเพิ่มเติมเกี่ยวกับการป้องกันการฉีด SQL


18
หรือเยี่ยมชมเอกสารอย่างเป็นทางการและเรียนรู้ PDO และงบเตรียม เส้นโค้งการเรียนรู้เล็ก ๆ แต่ถ้าคุณรู้ว่า SQL ค่อนข้างดีคุณจะไม่มีปัญหาในการปรับตัว
coder

2
สำหรับกรณีเฉพาะของ SQL Injection นี่เป็นคำตอบที่ถูกต้อง!
Scott Arciszewski

4
โปรดทราบว่าคำสั่งที่เตรียมไว้จะไม่เพิ่มความปลอดภัยใด ๆ แบบสอบถามที่มีพารามิเตอร์ทำ พวกเขาเพิ่งจะใช้งานร่วมกันได้ง่ายใน PHP
พื้นฐาน

มันไม่ใช่วิธีการรับประกันเท่านั้น การป้อนข้อมูลและ unhex ที่เป็นเลขฐานสิบหกในแบบสอบถามจะป้องกันได้เช่นกัน นอกจากนี้การโจมตีฐานสิบหกจะไม่สามารถทำได้หากคุณใช้การทำ hexing ให้ถูกต้อง
Ramon Bakker

จะทำอย่างไรถ้าคุณป้อนบางสิ่งที่มีความเชี่ยวชาญเช่นที่อยู่อีเมลหรือชื่อผู้ใช้
Abraham Brookes

79

ไม่คุณไม่สามารถกรองข้อมูลโดยอัตโนมัติโดยไม่ต้องคำนึงถึงบริบทของสิ่งที่มีอยู่ บางครั้งคุณต้องการที่จะใช้แบบสอบถาม SQL เป็นอินพุตและบางครั้งคุณต้องการที่จะใช้ HTML เป็นอินพุต

คุณต้องกรองอินพุตในรายการที่อนุญาต - ตรวจสอบให้แน่ใจว่าข้อมูลตรงกับข้อกำหนดบางอย่างของสิ่งที่คุณคาดหวัง จากนั้นคุณต้องหลบหนีก่อนใช้งานทั้งนี้ขึ้นอยู่กับบริบทที่คุณใช้งาน

กระบวนการหนีข้อมูลสำหรับ SQL - เพื่อป้องกันการฉีด SQL - แตกต่างอย่างมากจากกระบวนการหนีข้อมูลสำหรับ (X) HTML เพื่อป้องกัน XSS


52

PHP มีฟังก์ชั่น filter_input ที่ดีใหม่ในตอนนี้เช่นคุณสามารถปลดปล่อย 'สุดยอด e-mail regex' ในขณะนี้ว่ามีประเภท FILTER_VALIDATE_EMAIL ในตัว

คลาสตัวกรองของฉันเอง (ใช้ JavaScript เพื่อเน้นฟิลด์ที่ผิดพลาด) สามารถเริ่มต้นได้โดยการร้องขอ ajax หรือการโพสต์รูปแบบปกติ (ดูตัวอย่างด้านล่าง)

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



}

แน่นอนว่าคุณต้องทำแบบสอบถาม SQL ของคุณเช่นกันขึ้นอยู่กับประเภทของฐานข้อมูลที่คุณใช้ (mysql_real_escape_string () นั้นไร้ประโยชน์สำหรับเซิร์ฟเวอร์ sql เป็นต้น) คุณอาจต้องการจัดการสิ่งนี้โดยอัตโนมัติในแอปพลิเคชันเลเยอร์ที่เหมาะสมของคุณเช่น ORM นอกจากนี้ตามที่กล่าวไว้ข้างต้น: สำหรับการแสดงผลเป็น html ให้ใช้ฟังก์ชั่นเฉพาะ php อื่น ๆ เช่น htmlspecialchars;)

สำหรับการอนุญาตการป้อนข้อมูล HTML ด้วยคลาสที่ถูกปล้นและ / หรือแท็กนั้นขึ้นอยู่กับแพ็คเกจการตรวจสอบ xss โดยเฉพาะ อย่าเขียน REGEXES ของคุณเองเพื่อหาค่า HTML!


18
ลักษณะเช่นนี้มันอาจจะเป็นสคริปต์ที่มีประโยชน์สำหรับการตรวจสอบปัจจัยการผลิต แต่มันก็สมบูรณ์ไม่เกี่ยวข้องกับคำถามที่ว่า
rjmunro

43

ไม่ไม่มี

ก่อนอื่นการฉีด SQL เป็นปัญหาการกรองอินพุตและ XSS เป็นเอาต์พุตที่หลีกเลี่ยงได้ดังนั้นคุณจะไม่ได้ดำเนินการทั้งสองในเวลาเดียวกันในวงจรชีวิตของโค้ด

กฎพื้นฐานของหัวแม่มือ

  • สำหรับแบบสอบถาม SQL ให้ผูกพารามิเตอร์ (เช่นเดียวกับ PDO) หรือใช้ฟังก์ชั่นการหลบหนีของไดรเวอร์สำหรับตัวแปรคิวรี่ (เช่นmysql_real_escape_string())
  • ใช้strip_tags()เพื่อกรอง HTML ที่ไม่ต้องการ
  • หลีกเลี่ยงเอาต์พุตอื่น ๆ ด้วยhtmlspecialchars()และคำนึงถึงพารามิเตอร์ที่ 2 และ 3 ที่นี่

1
ดังนั้นคุณจะใช้ strip_tags () หรือ htmlspecialchars () เมื่อคุณรู้ว่าอินพุตมี HTML ที่คุณต้องการกำจัดหรือหลบหนีตามลำดับ - คุณไม่ได้ใช้เพื่อความปลอดภัยใช่ไหม? นอกจากนี้เมื่อคุณทำสิ่งผูกมัดมันจะทำอะไรเพื่ออะไรเช่นโต๊ะบ๊อบบี้? "Robert '); DROP TABLE Students; -" มันหนีคำพูดได้หรือไม่?
Robert Mark Bram

2
หากคุณมีข้อมูลผู้ใช้ที่จะเข้าสู่ฐานข้อมูลและจะแสดงในหน้าเว็บในภายหลังมักจะไม่อ่านมากกว่าที่เขียนหรือไม่ สำหรับฉันแล้วการกรองครั้งเดียว (เป็นอินพุต) ให้เหมาะสมกว่าก่อนที่คุณจะจัดเก็บแทนที่จะต้องกรองทุกครั้งที่คุณแสดง ฉันทำบางสิ่งบางอย่างหายไปหรือผู้คนจำนวนมากโหวตให้ค่าใช้จ่ายที่ไม่จำเป็นในการดำเนินการและคำตอบที่ได้รับการยอมรับ
jbo5112

2
คำตอบที่ดีที่สุดสำหรับฉัน มันสั้นและตอบคำถามได้ดีถ้าคุณถามฉัน เป็นไปได้หรือไม่ที่จะโจมตี PHP อย่างใดอย่างหนึ่งผ่านทาง $ _POST หรือ $ _GET ด้วยการฉีดหรือเป็นไปไม่ได้?
Jo Smo

โอ้ใช่ $ post และ $ get arrays ยอมรับอักขระทั้งหมด แต่ตัวละครเหล่านั้นบางตัวสามารถใช้กับคุณได้หากตัวอักษรนั้นได้รับอนุญาตให้ระบุในหน้า php ที่โพสต์ ดังนั้นหากคุณไม่หลบหนีการห่อหุ้มตัวอักษร (เช่น "," และ ") มันสามารถเปิดเวกเตอร์โจมตีได้อักขระ` มักจะพลาดและสามารถใช้ในการแฮ็กการดำเนินการบรรทัดคำสั่งการสุขาภิบาลจะป้องกันการแฮ็คของผู้ใช้ แต่จะไม่ช่วยคุณในการแฮ็กแอปพลิเคชันไฟร์วอลล์บนเว็บ
drtechno

22

เพื่อแก้ไขปัญหา XSS ที่จะดูที่HTML เพียวริฟายเออ สามารถกำหนดค่าได้อย่างเป็นธรรมและมีประวัติที่ดี

สำหรับการโจมตีการฉีด SQL ตรวจสอบให้แน่ใจว่าคุณตรวจสอบอินพุตของผู้ใช้จากนั้นรันแม้ว่า mysql_real_escape_string () ฟังก์ชันจะไม่เอาชนะการโจมตีการฉีดทั้งหมดดังนั้นจึงเป็นสิ่งสำคัญที่คุณต้องตรวจสอบข้อมูลก่อนที่จะทิ้งลงในสตริงการสืบค้นของคุณ

ทางออกที่ดีกว่าคือการใช้คำสั่งที่เตรียมไว้ ห้องสมุด PDOและการขยาย mysqli สนับสนุนเหล่านี้


ไม่มี "วิธีที่ดีที่สุด" ในการทำบางอย่างเช่นการฆ่าเชื้ออินพุท .. ใช้ไลบรารี่บางอย่าง, ตัวกรอง HTML นั้นดี ห้องสมุดเหล่านี้ถูกทุบหลายครั้ง ดังนั้นมันจึงกันกระสุนได้มากกว่าสิ่งใด ๆ ที่คุณสามารถเกิดขึ้นได้ด้วยตัวคุณเอง
paan

ดูเพิ่มเติมbioinformatics.org/phplabware/internal_utilities/htmLawed จากความเข้าใจของฉัน WordPress ใช้รุ่นที่เก่ากว่าcore.trac.wordpress.org/browser/tags/2.9.2/wp-includes/kses.php
Steve Clay

ปัญหาเกี่ยวกับ wordpress คือมันไม่จำเป็นต้องเป็นการโจมตีแบบฉีด php-sql ที่ทำให้ฐานข้อมูลรั่วไหล พลาดปลั๊กอินที่ตั้งโปรแกรมไว้ซึ่งเก็บข้อมูลที่แบบสอบถาม xml เปิดเผยความลับนั้นมีปัญหามากกว่า
drtechno


17

เคล็ดลับหนึ่งที่สามารถช่วยในสถานการณ์เฉพาะที่คุณมีหน้าเว็บที่เหมือนกัน/mypage?id=53และคุณใช้ ID ในส่วนคำสั่ง WHERE คือเพื่อให้แน่ใจว่า ID เป็นจำนวนเต็มอย่างแน่นอน:

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

แต่แน่นอนว่าจะลดการโจมตีหนึ่งครั้งเท่านั้นดังนั้นอ่านคำตอบอื่น ๆ ทั้งหมด (และใช่ฉันรู้ว่ารหัสข้างต้นไม่ดี แต่มันแสดงให้เห็นถึงการป้องกันเฉพาะ)


11
ฉันใช้ $ id = intval ($ id) แทน :)
Duc Tran

การชี้จำนวนเต็มเป็นวิธีที่ดีในการตรวจสอบให้แน่ใจว่าใส่ข้อมูลตัวเลขเท่านั้น
ทดสอบ

1
$id = (int)$_GET['id']และ$que = sprintf('SELECT ... WHERE id="%d"', $id)ก็ดีเช่นกัน
vladkras

16

วิธีการฆ่าเชื้ออินพุตของผู้ใช้ด้วย PHP:

  • ใช้ MySQL และ PHP เวอร์ชันทันสมัย

  • ตั้ง charset อย่างชัดเจน:

    • $ mysqli-> set_charset ( "utf8");
      คู่มือ
    • $ pdo = PDO ใหม่ ('mysql: host = localhost; dbname = testdb; charset = UTF8', $ user, $ password);
      คู่มือ
    • $ pdo-> exec ("set names utf8");
      คู่มือ
    • $ pdo = PDO ใหม่ (
      "mysql: host = $ host; dbname = $ db", $ user, $ pass, 
      array (
      PDO :: ATTR_ERRMODE => PDO :: ERRMODE_EXCEPTION
      PDO :: MYSQL_ATTR_INIT_COMMAND => "SET NAMES utf8"
      )
      );
      คู่มือ
    • mysql_set_charset ( 'utf8')
      [เลิกใช้แล้วใน PHP 5.5.0 ลบออกใน PHP 7.0.0]
  • ใช้ชุดอักขระที่ปลอดภัย:

    • เลือก utf8, latin1, ascii .. , อย่าใช้ชุดอักขระที่มีช่องโหว่ big5, cp932, gb2312, gbk, sjis
  • ใช้ฟังก์ชั่น spatialized:

    • งบ MySQLi เตรียม:
      $ stmt = $ mysqli-> เตรียมพร้อม ('เลือก * จากการทดสอบ WHERE name =? LIMIT 1'); 
      $ param = "'OR 1 = 1 / *";
      $ stmt-> bind_param ('s', $ param);
      $ stmt-> รัน ();
    • PDO :: quote () - ใส่เครื่องหมายคำพูดรอบสตริงอินพุต (หากจำเป็น) และหลีกเลี่ยงอักขระพิเศษภายในสตริงอินพุตโดยใช้รูปแบบการอ้างอิงที่เหมาะสมกับไดรเวอร์พื้นฐาน:

      $ pdo = PDO ใหม่ ('mysql: host = localhost; dbname = testdb; charset = UTF8', $ user, $ password); กำหนดชุดอักขระอย่างชัดเจน
      $ pdo-> setAttribute (PDO :: ATTR_EMULATE_PREPARES, false); ปิดใช้งานการเลียนแบบคำสั่งที่เตรียมไว้เพื่อป้องกันการย้อนกลับไปสู่การเลียนแบบคำสั่งที่ MySQL ไม่สามารถจัดเตรียมแบบ
      ดั้งเดิม(เพื่อป้องกันการฉีด) $ var = $ pdo-> quote ("'OR 1 = 1 / *"); ไม่เพียง แต่จะหลบหนีไปตามตัวอักษร แต่ยังพูดถึงมัน (ในตัวอักษรคำพูดเดียว ') $ stmt = $ pdo-> แบบสอบถาม ("SELECT * จากการทดสอบ WHERE name = $ var LIMIT 1");

    • PDO Prepared Statement : งบ MySQLi ที่เตรียมไว้รองรับไดรเวอร์ฐานข้อมูลและพารามิเตอร์ที่มีชื่อ:

      $ pdo = PDO ใหม่ ('mysql: host = localhost; dbname = testdb; charset = UTF8', $ user, $ password); กำหนดชุดอักขระอย่างชัดเจน
      $ pdo-> setAttribute (PDO :: ATTR_EMULATE_PREPARES, false); ปิดใช้งานการเลียนแบบคำสั่งที่เตรียมไว้เพื่อป้องกันการย้อนกลับไปสู่การเลียนแบบคำสั่งที่ MySQL ไม่สามารถจัดทำแบบดั้งเดิม (เพื่อป้องกันการฉีด) $ stmt = $ pdo-> เตรียมพร้อม ('เลือก * จากการทดสอบ WHERE name =? LIMIT 1'); $ stmt-> execute (["'OR 1 = 1 / *"]);

    • mysql_real_escape_string [เลิกใช้แล้วใน PHP 5.5.0, ถูกลบใน PHP 7.0.0]
    • mysqli_real_escape_stringหนีอักขระพิเศษในสตริงสำหรับใช้ในคำสั่ง SQL โดยคำนึงถึงชุดอักขระปัจจุบันของการเชื่อมต่อ แต่แนะนำให้ใช้คำสั่งที่เตรียมไว้เพราะมันไม่ได้หนีออกมาแค่สายคำสั่งที่มาพร้อมกับแผนการดำเนินการแบบสอบถามที่สมบูรณ์รวมถึงตารางและดัชนีที่จะใช้มันเป็นวิธีที่ดีที่สุด
    • ใช้เครื่องหมายคำพูดเดี่ยว ('') ล้อมรอบตัวแปรของคุณภายในข้อความค้นหาของคุณ
  • ตรวจสอบตัวแปรที่มีสิ่งที่คุณคาดหวัง:

    • หากคุณคาดว่าจะเป็นจำนวนเต็มใช้:
      ctype_digit - ตรวจสอบอักขระที่เป็นตัวเลข 
      $ value = (int) $ value;
      $ value = intval ($ value);
      $ var = filter_var ('0755', FILTER_VALIDATE_INT, $ ตัวเลือก);
    • สำหรับสตริงใช้:
      is_string () - ค้นหาว่าชนิดของตัวแปรเป็นสตริงหรือไม่

      ใช้Filter Function filter_var () - กรองตัวแปรด้วยตัวกรองที่ระบุ:
      $ email = filter_var ($ email, FILTER_SANITIZE_EMAIL); 
      $ newstr = filter_var ($ str, FILTER_SANITIZE_STRING);
      ตัวกรองที่กำหนดไว้ล่วงหน้าเพิ่มเติม
    • filter_input () - รับตัวแปรภายนอกที่เฉพาะเจาะจงตามชื่อและเลือกกรอง:
      $ search_html = filter_input (INPUT_GET, 'ค้นหา', FILTER_SANITIZE_SPECIAL_CHARS);
    • preg_match () - ทำการจับคู่นิพจน์ปกติ
    • เขียนฟังก์ชั่นการตรวจสอบของคุณเอง

11

สิ่งที่คุณกำลังอธิบายที่นี่เป็นสองประเด็นที่แยกจากกัน:

  1. ฆ่าเชื้อ / กรองข้อมูลที่ผู้ใช้ป้อน
  2. กำลังเอาท์พุต

1) การป้อนข้อมูลผู้ใช้ควรถือว่าไม่ถูกต้องเสมอ

การใช้คำสั่งที่เตรียมไว้หรือ / และการกรองด้วย mysql_real_escape_string เป็นสิ่งที่จำเป็นอย่างยิ่ง PHP ยังมี filter_input ที่สร้างขึ้นซึ่งเป็นจุดเริ่มต้นที่ดี

2) นี่เป็นหัวข้อขนาดใหญ่และขึ้นอยู่กับบริบทของข้อมูลที่กำลังส่งออก สำหรับ HTML มีวิธีแก้ไขเช่น htmlpurifier อยู่ตรงนั้น ตามกฎของหัวแม่มือให้หลีกเลี่ยงสิ่งที่คุณส่งออกเสมอ

ทั้งสองประเด็นมีขนาดใหญ่เกินกว่าที่จะโพสต์ในโพสต์เดียว แต่มีโพสต์มากมายที่ให้รายละเอียดเพิ่มเติม:

วิธีการส่งออก PHP

ผลลัพธ์ PHP ที่ปลอดภัยยิ่งขึ้น


9

หากคุณใช้ PostgreSQL ข้อมูลที่ได้จาก PHP สามารถหนีได้ด้วย pg_escape_string ()

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

จากเอกสาร ( http://php.net/manual/es/function.pg-escape-string.php ):

pg_escape_string () หนีออกจากสตริงสำหรับการสืบค้นฐานข้อมูล มันจะส่งคืนสตริงที่ใช้ Escape ในรูปแบบ PostgreSQL โดยไม่ต้องใส่เครื่องหมายคำพูด


1
pg_escape_literal ()เป็นฟังก์ชั่นที่แนะนำให้ใช้สำหรับ PostgreSQL
ความลับツ

8

ไม่มีฟังก์ชั่น catchall เนื่องจากมีข้อกังวลหลายประการที่ต้องแก้ไข

  1. SQL Injection - วันนี้โดยทั่วไปทุกโครงการ PHP ควรจะใช้งบเตรียมผ่านวัตถุ PHP ข้อมูล (PDO)เป็นวิธีที่ดีที่สุดการป้องกันข้อผิดพลาดจากการอ้างจรจัดเช่นเดียวกับการแก้ปัญหาเต็มรูปแบบกับการฉีด นอกจากนี้ยังเป็นวิธีที่ยืดหยุ่นและปลอดภัยที่สุดในการเข้าถึงฐานข้อมูลของคุณ

    ลองดูการสอน PDO (เหมาะสมอย่างเดียว)สำหรับทุกสิ่งที่คุณต้องรู้เกี่ยวกับ PDO (ขอขอบคุณอย่างจริงใจต่อผู้สนับสนุน SO อันดับต้น ๆ @YourCommonSense สำหรับทรัพยากรที่ยอดเยี่ยมในหัวข้อนี้)

  2. XSS - ฆ่าเชื้อโรคข้อมูลระหว่างทาง ...

    • ตัวฟอก HTMLนั้นใช้เวลานานมากและยังได้รับการปรับปรุงอย่างแข็งขัน คุณสามารถใช้มันเพื่อฆ่าเชื้ออินพุตที่เป็นอันตรายในขณะที่ยังคงอนุญาตรายการแท็กที่ปลอดภัยและกำหนดค่าได้ ใช้งานได้ดีกับเครื่องมือแก้ไข WYSIWYG จำนวนมาก แต่อาจใช้งานได้ยากสำหรับบางกรณี

    • ในกรณีอื่น ๆ ที่เราไม่ต้องการยอมรับ HTML / Javascript เลยฉันพบว่าฟังก์ชั่นง่าย ๆ นี้มีประโยชน์ (และผ่านการตรวจสอบหลายครั้งกับ 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 - ฆ่าเชื้อข้อมูลให้หมด ...เว้นแต่คุณจะรับประกันว่าข้อมูลถูกทำให้ถูกสุขลักษณะก่อนที่จะเพิ่มลงในฐานข้อมูลของคุณคุณจะต้องฆ่าเชื้อก่อนที่จะแสดงให้ผู้ใช้ของคุณเราสามารถใช้ประโยชน์จากฟังก์ชั่น PHP ที่เป็นประโยชน์เหล่านี้:

    • เมื่อคุณโทรechoหรือprintแสดงค่าที่ผู้ใช้ระบุให้ใช้htmlspecialcharsยกเว้นว่าข้อมูลนั้นปลอดภัยถูกต้องและได้รับอนุญาตให้แสดง HTML
    • json_encode เป็นวิธีที่ปลอดภัยในการระบุค่าที่ผู้ใช้จัดหาจาก PHP ถึง Javascript
  4. คุณเรียกคำสั่งเปลือกภายนอกโดยใช้exec()หรือsystem()ฟังก์ชั่นหรือไปยังbacktickผู้ประกอบการหรือไม่ ถ้าเป็นเช่นนั้นนอกเหนือไปจาก SQL Injection และ XSS คุณอาจมีความกังวลเพิ่มเติมเพื่อที่อยู่ผู้ใช้ที่ใช้คำสั่งที่เป็นอันตรายบนเซิร์ฟเวอร์ของคุณ คุณต้องใช้escapeshellcmdหากคุณต้องการหลีกเลี่ยงคำสั่งทั้งหมดหรือescapeshellargเพื่อหลีกเลี่ยงการขัดแย้งกัน


สามารถใช้ mb_encode_numericentity แทนได้หรือไม่ เมื่อมันเข้ารหัสทุกอย่าง?
drtechno

@drtechno - mb_encode_numericentityถูกกล่าวถึงในhtmlspecialcharsลิงค์ใน # 3 XSS
webaholik

5

วิธีที่ง่ายที่สุดในการหลีกเลี่ยงข้อผิดพลาดในการฆ่าเชื้ออินพุตและหนีข้อมูลคือการใช้เฟรมเวิร์ก PHP เช่นSymfony , Netteเป็นต้นหรือเป็นส่วนหนึ่งของเฟรมเวิร์กนั้น

เครื่องมือสร้างเทมเพลตอย่างTwigหรือ Latte มีการหลบหลีกเอาต์พุตโดยปริยายคุณไม่จำเป็นต้องแก้ปัญหาด้วยตนเองหากคุณได้เอาท์พุตอย่างถูกต้องตามบริบท (HTML หรือ Javascript บางส่วนของหน้าเว็บ)

Framework นั้นถูกป้อนข้อมูลโดยอัตโนมัติและคุณไม่ควรใช้ตัวแปร $ _POST, $ _GET หรือ $ _SESSION โดยตรง แต่ผ่านกลไกเช่นการกำหนดเส้นทางการจัดการเซสชันเป็นต้น

และสำหรับเลเยอร์ฐานข้อมูล (โมเดล) มีกรอบ ORM เช่น Doctrine หรือ wrappers รอบ PDO เช่น Nette Database

คุณสามารถอ่านเพิ่มเติมได้ที่นี่ - กรอบซอฟต์แวร์คืออะไร?


3

เพียงแค่ต้องการที่จะเพิ่มในเรื่องของการหลบหนีเอาท์พุทถ้าคุณใช้ php DOMDocument เพื่อให้การส่งออก html ของคุณมันจะหนีโดยอัตโนมัติในบริบทที่เหมาะสม แอตทริบิวต์ (value = "") และข้อความภายในของ <span> ไม่เท่ากัน เพื่อความปลอดภัยกับ XSS โปรดอ่าน: OWASP XSS Cheat Sheet Cheat


2

คุณไม่เคยฆ่าเชื้ออินพุต

คุณทำความสะอาดผลผลิต

การแปลงที่คุณใช้กับข้อมูลเพื่อให้ปลอดภัยสำหรับการรวมอยู่ในคำสั่ง SQL จะแตกต่างจากที่คุณใช้สำหรับการรวมใน HTML จะแตกต่างจากที่คุณใช้สำหรับการรวมใน Javascript จะแตกต่างจากที่คุณใช้สำหรับการรวมใน LDIF แตกต่างจากที่คุณใช้กับการรวมใน CSS อย่างสิ้นเชิงแตกต่างจากที่คุณใช้กับการรวมในอีเมล ...

โดยทั้งหมดหมายถึงการตรวจสอบการป้อนข้อมูล - ตัดสินใจว่าคุณควรยอมรับมันสำหรับการประมวลผลเพิ่มเติมหรือบอกผู้ใช้ว่าไม่สามารถยอมรับได้ แต่อย่าใช้การเปลี่ยนแปลงใด ๆ กับการแสดงข้อมูลจนกว่าจะออกจาก PHP

นานมาแล้วมีคนพยายามคิดค้นขนาดเดียวที่เหมาะกับกลไกทั้งหมดสำหรับการหลบหนีข้อมูลและเราได้จบลงด้วย " magic_quotes " ซึ่งไม่ได้หลบหนีข้อมูลอย่างถูกต้องสำหรับเป้าหมายการส่งออกทั้งหมดและส่งผลให้การติดตั้งที่แตกต่างกัน


ปัญหาหนึ่งที่เกิดขึ้นคือไม่ได้เป็นการโจมตีฐานข้อมูลเสมอและการป้อนข้อมูลผู้ใช้ทั้งหมดควรได้รับการปกป้องจากระบบ ไม่ใช่แค่ภาษาเดียว ดังนั้นในเว็บไซต์ของคุณเมื่อคุณระบุข้อมูล $ _POST ของคุณแม้ว่าจะใช้การเชื่อมโยงข้อมูลมันก็สามารถหลบหนีออกมาได้เพียงพอที่จะเรียกใช้เชลล์หรือแม้แต่รหัส php อื่น ๆ
drtechno

"ไม่ใช่การโจมตีฐานข้อมูลเสมอไป": "การแปลงที่คุณใช้กับข้อมูลเพื่อให้ปลอดภัยสำหรับการรวมอยู่ในคำสั่ง SQL จะแตกต่างอย่างสิ้นเชิงจากที่ .... "
symcbean

"อินพุตผู้ใช้ทั้งหมดควรได้รับการปกป้องจากระบบ": ไม่มีระบบใดที่ควรได้รับการปกป้องจากอินพุตของผู้ใช้
symcbean

ดีฉันหมดคำ แต่ใช่ข้อมูลที่จำเป็นต้องได้รับการป้องกันจากผลกระทบการดำเนินงานของระบบ เพื่อชี้แจงเรื่องนี้ ...
34425

ทั้งอินพุตและเอาต์พุตควรถูกทำให้สะอาด
Tajni

1

อย่าเชื่อถือข้อมูลผู้ใช้

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

trim()ฟังก์ชั่นการลบช่องว่างและตัวอักษรที่กำหนดไว้ล่วงหน้าอื่น ๆ จากทั้งสองด้านของสตริง

stripslashes()ฟังก์ชั่นเอาเครื่องหมาย

htmlspecialchars()ฟังก์ชันแปลงตัวอักษรที่กำหนดไว้ล่วงหน้าบางส่วนไปยังหน่วยงาน HTML

อักขระที่กำหนดไว้ล่วงหน้าคือ:

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

1
สิ่งนี้จะปกป้องจากอะไร นี่สำหรับ XSS เหรอ? ทำไมถึงเรียกว่าclean_input? ทำไมคุณต้องการตัดเครื่องหมายทับ?
Dharman

4
คำเตือน:สิ่งนี้ไม่ได้ทำให้ข้อมูลผู้ใช้ปลอดภัย ฟังก์ชันนี้จะทำให้ข้อมูลของคุณเสียหายโดยไม่จำเป็นโดยไม่จำเป็น อย่าใช้มัน!
Dharman

คำสั่งของคุณเป็นเท็จ
Erik Thiart

0

มีส่วนขยายตัวกรอง ( howto-link , manual ) ซึ่งทำงานได้ดีกับตัวแปร GPC ทั้งหมด มันไม่ใช่สิ่งที่ต้องทำอย่างวิเศษ แต่คุณยังต้องใช้มัน

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.