fix(security): patch critical security vulnerabilities

- Remove User::$guarded = [] to prevent mass assignment attacks
- Enable SQL strict mode and disable emulated prepares (SQL injection prevention)
- Switch password hashing from bcrypt to argon2id (stronger algorithm)
- Enable session encryption to protect session data at rest
- Restrict TrustProxies to localhost only (prevent IP spoofing)
- Restrict CORS allowed_methods via env variable instead of wildcard
- Add PayPal amount mismatch detection to prevent payment manipulation
- Add double-capture prevention (idempotency check)
- Add expected_amount column to transactions table for verification
This commit is contained in:
root
2026-05-19 19:37:15 +02:00
parent 05fc7b04bc
commit 7f59024bef
8 changed files with 56 additions and 14 deletions
+29 -6
View File
@@ -4,6 +4,7 @@ namespace App\Http\Controllers\Shop;
use App\Http\Controllers\Controller;
use App\Http\Requests\AccountTopupFormRequest;
use App\Models\Shop\WebsitePaypalTransaction;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Log;
@@ -68,6 +69,7 @@ class PayPalController extends Controller
$request->user()->transactions()->create([
'transaction_id' => $response['id'],
'amount' => 0,
'expected_amount' => $amount,
]);
return redirect()->away($links['href']);
@@ -79,7 +81,7 @@ class PayPalController extends Controller
);
}
public function successful(Request $request): Response
public function successful(Request $request): Response|RedirectResponse
{
$request->validate([
'token' => 'required',
@@ -92,8 +94,12 @@ class PayPalController extends Controller
return to_route('shop.index')->withErrors(['message' => __('Something went wrong, please try again later')]);
}
if ($transaction->status === self::STATUS_COMPLETED) {
return to_route('shop.index')->withErrors(['message' => __('Transaction already processed')]);
}
$response = $this->getProvider()->capturePaymentOrder($request['token']);
$paymentDetails = $response['purchase_units'][0]['payments']['captures'][0];
$paymentDetails = $response['purchase_units'][0]['payments']['captures'][0] ?? null;
if (! isset($response['status'], $paymentDetails)) {
Log::error('Invalid response from PayPal', ['response' => $response]);
@@ -112,11 +118,28 @@ class PayPalController extends Controller
return to_route('shop.index')->withErrors(['message' => __('Something went wrong, please check your paypal account to make sure nothing was deducted and try again')]);
}
$paymentDetails = $response['purchase_units'][0]['payments']['captures'][0];
$capturedAmount = (float) $paymentDetails['amount']['value'];
$expectedAmount = (float) ($transaction->expected_amount ?? 0);
if ($expectedAmount > 0 && abs($capturedAmount - $expectedAmount) > 0.01) {
Log::warning('PayPal amount mismatch', [
'transaction_id' => $transaction->transaction_id,
'expected' => $expectedAmount,
'captured' => $capturedAmount,
]);
$transaction->update([
'status' => 'AMOUNT_MISMATCH',
'description' => 'Captured amount does not match expected amount',
'amount' => $capturedAmount,
]);
return to_route('shop.index')->withErrors(['message' => __('Transaction amount mismatch detected')]);
}
$transaction->update([
'status' => $paymentDetails['status'],
'amount' => $paymentDetails['amount']['value'],
'amount' => $capturedAmount,
'currency' => $paymentDetails['amount']['currency_code'],
]);
@@ -126,12 +149,12 @@ class PayPalController extends Controller
);
}
$user->increment('website_balance', $paymentDetails['amount']['value']);
$user->increment('website_balance', $capturedAmount);
return to_route('shop.index')->with('success', __('Transaction successful'));
}
public function cancelled(Request $request): Response
public function cancelled(Request $request): RedirectResponse
{
$request->validate([
'token' => 'required',
+1 -1
View File
@@ -15,7 +15,7 @@ class TrustProxies extends Middleware
* @var array<int, string>|string|null
*/
#[\Override]
protected $proxies;
protected $proxies = '127.0.0.1,::1';
/**
* The headers that should be used to detect proxies.