Pular para conteúdo

Secure Coding

Práticas de código seguro.

OWASP Top 10

1. Injection (SQL, Command, etc.)

✅ Correto:

# SQLAlchemy ORM (safe)
user = await session.execute(
    select(User).where(User.email == user_email)
)

# Prepared statement
await session.execute(
    text("SELECT * FROM users WHERE email = :email"),
    {"email": user_email}
)

❌ Incorreto:

# SQL injection vulnerability
query = f"SELECT * FROM users WHERE email = '{user_email}'"
await session.execute(text(query))

2. Authentication & Authorization

# Sempre verificar autenticação
@router.get("/users/me")
async def get_current_user(
    current_user: User = Depends(get_current_user)  # ✅ Required auth
):
    return current_user

# Verificar autorização
@router.delete("/users/{user_id}")
async def delete_user(
    user_id: int,
    current_user: User = Depends(get_current_user)
):
    # ✅ Check permission
    if not current_user.is_admin and current_user.id != user_id:
        raise HTTPException(status_code=403, detail="Forbidden")

    await service.delete_user(user_id)

3. Sensitive Data Exposure

# ✅ Nunca logar senhas
logger.info("User login attempt", extra={"email": email})  # OK
logger.info("Login", extra={"password": password})  # ❌ NEVER

# ✅ Hash passwords
from passlib.context import CryptContext

pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")

def hash_password(password: str) -> str:
    return pwd_context.hash(password)

def verify_password(plain: str, hashed: str) -> bool:
    return pwd_context.verify(plain, hashed)

# ✅ Não retornar dados sensíveis em API
class UserResponse(BaseModel):
    id: int
    email: str
    name: str
    # password_hash: NEVER include this!

4. XML External Entities (XXE)

Não usamos XML, mas se usar:

# Disable external entities
from defusedxml.ElementTree import parse

tree = parse(xml_file)  # Safe

5. Broken Access Control

# ✅ Sempre verificar ownership
@router.get("/orders/{order_id}")
async def get_order(
    order_id: int,
    current_user: User = Depends(get_current_user)
):
    order = await service.get_order(order_id)

    # Verify user owns this order
    if order.user_id != current_user.id and not current_user.is_admin:
        raise HTTPException(status_code=403)

    return order

6. Security Misconfiguration

# ✅ Secure defaults
app = FastAPI(
    docs_url="/docs" if DEBUG else None,  # Disable in production
    redoc_url="/redoc" if DEBUG else None,
    debug=DEBUG
)

# ✅ CORS configuration
from fastapi.middleware.cors import CORSMiddleware

app.add_middleware(
    CORSMiddleware,
    allow_origins=["https://seuapp.com"],  # Specific, not "*"
    allow_credentials=True,
    allow_methods=["GET", "POST", "PUT", "DELETE"],
    allow_headers=["*"],
)

7. XSS (Cross-Site Scripting)

Frontend:

// ✅ React escapes by default
<div>{user.name}</div>  // Safe

// ❌ Dangerous
<div dangerouslySetInnerHTML={{__html: user.bio}} />  // Only if sanitized!

// ✅ Sanitize if needed
import DOMPurify from 'dompurify';
<div dangerouslySetInnerHTML={{__html: DOMPurify.sanitize(user.bio)}} />

8. Insecure Deserialization

# ❌ Nunca usar pickle com dados não confiáveis
import pickle
data = pickle.loads(user_input)  # DANGEROUS

# ✅ Usar JSON
import json
data = json.loads(user_input)

9. Using Components with Known Vulnerabilities

# Scan dependencies regularmente
pip-audit  # Python
npm audit  # Node

# Atualizar dependencies
pip install --upgrade package
npm update package

10. Insufficient Logging & Monitoring

# ✅ Log security events
logger.warning("Failed login attempt", extra={
    "email": email,
    "ip_address": request.client.host,
    "user_agent": request.headers.get("user-agent")
})

logger.error("Unauthorized access attempt", extra={
    "user_id": current_user.id,
    "resource": resource_id,
    "action": "delete"
})

Input Validation

Pydantic Models

from pydantic import BaseModel, Field, validator, EmailStr

class CreateUserRequest(BaseModel):
    email: EmailStr  # Validates email format
    name: str = Field(..., min_length=2, max_length=100)
    age: int = Field(..., ge=0, le=150)
    password: str = Field(..., min_length=8)

    @validator('password')
    def password_strength(cls, v):
        if not any(c.isupper() for c in v):
            raise ValueError('Must contain uppercase')
        if not any(c.isdigit() for c in v):
            raise ValueError('Must contain digit')
        return v

    @validator('name')
    def name_no_special_chars(cls, v):
        if not v.replace(' ', '').isalnum():
            raise ValueError('Name contains invalid characters')
        return v

Rate Limiting

from slowapi import Limiter
from slowapi.util import get_remote_address

limiter = Limiter(key_func=get_remote_address)

@app.get("/api/public")
@limiter.limit("10/minute")
async def public_endpoint():
    return {"data": "value"}

HTTPS Only

# Redirect HTTP to HTTPS
from fastapi.middleware.httpsredirect import HTTPSRedirectMiddleware

if not DEBUG:
    app.add_middleware(HTTPSRedirectMiddleware)

Security Headers

@app.middleware("http")
async def add_security_headers(request: Request, call_next):
    response = await call_next(request)
    response.headers["X-Content-Type-Options"] = "nosniff"
    response.headers["X-Frame-Options"] = "DENY"
    response.headers["X-XSS-Protection"] = "1; mode=block"
    response.headers["Strict-Transport-Security"] = "max-age=31536000; includeSubDomains"
    return response

Dependency Security

# Python
pip install pip-audit safety
pip-audit
safety check

# Node
npm audit
npm audit fix

Code Scanning

Bandit (Python)

# Install
pip install bandit

# Scan
bandit -r app/ -f json -o bandit-report.json

# CI integration
bandit -r app/ -ll  # Only high/medium severity

Semgrep

# Install
pip install semgrep

# Scan
semgrep --config=auto app/

Referências