JCF3.0™ 유효성체크 적용 가이드

From JCFWiKi

Jump to: navigation, search

그림:check.gif

  • 산출물 : JCF3.0™ 유효성체크 적용 가이드
  • 작성자: 서경진
  • 작성일 : 2007/12/12
  • 버전 : 0.9
  • 개정이력 :

Copyright © 2007 Daewoo Information Systems Co., Ltd.

목차

[편집] 유효성체크 개요

유효성체크는 시스템, 어플리케이션, 컴포넌트, 클래스 등에 입력되는 입력값에 대한 유효성을 검증하는 과정을 말한다. 그리고 유효성이란 요구사항이나 표준,정책을 기반으로 특정 데이터의 크기, 유형, 범위, 문자셋 등을 사전에 정의하고 입력된 값이 적절한지를 판단하는 것이다. 따라서 유효성이 검증된 적절한 입력값만이 각 구성요소의 내부 로직에서 사용되도록 하는 것이 시스템의 안정성, 보안성, 무결성을 유지하는데 필수불가결하다고 할 수 있다. 일반적으로 유효성체크는 다음의 네 가지 사항에 대하여 검증을 수행하게 된다.

  • 유효성: 사전에 정의된 값인지 확인
  • 무결성: 최초의 값에서 변조되거나 변경된 값인지 확인
  • 안전성: 시스템에 악영향을 미지는 값인지 확인
  • 보안성: 보안상 취약한 값인지 확인

만약 이러한 유효성체크를 과도하게 적용하는 경우에는 시스템 성능에 악영향을 미칠 수 있다. 따라서 유효성체크를 쉽고 간단하며 빠르고 정확하게 할 수 있는 방법을 무엇일까? 유효성체크 방법에 대한 해결책을 찾아내기 전에 JCF3.0 아키텍처에서 적용하고자 하는 유효성체크 구조에 대해 간단히 살펴보도록 하겠다.

  • JCF3.0 유효성체크 구조

아키텍처상의 각 tier별로 다음과 같은 유효성체크가 수행되도록 JCF3.0 기반의 어플리케이션을 구성한다.

클라이언트의 브라우저에서 Javascript로 유효성체크를 수행하지만 클라이언트에서의 유효성체크는 절대 신뢰하지 않는다. 따라서 서버에서 각 tier별로 입력되는 값이나 파라미터에 대한 유효성체크를 철저하게 수행하기 위해 유효성체크 유틸클래스를 구성하고 AOP를 적용하여 일관되고 효과적인 아키텍처를 구성하는 것이 필요하다.

[편집] 정규식(Regular Expression)이란?

유효성체크와 같이 입력값에 대한 다양한 검증을 효과적으로 수행하기 위해서 정규식이라는 것을 적용하게 된다. 정규식은 다음과 같이 설명할 수 있다.

그림:check.gif

정규식(Regular Expression)이란 어떠한 언어를 일련의 문자,숫자나 기호로 표현할 수 있는 문자식을 의미한다. 다시 말하면, 문자열의 패턴을 표현할 수 있는 것이다. 따라서 정규식은 문자열의 패턴매칭(Pattern matching)을 위하여 사용할 수 있다. 즉, 정규식을 사용하여 문자패턴을 표현하고 어떤 문자열이 그 패턴과 동일한지 알 수 있도록 해주는 역할을 한다.

[편집] 왜 유효성체크에 정규식을 적용하는가?

  • 문자처리에 대한 자바언어의 비효율성

자바는 이전의 프로그래밍 언어에 비해 개발이 용이하여 특히 웹 개발에 많이 이용되고 있으며, 현재 기업용 솔루션 구현에 많이 사용되고 많은 인기를 누리고 있다. 그러나, 자바는 고급언어이므로 특정 작업을 세세히 구현하는 기능을 모두 제공하지는 않는다. 그런 취약점 중의 하나가 바로 자바의 문자처리 능력이다. 자바의 문자처리 능력은 한마디로 빈약하다. String 클래스가 어느 정도 그러한 기능을 구현하긴 하지만 강력한 기능을 발휘하기엔 턱없이 부족하다. 그러나, 자바로 개발하다 보면 강력한 문자처리 능력이 필요할 쓰일때가 있다. 예를 들어 HTML 문서의 폼(form)에서 사용자가 주민등록번호, 생년월일, 또는 전화번호를 입력하였을 경우 어떤 특정한 형태로 입력하라고 쓰여져 있는 웹페이지들을 많이 보았을 것이다. 그런데 사용자에게 일일이 개발자가 원하는 형태로 정보를 입력하라고 하는 것보다는 개발자가 직접 사용자가 입력한 정보에서 자기가 원하는 것을 추려서 사용하는 것이 사용자에게 불편함을 덜어줄 수 있고 많은 에러들을 피할 수 있다.

  • 정규식을 통한 자바의 문자처리 문제 해결

정규식을 사용하면 사용자가 어떤 형태의 데이터를 입력하든지 개발자가 원하는 형태로 사용할 수 있다. 그러므로, 특정한 형태의 데이터를 입력하라고 사용자에게 요구하는 것보다 정규식을 사용하는 것이 더욱 효율적이고 강력하며 세련된 프로그래밍이라고 할 수 있다. 이 외에 웹 환경에서뿐만 아니라 자바로 순수 어플리케이션을 개발하는 경우에도 문자처리 능력이 긴요하게 쓰일 곳은 얼마든지 있다. 따라서 위에서 언급한 정규식은 이러한 패턴매칭, 파싱, 그리고 문서처리에 아주 효과적이다.

[편집] Apache Jakarta RE 클래스 소개

  • Jakarta 정규식 패키지

현재 Jakarta 프로젝트에서 주도하여 개발하고 있는 정규식 패키지는 Regexp 패키지와 ORO 패키지 두 가지가 있다. 이중 ORO 패키지는 Perl과 똑같은 패턴매칭 기능은 물론 Perl의 다른 명령어(예를 들어 s///)까지 구현하고 있으며, 일반적인 문서처리(text processing) 기능까지 포함하므로 사용법이 RE 패키지보다 훨씬 더 복잡하다. 유효성체크와 같이 패턴매칭을 중심으로 사용할 경우 ORO 패키지는 배보다 배꼽이 더 큰 경우가 될 수 있으므로 Regexp 패키지를 적용하는 것이 더 좋다고 할 수 있다.

  • 패턴매칭을 위한 Regexp 패키지

Jakarta Regexp 패키지는 효과적인 유효성체크를 위해 정규식을 기반으로 문자열에 대한 유효성을 쉽고 간단하게 검증할 수 있는 수단(클래스)을 제공한다. 이와 같이 사용법이 간편한 Regexp 패키지에 대해 알아보고 설치방법과 사용법을 살펴보도록 하겠다.

  • 설치방법
    • 다운로드 받기
    • 설치
    • 프로그램 사용하기

일단 Jakarta 프로젝트의 홈페이지에 있는 Regexp 홈페이지에서 다음의 링크밑에 있는 Regexp 패키지를 다운받는다.

http://jakarta.apache.org/site/downloads/downloads_regexp.cgi 사이트에서 파일은 zip과 tar.gz 중 적절한 포맷으로 다운받으면 된다. Eclipse와 같은 IDE 도구를 사용한다면 Regrep 패키지를 설치하는 것은 간단하다. 압축된 파일을 임의의 디렉터리에 풀고 jakarta-regexp-1.5.jar 파일을 복사하여 해당 어플리케이션의 라이브러리를 보관하는 디렉터리에 붙여넣는다. 그리고 buildpath에 추가하면 Regexp 패키지를 사용할 준비가 된 것이다.

위에서처럼 설치가 됐으면 이제 프로그램에서 사용하는 것만 남았다. 자바의 import 명령어를 이용하여 사용하고 싶은 프로그램 시작부분에 다음과 같이 패키지를 추가시킨다.

import org.apache.regexp.*;

이제 Regexp 패키지를 사용할 수 있게 되었다. 지금부터는 실제로 프로그램에서 Regexp 패키지를 사용하는 방법과 용도를 알아볼 것이다. 그전에 일단 정규식의 사용방법부터 배워보도록 하겠다.

[편집] 정규식 사용방법

  • 객체

정규식의 객체 중에 가장 중요한 것들만 설명하도록 하겠다.


문자


• 유니코드 문자 - 유니코드 문자는 문자 그대로 표현한다. 즉, a는 문자열의 a와 매치된다.
• \ (backslash) - 정규식에 사용되는 특수문자, 예를 들어 * 나 \ 등을 매치할때 사용한다. 즉, * 를 매치하려면 \* 라고 써줘야한다.
• \t - 탭 문자와 매치된다.
• \n - 새줄문자와 매치된다.
• \r - 리턴문자와 매치된다.
• \f - form feed문자와 매치된다.

예를 들어 문자열 "a\bc*"와 매칭을 하고 싶다면 정규식은 "a\\bc\*"와 같이 사용한다. \와 *가 모두 정규식에 쓰이는 특수문자들이므로 앞에 \을 붙여주고 나머지는 문자 그대로 사용한다.


문자클래스


• [abc] - 제시된 단순 문자와 매치된다.
• [a-zA-Z] - a에서 z까지, 그리고 A에서 Z사이에 있는 모든 문자와 매치된다.
• [^a-zA-Z] - 위와 반대다. 즉 a에서 z까지, A에서 Z까지 사이에 없으면 매치된다.

만약 소문자와 매치하려면 [a-z]를 사용하거나 [^A-Z]를 사용하면 된다. 숫자도 마찬가지다. [0-9]를 사용하면 숫자와 매치되고 숫자가 아닌 것과 매치하려면 [^0-9]를 사용하면 된다.


사전에 정의된 클래스


• . - 새줄문자 이외의 모든것
• \w - 알파벳과 _ (영어단어에 쓰이는 문자)
• \W - 알파벳과 _이 아닌 것
• \s - 빈 공간
• \S - 빈 공간이 아닌 것
• \d - 숫자
• \D - 숫자가 아닌 것

예를 들어 "1a 2a" 또는 "3g 9i" 등의 문자열과 매치하고 싶을 땐 "\d\S\s\d\S"와 같이 정규식을 써주면 된다.


표준 POSIX 문자 클래스


• [:alnum:] - Alphanumeric characters.
• [:alpha:] - Alphabetic characters.
• [:blank:] - Space and tab characters.
• [:cntrl:] - Control characters.
• [:digit:] - Numeric characters.
• [:graph:] - Characters that are printable and are also visible. (A space is printable, but not visible, while an `a' is both.)
• [:lower:] - Lower-case alphabetic characters.
• [:print:] - Printable characters (characters that are not control characters.)
• [:punct:] - Punctuation characters (characters that are not letter, digits, control characters, or space characters).
• [:space:] - Space characters (such as space, tab, and formfeed, to name a few).
• [:upper:] - Upper-case alphabetic characters.
• [:xdigit:] - Characters that are hexadecimal digits.

비표준 POSIX 스타일 문자 클래스


• [:javastart:] - Start of a Java identifier.
• [:javapart:] - Part of a Java identifier.

단어나 줄의 경계를 매치할때


• ^ - 줄의 맨앞과 매치된다.
• $ - 줄의 맨끝과 매치된다.
• \b - 단어와 단어의 경계와 매치된다.

예를 들어 한 줄에 "aaaaa"라는 문자열만 있는 줄을 매치하고 싶으면 "^aaaaa$"와 같이 정규식을 써주면 된다. 만약 다른 문자열 속에 포함돼있는 "aaaaa"를 매치하고 싶다면 (예를 들어 "bbbbbaaaaabbbbb") 그냥 "aaaaa"만 해줘야 매치가 된다.


그 이외의 것들


• A|B - A 또는 B와 매치된다.
• (A) - A와 매치한것을 나중에 다시 사용할 때 쓴다.

어떤 문자열에서 매치한 부분을 나중에 다시 사용하고 싶을 때는 괄호를 사용한다. 예를 들어 사용자가 입력한 전화번호를 정규식을 사용하여 패턴매칭을 했다고 하자. 이렇게 매치된 전화번호를 데이터베이스에 저장하고 싶을 때 바로 정규식 안에서 괄호를 이용하여 매칭을 한다. 괄호 안에 매칭된 부분은 나중에 다시 사용하는 것이 가능하기 때문이다. 이것은 굉장히 중요하므로 나중에 예제로 다시 설명하겠다.


매치된 객체의 재사용


• \1 - 첫번째 괄호에 매치된 부분
• \2 - 두번째 괄호에 매치된 부분
• 세번째는 $3, 네번째는 $4 등으로 사용하면 됨

정규식 안에서 괄호에 매치된 부분을 다시 사용하고 싶을 때 쓰는 객체들이다. 예를 들어 "1 - 1" 처럼 처음과 나중의 숫자가 같은 문자열만 매칭하고 싶을 경우, "(\d+) \- \1"과 같은 정규식을 사용하면 간단하게 매칭할 수 있다. 여기서 \1 은 그 앞에서 \d+ 로 매치된 숫자를 가리킨다.


수량


수량은 각 객체가 문자열 안에서 몇 번이나 나타나는지 그 회수를 정해준다. 아마 많은 독자들이 윗부분을 보면서 정규식은 문자를 한 번에 한 개밖에 매치하지 못하나 하고 의문을 품을지 모르겠다. 그러나, 바로 밑에 나열한 것들로 수량을 정해줄 수 있다. 일단 그것들을 보고 예제를 통해서 사용 방법을 살펴보자.

• A* - A를 0번, 아니면 그 이상 매치한다.
• A+ - A를 한번, 아니면 그 이상 매치한다.
• A? - A를 0번, 아니면 한번만 매치한다.
• A{n} - A를 정확히 n번 매치한다.
• A{n,} - A를 n번 이상 매치한다.
• A{n,m} - A를 최소한 n번, 하지만 m번 이하로 매치한다.

[편집] Regexp 패키지 사용방법

  • 클래스 목록

일단 Regexp 패키지에서 가장 많이 사용되는 클래스는 다음과 같다.

• RE
• RECompiler
• REProgram
• RESyntaxException


RE 클래스


RE 클래스가 Regexp 패키지의 중심 클래스이며 이 클래스를 이용하여 패턴매칭을 한다. 다음은 생일날짜의 문자열을 패턴매칭하는 예제이다.

// 패키지를 import 해야 한다.
import org.apache.regexp.*;
 
// 다음과 같은 문자열을 사용자가 입력한 데이터라고 가정한다.
String bday = new String("54/4/27");
 
// 다음과 같이 정규식 객체를 생성시킨다. 인자는 정규식이다. 
// 옵션도 있지만 특별한 경우에만 사용하므로 여기선 생략한다. 
RE bday_pattern = new RE("([\\d{2}|\\d{4}])/(\\d{1,2})/(\\d{1,2})");
 
if (bday_pattern.match(bday)) {
    // 매치가 됐다. 그럼 매치된 문자들을 다시 불러오자.
    // 첫번째 괄호가 년도이다.
    String year = bday_pattern.getParen(1);
    // 두번째 괄호가 월, 그리고 세번째가 날짜이다.
    String month = bday_pattern.getParen(2);
    String day = bday_pattern.getParen(3);
 
    // 숫자들을 모두 추려냈으므로 다른 형태로 출력해보자.
    System.out.println(year + "년 " + month + "월 " + day + "일 ");
}

여기서 한가지 주의해야 할것은 RE 클래스의 생성자를 사용할 때 정규식이 "" 안에 들어간다는 사실이다. 정규식 자체가 문자화되기 때문에 만약 정규식 안에서 \ 를 사용하려면 \\ 를 대신 써야만 한다. System.out.println() 함수를 사용할때 만약 \ 를 직접 화면에 출력하고 싶으면 "\\" 라고 해야 하는 것과 같은 이치다. 그러므로 정규식에 \ 문자가 들어갈 때는 항상 두 개씩 써준다. 일단 정규식을 써준뒤 \가 들어가는곳에 \를 하나씩 더 추가할것을 필자는 권한다. 위의 예제에서 볼 수 있듯이 RE 클래스의 사용방법은 대단히 간단하다. 일단 생성자로 객체를 만든 다음, match() 함수를 통해서 패턴이 맞는지 틀린지를 가려낸다. 만약 패턴매칭이 됐다면 그 다음엔 getParen() 함수를 통해서 매치된 문자들을 추려낼 수 있다. 그 다음 처리는 물론 개발자에게 달려있다. 만약 실제로 위의 예제를 실행시키면 에러가 날것이다. RE 클래스의 객체를 생성할때 RESyntaxException 예외가 발생할 수 있는데 그것을 잡아줘야만 한다. 예외에 대해선 뒤에서 설명하겠다.


RECompiler 와 REProgram


정규식은 사용하기 전에 먼저 컴파일 해서 기계가 알아볼 수 있는 코드로 변환시켜야 한다. 하지만 RE 클래스를 사용하면 프로그램의 실행중에 자동으로 컴파일이 되므로 위의 예제에선 문제가 없다. 하지만 아무래도 실행 중에 다수의 복잡한 정규식을 컴파일 하려고 하면 프로그램의 성능에 영향을 미치게 된다. 그러므로 프로그램의 성능에 문제가 발생할 수 있으므로 정규식을 미리 컴파일 할 수 있다. 그것을 가능하게 해주는 것이 RECompiler 클래스이다. RECompiler 클래스로 정규식을 컴파일 하면 REProgram 의 객체가 나오는데 이 REProgram의 객체를 RE 클래스의 생성자의 인자로 사용해 주면 된다. 앞의 예제를 이용하여 이번엔 정규식을 미리 컴파일하도록 하자.

// 패키지를 import 해야 한다.
import org.apache.regexp.*;
 
String bday = new String("54/4/27");
 
// 정규식을 RECompiler 클래스를 이용하여 컴파일 한다.
RECompiler comp = new RECompiler("([\\d{2}|\\d{4}])/(\\d{1,2})/(\\d{1,2})");
 
// RECompiler 클래스의 compile() 함수를 이용하여 결과로 나오는 
// REProgram 을 곧바로 RE 클래스 생성자의 인자로 사용한다.
RE bday_pattern = new RE(comp.compile());
 
if (bday_pattern.match(bday)) {
    // 매치가 됐다. 그럼 매치된 문자들을 다시 불러오자.
    // 첫번째 괄호가 년도이다.
    String year = bday_pattern.getParen(1);
    // 두번째 괄호가 월, 그리고 세번째가 날짜이다.
    String month = bday_pattern.getParen(2);
    String day = bday_pattern.getParen(3);
 
    // 숫자들을 모두 추려냈으므로 다른 형태로 출력해보자.
    System.out.println(year + "년 " + month + "월 " + day + "일 ");
}

하지만 실제로 프로그램의 실행중에 이렇게 정규식을 컴파일 하는 일은 거의 없을 것이다. 그래서 Regexp 패키지에 같이 포함되어 나오는 클래스가 recompile 클래스이다. 이 클래스는 프로그램의 실행 중이 아니라 따로 독립적으로 사용할 수 있도록 되어 있으며 정규식을 REProgram 이 인식할 수 있는 프로그램으로 컴파일 해준다. 위의 예제를 통하여 다시 한번 살펴보자. 일단 위에서 사용하던 정규식을 도스창(명령 프롬프트)에서 다음과 같이 컴파일 한다. 여기서 실행에 주의해야 하는것이 디렉토리이다. 만약 recompile.class 파일이 C:\Program Files\jakarta-regexp\bin\classes\org\apache\regexp 에 있다면 C:\Program Files\jakarta-regexp\bin\classes 디렉토리에서 다음과 같이 실행시켜야 한다.

java org.apache.regexp.recomile bday "([\\d{2}|\\d{4}])/(\\d{1,2})/(\\d{1,2})" 위에서처럼 실행시키면 다음과 같이 출력될 것이다.

    // Pre-compiled regular expression '([\\d{2}|\\d{4}])/(\\d{1,2})/(\\d{1,2})'
    private static char[] bdayPatternInstructions = 
    {
        0x007c, 0x0000, 0x0061, 0x0028, 0x0001, 0x0003, 0x007c, 
        0x0000, 0x0014, 0x005b, 0x0007, 0x0011, 0x005c, 0x005c, 
        0x0064, 0x0064, 0x007b, 0x007b, 0x0032, 0x0032, 0x007d, 
        0x007d, 0x007c, 0x007c, 0x0034, 0x0034, 0x0029, 0x0001, 
        0x0003, 0x0041, 0x0001, 0x0004, 0x002f, 0x0028, 0x0002, 
        0x0003, 0x007c, 0x0000, 0x0018, 0x0041, 0x0001, 0x0004, 
        0x005c, 0x007c, 0x0000, 0x0007, 0x0041, 0x0001, 0x0007, 
        0x0064, 0x007c, 0x0000, 0x0003, 0x004e, 0x0000, 0x0003, 
        0x0041, 0x0001, 0x0004, 0x0064, 0x0029, 0x0002, 0x0003, 
        0x0041, 0x0001, 0x0004, 0x002f, 0x0028, 0x0003, 0x0003, 
        0x007c, 0x0000, 0x0018, 0x0041, 0x0001, 0x0004, 0x005c, 
        0x007c, 0x0000, 0x0007, 0x0041, 0x0001, 0x0007, 0x0064, 
        0x007c, 0x0000, 0x0003, 0x004e, 0x0000, 0x0003, 0x0041, 
        0x0001, 0x0004, 0x0064, 0x0029, 0x0003, 0x0003, 0x0045, 
        0x0000, 0x0000, 
    };
 
    private static RE bdayPattern = new RE(new REProgram(bdayPatternInstructions));

위에서처럼 출력이 됐으면 그것을 그대로 프로그램에 추가해서 사용하면 되겠다. 만약 사용하는 정규식이 많고 복잡할 경우 이렇게 미리 컴파일해서 사용하면 실행속도를 개선할 수 있을 것이다.


RESyntaxException


RESyntaxException 은 예외클래스로서 Regexp 패키지의 거의 모든 클래스의 함수들이 던진다. 위에 있는 예제들을 컴파일하려고 하면 RESyntaxException 을 잡아줘야 한다고 에러가 발생할 것이다. 다음의 예제와 같이 해주면 될 것이다.

try {
    RE data = new RE("test regexp");
}
catch (RESyntaxException e) {
    // 여기서 예외를 잡는다.
}

[편집] 유효성체크 유틸클래스 구성

JCF3.0에서 Regexp 패키지의 RE 클래스를 적용한 유효성체크를 각 요구사항별로 효과적으로 접근하여 사용할 수 있도록 final static으로 각 함수를 정의하였다.

[편집] 클래스 구조

jcf.util.validation 패키지에 RESecurityValidationUtils와 RESecurityPattern 클래스로 구성되어 있고, RESecurityValidationUtils 클래스는 유효성체크를 위한 함수를 정의하고 RESecurityPattern 클래스는 패턴매칭을 위한 패턴을 정의한다. 각 클래스의 구조는 다음의 그림에 상세하게 설계되어 있다.

유효성체크는 RESecurityPattern 클래스에 유효성을 검증하고자 하는 정규식(패턴)을 정의하고 해당 정규식을 적용하여 RESecurityValidationUtils 클래스에 유효성체크 함수를 정의하고 유효성체크를 하고자 하는 컴포넌트나 클래스에서 RESecurityValidationUtils.해당함수를 호출하면 된다.

  • RESecurityValidationUtils

현재 정의된 유효성체크 함수는 10가지 경우의 유효성을 검증한다.


같은 문자가 3회 이상 반복되는지 검증


public final static boolean validateCharRepeatation(String[] input) {
	boolean result = false;
 
	for (int i = 0; i < input.length; i++) {
		String temp = input[i];
		result = validateCharRepeatation(temp);
		if (result)
			return result;
	}
	return result;
}

문자, 숫자 조합 6자 이상 검증


public final static boolean validateCharDigit(String[] input) {
	boolean result = false;
 
	for (int i = 0; i < input.length; i++) {
		String temp = input[i];
 
		result = validateCharDigit(temp);
		System.out.println(temp + "(function): " + result);
		if (result)
			return true;
	}
	return result;
}

ID와 PW가 동일한지 검증


public final static boolean validateSameIdPwComparison(String id, String pw) {
	boolean result = id.equals(pw);
	System.out.println("ID: " + id + " PW: " + pw + " result: " + result);
	return result;
}

사용자 입력 문자열이 오름차순으로 구성되는지 검증


public final static boolean validateCharAscString(String[] input) {
	boolean result = false;
 
	for (int i = 0; i < input.length; i++) {
		result = true;
		String temp = input[i];
 
		result = validateCharAscString(temp);
		if (result)
			return true;
	}
	return result;
}

사용자 입력 문자열이 내림차순으로 구성되는지 검증


public final static boolean validateCharDescString(String[] input) {
	boolean result = false;
 
	for (int i = 0; i < input.length; i++) {
		result = true;
		String temp = input[i];
 
		result = validateCharDescString(temp);
		if (result)
			return true;
	}
	return result;
}

Cross Site Scripting 검증


public final static boolean validateCrossSiteScripting(String[] input) {
	RE validation_pattern = new RE(RESecurityPattern.CROSS_SITE_SCRIPTING_PATTERN);
	boolean result = false;
	System.out.println("--------------- Cross Site Scripting ----------------");
 
	for (int i = 0; i < input.length; i++) {
		String temp = input[i];
		result = validation_pattern.match(temp);
		System.out.println(temp + " result: " + result);
		if (result)
			return true;
	}
	return result;
}

SQL Injection 검증


public final static boolean validateSQLInjection(String[] input) {
	RE validation_pattern = new RE(RESecurityPattern.SQL_INJECTION_PATTERN_01);
	boolean result = false;
	System.out.println("--------------- SQL Injection 01 ----------------");
 
	for (int i = 0; i < input.length; i++) {
		String temp = input[i];
		result = validation_pattern.match(temp);
		System.out.println(temp + " result: " + result);
	}
 
	validation_pattern = new RE(RESecurityPattern.SQL_INJECTION_PATTERN_02);
	result = false;
	System.out.println("--------------- SQL Injection 02 ----------------");
 
	for (int i = 0; i < input.length; i++) {
		String temp = input[i];
		result = validation_pattern.match(temp);
		System.out.println(temp + " result: " + result);
	}
	return result;
}

Server Side Include 검증


public final static boolean validateServerSideInclude(String[] input) {
	RE validation_pattern = new RE(RESecurityPattern.SERVER_SIDE_INCLUDE_PATTERN);
	boolean result = false;
	System.out.println("--------------- Server Side Include ----------------");
 
	for (int i = 0; i < input.length; i++) {
		String temp = input[i];
		result = validation_pattern.match(temp);
		System.out.println(temp + " result: " + result);
	}
	return result;
}

공통 특수문자 포함 문자열 검증


public final static boolean validateSpecialCharacter(String[] input) {
	RE validation_pattern = new RE(RESecurityPattern.SPECIAL_CHARACTER_FILTERING_PATTERN);
	boolean result = false;
	System.out.println("--------------- Special Character Filtering ----------------");
 
	for (int i = 0; i < input.length; i++) {
		String temp = input[i];
		result = validation_pattern.match(temp);
		System.out.println(temp + " result: " + result);
	}
	return result;
}
  • RESecurityPattern

RE 클래스에 생성 시 다음의 패턴을 참조하여 유효성체크를 수행한다.

    public class RESecurityPattern {
	// ----------------------- Common Pattern --------------------
	public static final String DATE_PATTERN_01 = "([\\d{4}|\\d{4}])/(\\d{1,2})/(\\d{1,2})";
	public static final String DATE_PATTERN_02 = "([\\d{2}|\\d{4}])/(\\d{1,2})/(\\d{1,2})";
	public static final String DATE_PATTERN_03 = "([\\d{4}|\\d{4}])-(\\d{1,2})-(\\d{1,2})";
	public static final String DATE_PATTERN_04 = "([\\d{2}|\\d{4}])-(\\d{1,2})-(\\d{1,2})";
	public static final String ALNUM_PATTERN = "([:alnum:])";
	public static final String ALPHA_PATTERN = "([:alpha:])";
	public static final String DIGIT_PATTERN = "([:digit:])";
	public static final String GRAPH_PATTERN = "([:graph:])";
	public static final String LOWER_PATTERN = "([:lower:])";
	public static final String UPPER_PATTERN = "([:upper:])";
	public static final String PRINT_PATTERN = "([:print:])";
	public static final String HEXADECIMAL_DIGITS_PATTERN = "([:xdigit:])";
	public static final String EMAIL_ADDRESS_PATTERN = "(\\w[:print:]@[:alnum:].[:alpha:])";
 
	// ----------------------- Vulnerability Pattern -----------------
	public static final String CHAR_REPEAT_PATTERN = "[:alnum:]b{2,}[:alnum:]";
public static final String CROSS_SITE_SCRIPTING_PATTERN = "([:print:]script[:print:])";
public static final String SQL_INJECTION_PATTERN_01 = "'[:space:](or)|(OR)[:space:][:alnum:]=[:alnum:]";
public static final String SQL_INJECTION_PATTERN_02 = "\"[:space:](or)|(OR)[:space:]";
public static final String SERVER_SIDE_INCLUDE_PATTERN = "<!--#[:alnum:]";
public static final String SPECIAL_CHARACTER_FILTERING_PATTERN = "(['\"<>()&$#@*!~|;%])";
 

// ----------------------- ID/PW Pattern ---------------------- public static final String ALNUM_DIGITCONTOL_PATTERN = "[:alnum:]{5,}[:alnum:]";

}

[편집] 유효성체크 유틸의 확장

추가적으로 유효성체크를 위해 유틸을 확장하여 정의할 경우에는 다음과 같은 방법을 통해 정의하고 테스트할 수 있다.

[편집] 패턴의 구성 및 테스트

  • RE 클래스에서 제공하는 정규식을 통해 패턴을 정의한다.

웹 어플리케이션의 사용자 입력 필드에서 CROSS SITE SCRIPTING에 대한 보안성을 체크하기 위해 다음과 같은 패턴에 대한 정규식을 정의한다.

<script>alert(document.cookie);</script>
</TextArea><script>alert(document.cookie)</script>
>"><script>alert(document.cookie)</script>

위의 패턴을 통해 정규식을 정의한다.

([:print:]script[:print:])
  • 정의된 패턴을 RESecurityPattern 클래스에 등록한다.

정의된 정규식을 RESecurityPattern 클래스에 다음과 같이 등록한다.

public static final String CROSS_SITE_SCRIPTING_PATTERN = "([:print:]script[:print:])";
  • 등록된 패턴을 기반으로 RESecurityValidationUtils 클래스에 함수를 정의한다.

// Cross Site Scripting 검증
public final static boolean validateCrossSiteScripting(String[] input) {
	RE validation_pattern = new RE(RESecurityPattern.CROSS_SITE_SCRIPTING_PATTERN);
	boolean result = false;
	System.out.println("--------------- Cross Site Scripting ----------------");
 
	for (int i = 0; i < input.length; i++) {
		String temp = input[i];
		result = validation_pattern.match(temp);
		System.out.println(temp + " result: " + result);
		if (result)
			return true;
	}
	return result;
}
  • 정의된 함수를 TestCase에 따라 다양한 값을 입력하\고 그 결과가 명확한지 검증한다.

junit을 활용하여 다음과 같은 TestCase를 구성한다.

// Cross Site Scripting 검증
public void testCrossSiteScriptingValidation() throws Exception {
	RE validation_pattern = new RE(RESecurityPattern.CROSS_SITE_SCRIPTING_PATTERN);
	String[] input = { "<script>alert(document.cookie);</script>", "<script>alert('xss취약점발견');</script>",
			">'><%00script>alert('Watchfire XSS Test Successful')</script>", "<!--#echo var='document_url'-->",
			"javascript:alert(document.cookie)" };
	boolean result = false;
	System.out.println("--------------- Cross Site Scripting ----------------");
 
	for (int i = 0; i < input.length; i++) {
		String temp = input[i];
		result = validation_pattern.match(temp);
		System.out.println(temp + " result: " + result);
	}
	assertTrue(result);
}

테스트를 다음과 같이 (junit runs: alt+shft+x+t) 실행하면 테스트에 대한 결과를 확인할 수 있다.

아래와 같이 유효성 검증결과를 출력한다.

--------------- Cross Site Scripting ----------------
<script>alert(document.cookie);</script> result: true
<script>alert('xss취약점발견');</script> result: true
>'><%00script>alert('Watchfire XSS Test Successful')</script> result: true
<!--#echo var='document_url'--> result: false
javascript:alert(document.cookie) result: true
  • 검증된 함수를 컴포넌트나 클래스의 필요한 부분에 유효성체크를 적용한다.

[편집] 유효성체크 유틸의 적용

[편집] 참고