Skip to content

Commit 7fa6d17

Browse files
committed
v15.1.0_보안취약점(KVE-2026-1558, KVE-2026-1559) 소스 수정 반영
1 parent 7e97be0 commit 7fa6d17

10 files changed

Lines changed: 143 additions & 22 deletions

File tree

eXperDB-Management-WebConsole/src/main/java/com/k4m/dx/tcontrol/admin/accesshistory/web/AccessHistoryController.java

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,10 @@ public ModelAndView selectAccessHistory(@ModelAttribute("pagingVO") PagingVO pag
141141
String search = request.getParameter("search");
142142
String order_type = request.getParameter("order_type");
143143
String order = request.getParameter("order");
144+
// 보안: 정렬컬럼/방향/검색컬럼을 화이트리스트로 강제(SQL 인젝션 방지)
145+
order_type = CmmnUtils.whiteList(order_type, "exedtm", "exedtm", "usr_id");
146+
order = CmmnUtils.whiteList(order, "desc", "asc", "desc");
147+
type = CmmnUtils.whiteList(type, "usr_nm", "usr_nm", "usr_id");
144148
String sys_cd = request.getParameter("sys_cd");
145149
String locale_type = LocaleContextHolder.getLocale().getLanguage();
146150

@@ -222,6 +226,10 @@ public List<Map<String, Object>> selectSearchAccessHistoryNew(@ModelAttribute("p
222226
String search = request.getParameter("search");
223227
String order_type = request.getParameter("order_type");
224228
String order = request.getParameter("order");
229+
// 보안: 정렬컬럼/방향/검색컬럼을 화이트리스트로 강제(SQL 인젝션 방지)
230+
order_type = CmmnUtils.whiteList(order_type, "exedtm", "exedtm", "usr_id");
231+
order = CmmnUtils.whiteList(order, "desc", "asc", "desc");
232+
type = CmmnUtils.whiteList(type, "usr_nm", "usr_nm", "usr_id");
225233
String sys_cd = request.getParameter("sys_cd");
226234
String locale_type = LocaleContextHolder.getLocale().getLanguage();
227235

@@ -286,6 +294,10 @@ public ModelAndView selectSearchAccessHistory(@ModelAttribute("pagingVO") Paging
286294
String search = request.getParameter("search");
287295
String order_type = request.getParameter("order_type");
288296
String order = request.getParameter("order");
297+
// 보안: 정렬컬럼/방향/검색컬럼을 화이트리스트로 강제(SQL 인젝션 방지)
298+
order_type = CmmnUtils.whiteList(order_type, "exedtm", "exedtm", "usr_id");
299+
order = CmmnUtils.whiteList(order, "desc", "asc", "desc");
300+
type = CmmnUtils.whiteList(type, "usr_nm", "usr_nm", "usr_id");
289301
String sys_cd = request.getParameter("sys_cd");
290302
String locale_type = LocaleContextHolder.getLocale().getLanguage();
291303

@@ -376,6 +388,10 @@ public ModelAndView accessHistory_Excel(@ModelAttribute("historyVO") HistoryVO h
376388
String order_type = request.getParameter("excel_order_type");
377389
String order = request.getParameter("excel_order");
378390
String sys_cd = request.getParameter("excel_sys_cd");
391+
// 보안: 정렬컬럼/방향/검색컬럼을 화이트리스트로 강제(SQL 인젝션 방지)
392+
order_type = CmmnUtils.whiteList(order_type, "exedtm", "exedtm", "usr_id");
393+
order = CmmnUtils.whiteList(order, "desc", "asc", "desc");
394+
type = CmmnUtils.whiteList(type, "usr_nm", "usr_nm", "usr_id");
379395
String locale_type = LocaleContextHolder.getLocale().getLanguage();
380396

381397
param.put("lgi_dtm_start", lgi_dtm_start);

eXperDB-Management-WebConsole/src/main/java/com/k4m/dx/tcontrol/admin/usermanager/web/UserManagerController.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,8 @@ public ModelAndView userManager(@ModelAttribute("historyVO") HistoryVO historyVO
125125
accessHistoryService.insertHistory(historyVO);
126126

127127
String type = request.getParameter("type");
128+
// 보안: 검색컬럼을 화이트리스트로 강제(SQL 인젝션 방지)
129+
type = CmmnUtils.whiteList(type, "usr_nm", "usr_nm", "usr_id");
128130
String search = request.getParameter("search");
129131
String use_yn = request.getParameter("use_yn");
130132
String encp_use_yn = request.getParameter("encp_use_yn");

eXperDB-Management-WebConsole/src/main/java/com/k4m/dx/tcontrol/backup/web/BackupController.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1940,7 +1940,8 @@ public List<Map<String, Object>> selectMonthBckScheduleSearch(HttpServletRequest
19401940
HashMap<String, Object> paramvalue = new HashMap<String, Object>();
19411941
paramvalue.put("datest", (String) request.getParameter("stdate"));
19421942
paramvalue.put("dateed", (String) request.getParameter("eddate"));
1943-
paramvalue.put("db_svr_id", (String) request.getParameter("db_svr_id"));
1943+
// 보안: db_svr_id는 정수만 허용하여 SQL 인젝션을 차단한다(매퍼도 #{} 바인딩 사용).
1944+
paramvalue.put("db_svr_id", Integer.parseInt(request.getParameter("db_svr_id")));
19441945

19451946
result = backupService.selectMonthBckScheduleSearch(paramvalue);
19461947
} catch (Exception e) {

eXperDB-Management-WebConsole/src/main/java/com/k4m/dx/tcontrol/cmmn/CmmnUtils.java

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,27 @@ public class CmmnUtils {
4040

4141

4242
//private ConfigurableApplicationContext context;
43+
44+
/**
45+
* SQL 인젝션 방지용 화이트리스트.
46+
* MyBatis ${} 자리(정렬컬럼/검색컬럼/정렬방향 등 바인딩이 불가한 식별자)에 들어가는 값을
47+
* 허용 목록과 대조하여, 일치하는 정규화된 값만 반환하고 그 외에는 기본값을 반환한다.
48+
*
49+
* @param value 요청으로 들어온 원본 값
50+
* @param defaultValue 허용 목록에 없을 때 사용할 안전한 기본값
51+
* @param allowed 허용되는 값 목록
52+
* @return 허용 목록 내의 정규화된 값 또는 기본값
53+
*/
54+
public static String whiteList(String value, String defaultValue, String... allowed) {
55+
if (value != null) {
56+
for (String a : allowed) {
57+
if (a.equalsIgnoreCase(value)) {
58+
return a;
59+
}
60+
}
61+
}
62+
return defaultValue;
63+
}
4364

4465
public static boolean saveHistory(HttpServletRequest request,@ModelAttribute("historyVO") HistoryVO historyVO) {
4566
HttpSession session = request.getSession();

eXperDB-Management-WebConsole/src/main/java/com/k4m/dx/tcontrol/db2pg/history/web/Db2pgHistoryController.java

Lines changed: 91 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
11
package com.k4m.dx.tcontrol.db2pg.history.web;
22

33
import java.io.File;
4+
import java.io.FileInputStream;
5+
import java.io.IOException;
46
import java.text.SimpleDateFormat;
57
import java.util.ArrayList;
68
import java.util.HashMap;
79
import java.util.List;
810
import java.util.Map;
11+
import java.util.Properties;
912

1013
import javax.servlet.http.HttpServletRequest;
1114
import javax.servlet.http.HttpServletResponse;
@@ -16,10 +19,12 @@
1619
import org.springframework.stereotype.Controller;
1720
import org.springframework.web.bind.annotation.ModelAttribute;
1821
import org.springframework.web.bind.annotation.RequestMapping;
22+
import org.springframework.util.ResourceUtils;
1923
import org.springframework.web.bind.annotation.ResponseBody;
2024
import org.springframework.web.servlet.ModelAndView;
2125

2226
import com.k4m.dx.tcontrol.admin.accesshistory.service.AccessHistoryService;
27+
import com.k4m.dx.tcontrol.admin.menuauthority.service.MenuAuthorityService;
2328
import com.k4m.dx.tcontrol.cmmn.CmmnUtils;
2429
import com.k4m.dx.tcontrol.common.service.HistoryVO;
2530
import com.k4m.dx.tcontrol.db2pg.cmmn.DB2PG_LOG;
@@ -37,7 +42,10 @@ public class Db2pgHistoryController {
3742

3843
@Autowired
3944
private AccessHistoryService accessHistoryService;
40-
45+
46+
@Autowired
47+
private MenuAuthorityService menuAuthorityService;
48+
4149
/**
4250
* DB2PG 수행이력 화면을 보여준다.
4351
*
@@ -228,7 +236,10 @@ public ModelAndView db2pgResult(@ModelAttribute("historyVO") HistoryVO historyVO
228236

229237
try {
230238
String trans_save_pth = request.getParameter("trans_save_pth");
231-
db2pgResult = DB2PG_LOG.db2pgFile(trans_save_pth);
239+
// 보안: db2pg 루트 하위 경로만 허용(경로 조작 방지)
240+
if (resolveUnderDb2pgRoot(trans_save_pth) != null) {
241+
db2pgResult = DB2PG_LOG.db2pgFile(trans_save_pth);
242+
}
232243
} catch (Exception e) {
233244
e.printStackTrace();
234245
}
@@ -261,11 +272,15 @@ public ModelAndView db2pgProgress( HttpServletRequest request) {
261272
String[] result = null;
262273
try {
263274
String trans_save_pth = request.getParameter("trans_save_pth");
264-
lines = DB2PG_LOG.readLastLine(new File(trans_save_pth+"/result/progress.txt"), 1);
275+
// 보안: db2pg 루트 하위 경로만 허용(경로 조작 방지)
276+
File progressDir = resolveUnderDb2pgRoot(trans_save_pth);
277+
if (progressDir != null) {
278+
lines = DB2PG_LOG.readLastLine(new File(progressDir, "result/progress.txt"), 1);
279+
}
265280
} catch (Exception e) {
266281
System.out.println("* cannot found progress.txt");
267282
}
268-
if(lines.size() > 0 && lines.get(0).contains(",")){
283+
if(lines != null && lines.size() > 0 && lines.get(0).contains(",")){
269284
result = lines.get(0).split(",");
270285
mv.addObject("totalcnt",result[0]);
271286
mv.addObject("nowcnt",result[1]);
@@ -332,8 +347,9 @@ public ModelAndView db2pgResultDDL(@ModelAttribute("historyVO") HistoryVO histor
332347
String pattern = "yyyy-MM-dd HH:mm:ss";
333348
SimpleDateFormat simpleDateFormat = new SimpleDateFormat(pattern);
334349

335-
File dirFile = new File(ddl_save_pth);
336-
File [] fileList = dirFile.listFiles();
350+
// 보안: db2pg 루트 하위 디렉토리만 허용(경로 조작 방지)
351+
File dirFile = resolveUnderDb2pgRoot(ddl_save_pth);
352+
File [] fileList = (dirFile != null) ? dirFile.listFiles() : null;
337353

338354
if(fileList!=null){
339355
for(int i=0; i < fileList.length; i++){
@@ -352,6 +368,32 @@ public ModelAndView db2pgResultDDL(@ModelAttribute("historyVO") HistoryVO histor
352368
return result;
353369
}
354370

371+
/**
372+
* 보안: 사용자 입력 경로가 db2pg 루트(db2pg_path) 하위인지 검증한다.
373+
* 유효하면 정규화된 File을, 루트 이탈·오류·빈값이면 null을 반환한다(경로 조작 방지).
374+
*/
375+
private File resolveUnderDb2pgRoot(String userPath) {
376+
if (userPath == null || userPath.trim().isEmpty()) {
377+
return null;
378+
}
379+
try {
380+
Properties props = new Properties();
381+
try (FileInputStream in = new FileInputStream(
382+
ResourceUtils.getFile("classpath:egovframework/tcontrolProps/globals.properties"))) {
383+
props.load(in);
384+
}
385+
File root = new File(props.getProperty("db2pg_path")).getCanonicalFile();
386+
File target = new File(userPath).getCanonicalFile();
387+
if (target.getPath().equals(root.getPath())
388+
|| target.getPath().startsWith(root.getPath() + File.separator)) {
389+
return target;
390+
}
391+
} catch (Exception e) {
392+
// 검증 실패는 거부(null)로 처리
393+
}
394+
return null;
395+
}
396+
355397
/**
356398
* DDL 수행이력 결과를 파일로 다운받는다.
357399
*
@@ -361,21 +403,54 @@ public ModelAndView db2pgResultDDL(@ModelAttribute("historyVO") HistoryVO histor
361403
@RequestMapping(value = "/db2pg/popup/db2pgFileDownload.do")
362404
public void fileDownload(HttpServletRequest request, HttpServletResponse response){
363405
try {
364-
//파일경로입력
365-
366-
System.out.println("파일경로=" +request.getParameter("path"));
367-
System.out.println("파일명=" +request.getParameter("name"));
368-
369-
String filePath = request.getParameter("path");
370-
String fileName = request.getParameter("name");
371-
String viewFileNm = request.getParameter("name");
406+
// 인가: DB2PG 수행이력(MN00017) 읽기 권한 확인
407+
CmmnUtils cu = new CmmnUtils();
408+
List<Map<String, Object>> menuAut = cu.selectMenuAut(menuAuthorityService, "MN00017");
409+
if (menuAut == null || menuAut.isEmpty() || "N".equals(menuAut.get(0).get("read_aut_yn"))) {
410+
response.sendError(HttpServletResponse.SC_FORBIDDEN);
411+
return;
412+
}
413+
414+
String reqPath = request.getParameter("path");
415+
String reqName = request.getParameter("name");
416+
if (reqPath == null || reqPath.trim().isEmpty() || reqName == null || reqName.trim().isEmpty()) {
417+
response.sendError(HttpServletResponse.SC_BAD_REQUEST);
418+
return;
419+
}
420+
421+
// 보안: 파일명은 basename만 사용하여 경로 구분자·상위경로(../)를 제거한다.
422+
String safeName = new File(reqName).getName();
423+
424+
// 보안: 다운로드 루트(db2pg_path) 하위로만 허용한다(canonical 경로 prefix 검증).
425+
Properties props = new Properties();
426+
try (FileInputStream in = new FileInputStream(
427+
ResourceUtils.getFile("classpath:egovframework/tcontrolProps/globals.properties"))) {
428+
props.load(in);
429+
}
430+
File root = new File(props.getProperty("db2pg_path")).getCanonicalFile();
431+
File target = new File(reqPath, safeName).getCanonicalFile();
432+
if (!target.getPath().equals(root.getPath())
433+
&& !target.getPath().startsWith(root.getPath() + File.separator)) {
434+
response.sendError(HttpServletResponse.SC_FORBIDDEN);
435+
return;
436+
}
437+
if (!target.exists() || !target.isFile()) {
438+
response.sendError(HttpServletResponse.SC_NOT_FOUND);
439+
return;
440+
}
441+
442+
// 검증된 경로/파일명으로만 다운로드한다.
372443
DownloadView fileDown = new DownloadView(); //파일다운로드 객체생성
373-
fileDown.filDown(request, response, filePath, fileName, viewFileNm); //파일다운로드
444+
fileDown.filDown(request, response, target.getParent() + File.separator, target.getName(), safeName); //파일다운로드
374445

375446
} catch (Exception e) {
376447
e.printStackTrace();
448+
try {
449+
response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
450+
} catch (IOException ignore) {
451+
}
377452
}
378-
}
453+
}
379454

380455
@RequestMapping(value = "/db2pg/cancel.do")
381456
public @ResponseBody JSONObject db2pgCancel(HttpServletRequest request){

eXperDB-Management-WebConsole/src/main/java/com/k4m/dx/tcontrol/functions/schedule/web/ScheduleHistoryController.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,9 @@ public List<Map<String, Object>> selectScheduleHistoryNew(@ModelAttribute("histo
158158
String exe_result = request.getParameter("exe_result");
159159
String order_type = request.getParameter("order_type");
160160
String order = request.getParameter("order");
161+
// 보안: 정렬컬럼/방향을 화이트리스트로 강제(SQL 인젝션 방지)
162+
order_type = CmmnUtils.whiteList(order_type, "wrk_strt_dtm", "wrk_strt_dtm", "wrk_end_dtm");
163+
order = CmmnUtils.whiteList(order, "desc", "asc", "desc");
161164

162165
param.put("lgi_dtm_start", lgi_dtm_start);
163166
param.put("lgi_dtm_end", lgi_dtm_end);
@@ -219,6 +222,9 @@ public ModelAndView selectScheduleHistory(@ModelAttribute("historyVO") HistoryVO
219222
String exe_result = request.getParameter("exe_result");
220223
String order_type = request.getParameter("order_type");
221224
String order = request.getParameter("order");
225+
// 보안: 정렬컬럼/방향을 화이트리스트로 강제(SQL 인젝션 방지)
226+
order_type = CmmnUtils.whiteList(order_type, "wrk_strt_dtm", "wrk_strt_dtm", "wrk_end_dtm");
227+
order = CmmnUtils.whiteList(order, "desc", "asc", "desc");
222228

223229
param.put("lgi_dtm_start", lgi_dtm_start);
224230
param.put("lgi_dtm_end", lgi_dtm_end);

eXperDB-Management-WebConsole/src/main/resources/egovframework/sqlmap/mappers/backup/backupSql.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -948,7 +948,7 @@
948948
T_SCD_M A, T_BCK_WRKCNG_I B, T_SCD_D C
949949
WHERE A.SCD_ID = C.SCD_ID
950950
AND B.WRK_ID = C.WRK_ID
951-
AND B.DB_SVR_ID = ${db_svr_id}
951+
AND B.DB_SVR_ID = #{db_svr_id}
952952
]]>
953953
</select>
954954

eXperDB-Management-WebConsole/src/main/resources/egovframework/sqlmap/mappers/db2pg/dbms/dbmsSql.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
SYS_CD,
1414
SYS_CD_NM
1515
FROM T_SYSDTL_C
16-
WHERE GRP_CD = '${dbms_dscd}'
16+
WHERE GRP_CD = #{dbms_dscd}
1717
</select>
1818

1919

eXperDB-Management-WebConsole/src/main/resources/egovframework/sqlmap/mappers/functions/schedule/scheduleSql.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1231,7 +1231,7 @@
12311231
LEFT OUTER JOIN T_BCK_WRKCNG_I B ON A.WRK_ID = B.WRK_ID
12321232
LEFT OUTER JOIN T_DBSVRIPADR_I D ON D.DB_SVR_ID = B.DB_SVR_ID
12331233
WHERE 1=1
1234-
AND A.SCD_ID = '${value}'::numeric AND B.DB_SVR_IPADR_ID = D.DB_SVR_IPADR_ID
1234+
AND A.SCD_ID = #{value}::numeric AND B.DB_SVR_IPADR_ID = D.DB_SVR_IPADR_ID
12351235
ORDER BY A.EXE_ORD
12361236
</select>
12371237
</mapper>

eXperDB-Management-WebConsole/src/main/resources/egovframework/sqlmap/mappers/scale/instanceScaleSql.xml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -229,8 +229,8 @@
229229
AND EXECUTE_TYPE = #{execute_type_cd}
230230
AND AUTO_POLICY_SET_DIV = #{auto_policy_set_div}
231231
AND USEYN = #{useyn}::bpchar
232-
AND AUTO_LEVEL = ${auto_level}
233-
AND AUTO_POLICY_TIME = ${auto_policy_time}::numeric
232+
AND AUTO_LEVEL = #{auto_level}
233+
AND AUTO_POLICY_TIME = #{auto_policy_time}::numeric
234234

235235
<if test="wrk_id != null and wrk_id != '' ">
236236
AND WRK_ID != #{wrk_id}::numeric

0 commit comments

Comments
 (0)