본문 바로가기

programming study/Node.js

[생활코딩]WEB2-Node.js (9)(2020.12.23)

본 내용은 해당 강의 토대로 작성


App - 입력 정보에 대한 보안

웹 페이지를 관리할 때, 사용자들의 신상 정보가 있는 password.js 가 있다고 하자.

module.exports = {
  id:'jaemin',
  password:'123456'
}

이러한 파일은 절대로 유출되면 안 되는 것이다. 그런데, 만든 웹사이트에서는 매우 치명적인 보안 취약점이 있다.

쿼리스트링의 id 를 통해서 화면을 출력하는 기능이 있다. 이 쿼리스트링에 어떤 사용자가 악의적으로 ../passport를 입력하면 아래와 같이 화면이 출력된다.

Nodejs9-1

이렇게 매우 간단하게 상위 디렉토리로 넘어가서 password.js 를 출력해 버린 것이다. 이에 더해서, 더 상위의 디렉토리로 간다면 컴퓨터 전체의 파일들이 탐색되어 버릴 위험이 있다.

이렇게 맥없이 정보가 유출되지 않도록 외부에서 들어온 정보에 대한 보안을 추가해보자.

Node.js 기능에서 path를 조회할 수 있는 모듈이 있다.

var path = require('path')
path.parse('../password.js')

콘솔 창에서 모듈을 불러오고 ../password.js 를 조회하면 아래와 같이 출력된다.

 Nodejs9-2

여기서, path.parse('../password.js').base; 를 입력하면, base 만 선택적으로 조회할 수있다.

지금 웹 페이지에서는 아래와 같은 명령어로 페이지를 구현한다.

fs.readFile(`data/${queryData.id}`, 'utf8', function(err, description)

여기서 문제가 되는 부분은 data/${queryData.id} 이다. 쿼리스트링에 ../password.js 를 입력하면 data/../password.js 경로로 들어가게 되어, 그 화면이 출력되는 것이다.

위의 path.parse 명령어를 이용하여, base 만 선택적으로 뽑아온다면 data/password.js 경로로 들어가게 될 것이고, 이 경로는 존재하지 않으므로 정보가 유출되지 않을 것이다. 이것을 웹 페이지의 코드에 추가해준다.

var filteredId = path.parse(queryData.id).base;
           var filteredId = path.parse(queryData.id).base;
           fs.readFile(`data/${filteredId}`, 'utf8', function(err, description){
             ...
           }

변수 filteredId 에 쿼리스트링의 base만 추출되어 이 경로로 들어가게 된다. 그 결과,

Nodejs9-3

이렇게 타이틀은 바뀌었지만, 컴퓨터의 파일 내용은 유출되지 않는다. 다른 페이지에 넘어가는 기능은 영향을 받지 않는다. fs.readFile을 모두 찾아내서 보안기능을 추가하자.

App - 출력정보에 대한 보안

출력정보에서 발생하는 보안 이슈를 살펴보자.

XSS(Cross Site Scripting)이라는 웹 공격방법이 있다. 웹 페이지의 입력창에 아래를 입력하면,

<script>
alert('babo');
</script>

웹 브라우저가 <script> 태그 안 쪽을 JavaScript의 문법으로 해석해서 경고창을 띄우게 된다.Nodejs9-4

그래서 다른 방문자들이 보게 될 경우, 불편을 초래한다. 만약, location.herf을 사용해서 다른 사이트로 사용자를 보내버릴 수도 있고 사용자의 로그인 정보를 갈취 하는 등의 심각한 문제들을 야기한다. 이런것들을 방지하기 위해서, 사용자로부터 받은 정보를 출력시, 문제가 되는 것들을 필터링할 필요가 있다.

이러한 방법은 첫 째로, <script>로 되어있는 것을 지워버리는 강력한 방법을 사용하는 것이 있다. 둘 째로, 태그 명령어에 사용되는 <>를 웹 브라우저에 그대로 표시하는 방법이 있다. HTML에서 이 꺽쇠를 그대로 표현하는 방법은 &lt;&gt; 이 있다.

위의 방법을 사용하기 위해서, 다른 사람이 만든 모듈을 다운 받을 필요가 있다. Node.js의 패키지 매니저인 npm에서 검증받은 sanitize-html를 설치한다.

터미널에서 진행하고 있는 프로젝트로 들어가 npm을 시작한다,

npm init

이제, 해당 프로젝트를 패키지로 생성할 것이냐고 물어보는데, 기본값으로 설정한다. 프로젝트가 생성되었으면, package.json 파일이 생성된다.

이제, 설치하기 위해 아래 명령어를 입력한다.

npm install -S sanitize-html

-S 는 프로젝트에서만 사용할 작은 부픔으로서 해당 패키지를 설치한다는 뜻이다. 설치가 되면, node_modules 디렉토리가 생성되고 하위 파일중에 sanitize-html 이 설치된것을 볼 수 있다.

package.json 으로 들어가면,

Nodejs9-5

이렇게, dependencies 라는 곳에 패키지가 있는 것을 볼 수 있다. 이 곳은 앱이 해당 패키지를 사용하고 있다는 것이다. 설치한 sanitize-html 도 다른 많은 패키지를 사용한다. 그 패키지들도 다른 패키지들에 의존한다. 이렇게 복잡한 패키지들의 관계를 npm이 관리해준다.

이제 main.js에 패키지의 모듈을 적용한다. 변수 sanitizeHtml으로 모듈을 불러온다.

var sanitizeHtml = require('sanitize-html');

스크립트 태그를 살균할 곳에 적용한다.

  var sanitizedTitle = sanitizeHtml(title);
  var sanitizedDescription = sanitizeHtml(description);
  var list = template.List(filelist);
  var html = template.HTML(title, list,
              `<h2>${sanitizedTitle}</h2>${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>`

사용자가 입력한 데이터를 출력하기 전에, <script> 를 제거하기 위해서 tiltedescription을 살균 후 각각 변수명 sanitizedTitlesanitizedDescription으로 적용시킨다.

Nodejs9-6

그 결과, 텍스트를 남기고 태그만 삭제되어 출력된다.

만약, 허용하고 싶은 태그가 있다면

var sanitizedDescription = sanitizeHtml(description, {
               allowedTags:['h1']
             });

두 번째 인자에 입력한다. 위는 <h1> 태그를 허용한 경우이다.

느낀점

Node.js를 이용해서 App만들기를 마무리하였다. 공부하기전에는 웹의 편리한 기능들을 당연하게 썼는데 직접 만들어보니 정말 어려운 것이었다는 것을 몸소 느꼈다. 특히, 보안취악점은 고려할 수 없는 부분에서 발생할 수 있어서 위협적으로 느껴졌다. 막연하게 , 웹 공부를 하고나서는 나 혼자 완벽하게 만들 수 있을거라 생각했지만 나혼자서는 할 수 있는것이 굉장히 제한된다는 것을 깨달았다. 패키지와 라이브러리 그리고 다른 사람과의 협력은 필수불가결하다. 나는 슈퍼맨이 아니고 다른 사람들도 그러하니까….^^