Aller au contenu
Tous les challenges
OSINT Résolu

AlpAnalytics 2: Off the Books

Casser la k-anonymité du jeu v2 pour démasquer l'acheteur.

Auteur
Aubry Mangold (Aubrax)
ID CTFd
22
Scripts
3 fichiers

Ré-identification (cassage de la k-anonymité v2)

  • v2 : people (pid renouvelés, age_band, npa_region = 2 premiers chiffres, ev), events (ts tronqué à l’heure, zone, mode, party_size, group_name), période 16.01–28.02.2026.
  • Quasi-identifiants de Gaëlle : 40-49 ans (née 1984), région 14 (NPA 1422), VE → 54 candidats.
  • Recoupement v1↔v2 : les 18 événements v1 de 471c06bd entre le 16.01 et le 06.02 (ts tronqué à l’heure, P-YVE-GARE/ST-YVE→YVE-GARE, ST-ZUG→ZUG-GARE) se retrouvent tous chez 84422f7c (18/18 ; le suivant n’en a que 10). → match.py

Rendez-vous secrets

  • 84422f7c fait 3 allers-retours Yverdon→Berne (10.02, 24.02, 26.02) avec un payment à BER-AAREFORUM (restaurant « Aareforum Bern ») à 13h ; Berne est loin d’Yverdon et de Zoug.
  • Parmi les reservation à l’Aareforum ces jours-là, une seule personne apparaît les 3 jours : 34865105 (40-49 ans, région 63 = Zoug), réservation party_size 2 à 12h, au nom de « Gantenbein Planung » (nom utilisé uniquement par elle).
  • Sa trajectoire : pendulaire Cham↔Zoug chaque jour ouvré, sauf ces 3 jours où elle va Cham→Berne → c’est le managing director de l’acheteur, en poste à Zoug. → booker.py, meet.py

Reproduction

../alpanalytics/.venv/bin/python -I match.py files/v2 ../alpanalytics/files/v1
../alpanalytics/.venv/bin/python -I booker.py files/v2

Code

import sys, pandas as pd
pd.set_option('display.width',250,'display.max_rows',500)
V2, V1 = sys.argv[1], sys.argv[2]
p=pd.read_parquet(f'{V2}/people.parquet'); e=pd.read_parquet(f'{V2}/events.parquet')
e1=pd.read_parquet(f'{V1}/events.parquet')
pm={'P-YVE-GARE':'YVE-GARE','ST-YVE':'YVE-GARE','ST-ZUG':'ZUG-GARE'}
g=e1[(e1.pid=='471c06bd')&(e1.ts>='2026-01-16')]
sig=set(zip(g.ts.dt.floor('h'),g.place_id.map(lambda x:pm.get(x,x)),g['mode']))
c=p[(p.age_band=='40-49')&(p.npa_region==14)&p.ev]
res=[]
for pid,d in e[e.pid.isin(c.pid)&(e.ts<'2026-02-07')].groupby('pid'):
    s=set(zip(d.ts,d.zone,d['mode'])); res.append((len(sig&s),len(s),pid))
for r in sorted(res,reverse=True)[:5]: print(r, 'sur',len(sig))
best=sorted(res,reverse=True)[0][2]
d=e[e.pid==best]; print(p[p.pid==best]); print(d.to_string())