Skip to content

Commit

Permalink
Merge pull request from GHSA-x3vm-38hw-55wf
Browse files Browse the repository at this point in the history
CSS Injection security issue
  • Loading branch information
knsv committed Jun 28, 2022
2 parents 1410bad + 5110e42 commit 0ae1bdb
Show file tree
Hide file tree
Showing 7 changed files with 155 additions and 4 deletions.
10 changes: 10 additions & 0 deletions cypress/e2e/other/ghsa.spec.js
@@ -0,0 +1,10 @@
import { urlSnapshotTest } from '../../helpers/util';

describe('CSS injections', () => {
it('should not allow CSS injections outside of the diagram', () => {
urlSnapshotTest('http://localhost:9000/ghsa1.html', {
logLevel: 1,
flowchart: { htmlLabels: false },
});
});
});
50 changes: 50 additions & 0 deletions cypress/helpers/util.js
Expand Up @@ -70,6 +70,56 @@ export const imgSnapshotTest = (graphStr, _options, api = false, validation) =>
}
};

export const urlSnapshotTest = (url, _options, api = false, validation) => {
cy.log(_options);
const options = Object.assign(_options);
if (!options.fontFamily) {
options.fontFamily = 'courier';
}
if (!options.sequence) {
options.sequence = {};
}
if (!options.sequence || (options.sequence && !options.sequence.actorFontFamily)) {
options.sequence.actorFontFamily = 'courier';
}
if (options.sequence && !options.sequence.noteFontFamily) {
options.sequence.noteFontFamily = 'courier';
}
options.sequence.actorFontFamily = 'courier';
options.sequence.noteFontFamily = 'courier';
options.sequence.messageFontFamily = 'courier';
if (options.sequence && !options.sequence.actorFontFamily) {
options.sequence.actorFontFamily = 'courier';
}
if (!options.fontSize) {
options.fontSize = '16px';
}
const useAppli = Cypress.env('useAppli');
const branch = Cypress.env('codeBranch');
cy.log('Hello ' + useAppli ? 'Appli' : 'image-snapshot');
const name = (options.name || cy.state('runnable').fullTitle()).replace(/\s+/g, '-');

if (useAppli) {
cy.eyesOpen({
appName: 'Mermaid-' + branch,
testName: name,
batchName: branch,
});
}

cy.visit(url);
if (validation) cy.get('svg').should(validation);
cy.get('body');
// Default name to test title

if (useAppli) {
cy.eyesCheckWindow('Click!');
cy.eyesClose();
} else {
cy.matchImageSnapshot(name);
}
};

export const renderGraph = (graphStr, options, api) => {
const url = mermaidUrl(graphStr, options, api);

Expand Down
28 changes: 28 additions & 0 deletions cypress/platform/ghsa1.html
@@ -0,0 +1,28 @@
<html>
<script>
// %%{ init: { "logLevel":0, "themeVariables" : { "primaryColor": "#fff000","textColor": "green","apa":"} #target { background-color: crimson }" } } }%%
</script>
<body>
<div id="target">
<h1>This element does not belong to the SVG but we can style it</h1>
</div>
<svg id="diagram">
</svg>

<script src="./mermaid.js"></script>
<script>
mermaid.initialize({ startOnLoad: false, logLevel: 0 });

const graph = `
%%{ init: { "themeVariables" : { "textColor": "green;} #target { background-color: crimson }", "mainBkg": "#fff000" } } }%%
graph TD
A[Goose]
`;

const diagram = document.getElementById('diagram');
const svg = mermaid.render('diagram-svg', graph);
diagram.innerHTML = svg;
</script>
</body>

</html>
28 changes: 28 additions & 0 deletions cypress/platform/ghsa2.html
@@ -0,0 +1,28 @@
<html>
<script>
// %%{ init: { "logLevel":0, "themeVariables" : { "primaryColor": "#fff000","textColor": "green","apa":"} #target { background-color: crimson }" } } }%%
</script>
<body>
<div id="target">
<h1>This element does not belong to the SVG but we can style it</h1>
</div>
<svg id="diagram">
</svg>

<script src="./mermaid.js"></script>
<script>
mermaid.initialize({ startOnLoad: false, logLevel: 0 });

const graph = `
%%{ init: { "fontFamily" : "&125; * { background: red }" } }%%
graph TD
A[Goose]
`;

const diagram = document.getElementById('diagram');
const svg = mermaid.render('diagram-svg', graph);
diagram.innerHTML = svg;
</script>
</body>

</html>
2 changes: 2 additions & 0 deletions src/mermaidAPI.js
Expand Up @@ -385,6 +385,8 @@ const render = function (id, _txt, cb, container) {

let userStyles = '';
// user provided theme CSS
// If you add more configuration driven data into the user styles make sure that the value is
// sanitized bye the santiizeCSS function
if (cnf.themeCSS !== undefined) {
userStyles += `\n${cnf.themeCSS}`;
}
Expand Down
6 changes: 5 additions & 1 deletion src/styles.js
Expand Up @@ -10,6 +10,7 @@ import sequence from './diagrams/sequence/styles';
import stateDiagram from './diagrams/state/styles';
import journey from './diagrams/user-journey/styles';
import c4 from './diagrams/c4/styles';
import { log } from './logger';

const themes = {
flowchart,
Expand All @@ -30,7 +31,10 @@ const themes = {
c4,
};

export const calcThemeVariables = (theme, userOverRides) => theme.calcColors(userOverRides);
export const calcThemeVariables = (theme, userOverRides) => {
log.info('userOverides', userOverRides);
return theme.calcColors(userOverRides);
};

const getStyles = (type, userStyles, options) => {
return ` {
Expand Down
35 changes: 32 additions & 3 deletions src/utils.js
Expand Up @@ -1032,6 +1032,14 @@ export const directiveSanitizer = (args) => {
log.debug('sanitizing themeCss option');
args[key] = sanitizeCss(args[key]);
}
if (key.indexOf('fontFamily') >= 0) {
log.debug('sanitizing fontFamily option');
args[key] = sanitizeCss(args[key]);
}
if (key.indexOf('altFontFamily') >= 0) {
log.debug('sanitizing altFontFamily option');
args[key] = sanitizeCss(args[key]);
}
if (configKeys.indexOf(key) < 0) {
log.debug('sanitize deleting option', key);
delete args[key];
Expand All @@ -1044,11 +1052,32 @@ export const directiveSanitizer = (args) => {
});
}
}
if (args.themeVariables) {
const kArr = Object.keys(args.themeVariables);
for (let i = 0; i < kArr.length; i++) {
const k = kArr[i];
const val = args.themeVariables[k];
if (val && val.match && !val.match(/^[a-zA-Z0-9#,";()%. ]+$/)) {
args.themeVariables[k] = '';
}
}
}
log.debug('After sanitization', args);
};
export const sanitizeCss = (str) => {
const stringsearch = 'o';
const startCnt = (str.match(/\{/g) || []).length;
const endCnt = (str.match(/\}/g) || []).length;
let startCnt = 0;
let endCnt = 0;

for (let i = 0; i < str.length; i++) {
if (startCnt < endCnt) {
return '{ /* ERROR: Unbalanced CSS */ }';
}
if (str[i] === '{') {
startCnt++;
} else if (str[i] === '}') {
endCnt++;
}
}
if (startCnt !== endCnt) {
return '{ /* ERROR: Unbalanced CSS */ }';
}
Expand Down

0 comments on commit 0ae1bdb

Please sign in to comment.