TOOLS

تست نفوذ با پایتون: خودکارسازی کارهای تکراری در تست وب

محمدرضا مقدم
محمدرضا مقدم
توسعه‌دهنده ابزاربازبینی: ۲۵ مرداد ۱۴۰۵
۲ اردیبهشت ۱۴۰۵ ۸ دقیقه مطالعه

تست نفوذ با پایتون معمولاً به‌شکل «نوشتن اکسپلویت» تصویر می‌شود، اما در کار روزمره‌ی یک تست‌کننده‌ی برنامه‌های وب نقش پایتون چیز دیگری است: خودکارسازی بخش‌های خسته‌کننده و تکراری. کاری که یک اسکریپت بیست‌خطی در ده ثانیه انجام می‌دهد — مثلاً بازپخش دویست درخواست با سه نشست مختلف و مقایسه‌ی پاسخ‌ها — دستی چند ساعت وقت می‌گیرد و در وسط راه خطای انسانی هم دارد. این مقاله همان کاربردهای واقعی را با نمونه‌های کوتاه و غیرتهاجمی نشان می‌دهد.

در یک نگاه

  • کاربرد اصلی پایتون در تست وب، چسباندن ابزارها و تکرار درخواست‌ها است، نه بازنویسی چیزی که Burp بهتر انجام می‌دهد.
  • پرارزش‌ترین اسکریپتی که می‌نویسید، ماتریس مجوزدهی است: هر درخواست، با هر نقش، و مقایسه‌ی پاسخ‌ها.
  • شیء Session در کتابخانه‌ی requests کوکی‌ها را نگه می‌دارد؛ بدون آن هر آزمون احراز هویت‌شده‌ای بی‌معنا می‌شود.
  • افزونه‌ی رسمی Burp با Java و Montoya API نوشته می‌شود؛ برای اتوماسیون پایتونی، mitmproxy طبیعی‌ترین انتخاب است.

پایتون کجای تست نفوذ وب می‌نشیند؟

سه دسته کار وجود دارد که پایتون در آن‌ها بی‌رقیب است:

  • تکرار با تغییر کنترل‌شده: یک درخواست را با ده مقدار پارامتر، سه نشست مختلف و دو نسخه‌ی API بفرست و پاسخ‌ها را جدولی مقایسه کن.
  • پردازش خروجی ابزارها: خروجی JSON ابزارهای شناسایی را بخوان، فیلتر کن و به مرحله‌ی بعد بده.
  • استخراج و تحلیل: بیرون کشیدن اندپوینت‌ها از فایل‌های جاوااسکریپت، مقایسه‌ی ساختار پاسخ‌ها، و شناسایی الگوی شناسه‌ها.

و یک دسته کار هست که پایتون در آن انتخاب بدی است: بازنویسی چیزهایی که ابزار پخته‌ای برای آن‌ها وجود دارد. یک پروکسی میانی مثل Burp Suite یا Caido سال‌ها روی جزئیات پروتکل کار کرده؛ فازر ffuf با هم‌روندی بالا نوشته شده و اسکریپت پایتونی شما به آن سرعت نمی‌رسد.

باور غلط رایج

«با پایتون اسکنر خودم را می‌نویسم و دیگر به Burp نیازی ندارم.» نوشتن اسکریپت با یافتن آسیب‌پذیری یکی نیست. آسیب‌پذیری‌های پرخطر — کنترل دسترسی، منطق کسب‌وکار و زنجیره‌های چندمرحله‌ای — از تحلیل انسانی می‌آیند و اسکریپت فقط تصمیم شما را سریع‌تر اجرا می‌کند. اسکریپت خوب، تصمیم مشخصی را در مقیاس تکرار می‌کند؛ جایگزین تصمیم نمی‌شود.

مبانی: requests و مدیریت نشست

هسته‌ی کار، کتابخانه‌ی requests و شیء Session آن است. اهمیت Session این است که کوکی‌ها را بین درخواست‌ها نگه می‌دارد؛ بدون آن، درخواست دوم شما ناشناس محسوب می‌شود و هر نتیجه‌ای که می‌گیرید بی‌ارزش است.

import requests
s = requests.Session()
s.post("https://app.test/login", data={"u": "tester1", "p": PASS})
r = s.get("https://app.test/api/me")
print(r.status_code, r.json().get("role"))

چند نکته‌ی حرفه‌ای که تفاوت ایجاد می‌کند:

  • اعتبارسنجی گواهی را خاموش نکنید. این کتابخانه به‌صورت پیش‌فرض گواهی TLS را بررسی می‌کند و خاموش‌کردن آن عادت بدی است. اگر لازم است ترافیک از پروکسی آزمون شما عبور کند، گواهی ریشه‌ی همان پروکسی را به مجموعه‌ی مورد اعتماد اضافه کنید، نه این‌که بررسی را حذف کنید.
  • allow_redirects=False را جدی بگیرید. در آزمون کنترل دسترسی، تفاوت میان پاسخ 302 به صفحه‌ی ورود و پاسخ 200 با محتوای واقعی، همان چیزی است که دنبالش هستید. اگر اجازه‌ی پیگیری تغییر مسیر بدهید، این تفاوت گم می‌شود.
  • هدرها را دقیق تنظیم کنید: Content-Type نادرست باعث می‌شود سرور مسیر پردازش دیگری را انتخاب کند و نتیجه‌ی آزمون شما به مسئله‌ی واقعی مربوط نباشد.
  • برای APIهای مبتنی بر توکن، مسیر معمول ارسال هدر Authorization است؛ نکات اعتبارسنجی توکن در JWT آمده است.

ماتریس مجوزدهی: پرارزش‌ترین اسکریپت شما

این تنها کاربردی است که اگر فقط یک اسکریپت در کل عمر حرفه‌ای‌تان بنویسید، باید همین باشد. منطقش ساده است: مجموعه‌ی درخواست‌های برنامه را جمع می‌کنید، و هر درخواست را با هر نقش می‌فرستید. اگر نقشی که نباید دسترسی داشته باشد پاسخ 200 با محتوای واقعی گرفت، یک یافته‌ی کنترل دسترسی شکسته دارید.

import requests
tokens = {"admin": T_ADMIN, "user": T_USER, "anon": None}
for role, tok in tokens.items():
    h = {"Authorization": "Bearer " + tok} if tok else {}
    r = requests.get(URL, headers=h, allow_redirects=False)
    print(role, r.status_code, len(r.content))

خروجی را در قالب جدولی مثل این تفسیر کنید:

اندپوینتناشناسکاربر عادیمدیرنتیجه
GET /api/orders/1042302200200سالم (اگر ۱۰۴۲ مال همان کاربر باشد)
GET /api/orders/1043302200200IDOR — رکورد کاربر دیگر
DELETE /api/orders/1043401204204مجوزدهی سطح تابع شکسته
GET /admin/users302403200سالم
PATCH /api/users/me با role401200200انتساب انبوه — ارتقای نقش

دو تله‌ی رایج: اول، طول پاسخ به‌تنهایی ملاک نیست — برنامه ممکن است 200 با بدنه‌ی خالی برگرداند. دوم، بررسی متدها را فراموش نکنید؛ بسیاری از برنامه‌ها کنترل را روی GET اعمال می‌کنند و روی PUT و DELETE جا می‌گذارند. جزئیات این الگوها در IDOR و تست امنیت API آمده است.

تحلیل پاسخ و استخراج اندپوینت

برنامه‌های امروزی بخش بزرگی از منطق مسیریابی خود را در فایل‌های جاوااسکریپت باندل‌شده حمل می‌کنند، و در همان فایل‌ها آدرس اندپوینت‌هایی پیدا می‌شود که در رابط کاربری هیچ‌وقت فراخوانی نمی‌شوند — قابلیت‌های نیمه‌تمام، مسیرهای مدیریتی و نسخه‌های قدیمی API. یک استخراج ساده:

import re, sys
src = sys.stdin.read()
paths = set(re.findall(r'"(/api/[a-zA-Z0-9_/-]{2,60})"', src))
for p in sorted(paths):
    print(p)

خروجی این اسکریپت را نمی‌شود کورکورانه باور کرد؛ باید هر مسیر را با httpx یا دستی بررسی کنید تا مشخص شود واقعاً وجود دارد. اما همین فهرست، معمولاً چند اندپوینتی را رو می‌کند که در مستندات نیستند — و «مدیریت نادرست فهرست APIها» یکی از دسته‌های رسمی OWASP API Security Top 10 است.

کاربرد دوم، تحلیل تفاضلی است: دو پاسخ را با هم مقایسه کنید تا ببینید کدام فیلد بین دو نقش تفاوت دارد. این روش برای پیدا کردن فیلدهایی که نباید در پاسخ باشند (مانند هش رمز، توکن داخلی یا وضعیت حساب دیگران) بسیار مؤثر است.

چسباندن ابزارهای شناسایی به هم

مرحله‌ی شناسایی در عمل زنجیره‌ای از ابزارهاست: subfinder زیردامنه می‌دهد، httpx زنده‌بودن و عنوان و فناوری را برمی‌گرداند، و شما باید از میان چند هزار خط، مواردی را انتخاب کنید که ارزش نگاه دستی دارند. پایتون همان لایه‌ی انتخاب است:

import json, sys
for line in sys.stdin:
    o = json.loads(line)
    t = (o.get("title") or "").lower()
    if o.get("status_code") == 200 and any(k in t for k in ("admin", "login", "dashboard")):
        print(o["url"], "|", o.get("title"))

این الگو ساده است اما تفاوت عملی بزرگی می‌سازد: به‌جای مرور دستی خروجی، اولویت‌بندی خودکار می‌شود. همین منطق را می‌توان برای یافتن پاسخ‌های با کد وضعیت غیرمعمول، اندازه‌ی پاسخ خارج از الگو، یا هدرهای امنیتی غایب هم نوشت. توجه کنید که همه‌ی این کارها فقط روی دامنه‌ای مجاز است که در دامنه‌ی توافق‌شده‌ی تست ذکر شده باشد.

افزونه‌نویسی برای پروکسی میانی

وقتی نیاز دارید منطقی روی همه‌ی ترافیک آزمون اعمال شود، جای درست آن داخل پروکسی است، نه در یک اسکریپت جداگانه. وضعیت فعلی سه ابزار:

  • Burp Suite: مسیر رسمی و جاری افزونه‌نویسی، Montoya API است که بر پایه‌ی Java نوشته می‌شود. Burp قطعه‌های کوچک درون‌خطی (Bambdas) و بررسی‌های اسکن سفارشی (BChecks) هم دارد. پشتیبانی قدیمی از پایتون از طریق پل Jython وجود دارد اما برای کار جدید توصیه نمی‌شود.
  • Caido: اکوسیستم افزونه‌ی آن بر پایه‌ی جاوااسکریپت است و مانند خود Caido هنوز پیش از نسخه‌ی ۱ قرار دارد.
  • mitmproxy: افزونه‌ها را با پایتون می‌نویسید و همین آن را برای اتوماسیون پایتونی طبیعی‌ترین انتخاب می‌کند — به‌ویژه برای اجرای بدون رابط گرافیکی و در خط لوله.

یک افزونه‌ی نمونه‌ی mitmproxy که یک بررسی دفاعی ساده انجام می‌دهد:

def response(flow):
    c = flow.response.headers.get("set-cookie", "")
    if c and "httponly" not in c.lower():
        print("cookie without HttpOnly:", flow.request.pretty_url)

ابزار Turbo Intruder — که برای آزمون شرایط مسابقه و ارسال با کنترل دقیق زمان استفاده می‌شود — هم اسکریپت‌های خود را با پایتون می‌گیرد. برای مقایسه‌ی کلی ابزارها ابزارهای تست نفوذ و برای مسیر رشد مهارت نقشه‌ی راه تست نفوذ را ببینید. اگر می‌خواهید این نوع آزمون‌ها روی سامانه‌ی سازمان شما به‌صورت حرفه‌ای انجام شود، تست نفوذ وب پی‌هانتر همین رویکرد را در قالب یک ارزیابی مستند اجرا می‌کند.

پرسش‌های متداول

برای تست نفوذ وب حتماً باید پایتون بلد باشیم؟

برای شروع نه، اما برای رشد حرفه‌ای عملاً بله. بدون توان نوشتن اسکریپت، بخشی از آزمون‌ها — به‌ویژه ماتریس مجوزدهی روی برنامه‌های بزرگ و آزمون‌های حساس به زمان — عملاً غیرقابل انجام است. سطح لازم هم بالا نیست: کتابخانه‌ی درخواست HTTP، کار با JSON و حلقه و شرط.

کدام کتابخانه‌های پایتون برای تست وب لازم است؟

در عمل چند مورد کافی است: requests برای درخواست HTTP، json و re از کتابخانه‌ی استاندارد، یک تجزیه‌گر HTML برای استخراج ساختاری، و در صورت نیاز به HTTP/2 یا کار هم‌زمان، کتابخانه‌های مدرن‌تر مبتنی بر async.

با پایتون می‌توان اکسپلویت نوشت؟

در کار حرفه‌ای، خروجی مورد نیاز یک اثبات مفهوم است: کوتاه‌ترین کدی که وجود آسیب‌پذیری را روی محیط مجاز نشان می‌دهد و در گزارش قابل بازتولید باشد. ساخت و انتشار زنجیره‌های بهره‌جویی آماده برای هدف‌های واقعی، نه بخشی از کار ما است و نه چیزی که در این سایت آموزش داده شود.

تفاوت اسکریپت شخصی با ابزار آماده چیست؟

ابزار آماده برای حالت عمومی بهینه شده و سریع است؛ اسکریپت شخصی برای منطق خاص همین برنامه نوشته می‌شود. رویه‌ی درست، ترکیب هر دو است: ابزار آماده برای پوشش گسترده، و اسکریپت اختصاصی برای جریان‌های چندمرحله‌ای و قواعد کسب‌وکار که هیچ ابزار عمومی‌ای آن‌ها را نمی‌فهمد.

اسکریپت من درخواست‌های زیادی می‌فرستد؛ آیا مشکلی ایجاد می‌کند؟

بله، اگر کنترل نشود. نرخ ارسال باید در قواعد درگیری توافق شده باشد، مخصوصاً روی محیط عملیاتی. علاوه بر خطر اختلال سرویس، اسکریپت‌های پرحجم می‌توانند داده‌ی آزمایشی زیادی در پایگاه‌داده‌ی مشتری تولید کنند که پاک‌سازی آن خودش تعهدی است در قرارداد.

محمدرضا مقدم
WRITTEN BY

محمدرضا مقدم

توسعه‌دهنده ابزار

مغز خودکارسازی تیم؛ ابزارها و زیرساخت داخلی پی‌هانتر را می‌سازد تا اسکن و گزارش‌گیری سریع‌تر و دقیق‌تر انجام شود.

PENTEST

ابزارهای تست نفوذ: معرفی و راهنمای انتخاب

ادامه مطلب ←