天天看點

PHP SECURITY CALENDAR 2017 (1-10題)

關鍵代碼在__destruct析構函數中,使用in_array檢查$_FILES[‘solution’]上傳檔案的檔案名name是否在1~24的範圍之内來選擇是否執行move_uploaded_file,由于沒有設定in_array的第三個參數導緻了繞過檢查。

in_array :(PHP 4, PHP 5, PHP 7) 功能 :檢查數組中是否存在某個值 定義 : <code>in_array(mixed $needle, array $haystack, bool $strict = false): bool</code> 傳回值 :bool 大海撈針,在 $haystack 中搜尋 $needle ,如果沒有設定第三個參數 <code>strict</code> 則使用寬松的比較。 啟用第三個參數為true,則會使用強比較,檢查類型是否也相同

例如檔案名為 7shell.php 。因為PHP在使用 in_array() 函數判斷時,會将 7shell.php 強制轉換成數字7,而數字7在 range(1,24) 數組中,最終繞過 in_array() 函數判斷,導緻任意檔案上傳漏洞。

這次考驗的是xss漏洞,用到的模闆引擎Twig來輸出到頁面。關鍵點要繞過兩個函數escape和filter_var,在Twig模闆引擎定義的 escape 過濾器來過濾link,而實際上這裡的 escape 過濾器,是用PHP内置函數 htmlspecialchars 來實作的。<code>Twig</code>中的<code>{{link|escape}}</code>中的escape的和PHP中的<code>htmlspecialchars($link, ENT_QUOTES, 'UTF-8')</code>是一樣的,是以單引号和雙引号等都無法使用了

htmlspecialchars :(PHP 4, PHP 5, PHP 7) 功能 :将特殊字元轉換為 HTML 實體

第二處過濾在 第22行 ,這裡用了 filter_var 函數來過濾 nextSlide 變量,且用了 FILTER_VALIDATE_URL 過濾器來判斷是否是一個合法的url。<code>filter_var</code>的URL過濾非常的弱,隻是單純的從形式上檢測并沒有檢測協定。測試如下:

針對這兩處的過濾,我們可以考慮使用 javascript僞協定 來繞過,<code>javascript://comment%250aalert(1)</code>

這裡的 // 在JavaScript中表示單行注釋,是以後面的内容均為注釋,那為什麼會執行 alert 函數呢?那是因為我們這裡用了字元 %0a ,該字元為換行符,是以 alert 語句與注釋符 // 就不在同一行,就能執行。

後面的%250a其實是%0a的url編碼。這裡進行了二次編碼。因為payload發給伺服器後會解碼一次。通過<code>javascript://comment</code>繞過<code>filter_var</code>,最後得到<code>javascript://comment%0aalert()</code>進入到<code>&lt;a href="{{link|escape}}"&gt;Next slide »&lt;/a&gt;</code>剛好能夠觸發alert。

在第8行中的<code>class_exists()</code>會檢查是否存在對應的類,當調用<code>class_exists()</code>函數時會觸發使用者定義的<code>__autoload()</code>函數,用于加載找不到的類。

class_exists :(PHP 4, PHP 5, PHP 7) 功能 :檢查類是否已定義 定義 : <code>bool class_exists ( string $class_name[, bool $autoload = true ] )</code> $class_name 為類的名字,在比對的時候不區分大小寫。預設情況下 $autoload 為 true ,當 $autoload 為 true 時,會自動加載本程式中的 __autoload 函數;當 $autoload 為 false 時,則不調用 __autoload 函數。

除此之外,還有很多的函數在調用<code>__autoload()</code>的方法,如下:

是以如果我們輸入<code>../../../../etc/passwd</code>是,就會調用<code>class_exists()</code>,這樣就會觸發<code>__autoload()</code>中的<code>include</code>産生任意檔案包含。前提是 PHP5~5.3版本 之間才可以,這個漏洞在<code>PHP 5.4</code>中已經被修複了。

另一個是<code>blind xxe</code>漏洞,由于存在<code>class_exists()</code>,是以我們可以調用PHP的任意内置函數,并且通過<code>$controller = new $controllerName($data);</code>進行執行個體化。這個時候就可以借助與PHP中的<code>SimpleXMLElement</code>類來完成XXE攻擊。

第8-9行進行了<code>strpos</code>函數的過濾,然後把接收到的資料進行<code>SimpleXMLElement</code>函數處理。其實這裡由于<code>strpos</code>函數使用不當導緻了注入問題。

strpos — 查找字元串首次出現的位置 作用:主要是用來查找字元在字元串中首次出現的位置。

<code>strpos</code> 函數傳回查找到的子字元串的下标。如果字元串開頭就是我們要搜尋的目标,則傳回下标 0 ;如果搜尋不到,則傳回 false 。

由于PHP的自動類型轉換的關系,<code>0</code>和<code>false</code>是相等的,如下:

是以如果我們傳入的<code>username</code>和<code>password</code>的首位字元是<code>&lt;</code>或者是<code>&gt;</code>就可以繞過限制,那麼最後的pyaload就是:

最終傳入到<code>$this-&gt;login($xmlElement)</code>的<code>$xmlElement</code>值是<code>&lt;xml&gt;&lt;user="&lt;"&gt;&lt;injected-tag property=""/&gt;&lt;pass="&lt;"&gt;&lt;injected-tag property=""/&gt;&lt;/xml&gt;</code>這樣就可以進行注入了。

代碼中有個mail函數,如果第五個參數設定為-X,則可以寫入webshell

PHP SECURITY CALENDAR 2017 (1-10題)

上面這個樣例中,我們使用 -X 參數指定日志檔案,最終會在 /var/www/html/rce.php 檔案中寫入如下資料:

要到達mail函數,則需要經過兩個過濾<code>filter_var</code>和<code>escapeshellarg</code>

filter_var :使用特定的過濾器過濾一個變量

功能 :這裡主要是根據第二個參數filter過濾一些想要過濾的東西。

filter_var() 問題在于,在雙引号中即使存在特殊字元,仍然能夠通過檢測為true。以下是一些有效通過的例子:

當然由于引入的特殊符号,雖然繞過了 filter_var() 針對郵箱的檢測,但是由于在PHP的 mail() 函數在底層實作中,調用了 escapeshellcmd() 函數,對使用者輸入的郵箱位址進行檢測,導緻即使存在特殊符号,也會被 escapeshellcmd() 函數處理轉義,這樣就沒辦法達到指令執行的目的了。是以可以利用escapeshellarg和escapeshellcmd一起使用進而繞過。

escapeshellarg 函數轉義後,還會在左右各加一個單引号,但 escapeshellcmd 函數是直接加一個轉義符,對于成對的單引号, escapeshellcmd 函數預設不轉義。

PHP SECURITY CALENDAR 2017 (1-10題)

當escapeshellcmd() 和 escapeshellarg 一起使用,會造成特殊字元逃逸,下面我們給個簡單例子了解一下:

PHP SECURITY CALENDAR 2017 (1-10題)

傳入的參數是

由于<code>escapeshellarg</code>先對單引号轉義,再用單引号将左右兩部分括起來進而起到連接配接的作用。是以處理之後的效果如下:

接着 <code>escapeshellcmd</code> 函數對第二步處理後字元串中的 <code>\</code> 以及 <code>a=1'</code> 中的單引号進行轉義處理,結果如下所示:

由于第三步處理之後的payload中的 <code>\\</code> 被解釋成了 <code>\</code> 而不再是轉義字元,是以單引号配對連接配接之後将payload分割為三個部分,具體如下所示:

PHP SECURITY CALENDAR 2017 (1-10題)

是以這個payload可以簡化為 <code>curl 127.0.0.1\ -v -d a=1'</code> ,即向 <code>127.0.0.1\</code> 發起請求,POST 資料為 <code>a=1'</code> 。

在<code>clearToken()</code>方法中的正規表達式<code>[^a-z.-_]</code>,本意是将非<code>a-z</code>、<code>.</code>、<code>-</code>、<code>_</code>全部替換為空。這樣<code>../../../</code>目錄穿越的方式就無法使用了,因為<code>/</code>會被替換為空。

但是本題的問題在于<code>[^a-z.-_]</code>中的<code>-</code>沒有進行轉義。如果<code>-</code>沒有進行轉義,那麼<code>-</code>表示比對一個清單,例如<code>[1-9]</code>表示的數字1到9,但是如果<code>[1\-9]</code>表示就是比對字母<code>1</code>、<code>-</code>和<code>9</code>。是以在本題中使用的<code>[^a-z.-_]</code>表示的就是非ascii表中的序号為46至122的字母替換為空。那麼此時的<code>../.../</code>就不會被比對,就可以進行目錄穿越,進而造成任意檔案删除了。

最後的pyload可以寫為:<code>action=delete&amp;data=../../config.php</code>

首先來看parse_url函數

用法:parse_url(string <code>$url</code>, int <code>$component</code> = -1): [mixed] 本函數解析一個 URL 并傳回一個關聯數組,包含在 URL 中出現的各種組成部分。 如果省略了 <code>component</code> 參數,将傳回一個關聯數組 array,在目前至少會有一個元素在該數組中。數組中可能的鍵有以下幾種: scheme - 如 http host port user pass path query - 在問号 <code>?</code> 之後 fragment - 在散列符号 <code>#</code> 之後

例如:http://username:password@hostname/path?arg=value#anchor則會輸出以下

在題目中的<code>$var['query']</code>就是?後面的參數鍵值對。接下來看第二個函數<code>parse_str</code>

用法:parse_str(string,array) parse_str() 函數把查詢字元串解析到變量中。 把查詢字元串解析到變量中:

而這個<code>parse_str</code>就是容易産生變量覆寫漏洞的函數。同時<code>$_SERVER['HTTP_REFERER']</code>也是可控的,那麼就存在變量覆寫的漏洞了。

通過變量覆寫漏洞,我們可以覆寫掉<code>$config</code>,使其在我們構造的資料庫中進行查詢,這樣就能夠保證我們能夠順利地進行通過驗證。

最後的payload如下:<code>http://host/config[dbhost]=10.0.0.5&amp;config[dbuser]=root&amp;config[dbpass]=root&amp;config[dbname]=malicious&amp;id=1</code>

<code>preg_replace</code>函數的/e模式會産生代碼執行,下面是一個demo。第一個參數必須是比對到第三個參數,第二個參數就會産生指令執行

preg_replace:(PHP 5.5) 功能 : 函數執行一個正規表達式的搜尋和替換 定義 : <code>mixed preg_replace ( mixed $pattern , mixed $replacement , mixed $subject [, int $limit = -1 [, int &amp;$count ]] )</code> 搜尋 subject 中比對 pattern 的部分, 如果比對成功以 replacement 進行替換

我們可以通過控制 preg_replace 函數第1個、第3個參數,來執行代碼。但是可被當做代碼執行的第2個參數,卻固定為 'strtolower("\1")' 。

因為<code>strtolower("\\1")</code>使用的是雙引号,而php中的雙引号能夠執行代碼,比如

PHP SECURITY CALENDAR 2017 (1-10題)

是以此處的<code>strtolower("\\1")</code>就是\1

PHP SECURITY CALENDAR 2017 (1-10題)

\1在正規表達式中表示反向引用,即引用正則第一次比對到的值<code>{${phpinfo()}}</code>,這樣就相當于執行了<code>{${phpinfo()}}</code>

那麼本題的最後的payload可以寫為<code>/?.*={${phpinfo()}}</code>

但是,如果GET請求的參數名存在非法字元,PHP會将其替換成下劃線,即 <code>.*</code> 會變成 <code>_*</code> 。是以 payload 變為了:

這時候需要繞過 . 的話,可以利用以下payload,都是第一個參數比對第三個參數,然後執行第二個參數的代碼,反向引用了<code>{${phpinfo()}}</code>導緻代碼執行

這一題考察的是一個 str_replace 函數過濾不當造成的任意檔案包含漏洞。在上圖代碼 第18行 處,程式僅僅隻是将 ../ 字元替換成空,這并不能阻止攻擊者進行攻擊。例如攻擊者使用payload:....// 或者 ..././ ,在經過程式的 str_replace 函數處理後,都會變成 ../ ,是以上圖程式中的 str_replace 函數過濾是有問題的。

str_replace :(PHP 4, PHP 5, PHP 7) 功能 :子字元串替換 定義 : <code>mixed str_replace ( mixed $search , mixed $replace , mixed $subject [, int &amp;$count ] )</code> 該函數傳回一個字元串或者數組。如下: str_replace(字元串1,字元串2,字元串3):将字元串3中出現的所有字元串1換成字元串2。 str_replace(數組1,字元串1,字元串2):将字元串2中出現的所有數組1中的值,換成字元串1。 str_replace(數組1,數組2,字元串1):将字元串1中出現的所有數組1一一對應,替換成數組2的值,多餘的替換成空字元串。

那麼最後的請求的payload如下:

雖然這道題目存在<code>extract($_POST);</code>,但并不存在變量覆寫漏洞。 這個題目存在兩個關鍵的問題:

雖然做了pi值的防範,但是程式在header跳轉處理完之後,沒有使用<code>exit()</code>或者是<code>die()</code>退出,導緻後續的第11行代碼任然可以執行。

<code>assert()</code>能夠執行<code>"</code>中的代碼,如<code>assert("(int)phpinfo()");</code>

例如我們的payload為:pi=phpinfo() (這裡為POST傳遞資料),然後程式就會執行這個 phpinfo 函數。當然,你在浏覽器端可能看不到 phpinfo 的頁面,而是像下面這樣的圖檔:

PHP SECURITY CALENDAR 2017 (1-10題)

但是用 BurpSuite ,大家就可以清晰的看到程式執行了 phpinfo 函數:

PHP SECURITY CALENDAR 2017 (1-10題)

實際上,這種案例在真實環境下還不少。例如有些CMS通過檢查是否存在install.lock檔案,進而判斷程式是否安裝過。如果安裝過,就直接将使用者重定向到網站首頁,卻忘記直接退出程式,導緻網站重裝漏洞的發生。