보안 공부/소스코드 보안약점 진단

소스코드 보안약점 진단 - SQL 삽입(SQL Injection)

H.J.World 2021. 11. 2. 11:26
728x90
반응형

 

소스코드 보안약점 진단 // 소프트웨어 보안약점 진단 // SW 보안약점 진단과 같이 다양한 이름으로 불리는 진단 과업 중 하나이다.

SW개발보안은 해킹 등 사이버공격의 원인인 보안약점을 SW개발단계에서 사전에 제거하고 SW 개발 생명주기의 각 단계별로 수행하는 일련의 보안활동을 통하여 안전한 SW를 개발·운영하기 위한 목적으로 적용하는 개발체계이다.

해당 내용은 KISA에서 발간하는 취약점 진단 가이드 항목을 기준으로 작성한다.

 

1. SQL 삽입

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

취약한 웹 응용프로그램에서는 사용자로부터 입력된 값을 필터링 과정 없이 넘겨받아 동적쿼리(Dynamic Query)1)를 생성하기 때문에 개발자가 의도하지 않은 쿼리가 생성되어 정보유출에 악용될 수 있다.

 

가. 보안대책

PreparedStatement 객체 등을 이용하여 DB에 컴파일 된 쿼리문(상수)을 전달하는 방법을 사용한다. PreparedStatement를 사용하는 경우에는 DB 쿼리에 사용되는 외부입력값에 대하여 특수문자 및 쿼리 예약어를 필터링하고, 스트러츠(Struts), 스프링(Spring) 등과 같은 프레임워크를 사용하는 경우에는 외부입력값 검증모듈 및 보안모듈을 상황에 맞추어 적절하게 사용한다.

나. 보안대책

 

- 안전하지 않은 코드의 예 JDBC API -

다음은 안전하지 않은 코드의 예로, 외부로부터 입력받은 gubun의 값을 아무런 검증과정을 거치지 않고 SQL 쿼리를 생성하는데 사용하고 있다. 이 경우 gubun의 값으로 a' or 'a' = 'a 를 입력하면 조건절이 b_gubun = 'a' or 'a' = 'a' 로 바뀌어 쿼리의 구조가 변경되어 board 테이블의 모든 내용이 조회된다.

1: //외부로부터 입력받은 값을 검증 없이 사용할 경우 안전하지 않다.
2: String gubun = request.getParameter("gubun");
3: ......
4: String sql = "SELECT * FROM board WHERE b_gubun ='" + gubun + "'";
5: Connection con = db.getConnection();
6: Statement stmt = con.createStatement();
7: //외부로부터 입력받은 값이 검증 또는 처리 없이 쿼리로 수행되어 안전하지 않다.
8: ResultSet rs = stmt.executeQuery(sql);

 

 

- 안전한 코드의 예 JDBC API -

이를 안전한 코드로 변환하면 다음과 같다. 파라미터(Parameter)를 받는 PreparedStatement 객체를 상수 스트링으로 생성하고, 파라미터 부분을 setString, setParameter등의 메소드로 설정하여, 외부의 입력이 쿼리문의 구조를 바꾸는 것을 방지해야 한다.

1: String gubun = request.getParameter("gubun");
2: ......
3: //1. 사용자에 의해 외부로부터 입력받은 값은 안전하지 않을 수 있으므로, PreparedStatement
사용을 위해 ?문자로 바인딩 변수를 사용한다.
4: String sql = "SELECT * FROM board WHERE b_gubun = ?";
5: Connection con = db.getConnection();
6: //2. PreparedStatement 사용한다.
7: PreparedStatement pstmt = con.prepareStatement(sql);
8: //3.PreparedStatement 객체를 상수 스트링으로 생성하고, 파라미터 부분을 setString등의 메소드
로 설정하여 안전하다.
9: pstmt.setString(1, gubun);
10: ResultSet rs = pstmt.executeQuery()

 

 

- 안전하지 않은 코드의 예 MyBatis -

MyBatis Data Map은 외부에서 입력되는 값이 SQL 질의문을 연결하는 문자열로 사용되는 경우에 의도하지 않은 정보가 노출될 수 있는 공격 형태이다.

1: <?xml version="1.0" encoding="UTF-8" ?>
2: <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" 
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
3: ......
4: <select id="boardSearch" parameterType="map" resultType="BoardDto">
5: //$기호를 사용하는 경우 외부에서 입력된 keyword값을 문자열에 결합한 형태로 쿼리에 반영되므로
안전하지 않다.
6: select * from tbl_board where title like '%${keyword}%' order by pos asc
7: </select>

 

- 안전한 코드의 예 MyBatis -

외부 입력값을 MyBatis 쿼리맵에 바인딩할 경우 $ 기호가 아닌 # 기호를 사용해야 한다.
$ 기호를 사용하는 경우 입력값을 문자열에 결합하는 형태로 쿼리에 반영하므로 쿼리문이 조작될 수 있다.

1: <?xml version="1.0" encoding="UTF-8" ?>
2: <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" 
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
3: ......
4: <select id="boardSearch" parameterType="map" resultType="BoardDto">
5: //$ 대신 #기호를 사용하여 변수가 쿼리맵에 바인딩 될 수 있도록 수정하는 것이 안전하다.
6: select * from tbl_board where title like '%'||#{keyword}||'%' order by pos asc
7: </select>

 

- 안전하지 않은 코드의 예 C#-

다음 C# 코드는 외부 입력값을 SQL 쿼리에 직접 사용하고 있어, 쿼리의 구조가 변경될 위험이 있습니다.

1: public void ButtonClickBad(object sender, EventArgs e)
2: {
3: string connect = "MyConnString";
4: string usrinput = Request["ID"];
5: // 외부로부터 입력받은 값을 SQL 쿼리에 직접 사용하는 것은 안전하지 않다.
6: string query = "Select * From Products Where ProductID = " + usrinput;
7: using (var conn = new SqlConnection(connect))
8: {
9: using (var cmd = new SqlCommand(query, conn))
10: {
11: conn.Open();
12: cmd.ExecuteReader(); /* BUG */
13: }
14: }
15: }

 

- 안전한 코드의 예 C#-

파라미터 바인딩을 사용하여 쿼리의 구조가 변경될 위험을 제거해야 합니다.

1: void ButtonClickGood(object sender, EventArgs e)
2: {
3: string connect = "MyConnString";
4: string usrinput = Request["ID"];
5: //파라미터 바인딩을 위해 @ 을 사용합니다. 외부입력 값에 의해 쿼리 구조 변경을 할 수 없습니다.
6: string query = "Select * From Products Where ProductID = @ProductID";
7: using (var conn = new SqlConnection(connect))
8: {
9: using (var cmd = new SqlCommand(query, conn))
10: {
11: cmd.Parameters.AddWithValue("@ProductID", Convert.ToInt32(Request["ProductID"]);
12: conn.Open();
13: cmd.ExecuteReader();
14: }
15: }

 

오탐코드의 예

아래 코드에서는 외부 입력값이 위치하는 부분을 ?로 설정하고, 실행 시에 해당 파라미터가 전달되도록 수정함으로써 외부의 입력(name)이 쿼리의 구조를 변경시키는 것을 방지하고 있어 취약하지 않다.

1: import org.hibernate.Query
2: import org.hibernate.Session
3: ......
4: String name = request.getParameter(“name”);
5: Query query = session.createQuery(“from Student where studentName = ? “);
6: query.setString(0, name);
1: import org.hibernate.Query
2: import org.hibernate.Session
3: ......
4: String name = request.getParameter(“name”);
5: Query query = session.createQuery(“from Student where studentName = :name “);
6: query.setParameter(“name”, name);
728x90
반응형