Skip to content

Commit

Permalink
fix: πŸ› prevent html injection via data.uri link rendering
Browse files Browse the repository at this point in the history
Using a `hyperlink` node with e.g. `data.uri = '"><h1>html</h1><a
href="'` it was possible to inject arbitrary HTML next to the actual
hyperlink. We encode `"` within the `href` attribute. The same technique
should be applied for all attributes if added in the future.
  • Loading branch information
DanweDE committed Jul 30, 2021
1 parent a692e8e commit ecae89a
Show file tree
Hide file tree
Showing 4 changed files with 48 additions and 3 deletions.
21 changes: 21 additions & 0 deletions packages/rich-text-html-renderer/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions packages/rich-text-html-renderer/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,9 @@
},
"devDependencies": {
"@types/escape-html": "0.0.20",
"@types/lodash.clonedeep": "^4.5.6",
"jest": "^24.7.1",
"lodash.clonedeep": "^4.5.0",
"rimraf": "^2.6.3",
"rollup": "^1.11.0",
"rollup-plugin-commonjs": "^9.3.4",
Expand Down
21 changes: 19 additions & 2 deletions packages/rich-text-html-renderer/src/__test__/index.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Document, BLOCKS, MARKS, INLINES } from '@contentful/rich-text-types';

import cloneDeep from 'lodash/cloneDeep';
import { Document, Block, BLOCKS, MARKS, INLINES } from '@contentful/rich-text-types';
import { documentToHtmlString, Options } from '../index';
import {
hrDoc,
Expand Down Expand Up @@ -227,6 +227,23 @@ describe('documentToHtmlString', () => {
expect(documentToHtmlString(document)).toEqual(expected);
});

it('renders hyperlink without allowing html injection via `data.uri`', () => {
const document: Document = cloneDeep(hyperlinkDoc);
(document.content[0].content[1] as Block).data.uri = '">no html injection!<a href="';
const expected =
'<p>Some text <a href="&quot;>no html injection!<a href=&quot;">link</a> text.</p>';

expect(documentToHtmlString(document)).toEqual(expected);
});

it('renders hyperlink without invalid non-string `data.uri` values', () => {
const document: Document = cloneDeep(hyperlinkDoc);
(document.content[0].content[1] as Block).data.uri = 42;
const expected = '<p>Some text <a href="">link</a> text.</p>';

expect(documentToHtmlString(document)).toEqual(expected);
});

it(`renders asset hyperlink`, () => {
const asset = {
target: {
Expand Down
7 changes: 6 additions & 1 deletion packages/rich-text-html-renderer/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ import {
} from '@contentful/rich-text-types';
import React from 'react';

const attributeValue = (value: string) => `"${value.replace(/"/g, '&quot;')}"`;

const defaultNodeRenderers: RenderNode = {
[BLOCKS.PARAGRAPH]: (node, next) => `<p>${next(node.content)}</p>`,
[BLOCKS.HEADING_1]: (node, next) => `<h1>${next(node.content)}</h1>`,
Expand All @@ -32,7 +34,10 @@ const defaultNodeRenderers: RenderNode = {
[INLINES.ASSET_HYPERLINK]: node => defaultInline(INLINES.ASSET_HYPERLINK, node as Inline),
[INLINES.ENTRY_HYPERLINK]: node => defaultInline(INLINES.ENTRY_HYPERLINK, node as Inline),
[INLINES.EMBEDDED_ENTRY]: node => defaultInline(INLINES.EMBEDDED_ENTRY, node as Inline),
[INLINES.HYPERLINK]: (node, next) => `<a href="${node.data.uri}">${next(node.content)}</a>`,
[INLINES.HYPERLINK]: (node, next) => {
const href = typeof node.data.uri === 'string' ? node.data.uri : '';
return `<a href=${attributeValue(href)}>${next(node.content)}</a>`;
},
};

const defaultMarkRenderers: RenderMark = {
Expand Down

0 comments on commit ecae89a

Please sign in to comment.