웹 보안의 핵심은 원격지 사용자 입력을 신뢰하지 않는 것이다

아이디 입력란이 있으면 사람들은 아이디를 넣는다. 개발자는 그렇게 가정하고 코드를 짠다. 그런데 누군가는 그 칸에 1만 자를 때려 넣고, 누군가는 자바스크립트를, 누군가는 SQL문을 넣는다. 입력란은 사람의 선의를 전제로 만들어지지만, 공격자는 그 전제를 노린다

웹 보안을 코드 레벨에서 시작하는 출발점은 하나로 압축된다. 원격지 사용자 입력(remote user input)을 절대 신뢰하지 말고 항상 검증하라. 이 원칙 하나만 제대로 지켜도 시큐어 코딩 요구사항의 절반, 많게는 70%가 해결된다. 이 글은 그 원칙이 왜 중요한지, 입력 검증을 빠뜨리면 어떤 공격이 가능해지는지, 그리고 코드 레벨을 넘어 어떤 보안 계층이 필요한지를 정리한다. 1부에서 다룬 웹 서비스 구조 위에 반드시 얹어야 하는 내용이다

모든 원격지 입력은 검증 대상이다

시큐어 코딩을 배우면 가장 먼저 만나는 원칙이 입력 검증이다. 클라이언트에서 서버로 들어오는 모든 값은 신뢰의 대상이 아니라 검증의 대상이다

이유는 단순하다. 입력값이 어디서 왔는지 서버는 알 수 없다. 정상적인 폼에서 왔는지, 조작된 요청에서 왔는지 구분되지 않는다. 그래서 입력의 무결성을 코드 레벨에서 직접 확인해야 한다. 길이가 적절한지, 형식이 맞는지, 허용된 문자만 들어 있는지를 검사한 뒤 통과한 값만 처리한다

검증의 두 가지 방향을 구분하면 이해가 쉽다

방향의미
입력 검증(Validation)들어오는 값이 규칙에 맞는지 확인길이 제한, 형식 검사, 허용 문자만 통과
출력 처리(Escaping)내보내는 값을 안전하게 변환HTML 특수문자 이스케이프

들어올 때 거르고, 나갈 때 안전하게 변환한다. 이 두 가지가 코드 레벨 보안의 기본이다

입력 검증을 빠뜨리면 네 가지 공격이 열린다

입력을 신뢰한 대가는 구체적인 공격으로 돌아온다. 대표적인 네 가지는 공격자가 입력란에 무엇을 넣느냐로 갈린다

자바스크립트를 넣으면 XSS가 된다

입력란에 자바스크립트를 넣었는데 그것이 게시글로 저장됐다고 하자. 자바스크립트는 클라이언트에서 실행된다. 그 게시글을 다른 사용자가 열면, 내가 작성하지도 개발하지도 않은 스크립트가 그 사용자의 브라우저에서 실행된다. 이것이 크로스 사이트 스크립팅(XSS, Cross-Site Scripting)이다.

방어의 핵심은 출력 처리다. 사용자 입력을 화면에 출력할 때 <, >, & 같은 HTML 특수문자를 이스케이프해 스크립트가 코드가 아니라 단순 텍스트로 표시되게 한다

로그인 상태를 악용하면 CSRF가 된다

대부분의 사용자는 자주 쓰는 사이트에 로그인 상태로 머문다. 공격자는 이 점을 악용한다. 특정 동작을 수행하는 요청을 미리 만들어 두고, 사용자가 그 링크를 클릭하게 유도한다. 클릭하는 순간 사용자의 로그인 상태를 이용해 의도하지 않은 요청이 서버로 전송된다. 예를 들어 사용자도 모르게 어떤 물건을 구매하는 요청이 나간다. 이것이 크로스 사이트 요청 위조(CSRF, Cross-Site Request Forgery)다.

방어로는 CSRF 토큰을 쓴다. 정상적인 화면에서 발급된 토큰이 포함된 요청만 유효하게 처리하면, 외부에서 위조한 요청은 토큰이 없어 걸러진다

SQL문을 넣으면 SQL 인젝션이 된다

입력란에 SQL문을 넣었는데 그것이 검증 없이 쿼리에 합쳐지면, 공격자가 의도한 SQL이 DB에서 실행된다. 이것이 SQL 인젝션(SQL Injection)이다. 인증 우회, 데이터 탈취, 데이터 삭제까지 이어질 수 있다

방어의 정석은 입력값을 쿼리 문자열에 직접 이어 붙이지 않는 것이다. 값을 별도 파라미터로 바인딩하는 PreparedStatement(파라미터 바인딩)를 쓰면 입력은 데이터로만 취급되고 SQL 구문으로 해석되지 않는다

운영체제 명령을 넣으면 OS 커맨드 인젝션이 된다

입력란에 SQL 대신 운영체제 명령어를 넣고, 서버가 그 값을 시스템 명령으로 실행하면 OS 커맨드 인젝션(OS Command Injection)이 된다. 서버에서 임의 명령이 실행되므로 피해 범위가 가장 넓다.

네 공격의 공통점은 분명하다

입력 신뢰  →  검증 없이 처리  →  공격자가 넣은 코드가 실행됨

 자바스크립트   → XSS              (피해: 다른 사용자 브라우저)
 위조 요청     → CSRF             (피해: 로그인 사용자 권한 도용)
 SQL문       → SQL Injection    (피해: DB)
 OS 명령어    → OS Command Injection (피해: 서버 전체)

본질은 하나다. 원격지 입력을 신뢰하면 안 된다. 항상 무결성을 검증하고, 적절한 것만 실행되도록 처리하면 된다

코드 레벨만으로는 부족해 보안 솔루션 계층이 붙는다

모든 보안 책임을 개발자에게만 맡길 수는 없다. 그래서 트래픽이 애플리케이션에 닿기 전에 걸러 주는 보안 솔루션 계층이 앞단에 배치된다. 일반적인 배치 순서는 다음과 같다

[인터넷]
   │
 [IPS]              침입 방지 시스템 (가장 앞단)
   │
 [SSL 종단]          여기서 HTTPS 복호화 → 이후 평문
   │
 [WAF]              웹 애플리케이션 방화벽 (입력 검사)
   │
 [웹 서버 / WAS]

가장 앞에 IPS(침입 방지 시스템)가 있고, 그 뒤에 SSL 처리를 담당하는 지점이 있으며, 애플리케이션을 보호하는 웹 애플리케이션 방화벽(WAF, Web Application Firewall)이 놓인다

여기서 순서가 중요하다. HTTP 통신은 기본적으로 평문이다. SSL이 결합되면 HTTPS가 된다. 인터넷이라는 공개 네트워크를 지나는 동안 데이터는 암호화되어 있다. 암호화된 상태에서는 HTTP 수준의 내용을 들여다볼 수 없으므로, 그 상태로는 SQL문이 들어왔는지 자바스크립트가 들어왔는지 검사할 수 없다

그래서 SSL 인증서가 올라간 지점(SSL 종단)을 지나 평문이 된 뒤에야 WAF가 입력 내용을 조사할 수 있다. 즉 SSL 종단과 WAF, 두 보안 대응 체계를 함께 갖춰야 이 검사가 성립한다. 이런 구성을 갖추는 것이 ISMS-P 같은 보안 인증을 통과하기 위한 조건이기도 하다. 경우에 따라 WAF를 다른 위치로 옮기거나, 여러 개를 두어 더 강력한 보안을 구성하기도 한다

HTTPS의 신뢰는 PKI가 떠받친다

SSL을 다루다 보면 반드시 PKI를 알아야 한다. HTTPS가 단순히 데이터를 암호화하는 데 그치지 않고 “지금 접속한 서버가 진짜 그 서버가 맞는지”까지 보장하기 때문이다. 이 보장을 떠받치는 것이 PKI(Public Key Infrastructure, 공개키 기반 구조)다

핵심만 짚으면 이렇다. 서버는 공개키와 개인키 한 쌍을 가진다. 신뢰할 수 있는 인증기관(CA)이 서버의 공개키에 서명한 인증서를 발급한다. 브라우저는 이 인증서를 검증해 서버의 신원을 확인하고, 안전하게 교환한 키로 이후 통신을 암호화한다

[CA] --서명--> [서버 인증서(공개키 포함)]
                      │
        브라우저가 인증서 검증 → 서버 신원 확인 → 암호화 통신 수립

SSL과 관련된 PKI 이론을 모르면 웹 서비스를 구축하고 인증서를 다루는 과정에서 막히기 쉽다. 적어도 공개키·개인키, 인증서, CA의 관계는 알아 두는 편이 좋다

리소스 출처를 따지는 정책이 CORS다

클라이언트 관점에서 한 가지 더 짚는다. 브라우저는 한 화면을 구성하기 위해 여러 서버에서 리소스를 가져와 조합할 수 있다. 같은 네이버 도메인 안의 w1.naver.comw2.naver.com에서 가져오는 정도는 자연스럽다

문제는 전혀 무관한 출처에서 리소스가 끼어들 때다. 네이버 화면을 구성하는데 느닷없이 다른 도메인에서 리소스를 가져온다면, 처음부터 그렇게 설계한 것이 아닌 한 해킹으로 주입됐을 가능성을 의심해야 한다. 그래서 브라우저는 리소스의 출처(origin)를 따져, 다른 출처의 요청을 허용할지 말지를 결정한다. 이 정책이 CORS(Cross-Origin Resource Sharing)다

CORS가 왜 필요해졌는지 더 깊이 알고 싶다면 드라이브 바이 다운로드(drive-by download)라는 공격 기법을 찾아보면 된다. 사용자가 악성 사이트를 방문하기만 해도 악성코드가 내려받아지는 방식으로, 출처를 통제하는 정책이 왜 생겼는지를 이해하는 좋은 출발점이다

정리

정리하면 웹 보안의 출발점은 원격지 사용자 입력을 신뢰하지 않고 항상 검증하는 것이다. 이 원칙을 코드 레벨에서 지키면 XSS·CSRF·SQL 인젝션·OS 커맨드 인젝션을 막는 시큐어 코딩의 대부분이 해결된다. 그 위에 IPS·SSL·WAF라는 보안 솔루션 계층을 얹고, HTTPS를 떠받치는 PKI와 출처를 통제하는 CORS까지 갖추면 1부에서 그린 웹 서비스 구조가 비로소 안전하게 완성된다.

입력란 앞에서 던질 질문은 늘 하나다. 이 값을 신뢰해도 되는가. 답은 항상 아니오다