朋友求助,老项目客户要求升级到Struts2.5.30,规避必要漏洞和风险。搞了一两周没进展。
1、Method XX from action XX is not allowed!
网上方法基本是在XML中进行配置,算基本配置吧,包括
1)package标签中增加方法通配符
<global-allowed-methods>regex:.*</global-allowed-methods>
2)package标签增加 strict-method-invocation=”false” 属性配置
<package name="security" namespace="/xframe/security" extends="XXXDefault"
strict-method-invocation="false">
这个属性Struts2.5默认是SMI为true,且不能全局设置,只能一个个Package进行设置,不申明的都不允许访问。然后就是配置在struts.xml中的action可以用了,但自动扫描的包都不行。
网上能搜到的基本就是这个配置,但怎么都不行。
在仔细研究。
2、codebehind插件,替换为struts2.3引入替换的新插件convention
接上条,仔细研究后,发现老项目用的codebehind插件,简化配置。该插件会自动扫描制定的源码包,把扫描出来的action和响应方法都暴露给外面允许访问。
这恰恰是Struts2.5安全性能提升到的一个点。于是换convertion。
convertion的主要配置如下:
<!-- 限制方法调用之方法前缀 -->
<constant name="struts.strictMethodInvocation.methodRegex" value="([A-Za-z]*)"/>
<!-- 进行扫描的根包,该包会被扫描成action -->
<constant name="struts.convention.action.packages" value="com.xx" />
<!-- 指定的包会被进行扫描 -->
<constant name="struts.convention.package.locators" value="action,actions,web" />
<!-- 返回页面地址 -->
<constant name="struts.convention.result.path" value="/pages/" />
<!-- 用于进行action查找的后缀 -->
<constant name="struts.convention.action.suffix" value="Action" />
然后,发现依旧是not allowed!
3、自定义convention扩展
卡了好长时间,最后下载了Struts2.5的全套源码,通过eclipse设置源码后开始调试。发现在请求的时候,convention自动扫描到的Action,在请求的时候,生成的ActionConfig中的actionMethod压根就没有Package的global-allowed-method允许的通配符。然后通过XML配置进来的action确有通配符。
convertion和XML不通!!!
有卡壳,继续研究,尤其是官方的文档和WIKI
Convention plugin (apache.org)https://struts.apache.org/plugins/convention/Convention Plugin – Apache Struts 2 Wiki – Apache Software Foundation
https://cwiki.apache.org/confluence/display/WW/Convention+Plugin发现没有任何这方面的说明。没有!
然后是发现可以扩展convention,官方文档中的overwriting部分。
1)配置,在Struts.xml中配置自己的
<bean type="org.apache.struts2.convention.ActionConfigBuilder"
name="convention1" class="com.xxx.security.PackageBasedActionConfigBuilderCustom"></bean>
<constant name="struts.convention.actionConfigBuilder" value="convention1"/>
发现convention的PackageBasedActionConfigBuilder本身代码也很多,1141行。就没有按说明实现interface,而是直接继承了PackageBasedActionConfigBuilder,然后多关键方法做覆盖。
2)PackageBasedActionConfigBuilderCustom类
2.1)构造方法
@Inject
public PackageBasedActionConfigBuilderCustom(Configuration configuration, Container container, ObjectFactory objectFactory,
@Inject(ConventionConstants.CONVENTION_REDIRECT_TO_SLASH) String redirectToSlash,
@Inject(ConventionConstants.CONVENTION_DEFAULT_PARENT_PACKAGE) String defaultParentPackage) {
super(configuration, container, objectFactory, redirectToSlash,defaultParentPackage);
// Validate that the parameters are okay
this.configuration = configuration;
this.actionNameBuilder = container.getInstance(ActionNameBuilder.class, container.getInstance(String.class, ConventionConstants.CONVENTION_ACTION_NAME_BUILDER));
this.resultMapBuilder = container.getInstance(ResultMapBuilder.class, container.getInstance(String.class, ConventionConstants.CONVENTION_RESULT_MAP_BUILDER));
// this.interceptorMapBuilder = container.getInstance(InterceptorMapBuilder.class, container.getInstance(String.class, ConventionConstants.CONVENTION_INTERCEPTOR_MAP_BUILDER));
this.objectFactory = objectFactory;
// this.redirectToSlash = Boolean.parseBoolean(redirectToSlash);
if (LOG.isTraceEnabled()) {
LOG.trace("Setting action default parent package to [{}]", defaultParentPackage);
}
// this.defaultParentPackage = defaultParentPackage;
}
先调用父类的,其他变量是为了方便后面引用的。
2.2)createActionConfig
/**
* Creates a single ActionConfig object.
*
* @param pkgCfg The package the action configuration instance will belong to.
* @param actionClass The action class.
* @param actionName The name of the action.
* @param actionMethod The method that the annotation was on (if the annotation is not null) or
* the default method (execute).
* @param annotation The ActionName annotation that might override the action name and possibly
*/
protected void createActionConfig(PackageConfig.Builder pkgCfg, Class<?> actionClass, String actionName,
String actionMethod, Action annotation, Set<String> allowedMethods) {
Set<String> ss = new HashSet<>();
ss.add("regex:.*");
pkgCfg.addGlobalAllowedMethods(ss);
super.createActionConfig(pkgCfg, actionClass, actionName, actionMethod, annotation, allowedMethods);
//判断nameSpace空间是否严格限制方法调用,默认是不允许的。
pkgCfg.strictMethodInvocation(false);
packageNum++;
LOG.debug(packageNum + "\t" + pkgCfg.getNamespace() + "\t" + actionName + "\t" + actionMethod + "\t" + actionClass.getName());
}
本方法一是给自动构建的package增加GlobalAllowedMethods通配符,二是关闭方法调用严格限制。
如此,Not Allowed终于解决了。
2.3)Could not find action or result
之后还发现一个问题,找不到XML中配置的全局Result
<global-results>
<result name="error">/pages/xframe/global/Error.jsp</result>
<result name="login-input" type="redirectAction">
<param name="actionName">Login</param>
<param name="method">input</param>
<param name="namespace">/</param>
</result>
</global-results>
Could not find action or result:/xxx/.action
Could not find action or result
全局设定,也是为了简化处理的。但在XML中配置的内容,和convention中还是不互通。
重新修改了上述方法如下。
protected void createActionConfig(PackageConfig.Builder pkgCfg, Class<?> actionClass, String actionName,
String actionMethod, Action annotation, Set<String> allowedMethods) {
//获取XML中的defaultParentPackage对应的GlobalResult
for(String cfgName :this.configuration.getPackageConfigNames()){
PackageConfig cfg = this.configuration.getPackageConfig(cfgName);//XML中的defaultParentPackage
Map<String, ResultConfig> map = cfg.getGlobalResultConfigs();
if(map.size() > 0){
pkgCfg.addGlobalResultConfigs(map);
}
}
Set<String> ss = new HashSet<>();
ss.add("regex:.*");
pkgCfg.addGlobalAllowedMethods(ss);
super.createActionConfig(pkgCfg, actionClass, actionName, actionMethod, annotation, allowedMethods);
//判断nameSpace空间是否严格限制方法调用,默认是不允许的。
pkgCfg.strictMethodInvocation(false);
packageNum++;
LOG.debug(packageNum + "\t" + pkgCfg.getNamespace() + "\t" + actionName + "\t" + actionMethod + "\t" + actionClass.getName());
}
至此,问题解决。
其他诸如Convention扫描出来的Package的Namespace不规范等,单独写一个方法重新生成Namespace和ActionName即可。