Created
August 1, 2021 17:46
-
-
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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | |
} | |
} |
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
Execution example:
$ 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)