현재 진행 중인 프로젝트에서 `Sparrow`를 사용해 웹 취약점 진단을 실시했고, `innerHTML`을 사용한 코드들이 다량 발견되어 수정 조치했다.
단순히 코드를 고치는 것에서 더 나아가 같은 실수를 반복하지 않기 위해 왜 고쳐야 했는지를 기록해보도록 하겠다.
1.innerHTML
1.1 innerHTML 이란
MDN에서는 `innerHTML`을 이렇게 정의한다.
`Element.innerHTML`
Element속성(property) `innerHTML`은 요소(element) 내에 포함된 HTML 또는 XML 마크업을 가져오거나 설정합니다.
잘 와닿지 않는다. 예시로 만든 코드를 보도록 하자.
텍스트를 이용해 동적으로 테이블을 생성하는 코드이다.
1.2 innerHTML 예시 코드 (텍스트를 사용해 동적 테이블 생성)
<!DOCTYPE html>
<html lang="en">
<style>
table {width: 100%;border-collapse: collapse;margin-top: 20px;}
th, td {border: 1px solid #333;padding: 8px 12px;text-align: left;}
th {background-color: #f4f4f4;}
</style>
<script>
document.addEventListener("DOMContentLoaded", function(){
let tableDiv = document.getElementById("tableDiv");
const data = [
{ name : "이름1", age : "나이1"},
{ name : "이름2", age : "나이2"},
{ name : "이름3", age : "나이3"}
]
let tableTag = "<table>";
tableTag += "<thead><tr>";
for (let key in data[0]) {
tableTag += `<th>${key}</th>`;
}
tableTag += "</tr></thead>";
tableTag += "<tbody>";
data.forEach(row => {
tableTag += "<tr>";
for (let key in row) {
tableTag += `<td>${row[key]}</td>`;
}
tableTag += "</tr>";
});
tableTag += "</tbody>";
tableTag += "</table>";
tableDiv.innerHTML = tableTag;
})
</script>
<body>
<div id="tableDiv"></div>
</body>
</html>
코드를 실행하면 다음과 같은 화면을 확인할 수 있다.
html을 사용하지 않고, 자바스크립트로 테이블을 만들어냈다.
html은 정적이지만, `innerHTML`을 사용하면 동적으로 html 요소를 컨트롤할 수 있는 것이다!

`innerHTML`을 사용해서 테이블을 간단하게 삭제할 수도 있다.
// table 삭제
tableDiv.innerHTML = '';
`innerHTML`을 사용하는 것은 주로 DB에서 값을 조회해 화면에 뿌려줄 때 였던것 같다.
상황마다 불러오는 데이터의 개수와 값이 다르기 때문에 미리 그려놓을 수 없으므로 이렇게 동적으로 생성하게 되는 것이다.
그렇다면 `innerHTM`L이 아닌 `innerText`를 사용하면 어떻게 될까?

문자열 속 태그를 태그로 인식하지 않고 그대로 문자열로 인식해 화면에 뿌려주는 것을 볼 수 있다.
`innerHTML`은 어떻게 문자열을 태그로 인식하는 것일까?
그것은 브라우저의 렌더링 엔진(Rendering Engine) 때문이다.
`렌더링 엔진`은 문자열을 `HTML 파서(HTML Parser)`를 통해 파싱하게 되는데, 이때 HTML 파서는 문자열을 읽고, 태그를 인식하여 DOM 트리를 구축하거나 업데이트하게 된다.
문자열은 인간들에게 가독성이 매우 높은 직관적인 언어이므로 사용하기 편하다.
하지만 편한 만큼 위험성이 따르는데, 그게 `XSS(Cross-Site Scripting)`이다.
2.XSS(Cross-Site Scripting)
2.1 XSS란?
`XSS`는 웹 애플리케이션에서 발생할 수 있는 보안 취약점 중 하나로, 공격자가 악의적인 스크립트 코드를 웹 페이지에 삽입하여 다른 사용자에게 실행되도록 하는 공격 기법이다.
2.2 XSS 공격 예시(실패)
아까 만든 예제를 통해 알아보자.
아까 만든 예제의 데이터가 회원가입 과정을 통해 데이터를 입력받은 후, 입력 받은 데이터를 테이블로 만들어 주는 코드였다고 가정해 보자.
사용자가 이름과 나이에 각각 우리가 원하는 데이터만 잘 넣어주었다면 좋겠지만 모든 사람이 그렇게 정직하지는 않은 법,
어떤 사용자가 나이를 입력해야 할 공간에 스크립트를 입력했다.
이름 : 비밀
나이 : <script>alert('XSS 공격이다!');</script>
이런 값이 들어가는 것을 허용했다는 것부터 잘못이지만, 만약 적절한 조치를 취하지 못해 이런 문제 데이터가 입력되었을 때 `innerHTML`을 사용했다면 어떤 결과가 나타날까?
<!DOCTYPE html>
<html lang="en">
<head>
<style>
table {width: 100%;border-collapse: collapse;margin-top: 20px;}
th, td {border: 1px solid #333;padding: 8px 12px;text-align: left;}
th {background-color: #f4f4f4;}
</style>
<script>
document.addEventListener("DOMContentLoaded", function(){
let tableDiv = document.getElementById("tableDiv");
const data = [
{ name : "이름1", age : "나이1"},
{ name : "이름2", age : "나이2"},
{ name : "비밀", age : "<script>alert('XSS 공격이다!');<\/script>"}
]
let tableTag = "<table>";
tableTag += "<thead><tr>";
for (let key in data[0]) {
tableTag += `<th>${key}</th>`;
}
tableTag += "</tr></thead>";
tableTag += "<tbody>";
data.forEach(row => {
tableTag += "<tr>";
for (let key in row) {
tableTag += `<td>${row[key]}</td>`;
}
tableTag += "</tr>";
});
tableTag += "</tbody>";
tableTag += "</table>";
tableDiv.innerHTML = tableTag;
})
</script>
</head>
<body>
<div id="tableDiv"></div>
</body>
</html>

아무 일도 일어나지 않았다. 입력한 `alert`가 실행되기를 바랐는데 이상하다.
이 정도의 허접한 공격은 통하지 않는 것 같다.
브라우저의 보안 정책으로 인해 `<script>` 태그를 실행하지 않는다는 것을 알아냈다.
2.3 XSS 공격 예시 2
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>XSS 테스트</title>
<style>
table {width: 100%; border-collapse: collapse; margin-top: 20px;}
th, td {border: 1px solid #333; padding: 8px 12px; text-align: left;}
th {background-color: #f4f4f4;}
</style>
<script>
document.addEventListener("DOMContentLoaded", function(){
const input = document.getElementById("userInput");
const submitBtn = document.getElementById("submitBtn");
const displayDiv = document.getElementById("displayDiv");
submitBtn.addEventListener("click", function(){
const userText = input.value;
displayDiv.innerHTML = userText;
});
});
</script>
</head>
<body>
<h1>XSS 테스트</h1>
<input type="text" id="userInput" placeholder="텍스트를 입력하세요">
<button id="submitBtn">삽입</button>
<div id="displayDiv"></div>
</body>
</html>

인풋박스에 사용자가 텍스트를 입력할 수 있는 예제 코드이다.
여기에 사용자가 태그를 포함한 텍스트를 입력했을 때 어떻게 되는지 알아보자.
다음과 같은 텍스트를 입력했다.
<div style="width:100%; height:100%; color:yellow; background-color: red; "> 이것은 텍스트입니다. </div>

입력한 태그가 텍스트가 아닌 HTML태그로 파싱 되어 출력된 것을 확인할 수 있다.
사용자가 개발자의 구현의도와는 다른 용도로 코드를 사용했다.
용도 외에 다른 결과를 얻어낼 수 있으므로 이것은 큰 문제라고 볼 수 있다.
2.4 XSS 공격의 피해
`XSS`가 가져올 수 있는 피해는 다음과 같다.
1) 세션 하이재킹
2) 피싱
3) 웹사이트 변조
4) 악성 소프트웨어 배포
5) 기타 악의적인 행위(광고 클릭, 자동 구매, 데이터 조작)
3. 해결방법(textContent 사용)
여러 가지 방법을 찾을 수 있었다.
사용자가 입력한 값에 대해 유효성 검사를 실행하거나, HTML 특수 문자를 이스케이프 처리하거나, `DOMPurify`와 같은 보안 라이브러리를 사용하거나 아예 `innerHTML`을 사용하지 않으면 된다.
나는 `innerHTML`의 사용에 대한 지적을 받았기 때문에, `innerHTML`을 코드에서 제거하지 않으면 안 되는 상황이었다.
그래서 `innerHTML` 대신에 `textContent`를 사용하는 것으로 문제를 해결했다.
위 예제 코드에서 `innerHTML`을 `textContent`로 바꿔서 코드를 재실행해보겠다.

텍스트에 포함된 특수문자가 HTML 태그로 파싱 되지 않고 그대로 문자열로 출력되는 것을 확인할 수 있었다.
4. innerHTML과 textContent의 비교
특징 | innerHTML | textContent |
HTML 해석 | 제공된 문자열을 HTML로 파싱하여 요소의 자식으로 삽입 | 제공된 문자열을 텍스트로 삽입하며, HTML 태그를 해석하지 않음 |
보안 | 악의적인 스크립트가 삽입될 가능성이 있으며, XSS 공격에 취약 | 스크립트나 HTML 태그가 텍스트로 처리되며XSS 공격에 안전함 |
성능 | 복잡한 HTML을 삽입할 때는 오버헤드 발생 가능 있음 | 빠름 |
용도 | 동적으로 HTML 구조 변경, 외부 HTML 콘텐츠 삽입 | 단순 텍스트 표시 |
대상 요소의 영향 | HTML 구조 자체를 변경할 수 있으므로, CSS 스타일이나 다른 요소에도 영향을 미침 | 텍스트만 변경하므로, 요소의 다른 속성이나 구조에는 영향 미치지 않음 |
성능과 보안 면에서 `innerHTML` 보다 `textContent`가 좋은 것을 확인할 수 있다.
그러므로 불가피한 상황이 아니라면 `textContent`를 사용하는 것이 좋겠다.
물론 `textContent`는 텍스트만 다룰 수 있기 때문에 html요소를 생성하기 위해서는 다른 방법을 추가로 사용해야 한다.
바로 `createElement()`와 생성된 요소를 추가할 수 있는 `append()`이다.
`innerHTML` 사용을 지양하고 `textContent, createElement(), append()`를 사용하도록 하자.
'JavaScript' 카테고리의 다른 글
초간단 키보드 방향키를 누르면 움직이는 요소 구현하기 - 자바스크립트(Java Script) (0) | 2024.07.11 |
---|---|
초간단 마우스를 따라 다니는 요소 구현하기 - 자바스크립트(Java Script) (0) | 2024.07.05 |
버튼을 클릭하면 요소가 생성되어 떨어지는 기능 구현 - 자바스크립트(Java Script) (0) | 2024.07.04 |
마우스를 피해 움직이는 요소 구현하기 - 자바스크립트(Java Script) (1) | 2024.07.03 |
초간단 주사위 굴리기 기능 구현 - 자바스크립트(Java Script) (0) | 2024.07.03 |
현재 진행 중인 프로젝트에서 Sparrow
를 사용해 웹 취약점 진단을 실시했고, innerHTML
을 사용한 코드들이 다량 발견되어 수정 조치했다.
단순히 코드를 고치는 것에서 더 나아가 같은 실수를 반복하지 않기 위해 왜 고쳐야 했는지를 기록해보도록 하겠다.
1.innerHTML
1.1 innerHTML 이란
MDN에서는 innerHTML
을 이렇게 정의한다.
Element.innerHTML
Element속성(property) innerHTML
은 요소(element) 내에 포함된 HTML 또는 XML 마크업을 가져오거나 설정합니다.
잘 와닿지 않는다. 예시로 만든 코드를 보도록 하자.
텍스트를 이용해 동적으로 테이블을 생성하는 코드이다.
1.2 innerHTML 예시 코드 (텍스트를 사용해 동적 테이블 생성)
<!DOCTYPE html>
<html lang="en">
<style>
table {width: 100%;border-collapse: collapse;margin-top: 20px;}
th, td {border: 1px solid #333;padding: 8px 12px;text-align: left;}
th {background-color: #f4f4f4;}
</style>
<script>
document.addEventListener("DOMContentLoaded", function(){
let tableDiv = document.getElementById("tableDiv");
const data = [
{ name : "이름1", age : "나이1"},
{ name : "이름2", age : "나이2"},
{ name : "이름3", age : "나이3"}
]
let tableTag = "<table>";
tableTag += "<thead><tr>";
for (let key in data[0]) {
tableTag += `<th>${key}</th>`;
}
tableTag += "</tr></thead>";
tableTag += "<tbody>";
data.forEach(row => {
tableTag += "<tr>";
for (let key in row) {
tableTag += `<td>${row[key]}</td>`;
}
tableTag += "</tr>";
});
tableTag += "</tbody>";
tableTag += "</table>";
tableDiv.innerHTML = tableTag;
})
</script>
<body>
<div id="tableDiv"></div>
</body>
</html>
코드를 실행하면 다음과 같은 화면을 확인할 수 있다.
html을 사용하지 않고, 자바스크립트로 테이블을 만들어냈다.
html은 정적이지만, innerHTML
을 사용하면 동적으로 html 요소를 컨트롤할 수 있는 것이다!

innerHTML
을 사용해서 테이블을 간단하게 삭제할 수도 있다.
// table 삭제
tableDiv.innerHTML = '';
innerHTML
을 사용하는 것은 주로 DB에서 값을 조회해 화면에 뿌려줄 때 였던것 같다.
상황마다 불러오는 데이터의 개수와 값이 다르기 때문에 미리 그려놓을 수 없으므로 이렇게 동적으로 생성하게 되는 것이다.
그렇다면 innerHTM
L이 아닌 innerText
를 사용하면 어떻게 될까?

문자열 속 태그를 태그로 인식하지 않고 그대로 문자열로 인식해 화면에 뿌려주는 것을 볼 수 있다.
innerHTML
은 어떻게 문자열을 태그로 인식하는 것일까?
그것은 브라우저의 렌더링 엔진(Rendering Engine) 때문이다.
렌더링 엔진
은 문자열을 HTML 파서(HTML Parser)
를 통해 파싱하게 되는데, 이때 HTML 파서는 문자열을 읽고, 태그를 인식하여 DOM 트리를 구축하거나 업데이트하게 된다.
문자열은 인간들에게 가독성이 매우 높은 직관적인 언어이므로 사용하기 편하다.
하지만 편한 만큼 위험성이 따르는데, 그게 XSS(Cross-Site Scripting)
이다.
2.XSS(Cross-Site Scripting)
2.1 XSS란?
XSS
는 웹 애플리케이션에서 발생할 수 있는 보안 취약점 중 하나로, 공격자가 악의적인 스크립트 코드를 웹 페이지에 삽입하여 다른 사용자에게 실행되도록 하는 공격 기법이다.
2.2 XSS 공격 예시(실패)
아까 만든 예제를 통해 알아보자.
아까 만든 예제의 데이터가 회원가입 과정을 통해 데이터를 입력받은 후, 입력 받은 데이터를 테이블로 만들어 주는 코드였다고 가정해 보자.
사용자가 이름과 나이에 각각 우리가 원하는 데이터만 잘 넣어주었다면 좋겠지만 모든 사람이 그렇게 정직하지는 않은 법,
어떤 사용자가 나이를 입력해야 할 공간에 스크립트를 입력했다.
이름 : 비밀
나이 : <script>alert('XSS 공격이다!');</script>
이런 값이 들어가는 것을 허용했다는 것부터 잘못이지만, 만약 적절한 조치를 취하지 못해 이런 문제 데이터가 입력되었을 때 innerHTML
을 사용했다면 어떤 결과가 나타날까?
<!DOCTYPE html>
<html lang="en">
<head>
<style>
table {width: 100%;border-collapse: collapse;margin-top: 20px;}
th, td {border: 1px solid #333;padding: 8px 12px;text-align: left;}
th {background-color: #f4f4f4;}
</style>
<script>
document.addEventListener("DOMContentLoaded", function(){
let tableDiv = document.getElementById("tableDiv");
const data = [
{ name : "이름1", age : "나이1"},
{ name : "이름2", age : "나이2"},
{ name : "비밀", age : "<script>alert('XSS 공격이다!');<\/script>"}
]
let tableTag = "<table>";
tableTag += "<thead><tr>";
for (let key in data[0]) {
tableTag += `<th>${key}</th>`;
}
tableTag += "</tr></thead>";
tableTag += "<tbody>";
data.forEach(row => {
tableTag += "<tr>";
for (let key in row) {
tableTag += `<td>${row[key]}</td>`;
}
tableTag += "</tr>";
});
tableTag += "</tbody>";
tableTag += "</table>";
tableDiv.innerHTML = tableTag;
})
</script>
</head>
<body>
<div id="tableDiv"></div>
</body>
</html>

아무 일도 일어나지 않았다. 입력한 alert
가 실행되기를 바랐는데 이상하다.
이 정도의 허접한 공격은 통하지 않는 것 같다.
브라우저의 보안 정책으로 인해 <script>
태그를 실행하지 않는다는 것을 알아냈다.
2.3 XSS 공격 예시 2
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>XSS 테스트</title>
<style>
table {width: 100%; border-collapse: collapse; margin-top: 20px;}
th, td {border: 1px solid #333; padding: 8px 12px; text-align: left;}
th {background-color: #f4f4f4;}
</style>
<script>
document.addEventListener("DOMContentLoaded", function(){
const input = document.getElementById("userInput");
const submitBtn = document.getElementById("submitBtn");
const displayDiv = document.getElementById("displayDiv");
submitBtn.addEventListener("click", function(){
const userText = input.value;
displayDiv.innerHTML = userText;
});
});
</script>
</head>
<body>
<h1>XSS 테스트</h1>
<input type="text" id="userInput" placeholder="텍스트를 입력하세요">
<button id="submitBtn">삽입</button>
<div id="displayDiv"></div>
</body>
</html>

인풋박스에 사용자가 텍스트를 입력할 수 있는 예제 코드이다.
여기에 사용자가 태그를 포함한 텍스트를 입력했을 때 어떻게 되는지 알아보자.
다음과 같은 텍스트를 입력했다.
<div style="width:100%; height:100%; color:yellow; background-color: red; "> 이것은 텍스트입니다. </div>

입력한 태그가 텍스트가 아닌 HTML태그로 파싱 되어 출력된 것을 확인할 수 있다.
사용자가 개발자의 구현의도와는 다른 용도로 코드를 사용했다.
용도 외에 다른 결과를 얻어낼 수 있으므로 이것은 큰 문제라고 볼 수 있다.
2.4 XSS 공격의 피해
XSS
가 가져올 수 있는 피해는 다음과 같다.
1) 세션 하이재킹
2) 피싱
3) 웹사이트 변조
4) 악성 소프트웨어 배포
5) 기타 악의적인 행위(광고 클릭, 자동 구매, 데이터 조작)
3. 해결방법(textContent 사용)
여러 가지 방법을 찾을 수 있었다.
사용자가 입력한 값에 대해 유효성 검사를 실행하거나, HTML 특수 문자를 이스케이프 처리하거나, DOMPurify
와 같은 보안 라이브러리를 사용하거나 아예 innerHTML
을 사용하지 않으면 된다.
나는 innerHTML
의 사용에 대한 지적을 받았기 때문에, innerHTML
을 코드에서 제거하지 않으면 안 되는 상황이었다.
그래서 innerHTML
대신에 textContent
를 사용하는 것으로 문제를 해결했다.
위 예제 코드에서 innerHTML
을 textContent
로 바꿔서 코드를 재실행해보겠다.

텍스트에 포함된 특수문자가 HTML 태그로 파싱 되지 않고 그대로 문자열로 출력되는 것을 확인할 수 있었다.
4. innerHTML과 textContent의 비교
특징 | innerHTML | textContent |
HTML 해석 | 제공된 문자열을 HTML로 파싱하여 요소의 자식으로 삽입 | 제공된 문자열을 텍스트로 삽입하며, HTML 태그를 해석하지 않음 |
보안 | 악의적인 스크립트가 삽입될 가능성이 있으며, XSS 공격에 취약 | 스크립트나 HTML 태그가 텍스트로 처리되며XSS 공격에 안전함 |
성능 | 복잡한 HTML을 삽입할 때는 오버헤드 발생 가능 있음 | 빠름 |
용도 | 동적으로 HTML 구조 변경, 외부 HTML 콘텐츠 삽입 | 단순 텍스트 표시 |
대상 요소의 영향 | HTML 구조 자체를 변경할 수 있으므로, CSS 스타일이나 다른 요소에도 영향을 미침 | 텍스트만 변경하므로, 요소의 다른 속성이나 구조에는 영향 미치지 않음 |
성능과 보안 면에서 innerHTML
보다 textContent
가 좋은 것을 확인할 수 있다.
그러므로 불가피한 상황이 아니라면 textContent
를 사용하는 것이 좋겠다.
물론 textContent
는 텍스트만 다룰 수 있기 때문에 html요소를 생성하기 위해서는 다른 방법을 추가로 사용해야 한다.
바로 createElement()
와 생성된 요소를 추가할 수 있는 append()
이다.
innerHTML
사용을 지양하고 textContent, createElement(), append()
를 사용하도록 하자.
'JavaScript' 카테고리의 다른 글
초간단 키보드 방향키를 누르면 움직이는 요소 구현하기 - 자바스크립트(Java Script) (0) | 2024.07.11 |
---|---|
초간단 마우스를 따라 다니는 요소 구현하기 - 자바스크립트(Java Script) (0) | 2024.07.05 |
버튼을 클릭하면 요소가 생성되어 떨어지는 기능 구현 - 자바스크립트(Java Script) (0) | 2024.07.04 |
마우스를 피해 움직이는 요소 구현하기 - 자바스크립트(Java Script) (1) | 2024.07.03 |
초간단 주사위 굴리기 기능 구현 - 자바스크립트(Java Script) (0) | 2024.07.03 |