본문 바로가기
Secure Coding

[시큐어코딩] SQL Injection

by 수픽 2021. 8. 3.

>> SQL 삽입

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

 

> 보안대책

- PreparedStatement 객체 : DB에 컴파일 된 쿼리문을 전달하는 방법

- DB 커리에 사용되는 외부 입력값에 대해 특수 문자 및 쿼리 예약어 필터링

- Struts, Spring 등과 같은 프레임워크 사용 시 외부 입력값 검증모듈 및 보안 모듈을 상황에 맞추어 적절하게 사용

 

*PreparedStatement 객체 지원

JAVA : JDBC

Python : PDO

 

 

EX1) 외부로부터 입력받은 gubun의 값을 SQL 쿼리 생성하는 데 사용하기

PreparedStatement 객체 사용하기!

[안전하지 않은 코드]

 String sql = "SELECT * FROM board WHERE b_gubun = '" + gubun + "'";

 : 쿼리문에 외부로부터 입력받은 gubun의 값이 아무 검증 없이 사용되기 때문에 안전하지 않다.

//외부로부터 입력받은 값을 검증 없이 사용할 경우 안전하지 않다.
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);

 

안전하게 사용하려면?

- PreparedStatement 객체를 상수 스트링으로 생성

- 파라 미터 부분을 setString, setParameter 등의 메소드로 설정 : 외부의 입력이 쿼리문의 구조를 바꾸는 것을 방지

 

[안전한 코드]

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();

 


* 파이썬으로 생각해보면,

sql_insert = "insert into table1 values( {0}, '{1}', '{2}' )".format( 1, '가', '나' )

안전하지 않는 코드인, statement 기법을 사용한 코드이다. 직접 데이터를 넣는 모습을 확인할 수 있다.

 

sql_insert = 'insert into table1 values ( %s, %s, %s )'
sql_data = ( 1, '가', '나' )

 

이를 PreparedStatement 기법을 사용한 안전한 코드로 바꾸면 위와 같다.


 

* MyBatis vs Hibernate

- Persistence Layer : 데이터에 영속성 부여(데이터를 생성한 프로그램이 종료되더라도 사라지지 않는 데이터의 특성)

  -> JDBC 없이 간단한 작업만으로 데이터베이스와 연동되는 시스템을 빠르게 개발. 안정된 구동 보장

- SQL Mapper(SQL 문장으로 직접 DB 데이터 다룸)와 ORM(객체를 통해 간접적으로 DB 데이터 다룸)

 

 

EX2) MyBatis Data Map 공격

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

#<인자이름># 형태의 질의문 사용

 

[안전하지 않은 코드]

$기호 : 외부에서 입력된 keyword값을 문자열에 결합한 형태로 쿼리에 반영하므로 안전하지 않음.

-> 쿼리문 조작 가능

<?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>

 

[안전한 코드]

# 기호 : 변수가 쿼리맵에 바인딩 될 수 있도록 수정

<?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>

 

 

EX3) 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 + "' ");

 

[안전한 코드]

- 파라미터 위치 부분을 ? or :명명된 파라미터 변수로 설정

import org.hibernate.Query
import org.hibernate.Session
......
String name = request.getParameter("name");
//1-1. 파라미터 바인딩을 위해 ?를 사용한다.
Query query = session.createQuery("from Student where studentName = ? ");
//1-2. 파라미터 바인딩을 위해 :명명된 파라미터 변수를 사용한다.
Query query = session.createQuery("from Student where studentName = :name ");

//2-1. 파라미터 바인딩을 사용하여 외부 입력값에 의해 쿼리 구조 변경을 못하게 사용하였다.
query.setString(0, name);
//2-2. 파라미터 바인딩을 사용하여 외부 입력값에 의해 쿼리 구조 변경을 못하게 사용하였다.
query.setParameter("name", name);

 

결론: 외부 파라미터는 위험하다....!