Skip to content

Commit

Permalink
✨ Open Source Semgrep codemods (#448)
Browse files Browse the repository at this point in the history
- **:truck: open source semgrep codemods**
- **:sparkles: add semgrep codemods**
  • Loading branch information
ryandens authored Sep 18, 2024
1 parent 50cf515 commit 9196cb2
Show file tree
Hide file tree
Showing 59 changed files with 299,685 additions and 4 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/checks.yml
Original file line number Diff line number Diff line change
Expand Up @@ -61,10 +61,10 @@ jobs:
- name: Setup Python
uses: actions/setup-python@v4
with:
python-version: '3.10'
python-version: '3.11'

- name: Install Semgrep
run: python3 -m pip install semgrep==1.15.0
run: python3 -m pip install semgrep

- name: Run Check task
uses: gradle/gradle-build-action@842c587ad8aa4c68eeba24c396e15af4c2e9f30a # v2.9.0
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ jobs:

- uses: actions/setup-python@v4
with:
python-version: '3.10'
python-version: '3.11'

- name: Install Semgrep
run: python3 -m pip install semgrep
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,15 @@

import io.codemodder.CodeChanger;
import io.codemodder.Runner;
import io.codemodder.codemods.semgrep.SemgrepJavaDeserializationCodemod;
import io.codemodder.codemods.semgrep.SemgrepMissingSecureFlagCodemod;
import io.codemodder.codemods.semgrep.SemgrepReflectionInjectionCodemod;
import io.codemodder.codemods.semgrep.SemgrepSQLInjectionCodemod;
import io.codemodder.codemods.semgrep.SemgrepSQLInjectionFormattedSqlStringCodemod;
import io.codemodder.codemods.semgrep.SemgrepSSRFCodemod;
import io.codemodder.codemods.semgrep.SemgrepServletResponseWriterXSSCodemod;
import io.codemodder.codemods.semgrep.SemgrepWeakRandomCodemod;
import io.codemodder.codemods.semgrep.SemgrepXXECodemod;
import java.util.List;

/**
Expand Down Expand Up @@ -53,6 +62,15 @@ public static List<Class<? extends CodeChanger>> asList() {
SanitizeHttpHeaderCodemod.class,
SanitizeSpringMultipartFilenameCodemod.class,
SecureRandomCodemod.class,
SemgrepJavaDeserializationCodemod.class,
SemgrepMissingSecureFlagCodemod.class,
SemgrepReflectionInjectionCodemod.class,
SemgrepServletResponseWriterXSSCodemod.class,
SemgrepSSRFCodemod.class,
SemgrepSQLInjectionCodemod.class,
SemgrepSQLInjectionFormattedSqlStringCodemod.class,
SemgrepWeakRandomCodemod.class,
SemgrepXXECodemod.class,
SemgrepOverlyPermissiveFilePermissionsCodemod.class,
SimplifyRestControllerAnnotationsCodemod.class,
SubstituteReplaceAllCodemod.class,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
package io.codemodder.codemods.remediators.ssrf;

import static io.codemodder.ast.ASTTransforms.addImportIfMissing;

import com.github.javaparser.ast.CompilationUnit;
import com.github.javaparser.ast.NodeList;
import com.github.javaparser.ast.expr.Expression;
import com.github.javaparser.ast.expr.FieldAccessExpr;
import com.github.javaparser.ast.expr.MethodCallExpr;
import com.github.javaparser.ast.expr.NameExpr;
import com.github.javaparser.ast.expr.ObjectCreationExpr;
import io.codemodder.CodemodChange;
import io.codemodder.CodemodFileScanningResult;
import io.codemodder.DependencyGAV;
import io.codemodder.codetf.DetectorRule;
import io.codemodder.codetf.FixedFinding;
import io.codemodder.codetf.UnfixedFinding;
import io.codemodder.remediation.FixCandidate;
import io.codemodder.remediation.FixCandidateSearchResults;
import io.codemodder.remediation.FixCandidateSearcher;
import io.github.pixee.security.HostValidator;
import io.github.pixee.security.Urls;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Function;

final class DefaultSSRFRemediator implements SSRFRemediator {

@Override
public <T> CodemodFileScanningResult remediateAll(
final CompilationUnit cu,
final String path,
final DetectorRule detectorRule,
final List<T> issuesForFile,
final Function<T, String> getKey,
final Function<T, Integer> getStartLine,
final Function<T, Integer> getEndLine,
final Function<T, Integer> getStartColumn) {

// search for new URL() calls on those lines, assuming the tool points there -- there are plenty
// of more signatures to chase down
FixCandidateSearcher<T> searcher =
new FixCandidateSearcher.Builder<T>()
.withMatcher(mce -> mce.isConstructorForType("URL"))
.withMatcher(mce -> !mce.getArguments().isEmpty())
.build();

FixCandidateSearchResults<T> results =
searcher.search(
cu,
path,
detectorRule,
issuesForFile,
getKey,
getStartLine,
getEndLine,
getStartColumn);

List<CodemodChange> changes = new ArrayList<>();

for (FixCandidate<T> candidate : results.fixCandidates()) {
ObjectCreationExpr call = (ObjectCreationExpr) candidate.call().asNode();
List<T> issues = candidate.issues();
harden(cu, call);
List<FixedFinding> fixedFindings =
issues.stream()
.map(issue -> new FixedFinding(getKey.apply(issue), detectorRule))
.toList();
CodemodChange change =
CodemodChange.from(
getStartLine.apply(issues.get(0)),
List.of(DependencyGAV.JAVA_SECURITY_TOOLKIT),
fixedFindings);
changes.add(change);
}

List<UnfixedFinding> unfixedFindings = new ArrayList<>(results.unfixableFindings());
return CodemodFileScanningResult.from(changes, unfixedFindings);
}

private void harden(final CompilationUnit cu, final ObjectCreationExpr newUrlCall) {
NodeList<Expression> arguments = newUrlCall.getArguments();

/*
* We need to replace:
*
* URL u = new URL(foo)
*
* With:
*
* import io.github.pixee.security.Urls;
* ...
* URL u = Urls.create(foo, io.github.pixee.security.Urls.HTTP_PROTOCOLS, io.github.pixee.security.HostValidator.ALLOW_ALL)
*/
addImportIfMissing(cu, Urls.class.getName());
addImportIfMissing(cu, HostValidator.class.getName());
FieldAccessExpr httpProtocolsExpr = new FieldAccessExpr();
httpProtocolsExpr.setScope(new NameExpr(Urls.class.getSimpleName()));
httpProtocolsExpr.setName("HTTP_PROTOCOLS");

FieldAccessExpr denyCommonTargetsExpr = new FieldAccessExpr();

denyCommonTargetsExpr.setScope(new NameExpr(HostValidator.class.getSimpleName()));
denyCommonTargetsExpr.setName("DENY_COMMON_INFRASTRUCTURE_TARGETS");

NodeList<Expression> newArguments = new NodeList<>();
newArguments.addAll(arguments); // first are all the arguments they were passing to "new URL"
newArguments.add(httpProtocolsExpr); // load the protocols they're allowed
newArguments.add(denyCommonTargetsExpr); // load the host validator
MethodCallExpr safeCall =
new MethodCallExpr(new NameExpr(Urls.class.getSimpleName()), "create", newArguments);
newUrlCall.replace(safeCall);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package io.codemodder.codemods.remediators.ssrf;

import com.github.javaparser.ast.CompilationUnit;
import io.codemodder.CodemodFileScanningResult;
import io.codemodder.codetf.DetectorRule;
import java.util.List;
import java.util.function.Function;

/** Fixes SSRF vulnerabilities. */
public interface SSRFRemediator {

/** A default implementation for callers. */
SSRFRemediator DEFAULT = new DefaultSSRFRemediator();

/** Remediate all SSRF vulnerabilities in the given compilation unit. */
<T> CodemodFileScanningResult remediateAll(
CompilationUnit cu,
String path,
DetectorRule detectorRule,
List<T> issuesForFile,
Function<T, String> getKey,
Function<T, Integer> getStartLine,
Function<T, Integer> getEndLine,
Function<T, Integer> getColumn);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
package io.codemodder.codemods.remediators.weakrandom;

import static io.codemodder.ast.ASTTransforms.addImportIfMissing;

import com.github.javaparser.ast.CompilationUnit;
import com.github.javaparser.ast.expr.ObjectCreationExpr;
import io.codemodder.CodemodChange;
import io.codemodder.CodemodFileScanningResult;
import io.codemodder.codetf.DetectorRule;
import io.codemodder.codetf.FixedFinding;
import io.codemodder.codetf.UnfixedFinding;
import io.codemodder.remediation.RemediationMessages;
import java.security.SecureRandom;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Function;

final class DefaultWeakRandomRemediator implements WeakRandomRemediator {

@Override
public <T> CodemodFileScanningResult remediateAll(
final CompilationUnit cu,
final String path,
final DetectorRule detectorRule,
final List<T> issuesForFile,
final Function<T, String> getKey,
final Function<T, Integer> getLine,
final Function<T, Integer> getColumn) {

List<UnfixedFinding> unfixedFindings = new ArrayList<>();
List<CodemodChange> changes = new ArrayList<>();

for (T issue : issuesForFile) {

List<ObjectCreationExpr> unsafeRandoms =
cu.findAll(ObjectCreationExpr.class).stream()
.filter(oc -> oc.getType().asString().equals("Random"))
.filter(oc -> getLine.apply(issue) == oc.getRange().get().begin.line)
.filter(
oc -> {
Integer column = getColumn.apply(issue);
return column == null || column == oc.getRange().get().begin.column;
})
.toList();

if (unsafeRandoms.size() > 1) {
unfixedFindings.add(
new UnfixedFinding(
getKey.apply(issue),
detectorRule,
path,
getLine.apply(issue),
RemediationMessages.multipleCallsFound));
continue;
} else if (unsafeRandoms.isEmpty()) {
unfixedFindings.add(
new UnfixedFinding(
getKey.apply(issue),
detectorRule,
path,
getLine.apply(issue),
RemediationMessages.noCallsAtThatLocation));
continue;
}

ObjectCreationExpr unsafeRandom = unsafeRandoms.get(0);
unsafeRandom.setType("SecureRandom");
addImportIfMissing(cu, SecureRandom.class.getName());
changes.add(
CodemodChange.from(
getLine.apply(issue),
List.of(),
List.of(new FixedFinding(getKey.apply(issue), detectorRule))));
}

return CodemodFileScanningResult.from(changes, unfixedFindings);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package io.codemodder.codemods.remediators.weakrandom;

import com.github.javaparser.ast.CompilationUnit;
import io.codemodder.CodemodFileScanningResult;
import io.codemodder.codetf.DetectorRule;
import java.util.List;
import java.util.function.Function;

/** Fixes weak randomness. */
public interface WeakRandomRemediator {

/** A default implementation for callers. */
WeakRandomRemediator DEFAULT = new DefaultWeakRandomRemediator();

/** Remediate all weak random vulnerabilities in the given compilation unit. */
<T> CodemodFileScanningResult remediateAll(
CompilationUnit cu,
String path,
DetectorRule detectorRule,
List<T> issuesForFile,
Function<T, String> getKey,
Function<T, Integer> getLine,
Function<T, Integer> getColumn);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package io.codemodder.codemods.semgrep;

import com.github.javaparser.ast.CompilationUnit;
import io.codemodder.Codemod;
import io.codemodder.CodemodExecutionPriority;
import io.codemodder.CodemodFileScanningResult;
import io.codemodder.CodemodInvocationContext;
import io.codemodder.Importance;
import io.codemodder.ReviewGuidance;
import io.codemodder.RuleSarif;
import io.codemodder.SarifFindingKeyUtil;
import io.codemodder.codetf.DetectorRule;
import io.codemodder.providers.sarif.semgrep.ProvidedSemgrepScan;
import io.codemodder.remediation.GenericRemediationMetadata;
import io.codemodder.remediation.javadeserialization.JavaDeserializationRemediator;
import javax.inject.Inject;

/**
* Fixes some Semgrep issues reported under the id
* "java.lang.security.audit.object-deserialization.object-deserialization".
*/
@Codemod(
id = "semgrep:java/java.lang.security.audit.object-deserialization.object-deserialization",
reviewGuidance = ReviewGuidance.MERGE_WITHOUT_REVIEW,
executionPriority = CodemodExecutionPriority.HIGH,
importance = Importance.HIGH)
public final class SemgrepJavaDeserializationCodemod extends SemgrepJavaParserChanger {

private final JavaDeserializationRemediator remediator;

@Inject
public SemgrepJavaDeserializationCodemod(
@ProvidedSemgrepScan(
ruleId = "java.lang.security.audit.object-deserialization.object-deserialization")
final RuleSarif sarif) {
super(GenericRemediationMetadata.DESERIALIZATION.reporter(), sarif);
this.remediator = JavaDeserializationRemediator.DEFAULT;
}

@Override
public DetectorRule detectorRule() {
return new DetectorRule(
ruleSarif.getRule(),
"Insecure Deserialization",
"https://semgrep.dev/playground/r/java.lang.security.audit.object-deserialization.object-deserialization");
}

@Override
public CodemodFileScanningResult visit(
final CodemodInvocationContext context, final CompilationUnit cu) {
return remediator.remediateAll(
cu,
context.path().toString(),
detectorRule(),
ruleSarif.getResultsByLocationPath(context.path()),
SarifFindingKeyUtil::buildFindingId,
r -> r.getLocations().get(0).getPhysicalLocation().getRegion().getStartLine(),
r -> r.getLocations().get(0).getPhysicalLocation().getRegion().getEndLine(),
r -> r.getLocations().get(0).getPhysicalLocation().getRegion().getStartColumn());
}
}
Loading

0 comments on commit 9196cb2

Please sign in to comment.