高級篇主要講
1. 熟知各個開源框架歷史版本漏洞。
2. 業(yè)務邏輯漏洞
3. 多線程引發(fā)的漏洞
4. 事務鎖引發(fā)的漏洞
在高級篇審計中有很多漏洞正常情況下是不存在的只有在特殊情況下才有
PHP常用框架
Zendframwork,Yii,Laravel ,、ThinkPHP
這里舉例因為thinkphp由國內人開發(fā)用戶量較多而且歷史漏洞也多
Thinkphp歷史漏洞很多,對于漏洞形成原因可以自己復現(xiàn)。
篇幅有限只介紹披露漏洞
Query方法 低于3.1.3 有sql注入問題
Order方法 低于 5.x 有sql注入問題
Update方法 低于3.2.3 有sql注入問題
/**
* 更新記錄
* @access public
* @param mixed $data 數(shù)據(jù)
* @param array $options 表達式
* @return false | integer
*/
public function update($data,$options) {
$this->model = $options['model'];
$this->parseBind(!empty($options['bind'])?$options['bind']:array());
$table = $this->parseTable($options['table']);
$sql = 'UPDATE ' . $table . $this->parseSet($data);
if(strpos($table,',')){// 多表更新支持JOIN操作
$sql .= $this->parseJoin(!empty($options['join'])?$options['join']:'');
}
$sql .= $this->parseWhere(!empty($options['where'])?$options['where']:'');
if(!strpos($table,',')){
// 單表更新支持order和lmit
$sql .= $this->parseOrder(!empty($options['order'])?$options['order']:'')
.$this->parseLimit(!empty($options['limit'])?$options['limit']:'');
}
$sql .= $this->parseComment(!empty($options['comment'])?$options['comment']:'');
return $this->execute($sql,!empty($options['fetch_sql']) ? true : false);
}
5. x 版本有命令執(zhí)行漏洞
在github上也有歷史分支可以查看修復代碼
業(yè)務邏輯
想要對整體的邏輯進行審計
- 熟悉業(yè)務場景
- 熟悉業(yè)務流程
- 通讀代碼
多線程引發(fā)的漏洞
這里我寫了個例子
<?php
$money=100;//數(shù)據(jù)庫查詢的用戶余額
$buy=intval($_GET['buy']);
if ($money>0&& $money-$buy>0)
{
sleep(10);
$moeny-=$buy;
//寫入數(shù)據(jù)庫
}
return $money
正常情況下用戶余額一定不為負數(shù) 如果在并發(fā)情況下呢?
用戶發(fā)送惡意并發(fā)請求時就有可能出現(xiàn)這種情況。這么防御呢
這里需要知道事務和鎖的概念可以自行百度理解我這里簡單概述一下
事務:類似一個執(zhí)行任務 成功就任務完成 ,失敗任務自動回滾到未接任務前
鎖:悲觀鎖,樂觀鎖。
我們可以把多線程請求變成單線程處理,這里也可以用隊列壓入壓出。
<?php
$money = 100;//數(shù)據(jù)庫查詢的用戶余額
$buy = intval($_GET['buy']);
try {
if (flock($money, LOCK_EX)) {
if ($money > 0 && $money - $buy > 0) {
sleep(10);
$moeny -= $buy;
//寫入數(shù)據(jù)庫A
throw new ExceptionNew("xp");
//寫入數(shù)據(jù)庫B
}
flock($money, LOCK_UN);
}
} catch (Exception $exceptione) {
throw new ExceptionNew("xp");
}
return $money
這樣確實解決了這個并發(fā)問題,但又有另外一個問題,如果有多個數(shù)據(jù)庫操作中間一段中斷是無法對數(shù)據(jù)還原的,這里我們需要把事務也加上同時默認加鎖。
我們修改一下代碼看一下
<?php
$money=100;//數(shù)據(jù)庫查詢的用戶余額
$buy=intval($_GET['buy']);
try
{
$this->startTrans();//開啟事務
if ($money>0&& $money-$buy>0)
{
sleep(10);
$moeny-=$buy;
$this->commit(); //提交事務
//寫入數(shù)據(jù)庫
}
}
catch (Exception $exceptione)
{
$this->rollback();//回滾
}
return $money
<?php
$buy=intval($_GET['buy']);
try
{
$this->startTrans();//開啟事務
$money=100;//數(shù)據(jù)庫查詢的用戶余額
if ($money>0&& $money-$buy>0)
{
sleep(10);
$moeny-=$buy;
$this->commit(); //提交事務
//寫入數(shù)據(jù)庫
}
}
catch (Exception $exceptione)
{
$this->rollback();//回滾
}
return $money
在加了事務的悲觀鎖后,所有請求到已經開啟事務的代碼,都會進行阻塞只有提交了事務或者回滾才會處理下一個請求。
然而這樣的代碼并不能防御并發(fā)。這也是很多開發(fā)中的問題,確實做了事務加鎖,依然沒有用。 加事務必須是在查詢內加,不然依舊會造成并發(fā)問題。 我們在改改把讀放入事務鎖中。
<?php
$buy=intval($_GET['buy']);
try
{
$this->startTrans();//開啟事務
$money=100;//數(shù)據(jù)庫查詢的用戶余額
if ($money>0&& $money-$buy>0)
{
sleep(10);
$moeny-=$buy;
$this->commit(); //提交事務
//寫入數(shù)據(jù)庫
}
}
catch (Exception $exceptione)
{
$this->rollback();//回滾
}
return $money
這樣也解決了臟讀的問題。
臟讀:
(針對未提交數(shù)據(jù))如果一個事務中對數(shù)據(jù)進行了更新,但事務還沒有提交,另一個事務可以“看到”該事務沒有提交的更新結果,這樣造成的問題就是,如果第一個事務回滾,那么,第二個事務在此之前所“看到”的數(shù)據(jù)就是一筆臟數(shù)據(jù)。
當然也有更復雜的情況可能框架有多個端。這種二次利用的情況更加難以審計。
在實際審計中我們想要精通一個語言的代碼審計我們要做的更難
- 要比產品更懂業(yè)務
- 要比測試更懂流程
- 要比開發(fā)更懂代碼
- 要比架構更懂框架
自此囊括從初級到高級的學習就到此為止了,但我們的學習卻不能停止,這也是我個人對php代碼審計學習的理解肯定有不合理的地方,不足可以直接提出修改,共勉!