diff --git a/.eslintrc b/.eslintrc index e2a42cc7..cee9820d 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 63087a54..7b141c51 100644 --- a/js/privatebin.js +++ b/js/privatebin.js @@ -12,6 +12,7 @@ */ /** global: Base64 */ +/** global: DOMPurify */ /** global: FileReader */ /** global: RawDeflate */ /** global: history */ @@ -42,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 = { - '&': '&', - '<': '<', - '>': '>', - '"': '"', - "'": ''', - '/': '/', - '`': '`', - '=': '=' - }; - /** * cache for script location * @@ -133,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
'); - $element.html('
' + html + '
'); - } - // for other (sane) browsers: - else - { - $element.text(text); - } - } - /** * convert URLs to clickable links. * URLs to handle: @@ -166,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 = '$1'; - $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, + '$1' ); } @@ -268,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 * @@ -1764,9 +1699,10 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) { return; } - // set text - Helper.setElementText($plainText, text); - Helper.setElementText($prettyPrint, text); + // 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': @@ -1775,30 +1711,28 @@ 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'); 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(text), 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'); @@ -2287,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) { @@ -2591,7 +2529,7 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) { for (var i = 0; i < $head.length; i++) { newDoc.write($head[i].outerHTML); } - newDoc.write('
' + Helper.htmlEntities(paste) + '
'); + newDoc.write('
' + DOMPurify.sanitize(paste, {SAFE_FOR_JQUERY: true}) + '
'); newDoc.close(); } diff --git a/js/purify.min.js b/js/purify.min.js new file mode 100644 index 00000000..5fe41d20 --- /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/js/test.js b/js/test.js index 387628e6..cc1a0a37 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 = { + '&': '&', + '<': '<', + '>': '>', + '"': '"', + "'": ''', + '/': '/', + '`': '`', + '=': '=' + }, logFile = require('fs').createWriteStream('test.log'); global.$ = global.jQuery = require('./jquery-3.1.1'); @@ -26,6 +42,7 @@ require('./prettify'); global.prettyPrint = window.PR.prettyPrint; global.prettyPrintOne = window.PR.prettyPrintOne; global.showdown = require('./showdown-1.6.1'); +global.DOMPurify = require('./purify.min'); require('./bootstrap-3.3.7'); require('./privatebin'); @@ -34,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 () { @@ -92,7 +125,7 @@ describe('Helper', function () { var html = '', result = true; ids.forEach(function(item, i) { - html += '

' + $.PrivateBin.Helper.htmlEntities(contents[i] || contents[0]) + '
'; + html += '
' + htmlEntities(contents[i] || contents[0]) + '
'; }); var clean = jsdom(html); ids.forEach(function(item, i) { @@ -107,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 += '
' + $.PrivateBin.Helper.htmlEntities(contents[i] || contents[0]) + '
'; - }); - var elements = $('').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(); @@ -144,10 +149,7 @@ describe('Helper', function () { 'ignores non-URL content', 'string', function (content) { - var element = $('
' + content + '
'), - before = element.html(); - $.PrivateBin.Helper.urls2links(element); - return before === element.html(); + return content === $.PrivateBin.Helper.urls2links(content); } ); jsc.property( @@ -162,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 = $('
' + prefix + url + postfix + '
'); + prefix = htmlEntities(prefix), + postfix = ' ' + htmlEntities(postfix); // special cases: When the query string and fragment imply the beginning of an HTML entity, eg. � or &#x if ( @@ -174,11 +175,9 @@ describe('Helper', function () { { url = schema + '://' + address.join('') + '/?' + query.substring(0, query.length - 1); postfix = ''; - element = $('
' + prefix + url + '
'); } - $.PrivateBin.Helper.urls2links(element); - return element.html() === $('
' + prefix + '' + url + '' + postfix + '
').html(); + return prefix + '' + url + '' + postfix === $.PrivateBin.Helper.urls2links(prefix + url + postfix); } ); jsc.property( @@ -188,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 = $('
' + prefix + url + ' ' + postfix + '
'); - $.PrivateBin.Helper.urls2links(element); - return element.html() === $('
' + prefix + '' + url + ' ' + postfix + '
').html(); + prefix = htmlEntities(prefix), + postfix = htmlEntities(postfix); + return prefix + '' + url + ' ' + postfix === $.PrivateBin.Helper.urls2links(prefix + url + ' ' + postfix); } ); }); @@ -337,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 && !(/&/.test(result))); } ); @@ -582,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 = ''; $('body').html(contents); - var result = $.PrivateBin.Helper.htmlEntities( + var result = htmlEntities( $.PrivateBin.Model.getExpirationDefault() ); $.PrivateBin.Model.reset(); @@ -616,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 = ''; $('body').html(contents); - var result = $.PrivateBin.Helper.htmlEntities( + var result = htmlEntities( $.PrivateBin.Model.getFormatDefault() ); $.PrivateBin.Model.reset(); @@ -648,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('
' + value + '
'); $.PrivateBin.Model.init(); var result = $.PrivateBin.Model.hasCipherData(); @@ -668,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('
' + value + '
'); $.PrivateBin.Model.init(); - var result = $.PrivateBin.Helper.htmlEntities( + var result = htmlEntities( $.PrivateBin.Model.getCipherData() ); $.PrivateBin.Model.reset(); @@ -1450,8 +1447,6 @@ 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))//--', '></SCRIPT>">\'><SCRIPT>alert(String.fromCharCode(88,83,83))</SCRIPT>', '\'\';!--"<XSS>=&{()}', '<SCRIPT SRC=http://example.com/xss.js></SCRIPT>', @@ -1464,8 +1459,12 @@ 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))>' - // 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>' ]), 'string', function (format, prefix, xss, suffix) { @@ -1481,11 +1480,10 @@ 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 0bf06854..203fbcba 100644 --- a/tpl/bootstrap.php +++ b/tpl/bootstrap.php @@ -66,10 +66,11 @@ endif; if ($MARKDOWN): ?> <script type="text/javascript" src="js/showdown-1.6.1.js" integrity="sha512-e6kAsBTgFnTBnEQXrq8BV6+XFwxb3kyWHeEPOl+KhxaWt3xImE2zAW2+yP3E2CQ7F9yoJl1poVU9qxkOEtVsTQ==" crossorigin="anonymous"></script> + <script type="text/javascript" src="js/purify.min.js?<?php echo rawurlencode($VERSION); ?>" integrity="sha512-jJuy143F5Oy7oS3VkjzeJGBxIUuQ1H0eSjuvLGD3FiQzeu8Pwp5vI/jQ2dxlxSrzejmNMicdLHnIqH7R8Ft0lQ==" crossorigin="anonymous"></script> <?php endif; ?> - <script type="text/javascript" src="js/privatebin.js?<?php echo rawurlencode($VERSION); ?>" integrity="sha512-NWkO7YojpZyTn/xpIjVwo+VBSVKIfxHjE9NC41GzcWgt3w36XNZ+wYZZjXLJh44rBj9qBmZiXvvt9g5iNkadlg==" 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 5d74bcf3..867f8ed0 100644 --- a/tpl/page.php +++ b/tpl/page.php @@ -44,10 +44,11 @@ endif; if ($MARKDOWN): ?> <script type="text/javascript" src="js/showdown-1.6.1.js" integrity="sha512-e6kAsBTgFnTBnEQXrq8BV6+XFwxb3kyWHeEPOl+KhxaWt3xImE2zAW2+yP3E2CQ7F9yoJl1poVU9qxkOEtVsTQ==" crossorigin="anonymous"></script> + <script type="text/javascript" src="js/purify.min.js?<?php echo rawurlencode($VERSION); ?>" integrity="sha512-jJuy143F5Oy7oS3VkjzeJGBxIUuQ1H0eSjuvLGD3FiQzeu8Pwp5vI/jQ2dxlxSrzejmNMicdLHnIqH7R8Ft0lQ==" crossorigin="anonymous"></script> <?php endif; ?> - <script type="text/javascript" src="js/privatebin.js?<?php echo rawurlencode($VERSION); ?>" integrity="sha512-NWkO7YojpZyTn/xpIjVwo+VBSVKIfxHjE9NC41GzcWgt3w36XNZ+wYZZjXLJh44rBj9qBmZiXvvt9g5iNkadlg==" 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]-->