Zone Transfer in Fastapi
How Zone Transfer Manifests in Fastapi
Zone Transfer vulnerabilities in Fastapi applications typically occur when the framework inadvertently exposes internal DNS zone information through its endpoints. This manifests in several Fastapi-specific patterns that developers should be aware of.
The most common manifestation appears in Fastapi's dependency injection system. When developers create custom dependencies for DNS resolution or network operations, they might accidentally expose zone transfer capabilities. For example:
from fastapi import FastAPI, Depends
import dns.resolver
app = FastAPI()
async def get_dns_resolver() -> dns.resolver.Resolver:
resolver = dns.resolver.Resolver()
return resolver
@app.get("/dns-lookup")
async def dns_lookup(domain: str, resolver: dns.resolver.Resolver = Depends(get_dns_resolver)):
# Vulnerable: allows AXFR/IXFR queries if resolver is misconfigured
answers = resolver.resolve(domain)
return {"answers": [str(rdata) for rdata in answers]}This pattern becomes dangerous when the resolver is not properly configured to block zone transfer requests (AXFR/IXFR). Fastapi's async nature means these DNS queries can be executed without proper rate limiting, potentially allowing attackers to enumerate entire DNS zones.
Another Fastapi-specific manifestation occurs with background tasks. Developers often use Fastapi's BackgroundTasks to perform asynchronous DNS operations:
from fastapi import FastAPI, BackgroundTasks
app = FastAPI()
async def perform_dns_enumeration(domain: str):
# This could perform zone transfers if resolver is misconfigured
pass
@app.post("/start-enumeration")
async def start_enumeration(domain: str, background_tasks: BackgroundTasks):
background_tasks.add_task(perform_dns_enumeration, domain)
return {"status": "enumeration started"}The issue here is that background tasks can execute zone transfers without proper authentication or rate limiting, especially if the underlying DNS library doesn't enforce AXFR restrictions by default.
Fastapi's OpenAPI/Swagger documentation can also inadvertently expose zone transfer endpoints. When developers document internal DNS management endpoints, the auto-generated OpenAPI spec might reveal endpoints that should remain hidden:
@app.post("/admin/zone-transfer")
async def zone_transfer(domain: str):
# This endpoint should never be exposed publicly
resolver = dns.resolver.Resolver(configure=False)
resolver.nameservers = ['8.8.8.8']
# Zone transfer logic here
return {"zone_data": "..."}The Fastapi-specific vulnerability here is that the endpoint is documented in the OpenAPI spec and accessible without proper authentication decorators like @app.api_route with auth dependencies.
Fastapi-Specific Detection
Detecting zone transfer vulnerabilities in Fastapi applications requires a combination of static analysis and runtime scanning. The Fastapi framework's structure creates specific detection patterns that security scanners can leverage.
Static analysis should focus on Fastapi's dependency injection patterns. Look for dependencies that import dnspython or similar DNS libraries and are injected into endpoints without proper validation:
# Vulnerable pattern to detect
async def get_dns_resolver() -> dns.resolver.Resolver:
resolver = dns.resolver.Resolver()
return resolver
@app.get("/dns/")
async def dns_query(domain: str, resolver: dns.resolver.Resolver = Depends(get_dns_resolver)):
# This is vulnerable if resolver allows AXFR
return await resolver.resolve(domain, 'A')Security scanners should flag any endpoint that accepts DNS-related parameters (domain, nameserver, zone) and uses dependencies that create DNS resolvers. The scanner should check if the resolver is configured with allow_axfr=False or equivalent protections.
Runtime detection with middleBrick specifically targets Fastapi's async patterns. The scanner tests for zone transfer capabilities by sending AXFR requests to endpoints that might handle DNS queries:
# middleBrick would test patterns like:
import dns.query, dns.zone
# Test if endpoint accepts AXFR requests
axfr_query = dns.message.make_query('example.com', 'AXFR')
response = await client.post('/dns-query', json={'query': axfr_query.to_text()})
if response.status_code == 200 and 'zone' in response.json():
# Zone transfer vulnerability detected
return TruemiddleBrick's Fastapi-specific detection also examines the OpenAPI spec generation. It looks for endpoints that:
- Accept DNS-related parameters without authentication
- Use BackgroundTasks for network operations
- Have dependencies that create network clients
- Expose administrative endpoints in the public API spec
- Lack proper rate limiting decorators
The scanner tests Fastapi's built-in middleware and exception handlers to ensure they don't inadvertently expose zone information through error messages or debug responses.
Fastapi-Specific Remediation
Remediating zone transfer vulnerabilities in Fastapi requires a multi-layered approach that leverages Fastapi's native features and security best practices. Here's how to secure your Fastapi application against these specific threats.
First, implement proper dependency injection with security controls:
from fastapi import FastAPI, Depends, HTTPException
from fastapi.security import HTTPBearer
import dns.resolver
app = FastAPI()
security = HTTPBearer()
class SecureDNSResolver:
def __init__(self):
self.resolver = dns.resolver.Resolver()
# Block zone transfers by default
self.resolver.timeout = 3
self.resolver.lifetime = 3
async def resolve(self, domain: str, rdtype: str = 'A'):
if rdtype.upper() in ['AXFR', 'IXFR']:
raise HTTPException(status_code=403, detail="Zone transfers not allowed")
try:
return await self.resolver.resolve(domain, rdtype)
except Exception as e:
raise HTTPException(status_code=400, detail=str(e))
async def get_secure_resolver(token: str = Depends(security)):
# Validate token before providing resolver
if not validate_api_token(token):
raise HTTPException(status_code=401, detail="Invalid token")
return SecureDNSResolver()This pattern ensures that zone transfer requests are explicitly blocked and that DNS resolution requires proper authentication through Fastapi's dependency system.
Next, secure your endpoints with Fastapi's authentication and rate limiting:
from fastapi import FastAPI, Depends, BackgroundTasks
from fastapi.middleware import RateLimitMiddleware
from slowapi import Limiter, _rate_limit_exceeded_handler
from slowapi.util import get_remote_address
from slowapi.errors import RateLimitExceeded
app = FastAPI()
# Rate limiting middleware
limiter = Limiter(key_func=get_remote_address)
app.add_exception_handler(RateLimitExceeded, _rate_limit_exceeded_handler)
@app.middleware("http")
async def rate_limit_middleware(request, call_next):
if request.method == "POST" and "/dns/" in request.url.path:
# Stricter rate limits for DNS operations
limiter.limit("5/minute")(call_next)
return await call_next(request)For background tasks, implement proper security controls:
from fastapi import BackgroundTasks
from pydantic import BaseModel
from typing import List
class DNSQueryRequest(BaseModel):
domain: str
record_type: str = "A"
max_responses: int = 10
@app.post("/dns/query")
async def dns_query(
request: DNSQueryRequest,
background_tasks: BackgroundTasks,
resolver: SecureDNSResolver = Depends(get_secure_resolver)
):
if request.record_type.upper() in ['AXFR', 'IXFR']:
raise HTTPException(status_code=400, detail="Invalid record type")
if not is_valid_domain(request.domain):
raise HTTPException(status_code=400, detail="Invalid domain format")
# Process query with limits
background_tasks.add_task(
resolver.resolve_with_limits,
request.domain,
request.record_type,
request.max_responses
)
return {"status": "query queued"}Finally, secure your OpenAPI documentation and administrative endpoints:
from fastapi import APIRouter
# Create separate router for admin functions
admin_router = APIRouter(tags=["admin"])
@admin_router.post("/admin/dns-management")
async def dns_management(
token: str = Depends(security),
action: str,
domain: str = None
):
if not is_admin_user(token):
raise HTTPException(status_code=403, detail="Admin access required")
if action == "zone_transfer" and domain:
# Only allow zone transfers to admin users
if not await can_perform_zone_transfer(token, domain):
raise HTTPException(status_code=403, detail="Zone transfer not authorized")
# ... rest of admin logicRegister this admin router separately and ensure it's not included in the public OpenAPI spec:
# In your main app file
app.include_router(admin_router, prefix="/admin", include_in_schema=False)