a53b9958fa734f73aeffb9ddfe2fbad1ca65f90c galt Mon Jan 30 16:18:41 2017 -0800 Check-in of CSP2 Content-Security-Policy work. All C-language CGIs should now support CSP2 in browser to stop major forms of XSS javascript injection. Javascript on pages is gathered together, and then emitted in a single script block at the end with a nonce that tells the browser, this is js that we generated instead of being injected by a hacker. Both inline script from script blocks and inline js event handlers had to be pulled out and separated. You will not see js sprinkled through-out the page now. Older browsers that support CSP1 or that do not understand CSP at all will still work, just without protection. External js libraries loaded at runtime need to be added to the CSP policy header in src/lib/htmshell.c. diff --git src/hg/js/hgTracks.js src/hg/js/hgTracks.js index 54a5607..951d9f7 100644 --- src/hg/js/hgTracks.js +++ src/hg/js/hgTracks.js @@ -478,30 +478,55 @@ changeAssemblies: function (ele) // UNUSED? Larry's experimental code { // code to update page when user changes assembly select list. $.ajax({ type: "GET", url: "../cgi-bin/hgApi", data: cart.varsToUrlData({ 'cmd': 'defaultPos', 'db': getDb() }), dataType: "html", trueSuccess: genomePos.handleChange, success: catchErrorOrDispatch, error: errorHandler, cache: true }); return false; }, + + inlineJs : "", + + getInlineJs: function (response, status) + { + //alert("genomePos.getInlineJs called!"); // DEBUG REMOVE + genomePos.inlineJs = response; + }, + + fetchInlineJs: function (url) + { // code to fetch temp file with inline js in it. // OBSOLETE CSP1 + $.ajax({ + type: "GET", + async: false, // wait for result + url: url, + dataType: "html", + trueSuccess: genomePos.getInlineJs, + success: catchErrorOrDispatch, + error: errorHandler, + cache: false + }); + return genomePos.inlineJs; + }, + + convertedVirtCoords : {chromStart : -1, chromEnd : -1}, handleConvertChromPosToVirtCoords: function (response, status) { var virtStart = -1, virtEnd = -1; var newJson = scrapeVariable(response, "convertChromToVirtChrom"); if (!newJson) { warn("convertChromToVirtChrom object is missing from the response"); } else { virtStart = newJson.virtWinStart; virtEnd = newJson.virtWinEnd; } genomePos.convertedVirtCoords = {chromStart : virtStart, chromEnd : virtEnd}; }, @@ -3087,48 +3112,53 @@ // remove an existing dialog box var extToolDialog = $("#extToolDialog").remove(); // construct the contents var htmlLines = ["<ul class='indent'>"]; var winSize = hgTracks.winEnd - hgTracks.winStart; for (i = 0; i < extTools.length; i++) { var tool = extTools[i]; var toolId = tool[0]; var shortLabel = tool[1]; var longLabel = tool[2]; var maxSize = tool[3]; if ((maxSize===0) || (winSize < maxSize)) { var url = "hgTracks?hgsid="+getHgsid()+"&hgt.redirectTool="+toolId; - var onclick = "$('#extToolDialog').dialog('close');"; - htmlLines.push("<li><a onclick="+'"'+onclick+'"'+"id='extToolLink' target='_BLANK' href='"+url+"'>"+shortLabel+"</a>: <small>"+longLabel+"</small></li>"); + //var onclick = "$('#extToolDialog').dialog('close');"; + //htmlLines.push("<li><a onclick="+'"'+onclick+'"'+"id='extToolLink' target='_BLANK' href='"+url+"'>"+shortLabel+"</a>: <small>"+longLabel+"</small></li>"); + // onclick js code moved to jsInline + htmlLines.push("<li><a class='extToolLink2' target='_BLANK' href='"+url+"'>"+shortLabel+"</a>: <small>"+longLabel+"</small></li>"); } else { note = "<br><b>Needs zoom to < "+maxSize/1000+" kbp.</b></small></span></li>"; htmlLines.push('<li><span style="color:grey">'+shortLabel+": <small>"+longLabel+note); } } htmlLines.push("</ul>"); content = htmlLines.join(""); var title = hgTracks.chromName + ":" + (hgTracks.winStart+1) + "-" + hgTracks.winEnd; if (hgTracks.nonVirtPosition) title = hgTracks.nonVirtPosition; title += " on another website"; $("body").append("<div id='extToolDialog' title='"+title+"'><p>" + content + "</p>"); + // GALT + $('a.extToolLink2').click(function(){$('#extToolDialog').dialog('close');}); + // copied from the hgTrackUi function below var popMaxHeight = ($(window).height() - 40); var popMaxWidth = ($(window).width() - 40); var popWidth = 600; if (popWidth > popMaxWidth) popWidth = popMaxWidth; // also copied from the hgTrackUi code below $('#extToolDialog').dialog({ resizable: true, // Let description scroll vertically height: popMaxHeight, width: popWidth, minHeight: 200, minWidth: 600, maxHeight: popMaxHeight, @@ -3176,49 +3206,51 @@ url: cart.addUpdatesToUrl(myLink), dataType: "html", trueSuccess: popUpHgt.uiDialog, success: catchErrorOrDispatch, error: errorHandler, cache: false }); }, hgTracks: function (whichHgTracksMethod) { // Launches the popup but shields the ajax with a waitOnFunction waitOnFunction( popUpHgt._uiDialogRequest, whichHgTracksMethod); }, uiDialogOk: function (popObj) - { // When hgTracks Cfg popup closes with ok, then update cart and refresh parts of page + { // When popup closes with ok }, uiDialog: function (response, status) { // Take html from hgTracks and put it up as a modal dialog. // make sure all links (e.g. help links) open up in a new window response = response.replace(/<a /ig, "<a target='_blank' "); var cleanHtml = response; + cleanHtml = stripCspHeader(cleanHtml,false); // DEBUG msg with true cleanHtml = stripJsFiles(cleanHtml,false); // DEBUG msg with true cleanHtml = stripCssFiles(cleanHtml,false); // DEBUG msg with true cleanHtml = stripJsEmbedded(cleanHtml,false);// DEBUG msg with true cleanHtml = stripMainMenu(cleanHtml,false); // DEBUG msg with true $('#hgTracksDialog').html("<div id='pop' style='font-size:.9em;'>"+ cleanHtml +"</div>"); + // Strategy for popups with js: // - jsFiles and CSS should not be included in html. Here they are shluped out. // - The resulting files ought to be loadable dynamically (with getScript()), // but this was not working nicely with the modal dialog // Therefore include files must be included with hgTracks CGI ! // - embedded js should not be in the popup box. // - Somethings should be in a popup.ready() function, and this is emulated below, // as soon as the cleanHtml is added // Since there are many possible popup cfg dialogs, the ready should be all inclusive. // -- popup.ready() -- Here is the place to do things that might otherwise go // into a $('#pop').ready() routine! // Searching for some semblance of size suitability var popMaxHeight = ($(window).height() - 40); @@ -3276,32 +3308,33 @@ $('#hgTracksDialog').dialog('option' , 'title' , popUpHgt.title); $('#hgTracksDialog').dialog('open'); } }; // A function to show the keyboard help dialog box, bound to ? and called from the menu bar function showHotkeyHelp() { $("#hotkeyHelp").dialog({width:'600'}); } // A function to add an entry for the keyboard help dialog box to the menubar // and add text that indicates the shortcuts to many static menubar items as suggested by good old IBM CUA/SAA function addKeyboardHelpEntries() { - var html = '<li><a title="List all possible keyboard shortcuts" href="javascript:showHotkeyHelp()">Keyboard Shortcuts</a><span class="shortcut">?</span></li>'; + var html = '<li><a id="keybShorts" title="List all possible keyboard shortcuts" href="#">Keyboard Shortcuts</a><span class="shortcut">?</span></li>'; $('#help .last').before(html); + $("#keybShorts").onclick = function(){showHotKeyHelp(); return false;}; html = '<span class="shortcut">s s</span>'; $('#sessionsMenuLink').after(html); html = '<span class="shortcut">p s</span>'; $('#publicSessionsMenuLink').after(html); html = '<span class="shortcut">c t</span>'; $('#customTracksMenuLink').after(html); html = '<span class="shortcut">t h</span>'; $('#trackHubsMenuLink').after(html); html = '<span class="shortcut">t b</span>'; $('#blatMenuLink').after(html); @@ -3455,37 +3488,40 @@ var urlData = cart.varsToUrlData(changedVars); if (imageV2.mapIsUpdateable) { imageV2.requestImgUpdate(trackName,urlData,""); } else { window.location = "../cgi-bin/hgTracks?" + urlData + "&hgsid=" + getHgsid(); } } } } }, uiDialog: function (response, status) { // Take html from hgTrackUi and put it up as a modal dialog. + //alert("Got here popUp.uiDialog"); // DEBUG REMOVE GALT + // make sure all links (e.g. help links) open up in a new window response = response.replace(/<a /ig, "<a target='_blank' "); var cleanHtml = response; - cleanHtml = stripJsFiles(cleanHtml,true); // DEBUG msg with true - cleanHtml = stripCssFiles(cleanHtml,true); // DEBUG msg with true - cleanHtml = stripJsEmbedded(cleanHtml,true);// DEBUG msg with true + cleanHtml = stripJsFiles(cleanHtml,false); // DEBUG msg with true + cleanHtml = stripCssFiles(cleanHtml,false); // DEBUG msg with true + cleanHtml = stripJsEmbedded(cleanHtml,false);// DEBUG msg with true + //alert(cleanHtml); // DEBUG REMOVE $('#hgTrackUiDialog').html("<div id='pop' style='font-size:.9em;'>"+ cleanHtml +"</div>"); // Strategy for popups with js: // - jsFiles and CSS should not be included in html. Here they are shluped out. // - The resulting files ought to be loadable dynamically (with getScript()), // but this was not working nicely with the modal dialog // Therefore include files must be included with hgTracks CGI ! // - embedded js should not be in the popup box. // - Somethings should be in a popup.ready() function, and this is emulated below, // as soon as the cleanHtml is added // Since there are many possible popup cfg dialogs, the ready should be all inclusive. if ( ! popUp.trackDescriptionOnly ) { // If subtrack then vis rules differ var subtrack = tdbIsSubtrack(hgTracks.trackDb[popUp.trackName]) ? popUp.trackName :""; @@ -3899,32 +3935,66 @@ } window.location.assign(url); return false; } document.TrackHeaderForm.submit(); }, updateImgAndMap: function (response, status) { // Handle ajax response with an updated trackMap image, map and optional ideogram. // and maybe the redLines background too. // this.cmd can be used to figure out which menu item triggered this. // this.id === appropriate track if we are retrieving just a single track. // update local hgTracks.trackDb to reflect possible side-effects of ajax request. + // alert("response=["+response+"]"); // DEBUG GALT REMOVE + var newJson = scrapeVariable(response, "hgTracks"); + // added by GALT for CSP/XSS + if (!newJson) { + // OLD CSP1 way not using now? + var strippedJsFiles = {}; + stripJsFiles(response, false, strippedJsFiles); + //alert(strippedJsFiles.toSource()); // DEBUG GALT FF ONLY + //alert("strippedJsFiles.jsFiles="+strippedJsFiles.jsFiles+""); // DEBUG GALT REMOVE + var inlinePath = ""; + var i, len; + if (strippedJsFiles.jsFiles) { + for (i = 0, len = strippedJsFiles.jsFiles.length; i < len; ++i) { + //alert(strippedJsFiles.jsFiles[i]); // DEBUG REMOVE + var srcPattern="<script type='text/javascript' SRC='(.*)'></script>"; + var reg = new RegExp(srcPattern); + var a = reg.exec(strippedJsFiles.jsFiles[i]); + if (a && a[1]) { + if (a[1].match("inline")) { + inlinePath = a[1]; + //alert("SRC found: "+a[1]); // DEBUG REMOVE + } + } + } + } + if (inlinePath !== "") { + //alert("inlinePath found: "+inlinePath); // DEBUG REMOVE + var js = genomePos.fetchInlineJs(inlinePath); + //alert(js); // DEBUG REMOVE + response += ("<script type='text/javascript'>"+js+"</script>"); + newJson = scrapeVariable(response, "hgTracks"); + } + } + //alert(JSON.stringify(newJson)); // DEBUG Example var oldJson = hgTracks; var valid = false; if (!newJson) { var stripped = {}; stripJsEmbedded(response, true, stripped); if ( ! stripped.warnMsg ) warn("hgTracks object is missing from the response"); } else { if (this.id) { if (newJson.trackDb[this.id]) { var visibility = vis.enumOrder[newJson.trackDb[this.id].visibility]; var limitedVis; if (newJson.trackDb[this.id].limitedVis)