Skip to content

Commit c652a0d

Browse files
3y3martyanovandrey
andauthored
fix: XSS expluatation in static-content mode (#1092)
* fix: Fix XSS expluatation in static-content mode * chore: Update tests * use more uniq id * update snapshots * win test * remove slash escape --------- Co-authored-by: martyanov-av <[email protected]> Co-authored-by: Martyanov Andrey <[email protected]>
1 parent 20cec60 commit c652a0d

File tree

5 files changed

+417
-414
lines changed

5 files changed

+417
-414
lines changed

src/pages/document.ts

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,9 +65,14 @@ export function generateStaticMarkup(
6565
</head>
6666
<body class="g-root g-root_theme_light">
6767
<div id="root">${html}</div>
68+
<script type="application/json" id="diplodoc-state">
69+
${escapeJsonForHtml(props)}
70+
</script>
6871
<script type="application/javascript">
69-
window.STATIC_CONTENT = ${staticContent}
70-
window.__DATA__ = ${JSON.stringify(props)};
72+
${unescapeJsonFromHtml.toString()}
73+
const data = document.querySelector('script#diplodoc-state');
74+
window.__DATA__ = unescapeJsonFromHtml(data.innerText);
75+
window.STATIC_CONTENT = ${staticContent};
7176
</script>
7277
<script src="${toc + '.js'}" type="application/javascript"></script>
7378
${search?.resources ? `<script src="${search.resources}" type="application/javascript"></script>` : ''}
@@ -111,6 +116,26 @@ function getMetadata(metadata: VarsMetadata | undefined, restMeta: LeadingPage['
111116
return result;
112117
}
113118

119+
function escapeJsonForHtml(json: unknown): string {
120+
return JSON.stringify(json)
121+
.replace(/&/g, '&amp;')
122+
.replace(/</g, '&lt;')
123+
.replace(/>/g, '&gt;')
124+
.replace(/"/g, '&quot;')
125+
.replace(/'/g, '&#x27;');
126+
}
127+
128+
function unescapeJsonFromHtml(escaped: string): unknown {
129+
const unescaped = escaped
130+
.replace(/&#x27;/g, "'")
131+
.replace(/&quot;/g, '"')
132+
.replace(/&lt;/g, '<')
133+
.replace(/&gt;/g, '>')
134+
.replace(/&amp;/g, '&');
135+
136+
return JSON.parse(unescaped);
137+
}
138+
114139
function generateCSP(csp?: Record<string, string[]>[]) {
115140
if (!csp || !csp.length) {
116141
return '';

tests/e2e/__snapshots__/load-custom-resources.spec.ts.snap

Lines changed: 91 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -83,9 +83,20 @@ exports[`Allow load custom resources md2html single page with custom resources:
8383
<body class="g-root g-root_theme_light">
8484
<div id="root">
8585
</div>
86+
<script type="application/json"
87+
id="diplodoc-state"
88+
>
89+
{&quot;data&quot;:{&quot;data&quot;:{&quot;title&quot;:&quot;Documentation&quot;,&quot;description&quot;:&quot;&quot;,&quot;meta&quot;:{&quot;metadata&quot;:[{&quot;name&quot;:&quot;generator&quot;,&quot;content&quot;:&quot;Diplodoc Platform v4.57.8&quot;}],&quot;style&quot;:[&quot;_assets/style/test.css&quot;],&quot;script&quot;:[&quot;_assets/script/test1.js&quot;],&quot;title&quot;:&quot;Documentation&quot;,&quot;noIndex&quot;:true},&quot;links&quot;:[{&quot;title&quot;:&quot;Getting started with Documentation&quot;,&quot;description&quot;:&quot;This guide will show you the basics of working with Documentation&quot;,&quot;href&quot;:&quot;page.html&quot;}]},&quot;meta&quot;:{&quot;metadata&quot;:[{&quot;name&quot;:&quot;generator&quot;,&quot;content&quot;:&quot;Diplodoc Platform v4.57.8&quot;}],&quot;style&quot;:[&quot;_assets/style/test.css&quot;],&quot;script&quot;:[&quot;_assets/script/test1.js&quot;],&quot;title&quot;:&quot;Documentation&quot;,&quot;noIndex&quot;:true},&quot;title&quot;:&quot;Documentation&quot;,&quot;leading&quot;:true},&quot;router&quot;:{&quot;pathname&quot;:&quot;index&quot;,&quot;depth&quot;:1},&quot;lang&quot;:&quot;ru&quot;,&quot;langs&quot;:[&quot;ru&quot;]}
90+
</script>
8691
<script type="application/javascript">
87-
window.STATIC_CONTENT = false
88-
window.__DATA__ = {"data":{"data":{"title":"Documentation","description":"","meta":{"metadata":[{"name":"generator","content":"Diplodoc Platform vDIPLODOC-VERSION"}],"style":["_assets/style/test.css"],"script":["_assets/script/test1.js"],"title":"Documentation","noIndex":true},"links":[{"title":"Getting started with Documentation","description":"This guide will show you the basics of working with Documentation","href":"page.html"}]},"meta":{"metadata":[{"name":"generator","content":"Diplodoc Platform vDIPLODOC-VERSION"}],"style":["_assets/style/test.css"],"script":["_assets/script/test1.js"],"title":"Documentation","noIndex":true},"title":"Documentation","leading":true},"router":{"pathname":"index","depth":1},"lang":"ru","langs":["ru"]};
92+
function unescapeJsonFromHtml(escaped) {
93+
const unescaped = escaped.replace(/&#x27;/g, "'").replace(/&quot;/g, '"').replace(/&lt;/g, "
94+
<").replace(/&gt;/g, ">").replace(/&amp;/g, "&");
95+
return JSON.parse(unescaped);
96+
}
97+
const data = document.querySelector('script#diplodoc-state');
98+
window.__DATA__ = unescapeJsonFromHtml(data.innerText);
99+
window.STATIC_CONTENT = false;
89100
</script>
90101
<script src="toc.js"
91102
type="application/javascript"
@@ -151,11 +162,20 @@ exports[`Allow load custom resources md2html single page with custom resources:
151162
<body class="g-root g-root_theme_light">
152163
<div id="root">
153164
</div>
165+
<script type="application/json"
166+
id="diplodoc-state"
167+
>
168+
{&quot;data&quot;:{&quot;meta&quot;:{&quot;metadata&quot;:[{&quot;name&quot;:&quot;generator&quot;,&quot;content&quot;:&quot;Diplodoc Platform v4.57.8&quot;},{&quot;name&quot;:&quot;yfm&quot;,&quot;content&quot;:&quot;builder&quot;}],&quot;style&quot;:[&quot;_assets/style/test.css&quot;],&quot;script&quot;:[&quot;_assets/script/test1.js&quot;]},&quot;assets&quot;:[],&quot;headings&quot;:[],&quot;title&quot;:&quot;&quot;,&quot;includes&quot;:[],&quot;html&quot;:&quot;&lt;p&gt;Lorem&lt;/p&gt;/n&quot;,&quot;leading&quot;:false},&quot;router&quot;:{&quot;pathname&quot;:&quot;page&quot;,&quot;depth&quot;:1},&quot;lang&quot;:&quot;ru&quot;,&quot;langs&quot;:[&quot;ru&quot;]}
169+
</script>
154170
<script type="application/javascript">
155-
window.STATIC_CONTENT = false
156-
window.__DATA__ = {"data":{"meta":{"metadata":[{"name":"generator","content":"Diplodoc Platform vDIPLODOC-VERSION"},{"name":"yfm","content":"builder"}],"style":["_assets/style/test.css"],"script":["_assets/script/test1.js"]},"assets":[],"headings":[],"title":"","includes":[],"html":"
157-
<p>Lorem
158-
</p>/n","leading":false},"router":{"pathname":"page","depth":1},"lang":"ru","langs":["ru"]};
171+
function unescapeJsonFromHtml(escaped) {
172+
const unescaped = escaped.replace(/&#x27;/g, "'").replace(/&quot;/g, '"').replace(/&lt;/g, "
173+
<").replace(/&gt;/g, ">").replace(/&amp;/g, "&");
174+
return JSON.parse(unescaped);
175+
}
176+
const data = document.querySelector('script#diplodoc-state');
177+
window.__DATA__ = unescapeJsonFromHtml(data.innerText);
178+
window.STATIC_CONTENT = false;
159179
</script>
160180
<script src="toc.js"
161181
type="application/javascript"
@@ -218,11 +238,20 @@ exports[`Allow load custom resources md2html single page with custom resources:
218238
<body class="g-root g-root_theme_light">
219239
<div id="root">
220240
</div>
241+
<script type="application/json"
242+
id="diplodoc-state"
243+
>
244+
{&quot;data&quot;:{&quot;meta&quot;:{&quot;metadata&quot;:[{&quot;name&quot;:&quot;generator&quot;,&quot;content&quot;:&quot;Diplodoc Platform v4.57.8&quot;}],&quot;style&quot;:[&quot;_assets/style/test.css&quot;],&quot;script&quot;:[&quot;_assets/script/test1.js&quot;]},&quot;assets&quot;:[],&quot;headings&quot;:[],&quot;title&quot;:&quot;&quot;,&quot;includes&quot;:[],&quot;html&quot;:&quot;&lt;p&gt;Lorem&lt;/p&gt;/n&quot;,&quot;leading&quot;:false},&quot;router&quot;:{&quot;pathname&quot;:&quot;project/config&quot;,&quot;depth&quot;:2},&quot;lang&quot;:&quot;ru&quot;,&quot;langs&quot;:[&quot;ru&quot;]}
245+
</script>
221246
<script type="application/javascript">
222-
window.STATIC_CONTENT = false
223-
window.__DATA__ = {"data":{"meta":{"metadata":[{"name":"generator","content":"Diplodoc Platform vDIPLODOC-VERSION"}],"style":["_assets/style/test.css"],"script":["_assets/script/test1.js"]},"assets":[],"headings":[],"title":"","includes":[],"html":"
224-
<p>Lorem
225-
</p>/n","leading":false},"router":{"pathname":"project/config","depth":2},"lang":"ru","langs":["ru"]};
247+
function unescapeJsonFromHtml(escaped) {
248+
const unescaped = escaped.replace(/&#x27;/g, "'").replace(/&quot;/g, '"').replace(/&lt;/g, "
249+
<").replace(/&gt;/g, ">").replace(/&amp;/g, "&");
250+
return JSON.parse(unescaped);
251+
}
252+
const data = document.querySelector('script#diplodoc-state');
253+
window.__DATA__ = unescapeJsonFromHtml(data.innerText);
254+
window.STATIC_CONTENT = false;
226255
</script>
227256
<script src="toc.js"
228257
type="application/javascript"
@@ -282,14 +311,20 @@ exports[`Allow load custom resources md2html single page with custom resources:
282311
<body class="g-root g-root_theme_light">
283312
<div id="root">
284313
</div>
314+
<script type="application/json"
315+
id="diplodoc-state"
316+
>
317+
{&quot;data&quot;:{&quot;leading&quot;:false,&quot;html&quot;:&quot;&lt;p&gt;Lorem&lt;/p&gt;/n&lt;hr class=/&quot;yfm-page__delimeter/&quot;&gt;&lt;p&gt;Lorem&lt;/p&gt;/n&quot;,&quot;headings&quot;:[],&quot;meta&quot;:{&quot;style&quot;:[&quot;_assets/style/test.css&quot;],&quot;script&quot;:[&quot;_assets/script/test1.js&quot;]},&quot;title&quot;:&quot;Documentation&quot;},&quot;router&quot;:{&quot;pathname&quot;:&quot;single-page.html&quot;,&quot;depth&quot;:2,&quot;base&quot;:&quot;../&quot;},&quot;lang&quot;:&quot;ru&quot;,&quot;langs&quot;:[&quot;ru&quot;]}
318+
</script>
285319
<script type="application/javascript">
286-
window.STATIC_CONTENT = false
287-
window.__DATA__ = {"data":{"leading":false,"html":"
288-
<p>Lorem
289-
</p>/n
290-
<hr class=\\"yfm-page__delimeter\\">
291-
<p>Lorem
292-
</p>/n","headings":[],"meta":{"style":["_assets/style/test.css"],"script":["_assets/script/test1.js"]},"title":"Documentation"},"router":{"pathname":"single-page.html","depth":2,"base":"../"},"lang":"ru","langs":["ru"]};
320+
function unescapeJsonFromHtml(escaped) {
321+
const unescaped = escaped.replace(/&#x27;/g, "'").replace(/&quot;/g, '"').replace(/&lt;/g, "
322+
<").replace(/&gt;/g, ">").replace(/&amp;/g, "&");
323+
return JSON.parse(unescaped);
324+
}
325+
const data = document.querySelector('script#diplodoc-state');
326+
window.__DATA__ = unescapeJsonFromHtml(data.innerText);
327+
window.STATIC_CONTENT = false;
293328
</script>
294329
<script src="single-page-toc.js"
295330
type="application/javascript"
@@ -397,9 +432,20 @@ exports[`Allow load custom resources md2html with custom resources: index.html 1
397432
<body class="g-root g-root_theme_light">
398433
<div id="root">
399434
</div>
435+
<script type="application/json"
436+
id="diplodoc-state"
437+
>
438+
{&quot;data&quot;:{&quot;data&quot;:{&quot;title&quot;:&quot;Documentation&quot;,&quot;description&quot;:&quot;&quot;,&quot;meta&quot;:{&quot;metadata&quot;:[{&quot;name&quot;:&quot;generator&quot;,&quot;content&quot;:&quot;Diplodoc Platform v4.57.8&quot;}],&quot;style&quot;:[&quot;_assets/style/test.css&quot;],&quot;script&quot;:[&quot;_assets/script/test1.js&quot;],&quot;title&quot;:&quot;Documentation&quot;,&quot;noIndex&quot;:true},&quot;links&quot;:[{&quot;title&quot;:&quot;Getting started with Documentation&quot;,&quot;description&quot;:&quot;This guide will show you the basics of working with Documentation&quot;,&quot;href&quot;:&quot;page.html&quot;}]},&quot;meta&quot;:{&quot;metadata&quot;:[{&quot;name&quot;:&quot;generator&quot;,&quot;content&quot;:&quot;Diplodoc Platform v4.57.8&quot;}],&quot;style&quot;:[&quot;_assets/style/test.css&quot;],&quot;script&quot;:[&quot;_assets/script/test1.js&quot;],&quot;title&quot;:&quot;Documentation&quot;,&quot;noIndex&quot;:true},&quot;title&quot;:&quot;Documentation&quot;,&quot;leading&quot;:true},&quot;router&quot;:{&quot;pathname&quot;:&quot;index&quot;,&quot;depth&quot;:1},&quot;lang&quot;:&quot;ru&quot;,&quot;langs&quot;:[&quot;ru&quot;]}
439+
</script>
400440
<script type="application/javascript">
401-
window.STATIC_CONTENT = false
402-
window.__DATA__ = {"data":{"data":{"title":"Documentation","description":"","meta":{"metadata":[{"name":"generator","content":"Diplodoc Platform vDIPLODOC-VERSION"}],"style":["_assets/style/test.css"],"script":["_assets/script/test1.js"],"title":"Documentation","noIndex":true},"links":[{"title":"Getting started with Documentation","description":"This guide will show you the basics of working with Documentation","href":"page.html"}]},"meta":{"metadata":[{"name":"generator","content":"Diplodoc Platform vDIPLODOC-VERSION"}],"style":["_assets/style/test.css"],"script":["_assets/script/test1.js"],"title":"Documentation","noIndex":true},"title":"Documentation","leading":true},"router":{"pathname":"index","depth":1},"lang":"ru","langs":["ru"]};
441+
function unescapeJsonFromHtml(escaped) {
442+
const unescaped = escaped.replace(/&#x27;/g, "'").replace(/&quot;/g, '"').replace(/&lt;/g, "
443+
<").replace(/&gt;/g, ">").replace(/&amp;/g, "&");
444+
return JSON.parse(unescaped);
445+
}
446+
const data = document.querySelector('script#diplodoc-state');
447+
window.__DATA__ = unescapeJsonFromHtml(data.innerText);
448+
window.STATIC_CONTENT = false;
403449
</script>
404450
<script src="toc.js"
405451
type="application/javascript"
@@ -465,11 +511,20 @@ exports[`Allow load custom resources md2html with custom resources: page.html 1`
465511
<body class="g-root g-root_theme_light">
466512
<div id="root">
467513
</div>
514+
<script type="application/json"
515+
id="diplodoc-state"
516+
>
517+
{&quot;data&quot;:{&quot;meta&quot;:{&quot;metadata&quot;:[{&quot;name&quot;:&quot;generator&quot;,&quot;content&quot;:&quot;Diplodoc Platform v4.57.8&quot;},{&quot;name&quot;:&quot;yfm&quot;,&quot;content&quot;:&quot;builder&quot;}],&quot;style&quot;:[&quot;_assets/style/test.css&quot;],&quot;script&quot;:[&quot;_assets/script/test1.js&quot;]},&quot;assets&quot;:[],&quot;headings&quot;:[],&quot;title&quot;:&quot;&quot;,&quot;includes&quot;:[],&quot;html&quot;:&quot;&lt;p&gt;Lorem&lt;/p&gt;/n&quot;,&quot;leading&quot;:false},&quot;router&quot;:{&quot;pathname&quot;:&quot;page&quot;,&quot;depth&quot;:1},&quot;lang&quot;:&quot;ru&quot;,&quot;langs&quot;:[&quot;ru&quot;]}
518+
</script>
468519
<script type="application/javascript">
469-
window.STATIC_CONTENT = false
470-
window.__DATA__ = {"data":{"meta":{"metadata":[{"name":"generator","content":"Diplodoc Platform vDIPLODOC-VERSION"},{"name":"yfm","content":"builder"}],"style":["_assets/style/test.css"],"script":["_assets/script/test1.js"]},"assets":[],"headings":[],"title":"","includes":[],"html":"
471-
<p>Lorem
472-
</p>/n","leading":false},"router":{"pathname":"page","depth":1},"lang":"ru","langs":["ru"]};
520+
function unescapeJsonFromHtml(escaped) {
521+
const unescaped = escaped.replace(/&#x27;/g, "'").replace(/&quot;/g, '"').replace(/&lt;/g, "
522+
<").replace(/&gt;/g, ">").replace(/&amp;/g, "&");
523+
return JSON.parse(unescaped);
524+
}
525+
const data = document.querySelector('script#diplodoc-state');
526+
window.__DATA__ = unescapeJsonFromHtml(data.innerText);
527+
window.STATIC_CONTENT = false;
473528
</script>
474529
<script src="toc.js"
475530
type="application/javascript"
@@ -532,11 +587,20 @@ exports[`Allow load custom resources md2html with custom resources: project/conf
532587
<body class="g-root g-root_theme_light">
533588
<div id="root">
534589
</div>
590+
<script type="application/json"
591+
id="diplodoc-state"
592+
>
593+
{&quot;data&quot;:{&quot;meta&quot;:{&quot;metadata&quot;:[{&quot;name&quot;:&quot;generator&quot;,&quot;content&quot;:&quot;Diplodoc Platform v4.57.8&quot;}],&quot;style&quot;:[&quot;_assets/style/test.css&quot;],&quot;script&quot;:[&quot;_assets/script/test1.js&quot;]},&quot;assets&quot;:[],&quot;headings&quot;:[],&quot;title&quot;:&quot;&quot;,&quot;includes&quot;:[],&quot;html&quot;:&quot;&lt;p&gt;Lorem&lt;/p&gt;/n&quot;,&quot;leading&quot;:false},&quot;router&quot;:{&quot;pathname&quot;:&quot;project/config&quot;,&quot;depth&quot;:2},&quot;lang&quot;:&quot;ru&quot;,&quot;langs&quot;:[&quot;ru&quot;]}
594+
</script>
535595
<script type="application/javascript">
536-
window.STATIC_CONTENT = false
537-
window.__DATA__ = {"data":{"meta":{"metadata":[{"name":"generator","content":"Diplodoc Platform vDIPLODOC-VERSION"}],"style":["_assets/style/test.css"],"script":["_assets/script/test1.js"]},"assets":[],"headings":[],"title":"","includes":[],"html":"
538-
<p>Lorem
539-
</p>/n","leading":false},"router":{"pathname":"project/config","depth":2},"lang":"ru","langs":["ru"]};
596+
function unescapeJsonFromHtml(escaped) {
597+
const unescaped = escaped.replace(/&#x27;/g, "'").replace(/&quot;/g, '"').replace(/&lt;/g, "
598+
<").replace(/&gt;/g, ">").replace(/&amp;/g, "&");
599+
return JSON.parse(unescaped);
600+
}
601+
const data = document.querySelector('script#diplodoc-state');
602+
window.__DATA__ = unescapeJsonFromHtml(data.innerText);
603+
window.STATIC_CONTENT = false;
540604
</script>
541605
<script src="toc.js"
542606
type="application/javascript"

0 commit comments

Comments
 (0)