Beyond


Enumerating after gaining the root privilege

root


root@intentions:~# ll
total 48
drwx------  7 root root 4096 Jun 28 11:39 ./
drwxr-xr-x 18 root root 4096 Jun 19 13:34 ../
lrwxrwxrwx  1 root root    9 Jun 19 13:09 .bash_history -> /dev/null
-rw-r--r--  1 root root 3106 Oct 15  2021 .bashrc
drwx------  2 root root 4096 Feb  3 14:50 .cache/
-rw-------  1 root root   20 Jun 28 11:39 .lesshst
drwxr-xr-x  3 root root 4096 Feb  1 15:11 .local/
drwxr-xr-x  3 root root 4096 Jun 19 11:26 .npm/
-rw-r--r--  1 root root  161 Jul  9  2019 .profile
-rw-r-----  1 root root   33 Jul  6 13:29 root.txt
drwxr-xr-x  2 root root 4096 Jun 10 14:53 scripts/
drwx------  2 root root 4096 Jun 16 11:09 .ssh/
-rw-r--r--  1 root root   39 Jun 14 10:18 .vimrc

Home directory of the root user

root@intentions:~# ll scripts
total 16
drwxr-xr-x 2 root root 4096 Jun 10 14:53 ./
drwx------ 7 root root 4096 Jun 28 11:39 ../
-rwxr-xr-x 1 root root   34 Feb  3 00:58 clean1.sh*
-rwxr-xr-x 1 root root  214 Feb  3 00:58 clean2.sh*
root@intentions:~# cat scripts/clean1.sh 
#!/bin/bash
/usr/bin/rm /tmp/php*
root@intentions:~# cat scripts/clean2.sh 
#!/bin/bash
/usr/bin/find /var/www/html/intentions/storage/app/public -type f -name "*.php" -exec rm {} \;
/usr/bin/find /var/www/html/intentions/public/ -type f -not -name "index.php" -name "*.php" -exec rm {} \;

The /root/scripts directory contains 2 Bash scripts that were seen from the PSPY session earlier

root@intentions:~# crontab -l | grep -v '^#'
* * * * * /root/scripts/clean1.sh
*/10 * * * * /root/scripts/clean2.sh

The root user indeed has 2 cronjobs executing those 2 Bash scripts periodically

Steven


root@intentions:/home/steven# ll
total 32
drwxr-x--- 4 steven steven 4096 jun 19 13:09 ./
drwxr-xr-x 5 root   root   4096 jun 10 14:56 ../
lrwxrwxrwx 1 root   root      9 jun 19 13:09 .bash_history -> /dev/null
-rw-r--r-- 1 steven steven  220 Jan  6  2022 .bash_logout
-rw-r--r-- 1 steven steven 3771 Jan  6  2022 .bashrc
drwx------ 2 steven steven 4096 feb  1 14:48 .cache/
-rw-r--r-- 1 steven steven  807 Jan  6  2022 .profile
drwx------ 2 steven steven 4096 feb  1 14:37 .ssh/
-rw-r--r-- 1 steven steven   39 jun 14 10:19 .vimrc

The steven user doesn’t have much going on in the home directory

root@intentions:/home/steven# ll .ssh
total 8
drwx------ 2 steven steven 4096 feb  1 14:37 ./
drwxr-x--- 4 steven steven 4096 jun 19 13:09 ../
-rw------- 1 steven steven    0 feb  1 14:37 authorized_keys

There is the authorized_keys file in the SSH directory, but it’s empty That explains why the exploit script didn’t return anything

root@intentions:/home/steven# crontab -u steven -l 
no crontab for steven
 
root@intentions:/home/steven# find / -user steven -ls -type f 2>/dev/null
   131283      4 drwxr-x---   4 steven   steven       4096 jun 19 13:09 /home/steven
   131284      4 -rw-r--r--   1 steven   steven        807 Jan  6  2022 /home/steven/.profile
   131285      4 -rw-r--r--   1 steven   steven        220 Jan  6  2022 /home/steven/.bash_logout
   131192      4 -rw-r--r--   1 steven   steven         39 jun 14 10:19 /home/steven/.vimrc
   131286      4 -rw-r--r--   1 steven   steven       3771 Jan  6  2022 /home/steven/.bashrc
   131353      4 drwx------   2 steven   steven       4096 feb  1 14:48 /home/steven/.cache
   131354      0 -rw-r--r--   1 steven   steven          0 feb  1 14:48 /home/steven/.cache/motd.legal-displayed
   131177      4 drwx------   2 steven   steven       4096 feb  1 14:37 /home/steven/.ssh
   131358      0 -rw-------   1 steven   steven          0 feb  1 14:37 /home/steven/.ssh/authorized_keys

Nothing


root@intentions:/home/legal# ll
total 32
drwxr-x--- 4 legal legal 4096 Jun 19 13:09 ./
drwxr-xr-x 5 root  root  4096 Jun 10 14:56 ../
lrwxrwxrwx 1 root  root     9 Jun 19 13:09 .bash_history -> /dev/null
-rw-r--r-- 1 legal legal  220 Jun 10 14:56 .bash_logout
-rw-r--r-- 1 legal legal 3771 Jun 10 14:56 .bashrc
drwx------ 2 legal legal 4096 Jun 10 15:06 .cache/
-rw-r--r-- 1 legal legal  807 Jun 10 14:56 .profile
drwxrwx--- 2 legal legal 4096 Jun 10 15:06 uploads/
-rw-r--r-- 1 legal legal   39 Jun 14 10:18 .vimrc

The legal user has the uploads directory, which was interacting with the dmca_hashes.test file in the home directory of the greg user via /opt/scanner/scanner

root@intentions:/home/legal# ll uploads
total 15076
drwxrwx--- 2 legal legal    4096 Jun 10 15:06 ./
drwxr-x--- 4 legal legal    4096 Jun 19 13:09 ../
-rw-rw-r-- 1 legal legal  491340 Jun 10 15:05 alex-lvrs-6vbJyWLzONo-unsplash.jpg
-rw-rw-r-- 1 legal legal 1336670 Jun 10 15:05 alex-lvrs-iWYK6XWQ52E-unsplash.jpg
-rw-rw-r-- 1 legal legal 5251665 Jun 10 15:05 andy-holmes-WLtSGSq5mC4-unsplash.jpg
-rw-rw-r-- 1 legal legal  836377 Jun 10 15:05 derek-zhang-i5hAmZrIpu0-unsplash.jpg
-rw-rw-r-- 1 legal legal 1608889 Jun 10 15:05 ilia-bronskiy-MvWt7GPv-JY-unsplash.jpg
-rw-rw-r-- 1 legal legal 2187270 Jun 10 15:05 julian-missling-FvqKBmU439k-unsplash.jpg
-rw-r--r-- 1 legal legal   45067 Jun 10 15:03 kristin-o-karlsen-u8aXoDEcDR0-unsplash.jpg
-rw-rw-r-- 1 legal legal 3653253 Jun 10 15:05 zac-porter-p_yotEbRA0A-unsplash.jpg

The directory indeed contains the images files for comparison

root@intentions:/home/legal# /home/greg/dmca_check.sh
[+] DMCA-#1952 matches /home/legal/uploads/zac-porter-p_yotEbRA0A-unsplash.jpg
 
root@intentions:/home/legal# ll /home/legal/uploads/zac-porter-p_yotEbRA0A-unsplash.jpg
-rw-rw-r-- 1 legal legal 3653253 Jun 10 15:05 /home/legal/uploads/zac-porter-p_yotEbRA0A-unsplash.jpg
 
root@intentions:/home/legal# md5sum /home/legal/uploads/zac-porter-p_yotEbRA0A-unsplash.jpg
4ad7b71ecad6e9d76fb517fe85d757e6  /home/legal/uploads/zac-porter-p_yotEbRA0A-unsplash.jpg
 
root@intentions:/home/legal# /opt/scanner/scanner -c /home/legal/uploads/zac-porter-p_yotEbRA0A-unsplash.jpg -s 4ad7b71ecad6e9d76fb517fe85d757e6 -l 3653253
[+] 4ad7b71ecad6e9d76fb517fe85d757e6 matches /home/legal/uploads/zac-porter-p_yotEbRA0A-unsplash.jpg

It matches, 4ad7b71ecad6e9d76fb517fe85d757e6, with the byte length of 3653253

root@intentions:/home/legal# crontab -u legal -l
no crontab for legal
root@intentions:/home/legal# find / -user legal -ls 2>/dev/null
   147559      4 drwxr-x---   4 legal    legal        4096 Jun 19 13:09 /home/legal
   147569      4 drwxrwx---   2 legal    legal        4096 Jun 10 15:06 /home/legal/uploads
   131451     48 -rw-r--r--   1 legal    legal       45067 Jun 10 15:03 /home/legal/uploads/kristin-o-karlsen-u8aXoDEcDR0-unsplash.jpg
   131463   1308 -rw-rw-r--   1 legal    legal     1336670 Jun 10 15:05 /home/legal/uploads/alex-lvrs-iWYK6XWQ52E-unsplash.jpg
   131466   3568 -rw-rw-r--   1 legal    legal     3653253 Jun 10 15:05 /home/legal/uploads/zac-porter-p_yotEbRA0A-unsplash.jpg
   131464   5132 -rw-rw-r--   1 legal    legal     5251665 Jun 10 15:05 /home/legal/uploads/andy-holmes-WLtSGSq5mC4-unsplash.jpg
   131470    480 -rw-rw-r--   1 legal    legal      491340 Jun 10 15:05 /home/legal/uploads/alex-lvrs-6vbJyWLzONo-unsplash.jpg
   131452   1572 -rw-rw-r--   1 legal    legal     1608889 Jun 10 15:05 /home/legal/uploads/ilia-bronskiy-MvWt7GPv-JY-unsplash.jpg
   131468    820 -rw-rw-r--   1 legal    legal      836377 Jun 10 15:05 /home/legal/uploads/derek-zhang-i5hAmZrIpu0-unsplash.jpg
   131472   2140 -rw-rw-r--   1 legal    legal     2187270 Jun 10 15:05 /home/legal/uploads/julian-missling-FvqKBmU439k-unsplash.jpg
   131246      4 -rw-r--r--   1 legal    legal         807 Jun 10 14:56 /home/legal/.profile
   131250      4 -rw-r--r--   1 legal    legal         220 Jun 10 14:56 /home/legal/.bash_logout
   131180      4 -rw-r--r--   1 legal    legal          39 Jun 14 10:18 /home/legal/.vimrc
   131343      4 -rw-r--r--   1 legal    legal        3771 Jun 10 14:56 /home/legal/.bashrc
   147571      4 drwx------   2 legal    legal        4096 Jun 10 15:06 /home/legal/.cache
   131457      0 -rw-r--r--   1 legal    legal           0 Jun 10 15:06 /home/legal/.cache/motd.legal-displayed

Nothing else

Web


Finding out how I was able to exploit

  • The 2nd Order SQLi and
  • the php imagick module

API Routing


root@intentions:/var/www/html/intentions# cat  routes/api.php 
<?php
 
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Route;
use App\Http\Controllers\AuthController;
use App\Http\Controllers\GalleryController;
use App\Http\Controllers\AdminController;
/*
|--------------------------------------------------------------------------
| API Routes
|--------------------------------------------------------------------------
|
| Here is where you can register API routes for your application. These
| routes are loaded by the RouteServiceProvider within a group which
| is assigned the "api" middleware group. Enjoy building your API!
|
*/
 
Route::prefix('v1')->group(function () {
    Route::prefix('auth')->group(function () {
        // Below mention routes are public, user can access those without any restriction.
        // Create New User
        Route::post('register', [AuthController::class, 'register']);
        // Login User
        Route::post('login', [AuthController::class, 'login']);
        
        // Refresh the JWT Token
        Route::get('refresh', [AuthController::class, 'refresh']);
        
        // Below mention routes are available only for the authenticated users.
        Route::middleware('auth:api')->group(function () {
            // Get user info
            Route::get('user', [AuthController::class, 'user']);
            // Logout user from application
            Route::post('logout', [AuthController::class, 'logout']);
        });
    });
 
    Route::middleware('auth:api')->prefix('gallery')->group(function () {
        Route::post('user/genres', [GalleryController::class, 'updateUserGenres']);
        Route::get('user/feed', [GalleryController::class, 'getUserFeed']);
        Route::get('images', [GalleryController::class, 'getImages']);
    });
});
 
Route::prefix('v2')->group(function () {
    Route::prefix('auth')->group(function () {
        // Below mention routes are public, user can access those without any restriction.
        // Create New User
        Route::post('register', [AuthController::class, 'register']);
        // Login User
        Route::post('login', [AuthController::class, 'loginv2']);
        
        // Refresh the JWT Token
        Route::get('refresh', [AuthController::class, 'refresh']);
        
        // Below mention routes are available only for the authenticated users.
        Route::middleware('auth:api')->group(function () {
            // Get user info
            Route::get('user', [AuthController::class, 'user']);
            // Logout user from application
            Route::post('logout', [AuthController::class, 'logout']);
        });
    });
 
    Route::middleware(['auth:api', 'isadmin'])->prefix('admin')->group(function () {
        Route::get('users', [AdminController::class, 'getUsers']);
        Route::post('image/modify', [AdminController::class, 'modifyImage']);
        Route::get('image/{id}', [AdminController::class, 'getImage']);
    });
 
    Route::middleware('auth:api')->prefix('gallery')->group(function () {
        Route::post('user/genres', [GalleryController::class, 'updateUserGenres']);
        Route::get('user/feed', [GalleryController::class, 'getUserFeed']);
        Route::get('images', [GalleryController::class, 'getImages']);
    });
});

This is the /var/www/html/intentions/routes/api.php file The file basically works like a traffic controller for API requests

The v1 part of it defines where POST requests to user/genres should go; GalleryController::class, 'updateUserGenres' and GET requests to user/feed; GalleryController::class, 'getUserFeed'

GalleryController is located at /var/www/html/intentions/app/Http/Controllers/GalleryController.php

The v2 part of it also defines the traffic to the API endpoint at /api/v2/admin/image/modify Its pointing to `AdminController::class, ‘modifyImage’

I will start with the SQLi and move on to the PHP Imagick

2nd Order SQL Injection


The GalleryController.php file is basically the backend code that handles frontend requests to the /gallery page That includes POST requests to the genres parameter as well as GET requests to the feed parameter

root@intentions:/var/www/html/intentions/app/Http/Controllers# cat GalleryController.php 
<?php
 
namespace App\Http\Controllers;
 
use Illuminate\Http\Request;
use App\Models\GalleryImage;
use Illuminate\Support\Facades\Storage;
use Auth;
 
class GalleryController extends Controller
{
    //
    function updateUserGenres(Request $request) {
        $genres = "";
 
        if($request->has('genres')) {
            $genres = $request->input('genres');
        }
 
        $genres = str_replace(' ','', $genres);
 
        $user = auth::user();
        $user->genres = $genres;
        $user->save();
 
        return response()->json(['status' => 'success'], 200);
    }
 
    function getUserFeed(Request $request) {
        $genres = auth::user()->genres;
        $images = galleryimage::whereRaw("FIND_IN_SET(genre,'{$genres}')")->get();
        foreach($images as $image) {
            $image->url = storage::url($image->file);
        }
        return response()->json(['status' => 'success', 'data' => $images], 200);
    }
 
    function getImages(Request $request) {
        $images = galleryimage::all();
        foreach($images as $image) {
            $image->url = storage::url($image->file);
        }
 
        return response()->json(['status' => 'success', 'data' => $images], 200);
    }
}

This is the code.

both the updateUserGenres() and getUserFeed() functions are vulnerable to SQL injection due to the lack of proper input sanitization.

The updateUserGenres() function allows user input (genres) to be directly stored in the database without adequate sanitization or parameterization. This can result in the injection of malicious SQL queries into the genres field.

*The only security practice took in place is $genres = str_replace(' ','', $genres);, that basically removes whitespaces

Saving it After reloading

I was able to bypass this with --tamper=space2comment in sqlmap*

Later, the getUserFeed() function retrieves the genres value from the database and uses it in an SQL query without proper sanitization or parameterization. This allows an attacker to exploit the stored malicious query, leading to the 2n Order SQL injection.

It was a BLIND SQLi as the web server would only responded with either the code 500 or that static page below

PHP Imagick


The AdminController.php file is the backend for processing requests to the API endpoint at /api/v2/admin/image/modify

root@intentions:/var/www/html/intentions/app/Http/Controllers# cat AdminController.php 
<?php
 
namespace App\Http\Controllers;
use Imagick;
use Validator;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Storage;
use App\Models\User;
use App\Models\GalleryImage;
 
class AdminController extends Controller
{
    function getImage(Request $request, $id) {
        $image = GalleryImage::findOrFail($id);
        $image->url = Storage::url($image->file);
        $image->path = Storage::path($image->file);
 
        try {
            $i = new Imagick($image->path);
            $image->compression = $i->getImageCompression();
            $image->compressionQuality = $i->getImageCompressionQuality();
            $image->channels = $i->getImageChannelStatistics();
            $image->height = $i->getImageHeight();
            $image->width = $i->getImageWidth();
            $image->size = $i->getImageSize();
        }
        catch(\Exception $ex) {
        }
 
        return response()->json(['status' => 'success', 'data' => $image], 200);
    }
 
    //
    function modifyImage(Request $request) {
        $v = Validator::make($request->all(), [
            'path' => 'required',
            'effect' => 'required'
        ]);
        if ($v->fails())
        {
            return response()->json([
                'status' => 'error',
                'errors' => $v->errors()
            ], 422);
        }
        $path = $request->input('path');
        if(Storage::exists($path)) {
            $path = Storage::path($path);
        }
        try {
            $i = new Imagick($path);
 
            switch($request->input('effect')) {
                case 'charcoal':
                    $i->charcoalImage(1, 15);
                    break;
                case 'wave':
                    $i->waveImage(10, 5);
                    break;
                case 'swirl':
                    $i->swirlImage(111);
                    break;
                case 'sepia':
                    $i->sepiaToneImage(111);
                    break;
            }
            
            return "data:image/jpeg;base64," . base64_encode($i->getImageBlob());
        }
        catch(\Exception $ex) {
            return response("bad image path", 422);
        }
        
    }
 
    function getUsers(Request $request) {
        return User::all();
    }
}

It indeed loads the Imagick module with the modifyImage() function

The version of the installed PHP Imagick module is 3.7.0

Looking further into it online, I see that the module appears to rely on ImageMagick version 6.5.3-10+

magick is the CLI tool for ImageMagick, but it doesn’t seem to be available

There are ImageMagick libraries installed. VERSION 6.x

The Imagick extension for PHP is a wrapper around the ImageMagick library. The Imagick extension provides a convenient interface to interact with ImageMagick functionalities from within PHP.

Installation of the PHP Imagick extension, it does not include the ImageMagick library itself. Instead, it relies on the ImageMagick library being installed separately on the system. The extension acts as a bridge between PHP and the ImageMagick library, allowing to utilize ImageMagick’s capabilities in PHP code.

This confirms the vulnerabilities; CVE-2016-3714 and CVE-2016-3716