메뉴 바로가기 검색 및 카테고리 바로가기 본문 바로가기

한빛출판네트워크

IT/모바일

PHP에서 보안 문제를 해결하는 법 : 항상 위협이 되는 SQLi, XSS & CSRF 살펴보기

한빛미디어

|

2014-07-16

|

by HANBIT

31,670

제공 : 한빛 네트워크
저자 : Chris Cornutt
역자 : 한기갑(http://normalist.tistory.com/)
원문 : Preventing Problems in PHP Security

Chris Cornutt 당신 주위에 있는 모든 PHP 개발자가 당신에게 이 언어에는 특별한 허점이 있다고 말할 겁니다. 그들은 다른 언어를 쓰는 그들의 동료로부터 PHP는 질서가 없다거나 "이건 스크립트 언어야, 진짜 언어는 아니지"라는 소리를 들을겁니다. PHP가 또 하나 듣는 말이 있다면 안전하지 않다는 것입니다. 맞습니다. 그러나 다른 언어들도 이런 문제를 갖고 있습니다. 최근 Ruby의 중요한 취약점들이 언론에 공개되었고, Java도 오래 동안 사용하면서 약점들을 갖고 있습니다. 사람들은 PHP가 안전하지 않다고 폄하하지만, 그들은 보안에 약한 코드를 만드는 것은 언어가 아니라 개발자라는 사실을 잊습니다.

PHP는 언어 속성상 모든 요청(request)의 끝에 "죽습니다". 그래서 개발자들은 다른 지속되는 언어들을 개발할때와 달리 걱정할 필요가 없습니다. 물론 PHP 개발자로서 반드시 알아야하는 다른 언어에도 있는 공통적인 위험도 있습니다. 이 중에 가장 기본은 OWASP Top 10 리스트입니다. 여기서 잠깐 이런 위험들을 어떻게 피할 지 몇 가지만 알아보겠습니다.

SQL 인젝션 공격

당신이 보안에 대해 관심있게 지켜보고 있었다면, 당신은 분명히 SQL injection (SQLi) 공격으로 큰 또는 작은 회사에 침입하고 해킹하였다고 들어봤을 겁니다. 기본적으로, 이 공격은 시스템에 접근하려는 해커가 특별히 포맷된 문자열을 입력값의 일부로 사용해서 database 수준까지 내려가서 악의적인 명령어를 실행하는 공격입니다. 예를 들어, 당신의 스크립트의 입력값이 초전역변수인 $_GET에서 온다고 합시다. 만약 당신이 사용자가 당신에게 주는 값을 막거나 필터링하지 못하면 이 변수는 어떤 값이든 될 수 있습니다. 무섭죠? PHP에는 당신에게 이 공격을 피할 수 있게 해주는 쉬운 방법이 있습니다. 당신은 PDO라고 불리는 내장된 database abstraction layer를 사용할 수 있습니다. 이것은 prepared statement 기능입니다. 여기에 SQL injection 문제를 막을 수 있는 예제가 있습니다.
prepare("select foo from baz where bar = :one");
$stmt->bindParam(":one", $myValue);
$stmt->execute();
?>
차이점은 전달된 값이 문자열로 만들어진 SQL문의 일부가 아니라는 겁니다. 당신이 아래와 비슷한 것을 허용했을때 일어날 문제점에 대해서 상상해보세요.

첫번째 줄에 바운드 파라미터를 사용하는 것이 당신의 애플리케이션을 SQL injection으로부터 보호하는 데 좋고, 효율적입니다.

사이트 간 스크립팅

Cross-site scripting(Xss)은 사용자의 입력값이 무조건적으로 브라우저에 걸러지지 않고, 유효성 검사도 되지 않은채 다시 돌아오는 공격입니다. 안타깝게도, PHP는 이런 종류의 공격을 막도록 내장된 툴이 없기 때문에 이런 공격에 무기력합니다. 사용자가 입력한 값을 브라우져로 다시 전달할때 echo나 printf 등의 방법을 사용할 수 있지만, 어떤 것도 들어오는 data를 자동으로 체크할 수 없습니다. 대신에, 개발자에게 사용자의 data를 처리할 수 있는 가장 좋은 방법을 찾도록 맡깁니다.

확실히, 이것은 긍정적인 측면과 부정적인 측면이 있습니다. 긍정적인 측면은 개발자가 자신이 원하는대로 할지 안할지 모르는 시스템과 싸움을 할 필요가 없다는 겁니다. 이 측면은 개발자에게 자신의 상황에 맞춰서 data를 좀 더 융통성있게 다루도록 합니다. 안타깝게도, 그 반대도 사실입니다. 개발자가 필터링과 유효성 검사에 대해 생각하지 않고, 사용자에게 그대로 보여주는 일은 무척 쉬운 일입니다. 만약 누군가가 실행되는 자바스크립트에 $_GET 변수로 아래와 같은 문자열을 입력했다고 생각해보세요
http://mysite.com?foo=
이것은 무척 간단한 예제였습니다. 그러나, 이것은 많은 가능한 공격들에게 문을 열어놓는 것입니다. 특별히, 필터링을 전혀 하지 않는다면요. 그래서 PHP 개발자는 이것을 막기 위해 무엇을 할 수 있을까요? 누구나 쉽게 얘기하지만, 구현하기는 가장 어려운 한가지의 방법이 있습니다. 입력값을 필터링하고 출력값을 제어하는겁니다.

고맙게도 PHP(와 사용자들은)는 이 위험을 완화시켜줄 방법을 제안했습니다. 첫번째, 입력값 제어입니다. PHP에는 입력값 검사를 쉽게 할 수 있는 문자열 처리 함수들이 있습니다. 그러나, 하나만 고르라고 하면 융통성뿐만 아니라 신뢰성도 갖고 있는 함수 filter_var가 있습니다. 이 함수는 입력값의 필터링과 유효성 검사 모두에 쓸 수 있습니다. 여기 예제가 있습니다.

여기에 data의 유효성 검사들을 돕고, 유효하지 않은 입력값을 걸러줄 수 있는 필터 함수 목록이 있습니다.

지금까진 입력 부분을 살펴 봤습니다. 그럼 출력 부분은 어떨까요? 출력 부분을 도와줄 몇 가지 함수가 역시 있습니다.
  • htmlspecialchars: 이 내장 함수는 저장된 값의 HTML 요소들을 HTML 인코딩 버전으로 바꿔줍니다. 이 함수를 호출할 때는 꼭 optional encoding 값을 꼭 줘야합니다. 그래야 모든 다른 종류의 출력값 제어 이슈에 도움을 줍니다.

  • 외부 출력 제어 라이브러리들을 사용합니다. PHP에는 많은 템플릿 라이브러리들이 있고 다른 것들보다 출력값 제어에 뛰어난 성능을 가진 라이브러리가 있습니다. 하나의 예로 Symfony와 관계된 Twig 프로젝트가 있습니다. Twig는 기본적으로 당신의 페이지에 HTML이 들어가는걸 막아줌으로써 출력값을 제어합니다. 사실 당신은 어딘가에서 실수할 위험을 줄이기 위해 템플릿의 어디에서 이 기능을 꺼야할 지 지정해야 합니다.
당신이 Twig를 적용했다는 것만으로 당신의 사이트가 자동적으로 안전해지는 것은 아닙니다. 모든 라이브러리와 툴들은 각자의 한계와 결함이 있고, 그래서 당신은 여전히 이 라이브러리와 툴들이 제대로 모든 곳에서 작동하고 있는지 확인할 필요가 있습니다. 당신 사이트의 "많은 곳"에서가 아니구요. 여러 툴들이 이런 곳들을 찾도록 도와줍니다. 좋은 보안 스캐닝 소프트웨어로 당신의 사이트를 점검해보세요.

사이트간 요청 위조

마지막으로 저는 웹사이트 사이에 널리 퍼져있는 또 다른 중요한 이슈에 대해서 얘기하고자 합니다. 이것은 PHP에 대한 것만은 아닙니다. 이 약점은 사이트간 요청 위조라고 불리고 잡아내기 무척 어렵습니다. 이 공격이 매우 위험한 이유는 사용자 자신의 아이디로 자신이 직접 보내는 것처럼 목표 사이트를 공격하기 때문입니다. 예를 들어, 어떤 URL/resource가 당신의 app에서 다른 사용자에게 어드민 권한을 주고 그 권한으로 의심받지 않는 사용자들에게 특별한 링크를 클릭하도록 한다면, 보호받지 않는 요청이 공격을 실행하는동안 당신은 모를 수 있을 가능성이 매우 높습니다.

PHP 기반 애플리케이션에는 이런 공격을 막을 수 있는 몇가지 방법이 있고 간단하게 구현할 수 있습니다. 첫번째는 당신의 애플리케이션을 만들때 HTTP 동작의 차이점을 아는 것입니다. 당신이 HTTP request/response 구조의 기초에 대해 잘 알고 있다면, 당신은 이미 GET 과 POST가 무엇을 위한 것인지 알고 있을 겁니다. 그렇지 않다면, 간단히 요약을 해보겠습니다. Get request 는 보통 당신의 브라우져에서 http://owasp.org 같은 사이트에 갈 때 일어납니다. 브라우져는 서버에게 page의 data를 "get" 하고 다시 보냅니다. 반면에 POST는 ("posting") data를 서버에 전송합니다.

CSRF 공격은 보통 취약한 GET URL을 목표로 합니다. 경험에 기반한 법칙을 한가지 이야기 하면 어플리케이션을 계획할 때 GET request를 항상 동일하게 만들어야 합니다. 즉, GET request 는 어플리케이션의 상태에 영향을 주지 않고 반복적으로 실행될 수 있어야 합니다. 만약 당신이 사용자가 무언가를 바꾸게 하거나 데이터를 받기를 원한다면 POST를 쓰는 것이 좋습니다.

"그럼, 나의 POST request 는 어떻게 보호할까?" 이렇게 물어볼 수 있겠죠. "같은 방법으로 하면 여전히 취약하지 않을까?" 짧게 말하면 "예" 입니다. (최소한 기본옵션으로 쓸때는) PHP의 POST request는 반복적으로 사용되는 것을 막아줄 어떠한 것도 없습니다. CSRF 토큰을 넣어봅시다. CSRF 토큰이란 form 데이터(보통 사이트의 form 에서 오는 POST 데이터) 에 삽입된 데이터 조각(랜덤하게 만들어진 문자열)입니다. 똑같은 문자열이 사용자의 세션에 저장되고 POST 가 전송될 때 유효화됩니다.

여기에 Anthony Ferrara가 RandomLib 라이브러리를 이용해서 토큰을 만든 예제가 있습니다.
getMediumStrengthGenerator();
$token = $generator->generateString(64);
$_SESSION["token"] = $token;
?>
이 세 라인은 토큰을 만들고 사용자의 세션에 저장시킵니다.(세션은 사용자가 로그인하지 않아도 만들어집니다.) $token의 값은 HTML form의 hidden 필드로 페이지로 전송됩니다. form이 전송되었을 때 값을 서로 비교해보게 됩니다.

이 간단한 방법을 사용해서, 당신은 이 request가 당신의 site 에 있는 페이지에서 온 것인지 확인할 수 있습니다. 문자열을 페이지가 로드될 때 마다 랜덤하게 만듦으로서 당신의 사이트를 더 신뢰받게 만들 수 있습니다.

그러나 기다리세요, 더 많은 보완점들이 있습니다.

저는 여기서 OWASP top 10 리스트 중 3가지만 소개해드렸습니다. 아직도 7개의 중요한 이슈들이 남아있습니다. 고맙게도 PHP application의 위험을 완화시켜줄 수 있는 많은 기사들이 웹에 있습니다. 그래도, 한 가지 미리 알려드리면 당신이 읽은 기사의 날자를 확인하세요. 어떤 기사들은 오래된 것이고 이런 이슈들에 대해서 가장 최신의, 가장 신뢰받는 방법을 제공하지 못할 수 있습니다.

저는 세가지 이슈에 대해 여러분들에게 도움이 되었기를 바랍니다. 개발하는데 있어서 보안은 모든 개발 과정중에 모든 개발자들의 마음에 있어야 합니다. 보안은 마지막에 추가되서는 안됩니다. 만약 당신이 좋은, 튼튼한, 그리고 안전한 코드를 처음부터 만들고 효율적인 툴을 사용한다면, 당신 자신과 당신의 회사를 많은 고민에서 완전히 구할 수 있을겁니다.
TAG :
댓글 입력
자료실