사이버보안

SQL Injection

JeongBear 2024. 12. 14. 22:20

SQL 인젝션(SQL Injection)은 공격자가 웹 애플리케이션의 입력 필드를 악용하여 SQL 쿼리를 조작함으로써 데이터베이스에 비정상적인 접근을 시도하는 공격 기법입니다. 이 공격은 데이터 유출, 삭제, 수정, 또는 관리자 권한 탈취 등의 심각한 결과를 초래할 수 있습니다.

 

취약점: 애플리케이션에서 사용자 입력을 적절히 검증하거나, 쿼리에 직접 삽입할 때 이를 처리하지 않을 경우 발생합니다.

 

취약한 쿼리:

SELECT * FROM users WHERE username = '$username' AND password = '$password';

 

username에 ' OR '1'='1을 입력하면 쿼리가 다음과 같이 변조:

SELECT * FROM users WHERE username = '' OR '1'='1' AND password = '';

 

결과: 조건이 항상 참이 되어 데이터베이스의 모든 사용자 정보를 반환합니다.

 

실습 코드:

# SQL 인젝션 취약 코드
import sqlite3

# 데이터베이스 생성 및 초기화
def initialize_database():
    conn = sqlite3.connect(":memory:")
    cursor = conn.cursor()
    cursor.execute("CREATE TABLE users (id INTEGER PRIMARY KEY, username TEXT, password TEXT)")
    cursor.execute("INSERT INTO users (username, password) VALUES ('admin', 'admin123')")
    cursor.execute("INSERT INTO users (username, password) VALUES ('user', 'user123')")
    conn.commit()
    return conn

# 취약한 로그인 함수
def vulnerable_login(conn, username, password):
    cursor = conn.cursor()
    query = f"SELECT * FROM users WHERE username = '{username}' AND password = '{password}'"
    print(f"실행된 쿼리: {query}")
    cursor.execute(query)
    result = cursor.fetchone()
    return result

# SQL 인젝션 테스트
if __name__ == "__main__":
    conn = initialize_database()

    # 정상 입력
    print("=== 정상 로그인 시도 ===")
    user = vulnerable_login(conn, "admin", "admin123")
    print("로그인 성공" if user else "로그인 실패")

    # SQL 인젝션 공격
    print("\n=== SQL 인젝션 공격 시도 ===")
    user = vulnerable_login(conn, "' OR '1'='1", "any_password")
    print("로그인 성공" if user else "로그인 실패")

 

SQL Injection을 막는 방법

 

  • Prepared Statement 사용: 쿼리를 실행할 때, SQL과 데이터를 분리
  • 입력값 검증: 사용자의 입력 데이터를 검증
  • 최소 권한 접근: 데이터베이스 사용자의 권한을 최소화
  • ORM 사용: SQLAlchemy 같은 ORM을 통해 쿼리를 작성
  • 보안 모니터링: 의심스러운 활동을 감지하고 차단
  • WAF 사용: 웹 방화벽을 사용하여 방어
  • etc..

위의 실습 코드의 경우, Prepared Statement 또는 Parameterized Query를 사용하여 막으면

 

방어 코드:

# SQL 인젝션 방어 코드
def secure_login(conn, username, password):
    cursor = conn.cursor()
    query = "SELECT * FROM users WHERE username = ? AND password = ?"
    print(f"실행된 쿼리: {query} | 파라미터: ({username}, {password})")
    cursor.execute(query, (username, password))
    result = cursor.fetchone()
    return result

# 방어된 코드 테스트
if __name__ == "__main__":
    conn = initialize_database()

    # 정상 입력
    print("=== 정상 로그인 시도 ===")
    user = secure_login(conn, "admin", "admin123")
    print("로그인 성공" if user else "로그인 실패")

    # SQL 인젝션 공격
    print("\n=== SQL 인젝션 공격 시도 ===")
    user = secure_login(conn, "' OR '1'='1", "any_password")
    print("로그인 성공" if user else "로그인 실패")