Skip to content

Commit b8f8dd9

Browse files
committed
Initial work
1 parent 56aba73 commit b8f8dd9

File tree

10 files changed

+207
-13
lines changed

10 files changed

+207
-13
lines changed

core-codemods/src/main/java/io/codemodder/codemods/DefaultCodemods.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ public static List<Class<? extends CodeChanger>> asList() {
3939
CodeQLOutputResourceLeakCodemod.class,
4040
CodeQLPotentiallyUnsafeCryptoAlgorithmCodemod.class,
4141
CodeQLPredictableSeedCodemod.class,
42+
CodeQLRegexDoSCodemod.class,
4243
CodeQLRegexInjectionCodemod.class,
4344
CodeQLSQLInjectionCodemod.class,
4445
CodeQLSSRFCodemod.class,
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
package io.codemodder.codemods.codeql;
2+
3+
import com.contrastsecurity.sarif.Result;
4+
import com.github.javaparser.ast.CompilationUnit;
5+
import io.codemodder.*;
6+
import io.codemodder.codetf.DetectorRule;
7+
import io.codemodder.providers.sarif.codeql.ProvidedCodeQLScan;
8+
import io.codemodder.remediation.GenericRemediationMetadata;
9+
import io.codemodder.remediation.Remediator;
10+
import io.codemodder.remediation.regexdos.RegexDoSRemediator;
11+
import java.util.Optional;
12+
import javax.inject.Inject;
13+
14+
/** A codemod that mitigates regex dos vulnerabilities * */
15+
@Codemod(
16+
id = "codeql:java/regex-dos",
17+
reviewGuidance = ReviewGuidance.MERGE_AFTER_CURSORY_REVIEW,
18+
importance = Importance.MEDIUM,
19+
executionPriority = CodemodExecutionPriority.HIGH)
20+
public final class CodeQLRegexDoSCodemod extends CodeQLRemediationCodemod {
21+
22+
private final Remediator<Result> remediator;
23+
24+
@Inject
25+
public CodeQLRegexDoSCodemod(
26+
@ProvidedCodeQLScan(ruleId = "java/polynomial-redos") final RuleSarif sarif) {
27+
super(GenericRemediationMetadata.REGEX_DOS.reporter(), sarif);
28+
this.remediator = new RegexDoSRemediator<>();
29+
}
30+
31+
@Override
32+
public DetectorRule detectorRule() {
33+
return new DetectorRule(
34+
"polynomial-redos",
35+
"Polynomial regular expression used on uncontrolled data",
36+
"https://codeql.github.com/codeql-query-help/java/java-polynomial-redos/");
37+
}
38+
39+
@Override
40+
public CodemodFileScanningResult visit(
41+
final CodemodInvocationContext context, final CompilationUnit cu) {
42+
return remediator.remediateAll(
43+
cu,
44+
context.path().toString(),
45+
detectorRule(),
46+
ruleSarif.getResultsByLocationPath(context.path()),
47+
SarifFindingKeyUtil::buildFindingId,
48+
r -> r.getLocations().get(0).getPhysicalLocation().getRegion().getStartLine(),
49+
r ->
50+
Optional.ofNullable(
51+
r.getLocations().get(0).getPhysicalLocation().getRegion().getEndLine()),
52+
r ->
53+
Optional.ofNullable(
54+
r.getLocations().get(0).getPhysicalLocation().getRegion().getStartColumn()));
55+
}
56+
}

framework/codemodder-base/src/main/java/io/codemodder/ast/ASTs.java

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import com.github.javaparser.ast.nodeTypes.NodeWithSimpleName;
2020
import com.github.javaparser.ast.stmt.*;
2121
import com.github.javaparser.ast.type.TypeParameter;
22+
import com.github.javaparser.resolution.types.ResolvedType;
2223
import java.util.Iterator;
2324
import java.util.List;
2425
import java.util.Optional;
@@ -883,6 +884,20 @@ public boolean hasNext() {
883884
}
884885
}
885886

887+
/**
888+
* Resolves type of a given expression e.
889+
*
890+
* @param e
891+
* @return
892+
*/
893+
public static Optional<ResolvedType> calculateResolvedType(final Expression e) {
894+
try {
895+
return Optional.of(e.calculateResolvedType());
896+
} catch (final RuntimeException exception) {
897+
return Optional.empty();
898+
}
899+
}
900+
886901
/**
887902
* Checks if a node is a MethodCallExpr that is the initialization of a declaration with one of
888903
* the types in assignedToTypes.

framework/codemodder-base/src/main/java/io/codemodder/remediation/GenericRemediationMetadata.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ public enum GenericRemediationMetadata {
1818
PREDICTABLE_SEED("predictable-seed"),
1919
ZIP_SLIP("zip-slip"),
2020
REGEX_INJECTION("regex-injection"),
21+
REGEX_DOS("regex-dos"),
2122
ERROR_MESSAGE_EXPOSURE("error-message-exposure"),
2223
LOG_INJECTION("log-injection"),
2324
WEAK_CRYPTO_ALGORITHM("weak-crypto-algorithm");
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
package io.codemodder.remediation.regexdos;
2+
3+
import com.github.javaparser.ast.CompilationUnit;
4+
import com.github.javaparser.ast.Node;
5+
import com.github.javaparser.ast.expr.MethodCallExpr;
6+
import io.codemodder.ast.ASTs;
7+
import io.codemodder.remediation.MatchAndFixStrategy;
8+
import io.codemodder.remediation.SuccessOrReason;
9+
import java.util.List;
10+
import java.util.Optional;
11+
12+
/** Adds a timeout function and wraps regex match call with it * */
13+
final class RegexDoSFixStrategy extends MatchAndFixStrategy {
14+
15+
/**
16+
* Test if the node is a Pattern.matcher*() call
17+
*
18+
* @param node
19+
* @return
20+
*/
21+
@Override
22+
public boolean match(final Node node) {
23+
return Optional.of(node)
24+
.map(n -> n instanceof MethodCallExpr mce ? mce : null)
25+
.filter(mce -> mce.hasScope())
26+
// Check if the type is Pattern
27+
.filter(
28+
mce ->
29+
ASTs.calculateResolvedType(mce)
30+
.filter(t -> "java.util.regex.Pattern".equals(t.describe()))
31+
.isPresent())
32+
.filter(mce -> "matcher".equals(mce.getNameAsString()))
33+
.isPresent();
34+
}
35+
36+
private static List<String> matchingMethods =
37+
List.of("matches", "find", "replaceAll", "replaceFirst");
38+
39+
@Override
40+
public SuccessOrReason fix(final CompilationUnit cu, final Node node) {
41+
// Find all the matcher calls from the matchingMethods list
42+
// if any, wrap it with executeWithTimeout with a default 5000 of timeout
43+
// Add executeWithTimout method to the encompassing class
44+
// Add needed imports (Callable, RuntimeException)
45+
return SuccessOrReason.reason("Doesn't match expected code shape");
46+
}
47+
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
package io.codemodder.remediation.regexdos;
2+
3+
import com.github.javaparser.ast.CompilationUnit;
4+
import io.codemodder.CodemodFileScanningResult;
5+
import io.codemodder.codetf.DetectorRule;
6+
import io.codemodder.remediation.Remediator;
7+
import io.codemodder.remediation.SearcherStrategyRemediator;
8+
import java.util.Collection;
9+
import java.util.Optional;
10+
import java.util.function.Function;
11+
12+
/**
13+
* Fixes header injection pointed by issues.
14+
*
15+
* @param <T>
16+
*/
17+
public final class RegexDoSRemediator<T> implements Remediator<T> {
18+
19+
private final SearcherStrategyRemediator<T> searchStrategyRemediator;
20+
21+
public RegexDoSRemediator() {
22+
this.searchStrategyRemediator =
23+
new SearcherStrategyRemediator.Builder<T>()
24+
.withMatchAndFixStrategy(new RegexDoSFixStrategy())
25+
.build();
26+
}
27+
28+
@Override
29+
public CodemodFileScanningResult remediateAll(
30+
CompilationUnit cu,
31+
String path,
32+
DetectorRule detectorRule,
33+
Collection<T> findingsForPath,
34+
Function<T, String> findingIdExtractor,
35+
Function<T, Integer> findingStartLineExtractor,
36+
Function<T, Optional<Integer>> findingEndLineExtractor,
37+
Function<T, Optional<Integer>> findingColumnExtractor) {
38+
return searchStrategyRemediator.remediateAll(
39+
cu,
40+
path,
41+
detectorRule,
42+
findingsForPath,
43+
findingIdExtractor,
44+
findingStartLineExtractor,
45+
findingEndLineExtractor,
46+
findingColumnExtractor);
47+
}
48+
}
Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,20 @@
1-
This change removes exposure through sending/printing of error and exception data.
1+
This change adds a timout to regex matching calls from the `java.util.regex` libraries.
22

33
Our changes look like this:
44

55
```java
6-
void function(HttpServletResponse response) {
7-
PrintWriter pw = reponse.getWriter();
8-
try{
9-
...
10-
} catch (Exception e) {
11-
- pw.println(e.getMessage());
12-
}
13-
}
6+
+public <E> E executeWithTimeout(final Callable<E> action, final int timeout){
7+
+ Future<E> maybeResult = Executors.newSingleThreadExecutor().submit(action);
8+
+ try{
9+
+ return maybeResult.get(timeout, TimeUnit.MILLISECONDS);
10+
+ }catch(Exception e){
11+
+ throw new RuntimeException("Failed to execute within time limit.");
12+
+ }
13+
+}
14+
...
15+
String input = "aaaaaaaaaaaaaaaaaaaaa";
16+
Pattern pat = Pattern.compile("^(a+)+$");
17+
var matcher = pat.matcher(input);
18+
- matcher.matches();
19+
+ executeWithTimeout(() -> matcher.matches(), 5000);
1420
```
Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
2-
"summary" : "Removed printing/sending of error data",
3-
"change" : "Removed printing/sending of error data",
4-
"reviewGuidanceIJustification" : "While this change is most likely harmless, it may be the case that the other endpoint is expecting the message and needs adjustment.",
5-
"references" : ["https://cwe.mitre.org/data/definitions/209.html", "https://owasp.org/www-community/Improper_Error_Handling", "https://www.securecoding.cert.org/confluence/display/java/ERR01-J.+Do+not+allow+exceptions+to+expose+sensitive+information"]
2+
"summary" : "Added a timeout to regular expression matching",
3+
"change" : "Added a timeout to regular expression matching",
4+
"reviewGuidanceIJustification" : "The expected timeout is highly dependent on the application and should be adjusted to conform to it.",
5+
"references" : ["https://owasp.org/www-community/attacks/Regular_expression_Denial_of_Service_-_ReDoS", "https://cwe.mitre.org/data/definitions/400.html", "https://github.com/google/re2j"]
66
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
This change removes exposure through sending/printing of error and exception data.
2+
3+
Our changes look like this:
4+
5+
```java
6+
void function(HttpServletResponse response) {
7+
PrintWriter pw = reponse.getWriter();
8+
try{
9+
...
10+
} catch (Exception e) {
11+
- pw.println(e.getMessage());
12+
}
13+
}
14+
```
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"summary" : "Removed printing/sending of error data",
3+
"change" : "Removed printing/sending of error data",
4+
"reviewGuidanceIJustification" : "While this change is most likely harmless, it may be the case that the other endpoint is expecting the message and needs adjustment.",
5+
"references" : ["https://cwe.mitre.org/data/definitions/209.html", "https://owasp.org/www-community/Improper_Error_Handling", "https://www.securecoding.cert.org/confluence/display/java/ERR01-J.+Do+not+allow+exceptions+to+expose+sensitive+information"]
6+
}

0 commit comments

Comments
 (0)