Skip to content

Commit 1b3fe20

Browse files
authored
Add several CodeQL mappings (#457)
Adds several mappings for CodeQL rules, as well as adds a new integration test for a more recent version of CodeQL+WebGoat 2023.8.
1 parent 364702d commit 1b3fe20

File tree

55 files changed

+13326
-153
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

55 files changed

+13326
-153
lines changed

core-codemods/src/intTest/java/io/codemodder/integration/GitRepositoryTest.java

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,19 @@
11
package io.codemodder.integration;
22

3+
import static org.hamcrest.CoreMatchers.equalTo;
4+
import static org.hamcrest.CoreMatchers.is;
5+
import static org.hamcrest.MatcherAssert.assertThat;
6+
7+
import io.codemodder.codetf.CodeTFChangesetEntry;
8+
import io.codemodder.codetf.CodeTFReport;
9+
import io.codemodder.codetf.CodeTFResult;
310
import java.io.File;
411
import java.io.IOException;
512
import java.nio.file.Files;
613
import java.nio.file.Path;
14+
import java.util.Collection;
715
import java.util.Comparator;
16+
import java.util.List;
817
import java.util.Objects;
918
import java.util.stream.Stream;
1019
import org.eclipse.jgit.api.Git;
@@ -81,4 +90,61 @@ void createOutputFile() throws IOException {
8190
this.outputFile = Files.createTempFile("report", ".log").toFile();
8291
outputFile.deleteOnExit();
8392
}
93+
94+
protected void verifyNoFailedFiles(final CodeTFReport report) {
95+
List<String> failedFiles =
96+
report.getResults().stream()
97+
.map(CodeTFResult::getFailedFiles)
98+
.flatMap(Collection::stream)
99+
.toList();
100+
assertThat(failedFiles.size(), is(0));
101+
}
102+
103+
protected void verifyStandardCodemodResults(final List<CodeTFChangesetEntry> fileChanges) {
104+
// we only inject into a couple files
105+
assertThat(
106+
fileChanges.stream()
107+
.map(CodeTFChangesetEntry::getPath)
108+
.anyMatch(path -> path.endsWith("SerializationHelper.java")),
109+
is(true));
110+
111+
assertThat(
112+
fileChanges.stream()
113+
.map(CodeTFChangesetEntry::getPath)
114+
.anyMatch(path -> path.endsWith("InsecureDeserializationTask.java")),
115+
is(true));
116+
}
117+
118+
protected void verifyCodemodsHitWithChangesetCount(
119+
final CodeTFReport report, final String codemodId, final int changes) {
120+
List<CodeTFResult> results =
121+
report.getResults().stream()
122+
.filter(result -> codemodId.equals(result.getCodemod()))
123+
.toList();
124+
assertThat(results.size(), equalTo(1)); // should only have 1 entry per codemod
125+
assertThat(results.get(0).getChangeset().size(), equalTo(changes));
126+
}
127+
128+
protected static final String testPathIncludes =
129+
"**.java,"
130+
+ "**/*.java,"
131+
+ "pom.xml,"
132+
+ "**/pom.xml,"
133+
+ "**.jsp,"
134+
+ "**/*.jsp,"
135+
+ "web.xml,"
136+
+ "**/web.xml,"
137+
+ ".github/workflows/*.yml,"
138+
+ ".github/workflows/*.yaml";
139+
140+
protected static final String testPathExcludes =
141+
"**/test/**,"
142+
+ "**/testFixtures/**,"
143+
+ "**/*Test.java,"
144+
+ "**/intTest/**,"
145+
+ "**/tests/**,"
146+
+ "**/target/**,"
147+
+ "**/build/**,"
148+
+ "**/.mvn/**,"
149+
+ ".mvn/**";
84150
}
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
package io.codemodder.integration;
2+
3+
import static org.hamcrest.CoreMatchers.*;
4+
import static org.hamcrest.MatcherAssert.assertThat;
5+
6+
import com.fasterxml.jackson.databind.ObjectMapper;
7+
import io.codemodder.codemods.DefaultCodemods;
8+
import io.codemodder.codetf.CodeTFChangesetEntry;
9+
import io.codemodder.codetf.CodeTFReport;
10+
import io.codemodder.codetf.CodeTFResult;
11+
import java.io.FileReader;
12+
import java.util.Collection;
13+
import java.util.List;
14+
import org.junit.jupiter.api.Test;
15+
16+
final class WebGoat20238Test extends GitRepositoryTest {
17+
18+
WebGoat20238Test() {
19+
super("https://github.com/WebGoat/WebGoat", "main", "f9b810c5ee2d6731eb5e37172af20d276a7dfb98");
20+
}
21+
22+
@Test
23+
void it_remediates_webgoat_2023_8() throws Exception {
24+
25+
DefaultCodemods.main(
26+
new String[] {
27+
"--output",
28+
outputFile.getPath(),
29+
"--sarif",
30+
"src/test/resources/webgoat_v2023.8_from_ghas_06_2024.sarif",
31+
"--dont-exit",
32+
"--path-include=" + testPathIncludes,
33+
"--path-exclude=" + testPathExcludes,
34+
repoDir.getPath()
35+
});
36+
37+
ObjectMapper objectMapper = new ObjectMapper();
38+
39+
var report = objectMapper.readValue(new FileReader(outputFile), CodeTFReport.class);
40+
41+
verifyNoFailedFiles(report);
42+
43+
List<CodeTFChangesetEntry> fileChanges =
44+
report.getResults().stream()
45+
.map(CodeTFResult::getChangeset)
46+
.flatMap(Collection::stream)
47+
.toList();
48+
49+
assertThat(fileChanges.size(), is(50));
50+
51+
verifyStandardCodemodResults(fileChanges);
52+
53+
// count the changes associated with missing-jwt-signature-check from codeql
54+
List<CodeTFResult> jwtResults =
55+
report.getResults().stream()
56+
.filter(result -> "codeql:java/missing-jwt-signature-check".equals(result.getCodemod()))
57+
.toList();
58+
assertThat(jwtResults.size(), equalTo(1));
59+
60+
// this file is also only changed by including the codeql results
61+
CodeTFChangesetEntry jwtChange =
62+
fileChanges.stream()
63+
.filter(change -> change.getPath().endsWith("JWTRefreshEndpoint.java"))
64+
.findFirst()
65+
.orElseThrow();
66+
67+
assertThat(jwtChange.getChanges().size(), equalTo(2));
68+
assertThat(jwtChange.getChanges().get(0).getLineNumber(), equalTo(113));
69+
assertThat(jwtChange.getChanges().get(1).getLineNumber(), equalTo(140));
70+
71+
verifyCodemodsHitWithChangesetCount(report, "codeql:java/insecure-randomness", 0);
72+
verifyCodemodsHitWithChangesetCount(report, "codeql:java/ssrf", 1);
73+
verifyCodemodsHitWithChangesetCount(report, "codeql:java/xxe", 1);
74+
verifyCodemodsHitWithChangesetCount(report, "codeql:java/sql-injection", 6);
75+
verifyCodemodsHitWithChangesetCount(report, "codeql:java/insecure-cookie", 2);
76+
}
77+
}

core-codemods/src/intTest/java/io/codemodder/integration/WebGoat822Test.java

Lines changed: 14 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@
1616
import java.nio.file.Path;
1717
import java.util.Collection;
1818
import java.util.List;
19-
import java.util.stream.Collectors;
2019
import org.junit.jupiter.api.Test;
2120
import org.mozilla.universalchardet.UniversalDetector;
2221

@@ -26,29 +25,6 @@ final class WebGoat822Test extends GitRepositoryTest {
2625
super("https://github.com/WebGoat/WebGoat", "main", "e75cfbeb110e3d3a2ca3c8fee2754992d89c419d");
2726
}
2827

29-
private static final String testPathIncludes =
30-
"**.java,"
31-
+ "**/*.java,"
32-
+ "pom.xml,"
33-
+ "**/pom.xml,"
34-
+ "**.jsp,"
35-
+ "**/*.jsp,"
36-
+ "web.xml,"
37-
+ "**/web.xml,"
38-
+ ".github/workflows/*.yml,"
39-
+ ".github/workflows/*.yaml";
40-
41-
private static final String testPathExcludes =
42-
"**/test/**,"
43-
+ "**/testFixtures/**,"
44-
+ "**/*Test.java,"
45-
+ "**/intTest/**,"
46-
+ "**/tests/**,"
47-
+ "**/target/**,"
48-
+ "**/build/**,"
49-
+ "**/.mvn/**,"
50-
+ ".mvn/**";
51-
5228
@Test
5329
void it_injects_dependency_even_when_no_poms_included() throws Exception {
5430

@@ -125,13 +101,20 @@ void it_transforms_webgoat_normally() throws Exception {
125101
report.getResults().stream()
126102
.map(CodeTFResult::getChangeset)
127103
.flatMap(Collection::stream)
128-
.collect(Collectors.toList());
104+
.toList();
129105

130106
assertThat(fileChanges.size(), is(58));
131107

132108
// we only inject into a couple files
133109
verifyStandardCodemodResults(fileChanges);
134110

111+
// and that we inject the correct pom
112+
assertThat(
113+
fileChanges.stream()
114+
.map(CodeTFChangesetEntry::getPath)
115+
.anyMatch(path -> path.endsWith("insecure-deserialization/pom.xml")),
116+
is(true));
117+
135118
// this file is only changed by including the codeql results, which we didn't do in this test
136119
assertThat(
137120
fileChanges.stream()
@@ -165,9 +148,9 @@ void it_transforms_webgoat_with_codeql() throws Exception {
165148
report.getResults().stream()
166149
.map(CodeTFResult::getChangeset)
167150
.flatMap(Collection::stream)
168-
.collect(Collectors.toList());
151+
.toList();
169152

170-
assertThat(fileChanges.size(), is(62));
153+
assertThat(fileChanges.size(), is(64));
171154

172155
verifyStandardCodemodResults(fileChanges);
173156

@@ -187,37 +170,10 @@ void it_transforms_webgoat_with_codeql() throws Exception {
187170

188171
assertThat(ajaxJwtChange.getChanges().size(), equalTo(1));
189172
assertThat(ajaxJwtChange.getChanges().get(0).getLineNumber(), equalTo(53));
190-
}
191-
192-
private static void verifyStandardCodemodResults(final List<CodeTFChangesetEntry> fileChanges) {
193-
// we only inject into a couple files
194-
assertThat(
195-
fileChanges.stream()
196-
.map(CodeTFChangesetEntry::getPath)
197-
.anyMatch(path -> path.endsWith("SerializationHelper.java")),
198-
is(true));
199173

200-
assertThat(
201-
fileChanges.stream()
202-
.map(CodeTFChangesetEntry::getPath)
203-
.anyMatch(path -> path.endsWith("InsecureDeserializationTask.java")),
204-
is(true));
205-
206-
// and inject the correct pom
207-
208-
assertThat(
209-
fileChanges.stream()
210-
.map(CodeTFChangesetEntry::getPath)
211-
.anyMatch(path -> path.equals("webgoat-lessons/insecure-deserialization/pom.xml")),
212-
is(true));
213-
}
214-
215-
private static void verifyNoFailedFiles(final CodeTFReport report) {
216-
List<String> failedFiles =
217-
report.getResults().stream()
218-
.map(CodeTFResult::getFailedFiles)
219-
.flatMap(Collection::stream)
220-
.toList();
221-
assertThat(failedFiles.size(), is(0));
174+
verifyCodemodsHitWithChangesetCount(report, "codeql:java/insecure-randomness", 0);
175+
verifyCodemodsHitWithChangesetCount(report, "codeql:java/ssrf", 3);
176+
verifyCodemodsHitWithChangesetCount(report, "codeql:java/sql-injection", 5);
177+
verifyCodemodsHitWithChangesetCount(report, "codeql:java/insecure-cookie", 1);
222178
}
223179
}

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

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import io.codemodder.CodeChanger;
44
import io.codemodder.Runner;
5+
import io.codemodder.codemods.codeql.*;
56
import io.codemodder.codemods.semgrep.SemgrepJavaDeserializationCodemod;
67
import io.codemodder.codemods.semgrep.SemgrepMissingSecureFlagCodemod;
78
import io.codemodder.codemods.semgrep.SemgrepReflectionInjectionCodemod;
@@ -25,6 +26,21 @@ public static List<Class<? extends CodeChanger>> asList() {
2526
AddClarifyingBracesCodemod.class,
2627
AddMissingOverrideCodemod.class,
2728
AvoidImplicitPublicConstructorCodemod.class,
29+
CodeQLDeserializationOfUserControlledDataCodemod.class,
30+
CodeQLHttpResponseSplittingCodemod.class,
31+
CodeQLInputResourceLeakCodemod.class,
32+
CodeQLInsecureCookieCodemod.class,
33+
CodeQLInsecureRandomnessCodemod.class,
34+
CodeQLJDBCResourceLeakCodemod.class,
35+
CodeQLJEXLInjectionCodemod.class,
36+
CodeQLJNDIInjectionCodemod.class,
37+
CodeQLMavenSecureURLCodemod.class,
38+
CodeQLOutputResourceLeakCodemod.class,
39+
CodeQLSQLInjectionCodemod.class,
40+
CodeQLSSRFCodemod.class,
41+
CodeQLStackTraceExposureCodemod.class,
42+
CodeQLUnverifiedJwtCodemod.class,
43+
CodeQLXXECodemod.class,
2844
DeclareVariableOnSeparateLineCodemod.class,
2945
DefectDojoSqlInjectionCodemod.class,
3046
DefineConstantForLiteralCodemod.class,
@@ -39,14 +55,8 @@ public static List<Class<? extends CodeChanger>> asList() {
3955
HardenXStreamCodemod.class,
4056
HardenZipEntryPathsCodemod.class,
4157
HQLParameterizationCodemod.class,
42-
InputResourceLeakCodemod.class,
43-
InsecureCookieCodemod.class,
44-
JDBCResourceLeakCodemod.class,
45-
JEXLInjectionCodemod.class,
4658
JSPScriptletXSSCodemod.class,
4759
LimitReadlineCodemod.class,
48-
MavenSecureURLCodemod.class,
49-
OutputResourceLeakCodemod.class,
5060
OverridesMatchParentSynchronizationCodemod.class,
5161
PreventFileWriterLeakWithFilesCodemod.class,
5262
RandomizeSeedCodemod.class,
@@ -83,10 +93,8 @@ public static List<Class<? extends CodeChanger>> asList() {
8393
SonarXXECodemod.class,
8494
SQLParameterizerCodemod.class,
8595
SSRFCodemod.class,
86-
StackTraceExposureCodemod.class,
8796
SwitchLiteralFirstComparisonsCodemod.class,
8897
SwitchToStandardCharsetsCodemod.class,
89-
UnverifiedJwtCodemod.class,
9098
UpgradeSSLContextTLSCodemod.class,
9199
UpgradeSSLEngineTLSCodemod.class,
92100
UpgradeSSLParametersTLSCodemod.class,

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
import io.codemodder.*;
1414
import io.codemodder.javaparser.ChangesResult;
1515
import io.codemodder.providers.sarif.semgrep.SemgrepScan;
16+
import io.codemodder.remediation.resourceleak.ResourceLeakFixer;
1617
import java.io.File;
1718
import java.io.Writer;
1819
import javax.inject.Inject;

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import com.github.javaparser.ast.expr.Expression;
55
import io.codemodder.*;
66
import io.codemodder.javaparser.JavaParserChanger;
7+
import io.codemodder.remediation.resourceleak.ResourceLeakFixer;
78
import java.util.List;
89
import java.util.Optional;
910
import java.util.stream.Collectors;
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
package io.codemodder.codemods.codeql;
2+
3+
import com.github.javaparser.ast.CompilationUnit;
4+
import io.codemodder.*;
5+
import io.codemodder.codetf.DetectorRule;
6+
import io.codemodder.providers.sarif.codeql.ProvidedCodeQLScan;
7+
import io.codemodder.remediation.GenericRemediationMetadata;
8+
import io.codemodder.remediation.javadeserialization.JavaDeserializationRemediator;
9+
import javax.inject.Inject;
10+
11+
/** A codemod for automatically fixing untrusted deserialization from CodeQL. */
12+
@Codemod(
13+
id = "codeql:java/unsafe-deserialization",
14+
reviewGuidance = ReviewGuidance.MERGE_WITHOUT_REVIEW,
15+
importance = Importance.HIGH,
16+
executionPriority = CodemodExecutionPriority.HIGH)
17+
public final class CodeQLDeserializationOfUserControlledDataCodemod
18+
extends CodeQLRemediationCodemod {
19+
20+
private final JavaDeserializationRemediator remediator;
21+
22+
@Inject
23+
public CodeQLDeserializationOfUserControlledDataCodemod(
24+
@ProvidedCodeQLScan(ruleId = "java/unsafe-deserialization") final RuleSarif sarif) {
25+
super(GenericRemediationMetadata.DESERIALIZATION.reporter(), sarif);
26+
this.remediator = JavaDeserializationRemediator.DEFAULT;
27+
}
28+
29+
@Override
30+
public DetectorRule detectorRule() {
31+
return new DetectorRule(
32+
"unsafe-deserialization",
33+
"Deserialization of user-controlled data",
34+
"https://codeql.github.com/codeql-query-help/java/java-unsafe-deserialization/");
35+
}
36+
37+
@Override
38+
public CodemodFileScanningResult visit(
39+
final CodemodInvocationContext context, final CompilationUnit cu) {
40+
return remediator.remediateAll(
41+
cu,
42+
context.path().toString(),
43+
detectorRule(),
44+
ruleSarif.getResultsByLocationPath(context.path()),
45+
SarifFindingKeyUtil::buildFindingId,
46+
r -> r.getLocations().get(0).getPhysicalLocation().getRegion().getStartLine(),
47+
r -> r.getLocations().get(0).getPhysicalLocation().getRegion().getEndLine(),
48+
r -> r.getLocations().get(0).getPhysicalLocation().getRegion().getStartColumn());
49+
}
50+
}

0 commit comments

Comments
 (0)