← Cloudflare Turnstile / turnstile / tutorials
Detekce podvodů pomocí Ephemeral IDs
Ephemeral IDs vám umožní odhalit vzorce podvodného chování, které tradiční detekci podle IP adres uniknou. V tomto návodu si ukážeme, jak Ephemeral ID zaznamenávat, jak rozpoznat podezřelé vzorce a jak útočníky zablokovat.
Útočníci často zakládají stovky falešných účtů, aby zneužili promo akce, střídají adresy z proxy poolů, aby obešli rate limiting podle IP adresy, a používají skutečné prohlížeče, aby unikli základní detekci botů.
Tradiční detekce založená na IP adresách zde selhává, protože každý požadavek vypadá, že přišel z jiné adresy. Ephemeral ID tento problém řeší tím, že identifikuje samotné klientské zařízení, i když se IP adresy mění.
Než začnete
- Ephemeral IDs vyžadují Enterprise Bot Management hodnotou doplněk Enterprise Turnstile, případně samostatný Enterprise Turnstile. O zapnutí této funkce požádejte svůj account team.
- Potřebujete základní znalost integrace Turnstile. Podívejte se na Začínáme s Turnstile s dalšími informacemi.
Nastavení logování
Vytvořte tabulku pro ukládání událostí s Ephemeral ID.
CREATE TABLE turnstile_events (
id BIGSERIAL PRIMARY KEY,
ephemeral_id VARCHAR(64) NOT NULL,
event_type VARCHAR(50) NOT NULL, -- 'signup', 'login', 'checkout'
ip_address VARCHAR(45),
user_id VARCHAR(128), -- NULL for signups, populated after
created_at TIMESTAMPTZ DEFAULT NOW()
);
CREATE TABLE blocked_ephemeral_ids (
ephemeral_id VARCHAR(64) PRIMARY KEY,
reason VARCHAR(255),
created_at TIMESTAMPTZ DEFAULT NOW()
);Získání a zaznamenání Ephemeral IDs
Při volání Siteverify se Ephemeral ID vrací v metadata . Zaznamenávejte jej u každé chráněné akce.
async function verifyAndLogTurnstile(
token: string,
ip: string,
secretKey: string,
eventType: string,
db: Database,
): Promise<{ success: boolean; ephemeralId?: string; isBlocked: boolean }> {
// Call Siteverify API
const response = await fetch(
"https://challenges.cloudflare.com/turnstile/v0/siteverify",
{
method: "POST",
headers: { "Content-Type": "application/x-www-form-urlencoded" },
body: new URLSearchParams({
secret: secretKey,
response: token,
remoteip: ip,
}),
},
);
const result = await response.json();
if (!result.success) {
return { success: false, isBlocked: false };
}
const ephemeralId = result.metadata?.ephemeral_id;
if (ephemeralId) {
// Log the event
await db.query(
`INSERT INTO turnstile_events (ephemeral_id, event_type, ip_address)
VALUES ($1, $2, $3)`,
[ephemeralId, eventType, ip],
);
// Check if already blocked
const blocked = await db.query(
`SELECT 1 FROM blocked_ephemeral_ids WHERE ephemeral_id = $1`,
[ephemeralId],
);
if (blocked.rows.length > 0) {
return { success: true, ephemeralId, isBlocked: true };
}
}
return { success: true, ephemeralId, isBlocked: false };
}Použití Ephemeral ID při registraci
export async function handleSignup(request: Request, env: Env) {
const formData = await request.formData();
const email = formData.get("email") as string;
const turnstileToken = formData.get("cf-turnstile-response") as string;
const ip = request.headers.get("CF-Connecting-IP") || "";
// Verify Turnstile and log the Ephemeral ID
const verification = await verifyAndLogTurnstile(
turnstileToken,
ip,
env.TURNSTILE_SECRET_KEY,
"signup",
env.DB,
);
if (!verification.success) {
return new Response("Verification failed", { status: 400 });
}
// Block if this device is flagged
if (verification.isBlocked) {
// Return a generic message - don't reveal detection
return new Response("Please verify your email to continue", {
status: 202,
});
}
// Proceed with normal signup
const userId = await createUser(email, formData.get("password"));
// Update the log with the new user ID
if (verification.ephemeralId) {
await env.DB.query(
`UPDATE turnstile_events
SET user_id = $1
WHERE ephemeral_id = $2 AND event_type = 'signup' AND user_id IS NULL
ORDER BY created_at DESC LIMIT 1`,
[userId, verification.ephemeralId],
);
}
return new Response("Account created", { status: 201 });
}Odhalování vzorců podvodného chování
Následující dotaz spouštějte pravidelně, například každých pět minut, abyste našli podezřelá Ephemeral ID:
-- Find devices creating multiple accounts in the last hour
SELECT
ephemeral_id,
COUNT(*) as signup_count,
COUNT(DISTINCT ip_address) as unique_ips
FROM turnstile_events
WHERE
event_type = 'signup'
AND created_at > NOW() - INTERVAL '1 hour'
GROUP BY ephemeral_id
HAVING COUNT(*) > 3; -- More than 3 signups = suspiciousKdyž najdete podezřelá ID, zablokujte je:
INSERT INTO blocked_ephemeral_ids (ephemeral_id, reason)
SELECT
ephemeral_id,
'Multiple signups: ' || COUNT(*) || ' in 1 hour'
FROM turnstile_events
WHERE
event_type = 'signup'
AND created_at > NOW() - INTERVAL '1 hour'
GROUP BY ephemeral_id
HAVING COUNT(*) > 3
ON CONFLICT (ephemeral_id) DO NOTHING;Prošetřete a zasáhněte
Když kvůli zneužití zablokujete účty, najděte další účty ze stejného zařízení:
-- Find all accounts created from the same device as a banned user
SELECT DISTINCT te2.user_id, te2.created_at
FROM turnstile_events te1
JOIN turnstile_events te2 ON te1.ephemeral_id = te2.ephemeral_id
WHERE te1.user_id = 'BANNED_USER_ID'
AND te2.user_id IS NOT NULL
AND te2.user_id != 'BANNED_USER_ID';Hromadné označení účtů ke kontrole:
-- Flag all accounts from a suspicious device
UPDATE users
SET status = 'under_review'
WHERE id IN (
SELECT DISTINCT user_id
FROM turnstile_events
WHERE ephemeral_id = 'x:SUSPICIOUS_ID_HERE'
AND user_id IS NOT NULL
);Doporučení
- Zaznamenávejte okamžitě: Ephemeral ID si uložte hned při volání Siteverify.
- Tiché odmítnutí: Při blokování podvodů vracejte obecné chyby. Nikdy neprozraďte, že jste zařízení rozpoznali.
- Vyladit prahové hodnoty: Začněte s dotazem opatrně (například tři registrace za hodinu) a hodnotu upravte podle svého provozu.
- Kombinujte signály: Používejte Ephemeral ID společně s reputací IP adres a analýzou chování.