Struts2-s01漏洞复现分析

漏洞描述

该漏洞因为用户提交表单数据并且验证失败时,后端会将用户之前提交的参数值使用 OGNL 表达式 %{value} 进行解析,然后重新填充到对应的表单数据中。例如注册或登录页面,提交失败后端一般会默认返回之前提交的数据,由于后端使用 %{value} 对提交的数据执行了一次 OGNL 表达式解析,所以可以直接构造 Payload 进行命令执行。

受影响版本

WebWork 2.1 (with altSyntax enabled), WebWork 2.2.0 - WebWork 2.2.5, Struts 2.0.0 - Struts 2.0.8

复现环境

maven(struts-2.0.8)+tomcat7

漏洞缺陷位置

TextParseUtil.translateVariables

  1. public static Object translateVariables(char open, String expression, ValueStack stack, Class asType, ParsedValueEvaluator evaluator) {
  2. // deal with the "pure" expressions first!
  3. //expression = expression.trim();
  4. Object result = expression;
  5. //漏洞根本原因,未设置合理退出条件,导致ognl表达式递归解析
  6. while (true) {
  7. int start = expression.indexOf(open + "{");
  8. int length = expression.length();
  9. int x = start + 2;
  10. int end;
  11. char c;
  12. int count = 1;
  13. while (start != -1 && x < length && count != 0) {
  14. c = expression.charAt(x++);
  15. if (c == '{') {
  16. count++;
  17. } else if (c == '}') {
  18. count--;
  19. }
  20. }
  21. end = x - 1;
  22. //该if条件是唯一退出循环条件
  23. if ((start != -1) && (end != -1) && (count == 0)) {
  24. String var = expression.substring(start + 2, end);
  25. Object o = stack.findValue(var, asType);
  26. if (evaluator != null) {
  27. o = evaluator.evaluate(o);
  28. }
  29. String left = expression.substring(0, start);
  30. String right = expression.substring(end + 1);
  31. if (o != null) {
  32. if (TextUtils.stringSet(left)) {
  33. result = left + o;
  34. } else {
  35. result = o;
  36. }
  37. if (TextUtils.stringSet(right)) {
  38. result = result + right;
  39. }
  40. expression = left + o + right;
  41. } else {
  42. // the variable doesn't exist, so don't display anything
  43. result = left + right;
  44. expression = left + right;
  45. }
  46. } else {
  47. break;
  48. }
  49. }
  50. return XWorkConverter.getInstance().convertValue(stack.getContext(), result, asType);
  51. }

审计分析

流程分析

ognl

该ognl流程图对应漏洞缺陷行位置贴图代码块

代码分析及漏洞复现

漏洞核心

  1. struts2存在ognl递归解析缺陷
  2. ognl能执行代码

这个是ognl简单实用的测试代码

  1. OgnlContext context = new OgnlContext();
  2. Object obj = Ognl.getValue("'helloworld'.length()",context);
  3. System.out.println(obj);
  4. Object obj1 = Ognl.getValue("@java.lang.String@format('foo %s','bar')",context);
  5. System.out.println(obj1);
  6. Object obj2 = Ognl.getValue("@java.lang.Runtime@getRuntime().exec('open /Applications/Calculator.app/')",context);

该调用栈实现逻辑和上面的测试代码逻辑相同

Struts2-s01漏洞复现分析 - 图2

断点设置(重要)
com.opensymphony.xwork2.DefaultActionProxy

  • resultCode = interceptor.getInterceptor().intercept(DefaultActionInvocation.this);
  • result.execute(this);

com.opensymphony.xwork2.util.TextParseUtil

  • Object result = expression;
  • Object o = stack.findValue(var, asType);

org.apache.struts2.dispatcher.ServletDispatcherResult

  • dispatcher.forward(request, response);

org.apache.struts2.components.UIBean

  • expr = "%{" + name + "}";

断点设置好后开始观察数据流

  1. 使用poc进行测试
    Struts2-s01漏洞复现分析 - 图3
  1. 可以看到数据流首先经过很多过滤器
    Struts2-s01漏洞复现分析 - 图4
  1. 经过层层过滤器后进入到我们自己编写的代码位置,可以看到%{1+1}已经传递过来了
    Struts2-s01漏洞复现分析 - 图5
  1. 根据execute方法逻辑可知返回ERROR,进入下一个断点,可以看到resultCode=error,再根据我们自己配置的struts.xml可以看到返回error则转发到index.jsp页面
    Struts2-s01漏洞复现分析 - 图6
    Struts2-s01漏洞复现分析 - 图7
    Struts2-s01漏洞复现分析 - 图8
  1. 继续执行下一个断点,等到expression等于username,username是前端传递给后端的值的key,ongl引擎对其使用%{}包裹然后对其解析
    Struts2-s01漏洞复现分析 - 图9
    Struts2-s01漏洞复现分析 - 图10
  1. 在跟踪到下一个断点,进入到ognl解析逻辑,可以看到根据前端传递的username字段解析获取到传递过来的值%{1+1}
    Struts2-s01漏洞复现分析 - 图11
    Struts2-s01漏洞复现分析 - 图12
  1. 继续执行,发现%{1+1}被ognl继续解析,造成ognl表达式注入
    Struts2-s01漏洞复现分析 - 图13
  1. 最后可以看到最终返回的数据是2
    Struts2-s01漏洞复现分析 - 图14