Skip to content

Commit 2295b8a

Browse files
authored
Merge pull request #2032 from swagger-api/SWG-9288-utilizing-safe-url-resolver-for-oas-30
SWG-9288 utilizing safe url resolver for OAS 3.0 parsing
2 parents 21cdafd + 224037e commit 2295b8a

File tree

7 files changed

+167
-5
lines changed

7 files changed

+167
-5
lines changed

modules/swagger-parser-v3/src/main/java/io/swagger/v3/parser/ResolverCache.java

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@
1818
import io.swagger.v3.parser.core.models.SwaggerParseResult;
1919
import io.swagger.v3.parser.models.RefFormat;
2020
import io.swagger.v3.parser.models.RefType;
21+
import io.swagger.v3.parser.urlresolver.PermittedUrlsChecker;
22+
import io.swagger.v3.parser.urlresolver.exceptions.HostDeniedException;
2123
import io.swagger.v3.parser.util.DeserializationUtils;
2224
import io.swagger.v3.parser.util.PathUtils;
2325
import io.swagger.v3.parser.util.RefUtils;
@@ -146,6 +148,10 @@ public <T> T loadRef(String ref, RefFormat refFormat, Class<T> expectedType) {
146148
String contents = externalFileCache.get(file);
147149

148150
if (contents == null) {
151+
if(parseOptions.isSafelyResolveURL()){
152+
checkUrlIsPermitted(file);
153+
}
154+
149155
if(parentDirectory != null) {
150156
contents = RefUtils.readExternalRef(file, refFormat, auths, parentDirectory);
151157
}
@@ -374,6 +380,17 @@ private Object getFromMap(String ref, Map map, Pattern pattern) {
374380
return null;
375381
}
376382

383+
protected void checkUrlIsPermitted(String refSet) {
384+
try {
385+
PermittedUrlsChecker permittedUrlsChecker = new PermittedUrlsChecker(parseOptions.getRemoteRefAllowList(),
386+
parseOptions.getRemoteRefBlockList());
387+
388+
permittedUrlsChecker.verify(refSet);
389+
} catch (HostDeniedException e) {
390+
throw new RuntimeException(e.getMessage());
391+
}
392+
}
393+
377394
public boolean hasReferencedKey(String modelKey) {
378395
if(referencedModelKeys == null) {
379396
return false;

modules/swagger-parser-v3/src/test/java/io/swagger/v3/parser/test/OAI31DeserializationTest.java

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1045,7 +1045,7 @@ public void test31SafeURLResolving() {
10451045
parseOptions.setRemoteRefAllowList(allowList);
10461046
parseOptions.setRemoteRefBlockList(blockList);
10471047

1048-
SwaggerParseResult result = new OpenAPIV3Parser().readLocation("3.1.0/resolve/safeResolving/safeUrlResolvingWithPetstore.yaml", null, parseOptions);
1048+
SwaggerParseResult result = new OpenAPIV3Parser().readLocation("safeResolving/oas31SafeUrlResolvingWithPetstore.yaml", null, parseOptions);
10491049
if (result.getMessages() != null) {
10501050
for (String message : result.getMessages()) {
10511051
assertTrue(message.contains("Server returned HTTP response code: 403"));
@@ -1063,7 +1063,7 @@ public void test31SafeURLResolvingWithBlockedURL() {
10631063
parseOptions.setRemoteRefAllowList(allowList);
10641064
parseOptions.setRemoteRefBlockList(blockList);
10651065

1066-
SwaggerParseResult result = new OpenAPIV3Parser().readLocation("3.1.0/resolve/safeResolving/safeUrlResolvingWithPetstore.yaml", null, parseOptions);
1066+
SwaggerParseResult result = new OpenAPIV3Parser().readLocation("safeResolving/oas31SafeUrlResolvingWithPetstore.yaml", null, parseOptions);
10671067

10681068
if (result.getMessages() != null) {
10691069
for (String message : result.getMessages()) {
@@ -1084,7 +1084,7 @@ public void test31SafeURLResolvingWithTurnedOffSafeResolving() {
10841084
parseOptions.setRemoteRefAllowList(allowList);
10851085
parseOptions.setRemoteRefBlockList(blockList);
10861086

1087-
SwaggerParseResult result = new OpenAPIV3Parser().readLocation("3.1.0/resolve/safeResolving/safeUrlResolvingWithPetstore.yaml", null, parseOptions);
1087+
SwaggerParseResult result = new OpenAPIV3Parser().readLocation("safeResolving/oas31SafeUrlResolvingWithPetstore.yaml", null, parseOptions);
10881088
if (result.getMessages() != null) {
10891089
for (String message : result.getMessages()) {
10901090
assertTrue(message.contains("Server returned HTTP response code: 403"));
@@ -1098,7 +1098,7 @@ public void test31SafeURLResolvingWithLocalhostAndBlockedURL() {
10981098
parseOptions.setResolveFully(true);
10991099
parseOptions.setSafelyResolveURL(true);
11001100

1101-
SwaggerParseResult result = new OpenAPIV3Parser().readLocation("3.1.0/resolve/safeResolving/safeUrlResolvingWithLocalhost.yaml", null, parseOptions);
1101+
SwaggerParseResult result = new OpenAPIV3Parser().readLocation("safeResolving/oas31SafeUrlResolvingWithLocalhost.yaml", null, parseOptions);
11021102
if (result.getMessages() != null) {
11031103
for (String message : result.getMessages()) {
11041104
assertTrue(
@@ -1117,7 +1117,7 @@ public void test31SafeURLResolvingWithLocalhost() {
11171117
parseOptions.setRemoteRefBlockList(blockList);
11181118

11191119
String error = "URL is part of the explicit denylist. URL [https://petstore.swagger.io/v2/swagger.json]";
1120-
SwaggerParseResult result = new OpenAPIV3Parser().readLocation("3.1.0/resolve/safeResolving/safeUrlResolvingWithLocalhost.yaml", null, parseOptions);
1120+
SwaggerParseResult result = new OpenAPIV3Parser().readLocation("safeResolving/oas31SafeUrlResolvingWithLocalhost.yaml", null, parseOptions);
11211121

11221122
if (result.getMessages() != null) {
11231123
for (String message : result.getMessages()) {

modules/swagger-parser-v3/src/test/java/io/swagger/v3/parser/test/OpenAPIV3ParserTest.java

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3162,4 +3162,99 @@ public void testIssue_1746_headers_relative_paths() {
31623162
OpenAPI openAPI = parseResult.getOpenAPI();
31633163
assertEquals(openAPI.getPaths().get("/pets").getGet().getResponses().get("200").getHeaders().get("x-next").get$ref(), "#/components/headers/LocationInHeaders");
31643164
}
3165+
3166+
@Test(description = "Test safe resolving")
3167+
public void test31SafeURLResolving() {
3168+
ParseOptions parseOptions = new ParseOptions();
3169+
parseOptions.setResolveFully(true);
3170+
parseOptions.setSafelyResolveURL(true);
3171+
List<String> allowList = Collections.emptyList();
3172+
List<String> blockList = Collections.emptyList();
3173+
parseOptions.setRemoteRefAllowList(allowList);
3174+
parseOptions.setRemoteRefBlockList(blockList);
3175+
3176+
SwaggerParseResult result = new OpenAPIV3Parser().readLocation("safeResolving/oas30SafeUrlResolvingWithPetstore.yaml", null, parseOptions);
3177+
if (result.getMessages() != null) {
3178+
for (String message : result.getMessages()) {
3179+
assertTrue(message.contains("Server returned HTTP response code: 403"));
3180+
}
3181+
}
3182+
}
3183+
3184+
@Test(description = "Test safe resolving with blocked URL")
3185+
public void test31SafeURLResolvingWithBlockedURL() {
3186+
ParseOptions parseOptions = new ParseOptions();
3187+
parseOptions.setResolveFully(true);
3188+
parseOptions.setSafelyResolveURL(true);
3189+
List<String> allowList = Collections.emptyList();
3190+
List<String> blockList = Arrays.asList("petstore3.swagger.io");
3191+
parseOptions.setRemoteRefAllowList(allowList);
3192+
parseOptions.setRemoteRefBlockList(blockList);
3193+
3194+
SwaggerParseResult result = new OpenAPIV3Parser().readLocation("safeResolving/oas30SafeUrlResolvingWithPetstore.yaml", null, parseOptions);
3195+
3196+
if (result.getMessages() != null) {
3197+
for (String message : result.getMessages()) {
3198+
assertTrue(
3199+
message.contains("Server returned HTTP response code: 403") ||
3200+
message.contains("URL is part of the explicit denylist. URL [https://petstore3.swagger.io/api/v3/openapi.json]"));
3201+
}
3202+
}
3203+
}
3204+
3205+
@Test(description = "Test safe resolving with turned off safelyResolveURL option")
3206+
public void test31SafeURLResolvingWithTurnedOffSafeResolving() {
3207+
ParseOptions parseOptions = new ParseOptions();
3208+
parseOptions.setResolveFully(true);
3209+
parseOptions.setSafelyResolveURL(false);
3210+
List<String> allowList = Collections.emptyList();
3211+
List<String> blockList = Arrays.asList("petstore3.swagger.io");
3212+
parseOptions.setRemoteRefAllowList(allowList);
3213+
parseOptions.setRemoteRefBlockList(blockList);
3214+
3215+
SwaggerParseResult result = new OpenAPIV3Parser().readLocation("safeResolving/oas30SafeUrlResolvingWithPetstore.yaml", null, parseOptions);
3216+
if (result.getMessages() != null) {
3217+
for (String message : result.getMessages()) {
3218+
assertTrue(message.contains("Server returned HTTP response code: 403"));
3219+
}
3220+
}
3221+
}
3222+
3223+
@Test(description = "Test safe resolving with localhost and blocked url")
3224+
public void test31SafeURLResolvingWithLocalhostAndBlockedURL() {
3225+
ParseOptions parseOptions = new ParseOptions();
3226+
parseOptions.setResolveFully(true);
3227+
parseOptions.setSafelyResolveURL(true);
3228+
3229+
SwaggerParseResult result = new OpenAPIV3Parser().readLocation("safeResolving/oas30SafeUrlResolvingWithLocalhost.yaml", null, parseOptions);
3230+
if (result.getMessages() != null) {
3231+
for (String message : result.getMessages()) {
3232+
assertTrue(
3233+
message.contains("Server returned HTTP response code: 403") ||
3234+
message.contains("IP is restricted"));
3235+
}
3236+
}
3237+
}
3238+
3239+
@Test(description = "Test safe resolving with localhost url")
3240+
public void test31SafeURLResolvingWithLocalhost() {
3241+
ParseOptions parseOptions = new ParseOptions();
3242+
parseOptions.setResolveFully(true);
3243+
parseOptions.setSafelyResolveURL(true);
3244+
List<String> blockList = Arrays.asList("petstore.swagger.io");
3245+
parseOptions.setRemoteRefBlockList(blockList);
3246+
3247+
String error = "URL is part of the explicit denylist. URL [https://petstore.swagger.io/v2/swagger.json]";
3248+
SwaggerParseResult result = new OpenAPIV3Parser().readLocation("safeResolving/oas30SafeUrlResolvingWithLocalhost.yaml", null, parseOptions);
3249+
3250+
if (result.getMessages() != null) {
3251+
for (String message : result.getMessages()) {
3252+
assertTrue(
3253+
message.contains("Server returned HTTP response code: 403") ||
3254+
message.contains("IP is restricted") ||
3255+
message.contains(error)
3256+
);
3257+
}
3258+
}
3259+
}
31653260
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
openapi: 3.0.0
2+
info:
3+
version: "1.0.0"
4+
title: ssrf-test
5+
paths:
6+
/devices:
7+
get:
8+
operationId: getDevices
9+
responses:
10+
'200':
11+
description: All the devices
12+
content:
13+
application/json:
14+
schema:
15+
$ref: 'http://localhost/example'
16+
/pets:
17+
get:
18+
operationId: getPets
19+
responses:
20+
'200':
21+
description: All the pets
22+
content:
23+
application/json:
24+
schema:
25+
$ref: 'https://petstore.swagger.io/v2/swagger.json'
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
openapi: 3.0.0
2+
info:
3+
version: "1.0.0"
4+
title: ssrf-test
5+
paths:
6+
/devices:
7+
get:
8+
operationId: getDevices
9+
responses:
10+
'200':
11+
description: All the devices
12+
content:
13+
application/json:
14+
schema:
15+
$ref: 'https://petstore3.swagger.io/api/v3/openapi.json'
16+
/pets:
17+
get:
18+
operationId: getPets
19+
responses:
20+
'200':
21+
description: All the pets
22+
content:
23+
application/json:
24+
schema:
25+
$ref: 'https://petstore.swagger.io/v2/swagger.json'

0 commit comments

Comments
 (0)