Skip to content

Commit 5dc2aae

Browse files
fix: disallow SSRF via remote $ref (#2224)
1 parent 4040365 commit 5dc2aae

File tree

15 files changed

+634
-611
lines changed

15 files changed

+634
-611
lines changed

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

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,9 @@
2121
import io.swagger.v3.parser.urlresolver.PermittedUrlsChecker;
2222
import io.swagger.v3.parser.urlresolver.exceptions.HostDeniedException;
2323
import io.swagger.v3.parser.util.DeserializationUtils;
24+
import io.swagger.v3.parser.util.OpenAPIDeserializer;
2425
import io.swagger.v3.parser.util.PathUtils;
2526
import io.swagger.v3.parser.util.RefUtils;
26-
import io.swagger.v3.parser.util.OpenAPIDeserializer;
2727
import org.apache.commons.lang3.StringUtils;
2828

2929
import java.io.File;
@@ -72,6 +72,7 @@ public class ResolverCache {
7272
private Set<String> resolveValidationMessages;
7373
private final ParseOptions parseOptions;
7474
protected boolean openapi31;
75+
private final PermittedUrlsChecker permittedUrlsChecker;
7576

7677
/*
7778
* a map that stores original external references, and their associated renamed
@@ -94,6 +95,7 @@ public ResolverCache(OpenAPI openApi, List<AuthorizationValue> auths, String par
9495
this.rootPath = parentFileLocation;
9596
this.resolveValidationMessages = resolveValidationMessages;
9697
this.parseOptions = parseOptions;
98+
this.permittedUrlsChecker = new PermittedUrlsChecker(parseOptions.getRemoteRefAllowList(), parseOptions.getRemoteRefBlockList());
9799

98100
if(parentFileLocation != null) {
99101
if(parentFileLocation.startsWith("http") || parentFileLocation.startsWith("jar")) {
@@ -153,13 +155,13 @@ public <T> T loadRef(String ref, RefFormat refFormat, Class<T> expectedType) {
153155
}
154156

155157
if(parentDirectory != null) {
156-
contents = RefUtils.readExternalRef(file, refFormat, auths, parentDirectory);
158+
contents = RefUtils.readExternalRef(file, refFormat, auths, parentDirectory, permittedUrlsChecker);
157159
}
158160
else if(rootPath != null && rootPath.startsWith("http")) {
159-
contents = RefUtils.readExternalUrlRef(file, refFormat, auths, rootPath);
161+
contents = RefUtils.readExternalUrlRef(file, refFormat, auths, rootPath, permittedUrlsChecker);
160162
}
161163
else if (rootPath != null) {
162-
contents = RefUtils.readExternalClasspathRef(file, refFormat, auths, rootPath);
164+
contents = RefUtils.readExternalClasspathRef(file, refFormat, auths, rootPath, permittedUrlsChecker);
163165

164166
}
165167
externalFileCache.put(file, contents);
@@ -382,9 +384,6 @@ private Object getFromMap(String ref, Map map, Pattern pattern) {
382384

383385
protected void checkUrlIsPermitted(String refSet) {
384386
try {
385-
PermittedUrlsChecker permittedUrlsChecker = new PermittedUrlsChecker(parseOptions.getRemoteRefAllowList(),
386-
parseOptions.getRemoteRefBlockList());
387-
388387
permittedUrlsChecker.verify(refSet);
389388
} catch (HostDeniedException e) {
390389
throw new RuntimeException(e.getMessage());

modules/swagger-parser-v3/src/main/java/io/swagger/v3/parser/reference/ReferenceVisitor.java

Lines changed: 21 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@
1515
import io.swagger.v3.oas.models.security.SecurityScheme;
1616
import io.swagger.v3.parser.core.models.AuthorizationValue;
1717
import io.swagger.v3.parser.urlresolver.PermittedUrlsChecker;
18-
import io.swagger.v3.parser.urlresolver.exceptions.HostDeniedException;
1918
import io.swagger.v3.parser.util.RemoteUrl;
2019
import org.apache.commons.lang3.StringUtils;
2120
import org.slf4j.LoggerFactory;
@@ -35,6 +34,7 @@ public class ReferenceVisitor extends AbstractVisitor {
3534
protected OpenAPI31Traverser openAPITraverser;
3635
protected Reference reference;
3736
protected DereferencerContext context;
37+
private PermittedUrlsChecker permittedUrlsChecker;
3838

3939
public ReferenceVisitor(
4040
Reference reference,
@@ -59,6 +59,8 @@ public ReferenceVisitor(
5959
this.visited = visited;
6060
this.visitedMap = visitedMap;
6161
this.context = context;
62+
this.permittedUrlsChecker = new PermittedUrlsChecker(context.getParseOptions().getRemoteRefAllowList(),
63+
context.getParseOptions().getRemoteRefBlockList());
6264
}
6365

6466
public String toBaseURI(String uri) throws Exception{
@@ -83,7 +85,7 @@ public Reference toReference(String uri) throws Exception{
8385
return ref;
8486
}
8587

86-
public Reference toSchemaReference(String baseUri, JsonNode node) throws Exception{
88+
public Reference toSchemaReference(String baseUri, JsonNode node) {
8789
Map<String, Reference> referenceSet = this.reference.getReferenceSet();
8890
if (referenceSet.containsKey(baseUri)) {
8991
return referenceSet.get(baseUri);
@@ -193,20 +195,21 @@ public Header visitHeader(Header header){
193195
}
194196

195197
@Override
196-
public String readHttp(String uri, List<AuthorizationValue> auths) throws Exception {
198+
public String readHttp(String uri, List<AuthorizationValue> auths, PermittedUrlsChecker permittedUrlsChecker) throws Exception {
197199
if(context.getParseOptions().isSafelyResolveURL()){
198-
checkUrlIsPermitted(uri);
200+
permittedUrlsChecker.verify(uri);
201+
return RemoteUrl.urlToString(uri, auths, permittedUrlsChecker);
199202
}
200203
return RemoteUrl.urlToString(uri, auths);
201204
}
202205

203206
public<T> T resolveRef(T visiting, String ref, Class<T> clazz, BiFunction<T, ReferenceVisitor, T> traverseFunction){
204207
try {
205-
Reference reference = toReference(ref);
208+
Reference referenceObject = toReference(ref);
206209
String fragment = ReferenceUtils.getFragment(ref);
207-
JsonNode node = ReferenceUtils.jsonPointerEvaluate(fragment, reference.getJsonNode(), ref);
208-
T resolved = openAPITraverser.deserializeFragment(node, clazz, ref, fragment, reference.getMessages());
209-
ReferenceVisitor visitor = new ReferenceVisitor(reference, openAPITraverser, this.visited, this.visitedMap, context);
210+
JsonNode node = ReferenceUtils.jsonPointerEvaluate(fragment, referenceObject.getJsonNode(), ref);
211+
T resolved = openAPITraverser.deserializeFragment(node, clazz, ref, fragment, referenceObject.getMessages());
212+
ReferenceVisitor visitor = new ReferenceVisitor(referenceObject, openAPITraverser, this.visited, this.visitedMap, context);
210213
return traverseFunction.apply(resolved, visitor);
211214

212215
} catch (Exception e) {
@@ -226,39 +229,39 @@ public Schema resolveSchemaRef(Schema visiting, String ref, List<String> inherit
226229
}
227230
baseURI = ReferenceUtils.resolve(ref, baseURI);
228231
baseURI = ReferenceUtils.toBaseURI(baseURI);
229-
Reference reference = null;
232+
Reference referenceObject;
230233
boolean isAnchor = false;
231234
if (this.reference.getReferenceSet().containsKey(baseURI)) {
232-
reference = this.reference.getReferenceSet().get(baseURI);
235+
referenceObject = this.reference.getReferenceSet().get(baseURI);
233236
}
234237
else {
235-
JsonNode node = null;
238+
JsonNode node;
236239
try {
237240
node = parse(baseURI, this.reference.getAuths());
238241
} catch (Exception e) {
239242
// we can not parse, try ref
240243
baseURI = toBaseURI(ref);
241244
node = parse(baseURI, this.reference.getAuths());
242245
}
243-
reference = toSchemaReference(baseURI, node);
246+
referenceObject = toSchemaReference(baseURI, node);
244247
}
245248
String fragment = ReferenceUtils.getFragment(ref);
246-
JsonNode evaluatedNode = null;
249+
JsonNode evaluatedNode;
247250
try {
248-
evaluatedNode = ReferenceUtils.jsonPointerEvaluate(fragment, reference.getJsonNode(), ref);
251+
evaluatedNode = ReferenceUtils.jsonPointerEvaluate(fragment, referenceObject.getJsonNode(), ref);
249252
} catch (RuntimeException e) {
250253
// maybe anchor
251-
evaluatedNode = findAnchor(reference.getJsonNode(), fragment);
254+
evaluatedNode = findAnchor(referenceObject.getJsonNode(), fragment);
252255
if (evaluatedNode == null) {
253256
throw new RuntimeException("Could not find " + fragment + " in contents of " + ref);
254257
}
255258
isAnchor = true;
256259
}
257-
Schema resolved = openAPITraverser.deserializeFragment(evaluatedNode, Schema.class, ref, fragment, reference.getMessages());
260+
Schema resolved = openAPITraverser.deserializeFragment(evaluatedNode, Schema.class, ref, fragment, referenceObject.getMessages());
258261
if (isAnchor) {
259262
resolved.$anchor(null);
260263
}
261-
ReferenceVisitor visitor = new ReferenceVisitor(reference, openAPITraverser, this.visited, this.visitedMap, context);
264+
ReferenceVisitor visitor = new ReferenceVisitor(referenceObject, openAPITraverser, this.visited, this.visitedMap, context);
262265
return openAPITraverser.traverseSchema(resolved, visitor, inheritedIds);
263266
} catch (Exception e) {
264267
LOGGER.error("Error resolving schema " + ref, e);
@@ -313,13 +316,6 @@ public JsonNode parse(String absoluteUri, List<AuthorizationValue> auths) throws
313316
}
314317
}
315318

316-
return deserializeIntoTree(readURI(absoluteUri, auths));
317-
}
318-
319-
protected void checkUrlIsPermitted(String refSet) throws HostDeniedException {
320-
PermittedUrlsChecker permittedUrlsChecker = new PermittedUrlsChecker(context.getParseOptions().getRemoteRefAllowList(),
321-
context.getParseOptions().getRemoteRefBlockList());
322-
323-
permittedUrlsChecker.verify(refSet);
319+
return deserializeIntoTree(readURI(absoluteUri, auths, permittedUrlsChecker));
324320
}
325321
}

modules/swagger-parser-v3/src/main/java/io/swagger/v3/parser/reference/Visitor.java

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
import io.swagger.v3.oas.models.responses.ApiResponses;
1818
import io.swagger.v3.oas.models.security.SecurityScheme;
1919
import io.swagger.v3.parser.core.models.AuthorizationValue;
20+
import io.swagger.v3.parser.urlresolver.PermittedUrlsChecker;
2021
import io.swagger.v3.parser.util.ClasspathHelper;
2122
import io.swagger.v3.parser.util.RemoteUrl;
2223
import org.apache.commons.io.IOUtils;
@@ -68,18 +69,18 @@ default String readFile(String path) throws Exception {
6869
}
6970
}
7071

71-
default String readClasspath(String classPath) throws Exception {
72+
default String readClasspath(String classPath) {
7273
return ClasspathHelper.loadFileFromClasspath(classPath);
7374
}
74-
default String readHttp(String uri, List<AuthorizationValue> auths) throws Exception {
75-
return RemoteUrl.urlToString(uri, auths);
75+
default String readHttp(String uri, List<AuthorizationValue> auths, PermittedUrlsChecker permittedUrlsChecker) throws Exception {
76+
return RemoteUrl.urlToString(uri, auths, permittedUrlsChecker);
7677
}
7778

78-
default String readURI(String absoluteUri, List<AuthorizationValue> auths) throws Exception {
79+
default String readURI(String absoluteUri, List<AuthorizationValue> auths, PermittedUrlsChecker permittedUrlsChecker) throws Exception {
7980
URI resolved = new URI(absoluteUri);
8081
if (StringUtils.isNotBlank(resolved.getScheme())) {
8182
if (resolved.getScheme().startsWith("http")) {
82-
return readHttp(absoluteUri, auths);
83+
return readHttp(absoluteUri, auths, permittedUrlsChecker);
8384
} else if (resolved.getScheme().startsWith("file")) {
8485
return readFile(resolved.getPath());
8586
} else if (resolved.getScheme().startsWith("classpath")) {

0 commit comments

Comments
 (0)