본문 바로가기
프로그래밍 개발/NodeJs

Node.js - 출력정보에 대한 보안

by Jinseok Kim 2020. 12. 26.
반응형

 

출력정보에 대한 보안

 

 

 

페이지에서 사용자가 업데이트나 수정을 하였을때 폼 값 자체를 <script></script> 태그와 같은 것으로 버그를 유발하며 출력 정보를 조작할 수 있다.

 

 

아래의 예처럼 <script>태그로 alert 경고창을 뜨게 하도록 할 수도 있다. 

 

 

 

 

 

  • 이러한 버그는 아주 기본적인 예시일 뿐이며 더 나아간다면 정보 탈취 등등 많은 심각한 보안 문제로 사건이 커질 수 있다. 
  • 이를 해결하기 위해서는 필터링 처리를 하는 일명 '소독'이 필요하다.

 

 

 

아래 사이트는 npm이라는 모듈의 정보를 서비스 제공하는 사이트이다. 여기서 HTML의 <sciprt>와 같은 태그들을 필터링하기 위한 모듈 서비스 중 하나인 "sanitize-html"의 모듈을 찾을 수 있다.

 

유의할 점은 npm 모듈을 모두 신뢰하면 안된다. 사이트에 들어가 다른 개발자 사용자들의 후기와 다운로드 비율을 확인하고 검증되었는지 확인하는 것이 중요하다.

 

https://www.npmjs.com/package/sanitize-html

 

sanitize-html

Clean up user-submitted HTML, preserving whitelisted elements and whitelisted attributes on a per-element basis

www.npmjs.com

 

 

 

사이트에 들어가면 아래와 같이 사용방법이 나와있다.

 

 

 

 

 

 

 

 

 

 

npm 모듈 실행 방법과 활용

 

 

 

 

 

명령 프롬프트에서 "npm init"을 입력하고 엔터를 여러 번 치면 적용하고자 하는 웹 정보들의 절차를 시작하게 된다.

 

 

 

 

 

 

 

 

엔터를 Is thos OK? (yes)가 나올때까지 치면 다시 명령어를 칠 수 있는데 이는 npm 모듈을 적용할 수 있는 절차가 끝났다는 것이다.

 

그 다음으로 명령어 칸에 "npm install -S sanitize-html"을 적어주고 다시 엔터를 눌러 실행 해주면 sanitize-html이라는 모듈을 다운 받아 적용받고자 하는 웹의 정보 파일 폴더인 nodejs에 모듈의 내용 파일들이 저장된다.

 

 

 

 

 

 

 

아래와 같이 모듈 파일들이 적용된 것을 확인 할 수 있다. 저 수 많은 모듈 파일들은 sanitize-html을 실행시키기 위한 여러가지의 복잡한 의존파일들이다. 이 많은 것들을 npm 모듈이 해주는 것이다.

 

 

 

 

 

자세히 찾아보면 모듈 파일 폴더 목록에 sanitize-html이 들어있고 package.json 파일에서 "sanitize-html"버전을 의존하여 사용하고 있다는 것을 확인할 수있다.

 

 

 

 

 

 

이제 아래와 같이 코드를 수정하면 된다.

 


var http = require('http');
var fs = require('fs');
var url = require('url');
var qs = require('querystring');
var template = require('./data/muse.js');
var path = require('path');
var sanitizeHtml = require('sanitize-html');
//npm 모듈인 'sanitize-html'을 불러와 변수 sanitizeHtml로 선언해준다.
 
var app = http.createServer(function(request,response){
    var _url = request.url;
    var queryData = url.parse(_url, true).query;
    var pathname = url.parse(_url, true).pathname;
    if(pathname === '/'){
      if(queryData.id === undefined){
        fs.readdir('./data', function(error, filelist){
          var title = 'Welcome';
          var description = 'Hello, Node.js';
          var list = template.list(filelist);
          var html = template.HTML(title, list,
            `<h2>${title}</h2>${description}`,
            `<a href="/create">create</a>`
          );
          response.writeHead(200);
          response.end(html);
        });
      } else {
        fs.readdir('./data', function(error, filelist){
          var filteredId = path.parse(queryData.id).base; 
          fs.readFile(`data/${filteredId}`, 'utf8', function(err, description){
            var title = queryData.id;
            var sanitizedTitle = sanitizeHtml(title);
            var sanitizedDescription = sanitizeHtml(description, {
              allowedTags:['h1'] 
              //sanitizeHtml 변수 메소드의 두 번째 인자는 태그 중에 허용할 수 있는 태그를 설정할 수 있다.
            });
            //위와 같이 npm 모듈이 들어가 있는 변수 sanitizeHtml을 이용하여 title과 description
            //내용을 <sciprt>와 같은 태그로 제어하는 버그를 막아주고 필터링하여 소독한다고 말할 수 있다.
            var list = template.list(filelist);
            var html = template.HTML(title, list,
              `<h2>${sanitizedTitle}</h2>${sanitizedDescription}`, 
              //(title, description부분들 모두 sanitizedTitle, sanitizedDescription로 각각 수정.
              ` <a href="/create">create</a>
                <a href="/update?id=${sanitizedTitle}">update</a>
                <form action="delete_process" method="post">
                  <input type="hidden" name="id" value="${sanitizedTitle}">
                  <input type="submit" value="delete">
                </form>`
            );
            response.writeHead(200);
            response.end(html);
          });
        });
      }
    } else if(pathname === '/create'){
      fs.readdir('./data', function(error, filelist){
        var title = 'WEB - create';
        var list = template.list(filelist);
        var html = template.HTML(title, list, `
          <form action="/create_process" method="post">
            <p><input type="text" name="title" placeholder="title"></p>
            <p>
              <textarea name="description" placeholder="description"></textarea>
            </p>
            <p>
              <input type="submit">
            </p>
          </form>
        `, '');
        response.writeHead(200);
        response.end(html);
      });
    } else if(pathname === '/create_process'){
      var body = '';
      request.on('data', function(data){
          body = body + data;
      });
      request.on('end', function(){
          var post = qs.parse(body);
          var title = post.title;
          var description = post.description;
          fs.writeFile(`data/${title}`, description, 'utf8', function(err){
            response.writeHead(302, {Location: `/?id=${title}`});
            response.end();
          })
      });
    } else if(pathname === '/update'){
      fs.readdir('./data', function(error, filelist){
           var filteredId = path.parse(queryData.id).base;
        fs.readFile(`data/${filteredId}`, 'utf8', function(err, description){
          var title = queryData.id;
          var list = template.list(filelist);
          var html = template.HTML(title, list,
            `
            <form action="/update_process" method="post">
              <input type="hidden" name="id" value="${title}">
              <p><input type="text" name="title" placeholder="title" value="${title}"></p>
              <p>
                <textarea name="description" placeholder="description">${description}</textarea>
              </p>
              <p>
                <input type="submit">
              </p>
            </form>
            `,
            `<a href="/create">create</a> <a href="/update?id=${title}">update</a>`
          );
          response.writeHead(200);
          response.end(html);
        });
      });
    } else if(pathname === '/update_process'){
      var body = '';
      request.on('data', function(data){
          body = body + data;
      });
      request.on('end', function(){
          var post = qs.parse(body);
          var id = post.id;
            var filteredId = path.parse(id).base;
          var title = post.title;
          var description = post.description;
          fs.rename(`data/${filteredId}`, `data/${title}`, function(error){
            fs.writeFile(`data/${title}`, description, 'utf8', function(err){
              response.writeHead(302, {Location: `/?id=${title}`});
              response.end();
            })
          });
      });
    } else if(pathname === '/delete_process'){
      var body = '';
      request.on('data', function(data){
          body = body + data;
      });
      request.on('end', function(){
          var post = qs.parse(body);
          var id = post.id;
            var filteredId = path.parse(id).base;
          fs.unlink(`data/${filteredId}`, function(error){
            response.writeHead(302, {Location: `/`});
            response.end();
          })
      });
    } else {
      response.writeHead(404);
      response.end('Not found');
    }
});
app.listen(3000);

이번에는 alert 경고창이 뜨지 않았으며 따로 허용한 <h1>는 필터링 되지 않고 그대로 적용되었다.

 

 

결과 소스를 보면 따로 허용한 <h1>태그는 남아있지만 alert가 들어간 <sciprt> 태그의 없어진 것을 확인 할 수 있다.

 

 

 

 

반응형

댓글