본문 바로가기
NestJS

[NestJS] PostgreSQL 에서의 SQL Injection 방어

by 개복취 2024. 2. 2.

 

 

0. SQL Injection?

1. 왜 Injection이 되는걸까

2. 어떻게 대비해야할까

 


SQL Injection?: https://youtu.be/FoZ2cucLiDs?si=9YFOHgu6Iucb9ZCn

이거만큼 injection 설명 잘해놓은게 없다
SQL 인젝션 공격은 웹사이트의 보안 취약점을 이용하여 데이터베이스를 침투할 수 있는 공격 기법이다.
따옴표(')를 입력하여 발생하는 오류 메시지가 공격 가능한지 확인하는 첫 번째 단계이며, 유니온 셀렉트를 이용하여 다른 테이블의 내용까지 출력하는 것도 가능하다.

왜 Injection이 되는걸까?

https://blog.naver.com/skinfosec2000/220482240245

인젝션이 왜 되는지 더 딥하게 이해하려면, postgreSQL 파싱과정에 이해를 해야한다.

 

우리가 select 쿼리를 보내면 DBMS내부에서 문법을 검사할 때 (parse, bind, execute, patch)의 프로세스를 거쳐 출력하게 된다.

 

postgreSQL에서는 bison 파서 생성도구를 사용하여 구문 분석기로 사용한다.

 

내부에서는 LALR 기법으로 파싱되고 파싱트리는 아래와 같다.

 

https://blog.naver.com/skinfosec2000/220482240245

파싱과정에서 일반적인 Statement들을 사용할 때 모든 과정을 수행하게 된다. 그래서 인젝션 코드를 작성하면 한꺼번에 실행되는 것이다.

 

그러나, 준비된 명령문(또는 파라미터화된 쿼리)을 사용하면 SQL 코드가 데이터베이스에 전송되기 전에 미리 컴파일되고, 실행 시간에 매개변수가 전달된다.

 

즉, 바인딩 데이터는 SQL 문법이 아닌 내부의 인터프리터나 컴파일 언어로 처리하기 때문에 문법적인 의미를 가질 수 없다.

 

따라서, 바인딩 변수에 SQL공격 쿼리를 입력할지라도 의미있는 쿼리로 동작하지 않는다.


어떻게 막을 수 있을까?

 

<Raw Query로 작성할 때>

 

PREPARE 준비 명령문과 $1, $2 플레이스 홀더를 통해 구성할 수 있다.

-- 준비된 명령문 생성
PREPARE user_search (text, text) AS
    SELECT * FROM users WHERE username = $1 AND email = $2;

-- 명령문 실행
EXECUTE user_search('user_name', 'user_email@example.com');

-- 준비된 명령문 해제 (더 이상 사용하지 않을 경우)
DEALLOCATE user_search;

 

<TypeORM 쿼리빌더>

 

NestJS 환경에서의 웹서비스를 구축한다고 할 때 가장 편리한 방법은 TypeORM의 쿼리빌더를 사용하는 것이다. 동적으로 쿼리를 구성하기 때문에 인젝션 공격에 방어할 수 있다. 

import { getConnection } from "typeorm";

const userRepository = getConnection().getRepository(User);

const users = await userRepository
    .createQueryBuilder("user")
    .where("user.username = :username AND user.email = :email", { username: "user_name", email: "user_email@example.com" })
    .getMany();

 

여기서 :username:email은 TypeORM에서 제공하는 쿼리 빌더의 파라미터 바인딩 기능을 사용하여, 각각 user_nameuser_email@example.com 값으로 대체된다.

 

<TypeORM + raw Query>

또는, TypeORM과 혼합하여 사용할 수도 있다.

const result = await this.dataSource.query(
    `SELECT * FROM users WHERE id = $1 AND status = $2`,
    [userId, userStatus]
);

 TypeORM에서 '[ ]' 내부의 값이 배열로 제공된다. 순서대로 $1, $2 플레이스 홀더에 배치된다.

 


 

요약하자면...

TypeORM 쓰자..


https://blog.naver.com/skinfosec2000/220482240245

https://www.postgresql.org/docs/current/sql-prepare.html