Connector, SQL Injection, ORM

응용프로그램에서 DBMS 서버와 통신하는 방법에 대해서 알아보고, SQL Injection이라는 해킹 기법, RDBMS를 객체처럼 다루는 ORM에 대해 알아봅니다.
ORM, SQL Injection, Connector


Connector

내장형 DBMS를 제외한 DBMS는 일반적으로 서버-클라이언트 구조로 작성되어있습니다. 따라서 DBMS는 CLI, GUI 클라이언트 프로그램과 더불어 응용프로그램을 개발 할 때 사용 할 수 있는 커넥터 모듈(또는 라이브러리)을 다양한 플랫폼에 맞게 제공합니다. Node.js에서 MySQL을 사용하기 위해 npm으로 Node.js 플랫폼용 MySQL 커넥터 를 설치합니다.

~$ npm install mysql

먼저 DBMS 서버가 작동 중인 호스트, 포트로 TCP 연결을 맺어주고 로그인(익명 DB가 아니라, 권한이 필요한 DB의 경우) 과정을 거치게 됩니다. 이때 DB 작업을 수행 할 때마다 연결을 맺고 작업 후 바로 끊을 수도 있지만, 백엔드 프로세스가 종료되기 전까지 연결을 지속적으로 유지하는 방안도 있습니다. 이때는 OS나 DBMS 서버가 유휴 연결(특정 시간 동안 통신이 없는)을 강제로 종료 할 수도 있습니다.

연결 및 쿼리 실행

const mysql = require('mysql');
let connection = mysql.createConnection({
    host: 'localhost',
    user: 'root',
    password: '1234',
    database: 'db_name'
});

connection.connect();
connection.query('select * from users', (err, rows, fields) => {
  connection.end();

  if (err) throw err;
  console.log(rows);
});

SQL Injection

DBMS를 이용한 유저 로그인 과정을 생각해봅니다. 아래처럼 요청으로 들어온 유저의 아이디와 패스워드를 DBMS의 users 테이블과 비교하는 로직이 있을 수 있습니다.

db.query(`select * from users where uid like '${req.body.uid}' and password like '${req.body.password}'`, (err, rows) => {
    // ...
});

위의 코드도 일반적인 요청의 경우에는 별 문제가 없습니다만, 악의적인 입력 값을 넣으면 아래 같이 관리자 권한을 탈취 할 수 있습니다.

// req.body.uid = `admin' or `;
// req.body.password = ` like '`;
select * from users where uid like 'admin' or ' and password like ' like '';

혹은 여러줄의 쿼리가 순차적으로 실행된다면, 아래처럼 테이블 자체를 드랍시킬 수도 있고,

// req.body.uid = `uid'; drop table users;`;
// req.body.password = `xxxx`;
select * from users where uid like 'uid'; drop table users;' and password like 'xxxx';

단순히 서버에 런타임 오류를 발생시킬 수도 있습니다.

// req.body.uid = `'xxxx;`;
// req.body.password = `xxxx`;
select * from users where uid like ''xxxx;' and password like 'xxxx';

SQL InjectionSQL Injection

물론 공격자는 백엔드 코드를 볼 수 없기에 반복적인 공격을 통해서 위처럼 SQL Injection을 시도하겠지만, 성공하는 경우 위처럼 서버에 큰 타격을 줄 수 있겠습니다. 특히 내부 코드나 DB 스키마가 유출되면 큰 위협이 되겠습니다.

따라서 이를 원천적으로 방지 할 필요가 있습니다. 이를 방지하는 방법은 쿼리 포맷에 조합될 수 있는 문자열(입력 값)들에서 특수문자를 검증하고 치환하는 것으로 단순합니다. 물론 DBMS는 일반적으로 Connector단에서 이러한 기능을 간편히 제공합니다. Node.js MySQL Connector는 입력 값과 쿼리를 조합 할 때 아래처럼 코드를 작성 할 수 있습니다.

db.query(`select * from users where uid like ? and password like ?`, [req.body.uid, req.body.password], (err, rows) => {
    // ...
});

ORM

우리가 DBMS를 쓰기전에, 메모리 상에서 구현했던 데이터 모델들을 생각해봅니다. 각 모델들을 객체지향적으로 설계해서 우아하고 구조적인 코드를 작성 할 수 있었습니다.

ORMORM

ORM(Object Relational Mapping)은 코드상에서 DB의 테이블들을 타입(Class) 내지는 객체로 다룰 수 있도록, DBMS와의 통신 과정을 내포한 데이터 모델을 생성하는 모듈을 의미합니다. 이때 Table은 하나의 타입(Class)에 맵핑되고, Row는 그 타입의 인스턴스로 맵핑된다고 볼 수 있습니다.

ORM을 이용하면 데이터 처리 로직 및 DBMS와의 통신 과정을 은닉, 추상화 할 수 있기 때문에 구조적인 코드를 작성하는데 필수적입니다. 물론 시스템에서 다루는 데이터의 스키마가 복잡하지 않다면 굳이 DB 작업에 부가적인 비용을 초래하는 ORM을 이용 할 필요는 없습니다. 또한 ORM을 쓰더라도 복잡한 쿼리(집계, JOIN 등)를 수행 할 때는 DBMS에 직접 쿼리를 전송하는 코드를 혼용 할 수도 있겠습니다.

ORM은 DBMS 제조사에서 지원하기 보다는 오픈소스 프로젝트로 존재하곤 합니다. 또한 대부분의 웹 프레임워크들은 자체적으로 ORM 모듈을 내장하고 있습니다. 이번 챕터에서는 특정 ORM을 선택해서 공부하지 않고, ORM을 이용한 코드의 예시와 함께 Node.js의 ORM 모듈을 몇가지 소개하는 것으로 마무리합니다.

Sequelize

const Sequelize = require('sequelize');
const sequelize = new Sequelize('database', 'username', 'password');

const User = sequelize.define('user', {
  username: Sequelize.STRING,
  birthday: Sequelize.DATE
});

sequelize.sync()
  .then(() => User.create({
    username: 'janedoe',
    birthday: new Date(1980, 6, 20)
  }))
  .then(jane => {
    console.log(jane.get({
      plain: true
    }));
  });

Sequelize.js : PostgreSQL, MySQL, MariaDB, SQLite, MS-SQL를 지원하며 GitHub에서 최고의 활성도를 보이고 있는 오픈소스, 기능 많음

Waterline

const newOrg = await Organization.create({
  slug: 'foo'
})
.fetch();

Waterline : Node.js에서 높은 인기를 가진 Sails.js 웹 프레임워크에서 채택한 ORM, 문서 완성도 높음, 확장성 높음

다양한 패키지들의 코드의 짜임새나 기능을 보시고, 본인의 목적과 필요, 코드 스타일에 적합한 패키지를 선택하시면 좋겠습니다. 항상 그렇지만 라이브러리나 프레임워크는 필수가 아니라 선택입니다.

목차
5. 데이터베이스
6. 개발과 배포
저자

김동욱

개발 경력 약 10년, 전문 분야는 웹 및 각종 응용 소프트웨어 플랫폼입니다. Codeflow를 운영하고 있습니다.

2018년 04월 10일 업데이트

지원되지 않는 웹 브라우저거나 예기치 않은 오류가 발생했습니다.