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
legal
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