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