#include "PathTraversal.h" #include #include bool PathTraversal::InlineHasTraversalPattern(std::string_view path) { if (path == "..") return true; if (path.rfind("../", 0) == 0) return true; if (path.find("/../") != std::string::npos) return true; if (path.size() >= 3 && path.compare(path.size() - 3, 3, "/..") == 0) return true; return false; } std::string PathTraversal::UrlDecode(std::string_view src) { std::string dst; dst.reserve(src.length()); // 预分配内存,优化性能 for (size_t i = 0; i < src.length(); ++i) { // 确保 % 后面至少还有两个字符 if ((src[i] == '%') && ((i + 2) < src.length())) { // 转换为 unsigned char 规避 std::isxdigit 在处理非 ASCII(如UTF-8) 时的未定义行为 unsigned char hi = static_cast(src[i + 1]); unsigned char lo = static_cast(src[i + 2]); if (std::isxdigit(hi) && std::isxdigit(lo)) { auto hexToChar = [] (unsigned char c) -> int { if ((c >= '0') && (c <= '9')) return c - '0'; if ((c >= 'a') && (c <= 'f')) return c - 'a' + 10; if ((c >= 'A') && (c <= 'F')) return c - 'A' + 10; return 0; }; dst += static_cast((hexToChar(hi) << 4) | hexToChar(lo)); i += 2; continue; } } dst += src[i]; } return dst; } bool PathTraversal::HasPathTraversalPattern(std::string_view raw_url_path) { if (raw_url_path.empty()) return false; // 1. 进行 URL 解码,让隐藏的 %2f, %2e 现出原形 std::string decoded = UrlDecode(raw_url_path); // 2. 统一将 Windows 风格的反斜杠 `\` 替换为正斜杠 `/`,防止利用反斜杠绕过 std::replace(decoded.begin(), decoded.end(), '\\', '/'); // 3. 特征级严格匹配: // 路径穿透的本质是形成一个独立的 ".." 路径层级。 // 它只可能以四种形态存在:".."、"../开头的路径"、"/../中间路径"、以及"/.."结尾的路径。 if (decoded == "..") return true; if (decoded.rfind("../", 0) == 0) // 检查是否以 "../" 开头 return true; if (decoded.find("/../") != std::string::npos) // 检查是否包含 "/../" return true; if (decoded.size() >= 3 && decoded.compare(decoded.size() - 3, 3, "/..") == 0) // 检查是否以 "/.." 结尾 return true; return false; } bool PathTraversal::IsSafePath(const std::filesystem::path& base_path, const std::filesystem::path& user_path) { namespace fs = std::filesystem; try { // 1. 规范化沙盒基准路径 fs::path canonical_base = fs::weakly_canonical(base_path); // 2. 拼接并规范化目标路径 // 注意:若 user_path 为绝对路径(如 /etc/passwd),operator/ 会直接覆盖前面的路径, // weakly_canonical 依然能正确将其解析为最终的物理绝对路径。 fs::path canonical_target = fs::weakly_canonical(canonical_base / user_path); // 3. 计算逻辑相对关系 auto rel = canonical_target.lexically_relative(canonical_base); // 4. 边界一票否决制: // - 如果 rel 为空,说明两者不在同一根目录下(例如 Windows 跨盘符 C:\ 到 D:\) // - 如果相对路径的第一个组件是 "..", 说明逻辑路径已经逃逸出根目录 if (rel.empty() || *rel.begin() == "..") return false; return true; } catch (...) { // 捕获任何潜在的系统路径解析异常(如超长路径、非法字符等) return false; } } PathTraversal::UrlSafetyStatus PathTraversal::AnalyzeUrlTraversal(std::string_view raw_url_path) { // 1. 检查原始明文中是否存在穿透特征(统一斜杠处理) std::string raw_normalized_slash(raw_url_path); std::replace(raw_normalized_slash.begin(), raw_normalized_slash.end(), '\\', '/'); bool raw_has_traversal = InlineHasTraversalPattern(raw_normalized_slash); // 2. 进行 URL 解码,让隐藏的 %2f, %2e, %5c 现出原形 std::string decoded_path = UrlDecode(raw_url_path); std::replace(decoded_path.begin(), decoded_path.end(), '\\', '/'); bool decoded_has_traversal = InlineHasTraversalPattern(decoded_path); // 3. 对比前后差异 if (decoded_has_traversal) { if (!raw_has_traversal) // 【核心逻辑】明文看起来很安全,解码后突然蹦出穿透特征 -> 判定为转义绕过攻击 return UrlSafetyStatus::EvasiveTraversal; // 明文和密文都有,属于客户端手荡或老旧 SDK 拼接未规范化 return UrlSafetyStatus::LiteralTraversal; } return UrlSafetyStatus::Safe; } std::string PathTraversal::NormalizeUrlPath(std::string_view decoded_url_path) { if (decoded_url_path.empty()) return "/"; std::vector segments; size_t start = 0; // 逻辑切分路径组件 while (start < decoded_url_path.length()) { size_t end = decoded_url_path.find_first_of("/\\", start); std::string_view segment = decoded_url_path.substr(start, end - start); if (!segment.empty() && segment != ".") { if (segment == "..") { // 遇到 .. 则弹栈,实现路径向上坍缩 if (!segments.empty()) segments.pop_back(); } else segments.push_back(segment); } if (end == std::string_view::npos) break; start = end + 1; } // 重新拼接规范化后的标准 URL 路径 std::string result; for (const auto& seg : segments) { result += "/"; result.append(seg); } return result.empty() ? "/" : result; } std::string PathTraversal::ToString(UrlSafetyStatus s) { switch(s) { case UrlSafetyStatus::Safe: return "Safe"; case UrlSafetyStatus::LiteralTraversal: return "LiteralTraversal"; case UrlSafetyStatus::EvasiveTraversal: return "EvasiveTraversal"; default: return "Unknown??"; } return "Unknown??"; }