识别目标基于 Struts2 构建的方法

之所以说是标题党,是因为这两种方法都有相应的要求,不能做到 100%。这是我的学习笔记,个人能力有限,如果你期待的是一个可以快速集成进类似资产识别类工具里的方法,那你可能要失望了。

常规的办法有:

  1. 通过页面回显的错误消息来判断,页面不回显错误消息时则无效。
  2. 通过网页后缀来判断,如.do .action,有可能不准。
  3. 判断 /struts/webconsole.html 是否存在来进行判断,需要 devMode 为 true。

这里记录两种其它的方法以及我在调试源代码过程中分析出来的原理。

方法一:通过 actionErrors。此方法最早应该是由 kxlzx 在好些年前提出来的。要求是对应的 Action 需要继承自 ActionSupport 类。

利用方法:如原始 URL 为 https://threathunter.org/则检测所用的 URL 为 https://threathunter.org/?actionErrors=1111

如果返回的页面出现异常,则可以认定为目标是基于 Struts2 构建的。异常包括但不限于以下几种现象:

  1. 页面直接出现 404 或者 500 等错误
  2. 页面上输出了与业务有关错误消息,或者 1111 被回显到了页面上
  3. 页面的内容结构发生了明显的改变
  4. 页面发生了重定向

原理,这里简单写一下,因为涉及到请求-- 拦截器链 – action 的全过程,流程很多。

ActionSupport 类实现了 com.opensymphony.xwork2.ValidationAware 接口。此接口中有两个方法起到了作用:

  1. void setActionErrors(Collection<String errorMessages);
  2. boolean hasErrors();

hasErrors 方法的作用是用来检查当前 action 是否发生了错误。

ActionSupport 对这两个方法的实现如下:

  1. public void setActionErrors(Collection<String errorMessages) {
  2. validationAware.setActionErrors(errorMessages);
  3. }
  4. public boolean hasErrors() {
  5. return validationAware.hasErrors();
  6. }

Struts2 内建了 ParametersInteceptor,并将它放置在了默认的拦截器链中。基本可以说在访问绝大多数的 action 的时候,都会经过 ParametersInterceptor 这个拦截器的处理(当然这是取决于开发人员在 struts.xml 所定义的 package 是否继承自 struts-default,一般都会选择继承)。 此拦截器的作用,就是利用 OGNL 表达式将 HTTP 参数自动映射到 CompoundRoot 上面(CompoundRoot 的具体机制这里不细讲了),此拦截器造成了当年的 S2-003。

当前 Action 类的实例是位于 CompoundRoot 中的,所以当提交参数 actionErrors=111 的时候,会造成 OGNL 去调用 action.setActionErrors 方法。 调用完此方法后,action 的 hasErrors 方法会返回 true。

默认的拦截器链中还有另一个名为 workflow 的拦截器配置如下,该拦截器位于 ParametersInterceptor 的后方:<interceptor name=“workflow” class=“com.opensymphony.xwork2.interceptor.DefaultWorkflowInterceptor”/

其 doIntercept 方法代码如下:

  1. @Override
  2. protected String doIntercept(ActionInvocation invocation) throws Exception {
  3. Object action = invocation.getAction();
  4.  
  5. if (action instanceof ValidationAware) {
  6. ValidationAware validationAwareAction = (ValidationAware) action;
  7.  
  8. if (validationAwareAction.hasErrors()) {
  9. if (LOG.isDebugEnabled()) {
  10. LOG.debug("Errors on action [#0], returning result name [#1]", validationAwareAction, inputResultName);
  11. }
  12.  
  13. String resultName = inputResultName;
  14. resultName = processValidationWorkflowAware(action, resultName);
  15. resultName = processInputConfig(action, invocation.getProxy().getMethod(), resultName);
  16. resultName = processValidationErrorAware(action, resultName);
  17.  
  18. return resultName;
  19. }
  20. }
  21.  
  22. return invocation.invoke();
  23. }

它将检测 action 是否出现了错误,如果出现了错误,则中断拦截器链的运行,直接返回一个 result,这个 result 对应的就是我们最终看到的那个异常状态的页面。

方法二:通过 CheckboxInterceptor。这是我在调试 Struts2 的过程中找到的方法。本来是想找到一个 100% 通杀的办法的,结果没有找到。

要求:需要有一个能够回显到页面上的字符串类型的参数。我目前碰到的最多的地方就是各个目标的搜索功能。搜索功能往往会将 keyword 回显到页面上。

此拦截器本意是配合 HTML 中的 checkbox 来使用的,当某个参数没有被提交的时候,则认定这个 checkbox 没有被选中。

不多说了,直接看 CheckboxInterceptor 的 intercept 方法的代码吧:

  1. public String intercept(ActionInvocation ai) throws Exception {
  2. Map<String, Object parameters = ai.getInvocationContext().getParameters();
  3. Map<String, String[] newParams = new HashMap<String, String[]();
  4. Set<Map.Entry<String, Object entries = parameters.entrySet();
  5.  
  6. for (Iterator<Map.Entry<String, Object iterator = entries.iterator(); iterator.hasNext();) {
  7. Map.Entry<String, Object entry = iterator.next();
  8. String key = entry.getKey();
  9.  
  10. if (key.startsWith("__checkbox_")) {
  11. String name = key.substring("__checkbox_".length());
  12.  
  13. Object values = entry.getValue();
  14. iterator.remove();
  15. if (values != null && values instanceof String[] && ((String[])values).length 1) {
  16. if (LOG.isDebugEnabled()) {
  17. LOG.debug("Bypassing automatic checkbox detection due to multiple checkboxes of the same name: #0", name);
  18. }
  19. continue;
  20. }
  21.  
  22. // is this checkbox checked/submitted?
  23. if (!parameters.containsKey(name)) {
  24. // if not, let's be sure to default the value to false
  25. newParams.put(name, new String[]{uncheckedValue});
  26. }
  27. }
  28. }
  29.  
  30. parameters.putAll(newParams);
  31.  
  32. return ai.invoke();
  33. }

它会检查所有的参数,发现如果有参数名是 __checkbox_param 这种格式的,并且参数中不存在 param 参数,则它会添加一个名为 param 的参数,并将其值设置为 false。

例子:原始 URL: https://threathunter.org/?keyword=aaa,且页面回显出了 aaa检测 URL: https://threathunter.org/?__checkbox_keyword=aaa,如果发现回显变成了 false,则可以认定目标是基于 Struts2 开发的。

原理就是 CheckboxInterceptor 发现了一个名为 __checkbox_keyword 的参数,但是没有发现 keyword 参数,则它会添加一个 keyword 参数,并将其值设置为 false。

实例截图:1.png

2.png

A side note: 我遇到了一些环境,利用 actionErrors 可以断定是基于 Struts2 开发的,但是利用 CheckboxInterceptor 的方式却没有成功。如果你知道原因的话我们可以交流一下。:)

方法三:我没有测试。我看到 I18n 拦截器里面会取名为 request_only_locale 参数的值,用于设置 Locale,以实现全站语言的切换。对于支持多语言的网站,应该是可以利用这个参数来测试一下语言切换,如果切换成功可以认定目标网站使用了 Struts2(我想应该没有别的框架也是用的同名参数吧?)

posted @ 2020-12-11 11:55  桑中子衿  阅读(703)  评论(0编辑  收藏  举报