From 224037e33ed65d423324f366a060181f61b958e6 Mon Sep 17 00:00:00 2001 From: Milosz Tarka <milosz.tarka.it@gmail.com> Date: Wed, 3 Jan 2024 15:21:04 +0100 Subject: [PATCH] SWG-9288 utilizing safe url resolver for OAS 3.0 parsing --- .../io/swagger/v3/parser/ResolverCache.java | 17 ++++ .../parser/test/OAI31DeserializationTest.java | 10 +- .../v3/parser/test/OpenAPIV3ParserTest.java | 95 +++++++++++++++++++ .../oas30SafeUrlResolvingWithLocalhost.yaml | 25 +++++ .../oas30SafeUrlResolvingWithPetstore.yaml | 25 +++++ .../oas31SafeUrlResolvingWithLocalhost.yaml} | 0 .../oas31SafeUrlResolvingWithPetstore.yaml} | 0 7 files changed, 167 insertions(+), 5 deletions(-) create mode 100644 modules/swagger-parser-v3/src/test/resources/safeResolving/oas30SafeUrlResolvingWithLocalhost.yaml create mode 100644 modules/swagger-parser-v3/src/test/resources/safeResolving/oas30SafeUrlResolvingWithPetstore.yaml rename modules/swagger-parser-v3/src/test/resources/{3.1.0/resolve/safeResolving/safeUrlResolvingWithLocalhost.yaml => safeResolving/oas31SafeUrlResolvingWithLocalhost.yaml} (100%) rename modules/swagger-parser-v3/src/test/resources/{3.1.0/resolve/safeResolving/safeUrlResolvingWithPetstore.yaml => safeResolving/oas31SafeUrlResolvingWithPetstore.yaml} (100%) diff --git a/modules/swagger-parser-v3/src/main/java/io/swagger/v3/parser/ResolverCache.java b/modules/swagger-parser-v3/src/main/java/io/swagger/v3/parser/ResolverCache.java index 382ede57be..73cfb051c6 100644 --- a/modules/swagger-parser-v3/src/main/java/io/swagger/v3/parser/ResolverCache.java +++ b/modules/swagger-parser-v3/src/main/java/io/swagger/v3/parser/ResolverCache.java @@ -18,6 +18,8 @@ import io.swagger.v3.parser.core.models.SwaggerParseResult; import io.swagger.v3.parser.models.RefFormat; import io.swagger.v3.parser.models.RefType; +import io.swagger.v3.parser.urlresolver.PermittedUrlsChecker; +import io.swagger.v3.parser.urlresolver.exceptions.HostDeniedException; import io.swagger.v3.parser.util.DeserializationUtils; import io.swagger.v3.parser.util.PathUtils; import io.swagger.v3.parser.util.RefUtils; @@ -146,6 +148,10 @@ public <T> T loadRef(String ref, RefFormat refFormat, Class<T> expectedType) { String contents = externalFileCache.get(file); if (contents == null) { + if(parseOptions.isSafelyResolveURL()){ + checkUrlIsPermitted(file); + } + if(parentDirectory != null) { contents = RefUtils.readExternalRef(file, refFormat, auths, parentDirectory); } @@ -374,6 +380,17 @@ private Object getFromMap(String ref, Map map, Pattern pattern) { return null; } + protected void checkUrlIsPermitted(String refSet) { + try { + PermittedUrlsChecker permittedUrlsChecker = new PermittedUrlsChecker(parseOptions.getRemoteRefAllowList(), + parseOptions.getRemoteRefBlockList()); + + permittedUrlsChecker.verify(refSet); + } catch (HostDeniedException e) { + throw new RuntimeException(e.getMessage()); + } + } + public boolean hasReferencedKey(String modelKey) { if(referencedModelKeys == null) { return false; diff --git a/modules/swagger-parser-v3/src/test/java/io/swagger/v3/parser/test/OAI31DeserializationTest.java b/modules/swagger-parser-v3/src/test/java/io/swagger/v3/parser/test/OAI31DeserializationTest.java index ff515a16b3..91c14ec031 100644 --- a/modules/swagger-parser-v3/src/test/java/io/swagger/v3/parser/test/OAI31DeserializationTest.java +++ b/modules/swagger-parser-v3/src/test/java/io/swagger/v3/parser/test/OAI31DeserializationTest.java @@ -1045,7 +1045,7 @@ public void test31SafeURLResolving() { parseOptions.setRemoteRefAllowList(allowList); parseOptions.setRemoteRefBlockList(blockList); - SwaggerParseResult result = new OpenAPIV3Parser().readLocation("3.1.0/resolve/safeResolving/safeUrlResolvingWithPetstore.yaml", null, parseOptions); + SwaggerParseResult result = new OpenAPIV3Parser().readLocation("safeResolving/oas31SafeUrlResolvingWithPetstore.yaml", null, parseOptions); if (result.getMessages() != null) { for (String message : result.getMessages()) { assertTrue(message.contains("Server returned HTTP response code: 403")); @@ -1063,7 +1063,7 @@ public void test31SafeURLResolvingWithBlockedURL() { parseOptions.setRemoteRefAllowList(allowList); parseOptions.setRemoteRefBlockList(blockList); - SwaggerParseResult result = new OpenAPIV3Parser().readLocation("3.1.0/resolve/safeResolving/safeUrlResolvingWithPetstore.yaml", null, parseOptions); + SwaggerParseResult result = new OpenAPIV3Parser().readLocation("safeResolving/oas31SafeUrlResolvingWithPetstore.yaml", null, parseOptions); if (result.getMessages() != null) { for (String message : result.getMessages()) { @@ -1084,7 +1084,7 @@ public void test31SafeURLResolvingWithTurnedOffSafeResolving() { parseOptions.setRemoteRefAllowList(allowList); parseOptions.setRemoteRefBlockList(blockList); - SwaggerParseResult result = new OpenAPIV3Parser().readLocation("3.1.0/resolve/safeResolving/safeUrlResolvingWithPetstore.yaml", null, parseOptions); + SwaggerParseResult result = new OpenAPIV3Parser().readLocation("safeResolving/oas31SafeUrlResolvingWithPetstore.yaml", null, parseOptions); if (result.getMessages() != null) { for (String message : result.getMessages()) { assertTrue(message.contains("Server returned HTTP response code: 403")); @@ -1098,7 +1098,7 @@ public void test31SafeURLResolvingWithLocalhostAndBlockedURL() { parseOptions.setResolveFully(true); parseOptions.setSafelyResolveURL(true); - SwaggerParseResult result = new OpenAPIV3Parser().readLocation("3.1.0/resolve/safeResolving/safeUrlResolvingWithLocalhost.yaml", null, parseOptions); + SwaggerParseResult result = new OpenAPIV3Parser().readLocation("safeResolving/oas31SafeUrlResolvingWithLocalhost.yaml", null, parseOptions); if (result.getMessages() != null) { for (String message : result.getMessages()) { assertTrue( @@ -1117,7 +1117,7 @@ public void test31SafeURLResolvingWithLocalhost() { parseOptions.setRemoteRefBlockList(blockList); String error = "URL is part of the explicit denylist. URL [https://petstore.swagger.io/v2/swagger.json]"; - SwaggerParseResult result = new OpenAPIV3Parser().readLocation("3.1.0/resolve/safeResolving/safeUrlResolvingWithLocalhost.yaml", null, parseOptions); + SwaggerParseResult result = new OpenAPIV3Parser().readLocation("safeResolving/oas31SafeUrlResolvingWithLocalhost.yaml", null, parseOptions); if (result.getMessages() != null) { for (String message : result.getMessages()) { diff --git a/modules/swagger-parser-v3/src/test/java/io/swagger/v3/parser/test/OpenAPIV3ParserTest.java b/modules/swagger-parser-v3/src/test/java/io/swagger/v3/parser/test/OpenAPIV3ParserTest.java index 8f4141292f..01f69e1b0c 100644 --- a/modules/swagger-parser-v3/src/test/java/io/swagger/v3/parser/test/OpenAPIV3ParserTest.java +++ b/modules/swagger-parser-v3/src/test/java/io/swagger/v3/parser/test/OpenAPIV3ParserTest.java @@ -3162,4 +3162,99 @@ public void testIssue_1746_headers_relative_paths() { OpenAPI openAPI = parseResult.getOpenAPI(); assertEquals(openAPI.getPaths().get("/pets").getGet().getResponses().get("200").getHeaders().get("x-next").get$ref(), "#/components/headers/LocationInHeaders"); } + + @Test(description = "Test safe resolving") + public void test31SafeURLResolving() { + ParseOptions parseOptions = new ParseOptions(); + parseOptions.setResolveFully(true); + parseOptions.setSafelyResolveURL(true); + List<String> allowList = Collections.emptyList(); + List<String> blockList = Collections.emptyList(); + parseOptions.setRemoteRefAllowList(allowList); + parseOptions.setRemoteRefBlockList(blockList); + + SwaggerParseResult result = new OpenAPIV3Parser().readLocation("safeResolving/oas30SafeUrlResolvingWithPetstore.yaml", null, parseOptions); + if (result.getMessages() != null) { + for (String message : result.getMessages()) { + assertTrue(message.contains("Server returned HTTP response code: 403")); + } + } + } + + @Test(description = "Test safe resolving with blocked URL") + public void test31SafeURLResolvingWithBlockedURL() { + ParseOptions parseOptions = new ParseOptions(); + parseOptions.setResolveFully(true); + parseOptions.setSafelyResolveURL(true); + List<String> allowList = Collections.emptyList(); + List<String> blockList = Arrays.asList("petstore3.swagger.io"); + parseOptions.setRemoteRefAllowList(allowList); + parseOptions.setRemoteRefBlockList(blockList); + + SwaggerParseResult result = new OpenAPIV3Parser().readLocation("safeResolving/oas30SafeUrlResolvingWithPetstore.yaml", null, parseOptions); + + if (result.getMessages() != null) { + for (String message : result.getMessages()) { + assertTrue( + message.contains("Server returned HTTP response code: 403") || + message.contains("URL is part of the explicit denylist. URL [https://petstore3.swagger.io/api/v3/openapi.json]")); + } + } + } + + @Test(description = "Test safe resolving with turned off safelyResolveURL option") + public void test31SafeURLResolvingWithTurnedOffSafeResolving() { + ParseOptions parseOptions = new ParseOptions(); + parseOptions.setResolveFully(true); + parseOptions.setSafelyResolveURL(false); + List<String> allowList = Collections.emptyList(); + List<String> blockList = Arrays.asList("petstore3.swagger.io"); + parseOptions.setRemoteRefAllowList(allowList); + parseOptions.setRemoteRefBlockList(blockList); + + SwaggerParseResult result = new OpenAPIV3Parser().readLocation("safeResolving/oas30SafeUrlResolvingWithPetstore.yaml", null, parseOptions); + if (result.getMessages() != null) { + for (String message : result.getMessages()) { + assertTrue(message.contains("Server returned HTTP response code: 403")); + } + } + } + + @Test(description = "Test safe resolving with localhost and blocked url") + public void test31SafeURLResolvingWithLocalhostAndBlockedURL() { + ParseOptions parseOptions = new ParseOptions(); + parseOptions.setResolveFully(true); + parseOptions.setSafelyResolveURL(true); + + SwaggerParseResult result = new OpenAPIV3Parser().readLocation("safeResolving/oas30SafeUrlResolvingWithLocalhost.yaml", null, parseOptions); + if (result.getMessages() != null) { + for (String message : result.getMessages()) { + assertTrue( + message.contains("Server returned HTTP response code: 403") || + message.contains("IP is restricted")); + } + } + } + + @Test(description = "Test safe resolving with localhost url") + public void test31SafeURLResolvingWithLocalhost() { + ParseOptions parseOptions = new ParseOptions(); + parseOptions.setResolveFully(true); + parseOptions.setSafelyResolveURL(true); + List<String> blockList = Arrays.asList("petstore.swagger.io"); + parseOptions.setRemoteRefBlockList(blockList); + + String error = "URL is part of the explicit denylist. URL [https://petstore.swagger.io/v2/swagger.json]"; + SwaggerParseResult result = new OpenAPIV3Parser().readLocation("safeResolving/oas30SafeUrlResolvingWithLocalhost.yaml", null, parseOptions); + + if (result.getMessages() != null) { + for (String message : result.getMessages()) { + assertTrue( + message.contains("Server returned HTTP response code: 403") || + message.contains("IP is restricted") || + message.contains(error) + ); + } + } + } } \ No newline at end of file diff --git a/modules/swagger-parser-v3/src/test/resources/safeResolving/oas30SafeUrlResolvingWithLocalhost.yaml b/modules/swagger-parser-v3/src/test/resources/safeResolving/oas30SafeUrlResolvingWithLocalhost.yaml new file mode 100644 index 0000000000..3c217a5072 --- /dev/null +++ b/modules/swagger-parser-v3/src/test/resources/safeResolving/oas30SafeUrlResolvingWithLocalhost.yaml @@ -0,0 +1,25 @@ +openapi: 3.0.0 +info: + version: "1.0.0" + title: ssrf-test +paths: + /devices: + get: + operationId: getDevices + responses: + '200': + description: All the devices + content: + application/json: + schema: + $ref: 'http://localhost/example' + /pets: + get: + operationId: getPets + responses: + '200': + description: All the pets + content: + application/json: + schema: + $ref: 'https://petstore.swagger.io/v2/swagger.json' \ No newline at end of file diff --git a/modules/swagger-parser-v3/src/test/resources/safeResolving/oas30SafeUrlResolvingWithPetstore.yaml b/modules/swagger-parser-v3/src/test/resources/safeResolving/oas30SafeUrlResolvingWithPetstore.yaml new file mode 100644 index 0000000000..03c4fc33db --- /dev/null +++ b/modules/swagger-parser-v3/src/test/resources/safeResolving/oas30SafeUrlResolvingWithPetstore.yaml @@ -0,0 +1,25 @@ +openapi: 3.0.0 +info: + version: "1.0.0" + title: ssrf-test +paths: + /devices: + get: + operationId: getDevices + responses: + '200': + description: All the devices + content: + application/json: + schema: + $ref: 'https://petstore3.swagger.io/api/v3/openapi.json' + /pets: + get: + operationId: getPets + responses: + '200': + description: All the pets + content: + application/json: + schema: + $ref: 'https://petstore.swagger.io/v2/swagger.json' diff --git a/modules/swagger-parser-v3/src/test/resources/3.1.0/resolve/safeResolving/safeUrlResolvingWithLocalhost.yaml b/modules/swagger-parser-v3/src/test/resources/safeResolving/oas31SafeUrlResolvingWithLocalhost.yaml similarity index 100% rename from modules/swagger-parser-v3/src/test/resources/3.1.0/resolve/safeResolving/safeUrlResolvingWithLocalhost.yaml rename to modules/swagger-parser-v3/src/test/resources/safeResolving/oas31SafeUrlResolvingWithLocalhost.yaml diff --git a/modules/swagger-parser-v3/src/test/resources/3.1.0/resolve/safeResolving/safeUrlResolvingWithPetstore.yaml b/modules/swagger-parser-v3/src/test/resources/safeResolving/oas31SafeUrlResolvingWithPetstore.yaml similarity index 100% rename from modules/swagger-parser-v3/src/test/resources/3.1.0/resolve/safeResolving/safeUrlResolvingWithPetstore.yaml rename to modules/swagger-parser-v3/src/test/resources/safeResolving/oas31SafeUrlResolvingWithPetstore.yaml