프로필사진
owgno6
CODELIB
Recent Posts
Recent Comments
«   2024/05   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
Archives
Today
Total

티스토리 뷰

Secure Coding Java - SQL injection

 

 

 

입력 데이터 검증 및 표현

   프로그램 입력 값에 대한 검증 누락 또는 부적절한 검증, 데이터의 잘못된 형식지정, 일관되지 않은 언어셋 사용 등으로 

   인해 발생되는 보안약점으로 SQL 삽입, 크로스사이트 스크립트(XSS) 등의 공격을 유발할 수 있다.

 

 


SQL 삽입(Improper Neutralization of Special Elements used in an SQL Command, SQL Injection)



1. 정의


데이터베이스(DB)와 연동된 웹 어플리케이션에서 입력된 데이터에 대한 유효성 검증을 하지 않을 경우, 공격자가 입력 폼 및 URL 입력란에 SQL 문을 삽입하여 DB로부터 정보를 열람하거나 조작할 수 있는 보안약점을 말한다.

 

 

<그림 1> SQL 삽입


<그림 1>에서 나타낸 것처럼, 취약한 웹 어플리케이션에서는 사용자로부터 입력된 값을 필터링 과정없이 넘겨받아 동적 쿼리(Dynamic Query)를 생성한다. 이는 개발자가 의도하지 않은 쿼리가 생성되어 정보유출에 악용될 수 있다.

 



2. 안전한 코딩기법


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

 

preparedStatement 클래스를 사용할 수 없는 환경이라면, 입력값을 필터링 처리한 후 사용한다. 필터링 기준은 SQL구문 제한, 특수문자 제한, 길이제한을 복합적으로 사용한다.

 



3. 1 예제[1]


다음 <코드 1>은 안전하지 않은 코드의 예를 나타낸 것으로, 외부로부터 tableNamename의 값을 받아서 SQL 쿼리를 생성하고 있으며, name의 값으로 name' OR 'a'='a를 입력하면 조작된 쿼리를 생성하는 문자열 전달이 가능하다.

 


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


<코드 1> 안전하지 않은 코드의 예

 


이를 안전한 코드로 변환하면 다음 <코드 2>와 같다. 외부로부터 인자를 받는 preparedStatement 객체를 상수 스트링으로 생성하고, 인자 부분을 setXXX 메소드로 설정하여, 외부의 입력이 쿼리문의 구조를 바꾸는 것을 방지하는 것이 필요하다.

 


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


<코드 2> 안전한 코드의 예

 



3. 2 예제[2]

 

다음 <코드 3> 예제는 http request로부터 사용자 ID와 암호를 추출하여 SQL 질의문을 생성하고 있다. 사용자 ID guest' OR ' a'='a'-- 로 설정할 경우, 질의 문은 다음과 같이 생성된다.

 

SELECT * FROM members WHERE userId = 'guest' OR 'a'='a'-- AND password = ''

 

이 질의문에서 WHERE절이 항상 참이므로 암호가 올바르지 않더라도 사용자의 정보를 얻을 수 있다.

 

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
……
public class SqlInjectionSample extends HttpServlet
{
    private final String GET_USER_INFO_CMD = "get_user_info";
    private Connection con;
    
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws 
     ServletException, IOException
    {
        String command = request.getParameter("command");
        if (command.equals(GET_USER_INFO_CMD))
        {
            Statement stmt = con.createStatement();
            String userId = request.getParameter("user_id");
            String password = request.getParameter("password");
            String query = "SELECT * FROM members WHERE username '" + userId + 
                    "' AND password = '" + password + "'";
            stmt.executeUpdate(query);
    
        }
        ……
    }
    ……
 
}


<코드 3안전하지 않은 코드의 예

 



3. 3 예제[3] - makeSecureString 함수를 적용한 안전한 코드의 예

 

다음 <코드 4> 예제에서는 SQL 구문 생성 전에, 외부로부터 입력된 ID와 암호를 DB 질의문에 사용하기 안전한 형태로 함수(makeSecureString 함수)를 통하여 변경하였다.

 

makeSecureString 함수는 일반 문자열을 질의문의 인자로 사용해도 안전한 문자열로 바꾸는 함수이다. 이 함수는 다음 3가지의 제한 조건을 적용하여 안전한 문자열을 생성한다.

 

  • ID와 암호 같은 인자의 길이 제한 - 공격 구문을 작성하게 되면 일반으로 ID, 암호의 길이가 길어지게 된다. 따라서 인자의 길이를 일정 길이 이하로 제한하게 되면 인자를 통하여 공격 구문을 삽입하는 것이 힘들어진다.

  • 인자에 SQL문에서 쓰이는 예약어의 삽입을 제한 - 인젝션(Injection) 공격 구문을 작성하기 위해서는, SQL문에서 쓰이는 예약어가 쓰일 가능성이 높다. 따라서 SQL문에 쓰이는 명령어를 블랙리스트에 등록하고 인자에서 강제적으로 삭제함으로써 공격을 차단할 수 있다.

  • 인자에 알파벳과 숫자를 제외한 문자의 사용을 제한 - 공격 구문을 작성할 때, 특수 문자(가장 대표적인 예가 ' 이다)를 사용할 가능성이 높다. 따라서 알파벳과 숫자를 제외한 나머지 문자들을 인자에서 강제적으로 삭제함으로써 공격에 의한 피해를 방지할 수 있다.

 

다음 <코드 4> 안전한 코드의 예에서는, 위의 두 번째, 세 번째 제한 조건을 적용하기 위해서 다음 정규식(Regular expression)을 사용하였다.

 

[^\\p{Alnum}] | select | delete | update | insert | create | alter | drop

 

위 정규식에서 [^\\p{Alnum}]는 알파벳과 숫자를 제외한 나머지 문자를 의미하고, select, delete, update, insert, create, alter, dropSQL 문에서 사용되는 예약어들이다. 이를 널 string("")으로 대체하여 안전한 문자열로 만들었다.

 

이 외에도 makeSecureString 함수에서 생성되는 문자열을 좀 더 안전하게 만들기 위한 방법은 다음과 같다.

 

문자열의 길이 제한을 낮춘다.

 

정규식에 포함되는 단어의 개수를 높인다. 보다 정밀한 방어를 위해서는 악용 가능성이 있는 SQL procedure명이나 SQL 명령어들을 필터링할 정규식에 포함(블랙리스트 개념으로 작동)시킨다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
……
public class SqlInjectionSample extends HttpServlet
{
    //작업의 type을 지정한다.
    private final String GET_USER_INFO_CMD = "get_user_info";
    private Connection con;
 
    //id와 password의 최대 길이를 제한한다.
    private final static int MAX_USER_ID_LENGTH = 8;
    private final static int MAX_PASSWORD_LENGTH = 16;
 
    //select, delete update, insert 등 기존 명령어와 알파벳, 숫자를 제외한 다른 문자들을 
    //검출하는 정규식을 설정한다.
    private final static String UNSECURED_CHAR_REGULAR_EXPRESSION = "[^\\
        p{Alnum}]|select|delete|update|insert|create|alter|drop";
 
    private Pattern unsecuredCharPattern;
 
    //정규식을 초기화한다.
    public void initlalize()
    {
        unsecuredCharPattern = Pattern.compile(UNSECURED_CHAR_REGULAR_EXPRESSION, 
           attern.CASE_INSENSITIVE);
        //……
    }
 
    //입력값을 정규식을 이용해 필터링한 후 의심되는 부분을 없앤다.
    private String makeSecureString(final String str, int maxLength)
    {
        String securestStr = str.substring(0, maxLength);
        Matcher matcher = unsecuredCharPattern.matcher(securestStr);
        return matcher.replaceAll("");
    }
 
    //……
 
    //입력값을 받아 필터링한 후 쿼리로 만들어 처리한다.
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws
    ServletException, IOException
    {
        String command = request.getParameter("command");
        if (command.equals(GET_USER_INFO_CMD))
        {
            Statement stmt = con.createStatement();
            String userId = request.getParameter("user_id");
            String password = request.getParameter("password");
            String query = "SELECT * FROM members WHERE username '" + makeSecureString(userId,
                    MAX_USER_ID_LENGTH) + "' AND password = '" + makeSecureString(password,
                    MAX_PASSWORD_LENGTH) + "'";
 
            stmt.executeUpdate(query);
 
        }
        ……
    }
    ……
 
}
 


<코드 4> 안전한 코드의 예

 


3. 4 예제[4] - Blind SQL injection 공격에서 사용될 소지가 있는 공격구문들

 

다음의 <1> 예제들은 Blind SQL injection 공격에서 사용될 소지가 있는 공격구문들을 열거한 것이다. 특정한 코드를 선별해서 막는, 블랙리스트에 의거한 필터링 방식을 사용할 때에는 다양한 형태의 SQL 함수들을 이용한 공격방법들을 참고하여 리스트를 만들어야 한다.

 

  공격구문

 설명 

  IF (1=1) SELECT 'true' ELSE SELECT 'false'

 위험한 구문 허용 

  SELECT CHAR(0x66) 

 위험한 함수사용 허용  (다른 blind sql 공격구문에 자주 이용됨

  SELECT CONCAT(CHAR(75),CHAR(76),CHAR(77))

 위험한 함수사용 허용

  SELECT ASCII('a') 

 위험한 함수사용 허용

  SELECT header, txt FROM news UNION ALL SELECT name, pass FROM members

 위험한 구문 허용

  INSERT INTO members(id, user, pass) VALUES(1, "+SUBSTRING(@@version,1,10) , 10) 

 시스템 정보 노출 

 exec master..xp_cmdshell 'dir' 

 위험한 명령어 사용(시스템 down)

  SELECT ID, Username, Email FROM [User]WHERE ID = 1 AND ISNULL(ASCII(SUBSTRING((SELECT TOP 1 name FROM sysObjects WHERE xtYpe=0x55 AND name NOT IN(SELECT TOP 0 name FROM sysObjects WHERE xtYpe=0x55)),1,1)),0)>78--

 True, False를 통해 데이터 값을 유추할 수 있음

  IF (SELECT * FROM login) BENCHMARK(1000000,MD5(1))

 시스템 자원 소모 유도 

 WAITFOR DELAY '0:0:10'-- 

 시스템 자원 소모 유도 

 MD5(), SHA1(), PASSWORD(), ENCODE(), COMPRESS(), ROW_COUNT(), SCHEMA(), VERSION() 

 위험한 함수 사용

 bulk insert foo from '\\YOURIPADDRESS\C$\x.txt'

 Windows UNC Share를 악용

< 1> Blind SQL injection 공격에 사용될 소지가 있는 구문들


 

크게 if, union 등의 구문을 이용한 방법과, 각종 sql 함수(exec )를 이용한 다양한 공격방법들을 소개하고 있다. 필터링 코드의 완성도는 막아야할 공격방법, 즉 블랙리스트를 만드는 작성자가 얼마나 공격 패턴을 많이 알고 있느냐에 따라 좌우된다.

 



 

 

참고 문헌

 

[1] 시큐어 코딩 보안 가이드 
https://www.kisa.or.kr/public/laws/laws3_View.jsp?mode=view


[2] Java 보안 개발 가이드
http://wiki.wikisecurity.net/guide:java_


[3] 시큐어 코딩(Secure Coding) 1. SQL injection(SQL 삽입공격) 
http://needjarvis.tistory.com/175

 

[4] SQL Injection 시큐어코딩 
http://hackbyr0k.tistory.com/2

 


'Secure Coding' 카테고리의 다른 글

01 시큐어코딩(secure coding) 이란?  (0) 2018.08.11
댓글