从模板注入漏洞谈起,到一个烂尾的寻找python沙盒绕过的代码

宝宝很烦,代码烂尾了,宝宝并不想说什么

从模板注入漏洞谈起

不管是什么语言,php也好,python也好,在做前端的时候都会有模板技术,这可以将前端框架与后端所提供的动态值分离,对于后台开发者来说,只需要拿着前端模板然后像””.format()一样,来为里面的参数提供不同的值

1
2
3
4
$twig = new Twig_Environment(new Twig_Loader_String());
// 这里面Hello {{name}}就是模板,array("name" => $_GET["name"])就是在为其赋值
$output = $twig->render("Hello {{name}}", array("name" => $_GET["name"]));
echo $output;
1
2
3
4
$twig = new Twig_Environment(new Twig_Loader_String());
// 这种情况是直接填入到里面去
$output = $twig->render("Hello {$_GET['name']}");
echo $output;

上述两种都可以实现填充,然而会存在一种问题:
第一种是类似format()的函数实现,大多数模板实现这个功能的时候都会进行转义,所以xss是没法构造的,然而第二种的方式就是将$_GET['name']直接放到了模板里面,不会进行转义,所以可以构造xss

模板可能还支持对应语言的“编程功能”以及一些简单的运算

模板并不是静态的放进去,虽然模板产品类型很多,但或多或少都能执行一些逻辑

在Twig中,模板支持运行这样简单的逻辑,如果并没有运行的话,可能是关键字符被过滤,并不是模板不支持这样写

1
name={{2*10}} name={{2+2}}

而在python的Jinja2模板中,就能支持更多更强大的逻辑:

1
2
3
4
5
from jinja2 import Template
str = "{% for i in [1,2,3] %}{{ i }}{% endfor %}"
template = Template('your input: {}'.format(str))
print (template.render())

是不是感觉很像python的语法?那么是不是我们就相当于拿到了python的任意代码执行?其实还差的远,因为虽然我们能在模板里书写任意代码,但并不是能够调用任意函数,就像python程序里没有import os就不能使用os.system()一样。在Jinja2里如果我们想“import os”,需要这样做

1
template.globals['os'] = os

显然我们没有写这种代码的能力。。。但其实还是可以的,我们不用import os模块就能使用os的,这里就需要了解python的沙盒绕过了,也是今天我花时间最多却没搞出来的点。。。

python沙盒绕过

还是先大致统计下目前已知的需要用到沙盒绕过的情景

  • 本文中通过模板引擎注入代码,但没法import,也就相当于限制了一些关键函数的使用
  • 昨天USTC比赛里见到了python反序列化,所以也相当于可以执行任意代码,只是一些关键的模块被禁用了

如果程序不从函数注册来判断那些函数可以使用的话,我们可以通过以下过程来得到我们要用的模块或者函数

通过一个class的各种属性或方法,可以得到很多相关的东西,其中就可能包含我们想要的模块或函数

  • class.__dict__
  • class.__init__.__globals__
  • class.func_globals

应该还有很多,我们可以自己去测试

通过class.__class__.__base__.__subclasses__()我们就可以得到很多子类,其实在对上面的方法进行测试的时候,我们有时也可以看到很多类

![img](https://ws1.sinaimg.cn/large/93e435bbly1fkq0hprcbhj20id04nt8x.jpg)

于是我们通过一个个跳转,就可以最终找到我们要的函数或者模块,举个例子

1
2
3
4
5
6
for i in [].__class__.__base__.__subclasses__():
if i.__name__ == "catch_warnings":
print i.__init__.func_globals['linecache'].__dict__['os']
output:
<module 'os' from 'C:\Python27\lib\os.pyc'>

考虑实现自动查找定位到一个函数要走的路径

从前面举的例子来看,我们想找到一个函数很能需要经过很多跳才行,能不能遍历下自动去找呢?

注意,代码烂尾了。。。最后并没有实现想要的功能,哪里出问题了有说不太清,最后时间预算不够了,就只能烂尾了。。。如果以后有时间再尝试写写吧

首先阐述下我的实现:

我们有一个初始类,一般是[]或(),然后通过.__class__.__base__.__subclasses__()扩展成很多的子类,于是我们就维护一个类列表,去遍历分析它,虽然一开始列表里只有一个,但我们对每一个分析的类都去寻找子类并将新找到的类及时添加到列表末尾

然后在对每个类分析的过程中,尝试通过上面提到的一种获取函数的方法.__init__.__globals__来从中提取函数,如果找到了输出出来,有一点比较复杂的是,在提取函数语句的输出结果中,有很多中可能,有可能我们要的函数是在一个列表或字典中,这时候还需要再深入去分析,所以我就写成了一个递归

然后就是代码了,烂尾的代码。。。

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
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
#coding=utf-8
import os
import warnings
"""
大致的过程:
- 首先从一个类生成多个类
- 对类生成各种各样的东西,分这几种类型:
- 函数直接看是不是
- 类的话,就放到类遍历列表里面去,当然要先判断有没有才行
- 字典的话,生成value去再来分析
- 列表的话,直接去分析
"""
class_list = []
class_original = []
class_list.append(class_original)
goal_function = open
#通过__class__.__base__.__subclasses__生成子class
def get_subclass(_class):
try:
return _class.__class__.__base__.__subclasses__()
except:
return []
#从class中通过.__init__.__globals__来得到所有的各种类型的值
def get_something(_class):
if hasattr(_class.__init__, '__globals__'):
return _class.__init__.__globals__
#递归式的对一个列表进行分析
def analysis_list(list):
if list:
for i in list:
if type(i) == type([]):
analysis_list(i)
break
if type(i) == type({}):
analysis_dict(i)
break
if hasattr(i, '__call__'):
if i == goal_function:
print (i)
break
#还不是话,就推断是类了,但是目前还不敢肯定就这几种类型
if i not in class_list:
class_list.append(i)
#递归式的对一个字典进行分析
def analysis_dict(dict):
if dict:
for i in dict.values() :
if type(i) == type([]):
analysis_list(i)
break
if type(i) == type({}):
analysis_dict(i)
break
if hasattr(i, '__call__'):
if i == goal_function:
print (i)
break
#还不是话,就推断是类了,但是目前还不敢肯定就这几种类型
if i not in class_list:
class_list.append(i)
for i in class_list:
#首先增加一波子类
for j in get_subclass(i):
if j not in class_list:
class_list.append(j)
# print class_list
#开始对这个类进行分析查找
analysis_dict(get_something(i))