PHP函數默認設置引發的安全問題

前言

暑假不學習,和鹹魚並無區別。今天剛好在發掘一下默認配置可能存在問題和一些容易觸發漏洞的php函數,這裏做一個總結。
 

in_array()函數

相關知識

查閱PHP手冊:
(PHP 4, PHP 5, PHP 7)
in_array() — 檢查數組中是否存在某個值
大體用法爲:

bool in_array ( mixed $needle , array $haystack [, bool $strict = FALSE ] )

而官方的解釋也很有意思:
大海撈針,在大海(haystack)中搜索針(needle),如果沒有設置 strict 則使用寬鬆的比較。

漏洞問題

我們注意到

bool $strict = FALSE

寬鬆比較如果不設置,默認是FALSE,那麼這就會引來安全問題
如果設置$strict = True:則 in_array() 函數還會檢查 needle 的類型是否和 haystack 中的相同。
那麼不難得知,如果不設置,那麼就會產生弱類型的問題
例如:

<?php
$whitelist = range(1, 24);
$filename='sky';
var_dump(in_array($filename, $whitelist));
?>

此時運行結果爲false
但是如果我們將filename改爲1sky
成功利用弱比較,而繞過了這裏的檢測

典型案例

上面的實例已說明了問題,其實這個問題是存在於上次文件的檢查的
在php-security-calendar-2017-Wish List中

class Challenge {
    const UPLOAD_DIRECTORY = './solutions/';
    private $file;
    private $whitelist;

    public function __construct($file) {
        $this->file = $file;
        $this->whitelist = range(1, 24);
    }

    public function __destruct() {
        if (in_array($this->file['name'], $this->whitelist)) {
            move_uploaded_file(
                $this->file['tmp_name'],
                self::UPLOAD_DIRECTORY . $this->file['name']
            );
        }
    }
}

$challenge = new Challenge($_FILES['solution']);

我們不難看出,代碼的意圖上是想讓我們只傳數字名稱的文件的
而我們卻可以用1skyevil.php這樣的名稱去bypass
由於沒有修改in_array的默認設置,而導致了安全問題
可能這比較雞肋,但在後續對文件的處理中,前一步產生了非預期,可能會直接影響後一步的操作

漏洞修復

將寬鬆比較設爲true即可
可以看到,搜索的時候,直接要求前兩個參數均爲array
此時已經不存在弱比較問題

 

filter_var()函數

相關知識

(PHP 5 >= 5.2.0, PHP 7)
filter_var — 使用特定的過濾器過濾一個變量

mixed filter_var ( mixed $variable [, int $filter = FILTER_DEFAULT [, mixed $options ]] )

雖然官方說這是過濾器,但是如果用這個函數進行過濾,並且相信他的結果,是非常愚蠢的

漏洞問題

比較常用的當屬FILTER_VALIDATE_URL了吧,但是它存在非常多的過濾bypass
本應該用於check url是否合法的函數,就這樣放過了可能導致SSRF的url
類似的bypass還有:

0://evil.com:80$skysec.top:80/
0://evil.com:80;skysec.top:80/

詳細SSRF漏洞觸發可參考這篇文章:
http://skysec.top/2018/03/15/Some%20trick%20in%20ssrf%20and%20unserialize()/
除此之外,還能觸發xss

javascript://comment%0Aalert(1)

典型案例

// composer require "twig/twig"
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 »</a>';

        // Default twig setup, simulate loading
        // index.html file from disk
        $loader = new TwigLoaderArrayLoader([
            'index.html' => $indexTemplate
        ]);
        $this->twig = new TwigEnvironment($loader);
    }

    public function getNexSlideUrl() {
        $nextSlide = $_GET['nextSlide'];
        return filter_var($nextSlide, FILTER_VALIDATE_URL);
    }

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

(new Template())->render();

這裏不難看出是有模板渲染的,而模板渲染則有可能觸發xss
那麼尋找可控點,不難發現

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

這裏的Link是使用了getNexSlideUrl()的結果
我們跟進這個函數

 public function getNexSlideUrl() {
        $nextSlide = $_GET['nextSlide'];
        return filter_var($nextSlide, FILTER_VALIDATE_URL);
    }

這裏的nextSlide使用就充分相信了filter_var()的過濾結果
所以導致了XSS:

?nextSlide=javascript://comment%250aalert(1)

漏洞修復

不要輕易的相信filter_var(),它只能當做初步驗證函數,結果不能當做是否進入if的後續程序的條件

 

class_exists()函數

相關知識

(PHP 4, PHP 5, PHP 7)
class_exists — 檢查類是否已定義

bool class_exists ( string $class_name [, bool $autoload = true ] )

檢查指定的類是否已定義。

漏洞問題

上述操作表面上看起來似乎沒有什麼問題,和函數名一樣,檢查指定的類是否已定義
但是關鍵點就在於選項上,可以選擇調用或不調用__autoload
更值得思考的是,該函數默認調用了__autoload
什麼是__autoload
PHP手冊是這樣描述的:
在編寫面向對象(OOP) 程序時,很多開發者爲每個類新建一個 PHP 文件。 這會帶來一個煩惱:每個腳本的開頭,都需要包含(include)一個長長的列表(每個類都有個文件)。
在 PHP 5 中,已經不再需要這樣了。 spl_autoload_register() 函數可以註冊任意數量的自動加載器,當使用尚未被定義的類(class)和接口(interface)時自動去加載。通過註冊自動加載器,腳本引擎在 PHP 出錯失敗前有了最後一個機會加載所需的類。
那麼自動調用__autoload會產生什麼問題呢?
我們從下面的案例來看

典型案例

function __autoload($className) {
    include $className;
}

$controllerName = $_GET['c'];
$data = $_GET['d'];

if (class_exists($controllerName)) {
    $controller = new $controllerName($data['t'], $data['v']);
    $controller->render();
} else {
    echo 'There is no page with this name';
}

class HomeController {
    private $template;
    private $variables;

    public function __construct($template, $variables) {
        $this->template = $template;
        $this->variables = $variables;
    }

    public function render() {
        if ($this->variables['new']) {
            echo 'controller rendering new response';
        } else {
            echo 'controller rendering old response';
        }
    }
}

案例同樣來自php-security-calendar-2017
乍一看,這樣的代碼並不存在什麼高危的問題,但實際上因爲class_exists()的check自動調用了__autoload()
所以我們可以調用php的內置類實現攻擊,例如SimpleXMLElement
正常來說,應該是可以這樣觸發render():

http://localhost/xxe.php?c=HomeController&d[t]=sky&d[v][new]=skrskr

可以得到回顯

controller rendering new response

但此時我們可以使用SimpleXMLElement或是simplexml_load_string對象觸發盲打xxe,進行任意文件讀取
構造:

simplexml_load_file($filename,'SimpleXMLElement')

c=simplexml_load_file&d[t]=filename&d[v]=SimpleXMLElement

即可
而這裏的$filename使用最常見的盲打XXE的payload即可
這就不再贅述,詳細可參看

https://blog.csdn.net/u011721501/article/details/43775691

漏洞修復

對於特點情況,可關閉自動調用

bool $autoload = false

 

htmlentities()函數

相關知識

(PHP 4, PHP 5, PHP 7)
htmlentities — 將字符轉換爲 HTML 轉義字符

string htmlentities ( string $string [, int $flags = ENT_COMPAT | ENT_HTML401 [, string $encoding = ini_get("default_charset") [, bool $double_encode = true ]]] )

本函數各方面都和 htmlspecialchars() 一樣, 除了 htmlentities() 會轉換所有具有 HTML 實體的字符。
如果要解碼(反向操作),可以使用 html_entity_decode()。

漏洞問題

從上述知識來看,該函數應該是用來預防XSS,進行轉義的了
但是不幸的是
該函數默認使用的是ENT_COMPAT
即不會轉義單引號,那麼就可能產生非常嚴重的問題,例如如下案例

典型案例

$sanitized = [];

foreach ($_GET as $key => $value) {
    $sanitized[$key] = intval($value);
}

$queryParts = array_map(function ($key, $value) {
    return $key . '=' . $value;
}, array_keys($sanitized), array_values($sanitized));

$query = implode('&', $queryParts);

echo "<a href='/images/size.php?" .
    htmlentities($query) . "'>link</a>";

由於不會轉義單引號
我們可以隨意閉合

<a href='/images/size.php?htmlentities($query)'>link</a>

此時我們替換htmlentities($query)

' onclick=alert(1) //

這樣原語句就變成了

<a href='/images/size.php?' onclick=alert(1) //'>link</a>

這樣就成功的引起了xss
故此最終的payload爲

/?a'onclick%3dalert(1)%2f%2f=c

漏洞修復

必要的時候加上ENT_QUOTES選項

 

openssl_verify()函數

相關知識

(PHP 4 >= 4.0.4, PHP 5, PHP 7)
openssl_verify — 驗證簽名

int openssl_verify ( string $data , string $signature , mixed $pub_key_id [, mixed $signature_alg = OPENSSL_ALGO_SHA1 ] )

openssl_verify() 使用與pub_key_id關聯的公鑰驗證指定數據data的簽名signature是否正確。這必須是與用於簽名的私鑰相對應的公鑰。

漏洞問題

這個函數看起來是用於驗證簽名正確性的,怎麼會產生漏洞呢?
我們注意到它的返回值情況
其中,內部發送錯誤會返回-1
我們知道if判斷中,-1和1同樣都可以被當做true
那麼假設存在這樣的情況
if(openssl_verify())
那麼它出現錯誤的時候,則同樣可以經過check進入後續程序
如何觸發錯誤呢?
實際上只要使用另一個與當前公鑰不匹配的算法生成的簽名,即可觸發錯誤

典型案例

class JWT {
    public function verifyToken($data, $signature) {
        $pub = openssl_pkey_get_public("file://pub_key.pem");
        $signature = base64_decode($signature);
        if (openssl_verify($data, $signature, $pub)) {
            $object = json_decode(base64_decode($data));
            $this->loginAsUser($object);
        }
    }
}

(new JWT())->verifyToken($_GET['d'], $_GET['s']);

此時我們只需要使用一個不同於當前算法的公鑰算法,生成一個有效簽名,然後傳入參數
即可導致openssl_verify()發生內部錯誤,返回-1,順利通過驗證,達成簽名無效依然可以通過的目的

漏洞修復

if判斷中使用

if(openssl_verify()===1)

 

後記

php作爲一種功能強大的語言,它的庫中還有許多默認配置會引發安全問題,還等我們一一去探索,由於本人很菜,不能一一枚舉,在此拋磚引玉了!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章