Skip to content

Commit dcc0216

Browse files
fix: disallow SSRF via remote $ref
1 parent 4040365 commit dcc0216

File tree

12 files changed

+503
-553
lines changed

12 files changed

+503
-553
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: 7 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ public class ReferenceVisitor extends AbstractVisitor {
3535
protected OpenAPI31Traverser openAPITraverser;
3636
protected Reference reference;
3737
protected DereferencerContext context;
38+
private PermittedUrlsChecker permittedUrlsChecker;
3839

3940
public ReferenceVisitor(
4041
Reference reference,
@@ -59,6 +60,8 @@ public ReferenceVisitor(
5960
this.visited = visited;
6061
this.visitedMap = visitedMap;
6162
this.context = context;
63+
this.permittedUrlsChecker = new PermittedUrlsChecker(context.getParseOptions().getRemoteRefAllowList(),
64+
context.getParseOptions().getRemoteRefBlockList());
6265
}
6366

6467
public String toBaseURI(String uri) throws Exception{
@@ -193,11 +196,11 @@ public Header visitHeader(Header header){
193196
}
194197

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

203206
public<T> T resolveRef(T visiting, String ref, Class<T> clazz, BiFunction<T, ReferenceVisitor, T> traverseFunction){
@@ -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")) {

modules/swagger-parser-v3/src/main/java/io/swagger/v3/parser/util/RefUtils.java

Lines changed: 38 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,8 @@
33
import io.swagger.v3.parser.core.models.AuthorizationValue;
44
import io.swagger.v3.parser.models.RefFormat;
55
import io.swagger.v3.parser.processors.ExternalRefProcessor;
6+
import io.swagger.v3.parser.urlresolver.PermittedUrlsChecker;
67
import org.apache.commons.io.IOUtils;
7-
import org.apache.commons.lang3.StringUtils;
8-
9-
import static java.nio.charset.StandardCharsets.UTF_8;
108

119
import java.io.FileInputStream;
1210
import java.io.IOException;
@@ -18,6 +16,8 @@
1816
import java.util.List;
1917
import java.util.Optional;
2018

19+
import static java.nio.charset.StandardCharsets.UTF_8;
20+
2121
public class RefUtils {
2222

2323
private static final String REFERENCE_SEPARATOR = "#/";
@@ -50,10 +50,10 @@ public static String computeDefinitionName(String ref) {
5050
final String[] split = plausibleName.split("\\.");
5151
// Fix for issue-1621 and issue-1865
5252
//validate number of dots
53-
if(split.length > 2) {
53+
if (split.length > 2) {
5454
//Remove dot so ref can be interpreted as internal and relative in Swagger-Core schema class 'set$ref'
5555
plausibleName = String.join("", Arrays.copyOf(split, split.length - 1));
56-
}else{
56+
} else {
5757
plausibleName = split[0];
5858
}
5959
}
@@ -66,24 +66,26 @@ public static Optional<String> getExternalPath(String ref) {
6666
return Optional.empty();
6767
}
6868
return Optional.of(ref.split(REFERENCE_SEPARATOR))
69-
.filter(it -> it.length == 2)
70-
.map(it -> it[0])
71-
.filter(it -> !it.isEmpty());
69+
.filter(it -> it.length == 2)
70+
.map(it -> it[0])
71+
.filter(it -> !it.isEmpty());
7272
}
7373

7474
public static boolean isAnExternalRefFormat(RefFormat refFormat) {
7575
return refFormat == RefFormat.URL || refFormat == RefFormat.RELATIVE;
7676
}
7777

7878
public static RefFormat computeRefFormat(String ref) {
79-
RefFormat result = RefFormat.INTERNAL;
79+
RefFormat result;
8080
ref = mungedRef(ref);
81-
if(ref.startsWith("http")||ref.startsWith("https")) {
81+
if (ref.startsWith("http") || ref.startsWith("https")) {
8282
result = RefFormat.URL;
83-
} else if(ref.startsWith(REFERENCE_SEPARATOR)) {
83+
} else if (ref.startsWith(REFERENCE_SEPARATOR)) {
8484
result = RefFormat.INTERNAL;
85-
} else if(ref.startsWith(".") || ref.startsWith("/") || ref.indexOf(REFERENCE_SEPARATOR) > 0) {
85+
} else if (ref.startsWith(".") || ref.startsWith("/") || ref.indexOf(REFERENCE_SEPARATOR) > 0) {
8686
result = RefFormat.RELATIVE;
87+
} else {
88+
result = RefFormat.INTERNAL;
8789
}
8890

8991
return result;
@@ -103,7 +105,7 @@ public static String mungedRef(String refString) {
103105

104106

105107
public static String readExternalUrlRef(String file, RefFormat refFormat, List<AuthorizationValue> auths,
106-
String rootPath) {
108+
String rootPath, PermittedUrlsChecker permittedUrlsChecker) {
107109

108110
if (!RefUtils.isAnExternalRefFormat(refFormat)) {
109111
throw new RuntimeException("Ref is not external");
@@ -113,12 +115,12 @@ public static String readExternalUrlRef(String file, RefFormat refFormat, List<A
113115

114116
try {
115117
if (refFormat == RefFormat.URL) {
116-
result = RemoteUrl.urlToString(file, auths);
118+
result = RemoteUrl.urlToString(file, auths, permittedUrlsChecker);
117119
} else {
118120
//its assumed to be a relative ref
119121
String url = buildUrl(rootPath, file);
120122

121-
return readExternalRef(url, RefFormat.URL, auths, null);
123+
return readExternalRef(url, RefFormat.URL, auths, null, permittedUrlsChecker);
122124
}
123125
} catch (Exception e) {
124126
throw new RuntimeException("Unable to load " + refFormat + " ref: " + file, e);
@@ -129,7 +131,7 @@ public static String readExternalUrlRef(String file, RefFormat refFormat, List<A
129131
}
130132

131133
public static String readExternalClasspathRef(String file, RefFormat refFormat, List<AuthorizationValue> auths,
132-
String rootPath) {
134+
String rootPath, PermittedUrlsChecker permittedUrlsChecker) {
133135

134136
if (!RefUtils.isAnExternalRefFormat(refFormat)) {
135137
throw new RuntimeException("Ref is not external");
@@ -139,7 +141,7 @@ public static String readExternalClasspathRef(String file, RefFormat refFormat,
139141

140142
try {
141143
if (refFormat == RefFormat.URL) {
142-
result = RemoteUrl.urlToString(file, auths);
144+
result = RemoteUrl.urlToString(file, auths, permittedUrlsChecker);
143145
} else {
144146
//its assumed to be a relative ref
145147
String pathRef = ExternalRefProcessor.join(rootPath, file);
@@ -155,24 +157,23 @@ public static String readExternalClasspathRef(String file, RefFormat refFormat,
155157
}
156158

157159
public static String buildUrl(String rootPath, String relativePath) {
158-
if(rootPath == null || relativePath == null) {
159-
return null;
160-
}
161-
162-
try {
163-
int until = rootPath.lastIndexOf("/")+1;
164-
String root = rootPath.substring(0, until);
165-
URL rootUrl = new URL(root);
166-
URL finalUrl = new URL(rootUrl, relativePath);
167-
return finalUrl.toString();
168-
}
169-
catch(Exception e) {
170-
throw new RuntimeException(e);
171-
}
160+
if (rootPath == null || relativePath == null) {
161+
return null;
162+
}
163+
164+
try {
165+
int until = rootPath.lastIndexOf("/") + 1;
166+
String root = rootPath.substring(0, until);
167+
URL rootUrl = new URL(root);
168+
URL finalUrl = new URL(rootUrl, relativePath);
169+
return finalUrl.toString();
170+
} catch (Exception e) {
171+
throw new RuntimeException(e);
172+
}
172173
}
173174

174175
public static String readExternalRef(String file, RefFormat refFormat, List<AuthorizationValue> auths,
175-
Path parentDirectory) {
176+
Path parentDirectory, PermittedUrlsChecker permittedUrlsChecker) {
176177

177178
if (!RefUtils.isAnExternalRefFormat(refFormat)) {
178179
throw new RuntimeException("Ref is not external");
@@ -182,12 +183,12 @@ public static String readExternalRef(String file, RefFormat refFormat, List<Auth
182183

183184
try {
184185
if (refFormat == RefFormat.URL) {
185-
result = RemoteUrl.urlToString(file, auths);
186+
result = RemoteUrl.urlToString(file, auths, permittedUrlsChecker);
186187
} else {
187188
//its assumed to be a relative file ref
188189
final Path pathToUse = parentDirectory.resolve(file).normalize();
189190

190-
if(Files.exists(pathToUse)) {
191+
if (Files.exists(pathToUse)) {
191192
result = readAll(pathToUse);
192193
} else {
193194
String url = file;
@@ -206,18 +207,18 @@ public static String readExternalRef(String file, RefFormat refFormat, List<Auth
206207
}
207208
final Path pathToUse2 = parentDirectory.resolve(url).normalize();
208209

209-
if(Files.exists(pathToUse2)) {
210+
if (Files.exists(pathToUse2)) {
210211
result = readAll(pathToUse2);
211212
}
212213
}
213-
if (result == null){
214+
if (result == null) {
214215
result = ClasspathHelper.loadFileFromClasspath(file);
215216
}
216217

217218

218219
}
219220
} catch (Exception e) {
220-
throw new RuntimeException("Unable to load " + refFormat + " ref: " + file + " path: "+parentDirectory, e);
221+
throw new RuntimeException("Unable to load " + refFormat + " ref: " + file + " path: " + parentDirectory, e);
221222
}
222223

223224
return result;

0 commit comments

Comments
 (0)