안녕하세요 깍돌이 입니다.

저번에는 mysql 을 기본적으로 세팅해서 SELECT 문 정도로만 테스트로 해보았었는데요

이번에는 CRUD 작업들을 작성하기 전에 SQL 을 작성할때 흔히 말하는 "날 쿼리" 를 작성하지 않도록 하는 작업부터

해보도록 하려고 합니다.

 

DOCS (node-mysql2) & Prepared Statements 

우선  SQL 인젝션에 대해서 조사를 하다보면 가장 많이 나오는 이야기 들이 "날 쿼리" 를 사용하지 않아야 한다는 점입니다.  예를 들면 

  const sql = `SELECT * FROM ${table} where uuid = ${_uuid}`;

위와같이 작성 하였을 때 날 쿼리라고 하게 됩니다.  _uuid 값은 사용자가 웹에서 입력해서 넘어오는 값이 될수 있습니다. 그렇기 에 입력하는 입력 폼에서 sql injection 이 가능해지는 경우가 됩니다. 

 

또한 해당 입력되는 값에 SQL 의 예약어가 들어오게되면 서버 에러도 유발 시킬수 있습니다. 

 

우선 Prepared Statements 의 경우 왜 인젝션에 방어가 되는가 에 대한 설명은 블로그로 대체 하도록 하겠습니다.

m.blog.naver.com/PostView.nhn?blogId=skinfosec2000&logNo=220482240245&proxyReferer=https:%2F%2Fwww.google.com%2F

 

[SQL인젝션] Prepared Statement를 쓰면 SQL 인젝션 공격이 되지 않는 이유는?

Prepared Statement의 내부 원리 개요 SQL인젝션 취약점에 대한 대응방안으로 Prepared Statement와...

blog.naver.com

SK 인포섹 블로그 라고 되어있는데 보안은 전문가들에게 맡기는게 좋을 것 같습니다. ^^ 

 

그럼이제 Node.js & mysql2 로 해당 과정을 작성하여야 하는데요 위에 잘못된 예시인 "날 쿼리" 를 변경하는 작업을 보겠습니다. 

먼저 제목과 같이 DOCS 도큐먼트라고 하죠 저희가 쓰고 있는 라이브러리는 mysql2 을 쓰고있기 때문에 mysql2 의 도큐먼트를 먼저 찾아 보는게 좋습니다. (docs가 이해가안된다면 한글로 구글링하는 방안으로 ) 도큐먼트를 자주 보다보면 어느샌가 DOCS의 틀? 프레임? 덕분에  검색되는 내용보다 더 이해가 쉽게 되는 경우가 있습니다.

 

Node.js 의 사용모듈의 DOCS를 찾을 때 저는 npm 으로 먼저 확인합니다.

 

1. npm 검색 (mysql2)

 

2. Repository 이동

여기서 HomePage가 DOCS인경우도있고 Repository 가 DOCS인 경우도 있습니다. 둘다 같은 경우도있구요 저는 Repository 로 들어가서 prepared statements에 대해 검색해보았습니다.

 

떡하니 목차가 있네요 이제 해당 DOCS를 보고 위에 있는 "날 쿼리" 를 변경해보도록 하겠습니다.

DOCS 주소입니다.(github.com/sidorares/node-mysql2#using-prepared-statements)

기존 날 쿼리

  const sql = `SELECT * FROM ${table} where uuid = ${_uuid}`;

변경된 쿼리

const sql = `SELECT * FROM ${this.table} WHERE uuid = ?`;

단순 비교하자면 uuid 를 받는 부분이 ? 로 변경되어있습니다. 해당 이유는 ( SK 인포섹 블로그 글 참조 )

 

그리고 해당을 이용하여서 전체 수정시 아래와 같이 변경할수 있습니다.

 const uuid = req.params.id;

    if(uuid!==undefined){
      const sql = `SELECT * FROM ${this.table} WHERE uuid = ?`;

      const connection = mysql.createConnection(this.db_config);
      connection.connect();
  
      connection.execute(sql,[uuid],
        function(err,results,fields){
          if(err) {
            console.error(err);
            res.status(500).json({'message':'DB Result error'});
          }
          else{
            console.log(results);
            res.status(200).json({response:results})
            ;
          }
        })
      connection.end();
    }
    else{
      res.json({message:"Not Exist UUID"});
    }

저희는 express로 DB 에 대한 API 를 작성해야 하기 때문에 위와같이 작성이 되었습니다.

http://{serverIP}/action/uuid 와 같은 형태입니다.   action?uuid= 같은 형태가 아니기때문에 

(? 뒤에 붙는 부분들을 fragment라고 하는데 express에서는 req.query로 작성하면 되지만 위와같은 경우는)

req.params으로 받습니다.  

 

유의사항

기존에는 connection.query -> 변경된 코드를 유심히 보면 connection.exeucte 입니다. 

또한 values를 매핑하기 위한 (? 에 대칭 ) 두번째 파라미터는 [ ] -> 배열의 형태를 가지고있습니다. 이부분 유의하여서 작성하시기 바랍니다. 

 

기본적인 sql Injection 에 대한 SQL prepared statements 였습니다.

 

감사합니다.

 

안녕하세요 깍돌이입니당 ~

저번 포스팅까지는 Mysql 8.0 을 서버에 설치하고  간단하게 SQL 문 등 몇가지 테스트만 해보았는데요

이제는 해당서버에 있는 DB와 연동하기 위한 Manager 서버를 구성하려고 합니다.

 

Node.js 관련 포스팅은 다른글에도 있고 하니 기본적인건 제외 하고 시작 하도록 하겠습니다.

 

Package.json

해당 파일 이미지를 통해 어떠한 부분을 생성하였고 안하였는지 체크 하면 될 것 같습니다.

devDependencies -> npm i -D [모듈명] 으로 설치 한 모듈입니다.

dependencies ->npm i 로 [모듈명] 으로 설치 한 모듈입니다.

** mysql2 의 @types 는  npm i -D types/mysql2 으로 설치합니다.

  "dependencies": {
    "axios": "^0.20.0",
    "cookie-parser": "^1.4.5",
    "cors": "^2.8.5",
    "dotenv": "^8.2.0",
    "express": "^4.17.1",
    "helmet": "^4.1.0",
    "moment": "^2.27.0",
    "mysql2": "^2.1.0"
  },
  "devDependencies": {
    "@types/axios": "^0.14.0",
    "@types/cookie-parser": "^1.4.2",
    "@types/cors": "^2.8.7",
    "@types/express": "^4.17.7",
    "@types/moment": "^2.13.0",
    "@types/multer": "^1.4.4",
    "@types/mysql2": "github:types/mysql2",
    "@types/node": "^14.6.2",
    "@types/node-cron": "^2.0.3",
    "@types/serve-favicon": "^2.5.0",
    "pm2": "^4.4.1",
    "typescript": "^4.0.2"
  }

위와같이 설치 되었습니다. ( 작업하다 더 늘어나면 해당 글에 최신화를 할 예정입니다. )

현재 mysql8에는 

위와같이 테스트 데이터를 넣어 놓은 상태입니다.

 

Node.js에서 해당 파일을 mysql2 모듈을 이용해서 CRUD 하는 작업을 간단하게 작성 하도록 하겠습니다.

 

Express & TypeScript

Express.js를 TypeScript로 일단 기본 미들웨어 세팅을 구성하려고 합니다.

 

github.com/lgance/network.git

 

lgance/network

Contribute to lgance/network development by creating an account on GitHub.

github.com

위의 network 로 되어있는 프로젝트가 typescript & Express.js의 Boilerplate code 입니다. 현재는 더이상 사용하거나 추가하지 않기 때문에 위의 프로젝트를 기준으로 작성 하겠습니다.

 

Mysql 연결

기본적인 틀은 위의 github을 통해서 작성을 한 상태로 Mysql 을 연결하는 Router 만 작성하도록 하겠습니다.

 

import * as express from 'express';
import Controller from '../interface/controller.interface'


class ActionStatusController implements Controller {
  public path='/action';
  public pathId='/action/:id';
  public router = express.Router();

  constructor(){
    this.initializeRoutes();
  }
  private initializeRoutes(){
    /** 모든 액션 가져오기  */
    this.router.get(this.path,this.readAllActionStatus);
    /** 특정 액션 가져오기 */
    this.router.get(this.pathId,this.readActionStatus);
    /** 특정 액션 지우기*/
    this.router.delete(this.pathId,this.deleteActionStatus);
    /** 액션 생성 */
    this.router.post(this.pathId,this.createActionStatus);
    /** 액션 변경 */
    this.router.put(this.pathId,this.updateActionStatus);
  }

  private readAllActionStatus = async (req:express.Request,res:express.Response)=>{
    const _str = this._LOG(req).concat(' Read All Action Status');
    res.send(_str);
  }
  private createActionStatus = async(req:express.Request,res:express.Response)=>{
    const _str = this._LOG(req).concat(' Create Action Status');
    res.send(_str);
  }
  private readActionStatus = async (req:express.Request,res:express.Response)=>{
    const _str = this._LOG(req).concat(' Read Action Status');
    res.send(_str);
  }
  private updateActionStatus = async(req:express.Request,res:express.Response)=>{
    const _str = this._LOG(req).concat(' Update Action Status');
    res.send(_str);
  }
  private deleteActionStatus = async(req:express.Request,res:express.Response)=>{
    const _str = this._LOG(req).concat(' Delete Action Status');
    res.send(_str);
  } 

  private _LOG = (req:express.Request):string => {
    const id:string = req.params.id;
    console.log(`Check ID : [${id}]`);
    if(id===undefined){
      return '';
    }
    else{
      return id;
    }
  }

}
export default ActionStatusController;

기본적으로 action Controller를 하나 생성합니다.

 

각각의 기본적인 CRUD를 작성하겠습니다. ( 테스트용 ) 

초반이니까 얼렁 얼렁 작성해서 ㅎㅎ;;

 

일단  http://localhost/action 로 접속 해서 확인해봅시다.

 

정상적으로 나왔으니까 이제 mysql2을 이용하여서 간단한 쿼리로 SELECT 문을 이용하여 가져와 봅니다.

 

mysql 모듈 Import

import * as mysql from 'mysql2';

DB Config 파일 설정 

 private db_config = {
    "host":process.env.DB_HOST,
    "user":process.env.DB_USERNAME,
    "password":process.env.DB_PASSWORD,
    "database":process.env.DB_NAME
  }

ReadAllActionStatus Controller 수정 

 private readAllActionStatus = async (req:express.Request,res:express.Response)=>{
    const _str = this._LOG(req).concat(' Read All Action Status');
    const table = 'status_table';
    const query = `SELECT * FROM ${table}`;

    const connection = mysql.createConnection(this.db_config);
    connection.connect();

    connection.query(query,function(error,results,fields){
      if (error) throw error;
      console.log(results);
  
      res.send(results);
      console.log('connected as id ' + connection.threadId);
    });
    connection.end();
   
  }

 

 

결과는 ? 

 

네 정상적으로 연결되어 값을 가져옴을 확인하였습니다.  하지만 여러가지 문제가 있습니다.

 

1. query Callback 

connection.query 의 Callback 으로 결과를 받은 후에 res.send 를 해주기 때문에 Client한테 지연을 줄수 있습니다. (좋은방법 X)

 

2. SQL Injection 

sql injection 에 당하기 딱 좋은 방법이죠 위의 코드에선 안당하겠지만 (너무단순한 SQL 문이라)

이부분을 유의하여서 앞으로 작성하도록 하겠습니다.

 

감사합니다.

 

Thanks for always

 

 

 

 

 

 

 

 

안녕하세요 기본 세팅 및 정리  [2] 를 작성하다가 컴퓨터를 꺼버리게 되어서 작업 내용이 날아갔네요 ㅜㅜ

 

다시 기억나는대로 작성 해야겠습니다.  관련 내용은 ( DB Tool 인 Dbeaver 와  외부접속을 위한 Mysql 8.0 서버 세팅 등..)

 

이번에는 서버 세팅후에 이것저것 하나씩 찾아 보았던 부분들을 하나의 페이지로 작성하려고 합니다. 현재 DB 를 학부생 시절 이후로 아예 지식이 전무한 상태에서 DB관련 작업 및 세팅을 해야되서 구글의 힘을 빌리고 있습니다. ㅜ 

 

간단 시연 후에는 전반적으로 DB자체에 대한 공부를 진행 하여 포스팅 하도록 하겠습니다.

 

Date ( Mysql Date 현재 시간 )

Test Automation Platform 을 제작하는 부분에 있어서 모든 액션의 단계에서는 언제 실행되었는지에 대한 값이 필요하기 때문에 Date라는 컬럼이 꼭 필요하여야 했습니다. 또한 절대 없으면 안되는 값이 였구요  그래서 이 값을 DB에서 제공하는 현재 시간 값을 자동으로 넣을지 혹은 node.js에서 new Date를 찍어서 DB에 넣어 줄지에 대한 선택이 필요 하였고 근처 지인 개발자들에게 질문 했을 시 답은 DB에 있는 기본 날짜를 사용하라 였습니다.  단순하게 보자면 DB에서 new Date를 기본값으로 찍히는게 Node.js 에서 매번 insert 할 때 마다 new Date를 찍는거보다 훨씬 빠르고 부하가 적기 때문입니다.

 

그래서 생성된 Date컬럼에 기본값으로 넣어 주기 위해서 아래의 SQL문을 작성 하였습니다.

ALTER TABLE [테이블명] MODIFY COLUMN [컬럼명] datetime DEFAULT current_timestamp NOT NULL;
ALTER TABLE status_table MODIFY COLUMN `date` datetime DEFAULT current_timestamp NOT NULL;
ALTER TABLE [테이블명] MODIFY COLUMN [컬럼명] datetime DEFAULT now() NOT NULL;
ALTER TABLE status_table MODIFY COLUMN `date` datetime DEFAULT now() NOT NULL;

 

저는 현재 Mysql 8.0 버전을 사용하고 있으며 위의 2개 SQL문 이 되는 점을 확인 하였습니다.

alter를 이용하여 date 컬럼에 기본 값으로 now() 혹은 current_timestamp 을 사용하며 해당 컬럼은 NOT NULL 으로 설정 합니다.

 

Insert 문을 이용하여서 테스트시 

정상적으로 기본 날짜로 들어가는 점이 확인되었습니다.

 

AutoIncrement  초기화

NO 컬럼을 이용하여서 DB의 autoincrement 을 사용하지만 개발 중 테스트 단계에서는 이부분은 초기화 할 일이 있었습니다. 당연히 이후로는 없어야 할 것 이구요

NO 컬럼에 autoincrement가 들어가있습니다. 테스트 중이니 추후에 는 꼭 초기화가 필요합니다.

1- 초기화 를 위해 아래의 SQL문을 사용 합니다. 

ALTER TABLE [테이블 명] AUTO_INCREMENT = [시작 값]
ALTER TABLE status_table AUTO_INCREMENT=0

 ALTER TABLE [테이블명] AUTO_INCREMENT=[시작값] 인데요

 

해당 SQL문 전에

위와같은 데이터가 있은 후에 다시 INSERT를 하게되면

보시는 것처럼 AUTO_INCREMENT 값이 초기화가 되어있지 않습니다.  이를 동작하게 하기위해선 시작하는 AUTO_INCREMENT 값보다 더 큰 값이 있으면 안되기 때문인데요 해당 데이터를 전부 날려보고 다시 작업합니다.

 

DELETE FROM [Table명]
DELETE FROM status_table

아직은 테스트 단계니 날려보고 다시 INSERT를 하게되면

0부터 시작하게 되어 1이 표시됨을 확인 할수 있습니다.

 

 

 

 

 

 

 

 

 

전 포스팅인 세팅 [1] 에서 세팅이 끝났다면 이제 사용해야할 툴 을 다운로드 하여야 합니다.

 

필요하다면 generated 된 비밀번호를 변경하여야 합니다.

 

비밀번호 변경

mysql -u root -p 명령어로 로그인 시 비밀번호 ( generated - /var/log/mysqld.log ) 를 입력 후 해당 mysql은 8.0 버전 이기 때문에 로그인 후 mysql 선택시

 

mysql> use mysql;
No connection. Trying to reconnect...
Connection id:    11
Current database: *** NONE ***

ERROR 1820 (HY000): You must reset your password using ALTER USER statement before executing this statement.
mysql> 

 

와 같이 비밀번호를 변경해야 하는 문구가 발생합니다. 그래서 관련 내용을 찾기 위해 구글링을 해보았습니다.

1.  SET PASSWORD 

mysql> SET PASSWORD = PASSWORD('ppap1234!@');
ERROR 1064 (42000): You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'PASSWORD('ppap1234!@')' at line 1

정상적으로 되지않습니다.

2. ALTER 

mysql> ALTER USER'root'@'%' IDENTIFIED BY 'Ppap1234!@';
ERROR 1820 (HY000): You must reset your password using ALTER USER statement before executing this statement.

정상적으로 되지않았습니다.

 

해결

mysql> set password='Testpw12#$';
Query OK, 0 rows affected (0.01 sec)

FLUSH PRIVILEGES;

 

이제 변경도 완료 했기 때문에 아래와 같이 정상적으로 변경됨을 기대합니다.

mysql> use mysql
Reading table information for completion of table and column names
You can turn off this feature to get a quicker startup with -A

Database changed
mysql> 

위의 상태를 만들어 놓은 후 

 

DBeaver 

네 DBMS 관리 UI 툴로 제가 선택하게 된 툴입니다.

해당 설치를 위해서 https://dbeaver.io/ 로 이동 합니다.   메인 화면에서 Download를 선택 후  Windows Installer 

 

설치 후 실행 시 Mysql 선택

 

여러가지 세팅을 완료하고 접속을 하려고하면 "is not allowed to connect to this MySQL Server"  메시지가 발견된다.

포스팅 1에서 말씀 드렸듯이 NCP(NaverCloudPlatform)의 VM 을 사용하였습니다.  접속이 안되는 메시지 이기 때문에 1차적으로 ACG를 확인하여야 합니다. ( 공인 IP는 달려있는 상태입니다. ) 

 

테스트 용 및 Dev , Beta 환경이기 때문에 으로 일단은 0.0.0.0/0 의 3306 포트를 열어 놓았습니다. ( 위의 환경은 민간 Classic 환경입니다. ) -> 만약에 Prod 환경이라면 3306를 열지 않아도 BE를 통해서 DB를 조작하기때문에 상관이 없을 수 있습니다. 

--> ? 하지만 결과는 동작하지 않습니다. 

서버도 잘 떠있는데 말이죠 ... 여기서 이제 확인해야 할 것은 mysql 자체의 방화벽 설정입니다.

mysql 자체에서 허용을 해줘야 하는 부분인데 아무리 테스트여도 root로 열어주긴 싫기 때문에 ID를 새로 생성하였습니다.

// nstpmanager 계정 생성 
CREATE USER nstpmanager@'%' IDENTIFIED BY '비밀번호';

// nstpmanager 계정에 모든 IP허용
GRANT ALL ON *.* TO nstpmanager@'%';

 

그리고 DBeaver 에서 재시도

이부분은 간단합니다. 해당 Driver properties 에서 allowPublicKeyRetrieval 을 YES 로 허용해주면 됩니다.

 

 

완료 

 

 

** caching_sha2_password 오류에 대해서

아래의 SQL 구문을 통해서 DBeaver 접속을 해결하였지만 의문이 들긴 합니다.최신버전에서 sha2 방식을 사용한다면 해당 플러그인을 툴에서 지원하던지 해야되는데 접속을 위해서 DBMS에서 기존방식을 채택한다라 이부분도 나중에 확인해야 할 과제겠네요

 

 

안녕하세요 깍돌이입니다.

 

저ㅓㅓㅓㅓㅓㅇ말 오랜만에 글을 작성하는 거 같습니다. ㅜ

 

제품 릴리즈 배포하느라고 너무 바빴습니다. ( 변명 )  

 

사실 데모 시연을 하기 위해서 DB서버를 하나 세팅해놓아야 하는데 하는 일이 생겨서 남겨 놓으려고 합니다. 

 

OS는 Centos 7.8 

DBMS는 Mysql 8.0.xx버전을 사용할 생각입니다.

개발 플랫폼 Node.js를 사용합니다.

 

 

OS

Centos 7.8 은 설치가 되어있다는 가정하에 작성하겠습니다. 저는 NCP(NaverCloudPlatform)에 있는 Centos 7.8을 사용하였습니다.

 

DBMS

Mysql을 설치하기 위해서는 사이트를 접속해야 되니 아래의 사이트로 접속합시다.

https://www.mysql.com/downloads/

 

MySQL :: MySQL Downloads

Contact MySQL  |  Login  |  Register The world's most popular open source database MySQL.com Downloads Documentation Developer Zone MySQL Enterprise Edition includes the most comprehensive set of advanced features and management tools for MySQL. MySQL

www.mysql.com

Enterprise는 기업용이기 때문에 유료 형태로 제공이 됩니다. 우리는 무료 버전을 써야 하기 때문에

밑에 있는 Community Download를 클릭합시다.

 

Centos의 경우 YUM Repo를 사용하기 때문에

 

 

MySQL Yum Repository를 선택합니다.

 

그리고 Centos 7버 전이기 때문에 7 버전에서 Download 버튼을 클릭합니다.

 

 

Download버튼을 누르면 ~ 로그인을 해야 된다 뭐 이런 이야기가 나오는데요

 

하실 필요는 없으시고  No thanks, just start my download -> 우클릭 -> 링크 주소 복사 (E)

하고 

 

나의 VM으로 접속하셔서

 

 

wget [복사한 링크  붙여 넣기]  후 Enter 시 위와 같이 mysql repo 작업을 하는 rpm 이 다운로드되고

 

rpm -ivh mysql80-community-release-el7-3.noarch.rpm 

을 입력하여 레포를 설정합시다.

 

작업 후 확인을 위해서 

cat /etc/yum.repos.d/mysql-community.repo를 확인해 봅시다

 

 

 

우리의 Centos에 mysql 8.0을 깔기 위한 repository 작업이 끝났습니다.

yum -y install mysql-server로  해당 레포를 가지고 설치를 시작해봅시다.

 

설치 완료 후 mysql이라고 명령어를 검색하게 되면

 

Can't connect to local MySQL server through socket '/var/lib/mysql/mysql.sock ' (2) 에러가 발생합니다.

mysql 이 시작되기 않았기 때문인데요

 

아래의 명령어로 실행 , 활성화, 상태를 확인해봅시다

 

sudo systemctl start mysqld
sudo systemctl enable mysqld
sudo systemctl status mysqld

 

 

mysql_secure_installation라는 걸 통해서 보안설정을 하라는 다른 포스팅도 있고 그런데요 저는 8.0을 설치했기 때문에 따로 할 필요는 없어 보입니다.

 

 

 

mysqld -uroot -p

 

입력하시게 되면 root 비밀번호를 입력하여야 하는데 

?? 설정한 적이 없기 때문에 비밀번호를 알 수가 없습니다.

 

그러기 때문에 대부분 비밀번호 입력 없이 엔터를 치시면 접속이 된다고 하였지만

 

ERROR 1045 (28000): Access denied for user 'root'@'localhost' (using password: YES)

 

-> 저는 접속이 되지 않았습니다. ㅜ

 

일단 접속을 하기 위해서

 

vi /etc/my.cnf 파일에

 

skip-grant-tables를 입력하고 저장합니다.

 

 

그리고 systemctl restart mysqld

mysql -u root -p 비밀번호 무시하고 엔터 

 

 

접속 성공!!

 

 

------> 위의 과정은 모든 게 잘못된 과정이었습니다. 

 

Repo 까진 정상이었고 설치 시에 mysql-server가 아니라 

mysql-community-server로 설치하여야 합니다.

(5.6 이후로는 무료 버전에 community 가 붙는다고 합니다. )

 

yum -y install mysql-server              ----- > ( X ) 

yum -y install mysql-community-server ---->( O ) 

 

설치 완료 후

 

systemctl start mysqld

mysql -u root -p 시 비밀번호는

 

vi /var/log/mysqld.log

로그에 들어가면 password 가 generator 되어있습니다.

 

해당 비밀번호를 가지고 접속하시면 됩니다.

 

진짜 접속 성공!!

 

 

 

+ Recent posts