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