From eceae7187a6f0e9510bc1165f6977256b87f490f Mon Sep 17 00:00:00 2001 From: hackademix Date: Sun, 1 Jul 2018 01:01:23 +0200 Subject: Initial commit starting at version 10.1.8.3rc4. --- src/xss/ASPIdiocy.js | 638 +++++++++++++++++++++++ src/xss/Exceptions.js | 238 +++++++++ src/xss/FlashIdiocy.js | 147 ++++++ src/xss/InjectionChecker.js | 1199 +++++++++++++++++++++++++++++++++++++++++++ src/xss/XSS.js | 246 +++++++++ src/xss/sanitizeName.js | 4 + 6 files changed, 2472 insertions(+) create mode 100644 src/xss/ASPIdiocy.js create mode 100644 src/xss/Exceptions.js create mode 100644 src/xss/FlashIdiocy.js create mode 100644 src/xss/InjectionChecker.js create mode 100644 src/xss/XSS.js create mode 100644 src/xss/sanitizeName.js (limited to 'src/xss') diff --git a/src/xss/ASPIdiocy.js b/src/xss/ASPIdiocy.js new file mode 100644 index 0000000..c9958f1 --- /dev/null +++ b/src/xss/ASPIdiocy.js @@ -0,0 +1,638 @@ +'use strict'; + +var ASPIdiocy = XSS.ASPIdiocy = { + _replaceRx: /%u([0-9a-fA-F]{4})/g, + _affectsRx: /%u[0-9a-fA-F]{4}/, + _badPercentRx: /%(?!u[0-9a-fA-F]{4}|[0-9a-fA-F]{2})|%(?:00|u0000)[^&=]*/g, + + hasBadPercents(s) { + return this._badPercentRx.test(s) + }, + removeBadPercents(s) { + return s.replace(this._badPercentRx, ''); + }, + affects(s) { + return this._affectsRx.test(s); + }, + process(s) { + s = this.filter(s); + return /[\uff5f-\uffff]/.test(s) ? s + '&' + s.replace(/[\uff5f-\uffff]/g, '?') : s; + }, + filter(s) { + return this.removeBadPercents(s).replace(this._replaceRx, this._replace) + }, + + coalesceQuery(s) { // HPP protection, see https://www.owasp.org/images/b/ba/AppsecEU09_CarettoniDiPaola_v0.8.pdf + let qm = s.indexOf("?"); + if (qm < 0) return s; + let p = s.substring(0, qm); + let q = s.substring(qm + 1); + if (!q) return s; + + let unchanged = true; + let emptyParams = false; + + let pairs = (function rearrange(joinNames) { + let pairs = q.split("&"); + let accumulator = { + __proto__: null + }; + for (let j = 0, len = pairs.length; j < len; j++) { + let nv = pairs[j]; + let eq = nv.indexOf("="); + if (eq === -1) { + emptyParams = true; + if (joinNames && j < len - 1) { + pairs[j + 1] = nv + "&" + pairs[j + 1]; + delete pairs[j]; + } + continue; + } + let key = "#" + unescape(nv.substring(0, eq)).toLowerCase(); + if (key in accumulator) { + delete pairs[j]; + pairs[accumulator[key]] += ", " + nv.substring(eq + 1); + unchanged = false; + } else { + accumulator[key] = j; + } + } + return (emptyParams && !(unchanged || joinNames)) ? + pairs.concat(rearrange(true).filter(p => pairs.indexOf(p) === -1)) : + pairs; + })(); + + if (unchanged) return s; + for (let j = pairs.length; j-- > 0;) + if (!pairs[j]) pairs.splice(j, 1); + return p + pairs.join("&"); + }, + + _replace(match, hex) { + const k = parseInt(hex, 16); + const map = ASPIdiocy.map; + if (k in map) return map[k]; + const range = ASPIdiocy._findRange(k); + return range && range.data || String.fromCharCode(k); + }, + _findRange(k) { + const ranges = this.ranges; + for (let low = 0, high = ranges.length - 1; low <= high;) { + let i = parseInt((low + high) / 2); + let r = ranges[i]; + let comparison = k < r.start ? 1 : k > r.end ? -1 : 0; + if (comparison < 0) low = i + 1; + else if (comparison > 0) high = i - 1; + else return r; + } + return null; + } +} + +XSS.ASPIdiocy.map = { + 0x100: "\x41", + 0x101: "\x61", + 0x102: "\x41", + 0x103: "\x61", + 0x104: "\x41", + 0x105: "\x61", + 0x106: "\x43", + 0x107: "\x63", + 0x108: "\x43", + 0x109: "\x63", + 0x10a: "\x43", + 0x10b: "\x63", + 0x10c: "\x43", + 0x10d: "\x63", + 0x10e: "\x44", + 0x10f: "\x64", + 0x110: "\ufffd", + 0x111: "\x64", + 0x112: "\x45", + 0x113: "\x65", + 0x114: "\x45", + 0x115: "\x65", + 0x116: "\x45", + 0x117: "\x65", + 0x118: "\x45", + 0x119: "\x65", + 0x11a: "\x45", + 0x11b: "\x65", + 0x11c: "\x47", + 0x11d: "\x67", + 0x11e: "\x47", + 0x11f: "\x67", + 0x120: "\x47", + 0x121: "\x67", + 0x122: "\x47", + 0x123: "\x67", + 0x124: "\x48", + 0x125: "\x68", + 0x126: "\x48", + 0x127: "\x68", + 0x128: "\x49", + 0x129: "\x69", + 0x12a: "\x49", + 0x12b: "\x69", + 0x12c: "\x49", + 0x12d: "\x69", + 0x12e: "\x49", + 0x12f: "\x69", + 0x130: "\x49", + 0x131: "\x69", + 0x134: "\x4a", + 0x135: "\x6a", + 0x136: "\x4b", + 0x137: "\x6b", + 0x138: "\x3f", + 0x139: "\x4c", + 0x13a: "\x6c", + 0x13b: "\x4c", + 0x13c: "\x6c", + 0x13d: "\x4c", + 0x13e: "\x6c", + 0x141: "\x4c", + 0x142: "\x6c", + 0x143: "\x4e", + 0x144: "\x6e", + 0x145: "\x4e", + 0x146: "\x6e", + 0x147: "\x4e", + 0x148: "\x6e", + 0x14c: "\x4f", + 0x14d: "\x6f", + 0x14e: "\x4f", + 0x14f: "\x6f", + 0x150: "\x4f", + 0x151: "\x6f", + 0x154: "\x52", + 0x155: "\x72", + 0x156: "\x52", + 0x157: "\x72", + 0x158: "\x52", + 0x159: "\x72", + 0x15a: "\x53", + 0x15b: "\x73", + 0x15c: "\x53", + 0x15d: "\x73", + 0x15e: "\x53", + 0x15f: "\x73", + 0x162: "\x54", + 0x163: "\x74", + 0x164: "\x54", + 0x165: "\x74", + 0x166: "\x54", + 0x167: "\x74", + 0x168: "\x55", + 0x169: "\x75", + 0x16a: "\x55", + 0x16b: "\x75", + 0x16c: "\x55", + 0x16d: "\x75", + 0x16e: "\x55", + 0x16f: "\x75", + 0x170: "\x55", + 0x171: "\x75", + 0x172: "\x55", + 0x173: "\x75", + 0x174: "\x57", + 0x175: "\x77", + 0x176: "\x59", + 0x177: "\x79", + 0x178: "\ufffd", + 0x179: "\x5a", + 0x17a: "\x7a", + 0x17b: "\x5a", + 0x17c: "\x7a", + 0x17f: "\x3f", + 0x180: "\x62", + 0x189: "\ufffd", + 0x197: "\x49", + 0x19a: "\x6c", + 0x1a1: "\x6f", + 0x1ab: "\x74", + 0x1ae: "\x54", + 0x1af: "\x55", + 0x1b0: "\x75", + 0x1b6: "\x7a", + 0x1c0: "\x7c", + 0x1c3: "\x21", + 0x1cd: "\x41", + 0x1ce: "\x61", + 0x1cf: "\x49", + 0x1d0: "\x69", + 0x1d1: "\x4f", + 0x1d2: "\x6f", + 0x1d3: "\x55", + 0x1d4: "\x75", + 0x1d5: "\x55", + 0x1d6: "\x75", + 0x1d7: "\x55", + 0x1d8: "\x75", + 0x1d9: "\x55", + 0x1da: "\x75", + 0x1db: "\x55", + 0x1dc: "\x75", + 0x1dd: "\x3f", + 0x1de: "\x41", + 0x1df: "\x61", + 0x1e4: "\x47", + 0x1e5: "\x67", + 0x1e6: "\x47", + 0x1e7: "\x67", + 0x1e8: "\x4b", + 0x1e9: "\x6b", + 0x1ea: "\x4f", + 0x1eb: "\x6f", + 0x1ec: "\x4f", + 0x1ed: "\x6f", + 0x1f0: "\x6a", + 0x261: "\x67", + 0x2b9: "\x27", + 0x2ba: "\x22", + 0x2bb: "\x3f", + 0x2bc: "\x27", + 0x2c4: "\x5e", + 0x2c5: "\x3f", + 0x2c6: "\ufffd", + 0x2c7: "\x3f", + 0x2c8: "\x27", + 0x2cb: "\x60", + 0x2cc: "\x3f", + 0x2cd: "\x5f", + 0x2da: "\ufffd", + 0x2db: "\x3f", + 0x2dc: "\ufffd", + 0x300: "\x60", + 0x301: "\ufffd", + 0x302: "\x5e", + 0x303: "\x7e", + 0x308: "\ufffd", + 0x309: "\x3f", + 0x30a: "\ufffd", + 0x30e: "\x22", + 0x327: "\ufffd", + 0x37e: "\x3b", + 0x393: "\x47", + 0x398: "\x54", + 0x3a3: "\x53", + 0x3a6: "\x46", + 0x3a9: "\x4f", + 0x3b1: "\x61", + 0x3b2: "\ufffd", + 0x3b3: "\x3f", + 0x3b4: "\x64", + 0x3b5: "\x65", + 0x3bc: "\ufffd", + 0x3c0: "\x70", + 0x3c3: "\x73", + 0x3c4: "\x74", + 0x3c5: "\x3f", + 0x3c6: "\x66", + 0x4bb: "\x68", + 0x589: "\x3a", + 0x66a: "\x25", + 0x2012: "\x3f", + 0x2017: "\x3d", + 0x201b: "\x3f", + 0x201f: "\x3f", + 0x2023: "\x3f", + 0x2024: "\ufffd", + 0x2025: "\x3f", + 0x2026: "\ufffd", + 0x2030: "\ufffd", + 0x2031: "\x3f", + 0x2032: "\x27", + 0x2035: "\x60", + 0x2044: "\x2f", + 0x2070: "\ufffd", + 0x2074: "\x34", + 0x2075: "\x35", + 0x2076: "\x36", + 0x2077: "\x37", + 0x2078: "\x38", + 0x207f: "\x6e", + 0x2080: "\x30", + 0x2081: "\x31", + 0x2082: "\x32", + 0x2083: "\x33", + 0x2084: "\x34", + 0x2085: "\x35", + 0x2086: "\x36", + 0x2087: "\x37", + 0x2088: "\x38", + 0x2089: "\x39", + 0x20a1: "\ufffd", + 0x20a4: "\ufffd", + 0x20a7: "\x50", + 0x20ac: "\ufffd", + 0x2102: "\x43", + 0x2107: "\x45", + 0x210a: "\x67", + 0x210e: "\x68", + 0x210f: "\x3f", + 0x2112: "\x4c", + 0x2113: "\x6c", + 0x2114: "\x3f", + 0x2115: "\x4e", + 0x211a: "\x51", + 0x2122: "\ufffd", + 0x2123: "\x3f", + 0x2124: "\x5a", + 0x2128: "\x5a", + 0x2129: "\x3f", + 0x212a: "\x4b", + 0x212b: "\ufffd", + 0x212c: "\x42", + 0x212d: "\x43", + 0x2130: "\x45", + 0x2131: "\x46", + 0x2132: "\x3f", + 0x2133: "\x4d", + 0x2134: "\x6f", + 0x2205: "\ufffd", + 0x2212: "\x2d", + 0x2213: "\ufffd", + 0x2214: "\x3f", + 0x2215: "\x2f", + 0x2216: "\x5c", + 0x2217: "\x2a", + 0x221a: "\x76", + 0x221e: "\x38", + 0x2223: "\x7c", + 0x2229: "\x6e", + 0x2236: "\x3a", + 0x223c: "\x7e", + 0x2248: "\ufffd", + 0x2261: "\x3d", + 0x22c5: "\ufffd", + 0x2302: "\ufffd", + 0x2303: "\x5e", + 0x2310: "\ufffd", + 0x2320: "\x28", + 0x2321: "\x29", + 0x2329: "\x3c", + 0x232a: "\x3e", + 0x2500: "\x2d", + 0x2501: "\x3f", + 0x2502: "\ufffd", + 0x250c: "\x2b", + 0x2510: "\x2b", + 0x2514: "\x2b", + 0x2518: "\x2b", + 0x251c: "\x2b", + 0x2524: "\ufffd", + 0x252c: "\x2d", + 0x2534: "\x2d", + 0x253c: "\x2b", + 0x2550: "\x2d", + 0x2551: "\ufffd", + 0x2580: "\ufffd", + 0x2584: "\x5f", + 0x2588: "\ufffd", + 0x258c: "\ufffd", + 0x25a0: "\ufffd", + 0x263c: "\ufffd", + 0x2758: "\x7c", + 0x3000: "\x20", + 0x3008: "\x3c", + 0x3009: "\x3e", + 0x301a: "\x5b", + 0x301b: "\x5d", + 0x30fb: "\ufffd", + 0xff01: "\x21", + 0xff02: "\x22", + 0xff03: "\x23", + 0xff04: "\x24", + 0xff05: "\x25", + 0xff06: "\x26", + 0xff07: "\x27", + 0xff08: "\x28", + 0xff09: "\x29", + 0xff0a: "\x2a", + 0xff0b: "\x2b", + 0xff0c: "\x2c", + 0xff0d: "\x2d", + 0xff0e: "\x2e", + 0xff0f: "\x2f", + 0xff10: "\x30", + 0xff11: "\x31", + 0xff12: "\x32", + 0xff13: "\x33", + 0xff14: "\x34", + 0xff15: "\x35", + 0xff16: "\x36", + 0xff17: "\x37", + 0xff18: "\x38", + 0xff19: "\x39", + 0xff1a: "\x3a", + 0xff1b: "\x3b", + 0xff1c: "\x3c", + 0xff1d: "\x3d", + 0xff1e: "\x3e", + 0xff1f: "\x3f", + 0xff20: "\x40", + 0xff21: "\x41", + 0xff22: "\x42", + 0xff23: "\x43", + 0xff24: "\x44", + 0xff25: "\x45", + 0xff26: "\x46", + 0xff27: "\x47", + 0xff28: "\x48", + 0xff29: "\x49", + 0xff2a: "\x4a", + 0xff2b: "\x4b", + 0xff2c: "\x4c", + 0xff2d: "\x4d", + 0xff2e: "\x4e", + 0xff2f: "\x4f", + 0xff30: "\x50", + 0xff31: "\x51", + 0xff32: "\x52", + 0xff33: "\x53", + 0xff34: "\x54", + 0xff35: "\x55", + 0xff36: "\x56", + 0xff37: "\x57", + 0xff38: "\x58", + 0xff39: "\x59", + 0xff3a: "\x5a", + 0xff3b: "\x5b", + 0xff3c: "\x5c", + 0xff3d: "\x5d", + 0xff3e: "\x5e", + 0xff3f: "\x5f", + 0xff40: "\x60", + 0xff41: "\x61", + 0xff42: "\x62", + 0xff43: "\x63", + 0xff44: "\x64", + 0xff45: "\x65", + 0xff46: "\x66", + 0xff47: "\x67", + 0xff48: "\x68", + 0xff49: "\x69", + 0xff4a: "\x6a", + 0xff4b: "\x6b", + 0xff4c: "\x6c", + 0xff4d: "\x6d", + 0xff4e: "\x6e", + 0xff4f: "\x6f", + 0xff50: "\x70", + 0xff51: "\x71", + 0xff52: "\x72", + 0xff53: "\x73", + 0xff54: "\x74", + 0xff55: "\x75", + 0xff56: "\x76", + 0xff57: "\x77", + 0xff58: "\x78", + 0xff59: "\x79", + 0xff5a: "\x7a", + 0xff5b: "\x7b", + 0xff5c: "\x7c", + 0xff5d: "\x7d", + 0xff5e: "\x7e" +}; + +{ + let Range = class { + constructor(start, end, data) { + this.start = start; + this.end = end; + this.data = data; + } + }; + + XSS.ASPIdiocy.ranges = [ + new Range(0x80, 0xff, "\ufffd"), + new Range(0x132, 0x133, "\x3f"), + new Range(0x13f, 0x140, "\x3f"), + new Range(0x149, 0x14b, "\x3f"), + new Range(0x152, 0x153, "\ufffd"), + new Range(0x160, 0x161, "\ufffd"), + new Range(0x17d, 0x17e, "\ufffd"), + new Range(0x181, 0x188, "\x3f"), + new Range(0x18a, 0x190, "\x3f"), + new Range(0x191, 0x192, "\ufffd"), + new Range(0x193, 0x196, "\x3f"), + new Range(0x198, 0x199, "\x3f"), + new Range(0x19b, 0x19e, "\x3f"), + new Range(0x19f, 0x1a0, "\x4f"), + new Range(0x1a2, 0x1aa, "\x3f"), + new Range(0x1ac, 0x1ad, "\x3f"), + new Range(0x1b1, 0x1b5, "\x3f"), + new Range(0x1b7, 0x1bf, "\x3f"), + new Range(0x1c1, 0x1c2, "\x3f"), + new Range(0x1c4, 0x1cc, "\x3f"), + new Range(0x1e0, 0x1e3, "\x3f"), + new Range(0x1ee, 0x1ef, "\x3f"), + new Range(0x1f1, 0x260, "\x3f"), + new Range(0x262, 0x2b8, "\x3f"), + new Range(0x2bd, 0x2c3, "\x3f"), + new Range(0x2c9, 0x2ca, "\ufffd"), + new Range(0x2ce, 0x2d9, "\x3f"), + new Range(0x2dd, 0x2ff, "\x3f"), + new Range(0x304, 0x305, "\ufffd"), + new Range(0x306, 0x307, "\x3f"), + new Range(0x30b, 0x30d, "\x3f"), + new Range(0x30f, 0x326, "\x3f"), + new Range(0x328, 0x330, "\x3f"), + new Range(0x331, 0x332, "\x5f"), + new Range(0x333, 0x37d, "\x3f"), + new Range(0x37f, 0x392, "\x3f"), + new Range(0x394, 0x397, "\x3f"), + new Range(0x399, 0x3a2, "\x3f"), + new Range(0x3a4, 0x3a5, "\x3f"), + new Range(0x3a7, 0x3a8, "\x3f"), + new Range(0x3aa, 0x3b0, "\x3f"), + new Range(0x3b6, 0x3bb, "\x3f"), + new Range(0x3bd, 0x3bf, "\x3f"), + new Range(0x3c1, 0x3c2, "\x3f"), + new Range(0x3c7, 0x4ba, "\x3f"), + new Range(0x4bc, 0x588, "\x3f"), + new Range(0x58a, 0x669, "\x3f"), + new Range(0x66b, 0x1fff, "\x3f"), + new Range(0x2000, 0x2006, "\x20"), + new Range(0x2007, 0x200f, "\x3f"), + new Range(0x2010, 0x2011, "\x2d"), + new Range(0x2013, 0x2014, "\ufffd"), + new Range(0x2015, 0x2016, "\x3f"), + new Range(0x2018, 0x201a, "\ufffd"), + new Range(0x201c, 0x201e, "\ufffd"), + new Range(0x2020, 0x2022, "\ufffd"), + new Range(0x2027, 0x202f, "\x3f"), + new Range(0x2033, 0x2034, "\x3f"), + new Range(0x2036, 0x2038, "\x3f"), + new Range(0x2039, 0x203a, "\ufffd"), + new Range(0x203b, 0x2043, "\x3f"), + new Range(0x2045, 0x206f, "\x3f"), + new Range(0x2071, 0x2073, "\x3f"), + new Range(0x2079, 0x207e, "\x3f"), + new Range(0x208a, 0x20a0, "\x3f"), + new Range(0x20a2, 0x20a3, "\x3f"), + new Range(0x20a5, 0x20a6, "\x3f"), + new Range(0x20a8, 0x20ab, "\x3f"), + new Range(0x20ad, 0x2101, "\x3f"), + new Range(0x2103, 0x2106, "\x3f"), + new Range(0x2108, 0x2109, "\x3f"), + new Range(0x210b, 0x210d, "\x48"), + new Range(0x2110, 0x2111, "\x49"), + new Range(0x2116, 0x2117, "\x3f"), + new Range(0x2118, 0x2119, "\x50"), + new Range(0x211b, 0x211d, "\x52"), + new Range(0x211e, 0x2121, "\x3f"), + new Range(0x2125, 0x2127, "\x3f"), + new Range(0x212e, 0x212f, "\x65"), + new Range(0x2135, 0x2204, "\x3f"), + new Range(0x2206, 0x2211, "\x3f"), + new Range(0x2218, 0x2219, "\ufffd"), + new Range(0x221b, 0x221d, "\x3f"), + new Range(0x221f, 0x2222, "\x3f"), + new Range(0x2224, 0x2228, "\x3f"), + new Range(0x222a, 0x2235, "\x3f"), + new Range(0x2237, 0x223b, "\x3f"), + new Range(0x223d, 0x2247, "\x3f"), + new Range(0x2249, 0x2260, "\x3f"), + new Range(0x2262, 0x2263, "\x3f"), + new Range(0x2264, 0x2265, "\x3d"), + new Range(0x2266, 0x2269, "\x3f"), + new Range(0x226a, 0x226b, "\ufffd"), + new Range(0x226c, 0x22c4, "\x3f"), + new Range(0x22c6, 0x2301, "\x3f"), + new Range(0x2304, 0x230f, "\x3f"), + new Range(0x2311, 0x231f, "\x3f"), + new Range(0x2322, 0x2328, "\x3f"), + new Range(0x232b, 0x24ff, "\x3f"), + new Range(0x2503, 0x250b, "\x3f"), + new Range(0x250d, 0x250f, "\x3f"), + new Range(0x2511, 0x2513, "\x3f"), + new Range(0x2515, 0x2517, "\x3f"), + new Range(0x2519, 0x251b, "\x3f"), + new Range(0x251d, 0x2523, "\x3f"), + new Range(0x2525, 0x252b, "\x3f"), + new Range(0x252d, 0x2533, "\x3f"), + new Range(0x2535, 0x253b, "\x3f"), + new Range(0x253d, 0x254f, "\x3f"), + new Range(0x2552, 0x255d, "\x2b"), + new Range(0x255e, 0x2563, "\ufffd"), + new Range(0x2564, 0x2569, "\x2d"), + new Range(0x256a, 0x256c, "\x2b"), + new Range(0x256d, 0x257f, "\x3f"), + new Range(0x2581, 0x2583, "\x3f"), + new Range(0x2585, 0x2587, "\x3f"), + new Range(0x2589, 0x258b, "\x3f"), + new Range(0x258d, 0x258f, "\x3f"), + new Range(0x2590, 0x2593, "\ufffd"), + new Range(0x2594, 0x259f, "\x3f"), + new Range(0x25a1, 0x263b, "\x3f"), + new Range(0x263d, 0x2757, "\x3f"), + new Range(0x2759, 0x2fff, "\x3f"), + new Range(0x3001, 0x3007, "\x3f"), + new Range(0x300a, 0x300b, "\ufffd"), + new Range(0x300c, 0x3019, "\x3f"), + new Range(0x301c, 0x30fa, "\x3f"), + new Range(0x30fc, 0xff00, "\x3f") + ]; +} diff --git a/src/xss/Exceptions.js b/src/xss/Exceptions.js new file mode 100644 index 0000000..4e80c39 --- /dev/null +++ b/src/xss/Exceptions.js @@ -0,0 +1,238 @@ +'use strict'; + +XSS.Exceptions = (() => { + + var Exceptions = { + get legacyExceptions() { + delete this.legacyExceptions; + this.legacyExceptions = + Legacy.getRxPref("filterXExceptions", + Legacy.RX.multi, "g", /^https?:[a-z:/@.?-]*$/i); + return this.legacyExceptions; + }, + + async getWhitelist() { + return (await Storage.get("sync", "xssWhitelist")).xssWhitelist; + }, + async setWhitelist(xssWhitelist) { + await Storage.set("sync", {xssWhitelist}); + }, + + async shouldIgnore(xssReq) { + function logEx(...args) { + debug("[XSS preprocessing] Ignoring %o", xssReq, ...args); + } + + let { + srcObj, + destObj, + srcUrl, + destUrl, + srcOrigin, + destOrigin, + unescapedDest, + isGet, + isPost + } = xssReq; + + // same srcUrl + if (srcOrigin === destOrigin) { + return true; + } + + // same domain + https: source + if (/^https:/.test(srcOrigin) && xssReq.srcDomain === xssReq.destDomain) { + return true; + } + + if (/^(?:chrome|resource|moz-extension|about):/.test(srcOrigin)) { + debug("Privileged origin", srcOrigin); + } + + // destination or @source matching legacy regexp + if (this.legacyExceptions.test(unescapedDest) && + !this.isBadException(destObj.hostname) || + this.legacyExceptions.test("@" + unescape(srcUrl))) { + logEx("Legacy exception", this.legacyExceptions); + return true; + } + + if (!srcObj && isGet) { + if (/^https?:\/\/msdn\.microsoft\.com\/query\/[^<]+$/.test(unescapedDest)) { + return true; // MSDN from Microsoft VS + } + } + + if (srcOrigin) { // srcUrl-specific exceptions + + if (/^about:(?!blank)/.test(srcOrigin)) + return true; // any about: URL except about:blank + + if (srcOrigin === "https://www.youtube.com" && + /^https:\/\/(?:plus\.googleapis|apis\.google)\.com\/[\w/]+\/widget\/render\/comments\?/.test(destUrl) && + Legacy.getPref("filterXExceptions.yt_comments") + ) { + logEx("YouTube comments exception"); + return true; + } + + if (isPost) { + + if (srcOrigin === "https://sso.post.ch" && destOrigin === "https://app.swisspost.ch") { + return true; + } + + if (srcOrigin === "https://twitter.com" && /^https:\/\/.*\.twitter\.com$/.test(destOrigin)) { + return true; + } + + { + let rx = /^https:\/\/(?:[a-z]+\.)?unionbank\.com$/; + if (rx.test(srcOrigin) && rx.test(destOrigin)) { + return true; + } + } + + if (/^https?:\/\/csr\.ebay\.(?:\w{2,3}|co\.uk)\/cse\/start\.jsf$/.test(srcUrl) && + /^https?:\/\/msa-lfn\.ebay\.(?:\w{2,3}|co\.uk)\/ws\/eBayISAPI\.dll\?[^<'"%]*$/.test(unescapedDest) && + destObj.protocol === srcObj.protocol && + Legacy.getPref("filterXException.ebay")) { + logEx("Ebay exception"); + return true; + } + + if (/^https:\/\/(?:cap\.securecode\.com|www\.securesuite\.net|(?:.*?\.)?firstdata\.(?:l[tv]|com))$/.test(srcUrl) && + Legacy.getPref("filterXException.visa")) { + logEx("Verified by Visa exception"); + return true; + } + + if (/\.verizon\.com$/.test(srcOrigin) && + /^https:\/\/signin\.verizon\.com\/sso\/authsso\/forumLogin\.jsp$/.test(destUrl) && + Legacy.getPref("filterXExceptions.verizon")) { + logEx("Verizon login exception"); + return true; + } + + if (/^https?:\/\/mail\.lycos\.com\/lycos\/mail\/MailCompose\.lycos$/.test(srcUrl) && + /\.lycosmail\.lycos\.com$/.test(destOrigin) && + Legacy.getPref("filterXExceptions.lycosmail")) { + logEx("Lycos Mail exception"); + return true; + } + + if (/\.livejournal\.com$/.test(srcOrigin) && + /^https?:\/\/www\.livejournal\.com\/talkpost_do\.bml$/.test(destUrl) && + Legacy.getPref("filterXExceptions.livejournal")) { + logEx("Livejournal comments exception"); + return true; + } + + if (srcOrigin == "https://ssl.rapidshare.com" && + xssReq.srcDomain == "rapidshare.com") { + logEx("Rapidshare upload exception"); + return true; + } + + if (srcOrigin == "http://wm.letitbit.net" && + /^http:\/\/http\.letitbit\.net:81\/cgi-bin\/multi\/upload\.cgi\?/.test(destUrl) && + Legacy.getPref("filterXExceptions.letitibit") + ) { + logEx("letitbit.net upload exception"); + return true; + } + + if (/\.deviantart\.com$/.test(srcOrigin) && + /^http:\/\/my\.deviantart\.com\/journal\/update\b/.test(destUrl) && + Legacy.getPref("filterXExceptions.deviantart") + ) { + logEx("deviantart.com journal post exception"); + return true; + } + + if (srcOrigin == "https://www.mymedicare.gov" && + destOrigin == "https://myporal.medicare.gov" && + Legacy.getPref("filterXExceptions.medicare") + ) { + logEx("mymedicare.gov exception"); + return true; + } + + if (/^https?:\/\/(?:draft|www)\.blogger\.com\/template-editor\.g\?/.test(srcUrl) && + /^https?:\/\/[\w\-]+\.blogspot\.com\/b\/preview\?/.test(destUrl) && + Legacy.getPref("filterXExceptions.blogspot") + ) { + logEx("blogspot.com template preview exception"); + return true; + } + + if (/^https?:\/\/www\.readability\.com\/articles\/queue$/.test(destUrl) && + Legacy.getPref("filterXExceptions.readability")) { + logEx("Readability exception"); + return true; + } + + if (/^https?:\/\/pdf\.printfriendly\.com\/pdfs\/make$/.test(destUrl) && + Legacy.getPref("filterXExceptions.printfriendly")) { + logEx("Printfriendly exception"); + return true; + } + } + } + }, + + isBadException(host) { + // TLD check for Google search + let m = host.match(/\bgoogle\.((?:[a-z]{1,3}\.)?[a-z]+)$/i); + return m && tld.getPublicSuffix(host) != m[1]; + }, + + partial(xssReq) { + let { + srcObj, + destObj, + srcUrl, + destUrl, + srcOrigin, + destOrigin, + } = xssReq; + + let skipParams, skipRx; + if (/^https:\/\/www\.paypal\.com\/(?:[\w\-]+\/)?cgi-bin\/webscr\b/.test(destUrl)) { + // Paypal buttons encrypted parameter causes a DOS, strip it out + skipParams = ['encrypted']; + } else if (/\.adnxs\.com$/.test(srcOrigin) && /\.adnxs\.com$/.test(destOrigin)) { + skipParams = ['udj']; + } else if (/^https?:\/\/www\.mendeley\.com\/import\/bookmarklet\/$/.test(destUrl)) { + skipParams = ['html']; + } else if (destObj.hash && /^https:/.test(srcOrigin) && + (/^https?:\/\/api\.facebook\.com\//.test(srcUrl) || + /^https:\/\/tbpl\.mozilla\.org\//.test(srcUrl) || // work-around for hg reftest DOS + /^https:\/\/[^\/]+\.googleusercontent\.com\/gadgets\/ifr\?/.test(destUrl) // Google gadgets + )) { + skipRx = /#[^#]+$/; // remove receiver's hash + } else if (/^https?:\/\/apps\.facebook\.com\//.test(srcUrl) && Legacy.getPref("filterXExceptions.fbconnect")) { + skipRx = /&invite_url=javascript[^&]+/; // Zynga stuff + } else if (/^https?:\/\/l\.yimg\.com\/j\/static\/frame\?e=/.test(destUrl) && + /\.yahoo\.com$/.test(srcOrigin) && + Legacy.getPref("filterXExceptions.yahoo")) { + skipParams = ['e']; + } else if (/^https?:\/\/wpcomwidgets\.com\/\?/.test(destUrl)) { + skipParams = ["_data"]; + } else if (/^https:\/\/docs\.google\.com\/picker\?/.test(destUrl)) { + skipParams = ["nav", "pp"]; + } else if (/^https:\/\/.*[\?&]scope=/.test(destUrl)) { + skipRx = /[\?&]scope=[+\w]+(?=&|$)/; + } + if (skipParams) { + skipRx = new RegExp("(?:^|[&?])(?:" + skipParams.join('|') + ")=[^&]+", "g"); + } + return { + skipParams, + skipRx + }; + } + + }; + return Exceptions; +})(); diff --git a/src/xss/FlashIdiocy.js b/src/xss/FlashIdiocy.js new file mode 100644 index 0000000..c6835b1 --- /dev/null +++ b/src/xss/FlashIdiocy.js @@ -0,0 +1,147 @@ +'use strict'; + +XSS.FlashIdiocy = { + _affectsRx: /%(?:[8-9a-f]|[0-7]?[^0-9a-f])/i, // high (non-ASCII) percent encoding or invalid second digit + affects(s) { + return this._affectsRx.test(s); + }, + + purgeBadEncodings(s) { + return s.replace(/%(?:[0-9a-f]?(?:[^0-9a-f]|$))/ig, ""); + }, + + platformDecode(s) { + return s.replace(/%[8-9a-f][0-9a-f]/ig, s => this.map[s.substring(1).toLowerCase()]); + }, + + map: { + "80": "?", + "81": "", + "82": "?", + "83": "?", + "84": "?", + "85": "?", + "86": "?", + "87": "?", + "88": "?", + "89": "?", + "8a": "?", + "8b": "?", + "8c": "?", + "8d": "", + "8e": "?", + "8f": "", + "90": "", + "91": "?", + "92": "?", + "93": "?", + "94": "?", + "95": "?", + "96": "?", + "97": "?", + "98": "?", + "99": "?", + "9a": "?", + "9b": "?", + "9c": "?", + "9d": "", + "9e": "?", + "9f": "?", + "a0": " ", + "a1": "¡", + "a2": "¢", + "a3": "£", + "a4": "¤", + "a5": "¥", + "a6": "¦", + "a7": "§", + "a8": "¨", + "a9": "©", + "aa": "ª", + "ab": "«", + "ac": "¬", + "ad": "­", + "ae": "®", + "af": "¯", + "b0": "°", + "b1": "±", + "b2": "²", + "b3": "³", + "b4": "´", + "b5": "µ", + "b6": "¶", + "b7": "·", + "b8": "¸", + "b9": "¹", + "ba": "º", + "bb": "»", + "bc": "¼", + "bd": "½", + "be": "¾", + "bf": "¿", + "c0": "À", + "c1": "Á", + "c2": "Â", + "c3": "Ã", + "c4": "Ä", + "c5": "Å", + "c6": "Æ", + "c7": "Ç", + "c8": "È", + "c9": "É", + "ca": "Ê", + "cb": "Ë", + "cc": "Ì", + "cd": "Í", + "ce": "Î", + "cf": "Ï", + "d0": "Ð", + "d1": "Ñ", + "d2": "Ò", + "d3": "Ó", + "d4": "Ô", + "d5": "Õ", + "d6": "Ö", + "d7": "×", + "d8": "Ø", + "d9": "Ù", + "da": "Ú", + "db": "Û", + "dc": "Ü", + "dd": "Ý", + "de": "Þ", + "df": "ß", + "e0": "à", + "e1": "á", + "e2": "â", + "e3": "ã", + "e4": "ä", + "e5": "å", + "e6": "æ", + "e7": "ç", + "e8": "è", + "e9": "é", + "ea": "ê", + "eb": "ë", + "ec": "ì", + "ed": "í", + "ee": "î", + "ef": "ï", + "f0": "ð", + "f1": "ñ", + "f2": "ò", + "f3": "ó", + "f4": "ô", + "f5": "õ", + "f6": "ö", + "f7": "÷", + "f8": "ø", + "f9": "ù", + "fa": "ú", + "fb": "û", + "fc": "ü", + "fd": "ý", + "fe": "þ", + "ff": "ÿ", + } +}; diff --git a/src/xss/InjectionChecker.js b/src/xss/InjectionChecker.js new file mode 100644 index 0000000..9617e3d --- /dev/null +++ b/src/xss/InjectionChecker.js @@ -0,0 +1,1199 @@ +debug("Initializing InjectionChecker"); +XSS.InjectionChecker = (async () => { + await include([ + "/lib/Base64.js", + "/xss/FlashIdiocy.js", + "/xss/ASPIdiocy.js"] + ); + + var {FlashIdiocy, ASPIdiocy} = XSS; + + const wordCharRx = /\w/g; + + function fuzzify(s) { + return s.replace(wordCharRx, '\\W*(?:/[*/][^]*)?$&'); + } + + const IC_COMMENT_PATTERN = '\\s*(?:\\/[\\/\\*][^]+)?'; + const IC_WINDOW_OPENER_PATTERN = fuzzify("alert|confirm|prompt|open(?:URL)?|print|show") + "\\w*" + fuzzify("Dialog"); + const IC_EVAL_PATTERN = "\\b(?:" + + fuzzify('eval|import|set(?:Timeout|Interval)|(?:f|F)unction|Script|toString|Worker|document|constructor|generateCRMFRequest|jQuery|fetch|write(?:ln)?|__(?:define(?:S|G)etter|noSuchMethod)__|definePropert(?:y|ies)') + + "|\\$|" + IC_WINDOW_OPENER_PATTERN + ")\\b"; + const IC_EVENT_PATTERN = "on(?:p(?:o(?:inter(?:l(?:ock(?:change|error)|eave)|o(?:ver|ut)|cancel|enter|down|move|up)|p(?:up(?:hid(?:den|ing)|show(?:ing|n)|positioned)|state))|a(?:ge(?:hide|show)|(?:st|us)e)|ush(?:subscriptionchange)?|ro(?:cessorerror|gress)|lay(?:ing)?|hoto)|Moz(?:S(?:wipeGesture(?:(?:May)?Start|Update|End)?|crolledAreaChanged)|M(?:agnifyGesture(?:Update|Start)?|ouse(?:PixelScroll|Hittest))|EdgeUI(?:C(?:omplet|ancel)|Start)ed|RotateGesture(?:Update|Start)?|(?:Press)?TapGesture|AfterPaint)|m(?:o(?:z(?:pointerlock(?:change|error)|fullscreen(?:change|error)|key(?:down|up)onplugin|accesskeynotfound|orientationchange)|use(?:l(?:ongtap|eave)|o(?:ver|ut)|enter|wheel|down|move|up))|(?:idimessag|ut)e|essage(?:error)?|ark)|c(?:o(?:m(?:p(?:osition(?:update|start|end)|lete)|mand(?:update)?)|n(?:t(?:rollerchange|extmenu)|nect(?:ionavailable)?)|py)|h(?:(?:arging(?:time)?ch)?ange|ecking)|a(?:n(?:play(?:through)?|cel)|ched)|u(?:echange|t)|l(?:ick|ose))|s(?:ou(?:rce(?:(?:clos|end)ed|open)|nd(?:start|end))|e(?:lect(?:ionchange|start)?|ek(?:ing|ed)|t)|h(?:ipping(?:address|option)change|ow)|t(?:a(?:techange|lled|rt)|o(?:rage|p))|u(?:ccess|spend|bmit)|peech(?:start|end)|croll)|d(?:r(?:a(?:g(?:e(?:n(?:ter|d)|xit)|leave|start|drop|over)?|in)|op)|evice(?:(?:orienta|mo)tion|proximity|change|light)|(?:ischargingtime|uration)change|ata(?:available)?|ownloading|blclick)|a(?:nimation(?:iteration|cancel|start|end)|u(?:dio(?:process|start|end)|xclick)|b(?:solutedeviceorientation|ort)|fter(?:scriptexecute|print)|dd(?:sourcebuffer|track)|ppinstalled|ctivate)|DOM(?:Node(?:Inserted(?:IntoDocument)?|Removed(?:FromDocument)?)|(?:CharacterData|Subtree)Modified|A(?:ttrModified|ctivate)|Focus(?:Out|In)|MouseScroll)|r(?:e(?:s(?:ourcetimingbufferfull|ponseprogress|u(?:lt|me)|ize|et)|move(?:sourcebuffer|track)|adystatechange|pea(?:tEven)?t|questprogress)|atechange)|w(?:ebkit(?:Animation(?:Iteration|Start|End)|animation(?:iteration|start|end)|(?:TransitionE|transitione)nd)|a(?:iting(?:forkey)?|rning)|heel)|v(?:rdisplay(?:(?:presentchang|activat)e|d(?:eactivate|isconnect)|connect)|o(?:iceschanged|lumechange)|(?:isibility|ersion)change)|b(?:e(?:fore(?:p(?:aste|rint)|scriptexecute|c(?:opy|ut)|unload)|gin(?:Event)?)|ufferedamountlow|l(?:ocked|ur)|roadcast|oundary)|t(?:o(?:uch(?:cancel|start|move|end)|ggle)|ransition(?:cancel|start|end|run)|ime(?:update|out)|e(?:rminate|xt)|ypechange)|l(?:o(?:ad(?:e(?:d(?:meta)?data|nd)|ing(?:error|done)?|start)?|stpointercapture)|(?:anguage|evel)change|y)|u(?:p(?:date(?:(?:fou|e)nd|ready|start)?|gradeneeded)|n(?:derflow|load|mute)|serproximity)|g(?:amepad(?:(?:dis)?connected|button(?:down|up)|axismove)|otpointercapture|et)|o(?:(?:rientationchang|(?:ff|n)lin|bsolet)e|verflow|pen)|e(?:n(?:d(?:Event|ed)?|crypted|ter)|mptied|rror|xit)|f(?:ullscreen(?:change|error)|ocus(?:out|in)?|inish)|no(?:tificationcl(?:ick|ose)|update|match)|SVG(?:(?:Unl|L)oad|Resize|Scroll|Zoom)|key(?:statuseschange|press|down|up)|(?:CheckboxStateC|hashc)hange|R(?:adioStateChange|equest)|in(?:stall|valid|put)|AppCommand|zoom)" + // autogenerated from nsGkAtomList.h + ; + const IC_EVENT_DOS_PATTERN = + "\\b(?:" + IC_EVENT_PATTERN + ")[^]*=[^]*\\b(?:" + IC_WINDOW_OPENER_PATTERN + ")\\b" + + "|\\b(?:" + IC_WINDOW_OPENER_PATTERN + ")\\b[^]+\\b(?:" + IC_EVENT_PATTERN + ")[^]*="; + + var InjectionChecker = { + reset: function() { + + this.isPost = + this.base64 = + this.nameAssignment = false; + + this.base64tested = []; + + }, + + fuzzify: fuzzify, + syntax: new SyntaxChecker(), + _log: function(msg, t, i) { + if (msg) msg = this._printable(msg); + if (t) msg += " - TIME: " + (Date.now() - t); + if (i) msg += " - ITER: " + i; + debug("[InjectionChecker]", msg); + }, + + _printable: function(msg) { + return msg.toString().replace(/[^\u0020-\u007e]/g, function(s) { + return "{" + s.charCodeAt(0).toString(16) + "}"; + }); + }, + log: function() {}, + get logEnabled() { + return this.log == this._log; + }, + set logEnabled(v) { + this.log = v ? this._log : function() {}; + }, + + escalate: function(msg) { + this.log(msg); + log("[InjectionChecker] ", msg); + }, + + bb: function(brac, s, kets) { + for (var j = 3; j-- > 0;) { + s = brac + s + kets; + if (this.checkJSSyntax(s)) return true; + } + return false; + }, + + checkJSSyntax(s) { + // bracket balancing for micro injections like "''), e v a l (name,''" + if (/^(?:''|"")?[^\('"]*\)/.test(s)) return this.bb("x(\n", s, "\n)"); + if (/^(?:''|"")?[^\['"]*\\]/.test(s)) return this.bb("y[\n", s, "\n]"); + if (/^(?:''|"")?[^\{'"]*\}/.test(s)) return this.bb("function z() {\n", s, "\n}"); + + let syntax = this.syntax; + s += " /* COMMENT_TERMINATOR */\nDUMMY_EXPR"; + if (syntax.check(s)) { + this.log("Valid fragment " + s); + return true; + } + return false; + }, + + checkTemplates(script) { + let templateExpressions = script.replace(/[[\]{}]/g, ";"); + return templateExpressions !== script && + (this.maybeMavo(script) || + (this.maybeJS(templateExpressions, true) && + (this.syntax.check(templateExpressions) || + /[^><=]=[^=]/.test(templateExpressions) && this.syntax.check( + templateExpressions.replace(/([^><=])=(?=[^=])/g, '$1==')) + ))); + }, + + maybeMavo(s) { + return /\[[^]*\([^]*\)[^]*\]/.test(s) && /\b(?:and|or|mod|\$url\b)/.test(s) && + this.maybeJS(s.replace(/\b(?:and|or|mod|[[\]])/g, ',').replace(/\$url\b/g, 'location'), true); + }, + get breakStops() { + var def = "\\/\\?&#;\\s\\x00}<>"; // we stop on URL, JS and HTML delimiters + var bs = { + nq: new RegExp("[" + def + "]") + }; + Array.forEach("'\"`", // special treatment for quotes + function(c) { + bs[c] = new RegExp("[" + def + c + "]"); + } + ); + delete this.breakStops; + return (this.breakStops = bs); + }, + + collapseChars: (s) => s.replace(/\;+/g, ';').replace(/\/{4,}/g, '////') + .replace(/\s+/g, (s) => /\n/g.test(s) ? '\n' : ' '), + + _reduceBackslashes: (bs) => bs.length % 2 ? "\\" : "", + + reduceQuotes: function(s) { + if (s[0] == '/') { + // reduce common leading path fragment resembling a regular expression or a comment + s = s.replace(/^\/[^\/\n\r]+\//, '_RX_').replace(/^\/\/[^\r\n]*/, '//_COMMENT_'); + } + + if (/\/\*/.test(s) || // C-style comments, would make everything really tricky + /\w\s*(\/\/[\s\S]*)?\[[\s\S]*\w[\s\S]*\]/.test(s)) { // property accessors, risky + return s; + } + + if (/['"\/]/.test(s)) { + + // drop noisy backslashes + s = s.replace(/\\{2,}/g, this._reduceBackslashes); + + // drop escaped quotes + s = s.replace(/\\["'\/]/g, " EQ "); + var expr; + for (;;) { + expr = s.replace(/(^[^'"\/]*[;,\+\-=\(\[]\s*)\/[^\/]+\//g, "$1 _RX_ ") + .replace(/(^[^'"\/]*)(["']).*?\2/g, "$1 _QS_ "); + if (expr == s) break; + s = expr; + } + } + + // remove c++ style comments + return s.replace(/^([^'"`\\]*?)\/\/[^\r\n]*/g, "$1//_COMMENT_"); + }, + + reduceURLs: function(s) { + // nested URLs with protocol are parsed as C++ style comments, and since + // they're potentially very expensive, we preemptively remove them if possible + while (/^[^'"]*?:\/\//.test(s)) { + s = s.replace(/:\/\/[^*\s]*/, ':'); + } + s = s.replace(/:\/\/[^'"*\n]*/g, ':'); + + return (/\bhttps?:$/.test(s) && !/\bh\W*t\W*t\W*p\W*s?.*=/.test(s)) ? + s.replace(/\b(?:[\w.]+=)?https?:$/, '') : + s; + }, + + reduceJSON(s) { + const REPL = 'J'; + const toStringRx = /^function\s*toString\(\)\s*{\s*\[native code\]\s*\}$/; + + // optimistic case first, one big JSON block + let m = s.match(/{[^]+}|\[[^]*{[^]+}[^]*\]/); + if (!m) return s; + + // semicolon-separated JSON chunks, like on syndication.twitter.com + if (/}\s*;\s*{/.test(s)) s = s.split(";").map(chunk => this.reduceJSON(chunk)).join(";"); + + let [expr] = m; + try { + if (toStringRx.test(JSON.parse(expr).toString)) { + this.log("Reducing big JSON " + expr); + return this.reduceJSON(s.replace(expr, REPL)); + } + } catch (e) {} + let iterations = 0; + for (;;) { + let prev = s; + let start = s.indexOf("{"); + let end = s.lastIndexOf("}"); + let prevExpr = ""; + while (start > -1 && end - start > 1) { + expr = s.substring(start, end + 1); + if (expr === prevExpr) break; + end = s.lastIndexOf("}", end - 1); + if (end === -1) { + start = s.indexOf("{", start + 1); + end = s.lastIndexOf("}"); + } + try { + if (!toStringRx.test(JSON.parse(expr).toString)) + continue; + + this.log("Reducing JSON " + expr); + s = s.replace(expr, REPL); + break; + } catch (e) {} + + if (/\btoString\b[\s\S]*:/.test(expr)) { + continue; + } + + let qred = this.reduceQuotes(expr); + if (/\{(?:\s*(?:(?:\w+:)+\w+)+;\s*)+\}/.test(qred)) { + this.log("Reducing pseudo-JSON " + expr); + s = s.replace(expr, REPL); + break; + } + + if (!/[(=.]|[^:\s]\s*\[|:\s*(?:location|document|set(?:Timeout|Interval)|eval|open|show\w*Dialog|alert|confirm|prompt)\b|(?:\]|set)\s*:/.test(qred) && + this.checkJSSyntax("JSON = " + qred) // no-assignment JSON fails with "invalid label" + ) { + this.log("Reducing slow JSON " + expr); + s = s.replace(expr, REPL); + break; + } + prevExpr = expr; + } + + if (s === prev) break; + } + + return s; + }, + + reduceXML: function reduceXML(s) { + var res; + + for (let pos = s.indexOf("<"); pos !== -1; pos = s.indexOf("<", 1)) { + + let head = s.substring(0, pos); + let tail = s.substring(pos); + + let qnum = 0; + for (pos = -1; + (pos = head.indexOf('"', ++pos)) > -1;) { + if (pos === 0 || head[pos - 1] != '\\') qnum++; + } + if (qnum % 2) break; // odd quotes + + let t = tail.replace(/^<(\??\s*\/?[a-zA-Z][\w:-]*)(?:[\s+]+[\w:-]+="[^"]*")*[\s+]*(\/?\??)>/, '<$1$2>'); + + (res || (res = [])).push(head); + s = t; + } + if (res) { + res.push(s); + s = res.join(''); + } + + return s; + }, + + _singleAssignmentRx: new RegExp( + "(?:\\b" + fuzzify('document') + "\\b[^]*\\.|\\s" + fuzzify('setter') + "\\b[^]*=)|/.*/[^]*(?:\\.(?:" + + "\\b" + fuzzify("onerror") + "\\b[^]*=|" + + +fuzzify('source|toString') + ")|\\[)|" + IC_EVENT_DOS_PATTERN + ), + _riskyAssignmentRx: new RegExp( + "\\b(?:" + fuzzify('location|innerHTML|outerHTML') + ")\\b[^]*=" + ), + _nameRx: new RegExp( + "=[^]*\\b" + fuzzify('name') + "\\b|" + + fuzzify("hostname") + "[^]*=[^]*(?:\\b\\d|[\"'{}~^|<*/+-])" + ), + _evalAliasingRx: new RegExp( + "=[^]+\\[" + IC_EVAL_PATTERN + "\\W*\\]" // TODO: check if it can be coalesced into _maybeJSRx + ), + + _maybeJSRx: new RegExp( + '(?:(?:\\[[^]+\\]|\\.\\D)(?:[^]*\\([^]*\\)|[^*]`[^]+`|[^=]*=[^=][^]*\\S)' + + // double function call + '|\\([^]*\\([^]*\\)' + + ')|(?:^|\\W)(?:' + IC_EVAL_PATTERN + + ')(?:\\W+[^]*|)[(`]|(?:[=(]|\\{[^]+:)[^]*(?:' + // calling eval-like functions directly or... + IC_EVAL_PATTERN + // ... assigning them to another function possibly called by the victim later + ')[^]*[\\n,;:|]|\\b(?:' + + fuzzify('setter|location|innerHTML|outerHTML') + // eval-like assignments + ')\\b[^]*=|' + + '.' + IC_COMMENT_PATTERN + "src" + IC_COMMENT_PATTERN + '=' + + IC_EVENT_DOS_PATTERN + + "|\\b" + fuzzify("onerror") + "\\b[^]*=" + + "|=[s\\\\[ux]?\d{2}" + // escape (unicode/ascii/octal) + "|\\b(?:toString|valueOf)\\b" + IC_COMMENT_PATTERN + "=[^]*(?:" + IC_EVAL_PATTERN + ")" + + "|(?:\\)|(?:[^\\w$]|^)[$a-zA-Z_\\u0ff-\\uffff][$\\w\\u0ff-\\uffff]*)" + IC_COMMENT_PATTERN + '=>' + // concise function definition + "|(?:[^\\w$]|^)" + IC_EVENT_PATTERN + IC_COMMENT_PATTERN + "=" + ), + + _riskyParensRx: new RegExp( + "(?:^|\\W)(?:(?:" + IC_EVAL_PATTERN + "|on\\w+)\\s*[(`]|" + + fuzzify("with") + "\\b[^]*\\(|" + + fuzzify("for") + "\\b[^]*\\([^]*[\\w$\\u0080-\\uffff]+[^]*\\b(?:" + + fuzzify("in|of") + ")\\b)" + ), + + _dotRx: /\./g, + _removeDotsRx: /^openid\.[\w.-]+(?==)|(?:[?&#\/]|^)[\w.-]+(?=[\/\?&#]|$)|[\w\.]*\.(?:\b[A-Z]+|\w*\d|[a-z][$_])[\w.-]*|=[a-z.-]+\.(?:com|net|org|biz|info|xxx|[a-z]{2})(?:[;&/]|$)/g, + _removeDots: (p) => p.replace(InjectionChecker._dotRx, '|'), + _arrayAccessRx: /\s*\[\d+\]/g, + _riskyOperatorsRx: /[+-]{2}\s*(?:\/[*/][\s\S]+)?(?:\w+(?:\/[*/][\s\S]+)?[[.]|location)|(?:\]|\.\s*(?:\/[*/][\s\S]+)?\w+|location)\s*(?:\/[*/][\s\S]+)?([+-]{2}|[+*\/<>~-]+\s*(?:\/[*/][\s\S]+)?=)/, // inc/dec/self-modifying assignments on DOM props + _assignmentRx: /^(?:[^()="'\s]+=(?:[^(='"\[+]+|[?a-zA-Z_0-9;,&=/]+|[\d.|]+))$/, + _badRightHandRx: /=[\s\S]*(?:_QS_\b|[|.][\s\S]*source\b|<[\s\S]*\/[^>]*>)/, + _wikiParensRx: /^(?:[\w.|-]+\/)*\(*[\w\s-]+\([\w\s-]+\)[\w\s-]*\)*$/, + _neutralDotsRx: /(?:^|[\/;&#])[\w-]+\.[\w-]+[\?;\&#]/g, + _openIdRx: /^scope=(?:\w+\+)\w/, // OpenID authentication scope parameter, see http://forums.informaction.com/viewtopic.php?p=69851#p69851 + _gmxRx: /\$\(clientName\)-\$\(dataCenter\)\.(\w+\.)+\w+/, // GMX webmail, see http://forums.informaction.com/viewtopic.php?p=69700#p69700 + + maybeJS(expr, mavoChecked = false) { + if (!mavoChecked && this.maybeMavo(expr)) return true; + + if (/`[\s\S]*`/.test(expr) || // ES6 templates, extremely insidious!!! + this._evalAliasingRx.test(expr) || + this._riskyOperatorsRx.test(expr) // this must be checked before removing dots... + ) return true; + + expr = // dotted URL components can lead to false positives, let's remove them + expr.replace(this._removeDotsRx, this._removeDots) + .replace(this._arrayAccessRx, '_ARRAY_ACCESS_') + .replace(/<([\w:]+)>[^/g, '<$1/>') // reduce XML text nodes + .replace(/