시큐어 코딩

[!NOTE] 안전한 SW 를 만들려면 시큐어 코딩을 꼭 익혀야 하며 이를 실무에 손쉽게 적용하기 위해서는 프레임워크 도입을 권장합니다.

SQL Injection

OWASP 에서 1위로 선정한 위협으로 막기가 어렵지 않고 공격 당했을 경우 파급력이 크므로 꼭 고려해야 할 취약점입니다.

만약 사용자 id와 암호를 입력받아서 로그인을 처리하는 페이지가 있을 경우 SQL Injection 을 고려하지 않을 경우 다음과 같이 코딩하게 됩니다.

$sql="SELECT * FROM users WHERE userid='$userid' and password='$password'";

자바의 경우

String sql = "SELECT * FROM users WHERE userid='" + request.getParameter("id") + "'" + " and password='" + request.getParameter("password") + "'";;

위와 같은 코드는 클라이언트가 보낸 문자열을 검증없이 사용하므로 공격자가 password 에 ' or '1' = '1 라는 문자열을 넣으면 or 뒤의 문장이 참이므로 id와 암호를 몰라도 관리자로 로그인이 가능해져 버립니다.

SQL Injection

이 공격을 막으려면 위와 같이 동적 쿼리를 사용하지 말고 준비된 문장(Prepared Statement)와 매개변수 바인딩(Bound Parameter) 을 사용해야 합니다.

PHP 의 경우 mysql_* 같은 직접 데이타베이스에 연결하는 함수보다는 PDO 를 사용하는 게 좋으며 Java 의 경우 JDBC 직접 사용보다는 MyBatis나 Hibernate 같은 프레임워크를 사용하는 것이 좋습니다.

PHP 라라벨의 경우 쿼리 빌더를 사용하여 아래와 같은 쿼리를 입력한 경우

DB::select('select * from users where id = :userid and password = :password', ['userid' => 'myid', 'password' => 'mypasswd']);

다음과 같이 변환되므로 안전하게 실행됩니다.

$s = $dbh->prepare('SELECT * FROM users WHERE userid = :userid and password = :password') ;
$s->bindParam(':userid ', $userid );
$s->bindParam(':password', $password);

인증 및 세션 관리 취약점

인증이나 세션은 적절하게 보호되어야 하며 다음과 같이 관리되면 안 됩니다.

  1. 사용자 인증 정보를 해시나 암호화로 보호하지 않음

  2. 세션 ID 를 GET 파라미터로 전달하여 URL 에 노출

    http://example.com/user/login;jsessionId=39ju9jcsejcj3r
  3. 세션 타임 아웃이 설정되지 않았거나 너무 긴 경우

  4. 세션에 추가되는 정보가 유출 가능한 경우. 예로 관리자로 로그인했을 경우 admin=1 이 설정

위 문제를 해결하려면 HTTPS 를 적용하고 세션 데이타를 암호화 하는 게 좋습니다.

라라벨의 경우 다음과 같이 자동으로 세션 값을 암호화해서 전달합니다.

HTTP/1.1 200 OK
Server: nginx/1.9.9
Content-Type: text/html; charset=UTF-8
Connection: keep-alive
Cache-Control: no-cache
Date: Fri, 08 Apr 2016 17:40:42 GMT
Set-Cookie: XSRF-TOKEN=eyJpdiI6IlpONm1OKzBkSGw5WVBOeHZFRzc0WGc9PSIsInZhbHVlIjoiSzBnc01DVXlwVFwvUjc1NnA5YW93NllST3MrYUtYenZMSE5ERG9Ha0hcL2tFWEhMS3d5bjVtb1NBN296RW9EeG5EZ2t1RGdGUTVGZjZXQ2l5bm1wOUFTZz09IiwibWFjIjoiOWY2MjhlZGFlMDFkODYzMGZhMWQwM2Y3MTFiYTBkNGVhMGNhMzQzMmViOWVjNjY3ZmYwMWNmOTFlNTA3NGFjMyJ9; expires=Fri, 08-Apr-2016 19:40:42 GMT; Max-Age=7200; path=/
Set-Cookie: laravel_session=eyJpdiI6IldzcTFBbEpTTk9sZXJyUkRjbW9GblE9PSIsInZhbHVlIjoiNlp6RnVmdUx1eFwvVXVwY2xwVEE1bjN5eW9lbm9cL3BqXC9GVUNESTA1bmdcL1NHWkhYRDJ5U0JRbGtqTEFYV09QcVZuYUNTVFZzME56bkVZdUErNDlCNnZnPT0iLCJtYWMiOiIyODkyYzk5YmQ1MjBjNDQ2YTRlYWU3Y2I1Yjk1OTdhOWU5MWFmNmRlYzQ3ZWM1MDEwNTdhNWIzMjA1NGI0ZTBjIn0%3D; expires=Fri, 08-Apr-2016 19:40:42 GMT; Max-Age=7200; path=/; HttpOnly

크로스 사이트 스크립팅(XSS; Cross Site Scripting)

XSS 는 사용자의 입력값을 사용하여 동적인 내용을 표시하는 웹 페이지에서 외부 입력값에 대한 검증 없이 사용자에게 출력으로 내보내는 경우에 발생합니다.

즉 사용자가 글 쓰기가 가능한 게시판을 만들 경우 다음과 같이 게시글을 입력했을 때 이 글을 클릭한 사용자에게 바로 출력할 경우 자바스크립트가 실행되게 됩니다.

<script>alert("Welcome");</script>

위와 같은 취약점을 XSS 라 하며 이를 사용하여 공격자는 악성코드를 유포하거나 사용자의 쿠키를 갈취할 수 있습니다.

사용자의 입력은 꼭 소독해서 사용해야 합니다.

소독

PHP 는 htmlentities() 함수를 사용하여 입력 값을 소독할 수 있으며 Java 의 경우 OWASP 의 java encoder 라이브러리나 네이버의 lucy-xss-filter를 사용하면 됩니다.

취약한 직접 객체 참조

얼마전 모 서점에서 주문 번호만 입력하면 다른 이의 배송 정보와 결재 정보를 그대로 볼 수 있는 취약점이 있었습니다. 1

이런 문제를 해결하려면 사용자가 직접 입력한 객체의 소유자를 검증하고 인증 여부를 확인해야 합니다.

<?php
session_start();
if (!(isset($_SESSION['login']) && $_SESSION['login'] != '')) {
header ("Location: login.php");
return;
}
if (!isOwner($request)) {
http_response_code(403);
return;
}
}
?>

위와 같은 코드를 app 로직마다 추가해야 하는데 실수로 빼 먹으면 보안 문제가 발생하므로 프레임워크를 도입하고 필터나 미들웨어같이 app 로직 전에 수행되는 기능을 사용하여 처리하는 게 좋습니다.

라라벨 미들웨어 구성도

크로스 사이트 요청 변조(CSRF)

CSRF 는 인증된 사용자를 통해 공격자가 원하는 명령을 수행하게 하는 기법입니다.

공격자가 메일이나 사이트 게시글로 이미지나 링크를 전송하여 물품구매를 수행하거나 사이트 글을 변조하는 등의 작업을 수행할 수 있습니다.

CSRF 를 막기 위해서는 인증된 사용자라 하더라도 중요한 작업을 수행할 경우 다시 검증을 수행하도록 하는 방법입니다.

즉 POST/PUT/DELETE 등의 HTTP Method 수행시 FORM 등에 추측이 불가능한 임시 토큰을 삽입한 후에 서버에서 검증하면 됩니다.

프레임워크가 없이 이 공격을 막으려면 app 소스를 많이 수정해야 하므로 꼭 프레임워크 도입을 권장합니다.

라라벨의 경우 POST/PUT/DELETE HTTP Method 를 수행하면 자동으로 서버에서 CSRF 토큰을 검증하고 에러가 발생시 예외 처리하도록 동작하고 있으며 app\Http\Kernel.php 에 VerifyCsrfToken 미들웨어가 이 처리를 담당합니다.

protected $middlewareGroups = [
'web' => [
\App\Http\Middleware\EncryptCookies::class,
\Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
\Illuminate\Session\Middleware\StartSession::class,
\Illuminate\View\Middleware\ShareErrorsFromSession::class,
\App\Http\Middleware\VerifyCsrfToken::class,
\App\Http\Middleware\OwnerCheck::class,
],
];

컴포넌트의 취약점 점검

"알려진 취약점이 있는 컴포넌트 사용(Using Components with Known Vulnerabilities)" 은 OWASP 의 2013년 Top 중 9위인 위협으로 컴포넌트, 라이브러리, 프레임워크등의 보안 취약점을 악용하여 공격에 노출되게 됩니다.

Linux 는 yum 이나 apt-get 같은 패키지 관리자 기능을 제공하므로 운영 체제나 인프라 SW의 패치가 매우 쉬워졌지만 app 에서 사용하는 library 나 컴포넌트는 변경시 영향도때문에 교체가 쉽지 않습니다.

이 취약점을 예방하기 하려면 다음과 같은 방법이 있습니다.

  1. 개발시 외부 컴포넌트는 패키지 관리자를 사용하여 관리합니다. 패키지 관리자는 사용하는 언어나 프레임워크에 따라 다르며 자바의 경우 maven, gradle, PHP 는 composer, ruby 는 bundle, python 는 pip 등이 있습니다. 패키지 관리자를 사용하면 특정 컴포넌트를 교체하기가 용이해 집니다.

  2. 내/외부 컴포넌트들의 버전 및 의존성을 식별할 수 있도록 관리합니다. 빌드된 바이너리의 버전 관리는 유의적 버전(Semantic Versioning)을 따르는 것이 좋습니다.

  3. 인터넷진흥원의 보호나라 & KrCERTCVE 취약점 데이타베이스 및 보안 메일링리스트등을 구독하여 최신 동향을 파악합니다.

  4. 상용 repository manager(Sonatype nexus등) 에서는 repos 에 있는 라이브러리를 스캔하여 취약점 보고서를 생성하는 기능 제공하는 경우가 있으므로 여건이 된다면 이런 제품을 사용하는 것이 좋습니다. Sonatype nexus 의 취약점 보고서

[1] http://www.insight.co.kr/newsRead.php?ArtNo=56987