0x00 前言:
這篇是去年組內分享的時候給小伙伴寫的0基礎快速審計tp3系列的文章,主要是對架構做個分析以及審計一些sql注入漏洞~
現在想想打算放出來,過了一年了,可能里面有一些問題,望看到的大佬不吝指教
0x01 架構:
應用入口文件index.php:
if(version_compare(PHP_VERSION,'5.3.0','<')) die('require PHP > 5.3.0 !'); // 開啟調試模式 建議開發階段開啟 部署階段注釋或者設為false define('APP_DEBUG',True); // 定義應用目錄 define('APP_PATH','./Application/'); // 引入ThinkPHP入口文件 require './ThinkPHP/ThinkPHP.php'; app_debug為調試模式,開啟app_debug可使thinkphp報錯,方便調試和報錯注入 thinkphp入口文件中可查看當前的框架版本 const THINK_VERSION = '3.2.3';
應用文件目錄在Application(也可能不叫這個名字,在index.php定義應用目錄),也就是開發者的應用都是寫在這個文件夾里的,我們審計的部分也在這里。
mvc架構: mvc即模型,視圖,控制器
Model(模型)是應用程序中用於處理應用程序數據邏輯的部分,通常模型對象負責在數據庫中存取數據。
View(視圖)是應用程序中處理數據顯示的部分,通常視圖是依據模型數據創建的。
Controller(控制器)是應用程序中處理用戶交互的部分。通常控制器負責從視圖讀取數據,控制用戶輸入,並向模型發送數據。
而這個架構中我們的重點又放在controller里面,因為參數的傳遞是否可控通常是決定是否存在漏洞的根本因素。還有一個原因則是目前很多cms有一個問題就是完全的弱化了model層,controller層不但做了數據的接收,也同樣處理了數據邏輯,和數據庫進行交互。
因此一般來說,我們拿到一套thinkphp框架的cms時,邏輯應該是:
1.在index.php中開啟debug方便調試,確認應用目錄。 2.確定應用前台模塊。(application的下一個目錄,通常是Home,還可能存在Api和Admin。那么重點是審計Home和Api這種前台模塊,主要后台注入也沒啥意思...) 3.審計controller目錄中的所有控制器文件。
0x02 路由和傳參
傳參:
php中獲取參數一般是,$_GET,$_POST
等,框架中也可以使用這種方式傳參數,但更多的是使用框架自定義的函數I
函數
I
方法是ThinkPHP用於更加方便和安全的獲取系統輸入變量,可以用於任何地方,用法格式如下:
I('變量類型.變量名/修飾符',['默認值'],['過濾方法'],['額外數據源'])
example:I('id','','intval');
這種用法是使用intval過濾id參數,這個id參數可以來自get,post,它會自動辨別。
除了這種獲取參數的方法,Thinkphp還有一種獲取參數的方法,叫做參數綁定
example:
namespace Home\Controller; use Think\Controller; class BlogController extends Controller{ public function read($id){ echo 'id='.$id; } }
使用路由http://serverName/index.php?c=Blog&a=read&id=5
即是給$id
傳值為5
因此我們在查看是否存在可控參數時切不要忘記還有這種方式傳參。
路由:
thinkphp使用URL_MODEL=2 ;
這種方式來指定路由模式,1,2,3分別代表不同的路由模式 具體可參考http://document.thinkphp.cn/manual_3_2.html#url
文檔來查看,但依據我的經驗,除了某些特殊情況,我們可以在搭好cms之后通過黑盒快速找到路由規則。
0x03 TP常見的sql注入
1.智障拼接導致的sql注入:
public function show(){ $article=M('article'); $id=I('id'); $article->where('id='.$id)->setInc('views'); $data = $article->field('title,content,addtime,views')->find($id); $data['content']=htmlspecialchars_decode($data['content']); $this->ajaxReturn($data); } }
這段代碼中,$id
參數被直接拼接到where方法中的id
字段,導致sql注入。 I
函數默認的過濾方法是htmlspecialchars
,對sql注入的防御是沒有作用的。 而where方法只對數組形式的參數進行過濾 具體可參考官方文檔的說明: https://www.kancloud.cn/manual/thinkphp/1844
因此此處可總結為:
如果參數獲取時沒有進行有效過濾又通過字符串拼接的方式帶入where方法,則基本可斷定存在sql注入。
補天會收的cms中目前已經很少存在這種智障寫法了,但是非常非常小的cms里還是存在這種寫法的。還有就是安服的同學們做代碼審計很可能會遇到:)
2.不能預編譯的地方未作控制導致的注入:
tp3.x系列對於這種不能預編譯的語句的參數貌似沒做什么處理...包括limit order filed table..etc 因為按理來說這些地方是應該寫死的。。。 limit
注入代碼示例:
public function read(){ $Customer=M('Customer_abroad); $start = $_GET['start']; $limit = $_GET['limit']; $data['total'] = $Customer->count(); $data['success'] = true; $data['message'] = '讀取數據'; $data['data'] = $Customer->order('id DESC')->limit($start,$limit)->select(); $this->ajaxReturn($data,'JSON'); }
limit
方法即對應sql語句中的limit
,這里是沒辦法預編譯的,thinkphp也沒有對此處有防御方法,因此如果這里的參數可控就會存在limit注入
代碼來源:https://github.com/focalhot/FHCRM/blob/master/System/Lib/Action/CustomerAction.class.php
(已經沒了)
表名注入代碼示例:
public function zan_collect() { $data = $this->request->param(); $id = $data['id']; $uid = session('userid'); if (!session('userid') || !session('username')) { return json(array('code' => 0, 'msg' => '登錄后才能操作')); } else { //狀態: // 0 用戶 1 帖子 2 評論 $zan_collect = $data['zan_collect']; $msgsubject = ''; $zan_collect == 'zan' ? $msgsubject = '點贊' : $msgsubject = '收藏'; $tablename = ''; $type = $data['type']; switch ($type) { case 1: $tablename = 'forum'; break; case 2: $tablename = 'comment'; break; case 3: $tablename = 'article'; break; default: $msgsubject = '關注'; $tablename = 'user'; break; } $zuid = $id; if ($type != '0') { $zuid = Db::name($tablename)->where('id', $id)->value('uid'); } if ($zuid == $uid) { return json(array('code' => 0, 'res' => '減', 'msg' => '不可以孤芳自賞哦')); } $insertdata['type'] = $type; $insertdata['uid'] = $uid; $insertdata['sid'] = $id; $n = Db::name($zan_collect)->where($insertdata)->find();
這段代碼的最后一行Db::name($zan_collect)
,如果$zan_collect
是可控的,那么此處就存在一個表名注入。 另外,除了Db::name()
之外,thinkphp3.2.x還使用M
方法和D
方法實例表,因此如果此處的寫法為M($zan_collect)
或者D($zan_collect)
也是一樣的效果。
代碼來源:laysns-v2.4
注:對於order by ,limit,和表名的注入,一般的防御方法時將此處的參數寫死,或者使用switch case
語句。
3.不知道該怪開發還是怪框架的問題
舉個例子,3.x系列不用I函數而直接原生取值基本可以說涼涼... 這里就不細說了,太多了說起來費勁,可以看看先知某師傅寫的文章https://xz.aliyun.com/t/2630
內容非常全面,我就不贅述了,放一個栗子大家可以練手
https://github.com/duiying/TP3-CMS