淺析漏洞防範
本文簡單介紹在代碼中如何避免漏洞的産生,以較為常見的漏洞為例。
-
SQL注入漏洞:在編寫操作資料庫的代碼時,将外部變量直接拼接到SQL語句中且沒有經過任何過濾機制就放入資料庫中執行。
魔術引号:
magic_quotes_gpc:負責對GET、POST、COOKIE的值進行過濾,然而在php6、7中已經取消了該函數,是以我們可以自己定義一個函數來為資料加上\,即将所有外部變量用addslashes函數過濾:
<?php
function quotes_gpc($str) {
if(!get_magic_quotes_gpc()) {
$str = addslashes($str);
echo "轉義的結果:".$str;
}
return 0;
}
$s=$_GET['s'];
quotes_gpc($s);
但這樣也僅能防禦部分注入,寬位元組注入依舊會産生:
![](https://img.laitimes.com/img/_0nNw4CM6IyYiwiM6ICdiwiIyVGduV2YfNWawNyZuBnL0QzN0MjNxATMzETOwAjMwIzLc52YucWbp5GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.png)
mysql_real_escape_string:負責對字元串進行過濾,但從php7就被移除了,這裡還是舉個例子:
<?php
$conn = mysql_connect("localhost", "root", "root");
$id = mysql_real_escape_string($_GET['id'],$conn);
$sql = "select * from admin where id='".$id."'";
echo $sql
?>
當上文請求參數?id=1’是,會輸出:
select * from admin where id='1\''
intval等字元轉換:在上面的方法中面對int型的注入并無效果,容易被通過報錯和盲注的形式進行注入,這時候可以使用intval将外部變量轉換為int型:
<?php
$id = intval($_GET['kid']);
echo $id;
很明顯,當請求參數?kid=1 union select 1,2,執行結果:
PDO prepare預編譯:PHP pdo類似于.NET的SqlParameter或者java裡的prepareStatement,都是通過預編譯的方法來處理查詢,如下代碼中第5行,PDO::ATTR_EMULATE_PREPARES設定為false來禁止php進行本地模拟prepare,該行為會導緻參數轉義,gbk編碼下依舊會産生SQL寬位元組注入:
<?php
$name = "jadore";
$pass = "123456";
$pdo = new PDO("mysql:host=localhost; dbname=test", "root", "root");
$pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
$pdo->exec("set names 'utf8'");
$sql = "select * from user where username = ? and password =?";
$stmt = $pdo->prepare($sql);
if($stmt->execute(array($name,$pass))) {
echo $name."登入成功!";
}
-
XSS漏洞:分為反射型,存儲型,DOM型,Web應用讀取危害代碼并輸出在頁面上。
HTML實體轉義:利用htmlspecialchars(),htmlentities()函數,轉義<、>、&等字元
<?php
$arrs = array("<>","&");
foreach ($arrs as $arr) {
echo htmlentities($arr)."\n";
}
标簽事件屬性白名單:通過對标簽事件的白名單,即使用正規表達式來比對,如果比對到的事件不在白名單内,直接攔截,而不是将其替換為空。
3. ###### CSRF漏洞:劫持其他使用者進行某些惡意請求。
token驗證:令牌是防範CSRF較好的一種方式,簡單地了解就是在頁面或者COOKIE中添加一段不可猜解的字元串,而伺服器在接收使用者請求時會驗證該字元串是否為上次通路留下的即可判斷是否為非法請求,如果使用者沒有通路上一個頁面,token是很難擷取到的:
<?php
session_start();
function setToken() {
$_SESSION['token'] = md5(time()+rand(1,1000));
echo $_SESSION['token']."\n";
}
function checkToken() {
if(isset($_POST['token']) && $_POST['token'] === $_SESSION['token']) {
return true;
} else {
return false;
}
}
if(isset($_SESSION['token']) && checkToken()) {
echo "token驗證成功\n";
} else {
echo "token驗證失敗\n";
}
setToken();
?>
<form method="post">
<input type="text" name="token" value="<?=$_SESSION['token']?>">
<input type="submit">
</form>
首次通路頁面時token驗證失敗,因為我們從未打開這頁面,token也還沒擷取
而當我們送出相同的token時:
驗證碼驗證:這對于使用者的體驗會産生影響,不可能每個頁面都要求使用者去填寫驗證碼,是以應用場景類似于登入頁面。
-
檔案上傳漏洞:使用者上傳檔案,檔案字尾名并未被服務端嚴格檢查,導緻傳入可被伺服器解析的惡意腳本。
白名單過濾檔案拓展名:比如使用in_array或者===來校拓展名
重命名檔案,采用時間戳拼接随機數等方式:
<?php
function getExt($filename) {
return substr($filename,strripos($filename,'.')+1);
}
$allowExt = array('jpg','png','gif');
$filenameExt = strtolower(getExt($_FILES['file']['name']));
if(!in_array($filenameExt,$allowExt)) {
die("不允許的檔案名");
} else {
$filename = md5(time()+rand(1,1000)).".".$filenameExt;
move_uploaded_file($_FILES['file']['tmp_name'],"upload/".$filename);
}
-
代碼執行漏洞:應用程式本身過濾不嚴,導緻使用者通過請求将代碼注入應用中并執行。
采用白名單過濾參數,以該代碼舉例:
<?php
preg_replace('/(\w+)\|(.*)/ie', '$\\1="\\2";', $_GET['a']);
假設我們知道\2的範圍是純數字,那麼将正則改為:
<?php
preg_replace('/(\w+)\|(\d+)/ie', '$\\1="\\2";', $_GET['a']);
-
指令執行漏洞:相關的指令執行函數内參數過濾不嚴導緻使用者可以執行系統或者應用指令。
escapeshellarg ( string $arg ) : string
:在字元串周圍添加單引号,并附加引号,然後從字元串中轉義單引号。這樣使得$arg在確定最大安全性的同時,将參數直接作為Shell參數傳遞,簡單來說就是過濾參數,将參數限制在一對雙引号裡,此時再引入其他字元串會轉為空格:
escapeshellcmd ( string $command ) : string
:
escapeshellcmd()轉義字元串command 中在shell指令中可能具有特殊含義的所有字元。此函數可確定将指令正确傳遞給Shell exec()和 system()指令執行程式 ,或帶有反斜杠标記。過濾的字元為:&;|`*?~<>^()[]{}$\ \x0A \xFF %,而’、"僅僅在不成對的時候被轉義,可以看到escapeshellcmd 分别在Windows和Linux會添加
^和\
參數白名單:參數白名單是一種比較通用的修複方法,利用正規表達式即可,這裡邊不再記錄。
7. ##### 變量覆寫漏洞:函數使用不當。
有個不錯的例子我記錄下來:
$a=1;
foreach(array('_COOKIE','_GET','_POST') as $_request) {
foreach ($$_request as $_key => $value) {
echo $_key.'<br />';
$$_key = addslashes($value);
}
}
echo $a;
上圖代碼,假使我們送出參數?a=2那麼
使用初始變量:不進行變量注冊,直接使用初始的$_GET之類的變量進行操作,若需要注冊,則可以在代碼中定義變量,然後在請求中指派。
驗證變量是否存在,可以使用if語句,也可以使用extract函數的第二個參數EXTR_SKIP或者parse_str函數。