盤點2017年的WordPress插件漏洞
一、前言
據統計,全球29%的網站使用的都是WordPress。由于WordPress的使用非常廣泛,使得WordPress插件的安全問題成為了網絡犯罪分子們關注的焦點。一般來說,第三方提供的插件其安全等級肯定沒有WordPress核心插件的安全等級高,因此它們對于攻擊者來說,絕對是極具吸引力的攻擊目標。為了入侵目標WordPress站點,攻擊者需要利用插件中的安全漏洞,那么靜態代碼分析技術能檢測到這些漏洞嗎?
在這篇文章中,我們將會對2017年影響最為嚴重的插件漏洞進行分析。除此之外,我們還會跟大家介紹靜態代碼分析工具如何才能檢測到這些漏洞。
二、漏洞選取
我們選取公開已知插件漏洞的條件如下:
- 發布于2017年
- 受影響的插件安裝量非常大
- 漏洞影響嚴重
- 不需要認證或對服務器有任何要求(例如WP statistics插件的SQLi漏洞)
- 不會影響非開源的商業插件
RIPS可以對非WordPress核心插件進行代碼分析,接下來,我們會對WordPress的相關功能進行分析,并介紹如何對插件進行深入的代碼分析。
1. Loginizer 1.3.5-SQL注入漏洞(CVE-2017-12650)
目前,總共有55萬WordPress站點安裝了Loginizer插件。這款插件的作用理應是通過屏蔽暴力破解攻擊、啟用雙因素身份驗證、以及reCAPTCHA驗證碼機制來給WordPress的登錄功能增加安全性。但是在今年八月份,研究人員在Loginizer的登錄程序中發現了一個SQL注入漏洞,而這個漏洞反而會讓原本需要得到保護的管理員憑證處于安全風險之中。
接下來,我們一起看一看包含漏洞的代碼,并且跟大家解釋靜態代碼分析工具(以下簡稱SAST工具)如果想要檢測到這類漏洞的話,需要什么樣的要求。在接下來的分析過程中,我們給大家提供的只是簡單的代碼段,而實際的分析會更加復雜。
(1) 第一步:識別自定義的SQL封裝器
首先,SAST工具最基本的就是要識別出該插件中用戶自定義的函數lz_selectquery(),這個函數可以利用WordPress的數據庫驅動器來執行SQL語句。當這個函數被調用的時候,它需要對第一個參數進行SQL注入檢測。
modules/Emails/DetailView.php
- function lz_selectquery($query, $array = 0){
- global $wpdb;
- $result = $wpdb->get_results($query, 'ARRAY_A');
- }
(2) 第二步:識別輸入來源
對于SAST工具而言,另一種基本功能就是它必須能夠識別出PHP中所有常見的和不常見的用戶輸入來源。但攻擊者可以修改用戶自定義函數lz_getip()所返回的HTTP請求頭,因此該函數所返回的值就是不可信的了,所以整個數據流必須進行精準跟蹤。
modules/Emails/DetailView.php
- function lz_getip() {
- global $loginizer;
- if(isset($_SERVER["REMOTE_ADDR"])) {
- $ip = $_SERVER["REMOTE_ADDR"];
- }
- if(isset($_SERVER["HTTP_X_FORWARDED_FOR"])) {
- $ip = $_SERVER["HTTP_X_FORWARDED_FOR"];
- }
- return $ip;
- }
(3) 第三步:分析WordPress的action和filter
WordPress允許我們定義各種不同的action來調用自定義函數,為了跟蹤插件的控制流程,SAST工具必須要了解這些回調的運行機制。在下面給出的代碼段中,我們可以看到loginizer_load_plugin()函數是通過action調用的,這個函數可以從lz_getip()函數中獲取用戶輸入,并將其存儲再全局數組$loginizer之中,然后再通過WordPress filter來調用另一個自定義函數loginizer_wp_authenticate()。因此,除了WordPress action之外,SAST工具還需要了解WordPress filter的工作機制。
modules/Emails/DetailView.php
- function loginizer_load_plugin() {
- global $loginizer;
- $loginizer['current_ip'] = lz_getip();
- add_filter('authenticate', 'loginizer_wp_authenticate', 10001, 3);
- }
- add_action('plugins_loaded', 'loginizer_load_plugin');
(4) 第四步:分析全局變量
雖然下面這段代碼對于我們人類來說可以輕易看懂,但是對于SAST工具來說分析起來可就非常復雜了。它需要通過多個函數調用來分析全局數組$loginizer的數據流,只有這樣它才能夠檢測到lz_getip()函數傳遞給loginizer_can_login()函數(為對SQL語句中的拼接內容進行數據過濾)的用戶輸入信息。其中的SQL語句是通過自定義的SQL函數lz_selectquery()實現的,雖然WordPress可以通過模擬magic_quotes來防止注入,但是來自HTTP頭中的惡意用戶輸入數據并不會受此影響。
modules/Emails/DetailView.php
- function loginizer_wp_authenticate($user, $username, $password) {
- global $loginizer, $lz_error, $lz_cannot_login, $lz_user_pass;
- if(loginizer_can_login()) {
- return $user;
- }
- }
- function loginizer_can_login() {
- global $wpdb, $loginizer, $lz_error;
- $result = lz_selectquery("SELECT * FROM ".$wpdb->prefix."loginizer_logs
- WHERE ip = '".$loginizer['current_ip']."';");
- }
由于RIPS的分析算法主要針對的是PHP語言,因此它對WordPress的分析會比較到位,因此它能夠識別出WordPress復雜的數據流,并成功檢測出插件中的SQL注入漏洞。
2. Ultimate Form Builder Lite 1.3.6-SQL注入漏洞(CVE-2017-15919)
Ultimate Form Builder插件目前的安裝量已經超過了5萬,它允許我們使用拖拽的形式來創建聯系表單。在今年十月份,研究人員在該插件中發現并報告了一個嚴重的SQL注入漏洞,而該漏洞將允許攻擊者接管目標WordPress網站。
接下來,我們會分析代碼中存在的問題,以及SAST工具如何才能檢測到這個安全漏洞。
(1) 第一步:識別上下文環境
首先,插件要執行的所有SQL語句都需要被分析,雖然WordPress數據庫驅動跟插件代碼無關,但它所使用的方法(例如get_rwo())是可以被RIPS引擎識別的(搜索潛在的SQL注入漏洞)。
modules/Emails/DetailView.php
- class UFBL_Model {
- public static function get_form_detail( $form_id ) {
- global $wpdb;
- $table = UFBL_FORM_TABLE;
- $form_row = $wpdb->get_row("SELECT * FROM $table WHERE form_id = $form_id");
- return $form_row;
- }
- }
在這里,引擎可以分析SQL語句以識別注入環境。一般來說,這種地方的安全漏洞是很難被發現的。如果它讀取的是form_id=’$form_id’而不是form_id=$form_id,那么這條語句就沒有安全問題了,因為WordPress可以對用戶的輸入進行甄別。因此,只有那些上下文環境敏感的SAST工具才能夠感知到這種隱蔽的安全問題。
(2) 第二步:跟蹤用戶輸入數據流
這一步相對來說比較簡答,你可以從在下面的代碼中看到,如果工具無法分析到$form_id的潛在隱患,那么受污染的數據將有可能擴散到其他功能函數中。
modules/Emails/DetailView.php
- class UFBL_Lib {
- public static function do_form_process() {
- $form_data = array();
- foreach ( $_POST['form_data'] as $val ) {
- if ( strpos( $val['name'], '[]' ) !== false ) {
- $form_data_name = str_replace( '[]', '', $val['name'] );
- if ( !isset( $form_data[$form_data_name] ) ) {
- $form_data[$form_data_name] = array();
- }
- $form_data[$form_data_name][] = $val['value'];
- } else {
- $form_data[$val['name']] = $val['value'];
- }
- }
- $form_id = sanitize_text_field( $form_data['form_id'] );
- $form_row = UFBL_Model::get_form_detail( $form_id );
- }
- }
其中的$_POST[‘form_data’]被分配給了另一個數組,并使用了WordPress的內部過濾函數sanitize_text_field()來進行數據過濾。但是就我們現在所檢測到的這個SQL上下文環境來說,這個過濾函數并不能防止SQL注入的發生。為了檢測這種漏洞并防止假陽性,SAST工具需要跟蹤復雜的數據流,它們不僅要能夠識別WordPress的內部函數,而且還要能評估這些函數在不同環境下的影響。
3. Zen Mobile App Native 3.0-文件上傳漏洞(2017-02-27)
研究數據表明,Zen Mobile App Native插件已經成為了近期最容易受到攻擊的插件之一,而且這個漏洞也是2017年攻擊者最喜歡利用的三大漏洞之一。下面代碼中的漏洞還會影響很多其他的插件,例如Mobile App Builder 1.05。
/zen-mobile-app-native/server/images.php
- if (!$_FILES['file']['error']) {
- $name = md5(rand(100, 200));
- $ext = explode('.', $_FILES['file']['name']);
- $filename = $name . '.' . $ext1;
- $destination = 'images/' . $filename;
- $location = $_FILES["file"]["tmp_name"];
- move_uploaded_file($location, $destination);
- echo $plugin_url.'/server/images/' . $filename;
- }
受影響的代碼行數很少,因此SAST工具可以輕松發現這個安全問題。代碼會利用move_uploaded_file()函數來上傳文件,RIPS對其中的兩個用戶輸入參數進行了分析。雖然文件名會使用隨機哈希代替,但是文件擴展名仍然是攻擊者可以控制的。因此,攻擊者就可以通過上傳一個PHP shell文件(擴展名為.php)并在目標Web服務器中實現任意代碼執行。
4. Appointments 2.2.1-PHP對象注入漏洞(2017-10-02)
最后這個例子是非常容易發現的,很多工具可以直接通過簽名來發現其中的異常。不久之前,研究人員在Appointments插件中發現了一個PHP對象注入漏洞,而且很多其他的插件也存在這種漏洞,例如Flickr Gallery和RegistrationMagic Custom Registration Forms。這里的問題并不復雜,因為這里只有一行代碼:
/flickr-gallery.php
- $pager = unserialize(stripslashes($_POST['pager']));
更重要的是,這一行代碼就能給我們的應用程序帶來嚴重的影響。
三、總結
在這篇文章中,我們給大家簡單分析了2017年比較嚴重的四個WordPress插件漏洞,并且介紹了靜態代碼檢測工具如何才能檢測到這些安全漏洞。由于目前市場上大約有4萬多款WordPress插件,再加上WordPress插件數量一直在穩步增長,因此在2018年我們很可能會看到更多包含安全漏洞的插件出現。