From b6d7d56774e0d260ac4c07071151f5d42a7ed20e Mon Sep 17 00:00:00 2001 From: rugk Date: Tue, 21 Nov 2017 21:22:51 +0100 Subject: [PATCH 01/10] Sanitize HTML code using DOMPurify v1.0.2 Fixes https://github.com/PrivateBin/PrivateBin/issues/183 --- js/privatebin.js | 3 ++- js/purify.min.js | 2 ++ tpl/bootstrap.php | 3 ++- tpl/page.php | 3 ++- 4 files changed, 8 insertions(+), 3 deletions(-) create mode 100644 js/purify.min.js diff --git a/js/privatebin.js b/js/privatebin.js index 9e626e9..ddc1372 100644 --- a/js/privatebin.js +++ b/js/privatebin.js @@ -1761,8 +1761,9 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) { tables: true, tablesHeaderId: true }); + // let showdown convert the HTML and sanitize HTML *afterwards*! $plainText.html( - converter.makeHtml(text) + DOMPurify.sanitize(converter.makeHtml(text), {SAFE_FOR_JQUERY: true}) ); // add table classes from bootstrap css $plainText.find('table').addClass('table-condensed table-bordered'); diff --git a/js/purify.min.js b/js/purify.min.js new file mode 100644 index 0000000..5fe41d2 --- /dev/null +++ b/js/purify.min.js @@ -0,0 +1,2 @@ +!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):e.DOMPurify=t()}(this,function(){"use strict";function e(e,t){for(var n=t.length;n--;)"string"==typeof t[n]&&(t[n]=t[n].toLowerCase()),e[t[n]]=!0;return e}function t(e){var t={},n=void 0;for(n in e)Object.prototype.hasOwnProperty.call(e,n)&&(t[n]=e[n]);return t}function n(e){if(Array.isArray(e)){for(var t=0,n=Array(e.length);t0&&void 0!==arguments[0]?arguments[0]:p(),g=function(e){return o(e)};if(g.version="1.0.2",g.removed=[],!h||!h.document||9!==h.document.nodeType)return g.isSupported=!1,g;var y=h.document,v=!1,b=!1,T=h.document,A=h.DocumentFragment,x=h.HTMLTemplateElement,S=h.Node,k=h.NodeFilter,w=h.NamedNodeMap,E=void 0===w?h.NamedNodeMap||h.MozNamedAttrMap:w,O=h.Text,M=h.Comment,N=h.DOMParser,L=h.XMLHttpRequest,D=void 0===L?h.XMLHttpRequest:L,_=h.encodeURI,R=void 0===_?h.encodeURI:_;if("function"==typeof x){var C=T.createElement("template");C.content&&C.content.ownerDocument&&(T=C.content.ownerDocument)}var F=T,z=F.implementation,H=F.createNodeIterator,I=F.getElementsByTagName,j=F.createDocumentFragment,U=y.importNode,q={};g.isSupported=z&&void 0!==z.createHTMLDocument&&9!==T.documentMode;var W=null,B=e({},[].concat(n(r),n(i),n(a),n(l),n(s))),G=null,P=e({},[].concat(n(c),n(d),n(u),n(m))),V=null,X=null,Y=!0,K=!0,$=!1,J=!1,Q=!1,Z=/\{\{[\s\S]*|[\s\S]*\}\}/gm,ee=/<%[\s\S]*|[\s\S]*%>/gm,te=!1,ne=!1,oe=!1,re=!1,ie=!1,ae=!1,le=!0,se=!0,ce={},de=e({},["audio","head","math","script","style","template","svg","video"]),ue=e({},["audio","video","img","source","image"]),me=e({},["alt","class","for","id","label","name","pattern","placeholder","summary","title","value","style","xmlns"]),fe=null,pe=T.createElement("form"),he=function(o){"object"!==(void 0===o?"undefined":f(o))&&(o={}),W="ALLOWED_TAGS"in o?e({},o.ALLOWED_TAGS):B,G="ALLOWED_ATTR"in o?e({},o.ALLOWED_ATTR):P,V="FORBID_TAGS"in o?e({},o.FORBID_TAGS):{},X="FORBID_ATTR"in o?e({},o.FORBID_ATTR):{},ce="USE_PROFILES"in o&&o.USE_PROFILES,Y=!1!==o.ALLOW_ARIA_ATTR,K=!1!==o.ALLOW_DATA_ATTR,$=o.ALLOW_UNKNOWN_PROTOCOLS||!1,J=o.SAFE_FOR_JQUERY||!1,Q=o.SAFE_FOR_TEMPLATES||!1,te=o.WHOLE_DOCUMENT||!1,re=o.RETURN_DOM||!1,ie=o.RETURN_DOM_FRAGMENT||!1,ae=o.RETURN_DOM_IMPORT||!1,oe=o.FORCE_BODY||!1,le=!1!==o.SANITIZE_DOM,se=!1!==o.KEEP_CONTENT,Q&&(K=!1),ie&&(re=!0),ce&&(W=e({},[].concat(n(s))),G=[],!0===ce.html&&(e(W,r),e(G,c)),!0===ce.svg&&(e(W,i),e(G,d),e(G,m)),!0===ce.svgFilters&&(e(W,a),e(G,d),e(G,m)),!0===ce.mathMl&&(e(W,l),e(G,u),e(G,m))),o.ADD_TAGS&&(W===B&&(W=t(W)),e(W,o.ADD_TAGS)),o.ADD_ATTR&&(G===P&&(G=t(G)),e(G,o.ADD_ATTR)),o.ADD_URI_SAFE_ATTR&&e(me,o.ADD_URI_SAFE_ATTR),se&&(W["#text"]=!0),Object&&"freeze"in Object&&Object.freeze(o),fe=o},ge=function(e){g.removed.push({element:e});try{e.parentNode.removeChild(e)}catch(t){e.outerHTML=""}},ye=function(e,t){g.removed.push({attribute:t.getAttributeNode(e),from:t}),t.removeAttribute(e)},ve=function(e){var t=void 0,n=void 0;if(oe&&(e=""+e),b){try{e=R(e)}catch(e){}var o=new D;o.responseType="document",o.open("GET","data:text/html;charset=utf-8,"+e,!1),o.send(null),t=o.response}if(v)try{t=(new N).parseFromString(e,"text/html")}catch(e){}return t&&t.documentElement||((n=(t=z.createHTMLDocument("")).body).parentNode.removeChild(n.parentNode.firstElementChild),n.outerHTML=e),I.call(t,te?"html":"body")[0]};g.isSupported&&function(){var e=ve('');e.querySelector("svg")||(b=!0);try{(e=ve('

')).querySelector("svg img")&&(v=!0)}catch(e){}}();var be=function(e){return H.call(e.ownerDocument||e,e,k.SHOW_ELEMENT|k.SHOW_COMMENT|k.SHOW_TEXT,function(){return k.FILTER_ACCEPT},!1)},Te=function(e){return!(e instanceof O||e instanceof M)&&!("string"==typeof e.nodeName&&"string"==typeof e.textContent&&"function"==typeof e.removeChild&&e.attributes instanceof E&&"function"==typeof e.removeAttribute&&"function"==typeof e.setAttribute)},Ae=function(e){return"object"===(void 0===S?"undefined":f(S))?e instanceof S:e&&"object"===(void 0===e?"undefined":f(e))&&"number"==typeof e.nodeType&&"string"==typeof e.nodeName},xe=function(e,t,n){q[e]&&q[e].forEach(function(e){e.call(g,t,n,fe)})},Se=function(e){var t=void 0;if(xe("beforeSanitizeElements",e,null),Te(e))return ge(e),!0;var n=e.nodeName.toLowerCase();if(xe("uponSanitizeElement",e,{tagName:n,allowedTags:W}),!W[n]||V[n]){if(se&&!de[n]&&"function"==typeof e.insertAdjacentHTML)try{e.insertAdjacentHTML("AfterEnd",e.innerHTML)}catch(e){}return ge(e),!0}return!J||e.firstElementChild||e.content&&e.content.firstElementChild||!/l&&e.setAttribute("id",i.value);else{if("INPUT"===e.nodeName&&"type"===r&&"file"===o&&(G[r]||!X[r]))continue;"id"===n&&e.setAttribute(n,""),ye(n,e)}if(s.keepAttr&&(!le||"id"!==r&&"name"!==r||!(o in h||o in T||o in pe))){if(Q&&(o=(o=o.replace(Z," ")).replace(ee," ")),K&&ke.test(r));else if(Y&&we.test(r));else{if(!G[r]||X[r])continue;if(me[r]);else if(Ee.test(o.replace(Me,"")));else if("src"!==r&&"xlink:href"!==r||0!==o.indexOf("data:")||!ue[e.nodeName.toLowerCase()]){if($&&!Oe.test(o.replace(Me,"")));else if(o)continue}else;}try{e.setAttribute(n,o),g.removed.pop()}catch(e){}}}xe("afterSanitizeAttributes",e,null)}},Le=function e(t){var n=void 0,o=be(t);for(xe("beforeSanitizeShadowDOM",t,null);n=o.nextNode();)xe("uponSanitizeShadowNode",n,null),Se(n)||(n.content instanceof A&&e(n.content),Ne(n));xe("afterSanitizeShadowDOM",t,null)};return g.sanitize=function(e,t){var n=void 0,o=void 0,r=void 0,i=void 0,a=void 0;if(e||(e="\x3c!--\x3e"),"string"!=typeof e&&!Ae(e)){if("function"!=typeof e.toString)throw new TypeError("toString is not a function");e=e.toString()}if(!g.isSupported){if("object"===f(h.toStaticHTML)||"function"==typeof h.toStaticHTML){if("string"==typeof e)return h.toStaticHTML(e);if(Ae(e))return h.toStaticHTML(e.outerHTML)}return e}if(ne||he(t),g.removed=[],e instanceof S)1===(o=(n=ve("\x3c!--\x3e")).ownerDocument.importNode(e,!0)).nodeType&&"BODY"===o.nodeName?n=o:n.appendChild(o);else{if(!re&&!te&&-1===e.indexOf("<"))return e;if(!(n=ve(e)))return re?null:""}oe&&ge(n.firstChild);for(var l=be(n);r=l.nextNode();)3===r.nodeType&&r===i||Se(r)||(r.content instanceof A&&Le(r.content),Ne(r),i=r);if(re){if(ie)for(a=j.call(n.ownerDocument);n.firstChild;)a.appendChild(n.firstChild);else a=n;return ae&&(a=U.call(y,a,!0)),a}return te?n.outerHTML:n.innerHTML},g.setConfig=function(e){he(e),ne=!0},g.clearConfig=function(){fe=null,ne=!1},g.addHook=function(e,t){"function"==typeof t&&(q[e]=q[e]||[],q[e].push(t))},g.removeHook=function(e){q[e]&&q[e].pop()},g.removeHooks=function(e){q[e]&&(q[e]=[])},g.removeAllHooks=function(){q={}},g}var r=["a","abbr","acronym","address","area","article","aside","audio","b","bdi","bdo","big","blink","blockquote","body","br","button","canvas","caption","center","cite","code","col","colgroup","content","data","datalist","dd","decorator","del","details","dfn","dir","div","dl","dt","element","em","fieldset","figcaption","figure","font","footer","form","h1","h2","h3","h4","h5","h6","head","header","hgroup","hr","html","i","img","input","ins","kbd","label","legend","li","main","map","mark","marquee","menu","menuitem","meter","nav","nobr","ol","optgroup","option","output","p","pre","progress","q","rp","rt","ruby","s","samp","section","select","shadow","small","source","spacer","span","strike","strong","style","sub","summary","sup","table","tbody","td","template","textarea","tfoot","th","thead","time","tr","track","tt","u","ul","var","video","wbr"],i=["svg","a","altglyph","altglyphdef","altglyphitem","animatecolor","animatemotion","animatetransform","audio","canvas","circle","clippath","defs","desc","ellipse","filter","font","g","glyph","glyphref","hkern","image","line","lineargradient","marker","mask","metadata","mpath","path","pattern","polygon","polyline","radialgradient","rect","stop","style","switch","symbol","text","textpath","title","tref","tspan","video","view","vkern"],a=["feBlend","feColorMatrix","feComponentTransfer","feComposite","feConvolveMatrix","feDiffuseLighting","feDisplacementMap","feFlood","feFuncA","feFuncB","feFuncG","feFuncR","feGaussianBlur","feMerge","feMergeNode","feMorphology","feOffset","feSpecularLighting","feTile","feTurbulence"],l=["math","menclose","merror","mfenced","mfrac","mglyph","mi","mlabeledtr","mmuliscripts","mn","mo","mover","mpadded","mphantom","mroot","mrow","ms","mpspace","msqrt","mystyle","msub","msup","msubsup","mtable","mtd","mtext","mtr","munder","munderover"],s=["#text"],c=["accept","action","align","alt","autocomplete","background","bgcolor","border","cellpadding","cellspacing","checked","cite","class","clear","color","cols","colspan","coords","datetime","default","dir","disabled","download","enctype","face","for","headers","height","hidden","high","href","hreflang","id","ismap","label","lang","list","loop","low","max","maxlength","media","method","min","multiple","name","noshade","novalidate","nowrap","open","optimum","pattern","placeholder","poster","preload","pubdate","radiogroup","readonly","rel","required","rev","reversed","role","rows","rowspan","spellcheck","scope","selected","shape","size","span","srclang","start","src","step","style","summary","tabindex","title","type","usemap","valign","value","width","xmlns"],d=["accent-height","accumulate","additivive","alignment-baseline","ascent","attributename","attributetype","azimuth","basefrequency","baseline-shift","begin","bias","by","class","clip","clip-path","clip-rule","color","color-interpolation","color-interpolation-filters","color-profile","color-rendering","cx","cy","d","dx","dy","diffuseconstant","direction","display","divisor","dur","edgemode","elevation","end","fill","fill-opacity","fill-rule","filter","flood-color","flood-opacity","font-family","font-size","font-size-adjust","font-stretch","font-style","font-variant","font-weight","fx","fy","g1","g2","glyph-name","glyphref","gradientunits","gradienttransform","height","href","id","image-rendering","in","in2","k","k1","k2","k3","k4","kerning","keypoints","keysplines","keytimes","lang","lengthadjust","letter-spacing","kernelmatrix","kernelunitlength","lighting-color","local","marker-end","marker-mid","marker-start","markerheight","markerunits","markerwidth","maskcontentunits","maskunits","max","mask","media","method","mode","min","name","numoctaves","offset","operator","opacity","order","orient","orientation","origin","overflow","paint-order","path","pathlength","patterncontentunits","patterntransform","patternunits","points","preservealpha","r","rx","ry","radius","refx","refy","repeatcount","repeatdur","restart","result","rotate","scale","seed","shape-rendering","specularconstant","specularexponent","spreadmethod","stddeviation","stitchtiles","stop-color","stop-opacity","stroke-dasharray","stroke-dashoffset","stroke-linecap","stroke-linejoin","stroke-miterlimit","stroke-opacity","stroke","stroke-width","style","surfacescale","tabindex","targetx","targety","transform","text-anchor","text-decoration","text-rendering","textlength","type","u1","u2","unicode","values","viewbox","visibility","vert-adv-y","vert-origin-x","vert-origin-y","width","word-spacing","wrap","writing-mode","xchannelselector","ychannelselector","x","x1","x2","xmlns","y","y1","y2","z","zoomandpan"],u=["accent","accentunder","align","bevelled","close","columnsalign","columnlines","columnspan","denomalign","depth","dir","display","displaystyle","fence","frame","height","href","id","largeop","length","linethickness","lspace","lquote","mathbackground","mathcolor","mathsize","mathvariant","maxsize","minsize","movablelimits","notation","numalign","open","rowalign","rowlines","rowspacing","rowspan","rspace","rquote","scriptlevel","scriptminsize","scriptsizemultiplier","selection","separator","separators","stretchy","subscriptshift","supscriptshift","symmetric","voffset","width","xmlns"],m=["xlink:href","xml:id","xlink:title","xml:space","xmlns:xlink"],f="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e},p=function(){return"undefined"==typeof window?null:window};return o()}); +//# sourceMappingURL=purify.min.js.map diff --git a/tpl/bootstrap.php b/tpl/bootstrap.php index 103037e..e75614e 100644 --- a/tpl/bootstrap.php +++ b/tpl/bootstrap.php @@ -66,10 +66,11 @@ endif; if ($MARKDOWN): ?> + - + diff --git a/tpl/page.php b/tpl/page.php index 81d7c1a..6d636d0 100644 --- a/tpl/page.php +++ b/tpl/page.php @@ -44,10 +44,11 @@ endif; if ($MARKDOWN): ?> + - + From bbec693cab1148df6363486cd22d7388914fc397 Mon Sep 17 00:00:00 2001 From: rugk Date: Tue, 21 Nov 2017 22:26:02 +0100 Subject: [PATCH 02/10] Allow DOMPurify as a global --- .eslintrc | 1 + js/privatebin.js | 1 + tpl/bootstrap.php | 2 +- tpl/page.php | 2 +- 4 files changed, 4 insertions(+), 2 deletions(-) diff --git a/.eslintrc b/.eslintrc index e2a42cc..cee9820 100644 --- a/.eslintrc +++ b/.eslintrc @@ -11,6 +11,7 @@ env: globals: sjcl: false + DOMPurify: false # http://eslint.org/docs/rules/ rules: diff --git a/js/privatebin.js b/js/privatebin.js index ddc1372..0d34603 100644 --- a/js/privatebin.js +++ b/js/privatebin.js @@ -1763,6 +1763,7 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) { }); // let showdown convert the HTML and sanitize HTML *afterwards*! $plainText.html( + /** global: DOMPurify */ DOMPurify.sanitize(converter.makeHtml(text), {SAFE_FOR_JQUERY: true}) ); // add table classes from bootstrap css diff --git a/tpl/bootstrap.php b/tpl/bootstrap.php index e75614e..def90df 100644 --- a/tpl/bootstrap.php +++ b/tpl/bootstrap.php @@ -70,7 +70,7 @@ if ($MARKDOWN): - + diff --git a/tpl/page.php b/tpl/page.php index 6d636d0..9c1db28 100644 --- a/tpl/page.php +++ b/tpl/page.php @@ -48,7 +48,7 @@ if ($MARKDOWN): - + From 2d00202b42221a24b66f7019d4b39e4c5663298e Mon Sep 17 00:00:00 2001 From: El RIDO Date: Wed, 22 Nov 2017 07:03:29 +0100 Subject: [PATCH 03/10] correcting the XSS test, commenting two failing patterns, to be reviewed by @rugk --- js/privatebin.js | 12 +++++++++++- js/test.js | 9 +++++---- tpl/bootstrap.php | 2 +- tpl/page.php | 2 +- 4 files changed, 18 insertions(+), 7 deletions(-) diff --git a/js/privatebin.js b/js/privatebin.js index 20396b2..77e458f 100644 --- a/js/privatebin.js +++ b/js/privatebin.js @@ -12,6 +12,7 @@ */ /** global: Base64 */ +/** global: DOMPurify */ /** global: FileReader */ /** global: RawDeflate */ /** global: history */ @@ -1777,7 +1778,6 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) { }); // let showdown convert the HTML and sanitize HTML *afterwards*! $plainText.html( - /** global: DOMPurify */ DOMPurify.sanitize(converter.makeHtml(text), {SAFE_FOR_JQUERY: true}) ); // add table classes from bootstrap css @@ -1800,6 +1800,16 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) { // convert URLs to clickable links Helper.urls2links($plainText); Helper.urls2links($prettyPrint); + $plainText.html( + DOMPurify.sanitize( + $plainText.html(), {SAFE_FOR_JQUERY: true} + ) + ); + $prettyPrint.html( + DOMPurify.sanitize( + $prettyPrint.html(), {SAFE_FOR_JQUERY: true} + ) + ); $prettyPrint.css('white-space', 'pre-wrap'); $prettyPrint.css('word-break', 'normal'); diff --git a/js/test.js b/js/test.js index 4b68971..2e06c87 100644 --- a/js/test.js +++ b/js/test.js @@ -1451,8 +1451,9 @@ describe('PasteViewer', function () { // https://www.owasp.org/index.php/XSS_Filter_Evasion_Cheat_Sheet jsc.elements([ '

', - '\';alert(String.fromCharCode(88,83,83))//\';alert(String.fromCharCode(88,83,83))//";', - 'alert(String.fromCharCode(88,83,83))//";alert(String.fromCharCode(88,83,83))//--', +// @TODO these two pass, but aren't evaluated in this context - do they need to be sanitized, too? +// '\';alert(String.fromCharCode(88,83,83))//\';alert(String.fromCharCode(88,83,83))//";', +// 'alert(String.fromCharCode(88,83,83))//";alert(String.fromCharCode(88,83,83))//--', '></SCRIPT>">\'><SCRIPT>alert(String.fromCharCode(88,83,83))</SCRIPT>', '\'\';!--"<XSS>=&{()}', '<SCRIPT SRC=http://example.com/xss.js></SCRIPT>', @@ -1466,7 +1467,7 @@ describe('PasteViewer', function () { '<a onmouseover=alert(document.cookie)>xxs link</a>', '<IMG """><SCRIPT>alert("XSS")</SCRIPT>">', '<IMG SRC=javascript:alert(String.fromCharCode(88,83,83))>' - // the list goes on… + // @TODO the list goes on… ]), 'string', function (format, prefix, xss, suffix) { @@ -1482,7 +1483,7 @@ describe('PasteViewer', function () { $.PrivateBin.PasteViewer.setFormat(format); $.PrivateBin.PasteViewer.setText(text); $.PrivateBin.PasteViewer.run(); - var result = $('body').html().indexOf(xss) !== -1; + var result = $('body').html().indexOf(xss) === -1; clean(); return result; } diff --git a/tpl/bootstrap.php b/tpl/bootstrap.php index 208ab7d..aee9583 100644 --- a/tpl/bootstrap.php +++ b/tpl/bootstrap.php @@ -70,7 +70,7 @@ if ($MARKDOWN): <?php endif; ?> - <script type="text/javascript" src="js/privatebin.js?<?php echo rawurlencode($VERSION); ?>" integrity="sha512-zbiQgBDsSGOdieFoYhNqu7fnj+GpeuQRXQcUSg1U9lP3HlHUY6Lp1w/Hl/LK2y/RGjkoV3MRcjU/BQVGoZxKlw==" crossorigin="anonymous"></script> + <script type="text/javascript" src="js/privatebin.js?<?php echo rawurlencode($VERSION); ?>" integrity="sha512-VEBWEPLKeJ5Lv0T67Nal1g4NxeoOf2nlE20rF3uOq8fpBb/bKLR9wPxcl8mTjEiA+GAEQv0s4Fh5iT8wee1Nbw==" crossorigin="anonymous"></script> <!--[if lt IE 10]> <style type="text/css">body {padding-left:60px;padding-right:60px;} #ienotice {display:block;} #oldienotice {display:block;}</style> <![endif]--> diff --git a/tpl/page.php b/tpl/page.php index 2ecbb69..1d7355b 100644 --- a/tpl/page.php +++ b/tpl/page.php @@ -48,7 +48,7 @@ if ($MARKDOWN): <?php endif; ?> - <script type="text/javascript" src="js/privatebin.js?<?php echo rawurlencode($VERSION); ?>" integrity="sha512-zbiQgBDsSGOdieFoYhNqu7fnj+GpeuQRXQcUSg1U9lP3HlHUY6Lp1w/Hl/LK2y/RGjkoV3MRcjU/BQVGoZxKlw==" crossorigin="anonymous"></script> + <script type="text/javascript" src="js/privatebin.js?<?php echo rawurlencode($VERSION); ?>" integrity="sha512-VEBWEPLKeJ5Lv0T67Nal1g4NxeoOf2nlE20rF3uOq8fpBb/bKLR9wPxcl8mTjEiA+GAEQv0s4Fh5iT8wee1Nbw==" crossorigin="anonymous"></script> <!--[if lt IE 10]> <style type="text/css">body {padding-left:60px;padding-right:60px;} #ienotice {display:block;} #oldienotice {display:block;}</style> <![endif]--> From 9fa2ea3373dbce0396572df4aa8510ede87d0008 Mon Sep 17 00:00:00 2001 From: El RIDO <elrido@gmx.net> Date: Wed, 22 Nov 2017 08:05:06 +0100 Subject: [PATCH 04/10] ensuring text is sanitized in all cases, before being injected into the DOM --- js/privatebin.js | 17 ++++------------- tpl/bootstrap.php | 2 +- tpl/page.php | 2 +- 3 files changed, 6 insertions(+), 15 deletions(-) diff --git a/js/privatebin.js b/js/privatebin.js index 77e458f..86b6046 100644 --- a/js/privatebin.js +++ b/js/privatebin.js @@ -1766,8 +1766,9 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) { } // set text - Helper.setElementText($plainText, text); - Helper.setElementText($prettyPrint, text); + var sanitizedText = DOMPurify.sanitize(text, {SAFE_FOR_JQUERY: true}) + Helper.setElementText($plainText, sanitizedText); + Helper.setElementText($prettyPrint, sanitizedText); switch (format) { case 'markdown': @@ -1792,7 +1793,7 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) { $prettyPrint.html( prettyPrintOne( - Helper.htmlEntities(text), null, true + Helper.htmlEntities(sanitizedText), null, true ) ); // fall through, as the rest is the same @@ -1800,16 +1801,6 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) { // convert URLs to clickable links Helper.urls2links($plainText); Helper.urls2links($prettyPrint); - $plainText.html( - DOMPurify.sanitize( - $plainText.html(), {SAFE_FOR_JQUERY: true} - ) - ); - $prettyPrint.html( - DOMPurify.sanitize( - $prettyPrint.html(), {SAFE_FOR_JQUERY: true} - ) - ); $prettyPrint.css('white-space', 'pre-wrap'); $prettyPrint.css('word-break', 'normal'); diff --git a/tpl/bootstrap.php b/tpl/bootstrap.php index aee9583..89cd4ff 100644 --- a/tpl/bootstrap.php +++ b/tpl/bootstrap.php @@ -70,7 +70,7 @@ if ($MARKDOWN): <?php endif; ?> - <script type="text/javascript" src="js/privatebin.js?<?php echo rawurlencode($VERSION); ?>" integrity="sha512-VEBWEPLKeJ5Lv0T67Nal1g4NxeoOf2nlE20rF3uOq8fpBb/bKLR9wPxcl8mTjEiA+GAEQv0s4Fh5iT8wee1Nbw==" crossorigin="anonymous"></script> + <script type="text/javascript" src="js/privatebin.js?<?php echo rawurlencode($VERSION); ?>" integrity="sha512-asPypLWIpl03jZzFSOTfUuLsZ+DHVBrcGU0f9LeJrN/T33Al9q2+qt7V8nm6Ji88rUvYvnTp2/KgNgOSRJcQPw==" crossorigin="anonymous"></script> <!--[if lt IE 10]> <style type="text/css">body {padding-left:60px;padding-right:60px;} #ienotice {display:block;} #oldienotice {display:block;}</style> <![endif]--> diff --git a/tpl/page.php b/tpl/page.php index 1d7355b..c59fba7 100644 --- a/tpl/page.php +++ b/tpl/page.php @@ -48,7 +48,7 @@ if ($MARKDOWN): <?php endif; ?> - <script type="text/javascript" src="js/privatebin.js?<?php echo rawurlencode($VERSION); ?>" integrity="sha512-VEBWEPLKeJ5Lv0T67Nal1g4NxeoOf2nlE20rF3uOq8fpBb/bKLR9wPxcl8mTjEiA+GAEQv0s4Fh5iT8wee1Nbw==" crossorigin="anonymous"></script> + <script type="text/javascript" src="js/privatebin.js?<?php echo rawurlencode($VERSION); ?>" integrity="sha512-asPypLWIpl03jZzFSOTfUuLsZ+DHVBrcGU0f9LeJrN/T33Al9q2+qt7V8nm6Ji88rUvYvnTp2/KgNgOSRJcQPw==" crossorigin="anonymous"></script> <!--[if lt IE 10]> <style type="text/css">body {padding-left:60px;padding-right:60px;} #ienotice {display:block;} #oldienotice {display:block;}</style> <![endif]--> From 3d2dbabaece15a2e1b31c92d62272a7246b70137 Mon Sep 17 00:00:00 2001 From: rugk <rugk@posteo.de> Date: Wed, 22 Nov 2017 15:41:49 +0100 Subject: [PATCH 05/10] add some more tests from OWASP --- js/test.js | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/js/test.js b/js/test.js index 2e06c87..be4df6d 100644 --- a/js/test.js +++ b/js/test.js @@ -1451,9 +1451,6 @@ describe('PasteViewer', function () { // https://www.owasp.org/index.php/XSS_Filter_Evasion_Cheat_Sheet jsc.elements([ '<PLAINTEXT>', -// @TODO these two pass, but aren't evaluated in this context - do they need to be sanitized, too? -// '\';alert(String.fromCharCode(88,83,83))//\';alert(String.fromCharCode(88,83,83))//";', -// 'alert(String.fromCharCode(88,83,83))//";alert(String.fromCharCode(88,83,83))//--', '></SCRIPT>">\'><SCRIPT>alert(String.fromCharCode(88,83,83))</SCRIPT>', '\'\';!--"<XSS>=&{()}', '<SCRIPT SRC=http://example.com/xss.js></SCRIPT>', @@ -1466,8 +1463,18 @@ describe('PasteViewer', function () { '<a onmouseover="alert(document.cookie)">xxs link</a>', '<a onmouseover=alert(document.cookie)>xxs link</a>', '<IMG """><SCRIPT>alert("XSS")</SCRIPT>">', - '<IMG SRC=javascript:alert(String.fromCharCode(88,83,83))>' - // @TODO the list goes on… + '<IMG SRC=javascript:alert(String.fromCharCode(88,83,83))>', + '<IMG STYLE="xss:expr/*XSS*/ession(alert(\'XSS\'))">', + '<FRAMESET><FRAME SRC="javascript:alert(\'XSS\');"></FRAMESET>', + '<TABLE BACKGROUND="javascript:alert(\'XSS\')">', + '<TABLE><TD BACKGROUND="javascript:alert(\'XSS\')">', + '<SCRIPT>document.write("<SCRI");</SCRIPT>PT SRC="httx://xss.rocks/xss.js"></SCRIPT>', + '(alert)(1)', + 'a=alert,a(1)', + 'top[“al”+”ert”](1)', + 'top[/al/.source+/ert/.source](1)', + 'al\u0065rt(1)', + 'top[8680439..toString(30)](1)' ]), 'string', function (format, prefix, xss, suffix) { @@ -1490,4 +1497,3 @@ describe('PasteViewer', function () { ); }); }); - From 8d2e19f7917231dee1af2453f55ddbd21fd83af5 Mon Sep 17 00:00:00 2001 From: rugk <rugk@posteo.de> Date: Wed, 22 Nov 2017 16:48:00 +0100 Subject: [PATCH 06/10] Try to move sanitisation & links into setElementText --- js/privatebin.js | 139 +++++++++++++++++++++-------------------------- js/test.js | 40 +++++++------- 2 files changed, 83 insertions(+), 96 deletions(-) diff --git a/js/privatebin.js b/js/privatebin.js index 86b6046..df5dffc 100644 --- a/js/privatebin.js +++ b/js/privatebin.js @@ -43,26 +43,6 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) { var Helper = (function () { var me = {}; - /** - * character to HTML entity lookup table - * - * @see {@link https://github.com/janl/mustache.js/blob/master/mustache.js#L60} - * @name Helper.entityMap - * @private - * @enum {Object} - * @readonly - */ - var entityMap = { - '&': '&amp;', - '<': '&lt;', - '>': '&gt;', - '"': '&quot;', - "'": '&#39;', - '/': '&#x2F;', - '`': '&#x60;', - '=': '&#x3D;' - }; - /** * cache for script location * @@ -72,6 +52,36 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) { */ var baseUri = null; + /** + * convert URLs to clickable links. + * URLs to handle: + * <pre> + * magnet:?xt.1=urn:sha1:YNCKHTQCWBTRNJIV4WNAE52SJUQCZO5C&xt.2=urn:sha1:TXGCZQTH26NL6OUQAJJPFALHG2LTGBC7 + * http://example.com:8800/zero/?6f09182b8ea51997#WtLEUO5Epj9UHAV9JFs+6pUQZp13TuspAUjnF+iM+dM= + * http://user:example.com@localhost:8800/zero/?6f09182b8ea51997#WtLEUO5Epj9UHAV9JFs+6pUQZp13TuspAUjnF+iM+dM= + * </pre> + * Attention: Does *not* sanitize HTML code! It is strongly advised to sanitize it after running this function. + * + * + * @name Helper.urls2links + * @function + * @param {String} html - HTML code + */ + urls2links = function(html) + { + var markup = '<a href="$1" rel="nofollow">$1</a>'; + // short test: https://regex101.com/r/AttfVd/1 + html.replace( + /((http|https|ftp):\/\/[\w?=&.\/-;#@~%+*-]+(?![\w\s?&.\/;#~%"=-]*>))/ig, + markup + ) + // shorttest: https://regex101.com/r/sCm8Xe/2 + html.replace( + /((magnet):[\w?=&.\/-;#@~%+*-]+)/ig, + markup + ); + } + /** * converts a duration (in seconds) into human friendly approximation * @@ -135,55 +145,38 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) { } /** - * set text of a jQuery element (required for IE), + * set text of a jQuery element (required for IE) * * @name Helper.setElementText * @function * @param {jQuery} $element - a jQuery element * @param {string} text - the text to enter + * @param {bool} convertLinks - whether to convert the links in the text */ - me.setElementText = function($element, text) + me.setElementText = function($element, text, convertLinks) { - // For IE<10: Doesn't support white-space:pre-wrap; so we have to do this... - if ($('#oldienotice').is(':visible')) { - var html = me.htmlEntities(text).replace(/\n/ig, '\r\n<br>'); - $element.html('<pre>' + html + '</pre>'); + var isIe = $('#oldienotice').is(':visible'); + // text-only and no IE -> fast way: set text-only + if ((convertLinks === false) && isIe === false) { + return $element.text(text); } - // for other (sane) browsers: - else - { - $element.text(text); + + // convert text to plain-text + // but as we need to handle HTML code afterwards + var html = $(text).text(); + + if (convertLinks === true) { + html = me.urls2links(html); } - } - /** - * convert URLs to clickable links. - * URLs to handle: - * <pre> - * magnet:?xt.1=urn:sha1:YNCKHTQCWBTRNJIV4WNAE52SJUQCZO5C&xt.2=urn:sha1:TXGCZQTH26NL6OUQAJJPFALHG2LTGBC7 - * http://example.com:8800/zero/?6f09182b8ea51997#WtLEUO5Epj9UHAV9JFs+6pUQZp13TuspAUjnF+iM+dM= - * http://user:example.com@localhost:8800/zero/?6f09182b8ea51997#WtLEUO5Epj9UHAV9JFs+6pUQZp13TuspAUjnF+iM+dM= - * </pre> - * - * @name Helper.urls2links - * @function - * @param {Object} $element - a jQuery DOM element - */ - me.urls2links = function($element) - { - var markup = '<a href="$1" rel="nofollow">$1</a>'; - $element.html( - $element.html().replace( - /((http|https|ftp):\/\/[\w?=&.\/-;#@~%+*-]+(?![\w\s?&.\/;#~%"=-]*>))/ig, - markup - ) - ); - $element.html( - $element.html().replace( - /((magnet):[\w?=&.\/-;#@~%+*-]+)/ig, - markup - ) - ); + // workaround: IE<10 doesn't support white-space:pre-wrap; so we have to do this... + if (isIe) { + html = html.replace(/\n/ig, '\r\n<br>'); + } + + // finally sanitize it for security (XSS) reasons + html = me.sanitizeHtml(text); + $element.html(html); } /** @@ -270,19 +263,17 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) { } /** - * convert all applicable characters to HTML entities + * sanitizes html code to prevent XSS attacks * - * @see {@link https://www.owasp.org/index.php/XSS_(Cross_Site_Scripting)_Prevention_Cheat_Sheet#RULE_.231_-_HTML_Escape_Before_Inserting_Untrusted_Data_into_HTML_Element_Content} - * @name Helper.htmlEntities + * Now uses DOMPurify instead of some self-made stuff for security reasons. + * + * @name Helper.sanitizeHtml * @function * @param {string} str * @return {string} escaped HTML */ - me.htmlEntities = function(str) { - return String(str).replace( - /[&<>"'`=\/]/g, function(s) { - return entityMap[s]; - }); + me.sanitizeHtml = function(str) { + return DOMPurify.sanitize(str, {SAFE_FOR_JQUERY: true}); } /** @@ -1766,9 +1757,8 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) { } // set text - var sanitizedText = DOMPurify.sanitize(text, {SAFE_FOR_JQUERY: true}) - Helper.setElementText($plainText, sanitizedText); - Helper.setElementText($prettyPrint, sanitizedText); + Helper.setElementText($plainText, text, false); + Helper.setElementText($prettyPrint, text, true); switch (format) { case 'markdown': @@ -1793,15 +1783,12 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) { $prettyPrint.html( prettyPrintOne( - Helper.htmlEntities(sanitizedText), null, true + Helper.sanitizeHtml(text), null, true ) ); // fall through, as the rest is the same default: // = 'plaintext' - // convert URLs to clickable links - Helper.urls2links($plainText); - Helper.urls2links($prettyPrint); - + // adjust CSS so it looks good $prettyPrint.css('white-space', 'pre-wrap'); $prettyPrint.css('word-break', 'normal'); $prettyPrint.removeClass('prettyprint'); @@ -2594,7 +2581,7 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) { for (var i = 0; i < $head.length; i++) { newDoc.write($head[i].outerHTML); } - newDoc.write('</head><body><pre>' + Helper.htmlEntities(paste) + '</pre></body></html>'); + newDoc.write('</head><body><pre>' + Helper.sanitizeHtml(paste) + '</pre></body></html>'); newDoc.close(); } diff --git a/js/test.js b/js/test.js index be4df6d..a48efdf 100644 --- a/js/test.js +++ b/js/test.js @@ -93,7 +93,7 @@ describe('Helper', function () { var html = '', result = true; ids.forEach(function(item, i) { - html += '<div id="' + item.join('') + '">' + $.PrivateBin.Helper.htmlEntities(contents[i] || contents[0]) + '</div>'; + html += '<div id="' + item.join('') + '">' + $.PrivateBin.Helper.sanitizeHtml(contents[i] || contents[0]) + '</div>'; }); var clean = jsdom(html); ids.forEach(function(item, i) { @@ -122,7 +122,7 @@ describe('Helper', function () { var html = '', result = true; ids.forEach(function(item, i) { - html += '<div id="' + item.join('') + '">' + $.PrivateBin.Helper.htmlEntities(contents[i] || contents[0]) + '</div>'; + html += '<div id="' + item.join('') + '">' + $.PrivateBin.Helper.sanitizeHtml(contents[i] || contents[0]) + '</div>'; }); var elements = $('<body />').html(html); ids.forEach(function(item, i) { @@ -163,9 +163,9 @@ describe('Helper', function () { var query = query.join(''), fragment = fragment.join(''), url = schema + '://' + address.join('') + '/?' + query + '#' + fragment, - prefix = $.PrivateBin.Helper.htmlEntities(prefix), - postfix = ' ' + $.PrivateBin.Helper.htmlEntities(postfix), - element = $('<div>' + prefix + url + postfix + '</div>'); + prefix = $.PrivateBin.Helper.sanitizeHtml(prefix), + postfix = ' ' + $.PrivateBin.Helper.sanitizeHtml(postfix), + element = '<div>' + prefix + url + postfix + '</div>'; // special cases: When the query string and fragment imply the beginning of an HTML entity, eg. &#0 or &#x if ( @@ -175,11 +175,11 @@ describe('Helper', function () { { url = schema + '://' + address.join('') + '/?' + query.substring(0, query.length - 1); postfix = ''; - element = $('<div>' + prefix + url + '</div>'); + element = '<div>' + prefix + url + '</div>'; } $.PrivateBin.Helper.urls2links(element); - return element.html() === $('<div>' + prefix + '<a href="' + url + '" rel="nofollow">' + url + '</a>' + postfix + '</div>').html(); + return element.html() === '<div>' + prefix + '<a href="' + url + '" rel="nofollow">' + url + '</a>' + postfix + '</div>'; } ); jsc.property( @@ -189,8 +189,8 @@ describe('Helper', function () { 'string', function (prefix, query, postfix) { var url = 'magnet:?' + query.join('').replace(/^&+|&+$/gm,''), - prefix = $.PrivateBin.Helper.htmlEntities(prefix), - postfix = $.PrivateBin.Helper.htmlEntities(postfix), + prefix = $.PrivateBin.Helper.sanitizeHtml(prefix), + postfix = $.PrivateBin.Helper.sanitizeHtml(postfix), element = $('<div>' + prefix + url + ' ' + postfix + '</div>'); $.PrivateBin.Helper.urls2links(element); return element.html() === $('<div>' + prefix + '<a href="' + url + '" rel="nofollow">' + url + '</a> ' + postfix + '</div>').html(); @@ -329,7 +329,7 @@ describe('Helper', function () { ); }); - describe('htmlEntities', function () { + describe('sanitizeHtml', function () { after(function () { cleanup(); }); @@ -338,7 +338,7 @@ describe('Helper', function () { 'removes all HTML entities from any given string', 'string', function (string) { - var result = $.PrivateBin.Helper.htmlEntities(string); + var result = $.PrivateBin.Helper.sanitizeHtml(string); return !(/[<>"'`=\/]/.test(result)) && !(string.indexOf('&') > -1 && !(/&amp;/.test(result))); } ); @@ -583,8 +583,8 @@ describe('Model', function () { 'string', 'small nat', function (keys, value, key) { - keys = keys.map($.PrivateBin.Helper.htmlEntities); - value = $.PrivateBin.Helper.htmlEntities(value); + keys = keys.map($.PrivateBin.Helper.sanitizeHtml); + value = $.PrivateBin.Helper.sanitizeHtml(value); var content = keys.length > key ? keys[key] : (keys.length > 0 ? keys[0] : 'null'), contents = '<select id="pasteExpiration" name="pasteExpiration">'; keys.forEach(function(item) { @@ -596,7 +596,7 @@ describe('Model', function () { }); contents += '</select>'; $('body').html(contents); - var result = $.PrivateBin.Helper.htmlEntities( + var result = $.PrivateBin.Helper.sanitizeHtml( $.PrivateBin.Model.getExpirationDefault() ); $.PrivateBin.Model.reset(); @@ -617,8 +617,8 @@ describe('Model', function () { 'string', 'small nat', function (keys, value, key) { - keys = keys.map($.PrivateBin.Helper.htmlEntities); - value = $.PrivateBin.Helper.htmlEntities(value); + keys = keys.map($.PrivateBin.Helper.sanitizeHtml); + value = $.PrivateBin.Helper.sanitizeHtml(value); var content = keys.length > key ? keys[key] : (keys.length > 0 ? keys[0] : 'null'), contents = '<select id="pasteFormatter" name="pasteFormatter">'; keys.forEach(function(item) { @@ -630,7 +630,7 @@ describe('Model', function () { }); contents += '</select>'; $('body').html(contents); - var result = $.PrivateBin.Helper.htmlEntities( + var result = $.PrivateBin.Helper.sanitizeHtml( $.PrivateBin.Model.getFormatDefault() ); $.PrivateBin.Model.reset(); @@ -649,7 +649,7 @@ describe('Model', function () { 'checks if the element with id "cipherdata" contains any data', 'asciistring', function (value) { - value = $.PrivateBin.Helper.htmlEntities(value).trim(); + value = $.PrivateBin.Helper.sanitizeHtml(value).trim(); $('body').html('<div id="cipherdata">' + value + '</div>'); $.PrivateBin.Model.init(); var result = $.PrivateBin.Model.hasCipherData(); @@ -669,10 +669,10 @@ describe('Model', function () { 'returns the contents of the element with id "cipherdata"', 'asciistring', function (value) { - value = $.PrivateBin.Helper.htmlEntities(value).trim(); + value = $.PrivateBin.Helper.sanitizeHtml(value).trim(); $('body').html('<div id="cipherdata">' + value + '</div>'); $.PrivateBin.Model.init(); - var result = $.PrivateBin.Helper.htmlEntities( + var result = $.PrivateBin.Helper.sanitizeHtml( $.PrivateBin.Model.getCipherData() ); $.PrivateBin.Model.reset(); From 56f4ee5c20fc6dfd22c86c3f84337ff941a93e4f Mon Sep 17 00:00:00 2001 From: rugk <rugk@posteo.de> Date: Wed, 22 Nov 2017 16:48:54 +0100 Subject: [PATCH 07/10] Revert "Try to move sanitisation & links into setElementText" This reverts commit 8d2e19f7917231dee1af2453f55ddbd21fd83af5. --- js/privatebin.js | 139 ++++++++++++++++++++++++++--------------------- js/test.js | 40 +++++++------- 2 files changed, 96 insertions(+), 83 deletions(-) diff --git a/js/privatebin.js b/js/privatebin.js index df5dffc..86b6046 100644 --- a/js/privatebin.js +++ b/js/privatebin.js @@ -44,43 +44,33 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) { var me = {}; /** - * cache for script location + * character to HTML entity lookup table * - * @name Helper.baseUri + * @see {@link https://github.com/janl/mustache.js/blob/master/mustache.js#L60} + * @name Helper.entityMap * @private - * @enum {string|null} + * @enum {Object} + * @readonly */ - var baseUri = null; + var entityMap = { + '&': '&amp;', + '<': '&lt;', + '>': '&gt;', + '"': '&quot;', + "'": '&#39;', + '/': '&#x2F;', + '`': '&#x60;', + '=': '&#x3D;' + }; /** - * convert URLs to clickable links. - * URLs to handle: - * <pre> - * magnet:?xt.1=urn:sha1:YNCKHTQCWBTRNJIV4WNAE52SJUQCZO5C&xt.2=urn:sha1:TXGCZQTH26NL6OUQAJJPFALHG2LTGBC7 - * http://example.com:8800/zero/?6f09182b8ea51997#WtLEUO5Epj9UHAV9JFs+6pUQZp13TuspAUjnF+iM+dM= - * http://user:example.com@localhost:8800/zero/?6f09182b8ea51997#WtLEUO5Epj9UHAV9JFs+6pUQZp13TuspAUjnF+iM+dM= - * </pre> - * Attention: Does *not* sanitize HTML code! It is strongly advised to sanitize it after running this function. - * + * cache for script location * - * @name Helper.urls2links - * @function - * @param {String} html - HTML code + * @name Helper.baseUri + * @private + * @enum {string|null} */ - urls2links = function(html) - { - var markup = '<a href="$1" rel="nofollow">$1</a>'; - // short test: https://regex101.com/r/AttfVd/1 - html.replace( - /((http|https|ftp):\/\/[\w?=&.\/-;#@~%+*-]+(?![\w\s?&.\/;#~%"=-]*>))/ig, - markup - ) - // shorttest: https://regex101.com/r/sCm8Xe/2 - html.replace( - /((magnet):[\w?=&.\/-;#@~%+*-]+)/ig, - markup - ); - } + var baseUri = null; /** * converts a duration (in seconds) into human friendly approximation @@ -145,38 +135,55 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) { } /** - * set text of a jQuery element (required for IE) + * set text of a jQuery element (required for IE), * * @name Helper.setElementText * @function * @param {jQuery} $element - a jQuery element * @param {string} text - the text to enter - * @param {bool} convertLinks - whether to convert the links in the text */ - me.setElementText = function($element, text, convertLinks) + me.setElementText = function($element, text) { - var isIe = $('#oldienotice').is(':visible'); - // text-only and no IE -> fast way: set text-only - if ((convertLinks === false) && isIe === false) { - return $element.text(text); + // For IE<10: Doesn't support white-space:pre-wrap; so we have to do this... + if ($('#oldienotice').is(':visible')) { + var html = me.htmlEntities(text).replace(/\n/ig, '\r\n<br>'); + $element.html('<pre>' + html + '</pre>'); } - - // convert text to plain-text - // but as we need to handle HTML code afterwards - var html = $(text).text(); - - if (convertLinks === true) { - html = me.urls2links(html); - } - - // workaround: IE<10 doesn't support white-space:pre-wrap; so we have to do this... - if (isIe) { - html = html.replace(/\n/ig, '\r\n<br>'); + // for other (sane) browsers: + else + { + $element.text(text); } + } - // finally sanitize it for security (XSS) reasons - html = me.sanitizeHtml(text); - $element.html(html); + /** + * convert URLs to clickable links. + * URLs to handle: + * <pre> + * magnet:?xt.1=urn:sha1:YNCKHTQCWBTRNJIV4WNAE52SJUQCZO5C&xt.2=urn:sha1:TXGCZQTH26NL6OUQAJJPFALHG2LTGBC7 + * http://example.com:8800/zero/?6f09182b8ea51997#WtLEUO5Epj9UHAV9JFs+6pUQZp13TuspAUjnF+iM+dM= + * http://user:example.com@localhost:8800/zero/?6f09182b8ea51997#WtLEUO5Epj9UHAV9JFs+6pUQZp13TuspAUjnF+iM+dM= + * </pre> + * + * @name Helper.urls2links + * @function + * @param {Object} $element - a jQuery DOM element + */ + me.urls2links = function($element) + { + var markup = '<a href="$1" rel="nofollow">$1</a>'; + $element.html( + $element.html().replace( + /((http|https|ftp):\/\/[\w?=&.\/-;#@~%+*-]+(?![\w\s?&.\/;#~%"=-]*>))/ig, + markup + ) + ); + $element.html( + $element.html().replace( + /((magnet):[\w?=&.\/-;#@~%+*-]+)/ig, + markup + ) + ); } /** @@ -263,17 +270,19 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) { } /** - * sanitizes html code to prevent XSS attacks + * convert all applicable characters to HTML entities * - * Now uses DOMPurify instead of some self-made stuff for security reasons. - * - * @name Helper.sanitizeHtml + * @see {@link https://www.owasp.org/index.php/XSS_(Cross_Site_Scripting)_Prevention_Cheat_Sheet#RULE_.231_-_HTML_Escape_Before_Inserting_Untrusted_Data_into_HTML_Element_Content} + * @name Helper.htmlEntities * @function * @param {string} str * @return {string} escaped HTML */ - me.sanitizeHtml = function(str) { - return DOMPurify.sanitize(str, {SAFE_FOR_JQUERY: true}); + me.htmlEntities = function(str) { + return String(str).replace( + /[&<>"'`=\/]/g, function(s) { + return entityMap[s]; + }); } /** @@ -1757,8 +1766,9 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) { } // set text - Helper.setElementText($plainText, text, false); - Helper.setElementText($prettyPrint, text, true); + var sanitizedText = DOMPurify.sanitize(text, {SAFE_FOR_JQUERY: true}) + Helper.setElementText($plainText, sanitizedText); + Helper.setElementText($prettyPrint, sanitizedText); switch (format) { case 'markdown': @@ -1783,12 +1793,15 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) { $prettyPrint.html( prettyPrintOne( - Helper.sanitizeHtml(text), null, true + Helper.htmlEntities(sanitizedText), null, true ) ); // fall through, as the rest is the same default: // = 'plaintext' - // adjust CSS so it looks good + // convert URLs to clickable links + Helper.urls2links($plainText); + Helper.urls2links($prettyPrint); + $prettyPrint.css('white-space', 'pre-wrap'); $prettyPrint.css('word-break', 'normal'); $prettyPrint.removeClass('prettyprint'); @@ -2581,7 +2594,7 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) { for (var i = 0; i < $head.length; i++) { newDoc.write($head[i].outerHTML); } - newDoc.write('</head><body><pre>' + Helper.sanitizeHtml(paste) + '</pre></body></html>'); + newDoc.write('</head><body><pre>' + Helper.htmlEntities(paste) + '</pre></body></html>'); newDoc.close(); } diff --git a/js/test.js b/js/test.js index a48efdf..be4df6d 100644 --- a/js/test.js +++ b/js/test.js @@ -93,7 +93,7 @@ describe('Helper', function () { var html = '', result = true; ids.forEach(function(item, i) { - html += '<div id="' + item.join('') + '">' + $.PrivateBin.Helper.sanitizeHtml(contents[i] || contents[0]) + '</div>'; + html += '<div id="' + item.join('') + '">' + $.PrivateBin.Helper.htmlEntities(contents[i] || contents[0]) + '</div>'; }); var clean = jsdom(html); ids.forEach(function(item, i) { @@ -122,7 +122,7 @@ describe('Helper', function () { var html = '', result = true; ids.forEach(function(item, i) { - html += '<div id="' + item.join('') + '">' + $.PrivateBin.Helper.sanitizeHtml(contents[i] || contents[0]) + '</div>'; + html += '<div id="' + item.join('') + '">' + $.PrivateBin.Helper.htmlEntities(contents[i] || contents[0]) + '</div>'; }); var elements = $('<body />').html(html); ids.forEach(function(item, i) { @@ -163,9 +163,9 @@ describe('Helper', function () { var query = query.join(''), fragment = fragment.join(''), url = schema + '://' + address.join('') + '/?' + query + '#' + fragment, - prefix = $.PrivateBin.Helper.sanitizeHtml(prefix), - postfix = ' ' + $.PrivateBin.Helper.sanitizeHtml(postfix), - element = '<div>' + prefix + url + postfix + '</div>'; + prefix = $.PrivateBin.Helper.htmlEntities(prefix), + postfix = ' ' + $.PrivateBin.Helper.htmlEntities(postfix), + element = $('<div>' + prefix + url + postfix + '</div>'); // special cases: When the query string and fragment imply the beginning of an HTML entity, eg. &#0 or &#x if ( @@ -175,11 +175,11 @@ describe('Helper', function () { { url = schema + '://' + address.join('') + '/?' + query.substring(0, query.length - 1); postfix = ''; - element = '<div>' + prefix + url + '</div>'; + element = $('<div>' + prefix + url + '</div>'); } $.PrivateBin.Helper.urls2links(element); - return element.html() === '<div>' + prefix + '<a href="' + url + '" rel="nofollow">' + url + '</a>' + postfix + '</div>'; + return element.html() === $('<div>' + prefix + '<a href="' + url + '" rel="nofollow">' + url + '</a>' + postfix + '</div>').html(); } ); jsc.property( @@ -189,8 +189,8 @@ describe('Helper', function () { 'string', function (prefix, query, postfix) { var url = 'magnet:?' + query.join('').replace(/^&+|&+$/gm,''), - prefix = $.PrivateBin.Helper.sanitizeHtml(prefix), - postfix = $.PrivateBin.Helper.sanitizeHtml(postfix), + prefix = $.PrivateBin.Helper.htmlEntities(prefix), + postfix = $.PrivateBin.Helper.htmlEntities(postfix), element = $('<div>' + prefix + url + ' ' + postfix + '</div>'); $.PrivateBin.Helper.urls2links(element); return element.html() === $('<div>' + prefix + '<a href="' + url + '" rel="nofollow">' + url + '</a> ' + postfix + '</div>').html(); @@ -329,7 +329,7 @@ describe('Helper', function () { ); }); - describe('sanitizeHtml', function () { + describe('htmlEntities', function () { after(function () { cleanup(); }); @@ -338,7 +338,7 @@ describe('Helper', function () { 'removes all HTML entities from any given string', 'string', function (string) { - var result = $.PrivateBin.Helper.sanitizeHtml(string); + var result = $.PrivateBin.Helper.htmlEntities(string); return !(/[<>"'`=\/]/.test(result)) && !(string.indexOf('&') > -1 && !(/&amp;/.test(result))); } ); @@ -583,8 +583,8 @@ describe('Model', function () { 'string', 'small nat', function (keys, value, key) { - keys = keys.map($.PrivateBin.Helper.sanitizeHtml); - value = $.PrivateBin.Helper.sanitizeHtml(value); + keys = keys.map($.PrivateBin.Helper.htmlEntities); + value = $.PrivateBin.Helper.htmlEntities(value); var content = keys.length > key ? keys[key] : (keys.length > 0 ? keys[0] : 'null'), contents = '<select id="pasteExpiration" name="pasteExpiration">'; keys.forEach(function(item) { @@ -596,7 +596,7 @@ describe('Model', function () { }); contents += '</select>'; $('body').html(contents); - var result = $.PrivateBin.Helper.sanitizeHtml( + var result = $.PrivateBin.Helper.htmlEntities( $.PrivateBin.Model.getExpirationDefault() ); $.PrivateBin.Model.reset(); @@ -617,8 +617,8 @@ describe('Model', function () { 'string', 'small nat', function (keys, value, key) { - keys = keys.map($.PrivateBin.Helper.sanitizeHtml); - value = $.PrivateBin.Helper.sanitizeHtml(value); + keys = keys.map($.PrivateBin.Helper.htmlEntities); + value = $.PrivateBin.Helper.htmlEntities(value); var content = keys.length > key ? keys[key] : (keys.length > 0 ? keys[0] : 'null'), contents = '<select id="pasteFormatter" name="pasteFormatter">'; keys.forEach(function(item) { @@ -630,7 +630,7 @@ describe('Model', function () { }); contents += '</select>'; $('body').html(contents); - var result = $.PrivateBin.Helper.sanitizeHtml( + var result = $.PrivateBin.Helper.htmlEntities( $.PrivateBin.Model.getFormatDefault() ); $.PrivateBin.Model.reset(); @@ -649,7 +649,7 @@ describe('Model', function () { 'checks if the element with id "cipherdata" contains any data', 'asciistring', function (value) { - value = $.PrivateBin.Helper.sanitizeHtml(value).trim(); + value = $.PrivateBin.Helper.htmlEntities(value).trim(); $('body').html('<div id="cipherdata">' + value + '</div>'); $.PrivateBin.Model.init(); var result = $.PrivateBin.Model.hasCipherData(); @@ -669,10 +669,10 @@ describe('Model', function () { 'returns the contents of the element with id "cipherdata"', 'asciistring', function (value) { - value = $.PrivateBin.Helper.sanitizeHtml(value).trim(); + value = $.PrivateBin.Helper.htmlEntities(value).trim(); $('body').html('<div id="cipherdata">' + value + '</div>'); $.PrivateBin.Model.init(); - var result = $.PrivateBin.Helper.sanitizeHtml( + var result = $.PrivateBin.Helper.htmlEntities( $.PrivateBin.Model.getCipherData() ); $.PrivateBin.Model.reset(); From d0cccce7a8c3cb52996f3d48e28f47ba3c68c3f8 Mon Sep 17 00:00:00 2001 From: El RIDO <elrido@gmx.net> Date: Wed, 22 Nov 2017 20:49:23 +0100 Subject: [PATCH 08/10] removing patterns that don't get sanitized, but also don't get interpreted when inserted into the HTML --- js/test.js | 6 ------ 1 file changed, 6 deletions(-) diff --git a/js/test.js b/js/test.js index be4df6d..99180fa 100644 --- a/js/test.js +++ b/js/test.js @@ -1469,12 +1469,6 @@ describe('PasteViewer', function () { '<TABLE BACKGROUND="javascript:alert(\'XSS\')">', '<TABLE><TD BACKGROUND="javascript:alert(\'XSS\')">', '<SCRIPT>document.write("<SCRI");</SCRIPT>PT SRC="httx://xss.rocks/xss.js"></SCRIPT>', - '(alert)(1)', - 'a=alert,a(1)', - 'top[“al”+”ert”](1)', - 'top[/al/.source+/ert/.source](1)', - 'al\u0065rt(1)', - 'top[8680439..toString(30)](1)' ]), 'string', function (format, prefix, xss, suffix) { From a0740ff79f9076ec7fa4d80bdfb32337a7136482 Mon Sep 17 00:00:00 2001 From: El RIDO <elrido@gmx.net> Date: Wed, 22 Nov 2017 22:27:38 +0100 Subject: [PATCH 09/10] getting rid of htmlEntities (except for tests) and setElementText (dropping IE9 support), changing urls2links interface, all to avoid double encoding sanitized HTML --- js/privatebin.js | 107 +++++++++------------------------------------- js/test.js | 104 ++++++++++++++++++++++---------------------- tpl/bootstrap.php | 2 +- tpl/page.php | 2 +- 4 files changed, 73 insertions(+), 142 deletions(-) diff --git a/js/privatebin.js b/js/privatebin.js index 86b6046..7b141c5 100644 --- a/js/privatebin.js +++ b/js/privatebin.js @@ -43,26 +43,6 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) { var Helper = (function () { var me = {}; - /** - * character to HTML entity lookup table - * - * @see {@link https://github.com/janl/mustache.js/blob/master/mustache.js#L60} - * @name Helper.entityMap - * @private - * @enum {Object} - * @readonly - */ - var entityMap = { - '&': '&amp;', - '<': '&lt;', - '>': '&gt;', - '"': '&quot;', - "'": '&#39;', - '/': '&#x2F;', - '`': '&#x60;', - '=': '&#x3D;' - }; - /** * cache for script location * @@ -134,28 +114,6 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) { } } - /** - * set text of a jQuery element (required for IE), - * - * @name Helper.setElementText - * @function - * @param {jQuery} $element - a jQuery element - * @param {string} text - the text to enter - */ - me.setElementText = function($element, text) - { - // For IE<10: Doesn't support white-space:pre-wrap; so we have to do this... - if ($('#oldienotice').is(':visible')) { - var html = me.htmlEntities(text).replace(/\n/ig, '\r\n<br>'); - $element.html('<pre>' + html + '</pre>'); - } - // for other (sane) browsers: - else - { - $element.text(text); - } - } - /** * convert URLs to clickable links. * URLs to handle: @@ -167,22 +125,14 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) { * * @name Helper.urls2links * @function - * @param {Object} $element - a jQuery DOM element + * @param {string} html + * @return {string} */ - me.urls2links = function($element) + me.urls2links = function(html) { - var markup = '<a href="$1" rel="nofollow">$1</a>'; - $element.html( - $element.html().replace( - /((http|https|ftp):\/\/[\w?=&.\/-;#@~%+*-]+(?![\w\s?&.\/;#~%"=-]*>))/ig, - markup - ) - ); - $element.html( - $element.html().replace( - /((magnet):[\w?=&.\/-;#@~%+*-]+)/ig, - markup - ) + return html.replace( + /(((http|https|ftp):\/\/[\w?=&.\/-;#@~%+*-]+(?![\w\s?&.\/;#~%"=-]*>))|((magnet):[\w?=&.\/-;#@~%+*-]+))/ig, + '<a href="$1" rel="nofollow">$1</a>' ); } @@ -269,22 +219,6 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) { return baseUri; } - /** - * convert all applicable characters to HTML entities - * - * @see {@link https://www.owasp.org/index.php/XSS_(Cross_Site_Scripting)_Prevention_Cheat_Sheet#RULE_.231_-_HTML_Escape_Before_Inserting_Untrusted_Data_into_HTML_Element_Content} - * @name Helper.htmlEntities - * @function - * @param {string} str - * @return {string} escaped HTML - */ - me.htmlEntities = function(str) { - return String(str).replace( - /[&<>"'`=\/]/g, function(s) { - return entityMap[s]; - }); - } - /** * resets state, used for unit testing * @@ -1765,10 +1699,10 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) { return; } - // set text - var sanitizedText = DOMPurify.sanitize(text, {SAFE_FOR_JQUERY: true}) - Helper.setElementText($plainText, sanitizedText); - Helper.setElementText($prettyPrint, sanitizedText); + // set sanitized and linked text + var sanitizedLinkedText = DOMPurify.sanitize(Helper.urls2links(text), {SAFE_FOR_JQUERY: true}); + $plainText.html(sanitizedLinkedText); + $prettyPrint.html(sanitizedLinkedText); switch (format) { case 'markdown': @@ -1785,23 +1719,20 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) { $plainText.find('table').addClass('table-condensed table-bordered'); break; case 'syntaxhighlighting': - // @TODO is this really needed or is "one" enough? + // yes, this is really needed to initialize the environment if (typeof prettyPrint === 'function') { prettyPrint(); } $prettyPrint.html( - prettyPrintOne( - Helper.htmlEntities(sanitizedText), null, true + DOMPurify.sanitize( + prettyPrintOne(Helper.urls2links(text), null, true), + {SAFE_FOR_JQUERY: true} ) ); // fall through, as the rest is the same default: // = 'plaintext' - // convert URLs to clickable links - Helper.urls2links($plainText); - Helper.urls2links($prettyPrint); - $prettyPrint.css('white-space', 'pre-wrap'); $prettyPrint.css('word-break', 'normal'); $prettyPrint.removeClass('prettyprint'); @@ -2290,8 +2221,12 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) { var $commentEntryData = $commentEntry.find('div.commentdata'); // set & parse text - Helper.setElementText($commentEntryData, commentText); - Helper.urls2links($commentEntryData); + $commentEntryData.html( + DOMPurify.sanitize( + Helper.urls2links(commentText), + {SAFE_FOR_JQUERY: true} + ) + ); // set nickname if (nickname.length > 0) { @@ -2594,7 +2529,7 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) { for (var i = 0; i < $head.length; i++) { newDoc.write($head[i].outerHTML); } - newDoc.write('</head><body><pre>' + Helper.htmlEntities(paste) + '</pre></body></html>'); + newDoc.write('</head><body><pre>' + DOMPurify.sanitize(paste, {SAFE_FOR_JQUERY: true}) + '</pre></body></html>'); newDoc.close(); } diff --git a/js/test.js b/js/test.js index 99180fa..06535f0 100644 --- a/js/test.js +++ b/js/test.js @@ -15,6 +15,22 @@ var jsc = require('jsverify'), // schemas supported by the whatwg-url library schemas = ['ftp','gopher','http','https','ws','wss'], supportedLanguages = ['de', 'es', 'fr', 'it', 'no', 'pl', 'pt', 'oc', 'ru', 'sl', 'zh'], + + /** + * character to HTML entity lookup table + * + * @see {@link https://github.com/janl/mustache.js/blob/master/mustache.js#L60} + */ + entityMap = { + '&': '&amp;', + '<': '&lt;', + '>': '&gt;', + '"': '&quot;', + "'": '&#39;', + '/': '&#x2F;', + '`': '&#x60;', + '=': '&#x3D;' + }, logFile = require('fs').createWriteStream('test.log'); global.$ = global.jQuery = require('./jquery-3.1.1'); @@ -35,6 +51,22 @@ console.info = console.warn = console.error = function () { logFile.write(Array.prototype.slice.call(arguments).join('') + '\n'); } +/** + * convert all applicable characters to HTML entities + * + * @see {@link https://www.owasp.org/index.php/XSS_(Cross_Site_Scripting)_Prevention_Cheat_Sheet#RULE_.231_-_HTML_Escape_Before_Inserting_Untrusted_Data_into_HTML_Element_Content} + * @name htmlEntities + * @function + * @param {string} str + * @return {string} escaped HTML + */ +function htmlEntities(str) { + return String(str).replace( + /[&<>"'`=\/]/g, function(s) { + return entityMap[s]; + }); +} + describe('Helper', function () { describe('secondsToHuman', function () { after(function () { @@ -93,7 +125,7 @@ describe('Helper', function () { var html = '', result = true; ids.forEach(function(item, i) { - html += '<div id="' + item.join('') + '">' + $.PrivateBin.Helper.htmlEntities(contents[i] || contents[0]) + '</div>'; + html += '<div id="' + item.join('') + '">' + htmlEntities(contents[i] || contents[0]) + '</div>'; }); var clean = jsdom(html); ids.forEach(function(item, i) { @@ -108,34 +140,6 @@ describe('Helper', function () { ); }); - describe('setElementText', function () { - after(function () { - cleanup(); - }); - - jsc.property( - 'replaces the content of an element', - jsc.nearray(jsc.nearray(jsc.elements(alnumString))), - 'nearray string', - 'string', - function (ids, contents, replacingContent) { - var html = '', - result = true; - ids.forEach(function(item, i) { - html += '<div id="' + item.join('') + '">' + $.PrivateBin.Helper.htmlEntities(contents[i] || contents[0]) + '</div>'; - }); - var elements = $('<body />').html(html); - ids.forEach(function(item, i) { - var id = item.join(''), - element = elements.find('#' + id).first(); - $.PrivateBin.Helper.setElementText(element, replacingContent); - result *= replacingContent === element.text(); - }); - return Boolean(result); - } - ); - }); - describe('urls2links', function () { after(function () { cleanup(); @@ -145,10 +149,7 @@ describe('Helper', function () { 'ignores non-URL content', 'string', function (content) { - var element = $('<div>' + content + '</div>'), - before = element.html(); - $.PrivateBin.Helper.urls2links(element); - return before === element.html(); + return content === $.PrivateBin.Helper.urls2links(content); } ); jsc.property( @@ -163,9 +164,8 @@ describe('Helper', function () { var query = query.join(''), fragment = fragment.join(''), url = schema + '://' + address.join('') + '/?' + query + '#' + fragment, - prefix = $.PrivateBin.Helper.htmlEntities(prefix), - postfix = ' ' + $.PrivateBin.Helper.htmlEntities(postfix), - element = $('<div>' + prefix + url + postfix + '</div>'); + prefix = htmlEntities(prefix), + postfix = ' ' + htmlEntities(postfix); // special cases: When the query string and fragment imply the beginning of an HTML entity, eg. &#0 or &#x if ( @@ -175,11 +175,9 @@ describe('Helper', function () { { url = schema + '://' + address.join('') + '/?' + query.substring(0, query.length - 1); postfix = ''; - element = $('<div>' + prefix + url + '</div>'); } - $.PrivateBin.Helper.urls2links(element); - return element.html() === $('<div>' + prefix + '<a href="' + url + '" rel="nofollow">' + url + '</a>' + postfix + '</div>').html(); + return prefix + '<a href="' + url + '" rel="nofollow">' + url + '</a>' + postfix === $.PrivateBin.Helper.urls2links(prefix + url + postfix); } ); jsc.property( @@ -189,11 +187,9 @@ describe('Helper', function () { 'string', function (prefix, query, postfix) { var url = 'magnet:?' + query.join('').replace(/^&+|&+$/gm,''), - prefix = $.PrivateBin.Helper.htmlEntities(prefix), - postfix = $.PrivateBin.Helper.htmlEntities(postfix), - element = $('<div>' + prefix + url + ' ' + postfix + '</div>'); - $.PrivateBin.Helper.urls2links(element); - return element.html() === $('<div>' + prefix + '<a href="' + url + '" rel="nofollow">' + url + '</a> ' + postfix + '</div>').html(); + prefix = htmlEntities(prefix), + postfix = htmlEntities(postfix); + return prefix + '<a href="' + url + '" rel="nofollow">' + url + '</a> ' + postfix === $.PrivateBin.Helper.urls2links(prefix + url + ' ' + postfix); } ); }); @@ -338,7 +334,7 @@ describe('Helper', function () { 'removes all HTML entities from any given string', 'string', function (string) { - var result = $.PrivateBin.Helper.htmlEntities(string); + var result = htmlEntities(string); return !(/[<>"'`=\/]/.test(result)) && !(string.indexOf('&') > -1 && !(/&amp;/.test(result))); } ); @@ -583,8 +579,8 @@ describe('Model', function () { 'string', 'small nat', function (keys, value, key) { - keys = keys.map($.PrivateBin.Helper.htmlEntities); - value = $.PrivateBin.Helper.htmlEntities(value); + keys = keys.map(htmlEntities); + value = htmlEntities(value); var content = keys.length > key ? keys[key] : (keys.length > 0 ? keys[0] : 'null'), contents = '<select id="pasteExpiration" name="pasteExpiration">'; keys.forEach(function(item) { @@ -596,7 +592,7 @@ describe('Model', function () { }); contents += '</select>'; $('body').html(contents); - var result = $.PrivateBin.Helper.htmlEntities( + var result = htmlEntities( $.PrivateBin.Model.getExpirationDefault() ); $.PrivateBin.Model.reset(); @@ -617,8 +613,8 @@ describe('Model', function () { 'string', 'small nat', function (keys, value, key) { - keys = keys.map($.PrivateBin.Helper.htmlEntities); - value = $.PrivateBin.Helper.htmlEntities(value); + keys = keys.map(htmlEntities); + value = htmlEntities(value); var content = keys.length > key ? keys[key] : (keys.length > 0 ? keys[0] : 'null'), contents = '<select id="pasteFormatter" name="pasteFormatter">'; keys.forEach(function(item) { @@ -630,7 +626,7 @@ describe('Model', function () { }); contents += '</select>'; $('body').html(contents); - var result = $.PrivateBin.Helper.htmlEntities( + var result = htmlEntities( $.PrivateBin.Model.getFormatDefault() ); $.PrivateBin.Model.reset(); @@ -649,7 +645,7 @@ describe('Model', function () { 'checks if the element with id "cipherdata" contains any data', 'asciistring', function (value) { - value = $.PrivateBin.Helper.htmlEntities(value).trim(); + value = htmlEntities(value).trim(); $('body').html('<div id="cipherdata">' + value + '</div>'); $.PrivateBin.Model.init(); var result = $.PrivateBin.Model.hasCipherData(); @@ -669,10 +665,10 @@ describe('Model', function () { 'returns the contents of the element with id "cipherdata"', 'asciistring', function (value) { - value = $.PrivateBin.Helper.htmlEntities(value).trim(); + value = htmlEntities(value).trim(); $('body').html('<div id="cipherdata">' + value + '</div>'); $.PrivateBin.Model.init(); - var result = $.PrivateBin.Helper.htmlEntities( + var result = htmlEntities( $.PrivateBin.Model.getCipherData() ); $.PrivateBin.Model.reset(); diff --git a/tpl/bootstrap.php b/tpl/bootstrap.php index 89cd4ff..203fbcb 100644 --- a/tpl/bootstrap.php +++ b/tpl/bootstrap.php @@ -70,7 +70,7 @@ if ($MARKDOWN): <?php endif; ?> - <script type="text/javascript" src="js/privatebin.js?<?php echo rawurlencode($VERSION); ?>" integrity="sha512-asPypLWIpl03jZzFSOTfUuLsZ+DHVBrcGU0f9LeJrN/T33Al9q2+qt7V8nm6Ji88rUvYvnTp2/KgNgOSRJcQPw==" crossorigin="anonymous"></script> + <script type="text/javascript" src="js/privatebin.js?<?php echo rawurlencode($VERSION); ?>" integrity="sha512-EvNAh1GXOoUiGZ/W8iPtzsce06bvVHy6+ajJztmfSgdQcKMPoj0dB8j1FC90MEChl7MOeR4xozvDymH/6HwIlA==" crossorigin="anonymous"></script> <!--[if lt IE 10]> <style type="text/css">body {padding-left:60px;padding-right:60px;} #ienotice {display:block;} #oldienotice {display:block;}</style> <![endif]--> diff --git a/tpl/page.php b/tpl/page.php index c59fba7..867f8ed 100644 --- a/tpl/page.php +++ b/tpl/page.php @@ -48,7 +48,7 @@ if ($MARKDOWN): <?php endif; ?> - <script type="text/javascript" src="js/privatebin.js?<?php echo rawurlencode($VERSION); ?>" integrity="sha512-asPypLWIpl03jZzFSOTfUuLsZ+DHVBrcGU0f9LeJrN/T33Al9q2+qt7V8nm6Ji88rUvYvnTp2/KgNgOSRJcQPw==" crossorigin="anonymous"></script> + <script type="text/javascript" src="js/privatebin.js?<?php echo rawurlencode($VERSION); ?>" integrity="sha512-EvNAh1GXOoUiGZ/W8iPtzsce06bvVHy6+ajJztmfSgdQcKMPoj0dB8j1FC90MEChl7MOeR4xozvDymH/6HwIlA==" crossorigin="anonymous"></script> <!--[if lt IE 10]> <style type="text/css">body {padding-left:60px;padding-right:60px;} #ienotice {display:block;} #oldienotice {display:block;}</style> <![endif]--> From d9c6b634b94d849bf56943094f2d6a96b94b6469 Mon Sep 17 00:00:00 2001 From: El RIDO <elrido@gmx.net> Date: Wed, 22 Nov 2017 22:44:38 +0100 Subject: [PATCH 10/10] remove dangling comma --- js/test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/test.js b/js/test.js index 06535f0..cc1a0a3 100644 --- a/js/test.js +++ b/js/test.js @@ -1464,7 +1464,7 @@ describe('PasteViewer', function () { '<FRAMESET><FRAME SRC="javascript:alert(\'XSS\');"></FRAMESET>', '<TABLE BACKGROUND="javascript:alert(\'XSS\')">', '<TABLE><TD BACKGROUND="javascript:alert(\'XSS\')">', - '<SCRIPT>document.write("<SCRI");</SCRIPT>PT SRC="httx://xss.rocks/xss.js"></SCRIPT>', + '<SCRIPT>document.write("<SCRI");</SCRIPT>PT SRC="httx://xss.rocks/xss.js"></SCRIPT>' ]), 'string', function (format, prefix, xss, suffix) {