혼자 사용할 목적의 장난감이 아닌 이상 개발자가 항상 염두에 두고 있어야할 단어가 바로 '보안'이다. 아무리 잘 만들어진 애플리케이션이라도 작은 실수 하나로 치명타를 입을 수 있기 때문이다. 애플리케이션이 안전한가? 그렇지 않은가는 전적으로 개발자의 몫이다. 프레임워크인 루비 온 레일스는 그 어느 것도 책임져주지 않는다. 다만, 몇가지 힌트를 제공할 뿐이다. 따라서 개발자의 문제가 일어날 수 있는 부분을 미리 인지하고, 방어적으로 개발하는 방법 뿐이다.
한가지 흥미로운 점은 레일스가 1.x(현재 안정버전)까지는 완전한 방관자로서 '보안은 알아서 잘 플러그인이나 자체 노하우등으로 해결하세요'라는 노선이었는데, 2.0(EdgeRails, 현재 개발버전)에서는 몇가지 보안 권고(화이트 리스트 기반 sanitize, CSRF Killer 등)를 메인 소스 코드에 포함했다는 사실이다. 개인적으로 반가운 변화라고 생각한다.
지금부터 웹 애플리케이션을 작성할 때 염두에 두어야 할 보안관련 이슈를 몇가지를 하나씩 짚어보며, 레일스식 대처법을 따져보도록 하자.
먼저 서버 사이드에서는 사용자의 입력은 모두 잘못될 수 있다는 가정을 하고 개발해야한다. 절대 데이터 그 자체를 믿어서는 안된다. 클라이언트에서 아무리 강력한 Validation을 행하더라도, 서버에서는 다시 한번 확인할 필요가 있다.
입력 데이트를 변조해서 이루어지는 대표적인 공격 형태가 바로 SQL Injection(SQL 주입)이다. 예를 들어 다음과 같은 코드가 있다고 해보자.
대부분의 경우에는 name 파라메터가 사람 이름으로 넘어오겠지만, 어떤 사람이 악의적은 목적을 가지고 name에 "' OR 1"이라고 넣었다고 해보자. 이 때 만들어지는 쿼리는 아래와 같다.
너무나도 손쉽게 시스템의 전체 사용자 정보를 얻어낼 수 있게 되었다. SQL Inject을 이용해서 특정 테이블을 지운다든가 하는 위험한 행동도 가능하니 특히나 주의해야한다. 다행히 몇가지 코딩 규칙만 지킨다면 SQL Injection 공격은 어렵지않게 막을 수 있다. 규칙은 간단하다. 직접 SQL을 만들지 말라. 아래는 안전한 코드들이다.
아래는 레일스 컨트롤러에서 흔히 볼 수 있는 코드이다. 가만 생각해보면 대단히 위험한 코드이기도 하다.
users 테이블의 속성 중에는 사용자가 쉽게 고칠 수 있게 해서는 것도 분명 있을 것이다. 예를 들면 권한이나 암호화를 위한 Digest, 또는 로그인 저장을 위한 키등이 그것이다. 따라서 모델 속성을 외부에서 접근한 것(Public)과 내부적인 것(Protected)로 구분할 필요가 있다. 기본값은 public이니, 필요한 속성을 아래와 같은 코드로 protected로 바꿔주면 된다.
SQL Injection 만큼이나 유명세를 떨치고 있는 기법이 바로 XSS다. XSS는 악의적인 스크립트를 삽입해 의도하지 않은 행동(예를 들어 쿠키나 개인정보를 제3자에게 유출하는 등)을 유발한다. 특히나 어떤 사이트에서는 자바스크립트를 삽입해 현재 로그인된 사용자의 쿠키(document.cookie)를 얻어내어 그 사용자의 로그인 권한을 획득하는 것도 가능하므로 주의해야한다. XSS에 대한 자세한 설명은 아래 링크를 참조하자.
XSS를 막으려면, 사용자가 입력한 값을 화면에 출력하기 전에 모든 값을 이스케이핑해야한다. 이를 위해 루비에서 제공하는 메서드는 html_escape 메서드다. 줄여서 h를 사용해도 된다.
어떤 템플릿 엔진은 항상 이스케이프해서 출력하도록 하기도 한다(ex, Erubis). 기본 erb를 사용하는 경우는 Safe ERB 플러그인을 통해 같은 효과를 얻을 수 있다.
반드시 HTML을 다뤄야한다면, 사용자가 입력한 HTML을 특정 룰(화이트 리스트)에 기반해 안전한 형식으로 변환해서 저장해야한다. 이 과정에서 알려진 XSS 패턴을 모두 제거하는 것이다. 레일스 1.x 를 사용하고 있다면 레일스에서 제공하는 함수인 sanitize를 믿어서는 안된다. 이 구현은 블랙리스트를 기반으로 매우 일부 케이스만들 없애줄 뿐이다. 대신 White List 플러그인을 이용하는 것이 좋다.
최신 개발 버전(레일스 2.0)을 사용한다면 sanitize를 믿고 사용해도 좋다. 위에서 소개한 White List 플러그인이 레일스 소스에 포함되었기 때문이다. 자세한 구현은 ActionView::Helpers::TextHelper를 참조하기 바란다.
XSRF는 XSS보다 광범위한 공격이다. 특정 URL이나 폼은 정상적인 사용자가 정해진 시나리오에 따라 요청할거라는 가정이 깨지는 순간이 바로 XSRF이다. 예를 들어, 자신의 비밀번호를 특정 메일로 전송하는 기능이 있다고 해보자. 이 기능이 GET 방식으로 구현되었다고 하자. 사용자가 사이트에 로그인한 채로 서드파티 사이트를 방문했다. 이 사이트에서는 메일 전송 주소로 리다이렉트된다. 그렇다면, 어렵지 않게 사용자의 비밀번호를 획득할 수 있다. POST로 구현되었다면 어떨까? 조금 번거롭기는 하겠지만, 해당 주소로 사용자 대신 자동으로 Submit 되게 하는 방식으로 같은 일을 행할 수 있다. 스팸 메일을 통해서도 같은 공격이 가능하다. XSRF에 대한 보다 자세한 내용은 아래 링크를 참조하자.
어떻게 막을 수 있을까? 일단 GET/POST는 구분해서 쓰는 것이 좋다. GET은 위험하지 않는 동작에만 사용해야 한다. URL 노출이 훨씬 쉽기 때문이다. POST를 사용한 경우는 Redirection이나 메일을 이용한 공격에 노출될 확률이 줄어든다. POST가 완벽한 해결책은 아니지만, 예방을 위한 시작점이다. REST 스타일을 따른다면 자연스러울 것이다.
그리고 POST되는 폼에 숨겨진 값으로 보안을 위한 토큰을 숨겨두고, 서버 사이드에서 올바른 토큰과 함께 submit 되었는지 확인한다. 이렇게 하면 올바른 사용자가 폼을 제출한다는 가정을 계속 지킬 수 있다. XSRF 공격을 쉽게 막기 위해서는 레일스 최신 버전을 사용하는 것이 좋다. 레일스 2.0이 될 최신 개발 버전에는 CSRF Killer 플러그인이 포함되었기 때문이다. 관련 구현은 ActionController::RequestForgeryProtection 모듈에 있다. 그리고 기본값으로 이 기능을 사용하도록 추천하고 있다. 자세한 내용은 아래 문서를 참조하기 바란다.
파일 업로드를 허용하는 애플리케이션이라면 몇 가지 주의할 사항이 있다. 웹서버는 확장자를 통해 실행 방법을 결정하는 것이 보통이므로 사용자가 입력한 파일 이름은 View를 위해서만 사용하는 것이 좋다. 즉 사용자가 제시한 파일 이름은 DB에 저장하고 실제 파일은 웹서버의 Document Root가 아닌 다른 위치에 애플리케이션에서 생성한 이름으로 파일을 저장하는 것이 추천할만한 방법이다.
다운로드 할 때는 브라우저에서 직접 실행되기보다는 다운로드가 일어나도록 헤더를 함께 보내야한다. 그렇게 하지 않으면 XSS 공격이 가능하기 때문이다. send_file 메서드를 이용하면 이런 동작이 기본값이다.
근본적으로 레일스 어플리케이션에서는 첨부파일을 다루지 않는 것이 상책이다. 보통 업로드/다운로드는 오래걸리기 마련이고, 이 작업이 진행되는 동안 다른 요청은 미뤄질 것이기 때문이다. 이는 애플리케이션 전체의 건강에 영향을 미친다. 이를 이용한 공격도 가능할 것이다. 다운로드는 X-Sendfile을 활용하고, 업로드도 별도의 Mongrel Handler를 작성하는 것을 추천한다.
세션 하이재킹은 여러 방법을 이용해 다른 사용자의 세션 ID(쿠키)를 훔쳐서 다른 사람의 권한을 비정상적으로 가로채는 것을 말한다. 네트워크 스니핑을 통한 방법이 가능하므로, 되도록 SSL을 통한 압호화를 사용하는 것이 좋다. 그리고 로그인 쿠키는 안전한 쿠키, 즉 HTTPS 영역에서만 읽을 수 있는 것으로 사용한다. SSL의 사용은 애플리케이션의 성능을 저하시키기도 하므로 로그인등 민감한 부분에만 선별적으로 적용하는 것도 좋다. 레일스는 HTTPS 지원을 위한 여러 장치를 마련하고 있으므로, 큰 어려움없이 적용할 수 있을 것이다.
세션 ID가 유출되었을 경우를 대비해 세션에 몇 가지 장치를 해도 된다. 가능한 방법은 다음과 같다.
나는 보안 전문가는 아니다. 하지만 웹 서비스를 직접 개발/운영하다보니 보안을 고려한 안전한 코딩이 그 어떤 것보다 우선이라는 사실을 깨닫게 된다. 완벽하게 견고해지는 것은 쉽지 않지만, 우리는 레일스라는 좋은 프레임워크 위에 있기에 조금만 노력하면 이미 알려진 케이스들을 어렵지 않게 막아낼 수 있다. 약간의 관심과 주의가 필요할 뿐이다.
여기서 다루지 못한 내용도 많다. 애플리케이션을 배포하기 전에 꼭 아래 링크들의 글을 읽어보며, 자신의 애플리케이션은 안전한지 자체 진단을 마치는 것이 좋겠다.