문서의 이전 판입니다!


JSP 보안 개발 가이드

1절. 보안 대책

1. 스크립트 삽입 취약점

가. 취약점 상세 내용 및 보안 대책

Javascript, Vbscript 등 PC의 웹브라우저에서 실행되는 클라이언트 사이트 스크립트를 다른 사용자의 웹브라우저에서 실행하도록 함으로서 웹 브라우저를 제어하여 PC를 공격하는 취약점을 말한다. 공격에 사용되는 유형은 크게 두가지로 분류할 수 있다.

(1) Reflected XSS

이 유형의 XSS는 클라이언트 측에서 전송된 데이터가 서버측에서 즉시 처리된 후 사용자에게 응답으로 전송되는 돌아오는 경우에 해당한다. 다음과 같은 상황에서 발생이 가능하다.

  1. 검색 페이지에서 입력된 검색어가 검색 결과 페이지에 표시되는 경우
  2. URL에 포함된 특정 파라미터가 응답 페이지에 hidden 속성으로 포함되어 있는 경우

위의 두가지 유형은 본질적으로는 동일한 내용이다.
이러한 유형의 XSS 는 사회 공학적 방법 등을 통해서 공격 스크립트가 포함된 URL을 타인이 실행하도록 유도함으로서 (예를 들면 메신저로 링크를 전송하거나, 게시판에 URL을 링크로 걸어놓고 클릭을 유도하는 방식 등) 공격에 이용될 수 있다. 다만, 공격자가 강제적으로 타 사용자의 PC에서 공격 스크립트가 실행되도록 할 수는 없으므로 일반적으로 심각한 취약점으로 분류되지는 않는다.
그러나 공공기관이나 금융권과 같이 신뢰도가 중요한 대상인 경우에는 이 유형의 XSS 도 모두 제거해야 하며, 그 외에도 대외에 공개된 웹사이트라면 일반적으로 제거하도록 하는 것이 좋다.

(2) Stored XSS

게시판과 같이 사용자가 입력한 정보가 서버측 DB에 저장되어 있다가 타 사용자가 해당 정보를 열람할 때 DB에 저장되어 있는 정보를 가져와 화면에 출력하는 방식의 웹 애플리케이션에서 발생하는 취약점으로서, XSS의 가장 핵심적인 취약점이라고 할 수 있다. 불특정 다수를 대상으로 열람자의 웹브라우저에서 공격 스크립트를 실행할 수 있기 때문에 심각한 위협이 발생한다.
공격자가 XSS를 목적으로 악성 스크립트를 삽입하여 둘 수 있는 위치는 비단 게시판의 글 본문 뿐만 아니라 제목, 작성자 이름, 날짜, 첨부파일 이름 등이 모두 가능하며, 또한 게시판 외에도 사용자 프로필의 닉네임, 주소, 전화번호, 등 사용자가 입력이 가능한 모든 데이터에 스크립트가 삽입될 수 있다.

  • 게시판 : 제목, 본문, 작성자, 날짜, 첨부파일 이름, 태그, 분류 등
  • 사용자 프로필 : 이름, 닉네임, 주소, 전화번호, 이메일, 직업, 기타 입력정보
  • 기타 사용자가 입력 가능한 모든 데이터

나. 보안 대책

XSS에 대한 보안 대책은 적용할 대상에 따라 다음의 두가지 경우를 나누어 적용해야 한다. 먼저 태그를 사용할 필요가 없는 경우와 태그의 사용을 허용해야 하는 경우이다.

  • 태그를 사용할 필요가 없는 경우
    사내 게시판이나 reflected XSS의 경우와 같이 문자열 내에 HTML 태그를 허용할 필요가 없는 경우
  • 태그를 사용해야 하는 경우
    대외 고객용 게시판, 커뮤니티 게시판 등과 같이 사용자의 필요에 의해서 문자열 내에 태그를 사용해야만 하는 경우
(1) 태그를 허용할 필요가 없는 경우 (사내 게시판, Reflected XSS)

태그를 허용할 필요가 없는 사내 게시판 등 단순 게시판의 경우는 사용자가 입력하는 모든 데이터에서 <, > 문자를 HTML Special Character 로 인코딩 함으로서 XSS를 완전히 차단할 수 있다. HTML Special Character 로 인코딩 된 코드는 웹브라우저에서 화면에 출력될 때는 정상적인 문자열로 출력되지만, 태그나 스크립트로서는 작동하지 않기때문에 XSS 로부터 안전하게 된다.

인코딩 대상 문자 HTML Special Character 코드
< &lt;
> &gt;


Reflected XSS 는 화면에 표시되는 모든 변수값들 (hidden field 포함)에 대해서 발생 가능하므로 모든 출력값에 대한 검증이 수행되어야 한다.

JSP

따로 제공하는 인코딩 함수가 없으므로 직접 replace 문을 통해 <, > 를 필터링하는 함수를 작성하여 사용한다.

resultStr = resultStr.replaceAll("<", "&lt;");
resultStr = resultStr.replaceAll(">", "&gt;");

한편, jsp 에서는 프리젠테이션 페이지를 작성할 때, JSTL 을 사용하면 모든 출력값이 자동으로 인코딩되므로 따로 replace를 할 필요가 없으므로 편리할 수 있다. (개발 단계라면 권고할 만 함)

<c:out value="${value}" />
(2) 태그를 허용해야 하는 경우 (공개 게시판)
게시판의 태그 허용에 대한 대응

공개 게시판과 같은 커뮤니케이션 중심의 웹 애플리케이션에서는 사용자들이 게시물을 꾸미거나 링크, 이미지, 동영상 등의 사용을 원하기 때문에 HTML 태그를 완전히 금지할 수 없다. 따라서 <, >를 필터링하되, 사용자가 사용해야하는 태그에 대해서는 <, > 를 필터링해서는 안된다.
따라서 웹사이트 운영자는 자신의 게시판에서 사용자에게 허가할 최소한의 HTML 태그를 선별한 후, 각각의 태그들에 대해서만 역변환 (즉, &lt;, &gt; 등으로 replace 된 <, >를 다시 원래대로 되돌리는 것)해야 한다.
또한 해당 태그들을 허용할 경우 발생 가능한 위협들에 대해서 대처하기 위한 별도의 방어 코드를 추가로 작성해야 한다. 방어 코드는 다음의 두 가지 방식을 적용할 수 있다.

가. 단어 blacklist 를 필터링 하는 방식

이 방식은 XSS 공격에 자주 사용되는 단어를 필터링하여 사용할 수 없도록 하는 방식이다
이 방식은 구현하기가 간단하지만, blacklist에 포함되지 않는 코드가 공격에 사용될 경우 막을 수 없으며, 또한 필터링 우회 기법이 발전함에 따라 필터링 자체가 우회될 가능성이 있다. 또한 blacklist로 선정된 단어를 사용자가 게시물에 사용할 수 없다는 단점이 있다.

blacklist 코드 예
// blacklist 를 필터링하여 사용할 수 없도록 함
test_str_low= test_str.toLowerCase();
test_str = test_str_low;
test_str = test_str.replaceAll("javascript", "x-javascript");
test_str = test_str.replaceAll("script", "x-script");
test_str = test_str.replaceAll("iframe", "x-iframe");
test_str = test_str.replaceAll("document", "x-document");
test_str = test_str.replaceAll("vbscript", "x-vbscript");
test_str = test_str.replaceAll("applet", "x-applet");
test_str = test_str.replaceAll("embed", "x-embed");  // embed 태그를 사용하지 않을 경우만
test_str = test_str.replaceAll("object", "x-object");    // object 태그를 사용하지 않을 경우만
test_str = test_str.replaceAll("frame", "x-frame");
test_str = test_str.replaceAll("grameset", "x-grameset");
test_str = test_str.replaceAll("layer", "x-layer");
test_str = test_str.replaceAll("bgsound", "x-bgsound");
test_str = test_str.replaceAll("alert", "x-alert");
test_str = test_str.replaceAll("onblur", "x-onblur");
test_str = test_str.replaceAll("onchange", "x-onchange");
test_str = test_str.replaceAll("onclick", "x-onclick");
test_str = test_str.replaceAll("ondblclick","x-ondblclick");
test_str = test_str.replaceAll("enerror", "x-enerror");
test_str = test_str.replaceAll("onfocus", "x-onfocus");
test_str = test_str.replaceAll("onload", "x-onload");
test_str = test_str.replaceAll("onmouse", "x-onmouse");
test_str = test_str.replaceAll("onscroll", "x-onscroll");
test_str = test_str.replaceAll("onsubmit", "x-onsubmit");
test_str = test_str.replaceAll("onunload", "x-onunload");
}
나. 태그의 property를 검증하는 방식

사용을 허가할 각각의 태그들에 대해서 property 들에 대해 개별적으로 유효성 검사를 실시하여 정상적이지 않은 방식으로 사용된 property를 걸러내는 방식이다. 이 방식은 구현하기가 좀더 복잡하지만 허가되지 않은 모든 형태의 입력을 거부하는 방식이므로 우회될 가능성이 낮아 보다 안전하며, 필터링이 사용자의 게시물의 내용을 손상시키지 않는 장점이 있다.
예를 들면 <img> 태그에 대해 다음과 같은 규칙을 적용하여 규칙이 위반된 property는 모두 삭제하는 방식이다.

img 태그의 허가 규칙
- img 태그는 property에 src, width, height 만 허용(그 외의 모든 property 는 무시)
- src 는 http://- 로 시작하는 이미지 파일 경로만 허용 (확장자로 . jpg .gif 등만 허용)
- width, height 의 값은 숫자만 허용

이 방식의 대책을 적용하기 위해서 고려해야 할 요소들은 다음과 같다.

  • 최소한의 HTML 태그만 허용한다
  • 각 태그별로 필수적인 프라퍼티만 허용한다
  • onClick, onerror 등 이벤트 유형의 프라퍼티는 허용하지 않는다
  • 각 프라퍼티에 삽입되는 데이터의 형식을 엄격하게 제한한다. (예 : “src” 프라퍼티에 URL 주소만 입력 가능해야 하며 “javascript:alert(‘test’);” 등과 같은 내용이 입력되어서는 안됨)

새로운 HTML 규약의 개발, 웹브라우저의 발전 등으로 인해 XSS 공격 패턴은 계속 새로운 패턴이 개발될 것이기 때문에, XSS 에 대한 필터링 로직 역시 지속적으로 테스트되고 수정되어야 할 필요가 있다.

2. SQL Injection 취약점

가. 취약점 상세 내용 및 보안 대책

웹 사이트는 DBMS와 연동하므로 웹 어플리케이션에서 사용자의 입력값을 통하여 데이타 트랜잭션이 발생함. 만일 사용자 입력값에 대한 유효성 검증이 누락될 경우, 악의적인 사용자는 정상적인 입력값 대신 SQL 쿼리문이 포함된 조작된 입력값을 전송하여 서버측 쿼리문의 구조를 변경할 수 있으며, 이를 통해서 사용자 인증을 우회하거나 데이터베이스의 정보를 유출시키고 서버의 시스템 명령어를 실행시킬 수도 있음.

나. 보안 대책

(1) Database 운영 레벨에서 SQL Injection 방어 대책

DB 또는 웹사이트 운영자가 SQL Injection을 방어하기 위해서는 기본적으로 다음과 같은 사항에 유의하여 관리가 필요하다.

  • DB 계정에 최소 권한 부여
    • 웹사이트에서 사용되는 DB 계정에 최소한의 권한만 부여해야 함
    • DB 계정에 system 권한 (sysdba, sa, root 등)을 부여해서는 안됨
  • 에러 노출 방지
    • 에러 발생시 웹서버 또는 WAS 에서 정형화된 에러 페이지를 출력하도록 설정하여 에러에 의한 DB 정보 노출을 방지함
  • SQL Server의 경우 xp_cmdshell 확장 프로시저를 제거함

그러나 위와 같은 방법만으로는 SQL Injection을 방어할 수 없으므로 다음과 같이 소스코드 레벨에서 취약점을 제거하기 위한 대책을 적용해야 한다.

(2) 소스코드 레벨에서 Prepared Statement 구문을 통한 SQL Injection 방어

웹사이트가 개발중에 있다면 DAO 객체 또는 DB 연결 구문에서 Prepared Statement 구문을 이용하여 DB 트랜잭션을 처리함으로서 SQL Injection 취약점이 근본적으로 발생하지 않는 구조로 웹사이트를 개발해야한다.
Prepared Statement 구문은 SQL 쿼리를 선처리하여 컴파일 한 후, 이후 입력되는 변수값을 항상 문자열 변수로 다루기 때문에 사용자가 어떤 악의적인 SQL 구문을 변수에 삽입해도 SQL 문에 영향을 미치지 않아 SQL Injection이 발생하지 않는다.

JSP 에서 Prepared Statement 구문 사용

Jsp 에서 prepared Statement 구문을 구성하는 간단한 예시는 다음과 같다.

prepared Statement 구문
// 객체 선언
Connection con;
PreparedStatement pstmt;
ResultSet rs;

// DB연결 생성
con = DriverManager.getConnection(-------);

// Query 문 및 변수값 세팅
pstmt = con.prepareStatement(“Select  name, id, num from Membertable where id=? And passwd=?“);
pstmt.setString(1, "test");             // 변수형에 따라 setXXXX 사용 ex) setInt, setDouble
pstmt.setString(2, "testpwd");

// Query 문 실행
rs = pstmt.executeQuery();          // executeQuery, executeUpdate

// 결과 처리
if (rs.next()){
	// rs 객체로부터 칼럼 이름을 이용한 결과값 추출하여 사용자 펑션의 실행
	Run_user_function (…, rs.getString(name), rs.getString(id), rs.getint(num));
	// 또는 인덱스를 이용한 결과값 추출
	Run_user_function (…, rs.getString(1), rs.getString(2), rs.getint(3));

}

// 객체 close
rs.close();
pstmt.close();

※ 참고 : 저장 프로시저를 이용하여 DB 트랜잭션을 처리하는 방법 역시 prepared statement 구문과 동일한 효과를 기대할 수 있다.

(3) 소스코드 레벨에서 필터링 코드를 통한 SQL Injection 방어

만일 이미 운영중인 웹사이트에서 DB 트랜잭션 처리를 Prepared Statement 구문으로 수정하는 것이 어려울 경우, 차선책으로 특수 문자 및 Query 문자열에 대한 필터링을 통해서 SQL Injection 이 발생하지 않도록 해야 한다.
사용자측으로부터 전달되는 파라메터 중 DB 쿼리문에 사용되는 모든 파라미터 (게시판 ID, 글 번호, 검색 keyword, 사용자 번호, 항목 번호, 분류명칭 등)로부터 입력되는 데이터에 대하여 아래와 같이 특수 문자 및 Query 예약어를 필터링해 에러 처리를 하거나 ‘\’ 문자 또는 공백 문자로 치환한다.

필터링 할 특수 문자 및 구문
' union
select
insert
# drop
( update
) from
; where
@ join
= substr (oracle)
* user_tables (oracle)
/ user_table_columns (oracle)
+ subsring (ms-sql)
information_schema (mysql) sysobjects (ms-sql)
table_schema (mysql) declare (ms-sql)