토이 프로젝트 - Vue, Firebase로 서버리스 PWA 개발

15. PWA myBlog 블로그 글보기 보안 sanitize-html - XSS, SQL Injection, CORS, HTTPS

그랜파 개발자 2025. 2. 16. 05:00

글보기 보안

블로그 글보기 기능을 구현할 때에는 게시글을 안전하게 표시하려면 보안 조치가 필요합니다.

1. XSS(크로스 사이트 스크립팅) 공격 방지

문제점

사용자가 <script>alert('해킹됨!')</script> 같은 악성 스크립트를 입력하면, 브라우저에서 그대로 실행될 수 있음.
-> 방문자의 개인정보 탈취, 피싱, 악성 코드 실행 등의 보안 문제 발생

해결 방법

  • sanitize-html 같은 HTML 필터링 라이브러리 사용
  • Vue의 v-html을 직접 사용하지 않거나, 반드시 필터링 후 사용
import sanitizeHtml from "sanitize-html";

const safeHtml = sanitizeHtml(userInput, {
  allowedTags: ["b", "i", "strong", "a", "p", "ul", "li", "h1", "h2"],
  allowedAttributes: { a: ["href", "title"], img: ["src", "alt"] }
});
  • 주의: v-html을 사용할 경우, 반드시 sanitize-html로 필터링해야 함.
<template>
  <div v-html="safeHtml"></div> <!-- v-html 사용 시 보안 강화 필수 -->
</template>

<script>
export default {
  data() {
    return {
      safeHtml: sanitizeHtml("<script>alert('XSS');</script><p>안전한 텍스트</p>")
    };
  }
};
</script>
  • 악성 <script> 태그 제거 → 안전한 HTML만 표시

2. SQL Injection 방지 (백엔드 사용 시)

문제점

SQL 데이터를 직접 조회할 때 사용자 입력이 SQL 문으로 실행될 가능성이 있음.

SELECT * FROM posts WHERE id = '1' OR '1' = '1'
 

위 코드처럼 항상 참이 되는 조건이 들어가면 모든 데이터가 노출될 수 있음.

해결 방법

  • 파이어스토어 사용 시 Firestore Security Rules 적용
  • SQL을 사용할 경우 ORM(예: Prisma, Sequelize) 또는 Prepared Statement 사용
// SQL Injection 방지 (예제: MySQL + Node.js)
const query = "SELECT * FROM posts WHERE id = ?";
db.query(query, [userInput]); // 바인딩 처리로 SQL Injection 방지
  • 직접 문자열을 연결하지 않고, 안전하게 처리

3. API 엔드포인트 보안 (인증 & 권한 설정)

문제점

블로그 글을 조회하는 API가 인증 없이 열려 있으면 비공개 글이 노출될 위험이 있음.

해결 방법

  • Firebase Firestore 보안 규칙 설정
rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    match /posts/{postId} {
      allow read: if resource.data.isPublic == true || request.auth != null;
    }
  }
}
  • isPublic == true인 경우에만 비로그인 사용자도 조회 가능
  • 로그인한 사용자만 글을 볼 수 있도록 설정 가능

4. CORS(Cross-Origin Resource Sharing) 보안

문제점

API가 모든 도메인에서 접근 가능하면, 악성 사이트에서 데이터를 요청할 수 있음.

해결 방법

  • CORS 설정에서 신뢰할 수 있는 도메인만 허용
// Node.js Express CORS 설정 (보안 강화)
const cors = require("cors");

app.use(cors({
  origin: ["https://myblog.com"], // 허용할 도메인만 지정
  methods: ["GET"], // 글 조회만 허용
  credentials: true
}));

5. 로그인 기반 글 조회 제한

문제점

비회원이 유료 게시물 또는 관리자 전용 글을 조회할 수 있음.

해결 방법

  • Firebase Authentication + Firestore Security Rules 적용
  • Vue Router에서 로그인 여부 확인 후 페이지 접근 제한
router.beforeEach((to, from, next) => {
  const isAuthenticated = !!firebase.auth().currentUser;
  if (to.meta.requiresAuth && !isAuthenticated) {
    next("/login");
  } else {
    next();
  }
});
  • requiresAuth: true인 페이지는 로그인된 사용자만 접근 가능

6. HTTPS 사용

문제점

HTTP를 사용하면 로그인 정보, API 요청 데이터가 평문으로 노출될 위험이 있음.

해결 방법

  • HTTPS를 강제 사용 (Let's Encrypt, Cloudflare 활용)
  • HSTS(HTTP Strict Transport Security) 설정
server {
  listen 443 ssl;
  server_name myblog.com;
  ssl_certificate /etc/letsencrypt/live/myblog.com/fullchain.pem;
  ssl_certificate_key /etc/letsencrypt/live/myblog.com/privkey.pem;
  add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
}
  • HTTP → HTTPS 자동 리디렉션

7. 데이터 무결성 보호

문제점

사용자가 API 요청을 변조하여 다른 사용자의 게시글을 수정/삭제할 가능성이 있음.

해결 방법

  • Firestore Rules에서 본인 글만 수정 가능하도록 설정
match /posts/{postId} {
  allow update, delete: if request.auth.uid == resource.data.authorId;
}
  • 로그인된 사용자의 UID와 게시글 작성자의 UID가 동일한 경우에만 수정 가능

8. 속도 및 성능 보안

문제점

트래픽 폭주 시 서비스가 마비될 가능성이 있음.

해결 방법

  • 캐싱(Cache-Control) 사용
  • Firebase Firestore의 limit() 사용하여 과도한 쿼리 방지
const q = query(collection(db, "posts"), orderBy("createdAt", "desc"), limit(10));
  • Cloudflare, AWS WAF 같은 웹 방화벽(WAF) 활용

결론

보안 이슈해결 방법

XSS 공격 sanitize-html 사용, v-html 최소화
SQL Injection ORM, Prepared Statement 사용
API 보안 Firebase Security Rules 적용
CORS 설정 신뢰할 도메인만 허용
로그인 필요 router.beforeEach() 활용
HTTPS SSL 인증서 적용, HSTS 활성화
데이터 보호 본인 글만 수정 가능하도록 보안 규칙 설정
속도 최적화 캐싱, Firestore limit() 사용

sanitize-html란?

sanitize-html은 HTML 보안을 위해 사용되는 JavaScript 라이브러리입니다.
이 라이브러리는 사용자로부터 입력받은 HTML을 필터링하여 XSS(Cross-Site Scripting) 공격을 방지합니다.

왜 sanitize-html이 필요한가?

사용자가 입력한 HTML을 그대로 렌더링하면 악성 스크립트 공격이 가능합니다.
- sanitize-html을 사용하면 악성 스크립트를 자동으로 제거합니다.

sanitize-html 설치

npm install sanitize-html

사용법

기본 사용 예제

const sanitizeHtml = require("sanitize-html");

const dirty = '<script>alert("해킹됨!");</script><h1>안전한 제목</h1>';
const clean = sanitizeHtml(dirty);

console.log(clean); // "<h1>안전한 제목</h1>"
  • <script> 태그가 제거됨 → 보안 강화!

허용할 태그 지정하기

기본적으로 모든 스크립트 관련 태그는 제거되지만, 특정 태그만 허용할 수도 있습니다.

const clean = sanitizeHtml(dirty, {
  allowedTags: ["b", "i", "em", "strong", "h1"], // 허용할 태그 지정
});

console.log(clean); // "<h1>안전한 제목</h1>"
  • script는 제거되고, h1 태그는 유지됨

허용할 속성 지정하기

img, a 등의 태그는 특정 속성만 허용하는 것이 안전합니다.

const clean = sanitizeHtml(dirty, {
  allowedTags: ["a", "img"],
  allowedAttributes: {
    a: ["href", "title"],
    img: ["src", "alt"]
  }
});
  • 링크와 이미지의 속성을 제한하여 악성 코드 방지

필터링된 태그를 다른 값으로 변환

sanitize-html을 사용하면 제거된 태그를 특정 텍스트로 변환할 수도 있습니다.

const clean = sanitizeHtml(dirty, {
  transformTags: {
    "script": () => ({
      tagName: "div",
      text: "[스크립트 제거됨]"
    })
  }
});

console.log(clean); // "[스크립트 제거됨] <h1>안전한 제목</h1>"
  • 스크립트가 제거되지 않고 특정 문구로 변환됨

결론

sanitize-html은 사용자 입력을 필터링하여 XSS 공격을 방지하는 강력한 도구입니다.
-> 보안이 중요한 웹사이트(예: 블로그, 게시판, 채팅)에서 필수적으로 사용해야 합니다.