개발일지/Node.js

SQL Injection 방지 - 권장방식에는 다 이유가 있지..

2024. 1. 31. 13:14
목차
  1. nodejs express 공부를 하고 있는데, 쿼리를 작성하던 중 문득 생각이 들었다.
  2. 그래서 테스트를 해보았다.
  3. 1. ${변수}
  4. 2. 플레이스 홀더(공식 권장 방식)
  5. mariadb의 공식문서를 살펴보면
  6. 결론
  7. 권장하는 방법을 쓰자.
  8. 전체 코드
728x90

nodejs express 공부를 하고 있는데, 쿼리를 작성하던 중 문득 생각이 들었다.

`
SELECT * FROM users
WHERE user_id = ?
`

 

mariadb 모듈에는 query 함수가 있는데,

첫 번째 인자로 쿼리 문자열 받고,

두 번째 인자로 쿼리의 플레이스홀더(? 부분)에 해당하는 부분을 배열 형태로 받는다.

세 번째 인자로 콜백함수를 받는다.

 

여기서 ${} 형태로 작성하면 가독성과 유지보수 측면에서 더 좋지 않나? 라는 생각이 들었다.

`
SELECT * FROM users
WHERE user_id = ${userId}
`

 

이런 식으로 작성하고 두 번째 인자를 생략하면 정상적으로 작동된다.

 

하지만 또 한편으로 드는 생각이 굳이 첫 번째 방식으로 작성하고 두 번째 인자로 배열을 받는 이유가 있지 않을까? 라는 생각이 들었다.

 

그래서 검색을 좀 해보니 두번째 방식으로 작성을 하게 되면 SQL Injection 공격에 취약해진다고 한다.

 

역시 개발자들은 항상 코드에 이유가 있다.

 

그래서 테스트를 해보았다.

1. ${변수}

// 소스코드
login1 = (email, password) => {
  return new Promise((resolve, reject) => {
    const query = `
      SELECT * FROM users u
      WHERE email = '${email}' AND password = '${password}';
      `;
    db.query(query, (error, data) => {
      if (error) reject(error);
      else resolve(data[0]);
    });
  });
}

// email과 password에 아래와 같이 입력
login1("test1", "' OR '1' = '1")
    .then(result => console.log(result));
    
/* 결과
{
    "user_id": 1,
    "email": "test1",
    "name": "네임",
    "password": "$2b$10$qJ3/h/W4yiLLygW6.1xn8uI6xpbLY1/h6EpX./PkydaZ6tBd3AakC",
    "created_at": "2024-01-21T13:04:43.000Z",
    "modified_at": "2024-01-31T02:56:35.000Z"
}
*/

 

위 코드를 실행하면 데이터베이스에서는 아래와 같은 쿼리가 실행된다.

-- 실제 데이터베이스에서 실행되는 쿼리
SELECT * FROM users u
WHERE email = 'test1' AND password = '' OR '1'  = '1';

-- 위 쿼리는 결국 아래 쿼리와 같다
SELECT * FROM users u

DB에서 해당 쿼리를 실행하였을 경우 users테이블의 모든 데이터가 조회된다.

코드에서 data[0]를 통해 첫번째 데이터만 응답하도록 했기 때문에 한건만 조회가 되었지만, 
모든 데이터에서 첫번째 데이터가 반환되게 된다.
 

2. 플레이스 홀더(공식 권장 방식)

// 소스코드
const login2 = (email, password) => {
    return new Promise((resolve, reject) => {
        const query = `
        SELECT * FROM users u
        WHERE email = ? AND password = ?;
        `;
        db.query(query, [email, password], (error, data) => {
            if (error) reject(error);
            else resolve(data[0]);
        });
    });
}

// email과 password에 아래와 같이 입력
login2("test1", "' OR '1' = '1")
    .then(result => console.log(result));
    
/* 결과
undefined
*/

 

위 코드를 실행하면 데이터베이스에서는 아래와 같은 쿼리가 실행된다.

-- 실제 데이터베이스에서 실행되는 쿼리
SELECT * FROM users u
WHERE email = 'test1' AND password = "'' OR '1'  = '1'";

SQL 인젝션을 위해 입력한 password부분이 하나의 문자열로 데이터베이스에서 실행이 된다.

그러므로 조건에 맞는 데이터가 없기 때문에 빈 배열을 가져오게 되고, 빈 배열의 첫 번째 데이터를 리턴하기 때문에 undefined가 반환된다.

 

mariadb의 공식문서를 살펴보면

https://github.com/mariadb-corporation/mariadb-connector-nodejs/blob/master/documentation/callback-api.md

첫번째 줄을 살펴보면 To avoid SQL Injection attacks, 라고 되어 있는 것을 볼 수 있다.

SQL Injection attacks를 피하기 위해 라고 친절하게 적어놓았다.

 

결론

물론, 비밀번호의 경우 데이터베이스에 암호화해서 저장을 하고, 암호화 모듈에서 제공하는 함수를 이용해서 비교를 하기 때문에 위 쿼리처럼 WHERE절에 email AND password를 사용해서 로그인을 진행하는 경우는 드물다.

 

그렇다면 첫번째 방법을 사용해도 되는 거냐?

당연히 절대 안 된다.

위에서는 password에 조건문만 집어넣었지만 DELETE, DROP TABLE, UPDATE 등 다양한 쿼리를 사용해서 공격을 시도할 수 있다.

(이 경우에도 생각해야 할 것들이 있지만 주제와 맞지 않으므로 pass)

 

어쨌든 해커가 공격할 여지를 남겨주는 것은 좋은 방법이 아니다.

 

권장하는 방법을 쓰자.

 

전체 코드

const mariadb = require('mariadb/callback');

const DB_HOST = '호스트';
const DB_PORT = '포트';
const DB_USER = '유저';
const DB_PASSWORD = '비밀번호';
const DB_DATABASE = '데이터베이스명';

const db = mariadb.createConnection({
    host: DB_HOST,
    port: DB_PORT,
    user: DB_USER,
    password: DB_PASSWORD,
    database: DB_DATABASE,
});

// ${} 방식
const login1 = (email, password) => {
    return new Promise((resolve, reject) => {
        const query = `
        SELECT * FROM users u
        WHERE email = '${email}' AND password = '${password}';
        `;
        db.query(query, (error, data) => {
            if (error) reject(error);
            else resolve(data[0]);
        });
    });
}
login1("test1", "' OR '1' = '1")
    .then(result => console.log(result));

// ? 방식
const login2 = (email, password) => {
    return new Promise((resolve, reject) => {
        const query = `
            SELECT * FROM users u
            WHERE email = ? AND password = ?;
            `;
        db.query(query, [email, password], (error, data) => {
            if (error) reject(error);
            else resolve(data[0]);
        });
    });
}
login2("test1", "' OR '1' = '1")
    .then(result => console.log(result));
728x90
저작자표시 비영리 변경금지 (새창열림)

'개발일지 > Node.js' 카테고리의 다른 글

Prisma vs TypeORM 벤치마크  (0) 2024.04.09
TypeORM + MySQL8 AsText does not exist  (0) 2024.03.12
Nestjs - Shared modules : 의존성 주입을 통하여 모듈 공유하기  (0) 2024.02.29
TypeORM - @Column({ unique : true }) vs @Unique()  (0) 2024.02.23
  1. nodejs express 공부를 하고 있는데, 쿼리를 작성하던 중 문득 생각이 들었다.
  2. 그래서 테스트를 해보았다.
  3. 1. ${변수}
  4. 2. 플레이스 홀더(공식 권장 방식)
  5. mariadb의 공식문서를 살펴보면
  6. 결론
  7. 권장하는 방법을 쓰자.
  8. 전체 코드
'개발일지/Node.js' 카테고리의 다른 글
  • Prisma vs TypeORM 벤치마크
  • TypeORM + MySQL8 AsText does not exist
  • Nestjs - Shared modules : 의존성 주입을 통하여 모듈 공유하기
  • TypeORM - @Column({ unique : true }) vs @Unique()
E-room
E-room
나의 성취 기록들
E-room Achievement Logs나의 성취 기록들
E-room
E-room Achievement Logs
E-room
전체
오늘
어제
  • 분류 전체보기
    • 개발일지
      • 돌픽
      • Spring
      • Algorithm
      • Java
      • Node.js
      • Python
      • DataBase
      • 웹개발
      • JavaScript
      • 컴퓨터지식
      • Django
    • 이것저것
    • 피드백 감사히 받겠습니다

블로그 메뉴

  • 태그
  • Github
  • 돌픽-이상형월드컵

인기 글

최근 글

최근 댓글

태그

  • Java
  • Django
  • python
  • search
  • 알고리즘
  • javascript
  • 파이썬
  • 자바
  • Spring
  • 순열
  • dfs
  • boot
  • 생활코딩
  • 백준
  • 수열
  • API
  • 다이나믹
  • 조합
  • JPA
  • algorithm
  • SQL
  • 탐색
  • 프로그래밍
  • 스파르타코딩클럽
  • 자료구조
  • 재귀
  • 완전탐색
  • 백트래킹
  • mysql
  • dp

공지사항

hELLO · Designed By 정상우.
E-room
SQL Injection 방지 - 권장방식에는 다 이유가 있지..
상단으로

티스토리툴바

단축키

내 블로그

내 블로그 - 관리자 홈 전환
Q
Q
새 글 쓰기
W
W

블로그 게시글

글 수정 (권한 있는 경우)
E
E
댓글 영역으로 이동
C
C

모든 영역

이 페이지의 URL 복사
S
S
맨 위로 이동
T
T
티스토리 홈 이동
H
H
단축키 안내
Shift + /
⇧ + /

* 단축키는 한글/영문 대소문자로 이용 가능하며, 티스토리 기본 도메인에서만 동작합니다.