Skip to content

Commit 9196cb2

Browse files
authored
✨ Open Source Semgrep codemods (#448)
- **:truck: open source semgrep codemods** - **:sparkles: add semgrep codemods**
1 parent 50cf515 commit 9196cb2

File tree

59 files changed

+299685
-4
lines changed

Some content is hidden

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

59 files changed

+299685
-4
lines changed

.github/workflows/checks.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,10 +61,10 @@ jobs:
6161
- name: Setup Python
6262
uses: actions/setup-python@v4
6363
with:
64-
python-version: '3.10'
64+
python-version: '3.11'
6565

6666
- name: Install Semgrep
67-
run: python3 -m pip install semgrep==1.15.0
67+
run: python3 -m pip install semgrep
6868

6969
- name: Run Check task
7070
uses: gradle/gradle-build-action@842c587ad8aa4c68eeba24c396e15af4c2e9f30a # v2.9.0

.github/workflows/release.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ jobs:
2525

2626
- uses: actions/setup-python@v4
2727
with:
28-
python-version: '3.10'
28+
python-version: '3.11'
2929

3030
- name: Install Semgrep
3131
run: python3 -m pip install semgrep

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

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,15 @@
22

33
import io.codemodder.CodeChanger;
44
import io.codemodder.Runner;
5+
import io.codemodder.codemods.semgrep.SemgrepJavaDeserializationCodemod;
6+
import io.codemodder.codemods.semgrep.SemgrepMissingSecureFlagCodemod;
7+
import io.codemodder.codemods.semgrep.SemgrepReflectionInjectionCodemod;
8+
import io.codemodder.codemods.semgrep.SemgrepSQLInjectionCodemod;
9+
import io.codemodder.codemods.semgrep.SemgrepSQLInjectionFormattedSqlStringCodemod;
10+
import io.codemodder.codemods.semgrep.SemgrepSSRFCodemod;
11+
import io.codemodder.codemods.semgrep.SemgrepServletResponseWriterXSSCodemod;
12+
import io.codemodder.codemods.semgrep.SemgrepWeakRandomCodemod;
13+
import io.codemodder.codemods.semgrep.SemgrepXXECodemod;
514
import java.util.List;
615

716
/**
@@ -53,6 +62,15 @@ public static List<Class<? extends CodeChanger>> asList() {
5362
SanitizeHttpHeaderCodemod.class,
5463
SanitizeSpringMultipartFilenameCodemod.class,
5564
SecureRandomCodemod.class,
65+
SemgrepJavaDeserializationCodemod.class,
66+
SemgrepMissingSecureFlagCodemod.class,
67+
SemgrepReflectionInjectionCodemod.class,
68+
SemgrepServletResponseWriterXSSCodemod.class,
69+
SemgrepSSRFCodemod.class,
70+
SemgrepSQLInjectionCodemod.class,
71+
SemgrepSQLInjectionFormattedSqlStringCodemod.class,
72+
SemgrepWeakRandomCodemod.class,
73+
SemgrepXXECodemod.class,
5674
SemgrepOverlyPermissiveFilePermissionsCodemod.class,
5775
SimplifyRestControllerAnnotationsCodemod.class,
5876
SubstituteReplaceAllCodemod.class,
Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
package io.codemodder.codemods.remediators.ssrf;
2+
3+
import static io.codemodder.ast.ASTTransforms.addImportIfMissing;
4+
5+
import com.github.javaparser.ast.CompilationUnit;
6+
import com.github.javaparser.ast.NodeList;
7+
import com.github.javaparser.ast.expr.Expression;
8+
import com.github.javaparser.ast.expr.FieldAccessExpr;
9+
import com.github.javaparser.ast.expr.MethodCallExpr;
10+
import com.github.javaparser.ast.expr.NameExpr;
11+
import com.github.javaparser.ast.expr.ObjectCreationExpr;
12+
import io.codemodder.CodemodChange;
13+
import io.codemodder.CodemodFileScanningResult;
14+
import io.codemodder.DependencyGAV;
15+
import io.codemodder.codetf.DetectorRule;
16+
import io.codemodder.codetf.FixedFinding;
17+
import io.codemodder.codetf.UnfixedFinding;
18+
import io.codemodder.remediation.FixCandidate;
19+
import io.codemodder.remediation.FixCandidateSearchResults;
20+
import io.codemodder.remediation.FixCandidateSearcher;
21+
import io.github.pixee.security.HostValidator;
22+
import io.github.pixee.security.Urls;
23+
import java.util.ArrayList;
24+
import java.util.List;
25+
import java.util.function.Function;
26+
27+
final class DefaultSSRFRemediator implements SSRFRemediator {
28+
29+
@Override
30+
public <T> CodemodFileScanningResult remediateAll(
31+
final CompilationUnit cu,
32+
final String path,
33+
final DetectorRule detectorRule,
34+
final List<T> issuesForFile,
35+
final Function<T, String> getKey,
36+
final Function<T, Integer> getStartLine,
37+
final Function<T, Integer> getEndLine,
38+
final Function<T, Integer> getStartColumn) {
39+
40+
// search for new URL() calls on those lines, assuming the tool points there -- there are plenty
41+
// of more signatures to chase down
42+
FixCandidateSearcher<T> searcher =
43+
new FixCandidateSearcher.Builder<T>()
44+
.withMatcher(mce -> mce.isConstructorForType("URL"))
45+
.withMatcher(mce -> !mce.getArguments().isEmpty())
46+
.build();
47+
48+
FixCandidateSearchResults<T> results =
49+
searcher.search(
50+
cu,
51+
path,
52+
detectorRule,
53+
issuesForFile,
54+
getKey,
55+
getStartLine,
56+
getEndLine,
57+
getStartColumn);
58+
59+
List<CodemodChange> changes = new ArrayList<>();
60+
61+
for (FixCandidate<T> candidate : results.fixCandidates()) {
62+
ObjectCreationExpr call = (ObjectCreationExpr) candidate.call().asNode();
63+
List<T> issues = candidate.issues();
64+
harden(cu, call);
65+
List<FixedFinding> fixedFindings =
66+
issues.stream()
67+
.map(issue -> new FixedFinding(getKey.apply(issue), detectorRule))
68+
.toList();
69+
CodemodChange change =
70+
CodemodChange.from(
71+
getStartLine.apply(issues.get(0)),
72+
List.of(DependencyGAV.JAVA_SECURITY_TOOLKIT),
73+
fixedFindings);
74+
changes.add(change);
75+
}
76+
77+
List<UnfixedFinding> unfixedFindings = new ArrayList<>(results.unfixableFindings());
78+
return CodemodFileScanningResult.from(changes, unfixedFindings);
79+
}
80+
81+
private void harden(final CompilationUnit cu, final ObjectCreationExpr newUrlCall) {
82+
NodeList<Expression> arguments = newUrlCall.getArguments();
83+
84+
/*
85+
* We need to replace:
86+
*
87+
* URL u = new URL(foo)
88+
*
89+
* With:
90+
*
91+
* import io.github.pixee.security.Urls;
92+
* ...
93+
* URL u = Urls.create(foo, io.github.pixee.security.Urls.HTTP_PROTOCOLS, io.github.pixee.security.HostValidator.ALLOW_ALL)
94+
*/
95+
addImportIfMissing(cu, Urls.class.getName());
96+
addImportIfMissing(cu, HostValidator.class.getName());
97+
FieldAccessExpr httpProtocolsExpr = new FieldAccessExpr();
98+
httpProtocolsExpr.setScope(new NameExpr(Urls.class.getSimpleName()));
99+
httpProtocolsExpr.setName("HTTP_PROTOCOLS");
100+
101+
FieldAccessExpr denyCommonTargetsExpr = new FieldAccessExpr();
102+
103+
denyCommonTargetsExpr.setScope(new NameExpr(HostValidator.class.getSimpleName()));
104+
denyCommonTargetsExpr.setName("DENY_COMMON_INFRASTRUCTURE_TARGETS");
105+
106+
NodeList<Expression> newArguments = new NodeList<>();
107+
newArguments.addAll(arguments); // first are all the arguments they were passing to "new URL"
108+
newArguments.add(httpProtocolsExpr); // load the protocols they're allowed
109+
newArguments.add(denyCommonTargetsExpr); // load the host validator
110+
MethodCallExpr safeCall =
111+
new MethodCallExpr(new NameExpr(Urls.class.getSimpleName()), "create", newArguments);
112+
newUrlCall.replace(safeCall);
113+
}
114+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package io.codemodder.codemods.remediators.ssrf;
2+
3+
import com.github.javaparser.ast.CompilationUnit;
4+
import io.codemodder.CodemodFileScanningResult;
5+
import io.codemodder.codetf.DetectorRule;
6+
import java.util.List;
7+
import java.util.function.Function;
8+
9+
/** Fixes SSRF vulnerabilities. */
10+
public interface SSRFRemediator {
11+
12+
/** A default implementation for callers. */
13+
SSRFRemediator DEFAULT = new DefaultSSRFRemediator();
14+
15+
/** Remediate all SSRF vulnerabilities in the given compilation unit. */
16+
<T> CodemodFileScanningResult remediateAll(
17+
CompilationUnit cu,
18+
String path,
19+
DetectorRule detectorRule,
20+
List<T> issuesForFile,
21+
Function<T, String> getKey,
22+
Function<T, Integer> getStartLine,
23+
Function<T, Integer> getEndLine,
24+
Function<T, Integer> getColumn);
25+
}
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
package io.codemodder.codemods.remediators.weakrandom;
2+
3+
import static io.codemodder.ast.ASTTransforms.addImportIfMissing;
4+
5+
import com.github.javaparser.ast.CompilationUnit;
6+
import com.github.javaparser.ast.expr.ObjectCreationExpr;
7+
import io.codemodder.CodemodChange;
8+
import io.codemodder.CodemodFileScanningResult;
9+
import io.codemodder.codetf.DetectorRule;
10+
import io.codemodder.codetf.FixedFinding;
11+
import io.codemodder.codetf.UnfixedFinding;
12+
import io.codemodder.remediation.RemediationMessages;
13+
import java.security.SecureRandom;
14+
import java.util.ArrayList;
15+
import java.util.List;
16+
import java.util.function.Function;
17+
18+
final class DefaultWeakRandomRemediator implements WeakRandomRemediator {
19+
20+
@Override
21+
public <T> CodemodFileScanningResult remediateAll(
22+
final CompilationUnit cu,
23+
final String path,
24+
final DetectorRule detectorRule,
25+
final List<T> issuesForFile,
26+
final Function<T, String> getKey,
27+
final Function<T, Integer> getLine,
28+
final Function<T, Integer> getColumn) {
29+
30+
List<UnfixedFinding> unfixedFindings = new ArrayList<>();
31+
List<CodemodChange> changes = new ArrayList<>();
32+
33+
for (T issue : issuesForFile) {
34+
35+
List<ObjectCreationExpr> unsafeRandoms =
36+
cu.findAll(ObjectCreationExpr.class).stream()
37+
.filter(oc -> oc.getType().asString().equals("Random"))
38+
.filter(oc -> getLine.apply(issue) == oc.getRange().get().begin.line)
39+
.filter(
40+
oc -> {
41+
Integer column = getColumn.apply(issue);
42+
return column == null || column == oc.getRange().get().begin.column;
43+
})
44+
.toList();
45+
46+
if (unsafeRandoms.size() > 1) {
47+
unfixedFindings.add(
48+
new UnfixedFinding(
49+
getKey.apply(issue),
50+
detectorRule,
51+
path,
52+
getLine.apply(issue),
53+
RemediationMessages.multipleCallsFound));
54+
continue;
55+
} else if (unsafeRandoms.isEmpty()) {
56+
unfixedFindings.add(
57+
new UnfixedFinding(
58+
getKey.apply(issue),
59+
detectorRule,
60+
path,
61+
getLine.apply(issue),
62+
RemediationMessages.noCallsAtThatLocation));
63+
continue;
64+
}
65+
66+
ObjectCreationExpr unsafeRandom = unsafeRandoms.get(0);
67+
unsafeRandom.setType("SecureRandom");
68+
addImportIfMissing(cu, SecureRandom.class.getName());
69+
changes.add(
70+
CodemodChange.from(
71+
getLine.apply(issue),
72+
List.of(),
73+
List.of(new FixedFinding(getKey.apply(issue), detectorRule))));
74+
}
75+
76+
return CodemodFileScanningResult.from(changes, unfixedFindings);
77+
}
78+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package io.codemodder.codemods.remediators.weakrandom;
2+
3+
import com.github.javaparser.ast.CompilationUnit;
4+
import io.codemodder.CodemodFileScanningResult;
5+
import io.codemodder.codetf.DetectorRule;
6+
import java.util.List;
7+
import java.util.function.Function;
8+
9+
/** Fixes weak randomness. */
10+
public interface WeakRandomRemediator {
11+
12+
/** A default implementation for callers. */
13+
WeakRandomRemediator DEFAULT = new DefaultWeakRandomRemediator();
14+
15+
/** Remediate all weak random vulnerabilities in the given compilation unit. */
16+
<T> CodemodFileScanningResult remediateAll(
17+
CompilationUnit cu,
18+
String path,
19+
DetectorRule detectorRule,
20+
List<T> issuesForFile,
21+
Function<T, String> getKey,
22+
Function<T, Integer> getLine,
23+
Function<T, Integer> getColumn);
24+
}
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
package io.codemodder.codemods.semgrep;
2+
3+
import com.github.javaparser.ast.CompilationUnit;
4+
import io.codemodder.Codemod;
5+
import io.codemodder.CodemodExecutionPriority;
6+
import io.codemodder.CodemodFileScanningResult;
7+
import io.codemodder.CodemodInvocationContext;
8+
import io.codemodder.Importance;
9+
import io.codemodder.ReviewGuidance;
10+
import io.codemodder.RuleSarif;
11+
import io.codemodder.SarifFindingKeyUtil;
12+
import io.codemodder.codetf.DetectorRule;
13+
import io.codemodder.providers.sarif.semgrep.ProvidedSemgrepScan;
14+
import io.codemodder.remediation.GenericRemediationMetadata;
15+
import io.codemodder.remediation.javadeserialization.JavaDeserializationRemediator;
16+
import javax.inject.Inject;
17+
18+
/**
19+
* Fixes some Semgrep issues reported under the id
20+
* "java.lang.security.audit.object-deserialization.object-deserialization".
21+
*/
22+
@Codemod(
23+
id = "semgrep:java/java.lang.security.audit.object-deserialization.object-deserialization",
24+
reviewGuidance = ReviewGuidance.MERGE_WITHOUT_REVIEW,
25+
executionPriority = CodemodExecutionPriority.HIGH,
26+
importance = Importance.HIGH)
27+
public final class SemgrepJavaDeserializationCodemod extends SemgrepJavaParserChanger {
28+
29+
private final JavaDeserializationRemediator remediator;
30+
31+
@Inject
32+
public SemgrepJavaDeserializationCodemod(
33+
@ProvidedSemgrepScan(
34+
ruleId = "java.lang.security.audit.object-deserialization.object-deserialization")
35+
final RuleSarif sarif) {
36+
super(GenericRemediationMetadata.DESERIALIZATION.reporter(), sarif);
37+
this.remediator = JavaDeserializationRemediator.DEFAULT;
38+
}
39+
40+
@Override
41+
public DetectorRule detectorRule() {
42+
return new DetectorRule(
43+
ruleSarif.getRule(),
44+
"Insecure Deserialization",
45+
"https://semgrep.dev/playground/r/java.lang.security.audit.object-deserialization.object-deserialization");
46+
}
47+
48+
@Override
49+
public CodemodFileScanningResult visit(
50+
final CodemodInvocationContext context, final CompilationUnit cu) {
51+
return remediator.remediateAll(
52+
cu,
53+
context.path().toString(),
54+
detectorRule(),
55+
ruleSarif.getResultsByLocationPath(context.path()),
56+
SarifFindingKeyUtil::buildFindingId,
57+
r -> r.getLocations().get(0).getPhysicalLocation().getRegion().getStartLine(),
58+
r -> r.getLocations().get(0).getPhysicalLocation().getRegion().getEndLine(),
59+
r -> r.getLocations().get(0).getPhysicalLocation().getRegion().getStartColumn());
60+
}
61+
}

0 commit comments

Comments
 (0)