This commit is contained in:
UnknownObject
2026-06-30 17:49:51 +08:00
commit 292c09619e
200 changed files with 23718 additions and 0 deletions
+175
View File
@@ -0,0 +1,175 @@
#include "PathTraversal.h"
#include <vector>
#include <algorithm>
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<unsigned char>(src[i + 1]);
unsigned char lo = static_cast<unsigned char>(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<char>((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<std::string_view> 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??";
}