[웹해킹] SQL Injection 총정리

들어가며

웹 모의해킹, 취약점진단 실무를 하면서 다양한 구조로 개발된 웹 페이지들을 대상으로 한다. 그 중 가장 쉬운 공격이면서 크리티컬한 취약점 중 하나인 SQL Injection에 대해서 설명하고 정리한다. 공격을 위해서는 내가 삽입한 코드의 동작 원리를 이해하고 백엔드에서 어떻게 실행되는지 이해한 후에 다음 단계의 공격을 해야 한다. 모의해킹 직무 면접의 단골 질문이면서 필드에서 가장 크리티컬하게 만날 수 있는 취약점이다. 원리부터 대응방안까지 하나씩 살펴보자.

SQL(Structured Query Language)?

왜 개발자들은 SQL을 사용할까?

웹 사이트를 운영하기 위해서는 많은 데이터의 관리가 필요하다. 쇼핑몰을 예로 들면 이용자의 회원정보, 물건의 판매정보, 상품 문의 게시글 등 시간이 지날수록 많은 데이터가 쌓이게 된다. 이들을 그냥 텍스트 파일이나 그림 파일로 관리하게 되면 관리하기도 어렵고 원하는 데이터를 탐색하기 위해 오랜 시간이 소요된다.

이를 해결하기 위해 DB(Database) 개념을 도입하였고 중복된 데이터를 없애고, 자료를 구조화하여, 효율적인 처리를 할 수 있도록 하였다.

관계형 데이터베이스의 테이블을 구성하여 키(key)-값(value)의 관계를 나타내도록 하였다. 이처럼 데이터의 종속성을 관계(relationshop)로 표현하는 것이 관계형 데이터베이스의 특징이다.

관계형 데이터베이스(출처 : http://tcpschool.com/mysql/mysql_intro_relationalDB)

이외에도 다음과 같은 특징이 있다.

1. 데이터의 분류, 정렬, 탐색 속도가 빠르다.

2. 오랫동안 사용된 만큼 신뢰성이 높고, 어떤 상황에서도 데이터의 무결성을 보장한다.

3. 기존에 작성된 스키마를 수정하기가 어렵다.

4. 데이터베이스의 부하를 분석하는 것이 어렵다.

추가적으로 관계를 나타내는 방법으로 일대일, 일대다, 다대다가 있으나 주제와 벗어나므로 다루지 않는다.

 

SQL Injection

SQL Injection은 사용자의 입력값이 서버측에서 코드로 실행되는 '코드 인젝션'  공격 기법 중 하나이며, 서버의 데이터베이스를 대상으로 하는 공격이다. 사용자 입력값을 제대로 필터링, 이스케이프 하지 않아 발생한다. 2017년 3월 발생한 "여기어때" 고객 정보 및 고객 투숙 정보 노출 사고, 2015년 "뽐뿌" 개인정보 노출 사고가 SQL Injection 공격이 원인이었다.

공격의 핵심은 클라이언트 측에서 SQL 쿼리에 신뢰할 수 없는 데이터가 입력되었을 때, 데이터가 쿼리 로직의 일부로 해석되어 DB에서 실행될 때 발생한다.

 

공격 목적

SQL Injection 공격 목적은 다음과 같다. SQL 구문에 추가 절을 삽입하게 되면 쿼리의 의미가 변경되고 의도하지 않은 데이터의 유출, 변조가 가능하다.

1. 정보 유출(Information Leakage)

2. 저장된 데이터 유출 및 조작(Disclosure & Manipulation of stored Data)

3. 원격 코드 실행(Remote Code Excution)
    일부 데이터베이스의 경우 확장 프로시저를 이용하여 원격으로 시스템 명령의 실행이 가능하다. 시스템 명령의 실행은 원격 자원 접근 및 데이터 유출, 삭제가 가능하다.

4. 인증 우회(Bypassing authorisation controls)
    공격의 대표적인 경우로 로그인 폼 등에서 발생한다. 상위 권한을 가진 사용자의 권한으로 인증 절차를 우회하여 로그인 정보 없이 로그인할 수 있다.

SQL Injcetion 공격 구분

필자가 설명의 용이를 위해 구분하였다.

구분 예시
Classic SQLi Union-based SQLi, Error-based SQLi
Blind SQLi Boolean-based SQLi, Time-based SQLi
Out-of-band SQLi -

 

Classic SQLi

Classic SQLi는 가장 일반적인 공격 방법이며, 공격자가 동일한 통신 구간(ex : 브라우저)에서 공격을 시도하고 결과를 얻을 수 있다.

가장 쉬운 SQLi 예제를 아래와 같이 다룬다. 사용자가 Parasite를 검색어로 하여 Movies DB에서 결과를 출력한 모습이다.

Parasite 검색 및 결과 확인

사용자가 입력한 검색어가 WHERE 절 내에 바로 삽입되는 것을 확인하고 공격자는 SQLi 공격을 시도한다.

DB에 어떤 데이터가 있는지 모르는 상태에서 WHERE 절을 항상 참으로 만족시키는 OR '1'='1'-- 구문을 삽입하였고 DB는 쿼리로 실행하여 모든 데이터가 출력되는 것을 확인할 수 있다.

구문을 분석하면 사용자가 입력한 데이터는 SQL 쿼리의 작은따옴표(')에 위치한다. 공격자는 WHERE 절의 앞이나 뒤 둘중 하나만 참이되게끔 하기 위해 OR 절을 추가한 것이다. 따라서 WHERE 절은 movieFullName이 'Parasite'이거나, '1'='1'인 경우 SELECT의 movieFullName, movieYear를 출력하도록 했기 때문에 DB 내에 있는 모든 데이터를 출력한다.

로그인 기능의 경우 사용자의 ID와 Password를 DB에 저장한 후 사용자가 입력한 데이터가 맞는지 비교하는 구문을 위와 같이 사용하는 경우가 있다.

SELECT * 
FROM USERTABLE 
WHERE username='your_user_input' AND password='your_password_input'

여기서 SQLi 공격을 시도하면 아래와 같이 쿼리문이 완성된다.

SELECT * 
FROM USERTABLE 
WHERE username='random_input' OR 1=1 -- 'AND password='random_password'

구문에서 '--'를 사용한 것은 주석의 의미이며 이하의 서브쿼리를 모두 의미없는 문자열로 만들어 공격자가 의도한 대로 실행되도록 한다.

Union-based SQLi

SQL 구문에서 UNION은 두개나 그 이상의 SELECT 구문을 결합하여 하나의 결과로 출력한다. 이러한 기능을 활용하여 공격자는 쿼리의 일부로 추가 구문을 삽입하여 HTTP 응답으로 결과를 가져올 수 있다.

GET https://my-vulnerable-site.com/product.php?id=1%20UNION%20SELECT%201%20FROM%20information_schema.tables
-- or --
GET https://my-vulnerable-site.com/product.php?id=1 UNION SELECT 1 FROM information_schema.tables

위 구문은 MYSQL에서만 실행되는 구문이다. UNION 절을 사용하기 위해서는 두 테이블의 집합을 결합하기 때문에 열의 수와 데이터 유형은 원래 테이블과 같아야 한다.

 


Tip. DB 종류 판별

입력한 구문이 SQL 구문이 되어 실행되는 것을 확인했다면 DB의 종류를 구분해야한다.

  • Oracle: SELECT version FROM v$instance
  • Microsoft: SELECT @@version
  • PostgreSQL: SELECT version()
  • MySQL: SELECT @@version
  • SQLite: SELECT sqlite_version()

만약 쿼리가 실행되는 테이블에 4개의 열이 있다는 것을 알면 DBMS가 무엇인지 추측하기 위해 다음의 쿼리를 작성할 수 있다.

' UNION SELECT 1, 1, 1, version_request --

1. 정확한 컬럼 수 찾기

GET https://my-vulnerable-site.com/product.php?id=1 ORDER BY <X>--

구문에서 'X'의 숫자를 1씩 늘리면서 시도하면 정확한 열의 수가 일치할 때 HTTP 응답에서 변화를 확인한다.

2. 페이지에서 SQLi에 취약한 영역 찾기

원본 쿼리가 사용자가 입력한 데이터를 검색한 결과를 나타내는 기능이라면 UNION을 사용하여 추가 쿼리를 실행한 결과까지 응답에 포함시킬 수 있다.

GET https://my-vulnerable-site.com/product.php?id=1 UNION ALL SELECT 1,2,3,4,5--

열의 개수가 일치하는 경우 숫자 1~5가 각 열에 표시되며 이를 이용해 더 많은 정보를 획득하려면 다음과 같이 사용한다.

그 결과로 기존에 "4"가 보이던 위치에서 DB의 테이블 이름이 보이는 것을 확인할 수 있다.

GET https://my-vulnerable-site.com/product.php?id=1 UNION ALL SELECT 1,2,3,group_concat(table_name),5 FROM information_schema.tables WHERE table_schema = database()--

 

Error-based SQLi

Error-based SQLi는 기본적으로 공격자가 데이터베이스의 구조를 얻고 이해하기 위해 서버에서 반환되는 에러에 의존한다. 위의 UNION Injection에서 열(Column)의 갯수를 찾기 위해 열의 갯수를 하나씩 올리면서 결과를 살폈던 것도 범위에 포함된다.

개발 시 응답에 포함되는 에러 정보는 개발자에게 큰 도움이 되지만 운영중인 웹애플리케이션에서의 자세한 에러 정보는 공격자에게 큰 도움이 된다.


SQL 작성 실습 사이트

개발과 해킹은 직접 코드를 한줄씩 작성해보면서 자신이 입력한 구문이 어떻게 실행되는지 확인해 보는 것이 좋다.

하지만 환경상 어려운 경우 다음의 사이트에서 제공하는 기능을 사용해볼 수 있다.

1. ORACLE DB

https://livesql.oracle.com/

2. SQL Fiddle

DB종류별로 SQL 구문의 실행결과를 확인할 수 있다.

http://sqlfiddle.com/

3. DB Fiddle

https://dbfiddle.uk/

 

Cheatsheet

위의 글을 보고 원리와 공격 방법을 모두 이해했다면 다양한 공격 구문과 사례들을 모아놓은 Cheatsheet를 활용할 수 있다. 공격 페이로드의 무분별한 사용은 절대 금지하며 사이트의 영향도를 검토한 이후 사용할 수 있도록 한다.

1. payload all the things

가장 애용하는 페이지이다. SQL Injection 말고도 다양한 공격 페이로드를 공유한다.

https://github.com/swisskyrepo/PayloadsAllTheThings/tree/master/SQL%20Injection

 

GitHub - swisskyrepo/PayloadsAllTheThings: A list of useful payloads and bypass for Web Application Security and Pentest/CTF

A list of useful payloads and bypass for Web Application Security and Pentest/CTF - GitHub - swisskyrepo/PayloadsAllTheThings: A list of useful payloads and bypass for Web Application Security and ...

github.com

2. Pentest Monkey

https://pentestmonkey.net/category/cheat-sheet/sql-injection

 

SQL Injection | pentestmonkey

Ingres seems to be one of the less common database backends for web applications, so I thought it would be worth installing it and making some notes to make my next Ingres-based web app test a little easier.

pentestmonkey.net

3. SQLMap

https://sqlmap.org/ 에서 다운로드 가능하며 Kali Linux 등에 자동으로 내장되어 있는 도구이다. 타사가 개발한 도구의 경우 백도어 내장, 추출한 데이터의 외부 전송 가능성이 있으므로 충분한 검토 후에 사용하도록 한다.

SQLMap의 경우 개발자가 오픈소스로 코드를 공개하고 있으므로 추천하였다. 소스코드를 다운받아 어떤 페이로드가 내장되어 있는지 분석해보는 것도 학습이 될 것이다.

https://github.com/sqlmapproject/sqlmap

 

GitHub - sqlmapproject/sqlmap: Automatic SQL injection and database takeover tool

Automatic SQL injection and database takeover tool - GitHub - sqlmapproject/sqlmap: Automatic SQL injection and database takeover tool

github.com

 

대응방안

가장 쉽고 빠른 보안대책은 Prepared Statement의 적용이다. Prepared Statement 또는 매개변수화된 구문을 사용해서 동일한 구문을 실행함에 있어서 SQLi 공격을 방지하고 높은 효율성으로 쿼리문을 실행하도록 한다.

바인딩된 변수는 쿼리와 별도로 서버로 전송되므로 사용자가 입력한 구문이 쿼리로 실행될 수가 없다. 서버는 쿼리 템플릿이 모두 분석된 이후 실행 시점에서 사용자 입력값을 사용한다. 바인딩된 매개변수는 쿼리 문자열로 삽입되지 않으므로 이스케이프할 필요가 없다.

이는 쿼리 실행에서도 장점이 있으므로 공식 홈페이지에서도 권장된다.

mysqli.quickstart.prepared-statements

https://www.php.net/manual/en/mysqli.quickstart.prepared-statements.php

언어별로 권장하는 Prepared Statement 사용법을 제시한다.

Native — C# (ASP.NET)

System.Data.SqlClient’s SqlCommand.Prepare

Native — Go (Golang)

golang.org/pkg/database/sql’s DB.Prepare

Native — Java

java.sql’s PreparedStatement

하지만 개발 구조상 Prepared Statement의 적용이 어려운 경우가 있다. 그에 따른 대체 방법은 다음과 같다.

모든 대응 방안은 "사용자측에서 전달되는 모든 입력값은 신뢰 금지"가 원칙이다.

1. 블랙리스트 & 화이트 리스트 기반 필터링

블랙리스트는 SQL 쿼리의 구조를 변경하는 특수문자, SQL 예약어 등을 미리 등록해놓고 입력을 제한하는 방식이다.

해당 문자가 입력되었을 때 에러 메시지를 반환하거나, 미리 준비한 문자열로 치환하는 방법이 있다.

(특수문자) ' , " , = , & , | , ! , ( , ) , { , } , $ , % , @ , #, -- 등
(예 약 어) UNION, GROUP BY, IF, COLUMN, END, INSTANCE 등
(함 수 명) DATABASE(), CONCAT(), COUNT(), LOWER() 등

화이트 리스트 방식은 블랙리스트보다 좀더 강력하지만 이용자 편의성이 떨어지는 방식이다. 허용하는 문자열을 미리 등록해놓고 리스트 이외의 문자열이 입력되었을 때 차단한다. 사이트 기능에 따라 하나로 정의된 사전 목록을 사용할 수 없기 때문에 패턴화, 정규화한 문자열을 사용하는 것을 권장한다.

2. 오류 메시지 출력 제한

DB 오류 메시지의 노출은 공격자에게 2차 공격을 할 수 있는 정보를 제공하므로 오류 메시지의 출력을 금지한다.

DB 버전정보, DB 종류 등을 포함하여 로그인 시 ID, Password 오류 여부도 불필요한 경우 노출하지 않는다.

3. DB 보안 적용

관리자 DB 계정과 웹애플리케이션 운영 계정의 권한을 분리하여 운영한다. DDL, DML 등 사용하는 구문 별 계정을 구분하여 운영한다.

이외에도 웹 방화벽 사용, 주기적 로그 점검, 취약점 점검 등을 적용할 수 있다.

반응형