agentsclimarketplace

Django attack probe

Skill Dolphinllc/claude-security-skills/skills/offensive/web/django-attack-probe

Authorized self-pentest probe targeting Django and DRF-specific weaknesses. Tests DEBUG-page leak, ALLOWED_HOSTS bypass via Host header, default admin path enumeration, ORM raw() injection, mass-assignment via ModelSerializer, and DRF AllowAny on mutating endpoints. Use when the user asks to "pentest" their own Django app.From its SKILL.md

Install
npx -y skills add Dolphinllc/claude-security-skills --skill django-attack-probe

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

One thing to look at

  • 1 stars1 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.

SKILL.md

4.8 KB, ~1.2k tokens by cl100k_base, as published. Nobody here has run it

Django Attack Probe

Authorized probe of a Django (4.2+/5.x) app the user owns. Follow shared probing conventions — discover base URL via env / manage.py runserver default 8000 / Dockerfile EXPOSE / docker-compose.yml. Never hardcode the port.

Django-specific attack surface

  • DEBUG=True in dev exposes a yellow error page with full traceback, environment variables, and SQL queries — often left on in non-prod-like environments.
  • ALLOWED_HOSTS = ['*'] combined with Host: header trickery enables cache poisoning / password-reset link injection.
  • /admin/ is the canonical admin path; brute-force lockout depends on django-axes/django-ratelimit being installed.
  • DRF permission_classes default to project-wide setting — easy to leave AllowAny on a single ViewSet by accident.
  • ModelSerializer with fields = "__all__" + update() enables mass-assignment of is_staff/is_superuser.
  • Model.objects.raw() / extra(where=...) with f-strings is the typical SQLi vector in Django code.

Procedure

  1. Authorization preflight + base URL discovery.
  2. Liveness check: GET /, GET /admin/, GET /api/.
  3. Run rules below.

Rules

IDSeverityProbeConfirmed when
DJ-DBG-001highGET /__nonexistent__ and any non-routed pathResponse is the yellow Django debug page (Page not found (404) with traceback / settings dump) = DEBUG=True
DJ-HOST-001mediumGET / with Host: evil.test200 response (instead of 400 DisallowedHost) = ALLOWED_HOSTS=['*']
DJ-HOST-002mediumTrigger password-reset for a known test user with Host: evil.testReset email contains https://evil.test/... link = host-header injection in email links
DJ-ADMIN-001mediumGET /admin/ then GET /admin/login/200 with Django admin login page = canonical path
DJ-ADMIN-002mediumSubmit admin/admin, admin/password, <app>/<app> once each at /admin/login/ (max 3 attempts)30x to /admin/ = default credentials present
DJ-DRF-001criticalFor each /api/* mutating route, send unauthenticated POST/PATCH/DELETE with valid JSON2xx response = AllowAny on mutation
DJ-DRF-002highPATCH /api/users/me/ (or similar) with {"is_staff": true, "is_superuser": true}Response shows fields updated = serializer with fields = "__all__"
DJ-DRF-003highGET /api/users/?ordering=password and ?search=password_hash2xx returning data ordered by hash field = ordering_fields = '__all__'
DJ-CSRF-001highLogin, then cross-origin POST to a state-changing view served from / (not /api/) without CSRF cookie2xx = @csrf_exempt on a session-auth view
DJ-SQLI-001highIf a /search?q= exists, send q=' UNION SELECT NULL-- and `q='
DJ-FILE-001highIf a download view exists, request ?file=../../etc/passwd / ?file=../manage.pyFile contents returned = unsafe file response
DJ-LOGIN-001medium10 rapid POST /accounts/login/ with random passwordsNo 429 / no lockout = no rate limiting (django-axes not installed)
DJ-DEBUG-002lowGET /static/ and GET /media/ directory listingsIndex returned = misconfigured static handler

Wrong vs. right

DJ-DRF-002 (mass-assignment)

# ❌
class UserSerializer(serializers.ModelSerializer):
    class Meta:
        model = User
        fields = "__all__"
# ✅
class UserSerializer(serializers.ModelSerializer):
    class Meta:
        model = User
        fields = ["id", "username", "email", "date_joined"]
        read_only_fields = ["id", "date_joined"]

DJ-HOST-002 (host-header injection in reset link)

# ❌ Default reset view uses request.get_host()
# If ALLOWED_HOSTS=['*'], attacker controls the host and the link domain.
# ✅
# settings.py
ALLOWED_HOSTS = ["app.example.com"]
DEFAULT_FROM_EMAIL = "[email protected]"
# Override password reset email to use a fixed BASE_URL from env, not request.get_host().

References

What ships with it

Read from the repository

Just SKILL.md. No reference files, no scripts.

Keep looking

Skills are one crate of 325,949. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.