mirror of
https://github.com/mermaid-js/mermaid.git
synced 2025-01-28 07:03:17 +08:00
Merge pull request from GHSA-x3vm-38hw-55wf
CSS Injection security issue
This commit is contained in:
commit
0ae1bdb61a
10
cypress/e2e/other/ghsa.spec.js
Normal file
10
cypress/e2e/other/ghsa.spec.js
Normal file
@ -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 },
|
||||
});
|
||||
});
|
||||
});
|
@ -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);
|
||||
|
||||
|
28
cypress/platform/ghsa1.html
Normal file
28
cypress/platform/ghsa1.html
Normal file
@ -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
cypress/platform/ghsa2.html
Normal file
28
cypress/platform/ghsa2.html
Normal file
@ -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>
|
@ -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}`;
|
||||
}
|
||||
|
@ -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,
|
||||
@ -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 ` {
|
||||
|
35
src/utils.js
35
src/utils.js
@ -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];
|
||||
@ -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 */ }';
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user