1.0.9 — imitation d'empreinte TLS en seconde tentative
This commit is contained in:
1 parent
12973dab5d
commit
d215b32a89
11 files changed
+86
-18
No files matched your search
@@ -2,6 +2,20 @@
|
|||||||
|
|
||||||
Format inspiré de [Keep a Changelog](https://keepachangelog.com/fr/1.1.0/).
|
Format inspiré de [Keep a Changelog](https://keepachangelog.com/fr/1.1.0/).
|
||||||
|
|
||||||
|
## [1.0.9] — 2026-09-13
|
||||||
|
|
||||||
|
### Ajouté
|
||||||
|
- **Imitation d'empreinte TLS sur la seconde tentative.** Les protections
|
||||||
|
modernes identifient un client par son empreinte TLS et HTTP/2 avant même de
|
||||||
|
lire ses en-têtes — c'est pourquoi les en-têtes seuls n'avaient récupéré que
|
||||||
|
3 sites sur 506. Maigret embarque `CurlCffiChecker`, qui imite l'empreinte
|
||||||
|
d'un vrai navigateur, mais ne l'active que pour les 108 sites portant
|
||||||
|
`tls_fingerprint` en amont. Limier l'étend désormais à tous les sites rejoués
|
||||||
|
en seconde passe. Désactivable par `LIMIER_TLS_IMPERSONATION_RETRY=false`.
|
||||||
|
|
||||||
|
Plus lent qu'aiohttp, donc réservé aux sites ayant déjà refusé la première
|
||||||
|
requête — le coût ne porte que là où il peut rapporter.
|
||||||
|
|
||||||
## [1.0.8] — 2026-09-12
|
## [1.0.8] — 2026-09-12
|
||||||
|
|
||||||
### Modifié
|
### Modifié
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|||||||
|
|
||||||
[project]
|
[project]
|
||||||
name = "limier"
|
name = "limier"
|
||||||
version = "1.0.8"
|
version = "1.0.9"
|
||||||
description = "Plateforme OSINT auto-hébergée : pseudonyme, nom, e-mail, domaine"
|
description = "Plateforme OSINT auto-hébergée : pseudonyme, nom, e-mail, domaine"
|
||||||
requires-python = ">=3.12,<3.14"
|
requires-python = ">=3.12,<3.14"
|
||||||
license = { text = "MIT" }
|
license = { text = "MIT" }
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
"""Limier — plateforme OSINT auto-hébergée."""
|
"""Limier — plateforme OSINT auto-hébergée."""
|
||||||
|
|
||||||
__version__ = "1.0.8"
|
__version__ = "1.0.9"
|
||||||
@@ -127,6 +127,15 @@ class Settings(BaseSettings):
|
|||||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:155.0) Gecko/20100101 Firefox/155.0"
|
"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:155.0) Gecko/20100101 Firefox/155.0"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
tls_impersonation_retry: bool = True
|
||||||
|
"""Imiter l'empreinte TLS d'un navigateur lors de la seconde tentative.
|
||||||
|
|
||||||
|
S'appuie sur ``curl_cffi``, déjà embarqué par Maigret. C'est le levier qui
|
||||||
|
agit sur les protections modernes : elles identifient un client par son
|
||||||
|
empreinte TLS bien avant de lire ses en-têtes. Plus lent qu'aiohttp, donc
|
||||||
|
réservé aux sites ayant déjà refusé la première requête.
|
||||||
|
"""
|
||||||
|
|
||||||
outbound_browser_headers: bool = True
|
outbound_browser_headers: bool = True
|
||||||
"""Présenter un jeu d'en-têtes de navigateur sur les requêtes sortantes.
|
"""Présenter un jeu d'en-têtes de navigateur sur les requêtes sortantes.
|
||||||
|
|
||||||
|
|||||||
@@ -173,8 +173,30 @@ def selectionner_sites(
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def dupliquer_avec_entetes(sites: dict) -> dict:
|
def dupliquer_pour_seconde_passe(sites: dict) -> dict:
|
||||||
"""Copie des sites donnés, avec les en-têtes de navigateur ajoutés.
|
"""Copie des sites donnés, préparée pour une seconde tentative renforcée.
|
||||||
|
|
||||||
|
Deux renforts sont appliqués, du moins au plus efficace :
|
||||||
|
|
||||||
|
**Les en-têtes de navigateur**, pour les sites qui n'en déclarent pas. Gain
|
||||||
|
mesuré en conditions réelles : 3 sites récupérés sur 506. Faible, mais réel.
|
||||||
|
|
||||||
|
**L'imitation d'empreinte TLS**, qui est le vrai levier. Les protections
|
||||||
|
modernes identifient un client par son empreinte TLS et HTTP/2 avant même
|
||||||
|
de lire ses en-têtes : annoncer Firefox tout en présentant l'empreinte de
|
||||||
|
Python est précisément ce qui déclenche le blocage. Maigret embarque
|
||||||
|
``CurlCffiChecker``, qui imite l'empreinte d'un vrai navigateur, mais ne
|
||||||
|
l'active que pour les sites portant ``tls_fingerprint`` dans leur champ
|
||||||
|
``protection`` — 108 sites sur 3 302 en amont. On l'étend ici à tous les
|
||||||
|
sites de la seconde passe.
|
||||||
|
|
||||||
|
Ce renfort n'est pas gratuit : curl_cffi est plus lent qu'aiohttp et ouvre
|
||||||
|
ses propres connexions. Le réserver aux sites ayant déjà échoué limite ce
|
||||||
|
coût aux seuls cas où il peut rapporter quelque chose.
|
||||||
|
|
||||||
|
Note : pour ces sites, curl_cffi retire le User-Agent afin d'utiliser celui
|
||||||
|
du navigateur imité — c'est voulu, la cohérence entre empreinte et en-têtes
|
||||||
|
est justement ce qu'on cherche.
|
||||||
|
|
||||||
**Duplication et non modification sur place.** Les objets rendus par
|
**Duplication et non modification sur place.** Les objets rendus par
|
||||||
``ranked_sites_dict`` proviennent de la base en cache, partagée par tous les
|
``ranked_sites_dict`` proviennent de la base en cache, partagée par tous les
|
||||||
@@ -186,12 +208,26 @@ def dupliquer_avec_entetes(sites: dict) -> dict:
|
|||||||
"""
|
"""
|
||||||
from maigret.sites import MaigretSite
|
from maigret.sites import MaigretSite
|
||||||
|
|
||||||
|
from limier.config import get_settings
|
||||||
|
|
||||||
|
settings = get_settings()
|
||||||
entetes = entetes_navigateur()
|
entetes = entetes_navigateur()
|
||||||
copies = {}
|
copies = {}
|
||||||
|
|
||||||
for nom, site in sites.items():
|
for nom, site in sites.items():
|
||||||
copie = MaigretSite(site.name, dict(site.json))
|
copie = MaigretSite(site.name, dict(site.json))
|
||||||
copie.headers = dict(site.headers) if site.headers else dict(entetes)
|
|
||||||
|
if settings.outbound_browser_headers:
|
||||||
|
copie.headers = dict(site.headers) if site.headers else dict(entetes)
|
||||||
|
|
||||||
|
if settings.tls_impersonation_retry:
|
||||||
|
protection = list(site.protection or [])
|
||||||
|
if "tls_fingerprint" not in protection:
|
||||||
|
protection.append("tls_fingerprint")
|
||||||
|
copie.protection = protection
|
||||||
|
|
||||||
copies[nom] = copie
|
copies[nom] = copie
|
||||||
|
|
||||||
return copies
|
return copies
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -347,8 +347,12 @@ class MoteurPseudonyme(Engine):
|
|||||||
module=self.name,
|
module=self.name,
|
||||||
level="info",
|
level="info",
|
||||||
message=(
|
message=(
|
||||||
f"Seconde tentative sur {len(a_rejouer)} site(s) en échec, "
|
f"Seconde tentative sur {len(a_rejouer)} site(s) en échec"
|
||||||
"avec des en-têtes de navigateur."
|
+ (
|
||||||
|
", avec imitation d'empreinte TLS."
|
||||||
|
if self.settings.tls_impersonation_retry
|
||||||
|
else ", avec des en-têtes de navigateur."
|
||||||
|
)
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
# Le total inclut désormais cette seconde passe, sinon la barre de
|
# Le total inclut désormais cette seconde passe, sinon la barre de
|
||||||
@@ -357,7 +361,7 @@ class MoteurPseudonyme(Engine):
|
|||||||
recuperes_avant = len(deja_vus)
|
recuperes_avant = len(deja_vus)
|
||||||
async for evenement in self._chercher_un(
|
async for evenement in self._chercher_un(
|
||||||
pseudo=pseudo,
|
pseudo=pseudo,
|
||||||
sites=database.dupliquer_avec_entetes(a_rejouer),
|
sites=database.dupliquer_pour_seconde_passe(a_rejouer),
|
||||||
compteur=compteur,
|
compteur=compteur,
|
||||||
label=f"{label} — seconde tentative",
|
label=f"{label} — seconde tentative",
|
||||||
deja_vus=deja_vus,
|
deja_vus=deja_vus,
|
||||||
|
|||||||
+6
-1
@@ -7,7 +7,7 @@
|
|||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
REGISTRY=registry.tips-of-mine.com
|
REGISTRY=registry.tips-of-mine.com
|
||||||
TAG=1.0.8
|
TAG=1.0.9
|
||||||
LIMIER_HOST=limier.tips-of-mine.com
|
LIMIER_HOST=limier.tips-of-mine.com
|
||||||
LIMIER_PUBLIC_URL=https://limier.tips-of-mine.com
|
LIMIER_PUBLIC_URL=https://limier.tips-of-mine.com
|
||||||
LIMIER_ENVIRONMENT=prod
|
LIMIER_ENVIRONMENT=prod
|
||||||
@@ -76,4 +76,9 @@ LIMIER_DNS_RESOLVERS=1.1.1.1,9.9.9.9
|
|||||||
# refusé la première requête. Mettre à false pour s'en tenir au comportement
|
# refusé la première requête. Mettre à false pour s'en tenir au comportement
|
||||||
# natif de Maigret et comparer les taux de blocage.
|
# natif de Maigret et comparer les taux de blocage.
|
||||||
LIMIER_OUTBOUND_BROWSER_HEADERS=true
|
LIMIER_OUTBOUND_BROWSER_HEADERS=true
|
||||||
|
|
||||||
|
# Imitation d'empreinte TLS sur la seconde tentative (curl_cffi). C'est le
|
||||||
|
# levier qui agit réellement sur les protections anti-robot. Mettre à false
|
||||||
|
# pour mesurer ce qu'il apporte.
|
||||||
|
LIMIER_TLS_IMPERSONATION_RETRY=true
|
||||||
LIMIER_WORKER_MAX_JOBS=4
|
LIMIER_WORKER_MAX_JOBS=4
|
||||||
@@ -56,7 +56,7 @@ x-limier-env: &limier-env
|
|||||||
services:
|
services:
|
||||||
|
|
||||||
limier-api:
|
limier-api:
|
||||||
image: ${REGISTRY:-registry.tips-of-mine.com}/limier/limier-backend:${TAG:-1.0.8}
|
image: ${REGISTRY:-registry.tips-of-mine.com}/limier/limier-backend:${TAG:-1.0.9}
|
||||||
container_name: limier-api
|
container_name: limier-api
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
command: ["limier-api"]
|
command: ["limier-api"]
|
||||||
@@ -99,7 +99,7 @@ services:
|
|||||||
- "homepage.description=Plateforme OSINT"
|
- "homepage.description=Plateforme OSINT"
|
||||||
|
|
||||||
limier-worker:
|
limier-worker:
|
||||||
image: ${REGISTRY:-registry.tips-of-mine.com}/limier/limier-backend:${TAG:-1.0.8}
|
image: ${REGISTRY:-registry.tips-of-mine.com}/limier/limier-backend:${TAG:-1.0.9}
|
||||||
container_name: limier-worker
|
container_name: limier-worker
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
command: ["limier-worker"]
|
command: ["limier-worker"]
|
||||||
@@ -131,7 +131,7 @@ services:
|
|||||||
- "com.centurylinklabs.watchtower.enable=true"
|
- "com.centurylinklabs.watchtower.enable=true"
|
||||||
|
|
||||||
limier-web:
|
limier-web:
|
||||||
image: ${REGISTRY:-registry.tips-of-mine.com}/limier/limier-frontend:${TAG:-1.0.8}
|
image: ${REGISTRY:-registry.tips-of-mine.com}/limier/limier-frontend:${TAG:-1.0.9}
|
||||||
container_name: limier-web
|
container_name: limier-web
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
volumes:
|
volumes:
|
||||||
@@ -214,7 +214,7 @@ services:
|
|||||||
# ``--profile``, pour que l'installation par défaut reste minimale.
|
# ``--profile``, pour que l'installation par défaut reste minimale.
|
||||||
|
|
||||||
limier-holehe:
|
limier-holehe:
|
||||||
image: ${REGISTRY:-registry.tips-of-mine.com}/limier/limier-holehe:${TAG:-1.0.8}
|
image: ${REGISTRY:-registry.tips-of-mine.com}/limier/limier-holehe:${TAG:-1.0.9}
|
||||||
container_name: limier-holehe
|
container_name: limier-holehe
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
profiles: ["holehe"]
|
profiles: ["holehe"]
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ Ni base de données, ni Redis, ni Authentik. On appelle le moteur directement.
|
|||||||
cd /opt # ou n'importe où, il suffit d'avoir essai_moteur.py sous la main
|
cd /opt # ou n'importe où, il suffit d'avoir essai_moteur.py sous la main
|
||||||
|
|
||||||
docker run --rm -v "$PWD/essai_moteur.py:/tmp/essai.py" \
|
docker run --rm -v "$PWD/essai_moteur.py:/tmp/essai.py" \
|
||||||
registry.tips-of-mine.com/limier/limier-backend:1.0.8 \
|
registry.tips-of-mine.com/limier/limier-backend:1.0.9 \
|
||||||
python /tmp/essai.py domain tips-of-mine.com
|
python /tmp/essai.py domain tips-of-mine.com
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -24,7 +24,7 @@ Le moteur pseudonyme, en version courte :
|
|||||||
```bash
|
```bash
|
||||||
docker run --rm -v "$PWD/essai_moteur.py:/tmp/essai.py" \
|
docker run --rm -v "$PWD/essai_moteur.py:/tmp/essai.py" \
|
||||||
-e LIMIER_USERNAME_TOP_SITES=80 \
|
-e LIMIER_USERNAME_TOP_SITES=80 \
|
||||||
registry.tips-of-mine.com/limier/limier-backend:1.0.8 \
|
registry.tips-of-mine.com/limier/limier-backend:1.0.9 \
|
||||||
python /tmp/essai.py username soxoj
|
python /tmp/essai.py username soxoj
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -33,7 +33,7 @@ Et la recherche par nom, qui montre les variantes déduites :
|
|||||||
```bash
|
```bash
|
||||||
docker run --rm -v "$PWD/essai_moteur.py:/tmp/essai.py" \
|
docker run --rm -v "$PWD/essai_moteur.py:/tmp/essai.py" \
|
||||||
-e LIMIER_USERNAME_TOP_SITES=60 \
|
-e LIMIER_USERNAME_TOP_SITES=60 \
|
||||||
registry.tips-of-mine.com/limier/limier-backend:1.0.8 \
|
registry.tips-of-mine.com/limier/limier-backend:1.0.9 \
|
||||||
python /tmp/essai.py name "Hubert Cornet"
|
python /tmp/essai.py name "Hubert Cornet"
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "limier-frontend",
|
"name": "limier-frontend",
|
||||||
"version": "1.0.8",
|
"version": "1.0.9",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
Reference in new issue
Block a user