如果使用 prepare() 和 bind_param() 作為在 SQL 陳述式中放置任意字串值的替代方案,則可以避免所有字元跳脫問題(在 PHP 端)。 這是可行的,因為繫結的參數值不是透過 SQL 陳述式語法傳遞的。
(PHP 5, PHP 7, PHP 8)
mysqli::real_escape_string -- mysqli_real_escape_string — 將字串中的特殊字元逸出,以便在 SQL 陳述式中使用,同時考量到連線的目前字元集
物件導向風格
程序風格
此函數用於建立可在 SQL 陳述式中使用的合法 SQL 字串。給定的字串會被編碼以產生一個已逸出處理的 SQL 字串,並會考量連線目前的字元集。
必須在伺服器層級或使用 API 函數 mysqli_set_charset() 設定字元集,才能影響 mysqli_real_escape_string()。有關更多資訊,請參閱關於字元集的概念章節。
mysql
僅程序風格:由 mysqli_connect() 或 mysqli_init() 返回的 mysqli 物件
string
要逸出處理的字串。
編碼的字元為 NUL (ASCII 0)
、\n
、\r
、\
、'
、"
和 CTRL+Z。
返回已逸出處理的字串。
範例 #1 mysqli::real_escape_string() 範例
物件導向風格
<?php
mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT);
$mysqli = new mysqli("localhost", "my_user", "my_password", "world");
$city = "'s-Hertogenbosch";
/* 使用已逸出處理的 $city 的查詢將會成功 */
$query = sprintf("SELECT CountryCode FROM City WHERE name='%s'",
$mysqli->real_escape_string($city));
$result = $mysqli->query($query);
printf("查詢回傳 %d 列資料。\n", $result->num_rows);
/* 這個查詢將會失敗,因為我們沒有逸出處理 $city */
$query = sprintf("SELECT CountryCode FROM City WHERE name='%s'", $city);
$result = $mysqli->query($query);
程序風格
<?php
mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT);
$mysqli = mysqli_connect("localhost", "my_user", "my_password", "world");
$city = "'s-Hertogenbosch";
/* 已跳脫 $city 的查詢會正常運作 */
$query = sprintf("SELECT CountryCode FROM City WHERE name='%s'",
mysqli_real_escape_string($mysqli, $city));
$result = mysqli_query($mysqli, $query);
printf("查詢結果返回 %d 列。\n", mysqli_num_rows($result));
/* 這個查詢會失敗,因為我們沒有跳脫 $city */
$query = sprintf("SELECT CountryCode FROM City WHERE name='%s'", $city);
$result = mysqli_query($mysqli, $query);
以上範例的輸出會類似於
Select returned 1 rows. Fatal error: Uncaught mysqli_sql_exception: 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 's-Hertogenbosch'' at line 1 in...
如果使用 prepare() 和 bind_param() 作為在 SQL 陳述式中放置任意字串值的替代方案,則可以避免所有字元跳脫問題(在 PHP 端)。 這是可行的,因為繫結的參數值不是透過 SQL 陳述式語法傳遞的。
請注意,此函式不會跳脫 _(底線)和 %(百分比)符號,它們在 LIKE 子句中具有特殊含義。
據我所知,沒有函式可以做到這一點,因此您必須自行在它們前面加上反斜線來跳脫它們。
提供數個支援 UTF-8 / 多位元組的跳脫函式。
這些函式是 mysqli::real_escape_string 的替代方案,只要您的資料庫連線和多位元組擴充套件使用相同的字元集 (UTF-8),它們就會跳脫與 mysqli::real_escape_string 相同的字元,並產生相同的結果。
這是基於我為我的 SQL 查詢建構器類別所做的研究
https://github.com/twister-php/sql
<?php
/**
* 傳回一個字串,在需要跳脫的字元前加上反斜線。
* 根據 MySQL 的要求,適用於多位元組字元集
* 編碼的字元為 NUL (ASCII 0), \n, \r, \, ', ", 和 ctrl-Z。
*
* @param string $string 要添加斜線的字串
* @return 在保留字元前加上 `\` 的 $string
*
* @author Trevor Herselman
*/
if (function_exists('mb_ereg_replace'))
{
function mb_escape(string $string)
{
return mb_ereg_replace('[\x00\x0A\x0D\x1A\x22\x27\x5C]', '\\\0', $string);
}
} else {
function mb_escape(string $string)
{
return preg_replace('~[\x00\x0A\x0D\x1A\x22\x27\x5C]~u', '\\\$0', $string);
}
}
?>
跳脫的字元為(與 mysqli::real_escape_string 相同)
00 = \0 (NUL)
0A = \n
0D = \r
1A = ctl-Z
22 = "
27 = '
5C = \
注意:preg_replace() 處於 PCRE_UTF8 (UTF-8) 模式 (`u`)。
增強版本
在跳脫用於 `LIKE` 語法的字串時,請記住您還需要跳脫特殊字元 _ 和 %
因此,這是一個更安全的版本(即使與 mysqli::real_escape_string 相比,因為使用者輸入中的 % 字元可能會導致 LIKE 陳述式中出現意外結果,甚至透過 SQL 注入造成安全漏洞)。
<?php
/**
* 傳回一個字串,其中需要跳脫的字元前會加上反斜線。
* 這是 MySQL 所要求的,並且適用於多位元組字元集
* 編碼的字元為 NUL (ASCII 0)、\n、\r、\、'、" 和 Ctrl-Z。
* 此外,特殊控制字元 % 和 _ 也會被跳脫,
* 適用於所有語句,但特別適用於 `LIKE`。
*
* @param string $string 要加上反斜線的字串
* @return 在保留字元前加上 `\` 的 $string
*
* @author Trevor Herselman
*/
if (function_exists('mb_ereg_replace'))
{
function mb_escape(string $string)
{
return mb_ereg_replace('[\x00\x0A\x0D\x1A\x22\x25\x27\x5C\x5F]', '\\\0', $string);
}
} else {
function mb_escape(string $string)
{
return preg_replace('~[\x00\x0A\x0D\x1A\x22\x25\x27\x5C\x5F]~u', '\\\$0', $string);
}
}
?>
跳脫的其他字元
25 = %
5F = _
額外函式
原始的 MySQL `utf8` 字元集(用於資料表和欄位)僅支援 3 位元組序列。
4 位元組字元並不常見,但我曾遇到 4 位元組 UTF-8 字元導致查詢執行失敗的情況,因此您應盡可能使用 `utf8mb4`。
但是,如果您仍然想使用 `utf8`,則可以使用以下函式來替換所有 4 位元組序列。
<?php
// 修改自:https://stackoverflow.com/a/24672780/2726557
function mysql_utf8_sanitizer(string $str)
{
return preg_replace('/[\x{10000}-\x{10FFFF}]/u', "\xEF\xBF\xBD", $str);
}
?>
自行選擇並承擔風險!
如果您想知道為什麼除了 (\, ' 和 ") 之外,NUL (ASCII 0)、\n、\r 和 Control-Z 也會被跳脫:這不是為了防止 SQL 注入,而是為了防止您的 SQL 日誌檔變得無法讀取。
跳脫 % 和 _ 萬用字元時請小心。根據以下網址底部一個經常被忽略的註釋:
https://mysqldev.dev.org.tw/doc/refman/5.7/en/string-literals.html#character-escape-sequences
跳脫序列 \% 和 \_ *僅*在 LIKE 中出現時才會被解讀為 % 和 _!(MySQL 8.0 也是如此)
在一般的字串字面值中,跳脫序列 \% 和 \_ 會被視為這兩個字元對。因此,如果這些跳脫序列出現在 WHERE "=" 而不是 WHERE LIKE 中,它們將*不會*匹配單個 % 或 _ 字元!
因此,*必須*使用兩個「跳脫」函式:一個用於一般字串字面值的 real-escape-string(或等效函式),以及一個*僅*用於打算在 LIKE 中使用的字串字面值的修改過的跳脫函式。