Computer Science 모아보기 👉🏻 https://github.com/seoul-developer/CS
💋 SQL Injection이란?
SQL Injection은 악의적인 사용자가 웹 애플리케이션 또는 다른 소프트웨어를 공격하기 위해 사용되는 기법 중 하나입니다.
사용자로부터 입력받은 데이터를 통해 SQL 쿼리를 조작하여 데이터베이스에 무단으로 접근하거나 조작하는 공격
을 의미합니다.
💋 SQL Injection의 종류
✔️ Classic SQL Injection
예를 들어, 로그인 폼에 사용자가 입력한 아이디와 패스워드로 쿼리를 생성하는 경우를 생각해봅시다.
SELECT * FROM users WHERE username = '입력한아이디' AND password = '입력한패스워드';
여기에 아이디랑 패스워드에 ' OR '1'='1'; --
를 입력하면 아래와 같은 쿼리가 생성되어 모든 사용자의 정보가 반환될 수 있습니다.
SELECT * FROM users WHERE username = '' OR '1'='1'; --' AND password = '' OR '1'='1';
✔️ Union-based SQL Injection
다른 테이블의 데이터를 가져오는 공격입니다. 예를 들어, URL 매개변수를 통해 페이지의 카테고리를 나타내는 경우,
SELECT * FROM products WHERE category_id = '입력한카테고리';
여기에 ' UNION SELECT null, username, password FROM users; --
를 입력하면 사용자 테이블의 정보가 함께 나타날 수 있습니다.
✔️ Time-based Blind SQL Injection
딜레이 함수를 사용하여 서버의 응답 시간을 이용해 정보를 추출하는 공격입니다. 예를 들어, 아이디를 체크하는 쿼리에서 딜레이 함수를 삽입할 수 있습니다.
SELECT * FROM users WHERE username = '입력한아이디' AND IF(1=1, SLEEP(5), 0);
여기에 아이디에 ' OR IF(1=1, SLEEP(5), 0); --
를 입력하면 서버는 5초 동안 응답을 지연시키면서 정보를 확인합니다.
✔️ Error-based SQL Injection
서버의 에러 메시지를 이용하여 정보를 추출하는 공격입니다. 아이디로 쿼리를 생성하는 경우,
SELECT * FROM users WHERE username = '입력한아이디';
여기에 아이디에 ' OR 1=CONVERT(int, (SELECT @@version)); --
를 입력하면 에러 메시지를 통해 데이터베이스 버전을 확인할 수 있습니다.
💋 SQL Injection 예방 방법
✔️ 꼼꼼한 입력 검증 (Input Validation)
사용자의 입력을 검증하여 예상치 못한 문자열을 방지합니다.
특수문자나 SQL 예약어를 필터링하거나, 정규표현식을 사용하여 유효성을 확인할 수 있습니다.
# Python Flask에서의 입력 검증 예시
from flask import request
username = request.form['username']
password = request.form['password']
# 예상치 못한 문자나 SQL 예약어를 필터링
if not username.isalnum() or not password.isalnum():
return "잘못된 입력입니다."
✔️ Prepared Statements (Parameterized Queries)
SQL 문장에 사용자 입력을 직접 삽입하는 대신, 매개변수를 사용하여 동적으로 쿼리를 생성합니다.
대부분의 프로그래밍 언어와 데이터베이스 라이브러리는 이를 지원합니다.
// Java에서의 Prepared Statements 사용 예시 (JDBC)
String sql = "SELECT * FROM users WHERE username = ? AND password = ?";
PreparedStatement pstmt = connection.prepareStatement(sql);
pstmt.setString(1, inputUsername);
pstmt.setString(2, inputPassword);
ResultSet result = pstmt.executeQuery();
✔️ ORM (Object-Relational Mapping) 사용
ORM 라이브러리를 사용하면 SQL 쿼리를 직접 작성하는 대신 객체와 데이터베이스 테이블 간의 매핑을 통해 데이터를 다룰 수 있습니다.
# Python SQLAlchemy ORM 사용 예시
from sqlalchemy import create_engine, Column, String
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker
Base = declarative_base()
class User(Base):
__tablename__ = 'users'
id = Column(Integer, primary_key=True)
username = Column(String)
password = Column(String)
engine = create_engine('your_database_url')
Base.metadata.create_all(engine)
Session = sessionmaker(bind=engine)
session = Session()
user = session.query(User)
.filter_by(username=inputUsername, password=inputPassword)
.first()
✔️ Stored Procedures 사용
데이터베이스에 저장된 프로시저를 사용하여 쿼리를 실행하면, 외부 입력에 직접 노출되지 않아 SQL 인젝션 공격을 예방할 수 있습니다.
-- 저장 프로시저 예시 (MySQL)
DELIMITER //
CREATE PROCEDURE AuthenticateUser(IN p_username VARCHAR(255), IN p_password VARCHAR(255))
BEGIN
SELECT * FROM users WHERE username = p_username AND password = p_password;
END //
DELIMITER ;
이후에는 프로시저를 호출하여 사용자 인증을 수행합니다.
CALL AuthenticateUser('입력한아이디', '입력한패스워드');
✔️ 서버의 에러 메세지 노출 금지
에러 메시지에는 공격자에게 정보를 제공할 수 있는 내용이 들어있을 수 있기 때문에 민감한 정보를 노출하지 않도록 주의해야 합니다.
- 에러 메시지 일반화 (Generic Error Messages)
- 에러가 발생했을 때 클라이언트에게 구체적인 내용을 보여주는 대신, 일반적이고 사용자 친화적인 에러 메시지를 표시합니다.
- 예를 들어, "로그인에 실패했습니다"와 같은 메시지를 사용하여 구체적인 오류 내용을 감춥니다.
- 에러 로깅 (Error Logging)
- 서버 측에서는 에러 로깅을 통해 시스템의 동작과 관련된 정보를 수집할 수 있습니다.
- 사용자에게는 디버그 정보를 노출하지 않도록 조심해야 합니다.
- 에러 로그는 시스템 관리자나 개발자에게만 접근 가능하도록 설정해야 합니다.
- Custom Error Pages 사용
- 사용자가 에러를 발생시켰을 때 보여지는 페이지를 사용자 정의할 수 있습니다.
- 일반적으로는 일반적이고 유용한 정보만을 담은 페이지로 리다이렉트하도록 설정합니다.
- 에러 정보 익명화 (Obfuscating Error Information)
- 에러 메시지에 민감한 정보가 들어가지 않도록 신경 쓰는 것이 중요합니다.
- 민감한 정보는 로깅되지 않도록 조치하고, 클라이언트에게는 일반적인 오류 메시지만을 전송하도록 설정합니다.
- 개발 환경에서의 디버그 모드 비활성화
- 운영 환경에서는 디버그 모드를 꺼두어야 합니다.
- 디버그 모드는 보다 상세한 에러 정보를 제공하는데, 공격자에게 유용한 정보가 될 수 있습니다.
💋 (참고) 정말로 Prepared Statements, ORM 쓰면 다 해결될까?
이 부분은 이리내씨의 댓글로 인해 문제 제기 후 추가되었습니다.
한국 인터넷 진흥원의 자료 139~140쪽에 따르면, Prepared Statements, ORM을 사용하더라도 안전하지 않은 케이스가 있다고 하는데요...ㅋㅋㅌㅌ
Prepared Statements을 사용하더라도 검증받지 않은 값을 그대로 사용할 경우에 위험합니다. 파라미터 바인딩 꼭 해야 합니당
꼭 안전하게 사용합시다!
💋 참고자료
- https://www.youtube.com/watch?v=FoZ2cucLiDs
- https://noirstar.tistory.com/264
- https://namu.wiki/w/SQL injection
- https://www.kisa.or.kr/2060204/form?postSeq=5&lang_type=KO&page=1#fnPostAttachDownload
도움이 되었다면, 공감/댓글을 달아주면 깃짱에게 큰 힘이 됩니다!🌟
비밀댓글과 메일을 통해 오는 개인적인 질문은 받지 않고 있습니다. 꼭 공개댓글로 남겨주세요!