Skip to content

Commit

Permalink
feat: added tests for unsecure content, restricted href to http and h…
Browse files Browse the repository at this point in the history
…ttps.
  • Loading branch information
arnog committed Jan 30, 2025
1 parent 35d7ccc commit 4234bf6
Show file tree
Hide file tree
Showing 4 changed files with 85 additions and 8 deletions.
23 changes: 17 additions & 6 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,22 @@
## [Unreleased]

### Security Advisories

- [**security advisory**](https://github.com/advisories/GHSA-qwj6-q94f-8425)
Untrusted input could be used to inject arbitrary HTML or JavaScript code in a
page using a mathfield or math content rendered by Mathlive that contained an
`\htmlData{}` command with malicious input.

The content of the `\htmlData{}` command is now sanitized and the 🚫 emoji is
displayed instead.

In general, if you are handling untrusted input, you should consider using the
`MathfieldElement.createHTML()` method to sanitize content. The `createHTML()`
method follows the recommendations from the
[Trusted Type](https://www.w3.org/TR/trusted-types/) specification.

- The `\href` command now only allows URLs with the `http` or `https` protocol.

### Issues Resolved

- Generate only standard trigonometric functions, i.e. those available in the
Expand Down Expand Up @@ -67,12 +84,6 @@ For example:
- **#2576** The command `\perp` was mapped to the wrong symbol (U+22A5). It is
now mapped to the correct symbol (U+27C2)

- [**security advisory**](https://github.com/advisories/GHSA-qwj6-q94f-8425)
Untrusted input could be used to inject arbitrary HTML or JavaScript code in a
page using a mathfield or math content rendered by Mathlive that contained an
`\htmlData{}` command with malicious input. The content of the `\htmlData{}`
command is now sanitized and the 🚫 emoji is displayed instead.

## 0.103.0 _2024-12-10_

### Issues Resolved
Expand Down
12 changes: 10 additions & 2 deletions src/core/box.ts
Original file line number Diff line number Diff line change
Expand Up @@ -456,8 +456,16 @@ export class Box implements BoxInterface {
const matched = entry.match(/([^=]+)=(.+$)/);
if (matched) {
const key = sanitizeAttributeName(matched[1]);
if (key)
props.push(`data-${key}=${sanitizeAttributeValue(matched[2])}`);
if (key) {
if (key === 'href') {
// Validate the URL
const url = new URL(matched[2]);
if (url.protocol !== 'http:' && url.protocol !== 'https:')
throw new Error(`Invalid URL: ${matched[2]}`);
props.push(`href="${matched[2].replace(/"/g, '"')}"`);
} else
props.push(`data-${key}=${sanitizeAttributeValue(matched[2])}`);
}
} else {
const key = sanitizeAttributeName(entry);
if (key) props.push(`data-${key} `);
Expand Down
2 changes: 2 additions & 0 deletions src/latex-commands/styling.ts
Original file line number Diff line number Diff line change
Expand Up @@ -655,7 +655,9 @@ defineFunction('href', '{url:string}{content:auto}', {
render: (atom, context) => {
const box = atom.createBox(context);
const href = (atom.args![0] as string) ?? '';

if (href) box.htmlData = `href=${href}`;

return box;
},
});
Expand Down
56 changes: 56 additions & 0 deletions test/security.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { convertLatexToMarkup } from '../src/public/mathlive-ssr';

//
// Validate that unsecure input is blocked
//
test('Unsecure Content', () => {
expect(() =>
convertLatexToMarkup('\\href{javascript:alert(1)}{Click me}')
).toThrow();

expect(() =>
convertLatexToMarkup('\\htmlData{><img/onerror=alert(1)"src=}{x}')
).toThrow();

expect(
convertLatexToMarkup('\\htmlData{x=" ><img/onerror=alert(1) src>}{x}')
).toMatch(
'<span class="ML__latex"><span class="ML__strut" style="height:0.44em"></span><span class="ML__base"><span data-x="&quot; ><img/onerror=alert(1) src>"><span class="ML__mathit">x</span></span></span></span>'
);

// href via htmlData is blocked (unless it's a safe URL)
expect(() =>
convertLatexToMarkup('\\htmlData{href="javascript:alert(1)"}{x}')
).toThrow();

expect(() =>
convertLatexToMarkup('\\href{javascript:alert(1)}{x}')
).toThrow();

expect(() =>
convertLatexToMarkup(
'\\href{data:application/json;base64,eyJmb28iOiJiYXIifQ==}{x}'
)
).toThrow();

expect(() =>
convertLatexToMarkup(
'\\href{\t\t\tj\ta\tv\ta\ts\tc\tr\ti\tp\tt:alert(1)}{x}'
)
).toThrow();
});

//
// Secure Content
//
test('Secure Content', () => {
// http and https protocols are allowed with \href
expect(convertLatexToMarkup('\\href{http://example.com}{x}')).toMatch(
'<span class="ML__latex"><span class="ML__strut" style="height:0.44em"></span><span class="ML__base"><span href="http://example.com"><span class="ML__mathit">x</span></span></span></span>'
);

// Gets turned into a data-onerror attribute, so safe
expect(convertLatexToMarkup('\\htmlData{onerror=alert(1)}{x}')).toMatch(
'<span class="ML__latex"><span class="ML__strut" style="height:0.44em"></span><span class="ML__base"><span data-onerror="alert(1)"><span class="ML__mathit">x</span></span></span></span>'
);
});

0 comments on commit 4234bf6

Please sign in to comment.