
اعثر على كل طلب API يقوم به تطبيق ويب، من خلال JavaScript الذي يرسله: الطريقة، وعنوان URL، والجسم، والترويسات، وعمليات GraphQL. أمر واحد.
استعد كل طلب صُمم تطبيق ويب لتنفيذه من شيفرة JavaScript التي يوزعها: الطريقة، وعنوان URL، والجسم، ونوع المحتوى، والترويسات.
تحتوي حزمة JavaScript لتطبيق صفحة واحدة على كل طلب API يمكن للتطبيق تنفيذه. عند إعطائه عنوان URL، يعثر jshound على حزمة التطبيق، ويزحف كل محتواها، بما في ذلك القطع المحمّلة بتأخير، ويستخرج تلك الطلبات.
إنه سلبي. الطلبات الوحيدة التي ينفذها هي طلبات GET غير مصادَق عليها للملفات الثابتة العامة، والصفحة وشيفرة JavaScript الخاصة بها. يعمل التحليل محليًا، ولا يُرسل شيء إلى API التطبيق.
git clone https://github.com/AmbitSecurity/jshound && cd jshound
python3 -m venv .venv && source .venv/bin/activate
pip install -e .
يتطلب Python 3.10 أو أحدث وNode 18 أو أحدث.
jshound supabase.com/dashboard
$ jshound supabase.com/dashboard
307 128B https://supabase.com/dashboard
200 19K https://supabase.com/dashboard/org
200 28K https://frontend-assets.supabase.com/studio/8617532ae1a4/_next/static/chunks/1p849pzlxcp4n.js?dpl=dpl_ESaSquAbnWrJqbvvyRh7F1qj5wWH
...
supabase.com 200 2282 files (2281 from frontend-assets.supabase.com, 1 from supabase.com) -> 387 endpoints
1 declared chunk(s) could not be fetched
-> .jshound/supabase.com/endpoints.txt (also requests.jsonl, urls.txt, openapi.json)
frontend-assets.supabase.com، حيث يوجد بناء supabase.POST https://api.supabase.com/platform/auth/{ref}/otp {"phone":"state:phone"}
DELETE https://api.supabase.com/v1/projects/{param_n}/functions/{param_e}
DELETE https://supabase.com/dashboard/api/integrations/stripe-sync application/json {"projectRef":"input:e","startTime":"input:t"}
POST https://supabase.com/docs/api/search/fts application/json {"query":"computed:trim"}
عدة أهداف في وقت واحد، أو قائمة على stdin:
jshound app.example.com api.example.com
cat scope.txt | jshound
الملفات الموجودة بالفعل على القرص، مثل مجلد js/ من تشغيل سابق، تُقرأ دون لمس الشبكة:
$ jshound examples/demo-app
GET https://demo-app.test/api/feature-flags
POST https://demo-app.test/t/events {"sid":"?","evt":"?"}
DELETE https://demo-app.test/v1/sessions/current
POST {baseUrl}/v1/media multipart/form-data {"file":"file-upload"}
POST {baseUrl}/v1/subscribe application/json {"email":"computed:trim","pageUri":"state:href"}
GET {baseUrl}/v1/users/{param_o}/profile
يذهب كل شيء إلى .jshound/<host>/، أو إلى -o DIR:
endpoints.txt | الجدول أعلاه، طلب واحد في كل سطر |
requests.jsonl | كائن JSON واحد لكل طلب: method، url، body، content_type، headers، graphql أو rpc، والصفحة والملف الذي جاء منه |
urls.txt | METHOD URL للطلبات التي يكون مضيفها معروفًا، للأدوات التي تأخذ قائمة |
openapi.json | مستند OpenAPI 3، للاستيراد إلى Burp أو Caido أو Postman |
js/ | كل ملف تم جلبه |
{...} قيمة لم يتمكن jshound من حلها، مكتوبة كما تكتبها الشيفرة: {baseUrl}،
{window.__CONFIG__.apiBaseUrl}. في مسار، {ref} معامل، و{param_o} هو
معامل الدالة o./api/surveys/، يمر عبر شيفرة لا يتبعها jshound إلى
أساسها بعد. يسمّي requests.jsonl الملف الذي جاء منه.input: وسيط، state: خاصية
لكائن، computed: نتيجة استدعاء، ? غير معروف.application/json? نوع محتوى مستنتج من طلبات الملف الأخرى بدلًا من قراءته
في موضع الاستدعاء. ~{...} جسم يحتوي على حقول أكثر من تلك المذكورة.{graphql} عملية GraphQL، مع اسمها ونوعها ومتغيراتها كجسم.| bundler | عناوين URL للقطع المحمّلة بتأخير | شجرة الوحدات | كيف |
|---|---|---|---|
| webpack 5 | نعم | نعم | يقرأ __webpack_require__.u من شجرة الصياغة |
| webpack 4 | نعم | نعم | ليس لديه .u، لذا يُطابَق باني العنوان المسمّى بالشكل بدلًا من الاسم |
| Rspack | نعم | نعم | متوافق مع webpack، ويصدر عادةً ESM |
| Turbopack | نعم | غير متاح | لا يوجد باني على الإطلاق؛ قائمة القطع مصفوفة حرفية |
| Vite / Rollup | نعم | غير متاح | المسارات مكتوبة (__vite__mapDeps)؛ ESM المسطح ليس لديه سجل للتنقل فيه |
| esbuild | نعم | غير متاح | نفسه |
| Parcel | نعم | غير متاح | العام العالمي للوقت التشغيلي ملحق لكل بناء (parcelRequire5707) وجدول القطع كائن، وليس مصفوفة |
| Metro (React Native) | غير متاح | غير متاح | يوزع حزمة واحدة، بلا قطع؛ يُكتشف بواسطة __METRO_GLOBAL_PREFIX__ |
| webpack Module Federation | نعم | نعم | الباني لا يحمل خريطة معرّفات، لذا تُؤخذ المعرّفات من مواضع استدعاء __webpack_require__.e(480) |
يطلب jshound الهدف الذي تعطيه إياه، وملفات JavaScript التي تحمّلها صفحات ذلك الهدف، أينما
كانت مستضافة. بالنسبة لـ supabase، هذا في الغالب frontend-assets.supabase.com. هذه ملفات ثابتة
عامة يحمّلها متصفح أي زائر. لا تحصل تلك المضيفات الأخرى على شيء سوى طلبات GET تلك.
يرفض أي عنوان URL يسمّيه هدف في مساحة عنوان خاصة (loopback، RFC1918، link local)، إلا إذا
كان الهدف الذي أعطيته إياه خاصًا بنفسه أو مررت --allow-private.
-o DIR | أين تذهب النتائج (افتراضيًا .jshound/<host>) |
-q | لا تطبع كل طلب عند تنفيذه |
-v | اطبع أيضًا سجل التحليل |
--max-chunks N | توقف بعد هذا العدد من القطع المحمّلة بتأخير والاستيرادات (افتراضيًا 50000) |
--no-chunks | لا تتبع القطع المحمّلة بتأخير |
--proxy URL | أرسل كل طلب عبر وسيط (أو عيّن JSHOUND_PROXY) |
--proxy-ca FILE | شهادة CA لوسيط يعترض TLS |
--client PROFILE | ثبّت ملف تعريف TLS واحد للمتصفح بدلًا من التبديل عند الحجب |
--allow-private | اسمح بعناوين URL في مساحة عنوان خاصة (انظر النطاق) |
blocked by Cloudflare ولا يقرأ شيئًا. يحاكي jshound أحدث Chrome وFirefox
وSafari التي يعرفها curl_cffi، لذا أبقِ curl_cffi محدّثًا. يساعد --proxy مع حدود المعدل
وحظر IP، لا مع التحديات.document.createElement("script")، باستخدام عنوان URL تحصل عليه من استدعاء API، ليس في أي شيء
ينزّله jshound.انظر CONTRIBUTING.md.
MIT.