<설명>
웹 서비스에서 제공하는 기능을 이용하여 자동화 공격이 가능한 취약점이다. 공격자는 웹 서비스의 데이터 등록, SNS/메일 발송 등의 기능을 자동으로 수행하는 도구를 이용하여 서비스 거부, 스펨 데이터 전송 등의 가능하다.
<대응방안>
특정 시간 내 동일 프로세스를 반복 실행하지 못하도록 시간제한 설정을 하거나, 자동화 공격에 의한 시스템 과부하를 방지하기 위해 다량 패킷 유입 시 해당 접속을 차단하는 것을 권장한다. 또한, 캡챠 기법 등을 이용하여 입력값에 대한 검증을 함으로써 자동화 도구를 통한 공격을 방어해야 한다.
* 제일 최신 버전이 java6 참고
https://simplecaptcha.sourceforge.net/
1.1.1. lib 폴더에 jar 파일 추가
1.1.2. gradle 파일 수정
dependencies {
implementation project(':dev-core')
implementation files("lib/simplecaptcha-1.2.1.jar")
implementation 'org.springframework.boot:spring-boot-starter-cache'
implementation 'org.springframework.boot:spring-boot-starter-data-jdbc'
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
...
}
1.1.3. gradle reload
package com.captcha.dao;
import nl.captcha.text.producer.TextProducer;
public class CaptchaDao implements TextProducer {
private String str;
public void SetTextProducer(String getAnswer) {
this.str = getAnswer;
}
@Override
public String getText() {
return this.str;
}
}
@Slf4j
@Controller
@RequestMapping("/captcha")
public class CaptchaController {
@GetMapping
public ModelAndView Captcha(@RequestParam Map<String, Object> param) {
ModelAndView modelAndView = new ModelAndView();
modelAndView.addAllObjects(param);
modelAndView.setViewName("empty:/captchaValid");
return modelAndView;
}
@GetMapping("/captchaImg")
@ResponseBody
public void captchaImg(HttpServletRequest req, HttpServletResponse res) throws Exception {
new CaptchaService().getImageCaptcha(req, res);
}
// 사용자가 입력한 문자열 check
@PostMapping("/checkAnswer")
@ResponseBody
public String checkAnswer(HttpServletRequest req, HttpServletResponse res) {
String result = "";
Captcha captcha = (Captcha) req.getSession().getAttribute(Captcha.NAME);
String answer = req.getParameter("answer");
if(answer != null && !"".equals(answer)) {
if(captcha.isCorrect(answer)) {
req.getSession().removeAttribute(Captcha.NAME);
result = "200";
} else {
result = "400";
}
}
return result;
}
}
public class CaptchaService {
// 이미지 가로, 세로 크기
private static int width = 150;
private static int height = 50;
/* captcha 이미지 생성 */
public void getImageCaptcha(HttpServletRequest req, HttpServletResponse res) {
/* font & color */
List<Font> fontList = new ArrayList<Font>();
fontList.add(new Font("", Font.HANGING_BASELINE, 40));
fontList.add(new Font("Courier", Font.ITALIC, 40));
fontList.add(new Font("", Font.PLAIN, 40));
List<Color> colorList = new ArrayList<Color>();
colorList.add(Color.BLACK);
Captcha captcha = new Captcha.Builder(width, height)
.addText(new NumbersAnswerProducer(6), new DefaultWordRenderer(colorList, fontList))
.addNoise().addBorder()
.addBackground(new GradiatedBackgroundProducer())
.build();
/* session 객체에 저장 */
req.getSession().setAttribute(Captcha.NAME, captcha);
CaptchaServletUtil.writeImage(res, captcha.getImage());
}
}
<%@ page contentType="text/html;charset=UTF-8" language="java"%>
<%@ taglib uri="http://tiles.apache.org/tags-tiles" prefix="tiles"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<section class="join-contents is-small has-large-gap-top">
<div class="join-completed-wrap">
<div class="join-completed-contents">
<svg class="icon is-blue-check is-medium"><use href="#check_blue"></use></svg>
<h2 class="page-title is-large">자동 입력 방지</h2>
<div style="overflow: hidden">
<div>
<img id="captchaImg" title="CaptchaImg" src="">
</div>
</div>
<div style="padding:3px">
<input id="reload" type="button" onclick="javaScript:getImage()" value="새로고침">
</div>
<div style="padding:3px">
<input id="answer" type="text" value="">
<input id="check" type="button" value="확인">
</div>
</div>
</div>
</section>
<script type="text/javascript">
$(document).ready(function (e) {
getImage();
});
function getImage() {
let rand = Math.random();
let url = "/captcha/captchaImg?rand=" + rand;
$("#captchaImg").attr('src', url);
}
$(document).on('click', '#check', function (e) {
$.ajax({
url : '/captcha/checkAnswer',
type : 'POST',
async: false,
data: {answer: $("#answer").val()}
}).done(function(result) {
if(result == 200) {
let gbCd = `${gbCd}`;
let queryString = null;
if(gbCd == "T1") {
queryString = `busiNo=${busiNo}&personNm=${personNm}&personEmail=${personEmail}`;
$.ajax({
url : '/company/getFindId',
type : 'POST',
data: queryString
}).done(function(result) {
if(!result || !Object.keys(result).length){
goPost('/company/findFail','type','idFail');
} else {
goPost('/company/findComplete','loginId',result.email);
}
}).fail(function(jqXHR) {
goPost('/company/findFail','type','idFail');
return;
});
} else if(gbCd == "T2") {
let queryString = `findLoginId=${findLoginId}&busiNo=${busiNo}&personNm=${personNm}&personEmail=${personEmail}`;
$.ajax({
url : '/company/getFindPwd',
type : 'GET',
data: queryString
}).done(function(result) {
if(!result || !Object.keys(result).length){
goPost('/company/findFail','type','pwdFail');
} else {
goPost('/company/findComplete','email',result.email);
}
}).fail(function(jqXHR) {
goPost('/company/findFail','type','pwdFail');
return;
});
} else {
alert('오류가 발생했습니다.');
return;
}
} else {
alert('입력값이 일치하지 않습니다.');
getImage();
$("#answer").val("");
}
});
});
function goPost(action,name,val){
let form = document.createElement('form');
let objs;
objs = document.createElement('input');
objs.setAttribute('type', 'hidden');
objs.setAttribute('name', name);
objs.setAttribute('value', val);
form.appendChild(objs);
form.setAttribute('method','post');
form.setAttribute('action', action);
document.body.appendChild(form);
form.submit();
}
</script>