我們是使用 PHP 開發、以 CheckMarx 進行源碼檢測。在這篇持續補充各種 CheckMarx 修補心得~
弱點:
- File Inclusion
- Inappropriate Encoding for Output Context
- Parameter Tampering
- Reflected XSS All Clients
- Remote File Inclusion
- Stored XSS
- Client DOM Stored XSS
- Client Potential XSS
- File Manipulation
- CSRF
程式完成後要進 CheckMarx 掃描,確認原始碼沒有弱點。但是 CheckMarx 非常敏感,看到以下系統變數,就會判斷可能成為注入點:
- $_SERVER
- $_FILE
- $_POST
- $_GET
我原本很賣力的寫正規表示式 (regular expression),用 preg_match 來過濾傳入的參數,但是 CheckMarx 還是判斷我沒有處理,我猜可能是因為 CheckMarx 無力判斷 regex 是否寫得夠嚴謹。
上述的變數常常會 CheckMarx 被判斷為以下弱點:
- Reflected XSS All Clients
- Remote File Inclusion
- File Inclusion
參考《Checkmarx的Reflected XSS All Clients如何修補?》,看到苦主們集體哀嚎,其中有人說「代理商回應修改過的解法是ok的,建議客戶可以加入例外清單」,但是不是每個人家裡的資安都會接受這種「看到弱點卻沒有解決它」的修正方式。
另外有個人建議的做法是使用 PHP 內建的過濾函式 filter_var_array() 或是 filter_input_array()。這兩個函式內的可用參數可參考 PHP 官方的文件 "預設常數說明"(簡體中文版,上方亦可切換語系),或是 w3school 的簡體中文版也有整理:《PHP filter 函數》。
另外,雖然我們原本用 PDO 的參數化查詢寫法,但是某版本的 CheckMarx 口味特殊,只接受在 PDO execute 時用陣列的方式餵已命名參數,所以原本是這樣寫:
<?php
$sql = "select * from test_table from id= ?";
$sth = $dbh->prepare($sql);
$sth->bindValue(1, $testID, PDO::PARAM_INT);
?>
得改成這樣寫:
<?php
$sql = "select * from test_table from id=:testID";
$sth = $dbh->prepare($sql);
$sth->execute([ "testID" => $testID ])
?>
否則照樣會吃一記 SQL Injection 弱點。
弱點:Missing HSTS Header
按照報告中的建議,在 PHP 裡加入以下 header 即可:
<?php header("Strict-Transport-Security: max-age=31536000; includeSubDomains"); ?>
弱點:Second Order SQL Injection
原本我使用 PDO 將資料庫裡的資料撈出來後,透過 fetch() 放到 $row 裡,想說可以比照前面過濾參數的方式,把 $row 先用 filter_var_array() 濾過就拿去用,但一直跳出中風險弱點 "Second Order SQL Injection"。
後來在同事的指點下發現,再多包一層字串取代 str_replace(),將內容的單引號消掉,就沒事了。
<?php $test = str_replace("'", "", $row["TEST_COL"]); ?>
弱點:
- Cross Site History Manipulation
- Insecure Randomness
"Cross Site History Manipulation" 這個我看不懂為什麼是定格在 if 條件式下,原本以為是對裡面的條件有疑義,後來看了《CheckMarx Cross Site History Manipulation 解决方案》才發現,我的 else 裡會執行轉址,轉到顧定頁面,這個行為被判斷成有攻擊的可能。但其實不會啊,我就是想要使用者在條件不成立時一律轉到固定頁面。
如果硬是要修掉,就是在轉址時加個不重複的隨機參數。我原本加 time(),但被判斷成還不夠安全;只加 rand(),還出現新的弱點 "Insecure Randomness",加入亂數的 seed 也沒用,用 mt_srand() 處理也不行:
<?php header("Location: test.php?l=".$lang."&t=".mt_srand(); ?>
使用 openssl_random_pseudo_bytes 後,雖然不會出現 "Insecure Randomness",但是還是會出現 "Cross Site History Manipulation" 。後來我嘗試把轉址的內容抽出來變成 function,忽然就好了。XDrz
有天再搜尋一下查到 "How insecure are PHP's rand functions?",有人提到:
"There are now cryptographically secure pseudo-random generators in PHP 7, including random_int and random_bytes. Might be worth checking those out."(PHP 7 有安全的加密器 random_int() 與 random_bytes(),也許值得一試。)
我用了 random_int() 取代 rand(),貌似有消除弱點 "Insecure Randomness"。
弱點:Session Fixation
目的是要避免攻擊者利用未結束的 session 來偽冒受害者,解決方式是在 session_start() 之前,先使用 session_destroy()。
弱點:Client DOM Stored XSS
如果 JavaScript 有帶 HTML 格式的值要塞到頁面元素上,會很容易觸發 Checkmarx 的 Client DOM Stored XSS。解決辦法可以利用 jQuery 的 parseHTML:
function filterHtml(html){
var parsed = $.parseHTML(html);
var filteredHtml = $('<div/>').append(parsed).html();
return filteredHtml;
}
如果沒有一定要用 $.html(),也可以使用以下兩個方法來處理:
- 使用 jQuery 的 text() 來取代 html(),設定元素的內容。
- 使用 jQuery 的 append() 來取代 html(),在指定節點增加元素內容。
弱點: Client Potential XSS
如果有值直接取出後使用,會觸發 Client Potential XSS,技巧是把角括號取代掉:
function filterXss(str) {
str = str.replace(">", "");
str = str.replace("<", "");
return str;
}
弱點:File Manipulation
如果整串路徑是直接從使用者有能力干預的參數裡取得(例如:$_FILES['user_upload']['tmp_name']),會有被變更的風險。
需要使用 basename() 取得檔名,再轉到指定的目錄。
例如:
$tmpfile = sys_get_temp_dir().basename($_FILES['user_upload']['tmp_name']);
弱點:CSRF
需要以 token 確保資料沒有被異動。
以上,給各位苦情的同業們參考。
留言列表