mirror of
https://github.com/eduardogsilva/wireguard_webadmin.git
synced 2026-03-17 22:36:17 +00:00
enhance security by adding cache control headers, validating password length, and rejecting encoded slashes in path processing
This commit is contained in:
@@ -7,7 +7,12 @@ from auth_gateway.models.auth import UserModel
|
|||||||
password_hasher = PasswordHasher()
|
password_hasher = PasswordHasher()
|
||||||
|
|
||||||
|
|
||||||
|
MAX_PASSWORD_LENGTH = 1024
|
||||||
|
|
||||||
|
|
||||||
def verify_user_password(username: str, password: str, users: dict[str, UserModel]) -> UserModel | None:
|
def verify_user_password(username: str, password: str, users: dict[str, UserModel]) -> UserModel | None:
|
||||||
|
if not password or len(password) > MAX_PASSWORD_LENGTH:
|
||||||
|
return None
|
||||||
user = users.get(username)
|
user = users.get(username)
|
||||||
if not user or not user.password_hash:
|
if not user or not user.password_hash:
|
||||||
return None
|
return None
|
||||||
|
|||||||
@@ -57,6 +57,7 @@ async def auth_check(request: Request):
|
|||||||
return re.sub(r"[\r\n\x00]", "", value)
|
return re.sub(r"[\r\n\x00]", "", value)
|
||||||
|
|
||||||
response = PlainTextResponse("OK", status_code=200)
|
response = PlainTextResponse("OK", status_code=200)
|
||||||
|
response.headers["Cache-Control"] = "no-store"
|
||||||
if session:
|
if session:
|
||||||
if session.username:
|
if session.username:
|
||||||
response.headers["X-Auth-User"] = _safe_header(session.username)
|
response.headers["X-Auth-User"] = _safe_header(session.username)
|
||||||
|
|||||||
@@ -317,6 +317,7 @@ async def login_oidc_start(request: Request, next: str = "/"):
|
|||||||
|
|
||||||
|
|
||||||
@router.get("/login/oidc/callback")
|
@router.get("/login/oidc/callback")
|
||||||
|
@limiter.limit(AUTH_RATE_LIMIT)
|
||||||
async def login_oidc_callback(request: Request, state: str):
|
async def login_oidc_callback(request: Request, state: str):
|
||||||
runtime_config = get_runtime_config(request)
|
runtime_config = get_runtime_config(request)
|
||||||
oidc_state = request.app.state.session_service.consume_oidc_state(state)
|
oidc_state = request.app.state.session_service.consume_oidc_state(state)
|
||||||
@@ -352,10 +353,12 @@ async def login_oidc_callback(request: Request, state: str):
|
|||||||
|
|
||||||
|
|
||||||
def _safe_redirect_path(url: str | None) -> str:
|
def _safe_redirect_path(url: str | None) -> str:
|
||||||
"""Accept only relative paths to prevent open redirects."""
|
"""Accept only relative paths to prevent open redirects, including protocol-relative URLs."""
|
||||||
if not url or "://" in url or not url.startswith("/"):
|
if not url:
|
||||||
return "/"
|
return "/"
|
||||||
return url
|
from urllib.parse import urlsplit
|
||||||
|
path = urlsplit(url).path or "/"
|
||||||
|
return path if path.startswith("/") else "/"
|
||||||
|
|
||||||
|
|
||||||
def _do_logout(request: Request, next_url: str = "/") -> RedirectResponse:
|
def _do_logout(request: Request, next_url: str = "/") -> RedirectResponse:
|
||||||
|
|||||||
@@ -81,6 +81,19 @@ def build_caddyfile(apps, auth_policies, routes):
|
|||||||
for header_name in AUTH_IDENTITY_HEADERS:
|
for header_name in AUTH_IDENTITY_HEADERS:
|
||||||
lines.append(f"{indent}request_header -{header_name}")
|
lines.append(f"{indent}request_header -{header_name}")
|
||||||
|
|
||||||
|
def emit_encoded_slash_block():
|
||||||
|
# Reject paths containing %2f or %2F (percent-encoded slash).
|
||||||
|
# Caddy's path matcher does not decode percent-encoding, so /%2fadmin
|
||||||
|
# would NOT match path /admin and would fall through to the default
|
||||||
|
# (potentially bypass) handler, even though upstreams may decode it to /admin.
|
||||||
|
lines.append(" @encoded_slash {")
|
||||||
|
lines.append(" path_regexp (?i)%2f")
|
||||||
|
lines.append(" }")
|
||||||
|
lines.append(" handle @encoded_slash {")
|
||||||
|
lines.append(" respond 400")
|
||||||
|
lines.append(" }")
|
||||||
|
lines.append("")
|
||||||
|
|
||||||
def emit_route_matcher(matcher_name, path_prefix):
|
def emit_route_matcher(matcher_name, path_prefix):
|
||||||
matcher_name = re.sub(r"[^A-Za-z0-9_]", "_", matcher_name)
|
matcher_name = re.sub(r"[^A-Za-z0-9_]", "_", matcher_name)
|
||||||
normalized_prefix = path_prefix.strip().rstrip("/") or "/"
|
normalized_prefix = path_prefix.strip().rstrip("/") or "/"
|
||||||
@@ -139,6 +152,7 @@ def build_caddyfile(apps, auth_policies, routes):
|
|||||||
lines.append(" request_header -X-Forwarded-Host")
|
lines.append(" request_header -X-Forwarded-Host")
|
||||||
emit_identity_header_sanitization()
|
emit_identity_header_sanitization()
|
||||||
lines.append("")
|
lines.append("")
|
||||||
|
emit_encoded_slash_block()
|
||||||
emit_auth_portal()
|
emit_auth_portal()
|
||||||
|
|
||||||
for static_route in static_routes:
|
for static_route in static_routes:
|
||||||
|
|||||||
Reference in New Issue
Block a user