本文转自互联网
velocity的标签中支持$abc 这样的语法,如果abc是一个对象,则写模板时就可以利用它来进行反射,调用一些危险的方法,如
1
|
$vm.getClass().newInstance()
|
1
|
#
set ($exec
=
"kxlzx"
)$exec.
class
.forName(
"java.lang.Runtime"
).getRuntime().exec(
"calc"
)
|
通过反射,让系统本身出现了安全漏洞,这类危险的操作,可以通过屏蔽反射来杜绝,在velocity属性中添加一行配置即可
1
|
runtime.introspector.uberspect
=
org.apache.velocity.util.introspection.SecureUberspector
|
velocity默认的配置为:
1
|
runtime.introspector.uberspect
=
org.apache.velocity.util.introspection.UberspectImpl
|
本文主要讨论从velocity初始化过程到解析标签。以及如何通过
1
|
SecureUberspector
|
来屏蔽反射,欢迎补充。
velocity的内省主要的用处是解析如$a.id,$a.name的引用,与其说是内省,不如说是通过反射找get方法。。。。
先来分析velocity的初始化过程
这里只是对velocity初始化过程的概括,初始化过程大量依赖的配置参数,即velocity.properties,用户一般自定义该文件或直接载入Properties,默认的配置目录为
1
|
org/apache/velocity/runtime/defaults/velocity.properties
|
调用方法渲染流程
1
2
|
VelocityEngine velocityEngine
=
new
VelocityEngine(
"/velocity.properties"
);
velocityEngine.evaluate(context, writer,
"logMsgName"
,
new
InputStreamReader(VelocityTest
2
.
class
.getResource(
"test.vm"
).openStream()));
|
生成NodeTree的代码比较复杂,可能用的是某种算法,总之,最后的Tree里面包含了所有的vm信息,如果是parse\include会生成AsTDirective,如果是文本,会生成ASTText对应,如果是set,会生成ASTSetDirective,如果是引用java教程,会生成ASTReference对应。。等等。。
这里列举几个标签的处理流程
parse
从源代码分析来看,parse标签里面的内容甚至可以写成动态的,如
1
|
#
parse(
"${a}.vm"
)
|
发送includeEvent和velocity初始化过程中的各个事件处理器是对应的,引入vm文件外的文件,都会触发includeEvent,然后根据其返回值,来找到真正的vm资源文件,因此,我们可以在eventHander中重定向返回的资源位置,如 a.vm -> b.vm
另外,parse和include 对velocity来说,是两种type,解析parse文件时,会把context传入进行解析
引用标签,如$a,$vm.id
普通的引用渲染流程不包括子流程 “SecureUberspector拦截方法”,如果引用值为$a.id ,则会去找a.getid() -> a.getId(),,然后反射调用method.invoke(objecct)
引用的渲染流程会根据identifier和method进行不同的流程
identifier方式即$a.id
method方式即$a.println()
为什么下面两行嗲吗的效果一样呢
1
2
|
$exec.
class
$exec.getClass()
|
原因就在这里,velocity把class当成一个属性来处理了,因此,去找getClass方法,恰好对象都有getClass方法,这样效果就和直接写$exec.getClass()一样了
SecureUberspector如果达到屏蔽反射方法的呢,先来看一看它的类依赖
UberspecttImpl中有一个introspector对象,SecureUberspector对其进行了重定义SecureUberspector的初始化方法如下,badPackages和badClass的配置也是在默认的velocity.properties中配置的,用户可以添加更多的配置
1
2
3
4
5
6
7
8
9
10
|
public
void
init()
{
String [] badPackages = runtimeServices.getConfiguration()
.getStringArray(RuntimeConstants.INTROSPECTOR_RESTRICT_PACKAGES);
String [] badClasses = runtimeServices.getConfiguration()
.getStringArray(RuntimeConstants.INTROSPECTOR_RESTRICT_CLASSES);
introspector =
new
SecureIntrospectorImpl(badClasses, badPackages, log);
}
|
SecureIntrospectorImpl实现了方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
public
Method getMethod(Class clazz, String methodName, Object[] params)
throws
IllegalArgumentException
{
if
(!checkObjectExecutePermission(clazz, methodName))
{
log.warn(
"Cannot retrieve method "
+ methodName +
" from object of class "
+ clazz.getName() +
" due to security restrictions."
);
return
null
;
}
else
{
return
super
.getMethod(clazz, methodName, params);
}
}
|
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
78
|
/**
* Determine which methods and classes to prevent from executing. Always blocks
* methods wait() and notify(). Always allows methods on Number, Boolean, and String.
* Prohibits method calls on classes related to reflection and system operations.
* For the complete list, see the properties <code>introspector.restrict.classes</code>
* and <code>introspector.restrict.packages</code>.
*
* @param clazz Class on which method will be called
* @param methodName Name of method to be called
* @see org.apache.velocity.util.introspection.SecureIntrospectorControl#checkObjectExecutePermission(java.lang.Class, java.lang.String)
*/
public
boolean
checkObjectExecutePermission(Class clazz, String methodName)
{
/**
* check for wait and notify
*/
if
(methodName !=
null
&&
(methodName.equals(
"wait"
) || methodName.equals(
"notify"
)) )
{
return
false
;
}
/**
* Always allow the most common classes - Number, Boolean and String
*/
else
if
(Number.
class
.isAssignableFrom(clazz))
{
return
true
;
}
else
if
(Boolean.
class
.isAssignableFrom(clazz))
{
return
true
;
}
else
if
(String.
class
.isAssignableFrom(clazz))
{
return
true
;
}
/**
* Always allow Class.getName()
*/
else
if
(Class.
class
.isAssignableFrom(clazz) &&
(methodName !=
null
) && methodName.equals(
"getName"
))
{
return
true
;
}
/**
* check the classname (minus any array info)
* whether it matches disallowed classes or packages
*/
String className = clazz.getName();
if
(className.startsWith(
"[L"
) && className.endsWith(
";"
))
{
className = className.substring(
2
, className.length() -
1
);
}
int
dotPos = className.lastIndexOf(
'.'
);
String packageName = (dotPos == -
1
) ?
""
: className.substring(
0
, dotPos);
for
(
int
i =
0
, size = badPackages.length; i < size; i++)
{
if
(packageName.equals(badPackages[i]))
{
return
false
;
}
}
for
(
int
i =
0
, size = badClasses.length; i < size; i++)
{
if
(className.equals(badClasses[i]))
{
return
false
;
}
}
return
true
;
}
|
SecureIntrospectorImpl
这里可以看见它对对象访问方法的屏蔽操作
badPackage:java.lang.refect
badClass:
那么,为什么我们不直接使用SecureIntrospectorImpl呢,因为它仅仅是一个工具
SecureUberspector类对foreach标签也进行了支持
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
|
/**
* Get an iterator from the given object. Since the superclass method
* this secure version checks for execute permission.
*
* @param obj object to iterate over
* @param i line, column, template info
* @return Iterator for object
* @throws Exception
*/
public
Iterator getIterator(Object obj, Info i)
throws
Exception
{
if
(obj !=
null
)
{
SecureIntrospectorControl sic = (SecureIntrospectorControl)introspector;
if
(sic.checkObjectExecutePermission(obj.getClass(),
null
))
{
return
super
.getIterator(obj, i);
}
else
{
log.warn(
"Cannot retrieve iterator from "
+ obj.getClass() +
" due to security restrictions."
);
}
}
return
null
;
}
|
就这样,foreach时,如果对象是java.lang.refect包下的类或badClass,就没有权限了