2023. 6. 2. 15:50ㆍInternational Sign Lang 프로젝트/백엔드
위 글을 읽고 와주시면 감사하겠습니다!
1. 오늘의 주제
오늘의 주제는 바로 기존의 코드에 보안 이슈를 해결하는 것입니다!
먼저 저번까지 완료된 /countries API의 코드를 보겠습니다.
const express = require("express")
const mysql = require("mysql2")
const fs = require("fs")
const router = express.Router()
const [host, user, password, database] = fs.readFileSync("database_config.txt", "utf8").split("\n")
const db = mysql.createConnection({
host,
user,
password,
database,
})
router.post("", function (req, res) {
const from_country = req.body.from_country
const to_country = req.body.to_country
if (from_country.length !== 2 || to_country.length !== 2) {
return res.status(400).json({
error: "Invalid country code",
})
}
const query = "INSERT INTO isl (from_country, to_country) VALUES (?, ?)"
db.query(query, [from_country, to_country], function (err, result) {
if (err) {
console.log(err)
return res.status(500).json({
error: "Database error",
})
}
res.json({
message: "Data Received",
from_country: from_country,
to_country: to_country,
})
})
})
module.exports = router
간단하게 FrontEnd 에서 from_country, to_country를 받아
DB 에다가 나라 클릭에 대한 결과를 넣는 API 입니다.
하지만 위 코드는 보안에 매우 허술한 코드입니다!
대표적으로 CSRF 공격, HttpOnly 쿠키, CSP 등의 보안 문제가 있죠!
이 문제들을 해결하지 않는 다면... 추후 제 사이트를 오픈했을 때
악성 공격에 대해 굉장히 무력한 모습을 보일 것입니다...ㅠ...
그렇기에 위 문제들에 대한 대비를 코드로 구현해 보도록 하겠습니다!
2. 주제에 대한 간략한 설명
이 글에서는 CSRF, HttpOnly, CSP에 대해 간단하게만 설명하고
위 사항들에 대해 구체적인 코드와 함께 다른 글로 찾아뵙도록 하겠습니다!
CSRF
[해석]
[출처] :
HttpOnly
[해석]
[출처] :
CSP
[해석]
[출처] :
3. 코드로 실제로 구현해 보기
언제 Token을 발행해야 할까?
먼저 ISL 프로젝트의 특징 중 하나를 알아야 하는 게
ISL 은 현재 회원이라는 개념이 존재하지 않습니다.
보통은 CSRF 토큰은 로그인/회원가입 하면서 동시에 발행을 하는 데
저의 경우에는 로그인/회원가입을 하는 경우가 존재하지 않습니다.
그래서 언제 CSRF 토큰을 발행하냐에 대한 고민을 했어야 했습니다.
생각한 타이밍은 2 가지입니다.
- 프론트엔드에서 첫 POST 때 브라우저에 CSRF 토큰을 불러오고, 그 후에는 계속 토큰을 담아 보내기
- 웹페이지에 처음 접속했을 때, 프론트엔드에서 백엔드로 CSRF 토큰 요청하기
2 가지 중 2 번째 방 안으로 택하게 되었습니다.
ISL 특성상 사용자는 한 번만 클릭하기보단
여러 번을 클릭하게 될 것입니다.
그렇기에 처음 법규를 날리기(/countries, POST)를 했을 때
토큰을 불러오느라 웹페이지가 느리다는 경험을 줄 수 있습니다.
그래서 2번으로 택하게 되었습니다.
그래서 어떻게?
예전의 글들처럼, 일일이 CSRF 공격에 대해 대응하기 위해
순수하게 JS 코드로 구현을 할 수 있지만 이 또한 수많은 에러와 보안, 예외상황을
고려해야 하기 때문에 손수 짜는 건 공수가 오래 듭니다!
그래서 이러한 각 언어, 프레임워크 별로 주로 사용하는 라이브러리가 존재하고
Node.js 같은 경우 이를 위해 csurf라는 라이브러리를 사용합니다!
(csurf 로고)
(csurf 설명 인용)
이렇게 csurf를 적용하기 위해 app.js(서버 설정)의 코드를 수정하고,
'/csrf-token'라는 CSRF 토큰 발행을 위한 API를 생성했습니다.
파일의 위치는 /routes/csrfToken.js입니다!
// csrfToken.js
var express = require("express")
var router = express.Router()
router.get("/", function (req, res, next) {
res.json({
csrfToken: req.csrfToken(),
})
})
module.exports = router
csurf의 특징으로는 HttpOnly에 대한 걱정을 동시에 해결한다는 것입니다!
res.cookie('your_cookie_name', 'your_cookie_value', { httpOnly: true });
이런 식으로 별도로 response의 쿠키에 대한 설정을 해줘야 하지만
알아서 해주게 됩니다!
그리고 CSP 문제를 해결하기 위해 helmet이라는 라이브러리를 설치하고
이를 또 서버 설정을 해야 하기 때문에 app.js를 수정해야 합니다!
// app.js
var express = require("express")
var path = require("path")
var cookieParser = require("cookie-parser")
var logger = require("morgan")
var csrf = require("csurf")
var helmet = require("helmet")
var countriesRouter = require("./routes/countries")
var csrfTokenRouter = require("./routes/csrfToken")
var app = express()
app.use(logger("dev"))
app.use(express.json())
app.use(express.urlencoded({ extended: false }))
app.use(cookieParser())
app.use(helmet())
app.use(
helmet.contentSecurityPolicy({
directives: {
defaultSrc: ["'self'"],
scriptSrc: ["'self'", "'unsafe-inline'", "'unsafe-eval'"],
objectSrc: ["'none'"],
upgradeInsecureRequests: [],
},
})
)
// Middleware
app.use(csrf({ cookie: true }))
// routers
app.use("/countries", countriesRouter)
app.use("/csrf-token", csrfTokenRouter)
// catch 404 and forward to error handler
app.use(function (req, res, next) {
res.status(404).json({ error: "Not Found" })
})
// error handler
app.use(function (err, req, res, next) {
// set locals, only providing error in development
var message = err.message
var error = req.app.get("env") === "development" ? err : {}
// return the error message as JSON
res.status(err.status || 500)
res.json({ error: message, details: error })
})
module.exports = app
TIL
하나의 API 에도 정말 여러 보안 이슈가 생길 수 있다는 것과,
언어와 프레임워크 별로 이를 다루는 라이브러리들에 대해서도 알게 되었습니다.
지인 중에 Spring Boot + Spring Security로 오늘 해결한 문제를 간단하게 해결하신 분과
얘기하다 보니, 제가 한 것보다 세팅을 훨씬 쉽게 하셨더라고요.
'International Sign Lang 프로젝트 > 백엔드' 카테고리의 다른 글
Day 12-2 : 솔직히 본인 테이블에 더미 데이터 1,000 개 넣어본 사람? 없으면 보자, 프로시저로 더미 데이터 생성하기 (0) | 2023.06.08 |
---|---|
Day 12-1 CSRF 와 CORS 는 매우 연관이 깊다?! (0) | 2023.06.08 |
Day 9 : 에러나면 손모가지 날라가붕께 (2) | 2023.05.30 |
Day 8 : 가상 DOM 생성, 웹 스크래핑, 및 JSON 데이터 MySQL 저장 (후 많다 많아) (0) | 2023.05.29 |
Day 7 : 왜 굳이 웹 서버 프레임워크를 사용해야 하나? (0) | 2023.05.25 |