Angular

Tip

AWS ν•΄ν‚Ή 배우기 및 μ—°μŠ΅ν•˜κΈ°:HackTricks Training AWS Red Team Expert (ARTE)
GCP ν•΄ν‚Ή 배우기 및 μ—°μŠ΅ν•˜κΈ°: HackTricks Training GCP Red Team Expert (GRTE) Azure ν•΄ν‚Ή 배우기 및 μ—°μŠ΅ν•˜κΈ°: HackTricks Training Azure Red Team Expert (AzRTE)

HackTricks μ§€μ›ν•˜κΈ°

The Checklist

Checklist from here.

  • Angular은 ν΄λΌμ΄μ–ΈνŠΈ μΈ‘ ν”„λ ˆμž„μ›Œν¬λ‘œ κ°„μ£Όλ˜λ©° μ„œλ²„ μΈ‘ 보호λ₯Ό μ œκ³΅ν•  κ²ƒμœΌλ‘œ κΈ°λŒ€λ˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€.
  • ν”„λ‘œμ νŠΈ κ΅¬μ„±μ—μ„œ 슀크립트의 μ†ŒμŠ€ 맡이 λΉ„ν™œμ„±ν™”λ˜μ–΄ μžˆμŠ΅λ‹ˆλ‹€.
  • μ‹ λ’°ν•  수 μ—†λŠ” μ‚¬μš©μž μž…λ ₯은 항상 ν…œν”Œλ¦Ώμ—μ„œ μ‚¬μš©λ˜κΈ° 전에 λ³΄κ°„λ˜κ±°λ‚˜ μ •λ¦¬λ©λ‹ˆλ‹€.
  • μ‚¬μš©μžλŠ” μ„œλ²„ μΈ‘ λ˜λŠ” ν΄λΌμ΄μ–ΈνŠΈ μΈ‘ ν…œν”Œλ¦Ώμ— λŒ€ν•œ μ œμ–΄ κΆŒν•œμ΄ μ—†μŠ΅λ‹ˆλ‹€.
  • μ‹ λ’°ν•  수 μ—†λŠ” μ‚¬μš©μž μž…λ ₯은 μ• ν”Œλ¦¬μΌ€μ΄μ…˜μ—μ„œ μ‹ λ’°ν•˜κΈ° 전에 μ μ ˆν•œ λ³΄μ•ˆ μ»¨ν…μŠ€νŠΈλ₯Ό μ‚¬μš©ν•˜μ—¬ μ •λ¦¬λ©λ‹ˆλ‹€.
  • BypassSecurity* λ©”μ„œλ“œλŠ” μ‹ λ’°ν•  수 μ—†λŠ” μž…λ ₯κ³Ό ν•¨κ»˜ μ‚¬μš©λ˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€.
  • μ‹ λ’°ν•  수 μ—†λŠ” μ‚¬μš©μž μž…λ ₯은 ElementRef, Renderer2, Document와 같은 Angular ν΄λž˜μŠ€λ‚˜ 기타 JQuery/DOM 싱크에 μ „λ‹¬λ˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€.

What is Angular

AngularλŠ” κ°•λ ₯ν•œ 및 μ˜€ν”ˆ μ†ŒμŠ€ ν”„λ‘ νŠΈ μ—”λ“œ ν”„λ ˆμž„μ›Œν¬λ‘œ Googleμ—μ„œ μœ μ§€ κ΄€λ¦¬ν•©λ‹ˆλ‹€. TypeScriptλ₯Ό μ‚¬μš©ν•˜μ—¬ μ½”λ“œ 가독성과 디버깅을 ν–₯μƒμ‹œν‚΅λ‹ˆλ‹€. κ°•λ ₯ν•œ λ³΄μ•ˆ λ©”μ»€λ‹ˆμ¦˜μ„ 톡해 AngularλŠ” XSS 및 μ˜€ν”ˆ λ¦¬λ””λ ‰μ…˜κ³Ό 같은 일반적인 ν΄λΌμ΄μ–ΈνŠΈ μΈ‘ 취약점을 λ°©μ§€ν•©λ‹ˆλ‹€. μ„œλ²„ μΈ‘μ—μ„œλ„ μ‚¬μš©ν•  수 μžˆμ–΄ μ–‘μͺ½μ—μ„œ λ³΄μ•ˆ κ³ λ € 사항이 μ€‘μš”ν•©λ‹ˆλ‹€.

Framework architecture

Angular의 κΈ°λ³Έ κ°œλ…μ„ 더 잘 μ΄ν•΄ν•˜κΈ° μœ„ν•΄ ν•„μˆ˜ κ°œλ…μ„ μ‚΄νŽ΄λ³΄κ² μŠ΅λ‹ˆλ‹€.

일반적인 Angular ν”„λ‘œμ νŠΈλŠ” 보톡 λ‹€μŒκ³Ό κ°™μŠ΅λ‹ˆλ‹€:

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 μ• ν”Œλ¦¬μΌ€μ΄μ…˜μ€ μ΅œμ†Œν•œ ν•˜λ‚˜μ˜ μ»΄ν¬λ„ŒνŠΈ, 즉 μ»΄ν¬λ„ŒνŠΈ 계측을 DOMκ³Ό μ—°κ²°ν•˜λŠ” 루트 μ»΄ν¬λ„ŒνŠΈ(AppComponent)λ₯Ό κ°€μ§€κ³  μžˆμŠ΅λ‹ˆλ‹€. 각 μ»΄ν¬λ„ŒνŠΈλŠ” μ• ν”Œλ¦¬μΌ€μ΄μ…˜ 데이터와 λ‘œμ§μ„ ν¬ν•¨ν•˜λŠ” 클래슀λ₯Ό μ •μ˜ν•˜λ©°, νƒ€κ²Ÿ ν™˜κ²½μ— ν‘œμ‹œλ  λ·°λ₯Ό μ •μ˜ν•˜λŠ” HTML ν…œν”Œλ¦Ώκ³Ό μ—°κ²°λ©λ‹ˆλ‹€. @Component() λ°μ½”λ ˆμ΄ν„°λŠ” κ·Έ μ•„λž˜μ˜ 클래슀λ₯Ό μ»΄ν¬λ„ŒνŠΈλ‘œ μ‹λ³„ν•˜κ³ , ν…œν”Œλ¦Ώ 및 κ΄€λ ¨ μ»΄ν¬λ„ŒνŠΈ μ „μš© 메타데이터λ₯Ό μ œκ³΅ν•©λ‹ˆλ‹€. AppComponentλŠ” app.component.ts νŒŒμΌμ— μ •μ˜λ˜μ–΄ μžˆμŠ΅λ‹ˆλ‹€.

Angular NgModulesλŠ” μ• ν”Œλ¦¬μΌ€μ΄μ…˜ 도메인, μ›Œν¬ν”Œλ‘œμš° λ˜λŠ” λ°€μ ‘ν•˜κ²Œ κ΄€λ ¨λœ κΈ°λŠ₯ μ„ΈνŠΈλ₯Ό μœ„ν•œ 컴파일 μ»¨ν…μŠ€νŠΈλ₯Ό μ„ μ–Έν•©λ‹ˆλ‹€. λͺ¨λ“  Angular μ• ν”Œλ¦¬μΌ€μ΄μ…˜μ€ 일반적으둜 AppModuleμ΄λΌλŠ” μ΄λ¦„μ˜ 루트 λͺ¨λ“ˆμ„ κ°€μ§€κ³  있으며, μ΄λŠ” μ• ν”Œλ¦¬μΌ€μ΄μ…˜μ„ μ‹œμž‘ν•˜λŠ” λΆ€νŠΈμŠ€νŠΈλž© λ©”μ»€λ‹ˆμ¦˜μ„ μ œκ³΅ν•©λ‹ˆλ‹€. μ• ν”Œλ¦¬μΌ€μ΄μ…˜μ€ 일반적으둜 λ§Žμ€ κΈ°λŠ₯ λͺ¨λ“ˆμ„ ν¬ν•¨ν•©λ‹ˆλ‹€. AppModule은 app.module.ts νŒŒμΌμ— μ •μ˜λ˜μ–΄ μžˆμŠ΅λ‹ˆλ‹€.

Angular Router NgModule은 μ• ν”Œλ¦¬μΌ€μ΄μ…˜μ˜ λ‹€μ–‘ν•œ μƒνƒœμ™€ λ·° 계측 κ°„μ˜ 탐색 경둜λ₯Ό μ •μ˜ν•  수 μžˆλŠ” μ„œλΉ„μŠ€λ₯Ό μ œκ³΅ν•©λ‹ˆλ‹€. RouterModule은 app-routing.module.ts νŒŒμΌμ— μ •μ˜λ˜μ–΄ μžˆμŠ΅λ‹ˆλ‹€.

νŠΉμ • 뷰와 μ—°κ²°λ˜μ§€ μ•Šμ€ λ°μ΄ν„°λ‚˜ λ‘œμ§μ„ κ³΅μœ ν•˜κ³  μ‹Άλ‹€λ©΄ μ„œλΉ„μŠ€ 클래슀λ₯Ό μƒμ„±ν•©λ‹ˆλ‹€. μ„œλΉ„μŠ€ 클래슀 μ •μ˜λŠ” μ¦‰μ‹œ @Injectable() λ°μ½”λ ˆμ΄ν„°λ‘œ μ‹œμž‘λ©λ‹ˆλ‹€. 이 λ°μ½”λ ˆμ΄ν„°λŠ” λ‹€λ₯Έ μ œκ³΅μžκ°€ ν΄λž˜μŠ€μ— μ˜μ‘΄μ„±μœΌλ‘œ μ£Όμž…λ  수 μžˆλ„λ‘ ν•˜λŠ” 메타데이터λ₯Ό μ œκ³΅ν•©λ‹ˆλ‹€. μ˜μ‘΄μ„± μ£Όμž…(DI)은 μ»΄ν¬λ„ŒνŠΈ 클래슀λ₯Ό κ°„κ²°ν•˜κ³  효율적으둜 μœ μ§€ν•  수 있게 ν•΄μ€λ‹ˆλ‹€. 이듀은 μ„œλ²„μ—μ„œ 데이터λ₯Ό κ°€μ Έμ˜€κ±°λ‚˜, μ‚¬μš©μž μž…λ ₯을 κ²€μ¦ν•˜κ±°λ‚˜, μ½˜μ†”μ— 직접 둜그λ₯Ό 남기지 μ•ŠμœΌλ©°, μ΄λŸ¬ν•œ μž‘μ—…μ„ μ„œλΉ„μŠ€μ— μœ„μž„ν•©λ‹ˆλ‹€.

Sourcemap ꡬ성

Angular ν”„λ ˆμž„μ›Œν¬λŠ” tsconfig.json μ˜΅μ…˜μ„ λ”°λ₯΄λ©° TypeScript νŒŒμΌμ„ JavaScript μ½”λ“œλ‘œ λ³€ν™˜ν•œ ν›„ angular.json κ΅¬μ„±μœΌλ‘œ ν”„λ‘œμ νŠΈλ₯Ό λΉŒλ“œν•©λ‹ˆλ‹€. angular.json νŒŒμΌμ„ μ‚΄νŽ΄λ³΄λ©΄ μ†ŒμŠ€λ§΅μ„ ν™œμ„±ν™”ν•˜κ±°λ‚˜ λΉ„ν™œμ„±ν™”ν•˜λŠ” μ˜΅μ…˜μ΄ μžˆμŒμ„ ν™•μΈν–ˆμŠ΅λ‹ˆλ‹€. Angular λ¬Έμ„œμ— λ”°λ₯΄λ©΄, κΈ°λ³Έ ꡬ성은 μŠ€ν¬λ¦½νŠΈμ— λŒ€ν•΄ μ†ŒμŠ€λ§΅ 파일이 ν™œμ„±ν™”λ˜μ–΄ 있으며 기본적으둜 숨겨져 μžˆμ§€ μ•ŠμŠ΅λ‹ˆλ‹€:

"sourceMap": {
"scripts": true,
"styles": true,
"vendor": false,
"hidden": false
}

일반적으둜, sourcemap νŒŒμΌμ€ μƒμ„±λœ νŒŒμΌμ„ 원본 νŒŒμΌμ— λ§€ν•‘ν•˜μ—¬ 디버깅 λͺ©μ μœΌλ‘œ μ‚¬μš©λ©λ‹ˆλ‹€. λ”°λΌμ„œ ν”„λ‘œλ•μ…˜ ν™˜κ²½μ—μ„œ μ‚¬μš©ν•˜λŠ” 것은 ꢌμž₯λ˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€. sourcemapsκ°€ ν™œμ„±ν™”λ˜λ©΄ Angular ν”„λ‘œμ νŠΈμ˜ μ›λž˜ μƒνƒœλ₯Ό λ³΅μ œν•˜μ—¬ 가독성을 높이고 파일 뢄석에 도움이 λ©λ‹ˆλ‹€. κ·ΈλŸ¬λ‚˜ λΉ„ν™œμ„±ν™”λœ 경우, κ²€ν† μžλŠ” λ³΄μ•ˆ νŒ¨ν„΄μ„ κ²€μƒ‰ν•˜μ—¬ 컴파일된 JavaScript νŒŒμΌμ„ μˆ˜λ™μœΌλ‘œ 뢄석할 수 μžˆμŠ΅λ‹ˆλ‹€.

λ˜ν•œ, Angular ν”„λ‘œμ νŠΈμ˜ 컴파일된 JavaScript νŒŒμΌμ€ λΈŒλΌμš°μ € 개발자 도ꡬ β†’ Sources (λ˜λŠ” Debugger and Sources) β†’ [id].main.jsμ—μ„œ 찾을 수 μžˆμŠ΅λ‹ˆλ‹€. ν™œμ„±ν™”λœ μ˜΅μ…˜μ— 따라 이 파일의 끝에 //# sourceMappingURL=[id].main.js.map 행이 포함될 수 있으며, hidden μ˜΅μ…˜μ΄ true둜 μ„€μ •λœ 경우 ν¬ν•¨λ˜μ§€ μ•Šμ„ 수 μžˆμŠ΅λ‹ˆλ‹€. κ·ΈλŸΌμ—λ„ λΆˆκ΅¬ν•˜κ³  scripts에 λŒ€ν•΄ sourcemap이 λΉ„ν™œμ„±ν™”λ˜λ©΄ ν…ŒμŠ€νŠΈκ°€ 더 λ³΅μž‘ν•΄μ§€κ³  νŒŒμΌμ„ 얻을 수 μ—†μŠ΅λ‹ˆλ‹€. λ˜ν•œ, ν”„λ‘œμ νŠΈ λΉŒλ“œ 쀑에 ng build --source-map와 같이 sourcemap을 ν™œμ„±ν™”ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

데이터 바인딩

바인딩은 ꡬ성 μš”μ†Œμ™€ ν•΄λ‹Ή λ·° κ°„μ˜ 톡신 ν”„λ‘œμ„ΈμŠ€λ₯Ό λ‚˜νƒ€λƒ…λ‹ˆλ‹€. μ΄λŠ” Angular ν”„λ ˆμž„μ›Œν¬μ™€ 데이터 전솑에 μ‚¬μš©λ©λ‹ˆλ‹€. λ°μ΄ν„°λŠ” 이벀트, 보간, 속성 λ˜λŠ” μ–‘λ°©ν–₯ 바인딩 λ©”μ»€λ‹ˆμ¦˜μ„ 톡해 전달될 수 μžˆμŠ΅λ‹ˆλ‹€. λ˜ν•œ, λ°μ΄ν„°λŠ” κ΄€λ ¨ ꡬ성 μš”μ†Œ(λΆ€λͺ¨-μžμ‹ 관계) κ°„ 및 두 개의 κ΄€λ ¨ μ—†λŠ” ꡬ성 μš”μ†Œ 간에 Service κΈ°λŠ₯을 μ‚¬μš©ν•˜μ—¬ 곡유될 수 μžˆμŠ΅λ‹ˆλ‹€.

바인딩은 데이터 흐름에 따라 λΆ„λ₯˜ν•  수 μžˆμŠ΅λ‹ˆλ‹€:

  • 데이터 μ†ŒμŠ€μ—μ„œ λ·° λŒ€μƒκΉŒμ§€ (포함 interpolation, properties, attributes, classes 및 styles); ν…œν”Œλ¦Ώμ—μ„œ [] λ˜λŠ” {{}}λ₯Ό μ‚¬μš©ν•˜μ—¬ μ μš©ν•  수 μžˆμŠ΅λ‹ˆλ‹€;
  • λ·° λŒ€μƒμ—μ„œ 데이터 μ†ŒμŠ€κΉŒμ§€ (포함 events); ν…œν”Œλ¦Ώμ—μ„œ ()λ₯Ό μ‚¬μš©ν•˜μ—¬ μ μš©ν•  수 μžˆμŠ΅λ‹ˆλ‹€;
  • μ–‘λ°©ν–₯; ν…œν”Œλ¦Ώμ—μ„œ [()]λ₯Ό μ‚¬μš©ν•˜μ—¬ μ μš©ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

바인딩은 속성, 이벀트 및 μ†μ„±λΏλ§Œ μ•„λ‹ˆλΌ μ†ŒμŠ€ μ§€μ‹œλ¬Έμ˜ λͺ¨λ“  곡개 λ©€λ²„μ—μ„œ ν˜ΈμΆœν•  수 μžˆμŠ΅λ‹ˆλ‹€:

TYPETARGETEXAMPLES
PropertyElement property, Component property, Directive property<img [alt]=β€œhero.name” [src]=β€œheroImageUrl”>
EventElement event, Component event, Directive event<button type=β€œbutton” (click)=β€œonSave()”>Save
Two-wayEvent and property<input [(ngModel)]=β€œname”>
AttributeAttribute (the exception)<button type=β€œbutton” [attr.aria-label]=β€œhelp”>help
Classclass property<div [class.special]=β€œisSpecial”>Special
Stylestyle property<button type=β€œbutton” [style.color]=β€œisSpecial ? β€˜red’ : β€˜green’”>

Angular λ³΄μ•ˆ λͺ¨λΈ

Angular의 μ„€κ³„λŠ” 기본적으둜 λͺ¨λ“  데이터λ₯Ό μΈμ½”λ”©ν•˜κ±°λ‚˜ μ •ν™”ν•˜μ—¬ XSS 취약점을 λ°œκ²¬ν•˜κ³  μ•…μš©ν•˜κΈ° 점점 더 μ–΄λ ΅κ²Œ λ§Œλ“­λ‹ˆλ‹€. 데이터 μ²˜λ¦¬μ—λŠ” 두 κ°€μ§€ λšœλ ·ν•œ μ‹œλ‚˜λ¦¬μ˜€κ°€ μžˆμŠ΅λ‹ˆλ‹€:

  1. 보간 λ˜λŠ” {{user_input}} - μ»¨ν…μŠ€νŠΈμ— λ―Όκ°ν•œ 인코딩을 μˆ˜ν–‰ν•˜κ³  μ‚¬μš©μž μž…λ ₯을 ν…μŠ€νŠΈλ‘œ ν•΄μ„ν•©λ‹ˆλ‹€;
//app.component.ts
test = "<script>alert(1)</script><h1>test</h1>";

//app.component.html
{{test}}

κ²°κ³Ό: &lt;script&gt;alert(1)&lt;/script&gt;&lt;h1&gt;test&lt;/h1&gt; 2. 속성, 속성, 클래슀 및 μŠ€νƒ€μΌμ— 바인딩 λ˜λŠ” [attribute]="user_input" - 제곡된 λ³΄μ•ˆ μ»¨ν…μŠ€νŠΈμ— 따라 μ •ν™”λ₯Ό μˆ˜ν–‰ν•©λ‹ˆλ‹€.

//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둜 해석할 λ•Œ μ‚¬μš©λ©λ‹ˆλ‹€;
  • STYLE은 style 속성에 CSSλ₯Ό 바인딩할 λ•Œ μ‚¬μš©λ©λ‹ˆλ‹€;
  • URL은 <a href>와 같은 URL 속성에 μ‚¬μš©λ©λ‹ˆλ‹€;
  • SCRIPTλŠ” JavaScript μ½”λ“œμ— μ‚¬μš©λ©λ‹ˆλ‹€;
  • RESOURCE_URL은 μ½”λ“œλ‘œ λ‘œλ“œλ˜κ³  μ‹€ν–‰λ˜λŠ” URL둜, 예λ₯Ό λ“€μ–΄ <script src>μ—μ„œ μ‚¬μš©λ©λ‹ˆλ‹€.

취약점

λ³΄μ•ˆ μ‹ λ’° 우회 방법

AngularλŠ” κΈ°λ³Έ μ •ν™” ν”„λ‘œμ„ΈμŠ€λ₯Ό μš°νšŒν•˜κ³  νŠΉμ • μ»¨ν…μŠ€νŠΈμ—μ„œ 값이 μ•ˆμ „ν•˜κ²Œ μ‚¬μš©λ  수 μžˆμŒμ„ λ‚˜νƒ€λ‚΄κΈ° μœ„ν•΄ λ‹€μŒ λ‹€μ„― κ°€μ§€ μ˜ˆμ™€ 같은 방법 λͺ©λ‘μ„ λ„μž…ν•©λ‹ˆλ‹€:

  1. bypassSecurityTrustUrl은 μ£Όμ–΄μ§„ 값이 μ•ˆμ „ν•œ μŠ€νƒ€μΌ URLμž„μ„ λ‚˜νƒ€λ‚΄λŠ” 데 μ‚¬μš©λ©λ‹ˆλ‹€:
//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>
  1. bypassSecurityTrustResourceUrl은 μ£Όμ–΄μ§„ 값이 μ•ˆμ „ν•œ λ¦¬μ†ŒμŠ€ URLμž„μ„ λ‚˜νƒ€λ‚΄λŠ” 데 μ‚¬μš©λ©λ‹ˆλ‹€:
//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">
  1. bypassSecurityTrustHtml은 μ£Όμ–΄μ§„ 값이 μ•ˆμ „ν•œ HTMLμž„μ„ λ‚˜νƒ€λ‚΄λŠ” 데 μ‚¬μš©λ©λ‹ˆλ‹€. 이 λ°©λ²•μœΌλ‘œ DOM νŠΈλ¦¬μ— script μš”μ†Œλ₯Ό μ‚½μž…ν•˜λ©΄ ν¬ν•¨λœ JavaScript μ½”λ“œκ°€ μ‹€ν–‰λ˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€.
//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>
  1. bypassSecurityTrustScriptλŠ” μ£Όμ–΄μ§„ 값이 μ•ˆμ „ν•œ JavaScriptμž„μ„ λ‚˜νƒ€λ‚΄λŠ” 데 μ‚¬μš©λ©λ‹ˆλ‹€. κ·ΈλŸ¬λ‚˜ 이 방법을 μ‚¬μš©ν•˜μ—¬ ν…œν”Œλ¦Ώμ—μ„œ JS μ½”λ“œλ₯Ό μ‹€ν–‰ν•  수 μ—†κΈ° λ•Œλ¬Έμ— κ·Έ λ™μž‘μ΄ μ˜ˆμΈ‘ν•  수 μ—†μŒμ„ λ°œκ²¬ν–ˆμŠ΅λ‹ˆλ‹€.
//app.component.ts
this.trustedScript = this.sanitizer.bypassSecurityTrustScript("alert('bypass Security TrustScript')");

//app.component.html
<script [innerHtml]="trustedScript"></script>

//result
-
  1. bypassSecurityTrustStyle은 μ£Όμ–΄μ§„ 값이 μ•ˆμ „ν•œ CSSμž„μ„ λ‚˜νƒ€λ‚΄λŠ” 데 μ‚¬μš©λ©λ‹ˆλ‹€. λ‹€μŒ μ˜ˆλŠ” CSS μ£Όμž…μ„ μ„€λͺ…ν•©λ‹ˆλ‹€:
//app.component.ts
this.trustedStyle = this.sanitizer.bypassSecurityTrustStyle('background-image: url(https://example.com/exfil/a)');

//app.component.html
<input type="password" name="pwd" value="01234" [style]="trustedStyle">

//result
Request URL: GET example.com/exfil/a

AngularλŠ” 뷰에 ν‘œμ‹œν•˜κΈ° 전에 데이터λ₯Ό μ •ν™”ν•˜κΈ° μœ„ν•΄ sanitize λ©”μ„œλ“œλ₯Ό μ œκ³΅ν•©λ‹ˆλ‹€. 이 λ©”μ„œλ“œλŠ” 제곡된 λ³΄μ•ˆ μ»¨ν…μŠ€νŠΈλ₯Ό μ‚¬μš©ν•˜κ³  μž…λ ₯을 μ μ ˆν•˜κ²Œ μ •ν™”ν•©λ‹ˆλ‹€. κ·ΈλŸ¬λ‚˜ νŠΉμ • 데이터 및 μ»¨ν…μŠ€νŠΈμ— λŒ€ν•΄ μ˜¬λ°”λ₯Έ λ³΄μ•ˆ μ»¨ν…μŠ€νŠΈλ₯Ό μ‚¬μš©ν•˜λŠ” 것이 μ€‘μš”ν•©λ‹ˆλ‹€. 예λ₯Ό λ“€μ–΄, HTML μ½˜ν…μΈ μ— SecurityContext.URL을 μ μš©ν•˜λ©΄ μœ„ν—˜ν•œ HTML 값에 λŒ€ν•œ 보호λ₯Ό μ œκ³΅ν•˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€. μ΄λŸ¬ν•œ μ‹œλ‚˜λ¦¬μ˜€μ—μ„œ λ³΄μ•ˆ μ»¨ν…μŠ€νŠΈμ˜ μ˜€μš©μ€ XSS μ·¨μ•½μ μœΌλ‘œ μ΄μ–΄μ§ˆ 수 μžˆμŠ΅λ‹ˆλ‹€.

HTML μ£Όμž…

이 취약점은 μ‚¬μš©μž μž…λ ₯이 innerHTML, outerHTML λ˜λŠ” iframe srcdoc의 μ„Έ κ°€μ§€ 속성 쀑 ν•˜λ‚˜μ— 바인딩될 λ•Œ λ°œμƒν•©λ‹ˆλ‹€. μ΄λŸ¬ν•œ 속성에 λ°”μΈλ”©ν•˜λ©΄ HTML이 κ·ΈλŒ€λ‘œ ν•΄μ„λ˜λ©°, μž…λ ₯은 SecurityContext.HTML을 μ‚¬μš©ν•˜μ—¬ μ •ν™”λ©λ‹ˆλ‹€. λ”°λΌμ„œ HTML μ£Όμž…μ€ κ°€λŠ₯ν•˜μ§€λ§Œ ꡐ차 μ‚¬μ΄νŠΈ μŠ€ν¬λ¦½νŒ…(XSS)은 λΆˆκ°€λŠ₯ν•©λ‹ˆλ‹€.

innerHTML을 μ‚¬μš©ν•˜λŠ” 예:

//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>

test

ν…œν”Œλ¦Ώ μ£Όμž…

ν΄λΌμ΄μ–ΈνŠΈ μΈ‘ λ Œλ”λ§ (CSR)

AngularλŠ” ν…œν”Œλ¦Ώμ„ ν™œμš©ν•˜μ—¬ νŽ˜μ΄μ§€λ₯Ό λ™μ μœΌλ‘œ κ΅¬μ„±ν•©λ‹ˆλ‹€. 이 μ ‘κ·Ό 방식은 Angularκ°€ 평가할 ν…œν”Œλ¦Ώ ν‘œν˜„μ‹μ„ 이쀑 μ€‘κ΄„ν˜Έ({{}})둜 κ°μ‹ΈλŠ” 것을 ν¬ν•¨ν•©λ‹ˆλ‹€. μ΄λ ‡κ²Œ ν•¨μœΌλ‘œμ¨ ν”„λ ˆμž„μ›Œν¬λŠ” μΆ”κ°€ κΈ°λŠ₯을 μ œκ³΅ν•©λ‹ˆλ‹€. 예λ₯Ό λ“€μ–΄, {{1+1}}와 같은 ν…œν”Œλ¦Ώμ€ 2둜 ν‘œμ‹œλ©λ‹ˆλ‹€.

일반적으둜 AngularλŠ” ν…œν”Œλ¦Ώ ν‘œν˜„μ‹κ³Ό ν˜Όλ™λ  수 μžˆλŠ” μ‚¬μš©μž μž…λ ₯을 μ΄μŠ€μΌ€μ΄ν”„ν•©λ‹ˆλ‹€ (예: `< > ’ β€œ ``와 같은 문자). μ΄λŠ” λΈ”λž™λ¦¬μŠ€νŠΈ 문자λ₯Ό μ‚¬μš©ν•˜μ§€ μ•ŠκΈ° μœ„ν•΄ JavaScript λ¬Έμžμ—΄ 객체λ₯Ό μƒμ„±ν•˜λŠ” ν•¨μˆ˜λ₯Ό ν™œμš©ν•˜λŠ” λ“± 이 μ œν•œμ„ μš°νšŒν•˜κΈ° μœ„ν•΄ μΆ”κ°€ 단계가 ν•„μš”ν•¨μ„ μ˜λ―Έν•©λ‹ˆλ‹€. κ·ΈλŸ¬λ‚˜ 이λ₯Ό λ‹¬μ„±ν•˜κΈ° μœ„ν•΄μ„œλŠ” Angular의 μ»¨ν…μŠ€νŠΈ, 속성 및 λ³€μˆ˜λ₯Ό κ³ λ €ν•΄μ•Ό ν•©λ‹ˆλ‹€. λ”°λΌμ„œ ν…œν”Œλ¦Ώ μ£Όμž… 곡격은 λ‹€μŒκ³Ό 같이 λ‚˜νƒ€λ‚  수 μžˆμŠ΅λ‹ˆλ‹€:

//app.component.ts
const _userInput = '{{constructor.constructor(\'alert(1)\'()}}'
@Component({
selector: 'app-root',
template: '<h1>title</h1>' + _userInput
})

μœ„μ—μ„œ μ„€λͺ…ν•œ 바와 같이: constructorλŠ” Object constructor μ†μ„±μ˜ λ²”μœ„λ₯Ό λ‚˜νƒ€λ‚΄λ©°, 이λ₯Ό 톡해 String μƒμ„±μžλ₯Ό ν˜ΈμΆœν•˜κ³  μž„μ˜μ˜ μ½”λ“œλ₯Ό μ‹€ν–‰ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

μ„œλ²„ μ‚¬μ΄λ“œ λ Œλ”λ§ (SSR)

CSRκ³Ό 달리, Angular Universal은 ν…œν”Œλ¦Ώ 파일의 SSR을 λ‹΄λ‹Ήν•©λ‹ˆλ‹€. μ΄λŸ¬ν•œ νŒŒμΌμ€ μ‚¬μš©μžμ—κ²Œ μ „λ‹¬λ©λ‹ˆλ‹€. μ΄λŸ¬ν•œ ꡬ뢄에도 λΆˆκ΅¬ν•˜κ³ , Angular Universal은 SSR λ³΄μ•ˆμ„ κ°•ν™”ν•˜κΈ° μœ„ν•΄ CSRμ—μ„œ μ‚¬μš©λ˜λŠ” λ™μΌν•œ μ„Έμ²™ λ©”μ»€λ‹ˆμ¦˜μ„ μ μš©ν•©λ‹ˆλ‹€. SSRμ—μ„œμ˜ ν…œν”Œλ¦Ώ μ£Όμž… 취약점은 CSRμ—μ„œμ™€ λ™μΌν•œ λ°©μ‹μœΌλ‘œ 발견될 수 있으며, μ‚¬μš©λ˜λŠ” ν…œν”Œλ¦Ώ μ–Έμ–΄κ°€ λ™μΌν•˜κΈ° λ•Œλ¬Έμž…λ‹ˆλ‹€.

λ¬Όλ‘ , Pug 및 Handlebars와 같은 μ„œλ“œνŒŒν‹° ν…œν”Œλ¦Ώ 엔진을 μ‚¬μš©ν•  λ•Œ μƒˆλ‘œμš΄ ν…œν”Œλ¦Ώ μ£Όμž… 취약점이 λ°œμƒν•  κ°€λŠ₯성도 μžˆμŠ΅λ‹ˆλ‹€.

XSS

DOM μΈν„°νŽ˜μ΄μŠ€

μ•žμ„œ μ–ΈκΈ‰ν•œ 바와 같이, μš°λ¦¬λŠ” Document μΈν„°νŽ˜μ΄μŠ€λ₯Ό μ‚¬μš©ν•˜μ—¬ DOM에 직접 μ ‘κ·Όν•  수 μžˆμŠ΅λ‹ˆλ‹€. μ‚¬μš©μž μž…λ ₯이 사전에 κ²€μ¦λ˜μ§€ μ•ŠμœΌλ©΄ ꡐ차 μ‚¬μ΄νŠΈ μŠ€ν¬λ¦½νŒ…(XSS) μ·¨μ•½μ μœΌλ‘œ μ΄μ–΄μ§ˆ 수 μžˆμŠ΅λ‹ˆλ‹€.

μ•„λž˜ μ˜ˆμ œμ—μ„œλŠ” document.write() 및 document.createElement() λ©”μ„œλ“œλ₯Ό μ‚¬μš©ν–ˆμŠ΅λ‹ˆλ‹€:

//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 μ£Όμž… 취약점을 μ΄ˆλž˜ν•  수 μžˆμŠ΅λ‹ˆλ‹€:
//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 λ°©μ§€ λ©”μ»€λ‹ˆμ¦˜μ΄ μ—†μŠ΅λ‹ˆλ‹€.
//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 곡격을 μœ λ°œν•  수 μžˆμŠ΅λ‹ˆλ‹€:
//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λŠ” HTML DOM 객체 μ‘°μž‘μ„ 돕기 μœ„ν•΄ Angular ν”„λ‘œμ νŠΈμ—μ„œ μ‚¬μš©ν•  수 μžˆλŠ” λΉ λ₯΄κ³  μž‘κ³  κΈ°λŠ₯이 ν’λΆ€ν•œ JavaScript λΌμ΄λΈŒλŸ¬λ¦¬μž…λ‹ˆλ‹€. κ·ΈλŸ¬λ‚˜ 이 라이브러리의 λ©”μ„œλ“œλŠ” XSS 취약점을 λ‹¬μ„±ν•˜κΈ° μœ„ν•΄ μ•…μš©λ  수 μžˆλŠ” κ²ƒμœΌλ‘œ μ•Œλ €μ Έ μžˆμŠ΅λ‹ˆλ‹€. μ·¨μ•½ν•œ jQuery λ©”μ„œλ“œκ°€ Angular ν”„λ‘œμ νŠΈμ—μ„œ μ–΄λ–»κ²Œ μ•…μš©λ  수 μžˆλŠ”μ§€ λ…Όμ˜ν•˜κΈ° μœ„ν•΄ 이 ν•˜μœ„ μ„Ήμ…˜μ„ μΆ”κ°€ν–ˆμŠ΅λ‹ˆλ‹€.

  • html() λ©”μ„œλ“œλŠ” μΌμΉ˜ν•˜λŠ” μš”μ†Œ μ§‘ν•©μ˜ 첫 번째 μš”μ†Œμ˜ HTML λ‚΄μš©μ„ κ°€μ Έμ˜€κ±°λ‚˜ λͺ¨λ“  μΌμΉ˜ν•˜λŠ” μš”μ†Œμ˜ HTML λ‚΄μš©μ„ μ„€μ •ν•©λ‹ˆλ‹€. κ·ΈλŸ¬λ‚˜ 섀계상 HTML λ¬Έμžμ—΄μ„ μˆ˜μš©ν•˜λŠ” λͺ¨λ“  jQuery μƒμ„±μž λ˜λŠ” λ©”μ„œλ“œλŠ” 잠재적으둜 μ½”λ“œλ₯Ό μ‹€ν–‰ν•  수 μžˆμŠ΅λ‹ˆλ‹€. μ΄λŠ” <script> νƒœκ·Έμ˜ μ£Όμž… λ˜λŠ” μ½”λ“œλ₯Ό μ‹€ν–‰ν•˜λŠ” HTML μ†μ„±μ˜ μ‚¬μš©μœΌλ‘œ λ°œμƒν•  수 μžˆμŠ΅λ‹ˆλ‹€.
//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 λ…Έλ“œ μ§‘ν•©μœΌλ‘œ λ³€ν™˜ν•˜κΈ° μœ„ν•΄ κΈ°λ³Έ λ©”μ„œλ“œλ₯Ό μ‚¬μš©ν•˜λ©°, 이 λ…Έλ“œλŠ” λ¬Έμ„œμ— μ‚½μž…λ  수 μžˆμŠ΅λ‹ˆλ‹€.
jQuery.parseHTML(data [, context ] [, keepScripts ])

μ•žμ„œ μ–ΈκΈ‰ν–ˆλ“―μ΄ HTML λ¬Έμžμ—΄μ„ μˆ˜μš©ν•˜λŠ” λŒ€λΆ€λΆ„μ˜ jQuery APIλŠ” HTML에 ν¬ν•¨λœ 슀크립트λ₯Ό μ‹€ν–‰ν•©λ‹ˆλ‹€. jQuery.parseHTML() λ©”μ„œλ“œλŠ” keepScriptsκ°€ λͺ…μ‹œμ μœΌλ‘œ trueκ°€ μ•„λ‹Œ ν•œ νŒŒμ‹±λœ HTMLμ—μ„œ 슀크립트λ₯Ό μ‹€ν–‰ν•˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€. κ·ΈλŸ¬λ‚˜ λŒ€λΆ€λΆ„μ˜ ν™˜κ²½μ—μ„œ <img onerror> 속성을 톡해 κ°„μ ‘μ μœΌλ‘œ 슀크립트λ₯Ό μ‹€ν–‰ν•˜λŠ” 것은 μ—¬μ „νžˆ κ°€λŠ₯ν•©λ‹ˆλ‹€.

//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을 μ‚¬μš©ν•˜λŠ” κ²ƒμž…λ‹ˆλ‹€. μ΄λŠ” λΈŒλΌμš°μ €λ₯Ό μƒˆ νŽ˜μ΄μ§€λ‘œ λ¦¬λ””λ ‰μ…˜ν•˜λŠ” 데에도 μ‚¬μš©ν•  수 μžˆμŠ΅λ‹ˆλ‹€. λ”°λΌμ„œ 이 객체에 λŒ€ν•œ μ œμ–΄λ₯Ό κ°€μ§€λ©΄ μ—΄λ¦° λ¦¬λ””λ ‰μ…˜ 취약점을 μ•…μš©ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

//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μ—μ„œ λ¬Έμ„œλ₯Ό λ‘œλ“œν•˜κ³  ν‘œμ‹œν•˜λ„λ‘ 창을 μœ λ„ν•©λ‹ˆλ‹€. 이 λ©”μ„œλ“œμ— λŒ€ν•œ μ œμ–΄λ₯Ό κ°€μ§€λ©΄ μ—΄λ¦° λ¦¬λ””λ ‰μ…˜ 곡격의 싱크가 될 수 μžˆμŠ΅λ‹ˆλ‹€.

//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()λ₯Ό μ‚¬μš©ν•œ ν›„ ν˜„μž¬ νŽ˜μ΄μ§€κ°€ μ„Έμ…˜ 기둝에 μ €μž₯λ˜μ§€ μ•ŠλŠ”λ‹€λŠ” κ²ƒμž…λ‹ˆλ‹€. κ·ΈλŸ¬λ‚˜ 이 λ©”μ„œλ“œμ— λŒ€ν•œ μ œμ–΄λ₯Ό κ°€μ§ˆ λ•Œ μ—΄λ¦° λ¦¬λ””λ ‰μ…˜ 취약점을 μ•…μš©ν•˜λŠ” 것도 κ°€λŠ₯ν•©λ‹ˆλ‹€.

//app.component.ts
...
export class AppComponent {
goToUrl(): void {
window.location.replace("http://google.com/about")
}
}
  • window.open()

window.open() λ©”μ„œλ“œλŠ” URL을 λ°›μ•„ ν•΄λ‹Ή λ¦¬μ†ŒμŠ€λ₯Ό μƒˆ νƒ­μ΄λ‚˜ κΈ°μ‘΄ νƒ­ λ˜λŠ” 창에 λ‘œλ“œν•©λ‹ˆλ‹€. 이 λ©”μ„œλ“œμ— λŒ€ν•œ μ œμ–΄λ₯Ό κ°€μ§€λ©΄ XSS λ˜λŠ” μ—΄λ¦° λ¦¬λ””λ ‰μ…˜ 취약점을 μœ λ°œν•  κΈ°νšŒκ°€ 될 수 μžˆμŠ΅λ‹ˆλ‹€.

//app.component.ts
...
export class AppComponent {
goToUrl(): void {
window.open("https://google.com/about", "_blank")
}
}

Angular 클래슀

  • Angular λ¬Έμ„œμ— λ”°λ₯΄λ©΄, Angular DocumentλŠ” DOM λ¬Έμ„œμ™€ λ™μΌν•˜λ―€λ‘œ Angularμ—μ„œ ν΄λΌμ΄μ–ΈνŠΈ μΈ‘ 취약점을 μ•…μš©ν•˜κΈ° μœ„ν•΄ DOM λ¬Έμ„œμ— λŒ€ν•œ 일반 벑터λ₯Ό μ‚¬μš©ν•  수 μžˆμŠ΅λ‹ˆλ‹€. Document.location 속성과 λ©”μ„œλ“œλŠ” μ•„λž˜ μ˜ˆμ™€ 같이 성곡적인 μ—΄λ¦° λ¦¬λ””λ ‰μ…˜ 곡격의 싱크가 될 수 μžˆμŠ΅λ‹ˆλ‹€:
//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은 μ• ν”Œλ¦¬μΌ€μ΄μ…˜μ΄ λΈŒλΌμš°μ €μ˜ ν˜„μž¬ URLκ³Ό μƒν˜Έμž‘μš©ν•˜λŠ” 데 μ‚¬μš©ν•  수 μžˆλŠ” Angular μ„œλΉ„μŠ€μž…λ‹ˆλ‹€. 이 μ„œλΉ„μŠ€λŠ” μ£Όμ–΄μ§„ URL을 μ‘°μž‘ν•˜λŠ” μ—¬λŸ¬ λ©”μ„œλ“œ - go(), replaceState(), 및 prepareExternalUrl()λ₯Ό κ°€μ§€κ³  μžˆμŠ΅λ‹ˆλ‹€. κ·ΈλŸ¬λ‚˜ μš°λ¦¬λŠ” 이λ₯Ό μ™ΈλΆ€ λ„λ©”μΈμœΌλ‘œ λ¦¬λ””λ ‰μ…˜ν•˜λŠ” 데 μ‚¬μš©ν•  수 μ—†μŠ΅λ‹ˆλ‹€. 예λ₯Ό λ“€μ–΄:
//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 ν΄λž˜μŠ€λŠ” 주둜 λ™μΌν•œ 도메인 λ‚΄μ—μ„œ νƒμƒ‰ν•˜λŠ” 데 μ‚¬μš©λ˜λ©° μ• ν”Œλ¦¬μΌ€μ΄μ…˜μ— 좔가적인 취약점을 λ„μž…ν•˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€:
//app-routing.module.ts
const routes: Routes = [
{ path: '', redirectTo: 'https://google.com', pathMatch: 'full' }]

κ²°κ³Ό: http://localhost:4200/https:

λ‹€μŒ λ©”μ„œλ“œλ„ 도메인 λ²”μœ„ λ‚΄μ—μ„œ νƒμƒ‰ν•©λ‹ˆλ‹€:

const routes: Routes = [ { path: '', redirectTo: 'ROUTE', pathMatch: 'prefix' } ]
this.router.navigate(['PATH'])
this.router.navigateByUrl('URL')

μ°Έκ³  λ¬Έν—Œ

Tip

AWS ν•΄ν‚Ή 배우기 및 μ—°μŠ΅ν•˜κΈ°:HackTricks Training AWS Red Team Expert (ARTE)
GCP ν•΄ν‚Ή 배우기 및 μ—°μŠ΅ν•˜κΈ°: HackTricks Training GCP Red Team Expert (GRTE) Azure ν•΄ν‚Ή 배우기 및 μ—°μŠ΅ν•˜κΈ°: HackTricks Training Azure Red Team Expert (AzRTE)

HackTricks μ§€μ›ν•˜κΈ°