Quando você usa o recurso de URL de retorno de chamada do CaptchaAI (pingback), seu servidor expõe um terminal HTTP que recebe soluções CAPTCHA. Sem validação, quem descobrir aquela URL pode enviar soluções falsas. Este tutorial aborda como proteger endpoints de retorno de chamada.
O fluxo de retorno de chamada
1. You submit task:
POST https://ocr.captchaai.com/in.php
?key=YOUR_API_KEY
&method=userrecaptcha
&googlekey=SITE_KEY
&pageurl=https://example.com
&pingback=https://your-server.com/captcha/callback
2. CaptchaAI solves the CAPTCHA
3. CaptchaAI sends result to your endpoint:
GET https://your-server.com/captcha/callback?id=TASK_ID&code=SOLUTION_TOKEN
O problema: a etapa 3 é uma solicitação não autenticada. Você precisa verificar se realmente veio de CaptchaAI.
Estratégia de validação 1: verificação de ID de tarefa
A abordagem mais simples: aceite apenas resultados de retorno de chamada para IDs de tarefas que você realmente enviou.
Python (frasco)
import os
import threading
import requests
from flask import Flask, request, jsonify
app = Flask(__name__)
# Thread-safe set of pending task IDs
pending_tasks = set()
pending_lock = threading.Lock()
results = {}
API_KEY = os.environ["CAPTCHAAI_API_KEY"]
def submit_captcha(sitekey, pageurl):
"""Submit CAPTCHA and register the task ID."""
resp = requests.post("https://ocr.captchaai.com/in.php", data={
"key": API_KEY,
"method": "userrecaptcha",
"googlekey": sitekey,
"pageurl": pageurl,
"pingback": "https://your-server.com/captcha/callback",
"json": 1
})
data = resp.json()
if data.get("status") == 1:
task_id = data["request"]
with pending_lock:
pending_tasks.add(task_id)
return task_id
return None
@app.route("/captcha/callback")
def captcha_callback():
task_id = request.args.get("id")
solution = request.args.get("code")
# Validate: only accept known task IDs
with pending_lock:
if task_id not in pending_tasks:
return jsonify({"error": "unknown task"}), 403
pending_tasks.discard(task_id)
results[task_id] = solution
return "OK", 200
JavaScript (Expresso)
const express = require("express");
const axios = require("axios");
const app = express();
const API_KEY = process.env.CAPTCHAAI_API_KEY;
const pendingTasks = new Set();
const results = new Map();
async function submitCaptcha(sitekey, pageurl) {
const resp = await axios.post("https://ocr.captchaai.com/in.php", null, {
params: {
key: API_KEY,
method: "userrecaptcha",
googlekey: sitekey,
pageurl: pageurl,
pingback: "https://your-server.com/captcha/callback",
json: 1,
},
});
if (resp.data.status === 1) {
const taskId = resp.data.request;
pendingTasks.add(taskId);
return taskId;
}
return null;
}
app.get("/captcha/callback", (req, res) => {
const taskId = req.query.id;
const solution = req.query.code;
// Validate: only accept known task IDs
if (!pendingTasks.has(taskId)) {
return res.status(403).json({ error: "unknown task" });
}
pendingTasks.delete(taskId);
results.set(taskId, solution);
res.sendStatus(200);
});
app.listen(3000);
Estratégia de validação 2: token de assinatura HMAC
Adicione um token secreto ao seu URL de retorno de chamada que os invasores não consigam adivinhar.
Pitão
import hashlib
import hmac
import os
CALLBACK_SECRET = os.environ["CALLBACK_SECRET"] # Random 32+ character string
def generate_callback_url(task_id):
"""Generate callback URL with HMAC signature."""
signature = hmac.new(
CALLBACK_SECRET.encode(),
task_id.encode(),
hashlib.sha256
).hexdigest()
return f"https://your-server.com/captcha/callback?token={signature}"
@app.route("/captcha/callback")
def captcha_callback():
task_id = request.args.get("id")
token = request.args.get("token")
solution = request.args.get("code")
# Verify HMAC signature
expected = hmac.new(
CALLBACK_SECRET.encode(),
task_id.encode(),
hashlib.sha256
).hexdigest()
if not hmac.compare_digest(token, expected):
return jsonify({"error": "invalid signature"}), 403
results[task_id] = solution
return "OK", 200
JavaScript
const crypto = require("crypto");
const CALLBACK_SECRET = process.env.CALLBACK_SECRET;
function generateCallbackUrl(taskId) {
const signature = crypto
.createHmac("sha256", CALLBACK_SECRET)
.update(taskId)
.digest("hex");
return `https://your-server.com/captcha/callback?token=${signature}`;
}
app.get("/captcha/callback", (req, res) => {
const taskId = req.query.id;
const token = req.query.token;
const solution = req.query.code;
// Verify HMAC signature
const expected = crypto
.createHmac("sha256", CALLBACK_SECRET)
.update(taskId)
.digest("hex");
if (!crypto.timingSafeEqual(Buffer.from(token), Buffer.from(expected))) {
return res.status(403).json({ error: "invalid signature" });
}
results.set(taskId, solution);
res.sendStatus(200);
});
Use a URL gerada ao enviar: pingback=https://your-server.com/captcha/callback?token=HMAC_SIGNATURE.
Estratégia de validação 3: lista de permissões de IP
Restrinja seu endpoint de retorno de chamada aos IPs do servidor CaptchaAI.
Python (frasco)
# CaptchaAI callback source IPs (verify current IPs with CaptchaAI support)
ALLOWED_IPS = {"138.201.XX.XX", "148.251.XX.XX"} # Replace with actual IPs
@app.before_request
def check_ip():
if request.path.startswith("/captcha/callback"):
client_ip = request.remote_addr
if client_ip not in ALLOWED_IPS:
return jsonify({"error": "forbidden"}), 403
JavaScript (Expresso)
const ALLOWED_IPS = new Set(["138.201.XX.XX", "148.251.XX.XX"]);
app.use("/captcha/callback", (req, res, next) => {
const clientIp = req.ip || req.connection.remoteAddress;
if (!ALLOWED_IPS.has(clientIp)) {
return res.status(403).json({ error: "forbidden" });
}
next();
});
Observação: Entre em contato com o suporte CaptchaAI para obter a lista atual de IPs de origem de retorno de chamada. Se você estiver atrás de um proxy reverso, certifique-se de que os cabeçalhos
X-Forwarded-Forestejam configurados corretamente.
Prevenção de ataques de repetição
Até mesmo retornos de chamada válidos podem ser reproduzidos. Adicione uma verificação de carimbo de data/hora e aplicação de uso único:
Pitão
import time
CALLBACK_TTL = 300 # Reject callbacks older than 5 minutes
used_callbacks = set()
@app.route("/captcha/callback")
def captcha_callback():
task_id = request.args.get("id")
timestamp = request.args.get("ts")
solution = request.args.get("code")
# Check timestamp freshness
if timestamp:
age = time.time() - float(timestamp)
if age > CALLBACK_TTL or age < 0:
return jsonify({"error": "expired"}), 403
# One-time use
if task_id in used_callbacks:
return jsonify({"error": "already processed"}), 409
used_callbacks.add(task_id)
results[task_id] = solution
return "OK", 200
Lista de verificação de segurança combinada
| Camada | Proteção contra | Implementação |
|---|---|---|
| Verificação de ID de tarefa | Injeção de tarefa Random/unknown | Armazene IDs pendentes, rejeite desconhecidos |
| Assinatura HMAC | Adivinhação de URL, retornos de chamada forjados | Assinar URL de retorno de chamada com segredo |
| Lista de permissões de IP | Solicitações de servidores não autorizados | Lista de permissões de IPs CaptchaAI |
| Prevenção de repetição | Retornos de chamada válidos reenviados | Validação de uso único + carimbo de data/hora |
| HTTPS | Espionagem, homem do meio | TLS no terminal de retorno de chamada |
Solução de problemas
| Problema | Causa | Correção |
|---|---|---|
| Todos os retornos de chamada foram rejeitados | A lista de permissões de IP não inclui IPs CaptchaAI | Verifique IPs atuais com suporte; verifique os cabeçalhos do proxy reverso |
| A verificação HMAC falha | Incompatibilidade de ID de tarefa entre envio e retorno de chamada | Certifique-se de usar o ID de tarefa exato retornado por in.php |
| Retornos de chamada duplicados processados | Condição de corrida em retornos de chamada simultâneos | Use operações de conjunto atômico ou restrições exclusivas do banco de dados |
| Tempo limite dos retornos de chamada | O endpoint demora muito para responder | Processar assíncrono – aceitar imediatamente, processar em segundo plano |
Perguntas frequentes
Devo usar todas as quatro estratégias de validação juntas?
Use a verificação de ID de tarefa (Estratégia 1) como mínimo. Adicione assinaturas HMAC (Estratégia 2) para endpoints voltados ao público. A lista de permissões de IP (Estratégia 3) é ideal se CaptchaAI publicar IPs de retorno de chamada estáveis. A prevenção de repetição é essencial para fluxos de trabalho financeiros ou confidenciais.
O que acontece se meu endpoint de retorno de chamada estiver inativo quando CaptchaAI enviar o resultado?
A solução ainda está disponível através do endpoint de pesquisa (res.php). Implemente um substituto que pesquise quaisquer tarefas que não recebam retornos de chamada dentro de um período de tempo limite.
Posso usar TLS mútuo (mTLS) para autenticação de retorno de chamada?
Em teoria, sim, mas o sistema de retorno de chamada do CaptchaAI usa solicitações HTTPS GET padrão. As assinaturas HMAC fornecem autenticação equivalente sem exigir gerenciamento de certificado.
Artigos relacionados
- Padrões de tratamento de erros de retorno de chamada Captchaai
- Guia de webhook de URL de retorno de chamada Captchaai
Próximas etapas
Proteja seus endpoints de retorno de chamada CaptchaAI -obtenha sua chave APIe implementar validação de assinatura.
Guias relacionados: