그곰의 생활

[스크랩]JSP 자동등록 방지 본문

Server-side/Lang

[스크랩]JSP 자동등록 방지

그곰 2011. 11. 17. 13:04
요즘 공개된 게시판 등에는 프로그램(로봇)에 의해 자동 등록된 광고물들을 흔히 볼 수 있는데, 이것은 보는 사람의 눈살을 찌푸리게 만들고, 사이트 운영자의 관리 부담도 가중시킵니다.  그래서 이를 막기위한 알고리즘을 구상해 보았습니다.

자동등록 방식중 가장 일반적인 형태는 먼저 로봇을 이용해 게시판의 글쓰기 폼을 찾아 제목, 본문 등의 필드 이름들과 action url 등을 찾아 DB화 시켜두었다가, 고객의 요청(?)이 있을때 광고 내용을 필드에 맞게 구성해 action url로 바로 POST로 날리는 방법을 사용하고 있습니다.

따라서 프로그램이 실제 광고글을 등록하고 다닐때는 글쓰기 폼을 거치지 않고 바로 action script로 데이터를 날리게 됩니다.  이 원리를 이용해 action script에서는 정상적으로 글쓰기 폼을 거쳐왔는지만 확인하면 프로그램에 의한 광고글을 상당부분 차단시킬 수 있습니다.

action script에서 정상적으로 글쓰기 폼을 거쳐온 데이터인지의 여부를 확인하는 방법에는 여러가지가 있는데, 가장 쉬운 방법이 HTTP_REFERER 환경 변수를 확인하는 방법이 있습니다.  그러나 가장 쉬운 방법인만큼 프로그램쪽에서도 가장 쉽게 피해갈 수 있습니다.  (그러나 이런 단순한 로직으로도 효과적으로 광고글을 막을 수 있습니다. 단, 구형 브라우져중 일부는 HTTP_REFERER를 보내지 않는 브라우져도 있으니 호환성 문제가 대두됩니다)

그리고 JavaScript 방식이 있는데, 예를들어 agent라는 히든 필드를 만들고 폼의 submit 버튼을 누르는 순간 JavaScript로 브라우져의 User Agent 정보를 읽어와 이 필드에 값을 넣어둡니다.  그 후 action script에서는 agent 필드를 통해 넘어온 값과 HTTP_USER_AGENT 환경 변수의 값을 비교해봐서 두 값이 틀리면 글 등록을 막게됩니다.  따라서 정상적으로 폼을 거쳐오지 않으면 글을 등록할 수 없습니다.  실제 광고글이 많이 올라오는 사이트에 적용시켜본 결과 상당한 효과가 있었습니다.  그러나 이 방법 역시 주요 로직이 클라이언트 측에서 이뤄지므로, 마음만 먹으면 쉽게 뚫을 수 있습니다.

또 쿠키 방식이 있는데 글쓰기 폼에서 특정한 쿠키를 보낸 후 action script에서 쿠키의 존재 유무에 따라 글등록 작업을 수행하는 방식입니다.  이 방식 역시 효과는 좋지만, 문제는 IE6의 개인정보보호 기능으로 인해 일부 환경에서 쿠키가 거부되거나 사용자가 쿠키를 받지 않게 설정해둔 경우에는 정상적인 글도 등록이 되지 않는 문제가 발생할 소지가 있습니다.

이에 따라 아무래도 클라이언트보다는 서버쪽에 의존하는 방안이 가장 효과적이고 문제도 없을 것이라고 생각합니다.  서버에서 처리해주면 단순히 소스에 보이는 html이나 javascript 코드만 분석해서는 뚫을 수 없고, 쿠키와 같이 정상적인 글이 등록되지 않는 문제가 발생하는 것도 방지할 수 있습니다.

< 사용예 >

일단 글쓰기 폼에서는 임의의 히든필드(아래 예에서는 validation이라는 이름을 사용)의 히든필드를 만들고, 그 값은 다음과 같이 생성해줍니다.

<input type="hidden" name="validation" 
value="<%=MD5 (YYYYMMDDHH + CoCode + Remote IP + PASSWORD)%>">

그리고 폼데이터를 POST로 받아 저장하는 부분에서도 다음과 같은 MD5 해쉬 문자열을 만듭니다.

hash1 = MD5(YYYYMMDDHH + CoCode + Remote IP + PASSWORD);
hash2 = MD5(YYYYMMDDHH-1 + CoCode + Remote IP + PASSWORD);

최종적으로 Form에서 넘어온 validation 값이 hash1 또는 hash2와 일치를 하게되면 정당한 글 등록으로 판단해 실제로 글을 저장하고, 그렇지 않으면 프로그램에 의한 등록으로 간주하고 튕겨내면 됩니다.

간략하게 설명드리자면, MD5는 MD5 hashing을 수행하는 함수이며 YYYYMMDDHH는 시스템의 현재 시간을 [연월일시] 까지만 문자열로 넣어둔 것입니다.  (예: 2003052315)

그리고 PASSWORD는 뚫리는 것을 방지하기 위해 넣는 임의의 문자열로서 폼페이지와 처리페이지가 동일한 값만 하드코딩으로 유지하면 됩니다.  (MD5는 역변환이 불가능하므로, 알고리즘을 알아내더라도 정작 PASSWORD 값을 알지못한다면, 등록기에서 정당한 글 등록자로 위장해 글을 등록할 수 있는 방법이 없을 것입니다)

이제 폼에서 넘어온 validation 값과 action script에서 계산한 hash1 값이 일치하면 정당한 사용자라 볼 수 있습니다. 

그러나 만약 누군가 9시 59분경에 폼을 열어서 글쓰기를 시작해 10시 5분경에 [저장] 버튼을 눌렀을 경우에는 폼에서의 [연월일시] 시간과 action script에서의 [연월일시] 시간이 서로 달라져 글저장이 안될 것입니다. 따라서 hash2에 현재로부터 1시간전에 해당하는 Hash값도 함께 계산해서 폼에서 넘어온 validation과 hash1 또는 hash2 둘 중의 하나가 일치하면 Pass시켜주는 것입니다.

단, 주의하실 점은 hash2의 ([연월일시] - 1)을 계산하실때는 단순히 시간에서 1만 빼게 되면, 매일 자정쯤이나 그 달의 말일 자정, 그 해의 마지막날의 자정쯤에 등록되는 글에는 일 또는 월, 연도 때문에 문제가 발생할 수 있으므로, 이 점까지 감안하여 계산을 해야 할 것 입니다. (Calendar 객체의 add 메소드 사용 등)

< 샘플코드 >

1. Form 페이지에서 Validation Code값 구하기

StringBuffer sb = new StringBuffer();
String dateVal = new SimpleDateFormat("yyyyMMddHH").format( new Date() );
sb.append( dateVal );
sb.append( coCode );
sb.append( request.getRemoteAddr() );
sb.append( "Korean Marketplace - Ace of Global Korean Marketplace");  // 패스워드
String validation = SmeString.toMD5(sb.toString());
...
...
<input type="hidden" name="validation" value="<%=validation%>">
...

2. 폼처리 프로그램에서 hash1 구하기 : 위 코드와 동일

3. 폼처리 프로그램에서 hash2 값 구하기

StringBuffer sb = new StringBuffer();
Calendar cl = Calendar.getInstance();
cl.add(Calendar.HOUR_OF_DAY, -1); // 한시간전 시간 구하기
String dateVal = new SimpleDateFormat("yyyyMMddHH").format( cl.getTime() );
sb.append( dateVal );
sb.append( coCode );
sb.append( request.getRemoteAddr() );
sb.append( "Korean Marketplace - Ace of Global Korean Marketplace");  // 패스워드
String hash2 = SmeString.toMD5(sb.toString());

if ( !hash1.equals(validation) && !hash2.equals(validation) ) {
    return;
}
Comments