Skip to content

Commit 1cf43de

Browse files
AndrewKushnirPawel Kozlowski
authored andcommitted
fix(common): sanitize rawSrc and rawSrcset values in NgOptimizedImage directive (#47082)
This commit applies a sanitization to values produced by a loader, before they are used for the `src` and `srcset` image element properties. PR Close #47082
1 parent 3774d84 commit 1cf43de

File tree

2 files changed

+60
-3
lines changed

2 files changed

+60
-3
lines changed

packages/common/src/directives/ng_optimized_image/ng_optimized_image.ts

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
* found in the LICENSE file at https://angular.io/license
77
*/
88

9-
import {Directive, ElementRef, Inject, Injectable, Injector, Input, NgModule, NgZone, OnChanges, OnDestroy, OnInit, Renderer2, SimpleChanges, ɵformatRuntimeError as formatRuntimeError, ɵRuntimeError as RuntimeError} from '@angular/core';
9+
import {Directive, ElementRef, Inject, Injector, Input, NgModule, NgZone, OnChanges, OnDestroy, OnInit, Renderer2, SimpleChanges, ɵ_sanitizeUrl as sanitizeUrl, ɵRuntimeError as RuntimeError} from '@angular/core';
1010

1111
import {RuntimeErrorCode} from '../../errors';
1212

@@ -161,9 +161,15 @@ export class NgOptimizedImage implements OnInit, OnChanges, OnDestroy {
161161
}
162162
this.setHostAttribute('loading', this.getLoadingBehavior());
163163
this.setHostAttribute('fetchpriority', this.getFetchPriority());
164+
165+
// Use the `sanitizeUrl` function directly (vs using `DomSanitizer`),
166+
// to make the code more tree-shakable (avoid referencing the entire class).
167+
// The same function is used when regular `src` is used on an `<img>` element.
168+
const src = sanitizeUrl(this.getRewrittenSrc());
169+
164170
// The `src` and `srcset` attributes should be set last since other attributes
165171
// could affect the image's loading behavior.
166-
this.setHostAttribute('src', this.getRewrittenSrc());
172+
this.setHostAttribute('src', src);
167173
if (this.rawSrcset) {
168174
this.setHostAttribute('srcset', this.getRewrittenSrcset());
169175
}
@@ -204,7 +210,8 @@ export class NgOptimizedImage implements OnInit, OnChanges, OnDestroy {
204210
const finalSrcs = this.rawSrcset.split(',').filter(src => src !== '').map(srcStr => {
205211
srcStr = srcStr.trim();
206212
const width = widthSrcSet ? parseFloat(srcStr) : parseFloat(srcStr) * this.width!;
207-
return `${this.imageLoader({src: this.rawSrc, width})} ${srcStr}`;
213+
const imgSrc = sanitizeUrl(this.imageLoader({src: this.rawSrc, width}));
214+
return `${imgSrc} ${srcStr}`;
208215
});
209216
return finalSrcs.join(', ');
210217
}

packages/common/test/directives/ng_optimized_image_spec.ts

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -472,6 +472,56 @@ describe('Image directive', () => {
472472
});
473473
});
474474

475+
describe('sanitization', () => {
476+
// Loader function that just returns provided `src`.
477+
const noopLoader = (config: ImageLoaderConfig) => config.src;
478+
479+
it('should apply sanitization to the `rawSrc` with a static value', () => {
480+
setupTestingModule({imageLoader: noopLoader});
481+
482+
const template =
483+
'<img rawSrc="javascript:alert(`Unsafe code execution`)" width="50" height="50">';
484+
const fixture = createTestComponent(template);
485+
fixture.detectChanges();
486+
487+
const img = fixture.nativeElement.querySelector('img')!;
488+
// The `src` looks like this: 'unsafe:javascript:alert(`Unsafe code execution`)'
489+
expect(img.src.startsWith('unsafe:')).toBe(true);
490+
});
491+
492+
it('should apply sanitization to the `rawSrc` when used as a binding', () => {
493+
setupTestingModule({imageLoader: noopLoader});
494+
495+
const template =
496+
'<img [rawSrc]="\'javascript:alert(`Unsafe code execution`)\'" width="50" height="50">';
497+
const fixture = createTestComponent(template);
498+
fixture.detectChanges();
499+
500+
const img = fixture.nativeElement.querySelector('img')!;
501+
// The `src` looks like this: 'unsafe:javascript:alert(`Unsafe code execution`)'
502+
expect(img.src.startsWith('unsafe:')).toBe(true);
503+
});
504+
505+
it('should apply sanitization to the `rawSrcset` value', () => {
506+
setupTestingModule({imageLoader: noopLoader});
507+
508+
const template =
509+
`<img rawSrc="javascript:alert(\`Unsafe code execution\`)" rawSrcset="100w, 200w" width="100" height="50">`;
510+
const fixture = createTestComponent(template);
511+
fixture.detectChanges();
512+
513+
const nativeElement = fixture.nativeElement as HTMLElement;
514+
const img = nativeElement.querySelector('img')!;
515+
516+
// The `src` looks like this: 'unsafe:javascript:alert(`Unsafe code execution`)'
517+
expect(img.src.startsWith('unsafe:')).toBe(true);
518+
expect(img.srcset)
519+
.toBe(
520+
'unsafe:javascript:alert(`Unsafe code execution`) 100w, ' +
521+
'unsafe:javascript:alert(`Unsafe code execution`) 200w');
522+
});
523+
});
524+
475525
describe('fetch priority', () => {
476526
it('should be "high" for priority images', () => {
477527
setupTestingModule();

0 commit comments

Comments
 (0)