|
|
|
@ -193,35 +193,30 @@ def inspect_value(value: str, source: str):
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if XSS_PATTERN.search(value):
|
|
|
|
if XSS_PATTERN.search(value):
|
|
|
|
log.warning(f"Security violation: Potential XSS payload detected in {source}")
|
|
|
|
|
|
|
|
raise HTTPException(
|
|
|
|
raise HTTPException(
|
|
|
|
status_code=422,
|
|
|
|
status_code=422,
|
|
|
|
detail=f"Potential XSS payload detected in {source}",
|
|
|
|
detail=f"Potential XSS payload detected in {source}",
|
|
|
|
)
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
if SQLI_PATTERN.search(value):
|
|
|
|
if SQLI_PATTERN.search(value):
|
|
|
|
log.warning(f"Security violation: Potential SQL injection payload detected in {source}")
|
|
|
|
|
|
|
|
raise HTTPException(
|
|
|
|
raise HTTPException(
|
|
|
|
status_code=422,
|
|
|
|
status_code=422,
|
|
|
|
detail=f"Potential SQL injection payload detected in {source}",
|
|
|
|
detail=f"Potential SQL injection payload detected in {source}",
|
|
|
|
)
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
if RCE_PATTERN.search(value):
|
|
|
|
if RCE_PATTERN.search(value):
|
|
|
|
log.warning(f"Security violation: Potential RCE payload detected in {source}")
|
|
|
|
|
|
|
|
raise HTTPException(
|
|
|
|
raise HTTPException(
|
|
|
|
status_code=422,
|
|
|
|
status_code=422,
|
|
|
|
detail=f"Potential RCE payload detected in {source}",
|
|
|
|
detail=f"Potential RCE payload detected in {source}",
|
|
|
|
)
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
if TRAVERSAL_PATTERN.search(value):
|
|
|
|
if TRAVERSAL_PATTERN.search(value):
|
|
|
|
log.warning(f"Security violation: Potential Path Traversal payload detected in {source}")
|
|
|
|
|
|
|
|
raise HTTPException(
|
|
|
|
raise HTTPException(
|
|
|
|
status_code=422,
|
|
|
|
status_code=422,
|
|
|
|
detail=f"Potential Path Traversal payload detected in {source}",
|
|
|
|
detail=f"Potential Path Traversal payload detected in {source}",
|
|
|
|
)
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
if has_control_chars(value):
|
|
|
|
if has_control_chars(value):
|
|
|
|
log.warning(f"Security violation: Invalid control characters detected in {source}")
|
|
|
|
|
|
|
|
raise HTTPException(
|
|
|
|
raise HTTPException(
|
|
|
|
status_code=422,
|
|
|
|
status_code=422,
|
|
|
|
detail=f"Invalid control characters detected in {source}",
|
|
|
|
detail=f"Invalid control characters detected in {source}",
|
|
|
|
@ -232,14 +227,12 @@ def inspect_json(obj, path="body", check_whitelist=True):
|
|
|
|
if isinstance(obj, dict):
|
|
|
|
if isinstance(obj, dict):
|
|
|
|
for key, value in obj.items():
|
|
|
|
for key, value in obj.items():
|
|
|
|
if key in FORBIDDEN_JSON_KEYS:
|
|
|
|
if key in FORBIDDEN_JSON_KEYS:
|
|
|
|
log.warning(f"Security violation: Forbidden JSON key detected: {path}.{key}")
|
|
|
|
|
|
|
|
raise HTTPException(
|
|
|
|
raise HTTPException(
|
|
|
|
status_code=422,
|
|
|
|
status_code=422,
|
|
|
|
detail=f"Forbidden JSON key detected: {path}.{key}",
|
|
|
|
detail=f"Forbidden JSON key detected: {path}.{key}",
|
|
|
|
)
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
if check_whitelist and key not in ALLOWED_DATA_PARAMS:
|
|
|
|
if check_whitelist and key not in ALLOWED_DATA_PARAMS:
|
|
|
|
log.warning(f"Security violation: Unknown JSON key detected: {path}.{key}")
|
|
|
|
|
|
|
|
raise HTTPException(
|
|
|
|
raise HTTPException(
|
|
|
|
status_code=422,
|
|
|
|
status_code=422,
|
|
|
|
detail=f"Unknown JSON key detected: {path}.{key}",
|
|
|
|
detail=f"Unknown JSON key detected: {path}.{key}",
|
|
|
|
@ -273,7 +266,6 @@ class RequestValidationMiddleware(BaseHTTPMiddleware):
|
|
|
|
ALLOW_DUPLICATE_HEADERS = {'accept', 'accept-encoding', 'accept-language', 'accept-charset', 'cookie'}
|
|
|
|
ALLOW_DUPLICATE_HEADERS = {'accept', 'accept-encoding', 'accept-language', 'accept-charset', 'cookie'}
|
|
|
|
real_duplicates = [h for h in duplicate_headers if h not in ALLOW_DUPLICATE_HEADERS]
|
|
|
|
real_duplicates = [h for h in duplicate_headers if h not in ALLOW_DUPLICATE_HEADERS]
|
|
|
|
if real_duplicates:
|
|
|
|
if real_duplicates:
|
|
|
|
log.warning(f"Security violation: Duplicate headers detected: {real_duplicates}")
|
|
|
|
|
|
|
|
raise HTTPException(
|
|
|
|
raise HTTPException(
|
|
|
|
status_code=422,
|
|
|
|
status_code=422,
|
|
|
|
detail=f"Duplicate headers are not allowed: {real_duplicates}",
|
|
|
|
detail=f"Duplicate headers are not allowed: {real_duplicates}",
|
|
|
|
@ -284,7 +276,6 @@ class RequestValidationMiddleware(BaseHTTPMiddleware):
|
|
|
|
if unknown_headers:
|
|
|
|
if unknown_headers:
|
|
|
|
filtered_unknown = [h for h in unknown_headers if not h.startswith('sec-')]
|
|
|
|
filtered_unknown = [h for h in unknown_headers if not h.startswith('sec-')]
|
|
|
|
if filtered_unknown:
|
|
|
|
if filtered_unknown:
|
|
|
|
log.warning(f"Security violation: Unknown headers detected: {filtered_unknown}")
|
|
|
|
|
|
|
|
raise HTTPException(
|
|
|
|
raise HTTPException(
|
|
|
|
status_code=422,
|
|
|
|
status_code=422,
|
|
|
|
detail=f"Unknown headers detected: {filtered_unknown}",
|
|
|
|
detail=f"Unknown headers detected: {filtered_unknown}",
|
|
|
|
@ -299,7 +290,6 @@ class RequestValidationMiddleware(BaseHTTPMiddleware):
|
|
|
|
# 1. Query string limits
|
|
|
|
# 1. Query string limits
|
|
|
|
# -------------------------
|
|
|
|
# -------------------------
|
|
|
|
if len(request.url.query) > MAX_QUERY_LENGTH:
|
|
|
|
if len(request.url.query) > MAX_QUERY_LENGTH:
|
|
|
|
log.warning(f"Security violation: Query string too long")
|
|
|
|
|
|
|
|
raise HTTPException(
|
|
|
|
raise HTTPException(
|
|
|
|
status_code=422,
|
|
|
|
status_code=422,
|
|
|
|
detail="Query string too long",
|
|
|
|
detail="Query string too long",
|
|
|
|
@ -308,7 +298,6 @@ class RequestValidationMiddleware(BaseHTTPMiddleware):
|
|
|
|
params = request.query_params.multi_items()
|
|
|
|
params = request.query_params.multi_items()
|
|
|
|
|
|
|
|
|
|
|
|
if len(params) > MAX_QUERY_PARAMS:
|
|
|
|
if len(params) > MAX_QUERY_PARAMS:
|
|
|
|
log.warning(f"Security violation: Too many query parameters")
|
|
|
|
|
|
|
|
raise HTTPException(
|
|
|
|
raise HTTPException(
|
|
|
|
status_code=422,
|
|
|
|
status_code=422,
|
|
|
|
detail="Too many query parameters",
|
|
|
|
detail="Too many query parameters",
|
|
|
|
@ -317,7 +306,6 @@ class RequestValidationMiddleware(BaseHTTPMiddleware):
|
|
|
|
# Check for unknown query parameters
|
|
|
|
# Check for unknown query parameters
|
|
|
|
unknown_params = [key for key, _ in params if key not in ALLOWED_DATA_PARAMS]
|
|
|
|
unknown_params = [key for key, _ in params if key not in ALLOWED_DATA_PARAMS]
|
|
|
|
if unknown_params:
|
|
|
|
if unknown_params:
|
|
|
|
log.warning(f"Security violation: Unknown query parameters detected: {unknown_params}")
|
|
|
|
|
|
|
|
raise HTTPException(
|
|
|
|
raise HTTPException(
|
|
|
|
status_code=422,
|
|
|
|
status_code=422,
|
|
|
|
detail=f"Unknown query parameters detected: {unknown_params}",
|
|
|
|
detail=f"Unknown query parameters detected: {unknown_params}",
|
|
|
|
@ -333,7 +321,6 @@ class RequestValidationMiddleware(BaseHTTPMiddleware):
|
|
|
|
]
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
|
|
if duplicates:
|
|
|
|
if duplicates:
|
|
|
|
log.warning(f"Security violation: Duplicate query parameters detected: {duplicates}")
|
|
|
|
|
|
|
|
raise HTTPException(
|
|
|
|
raise HTTPException(
|
|
|
|
status_code=422,
|
|
|
|
status_code=422,
|
|
|
|
detail=f"Duplicate query parameters are not allowed: {duplicates}",
|
|
|
|
detail=f"Duplicate query parameters are not allowed: {duplicates}",
|
|
|
|
@ -351,19 +338,16 @@ class RequestValidationMiddleware(BaseHTTPMiddleware):
|
|
|
|
try:
|
|
|
|
try:
|
|
|
|
size_val = int(value)
|
|
|
|
size_val = int(value)
|
|
|
|
if size_val > 50:
|
|
|
|
if size_val > 50:
|
|
|
|
log.warning(f"Security violation: Pagination size too large ({size_val})")
|
|
|
|
|
|
|
|
raise HTTPException(
|
|
|
|
raise HTTPException(
|
|
|
|
status_code=422,
|
|
|
|
status_code=422,
|
|
|
|
detail=f"Pagination size '{key}' cannot exceed 50",
|
|
|
|
detail=f"Pagination size '{key}' cannot exceed 50",
|
|
|
|
)
|
|
|
|
)
|
|
|
|
if size_val % 5 != 0:
|
|
|
|
if size_val % 5 != 0:
|
|
|
|
log.warning(f"Security violation: Pagination size not multiple of 5 ({size_val})")
|
|
|
|
|
|
|
|
raise HTTPException(
|
|
|
|
raise HTTPException(
|
|
|
|
status_code=422,
|
|
|
|
status_code=422,
|
|
|
|
detail=f"Pagination size '{key}' must be a multiple of 5",
|
|
|
|
detail=f"Pagination size '{key}' must be a multiple of 5",
|
|
|
|
)
|
|
|
|
)
|
|
|
|
except ValueError:
|
|
|
|
except ValueError:
|
|
|
|
log.warning(f"Security violation: Pagination size invalid value ({value})")
|
|
|
|
|
|
|
|
raise HTTPException(
|
|
|
|
raise HTTPException(
|
|
|
|
status_code=422,
|
|
|
|
status_code=422,
|
|
|
|
detail=f"Pagination size '{key}' must be an integer",
|
|
|
|
detail=f"Pagination size '{key}' must be an integer",
|
|
|
|
@ -377,7 +361,6 @@ class RequestValidationMiddleware(BaseHTTPMiddleware):
|
|
|
|
content_type.startswith(t)
|
|
|
|
content_type.startswith(t)
|
|
|
|
for t in ("application/json", "multipart/form-data", "application/x-www-form-urlencoded")
|
|
|
|
for t in ("application/json", "multipart/form-data", "application/x-www-form-urlencoded")
|
|
|
|
):
|
|
|
|
):
|
|
|
|
log.warning(f"Security violation: Unsupported Content-Type: {content_type}")
|
|
|
|
|
|
|
|
raise HTTPException(status_code=422, detail="Unsupported Content-Type")
|
|
|
|
raise HTTPException(status_code=422, detail="Unsupported Content-Type")
|
|
|
|
|
|
|
|
|
|
|
|
# -------------------------
|
|
|
|
# -------------------------
|
|
|
|
@ -394,7 +377,6 @@ class RequestValidationMiddleware(BaseHTTPMiddleware):
|
|
|
|
has_body = True
|
|
|
|
has_body = True
|
|
|
|
|
|
|
|
|
|
|
|
if has_query and has_body:
|
|
|
|
if has_query and has_body:
|
|
|
|
log.warning(f"Security violation: Mixed parameters (query + JSON body)")
|
|
|
|
|
|
|
|
raise HTTPException(
|
|
|
|
raise HTTPException(
|
|
|
|
status_code=422,
|
|
|
|
status_code=422,
|
|
|
|
detail="Parameters must be from a single source (query string or JSON body), mixed sources are not allowed",
|
|
|
|
detail="Parameters must be from a single source (query string or JSON body), mixed sources are not allowed",
|
|
|
|
|