Skip to content

Instantly share code, notes, and snippets.

@righettod
Created August 1, 2021 17:46
Show Gist options
  • Save righettod/11895cd038dc59a6de42b568ae05feb5 to your computer and use it in GitHub Desktop.
Save righettod/11895cd038dc59a6de42b568ae05feb5 to your computer and use it in GitHub Desktop.
Method to try to decrease the exploitability/interest of the SSRF by design exposed by HTTP Signature in PSD2 STET usage context.
package eu.righettod;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.time.Duration;
import java.util.Arrays;
import java.util.Locale;
import java.util.Optional;
import java.util.regex.Pattern;
public class PSD2StetHelper {
public static void main(String[] args) throws Exception {
String[] testUrl = {
"https://test.com/myQsealCertificate_714f8154ec259ac40b8a9786c9908488b2582X68b17e865fede4636d726b709fX",
"https://test.com/myQsealCertificate_714f8154ec259ac40b8a9786c9908488b2582b68b17e865fede4636d726b709f?a=b",
"http://test.com/myQsealCertificate_714f8154ec259ac40b8a9786c9908488b2582b68b17e865fede4636d726b709f",
"https://test.com/myQsealCertificate_714f8154ec259ac40b8a9786c99",
"https://test.com/myQsealCertificate-714f8154ec259ac40b8a9786c9908488b2582b68b17e865fede4636d726b709f",
"https://gist.githubusercontent.com/righettod/4e230425c40e114579c53e59a1ca21b2/raw/58f7bf3730a33bb630412846239b341e8d8f2b18/myQsealCertificate_714f8154ec259ac40b8a9786c9908488b2582b68b17e865fede4636d726b709f"
};
Arrays.stream(testUrl).forEach(u -> System.out.printf("[%s] %s\n", isSafeUrl(u), u));
}
/**
* The PSD2 STET specification require to use HTTP Signature.
* <br>
* Section 3.5 of the document "Version 1.5.1 - Documentation Framework".
* <br>
* The problem is that, by design, the HTTP Signature specification is prone to blind SSRF.
* <br>
* The objective of this code is to try to decrease the "exploitability/interest" of this SSRF for an attacker.
* <br>
* URL example taken from the STET specification: https://path.to/myQsealCertificate_714f8154ec259ac40b8a9786c9908488b2582b68b17e865fede4636d726b709f
* <br>
* Require java 11+
* <br>
* Not external dependencies
*
* @param certificateUrl Url pointing to a Qualified Certificate (QSealC) encoded in PEM format and respecting the ETSI/TS119495 technical Specification .
* @return TRUE only if the url point to a Qualified Certificate in PEM format.
* @see "https://www.stet.eu/assets/files/PSD2/1-5-1-6/api-dsp2-stet-v1.5.1.6-part-1-framework.pdf"
* @see "https://portswigger.net/web-security/ssrf"
* @see "https://datatracker.ietf.org/doc/html/draft-cavage-http-signatures-12"
* @see "https://openjdk.java.net/groups/net/httpclient/intro.html"
*/
private static boolean isSafeUrl(String certificateUrl) {
boolean isValid = false;
String userAgent = "Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:47.0) Gecko/20100101 Firefox/47.0";
try {
//1. Ensure that the URL end with the SHA-256 fingerprint encoded in HEX of the certificate like requested by STET
if (certificateUrl != null && certificateUrl.lastIndexOf("_") != -1) {
String digestPart = certificateUrl.substring(certificateUrl.lastIndexOf("_") + 1);
if (Pattern.matches("[0-9a-f]{64}", digestPart)) {
//2. Ensure that the URL is a valid url by creating a instance of the class URI
URI uri = URI.create(certificateUrl);
//3. Require usage of HTTPS and reject any url containing query parameters
if ("https".equalsIgnoreCase(uri.getScheme()) && uri.getQuery() == null) {
//4. Perform a HTTP HEAD request in order to get the content type of the remote resource
//and limit the interest to use the SSRF because to pass the check the url need to:
//- Cannot have query parameters
//- Use HTTPS protocol
//- End with a string having the format "_[0-9a-f]{64}"
//- Trigger the malicious action that the attacker want but with a HTTP HEAD without any redirect
HttpClient client = HttpClient.newBuilder().followRedirects(HttpClient.Redirect.NEVER).build();
HttpRequest request = HttpRequest.newBuilder()
.uri(uri)
.timeout(Duration.ofSeconds(10))
.method("HEAD", HttpRequest.BodyPublishers.noBody())
.header("User-Agent", userAgent).build();//To not remotely disclose the version of java used
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
if (response.statusCode() == 200) {
//5. Ensure that the response content type is "text/plain"
Optional<String> contentType = response.headers().firstValue("Content-Type");
isValid = (contentType.isPresent() && contentType.get().trim().toLowerCase(Locale.ENGLISH).startsWith("text/plain"));
}
}
}
}
} catch (Exception e) {
//TODO: Log error correctly :)
e.printStackTrace();
//Override value - paranoid mode :)
isValid = false;
}
return isValid;
}
}
@righettod
Copy link
Author

Execution example:

image

$ java -version
openjdk version "12.0.2" 2019-07-16
OpenJDK Runtime Environment AdoptOpenJDK (build 12.0.2+10)
OpenJDK 64-Bit Server VM AdoptOpenJDK (build 12.0.2+10, mixed mode, sharing)

@righettod
Copy link
Author

Important

The initiative was moved to a dedicated repository so any update will be performed on the repo and not here anymore.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment