티스토리 뷰

 

 

1. 개요

 

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

 

2. 보안대책

 

- 정적 쿼리 사용: PreparedStatement 객체를 이용하여 컴파일 된 정적 쿼리문(상수)으로 쿼리문의 구조가 외부의 값을 통해 바뀌지 않도록 한다. C#에서는 @, MyBatis에서는 #, JAVA 에서는 ?을 이용하여 파라미터 바인딩을 사용한다.

- 유효성 검사: 동적 쿼리를 사용해야 한다면, 입력값에 대하여 쿼리문의 구조를 변경할 수 있는 특수문자 및 쿼리 예약어를 검사(필터링)한 후 쿼리문 생성에 사용한다.
- 보안 모듈 사용: 스트러츠(Struts), 스프링(Spring) 등과 같은 프레임워크를 사용하는 경우에는 외부입력값 검증모듈 및 보안모듈을 상황에 맞추어 적절하게 사용한다.
- 불필요한 정보 노출 방지: 서버의 오류 정보가 노출되지 않도록 한다.
- 권한 관리: 웹 애플리케이션의 데이터베이스 사용자 권한을 최소화한다.

 

3. 코드예제

※ 아래 예제들은 KISA의 소프트웨어 개발보안 가이드, SW 보안약점 진단가이드의 예제를 옮겨놓았습니다.

 

[안전하지 않은 코드 - JDBC API]

- 외부에서 입력 받은 값을 별도 조치 없이 사용, SQL 질의문에 직접 데이터가 입력되고 있음

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

 

[안전한 코드 - JDBC API]

- 외부에서 전달 받은 값을 PreparedStatement 사용을 위해 ?로 바인딩하여 사용

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

 

[안전하지 않은 코드 - MyBatis]

- MyBatis에서 외부의 값을 $기호로 받을 경우 문자열이 쿼리에 반영되어 취약

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

 

[안전한 코드 - MyBatis]

- # 기호로 외부의 값을 받는 경우, 쿼리에 바인딩되어 양호

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

 

[안전하지 않은 코드 - Hibernate]

import org.hibernate.Query
import org.hibernate.Session
......
//외부로부터 입력받은 값을 검증 없이 사용할 경우 안전하지 않다.
String name = request.getParameter("name");
//Hiberate는 기본으로 PreparedStatement를 사용하지만, 파라미터 바인딩 없이 사용 할 경우 안전하지 않다.
Query query = session.createQuery("from Student where studentName = '" + name + "' ");

 

[안전한 코드 - Hibernate]

import org.hibernate.Query
import org.hibernate.Session
......
String name = request.getParameter("name");
//1. 파라미터 바인딩을 위해 ?를 사용한다.
Query query = session.createQuery("from Student where studentName = ? ");
//2. 파라미터 바인딩을 사용하여 외부 입력값에 의해 쿼리 구조 변경을 못하게 사용하였다.
query.setString(0, name);
import org.hibernate.Query
import org.hibernate.Session
......
String name = request.getParameter("name");
//1. 파라미터 바인딩을 위해 명명된 파라미터 변수를 사용한다.
Query query = session.createQuery("from Student where studentName = :name ");
//2. 파라미터 바인딩을 사용하여 외부 입력값에 의해 쿼리 구조 변경을 못하게 사용하였다.
query.setParameter("name", name);

 

[안전하지 않은 코드 - C#]

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

 

[안전한 코드 - C#]

-외부의 값을 파라미터 바인딩을 위해 @사용

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