如何合规Sanitize SOQL返回URL以消除Snyk的SSRF漏洞告警(无法硬编码)
我有如下APEX代码:
MyType mdt = [ SELECT Token_Endpoint__c FROM Google_OAuth2__mdt LIMIT 1 ]; String tokenEndpoint = mdt.Token_Endpoint__c; HttpRequest req = new HttpRequest(); req.setEndpoint(tokenEndpoint);
Snyk代码检测指出req.setEndpoint调用存在**服务器端请求伪造(SSRF)**漏洞,该检测结果合理,但我尝试的白名单方案都无法绕过告警:
Snyk告警内容:
Unsanitized input from a SOQL statement flows into setendpoint, where it is used as an URL to perform a request. This may result in a Server-Side Request Forgery vulnerability.
✗ [High] Server-Side Request Forgery (SSRF)
我试过两种白名单方式,均触发告警(注:原代码存在逻辑错误,校验时未使用元数据获取的tokenEndpoint,而是直接写死了白名单值,即使修复后仍无法绕过Snyk检测):
- 白名单Set校验:
String tokenEndpoint = mdt.Token_Endpoint__c; Set<String> allowList = new Set<String>{'https://foo/...'}; if (!allowList.contains(tokenEndpoint)) { throw new IllegalArgumentException('Invalid endpoint'); } HttpRequest req = new HttpRequest(); req.setEndpoint(tokenEndpoint);
- Map映射校验:
String tokenEndpoint = mdt.Token_Endpoint__c; Map<String, String> allowList = new Map<String, String>{{'https://foo/...', 'https://foo/...'}}; if (!allowList.containsKey(tokenEndpoint)) { throw new IllegalArgumentException('Invalid endpoint'); } HttpRequest req = new HttpRequest(); req.setEndpoint(allowList.get(tokenEndpoint));
目前只有硬编码URL能让Snyk认可安全性,但这不符合我通过自定义元数据管理配置的需求,需要可行的解决方案。
可行的解决方案
方案1:自定义元数据存标识,代码映射到硬编码URL
这种方式既保留配置灵活性(元数据控制启用哪个端点),又让Snyk识别到实际使用的是硬编码的安全URL:
MyType mdt = [ SELECT Endpoint_Identifier__c FROM Google_OAuth2__mdt LIMIT 1 ]; // 硬编码合法URL映射 Map<String, String> endpointMap = new Map<String, String>{ 'GOOGLE_TOKEN' => 'https://oauth2.googleapis.com/token', 'FOO_SERVICE' => 'https://foo/...' }; String validEndpoint = endpointMap.get(mdt.Endpoint_Identifier__c); if (validEndpoint == null) { throw new IllegalArgumentException('Invalid endpoint identifier'); } HttpRequest req = new HttpRequest(); req.setEndpoint(validEndpoint);
原理:Snyk会追踪到validEndpoint来自硬编码的Map,而非直接来自SOQL查询的用户可控输入,因此不会触发SSRF告警。
方案2:严格解析并校验URL的核心部分
通过URL类解析元数据中的URL,校验协议、域名等关键部分,然后重构安全的URL字符串:
MyType mdt = [ SELECT Token_Endpoint__c FROM Google_OAuth2__mdt LIMIT 1 ]; String rawEndpoint = mdt.Token_Endpoint__c; URL parsedUrl; try { parsedUrl = new URL(rawEndpoint); } catch (MalformedURLException e) { throw new IllegalArgumentException('Invalid URL format'); } // 白名单校验:仅允许HTTPS协议,且域名在合法列表内 Set<String> allowedDomains = new Set<String>{'oauth2.googleapis.com', 'foo.com'}; if (!parsedUrl.getProtocol().equals('https') || !allowedDomains.contains(parsedUrl.getHost())) { throw new IllegalArgumentException('Unauthorized endpoint'); } // 使用解析后的安全部分重构URL(避免原字符串中的恶意参数) String safeEndpoint = parsedUrl.getProtocol() + '://' + parsedUrl.getHost() + parsedUrl.getPath(); HttpRequest req = new HttpRequest(); req.setEndpoint(safeEndpoint);
原理:Snyk能识别到代码对URL做了严格的解析和校验,仅保留安全的协议、域名和路径,阻断了恶意输入的可能性。
方案3:使用枚举类管理合法端点
定义枚举类存储所有合法URL,元数据存储枚举值的名称,代码中转换为枚举对应的URL:
public enum ValidEndpoints { GOOGLE_TOKEN('https://oauth2.googleapis.com/token'), FOO_SERVICE('https://foo/...'); public final String url; ValidEndpoints(String url) { this.url = url; } } // 获取元数据配置 MyType mdt = [ SELECT Endpoint_Name__c FROM Google_OAuth2__mdt LIMIT 1 ]; ValidEndpoints selectedEndpoint; try { selectedEndpoint = ValidEndpoints.valueOf(mdt.Endpoint_Name__c); } catch (IllegalArgumentException e) { throw new IllegalArgumentException('Invalid endpoint name'); } HttpRequest req = new HttpRequest(); req.setEndpoint(selectedEndpoint.url);
原理:枚举类的URL是硬编码的,Snyk会认为这是安全的输入,同时元数据仅用于选择枚举值,不会引入可控风险。
内容的提问来源于stack exchange,提问作者clumsyjedi

