Struts2 漏洞分析及如何提前预防

论坛 期权论坛 脚本     
niminba   2021-5-23 02:55   682   0

    2016年4月26日,Apache Struts2官方又发布了一份安全公告:Apache Struts2 服务在开启动态方法调用的情况下可以远程执行任意命令,官方编号 S2-032,CVE编号 CVE-2016-3081。这是自2012年Struts2命令执行漏洞大规模爆发之后,该服务时隔四年再次爆发大规模漏洞。该漏洞也是今年目前爆出的最严重安全漏洞。黑客利用该漏洞,可对企业服务器实施远程操作,从而导致数据泄露、远程主机被控、内网渗透等重大安全威胁。

    漏洞发生后,又是一次安全和相关公司的一次集体盛会,漏洞利用者在尽可能的利用此次漏洞来显示水平的高超;各大众测平台纷纷发布中招公司,来提升平台的作用;各大安全公司也充分利用此次漏洞来提高公司的影响力,借势营销,什么免费检测,第一时间升级等。还剩一大堆郁闷的厂家,我没招谁没惹谁啊;然后就是大量的苦闷的开发运维人员要连夜升级漏洞补丁。

    但是对漏洞的原理危害影响防护等少有提及。本文就是针对以上几点提出自己的见解。

原理

    这个漏洞是利用struts2的动态执行OGNL来访问任意java代码的,利用该漏洞,可以扫描远程网页,判断是否存在该类漏洞,进而发送恶意指令,实现文件上传,执行本机命令等后续攻击。

    OGNL是Object-Graph Navigation Language的缩写,全称为对象图导航语言,是一种功能强大的表达式语言,它通过简单一致的语法,可以任意存取对象的属性或者调用对象的方法,能够遍历整个对象的结构图,实现对象属性类型的转换等功能。

#、%和$符号在OGNL表达式中经常出现

1.#符号的用途一般有三种。

访问非根对象属性,例如#session.msg表达式,由于Struts 2中值栈被视为根对象,所以访问其他非根对象时,需要加#前缀;用于过滤和投影(projecting)集合,如persons.{?#this.age>25},persons.{?#this.name=='pla1'}.{age}[0];用来构造Map,例如示例中的#{'foo1':'bar1', 'foo2':'bar2'}。

2.%符号

%符号的用途是在标志的属性为字符串类型时,计算OGNL表达式的值,这个类似js中的eval,很暴力。

3.$符号主要有两个方面的用途。

在国际化资源文件中,引用OGNL表达式,例如国际化资源文件中的代码:reg.agerange=国际化资源信息:年龄必须在${min}同${max}之间; 在Struts 2框架的配置文件中引用OGNL表达式。

代码利用流程

1、客户端请求

http://{webSiteIP.webApp}:{portNum}/{vul.action}?method={malCmdStr}

2、DefaultActionProxy的DefaultActionProxy函数处理请求。

protected DefaultActionProxy(ActionInvocation inv, String namespace, String actionName, String methodName, boolean executeResult, boolean cleanupContext) {
  this.invocation = inv;
  this.cleanupContext = cleanupContext;
  LOG.debug("Creating an DefaultActionProxy for namespace [{}] and action name [{}]", namespace, actionName);
 
  this.actionName = StringEscapeUtils.escapeHtml4(actionName);
  this.namespace = namespace;
  this.executeResult = executeResult;
  //攻击者可以通过变量传递、语法补齐、字符转义等方法进行绕过。
  this.method = StringEscapeUtils.escapeEcmaScript(StringEscapeUtils.escapeHtml4(methodName));
}

3、DefaultActionMapper的DefaultActionMapper方法method方法名

String name = key.substring(ACTION_PREFIX.length());
if (allowDynamicMethodCalls) {
   int bang = name.indexOf('!');
   if (bang != -1) {
     //获取方法名
     String method = cleanupActionName(name.substring(bang + 1));
     mapping.setMethod(method);
     name = name.substring(0, bang);
  }
}

4、调用DefaultActionInvocation 的invokeAction方法执行传入的方法。

protected String invokeAction(Object action, ActionConfig actionConfig) throws Exception {
  String methodName = proxy.getMethod();
 
  LOG.debug("Executing action method = {}", methodName);
 
  String timerKey = "invokeAction: " + proxy.getActionName();
  try {
    UtilTimerStack.push(timerKey);
 
    Object methodResult;
    try {
      //执行方法
      methodResult = ognlUtil.getValue(methodName + "()", getStack().getContext(), action);
    } catch (MethodFailedException e) {

解决办法

官方的解决办法是在第三步中的函数cleanupActionName增加了校验。

protected Pattern allowedActionNames = Pattern.compile("[a-zA-Z0-9._!/\\-]*");
protected String cleanupActionName(final String rawActionName) {
  //校验,输入过滤正则匹配("[a-zA-Z0-9._!/\\-]*"),这是采取白名单方式,只允许大小军VA
g3rr4(zsr^g&7*+j:'?"[vs{j6ǖ4(
分享到 :
0 人收藏
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

积分:1060120
帖子:212021
精华:0
期权论坛 期权论坛
发布
内容

下载期权论坛手机APP