
步骤
打开网站可以看到存在flag.php,所以是要通过SSRF获取flag.php的内容。在intersting chanllenge中查看源代码。
代码分析
php
<?php
highlight_file(__FILE__); //显示源码
function check_inner_ip($url)
{
$match_result=preg_match('/^(http|https)?:\/\/.*(\/)?.*$/',$url); //正则匹配是否含有http,https
if (!$match_result)
{
die('url fomat error');
}
try
{
$url_parse=parse_url($url); //解析url,返回一个关联数组,包含了url的各个部分,如协议,主机等
}
catch(Exception $e)
{
die('url fomat error');
return false;
}
$hostname=$url_parse['host']; //获取主机名
$ip=gethostbyname($hostname); //通过主机名获取ip地址,即dns解析
$int_ip=ip2long($ip); //将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; //检查ip是否属于内网
}
function safe_request_url($url)
{
if (check_inner_ip($url)) //调用检查是否是内网ip的函数
{
echo $url.' is inner ip';
}
else
{
$ch = curl_init(); //初始化cURL会话
curl_setopt($ch, CURLOPT_URL, $url); //设置要请求的url
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); //将结果以字符串返回,而不是直接输出
curl_setopt($ch, CURLOPT_HEADER, 0); //不包含头部信息
$output = curl_exec($ch); //执行cURL请求
$result_info = curl_getinfo($ch); //获取请求的信息
if ($result_info['redirect_url']) //如果是重定向,递归调用safe_request_url
{
safe_request_url($result_info['redirect_url']);
}
curl_close($ch); //关闭cURL会话
var_dump($output);
}
}
$url = $_GET['url']; //接收get参数url
if(!empty($url)){ //判空
safe_request_url($url);
}
?>
漏洞点1
parse_url
此函数会返回一个关联数组,包含url各个组成部分,如果缺少其中的某一项,则不会为这个组成部分创建数组项目。
组成部分:
- scheme -- 如 http
- host 域名
- port 端口
- pass
- path 路径
- query -- 在问号 ? 之后
- fragment -- 在散列符号 # 之后
关键点在于此函数并不意味着给定的url是合法的,它只是将url的各个部分分开。即parse_url可以接受不完整的url,并尽量解析正确。
传入的url如果是:http://@127.0.0.1:80@baidu.com/flag.php
返回的数组为:

这里host变为第二个@符号后面的了,所以host可控了,在配合gethostbyname获取域名的ip。
这里第一个@符的作用是让cur_getinfo()函数正确解析,而解析的内容就是第一个@符后面的内容:127.0.0.1:80,这样就成功绕过了。

漏洞点2
gethostbyname
将域名解析为ip,如果是127.0.0.1则会解析为本地所在的公网ip。
payload:
bash
http://127.0.0.1:80/flag.php
