Web
Nmap discovered a Web server on the target port 80
The running service is nginx 1.16.1
┌──(kali㉿kali)-[~/PEN-200/PG_PRACTICE/splodge]
└─$ curl -I http://$IP/
HTTP/1.1 403 Forbidden
Server: nginx/1.16.1
Date: Mon, 03 Mar 2025 13:17:41 GMT
Content-Type: text/html
Content-Length: 153
Connection: keep-alive
403
at Webroot
Initial Nmap scan reveals the presence of
.git
directory earlier
However, it returns 403
git-dumper
┌──(kali㉿kali)-[~/PEN-200/PG_PRACTICE/splodge]
└─$ git-dumper http://$IP/.git ./git
[-] Testing http://192.168.219.108/.git/HEAD [200]
[-] Testing http://192.168.219.108/.git/ [403]
[-] Fetching common files
[...REDACTED...]
[-] Running git checkout .
┌──(kali㉿kali)-[~/PEN-200/PG_PRACTICE/splodge/git]
└─$ cd git ; git log
commit 6c119454548d7d9933b6f40a2c26ecf436e0bedd (HEAD -> master)
Author: The Splodge <admin@splodge.offsec>
Date: Sat Oct 17 22:54:11 2020 -0400
initial commit
Exfiltrating the .git
directory using git-dumper
Source Code Analysis
┌──(kali㉿kali)-[~/PEN-200/PG_PRACTICE/splodge/git]
└─$ ll .
total 212K
4.0K drwxrwxr-x 11 kali kali 4.0K Mar 3 15:25 .
4.0K drwxrwxr-x 7 kali kali 4.0K Mar 3 15:25 .git
4.0K drwxrwxr-x 3 kali kali 4.0K Mar 3 15:25 public
4.0K drwxrwxr-x 5 kali kali 4.0K Mar 3 15:25 resources
4.0K drwxrwxr-x 2 kali kali 4.0K Mar 3 15:25 routes
4.0K -rw-rw-r-- 1 kali kali 563 Mar 3 15:25 server.php
4.0K drwxrwxr-x 5 kali kali 4.0K Mar 3 15:25 storage
4.0K drwxrwxr-x 2 kali kali 4.0K Mar 3 15:25 config
4.0K drwxrwxr-x 5 kali kali 4.0K Mar 3 15:25 database
4.0K -rw-rw-r-- 1 kali kali 1.1K Mar 3 15:25 phpunit.xml
4.0K -rw-rw-r-- 1 kali kali 111 Mar 3 15:25 .gitattributes
4.0K -rw-rw-r-- 1 kali kali 146 Mar 3 15:25 .gitignore
4.0K drwxrwxr-x 7 kali kali 4.0K Mar 3 15:25 app
4.0K -rwxrwxr-x 1 kali kali 1.7K Mar 3 15:25 artisan
4.0K drwxrwxr-x 3 kali kali 4.0K Mar 3 15:25 bootstrap
4.0K -rw-rw-r-- 1 kali kali 1.3K Mar 3 15:25 composer.json
144K -rw-rw-r-- 1 kali kali 142K Mar 3 15:25 composer.lock
4.0K drwxrwxr-x 3 kali kali 4.0K Mar 3 15:24 ..
Dumped the entire web app directory
┌──(kali㉿kali)-[~/PEN-200/PG_PRACTICE/splodge/git]
└─$ ll public
total 20K
4.0K drwxrwxr-x 3 kali kali 4.0K Mar 3 15:25 .
4.0K drwxrwxr-x 11 kali kali 4.0K Mar 3 15:25 ..
4.0K drwxrwxr-x 2 kali kali 4.0K Mar 3 15:25 css
0 -rw-rw-r-- 1 kali kali 0 Mar 3 15:25 favicon.ico
4.0K -rw-rw-r-- 1 kali kali 1.8K Mar 3 15:25 index.php
4.0K -rw-rw-r-- 1 kali kali 24 Mar 3 15:25 robots.txt
There is just the index.php
file
.git/config
There is a username disclosure at the
.git/config
file
admin
database/seeds/DatabaseSeeder.php
┌──(kali㉿kali)-[~/PEN-200/PG_PRACTICE/splodge/git]
└─$ cat database/seeds/DatabaseSeeder.php
<?php
use Illuminate\Database\Seeder;
class DatabaseSeeder extends Seeder
{
/**
* Run the database seeds.
*
* @return void
*/
public function run()
{
DB::table('posts')->insert([
'title' => 'Hello World!',
'content' => 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.',
'author' => 'The Splodge'
]);
DB::table('posts')->insert([
'title' => 'Test Post Please Ignore',
'content' => 'Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora incidunt ut labore et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? Quis autem vel eum iure reprehenderit qui in ea voluptate velit esse quam nihil molestiae consequatur, vel illum qui dolorem eum fugiat quo voluptas nulla pariatur?',
'author' => 'The Splodge'
]);
DB::table('settings')->insert([
'title' => 'Splodge',
'filter' => '//',
'replacement' => '',
'password' => 'SplodgeSplodgeSplodge'
]);
}
}
Another credential disclosure at the database/seeds/DatabaseSeeder.php
file
SplodgeSplodgeSplodge
ENV
┌──(kali㉿kali)-[~/PEN-200/PG_PRACTICE/splodge/git]
└─$ cat config/database.php
<?php
return [
'default' => env('DB_CONNECTION', 'mysql'),
'connections' => [
'sqlite' => [
'driver' => 'sqlite',
'database' => env('DB_DATABASE', database_path('database.sqlite')),
'prefix' => '',
],
'mysql' => [
'driver' => 'mysql',
'host' => env('DB_HOST', '127.0.0.1'),
'port' => env('DB_PORT', '3306'),
'database' => env('DB_DATABASE', 'forge'),
'username' => env('DB_USERNAME', 'forge'),
'password' => env('DB_PASSWORD', ''),
'unix_socket' => env('DB_SOCKET', ''),
'charset' => 'utf8mb4',
'collation' => 'utf8mb4_unicode_ci',
'prefix' => '',
'strict' => true,
'engine' => null,
],
'pgsql' => [
'driver' => 'pgsql',
'host' => env('DB_HOST', '127.0.0.1'),
'port' => env('DB_PORT', '5432'),
'database' => env('DB_DATABASE', 'forge'),
'username' => env('DB_USERNAME', 'forge'),
'password' => env('DB_PASSWORD', ''),
'charset' => 'utf8',
'prefix' => '',
'schema' => 'public',
'sslmode' => 'prefer',
],
'sqlsrv' => [
'driver' => 'sqlsrv',
'host' => env('DB_HOST', 'localhost'),
'port' => env('DB_PORT', '1433'),
'database' => env('DB_DATABASE', 'forge'),
'username' => env('DB_USERNAME', 'forge'),
'password' => env('DB_PASSWORD', ''),
'charset' => 'utf8',
'prefix' => '',
],
],
'migrations' => 'migrations',
'redis' => [
'client' => 'predis',
'default' => [
'host' => env('REDIS_HOST', '127.0.0.1'),
'password' => env('REDIS_PASSWORD', null),
'port' => env('REDIS_PORT', 6379),
'database' => 0,
],
],
];
Pretty much all the files inside the config
directory are configured to fetch sensitive data from the environment variables
route
┌──(kali㉿kali)-[~/PEN-200/PG_PRACTICE/splodge/git]
└─$ cat routes/web.php
<?php
use Illuminate\Support\Facades\Route;
use App\Http\Controllers\AdminController;
use App\Http\Controllers\PostController;
/*
|--------------------------------------------------------------------------
| Web Routes
|--------------------------------------------------------------------------
|
| Here is where you can register web routes for your application. These
| routes are loaded by the RouteServiceProvider within a group which
| contains the "web" middleware group. Now create something great!
|
*/
Route::get('/', 'PostController@index');
Route::get('/posts/{post}', 'PostController@show');
Route::post('/posts/{post}', 'PostController@comment');
Route::get('/admin', 'AdminController@index');
Route::post('/admin', 'AdminController@update');
Route::get('/login', 'AdminController@login');
Route::post('/login', 'AdminController@postLogin');
There appears to be multiple endpoints within this application, including the /admin
endpoint that supports 2 functions index
and update
AdminController
┌──(kali㉿kali)-[~/…/git/app/Http/Controllers]
└─$ cat AdminController.php
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Cookie;
class AdminController extends Controller
{
/**
* Display a listing of the resource.
*
* @return \Illuminate\Http\Response
*/
public function index()
{
$isAdmin = Cookie::get('SPLODGESESSION');
if ($isAdmin === '1') {
$settings = DB::table('settings')->first();
return view('admin', ['settings' => $settings]);
} else {
return redirect('login');
}
}
/**
* Display a listing of the resource.
*
* @return \Illuminate\Http\Response
*/
public function update(Request $request)
{
$settings = DB::table('settings')->first();
$title = $request->input('title') ?: $settings->title;
$filter = $request->input('filter') ?: $settings->filter;
$replacement = $request->input('replacement') ?: $settings->replacement;
$password = $request->input('password') ?: $settings->password;
$isAdmin = Cookie::get('SPLODGESESSION');
if ($isAdmin === '1') {
DB::table('settings')->update(['title' => $title, 'filter' => $filter, 'replacement' => $replacement, 'password' => $password]);
$settings = DB::table('settings')->first();
return view('admin', ['settings' => $settings]);
} else {
return redirect('login');
}
}
public function login(Request $request)
{
return view('login');
}
public function postLogin(Request $request)
{
$settings = DB::table('settings')->first();
$username = $request->input('username');
$password = $request->input('password');
if ($username === 'admin' && $password === $settings->password) {
Cookie::queue(Cookie::make('SPLODGESESSION', '1', 60));
return redirect('admin');
}
return view('login');
}
}
While the index
function is just for session handling, the update
function is rather interesting as it sets a replacement filter.
PostController
┌──(kali㉿kali)-[~/…/git/app/Http/Controllers]
└─$ cat PostController.php
<?php
namespace App\Http\Controllers;
use App\Models\Post;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
class PostController extends Controller
{
/**
* Display a listing of the resource.
*
* @return \Illuminate\Http\Response
*/
public function index()
{
$posts = DB::table('posts')->get();
return view('index', ['posts' => $posts]);
}
/**
* Display the specified resource.
*
* @param \App\Models\Post $post
* @return \Illuminate\Http\Response
*/
public function show(Post $post)
{
$comments = DB::table('comments')->where('post_id', '=', $post->id)->get();
return view('post', ['post' => $post, 'comments' => $comments]);
}
/**
* Comment on a post
*
* @param \App\Models\Post $post
* @return \Illuminate\Http\Response
*/
public function comment(Request $request, Post $post)
{
error_reporting(E_ALL & ~E_NOTICE & ~E_DEPRECATED);
$author = $request->input('commentAuthor');
$message = $request->input('commentMessage');
$settings = DB::table('settings')->first();
$message = preg_replace($settings->filter, $settings->replacement, $message);
DB::table('comments')->insert(['post_id' => $post->id, 'author' => $author, 'message' => $message]);
$comments = DB::table('comments')->where('post_id', '=', $post->id)->get();
return view('post', ['post' => $post, 'comments' => $comments]);
}
}
Checking the PostController.php
file reveals even more interesting feature in the comment
function.
It first grabs the replacement filter, the setting
table, and uses the PHP’s preg_replace function to replace filter
keyword with the replacement
.
Vulnerabilities
Checking the PHP’s preg_replace function for vulnerabilities online shows interesting results