您好,登錄后才能下訂單哦!
本文主要給大家介紹php內置過濾函數講義,其所涉及的東西,從理論知識來獲悉,有很多書籍、文獻可供大家參考,從現實意義角度出發,億速云累計多年的實踐經驗可分享給大家。
0x00:php內置過濾函數
php有內置的函數用來防御***,簡單的介紹幾個函數。
魔術引號
當打開時,所有的 '(單引號),"(雙引號),\(反斜線)和 NULL 字符都會被自動加上一個反斜線進行轉義。這和 addslashes() 作用完全相同。
一共有三個魔術引號指令:
magic_quotes_gpc 影響到 HTTP 請求數據(GET,POST 和 COOKIE)。不能在運行時改變。在 PHP 中默認值為 on。 參見 get_magic_quotes_gpc()。
magic_quotes_runtime 如果打開的話,大部份從外部來源取得數據并返回的函數,包括從數據庫和文本文件,所返回的數據都會被反斜線轉義。該選項可在運行的時改變,在 PHP 中的默認值為 off。 參見 set_magic_quotes_runtime() 和 get_magic_quotes_runtime()。
magic_quotes_sybase 如果打開的話,將會使用單引號對單引號進行轉義而非反斜線。此選項會完全覆蓋 magic_quotes_gpc。如果同時打開兩個選項的話,單引號將會被轉義成 ''。而雙引號、反斜線 和 NULL 字符將不會進行轉義。 如何取得其值參見 ini_get()。
mysql_real_escape_string
轉義sql語句中使用的字符串中的特殊字符:\x00、\n、\r、\、'、"、\x1a
addslashes()
返回在預定義字符之前添加反斜杠的字符串,預定義字符:'、"、\、NULL
看了很多php網站在防sql注入上還在使用ddslashes和str_replace,百度一下"PHP防注入"也同樣在使用他們,實踐發現就連mysql_real_escape_string也有***可以繞過的辦法,如果你的系統仍在用上面三個方法,建議更好。
用str_replace以及各種php字符替換函數來防注入已經不用我說了,這種“黑名單”式的防御已經被證明是經不起時間考驗的。
下面給出繞過addslasher和mysql_real_escape_string的方法(Trick)。
如果你不確定你的系統是否有SQL注入的風險,請將下面的下面的DEMO部署到你的云服務器,如果運行結果相同,那么請參考最后的完美的解決方案。
mysql:
mysql> select version(); +---------------------+ | version() | +---------------------+ | 5.0.45-community-ny | +---------------------+ 1 row in set (0.00 sec) mysql> create database test default charset GBK; Query OK, 1 row affected (0.00 sec) mysql> use test; Database changed mysql> CREATE TABLE users ( username VARCHAR(32) CHARACTER SET GBK, password VARCHAR(32) CHARACTER SET GBK, PRIMARY KEY (username) ); Query OK, 0 rows affected (0.02 sec) mysql> insert into users SET username='ewrfg', password='wer44'; Query OK, 1 row affected (0.01 sec) mysql> insert into users SET username='ewrfg2', password='wer443'; Query OK, 1 row affected (0.01 sec) mysql> insert into users SET username='ewrfg4', password='wer4434'; Query OK, 1 row affected (0.01 sec)=
php:
<?php echo "PHP version: ".PHP_VERSION."\n"; mysql_connect('servername','username','password'); mysql_select_db("test"); mysql_query("SET NAMES GBK"); $_POST['username'] = chr(0xbf).chr(0x27).' OR username = username /*'; $_POST['password'] = 'guess'; $username = addslashes($_POST['username']); $password = addslashes($_POST['password']); $sql = "SELECT * FROM users WHERE username = '$username' AND password = '$password'"; $result = mysql_query($sql) or trigger_error(mysql_error().$sql); var_dump(mysql_num_rows($result)); var_dump(mysql_client_encoding()); $username = mysql_real_escape_string($_POST['username']); $password = mysql_real_escape_string($_POST['password']); $sql = "SELECT * FROM users WHERE username = '$username' AND password = '$password'"; $result = mysql_query($sql) or trigger_error(mysql_error().$sql); var_dump(mysql_num_rows($result)); var_dump(mysql_client_encoding()); mysql_set_charset("GBK"); $username = mysql_real_escape_string($_POST['username']); $password = mysql_real_escape_string($_POST['password']); $sql = "SELECT * FROM users WHERE username = '$username' AND password = '$password'"; $result = mysql_query($sql) or trigger_error(mysql_error().$sql); var_dump(mysql_num_rows($result)); var_dump(mysql_client_encoding());
結果:
PHP version: 5.5.44 int(3) string(6) "latin1" int(3) string(6) "latin1" int(0) string(3) "gbk"
可以看出來不論是使用addslashes還是mysql_real_escape_string,我都可以利用編碼的漏洞來實現輸入任意密碼就能登錄服務器的注入***!!!!
0x01:寬字節注入
盡管現在呼吁所有的程序都使用unicode編碼,所有的網站都使用utf-8編碼,來一個統一的國際規范。但仍然有很多,包括國內及國外(特別是非英語國家)的一些cms,仍然使用著自己國家的一套編碼,比如gbk,作為自己默認的編碼類型。也有一些cms為了考慮老用戶,所以出了gbk和utf-8兩個版本。
我們就以gbk字符編碼為示范,拉開帷幕。gbk是一種多字符編碼,有一個地方尤其要注意:
通常來說,一個gbk編碼漢字,占用2個字節。一個utf-8編碼的漢字,占用3個字節。在php中,我們可以通過輸出
echo strlen("和");
來測試。當將頁面編碼保存為gbk時輸出2,utf-8時輸出3。
除了gbk以外,所有ANSI編碼都是2個字節。ansi只是一個標準,在不用的電腦上它代表的編碼可能不相同,比如簡體中文系統中ANSI就代表是GBK。
如上,想繞過魔術引號等限制,有兩種方式:
1.將\前面再加一個\(或單數個即可),變成\\’,這樣\被轉義了,’逃出了限制
2.將\去掉。
我們這里的寬字節注入是利用mysql的一個特性,mysql在使用GBK編碼的時候,會認為兩個字符是一個漢字(前一個ascii碼要大于128,才到漢字的范圍)。
這就是mysql的特性,因為gbk是多字節編碼,他認為兩個字節代表一個漢字,所以%df和后面的\也就是%5c變成了一個漢字“運”,而’逃逸了出來。再嘗試“%df%df%27”,就不報錯了。因為%df%df是一個漢字,%5c%27不是漢字,仍然是\’。
那么mysql怎么判斷一個字符是不是漢字,根據gbk編碼,第一個字節ascii碼大于128,基本上就可以了。比如我們不用%df,用%a1也可以,%a1%5c他可能不是漢字,但一定會被mysql認為是一個寬字符,就能夠讓后面的%27逃逸了出來。
但需要注意的是
gb2312和gbk應該都是寬字節家族的一員,卻不能注入,這歸結于gb2312編碼的取值范圍。它的高位范圍是0xA1~0xF7,低位范圍是0xA1~0xFE,而\是0x5c,是不在低位范圍中的。所以,0x5c根本不是gb2312中的編碼,所以自然也是不會被吃掉的。
所以,把這個思路擴展到世界上所有多字節編碼,我們可以這樣認為:只要低位的范圍中含有0x5c的編碼,就可以進行寬字符注入。
0x02:寬字符注入的修復
先調用mysql_set_charset函數設置連接所使用的字符集為gbk,再調用mysql_real_escape_string來過濾用戶輸入。
這個方式是可行的,但有部分老的cms,在多處使用addslashes來過濾字符串,我們不可能去一個一個把addslashes都修改成mysql_real_escape_string。我們第二個解決方案就是,將character_set_client設置為binary(二進制)。
只需在所有sql語句前指定一下連接的形式是二進制:
mysql_query("SET character_set_connection=gbk, character_set_results=gbk,character_set_client=binary", $conn);
這幾個變量是什么意思?
當我們的mysql接受到客戶端的數據后,會認為他的編碼是character_set_client,然后會將之將換成character_set_connection的編碼,然后進入具體表和字段后,再轉換成字段對應的編碼。
然后,當查詢結果產生后,會從表和字段的編碼,轉換成character_set_results編碼,返回給客戶端。
所以,我們將character_set_client設置成binary,就不存在寬字節或多字節的問題了,所有數據以二進制的形式傳遞,就能有效避免寬字符注入。
0x03:PDO和MYSQLI
完美解決方案就是使用擁有Prepared Statement機制的PDO和MYSQLi來代替mysql_query(注:mysql_query自 PHP 5.5.0 起已廢棄,并在將來會被移除):
PDO:
$pdo = new PDO('mysql:dbname=dbtest;host=127.0.0.1;charset=utf8', 'user', 'pass'); $pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, false); $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); $stmt = $pdo->prepare('SELECT * FROM employees WHERE name = :name'); $stmt->execute(array('name' => $name)); foreach ($stmt as $row) { // do something with $row }
MYSQLi:
$stmt = $dbConnection->prepare('SELECT * FROM employees WHERE name = ?'); $stmt->bind_param('s', $name); $stmt->execute(); $result = $stmt->get_result(); while ($row = $result->fetch_assoc()) { // do something with $row }
看了以上php內置過濾函數講義介紹,希望能給大家在實際運用中帶來一定的幫助。本文由于篇幅有限,難免會有不足和需要補充的地方,大家可以繼續關注億速云行業資訊板塊,會定期給大家更新行業新聞和知識,如有需要更加專業的解答,可在官網聯系我們的24小時售前售后,隨時幫您解答問題的。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。