Originally published on medium
Before an attacker exploits your app they study it. Here is what they are looking for and how to stop them finding it.
This is the sixth article in a series on PHP and Laravel application security.
So far we have covered:
- Detecting SQL injection attempts in PHP logs
- Why URL encoding blinds most PHP security checks
- The decode bomb problem with unlimited URL decoding
- Why parameterized queries are the only real fix for SQL injection
- XSS prevention in Laravel and why
{!! !!}is the line between safe and hacked
Every article in this series follows the same principle: understand the attack before you try to stop it.
Reconnaissance is no different. And it is the step most developers never think about.
What Reconnaissance Actually Is
Before an attacker exploits your Laravel application they study it.
They are not guessing blindly. They are systematically collecting information that tells them what framework and version you are running, what routes exist, what errors your app produces, what files are publicly accessible, and where your admin panel lives.
This phase is called reconnaissance. And most Laravel apps make it embarrassingly easy.
The attacker does not need to find a vulnerability to do damage at this stage. They just need your app to answer questions it should never be answering.
What Attackers Are Looking For
1. Debug Mode Left On in Production
This is the single most dangerous information disclosure mistake in Laravel.
When APP_DEBUG=true in your production environment, Laravel shows a full stack trace on every error. That stack trace includes your full file system paths, your environment variables, your database connection details, your application code, and every variable in scope at the point of failure.
An attacker who triggers any error on your application a 404, a validation failure, a missing parameter gets a complete map of your server.
Check your production .env file right now:
APP_DEBUG=false
APP_ENV=production
These two lines are not optional. They are the minimum baseline for any Laravel app handling real user data.
2. Exposed .env Files
Your .env file contains your database credentials, API keys, mail server passwords, payment gateway secrets, and application encryption key. It lives in your project root.
If your web server is misconfigured, this file is publicly accessible:
https://yoursite.com/.env
An attacker who reads your .env file may gain access to database credentials, API keys, application secrets, and other sensitive configuration depending on what is stored there.
This is not theoretical. Automated scanners probe every Laravel site they find for exposed .env files within minutes of the site going live. It is one of the first checks they run automatically.
3. Exposed .git Directory
If you deployed by cloning a git repository and did not remove the .git directory from your web root:
https://yoursite.com/.git/config
An attacker may be able to reconstruct your source code from exposed Git objects. Your complete application logic. Your commit history. Every credential that was ever accidentally committed.
Tools like GitDumper automate this reconstruction entirely. What took a developer months to build can be downloaded in minutes.
4. Framework and Version Fingerprinting
Default Laravel error pages and responses can reveal framework information if they are not customized. HTTP response headers sometimes include framework and PHP version information. The structure of session cookies, route patterns, and form token names all point to Laravel specifically.
Default server responses often include:
X-Powered-By: PHP/8.1.0
Server: Apache/2.4.41
Once an attacker knows your framework and version they know exactly which known vulnerabilities to check, which default misconfigurations are most common, and which attack patterns are most likely to succeed.
Remove these headers. They are free reconnaissance for anyone watching.
5. Laravel Packages and Developer Tool Routes
Laravel packages and developer tools can expose routes that reveal application internals:
/telescope — Laravel Telescope debugger
/horizon — Laravel Horizon queue dashboard
/_debugbar — Laravel Debugbar
If any of these are accessible in production without authentication, an attacker has a window into your queue jobs, database queries, cache operations, mail logs, and exception details.
These tools are invaluable for development. They are serious vulnerabilities in production if left unprotected.
6. Verbose Error Messages
Even with debug mode disabled, careless error handling reveals too much:
// This tells an attacker your table name exists
// and gives them information about your schema
catch (Exception $e) {
echo "Error querying users table: " . $e->getMessage();
}
A developer who exposes database table names, column names, or file paths in error messages is doing reconnaissance for the attacker.
The correct approach:
catch (Exception $e) {
// Log everything internally
Log::error('Database error: ' . $e->getMessage(), [
'file' => $e->getFile(),
'line' => $e->getLine()
]);
// Show nothing useful externally
return response('Something went wrong. Please try again.', 500);
}
Log the full error internally. Show nothing externally. The user does not need to know what went wrong technically. The attacker definitely should not know.
7. Predictable Admin Routes
Automated scanners probe for common admin panel locations on every site they find:
/admin
/admin/login
/dashboard
/administrator
/panel
/backend
/manage
/cp
If your admin panel is at one of these locations with no rate limiting, no IP restriction, and no additional authentication layer, it is being probed constantly without you knowing.
8. Directory Listing
If your web server has directory listing enabled, an attacker can browse your file structure like a file manager:
https://yoursite.com/storage/
https://yoursite.com/public/uploads/
They see every file you have uploaded. Every log file. Every temporary file. Every backup your application has ever generated.
9. Backup Files Left in Public Directories
Developers sometimes create backup files that end up publicly accessible:
https://yoursite.com/index.php.bak
https://yoursite.com/config.php.old
https://yoursite.com/database.sql
https://yoursite.com/backup.zip
Automated scanners check for hundreds of these patterns on every site they probe. A database backup file left in a public directory is a complete data breach waiting to be discovered.
How Attackers Automate This
Reconnaissance is not done manually by a person sitting at a keyboard.
Automated tools scan millions of sites continuously:
- Nikto scans for thousands of known vulnerabilities and misconfigurations
- Gobuster brute forces directory and file names against wordlists of thousands of common paths
- WhatWeb fingerprints web technologies from response headers and page content
- GitDumper reconstructs source code from exposed Git objects automatically
These tools run against every IP address they can find. Your site is being scanned whether you know it or not. The question is not whether reconnaissance is happening it is whether your app is giving the scanners useful answers.
What to Hide — The Complete Checklist
Laravel configuration:
# .env — production settings
APP_ENV=production
APP_DEBUG=false
// config/app.php
'debug' => (bool) env('APP_DEBUG', false),
Restrict developer tool access:
Configure Telescope authorization and ensure the dashboard is restricted to trusted users or IP addresses:
// app/Providers/TelescopeServiceProvider.php
Gate::define('viewTelescope', function ($user) {
return in_array(request()->ip(), [
'127.0.0.1',
// your trusted IP addresses
]);
});
Apply the same principle to Horizon and Debugbar both should require authentication or IP restriction before any production deployment.
Block sensitive files at the web server level:
For Nginx:
location ~ /\.(env|git|htaccess) {
deny all;
return 404;
}
location ~ \.(bak|old|sql|zip|tar|gz)$ {
deny all;
return 404;
}
For Apache:
<FilesMatch "\.(env|git|bak|old|sql|zip)$">
Order allow,deny
Deny from all
</FilesMatch>
Remove technology headers:
In php.ini:
expose_php = Off
For Nginx:
server_tokens off;
For Apache:
ServerTokens Prod
ServerSignature Off
Disable directory listing:
For Nginx:
autoindex off;
For Apache:
Options -Indexes
Custom error pages that reveal nothing:
// app/Exceptions/Handler.php
public function render($request, Throwable $exception)
{
if (app()->environment('production')) {
if ($this->isHttpException($exception)) {
return response()->view(
'errors.' . $exception->getStatusCode(),
[],
$exception->getStatusCode()
);
}
return response()->view('errors.500', [], 500);
}
return parent::render($request, $exception);
}
Rate limit sensitive routes:
// routes/web.php
Route::middleware(['throttle:5,1'])->group(function () {
Route::post('/login', [AuthController::class, 'login']);
Route::post('/admin/login', [AdminController::class, 'login']);
});
5 attempts per minute. After that, Laravel applies throttling and returns a 429 response until the limit window resets.
The Deployment Checklist
Run this after every deployment:
-
APP_DEBUG=falseconfirmed in production -
APP_ENV=productionconfirmed - .env file not accessible via browser
- .git directory not in web root or blocked at server level
- Telescope, Horizon, Debugbar behind authentication or IP restriction
- Directory listing disabled
- Technology headers removed
- No backup or temporary files in public directories
- Admin routes rate limited
- Custom error pages returning generic messages
Where Kriosa Fits
Security configuration reduces your attack surface, but it does not stop attackers from trying. Every public application receives automated scanning sooner or later.
Reconnaissance generates distinctive traffic patterns that differ from normal user behavior.
Rapid sequential requests to non-existent paths. Requests for known sensitive file locations like .env, .git/config, and backup extensions. Requests with scanner user agent strings. Unusual probing patterns against admin route locations.
These patterns are exactly what Kriosa's ML engine detects flagging reconnaissance activity in your XAI dashboard before it turns into exploitation.
You cannot always prevent an attacker from probing your app. You can prevent your app from giving useful answers. And you can know the moment probing starts.
Configuration closes the information gaps. Kriosa watches for the probing behavior.
Both layers together.
Try it free: kriosa.com
Install it: composer require kriosa-ai/kriosa-php . Kriosa documentation
Built by a developer from Cameroon, for developers who want to understand their security not just outsource it.
The Series So Far
- Article 1: What your PHP logs actually look like during a SQL injection attack
- Article 2: Why URL encoding can break PHP security checks
- Article 3: The decode bomb problem — why unlimited URL decoding can be its own vulnerability
- Article 4: Parameterized queries — the only real fix for SQL injection
-
Article 5: XSS prevention in Laravel and why
{!! !!}is the line between safe and hacked - Article 6: This article — how attackers enumerate your Laravel app and what to hide
Top comments (0)