AngularJS SCE 비활성화

Disabling AngularJS SCE

설명

AngularJS의 SCE(Strict Contextual Escaping)는 HTML이나 민감한 URL 바인딩에 컨텍스트별 신뢰 검사를 적용합니다. HTML은 $sanitize가 있으면 정화하고, 없으면 신뢰되지 않은 값을 거부합니다. 모든 CSS나 JavaScript를 자동으로 정화하는 기능은 아닙니다. $sceProvider.enabled(false)로 SCE를 전역 비활성화하면, 신뢰되지 않은 값을 ng-bind-html 등에 넣을 때 XSS로 이어질 수 있습니다. 공용 모듈에서 끄는 경우에도 애플리케이션 전체에 영향을 줍니다.

AngularJS의 공식 지원은 2022년 1월에 종료되었습니다. 아래 내용은 기존 AngularJS 애플리케이션의 유지보수를 위한 안내입니다.

잠재적 영향

  • XSS에 의한 임의 스크립트 실행(Arbitrary JavaScript Execution): 사용자가 본 문서 컨텍스트에서 악성 스크립트를 실행하여 UI 조작, 추가 공격을 수행할 수 있음
  • 세션 탈취(Session Hijacking): 쿠키/토큰을 탈취해 사용자 계정을 가로챌 수 있음
  • 민감정보 유출(Data Exfiltration): 페이지 내 표시되거나 접근 가능한 데이터(개인정보, CSRF 토큰 등)를 외부로 전송 가능
  • 페이지 변조 및 피싱(Defacement/Phishing): DOM을 조작해 가짜 입력폼, 결제창을 띄워 사용자 기만
  • 추가 공격으로의 발판(Defense Bypass/CSRF Chain): SCE 비활성화와 입력 검증·CSP 등의 방어 부족이 겹치면 피해가 커질 수 있음

해결 방법

  • $sceProvider.enabled(false)를 사용하지 말고 기본값(SCE 활성화)을 유지하세요.
  • 동적 HTML이 꼭 필요하면 다음 방식으로 처리하세요:
    • ng-bind(텍스트 바인딩) 사용으로 HTML 해석을 차단하세요.
    • ngSanitize 모듈의 $sanitize로 정화한 뒤, 꼭 필요한 경우에만 $sce.trustAsHtml(cleanHtml)로 필요한 컨텍스트에서만 신뢰하도록 표시하세요.
  • 사용자 생성 컨텐츠(댓글, 소개글 등)는 서버/클라이언트 모두에서 허용 목록 기반의 정화(whitelist sanitization) 수행한 뒤 저장하거나 표시하세요.
  • 템플릿에서 ng-bind-html 사용 시 입력 출처를 제한하고, 신뢰할 수 있는 데이터만 바인딩하세요.
  • 서드파티 스크립트/모듈이 SCE를 끄지 않는지 코드 리뷰와 정적 분석(SAST)으로 점검하세요.
  • CSP(Content-Security-Policy)를 적용해 인라인 스크립트와 임의 스크립트 로딩을 제한하여 피해 범위를 줄이세요.

예시

변경 전

각 블록은 별도 app.js와 index.html의 일부를 함께 보여 줍니다.

javascript
<!-- app.js -->
(function(){
  angular.module('shopApp', [])
    .config(['$sceProvider', function($sceProvider){
      // BAD: 전역 SCE 비활성화
      $sceProvider.enabled(false);
    }])
    .controller('CommentCtrl', ['$location', function($location){
      var vm = this;
      // 공격자가 URL ?c=<img src=x onerror=alert(1)> 로 주입 가능
      vm.comment = $location.search().c || '';
    }]);
})();

<!-- index.html -->
<div ng-app="shopApp" ng-controller="CommentCtrl as vm">
  <!-- BAD: SCE 꺼진 상태에서 HTML 그대로 실행됨 -->
  <div ng-bind-html="vm.comment"></div>
</div>

변경 후

javascript
<!-- app.js -->
(function(){
  angular.module('shopApp', ['ngSanitize'])
    // 주의: SCE 비활성화 설정 없음(기본 활성화 유지)
    .controller('CommentCtrl', ['$location', '$sanitize', '$sce', function($location, $sanitize, $sce){
      var vm = this;
      var raw = $location.search().c || '';
      // 1) 가장 안전: 텍스트로만 표시
      vm.commentText = raw;
      // 2) 동적 HTML이 꼭 필요할 때만: 정화 후 한정 신뢰
      var clean = $sanitize(raw);
      vm.commentHtml = $sce.trustAsHtml(clean);
    }]);
})();

<!-- index.html -->
<div ng-app="shopApp" ng-controller="CommentCtrl as vm">
  <!-- 안전: HTML 해석 없이 텍스트로 렌더링 -->
  <div ng-bind="vm.commentText"></div>

  <!-- 필요 시에만 사용: $sanitize + trustAsHtml 결과만 바인딩 -->
  <div ng-bind-html="vm.commentHtml"></div>
</div>

설명:

  • 변경 전: $sceProvider.enabled(false)로 SCE 검사를 끈 상태에서 URL 입력을 ng-bind-html에 전달해 위험한 HTML이 렌더링될 수 있습니다.
  • 변경 후: SCE를 유지하고 기본적으로 ng-bind로 텍스트를 표시합니다. HTML이 필요한 경우에는 먼저 $sanitize를 적용합니다. $sce.trustAsHtml 자체는 정화 함수가 아니라 해당 값을 신뢰한다고 표시하는 API입니다.

참조