세리프 따라잡기
nodejs의 보안(입·출력)과 API에 대해, 끝으로…. 본문
1. 입력 정보에 대한 보안
나중에 데이터베이스를 통해 더 자세히 데이터를 관리할텐데, DB는 id와 password가 있어야 데이터를 가져올 수 있다. 예를 들어 main 폴더에 password.js 파일을 생성해 아래와 같은 정보를 입력했다고 하자.
module.exports = {
id: 'gothic',
password: '1234'
}
이 정보에 보안을 해두지 않았다면, 우리는 쿼리데이터를 사용하는 '?id=' 부분에 상위 폴더로 가는 키워드를 입력하여 패스워드를 볼 수 있다.
이 문제를 해결하기 위해 nodejs.org/api/path.html#path_path_parse_path 참고해, 콘솔창에 입력해 분석해보자.
여기서 기본(base)은 password.js이기 때문에 ../password를 입력해도 경로를 탐색할 수 있는 정보를 세탁시킬 수 있다.
- 오염된 정보가 들어올 때 생길 문제에 대한 코드들 수정
var http = require('http');
var fs = require('fs');
var url = require('url');
var qs = require('querystring');
var template = require('./lib/template.js');
var path = require('path'); //사용자로부터 경로가 들어온 모든 곳의 내용을 바꿔줘야 한다.
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 (err, 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 (err, filelist) {
var fillterdid = path.parse(querydata.id).base; //오염된 정보를 쳐내는 것을 fillter라고 한다. + path.parse(경로 정보) 문법 활용.
fs.readFile(`data/${fillterdid}`, 'utf8', function (err, description) { //외부에서 들어온 정보 부분 + 경로 정보를 fillterdid로 바꾼다. 정상 처리가 된다면 undefined가 뜬다. 그러면 이제 readfile 부분을 다 수정해준다.
var title = querydata.id;
var list = template.list(filelist);
var html = template.html(title, list,
`<h2>${title}</h2>${description}`,
`<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.writeHead(200);
response.end(html);
});
});
}
} else if (pathname === '/create') {
fs.readdir('./data', function (err, 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 += 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 (err, filelist) {
var fillterdid = path.parse(querydata.id).base; // 코드 추가
fs.readFile(`data/${fillterdid}`, '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 += data;
});
request.on('end', function () {
var post = qs.parse(body);
var id = post.id;
var title = post.title;
var description = post.description;
fs.rename(`data/${id}`, `data/${title}`, function (err) {
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 += data;
});
request.on('end', function () {
var post = qs.parse(body);
var id = post.id;
var fillterdid = path.parse(id).base; // 추가
fs.unlink(`data/${fillterdid}`, function (err) { // 수정
response.writeHead(302, { location: `/` });
response.end();
})
});
} else {
response.writeHead(404);
response.end('not found');
};
});
app.listen(3000);
2. 출력 정보에 대한 보안
우리가 만든 웹페이지에 누군가가 들어가 create 버튼을 눌러 아래와 같은 html코드를 작성했다고 생각해보자!
가정1)
// title: XSS
// description:
<script>
alert('merong')
</script>
script는 html 태그인데 이 안쪽에 있는 콘텐츠는 자바 스크립트가 오도록 한다. (웹 브라우저에서)
alert = 경고창 기능을 실행시킬 때는 이 코드를 쓴다.
- 이 정도는 귀여운 편..
가정2)
<script>
location.href='https://malgun-gothic.tistory.com/';
</script>
location.href 는 뒤에 적힌 주소로 상대방을 보내는 역할을 한다.
- 이렇게 되면 큰 문제가 발생할 수 있다 (사용자 정보 해킹 등)
때문에 사용자로부터 입력을 받은 정보를 바깥으로 출력할 때는 정보에서 문제가 발생할 수 있는 것들을 필터링하는 작업을 해야 한다.
해결1)
들어온 데이터를 그냥 다 삭제한다.
해결2)
www.w3schools.com/html/html_entities.asp 참고. '가정2'의 코드들을 있는 그대로 표시하게끔(보이는 건 가정2 처럼 보임) 바꾼다.
<script>
location.href='https://malgun-gothic.tistory.com/';
</script>
하지만 위의 해결 2가지는 수동적으로 해결해야하기 때문에 번거롭다.
해결3)
www.npmjs.com/package/sanitize-html 이용 및 참고.
[ npm init 이용 > 내 애플리케이션을 npm으로 관리하기 위한 절차 실행 > 완료되면 package.json 파일이 생성된다. > 콘솔창에 npm install -S sanitize-html을 입력하여 설치. 이때 -S는 우리가 진행하는 프로젝트에서만 사용할 작은 조각(부품)으로 설치함. > 완료되면 node_modules 파일이 생성된다. ] 아래는 사용하여 수정한 코드.
var http = require('http');
var fs = require('fs');
var url = require('url');
var qs = require('querystring');
var template = require('./lib/template.js');
var path = require('path');
var sanitizehtml = require('sanitize-html'); // 불러오기.
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 (err, 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 (err, filelist) {
var fillterdid = path.parse(querydata.id).base;
fs.readFile(`data/${fillterdid}`, 'utf8', function (err, description) {
var title = querydata.id;
var sanitizedtitle = sanitizehtml(title); //별도의 변수를 쓴 이유는 자신이 사용하는 변수가 살균이 되었는지를 이름을 통해서 알 수 있어서.
var sanitizeddescription = sanitizehtml(description, {
allowedTags:['h1']
}); //2번째 인자로 객체를 줘서 허용하고 싶은 태그들을 쓰면, 살균되지 않고 사용할 수 있게 해준다.
var list = template.list(filelist);
var html = template.html(sanitizedtitle, 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>`); // 이 코드들에 있는 title과 description 부분을 sanitizedtitle, description로 수정.
response.writeHead(200);
response.end(html);
}); //실행 결과: <>안에 쓴 태그들이 살균으로 인해 안 보인다. sanitize-html은 script 같은 민감한 태그가 있으면 해당 코드 부분을 살균해버린다. 민감한 태그가 아니라면 태그만 없애고 해당 내용은 살려준다. 만약 없애지 않고 사용하고 싶다면 위에 허용 코드(allowedTags)를 추가하면 된다.
});
}
} // 이 아래부터는 앞선 1번 코드와 동일.
3. API(Application Programming Interface)와 Create Server
API: 약속된 조작 장치(interface)를 실행시킴으로서 애플리케이션을 만들 수 있게(application programming) 된다. 어떤 프로그래밍 언어를 만나도 api를 찾거나 검색을 통해 문제를 해결할 수 있게 한다.
nodejs.org/ko/docs/ (~14.15.~은 각 버전 별로 어떠한 기능을 마련하고 있는지를 보여주는 문서라는 뜻)
// 문법: http.createServer([options][, requestListener]) 여기서 []는 인자는 있을 수도 있고 없을 수도 있다는 의미. → requestListener = 함수(function)이다. 때문에 우리가 첫번째 인자로 긴 함수를 적어준 것이다. + app이라는 변수 안에는 http.server라는 객체가 담겨 있다.
var app = http.createServer(function (request, response) { //createserver를 이용해 웹서버를 만든 것. + 웹서버로 외부에서 요청이 들어올 때마다, 웹 서버는 첫번째 인자에 해당하는 함수를 호출하고 그 함수의 1번째 parameter로 웹 브라우저로 부터 들어온 요청에 대한 여러가지 정보를 담고 있는 객체인 request라고 하는 인자로, 2번째는 함수 안의 구현을 통해 사용자에게 전송하고 싶은 정보를 response를 통해 응답할 수 있도록 하는 것.
~ code ~
app.listen(3000); //문법: server.listen([port[, host[, backlog]]][, callback]) = 3000번 포트에 들어오면 우리가 만든 앱이 응답하여 동작하게 되는 것.
Nodejs study를 끝으로 함께 배우면 좋을 것들 memo
-javascript
-database
-mongoDB
-mySQL
-framework
-module(npm)
-api
node.js awesome = 북마크 같은 것. 검색을 통해 좋은 정보를 알아보자.
끝~~~~~~~~~~~👩🔧
'Node.js' 카테고리의 다른 글
nodejs-express 미들웨어(body-parser, compression) 설치 및 사용 방법 정리, 만들기, 실행순서에 대해 (0) | 2021.01.18 |
---|---|
nodejs-express 설치, helloworld 출력 및 코드 의미, 홈·상세 페이지 구현, (0) | 2021.01.12 |
nodejs 객체와 모듈에 대해 (0) | 2021.01.10 |
nodejs app제작(전송 데이터 받기), 파일생성과 리다이렉션, 글 수정 및 삭제 버튼 구현 (0) | 2021.01.07 |
nodejs에서 동기와 비동기, callback, 패키지 매니저(pm2), html form, 글쓰기 ui 생성 (0) | 2021.01.06 |