All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.codemodder.codemods.HardenJavaDeserializationCodemod Maven / Gradle / Ivy

There is a newer version: 0.97.3
Show newest version
package io.codemodder.codemods;

import static io.codemodder.ast.ASTTransforms.addImportIfMissing;
import static io.codemodder.javaparser.JavaParserTransformer.replace;

import com.contrastsecurity.sarif.Result;
import com.github.javaparser.ast.CompilationUnit;
import com.github.javaparser.ast.Node;
import com.github.javaparser.ast.body.FieldDeclaration;
import com.github.javaparser.ast.body.VariableDeclarator;
import com.github.javaparser.ast.expr.*;
import com.github.javaparser.ast.stmt.BlockStmt;
import com.github.javaparser.ast.stmt.ExpressionStmt;
import com.github.javaparser.ast.stmt.Statement;
import com.github.javaparser.ast.stmt.TryStmt;
import io.codemodder.*;
import io.codemodder.ast.ASTTransforms;
import io.codemodder.javaparser.ChangesResult;
import io.codemodder.providers.sarif.semgrep.SemgrepScan;
import io.github.pixee.security.ObjectInputFilters;
import java.io.ObjectInputStream;
import java.util.List;
import java.util.Optional;
import javax.inject.Inject;

/** Adds gadget filtering logic to {@link java.io.ObjectInputStream}. */
@Codemod(
    id = "pixee:java/harden-java-deserialization",
    importance = Importance.HIGH,
    reviewGuidance = ReviewGuidance.MERGE_WITHOUT_REVIEW)
public final class HardenJavaDeserializationCodemod extends CompositeJavaParserChanger {

  @Inject
  public HardenJavaDeserializationCodemod(
      final VariableDeclarationDeserializationShapeChanger varDeclChanger,
      final AnonymousDeserializationShapeChanger anonymousChanger) {
    super(varDeclChanger, anonymousChanger);
  }

  /**
   * This variant is used in situations where the deserialization is done with an unnamed stack
   * variable.
   *
   * 
{@code
   * LOG.info("Deserialized object: {}", new ObjectInputStream(httpServletRequest.getInputStream()).readObject());
   * }
*/ private static class AnonymousDeserializationShapeChanger extends SarifPluginJavaParserChanger { @Inject public AnonymousDeserializationShapeChanger( @SemgrepScan(ruleId = "harden-java-deserialization-anonymous") final RuleSarif sarif) { super( sarif, ObjectCreationExpr.class, RegionNodeMatcher.MATCHES_START, CodemodReporterStrategy.empty()); } @Override public ChangesResult onResultFound( final CodemodInvocationContext context, final CompilationUnit cu, final ObjectCreationExpr objectCreationExpr, final Result result) { replace(objectCreationExpr) .withStaticMethod(ObjectInputFilters.class.getName(), "createSafeObjectInputStream") .withStaticImport() .withSameArguments(); return ChangesResult.changesAppliedWith(List.of(DependencyGAV.JAVA_SECURITY_TOOLKIT)); } } private static final class VariableDeclarationDeserializationShapeChanger extends SarifPluginJavaParserChanger { @Inject public VariableDeclarationDeserializationShapeChanger( @SemgrepScan(ruleId = "harden-java-deserialization") final RuleSarif sarif) { super( sarif, VariableDeclarator.class, RegionNodeMatcher.MATCHES_START, CodemodReporterStrategy.empty()); } @Override public ChangesResult onResultFound( final CodemodInvocationContext context, final CompilationUnit cu, final VariableDeclarator variableDeclarator, final Result result) { Statement newStatement = generateFilterHardeningStatement(variableDeclarator.getNameAsExpression()); Optional wrappedParentNode = variableDeclarator.getParentNode(); if (wrappedParentNode.isEmpty()) { return ChangesResult.noChanges; } Node parentNode = wrappedParentNode.get(); Class parentType = parentNode.getClass(); if (FieldDeclaration.class.equals(parentType)) { return ChangesResult.noChanges; } if (VariableDeclarationExpr.class.equals(parentType)) { Node variableDeclarationParent = parentNode.getParentNode().get(); Class variableDeclarationParentType = variableDeclarationParent.getClass(); if (ExpressionStmt.class.equals(variableDeclarationParentType)) { ExpressionStmt expressionStmt = (ExpressionStmt) variableDeclarationParent; ASTTransforms.addStatementAfterStatement(expressionStmt, newStatement); addImportIfMissing(cu, ObjectInputFilters.class.getName()); return ChangesResult.changesAppliedWith(dependency); } // if we're not in an expression statement, we might be in a try-with-resources statement if (TryStmt.class.equals(variableDeclarationParentType)) { TryStmt tryStatement = (TryStmt) variableDeclarationParent; BlockStmt tryBlock = tryStatement.getTryBlock(); ASTTransforms.addStatementBeforeStatement(tryBlock.getStatements().get(0), newStatement); addImportIfMissing(cu, ObjectInputFilters.class.getName()); return ChangesResult.changesAppliedWith(dependency); } } return ChangesResult.noChanges; } /** * Generates an expression to invoke {@link * ObjectInputFilters#enableObjectFilterIfUnprotected(ObjectInputStream)} on the original scope * (the {@link ObjectInputStream}). */ private Statement generateFilterHardeningStatement(final Expression originalScope) { // this statement is the callback to our hardening code var callbackClass = new NameExpr(ObjectInputFilters.class.getSimpleName()); var hardenStatement = new MethodCallExpr(callbackClass, "enableObjectFilterIfUnprotected"); hardenStatement.addArgument(originalScope); return new ExpressionStmt(hardenStatement); } private static final List dependency = List.of(DependencyGAV.JAVA_SECURITY_TOOLKIT); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy