1/** 2 * The OWASP CSRFGuard Project, BSD License 3 * Eric Sheridan ([email protected]), Copyright (c) 2011 4 * All rights reserved. 5 * 6 * Redistribution and use in source and binary forms, with or without 7 * modification, are permitted provided that the following conditions are met: 8 * 9 * 1. Redistributions of source code must retain the above copyright notice, 10 * this list of conditions and the following disclaimer. 11 * 2. Redistributions in binary form must reproduce the above copyright 12 * notice, this list of conditions and the following disclaimer in the 13 * documentation and/or other materials provided with the distribution. 14 * 3. Neither the name of OWASP nor the names of its contributors may be used 15 * to endorse or promote products derived from this software without specific 16 * prior written permission. 17 * 18 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 21 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 22 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 23 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 24 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 25 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 27 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 */ 29(function() { 30 31 function addEvent( obj, type, fn ) { 32 if (obj.addEventListener) { 33 obj.addEventListener( type, fn, false ); 34 EventCache.add(obj, type, fn); 35 } 36 else if (obj.attachEvent) { 37 obj["e"+type+fn] = fn; 38 obj[type+fn] = function() { obj["e"+type+fn]( window.event ); } 39 obj.attachEvent( "on"+type, obj[type+fn] ); 40 EventCache.add(obj, type, fn); 41 } 42 else { 43 obj["on"+type] = obj["e"+type+fn]; 44 } 45 } 46 47 var EventCache = function(){ 48 var listEvents = []; 49 return { 50 listEvents : listEvents, 51 add : function(node, sEventName, fHandler){ 52 listEvents.push(arguments); 53 }, 54 flush : function(){ 55 var i, item; 56 for(i = listEvents.length - 1; i >= 0; i = i - 1){ 57 item = listEvents[i]; 58 if(item[0].removeEventListener){ 59 item[0].removeEventListener(item[1], item[2], item[3]); 60 } 61 if(item[1].substring(0, 2) != "on"){ 62 item[1] = "on" + item[1]; 63 } 64 if(item[0].detachEvent){ 65 item[0].detachEvent(item[1], item[2]); 66 } 67 } 68 } 69 }; 70 }(); 71 72 /** string utility functions * */ 73 if(typeof String.prototype.startsWith !== "function") { 74 String.prototype.startsWith = function(prefix) { 75 return this.indexOf(prefix) === 0; 76 }; 77 } 78 if(typeof String.prototype.endsWith !== "function") { 79 String.prototype.endsWith = function(suffix) { 80 return this.match(suffix+"$") == suffix; 81 }; 82 } 83 84 /** hook using standards based prototype * */ 85 function hijackStandard() { 86 XMLHttpRequest.prototype._open = XMLHttpRequest.prototype.open; 87 XMLHttpRequest.prototype.open = function(method, url, async, user, pass) { 88 this.url = url; 89 90 this._open.apply(this, arguments); 91 }; 92 93 XMLHttpRequest.prototype._send = XMLHttpRequest.prototype.send; 94 XMLHttpRequest.prototype.send = function(data) { 95 if(this.onsend != null) { 96 this.onsend.apply(this, arguments); 97 } 98 99 this._send.apply(this, arguments); 100 } 101 } 102 103 /** ie does not properly support prototype - wrap completely * */ 104 function hijackExplorer() { 105 var _XMLHttpRequest = window.XMLHttpRequest; 106 107 function alloc_XMLHttpRequest() { 108 this.base = _XMLHttpRequest ? new _XMLHttpRequest : new window.ActiveXObject("Microsoft.XMLHTTP"); 109 } 110 111 function init_XMLHttpRequest() { 112 return new alloc_XMLHttpRequest; 113 } 114 115 init_XMLHttpRequest.prototype = alloc_XMLHttpRequest.prototype; 116 117 /** constants * */ 118 init_XMLHttpRequest.UNSENT = 0; 119 init_XMLHttpRequest.OPENED = 1; 120 init_XMLHttpRequest.HEADERS_RECEIVED = 2; 121 init_XMLHttpRequest.LOADING = 3; 122 init_XMLHttpRequest.DONE = 4; 123 124 /** properties * */ 125 init_XMLHttpRequest.prototype.status = 0; 126 init_XMLHttpRequest.prototype.statusText = ""; 127 init_XMLHttpRequest.prototype.readyState = init_XMLHttpRequest.UNSENT; 128 init_XMLHttpRequest.prototype.responseText = ""; 129 init_XMLHttpRequest.prototype.responseXML = null; 130 init_XMLHttpRequest.prototype.onsend = null; 131 132 init_XMLHttpRequest.url = null; 133 init_XMLHttpRequest.onreadystatechange = null; 134 135 /** methods * */ 136 init_XMLHttpRequest.prototype.open = function(method, url, async, user, pass) { 137 var self = this; 138 this.url = url; 139 140 this.base.open(method, url, async, user, pass); 141 142 this.base.onreadystatechange = function() { 143 try { self.status = self.base.status; } catch (e) { } 144 try { self.statusText = self.base.statusText; } catch (e) { } 145 try { self.readyState = self.base.readyState; } catch (e) { } 146 try { self.responseText = self.base.responseText; } catch(e) { } 147 try { self.responseXML = self.base.responseXML; } catch(e) { } 148 149 if(self.onreadystatechange != null) { 150 self.onreadystatechange.apply(this, arguments); 151 } 152 } 153 }; 154 155 init_XMLHttpRequest.prototype.send = function(data) { 156 if(this.onsend != null) { 157 this.onsend.apply(this, arguments); 158 } 159 160 this.base.send(data); 161 }; 162 163 init_XMLHttpRequest.prototype.abort = function() { 164 this.base.abort(); 165 }; 166 167 init_XMLHttpRequest.prototype.getAllResponseHeaders = function() { 168 return this.base.getAllResponseHeaders(); 169 }; 170 171 init_XMLHttpRequest.prototype.getResponseHeader = function(name) { 172 return this.base.getResponseHeader(name); 173 }; 174 175 init_XMLHttpRequest.prototype.setRequestHeader = function(name, value) { 176 return this.base.setRequestHeader(name, value); 177 }; 178 179 /** hook * */ 180 window.XMLHttpRequest = init_XMLHttpRequest; 181 } 182 183 /** check if valid domain based on domainStrict * */ 184 function isValidDomain(current, target) { 185 var result = false;
186 187 /** check exact or subdomain match * */ 188 if(current == target) { 189 result = true; 190 } else if(false == false) { 191 if(target.charAt(0) == '.') { 192 result = current.endsWith(target); 193 } else { 194 result = current.endsWith('.' + target); 195 } 196 } 197 198 return result; 199 } 200 201 /** determine if uri/url points to valid domain * */ 202 function isValidUrl(src) { 203 var result = false; 204 205 /** parse out domain to make sure it points to our own * */ 206 if(src.substring(0, 7) == "http://" || src.substring(0, 8) == "https://") { 207 var token = "://"; 208 var index = src.indexOf(token); 209 var part = src.substring(index + token.length); 210 var domain = ""; 211 212 /** parse up to end, first slash, or anchor * */ 213 for(i=0; i<part.length; i++) { 214 var character = part.charAt(i); 215 216 if(character == '/' || character == ':' || character == '#') { 217 break; 218 } else { 219 domain += character; 220 } 221 } 222 223 result = isValidDomain(document.domain, domain); 224 /** explicitly skip anchors * */ 225 } else if(src.charAt(0) == '#') { 226 result = false;
227 /** ensure it is a local resource without a protocol * */ 228 } else if(!src.startsWith("//") && (src.charAt(0) == '/' || src.indexOf(':') == -1)) { 229 result = true; 230 } 231 232 return result; 233 } 234 235 /** parse uri from url * */ 236 function parseUri(url) { 237 var uri = ""; 238 var token = "://"; 239 var index = url.indexOf(token); 240 var part = ""; 241 242 /** 243 * ensure to skip protocol and prepend context path for non-qualified 244 * resources (ex: "protect.html" vs 245 * "/Owasp.CsrfGuard.Test/protect.html"). 246 */ 247 if(index > 0) { 248 part = url.substring(index + token.length); 249 } else if(url.charAt(0) != '/') { 250 part = "/" + url; 251 } else { 252 part = url; 253 } 254 255 /** parse up to end or query string * */ 256 var uriContext = (index == -1); 257 258 for(var i=0; i<part.length; i++) { 259 var character = part.charAt(i); 260 261 if(character == '/') { 262 uriContext = true; 263 } else if(uriContext == true && (character == '?' || character == '#')) { 264 uriContext = false; 265 break; 266 } 267 268 if(uriContext == true) { 269 uri += character; 270 } 271 } 272 273 return uri; 274 } 275 276 /** inject tokens as hidden fields into forms **/ 277 function injectTokenForm(form, tokenName, tokenValue, pageTokens,injectGetForms) { 278 279 if (!injectGetForms) { 280 var method = form.getAttribute("method"); 281 282 if ((typeof method != 'undefined') && method != null && method.toLowerCase() == "get") { 283 return; 284 } 285 } 286 287 var value = tokenValue; 288 var action = form.getAttribute("action"); 289 290 if(action != null && isValidUrl(action)) { 291 var uri = parseUri(action); 292 value = pageTokens[uri] != null ? pageTokens[uri] : tokenValue; 293 } 294 295 var hidden = document.createElement("input"); 296 297 hidden.setAttribute("type", "hidden"); 298 hidden.setAttribute("name", tokenName); 299 hidden.setAttribute("value", value); 300 301 form.appendChild(hidden); 302 } 303 304 /** inject tokens as query string parameters into url **/ 305 function injectTokenAttribute(element, attr, tokenName, tokenValue, pageTokens) { 306 var location = element.getAttribute(attr); 307 308 if(location != null && isValidUrl(location)) { 309 var uri = parseUri(location); 310 var value = (pageTokens[uri] != null ? pageTokens[uri] : tokenValue); 311 312 if(location.indexOf('?') != -1) { 313 location = location + '&' + tokenName + '=' + value; 314 } else { 315 location = location + '?' + tokenName + '=' + value; 316 } 317 318 try { 319 element.setAttribute(attr, location); 320 } catch (e) { 321 // attempted to set/update unsupported attribute 322 } 323 } 324 } 325 326 /** inject csrf prevention tokens throughout dom **/ 327 function injectTokens(tokenName, tokenValue) { 328 /** obtain reference to page tokens if enabled **/ 329 var pageTokens = {}; 330 331 if(false == true) { 332 pageTokens = requestPageTokens(); 333 } 334 335 /** iterate over all elements and injection token **/ 336 var all = document.all ? document.all : document.getElementsByTagName('*'); 337 var len = all.length; 338 339 for(var i=0; i<len; i++) { 340 var element = all[i]; 341 342 /** inject into form **/ 343 if(element.tagName.toLowerCase() == "form") { 344 if(true) { 345 injectTokenForm(element, tokenName, tokenValue, pageTokens,false); 346 347 /** adjust array length after addition of new element **/ 348 len = all.length; 349 } 350 if (false) { 351 injectTokenAttribute(element, "action", tokenName, tokenValue, pageTokens); 352 } 353 /** inject into attribute **/ 354 } else if(false) { 355 injectTokenAttribute(element, "src", tokenName, tokenValue, pageTokens); 356 injectTokenAttribute(element, "href", tokenName, tokenValue, pageTokens); 357 } 358 } 359 } 360 361 /** obtain array of page specific tokens **/ 362 function requestPageTokens() { 363 var xhr = window.XMLHttpRequest ? new window.XMLHttpRequest : new window.ActiveXObject("Microsoft.XMLHTTP"); 364 var pageTokens = {}; 365 366 xhr.open("POST", "/csrf_js"+(typeof(resourceAccountIdRoutingURl)!="undefined"?"?"+resourceAccountIdRoutingURl:""), false); 367 xhr.send(null); 368 369 var text = xhr.responseText; 370 var name = ""; 371 var value = ""; 372 var nameContext = true; 373 374 for(var i=0; i<text.length; i++) { 375 var character = text.charAt(i); 376 377 if(character == ':') { 378 nameContext = false;
379 } else if(character != ',') { 380 if(nameContext == true) { 381 name += character; 382 } else { 383 value += character; 384 } 385 } 386 387 if(character == ',' || (i + 1) >= text.length) { 388 pageTokens[name] = value; 389 name = ""; 390 value = ""; 391 nameContext = true; 392 } 393 } 394 395 return pageTokens; 396 } 397 398 399 /** 400 * Only inject the tokens if the JavaScript was referenced from HTML that 401 * was served by us. Otherwise, the code was referenced from malicious HTML 402 * which may be trying to steal tokens using JavaScript hijacking 403 * techniques. 404 */ 405 if(isValidDomain(document.domain, "zoomgov.com")) { 406 /** optionally include Ajax support * */ 407 if(true == true) { 408 if(navigator.appName == "Microsoft Internet Explorer") { 409 hijackExplorer(); 410 } else { 411 hijackStandard(); 412 } 413 414 var xhr = window.XMLHttpRequest ? new window.XMLHttpRequest : new window.ActiveXObject("Microsoft.XMLHTTP"); 415 var csrfToken = {}; 416 xhr.open("POST", "/csrf_js"+(typeof(resourceAccountIdRoutingURl)!="undefined"?"?"+resourceAccountIdRoutingURl:""), false); 417 xhr.setRequestHeader("FETCH-CSRF-TOKEN", "1"); 418 xhr.send(null); 419 420 var token_pair = xhr.responseText; 421 token_pair = token_pair.split(":"); 422 var token_name = token_pair[0]; 423 var token_value = token_pair[1]; 424 425 XMLHttpRequest.prototype.onsend = function(data) { 426 if(isValidUrl(this.url)) { 427 this.setRequestHeader("X-Requested-With", "XMLHttpRequest"); 428 this.setRequestHeader("X-Requested-With", "OWASP CSRFGuard Project"); 429 this.setRequestHeader(token_name, token_value); 430 } 431 }; 432 } 433 434 /** update nodes in DOM after load **/ 435 addEvent(window,'unload',EventCache.flush); 436 addEvent(window,'load', function() { 437 injectTokens(token_name, token_value); 438 }); 439 } else { 440 alert("OWASP CSRFGuard JavaScript was included from within an unauthorized domain!"); 441 } 442})();
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.