2025. 2. 15. 19:20ㆍ프로젝트
2. 입력값 검증 및 웹 보안 (필수)
- XSS(크로스사이트 스크립트) 방지:
- React에서 사용자 입력값 escape 처리
- dangerouslySetInnerHTML 사용 금지
- SQL Injection 방지:
- ORM 사용 또는 Prepared Statement 적용
- 모든 입력값 sanitize 처리
- CSRF(크로스사이트 요청 위조) 방지:
- CSRF Token 사용 및 SameSite 속성 설정
- API는 Referer 및 Origin 검증
- 파일 업로드 보안:
- 업로드 확장자 제한 (예: .exe, .php 금지)
- 파일은 외부 스토리지(AWS S3 등) 사용 및 서버 외부에 저장
1. XSS란
공격자가 악성 JavaScript 코드를 삽입해 사용자 브라우저에서 세션 쿠키 탈취, 피싱, 악성 행동 수행하는 공격
댓글, 게시판, 피드백 등 입력받는 모든 필드가 위험
XSS 방지 방법
1. 입력값 필터링 (입력 시 검사)
<script> 태그 등 위험한 문자 제거 및 이스케이프 처리
모든 입력값은 서버에서 정규표현식 및 express-validator로 필터링
2. 출력 시 이스케이프 처리 (출력 시 검사)
React, HTML 출력 시 HTML 인코딩 적용 (<, >, " 등)
2. SQL Injection이란
입력값을 조작해 데이터베이스에 직접 명령을 주입하는 공격
예: id=1 OR 1=1 → 모든 데이터 유출
가장 치명적이며 데이터 유출 위험이 큰 공격
SQL Injection 방지 방법
1. Prepared Statement(파라미터 바인딩) 사용
쿼리와 데이터 구분 → SQL 주입 차단
2. ORM 사용 (Sequelize, Prisma)
직접 쿼리 작성 대신 ORM 사용 시 주입 공격 예방
3. CSRF란
사용자의 인증된 세션을 도용해 강제 요청을 보내는 공격
특히, 시스템 삭제, 문항 삭제, 피드백 작성 등의 POST 요청이 위험
CSRF 방지 방법
1. csrf 미들웨어 사용 (서버 측 토큰 검증)
2. SameSite 쿠키 적용 (세션 보안에서 이미 적용했음)
3. Referer 및 Origin 헤더 검증 (권장)
4. 유효성 검사(Validation)의 중요성
서버 과부하 및 악성 데이터 삽입 방지
데이터 정합성 보장 (문항 응답, 이메일, 비밀번호 등)
같은 보안 항목(CSRF, XSS)인데 세션 보안에서 했는데 또 해야 할까?
-> 각각은 목적과 방어 범위가 다르기 때문에 모두 필요합니다.
하지만 세션 보안에서 이미 일부 보호가 되어 있다면 중복을 피하고, 부족한 부분만 추가하면 됩니다.
구체적인 차이점 정리
보안 항목세션 보안에서 적용한 것웹 보안에서 추가해야 할 것
보안 항목 | 세션 보안에서 적용한 것 | 웹 보안에서 추가해야 할 것 |
XSS (크로스사이트 스크립트) | httpOnly, secure 쿠키 설정으로 세션 쿠키 탈취 차단 (세션 보호) | 입력값 필터링(express-validator), 출력 시 이스케이프(DOMPurify), 보안 헤더(helmet) (웹 자체 보호) |
CSRF (크로스사이트 요청 위조) | sameSite: strict 또는 httpOnly로 세션 쿠키 보호 (기본 방어) | csrf 토큰(csurf), Referer 검증으로 중복 요청 및 자동 요청 방지 (추가 보호) |
보안 헤더 강화 | X (세션 설정만으로는 보안 헤더 부족) | helmet으로 브라우저 보안 설정 강화(XSS, Clickjacking 등) |
왜 세션 보안 외에 추가 보안이 필요한가?
XSS는 세션만 보호해도 충분하지 않다:
- httpOnly 쿠키 설정은 **세션 쿠키 탈취(XSS)**는 막지만,
- 댓글, 자가진단 의견 등 입력 필드에 악성 스크립트 삽입은 여전히 가능함.
그래서 express-validator, helmet, DOMPurify로 입력 및 출력 자체를 보호해야 함.
CSRF는 세션 보안만으로 완벽하지 않다:
- sameSite: strict은 대부분 자동 요청 방어에 좋지만,
- SameSite 정책을 우회하거나, 소셜 로그인 등의 크로스사이트 기능에는 취약할 수 있음.
그래서 csrf 토큰(csurf) 및 Referer 검증으로 완벽한 CSRF 보호가 필요함.
보안 헤더(helmet)는 세션 설정만으로 불가능:
- X-Frame-Options(클릭재킹 방지), X-Content-Type-Options(파일 스니핑 방지) 등은
세션 설정과 무관하며, 서버 응답 헤더를 추가해야만 보안이 강화됨.
helmet은 필수입니다.
결론
보안 항목 | 세션 보안 설정 시도 | 추가 보안 필요 여부 |
XSS | httpOnly 쿠키 | 예: express-validator, helmet, DOMPurify |
CSRF | sameSite: strict | 예: csurf, Referer 검증 |
보안 헤더 강화 | (세션으로 불가능) | 예: helmet |
5. 파일 업로드 보안 (기본) – 보안 강화 방법
파일 업로드 기능은 악성 코드 실행, 서버 디렉토리 접근, 과도한 용량 소비 등 보안 위협이 많습니다.
따라서 파일 확장자 제한, MIME 타입 검사, 파일 크기 제한, 외부 스토리지(AWS S3) 사용 등의 보안 조치가 필요합니다.
app.js
app.use("/uploads", express.static(path.join(__dirname, "uploads")));
app.post("/upload", upload.single("image"), (req, res) => {
if (!req.file) {
console.log("❌ 파일이 없습니다.");
return res.status(400).json({ error: "파일이 없습니다." });
}
console.log("✅ 파일 업로드 성공:", req.file.path);
const imageUrl = `${
process.env.SERVER_URL || "http://localhost:3000"
}/uploads/${req.file.filename}`;
res.json({ url: imageUrl }); // ✅ 클라이언트에 이미지 URL 반환
});
upload.js
import multer from "multer";
import path from "path";
import fs from "fs";
const uploadDir = "uploads/";
if (!fs.existsSync(uploadDir)) {
fs.mkdirSync(uploadDir, { recursive: true });
}
const fileFilter = (req, file, cb) => {
const allowedTypes = ["image/jpeg", "image/png"];
if (allowedTypes.includes(file.mimetype)) {
cb(null, true);
} else {
cb(new Error("지원하지 않는 파일 형식입니다."), false);
}
};
const storage = multer.diskStorage({
destination: (req, file, cb) => {
cb(null, uploadDir);
},
filename: (req, file, cb) => {
cb(null, Date.now() + path.extname(file.originalname));
},
});
const upload = multer({
storage,
limits: { fileSize: 5 * 1024 * 1024 },
fileFilter,
});
export default upload;
현재 적용된 보안 항목 (완료됨)
✅ 파일 확장자 제한 | 완료 | .jpeg, .png 이미지 파일만 허용 |
✅ 파일 크기 제한 | 완료 | 5MB 이하만 업로드 가능 (limits: { fileSize: 5 * 1024 * 1024 }) |
✅ 파일 저장 경로 생성 | 완료 | uploads/ 디렉토리가 없으면 자동 생성 |
✅ 파일명 랜덤화 (부분 적용) | 부분 완료 | Date.now()로 파일명을 변경 (UUID 적용 필요) |
✅ 정적 파일 제공 (app.use("/uploads")) | 완료 | 업로드된 파일을 /uploads 경로에서 접근 가능 |
아직 적용되지 않은 보안 항목 (추가 필요)
🟡 MIME 타입 검사 | 추가 필요 | file.mimetype만 검사 → 확장자 속이기 가능 |
🟡 파일명 완전 랜덤화 (UUID 적용) | 추가 필요 | Date.now() 대신 UUID 사용 |
추가 보안 적용 방법
✅ 1. MIME 타입 추가 검사 (보안 강화)
현재 file.mimetype만 확인하고 있는데, 확장자를 속여 우회할 수 있음 → MIME 검사 강화 필요
import mime from "mime-types";
const fileFilter = (req, file, cb) => {
const allowedTypes = ["image/jpeg", "image/png"];
const mimetype = mime.lookup(file.originalname); // 실제 MIME 타입 검사
if (allowedTypes.includes(file.mimetype) && mimetype === file.mimetype) {
cb(null, true);
} else {
cb(new Error("지원하지 않는 파일 형식입니다."), false);
}
};
2. 파일명 완전 랜덤화 (UUID 적용)
현재는 Date.now()로 저장 → 보안 강화를 위해 UUID 사용 추천
import { v4 as uuidv4 } from "uuid";
const storage = multer.diskStorage({
destination: (req, file, cb) => {
cb(null, uploadDir);
},
filename: (req, file, cb) => {
const ext = path.extname(file.originalname);
cb(null, uuidv4() + ext); // UUID 기반 파일명 랜덤화
},
});
6. 로그 및 모니터링 – API 요청 기록 및 보안 이벤트 로깅
📌 목표
✅ API 요청 및 응답 기록
✅ 로그인 실패, CSRF 차단, XSS 시도 등 보안 이벤트 로깅
✅ 관리자 알림 시스템 적용 (이상 징후 감지 시 이메일/SMS 알림)
Winston 로그 시스템 적용
로그를 남기기 위해 winston 및 winston-daily-rotate-file을 사용합니다.
npm install winston winston-daily-rotate-file
로그 설정 config/logger.js
import winston from "winston";
import DailyRotateFile from "winston-daily-rotate-file";
import path from "path";
const logDir = "logs";
// ✅ 로그 파일 회전 설정 (하루 단위 저장)
const transport = new DailyRotateFile({
filename: path.join(logDir, "server-%DATE%.log"),
datePattern: "YYYY-MM-DD",
maxSize: "20m",
maxFiles: "14d", // 14일 동안 로그 유지
});
// ✅ 로거 설정
const logger = winston.createLogger({
level: "info",
format: winston.format.combine(
winston.format.timestamp(),
winston.format.json()
),
transports: [
transport,
new winston.transports.Console({ format: winston.format.simple() }),
],
});
export default logger;
모든 API 요청 로깅 (미들웨어 적용)
이제 모든 API 요청을 logger.js에 기록합니다.
middlewares/logger.js 생성
import logger from "../config/logger.js";
// ✅ API 요청 로깅 미들웨어
export const requestLogger = (req, res, next) => {
logger.info({
method: req.method,
url: req.originalUrl,
ip: req.ip,
userAgent: req.headers["user-agent"],
});
next();
};
서버에 적용 (app.js)
import { requestLogger } from "./middlewares/logger.js";
app.use(requestLogger);
로그인 실패, CSRF 차단 로그 남기기
로그인 실패 및 보안 이벤트를 별도로 기록합니다.
import logger from "../config/logger.js";
app.post("/login", (req, res) => {
const { email, password } = req.body;
const user = authenticateUser(email, password);
if (!user) {
logger.warn(`로그인 실패 - 이메일: ${email}, IP: ${req.ip}`);
return res.status(401).json({ message: "이메일 또는 비밀번호가 잘못되었습니다." });
}
req.session.user = { id: user.id, email: user.email, role: user.role };
logger.info(`로그인 성공 - 이메일: ${email}, IP: ${req.ip}`);
res.status(200).json({ message: "로그인 성공" });
});
관리자에게 보안 이벤트 알림 (이메일/SMS 전송)
로그인 실패 5회 이상 시 관리자에게 이메일 전송
import nodemailer from "nodemailer";
import dotenv from "dotenv";
dotenv.config();
// ✅ 이메일 전송 설정
const transporter = nodemailer.createTransport({
service: "gmail",
auth: {
user: process.env.ADMIN_EMAIL,
pass: process.env.ADMIN_EMAIL_PASSWORD,
},
});
// ✅ 보안 이벤트 감지 시 이메일 전송
const sendSecurityAlert = async (message) => {
await transporter.sendMail({
from: `"보안 경고" <${process.env.ADMIN_EMAIL}>`,
to: process.env.ADMIN_EMAIL,
subject: "🚨 보안 경고: 로그인 시도 감지",
text: message,
});
};
export { sendSecurityAlert };
로그인 실패 시 이메일 경고 추가 (routes/auth.js)
if (failedLoginAttempts[email] >= 5) {
await sendSecurityAlert(`경고: ${email} 계정에서 로그인 실패가 반복되었습니다.`);
}
7. API Rate Limit – 전체 API 속도 제한 적용
📌 목표
✅ express-rate-limit을 사용해 API 요청 횟수 제한
✅ 브루트 포스 및 DDoS 공격 방어
npm install express-rate-limit
미들웨어 설정 (middlewares/rateLimit.js)
import rateLimit from "express-rate-limit";
// ✅ 기본 API 속도 제한 (1분에 최대 100회)
export const apiLimiter = rateLimit({
windowMs: 1 * 60 * 1000, // 1분
max: 100, // 최대 100회 요청 가능
message: "요청이 너무 많습니다. 나중에 다시 시도하세요.",
});
// ✅ 로그인 속도 제한 (5분간 최대 5회)
export const loginLimiter = rateLimit({
windowMs: 5 * 60 * 1000, // 5분
max: 5, // 5회 이상 로그인 시도 금지
message: "로그인 시도가 너무 많습니다. 5분 후 다시 시도하세요.",
});
서버에 적용 (app.js)
import { apiLimiter, loginLimiter } from "./middlewares/rateLimit.js";
app.use("/api", apiLimiter);
app.post("/login", loginLimiter, loginRouteHandler);
8. CORS 보안 강화
📌 목표:
✅ 허용된 오리진(CLIENT_URL)만 접근 가능하도록 설정
✅ 인증 요청 시 credentials: true 적용
미들웨어 설정 (app.js)
import cors from "cors";
app.use(
cors({
origin: process.env.CLIENT_URL || "http://localhost:5173",
methods: ["GET", "POST", "PUT", "DELETE"],
allowedHeaders: ["Content-Type", "Authorization"],
credentials: true,
})
);
'프로젝트' 카테고리의 다른 글
개인정보-컴플라이언스-웹애플리케이션 (3. 데이터베이스 및 개인정보 보호) (0) | 2025.02.15 |
---|---|
개인정보-컴플라이언스-웹애플리케이션 (1. 인증 및 세션 보안) (0) | 2025.02.14 |
개인정보-컴플라이언스-웹애플리케이션 (수정사항 7) (0) | 2025.02.02 |
개인정보-컴플라이언스-웹애플리케이션 (수정사항 6) (0) | 2025.01.29 |
개인정보-컴플라이언스-웹애플리케이션 (수정사항 5) (0) | 2025.01.29 |