前言
暑假不學習,和鹹魚並無區別。今天剛好在發掘一下默認配置可能存在問題和一些容易觸發漏洞的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作爲一種功能強大的語言,它的庫中還有許多默認配置會引發安全問題,還等我們一一去探索,由於本人很菜,不能一一枚舉,在此拋磚引玉了!