1. 程式人生 > >SQL注入防禦之三——SQL語句預處理(PHP)

SQL注入防禦之三——SQL語句預處理(PHP)

許多成熟的資料庫都支援預處理語句(Prepared Statements)的概念。它們是什麼東西?你可以把它們想成是一種編譯過的要執行的SQL語句模板,可以使用不同的變數引數定製它。預處理語句的引數不需要使用引號,底層驅動會為你處理這個。如果你的應用獨佔地使用預處理語句,你就可以確信沒有SQL注入會發生。

概述

預處理語句用於執行多個相同的 SQL 語句,並且執行效率更高。

預處理語句的工作原理如下:

  1. 預處理:建立 SQL 語句模板併發送到資料庫。預留的值使用引數 “?” 標記 。例如:
 INSERT 
            INTO MyGuests (firstname, lastname, email) VALUES
(?, ?, ?)
  1. 資料庫解析,編譯,對SQL語句模板執行查詢優化,並存儲結果不輸出。
  2. 執行:最後,將應用繫結的值傳遞給引數(”?” 標記),資料庫執行語句。應用可以多次執行語句,如果引數的值不一樣。

相比於直接執行SQL語句,預處理語句有兩個主要優點:

  1. 預處理語句大大減少了分析時間,只做了一次查詢(雖然語句多次執行)。
  2. 繫結引數減少了伺服器頻寬,你只需要傳送查詢的引數,而不是整個語句。
  3. 預處理語句針對SQL注入是非常有用的,因為引數值傳送後使用不同的協議,保證了資料的合法性。

 而在PHP當中,使用預處理語句主要有兩種方法:

  1. 使用mysqli資料庫
  2. 使用PDOStatement類物件

準備

 分析之前我們先來檢視PDOStatement類中的全部成員方法如下所示:

PDOStatement::bindColumn — 繫結一列到一個 PHP 變數
PDOStatement::bindParam — 繫結一個引數到指定的變數名
PDOStatement::bindValue — 把一個值繫結到一個引數
PDOStatement::closeCursor — 關閉遊標,使語句能再次被執行。
PDOStatement::columnCount — 返回結果集中的列數
PDOStatement::debugDumpParams — 列印一條 SQL 預處理命令
PDOStatement
::errorCode — 獲取跟上一次語句控制代碼操作相關的 SQLSTATE PDOStatement::errorInfo — 獲取跟上一次語句控制代碼操作相關的擴充套件錯誤資訊 PDOStatement::execute — 執行一條預處理語句 PDOStatement::fetch — 從結果集中獲取下一行 PDOStatement::fetchAll — 返回一個包含結果集中所有行的陣列 PDOStatement::fetchColumn — 從結果集中的下一行返回單獨的一列。 PDOStatement::fetchObject — 獲取下一行並作為一個物件返回。 PDOStatement::getAttribute — 檢索一個語句屬性 PDOStatement::getColumnMeta — 返回結果集中一列的元資料 PDOStatement::nextRowset — 在一個多行集語句控制代碼中推進到下一個行集 PDOStatement::rowCount — 返回受上一個 SQL 語句影響的行數 PDOStatement::setAttribute — 設定一個語句屬性 PDOStatement::setFetchMode — 為語句設定預設的獲取模式。

分析

MySQLi 預處理語句

以下例項在 MySQLi 中使用了預處理語句,並綁定了相應的引數:

<?php
$servername = "localhost";
$username = "username";
$password = "password";
$dbname = "myDB";

// 建立連線
$conn = new mysqli($servername, $username, $password, $dbname);

// 檢測連線
if ($conn->connect_error) {
    die("連線失敗: " . $conn->connect_error);
}

// 預處理及繫結
$stmt = $conn->prepare("INSERT INTO MyGuests (firstname, lastname, email) VALUES(?, ?, ?)");
$stmt->bind_param("sss", $firstname, $lastname, $email);

// 設定引數並執行
$firstname = "John";
$lastname = "Doe";
$email = "[email protected]";
$stmt->execute();

$firstname = "Mary";
$lastname = "Moe";
$email = "[email protected]";
$stmt->execute();

$firstname = "Julie";
$lastname = "Dooley";
$email = "[email protected]";
$stmt->execute();

echo "新記錄插入成功";

$stmt->close();
$conn->close();
?>

解析以下例項的每行程式碼:

"INSERT INTO MyGuests (firstname, lastname, email) VALUES(?, ?, ?)"

在 SQL 語句中,我們使用了問號 (?),在此我們可以將問號替換為整型,字串,雙精度浮點型和布林值。

接下來,讓我們來看下 bind_param() 函式:

$stmt->bind_param("sss", $firstname, $lastname, $email);

該函式綁定了 SQL 的引數,且告訴資料庫引數的值。 “sss” 引數列處理其餘引數的資料型別。s 字元告訴資料庫該引數為字串。

引數有以下四種類型:

i - integer(整型)
d - double(雙精度浮點型)
s - string(字串)
b - BLOB(binary large object:二進位制大物件)

每個引數都需要指定型別。

通過告訴資料庫引數的資料型別,可以降低 SQL 注入的風險。

PDO Statement類物件預處理語句

1.準備語句:

 重複執行一個SQL查詢,通過每次迭代使用不同的引數,這種情況使用預處理語句執行效率最高。使用預處理語句,首先需要在資料庫伺服器中先準備好“一個SQL語句”,但並不需要馬上執行。PDO支援使用“佔位符”語法,將變數繫結到這個預處理的SQL語句中。對於一個準備好的SQL語句,如果在每次執行時都要改變一些列值,這種情況必須使用“佔位符號”而不是具體的列值。在PDO中有兩種使用佔位符的語法:“命名引數”和“問號引數”,使用哪一種語法要看個人的喜好。

使用命名引數作為佔位符的INSERT插入語句:

$dbh->prepare(“insert into contactinfo(name,address,phone) values(:name,:address,:phone)”);

 需要自定義一個字串作為“命名引數”,每個命名引數需要冒號(:)開始,引數的命名一定要有意義,最好和對應的欄位名稱相同。
使用問號(?)引數作為佔位符的INSERT插入語句:

$dbh->prepare(“insert into contactinfo(name,address,phone) values(?,?,?)”);

 問號引數一定要和欄位的位置順序對應。不管是使用哪一種引數作為佔位符構成的查詢,或是語句中沒有用到佔位符,都需要使用PDO物件中的prepare()方法,去準備這個將要用於迭代執行的查詢,並返回PDOStatement類物件。

2.繫結引數:

 當SQL語句通過PDO物件中的prepare()方法在資料庫伺服器端準備好了以後,如果使用了佔位符,就需要在每次執行時替換輸入的引數。可以通過PDOStatement物件中的bindParam()方法,把引數變數繫結到準備好的佔位符上(位置或名字要對應)。方法bindParame()的原型如下所示:

bool PDOStatement::bindParam ( mixed $parameter , mixed &$variable [, int $data_type = PDO::PARAM_STR [, int $length [, mixed $driver_options ]]] )

第一個引數parameter是必選項,如果在準備好的查詢中佔位符語法使用名字引數,那麼將名字引數字串作為bindParam()方法的第一個引數提供。如果佔位符語法使用問號引數,那麼將準備好的查詢中列值佔位符的索引偏移量,作為該方法的第一個引數。

第二個引數variable也是可選項,提供供給第一個引數所指定佔位符的值。因為該引數是按引用傳遞的,所以只能提供變數作為引數,不能直接提供數值。

第三個引數data_type是可選項,為當前被繫結的引數設定資料型別。可以為以下值。

PDO::PARAM_BOOL 代表boolean資料型別。
PDO::PARAM_NULL 代表SQL中的NULL型別。
PDO::PARAM_INT 代表SQL中的INTEGER資料型別。
PDO::PARAM_STR 代表SQL中的CHAR、VARCHAR和其他字串資料型別。
PDO::PARAM_LOB 代表SQL中大物件資料型別。

第四個引數length是可選項,用於指定資料型別的長度。

第五個引數driver_options是可選項,通過該引數提供任何資料庫驅動程式特定的選項。
 使用命名引數作為佔位符的引數繫結示例:

<?php
//...省略PDO連線資料庫程式碼
$query = "insert into contactinfo (name,address,phone) values(:name,:address,:phone)";
$stmt = $dbh->prepare($query);          //呼叫PDO物件中的prepare()方法

$stmt->blinparam(':name',$name);        //將變數$name的引用繫結到準備好的查詢名字引數":name"中
$stmt->blinparam(':address',$address);
$stmt->blinparam(':phone',phone);
//...
?>

 使用問號(?)作為佔位符的引數繫結示例:

<?php
//...省略PDO連線資料庫程式碼
$query = "insert into contactinfo (name,address,phone) values(?,?,?)";
$stmt = $dbh->prepare($query);          //呼叫PDO物件中的prepare()方法

$stmt->blinparam(1,$name,PDO::PARAM_STR);        //將變數$name的引用繫結到準備好的查詢名字引數":name"中
$stmt->blinparam(2,$address,PDO::PARAM_STR);
$stmt->blinparam(3,phone,PDO::PARAM_STR,20);
//...
?>

3.執行準備語句:

 當準備語句完成,並綁定了相應的引數後,就可以通過呼叫PDOStatement類物件中的execute()方法,反覆執行在資料庫快取區準備好的語句了。在下面的示例中,向前面提供的contactinfo表中,使用預處理方式連續執行同一個INSERT語句,通過改變不同的引數新增兩條記錄。如下所示:

<?php
try {
     $dbh = new PDO('mysql:dbname=testdb;host=localhost', $username, $passwd);
}catch (PDOException $e){
    echo '資料庫連線失敗:'.$e->getMessage();
    exit;
}

$query = "insert into contactinfo (name,address,phone) values(?,?,?)";
$stmt = $dbh->prepare($query);

$stmt->blinparam(1,$name);     
$stmt->blinparam(2,$address);
$stmt->blinparam(3,phone);

$name = "趙某某";
$address = "海淀區中關村";
$phone = "15801688348";

$stmt->execute();           //執行引數被繫結後的準備語句
?>

 如果你只是要傳遞輸入引數,並且有許多這樣的引數要傳遞,那麼你會覺得下面所示的快捷方式語法非常有幫助。是通過在execute()方法中提供一個可選引數,該引數是由準備查詢中的命名引數佔位符組成的陣列,這是第二種為預處理查詢在執行中替換輸入引數的方式。此語法使你能夠省去對$stmt->bindParam()的呼叫。將上面的示例做如下修改:

<?php
//...省略PDO連線資料庫程式碼
$query = "insert into contactinfo (name,address,phone) values(?,?,?)";
$stmt = $dbh->prepare($query);

//傳遞一個數組為預處理查詢中的命名引數繫結值,並執行一次。
$stmt->execute(array("趙某某","海淀區","15801688348"));
?>

 另外,如果執行的是INSERT語句,並且資料表中有自動增長的ID欄位,可以使用PDO物件中的lastinsertId()方法獲取最後插入資料表中的記錄ID。如果需要檢視其他DML語句是否執行成功,可以通過PDOStatement類物件中的rowCount()方法獲取影響記錄的行數。

總結

 預處理語句的引數不需要使用引號,底層驅動會為你處理這個。如果你的應用獨佔地使用預處理語句,你就可以確信沒有SQL注入會發生。
 查詢只需要被解析(或準備)一次,但可以使用相同或不同的引數執行多次。當查詢準備好(Prepared)之後,資料庫就會分析,編譯並優化它要執行查詢的計劃。對於複雜查詢來說,如果你要重複執行許多次有不同引數的但結構相同的查詢,這個過程會佔用大量的時間,使得你的應用變慢。通過使用一個預處理語句你就可以避免重複分析、編譯、優化的環節。簡單來說,預處理語句使用更少的資源,執行速度也就更快。

相關推薦

SQL注入防禦——SQL語句處理PHP

許多成熟的資料庫都支援預處理語句(Prepared Statements)的概念。它們是什麼東西?你可以把它們想成是一種編譯過的要執行的SQL語句模板,可以使用不同的變數引數定製它。預處理語句的引數不需要使用引號,底層驅動會為你處理這個。如果你的應用獨佔地

SQL注入防禦二——注入關鍵詞過濾PHP

SQL Injection:就是通過把SQL命令插入到Web表單遞交或輸入域名或頁面請求的查詢字串,最終達到欺騙伺服器執行惡意的SQL命令。 概述   歡迎來到本人的SQL注入防禦系列的第二篇文章,上一篇文章我們講到了偽靜態的技術來防止SQL注入,

SQL to LinQ 查詢條件 In的表示.any

strQuery = "SELECT OPER, OPER_DESC, OPER_SHORT_DESC FROM WIPOPER WHERE" + " FACTORY = ?"

程世東老師TensorFlow實戰——個性化推薦,程式碼學習筆記資料匯入&資料處理

程式碼來自於知乎:https://zhuanlan.zhihu.com/p/32078473 /程式碼地址https://github.com/chengstone/movie_recommender/blob/master/movie_recommender.ipynb 下一篇有一些資料的

程世東老師TensorFlow實戰——個性化推薦,程式碼學習筆記資料匯入&資料處理

這篇主要是進行程式碼中的一些數值視覺化,幫助理解 程式碼來自於知乎:https://zhuanlan.zhihu.com/p/32078473 /程式碼地址https://github.com/chengstone/movie_recommender/blob/master/movie_re

Linux作業系統備份:通過二進位制拷貝dd方式實現Linux作業系統資料的備份

今天我們介紹另外一種粗曠,但是相對簡單的備份方法:通過dd命令二進位制拷貝方式備份作業系統資料。dd拷貝的方式不能線上實施,因為dd是二進位制的塊拷貝,若拷貝過程中有寫檔案操作,會導致檔案系統不一致(如某個節點建立到一半被dd拷貝走了),因此,這種方式必須進入記憶體操作系的單使用者模式下操作,實施

WIN32介面開發:DUI雛形開發

前言:這部分涉及工程比較大,所以我打算分開為兩篇來寫,第一篇完成基本框架的構建,第二篇新增上EVENT和NOTIFY機制。 完成目標:仿照DirectUI,完成一個基本雛形,開發一個佈局控制元件(Dialog),和一個按鈕控制元件(Button),通過XML來佈局窗體,最後

Android核心學習----------Power原始碼分析學習1

Android核心學習 -----Power原始碼分析學習(1) 1.    前言 最近學習了一下Android的Power原始碼,雖然還沒學習通透,但是有點感覺了,怕後面忘了東西,就邊學便把東西記錄下來吧。如果有大神再致電一二那更是感激不盡了 Android4.4

字串————向字串快速排序Quick3string

上一篇介紹了字串的兩種經典排序方法(LSD MSD): https://www.cnblogs.com/Unicron/p/11531111.html   在三向字串快速排序中我們只需要改進一下快速排序的程式碼就能實現它,它特別適用於較長的含有公共字首的字串,並且不需要任何額外空間。程

【ADNI】資料處理6ADNI_slice_dataloader ||| show image

ADNI Series 1、【ADNI】資料預處理(1)SPM,CAT12 2、【ADNI】資料預處理(2)獲取 subject slices 3、【ADNI】資料預處理(3)CNNs 4、【ADNI】資料預處理(4)Get top k slices according to CNN

【ADNI】資料處理5Get top k slices (pMCI_sMCI) according to CNNs

ADNI Series 1、【ADNI】資料預處理(1)SPM,CAT12 2、【ADNI】資料預處理(2)獲取 subject slices 3、【ADNI】資料預處理(3)CNNs 4、【ADNI】資料預處理(4)Get top k slices according to CNN

【ADNI】資料處理4Get top k slices according to CNNs

ADNI Series 1、【ADNI】資料預處理(1)SPM,CAT12 2、【ADNI】資料預處理(2)獲取 subject slices 3、【ADNI】資料預處理(3)CNNs 4、【ADNI】資料預處理(4)Get top k slices according to CNN

【ADNI】資料處理3CNNs

ADNI Series 1、【ADNI】資料預處理(1)SPM,CAT12 2、【ADNI】資料預處理(2)獲取 subject slices 3、【ADNI】資料預處理(3)CNNs 4、【ADNI】資料預處理(4)Get top k slices according to CNN

【ADNI】資料處理2獲取 subject slices

ADNI Series 1、【ADNI】資料預處理(1)SPM,CAT12 2、【ADNI】資料預處理(2)獲取 subject slices 3、【ADNI】資料預處理(3)CNNs 4、【ADNI】資料預處理(4)Get top k slices according to CNN

【ADNI】資料處理1SPM,CAT12

ADNI Series 1、【ADNI】資料預處理(1)SPM,CAT12 2、【ADNI】資料預處理(2)獲取 subject slices 3、【ADNI】資料預處理(3)CNNs 4、【ADNI】資料預處理(4)Get top k slices according to CNN

OpenCV在字符提取中進行的處理

的人 lease pos extract .net avg 圖像 rar 識別 OCR簡介熟悉OCR的人都了解,OCR大致分為兩個部分: -文字提取text extractor -文字識別text recognition 其中,第一部分是屬於圖像處理部分,涉及到圖像分割的知

計算機視覺基礎~影象處理

5.1梯度Prewitt濾波/卷積  水平梯度/垂直邊緣  垂直梯度/水平邊緣 5.2梯度Sobel濾波/卷積  梯度Sobel濾波/卷積  垂直梯度/水平邊緣 5.3梯度Laplacian濾波/卷積  二

資料處理2資料整合 和 資料變換 資料規約

資料整合 資料探勘的過程中往往需要的資料分佈在不同的資料庫,資料整合就是將多個數據源合併存放在一個一致的資料儲存(如資料倉庫)中的過程。 實體識別 同名異義 名字相同但實際代表的含義不同 異名同義 名字不同但代表的意思相同 單位不統一 冗餘屬性識別

資料處理1資料清洗

資料預處理的內容主要包括資料清洗,資料整合,資料變換和資料規約。 資料清洗 資料清洗主要是刪除原始資料集中的無關資料、重複資料,平滑噪聲資料,帥選掉與挖掘主題無關的資料,處理缺失值、異常值等。 缺失值處理 缺失值處理的方法可分為三類: 刪除記錄、資料插補和不處理。 常用的

python機器學習::資料處理1【轉】

轉載自:http://2hwp.com/2016/02/03/data-preprocessing/ 常見的資料預處理方法,以下通過sklearn的preprocessing模組來介紹; 1. 標準化(Standardization or Mean Removal and