PHP程式碼審計02之filter_var()函式缺陷

雪痕*發表於2020-10-01

前言

根據紅日安全寫的文章,學習PHP程式碼審計審計的第二節內容,題目均來自PHP SECURITY CALENDAR 2017,講完這個題目,會有一道CTF題目來進行鞏固,外加一個例項來深入分析,想了解上一篇的內容,可以點選這裡:PHP程式碼審計01之in_array()函式缺陷
下面我們開始分析。

漏洞分析

下面來看第一題,程式碼如下:

<?php
require 'vendor/autoload.php';

class Template{
    private $twig;

    public function __construct()
    {            //這裡是第一次過濾
        $indexTemplate = 'img'.'src="https://loremflickr.com/320/240">'.
            '<a href="{{link|escape}}">Next slide &raquo;</a>';
            
         $loader = new Twig\Loader\ArrayLoader([
             'index.html'=>$indexTemplate
         ]);
         $this->twig = new Twig\Environment($loader);
    }

    public function getNexSlideUrl(){
        $nexSlide = $_GET['nexSlide'];
            //這裡是第二次過濾
        return filter_var($nexSlide,FILTER_VALIDATE_URL);
    }

    public function render(){
        echo $this->twig->render(
            'index.html',
            ['link'=>$this->getNexSlideUrl()]
        );
    }
}
(new Template())->render();
?>

這一關用的是PHP的一個模板引擎Twig,考察的是XSS漏洞,也就是跨站指令碼攻擊。雖然程式使用了escape和filter_var()兩個過濾方法,但是。還是可以被繞過的。下面我們看第一處過濾,在上面程式碼的第10行,使用Twig模板引擎定義的escape過濾器來過濾link。而escape過濾器預設情況下,它使用HTML轉義策略,也就是escape將PHP本機htmlspecialchars函式用於HTML轉義策略,現在我們看一下PHP手冊,htmlspecialchars函式是如何定義的。

其實就是把一些預定義字元轉換成HTML實體。具體看下錶:

下面我們來看第二處過濾,是在上面程式碼第20行,是用filter_var()來進行過濾,下面我們來看看PHP手冊對這個函式的定義:

具體引數設定如下表:

上面程式碼是用了FILTER_VALIDATE_URL,把值作為 URL 來驗證。這個函式過濾其他的引數設定說明,如下:

  • FILTER_CALLBACK:呼叫使用者自定義函式來過濾資料。
  • FILTER_SANITIZE_STRING:去除標籤,去除或編碼特殊字元。
  • FILTER_SANITIZE_STRIPPED:”string” 過濾器的別名。
  • FILTER_SANITIZE_ENCODED:URL-encode 字串,去除或編碼特殊字元。
  • FILTER_SANITIZE_SPECIAL_CHARS:HTML 轉義字元 ‘”<>& 以及 ASCII 值小於 32 的字元。
  • FILTER_SANITIZE_EMAIL:刪除所有字元,除了字母、數字以及 !#$%&’*+-/=?^_{|}~@.[]
  • FILTER_SANITIZE_URL:刪除所有字元,除了字母、數字以及 $-_.+!*'(),{}|\^~[]<>#%”;/??&=
  • FILTER_SANITIZE_NUMBER_INT:刪除所有字元,除了數字和 +-
  • FILTER_SANITIZE_NUMBER_FLOAT:刪除所有字元,除了數字、+- 以及 .,eE。
  • FILTER_SANITIZE_MAGIC_QUOTES:應用 addslashes()。
  • FILTER_UNSAFE_RAW:不進行任何過濾,去除或編碼特殊字元。
  • FILTER_VALIDATE_INT:在指定的範圍以整數驗證值。
  • FILTER_VALIDATE_BOOLEAN:如果是 “1”, “true”, “on” 以及 “yes”,則返回 true,如果是 “0”, “false”, “off”, “no” 以及 “”,則返回 false。否則返回 NULL。
  • FILTER_VALIDATE_FLOAT:以浮點數驗證值。
  • FILTER_VALIDATE_REGEXP:根據 regexp,相容 Perl 的正規表示式來驗證值。
  • FILTER_VALIDATE_URL:把值作為 URL 來驗證。
  • FILTER_VALIDATE_EMAIL:把值作為 e-mail 來驗證。
  • FILTER_VALIDATE_IP:把值作為 IP 地址來驗證。

通過對兩個過濾器的瞭解,我們想想該如何繞過呢?,其實,這裡可以通過JavaScript偽協議來繞過,為了更好的理解,這裡寫一小段簡單的程式碼。

$url = filter_var($_GET['url'],FILTER_VALIDATE_URL);
var_dump($url);
$url = htmlspecialchars($url);
var_dump($url);
echo "<a href='$url'>測試一下</a>";

下面我們構造payload,用JavaScript偽協議來繞過,payload為:javascript://test%250aalert(1),然後執行如下圖:

其實上面payload中,//後面的內容全是註釋的內容,那為什麼還是會被執行呢?因為在上面的payload用了%0a,%進行了編碼,成了%25,這是換行符,所以執行了我們們的彈窗。

CTF練習

通過上面的分析,是不是對filter_var()函式有了一定的瞭解呢,讓我們們用一道CTF的題目來鞏固一下吧。這道題也是因為filter_var被繞過,導致命令執行。看下面程式碼。

<?php
$url = $_GET['url'];
            //檢查是否是合法的URL
if (isset($url)&&filter_var($url,FILTER_VALIDATE_URL)) {
    $site_info = parse_url($url);
      //正則判斷
    if (preg_match('/test.com$/', $site_info['host'])) {
        exec('curl "' . $site_info['host'] . '"', $result);
        echo "<center><h1>You have curl {$site_info['host']}
successfully!</h1></center>
<center><textarea rows ='20' cols='90'>";
        echo implode(' ', $result);
    } else {
        die("<center><h1>Error: Host not allowed</h1></center>");
    }
}
else{
    echo "<center><h1>Just curl test.com.com!</h1></center><br>
          <center><h3>For example:?url=http://test.com.com</h3></center>";
}
?>

現在分析上面的程式碼,GET接收url引數,然後用filter_var檢查是否為合法的URL,接著走到下面的程式碼,正則判斷結尾必須還有test.com。
然後用了exec函式,看到它我們嘴就有了笑容,因為只要我們繞過上面兩處檢查,我們就可以為所欲為,命令執行了。而上面我們分析了,可以使用偽協議來繞過filter_var的檢查,至於正則判斷,只要我們結尾包含test.com,就繞過了正則檢查。
現在我們設定payload:javascript://123";ls;"test.com
偽協議繞過了filter_var檢查。結尾繞過了正則判斷,當與exec函式拼接後就形成了三條命令

  • curl "javascript://123"
  • ls
  • "test.com"
    這就成功命令執行,第一條和第三條不會被執行,當第二條被執行後,就可以檢視到當前目錄,發現flag.php,然後用cat命令來檢視檔案,拿到flag,這裡需要注意的是,如果直接使用cat是會包含空格的,這樣無法繞過filter_var()函式的過濾,所以用<代替空格。最後的flag為:javascript://";cat<flag.php;"test.com
    如果是Windows機器,換一下語法就可以了,比如要檢視目錄:
//檢視目錄
javascript://";dir;"test.com
//檢視檔案
javascript://";type=flag.php;"test.com

例項分析

通過上面的題目和CTF練習,是不是感覺正到勁頭了,下面我們們分析例項,是Anchor 0.9.2版本,在這個版本中,當使用者訪問一個不存在的url時,程式會呼叫404模板,而這個模板存在XSS漏洞。現在我們們來仔細分析。下面看程式碼:

現在我們開啟themes\default\404.php檔案
看第六行,接下來我們搜尋這個函式,發現它在anchor\functions\helpers.php檔案中,並看到current_url是由Uri類的current方法實現的,如下圖:

下面我們跟進Uri類,是在system\Uri.php檔案中,如下圖:

發現這裡呼叫了static::detect()方法,( static:: 是在PHP5.3版本之後引入的延遲靜態繫結寫法)
detect()方法就在下方,具體程式碼如下圖:

detect()方法會獲取$_SERVER陣列中的REQUEST_URI, PATH_INFO, ORIG_PATH_INFO鍵值,如果存在其中一個鍵,並且符合filter_var($uri, FILTER_SANITIZE_URL)和parse_url($uri, PHP_URL_PATH)則直接將$uri傳入format()方法中。現在我們跟進這個方法。就在下邊,如圖:

發現程式過濾了三次,看截圖130行到136行。但是沒有對XSS進行過濾。下面我們來構造payload。

漏洞利用

現在我們構造payload:localhost/anchor/index.php/<script>alert("XSS")</script>
現在結合上面的講解來分析,當我們訪問這個不存在的url時,程式會呼叫404模板頁面,然後呼叫current_url()函式來獲取當前檔名,也就是我們們構造的<script>alert("XSS")</script>,嵌入了標籤中,找成XSS攻擊。效果如下圖:

小結

通過上面的分析,是不是對filter_var()函式理解更深了呢?下一篇文章會對例項化任意物件漏洞進行學習和分析,一起努力吧!

相關文章