NDC Oslo 2025 - 자바스크립트 보안 이 영상 하나로 끝내세요 (개발자 필독)
여기 아주 좋은 유튜브 강연이 있는데요, 이 강연의 핵심만 전체적으로 살펴볼까 힙니다.
발표자는 타냐 젠카(Tanya Janca)라는 분인데, 무려 캐나다에서 대테러 업무까지 맡았던 엄청난 경력의 보안 전문가거든요.
이 분이 자바스크립트 개발자들이 꼭 알아야 할 보안 팁들을 아주 쉽고 재미있게 풀어주더라고요.
사실 자바스크립트는 이제 웹의 전부라고 해도 과언이 아니죠.
인터넷을 아름답고 반응성 있게 만들어주는 핵심 언어인데요.
이렇게 널리 쓰이는 만큼 공격자들의 가장 큰 타겟이 되기도 합니다.
그래서 우리 개발자들이 사용자를 보호하기 위해 안전한 코드를 작성하는 건 정말 중요한 책임이라고 할 수 있죠.
왜 시큐어 코딩이 그토록 중요할까요
매년 '버라이즌 데이터 유출 보고서'라는 게 나오는데요.
제가 이 보고서를 읽기 시작한 2015년부터 지금까지, 매년 데이터 유출의 가장 큰 원인 1위는 바로 '안전하지 않은 소프트웨어'였습니다.
물론 시작은 피싱이나 사회 공학적 공격일 수 있지만, 결국 해커가 데이터를 빼내는 결정적인 통로는 바로 우리가 만든 앱의 보안 취약점이라는 거죠.
이제는 사용자들도 보안을 중요하게 생각하고, GDPR처럼 법적인 규제도 점점 강해지고 있습니다.
더 이상 보안은 '하면 좋은 것'이 아니라 '반드시 해야 하는 것'이 된 셈이죠.
자바스크립트 보안의 핵심 '크로스사이트 스크립팅(XSS)'
본격적인 팁에 앞서서 자바스크립트 보안의 '알파이자 오메가'인 크로스사이트 스크립팅(XSS)부터 짚고 넘어가야 하는데요.
XSS는 수많은 공격 기법 중에서 유일하게 '자바스크립트만을 위해' 설계된 공격입니다.
'인젝션 공격'의 한 종류인데, 공격자가 입력한 악성 스크립트를 앱이 '원래 코드의 일부'인 것처럼 착각하고 실행시켜 버리는 거죠.
원래는 '데이터'로 처리되어야 할 입력값이 '코드'로 해석되면서 문제가 생기는 겁니다.
다른 인젝션 공격은 우리 데이터베이스나 서버를 공격하는데요.
XSS는 우리 앱을 사용하는 '사용자'를 직접 공격한다는 점에서 훨씬 더 악질적이라고 할 수 있습니다.
이 XSS를 막는 세 가지 핵심 원칙이 있는데요.
첫째는 '입력값 검증(Input Validation)', 둘째는 '출력값 인코딩(Output Encoding)', 그리고 마지막은 '콘텐츠 보안 정책(Content Security Policy) 헤더 사용'입니다.
이 세 가지만 잘 지켜도 대부분의 XSS 공격은 막아낼 수 있죠.
이제 이 원칙들을 바탕으로 한 구체적인 팁들을 하나씩 살펴보겠습니다.
1. 프레임워크를 적극적으로 활용하세요
요즘은 바닐라 자바스크립트만으로 개발하는 경우는 드물죠.
React, Angular, Vue.js 같은 프레임워크는 기본적으로 '출력 인코딩'을 자동으로 처리해주는 기능이 내장되어 있습니다.
출력 인코딩이란 화면에 데이터를 표시할 때, 이게 그냥 '텍스트'일 뿐이라고 명시해서 스크립트로서의 실행 능력을 빼앗아 버리는 건데요.
이것만으로도 XSS 공격의 상당수를 막을 수 있죠.
물론 프레임워크에도 함정은 있습니다.
예를 들어 React의 dangerouslySetInnerHTML
이나 Angular의 bypassSecurityTrust
같은 함수들은 보안 기능을 일부러 우회하는 거라 정말 조심해서 써야 합니다.
2. 인라인 스크립팅은 이제 그만
HTML 파일 안에 <script>
태그를 섞어 쓰는 '인라인 스크립팅'은 정말 피해야 할 습관 중 하나인데요.
코드를 별도 파일로 분리하는 건 성능, 유지보수 측면에서도 좋은 모범 사례일 뿐만 아니라 보안에도 훨씬 안전합니다.
HTML과 자바스크립트가 뒤섞여 있으면 XSS 공격이 파고들 틈이 훨씬 많아지거든요.
만약 정말 어쩔 수 없이 써야 한다면, 데이터 변수를 반드시 '따옴표'로 감싸서 이건 '텍스트 데이터'라는 걸 명확히 알려줘야 합니다.
3. 사용자 입력은 '데이터'로만 취급하세요
사용자로부터 받은 입력값은 '저장'하거나 '표시'하는 용도로만 생각하는 게 가장 안전합니다.
이걸 가지고 어떤 결정을 내리거나 로직을 처리하려고 하면 위험해지기 시작하죠.
예를 들어, 사용자가 입력한 URL로 바로 리다이렉트 시킨다거나 하는 코드는 정말 위험한데요.
만약 꼭 그래야 한다면, 우리가 미리 허용해 둔 '안전한 URL 목록'과 비교해서 검증하는 절차를 반드시 거쳐야 합니다.
4. innerHTML
대신 textContent
를 사용하세요
이건 정말 중요한 내용인데요.innerHTML
이나 outerHTML
은 XSS 취약점의 주범이라고 할 수 있습니다.
이 함수들은 문자열을 받아서 HTML 코드로 '해석'하고 실행해버리거든요.
반면에 textContent
나 createElement
같은 함수는 입력값을 순수한 '텍스트'로만 취급하기 때문에 훨씬 안전합니다.
// ❌ 위험한 방식 (Bad Practice)
const userInput = "<img src=x onerror=alert('XSS')>";
document.getElementById("myDiv").innerHTML = userInput; // 스크립트가 실행될 수 있음
// ✅ 안전한 방식 (Good Practice)
const userInput = "<img src=x onerror=alert('XSS')>";
document.getElementById("myDiv").textContent = userInput; // 단순 텍스트로 출력됨
위 예제를 보면 왜 textContent
가 안전한지 바로 이해가 되죠.
5. escape
대신 encodeURIComponent
를 사용하세요
URL을 다룰 때는 escape
, unescape
함수 대신 encodeURIComponent
, decodeURIComponent
를 쓰는 게 좋습니다.encodeURIComponent
는 URI의 각 구성 요소를 더 세밀하게 분해해서 안전하게 인코딩해주거든요.
최근 OWASP Top 10에도 오른 '서버 사이드 요청 위조(SSRF)' 같은 공격은 URL 형식을 조작해서 발생하는데, 이런 최신 함수를 사용하면 이런 공격을 예방하는 데 큰 도움이 됩니다.
6. document.write
는 잊어주세요
document.write
는 이제는 사용하지 않는 오래된 방식이기도 하고, 'DOM 기반 XSS'의 원인이 되기도 하는데요.
DOM 기반 XSS는 안전하지 않은 데이터를 DOM에 저장했다가 나중에 그걸 꺼내 쓸 때 문제가 발생하는 방식입니다.createElement
나 appendChild
같은 최신 DOM API를 사용하면 요소, 속성, 이벤트 핸들링을 훨씬 더 세밀하고 안전하게 제어할 수 있죠.
7. CSS URL에 데이터를 전달할 땐 URL 인코딩은 필수
CSS는 프로그래밍 언어가 아니기 때문에 스스로 보안 처리를 하지 못합니다.
그래서 자바스크립트에서 CSS의 url()
메소드에 데이터를 전달할 때는, 전달하기 '전에' 반드시 URL 인코딩을 해줘야 하죠.
이걸 깜빡하면 특수문자가 필터링 없이 넘어가서 XSS 공격의 빌미를 제공할 수 있습니다.
8. 동적 속성 대신 정적 속성을 사용하세요
setAttribute
를 사용할 때는 동적인 속성보다 정적인 속성을 사용하는 것이 안전한데요.
'동적 속성'이란 onclick
, onblur
처럼 이벤트 핸들러나 href
, src
처럼 실행 가능한 콘텐츠를 담을 수 있는 속성들을 말합니다.
이런 속성에 사용자 입력값을 그대로 넣는 것은 정말 위험하죠.align
, alt
, width
처럼 단순히 값을 나타내는 '정적 속성' 위주로 사용하는 것이 좋습니다.
9. 'use strict'를 생활화하세요
자바스크립트 파일 상단에 'use strict';
를 선언하면 '엄격 모드'가 활성화되는데요.
엄격 모드는 더 엄격한 파싱과 에러 핸들링을 강제해서 보안에 큰 도움이 됩니다.
예를 들어, 우연히 전역 변수가 선언되는 것을 막아주고, 예상치 못한 방식으로 코드가 동작하는 것을 방지해주죠.
이런 '예상치 못한 동작'들이 바로 '비즈니스 로직 취약점'으로 이어지는 경우가 많습니다.
10. HTTP 파라미터 오염(HPP) 공격을 막으세요
HTTP 파라미터 오염은 공격자가 동일한 이름의 파라미터를 여러 개 보내서 시스템을 속이는 공격인데요.
Express를 사용한다면 hpp
미들웨어를 사용해서 간단하게 방어할 수 있습니다.
직접 방어 코드를 작성한다면, 서버 사이드에서 허용된 파라미터만 받는 '얼라우리스트(Allow list)' 기반의 강력한 입력값 검증이 필요하죠.
11. DOM 기반 XSS는 'DOMPurify'로 해결
DOM 기반 XSS를 막기 위해 직접 코드를 짜는 것보다, 이미 검증된 라이브러리를 쓰는 게 훨씬 효율적이고 안전합니다.
'DOMPurify'라는 라이브러리는 거의 업계 표준처럼 사용되고 있는데요.
HTML, MathML, SVG 코드에서 XSS 공격 코드를 아주 효과적으로 제거해줍니다.
12. 외부 스크립트는 '서브리소스 무결성(SRI)'으로 검증하세요
CDN 등 외부에서 스크립트 파일을 불러올 때, 중간에 누군가 그 파일을 변조할 수도 있겠죠.
'서브리소스 무결성(SRI)'은 <script>
태그에 해시값을 추가해서, 우리가 다운로드한 파일이 원본과 일치하는지 브라우저가 검증하게 하는 기능입니다.
<script src="https://example.com/example.js"
integrity="sha384-oqVuAfXRKap7fdgcCY5uykM6+R9GqQ8K/uxy9rx7HNQlGYl1kPzQho1wx4JwY8wJ"
crossorigin="anonymous"></script>
이렇게 integrity
속성을 추가해주면, 만약 파일 내용이 1비트라도 다를 경우 브라우저는 스크립트 실행을 차단합니다.
13. CSRF 토큰 검증은 백엔드의 몫
크로스사이트 요청 위조(CSRF)는 사용자가 자신의 의지와 무관하게 공격자가 의도한 행동을 하게 만드는 공격인데요.
예를 들어, 로그인된 사용자가 악성 링크를 클릭하면 자기도 모르게 게시글이 삭제되거나 상품이 주문되는 식이죠.
보통 프론트엔드 프레임워크는 요청을 보낼 때 CSRF 토큰을 자동으로 포함시켜 줍니다.
하지만 이걸로 끝이 아니죠.
자바스크립트가 토큰을 '보내는' 역할을 했다면, 백엔드에서는 그 토큰이 유효한지 반드시 '검증'해야 합니다.
이 검증 로직이 빠져있으면 아무 소용이 없죠.
14. 린터와 SAST 도구를 함께 사용하세요
린터(Linter)는 코드 스타일을 교정하고 잠재적인 오류를 찾아주는 훌륭한 도구입니다.
하지만 린터가 보안 취약점까지 모두 찾아주지는 않죠.
보안에 특화된 '정적 애플리케이션 보안 테스팅(SAST)' 도구를 함께 사용하는 것이 좋은데요.
SAST는 코드를 실행하지 않고 분석해서 XSS, SQL 인젝션 같은 보안 취약점을 찾아줍니다.
이것만은 절대 쓰지 마세요 - 위험한 함수 목록
발표자는 특히 다음과 같은 함수들은 사용을 피하라고 강력하게 권고하는데요.
이 함수들은 코드를 문자열 형태로 받아서 동적으로 실행하는 기능 때문에 보안에 아주 취약하기 때문입니다.
eval()
innerHTML
,outerHTML
document.write()
setTimeout()
,setInterval()
(문자열 인자 사용 시)new Function()
(문자열 인자 사용 시)escape()
,unescape()
이 함수들을 꼭 써야만 하는 상황은 거의 없습니다.
대부분 더 안전하고 현대적인 대체 함수들이 존재하죠.
마치며
강연의 결론은 명확합니다.
안전하지 않은 함수 사용을 피하고, 더 안전한 대안을 사용하며, XSS 공격을 막기 위한 레시피(입력값 검증, 출력 인코딩, CSP)를 철저히 지키라는 것이죠.
발표자는 마지막으로 몇 가지 유용한 무료 보안 도구도 소개해줬는데요.
- Snyk Community: 코드, 의존성, 컨테이너, IaC의 취약점을 찾아주는 SAST 도구입니다.
- DOMPurify: DOM 기반 XSS를 막아주는 최고의 라이브러리죠.
- Retire.js: 내가 사용하는 자바스크립트 라이브러리에 알려진 취약점이 있는지 검사해줍니다.
- npm audit, yarn audit: 이건 다들 잘 아시죠.
결국 보안은 '나중에 추가하는 기능'이 아니라 개발의 모든 단계에 녹아들어야 하는 '문화'인 것 같습니다.
오늘 살펴본 팁들을 하나씩 실천하다 보면, 우리 모두 사용자가 믿고 쓸 수 있는 안전한 서비스를 만드는 데 기여할 수 있을 거예요.