Mình tham gia giải lần này chủ yếu là để lấy lại cảm giác tay khi chơi ctf (gọi là tìm lại cái phong độ tí ti đã mất cũng được 🤣), mặc dù bản thân không giỏi các dạng đề về client side nhưng ở post này cũng xin gửi đến mọi người 2 bài, mà thật ra là mình cũng chỉ giải được có mỗi 2 câu này. ![](https://hackmd.io/_uploads/ByvPOaC_2.png) ## peanut-xss ![](https://hackmd.io/_uploads/HJaE05Au3.png) ### Tổng quan Trang web cho phép người dùng soạn thảo nội dung bằng html ![](https://hackmd.io/_uploads/ry6EyiA_n.png) đồng thời hỗ trợ tính năng "expandable, embeddable explanations" với thư viện [nutshell](https://github.com/ncase/nutshell). ![](https://hackmd.io/_uploads/H1-LFiRO2.png) Source của trang chủ yếu nằm ở front end và bên cạnh nutshell, thư viện DOMPurify được sử dụng cho mục đích sanitize html. ![](https://hackmd.io/_uploads/H1AfXsC_n.png) Về yêu cầu của bài, có thể đoán được flag cần lấy nằm trong cookie của bot. ![](https://hackmd.io/_uploads/H1C59o0d3.png) ### Phân tích Để tìm hướng giải cho bài này, cần phân tích mã nguồn của thư viện nutshell. Sau khi được sanitize bởi DOMpurify mặc định sẽ khởi chạy hàm `Nutshell.start()` ```javascript= ///////////////////////////////////////////////////////////////////// // ⭐️ Start Nutshell! ///////////////////////////////////////////////////////////////////// // By default, start Nutshell on DOMContentLoaded // (you may want to delay this e.g. if your blog's content is AJAX'd in) window.addEventListener('DOMContentLoaded', () => { if (Nutshell.options.startOnLoad) Nutshell.start(); }); // NUTSHELL START Nutshell.start = (el = document.body) => { // Restart! Nutshell.htmlCache = {}; Nutshell._nutshellsOpen = 0; // IF TOP PAGE: Convert this page! // (By default, the whole document. But you can specify element, // i.e. leaving out comments section) // IF NOT TOP PAGE: // I must have been created for postMessage; give parent my HTML. if (window == window.top) { // Add self's HTML to my own cached Nutshell.htmlCache[Nutshell.thisPageURL] = _purifyHTML(el.innerHTML, Nutshell.thisPageURL); // Add styles & convert page Nutshell.addStyles(); Nutshell.hideHeadings(el); Nutshell.convertLinksToExpandables(el); Nutshell.convertHeadings(el); // Fill out other UI with localized text // (only set by user after Nutshell.js file included, hence this) Nutshell.fillCloseAllText(); Nutshell.fillEmbedModalText(); } else { // Tell my parent (from any origin) my HTML! _sendParentMyHTML(); } }; ``` Xét với trường hợp `window == window.top` (không nằm trong iframe), thực hiện thêm vào các định dạng + ẩn đi các heading + **chuyển links thành các "expandable" elements** + ... Hàm `Nutshell.convertLinksToExpandables()` có một sink DOM-based XSS, với `ex` là các anchor element lấy từ `dom` (`document.body`) ![](https://hackmd.io/_uploads/SyAZRsAO2.png) Chẳng hạn một tag `a` có dạng ```html! <a>:CTF</a> ``` ta có đoạn code dòng 615 sẽ tương đương ```html linkText.innerHTML = "CTF"; ``` Vậy chỉ cần chèn payload XSS vào vị trí chuỗi `CTF` là có thể hoàn thành challenge. _**Về sự khác nhau giữa innerText và innerHTML**_ ![](https://hackmd.io/_uploads/BkuYr30O2.png) Với một trang html như sau ![](https://hackmd.io/_uploads/S1N8G3A_3.png) Ta có được kết quả khi truy xuất innerText và innerHTML tương ứng là: ![](https://hackmd.io/_uploads/SymwfhA_h.png) Từ đó suy ra payload "sống sót" được khỏi DOMPurify mà vẫn khai thác được DOM-based XSS: ```html <a>:&lt;img src=x onerror=alert(origin)&gt;> ``` ![](https://hackmd.io/_uploads/rJYXXnAu3.png) ![](https://hackmd.io/_uploads/HyDV73A_h.png) ### Khai thác Set up exploit server ![](https://hackmd.io/_uploads/BywnXnR_3.png) Gửi link cho bot ![](https://hackmd.io/_uploads/HJZjm2Run.png) Kết quả ![](https://hackmd.io/_uploads/r1AAQ3Cu2.png) --- ## adminplz > Source tải tại [link](https://web.archive.org/web/20230702082650/https://storage.googleapis.com/ctfd-storage-466eea38a5233c29/6610db7bf60990c9b2dfbc3b31efcf0a/handout.tar.gz?response-content-disposition=attachment%3B%20filename%3Dhandout.tar.gz&response-cache-control=max-age%3D3600&X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=GOOG1EN7ELITYLWBOWPOWXC735VXKNQBJROI5TWXT47YKQUAANJ7L63462CKQ%2F20230702%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20230702T080000Z&X-Amz-Expires=3600&X-Amz-SignedHeaders=host&X-Amz-Signature=e3b59cf11ef11385a4b90d8b974f2b1cf6f4d1a95de5165e3bd8ac0d0b0d50e5) ![](https://hackmd.io/_uploads/r1vB09COn.png) ### Tổng quan Truy cập vào đường dẫn, ta thấy có hai chức năng - Login - Report ![](https://hackmd.io/_uploads/HJ_k8nAu3.png) Trang web được xây dựng dựa trên spring boot framework và mục tiêu của bài này vẫn là khai thác lỗi client-side để lấy flag. ### Phân tích - Về vị trí của flag -> nằm trong file `/flag.html` ![](https://hackmd.io/_uploads/HJ6BK20_2.png) - Về chức năng login, chỉ đơn giản là set thuộc tính `user` của session object với giá trị là `user` object từ request. ![](https://hackmd.io/_uploads/ByV5UhCu2.png) Trong đó `user` object là một model với hai thuộc tính `username` và `password` ![](https://hackmd.io/_uploads/By3UwhCu3.png) - Về chức năng report, sẽ khởi chạy instance của headless chrome đến đường dẫn url lấy từ người dùng (mỗi lần report cách nhau 5p) ![](https://hackmd.io/_uploads/Hy96vh0un.png) bot trước tiên sẽ thực hiện đăng nhập vào trang web, sau đó mới truy cập đến đường dẫn được cung cấp ![](https://hackmd.io/_uploads/rklHunROh.png) - Trong mã nguồn tồn tại một controller `/admin`, chủ yếu dành cho admin để xem bất kì resource nào dựa vào `view` param. ```java @GetMapping({"/admin"}) public Resource admin(HttpServletRequest req, HttpSession session, @RequestParam String view) { if (this.isLoggedIn(session) && view.contains("flag")) { logger.warn("user {} [{}] attempted to access restricted view", ((User)session.getAttribute("user")).getUsername(), session.getId()); } return app.getResource(this.isAdmin(req, session) ? view : "error.html"); } ``` Hai method `isAdmin()` và `isLoggedIn()` như sau ![](https://hackmd.io/_uploads/B1Qyt30u3.png) Server sử dụng thư viện log4j để log lại **username** và **session cookie** trong trường hợp "user hiện tại **đã đăng nhập** và muốn xem các view có chứa chuỗi **flag**". Vị trí của file log: ![](https://hackmd.io/_uploads/r1wfj3Cu2.png) Bên cạnh đó, `CSP` filter được dùng để triển khai csp cho trang web ![](https://hackmd.io/_uploads/H1tU52Cd2.png) Và thuộc tính Same Site của cookie là `Strict` ![](https://hackmd.io/_uploads/BJyt5hRu3.png) Sau khi thử login vào account admin và view các resource thì mình nhận thấy mặc định giá trị content type trong response thông qua `ApplicationContext.getResource()` là `text/html` ![](https://hackmd.io/_uploads/Bkue6nCO2.png) -> Vậy có thể tận dụng file này cho việc khai thác. Ý tưởng để giải bài này là 1. Khiến server log lại session cookie của admin (bot) 2. Tìm cách lấy session cookie 3. Sử dụng session cookie và truy cập đến `/admin?view=/flag.html` để lấy flag Để giải quyết vấn đề (1) chỉ cần report với đường dẫn url là `http://127.0.0.1:8080/admin?view=/flag.html`. Về vấn đề (2), do directive `default-src` bị set thành `none` nên khó có thể XSS với các cách thường hay làm, nhưng vì mục tiêu chỉ là session cookie của bot nên mình áp dụng kĩ thuật [dangling markup](https://portswigger.net/web-security/cross-site-scripting/dangling-markup) - cụ thể là sử dụng `metag` tag và redirect con bot đến exploit server. ### Khai thác - Gửi các request theo thứ tự hợp lý để ghi được payload hoàn chỉnh vào log file và trigger redirect. [Burp Project](https://cdn.discordapp.com/attachments/1124561091511472299/1124995209508429824/2023-07-01-adminplz.burp) - Lấy được session cookie của bot ![](https://hackmd.io/_uploads/ryTZzT0d3.png) ![](https://hackmd.io/_uploads/S1WXGpAOh.png) - Và flag ![](https://hackmd.io/_uploads/Skh4zaAOh.png)