8. outputting raw html (feat. v-html)

Cross Site Scripting (XSS)

이 강의에서는 Vue.js를 사용하여 웹 애플리케이션에서 원시 HTML을 출력하는 방법과 관련된 보안 위험에 대해 배웁니다. 크로스 사이트 스크립팅(XSS)이라는 보안 위험은 악의적인 HTML이 웹 페이지에 삽입될 때 발생합니다. 이는 외부 데이터 소스에서 HTML을 출력할 때 자주 발생하는데, Vue.js는 이러한 보안 위험을 방지하기 위해 데이터를 원시 텍스트로만 출력합니다.

v-html 디렉티브를 사용하면 원시 HTML을 출력할 수 있지만, 이는 보안 위험을 증가시킬 수 있습니다. 만약 데이터베이스가 해커에 의해 침해되면, 해커는 링크를 해로운 사이트로 변경할 수 있으며, 이는 크로스 사이트 스크립팅의 한 예입니다. 프론트엔드 개발자의 책임은 문자를 이스케이프 처리하는 것이며, Vue.js는 이를 도와줍니다. 그러나 모든 상황을 해결할 수는 없으며, 백엔드 개발자도 데이터베이스에 삽입되는 모든 것을 살균하고 이스케이프 처리하는 것이 중요합니다. 데이터 소스를 신뢰하는 것도 중요하며, 백엔드가 안전하면 프론트엔드 애플리케이션도 안전할 것입니다.


이 강의에서는 코드로 들어가기 전에 원시 HTML(raw HTML)을 출력하는 방법을 배웁니다. 먼저, 이 작업을 수행할 때 발생할 수 있는 보안 문제에 대해 언급합니다. 언급된 보안 위험은 크로스 사이트 스크립팅(Cross Site Scripting, XSS)으로, 악의적인 HTML이 웹 페이지에 삽입되어 애플리케이션의 의도치 않은 동작을 유발할 때 발생합니다. 이는 특히 외부 데이터 소스에서 HTML을 출력할 때 자주 발생합니다.

코스 대부분에서는 기본 데이터를 다루었으며, HTML은 템플릿 내부에 남아있었습니다. 그러나 데이터 소스에서 HTML이 오는 경우도 있으며, 이 경우 크로스 사이트 스크립팅에 노출될 수 있습니다. 데이터는 외부 소스(데이터베이스나 API)에서 올 가능성이 높으며, 이 소스들이 침해되면 애플리케이션도 문제에 직면할 수 있습니다.

원시 HTML을 출력하는 방법을 배우기 전에, 이러한 보안 위험에 대해 이야기합니다. Vue.js에서는 데이터를 원시 텍스트로만 출력하는 것이 기본 동작입니다. 이는 데이터가 침해되었을 경우 애플리케이션의 손상을 방지하기 위한 것입니다. 그러나 원시 HTML을 출력하고자 할 때는 v-html 디렉티브를 사용할 수 있습니다. 이 디렉티브는 unescapable HTML을 출력할 수 있게 해줍니다.

그러나 데이터베이스가 해커에 의해 침해되고 원시 HTML 렌더링이 페이지에 존재한다면, 해커는 링크를 해로운 사이트로 변경할 수 있습니다. 이는 크로스 사이트 스크립팅의 한 예입니다. 프론트엔드 개발자로서 우리의 책임은 문자를 이스케이프 처리하는 것입니다. Vue는 이를 도와주지만, 모든 상황을 해결할 수는 없습니다.

백엔드 개발자의 책임은 데이터베이스에 삽입되는 모든 것을 살균하고 이스케이프 처리하는 것입니다. 데이터 소스를 신뢰하는 것도 중요합니다. 대부분의 API는 안전하지만, 받는 데이터의 종류를 항상 알아야 합니다. 백엔드가 안전하면 프론트엔드 애플리케이션도 안전할 것입니다.


Very little front-end developers can do.

Most of the responsibility is on the back-end developer.

Make sure you get your data from a trusted source.

프론트엔드 개발자가 할 수 있는 일이 거의 없습니다.

대부분의 책임은 백엔드 개발자에게 있습니다.

신뢰할 수 있는 소스에서 데이터를 가져왔는지 확인하세요.

index.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Vue</title>
    <link rel="stylesheet" href="main.css">
</head>
<body>
<div id="app" v-cloak>
    <p>{{  fullName() }}</p>
    <p><a :href="url" target="_blank">Google</a></p>
    <p>{{ raw_url }}</p>
    <p v-html="raw_url"></p>
    <hr />

    <label>First Name</label>
    <input type="text" v-model="firstName">

    <label>Last Name</label>
    <input type="text" v-model="lastName">
</div>

<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/3.4.6/vue.global.prod.min.js"></script>
<script src="app.js"></script>
</body>
</html>