การฉีด SQL ที่อยู่รอบ ๆ mysql_real_escape_string ()

Apr 21 2011

มีความเป็นไปได้ในการฉีด SQL แม้ว่าจะใช้mysql_real_escape_string()ฟังก์ชันหรือไม่?

พิจารณาสถานการณ์ตัวอย่างนี้ SQL ถูกสร้างขึ้นใน PHP ดังนี้:

$login = mysql_real_escape_string(GetFromPost('login')); $password = mysql_real_escape_string(GetFromPost('password'));

$sql = "SELECT * FROM table WHERE login='$login' AND password='$password'";

ฉันเคยได้ยินหลายคนพูดกับฉันว่ารหัสแบบนั้นยังคงอันตรายและสามารถแฮ็คได้แม้จะmysql_real_escape_string()ใช้ฟังก์ชัน แต่ฉันไม่สามารถคิดหาประโยชน์ใด ๆ ที่เป็นไปได้?

การฉีดแบบคลาสสิกเช่นนี้:

aaa' OR 1=1 --

ไม่ทำงาน.

คุณรู้หรือไม่ว่ามีการฉีดเข้าไปในโค้ด PHP ด้านบนหรือไม่?

คำตอบ

393 WesleyvanOpdorp Apr 21 2011 at 15:05

พิจารณาแบบสอบถามต่อไปนี้:

$iId = mysql_real_escape_string("1 OR 1=1"); $sSql = "SELECT * FROM table WHERE id = $iId";

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

$iId = (int)"1 OR 1=1";
$sSql = "SELECT * FROM table WHERE id = $iId";
652 ircmaxell Aug 25 2012 at 09:08

คำตอบสั้น ๆใช่ใช่มีวิธีการที่จะได้รับรอบ mysql_real_escape_string()# สำหรับเคส EDGE OBSCURE มาก !!!

คำตอบยาว ๆ ไม่ใช่เรื่องง่าย ก็ขึ้นอยู่การโจมตีแสดงให้เห็นที่นี่

การโจมตี

เริ่มจากการแสดงการโจมตี ...

mysql_query('SET NAMES gbk');
$var = mysql_real_escape_string("\xbf\x27 OR 1=1 /*"); mysql_query("SELECT * FROM test WHERE name = '$var' LIMIT 1");

ในบางสถานการณ์ระบบจะส่งคืนมากกว่า 1 แถว มาดูว่าเกิดอะไรขึ้นที่นี่:

  1. การเลือกชุดอักขระ

    mysql_query('SET NAMES gbk');
    

    สำหรับการโจมตีการทำงานเราต้องเข้ารหัสที่เซิร์ฟเวอร์ของคาดหวังว่าในการเชื่อมต่อทั้งในการเข้ารหัส'ในขณะที่เช่น ASCII 0x27 และจะมีตัวละครบางคนที่มีไบต์สุดท้ายเป็น ASCII คือ\ 0x5cมันจะเปิดออกมี 5 การเข้ารหัสดังกล่าวได้รับการสนับสนุนใน MySQL 5.6 โดยค่าเริ่มต้น: big5, cp932, gb2312, และgbk sjisเราจะเลือกgbkที่นี่

    ตอนนี้สิ่งสำคัญมากที่จะต้องสังเกตการใช้SET NAMESที่นี่ ชุดนี้ชุดอักขระบนเซิร์ฟเวอร์ หากเราใช้การเรียกใช้ฟังก์ชัน C API mysql_set_charset()เราก็สบายดี (ในรุ่น MySQL ตั้งแต่ปี 2549) แต่เพิ่มเติมว่าทำไมในอีกไม่กี่นาที ...

  2. Payload

    0xbf27น้ำหนักบรรทุกที่เรากำลังจะใช้สำหรับการฉีดนี้เริ่มต้นด้วยลำดับไบต์ ในgbkที่ตัวอักษรสัญลักษณ์ไม่ถูกต้อง ในก็สตริงlatin1 ¿'โปรดสังเกตว่าในlatin1 และ gbk , 0x27ในตัวเองเป็นตัวอักษร'ตัวอักษร

    เราได้เลือกน้ำหนักบรรทุกนี้เพราะถ้าเราเรียกaddslashes()มันว่าเราต้องการแทรก ASCII \คือ0x5cก่อนที่'ตัวละคร ดังนั้นเราจึงต้องการปิดท้ายด้วย0xbf5c27ซึ่งgbkเป็นลำดับสองตัวอักษร: ตามมาด้วย0xbf5c 0x27หรือคำอื่น ๆที่ถูกต้อง'ของตัวละครตามมาด้วยการไม่ใช้ Escape addslashes()แต่เราไม่ได้ใช้ ไปยังขั้นตอนต่อไป ...

  3. mysql_real_escape_string ()

    การเรียก C API mysql_real_escape_string()แตกต่างจากตรงaddslashes()ที่ทราบชุดอักขระการเชื่อมต่อ ดังนั้นจึงสามารถดำเนินการ Escape ได้อย่างเหมาะสมสำหรับชุดอักขระที่เซิร์ฟเวอร์คาดหวัง อย่างไรก็ตามจนถึงจุดนี้ลูกค้าคิดว่าเรายังคงใช้latin1การเชื่อมต่ออยู่เพราะเราไม่เคยบอกเป็นอย่างอื่น เราไม่ได้บอกเซิร์ฟเวอร์ที่เรากำลังใช้gbkแต่ลูกค้าlatin1ยังคงคิดว่ามันเป็น

    ดังนั้นการเรียกให้mysql_real_escape_string()แทรกแบ็กสแลชและเรามี'อักขระที่แขวนอยู่ฟรีในเนื้อหา "Escape" ของเรา! ในความเป็นจริงถ้าเราดู$varในgbkชุดอักขระเราจะเห็น:

    縗 'หรือ 1 = 1 / *

    ซึ่งเป็นสิ่งที่การโจมตีต้องการ

  4. แบบสอบถาม

    ส่วนนี้เป็นเพียงพิธีการ แต่นี่คือแบบสอบถามที่แสดงผล:

    SELECT * FROM test WHERE name = '縗' OR 1=1 /*' LIMIT 1
    

ขอแสดงความยินดีคุณเพิ่งโจมตีโปรแกรมสำเร็จโดยใช้mysql_real_escape_string()...

เลว

มันแย่ลง PDOค่าเริ่มต้นเป็นการเลียนแบบคำสั่งที่เตรียมไว้ด้วย MySQL นั่นหมายความว่าในฝั่งไคลเอ็นต์โดยทั่วไปจะทำการ sprintf ผ่านmysql_real_escape_string()(ในไลบรารี C) ซึ่งหมายความว่าสิ่งต่อไปนี้จะส่งผลให้การฉีดสำเร็จ:

$pdo->query('SET NAMES gbk');
$stmt = $pdo->prepare('SELECT * FROM test WHERE name = ? LIMIT 1');
$stmt->execute(array("\xbf\x27 OR 1=1 /*"));

ตอนนี้เป็นที่น่าสังเกตว่าคุณสามารถป้องกันสิ่งนี้ได้โดยปิดใช้งานคำสั่งที่เลียนแบบเตรียมไว้:

$pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);

นี้จะมักจะส่งผลให้งบเตรียมความจริง (เช่นข้อมูลที่ถูกส่งผ่านในแพ็คเก็ตที่แยกต่างหากจากแบบสอบถาม) อย่างไรก็ตามทราบว่า PDO จะเงียบทางเลือกที่จะลอกเลียนแบบงบที่ MySQL ไม่สามารถเตรียมความพร้อมโดยกำเนิด: ผู้ที่จะสามารถมีการระบุไว้ในคู่มือ แต่ระวังเพื่อเลือกรุ่นของเซิร์ฟเวอร์ที่เหมาะสม)

น่าเกลียด

ผมบอกว่าที่จุดเริ่มต้นมากที่เราอาจมีการป้องกันทั้งหมดนี้ถ้าเราได้นำมาใช้แทนmysql_set_charset('gbk') SET NAMES gbkและนั่นเป็นความจริงหากคุณใช้ MySQL รุ่นตั้งแต่ปี 2549

หากคุณกำลังใช้รุ่น MySQL ก่อนหน้านี้แล้วข้อผิดพลาดในmysql_real_escape_string()ความหมายว่าตัวอักษรสัญลักษณ์ที่ไม่ถูกต้องเช่นผู้ที่อยู่ในส่วนของข้อมูลของเราได้รับการรักษาเป็นไบต์เดียวสำหรับวัตถุประสงค์ในการหลบหนีแม้กระทั่งในกรณีที่ลูกค้าได้รับแจ้งอย่างถูกต้องของการเข้ารหัสการเชื่อมต่อและการโจมตีครั้งนี้จะ ยังคงประสบความสำเร็จ ข้อผิดพลาดได้รับการแก้ไขใน MySQL 4.1.20 , 5.0.22และ5.1.11

แต่ส่วนที่แย่ที่สุดคือPDOไม่เปิดเผย C API mysql_set_charset()จนถึง 5.3.6 ดังนั้นในเวอร์ชันก่อนหน้าจึงไม่สามารถป้องกันการโจมตีนี้ได้สำหรับทุกคำสั่งที่เป็นไปได้! มันสัมผัสในขณะนี้เป็นพารามิเตอร์ DSN

พระคุณแห่งการออม

ดังที่เราได้กล่าวไว้ในตอนต้นเพื่อให้การโจมตีนี้ทำงานได้การเชื่อมต่อฐานข้อมูลจะต้องเข้ารหัสโดยใช้ชุดอักขระที่มีช่องโหว่ utf8mb4คือไม่เสี่ยงและยังสามารถรองรับทุกอักขระ Unicode: เพื่อให้คุณสามารถเลือกที่จะใช้ที่แทน แต่จะได้รับเพียงมีตั้งแต่ MySQL 5.5.3 ทางเลือกคือutf8ซึ่งยังไม่เสี่ยงและสามารถรองรับทั้งของ Unicode สื่อสารได้หลายภาษาเครื่องบินขั้นพื้นฐาน

หรือคุณสามารถเปิดใช้งานNO_BACKSLASH_ESCAPESโหมด SQL ซึ่ง (เหนือสิ่งอื่นใด) จะเปลี่ยนแปลงการทำงานของmysql_real_escape_string(). เมื่อเปิดใช้งานโหมดนี้0x27จะถูกแทนที่ด้วย0x2727มากกว่า0x5c27ดังนั้นกระบวนการ Escape จึงไม่สามารถสร้างอักขระที่ถูกต้องในการเข้ารหัสที่มีช่องโหว่ซึ่งไม่มีอยู่ก่อนหน้านี้ (เช่น0xbf27ยังคงเป็น0xbf27ฯลฯ ) ดังนั้นเซิร์ฟเวอร์จะยังคงปฏิเสธสตริงว่าไม่ถูกต้อง . อย่างไรก็ตามโปรดดูคำตอบของ @ eggyalสำหรับช่องโหว่ต่างๆที่อาจเกิดขึ้นจากการใช้โหมด SQL นี้

ตัวอย่างที่ปลอดภัย

ตัวอย่างต่อไปนี้ปลอดภัย:

mysql_query('SET NAMES utf8');
$var = mysql_real_escape_string("\xbf\x27 OR 1=1 /*"); mysql_query("SELECT * FROM test WHERE name = '$var' LIMIT 1");

เนื่องจากเซิร์ฟเวอร์คาดว่าutf8...

mysql_set_charset('gbk');
$var = mysql_real_escape_string("\xbf\x27 OR 1=1 /*"); mysql_query("SELECT * FROM test WHERE name = '$var' LIMIT 1");

เนื่องจากเราได้กำหนดชุดอักขระอย่างถูกต้องเพื่อให้ไคลเอนต์และเซิร์ฟเวอร์ตรงกัน

$pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, false); $pdo->query('SET NAMES gbk');
$stmt = $pdo->prepare('SELECT * FROM test WHERE name = ? LIMIT 1');
$stmt->execute(array("\xbf\x27 OR 1=1 /*"));

เนื่องจากเราได้ปิดข้อความที่จำลองขึ้นที่เตรียมไว้

$pdo = new PDO('mysql:host=localhost;dbname=testdb;charset=gbk', $user, $password);
$stmt = $pdo->prepare('SELECT * FROM test WHERE name = ? LIMIT 1');
$stmt->execute(array("\xbf\x27 OR 1=1 /*"));

เพราะเราได้ตั้งค่าตัวอักษรอย่างถูกต้อง

$mysqli->query('SET NAMES gbk');
$stmt = $mysqli->prepare('SELECT * FROM test WHERE name = ? LIMIT 1');
$param = "\xbf\x27 OR 1=1 /*"; $stmt->bind_param('s', $param); $stmt->execute();

เนื่องจาก MySQLi ทำงบที่เตรียมไว้อย่างแท้จริงตลอดเวลา

ห่อ

ถ้าคุณ:

  • ใช้ MySQL เวอร์ชันที่ทันสมัย ​​(รุ่นก่อนหน้า 5.1, 5.5, 5.6 ทั้งหมด ฯลฯ ) และ mysql_set_charset() / $mysqli->set_charset()/ / / / / / / PDO ของพารามิเตอร์ชุดอักขระ DSN (ใน PHP ≥ 5.3.6)

หรือ

  • อย่าใช้ชุดอักขระที่มีช่องโหว่ในการเข้ารหัสการเชื่อมต่อ (คุณใช้เฉพาะutf8/ latin1/ ascii/ etc)

คุณปลอดภัย 100%

มิฉะนั้นคุณจะเสี่ยงแม้ว่าคุณจะใช้mysql_real_escape_string() ...

190 eggyal Apr 25 2014 at 02:15

TL; ดร

mysql_real_escape_string()จะไม่ให้การป้องกันใด ๆ (และยังสามารถโกงข้อมูลของคุณได้อีก) หาก:

  • NO_BACKSLASH_ESCAPESเปิดใช้งานโหมด SQL ของ MySQL (ซึ่งอาจเป็นไปได้เว้นแต่คุณจะเลือกโหมด SQL อื่นอย่างชัดเจนทุกครั้งที่เชื่อมต่อ ) และ

  • ลิเทอรัลสตริง SQL ของคุณถูกยกมาโดยใช้"อักขระอัญประกาศ

สิ่งนี้ถูกยื่นเป็นข้อบกพร่อง # 72458และได้รับการแก้ไขแล้วใน MySQL v5.7.6 (ดูหัวข้อ " The Saving Grace " ด้านล่าง)

นี่คืออีกอัน (อาจจะน้อยกว่านี้?) EDGE CASE ที่คลุมเครือ !!!

เพื่อเป็นการแสดงความเคารพต่อคำตอบที่ยอดเยี่ยมของ @ ircmaxell (จริงๆแล้วนี่ควรจะเป็นการเยินยอและไม่ใช่การลอกเลียนแบบ!) ฉันจะใช้รูปแบบของเขา:

การโจมตี

เริ่มต้นด้วยการสาธิต ...

mysql_query('SET SQL_MODE="NO_BACKSLASH_ESCAPES"'); // could already be set
$var = mysql_real_escape_string('" OR 1=1 -- '); mysql_query('SELECT * FROM test WHERE name = "'.$var.'" LIMIT 1');

สิ่งนี้จะส่งคืนระเบียนทั้งหมดจากtestตาราง การผ่า:

  1. การเลือกโหมด SQL

    mysql_query('SET SQL_MODE="NO_BACKSLASH_ESCAPES"');
    

    ตามที่ระบุไว้ในString Literals :

    มีหลายวิธีในการรวมอักขระเครื่องหมายคำพูดไว้ในสตริง:

    • '” ในสตริงที่ยกมาด้วย“ '” อาจเขียนว่า“ ''

    • "” ในสตริงที่ยกมาด้วย“ "” อาจเขียนว่า“ ""

    • นำหน้าอักขระเครื่องหมายคำพูดด้วยอักขระหลีก (“ \”)

    • " '" ภายในสตริงที่มีเครื่องหมาย " "" ไม่จำเป็นต้องได้รับการดูแลเป็นพิเศษและไม่จำเป็นต้องเพิ่มเป็นสองเท่าหรือหลีกหนี ในทำนองเดียวกัน " "" ในสตริงที่มีเครื่องหมาย " '" ไม่จำเป็นต้องได้รับการดูแลเป็นพิเศษ

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

  2. Payload

    " OR 1=1 -- 
    

    น้ำหนักบรรทุกเริ่มต้นการฉีดนี้ด้วย"อักขระอย่างแท้จริง ไม่มีการเข้ารหัสโดยเฉพาะ ไม่มีอักขระพิเศษ ไม่มีไบต์แปลก ๆ

  3. mysql_real_escape_string ()

    $var = mysql_real_escape_string('" OR 1=1 -- ');
    

    โชคดีที่mysql_real_escape_string()ตรวจสอบโหมด SQL และปรับพฤติกรรมให้เหมาะสม ดูlibmysql.c:

    ulong STDCALL
    mysql_real_escape_string(MYSQL *mysql, char *to,const char *from,
                 ulong length)
    {
      if (mysql->server_status & SERVER_STATUS_NO_BACKSLASH_ESCAPES)
        return escape_quotes_for_mysql(mysql->charset, to, 0, from, length);
      return escape_string_for_mysql(mysql->charset, to, 0, from, length);
    }
    

    ดังนั้นฟังก์ชันพื้นฐานที่แตกต่างกันescape_quotes_for_mysql()จึงถูกเรียกใช้หากใช้NO_BACKSLASH_ESCAPESโหมด SQL ดังที่ได้กล่าวไว้ข้างต้นฟังก์ชันดังกล่าวจำเป็นต้องทราบว่าจะใช้อักขระใดในการอ้างอิงตัวอักษรเพื่อที่จะทำซ้ำโดยไม่ทำให้อักขระอัญประกาศอื่นซ้ำตามตัวอักษร

    อย่างไรก็ตามฟังก์ชันนี้จะอนุมานโดยพลการว่าสตริงจะถูกยกมาโดยใช้'อักขระอัญประกาศเดี่ยว ดูcharset.c:

    /*
      Escape apostrophes by doubling them up
    
    // [ deletia 839-845 ]
    
      DESCRIPTION
        This escapes the contents of a string by doubling up any apostrophes that
        it contains. This is used when the NO_BACKSLASH_ESCAPES SQL_MODE is in
        effect on the server.
    
    // [ deletia 852-858 ]
    */
    
    size_t escape_quotes_for_mysql(CHARSET_INFO *charset_info,
                                   char *to, size_t to_length,
                                   const char *from, size_t length)
    {
    // [ deletia 865-892 ]
    
        if (*from == '\'')
        {
          if (to + 2 > to_end)
          {
            overflow= TRUE;
            break;
          }
          *to++= '\'';
          *to++= '\'';
        }
    

    ดังนั้นจึงปล่อยให้"อักขระอัญประกาศคู่โดยไม่ถูกแตะต้อง (และเพิ่มเป็นสองเท่าของ'อักขระอัญประกาศเดี่ยวทั้งหมด) โดยไม่คำนึงถึงอักขระจริงที่ใช้ในการอ้างอิงตัวอักษร ! ในกรณีของเรา$varยังคงอยู่ตรงเช่นเดียวกับการโต้แย้งที่ถูกจัดให้ไปmysql_real_escape_string()-IT ราวกับว่าไม่มีหนีได้ที่สถานที่ทั้งหมด

  4. แบบสอบถาม

    mysql_query('SELECT * FROM test WHERE name = "'.$var.'" LIMIT 1');
    

    สิ่งที่เป็นทางการแบบสอบถามที่แสดงผลคือ:

    SELECT * FROM test WHERE name = "" OR 1=1 -- " LIMIT 1
    

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

เลว

mysql_set_charset()ไม่สามารถช่วยได้เนื่องจากไม่มีส่วนเกี่ยวข้องกับชุดอักขระ และไม่สามารถทำได้mysqli::real_escape_string()เนื่องจากเป็นเพียงกระดาษห่อหุ้มที่แตกต่างกันในฟังก์ชันเดียวกันนี้

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

น่าเกลียด

มันแย่ลง NO_BACKSLASH_ESCAPESอาจไม่ใช่เรื่องแปลกทั้งหมดเนื่องจากความจำเป็นในการใช้งานเพื่อความเข้ากันได้กับ SQL มาตรฐาน (เช่นดูส่วน 5.3 ของข้อกำหนดSQL-92ได้แก่ การสร้าง<quote symbol> ::= <quote><quote>ไวยากรณ์และการขาดความหมายพิเศษใด ๆ ที่กำหนดให้กับแบ็กสแลช) นอกจากนี้แนะนำให้ใช้อย่างชัดเจนเพื่อเป็นวิธีแก้ปัญหาสำหรับข้อบกพร่อง (แก้ไขนานแล้ว) ที่โพสต์ของ ircmaxell อธิบายไว้ ใครจะรู้บ้างว่า DBA บางตัวอาจกำหนดค่าให้เปิดโดยค่าเริ่มต้นเนื่องจากเป็นการกีดกันการใช้วิธีการหลีกเลี่ยงที่ไม่ถูกต้องเช่นaddslashes().

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

พระคุณแห่งการออม

ตราบเท่าที่คุณตั้งค่าโหมด SQL อย่างชัดเจนเสมอไม่ให้รวมNO_BACKSLASH_ESCAPESหรืออ้างตัวอักษรสตริง MySQL โดยใช้อักขระอัญประกาศเดี่ยวข้อผิดพลาดนี้ไม่สามารถอยู่ด้านหลังส่วนหัวที่น่าเกลียดได้: escape_quotes_for_mysql()จะไม่ใช้ตามลำดับหรือสมมติฐานเกี่ยวกับอักขระเครื่องหมายคำพูดที่ต้องการการทำซ้ำ ถูกต้อง.

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

ใน PDO ทั้งฟังก์ชันที่เทียบเท่ากันPDO::quote()และโปรแกรมจำลองคำสั่งที่เตรียมไว้จะเรียกใช้mysql_handle_quoter()- ซึ่งทำเช่นนี้ทุกประการ: ช่วยให้มั่นใจได้ว่าลิเทอรัลที่หลบหนีถูกยกมาในเครื่องหมายอัญประกาศเดี่ยวดังนั้นคุณจึงมั่นใจได้ว่า PDO จะได้รับการป้องกันจากข้อบกพร่องนี้เสมอ

สำหรับ MySQL v5.7.6 ข้อบกพร่องนี้ได้รับการแก้ไขแล้ว ดูบันทึกการเปลี่ยนแปลง :

เพิ่มหรือเปลี่ยนแปลงฟังก์ชันการทำงาน

  • การเปลี่ยนแปลงที่เข้ากันไม่ได้:มีการนำฟังก์ชัน C API ใหม่mysql_real_escape_string_quote()มาใช้แทนmysql_real_escape_string()เนื่องจากฟังก์ชันหลังอาจไม่สามารถเข้ารหัสอักขระได้อย่างถูกต้องเมื่อNO_BACKSLASH_ESCAPESเปิดใช้งานโหมด SQL ในกรณีนี้mysql_real_escape_string()ไม่สามารถหลีกเลี่ยงอักขระเครื่องหมายคำพูดได้ยกเว้นโดยการเพิ่มเป็นสองเท่าและเพื่อให้ทำได้อย่างถูกต้องต้องทราบข้อมูลเพิ่มเติมเกี่ยวกับบริบทการอ้างอิงมากกว่าที่มีอยู่ mysql_real_escape_string_quote()ใช้อาร์กิวเมนต์พิเศษสำหรับระบุบริบทการอ้างอิง สำหรับรายละเอียดการใช้งานดู mysql_real_escape_string_quote ()

     บันทึก

    ควรแก้ไขแอปพลิเคชันเพื่อใช้mysql_real_escape_string_quote()แทนmysql_real_escape_string()ซึ่งตอนนี้ล้มเหลวและเกิดCR_INSECURE_API_ERRข้อผิดพลาดหากNO_BACKSLASH_ESCAPESเปิดใช้งาน

    อ้างอิง: ดูข้อผิดพลาด # 19211994 ด้วย

ตัวอย่างที่ปลอดภัย

เมื่อนำมารวมกับข้อบกพร่องที่อธิบายโดย ircmaxell ตัวอย่างต่อไปนี้ปลอดภัยทั้งหมด (สมมติว่ามีการใช้ MySQL ที่ช้ากว่า 4.1.20, 5.0.22, 5.1.11 หรือไม่ได้ใช้การเข้ารหัสการเชื่อมต่อ GBK / Big5) :

mysql_set_charset($charset);
mysql_query("SET SQL_MODE=''");
$var = mysql_real_escape_string('" OR 1=1 /*'); mysql_query('SELECT * FROM test WHERE name = "'.$var.'" LIMIT 1');

... เพราะเราได้เลือกโหมด SQL NO_BACKSLASH_ESCAPESอย่างชัดเจนที่ไม่รวม

mysql_set_charset($charset); $var = mysql_real_escape_string("' OR 1=1 /*");
mysql_query("SELECT * FROM test WHERE name = '$var' LIMIT 1");

... เพราะเรากำลังอ้างถึงลิเทอรัลสตริงของเราด้วยเครื่องหมายอัญประกาศเดี่ยว

$stmt = $pdo->prepare('SELECT * FROM test WHERE name = ? LIMIT 1'); $stmt->execute(["' OR 1=1 /*"]);

... เนื่องจากคำสั่ง PDO ที่เตรียมไว้นั้นไม่ได้รับผลกระทบจากช่องโหว่นี้ (และของ ircmaxell ก็เช่นกันหากคุณใช้PHP≥5.3.6และชุดอักขระได้รับการตั้งค่าอย่างถูกต้องใน DSN หรือการจำลองคำสั่งที่เตรียมไว้ถูกปิดใช้งาน) .

$var = $pdo->quote("' OR 1=1 /*");
$stmt = $pdo->query("SELECT * FROM test WHERE name = $var LIMIT 1");

... เนื่องจากquote()ฟังก์ชั่นของ PDO ไม่เพียง แต่หลีกหนีลิเทอรัลเท่านั้น แต่ยังใส่เครื่องหมายคำพูด (ใน'อักขระเครื่องหมายคำพูดเดี่ยว) โปรดทราบว่าเพื่อหลีกเลี่ยงข้อผิดพลาดของ ircmaxell ในกรณีนี้คุณต้องใช้PHP≥5.3.6 และตั้งค่าอักขระใน DSN ให้ถูกต้อง

$stmt = $mysqli->prepare('SELECT * FROM test WHERE name = ? LIMIT 1'); $param = "' OR 1=1 /*";
$stmt->bind_param('s', $param);
$stmt->execute();

... เพราะ MySQLi งบที่เตรียมไว้ปลอดภัย

ห่อ

ดังนั้นหากคุณ:

  • ใช้งบที่เตรียมไว้

หรือ

  • ใช้ MySQL v5.7.6 หรือใหม่กว่า

หรือ

  • ในนอกจากนี้เพื่อการหนึ่งของการแก้ปัญหาในการสรุป ircmaxell ของการใช้งานที่หนึ่งอย่างน้อย:

    • PDO;
    • ลิเทอรัลสตริงที่ยกมาเดี่ยว หรือ
    • โหมด SQL ที่ตั้งค่าอย่างชัดเจนซึ่งไม่รวม NO_BACKSLASH_ESCAPES

... คุณควรปลอดภัยอย่างสมบูรณ์ (ช่องโหว่ที่อยู่นอกขอบเขตของสตริงที่หลุดออกไปด้านข้าง)

19 Slava Apr 21 2011 at 15:01

ไม่มีอะไรที่จะผ่านสิ่งนั้นไปได้นอกจาก%สัญลักษณ์แทน อาจเป็นอันตรายหากคุณใช้LIKEคำสั่งเนื่องจากผู้โจมตีสามารถใส่ได้%เหมือนกับการเข้าสู่ระบบหากคุณไม่กรองสิ่งนั้นออกและจะต้องปิดกั้นรหัสผ่านของผู้ใช้ของคุณ ผู้คนมักแนะนำให้ใช้ข้อความที่เตรียมไว้เพื่อให้ปลอดภัย 100% เนื่องจากข้อมูลไม่สามารถรบกวนการสืบค้นได้ด้วยวิธีนั้น แต่สำหรับคำถามง่ายๆเช่นนี้อาจมีประสิทธิภาพมากกว่าที่จะทำบางสิ่งเช่น$login = preg_replace('/[^a-zA-Z0-9_]/', '', $login);