Skip to content

Commit 224e60e

Browse files
crisbetoatscott
authored andcommitted
fix(core): sanitize translated attribute bindings with interpolations
Fixes that we weren't sanitizing attribute bindings with interpolations if they're marked for translation, for example: `<a href="{{evilLink}}" i18n-href></a>`. Also adds a bit more test coverage for our sanitization. (cherry picked from commit 8630319)
1 parent 492e756 commit 224e60e

File tree

2 files changed

+74
-13
lines changed

2 files changed

+74
-13
lines changed

packages/core/src/render3/i18n/i18n_parse.ts

Lines changed: 9 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -388,7 +388,7 @@ export function i18nAttributesFirstPass(tView: TView, index: number, values: str
388388
previousElementIndex,
389389
attrName,
390390
countBindings(updateOpCodes),
391-
null,
391+
URI_ATTRS[attrName.toLowerCase()] ? _sanitizeUrl : null,
392392
);
393393
}
394394
}
@@ -810,18 +810,14 @@ function walkIcuTree(
810810
const hasBinding = !!attr.value.match(BINDING_REGEXP);
811811
if (hasBinding) {
812812
if (VALID_ATTRS.hasOwnProperty(lowerAttrName)) {
813-
if (URI_ATTRS[lowerAttrName]) {
814-
generateBindingUpdateOpCodes(
815-
update,
816-
attr.value,
817-
newIndex,
818-
attr.name,
819-
0,
820-
_sanitizeUrl,
821-
);
822-
} else {
823-
generateBindingUpdateOpCodes(update, attr.value, newIndex, attr.name, 0, null);
824-
}
813+
generateBindingUpdateOpCodes(
814+
update,
815+
attr.value,
816+
newIndex,
817+
attr.name,
818+
0,
819+
URI_ATTRS[lowerAttrName] ? _sanitizeUrl : null,
820+
);
825821
} else {
826822
ngDevMode &&
827823
console.warn(

packages/core/test/acceptance/i18n_spec.ts

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3534,6 +3534,71 @@ describe('runtime i18n', () => {
35343534
'translatedText value',
35353535
);
35363536
});
3537+
3538+
describe('attribute sanitization', () => {
3539+
@Component({template: ''})
3540+
class SanitizeAppComp {
3541+
url = 'javascript:alert("oh no")';
3542+
count = 0;
3543+
}
3544+
3545+
it('should sanitize translated attribute binding', () => {
3546+
const fixture = initWithTemplate(SanitizeAppComp, '<a [attr.href]="url" i18n-href></a>');
3547+
const link: HTMLAnchorElement = fixture.nativeElement.querySelector('a');
3548+
expect(link.getAttribute('href')).toMatch(/^unsafe:/);
3549+
});
3550+
3551+
it('should sanitize translated property binding', () => {
3552+
const fixture = initWithTemplate(SanitizeAppComp, '<a [href]="url" i18n-href></a>');
3553+
const link: HTMLAnchorElement = fixture.nativeElement.querySelector('a');
3554+
expect(link.getAttribute('href')).toMatch(/^unsafe:/);
3555+
});
3556+
3557+
it('should sanitize translated interpolation', () => {
3558+
const fixture = initWithTemplate(SanitizeAppComp, '<a href="{{url}}" i18n-href></a>');
3559+
const link: HTMLAnchorElement = fixture.nativeElement.querySelector('a');
3560+
expect(link.getAttribute('href')).toMatch(/^unsafe:/);
3561+
});
3562+
3563+
it('should sanitize interpolation inside translated element', () => {
3564+
const fixture = initWithTemplate(SanitizeAppComp, `<div i18n><a href="{{url}}"></a></div>`);
3565+
const link: HTMLAnchorElement = fixture.nativeElement.querySelector('a');
3566+
expect(link.getAttribute('href')).toMatch(/^unsafe:/);
3567+
});
3568+
3569+
it('should sanitize attribute binding inside translated element', () => {
3570+
const fixture = initWithTemplate(
3571+
SanitizeAppComp,
3572+
`<div i18n><a [attr.href]="url"></a></div>`,
3573+
);
3574+
const link: HTMLAnchorElement = fixture.nativeElement.querySelector('a');
3575+
expect(link.getAttribute('href')).toMatch(/^unsafe:/);
3576+
});
3577+
3578+
it('should sanitize property binding inside translated element', () => {
3579+
const fixture = initWithTemplate(SanitizeAppComp, `<div i18n><a [href]="url"></a></div>`);
3580+
const link: HTMLAnchorElement = fixture.nativeElement.querySelector('a');
3581+
expect(link.getAttribute('href')).toMatch(/^unsafe:/);
3582+
});
3583+
3584+
it('should sanitize property binding inside an ICU', () => {
3585+
const fixture = initWithTemplate(
3586+
SanitizeAppComp,
3587+
`<div i18n>{count, plural,
3588+
=0 {no <strong>link</strong> yet}
3589+
other {{{count}} Here is the <a href="{{url}}">link</a>!}
3590+
}</div>`,
3591+
);
3592+
3593+
expect(fixture.nativeElement.querySelector('a')).toBeFalsy();
3594+
3595+
fixture.componentInstance.count = 1;
3596+
fixture.detectChanges();
3597+
const link: HTMLAnchorElement = fixture.nativeElement.querySelector('a');
3598+
expect(link).toBeTruthy();
3599+
expect(link.getAttribute('href')).toMatch(/^unsafe:/);
3600+
});
3601+
});
35373602
});
35383603

35393604
function initWithTemplate(compType: Type<any>, template: string) {

0 commit comments

Comments
 (0)