행복한 아빠

[Grails1.0 사용자 가이드] 11. 보안 본문

Grails

[Grails1.0 사용자 가이드] 11. 보안

행복한아빠 2008. 3. 12. 13:27


11. 보안

Grails는 자바 서블릿보다 더 안전하지도 덜 안전하지도 않다. 어쨌거나 자바 서블릿(그리고 Grails)는 매우 안전하고 일반적인 버퍼 공격(overrun)과 악의적인 URL 공격에 큰 면역력을 가지고 있는데 이는 자바 가상 머신 기반 코드의 본질에 기인한다.

웹 보안 문제는 보통 개발자의 무경험이나 실수에 기인한다. 그리고 Grails에는 이런 일반적인 실수를 피할수 있게하고 안전한 애플리케이션을 쉽게 작성할 수 있는 방법이 몇 가지 있다.

Grails가 자동으로 해주는 것
기본으로 Grails는 내장된 안전 메커니즘이 몇 가지 있다.

  1.  GORM 도메인 클래스를 통한 모든 표준 데이터베이스 접근은 SQL 주입 공격(injection attacks)으로부터 보호 받으로 수 있도록 자동으로 SQL escpaed한다.
  2. 기본 scaffolding 템플릿은 출력할 때 모든 데이터 필드를 HTML escape한다.
  3. 코드 주입(injection)을 보호하기 위해 link, form, createLink, createLinkTo 등과 같은 Grails link는 알맞은 escaping 메커니즘을 사용한다.
  4. HTML, 자바스크립트 그리고 URL을 사용할 때 여기서 주입(injection) 공격을 보호하기 위해 간단히 데이터를 escape할 수 있는 codec을 제공한다.
역주)
escape 공격
: 악의적인 사용자가 의도하지 않은 실행을 가능하도록 입력값으로 코드를 입력하는 것을 말한다. 안전하지 않은 서버일 경우 입력값으로 사용자가 입력한 코드(SQL이나 기타 심각한 파라메터 변경)가 그대로 실행되어 심각한 보안문제를 읽으킬 수 있다. 이것을 처리하는 서버는 사용자가 입력한 값이 코드가 아닌 데이터로 안전하게 처리되도록 사용자 입력을 중 명령어로 인식될 수 있는 부분을 적절히 변환해 준다. 이렇게 명령어로 실행될 수 있는 문자를 escape(탈출) 문자로 변경하는 것을 escape한다고 한다. 적절한 우리말을 찾기가 어렵다.ㅠㅠ 예를 들어 SQL에서 입력값으로 '1 or 1 =1'로 들어오면 escape하지 않고 SQL의 문자중 조건으로 단순히 치환하면 모든 데이터를 접근할 수 있다. 이런 경우는 PreparedStatement를 사용하여 해결할 수 있다.


11.1 공격에 대해 안전하게 보호하기

SQL 주입(injection)
GORM 도메인클래스의 근간 기술인 Hibernate는 데이터베이스에 전하는 데이터를 자동으로 escape하므로 이것은 이슈가 되지 않는다. 어쨌거나 검사하지 않은 요청 파라메터를 사용하는 좋지 않은 동적 HQL 코드를 작성할 가능성은 여전히 존재한다. 예를 들어 다음은 HQL 주입 공격에 취약하다.

def vulnerable = {
    def books = Book.find("from Book as b where b.title ='" + params.title + "'")
}

절대 이렇게 하지 말라. 파라메터 전달이 필요하다면 대산 이름에 의한(named) 또는 순서에 의한(positional) 파라메터를 사용하라.

def safe = {
    def books = Book.find("from Book as b where b.title =?", [params.title])
}


피싱(Phishing)
이것은 당신의 브랜드를 훔치는 사회 이슈로 당신의 고객과 공적인 의사소통이 필요하다. 고객은 진짜 이메일 수신을 어떻게 식별하는지 알 필요가 있다.

XSS - cross-site scripting injection
들어오는 요청이 당신의 애플리케이션으로 부터 기인한 것인지 아니면 다른 사이트에서 온 것인지 검증하는 것은 매우 중요하다. 티켓팅과 플로우 시스템이 이것을 도와줄 수 있다. 그리고 Grails는 기본으로 보안을 포함한 Spring 웹 플로우 지원한다.

또한 모든 뷰로 표현되는 데이터 값이 확실히 알맞게 escaped되는 것도 매우 중요하다. 예를 들어 악의를 가진 사람들이 자바스크립트를나 다른 HTML를 데이터나 다른 사람에게 보여주는 태그에 끼워넣지(inject) 못하도록 HTML이나 XHTML로 렌더링할 때 모든 객체에 대해 encodeAsHTML을 호출해야 한다. Grails는 이 목적을 위해 몇 가지 동적 인코딩 메소드들을 제공하는데 출력에 escape된 포맷을 지원하지 않을 경우 고유의 코덱(codec)을 쉽게 작성할 수 있다.

또한 리다이렉트하기 위한 다음 URL을 결정하기 위해 request 파라메터나 데이터 필드를 사용하는 것을 피해야 한다. 예를 들어 로그인 성공 후 리다이렉트를 결정하기 위해 suuccessURL 파라메터를 사용한다고 할 때 공격자는 당신의 사이트를 이용하여 로그인 절차를 흉내낼 수 있어 로그인 한 사용자는 공격자의 사이트로 돌아갈 수 있다. 암시적으로 그 사이트의 로그인한 계정을 자바스크립트 코드로 이용할 수 있게 된다.

HTML/URL 주입
나중에 페이지 안에 link를 생성하기 위해 데이터에 link와 같은 나쁜 데이터를 넣으면, link를 클릭하는 경우 예상치 못한 작동을 유발하거나 다른 사이트로 리다이렉트 할 수도 있으며 request 파라메터를 바꿀 수도 있다.

Grails가 제공하는 코덱을 이용하여 HTML/URL 주입을 쉽게 처리할 수 있다. 그리고 Grails가 제공하는 모든 태그 라이브러리는 알맞은 곳에서 encodeAsURL을 호출한다. 만일 URL을 생성하는 당신 고유의 태그를 작성한다면 이것을 염두해 둘 필요가 있다.

서비스 거부
이 경우 로드 밸런서나 다른 장치가 보다 유용하다. 그러나 또한 터무니 없는 질의와 관련한 이슈가 있는데 예를 들어 공격자가 만든 link가 결과 세트의 개수를 최대로 해서 서버의 메모리 한계를 넘어 시스템이 느려지거나 다운되는 경우가 있다. 여기에서의 해결책은 항상 request 파라메터 동적 finder나 다른 ORM 질의 메소드에 전달하기 전 검사하여 나쁜 파라메터 값을 제거하는 것이다.

def safeMax = Math.max(params.max?.toInteger(), 100) // 100개 이상이 반환되지 않도록 조치
return Book.list(max:safeMax)

추측이 가능한 ID
많은 애플리케이션들이 URL의 마지막 부분으로 GORM이나 다른 곳에서 객체를 조회할 수 있는 ID를 사용한다. 특히 GORM의 경우 보통 ID가 순차적인 integer이므로 쉽게 추측이 가능하다.

따라서 사용자에게 응답을 보내기 전 사용자가 요청한 ID로 그 객체를 보는 것이 허가되었는지 반드시 확인해야 한다. 이렇게 하지 않는 것을 기본 비밀번호로 "letmein" 등을 쓰는 것 같은 필연적으로 깨지는 "애매한 보안(security through obscurity)"이라 한다.

반드시 모든 보호되지 않은 URL은 어떤 방법으로도 공개적으로 접근할 수 있다고 가정해야 한다.


11.2 문자열 인코딩과 디코딩
Grails는 동적 인코드/디코드 메소드 개념을 지원한다. Grails는 표준 코덱 집합을 포함하고 있다. Grails는 또한 실시간에 인식하는 고유한 코덱을 개발자가 공헌할 수 있도록 간단한 메커니즘을 지원한다.

코덱 클래스
Grails 코덱 클래스는 인코드 클로우저(closure)와 디코드 클로우저 둘 다 포함할 수 있다. Grails 애플리케이션이 시작될 때 Grails 프레임워크는 grails-app/utils 디렉토리에서 코덱들을 동적으로 로드한다.

프레임워크는 약속(convention)에 따라 grails-app/utils 아래에서 이름이 Codec으로 끝나는 클래스를 찾는다. 예를 들면 Grails가 제공하는 표준 코덱 중 하나는 HTMLCodec이다.

코덱이 코드 블럭을 지정한 encode 속성을 가지면 Grails는 동적인 encode 메소드를 생성하고 코덱을 표현하는 이름으로 String  클래스에 그 메소드를 추가한다. 예를 들면 HTMLCodec 클래스는 encode 블럭을 정의하므로 Grails는 그 클로우저를 encodeAsHTML이란 이름으로 String 클래스에 붙일 것이다.

HTMLCodec과 URLCodec 클래스는 또한 decode 블럭을 정의하므로 Grails는 이것들을 decodeHTMLdecodeURL 이름으로 붙일 것이다. Grails 애플리케이션 어디에서든지 동적 코덱 메소드를 호출할 수 있다. 예를 들면, 'description'이라는 속성을 가진 보고서가 있는데 설명은 HTML 문서로 표현되기 위해서 escape이 필요한 특수 문자를 가진 경우를 보자. 이것을 처리하기 위한 한 방법은 GSP에서 동적 인코드 메소드를 아래와 같이 호출하는 것이다.

${report.description.encodeAsHTML()}

디코딩은 value.decodeHTML() 문법으로 실행한다.


표준 코덱들

HTMLCodec
이 코덱은 HTML escaping과 unescaping을 수행한다. 따라서 어떠한 HTML 태그를 생성하지 않고 페이지 레이아웃에 해를 끼치지 않고 HTML 페이지에 값을 안전하게 렌더링할 수 있다. 예를 들면,  "Don't you know that 2 > 1?" 값은 > 문자가 태그를 닫기 때문에 HTML 페이지에 안전하게 보여줄 수 없다. 이 값은 특히 다음과 같은 input 필드의 값 속성으로 렌더링할 때 아주 나쁘다.
사용 예제

<input name="comment.message" value="${comment.message.encodeAsHTML()}"/>

사용자 삽입 이미지
HTML 인코딩은 apostrohe(소유격부호)와 단따옴표는 다시 인코드하지 않는다는 것에 주의하라. 따라서 apostrohpe(')가 페이지를 망가뜨리지 않도록 애트리뷰트 값에 반드시 쌍따옴표(")를 사용한다.


URLCodec
Link나 폼 액션 또는 URL 생성이 필요한 어느 시점에서든지 URL을 만들기 위해 URL 인코딩이 필요하다. 이것은 URL의 의미를 바꾸는 불법적인 문자를 막아준다. 예를 들면 "Apple & Blackberry"는 GET 요청에서 엠퍼센드(&)가 파라메터 파싱에서 둘을 분리하므로 파라메터로 잘 작동하지 않을 것이다.
사용예제

<a href="/mycontroller/find?searchKey=${lastSearch.encodeAsURL()}">Repeat last search</a>


Base64Codec
Base64 인코드/디코드 기능을 수행한다. 사용 예제

Your registration code is: ${user.registrationCode.encodeAsBase64()}


JavaScriptCodec
유효한 자바스크립트 문자열을 사용할 수 있도록 문자열 escape를 한다. 사용 예제:

Element.update('${elementId}', '${render(template: "/common/message").encodeAsJavaScript()}')


사용자 정의 코덱들
애플리케이션은 그들 고유의 코덱들을 정의할 수 있다. 그리고 Grails는 표준 코덱들과 함께 그것들을 로드할 것이다. 사용자 정의 코덱 클래스는 grails-app/utils/ 디렉토리에 정의하고 이름이 Codec로 끝나야 한다. 코덱은 static encode 블럭과 static decode 블럭  두 개 모두 가질 수 있다. 블럭은 동적 메소드를 호출하는 대상이 되는 객체를 나타내는 하나의 아규먼트를 가진다. 예를 들면

class PigLatinCodec {
    static encode = { str ->
        // convert the string to piglatin and return the result
    }
}

위 코덱을 애플리케이션에서 다음과 같이 사용할 수 있다.

${lastName.encodeAsPigLatin()}


11.3 인증(Authentication)
현재 인증을 위한 기본 메커니즘은 없지만 수천가지 다른 방법으로 인증을 구현할 수 있다. 어쨌거나 인터셉터필터를 이용한 간단한 인증 메커니즘을 구현하는 것은 진짜 간단한다.

필터로 모든 컨트롤러나 URI 공간을 가로질러 인증을 적용할 수 있다. 예를 들면 grails-app/conf/SecurityFilters.groovy에 새로운 필터 집합을 생성할 수 있다.

class SecurityFilters {
    def filters = {
        loginCheck(controller:'*', action:'*') {
            before = {
                if(!session.user && actionName != "login") {
                    redirect(controller:"user",action:"login")
                    return false    
                }
            }
        }
    }
}

여기의 loginCheck 필터는 액션이 실행되기전 실행을 가로챈다. 그리고 만일 세션에 사용자가 없고 login 액션이 아니라면 login 액션으로 리다이렉트한다.

login 액션도 다음과 같이 간단하다.

def login = {
    if(request.get) render(view:"login")
    else {
        def u = User.findByLogin(params.login)
        if(u) {
            if(u.password == params.password) {
                session.user = u
                redirect(action:"home")
            }
            else {
                render(view:"login", model:[message:"Password incorrect"])
            }
        }
        else {
            render(view:"login", model:[message:"User not found"])
        }
    }
}


11.4 보안 플러그인
만일 간단한 인증을 넘어서 인가, 역할 등의 진보된 기능이 필요하다면 유효한 보안 플러그인 중 하나를 고려해 볼 수 있다.

11.4.1 Acegi
Acegi 플러그인은 Spring Acegi 프로젝트를 기반으로 하는데 이것은 모든 인증, 인가 종류의 스키마를 구축하기 위한 유연하고 확장성 있는 프레임워크를 제공한다.

Acegi 플러그인을 사용하면 URI와 역할을 맵핑해야 하며 Acegi 플러그인은 사용자, 인증정보 그리고 request 맵 모델을 위한 기본 도메인 모델을 제공한다. 더 자세한 정보는 wiki의 문서여기를 보라.

11.4.2 JSecurity
JSecurity는 자바 POJO 지향의 또 다른 보안 프레임워크로 역시 realms, 사용자, 역할 그리고 권한 모델을 위한 기본 도메인 모델을 제공한다. JSecurity를 사용하려면 보안을 원하는 각 컨트롤러가 JsecAuthBase라는 컨트롤러를 상속해야 한다. 그런 후 역할을 설정하는 accessControl 블럭을 제공해야 한다.
다음은 그 예제이다.

class ExampleController extends JsecAuthBase {
    static accessControl = {
        // All actions require the 'Observer' role.
        role(name: 'Observer')
        // The 'edit' action requires the 'Administrator' role.
        role(name: 'Administrator', action: 'edit')
        // Alternatively, several actions can be specified.
        role(name: 'Administrator', only: [ 'create', 'edit', 'save', 'update' ])
    }    …
}


JSecurity 플러그인에 대한 더 자세한 정보는 JSecurity Quick Start를 참조하라.

---
원문: 11. Security

0 Comments
댓글쓰기 폼