Tank · Code Alliance Developers
Termux Arsenalby Tank · Code Alliance

Leitura técnica · use o conhecimento de forma ética e autorizada

← estudos
Pagamentos22 min · avançado · atualizado 2026-06-29

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

1/4
nível 1 · introdução acessívelnível 2 · em brevenível 3 · em breve

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 pagar

Script 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 BR

Checklist 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.

0/7
  • $ 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.

0/7
  • $ 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.

0/14

⚠ Ainda há itens pendentes — veja as seções acima

Texto em Markdown · cola em qualquer editor / ticket

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

  • Termuxshell onde os scripts rodam
  • Nmapchecar 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

→ guia do lab

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

  1. Mapear o fluxo (auth → 3DS challenge → capture → refund)
  2. Testar cada estado de erro documentado pelo PSP
  3. Injetar variações no device fingerprint e observar o desafio 3DS
  4. Medir latência e códigos de resposta para cada persona-teste
  5. 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.

0/5

Prática segura · Pagamentos

→ guia do lab

Monte 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.