You've already forked Atomcms-edit
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:
@@ -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',
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -127,8 +127,6 @@ class User extends Authenticatable implements FilamentUser, HasName
|
||||
#[\Override]
|
||||
protected $fillable = ['username', 'mail', 'password', 'account_created', 'last_login', 'motto', 'look', 'credits', 'auth_ticket', 'home_room', 'ip_register', 'ip_current', 'referral_code', 'preferences', 'team_id', 'avatar_background', 'home_background', 'pincode', 'secret_key', 'extra_rank', 'is_hidden', 'background_id', 'background_stand_id', 'background_overlay_id', 'radio_points', 'pixels', 'points', 'online', 'gender', 'rank', 'mail_verified', 'two_factor_secret', 'two_factor_recovery_codes', 'two_factor_confirmed_at'];
|
||||
|
||||
protected $guarded = [];
|
||||
|
||||
#[\Override]
|
||||
protected $hidden = ['id', 'password', 'remember_token'];
|
||||
|
||||
|
||||
Reference in New Issue
Block a user