Skip to content

Commit 810e13a

Browse files
author
drighetto
committed
Implement ReDOS related methods #1
1 parent ee3644a commit 810e13a

File tree

3 files changed

+58
-2
lines changed

3 files changed

+58
-2
lines changed

.idea/inspectionProfiles/Project_Default.xml

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/main/java/eu/righettod/SecurityUtils.java

Lines changed: 40 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,8 +58,9 @@
5858
import java.nio.file.Files;
5959
import java.security.MessageDigest;
6060
import java.time.Duration;
61-
import java.util.List;
6261
import java.util.*;
62+
import java.util.List;
63+
import java.util.concurrent.*;
6364
import java.util.concurrent.atomic.AtomicInteger;
6465
import java.util.regex.Pattern;
6566
import java.util.zip.ZipEntry;
@@ -1285,7 +1286,44 @@ public static boolean applyJWTExtraValidation(DecodedJWT token, TokenType expect
12851286
//In case of error then assume that the check failed
12861287
isValid = false;
12871288
}
1288-
12891289
return isValid;
12901290
}
1291+
1292+
/**
1293+
* Apply a validations on a regular expression to ensure that is not prone to the ReDOS attack.
1294+
* <br>If your technology is supported by <a href="https://github.com/doyensec/regexploit">regexploit</a> then <b>use it instead of this method!</b>
1295+
* <br>Indeed, the <a href="https://www.doyensec.com/">doyensec</a> team has made an intensive and amazing work on this topic.
1296+
*
1297+
* @param regex String expected to be a valid regular expression.
1298+
* @param data Test data on which the regular expression is executed for the test.
1299+
* @param maximumRunningTimeInSeconds Optional parameter to specify a number of seconds above which a regex execution time is considered as not safe (default to 4 seconds when not specified).
1300+
* @return True only if the string pass all validations.
1301+
* @see "https://github.blog/security/how-to-fix-a-redos/"
1302+
* @see "https://learn.snyk.io/lesson/redos/?ecosystem=javascript"
1303+
* @see "https://rules.sonarsource.com/java/RSPEC-2631/"
1304+
* @see "https://github.com/doyensec/regexploit"
1305+
* @see "https://wiki.owasp.org/images/2/23/OWASP_IL_2009_ReDoS.pdf"
1306+
* @see "https://owasp.org/www-community/attacks/Regular_expression_Denial_of_Service_-_ReDoS"
1307+
*/
1308+
public static boolean isRegexSafe(String regex, String data, Optional<Integer> maximumRunningTimeInSeconds) {
1309+
boolean isSafe = false;
1310+
final String testData = (data != null) ? data : "";
1311+
int executionTimeout = maximumRunningTimeInSeconds.orElse(4);
1312+
ExecutorService executor = Executors.newSingleThreadExecutor();
1313+
try {
1314+
Callable<Boolean> task = () -> {
1315+
Pattern pattern = Pattern.compile(regex);
1316+
return pattern.matcher(testData).matches();
1317+
};
1318+
List<Future<Boolean>> tasks = executor.invokeAll(List.of(task), executionTimeout, TimeUnit.SECONDS);
1319+
if (!tasks.getFirst().isCancelled()) {
1320+
isSafe = true;
1321+
}
1322+
} catch (Exception e) {
1323+
isSafe = false;
1324+
} finally {
1325+
executor.shutdownNow();
1326+
}
1327+
return isSafe;
1328+
}
12911329
}

src/test/java/eu/righettod/TestSecurityUtils.java

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -601,5 +601,20 @@ private DecodedJWT generateJWTToken(TokenType tokenType, String jti, boolean use
601601
JWTVerifier verifier = JWT.require(algorithm).build();
602602
return verifier.verify(signedToken);
603603
}
604+
605+
@Test
606+
public void isRegexSafe() {
607+
final String templateMsgFalseNegative = "Regular expression '%s' must be detected as not safe!";
608+
final String templateMsgFalsePositive = "Regular expression '%s' must be detected as safe!";
609+
String testData = "a".repeat(400) + "!";
610+
//Test unsafe case
611+
String testRegex = "(.*a){10}";
612+
boolean result = SecurityUtils.isRegexSafe(testRegex, testData, Optional.empty());
613+
assertFalse(result, String.format(templateMsgFalseNegative, testRegex));
614+
//Test safe case
615+
testRegex = "[a-z]+";
616+
result = SecurityUtils.isRegexSafe(testRegex, testData, Optional.empty());
617+
assertTrue(result, String.format(templateMsgFalsePositive, testRegex));
618+
}
604619
}
605620

0 commit comments

Comments
 (0)