agentsclimarketplace

Django attack probe

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

Defensive security skills for Claude Code and the Claude Agent SDK — web applications and generative AI systems.

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.

What its author says it does

Copied from the file, not written here

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.

SKILL.md

4.8 KB, 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

Keep looking

Skills are one crate of 328,083. 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.