Perfil pagador nível 3: consistência de device, clock drift e reparo fino
Nível 3 da trilha. Aprofunda as checagens de dispositivo (timezone vs. IP, locale vs. UA, accept-language, clock drift via NTP, fingerprint Canvas/WebGL estável) e mostra como reparar inconsistências antes do checkout para evitar challenge/recusa indevida.
🎧 audiobook · nível 1
Perfil pagador nível 3: consistência de device, clock drift e reparo fino
Continuação do nível 2
No nível 2 você mediu rede, identidade e device em traços grossos. Aqui descemos para os sinais finos que ACS, Stripe Radar e Adyen ponderam pesado: coerência entre fuso/IP/locale, drift do relógio (importante para 3DS, OTP TOTP e cookies de sessão), client hints e fingerprint estável. Inconsistência aqui costuma ser a diferença entre frictionless e challenge.
Por que consistência importa mais que “limpeza”
Um device limpo mas inconsistente (IP no Brasil, fuso em UTC, locale en_US, accept-language pt-BR;q=0.9, relógio 47s atrasado) levanta mais alertas do que um device com root coerente. ACS e antifraude calculam um vetor de coerência: cada par de campos contraditórios soma para o risco. O objetivo do nível 3 é zerar contradições.
- IP geo (BR/SP) ↔ timezone do device (America/Sao_Paulo, -03:00).
- Locale do SO (pt_BR.UTF-8) ↔ Accept-Language do browser (pt-BR,pt;q=0.9).
- User-Agent declarado ↔ Client Hints (Sec-CH-UA-Platform, Sec-CH-UA-Mobile).
- Relógio do device ↔ tempo NTP real (drift < 2s).
- Resolução de tela ↔ DPR ↔ família de device declarada no UA.
Clock drift: o sinal mais subestimado
TOTP (Google/Microsoft Authenticator) tolera ~30s. 3DS challenge com OTP por SMS tolera ~5min. Mas cookies HttpOnly de sessão, JWT exp, e o campo acsTransID do 3DS carregam timestamps que precisam bater com NTP. Devices com relógio puxado manualmente (offline há semanas, BIOS RTC errada, fuso trocado sem ajustar hora) viram challenge sistematicamente.
Medir drift NTP rápido no Termux
pkg install -y ntp
ntpdate -q pool.ntp.org # mostra offset em segundos
# saida tipica: "server x.x.x.x, stratum 2, offset -0.043s"
# se |offset| > 2s, reparar antes de pagarScript Python — consistência fina (nível 3)
Complementa o payer_profile.py do nível 2: coleta timezone vs. IP, locale vs. accept-language, drift NTP, hash de fingerprint estável (UA + plataforma + resolução + fuso) e cospe os pares contraditórios encontrados, com sugestão de reparo para cada um.
Pré-requisitos
pkg update -y
pkg install -y python ntp tzdata
pip install requests rich ntplib~/payer_consistency.py — checagens de consistência
#!/usr/bin/env python3
"""
payer_consistency.py — nivel 3 da trilha de pagamentos.
Mede CONSISTENCIA entre IP/fuso/locale/UA/clock e propoe reparos.
"""
from __future__ import annotations
import os, sys, json, time, locale, hashlib, subprocess
from datetime import datetime, timezone
import requests, ntplib
from rich.console import Console
from rich.table import Table
console = Console()
TIMEOUT = 8
# mapa simplificado: pais -> fusos esperados
COUNTRY_TZ = {
"BR": ["America/Sao_Paulo","America/Bahia","America/Fortaleza",
"America/Manaus","America/Belem","America/Recife"],
"US": ["America/New_York","America/Chicago","America/Denver",
"America/Los_Angeles","America/Phoenix"],
"PT": ["Europe/Lisbon"], "ES": ["Europe/Madrid"],
"AR": ["America/Argentina/Buenos_Aires"],
}
COUNTRY_LANG = {
"BR": ("pt", "pt_BR"), "PT": ("pt", "pt_PT"),
"US": ("en", "en_US"), "GB": ("en", "en_GB"),
"AR": ("es", "es_AR"), "ES": ("es", "es_ES"),
}
def get_ip_country() -> str | None:
try:
r = requests.get("https://ipwho.is/", timeout=TIMEOUT).json()
return r.get("country_code")
except Exception:
return None
def get_device_tz() -> str:
# Termux/Android expoe via getprop; fallback para /etc/timezone
try:
out = subprocess.check_output(["getprop","persist.sys.timezone"],
timeout=2).decode().strip()
if out: return out
except Exception:
pass
if os.path.exists("/etc/timezone"):
return open("/etc/timezone").read().strip()
return time.strftime("%Z")
def get_locale() -> str:
try:
return locale.getlocale()[0] or os.environ.get("LANG","")
except Exception:
return os.environ.get("LANG","")
def get_clock_drift() -> float | None:
"""Offset em segundos entre relogio local e NTP pool."""
try:
c = ntplib.NTPClient()
r = c.request("pool.ntp.org", version=3, timeout=TIMEOUT)
return float(r.offset)
except Exception:
return None
def expected_ua_hint() -> dict:
"""User-Agent + Client Hints que um Chrome Android real envia,
derivados do que o device declara via env. Usado so para
voce comparar com o que o browser de pagamento envia."""
return {
"platform": "Android" if os.environ.get("TERMUX_VERSION") else "Linux",
"mobile": bool(os.environ.get("TERMUX_VERSION")),
"arch": os.uname().machine,
}
def fp_hash(parts: list[str]) -> str:
return hashlib.sha256("|".join(parts).encode()).hexdigest()[:16]
# -------------------- analise -------------------- #
def analyse(country, dev_tz, loc, drift):
issues = []
fixes = []
# 1. timezone vs IP
if country and country in COUNTRY_TZ:
if dev_tz not in COUNTRY_TZ[country]:
issues.append(f"Timezone {dev_tz} nao bate com pais do IP ({country}).")
fixes.append(
f"No Android: Ajustes > Sistema > Data e Hora > Fuso "
f"-> {COUNTRY_TZ[country][0]}. Reabra o Chrome depois."
)
# 2. locale vs IP
if country and country in COUNTRY_LANG:
lang_pref, full = COUNTRY_LANG[country]
if not (loc or "").lower().startswith(lang_pref):
issues.append(f"Locale '{loc}' divergente do esperado ({full}).")
fixes.append(
f"Android: Ajustes > Sistema > Idiomas -> coloque "
f"{full.replace('_','-')} no TOPO. No Chrome: chrome://settings/languages."
)
# 3. clock drift
if drift is None:
issues.append("Nao foi possivel medir drift NTP (sem rede?).")
elif abs(drift) > 2:
issues.append(f"Clock drift {drift:+.2f}s (limite saudavel: +-2s).")
fixes.append(
"Ative 'Data e hora automatica' (NTP do sistema). Em Termux: "
"pkg install ntp && sudo ntpdate -u pool.ntp.org (root) "
"ou apenas ative o toggle nas Configuracoes do Android."
)
elif abs(drift) > 0.5:
issues.append(f"Clock drift {drift:+.2f}s (aceitavel, mas monitorar).")
# 4. fingerprint hash (so para voce notar mudancas entre execucoes)
fp = fp_hash([dev_tz, str(loc), str(os.uname().machine),
str(os.environ.get("TERMUX_VERSION",""))])
return issues, fixes, fp
# -------------------- main -------------------- #
def main():
country = (sys.argv[1] if len(sys.argv) > 1 else "").upper() or get_ip_country() or "BR"
dev_tz = get_device_tz()
loc = get_locale()
console.rule("[bold cyan]Medindo consistencia")
with console.status("consultando NTP..."):
drift = get_clock_drift()
t = Table(title="Sinais coletados", show_lines=True)
t.add_column("Campo"); t.add_column("Valor")
t.add_row("country (IP)", str(country))
t.add_row("device timezone", dev_tz)
t.add_row("locale", str(loc))
t.add_row("clock drift (s)", f"{drift:+.3f}" if drift is not None else "n/a")
t.add_row("ua hints (esperado)", json.dumps(expected_ua_hint()))
console.print(t)
issues, fixes, fp = analyse(country, dev_tz, loc, drift)
console.rule("[bold yellow]Inconsistencias")
if issues:
for i in issues: console.print(f" ! {i}")
else:
console.print(" [green]Nenhuma. Perfil coerente para 3DS frictionless.[/]")
console.rule("[bold green]Reparos sugeridos")
if fixes:
for f in fixes: console.print(f" -> {f}")
else:
console.print(" Nada a reparar agora.")
console.print(f"\n[dim]fingerprint estavel: {fp}[/dim]")
out = {"ts": datetime.now(timezone.utc).isoformat(),
"country": country, "timezone": dev_tz, "locale": loc,
"clock_drift_s": drift, "fingerprint": fp,
"issues": issues, "fixes": fixes}
p = os.path.expanduser("~/payer_consistency_report.json")
json.dump(out, open(p,"w"), indent=2, ensure_ascii=False)
console.print(f"[dim]Relatorio salvo em {p}[/dim]")
if __name__ == "__main__":
main()Executar
python ~/payer_consistency.py BRChecklist interativa: reparo + revalidação pré-checkout
Use o roteiro abaixo para guiar o reparo passo a passo. Cada item fica salvo neste dispositivo — você pode parar, executar no Termux/Android, voltar e marcar. Só envie a transação quando as duas listas estiverem 100% concluídas.
Checklist interativo
Fase 1 — Reparar o device
Execute na ordem. Cada passo elimina um sinal contraditório que o antifraude pondera pesado.
$ chrome://settings/languages
Checklist interativo
Fase 2 — Revalidar antes de clicar em “Pagar”
Rode novamente as medições do script. Se algum item falhar, volte para a Fase 1 e refaça só o passo correspondente.
$ ntpdate -q pool.ntp.org$ python ~/payer_consistency.py BR$ echo 'Abra DevTools > Network > Headers no próprio checkout para conferir.'$ cat ~/payer_consistency_report.json
Resumo copiável
Resumo final antes da transação
Gere um relatório copiável com o estado real das duas checklists deste dispositivo. Cole no seu diário, num ticket interno ou guarde como evidência.
⚠ Ainda há itens pendentes — veja as seções acima
Client Hints e Privacy Sandbox
A partir de 2025, Chrome reduziu o User-Agent legado e migrou para Sec-CH-UA-*. Devices com modificações que zeram client hints (alguns browsers de privacidade extrema) ficam ‘mudos’ para o antifraude e isso conta como sinal NEGATIVO. Para pagamento, prefira Chrome/Edge sem extensões de privacidade agressivas.
Fingerprint estável vs. fingerprint randomizado
- Antifraude PREFERE fingerprint estável repetido — significa “mesmo humano voltando”.
- Fingerprint randomizado a cada visita (Brave em modo strict, anti-detect browsers) eleva score de risco.
- Mantenha o mesmo browser para compras recorrentes no mesmo merchant; troque só se trocar de device de verdade.
- O hash impresso pelo script muda quando você altera fuso/locale — use como termômetro.
Continua valendo: escopo defensivo
Tudo aqui é para você reparar SEU próprio device em compras LEGÍTIMAS. Forjar consistência em device alheio, usar antidetect para abrir contas, ou rotacionar fingerprints para burlar bloqueio é fraude (Lei 14.155/21).
Ferramentas relacionadas
- ▸Termux — shell onde os scripts rodam
- ▸Nmap — checar se sua rede não tem proxy transparente intermediário
Próximo (nível 4 — planejado)
Leitura prática de logs de decline: códigos R/05/51/61/65, mapeamento para causa-raiz, e roteiro de conversa com o 0800 do emissor para liberar BIN/MCC sem migrar de banco.
Template OffSec · aplicar antes de rodar
Objetivo → Preparação → Execução → Evidência → Ética
Objetivo
Compreender protocolos (3DS, tokenização, PIX) e falhas comuns no checkout para reforçar antifraude — sem tocar em cartão real de terceiros.
Pré-requisitos
- Ambiente sandbox do PSP com credenciais de teste
- Coleção Postman/Insomnia com fluxos parametrizados
- Cartões de teste oficiais (Visa 4111..., Mastercard 5555..., etc.)
- Log estruturado (JSON) para cada request/response
Passos
- Mapear o fluxo (auth → 3DS challenge → capture → refund)
- Testar cada estado de erro documentado pelo PSP
- Injetar variações no device fingerprint e observar o desafio 3DS
- Medir latência e códigos de resposta para cada persona-teste
- Consolidar findings em playbook com decision tree para o time de risco
Evidências a coletar
- Coleção Postman versionada
- Log request/response com sanitização de PAN (só BIN + últimos 4)
- Screenshots do painel do PSP mostrando a transação
Regra prática: se você não guardou evidência reprodutível, o achado não existe.
Guia prático (lab autorizado)
alvo · Sandbox oficial do PSP (Stripe test, Adyen test, PagSeguro sandbox)
curl https://api.stripe.com/v1/payment_intents -u sk_test_...: -d amount=1000 -d currency=brl -d 'payment_method_types[]=card'verificar · O painel do PSP em modo teste é a fonte da verdade — compare com o response do seu backend. Divergência = bug no seu lado.
Checklist interativo
Checklist ético (marcar antes do primeiro comando)
Autorização, escopo, lab e proteção de dados. Sem 100%, não roda fora do lab.
Prática segura · Pagamentos
→ guia do labMonte um ambiente isolado antes de qualquer teste — use Docker (DVWA/juice-shop), uma VM host-only ou um alvo autorizado (HTB, TryHackMe, PortSwigger Academy).
Nunca aponte para alvo em produção. Se estiver no celular, use proot-distro + Debian para não sujar o Termux principal.
