리셋 되지 말자

path 보안 본문

NodeJS/생활코딩

path 보안

kyeongjun-dev 2020. 9. 14. 16:08

path 경로 세탁(?)

현재의 코드로는 사용자가 나의 컴퓨터(nodejs 웹서버가 실행되는 컴퓨터)를 탐색할 수 있는 발생해선 안되는 경우가 생긴다. (경로를 localhost/../../?=id 등과 같이...)

이러한 문제를 방지하기위해서 경로를 세탁해주어야 한다.

path=require('path')

path.parse('./main.js');
{ root: '', dir: '.', base: 'main.js', ext: '.js', name: 'main' }

path.parse('./main.js').base;
'main.js'

위의 코드는 nodejs에서 기본으로 제공하는 path 모듈의 parse를 이용한 예시이다. path.parse(경로)를 입력하면 해당 경로에 대한 정보를 객체로 준다. 그래서 .base와 같이 객체의 값을 사용하는 것처럼 사용할 수 있다.

 

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

원래 queryData.id 였던 부분을 위와같이 filteredId를 별도로 두어서, 경로를 세탁해준다. (queryData.id에 들어오는 경로에서 .. 이나 . 과 같은 문자열이 차단되기 때문에 더이상 ../을 사용할 수없게 된다.)

 

request.on('end', function () {
//정보를 qs 모듈로 post라는 객체로 객체화
var post = qs.parse(body); // 지금까지 저장한 body 데이터를 querystring 모듈의 parse를 사용하면 post데이터의 post 정보가 들어있다.
var title = post.id;
var filteredId = path.parse(title).base;
console.log(post.id);

fs.unlinkSync(`data/${filteredId}`, function(err){

삭제할 때도 id를 외부에서 가져오기 때문에 수정해준다.

  • 전체 소스
var http = require('http');
var fs = require('fs');
var url = require('url'); // url 모듈을 사용한다
var qs = require('querystring');
var path = require('path');
function templateFiles(filelist) {
  var list = '<ul>';
  for (var i = 0; i < filelist.length; i++) {
    list = list + `<li><a href="/?id=${filelist[i]}">${filelist[i]}</a></li>`;
  }

  list = list + '</ul>';
  return list;
}

function templateHTML(title, list, body, control) {
  return `
    <!doctype html>
    <html>
    <head>
      <title>WEB1 - ${title}</title>
      <meta charset="utf-8">
    </head>
    <body>
      <h1><a href="/">WEB2</a></h1>
      ${list}
      ${control}
      <h2>${title}</h2>
      ${body}
    </body>
    </html>    
  `;
}

// request = 요청할 때 웹브라우저가 보낸 정보, response = 응답할 때 우리가 웹브라우저에게 전송할 정보
var app = http.createServer(function (request, response) {
  var _url = request.url;
  var queryData = url.parse(request.url, true).query;
  var pathname = url.parse(_url, true).pathname;
  console.log(pathname);
  // 루트 디렉토리 (/)로부터 존재하는 페이지를 요청하면 페이지 표시
  if (pathname === '/') {
    
    if (queryData.id === undefined) { // 없는 값을 호출하려고 하면 javascript는 undefined라고 한다.(약속)
      
      fs.readFile('data/' + filteredId, 'utf8', function (err, description) {
        var title = queryData.id;
        title = 'Welcome';
        description = 'Hello, Node.js';

        fs.readdir('./data', function (error, filelist) {
          var list = templateFiles(filelist);
          var template = templateHTML(title, list,
            `<p>${description}</p>`,
            `<a href="/create">create</a>`
          );

          //response.end(fs.readFileSync(__dirname + url));
          response.writeHead(200); // 200을 전송하면, 파일을 잘 전송했다고 하는 약속
          response.end(template);
        });
      });
    } else {
      var filteredId = path.parse(queryData.id).base;
      fs.readFile('data/' + filteredId, 'utf8', function (err, description) {
        var title = queryData.id;
        fs.readdir('./data', function (error, filelist) {
          var list = templateFiles(filelist);
          var template = templateHTML(title, list,
            `<p>${description}</p>`,
            `<a href="/create">create</a> 
             <a href="/update?id=${title}">update</a>
             <form action="delete_process" method="POST">
              <input type="hidden" name="id" value=${title}>
              <input type="submit" value="delete">
             </form>
             `
          );

          //response.end(fs.readFileSync(__dirname + url));
          response.writeHead(200); // 200을 전송하면, 파일을 잘 전송했다고 하는 약속
          response.end(template);
        });
      });
    }
  } else if (pathname === '/create') {
    fs.readdir('./data', function (error, filelist) {
      //response.end(fs.readFileSync(__dirname + url));
      var title = 'WEB- create';
      var list = templateFiles(filelist);

      var template = templateHTML(title, list, `
          <form action="http://localhost:80/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.end(fs.readFileSync(__dirname + url));
      response.writeHead(200); // 200을 전송하면, 파일을 잘 전송했다고 하는 약속
      response.end(template);
    });

  } else if (pathname === '/create_process') {
    var body = '';
    //POST 방식으로 데이터를 보낼 때, 데이터가 한번에 너무 많으면, 특정한 양(조각)을 수신할 때마다 서버는 콜백 함수를 호출하도록 약속되어 있다.
    request.on('data', function (data) {
      body = body + data; // 콜백이 실행될 때마다 데이터를 추가
      if (body.length > 1e6) request.connection.destroy(); // 데이터가 너~무 많으면 연결을 강제로 종료
    });


    //Data가 조각 조각 들어오다가 더이상 데이터가 않오면 이게 실행되고, 콜백 함수가 실행됨
    request.on('end', function () {
      //정보를 qs 모듈로 post라는 객체로 객체화
      var post = qs.parse(body); // 지금까지 저장한 body 데이터를 querystring 모듈의 parse를 사용하면 post데이터의 post 정보가 들어있다.
      var title = post.title;
      var description = post.description;

      // data 디렉토리에 title이름으로 된 description 내용의 파일 생성
      fs.writeFile(`data/${title}`, description, 'utf8', function (err) {
        // writehead의 200은 성공했다는 뜻, 302는 페이지를 다른곳으로 redirection하라는 뜻
        response.writeHead(302, { Location: `/?id=${title}` });
        response.end();
      });
    });

  } else if (pathname === '/update') { // 원래 글의 내용을 가져오고
    var filteredId = path.parse(queryData.id).base;
    fs.readFile('data/' + filteredId, 'utf8', function (err, description) {
      var title = queryData.id;
      fs.readdir('./data', function (error, filelist) {
        var list = templateFiles(filelist);
        var template = templateHTML(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">${description}</textarea>
            </p>
            <p>
              <input type="submit">
            </p>
          </form>
          `,
          `<a href="/create">create</a> <a href="/update?id=${title}">update</a>`
        );

        //response.end(fs.readFileSync(__dirname + url));
        response.writeHead(200); // 200을 전송하면, 파일을 잘 전송했다고 하는 약속
        response.end(template);
      });
    });
  } else if(pathname === '/update_process'){
    var body = '';
    //POST 방식으로 데이터를 보낼 때, 데이터가 한번에 너무 많으면, 특정한 양(조각)을 수신할 때마다 서버는 콜백 함수를 호출하도록 약속되어 있다.
    request.on('data', function (data) {
      body = body + data; // 콜백이 실행될 때마다 데이터를 추가
      if (body.length > 1e6) request.connection.destroy(); // 데이터가 너~무 많으면 연결을 강제로 종료
    });

    //Data가 조각 조각 들어오다가 더이상 데이터가 않오면 이게 실행되고, 콜백 함수가 실행됨
    request.on('end', function () {
      //정보를 qs 모듈로 post라는 객체로 객체화
      var post = qs.parse(body); // 지금까지 저장한 body 데이터를 querystring 모듈의 parse를 사용하면 post데이터의 post 정보가 들어있다.
      var title = post.title;
      var postId = post.id;
      var description = post.description;

      console.log(post);

      fs.rename(`data/${postId}`, `data/${title}`, (err)=>{
        fs.writeFile(`data/${title}`, description, 'utf8', function (err) {
          // writehead의 200은 성공했다는 뜻, 302는 페이지를 다른곳으로 redirection하라는 뜻
          response.writeHead(302, { Location: `/?id=${title}` });
          response.end();  
        });  
        
        console.log('rename completed!!');
        
      });
      

      // // data 디렉토리에 title이름으로 된 description 내용의 파일 생성
      // fs.writeFile(`data/${title}`, description, 'utf8', function (err) {
      //   // writehead의 200은 성공했다는 뜻, 302는 페이지를 다른곳으로 redirection하라는 뜻
      //   response.writeHead(302, { Location: `/?id=${title}` });
      //   response.end();
      // });
    });
  } else if(pathname==='/delete_process'){
    var body = '';
    //POST 방식으로 데이터를 보낼 때, 데이터가 한번에 너무 많으면, 특정한 양(조각)을 수신할 때마다 서버는 콜백 함수를 호출하도록 약속되어 있다.
    request.on('data', function (data) {
      body = body + data; // 콜백이 실행될 때마다 데이터를 추가
      if (body.length > 1e6) request.connection.destroy(); // 데이터가 너~무 많으면 연결을 강제로 종료
    });

    //Data가 조각 조각 들어오다가 더이상 데이터가 않오면 이게 실행되고, 콜백 함수가 실행됨
    request.on('end', function () {
      //정보를 qs 모듈로 post라는 객체로 객체화
      var post = qs.parse(body); // 지금까지 저장한 body 데이터를 querystring 모듈의 parse를 사용하면 post데이터의 post 정보가 들어있다.
      var title = post.id;
      var filteredId = path.parse(title).base;
      console.log(post.id);

      fs.unlinkSync(`data/${filteredId}`, function(err){
        
      });
      response.writeHead(302, {Location: `/`});
      response.end();
    });
  }
  else { // 없는 페이지를 요청하면 404 에러
    response.writeHead(404);
    response.end('Not found');
  }
});
app.listen(80);

'NodeJS > 생활코딩' 카테고리의 다른 글

[NodeJS] SQL Injection  (2) 2020.09.19
[NodeJS] 출력에 대한 보안  (0) 2020.09.14
글 삭제-삭제 기능 완성  (0) 2020.09.11
글 삭제-삭제 버튼 생성  (0) 2020.09.11
글수정-수정된 내용 저장  (0) 2020.09.11
Comments