From 6b12f9a2170fbd55de9ec202c6f0301cf5d59289 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9C=A0=EC=A7=80=EC=9D=B8?= Date: Wed, 17 Jun 2026 19:03:58 +0900 Subject: [PATCH] =?UTF-8?q?=EB=AA=A8=EC=9D=98=EC=B9=A8=ED=88=AC=20?= =?UTF-8?q?=EC=A1=B0=EC=B9=98:=20=EC=9E=90=EB=8F=99=ED=99=94=20=EA=B3=B5?= =?UTF-8?q?=EA=B2=A9=20=EB=8C=80=EC=9D=91=20=EC=9A=94=EC=B2=AD=ED=9A=9F?= =?UTF-8?q?=EC=88=98=20=EC=A0=9C=ED=95=9C(Rate=20Limiting)=20=EC=B2=98?= =?UTF-8?q?=EB=A6=AC=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/community/CommunityController.java | 283 +++++++++++++++++- .../views/body/cmuboard/cmuboard_write.jsp | 25 +- 2 files changed, 301 insertions(+), 7 deletions(-) diff --git a/src/main/java/geoinfo/main/community/CommunityController.java b/src/main/java/geoinfo/main/community/CommunityController.java index d79fd22e..46ed04e2 100644 --- a/src/main/java/geoinfo/main/community/CommunityController.java +++ b/src/main/java/geoinfo/main/community/CommunityController.java @@ -14,16 +14,17 @@ import java.util.Map.Entry; import javax.annotation.Resource; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpSession; import org.apache.commons.io.FilenameUtils; import org.apache.commons.io.IOUtils; -import org.jfree.util.Log; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartRequest; import org.springframework.web.servlet.ModelAndView; @@ -106,6 +107,38 @@ public class CommunityController { return mv; } + /** + * (260616/YJI) 사이버모의침투 자동화 공격 대응 + * 15초 이내 커뮤니티 게시글 등록 제한 + * @param request + * @param response + * @return + * @throws Exception + */ + @ResponseBody + @RequestMapping(value = "/rateLimitTst.do") + public Map cmuboard_save_rateLimit(HttpServletRequest request, HttpServletResponse response) throws Exception { + Map result = new HashMap(); + + // (260615/YJI) 사이버모의침투 대응훈련 자동화 공격 관련 조치 시작 + HttpSession session = request.getSession(); + // 동일한 작성자의 15초 이내로 게시글 등록을 막는다 + Long lastWriteTime = (Long) session.getAttribute("BOARD_WRITE_TIME"); + + long now = System.currentTimeMillis(); + + if(lastWriteTime != null && now - lastWriteTime < 15000) { + result.put("code", "FAIL"); + result.put("msg", "연속된 글 등록은 제한됩니다. 잠시 후 다시 시도해 주세요."); + return result; + } + // (260615/YJI) 사이버모의침투 대응훈련 자동화 공격 관련 조치 끝 + session.setAttribute("BOARD_WRITE_TIME", now); // (260615/YJI) 사이버모의침투 대응훈련 자동화 공격 관련 조치 + + result.put("code", "SUCCESS"); + return result; + } + /* // 게시글 저장 @RequestMapping(value = "/cmuboard_save.do") public ModelAndView cmuboard_save(MultipartRequest multi, HttpServletRequest request, HttpServletResponse response, Map map) throws Exception { @@ -197,7 +230,7 @@ public class CommunityController { // String fileName = ""; /* for (int i = 1; i < 4; i++) { fileName[i] = multi.getOriginalFileName("fileName" + i); - }*/ + }* / FileCmmn fileCmmn = FileCmmn.getInstance(); boolean isFileChk = true; @@ -213,6 +246,238 @@ public class CommunityController { return mv; } + /*if (!(file_ext.equalsIgnoreCase("hwp") || file_ext.equalsIgnoreCase("pdf") || file_ext.equalsIgnoreCase("ppt") || file_ext.equalsIgnoreCase("pptx") || file_ext.equalsIgnoreCase("wmv") || file_ext.equalsIgnoreCase("txt") || file_ext.equalsIgnoreCase("exe") || file_ext.equalsIgnoreCase("zip") || file_ext.equalsIgnoreCase("jpg") || file_ext.equalsIgnoreCase("gif") || file_ext.equalsIgnoreCase("png") || file_ext.equalsIgnoreCase("doc") || file_ext.equalsIgnoreCase("docx") + || file_ext.equalsIgnoreCase("xlsx") || file_ext.equalsIgnoreCase("xls"))) { + mv.addObject("msg", ""); + fileName[i] = null; + + for (int j = 1; j < 4; j++) { + if (multi.getFile("fileName" + j) != null) + System.out.println("AAAAA" + j); + //multi.getFile("fileName" + j)..delete(); + } + return mv; + }* / + } + } + + try { + for (int i = 1; i < 4; i++) { + // fileName[i] = + // multi.getOriginalFileName("fileName"+i); + System.out.println("fileName = " + fileName[i]); + if (fileName[i] != null) { + GregorianCalendar cal = new GregorianCalendar(); + L_dat = cal.get(Calendar.YEAR) + "_" + cal.get(Calendar.MONTH) + "_" + cal.get(Calendar.DATE); + L_tmp = cal.get(Calendar.HOUR) + "_" + cal.get(Calendar.MINUTE) + "_" + cal.get(Calendar.SECOND); + saveName[i] = L_dat + "_" + L_tmp + i + fileName[i].substring(fileName[i].lastIndexOf(".")); + } else { + fileName[i] = ""; + saveName[i] = ""; + } + File up1 = new File(savePath + "/", FilenameUtils.getName(fileName[i])); + File up2 = new File(savePath + "/", FilenameUtils.getName(saveName[i])); + + if (up1.exists()) { + boolean rslt = up1.renameTo(up2); + } + } + + String fileName1 = fileName[1]; + String fileName2 = fileName[2]; + String fileName3 = fileName[3]; + String saveName1 = saveName[1]; + String saveName2 = saveName[2]; + String saveName3 = saveName[3]; + + map.put("name", name); + map.put("pass", password); + map.put("email", email); + map.put("homepage", homepage); + // 2017.10.18 dhlee 스크립트 및 특수문자 변환 + subject = RequestWrapper.cleanXSS(subject); + map.put("subject", subject); + + // 2017.10.18 dhlee 스크립트 및 특수문자 변환 + content = RequestWrapper.cleanXSS(content); + map.put("content", content); + map.put("fileName", fileName1); + map.put("saveName", saveName1); + map.put("fileName2", fileName2); + map.put("saveName2", saveName2); + map.put("fileName3", fileName3); + map.put("saveName3", saveName3); + map.put("top", 0); + + communityService.insertCmuboard(map); + affectedRows = 1; + } catch (IndexOutOfBoundsException ex) { + logger.debug("error", ex); + } catch (NumberFormatException ex) { + logger.debug("error", ex); + } catch (IOException ex) { + logger.debug("error", ex); + } catch (Exception e) { + logger.debug("error", e); + } + + if (affectedRows > 0) { + //mv.addObject("msg", ""); // 202007 삭제 + mv.addObject("msg", ""); + } else { + //mv.addObject("msg", ""); // 202007 삭제 + mv.addObject("msg", ""); + } +/* } catch (IOException e) { + System.out.println("e.getMessage() : " + e.getMessage()); + }* / + } + + return mv; + }*/ + + /** + * (260616/YJI) 사이버모의침투 자동화 공격 대응 보완된 게시글 저장 + * 15초 이내 커뮤니티 게시글 등록 제한 + * @param multi + * @param request + * @param response + * @param map + * @return code, msg + * SUCCESS: 성공, FAIL: 실패, RATE_LIMIT: 제한시간 요청횟수 초과, LOGIN_EXP: 로그인 풀림, BLOCKED_WORD: 금칙어, INVALID_FILE: 파일오류 + * BLOCKED_WORD + * @throws Exception + */ + @ResponseBody + @RequestMapping(value = "/cmuboard_save.do") + public Map cmuboard_save(MultipartRequest multi, HttpServletRequest request, HttpServletResponse response, Map map) throws Exception { +// ModelAndView mv = new ModelAndView("body/cmuboard/cmuboard_save"); + Map result = new HashMap(); + + // (260615/YJI) 사이버모의침투 대응훈련 자동화 공격 관련 조치 시작 + HttpSession session = request.getSession(); + // 동일한 작성자의 15초 이내로 게시글 등록을 막는다 + Long lastWriteTime = (Long) session.getAttribute("BOARD_WRITE_TIME"); + + long now = System.currentTimeMillis(); + + if(lastWriteTime != null && now - lastWriteTime < 15000) { + result.put("code", "RATE_LIMIT"); + result.put("msg", "연속된 글 등록은 제한됩니다. 잠시 후 다시 시도해 주세요."); + return result; + } + // (260615/YJI) 사이버모의침투 대응훈련 자동화 공격 관련 조치 끝 + if (request.getSession().getAttribute("USERID") == null) { + //mv.addObject("msg", ""); // 202007 삭제 +// mv.addObject("msg", ""); +// return mv; + result.put("code", "LOGIN_EXP"); + result.put("msg", "로그인후 등록하실 수 있습니다"); + return result; + } else { + String savePath = EgovProperties.getProperty("Geoinfo.FilePath"); + + String fileName[] = new String[4]; + String saveName[] = new String[4]; + int pos = 1; + + Map multipartFiles = multi.getFileMap(); + + for(Entry entry : multipartFiles.entrySet()) { + MultipartFile multipartFile = entry.getValue(); + if(!multipartFile.isEmpty()) { + fileName[pos] = new String(multipartFile.getOriginalFilename().getBytes()); +// fileName333[pos] = new String(multipartFile.getOriginalFilename().getBytes("8859_1"),"euc-kr"); +// fileName[pos] = new String(multipartFile.getOriginalFilename().getBytes("8859_1"),"utf-8"); + + System.out.println(entry.getKey() + " : " + fileName[pos]); + System.out.println("savePath = " + savePath); + + //웹 취약점 때문에 수정 + String file_ext = fileName[pos].substring(fileName[pos].lastIndexOf('.') + 1); // 파일확장자 + String file_name = fileName[pos].substring(0,fileName[pos].lastIndexOf('.')); // 파일확장자 + file_ext = file_ext.replaceAll("\\.", "").replaceAll("/", "").replaceAll("\\\\", "").replaceAll ("&",""); + file_name = file_name.replaceAll("\\.", "").replaceAll("/", "").replaceAll("\\\\", "").replaceAll ("&",""); + //웹 취약점 때문에 수정 23.02.14 + + String new_file = (savePath + file_name + "." + file_ext); + System.out.println(new_file); + File file = new File(new_file); + if(!file.isFile()) { + file.createNewFile(); + } + OutputStream output = new FileOutputStream(file); + IOUtils.copy(multipartFile.getInputStream(), output); + output.close(); + pos++; + } + } + + //MultipartRequest multi = null; + int fileSizeLimit = 500 * 1024 * 1024; + + //try { + //multi = new MultipartRequest(request, savePath, fileSizeLimit, "euc-kr", new DefaultFileRenamePolicy()); + + String name = (String)request.getSession().getAttribute("USERNAME"); + // String name = multi.getParameter("name"); + String password = request.getParameter("password"); + String email = request.getParameter("email"); + String homepage = request.getParameter("homepage"); + String subject = request.getParameter("subject"); + String content = request.getParameter("content"); + + // 금칙어 검증 메소드 호출 + String detected = MyUtil.checkForbiddenWords(content); + if (!detected.isEmpty()) { +// String alertMsg = "운영에 허용되지 않는 단어로 인해 게시글 등록이 실패하였습니다.\\n차단된 단어: " + detected; +// mv.addObject("msg", ""); +// return mv; + result.put("code", "BLOCKED_WORD"); + result.put("msg", "운영에 허용되지 않는 단어로 인해 게시글 등록이 실패하였습니다.\\n차단된 단어: " + detected); + return result; + } + + //String subject = new String(request.getParameter("subject").getBytes("8859_1"),"utf-8"); + //String content = new String(request.getParameter("content").getBytes("8859_1"),"utf-8"); + + name = GeoinfoCommon.strReplace(name); + password = GeoinfoCommon.strReplace(password); + email = GeoinfoCommon.strReplace(email); + subject = GeoinfoCommon.strReplace(subject); + // 특수문자열 처리(HTML태그) + + email = GeoinfoCommon.parseData(email); + homepage = GeoinfoCommon.parseData(homepage); + subject = GeoinfoCommon.parseData(subject); + content = GeoinfoCommon.parseData(content); + + int affectedRows = 0; + String L_dat = null; + String L_tmp = null; + // String saveName = ""; + // String fileName = ""; +/* for (int i = 1; i < 4; i++) { + fileName[i] = multi.getOriginalFileName("fileName" + i); + }*/ + + FileCmmn fileCmmn = FileCmmn.getInstance(); + boolean isFileChk = true; + + for (int i = 1; i < 4; i++) { + if (fileName[i] != null) { + String file_ext = fileName[i].substring(fileName[i].lastIndexOf('.') + 1); + + if ( ! fileCmmn.isZipCheck(fileName[i])) { + //mv.addObject("msg", ""); // 202007 삭제 +// mv.addObject("msg", ""); +// fileName[i] = null; +// return mv; + result.put("code", "INVALID_FILE"); + result.put("msg", "등록할 수 없는 파일입니다."); + return result; + } + /*if (!(file_ext.equalsIgnoreCase("hwp") || file_ext.equalsIgnoreCase("pdf") || file_ext.equalsIgnoreCase("ppt") || file_ext.equalsIgnoreCase("pptx") || file_ext.equalsIgnoreCase("wmv") || file_ext.equalsIgnoreCase("txt") || file_ext.equalsIgnoreCase("exe") || file_ext.equalsIgnoreCase("zip") || file_ext.equalsIgnoreCase("jpg") || file_ext.equalsIgnoreCase("gif") || file_ext.equalsIgnoreCase("png") || file_ext.equalsIgnoreCase("doc") || file_ext.equalsIgnoreCase("docx") || file_ext.equalsIgnoreCase("xlsx") || file_ext.equalsIgnoreCase("xls"))) { mv.addObject("msg", ""); @@ -290,19 +555,25 @@ public class CommunityController { if (affectedRows > 0) { //mv.addObject("msg", ""); // 202007 삭제 - mv.addObject("msg", ""); +// mv.addObject("msg", ""); + session.setAttribute("BOARD_WRITE_TIME", now); // (260615/YJI) 사이버모의침투 대응훈련 자동화 공격 관련 조치 + result.put("code", "SUCCESS"); + result.put("msg", "정상적으로 등록이 완료되었습니다."); } else { //mv.addObject("msg", ""); // 202007 삭제 - mv.addObject("msg", ""); +// mv.addObject("msg", ""); + result.put("code", "FAIL"); + result.put("msg", "오류입니다."); } /* } catch (IOException e) { System.out.println("e.getMessage() : " + e.getMessage()); }*/ } - return mv; +// return mv; + return result; } - + // 게시글 읽기 @RequestMapping(value = "/cmuboard_read.do") public ModelAndView cmuboard_read(@RequestParam Map params, HttpServletRequest request, HttpServletResponse response, @RequestParam("id") int id) throws Exception { diff --git a/src/main/webapp/WEB-INF/views/body/cmuboard/cmuboard_write.jsp b/src/main/webapp/WEB-INF/views/body/cmuboard/cmuboard_write.jsp index 2cc39cf1..69a00c15 100644 --- a/src/main/webapp/WEB-INF/views/body/cmuboard/cmuboard_write.jsp +++ b/src/main/webapp/WEB-INF/views/body/cmuboard/cmuboard_write.jsp @@ -168,8 +168,31 @@ return; } } + + + var formData = new FormData(f); + + $.ajax({ + url : "cmuboard_save.do", + type : "POST", + data : formData, + processData : false, + contentType : false, + cache : false, + success : function(res) { + if(res.code == "SUCCESS") { + alert(res.msg); + location.href = "topMenuSelect.do?url=cmuboard"; + } else { + alert(res.msg); + } + }, + error : function(xhr, status, error) { + alert("처리 중 오류가 발생하였습니다."); + console.log(error); + } + }); - f.submit(); } } // 파일업로드 확장자 체크 함수