Skip to content

Commit

Permalink
Merge pull request #2032 from swagger-api/SWG-9288-utilizing-safe-url…
Browse files Browse the repository at this point in the history
…-resolver-for-oas-30

SWG-9288 utilizing safe url resolver for OAS 3.0 parsing
  • Loading branch information
MiloszTarka authored Jan 11, 2024
2 parents 21cdafd + 224037e commit 2295b8a
Show file tree
Hide file tree
Showing 7 changed files with 167 additions and 5 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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);
}
Expand Down Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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"));
Expand All @@ -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()) {
Expand All @@ -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"));
Expand All @@ -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(
Expand All @@ -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()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
);
}
}
}
}
Original file line number Diff line number Diff line change
@@ -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'
Original file line number Diff line number Diff line change
@@ -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'

0 comments on commit 2295b8a

Please sign in to comment.