diff --git a/public_html/_incl/tools.auth.php b/public_html/_incl/tools.auth.php index 58977ec..e69d494 100644 --- a/public_html/_incl/tools.auth.php +++ b/public_html/_incl/tools.auth.php @@ -8,12 +8,17 @@ $ua = $_SERVER['HTTP_USER_AGENT']; if (str_starts_with($ua, "Axia4Auth/")) { $username = explode("/", $ua)[1]; $userpass = explode("/", $ua)[2]; - $userdata = json_decode(file_get_contents("/DATA/Usuarios/" . Sf($username) . ".json"), true); + $user_filename = safe_username_to_filename($username); + if ($user_filename === "") { + header("HTTP/1.1 403 Forbidden"); + die(); + } + $userdata = json_decode(file_get_contents("/DATA/Usuarios/" . $user_filename . ".json"), true); if (!$userdata) { header("HTTP/1.1 403 Forbidden"); die(); } - if (password_verify($userpass, $userdata["password"])) { + if (!password_verify($userpass, $userdata["password_hash"])) { header("HTTP/1.1 403 Forbidden"); die(); } @@ -30,11 +35,14 @@ if ($_SESSION["auth_ok"] != true && isset($_COOKIE["auth_user"]) && isset($_COOK $username = $_COOKIE["auth_user"]; $userpass_b64 = $_COOKIE["auth_pass_b64"]; $userpass = base64_decode($userpass_b64); - $userdata = json_decode(file_get_contents("/DATA/Usuarios/" . Sf($username) . ".json"), true); - if ($userdata && password_verify($userpass, $userdata["password_hash"])) { - $_SESSION["auth_user"] = $username; - $_SESSION["auth_data"] = $userdata; - $_SESSION["auth_ok"] = true; + $user_filename = safe_username_to_filename($username); + if ($user_filename !== "") { + $userdata = json_decode(file_get_contents("/DATA/Usuarios/" . $user_filename . ".json"), true); + if ($userdata && password_verify($userpass, $userdata["password_hash"])) { + $_SESSION["auth_user"] = $username; + $_SESSION["auth_data"] = $userdata; + $_SESSION["auth_ok"] = true; + } } } @@ -42,8 +50,11 @@ if ($_SESSION["auth_ok"] != true && isset($_COOKIE["auth_user"]) && isset($_COOK if (isset($_SESSION["auth_ok"]) && $_SESSION["auth_ok"] && isset($_SESSION["auth_user"])) { if (isset($AuthConfig["session_load_mode"]) && $AuthConfig["session_load_mode"] === "force") { $username = $_SESSION["auth_user"]; - $userdata = json_decode(file_get_contents("/DATA/Usuarios/" . Sf($username) . ".json"), true); - $_SESSION["auth_data"] = $userdata; + $user_filename = safe_username_to_filename($username); + if ($user_filename !== "") { + $userdata = json_decode(file_get_contents("/DATA/Usuarios/" . $user_filename . ".json"), true); + $_SESSION["auth_data"] = $userdata; + } $_SESSION["last_reload_time"] = time(); } elseif (isset($AuthConfig["session_load_mode"]) && $AuthConfig["session_load_mode"] === "never") { // Do nothing, never reload session data @@ -52,8 +63,11 @@ if (isset($_SESSION["auth_ok"]) && $_SESSION["auth_ok"] && isset($_SESSION["auth $last_reload = $_SESSION["last_reload_time"]; if (time() - $last_reload > 300) { $username = $_SESSION["auth_user"]; - $userdata = json_decode(file_get_contents("/DATA/Usuarios/" . Sf($username) . ".json"), true); - $_SESSION["auth_data"] = $userdata; + $user_filename = safe_username_to_filename($username); + if ($user_filename !== "") { + $userdata = json_decode(file_get_contents("/DATA/Usuarios/" . $user_filename . ".json"), true); + $_SESSION["auth_data"] = $userdata; + } $_SESSION["last_reload_time"] = time(); } } else { diff --git a/public_html/_incl/tools.security.php b/public_html/_incl/tools.security.php index 3b0a3c5..d25061d 100644 --- a/public_html/_incl/tools.security.php +++ b/public_html/_incl/tools.security.php @@ -58,6 +58,32 @@ function Si($input) { return $input; } +function safe_username_to_filename($username) { + /** + * Convert a username (plain username or email) to a safe filename for use in file operations. + * + * Email addresses have @ replaced with __ to match how Google OAuth users are stored. + * The result contains only alphanumeric characters, dots, underscores, and hyphens. + * + * @param string $username The username or email to convert. + * @return string The safe filename (without path or extension), or "" if invalid. + */ + $filename = strtolower((string)$username); + // Remove null bytes + $filename = str_replace("\0", "", $filename); + // Replace @ with __ (to match Google OAuth file naming) + $filename = str_replace("@", "__", $filename); + // Remove any path components to prevent directory traversal + $filename = basename($filename); + // Remove .. sequences + $filename = str_replace("..", "", $filename); + // Keep only alphanumeric, dot, underscore, hyphen + $filename = preg_replace("/[^a-zA-Z0-9._-]/", "_", $filename); + // Trim dots and underscores from ends + $filename = trim($filename, "._"); + return $filename; +} + function Sb($input) { /** * Sanitize a boolean input by converting it to a boolean value. diff --git a/public_html/_incl/tools.session.php b/public_html/_incl/tools.session.php index 62c8a32..cb4928d 100644 --- a/public_html/_incl/tools.session.php +++ b/public_html/_incl/tools.session.php @@ -1,5 +1,4 @@ 604800 ]); -session_regenerate_id(); ini_set("session.use_only_cookies", "true"); ini_set("session.use_trans_sid", "false"); diff --git a/public_html/_login.php b/public_html/_login.php index ca6b678..c459471 100755 --- a/public_html/_login.php +++ b/public_html/_login.php @@ -5,11 +5,30 @@ if (!isset($AuthConfig)) { $AuthConfig = json_decode(file_get_contents("/DATA/AuthConfig.json"), true); } $DOMAIN = $_SERVER["HTTP_X_FORWARDED_HOST"] ?? $_SERVER["HTTP_HOST"]; + +/** + * Return a safe redirect URL: only allow relative paths starting with a single slash. + * Falls back to "/" for any external, protocol-relative, or otherwise unsafe URLs. + */ +function safe_redir($url) { + $url = (string)$url; + // Must start with a single "/" but not "//" (protocol-relative) + if (preg_match('#^/[^/]#', $url) || $url === '/') { + // Strip newlines to prevent header injection + return preg_replace('/[\r\n]/', '', $url); + } + return '/'; +} + if ($_GET["reload_user"] == "1") { - $user = str_replace("@", "__", $_SESSION["auth_user"]); - $userdata = json_decode(file_get_contents("/DATA/Usuarios/" . Sf($user) . ".json"), true); + $user_filename = safe_username_to_filename($_SESSION["auth_user"] ?? ""); + if ($user_filename === "") { + header("Location: /"); + die(); + } + $userdata = json_decode(file_get_contents("/DATA/Usuarios/" . $user_filename . ".json"), true); $_SESSION['auth_data'] = $userdata; - $redir = $_GET["redir"] ?? "/"; + $redir = safe_redir($_GET["redir"] ?? "/"); header("Location: $redir"); die(); } @@ -20,6 +39,15 @@ if ($_GET["google_callback"] == "1") { if (!isset($_GET["code"])) { die("Error: No se recibió el código de autorización de Google."); } + + // Validate CSRF nonce from state parameter + $state_raw = $_GET["state"] ?? ""; + $state = json_decode(base64_decode($state_raw), true); + $state_nonce = $state["nonce"] ?? ""; + if (!$state_nonce || !isset($_SESSION["oauth_nonce"]) || !hash_equals($_SESSION["oauth_nonce"], $state_nonce)) { + die("Error: Estado OAuth inválido. Por favor, inténtalo de nuevo."); + } + unset($_SESSION["oauth_nonce"]); $code = $_GET["code"]; @@ -56,7 +84,11 @@ if ($_GET["google_callback"] == "1") { $email = $user_info["email"]; $name = $user_info["name"] ?? explode("@", $email)[0]; - $userfile = "/DATA/Usuarios/" . Sf(strtolower(str_replace("@", "__", $email))) . ".json"; + $user_filename = safe_username_to_filename($email); + if ($user_filename === "") { + die("Error: Dirección de correo inválida."); + } + $userfile = "/DATA/Usuarios/" . $user_filename . ".json"; $password = bin2hex(random_bytes(16)); // Generar una contraseña aleatoria para el usuario, aunque no se usará para iniciar sesión if (file_exists($userfile)) { $userdata = json_decode(file_get_contents($userfile), true); @@ -72,13 +104,15 @@ if ($_GET["google_callback"] == "1") { file_put_contents($userfile, json_encode($userdata)); } + session_regenerate_id(true); $_SESSION['auth_user'] = $email; $_SESSION['auth_data'] = $userdata; $_SESSION['auth_ok'] = true; - setcookie("auth_user", $email, time() + (86400 * 30), "/"); - setcookie("auth_pass_b64", base64_encode($password), time() + (86400 * 30), "/"); + $cookie_options = ["expires" => time() + (86400 * 30), "path" => "/", "httponly" => true, "secure" => true, "samesite" => "Lax"]; + setcookie("auth_user", $email, $cookie_options); + setcookie("auth_pass_b64", base64_encode($password), $cookie_options); - $redir = json_decode(base64_decode($_GET["state"]), true)["redir"] ?? "/"; + $redir = safe_redir($state["redir"] ?? "/"); header("Location: $redir"); die(); @@ -89,6 +123,10 @@ if ($_GET["google"] == "1") { } $url = "https://accounts.google.com/o/oauth2/auth"; + // Generate a CSRF nonce and store it in the session + $oauth_nonce = bin2hex(random_bytes(16)); + $_SESSION["oauth_nonce"] = $oauth_nonce; + // build the HTTP GET query $params = array( "response_type" => "code", @@ -96,7 +134,8 @@ if ($_GET["google"] == "1") { "redirect_uri" => "https://$DOMAIN/_login.php?google_callback=1", "scope" => "email openid profile", "state" => base64_encode(json_encode([ - "redir" => $_GET["redir"] ?? "/" + "redir" => safe_redir($_GET["redir"] ?? "/"), + "nonce" => $oauth_nonce ])) ); @@ -107,16 +146,17 @@ if ($_GET["google"] == "1") { die(); } if ($_GET["logout"] == "1") { - $redir = $_GET["redir"] ?? "/"; - setcookie("auth_user", "", time() - 3600, "/"); - setcookie("auth_pass_b64", "", time() - 3600, "/"); + $redir = safe_redir($_GET["redir"] ?? "/"); + $cookie_options_expired = ["expires" => time() - 3600, "path" => "/", "httponly" => true, "secure" => true, "samesite" => "Lax"]; + setcookie("auth_user", "", $cookie_options_expired); + setcookie("auth_pass_b64", "", $cookie_options_expired); session_destroy(); header("Location: $redir"); die(); } if ($_GET["clear_session"] == "1") { session_destroy(); - $redir = $_GET["redir"] ?? "/"; + $redir = safe_redir($_GET["redir"] ?? "/"); header("Location: $redir"); die(); } @@ -124,19 +164,22 @@ if (isset($_POST["user"])) { $valid = ""; $user = trim(strtolower($_POST["user"])); $password = $_POST["password"]; - $userdata = json_decode(file_get_contents("/DATA/Usuarios/" . Sf($user) . ".json"), true); + $user_filename = safe_username_to_filename($user); + $userdata = ($user_filename !== "") ? json_decode(@file_get_contents("/DATA/Usuarios/" . $user_filename . ".json"), true) : null; if (!isset($userdata["password_hash"])) { $_GET["_result"] = "El usuario no existe."; } - $hash = $userdata["password_hash"]; - if (password_verify($password, $hash)) { + $hash = $userdata["password_hash"] ?? null; + if ($hash && password_verify($password, $hash)) { + session_regenerate_id(true); $_SESSION['auth_user'] = $user; $_SESSION['auth_data'] = $userdata; $_SESSION['auth_ok'] = true; - setcookie("auth_user", $user, time() + (86400 * 30), "/"); - setcookie("auth_pass_b64", base64_encode($password), time() + (86400 * 30), "/"); - $redir = $_GET["redir"] ?? "/"; + $cookie_options = ["expires" => time() + (86400 * 30), "path" => "/", "httponly" => true, "secure" => true, "samesite" => "Lax"]; + setcookie("auth_user", $user, $cookie_options); + setcookie("auth_pass_b64", base64_encode($password), $cookie_options); + $redir = safe_redir($_GET["redir"] ?? "/"); header("Location: $redir"); die(); } else { diff --git a/public_html/entreaulas/_filefetch.php b/public_html/entreaulas/_filefetch.php index c3df766..e181d88 100755 --- a/public_html/entreaulas/_filefetch.php +++ b/public_html/entreaulas/_filefetch.php @@ -1,8 +1,11 @@