buuctf 刷題記錄 [第二章 web進階]SSRF Training
點擊interesting challenge顯示源碼
<?php
highlight_file(__FILE__);
function check_inner_ip($url)
{
$match_result=preg_match('/^(http|https)?:\/\/.*(\/)?.*$/',$url);
if (!$match_result)
{
die('url fomat error');
}
try
{
$url_parse=parse_url($url);
}
catch(Exception $e)
{
die('url fomat error');
return false;
}
$hostname=$url_parse['host'];
$ip=gethostbyname($hostname);
$int_ip=ip2long($ip);
return ip2long('127.0.0.0')>>24 == $int_ip>>24 || ip2long('10.0.0.0')>>24 == $int_ip>>24 || ip2long('172.16.0.0')>>20 == $int_ip>>20 || ip2long('192.168.0.0')>>16 == $int_ip>>16;
}
function safe_request_url($url)
{
if (check_inner_ip($url))
{
echo $url.' is inner ip';
}
else
{
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_HEADER, 0);
$output = curl_exec($ch);
$result_info = curl_getinfo($ch);
if ($result_info['redirect_url'])
{
safe_request_url($result_info['redirect_url']);
}
curl_close($ch);
var_dump($output);
}
}
$url = $_GET['url'];
if(!empty($url)){
safe_request_url($url);
}
?>
這道題可以動態分析加靜態分析一起使用
代碼審計
GET方式傳入url賦值給$url
檢測url是否為空
不為空則執行safe_request_url
函數
safe_request_url
函數在一開始就有個通過check_inner_ip($url)
進行校驗的判斷
接下來看check_inner_ip($url)
函數
check_inner_ip
函數:
首先preg_match
正則匹配,檢查傳入的url是否是url格式
如果是url格式,則繼續執行safe_request_url
1. curl_close — 關閉一個cURL會話
2. curl_copy_handle — 復制一個cURL句柄和它的所有選項
3. curl_errno — 返回最后一次的錯誤號
4. curl_error — 返回一個保護當前會話最近一次錯誤的字符串
5. curl_escape — 使用 URL 編碼給定的字符串
6. curl_exec — 執行一個cURL會話
7. curl_file_create — 創建一個 CURLFile 對象
8. curl_getinfo — 獲取一個cURL連接資源句柄的信息
9. curl_init — 初始化一個cURL會話
10. curl_multi_add_handle — 向curl批處理會話中添加單獨的curl句柄
11. curl_multi_close — 關閉一組cURL句柄
12. curl_multi_exec — 運行當前 cURL 句柄的子連接
13. curl_multi_getcontent — 如果設置了CURLOPT_RETURNTRANSFER,則返回獲取的輸出的文本流
14. curl_multi_info_read — 獲取當前解析的cURL的相關傳輸信息
15. curl_multi_init — 返回一個新cURL批處理句柄
16. curl_multi_remove_handle — 移除curl批處理句柄資源中的某個句柄資源
17. curl_multi_select — 等待所有cURL批處理中的活動連接
18. curl_multi_setopt — 為 cURL 並行處理設置一個選項
19. curl_multi_strerror — Return string describing error code
20. curl_pause — Pause and unpause a connection
21. curl_reset — Reset all options of a libcurl session handle
22. curl_setopt_array — 為cURL傳輸會話批量設置選項
23. curl_setopt — 設置一個cURL傳輸選項
24. curl_share_close — Close a cURL share handle
25. curl_share_init — Initialize a cURL share handle
26. curl_share_setopt — Set an option for a cURL share handle.
27. curl_strerror — Return string describing the given error code
28. curl_unescape — 解碼給定的 URL 編碼的字符串
29. curl_version — 獲取cURL版本信息
題目中主要用到
curl_init — 初始化一個cURL會話
curl_setopt — 設置一個cURL傳輸選項
curl_exec — 執行一個cURL會話
curl_getinfo — 獲取一個cURL連接資源句柄的信息
curl_close — 關閉一個cURL會話
其中curl_setopt用法為
好吧,太亂了,直接看例子
以上的curl部分大致功能就是初始化,然后獲取一個網頁
這個if的作用就是如果沒有獲取到信息,就重復獲取,重復執行safe_request_url
函數
最后把exec
后的數據dump
出來
var_dump($output);
之后就出函數了
接下來將parse_url
后的url
賦值給$url_parse
如果parse_url
執行失敗,則返回false
由此可見,parse_url
會將url
分成6個部分
[scheme] => http
[host] => hostname
[user] => username
[pass] => password
[path] => /path
[query] => arg=value
[fragment] => anchor
接下來,是一個賦值過程
hostname
得到的是前面url_parse
分離出來的host
部分
ip
得到的是將hostname轉換為ip地址后的數值(就是把url轉成ip地址)
繞過
我們最終的目的是要curl 127.0.0.1/flag
然后得到dump
出來的數據
那么該怎么繞過這兩重檢測呢
直接傳入http://a:@127.0.0.1:80@baidu.com/flag.php
payload:
http://c83f8e90-2d41-465e-b179-6fdf4b9bfa8f.node3.buuoj.cn/challenge.php/?url=http://a:@127.0.0.1:80@baidu.com/flag.php
其中flag.php是在界面給的提示
這個payload的作用就是
讓檢測到的url
和curl請求的url
不一致
原理我記得好久之前我看wooyun上好像有過,但是我記不住了😕
這個payload傳入后
safe_request_url
檢測之后 parse_url
取到的host
是baidu.com
這個原理還要去找找呀。。。
參考:https://blog.csdn.net/wuyaowangchuan/article/details/110433971
最后附上整篇的審計
<?php
highlight_file(__FILE__);//用PHP高亮顯示當前的文件
function check_inner_ip($url) //獲取url的域名,將域名轉為ip,然后再判斷這個ip是否是私有地址
{
$match_result=preg_match('/^(http|https)?:\/\/.*(\/)?.*$/',$url);
//返回$url的匹配,值將是 0 次(不匹配)或 1
//^從開頭開始匹配
//? 匹配0或1個正好在它之前的那個字符。注意:這個元字符不是所有的軟件都支持的
//( )標記一個子表達式的開始和結束位置。子表達式可以獲取供以后使用。要匹配這些字符,請使用 \( 和 \)
//. 匹配除換行符 \n 之外的任何單字符。要匹配 . ,請使用 \.
//* 匹配前面的子表達式零次或多次。要匹配 * 字符,請使用 \*
//$:從字符串末尾進行匹配
if (!$match_result)
{
die('url fomat error');
//如果url不符合正則表達式
}
try
{
$url_parse=parse_url($url);
//分解出一個URL的各個部
// $url_parse是一個數組
}
catch(Exception $e)
{
die('url fomat error');
return false;
}
$hostname=$url_parse['host']; //hostname 為主機名,也就是域名
$ip=gethostbyname($hostname); //通過域名獲取IP地址
$int_ip=ip2long($ip); //ip2long:將IPv4的ip地址(以小數點分隔形式)轉換為int
//ip2long('127.0.0.0')>>24 == $int_ip>>24這里的話,我搜網上也沒搜到時什么,自己推測,>>代表着右移
//ip2long('127.0.0.0')>>24,就是把127.0.0.0的整形右移24位,IP一共32位,右移后不就剩個A段“127”了
//$int_ip>>24,就是把經過parse_url()后的IP右移24位,如果我們輸入的是127.0.0.1,有以后應該就是127
//綜上所述,這里就是一個IP白名單,不允許我們輸入類似127.*.*.*、10.*.*.*、172.16.*.*、192.168.*.*
return ip2long('127.0.0.0')>>24 == $int_ip>>24 || ip2long('10.0.0.0')>>24 == $int_ip>>24 || ip2long('172.16.0.0')>>20 == $int_ip>>20 || ip2long('192.168.0.0')>>16 == $int_ip>>16;
//判斷是否是私有地址,是則這個函數就返回1
}
function safe_request_url($url)
{
if (check_inner_ip($url))
//判斷url是否是私有地址
{
echo $url.' is inner ip';
}
else
{
$ch = curl_init(); //初始化新的會話,返回 cURL 句柄,供curl_setopt()、 curl_exec() 和 curl_close() 函數使用
curl_setopt($ch, CURLOPT_URL, $url); //訪問的域名
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); //
curl_setopt($ch, CURLOPT_HEADER, 0); //
//curl_setopt函數參數解釋:https://www.cnblogs.com/lilyhomexl/p/6278921.html
$output = curl_exec($ch); //抓取URL並把它傳遞給瀏覽器
$result_info = curl_getinfo($ch); //php curl請求在curl_exec()函數執行之后,可以使用curl_getinfo()函數獲取CURL請求輸出的相關信息
//[php curl curl_getinfo()返回參數詳解](https://www.cnblogs.com/zqifa/p/php-curl-3.html)
if ($result_info['redirect_url'])
{
safe_request_url($result_info['redirect_url']);
}
curl_close($ch); // 關閉cURL資源,並且釋放系統資源
var_dump($output); //執行
}
}
$url = $_GET['url'];
if(!empty($url)){
safe_request_url($url);
}