Hướng Dẫn Thực Hành

CaptchaAI Webhook Security: Xác thực chữ ký gọi lại

Khi bạn sử dụng tính năng URL gọi lại của CaptchaAI (pingback), máy chủ của bạn sẽ hiển thị điểm cuối HTTP nhận giải pháp CAPTCHA. Nếu không xác thực, bất kỳ ai phát hiện ra URL đó đều có thể gửi giải pháp giả mạo. Hướng dẫn này trình bày cách bảo mật các điểm cuối gọi lại.

Luồng gọi lại


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

Vấn đề: bước 3 là một yêu cầu chưa được xác thực. Bạn cần xác minh nó thực sự đến từ CaptchaAI.

Chiến lược xác thực 1: Xác minh ID nhiệm vụ

Cách tiếp cận đơn giản nhất - chỉ chấp nhận kết quả gọi lại cho ID nhiệm vụ mà bạn thực sự đã gửi.

Python (Bình)

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 (Express)

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);

Chiến lược xác thực 2: Mã thông báo chữ ký HMAC

Thêm mã thông báo bí mật vào URL gọi lại của bạn mà kẻ tấn công không thể đoán được.

Python

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);
});

Sử dụng URL được tạo khi gửi: pingback=https://your-server.com/captcha/callback?token=HMAC_SIGNATURE.

Chiến lược xác thực 3: Danh sách cho phép IP

Hạn chế điểm cuối gọi lại của bạn ở IP máy chủ của CaptchaAI.

Python (Bình)

# 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 (Express)

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();
});

Lưu ý: Hãy liên hệ với bộ phận hỗ trợ của CaptchaAI để biết danh sách IP nguồn gọi lại hiện tại. Nếu bạn sử dụng proxy ngược, hãy đảm bảo tiêu đề X-Forwarded-For được định cấu hình chính xác.

Phòng chống tấn công phát lại

Ngay cả những cuộc gọi lại hợp lệ cũng có thể được phát lại. Thêm kiểm tra dấu thời gian và thực thi sử dụng một lần:

Python

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

Danh sách kiểm tra bảo mật kết hợp

Lớp Bảo vệ chống lại Thực hiện
Xác minh ID nhiệm vụ Chèn nhiệm vụ ngẫu nhiên/unknown Lưu trữ các ID đang chờ xử lý, từ chối các ID không xác định
chữ ký HMAC Đoán URL, gọi lại giả mạo Ký URL gọi lại bí mật
Danh sách cho phép IP Yêu cầu từ máy chủ trái phép Danh sách trắng IP CaptchaAI
Ngăn chặn phát lại Đã gửi lại các lệnh gọi lại hợp lệ Sử dụng một lần + xác thực dấu thời gian
HTTPS Nghe lén, xen vào giữa TLS trên điểm cuối gọi lại

Khắc phục sự cố

Vấn đề Nguyên nhân Cách xử lý
Tất cả các cuộc gọi lại bị từ chối Danh sách IP cho phép không bao gồm IP CaptchaAI Xác minh IP hiện tại với sự hỗ trợ; kiểm tra tiêu đề proxy ngược
Xác minh HMAC không thành công ID nhiệm vụ không khớp giữa gửi và gọi lại Đảm bảo bạn sử dụng ID tác vụ chính xác được in.php trả về
Đã xử lý các lệnh gọi lại trùng lặp Điều kiện cuộc đua trên các lệnh gọi lại đồng thời Sử dụng các phép toán tập hợp nguyên tử hoặc các ràng buộc duy nhất của cơ sở dữ liệu
Hết thời gian gọi lại Điểm cuối mất quá nhiều thời gian để phản hồi Xử lý không đồng bộ - chấp nhận ngay lập tức, xử lý ở chế độ nền

Câu hỏi thường gặp

Tôi có nên sử dụng cả bốn chiến lược xác thực cùng nhau không?

Sử dụng xác minh ID nhiệm vụ (Chiến lược 1) ở mức tối thiểu. Thêm chữ ký HMAC (Chiến lược 2) cho các điểm cuối công khai. Danh sách IP cho phép (Chiến lược 3) là lý tưởng nếu CaptchaAI xuất bản IP gọi lại ổn định. Ngăn ngừa lặp lại là điều cần thiết cho quy trình công việc tài chính hoặc nhạy cảm.

Điều gì xảy ra nếu điểm cuối gọi lại của tôi không hoạt động khi CaptchaAI gửi kết quả?

Giải pháp vẫn có sẵn thông qua điểm cuối bỏ phiếu (res.php). Triển khai một phương án dự phòng thăm dò ý kiến ​​cho bất kỳ tác vụ nào không nhận được lệnh gọi lại trong một khoảng thời gian chờ.

Tôi có thể sử dụng TLS chung (mTLS) để xác thực gọi lại không?

Về lý thuyết là có – nhưng hệ thống gọi lại của CaptchaAI sử dụng các yêu cầu HTTPS GET tiêu chuẩn. Chữ ký HMAC cung cấp xác thực tương đương mà không yêu cầu quản lý chứng chỉ.

bài viết liên quan

Các bước tiếp theo

Bảo mật điểm cuối gọi lại CaptchaAI của bạn —lấy khóa API của bạnvà thực hiện xác nhận chữ ký.

Hướng dẫn liên quan:

Os comentários estão desativados para este artigo.