본문 바로가기
computer science/데이터베이스

[데이터베이스] SQL Injection

by 박연호의 개발 블로그 2020. 8. 26.



이번시간에는 SQL Injection에 대해 공부해 보겠습니다.

 

SQL Injection은 데이터베이스와 연동된 웹 어플리케이션에서 SQL 질의문에 대한 필터링이 제대로 이루어지지 않을 경우 공격자가 입력이 가능한 폼(웹 브라우저 주소 입력창 또는 로그인 폼 등..)에 조작된 질의문을 삽입하여 웹 서버의 데이터베이스를 조작할 수 있는 취약점을 의미합니다

 

SQL Injection은 다음의 2가지 항목을 충족해야만 공격이 가능합니다.

 

1. 웹 어플리케이션이 DB를 사용하고 있다.

2. 클라이언트의 입력값이 DB 쿼리문에서 사용된다.

 

최근 대부분의 웹 어플리케이션은 client - 서버 - DB의 구조를 가지기 때문에 위 두가지 조건을 대부분 충족합니다. 때문에 이러한 SQL Injection의 대상이 될 수 있습니다.


SQL Injection 종류

SQL Inection에는 크게 3가지로 분류하 수 있습니다.

 

1. 인증 우회(AB : Auth ByPass)

2. 데이터 노출(DD : Data Disclosure)

3. 원격 명령 실행(RCE : Remote Command Execute)

 

// 테이블 구조

mysql> desc user;
+-------+-------------+------+-----+---------+-------+
| Field | Type        | Null | Key | Default | Extra |
+-------+-------------+------+-----+---------+-------+
| id    | varchar(50) | YES  |     | NULL    |       |
| pw    | varchar(50) | YES  |     | NULL    |       |
| name  | varchar(50) | YES  |     | NULL    |       |
+-------+-------------+------+-----+---------+-------+
3 rows in set (0.00 sec)


mysql> select * from user;
+-------+------+-------+
| id    | pw   | name  |
+-------+------+-------+
| user1 | pw1  | name1 |
+-------+------+-------+
1 row in set (0.00 sec)



// 서버코드

app.post("/test", (req, res, next) => {

  const query = `select * from user where id=${req.body.id} and pw=${req.body.pw}`;

  connection.query(query);
});

 

1. 인증 우회(AB : Auth ByPass)

보통 로그인을 할 때 아이디/비밀번호를 form에 입력 후 서버로 전송하고 DB에서 해당 정보를 조회하게 됩니다. 이때 아이디/비밀번호에 SQL의 논리적 오류를 이용하여 로그인 Query문을 무조건 true가 나오게 하여 인증을 무력화 할 수 있습니다.

app.post("/test", (req, res, next) => {
  
  // {id : 'user1' , pw:'qwe' or 2>1--}
  const query = `select * from user where id=${req.body.id} and pw=${req.body.pw}`; 

  connection.query(query, (err, result) => {
  // [ RowDataPacket { id: 'user1', pw: 'pw1', name: 'name1' } ]
    console.log(result); 
  });
  res.end();
});

 

DB에서 user1의 비밀번호는 pw1인데도 불구하고 유저가 조회되었습니다. 전체쿼리를 보면 ~ and pw ='qwe' or 2>1 --가 됩니다. 여기서 pw의 값은 'qwe'가 아니지만 2>1은 true이기 때문에 and 이후의 결과값은 true가 되어 해당 유저가 조회되게 됩니다. 마지막 --은 주석을 의미하기 때문에 실행되지 않습니다.

 

2 > 1에는 '1'='1'--, true...등 참이 되는 어떠한 조건을 넣어도 유효합니다.

 

또한 pw에 빈값을 넣고 id에 'user1'--을 넣어도 유효한데 이는 select * from user where user='user1'와 같은 쿼리문이 됩니다.

multipleStatements: true 

app.post("/test", (req, res, next) => {
  // {id : '';insert into user(id,pw,name) values('user2','pw2','name2'); , pw:''}
  const query = `select * from user where id=${req.body.id} and pw=${req.body.pw}`;

  connection.query(query, (err, result) => {
    console.log(result);
  });
  res.end();
});


mysql> select * from user;
+-------+------+-------+
| id    | pw   | name  |
+-------+------+-------+
| user1 | pw1  | name1 |
| user2 | pw2  | name2 |
+-------+------+-------+
2 rows in set (0.00 sec)

이번에 id로 '';insert into user(id,pw,name) values('user2','pw2','name2');를 넣어주었습니다. 이렇게 되면 ''라는 사용자를 찾고 ;에서 실행을 종료하지만, multipleStatements:true 옵션을 주었기 때문에 Query를 여러개 실행시켜 원하는 유저의 정보를 DB에 삽입할 수 있습니다.(multipleStatements옵션은 npm mysql 모듈에서 connection을 생성할 때 넘겨주는 옵션값입니다). 지금은 새로운 사용자를 입력하였지만 원한다면 DB에 저장된 사용자의 정보를 수정하거나, DB, table을 날려버리는 Query문을 넣을 수도 있습니다.

 

2.  데이터 노출(DD : Data Disclosure)

시스템의 주요 데이터 절취를 목적으로 하는 방식이며, Error Based, Union Based, Blind Based, Time Based 방식이 있습니다. 웹 사이트에서 에러정보를 확인할 수 있는지 없는지에 따라 공격방식이 결정됩니다. 

app.post("/test", (req, res, next) => {
  // {id : '';insert into user(id,pw,name,age) values('user2','pw2','name2',21) , pw:''}
  const query = `select * from user where id=${req.body.id} and pw=${req.body.pw}`;

  connection.query(query, (err, result) => {
    if (err) {
      console.log(err); // "Unknown column 'age' in 'field list'",
    }
    console.log(result);
  });
  res.end();
});

에러의 내용은 현재 user 테이블에는 age라는 콜럼이 없다고 알려주고 있습니다. 이렇게 외부 공격자는 콜럼을 하나씩 넣어보면서 테이블의 구조와 데이터 타입을 파악할 수 있습니다.


SQL Injection 예방

1. 로그인 전, 검증 로직을 추가하여 미리 설정한 특수문자들이 들어왔을 때 요청을 막아낸다.

 

2. SQL 서버 오류가 발생 한 경우, 해당 에러 메시지를 사용자가 볼 수 없도록 숨겨둔다

 

3. preparedstatement을 사용한다. query문에 변수가 들어갈 자리에 ?를 넣고, 그 다음 인자에 ?에 들어갈 변수를 넣어준다.

connection.query(`select * from user where id=? and pw=${req.body.pw}`,[req.body.id]);

4. connection의 escape메서드 를사용한다(node.js 기준)

 `select * from user where id=? and pw=${connection.escape(req.body.pw)}`