XSS와 CSRF에 대응하기
지금까지 작성한 코드는 JWT토큰을 이용해서 인증을 진행했다.
XSS는 간단하게 말하면 스크립팅 공격이라고 할 수 있는데
우리가 개발자 도구로 읽을 수 있는 내용은 다른 해커들도 다 읽을 수 있기에
토큰에 대한 정보를 가려야한다.
이를 위해 우리는 Cookie를 생각할 수 있다
쿠키는 옵션을 설정하면 브라우저에서만 읽을 수 있으므로 노출시키지 않을 수 있다.
그러면 쿠키에다가 토큰에 대한 정보를 담아서 보내고
프론트쪽은 로컬스토리지에 토큰에 대한 정보를 저장하는 게 아니라
메모리상에 보관하여 XSS공격에 대응할 수 있다.
function setToken(res, token) {
const options = {
maxAge: config.jwt.expiresInDays * 1000,
httpOnly: true,
sameSite: 'none',
secure: true,
};
res.cookie('token', token, options);
}
코드를 크게 수정할 부분은 없었고 token을 보내줄 때 res.cookie에 담아 전송했다.
그리고 쿠키에 대한 옵션들을 설정했는데
maxAge는 얼마나 보관할지를 나타내는데 단위는 ms이며 토큰의 유효기간과 같게 설정하는 게 좋다.
httpOnly옵션은 브라우저에서만 읽을 수 있게 해주는 것이고 samesite는 cors와 비슷하다고 보면 된다.
이렇게 해주면 XSS에 대한 공격은 막을 수 있는데 CSRF에 취약할 수 있다.
CSRF는 사용자모르게 어떤 요청을 보내 정보를 취하는 공격방법이다.
CSRF공격까지 막기 위해 CSRFToken을 추가로 발행하여 막을 수 있다.
사용자는 앱을 이용하기 위해 접속 시 CSRF토큰을 서버에 요청하고
이를 메모리상에 보관하여 다른 요청을 할 때 사용한다.
router.get('/csrf-token', authController.csrfToekn);
--------------router------
export async function csrfToekn(req, res, next) {
const csrfToekn = await generateCSRFToken();
res.status(200).json({ csrfToekn });
}
async function generateCSRFToken() {
return bcrypt.hash(config.csrf.plainToken, 1);
}
----------controller-----------
app.use(csrfCheck);
app.use('/tweets', tweetsRouter);
app.use('/auth', authRouter);
---------app.js--------------
export const csrfCheck = (req, res, next) => {
if (req.method == 'GET' || req.method == 'OPTIONS' || req.method == 'HEAD') {
return next();
}
const csrfHeader = req.get('dwitter-csrf-token');
if (!csrfHeader) {
console.warn('Missing required "csrf_token" header', req.headers.origin);
return res.status(403).json({ message: 'failed CSRF check' });
}
validateCsrfToken(csrfHeader)
.then((valid) => {
if (!valid) {
console.warn(
'Value provided in "csrf_token" header does not validate',
req.headers.origin,
csrfHeader
);
return res.status(403).json({ message: 'failed CSRF check' });
}
next();
})
.catch((err) => {
console.log(err);
return res.status(500).json({ message: 'something went wrong' });
});
};
async function validateCsrfToken(csrfHeader) {
return bcrypt.compare(config.csrf.plainToekn, csrfHeader);
}
---------csrf Check-------
코드를 살펴보면 csrf미들웨어를 정의하고 tweet이나 app주소로 요청하기 전에 csrf를 검사한다.
csrf Check를 보자
아무리 많은 요청이 와도 서버에 영향을 주지 않는(멱등성이 낮은?! idempotent X)요청은 next로 가볍게 넘겨주고
사용자 정의 헤더인 dwitter-csrf-token유무를 확인한다
헤더가 있으면 검증을 실시하고 아니면 403을 보낸다
이 과정을 통해 올바른 주소에서 접근했는지 확인하여 csrf를 방지할 수 있다.
클라이언트 측은 웹앱에 접속하자마자 csrf토큰부터 부여받는 로직으로 작성했지만 백엔드 위주이므로 생략하겠다.