PHP代碼審計筆記--SQL注入


   

0X01 普通注入

SQL參數拼接,未做任何過濾

<?php
$con = mysql_connect("localhost","root","root");
if (!$con){die('Could not connect: ' . mysql_error());}
mysql_select_db("test", $con);
$id = stripcslashes($_REQUEST[ 'id' ]);
$query  = "SELECT * FROM users WHERE id = $id ";
$result = mysql_query($query)or die('<pre>'.mysql_error().'</pre>');
while($row = mysql_fetch_array($result))
  {
  echo $row['0'] . " " . $row['1'];
  echo "<br />";
  }
echo "<br/>";
echo $query;
mysql_close($con);
?>

測試語句:id=1 UNION SELECT user(),2,3,4 from users

0x02 寬字節注入

A、MYSQL中的寬字符注入

示例代碼:

<?php
$con = mysql_connect("localhost","root","root");
mysql_query("SET NAMES 'gbk'");
mysql_select_db("test", $con);
$id = isset($_GET['id']) ? addslashes($_GET['id']) : 1;
$query  = "SELECT * FROM users WHERE id ='{$id}' ";
$result = mysql_query($query)or die('<pre>'.mysql_error().'</pre>');
while($row = mysql_fetch_array($result))
  {
  echo $row['0'] . " " . $row['1'];
  echo "<br />";
  }
echo "<br/>";
echo $query;
mysql_close($con);
?>

測試語句:%df%27

mysql的特性,因為gbk是多字節編碼,兩個字節代表一個漢字,所以%df和后面的\也就是%5c變成了一個漢字“運”,而’逃逸了出來。

根據gbk編碼,第一個字節ascii碼大於128,基本上就可以了。比如我們不用%df,用%a1也可以.

gb2312編碼的取值范圍。它的高位范圍是0xA1~0xF7,低位范圍是0xA1~0xFE,而\是0x5c,是不在低位范圍中的。所以,0x5c根本不是gb2312中的編碼,所以不會造成寬字節注入。擴展到世界上所有多字節編碼,只要低位的范圍中含有0x5c的編碼,就可以進行寬字符注入。

寬字符注入的修復:

方案一:指定php連接mysql的字符集

mysql_set_charset('gbk',$conn);

$id =mysql_real_escape_string($_GET['id']);

方案二:將character_set_client設置為binary(二進制)

mysql_query("SET character_set_connection=gbk, character_set_results=gbk,character_set_client=binary", $conn); 

將character_set_client設置成binary,就不存在寬字節或多字節的問題了,所有數據以二進制的形式傳遞,就能有效避免寬字符注入。

 

B、PHP 編碼轉換

示例代碼:

<?php
$con = mysql_connect("localhost","root","root");
mysql_query("SET NAMES 'gbk'");
mysql_select_db("test", $con);
mysql_query("SET character_set_connection=gbk, character_set_results=gbk,character_set_client=binary", $con); 
$id = isset($_GET['id']) ? addslashes($_GET['id']) : 1;
$id=iconv('utf-8','gbk',$id);
$query  = "SELECT * FROM users WHERE id ='{$id}' ";
$result = mysql_query($query)or die('<pre>'.mysql_error().'</pre>');

while($row = mysql_fetch_array($result))
  {
  echo $row['0'] . " " . $row['1'];
  echo "<br />";
  }
echo "<br/>";
echo $query;

mysql_close($con);

?>

測試語句: 錦'

錦這個字:它的utf-8編碼是%e9%8c%a6,它的gbk編碼是%e5%5c

錦被iconv從utf-8轉換成gbk后,變成了%e5%5c,而后面的’被addslashes變成了%5c%27,這樣組合起來就是%e5%5c%5c%27,兩個%5c就是\\,正好把反斜杠轉義了,導致’逃逸出單引號,產生注入。

$id=iconv('gbk','utf-8',$id);  //使用%df%27來測試

一個gbk漢字2字節,utf-8漢字3字節,如果我們把gbk轉換成utf-8,則php會每兩個字節一轉換。所以,如果\’前面的字符是奇數的話,勢必會吞掉\,’逃出限制。

其他函數:

mb_convert_encoding($id,'utf-8','gbk')  //GBK To UTF-8與 iconv('gbk','utf-8',$id)一樣

參考文章:

  PHP字符編碼繞過漏洞總結   http://www.cnblogs.com/Safe3/archive/2008/08/22/1274095.html

  淺析白盒審計中的字符編碼及SQL注入    http://www.freebuf.com/articles/web/31537.html

 

0x03 編碼解碼

找一些編碼解碼的函數來繞過防護,,如urldecode() 、rawurldecode()、base64_decode()

<?php
$con = mysql_connect("localhost","root","root");
mysql_select_db("test", $con);
$id = addslashes($_REQUEST['id']);
$id = urldecode($id);//$id = base64_decode($id);
$query  = "SELECT * FROM users WHERE id = '{$id}'";
$result = mysql_query($query)or die('<pre>'.mysql_error().'</pre>');

while($row = mysql_fetch_array($result))
  {
  echo $row['0'] . " " . $row['1'];
  echo "<br />";
  }
echo "<br/>";
echo $query;
mysql_close($con);
?>

測試語句:

  1'union select 1,2,3,4%23    單引號Urlencode           1%2527union select 1,2,3,4%23

  1'union select 1,2,3,4# Base64 Encode MSd1bmlvbiBzZWxlY3QgMSwyLDMsNCM=

0x04 二次注入

  入庫后轉義符就會消失,變成hack',查詢出庫的就是hack',如果拼接到SQL語句,成功引入了單引號閉合前面字符,導致注入。

測試:

   CREATE TABLE  test (user VARCHAR(20) NOT NULL);

  INSERT INTO test values('hack\'');

  

示例代碼:

//測試數據
create table test(
id INT NOT NULL,
user VARCHAR(100) NOT NULL,
pass VARCHAR(100) NOT NULL
)
INSERT INTO test values(1,'hack','hack');

//測試代碼
<?php
$con = mysql_connect("localhost","root","root");
mysql_select_db("test", $con);
mysql_query("SET character_set_connection=gbk, character_set_results=gbk,character_set_client=binary", $con); 
//update入庫
if (isset($_GET['key'])){
    $key=addslashes($_REQUEST['key']);
    $query ="update test set user='{$key}' where id=1";
    echo "INSERT SQL: ".$query."<br/>";
    $result = mysql_query($query);
}
//select 出庫,並帶入查詢
$query  = "SELECT * FROM test WHERE id = 1";
$result = mysql_query($query);
$row = mysql_fetch_row($result);
echo "<br/>";
$query  = "SELECT * FROM test WHERE user = '{$row[1]}'";
print_r('SELECT SQL: '.$query.'<br/>');
$result = mysql_query($query)or die('<pre>'.mysql_error().'</pre>');;
echo "<br/>";
print_r(mysql_fetch_row($result));
mysql_close($con);
?>

測試截圖:

0x05 全局防護盲點

1、str_replace函數  過濾單引號等,可能造成注入;

2、stripslashes() 函數刪除由 addslashes() 函數添加的反斜杠。stripslashes函數使用不當,可能造成注入;

①注入點類似id=1這種整型的參數就會完全無視GPC的過濾;
②注入點包含鍵值對的,那么這里只檢測了value,對key的過濾就沒有防護;
③有時候全局的過濾只過濾掉GET、POST和COOKIE,但是沒過濾SERVER。

①FILES注入,全局只轉義掉GET、POST等傳來的參數,遺漏了FILES;
②變量覆蓋,危險函數:extract()、parse_str()、$$。

  

0X06 漏洞防護

  基本思路:輸入(解決數字型注入)-------轉義處理(解決字符型注入)-------輸出(解決數據庫報錯)

1、檢查輸入的數據是否具有所期望的數據格式。PHP 有很多可以用於檢查輸入的函數,從簡單的變量函數和字符類型函數(比如 is_numeric(),ctype_digit())到復雜的 Perl 兼容正則表達式函數都可以完成這個工作。如果程序等待輸入一個數字,可以考慮使用 is_numeric() 來檢查,或者直接使用 settype() 來轉換它的類型,也可以用 sprintf() 把它格式化為數字。

2、PHP內置轉義函數

 

Addslashes()   http://php.net/manual/zh/function.addslashes.php

magic_quote_gpc http://php.net/manual/zh/info.configuration.php#ini.magic-quotes-gpc

mysql_real_escape_string()  http://php.net/manual/zh/function.mysql-real-escape-string.php

mysql_escape_string()      http://php.net/manual/zh/function.mysql-escape-string.php

3、數據庫報錯信息泄露防范:

  把php.ini文件display_errors = Off

  數據庫查詢函數前面加一個@字符

 

最有效可預防SQL注入攻擊的防御方式:預處理技術進行數據庫查詢:

代碼示例:

<?php
$mysqli = new MySQLi("localhost","root","root","test");
if(!$mysqli){
 die($mysqli->error);
}
$sql = "select id,username,password from users where id=?";////創建一個預定義的對象 ?占位
$mysqli_stmt = $mysqli->prepare($sql);
$id=$_REQUEST['id'];
$mysqli_stmt->bind_param("i",$id);////綁定參數
$mysqli_stmt->bind_result($id,$username,$password);////綁定結果集
$mysqli_stmt->execute();//執行
while($mysqli_stmt->fetch()){   //取出綁定的結果集
 echo $id." ".$username ." ". $password;
}
echo  "<br/>";
echo $sql;
$mysqli_stmt->free_result(); ////關閉結果集
$mysqli_stmt->close();
$mysqli->close();
?>

 

關於我:一個網絡安全愛好者,致力於分享原創高質量干貨,歡迎關注我的個人微信公眾號:Bypass--,瀏覽更多精彩文章。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM