URL방식 과 WHATWG 방식
url 방식
href | ||||||
protocol | auth | host | path | hash | ||
hostname | port | pathname | search | |||
query | ||||||
https: | user : pass | @sub.example.com | :8000 | /p/a/t/h | ?query=string | #hash |
whatwg
url방식보다 진일보 된 방식이다.
WHATWG URL의 origin 속성에는 protocol 및 host 가 포함 되지만 username 또는 password 는 포함 되지 않는다 .
href | |||||||
origin | origin | pathname | search | hash | |||
protocol | username | password | host | ||||
hostname | port | ||||||
https: | user | : pass | @sub.example.com | :8000 | /p/a/t/h | ?query=string | #hash |
const url = require('url');
//주소 문자열을 URL 객체로 만들기
const parsedUrl = url.parse('https://-------');
// url.parse(): Url {
// protocol: 'https:',
// slashes: true,
// auth: null,
// host: 'sskey.tistory.com',
// port: null,
// hostname: 'sskey.tistory.com',
// hash: null,
// search: '?type=post&returnURL=%2Fmanage%2Fposts%2F',
// query: 'type=post&returnURL=%2Fmanage%2Fposts%2F',
// pathname: '/manage/newpost/',
// path: '/manage/newpost/?type=post&returnURL=%2Fmanage%2Fposts%2F',
// href: 'https://sskey.tistory.com/manage/newpost/?type=post&returnURL=%2Fmanage%2Fposts%2F'
// }
//URL 객체를 주소 문자열로 만들기
console.log('url.format():', url.format(parsedUrl));
// url.format(): https://sskey.tistory.com/manage/newpost/?type=post&returnURL=%2Fmanage%2Fposts%2F
whatwg
const { URL } = require('url');
const myURL = new URL('https://sskey.tistory.com/manage/newpost/?type=post&returnURL=%2Fmanage%2Fposts%2F');
console.log('new URL():', myURL);
// new URL(): URL {
// href: 'https://sskey.tistory.com/manage/newpost/?type=post&returnURL=%2Fmanage%2Fposts%2F',
// origin: 'https://sskey.tistory.com',
// protocol: 'https:',
// username: '',
// password: '',
// host: 'sskey.tistory.com',
// hostname: 'sskey.tistory.com',
// port: '',
// pathname: '/manage/newpost/',
// search: '?type=post&returnURL=%2Fmanage%2Fposts%2F',
// searchParams: URLSearchParams { 'type' => 'post', 'returnURL' => '/manage/posts/' },
// hash: ''
// }
console.log('url.format():', url.format(myURL));
// url.format(): https://sskey.tistory.com/manage/newpost/?type=post&returnURL=%2Fmanage%2Fposts%2F
라우터 만들기
user로 요청했을 때 사용자 정보를 보여주고
feed로 요청했을 때 사진 목록을 보여주고
이외의 요청은 페이지가 없다는 404에러 화면을 보여줍니다.
const http = require("http"); // 객체 생성
const url = require("url");
http
.createServer((req, res) => {
const path = url.parse(req.url, true).pathname;
res.setHeader("Content-type", "text/html; charset=utf-8");
if (path === "/user") {
res.end("[user] name : andy, age: 30");
} else if (path === "/feed") {
res.end(`
<meta charset="UTF-8">
<ul>
<li>picture1</li>
<li>picture2</li>
</ul>
`);
} else {
res.statusCode = 404;
res.end("404 page not found");
}
})
.listen("3000", () => console.log("라우터를 만들어보자."));
소스 리펙토링
동작결과를 변경하지 않으면서 코드의 구조를 재조정하는 작업입니다.
가독성을 높이고 유지보수를 편하게 하는 목적으로 진행됩니다.
const http = require("http"); // 객체 생성
const url = require("url");
http
.createServer((req, res) => {
const path = url.parse(req.url, true).pathname;
res.setHeader("Content-type", "text/html; charset=utf-8");
if (path === "/user") {
user(req, res);
} else if (path === "/feed") {
user(req, res);
} else {
notFound(req, res);
}
})
.listen("3000", () => console.log("라우터를 만들어보자."));
const user = (req, res) => {
res.end(`[user] name : andy, age: 30`);
};
const feed = (req, res) => {
res.end(`<ul>
<li>picture1</li>
<li>picture2</li>
<li>picture3</li>
</ul>
`); // ➍
};
const notFound = (req, res) => {
res.statusCode = 404;
res.end("404 page not found"); // ➍
};
url.parse() 취약점 발생 원인
Node.js의 url.parse()는 WHATWG URL API가 아닌 자체적인 스펙으로 개발된 함수에요.
WHATWG URL API WHATWG는 Web Hypertext Application Technology Working Group의 약어로 국제 웹 표준화 그룹을 뜻해요. WHATWG URL API 는 국제 표준 스펙으로 URL(Uniform Resource Locator)을 다룰 수 있도록 제공되는 API입니다.
WHATWG URL API가 등장하기 전에 자체적으로 개발된 URL 파싱 함수로 보이는데요. 표준 스펙이 아니다 보니 다른 파서(parser)와 결과가 다르고, 이 때문에 예상하기 어려운 코드 흐름도 발생했어요.
url.parse()에서는 hostname을 잘못된 방식으로 파싱하는 취약점이 있었는데요. 아래 보이는 Node.js url 라이브러리의 getHostname() 함수에서 발생했어요.
/* comment
해당 취약점은 v19.1.0에서 패치되었습니다.
아래 코드는 v19.1.0 이전 버전에서 확인할 수 있습니다.
*/
function getHostname(self, rest, hostname) {
for (let i = 0; i < hostname.length; ++i) {
const code = hostname.charCodeAt(i);
const isValid = (code >= CHAR_LOWERCASE_A && code <= CHAR_LOWERCASE_Z) ||
code === CHAR_DOT ||
(code >= CHAR_UPPERCASE_A && code <= CHAR_UPPERCASE_Z) ||
(code >= CHAR_0 && code <= CHAR_9) ||
code === CHAR_HYPHEN_MINUS ||
code === CHAR_PLUS ||
code === CHAR_UNDERSCORE ||
code > 127;
// Invalid host character
if (!isValid) {
self.hostname = hostname.slice(0, i);
return `/${hostname.slice(i)}${rest}`;
}
}
return rest;
}
getHostname() 함수의 로직은 단순해요. 반복문으로 전달된 문자열의 문자를 하나씩 가져온 뒤, isvalid 조건에 맞는 값을 구하는 로직인데요. 조건에 맞지 않는 문자가 발견되면, 이전 문자까지 문자열을 slice하고 hostname으로 설정해요. 그 뒤로 오는 문자들은 모두 path로 설정하고요.
isValid 의 조건을 정규식으로 표현해보면 /[a-zA-Z0-9\.\-\+_]/u 와 같은데요(ECMAScript 기준). “hostname으로는 저 범위의 문자들만 올 수 있어!”라고 설정해둔 것이죠. hostname에 올 수 없는 문자열은 모두 path로 정의하고요.
getHostname() 함수에 디버깅 코드를 추가해보면, 실제로 isValid 조건에 해당하지 않는 문자가 반복문에 오면, hostname 파싱을 중단해요. 나머지 문자열은 앞에 / 를 붙여 path로 사용하고요.
그래서 http://EVIL_DOMAIN*.toss.im 의 hostname이 EVIL_DOMAIN 이 되어버리면서 Hostname Spoofing 취약점이 발생해요.
Hostname Spoofing
Hostname Spoofing은 시스템을 대상으로 Hostname을 속이는 해킹 기법을 말합니다.
Spoofing은 ‘속이다’라는 사전적 의미를 갖고 있으며, 시스템을 대상으로 어떠한 정보를 속이는 해킹 기법을 Spoofing이라고 합니다.
'IT > node.js' 카테고리의 다른 글
[node] Express의 장점과 미들 웨어 설치 (1) | 2024.07.23 |
---|---|
[node] 익스프레스 없이 http 요청하기 (2) | 2024.07.23 |
[node] 에러 해결법 Error: listen EADDRINUSE :::8000temp/오류 및 해결방법, 맥북에게 포트를 빼앗겼을 때 (0) | 2024.07.18 |
[node] 'error:03000086:digital envelope routines::initialization error' (0) | 2024.07.18 |