รองรับ PDO สำหรับการสืบค้นหลายรายการ (PDO_MYSQL, PDO_MYSQLND)


102

ฉันรู้ว่า PDO ไม่สนับสนุนการดำเนินการหลายคำสั่งในคำสั่งเดียว ฉันใช้ Googleing และพบโพสต์สองสามรายการที่พูดถึง PDO_MYSQL และ PDO_MYSQLND

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

จาก: การป้องกัน SQL Injection โดยใช้ PDO และ Zend Framework (มิถุนายน 2553 โดย Julian)

ดูเหมือนว่า PDO_MYSQL และ PDO_MYSQLND จะให้การสนับสนุนสำหรับการสืบค้นหลายรายการ แต่ฉันไม่สามารถค้นหาข้อมูลเพิ่มเติมเกี่ยวกับพวกเขาได้ โครงการเหล่านี้ถูกยกเลิกหรือไม่ มีวิธีใดบ้างในการเรียกใช้แบบสอบถามหลายรายการโดยใช้ PDO


4
ใช้ธุรกรรม SQL
tereško

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

ตอนนี้เป็นปี 2020 และ PDO รองรับสิ่งนี้ - ดูคำตอบของฉันด้านล่าง
Andris

คำตอบ:


141

อย่างที่ฉันทราบPDO_MYSQLNDถูกแทนที่ด้วยPDO_MYSQLPHP 5.3 ส่วนที่สับสนคือชื่อยังPDO_MYSQLอยู่ ตอนนี้ ND เป็นไดรเวอร์เริ่มต้นสำหรับ MySQL + PDO

โดยรวมแล้วในการดำเนินการค้นหาหลายรายการพร้อมกันคุณต้อง:

  • PHP 5.3 ขึ้นไป
  • mysqlnd
  • งบเตรียมจำลอง ตรวจสอบให้แน่ใจว่าPDO::ATTR_EMULATE_PREPARESได้ตั้งค่าเป็น1(ค่าเริ่มต้น) หรือคุณสามารถหลีกเลี่ยงการใช้งบที่เตรียมไว้และใช้$pdo->execโดยตรง

ใช้ exec

$db = new PDO("mysql:host=localhost;dbname=test", 'root', '');

// works regardless of statements emulation
$db->setAttribute(PDO::ATTR_EMULATE_PREPARES, 0);

$sql = "
DELETE FROM car; 
INSERT INTO car(name, type) VALUES ('car1', 'coupe'); 
INSERT INTO car(name, type) VALUES ('car2', 'coupe');
";

$db->exec($sql);

การใช้คำสั่ง

$db = new PDO("mysql:host=localhost;dbname=test", 'root', '');

// works not with the following set to 0. You can comment this line as 1 is default
$db->setAttribute(PDO::ATTR_EMULATE_PREPARES, 1);

$sql = "
DELETE FROM car; 
INSERT INTO car(name, type) VALUES ('car1', 'coupe'); 
INSERT INTO car(name, type) VALUES ('car2', 'coupe');
";

$stmt = $db->prepare($sql);
$stmt->execute();

หมายเหตุ:

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


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

9
รหัสในคำตอบนี้ไม่ดีและส่งเสริมการปฏิบัติที่เป็นอันตรายบางอย่าง (การใช้การจำลองเพื่อเตรียมคำสั่งซึ่งทำให้โค้ดเปิดช่องโหว่ในการแทรก SQL ) อย่าใช้มัน
tereško

17
ไม่มีอะไรผิดปกติกับคำตอบนี้และโหมดจำลองโดยเฉพาะ เปิดใช้งานโดยค่าเริ่มต้นใน pdo_mysql และหากมีปัญหาใด ๆ ก็จะมีการฉีดยาหลายพันครั้ง แต่ยังไม่มีใครเข้าใกล้ มันจะไป
สามัญสำนึกของคุณ

3
ในความเป็นจริงมีเพียงคนเดียวที่จัดการให้ไม่เพียง แต่แสดงอารมณ์ แต่มีข้อโต้แย้งบางอย่างคือ ircmaxell อย่างไรก็ตามลิงก์ที่เขานำมานั้นค่อนข้างไม่เกี่ยวข้อง ประการแรกไม่สามารถใช้งานได้เลยเนื่องจากมีการระบุไว้อย่างชัดเจนว่า"PDO มีภูมิคุ้มกันจากข้อบกพร่องนี้เสมอ" ในขณะที่อันที่สองสามารถแก้ไขได้โดยการตั้งค่าการเข้ารหัสที่เหมาะสม ดังนั้นจึงสมควรได้รับหมายเหตุไม่เตือนและน่าสนใจน้อยกว่า
สามัญสำนึกของคุณ

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

17

หลังจากครึ่งวันของการเล่นซอกับสิ่งนี้พบว่า PDO มีจุดบกพร่องที่ ...

-

//This would run as expected:
$pdo->exec("valid-stmt1; valid-stmt2;");

-

//This would error out, as expected:
$pdo->exec("non-sense; valid-stmt1;");

-

//Here is the bug:
$pdo->exec("valid-stmt1; non-sense; valid-stmt3;");

มันจะดำเนินการ"valid-stmt1;"หยุด"non-sense;"และไม่เกิดข้อผิดพลาด จะไม่เรียกใช้"valid-stmt3;"คืนจริงและโกหกว่าทุกอย่างดำเนินไปด้วยดี

ฉันคาดหวังว่ามันจะผิดพลาด"non-sense;"แต่ก็ไม่เกิดขึ้น

ที่นี่ฉันพบข้อมูลนี้: แบบสอบถาม PDO ที่ไม่ถูกต้องไม่ส่งคืนข้อผิดพลาด

นี่คือจุดบกพร่อง: https://bugs.php.net/bug.php?id=61613


ดังนั้นฉันจึงลองทำสิ่งนี้กับ mysqli และยังไม่พบคำตอบที่ชัดเจนเกี่ยวกับวิธีการทำงานดังนั้นฉันจึงคิดว่าจะทิ้งไว้ที่นี่สำหรับผู้ที่ต้องการใช้ ..

try{
    // db connection
    $mysqli = new mysqli("host", "user" , "password", "database");
    if($mysqli->connect_errno){
        throw new Exception("Connection Failed: [".$mysqli->connect_errno. "] : ".$mysqli->connect_error );
        exit();
    }

    // read file.
    // This file has multiple sql statements.
    $file_sql = file_get_contents("filename.sql");

    if($file_sql == "null" || empty($file_sql) || strlen($file_sql) <= 0){
        throw new Exception("File is empty. I wont run it..");
    }

    //run the sql file contents through the mysqli's multi_query function.
    // here is where it gets complicated...
    // if the first query has errors, here is where you get it.
    $sqlFileResult = $mysqli->multi_query($file_sql);
    // this returns false only if there are errros on first sql statement, it doesn't care about the rest of the sql statements.

    $sqlCount = 1;
    if( $sqlFileResult == false ){
        throw new Exception("File: '".$fullpath."' , Query#[".$sqlCount."], [".$mysqli->errno."]: '".$mysqli->error."' }");
    }

    // so handle the errors on the subsequent statements like this.
    // while I have more results. This will start from the second sql statement. The first statement errors are thrown above on the $mysqli->multi_query("SQL"); line
    while($mysqli->more_results()){
        $sqlCount++;
        // load the next result set into mysqli's active buffer. if this fails the $mysqli->error, $mysqli->errno will have appropriate error info.
        if($mysqli->next_result() == false){
            throw new Exception("File: '".$fullpath."' , Query#[".$sqlCount."], Error No: [".$mysqli->errno."]: '".$mysqli->error."' }");
        }
    }
}
catch(Exception $e){
    echo $e->getMessage(). " <pre>".$e->getTraceAsString()."</pre>";
}

จะทำงานได้หรือไม่ถ้าคุณเรียกใช้$pdo->exec("valid-stmt1; non-sense; valid-stmt3;");โดยไม่มีผู้บริหารสองคนก่อนหน้านี้? ฉันจะได้รับมันจะโยนข้อผิดพลาดอยู่ตรงกลาง แต่ไม่เมื่อดำเนินการหลังจากที่ผู้บริหารที่ประสบความสำเร็จ
Jeff Puckett

ไม่มันไม่ นั่นคือจุดบกพร่องของ PDO
Sai Phaninder Reddy J

1
ที่ไม่ดีของฉันทั้ง 3 $pdo->exec("")เป็นอิสระจากกัน ตอนนี้ฉันแยกพวกเขาออกเพื่อระบุว่าพวกเขาไม่จำเป็นต้องอยู่ในลำดับปัญหาที่จะเกิดขึ้น 3 เหล่านี้คือ 3 การกำหนดค่าของการเรียกใช้แบบสอบถามหลายรายการในคำสั่ง exec เดียว
Sai Phaninder Reddy J

น่าสนใจ. คุณมีโอกาสเห็นคำถามที่โพสต์ไว้หรือไม่? ฉันสงสัยว่าสิ่งนี้ได้รับการแก้ไขบางส่วนหรือไม่เพราะฉันสามารถรับข้อผิดพลาดที่เกิดขึ้นได้หากเป็นเพียงexecหน้าเดียว แต่ถ้าฉันเรียกใช้หลายรายการexecโดยมีคำสั่ง SQL หลายรายการในนั้นฉันจะสร้างข้อผิดพลาดเดียวกันที่นี่ แต่ถ้าเป็นเพียงexecหน้าเดียวฉันก็ไม่สามารถทำซ้ำได้
Jeff Puckett

ข้อความนั้นexecในเพจของคุณมีหลายข้อความหรือไม่
Sai Phaninder Reddy J

3

แนวทางที่รวดเร็วและสกปรก:

function exec_sql_from_file($path, PDO $pdo) {
    if (! preg_match_all("/('(\\\\.|.)*?'|[^;])+/s", file_get_contents($path), $m))
        return;

    foreach ($m[0] as $sql) {
        if (strlen(trim($sql)))
            $pdo->exec($sql);
    }
}

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


1
นี้ล้มเหลวหากไฟล์ SQL ของคุณมี MySQL ใด ๆ ในตัวคำสั่ง ... มันอาจจะระเบิดออก จำกัด หน่วยความจำ PHP ของคุณมากเกินไปถ้าไฟล์ SQL ที่มีขนาดใหญ่ ... แยกบน;แบ่งถ้า SQL ของคุณมีขั้นตอนหรือทริกเกอร์นิยาม ... จำนวนมาก เหตุผลว่าทำไมมันถึงไม่ดี
Bill Karwin

1

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

function arrayOfQuerys($arrayQuery)
{
    $mx = true;
    $conn->beginTransaction();
    try {
        foreach ($arrayQuery AS $item) {
            $stmt = $conn->prepare($item["query"]);
            $stmt->execute($item["params"]);
            $result = $stmt->rowCount();
            if($result == 0)
                $mx = false;
         }
         if($mx == true)
             $conn->commit();
         else
             $conn->rollBack();
    } catch (Exception $e) {
        $conn->rollBack();
        echo "Failed: " . $e->getMessage();
    }
    return $mx;
}

สำหรับการใช้งาน (ตัวอย่าง):

 $arrayQuery = Array(
    Array(
        "query" => "UPDATE test SET title = ? WHERE test.id = ?",
        "params" => Array("aa1", 1)
    ),
    Array(
        "query" => "UPDATE test SET title = ? WHERE test.id = ?",
        "params" => Array("bb1", 2)
    )
);
arrayOfQuerys($arrayQuery);

และการเชื่อมต่อของฉัน:

    try {
        $options = array(
            //For updates where newvalue = oldvalue PDOStatement::rowCount()   returns zero. You can use this:
            PDO::MYSQL_ATTR_FOUND_ROWS => true
        );
        $conn = new PDO("mysql:host=$servername;dbname=$database", $username, $password, $options);
        $conn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
    } catch (PDOException $e) {
        echo "Error connecting to SQL Server: " . $e->getMessage();
    }

หมายเหตุ:
โซลูชันนี้ช่วยให้คุณสามารถเรียกใช้คำสั่งหลายคำสั่งร่วมกันได้
หากคำสั่งไม่ถูกต้องเกิดขึ้นก็จะไม่ดำเนินการคำสั่งอื่นใด


0

พยายามติดตามรหัส

 $db = new PDO("mysql:host={$dbhost};dbname={$dbname};charset=utf8", $dbuser, $dbpass, array(PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION));

แล้ว

 try {
 $db->query('SET NAMES gbk');
 $stmt = $db->prepare('SELECT * FROM 2_1_paidused WHERE NumberRenamed = ? LIMIT 1');
 $stmt->execute(array("\xbf\x27 OR 1=1 /*"));
 }
 catch (PDOException $e){
 echo "DataBase Errorz: " .$e->getMessage() .'<br>';
 }
 catch (Exception $e) {
 echo "General Errorz: ".$e->getMessage() .'<br>';
 }

และได้

DataBase Errorz: SQLSTATE[42000]: Syntax error or access violation: 1064 You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '/*' LIMIT 1' at line 1

ถ้าเพิ่ม$db->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);หลัง$db = ...

จากนั้นมีหน้าว่าง

หากSELECTลองแทนแสดงDELETEว่าในทั้งสองกรณีมีข้อผิดพลาดเช่น

 DataBase Errorz: SQLSTATE[42000]: Syntax error or access violation: 1064 You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '* FROM 2_1_paidused WHERE NumberRenamed = '¿\' OR 1=1 /*' LIMIT 1' at line 1

ดังนั้นข้อสรุปของฉันว่าฉีดไม่ได้ ...


3
คุณควรตั้งคำถามใหม่ที่อ้างอิงถึงคำถามนี้
สามัญสำนึกของคุณ

ไม่มีคำถามมากนักจากสิ่งที่ฉันพยายาม และข้อสรุปของฉัน คำถามเริ่มต้นเป็นคำถามเก่าซึ่งอาจไม่เกิดขึ้นจริงในขณะนี้
Andris

ไม่แน่ใจว่าสิ่งนี้เกี่ยวข้องกับสิ่งใดในคำถามอย่างไร
cHao

ในคำถามคือคำคำbut you risk to be injected with multiple queries.ตอบของฉันเกี่ยวกับการฉีดยา
Andris

0

ลองใช้ฟังก์ชันนี้: แบบสอบถามจำนวนมากและการแทรกค่าหลายค่า

function employmentStatus($Status) {
$pdo = PDO2::getInstance();

$sql_parts = array(); 
for($i=0; $i<count($Status); $i++){
    $sql_parts[] = "(:userID, :val$i)";
}

$requete = $pdo->dbh->prepare("DELETE FROM employment_status WHERE userid = :userID; INSERT INTO employment_status (userid, status) VALUES ".implode(",", $sql_parts));
$requete->bindParam(":userID", $_SESSION['userID'],PDO::PARAM_INT);
for($i=0; $i<count($Status); $i++){
    $requete->bindParam(":val$i", $Status[$i],PDO::PARAM_STR);
}
if ($requete->execute()) {
    return true;
}
return $requete->errorInfo();
}

0

PDO รองรับสิ่งนี้ (ณ ปี 2020) เพียงแค่ทำการเรียกใช้ query () บนวัตถุ PDO ตามปกติโดยแยกการสืบค้นตาม; จากนั้น nextRowset () เพื่อก้าวไปยังผลลัพธ์ SELECT ถัดไปหากคุณมีหลาย ชุดผลลัพธ์จะอยู่ในลำดับเดียวกับข้อความค้นหา เห็นได้ชัดว่าเกี่ยวกับผลกระทบด้านความปลอดภัย - ดังนั้นอย่ายอมรับคำค้นหาที่ผู้ใช้ให้มาใช้พารามิเตอร์ ฯลฯ ฉันใช้กับแบบสอบถามที่สร้างโดยรหัสเป็นต้น

$statement = $connection->query($query);
do {
  $data[] = $statement->fetchAll(PDO::FETCH_ASSOC);
} while ($statement->nextRowset());

ฉันไม่เคยเข้าใจเหตุผลแบบนี้ "นี่คือรหัสที่เป็นช่องโหว่ขนาดใหญ่ในการรักษาความปลอดภัยที่ละเลยแนวทางปฏิบัติที่ดีที่แนะนำทั้งหมดดังนั้นคุณต้องคำนึงถึงผลกระทบด้านความปลอดภัย" ใครน่าจะคิดยังไง เมื่อไหร่ที่ควรคิด - ก่อนใช้โค้ดนี้หรือหลังโดนแฮ็ก? ทำไมคุณไม่คิดให้ดีก่อนที่จะเขียนฟังก์ชันนี้หรือเสนอให้คนอื่น ๆ
สามัญสำนึกของคุณ

2
เรียน @YourCommonSense การเรียกใช้การสืบค้นหลายรายการในครั้งเดียวช่วยในเรื่องประสิทธิภาพการรับส่งข้อมูลเครือข่ายน้อยลง + เซิร์ฟเวอร์สามารถเพิ่มประสิทธิภาพการสืบค้นที่เกี่ยวข้องได้ ตัวอย่าง (แบบง่าย) ของฉันมีไว้เพื่อแนะนำวิธีการที่จำเป็นในการใช้งานเท่านั้น เป็นช่องโหว่แห่งความปลอดภัยก็ต่อเมื่อคุณไม่ใช้แนวทางปฏิบัติที่ดีเหล่านั้นที่คุณอ้างถึง BTW ฉันสงสัยคนที่พูดว่า "ฉันจะไม่มีวันเข้าใจ ... " เมื่อพวกเขาทำได้อย่างง่ายดาย ... :-)
Andris
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.