ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [Node.js & MySQL] 보안: SQL injection, Escaping
    생활코딩/WEBn 2021. 1. 13. 20:41

    외부로부터 유입되는 정보는 필터링이 필요하다.

    유저가 입력 데이터로 악성 SQL을 주입해 공격하는 경우인 SQL injection 예방법과

    해당 데이터를 웹브라우저로 실행할 때 발생할 수 있는 Cross site scripting (XSS) 예방법을 살펴본다.

     

    [강의 출처] opentutorials.org/course/3347/21299

     

    SQL injection 예방법

    페이지를 아래처럼 로드한다고 할 때,

    var mysql = require('mysql');
    
    var db = mysql.createConnection({
    	  host     : 'localhost',
    	  user     : 'root',
    	  password : 'password',
    	  database : 'data'
    	})
    db.connect();
    
    var app = http.createServer(function(request, response){
        var _url = request.url;
        var id = url.parse(_url, true).query.id;
    // 여기까지 설정
    
    
        db.query('SELECT * FROM author WHERE id=?',[id], function(err, data){
          // 실제 실행, id는 위에서 URL의 쿼리부분을 읽어온 값이다
        }

    id='1'인 경우 실제 SQL문은 아래처럼 생성된다.

    SELECT * FROM author WHERE id = '1'

    공격자가 URL에 입력된 쿼리 부분이 그대로 SQL문에 전달된다는 사실을 이용해서, 아래와 같은 URL을 요청했다고 하면,

    https://testpage.com/?id=1;DROP TABLE tablename;

    이런 SQL문이 생성된다. SQL은 세미콜론으로 명령을 구분하지만, 전체 인풋이 텍스트로 묶여있기 때문에 해당 공격은 실행되지 않는다.

    SELECT * FROM author WHERE id = '1;DROP TABLE tablename;'

     

    그 이유는 db.query를 호출할 때 1번째 인자로 쿼리문('SELECT * FROM author WHERE id=?')을,

    2번째 인자로 입력데이터(id)를 주어서 mysql '?'를 변수로 치환 후 처리했기 때문이다.

    따라서 가능하면 ? 치환형태로 쿼리문을 실행하는 것이 좋다.

     

    이것이 가능한 이유는 mysql 모듈에서 기본적으로 SQL Injection(입력 데이터로 악성 SQL을 주입해 공격하는 방법)을 예방하기 위한 처리를 해두었기 때문이다.

     

     

    *도큐먼트의 해당 부분에 아래와 같이 설명되어 있다.

    In order to avoid SQL Injection attacks, you should always escape any user provided data before using it inside a SQL query. You can do so using the mysql.escape(), connection.escape() or pool.escape() methods:

    SQL문 삽입을 통한 공격을 피하기 위해서는, 유저가 입력한 데이터를 SQL쿼리에 사용하기 전에 항상 치환해야한다. mysql.escape(), connection.escape(), pool.escape() 함수를 이용할 수 있다.

    var userId = 'some user provided value';
    var sql    = 'SELECT * FROM users WHERE id = ' + connection.escape(userId);  // 이스케이프가 적용되었다
    connection.query(sql, function (error, results, fields) {
      if (error) throw error;
      // ...
    });

    Alternatively, you can use ? characters as placeholders for values you would like to have escaped like this:

    대안으로 ? 문자를 써서 치환하고자 하는 값의 플레이스홀더로 사용할 수 있다:

    connection.query('SELECT * FROM users WHERE id = ?', [userId], function (error, results, fields) {
      if (error) throw error;
      // ...
    });

    그리고 데이터타입에 따라서 어떻게 치환되는지에 대해서도 설명이 나와 있는데,

    숫자는 그대로, 불리언값은 true/false, 데이터는 'YYYY-mm-dd HH:ii:ss' 형태, undefined, null은 NULL 등등이 있다.

    그 중 유용할 듯한 부분은 객체는 key = 'val'로 치환되는 부분.

    var post  = {id: 1, title: 'Hello MySQL'};
    var query = connection.query('INSERT INTO posts SET ?', post, function (error, results, fields) {
      if (error) throw error;
      // Neat!
    });
    console.log(query.sql); // INSERT INTO posts SET `id` = 1, `title` = 'Hello MySQL'

    객체를 넣으면 알아서 변환해준다! 정말이지 좋은 모듈이다.

     

    혹시 이 방법을 몰랐다고 하더라도, 다행히 mysql 모듈측에서 connection.query()로 실행될 수 있는 구문 수를 제한해두었기에 파국으로 치닫지는 않는다.

     

    마찬가지로 도큐먼트에 보면 Connection option에 아래와 같은 옵션이 있다.

    multipleStatements: Allow multiple mysql statements per query. Be careful with this, it could increase the scope of SQL injection attacks. (Default: false)

    한 쿼리에서 여러 mysql 구문을 실행할 수 있도록 허용한다. SQL 주입 공격 유효범위가 늘어날 수 있음에 유의. (기본값은 false)

     

    multipleStatements가 true인 경우에는 테이블이 DROP 될 수 있으니 조심하고, 기본값은 이유 없이 변경하지 말자. 모듈 사용 전에 도큐멘트만 잘 살펴봐도 대부분의 파국은 막을 수 있을 듯하다.

     

     

     

     

    Cross site scripting (XSS) 예방법

    기본에 다루었던 sanitize-html와 같은 방법으로 예방한다.

    유저 입력 데이터를 처리하는 부분을 sanitizeHTML()로 감싸주면 된다.

    유저 입력 데이터를 다루는 모든 부분에 처리가 필요하기 때문에 종종 빠뜨리는 부분이 나올 수 있다. 코딩할 때부터 미리미리 해두자.

    댓글

Designed by Tistory.