القاعدة الأولى في تأمين رد النداء بسيطة: لا تثق بأي طلب وارد على نقطة نهايتك حتى تُثبت أنه صادر عن CaptchaAI فعلاً. فبمجرّد أن تمرّر معلمة pingback عند إرسال المهمة، يتحوّل خادمك إلى نقطة نهاية عامة على الإنترنت تستقبل رموز الحل من أي جهة تعرف عنوانها — لا من CaptchaAI وحدها. تُغلق هذه الفجوة أربع طبقات متتالية فوق HTTPS: التحقق من معرّف المهمة، وتوقيع HMAC، وقصر الاستقبال على عناوين IP الخاصة بـ CaptchaAI، ومنع إعادة التشغيل. نبني كل طبقة أدناه بأمثلة Python وJavaScript جاهزة للنسخ.
الطبقات الأربع في لمحة
قبل الدخول في الشيفرة، إليك خريطة الطبقات الأربع فوق أساس HTTPS: ما تصدّه كل طبقة وكيف تُنفَّذ. نبنيها واحدة تلو الأخرى، وكل طبقة تكمّل ما قبلها لا تحلّ محلّه:
| الطبقة | تحمي من | كيفية التنفيذ |
|---|---|---|
| التحقق من معرّف المهمة | حقن مهام عشوائية أو مجهولة | خزّن المعرّفات المعلّقة وارفض ما لا تعرفه |
| توقيع HMAC | تخمين العنوان وتزوير طلبات رد النداء | وقّع عنوان رد النداء بسرّ لا يُخمَّن |
| قائمة عناوين IP المسموح بها | طلبات من خوادم غير مصرّح بها | اقصر الوصول على عناوين CaptchaAI |
| منع إعادة التشغيل | إعادة إرسال طلبات صالحة مسبقاً | استخدام لمرة واحدة مع تحقّق من الطابع الزمني |
| HTTPS | التنصّت وهجمات الوسيط | فعّل TLS على نقطة نهاية رد النداء |
مسار رد النداء: أين تكمن الثغرة بالضبط
يمرّ رد النداء بثلاث مراحل واضحة قبل أن يصل الرمز إلى تطبيقك:
- ترسل المهمة ومعها عنوان نقطة نهايتك في معلمة
pingback. - تحلّ CaptchaAI الكابتشا في الخلفية.
- تُعيد النتيجة إلى نقطة نهايتك عبر طلب
GETيحمل المعرّف والرمز.
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
المرحلة الثالثة هي موضع الخطر: طلب GET وارد بلا مصادقة افتراضية، يتشابه فيه الطلب الحقيقي والمزوَّر شكلاً تماماً. من هنا تبدأ مسؤوليتك — أن تتحقّق من مصدر الطلب قبل أن يمرّر رمزه إلى منطق تطبيقك.
لماذا لا يكفي HTTPS ولا كتم العنوان
قد يبدو أن إخفاء عنوان نقطة النهاية أو الاكتفاء بـ HTTPS حماية كافية، لكن كليهما يترك الباب موارباً:
- خلافاً للاستطلاع الدوري الذي تبدأه أنت وتتحكّم في توقيته، يجعل رد النداء خادمك مستقبِلاً سلبياً لطلبات لا تتوقّع مصدرها مسبقاً.
- يشفّر HTTPS المحتوى أثناء النقل ويمنع التنصّت وهجمات الوسيط، لكنه لا يتحقّق من هوية المُرسِل إطلاقاً.
- يكفي أن يتسرّب العنوان مرة واحدة — في سجلّات الخادم أو في كود الواجهة الأمامية — حتى يصبح معروفاً لأي طرف.
تخيّل منصة حجوزات في الخليج تعالج آلاف عمليات التحقق يومياً ضمن تدفّق إتمام الشراء. لو ظهر عنوان نقطة نهاية رد النداء في سجلّ عام أو في طلب شبكة مكشوف، لأمكن لطرف خارجي أن يرسل رموز حل مزيفة تُعتمد كأنها ناتجة عن تحقّق لم يحدث فعلاً. الطبقات الأربع التالية تُغلق هذه الثغرة واحدة تلو الأخرى، وتُبنى فوق بعضها لا بديلاً عن بعضها.
الطبقة الأولى: اقبل معرّفات المهام التي أرسلتها فقط
أبسط تحقّق أن ترفض أي رد نداء يحمل معرّفاً لم ترسله أنت. احتفظ بمجموعة من معرّفات المهام المعلّقة، وقارن كل طلب وارد بها قبل الوثوق به. انتبه إلى ثلاث نقاط في التنفيذ:
- خزّن المعرّف لحظة الإرسال، لا عند استلام رد النداء.
- احرس المجموعة بقفل لأن الطلبات قد تصل بالتوازي.
- احذف المعرّف بعد أول استخدام حتى لا يُقبل مرتين.
Python (Flask)
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);
ملاحظة: خزِّن معرّف المهمة لحظة استدعاء
in.phpلا عند وصول رد النداء؛ فلو انتظرت الرد لتسجّل المعرّف، لسبق ردُّ نداءٍ مزيّف الردَّ الحقيقي أحياناً وتجاوز هذا الفحص.
الطبقة الثانية: وقّع كل رد نداء بـ HMAC
يكشف تحقّق المعرّف الطلبات المجهولة، لكن مهاجماً يرصد معرّفاً حقيقياً قد يعيد استخدامه لاحقاً. لسدّ هذه الثغرة، اربط كل عنوان رد نداء بتوقيع HMAC عبر ثلاث خطوات:
- ولّد توقيعاً من معرّف المهمة بسرّ لا يعرفه إلا خادمك.
- أرفق التوقيع بعنوان رد النداء لحظة الإرسال.
- تحقّق منه عند الاستقبال عبر مقارنة ثابتة الزمن تُحصّنك من هجمات توقيت المقارنة.
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);
});
مرّر العنوان الموقَّع عند الإرسال: pingback=https://your-server.com/captcha/callback?token=HMAC_SIGNATURE. أي طلب لا يحمل توقيعاً مطابقاً يُرفض بالرمز 403 قبل أن يلمس منطق تطبيقك.
ملاحظة: لا تسجّل العنوان الموقَّع في سجلّات الوصول ولا تُرفقه في روابط خارجية؛ فالتوقيع سرٌّ فعّال، ومن يقرأه يستطيع انتحال رد نداء صحيح حتى دون معرفة قيمة
CALLBACK_SECRETنفسها.
الطبقة الثالثة: اقصر الاستقبال على عناوين IP الخاصة بـ CaptchaAI
أضف خط دفاع على مستوى الشبكة بقبول الطلبات الصادرة عن خوادم CaptchaAI فقط. تتميّز هذه الطبقة بأنها:
- تعمل قبل وصول الطلب إلى منطق التطبيق، فتخفّف الحمل عن خادمك.
- توقف الطلبات القادمة من خوادم غير مصرّح بها فوراً وعلى مستوى الشبكة.
- تبقى مكمّلة لتوقيع HMAC حتى لا ينكسر التحقق عند تغيّر عناوين IP.
Python (Flask)
# 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();
});
ملاحظة: تواصل مع دعم CaptchaAI للحصول على القائمة الحالية لعناوين IP الخاصة برد النداء. وإذا كنت خلف خادم وسيط عكسي، فتأكّد من ضبط رأس
X-Forwarded-Forبشكل صحيح حتى تقرأ عنوان العميل الفعلي لا عنوان الوكيل.
الطبقة الرابعة: أوقف إعادة تشغيل الطلبات
حتى طلب رد نداء صحيح تماماً يمكن التقاطه وإعادة إرساله لاحقاً. اجمع فحصين معاً حتى تُبطل هذه المحاولة:
- حداثة الطابع الزمني: ارفض أي طلب تجاوز عمره خمس دقائق.
- الاستخدام لمرة واحدة: ارفض أي معرّف سبقت معالجته.
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
أسئلة شائعة
لماذا يحتاج رد النداء إلى تحقّق أكثر من الاستطلاع الدوري؟
لأنك في الاستطلاع الدوري تتحكّم في كل شيء: أنت من يبدأ الطلب ويعرف وجهته، فلا مجال لأن ينتحل طرف خارجي دور الخادم. أما رد النداء فيعكس الاتجاه، إذ يصلك الطلب من الخارج دون أن تبدأه، فيصبح أي طرف يعرف العنوان قادراً على التظاهر بأنه CaptchaAI. هذا الفارق البنيوي هو ما يفرض التحقق من المصدر: معرّف المهمة وتوقيع HMAC يُثبتان أن الطلب صادر عمّن تتوقّعه فعلاً لا عن منتحِل.
أين أحفظ قيمة CALLBACK_SECRET بأمان؟
احفظها في متغيّرات البيئة أو في خدمة إدارة الأسرار التي توفّرها منصة الاستضافة، ولا تضعها في الكود المصدري أو في مستودع Git. استخدم سلسلة عشوائية لا تقل عن 32 حرفاً، وبدّلها دورياً. فمن يحصل على هذا السرّ يستطيع توليد توقيعات صحيحة تمرّ عبر الطبقة الثانية، لذا عامله بالحساسية نفسها التي تعامل بها مفتاح الـ API.
كيف أختبر الطبقات الأربع محلياً قبل النشر؟
شغّل الخادم محلياً وأرسل طلب رد نداء تجريبياً عبر curl بتوقيع HMAC مولَّد يدوياً لمعرّف مهمة معروف. ينبغي أن يعيد الخادم 200 للتوقيع الصحيح، و403 لتوقيع خاطئ أو معرّف مجهول، و409 لرمز استُخدم من قبل. تشغيل هذه الحالات الثلاث قبل الإنتاج يكشف أخطاء عدم تطابق المعرّف مبكراً.
هل يُبطل تغيّر عناوين IP حماية رد النداء؟
قد تتغيّر عناوين خوادم CaptchaAI مع الوقت، لذا لا تجعل قائمة IP طبقة حمايتك الوحيدة وإلا رفضت طلبات مشروعة عند أول تحديث. راجع القائمة الحالية عبر دعم CaptchaAI، واجمع بين قصر العناوين وتوقيع HMAC حتى يظل التحقق قائماً عند تبدّل الخوادم. وفي البيئات التي تستخدم خادماً وسيطاً عكسياً، اقرأ عنوان العميل من رأس X-Forwarded-For الصحيح.
معالجة المشكلات الشائعة
| المشكلة | السبب المحتمل | الحل |
|---|---|---|
| رفض جميع طلبات رد النداء | قائمة IP لا تتضمّن عناوين CaptchaAI الحالية | تحقّق من العناوين مع الدعم وراجع رؤوس الوكيل العكسي |
| فشل التحقق من توقيع HMAC | اختلاف معرّف المهمة بين الإرسال ورد النداء | استخدم معرّف المهمة نفسه الذي أعاده in.php بالضبط |
| معالجة طلبات رد نداء مكرّرة | تعارض تزامني على الطلبات المتوازية | استخدم عمليات مجموعة ذرّية أو قيد تفرّد في قاعدة البيانات |
| انتهاء مهلة طلبات رد النداء | استغراق نقطة النهاية وقتاً طويلاً في الرد | استقبل الطلب فوراً وعالجه في الخلفية بشكل غير متزامن |
الخطوات التالية
- ابدأ سريعاً مع CaptchaAI: حُلّ أول كابتشا في 5 دقائق
- حلّ reCAPTCHA v2 عبر الـ API خطوة بخطوة
- حلّ Cloudflare Turnstile عبر الـ API
- حلّ GeeTest v3 عبر الـ API