📋 Laravel Technical Audit
📌 Overview
Overall risk classification: 🔴 Critical
The codebase demonstrates severe security and architectural deficiencies. The most alarming issues include a Global IDOR (Insecure Direct Object Reference) in the account scoping middleware, which allows any authenticated user to impersonate any other account, and Non-Atomic Financial Checks, which expose the system to multi-user race conditions and potential financial drain. Additionally, the presence of hardcoded sensitive credentials and weak authentication factors (4-digit PINs for financial withdrawals) places the entire platform at high risk of compromise.
🚨 Critical Issues
🔴 Issue 1: Global Account Impersonation (IDOR)
- Type: Security / Broken Access Control
- Severity: Critical
- Affected file:
app/Http/Middleware/SetActiveAccountMiddleware.php - Code snippet:
php
public function handle(Request $request, Closure $next): Response {
$accountHeader = $request->header('account') ?? $request->header('X-Customer-Id');
// ...
} else {
$account = Account::find($accountHeader); // <--- NO PERMISSION CHECK
if (!$account) { throw new ModelNotFoundException('Conta não encontrada.', 404); }
}
$request->attributes->set('account', $account);
return $next($request);
}- Technical description: The middleware trusts the
accountheader value without verifying if the authenticated user belongs to or has permissions for that specific account. - Actual impact: Any logged-in user can access data and perform actions on behalf of ANY other account in the system simply by changing the HTTP header.
- Possible exploit: An attacker can enumerate account IDs and scrape balances, client data, and initiate transactions across the entire platform.
- Recommended fix: Verify the mapping between the user and the requested account (e.g., via the
user_accountspivot table). - Corrected code:
php
$account = Auth::user()->accounts()->where('accounts.id', $accountHeader)->firstOrFail();🔴 Issue 2: Financial Race Condition (Balance Exhaustion)
- Type: Logic / Concurrency
- Severity: Critical
- Affected file:
app/Http/Controllers/QuickPay/SicrediController.php(andWithdrawController.php) - Code snippet:
php
$balance = $accout->balance() - $txa;
if ($balance < $request->mount) {
return response()->json(['message' => 'Saldo insuficiente.'], 400);
}
// ... API CALL TO PAY ...- Technical description: The balance check is not atomic. If two identical requests are sent simultaneously, both could read the "sufficient" balance before either results in a debit, leading to a negative balance and loss of funds.
- Actual impact: Financial loss due to overdraft/double-spending.
- Possible exploit: Using tools like
burp intruderor simple scripts to send concurrent withdrawal requests. - Recommended fix: Use database-level negative balance constraints and pessimistic locking (
lockForUpdate). - Corrected code:
php
DB::transaction(function () use ($accout, $request) {
$balanceRecord = Balance::where('account_id', $accout->id)->lockForUpdate()->first();
if ($balanceRecord->balance < $request->mount + $accout->tax_pix_out) {
throw new \Exception("Saldo insuficiente.");
}
// Record transaction...
});🔴 Issue 3: Hardcoded Application Secrets
- Type: Security
- Severity: Critical
- Affected file:
app/Http/Controllers/QuickPay/SicrediController.php - Code snippet:
php
"client_id" => "multipag-fastgivrmoneyip-client",
"client_secret" => "y5EU7QV3H6YWo9hzLJUFgvWwxfi5f9t6",- Technical description: Production credentials for the Sicredi payment gateway are hardcoded in the repository.
- Actual impact: Exposure of credentials via source code access. Full ability for an attacker to manipulate bank payments.
- Recommended fix: Use
.envandconfig()helpers.
🔴 Issue 4: Missing Ownership Check in Withdrawal Confirmation
- Type: Security / IDOR
- Severity: Critical
- Affected file:
app/Http/Controllers/WithdrawController.php - Code snippet:
php
public function endWithdraw(Request $request) {
// ...
$pix = PixWithdraw::whereTxid($request->txid)->whereStatus(0)->first();
if ($pix === null) { ... }
// ... continues to confirm based on the TXID only ...
}- Technical description: The
endWithdrawmethod retrieves the withdrawal record bytxidbut does not scope it to the currently authenticated account. - Actual impact: An attacker knowing a
txid(possibly via enumeration or sniffing) could confirm a withdrawal for another account. - Recommended fix: Scope the query to the account in the request attributes.
- Corrected code:
php
$account = $request->attributes->get('account');
$pix = PixWithdraw::where('txid', $request->txid)
->where('account_id', $account->id)
->where('status', 0)
->firstOrFail();⚠ Moderate Issues
🟡 Use of env() outside config files
- Type: Best Practice / Performance
- Severity: Moderate
- Description:
env()is called directly in multiple controllers (e.g.BoletoController). Ifconfig:cacheis executed, these calls will always returnnull. - Impact: Application fails mysteriously in production.
- Fix: Map values in
config/*.phpand useconfig('app.url').
🟡 Low Entropy for Financial Codes
- Type: Security
- Severity: Moderate
- Description: Withdrawal codes are 4-digit numeric (
rand(1000, 9999)). - Impact: Vulnerable to brute-force attacks within the 4-minute expiration window.
- Fix: Use
Str::random(6)or 6-digit codes with strict rate-limiting.
🟡 Mass Assignment Risk on User
- Type: Security
- Severity: Moderate
- Description:
statusis in$fillable. If a profile edit endpoint passesrequest()->all()to$user->update(), a user can reactivate themselves or change internal statuses.
ℹ Minor Issues
🔵 Redundant Queries in Observers
- Type: Performance
- Description:
TransactionObservercalls$transaction->update()inside thecreatedhook. This triggers a second query immediately after every insert. - Fix: Use the
creatinghook to modify attributes before the insert.
🔒 OWASP Analysis
- A01:2021-Broken Access Control: CRITICAL. Global IDOR in Middleware.
- A02:2021-Cryptographic Failures: CRITICAL. Hardcoded Client Secret.
- A07:2021-Identification and Authentication Failures: HIGH. Low entropy PINs.
- Mass Assignment: MEDIUM. Sensitive fields in
$fillablemodels.
⚡ Performance Bottlenecks
- Inefficient Balance View:
vw_balancelikely sums all transactions on the fly. As the database grows to millions of rows, checking balance will stop scaling. Suggestion: Use a cached balance column in theaccountsorbalancestable, updated via increments. - N+1 Queries: Potential in
InvoiceControllerwhen loadingpixandboletorelations inside theChargeResourcewithout eager loading in the controller. - Memory Limits:
ini_set('memory_limit', '-1')inInvoiceControlleris a "code smell" indicating poor query optimization in exports.
🧠 Architectural Recommendations
- Service Layer: Extract financial logic from
SicrediControllerandWithdrawControllerinto specialized Services. - Transaction Scoping: Moving mapping checks into Middleware or dedicated Trait for all Models.
- Pessimistic Locking: Crucial for all financial debit operations.
🧪 Testing Recommendations
- Concurrency Tests: Use
laravel/pintor custom scripts to test the balance check under load. - IDOR Tests: Create Feature tests that attempt to access Account B's data using Account A's token + header.
- Mocking: Use
Http::fake()to mock the Sicredi gateway respond.
✅ Final Checklist
- [ ] Secure mass assignment (❌ -
statusexposed) - [ ] Correct authorization (❌ - Critical IDOR in Middleware)
- [ ] No N+1 (⚠️ - Potential in resources)
- [ ] No SQL Injection (✅ - Mostly using Eloquent/Bindings)
- [ ] Correct transactions (❌ - Missing atomic locks)
- [ ] Testable code (⚠️ - Coupled controllers)
- [ ] Sustainable architecture (⚠️ - God controllers)