Parent : [[INDEX-CYBERSECU]]
Détection de skimming bancaire - Payment data interception via custom controller
Problème
Un controller CodeIgniter de 1,6 Ko (Security.php), présent dans le code de production, reçoit le numéro de carte complet (PAN), le CVV, la date d'expiration et le nom du titulaire via POST. Les données transitent d'abord en clair par le serveur YGG avant d'être relayées vers un processeur de paiement (Singularity) via un formulaire caché auto-soumis.
Contexte archive Ygg
- Source :
https://yggleak.top/fr/home/ygg-dossier - Fichier :
Security.php(controller CodeIgniter, 1,6 Ko) - Données interceptées : PAN (numéro de carte), CVV, date d'expiration, nom du titulaire
- Processeur : Singularity
- Technique : Formulaire caché auto-soumis
- Redirection silencieuse vers
google.comquand le token de commande est invalide (anti-analyse) - Témoignages : Fraudes bancaires sur Reddit après des paiements sur YGG
Technique de l'attaquant
1. Interception des données bancaires
// Security.php — Controller CodeIgniter
// Reçoit via POST :
// - PAN (Primary Account Number) = numéro de carte complet
// - CVV (Card Verification Value)
// - Expiration date
// - Cardholder name
// Les données transitent EN CLAIR sur le serveur YGG
// Puis relayées vers le processeur Singularity via formulaire caché
2. Anti-analyse : redirection vers google.com
// Si token de commande invalide → redirection silencieuse vers google.com
// Technique classique d'anti-analyse pour masquer le comportement réel
3. Formulaire caché auto-soumis
<!-- Formulaire invisible qui auto-submit les données vers Singularity -->
<form id="payment-form" action="https://processor.singularity.com/pay" method="POST" style="display:none;">
<input type="hidden" name="card_number" value="[PAN INTERCEPTÉ]">
<input type="hidden" name="cvv" value="[CVV INTERCEPTÉ]">
<input type="hidden" name="expiry" value="[EXPIRATION INTERCEPTÉE]">
<input type="hidden" name="name" value="[NOM INTERCEPTÉ]">
</form>
<script>document.getElementById('payment-form').submit();</script>
4. Témoignages d'utilisateurs
"Après avoir payé du free leech la semaine dernière par PayPal, aujourd'hui 4000E de matériel de son ont été commandé chez Thomann et livré à une boutique inexistante du 93 sous le nom de Kevin. Ce PayPal n'avait pas été utilisé depuis plus d'un an sauf pour YGG." — u/TatonTatonne, octobre 2024
Solution protectif — Protection contre le skimming bancaire
1. Jamais toucher les données bancaires
## Règle d'or PCI-DSS
**NE JAMAIS** :
- ❌ Recevoir le PAN (numéro de carte) sur son serveur
- ❌ Recevoir le CVV sur son serveur
- ❌ Recevoir la date d'expiration sur son serveur
- ❌ Stocker les données bancaires (même chiffrées)
- ❌ Logger les données bancaires
- ❌ Relayer les données bancaires via son serveur
**TOUJOURS** :
- ✅ Utiliser un checkout hébergé par le processeur (Stripe Checkout, PayPal Smart Buttons)
- ✅ Rediriger l'utilisateur vers la page de paiement du processeur
- ✅ Utiliser des tokens (Stripe Elements, PayPal Vault)
- ✅ Le serveur ne reçoit qu'un token de confirmation, jamais les données brutes
2. Implémentation correcte avec Stripe Checkout
// checkout.php — Implémentation PCI-DSS compliant
require 'vendor/autoload.php';
\Stripe\Stripe::setApiKey(getenv('STRIPE_SECRET_KEY'));
// Créer une session de checkout (redirection vers Stripe)
$session = \Stripe\Checkout\Session::create([
'payment_method_types' => ['card'],
'line_items' => [[
'price_data' => [
'currency' => 'eur',
'product_data' => [
'name' => 'Donation YGG',
],
'unit_amount' => 1499, // 14.99 EUR
],
'quantity' => 1,
]],
'mode' => 'payment',
'success_url' => 'https://monsite.com/success?session_id={CHECKOUT_SESSION_ID}',
'cancel_url' => 'https://monsite.com/cancel',
]);
// Redirection vers Stripe (PAS de données bancaires sur notre serveur)
header("Location: " . $session->url);
exit;
3. Détection de skimming côté serveur
// detect-skimming.php — Détecter les tentatives d'interception
function detectPaymentSkimming($requestData) {
$suspiciousPatterns = [
// Champs de carte bancaire
'card_number' => '/^\d{13,19}$/',
'cardNumber' => '/^\d{13,19}$/',
'pan' => '/^\d{13,19}$/',
'cvv' => '/^\d{3,4}$/',
'cvc' => '/^\d{3,4}$/',
'expiry' => '/^(0[1-9]|1[0-2])\/(\d{2}|\d{4})$/',
'expiration' => '/^(0[1-9]|1[0-2])\/(\d{2}|\d{4})$/',
];
$detected = [];
foreach ($suspiciousPatterns as $field => $pattern) {
if (isset($requestData[$field]) && preg_match($pattern, $requestData[$field])) {
$detected[] = [
'field' => $field,
'value_masked' => substr($requestData[$field], 0, 4) . '****',
'pattern' => 'payment_data'
];
}
}
// Détecter les Luhn-valid numbers (numéros de carte valides)
foreach ($requestData as $key => $value) {
if (is_string($value) && strlen($value) >= 13 && strlen($value) <= 19) {
if (isLuhnValid($value)) {
$detected[] = [
'field' => $key,
'value_masked' => substr($value, 0, 4) . '****',
'pattern' => 'luhn_valid_pan'
];
}
}
}
return $detected;
}
function isLuhnValid($number) {
$sum = 0;
$alternate = false;
for ($i = strlen($number) - 1; $i >= 0; $i--) {
$n = intval($number[$i]);
if ($alternate) {
$n *= 2;
if ($n > 9) $n -= 9;
}
$sum += $n;
$alternate = !$alternate;
}
return ($sum % 10 === 0);
}
// Utilisation
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$skimmingDetected = detectPaymentSkimming($_POST);
if (!empty($skimmingDetected)) {
// ALERTE : Données bancaires détectées dans une requête POST
error_log("[SKIMMING-DETECT] Données bancaires interceptées! Fields: " .
json_encode(array_column($skimmingDetected, 'field')));
// Bloquer la requête
http_response_code(403);
die('Payment data interception detected');
}
}
4. Monitoring des formulaires cachés
// detect-hidden-forms.js — Détecter les formulaires de paiement cachés
(function() {
'use strict';
// Surveiller la création de formulaires
const observer = new MutationObserver(mutations => {
mutations.forEach(mutation => {
mutation.addedNodes.forEach(node => {
if (node.tagName === 'FORM' || (node.querySelector && node.querySelector('form'))) {
const forms = node.tagName === 'FORM' ? [node] : node.querySelectorAll('form');
forms.forEach(form => {
const style = window.getComputedStyle(form);
const isHidden = style.display === 'none' || style.visibility === 'hidden';
if (isHidden) {
const inputs = form.querySelectorAll('input');
const hasCardFields = Array.from(inputs).some(input => {
const name = (input.name || input.id || '').toLowerCase();
return name.includes('card') || name.includes('cvv') ||
name.includes('expir') || name.includes('pan');
});
if (hasCardFields) {
console.error('[SKIMMING-DETECT] Formulaire de paiement caché détecté!');
console.error(' Action:', form.action);
console.error(' Inputs:', Array.from(inputs).map(i => i.name || i.id));
// Empêcher la soumission
form.addEventListener('submit', e => {
e.preventDefault();
e.stopPropagation();
alert('Formulaire de paiement suspect bloqué');
}, true);
}
}
});
}
});
});
});
observer.observe(document.body, { childList: true, subtree: true });
})();
5. Audit des controllers de paiement
// audit-payment-controllers.php — Auditer les controllers de paiement
function auditPaymentControllers($controllersDir) {
$suspiciousFiles = [];
foreach (glob($controllersDir . '/*.php') as $file) {
$content = file_get_contents($file);
$suspiciousPatterns = [
'card_number' => '/card[_\s]?number/i',
'cvv' => '/\bcvv\b/i',
'cvc' => '/\bcvc\b/i',
'pan' => '/\bpan\b/i',
'hidden_form' => '/display\s*:\s*none|visibility\s*:\s*hidden/i',
'auto_submit' => '/\.submit\(\)|form\.submit/i',
'redirect_google' => '/google\.com/i',
];
foreach ($suspiciousPatterns as $patternName => $pattern) {
if (preg_match($pattern, $content)) {
$suspiciousFiles[] = [
'file' => basename($file),
'pattern' => $patternName,
'line' => getLineNumber($content, $pattern)
];
}
}
}
return $suspiciousFiles;
}
function getLineNumber($content, $pattern) {
$lines = explode("\n", $content);
foreach ($lines as $lineNumber => $line) {
if (preg_match($pattern, $line)) {
return $lineNumber + 1;
}
}
return 0;
}
Checklist de validation
- Pas de réception de PAN/CVV/expiration sur le serveur
- Checkout hébergé par le processeur (Stripe Checkout, PayPal Smart Buttons)
- Pas de formulaire caché auto-soumis avec des données bancaires
- Pas de redirection vers google.com ou autre en cas de token invalide (anti-analyse)
- Logs ne contiennent JAMAIS de données bancaires
- Monitoring des requêtes POST contenant des Luhn-valid numbers
- Audit régulier des controllers de paiement
- Détection de formulaires cachés côté client
- PCI-DSS compliance (SAQ-A ou SAQ-A-EP)
- Tokenisation des paiements (pas de données brutes)
- Https obligatoire pour toutes les pages de paiement
- CSP (Content Security Policy) restrictive sur les pages de paiement
Anti-pattern associé
KNOW-ANT-018— Custom payment controller handling raw card data
Références
- Archive Ygg :
Security.php(YGGLeak) - PCI-DSS : https://www.pcisecuritystandards.org
- OWASP : Payment Card Verification