From 77dce558c93c0a71fc7e75c715d65b1dca0f9063 Mon Sep 17 00:00:00 2001 From: Arshan Dabirsiaghi Date: Fri, 10 Mar 2023 19:42:36 -0500 Subject: [PATCH 01/19] wip but getting there --- gradle/libs.versions.toml | 6 + languages/codemodder-common/build.gradle.kts | 44 + .../main/java/io/codemodder}/ChangedFile.java | 2 +- .../java/io/codemodder}/DependencyGAV.java | 2 +- .../src/main/java/io/codemodder}/Weave.java | 2 +- .../build.gradle.kts | 52 + .../java/io/codemodder/codemods/Runner.java | 11 + .../codemods/SecureRandomCodemod.java | 49 + .../src/main/resources/secure-random.semgrep | 7 + .../build.gradle.kts | 53 + .../ChangeConstructorTypeVisitor.java | 47 + .../java/io/codemodder/ChangeContext.java | 7 + .../src/main/java/io/codemodder/Changer.java | 4 + .../java/io/codemodder/ChangerHandler.java | 5 + .../src/main/java/io/codemodder/Codemod.java | 40 + .../java/io/codemodder/CodemodInvoker.java | 118 + .../java/io/codemodder/CodemodProvider.java | 22 + .../DependencyManagementProvider.java | 9 + .../java/io/codemodder/JavaParserChanger.java | 15 + .../java/io/codemodder/JavaParserInvoker.java | 3 + .../io/codemodder/JavaParserProvider.java | 19 + .../java/io/codemodder/JavaParserUtils.java | 46 + .../java/io/codemodder/ProjectProvider.java | 17 + .../java/io/codemodder/RawFileChanger.java | 3 + .../java/io/codemodder/ReviewGuidance.java | 8 + .../src/main/java/io/codemodder/Sarif.java | 37 + .../io/codemodder/CodemodInvokerTest.java | 37 + .../build.gradle.kts | 45 + .../sarif/semgrep/DefaultSemgrepRunner.java | 49 + .../semgrep/DefaultSemgrepSarifProvider.java | 41 + .../sarif/semgrep/SemgrepRunner.java | 18 + .../sarif/semgrep/SemgrepSarifProvider.java | 16 + .../semgrep/SemgrepSarifProviderTest.java | 65 + .../src/test/resources/example.semgrep | 7 + .../src/test/resources/webgoat_codeql.sarif | 4490 +++++++++++++++++ languages/java/build.gradle.kts | 7 + .../openpixee/java/CodeTFReportGenerator.java | 2 + .../io/openpixee/java/FileBasedVisitor.java | 1 + .../java/io/openpixee/java/FileWeaver.java | 1 + .../io/openpixee/java/FileWeavingContext.java | 1 + .../io/openpixee/java/JavaFixitCliRun.java | 2 + .../java/io/openpixee/java/JspLineWeave.java | 1 + .../java/io/openpixee/java/SourceWeaver.java | 21 +- .../java/io/openpixee/java/WeavingResult.java | 1 + .../ReflectionInjectionVisitorFactory.java | 4 +- .../codeql/InsecureCookieVisitorFactory.java | 2 +- .../JDBCResourceLeakVisitorFactory.java | 2 +- .../codeql/JEXLInjectionVisitorFactory.java | 4 +- .../plugins/codeql/MavenSecureURLVisitor.java | 4 +- .../StackTraceExposureVisitorFactory.java | 2 +- .../UnverifiedJwtParseVisitorFactory.java | 2 +- .../contrast/JavaXssVisitorFactory.java | 4 +- .../contrast/JspExpressionOutputMethod.java | 2 +- .../SarifBasedJspScriptletXSSVisitor.java | 6 +- .../ApacheMultipartVisitorFactory.java | 4 +- .../DependencyInjectingVisitor.java | 6 +- .../DeserializationVisitorFactory.java | 4 +- .../HeaderInjectionVisitorFactory.java | 4 +- .../JakartaForwardVisitoryFactory.java | 4 +- .../protections/JspScriptletXSSVisitor.java | 2 +- .../PredictableSeedVisitorFactory.java | 2 +- .../java/protections/RegexTextVisitor.java | 6 +- .../RuntimeExecVisitorFactory.java | 4 +- .../SQLParameterizerVisitorFactory.java | 2 +- .../SSLContextGetInstanceVisitorFactory.java | 2 +- .../SSLProtocolsVisitorFactory.java | 2 +- .../java/protections/SSRFVisitorFactory.java | 4 +- .../SpringMultipartVisitorFactory.java | 4 +- .../protections/TransformationResult.java | 2 +- .../UnsafeReadlineVisitorFactory.java | 4 +- .../protections/VerbTamperingVisitor.java | 2 +- .../protections/WeakPRNGVisitorFactory.java | 2 +- .../protections/XMLDecoderVisitorFactory.java | 4 +- .../protections/XStreamVisitorFactory.java | 2 +- .../java/protections/XXEVisitorFactory.java | 4 +- .../ZipFileOverwriteVisitoryFactory.java | 4 +- .../plugins/codeql/MavenSecureURLTest.java | 2 +- .../SarifBasedJspScriptletXSSWeaverTest.java | 6 +- .../DependencyGAVInjectingTest.java | 6 +- .../JspScriptletXSSWeaverTest.java | 4 +- .../java/protections/WeavingTests.java | 2 +- settings.gradle.kts | 2 +- 82 files changed, 5492 insertions(+), 69 deletions(-) create mode 100644 languages/codemodder-common/build.gradle.kts rename languages/{java/src/main/java/io/openpixee/java => codemodder-common/src/main/java/io/codemodder}/ChangedFile.java (98%) rename languages/{java/src/main/java/io/openpixee/java => codemodder-common/src/main/java/io/codemodder}/DependencyGAV.java (98%) rename languages/{java/src/main/java/io/openpixee/java => codemodder-common/src/main/java/io/codemodder}/Weave.java (98%) create mode 100644 languages/codemodder-default-codemods/build.gradle.kts create mode 100644 languages/codemodder-default-codemods/src/main/java/io/codemodder/codemods/Runner.java create mode 100644 languages/codemodder-default-codemods/src/main/java/io/codemodder/codemods/SecureRandomCodemod.java create mode 100644 languages/codemodder-default-codemods/src/main/resources/secure-random.semgrep create mode 100644 languages/codemodder-framework-java/build.gradle.kts create mode 100644 languages/codemodder-framework-java/src/main/java/io/codemodder/ChangeConstructorTypeVisitor.java create mode 100644 languages/codemodder-framework-java/src/main/java/io/codemodder/ChangeContext.java create mode 100644 languages/codemodder-framework-java/src/main/java/io/codemodder/Changer.java create mode 100644 languages/codemodder-framework-java/src/main/java/io/codemodder/ChangerHandler.java create mode 100644 languages/codemodder-framework-java/src/main/java/io/codemodder/Codemod.java create mode 100644 languages/codemodder-framework-java/src/main/java/io/codemodder/CodemodInvoker.java create mode 100644 languages/codemodder-framework-java/src/main/java/io/codemodder/CodemodProvider.java create mode 100644 languages/codemodder-framework-java/src/main/java/io/codemodder/DependencyManagementProvider.java create mode 100644 languages/codemodder-framework-java/src/main/java/io/codemodder/JavaParserChanger.java create mode 100644 languages/codemodder-framework-java/src/main/java/io/codemodder/JavaParserInvoker.java create mode 100644 languages/codemodder-framework-java/src/main/java/io/codemodder/JavaParserProvider.java create mode 100644 languages/codemodder-framework-java/src/main/java/io/codemodder/JavaParserUtils.java create mode 100644 languages/codemodder-framework-java/src/main/java/io/codemodder/ProjectProvider.java create mode 100644 languages/codemodder-framework-java/src/main/java/io/codemodder/RawFileChanger.java create mode 100644 languages/codemodder-framework-java/src/main/java/io/codemodder/ReviewGuidance.java create mode 100644 languages/codemodder-framework-java/src/main/java/io/codemodder/Sarif.java create mode 100644 languages/codemodder-framework-java/src/test/java/io/codemodder/CodemodInvokerTest.java create mode 100644 languages/codemodder-semgrep-provider/build.gradle.kts create mode 100644 languages/codemodder-semgrep-provider/src/main/java/io/codemodder/providers/sarif/semgrep/DefaultSemgrepRunner.java create mode 100644 languages/codemodder-semgrep-provider/src/main/java/io/codemodder/providers/sarif/semgrep/DefaultSemgrepSarifProvider.java create mode 100644 languages/codemodder-semgrep-provider/src/main/java/io/codemodder/providers/sarif/semgrep/SemgrepRunner.java create mode 100644 languages/codemodder-semgrep-provider/src/main/java/io/codemodder/providers/sarif/semgrep/SemgrepSarifProvider.java create mode 100644 languages/codemodder-semgrep-provider/src/test/java/io/codemodder/providers/sarif/semgrep/SemgrepSarifProviderTest.java create mode 100644 languages/codemodder-semgrep-provider/src/test/resources/example.semgrep create mode 100644 languages/codemodder-semgrep-provider/src/test/resources/webgoat_codeql.sarif diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index f6943c900..8cffb308d 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -4,12 +4,15 @@ graalvm = "22.3.0" jackson = "2.13.1" javaparser-core = "3.25.0" javaparser-symbolsolver = "3.15.15" +java-security-toolkit = "1.0.0" +javax-inject = "1" commons-jexl = "3.2.1" logback = "1.4.5" maven = "3.8.7" openpixee-toolkit = "1.0.0" picocli = "4.7.0" slf4j = "2.0.6" +guice = "5.1.0" [libraries] autovalue-annotations = { module = "com.google.auto.value:auto-value-annotations", version.ref = "auto-value" } @@ -20,6 +23,7 @@ commons-collections4 = "org.apache.commons:commons-collections4:4.4" contrast-sarif = "com.contrastsecurity:java-sarif:2.0" graal-sdk = { module = "org.graalvm.sdk:graal-sdk", version.ref = "graalvm" } gson = "com.google.code.gson:gson:2.9.0" +guice = { module = "com.google.inject:guice", version.ref = "guice" } immutables = "org.immutables:value:2.9.0" jackson-core = { module = "com.fasterxml.jackson.core:jackson-core", version.ref = "jackson" } jackson-yaml = { module = "com.fasterxml.jackson.dataformat:jackson-dataformat-yaml", version.ref = "jackson" } @@ -28,6 +32,8 @@ javaparser-core = { module = "com.github.javaparser:javaparser-core", version.re javaparser-symbolsolver-core = { module = "com.github.javaparser:javaparser-symbol-solver-core", version.ref = "javaparser-core" } javaparser-symbolsolver-model = { module = "com.github.javaparser:javaparser-symbol-solver-model", version.ref = "javaparser-symbolsolver" } javaparser-symbolsolver-logic = { module = "com.github.javaparser:javaparser-symbol-solver-logic", version.ref = "javaparser-symbolsolver" } +java-security-toolkit = { module = "io.openpixee:java-security-toolkit", version.ref = "java-security-toolkit" } +javax-inject = { module="javax.inject:javax.inject", version.ref = "javax-inject"} jetbrains-annotations = "org.jetbrains:annotations:23.0.0" jfiglet = "com.github.lalyos:jfiglet:0.0.8" juniversalchardet = "com.github.albfernandez:juniversalchardet:2.4.0" diff --git a/languages/codemodder-common/build.gradle.kts b/languages/codemodder-common/build.gradle.kts new file mode 100644 index 000000000..89b10cc85 --- /dev/null +++ b/languages/codemodder-common/build.gradle.kts @@ -0,0 +1,44 @@ +@Suppress("DSL_SCOPE_VIOLATION") // https://github.com/gradle/gradle/issues/22797 +plugins { + id("io.openpixee.codetl.base") + id("io.openpixee.codetl.java-library") + id("io.openpixee.codetl.maven-publish") + id("application") + alias(libs.plugins.fileversioning) +} + +java { + toolchain { + languageVersion.set(JavaLanguageVersion.of(11)) + } +} + +spotless { + java { + // do we need anything here? + } +} + +publishing { + publications { + register("maven") { + from(components["java"]) + artifactId = "codemodder-common" + } + } +} + +dependencies { + compileOnly(libs.jetbrains.annotations) + implementation(libs.guice) + implementation(libs.contrast.sarif) + implementation(libs.java.security.toolkit) + implementation(libs.slf4j.api) + + testImplementation(testlibs.bundles.junit.jupiter) + testImplementation(testlibs.bundles.hamcrest) + testImplementation(testlibs.assertj) + testImplementation(testlibs.jgit) + testImplementation(testlibs.mockito) + testRuntimeOnly(testlibs.junit.jupiter.engine) +} diff --git a/languages/java/src/main/java/io/openpixee/java/ChangedFile.java b/languages/codemodder-common/src/main/java/io/codemodder/ChangedFile.java similarity index 98% rename from languages/java/src/main/java/io/openpixee/java/ChangedFile.java rename to languages/codemodder-common/src/main/java/io/codemodder/ChangedFile.java index 63b0999ca..2587440d7 100644 --- a/languages/java/src/main/java/io/openpixee/java/ChangedFile.java +++ b/languages/codemodder-common/src/main/java/io/codemodder/ChangedFile.java @@ -1,4 +1,4 @@ -package io.openpixee.java; +package io.codemodder; import java.util.Collections; import java.util.List; diff --git a/languages/java/src/main/java/io/openpixee/java/DependencyGAV.java b/languages/codemodder-common/src/main/java/io/codemodder/DependencyGAV.java similarity index 98% rename from languages/java/src/main/java/io/openpixee/java/DependencyGAV.java rename to languages/codemodder-common/src/main/java/io/codemodder/DependencyGAV.java index 4c7789c89..12a88a6e3 100644 --- a/languages/java/src/main/java/io/openpixee/java/DependencyGAV.java +++ b/languages/codemodder-common/src/main/java/io/codemodder/DependencyGAV.java @@ -1,4 +1,4 @@ -package io.openpixee.java; +package io.codemodder; import java.util.Objects; diff --git a/languages/java/src/main/java/io/openpixee/java/Weave.java b/languages/codemodder-common/src/main/java/io/codemodder/Weave.java similarity index 98% rename from languages/java/src/main/java/io/openpixee/java/Weave.java rename to languages/codemodder-common/src/main/java/io/codemodder/Weave.java index 80181e8c8..60dbb47f3 100644 --- a/languages/java/src/main/java/io/openpixee/java/Weave.java +++ b/languages/codemodder-common/src/main/java/io/codemodder/Weave.java @@ -1,4 +1,4 @@ -package io.openpixee.java; +package io.codemodder; import java.util.List; import java.util.Objects; diff --git a/languages/codemodder-default-codemods/build.gradle.kts b/languages/codemodder-default-codemods/build.gradle.kts new file mode 100644 index 000000000..777d9b9b7 --- /dev/null +++ b/languages/codemodder-default-codemods/build.gradle.kts @@ -0,0 +1,52 @@ +@Suppress("DSL_SCOPE_VIOLATION") // https://github.com/gradle/gradle/issues/22797 +plugins { + id("io.openpixee.codetl.base") + id("io.openpixee.codetl.java-library") + id("io.openpixee.codetl.maven-publish") + id("application") + alias(libs.plugins.fileversioning) +} + +java { + toolchain { + languageVersion.set(JavaLanguageVersion.of(11)) + } +} + +application { + mainClass.set("io.codemodder.codemods.Runner") +} + +spotless { + java { + // do we need anything here? + } +} + +publishing { + publications { + register("maven") { + from(components["java"]) + artifactId = "codemodder-default-codemods" + } + } +} + +dependencies { + implementation(libs.javax.inject) + implementation(libs.contrast.sarif) + implementation(libs.slf4j.api) + implementation(libs.javaparser.core) + implementation(libs.javaparser.symbolsolver.core) + implementation(libs.javaparser.symbolsolver.logic) + implementation(libs.javaparser.symbolsolver.model) + implementation(project(":languages:codemodder-framework-java")) + implementation(project(":languages:codemodder-semgrep-provider")) + + testImplementation(testlibs.bundles.junit.jupiter) + testImplementation(testlibs.bundles.hamcrest) + testImplementation(testlibs.assertj) + testImplementation(testlibs.mockito) + + testRuntimeOnly(testlibs.junit.jupiter.engine) +} diff --git a/languages/codemodder-default-codemods/src/main/java/io/codemodder/codemods/Runner.java b/languages/codemodder-default-codemods/src/main/java/io/codemodder/codemods/Runner.java new file mode 100644 index 000000000..28511ee73 --- /dev/null +++ b/languages/codemodder-default-codemods/src/main/java/io/codemodder/codemods/Runner.java @@ -0,0 +1,11 @@ +package io.codemodder.codemods; + +import static io.codemodder.CodemodInvoker.run; + +/** Invokes the codemod from a command line. */ +public final class Runner { + + public static void main(final String[] args) { + run(SecureRandomCodemod.class); + } +} diff --git a/languages/codemodder-default-codemods/src/main/java/io/codemodder/codemods/SecureRandomCodemod.java b/languages/codemodder-default-codemods/src/main/java/io/codemodder/codemods/SecureRandomCodemod.java new file mode 100644 index 000000000..91f2120a5 --- /dev/null +++ b/languages/codemodder-default-codemods/src/main/java/io/codemodder/codemods/SecureRandomCodemod.java @@ -0,0 +1,49 @@ +package io.codemodder.codemods; + +import com.contrastsecurity.sarif.Result; +import com.contrastsecurity.sarif.SarifSchema210; +import com.github.javaparser.ast.CompilationUnit; +import com.github.javaparser.ast.visitor.ModifierVisitor; +import io.codemodder.ChangeConstructorTypeVisitor; +import io.codemodder.ChangeContext; +import io.codemodder.Codemod; +import io.codemodder.JavaParserChanger; +import io.codemodder.ReviewGuidance; +import io.codemodder.Sarif; +import io.codemodder.providers.sarif.semgrep.SemgrepSarifProvider; +import java.io.IOException; +import java.net.URISyntaxException; +import java.util.List; +import java.util.Optional; +import javax.inject.Inject; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** Turns {@link java.util.Random} into {@link java.security.SecureRandom}. */ +@Codemod( + value = "pixee:java/secure-random", + author = "arshan@pixee.ai", + reviewGuidance = ReviewGuidance.MERGE_WITHOUT_REVIEW) +public final class SecureRandomCodemod implements JavaParserChanger { + + private final SarifSchema210 sarif; + + @Inject + public SecureRandomCodemod(final SemgrepSarifProvider sarifProvider) + throws IOException, URISyntaxException { + this.sarif = sarifProvider.getSarif("secure-random.semgrep"); + } + + @Override + public Optional> createModifierVisitor(final CompilationUnit cu) { + List results = Sarif.getResultsForCompilationUnit(sarif, cu); + logger.debug("Found {} results in {} to change", results.size(), cu.getPrimaryTypeName().get()); + if (!results.isEmpty()) { + return Optional.of( + new ChangeConstructorTypeVisitor(Sarif.findRegions(results), "java.lang.SecureRandom")); + } + return Optional.empty(); + } + + private static final Logger logger = LoggerFactory.getLogger(SecureRandomCodemod.class); +} diff --git a/languages/codemodder-default-codemods/src/main/resources/secure-random.semgrep b/languages/codemodder-default-codemods/src/main/resources/secure-random.semgrep new file mode 100644 index 000000000..c2650bec1 --- /dev/null +++ b/languages/codemodder-default-codemods/src/main/resources/secure-random.semgrep @@ -0,0 +1,7 @@ +rules: + - id: secure-random + pattern: new Random() + message: Insecure PRNG + languages: + - java + severity: WARNING diff --git a/languages/codemodder-framework-java/build.gradle.kts b/languages/codemodder-framework-java/build.gradle.kts new file mode 100644 index 000000000..d06382e50 --- /dev/null +++ b/languages/codemodder-framework-java/build.gradle.kts @@ -0,0 +1,53 @@ +@Suppress("DSL_SCOPE_VIOLATION") // https://github.com/gradle/gradle/issues/22797 +plugins { + id("io.openpixee.codetl.base") + id("io.openpixee.codetl.java-library") + id("io.openpixee.codetl.maven-publish") + id("application") + alias(libs.plugins.fileversioning) +} + +java { + toolchain { + languageVersion.set(JavaLanguageVersion.of(11)) + } +} + +spotless { + java { + // do we need anything here? + } +} + +publishing { + publications { + register("maven") { + from(components["java"]) + artifactId = "codemodder-framework-java" + } + } +} + +dependencies { + compileOnly(libs.jetbrains.annotations) + + api("io.github.pixee:codetf-java:0.0.2") // TODO bring codetf-java into the monorepo + + implementation(libs.guice) + implementation(libs.contrast.sarif) + implementation(libs.javaparser.core) + implementation(libs.javaparser.symbolsolver.core) + implementation(libs.javaparser.symbolsolver.logic) + implementation(libs.javaparser.symbolsolver.model) + implementation(libs.logback.classic) + implementation(libs.maven.model) + implementation(libs.slf4j.api) + implementation(project(":languages:codemodder-common")) + + testImplementation(testlibs.bundles.junit.jupiter) + testImplementation(testlibs.bundles.hamcrest) + testImplementation(testlibs.assertj) + testImplementation(testlibs.mockito) + + testRuntimeOnly(testlibs.junit.jupiter.engine) +} diff --git a/languages/codemodder-framework-java/src/main/java/io/codemodder/ChangeConstructorTypeVisitor.java b/languages/codemodder-framework-java/src/main/java/io/codemodder/ChangeConstructorTypeVisitor.java new file mode 100644 index 000000000..10a078f7a --- /dev/null +++ b/languages/codemodder-framework-java/src/main/java/io/codemodder/ChangeConstructorTypeVisitor.java @@ -0,0 +1,47 @@ +package io.codemodder; + +import static io.codemodder.JavaParserUtils.addImportIfMissing; +import static io.codemodder.JavaParserUtils.regionMatchesNode; + +import com.contrastsecurity.sarif.Region; +import com.github.javaparser.Position; +import com.github.javaparser.ast.expr.ObjectCreationExpr; +import com.github.javaparser.ast.type.ClassOrInterfaceType; +import com.github.javaparser.ast.visitor.ModifierVisitor; +import com.github.javaparser.ast.visitor.Visitable; +import java.util.List; +import java.util.Objects; + +/** + * A utility type that makes switching one type's constructor for another, with no changes to + * arguments. + */ +public final class ChangeConstructorTypeVisitor extends ModifierVisitor { + + private final List regions; + private final String newType; + private final String newTypeSimpleName; + + public ChangeConstructorTypeVisitor(final List regions, final String newType) { + this.regions = Objects.requireNonNull(regions); + this.newType = Objects.requireNonNull(newType); + this.newTypeSimpleName = toSimpleName(newType); + } + + @SuppressWarnings("OptionalGetWithoutIsPresent") + @Override + public Visitable visit(final ObjectCreationExpr objectCreationExpr, final ChangeContext context) { + if (regions.stream().anyMatch(region -> regionMatchesNode(objectCreationExpr, region))) { + objectCreationExpr.setType(new ClassOrInterfaceType(newTypeSimpleName)); + addImportIfMissing(objectCreationExpr.findCompilationUnit().get(), newType); + Position begin = objectCreationExpr.getRange().get().begin; + context.record(begin.line, begin.column); + } + return super.visit(objectCreationExpr, context); + } + + /** Convert FQCN to simple type. */ + private String toSimpleName(final String newType) { + return newType.substring(newType.lastIndexOf(".") + 1); + } +} diff --git a/languages/codemodder-framework-java/src/main/java/io/codemodder/ChangeContext.java b/languages/codemodder-framework-java/src/main/java/io/codemodder/ChangeContext.java new file mode 100644 index 000000000..5de153620 --- /dev/null +++ b/languages/codemodder-framework-java/src/main/java/io/codemodder/ChangeContext.java @@ -0,0 +1,7 @@ +package io.codemodder; + +/** Holds information about the */ +public interface ChangeContext { + + void record(int line, int column); +} diff --git a/languages/codemodder-framework-java/src/main/java/io/codemodder/Changer.java b/languages/codemodder-framework-java/src/main/java/io/codemodder/Changer.java new file mode 100644 index 000000000..16433f378 --- /dev/null +++ b/languages/codemodder-framework-java/src/main/java/io/codemodder/Changer.java @@ -0,0 +1,4 @@ +package io.codemodder; + +/** An empty interface that marks that a codemod type has provided some utility to change. */ +public interface Changer {} diff --git a/languages/codemodder-framework-java/src/main/java/io/codemodder/ChangerHandler.java b/languages/codemodder-framework-java/src/main/java/io/codemodder/ChangerHandler.java new file mode 100644 index 000000000..096fc798f --- /dev/null +++ b/languages/codemodder-framework-java/src/main/java/io/codemodder/ChangerHandler.java @@ -0,0 +1,5 @@ +package io.codemodder; + +public interface ChangerHandler { + void run(); +} diff --git a/languages/codemodder-framework-java/src/main/java/io/codemodder/Codemod.java b/languages/codemodder-framework-java/src/main/java/io/codemodder/Codemod.java new file mode 100644 index 000000000..ce9f2ade3 --- /dev/null +++ b/languages/codemodder-framework-java/src/main/java/io/codemodder/Codemod.java @@ -0,0 +1,40 @@ +package io.codemodder; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** Used to mark types providing codemod functionality and provide the necessary metadata. */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +public @interface Codemod { + + /** + * The codemod ID, which must follow this nomenclature: + * + *

[vendor]:[language]/[unique identifier] + * + *

Some examples: + * + *

    + *
  • pixee:java/secure-random + *
  • codeql:java/xss + *
+ */ + String value(); + + /** + * The codemod author, hopefully "email", "First Last (email)", or "Team Name (email)". + * + * @return the author of the codemod + */ + String author(); + + /** + * The review guidance for the changes introduced by this codemod + * + * @return review guidance + */ + ReviewGuidance reviewGuidance(); +} diff --git a/languages/codemodder-framework-java/src/main/java/io/codemodder/CodemodInvoker.java b/languages/codemodder-framework-java/src/main/java/io/codemodder/CodemodInvoker.java new file mode 100644 index 000000000..da2f598dd --- /dev/null +++ b/languages/codemodder-framework-java/src/main/java/io/codemodder/CodemodInvoker.java @@ -0,0 +1,118 @@ +package io.codemodder; + +import com.google.inject.AbstractModule; +import com.google.inject.Guice; +import com.google.inject.Injector; +import java.nio.file.Path; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Objects; +import java.util.ServiceLoader; +import java.util.Set; +import java.util.regex.Pattern; +import java.util.stream.Collectors; +import org.codehaus.plexus.util.StringUtils; +import org.jetbrains.annotations.VisibleForTesting; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * This is the entry point for codemod authors to invoke their codemods through our framework. Every + * codemod should create a codemod then create a separate type with a main() method that invokes + * this entry point. + */ +public final class CodemodInvoker { + + private final Set allModules; + private final Injector injector; + private final List> codemodTypes; + private final Path repsitoryDir; + + public CodemodInvoker(final List> codemodTypes, Path repositoryDir) { + // get all the providers ready for dependency injection & codemod instantiation + List providers = + ServiceLoader.load(CodemodProvider.class).stream() + .map(ServiceLoader.Provider::get) + .collect(Collectors.toUnmodifiableList()); + Set allModules = new HashSet<>(); + + for (CodemodProvider provider : providers) { + Set modules = provider.getModules(); + allModules.addAll(modules); + } + + this.repsitoryDir = Objects.requireNonNull(repositoryDir); + this.allModules = Collections.unmodifiableSet(allModules); + this.injector = Guice.createInjector(allModules); + this.codemodTypes = Collections.unmodifiableList(codemodTypes); + + // validate all of the codemods + for (Class type : codemodTypes) { + Codemod codemodAnnotation = type.getAnnotation(Codemod.class); + validateRequiredFields(codemodAnnotation); + } + } + + // will invoke every changer for every file, collecting the diffs + // will collate report + // will spit out CodeTF or whatever + + /** + * @param javaFile + * @return + */ + public ChangedFile execute(final String javaFile) { + + // find a provider that can handle invoking the codemod "change phase" + for (Class type : codemodTypes) { + // Changer changer = injector.getInstance(type); + // if(changer instanceof JavaParserChanger) { + //// CompilationUnit cu = parseJavaCode(javaFilePath); + //// ((JavaParserChanger) changer).createModifierVisitor(cu); + //// serializeBack(); + // } else if (changer instanceof SpoonChanger) { + // + // } else { + // throw new IllegalArgumentException(""); + // } + // for(CodemodProvider provider : providers) { + // provider.get + // } + } + return null; + } + + /** Invoke the given codemods. */ + public static void run(final Class... codemodTypes) { + // loop through every file + // new CodemodInvoker(codemodTypes).execute(); + } + + private static void validateRequiredFields(final Codemod codemodAnnotation) { + String author = codemodAnnotation.author(); + if (StringUtils.isBlank(author)) { + throw new IllegalArgumentException("must have an author"); + } + + String id = codemodAnnotation.value(); + if (isValidCodemodId(id)) { + throw new IllegalArgumentException("must have an author"); + } + + ReviewGuidance reviewGuidance = codemodAnnotation.reviewGuidance(); + if (reviewGuidance == null) { + throw new IllegalArgumentException("must have review guidance"); + } + } + + @VisibleForTesting + static boolean isValidCodemodId(final String codemodId) { + return codemodIdPattern.matcher(codemodId).matches(); + } + + private static final Pattern codemodIdPattern = + Pattern.compile("^([A-Za-z0-9]+):([A-Za-z0-9]+)\\/([A-Za-z0-9\\-]+)$"); + + private static final Logger log = LoggerFactory.getLogger(CodemodInvoker.class); +} diff --git a/languages/codemodder-framework-java/src/main/java/io/codemodder/CodemodProvider.java b/languages/codemodder-framework-java/src/main/java/io/codemodder/CodemodProvider.java new file mode 100644 index 000000000..c4bd7941e --- /dev/null +++ b/languages/codemodder-framework-java/src/main/java/io/codemodder/CodemodProvider.java @@ -0,0 +1,22 @@ +package io.codemodder; + +import com.google.inject.AbstractModule; +import java.util.Optional; +import java.util.Set; + +/** + * A type that helps provide functionality codemods. For instance, we may have providers that run + * SAST tools, help codemods understand build files, dependency management, etc. + */ +public interface CodemodProvider { + + /** + * Return a set of Guice modules that allow dependency injection + * + * @return a set of modules that perform dependency injection + */ + Set getModules(); + + /** Provide a changer handler for the given {@link Changer} if they want to support it. */ + Optional getChangerHandler(Changer changer); +} diff --git a/languages/codemodder-framework-java/src/main/java/io/codemodder/DependencyManagementProvider.java b/languages/codemodder-framework-java/src/main/java/io/codemodder/DependencyManagementProvider.java new file mode 100644 index 000000000..66da5cea9 --- /dev/null +++ b/languages/codemodder-framework-java/src/main/java/io/codemodder/DependencyManagementProvider.java @@ -0,0 +1,9 @@ +package io.codemodder; + +/** A provider that offers developers a way to query and manage dependencies. */ +public interface DependencyManagementProvider { + + boolean hasDependency(String groupId, String artifactId, String minimumVersion); + + void addDependency(String groupId, String artifactId, String minimumVersion); +} diff --git a/languages/codemodder-framework-java/src/main/java/io/codemodder/JavaParserChanger.java b/languages/codemodder-framework-java/src/main/java/io/codemodder/JavaParserChanger.java new file mode 100644 index 000000000..98785585c --- /dev/null +++ b/languages/codemodder-framework-java/src/main/java/io/codemodder/JavaParserChanger.java @@ -0,0 +1,15 @@ +package io.codemodder; + +import com.github.javaparser.ast.CompilationUnit; +import com.github.javaparser.ast.visitor.ModifierVisitor; +import java.util.Optional; + +/** {@inheritDoc} Uses JavaParser to change Java source files. */ +public interface JavaParserChanger extends Changer { + + /** + * Creates a visitor for a given Java source file, or not. It's up to the implementing type to + * determine if and how source file should be changed. + */ + Optional> createModifierVisitor(CompilationUnit cu); +} diff --git a/languages/codemodder-framework-java/src/main/java/io/codemodder/JavaParserInvoker.java b/languages/codemodder-framework-java/src/main/java/io/codemodder/JavaParserInvoker.java new file mode 100644 index 000000000..414f09574 --- /dev/null +++ b/languages/codemodder-framework-java/src/main/java/io/codemodder/JavaParserInvoker.java @@ -0,0 +1,3 @@ +package io.codemodder; + +public class JavaParserInvoker {} diff --git a/languages/codemodder-framework-java/src/main/java/io/codemodder/JavaParserProvider.java b/languages/codemodder-framework-java/src/main/java/io/codemodder/JavaParserProvider.java new file mode 100644 index 000000000..850950a5d --- /dev/null +++ b/languages/codemodder-framework-java/src/main/java/io/codemodder/JavaParserProvider.java @@ -0,0 +1,19 @@ +package io.codemodder; + +import com.google.inject.AbstractModule; +import java.util.Optional; +import java.util.Set; + +/** Turns Java files into. */ +public class JavaParserProvider implements CodemodProvider { + + @Override + public Set getModules() { + return Set.of(); + } + + @Override + public Optional getChangerHandler(final Changer changer) { + return Optional.empty(); + } +} diff --git a/languages/codemodder-framework-java/src/main/java/io/codemodder/JavaParserUtils.java b/languages/codemodder-framework-java/src/main/java/io/codemodder/JavaParserUtils.java new file mode 100644 index 000000000..b6165e08c --- /dev/null +++ b/languages/codemodder-framework-java/src/main/java/io/codemodder/JavaParserUtils.java @@ -0,0 +1,46 @@ +package io.codemodder; + +import com.contrastsecurity.sarif.Region; +import com.github.javaparser.ast.CompilationUnit; +import com.github.javaparser.ast.ImportDeclaration; +import com.github.javaparser.ast.Node; +import com.github.javaparser.ast.NodeList; + +public final class JavaParserUtils { + + /** + * Adds a type to the import list if it's not in there directly, and if it's not implied by a + * wildcard. Care will be taken to ensure it's inserted in alphabetical order. + * + * @param cu the class we're changing + * @param className the new type to ensure is imported + */ + public static void addImportIfMissing(final CompilationUnit cu, final String className) { + NodeList imports = cu.getImports(); + ImportDeclaration newImport = new ImportDeclaration(className, false, false); + if (imports.contains(newImport)) { + return; + } + for (int i = 0; i < imports.size(); i++) { + ImportDeclaration existingImport = imports.get(i); + + if (existingImport.getNameAsString().compareToIgnoreCase(className) > 0) { + + imports.addBefore(newImport, existingImport); + return; + } + } + cu.addImport(className); + } + + /** + * Return true if the {@link Node} is within the {@link Region} described. + * + * @param node the node to search for within the boundary + * @param region the given region that defines the boundary + * @return true, if the node is within the region + */ + public static boolean regionMatchesNode(final Node node, final Region region) { + return false; + } +} diff --git a/languages/codemodder-framework-java/src/main/java/io/codemodder/ProjectProvider.java b/languages/codemodder-framework-java/src/main/java/io/codemodder/ProjectProvider.java new file mode 100644 index 000000000..0e18fa9cc --- /dev/null +++ b/languages/codemodder-framework-java/src/main/java/io/codemodder/ProjectProvider.java @@ -0,0 +1,17 @@ +package io.codemodder; + +import java.util.Optional; + +/** A provider that offers project information, e.g., the compiler's Java source code level. */ +public interface ProjectProvider { + + /** + * @return the Java source version the project build file appears to target. + */ + Optional javaSourceTargetVersion(); + + /** + * @return the Java bytecode version the project build file appears to target. + */ + Optional javaBytecodeTargetVersion(); +} diff --git a/languages/codemodder-framework-java/src/main/java/io/codemodder/RawFileChanger.java b/languages/codemodder-framework-java/src/main/java/io/codemodder/RawFileChanger.java new file mode 100644 index 000000000..06aaabf47 --- /dev/null +++ b/languages/codemodder-framework-java/src/main/java/io/codemodder/RawFileChanger.java @@ -0,0 +1,3 @@ +package io.codemodder; + +public interface RawFileChanger extends Changer {} diff --git a/languages/codemodder-framework-java/src/main/java/io/codemodder/ReviewGuidance.java b/languages/codemodder-framework-java/src/main/java/io/codemodder/ReviewGuidance.java new file mode 100644 index 000000000..8324e8b27 --- /dev/null +++ b/languages/codemodder-framework-java/src/main/java/io/codemodder/ReviewGuidance.java @@ -0,0 +1,8 @@ +package io.codemodder; + +/** Represent's a codemod author's confidence that changes wll be safe and effective. */ +public enum ReviewGuidance { + MERGE_AFTER_REVIEW, + MERGE_AFTER_CURSORY_REVIEW, + MERGE_WITHOUT_REVIEW +} diff --git a/languages/codemodder-framework-java/src/main/java/io/codemodder/Sarif.java b/languages/codemodder-framework-java/src/main/java/io/codemodder/Sarif.java new file mode 100644 index 000000000..c3b039ff5 --- /dev/null +++ b/languages/codemodder-framework-java/src/main/java/io/codemodder/Sarif.java @@ -0,0 +1,37 @@ +package io.codemodder; + +import com.contrastsecurity.sarif.Region; +import com.contrastsecurity.sarif.Result; +import com.contrastsecurity.sarif.SarifSchema210; +import com.github.javaparser.ast.CompilationUnit; +import java.util.List; + +/** + * A wrapper around {@link com.contrastsecurity.sarif.SarifSchema210} that also provides convenience + * methods that make writing codemods easier. + */ +public final class Sarif { + + private Sarif() {} + + /** + * Get all the {@link Result} that are from the given {@link CompilationUnit}. + * + * @param cu the source file + * @return a {@link List} containing the SARIF results for this file + */ + public static List getResultsForCompilationUnit( + final SarifSchema210 sarif, final CompilationUnit cu) { + throw new UnsupportedOperationException(); + } + + /** + * Get all of the {@link Region} entries for the given {@link Result} list. + * + * @param results the results to map to source code locations + * @return a list of source code locations + */ + public static List findRegions(final List results) { + throw new UnsupportedOperationException(); + } +} diff --git a/languages/codemodder-framework-java/src/test/java/io/codemodder/CodemodInvokerTest.java b/languages/codemodder-framework-java/src/test/java/io/codemodder/CodemodInvokerTest.java new file mode 100644 index 000000000..56053584c --- /dev/null +++ b/languages/codemodder-framework-java/src/test/java/io/codemodder/CodemodInvokerTest.java @@ -0,0 +1,37 @@ +package io.codemodder; + +import static io.codemodder.CodemodInvoker.isValidCodemodId; +import static org.hamcrest.MatcherAssert.assertThat; + +import org.hamcrest.CoreMatchers; +import org.junit.jupiter.api.Test; + +final class CodemodInvokerTest { + + @Codemod( + value = "test_mod", + author = "valid@valid.com", + reviewGuidance = ReviewGuidance.MERGE_AFTER_CURSORY_REVIEW) + static class InvalidCodemodName implements Changer {} + + @Codemod( + value = "test_mod", + author = " ", + reviewGuidance = ReviewGuidance.MERGE_AFTER_CURSORY_REVIEW) + class EmptyCodemodAuthor implements Changer {} + + @Codemod( + value = "pixee:java/id", + author = "valid@valid.com", + reviewGuidance = ReviewGuidance.MERGE_AFTER_CURSORY_REVIEW) + final class ValidCodemod implements Changer {} + + @Test + void it_validates_codemod_ids() { + assertThat(isValidCodemodId("pixee:java/id"), CoreMatchers.is(true)); + assertThat(isValidCodemodId("pixee:java/id-with-slashes-numbers-34"), CoreMatchers.is(true)); + assertThat(isValidCodemodId("some-thing:java/id"), CoreMatchers.is(false)); + assertThat(isValidCodemodId("missing:token"), CoreMatchers.is(false)); + assertThat(isValidCodemodId("missing:separator/"), CoreMatchers.is(false)); + } +} diff --git a/languages/codemodder-semgrep-provider/build.gradle.kts b/languages/codemodder-semgrep-provider/build.gradle.kts new file mode 100644 index 000000000..339d5bd9b --- /dev/null +++ b/languages/codemodder-semgrep-provider/build.gradle.kts @@ -0,0 +1,45 @@ +@Suppress("DSL_SCOPE_VIOLATION") // https://github.com/gradle/gradle/issues/22797 +plugins { + id("io.openpixee.codetl.base") + id("io.openpixee.codetl.java-library") + id("io.openpixee.codetl.maven-publish") + id("application") + alias(libs.plugins.fileversioning) +} + +java { + toolchain { + languageVersion.set(JavaLanguageVersion.of(11)) + } +} + +spotless { + java { + // do we need anything here? + } +} + +publishing { + publications { + register("maven") { + from(components["java"]) + artifactId = "codemodder-provider-sarif-semgrep" + } + } +} + +dependencies { + compileOnly(libs.jetbrains.annotations) + implementation(libs.guice) + implementation(libs.contrast.sarif) + implementation(libs.java.security.toolkit) + implementation(libs.slf4j.api) + implementation(project(":languages:codemodder-framework-java")) + + testImplementation(testlibs.bundles.junit.jupiter) + testImplementation(testlibs.bundles.hamcrest) + testImplementation(testlibs.assertj) + testImplementation(testlibs.jgit) + testImplementation(testlibs.mockito) + testRuntimeOnly(testlibs.junit.jupiter.engine) +} diff --git a/languages/codemodder-semgrep-provider/src/main/java/io/codemodder/providers/sarif/semgrep/DefaultSemgrepRunner.java b/languages/codemodder-semgrep-provider/src/main/java/io/codemodder/providers/sarif/semgrep/DefaultSemgrepRunner.java new file mode 100644 index 000000000..2d85b2829 --- /dev/null +++ b/languages/codemodder-semgrep-provider/src/main/java/io/codemodder/providers/sarif/semgrep/DefaultSemgrepRunner.java @@ -0,0 +1,49 @@ +package io.codemodder.providers.sarif.semgrep; + +import com.contrastsecurity.sarif.SarifSchema210; +import com.fasterxml.jackson.databind.ObjectMapper; +import io.openpixee.security.SystemCommand; +import java.io.File; +import java.io.FileReader; +import java.io.IOException; +import java.nio.file.Path; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** {@inheritDoc} */ +final class DefaultSemgrepRunner implements SemgrepRunner { + + /** {@inheritDoc} */ + @Override + public SarifSchema210 runWithSingleRule(final Path rule, final Path repository) + throws IOException { + String ruleDirectoryPath = rule.toString(); + String repositoryPath = repository.toString(); + File sarifFile = File.createTempFile("semgrep", ".sarif"); + sarifFile.deleteOnExit(); + Process p = + SystemCommand.runCommand( + Runtime.getRuntime(), + new String[] { + "semgrep", + "--sarif", + "-o", + sarifFile.getAbsolutePath(), + "--config", + ruleDirectoryPath, + repositoryPath + }); + try { + int rc = p.waitFor(); + if (rc != 0) { + throw new RuntimeException("error code seen from semgrep execution: " + rc); + } + } catch (InterruptedException e) { + logger.error("problem waiting for semgrep process execution", e); + } + + return new ObjectMapper().readValue(new FileReader(sarifFile), SarifSchema210.class); + } + + private static final Logger logger = LoggerFactory.getLogger(SemgrepRunner.class); +} diff --git a/languages/codemodder-semgrep-provider/src/main/java/io/codemodder/providers/sarif/semgrep/DefaultSemgrepSarifProvider.java b/languages/codemodder-semgrep-provider/src/main/java/io/codemodder/providers/sarif/semgrep/DefaultSemgrepSarifProvider.java new file mode 100644 index 000000000..1c5eff008 --- /dev/null +++ b/languages/codemodder-semgrep-provider/src/main/java/io/codemodder/providers/sarif/semgrep/DefaultSemgrepSarifProvider.java @@ -0,0 +1,41 @@ +package io.codemodder.providers.sarif.semgrep; + +import com.contrastsecurity.sarif.SarifSchema210; +import java.io.IOException; +import java.net.URISyntaxException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; + +/** This is the memoizing Semgrep runner that we'll give to codemods. */ +final class DefaultSemgrepSarifProvider implements SemgrepSarifProvider { + + private final Map sarifs; + private final Path repositoryDir; + + DefaultSemgrepSarifProvider(final Path repositoryDir) { + this.sarifs = new HashMap<>(); + this.repositoryDir = Objects.requireNonNull(repositoryDir); + } + + @Override + public SarifSchema210 getSarif(final String rulePath) throws IOException, URISyntaxException { + if (sarifs.containsKey(rulePath)) { + return sarifs.get(rulePath); + } + + String ruleYaml = + Files.readString(Paths.get(getClass().getClassLoader().getResource(rulePath).toURI())); + Path semgrepRuleFile = Files.createTempFile("semgrep", ".yaml"); + Files.write(semgrepRuleFile, ruleYaml.getBytes(StandardCharsets.UTF_8)); + + SarifSchema210 sarif = + new DefaultSemgrepRunner().runWithSingleRule(semgrepRuleFile, repositoryDir); + sarifs.put(rulePath, sarif); + return sarif; + } +} diff --git a/languages/codemodder-semgrep-provider/src/main/java/io/codemodder/providers/sarif/semgrep/SemgrepRunner.java b/languages/codemodder-semgrep-provider/src/main/java/io/codemodder/providers/sarif/semgrep/SemgrepRunner.java new file mode 100644 index 000000000..6149da83b --- /dev/null +++ b/languages/codemodder-semgrep-provider/src/main/java/io/codemodder/providers/sarif/semgrep/SemgrepRunner.java @@ -0,0 +1,18 @@ +package io.codemodder.providers.sarif.semgrep; + +import com.contrastsecurity.sarif.SarifSchema210; +import java.io.IOException; +import java.nio.file.Path; + +/** Responsible for running semgrep */ +interface SemgrepRunner { + + /** + * Execute semgrep. + * + * @param rule the directory/file where the rule(s) are stored + * @param repository the directory containing the code to be run on + * @return the resulting SARIF + */ + SarifSchema210 runWithSingleRule(Path rule, Path repository) throws IOException; +} diff --git a/languages/codemodder-semgrep-provider/src/main/java/io/codemodder/providers/sarif/semgrep/SemgrepSarifProvider.java b/languages/codemodder-semgrep-provider/src/main/java/io/codemodder/providers/sarif/semgrep/SemgrepSarifProvider.java new file mode 100644 index 000000000..5ece666d7 --- /dev/null +++ b/languages/codemodder-semgrep-provider/src/main/java/io/codemodder/providers/sarif/semgrep/SemgrepSarifProvider.java @@ -0,0 +1,16 @@ +package io.codemodder.providers.sarif.semgrep; + +import com.contrastsecurity.sarif.SarifSchema210; +import java.io.IOException; +import java.net.URISyntaxException; + +/** A provider that invokes semgrep (assuming Semgrep is on the $PATH) */ +public interface SemgrepSarifProvider { + + /** + * Invokes Semgrep with given rule and return the SARIF + * + * @param rulePath the classpath location of a semgrep YAML rule + */ + SarifSchema210 getSarif(String rulePath) throws IOException, URISyntaxException; +} diff --git a/languages/codemodder-semgrep-provider/src/test/java/io/codemodder/providers/sarif/semgrep/SemgrepSarifProviderTest.java b/languages/codemodder-semgrep-provider/src/test/java/io/codemodder/providers/sarif/semgrep/SemgrepSarifProviderTest.java new file mode 100644 index 000000000..6956787d5 --- /dev/null +++ b/languages/codemodder-semgrep-provider/src/test/java/io/codemodder/providers/sarif/semgrep/SemgrepSarifProviderTest.java @@ -0,0 +1,65 @@ +package io.codemodder.providers.sarif.semgrep; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.MatcherAssert.assertThat; + +import com.contrastsecurity.sarif.Result; +import com.contrastsecurity.sarif.Run; +import com.contrastsecurity.sarif.SarifSchema210; +import com.google.inject.AbstractModule; +import com.google.inject.Guice; +import com.google.inject.Inject; +import com.google.inject.Injector; +import java.io.IOException; +import java.net.URISyntaxException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.List; +import java.util.Objects; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +final class SemgrepSarifProviderTest { + + @Test + void it_injects_sarif(@TempDir Path repositoryDir) throws IOException { + Path javaFile = Files.createTempFile(repositoryDir, "WeakRandom", ".java"); + String insecureRandomJavaClass = "class Foo { Random rnd = new Random(); }"; + Files.write(javaFile, insecureRandomJavaClass.getBytes(StandardCharsets.UTF_8)); + + Injector injector = Guice.createInjector(new SemgrepSarifModule(repositoryDir)); + UsesSemgrepSarif instance = injector.getInstance(UsesSemgrepSarif.class); + List runs = instance.sarif.getRuns(); + assertThat(runs.size(), is(1)); + List results = runs.get(0).getResults(); + assertThat(results.size(), is(1)); + Result result = results.get(0); + assertThat(result.getRuleId().endsWith("secure-random"), is(true)); + String resultPhysicalFilePath = + result.getLocations().get(0).getPhysicalLocation().getArtifactLocation().getUri(); + assertThat(resultPhysicalFilePath.contains("WeakRandom"), is(true)); + } + + static class UsesSemgrepSarif { + private final SarifSchema210 sarif; + + @Inject + UsesSemgrepSarif(SemgrepSarifProvider sarifProvider) throws IOException, URISyntaxException { + this.sarif = Objects.requireNonNull(sarifProvider.getSarif("example.semgrep")); + } + } + + static class SemgrepSarifModule extends AbstractModule { + private final Path repositoryDir; + + SemgrepSarifModule(Path repositoryDir) { + this.repositoryDir = Objects.requireNonNull(repositoryDir); + } + + @Override + protected void configure() { + bind(SemgrepSarifProvider.class).toInstance(new DefaultSemgrepSarifProvider(repositoryDir)); + } + } +} diff --git a/languages/codemodder-semgrep-provider/src/test/resources/example.semgrep b/languages/codemodder-semgrep-provider/src/test/resources/example.semgrep new file mode 100644 index 000000000..c2650bec1 --- /dev/null +++ b/languages/codemodder-semgrep-provider/src/test/resources/example.semgrep @@ -0,0 +1,7 @@ +rules: + - id: secure-random + pattern: new Random() + message: Insecure PRNG + languages: + - java + severity: WARNING diff --git a/languages/codemodder-semgrep-provider/src/test/resources/webgoat_codeql.sarif b/languages/codemodder-semgrep-provider/src/test/resources/webgoat_codeql.sarif new file mode 100644 index 000000000..31e1908cd --- /dev/null +++ b/languages/codemodder-semgrep-provider/src/test/resources/webgoat_codeql.sarif @@ -0,0 +1,4490 @@ +{ + "runs": [ + { + "artifacts": [ + { + "location": { + "index": 0, + "uri": "webgoat-lessons/ssrf/src/main/java/org/owasp/webgoat/ssrf/SSRFTask2.java" + } + }, + { + "location": { + "index": 1, + "uri": "webgoat-lessons/jwt/src/main/java/org/owasp/webgoat/jwt/JWTVotesEndpoint.java" + } + }, + { + "location": { + "index": 2, + "uri": "webgoat-lessons/xxe/src/main/java/org/owasp/webgoat/xxe/Comments.java" + } + }, + { + "location": { + "index": 3, + "uri": "webgoat-lessons/xxe/src/main/java/org/owasp/webgoat/xxe/ContentTypeAssignment.java" + } + }, + { + "location": { + "index": 4, + "uri": "webgoat-lessons/xxe/src/main/java/org/owasp/webgoat/xxe/SimpleXXE.java" + } + }, + { + "location": { + "index": 5, + "uri": "webgoat-lessons/vulnerable-components/src/main/java/org/owasp/webgoat/vulnerable_components/VulnerableComponentsLesson.java" + } + }, + { + "location": { + "index": 6, + "uri": "webgoat-lessons/jwt/src/main/java/org/owasp/webgoat/jwt/JWTRefreshEndpoint.java" + } + }, + { + "location": { + "index": 7, + "uri": "webgoat-lessons/jwt/src/main/java/org/owasp/webgoat/jwt/JWTSecretKeyEndpoint.java" + } + }, + { + "location": { + "index": 8, + "uri": "webgoat-integration-tests/src/test/java/org/owasp/webgoat/JWTLessonTest.java" + } + }, + { + "location": { + "index": 9, + "uri": "webgoat-lessons/crypto/src/main/java/org/owasp/webgoat/crypto/HashingAssignment.java" + } + }, + { + "location": { + "index": 10, + "uri": "webgoat-container/src/main/java/org/owasp/webgoat/AjaxAuthenticationEntryPoint.java" + } + }, + { + "location": { + "index": 11, + "uri": "webgoat-lessons/sql-injection/src/main/java/org/owasp/webgoat/sql_injection/introduction/SqlInjectionLesson9.java" + } + }, + { + "location": { + "index": 12, + "uri": "webgoat-lessons/sql-injection/src/main/java/org/owasp/webgoat/sql_injection/introduction/SqlInjectionLesson8.java" + } + }, + { + "location": { + "index": 13, + "uri": "webgoat-lessons/sql-injection/src/main/java/org/owasp/webgoat/sql_injection/introduction/SqlInjectionLesson5b.java" + } + }, + { + "location": { + "index": 14, + "uri": "webgoat-lessons/sql-injection/src/main/java/org/owasp/webgoat/sql_injection/introduction/SqlInjectionLesson5a.java" + } + }, + { + "location": { + "index": 15, + "uri": "webgoat-lessons/sql-injection/src/main/java/org/owasp/webgoat/sql_injection/introduction/SqlInjectionLesson5.java" + } + }, + { + "location": { + "index": 16, + "uri": "webgoat-lessons/sql-injection/src/main/java/org/owasp/webgoat/sql_injection/introduction/SqlInjectionLesson4.java" + } + }, + { + "location": { + "index": 17, + "uri": "webgoat-lessons/sql-injection/src/main/java/org/owasp/webgoat/sql_injection/introduction/SqlInjectionLesson3.java" + } + }, + { + "location": { + "index": 18, + "uri": "webgoat-lessons/sql-injection/src/main/java/org/owasp/webgoat/sql_injection/introduction/SqlInjectionLesson2.java" + } + }, + { + "location": { + "index": 19, + "uri": "webgoat-lessons/sql-injection/src/main/java/org/owasp/webgoat/sql_injection/introduction/SqlInjectionLesson10.java" + } + }, + { + "location": { + "index": 20, + "uri": "webgoat-lessons/sql-injection/src/main/java/org/owasp/webgoat/sql_injection/advanced/SqlInjectionLesson6a.java" + } + }, + { + "location": { + "index": 21, + "uri": "webgoat-lessons/sql-injection/src/main/java/org/owasp/webgoat/sql_injection/mitigation/SqlOnlyInputValidation.java" + } + } + ], + "automationDetails": { + "id": ".github/workflows/codeql.yml:analyse/" + }, + "conversion": { + "tool": { + "driver": { + "name": "GitHub Code Scanning" + } + } + }, + "results": [ + { + "codeFlows": [ + { + "threadFlows": [ + { + "locations": [ + { + "location": { + "message": { + "text": "url : String" + }, + "physicalLocation": { + "artifactLocation": { + "index": 0, + "uri": "webgoat-lessons/ssrf/src/main/java/org/owasp/webgoat/ssrf/SSRFTask2.java" + }, + "region": { + "endColumn": 59, + "endLine": 46, + "startColumn": 35, + "startLine": 46 + } + } + } + }, + { + "location": { + "message": { + "text": "url : String" + }, + "physicalLocation": { + "artifactLocation": { + "index": 0, + "uri": "webgoat-lessons/ssrf/src/main/java/org/owasp/webgoat/ssrf/SSRFTask2.java" + }, + "region": { + "endColumn": 27, + "endLine": 47, + "startColumn": 24, + "startLine": 47 + } + } + } + }, + { + "location": { + "message": { + "text": "url : String" + }, + "physicalLocation": { + "artifactLocation": { + "index": 0, + "uri": "webgoat-lessons/ssrf/src/main/java/org/owasp/webgoat/ssrf/SSRFTask2.java" + }, + "region": { + "endColumn": 46, + "endLine": 50, + "startColumn": 36, + "startLine": 50 + } + } + } + }, + { + "location": { + "message": { + "text": "new URL(...)" + }, + "physicalLocation": { + "artifactLocation": { + "index": 0, + "uri": "webgoat-lessons/ssrf/src/main/java/org/owasp/webgoat/ssrf/SSRFTask2.java" + }, + "region": { + "endColumn": 47, + "endLine": 53, + "startColumn": 35, + "startLine": 53 + } + } + } + } + ] + } + ] + }, + { + "threadFlows": [ + { + "locations": [ + { + "location": { + "message": { + "text": "url : String" + }, + "physicalLocation": { + "artifactLocation": { + "index": 0, + "uri": "webgoat-lessons/ssrf/src/main/java/org/owasp/webgoat/ssrf/SSRFTask2.java" + }, + "region": { + "endColumn": 59, + "endLine": 46, + "startColumn": 35, + "startLine": 46 + } + } + } + }, + { + "location": { + "message": { + "text": "url : String" + }, + "physicalLocation": { + "artifactLocation": { + "index": 0, + "uri": "webgoat-lessons/ssrf/src/main/java/org/owasp/webgoat/ssrf/SSRFTask2.java" + }, + "region": { + "endColumn": 27, + "endLine": 47, + "startColumn": 24, + "startLine": 47 + } + } + } + }, + { + "location": { + "message": { + "text": "url : String" + }, + "physicalLocation": { + "artifactLocation": { + "index": 0, + "uri": "webgoat-lessons/ssrf/src/main/java/org/owasp/webgoat/ssrf/SSRFTask2.java" + }, + "region": { + "endColumn": 46, + "endLine": 50, + "startColumn": 36, + "startLine": 50 + } + } + } + }, + { + "location": { + "message": { + "text": "url : String" + }, + "physicalLocation": { + "artifactLocation": { + "index": 0, + "uri": "webgoat-lessons/ssrf/src/main/java/org/owasp/webgoat/ssrf/SSRFTask2.java" + }, + "region": { + "endColumn": 46, + "endLine": 53, + "startColumn": 43, + "startLine": 53 + } + } + } + }, + { + "location": { + "message": { + "text": "new URL(...)" + }, + "physicalLocation": { + "artifactLocation": { + "index": 0, + "uri": "webgoat-lessons/ssrf/src/main/java/org/owasp/webgoat/ssrf/SSRFTask2.java" + }, + "region": { + "endColumn": 47, + "endLine": 53, + "startColumn": 35, + "startLine": 53 + } + } + } + } + ] + } + ] + } + ], + "correlationGuid": "0a707b1d-4b19-47a1-ad04-c83e3ea0f35b", + "level": "error", + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "index": 0, + "uri": "webgoat-lessons/ssrf/src/main/java/org/owasp/webgoat/ssrf/SSRFTask2.java" + }, + "region": { + "endColumn": 47, + "endLine": 53, + "startColumn": 35, + "startLine": 53 + } + } + } + ], + "message": { + "text": "Potential server-side request forgery due to [a user-provided value](1)." + }, + "partialFingerprints": { + "primaryLocationLineHash": "e26804d6c609fb76:1" + }, + "properties": { + "github/alertNumber": 24, + "github/alertUrl": "https://api.github.com/repos/nahsra/WebGoat/code-scanning/alerts/24" + }, + "relatedLocations": [ + { + "id": 1, + "message": { + "text": "a user-provided value" + }, + "physicalLocation": { + "artifactLocation": { + "index": 0, + "uri": "webgoat-lessons/ssrf/src/main/java/org/owasp/webgoat/ssrf/SSRFTask2.java" + }, + "region": { + "endColumn": 59, + "endLine": 46, + "startColumn": 35, + "startLine": 46 + } + } + } + ], + "rule": { + "toolComponent": { + "index": 14 + }, + "index": 29 + } + }, + { + "correlationGuid": "f5b73719-2665-4a67-9352-3005bcc59e9d", + "level": "error", + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "index": 1, + "uri": "webgoat-lessons/jwt/src/main/java/org/owasp/webgoat/jwt/JWTVotesEndpoint.java" + }, + "region": { + "endColumn": 39, + "endLine": 111, + "startColumn": 13, + "startLine": 111 + } + } + } + ], + "message": { + "text": "Cookie is added to response without the 'secure' flag being set." + }, + "partialFingerprints": { + "primaryLocationLineHash": "178a5d22f3f4ca47:1" + }, + "properties": { + "github/alertNumber": 23, + "github/alertUrl": "https://api.github.com/repos/nahsra/WebGoat/code-scanning/alerts/23" + }, + "rule": { + "toolComponent": { + "index": 14 + }, + "index": 12 + } + }, + { + "correlationGuid": "c404540f-0dba-49fe-9fd1-f1b04785c642", + "level": "error", + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "index": 1, + "uri": "webgoat-lessons/jwt/src/main/java/org/owasp/webgoat/jwt/JWTVotesEndpoint.java" + }, + "region": { + "endColumn": 39, + "endLine": 106, + "startColumn": 13, + "startLine": 106 + } + } + } + ], + "message": { + "text": "Cookie is added to response without the 'secure' flag being set." + }, + "partialFingerprints": { + "primaryLocationLineHash": "84a4c92c523d81a1:1" + }, + "properties": { + "github/alertNumber": 22, + "github/alertUrl": "https://api.github.com/repos/nahsra/WebGoat/code-scanning/alerts/22" + }, + "rule": { + "toolComponent": { + "index": 14 + }, + "index": 12 + } + }, + { + "codeFlows": [ + { + "threadFlows": [ + { + "locations": [ + { + "location": { + "message": { + "text": "commentStr : String" + }, + "physicalLocation": { + "artifactLocation": { + "index": 3, + "uri": "webgoat-lessons/xxe/src/main/java/org/owasp/webgoat/xxe/ContentTypeAssignment.java" + }, + "region": { + "endColumn": 97, + "endLine": 59, + "startColumn": 67, + "startLine": 59 + } + } + } + }, + { + "location": { + "message": { + "text": "commentStr : String" + }, + "physicalLocation": { + "artifactLocation": { + "index": 3, + "uri": "webgoat-lessons/xxe/src/main/java/org/owasp/webgoat/xxe/ContentTypeAssignment.java" + }, + "region": { + "endColumn": 63, + "endLine": 74, + "startColumn": 53, + "startLine": 74 + } + } + } + }, + { + "location": { + "message": { + "text": "xml : String" + }, + "physicalLocation": { + "artifactLocation": { + "index": 2, + "uri": "webgoat-lessons/xxe/src/main/java/org/owasp/webgoat/xxe/Comments.java" + }, + "region": { + "endColumn": 42, + "endLine": 92, + "startColumn": 32, + "startLine": 92 + } + } + } + }, + { + "location": { + "message": { + "text": "xml : String" + }, + "physicalLocation": { + "artifactLocation": { + "index": 2, + "uri": "webgoat-lessons/xxe/src/main/java/org/owasp/webgoat/xxe/Comments.java" + }, + "region": { + "endColumn": 65, + "endLine": 101, + "startColumn": 62, + "startLine": 101 + } + } + } + }, + { + "location": { + "message": { + "text": "new StringReader(...)" + }, + "physicalLocation": { + "artifactLocation": { + "index": 2, + "uri": "webgoat-lessons/xxe/src/main/java/org/owasp/webgoat/xxe/Comments.java" + }, + "region": { + "endColumn": 66, + "endLine": 101, + "startColumn": 45, + "startLine": 101 + } + } + } + } + ] + } + ] + }, + { + "threadFlows": [ + { + "locations": [ + { + "location": { + "message": { + "text": "commentStr : String" + }, + "physicalLocation": { + "artifactLocation": { + "index": 4, + "uri": "webgoat-lessons/xxe/src/main/java/org/owasp/webgoat/xxe/SimpleXXE.java" + }, + "region": { + "endColumn": 100, + "endLine": 69, + "startColumn": 70, + "startLine": 69 + } + } + } + }, + { + "location": { + "message": { + "text": "commentStr : String" + }, + "physicalLocation": { + "artifactLocation": { + "index": 4, + "uri": "webgoat-lessons/xxe/src/main/java/org/owasp/webgoat/xxe/SimpleXXE.java" + }, + "region": { + "endColumn": 59, + "endLine": 76, + "startColumn": 49, + "startLine": 76 + } + } + } + }, + { + "location": { + "message": { + "text": "xml : String" + }, + "physicalLocation": { + "artifactLocation": { + "index": 2, + "uri": "webgoat-lessons/xxe/src/main/java/org/owasp/webgoat/xxe/Comments.java" + }, + "region": { + "endColumn": 42, + "endLine": 92, + "startColumn": 32, + "startLine": 92 + } + } + } + }, + { + "location": { + "message": { + "text": "xml : String" + }, + "physicalLocation": { + "artifactLocation": { + "index": 2, + "uri": "webgoat-lessons/xxe/src/main/java/org/owasp/webgoat/xxe/Comments.java" + }, + "region": { + "endColumn": 65, + "endLine": 101, + "startColumn": 62, + "startLine": 101 + } + } + } + }, + { + "location": { + "message": { + "text": "new StringReader(...)" + }, + "physicalLocation": { + "artifactLocation": { + "index": 2, + "uri": "webgoat-lessons/xxe/src/main/java/org/owasp/webgoat/xxe/Comments.java" + }, + "region": { + "endColumn": 66, + "endLine": 101, + "startColumn": 45, + "startLine": 101 + } + } + } + } + ] + } + ] + } + ], + "correlationGuid": "e54b8c0f-4638-4a9f-9325-d8742e367564", + "level": "error", + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "index": 2, + "uri": "webgoat-lessons/xxe/src/main/java/org/owasp/webgoat/xxe/Comments.java" + }, + "region": { + "endColumn": 66, + "endLine": 101, + "startColumn": 45, + "startLine": 101 + } + } + } + ], + "message": { + "text": "Unsafe parsing of XML file from [user input](1).\nUnsafe parsing of XML file from [user input](2)." + }, + "partialFingerprints": { + "primaryLocationLineHash": "1c93f3ad0a8f54:1" + }, + "properties": { + "github/alertNumber": 21, + "github/alertUrl": "https://api.github.com/repos/nahsra/WebGoat/code-scanning/alerts/21" + }, + "relatedLocations": [ + { + "id": 1, + "message": { + "text": "user input" + }, + "physicalLocation": { + "artifactLocation": { + "index": 0, + "uri": "webgoat-lessons/xxe/src/main/java/org/owasp/webgoat/xxe/ContentTypeAssignment.java" + }, + "region": { + "endColumn": 97, + "endLine": 59, + "startColumn": 67, + "startLine": 59 + } + } + }, + { + "id": 2, + "message": { + "text": "user input" + }, + "physicalLocation": { + "artifactLocation": { + "index": 0, + "uri": "webgoat-lessons/xxe/src/main/java/org/owasp/webgoat/xxe/SimpleXXE.java" + }, + "region": { + "endColumn": 100, + "endLine": 69, + "startColumn": 70, + "startLine": 69 + } + } + } + ], + "rule": { + "toolComponent": { + "index": 14 + }, + "index": 43 + } + }, + { + "codeFlows": [ + { + "threadFlows": [ + { + "locations": [ + { + "location": { + "message": { + "text": "payload : String" + }, + "physicalLocation": { + "artifactLocation": { + "index": 5, + "uri": "webgoat-lessons/vulnerable-components/src/main/java/org/owasp/webgoat/vulnerable_components/VulnerableComponentsLesson.java" + }, + "region": { + "endColumn": 56, + "endLine": 41, + "startColumn": 28, + "startLine": 41 + } + } + } + }, + { + "location": { + "message": { + "text": "payload" + }, + "physicalLocation": { + "artifactLocation": { + "index": 5, + "uri": "webgoat-lessons/vulnerable-components/src/main/java/org/owasp/webgoat/vulnerable_components/VulnerableComponentsLesson.java" + }, + "region": { + "endColumn": 56, + "endLine": 52, + "startColumn": 49, + "startLine": 52 + } + } + } + } + ] + } + ] + } + ], + "correlationGuid": "dc7a7a3c-5c30-41e9-b2bb-19b9da3fbd4e", + "level": "error", + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "index": 5, + "uri": "webgoat-lessons/vulnerable-components/src/main/java/org/owasp/webgoat/vulnerable_components/VulnerableComponentsLesson.java" + }, + "region": { + "endColumn": 57, + "endLine": 52, + "startColumn": 33, + "startLine": 52 + } + } + } + ], + "message": { + "text": "Unsafe deserialization of [user input](1)." + }, + "partialFingerprints": { + "primaryLocationLineHash": "350c8895428d29a7:1" + }, + "properties": { + "github/alertNumber": 20, + "github/alertUrl": "https://api.github.com/repos/nahsra/WebGoat/code-scanning/alerts/20" + }, + "relatedLocations": [ + { + "id": 1, + "message": { + "text": "user input" + }, + "physicalLocation": { + "artifactLocation": { + "index": 0, + "uri": "webgoat-lessons/vulnerable-components/src/main/java/org/owasp/webgoat/vulnerable_components/VulnerableComponentsLesson.java" + }, + "region": { + "endColumn": 56, + "endLine": 41, + "startColumn": 28, + "startLine": 41 + } + } + } + ], + "rule": { + "toolComponent": { + "index": 14 + }, + "index": 35 + } + }, + { + "correlationGuid": "4f30d956-166d-4303-b83c-70617b7f455a", + "level": "error", + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "index": 1, + "uri": "webgoat-lessons/jwt/src/main/java/org/owasp/webgoat/jwt/JWTVotesEndpoint.java" + }, + "region": { + "endColumn": 68, + "endLine": 170, + "startColumn": 27, + "startLine": 170 + } + } + } + ], + "message": { + "text": "A signing key is set [here](1), but the signature is not verified." + }, + "partialFingerprints": { + "primaryLocationLineHash": "55fa3c43091de850:3" + }, + "properties": { + "github/alertNumber": 19, + "github/alertUrl": "https://api.github.com/repos/nahsra/WebGoat/code-scanning/alerts/19" + }, + "relatedLocations": [ + { + "id": 1, + "message": { + "text": "here" + }, + "physicalLocation": { + "artifactLocation": { + "index": 0, + "uri": "webgoat-lessons/jwt/src/main/java/org/owasp/webgoat/jwt/JWTVotesEndpoint.java" + }, + "region": { + "endColumn": 68, + "endLine": 170, + "startColumn": 27, + "startLine": 170 + } + } + } + ], + "rule": { + "toolComponent": { + "index": 14 + }, + "index": 20 + } + }, + { + "correlationGuid": "5d9c2e2d-9b33-4387-a9f9-db4ab5fdb0ff", + "level": "error", + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "index": 1, + "uri": "webgoat-lessons/jwt/src/main/java/org/owasp/webgoat/jwt/JWTVotesEndpoint.java" + }, + "region": { + "endColumn": 68, + "endLine": 148, + "startColumn": 27, + "startLine": 148 + } + } + } + ], + "message": { + "text": "A signing key is set [here](1), but the signature is not verified." + }, + "partialFingerprints": { + "primaryLocationLineHash": "55fa3c43091de850:2" + }, + "properties": { + "github/alertNumber": 18, + "github/alertUrl": "https://api.github.com/repos/nahsra/WebGoat/code-scanning/alerts/18" + }, + "relatedLocations": [ + { + "id": 1, + "message": { + "text": "here" + }, + "physicalLocation": { + "artifactLocation": { + "index": 0, + "uri": "webgoat-lessons/jwt/src/main/java/org/owasp/webgoat/jwt/JWTVotesEndpoint.java" + }, + "region": { + "endColumn": 68, + "endLine": 148, + "startColumn": 27, + "startLine": 148 + } + } + } + ], + "rule": { + "toolComponent": { + "index": 14 + }, + "index": 20 + } + }, + { + "correlationGuid": "0c1df2c8-5989-4ccd-b0e5-cce7fe2f1a0c", + "level": "error", + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "index": 1, + "uri": "webgoat-lessons/jwt/src/main/java/org/owasp/webgoat/jwt/JWTVotesEndpoint.java" + }, + "region": { + "endColumn": 68, + "endLine": 125, + "startColumn": 27, + "startLine": 125 + } + } + } + ], + "message": { + "text": "A signing key is set [here](1), but the signature is not verified." + }, + "partialFingerprints": { + "primaryLocationLineHash": "55fa3c43091de850:1" + }, + "properties": { + "github/alertNumber": 17, + "github/alertUrl": "https://api.github.com/repos/nahsra/WebGoat/code-scanning/alerts/17" + }, + "relatedLocations": [ + { + "id": 1, + "message": { + "text": "here" + }, + "physicalLocation": { + "artifactLocation": { + "index": 0, + "uri": "webgoat-lessons/jwt/src/main/java/org/owasp/webgoat/jwt/JWTVotesEndpoint.java" + }, + "region": { + "endColumn": 68, + "endLine": 125, + "startColumn": 27, + "startLine": 125 + } + } + } + ], + "rule": { + "toolComponent": { + "index": 14 + }, + "index": 20 + } + }, + { + "correlationGuid": "4b7b3fac-206d-459c-8e61-c348857dcd61", + "level": "error", + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "index": 6, + "uri": "webgoat-lessons/jwt/src/main/java/org/owasp/webgoat/jwt/JWTRefreshEndpoint.java" + }, + "region": { + "endColumn": 80, + "endLine": 129, + "startColumn": 39, + "startLine": 129 + } + } + } + ], + "message": { + "text": "A signing key is set [here](1), but the signature is not verified." + }, + "partialFingerprints": { + "primaryLocationLineHash": "2d030bc00a086d8a:1" + }, + "properties": { + "github/alertNumber": 16, + "github/alertUrl": "https://api.github.com/repos/nahsra/WebGoat/code-scanning/alerts/16" + }, + "relatedLocations": [ + { + "id": 1, + "message": { + "text": "here" + }, + "physicalLocation": { + "artifactLocation": { + "index": 0, + "uri": "webgoat-lessons/jwt/src/main/java/org/owasp/webgoat/jwt/JWTRefreshEndpoint.java" + }, + "region": { + "endColumn": 80, + "endLine": 129, + "startColumn": 39, + "startLine": 129 + } + } + } + ], + "rule": { + "toolComponent": { + "index": 14 + }, + "index": 20 + } + }, + { + "correlationGuid": "16aa40a1-82dd-43c1-81ba-2a25d51a809c", + "level": "error", + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "index": 6, + "uri": "webgoat-lessons/jwt/src/main/java/org/owasp/webgoat/jwt/JWTRefreshEndpoint.java" + }, + "region": { + "endColumn": 64, + "endLine": 104, + "startColumn": 23, + "startLine": 104 + } + } + } + ], + "message": { + "text": "A signing key is set [here](1), but the signature is not verified." + }, + "partialFingerprints": { + "primaryLocationLineHash": "25e8034b7fe5b633:1" + }, + "properties": { + "github/alertNumber": 15, + "github/alertUrl": "https://api.github.com/repos/nahsra/WebGoat/code-scanning/alerts/15" + }, + "relatedLocations": [ + { + "id": 1, + "message": { + "text": "here" + }, + "physicalLocation": { + "artifactLocation": { + "index": 0, + "uri": "webgoat-lessons/jwt/src/main/java/org/owasp/webgoat/jwt/JWTRefreshEndpoint.java" + }, + "region": { + "endColumn": 64, + "endLine": 104, + "startColumn": 23, + "startLine": 104 + } + } + } + ], + "rule": { + "toolComponent": { + "index": 14 + }, + "index": 20 + } + }, + { + "correlationGuid": "efec0347-b530-46aa-ba26-90d8c13e2b04", + "level": "error", + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "index": 7, + "uri": "webgoat-lessons/jwt/src/main/java/org/owasp/webgoat/jwt/JWTSecretKeyEndpoint.java" + }, + "region": { + "endColumn": 62, + "endLine": 78, + "startColumn": 23, + "startLine": 78 + } + } + } + ], + "message": { + "text": "A signing key is set [here](1), but the signature is not verified." + }, + "partialFingerprints": { + "primaryLocationLineHash": "39200c3c4fe0b433:1" + }, + "properties": { + "github/alertNumber": 14, + "github/alertUrl": "https://api.github.com/repos/nahsra/WebGoat/code-scanning/alerts/14" + }, + "relatedLocations": [ + { + "id": 1, + "message": { + "text": "here" + }, + "physicalLocation": { + "artifactLocation": { + "index": 0, + "uri": "webgoat-lessons/jwt/src/main/java/org/owasp/webgoat/jwt/JWTSecretKeyEndpoint.java" + }, + "region": { + "endColumn": 62, + "endLine": 78, + "startColumn": 23, + "startLine": 78 + } + } + } + ], + "rule": { + "toolComponent": { + "index": 14 + }, + "index": 20 + } + }, + { + "correlationGuid": "01523c23-9021-4e17-b215-4b662fa71b9c", + "level": "error", + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "index": 8, + "uri": "webgoat-integration-tests/src/test/java/org/owasp/webgoat/JWTLessonTest.java" + }, + "region": { + "endColumn": 75, + "endLine": 72, + "startColumn": 18, + "startLine": 72 + } + } + } + ], + "message": { + "text": "A signing key is set [here](1), but the signature is not verified." + }, + "partialFingerprints": { + "primaryLocationLineHash": "31eaf1239ab18658:1" + }, + "properties": { + "github/alertNumber": 13, + "github/alertUrl": "https://api.github.com/repos/nahsra/WebGoat/code-scanning/alerts/13" + }, + "relatedLocations": [ + { + "id": 1, + "message": { + "text": "here" + }, + "physicalLocation": { + "artifactLocation": { + "index": 0, + "uri": "webgoat-integration-tests/src/test/java/org/owasp/webgoat/JWTLessonTest.java" + }, + "region": { + "endColumn": 75, + "endLine": 72, + "startColumn": 18, + "startLine": 72 + } + } + } + ], + "rule": { + "toolComponent": { + "index": 14 + }, + "index": 20 + } + }, + { + "correlationGuid": "9a7f2bc9-a7ba-4974-b452-768a926ab837", + "level": "warning", + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "index": 9, + "uri": "webgoat-lessons/crypto/src/main/java/org/owasp/webgoat/crypto/HashingAssignment.java" + }, + "region": { + "endColumn": 58, + "endLine": 56, + "startColumn": 26, + "startLine": 56 + } + } + } + ], + "message": { + "text": "Cryptographic algorithm [MD5](1) is weak and should not be used." + }, + "partialFingerprints": { + "primaryLocationLineHash": "99e2d6034d1626c0:1" + }, + "properties": { + "github/alertNumber": 12, + "github/alertUrl": "https://api.github.com/repos/nahsra/WebGoat/code-scanning/alerts/12" + }, + "relatedLocations": [ + { + "id": 1, + "message": { + "text": "MD5" + }, + "physicalLocation": { + "artifactLocation": { + "index": 0, + "uri": "webgoat-lessons/crypto/src/main/java/org/owasp/webgoat/crypto/HashingAssignment.java" + }, + "region": { + "endColumn": 57, + "endLine": 56, + "startColumn": 52, + "startLine": 56 + } + } + } + ], + "rule": { + "toolComponent": { + "index": 14 + }, + "index": 38 + } + }, + { + "correlationGuid": "2ff0b197-ca8f-4eb0-8fd5-3ad326540ea3", + "level": "error", + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "index": 10, + "uri": "webgoat-container/src/main/java/org/owasp/webgoat/AjaxAuthenticationEntryPoint.java" + }, + "region": { + "endColumn": 63, + "endLine": 53, + "startColumn": 37, + "startLine": 53 + } + } + } + ], + "message": { + "text": "[Error information](1) can be exposed to an external user." + }, + "partialFingerprints": { + "primaryLocationLineHash": "69b1bde9108b11cf:1" + }, + "properties": { + "github/alertNumber": 11, + "github/alertUrl": "https://api.github.com/repos/nahsra/WebGoat/code-scanning/alerts/11" + }, + "relatedLocations": [ + { + "id": 1, + "message": { + "text": "Error information" + }, + "physicalLocation": { + "artifactLocation": { + "index": 0, + "uri": "webgoat-container/src/main/java/org/owasp/webgoat/AjaxAuthenticationEntryPoint.java" + }, + "region": { + "endColumn": 63, + "endLine": 53, + "startColumn": 37, + "startLine": 53 + } + } + } + ], + "rule": { + "toolComponent": { + "index": 14 + }, + "index": 30 + } + }, + { + "codeFlows": [ + { + "threadFlows": [ + { + "locations": [ + { + "location": { + "message": { + "text": "name : String" + }, + "physicalLocation": { + "artifactLocation": { + "index": 11, + "uri": "webgoat-lessons/sql-injection/src/main/java/org/owasp/webgoat/sql_injection/introduction/SqlInjectionLesson9.java" + }, + "region": { + "endColumn": 60, + "endLine": 55, + "startColumn": 35, + "startLine": 55 + } + } + } + }, + { + "location": { + "message": { + "text": "name : String" + }, + "physicalLocation": { + "artifactLocation": { + "index": 11, + "uri": "webgoat-lessons/sql-injection/src/main/java/org/owasp/webgoat/sql_injection/introduction/SqlInjectionLesson9.java" + }, + "region": { + "endColumn": 45, + "endLine": 56, + "startColumn": 41, + "startLine": 56 + } + } + } + }, + { + "location": { + "message": { + "text": "name : String" + }, + "physicalLocation": { + "artifactLocation": { + "index": 11, + "uri": "webgoat-lessons/sql-injection/src/main/java/org/owasp/webgoat/sql_injection/introduction/SqlInjectionLesson9.java" + }, + "region": { + "endColumn": 64, + "endLine": 59, + "startColumn": 53, + "startLine": 59 + } + } + } + }, + { + "location": { + "message": { + "text": "query" + }, + "physicalLocation": { + "artifactLocation": { + "index": 11, + "uri": "webgoat-lessons/sql-injection/src/main/java/org/owasp/webgoat/sql_injection/introduction/SqlInjectionLesson9.java" + }, + "region": { + "endColumn": 65, + "endLine": 66, + "startColumn": 60, + "startLine": 66 + } + } + } + } + ] + } + ] + }, + { + "threadFlows": [ + { + "locations": [ + { + "location": { + "message": { + "text": "auth_tan : String" + }, + "physicalLocation": { + "artifactLocation": { + "index": 11, + "uri": "webgoat-lessons/sql-injection/src/main/java/org/owasp/webgoat/sql_injection/introduction/SqlInjectionLesson9.java" + }, + "region": { + "endColumn": 91, + "endLine": 55, + "startColumn": 62, + "startLine": 55 + } + } + } + }, + { + "location": { + "message": { + "text": "auth_tan : String" + }, + "physicalLocation": { + "artifactLocation": { + "index": 11, + "uri": "webgoat-lessons/sql-injection/src/main/java/org/owasp/webgoat/sql_injection/introduction/SqlInjectionLesson9.java" + }, + "region": { + "endColumn": 55, + "endLine": 56, + "startColumn": 47, + "startLine": 56 + } + } + } + }, + { + "location": { + "message": { + "text": "auth_tan : String" + }, + "physicalLocation": { + "artifactLocation": { + "index": 11, + "uri": "webgoat-lessons/sql-injection/src/main/java/org/owasp/webgoat/sql_injection/introduction/SqlInjectionLesson9.java" + }, + "region": { + "endColumn": 81, + "endLine": 59, + "startColumn": 66, + "startLine": 59 + } + } + } + }, + { + "location": { + "message": { + "text": "query" + }, + "physicalLocation": { + "artifactLocation": { + "index": 11, + "uri": "webgoat-lessons/sql-injection/src/main/java/org/owasp/webgoat/sql_injection/introduction/SqlInjectionLesson9.java" + }, + "region": { + "endColumn": 65, + "endLine": 66, + "startColumn": 60, + "startLine": 66 + } + } + } + } + ] + } + ] + } + ], + "correlationGuid": "330d25ad-e2a2-4274-8677-46a67721abd5", + "level": "error", + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "index": 11, + "uri": "webgoat-lessons/sql-injection/src/main/java/org/owasp/webgoat/sql_injection/introduction/SqlInjectionLesson9.java" + }, + "region": { + "endColumn": 65, + "endLine": 66, + "startColumn": 60, + "startLine": 66 + } + } + } + ], + "message": { + "text": "Query might include code from [this user input](1).\nQuery might include code from [this user input](2)." + }, + "partialFingerprints": { + "primaryLocationLineHash": "c3579bd895056390:1" + }, + "properties": { + "github/alertNumber": 10, + "github/alertUrl": "https://api.github.com/repos/nahsra/WebGoat/code-scanning/alerts/10" + }, + "relatedLocations": [ + { + "id": 1, + "message": { + "text": "this user input" + }, + "physicalLocation": { + "artifactLocation": { + "index": 0, + "uri": "webgoat-lessons/sql-injection/src/main/java/org/owasp/webgoat/sql_injection/introduction/SqlInjectionLesson9.java" + }, + "region": { + "endColumn": 60, + "endLine": 55, + "startColumn": 35, + "startLine": 55 + } + } + }, + { + "id": 2, + "message": { + "text": "this user input" + }, + "physicalLocation": { + "artifactLocation": { + "index": 0, + "uri": "webgoat-lessons/sql-injection/src/main/java/org/owasp/webgoat/sql_injection/introduction/SqlInjectionLesson9.java" + }, + "region": { + "endColumn": 91, + "endLine": 55, + "startColumn": 62, + "startLine": 55 + } + } + } + ], + "rule": { + "toolComponent": { + "index": 14 + }, + "index": 28 + } + }, + { + "codeFlows": [ + { + "threadFlows": [ + { + "locations": [ + { + "location": { + "message": { + "text": "name : String" + }, + "physicalLocation": { + "artifactLocation": { + "index": 12, + "uri": "webgoat-lessons/sql-injection/src/main/java/org/owasp/webgoat/sql_injection/introduction/SqlInjectionLesson8.java" + }, + "region": { + "endColumn": 60, + "endLine": 54, + "startColumn": 35, + "startLine": 54 + } + } + } + }, + { + "location": { + "message": { + "text": "name : String" + }, + "physicalLocation": { + "artifactLocation": { + "index": 12, + "uri": "webgoat-lessons/sql-injection/src/main/java/org/owasp/webgoat/sql_injection/introduction/SqlInjectionLesson8.java" + }, + "region": { + "endColumn": 51, + "endLine": 55, + "startColumn": 47, + "startLine": 55 + } + } + } + }, + { + "location": { + "message": { + "text": "name : String" + }, + "physicalLocation": { + "artifactLocation": { + "index": 12, + "uri": "webgoat-lessons/sql-injection/src/main/java/org/owasp/webgoat/sql_injection/introduction/SqlInjectionLesson8.java" + }, + "region": { + "endColumn": 70, + "endLine": 58, + "startColumn": 59, + "startLine": 58 + } + } + } + }, + { + "location": { + "message": { + "text": "query" + }, + "physicalLocation": { + "artifactLocation": { + "index": 12, + "uri": "webgoat-lessons/sql-injection/src/main/java/org/owasp/webgoat/sql_injection/introduction/SqlInjectionLesson8.java" + }, + "region": { + "endColumn": 65, + "endLine": 66, + "startColumn": 60, + "startLine": 66 + } + } + } + } + ] + } + ] + }, + { + "threadFlows": [ + { + "locations": [ + { + "location": { + "message": { + "text": "auth_tan : String" + }, + "physicalLocation": { + "artifactLocation": { + "index": 12, + "uri": "webgoat-lessons/sql-injection/src/main/java/org/owasp/webgoat/sql_injection/introduction/SqlInjectionLesson8.java" + }, + "region": { + "endColumn": 91, + "endLine": 54, + "startColumn": 62, + "startLine": 54 + } + } + } + }, + { + "location": { + "message": { + "text": "auth_tan : String" + }, + "physicalLocation": { + "artifactLocation": { + "index": 12, + "uri": "webgoat-lessons/sql-injection/src/main/java/org/owasp/webgoat/sql_injection/introduction/SqlInjectionLesson8.java" + }, + "region": { + "endColumn": 61, + "endLine": 55, + "startColumn": 53, + "startLine": 55 + } + } + } + }, + { + "location": { + "message": { + "text": "auth_tan : String" + }, + "physicalLocation": { + "artifactLocation": { + "index": 12, + "uri": "webgoat-lessons/sql-injection/src/main/java/org/owasp/webgoat/sql_injection/introduction/SqlInjectionLesson8.java" + }, + "region": { + "endColumn": 87, + "endLine": 58, + "startColumn": 72, + "startLine": 58 + } + } + } + }, + { + "location": { + "message": { + "text": "query" + }, + "physicalLocation": { + "artifactLocation": { + "index": 12, + "uri": "webgoat-lessons/sql-injection/src/main/java/org/owasp/webgoat/sql_injection/introduction/SqlInjectionLesson8.java" + }, + "region": { + "endColumn": 65, + "endLine": 66, + "startColumn": 60, + "startLine": 66 + } + } + } + } + ] + } + ] + } + ], + "correlationGuid": "003f2452-d35e-46ee-b919-38e38771d412", + "level": "error", + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "index": 12, + "uri": "webgoat-lessons/sql-injection/src/main/java/org/owasp/webgoat/sql_injection/introduction/SqlInjectionLesson8.java" + }, + "region": { + "endColumn": 65, + "endLine": 66, + "startColumn": 60, + "startLine": 66 + } + } + } + ], + "message": { + "text": "Query might include code from [this user input](1).\nQuery might include code from [this user input](2)." + }, + "partialFingerprints": { + "primaryLocationLineHash": "615295edb2bddc7:1" + }, + "properties": { + "github/alertNumber": 9, + "github/alertUrl": "https://api.github.com/repos/nahsra/WebGoat/code-scanning/alerts/9" + }, + "relatedLocations": [ + { + "id": 1, + "message": { + "text": "this user input" + }, + "physicalLocation": { + "artifactLocation": { + "index": 0, + "uri": "webgoat-lessons/sql-injection/src/main/java/org/owasp/webgoat/sql_injection/introduction/SqlInjectionLesson8.java" + }, + "region": { + "endColumn": 60, + "endLine": 54, + "startColumn": 35, + "startLine": 54 + } + } + }, + { + "id": 2, + "message": { + "text": "this user input" + }, + "physicalLocation": { + "artifactLocation": { + "index": 0, + "uri": "webgoat-lessons/sql-injection/src/main/java/org/owasp/webgoat/sql_injection/introduction/SqlInjectionLesson8.java" + }, + "region": { + "endColumn": 91, + "endLine": 54, + "startColumn": 62, + "startLine": 54 + } + } + } + ], + "rule": { + "toolComponent": { + "index": 14 + }, + "index": 28 + } + }, + { + "codeFlows": [ + { + "threadFlows": [ + { + "locations": [ + { + "location": { + "message": { + "text": "userid : String" + }, + "physicalLocation": { + "artifactLocation": { + "index": 13, + "uri": "webgoat-lessons/sql-injection/src/main/java/org/owasp/webgoat/sql_injection/introduction/SqlInjectionLesson5b.java" + }, + "region": { + "endColumn": 62, + "endLine": 51, + "startColumn": 35, + "startLine": 51 + } + } + } + }, + { + "location": { + "message": { + "text": "userid : String" + }, + "physicalLocation": { + "artifactLocation": { + "index": 13, + "uri": "webgoat-lessons/sql-injection/src/main/java/org/owasp/webgoat/sql_injection/introduction/SqlInjectionLesson5b.java" + }, + "region": { + "endColumn": 51, + "endLine": 52, + "startColumn": 45, + "startLine": 52 + } + } + } + }, + { + "location": { + "message": { + "text": "accountName : String" + }, + "physicalLocation": { + "artifactLocation": { + "index": 13, + "uri": "webgoat-lessons/sql-injection/src/main/java/org/owasp/webgoat/sql_injection/introduction/SqlInjectionLesson5b.java" + }, + "region": { + "endColumn": 82, + "endLine": 55, + "startColumn": 64, + "startLine": 55 + } + } + } + }, + { + "location": { + "message": { + "text": "queryString" + }, + "physicalLocation": { + "artifactLocation": { + "index": 13, + "uri": "webgoat-lessons/sql-injection/src/main/java/org/owasp/webgoat/sql_injection/introduction/SqlInjectionLesson5b.java" + }, + "region": { + "endColumn": 78, + "endLine": 58, + "startColumn": 67, + "startLine": 58 + } + } + } + } + ] + } + ] + } + ], + "correlationGuid": "e1dde0ca-f78f-43f1-a434-b7b036ef6ccd", + "level": "error", + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "index": 13, + "uri": "webgoat-lessons/sql-injection/src/main/java/org/owasp/webgoat/sql_injection/introduction/SqlInjectionLesson5b.java" + }, + "region": { + "endColumn": 78, + "endLine": 58, + "startColumn": 67, + "startLine": 58 + } + } + } + ], + "message": { + "text": "Query might include code from [this user input](1)." + }, + "partialFingerprints": { + "primaryLocationLineHash": "c54383daa06514cd:1" + }, + "properties": { + "github/alertNumber": 8, + "github/alertUrl": "https://api.github.com/repos/nahsra/WebGoat/code-scanning/alerts/8" + }, + "relatedLocations": [ + { + "id": 1, + "message": { + "text": "this user input" + }, + "physicalLocation": { + "artifactLocation": { + "index": 0, + "uri": "webgoat-lessons/sql-injection/src/main/java/org/owasp/webgoat/sql_injection/introduction/SqlInjectionLesson5b.java" + }, + "region": { + "endColumn": 62, + "endLine": 51, + "startColumn": 35, + "startLine": 51 + } + } + } + ], + "rule": { + "toolComponent": { + "index": 14 + }, + "index": 28 + } + }, + { + "codeFlows": [ + { + "threadFlows": [ + { + "locations": [ + { + "location": { + "message": { + "text": "account : String" + }, + "physicalLocation": { + "artifactLocation": { + "index": 14, + "uri": "webgoat-lessons/sql-injection/src/main/java/org/owasp/webgoat/sql_injection/introduction/SqlInjectionLesson5a.java" + }, + "region": { + "endColumn": 63, + "endLine": 53, + "startColumn": 35, + "startLine": 53 + } + } + } + }, + { + "location": { + "message": { + "text": "... + ... : String" + }, + "physicalLocation": { + "artifactLocation": { + "index": 14, + "uri": "webgoat-lessons/sql-injection/src/main/java/org/owasp/webgoat/sql_injection/introduction/SqlInjectionLesson5a.java" + }, + "region": { + "endColumn": 74, + "endLine": 54, + "startColumn": 32, + "startLine": 54 + } + } + } + }, + { + "location": { + "message": { + "text": "accountName : String" + }, + "physicalLocation": { + "artifactLocation": { + "index": 14, + "uri": "webgoat-lessons/sql-injection/src/main/java/org/owasp/webgoat/sql_injection/introduction/SqlInjectionLesson5a.java" + }, + "region": { + "endColumn": 62, + "endLine": 57, + "startColumn": 44, + "startLine": 57 + } + } + } + }, + { + "location": { + "message": { + "text": "query" + }, + "physicalLocation": { + "artifactLocation": { + "index": 14, + "uri": "webgoat-lessons/sql-injection/src/main/java/org/owasp/webgoat/sql_injection/introduction/SqlInjectionLesson5a.java" + }, + "region": { + "endColumn": 65, + "endLine": 62, + "startColumn": 60, + "startLine": 62 + } + } + } + } + ] + } + ] + }, + { + "threadFlows": [ + { + "locations": [ + { + "location": { + "message": { + "text": "operator : String" + }, + "physicalLocation": { + "artifactLocation": { + "index": 14, + "uri": "webgoat-lessons/sql-injection/src/main/java/org/owasp/webgoat/sql_injection/introduction/SqlInjectionLesson5a.java" + }, + "region": { + "endColumn": 94, + "endLine": 53, + "startColumn": 65, + "startLine": 53 + } + } + } + }, + { + "location": { + "message": { + "text": "... + ... : String" + }, + "physicalLocation": { + "artifactLocation": { + "index": 14, + "uri": "webgoat-lessons/sql-injection/src/main/java/org/owasp/webgoat/sql_injection/introduction/SqlInjectionLesson5a.java" + }, + "region": { + "endColumn": 74, + "endLine": 54, + "startColumn": 32, + "startLine": 54 + } + } + } + }, + { + "location": { + "message": { + "text": "accountName : String" + }, + "physicalLocation": { + "artifactLocation": { + "index": 14, + "uri": "webgoat-lessons/sql-injection/src/main/java/org/owasp/webgoat/sql_injection/introduction/SqlInjectionLesson5a.java" + }, + "region": { + "endColumn": 62, + "endLine": 57, + "startColumn": 44, + "startLine": 57 + } + } + } + }, + { + "location": { + "message": { + "text": "query" + }, + "physicalLocation": { + "artifactLocation": { + "index": 14, + "uri": "webgoat-lessons/sql-injection/src/main/java/org/owasp/webgoat/sql_injection/introduction/SqlInjectionLesson5a.java" + }, + "region": { + "endColumn": 65, + "endLine": 62, + "startColumn": 60, + "startLine": 62 + } + } + } + } + ] + } + ] + }, + { + "threadFlows": [ + { + "locations": [ + { + "location": { + "message": { + "text": "injection : String" + }, + "physicalLocation": { + "artifactLocation": { + "index": 14, + "uri": "webgoat-lessons/sql-injection/src/main/java/org/owasp/webgoat/sql_injection/introduction/SqlInjectionLesson5a.java" + }, + "region": { + "endColumn": 126, + "endLine": 53, + "startColumn": 96, + "startLine": 53 + } + } + } + }, + { + "location": { + "message": { + "text": "... + ... : String" + }, + "physicalLocation": { + "artifactLocation": { + "index": 14, + "uri": "webgoat-lessons/sql-injection/src/main/java/org/owasp/webgoat/sql_injection/introduction/SqlInjectionLesson5a.java" + }, + "region": { + "endColumn": 74, + "endLine": 54, + "startColumn": 32, + "startLine": 54 + } + } + } + }, + { + "location": { + "message": { + "text": "accountName : String" + }, + "physicalLocation": { + "artifactLocation": { + "index": 14, + "uri": "webgoat-lessons/sql-injection/src/main/java/org/owasp/webgoat/sql_injection/introduction/SqlInjectionLesson5a.java" + }, + "region": { + "endColumn": 62, + "endLine": 57, + "startColumn": 44, + "startLine": 57 + } + } + } + }, + { + "location": { + "message": { + "text": "query" + }, + "physicalLocation": { + "artifactLocation": { + "index": 14, + "uri": "webgoat-lessons/sql-injection/src/main/java/org/owasp/webgoat/sql_injection/introduction/SqlInjectionLesson5a.java" + }, + "region": { + "endColumn": 65, + "endLine": 62, + "startColumn": 60, + "startLine": 62 + } + } + } + } + ] + } + ] + } + ], + "correlationGuid": "19fc92ea-119e-4db5-874a-a9406524dd8b", + "level": "error", + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "index": 14, + "uri": "webgoat-lessons/sql-injection/src/main/java/org/owasp/webgoat/sql_injection/introduction/SqlInjectionLesson5a.java" + }, + "region": { + "endColumn": 65, + "endLine": 62, + "startColumn": 60, + "startLine": 62 + } + } + } + ], + "message": { + "text": "Query might include code from [this user input](1).\nQuery might include code from [this user input](2).\nQuery might include code from [this user input](3)." + }, + "partialFingerprints": { + "primaryLocationLineHash": "2b95e3c48ba92cd0:1" + }, + "properties": { + "github/alertNumber": 7, + "github/alertUrl": "https://api.github.com/repos/nahsra/WebGoat/code-scanning/alerts/7" + }, + "relatedLocations": [ + { + "id": 1, + "message": { + "text": "this user input" + }, + "physicalLocation": { + "artifactLocation": { + "index": 0, + "uri": "webgoat-lessons/sql-injection/src/main/java/org/owasp/webgoat/sql_injection/introduction/SqlInjectionLesson5a.java" + }, + "region": { + "endColumn": 63, + "endLine": 53, + "startColumn": 35, + "startLine": 53 + } + } + }, + { + "id": 2, + "message": { + "text": "this user input" + }, + "physicalLocation": { + "artifactLocation": { + "index": 0, + "uri": "webgoat-lessons/sql-injection/src/main/java/org/owasp/webgoat/sql_injection/introduction/SqlInjectionLesson5a.java" + }, + "region": { + "endColumn": 94, + "endLine": 53, + "startColumn": 65, + "startLine": 53 + } + } + }, + { + "id": 3, + "message": { + "text": "this user input" + }, + "physicalLocation": { + "artifactLocation": { + "index": 0, + "uri": "webgoat-lessons/sql-injection/src/main/java/org/owasp/webgoat/sql_injection/introduction/SqlInjectionLesson5a.java" + }, + "region": { + "endColumn": 126, + "endLine": 53, + "startColumn": 96, + "startLine": 53 + } + } + } + ], + "rule": { + "toolComponent": { + "index": 14 + }, + "index": 28 + } + }, + { + "codeFlows": [ + { + "threadFlows": [ + { + "locations": [ + { + "location": { + "message": { + "text": "query : String" + }, + "physicalLocation": { + "artifactLocation": { + "index": 15, + "uri": "webgoat-lessons/sql-injection/src/main/java/org/owasp/webgoat/sql_injection/introduction/SqlInjectionLesson5.java" + }, + "region": { + "endColumn": 47, + "endLine": 65, + "startColumn": 35, + "startLine": 65 + } + } + } + }, + { + "location": { + "message": { + "text": "query : String" + }, + "physicalLocation": { + "artifactLocation": { + "index": 15, + "uri": "webgoat-lessons/sql-injection/src/main/java/org/owasp/webgoat/sql_injection/introduction/SqlInjectionLesson5.java" + }, + "region": { + "endColumn": 37, + "endLine": 66, + "startColumn": 32, + "startLine": 66 + } + } + } + }, + { + "location": { + "message": { + "text": "query : String" + }, + "physicalLocation": { + "artifactLocation": { + "index": 15, + "uri": "webgoat-lessons/sql-injection/src/main/java/org/owasp/webgoat/sql_injection/introduction/SqlInjectionLesson5.java" + }, + "region": { + "endColumn": 56, + "endLine": 69, + "startColumn": 44, + "startLine": 69 + } + } + } + }, + { + "location": { + "message": { + "text": "query" + }, + "physicalLocation": { + "artifactLocation": { + "index": 15, + "uri": "webgoat-lessons/sql-injection/src/main/java/org/owasp/webgoat/sql_injection/introduction/SqlInjectionLesson5.java" + }, + "region": { + "endColumn": 45, + "endLine": 72, + "startColumn": 40, + "startLine": 72 + } + } + } + } + ] + } + ] + } + ], + "correlationGuid": "3a93a5b9-6728-4c54-821b-8f16e9f62d47", + "level": "error", + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "index": 15, + "uri": "webgoat-lessons/sql-injection/src/main/java/org/owasp/webgoat/sql_injection/introduction/SqlInjectionLesson5.java" + }, + "region": { + "endColumn": 45, + "endLine": 72, + "startColumn": 40, + "startLine": 72 + } + } + } + ], + "message": { + "text": "Query might include code from [this user input](1)." + }, + "partialFingerprints": { + "primaryLocationLineHash": "31dcbb8961cbab44:1" + }, + "properties": { + "github/alertNumber": 6, + "github/alertUrl": "https://api.github.com/repos/nahsra/WebGoat/code-scanning/alerts/6" + }, + "relatedLocations": [ + { + "id": 1, + "message": { + "text": "this user input" + }, + "physicalLocation": { + "artifactLocation": { + "index": 0, + "uri": "webgoat-lessons/sql-injection/src/main/java/org/owasp/webgoat/sql_injection/introduction/SqlInjectionLesson5.java" + }, + "region": { + "endColumn": 47, + "endLine": 65, + "startColumn": 35, + "startLine": 65 + } + } + } + ], + "rule": { + "toolComponent": { + "index": 14 + }, + "index": 28 + } + }, + { + "codeFlows": [ + { + "threadFlows": [ + { + "locations": [ + { + "location": { + "message": { + "text": "query : String" + }, + "physicalLocation": { + "artifactLocation": { + "index": 16, + "uri": "webgoat-lessons/sql-injection/src/main/java/org/owasp/webgoat/sql_injection/introduction/SqlInjectionLesson4.java" + }, + "region": { + "endColumn": 61, + "endLine": 56, + "startColumn": 35, + "startLine": 56 + } + } + } + }, + { + "location": { + "message": { + "text": "query : String" + }, + "physicalLocation": { + "artifactLocation": { + "index": 16, + "uri": "webgoat-lessons/sql-injection/src/main/java/org/owasp/webgoat/sql_injection/introduction/SqlInjectionLesson4.java" + }, + "region": { + "endColumn": 37, + "endLine": 57, + "startColumn": 32, + "startLine": 57 + } + } + } + }, + { + "location": { + "message": { + "text": "query : String" + }, + "physicalLocation": { + "artifactLocation": { + "index": 16, + "uri": "webgoat-lessons/sql-injection/src/main/java/org/owasp/webgoat/sql_injection/introduction/SqlInjectionLesson4.java" + }, + "region": { + "endColumn": 56, + "endLine": 60, + "startColumn": 44, + "startLine": 60 + } + } + } + }, + { + "location": { + "message": { + "text": "query" + }, + "physicalLocation": { + "artifactLocation": { + "index": 16, + "uri": "webgoat-lessons/sql-injection/src/main/java/org/owasp/webgoat/sql_injection/introduction/SqlInjectionLesson4.java" + }, + "region": { + "endColumn": 46, + "endLine": 63, + "startColumn": 41, + "startLine": 63 + } + } + } + } + ] + } + ] + } + ], + "correlationGuid": "6a76563a-8f5a-449f-8349-31bb71545891", + "level": "error", + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "index": 16, + "uri": "webgoat-lessons/sql-injection/src/main/java/org/owasp/webgoat/sql_injection/introduction/SqlInjectionLesson4.java" + }, + "region": { + "endColumn": 46, + "endLine": 63, + "startColumn": 41, + "startLine": 63 + } + } + } + ], + "message": { + "text": "Query might include code from [this user input](1)." + }, + "partialFingerprints": { + "primaryLocationLineHash": "70e3ff06f7af756a:1" + }, + "properties": { + "github/alertNumber": 5, + "github/alertUrl": "https://api.github.com/repos/nahsra/WebGoat/code-scanning/alerts/5" + }, + "relatedLocations": [ + { + "id": 1, + "message": { + "text": "this user input" + }, + "physicalLocation": { + "artifactLocation": { + "index": 0, + "uri": "webgoat-lessons/sql-injection/src/main/java/org/owasp/webgoat/sql_injection/introduction/SqlInjectionLesson4.java" + }, + "region": { + "endColumn": 61, + "endLine": 56, + "startColumn": 35, + "startLine": 56 + } + } + } + ], + "rule": { + "toolComponent": { + "index": 14 + }, + "index": 28 + } + }, + { + "codeFlows": [ + { + "threadFlows": [ + { + "locations": [ + { + "location": { + "message": { + "text": "query : String" + }, + "physicalLocation": { + "artifactLocation": { + "index": 17, + "uri": "webgoat-lessons/sql-injection/src/main/java/org/owasp/webgoat/sql_injection/introduction/SqlInjectionLesson3.java" + }, + "region": { + "endColumn": 61, + "endLine": 56, + "startColumn": 35, + "startLine": 56 + } + } + } + }, + { + "location": { + "message": { + "text": "query : String" + }, + "physicalLocation": { + "artifactLocation": { + "index": 17, + "uri": "webgoat-lessons/sql-injection/src/main/java/org/owasp/webgoat/sql_injection/introduction/SqlInjectionLesson3.java" + }, + "region": { + "endColumn": 37, + "endLine": 57, + "startColumn": 32, + "startLine": 57 + } + } + } + }, + { + "location": { + "message": { + "text": "query : String" + }, + "physicalLocation": { + "artifactLocation": { + "index": 17, + "uri": "webgoat-lessons/sql-injection/src/main/java/org/owasp/webgoat/sql_injection/introduction/SqlInjectionLesson3.java" + }, + "region": { + "endColumn": 56, + "endLine": 60, + "startColumn": 44, + "startLine": 60 + } + } + } + }, + { + "location": { + "message": { + "text": "query" + }, + "physicalLocation": { + "artifactLocation": { + "index": 17, + "uri": "webgoat-lessons/sql-injection/src/main/java/org/owasp/webgoat/sql_injection/introduction/SqlInjectionLesson3.java" + }, + "region": { + "endColumn": 46, + "endLine": 65, + "startColumn": 41, + "startLine": 65 + } + } + } + } + ] + } + ] + } + ], + "correlationGuid": "571c000e-9298-4012-9547-f63907ae8dec", + "level": "error", + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "index": 17, + "uri": "webgoat-lessons/sql-injection/src/main/java/org/owasp/webgoat/sql_injection/introduction/SqlInjectionLesson3.java" + }, + "region": { + "endColumn": 46, + "endLine": 65, + "startColumn": 41, + "startLine": 65 + } + } + } + ], + "message": { + "text": "Query might include code from [this user input](1)." + }, + "partialFingerprints": { + "primaryLocationLineHash": "6831612ccb8d413b:1" + }, + "properties": { + "github/alertNumber": 4, + "github/alertUrl": "https://api.github.com/repos/nahsra/WebGoat/code-scanning/alerts/4" + }, + "relatedLocations": [ + { + "id": 1, + "message": { + "text": "this user input" + }, + "physicalLocation": { + "artifactLocation": { + "index": 0, + "uri": "webgoat-lessons/sql-injection/src/main/java/org/owasp/webgoat/sql_injection/introduction/SqlInjectionLesson3.java" + }, + "region": { + "endColumn": 61, + "endLine": 56, + "startColumn": 35, + "startLine": 56 + } + } + } + ], + "rule": { + "toolComponent": { + "index": 14 + }, + "index": 28 + } + }, + { + "codeFlows": [ + { + "threadFlows": [ + { + "locations": [ + { + "location": { + "message": { + "text": "query : String" + }, + "physicalLocation": { + "artifactLocation": { + "index": 18, + "uri": "webgoat-lessons/sql-injection/src/main/java/org/owasp/webgoat/sql_injection/introduction/SqlInjectionLesson2.java" + }, + "region": { + "endColumn": 61, + "endLine": 55, + "startColumn": 35, + "startLine": 55 + } + } + } + }, + { + "location": { + "message": { + "text": "query : String" + }, + "physicalLocation": { + "artifactLocation": { + "index": 18, + "uri": "webgoat-lessons/sql-injection/src/main/java/org/owasp/webgoat/sql_injection/introduction/SqlInjectionLesson2.java" + }, + "region": { + "endColumn": 37, + "endLine": 56, + "startColumn": 32, + "startLine": 56 + } + } + } + }, + { + "location": { + "message": { + "text": "query : String" + }, + "physicalLocation": { + "artifactLocation": { + "index": 18, + "uri": "webgoat-lessons/sql-injection/src/main/java/org/owasp/webgoat/sql_injection/introduction/SqlInjectionLesson2.java" + }, + "region": { + "endColumn": 56, + "endLine": 59, + "startColumn": 44, + "startLine": 59 + } + } + } + }, + { + "location": { + "message": { + "text": "query" + }, + "physicalLocation": { + "artifactLocation": { + "index": 18, + "uri": "webgoat-lessons/sql-injection/src/main/java/org/owasp/webgoat/sql_injection/introduction/SqlInjectionLesson2.java" + }, + "region": { + "endColumn": 61, + "endLine": 62, + "startColumn": 56, + "startLine": 62 + } + } + } + } + ] + } + ] + } + ], + "correlationGuid": "83f28801-a3dc-49ab-a2d9-d096032b3d82", + "level": "error", + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "index": 18, + "uri": "webgoat-lessons/sql-injection/src/main/java/org/owasp/webgoat/sql_injection/introduction/SqlInjectionLesson2.java" + }, + "region": { + "endColumn": 61, + "endLine": 62, + "startColumn": 56, + "startLine": 62 + } + } + } + ], + "message": { + "text": "Query might include code from [this user input](1)." + }, + "partialFingerprints": { + "primaryLocationLineHash": "1c69de562b7d4118:1" + }, + "properties": { + "github/alertNumber": 3, + "github/alertUrl": "https://api.github.com/repos/nahsra/WebGoat/code-scanning/alerts/3" + }, + "relatedLocations": [ + { + "id": 1, + "message": { + "text": "this user input" + }, + "physicalLocation": { + "artifactLocation": { + "index": 0, + "uri": "webgoat-lessons/sql-injection/src/main/java/org/owasp/webgoat/sql_injection/introduction/SqlInjectionLesson2.java" + }, + "region": { + "endColumn": 61, + "endLine": 55, + "startColumn": 35, + "startLine": 55 + } + } + } + ], + "rule": { + "toolComponent": { + "index": 14 + }, + "index": 28 + } + }, + { + "codeFlows": [ + { + "threadFlows": [ + { + "locations": [ + { + "location": { + "message": { + "text": "action_string : String" + }, + "physicalLocation": { + "artifactLocation": { + "index": 19, + "uri": "webgoat-lessons/sql-injection/src/main/java/org/owasp/webgoat/sql_injection/introduction/SqlInjectionLesson10.java" + }, + "region": { + "endColumn": 69, + "endLine": 52, + "startColumn": 35, + "startLine": 52 + } + } + } + }, + { + "location": { + "message": { + "text": "action_string : String" + }, + "physicalLocation": { + "artifactLocation": { + "index": 19, + "uri": "webgoat-lessons/sql-injection/src/main/java/org/owasp/webgoat/sql_injection/introduction/SqlInjectionLesson10.java" + }, + "region": { + "endColumn": 57, + "endLine": 53, + "startColumn": 44, + "startLine": 53 + } + } + } + }, + { + "location": { + "message": { + "text": "action : String" + }, + "physicalLocation": { + "artifactLocation": { + "index": 19, + "uri": "webgoat-lessons/sql-injection/src/main/java/org/owasp/webgoat/sql_injection/introduction/SqlInjectionLesson10.java" + }, + "region": { + "endColumn": 69, + "endLine": 56, + "startColumn": 56, + "startLine": 56 + } + } + } + }, + { + "location": { + "message": { + "text": "query" + }, + "physicalLocation": { + "artifactLocation": { + "index": 19, + "uri": "webgoat-lessons/sql-injection/src/main/java/org/owasp/webgoat/sql_injection/introduction/SqlInjectionLesson10.java" + }, + "region": { + "endColumn": 65, + "endLine": 63, + "startColumn": 60, + "startLine": 63 + } + } + } + } + ] + } + ] + } + ], + "correlationGuid": "352c4a51-9495-4013-b121-da4188468c12", + "level": "error", + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "index": 19, + "uri": "webgoat-lessons/sql-injection/src/main/java/org/owasp/webgoat/sql_injection/introduction/SqlInjectionLesson10.java" + }, + "region": { + "endColumn": 65, + "endLine": 63, + "startColumn": 60, + "startLine": 63 + } + } + } + ], + "message": { + "text": "Query might include code from [this user input](1)." + }, + "partialFingerprints": { + "primaryLocationLineHash": "983b99783dada75a:1" + }, + "properties": { + "github/alertNumber": 2, + "github/alertUrl": "https://api.github.com/repos/nahsra/WebGoat/code-scanning/alerts/2" + }, + "relatedLocations": [ + { + "id": 1, + "message": { + "text": "this user input" + }, + "physicalLocation": { + "artifactLocation": { + "index": 0, + "uri": "webgoat-lessons/sql-injection/src/main/java/org/owasp/webgoat/sql_injection/introduction/SqlInjectionLesson10.java" + }, + "region": { + "endColumn": 69, + "endLine": 52, + "startColumn": 35, + "startLine": 52 + } + } + } + ], + "rule": { + "toolComponent": { + "index": 14 + }, + "index": 28 + } + }, + { + "codeFlows": [ + { + "threadFlows": [ + { + "locations": [ + { + "location": { + "message": { + "text": "userid_6a : String" + }, + "physicalLocation": { + "artifactLocation": { + "index": 20, + "uri": "webgoat-lessons/sql-injection/src/main/java/org/owasp/webgoat/sql_injection/advanced/SqlInjectionLesson6a.java" + }, + "region": { + "endColumn": 65, + "endLine": 51, + "startColumn": 35, + "startLine": 51 + } + } + } + }, + { + "location": { + "message": { + "text": "userid_6a : String" + }, + "physicalLocation": { + "artifactLocation": { + "index": 20, + "uri": "webgoat-lessons/sql-injection/src/main/java/org/owasp/webgoat/sql_injection/advanced/SqlInjectionLesson6a.java" + }, + "region": { + "endColumn": 41, + "endLine": 52, + "startColumn": 32, + "startLine": 52 + } + } + } + }, + { + "location": { + "message": { + "text": "accountName : String" + }, + "physicalLocation": { + "artifactLocation": { + "index": 20, + "uri": "webgoat-lessons/sql-injection/src/main/java/org/owasp/webgoat/sql_injection/advanced/SqlInjectionLesson6a.java" + }, + "region": { + "endColumn": 59, + "endLine": 56, + "startColumn": 41, + "startLine": 56 + } + } + } + }, + { + "location": { + "message": { + "text": "query" + }, + "physicalLocation": { + "artifactLocation": { + "index": 20, + "uri": "webgoat-lessons/sql-injection/src/main/java/org/owasp/webgoat/sql_injection/advanced/SqlInjectionLesson6a.java" + }, + "region": { + "endColumn": 65, + "endLine": 67, + "startColumn": 60, + "startLine": 67 + } + } + } + } + ] + } + ] + }, + { + "threadFlows": [ + { + "locations": [ + { + "location": { + "message": { + "text": "userId : String" + }, + "physicalLocation": { + "artifactLocation": { + "index": 21, + "uri": "webgoat-lessons/sql-injection/src/main/java/org/owasp/webgoat/sql_injection/mitigation/SqlOnlyInputValidation.java" + }, + "region": { + "endColumn": 95, + "endLine": 48, + "startColumn": 32, + "startLine": 48 + } + } + } + }, + { + "location": { + "message": { + "text": "userId : String" + }, + "physicalLocation": { + "artifactLocation": { + "index": 21, + "uri": "webgoat-lessons/sql-injection/src/main/java/org/owasp/webgoat/sql_injection/mitigation/SqlOnlyInputValidation.java" + }, + "region": { + "endColumn": 68, + "endLine": 52, + "startColumn": 62, + "startLine": 52 + } + } + } + }, + { + "location": { + "message": { + "text": "accountName : String" + }, + "physicalLocation": { + "artifactLocation": { + "index": 20, + "uri": "webgoat-lessons/sql-injection/src/main/java/org/owasp/webgoat/sql_injection/advanced/SqlInjectionLesson6a.java" + }, + "region": { + "endColumn": 59, + "endLine": 56, + "startColumn": 41, + "startLine": 56 + } + } + } + }, + { + "location": { + "message": { + "text": "query" + }, + "physicalLocation": { + "artifactLocation": { + "index": 20, + "uri": "webgoat-lessons/sql-injection/src/main/java/org/owasp/webgoat/sql_injection/advanced/SqlInjectionLesson6a.java" + }, + "region": { + "endColumn": 65, + "endLine": 67, + "startColumn": 60, + "startLine": 67 + } + } + } + } + ] + } + ] + } + ], + "correlationGuid": "11e9ead0-0c29-494d-a56c-ffb63d9d57b1", + "level": "error", + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "index": 20, + "uri": "webgoat-lessons/sql-injection/src/main/java/org/owasp/webgoat/sql_injection/advanced/SqlInjectionLesson6a.java" + }, + "region": { + "endColumn": 65, + "endLine": 67, + "startColumn": 60, + "startLine": 67 + } + } + } + ], + "message": { + "text": "Query might include code from [this user input](1).\nQuery might include code from [this user input](2)." + }, + "partialFingerprints": { + "primaryLocationLineHash": "2b95e3c48ba92cd0:1" + }, + "properties": { + "github/alertNumber": 1, + "github/alertUrl": "https://api.github.com/repos/nahsra/WebGoat/code-scanning/alerts/1" + }, + "relatedLocations": [ + { + "id": 1, + "message": { + "text": "this user input" + }, + "physicalLocation": { + "artifactLocation": { + "index": 0, + "uri": "webgoat-lessons/sql-injection/src/main/java/org/owasp/webgoat/sql_injection/advanced/SqlInjectionLesson6a.java" + }, + "region": { + "endColumn": 65, + "endLine": 51, + "startColumn": 35, + "startLine": 51 + } + } + }, + { + "id": 2, + "message": { + "text": "this user input" + }, + "physicalLocation": { + "artifactLocation": { + "index": 0, + "uri": "webgoat-lessons/sql-injection/src/main/java/org/owasp/webgoat/sql_injection/mitigation/SqlOnlyInputValidation.java" + }, + "region": { + "endColumn": 95, + "endLine": 48, + "startColumn": 32, + "startLine": 48 + } + } + } + ], + "rule": { + "toolComponent": { + "index": 14 + }, + "index": 28 + } + } + ], + "tool": { + "driver": { + "name": "CodeQL", + "version": "2.8.0" + }, + "extensions": [ + { + "name": "legacy-upgrades", + "semanticVersion": "0.0.0" + }, + { + "name": "codeql/python-all", + "semanticVersion": "0.0.8+50f546043a9ecea98b2bf34e7376ee9ef30a8779" + }, + { + "name": "codeql/python-examples", + "semanticVersion": "0.0.2+50f546043a9ecea98b2bf34e7376ee9ef30a8779" + }, + { + "name": "codeql/ruby-all", + "semanticVersion": "0.0.8+50f546043a9ecea98b2bf34e7376ee9ef30a8779" + }, + { + "name": "codeql/java-all", + "semanticVersion": "0.0.8+50f546043a9ecea98b2bf34e7376ee9ef30a8779" + }, + { + "name": "codeql/csharp-all", + "semanticVersion": "0.0.8+50f546043a9ecea98b2bf34e7376ee9ef30a8779" + }, + { + "name": "codeql/javascript-all", + "semanticVersion": "0.0.9+50f546043a9ecea98b2bf34e7376ee9ef30a8779" + }, + { + "name": "codeql/csharp-examples", + "semanticVersion": "0.0.2+50f546043a9ecea98b2bf34e7376ee9ef30a8779" + }, + { + "name": "codeql/ruby-examples", + "semanticVersion": "0.0.2+50f546043a9ecea98b2bf34e7376ee9ef30a8779" + }, + { + "name": "codeql/cpp-examples", + "semanticVersion": "0.0.2+50f546043a9ecea98b2bf34e7376ee9ef30a8779" + }, + { + "name": "codeql/ruby-queries", + "semanticVersion": "0.0.8+50f546043a9ecea98b2bf34e7376ee9ef30a8779" + }, + { + "name": "codeql/go-examples", + "semanticVersion": "0.0.2+14d227a232489bccc3e2661088452af185a2516b" + }, + { + "name": "codeql/java-examples", + "semanticVersion": "0.0.2+50f546043a9ecea98b2bf34e7376ee9ef30a8779" + }, + { + "name": "codeql/csharp-queries", + "semanticVersion": "0.0.8+50f546043a9ecea98b2bf34e7376ee9ef30a8779" + }, + { + "name": "codeql/java-queries", + "rules": [ + { + "defaultConfiguration": { + "level": "error" + }, + "fullDescription": { + "text": "Instantiating an Android fragment from a user-provided value may allow a malicious application to bypass access controls, exposing the application to unintended effects." + }, + "help": { + "markdown": "# Android fragment injection\nWhen fragments are instantiated with externally provided names, this exposes any exported activity that dynamically creates and hosts the fragment to fragment injection. A malicious application could provide the name of an arbitrary fragment, even one not designed to be externally accessible, and inject it into the activity. This can bypass access controls and expose the application to unintended effects.\n\nFragments are reusable parts of an Android application's user interface. Even though a fragment controls its own lifecycle and layout, and handles its input events, it cannot exist on its own: it must be hosted either by an activity or another fragment. This means that, normally, a fragment will be accessible by third-party applications (that is, exported) only if its hosting activity is itself exported.\n\n\n## Recommendation\nIn general, do not instantiate classes (including fragments) with user-provided names unless the name has been properly validated. Also, if an exported activity is extending the `PreferenceActivity` class, make sure that the `isValidFragment` method is overriden and only returns `true` when the provided `fragmentName` points to an intended fragment.\n\n\n## Example\nThe following example shows two cases: in the first one, untrusted data is used to instantiate and add a fragment to an activity, while in the second one, a fragment is safely added with a static name.\n\n\n```java\npublic class MyActivity extends FragmentActivity {\n\n @Override\n protected void onCreate(Bundle savedInstance) {\n try {\n super.onCreate(savedInstance);\n // BAD: Fragment instantiated from user input without validation\n {\n String fName = getIntent().getStringExtra(\"fragmentName\");\n getFragmentManager().beginTransaction().replace(com.android.internal.R.id.prefs,\n Fragment.instantiate(this, fName, null)).commit();\n }\n // GOOD: Fragment instantiated statically\n {\n getFragmentManager().beginTransaction()\n .replace(com.android.internal.R.id.prefs, new MyFragment()).commit();\n }\n } catch (Exception e) {\n }\n }\n\n}\n\n```\nThe next example shows two activities that extend `PreferenceActivity`. The first activity overrides `isValidFragment`, but it wrongly returns `true` unconditionally. The second activity correctly overrides `isValidFragment` so that it only returns `true` when `fragmentName` is a trusted fragment name.\n\n\n```java\nclass UnsafeActivity extends PreferenceActivity {\n\n @Override\n protected boolean isValidFragment(String fragmentName) {\n // BAD: any Fragment name can be provided.\n return true;\n }\n}\n\n\nclass SafeActivity extends PreferenceActivity {\n @Override\n protected boolean isValidFragment(String fragmentName) {\n // Good: only trusted Fragment names are allowed.\n return SafeFragment1.class.getName().equals(fragmentName)\n || SafeFragment2.class.getName().equals(fragmentName)\n || SafeFragment3.class.getName().equals(fragmentName);\n }\n\n}\n\n\n```\n\n## References\n* Google Help: [How to fix Fragment Injection vulnerability](https://support.google.com/faqs/answer/7188427?hl=en).\n* IBM Security Systems: [Android collapses into Fragments](https://securityintelligence.com/wp-content/uploads/2013/12/android-collapses-into-fragments.pdf).\n* Android Developers: [Fragments](https://developer.android.com/guide/fragments)\n* Common Weakness Enumeration: [CWE-470](https://cwe.mitre.org/data/definitions/470.html).\n", + "text": "# Android fragment injection\nWhen fragments are instantiated with externally provided names, this exposes any exported activity that dynamically creates and hosts the fragment to fragment injection. A malicious application could provide the name of an arbitrary fragment, even one not designed to be externally accessible, and inject it into the activity. This can bypass access controls and expose the application to unintended effects.\n\nFragments are reusable parts of an Android application's user interface. Even though a fragment controls its own lifecycle and layout, and handles its input events, it cannot exist on its own: it must be hosted either by an activity or another fragment. This means that, normally, a fragment will be accessible by third-party applications (that is, exported) only if its hosting activity is itself exported.\n\n\n## Recommendation\nIn general, do not instantiate classes (including fragments) with user-provided names unless the name has been properly validated. Also, if an exported activity is extending the `PreferenceActivity` class, make sure that the `isValidFragment` method is overriden and only returns `true` when the provided `fragmentName` points to an intended fragment.\n\n\n## Example\nThe following example shows two cases: in the first one, untrusted data is used to instantiate and add a fragment to an activity, while in the second one, a fragment is safely added with a static name.\n\n\n```java\npublic class MyActivity extends FragmentActivity {\n\n @Override\n protected void onCreate(Bundle savedInstance) {\n try {\n super.onCreate(savedInstance);\n // BAD: Fragment instantiated from user input without validation\n {\n String fName = getIntent().getStringExtra(\"fragmentName\");\n getFragmentManager().beginTransaction().replace(com.android.internal.R.id.prefs,\n Fragment.instantiate(this, fName, null)).commit();\n }\n // GOOD: Fragment instantiated statically\n {\n getFragmentManager().beginTransaction()\n .replace(com.android.internal.R.id.prefs, new MyFragment()).commit();\n }\n } catch (Exception e) {\n }\n }\n\n}\n\n```\nThe next example shows two activities that extend `PreferenceActivity`. The first activity overrides `isValidFragment`, but it wrongly returns `true` unconditionally. The second activity correctly overrides `isValidFragment` so that it only returns `true` when `fragmentName` is a trusted fragment name.\n\n\n```java\nclass UnsafeActivity extends PreferenceActivity {\n\n @Override\n protected boolean isValidFragment(String fragmentName) {\n // BAD: any Fragment name can be provided.\n return true;\n }\n}\n\n\nclass SafeActivity extends PreferenceActivity {\n @Override\n protected boolean isValidFragment(String fragmentName) {\n // Good: only trusted Fragment names are allowed.\n return SafeFragment1.class.getName().equals(fragmentName)\n || SafeFragment2.class.getName().equals(fragmentName)\n || SafeFragment3.class.getName().equals(fragmentName);\n }\n\n}\n\n\n```\n\n## References\n* Google Help: [How to fix Fragment Injection vulnerability](https://support.google.com/faqs/answer/7188427?hl=en).\n* IBM Security Systems: [Android collapses into Fragments](https://securityintelligence.com/wp-content/uploads/2013/12/android-collapses-into-fragments.pdf).\n* Android Developers: [Fragments](https://developer.android.com/guide/fragments)\n* Common Weakness Enumeration: [CWE-470](https://cwe.mitre.org/data/definitions/470.html).\n" + }, + "id": "java/android/fragment-injection", + "name": "java/android/fragment-injection", + "properties": { + "security-severity": "9.800000", + "tags": [ + "external/cwe/cwe-470", + "security" + ] + }, + "shortDescription": { + "text": "Android fragment injection" + } + }, + { + "defaultConfiguration": { + "level": "error" + }, + "fullDescription": { + "text": "An insecure implementation of the 'isValidFragment' method of the 'PreferenceActivity' class may allow a malicious application to bypass access controls, exposing the application to unintended effects." + }, + "help": { + "markdown": "# Android fragment injection in PreferenceActivity\nWhen fragments are instantiated with externally provided names, this exposes any exported activity that dynamically creates and hosts the fragment to fragment injection. A malicious application could provide the name of an arbitrary fragment, even one not designed to be externally accessible, and inject it into the activity. This can bypass access controls and expose the application to unintended effects.\n\nFragments are reusable parts of an Android application's user interface. Even though a fragment controls its own lifecycle and layout, and handles its input events, it cannot exist on its own: it must be hosted either by an activity or another fragment. This means that, normally, a fragment will be accessible by third-party applications (that is, exported) only if its hosting activity is itself exported.\n\n\n## Recommendation\nIn general, do not instantiate classes (including fragments) with user-provided names unless the name has been properly validated. Also, if an exported activity is extending the `PreferenceActivity` class, make sure that the `isValidFragment` method is overriden and only returns `true` when the provided `fragmentName` points to an intended fragment.\n\n\n## Example\nThe following example shows two cases: in the first one, untrusted data is used to instantiate and add a fragment to an activity, while in the second one, a fragment is safely added with a static name.\n\n\n```java\npublic class MyActivity extends FragmentActivity {\n\n @Override\n protected void onCreate(Bundle savedInstance) {\n try {\n super.onCreate(savedInstance);\n // BAD: Fragment instantiated from user input without validation\n {\n String fName = getIntent().getStringExtra(\"fragmentName\");\n getFragmentManager().beginTransaction().replace(com.android.internal.R.id.prefs,\n Fragment.instantiate(this, fName, null)).commit();\n }\n // GOOD: Fragment instantiated statically\n {\n getFragmentManager().beginTransaction()\n .replace(com.android.internal.R.id.prefs, new MyFragment()).commit();\n }\n } catch (Exception e) {\n }\n }\n\n}\n\n```\nThe next example shows two activities that extend `PreferenceActivity`. The first activity overrides `isValidFragment`, but it wrongly returns `true` unconditionally. The second activity correctly overrides `isValidFragment` so that it only returns `true` when `fragmentName` is a trusted fragment name.\n\n\n```java\nclass UnsafeActivity extends PreferenceActivity {\n\n @Override\n protected boolean isValidFragment(String fragmentName) {\n // BAD: any Fragment name can be provided.\n return true;\n }\n}\n\n\nclass SafeActivity extends PreferenceActivity {\n @Override\n protected boolean isValidFragment(String fragmentName) {\n // Good: only trusted Fragment names are allowed.\n return SafeFragment1.class.getName().equals(fragmentName)\n || SafeFragment2.class.getName().equals(fragmentName)\n || SafeFragment3.class.getName().equals(fragmentName);\n }\n\n}\n\n\n```\n\n## References\n* Google Help: [How to fix Fragment Injection vulnerability](https://support.google.com/faqs/answer/7188427?hl=en).\n* IBM Security Systems: [Android collapses into Fragments](https://securityintelligence.com/wp-content/uploads/2013/12/android-collapses-into-fragments.pdf).\n* Android Developers: [Fragments](https://developer.android.com/guide/fragments)\n* Common Weakness Enumeration: [CWE-470](https://cwe.mitre.org/data/definitions/470.html).\n", + "text": "# Android fragment injection in PreferenceActivity\nWhen fragments are instantiated with externally provided names, this exposes any exported activity that dynamically creates and hosts the fragment to fragment injection. A malicious application could provide the name of an arbitrary fragment, even one not designed to be externally accessible, and inject it into the activity. This can bypass access controls and expose the application to unintended effects.\n\nFragments are reusable parts of an Android application's user interface. Even though a fragment controls its own lifecycle and layout, and handles its input events, it cannot exist on its own: it must be hosted either by an activity or another fragment. This means that, normally, a fragment will be accessible by third-party applications (that is, exported) only if its hosting activity is itself exported.\n\n\n## Recommendation\nIn general, do not instantiate classes (including fragments) with user-provided names unless the name has been properly validated. Also, if an exported activity is extending the `PreferenceActivity` class, make sure that the `isValidFragment` method is overriden and only returns `true` when the provided `fragmentName` points to an intended fragment.\n\n\n## Example\nThe following example shows two cases: in the first one, untrusted data is used to instantiate and add a fragment to an activity, while in the second one, a fragment is safely added with a static name.\n\n\n```java\npublic class MyActivity extends FragmentActivity {\n\n @Override\n protected void onCreate(Bundle savedInstance) {\n try {\n super.onCreate(savedInstance);\n // BAD: Fragment instantiated from user input without validation\n {\n String fName = getIntent().getStringExtra(\"fragmentName\");\n getFragmentManager().beginTransaction().replace(com.android.internal.R.id.prefs,\n Fragment.instantiate(this, fName, null)).commit();\n }\n // GOOD: Fragment instantiated statically\n {\n getFragmentManager().beginTransaction()\n .replace(com.android.internal.R.id.prefs, new MyFragment()).commit();\n }\n } catch (Exception e) {\n }\n }\n\n}\n\n```\nThe next example shows two activities that extend `PreferenceActivity`. The first activity overrides `isValidFragment`, but it wrongly returns `true` unconditionally. The second activity correctly overrides `isValidFragment` so that it only returns `true` when `fragmentName` is a trusted fragment name.\n\n\n```java\nclass UnsafeActivity extends PreferenceActivity {\n\n @Override\n protected boolean isValidFragment(String fragmentName) {\n // BAD: any Fragment name can be provided.\n return true;\n }\n}\n\n\nclass SafeActivity extends PreferenceActivity {\n @Override\n protected boolean isValidFragment(String fragmentName) {\n // Good: only trusted Fragment names are allowed.\n return SafeFragment1.class.getName().equals(fragmentName)\n || SafeFragment2.class.getName().equals(fragmentName)\n || SafeFragment3.class.getName().equals(fragmentName);\n }\n\n}\n\n\n```\n\n## References\n* Google Help: [How to fix Fragment Injection vulnerability](https://support.google.com/faqs/answer/7188427?hl=en).\n* IBM Security Systems: [Android collapses into Fragments](https://securityintelligence.com/wp-content/uploads/2013/12/android-collapses-into-fragments.pdf).\n* Android Developers: [Fragments](https://developer.android.com/guide/fragments)\n* Common Weakness Enumeration: [CWE-470](https://cwe.mitre.org/data/definitions/470.html).\n" + }, + "id": "java/android/fragment-injection-preference-activity", + "name": "java/android/fragment-injection-preference-activity", + "properties": { + "security-severity": "9.800000", + "tags": [ + "external/cwe/cwe-470", + "security" + ] + }, + "shortDescription": { + "text": "Android fragment injection in PreferenceActivity" + } + }, + { + "defaultConfiguration": { + "level": "error" + }, + "fullDescription": { + "text": "Sending an implicit and mutable 'PendingIntent' to an unspecified third party component may provide an attacker with access to internal components of the application or cause other unintended effects." + }, + "help": { + "markdown": "# Use of implicit PendingIntents\nA `PendingIntent` is used to wrap an `Intent` that will be supplied and executed by another application. When the `Intent` is executed, it behaves as if it were run directly by the supplying application, using the privileges of that application.\n\nIf a `PendingIntent` is configured to be mutable, the fields of its internal `Intent` can be changed by the receiving application if they were not previously set. This means that a mutable `PendingIntent` that has not defined a destination component (that is, an implicit `PendingIntent`) can be altered to execute an arbitrary action with the privileges of the application that created it.\n\nA malicious application can access an implicit `PendingIntent` as follows:\n\n* It is wrapped and sent as an extra of another implicit `Intent`.\n* It is sent as the action of a `Slide`.\n* It is sent as the action of a `Notification`.\n\n\nOn gaining access, the attacker can modify the underlying `Intent` and execute an arbitrary action with elevated privileges. This could give the malicious application access to private components of the victim application, or the ability to perform actions without having the necessary permissions.\n\n\n## Recommendation\nAvoid creating implicit `PendingIntent`s. This means that the underlying `Intent` should always have an explicit destination component.\n\nWhen you add the `PendingIntent` as an extra of another `Intent`, make sure that this second `Intent` also has an explicit destination component, so that it is not delivered to untrusted applications.\n\nCreate the `PendingIntent` using the flag `FLAG_IMMUTABLE` whenever possible, to prevent the destination component from modifying empty fields of the underlying `Intent`.\n\n\n## Example\nIn the following examples, a `PendingIntent` is created and wrapped as an extra of another `Intent`.\n\nIn the first example, both the `PendingIntent` and the `Intent` it is wrapped in are implicit, making them vulnerable to attack.\n\nIn the second example, the issue is avoided by adding explicit destination components to the `PendingIntent` and the wrapping `Intent`.\n\nThe third example uses the `FLAG_IMMUTABLE` flag to prevent the underlying `Intent` from being modified by the destination component.\n\n\n```java\nimport android.app.Activity;\nimport android.app.PendingIntent;\nimport android.content.Intent;\nimport android.os.Bundle;\n\npublic class ImplicitPendingIntents extends Activity {\n\n\tpublic void onCreate(Bundle savedInstance) {\n\t\t{\n\t\t\t// BAD: an implicit Intent is used to create a PendingIntent.\n\t\t\t// The PendingIntent is then added to another implicit Intent\n\t\t\t// and started.\n\t\t\tIntent baseIntent = new Intent();\n\t\t\tPendingIntent pi =\n\t\t\t\t\tPendingIntent.getActivity(this, 0, baseIntent, PendingIntent.FLAG_ONE_SHOT);\n\t\t\tIntent fwdIntent = new Intent(\"SOME_ACTION\");\n\t\t\tfwdIntent.putExtra(\"fwdIntent\", pi);\n\t\t\tsendBroadcast(fwdIntent);\n\t\t}\n\n\t\t{\n\t\t\t// GOOD: both the PendingIntent and the wrapping Intent are explicit.\n\t\t\tIntent safeIntent = new Intent(this, AnotherActivity.class);\n\t\t\tPendingIntent pi =\n\t\t\t\t\tPendingIntent.getActivity(this, 0, safeIntent, PendingIntent.FLAG_ONE_SHOT);\n\t\t\tIntent fwdIntent = new Intent();\n\t\t\tfwdIntent.setClassName(\"destination.package\", \"DestinationClass\");\n\t\t\tfwdIntent.putExtra(\"fwdIntent\", pi);\n\t\t\tstartActivity(fwdIntent);\n\t\t}\n\n\t\t{\n\t\t\t// GOOD: The PendingIntent is created with FLAG_IMMUTABLE.\n\t\t\tIntent baseIntent = new Intent(\"SOME_ACTION\");\n\t\t\tPendingIntent pi =\n\t\t\t\t\tPendingIntent.getActivity(this, 0, baseIntent, PendingIntent.FLAG_IMMUTABLE);\n\t\t\tIntent fwdIntent = new Intent();\n\t\t\tfwdIntent.setClassName(\"destination.package\", \"DestinationClass\");\n\t\t\tfwdIntent.putExtra(\"fwdIntent\", pi);\n\t\t\tstartActivity(fwdIntent);\n\t\t}\n\t}\n}\n\n```\n\n## References\n* Google Help: [ Remediation for Implicit PendingIntent Vulnerability ](https://support.google.com/faqs/answer/10437428?hl=en)\n* University of Potsdam: [ PIAnalyzer: A precise approach for PendingIntent vulnerability analysis ](https://www.cs.uni-potsdam.de/se/papers/esorics18.pdf)\n* Common Weakness Enumeration: [CWE-927](https://cwe.mitre.org/data/definitions/927.html).\n", + "text": "# Use of implicit PendingIntents\nA `PendingIntent` is used to wrap an `Intent` that will be supplied and executed by another application. When the `Intent` is executed, it behaves as if it were run directly by the supplying application, using the privileges of that application.\n\nIf a `PendingIntent` is configured to be mutable, the fields of its internal `Intent` can be changed by the receiving application if they were not previously set. This means that a mutable `PendingIntent` that has not defined a destination component (that is, an implicit `PendingIntent`) can be altered to execute an arbitrary action with the privileges of the application that created it.\n\nA malicious application can access an implicit `PendingIntent` as follows:\n\n* It is wrapped and sent as an extra of another implicit `Intent`.\n* It is sent as the action of a `Slide`.\n* It is sent as the action of a `Notification`.\n\n\nOn gaining access, the attacker can modify the underlying `Intent` and execute an arbitrary action with elevated privileges. This could give the malicious application access to private components of the victim application, or the ability to perform actions without having the necessary permissions.\n\n\n## Recommendation\nAvoid creating implicit `PendingIntent`s. This means that the underlying `Intent` should always have an explicit destination component.\n\nWhen you add the `PendingIntent` as an extra of another `Intent`, make sure that this second `Intent` also has an explicit destination component, so that it is not delivered to untrusted applications.\n\nCreate the `PendingIntent` using the flag `FLAG_IMMUTABLE` whenever possible, to prevent the destination component from modifying empty fields of the underlying `Intent`.\n\n\n## Example\nIn the following examples, a `PendingIntent` is created and wrapped as an extra of another `Intent`.\n\nIn the first example, both the `PendingIntent` and the `Intent` it is wrapped in are implicit, making them vulnerable to attack.\n\nIn the second example, the issue is avoided by adding explicit destination components to the `PendingIntent` and the wrapping `Intent`.\n\nThe third example uses the `FLAG_IMMUTABLE` flag to prevent the underlying `Intent` from being modified by the destination component.\n\n\n```java\nimport android.app.Activity;\nimport android.app.PendingIntent;\nimport android.content.Intent;\nimport android.os.Bundle;\n\npublic class ImplicitPendingIntents extends Activity {\n\n\tpublic void onCreate(Bundle savedInstance) {\n\t\t{\n\t\t\t// BAD: an implicit Intent is used to create a PendingIntent.\n\t\t\t// The PendingIntent is then added to another implicit Intent\n\t\t\t// and started.\n\t\t\tIntent baseIntent = new Intent();\n\t\t\tPendingIntent pi =\n\t\t\t\t\tPendingIntent.getActivity(this, 0, baseIntent, PendingIntent.FLAG_ONE_SHOT);\n\t\t\tIntent fwdIntent = new Intent(\"SOME_ACTION\");\n\t\t\tfwdIntent.putExtra(\"fwdIntent\", pi);\n\t\t\tsendBroadcast(fwdIntent);\n\t\t}\n\n\t\t{\n\t\t\t// GOOD: both the PendingIntent and the wrapping Intent are explicit.\n\t\t\tIntent safeIntent = new Intent(this, AnotherActivity.class);\n\t\t\tPendingIntent pi =\n\t\t\t\t\tPendingIntent.getActivity(this, 0, safeIntent, PendingIntent.FLAG_ONE_SHOT);\n\t\t\tIntent fwdIntent = new Intent();\n\t\t\tfwdIntent.setClassName(\"destination.package\", \"DestinationClass\");\n\t\t\tfwdIntent.putExtra(\"fwdIntent\", pi);\n\t\t\tstartActivity(fwdIntent);\n\t\t}\n\n\t\t{\n\t\t\t// GOOD: The PendingIntent is created with FLAG_IMMUTABLE.\n\t\t\tIntent baseIntent = new Intent(\"SOME_ACTION\");\n\t\t\tPendingIntent pi =\n\t\t\t\t\tPendingIntent.getActivity(this, 0, baseIntent, PendingIntent.FLAG_IMMUTABLE);\n\t\t\tIntent fwdIntent = new Intent();\n\t\t\tfwdIntent.setClassName(\"destination.package\", \"DestinationClass\");\n\t\t\tfwdIntent.putExtra(\"fwdIntent\", pi);\n\t\t\tstartActivity(fwdIntent);\n\t\t}\n\t}\n}\n\n```\n\n## References\n* Google Help: [ Remediation for Implicit PendingIntent Vulnerability ](https://support.google.com/faqs/answer/10437428?hl=en)\n* University of Potsdam: [ PIAnalyzer: A precise approach for PendingIntent vulnerability analysis ](https://www.cs.uni-potsdam.de/se/papers/esorics18.pdf)\n* Common Weakness Enumeration: [CWE-927](https://cwe.mitre.org/data/definitions/927.html).\n" + }, + "id": "java/android/implicit-pendingintents", + "name": "java/android/implicit-pendingintents", + "properties": { + "security-severity": "8.200000", + "tags": [ + "external/cwe/cwe-927", + "security" + ] + }, + "shortDescription": { + "text": "Use of implicit PendingIntents" + } + }, + { + "defaultConfiguration": { + "level": "error" + }, + "fullDescription": { + "text": "Starting Android components with user-provided Intents can provide access to internal components of the application, increasing the attack surface and potentially causing unintended effects." + }, + "help": { + "markdown": "# Android Intent redirection\nAn exported Android component that obtains a user-provided Intent and uses it to launch another component can be exploited to obtain access to private, unexported components of the same app or to launch other apps' components on behalf of the victim app.\n\n\n## Recommendation\nDo not export components that start other components from a user-provided Intent. They can be made private by setting the `android:exported` property to `false` in the app's Android Manifest.\n\nIf this is not possible, restrict either which apps can send Intents to the affected component, or which components can be started from it.\n\n\n## Example\nThe following snippet contains three examples. In the first example, an arbitrary component can be started from the externally provided `forward_intent` Intent. In the second example, the destination component of the Intent is first checked to make sure it is safe. In the third example, the component that created the Intent is first checked to make sure it comes from a trusted origin.\n\n\n```java\n// BAD: A user-provided Intent is used to launch an arbitrary component\nIntent forwardIntent = (Intent) getIntent().getParcelableExtra(\"forward_intent\");\nstartActivity(forwardIntent);\n\n// GOOD: The destination component is checked before launching it\nIntent forwardIntent = (Intent) getIntent().getParcelableExtra(\"forward_intent\");\nComponentName destinationComponent = forwardIntent.resolveActivity(getPackageManager());\nif (destinationComponent.getPackageName().equals(\"safe.package\") && \n destinationComponent.getClassName().equals(\"SafeClass\")) {\n startActivity(forwardIntent);\n}\n\n// GOOD: The component that sent the Intent is checked before launching the destination component\nIntent forwardIntent = (Intent) getIntent().getParcelableExtra(\"forward_intent\");\nComponentName originComponent = getCallingActivity();\nif (originComponent.getPackageName().equals(\"trusted.package\") && originComponent.getClassName().equals(\"TrustedClass\")) {\n startActivity(forwardIntent);\n}\n\n```\n\n## References\n* Google: [Remediation for Intent Redirection Vulnerability](https://support.google.com/faqs/answer/9267555?hl=en).\n* OWASP Mobile Security Testing Guide: [Intents](https://mobile-security.gitbook.io/mobile-security-testing-guide/android-testing-guide/0x05a-platform-overview#intents).\n* Android Developers: [The android:exported attribute](https://developer.android.com/guide/topics/manifest/activity-element#exported).\n* Common Weakness Enumeration: [CWE-926](https://cwe.mitre.org/data/definitions/926.html).\n* Common Weakness Enumeration: [CWE-940](https://cwe.mitre.org/data/definitions/940.html).\n", + "text": "# Android Intent redirection\nAn exported Android component that obtains a user-provided Intent and uses it to launch another component can be exploited to obtain access to private, unexported components of the same app or to launch other apps' components on behalf of the victim app.\n\n\n## Recommendation\nDo not export components that start other components from a user-provided Intent. They can be made private by setting the `android:exported` property to `false` in the app's Android Manifest.\n\nIf this is not possible, restrict either which apps can send Intents to the affected component, or which components can be started from it.\n\n\n## Example\nThe following snippet contains three examples. In the first example, an arbitrary component can be started from the externally provided `forward_intent` Intent. In the second example, the destination component of the Intent is first checked to make sure it is safe. In the third example, the component that created the Intent is first checked to make sure it comes from a trusted origin.\n\n\n```java\n// BAD: A user-provided Intent is used to launch an arbitrary component\nIntent forwardIntent = (Intent) getIntent().getParcelableExtra(\"forward_intent\");\nstartActivity(forwardIntent);\n\n// GOOD: The destination component is checked before launching it\nIntent forwardIntent = (Intent) getIntent().getParcelableExtra(\"forward_intent\");\nComponentName destinationComponent = forwardIntent.resolveActivity(getPackageManager());\nif (destinationComponent.getPackageName().equals(\"safe.package\") && \n destinationComponent.getClassName().equals(\"SafeClass\")) {\n startActivity(forwardIntent);\n}\n\n// GOOD: The component that sent the Intent is checked before launching the destination component\nIntent forwardIntent = (Intent) getIntent().getParcelableExtra(\"forward_intent\");\nComponentName originComponent = getCallingActivity();\nif (originComponent.getPackageName().equals(\"trusted.package\") && originComponent.getClassName().equals(\"TrustedClass\")) {\n startActivity(forwardIntent);\n}\n\n```\n\n## References\n* Google: [Remediation for Intent Redirection Vulnerability](https://support.google.com/faqs/answer/9267555?hl=en).\n* OWASP Mobile Security Testing Guide: [Intents](https://mobile-security.gitbook.io/mobile-security-testing-guide/android-testing-guide/0x05a-platform-overview#intents).\n* Android Developers: [The android:exported attribute](https://developer.android.com/guide/topics/manifest/activity-element#exported).\n* Common Weakness Enumeration: [CWE-926](https://cwe.mitre.org/data/definitions/926.html).\n* Common Weakness Enumeration: [CWE-940](https://cwe.mitre.org/data/definitions/940.html).\n" + }, + "id": "java/android/intent-redirection", + "name": "java/android/intent-redirection", + "properties": { + "security-severity": "7.500000", + "tags": [ + "external/cwe/cwe-926", + "external/cwe/cwe-940", + "security" + ] + }, + "shortDescription": { + "text": "Android Intent redirection" + } + }, + { + "defaultConfiguration": { + "level": "error" + }, + "fullDescription": { + "text": "Returning an externally provided Intent via 'setResult' may allow a malicious application to access arbitrary content providers of the vulnerable application." + }, + "help": { + "markdown": "# Intent URI permission manipulation\nWhen an Android component expects a result from an Activity, `startActivityForResult` can be used. The started Activity can then use `setResult` to return the appropriate data to the calling component.\n\nIf an Activity obtains the incoming, user-provided Intent and directly returns it via `setResult` without any checks, the application may be unintentionally giving arbitrary access to its content providers, even if they are not exported, as long as they are configured with the attribute `android:grantUriPermissions=\"true\"`. This happens because the attacker adds the appropriate URI permission flags to the provided Intent, which take effect once the Intent is reflected back.\n\n\n## Recommendation\nAvoid returning user-provided or untrusted Intents via `setResult`. Use a new Intent instead.\n\nIf it is required to use the received Intent, make sure that it does not contain URI permission flags, either by checking them with `Intent.getFlags` or removing them with `Intent.removeFlags`.\n\n\n## Example\nThe following sample contains three examples. In the first example, a user-provided Intent is obtained and directly returned back with `setResult`, which is dangerous. In the second example, a new Intent is created to safely return the desired data. The third example shows how the obtained Intent can be sanitized by removing dangerous flags before using it to return data to the calling component.\n\n\n```java\npublic class IntentUriPermissionManipulation extends Activity {\n\n // BAD: the user-provided Intent is returned as-is\n public void dangerous() {\n Intent intent = getIntent();\n intent.putExtra(\"result\", \"resultData\");\n setResult(intent);\n }\n\n // GOOD: a new Intent is created and returned\n public void safe() {\n Intent intent = new Intent();\n intent.putExtra(\"result\", \"resultData\");\n setResult(intent);\n }\n\n // GOOD: the user-provided Intent is sanitized before being returned\n public void sanitized() {\n Intent intent = getIntent();\n intent.putExtra(\"result\", \"resultData\");\n intent.removeFlags(\n Intent.FLAG_GRANT_WRITE_URI_PERMISSION | Intent.FLAG_GRANT_READ_URI_PERMISSION);\n setResult(intent);\n }\n}\n\n```\n\n## References\n* Google Help: [Remediation for Intent Redirection Vulnerability](https://support.google.com/faqs/answer/9267555?hl=en).\n* Common Weakness Enumeration: [CWE-266](https://cwe.mitre.org/data/definitions/266.html).\n* Common Weakness Enumeration: [CWE-926](https://cwe.mitre.org/data/definitions/926.html).\n", + "text": "# Intent URI permission manipulation\nWhen an Android component expects a result from an Activity, `startActivityForResult` can be used. The started Activity can then use `setResult` to return the appropriate data to the calling component.\n\nIf an Activity obtains the incoming, user-provided Intent and directly returns it via `setResult` without any checks, the application may be unintentionally giving arbitrary access to its content providers, even if they are not exported, as long as they are configured with the attribute `android:grantUriPermissions=\"true\"`. This happens because the attacker adds the appropriate URI permission flags to the provided Intent, which take effect once the Intent is reflected back.\n\n\n## Recommendation\nAvoid returning user-provided or untrusted Intents via `setResult`. Use a new Intent instead.\n\nIf it is required to use the received Intent, make sure that it does not contain URI permission flags, either by checking them with `Intent.getFlags` or removing them with `Intent.removeFlags`.\n\n\n## Example\nThe following sample contains three examples. In the first example, a user-provided Intent is obtained and directly returned back with `setResult`, which is dangerous. In the second example, a new Intent is created to safely return the desired data. The third example shows how the obtained Intent can be sanitized by removing dangerous flags before using it to return data to the calling component.\n\n\n```java\npublic class IntentUriPermissionManipulation extends Activity {\n\n // BAD: the user-provided Intent is returned as-is\n public void dangerous() {\n Intent intent = getIntent();\n intent.putExtra(\"result\", \"resultData\");\n setResult(intent);\n }\n\n // GOOD: a new Intent is created and returned\n public void safe() {\n Intent intent = new Intent();\n intent.putExtra(\"result\", \"resultData\");\n setResult(intent);\n }\n\n // GOOD: the user-provided Intent is sanitized before being returned\n public void sanitized() {\n Intent intent = getIntent();\n intent.putExtra(\"result\", \"resultData\");\n intent.removeFlags(\n Intent.FLAG_GRANT_WRITE_URI_PERMISSION | Intent.FLAG_GRANT_READ_URI_PERMISSION);\n setResult(intent);\n }\n}\n\n```\n\n## References\n* Google Help: [Remediation for Intent Redirection Vulnerability](https://support.google.com/faqs/answer/9267555?hl=en).\n* Common Weakness Enumeration: [CWE-266](https://cwe.mitre.org/data/definitions/266.html).\n* Common Weakness Enumeration: [CWE-926](https://cwe.mitre.org/data/definitions/926.html).\n" + }, + "id": "java/android/intent-uri-permission-manipulation", + "name": "java/android/intent-uri-permission-manipulation", + "properties": { + "security-severity": "7.800000", + "tags": [ + "external/cwe/cwe-266", + "external/cwe/cwe-926", + "security" + ] + }, + "shortDescription": { + "text": "Intent URI permission manipulation" + } + }, + { + "defaultConfiguration": { + "level": "error" + }, + "fullDescription": { + "text": "Storing sensitive information in cleartext can expose it to an attacker." + }, + "help": { + "markdown": "# Cleartext storage of sensitive information in cookie\nSensitive information that is stored unencrypted is accessible to an attacker who gains access to the storage.\n\n\n## Recommendation\nEnsure that sensitive information is always encrypted before being stored. It may be wise to encrypt information before it is put into a heap data structure (such as `Java.util.Properties`) that may be written to disk later. Objects that are serializable or marshallable should also always contain encrypted information unless you are certain that they are not ever going to be serialized.\n\nIn general, decrypt sensitive information only at the point where it is necessary for it to be used in cleartext.\n\n\n## Example\nThe following example shows two ways of storing user credentials in a cookie. In the 'BAD' case, the credentials are simply stored in cleartext. In the 'GOOD' case, the credentials are hashed before storing them.\n\n\n```java\npublic static void main(String[] args) {\n\t{\n\t\tString data;\n\t\tPasswordAuthentication credentials =\n\t\t\t\tnew PasswordAuthentication(\"user\", \"BP@ssw0rd\".toCharArray());\n\t\tdata = credentials.getUserName() + \":\" + new String(credentials.getPassword());\n\t\n\t\t// BAD: store data in a cookie in cleartext form\n\t\tresponse.addCookie(new Cookie(\"auth\", data));\n\t}\n\t\n\t{\n\t\tString data;\n\t\tPasswordAuthentication credentials =\n\t\t\t\tnew PasswordAuthentication(\"user\", \"GP@ssw0rd\".toCharArray());\n\t\tString salt = \"ThisIsMySalt\";\n\t\tMessageDigest messageDigest = MessageDigest.getInstance(\"SHA-512\");\n\t\tmessageDigest.reset();\n\t\tString credentialsToHash =\n\t\t\t\tcredentials.getUserName() + \":\" + credentials.getPassword();\n\t\tbyte[] hashedCredsAsBytes =\n\t\t\t\tmessageDigest.digest((salt+credentialsToHash).getBytes(\"UTF-8\"));\n\t\tdata = bytesToString(hashedCredsAsBytes);\n\t\t\n\t\t// GOOD: store data in a cookie in encrypted form\n\t\tresponse.addCookie(new Cookie(\"auth\", data));\n\t}\n}\n\n```\n\n## References\n* SEI CERT Oracle Coding Standard for Java: [SER03-J. Do not serialize unencrypted, sensitive data](https://wiki.sei.cmu.edu/confluence/display/java/SER03-J.+Do+not+serialize+unencrypted+sensitive+data).\n* M. Dowd, J. McDonald and J. Schuhm, *The Art of Software Security Assessment*, 1st Edition, Chapter 2 - 'Common Vulnerabilities of Encryption', p. 43. Addison Wesley, 2006.\n* M. Howard and D. LeBlanc, *Writing Secure Code*, 2nd Edition, Chapter 9 - 'Protecting Secret Data', p. 299. Microsoft, 2002.\n* Common Weakness Enumeration: [CWE-315](https://cwe.mitre.org/data/definitions/315.html).\n", + "text": "# Cleartext storage of sensitive information in cookie\nSensitive information that is stored unencrypted is accessible to an attacker who gains access to the storage.\n\n\n## Recommendation\nEnsure that sensitive information is always encrypted before being stored. It may be wise to encrypt information before it is put into a heap data structure (such as `Java.util.Properties`) that may be written to disk later. Objects that are serializable or marshallable should also always contain encrypted information unless you are certain that they are not ever going to be serialized.\n\nIn general, decrypt sensitive information only at the point where it is necessary for it to be used in cleartext.\n\n\n## Example\nThe following example shows two ways of storing user credentials in a cookie. In the 'BAD' case, the credentials are simply stored in cleartext. In the 'GOOD' case, the credentials are hashed before storing them.\n\n\n```java\npublic static void main(String[] args) {\n\t{\n\t\tString data;\n\t\tPasswordAuthentication credentials =\n\t\t\t\tnew PasswordAuthentication(\"user\", \"BP@ssw0rd\".toCharArray());\n\t\tdata = credentials.getUserName() + \":\" + new String(credentials.getPassword());\n\t\n\t\t// BAD: store data in a cookie in cleartext form\n\t\tresponse.addCookie(new Cookie(\"auth\", data));\n\t}\n\t\n\t{\n\t\tString data;\n\t\tPasswordAuthentication credentials =\n\t\t\t\tnew PasswordAuthentication(\"user\", \"GP@ssw0rd\".toCharArray());\n\t\tString salt = \"ThisIsMySalt\";\n\t\tMessageDigest messageDigest = MessageDigest.getInstance(\"SHA-512\");\n\t\tmessageDigest.reset();\n\t\tString credentialsToHash =\n\t\t\t\tcredentials.getUserName() + \":\" + credentials.getPassword();\n\t\tbyte[] hashedCredsAsBytes =\n\t\t\t\tmessageDigest.digest((salt+credentialsToHash).getBytes(\"UTF-8\"));\n\t\tdata = bytesToString(hashedCredsAsBytes);\n\t\t\n\t\t// GOOD: store data in a cookie in encrypted form\n\t\tresponse.addCookie(new Cookie(\"auth\", data));\n\t}\n}\n\n```\n\n## References\n* SEI CERT Oracle Coding Standard for Java: [SER03-J. Do not serialize unencrypted, sensitive data](https://wiki.sei.cmu.edu/confluence/display/java/SER03-J.+Do+not+serialize+unencrypted+sensitive+data).\n* M. Dowd, J. McDonald and J. Schuhm, *The Art of Software Security Assessment*, 1st Edition, Chapter 2 - 'Common Vulnerabilities of Encryption', p. 43. Addison Wesley, 2006.\n* M. Howard and D. LeBlanc, *Writing Secure Code*, 2nd Edition, Chapter 9 - 'Protecting Secret Data', p. 299. Microsoft, 2002.\n* Common Weakness Enumeration: [CWE-315](https://cwe.mitre.org/data/definitions/315.html).\n" + }, + "id": "java/cleartext-storage-in-cookie", + "name": "java/cleartext-storage-in-cookie", + "properties": { + "security-severity": "5.000000", + "tags": [ + "external/cwe/cwe-315", + "security" + ] + }, + "shortDescription": { + "text": "Cleartext storage of sensitive information in cookie" + } + }, + { + "defaultConfiguration": { + "level": "error" + }, + "fullDescription": { + "text": "Using externally controlled strings in a command line is vulnerable to malicious changes in the strings." + }, + "help": { + "markdown": "# Uncontrolled command line\nCode that passes user input directly to `Runtime.exec`, or some other library routine that executes a command, allows the user to execute malicious code.\n\n\n## Recommendation\nIf possible, use hard-coded string literals to specify the command to run or library to load. Instead of passing the user input directly to the process or library function, examine the user input and then choose among hard-coded string literals.\n\nIf the applicable libraries or commands cannot be determined at compile time, then add code to verify that the user input string is safe before using it.\n\n\n## Example\nThe following example shows code that takes a shell script that can be changed maliciously by a user, and passes it straight to `Runtime.exec` without examining it first.\n\n\n```java\nclass Test {\n public static void main(String[] args) {\n String script = System.getenv(\"SCRIPTNAME\");\n if (script != null) {\n // BAD: The script to be executed is controlled by the user.\n Runtime.getRuntime().exec(script);\n }\n }\n}\n```\n\n## References\n* OWASP: [Command Injection](https://www.owasp.org/index.php/Command_Injection).\n* SEI CERT Oracle Coding Standard for Java: [IDS07-J. Sanitize untrusted data passed to the Runtime.exec() method](https://wiki.sei.cmu.edu/confluence/display/java/IDS07-J.+Sanitize+untrusted+data+passed+to+the+Runtime.exec()+method).\n* Common Weakness Enumeration: [CWE-78](https://cwe.mitre.org/data/definitions/78.html).\n* Common Weakness Enumeration: [CWE-88](https://cwe.mitre.org/data/definitions/88.html).\n", + "text": "# Uncontrolled command line\nCode that passes user input directly to `Runtime.exec`, or some other library routine that executes a command, allows the user to execute malicious code.\n\n\n## Recommendation\nIf possible, use hard-coded string literals to specify the command to run or library to load. Instead of passing the user input directly to the process or library function, examine the user input and then choose among hard-coded string literals.\n\nIf the applicable libraries or commands cannot be determined at compile time, then add code to verify that the user input string is safe before using it.\n\n\n## Example\nThe following example shows code that takes a shell script that can be changed maliciously by a user, and passes it straight to `Runtime.exec` without examining it first.\n\n\n```java\nclass Test {\n public static void main(String[] args) {\n String script = System.getenv(\"SCRIPTNAME\");\n if (script != null) {\n // BAD: The script to be executed is controlled by the user.\n Runtime.getRuntime().exec(script);\n }\n }\n}\n```\n\n## References\n* OWASP: [Command Injection](https://www.owasp.org/index.php/Command_Injection).\n* SEI CERT Oracle Coding Standard for Java: [IDS07-J. Sanitize untrusted data passed to the Runtime.exec() method](https://wiki.sei.cmu.edu/confluence/display/java/IDS07-J.+Sanitize+untrusted+data+passed+to+the+Runtime.exec()+method).\n* Common Weakness Enumeration: [CWE-78](https://cwe.mitre.org/data/definitions/78.html).\n* Common Weakness Enumeration: [CWE-88](https://cwe.mitre.org/data/definitions/88.html).\n" + }, + "id": "java/command-line-injection", + "name": "java/command-line-injection", + "properties": { + "security-severity": "9.800000", + "tags": [ + "external/cwe/cwe-078", + "external/cwe/cwe-088", + "security" + ] + }, + "shortDescription": { + "text": "Uncontrolled command line" + } + }, + { + "defaultConfiguration": { + "level": "error" + }, + "fullDescription": { + "text": "Using concatenated strings in a command line is vulnerable to malicious insertion of special characters in the strings." + }, + "help": { + "markdown": "# Building a command line with string concatenation\nCode that builds a command line by concatenating strings that have been entered by a user allows the user to execute malicious code.\n\n\n## Recommendation\nExecute external commands using an array of strings rather than a single string. By using an array, many possible vulnerabilities in the formatting of the string are avoided.\n\n\n## Example\nIn the following example, `latlonCoords` contains a string that has been entered by a user but not validated by the program. This allows the user to, for example, append an ampersand (&) followed by the command for a malicious program to the end of the string. The ampersand instructs Windows to execute another program. In the block marked 'BAD', `latlonCoords` is passed to `exec` as part of a concatenated string, which allows more than one command to be executed. However, in the block marked 'GOOD', `latlonCoords` is passed as part of an array, which means that `exec` treats it only as an argument.\n\n\n```java\nclass Test {\n public static void main(String[] args) {\n // BAD: user input might include special characters such as ampersands\n {\n String latlonCoords = args[1];\n Runtime rt = Runtime.getRuntime();\n Process exec = rt.exec(\"cmd.exe /C latlon2utm.exe \" + latlonCoords);\n }\n\n // GOOD: use an array of arguments instead of executing a string\n {\n String latlonCoords = args[1];\n Runtime rt = Runtime.getRuntime();\n Process exec = rt.exec(new String[] {\n \"c:\\\\path\\to\\latlon2utm.exe\",\n latlonCoords });\n }\n }\n}\n\n```\n\n## References\n* OWASP: [Command Injection](https://www.owasp.org/index.php/Command_Injection).\n* SEI CERT Oracle Coding Standard for Java: [IDS07-J. Sanitize untrusted data passed to the Runtime.exec() method](https://wiki.sei.cmu.edu/confluence/display/java/IDS07-J.+Sanitize+untrusted+data+passed+to+the+Runtime.exec()+method).\n* Common Weakness Enumeration: [CWE-78](https://cwe.mitre.org/data/definitions/78.html).\n* Common Weakness Enumeration: [CWE-88](https://cwe.mitre.org/data/definitions/88.html).\n", + "text": "# Building a command line with string concatenation\nCode that builds a command line by concatenating strings that have been entered by a user allows the user to execute malicious code.\n\n\n## Recommendation\nExecute external commands using an array of strings rather than a single string. By using an array, many possible vulnerabilities in the formatting of the string are avoided.\n\n\n## Example\nIn the following example, `latlonCoords` contains a string that has been entered by a user but not validated by the program. This allows the user to, for example, append an ampersand (&) followed by the command for a malicious program to the end of the string. The ampersand instructs Windows to execute another program. In the block marked 'BAD', `latlonCoords` is passed to `exec` as part of a concatenated string, which allows more than one command to be executed. However, in the block marked 'GOOD', `latlonCoords` is passed as part of an array, which means that `exec` treats it only as an argument.\n\n\n```java\nclass Test {\n public static void main(String[] args) {\n // BAD: user input might include special characters such as ampersands\n {\n String latlonCoords = args[1];\n Runtime rt = Runtime.getRuntime();\n Process exec = rt.exec(\"cmd.exe /C latlon2utm.exe \" + latlonCoords);\n }\n\n // GOOD: use an array of arguments instead of executing a string\n {\n String latlonCoords = args[1];\n Runtime rt = Runtime.getRuntime();\n Process exec = rt.exec(new String[] {\n \"c:\\\\path\\to\\latlon2utm.exe\",\n latlonCoords });\n }\n }\n}\n\n```\n\n## References\n* OWASP: [Command Injection](https://www.owasp.org/index.php/Command_Injection).\n* SEI CERT Oracle Coding Standard for Java: [IDS07-J. Sanitize untrusted data passed to the Runtime.exec() method](https://wiki.sei.cmu.edu/confluence/display/java/IDS07-J.+Sanitize+untrusted+data+passed+to+the+Runtime.exec()+method).\n* Common Weakness Enumeration: [CWE-78](https://cwe.mitre.org/data/definitions/78.html).\n* Common Weakness Enumeration: [CWE-88](https://cwe.mitre.org/data/definitions/88.html).\n" + }, + "id": "java/concatenated-command-line", + "name": "java/concatenated-command-line", + "properties": { + "security-severity": "9.800000", + "tags": [ + "external/cwe/cwe-078", + "external/cwe/cwe-088", + "security" + ] + }, + "shortDescription": { + "text": "Building a command line with string concatenation" + } + }, + { + "defaultConfiguration": { + "level": "error" + }, + "fullDescription": { + "text": "Evaluation of a user-controlled Groovy script may lead to arbitrary code execution." + }, + "help": { + "markdown": "# Groovy Language injection\nApache Groovy is a powerful, optionally typed and dynamic language, with static-typing and static compilation capabilities. It integrates smoothly with any Java program, and immediately delivers to your application powerful features, including scripting capabilities, Domain-Specific Language authoring, runtime and compile-time meta-programming and functional programming. If a Groovy script is built using attacker-controlled data, and then evaluated, then it may allow the attacker to achieve RCE.\n\n\n## Recommendation\nIt is generally recommended to avoid using untrusted input in a Groovy evaluation. If this is not possible, use a sandbox solution. Developers must also take care that Groovy compile-time metaprogramming can also lead to RCE: it is possible to achieve RCE by compiling a Groovy script (see the article \"Abusing Meta Programming for Unauthenticated RCE!\" linked below). Groovy's `SecureASTCustomizer` allows securing source code by controlling what code constructs are permitted. This is typically done when using Groovy for its scripting or domain specific language (DSL) features. The fundamental problem is that Groovy is a dynamic language, yet `SecureASTCustomizer` works by looking at Groovy AST statically. This makes it very easy for an attacker to bypass many of the intended checks (see \\[Groovy SecureASTCustomizer is harmful\\](https://kohsuke.org/2012/04/27/groovy-secureastcustomizer-is-harmful/)). Therefore, besides `SecureASTCustomizer`, runtime checks are also necessary before calling Groovy methods (see \\[Improved sandboxing of Groovy scripts\\](https://melix.github.io/blog/2015/03/sandboxing.html)). It is also possible to use a block-list method, excluding unwanted classes from being loaded by the JVM. This method is not always recommended, because block-lists can be bypassed by unexpected values.\n\n\n## Example\nThe following example uses untrusted data to evaluate a Groovy script.\n\n\n```java\npublic class GroovyInjection {\n void injectionViaClassLoader(HttpServletRequest request) { \n String script = request.getParameter(\"script\");\n final GroovyClassLoader classLoader = new GroovyClassLoader();\n Class groovy = classLoader.parseClass(script);\n GroovyObject groovyObj = (GroovyObject) groovy.newInstance();\n }\n\n void injectionViaEval(HttpServletRequest request) {\n String script = request.getParameter(\"script\");\n Eval.me(script);\n }\n\n void injectionViaGroovyShell(HttpServletRequest request) {\n GroovyShell shell = new GroovyShell();\n String script = request.getParameter(\"script\");\n shell.evaluate(script);\n }\n\n void injectionViaGroovyShellGroovyCodeSource(HttpServletRequest request) {\n GroovyShell shell = new GroovyShell();\n String script = request.getParameter(\"script\");\n GroovyCodeSource gcs = new GroovyCodeSource(script, \"test\", \"Test\");\n shell.evaluate(gcs);\n }\n}\n\n\n```\nThe following example uses classloader block-list approach to exclude loading dangerous classes.\n\n\n```java\npublic class SandboxGroovyClassLoader extends ClassLoader {\n public SandboxGroovyClassLoader(ClassLoader parent) {\n super(parent);\n }\n\n /* override `loadClass` here to prevent loading sensitive classes, such as `java.lang.Runtime`, `java.lang.ProcessBuilder`, `java.lang.System`, etc. */\n /* Note we must also block `groovy.transform.ASTTest`, `groovy.lang.GrabConfig` and `org.buildobjects.process.ProcBuilder` to prevent compile-time RCE. */\n\n static void runWithSandboxGroovyClassLoader() throws Exception {\n // GOOD: route all class-loading via sand-boxing classloader.\n SandboxGroovyClassLoader classLoader = new GroovyClassLoader(new SandboxGroovyClassLoader());\n \n Class scriptClass = classLoader.parseClass(untrusted.getQueryString());\n Object scriptInstance = scriptClass.newInstance();\n Object result = scriptClass.getDeclaredMethod(\"bar\", new Class[]{}).invoke(scriptInstance, new Object[]{});\n }\n}\n```\n\n## References\n* Orange Tsai: [Abusing Meta Programming for Unauthenticated RCE!](https://blog.orange.tw/2019/02/abusing-meta-programming-for-unauthenticated-rce.html).\n* Cédric Champeau: [Improved sandboxing of Groovy scripts](https://melix.github.io/blog/2015/03/sandboxing.html).\n* Kohsuke Kawaguchi: [Groovy SecureASTCustomizer is harmful](https://kohsuke.org/2012/04/27/groovy-secureastcustomizer-is-harmful/).\n* Welk1n: [Groovy Injection payloads](https://github.com/welk1n/exploiting-groovy-in-Java/).\n* Charles Chan: [Secure Groovy Script Execution in a Sandbox](https://levelup.gitconnected.com/secure-groovy-script-execution-in-a-sandbox-ea39f80ee87/).\n* Eugene: [Scripting and sandboxing in a JVM environment](https://stringconcat.com/en/scripting-and-sandboxing/).\n* Common Weakness Enumeration: [CWE-94](https://cwe.mitre.org/data/definitions/94.html).\n", + "text": "# Groovy Language injection\nApache Groovy is a powerful, optionally typed and dynamic language, with static-typing and static compilation capabilities. It integrates smoothly with any Java program, and immediately delivers to your application powerful features, including scripting capabilities, Domain-Specific Language authoring, runtime and compile-time meta-programming and functional programming. If a Groovy script is built using attacker-controlled data, and then evaluated, then it may allow the attacker to achieve RCE.\n\n\n## Recommendation\nIt is generally recommended to avoid using untrusted input in a Groovy evaluation. If this is not possible, use a sandbox solution. Developers must also take care that Groovy compile-time metaprogramming can also lead to RCE: it is possible to achieve RCE by compiling a Groovy script (see the article \"Abusing Meta Programming for Unauthenticated RCE!\" linked below). Groovy's `SecureASTCustomizer` allows securing source code by controlling what code constructs are permitted. This is typically done when using Groovy for its scripting or domain specific language (DSL) features. The fundamental problem is that Groovy is a dynamic language, yet `SecureASTCustomizer` works by looking at Groovy AST statically. This makes it very easy for an attacker to bypass many of the intended checks (see \\[Groovy SecureASTCustomizer is harmful\\](https://kohsuke.org/2012/04/27/groovy-secureastcustomizer-is-harmful/)). Therefore, besides `SecureASTCustomizer`, runtime checks are also necessary before calling Groovy methods (see \\[Improved sandboxing of Groovy scripts\\](https://melix.github.io/blog/2015/03/sandboxing.html)). It is also possible to use a block-list method, excluding unwanted classes from being loaded by the JVM. This method is not always recommended, because block-lists can be bypassed by unexpected values.\n\n\n## Example\nThe following example uses untrusted data to evaluate a Groovy script.\n\n\n```java\npublic class GroovyInjection {\n void injectionViaClassLoader(HttpServletRequest request) { \n String script = request.getParameter(\"script\");\n final GroovyClassLoader classLoader = new GroovyClassLoader();\n Class groovy = classLoader.parseClass(script);\n GroovyObject groovyObj = (GroovyObject) groovy.newInstance();\n }\n\n void injectionViaEval(HttpServletRequest request) {\n String script = request.getParameter(\"script\");\n Eval.me(script);\n }\n\n void injectionViaGroovyShell(HttpServletRequest request) {\n GroovyShell shell = new GroovyShell();\n String script = request.getParameter(\"script\");\n shell.evaluate(script);\n }\n\n void injectionViaGroovyShellGroovyCodeSource(HttpServletRequest request) {\n GroovyShell shell = new GroovyShell();\n String script = request.getParameter(\"script\");\n GroovyCodeSource gcs = new GroovyCodeSource(script, \"test\", \"Test\");\n shell.evaluate(gcs);\n }\n}\n\n\n```\nThe following example uses classloader block-list approach to exclude loading dangerous classes.\n\n\n```java\npublic class SandboxGroovyClassLoader extends ClassLoader {\n public SandboxGroovyClassLoader(ClassLoader parent) {\n super(parent);\n }\n\n /* override `loadClass` here to prevent loading sensitive classes, such as `java.lang.Runtime`, `java.lang.ProcessBuilder`, `java.lang.System`, etc. */\n /* Note we must also block `groovy.transform.ASTTest`, `groovy.lang.GrabConfig` and `org.buildobjects.process.ProcBuilder` to prevent compile-time RCE. */\n\n static void runWithSandboxGroovyClassLoader() throws Exception {\n // GOOD: route all class-loading via sand-boxing classloader.\n SandboxGroovyClassLoader classLoader = new GroovyClassLoader(new SandboxGroovyClassLoader());\n \n Class scriptClass = classLoader.parseClass(untrusted.getQueryString());\n Object scriptInstance = scriptClass.newInstance();\n Object result = scriptClass.getDeclaredMethod(\"bar\", new Class[]{}).invoke(scriptInstance, new Object[]{});\n }\n}\n```\n\n## References\n* Orange Tsai: [Abusing Meta Programming for Unauthenticated RCE!](https://blog.orange.tw/2019/02/abusing-meta-programming-for-unauthenticated-rce.html).\n* Cédric Champeau: [Improved sandboxing of Groovy scripts](https://melix.github.io/blog/2015/03/sandboxing.html).\n* Kohsuke Kawaguchi: [Groovy SecureASTCustomizer is harmful](https://kohsuke.org/2012/04/27/groovy-secureastcustomizer-is-harmful/).\n* Welk1n: [Groovy Injection payloads](https://github.com/welk1n/exploiting-groovy-in-Java/).\n* Charles Chan: [Secure Groovy Script Execution in a Sandbox](https://levelup.gitconnected.com/secure-groovy-script-execution-in-a-sandbox-ea39f80ee87/).\n* Eugene: [Scripting and sandboxing in a JVM environment](https://stringconcat.com/en/scripting-and-sandboxing/).\n* Common Weakness Enumeration: [CWE-94](https://cwe.mitre.org/data/definitions/94.html).\n" + }, + "id": "java/groovy-injection", + "name": "java/groovy-injection", + "properties": { + "tags": [ + "external/cwe/cwe-094", + "security" + ] + }, + "shortDescription": { + "text": "Groovy Language injection" + } + }, + { + "defaultConfiguration": { + "level": "error" + }, + "fullDescription": { + "text": "Writing user input directly to an HTTP header makes code vulnerable to attack by header splitting." + }, + "help": { + "markdown": "# HTTP response splitting\nDirectly writing user input (for example, an HTTP request parameter) to an HTTP header can lead to an HTTP response-splitting vulnerability. If the user input includes blank lines in it, and if the servlet container does not itself escape the blank lines, then a remote user can cause the response to turn into two separate responses, one of which is controlled by the remote user.\n\n\n## Recommendation\nGuard against HTTP header splitting in the same way as guarding against cross-site scripting. Before passing any data into HTTP headers, either check the data for special characters, or escape any special characters that are present.\n\n\n## Example\nThe following example shows the 'name' parameter being written to a cookie in two different ways. The first way writes it directly to the cookie, and thus is vulnerable to response-splitting attacks. The second way first removes all special characters, thus avoiding the potential problem.\n\n\n```java\npublic class ResponseSplitting extends HttpServlet {\n\tprotected void doGet(HttpServletRequest request, HttpServletResponse response)\n\tthrows ServletException, IOException {\n\t\t// BAD: setting a cookie with an unvalidated parameter\n\t\tCookie cookie = new Cookie(\"name\", request.getParameter(\"name\"));\n\t\tresponse.addCookie(cookie);\n\n\t\t// GOOD: remove special characters before putting them in the header\n\t\tString name = removeSpecial(request.getParameter(\"name\"));\n\t\tCookie cookie2 = new Cookie(\"name\", name);\n\t\tresponse.addCookie(cookie2);\n\t}\n\n\tprivate static String removeSpecial(String str) {\n\t\treturn str.replaceAll(\"[^a-zA-Z ]\", \"\");\n\t}\n}\n\n```\n\n## Example\nThe following example shows the use of the library 'netty' with HTTP response-splitting verification configurations. The second way will verify the parameters before using them to build the HTTP response.\n\n\n```java\nimport io.netty.handler.codec.http.DefaultHttpHeaders;\n\npublic class ResponseSplitting {\n // BAD: Disables the internal response splitting verification\n private final DefaultHttpHeaders badHeaders = new DefaultHttpHeaders(false);\n\n // GOOD: Verifies headers passed don't contain CRLF characters\n private final DefaultHttpHeaders goodHeaders = new DefaultHttpHeaders();\n\n // BAD: Disables the internal response splitting verification\n private final DefaultHttpResponse badResponse = new DefaultHttpResponse(version, httpResponseStatus, false);\n\n // GOOD: Verifies headers passed don't contain CRLF characters\n private final DefaultHttpResponse goodResponse = new DefaultHttpResponse(version, httpResponseStatus);\n}\n\n```\n\n## References\n* InfosecWriters: [HTTP response splitting](http://www.infosecwriters.com/Papers/DCrab_HTTP_Response.pdf).\n* OWASP: [HTTP Response Splitting](https://www.owasp.org/index.php/HTTP_Response_Splitting).\n* Wikipedia: [HTTP response splitting](http://en.wikipedia.org/wiki/HTTP_response_splitting).\n* Common Weakness Enumeration: [CWE-113](https://cwe.mitre.org/data/definitions/113.html).\n", + "text": "# HTTP response splitting\nDirectly writing user input (for example, an HTTP request parameter) to an HTTP header can lead to an HTTP response-splitting vulnerability. If the user input includes blank lines in it, and if the servlet container does not itself escape the blank lines, then a remote user can cause the response to turn into two separate responses, one of which is controlled by the remote user.\n\n\n## Recommendation\nGuard against HTTP header splitting in the same way as guarding against cross-site scripting. Before passing any data into HTTP headers, either check the data for special characters, or escape any special characters that are present.\n\n\n## Example\nThe following example shows the 'name' parameter being written to a cookie in two different ways. The first way writes it directly to the cookie, and thus is vulnerable to response-splitting attacks. The second way first removes all special characters, thus avoiding the potential problem.\n\n\n```java\npublic class ResponseSplitting extends HttpServlet {\n\tprotected void doGet(HttpServletRequest request, HttpServletResponse response)\n\tthrows ServletException, IOException {\n\t\t// BAD: setting a cookie with an unvalidated parameter\n\t\tCookie cookie = new Cookie(\"name\", request.getParameter(\"name\"));\n\t\tresponse.addCookie(cookie);\n\n\t\t// GOOD: remove special characters before putting them in the header\n\t\tString name = removeSpecial(request.getParameter(\"name\"));\n\t\tCookie cookie2 = new Cookie(\"name\", name);\n\t\tresponse.addCookie(cookie2);\n\t}\n\n\tprivate static String removeSpecial(String str) {\n\t\treturn str.replaceAll(\"[^a-zA-Z ]\", \"\");\n\t}\n}\n\n```\n\n## Example\nThe following example shows the use of the library 'netty' with HTTP response-splitting verification configurations. The second way will verify the parameters before using them to build the HTTP response.\n\n\n```java\nimport io.netty.handler.codec.http.DefaultHttpHeaders;\n\npublic class ResponseSplitting {\n // BAD: Disables the internal response splitting verification\n private final DefaultHttpHeaders badHeaders = new DefaultHttpHeaders(false);\n\n // GOOD: Verifies headers passed don't contain CRLF characters\n private final DefaultHttpHeaders goodHeaders = new DefaultHttpHeaders();\n\n // BAD: Disables the internal response splitting verification\n private final DefaultHttpResponse badResponse = new DefaultHttpResponse(version, httpResponseStatus, false);\n\n // GOOD: Verifies headers passed don't contain CRLF characters\n private final DefaultHttpResponse goodResponse = new DefaultHttpResponse(version, httpResponseStatus);\n}\n\n```\n\n## References\n* InfosecWriters: [HTTP response splitting](http://www.infosecwriters.com/Papers/DCrab_HTTP_Response.pdf).\n* OWASP: [HTTP Response Splitting](https://www.owasp.org/index.php/HTTP_Response_Splitting).\n* Wikipedia: [HTTP response splitting](http://en.wikipedia.org/wiki/HTTP_response_splitting).\n* Common Weakness Enumeration: [CWE-113](https://cwe.mitre.org/data/definitions/113.html).\n" + }, + "id": "java/http-response-splitting", + "name": "java/http-response-splitting", + "properties": { + "security-severity": "6.100000", + "tags": [ + "external/cwe/cwe-113", + "security" + ] + }, + "shortDescription": { + "text": "HTTP response splitting" + } + }, + { + "defaultConfiguration": { + "level": "warning" + }, + "fullDescription": { + "text": "Compound assignment statements (for example 'intvar += longvar') that implicitly cast a value of a wider type to a narrower type may result in information loss and numeric errors such as overflows." + }, + "help": { + "markdown": "# Implicit narrowing conversion in compound assignment\nCompound assignment statements of the form `x += y` or `x *= y` perform an implicit narrowing conversion if the type of `x` is narrower than the type of `y`. For example, `x += y` is equivalent to `x = (T)(x + y)`, where `T` is the type of `x`. This can result in information loss and numeric errors such as overflows.\n\n\n## Recommendation\nEnsure that the type of the left-hand side of the compound assignment statement is at least as wide as the type of the right-hand side.\n\n\n## Example\nIf `x` is of type `short` and `y` is of type `int`, the expression `x + y` is of type `int`. However, the expression `x += y` is equivalent to `x = (short) (x + y)`. The expression `x + y` is cast to the type of the left-hand side of the assignment: `short`, possibly leading to information loss.\n\nTo avoid implicitly narrowing the type of `x + y`, change the type of `x` to `int`. Then the types of `x` and `x + y` are both `int` and there is no need for an implicit cast.\n\n\n## References\n* J. Bloch and N. Gafter, *Java Puzzlers: Traps, Pitfalls, and Corner Cases*, Puzzle 9. Addison-Wesley, 2005.\n* Java Language Specification: [Compound Assignment Operators](https://docs.oracle.com/javase/specs/jls/se11/html/jls-15.html#jls-15.26.2), [Narrowing Primitive Conversion](https://docs.oracle.com/javase/specs/jls/se11/html/jls-5.html#jls-5.1.3).\n* SEI CERT Oracle Coding Standard for Java: [NUM00-J. Detect or prevent integer overflow](https://wiki.sei.cmu.edu/confluence/display/java/NUM00-J.+Detect+or+prevent+integer+overflow).\n* Common Weakness Enumeration: [CWE-190](https://cwe.mitre.org/data/definitions/190.html).\n* Common Weakness Enumeration: [CWE-192](https://cwe.mitre.org/data/definitions/192.html).\n* Common Weakness Enumeration: [CWE-197](https://cwe.mitre.org/data/definitions/197.html).\n* Common Weakness Enumeration: [CWE-681](https://cwe.mitre.org/data/definitions/681.html).\n", + "text": "# Implicit narrowing conversion in compound assignment\nCompound assignment statements of the form `x += y` or `x *= y` perform an implicit narrowing conversion if the type of `x` is narrower than the type of `y`. For example, `x += y` is equivalent to `x = (T)(x + y)`, where `T` is the type of `x`. This can result in information loss and numeric errors such as overflows.\n\n\n## Recommendation\nEnsure that the type of the left-hand side of the compound assignment statement is at least as wide as the type of the right-hand side.\n\n\n## Example\nIf `x` is of type `short` and `y` is of type `int`, the expression `x + y` is of type `int`. However, the expression `x += y` is equivalent to `x = (short) (x + y)`. The expression `x + y` is cast to the type of the left-hand side of the assignment: `short`, possibly leading to information loss.\n\nTo avoid implicitly narrowing the type of `x + y`, change the type of `x` to `int`. Then the types of `x` and `x + y` are both `int` and there is no need for an implicit cast.\n\n\n## References\n* J. Bloch and N. Gafter, *Java Puzzlers: Traps, Pitfalls, and Corner Cases*, Puzzle 9. Addison-Wesley, 2005.\n* Java Language Specification: [Compound Assignment Operators](https://docs.oracle.com/javase/specs/jls/se11/html/jls-15.html#jls-15.26.2), [Narrowing Primitive Conversion](https://docs.oracle.com/javase/specs/jls/se11/html/jls-5.html#jls-5.1.3).\n* SEI CERT Oracle Coding Standard for Java: [NUM00-J. Detect or prevent integer overflow](https://wiki.sei.cmu.edu/confluence/display/java/NUM00-J.+Detect+or+prevent+integer+overflow).\n* Common Weakness Enumeration: [CWE-190](https://cwe.mitre.org/data/definitions/190.html).\n* Common Weakness Enumeration: [CWE-192](https://cwe.mitre.org/data/definitions/192.html).\n* Common Weakness Enumeration: [CWE-197](https://cwe.mitre.org/data/definitions/197.html).\n* Common Weakness Enumeration: [CWE-681](https://cwe.mitre.org/data/definitions/681.html).\n" + }, + "id": "java/implicit-cast-in-compound-assignment", + "name": "java/implicit-cast-in-compound-assignment", + "properties": { + "security-severity": "8.100000", + "tags": [ + "external/cwe/cwe-190", + "external/cwe/cwe-192", + "external/cwe/cwe-197", + "external/cwe/cwe-681", + "reliability", + "security" + ] + }, + "shortDescription": { + "text": "Implicit narrowing conversion in compound assignment" + } + }, + { + "defaultConfiguration": { + "level": "error" + }, + "fullDescription": { + "text": "User-controlled data may be evaluated as a Java EL expression, leading to arbitrary code execution." + }, + "help": { + "markdown": "# Insecure Bean Validation\nCustom error messages for constraint validators support different types of interpolation, including [Java EL expressions](https://docs.jboss.org/hibernate/validator/5.1/reference/en-US/html/chapter-message-interpolation.html#section-interpolation-with-message-expressions). Controlling part of the message template being passed to `ConstraintValidatorContext.buildConstraintViolationWithTemplate()` argument can lead to arbitrary Java code execution. Unfortunately, it is common that validated (and therefore, normally untrusted) bean properties flow into the custom error message.\n\n\n## Recommendation\nThere are different approaches to remediate the issue:\n\n* Do not include validated bean properties in the custom error message.\n* Use parameterized messages instead of string concatenation. For example:\n```\nHibernateConstraintValidatorContext context =\n constraintValidatorContext.unwrap(HibernateConstraintValidatorContext.class);\ncontext.addMessageParameter(\"foo\", \"bar\");\ncontext.buildConstraintViolationWithTemplate(\"My violation message contains a parameter {foo}\")\n .addConstraintViolation();\n```\n* Sanitize the validated bean properties to make sure that there are no EL expressions. An example of valid sanitization logic can be found [here](https://github.com/hibernate/hibernate-validator/blob/master/engine/src/main/java/org/hibernate/validator/internal/engine/messageinterpolation/util/InterpolationHelper.java#L17).\n* Disable the EL interpolation and only use `ParameterMessageInterpolator`:\n```\nValidator validator = Validation.byDefaultProvider()\n .configure()\n .messageInterpolator(new ParameterMessageInterpolator())\n .buildValidatorFactory()\n .getValidator();\n```\n* Replace Hibernate Validator with Apache BVal, which in its latest version does not interpolate EL expressions by default. Note that this replacement may not be a simple drop-in replacement.\n\n## Example\nThe following validator could result in arbitrary Java code execution:\n\n\n```java\nimport javax.validation.ConstraintValidator;\nimport javax.validation.ConstraintValidatorContext;\nimport org.hibernate.validator.constraintvalidation.HibernateConstraintValidatorContext;\nimport java.util.regex.Matcher;\nimport java.util.regex.Pattern;\n\npublic class TestValidator implements ConstraintValidator {\n\n public static class InterpolationHelper {\n\n public static final char BEGIN_TERM = '{';\n public static final char END_TERM = '}';\n public static final char EL_DESIGNATOR = '$';\n public static final char ESCAPE_CHARACTER = '\\\\';\n\n private static final Pattern ESCAPE_MESSAGE_PARAMETER_PATTERN = Pattern.compile( \"([\\\\\" + ESCAPE_CHARACTER + BEGIN_TERM + END_TERM + EL_DESIGNATOR + \"])\" );\n\n private InterpolationHelper() {\n }\n\n public static String escapeMessageParameter(String messageParameter) {\n if ( messageParameter == null ) {\n return null;\n }\n return ESCAPE_MESSAGE_PARAMETER_PATTERN.matcher( messageParameter ).replaceAll( Matcher.quoteReplacement( String.valueOf( ESCAPE_CHARACTER ) ) + \"$1\" );\n }\n\n }\n\n @Override\n public boolean isValid(String object, ConstraintValidatorContext constraintContext) {\n String value = object + \" is invalid\";\n\n // Bad: Bean properties (normally user-controlled) are passed directly to `buildConstraintViolationWithTemplate`\n constraintContext.buildConstraintViolationWithTemplate(value).addConstraintViolation().disableDefaultConstraintViolation();\n\n // Good: Bean properties (normally user-controlled) are escaped \n String escaped = InterpolationHelper.escapeMessageParameter(value);\n constraintContext.buildConstraintViolationWithTemplate(escaped).addConstraintViolation().disableDefaultConstraintViolation();\n\n // Good: Bean properties (normally user-controlled) are parameterized\n HibernateConstraintValidatorContext context = constraintContext.unwrap( HibernateConstraintValidatorContext.class );\n context.addMessageParameter( \"prop\", object );\n context.buildConstraintViolationWithTemplate( \"{prop} is invalid\").addConstraintViolation();\n return false;\n }\n\n}\n\n```\n\n## References\n* Hibernate Reference Guide: [ConstraintValidatorContext](https://docs.jboss.org/hibernate/stable/validator/reference/en-US/html_single/#_the_code_constraintvalidatorcontext_code).\n* GitHub Security Lab research: [Bean validation](https://securitylab.github.com/research/bean-validation-RCE).\n* Common Weakness Enumeration: [CWE-94](https://cwe.mitre.org/data/definitions/94.html).\n", + "text": "# Insecure Bean Validation\nCustom error messages for constraint validators support different types of interpolation, including [Java EL expressions](https://docs.jboss.org/hibernate/validator/5.1/reference/en-US/html/chapter-message-interpolation.html#section-interpolation-with-message-expressions). Controlling part of the message template being passed to `ConstraintValidatorContext.buildConstraintViolationWithTemplate()` argument can lead to arbitrary Java code execution. Unfortunately, it is common that validated (and therefore, normally untrusted) bean properties flow into the custom error message.\n\n\n## Recommendation\nThere are different approaches to remediate the issue:\n\n* Do not include validated bean properties in the custom error message.\n* Use parameterized messages instead of string concatenation. For example:\n```\nHibernateConstraintValidatorContext context =\n constraintValidatorContext.unwrap(HibernateConstraintValidatorContext.class);\ncontext.addMessageParameter(\"foo\", \"bar\");\ncontext.buildConstraintViolationWithTemplate(\"My violation message contains a parameter {foo}\")\n .addConstraintViolation();\n```\n* Sanitize the validated bean properties to make sure that there are no EL expressions. An example of valid sanitization logic can be found [here](https://github.com/hibernate/hibernate-validator/blob/master/engine/src/main/java/org/hibernate/validator/internal/engine/messageinterpolation/util/InterpolationHelper.java#L17).\n* Disable the EL interpolation and only use `ParameterMessageInterpolator`:\n```\nValidator validator = Validation.byDefaultProvider()\n .configure()\n .messageInterpolator(new ParameterMessageInterpolator())\n .buildValidatorFactory()\n .getValidator();\n```\n* Replace Hibernate Validator with Apache BVal, which in its latest version does not interpolate EL expressions by default. Note that this replacement may not be a simple drop-in replacement.\n\n## Example\nThe following validator could result in arbitrary Java code execution:\n\n\n```java\nimport javax.validation.ConstraintValidator;\nimport javax.validation.ConstraintValidatorContext;\nimport org.hibernate.validator.constraintvalidation.HibernateConstraintValidatorContext;\nimport java.util.regex.Matcher;\nimport java.util.regex.Pattern;\n\npublic class TestValidator implements ConstraintValidator {\n\n public static class InterpolationHelper {\n\n public static final char BEGIN_TERM = '{';\n public static final char END_TERM = '}';\n public static final char EL_DESIGNATOR = '$';\n public static final char ESCAPE_CHARACTER = '\\\\';\n\n private static final Pattern ESCAPE_MESSAGE_PARAMETER_PATTERN = Pattern.compile( \"([\\\\\" + ESCAPE_CHARACTER + BEGIN_TERM + END_TERM + EL_DESIGNATOR + \"])\" );\n\n private InterpolationHelper() {\n }\n\n public static String escapeMessageParameter(String messageParameter) {\n if ( messageParameter == null ) {\n return null;\n }\n return ESCAPE_MESSAGE_PARAMETER_PATTERN.matcher( messageParameter ).replaceAll( Matcher.quoteReplacement( String.valueOf( ESCAPE_CHARACTER ) ) + \"$1\" );\n }\n\n }\n\n @Override\n public boolean isValid(String object, ConstraintValidatorContext constraintContext) {\n String value = object + \" is invalid\";\n\n // Bad: Bean properties (normally user-controlled) are passed directly to `buildConstraintViolationWithTemplate`\n constraintContext.buildConstraintViolationWithTemplate(value).addConstraintViolation().disableDefaultConstraintViolation();\n\n // Good: Bean properties (normally user-controlled) are escaped \n String escaped = InterpolationHelper.escapeMessageParameter(value);\n constraintContext.buildConstraintViolationWithTemplate(escaped).addConstraintViolation().disableDefaultConstraintViolation();\n\n // Good: Bean properties (normally user-controlled) are parameterized\n HibernateConstraintValidatorContext context = constraintContext.unwrap( HibernateConstraintValidatorContext.class );\n context.addMessageParameter( \"prop\", object );\n context.buildConstraintViolationWithTemplate( \"{prop} is invalid\").addConstraintViolation();\n return false;\n }\n\n}\n\n```\n\n## References\n* Hibernate Reference Guide: [ConstraintValidatorContext](https://docs.jboss.org/hibernate/stable/validator/reference/en-US/html_single/#_the_code_constraintvalidatorcontext_code).\n* GitHub Security Lab research: [Bean validation](https://securitylab.github.com/research/bean-validation-RCE).\n* Common Weakness Enumeration: [CWE-94](https://cwe.mitre.org/data/definitions/94.html).\n" + }, + "id": "java/insecure-bean-validation", + "name": "java/insecure-bean-validation", + "properties": { + "security-severity": "9.300000", + "tags": [ + "external/cwe/cwe-094", + "security" + ] + }, + "shortDescription": { + "text": "Insecure Bean Validation" + } + }, + { + "defaultConfiguration": { + "level": "error" + }, + "fullDescription": { + "text": "Insecure cookies may be sent in cleartext, which makes them vulnerable to interception." + }, + "help": { + "markdown": "# Failure to use secure cookies\nFailing to set the 'secure' flag on a cookie can cause it to be sent in cleartext. This makes it easier for an attacker to intercept.\n\n\n## Recommendation\nAlways use `setSecure` to set the 'secure' flag on a cookie before adding it to an `HttpServletResponse`.\n\n\n## Example\nThis example shows two ways of adding a cookie to an `HttpServletResponse`. The first way leaves out the setting of the 'secure' flag; the second way includes the setting of the flag.\n\n\n```java\npublic static void test(HttpServletRequest request, HttpServletResponse response) {\n\t{\n\t\tCookie cookie = new Cookie(\"secret\", \"fakesecret\");\n\t\t\n\t\t// BAD: 'secure' flag not set\n\t\tresponse.addCookie(cookie);\n\t}\n\n\t{\n\t\tCookie cookie = new Cookie(\"secret\", \"fakesecret\");\n\t\t\n\t\t// GOOD: set 'secure' flag\n\t\tcookie.setSecure(true);\n\t\tresponse.addCookie(cookie);\n\t}\n}\n```\n\n## References\n* SEI CERT Oracle Coding Standard for Java: [SER03-J. Do not serialize unencrypted, sensitive data](https://wiki.sei.cmu.edu/confluence/display/java/SER03-J.+Do+not+serialize+unencrypted+sensitive+data).\n* Java Platform, Enterprise Edition (Java EE) 7, API Specification: [Class Cookie](https://docs.oracle.com/javaee/7/api/javax/servlet/http/Cookie.html).\n* Common Weakness Enumeration: [CWE-614](https://cwe.mitre.org/data/definitions/614.html).\n", + "text": "# Failure to use secure cookies\nFailing to set the 'secure' flag on a cookie can cause it to be sent in cleartext. This makes it easier for an attacker to intercept.\n\n\n## Recommendation\nAlways use `setSecure` to set the 'secure' flag on a cookie before adding it to an `HttpServletResponse`.\n\n\n## Example\nThis example shows two ways of adding a cookie to an `HttpServletResponse`. The first way leaves out the setting of the 'secure' flag; the second way includes the setting of the flag.\n\n\n```java\npublic static void test(HttpServletRequest request, HttpServletResponse response) {\n\t{\n\t\tCookie cookie = new Cookie(\"secret\", \"fakesecret\");\n\t\t\n\t\t// BAD: 'secure' flag not set\n\t\tresponse.addCookie(cookie);\n\t}\n\n\t{\n\t\tCookie cookie = new Cookie(\"secret\", \"fakesecret\");\n\t\t\n\t\t// GOOD: set 'secure' flag\n\t\tcookie.setSecure(true);\n\t\tresponse.addCookie(cookie);\n\t}\n}\n```\n\n## References\n* SEI CERT Oracle Coding Standard for Java: [SER03-J. Do not serialize unencrypted, sensitive data](https://wiki.sei.cmu.edu/confluence/display/java/SER03-J.+Do+not+serialize+unencrypted+sensitive+data).\n* Java Platform, Enterprise Edition (Java EE) 7, API Specification: [Class Cookie](https://docs.oracle.com/javaee/7/api/javax/servlet/http/Cookie.html).\n* Common Weakness Enumeration: [CWE-614](https://cwe.mitre.org/data/definitions/614.html).\n" + }, + "id": "java/insecure-cookie", + "name": "java/insecure-cookie", + "properties": { + "security-severity": "5.000000", + "tags": [ + "external/cwe/cwe-614", + "security" + ] + }, + "shortDescription": { + "text": "Failure to use secure cookies" + } + }, + { + "defaultConfiguration": { + "level": "error" + }, + "fullDescription": { + "text": "Trusting all certificates allows an attacker to perform a machine-in-the-middle attack." + }, + "help": { + "markdown": "# `TrustManager` that accepts all certificates\nIf the `checkServerTrusted` method of a `TrustManager` never throws a `CertificateException`, it trusts every certificate. This allows an attacker to perform a machine-in-the-middle attack against the application, therefore breaking any security Transport Layer Security (TLS) gives.\n\nAn attack might look like this:\n\n1. The vulnerable program connects to `https://example.com`.\n1. The attacker intercepts this connection and presents a valid, self-signed certificate for `https://example.com`.\n1. The vulnerable program calls the `checkServerTrusted` method to check whether it should trust the certificate.\n1. The `checkServerTrusted` method of your `TrustManager` does not throw a `CertificateException`.\n1. The vulnerable program accepts the certificate and proceeds with the connection since your `TrustManager` implicitly trusted it by not throwing an exception.\n1. The attacker can now read the data your program sends to `https://example.com` and/or alter its replies while the program thinks the connection is secure.\n\n## Recommendation\nDo not use a custom `TrustManager` that trusts any certificate. If you have to use a self-signed certificate, don't trust every certificate, but instead only trust this specific certificate. See below for an example of how to do this.\n\n\n## Example\nIn the first (bad) example, the `TrustManager` never throws a `CertificateException` and therefore implicitly trusts any certificate. This allows an attacker to perform a machine-in-the-middle attack. In the second (good) example, the self-signed certificate that should be trusted is loaded into a `KeyStore`. This explicitly defines the certificate as trusted and there is no need to create a custom `TrustManager`.\n\n\n```java\npublic static void main(String[] args) throws Exception {\n {\n class InsecureTrustManager implements X509TrustManager {\n @Override\n public X509Certificate[] getAcceptedIssuers() {\n return null;\n }\n\n @Override\n public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {\n // BAD: Does not verify the certificate chain, allowing any certificate.\n }\n\n @Override\n public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {\n\n }\n }\n SSLContext context = SSLContext.getInstance(\"TLS\");\n TrustManager[] trustManager = new TrustManager[] { new InsecureTrustManager() };\n context.init(null, trustManager, null);\n }\n {\n SSLContext context = SSLContext.getInstance(\"TLS\");\n File certificateFile = new File(\"path/to/self-signed-certificate\");\n // Create a `KeyStore` with default type\n KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());\n // `keyStore` is initially empty\n keyStore.load(null, null);\n X509Certificate generatedCertificate;\n try (InputStream cert = new FileInputStream(certificateFile)) {\n generatedCertificate = (X509Certificate) CertificateFactory.getInstance(\"X509\")\n .generateCertificate(cert);\n }\n // Add the self-signed certificate to the key store\n keyStore.setCertificateEntry(certificateFile.getName(), generatedCertificate);\n // Get default `TrustManagerFactory`\n TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());\n // Use it with our key store that trusts our self-signed certificate\n tmf.init(keyStore);\n TrustManager[] trustManagers = tmf.getTrustManagers();\n context.init(null, trustManagers, null);\n // GOOD, we are not using a custom `TrustManager` but instead have\n // added the self-signed certificate we want to trust to the key\n // store. Note, the `trustManagers` will **only** trust this one\n // certificate.\n \n URL url = new URL(\"https://self-signed.badssl.com/\");\n HttpsURLConnection conn = (HttpsURLConnection) url.openConnection();\n conn.setSSLSocketFactory(context.getSocketFactory());\n }\n}\n\n```\n\n## References\n* Android Developers: [Security with HTTPS and SSL](https://developer.android.com/training/articles/security-ssl).\n* Common Weakness Enumeration: [CWE-295](https://cwe.mitre.org/data/definitions/295.html).\n", + "text": "# `TrustManager` that accepts all certificates\nIf the `checkServerTrusted` method of a `TrustManager` never throws a `CertificateException`, it trusts every certificate. This allows an attacker to perform a machine-in-the-middle attack against the application, therefore breaking any security Transport Layer Security (TLS) gives.\n\nAn attack might look like this:\n\n1. The vulnerable program connects to `https://example.com`.\n1. The attacker intercepts this connection and presents a valid, self-signed certificate for `https://example.com`.\n1. The vulnerable program calls the `checkServerTrusted` method to check whether it should trust the certificate.\n1. The `checkServerTrusted` method of your `TrustManager` does not throw a `CertificateException`.\n1. The vulnerable program accepts the certificate and proceeds with the connection since your `TrustManager` implicitly trusted it by not throwing an exception.\n1. The attacker can now read the data your program sends to `https://example.com` and/or alter its replies while the program thinks the connection is secure.\n\n## Recommendation\nDo not use a custom `TrustManager` that trusts any certificate. If you have to use a self-signed certificate, don't trust every certificate, but instead only trust this specific certificate. See below for an example of how to do this.\n\n\n## Example\nIn the first (bad) example, the `TrustManager` never throws a `CertificateException` and therefore implicitly trusts any certificate. This allows an attacker to perform a machine-in-the-middle attack. In the second (good) example, the self-signed certificate that should be trusted is loaded into a `KeyStore`. This explicitly defines the certificate as trusted and there is no need to create a custom `TrustManager`.\n\n\n```java\npublic static void main(String[] args) throws Exception {\n {\n class InsecureTrustManager implements X509TrustManager {\n @Override\n public X509Certificate[] getAcceptedIssuers() {\n return null;\n }\n\n @Override\n public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {\n // BAD: Does not verify the certificate chain, allowing any certificate.\n }\n\n @Override\n public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {\n\n }\n }\n SSLContext context = SSLContext.getInstance(\"TLS\");\n TrustManager[] trustManager = new TrustManager[] { new InsecureTrustManager() };\n context.init(null, trustManager, null);\n }\n {\n SSLContext context = SSLContext.getInstance(\"TLS\");\n File certificateFile = new File(\"path/to/self-signed-certificate\");\n // Create a `KeyStore` with default type\n KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());\n // `keyStore` is initially empty\n keyStore.load(null, null);\n X509Certificate generatedCertificate;\n try (InputStream cert = new FileInputStream(certificateFile)) {\n generatedCertificate = (X509Certificate) CertificateFactory.getInstance(\"X509\")\n .generateCertificate(cert);\n }\n // Add the self-signed certificate to the key store\n keyStore.setCertificateEntry(certificateFile.getName(), generatedCertificate);\n // Get default `TrustManagerFactory`\n TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());\n // Use it with our key store that trusts our self-signed certificate\n tmf.init(keyStore);\n TrustManager[] trustManagers = tmf.getTrustManagers();\n context.init(null, trustManagers, null);\n // GOOD, we are not using a custom `TrustManager` but instead have\n // added the self-signed certificate we want to trust to the key\n // store. Note, the `trustManagers` will **only** trust this one\n // certificate.\n \n URL url = new URL(\"https://self-signed.badssl.com/\");\n HttpsURLConnection conn = (HttpsURLConnection) url.openConnection();\n conn.setSSLSocketFactory(context.getSocketFactory());\n }\n}\n\n```\n\n## References\n* Android Developers: [Security with HTTPS and SSL](https://developer.android.com/training/articles/security-ssl).\n* Common Weakness Enumeration: [CWE-295](https://cwe.mitre.org/data/definitions/295.html).\n" + }, + "id": "java/insecure-trustmanager", + "name": "java/insecure-trustmanager", + "properties": { + "security-severity": "7.500000", + "tags": [ + "external/cwe/cwe-295", + "security" + ] + }, + "shortDescription": { + "text": "`TrustManager` that accepts all certificates" + } + }, + { + "defaultConfiguration": { + "level": "error" + }, + "fullDescription": { + "text": "Evaluation of a user-controlled JEXL expression may lead to arbitrary code execution." + }, + "help": { + "markdown": "# Expression language injection (JEXL)\nJava EXpression Language (JEXL) is a simple expression language provided by the Apache Commons JEXL library. The syntax is close to a mix of ECMAScript and shell-script. The language allows invocation of methods available in the JVM. If a JEXL expression is built using attacker-controlled data, and then evaluated, then it may allow the attacker to run arbitrary code.\n\n\n## Recommendation\nIt is generally recommended to avoid using untrusted input in a JEXL expression. If it is not possible, JEXL expressions should be run in a sandbox that allows accessing only explicitly allowed classes.\n\n\n## Example\nThe following example uses untrusted data to build and run a JEXL expression.\n\n\n```java\npublic void evaluate(Socket socket) throws IOException {\n try (BufferedReader reader = new BufferedReader(\n new InputStreamReader(socket.getInputStream()))) {\n \n String input = reader.readLine();\n JexlEngine jexl = new JexlBuilder().create();\n JexlExpression expression = jexl.createExpression(input);\n JexlContext context = new MapContext();\n expression.evaluate(context);\n }\n}\n```\nThe next example shows how an untrusted JEXL expression can be run in a sandbox that allows accessing only methods in the `java.lang.Math` class. The sandbox is implemented using `JexlSandbox` class that is provided by Apache Commons JEXL 3.\n\n\n```java\npublic void evaluate(Socket socket) throws IOException {\n try (BufferedReader reader = new BufferedReader(\n new InputStreamReader(socket.getInputStream()))) {\n \n JexlSandbox onlyMath = new JexlSandbox(false);\n onlyMath.white(\"java.lang.Math\");\n JexlEngine jexl = new JexlBuilder().sandbox(onlyMath).create();\n \n String input = reader.readLine();\n JexlExpression expression = jexl.createExpression(input);\n JexlContext context = new MapContext();\n expression.evaluate(context);\n }\n}\n```\nThe next example shows another way how a sandbox can be implemented. It uses a custom implementation of `JexlUberspect` that checks if callees are instances of allowed classes.\n\n\n```java\npublic void evaluate(Socket socket) throws IOException {\n try (BufferedReader reader = new BufferedReader(\n new InputStreamReader(socket.getInputStream()))) {\n \n JexlUberspect sandbox = new JexlUberspectSandbox();\n JexlEngine jexl = new JexlBuilder().uberspect(sandbox).create();\n \n String input = reader.readLine();\n JexlExpression expression = jexl.createExpression(input);\n JexlContext context = new MapContext();\n expression.evaluate(context);\n }\n\n private static class JexlUberspectSandbox implements JexlUberspect {\n\n private static final List ALLOWED_CLASSES =\n Arrays.asList(\"java.lang.Math\", \"java.util.Random\");\n\n private final JexlUberspect uberspect = new JexlBuilder().create().getUberspect();\n\n private void checkAccess(Object obj) {\n if (!ALLOWED_CLASSES.contains(obj.getClass().getCanonicalName())) {\n throw new AccessControlException(\"Not allowed\");\n }\n }\n\n @Override\n public JexlMethod getMethod(Object obj, String method, Object... args) {\n checkAccess(obj);\n return uberspect.getMethod(obj, method, args);\n }\n\n @Override\n public List getResolvers(JexlOperator op, Object obj) {\n checkAccess(obj);\n return uberspect.getResolvers(op, obj);\n }\n\n @Override\n public void setClassLoader(ClassLoader loader) {\n uberspect.setClassLoader(loader);\n }\n\n @Override\n public int getVersion() {\n return uberspect.getVersion();\n }\n\n @Override\n public JexlMethod getConstructor(Object obj, Object... args) {\n checkAccess(obj);\n return uberspect.getConstructor(obj, args);\n }\n\n @Override\n public JexlPropertyGet getPropertyGet(Object obj, Object identifier) {\n checkAccess(obj);\n return uberspect.getPropertyGet(obj, identifier);\n }\n\n @Override\n public JexlPropertyGet getPropertyGet(List resolvers, Object obj, Object identifier) {\n checkAccess(obj);\n return uberspect.getPropertyGet(resolvers, obj, identifier);\n }\n\n @Override\n public JexlPropertySet getPropertySet(Object obj, Object identifier, Object arg) {\n checkAccess(obj);\n return uberspect.getPropertySet(obj, identifier, arg);\n }\n\n @Override\n public JexlPropertySet getPropertySet(List resolvers, Object obj, Object identifier, Object arg) {\n checkAccess(obj);\n return uberspect.getPropertySet(resolvers, obj, identifier, arg);\n }\n\n @Override\n public Iterator getIterator(Object obj) {\n checkAccess(obj);\n return uberspect.getIterator(obj);\n }\n\n @Override\n public JexlArithmetic.Uberspect getArithmetic(JexlArithmetic arithmetic) {\n return uberspect.getArithmetic(arithmetic);\n } \n }\n}\n```\n\n## References\n* Apache Commons JEXL: [Project page](https://commons.apache.org/proper/commons-jexl/).\n* Apache Commons JEXL documentation: [JEXL 2.1.1 API](https://commons.apache.org/proper/commons-jexl/javadocs/apidocs-2.1.1/).\n* Apache Commons JEXL documentation: [JEXL 3.1 API](https://commons.apache.org/proper/commons-jexl/apidocs/index.html).\n* OWASP: [Expression Language Injection](https://owasp.org/www-community/vulnerabilities/Expression_Language_Injection).\n* Common Weakness Enumeration: [CWE-94](https://cwe.mitre.org/data/definitions/94.html).\n", + "text": "# Expression language injection (JEXL)\nJava EXpression Language (JEXL) is a simple expression language provided by the Apache Commons JEXL library. The syntax is close to a mix of ECMAScript and shell-script. The language allows invocation of methods available in the JVM. If a JEXL expression is built using attacker-controlled data, and then evaluated, then it may allow the attacker to run arbitrary code.\n\n\n## Recommendation\nIt is generally recommended to avoid using untrusted input in a JEXL expression. If it is not possible, JEXL expressions should be run in a sandbox that allows accessing only explicitly allowed classes.\n\n\n## Example\nThe following example uses untrusted data to build and run a JEXL expression.\n\n\n```java\npublic void evaluate(Socket socket) throws IOException {\n try (BufferedReader reader = new BufferedReader(\n new InputStreamReader(socket.getInputStream()))) {\n \n String input = reader.readLine();\n JexlEngine jexl = new JexlBuilder().create();\n JexlExpression expression = jexl.createExpression(input);\n JexlContext context = new MapContext();\n expression.evaluate(context);\n }\n}\n```\nThe next example shows how an untrusted JEXL expression can be run in a sandbox that allows accessing only methods in the `java.lang.Math` class. The sandbox is implemented using `JexlSandbox` class that is provided by Apache Commons JEXL 3.\n\n\n```java\npublic void evaluate(Socket socket) throws IOException {\n try (BufferedReader reader = new BufferedReader(\n new InputStreamReader(socket.getInputStream()))) {\n \n JexlSandbox onlyMath = new JexlSandbox(false);\n onlyMath.white(\"java.lang.Math\");\n JexlEngine jexl = new JexlBuilder().sandbox(onlyMath).create();\n \n String input = reader.readLine();\n JexlExpression expression = jexl.createExpression(input);\n JexlContext context = new MapContext();\n expression.evaluate(context);\n }\n}\n```\nThe next example shows another way how a sandbox can be implemented. It uses a custom implementation of `JexlUberspect` that checks if callees are instances of allowed classes.\n\n\n```java\npublic void evaluate(Socket socket) throws IOException {\n try (BufferedReader reader = new BufferedReader(\n new InputStreamReader(socket.getInputStream()))) {\n \n JexlUberspect sandbox = new JexlUberspectSandbox();\n JexlEngine jexl = new JexlBuilder().uberspect(sandbox).create();\n \n String input = reader.readLine();\n JexlExpression expression = jexl.createExpression(input);\n JexlContext context = new MapContext();\n expression.evaluate(context);\n }\n\n private static class JexlUberspectSandbox implements JexlUberspect {\n\n private static final List ALLOWED_CLASSES =\n Arrays.asList(\"java.lang.Math\", \"java.util.Random\");\n\n private final JexlUberspect uberspect = new JexlBuilder().create().getUberspect();\n\n private void checkAccess(Object obj) {\n if (!ALLOWED_CLASSES.contains(obj.getClass().getCanonicalName())) {\n throw new AccessControlException(\"Not allowed\");\n }\n }\n\n @Override\n public JexlMethod getMethod(Object obj, String method, Object... args) {\n checkAccess(obj);\n return uberspect.getMethod(obj, method, args);\n }\n\n @Override\n public List getResolvers(JexlOperator op, Object obj) {\n checkAccess(obj);\n return uberspect.getResolvers(op, obj);\n }\n\n @Override\n public void setClassLoader(ClassLoader loader) {\n uberspect.setClassLoader(loader);\n }\n\n @Override\n public int getVersion() {\n return uberspect.getVersion();\n }\n\n @Override\n public JexlMethod getConstructor(Object obj, Object... args) {\n checkAccess(obj);\n return uberspect.getConstructor(obj, args);\n }\n\n @Override\n public JexlPropertyGet getPropertyGet(Object obj, Object identifier) {\n checkAccess(obj);\n return uberspect.getPropertyGet(obj, identifier);\n }\n\n @Override\n public JexlPropertyGet getPropertyGet(List resolvers, Object obj, Object identifier) {\n checkAccess(obj);\n return uberspect.getPropertyGet(resolvers, obj, identifier);\n }\n\n @Override\n public JexlPropertySet getPropertySet(Object obj, Object identifier, Object arg) {\n checkAccess(obj);\n return uberspect.getPropertySet(obj, identifier, arg);\n }\n\n @Override\n public JexlPropertySet getPropertySet(List resolvers, Object obj, Object identifier, Object arg) {\n checkAccess(obj);\n return uberspect.getPropertySet(resolvers, obj, identifier, arg);\n }\n\n @Override\n public Iterator getIterator(Object obj) {\n checkAccess(obj);\n return uberspect.getIterator(obj);\n }\n\n @Override\n public JexlArithmetic.Uberspect getArithmetic(JexlArithmetic arithmetic) {\n return uberspect.getArithmetic(arithmetic);\n } \n }\n}\n```\n\n## References\n* Apache Commons JEXL: [Project page](https://commons.apache.org/proper/commons-jexl/).\n* Apache Commons JEXL documentation: [JEXL 2.1.1 API](https://commons.apache.org/proper/commons-jexl/javadocs/apidocs-2.1.1/).\n* Apache Commons JEXL documentation: [JEXL 3.1 API](https://commons.apache.org/proper/commons-jexl/apidocs/index.html).\n* OWASP: [Expression Language Injection](https://owasp.org/www-community/vulnerabilities/Expression_Language_Injection).\n* Common Weakness Enumeration: [CWE-94](https://cwe.mitre.org/data/definitions/94.html).\n" + }, + "id": "java/jexl-expression-injection", + "name": "java/jexl-expression-injection", + "properties": { + "security-severity": "9.300000", + "tags": [ + "external/cwe/cwe-094", + "security" + ] + }, + "shortDescription": { + "text": "Expression language injection (JEXL)" + } + }, + { + "defaultConfiguration": { + "level": "error" + }, + "fullDescription": { + "text": "Using a vulnerable version of JHipster to generate random numbers makes it easier for attackers to take over accounts." + }, + "help": { + "markdown": "# Detect JHipster Generator Vulnerability CVE-2019-16303\nThis query detects instances of `RandomUtil.java` that were generated by a [JHipster](https://www.jhipster.tech/) version that is vulnerable to [CVE-2019-16303](https://github.com/jhipster/jhipster-kotlin/security/advisories/GHSA-j3rh-8vwq-wh84).\n\nIf an app uses `RandomUtil.java` generated by a vulnerable version of JHipster, attackers can request a password reset token and use this to predict the value of future reset tokens generated by this server. Using this information, they can create a reset link that allows them to take over any account.\n\nThis vulnerability has a [ CVSS v3.0 Base Score of 9.8/10 ](https://nvd.nist.gov/vuln-metrics/cvss/v3-calculator?name=CVE-2019-16303&vector=AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H&version=3.1&source=NIST).\n\n\n## Example\nThe example below shows the vulnerable `RandomUtil` class generated by [JHipster prior to version 6.3.0](https://www.jhipster.tech/2019/09/13/jhipster-release-6.3.0.html).\n\n\n```java\nimport org.apache.commons.lang3.RandomStringUtils;\n\n/**\n * Utility class for generating random Strings.\n */\npublic final class RandomUtil {\n\n private static final int DEF_COUNT = 20;\n\n private RandomUtil() {\n }\n\n /**\n * Generate a password.\n *\n * @return the generated password.\n */\n public static String generatePassword() {\n return RandomStringUtils.randomAlphanumeric(DEF_COUNT); // BAD: RandomStringUtils does not use SecureRandom\n }\n\n /**\n * Generate an activation key.\n *\n * @return the generated activation key.\n */\n public static String generateActivationKey() {\n return RandomStringUtils.randomNumeric(DEF_COUNT); // BAD: RandomStringUtils does not use SecureRandom\n }\n\n /**\n * Generate a reset key.\n *\n * @return the generated reset key.\n */\n public static String generateResetKey() {\n return RandomStringUtils.randomNumeric(DEF_COUNT); // BAD: RandomStringUtils does not use SecureRandom\n }\n\n /**\n * Generate a unique series to validate a persistent token, used in the\n * authentication remember-me mechanism.\n *\n * @return the generated series data.\n */\n public static String generateSeriesData() {\n return RandomStringUtils.randomAlphanumeric(DEF_COUNT); // BAD: RandomStringUtils does not use SecureRandom\n }\n\n /**\n * Generate a persistent token, used in the authentication remember-me mechanism.\n *\n * @return the generated token data.\n */\n public static String generateTokenData() {\n return RandomStringUtils.randomAlphanumeric(DEF_COUNT); // BAD: RandomStringUtils does not use SecureRandom\n }\n}\n\n```\nBelow is a fixed version of the `RandomUtil` class.\n\n\n```java\nimport org.apache.commons.lang3.RandomStringUtils;\n\nimport java.security.SecureRandom;\n\n/**\n * Utility class for generating random Strings.\n */\npublic final class RandomUtil {\n private static final SecureRandom SECURE_RANDOM = new SecureRandom(); // GOOD: Using SecureRandom\n\n private static final int DEF_COUNT = 20;\n\n static {\n SECURE_RANDOM.nextBytes(new byte[64]);\n }\n\n private RandomUtil() {\n }\n\n private static String generateRandomAlphanumericString() {\n // GOOD: Passing Secure Random to RandomStringUtils::random\n return RandomStringUtils.random(DEF_COUNT, 0, 0, true, true, null, SECURE_RANDOM);\n }\n\n /**\n * Generate a password.\n *\n * @return the generated password.\n */\n public static String generatePassword() {\n return generateRandomAlphanumericString();\n }\n\n /**\n * Generate an activation key.\n *\n * @return the generated activation key.\n */\n public static String generateActivationKey() {\n return generateRandomAlphanumericString();\n }\n\n /**\n * Generate a reset key.\n *\n * @return the generated reset key.\n */\n public static String generateResetKey() {\n return generateRandomAlphanumericString();\n }\n\n /**\n * Generate a unique series to validate a persistent token, used in the\n * authentication remember-me mechanism.\n *\n * @return the generated series data.\n */\n public static String generateSeriesData() {\n return generateRandomAlphanumericString();\n }\n\n /**\n * Generate a persistent token, used in the authentication remember-me mechanism.\n *\n * @return the generated token data.\n */\n public static String generateTokenData() {\n return generateRandomAlphanumericString();\n }\n}\n\n```\n\n## Recommendation\nYou should refactor the `RandomUtil` class and replace every call to `RandomStringUtils.randomAlphaNumeric`. You could regenerate the class using the latest version of JHipster, or use an automated refactoring. For example, using the [Patching JHipster CWE-338](https://github.com/moderneinc/jhipster-cwe-338) for the [Rewrite project](https://github.com/openrewrite/rewrite).\n\n\n## References\n* Cloudflare Blog: [ Why secure systems require random numbers ](https://blog.cloudflare.com/why-randomness-matters/)\n* Hacker News: [ How I Hacked Hacker News (with arc security advisory) ](https://news.ycombinator.com/item?id=639976)\n* Posts by Pucara Information Security Team: [ The Java Soothsayer: A practical application for insecure randomness. (Includes free 0day) ](https://blog.pucarasec.com/2020/05/09/the-java-soothsayer-a-practical-application-for-insecure-randomness-includes-free-0day/)\n* Common Weakness Enumeration: [CWE-338](https://cwe.mitre.org/data/definitions/338.html).\n", + "text": "# Detect JHipster Generator Vulnerability CVE-2019-16303\nThis query detects instances of `RandomUtil.java` that were generated by a [JHipster](https://www.jhipster.tech/) version that is vulnerable to [CVE-2019-16303](https://github.com/jhipster/jhipster-kotlin/security/advisories/GHSA-j3rh-8vwq-wh84).\n\nIf an app uses `RandomUtil.java` generated by a vulnerable version of JHipster, attackers can request a password reset token and use this to predict the value of future reset tokens generated by this server. Using this information, they can create a reset link that allows them to take over any account.\n\nThis vulnerability has a [ CVSS v3.0 Base Score of 9.8/10 ](https://nvd.nist.gov/vuln-metrics/cvss/v3-calculator?name=CVE-2019-16303&vector=AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H&version=3.1&source=NIST).\n\n\n## Example\nThe example below shows the vulnerable `RandomUtil` class generated by [JHipster prior to version 6.3.0](https://www.jhipster.tech/2019/09/13/jhipster-release-6.3.0.html).\n\n\n```java\nimport org.apache.commons.lang3.RandomStringUtils;\n\n/**\n * Utility class for generating random Strings.\n */\npublic final class RandomUtil {\n\n private static final int DEF_COUNT = 20;\n\n private RandomUtil() {\n }\n\n /**\n * Generate a password.\n *\n * @return the generated password.\n */\n public static String generatePassword() {\n return RandomStringUtils.randomAlphanumeric(DEF_COUNT); // BAD: RandomStringUtils does not use SecureRandom\n }\n\n /**\n * Generate an activation key.\n *\n * @return the generated activation key.\n */\n public static String generateActivationKey() {\n return RandomStringUtils.randomNumeric(DEF_COUNT); // BAD: RandomStringUtils does not use SecureRandom\n }\n\n /**\n * Generate a reset key.\n *\n * @return the generated reset key.\n */\n public static String generateResetKey() {\n return RandomStringUtils.randomNumeric(DEF_COUNT); // BAD: RandomStringUtils does not use SecureRandom\n }\n\n /**\n * Generate a unique series to validate a persistent token, used in the\n * authentication remember-me mechanism.\n *\n * @return the generated series data.\n */\n public static String generateSeriesData() {\n return RandomStringUtils.randomAlphanumeric(DEF_COUNT); // BAD: RandomStringUtils does not use SecureRandom\n }\n\n /**\n * Generate a persistent token, used in the authentication remember-me mechanism.\n *\n * @return the generated token data.\n */\n public static String generateTokenData() {\n return RandomStringUtils.randomAlphanumeric(DEF_COUNT); // BAD: RandomStringUtils does not use SecureRandom\n }\n}\n\n```\nBelow is a fixed version of the `RandomUtil` class.\n\n\n```java\nimport org.apache.commons.lang3.RandomStringUtils;\n\nimport java.security.SecureRandom;\n\n/**\n * Utility class for generating random Strings.\n */\npublic final class RandomUtil {\n private static final SecureRandom SECURE_RANDOM = new SecureRandom(); // GOOD: Using SecureRandom\n\n private static final int DEF_COUNT = 20;\n\n static {\n SECURE_RANDOM.nextBytes(new byte[64]);\n }\n\n private RandomUtil() {\n }\n\n private static String generateRandomAlphanumericString() {\n // GOOD: Passing Secure Random to RandomStringUtils::random\n return RandomStringUtils.random(DEF_COUNT, 0, 0, true, true, null, SECURE_RANDOM);\n }\n\n /**\n * Generate a password.\n *\n * @return the generated password.\n */\n public static String generatePassword() {\n return generateRandomAlphanumericString();\n }\n\n /**\n * Generate an activation key.\n *\n * @return the generated activation key.\n */\n public static String generateActivationKey() {\n return generateRandomAlphanumericString();\n }\n\n /**\n * Generate a reset key.\n *\n * @return the generated reset key.\n */\n public static String generateResetKey() {\n return generateRandomAlphanumericString();\n }\n\n /**\n * Generate a unique series to validate a persistent token, used in the\n * authentication remember-me mechanism.\n *\n * @return the generated series data.\n */\n public static String generateSeriesData() {\n return generateRandomAlphanumericString();\n }\n\n /**\n * Generate a persistent token, used in the authentication remember-me mechanism.\n *\n * @return the generated token data.\n */\n public static String generateTokenData() {\n return generateRandomAlphanumericString();\n }\n}\n\n```\n\n## Recommendation\nYou should refactor the `RandomUtil` class and replace every call to `RandomStringUtils.randomAlphaNumeric`. You could regenerate the class using the latest version of JHipster, or use an automated refactoring. For example, using the [Patching JHipster CWE-338](https://github.com/moderneinc/jhipster-cwe-338) for the [Rewrite project](https://github.com/openrewrite/rewrite).\n\n\n## References\n* Cloudflare Blog: [ Why secure systems require random numbers ](https://blog.cloudflare.com/why-randomness-matters/)\n* Hacker News: [ How I Hacked Hacker News (with arc security advisory) ](https://news.ycombinator.com/item?id=639976)\n* Posts by Pucara Information Security Team: [ The Java Soothsayer: A practical application for insecure randomness. (Includes free 0day) ](https://blog.pucarasec.com/2020/05/09/the-java-soothsayer-a-practical-application-for-insecure-randomness-includes-free-0day/)\n* Common Weakness Enumeration: [CWE-338](https://cwe.mitre.org/data/definitions/338.html).\n" + }, + "id": "java/jhipster-prng", + "name": "java/jhipster-prng", + "properties": { + "security-severity": "7.800000", + "tags": [ + "external/cwe/cwe-338", + "security" + ] + }, + "shortDescription": { + "text": "Detect JHipster Generator Vulnerability CVE-2019-16303" + } + }, + { + "defaultConfiguration": { + "level": "error" + }, + "fullDescription": { + "text": "Performing a JNDI lookup with a user-controlled name can lead to the download of an untrusted object and to execution of arbitrary code." + }, + "help": { + "markdown": "# JNDI lookup with user-controlled name\nThe Java Naming and Directory Interface (JNDI) is a Java API for a directory service that allows Java software clients to discover and look up data and resources (in the form of Java objects) via a name. If the name being used to look up the data is controlled by the user, it can point to a malicious server, which can return an arbitrary object. In the worst case, this can allow remote code execution.\n\n\n## Recommendation\nThe general recommendation is to avoid passing untrusted data to the `InitialContext.lookup ` method. If the name being used to look up the object must be provided by the user, make sure that it's not in the form of an absolute URL or that it's the URL pointing to a trused server.\n\n\n## Example\nIn the following examples, the code accepts a name from the user, which it uses to look up an object.\n\nIn the first example, the user provided name is used to look up an object.\n\nThe second example validates the name before using it to look up an object.\n\n\n```java\nimport javax.naming.Context;\nimport javax.naming.InitialContext;\n\npublic void jndiLookup(HttpServletRequest request) throws NamingException {\n String name = request.getParameter(\"name\");\n\n Hashtable env = new Hashtable();\n env.put(Context.INITIAL_CONTEXT_FACTORY, \"com.sun.jndi.rmi.registry.RegistryContextFactory\");\n env.put(Context.PROVIDER_URL, \"rmi://trusted-server:1099\");\n InitialContext ctx = new InitialContext(env);\n\n // BAD: User input used in lookup\n ctx.lookup(name);\n\n // GOOD: The name is validated before being used in lookup\n if (isValid(name)) {\n ctx.lookup(name);\n } else {\n // Reject the request\n }\n}\n```\n\n## References\n* Oracle: [Java Naming and Directory Interface (JNDI)](https://docs.oracle.com/javase/8/docs/technotes/guides/jndi/).\n* Black Hat materials: [A Journey from JNDI/LDAP Manipulation to Remote Code Execution Dream Land](https://www.blackhat.com/docs/us-16/materials/us-16-Munoz-A-Journey-From-JNDI-LDAP-Manipulation-To-RCE-wp.pdf).\n* Veracode: [Exploiting JNDI Injections in Java](https://www.veracode.com/blog/research/exploiting-jndi-injections-java).\n* Common Weakness Enumeration: [CWE-74](https://cwe.mitre.org/data/definitions/74.html).\n", + "text": "# JNDI lookup with user-controlled name\nThe Java Naming and Directory Interface (JNDI) is a Java API for a directory service that allows Java software clients to discover and look up data and resources (in the form of Java objects) via a name. If the name being used to look up the data is controlled by the user, it can point to a malicious server, which can return an arbitrary object. In the worst case, this can allow remote code execution.\n\n\n## Recommendation\nThe general recommendation is to avoid passing untrusted data to the `InitialContext.lookup ` method. If the name being used to look up the object must be provided by the user, make sure that it's not in the form of an absolute URL or that it's the URL pointing to a trused server.\n\n\n## Example\nIn the following examples, the code accepts a name from the user, which it uses to look up an object.\n\nIn the first example, the user provided name is used to look up an object.\n\nThe second example validates the name before using it to look up an object.\n\n\n```java\nimport javax.naming.Context;\nimport javax.naming.InitialContext;\n\npublic void jndiLookup(HttpServletRequest request) throws NamingException {\n String name = request.getParameter(\"name\");\n\n Hashtable env = new Hashtable();\n env.put(Context.INITIAL_CONTEXT_FACTORY, \"com.sun.jndi.rmi.registry.RegistryContextFactory\");\n env.put(Context.PROVIDER_URL, \"rmi://trusted-server:1099\");\n InitialContext ctx = new InitialContext(env);\n\n // BAD: User input used in lookup\n ctx.lookup(name);\n\n // GOOD: The name is validated before being used in lookup\n if (isValid(name)) {\n ctx.lookup(name);\n } else {\n // Reject the request\n }\n}\n```\n\n## References\n* Oracle: [Java Naming and Directory Interface (JNDI)](https://docs.oracle.com/javase/8/docs/technotes/guides/jndi/).\n* Black Hat materials: [A Journey from JNDI/LDAP Manipulation to Remote Code Execution Dream Land](https://www.blackhat.com/docs/us-16/materials/us-16-Munoz-A-Journey-From-JNDI-LDAP-Manipulation-To-RCE-wp.pdf).\n* Veracode: [Exploiting JNDI Injections in Java](https://www.veracode.com/blog/research/exploiting-jndi-injections-java).\n* Common Weakness Enumeration: [CWE-74](https://cwe.mitre.org/data/definitions/74.html).\n" + }, + "id": "java/jndi-injection", + "name": "java/jndi-injection", + "properties": { + "tags": [ + "external/cwe/cwe-074", + "security" + ] + }, + "shortDescription": { + "text": "JNDI lookup with user-controlled name" + } + }, + { + "defaultConfiguration": { + "level": "error" + }, + "fullDescription": { + "text": "Building an LDAP query from user-controlled sources is vulnerable to insertion of malicious LDAP code by the user." + }, + "help": { + "markdown": "# LDAP query built from user-controlled sources\nIf an LDAP query is built using string concatenation, and the components of the concatenation include user input, a user is likely to be able to run malicious LDAP queries.\n\n\n## Recommendation\nIf user input must be included in an LDAP query, it should be escaped to avoid a malicious user providing special characters that change the meaning of the query. If possible build the LDAP query using framework helper methods, for example from Spring's `LdapQueryBuilder` and `LdapNameBuilder`, instead of string concatenation. Alternatively, escape user input using an appropriate LDAP encoding method, for example: `encodeForLDAP` or `encodeForDN` from OWASP ESAPI, `LdapEncoder.filterEncode` or `LdapEncoder.nameEncode` from Spring LDAP, or `Filter.encodeValue` from UnboundID library.\n\n\n## Example\nIn the following examples, the code accepts an \"organization name\" and a \"username\" from the user, which it uses to query LDAP.\n\nThe first example concatenates the unvalidated and unencoded user input directly into both the DN (Distinguished Name) and the search filter used for the LDAP query. A malicious user could provide special characters to change the meaning of these queries, and search for a completely different set of values. The LDAP query is executed using Java JNDI API.\n\nThe second example uses the OWASP ESAPI library to encode the user values before they are included in the DN and search filters. This ensures the meaning of the query cannot be changed by a malicious user.\n\n\n```java\nimport javax.naming.directory.DirContext;\nimport org.owasp.esapi.Encoder;\nimport org.owasp.esapi.reference.DefaultEncoder;\n\npublic void ldapQueryBad(HttpServletRequest request, DirContext ctx) throws NamingException {\n String organizationName = request.getParameter(\"organization_name\");\n String username = request.getParameter(\"username\");\n\n // BAD: User input used in DN (Distinguished Name) without encoding\n String dn = \"OU=People,O=\" + organizationName;\n\n // BAD: User input used in search filter without encoding\n String filter = \"username=\" + userName;\n\n ctx.search(dn, filter, new SearchControls());\n}\n\npublic void ldapQueryGood(HttpServletRequest request, DirContext ctx) throws NamingException {\n String organizationName = request.getParameter(\"organization_name\");\n String username = request.getParameter(\"username\");\n\n // ESAPI encoder\n Encoder encoder = DefaultEncoder.getInstance();\n\n // GOOD: Organization name is encoded before being used in DN\n String safeOrganizationName = encoder.encodeForDN(organizationName);\n String safeDn = \"OU=People,O=\" + safeOrganizationName;\n\n // GOOD: User input is encoded before being used in search filter\n String safeUsername = encoder.encodeForLDAP(username);\n String safeFilter = \"username=\" + safeUsername;\n \n ctx.search(safeDn, safeFilter, new SearchControls());\n}\n```\nThe third example uses Spring `LdapQueryBuilder` to build an LDAP query. In addition to simplifying the building of complex search parameters, it also provides proper escaping of any unsafe characters in search filters. The DN is built using `LdapNameBuilder`, which also provides proper escaping.\n\n\n```java\nimport static org.springframework.ldap.query.LdapQueryBuilder.query;\nimport org.springframework.ldap.support.LdapNameBuilder;\n\npublic void ldapQueryGood(@RequestParam String organizationName, @RequestParam String username) {\n // GOOD: Organization name is encoded before being used in DN\n String safeDn = LdapNameBuilder.newInstance()\n .add(\"O\", organizationName)\n .add(\"OU=People\")\n .build().toString();\n\n // GOOD: User input is encoded before being used in search filter\n LdapQuery query = query()\n .base(safeDn)\n .where(\"username\").is(username);\n\n ldapTemplate.search(query, new AttributeCheckAttributesMapper());\n}\n```\nThe fourth example uses `UnboundID` classes, `Filter` and `DN`, to construct a safe filter and base DN.\n\n\n```java\nimport com.unboundid.ldap.sdk.LDAPConnection;\nimport com.unboundid.ldap.sdk.DN;\nimport com.unboundid.ldap.sdk.RDN;\nimport com.unboundid.ldap.sdk.Filter;\n\npublic void ldapQueryGood(HttpServletRequest request, LDAPConnection c) {\n String organizationName = request.getParameter(\"organization_name\");\n String username = request.getParameter(\"username\");\n\n // GOOD: Organization name is encoded before being used in DN\n DN safeDn = new DN(new RDN(\"OU\", \"People\"), new RDN(\"O\", organizationName));\n\n // GOOD: User input is encoded before being used in search filter\n Filter safeFilter = Filter.createEqualityFilter(\"username\", username);\n \n c.search(safeDn.toString(), SearchScope.ONE, safeFilter);\n}\n```\nThe fifth example shows how to build a safe filter and DN using the Apache LDAP API.\n\n\n```java\nimport org.apache.directory.ldap.client.api.LdapConnection;\nimport org.apache.directory.api.ldap.model.name.Dn;\nimport org.apache.directory.api.ldap.model.name.Rdn;\nimport org.apache.directory.api.ldap.model.message.SearchRequest;\nimport org.apache.directory.api.ldap.model.message.SearchRequestImpl;\nimport static org.apache.directory.ldap.client.api.search.FilterBuilder.equal;\n\npublic void ldapQueryGood(HttpServletRequest request, LdapConnection c) {\n String organizationName = request.getParameter(\"organization_name\");\n String username = request.getParameter(\"username\");\n\n // GOOD: Organization name is encoded before being used in DN\n Dn safeDn = new Dn(new Rdn(\"OU\", \"People\"), new Rdn(\"O\", organizationName));\n\n // GOOD: User input is encoded before being used in search filter\n String safeFilter = equal(\"username\", username);\n \n SearchRequest searchRequest = new SearchRequestImpl();\n searchRequest.setBase(safeDn);\n searchRequest.setFilter(safeFilter);\n c.search(searchRequest);\n}\n```\n\n## References\n* OWASP: [LDAP Injection Prevention Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/LDAP_Injection_Prevention_Cheat_Sheet.html).\n* OWASP ESAPI: [OWASP ESAPI](https://owasp.org/www-project-enterprise-security-api/).\n* Spring LdapQueryBuilder doc: [LdapQueryBuilder](https://docs.spring.io/spring-ldap/docs/current/apidocs/org/springframework/ldap/query/LdapQueryBuilder.html).\n* Spring LdapNameBuilder doc: [LdapNameBuilder](https://docs.spring.io/spring-ldap/docs/current/apidocs/org/springframework/ldap/support/LdapNameBuilder.html).\n* UnboundID: [Understanding and Defending Against LDAP Injection Attacks](https://ldap.com/2018/05/04/understanding-and-defending-against-ldap-injection-attacks/).\n* Common Weakness Enumeration: [CWE-90](https://cwe.mitre.org/data/definitions/90.html).\n", + "text": "# LDAP query built from user-controlled sources\nIf an LDAP query is built using string concatenation, and the components of the concatenation include user input, a user is likely to be able to run malicious LDAP queries.\n\n\n## Recommendation\nIf user input must be included in an LDAP query, it should be escaped to avoid a malicious user providing special characters that change the meaning of the query. If possible build the LDAP query using framework helper methods, for example from Spring's `LdapQueryBuilder` and `LdapNameBuilder`, instead of string concatenation. Alternatively, escape user input using an appropriate LDAP encoding method, for example: `encodeForLDAP` or `encodeForDN` from OWASP ESAPI, `LdapEncoder.filterEncode` or `LdapEncoder.nameEncode` from Spring LDAP, or `Filter.encodeValue` from UnboundID library.\n\n\n## Example\nIn the following examples, the code accepts an \"organization name\" and a \"username\" from the user, which it uses to query LDAP.\n\nThe first example concatenates the unvalidated and unencoded user input directly into both the DN (Distinguished Name) and the search filter used for the LDAP query. A malicious user could provide special characters to change the meaning of these queries, and search for a completely different set of values. The LDAP query is executed using Java JNDI API.\n\nThe second example uses the OWASP ESAPI library to encode the user values before they are included in the DN and search filters. This ensures the meaning of the query cannot be changed by a malicious user.\n\n\n```java\nimport javax.naming.directory.DirContext;\nimport org.owasp.esapi.Encoder;\nimport org.owasp.esapi.reference.DefaultEncoder;\n\npublic void ldapQueryBad(HttpServletRequest request, DirContext ctx) throws NamingException {\n String organizationName = request.getParameter(\"organization_name\");\n String username = request.getParameter(\"username\");\n\n // BAD: User input used in DN (Distinguished Name) without encoding\n String dn = \"OU=People,O=\" + organizationName;\n\n // BAD: User input used in search filter without encoding\n String filter = \"username=\" + userName;\n\n ctx.search(dn, filter, new SearchControls());\n}\n\npublic void ldapQueryGood(HttpServletRequest request, DirContext ctx) throws NamingException {\n String organizationName = request.getParameter(\"organization_name\");\n String username = request.getParameter(\"username\");\n\n // ESAPI encoder\n Encoder encoder = DefaultEncoder.getInstance();\n\n // GOOD: Organization name is encoded before being used in DN\n String safeOrganizationName = encoder.encodeForDN(organizationName);\n String safeDn = \"OU=People,O=\" + safeOrganizationName;\n\n // GOOD: User input is encoded before being used in search filter\n String safeUsername = encoder.encodeForLDAP(username);\n String safeFilter = \"username=\" + safeUsername;\n \n ctx.search(safeDn, safeFilter, new SearchControls());\n}\n```\nThe third example uses Spring `LdapQueryBuilder` to build an LDAP query. In addition to simplifying the building of complex search parameters, it also provides proper escaping of any unsafe characters in search filters. The DN is built using `LdapNameBuilder`, which also provides proper escaping.\n\n\n```java\nimport static org.springframework.ldap.query.LdapQueryBuilder.query;\nimport org.springframework.ldap.support.LdapNameBuilder;\n\npublic void ldapQueryGood(@RequestParam String organizationName, @RequestParam String username) {\n // GOOD: Organization name is encoded before being used in DN\n String safeDn = LdapNameBuilder.newInstance()\n .add(\"O\", organizationName)\n .add(\"OU=People\")\n .build().toString();\n\n // GOOD: User input is encoded before being used in search filter\n LdapQuery query = query()\n .base(safeDn)\n .where(\"username\").is(username);\n\n ldapTemplate.search(query, new AttributeCheckAttributesMapper());\n}\n```\nThe fourth example uses `UnboundID` classes, `Filter` and `DN`, to construct a safe filter and base DN.\n\n\n```java\nimport com.unboundid.ldap.sdk.LDAPConnection;\nimport com.unboundid.ldap.sdk.DN;\nimport com.unboundid.ldap.sdk.RDN;\nimport com.unboundid.ldap.sdk.Filter;\n\npublic void ldapQueryGood(HttpServletRequest request, LDAPConnection c) {\n String organizationName = request.getParameter(\"organization_name\");\n String username = request.getParameter(\"username\");\n\n // GOOD: Organization name is encoded before being used in DN\n DN safeDn = new DN(new RDN(\"OU\", \"People\"), new RDN(\"O\", organizationName));\n\n // GOOD: User input is encoded before being used in search filter\n Filter safeFilter = Filter.createEqualityFilter(\"username\", username);\n \n c.search(safeDn.toString(), SearchScope.ONE, safeFilter);\n}\n```\nThe fifth example shows how to build a safe filter and DN using the Apache LDAP API.\n\n\n```java\nimport org.apache.directory.ldap.client.api.LdapConnection;\nimport org.apache.directory.api.ldap.model.name.Dn;\nimport org.apache.directory.api.ldap.model.name.Rdn;\nimport org.apache.directory.api.ldap.model.message.SearchRequest;\nimport org.apache.directory.api.ldap.model.message.SearchRequestImpl;\nimport static org.apache.directory.ldap.client.api.search.FilterBuilder.equal;\n\npublic void ldapQueryGood(HttpServletRequest request, LdapConnection c) {\n String organizationName = request.getParameter(\"organization_name\");\n String username = request.getParameter(\"username\");\n\n // GOOD: Organization name is encoded before being used in DN\n Dn safeDn = new Dn(new Rdn(\"OU\", \"People\"), new Rdn(\"O\", organizationName));\n\n // GOOD: User input is encoded before being used in search filter\n String safeFilter = equal(\"username\", username);\n \n SearchRequest searchRequest = new SearchRequestImpl();\n searchRequest.setBase(safeDn);\n searchRequest.setFilter(safeFilter);\n c.search(searchRequest);\n}\n```\n\n## References\n* OWASP: [LDAP Injection Prevention Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/LDAP_Injection_Prevention_Cheat_Sheet.html).\n* OWASP ESAPI: [OWASP ESAPI](https://owasp.org/www-project-enterprise-security-api/).\n* Spring LdapQueryBuilder doc: [LdapQueryBuilder](https://docs.spring.io/spring-ldap/docs/current/apidocs/org/springframework/ldap/query/LdapQueryBuilder.html).\n* Spring LdapNameBuilder doc: [LdapNameBuilder](https://docs.spring.io/spring-ldap/docs/current/apidocs/org/springframework/ldap/support/LdapNameBuilder.html).\n* UnboundID: [Understanding and Defending Against LDAP Injection Attacks](https://ldap.com/2018/05/04/understanding-and-defending-against-ldap-injection-attacks/).\n* Common Weakness Enumeration: [CWE-90](https://cwe.mitre.org/data/definitions/90.html).\n" + }, + "id": "java/ldap-injection", + "name": "java/ldap-injection", + "properties": { + "security-severity": "9.800000", + "tags": [ + "external/cwe/cwe-090", + "security" + ] + }, + "shortDescription": { + "text": "LDAP query built from user-controlled sources" + } + }, + { + "defaultConfiguration": { + "level": "error" + }, + "fullDescription": { + "text": "Using a deprecated artifact repository may eventually give attackers access for a supply chain attack." + }, + "help": { + "markdown": "# Depending upon JCenter/Bintray as an artifact repository\n[Bintray and JCenter are shutting down on February 1st, 2022](https://jfrog.com/blog/into-the-sunset-bintray-jcenter-gocenter-and-chartcenter/). Relying upon repositories that are deprecated or scheduled to be shutdown can have unintended consequences; for example, artifacts being resolved from a different artifact server or a total failure of the CI build.\n\nWhen artifact repositories are left unmaintained for a long period of time, vulnerabilities may emerge. Theoretically, this could allow attackers to inject malicious code into the artifacts that you are resolving and infect build artifacts that are being produced. This can be used by attackers to perform a [supply chain attack](https://en.wikipedia.org/wiki/Supply_chain_attack) against your project's users.\n\n\n## Recommendation\nAlways use the canonical repository for resolving your dependencies.\n\n\n## Example\nThe following example shows locations in a Maven POM file where artifact repository upload/download is configured. The use of Bintray in any of these locations is not advised.\n\n\n```xml\n\n\n\n 4.0.0\n\n com.semmle\n parent\n 1.0\n pom\n\n Bintray Usage\n An example of using bintray to download and upload dependencies\n\n \n \n jcenter\n JCenter\n \n https://jcenter.bintray.com\n \n \n jcenter-snapshots\n JCenter\n \n https://jcenter.bintray.com\n \n \n \n \n jcenter\n JCenter\n \n https://jcenter.bintray.com\n \n \n \n \n jcenter\n JCenter\n \n https://dl.bintray.com/groovy/maven\n \n \n \n \n jcenter-plugins\n JCenter\n \n https://jcenter.bintray.com\n \n \n\n\n```\n\n## References\n* JFrog blog: [ Into the Sunset on May 1st: Bintray, JCenter, GoCenter, and ChartCenter ](https://jfrog.com/blog/into-the-sunset-bintray-jcenter-gocenter-and-chartcenter/)\n* Common Weakness Enumeration: [CWE-1104](https://cwe.mitre.org/data/definitions/1104.html).\n", + "text": "# Depending upon JCenter/Bintray as an artifact repository\n[Bintray and JCenter are shutting down on February 1st, 2022](https://jfrog.com/blog/into-the-sunset-bintray-jcenter-gocenter-and-chartcenter/). Relying upon repositories that are deprecated or scheduled to be shutdown can have unintended consequences; for example, artifacts being resolved from a different artifact server or a total failure of the CI build.\n\nWhen artifact repositories are left unmaintained for a long period of time, vulnerabilities may emerge. Theoretically, this could allow attackers to inject malicious code into the artifacts that you are resolving and infect build artifacts that are being produced. This can be used by attackers to perform a [supply chain attack](https://en.wikipedia.org/wiki/Supply_chain_attack) against your project's users.\n\n\n## Recommendation\nAlways use the canonical repository for resolving your dependencies.\n\n\n## Example\nThe following example shows locations in a Maven POM file where artifact repository upload/download is configured. The use of Bintray in any of these locations is not advised.\n\n\n```xml\n\n\n\n 4.0.0\n\n com.semmle\n parent\n 1.0\n pom\n\n Bintray Usage\n An example of using bintray to download and upload dependencies\n\n \n \n jcenter\n JCenter\n \n https://jcenter.bintray.com\n \n \n jcenter-snapshots\n JCenter\n \n https://jcenter.bintray.com\n \n \n \n \n jcenter\n JCenter\n \n https://jcenter.bintray.com\n \n \n \n \n jcenter\n JCenter\n \n https://dl.bintray.com/groovy/maven\n \n \n \n \n jcenter-plugins\n JCenter\n \n https://jcenter.bintray.com\n \n \n\n\n```\n\n## References\n* JFrog blog: [ Into the Sunset on May 1st: Bintray, JCenter, GoCenter, and ChartCenter ](https://jfrog.com/blog/into-the-sunset-bintray-jcenter-gocenter-and-chartcenter/)\n* Common Weakness Enumeration: [CWE-1104](https://cwe.mitre.org/data/definitions/1104.html).\n" + }, + "id": "java/maven/dependency-upon-bintray", + "name": "java/maven/dependency-upon-bintray", + "properties": { + "security-severity": "6.500000", + "tags": [ + "external/cwe/cwe-1104", + "security" + ] + }, + "shortDescription": { + "text": "Depending upon JCenter/Bintray as an artifact repository" + } + }, + { + "defaultConfiguration": { + "level": "error" + }, + "fullDescription": { + "text": "Non-HTTPS connections can be intercepted by third parties." + }, + "help": { + "markdown": "# Failure to use HTTPS or SFTP URL in Maven artifact upload/download\nUsing an insecure protocol like HTTP or FTP to download your dependencies leaves your Maven build vulnerable to a [Man in the Middle (MITM)](https://en.wikipedia.org/wiki/Man-in-the-middle_attack). This can allow attackers to inject malicious code into the artifacts that you are resolving and infect build artifacts that are being produced. This can be used by attackers to perform a [Supply chain attack](https://en.wikipedia.org/wiki/Supply_chain_attack) against your project's users.\n\nThis vulnerability has a [ CVSS v3.1 base score of 8.1/10 ](https://nvd.nist.gov/vuln-metrics/cvss/v3-calculator?vector=AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H&version=3.1).\n\n\n## Recommendation\nAlways use HTTPS or SFTP to download artifacts from artifact servers.\n\n\n## Example\nThese examples show examples of locations in Maven POM files where artifact repository upload/download is configured. The first shows the use of HTTP, the second shows the use of HTTPS.\n\n\n```xml\n\n\n\n 4.0.0\n\n com.semmle\n parent\n 1.0\n pom\n\n Security Testing\n An example of insecure download and upload of dependencies\n\n \n \n insecure-releases\n Insecure Repository Releases\n \n http://insecure-repository.example\n \n \n insecure-snapshots\n Insecure Repository Snapshots\n \n http://insecure-repository.example\n \n \n \n \n insecure\n Insecure Repository\n \n http://insecure-repository.example\n \n \n \n \n insecure-plugins\n Insecure Repository Releases\n \n http://insecure-repository.example\n \n \n\n\n```\n\n```xml\n\n\n\n 4.0.0\n\n com.semmle\n parent\n 1.0\n pom\n\n Security Testing\n An example of secure download and upload of dependencies\n\n \n \n insecure-releases\n Secure Repository Releases\n \n https://insecure-repository.example\n \n \n insecure-snapshots\n Secure Repository Snapshots\n \n https://insecure-repository.example\n \n \n \n \n insecure\n Secure Repository\n \n https://insecure-repository.example\n \n \n \n \n insecure-plugins\n Secure Repository Releases\n \n https://insecure-repository.example\n \n \n\n\n```\n\n## References\n* Research: [ Want to take over the Java ecosystem? All you need is a MITM! ](https://medium.com/bugbountywriteup/want-to-take-over-the-java-ecosystem-all-you-need-is-a-mitm-1fc329d898fb?source=friends_link&sk=3c99970c55a899ad9ef41f126efcde0e)\n* Research: [ How to take over the computer of any Java (or Closure or Scala) Developer. ](https://max.computer/blog/how-to-take-over-the-computer-of-any-java-or-clojure-or-scala-developer/)\n* Proof of Concept: [ mveytsman/dilettante ](https://github.com/mveytsman/dilettante)\n* Additional Gradle & Maven plugin: [ Announcing nohttp ](https://spring.io/blog/2019/06/10/announcing-nohttp)\n* Java Ecosystem Announcement: [ HTTP Decommission Artifact Server Announcements ](https://gist.github.com/JLLeitschuh/789e49e3d34092a005031a0a1880af99)\n* Common Weakness Enumeration: [CWE-300](https://cwe.mitre.org/data/definitions/300.html).\n* Common Weakness Enumeration: [CWE-319](https://cwe.mitre.org/data/definitions/319.html).\n* Common Weakness Enumeration: [CWE-494](https://cwe.mitre.org/data/definitions/494.html).\n* Common Weakness Enumeration: [CWE-829](https://cwe.mitre.org/data/definitions/829.html).\n", + "text": "# Failure to use HTTPS or SFTP URL in Maven artifact upload/download\nUsing an insecure protocol like HTTP or FTP to download your dependencies leaves your Maven build vulnerable to a [Man in the Middle (MITM)](https://en.wikipedia.org/wiki/Man-in-the-middle_attack). This can allow attackers to inject malicious code into the artifacts that you are resolving and infect build artifacts that are being produced. This can be used by attackers to perform a [Supply chain attack](https://en.wikipedia.org/wiki/Supply_chain_attack) against your project's users.\n\nThis vulnerability has a [ CVSS v3.1 base score of 8.1/10 ](https://nvd.nist.gov/vuln-metrics/cvss/v3-calculator?vector=AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H&version=3.1).\n\n\n## Recommendation\nAlways use HTTPS or SFTP to download artifacts from artifact servers.\n\n\n## Example\nThese examples show examples of locations in Maven POM files where artifact repository upload/download is configured. The first shows the use of HTTP, the second shows the use of HTTPS.\n\n\n```xml\n\n\n\n 4.0.0\n\n com.semmle\n parent\n 1.0\n pom\n\n Security Testing\n An example of insecure download and upload of dependencies\n\n \n \n insecure-releases\n Insecure Repository Releases\n \n http://insecure-repository.example\n \n \n insecure-snapshots\n Insecure Repository Snapshots\n \n http://insecure-repository.example\n \n \n \n \n insecure\n Insecure Repository\n \n http://insecure-repository.example\n \n \n \n \n insecure-plugins\n Insecure Repository Releases\n \n http://insecure-repository.example\n \n \n\n\n```\n\n```xml\n\n\n\n 4.0.0\n\n com.semmle\n parent\n 1.0\n pom\n\n Security Testing\n An example of secure download and upload of dependencies\n\n \n \n insecure-releases\n Secure Repository Releases\n \n https://insecure-repository.example\n \n \n insecure-snapshots\n Secure Repository Snapshots\n \n https://insecure-repository.example\n \n \n \n \n insecure\n Secure Repository\n \n https://insecure-repository.example\n \n \n \n \n insecure-plugins\n Secure Repository Releases\n \n https://insecure-repository.example\n \n \n\n\n```\n\n## References\n* Research: [ Want to take over the Java ecosystem? All you need is a MITM! ](https://medium.com/bugbountywriteup/want-to-take-over-the-java-ecosystem-all-you-need-is-a-mitm-1fc329d898fb?source=friends_link&sk=3c99970c55a899ad9ef41f126efcde0e)\n* Research: [ How to take over the computer of any Java (or Closure or Scala) Developer. ](https://max.computer/blog/how-to-take-over-the-computer-of-any-java-or-clojure-or-scala-developer/)\n* Proof of Concept: [ mveytsman/dilettante ](https://github.com/mveytsman/dilettante)\n* Additional Gradle & Maven plugin: [ Announcing nohttp ](https://spring.io/blog/2019/06/10/announcing-nohttp)\n* Java Ecosystem Announcement: [ HTTP Decommission Artifact Server Announcements ](https://gist.github.com/JLLeitschuh/789e49e3d34092a005031a0a1880af99)\n* Common Weakness Enumeration: [CWE-300](https://cwe.mitre.org/data/definitions/300.html).\n* Common Weakness Enumeration: [CWE-319](https://cwe.mitre.org/data/definitions/319.html).\n* Common Weakness Enumeration: [CWE-494](https://cwe.mitre.org/data/definitions/494.html).\n* Common Weakness Enumeration: [CWE-829](https://cwe.mitre.org/data/definitions/829.html).\n" + }, + "id": "java/maven/non-https-url", + "name": "java/maven/non-https-url", + "properties": { + "security-severity": "8.100000", + "tags": [ + "external/cwe/cwe-300", + "external/cwe/cwe-319", + "external/cwe/cwe-494", + "external/cwe/cwe-829", + "security" + ] + }, + "shortDescription": { + "text": "Failure to use HTTPS or SFTP URL in Maven artifact upload/download" + } + }, + { + "defaultConfiguration": { + "level": "error" + }, + "fullDescription": { + "text": "Failing to check the Json Web Token (JWT) signature may allow an attacker to forge their own tokens." + }, + "help": { + "markdown": "# Missing JWT signature check\nA JSON Web Token (JWT) consists of three parts: header, payload, and signature. The `io.jsonwebtoken.jjwt` library is one of many libraries used for working with JWTs. It offers different methods for parsing tokens like `parse`, `parseClaimsJws`, and `parsePlaintextJws`. The last two correctly verify that the JWT is properly signed. This is done by computing the signature of the combination of header and payload and comparing the locally computed signature with the signature part of the JWT.\n\nTherefore it is necessary to provide the `JwtParser` with a key that is used for signature validation. Unfortunately the `parse` method **accepts** a JWT whose signature is empty although a signing key has been set for the parser. This means that an attacker can create arbitrary JWTs that will be accepted if this method is used.\n\n\n## Recommendation\nAlways verify the signature by using either the `parseClaimsJws` and `parsePlaintextJws` methods or by overriding the `onPlaintextJws` or `onClaimsJws` of `JwtHandlerAdapter`.\n\n\n## Example\nThe following example shows four cases where a signing key is set for a parser. In the first 'BAD' case the `parse` method is used, which will not validate the signature. The second 'BAD' case uses a `JwtHandlerAdapter` where the `onPlaintextJwt` method is overriden, so it will not validate the signature. The third and fourth 'GOOD' cases use `parseClaimsJws` method or override the `onPlaintextJws` method.\n\n\n```java\npublic void badJwt(String token) {\n Jwts.parserBuilder()\n .setSigningKey(\"someBase64EncodedKey\").build()\n .parse(token); // BAD: Does not verify the signature\n}\n\npublic void badJwtHandler(String token) {\n Jwts.parserBuilder()\n .setSigningKey(\"someBase64EncodedKey\").build()\n .parse(plaintextJwt, new JwtHandlerAdapter>() {\n @Override\n public Jwt onPlaintextJwt(Jwt jwt) {\n return jwt;\n }\n }); // BAD: The handler is called on an unverified JWT\n}\n\npublic void goodJwt(String token) {\n Jwts.parserBuilder()\n .setSigningKey(\"someBase64EncodedKey\").build()\n .parseClaimsJws(token) // GOOD: Verify the signature\n .getBody();\n}\n\npublic void goodJwtHandler(String token) {\n Jwts.parserBuilder()\n .setSigningKey(\"someBase64EncodedKey\").build()\n .parse(plaintextJwt, new JwtHandlerAdapter>() {\n @Override\n public Jws onPlaintextJws(Jws jws) {\n return jws;\n }\n }); // GOOD: The handler is called on a verified JWS\n}\n```\n\n## References\n* zofrex: [How I Found An alg=none JWT Vulnerability in the NHS Contact Tracing App](https://www.zofrex.com/blog/2020/10/20/alg-none-jwt-nhs-contact-tracing-app/).\n* Common Weakness Enumeration: [CWE-347](https://cwe.mitre.org/data/definitions/347.html).\n", + "text": "# Missing JWT signature check\nA JSON Web Token (JWT) consists of three parts: header, payload, and signature. The `io.jsonwebtoken.jjwt` library is one of many libraries used for working with JWTs. It offers different methods for parsing tokens like `parse`, `parseClaimsJws`, and `parsePlaintextJws`. The last two correctly verify that the JWT is properly signed. This is done by computing the signature of the combination of header and payload and comparing the locally computed signature with the signature part of the JWT.\n\nTherefore it is necessary to provide the `JwtParser` with a key that is used for signature validation. Unfortunately the `parse` method **accepts** a JWT whose signature is empty although a signing key has been set for the parser. This means that an attacker can create arbitrary JWTs that will be accepted if this method is used.\n\n\n## Recommendation\nAlways verify the signature by using either the `parseClaimsJws` and `parsePlaintextJws` methods or by overriding the `onPlaintextJws` or `onClaimsJws` of `JwtHandlerAdapter`.\n\n\n## Example\nThe following example shows four cases where a signing key is set for a parser. In the first 'BAD' case the `parse` method is used, which will not validate the signature. The second 'BAD' case uses a `JwtHandlerAdapter` where the `onPlaintextJwt` method is overriden, so it will not validate the signature. The third and fourth 'GOOD' cases use `parseClaimsJws` method or override the `onPlaintextJws` method.\n\n\n```java\npublic void badJwt(String token) {\n Jwts.parserBuilder()\n .setSigningKey(\"someBase64EncodedKey\").build()\n .parse(token); // BAD: Does not verify the signature\n}\n\npublic void badJwtHandler(String token) {\n Jwts.parserBuilder()\n .setSigningKey(\"someBase64EncodedKey\").build()\n .parse(plaintextJwt, new JwtHandlerAdapter>() {\n @Override\n public Jwt onPlaintextJwt(Jwt jwt) {\n return jwt;\n }\n }); // BAD: The handler is called on an unverified JWT\n}\n\npublic void goodJwt(String token) {\n Jwts.parserBuilder()\n .setSigningKey(\"someBase64EncodedKey\").build()\n .parseClaimsJws(token) // GOOD: Verify the signature\n .getBody();\n}\n\npublic void goodJwtHandler(String token) {\n Jwts.parserBuilder()\n .setSigningKey(\"someBase64EncodedKey\").build()\n .parse(plaintextJwt, new JwtHandlerAdapter>() {\n @Override\n public Jws onPlaintextJws(Jws jws) {\n return jws;\n }\n }); // GOOD: The handler is called on a verified JWS\n}\n```\n\n## References\n* zofrex: [How I Found An alg=none JWT Vulnerability in the NHS Contact Tracing App](https://www.zofrex.com/blog/2020/10/20/alg-none-jwt-nhs-contact-tracing-app/).\n* Common Weakness Enumeration: [CWE-347](https://cwe.mitre.org/data/definitions/347.html).\n" + }, + "id": "java/missing-jwt-signature-check", + "name": "java/missing-jwt-signature-check", + "properties": { + "security-severity": "7.800000", + "tags": [ + "external/cwe/cwe-347", + "security" + ] + }, + "shortDescription": { + "text": "Missing JWT signature check" + } + }, + { + "defaultConfiguration": { + "level": "error" + }, + "fullDescription": { + "text": "Evaluation of a user-controlled MVEL expression may lead to remote code execution." + }, + "help": { + "markdown": "# Expression language injection (MVEL)\nMVEL is an expression language based on Java-syntax, which offers many features including invocation of methods available in the JVM. If a MVEL expression is built using attacker-controlled data, and then evaluated, then it may allow attackers to run arbitrary code.\n\n\n## Recommendation\nIncluding user input in a MVEL expression should be avoided.\n\n\n## Example\nIn the following sample, the first example uses untrusted data to build a MVEL expression and then runs it in the default context. In the second example, the untrusted data is validated with a custom method that checks that the expression does not contain unexpected code before evaluating it.\n\n\n```java\npublic void evaluate(Socket socket) throws IOException {\n try (BufferedReader reader = new BufferedReader(\n new InputStreamReader(socket.getInputStream()))) {\n \n String expression = reader.readLine();\n // BAD: the user-provided expression is directly evaluated\n MVEL.eval(expression);\n }\n}\n\npublic void safeEvaluate(Socket socket) throws IOException {\n try (BufferedReader reader = new BufferedReader(\n new InputStreamReader(socket.getInputStream()))) {\n \n String expression = reader.readLine();\n // GOOD: the user-provided expression is validated before evaluation\n validateExpression(expression);\n MVEL.eval(expression);\n }\n}\n\nprivate void validateExpression(String expression) {\n // Validate that the expression does not contain unexpected code.\n // For instance, this can be done with allow-lists or deny-lists of code patterns.\n}\n```\n\n## References\n* MVEL Documentation: [Language Guide for 2.0](http://mvel.documentnode.com/).\n* OWASP: [Expression Language Injection](https://owasp.org/www-community/vulnerabilities/Expression_Language_Injection).\n* Common Weakness Enumeration: [CWE-94](https://cwe.mitre.org/data/definitions/94.html).\n", + "text": "# Expression language injection (MVEL)\nMVEL is an expression language based on Java-syntax, which offers many features including invocation of methods available in the JVM. If a MVEL expression is built using attacker-controlled data, and then evaluated, then it may allow attackers to run arbitrary code.\n\n\n## Recommendation\nIncluding user input in a MVEL expression should be avoided.\n\n\n## Example\nIn the following sample, the first example uses untrusted data to build a MVEL expression and then runs it in the default context. In the second example, the untrusted data is validated with a custom method that checks that the expression does not contain unexpected code before evaluating it.\n\n\n```java\npublic void evaluate(Socket socket) throws IOException {\n try (BufferedReader reader = new BufferedReader(\n new InputStreamReader(socket.getInputStream()))) {\n \n String expression = reader.readLine();\n // BAD: the user-provided expression is directly evaluated\n MVEL.eval(expression);\n }\n}\n\npublic void safeEvaluate(Socket socket) throws IOException {\n try (BufferedReader reader = new BufferedReader(\n new InputStreamReader(socket.getInputStream()))) {\n \n String expression = reader.readLine();\n // GOOD: the user-provided expression is validated before evaluation\n validateExpression(expression);\n MVEL.eval(expression);\n }\n}\n\nprivate void validateExpression(String expression) {\n // Validate that the expression does not contain unexpected code.\n // For instance, this can be done with allow-lists or deny-lists of code patterns.\n}\n```\n\n## References\n* MVEL Documentation: [Language Guide for 2.0](http://mvel.documentnode.com/).\n* OWASP: [Expression Language Injection](https://owasp.org/www-community/vulnerabilities/Expression_Language_Injection).\n* Common Weakness Enumeration: [CWE-94](https://cwe.mitre.org/data/definitions/94.html).\n" + }, + "id": "java/mvel-expression-injection", + "name": "java/mvel-expression-injection", + "properties": { + "tags": [ + "external/cwe/cwe-094", + "security" + ] + }, + "shortDescription": { + "text": "Expression language injection (MVEL)" + } + }, + { + "defaultConfiguration": { + "level": "error" + }, + "fullDescription": { + "text": "Disabling HTTP header validation makes code vulnerable to attack by header splitting if user input is written directly to an HTTP header." + }, + "help": { + "markdown": "# Disabled Netty HTTP header validation\nDirectly writing user input (for example, an HTTP request parameter) to an HTTP header can lead to an HTTP response-splitting vulnerability. If the user input includes blank lines in it, and if the servlet container does not itself escape the blank lines, then a remote user can cause the response to turn into two separate responses, one of which is controlled by the remote user.\n\n\n## Recommendation\nGuard against HTTP header splitting in the same way as guarding against cross-site scripting. Before passing any data into HTTP headers, either check the data for special characters, or escape any special characters that are present.\n\n\n## Example\nThe following example shows the 'name' parameter being written to a cookie in two different ways. The first way writes it directly to the cookie, and thus is vulnerable to response-splitting attacks. The second way first removes all special characters, thus avoiding the potential problem.\n\n\n```java\npublic class ResponseSplitting extends HttpServlet {\n\tprotected void doGet(HttpServletRequest request, HttpServletResponse response)\n\tthrows ServletException, IOException {\n\t\t// BAD: setting a cookie with an unvalidated parameter\n\t\tCookie cookie = new Cookie(\"name\", request.getParameter(\"name\"));\n\t\tresponse.addCookie(cookie);\n\n\t\t// GOOD: remove special characters before putting them in the header\n\t\tString name = removeSpecial(request.getParameter(\"name\"));\n\t\tCookie cookie2 = new Cookie(\"name\", name);\n\t\tresponse.addCookie(cookie2);\n\t}\n\n\tprivate static String removeSpecial(String str) {\n\t\treturn str.replaceAll(\"[^a-zA-Z ]\", \"\");\n\t}\n}\n\n```\n\n## Example\nThe following example shows the use of the library 'netty' with HTTP response-splitting verification configurations. The second way will verify the parameters before using them to build the HTTP response.\n\n\n```java\nimport io.netty.handler.codec.http.DefaultHttpHeaders;\n\npublic class ResponseSplitting {\n // BAD: Disables the internal response splitting verification\n private final DefaultHttpHeaders badHeaders = new DefaultHttpHeaders(false);\n\n // GOOD: Verifies headers passed don't contain CRLF characters\n private final DefaultHttpHeaders goodHeaders = new DefaultHttpHeaders();\n\n // BAD: Disables the internal response splitting verification\n private final DefaultHttpResponse badResponse = new DefaultHttpResponse(version, httpResponseStatus, false);\n\n // GOOD: Verifies headers passed don't contain CRLF characters\n private final DefaultHttpResponse goodResponse = new DefaultHttpResponse(version, httpResponseStatus);\n}\n\n```\n\n## References\n* InfosecWriters: [HTTP response splitting](http://www.infosecwriters.com/Papers/DCrab_HTTP_Response.pdf).\n* OWASP: [HTTP Response Splitting](https://www.owasp.org/index.php/HTTP_Response_Splitting).\n* Wikipedia: [HTTP response splitting](http://en.wikipedia.org/wiki/HTTP_response_splitting).\n* Common Weakness Enumeration: [CWE-113](https://cwe.mitre.org/data/definitions/113.html).\n", + "text": "# Disabled Netty HTTP header validation\nDirectly writing user input (for example, an HTTP request parameter) to an HTTP header can lead to an HTTP response-splitting vulnerability. If the user input includes blank lines in it, and if the servlet container does not itself escape the blank lines, then a remote user can cause the response to turn into two separate responses, one of which is controlled by the remote user.\n\n\n## Recommendation\nGuard against HTTP header splitting in the same way as guarding against cross-site scripting. Before passing any data into HTTP headers, either check the data for special characters, or escape any special characters that are present.\n\n\n## Example\nThe following example shows the 'name' parameter being written to a cookie in two different ways. The first way writes it directly to the cookie, and thus is vulnerable to response-splitting attacks. The second way first removes all special characters, thus avoiding the potential problem.\n\n\n```java\npublic class ResponseSplitting extends HttpServlet {\n\tprotected void doGet(HttpServletRequest request, HttpServletResponse response)\n\tthrows ServletException, IOException {\n\t\t// BAD: setting a cookie with an unvalidated parameter\n\t\tCookie cookie = new Cookie(\"name\", request.getParameter(\"name\"));\n\t\tresponse.addCookie(cookie);\n\n\t\t// GOOD: remove special characters before putting them in the header\n\t\tString name = removeSpecial(request.getParameter(\"name\"));\n\t\tCookie cookie2 = new Cookie(\"name\", name);\n\t\tresponse.addCookie(cookie2);\n\t}\n\n\tprivate static String removeSpecial(String str) {\n\t\treturn str.replaceAll(\"[^a-zA-Z ]\", \"\");\n\t}\n}\n\n```\n\n## Example\nThe following example shows the use of the library 'netty' with HTTP response-splitting verification configurations. The second way will verify the parameters before using them to build the HTTP response.\n\n\n```java\nimport io.netty.handler.codec.http.DefaultHttpHeaders;\n\npublic class ResponseSplitting {\n // BAD: Disables the internal response splitting verification\n private final DefaultHttpHeaders badHeaders = new DefaultHttpHeaders(false);\n\n // GOOD: Verifies headers passed don't contain CRLF characters\n private final DefaultHttpHeaders goodHeaders = new DefaultHttpHeaders();\n\n // BAD: Disables the internal response splitting verification\n private final DefaultHttpResponse badResponse = new DefaultHttpResponse(version, httpResponseStatus, false);\n\n // GOOD: Verifies headers passed don't contain CRLF characters\n private final DefaultHttpResponse goodResponse = new DefaultHttpResponse(version, httpResponseStatus);\n}\n\n```\n\n## References\n* InfosecWriters: [HTTP response splitting](http://www.infosecwriters.com/Papers/DCrab_HTTP_Response.pdf).\n* OWASP: [HTTP Response Splitting](https://www.owasp.org/index.php/HTTP_Response_Splitting).\n* Wikipedia: [HTTP response splitting](http://en.wikipedia.org/wiki/HTTP_response_splitting).\n* Common Weakness Enumeration: [CWE-113](https://cwe.mitre.org/data/definitions/113.html).\n" + }, + "id": "java/netty-http-response-splitting", + "name": "java/netty-http-response-splitting", + "properties": { + "security-severity": "6.100000", + "tags": [ + "external/cwe/cwe-113", + "security" + ] + }, + "shortDescription": { + "text": "Disabled Netty HTTP header validation" + } + }, + { + "defaultConfiguration": { + "level": "error" + }, + "fullDescription": { + "text": "Evaluation of OGNL Expression Language statement with user-controlled input can lead to execution of arbitrary code." + }, + "help": { + "markdown": "# OGNL Expression Language statement with user-controlled input\nObject-Graph Navigation Language (OGNL) is an open-source Expression Language (EL) for Java. OGNL can create or change executable code, consequently it can introduce critical security flaws to any application that uses it. Evaluation of unvalidated expressions is a common flaw in OGNL. This exposes the properties of Java objects to modification by an attacker and may allow them to execute arbitrary code.\n\n\n## Recommendation\nThe general recommendation is to avoid evaluating untrusted ONGL expressions. If user-provided OGNL expressions must be evaluated, do this in a sandbox and validate the expressions before evaluation.\n\n\n## Example\nIn the following examples, the code accepts an OGNL expression from the user and evaluates it.\n\nIn the first example, the user-provided OGNL expression is parsed and evaluated.\n\nThe second example validates the expression and evaluates it inside a sandbox. You can add a sandbox by setting a system property, as shown in the example, or by adding `-Dognl.security.manager` to JVM arguments.\n\n\n```java\nimport ognl.Ognl;\nimport ognl.OgnlException;\n\npublic void evaluate(HttpServletRequest request, Object root) throws OgnlException {\n String expression = request.getParameter(\"expression\");\n\n // BAD: User provided expression is evaluated\n Ognl.getValue(expression, root);\n \n // GOOD: The name is validated and expression is evaluated in sandbox\n System.setProperty(\"ognl.security.manager\", \"\"); // Or add -Dognl.security.manager to JVM args\n if (isValid(expression)) {\n Ognl.getValue(expression, root);\n } else {\n // Reject the request\n }\n}\n\npublic void isValid(Strig expression) {\n // Custom method to validate the expression.\n // For instance, make sure it doesn't include unexpected code.\n}\n\n```\n\n## References\n* Apache Commons: [Apache Commons OGNL](https://commons.apache.org/proper/commons-ognl/).\n* Struts security: [Proactively protect from OGNL Expression Injections attacks](https://struts.apache.org/security/#proactively-protect-from-ognl-expression-injections-attacks-if-easily-applicable).\n* Common Weakness Enumeration: [CWE-917](https://cwe.mitre.org/data/definitions/917.html).\n", + "text": "# OGNL Expression Language statement with user-controlled input\nObject-Graph Navigation Language (OGNL) is an open-source Expression Language (EL) for Java. OGNL can create or change executable code, consequently it can introduce critical security flaws to any application that uses it. Evaluation of unvalidated expressions is a common flaw in OGNL. This exposes the properties of Java objects to modification by an attacker and may allow them to execute arbitrary code.\n\n\n## Recommendation\nThe general recommendation is to avoid evaluating untrusted ONGL expressions. If user-provided OGNL expressions must be evaluated, do this in a sandbox and validate the expressions before evaluation.\n\n\n## Example\nIn the following examples, the code accepts an OGNL expression from the user and evaluates it.\n\nIn the first example, the user-provided OGNL expression is parsed and evaluated.\n\nThe second example validates the expression and evaluates it inside a sandbox. You can add a sandbox by setting a system property, as shown in the example, or by adding `-Dognl.security.manager` to JVM arguments.\n\n\n```java\nimport ognl.Ognl;\nimport ognl.OgnlException;\n\npublic void evaluate(HttpServletRequest request, Object root) throws OgnlException {\n String expression = request.getParameter(\"expression\");\n\n // BAD: User provided expression is evaluated\n Ognl.getValue(expression, root);\n \n // GOOD: The name is validated and expression is evaluated in sandbox\n System.setProperty(\"ognl.security.manager\", \"\"); // Or add -Dognl.security.manager to JVM args\n if (isValid(expression)) {\n Ognl.getValue(expression, root);\n } else {\n // Reject the request\n }\n}\n\npublic void isValid(Strig expression) {\n // Custom method to validate the expression.\n // For instance, make sure it doesn't include unexpected code.\n}\n\n```\n\n## References\n* Apache Commons: [Apache Commons OGNL](https://commons.apache.org/proper/commons-ognl/).\n* Struts security: [Proactively protect from OGNL Expression Injections attacks](https://struts.apache.org/security/#proactively-protect-from-ognl-expression-injections-attacks-if-easily-applicable).\n* Common Weakness Enumeration: [CWE-917](https://cwe.mitre.org/data/definitions/917.html).\n" + }, + "id": "java/ognl-injection", + "name": "java/ognl-injection", + "properties": { + "tags": [ + "external/cwe/cwe-917", + "security" + ] + }, + "shortDescription": { + "text": "OGNL Expression Language statement with user-controlled input" + } + }, + { + "defaultConfiguration": { + "level": "error" + }, + "fullDescription": { + "text": "Accessing paths influenced by users can allow an attacker to access unexpected resources." + }, + "help": { + "markdown": "# Uncontrolled data used in path expression\nAccessing paths controlled by users can allow an attacker to access unexpected resources. This can result in sensitive information being revealed or deleted, or an attacker being able to influence behavior by modifying unexpected files.\n\nPaths that are naively constructed from data controlled by a user may contain unexpected special characters, such as \"..\". Such a path may potentially point to any directory on the file system.\n\n\n## Recommendation\nValidate user input before using it to construct a file path. Ideally, follow these rules:\n\n* Do not allow more than a single \".\" character.\n* Do not allow directory separators such as \"/\" or \"\\\\\" (depending on the file system).\n* Do not rely on simply replacing problematic sequences such as \"../\". For example, after applying this filter to \".../...//\" the resulting string would still be \"../\".\n* Ideally use a whitelist of known good patterns.\n\n## Example\nIn this example, a file name is read from a `java.net.Socket` and then used to access a file in the user's home directory and send it back over the socket. However, a malicious user could enter a file name which contains special characters. For example, the string \"../../etc/passwd\" will result in the code reading the file located at \"/home/\\[user\\]/../../etc/passwd\", which is the system's password file. This file would then be sent back to the user, giving them access to all the system's passwords.\n\n\n```java\npublic void sendUserFile(Socket sock, String user) {\n\tBufferedReader filenameReader = new BufferedReader(\n\t\t\tnew InputStreamReader(sock.getInputStream(), \"UTF-8\"));\n\tString filename = filenameReader.readLine();\n\t// BAD: read from a file using a path controlled by the user\n\tBufferedReader fileReader = new BufferedReader(\n\t\t\tnew FileReader(\"/home/\" + user + \"/\" + filename));\n\tString fileLine = fileReader.readLine();\n\twhile(fileLine != null) {\n\t\tsock.getOutputStream().write(fileLine.getBytes());\n\t\tfileLine = fileReader.readLine();\n\t}\n}\n\npublic void sendUserFileFixed(Socket sock, String user) {\n\t// ...\n\t\n\t// GOOD: remove all dots and directory delimiters from the filename before using\n\tString filename = filenameReader.readLine().replaceAll(\"\\.\", \"\").replaceAll(\"/\", \"\");\n\tBufferedReader fileReader = new BufferedReader(\n\t\t\tnew FileReader(\"/home/\" + user + \"/\" + filename));\n\n\t// ...\n}\n```\n\n## References\n* OWASP: [Path Traversal](https://owasp.org/www-community/attacks/Path_Traversal).\n* Common Weakness Enumeration: [CWE-22](https://cwe.mitre.org/data/definitions/22.html).\n* Common Weakness Enumeration: [CWE-23](https://cwe.mitre.org/data/definitions/23.html).\n* Common Weakness Enumeration: [CWE-36](https://cwe.mitre.org/data/definitions/36.html).\n* Common Weakness Enumeration: [CWE-73](https://cwe.mitre.org/data/definitions/73.html).\n", + "text": "# Uncontrolled data used in path expression\nAccessing paths controlled by users can allow an attacker to access unexpected resources. This can result in sensitive information being revealed or deleted, or an attacker being able to influence behavior by modifying unexpected files.\n\nPaths that are naively constructed from data controlled by a user may contain unexpected special characters, such as \"..\". Such a path may potentially point to any directory on the file system.\n\n\n## Recommendation\nValidate user input before using it to construct a file path. Ideally, follow these rules:\n\n* Do not allow more than a single \".\" character.\n* Do not allow directory separators such as \"/\" or \"\\\\\" (depending on the file system).\n* Do not rely on simply replacing problematic sequences such as \"../\". For example, after applying this filter to \".../...//\" the resulting string would still be \"../\".\n* Ideally use a whitelist of known good patterns.\n\n## Example\nIn this example, a file name is read from a `java.net.Socket` and then used to access a file in the user's home directory and send it back over the socket. However, a malicious user could enter a file name which contains special characters. For example, the string \"../../etc/passwd\" will result in the code reading the file located at \"/home/\\[user\\]/../../etc/passwd\", which is the system's password file. This file would then be sent back to the user, giving them access to all the system's passwords.\n\n\n```java\npublic void sendUserFile(Socket sock, String user) {\n\tBufferedReader filenameReader = new BufferedReader(\n\t\t\tnew InputStreamReader(sock.getInputStream(), \"UTF-8\"));\n\tString filename = filenameReader.readLine();\n\t// BAD: read from a file using a path controlled by the user\n\tBufferedReader fileReader = new BufferedReader(\n\t\t\tnew FileReader(\"/home/\" + user + \"/\" + filename));\n\tString fileLine = fileReader.readLine();\n\twhile(fileLine != null) {\n\t\tsock.getOutputStream().write(fileLine.getBytes());\n\t\tfileLine = fileReader.readLine();\n\t}\n}\n\npublic void sendUserFileFixed(Socket sock, String user) {\n\t// ...\n\t\n\t// GOOD: remove all dots and directory delimiters from the filename before using\n\tString filename = filenameReader.readLine().replaceAll(\"\\.\", \"\").replaceAll(\"/\", \"\");\n\tBufferedReader fileReader = new BufferedReader(\n\t\t\tnew FileReader(\"/home/\" + user + \"/\" + filename));\n\n\t// ...\n}\n```\n\n## References\n* OWASP: [Path Traversal](https://owasp.org/www-community/attacks/Path_Traversal).\n* Common Weakness Enumeration: [CWE-22](https://cwe.mitre.org/data/definitions/22.html).\n* Common Weakness Enumeration: [CWE-23](https://cwe.mitre.org/data/definitions/23.html).\n* Common Weakness Enumeration: [CWE-36](https://cwe.mitre.org/data/definitions/36.html).\n* Common Weakness Enumeration: [CWE-73](https://cwe.mitre.org/data/definitions/73.html).\n" + }, + "id": "java/path-injection", + "name": "java/path-injection", + "properties": { + "security-severity": "7.500000", + "tags": [ + "external/cwe/cwe-022", + "external/cwe/cwe-023", + "external/cwe/cwe-036", + "external/cwe/cwe-073", + "security" + ] + }, + "shortDescription": { + "text": "Uncontrolled data used in path expression" + } + }, + { + "defaultConfiguration": { + "level": "error" + }, + "fullDescription": { + "text": "Using a predictable seed in a pseudo-random number generator can lead to predictability of the numbers generated by it." + }, + "help": { + "markdown": "# Use of a predictable seed in a secure random number generator\nUsing a predictable seed in a pseudo-random number generator can lead to predictability of the numbers generated by it.\n\n\n## Recommendation\nIf the predictability of the pseudo-random number generator does not matter then consider using the faster `Random` class from `java.util`. If it is important that the pseudo-random number generator produces completely unpredictable values then either let the generator securely seed itself by not specifying a seed or specify a randomly generated, unpredictable seed.\n\n\n## Example\nIn the first example shown here, a constant value is used as a seed. Depending on the implementation of ` SecureRandom`, this could lead to the same random number being generated each time the code is executed.\n\nIn the second example shown here, the system time is used as a seed. Depending on the implementation of ` SecureRandom`, if an attacker knows what time the code was run, they could predict the generated random number.\n\nIn the third example shown here, the random number generator is allowed to generate its own seed, which it will do in a secure way.\n\n\n```java\nSecureRandom prng = new SecureRandom();\nint randomData = 0;\n\n// BAD: Using a constant value as a seed for a random number generator means all numbers it generates are predictable.\nprng.setSeed(12345L);\nrandomData = prng.next(32);\n\n// BAD: System.currentTimeMillis() returns the system time which is predictable.\nprng.setSeed(System.currentTimeMillis());\nrandomData = prng.next(32);\n\n// GOOD: SecureRandom implementations seed themselves securely by default.\nprng = new SecureRandom();\nrandomData = prng.next(32);\n\n```\n\n## References\n* Common Weakness Enumeration: [CWE-335](https://cwe.mitre.org/data/definitions/335.html).\n", + "text": "# Use of a predictable seed in a secure random number generator\nUsing a predictable seed in a pseudo-random number generator can lead to predictability of the numbers generated by it.\n\n\n## Recommendation\nIf the predictability of the pseudo-random number generator does not matter then consider using the faster `Random` class from `java.util`. If it is important that the pseudo-random number generator produces completely unpredictable values then either let the generator securely seed itself by not specifying a seed or specify a randomly generated, unpredictable seed.\n\n\n## Example\nIn the first example shown here, a constant value is used as a seed. Depending on the implementation of ` SecureRandom`, this could lead to the same random number being generated each time the code is executed.\n\nIn the second example shown here, the system time is used as a seed. Depending on the implementation of ` SecureRandom`, if an attacker knows what time the code was run, they could predict the generated random number.\n\nIn the third example shown here, the random number generator is allowed to generate its own seed, which it will do in a secure way.\n\n\n```java\nSecureRandom prng = new SecureRandom();\nint randomData = 0;\n\n// BAD: Using a constant value as a seed for a random number generator means all numbers it generates are predictable.\nprng.setSeed(12345L);\nrandomData = prng.next(32);\n\n// BAD: System.currentTimeMillis() returns the system time which is predictable.\nprng.setSeed(System.currentTimeMillis());\nrandomData = prng.next(32);\n\n// GOOD: SecureRandom implementations seed themselves securely by default.\nprng = new SecureRandom();\nrandomData = prng.next(32);\n\n```\n\n## References\n* Common Weakness Enumeration: [CWE-335](https://cwe.mitre.org/data/definitions/335.html).\n" + }, + "id": "java/predictable-seed", + "name": "java/predictable-seed", + "properties": { + "security-severity": "9.800000", + "tags": [ + "external/cwe/cwe-335", + "security" + ] + }, + "shortDescription": { + "text": "Use of a predictable seed in a secure random number generator" + } + }, + { + "defaultConfiguration": { + "level": "error" + }, + "fullDescription": { + "text": "Evaluation of a user-controlled Spring Expression Language (SpEL) expression may lead to remote code execution." + }, + "help": { + "markdown": "# Expression language injection (Spring)\nThe Spring Expression Language (SpEL) is a powerful expression language provided by the Spring Framework. The language offers many features including invocation of methods available in the JVM. If a SpEL expression is built using attacker-controlled data, and then evaluated in a powerful context, then it may allow the attacker to run arbitrary code.\n\nThe `SpelExpressionParser` class parses a SpEL expression string and returns an `Expression` instance that can be then evaluated by calling one of its methods. By default, an expression is evaluated in a powerful `StandardEvaluationContext` that allows the expression to access other methods available in the JVM.\n\n\n## Recommendation\nIn general, including user input in a SpEL expression should be avoided. If user input must be included in the expression, it should be then evaluated in a limited context that doesn't allow arbitrary method invocation.\n\n\n## Example\nThe following example uses untrusted data to build a SpEL expression and then runs it in the default powerful context.\n\n\n```java\npublic Object evaluate(Socket socket) throws IOException {\n try (BufferedReader reader = new BufferedReader(\n new InputStreamReader(socket.getInputStream()))) {\n\n String string = reader.readLine();\n ExpressionParser parser = new SpelExpressionParser();\n Expression expression = parser.parseExpression(string);\n return expression.getValue();\n }\n}\n```\nThe next example shows how an untrusted SpEL expression can be run in `SimpleEvaluationContext` that doesn't allow accessing arbitrary methods. However, it's recommended to avoid using untrusted input in SpEL expressions.\n\n\n```java\npublic Object evaluate(Socket socket) throws IOException {\n try (BufferedReader reader = new BufferedReader(\n new InputStreamReader(socket.getInputStream()))) {\n\n String string = reader.readLine();\n ExpressionParser parser = new SpelExpressionParser();\n Expression expression = parser.parseExpression(string);\n SimpleEvaluationContext context \n = SimpleEvaluationContext.forReadWriteDataBinding().build();\n return expression.getValue(context);\n }\n}\n```\n\n## References\n* Spring Framework Reference Documentation: [Spring Expression Language (SpEL)](https://docs.spring.io/spring/docs/4.2.x/spring-framework-reference/html/expressions.html).\n* OWASP: [Expression Language Injection](https://owasp.org/www-community/vulnerabilities/Expression_Language_Injection).\n* Common Weakness Enumeration: [CWE-94](https://cwe.mitre.org/data/definitions/94.html).\n", + "text": "# Expression language injection (Spring)\nThe Spring Expression Language (SpEL) is a powerful expression language provided by the Spring Framework. The language offers many features including invocation of methods available in the JVM. If a SpEL expression is built using attacker-controlled data, and then evaluated in a powerful context, then it may allow the attacker to run arbitrary code.\n\nThe `SpelExpressionParser` class parses a SpEL expression string and returns an `Expression` instance that can be then evaluated by calling one of its methods. By default, an expression is evaluated in a powerful `StandardEvaluationContext` that allows the expression to access other methods available in the JVM.\n\n\n## Recommendation\nIn general, including user input in a SpEL expression should be avoided. If user input must be included in the expression, it should be then evaluated in a limited context that doesn't allow arbitrary method invocation.\n\n\n## Example\nThe following example uses untrusted data to build a SpEL expression and then runs it in the default powerful context.\n\n\n```java\npublic Object evaluate(Socket socket) throws IOException {\n try (BufferedReader reader = new BufferedReader(\n new InputStreamReader(socket.getInputStream()))) {\n\n String string = reader.readLine();\n ExpressionParser parser = new SpelExpressionParser();\n Expression expression = parser.parseExpression(string);\n return expression.getValue();\n }\n}\n```\nThe next example shows how an untrusted SpEL expression can be run in `SimpleEvaluationContext` that doesn't allow accessing arbitrary methods. However, it's recommended to avoid using untrusted input in SpEL expressions.\n\n\n```java\npublic Object evaluate(Socket socket) throws IOException {\n try (BufferedReader reader = new BufferedReader(\n new InputStreamReader(socket.getInputStream()))) {\n\n String string = reader.readLine();\n ExpressionParser parser = new SpelExpressionParser();\n Expression expression = parser.parseExpression(string);\n SimpleEvaluationContext context \n = SimpleEvaluationContext.forReadWriteDataBinding().build();\n return expression.getValue(context);\n }\n}\n```\n\n## References\n* Spring Framework Reference Documentation: [Spring Expression Language (SpEL)](https://docs.spring.io/spring/docs/4.2.x/spring-framework-reference/html/expressions.html).\n* OWASP: [Expression Language Injection](https://owasp.org/www-community/vulnerabilities/Expression_Language_Injection).\n* Common Weakness Enumeration: [CWE-94](https://cwe.mitre.org/data/definitions/94.html).\n" + }, + "id": "java/spel-expression-injection", + "name": "java/spel-expression-injection", + "properties": { + "tags": [ + "external/cwe/cwe-094", + "security" + ] + }, + "shortDescription": { + "text": "Expression language injection (Spring)" + } + }, + { + "defaultConfiguration": { + "level": "error" + }, + "fullDescription": { + "text": "Disabling CSRF protection makes the application vulnerable to a Cross-Site Request Forgery (CSRF) attack." + }, + "help": { + "markdown": "# Disabled Spring CSRF protection\nWhen you set up a web server to receive a request from a client without any mechanism for verifying that it was intentionally sent, then it is vulnerable to attack. An attacker can trick a client into making an unintended request to the web server that will be treated as an authentic request. This can be done via a URL, image load, XMLHttpRequest, etc. and can result in exposure of data or unintended code execution.\n\n\n## Recommendation\nWhen you use Spring, Cross-Site Request Forgery (CSRF) protection is enabled by default. Spring's recommendation is to use CSRF protection for any request that could be processed by a browser client by normal users.\n\n\n## Example\nThe following example shows the Spring Java configuration with CSRF protection disabled. This type of configuration should only be used if you are creating a service that is used only by non-browser clients.\n\n\n```java\nimport org.springframework.context.annotation.Configuration;\nimport org.springframework.security.config.annotation.web.builders.HttpSecurity;\nimport org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;\nimport org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;\n\n@EnableWebSecurity\n@Configuration\npublic class WebSecurityConfig extends WebSecurityConfigurerAdapter {\n @Override\n protected void configure(HttpSecurity http) throws Exception {\n http\n .csrf(csrf ->\n // BAD - CSRF protection shouldn't be disabled\n csrf.disable() \n );\n }\n}\n\n```\n\n## References\n* OWASP: [Cross-Site Request Forgery (CSRF)](https://www.owasp.org/index.php/Cross-Site_Request_Forgery_(CSRF)).\n* Spring Security Reference: [ Cross Site Request Forgery (CSRF) for Servlet Environments ](https://docs.spring.io/spring-security/site/docs/current/reference/html5/#servlet-csrf).\n* Common Weakness Enumeration: [CWE-352](https://cwe.mitre.org/data/definitions/352.html).\n", + "text": "# Disabled Spring CSRF protection\nWhen you set up a web server to receive a request from a client without any mechanism for verifying that it was intentionally sent, then it is vulnerable to attack. An attacker can trick a client into making an unintended request to the web server that will be treated as an authentic request. This can be done via a URL, image load, XMLHttpRequest, etc. and can result in exposure of data or unintended code execution.\n\n\n## Recommendation\nWhen you use Spring, Cross-Site Request Forgery (CSRF) protection is enabled by default. Spring's recommendation is to use CSRF protection for any request that could be processed by a browser client by normal users.\n\n\n## Example\nThe following example shows the Spring Java configuration with CSRF protection disabled. This type of configuration should only be used if you are creating a service that is used only by non-browser clients.\n\n\n```java\nimport org.springframework.context.annotation.Configuration;\nimport org.springframework.security.config.annotation.web.builders.HttpSecurity;\nimport org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;\nimport org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;\n\n@EnableWebSecurity\n@Configuration\npublic class WebSecurityConfig extends WebSecurityConfigurerAdapter {\n @Override\n protected void configure(HttpSecurity http) throws Exception {\n http\n .csrf(csrf ->\n // BAD - CSRF protection shouldn't be disabled\n csrf.disable() \n );\n }\n}\n\n```\n\n## References\n* OWASP: [Cross-Site Request Forgery (CSRF)](https://www.owasp.org/index.php/Cross-Site_Request_Forgery_(CSRF)).\n* Spring Security Reference: [ Cross Site Request Forgery (CSRF) for Servlet Environments ](https://docs.spring.io/spring-security/site/docs/current/reference/html5/#servlet-csrf).\n* Common Weakness Enumeration: [CWE-352](https://cwe.mitre.org/data/definitions/352.html).\n" + }, + "id": "java/spring-disabled-csrf-protection", + "name": "java/spring-disabled-csrf-protection", + "properties": { + "security-severity": "8.800000", + "tags": [ + "external/cwe/cwe-352", + "security" + ] + }, + "shortDescription": { + "text": "Disabled Spring CSRF protection" + } + }, + { + "defaultConfiguration": { + "level": "error" + }, + "fullDescription": { + "text": "Building a SQL or Java Persistence query from user-controlled sources is vulnerable to insertion of malicious code by the user." + }, + "help": { + "markdown": "# Query built from user-controlled sources\nIf a database query is built using string concatenation, and the components of the concatenation include user input, a user is likely to be able to run malicious database queries. This applies to various database query languages, including SQL and the Java Persistence Query Language.\n\n\n## Recommendation\nUsually, it is better to use a SQL prepared statement than to build a complete SQL query with string concatenation. A prepared statement can include a wildcard, written as a question mark (?), for each part of the SQL query that is expected to be filled in by a different value each time it is run. When the query is later executed, a value must be supplied for each wildcard in the query.\n\nIn the Java Persistence Query Language, it is better to use queries with parameters than to build a complete query with string concatenation. A Java Persistence query can include a parameter placeholder for each part of the query that is expected to be filled in by a different value when run. A parameter placeholder may be indicated by a colon (:) followed by a parameter name, or by a question mark (?) followed by an integer position. When the query is later executed, a value must be supplied for each parameter in the query, using the `setParameter` method. Specifying the query using the `@NamedQuery` annotation introduces an additional level of safety: the query must be a constant string literal, preventing construction by string concatenation, and the only way to fill in values for parts of the query is by setting positional parameters.\n\nIt is good practice to use prepared statements (in SQL) or query parameters (in the Java Persistence Query Language) for supplying parameter values to a query, whether or not any of the parameters are directly traceable to user input. Doing so avoids any need to worry about quoting and escaping.\n\n\n## Example\nIn the following example, the code runs a simple SQL query in two different ways.\n\nThe first way involves building a query, `query1`, by concatenating an environment variable with some string literals. The environment variable can include special characters, so this code allows for SQL injection attacks.\n\nThe second way, which shows good practice, involves building a query, `query2`, with a single string literal that includes a wildcard (`?`). The wildcard is then given a value by calling `setString`. This version is immune to injection attacks, because any special characters in the environment variable are not given any special treatment.\n\n\n```java\n{\n // BAD: the category might have SQL special characters in it\n String category = System.getenv(\"ITEM_CATEGORY\");\n Statement statement = connection.createStatement();\n String query1 = \"SELECT ITEM,PRICE FROM PRODUCT WHERE ITEM_CATEGORY='\"\n + category + \"' ORDER BY PRICE\";\n ResultSet results = statement.executeQuery(query1);\n}\n\n{\n // GOOD: use a prepared query\n String category = System.getenv(\"ITEM_CATEGORY\");\n String query2 = \"SELECT ITEM,PRICE FROM PRODUCT WHERE ITEM_CATEGORY=? ORDER BY PRICE\";\n PreparedStatement statement = connection.prepareStatement(query2);\n statement.setString(1, category);\n ResultSet results = statement.executeQuery();\n}\n```\n\n## Example\nThe following code shows several different ways to run a Java Persistence query.\n\nThe first example involves building a query, `query1`, by concatenating an environment variable with some string literals. Just like the SQL example, the environment variable can include special characters, so this code allows for Java Persistence query injection attacks.\n\nThe remaining examples demonstrate different methods for safely building a Java Persistence query with user-supplied values:\n\n1. `query2` uses a single string literal that includes a placeholder for a parameter, indicated by a colon (`:`) and parameter name (`category`).\n1. `query3` uses a single string literal that includes a placeholder for a parameter, indicated by a question mark (`?`) and position number (`1`).\n1. `namedQuery1` is defined using the `@NamedQuery` annotation, whose `query` attribute is a string literal that includes a placeholder for a parameter, indicated by a colon (`:`) and parameter name (`category`).\n1. `namedQuery2` is defined using the `@NamedQuery` annotation, whose `query` attribute includes a placeholder for a parameter, indicated by a question mark (`?`) and position number (`1`).\nThe parameter is then given a value by calling `setParameter`. These versions are immune to injection attacks, because any special characters in the environment variable or user-supplied value are not given any special treatment.\n\n\n```java\n{\n // BAD: the category might have Java Persistence Query Language special characters in it\n String category = System.getenv(\"ITEM_CATEGORY\");\n Statement statement = connection.createStatement();\n String query1 = \"SELECT p FROM Product p WHERE p.category LIKE '\"\n + category + \"' ORDER BY p.price\";\n Query q = entityManager.createQuery(query1);\n}\n\n{\n // GOOD: use a named parameter and set its value\n String category = System.getenv(\"ITEM_CATEGORY\");\n String query2 = \"SELECT p FROM Product p WHERE p.category LIKE :category ORDER BY p.price\"\n Query q = entityManager.createQuery(query2);\n q.setParameter(\"category\", category);\n}\n\n{\n // GOOD: use a positional parameter and set its value\n String category = System.getenv(\"ITEM_CATEGORY\");\n String query3 = \"SELECT p FROM Product p WHERE p.category LIKE ?1 ORDER BY p.price\"\n Query q = entityManager.createQuery(query3);\n q.setParameter(1, category);\n}\n\n{\n // GOOD: use a named query with a named parameter and set its value\n @NamedQuery(\n name=\"lookupByCategory\",\n query=\"SELECT p FROM Product p WHERE p.category LIKE :category ORDER BY p.price\")\n private static class NQ {}\n ...\n String category = System.getenv(\"ITEM_CATEGORY\");\n Query namedQuery1 = entityManager.createNamedQuery(\"lookupByCategory\");\n namedQuery1.setParameter(\"category\", category);\n}\n\n{\n // GOOD: use a named query with a positional parameter and set its value\n @NamedQuery(\n name=\"lookupByCategory\",\n query=\"SELECT p FROM Product p WHERE p.category LIKE ?1 ORDER BY p.price\")\n private static class NQ {}\n ...\n String category = System.getenv(\"ITEM_CATEGORY\");\n Query namedQuery2 = entityManager.createNamedQuery(\"lookupByCategory\");\n namedQuery2.setParameter(1, category);\n}\n```\n\n## References\n* OWASP: [SQL Injection Prevention Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/SQL_Injection_Prevention_Cheat_Sheet.html).\n* SEI CERT Oracle Coding Standard for Java: [IDS00-J. Prevent SQL injection](https://wiki.sei.cmu.edu/confluence/display/java/IDS00-J.+Prevent+SQL+injection).\n* The Java Tutorials: [Using Prepared Statements](https://docs.oracle.com/javase/tutorial/jdbc/basics/prepared.html).\n* The Java EE Tutorial: [The Java Persistence Query Language](https://docs.oracle.com/javaee/7/tutorial/persistence-querylanguage.htm).\n* Common Weakness Enumeration: [CWE-89](https://cwe.mitre.org/data/definitions/89.html).\n* Common Weakness Enumeration: [CWE-564](https://cwe.mitre.org/data/definitions/564.html).\n", + "text": "# Query built from user-controlled sources\nIf a database query is built using string concatenation, and the components of the concatenation include user input, a user is likely to be able to run malicious database queries. This applies to various database query languages, including SQL and the Java Persistence Query Language.\n\n\n## Recommendation\nUsually, it is better to use a SQL prepared statement than to build a complete SQL query with string concatenation. A prepared statement can include a wildcard, written as a question mark (?), for each part of the SQL query that is expected to be filled in by a different value each time it is run. When the query is later executed, a value must be supplied for each wildcard in the query.\n\nIn the Java Persistence Query Language, it is better to use queries with parameters than to build a complete query with string concatenation. A Java Persistence query can include a parameter placeholder for each part of the query that is expected to be filled in by a different value when run. A parameter placeholder may be indicated by a colon (:) followed by a parameter name, or by a question mark (?) followed by an integer position. When the query is later executed, a value must be supplied for each parameter in the query, using the `setParameter` method. Specifying the query using the `@NamedQuery` annotation introduces an additional level of safety: the query must be a constant string literal, preventing construction by string concatenation, and the only way to fill in values for parts of the query is by setting positional parameters.\n\nIt is good practice to use prepared statements (in SQL) or query parameters (in the Java Persistence Query Language) for supplying parameter values to a query, whether or not any of the parameters are directly traceable to user input. Doing so avoids any need to worry about quoting and escaping.\n\n\n## Example\nIn the following example, the code runs a simple SQL query in two different ways.\n\nThe first way involves building a query, `query1`, by concatenating an environment variable with some string literals. The environment variable can include special characters, so this code allows for SQL injection attacks.\n\nThe second way, which shows good practice, involves building a query, `query2`, with a single string literal that includes a wildcard (`?`). The wildcard is then given a value by calling `setString`. This version is immune to injection attacks, because any special characters in the environment variable are not given any special treatment.\n\n\n```java\n{\n // BAD: the category might have SQL special characters in it\n String category = System.getenv(\"ITEM_CATEGORY\");\n Statement statement = connection.createStatement();\n String query1 = \"SELECT ITEM,PRICE FROM PRODUCT WHERE ITEM_CATEGORY='\"\n + category + \"' ORDER BY PRICE\";\n ResultSet results = statement.executeQuery(query1);\n}\n\n{\n // GOOD: use a prepared query\n String category = System.getenv(\"ITEM_CATEGORY\");\n String query2 = \"SELECT ITEM,PRICE FROM PRODUCT WHERE ITEM_CATEGORY=? ORDER BY PRICE\";\n PreparedStatement statement = connection.prepareStatement(query2);\n statement.setString(1, category);\n ResultSet results = statement.executeQuery();\n}\n```\n\n## Example\nThe following code shows several different ways to run a Java Persistence query.\n\nThe first example involves building a query, `query1`, by concatenating an environment variable with some string literals. Just like the SQL example, the environment variable can include special characters, so this code allows for Java Persistence query injection attacks.\n\nThe remaining examples demonstrate different methods for safely building a Java Persistence query with user-supplied values:\n\n1. `query2` uses a single string literal that includes a placeholder for a parameter, indicated by a colon (`:`) and parameter name (`category`).\n1. `query3` uses a single string literal that includes a placeholder for a parameter, indicated by a question mark (`?`) and position number (`1`).\n1. `namedQuery1` is defined using the `@NamedQuery` annotation, whose `query` attribute is a string literal that includes a placeholder for a parameter, indicated by a colon (`:`) and parameter name (`category`).\n1. `namedQuery2` is defined using the `@NamedQuery` annotation, whose `query` attribute includes a placeholder for a parameter, indicated by a question mark (`?`) and position number (`1`).\nThe parameter is then given a value by calling `setParameter`. These versions are immune to injection attacks, because any special characters in the environment variable or user-supplied value are not given any special treatment.\n\n\n```java\n{\n // BAD: the category might have Java Persistence Query Language special characters in it\n String category = System.getenv(\"ITEM_CATEGORY\");\n Statement statement = connection.createStatement();\n String query1 = \"SELECT p FROM Product p WHERE p.category LIKE '\"\n + category + \"' ORDER BY p.price\";\n Query q = entityManager.createQuery(query1);\n}\n\n{\n // GOOD: use a named parameter and set its value\n String category = System.getenv(\"ITEM_CATEGORY\");\n String query2 = \"SELECT p FROM Product p WHERE p.category LIKE :category ORDER BY p.price\"\n Query q = entityManager.createQuery(query2);\n q.setParameter(\"category\", category);\n}\n\n{\n // GOOD: use a positional parameter and set its value\n String category = System.getenv(\"ITEM_CATEGORY\");\n String query3 = \"SELECT p FROM Product p WHERE p.category LIKE ?1 ORDER BY p.price\"\n Query q = entityManager.createQuery(query3);\n q.setParameter(1, category);\n}\n\n{\n // GOOD: use a named query with a named parameter and set its value\n @NamedQuery(\n name=\"lookupByCategory\",\n query=\"SELECT p FROM Product p WHERE p.category LIKE :category ORDER BY p.price\")\n private static class NQ {}\n ...\n String category = System.getenv(\"ITEM_CATEGORY\");\n Query namedQuery1 = entityManager.createNamedQuery(\"lookupByCategory\");\n namedQuery1.setParameter(\"category\", category);\n}\n\n{\n // GOOD: use a named query with a positional parameter and set its value\n @NamedQuery(\n name=\"lookupByCategory\",\n query=\"SELECT p FROM Product p WHERE p.category LIKE ?1 ORDER BY p.price\")\n private static class NQ {}\n ...\n String category = System.getenv(\"ITEM_CATEGORY\");\n Query namedQuery2 = entityManager.createNamedQuery(\"lookupByCategory\");\n namedQuery2.setParameter(1, category);\n}\n```\n\n## References\n* OWASP: [SQL Injection Prevention Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/SQL_Injection_Prevention_Cheat_Sheet.html).\n* SEI CERT Oracle Coding Standard for Java: [IDS00-J. Prevent SQL injection](https://wiki.sei.cmu.edu/confluence/display/java/IDS00-J.+Prevent+SQL+injection).\n* The Java Tutorials: [Using Prepared Statements](https://docs.oracle.com/javase/tutorial/jdbc/basics/prepared.html).\n* The Java EE Tutorial: [The Java Persistence Query Language](https://docs.oracle.com/javaee/7/tutorial/persistence-querylanguage.htm).\n* Common Weakness Enumeration: [CWE-89](https://cwe.mitre.org/data/definitions/89.html).\n* Common Weakness Enumeration: [CWE-564](https://cwe.mitre.org/data/definitions/564.html).\n" + }, + "id": "java/sql-injection", + "name": "java/sql-injection", + "properties": { + "security-severity": "8.800000", + "tags": [ + "external/cwe/cwe-089", + "external/cwe/cwe-564", + "security" + ] + }, + "shortDescription": { + "text": "Query built from user-controlled sources" + } + }, + { + "defaultConfiguration": { + "level": "error" + }, + "fullDescription": { + "text": "Making web requests based on unvalidated user-input may cause the server to communicate with malicious servers." + }, + "help": { + "markdown": "# Server-side request forgery\nDirectly incorporating user input into an HTTP request without validating the input can facilitate server-side request forgery (SSRF) attacks. In these attacks, the server may be tricked into making a request and interacting with an attacker-controlled server.\n\n\n## Recommendation\nTo guard against SSRF attacks, you should avoid putting user-provided input directly into a request URL. Instead, maintain a list of authorized URLs on the server; then choose from that list based on the input provided. Alternatively, ensure requests constructed from user input are limited to a particular host or more restrictive URL prefix.\n\n\n## Example\nThe following example shows an HTTP request parameter being used directly to form a new request without validating the input, which facilitates SSRF attacks. It also shows how to remedy the problem by validating the user input against a known fixed string.\n\n\n```java\nimport java.net.http.HttpClient;\n\npublic class SSRF extends HttpServlet {\n\tprivate static final String VALID_URI = \"http://lgtm.com\";\n\tprivate HttpClient client = HttpClient.newHttpClient();\n\n\tprotected void doGet(HttpServletRequest request, HttpServletResponse response)\n\t\tthrows ServletException, IOException {\n\t\tURI uri = new URI(request.getParameter(\"uri\"));\n\t\t// BAD: a request parameter is incorporated without validation into a Http request\n\t\tHttpRequest r = HttpRequest.newBuilder(uri).build();\n\t\tclient.send(r, null);\n\n\t\t// GOOD: the request parameter is validated against a known fixed string\n\t\tif (VALID_URI.equals(request.getParameter(\"uri\"))) {\n\t\t\tHttpRequest r2 = HttpRequest.newBuilder(uri).build();\n\t\t\tclient.send(r2, null);\n\t\t}\n\t}\n}\n\n```\n\n## References\n* [OWASP SSRF](https://owasp.org/www-community/attacks/Server_Side_Request_Forgery)\n* Common Weakness Enumeration: [CWE-918](https://cwe.mitre.org/data/definitions/918.html).\n", + "text": "# Server-side request forgery\nDirectly incorporating user input into an HTTP request without validating the input can facilitate server-side request forgery (SSRF) attacks. In these attacks, the server may be tricked into making a request and interacting with an attacker-controlled server.\n\n\n## Recommendation\nTo guard against SSRF attacks, you should avoid putting user-provided input directly into a request URL. Instead, maintain a list of authorized URLs on the server; then choose from that list based on the input provided. Alternatively, ensure requests constructed from user input are limited to a particular host or more restrictive URL prefix.\n\n\n## Example\nThe following example shows an HTTP request parameter being used directly to form a new request without validating the input, which facilitates SSRF attacks. It also shows how to remedy the problem by validating the user input against a known fixed string.\n\n\n```java\nimport java.net.http.HttpClient;\n\npublic class SSRF extends HttpServlet {\n\tprivate static final String VALID_URI = \"http://lgtm.com\";\n\tprivate HttpClient client = HttpClient.newHttpClient();\n\n\tprotected void doGet(HttpServletRequest request, HttpServletResponse response)\n\t\tthrows ServletException, IOException {\n\t\tURI uri = new URI(request.getParameter(\"uri\"));\n\t\t// BAD: a request parameter is incorporated without validation into a Http request\n\t\tHttpRequest r = HttpRequest.newBuilder(uri).build();\n\t\tclient.send(r, null);\n\n\t\t// GOOD: the request parameter is validated against a known fixed string\n\t\tif (VALID_URI.equals(request.getParameter(\"uri\"))) {\n\t\t\tHttpRequest r2 = HttpRequest.newBuilder(uri).build();\n\t\t\tclient.send(r2, null);\n\t\t}\n\t}\n}\n\n```\n\n## References\n* [OWASP SSRF](https://owasp.org/www-community/attacks/Server_Side_Request_Forgery)\n* Common Weakness Enumeration: [CWE-918](https://cwe.mitre.org/data/definitions/918.html).\n" + }, + "id": "java/ssrf", + "name": "java/ssrf", + "properties": { + "security-severity": "9.100000", + "tags": [ + "external/cwe/cwe-918", + "security" + ] + }, + "shortDescription": { + "text": "Server-side request forgery" + } + }, + { + "defaultConfiguration": { + "level": "error" + }, + "fullDescription": { + "text": "Information from a stack trace propagates to an external user. Stack traces can unintentionally reveal implementation details that are useful to an attacker for developing a subsequent exploit." + }, + "help": { + "markdown": "# Information exposure through a stack trace\nSoftware developers often add stack traces to error messages, as a debugging aid. Whenever that error message occurs for an end user, the developer can use the stack trace to help identify how to fix the problem. In particular, stack traces can tell the developer more about the sequence of events that led to a failure, as opposed to merely the final state of the software when the error occurred.\n\nUnfortunately, the same information can be useful to an attacker. The sequence of class names in a stack trace can reveal the structure of the application as well as any internal components it relies on. Furthermore, the error message at the top of a stack trace can include information such as server-side file names and SQL code that the application relies on, allowing an attacker to fine-tune a subsequent injection attack.\n\n\n## Recommendation\nSend the user a more generic error message that reveals less information. Either suppress the stack trace entirely, or log it only on the server.\n\n\n## Example\nIn the following example, an exception is handled in two different ways. In the first version, labeled BAD, the exception is sent back to the remote user using the `sendError()` method. As such, the user is able to see a detailed stack trace, which may contain sensitive information. In the second version, the error message is logged only on the server. That way, the developers can still access and use the error log, but remote users will not see the information.\n\n\n```java\nprotected void doGet(HttpServletRequest request, HttpServletResponse response) {\n\ttry {\n\t\tdoSomeWork();\n\t} catch (NullPointerException ex) {\n\t\t// BAD: printing a stack trace back to the response\n\t\tex.printStackTrace(response.getWriter());\n\t\treturn;\n\t}\n\n\ttry {\n\t\tdoSomeWork();\n\t} catch (NullPointerException ex) {\n\t\t// GOOD: log the stack trace, and send back a non-revealing response\n\t\tlog(\"Exception occurred\", ex);\n\t\tresponse.sendError(\n\t\t\tHttpServletResponse.SC_INTERNAL_SERVER_ERROR,\n\t\t\t\"Exception occurred\");\n\t\treturn;\n\t}\n}\n\n```\n\n## References\n* OWASP: [Improper Error Handling](https://owasp.org/www-community/Improper_Error_Handling).\n* CERT Java Coding Standard: [ERR01-J. Do not allow exceptions to expose sensitive information](https://www.securecoding.cert.org/confluence/display/java/ERR01-J.+Do+not+allow+exceptions+to+expose+sensitive+information).\n* Common Weakness Enumeration: [CWE-209](https://cwe.mitre.org/data/definitions/209.html).\n* Common Weakness Enumeration: [CWE-497](https://cwe.mitre.org/data/definitions/497.html).\n", + "text": "# Information exposure through a stack trace\nSoftware developers often add stack traces to error messages, as a debugging aid. Whenever that error message occurs for an end user, the developer can use the stack trace to help identify how to fix the problem. In particular, stack traces can tell the developer more about the sequence of events that led to a failure, as opposed to merely the final state of the software when the error occurred.\n\nUnfortunately, the same information can be useful to an attacker. The sequence of class names in a stack trace can reveal the structure of the application as well as any internal components it relies on. Furthermore, the error message at the top of a stack trace can include information such as server-side file names and SQL code that the application relies on, allowing an attacker to fine-tune a subsequent injection attack.\n\n\n## Recommendation\nSend the user a more generic error message that reveals less information. Either suppress the stack trace entirely, or log it only on the server.\n\n\n## Example\nIn the following example, an exception is handled in two different ways. In the first version, labeled BAD, the exception is sent back to the remote user using the `sendError()` method. As such, the user is able to see a detailed stack trace, which may contain sensitive information. In the second version, the error message is logged only on the server. That way, the developers can still access and use the error log, but remote users will not see the information.\n\n\n```java\nprotected void doGet(HttpServletRequest request, HttpServletResponse response) {\n\ttry {\n\t\tdoSomeWork();\n\t} catch (NullPointerException ex) {\n\t\t// BAD: printing a stack trace back to the response\n\t\tex.printStackTrace(response.getWriter());\n\t\treturn;\n\t}\n\n\ttry {\n\t\tdoSomeWork();\n\t} catch (NullPointerException ex) {\n\t\t// GOOD: log the stack trace, and send back a non-revealing response\n\t\tlog(\"Exception occurred\", ex);\n\t\tresponse.sendError(\n\t\t\tHttpServletResponse.SC_INTERNAL_SERVER_ERROR,\n\t\t\t\"Exception occurred\");\n\t\treturn;\n\t}\n}\n\n```\n\n## References\n* OWASP: [Improper Error Handling](https://owasp.org/www-community/Improper_Error_Handling).\n* CERT Java Coding Standard: [ERR01-J. Do not allow exceptions to expose sensitive information](https://www.securecoding.cert.org/confluence/display/java/ERR01-J.+Do+not+allow+exceptions+to+expose+sensitive+information).\n* Common Weakness Enumeration: [CWE-209](https://cwe.mitre.org/data/definitions/209.html).\n* Common Weakness Enumeration: [CWE-497](https://cwe.mitre.org/data/definitions/497.html).\n" + }, + "id": "java/stack-trace-exposure", + "name": "java/stack-trace-exposure", + "properties": { + "security-severity": "5.400000", + "tags": [ + "external/cwe/cwe-209", + "external/cwe/cwe-497", + "security" + ] + }, + "shortDescription": { + "text": "Information exposure through a stack trace" + } + }, + { + "defaultConfiguration": { + "level": "warning" + }, + "fullDescription": { + "text": "The total number of lines of code across all files. This is a useful metric of the size of a database. For all files that were seen during the build, this query counts the lines of code, excluding whitespace or comments." + }, + "id": "java/summary/lines-of-code", + "name": "java/summary/lines-of-code", + "properties": { + "tags": [ + "lines-of-code", + "summary" + ] + }, + "shortDescription": { + "text": "Total lines of code in the database" + } + }, + { + "defaultConfiguration": { + "level": "error" + }, + "fullDescription": { + "text": "Using external input in format strings can lead to exceptions or information leaks." + }, + "help": { + "markdown": "# Use of externally-controlled format string\nThe `String.format` method and related methods, like `PrintStream.printf` and `Formatter.format`, all accept a format string that is used to format the trailing arguments to the format call by providing inline format specifiers. If the format string contains unsanitized input from an untrusted source, then that string may contain extra format specifiers that cause an exception to be thrown or information to be leaked.\n\nThe Java standard library implementation for the format methods throws an exception if either the format specifier does not match the type of the argument, or if there are too few or too many arguments. If unsanitized input is used in the format string, it may contain invalid extra format specifiers which cause an exception to be thrown.\n\nPositional format specifiers may be used to access an argument to the format call by position. Unsanitized input in the format string may use a positional format specifier to access information that was not intended to be visible. For example, when formatting a Calendar instance we may intend to print only the year, but a user-specified format string may include a specifier to access the month and day.\n\n\n## Recommendation\nIf the argument passed as a format string is meant to be a plain string rather than a format string, then pass `%s` as the format string, and pass the original argument as the sole trailing argument.\n\n\n## Example\nThe following program is meant to check a card security code for a stored credit card:\n\n\n```java\npublic class ResponseSplitting extends HttpServlet {\n protected void doGet(HttpServletRequest request, HttpServletResponse response)\n throws ServletException, IOException {\n Calendar expirationDate = new GregorianCalendar(2017, GregorianCalendar.SEPTEMBER, 1);\n // User provided value\n String cardSecurityCode = request.getParameter(\"cardSecurityCode\");\n \n if (notValid(cardSecurityCode)) {\n \n /*\n * BAD: user provided value is included in the format string.\n * A malicious user could provide an extra format specifier, which causes an\n * exception to be thrown. Or they could provide a %1$tm or %1$te format specifier to\n * access the month or day of the expiration date.\n */\n System.out.format(cardSecurityCode +\n \" is not the right value. Hint: the card expires in %1$ty.\",\n expirationDate);\n \n // GOOD: %s is used to include the user-provided cardSecurityCode in the output\n System.out.format(\"%s is not the right value. Hint: the card expires in %2$ty.\",\n cardSecurityCode,\n expirationDate);\n }\n\n }\n}\n```\nHowever, in the first format call it uses the cardSecurityCode provided by the user in a format string. If the user includes a format specifier in the cardSecurityCode field, they may be able to cause an exception to be thrown, or to be able to access extra information about the stored card expiration date.\n\nThe second format call shows the correct approach. The user-provided value is passed as an argument to the format call. This prevents any format specifiers in the user provided value from being evaluated.\n\n\n## References\n* SEI CERT Oracle Coding Standard for Java: [IDS06-J. Exclude unsanitized user input from format strings](https://wiki.sei.cmu.edu/confluence/display/java/IDS06-J.+Exclude+unsanitized+user+input+from+format+strings).\n* The Java Tutorials: [Formatting Numeric Print Output](https://docs.oracle.com/javase/tutorial/java/data/numberformat.html).\n* Java API Specification: [Formatter](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/Formatter.html).\n* Common Weakness Enumeration: [CWE-134](https://cwe.mitre.org/data/definitions/134.html).\n", + "text": "# Use of externally-controlled format string\nThe `String.format` method and related methods, like `PrintStream.printf` and `Formatter.format`, all accept a format string that is used to format the trailing arguments to the format call by providing inline format specifiers. If the format string contains unsanitized input from an untrusted source, then that string may contain extra format specifiers that cause an exception to be thrown or information to be leaked.\n\nThe Java standard library implementation for the format methods throws an exception if either the format specifier does not match the type of the argument, or if there are too few or too many arguments. If unsanitized input is used in the format string, it may contain invalid extra format specifiers which cause an exception to be thrown.\n\nPositional format specifiers may be used to access an argument to the format call by position. Unsanitized input in the format string may use a positional format specifier to access information that was not intended to be visible. For example, when formatting a Calendar instance we may intend to print only the year, but a user-specified format string may include a specifier to access the month and day.\n\n\n## Recommendation\nIf the argument passed as a format string is meant to be a plain string rather than a format string, then pass `%s` as the format string, and pass the original argument as the sole trailing argument.\n\n\n## Example\nThe following program is meant to check a card security code for a stored credit card:\n\n\n```java\npublic class ResponseSplitting extends HttpServlet {\n protected void doGet(HttpServletRequest request, HttpServletResponse response)\n throws ServletException, IOException {\n Calendar expirationDate = new GregorianCalendar(2017, GregorianCalendar.SEPTEMBER, 1);\n // User provided value\n String cardSecurityCode = request.getParameter(\"cardSecurityCode\");\n \n if (notValid(cardSecurityCode)) {\n \n /*\n * BAD: user provided value is included in the format string.\n * A malicious user could provide an extra format specifier, which causes an\n * exception to be thrown. Or they could provide a %1$tm or %1$te format specifier to\n * access the month or day of the expiration date.\n */\n System.out.format(cardSecurityCode +\n \" is not the right value. Hint: the card expires in %1$ty.\",\n expirationDate);\n \n // GOOD: %s is used to include the user-provided cardSecurityCode in the output\n System.out.format(\"%s is not the right value. Hint: the card expires in %2$ty.\",\n cardSecurityCode,\n expirationDate);\n }\n\n }\n}\n```\nHowever, in the first format call it uses the cardSecurityCode provided by the user in a format string. If the user includes a format specifier in the cardSecurityCode field, they may be able to cause an exception to be thrown, or to be able to access extra information about the stored card expiration date.\n\nThe second format call shows the correct approach. The user-provided value is passed as an argument to the format call. This prevents any format specifiers in the user provided value from being evaluated.\n\n\n## References\n* SEI CERT Oracle Coding Standard for Java: [IDS06-J. Exclude unsanitized user input from format strings](https://wiki.sei.cmu.edu/confluence/display/java/IDS06-J.+Exclude+unsanitized+user+input+from+format+strings).\n* The Java Tutorials: [Formatting Numeric Print Output](https://docs.oracle.com/javase/tutorial/java/data/numberformat.html).\n* Java API Specification: [Formatter](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/Formatter.html).\n* Common Weakness Enumeration: [CWE-134](https://cwe.mitre.org/data/definitions/134.html).\n" + }, + "id": "java/tainted-format-string", + "name": "java/tainted-format-string", + "properties": { + "security-severity": "9.300000", + "tags": [ + "external/cwe/cwe-134", + "security" + ] + }, + "shortDescription": { + "text": "Use of externally-controlled format string" + } + }, + { + "defaultConfiguration": { + "level": "error" + }, + "fullDescription": { + "text": "Casting user-controlled numeric data to a narrower type without validation can cause unexpected truncation." + }, + "help": { + "markdown": "# User-controlled data in numeric cast\nCasting a user-controlled numeric value to a narrower type can result in truncated values unless the input is validated.\n\nNarrowing conversions may cause potentially unintended results. For example, casting the positive integer value `128` to type `byte` yields the negative value `-128`.\n\n\n## Recommendation\nGuard against unexpected truncation of user-controlled arithmetic data by doing one of the following:\n\n* Validate the user input.\n* Define a guard on the cast expression, so that the cast is performed only if the input is known to be within the range of the resulting type.\n* Avoid casting to a narrower type, and instead continue to use a wider type.\n\n## Example\nIn this example, a value is read from standard input into a `long`. Because the value is a user-controlled value, it could be extremely large. Casting this value to a narrower type could therefore cause unexpected truncation. The `scaled2` example uses a guard to avoid this problem and checks the range of the input before performing the cast. If the value is too large to cast to type `int` it is rejected as invalid.\n\n\n```java\nclass Test {\n\tpublic static void main(String[] args) throws IOException {\n\t\t{\n\t\t\tlong data;\n\n\t\t\tBufferedReader readerBuffered = new BufferedReader(\n\t\t\t\t\tnew InputStreamReader(System.in, \"UTF-8\"));\n\t\t\tString stringNumber = readerBuffered.readLine();\n\t\t\tif (stringNumber != null) {\n\t\t\t\tdata = Long.parseLong(stringNumber.trim());\n\t\t\t} else {\n\t\t\t\tdata = 0;\n\t\t\t}\n\n\t\t\t// AVOID: potential truncation if input data is very large,\n\t\t\t// for example 'Long.MAX_VALUE'\n\t\t\tint scaled = (int)data;\n\n\t\t\t//...\n\n\t\t\t// GOOD: use a guard to ensure no truncation occurs\n\t\t\tint scaled2;\n\t\t\tif (data > Integer.MIN_VALUE && data < Integer.MAX_VALUE)\n\t\t\t\tscaled2 = (int)data;\n\t\t\telse\n\t\t\t\tthrow new IllegalArgumentException(\"Invalid input\");\n\t\t}\n\t}\n}\n```\n\n## References\n* SEI CERT Oracle Coding Standard for Java: [NUM12-J. Ensure conversions of numeric types to narrower types do not result in lost or misinterpreted data](https://wiki.sei.cmu.edu/confluence/display/java/NUM12-J.+Ensure+conversions+of+numeric+types+to+narrower+types+do+not+result+in+lost+or+misinterpreted+data).\n* Common Weakness Enumeration: [CWE-197](https://cwe.mitre.org/data/definitions/197.html).\n* Common Weakness Enumeration: [CWE-681](https://cwe.mitre.org/data/definitions/681.html).\n", + "text": "# User-controlled data in numeric cast\nCasting a user-controlled numeric value to a narrower type can result in truncated values unless the input is validated.\n\nNarrowing conversions may cause potentially unintended results. For example, casting the positive integer value `128` to type `byte` yields the negative value `-128`.\n\n\n## Recommendation\nGuard against unexpected truncation of user-controlled arithmetic data by doing one of the following:\n\n* Validate the user input.\n* Define a guard on the cast expression, so that the cast is performed only if the input is known to be within the range of the resulting type.\n* Avoid casting to a narrower type, and instead continue to use a wider type.\n\n## Example\nIn this example, a value is read from standard input into a `long`. Because the value is a user-controlled value, it could be extremely large. Casting this value to a narrower type could therefore cause unexpected truncation. The `scaled2` example uses a guard to avoid this problem and checks the range of the input before performing the cast. If the value is too large to cast to type `int` it is rejected as invalid.\n\n\n```java\nclass Test {\n\tpublic static void main(String[] args) throws IOException {\n\t\t{\n\t\t\tlong data;\n\n\t\t\tBufferedReader readerBuffered = new BufferedReader(\n\t\t\t\t\tnew InputStreamReader(System.in, \"UTF-8\"));\n\t\t\tString stringNumber = readerBuffered.readLine();\n\t\t\tif (stringNumber != null) {\n\t\t\t\tdata = Long.parseLong(stringNumber.trim());\n\t\t\t} else {\n\t\t\t\tdata = 0;\n\t\t\t}\n\n\t\t\t// AVOID: potential truncation if input data is very large,\n\t\t\t// for example 'Long.MAX_VALUE'\n\t\t\tint scaled = (int)data;\n\n\t\t\t//...\n\n\t\t\t// GOOD: use a guard to ensure no truncation occurs\n\t\t\tint scaled2;\n\t\t\tif (data > Integer.MIN_VALUE && data < Integer.MAX_VALUE)\n\t\t\t\tscaled2 = (int)data;\n\t\t\telse\n\t\t\t\tthrow new IllegalArgumentException(\"Invalid input\");\n\t\t}\n\t}\n}\n```\n\n## References\n* SEI CERT Oracle Coding Standard for Java: [NUM12-J. Ensure conversions of numeric types to narrower types do not result in lost or misinterpreted data](https://wiki.sei.cmu.edu/confluence/display/java/NUM12-J.+Ensure+conversions+of+numeric+types+to+narrower+types+do+not+result+in+lost+or+misinterpreted+data).\n* Common Weakness Enumeration: [CWE-197](https://cwe.mitre.org/data/definitions/197.html).\n* Common Weakness Enumeration: [CWE-681](https://cwe.mitre.org/data/definitions/681.html).\n" + }, + "id": "java/tainted-numeric-cast", + "name": "java/tainted-numeric-cast", + "properties": { + "security-severity": "9.000000", + "tags": [ + "external/cwe/cwe-197", + "external/cwe/cwe-681", + "security" + ] + }, + "shortDescription": { + "text": "User-controlled data in numeric cast" + } + }, + { + "defaultConfiguration": { + "level": "error" + }, + "fullDescription": { + "text": "Using user-controlled data in a permissions check may result in inappropriate permissions being granted." + }, + "help": { + "markdown": "# User-controlled data used in permissions check\nUsing user-controlled data in a permissions check may allow a user to gain unauthorized access to protected functionality or data.\n\n\n## Recommendation\nWhen checking whether a user is authorized for a particular activity, do not use data that is controlled by that user in the permissions check. If necessary, always validate the input, ideally against a fixed list of expected values.\n\nSimilarly, do not decide which permission to check for based on user data. In particular, avoid using computation to decide which permissions to check for. Use fixed permissions for particular actions, rather than generating the permission to check for.\n\n\n## Example\nThis example, using the Apache Shiro security framework, shows two ways to specify the permissions to check. The first way uses a string, `whatDoTheyWantToDo`, to specify the permissions to check. However, this string is built from user input. This can allow an attacker to force a check against a permission that they know they have, rather than the permission that should be checked. For example, while trying to access the account details of another user, the attacker could force the system to check whether they had permissions to access their *own* account details, which is incorrect, and would allow them to perform the action. The second, more secure way uses a fixed check that does not depend on data that is controlled by the user.\n\n\n```java\npublic static void main(String[] args) {\n\tString whatDoTheyWantToDo = args[0];\n\tSubject subject = SecurityUtils.getSubject();\n\n\t// BAD: permissions decision made using tainted data\n\tif(subject.isPermitted(\"domain:sublevel:\" + whatDoTheyWantToDo))\n\t\tdoIt();\n\n\t// GOOD: use fixed checks\n\tif(subject.isPermitted(\"domain:sublevel:whatTheMethodDoes\"))\n\t\tdoIt();\n}\n```\n\n## References\n* SEI CERT Oracle Coding Standard for Java: [SEC02-J. Do not base security checks on untrusted sources](https://wiki.sei.cmu.edu/confluence/display/java/SEC02-J.+Do+not+base+security+checks+on+untrusted+sources).\n* Common Weakness Enumeration: [CWE-807](https://cwe.mitre.org/data/definitions/807.html).\n* Common Weakness Enumeration: [CWE-290](https://cwe.mitre.org/data/definitions/290.html).\n", + "text": "# User-controlled data used in permissions check\nUsing user-controlled data in a permissions check may allow a user to gain unauthorized access to protected functionality or data.\n\n\n## Recommendation\nWhen checking whether a user is authorized for a particular activity, do not use data that is controlled by that user in the permissions check. If necessary, always validate the input, ideally against a fixed list of expected values.\n\nSimilarly, do not decide which permission to check for based on user data. In particular, avoid using computation to decide which permissions to check for. Use fixed permissions for particular actions, rather than generating the permission to check for.\n\n\n## Example\nThis example, using the Apache Shiro security framework, shows two ways to specify the permissions to check. The first way uses a string, `whatDoTheyWantToDo`, to specify the permissions to check. However, this string is built from user input. This can allow an attacker to force a check against a permission that they know they have, rather than the permission that should be checked. For example, while trying to access the account details of another user, the attacker could force the system to check whether they had permissions to access their *own* account details, which is incorrect, and would allow them to perform the action. The second, more secure way uses a fixed check that does not depend on data that is controlled by the user.\n\n\n```java\npublic static void main(String[] args) {\n\tString whatDoTheyWantToDo = args[0];\n\tSubject subject = SecurityUtils.getSubject();\n\n\t// BAD: permissions decision made using tainted data\n\tif(subject.isPermitted(\"domain:sublevel:\" + whatDoTheyWantToDo))\n\t\tdoIt();\n\n\t// GOOD: use fixed checks\n\tif(subject.isPermitted(\"domain:sublevel:whatTheMethodDoes\"))\n\t\tdoIt();\n}\n```\n\n## References\n* SEI CERT Oracle Coding Standard for Java: [SEC02-J. Do not base security checks on untrusted sources](https://wiki.sei.cmu.edu/confluence/display/java/SEC02-J.+Do+not+base+security+checks+on+untrusted+sources).\n* Common Weakness Enumeration: [CWE-807](https://cwe.mitre.org/data/definitions/807.html).\n* Common Weakness Enumeration: [CWE-290](https://cwe.mitre.org/data/definitions/290.html).\n" + }, + "id": "java/tainted-permissions-check", + "name": "java/tainted-permissions-check", + "properties": { + "security-severity": "7.800000", + "tags": [ + "external/cwe/cwe-290", + "external/cwe/cwe-807", + "security" + ] + }, + "shortDescription": { + "text": "User-controlled data used in permissions check" + } + }, + { + "defaultConfiguration": { + "level": "error" + }, + "fullDescription": { + "text": "Deserializing user-controlled data may allow attackers to execute arbitrary code." + }, + "help": { + "markdown": "# Deserialization of user-controlled data\nDeserializing untrusted data using any deserialization framework that allows the construction of arbitrary serializable objects is easily exploitable and in many cases allows an attacker to execute arbitrary code. Even before a deserialized object is returned to the caller of a deserialization method a lot of code may have been executed, including static initializers, constructors, and finalizers. Automatic deserialization of fields means that an attacker may craft a nested combination of objects on which the executed initialization code may have unforeseen effects, such as the execution of arbitrary code.\n\nThere are many different serialization frameworks. This query currently supports Kryo, XmlDecoder, XStream, SnakeYaml, JYaml, JsonIO, YAMLBeans, HessianBurlap, Castor, Burlap, Jackson, Jabsorb, Jodd JSON, Flexjson, Gson and Java IO serialization through `ObjectInputStream`/`ObjectOutputStream`.\n\n\n## Recommendation\nAvoid deserialization of untrusted data if at all possible. If the architecture permits it then use other formats instead of serialized objects, for example JSON or XML. However, these formats should not be deserialized into complex objects because this provides further opportunities for attack. For example, XML-based deserialization attacks are possible through libraries such as XStream and XmlDecoder. Alternatively, a tightly controlled whitelist can limit the vulnerability of code, but be aware of the existence of so-called Bypass Gadgets, which can circumvent such protection measures.\n\n\n## Example\nThe following example calls `readObject` directly on an `ObjectInputStream` that is constructed from untrusted data, and is therefore inherently unsafe.\n\n\n```java\npublic MyObject {\n public int field;\n MyObject(int field) {\n this.field = field;\n }\n}\n\npublic MyObject deserialize(Socket sock) {\n try(ObjectInputStream in = new ObjectInputStream(sock.getInputStream())) {\n return (MyObject)in.readObject(); // unsafe\n }\n}\n\n```\nRewriting the communication protocol to only rely on reading primitive types from the input stream removes the vulnerability.\n\n\n```java\npublic MyObject deserialize(Socket sock) {\n try(DataInputStream in = new DataInputStream(sock.getInputStream())) {\n return new MyObject(in.readInt());\n }\n}\n\n```\n\n## References\n* OWASP vulnerability description: [Deserialization of untrusted data](https://www.owasp.org/index.php/Deserialization_of_untrusted_data).\n* OWASP guidance on deserializing objects: [Deserialization Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/Deserialization_Cheat_Sheet.html).\n* Talks by Chris Frohoff & Gabriel Lawrence: [ AppSecCali 2015: Marshalling Pickles - how deserializing objects will ruin your day](http://frohoff.github.io/appseccali-marshalling-pickles/), [OWASP SD: Deserialize My Shorts: Or How I Learned to Start Worrying and Hate Java Object Deserialization](http://frohoff.github.io/owaspsd-deserialize-my-shorts/).\n* Alvaro Muñoz & Christian Schneider, RSAConference 2016: [Serial Killer: Silently Pwning Your Java Endpoints](https://speakerdeck.com/pwntester/serial-killer-silently-pwning-your-java-endpoints).\n* SnakeYaml documentation on deserialization: [SnakeYaml deserialization](https://bitbucket.org/asomov/snakeyaml/wiki/Documentation#markdown-header-loading-yaml).\n* Hessian deserialization and related gadget chains: [Hessian deserialization](https://paper.seebug.org/1137/).\n* Castor and Hessian java deserialization vulnerabilities: [Castor and Hessian deserialization](https://securitylab.github.com/research/hessian-java-deserialization-castor-vulnerabilities/).\n* Remote code execution in JYaml library: [JYaml deserialization](https://www.cybersecurity-help.cz/vdb/SB2020022512).\n* JsonIO deserialization vulnerabilities: [JsonIO deserialization](https://klezvirus.github.io/Advanced-Web-Hacking/Serialisation/).\n* Research by Moritz Bechler: [Java Unmarshaller Security - Turning your data into code execution](https://www.github.com/mbechler/marshalsec/blob/master/marshalsec.pdf?raw=true)\n* Blog posts by the developer of Jackson libraries: [On Jackson CVEs: Don’t Panic — Here is what you need to know](https://cowtowncoder.medium.com/on-jackson-cves-dont-panic-here-is-what-you-need-to-know-54cd0d6e8062) [Jackson 2.10: Safe Default Typing](https://cowtowncoder.medium.com/jackson-2-10-safe-default-typing-2d018f0ce2ba)\n* Jabsorb documentation on deserialization: [Jabsorb JSON Serializer](https://github.com/Servoy/jabsorb/blob/master/src/org/jabsorb/).\n* Jodd JSON documentation on deserialization: [JoddJson Parser](https://json.jodd.org/parser).\n* RCE in Flexjson: [Flexjson deserialization](https://codewhitesec.blogspot.com/2020/03/liferay-portal-json-vulns.html).\n* Android Intent deserialization vulnerabilities with GSON parser: [Insecure use of JSON parsers](https://blog.oversecured.com/Exploiting-memory-corruption-vulnerabilities-on-Android/#insecure-use-of-json-parsers).\n* Common Weakness Enumeration: [CWE-502](https://cwe.mitre.org/data/definitions/502.html).\n", + "text": "# Deserialization of user-controlled data\nDeserializing untrusted data using any deserialization framework that allows the construction of arbitrary serializable objects is easily exploitable and in many cases allows an attacker to execute arbitrary code. Even before a deserialized object is returned to the caller of a deserialization method a lot of code may have been executed, including static initializers, constructors, and finalizers. Automatic deserialization of fields means that an attacker may craft a nested combination of objects on which the executed initialization code may have unforeseen effects, such as the execution of arbitrary code.\n\nThere are many different serialization frameworks. This query currently supports Kryo, XmlDecoder, XStream, SnakeYaml, JYaml, JsonIO, YAMLBeans, HessianBurlap, Castor, Burlap, Jackson, Jabsorb, Jodd JSON, Flexjson, Gson and Java IO serialization through `ObjectInputStream`/`ObjectOutputStream`.\n\n\n## Recommendation\nAvoid deserialization of untrusted data if at all possible. If the architecture permits it then use other formats instead of serialized objects, for example JSON or XML. However, these formats should not be deserialized into complex objects because this provides further opportunities for attack. For example, XML-based deserialization attacks are possible through libraries such as XStream and XmlDecoder. Alternatively, a tightly controlled whitelist can limit the vulnerability of code, but be aware of the existence of so-called Bypass Gadgets, which can circumvent such protection measures.\n\n\n## Example\nThe following example calls `readObject` directly on an `ObjectInputStream` that is constructed from untrusted data, and is therefore inherently unsafe.\n\n\n```java\npublic MyObject {\n public int field;\n MyObject(int field) {\n this.field = field;\n }\n}\n\npublic MyObject deserialize(Socket sock) {\n try(ObjectInputStream in = new ObjectInputStream(sock.getInputStream())) {\n return (MyObject)in.readObject(); // unsafe\n }\n}\n\n```\nRewriting the communication protocol to only rely on reading primitive types from the input stream removes the vulnerability.\n\n\n```java\npublic MyObject deserialize(Socket sock) {\n try(DataInputStream in = new DataInputStream(sock.getInputStream())) {\n return new MyObject(in.readInt());\n }\n}\n\n```\n\n## References\n* OWASP vulnerability description: [Deserialization of untrusted data](https://www.owasp.org/index.php/Deserialization_of_untrusted_data).\n* OWASP guidance on deserializing objects: [Deserialization Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/Deserialization_Cheat_Sheet.html).\n* Talks by Chris Frohoff & Gabriel Lawrence: [ AppSecCali 2015: Marshalling Pickles - how deserializing objects will ruin your day](http://frohoff.github.io/appseccali-marshalling-pickles/), [OWASP SD: Deserialize My Shorts: Or How I Learned to Start Worrying and Hate Java Object Deserialization](http://frohoff.github.io/owaspsd-deserialize-my-shorts/).\n* Alvaro Muñoz & Christian Schneider, RSAConference 2016: [Serial Killer: Silently Pwning Your Java Endpoints](https://speakerdeck.com/pwntester/serial-killer-silently-pwning-your-java-endpoints).\n* SnakeYaml documentation on deserialization: [SnakeYaml deserialization](https://bitbucket.org/asomov/snakeyaml/wiki/Documentation#markdown-header-loading-yaml).\n* Hessian deserialization and related gadget chains: [Hessian deserialization](https://paper.seebug.org/1137/).\n* Castor and Hessian java deserialization vulnerabilities: [Castor and Hessian deserialization](https://securitylab.github.com/research/hessian-java-deserialization-castor-vulnerabilities/).\n* Remote code execution in JYaml library: [JYaml deserialization](https://www.cybersecurity-help.cz/vdb/SB2020022512).\n* JsonIO deserialization vulnerabilities: [JsonIO deserialization](https://klezvirus.github.io/Advanced-Web-Hacking/Serialisation/).\n* Research by Moritz Bechler: [Java Unmarshaller Security - Turning your data into code execution](https://www.github.com/mbechler/marshalsec/blob/master/marshalsec.pdf?raw=true)\n* Blog posts by the developer of Jackson libraries: [On Jackson CVEs: Don’t Panic — Here is what you need to know](https://cowtowncoder.medium.com/on-jackson-cves-dont-panic-here-is-what-you-need-to-know-54cd0d6e8062) [Jackson 2.10: Safe Default Typing](https://cowtowncoder.medium.com/jackson-2-10-safe-default-typing-2d018f0ce2ba)\n* Jabsorb documentation on deserialization: [Jabsorb JSON Serializer](https://github.com/Servoy/jabsorb/blob/master/src/org/jabsorb/).\n* Jodd JSON documentation on deserialization: [JoddJson Parser](https://json.jodd.org/parser).\n* RCE in Flexjson: [Flexjson deserialization](https://codewhitesec.blogspot.com/2020/03/liferay-portal-json-vulns.html).\n* Android Intent deserialization vulnerabilities with GSON parser: [Insecure use of JSON parsers](https://blog.oversecured.com/Exploiting-memory-corruption-vulnerabilities-on-Android/#insecure-use-of-json-parsers).\n* Common Weakness Enumeration: [CWE-502](https://cwe.mitre.org/data/definitions/502.html).\n" + }, + "id": "java/unsafe-deserialization", + "name": "java/unsafe-deserialization", + "properties": { + "security-severity": "9.800000", + "tags": [ + "external/cwe/cwe-502", + "security" + ] + }, + "shortDescription": { + "text": "Deserialization of user-controlled data" + } + }, + { + "defaultConfiguration": { + "level": "error" + }, + "fullDescription": { + "text": "Marking a certificate as valid for a host without checking the certificate hostname allows an attacker to perform a machine-in-the-middle attack." + }, + "help": { + "markdown": "# Unsafe hostname verification\nIf a `HostnameVerifier` always returns `true` it will not verify the hostname at all. This stops Transport Layer Security (TLS) providing any security and allows an attacker to perform a man-in-the-middle attack against the application.\n\nAn attack might look like this:\n\n1. The program connects to `https://example.com`.\n1. The attacker intercepts this connection and presents an apparently-valid certificate of their choosing.\n1. The `TrustManager` of the program verifies that the certificate has been issued by a trusted certificate authority.\n1. The Java HTTPS library checks whether the certificate has been issued for the host `example.com`. This check fails because the certificate has been issued for a domain controlled by the attacker, for example: `malicious.domain`.\n1. The HTTPS library wants to reject the certificate because the hostname does not match. Before doing this it checks whether a `HostnameVerifier` exists.\n1. Your `HostnameVerifier` is called which returns `true` for any certificate so also for this one.\n1. The program proceeds with the connection since your `HostnameVerifier` accepted it.\n1. The attacker can now read the data your program sends to `https://example.com` and/or alter its replies while the program thinks the connection is secure.\n\n## Recommendation\nDo not use an open `HostnameVerifier`. If you have a configuration problem with TLS/HTTPS, you should always solve the configuration problem instead of using an open verifier.\n\n\n## Example\nIn the first (bad) example, the `HostnameVerifier` always returns `true`. This allows an attacker to perform a man-in-the-middle attack, because any certificate is accepted despite an incorrect hostname. In the second (good) example, the `HostnameVerifier` only returns `true` when the certificate has been correctly checked.\n\n\n```java\npublic static void main(String[] args) {\n\n\t{\n\t\tHostnameVerifier verifier = new HostnameVerifier() {\n\t\t\t@Override\n\t\t\tpublic boolean verify(String hostname, SSLSession session) {\n\t\t\t\treturn true; // BAD: accept even if the hostname doesn't match\n\t\t\t}\n\t\t};\n\t\tHttpsURLConnection.setDefaultHostnameVerifier(verifier);\n\t}\n\n\t{\n\t\tHostnameVerifier verifier = new HostnameVerifier() {\n\t\t\t@Override\n\t\t\tpublic boolean verify(String hostname, SSLSession session) {\n\t\t\t\ttry { // GOOD: verify the certificate\n\t\t\t\t\tCertificate[] certs = session.getPeerCertificates();\n\t\t\t\t\tX509Certificate x509 = (X509Certificate) certs[0];\n\t\t\t\t\tcheck(new String[]{host}, x509);\n\t\t\t\t\treturn true;\n\t\t\t\t} catch (SSLException e) {\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\t\t\t}\n\t\t};\n\t\tHttpsURLConnection.setDefaultHostnameVerifier(verifier);\n\t}\n\n}\n```\n\n## References\n* Android developers: [Security with HTTPS and SSL](https://developer.android.com/training/articles/security-ssl).\n* Terse systems blog: [Fixing Hostname Verification](https://tersesystems.com/blog/2014/03/23/fixing-hostname-verification/).\n* Common Weakness Enumeration: [CWE-297](https://cwe.mitre.org/data/definitions/297.html).\n", + "text": "# Unsafe hostname verification\nIf a `HostnameVerifier` always returns `true` it will not verify the hostname at all. This stops Transport Layer Security (TLS) providing any security and allows an attacker to perform a man-in-the-middle attack against the application.\n\nAn attack might look like this:\n\n1. The program connects to `https://example.com`.\n1. The attacker intercepts this connection and presents an apparently-valid certificate of their choosing.\n1. The `TrustManager` of the program verifies that the certificate has been issued by a trusted certificate authority.\n1. The Java HTTPS library checks whether the certificate has been issued for the host `example.com`. This check fails because the certificate has been issued for a domain controlled by the attacker, for example: `malicious.domain`.\n1. The HTTPS library wants to reject the certificate because the hostname does not match. Before doing this it checks whether a `HostnameVerifier` exists.\n1. Your `HostnameVerifier` is called which returns `true` for any certificate so also for this one.\n1. The program proceeds with the connection since your `HostnameVerifier` accepted it.\n1. The attacker can now read the data your program sends to `https://example.com` and/or alter its replies while the program thinks the connection is secure.\n\n## Recommendation\nDo not use an open `HostnameVerifier`. If you have a configuration problem with TLS/HTTPS, you should always solve the configuration problem instead of using an open verifier.\n\n\n## Example\nIn the first (bad) example, the `HostnameVerifier` always returns `true`. This allows an attacker to perform a man-in-the-middle attack, because any certificate is accepted despite an incorrect hostname. In the second (good) example, the `HostnameVerifier` only returns `true` when the certificate has been correctly checked.\n\n\n```java\npublic static void main(String[] args) {\n\n\t{\n\t\tHostnameVerifier verifier = new HostnameVerifier() {\n\t\t\t@Override\n\t\t\tpublic boolean verify(String hostname, SSLSession session) {\n\t\t\t\treturn true; // BAD: accept even if the hostname doesn't match\n\t\t\t}\n\t\t};\n\t\tHttpsURLConnection.setDefaultHostnameVerifier(verifier);\n\t}\n\n\t{\n\t\tHostnameVerifier verifier = new HostnameVerifier() {\n\t\t\t@Override\n\t\t\tpublic boolean verify(String hostname, SSLSession session) {\n\t\t\t\ttry { // GOOD: verify the certificate\n\t\t\t\t\tCertificate[] certs = session.getPeerCertificates();\n\t\t\t\t\tX509Certificate x509 = (X509Certificate) certs[0];\n\t\t\t\t\tcheck(new String[]{host}, x509);\n\t\t\t\t\treturn true;\n\t\t\t\t} catch (SSLException e) {\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\t\t\t}\n\t\t};\n\t\tHttpsURLConnection.setDefaultHostnameVerifier(verifier);\n\t}\n\n}\n```\n\n## References\n* Android developers: [Security with HTTPS and SSL](https://developer.android.com/training/articles/security-ssl).\n* Terse systems blog: [Fixing Hostname Verification](https://tersesystems.com/blog/2014/03/23/fixing-hostname-verification/).\n* Common Weakness Enumeration: [CWE-297](https://cwe.mitre.org/data/definitions/297.html).\n" + }, + "id": "java/unsafe-hostname-verification", + "name": "java/unsafe-hostname-verification", + "properties": { + "security-severity": "5.900000", + "tags": [ + "external/cwe/cwe-297", + "security" + ] + }, + "shortDescription": { + "text": "Unsafe hostname verification" + } + }, + { + "defaultConfiguration": { + "level": "error" + }, + "fullDescription": { + "text": "URL redirection based on unvalidated user-input may cause redirection to malicious web sites." + }, + "help": { + "markdown": "# URL redirection from remote source\nDirectly incorporating user input into a URL redirect request without validating the input can facilitate phishing attacks. In these attacks, unsuspecting users can be redirected to a malicious site that looks very similar to the real site they intend to visit, but which is controlled by the attacker.\n\n\n## Recommendation\nTo guard against untrusted URL redirection, it is advisable to avoid putting user input directly into a redirect URL. Instead, maintain a list of authorized redirects on the server; then choose from that list based on the user input provided.\n\n\n## Example\nThe following example shows an HTTP request parameter being used directly in a URL redirect without validating the input, which facilitates phishing attacks. It also shows how to remedy the problem by validating the user input against a known fixed string.\n\n\n```java\npublic class UrlRedirect extends HttpServlet {\n\tprivate static final String VALID_REDIRECT = \"http://cwe.mitre.org/data/definitions/601.html\";\n\n\tprotected void doGet(HttpServletRequest request, HttpServletResponse response)\n\tthrows ServletException, IOException {\n\t\t// BAD: a request parameter is incorporated without validation into a URL redirect\n\t\tresponse.sendRedirect(request.getParameter(\"target\"));\n\n\t\t// GOOD: the request parameter is validated against a known fixed string\n\t\tif (VALID_REDIRECT.equals(request.getParameter(\"target\"))) {\n\t\t\tresponse.sendRedirect(VALID_REDIRECT);\n\t\t}\n\t}\n}\n\n```\n\n## References\n* Common Weakness Enumeration: [CWE-601](https://cwe.mitre.org/data/definitions/601.html).\n", + "text": "# URL redirection from remote source\nDirectly incorporating user input into a URL redirect request without validating the input can facilitate phishing attacks. In these attacks, unsuspecting users can be redirected to a malicious site that looks very similar to the real site they intend to visit, but which is controlled by the attacker.\n\n\n## Recommendation\nTo guard against untrusted URL redirection, it is advisable to avoid putting user input directly into a redirect URL. Instead, maintain a list of authorized redirects on the server; then choose from that list based on the user input provided.\n\n\n## Example\nThe following example shows an HTTP request parameter being used directly in a URL redirect without validating the input, which facilitates phishing attacks. It also shows how to remedy the problem by validating the user input against a known fixed string.\n\n\n```java\npublic class UrlRedirect extends HttpServlet {\n\tprivate static final String VALID_REDIRECT = \"http://cwe.mitre.org/data/definitions/601.html\";\n\n\tprotected void doGet(HttpServletRequest request, HttpServletResponse response)\n\tthrows ServletException, IOException {\n\t\t// BAD: a request parameter is incorporated without validation into a URL redirect\n\t\tresponse.sendRedirect(request.getParameter(\"target\"));\n\n\t\t// GOOD: the request parameter is validated against a known fixed string\n\t\tif (VALID_REDIRECT.equals(request.getParameter(\"target\"))) {\n\t\t\tresponse.sendRedirect(VALID_REDIRECT);\n\t\t}\n\t}\n}\n\n```\n\n## References\n* Common Weakness Enumeration: [CWE-601](https://cwe.mitre.org/data/definitions/601.html).\n" + }, + "id": "java/unvalidated-url-redirection", + "name": "java/unvalidated-url-redirection", + "properties": { + "security-severity": "6.100000", + "tags": [ + "external/cwe/cwe-601", + "security" + ] + }, + "shortDescription": { + "text": "URL redirection from remote source" + } + }, + { + "defaultConfiguration": { + "level": "warning" + }, + "fullDescription": { + "text": "Using broken or weak cryptographic algorithms can allow an attacker to compromise security." + }, + "help": { + "markdown": "# Use of a broken or risky cryptographic algorithm\nUsing broken or weak cryptographic algorithms can leave data vulnerable to being decrypted.\n\nMany cryptographic algorithms provided by cryptography libraries are known to be weak, or flawed. Using such an algorithm means that an attacker may be able to easily decrypt the encrypted data.\n\n\n## Recommendation\nEnsure that you use a strong, modern cryptographic algorithm. Use at least AES-128 or RSA-2048. Do not use the ECB encryption mode since it is vulnerable to replay and other attacks.\n\n\n## Example\nThe following code shows an example of using a java `Cipher` to encrypt some data. When creating a `Cipher` instance, you must specify the encryption algorithm to use. The first example uses DES, which is an older algorithm that is now considered weak. The second example uses AES, which is a strong modern algorithm.\n\n\n```java\n// BAD: DES is a weak algorithm \nCipher des = Cipher.getInstance(\"DES\");\ncipher.init(Cipher.ENCRYPT_MODE, secretKeySpec);\n\nbyte[] encrypted = cipher.doFinal(input.getBytes(\"UTF-8\"));\n\n// ...\n\n// GOOD: AES is a strong algorithm\nCipher des = Cipher.getInstance(\"AES\");\n\n// ...\n```\n\n## References\n* NIST, FIPS 140 Annex a: [ Approved Security Functions](http://csrc.nist.gov/publications/fips/fips140-2/fips1402annexa.pdf).\n* NIST, SP 800-131A: [ Transitions: Recommendation for Transitioning the Use of Cryptographic Algorithms and Key Lengths](http://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-131Ar1.pdf).\n* Common Weakness Enumeration: [CWE-327](https://cwe.mitre.org/data/definitions/327.html).\n* Common Weakness Enumeration: [CWE-328](https://cwe.mitre.org/data/definitions/328.html).\n", + "text": "# Use of a broken or risky cryptographic algorithm\nUsing broken or weak cryptographic algorithms can leave data vulnerable to being decrypted.\n\nMany cryptographic algorithms provided by cryptography libraries are known to be weak, or flawed. Using such an algorithm means that an attacker may be able to easily decrypt the encrypted data.\n\n\n## Recommendation\nEnsure that you use a strong, modern cryptographic algorithm. Use at least AES-128 or RSA-2048. Do not use the ECB encryption mode since it is vulnerable to replay and other attacks.\n\n\n## Example\nThe following code shows an example of using a java `Cipher` to encrypt some data. When creating a `Cipher` instance, you must specify the encryption algorithm to use. The first example uses DES, which is an older algorithm that is now considered weak. The second example uses AES, which is a strong modern algorithm.\n\n\n```java\n// BAD: DES is a weak algorithm \nCipher des = Cipher.getInstance(\"DES\");\ncipher.init(Cipher.ENCRYPT_MODE, secretKeySpec);\n\nbyte[] encrypted = cipher.doFinal(input.getBytes(\"UTF-8\"));\n\n// ...\n\n// GOOD: AES is a strong algorithm\nCipher des = Cipher.getInstance(\"AES\");\n\n// ...\n```\n\n## References\n* NIST, FIPS 140 Annex a: [ Approved Security Functions](http://csrc.nist.gov/publications/fips/fips140-2/fips1402annexa.pdf).\n* NIST, SP 800-131A: [ Transitions: Recommendation for Transitioning the Use of Cryptographic Algorithms and Key Lengths](http://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-131Ar1.pdf).\n* Common Weakness Enumeration: [CWE-327](https://cwe.mitre.org/data/definitions/327.html).\n* Common Weakness Enumeration: [CWE-328](https://cwe.mitre.org/data/definitions/328.html).\n" + }, + "id": "java/weak-cryptographic-algorithm", + "name": "java/weak-cryptographic-algorithm", + "properties": { + "security-severity": "7.500000", + "tags": [ + "external/cwe/cwe-327", + "external/cwe/cwe-328", + "security" + ] + }, + "shortDescription": { + "text": "Use of a broken or risky cryptographic algorithm" + } + }, + { + "defaultConfiguration": { + "level": "error" + }, + "fullDescription": { + "text": "Reading from a file which is set as world writable is dangerous because the file may be modified or removed by external actors." + }, + "help": { + "markdown": "# Reading from a world writable file\nReading from a world-writable file is dangerous on a multi-user system because other users may be able to affect program execution by modifying or deleting the file.\n\n\n## Recommendation\nDo not make files explicitly world writable unless the file is intended to be written by multiple users on a multi-user system. In many cases, the file may only need to be writable for the current user.\n\nFor some file systems, there may be alternatives to setting the file to be world writable. For example, POSIX file systems support \"groups\" which may be used to ensure that only subset of all the users can write to the file. Access Control Lists (ACLs) are available for many operating system and file system combinations, and can provide fine-grained read and write support without resorting to world writable permissions.\n\n\n## Example\nIn the following example, we are loading some configuration parameters from a file:\n\n```java\n\nprivate void readConfig(File configFile) {\n if (!configFile.exists()) {\n // Create an empty config file\n configFile.createNewFile();\n // Make the file writable for all\n configFile.setWritable(true, false);\n }\n // Now read the config\n loadConfig(configFile);\n}\n\n```\nIf the configuration file does not yet exist, an empty file is created. Creating an empty file can simplify the later code and is a convenience for the user. However, by setting the file to be world writable, we allow any user on the system to modify the configuration, not just the current user. If there may be untrusted users on the system, this is potentially dangerous.\n\n\n## References\n* SEI CERT Oracle Coding Standard for Java: [FIO01-J. Create files with appropriate access permissions](https://wiki.sei.cmu.edu/confluence/display/java/FIO01-J.+Create+files+with+appropriate+access+permissions).\n* Common Weakness Enumeration: [CWE-732](https://cwe.mitre.org/data/definitions/732.html).\n", + "text": "# Reading from a world writable file\nReading from a world-writable file is dangerous on a multi-user system because other users may be able to affect program execution by modifying or deleting the file.\n\n\n## Recommendation\nDo not make files explicitly world writable unless the file is intended to be written by multiple users on a multi-user system. In many cases, the file may only need to be writable for the current user.\n\nFor some file systems, there may be alternatives to setting the file to be world writable. For example, POSIX file systems support \"groups\" which may be used to ensure that only subset of all the users can write to the file. Access Control Lists (ACLs) are available for many operating system and file system combinations, and can provide fine-grained read and write support without resorting to world writable permissions.\n\n\n## Example\nIn the following example, we are loading some configuration parameters from a file:\n\n```java\n\nprivate void readConfig(File configFile) {\n if (!configFile.exists()) {\n // Create an empty config file\n configFile.createNewFile();\n // Make the file writable for all\n configFile.setWritable(true, false);\n }\n // Now read the config\n loadConfig(configFile);\n}\n\n```\nIf the configuration file does not yet exist, an empty file is created. Creating an empty file can simplify the later code and is a convenience for the user. However, by setting the file to be world writable, we allow any user on the system to modify the configuration, not just the current user. If there may be untrusted users on the system, this is potentially dangerous.\n\n\n## References\n* SEI CERT Oracle Coding Standard for Java: [FIO01-J. Create files with appropriate access permissions](https://wiki.sei.cmu.edu/confluence/display/java/FIO01-J.+Create+files+with+appropriate+access+permissions).\n* Common Weakness Enumeration: [CWE-732](https://cwe.mitre.org/data/definitions/732.html).\n" + }, + "id": "java/world-writable-file-read", + "name": "java/world-writable-file-read", + "properties": { + "security-severity": "7.800000", + "tags": [ + "external/cwe/cwe-732", + "security" + ] + }, + "shortDescription": { + "text": "Reading from a world writable file" + } + }, + { + "defaultConfiguration": { + "level": "error" + }, + "fullDescription": { + "text": "Building an XPath expression from user-controlled sources is vulnerable to insertion of malicious code by the user." + }, + "help": { + "markdown": "# XPath injection\nIf an XPath expression is built using string concatenation, and the components of the concatenation include user input, it makes it very easy for a user to create a malicious XPath expression.\n\n\n## Recommendation\nIf user input must be included in an XPath expression, either sanitize the data or pre-compile the query and use variable references to include the user input.\n\nXPath injection can also be prevented by using XQuery.\n\n\n## Example\nIn the first three examples, the code accepts a name and password specified by the user, and uses this unvalidated and unsanitized value in an XPath expression. This is vulnerable to the user providing special characters or string sequences that change the meaning of the XPath expression to search for different values.\n\nIn the fourth example, the code uses `setXPathVariableResolver` which prevents XPath injection.\n\nThe final two examples are for dom4j. They show an example of XPath injection and one method of preventing it.\n\n\n```java\nfinal String xmlStr = \"\" + \n \" \" + \n \" \" + \n \"\";\ntry {\n DocumentBuilderFactory domFactory = DocumentBuilderFactory.newInstance();\n domFactory.setNamespaceAware(true);\n DocumentBuilder builder = domFactory.newDocumentBuilder();\n //Document doc = builder.parse(\"user.xml\");\n Document doc = builder.parse(new InputSource(new StringReader(xmlStr)));\n\n XPathFactory factory = XPathFactory.newInstance();\n XPath xpath = factory.newXPath();\n\n // Injectable data\n String user = request.getParameter(\"user\");\n String pass = request.getParameter(\"pass\");\n if (user != null && pass != null) {\n boolean isExist = false;\n\n // Bad expression\n String expression1 = \"/users/user[@name='\" + user + \"' and @pass='\" + pass + \"']\";\n isExist = (boolean)xpath.evaluate(expression1, doc, XPathConstants.BOOLEAN);\n System.out.println(isExist);\n\n // Bad expression\n XPathExpression expression2 = xpath.compile(\"/users/user[@name='\" + user + \"' and @pass='\" + pass + \"']\");\n isExist = (boolean)expression2.evaluate(doc, XPathConstants.BOOLEAN);\n System.out.println(isExist);\n\n // Bad expression\n StringBuffer sb = new StringBuffer(\"/users/user[@name=\");\n sb.append(user);\n sb.append(\"' and @pass='\");\n sb.append(pass);\n sb.append(\"']\");\n String query = sb.toString();\n XPathExpression expression3 = xpath.compile(query);\n isExist = (boolean)expression3.evaluate(doc, XPathConstants.BOOLEAN);\n System.out.println(isExist);\n\n // Good expression\n String expression4 = \"/users/user[@name=$user and @pass=$pass]\";\n xpath.setXPathVariableResolver(v -> {\n switch (v.getLocalPart()) {\n case \"user\":\n return user;\n case \"pass\":\n return pass;\n default:\n throw new IllegalArgumentException();\n }\n });\n isExist = (boolean)xpath.evaluate(expression4, doc, XPathConstants.BOOLEAN);\n System.out.println(isExist);\n\n\n // Bad Dom4j \n org.dom4j.io.SAXReader reader = new org.dom4j.io.SAXReader();\n org.dom4j.Document document = reader.read(new InputSource(new StringReader(xmlStr)));\n isExist = document.selectSingleNode(\"/users/user[@name='\" + user + \"' and @pass='\" + pass + \"']\") != null;\n // or document.selectNodes\n System.out.println(isExist);\n\n // Good Dom4j\n org.jaxen.SimpleVariableContext svc = new org.jaxen.SimpleVariableContext();\n svc.setVariableValue(\"user\", user);\n svc.setVariableValue(\"pass\", pass);\n String xpathString = \"/users/user[@name=$user and @pass=$pass]\";\n org.dom4j.XPath safeXPath = document.createXPath(xpathString);\n safeXPath.setVariableContext(svc);\n isExist = safeXPath.selectSingleNode(document) != null;\n System.out.println(isExist);\n }\n} catch (ParserConfigurationException e) {\n\n} catch (SAXException e) {\n\n} catch (XPathExpressionException e) {\n\n} catch (org.dom4j.DocumentException e) {\n\n}\n```\n\n## References\n* OWASP: [Testing for XPath Injection](https://owasp.org/www-project-web-security-testing-guide/latest/4-Web_Application_Security_Testing/07-Input_Validation_Testing/09-Testing_for_XPath_Injection).\n* OWASP: [XPath Injection](https://owasp.org/www-community/attacks/XPATH_Injection).\n* Common Weakness Enumeration: [CWE-643](https://cwe.mitre.org/data/definitions/643.html).\n", + "text": "# XPath injection\nIf an XPath expression is built using string concatenation, and the components of the concatenation include user input, it makes it very easy for a user to create a malicious XPath expression.\n\n\n## Recommendation\nIf user input must be included in an XPath expression, either sanitize the data or pre-compile the query and use variable references to include the user input.\n\nXPath injection can also be prevented by using XQuery.\n\n\n## Example\nIn the first three examples, the code accepts a name and password specified by the user, and uses this unvalidated and unsanitized value in an XPath expression. This is vulnerable to the user providing special characters or string sequences that change the meaning of the XPath expression to search for different values.\n\nIn the fourth example, the code uses `setXPathVariableResolver` which prevents XPath injection.\n\nThe final two examples are for dom4j. They show an example of XPath injection and one method of preventing it.\n\n\n```java\nfinal String xmlStr = \"\" + \n \" \" + \n \" \" + \n \"\";\ntry {\n DocumentBuilderFactory domFactory = DocumentBuilderFactory.newInstance();\n domFactory.setNamespaceAware(true);\n DocumentBuilder builder = domFactory.newDocumentBuilder();\n //Document doc = builder.parse(\"user.xml\");\n Document doc = builder.parse(new InputSource(new StringReader(xmlStr)));\n\n XPathFactory factory = XPathFactory.newInstance();\n XPath xpath = factory.newXPath();\n\n // Injectable data\n String user = request.getParameter(\"user\");\n String pass = request.getParameter(\"pass\");\n if (user != null && pass != null) {\n boolean isExist = false;\n\n // Bad expression\n String expression1 = \"/users/user[@name='\" + user + \"' and @pass='\" + pass + \"']\";\n isExist = (boolean)xpath.evaluate(expression1, doc, XPathConstants.BOOLEAN);\n System.out.println(isExist);\n\n // Bad expression\n XPathExpression expression2 = xpath.compile(\"/users/user[@name='\" + user + \"' and @pass='\" + pass + \"']\");\n isExist = (boolean)expression2.evaluate(doc, XPathConstants.BOOLEAN);\n System.out.println(isExist);\n\n // Bad expression\n StringBuffer sb = new StringBuffer(\"/users/user[@name=\");\n sb.append(user);\n sb.append(\"' and @pass='\");\n sb.append(pass);\n sb.append(\"']\");\n String query = sb.toString();\n XPathExpression expression3 = xpath.compile(query);\n isExist = (boolean)expression3.evaluate(doc, XPathConstants.BOOLEAN);\n System.out.println(isExist);\n\n // Good expression\n String expression4 = \"/users/user[@name=$user and @pass=$pass]\";\n xpath.setXPathVariableResolver(v -> {\n switch (v.getLocalPart()) {\n case \"user\":\n return user;\n case \"pass\":\n return pass;\n default:\n throw new IllegalArgumentException();\n }\n });\n isExist = (boolean)xpath.evaluate(expression4, doc, XPathConstants.BOOLEAN);\n System.out.println(isExist);\n\n\n // Bad Dom4j \n org.dom4j.io.SAXReader reader = new org.dom4j.io.SAXReader();\n org.dom4j.Document document = reader.read(new InputSource(new StringReader(xmlStr)));\n isExist = document.selectSingleNode(\"/users/user[@name='\" + user + \"' and @pass='\" + pass + \"']\") != null;\n // or document.selectNodes\n System.out.println(isExist);\n\n // Good Dom4j\n org.jaxen.SimpleVariableContext svc = new org.jaxen.SimpleVariableContext();\n svc.setVariableValue(\"user\", user);\n svc.setVariableValue(\"pass\", pass);\n String xpathString = \"/users/user[@name=$user and @pass=$pass]\";\n org.dom4j.XPath safeXPath = document.createXPath(xpathString);\n safeXPath.setVariableContext(svc);\n isExist = safeXPath.selectSingleNode(document) != null;\n System.out.println(isExist);\n }\n} catch (ParserConfigurationException e) {\n\n} catch (SAXException e) {\n\n} catch (XPathExpressionException e) {\n\n} catch (org.dom4j.DocumentException e) {\n\n}\n```\n\n## References\n* OWASP: [Testing for XPath Injection](https://owasp.org/www-project-web-security-testing-guide/latest/4-Web_Application_Security_Testing/07-Input_Validation_Testing/09-Testing_for_XPath_Injection).\n* OWASP: [XPath Injection](https://owasp.org/www-community/attacks/XPATH_Injection).\n* Common Weakness Enumeration: [CWE-643](https://cwe.mitre.org/data/definitions/643.html).\n" + }, + "id": "java/xml/xpath-injection", + "name": "java/xml/xpath-injection", + "properties": { + "security-severity": "9.800000", + "tags": [ + "external/cwe/cwe-643", + "security" + ] + }, + "shortDescription": { + "text": "XPath injection" + } + }, + { + "defaultConfiguration": { + "level": "error" + }, + "fullDescription": { + "text": "Performing an XSLT transformation with user-controlled stylesheets can lead to information disclosure or execution of arbitrary code." + }, + "help": { + "markdown": "# XSLT transformation with user-controlled stylesheet\nXSLT (Extensible Stylesheet Language Transformations) is a language for transforming XML documents into other XML documents or other formats. Processing unvalidated XSLT stylesheets can allow attackers to read arbitrary files from the filesystem or to execute arbitrary code.\n\n\n## Recommendation\nThe general recommendation is to not process untrusted XSLT stylesheets. If user-provided stylesheets must be processed, enable the secure processing mode.\n\n\n## Example\nIn the following examples, the code accepts an XSLT stylesheet from the user and processes it.\n\nIn the first example, the user-provided XSLT stylesheet is parsed and processed.\n\nIn the second example, secure processing mode is enabled.\n\n\n```java\nimport javax.xml.XMLConstants;\nimport javax.xml.transform.TransformerFactory;\nimport javax.xml.transform.stream.StreamResult;\nimport javax.xml.transform.stream.StreamSource;\n\npublic void transform(Socket socket, String inputXml) throws Exception {\n StreamSource xslt = new StreamSource(socket.getInputStream());\n StreamSource xml = new StreamSource(new StringReader(inputXml));\n StringWriter result = new StringWriter();\n TransformerFactory factory = TransformerFactory.newInstance();\n\n // BAD: User provided XSLT stylesheet is processed\n factory.newTransformer(xslt).transform(xml, new StreamResult(result));\n\n // GOOD: The secure processing mode is enabled\n factory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);\n factory.newTransformer(xslt).transform(xml, new StreamResult(result));\n} \n```\n\n## References\n* Wikipedia: [XSLT](https://en.wikipedia.org/wiki/XSLT).\n* The Java Tutorials: [Transforming XML Data with XSLT](https://docs.oracle.com/javase/tutorial/jaxp/xslt/transformingXML.html).\n* [XSLT Injection Basics](https://blog.hunniccyber.com/ektron-cms-remote-code-execution-xslt-transform-injection-java/).\n* Common Weakness Enumeration: [CWE-74](https://cwe.mitre.org/data/definitions/74.html).\n", + "text": "# XSLT transformation with user-controlled stylesheet\nXSLT (Extensible Stylesheet Language Transformations) is a language for transforming XML documents into other XML documents or other formats. Processing unvalidated XSLT stylesheets can allow attackers to read arbitrary files from the filesystem or to execute arbitrary code.\n\n\n## Recommendation\nThe general recommendation is to not process untrusted XSLT stylesheets. If user-provided stylesheets must be processed, enable the secure processing mode.\n\n\n## Example\nIn the following examples, the code accepts an XSLT stylesheet from the user and processes it.\n\nIn the first example, the user-provided XSLT stylesheet is parsed and processed.\n\nIn the second example, secure processing mode is enabled.\n\n\n```java\nimport javax.xml.XMLConstants;\nimport javax.xml.transform.TransformerFactory;\nimport javax.xml.transform.stream.StreamResult;\nimport javax.xml.transform.stream.StreamSource;\n\npublic void transform(Socket socket, String inputXml) throws Exception {\n StreamSource xslt = new StreamSource(socket.getInputStream());\n StreamSource xml = new StreamSource(new StringReader(inputXml));\n StringWriter result = new StringWriter();\n TransformerFactory factory = TransformerFactory.newInstance();\n\n // BAD: User provided XSLT stylesheet is processed\n factory.newTransformer(xslt).transform(xml, new StreamResult(result));\n\n // GOOD: The secure processing mode is enabled\n factory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);\n factory.newTransformer(xslt).transform(xml, new StreamResult(result));\n} \n```\n\n## References\n* Wikipedia: [XSLT](https://en.wikipedia.org/wiki/XSLT).\n* The Java Tutorials: [Transforming XML Data with XSLT](https://docs.oracle.com/javase/tutorial/jaxp/xslt/transformingXML.html).\n* [XSLT Injection Basics](https://blog.hunniccyber.com/ektron-cms-remote-code-execution-xslt-transform-injection-java/).\n* Common Weakness Enumeration: [CWE-74](https://cwe.mitre.org/data/definitions/74.html).\n" + }, + "id": "java/xslt-injection", + "name": "java/xslt-injection", + "properties": { + "tags": [ + "external/cwe/cwe-074", + "security" + ] + }, + "shortDescription": { + "text": "XSLT transformation with user-controlled stylesheet" + } + }, + { + "defaultConfiguration": { + "level": "error" + }, + "fullDescription": { + "text": "Writing user input directly to a web page allows for a cross-site scripting vulnerability." + }, + "help": { + "markdown": "# Cross-site scripting\nDirectly writing user input (for example, an HTTP request parameter) to a web page, without properly sanitizing the input first, allows for a cross-site scripting vulnerability.\n\n\n## Recommendation\nTo guard against cross-site scripting, consider using contextual output encoding/escaping before writing user input to the page, or one of the other solutions that are mentioned in the reference.\n\n\n## Example\nThe following example shows the page parameter being written directly to the server error page, leaving the website vulnerable to cross-site scripting.\n\n\n```java\npublic class XSS extends HttpServlet {\n\tprotected void doGet(HttpServletRequest request, HttpServletResponse response)\n\tthrows ServletException, IOException {\n\t\t// BAD: a request parameter is written directly to an error response page\n\t\tresponse.sendError(HttpServletResponse.SC_NOT_FOUND,\n\t\t\t\t\"The page \\\"\" + request.getParameter(\"page\") + \"\\\" was not found.\");\n\t}\n}\n\n```\n\n## References\n* OWASP: [XSS (Cross Site Scripting) Prevention Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/Cross_Site_Scripting_Prevention_Cheat_Sheet.html).\n* Wikipedia: [Cross-site scripting](http://en.wikipedia.org/wiki/Cross-site_scripting).\n* Common Weakness Enumeration: [CWE-79](https://cwe.mitre.org/data/definitions/79.html).\n", + "text": "# Cross-site scripting\nDirectly writing user input (for example, an HTTP request parameter) to a web page, without properly sanitizing the input first, allows for a cross-site scripting vulnerability.\n\n\n## Recommendation\nTo guard against cross-site scripting, consider using contextual output encoding/escaping before writing user input to the page, or one of the other solutions that are mentioned in the reference.\n\n\n## Example\nThe following example shows the page parameter being written directly to the server error page, leaving the website vulnerable to cross-site scripting.\n\n\n```java\npublic class XSS extends HttpServlet {\n\tprotected void doGet(HttpServletRequest request, HttpServletResponse response)\n\tthrows ServletException, IOException {\n\t\t// BAD: a request parameter is written directly to an error response page\n\t\tresponse.sendError(HttpServletResponse.SC_NOT_FOUND,\n\t\t\t\t\"The page \\\"\" + request.getParameter(\"page\") + \"\\\" was not found.\");\n\t}\n}\n\n```\n\n## References\n* OWASP: [XSS (Cross Site Scripting) Prevention Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/Cross_Site_Scripting_Prevention_Cheat_Sheet.html).\n* Wikipedia: [Cross-site scripting](http://en.wikipedia.org/wiki/Cross-site_scripting).\n* Common Weakness Enumeration: [CWE-79](https://cwe.mitre.org/data/definitions/79.html).\n" + }, + "id": "java/xss", + "name": "java/xss", + "properties": { + "security-severity": "6.100000", + "tags": [ + "external/cwe/cwe-079", + "security" + ] + }, + "shortDescription": { + "text": "Cross-site scripting" + } + }, + { + "defaultConfiguration": { + "level": "error" + }, + "fullDescription": { + "text": "Parsing user-controlled XML documents and allowing expansion of external entity references may lead to disclosure of confidential data or denial of service." + }, + "help": { + "markdown": "# Resolving XML external entity in user-controlled data\nParsing untrusted XML files with a weakly configured XML parser may lead to an XML External Entity (XXE) attack. This type of attack uses external entity references to access arbitrary files on a system, carry out denial of service, or server side request forgery. Even when the result of parsing is not returned to the user, out-of-band data retrieval techniques may allow attackers to steal sensitive data. Denial of services can also be carried out in this situation.\n\nThere are many XML parsers for Java, and most of them are vulnerable to XXE because their default settings enable parsing of external entities. This query currently identifies vulnerable XML parsing from the following parsers: `javax.xml.parsers.DocumentBuilder`, `javax.xml.stream.XMLStreamReader`, `org.jdom.input.SAXBuilder`/`org.jdom2.input.SAXBuilder`, `javax.xml.parsers.SAXParser`,`org.dom4j.io.SAXReader`, `org.xml.sax.XMLReader`, `javax.xml.transform.sax.SAXSource`, `javax.xml.transform.TransformerFactory`, `javax.xml.transform.sax.SAXTransformerFactory`, `javax.xml.validation.SchemaFactory`, `javax.xml.bind.Unmarshaller` and `javax.xml.xpath.XPathExpression`.\n\n\n## Recommendation\nThe best way to prevent XXE attacks is to disable the parsing of any Document Type Declarations (DTDs) in untrusted data. If this is not possible you should disable the parsing of external general entities and external parameter entities. This improves security but the code will still be at risk of denial of service and server side request forgery attacks. Protection against denial of service attacks may also be implemented by setting entity expansion limits, which is done by default in recent JDK and JRE implementations. Because there are many different ways to disable external entity retrieval with varying support between different providers, in this query we choose to specifically check for the [OWASP recommended way](https://cheatsheetseries.owasp.org/cheatsheets/XML_External_Entity_Prevention_Cheat_Sheet.html#java) to disable external entity retrieval for a particular parser. There may be other ways of making a particular parser safe which deviate from these guidelines, in which case this query will continue to flag the parser as potentially dangerous.\n\n\n## Example\nThe following example calls `parse` on a `DocumentBuilder` that is not safely configured on untrusted data, and is therefore inherently unsafe.\n\n\n```java\npublic void parse(Socket sock) throws Exception {\n DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();\n DocumentBuilder builder = factory.newDocumentBuilder();\n builder.parse(sock.getInputStream()); //unsafe\n}\n\n```\nIn this example, the `DocumentBuilder` is created with DTD disabled, securing it against XXE attack.\n\n\n```java\npublic void disableDTDParse(Socket sock) throws Exception {\n DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();\n factory.setFeature(\"http://apache.org/xml/features/disallow-doctype-decl\", true);\n DocumentBuilder builder = factory.newDocumentBuilder();\n builder.parse(sock.getInputStream()); //safe\n}\n\n```\n\n## References\n* OWASP vulnerability description: [XML External Entity (XXE) Processing](https://www.owasp.org/index.php/XML_External_Entity_(XXE)_Processing).\n* OWASP guidance on parsing xml files: [XXE Prevention Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/XML_External_Entity_Prevention_Cheat_Sheet.html#java).\n* Paper by Timothy Morgen: [XML Schema, DTD, and Entity Attacks](https://research.nccgroup.com/2014/05/19/xml-schema-dtd-and-entity-attacks-a-compendium-of-known-techniques/)\n* Out-of-band data retrieval: Timur Yunusov & Alexey Osipov, Black hat EU 2013: [XML Out-Of-Band Data Retrieval](https://www.slideshare.net/qqlan/bh-ready-v4).\n* Denial of service attack (Billion laughs): [Billion Laughs.](https://en.wikipedia.org/wiki/Billion_laughs)\n* The Java Tutorials: [Processing Limit Definitions.](https://docs.oracle.com/javase/tutorial/jaxp/limits/limits.html)\n* Common Weakness Enumeration: [CWE-611](https://cwe.mitre.org/data/definitions/611.html).\n* Common Weakness Enumeration: [CWE-776](https://cwe.mitre.org/data/definitions/776.html).\n* Common Weakness Enumeration: [CWE-827](https://cwe.mitre.org/data/definitions/827.html).\n", + "text": "# Resolving XML external entity in user-controlled data\nParsing untrusted XML files with a weakly configured XML parser may lead to an XML External Entity (XXE) attack. This type of attack uses external entity references to access arbitrary files on a system, carry out denial of service, or server side request forgery. Even when the result of parsing is not returned to the user, out-of-band data retrieval techniques may allow attackers to steal sensitive data. Denial of services can also be carried out in this situation.\n\nThere are many XML parsers for Java, and most of them are vulnerable to XXE because their default settings enable parsing of external entities. This query currently identifies vulnerable XML parsing from the following parsers: `javax.xml.parsers.DocumentBuilder`, `javax.xml.stream.XMLStreamReader`, `org.jdom.input.SAXBuilder`/`org.jdom2.input.SAXBuilder`, `javax.xml.parsers.SAXParser`,`org.dom4j.io.SAXReader`, `org.xml.sax.XMLReader`, `javax.xml.transform.sax.SAXSource`, `javax.xml.transform.TransformerFactory`, `javax.xml.transform.sax.SAXTransformerFactory`, `javax.xml.validation.SchemaFactory`, `javax.xml.bind.Unmarshaller` and `javax.xml.xpath.XPathExpression`.\n\n\n## Recommendation\nThe best way to prevent XXE attacks is to disable the parsing of any Document Type Declarations (DTDs) in untrusted data. If this is not possible you should disable the parsing of external general entities and external parameter entities. This improves security but the code will still be at risk of denial of service and server side request forgery attacks. Protection against denial of service attacks may also be implemented by setting entity expansion limits, which is done by default in recent JDK and JRE implementations. Because there are many different ways to disable external entity retrieval with varying support between different providers, in this query we choose to specifically check for the [OWASP recommended way](https://cheatsheetseries.owasp.org/cheatsheets/XML_External_Entity_Prevention_Cheat_Sheet.html#java) to disable external entity retrieval for a particular parser. There may be other ways of making a particular parser safe which deviate from these guidelines, in which case this query will continue to flag the parser as potentially dangerous.\n\n\n## Example\nThe following example calls `parse` on a `DocumentBuilder` that is not safely configured on untrusted data, and is therefore inherently unsafe.\n\n\n```java\npublic void parse(Socket sock) throws Exception {\n DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();\n DocumentBuilder builder = factory.newDocumentBuilder();\n builder.parse(sock.getInputStream()); //unsafe\n}\n\n```\nIn this example, the `DocumentBuilder` is created with DTD disabled, securing it against XXE attack.\n\n\n```java\npublic void disableDTDParse(Socket sock) throws Exception {\n DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();\n factory.setFeature(\"http://apache.org/xml/features/disallow-doctype-decl\", true);\n DocumentBuilder builder = factory.newDocumentBuilder();\n builder.parse(sock.getInputStream()); //safe\n}\n\n```\n\n## References\n* OWASP vulnerability description: [XML External Entity (XXE) Processing](https://www.owasp.org/index.php/XML_External_Entity_(XXE)_Processing).\n* OWASP guidance on parsing xml files: [XXE Prevention Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/XML_External_Entity_Prevention_Cheat_Sheet.html#java).\n* Paper by Timothy Morgen: [XML Schema, DTD, and Entity Attacks](https://research.nccgroup.com/2014/05/19/xml-schema-dtd-and-entity-attacks-a-compendium-of-known-techniques/)\n* Out-of-band data retrieval: Timur Yunusov & Alexey Osipov, Black hat EU 2013: [XML Out-Of-Band Data Retrieval](https://www.slideshare.net/qqlan/bh-ready-v4).\n* Denial of service attack (Billion laughs): [Billion Laughs.](https://en.wikipedia.org/wiki/Billion_laughs)\n* The Java Tutorials: [Processing Limit Definitions.](https://docs.oracle.com/javase/tutorial/jaxp/limits/limits.html)\n* Common Weakness Enumeration: [CWE-611](https://cwe.mitre.org/data/definitions/611.html).\n* Common Weakness Enumeration: [CWE-776](https://cwe.mitre.org/data/definitions/776.html).\n* Common Weakness Enumeration: [CWE-827](https://cwe.mitre.org/data/definitions/827.html).\n" + }, + "id": "java/xxe", + "name": "java/xxe", + "properties": { + "security-severity": "9.100000", + "tags": [ + "external/cwe/cwe-611", + "external/cwe/cwe-776", + "external/cwe/cwe-827", + "security" + ] + }, + "shortDescription": { + "text": "Resolving XML external entity in user-controlled data" + } + }, + { + "defaultConfiguration": { + "level": "error" + }, + "fullDescription": { + "text": "Extracting files from a malicious archive without validating that the destination file path is within the destination directory can cause files outside the destination directory to be overwritten." + }, + "help": { + "markdown": "# Arbitrary file write during archive extraction (\"Zip Slip\")\nExtracting files from a malicious zip archive (or another archive format) without validating that the destination file path is within the destination directory can cause files outside the destination directory to be overwritten, due to the possible presence of directory traversal elements (`..`) in archive paths.\n\nZip archives contain archive entries representing each file in the archive. These entries include a file path for the entry, but these file paths are not restricted and may contain unexpected special elements such as the directory traversal element (`..`). If these file paths are used to determine an output file to write the contents of the archive item to, then the file may be written to an unexpected location. This can result in sensitive information being revealed or deleted, or an attacker being able to influence behavior by modifying unexpected files.\n\nFor example, if a zip file contains a file entry `..\\sneaky-file`, and the zip file is extracted to the directory `c:\\output`, then naively combining the paths would result in an output file path of `c:\\output\\..\\sneaky-file`, which would cause the file to be written to `c:\\sneaky-file`.\n\n\n## Recommendation\nEnsure that output paths constructed from zip archive entries are validated to prevent writing files to unexpected locations.\n\nThe recommended way of writing an output file from a zip archive entry is to verify that the normalized full path of the output file starts with a prefix that matches the destination directory. Path normalization can be done with either `java.io.File.getCanonicalFile()` or `java.nio.file.Path.normalize()`. Prefix checking can be done with `String.startsWith(..)`, but it is better to use `java.nio.file.Path.startsWith(..)`, as the latter works on complete path segments.\n\nAnother alternative is to validate archive entries against a whitelist of expected files.\n\n\n## Example\nIn this example, a file path taken from a zip archive item entry is combined with a destination directory. The result is used as the destination file path without verifying that the result is within the destination directory. If provided with a zip file containing an archive path like `..\\sneaky-file`, then this file would be written outside the destination directory.\n\n\n```java\nvoid writeZipEntry(ZipEntry entry, File destinationDir) {\n File file = new File(destinationDir, entry.getName());\n FileOutputStream fos = new FileOutputStream(file); // BAD\n // ... write entry to fos ...\n}\n\n```\nTo fix this vulnerability, we need to verify that the normalized `file` still has `destinationDir` as its prefix, and throw an exception if this is not the case.\n\n\n```java\nvoid writeZipEntry(ZipEntry entry, File destinationDir) {\n File file = new File(destinationDir, entry.getName());\n if (!file.toPath().normalize().startsWith(destinationDir.toPath()))\n throw new Exception(\"Bad zip entry\");\n FileOutputStream fos = new FileOutputStream(file); // OK\n // ... write entry to fos ...\n}\n\n```\n\n## References\n* Snyk: [Zip Slip Vulnerability](https://snyk.io/research/zip-slip-vulnerability).\n* OWASP: [Path Traversal](https://owasp.org/www-community/attacks/Path_Traversal).\n* Common Weakness Enumeration: [CWE-22](https://cwe.mitre.org/data/definitions/22.html).\n", + "text": "# Arbitrary file write during archive extraction (\"Zip Slip\")\nExtracting files from a malicious zip archive (or another archive format) without validating that the destination file path is within the destination directory can cause files outside the destination directory to be overwritten, due to the possible presence of directory traversal elements (`..`) in archive paths.\n\nZip archives contain archive entries representing each file in the archive. These entries include a file path for the entry, but these file paths are not restricted and may contain unexpected special elements such as the directory traversal element (`..`). If these file paths are used to determine an output file to write the contents of the archive item to, then the file may be written to an unexpected location. This can result in sensitive information being revealed or deleted, or an attacker being able to influence behavior by modifying unexpected files.\n\nFor example, if a zip file contains a file entry `..\\sneaky-file`, and the zip file is extracted to the directory `c:\\output`, then naively combining the paths would result in an output file path of `c:\\output\\..\\sneaky-file`, which would cause the file to be written to `c:\\sneaky-file`.\n\n\n## Recommendation\nEnsure that output paths constructed from zip archive entries are validated to prevent writing files to unexpected locations.\n\nThe recommended way of writing an output file from a zip archive entry is to verify that the normalized full path of the output file starts with a prefix that matches the destination directory. Path normalization can be done with either `java.io.File.getCanonicalFile()` or `java.nio.file.Path.normalize()`. Prefix checking can be done with `String.startsWith(..)`, but it is better to use `java.nio.file.Path.startsWith(..)`, as the latter works on complete path segments.\n\nAnother alternative is to validate archive entries against a whitelist of expected files.\n\n\n## Example\nIn this example, a file path taken from a zip archive item entry is combined with a destination directory. The result is used as the destination file path without verifying that the result is within the destination directory. If provided with a zip file containing an archive path like `..\\sneaky-file`, then this file would be written outside the destination directory.\n\n\n```java\nvoid writeZipEntry(ZipEntry entry, File destinationDir) {\n File file = new File(destinationDir, entry.getName());\n FileOutputStream fos = new FileOutputStream(file); // BAD\n // ... write entry to fos ...\n}\n\n```\nTo fix this vulnerability, we need to verify that the normalized `file` still has `destinationDir` as its prefix, and throw an exception if this is not the case.\n\n\n```java\nvoid writeZipEntry(ZipEntry entry, File destinationDir) {\n File file = new File(destinationDir, entry.getName());\n if (!file.toPath().normalize().startsWith(destinationDir.toPath()))\n throw new Exception(\"Bad zip entry\");\n FileOutputStream fos = new FileOutputStream(file); // OK\n // ... write entry to fos ...\n}\n\n```\n\n## References\n* Snyk: [Zip Slip Vulnerability](https://snyk.io/research/zip-slip-vulnerability).\n* OWASP: [Path Traversal](https://owasp.org/www-community/attacks/Path_Traversal).\n* Common Weakness Enumeration: [CWE-22](https://cwe.mitre.org/data/definitions/22.html).\n" + }, + "id": "java/zipslip", + "name": "java/zipslip", + "properties": { + "security-severity": "7.500000", + "tags": [ + "external/cwe/cwe-022", + "security" + ] + }, + "shortDescription": { + "text": "Arbitrary file write during archive extraction (\"Zip Slip\")" + } + } + ], + "semanticVersion": "0.0.8+50f546043a9ecea98b2bf34e7376ee9ef30a8779" + }, + { + "name": "codeql/javascript-examples", + "semanticVersion": "0.0.3+50f546043a9ecea98b2bf34e7376ee9ef30a8779" + }, + { + "name": "codeql/go-queries", + "semanticVersion": "0.0.7+14d227a232489bccc3e2661088452af185a2516b" + }, + { + "name": "codeql/python-queries", + "semanticVersion": "0.0.8+50f546043a9ecea98b2bf34e7376ee9ef30a8779" + }, + { + "name": "codeql/cpp-all", + "semanticVersion": "0.0.8+50f546043a9ecea98b2bf34e7376ee9ef30a8779" + }, + { + "name": "codeql/cpp-queries", + "semanticVersion": "0.0.8+50f546043a9ecea98b2bf34e7376ee9ef30a8779" + }, + { + "name": "codeql/javascript-queries", + "semanticVersion": "0.0.9+50f546043a9ecea98b2bf34e7376ee9ef30a8779" + }, + { + "name": "codeql/go-all", + "semanticVersion": "0.0.7+14d227a232489bccc3e2661088452af185a2516b" + }, + { + "name": "codeql/suite-helpers", + "semanticVersion": "0.0.3+50f546043a9ecea98b2bf34e7376ee9ef30a8779" + } + ] + }, + "versionControlProvenance": [ + { + "branch": "refs/heads/develop", + "repositoryUri": "https://github.com/nahsra/WebGoat", + "revisionId": "701eef1f1db29088d14480b3a2aad4610aa709df" + } + ] + } + ], + "$schema": "https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json", + "version": "2.1.0" +} diff --git a/languages/java/build.gradle.kts b/languages/java/build.gradle.kts index 103aadab9..c32632a2d 100644 --- a/languages/java/build.gradle.kts +++ b/languages/java/build.gradle.kts @@ -61,12 +61,19 @@ dependencies { implementation(libs.maven.model) implementation("io.openpixee:java-jdbc-parameterizer:0.0.7") // TODO bring into monorepo implementation("io.openpixee.maven:pom-operator:0.0.3") // TODO bring into monorepo + { + exclude(group = "com.google.inject", module = "guice") + } implementation(libs.openpixee.toolkit) implementation(libs.openpixee.toolkit.xstream) implementation(libs.picocli) implementation(libs.progressbar) implementation(libs.slf4j.api) + implementation(project(":languages:codemodder-common")) + implementation(project(":languages:codemodder-framework-java")) + implementation(project(":languages:codemodder-default-codemods")) + testCompileOnly(libs.jetbrains.annotations) testImplementation(testlibs.bundles.junit.jupiter) diff --git a/languages/java/src/main/java/io/openpixee/java/CodeTFReportGenerator.java b/languages/java/src/main/java/io/openpixee/java/CodeTFReportGenerator.java index feb2a2b74..e01adc753 100644 --- a/languages/java/src/main/java/io/openpixee/java/CodeTFReportGenerator.java +++ b/languages/java/src/main/java/io/openpixee/java/CodeTFReportGenerator.java @@ -5,6 +5,8 @@ import com.github.difflib.patch.Patch; import com.github.difflib.patch.PatchFailedException; import com.google.common.annotations.VisibleForTesting; +import io.codemodder.ChangedFile; +import io.codemodder.Weave; import io.github.pixee.codetf.CodeTFChange; import io.github.pixee.codetf.CodeTFConfiguration; import io.github.pixee.codetf.CodeTFFileExtensionScanned; diff --git a/languages/java/src/main/java/io/openpixee/java/FileBasedVisitor.java b/languages/java/src/main/java/io/openpixee/java/FileBasedVisitor.java index 3ee340556..d69eb2332 100644 --- a/languages/java/src/main/java/io/openpixee/java/FileBasedVisitor.java +++ b/languages/java/src/main/java/io/openpixee/java/FileBasedVisitor.java @@ -1,5 +1,6 @@ package io.openpixee.java; +import io.codemodder.ChangedFile; import java.io.File; import java.util.Set; diff --git a/languages/java/src/main/java/io/openpixee/java/FileWeaver.java b/languages/java/src/main/java/io/openpixee/java/FileWeaver.java index ec6550c07..16f0735eb 100644 --- a/languages/java/src/main/java/io/openpixee/java/FileWeaver.java +++ b/languages/java/src/main/java/io/openpixee/java/FileWeaver.java @@ -2,6 +2,7 @@ import static java.nio.file.Files.walk; +import io.codemodder.ChangedFile; import java.io.File; import java.io.IOException; import java.nio.file.Path; diff --git a/languages/java/src/main/java/io/openpixee/java/FileWeavingContext.java b/languages/java/src/main/java/io/openpixee/java/FileWeavingContext.java index c9dcb40df..de8df6bfa 100644 --- a/languages/java/src/main/java/io/openpixee/java/FileWeavingContext.java +++ b/languages/java/src/main/java/io/openpixee/java/FileWeavingContext.java @@ -1,6 +1,7 @@ package io.openpixee.java; import com.github.javaparser.ast.Node; +import io.codemodder.Weave; import java.io.File; import java.util.ArrayList; import java.util.List; diff --git a/languages/java/src/main/java/io/openpixee/java/JavaFixitCliRun.java b/languages/java/src/main/java/io/openpixee/java/JavaFixitCliRun.java index f7aedc702..1545e5c8c 100644 --- a/languages/java/src/main/java/io/openpixee/java/JavaFixitCliRun.java +++ b/languages/java/src/main/java/io/openpixee/java/JavaFixitCliRun.java @@ -2,6 +2,8 @@ import ch.qos.logback.classic.Level; import com.fasterxml.jackson.databind.ObjectMapper; +import io.codemodder.ChangedFile; +import io.codemodder.Weave; import io.github.pixee.codetf.CodeTFReport; import java.io.File; import java.io.IOException; diff --git a/languages/java/src/main/java/io/openpixee/java/JspLineWeave.java b/languages/java/src/main/java/io/openpixee/java/JspLineWeave.java index 165883ceb..a5dadab10 100644 --- a/languages/java/src/main/java/io/openpixee/java/JspLineWeave.java +++ b/languages/java/src/main/java/io/openpixee/java/JspLineWeave.java @@ -1,5 +1,6 @@ package io.openpixee.java; +import io.codemodder.DependencyGAV; import java.util.Objects; import java.util.Optional; import org.jetbrains.annotations.Nullable; diff --git a/languages/java/src/main/java/io/openpixee/java/SourceWeaver.java b/languages/java/src/main/java/io/openpixee/java/SourceWeaver.java index 295596b19..f7ba5e085 100644 --- a/languages/java/src/main/java/io/openpixee/java/SourceWeaver.java +++ b/languages/java/src/main/java/io/openpixee/java/SourceWeaver.java @@ -9,6 +9,7 @@ import com.github.javaparser.symbolsolver.resolution.typesolvers.CombinedTypeSolver; import com.github.javaparser.symbolsolver.resolution.typesolvers.JavaParserTypeSolver; import com.github.javaparser.symbolsolver.resolution.typesolvers.ReflectionTypeSolver; +import io.codemodder.ChangedFile; import java.io.File; import java.io.FileInputStream; import java.io.IOException; @@ -66,6 +67,8 @@ final class DefaultSourceWeaver implements SourceWeaver { LOG.debug("Files to scan: {}", totalFiles); + // CodemodInvoker codemodInvoker = new CodemodInvoker(); + try (ProgressBar pb = CLI.createProgressBuilderBase() .setTaskName("Scanning source files") @@ -74,10 +77,16 @@ final class DefaultSourceWeaver implements SourceWeaver { for (String javaFile : javaSourceFiles) { try { pb.step(); - final ChangedFile changedFile = + ChangedFile changedFile = scanIndividualJavaFile(javaParser, javaFile, visitorFactories, includesExcludes); if (changedFile != null) { changedFiles.add(changedFile); + } else { + changedFile = + scanIndividualJavaFileWithCodemodders(javaParser, javaFile, includesExcludes); + if (changedFile != null) { + changedFiles.add(changedFile); + } } } catch (UnparseableFileException e) { LOG.debug("Problem parsing file {}", javaFile, e); @@ -89,6 +98,14 @@ final class DefaultSourceWeaver implements SourceWeaver { return new WeavingResult.Default(changedFiles, unscannableFiles); } + private ChangedFile scanIndividualJavaFileWithCodemodders( + final JavaParser javaParser, + final String javaFile, + final IncludesExcludes includesExcludes) { + + return null; + } + private static class UnparseableFileException extends Exception { private UnparseableFileException(final String javaFile) { super(javaFile); @@ -140,7 +157,7 @@ private ChangedFile scanType( File modifiedFile = File.createTempFile(javaFile.getName(), ".java"); FileUtils.write(modifiedFile, modified, encoding); - return new ChangedFile.Default( + return ChangedFile.createDefault( javaFile.getAbsolutePath(), modifiedFile.getAbsolutePath(), context.weaves()); } diff --git a/languages/java/src/main/java/io/openpixee/java/WeavingResult.java b/languages/java/src/main/java/io/openpixee/java/WeavingResult.java index d9f6a9cd9..7bcca24e1 100644 --- a/languages/java/src/main/java/io/openpixee/java/WeavingResult.java +++ b/languages/java/src/main/java/io/openpixee/java/WeavingResult.java @@ -1,5 +1,6 @@ package io.openpixee.java; +import io.codemodder.ChangedFile; import java.util.Collections; import java.util.Objects; import java.util.Set; diff --git a/languages/java/src/main/java/io/openpixee/java/plugins/ReflectionInjectionVisitorFactory.java b/languages/java/src/main/java/io/openpixee/java/plugins/ReflectionInjectionVisitorFactory.java index 1ec6fafbd..0b50b692b 100644 --- a/languages/java/src/main/java/io/openpixee/java/plugins/ReflectionInjectionVisitorFactory.java +++ b/languages/java/src/main/java/io/openpixee/java/plugins/ReflectionInjectionVisitorFactory.java @@ -9,13 +9,13 @@ import com.github.javaparser.ast.expr.NameExpr; import com.github.javaparser.ast.visitor.ModifierVisitor; import com.github.javaparser.ast.visitor.Visitable; -import io.openpixee.java.DependencyGAV; +import io.codemodder.DependencyGAV; +import io.codemodder.Weave; import io.openpixee.java.DoNothingVisitor; import io.openpixee.java.FileWeavingContext; import io.openpixee.java.Sarif; import io.openpixee.java.TypeLocator; import io.openpixee.java.VisitorFactory; -import io.openpixee.java.Weave; import io.openpixee.java.ast.ASTTransforms; import io.openpixee.security.Reflection; import java.io.File; diff --git a/languages/java/src/main/java/io/openpixee/java/plugins/codeql/InsecureCookieVisitorFactory.java b/languages/java/src/main/java/io/openpixee/java/plugins/codeql/InsecureCookieVisitorFactory.java index 0dbe76792..20fb71def 100644 --- a/languages/java/src/main/java/io/openpixee/java/plugins/codeql/InsecureCookieVisitorFactory.java +++ b/languages/java/src/main/java/io/openpixee/java/plugins/codeql/InsecureCookieVisitorFactory.java @@ -12,11 +12,11 @@ import com.github.javaparser.ast.stmt.Statement; import com.github.javaparser.ast.visitor.ModifierVisitor; import com.github.javaparser.ast.visitor.Visitable; +import io.codemodder.Weave; import io.openpixee.java.DoNothingVisitor; import io.openpixee.java.FileWeavingContext; import io.openpixee.java.Sarif; import io.openpixee.java.VisitorFactory; -import io.openpixee.java.Weave; import io.openpixee.java.ast.ASTTransforms; import io.openpixee.java.ast.ASTs; import java.io.File; diff --git a/languages/java/src/main/java/io/openpixee/java/plugins/codeql/JDBCResourceLeakVisitorFactory.java b/languages/java/src/main/java/io/openpixee/java/plugins/codeql/JDBCResourceLeakVisitorFactory.java index 55f9e0445..a1f49082a 100644 --- a/languages/java/src/main/java/io/openpixee/java/plugins/codeql/JDBCResourceLeakVisitorFactory.java +++ b/languages/java/src/main/java/io/openpixee/java/plugins/codeql/JDBCResourceLeakVisitorFactory.java @@ -6,10 +6,10 @@ import com.github.javaparser.ast.expr.MethodCallExpr; import com.github.javaparser.ast.visitor.ModifierVisitor; import com.github.javaparser.ast.visitor.Visitable; +import io.codemodder.Weave; import io.openpixee.java.DoNothingVisitor; import io.openpixee.java.FileWeavingContext; import io.openpixee.java.VisitorFactory; -import io.openpixee.java.Weave; import java.io.File; import java.io.IOException; import java.util.List; diff --git a/languages/java/src/main/java/io/openpixee/java/plugins/codeql/JEXLInjectionVisitorFactory.java b/languages/java/src/main/java/io/openpixee/java/plugins/codeql/JEXLInjectionVisitorFactory.java index 0178ae32a..853c60416 100644 --- a/languages/java/src/main/java/io/openpixee/java/plugins/codeql/JEXLInjectionVisitorFactory.java +++ b/languages/java/src/main/java/io/openpixee/java/plugins/codeql/JEXLInjectionVisitorFactory.java @@ -7,11 +7,11 @@ import com.github.javaparser.ast.expr.NameExpr; import com.github.javaparser.ast.visitor.ModifierVisitor; import com.github.javaparser.ast.visitor.Visitable; -import io.openpixee.java.DependencyGAV; +import io.codemodder.DependencyGAV; +import io.codemodder.Weave; import io.openpixee.java.DoNothingVisitor; import io.openpixee.java.FileWeavingContext; import io.openpixee.java.VisitorFactory; -import io.openpixee.java.Weave; import java.io.File; import java.io.IOException; import java.util.List; diff --git a/languages/java/src/main/java/io/openpixee/java/plugins/codeql/MavenSecureURLVisitor.java b/languages/java/src/main/java/io/openpixee/java/plugins/codeql/MavenSecureURLVisitor.java index 0767123ff..ee593d2f6 100644 --- a/languages/java/src/main/java/io/openpixee/java/plugins/codeql/MavenSecureURLVisitor.java +++ b/languages/java/src/main/java/io/openpixee/java/plugins/codeql/MavenSecureURLVisitor.java @@ -2,10 +2,10 @@ import com.contrastsecurity.sarif.PhysicalLocation; import com.contrastsecurity.sarif.Result; -import io.openpixee.java.ChangedFile; +import io.codemodder.ChangedFile; +import io.codemodder.Weave; import io.openpixee.java.FileBasedVisitor; import io.openpixee.java.FileWeavingContext; -import io.openpixee.java.Weave; import io.openpixee.java.WeavingResult; import java.io.BufferedReader; import java.io.BufferedWriter; diff --git a/languages/java/src/main/java/io/openpixee/java/plugins/codeql/StackTraceExposureVisitorFactory.java b/languages/java/src/main/java/io/openpixee/java/plugins/codeql/StackTraceExposureVisitorFactory.java index d07977126..de0fa08bc 100644 --- a/languages/java/src/main/java/io/openpixee/java/plugins/codeql/StackTraceExposureVisitorFactory.java +++ b/languages/java/src/main/java/io/openpixee/java/plugins/codeql/StackTraceExposureVisitorFactory.java @@ -11,10 +11,10 @@ import com.github.javaparser.ast.expr.MethodCallExpr; import com.github.javaparser.ast.visitor.ModifierVisitor; import com.github.javaparser.ast.visitor.Visitable; +import io.codemodder.Weave; import io.openpixee.java.DoNothingVisitor; import io.openpixee.java.FileWeavingContext; import io.openpixee.java.VisitorFactory; -import io.openpixee.java.Weave; import java.io.File; import java.io.IOException; import java.util.List; diff --git a/languages/java/src/main/java/io/openpixee/java/plugins/codeql/UnverifiedJwtParseVisitorFactory.java b/languages/java/src/main/java/io/openpixee/java/plugins/codeql/UnverifiedJwtParseVisitorFactory.java index de6aa8e5a..6fa7bc61a 100644 --- a/languages/java/src/main/java/io/openpixee/java/plugins/codeql/UnverifiedJwtParseVisitorFactory.java +++ b/languages/java/src/main/java/io/openpixee/java/plugins/codeql/UnverifiedJwtParseVisitorFactory.java @@ -8,10 +8,10 @@ import com.github.javaparser.ast.expr.MethodCallExpr; import com.github.javaparser.ast.visitor.ModifierVisitor; import com.github.javaparser.ast.visitor.Visitable; +import io.codemodder.Weave; import io.openpixee.java.DoNothingVisitor; import io.openpixee.java.FileWeavingContext; import io.openpixee.java.VisitorFactory; -import io.openpixee.java.Weave; import java.io.File; import java.io.IOException; import java.util.List; diff --git a/languages/java/src/main/java/io/openpixee/java/plugins/contrast/JavaXssVisitorFactory.java b/languages/java/src/main/java/io/openpixee/java/plugins/contrast/JavaXssVisitorFactory.java index d1a42adc5..dc2c8b61f 100644 --- a/languages/java/src/main/java/io/openpixee/java/plugins/contrast/JavaXssVisitorFactory.java +++ b/languages/java/src/main/java/io/openpixee/java/plugins/contrast/JavaXssVisitorFactory.java @@ -14,13 +14,13 @@ import com.github.javaparser.ast.expr.NameExpr; import com.github.javaparser.ast.visitor.ModifierVisitor; import com.github.javaparser.ast.visitor.Visitable; -import io.openpixee.java.DependencyGAV; +import io.codemodder.DependencyGAV; +import io.codemodder.Weave; import io.openpixee.java.DoNothingVisitor; import io.openpixee.java.FileWeavingContext; import io.openpixee.java.Sarif; import io.openpixee.java.TypeLocator; import io.openpixee.java.VisitorFactory; -import io.openpixee.java.Weave; import io.openpixee.java.ast.ASTTransforms; import java.io.File; import java.io.IOException; diff --git a/languages/java/src/main/java/io/openpixee/java/plugins/contrast/JspExpressionOutputMethod.java b/languages/java/src/main/java/io/openpixee/java/plugins/contrast/JspExpressionOutputMethod.java index 540e7a2ea..4db180e73 100644 --- a/languages/java/src/main/java/io/openpixee/java/plugins/contrast/JspExpressionOutputMethod.java +++ b/languages/java/src/main/java/io/openpixee/java/plugins/contrast/JspExpressionOutputMethod.java @@ -1,6 +1,6 @@ package io.openpixee.java.plugins.contrast; -import io.openpixee.java.DependencyGAV; +import io.codemodder.DependencyGAV; import io.openpixee.java.JspLineWeave; import org.apache.commons.lang3.StringUtils; diff --git a/languages/java/src/main/java/io/openpixee/java/plugins/contrast/SarifBasedJspScriptletXSSVisitor.java b/languages/java/src/main/java/io/openpixee/java/plugins/contrast/SarifBasedJspScriptletXSSVisitor.java index 528fdfc45..123cd0295 100644 --- a/languages/java/src/main/java/io/openpixee/java/plugins/contrast/SarifBasedJspScriptletXSSVisitor.java +++ b/languages/java/src/main/java/io/openpixee/java/plugins/contrast/SarifBasedJspScriptletXSSVisitor.java @@ -1,12 +1,12 @@ package io.openpixee.java.plugins.contrast; import com.contrastsecurity.sarif.Result; -import io.openpixee.java.ChangedFile; -import io.openpixee.java.DependencyGAV; +import io.codemodder.ChangedFile; +import io.codemodder.DependencyGAV; +import io.codemodder.Weave; import io.openpixee.java.FileBasedVisitor; import io.openpixee.java.FileWeavingContext; import io.openpixee.java.JspLineWeave; -import io.openpixee.java.Weave; import io.openpixee.java.WeavingResult; import java.io.File; import java.io.IOException; diff --git a/languages/java/src/main/java/io/openpixee/java/protections/ApacheMultipartVisitorFactory.java b/languages/java/src/main/java/io/openpixee/java/protections/ApacheMultipartVisitorFactory.java index c3f7b1ab8..503089d92 100644 --- a/languages/java/src/main/java/io/openpixee/java/protections/ApacheMultipartVisitorFactory.java +++ b/languages/java/src/main/java/io/openpixee/java/protections/ApacheMultipartVisitorFactory.java @@ -6,13 +6,13 @@ import com.github.javaparser.ast.expr.NameExpr; import com.github.javaparser.ast.expr.StringLiteralExpr; import com.github.javaparser.ast.visitor.ModifierVisitor; -import io.openpixee.java.DependencyGAV; +import io.codemodder.DependencyGAV; +import io.codemodder.Weave; import io.openpixee.java.FileWeavingContext; import io.openpixee.java.MethodCallPredicateFactory; import io.openpixee.java.MethodCallTransformingModifierVisitor; import io.openpixee.java.Transformer; import io.openpixee.java.VisitorFactory; -import io.openpixee.java.Weave; import io.openpixee.java.ast.ASTTransforms; import io.openpixee.security.Filenames; import java.io.File; diff --git a/languages/java/src/main/java/io/openpixee/java/protections/DependencyInjectingVisitor.java b/languages/java/src/main/java/io/openpixee/java/protections/DependencyInjectingVisitor.java index 3dd46254b..6fe5d92a6 100644 --- a/languages/java/src/main/java/io/openpixee/java/protections/DependencyInjectingVisitor.java +++ b/languages/java/src/main/java/io/openpixee/java/protections/DependencyInjectingVisitor.java @@ -4,11 +4,11 @@ import com.github.difflib.patch.AbstractDelta; import com.github.difflib.patch.Patch; import com.google.common.annotations.VisibleForTesting; -import io.openpixee.java.ChangedFile; -import io.openpixee.java.DependencyGAV; +import io.codemodder.ChangedFile; +import io.codemodder.DependencyGAV; +import io.codemodder.Weave; import io.openpixee.java.FileBasedVisitor; import io.openpixee.java.FileWeavingContext; -import io.openpixee.java.Weave; import io.openpixee.java.WeavingResult; import io.openpixee.maven.operator.POMOperator; import io.openpixee.maven.operator.ProjectModel; diff --git a/languages/java/src/main/java/io/openpixee/java/protections/DeserializationVisitorFactory.java b/languages/java/src/main/java/io/openpixee/java/protections/DeserializationVisitorFactory.java index a1eda9695..73d74c34b 100644 --- a/languages/java/src/main/java/io/openpixee/java/protections/DeserializationVisitorFactory.java +++ b/languages/java/src/main/java/io/openpixee/java/protections/DeserializationVisitorFactory.java @@ -11,14 +11,14 @@ import com.github.javaparser.ast.stmt.ExpressionStmt; import com.github.javaparser.ast.stmt.Statement; import com.github.javaparser.ast.visitor.ModifierVisitor; -import io.openpixee.java.DependencyGAV; +import io.codemodder.DependencyGAV; +import io.codemodder.Weave; import io.openpixee.java.FileWeavingContext; import io.openpixee.java.MethodCallPredicateFactory; import io.openpixee.java.MethodCallTransformingModifierVisitor; import io.openpixee.java.TransformationException; import io.openpixee.java.Transformer; import io.openpixee.java.VisitorFactory; -import io.openpixee.java.Weave; import io.openpixee.java.ast.ASTTransforms; import io.openpixee.java.ast.ASTs; import io.openpixee.security.ObjectInputFilters; diff --git a/languages/java/src/main/java/io/openpixee/java/protections/HeaderInjectionVisitorFactory.java b/languages/java/src/main/java/io/openpixee/java/protections/HeaderInjectionVisitorFactory.java index aab9617b8..caea42c83 100644 --- a/languages/java/src/main/java/io/openpixee/java/protections/HeaderInjectionVisitorFactory.java +++ b/languages/java/src/main/java/io/openpixee/java/protections/HeaderInjectionVisitorFactory.java @@ -7,13 +7,13 @@ import com.github.javaparser.ast.expr.NameExpr; import com.github.javaparser.ast.expr.StringLiteralExpr; import com.github.javaparser.ast.visitor.ModifierVisitor; -import io.openpixee.java.DependencyGAV; +import io.codemodder.DependencyGAV; +import io.codemodder.Weave; import io.openpixee.java.FileWeavingContext; import io.openpixee.java.MethodCallPredicateFactory; import io.openpixee.java.MethodCallTransformingModifierVisitor; import io.openpixee.java.Transformer; import io.openpixee.java.VisitorFactory; -import io.openpixee.java.Weave; import io.openpixee.java.ast.ASTTransforms; import io.openpixee.security.Newlines; import java.io.File; diff --git a/languages/java/src/main/java/io/openpixee/java/protections/JakartaForwardVisitoryFactory.java b/languages/java/src/main/java/io/openpixee/java/protections/JakartaForwardVisitoryFactory.java index 34f089f67..9798741a9 100644 --- a/languages/java/src/main/java/io/openpixee/java/protections/JakartaForwardVisitoryFactory.java +++ b/languages/java/src/main/java/io/openpixee/java/protections/JakartaForwardVisitoryFactory.java @@ -6,13 +6,13 @@ import com.github.javaparser.ast.expr.NameExpr; import com.github.javaparser.ast.expr.StringLiteralExpr; import com.github.javaparser.ast.visitor.ModifierVisitor; -import io.openpixee.java.DependencyGAV; +import io.codemodder.DependencyGAV; +import io.codemodder.Weave; import io.openpixee.java.FileWeavingContext; import io.openpixee.java.MethodCallPredicateFactory; import io.openpixee.java.MethodCallTransformingModifierVisitor; import io.openpixee.java.Transformer; import io.openpixee.java.VisitorFactory; -import io.openpixee.java.Weave; import io.openpixee.java.ast.ASTTransforms; import io.openpixee.security.Jakarta; import java.io.File; diff --git a/languages/java/src/main/java/io/openpixee/java/protections/JspScriptletXSSVisitor.java b/languages/java/src/main/java/io/openpixee/java/protections/JspScriptletXSSVisitor.java index 5ad5cec77..de37c3737 100644 --- a/languages/java/src/main/java/io/openpixee/java/protections/JspScriptletXSSVisitor.java +++ b/languages/java/src/main/java/io/openpixee/java/protections/JspScriptletXSSVisitor.java @@ -3,7 +3,7 @@ import static org.apache.commons.lang3.StringUtils.endsWithIgnoreCase; import com.google.common.annotations.VisibleForTesting; -import io.openpixee.java.DependencyGAV; +import io.codemodder.DependencyGAV; import java.util.regex.Pattern; import org.jetbrains.annotations.NotNull; diff --git a/languages/java/src/main/java/io/openpixee/java/protections/PredictableSeedVisitorFactory.java b/languages/java/src/main/java/io/openpixee/java/protections/PredictableSeedVisitorFactory.java index fc46f96ae..ca6d081e4 100644 --- a/languages/java/src/main/java/io/openpixee/java/protections/PredictableSeedVisitorFactory.java +++ b/languages/java/src/main/java/io/openpixee/java/protections/PredictableSeedVisitorFactory.java @@ -7,12 +7,12 @@ import com.github.javaparser.ast.expr.MethodCallExpr; import com.github.javaparser.ast.expr.NameExpr; import com.github.javaparser.ast.visitor.ModifierVisitor; +import io.codemodder.Weave; import io.openpixee.java.FileWeavingContext; import io.openpixee.java.MethodCallPredicateFactory; import io.openpixee.java.MethodCallTransformingModifierVisitor; import io.openpixee.java.Transformer; import io.openpixee.java.VisitorFactory; -import io.openpixee.java.Weave; import java.io.File; import java.util.List; import java.util.Optional; diff --git a/languages/java/src/main/java/io/openpixee/java/protections/RegexTextVisitor.java b/languages/java/src/main/java/io/openpixee/java/protections/RegexTextVisitor.java index 31686de1c..115280907 100644 --- a/languages/java/src/main/java/io/openpixee/java/protections/RegexTextVisitor.java +++ b/languages/java/src/main/java/io/openpixee/java/protections/RegexTextVisitor.java @@ -1,10 +1,10 @@ package io.openpixee.java.protections; -import io.openpixee.java.ChangedFile; -import io.openpixee.java.DependencyGAV; +import io.codemodder.ChangedFile; +import io.codemodder.DependencyGAV; +import io.codemodder.Weave; import io.openpixee.java.FileBasedVisitor; import io.openpixee.java.FileWeavingContext; -import io.openpixee.java.Weave; import io.openpixee.java.WeavingResult; import java.io.File; import java.io.IOException; diff --git a/languages/java/src/main/java/io/openpixee/java/protections/RuntimeExecVisitorFactory.java b/languages/java/src/main/java/io/openpixee/java/protections/RuntimeExecVisitorFactory.java index e0f3f468c..740d522b0 100644 --- a/languages/java/src/main/java/io/openpixee/java/protections/RuntimeExecVisitorFactory.java +++ b/languages/java/src/main/java/io/openpixee/java/protections/RuntimeExecVisitorFactory.java @@ -6,13 +6,13 @@ import com.github.javaparser.ast.expr.MethodCallExpr; import com.github.javaparser.ast.expr.NameExpr; import com.github.javaparser.ast.visitor.ModifierVisitor; -import io.openpixee.java.DependencyGAV; +import io.codemodder.DependencyGAV; +import io.codemodder.Weave; import io.openpixee.java.FileWeavingContext; import io.openpixee.java.MethodCallPredicateFactory; import io.openpixee.java.MethodCallTransformingModifierVisitor; import io.openpixee.java.Transformer; import io.openpixee.java.VisitorFactory; -import io.openpixee.java.Weave; import io.openpixee.java.ast.ASTTransforms; import io.openpixee.security.SystemCommand; import java.io.File; diff --git a/languages/java/src/main/java/io/openpixee/java/protections/SQLParameterizerVisitorFactory.java b/languages/java/src/main/java/io/openpixee/java/protections/SQLParameterizerVisitorFactory.java index dbe7f52da..13a554bc0 100644 --- a/languages/java/src/main/java/io/openpixee/java/protections/SQLParameterizerVisitorFactory.java +++ b/languages/java/src/main/java/io/openpixee/java/protections/SQLParameterizerVisitorFactory.java @@ -4,9 +4,9 @@ import com.github.javaparser.ast.expr.*; import com.github.javaparser.ast.visitor.ModifierVisitor; import com.github.javaparser.ast.visitor.Visitable; +import io.codemodder.Weave; import io.openpixee.java.FileWeavingContext; import io.openpixee.java.VisitorFactory; -import io.openpixee.java.Weave; import io.openpixee.jdbcparameterizer.SQLParameterizer; import java.io.File; diff --git a/languages/java/src/main/java/io/openpixee/java/protections/SSLContextGetInstanceVisitorFactory.java b/languages/java/src/main/java/io/openpixee/java/protections/SSLContextGetInstanceVisitorFactory.java index da8d3f446..79707858c 100644 --- a/languages/java/src/main/java/io/openpixee/java/protections/SSLContextGetInstanceVisitorFactory.java +++ b/languages/java/src/main/java/io/openpixee/java/protections/SSLContextGetInstanceVisitorFactory.java @@ -6,12 +6,12 @@ import com.github.javaparser.ast.expr.NameExpr; import com.github.javaparser.ast.expr.StringLiteralExpr; import com.github.javaparser.ast.visitor.ModifierVisitor; +import io.codemodder.Weave; import io.openpixee.java.FileWeavingContext; import io.openpixee.java.MethodCallPredicateFactory; import io.openpixee.java.MethodCallTransformingModifierVisitor; import io.openpixee.java.Transformer; import io.openpixee.java.VisitorFactory; -import io.openpixee.java.Weave; import java.io.File; import java.util.List; import java.util.Optional; diff --git a/languages/java/src/main/java/io/openpixee/java/protections/SSLProtocolsVisitorFactory.java b/languages/java/src/main/java/io/openpixee/java/protections/SSLProtocolsVisitorFactory.java index 185a25d05..4603fe813 100644 --- a/languages/java/src/main/java/io/openpixee/java/protections/SSLProtocolsVisitorFactory.java +++ b/languages/java/src/main/java/io/openpixee/java/protections/SSLProtocolsVisitorFactory.java @@ -10,12 +10,12 @@ import com.github.javaparser.ast.expr.StringLiteralExpr; import com.github.javaparser.ast.type.ClassOrInterfaceType; import com.github.javaparser.ast.visitor.ModifierVisitor; +import io.codemodder.Weave; import io.openpixee.java.FileWeavingContext; import io.openpixee.java.MethodCallPredicateFactory; import io.openpixee.java.MethodCallTransformingModifierVisitor; import io.openpixee.java.Transformer; import io.openpixee.java.VisitorFactory; -import io.openpixee.java.Weave; import java.io.File; import java.util.List; import java.util.Objects; diff --git a/languages/java/src/main/java/io/openpixee/java/protections/SSRFVisitorFactory.java b/languages/java/src/main/java/io/openpixee/java/protections/SSRFVisitorFactory.java index 0f7cd2a3d..4a7d8138e 100644 --- a/languages/java/src/main/java/io/openpixee/java/protections/SSRFVisitorFactory.java +++ b/languages/java/src/main/java/io/openpixee/java/protections/SSRFVisitorFactory.java @@ -6,10 +6,10 @@ import com.github.javaparser.ast.type.ClassOrInterfaceType; import com.github.javaparser.ast.visitor.ModifierVisitor; import com.github.javaparser.ast.visitor.Visitable; -import io.openpixee.java.DependencyGAV; +import io.codemodder.DependencyGAV; +import io.codemodder.Weave; import io.openpixee.java.FileWeavingContext; import io.openpixee.java.VisitorFactory; -import io.openpixee.java.Weave; import io.openpixee.java.ast.ASTTransforms; import io.openpixee.security.*; import java.io.File; diff --git a/languages/java/src/main/java/io/openpixee/java/protections/SpringMultipartVisitorFactory.java b/languages/java/src/main/java/io/openpixee/java/protections/SpringMultipartVisitorFactory.java index 767c57681..db01b401a 100644 --- a/languages/java/src/main/java/io/openpixee/java/protections/SpringMultipartVisitorFactory.java +++ b/languages/java/src/main/java/io/openpixee/java/protections/SpringMultipartVisitorFactory.java @@ -6,13 +6,13 @@ import com.github.javaparser.ast.expr.NameExpr; import com.github.javaparser.ast.expr.StringLiteralExpr; import com.github.javaparser.ast.visitor.ModifierVisitor; -import io.openpixee.java.DependencyGAV; +import io.codemodder.DependencyGAV; +import io.codemodder.Weave; import io.openpixee.java.FileWeavingContext; import io.openpixee.java.MethodCallPredicateFactory; import io.openpixee.java.MethodCallTransformingModifierVisitor; import io.openpixee.java.Transformer; import io.openpixee.java.VisitorFactory; -import io.openpixee.java.Weave; import io.openpixee.java.ast.ASTTransforms; import io.openpixee.security.Filenames; import java.io.File; diff --git a/languages/java/src/main/java/io/openpixee/java/protections/TransformationResult.java b/languages/java/src/main/java/io/openpixee/java/protections/TransformationResult.java index 779ee6b72..c2727eb05 100644 --- a/languages/java/src/main/java/io/openpixee/java/protections/TransformationResult.java +++ b/languages/java/src/main/java/io/openpixee/java/protections/TransformationResult.java @@ -1,6 +1,6 @@ package io.openpixee.java.protections; -import io.openpixee.java.Weave; +import io.codemodder.Weave; import java.util.Objects; import java.util.Optional; diff --git a/languages/java/src/main/java/io/openpixee/java/protections/UnsafeReadlineVisitorFactory.java b/languages/java/src/main/java/io/openpixee/java/protections/UnsafeReadlineVisitorFactory.java index 0d254bf6a..d5de80edc 100644 --- a/languages/java/src/main/java/io/openpixee/java/protections/UnsafeReadlineVisitorFactory.java +++ b/languages/java/src/main/java/io/openpixee/java/protections/UnsafeReadlineVisitorFactory.java @@ -4,13 +4,13 @@ import com.github.javaparser.ast.NodeList; import com.github.javaparser.ast.expr.*; import com.github.javaparser.ast.visitor.ModifierVisitor; -import io.openpixee.java.DependencyGAV; +import io.codemodder.DependencyGAV; +import io.codemodder.Weave; import io.openpixee.java.FileWeavingContext; import io.openpixee.java.MethodCallPredicateFactory; import io.openpixee.java.MethodCallTransformingModifierVisitor; import io.openpixee.java.Transformer; import io.openpixee.java.VisitorFactory; -import io.openpixee.java.Weave; import io.openpixee.java.ast.ASTTransforms; import io.openpixee.security.*; import java.io.BufferedReader; diff --git a/languages/java/src/main/java/io/openpixee/java/protections/VerbTamperingVisitor.java b/languages/java/src/main/java/io/openpixee/java/protections/VerbTamperingVisitor.java index 7b9e6cf8d..411486122 100644 --- a/languages/java/src/main/java/io/openpixee/java/protections/VerbTamperingVisitor.java +++ b/languages/java/src/main/java/io/openpixee/java/protections/VerbTamperingVisitor.java @@ -1,7 +1,7 @@ package io.openpixee.java.protections; import com.google.common.annotations.VisibleForTesting; -import io.openpixee.java.DependencyGAV; +import io.codemodder.DependencyGAV; import java.util.regex.Pattern; import org.jetbrains.annotations.NotNull; diff --git a/languages/java/src/main/java/io/openpixee/java/protections/WeakPRNGVisitorFactory.java b/languages/java/src/main/java/io/openpixee/java/protections/WeakPRNGVisitorFactory.java index eca6a60ed..354994b88 100644 --- a/languages/java/src/main/java/io/openpixee/java/protections/WeakPRNGVisitorFactory.java +++ b/languages/java/src/main/java/io/openpixee/java/protections/WeakPRNGVisitorFactory.java @@ -4,12 +4,12 @@ import com.github.javaparser.ast.expr.ObjectCreationExpr; import com.github.javaparser.ast.type.ClassOrInterfaceType; import com.github.javaparser.ast.visitor.ModifierVisitor; +import io.codemodder.Weave; import io.openpixee.java.FileWeavingContext; import io.openpixee.java.ObjectCreationPredicateFactory; import io.openpixee.java.ObjectCreationTransformingModifierVisitor; import io.openpixee.java.Transformer; import io.openpixee.java.VisitorFactory; -import io.openpixee.java.Weave; import io.openpixee.java.ast.ASTTransforms; import java.io.File; import java.security.SecureRandom; diff --git a/languages/java/src/main/java/io/openpixee/java/protections/XMLDecoderVisitorFactory.java b/languages/java/src/main/java/io/openpixee/java/protections/XMLDecoderVisitorFactory.java index 7da0dad8e..4294dffab 100644 --- a/languages/java/src/main/java/io/openpixee/java/protections/XMLDecoderVisitorFactory.java +++ b/languages/java/src/main/java/io/openpixee/java/protections/XMLDecoderVisitorFactory.java @@ -7,13 +7,13 @@ import com.github.javaparser.ast.expr.NameExpr; import com.github.javaparser.ast.expr.ObjectCreationExpr; import com.github.javaparser.ast.visitor.ModifierVisitor; -import io.openpixee.java.DependencyGAV; +import io.codemodder.DependencyGAV; +import io.codemodder.Weave; import io.openpixee.java.FileWeavingContext; import io.openpixee.java.ObjectCreationPredicateFactory; import io.openpixee.java.ObjectCreationTransformingModifierVisitor; import io.openpixee.java.Transformer; import io.openpixee.java.VisitorFactory; -import io.openpixee.java.Weave; import io.openpixee.java.ast.ASTTransforms; import io.openpixee.security.XMLDecoderSecurity; import java.io.File; diff --git a/languages/java/src/main/java/io/openpixee/java/protections/XStreamVisitorFactory.java b/languages/java/src/main/java/io/openpixee/java/protections/XStreamVisitorFactory.java index dbbd5433a..c39f83ea5 100644 --- a/languages/java/src/main/java/io/openpixee/java/protections/XStreamVisitorFactory.java +++ b/languages/java/src/main/java/io/openpixee/java/protections/XStreamVisitorFactory.java @@ -12,12 +12,12 @@ import com.github.javaparser.ast.stmt.Statement; import com.github.javaparser.ast.type.ClassOrInterfaceType; import com.github.javaparser.ast.visitor.ModifierVisitor; +import io.codemodder.Weave; import io.openpixee.java.FileWeavingContext; import io.openpixee.java.ObjectCreationPredicateFactory; import io.openpixee.java.ObjectCreationTransformingModifierVisitor; import io.openpixee.java.Transformer; import io.openpixee.java.VisitorFactory; -import io.openpixee.java.Weave; import io.openpixee.java.ast.ASTTransforms; import io.openpixee.java.ast.ASTs; import java.io.File; diff --git a/languages/java/src/main/java/io/openpixee/java/protections/XXEVisitorFactory.java b/languages/java/src/main/java/io/openpixee/java/protections/XXEVisitorFactory.java index 1450a9cb7..0a34019d8 100644 --- a/languages/java/src/main/java/io/openpixee/java/protections/XXEVisitorFactory.java +++ b/languages/java/src/main/java/io/openpixee/java/protections/XXEVisitorFactory.java @@ -9,13 +9,13 @@ import com.github.javaparser.ast.expr.NameExpr; import com.github.javaparser.ast.stmt.BlockStmt; import com.github.javaparser.ast.visitor.ModifierVisitor; -import io.openpixee.java.DependencyGAV; +import io.codemodder.DependencyGAV; +import io.codemodder.Weave; import io.openpixee.java.FileWeavingContext; import io.openpixee.java.MethodCallPredicateFactory; import io.openpixee.java.MethodCallTransformingModifierVisitor; import io.openpixee.java.Transformer; import io.openpixee.java.VisitorFactory; -import io.openpixee.java.Weave; import io.openpixee.java.ast.ASTTransforms; import io.openpixee.java.ast.ASTs; import io.openpixee.security.XMLInputFactorySecurity; diff --git a/languages/java/src/main/java/io/openpixee/java/protections/ZipFileOverwriteVisitoryFactory.java b/languages/java/src/main/java/io/openpixee/java/protections/ZipFileOverwriteVisitoryFactory.java index f5ba84e2d..bb967ff58 100644 --- a/languages/java/src/main/java/io/openpixee/java/protections/ZipFileOverwriteVisitoryFactory.java +++ b/languages/java/src/main/java/io/openpixee/java/protections/ZipFileOverwriteVisitoryFactory.java @@ -5,13 +5,13 @@ import com.github.javaparser.ast.expr.NameExpr; import com.github.javaparser.ast.expr.ObjectCreationExpr; import com.github.javaparser.ast.visitor.ModifierVisitor; -import io.openpixee.java.DependencyGAV; +import io.codemodder.DependencyGAV; +import io.codemodder.Weave; import io.openpixee.java.FileWeavingContext; import io.openpixee.java.ObjectCreationPredicateFactory; import io.openpixee.java.ObjectCreationToMethodCallTransformingModifierVisitor; import io.openpixee.java.Transformer; import io.openpixee.java.VisitorFactory; -import io.openpixee.java.Weave; import io.openpixee.java.ast.ASTTransforms; import io.openpixee.security.ZipSecurity; import java.io.File; diff --git a/languages/java/src/test/java/io/openpixee/java/plugins/codeql/MavenSecureURLTest.java b/languages/java/src/test/java/io/openpixee/java/plugins/codeql/MavenSecureURLTest.java index 5888c11b1..4b384cb4c 100644 --- a/languages/java/src/test/java/io/openpixee/java/plugins/codeql/MavenSecureURLTest.java +++ b/languages/java/src/test/java/io/openpixee/java/plugins/codeql/MavenSecureURLTest.java @@ -9,9 +9,9 @@ import com.github.difflib.DiffUtils; import com.github.difflib.patch.AbstractDelta; import com.github.difflib.patch.Patch; +import io.codemodder.Weave; import io.openpixee.java.FileWeavingContext; import io.openpixee.java.IncludesExcludes; -import io.openpixee.java.Weave; import io.openpixee.java.plugins.JavaSarifMockFactory; import java.io.File; import java.io.IOException; diff --git a/languages/java/src/test/java/io/openpixee/java/plugins/contrast/SarifBasedJspScriptletXSSWeaverTest.java b/languages/java/src/test/java/io/openpixee/java/plugins/contrast/SarifBasedJspScriptletXSSWeaverTest.java index a232d3f03..01d32688a 100644 --- a/languages/java/src/test/java/io/openpixee/java/plugins/contrast/SarifBasedJspScriptletXSSWeaverTest.java +++ b/languages/java/src/test/java/io/openpixee/java/plugins/contrast/SarifBasedJspScriptletXSSWeaverTest.java @@ -5,9 +5,9 @@ import static org.hamcrest.MatcherAssert.assertThat; import com.contrastsecurity.sarif.Result; -import io.openpixee.java.ChangedFile; -import io.openpixee.java.DependencyGAV; -import io.openpixee.java.Weave; +import io.codemodder.ChangedFile; +import io.codemodder.DependencyGAV; +import io.codemodder.Weave; import io.openpixee.java.protections.WeavingTests; import java.io.IOException; import java.util.List; diff --git a/languages/java/src/test/java/io/openpixee/java/protections/DependencyGAVInjectingTest.java b/languages/java/src/test/java/io/openpixee/java/protections/DependencyGAVInjectingTest.java index b98c222e4..37fcb759b 100644 --- a/languages/java/src/test/java/io/openpixee/java/protections/DependencyGAVInjectingTest.java +++ b/languages/java/src/test/java/io/openpixee/java/protections/DependencyGAVInjectingTest.java @@ -3,10 +3,10 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.Mockito.mock; -import io.openpixee.java.ChangedFile; -import io.openpixee.java.DependencyGAV; +import io.codemodder.ChangedFile; +import io.codemodder.DependencyGAV; +import io.codemodder.Weave; import io.openpixee.java.FileWeavingContext; -import io.openpixee.java.Weave; import io.openpixee.maven.operator.Dependency; import io.openpixee.maven.operator.POMOperator; import io.openpixee.maven.operator.ProjectModel; diff --git a/languages/java/src/test/java/io/openpixee/java/protections/JspScriptletXSSWeaverTest.java b/languages/java/src/test/java/io/openpixee/java/protections/JspScriptletXSSWeaverTest.java index 362f35d6d..e1ceba2fe 100644 --- a/languages/java/src/test/java/io/openpixee/java/protections/JspScriptletXSSWeaverTest.java +++ b/languages/java/src/test/java/io/openpixee/java/protections/JspScriptletXSSWeaverTest.java @@ -2,8 +2,8 @@ import static io.openpixee.java.protections.JspScriptletXSSVisitor.xssJspScriptletRuleId; -import io.openpixee.java.DependencyGAV; -import io.openpixee.java.Weave; +import io.codemodder.DependencyGAV; +import io.codemodder.Weave; import java.io.IOException; import org.hamcrest.CoreMatchers; import org.hamcrest.MatcherAssert; diff --git a/languages/java/src/test/java/io/openpixee/java/protections/WeavingTests.java b/languages/java/src/test/java/io/openpixee/java/protections/WeavingTests.java index 17a085564..49a6020c1 100644 --- a/languages/java/src/test/java/io/openpixee/java/protections/WeavingTests.java +++ b/languages/java/src/test/java/io/openpixee/java/protections/WeavingTests.java @@ -2,7 +2,7 @@ import static org.assertj.core.api.Assertions.assertThat; -import io.openpixee.java.ChangedFile; +import io.codemodder.ChangedFile; import io.openpixee.java.FileBasedVisitor; import io.openpixee.java.FileWeavingContext; import io.openpixee.java.IncludesExcludes; diff --git a/settings.gradle.kts b/settings.gradle.kts index eeae53f76..fd706b3e2 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -8,4 +8,4 @@ plugins { id("io.openpixee.codetl.settings") } -include("cli", "languages:java", "languages:javascript") +include("cli", "languages:java", "languages:javascript", "languages:codemodder-framework-java", "languages:codemodder-semgrep-provider", "languages:codemodder-default-codemods", "languages:codemodder-common") From 84f32b9e4fd7d461e59eae3095507e7def213420 Mon Sep 17 00:00:00 2001 From: Arshan Dabirsiaghi Date: Fri, 10 Mar 2023 20:44:40 -0500 Subject: [PATCH 02/19] i think all common files are exported to common package and can start trying to get 1 codemod to run side by side --- .../io/codemodder}/FileWeavingContext.java | 19 ++++-------- .../java/io/codemodder}/IncludesExcludes.java | 2 +- .../io/codemodder}/LineIncludesExcludes.java | 2 +- .../main/java/io/codemodder}/PathMatcher.java | 12 ++++---- .../java/io/codemodder/CodemodInvoker.java | 7 +++-- .../io/openpixee/java/DoNothingVisitor.java | 1 + .../io/openpixee/java/FileBasedVisitor.java | 1 + .../java/io/openpixee/java/FileWeaver.java | 4 ++- .../io/openpixee/java/JavaFixitCliRun.java | 4 ++- ...MethodCallTransformingModifierVisitor.java | 3 +- ...MethodCallTransformingModifierVisitor.java | 3 +- ...ctCreationTransformingModifierVisitor.java | 3 +- .../java/io/openpixee/java/SourceWeaver.java | 29 ++++++++++++------- .../java/io/openpixee/java/Transformer.java | 1 + .../io/openpixee/java/VisitorFactory.java | 1 + .../ReflectionInjectionVisitorFactory.java | 5 ++-- .../codeql/InsecureCookieVisitorFactory.java | 5 ++-- .../JDBCResourceLeakVisitorFactory.java | 2 +- .../codeql/JEXLInjectionVisitorFactory.java | 2 +- .../plugins/codeql/MavenSecureURLVisitor.java | 2 +- .../StackTraceExposureVisitorFactory.java | 5 ++-- .../UnverifiedJwtParseVisitorFactory.java | 5 ++-- .../contrast/JavaXssVisitorFactory.java | 5 ++-- .../SarifBasedJspScriptletXSSVisitor.java | 2 +- .../ApacheMultipartVisitorFactory.java | 2 +- .../DependencyInjectingVisitor.java | 2 +- .../DeserializationVisitorFactory.java | 2 +- .../HeaderInjectionVisitorFactory.java | 2 +- .../JakartaForwardVisitoryFactory.java | 2 +- .../java/protections/PMDVisitorFactory.java | 2 +- .../PredictableSeedVisitorFactory.java | 2 +- .../java/protections/RegexTextVisitor.java | 2 +- .../RuntimeExecVisitorFactory.java | 2 +- .../SQLParameterizerVisitorFactory.java | 2 +- .../SSLContextGetInstanceVisitorFactory.java | 2 +- .../SSLProtocolsVisitorFactory.java | 2 +- .../java/protections/SSRFVisitorFactory.java | 5 ++-- .../SpringMultipartVisitorFactory.java | 2 +- .../UnsafeReadlineVisitorFactory.java | 2 +- .../protections/WeakPRNGVisitorFactory.java | 2 +- .../protections/XMLDecoderVisitorFactory.java | 2 +- .../protections/XStreamVisitorFactory.java | 2 +- .../java/protections/XXEVisitorFactory.java | 2 +- .../ZipFileOverwriteVisitoryFactory.java | 2 +- .../openpixee/java/IncludesExcludesTest.java | 2 ++ .../io/openpixee/java/JavaFixitCliTest.java | 1 + .../io/openpixee/java/PathMatcherTest.java | 1 + .../plugins/codeql/JDBCResourceLeakTest.java | 2 +- .../plugins/codeql/JEXLInjectionTest.java | 2 +- .../plugins/codeql/MavenSecureURLTest.java | 4 +-- .../DependencyGAVInjectingTest.java | 2 +- .../java/protections/WeavingTests.java | 18 +++++++++--- 52 files changed, 116 insertions(+), 84 deletions(-) rename languages/{java/src/main/java/io/openpixee/java => codemodder-common/src/main/java/io/codemodder}/FileWeavingContext.java (77%) rename languages/{java/src/main/java/io/openpixee/java => codemodder-common/src/main/java/io/codemodder}/IncludesExcludes.java (99%) rename languages/{java/src/main/java/io/openpixee/java => codemodder-common/src/main/java/io/codemodder}/LineIncludesExcludes.java (98%) rename languages/{java/src/main/java/io/openpixee/java => codemodder-common/src/main/java/io/codemodder}/PathMatcher.java (87%) diff --git a/languages/java/src/main/java/io/openpixee/java/FileWeavingContext.java b/languages/codemodder-common/src/main/java/io/codemodder/FileWeavingContext.java similarity index 77% rename from languages/java/src/main/java/io/openpixee/java/FileWeavingContext.java rename to languages/codemodder-common/src/main/java/io/codemodder/FileWeavingContext.java index de8df6bfa..501fdd25a 100644 --- a/languages/java/src/main/java/io/openpixee/java/FileWeavingContext.java +++ b/languages/codemodder-common/src/main/java/io/codemodder/FileWeavingContext.java @@ -1,7 +1,5 @@ -package io.openpixee.java; +package io.codemodder; -import com.github.javaparser.ast.Node; -import io.codemodder.Weave; import java.io.File; import java.util.ArrayList; import java.util.List; @@ -17,9 +15,6 @@ public interface FileWeavingContext { boolean madeWeaves(); - /** Intended to be used when dealing with AST objects. */ - boolean isLineIncluded(Node n); - /** Intended to be used in non-JavaParser situations. */ boolean isLineIncluded(int line); @@ -48,14 +43,6 @@ public boolean madeWeaves() { return !weaves.isEmpty(); } - @Override - public boolean isLineIncluded(final Node n) { - if (n.getRange().isEmpty()) { - return true; - } - return includesExcludes.matches(n.getRange().get().begin.line); - } - @Override public boolean isLineIncluded(final int line) { return includesExcludes.matches(line); @@ -66,4 +53,8 @@ static FileWeavingContext createDefault( final File file, final IncludesExcludes includesExcludes) { return new Default(includesExcludes.getIncludesExcludesForFile(file)); } + + static FileWeavingContext createDefault(final LineIncludesExcludes includesExcludesForFile) { + return new Default(includesExcludesForFile); + } } diff --git a/languages/java/src/main/java/io/openpixee/java/IncludesExcludes.java b/languages/codemodder-common/src/main/java/io/codemodder/IncludesExcludes.java similarity index 99% rename from languages/java/src/main/java/io/openpixee/java/IncludesExcludes.java rename to languages/codemodder-common/src/main/java/io/codemodder/IncludesExcludes.java index a7658c4f4..0e4c0c063 100644 --- a/languages/java/src/main/java/io/openpixee/java/IncludesExcludes.java +++ b/languages/codemodder-common/src/main/java/io/codemodder/IncludesExcludes.java @@ -1,4 +1,4 @@ -package io.openpixee.java; +package io.codemodder; import com.google.common.annotations.VisibleForTesting; import java.io.File; diff --git a/languages/java/src/main/java/io/openpixee/java/LineIncludesExcludes.java b/languages/codemodder-common/src/main/java/io/codemodder/LineIncludesExcludes.java similarity index 98% rename from languages/java/src/main/java/io/openpixee/java/LineIncludesExcludes.java rename to languages/codemodder-common/src/main/java/io/codemodder/LineIncludesExcludes.java index 1b24ce212..418ac5924 100644 --- a/languages/java/src/main/java/io/openpixee/java/LineIncludesExcludes.java +++ b/languages/codemodder-common/src/main/java/io/codemodder/LineIncludesExcludes.java @@ -1,4 +1,4 @@ -package io.openpixee.java; +package io.codemodder; import java.util.Collections; import java.util.Objects; diff --git a/languages/java/src/main/java/io/openpixee/java/PathMatcher.java b/languages/codemodder-common/src/main/java/io/codemodder/PathMatcher.java similarity index 87% rename from languages/java/src/main/java/io/openpixee/java/PathMatcher.java rename to languages/codemodder-common/src/main/java/io/codemodder/PathMatcher.java index fcfe7f210..979a7f7a8 100644 --- a/languages/java/src/main/java/io/openpixee/java/PathMatcher.java +++ b/languages/codemodder-common/src/main/java/io/codemodder/PathMatcher.java @@ -1,4 +1,4 @@ -package io.openpixee.java; +package io.codemodder; import java.io.File; import java.nio.file.FileSystem; @@ -7,13 +7,13 @@ import java.util.Objects; /** This type is used in include/exclude logic for matching paths. */ -final class PathMatcher { +public final class PathMatcher { private final Integer line; private final String repositoryRootPath; private final java.nio.file.PathMatcher matcher; - PathMatcher( + public PathMatcher( final FileSystem fs, final File repositoryRoot, final String pathPattern, @@ -25,7 +25,7 @@ final class PathMatcher { } /** Return if this path matcher matches the given file. */ - boolean matches(final File file) { + public boolean matches(final File file) { String candidateFilePath = Path.of(file.getAbsolutePath()).normalize().toString(); String relativeCandidateFilePath = candidateFilePath.substring(repositoryRootPath.length()); if (!relativeCandidateFilePath.startsWith("/")) { @@ -34,11 +34,11 @@ boolean matches(final File file) { return matcher.matches(Paths.get(relativeCandidateFilePath)); } - Integer line() { + public Integer line() { return line; } - boolean targetsLine() { + public boolean targetsLine() { return line != null; } diff --git a/languages/codemodder-framework-java/src/main/java/io/codemodder/CodemodInvoker.java b/languages/codemodder-framework-java/src/main/java/io/codemodder/CodemodInvoker.java index da2f598dd..3f606842a 100644 --- a/languages/codemodder-framework-java/src/main/java/io/codemodder/CodemodInvoker.java +++ b/languages/codemodder-framework-java/src/main/java/io/codemodder/CodemodInvoker.java @@ -60,9 +60,10 @@ public CodemodInvoker(final List> codemodTypes, Path re /** * @param javaFile + * @param context * @return */ - public ChangedFile execute(final String javaFile) { + public ChangedFile execute(final Path file, final FileWeavingContext context) { // find a provider that can handle invoking the codemod "change phase" for (Class type : codemodTypes) { @@ -96,8 +97,8 @@ private static void validateRequiredFields(final Codemod codemodAnnotation) { } String id = codemodAnnotation.value(); - if (isValidCodemodId(id)) { - throw new IllegalArgumentException("must have an author"); + if (!isValidCodemodId(id)) { + throw new IllegalArgumentException("must have valid codemod id"); } ReviewGuidance reviewGuidance = codemodAnnotation.reviewGuidance(); diff --git a/languages/java/src/main/java/io/openpixee/java/DoNothingVisitor.java b/languages/java/src/main/java/io/openpixee/java/DoNothingVisitor.java index cfbed7748..ad6ffa466 100644 --- a/languages/java/src/main/java/io/openpixee/java/DoNothingVisitor.java +++ b/languages/java/src/main/java/io/openpixee/java/DoNothingVisitor.java @@ -1,6 +1,7 @@ package io.openpixee.java; import com.github.javaparser.ast.visitor.ModifierVisitor; +import io.codemodder.FileWeavingContext; /** * Implementation of {@link ModifierVisitor} that does nothing in case we need to return a no-op diff --git a/languages/java/src/main/java/io/openpixee/java/FileBasedVisitor.java b/languages/java/src/main/java/io/openpixee/java/FileBasedVisitor.java index d69eb2332..ced823eb0 100644 --- a/languages/java/src/main/java/io/openpixee/java/FileBasedVisitor.java +++ b/languages/java/src/main/java/io/openpixee/java/FileBasedVisitor.java @@ -1,6 +1,7 @@ package io.openpixee.java; import io.codemodder.ChangedFile; +import io.codemodder.FileWeavingContext; import java.io.File; import java.util.Set; diff --git a/languages/java/src/main/java/io/openpixee/java/FileWeaver.java b/languages/java/src/main/java/io/openpixee/java/FileWeaver.java index 16f0735eb..04e6d47f8 100644 --- a/languages/java/src/main/java/io/openpixee/java/FileWeaver.java +++ b/languages/java/src/main/java/io/openpixee/java/FileWeaver.java @@ -3,6 +3,8 @@ import static java.nio.file.Files.walk; import io.codemodder.ChangedFile; +import io.codemodder.FileWeavingContext; +import io.codemodder.IncludesExcludes; import java.io.File; import java.io.IOException; import java.nio.file.Path; @@ -53,7 +55,7 @@ public WeavingResult weave( for (File filePath : files) { var canonicalFile = filePath.getCanonicalFile(); var context = - new FileWeavingContext.Default( + FileWeavingContext.createDefault( includesExcludes.getIncludesExcludesForFile(filePath)); WeavingResult result = fileBasedWeaver.visitRepositoryFile( diff --git a/languages/java/src/main/java/io/openpixee/java/JavaFixitCliRun.java b/languages/java/src/main/java/io/openpixee/java/JavaFixitCliRun.java index 1545e5c8c..f89a87a78 100644 --- a/languages/java/src/main/java/io/openpixee/java/JavaFixitCliRun.java +++ b/languages/java/src/main/java/io/openpixee/java/JavaFixitCliRun.java @@ -3,6 +3,7 @@ import ch.qos.logback.classic.Level; import com.fasterxml.jackson.databind.ObjectMapper; import io.codemodder.ChangedFile; +import io.codemodder.IncludesExcludes; import io.codemodder.Weave; import io.github.pixee.codetf.CodeTFReport; import java.io.File; @@ -110,7 +111,8 @@ public CodeTFReport run( // run the Java code visitors final var javaSourceWeaveResult = - javaSourceWeaver.weave(sourceDirectories, allJavaFiles, factories, includesExcludes); + javaSourceWeaver.weave( + repositoryRoot, sourceDirectories, allJavaFiles, factories, includesExcludes); // get the non-Java code visitors final List fileBasedVisitors = diff --git a/languages/java/src/main/java/io/openpixee/java/MethodCallTransformingModifierVisitor.java b/languages/java/src/main/java/io/openpixee/java/MethodCallTransformingModifierVisitor.java index 18d759779..c2828f19d 100644 --- a/languages/java/src/main/java/io/openpixee/java/MethodCallTransformingModifierVisitor.java +++ b/languages/java/src/main/java/io/openpixee/java/MethodCallTransformingModifierVisitor.java @@ -4,6 +4,7 @@ import com.github.javaparser.ast.expr.MethodCallExpr; import com.github.javaparser.ast.visitor.ModifierVisitor; import com.github.javaparser.ast.visitor.Visitable; +import io.codemodder.FileWeavingContext; import io.openpixee.java.protections.TransformationResult; import java.util.List; import java.util.Objects; @@ -37,7 +38,7 @@ public Visitable visit(final MethodCallExpr methodCallExpr, final FileWeavingCon return super.visit(methodCallExpr, context); } } - if (context.isLineIncluded(methodCallExpr)) { + if (context.isLineIncluded(methodCallExpr.getRange().get().begin.line)) { try { TransformationResult result = transformer.transform(methodCallExpr, context); diff --git a/languages/java/src/main/java/io/openpixee/java/ObjectCreationToMethodCallTransformingModifierVisitor.java b/languages/java/src/main/java/io/openpixee/java/ObjectCreationToMethodCallTransformingModifierVisitor.java index 4547aadf1..7ba9e6fd3 100644 --- a/languages/java/src/main/java/io/openpixee/java/ObjectCreationToMethodCallTransformingModifierVisitor.java +++ b/languages/java/src/main/java/io/openpixee/java/ObjectCreationToMethodCallTransformingModifierVisitor.java @@ -5,6 +5,7 @@ import com.github.javaparser.ast.expr.ObjectCreationExpr; import com.github.javaparser.ast.visitor.ModifierVisitor; import com.github.javaparser.ast.visitor.Visitable; +import io.codemodder.FileWeavingContext; import io.openpixee.java.protections.TransformationResult; import java.util.List; import java.util.Objects; @@ -40,7 +41,7 @@ public Visitable visit( return super.visit(objectCreationExpr, context); } } - if (context.isLineIncluded(objectCreationExpr)) { + if (context.isLineIncluded(objectCreationExpr.getRange().get().begin.line)) { try { TransformationResult result = transformer.transform(objectCreationExpr, context); diff --git a/languages/java/src/main/java/io/openpixee/java/ObjectCreationTransformingModifierVisitor.java b/languages/java/src/main/java/io/openpixee/java/ObjectCreationTransformingModifierVisitor.java index c8173209c..e67839f81 100644 --- a/languages/java/src/main/java/io/openpixee/java/ObjectCreationTransformingModifierVisitor.java +++ b/languages/java/src/main/java/io/openpixee/java/ObjectCreationTransformingModifierVisitor.java @@ -4,6 +4,7 @@ import com.github.javaparser.ast.expr.ObjectCreationExpr; import com.github.javaparser.ast.visitor.ModifierVisitor; import com.github.javaparser.ast.visitor.Visitable; +import io.codemodder.FileWeavingContext; import io.openpixee.java.protections.TransformationResult; import java.util.List; import java.util.Objects; @@ -38,7 +39,7 @@ public Visitable visit( return super.visit(objectCreationExpr, context); } } - if (context.isLineIncluded(objectCreationExpr)) { + if (context.isLineIncluded(objectCreationExpr.getRange().get().begin.line)) { try { TransformationResult result = transformer.transform(objectCreationExpr, context); diff --git a/languages/java/src/main/java/io/openpixee/java/SourceWeaver.java b/languages/java/src/main/java/io/openpixee/java/SourceWeaver.java index f7ba5e085..9fbc2603a 100644 --- a/languages/java/src/main/java/io/openpixee/java/SourceWeaver.java +++ b/languages/java/src/main/java/io/openpixee/java/SourceWeaver.java @@ -10,10 +10,15 @@ import com.github.javaparser.symbolsolver.resolution.typesolvers.JavaParserTypeSolver; import com.github.javaparser.symbolsolver.resolution.typesolvers.ReflectionTypeSolver; import io.codemodder.ChangedFile; +import io.codemodder.CodemodInvoker; +import io.codemodder.FileWeavingContext; +import io.codemodder.IncludesExcludes; +import io.codemodder.codemods.SecureRandomCodemod; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; +import java.nio.file.Path; import java.util.HashSet; import java.util.List; import java.util.Set; @@ -30,6 +35,7 @@ public interface SourceWeaver { /** Go through the given source directories and return a {@link WeavingResult}. */ WeavingResult weave( + File repository, List javaSourceDirectories, List javaFiles, List visitorFactories, @@ -51,6 +57,7 @@ final class DefaultSourceWeaver implements SourceWeaver { @Override public @NotNull WeavingResult weave( + final File repository, final List javaSourceDirectories, final List javaSourceFiles, final List visitorFactories, @@ -67,7 +74,8 @@ final class DefaultSourceWeaver implements SourceWeaver { LOG.debug("Files to scan: {}", totalFiles); - // CodemodInvoker codemodInvoker = new CodemodInvoker(); + CodemodInvoker codemodInvoker = + new CodemodInvoker(List.of(SecureRandomCodemod.class), Path.of(repository.toURI())); try (ProgressBar pb = CLI.createProgressBuilderBase() @@ -78,15 +86,10 @@ final class DefaultSourceWeaver implements SourceWeaver { try { pb.step(); ChangedFile changedFile = - scanIndividualJavaFile(javaParser, javaFile, visitorFactories, includesExcludes); + scanIndividualJavaFile( + codemodInvoker, javaParser, javaFile, visitorFactories, includesExcludes); if (changedFile != null) { changedFiles.add(changedFile); - } else { - changedFile = - scanIndividualJavaFileWithCodemodders(javaParser, javaFile, includesExcludes); - if (changedFile != null) { - changedFiles.add(changedFile); - } } } catch (UnparseableFileException e) { LOG.debug("Problem parsing file {}", javaFile, e); @@ -115,6 +118,7 @@ private UnparseableFileException(final String javaFile) { /** Scan the file. */ @Nullable private ChangedFile scanIndividualJavaFile( + final CodemodInvoker codemodInvoker, final JavaParser javaParser, final String javaFile, final List visitorFactories, @@ -129,11 +133,12 @@ private ChangedFile scanIndividualJavaFile( final CompilationUnit cu = result.getResult().orElseThrow(); LexicalPreservingPrinter.setup(cu); - return scanType(file, cu, visitorFactories, includesExcludes); + return scanType(codemodInvoker, file, cu, visitorFactories, includesExcludes); } /** For each type in a Java source file, we scan through the code. */ private ChangedFile scanType( + final CodemodInvoker codemodInvoker, final File javaFile, final CompilationUnit cu, final List visitorFactories, @@ -141,8 +146,9 @@ private ChangedFile scanType( throws IOException { final FileWeavingContext context = - new FileWeavingContext.Default(includesExcludes.getIncludesExcludesForFile(javaFile)); + FileWeavingContext.createDefault(includesExcludes.getIncludesExcludesForFile(javaFile)); + // do it the old school way visitorFactories.forEach( vf -> { final ModifierVisitor visitor = @@ -150,6 +156,9 @@ private ChangedFile scanType( cu.accept(visitor, context); }); + // do it with codemods + ChangedFile execute = codemodInvoker.execute(Path.of(javaFile.toURI()), context); + if (context.madeWeaves()) { final String encoding = detectEncoding(javaFile); final String modified = (LexicalPreservingPrinter.print(cu)); diff --git a/languages/java/src/main/java/io/openpixee/java/Transformer.java b/languages/java/src/main/java/io/openpixee/java/Transformer.java index 84876250d..de5bb6c03 100644 --- a/languages/java/src/main/java/io/openpixee/java/Transformer.java +++ b/languages/java/src/main/java/io/openpixee/java/Transformer.java @@ -1,5 +1,6 @@ package io.openpixee.java; +import io.codemodder.FileWeavingContext; import io.openpixee.java.protections.TransformationResult; public interface Transformer { diff --git a/languages/java/src/main/java/io/openpixee/java/VisitorFactory.java b/languages/java/src/main/java/io/openpixee/java/VisitorFactory.java index 19e70e385..5dd224e07 100644 --- a/languages/java/src/main/java/io/openpixee/java/VisitorFactory.java +++ b/languages/java/src/main/java/io/openpixee/java/VisitorFactory.java @@ -2,6 +2,7 @@ import com.github.javaparser.ast.CompilationUnit; import com.github.javaparser.ast.visitor.ModifierVisitor; +import io.codemodder.FileWeavingContext; import java.io.File; /** diff --git a/languages/java/src/main/java/io/openpixee/java/plugins/ReflectionInjectionVisitorFactory.java b/languages/java/src/main/java/io/openpixee/java/plugins/ReflectionInjectionVisitorFactory.java index 0b50b692b..6edce27eb 100644 --- a/languages/java/src/main/java/io/openpixee/java/plugins/ReflectionInjectionVisitorFactory.java +++ b/languages/java/src/main/java/io/openpixee/java/plugins/ReflectionInjectionVisitorFactory.java @@ -10,9 +10,9 @@ import com.github.javaparser.ast.visitor.ModifierVisitor; import com.github.javaparser.ast.visitor.Visitable; import io.codemodder.DependencyGAV; +import io.codemodder.FileWeavingContext; import io.codemodder.Weave; import io.openpixee.java.DoNothingVisitor; -import io.openpixee.java.FileWeavingContext; import io.openpixee.java.Sarif; import io.openpixee.java.TypeLocator; import io.openpixee.java.VisitorFactory; @@ -100,7 +100,8 @@ public Visitable visit(MethodCallExpr methodCallExpr, final FileWeavingContext c Optional resultRef = Sarif.getFirstMatchingResult( results, startLine, methodCallExpr.getNameAsString()); - if (resultRef.isPresent() && context.isLineIncluded(methodCallExpr)) { + if (resultRef.isPresent() + && context.isLineIncluded(methodCallExpr.getRange().get().begin.line)) { ASTTransforms.addImportIfMissing(cu, Reflection.class); MethodCallExpr safeCall = new MethodCallExpr(); safeCall.setName("loadAndVerify"); diff --git a/languages/java/src/main/java/io/openpixee/java/plugins/codeql/InsecureCookieVisitorFactory.java b/languages/java/src/main/java/io/openpixee/java/plugins/codeql/InsecureCookieVisitorFactory.java index 20fb71def..2758c9aa5 100644 --- a/languages/java/src/main/java/io/openpixee/java/plugins/codeql/InsecureCookieVisitorFactory.java +++ b/languages/java/src/main/java/io/openpixee/java/plugins/codeql/InsecureCookieVisitorFactory.java @@ -12,9 +12,9 @@ import com.github.javaparser.ast.stmt.Statement; import com.github.javaparser.ast.visitor.ModifierVisitor; import com.github.javaparser.ast.visitor.Visitable; +import io.codemodder.FileWeavingContext; import io.codemodder.Weave; import io.openpixee.java.DoNothingVisitor; -import io.openpixee.java.FileWeavingContext; import io.openpixee.java.Sarif; import io.openpixee.java.VisitorFactory; import io.openpixee.java.ast.ASTTransforms; @@ -87,7 +87,8 @@ public Visitable visit(final MethodCallExpr methodCallExpr, final FileWeavingCon if ("addCookie".equals(methodCallExpr.getNameAsString()) && methodCallExpr.getArguments().size() > 0) { Optional expRange = methodCallExpr.getRange(); - if (expRange.isPresent() && context.isLineIncluded(methodCallExpr)) { + if (expRange.isPresent() + && context.isLineIncluded(methodCallExpr.getRange().get().begin.line)) { boolean instrumentedThisMethodCall = false; for (int i = 0; i < locations.size() && !instrumentedThisMethodCall; i++) { PhysicalLocation location = locations.get(i); diff --git a/languages/java/src/main/java/io/openpixee/java/plugins/codeql/JDBCResourceLeakVisitorFactory.java b/languages/java/src/main/java/io/openpixee/java/plugins/codeql/JDBCResourceLeakVisitorFactory.java index a1f49082a..690f5cd10 100644 --- a/languages/java/src/main/java/io/openpixee/java/plugins/codeql/JDBCResourceLeakVisitorFactory.java +++ b/languages/java/src/main/java/io/openpixee/java/plugins/codeql/JDBCResourceLeakVisitorFactory.java @@ -6,9 +6,9 @@ import com.github.javaparser.ast.expr.MethodCallExpr; import com.github.javaparser.ast.visitor.ModifierVisitor; import com.github.javaparser.ast.visitor.Visitable; +import io.codemodder.FileWeavingContext; import io.codemodder.Weave; import io.openpixee.java.DoNothingVisitor; -import io.openpixee.java.FileWeavingContext; import io.openpixee.java.VisitorFactory; import java.io.File; import java.io.IOException; diff --git a/languages/java/src/main/java/io/openpixee/java/plugins/codeql/JEXLInjectionVisitorFactory.java b/languages/java/src/main/java/io/openpixee/java/plugins/codeql/JEXLInjectionVisitorFactory.java index 853c60416..0bf4a4807 100644 --- a/languages/java/src/main/java/io/openpixee/java/plugins/codeql/JEXLInjectionVisitorFactory.java +++ b/languages/java/src/main/java/io/openpixee/java/plugins/codeql/JEXLInjectionVisitorFactory.java @@ -8,9 +8,9 @@ import com.github.javaparser.ast.visitor.ModifierVisitor; import com.github.javaparser.ast.visitor.Visitable; import io.codemodder.DependencyGAV; +import io.codemodder.FileWeavingContext; import io.codemodder.Weave; import io.openpixee.java.DoNothingVisitor; -import io.openpixee.java.FileWeavingContext; import io.openpixee.java.VisitorFactory; import java.io.File; import java.io.IOException; diff --git a/languages/java/src/main/java/io/openpixee/java/plugins/codeql/MavenSecureURLVisitor.java b/languages/java/src/main/java/io/openpixee/java/plugins/codeql/MavenSecureURLVisitor.java index ee593d2f6..63084065c 100644 --- a/languages/java/src/main/java/io/openpixee/java/plugins/codeql/MavenSecureURLVisitor.java +++ b/languages/java/src/main/java/io/openpixee/java/plugins/codeql/MavenSecureURLVisitor.java @@ -3,9 +3,9 @@ import com.contrastsecurity.sarif.PhysicalLocation; import com.contrastsecurity.sarif.Result; import io.codemodder.ChangedFile; +import io.codemodder.FileWeavingContext; import io.codemodder.Weave; import io.openpixee.java.FileBasedVisitor; -import io.openpixee.java.FileWeavingContext; import io.openpixee.java.WeavingResult; import java.io.BufferedReader; import java.io.BufferedWriter; diff --git a/languages/java/src/main/java/io/openpixee/java/plugins/codeql/StackTraceExposureVisitorFactory.java b/languages/java/src/main/java/io/openpixee/java/plugins/codeql/StackTraceExposureVisitorFactory.java index de0fa08bc..dc28477df 100644 --- a/languages/java/src/main/java/io/openpixee/java/plugins/codeql/StackTraceExposureVisitorFactory.java +++ b/languages/java/src/main/java/io/openpixee/java/plugins/codeql/StackTraceExposureVisitorFactory.java @@ -11,9 +11,9 @@ import com.github.javaparser.ast.expr.MethodCallExpr; import com.github.javaparser.ast.visitor.ModifierVisitor; import com.github.javaparser.ast.visitor.Visitable; +import io.codemodder.FileWeavingContext; import io.codemodder.Weave; import io.openpixee.java.DoNothingVisitor; -import io.openpixee.java.FileWeavingContext; import io.openpixee.java.VisitorFactory; import java.io.File; import java.io.IOException; @@ -85,7 +85,8 @@ public Visitable visit(final MethodCallExpr methodCallExpr, final FileWeavingCon && methodCallExpr.getArguments().size() == 2) { Region findingRegion = location.getPhysicalLocation().getRegion(); Optional expRange = methodCallExpr.getRange(); - if (expRange.isPresent() && context.isLineIncluded(methodCallExpr)) { + if (expRange.isPresent() + && context.isLineIncluded(methodCallExpr.getRange().get().begin.line)) { int startLine = expRange.get().begin.line; if (startLine == findingRegion.getStartLine()) { NodeList newArguments = NodeList.nodeList(methodCallExpr.getArgument(0)); diff --git a/languages/java/src/main/java/io/openpixee/java/plugins/codeql/UnverifiedJwtParseVisitorFactory.java b/languages/java/src/main/java/io/openpixee/java/plugins/codeql/UnverifiedJwtParseVisitorFactory.java index 6fa7bc61a..55a989e25 100644 --- a/languages/java/src/main/java/io/openpixee/java/plugins/codeql/UnverifiedJwtParseVisitorFactory.java +++ b/languages/java/src/main/java/io/openpixee/java/plugins/codeql/UnverifiedJwtParseVisitorFactory.java @@ -8,9 +8,9 @@ import com.github.javaparser.ast.expr.MethodCallExpr; import com.github.javaparser.ast.visitor.ModifierVisitor; import com.github.javaparser.ast.visitor.Visitable; +import io.codemodder.FileWeavingContext; import io.codemodder.Weave; import io.openpixee.java.DoNothingVisitor; -import io.openpixee.java.FileWeavingContext; import io.openpixee.java.VisitorFactory; import java.io.File; import java.io.IOException; @@ -94,7 +94,8 @@ public Visitable visit(final MethodCallExpr methodCallExpr, final FileWeavingCon if ("parse".equals(methodCallExpr.getNameAsString()) && methodCallExpr.getArguments().size() == 1) { Optional expRange = methodCallExpr.getRange(); - if (expRange.isPresent() && context.isLineIncluded(methodCallExpr)) { + if (expRange.isPresent() + && context.isLineIncluded(methodCallExpr.getRange().get().begin.line)) { boolean instrumentedThisMethodCall = false; for (int i = 0; i < locations.size() && !instrumentedThisMethodCall; i++) { PhysicalLocation location = locations.get(i); diff --git a/languages/java/src/main/java/io/openpixee/java/plugins/contrast/JavaXssVisitorFactory.java b/languages/java/src/main/java/io/openpixee/java/plugins/contrast/JavaXssVisitorFactory.java index dc2c8b61f..cd5458b9f 100644 --- a/languages/java/src/main/java/io/openpixee/java/plugins/contrast/JavaXssVisitorFactory.java +++ b/languages/java/src/main/java/io/openpixee/java/plugins/contrast/JavaXssVisitorFactory.java @@ -15,9 +15,9 @@ import com.github.javaparser.ast.visitor.ModifierVisitor; import com.github.javaparser.ast.visitor.Visitable; import io.codemodder.DependencyGAV; +import io.codemodder.FileWeavingContext; import io.codemodder.Weave; import io.openpixee.java.DoNothingVisitor; -import io.openpixee.java.FileWeavingContext; import io.openpixee.java.Sarif; import io.openpixee.java.TypeLocator; import io.openpixee.java.VisitorFactory; @@ -102,7 +102,8 @@ private JavaXssVisitor(final CompilationUnit cu, final Set results) { public Visitable visit(final MethodCallExpr methodCallExpr, final FileWeavingContext context) { if (httpResponseWriteNames.contains(methodCallExpr.getNameAsString())) { Optional expRange = methodCallExpr.getRange(); - if (expRange.isPresent() && context.isLineIncluded(methodCallExpr)) { + if (expRange.isPresent() + && context.isLineIncluded(methodCallExpr.getRange().get().begin.line)) { int startLine = expRange.get().begin.line; Optional resultRef = Sarif.getFirstMatchingResult(results, startLine, methodCallExpr.getNameAsString()); diff --git a/languages/java/src/main/java/io/openpixee/java/plugins/contrast/SarifBasedJspScriptletXSSVisitor.java b/languages/java/src/main/java/io/openpixee/java/plugins/contrast/SarifBasedJspScriptletXSSVisitor.java index 123cd0295..05dbf2c71 100644 --- a/languages/java/src/main/java/io/openpixee/java/plugins/contrast/SarifBasedJspScriptletXSSVisitor.java +++ b/languages/java/src/main/java/io/openpixee/java/plugins/contrast/SarifBasedJspScriptletXSSVisitor.java @@ -3,9 +3,9 @@ import com.contrastsecurity.sarif.Result; import io.codemodder.ChangedFile; import io.codemodder.DependencyGAV; +import io.codemodder.FileWeavingContext; import io.codemodder.Weave; import io.openpixee.java.FileBasedVisitor; -import io.openpixee.java.FileWeavingContext; import io.openpixee.java.JspLineWeave; import io.openpixee.java.WeavingResult; import java.io.File; diff --git a/languages/java/src/main/java/io/openpixee/java/protections/ApacheMultipartVisitorFactory.java b/languages/java/src/main/java/io/openpixee/java/protections/ApacheMultipartVisitorFactory.java index 503089d92..96ba3e859 100644 --- a/languages/java/src/main/java/io/openpixee/java/protections/ApacheMultipartVisitorFactory.java +++ b/languages/java/src/main/java/io/openpixee/java/protections/ApacheMultipartVisitorFactory.java @@ -7,8 +7,8 @@ import com.github.javaparser.ast.expr.StringLiteralExpr; import com.github.javaparser.ast.visitor.ModifierVisitor; import io.codemodder.DependencyGAV; +import io.codemodder.FileWeavingContext; import io.codemodder.Weave; -import io.openpixee.java.FileWeavingContext; import io.openpixee.java.MethodCallPredicateFactory; import io.openpixee.java.MethodCallTransformingModifierVisitor; import io.openpixee.java.Transformer; diff --git a/languages/java/src/main/java/io/openpixee/java/protections/DependencyInjectingVisitor.java b/languages/java/src/main/java/io/openpixee/java/protections/DependencyInjectingVisitor.java index 6fe5d92a6..0cd6a53ce 100644 --- a/languages/java/src/main/java/io/openpixee/java/protections/DependencyInjectingVisitor.java +++ b/languages/java/src/main/java/io/openpixee/java/protections/DependencyInjectingVisitor.java @@ -6,9 +6,9 @@ import com.google.common.annotations.VisibleForTesting; import io.codemodder.ChangedFile; import io.codemodder.DependencyGAV; +import io.codemodder.FileWeavingContext; import io.codemodder.Weave; import io.openpixee.java.FileBasedVisitor; -import io.openpixee.java.FileWeavingContext; import io.openpixee.java.WeavingResult; import io.openpixee.maven.operator.POMOperator; import io.openpixee.maven.operator.ProjectModel; diff --git a/languages/java/src/main/java/io/openpixee/java/protections/DeserializationVisitorFactory.java b/languages/java/src/main/java/io/openpixee/java/protections/DeserializationVisitorFactory.java index 73d74c34b..0a8f2e7d4 100644 --- a/languages/java/src/main/java/io/openpixee/java/protections/DeserializationVisitorFactory.java +++ b/languages/java/src/main/java/io/openpixee/java/protections/DeserializationVisitorFactory.java @@ -12,8 +12,8 @@ import com.github.javaparser.ast.stmt.Statement; import com.github.javaparser.ast.visitor.ModifierVisitor; import io.codemodder.DependencyGAV; +import io.codemodder.FileWeavingContext; import io.codemodder.Weave; -import io.openpixee.java.FileWeavingContext; import io.openpixee.java.MethodCallPredicateFactory; import io.openpixee.java.MethodCallTransformingModifierVisitor; import io.openpixee.java.TransformationException; diff --git a/languages/java/src/main/java/io/openpixee/java/protections/HeaderInjectionVisitorFactory.java b/languages/java/src/main/java/io/openpixee/java/protections/HeaderInjectionVisitorFactory.java index caea42c83..0756c0323 100644 --- a/languages/java/src/main/java/io/openpixee/java/protections/HeaderInjectionVisitorFactory.java +++ b/languages/java/src/main/java/io/openpixee/java/protections/HeaderInjectionVisitorFactory.java @@ -8,8 +8,8 @@ import com.github.javaparser.ast.expr.StringLiteralExpr; import com.github.javaparser.ast.visitor.ModifierVisitor; import io.codemodder.DependencyGAV; +import io.codemodder.FileWeavingContext; import io.codemodder.Weave; -import io.openpixee.java.FileWeavingContext; import io.openpixee.java.MethodCallPredicateFactory; import io.openpixee.java.MethodCallTransformingModifierVisitor; import io.openpixee.java.Transformer; diff --git a/languages/java/src/main/java/io/openpixee/java/protections/JakartaForwardVisitoryFactory.java b/languages/java/src/main/java/io/openpixee/java/protections/JakartaForwardVisitoryFactory.java index 9798741a9..83ec33bdb 100644 --- a/languages/java/src/main/java/io/openpixee/java/protections/JakartaForwardVisitoryFactory.java +++ b/languages/java/src/main/java/io/openpixee/java/protections/JakartaForwardVisitoryFactory.java @@ -7,8 +7,8 @@ import com.github.javaparser.ast.expr.StringLiteralExpr; import com.github.javaparser.ast.visitor.ModifierVisitor; import io.codemodder.DependencyGAV; +import io.codemodder.FileWeavingContext; import io.codemodder.Weave; -import io.openpixee.java.FileWeavingContext; import io.openpixee.java.MethodCallPredicateFactory; import io.openpixee.java.MethodCallTransformingModifierVisitor; import io.openpixee.java.Transformer; diff --git a/languages/java/src/main/java/io/openpixee/java/protections/PMDVisitorFactory.java b/languages/java/src/main/java/io/openpixee/java/protections/PMDVisitorFactory.java index 59e4cac84..1bb42a876 100644 --- a/languages/java/src/main/java/io/openpixee/java/protections/PMDVisitorFactory.java +++ b/languages/java/src/main/java/io/openpixee/java/protections/PMDVisitorFactory.java @@ -6,9 +6,9 @@ import com.github.javaparser.ast.visitor.ModifierVisitor; import com.github.javaparser.ast.visitor.Visitable; import com.google.gson.GsonBuilder; +import io.codemodder.FileWeavingContext; import io.codescan.sarif.model.*; import io.openpixee.java.DoNothingVisitor; -import io.openpixee.java.FileWeavingContext; import io.openpixee.java.VisitorFactory; import java.io.File; import java.io.FileReader; diff --git a/languages/java/src/main/java/io/openpixee/java/protections/PredictableSeedVisitorFactory.java b/languages/java/src/main/java/io/openpixee/java/protections/PredictableSeedVisitorFactory.java index ca6d081e4..fa0d065ca 100644 --- a/languages/java/src/main/java/io/openpixee/java/protections/PredictableSeedVisitorFactory.java +++ b/languages/java/src/main/java/io/openpixee/java/protections/PredictableSeedVisitorFactory.java @@ -7,8 +7,8 @@ import com.github.javaparser.ast.expr.MethodCallExpr; import com.github.javaparser.ast.expr.NameExpr; import com.github.javaparser.ast.visitor.ModifierVisitor; +import io.codemodder.FileWeavingContext; import io.codemodder.Weave; -import io.openpixee.java.FileWeavingContext; import io.openpixee.java.MethodCallPredicateFactory; import io.openpixee.java.MethodCallTransformingModifierVisitor; import io.openpixee.java.Transformer; diff --git a/languages/java/src/main/java/io/openpixee/java/protections/RegexTextVisitor.java b/languages/java/src/main/java/io/openpixee/java/protections/RegexTextVisitor.java index 115280907..a1d687a03 100644 --- a/languages/java/src/main/java/io/openpixee/java/protections/RegexTextVisitor.java +++ b/languages/java/src/main/java/io/openpixee/java/protections/RegexTextVisitor.java @@ -2,9 +2,9 @@ import io.codemodder.ChangedFile; import io.codemodder.DependencyGAV; +import io.codemodder.FileWeavingContext; import io.codemodder.Weave; import io.openpixee.java.FileBasedVisitor; -import io.openpixee.java.FileWeavingContext; import io.openpixee.java.WeavingResult; import java.io.File; import java.io.IOException; diff --git a/languages/java/src/main/java/io/openpixee/java/protections/RuntimeExecVisitorFactory.java b/languages/java/src/main/java/io/openpixee/java/protections/RuntimeExecVisitorFactory.java index 740d522b0..69d86f832 100644 --- a/languages/java/src/main/java/io/openpixee/java/protections/RuntimeExecVisitorFactory.java +++ b/languages/java/src/main/java/io/openpixee/java/protections/RuntimeExecVisitorFactory.java @@ -7,8 +7,8 @@ import com.github.javaparser.ast.expr.NameExpr; import com.github.javaparser.ast.visitor.ModifierVisitor; import io.codemodder.DependencyGAV; +import io.codemodder.FileWeavingContext; import io.codemodder.Weave; -import io.openpixee.java.FileWeavingContext; import io.openpixee.java.MethodCallPredicateFactory; import io.openpixee.java.MethodCallTransformingModifierVisitor; import io.openpixee.java.Transformer; diff --git a/languages/java/src/main/java/io/openpixee/java/protections/SQLParameterizerVisitorFactory.java b/languages/java/src/main/java/io/openpixee/java/protections/SQLParameterizerVisitorFactory.java index 13a554bc0..78195928a 100644 --- a/languages/java/src/main/java/io/openpixee/java/protections/SQLParameterizerVisitorFactory.java +++ b/languages/java/src/main/java/io/openpixee/java/protections/SQLParameterizerVisitorFactory.java @@ -4,8 +4,8 @@ import com.github.javaparser.ast.expr.*; import com.github.javaparser.ast.visitor.ModifierVisitor; import com.github.javaparser.ast.visitor.Visitable; +import io.codemodder.FileWeavingContext; import io.codemodder.Weave; -import io.openpixee.java.FileWeavingContext; import io.openpixee.java.VisitorFactory; import io.openpixee.jdbcparameterizer.SQLParameterizer; import java.io.File; diff --git a/languages/java/src/main/java/io/openpixee/java/protections/SSLContextGetInstanceVisitorFactory.java b/languages/java/src/main/java/io/openpixee/java/protections/SSLContextGetInstanceVisitorFactory.java index 79707858c..676eca82f 100644 --- a/languages/java/src/main/java/io/openpixee/java/protections/SSLContextGetInstanceVisitorFactory.java +++ b/languages/java/src/main/java/io/openpixee/java/protections/SSLContextGetInstanceVisitorFactory.java @@ -6,8 +6,8 @@ import com.github.javaparser.ast.expr.NameExpr; import com.github.javaparser.ast.expr.StringLiteralExpr; import com.github.javaparser.ast.visitor.ModifierVisitor; +import io.codemodder.FileWeavingContext; import io.codemodder.Weave; -import io.openpixee.java.FileWeavingContext; import io.openpixee.java.MethodCallPredicateFactory; import io.openpixee.java.MethodCallTransformingModifierVisitor; import io.openpixee.java.Transformer; diff --git a/languages/java/src/main/java/io/openpixee/java/protections/SSLProtocolsVisitorFactory.java b/languages/java/src/main/java/io/openpixee/java/protections/SSLProtocolsVisitorFactory.java index 4603fe813..af78eb632 100644 --- a/languages/java/src/main/java/io/openpixee/java/protections/SSLProtocolsVisitorFactory.java +++ b/languages/java/src/main/java/io/openpixee/java/protections/SSLProtocolsVisitorFactory.java @@ -10,8 +10,8 @@ import com.github.javaparser.ast.expr.StringLiteralExpr; import com.github.javaparser.ast.type.ClassOrInterfaceType; import com.github.javaparser.ast.visitor.ModifierVisitor; +import io.codemodder.FileWeavingContext; import io.codemodder.Weave; -import io.openpixee.java.FileWeavingContext; import io.openpixee.java.MethodCallPredicateFactory; import io.openpixee.java.MethodCallTransformingModifierVisitor; import io.openpixee.java.Transformer; diff --git a/languages/java/src/main/java/io/openpixee/java/protections/SSRFVisitorFactory.java b/languages/java/src/main/java/io/openpixee/java/protections/SSRFVisitorFactory.java index 4a7d8138e..9e220970e 100644 --- a/languages/java/src/main/java/io/openpixee/java/protections/SSRFVisitorFactory.java +++ b/languages/java/src/main/java/io/openpixee/java/protections/SSRFVisitorFactory.java @@ -7,8 +7,8 @@ import com.github.javaparser.ast.visitor.ModifierVisitor; import com.github.javaparser.ast.visitor.Visitable; import io.codemodder.DependencyGAV; +import io.codemodder.FileWeavingContext; import io.codemodder.Weave; -import io.openpixee.java.FileWeavingContext; import io.openpixee.java.VisitorFactory; import io.openpixee.java.ast.ASTTransforms; import io.openpixee.security.*; @@ -45,7 +45,8 @@ public Visitable visit(final ObjectCreationExpr n, final FileWeavingContext cont ClassOrInterfaceType type = n.getType().asClassOrInterfaceType(); if (type.getNameAsString().equals("URL") || type.getNameAsString().equals("java.net.URL")) { if (!n.getArguments().isEmpty()) { - if (!hasAllConstantArguments(n.getArguments()) && context.isLineIncluded(n)) { + if (!hasAllConstantArguments(n.getArguments()) + && context.isLineIncluded(n.getRange().get().begin.line)) { /* * We need to replace: * diff --git a/languages/java/src/main/java/io/openpixee/java/protections/SpringMultipartVisitorFactory.java b/languages/java/src/main/java/io/openpixee/java/protections/SpringMultipartVisitorFactory.java index db01b401a..45eaae63e 100644 --- a/languages/java/src/main/java/io/openpixee/java/protections/SpringMultipartVisitorFactory.java +++ b/languages/java/src/main/java/io/openpixee/java/protections/SpringMultipartVisitorFactory.java @@ -7,8 +7,8 @@ import com.github.javaparser.ast.expr.StringLiteralExpr; import com.github.javaparser.ast.visitor.ModifierVisitor; import io.codemodder.DependencyGAV; +import io.codemodder.FileWeavingContext; import io.codemodder.Weave; -import io.openpixee.java.FileWeavingContext; import io.openpixee.java.MethodCallPredicateFactory; import io.openpixee.java.MethodCallTransformingModifierVisitor; import io.openpixee.java.Transformer; diff --git a/languages/java/src/main/java/io/openpixee/java/protections/UnsafeReadlineVisitorFactory.java b/languages/java/src/main/java/io/openpixee/java/protections/UnsafeReadlineVisitorFactory.java index d5de80edc..7ad23399a 100644 --- a/languages/java/src/main/java/io/openpixee/java/protections/UnsafeReadlineVisitorFactory.java +++ b/languages/java/src/main/java/io/openpixee/java/protections/UnsafeReadlineVisitorFactory.java @@ -5,8 +5,8 @@ import com.github.javaparser.ast.expr.*; import com.github.javaparser.ast.visitor.ModifierVisitor; import io.codemodder.DependencyGAV; +import io.codemodder.FileWeavingContext; import io.codemodder.Weave; -import io.openpixee.java.FileWeavingContext; import io.openpixee.java.MethodCallPredicateFactory; import io.openpixee.java.MethodCallTransformingModifierVisitor; import io.openpixee.java.Transformer; diff --git a/languages/java/src/main/java/io/openpixee/java/protections/WeakPRNGVisitorFactory.java b/languages/java/src/main/java/io/openpixee/java/protections/WeakPRNGVisitorFactory.java index 354994b88..58cecd85a 100644 --- a/languages/java/src/main/java/io/openpixee/java/protections/WeakPRNGVisitorFactory.java +++ b/languages/java/src/main/java/io/openpixee/java/protections/WeakPRNGVisitorFactory.java @@ -4,8 +4,8 @@ import com.github.javaparser.ast.expr.ObjectCreationExpr; import com.github.javaparser.ast.type.ClassOrInterfaceType; import com.github.javaparser.ast.visitor.ModifierVisitor; +import io.codemodder.FileWeavingContext; import io.codemodder.Weave; -import io.openpixee.java.FileWeavingContext; import io.openpixee.java.ObjectCreationPredicateFactory; import io.openpixee.java.ObjectCreationTransformingModifierVisitor; import io.openpixee.java.Transformer; diff --git a/languages/java/src/main/java/io/openpixee/java/protections/XMLDecoderVisitorFactory.java b/languages/java/src/main/java/io/openpixee/java/protections/XMLDecoderVisitorFactory.java index 4294dffab..782793615 100644 --- a/languages/java/src/main/java/io/openpixee/java/protections/XMLDecoderVisitorFactory.java +++ b/languages/java/src/main/java/io/openpixee/java/protections/XMLDecoderVisitorFactory.java @@ -8,8 +8,8 @@ import com.github.javaparser.ast.expr.ObjectCreationExpr; import com.github.javaparser.ast.visitor.ModifierVisitor; import io.codemodder.DependencyGAV; +import io.codemodder.FileWeavingContext; import io.codemodder.Weave; -import io.openpixee.java.FileWeavingContext; import io.openpixee.java.ObjectCreationPredicateFactory; import io.openpixee.java.ObjectCreationTransformingModifierVisitor; import io.openpixee.java.Transformer; diff --git a/languages/java/src/main/java/io/openpixee/java/protections/XStreamVisitorFactory.java b/languages/java/src/main/java/io/openpixee/java/protections/XStreamVisitorFactory.java index c39f83ea5..85082f5a6 100644 --- a/languages/java/src/main/java/io/openpixee/java/protections/XStreamVisitorFactory.java +++ b/languages/java/src/main/java/io/openpixee/java/protections/XStreamVisitorFactory.java @@ -12,8 +12,8 @@ import com.github.javaparser.ast.stmt.Statement; import com.github.javaparser.ast.type.ClassOrInterfaceType; import com.github.javaparser.ast.visitor.ModifierVisitor; +import io.codemodder.FileWeavingContext; import io.codemodder.Weave; -import io.openpixee.java.FileWeavingContext; import io.openpixee.java.ObjectCreationPredicateFactory; import io.openpixee.java.ObjectCreationTransformingModifierVisitor; import io.openpixee.java.Transformer; diff --git a/languages/java/src/main/java/io/openpixee/java/protections/XXEVisitorFactory.java b/languages/java/src/main/java/io/openpixee/java/protections/XXEVisitorFactory.java index 0a34019d8..d173616fc 100644 --- a/languages/java/src/main/java/io/openpixee/java/protections/XXEVisitorFactory.java +++ b/languages/java/src/main/java/io/openpixee/java/protections/XXEVisitorFactory.java @@ -10,8 +10,8 @@ import com.github.javaparser.ast.stmt.BlockStmt; import com.github.javaparser.ast.visitor.ModifierVisitor; import io.codemodder.DependencyGAV; +import io.codemodder.FileWeavingContext; import io.codemodder.Weave; -import io.openpixee.java.FileWeavingContext; import io.openpixee.java.MethodCallPredicateFactory; import io.openpixee.java.MethodCallTransformingModifierVisitor; import io.openpixee.java.Transformer; diff --git a/languages/java/src/main/java/io/openpixee/java/protections/ZipFileOverwriteVisitoryFactory.java b/languages/java/src/main/java/io/openpixee/java/protections/ZipFileOverwriteVisitoryFactory.java index bb967ff58..2c797ad59 100644 --- a/languages/java/src/main/java/io/openpixee/java/protections/ZipFileOverwriteVisitoryFactory.java +++ b/languages/java/src/main/java/io/openpixee/java/protections/ZipFileOverwriteVisitoryFactory.java @@ -6,8 +6,8 @@ import com.github.javaparser.ast.expr.ObjectCreationExpr; import com.github.javaparser.ast.visitor.ModifierVisitor; import io.codemodder.DependencyGAV; +import io.codemodder.FileWeavingContext; import io.codemodder.Weave; -import io.openpixee.java.FileWeavingContext; import io.openpixee.java.ObjectCreationPredicateFactory; import io.openpixee.java.ObjectCreationToMethodCallTransformingModifierVisitor; import io.openpixee.java.Transformer; diff --git a/languages/java/src/test/java/io/openpixee/java/IncludesExcludesTest.java b/languages/java/src/test/java/io/openpixee/java/IncludesExcludesTest.java index 96808de14..75d3a19e8 100644 --- a/languages/java/src/test/java/io/openpixee/java/IncludesExcludesTest.java +++ b/languages/java/src/test/java/io/openpixee/java/IncludesExcludesTest.java @@ -4,6 +4,8 @@ import static org.hamcrest.MatcherAssert.assertThat; import static org.junit.jupiter.api.Assertions.assertThrows; +import io.codemodder.IncludesExcludes; +import io.codemodder.LineIncludesExcludes; import java.io.File; import java.util.Collections; import java.util.List; diff --git a/languages/java/src/test/java/io/openpixee/java/JavaFixitCliTest.java b/languages/java/src/test/java/io/openpixee/java/JavaFixitCliTest.java index 8af9fc7e0..72ea20178 100644 --- a/languages/java/src/test/java/io/openpixee/java/JavaFixitCliTest.java +++ b/languages/java/src/test/java/io/openpixee/java/JavaFixitCliTest.java @@ -3,6 +3,7 @@ import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.MatcherAssert.assertThat; +import io.codemodder.IncludesExcludes; import java.io.File; import org.junit.jupiter.api.Test; diff --git a/languages/java/src/test/java/io/openpixee/java/PathMatcherTest.java b/languages/java/src/test/java/io/openpixee/java/PathMatcherTest.java index f40bb8c4c..209be457c 100644 --- a/languages/java/src/test/java/io/openpixee/java/PathMatcherTest.java +++ b/languages/java/src/test/java/io/openpixee/java/PathMatcherTest.java @@ -3,6 +3,7 @@ import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.MatcherAssert.assertThat; +import io.codemodder.IncludesExcludes; import java.io.File; import org.junit.jupiter.api.Test; diff --git a/languages/java/src/test/java/io/openpixee/java/plugins/codeql/JDBCResourceLeakTest.java b/languages/java/src/test/java/io/openpixee/java/plugins/codeql/JDBCResourceLeakTest.java index cbe73e371..309d6528a 100644 --- a/languages/java/src/test/java/io/openpixee/java/plugins/codeql/JDBCResourceLeakTest.java +++ b/languages/java/src/test/java/io/openpixee/java/plugins/codeql/JDBCResourceLeakTest.java @@ -1,6 +1,6 @@ package io.openpixee.java.plugins.codeql; -import io.openpixee.java.IncludesExcludes; +import io.codemodder.IncludesExcludes; import io.openpixee.java.plugins.JavaSarifMockFactory; import io.openpixee.java.protections.WeavingTests; import java.io.File; diff --git a/languages/java/src/test/java/io/openpixee/java/plugins/codeql/JEXLInjectionTest.java b/languages/java/src/test/java/io/openpixee/java/plugins/codeql/JEXLInjectionTest.java index 038012164..bf45b062e 100644 --- a/languages/java/src/test/java/io/openpixee/java/plugins/codeql/JEXLInjectionTest.java +++ b/languages/java/src/test/java/io/openpixee/java/plugins/codeql/JEXLInjectionTest.java @@ -1,6 +1,6 @@ package io.openpixee.java.plugins.codeql; -import io.openpixee.java.IncludesExcludes; +import io.codemodder.IncludesExcludes; import io.openpixee.java.plugins.JavaSarifMockFactory; import io.openpixee.java.protections.WeavingTests; import java.io.File; diff --git a/languages/java/src/test/java/io/openpixee/java/plugins/codeql/MavenSecureURLTest.java b/languages/java/src/test/java/io/openpixee/java/plugins/codeql/MavenSecureURLTest.java index 4b384cb4c..d2eb7a354 100644 --- a/languages/java/src/test/java/io/openpixee/java/plugins/codeql/MavenSecureURLTest.java +++ b/languages/java/src/test/java/io/openpixee/java/plugins/codeql/MavenSecureURLTest.java @@ -9,9 +9,9 @@ import com.github.difflib.DiffUtils; import com.github.difflib.patch.AbstractDelta; import com.github.difflib.patch.Patch; +import io.codemodder.FileWeavingContext; +import io.codemodder.IncludesExcludes; import io.codemodder.Weave; -import io.openpixee.java.FileWeavingContext; -import io.openpixee.java.IncludesExcludes; import io.openpixee.java.plugins.JavaSarifMockFactory; import java.io.File; import java.io.IOException; diff --git a/languages/java/src/test/java/io/openpixee/java/protections/DependencyGAVInjectingTest.java b/languages/java/src/test/java/io/openpixee/java/protections/DependencyGAVInjectingTest.java index 37fcb759b..905da7654 100644 --- a/languages/java/src/test/java/io/openpixee/java/protections/DependencyGAVInjectingTest.java +++ b/languages/java/src/test/java/io/openpixee/java/protections/DependencyGAVInjectingTest.java @@ -5,8 +5,8 @@ import io.codemodder.ChangedFile; import io.codemodder.DependencyGAV; +import io.codemodder.FileWeavingContext; import io.codemodder.Weave; -import io.openpixee.java.FileWeavingContext; import io.openpixee.maven.operator.Dependency; import io.openpixee.maven.operator.POMOperator; import io.openpixee.maven.operator.ProjectModel; diff --git a/languages/java/src/test/java/io/openpixee/java/protections/WeavingTests.java b/languages/java/src/test/java/io/openpixee/java/protections/WeavingTests.java index 49a6020c1..15379ac2a 100644 --- a/languages/java/src/test/java/io/openpixee/java/protections/WeavingTests.java +++ b/languages/java/src/test/java/io/openpixee/java/protections/WeavingTests.java @@ -3,9 +3,9 @@ import static org.assertj.core.api.Assertions.assertThat; import io.codemodder.ChangedFile; +import io.codemodder.FileWeavingContext; +import io.codemodder.IncludesExcludes; import io.openpixee.java.FileBasedVisitor; -import io.openpixee.java.FileWeavingContext; -import io.openpixee.java.IncludesExcludes; import io.openpixee.java.SourceDirectory; import io.openpixee.java.SourceWeaver; import io.openpixee.java.VisitorFactory; @@ -91,7 +91,12 @@ private static ChangedFile scanAndAssertNoErrorsWithOneFileChanged( final IncludesExcludes includesExcludes) throws IOException { var weave = - analyzer.weave(List.of(directory), directory.files(), visitorFactories, includesExcludes); + analyzer.weave( + new File("src/test"), + List.of(directory), + directory.files(), + visitorFactories, + includesExcludes); assertThat(weave).isNotNull(); assertThat(weave.unscannableFiles()).isEmpty(); var changedFiles = weave.changedFiles(); @@ -123,7 +128,12 @@ private static void scanAndAssertNoErrorsWithNoFilesChanged( final IncludesExcludes includesExcludes) throws IOException { var weave = - analyzer.weave(List.of(directory), directory.files(), visitorFactories, includesExcludes); + analyzer.weave( + new File("src/test"), + List.of(directory), + directory.files(), + visitorFactories, + includesExcludes); assertThat(weave).isNotNull(); assertThat(weave.unscannableFiles()).isEmpty(); From cf347747d2487ccc198c8e0c37600140fd84e6cf Mon Sep 17 00:00:00 2001 From: Arshan Dabirsiaghi Date: Sat, 11 Mar 2023 08:22:28 -0500 Subject: [PATCH 03/19] add semgrep --- .github/workflows/release.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 988d48e7c..ed404fa10 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -13,6 +13,8 @@ jobs: contents: write environment: Public Release runs-on: "ubuntu-latest" + container: + image: returntocorp/semgrep env: ORG_GRADLE_PROJECT_pixeeArtifactoryUsername: ${{ secrets.PIXEE_ARTIFACTORY_USERNAME }} ORG_GRADLE_PROJECT_pixeeArtifactoryPassword: ${{ secrets.PIXEE_ARTIFACTORY_PASSWORD }} From af366db47c027cc8716de7ea6a5214d2e5ebf7d5 Mon Sep 17 00:00:00 2001 From: Arshan Dabirsiaghi Date: Sat, 11 Mar 2023 08:50:24 -0500 Subject: [PATCH 04/19] tests pass, now pretty clean interface for repository injections --- .../codemods/SecureRandomCodemod.java | 6 ++- .../java/io/codemodder/CodeDirectory.java | 12 ++++++ .../io/codemodder/CodeDirectoryModule.java | 40 +++++++++++++++++++ .../java/io/codemodder/CodemodInvoker.java | 5 ++- .../java/io/codemodder/CodemodProvider.java | 4 -- .../io/codemodder/JavaParserProvider.java | 6 --- .../semgrep/DefaultSemgrepSarifProvider.java | 10 ++--- .../sarif/semgrep/SemgrepProvider.java | 14 +++++++ .../sarif/semgrep/SemgrepSarifModule.java | 11 +++++ .../sarif/semgrep/SemgrepSarifProvider.java | 4 +- .../semgrep/SemgrepSarifProviderTest.java | 40 +++++-------------- 11 files changed, 101 insertions(+), 51 deletions(-) create mode 100644 languages/codemodder-framework-java/src/main/java/io/codemodder/CodeDirectory.java create mode 100644 languages/codemodder-framework-java/src/main/java/io/codemodder/CodeDirectoryModule.java create mode 100644 languages/codemodder-semgrep-provider/src/main/java/io/codemodder/providers/sarif/semgrep/SemgrepProvider.java create mode 100644 languages/codemodder-semgrep-provider/src/main/java/io/codemodder/providers/sarif/semgrep/SemgrepSarifModule.java diff --git a/languages/codemodder-default-codemods/src/main/java/io/codemodder/codemods/SecureRandomCodemod.java b/languages/codemodder-default-codemods/src/main/java/io/codemodder/codemods/SecureRandomCodemod.java index 91f2120a5..4edf888fb 100644 --- a/languages/codemodder-default-codemods/src/main/java/io/codemodder/codemods/SecureRandomCodemod.java +++ b/languages/codemodder-default-codemods/src/main/java/io/codemodder/codemods/SecureRandomCodemod.java @@ -6,6 +6,7 @@ import com.github.javaparser.ast.visitor.ModifierVisitor; import io.codemodder.ChangeConstructorTypeVisitor; import io.codemodder.ChangeContext; +import io.codemodder.CodeDirectory; import io.codemodder.Codemod; import io.codemodder.JavaParserChanger; import io.codemodder.ReviewGuidance; @@ -29,9 +30,10 @@ public final class SecureRandomCodemod implements JavaParserChanger { private final SarifSchema210 sarif; @Inject - public SecureRandomCodemod(final SemgrepSarifProvider sarifProvider) + public SecureRandomCodemod( + final CodeDirectory codeDirectory, final SemgrepSarifProvider sarifProvider) throws IOException, URISyntaxException { - this.sarif = sarifProvider.getSarif("secure-random.semgrep"); + this.sarif = sarifProvider.getSarif(codeDirectory.asPath(), "secure-random.semgrep"); } @Override diff --git a/languages/codemodder-framework-java/src/main/java/io/codemodder/CodeDirectory.java b/languages/codemodder-framework-java/src/main/java/io/codemodder/CodeDirectory.java new file mode 100644 index 000000000..b2f441357 --- /dev/null +++ b/languages/codemodder-framework-java/src/main/java/io/codemodder/CodeDirectory.java @@ -0,0 +1,12 @@ +package io.codemodder; + +import java.io.File; +import java.nio.file.Path; + +/** Holds a code directory (e.g., a repository root). */ +public interface CodeDirectory { + + File asFile(); + + Path asPath(); +} diff --git a/languages/codemodder-framework-java/src/main/java/io/codemodder/CodeDirectoryModule.java b/languages/codemodder-framework-java/src/main/java/io/codemodder/CodeDirectoryModule.java new file mode 100644 index 000000000..f6f2a1029 --- /dev/null +++ b/languages/codemodder-framework-java/src/main/java/io/codemodder/CodeDirectoryModule.java @@ -0,0 +1,40 @@ +package io.codemodder; + +import com.google.inject.AbstractModule; +import java.io.File; +import java.nio.file.Path; +import java.util.Objects; + +/** Binds the repository directory for codemods. */ +final class CodeDirectoryModule extends AbstractModule { + + private final Path repositoryDir; + + CodeDirectoryModule(final Path repositoryDir) { + this.repositoryDir = Objects.requireNonNull(repositoryDir); + } + + @Override + protected void configure() { + bind(CodeDirectory.class).toInstance(new DefaultCodeDirectory(repositoryDir)); + } + + private static class DefaultCodeDirectory implements CodeDirectory { + + private final Path repositoryDir; + + private DefaultCodeDirectory(final Path repositoryDir) { + this.repositoryDir = Objects.requireNonNull(repositoryDir); + } + + @Override + public File asFile() { + return repositoryDir.toFile(); + } + + @Override + public Path asPath() { + return repositoryDir; + } + } +} diff --git a/languages/codemodder-framework-java/src/main/java/io/codemodder/CodemodInvoker.java b/languages/codemodder-framework-java/src/main/java/io/codemodder/CodemodInvoker.java index 3f606842a..f6eedcf63 100644 --- a/languages/codemodder-framework-java/src/main/java/io/codemodder/CodemodInvoker.java +++ b/languages/codemodder-framework-java/src/main/java/io/codemodder/CodemodInvoker.java @@ -37,6 +37,9 @@ public CodemodInvoker(final List> codemodTypes, Path re .collect(Collectors.toUnmodifiableList()); Set allModules = new HashSet<>(); + // add default module + allModules.add(new CodeDirectoryModule(repositoryDir)); + for (CodemodProvider provider : providers) { Set modules = provider.getModules(); allModules.addAll(modules); @@ -59,7 +62,7 @@ public CodemodInvoker(final List> codemodTypes, Path re // will spit out CodeTF or whatever /** - * @param javaFile + * @param file * @param context * @return */ diff --git a/languages/codemodder-framework-java/src/main/java/io/codemodder/CodemodProvider.java b/languages/codemodder-framework-java/src/main/java/io/codemodder/CodemodProvider.java index c4bd7941e..75584b019 100644 --- a/languages/codemodder-framework-java/src/main/java/io/codemodder/CodemodProvider.java +++ b/languages/codemodder-framework-java/src/main/java/io/codemodder/CodemodProvider.java @@ -1,7 +1,6 @@ package io.codemodder; import com.google.inject.AbstractModule; -import java.util.Optional; import java.util.Set; /** @@ -16,7 +15,4 @@ public interface CodemodProvider { * @return a set of modules that perform dependency injection */ Set getModules(); - - /** Provide a changer handler for the given {@link Changer} if they want to support it. */ - Optional getChangerHandler(Changer changer); } diff --git a/languages/codemodder-framework-java/src/main/java/io/codemodder/JavaParserProvider.java b/languages/codemodder-framework-java/src/main/java/io/codemodder/JavaParserProvider.java index 850950a5d..8fc8443ec 100644 --- a/languages/codemodder-framework-java/src/main/java/io/codemodder/JavaParserProvider.java +++ b/languages/codemodder-framework-java/src/main/java/io/codemodder/JavaParserProvider.java @@ -1,7 +1,6 @@ package io.codemodder; import com.google.inject.AbstractModule; -import java.util.Optional; import java.util.Set; /** Turns Java files into. */ @@ -11,9 +10,4 @@ public class JavaParserProvider implements CodemodProvider { public Set getModules() { return Set.of(); } - - @Override - public Optional getChangerHandler(final Changer changer) { - return Optional.empty(); - } } diff --git a/languages/codemodder-semgrep-provider/src/main/java/io/codemodder/providers/sarif/semgrep/DefaultSemgrepSarifProvider.java b/languages/codemodder-semgrep-provider/src/main/java/io/codemodder/providers/sarif/semgrep/DefaultSemgrepSarifProvider.java index 1c5eff008..16ddcffba 100644 --- a/languages/codemodder-semgrep-provider/src/main/java/io/codemodder/providers/sarif/semgrep/DefaultSemgrepSarifProvider.java +++ b/languages/codemodder-semgrep-provider/src/main/java/io/codemodder/providers/sarif/semgrep/DefaultSemgrepSarifProvider.java @@ -9,21 +9,19 @@ import java.nio.file.Paths; import java.util.HashMap; import java.util.Map; -import java.util.Objects; /** This is the memoizing Semgrep runner that we'll give to codemods. */ final class DefaultSemgrepSarifProvider implements SemgrepSarifProvider { private final Map sarifs; - private final Path repositoryDir; - DefaultSemgrepSarifProvider(final Path repositoryDir) { + DefaultSemgrepSarifProvider() { this.sarifs = new HashMap<>(); - this.repositoryDir = Objects.requireNonNull(repositoryDir); } @Override - public SarifSchema210 getSarif(final String rulePath) throws IOException, URISyntaxException { + public SarifSchema210 getSarif(final Path repository, final String rulePath) + throws IOException, URISyntaxException { if (sarifs.containsKey(rulePath)) { return sarifs.get(rulePath); } @@ -34,7 +32,7 @@ public SarifSchema210 getSarif(final String rulePath) throws IOException, URISyn Files.write(semgrepRuleFile, ruleYaml.getBytes(StandardCharsets.UTF_8)); SarifSchema210 sarif = - new DefaultSemgrepRunner().runWithSingleRule(semgrepRuleFile, repositoryDir); + new DefaultSemgrepRunner().runWithSingleRule(semgrepRuleFile, repository); sarifs.put(rulePath, sarif); return sarif; } diff --git a/languages/codemodder-semgrep-provider/src/main/java/io/codemodder/providers/sarif/semgrep/SemgrepProvider.java b/languages/codemodder-semgrep-provider/src/main/java/io/codemodder/providers/sarif/semgrep/SemgrepProvider.java new file mode 100644 index 000000000..ee831feeb --- /dev/null +++ b/languages/codemodder-semgrep-provider/src/main/java/io/codemodder/providers/sarif/semgrep/SemgrepProvider.java @@ -0,0 +1,14 @@ +package io.codemodder.providers.sarif.semgrep; + +import com.google.inject.AbstractModule; +import io.codemodder.CodemodProvider; +import java.util.Set; + +/** Provides Semgrep-related functionality to codemodder. */ +public final class SemgrepProvider implements CodemodProvider { + + @Override + public Set getModules() { + return Set.of(new SemgrepSarifModule()); + } +} diff --git a/languages/codemodder-semgrep-provider/src/main/java/io/codemodder/providers/sarif/semgrep/SemgrepSarifModule.java b/languages/codemodder-semgrep-provider/src/main/java/io/codemodder/providers/sarif/semgrep/SemgrepSarifModule.java new file mode 100644 index 000000000..e85fa3c70 --- /dev/null +++ b/languages/codemodder-semgrep-provider/src/main/java/io/codemodder/providers/sarif/semgrep/SemgrepSarifModule.java @@ -0,0 +1,11 @@ +package io.codemodder.providers.sarif.semgrep; + +import com.google.inject.AbstractModule; + +final class SemgrepSarifModule extends AbstractModule { + + @Override + protected void configure() { + bind(SemgrepSarifProvider.class).toInstance(new DefaultSemgrepSarifProvider()); + } +} diff --git a/languages/codemodder-semgrep-provider/src/main/java/io/codemodder/providers/sarif/semgrep/SemgrepSarifProvider.java b/languages/codemodder-semgrep-provider/src/main/java/io/codemodder/providers/sarif/semgrep/SemgrepSarifProvider.java index 5ece666d7..334c64ec7 100644 --- a/languages/codemodder-semgrep-provider/src/main/java/io/codemodder/providers/sarif/semgrep/SemgrepSarifProvider.java +++ b/languages/codemodder-semgrep-provider/src/main/java/io/codemodder/providers/sarif/semgrep/SemgrepSarifProvider.java @@ -3,6 +3,7 @@ import com.contrastsecurity.sarif.SarifSchema210; import java.io.IOException; import java.net.URISyntaxException; +import java.nio.file.Path; /** A provider that invokes semgrep (assuming Semgrep is on the $PATH) */ public interface SemgrepSarifProvider { @@ -12,5 +13,6 @@ public interface SemgrepSarifProvider { * * @param rulePath the classpath location of a semgrep YAML rule */ - SarifSchema210 getSarif(String rulePath) throws IOException, URISyntaxException; + SarifSchema210 getSarif(Path repositoryPath, String rulePath) + throws IOException, URISyntaxException; } diff --git a/languages/codemodder-semgrep-provider/src/test/java/io/codemodder/providers/sarif/semgrep/SemgrepSarifProviderTest.java b/languages/codemodder-semgrep-provider/src/test/java/io/codemodder/providers/sarif/semgrep/SemgrepSarifProviderTest.java index 6956787d5..fd0b6ddcd 100644 --- a/languages/codemodder-semgrep-provider/src/test/java/io/codemodder/providers/sarif/semgrep/SemgrepSarifProviderTest.java +++ b/languages/codemodder-semgrep-provider/src/test/java/io/codemodder/providers/sarif/semgrep/SemgrepSarifProviderTest.java @@ -6,31 +6,31 @@ import com.contrastsecurity.sarif.Result; import com.contrastsecurity.sarif.Run; import com.contrastsecurity.sarif.SarifSchema210; -import com.google.inject.AbstractModule; -import com.google.inject.Guice; -import com.google.inject.Inject; -import com.google.inject.Injector; import java.io.IOException; import java.net.URISyntaxException; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; import java.util.List; -import java.util.Objects; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; final class SemgrepSarifProviderTest { @Test - void it_injects_sarif(@TempDir Path repositoryDir) throws IOException { + void it_injects_sarif(@TempDir Path repositoryDir) throws IOException, URISyntaxException { + + // create vulnerable code in a new temporary repository dir Path javaFile = Files.createTempFile(repositoryDir, "WeakRandom", ".java"); String insecureRandomJavaClass = "class Foo { Random rnd = new Random(); }"; Files.write(javaFile, insecureRandomJavaClass.getBytes(StandardCharsets.UTF_8)); - Injector injector = Guice.createInjector(new SemgrepSarifModule(repositoryDir)); - UsesSemgrepSarif instance = injector.getInstance(UsesSemgrepSarif.class); - List runs = instance.sarif.getRuns(); + // run the scan + SarifSchema210 sarif = + new DefaultSemgrepSarifProvider().getSarif(repositoryDir, "example.semgrep"); + + // assert the scan went as we think it should + List runs = sarif.getRuns(); assertThat(runs.size(), is(1)); List results = runs.get(0).getResults(); assertThat(results.size(), is(1)); @@ -40,26 +40,4 @@ void it_injects_sarif(@TempDir Path repositoryDir) throws IOException { result.getLocations().get(0).getPhysicalLocation().getArtifactLocation().getUri(); assertThat(resultPhysicalFilePath.contains("WeakRandom"), is(true)); } - - static class UsesSemgrepSarif { - private final SarifSchema210 sarif; - - @Inject - UsesSemgrepSarif(SemgrepSarifProvider sarifProvider) throws IOException, URISyntaxException { - this.sarif = Objects.requireNonNull(sarifProvider.getSarif("example.semgrep")); - } - } - - static class SemgrepSarifModule extends AbstractModule { - private final Path repositoryDir; - - SemgrepSarifModule(Path repositoryDir) { - this.repositoryDir = Objects.requireNonNull(repositoryDir); - } - - @Override - protected void configure() { - bind(SemgrepSarifProvider.class).toInstance(new DefaultSemgrepSarifProvider(repositoryDir)); - } - } } From 2ebe359fb86d21f842e2012454e8ff9e8f477209 Mon Sep 17 00:00:00 2001 From: Arshan Dabirsiaghi Date: Sat, 11 Mar 2023 08:51:18 -0500 Subject: [PATCH 05/19] made name better --- .../providers/sarif/semgrep/SemgrepSarifProviderTest.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/languages/codemodder-semgrep-provider/src/test/java/io/codemodder/providers/sarif/semgrep/SemgrepSarifProviderTest.java b/languages/codemodder-semgrep-provider/src/test/java/io/codemodder/providers/sarif/semgrep/SemgrepSarifProviderTest.java index fd0b6ddcd..4baeb26a8 100644 --- a/languages/codemodder-semgrep-provider/src/test/java/io/codemodder/providers/sarif/semgrep/SemgrepSarifProviderTest.java +++ b/languages/codemodder-semgrep-provider/src/test/java/io/codemodder/providers/sarif/semgrep/SemgrepSarifProviderTest.java @@ -18,7 +18,8 @@ final class SemgrepSarifProviderTest { @Test - void it_injects_sarif(@TempDir Path repositoryDir) throws IOException, URISyntaxException { + void it_runs_semgrep_and_produces_expected_sarif(@TempDir Path repositoryDir) + throws IOException, URISyntaxException { // create vulnerable code in a new temporary repository dir Path javaFile = Files.createTempFile(repositoryDir, "WeakRandom", ".java"); From 197bd8579e4acc5ca8267f5ff7d546c76ce7ee10 Mon Sep 17 00:00:00 2001 From: Arshan Dabirsiaghi Date: Sat, 11 Mar 2023 08:55:38 -0500 Subject: [PATCH 06/19] made name better --- .../src/main/java/io/codemodder/CodemodInvoker.java | 4 ++-- .../resources/META-INF/services/io.codemodder.CodemodProvider | 1 + .../java/src/main/java/io/openpixee/java/SourceWeaver.java | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) create mode 100644 languages/codemodder-semgrep-provider/src/main/resources/META-INF/services/io.codemodder.CodemodProvider diff --git a/languages/codemodder-framework-java/src/main/java/io/codemodder/CodemodInvoker.java b/languages/codemodder-framework-java/src/main/java/io/codemodder/CodemodInvoker.java index f6eedcf63..de86d0537 100644 --- a/languages/codemodder-framework-java/src/main/java/io/codemodder/CodemodInvoker.java +++ b/languages/codemodder-framework-java/src/main/java/io/codemodder/CodemodInvoker.java @@ -66,7 +66,7 @@ public CodemodInvoker(final List> codemodTypes, Path re * @param context * @return */ - public ChangedFile execute(final Path file, final FileWeavingContext context) { + public void execute(final Path file, final FileWeavingContext context) { // find a provider that can handle invoking the codemod "change phase" for (Class type : codemodTypes) { @@ -84,7 +84,7 @@ public ChangedFile execute(final Path file, final FileWeavingContext context) { // provider.get // } } - return null; + return; } /** Invoke the given codemods. */ diff --git a/languages/codemodder-semgrep-provider/src/main/resources/META-INF/services/io.codemodder.CodemodProvider b/languages/codemodder-semgrep-provider/src/main/resources/META-INF/services/io.codemodder.CodemodProvider new file mode 100644 index 000000000..93acd7157 --- /dev/null +++ b/languages/codemodder-semgrep-provider/src/main/resources/META-INF/services/io.codemodder.CodemodProvider @@ -0,0 +1 @@ +io.codemodder.providers.sarif.semgrep.SemgrepProvider diff --git a/languages/java/src/main/java/io/openpixee/java/SourceWeaver.java b/languages/java/src/main/java/io/openpixee/java/SourceWeaver.java index 9fbc2603a..54763e76a 100644 --- a/languages/java/src/main/java/io/openpixee/java/SourceWeaver.java +++ b/languages/java/src/main/java/io/openpixee/java/SourceWeaver.java @@ -157,7 +157,7 @@ private ChangedFile scanType( }); // do it with codemods - ChangedFile execute = codemodInvoker.execute(Path.of(javaFile.toURI()), context); + codemodInvoker.execute(Path.of(javaFile.toURI()), context); if (context.madeWeaves()) { final String encoding = detectEncoding(javaFile); From 1735091ba0865d65c68bc483c30a6b7b15ef27e9 Mon Sep 17 00:00:00 2001 From: Arshan Dabirsiaghi Date: Sun, 12 Mar 2023 13:36:58 -0400 Subject: [PATCH 07/19] first codemod replaced --- .../build.gradle.kts | 1 + .../codemods/SecureRandomCodemod.java | 18 +++--- .../ChangeConstructorTypeVisitor.java | 20 ++++--- .../java/io/codemodder/ChangeContext.java | 7 --- .../io/codemodder/CodeDirectoryModule.java | 20 ------- .../java/io/codemodder/CodemodInvoker.java | 55 +++++++++--------- .../io/codemodder/DefaultCodeDirectory.java | 25 +++++++++ .../java/io/codemodder/JavaParserChanger.java | 4 +- .../java/io/codemodder/JavaParserUtils.java | 10 +++- .../src/main/java/io/codemodder/Sarif.java | 27 +++++++-- .../sarif/semgrep/DefaultSemgrepRunner.java | 56 ++++++++++++++----- .../semgrep/DefaultSemgrepSarifProvider.java | 13 +++-- .../sarif/semgrep/SemgrepSarifProvider.java | 4 +- .../semgrep/SemgrepSarifProviderTest.java | 4 +- .../java/io/openpixee/java/SourceWeaver.java | 10 +--- .../io/openpixee/java/VisitorAssembler.java | 1 - .../protections/WeakPRNGVisitorFactory.java | 1 + .../PredictableSeedVulnerability.java | 3 +- .../PredictableSeedVulnerabilityWeaved.java | 3 +- .../openpixee/java/VisitorAssemblerTest.java | 6 +- .../java/protections/WeakPRNGTest.java | 22 +++++++- 21 files changed, 196 insertions(+), 114 deletions(-) delete mode 100644 languages/codemodder-framework-java/src/main/java/io/codemodder/ChangeContext.java create mode 100644 languages/codemodder-framework-java/src/main/java/io/codemodder/DefaultCodeDirectory.java diff --git a/languages/codemodder-default-codemods/build.gradle.kts b/languages/codemodder-default-codemods/build.gradle.kts index 777d9b9b7..9543d8139 100644 --- a/languages/codemodder-default-codemods/build.gradle.kts +++ b/languages/codemodder-default-codemods/build.gradle.kts @@ -40,6 +40,7 @@ dependencies { implementation(libs.javaparser.symbolsolver.core) implementation(libs.javaparser.symbolsolver.logic) implementation(libs.javaparser.symbolsolver.model) + implementation(project(":languages:codemodder-common")) implementation(project(":languages:codemodder-framework-java")) implementation(project(":languages:codemodder-semgrep-provider")) diff --git a/languages/codemodder-default-codemods/src/main/java/io/codemodder/codemods/SecureRandomCodemod.java b/languages/codemodder-default-codemods/src/main/java/io/codemodder/codemods/SecureRandomCodemod.java index 4edf888fb..a36bb365a 100644 --- a/languages/codemodder-default-codemods/src/main/java/io/codemodder/codemods/SecureRandomCodemod.java +++ b/languages/codemodder-default-codemods/src/main/java/io/codemodder/codemods/SecureRandomCodemod.java @@ -5,15 +5,15 @@ import com.github.javaparser.ast.CompilationUnit; import com.github.javaparser.ast.visitor.ModifierVisitor; import io.codemodder.ChangeConstructorTypeVisitor; -import io.codemodder.ChangeContext; import io.codemodder.CodeDirectory; import io.codemodder.Codemod; +import io.codemodder.FileWeavingContext; import io.codemodder.JavaParserChanger; import io.codemodder.ReviewGuidance; import io.codemodder.Sarif; import io.codemodder.providers.sarif.semgrep.SemgrepSarifProvider; import java.io.IOException; -import java.net.URISyntaxException; +import java.nio.file.Path; import java.util.List; import java.util.Optional; import javax.inject.Inject; @@ -32,17 +32,21 @@ public final class SecureRandomCodemod implements JavaParserChanger { @Inject public SecureRandomCodemod( final CodeDirectory codeDirectory, final SemgrepSarifProvider sarifProvider) - throws IOException, URISyntaxException { + throws IOException { this.sarif = sarifProvider.getSarif(codeDirectory.asPath(), "secure-random.semgrep"); } @Override - public Optional> createModifierVisitor(final CompilationUnit cu) { - List results = Sarif.getResultsForCompilationUnit(sarif, cu); - logger.debug("Found {} results in {} to change", results.size(), cu.getPrimaryTypeName().get()); + public Optional> createModifierVisitor( + final CodeDirectory codeDirectory, final Path path, final CompilationUnit cu) { + List results = Sarif.getResultsForCompilationUnit(sarif, path); + logger.trace("Found {} results in {} to change", results.size(), path); if (!results.isEmpty()) { return Optional.of( - new ChangeConstructorTypeVisitor(Sarif.findRegions(results), "java.lang.SecureRandom")); + new ChangeConstructorTypeVisitor( + Sarif.findRegions(results), + "java.security.SecureRandom", + "pixee:java/secure-random")); } return Optional.empty(); } diff --git a/languages/codemodder-framework-java/src/main/java/io/codemodder/ChangeConstructorTypeVisitor.java b/languages/codemodder-framework-java/src/main/java/io/codemodder/ChangeConstructorTypeVisitor.java index 10a078f7a..e87e5bf23 100644 --- a/languages/codemodder-framework-java/src/main/java/io/codemodder/ChangeConstructorTypeVisitor.java +++ b/languages/codemodder-framework-java/src/main/java/io/codemodder/ChangeConstructorTypeVisitor.java @@ -16,26 +16,32 @@ * A utility type that makes switching one type's constructor for another, with no changes to * arguments. */ -public final class ChangeConstructorTypeVisitor extends ModifierVisitor { +public final class ChangeConstructorTypeVisitor extends ModifierVisitor { private final List regions; private final String newType; private final String newTypeSimpleName; + private final String codemodId; - public ChangeConstructorTypeVisitor(final List regions, final String newType) { + public ChangeConstructorTypeVisitor( + final List regions, final String newType, final String codemodId) { this.regions = Objects.requireNonNull(regions); this.newType = Objects.requireNonNull(newType); this.newTypeSimpleName = toSimpleName(newType); + this.codemodId = Objects.requireNonNull(codemodId); } @SuppressWarnings("OptionalGetWithoutIsPresent") @Override - public Visitable visit(final ObjectCreationExpr objectCreationExpr, final ChangeContext context) { + public Visitable visit( + final ObjectCreationExpr objectCreationExpr, final FileWeavingContext context) { if (regions.stream().anyMatch(region -> regionMatchesNode(objectCreationExpr, region))) { - objectCreationExpr.setType(new ClassOrInterfaceType(newTypeSimpleName)); - addImportIfMissing(objectCreationExpr.findCompilationUnit().get(), newType); - Position begin = objectCreationExpr.getRange().get().begin; - context.record(begin.line, begin.column); + if (context.isLineIncluded(objectCreationExpr.getRange().get().begin.line)) { + objectCreationExpr.setType(new ClassOrInterfaceType(newTypeSimpleName)); + addImportIfMissing(objectCreationExpr.findCompilationUnit().get(), newType); + Position begin = objectCreationExpr.getRange().get().begin; + context.addWeave(Weave.from(begin.line, codemodId)); + } } return super.visit(objectCreationExpr, context); } diff --git a/languages/codemodder-framework-java/src/main/java/io/codemodder/ChangeContext.java b/languages/codemodder-framework-java/src/main/java/io/codemodder/ChangeContext.java deleted file mode 100644 index 5de153620..000000000 --- a/languages/codemodder-framework-java/src/main/java/io/codemodder/ChangeContext.java +++ /dev/null @@ -1,7 +0,0 @@ -package io.codemodder; - -/** Holds information about the */ -public interface ChangeContext { - - void record(int line, int column); -} diff --git a/languages/codemodder-framework-java/src/main/java/io/codemodder/CodeDirectoryModule.java b/languages/codemodder-framework-java/src/main/java/io/codemodder/CodeDirectoryModule.java index f6f2a1029..339ccdfc6 100644 --- a/languages/codemodder-framework-java/src/main/java/io/codemodder/CodeDirectoryModule.java +++ b/languages/codemodder-framework-java/src/main/java/io/codemodder/CodeDirectoryModule.java @@ -1,7 +1,6 @@ package io.codemodder; import com.google.inject.AbstractModule; -import java.io.File; import java.nio.file.Path; import java.util.Objects; @@ -18,23 +17,4 @@ final class CodeDirectoryModule extends AbstractModule { protected void configure() { bind(CodeDirectory.class).toInstance(new DefaultCodeDirectory(repositoryDir)); } - - private static class DefaultCodeDirectory implements CodeDirectory { - - private final Path repositoryDir; - - private DefaultCodeDirectory(final Path repositoryDir) { - this.repositoryDir = Objects.requireNonNull(repositoryDir); - } - - @Override - public File asFile() { - return repositoryDir.toFile(); - } - - @Override - public Path asPath() { - return repositoryDir; - } - } } diff --git a/languages/codemodder-framework-java/src/main/java/io/codemodder/CodemodInvoker.java b/languages/codemodder-framework-java/src/main/java/io/codemodder/CodemodInvoker.java index de86d0537..ee50021e0 100644 --- a/languages/codemodder-framework-java/src/main/java/io/codemodder/CodemodInvoker.java +++ b/languages/codemodder-framework-java/src/main/java/io/codemodder/CodemodInvoker.java @@ -1,13 +1,17 @@ package io.codemodder; +import com.github.javaparser.ast.CompilationUnit; +import com.github.javaparser.ast.visitor.ModifierVisitor; import com.google.inject.AbstractModule; import com.google.inject.Guice; import com.google.inject.Injector; import java.nio.file.Path; +import java.util.Arrays; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Objects; +import java.util.Optional; import java.util.ServiceLoader; import java.util.Set; import java.util.regex.Pattern; @@ -29,7 +33,8 @@ public final class CodemodInvoker { private final List> codemodTypes; private final Path repsitoryDir; - public CodemodInvoker(final List> codemodTypes, Path repositoryDir) { + public CodemodInvoker( + final List> codemodTypes, final Path repositoryDir) { // get all the providers ready for dependency injection & codemod instantiation List providers = ServiceLoader.load(CodemodProvider.class).stream() @@ -57,40 +62,38 @@ public CodemodInvoker(final List> codemodTypes, Path re } } - // will invoke every changer for every file, collecting the diffs - // will collate report - // will spit out CodeTF or whatever - /** - * @param file - * @param context - * @return + * Run the codemods we've collected on the given file. + * + * @param cu the parsed JavaParser representation of the file + * @param context a model we should keep updating as we process the file */ - public void execute(final Path file, final FileWeavingContext context) { + public void execute(final Path path, final CompilationUnit cu, final FileWeavingContext context) { // find a provider that can handle invoking the codemod "change phase" for (Class type : codemodTypes) { - // Changer changer = injector.getInstance(type); - // if(changer instanceof JavaParserChanger) { - //// CompilationUnit cu = parseJavaCode(javaFilePath); - //// ((JavaParserChanger) changer).createModifierVisitor(cu); - //// serializeBack(); - // } else if (changer instanceof SpoonChanger) { - // - // } else { - // throw new IllegalArgumentException(""); - // } - // for(CodemodProvider provider : providers) { - // provider.get - // } + Changer changer = injector.getInstance(type); + if (changer instanceof JavaParserChanger) { + JavaParserChanger javaParserChanger = (JavaParserChanger) changer; + Optional> modifierVisitor = + javaParserChanger.createModifierVisitor( + new DefaultCodeDirectory(repsitoryDir), path, cu); + modifierVisitor.ifPresent( + changeContextModifierVisitor -> cu.accept(changeContextModifierVisitor, context)); + } else { + throw new UnsupportedOperationException("unknown or not"); + } } - return; } - /** Invoke the given codemods. */ + /** + * This is the entry point custom-built codemods are supposed to go through. Right now, this is + * not useful directly as we're worried about + */ public static void run(final Class... codemodTypes) { - // loop through every file - // new CodemodInvoker(codemodTypes).execute(); + new CodemodInvoker(Arrays.asList(codemodTypes), Path.of(".")); + // TODO: loop through the files and invoke the codemods on each file + // codemodInvoker.execute(); } private static void validateRequiredFields(final Codemod codemodAnnotation) { diff --git a/languages/codemodder-framework-java/src/main/java/io/codemodder/DefaultCodeDirectory.java b/languages/codemodder-framework-java/src/main/java/io/codemodder/DefaultCodeDirectory.java new file mode 100644 index 000000000..3d7a5f904 --- /dev/null +++ b/languages/codemodder-framework-java/src/main/java/io/codemodder/DefaultCodeDirectory.java @@ -0,0 +1,25 @@ +package io.codemodder; + +import java.io.File; +import java.nio.file.Path; +import java.util.Objects; + +/** {@inheritDoc} */ +final class DefaultCodeDirectory implements CodeDirectory { + + private final Path repositoryDir; + + DefaultCodeDirectory(final Path repositoryDir) { + this.repositoryDir = Objects.requireNonNull(repositoryDir); + } + + @Override + public File asFile() { + return repositoryDir.toFile(); + } + + @Override + public Path asPath() { + return repositoryDir; + } +} diff --git a/languages/codemodder-framework-java/src/main/java/io/codemodder/JavaParserChanger.java b/languages/codemodder-framework-java/src/main/java/io/codemodder/JavaParserChanger.java index 98785585c..a9a98054b 100644 --- a/languages/codemodder-framework-java/src/main/java/io/codemodder/JavaParserChanger.java +++ b/languages/codemodder-framework-java/src/main/java/io/codemodder/JavaParserChanger.java @@ -2,6 +2,7 @@ import com.github.javaparser.ast.CompilationUnit; import com.github.javaparser.ast.visitor.ModifierVisitor; +import java.nio.file.Path; import java.util.Optional; /** {@inheritDoc} Uses JavaParser to change Java source files. */ @@ -11,5 +12,6 @@ public interface JavaParserChanger extends Changer { * Creates a visitor for a given Java source file, or not. It's up to the implementing type to * determine if and how source file should be changed. */ - Optional> createModifierVisitor(CompilationUnit cu); + Optional> createModifierVisitor( + CodeDirectory codeDirectory, Path path, CompilationUnit cu); } diff --git a/languages/codemodder-framework-java/src/main/java/io/codemodder/JavaParserUtils.java b/languages/codemodder-framework-java/src/main/java/io/codemodder/JavaParserUtils.java index b6165e08c..f649b0930 100644 --- a/languages/codemodder-framework-java/src/main/java/io/codemodder/JavaParserUtils.java +++ b/languages/codemodder-framework-java/src/main/java/io/codemodder/JavaParserUtils.java @@ -1,6 +1,7 @@ package io.codemodder; import com.contrastsecurity.sarif.Region; +import com.github.javaparser.Range; import com.github.javaparser.ast.CompilationUnit; import com.github.javaparser.ast.ImportDeclaration; import com.github.javaparser.ast.Node; @@ -41,6 +42,13 @@ public static void addImportIfMissing(final CompilationUnit cu, final String cla * @return true, if the node is within the region */ public static boolean regionMatchesNode(final Node node, final Region region) { - return false; + Range sarifRange = + Range.range( + region.getStartLine(), + region.getStartColumn(), + region.getEndLine(), + region.getEndColumn()); + Range observedRange = node.getRange().get(); + return observedRange.overlapsWith(sarifRange); } } diff --git a/languages/codemodder-framework-java/src/main/java/io/codemodder/Sarif.java b/languages/codemodder-framework-java/src/main/java/io/codemodder/Sarif.java index c3b039ff5..887847b56 100644 --- a/languages/codemodder-framework-java/src/main/java/io/codemodder/Sarif.java +++ b/languages/codemodder-framework-java/src/main/java/io/codemodder/Sarif.java @@ -4,7 +4,11 @@ import com.contrastsecurity.sarif.Result; import com.contrastsecurity.sarif.SarifSchema210; import com.github.javaparser.ast.CompilationUnit; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; import java.util.List; +import java.util.stream.Collectors; /** * A wrapper around {@link com.contrastsecurity.sarif.SarifSchema210} that also provides convenience @@ -17,12 +21,25 @@ private Sarif() {} /** * Get all the {@link Result} that are from the given {@link CompilationUnit}. * - * @param cu the source file + * @param sarif the SARIF + * @param filePath the file that's being scanned * @return a {@link List} containing the SARIF results for this file */ public static List getResultsForCompilationUnit( - final SarifSchema210 sarif, final CompilationUnit cu) { - throw new UnsupportedOperationException(); + final SarifSchema210 sarif, final Path filePath) { + List results = sarif.getRuns().get(0).getResults(); + return results.stream() + .filter( + result -> { + String uri = + result.getLocations().get(0).getPhysicalLocation().getArtifactLocation().getUri(); + try { + return Files.isSameFile(filePath, Path.of(uri)); + } catch (IOException e) { // this should never happen + return false; + } + }) + .collect(Collectors.toUnmodifiableList()); } /** @@ -32,6 +49,8 @@ public static List getResultsForCompilationUnit( * @return a list of source code locations */ public static List findRegions(final List results) { - throw new UnsupportedOperationException(); + return results.stream() + .map(result -> result.getLocations().get(0).getPhysicalLocation().getRegion()) + .collect(Collectors.toUnmodifiableList()); } } diff --git a/languages/codemodder-semgrep-provider/src/main/java/io/codemodder/providers/sarif/semgrep/DefaultSemgrepRunner.java b/languages/codemodder-semgrep-provider/src/main/java/io/codemodder/providers/sarif/semgrep/DefaultSemgrepRunner.java index 2d85b2829..79a3b79a6 100644 --- a/languages/codemodder-semgrep-provider/src/main/java/io/codemodder/providers/sarif/semgrep/DefaultSemgrepRunner.java +++ b/languages/codemodder-semgrep-provider/src/main/java/io/codemodder/providers/sarif/semgrep/DefaultSemgrepRunner.java @@ -2,11 +2,14 @@ import com.contrastsecurity.sarif.SarifSchema210; import com.fasterxml.jackson.databind.ObjectMapper; -import io.openpixee.security.SystemCommand; import java.io.File; import java.io.FileReader; import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; import java.nio.file.Path; +import java.nio.file.StandardCopyOption; +import java.util.Optional; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -21,18 +24,36 @@ public SarifSchema210 runWithSingleRule(final Path rule, final Path repository) String repositoryPath = repository.toString(); File sarifFile = File.createTempFile("semgrep", ".sarif"); sarifFile.deleteOnExit(); - Process p = - SystemCommand.runCommand( - Runtime.getRuntime(), - new String[] { - "semgrep", - "--sarif", - "-o", - sarifFile.getAbsolutePath(), - "--config", - ruleDirectoryPath, - repositoryPath - }); + + String[] args = + new String[] { + "semgrep", + "--sarif", + "-o", + sarifFile.getAbsolutePath(), + "--config", + ruleDirectoryPath, + repositoryPath + }; + + // backup existing .segmrepignore if it exists + File existingSemgrepFile = new File(".semgrepignore"); + Optional backup = Optional.empty(); + + if (existingSemgrepFile.exists()) { + File backupFile = File.createTempFile("backup", ".semgrepignore"); + if (backupFile.exists()) { + backupFile.delete(); // i don't know how but this is happening in tests + } + Files.copy(existingSemgrepFile.toPath(), backupFile.toPath()); + backup = Optional.of(backupFile); + } + + // create an an empty .semgrepignore file + Files.write( + existingSemgrepFile.toPath(), OUR_SEMGREPIGNORE_CONTENTS.getBytes(StandardCharsets.UTF_8)); + + Process p = new ProcessBuilder(args).inheritIO().start(); try { int rc = p.waitFor(); if (rc != 0) { @@ -42,8 +63,17 @@ public SarifSchema210 runWithSingleRule(final Path rule, final Path repository) logger.error("problem waiting for semgrep process execution", e); } + // restore existing .semgrepignore if it exists + if (backup.isPresent()) { + Files.copy( + backup.get().toPath(), existingSemgrepFile.toPath(), StandardCopyOption.REPLACE_EXISTING); + } else { + existingSemgrepFile.delete(); + } return new ObjectMapper().readValue(new FileReader(sarifFile), SarifSchema210.class); } + private static final String OUR_SEMGREPIGNORE_CONTENTS = "# dont ignore anything"; + private static final Logger logger = LoggerFactory.getLogger(SemgrepRunner.class); } diff --git a/languages/codemodder-semgrep-provider/src/main/java/io/codemodder/providers/sarif/semgrep/DefaultSemgrepSarifProvider.java b/languages/codemodder-semgrep-provider/src/main/java/io/codemodder/providers/sarif/semgrep/DefaultSemgrepSarifProvider.java index 16ddcffba..246881931 100644 --- a/languages/codemodder-semgrep-provider/src/main/java/io/codemodder/providers/sarif/semgrep/DefaultSemgrepSarifProvider.java +++ b/languages/codemodder-semgrep-provider/src/main/java/io/codemodder/providers/sarif/semgrep/DefaultSemgrepSarifProvider.java @@ -2,13 +2,13 @@ import com.contrastsecurity.sarif.SarifSchema210; import java.io.IOException; -import java.net.URISyntaxException; +import java.io.InputStream; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; -import java.nio.file.Paths; import java.util.HashMap; import java.util.Map; +import org.apache.commons.io.IOUtils; /** This is the memoizing Semgrep runner that we'll give to codemods. */ final class DefaultSemgrepSarifProvider implements SemgrepSarifProvider { @@ -20,14 +20,15 @@ final class DefaultSemgrepSarifProvider implements SemgrepSarifProvider { } @Override - public SarifSchema210 getSarif(final Path repository, final String rulePath) - throws IOException, URISyntaxException { + public SarifSchema210 getSarif(final Path repository, final String rulePath) throws IOException { if (sarifs.containsKey(rulePath)) { return sarifs.get(rulePath); } - String ruleYaml = - Files.readString(Paths.get(getClass().getClassLoader().getResource(rulePath).toURI())); + InputStream ruleInputStream = getClass().getClassLoader().getResource(rulePath).openStream(); + String ruleYaml = IOUtils.toString(ruleInputStream, StandardCharsets.UTF_8); + ruleInputStream.close(); + Path semgrepRuleFile = Files.createTempFile("semgrep", ".yaml"); Files.write(semgrepRuleFile, ruleYaml.getBytes(StandardCharsets.UTF_8)); diff --git a/languages/codemodder-semgrep-provider/src/main/java/io/codemodder/providers/sarif/semgrep/SemgrepSarifProvider.java b/languages/codemodder-semgrep-provider/src/main/java/io/codemodder/providers/sarif/semgrep/SemgrepSarifProvider.java index 334c64ec7..191bfb13c 100644 --- a/languages/codemodder-semgrep-provider/src/main/java/io/codemodder/providers/sarif/semgrep/SemgrepSarifProvider.java +++ b/languages/codemodder-semgrep-provider/src/main/java/io/codemodder/providers/sarif/semgrep/SemgrepSarifProvider.java @@ -2,7 +2,6 @@ import com.contrastsecurity.sarif.SarifSchema210; import java.io.IOException; -import java.net.URISyntaxException; import java.nio.file.Path; /** A provider that invokes semgrep (assuming Semgrep is on the $PATH) */ @@ -13,6 +12,5 @@ public interface SemgrepSarifProvider { * * @param rulePath the classpath location of a semgrep YAML rule */ - SarifSchema210 getSarif(Path repositoryPath, String rulePath) - throws IOException, URISyntaxException; + SarifSchema210 getSarif(Path repositoryPath, String rulePath) throws IOException; } diff --git a/languages/codemodder-semgrep-provider/src/test/java/io/codemodder/providers/sarif/semgrep/SemgrepSarifProviderTest.java b/languages/codemodder-semgrep-provider/src/test/java/io/codemodder/providers/sarif/semgrep/SemgrepSarifProviderTest.java index 4baeb26a8..22cc01a24 100644 --- a/languages/codemodder-semgrep-provider/src/test/java/io/codemodder/providers/sarif/semgrep/SemgrepSarifProviderTest.java +++ b/languages/codemodder-semgrep-provider/src/test/java/io/codemodder/providers/sarif/semgrep/SemgrepSarifProviderTest.java @@ -7,7 +7,6 @@ import com.contrastsecurity.sarif.Run; import com.contrastsecurity.sarif.SarifSchema210; import java.io.IOException; -import java.net.URISyntaxException; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; @@ -18,8 +17,7 @@ final class SemgrepSarifProviderTest { @Test - void it_runs_semgrep_and_produces_expected_sarif(@TempDir Path repositoryDir) - throws IOException, URISyntaxException { + void it_runs_semgrep_and_produces_expected_sarif(@TempDir Path repositoryDir) throws IOException { // create vulnerable code in a new temporary repository dir Path javaFile = Files.createTempFile(repositoryDir, "WeakRandom", ".java"); diff --git a/languages/java/src/main/java/io/openpixee/java/SourceWeaver.java b/languages/java/src/main/java/io/openpixee/java/SourceWeaver.java index 54763e76a..babebe580 100644 --- a/languages/java/src/main/java/io/openpixee/java/SourceWeaver.java +++ b/languages/java/src/main/java/io/openpixee/java/SourceWeaver.java @@ -101,14 +101,6 @@ final class DefaultSourceWeaver implements SourceWeaver { return new WeavingResult.Default(changedFiles, unscannableFiles); } - private ChangedFile scanIndividualJavaFileWithCodemodders( - final JavaParser javaParser, - final String javaFile, - final IncludesExcludes includesExcludes) { - - return null; - } - private static class UnparseableFileException extends Exception { private UnparseableFileException(final String javaFile) { super(javaFile); @@ -157,7 +149,7 @@ private ChangedFile scanType( }); // do it with codemods - codemodInvoker.execute(Path.of(javaFile.toURI()), context); + codemodInvoker.execute(javaFile.toPath(), cu, context); if (context.madeWeaves()) { final String encoding = detectEncoding(javaFile); diff --git a/languages/java/src/main/java/io/openpixee/java/VisitorAssembler.java b/languages/java/src/main/java/io/openpixee/java/VisitorAssembler.java index 509713e99..111d89cc6 100644 --- a/languages/java/src/main/java/io/openpixee/java/VisitorAssembler.java +++ b/languages/java/src/main/java/io/openpixee/java/VisitorAssembler.java @@ -62,7 +62,6 @@ public List assembleJavaCodeScanningVisitorFactories( new RuntimeExecVisitorFactory(), new SpringMultipartVisitorFactory(), new UnsafeReadlineVisitorFactory(), - new WeakPRNGVisitorFactory(), new XMLDecoderVisitorFactory(), new XStreamVisitorFactory(), new XXEVisitorFactory(), diff --git a/languages/java/src/main/java/io/openpixee/java/protections/WeakPRNGVisitorFactory.java b/languages/java/src/main/java/io/openpixee/java/protections/WeakPRNGVisitorFactory.java index 58cecd85a..ac6f819ec 100644 --- a/languages/java/src/main/java/io/openpixee/java/protections/WeakPRNGVisitorFactory.java +++ b/languages/java/src/main/java/io/openpixee/java/protections/WeakPRNGVisitorFactory.java @@ -21,6 +21,7 @@ * This visitor replaces instance creation of {@link java.util.Random} with {@link * java.security.SecureRandom}. */ +@SuppressWarnings("unused") // will remove once we're happy with the codemod public final class WeakPRNGVisitorFactory implements VisitorFactory { @Override diff --git a/languages/java/src/test/java/com/acme/testcode/PredictableSeedVulnerability.java b/languages/java/src/test/java/com/acme/testcode/PredictableSeedVulnerability.java index 91cc50373..5e55d0509 100644 --- a/languages/java/src/test/java/com/acme/testcode/PredictableSeedVulnerability.java +++ b/languages/java/src/test/java/com/acme/testcode/PredictableSeedVulnerability.java @@ -1,11 +1,12 @@ package com.acme.testcode; +import java.security.SecureRandom; import java.util.Random; interface PredictableSeedVulnerability { default void hasThing() { - Random random = new Random(); + Random random = new SecureRandom(); random.setSeed(512L); random.setSeed(getLong()); } diff --git a/languages/java/src/test/java/com/acme/testcode/PredictableSeedVulnerabilityWeaved.java b/languages/java/src/test/java/com/acme/testcode/PredictableSeedVulnerabilityWeaved.java index 116270e33..770eb6c5b 100644 --- a/languages/java/src/test/java/com/acme/testcode/PredictableSeedVulnerabilityWeaved.java +++ b/languages/java/src/test/java/com/acme/testcode/PredictableSeedVulnerabilityWeaved.java @@ -1,11 +1,12 @@ package com.acme.testcode; +import java.security.SecureRandom; import java.util.Random; interface PredictableSeedVulnerabilityWeaved { default void hasThing() { - Random random = new Random(); + Random random = new SecureRandom(); random.setSeed(java.lang.System.currentTimeMillis()); random.setSeed(getLong()); } diff --git a/languages/java/src/test/java/io/openpixee/java/VisitorAssemblerTest.java b/languages/java/src/test/java/io/openpixee/java/VisitorAssemblerTest.java index e3dbb18d9..6b1a103f9 100644 --- a/languages/java/src/test/java/io/openpixee/java/VisitorAssemblerTest.java +++ b/languages/java/src/test/java/io/openpixee/java/VisitorAssemblerTest.java @@ -14,15 +14,15 @@ final class VisitorAssemblerTest { @Test void it_filters_java_factories_based_on_configuration() { VisitorAssembler assembler = VisitorAssembler.createDefault(); - RuleContext everythingButSecureRandom = - RuleContext.of(DefaultRuleSetting.ENABLED, List.of("pixee:java/secure-random")); + RuleContext everythingButUnsafeReadline = + RuleContext.of(DefaultRuleSetting.ENABLED, List.of("pixee:java/limit-readline")); RuleContext everything = RuleContext.of(DefaultRuleSetting.ENABLED, List.of()); File repositoryRoot = new File("."); List allFactories = assembler.assembleJavaCodeScanningVisitorFactories(repositoryRoot, everything, List.of()); List allButOne = assembler.assembleJavaCodeScanningVisitorFactories( - repositoryRoot, everythingButSecureRandom, List.of()); + repositoryRoot, everythingButUnsafeReadline, List.of()); // just make sure we're getting a reasonable number of factories assertThat(allFactories.isEmpty(), is(false)); diff --git a/languages/java/src/test/java/io/openpixee/java/protections/WeakPRNGTest.java b/languages/java/src/test/java/io/openpixee/java/protections/WeakPRNGTest.java index 77a3ec9c9..ec8261e4c 100644 --- a/languages/java/src/test/java/io/openpixee/java/protections/WeakPRNGTest.java +++ b/languages/java/src/test/java/io/openpixee/java/protections/WeakPRNGTest.java @@ -1,5 +1,11 @@ package io.openpixee.java.protections; +import com.github.javaparser.ast.CompilationUnit; +import com.github.javaparser.ast.visitor.ModifierVisitor; +import io.codemodder.FileWeavingContext; +import io.openpixee.java.DoNothingVisitor; +import io.openpixee.java.VisitorFactory; +import java.io.File; import java.io.IOException; import org.junit.jupiter.api.Test; @@ -7,7 +13,21 @@ final class WeakPRNGTest { @Test void it_replaces_insecure_random() throws IOException { + + // we use a DoNothingVisitor here because this should be provided by the new codemod WeavingTests.assertJavaWeaveWorkedAndWontReweave( - "src/test/java/com/acme/testcode/RandomVulnerability.java", new WeakPRNGVisitorFactory()); + "src/test/java/com/acme/testcode/RandomVulnerability.java", + new VisitorFactory() { + @Override + public ModifierVisitor createJavaCodeVisitorFor( + final File file, final CompilationUnit cu) { + return new DoNothingVisitor(); + } + + @Override + public String ruleId() { + return "pixee:java/unused"; + } + }); } } From d2d0176b7fbcbf16eece0e4880934597a2fd665d Mon Sep 17 00:00:00 2001 From: Arshan Dabirsiaghi Date: Sun, 12 Mar 2023 13:42:10 -0400 Subject: [PATCH 08/19] pre pr cleanup --- .../src/main/java/io/codemodder/CodemodInvoker.java | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/languages/codemodder-framework-java/src/main/java/io/codemodder/CodemodInvoker.java b/languages/codemodder-framework-java/src/main/java/io/codemodder/CodemodInvoker.java index ee50021e0..1cdb5e6e0 100644 --- a/languages/codemodder-framework-java/src/main/java/io/codemodder/CodemodInvoker.java +++ b/languages/codemodder-framework-java/src/main/java/io/codemodder/CodemodInvoker.java @@ -28,10 +28,9 @@ */ public final class CodemodInvoker { - private final Set allModules; private final Injector injector; private final List> codemodTypes; - private final Path repsitoryDir; + private final Path repositoryDir; public CodemodInvoker( final List> codemodTypes, final Path repositoryDir) { @@ -50,8 +49,7 @@ public CodemodInvoker( allModules.addAll(modules); } - this.repsitoryDir = Objects.requireNonNull(repositoryDir); - this.allModules = Collections.unmodifiableSet(allModules); + this.repositoryDir = Objects.requireNonNull(repositoryDir); this.injector = Guice.createInjector(allModules); this.codemodTypes = Collections.unmodifiableList(codemodTypes); @@ -77,7 +75,7 @@ public void execute(final Path path, final CompilationUnit cu, final FileWeaving JavaParserChanger javaParserChanger = (JavaParserChanger) changer; Optional> modifierVisitor = javaParserChanger.createModifierVisitor( - new DefaultCodeDirectory(repsitoryDir), path, cu); + new DefaultCodeDirectory(repositoryDir), path, cu); modifierVisitor.ifPresent( changeContextModifierVisitor -> cu.accept(changeContextModifierVisitor, context)); } else { From d5740b38edde36bece1c1b26f56a27c4b0e091ca Mon Sep 17 00:00:00 2001 From: Arshan Dabirsiaghi Date: Sun, 12 Mar 2023 15:03:22 -0400 Subject: [PATCH 09/19] now rule management is built in --- .../io/codemodder}/DefaultRuleSetting.java | 2 +- .../main/java/io/codemodder}/RuleContext.java | 2 +- .../codemodder/codemods/DefaultCodemods.java | 16 ++++++++++ .../codemods/SecureRandomCodemod.java | 5 ++++ .../src/main/java/io/codemodder/Changer.java | 6 +++- .../java/io/codemodder/CodemodInvoker.java | 30 ++++++++++++------- .../io/codemodder/CodemodInvokerTest.java | 21 +++++++++++-- .../java/DefaultSarifProcessorPlugin.java | 1 + .../java/io/openpixee/java/JavaFixitCli.java | 1 + .../io/openpixee/java/JavaFixitCliRun.java | 19 ++++++++++-- .../openpixee/java/PluginVisitorFinder.java | 1 + .../openpixee/java/SarifProcessorPlugin.java | 1 + .../java/io/openpixee/java/SourceWeaver.java | 7 ++--- .../io/openpixee/java/VisitorAssembler.java | 1 + .../java/plugins/codeql/CodeQlPlugin.java | 2 +- .../plugins/contrast/ContrastScanPlugin.java | 2 +- .../io/openpixee/java/RuleContextTest.java | 2 ++ .../openpixee/java/VisitorAssemblerTest.java | 2 ++ .../java/plugins/codeql/CodeQlPluginTest.java | 4 +-- .../contrast/ContrastScanPluginTest.java | 4 +-- .../java/protections/WeavingTests.java | 18 +++++++++-- 21 files changed, 114 insertions(+), 33 deletions(-) rename languages/{java/src/main/java/io/openpixee/java => codemodder-common/src/main/java/io/codemodder}/DefaultRuleSetting.java (96%) rename languages/{java/src/main/java/io/openpixee/java => codemodder-common/src/main/java/io/codemodder}/RuleContext.java (97%) create mode 100644 languages/codemodder-default-codemods/src/main/java/io/codemodder/codemods/DefaultCodemods.java diff --git a/languages/java/src/main/java/io/openpixee/java/DefaultRuleSetting.java b/languages/codemodder-common/src/main/java/io/codemodder/DefaultRuleSetting.java similarity index 96% rename from languages/java/src/main/java/io/openpixee/java/DefaultRuleSetting.java rename to languages/codemodder-common/src/main/java/io/codemodder/DefaultRuleSetting.java index f837d0acc..8be2e03e9 100644 --- a/languages/java/src/main/java/io/openpixee/java/DefaultRuleSetting.java +++ b/languages/codemodder-common/src/main/java/io/codemodder/DefaultRuleSetting.java @@ -1,4 +1,4 @@ -package io.openpixee.java; +package io.codemodder; import java.util.Objects; diff --git a/languages/java/src/main/java/io/openpixee/java/RuleContext.java b/languages/codemodder-common/src/main/java/io/codemodder/RuleContext.java similarity index 97% rename from languages/java/src/main/java/io/openpixee/java/RuleContext.java rename to languages/codemodder-common/src/main/java/io/codemodder/RuleContext.java index 31563700f..09adb9404 100644 --- a/languages/java/src/main/java/io/openpixee/java/RuleContext.java +++ b/languages/codemodder-common/src/main/java/io/codemodder/RuleContext.java @@ -1,4 +1,4 @@ -package io.openpixee.java; +package io.codemodder; import java.util.List; import java.util.Objects; diff --git a/languages/codemodder-default-codemods/src/main/java/io/codemodder/codemods/DefaultCodemods.java b/languages/codemodder-default-codemods/src/main/java/io/codemodder/codemods/DefaultCodemods.java new file mode 100644 index 000000000..e14421e9a --- /dev/null +++ b/languages/codemodder-default-codemods/src/main/java/io/codemodder/codemods/DefaultCodemods.java @@ -0,0 +1,16 @@ +package io.codemodder.codemods; + +import io.codemodder.Changer; +import java.util.List; + +/** + * Give an ability for users to list all the codemods so they don't have to reference them + * individually. + */ +public final class DefaultCodemods { + + /** Get a list of all the codemods in our default set. */ + public static List> asList() { + return List.of(SecureRandomCodemod.class); + } +} diff --git a/languages/codemodder-default-codemods/src/main/java/io/codemodder/codemods/SecureRandomCodemod.java b/languages/codemodder-default-codemods/src/main/java/io/codemodder/codemods/SecureRandomCodemod.java index a36bb365a..d6cc667b6 100644 --- a/languages/codemodder-default-codemods/src/main/java/io/codemodder/codemods/SecureRandomCodemod.java +++ b/languages/codemodder-default-codemods/src/main/java/io/codemodder/codemods/SecureRandomCodemod.java @@ -51,5 +51,10 @@ public Optional> createModifierVisitor( return Optional.empty(); } + @Override + public String getCodemodId() { + return "pixee:java/secure-random"; + } + private static final Logger logger = LoggerFactory.getLogger(SecureRandomCodemod.class); } diff --git a/languages/codemodder-framework-java/src/main/java/io/codemodder/Changer.java b/languages/codemodder-framework-java/src/main/java/io/codemodder/Changer.java index 16433f378..e97cee7f1 100644 --- a/languages/codemodder-framework-java/src/main/java/io/codemodder/Changer.java +++ b/languages/codemodder-framework-java/src/main/java/io/codemodder/Changer.java @@ -1,4 +1,8 @@ package io.codemodder; /** An empty interface that marks that a codemod type has provided some utility to change. */ -public interface Changer {} +public interface Changer { + + /** Get the ID. */ + String getCodemodId(); +} diff --git a/languages/codemodder-framework-java/src/main/java/io/codemodder/CodemodInvoker.java b/languages/codemodder-framework-java/src/main/java/io/codemodder/CodemodInvoker.java index 1cdb5e6e0..900d2891f 100644 --- a/languages/codemodder-framework-java/src/main/java/io/codemodder/CodemodInvoker.java +++ b/languages/codemodder-framework-java/src/main/java/io/codemodder/CodemodInvoker.java @@ -6,6 +6,7 @@ import com.google.inject.Guice; import com.google.inject.Injector; import java.nio.file.Path; +import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashSet; @@ -28,12 +29,18 @@ */ public final class CodemodInvoker { - private final Injector injector; - private final List> codemodTypes; + private final List codemods; private final Path repositoryDir; public CodemodInvoker( final List> codemodTypes, final Path repositoryDir) { + this(codemodTypes, RuleContext.of(DefaultRuleSetting.ENABLED, List.of()), repositoryDir); + } + + public CodemodInvoker( + final List> codemodTypes, + final RuleContext ruleContext, + final Path repositoryDir) { // get all the providers ready for dependency injection & codemod instantiation List providers = ServiceLoader.load(CodemodProvider.class).stream() @@ -49,15 +56,19 @@ public CodemodInvoker( allModules.addAll(modules); } - this.repositoryDir = Objects.requireNonNull(repositoryDir); - this.injector = Guice.createInjector(allModules); - this.codemodTypes = Collections.unmodifiableList(codemodTypes); - - // validate all of the codemods + // validate and instantiate the codemods + Injector injector = Guice.createInjector(allModules); + List codemods = new ArrayList<>(); for (Class type : codemodTypes) { Codemod codemodAnnotation = type.getAnnotation(Codemod.class); validateRequiredFields(codemodAnnotation); + Changer changer = injector.getInstance(type); + if (ruleContext.isRuleAllowed(changer.getCodemodId())) { + codemods.add(changer); + } } + this.codemods = Collections.unmodifiableList(codemods); + this.repositoryDir = Objects.requireNonNull(repositoryDir); } /** @@ -67,10 +78,7 @@ public CodemodInvoker( * @param context a model we should keep updating as we process the file */ public void execute(final Path path, final CompilationUnit cu, final FileWeavingContext context) { - - // find a provider that can handle invoking the codemod "change phase" - for (Class type : codemodTypes) { - Changer changer = injector.getInstance(type); + for (Changer changer : codemods) { if (changer instanceof JavaParserChanger) { JavaParserChanger javaParserChanger = (JavaParserChanger) changer; Optional> modifierVisitor = diff --git a/languages/codemodder-framework-java/src/test/java/io/codemodder/CodemodInvokerTest.java b/languages/codemodder-framework-java/src/test/java/io/codemodder/CodemodInvokerTest.java index 56053584c..cceb50d29 100644 --- a/languages/codemodder-framework-java/src/test/java/io/codemodder/CodemodInvokerTest.java +++ b/languages/codemodder-framework-java/src/test/java/io/codemodder/CodemodInvokerTest.java @@ -12,19 +12,34 @@ final class CodemodInvokerTest { value = "test_mod", author = "valid@valid.com", reviewGuidance = ReviewGuidance.MERGE_AFTER_CURSORY_REVIEW) - static class InvalidCodemodName implements Changer {} + static class InvalidCodemodName implements Changer { + @Override + public String getCodemodId() { + return "test:java/invalidname"; + } + } @Codemod( value = "test_mod", author = " ", reviewGuidance = ReviewGuidance.MERGE_AFTER_CURSORY_REVIEW) - class EmptyCodemodAuthor implements Changer {} + class EmptyCodemodAuthor implements Changer { + @Override + public String getCodemodId() { + return "test:java/emptyauthor"; + } + } @Codemod( value = "pixee:java/id", author = "valid@valid.com", reviewGuidance = ReviewGuidance.MERGE_AFTER_CURSORY_REVIEW) - final class ValidCodemod implements Changer {} + final class ValidCodemod implements Changer { + @Override + public String getCodemodId() { + return "test:java/id"; + } + } @Test void it_validates_codemod_ids() { diff --git a/languages/java/src/main/java/io/openpixee/java/DefaultSarifProcessorPlugin.java b/languages/java/src/main/java/io/openpixee/java/DefaultSarifProcessorPlugin.java index 1aae550fb..c8df700e7 100644 --- a/languages/java/src/main/java/io/openpixee/java/DefaultSarifProcessorPlugin.java +++ b/languages/java/src/main/java/io/openpixee/java/DefaultSarifProcessorPlugin.java @@ -2,6 +2,7 @@ import com.contrastsecurity.sarif.Run; import com.contrastsecurity.sarif.Tool; +import io.codemodder.RuleContext; import java.io.File; import java.util.Collections; import java.util.List; diff --git a/languages/java/src/main/java/io/openpixee/java/JavaFixitCli.java b/languages/java/src/main/java/io/openpixee/java/JavaFixitCli.java index 2acf4de2e..a0c0d8f01 100644 --- a/languages/java/src/main/java/io/openpixee/java/JavaFixitCli.java +++ b/languages/java/src/main/java/io/openpixee/java/JavaFixitCli.java @@ -5,6 +5,7 @@ import com.github.lalyos.jfiglet.FigletFont; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Stopwatch; +import io.codemodder.DefaultRuleSetting; import java.io.File; import java.io.IOException; import java.util.Collections; diff --git a/languages/java/src/main/java/io/openpixee/java/JavaFixitCliRun.java b/languages/java/src/main/java/io/openpixee/java/JavaFixitCliRun.java index f89a87a78..6ab4187ce 100644 --- a/languages/java/src/main/java/io/openpixee/java/JavaFixitCliRun.java +++ b/languages/java/src/main/java/io/openpixee/java/JavaFixitCliRun.java @@ -3,8 +3,14 @@ import ch.qos.logback.classic.Level; import com.fasterxml.jackson.databind.ObjectMapper; import io.codemodder.ChangedFile; +import io.codemodder.Changer; +import io.codemodder.CodemodInvoker; +import io.codemodder.DefaultRuleSetting; import io.codemodder.IncludesExcludes; +import io.codemodder.RuleContext; import io.codemodder.Weave; +import io.codemodder.codemods.DefaultCodemods; +import io.codemodder.codemods.SecureRandomCodemod; import io.github.pixee.codetf.CodeTFReport; import java.io.File; import java.io.IOException; @@ -107,12 +113,21 @@ public CodeTFReport run( .filter(file -> includesExcludes.shouldInspect(new File(file))) .collect(Collectors.toList()))); - LOG.debug("Scanning following files: {}", allJavaFiles); + LOG.debug("Scanning following files: {}", allJavaFiles.size()); + List> defaultCodemodTypes = DefaultCodemods.asList(); + CodemodInvoker codemodInvoker = + new CodemodInvoker( + List.of(SecureRandomCodemod.class), ruleContext, repositoryRoot.toPath()); // run the Java code visitors final var javaSourceWeaveResult = javaSourceWeaver.weave( - repositoryRoot, sourceDirectories, allJavaFiles, factories, includesExcludes); + repositoryRoot, + sourceDirectories, + allJavaFiles, + factories, + codemodInvoker, + includesExcludes); // get the non-Java code visitors final List fileBasedVisitors = diff --git a/languages/java/src/main/java/io/openpixee/java/PluginVisitorFinder.java b/languages/java/src/main/java/io/openpixee/java/PluginVisitorFinder.java index fe9acdc6e..fc45b6267 100644 --- a/languages/java/src/main/java/io/openpixee/java/PluginVisitorFinder.java +++ b/languages/java/src/main/java/io/openpixee/java/PluginVisitorFinder.java @@ -4,6 +4,7 @@ import com.contrastsecurity.sarif.SarifSchema210; import com.contrastsecurity.sarif.ToolComponent; import com.fasterxml.jackson.databind.ObjectMapper; +import io.codemodder.RuleContext; import java.io.File; import java.io.FileReader; import java.io.IOException; diff --git a/languages/java/src/main/java/io/openpixee/java/SarifProcessorPlugin.java b/languages/java/src/main/java/io/openpixee/java/SarifProcessorPlugin.java index 207b254a8..a593647a7 100644 --- a/languages/java/src/main/java/io/openpixee/java/SarifProcessorPlugin.java +++ b/languages/java/src/main/java/io/openpixee/java/SarifProcessorPlugin.java @@ -1,6 +1,7 @@ package io.openpixee.java; import com.contrastsecurity.sarif.Run; +import io.codemodder.RuleContext; import java.io.File; import java.util.List; diff --git a/languages/java/src/main/java/io/openpixee/java/SourceWeaver.java b/languages/java/src/main/java/io/openpixee/java/SourceWeaver.java index babebe580..0ab149382 100644 --- a/languages/java/src/main/java/io/openpixee/java/SourceWeaver.java +++ b/languages/java/src/main/java/io/openpixee/java/SourceWeaver.java @@ -13,12 +13,10 @@ import io.codemodder.CodemodInvoker; import io.codemodder.FileWeavingContext; import io.codemodder.IncludesExcludes; -import io.codemodder.codemods.SecureRandomCodemod; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; -import java.nio.file.Path; import java.util.HashSet; import java.util.List; import java.util.Set; @@ -39,6 +37,7 @@ WeavingResult weave( List javaSourceDirectories, List javaFiles, List visitorFactories, + CodemodInvoker codemodInvoker, IncludesExcludes includesExcludes) throws IOException; @@ -61,6 +60,7 @@ final class DefaultSourceWeaver implements SourceWeaver { final List javaSourceDirectories, final List javaSourceFiles, final List visitorFactories, + final CodemodInvoker codemodInvoker, final IncludesExcludes includesExcludes) throws IOException { /* @@ -74,9 +74,6 @@ final class DefaultSourceWeaver implements SourceWeaver { LOG.debug("Files to scan: {}", totalFiles); - CodemodInvoker codemodInvoker = - new CodemodInvoker(List.of(SecureRandomCodemod.class), Path.of(repository.toURI())); - try (ProgressBar pb = CLI.createProgressBuilderBase() .setTaskName("Scanning source files") diff --git a/languages/java/src/main/java/io/openpixee/java/VisitorAssembler.java b/languages/java/src/main/java/io/openpixee/java/VisitorAssembler.java index 111d89cc6..8c88feaf0 100644 --- a/languages/java/src/main/java/io/openpixee/java/VisitorAssembler.java +++ b/languages/java/src/main/java/io/openpixee/java/VisitorAssembler.java @@ -1,5 +1,6 @@ package io.openpixee.java; +import io.codemodder.RuleContext; import io.openpixee.java.plugins.codeql.CodeQlPlugin; import io.openpixee.java.plugins.contrast.ContrastScanPlugin; import io.openpixee.java.protections.*; diff --git a/languages/java/src/main/java/io/openpixee/java/plugins/codeql/CodeQlPlugin.java b/languages/java/src/main/java/io/openpixee/java/plugins/codeql/CodeQlPlugin.java index 046c84525..b56200278 100644 --- a/languages/java/src/main/java/io/openpixee/java/plugins/codeql/CodeQlPlugin.java +++ b/languages/java/src/main/java/io/openpixee/java/plugins/codeql/CodeQlPlugin.java @@ -2,9 +2,9 @@ import com.contrastsecurity.sarif.*; import com.google.common.annotations.VisibleForTesting; +import io.codemodder.RuleContext; import io.openpixee.java.DefaultSarifProcessorPlugin; import io.openpixee.java.FileBasedVisitor; -import io.openpixee.java.RuleContext; import io.openpixee.java.VisitorFactory; import java.io.File; import java.util.ArrayList; diff --git a/languages/java/src/main/java/io/openpixee/java/plugins/contrast/ContrastScanPlugin.java b/languages/java/src/main/java/io/openpixee/java/plugins/contrast/ContrastScanPlugin.java index 6de89c8e7..6d9990e88 100644 --- a/languages/java/src/main/java/io/openpixee/java/plugins/contrast/ContrastScanPlugin.java +++ b/languages/java/src/main/java/io/openpixee/java/plugins/contrast/ContrastScanPlugin.java @@ -8,9 +8,9 @@ import com.contrastsecurity.sarif.Tool; import com.contrastsecurity.sarif.ToolComponent; import com.google.common.annotations.VisibleForTesting; +import io.codemodder.RuleContext; import io.openpixee.java.DefaultSarifProcessorPlugin; import io.openpixee.java.FileBasedVisitor; -import io.openpixee.java.RuleContext; import io.openpixee.java.VisitorFactory; import io.openpixee.java.plugins.ReflectionInjectionVisitorFactory; import java.io.File; diff --git a/languages/java/src/test/java/io/openpixee/java/RuleContextTest.java b/languages/java/src/test/java/io/openpixee/java/RuleContextTest.java index bddd6fe04..a191dda78 100644 --- a/languages/java/src/test/java/io/openpixee/java/RuleContextTest.java +++ b/languages/java/src/test/java/io/openpixee/java/RuleContextTest.java @@ -3,6 +3,8 @@ import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.equalTo; +import io.codemodder.DefaultRuleSetting; +import io.codemodder.RuleContext; import java.util.List; import org.junit.jupiter.api.Test; diff --git a/languages/java/src/test/java/io/openpixee/java/VisitorAssemblerTest.java b/languages/java/src/test/java/io/openpixee/java/VisitorAssemblerTest.java index 6b1a103f9..f6a9938b1 100644 --- a/languages/java/src/test/java/io/openpixee/java/VisitorAssemblerTest.java +++ b/languages/java/src/test/java/io/openpixee/java/VisitorAssemblerTest.java @@ -4,6 +4,8 @@ import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.MatcherAssert.assertThat; +import io.codemodder.DefaultRuleSetting; +import io.codemodder.RuleContext; import java.io.File; import java.util.Collections; import java.util.List; diff --git a/languages/java/src/test/java/io/openpixee/java/plugins/codeql/CodeQlPluginTest.java b/languages/java/src/test/java/io/openpixee/java/plugins/codeql/CodeQlPluginTest.java index f75f34fbc..ea819b972 100644 --- a/languages/java/src/test/java/io/openpixee/java/plugins/codeql/CodeQlPluginTest.java +++ b/languages/java/src/test/java/io/openpixee/java/plugins/codeql/CodeQlPluginTest.java @@ -9,8 +9,8 @@ import com.contrastsecurity.sarif.Run; import com.contrastsecurity.sarif.SarifSchema210; import com.fasterxml.jackson.databind.ObjectMapper; -import io.openpixee.java.DefaultRuleSetting; -import io.openpixee.java.RuleContext; +import io.codemodder.DefaultRuleSetting; +import io.codemodder.RuleContext; import io.openpixee.java.VisitorFactory; import java.io.File; import java.io.FileReader; diff --git a/languages/java/src/test/java/io/openpixee/java/plugins/contrast/ContrastScanPluginTest.java b/languages/java/src/test/java/io/openpixee/java/plugins/contrast/ContrastScanPluginTest.java index 84acdd5d1..dd6ebb3ba 100644 --- a/languages/java/src/test/java/io/openpixee/java/plugins/contrast/ContrastScanPluginTest.java +++ b/languages/java/src/test/java/io/openpixee/java/plugins/contrast/ContrastScanPluginTest.java @@ -9,8 +9,8 @@ import com.contrastsecurity.sarif.Run; import com.contrastsecurity.sarif.SarifSchema210; import com.fasterxml.jackson.databind.ObjectMapper; -import io.openpixee.java.DefaultRuleSetting; -import io.openpixee.java.RuleContext; +import io.codemodder.DefaultRuleSetting; +import io.codemodder.RuleContext; import io.openpixee.java.VisitorFactory; import java.io.File; import java.io.FileReader; diff --git a/languages/java/src/test/java/io/openpixee/java/protections/WeavingTests.java b/languages/java/src/test/java/io/openpixee/java/protections/WeavingTests.java index 15379ac2a..1a83242f5 100644 --- a/languages/java/src/test/java/io/openpixee/java/protections/WeavingTests.java +++ b/languages/java/src/test/java/io/openpixee/java/protections/WeavingTests.java @@ -3,8 +3,10 @@ import static org.assertj.core.api.Assertions.assertThat; import io.codemodder.ChangedFile; +import io.codemodder.CodemodInvoker; import io.codemodder.FileWeavingContext; import io.codemodder.IncludesExcludes; +import io.codemodder.codemods.SecureRandomCodemod; import io.openpixee.java.FileBasedVisitor; import io.openpixee.java.SourceDirectory; import io.openpixee.java.SourceWeaver; @@ -46,14 +48,17 @@ public static ChangedFile assertJavaWeaveWorkedAndWontReweave( assertThat(fixedFile).isFile(); List visitorFactories = List.of(factory); + var analyzer = SourceWeaver.createDefault(); var testCodeDir = new File(vulnerableFile.getParent()); + var codemodInvoker = + new CodemodInvoker(List.of(SecureRandomCodemod.class), testCodeDir.toPath()); var directory = SourceDirectory.createDefault(testCodeDir.getPath(), List.of(pathToVulnerableFile)); var changedFile = scanAndAssertNoErrorsWithOneFileChanged( - analyzer, directory, visitorFactories, includesExcludes); + analyzer, directory, visitorFactories, codemodInvoker, includesExcludes); var expectedContents = FileUtils.readFileToString(fixedFile, Charset.defaultCharset()); var typeName = vulnerableFile.getName().replace(".java", ""); @@ -68,7 +73,7 @@ public static ChangedFile assertJavaWeaveWorkedAndWontReweave( directory = SourceDirectory.createDefault(testCodeDir.getPath(), List.of(pathToFixedFile)); scanAndAssertNoErrorsWithNoFilesChanged( - analyzer, directory, visitorFactories, includesExcludes); + analyzer, directory, visitorFactories, codemodInvoker, includesExcludes); return changedFile; } @@ -88,6 +93,7 @@ private static ChangedFile scanAndAssertNoErrorsWithOneFileChanged( final SourceWeaver analyzer, final SourceDirectory directory, final List visitorFactories, + final CodemodInvoker codemodInvoker, final IncludesExcludes includesExcludes) throws IOException { var weave = @@ -96,6 +102,7 @@ private static ChangedFile scanAndAssertNoErrorsWithOneFileChanged( List.of(directory), directory.files(), visitorFactories, + codemodInvoker, includesExcludes); assertThat(weave).isNotNull(); assertThat(weave.unscannableFiles()).isEmpty(); @@ -115,16 +122,20 @@ public static void scanAndAssertNoErrorsWithNoFilesChanged( List visitorFactories = List.of(factory); var analyzer = SourceWeaver.createDefault(); var testCodeDir = new File(vulnerableFile.getParent()); + var codemodInvoker = + new CodemodInvoker(List.of(SecureRandomCodemod.class), testCodeDir.toPath()); var directory = SourceDirectory.createDefault(testCodeDir.getPath(), List.of(pathToVulnerableFile)); + scanAndAssertNoErrorsWithNoFilesChanged( - analyzer, directory, visitorFactories, includesExcludes); + analyzer, directory, visitorFactories, codemodInvoker, includesExcludes); } private static void scanAndAssertNoErrorsWithNoFilesChanged( final SourceWeaver analyzer, final SourceDirectory directory, final List visitorFactories, + final CodemodInvoker codemodInvoker, final IncludesExcludes includesExcludes) throws IOException { var weave = @@ -133,6 +144,7 @@ private static void scanAndAssertNoErrorsWithNoFilesChanged( List.of(directory), directory.files(), visitorFactories, + codemodInvoker, includesExcludes); assertThat(weave).isNotNull(); From aed1dbcfb08fa159252f23f9811f728ad2363c91 Mon Sep 17 00:00:00 2001 From: Arshan Dabirsiaghi Date: Sun, 12 Mar 2023 15:12:47 -0400 Subject: [PATCH 10/19] run on semgrep container --- .github/workflows/checks.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml index b3cca9572..bfa86b4fb 100644 --- a/.github/workflows/checks.yml +++ b/.github/workflows/checks.yml @@ -7,6 +7,8 @@ jobs: check: name: Check runs-on: ubuntu-latest + container: + image: returntocorp/semgrep env: ORG_GRADLE_PROJECT_pixeeArtifactoryUsername: ${{ secrets.PIXEE_ARTIFACTORY_USERNAME }} ORG_GRADLE_PROJECT_pixeeArtifactoryPassword: ${{ secrets.PIXEE_ARTIFACTORY_PASSWORD }} From 26c4830c55b37c5d687ec592f292fe3829f5662a Mon Sep 17 00:00:00 2001 From: Arshan Dabirsiaghi Date: Mon, 13 Mar 2023 08:07:08 -0400 Subject: [PATCH 11/19] without graal --- .github/workflows/checks.yml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml index bfa86b4fb..8234bb802 100644 --- a/.github/workflows/checks.yml +++ b/.github/workflows/checks.yml @@ -22,12 +22,12 @@ jobs: with: distribution: 'temurin' java-version: '11' - - uses: graalvm/setup-graalvm@v1 - with: - version: 'latest' - java-version: '17' - components: 'js,native-image' - github-token: ${{ secrets.github_token }} +# - uses: graalvm/setup-graalvm@v1 +# with: +# version: 'latest' +# java-version: '17' +# components: 'js,native-image' +# github-token: ${{ secrets.github_token }} - uses: gradle/gradle-build-action@v2 with: arguments: check From bbb1edce21267b4345dfe8dec3033bafd30b4ab5 Mon Sep 17 00:00:00 2001 From: Arshan Dabirsiaghi Date: Mon, 13 Mar 2023 08:32:27 -0400 Subject: [PATCH 12/19] try different way of adding semgrep --- .github/workflows/checks.yml | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml index 8234bb802..a13712e84 100644 --- a/.github/workflows/checks.yml +++ b/.github/workflows/checks.yml @@ -7,8 +7,6 @@ jobs: check: name: Check runs-on: ubuntu-latest - container: - image: returntocorp/semgrep env: ORG_GRADLE_PROJECT_pixeeArtifactoryUsername: ${{ secrets.PIXEE_ARTIFACTORY_USERNAME }} ORG_GRADLE_PROJECT_pixeeArtifactoryPassword: ${{ secrets.PIXEE_ARTIFACTORY_PASSWORD }} @@ -17,17 +15,22 @@ jobs: with: submodules: 'true' token: ${{ secrets.TEMP_GITHUB_PAT }} + - uses: actions/setup-python@v4 + with: + python-version: '3.10' + - name: Install Semgrep + run: python3 -m pip install semgrep - uses: actions/setup-node@v3 - uses: actions/setup-java@v3 with: distribution: 'temurin' java-version: '11' -# - uses: graalvm/setup-graalvm@v1 -# with: -# version: 'latest' -# java-version: '17' -# components: 'js,native-image' -# github-token: ${{ secrets.github_token }} + - uses: graalvm/setup-graalvm@v1 + with: + version: 'latest' + java-version: '17' + components: 'js,native-image' + github-token: ${{ secrets.github_token }} - uses: gradle/gradle-build-action@v2 with: arguments: check From 4dc300b1e31e9ccd4c59c0c31475b27617613b22 Mon Sep 17 00:00:00 2001 From: Arshan Dabirsiaghi Date: Mon, 13 Mar 2023 08:50:45 -0400 Subject: [PATCH 13/19] add semgrep to release job --- .github/workflows/release.yml | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index ed404fa10..a65448d19 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -13,8 +13,6 @@ jobs: contents: write environment: Public Release runs-on: "ubuntu-latest" - container: - image: returntocorp/semgrep env: ORG_GRADLE_PROJECT_pixeeArtifactoryUsername: ${{ secrets.PIXEE_ARTIFACTORY_USERNAME }} ORG_GRADLE_PROJECT_pixeeArtifactoryPassword: ${{ secrets.PIXEE_ARTIFACTORY_PASSWORD }} @@ -24,6 +22,11 @@ jobs: with: submodules: 'true' token: ${{ secrets.TEMP_GITHUB_PAT }} + - uses: actions/setup-python@v4 + with: + python-version: '3.10' + - name: Install Semgrep + run: python3 -m pip install semgrep - uses: actions/setup-node@v3 - uses: actions/setup-java@v3 with: From fb856d57b52a8b0f1ca904f4496e86feedd19e93 Mon Sep 17 00:00:00 2001 From: Arshan Dabirsiaghi Date: Mon, 13 Mar 2023 21:08:08 -0400 Subject: [PATCH 14/19] pr feedback addressed --- languages/codemodder-common/build.gradle.kts | 1 - .../build.gradle.kts | 1 - .../codemods/SecureRandomCodemod.java | 52 ++------ ...cure-random.semgrep => secure-random.yaml} | 0 .../build.gradle.kts | 1 - .../src/main/java/io/codemodder/Changer.java | 6 +- .../java/io/codemodder/ChangerHandler.java | 5 - .../src/main/java/io/codemodder/Codemod.java | 2 +- .../codemodder/CodemodInvocationContext.java | 22 ++++ .../java/io/codemodder/CodemodInvoker.java | 24 +++- .../java/io/codemodder/CodemodProvider.java | 4 +- .../DefaultCodemodInvocationContext.java | 48 +++++++ .../java/io/codemodder/JavaParserChanger.java | 4 +- .../io/codemodder/JavaParserProvider.java | 13 -- .../java/io/codemodder/ProjectProvider.java | 17 --- .../main/java/io/codemodder/RuleSarif.java | 25 ++++ .../src/main/java/io/codemodder/Sarif.java | 56 -------- .../io/codemodder/CodemodInvokerTest.java | 27 +--- .../build.gradle.kts | 3 +- .../sarif/semgrep/DefaultSemgrepRunner.java | 28 ++-- .../semgrep/DefaultSemgrepSarifProvider.java | 40 ------ .../semgrep/SemgrepJavaParserChanger.java | 43 +++++++ .../sarif/semgrep/SemgrepModule.java | 121 ++++++++++++++++++ .../sarif/semgrep/SemgrepProvider.java | 8 +- .../sarif/semgrep/SemgrepRunner.java | 7 +- .../providers/sarif/semgrep/SemgrepSarif.java | 66 ++++++++++ .../sarif/semgrep/SemgrepSarifModule.java | 11 -- .../providers/sarif/semgrep/SemgrepScan.java | 16 +++ ...oviderTest.java => SemgrepRunnerTest.java} | 9 +- languages/java/build.gradle.kts | 1 + .../io/openpixee/java/JavaFixitCliRun.java | 4 +- 31 files changed, 421 insertions(+), 244 deletions(-) rename languages/codemodder-default-codemods/src/main/resources/{secure-random.semgrep => secure-random.yaml} (100%) delete mode 100644 languages/codemodder-framework-java/src/main/java/io/codemodder/ChangerHandler.java create mode 100644 languages/codemodder-framework-java/src/main/java/io/codemodder/CodemodInvocationContext.java create mode 100644 languages/codemodder-framework-java/src/main/java/io/codemodder/DefaultCodemodInvocationContext.java delete mode 100644 languages/codemodder-framework-java/src/main/java/io/codemodder/JavaParserProvider.java delete mode 100644 languages/codemodder-framework-java/src/main/java/io/codemodder/ProjectProvider.java create mode 100644 languages/codemodder-framework-java/src/main/java/io/codemodder/RuleSarif.java delete mode 100644 languages/codemodder-framework-java/src/main/java/io/codemodder/Sarif.java delete mode 100644 languages/codemodder-semgrep-provider/src/main/java/io/codemodder/providers/sarif/semgrep/DefaultSemgrepSarifProvider.java create mode 100644 languages/codemodder-semgrep-provider/src/main/java/io/codemodder/providers/sarif/semgrep/SemgrepJavaParserChanger.java create mode 100644 languages/codemodder-semgrep-provider/src/main/java/io/codemodder/providers/sarif/semgrep/SemgrepModule.java create mode 100644 languages/codemodder-semgrep-provider/src/main/java/io/codemodder/providers/sarif/semgrep/SemgrepSarif.java delete mode 100644 languages/codemodder-semgrep-provider/src/main/java/io/codemodder/providers/sarif/semgrep/SemgrepSarifModule.java create mode 100644 languages/codemodder-semgrep-provider/src/main/java/io/codemodder/providers/sarif/semgrep/SemgrepScan.java rename languages/codemodder-semgrep-provider/src/test/java/io/codemodder/providers/sarif/semgrep/{SemgrepSarifProviderTest.java => SemgrepRunnerTest.java} (81%) diff --git a/languages/codemodder-common/build.gradle.kts b/languages/codemodder-common/build.gradle.kts index 89b10cc85..5f325990c 100644 --- a/languages/codemodder-common/build.gradle.kts +++ b/languages/codemodder-common/build.gradle.kts @@ -15,7 +15,6 @@ java { spotless { java { - // do we need anything here? } } diff --git a/languages/codemodder-default-codemods/build.gradle.kts b/languages/codemodder-default-codemods/build.gradle.kts index 9543d8139..ce78164c0 100644 --- a/languages/codemodder-default-codemods/build.gradle.kts +++ b/languages/codemodder-default-codemods/build.gradle.kts @@ -19,7 +19,6 @@ application { spotless { java { - // do we need anything here? } } diff --git a/languages/codemodder-default-codemods/src/main/java/io/codemodder/codemods/SecureRandomCodemod.java b/languages/codemodder-default-codemods/src/main/java/io/codemodder/codemods/SecureRandomCodemod.java index d6cc667b6..597039f29 100644 --- a/languages/codemodder-default-codemods/src/main/java/io/codemodder/codemods/SecureRandomCodemod.java +++ b/languages/codemodder-default-codemods/src/main/java/io/codemodder/codemods/SecureRandomCodemod.java @@ -1,60 +1,36 @@ package io.codemodder.codemods; -import com.contrastsecurity.sarif.Result; -import com.contrastsecurity.sarif.SarifSchema210; -import com.github.javaparser.ast.CompilationUnit; +import com.contrastsecurity.sarif.Region; import com.github.javaparser.ast.visitor.ModifierVisitor; import io.codemodder.ChangeConstructorTypeVisitor; -import io.codemodder.CodeDirectory; import io.codemodder.Codemod; +import io.codemodder.CodemodInvocationContext; import io.codemodder.FileWeavingContext; -import io.codemodder.JavaParserChanger; import io.codemodder.ReviewGuidance; -import io.codemodder.Sarif; -import io.codemodder.providers.sarif.semgrep.SemgrepSarifProvider; -import java.io.IOException; -import java.nio.file.Path; +import io.codemodder.RuleSarif; +import io.codemodder.providers.sarif.semgrep.SemgrepJavaParserChanger; +import io.codemodder.providers.sarif.semgrep.SemgrepScan; import java.util.List; -import java.util.Optional; import javax.inject.Inject; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; /** Turns {@link java.util.Random} into {@link java.security.SecureRandom}. */ @Codemod( - value = "pixee:java/secure-random", + id = "pixee:java/secure-random", author = "arshan@pixee.ai", reviewGuidance = ReviewGuidance.MERGE_WITHOUT_REVIEW) -public final class SecureRandomCodemod implements JavaParserChanger { - - private final SarifSchema210 sarif; +public final class SecureRandomCodemod extends SemgrepJavaParserChanger { @Inject public SecureRandomCodemod( - final CodeDirectory codeDirectory, final SemgrepSarifProvider sarifProvider) - throws IOException { - this.sarif = sarifProvider.getSarif(codeDirectory.asPath(), "secure-random.semgrep"); + @SemgrepScan(pathToYaml = "/secure-random.yaml", ruleId = "secure-random") + final RuleSarif sarif) { + super(sarif); } @Override - public Optional> createModifierVisitor( - final CodeDirectory codeDirectory, final Path path, final CompilationUnit cu) { - List results = Sarif.getResultsForCompilationUnit(sarif, path); - logger.trace("Found {} results in {} to change", results.size(), path); - if (!results.isEmpty()) { - return Optional.of( - new ChangeConstructorTypeVisitor( - Sarif.findRegions(results), - "java.security.SecureRandom", - "pixee:java/secure-random")); - } - return Optional.empty(); + public ModifierVisitor createVisitor( + final CodemodInvocationContext context, final List regions) { + return new ChangeConstructorTypeVisitor( + regions, "java.security.SecureRandom", context.codemodId()); } - - @Override - public String getCodemodId() { - return "pixee:java/secure-random"; - } - - private static final Logger logger = LoggerFactory.getLogger(SecureRandomCodemod.class); } diff --git a/languages/codemodder-default-codemods/src/main/resources/secure-random.semgrep b/languages/codemodder-default-codemods/src/main/resources/secure-random.yaml similarity index 100% rename from languages/codemodder-default-codemods/src/main/resources/secure-random.semgrep rename to languages/codemodder-default-codemods/src/main/resources/secure-random.yaml diff --git a/languages/codemodder-framework-java/build.gradle.kts b/languages/codemodder-framework-java/build.gradle.kts index d06382e50..847f99fcb 100644 --- a/languages/codemodder-framework-java/build.gradle.kts +++ b/languages/codemodder-framework-java/build.gradle.kts @@ -15,7 +15,6 @@ java { spotless { java { - // do we need anything here? } } diff --git a/languages/codemodder-framework-java/src/main/java/io/codemodder/Changer.java b/languages/codemodder-framework-java/src/main/java/io/codemodder/Changer.java index e97cee7f1..16433f378 100644 --- a/languages/codemodder-framework-java/src/main/java/io/codemodder/Changer.java +++ b/languages/codemodder-framework-java/src/main/java/io/codemodder/Changer.java @@ -1,8 +1,4 @@ package io.codemodder; /** An empty interface that marks that a codemod type has provided some utility to change. */ -public interface Changer { - - /** Get the ID. */ - String getCodemodId(); -} +public interface Changer {} diff --git a/languages/codemodder-framework-java/src/main/java/io/codemodder/ChangerHandler.java b/languages/codemodder-framework-java/src/main/java/io/codemodder/ChangerHandler.java deleted file mode 100644 index 096fc798f..000000000 --- a/languages/codemodder-framework-java/src/main/java/io/codemodder/ChangerHandler.java +++ /dev/null @@ -1,5 +0,0 @@ -package io.codemodder; - -public interface ChangerHandler { - void run(); -} diff --git a/languages/codemodder-framework-java/src/main/java/io/codemodder/Codemod.java b/languages/codemodder-framework-java/src/main/java/io/codemodder/Codemod.java index ce9f2ade3..bacde72ef 100644 --- a/languages/codemodder-framework-java/src/main/java/io/codemodder/Codemod.java +++ b/languages/codemodder-framework-java/src/main/java/io/codemodder/Codemod.java @@ -22,7 +22,7 @@ *
  • codeql:java/xss * */ - String value(); + String id(); /** * The codemod author, hopefully "email", "First Last (email)", or "Team Name (email)". diff --git a/languages/codemodder-framework-java/src/main/java/io/codemodder/CodemodInvocationContext.java b/languages/codemodder-framework-java/src/main/java/io/codemodder/CodemodInvocationContext.java new file mode 100644 index 000000000..17afa24f3 --- /dev/null +++ b/languages/codemodder-framework-java/src/main/java/io/codemodder/CodemodInvocationContext.java @@ -0,0 +1,22 @@ +package io.codemodder; + +import java.nio.file.Path; + +/** The context we provide to each codemod. */ +public interface CodemodInvocationContext { + + /** + * A "flight recorder" that you used to record the changes you make, which will eventually be used + * to build a report. + */ + FileWeavingContext changeRecorder(); + + /** The root directory where the project being transformed lives. */ + CodeDirectory codeDirectory(); + + /** The individual file being changed. */ + Path path(); + + /** The ID of the codemod changing the file. */ + String codemodId(); +} diff --git a/languages/codemodder-framework-java/src/main/java/io/codemodder/CodemodInvoker.java b/languages/codemodder-framework-java/src/main/java/io/codemodder/CodemodInvoker.java index 900d2891f..85f2967fd 100644 --- a/languages/codemodder-framework-java/src/main/java/io/codemodder/CodemodInvoker.java +++ b/languages/codemodder-framework-java/src/main/java/io/codemodder/CodemodInvoker.java @@ -9,8 +9,10 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; +import java.util.HashMap; import java.util.HashSet; import java.util.List; +import java.util.Map; import java.util.Objects; import java.util.Optional; import java.util.ServiceLoader; @@ -31,6 +33,7 @@ public final class CodemodInvoker { private final List codemods; private final Path repositoryDir; + private final Map changerToCodemodIds; public CodemodInvoker( final List> codemodTypes, final Path repositoryDir) { @@ -42,6 +45,7 @@ public CodemodInvoker( final RuleContext ruleContext, final Path repositoryDir) { // get all the providers ready for dependency injection & codemod instantiation + List providers = ServiceLoader.load(CodemodProvider.class).stream() .map(ServiceLoader.Provider::get) @@ -51,11 +55,15 @@ public CodemodInvoker( // add default module allModules.add(new CodeDirectoryModule(repositoryDir)); + // add all provider modules for (CodemodProvider provider : providers) { - Set modules = provider.getModules(); + Set modules = provider.getModules(repositoryDir, codemodTypes); allModules.addAll(modules); } + // record which changers are associated with which codemod ids + Map changerToCodemodIds = new HashMap<>(); + // validate and instantiate the codemods Injector injector = Guice.createInjector(allModules); List codemods = new ArrayList<>(); @@ -63,10 +71,14 @@ public CodemodInvoker( Codemod codemodAnnotation = type.getAnnotation(Codemod.class); validateRequiredFields(codemodAnnotation); Changer changer = injector.getInstance(type); - if (ruleContext.isRuleAllowed(changer.getCodemodId())) { + String codemodId = codemodAnnotation.id(); + if (ruleContext.isRuleAllowed(codemodId)) { codemods.add(changer); + changerToCodemodIds.put(changer, codemodId); } } + + this.changerToCodemodIds = Collections.unmodifiableMap(changerToCodemodIds); this.codemods = Collections.unmodifiableList(codemods); this.repositoryDir = Objects.requireNonNull(repositoryDir); } @@ -83,7 +95,11 @@ public void execute(final Path path, final CompilationUnit cu, final FileWeaving JavaParserChanger javaParserChanger = (JavaParserChanger) changer; Optional> modifierVisitor = javaParserChanger.createModifierVisitor( - new DefaultCodeDirectory(repositoryDir), path, cu); + new DefaultCodemodInvocationContext( + new DefaultCodeDirectory(repositoryDir), + path, + changerToCodemodIds.get(changer), + context)); modifierVisitor.ifPresent( changeContextModifierVisitor -> cu.accept(changeContextModifierVisitor, context)); } else { @@ -108,7 +124,7 @@ private static void validateRequiredFields(final Codemod codemodAnnotation) { throw new IllegalArgumentException("must have an author"); } - String id = codemodAnnotation.value(); + String id = codemodAnnotation.id(); if (!isValidCodemodId(id)) { throw new IllegalArgumentException("must have valid codemod id"); } diff --git a/languages/codemodder-framework-java/src/main/java/io/codemodder/CodemodProvider.java b/languages/codemodder-framework-java/src/main/java/io/codemodder/CodemodProvider.java index 75584b019..1e6d94e22 100644 --- a/languages/codemodder-framework-java/src/main/java/io/codemodder/CodemodProvider.java +++ b/languages/codemodder-framework-java/src/main/java/io/codemodder/CodemodProvider.java @@ -1,6 +1,8 @@ package io.codemodder; import com.google.inject.AbstractModule; +import java.nio.file.Path; +import java.util.List; import java.util.Set; /** @@ -14,5 +16,5 @@ public interface CodemodProvider { * * @return a set of modules that perform dependency injection */ - Set getModules(); + Set getModules(Path repository, List> codemodTypes); } diff --git a/languages/codemodder-framework-java/src/main/java/io/codemodder/DefaultCodemodInvocationContext.java b/languages/codemodder-framework-java/src/main/java/io/codemodder/DefaultCodemodInvocationContext.java new file mode 100644 index 000000000..dbc2caee9 --- /dev/null +++ b/languages/codemodder-framework-java/src/main/java/io/codemodder/DefaultCodemodInvocationContext.java @@ -0,0 +1,48 @@ +package io.codemodder; + +import java.nio.file.Path; +import java.util.Objects; + +/** {@inheritDoc} */ +final class DefaultCodemodInvocationContext implements CodemodInvocationContext { + + private final CodeDirectory codeDirectory; + private final Path path; + private final String codemodId; + private final FileWeavingContext changeRecorder; + + DefaultCodemodInvocationContext( + final CodeDirectory codeDirectory, + final Path path, + final String codemodId, + final FileWeavingContext changeRecorder) { + this.codeDirectory = Objects.requireNonNull(codeDirectory); + this.path = Objects.requireNonNull(path); + this.codemodId = Objects.requireNonNull(codemodId); + this.changeRecorder = Objects.requireNonNull(changeRecorder); + } + + /** {@inheritDoc} */ + @Override + public FileWeavingContext changeRecorder() { + return changeRecorder; + } + + /** {@inheritDoc} */ + @Override + public CodeDirectory codeDirectory() { + return codeDirectory; + } + + /** {@inheritDoc} */ + @Override + public Path path() { + return path; + } + + /** {@inheritDoc} */ + @Override + public String codemodId() { + return codemodId; + } +} diff --git a/languages/codemodder-framework-java/src/main/java/io/codemodder/JavaParserChanger.java b/languages/codemodder-framework-java/src/main/java/io/codemodder/JavaParserChanger.java index a9a98054b..2a72547cd 100644 --- a/languages/codemodder-framework-java/src/main/java/io/codemodder/JavaParserChanger.java +++ b/languages/codemodder-framework-java/src/main/java/io/codemodder/JavaParserChanger.java @@ -1,8 +1,6 @@ package io.codemodder; -import com.github.javaparser.ast.CompilationUnit; import com.github.javaparser.ast.visitor.ModifierVisitor; -import java.nio.file.Path; import java.util.Optional; /** {@inheritDoc} Uses JavaParser to change Java source files. */ @@ -13,5 +11,5 @@ public interface JavaParserChanger extends Changer { * determine if and how source file should be changed. */ Optional> createModifierVisitor( - CodeDirectory codeDirectory, Path path, CompilationUnit cu); + CodemodInvocationContext context); } diff --git a/languages/codemodder-framework-java/src/main/java/io/codemodder/JavaParserProvider.java b/languages/codemodder-framework-java/src/main/java/io/codemodder/JavaParserProvider.java deleted file mode 100644 index 8fc8443ec..000000000 --- a/languages/codemodder-framework-java/src/main/java/io/codemodder/JavaParserProvider.java +++ /dev/null @@ -1,13 +0,0 @@ -package io.codemodder; - -import com.google.inject.AbstractModule; -import java.util.Set; - -/** Turns Java files into. */ -public class JavaParserProvider implements CodemodProvider { - - @Override - public Set getModules() { - return Set.of(); - } -} diff --git a/languages/codemodder-framework-java/src/main/java/io/codemodder/ProjectProvider.java b/languages/codemodder-framework-java/src/main/java/io/codemodder/ProjectProvider.java deleted file mode 100644 index 0e18fa9cc..000000000 --- a/languages/codemodder-framework-java/src/main/java/io/codemodder/ProjectProvider.java +++ /dev/null @@ -1,17 +0,0 @@ -package io.codemodder; - -import java.util.Optional; - -/** A provider that offers project information, e.g., the compiler's Java source code level. */ -public interface ProjectProvider { - - /** - * @return the Java source version the project build file appears to target. - */ - Optional javaSourceTargetVersion(); - - /** - * @return the Java bytecode version the project build file appears to target. - */ - Optional javaBytecodeTargetVersion(); -} diff --git a/languages/codemodder-framework-java/src/main/java/io/codemodder/RuleSarif.java b/languages/codemodder-framework-java/src/main/java/io/codemodder/RuleSarif.java new file mode 100644 index 000000000..f67f89da7 --- /dev/null +++ b/languages/codemodder-framework-java/src/main/java/io/codemodder/RuleSarif.java @@ -0,0 +1,25 @@ +package io.codemodder; + +import com.contrastsecurity.sarif.Region; +import com.contrastsecurity.sarif.SarifSchema210; +import java.nio.file.Path; +import java.util.List; + +/** Defines a model for interacting with SARIF. */ +public interface RuleSarif { + + /** + * Get all the regions for the SARIF with the matching rule ID + * + * @param path the file being scanned + * @param ruleId the semgrep rule ID + * @return the source code regions where the given rule was found in the given file + */ + List getRegionsFromResultsByRule(Path path, String ruleId); + + /** Return the entire SARIF as a model in case more comprehensive inspection is needed. */ + SarifSchema210 rawDocument(); + + /** Returns the string ID for the rule. */ + String getRule(); +} diff --git a/languages/codemodder-framework-java/src/main/java/io/codemodder/Sarif.java b/languages/codemodder-framework-java/src/main/java/io/codemodder/Sarif.java deleted file mode 100644 index 887847b56..000000000 --- a/languages/codemodder-framework-java/src/main/java/io/codemodder/Sarif.java +++ /dev/null @@ -1,56 +0,0 @@ -package io.codemodder; - -import com.contrastsecurity.sarif.Region; -import com.contrastsecurity.sarif.Result; -import com.contrastsecurity.sarif.SarifSchema210; -import com.github.javaparser.ast.CompilationUnit; -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.List; -import java.util.stream.Collectors; - -/** - * A wrapper around {@link com.contrastsecurity.sarif.SarifSchema210} that also provides convenience - * methods that make writing codemods easier. - */ -public final class Sarif { - - private Sarif() {} - - /** - * Get all the {@link Result} that are from the given {@link CompilationUnit}. - * - * @param sarif the SARIF - * @param filePath the file that's being scanned - * @return a {@link List} containing the SARIF results for this file - */ - public static List getResultsForCompilationUnit( - final SarifSchema210 sarif, final Path filePath) { - List results = sarif.getRuns().get(0).getResults(); - return results.stream() - .filter( - result -> { - String uri = - result.getLocations().get(0).getPhysicalLocation().getArtifactLocation().getUri(); - try { - return Files.isSameFile(filePath, Path.of(uri)); - } catch (IOException e) { // this should never happen - return false; - } - }) - .collect(Collectors.toUnmodifiableList()); - } - - /** - * Get all of the {@link Region} entries for the given {@link Result} list. - * - * @param results the results to map to source code locations - * @return a list of source code locations - */ - public static List findRegions(final List results) { - return results.stream() - .map(result -> result.getLocations().get(0).getPhysicalLocation().getRegion()) - .collect(Collectors.toUnmodifiableList()); - } -} diff --git a/languages/codemodder-framework-java/src/test/java/io/codemodder/CodemodInvokerTest.java b/languages/codemodder-framework-java/src/test/java/io/codemodder/CodemodInvokerTest.java index cceb50d29..4e9f6f7d8 100644 --- a/languages/codemodder-framework-java/src/test/java/io/codemodder/CodemodInvokerTest.java +++ b/languages/codemodder-framework-java/src/test/java/io/codemodder/CodemodInvokerTest.java @@ -9,37 +9,22 @@ final class CodemodInvokerTest { @Codemod( - value = "test_mod", + id = "test_mod", author = "valid@valid.com", reviewGuidance = ReviewGuidance.MERGE_AFTER_CURSORY_REVIEW) - static class InvalidCodemodName implements Changer { - @Override - public String getCodemodId() { - return "test:java/invalidname"; - } - } + static class InvalidCodemodName implements Changer {} @Codemod( - value = "test_mod", + id = "test_mod", author = " ", reviewGuidance = ReviewGuidance.MERGE_AFTER_CURSORY_REVIEW) - class EmptyCodemodAuthor implements Changer { - @Override - public String getCodemodId() { - return "test:java/emptyauthor"; - } - } + class EmptyCodemodAuthor implements Changer {} @Codemod( - value = "pixee:java/id", + id = "pixee:java/id", author = "valid@valid.com", reviewGuidance = ReviewGuidance.MERGE_AFTER_CURSORY_REVIEW) - final class ValidCodemod implements Changer { - @Override - public String getCodemodId() { - return "test:java/id"; - } - } + final class ValidCodemod implements Changer {} @Test void it_validates_codemod_ids() { diff --git a/languages/codemodder-semgrep-provider/build.gradle.kts b/languages/codemodder-semgrep-provider/build.gradle.kts index 339d5bd9b..82cc70c37 100644 --- a/languages/codemodder-semgrep-provider/build.gradle.kts +++ b/languages/codemodder-semgrep-provider/build.gradle.kts @@ -15,7 +15,6 @@ java { spotless { java { - // do we need anything here? } } @@ -34,6 +33,8 @@ dependencies { implementation(libs.contrast.sarif) implementation(libs.java.security.toolkit) implementation(libs.slf4j.api) + implementation(libs.javaparser.core) + implementation(project(":languages:codemodder-common")) implementation(project(":languages:codemodder-framework-java")) testImplementation(testlibs.bundles.junit.jupiter) diff --git a/languages/codemodder-semgrep-provider/src/main/java/io/codemodder/providers/sarif/semgrep/DefaultSemgrepRunner.java b/languages/codemodder-semgrep-provider/src/main/java/io/codemodder/providers/sarif/semgrep/DefaultSemgrepRunner.java index 79a3b79a6..c99b38ec3 100644 --- a/languages/codemodder-semgrep-provider/src/main/java/io/codemodder/providers/sarif/semgrep/DefaultSemgrepRunner.java +++ b/languages/codemodder-semgrep-provider/src/main/java/io/codemodder/providers/sarif/semgrep/DefaultSemgrepRunner.java @@ -9,6 +9,8 @@ import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.StandardCopyOption; +import java.util.ArrayList; +import java.util.List; import java.util.Optional; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -18,26 +20,24 @@ final class DefaultSemgrepRunner implements SemgrepRunner { /** {@inheritDoc} */ @Override - public SarifSchema210 runWithSingleRule(final Path rule, final Path repository) - throws IOException { - String ruleDirectoryPath = rule.toString(); + public SarifSchema210 run(final List ruleYamls, final Path repository) throws IOException { String repositoryPath = repository.toString(); File sarifFile = File.createTempFile("semgrep", ".sarif"); sarifFile.deleteOnExit(); - String[] args = - new String[] { - "semgrep", - "--sarif", - "-o", - sarifFile.getAbsolutePath(), - "--config", - ruleDirectoryPath, - repositoryPath - }; + List args = new ArrayList<>(); + args.add("semgrep"); + args.add("--sarif"); + args.add("-o"); + args.add(sarifFile.getAbsolutePath()); + for (Path ruleYamlPath : ruleYamls) { + args.add("--config"); + args.add(ruleYamlPath.toString()); + } + args.add(repositoryPath); // backup existing .segmrepignore if it exists - File existingSemgrepFile = new File(".semgrepignore"); + File existingSemgrepFile = new File(repository.toFile(), ".semgrepignore"); Optional backup = Optional.empty(); if (existingSemgrepFile.exists()) { diff --git a/languages/codemodder-semgrep-provider/src/main/java/io/codemodder/providers/sarif/semgrep/DefaultSemgrepSarifProvider.java b/languages/codemodder-semgrep-provider/src/main/java/io/codemodder/providers/sarif/semgrep/DefaultSemgrepSarifProvider.java deleted file mode 100644 index 246881931..000000000 --- a/languages/codemodder-semgrep-provider/src/main/java/io/codemodder/providers/sarif/semgrep/DefaultSemgrepSarifProvider.java +++ /dev/null @@ -1,40 +0,0 @@ -package io.codemodder.providers.sarif.semgrep; - -import com.contrastsecurity.sarif.SarifSchema210; -import java.io.IOException; -import java.io.InputStream; -import java.nio.charset.StandardCharsets; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.HashMap; -import java.util.Map; -import org.apache.commons.io.IOUtils; - -/** This is the memoizing Semgrep runner that we'll give to codemods. */ -final class DefaultSemgrepSarifProvider implements SemgrepSarifProvider { - - private final Map sarifs; - - DefaultSemgrepSarifProvider() { - this.sarifs = new HashMap<>(); - } - - @Override - public SarifSchema210 getSarif(final Path repository, final String rulePath) throws IOException { - if (sarifs.containsKey(rulePath)) { - return sarifs.get(rulePath); - } - - InputStream ruleInputStream = getClass().getClassLoader().getResource(rulePath).openStream(); - String ruleYaml = IOUtils.toString(ruleInputStream, StandardCharsets.UTF_8); - ruleInputStream.close(); - - Path semgrepRuleFile = Files.createTempFile("semgrep", ".yaml"); - Files.write(semgrepRuleFile, ruleYaml.getBytes(StandardCharsets.UTF_8)); - - SarifSchema210 sarif = - new DefaultSemgrepRunner().runWithSingleRule(semgrepRuleFile, repository); - sarifs.put(rulePath, sarif); - return sarif; - } -} diff --git a/languages/codemodder-semgrep-provider/src/main/java/io/codemodder/providers/sarif/semgrep/SemgrepJavaParserChanger.java b/languages/codemodder-semgrep-provider/src/main/java/io/codemodder/providers/sarif/semgrep/SemgrepJavaParserChanger.java new file mode 100644 index 000000000..84f9ba3b2 --- /dev/null +++ b/languages/codemodder-semgrep-provider/src/main/java/io/codemodder/providers/sarif/semgrep/SemgrepJavaParserChanger.java @@ -0,0 +1,43 @@ +package io.codemodder.providers.sarif.semgrep; + +import com.contrastsecurity.sarif.Region; +import com.github.javaparser.ast.visitor.ModifierVisitor; +import io.codemodder.CodemodInvocationContext; +import io.codemodder.FileWeavingContext; +import io.codemodder.JavaParserChanger; +import io.codemodder.RuleSarif; +import java.util.List; +import java.util.Objects; +import java.util.Optional; + +/** Provides base functionality for making JavaParser-based changes with Semgrep. */ +public abstract class SemgrepJavaParserChanger implements JavaParserChanger { + + protected final RuleSarif sarif; + + protected SemgrepJavaParserChanger(final RuleSarif semgrepSarif) { + this.sarif = Objects.requireNonNull(semgrepSarif); + } + + @Override + public Optional> createModifierVisitor( + final CodemodInvocationContext context) { + List regions = sarif.getRegionsFromResultsByRule(context.path(), sarif.getRule()); + if (!regions.isEmpty()) { + return Optional.of(createVisitor(context, regions)); + } + return Optional.empty(); + } + + /** + * Creates a visitor for the given context and locations. + * + * @param context the context of this files transformation + * @param regions the places in this file that have been identified as needing change by the + * static analysis + * @return a visitor that will perform the necessary changes in the given source code file + * positions + */ + public abstract ModifierVisitor createVisitor( + CodemodInvocationContext context, List regions); +} diff --git a/languages/codemodder-semgrep-provider/src/main/java/io/codemodder/providers/sarif/semgrep/SemgrepModule.java b/languages/codemodder-semgrep-provider/src/main/java/io/codemodder/providers/sarif/semgrep/SemgrepModule.java new file mode 100644 index 000000000..3f0ae2896 --- /dev/null +++ b/languages/codemodder-semgrep-provider/src/main/java/io/codemodder/providers/sarif/semgrep/SemgrepModule.java @@ -0,0 +1,121 @@ +package io.codemodder.providers.sarif.semgrep; + +import com.contrastsecurity.sarif.SarifSchema210; +import com.google.inject.AbstractModule; +import com.google.inject.Inject; +import io.codemodder.Changer; +import io.codemodder.RuleSarif; +import java.io.IOException; +import java.io.InputStream; +import java.lang.reflect.Constructor; +import java.lang.reflect.Parameter; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; +import org.apache.commons.io.IOUtils; + +/** Responsible for binding Semgrep-related things. */ +final class SemgrepModule extends AbstractModule { + + private final List> codemodTypes; + private final Path codeDirectory; + private final SemgrepRunner runner; + + SemgrepModule( + final Path codeDirectory, + final List> codemodTypes, + final SemgrepRunner runner) { + this.codemodTypes = Objects.requireNonNull(codemodTypes); + this.codeDirectory = Objects.requireNonNull(codeDirectory); + this.runner = Objects.requireNonNull(runner); + } + + @Override + protected void configure() { + + /* + * This sections holds some fair bit of the creation of the "magic" codemod authors get to run. Guice isn't very good + * about injecting a dependency which depends on an annotation's property, so we have to do some of the leg work + * to do that binding ourselves. + */ + List yamlClasspathPathsToRun = new ArrayList<>(); + List toBind = new ArrayList<>(); + + for (Class codemodType : codemodTypes) { + // find all constructors that are marked with @Inject + Constructor[] constructors = codemodType.getDeclaredConstructors(); + List> injectableConstructors = + Arrays.stream(constructors) + .filter( + constructor -> + constructor.getAnnotation(Inject.class) != null + || constructor.getAnnotation(javax.inject.Inject.class) != null) + .collect(Collectors.toUnmodifiableList()); + + // find all @SemgrepScan annotations in their parameters and batch them up for running + injectableConstructors.forEach( + constructor -> { + Parameter[] parameters = constructor.getParameters(); + Arrays.stream(parameters) + .forEach( + parameter -> { + SemgrepScan semgrepScanAnnotation = + parameter.getAnnotation(SemgrepScan.class); + if (semgrepScanAnnotation != null) { + if (!RuleSarif.class.equals(parameter.getType())) { + throw new IllegalArgumentException( + "Can only inject semgrep results into " + + RuleSarif.class.getSimpleName() + + " types"); + } + yamlClasspathPathsToRun.add(semgrepScanAnnotation.pathToYaml()); + toBind.add(semgrepScanAnnotation); + } + }); + }); + + // copy the yaml out of the classpath onto disk so semgrep can use them + List yamlRuleFiles = + yamlClasspathPathsToRun.stream() + .map(this::saveClasspathResourceToTemp) + .collect(Collectors.toUnmodifiableList()); + + // actually run the SARIF only once + SarifSchema210 sarif; + try { + sarif = runner.run(yamlRuleFiles, codeDirectory); + } catch (IOException e) { + throw new IllegalArgumentException("Semgrep execution failed", e); + } + + // bind the SARIF results + for (SemgrepScan sarifAnnotation : toBind) { + SemgrepSarif semgrepSarif = new SemgrepSarif(sarifAnnotation.ruleId(), sarif); + bind(RuleSarif.class).annotatedWith(sarifAnnotation).toInstance(semgrepSarif); + } + } + } + + /** + * Turn the yaml resource in the classpath into a file accessible via {@link Path}. Forgive the + * exception re-throwing but this is being used from a lambda and this shouldn't fail ever anyway. + */ + private Path saveClasspathResourceToTemp(final String yamlClasspathResourcePath) { + try { + InputStream ruleInputStream = getClass().getResource(yamlClasspathResourcePath).openStream(); + String ruleYaml = IOUtils.toString(ruleInputStream, StandardCharsets.UTF_8); + ruleInputStream.close(); + + Path semgrepRuleFile = Files.createTempFile("semgrep", ".yaml"); + Files.write(semgrepRuleFile, ruleYaml.getBytes(StandardCharsets.UTF_8)); + return semgrepRuleFile; + } catch (IOException e) { + throw new RuntimeException("failed to write write yaml to disk", e); + } + } +} diff --git a/languages/codemodder-semgrep-provider/src/main/java/io/codemodder/providers/sarif/semgrep/SemgrepProvider.java b/languages/codemodder-semgrep-provider/src/main/java/io/codemodder/providers/sarif/semgrep/SemgrepProvider.java index ee831feeb..0d049583d 100644 --- a/languages/codemodder-semgrep-provider/src/main/java/io/codemodder/providers/sarif/semgrep/SemgrepProvider.java +++ b/languages/codemodder-semgrep-provider/src/main/java/io/codemodder/providers/sarif/semgrep/SemgrepProvider.java @@ -1,14 +1,18 @@ package io.codemodder.providers.sarif.semgrep; import com.google.inject.AbstractModule; +import io.codemodder.Changer; import io.codemodder.CodemodProvider; +import java.nio.file.Path; +import java.util.List; import java.util.Set; /** Provides Semgrep-related functionality to codemodder. */ public final class SemgrepProvider implements CodemodProvider { @Override - public Set getModules() { - return Set.of(new SemgrepSarifModule()); + public Set getModules( + final Path codeDirectory, final List> codemodTypes) { + return Set.of(new SemgrepModule(codeDirectory, codemodTypes, new DefaultSemgrepRunner())); } } diff --git a/languages/codemodder-semgrep-provider/src/main/java/io/codemodder/providers/sarif/semgrep/SemgrepRunner.java b/languages/codemodder-semgrep-provider/src/main/java/io/codemodder/providers/sarif/semgrep/SemgrepRunner.java index 6149da83b..d7b08cbf5 100644 --- a/languages/codemodder-semgrep-provider/src/main/java/io/codemodder/providers/sarif/semgrep/SemgrepRunner.java +++ b/languages/codemodder-semgrep-provider/src/main/java/io/codemodder/providers/sarif/semgrep/SemgrepRunner.java @@ -3,6 +3,7 @@ import com.contrastsecurity.sarif.SarifSchema210; import java.io.IOException; import java.nio.file.Path; +import java.util.List; /** Responsible for running semgrep */ interface SemgrepRunner { @@ -10,9 +11,9 @@ interface SemgrepRunner { /** * Execute semgrep. * - * @param rule the directory/file where the rule(s) are stored - * @param repository the directory containing the code to be run on + * @param yamls the directory/file(s) where the rule(s) are stored + * @param codeDir the directory containing the code to be run on * @return the resulting SARIF */ - SarifSchema210 runWithSingleRule(Path rule, Path repository) throws IOException; + SarifSchema210 run(List yamls, Path codeDir) throws IOException; } diff --git a/languages/codemodder-semgrep-provider/src/main/java/io/codemodder/providers/sarif/semgrep/SemgrepSarif.java b/languages/codemodder-semgrep-provider/src/main/java/io/codemodder/providers/sarif/semgrep/SemgrepSarif.java new file mode 100644 index 000000000..108af4706 --- /dev/null +++ b/languages/codemodder-semgrep-provider/src/main/java/io/codemodder/providers/sarif/semgrep/SemgrepSarif.java @@ -0,0 +1,66 @@ +package io.codemodder.providers.sarif.semgrep; + +import com.contrastsecurity.sarif.Region; +import com.contrastsecurity.sarif.Result; +import com.contrastsecurity.sarif.SarifSchema210; +import io.codemodder.RuleSarif; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; + +/** {@inheritDoc} */ +public final class SemgrepSarif implements RuleSarif { + + private final SarifSchema210 sarif; + private final String ruleId; + + SemgrepSarif(final String ruleId, final SarifSchema210 sarif) { + this.sarif = Objects.requireNonNull(sarif); + this.ruleId = Objects.requireNonNull(ruleId); + } + + /** {@inheritDoc} */ + @Override + public SarifSchema210 rawDocument() { + return sarif; + } + + /** {@inheritDoc} */ + @Override + public String getRule() { + return ruleId; + } + + /** {@inheritDoc} */ + @Override + public List getRegionsFromResultsByRule(final Path path, final String ruleId) { + List resultsFilteredByRule = + sarif.getRuns().get(0).getResults().stream() + .filter(result -> result.getRuleId().endsWith("." + ruleId)) + .collect(Collectors.toUnmodifiableList()); + List resultsFilteredByRuleAndPath = + resultsFilteredByRule.stream() + .filter( + result -> { + String uri = + result + .getLocations() + .get(0) + .getPhysicalLocation() + .getArtifactLocation() + .getUri(); + try { + return Files.isSameFile(path, Path.of(uri)); + } catch (IOException e) { // this should never happen + return false; + } + }) + .collect(Collectors.toUnmodifiableList()); + return resultsFilteredByRuleAndPath.stream() + .map(result -> result.getLocations().get(0).getPhysicalLocation().getRegion()) + .collect(Collectors.toUnmodifiableList()); + } +} diff --git a/languages/codemodder-semgrep-provider/src/main/java/io/codemodder/providers/sarif/semgrep/SemgrepSarifModule.java b/languages/codemodder-semgrep-provider/src/main/java/io/codemodder/providers/sarif/semgrep/SemgrepSarifModule.java deleted file mode 100644 index e85fa3c70..000000000 --- a/languages/codemodder-semgrep-provider/src/main/java/io/codemodder/providers/sarif/semgrep/SemgrepSarifModule.java +++ /dev/null @@ -1,11 +0,0 @@ -package io.codemodder.providers.sarif.semgrep; - -import com.google.inject.AbstractModule; - -final class SemgrepSarifModule extends AbstractModule { - - @Override - protected void configure() { - bind(SemgrepSarifProvider.class).toInstance(new DefaultSemgrepSarifProvider()); - } -} diff --git a/languages/codemodder-semgrep-provider/src/main/java/io/codemodder/providers/sarif/semgrep/SemgrepScan.java b/languages/codemodder-semgrep-provider/src/main/java/io/codemodder/providers/sarif/semgrep/SemgrepScan.java new file mode 100644 index 000000000..625a4d923 --- /dev/null +++ b/languages/codemodder-semgrep-provider/src/main/java/io/codemodder/providers/sarif/semgrep/SemgrepScan.java @@ -0,0 +1,16 @@ +package io.codemodder.providers.sarif.semgrep; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import javax.inject.Qualifier; + +@Qualifier +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.PARAMETER) +public @interface SemgrepScan { + String pathToYaml(); + + String ruleId(); +} diff --git a/languages/codemodder-semgrep-provider/src/test/java/io/codemodder/providers/sarif/semgrep/SemgrepSarifProviderTest.java b/languages/codemodder-semgrep-provider/src/test/java/io/codemodder/providers/sarif/semgrep/SemgrepRunnerTest.java similarity index 81% rename from languages/codemodder-semgrep-provider/src/test/java/io/codemodder/providers/sarif/semgrep/SemgrepSarifProviderTest.java rename to languages/codemodder-semgrep-provider/src/test/java/io/codemodder/providers/sarif/semgrep/SemgrepRunnerTest.java index 22cc01a24..e1bbb2a9d 100644 --- a/languages/codemodder-semgrep-provider/src/test/java/io/codemodder/providers/sarif/semgrep/SemgrepSarifProviderTest.java +++ b/languages/codemodder-semgrep-provider/src/test/java/io/codemodder/providers/sarif/semgrep/SemgrepRunnerTest.java @@ -11,10 +11,11 @@ import java.nio.file.Files; import java.nio.file.Path; import java.util.List; +import org.apache.commons.io.IOUtils; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; -final class SemgrepSarifProviderTest { +final class SemgrepRunnerTest { @Test void it_runs_semgrep_and_produces_expected_sarif(@TempDir Path repositoryDir) throws IOException { @@ -24,9 +25,11 @@ void it_runs_semgrep_and_produces_expected_sarif(@TempDir Path repositoryDir) th String insecureRandomJavaClass = "class Foo { Random rnd = new Random(); }"; Files.write(javaFile, insecureRandomJavaClass.getBytes(StandardCharsets.UTF_8)); + Path ruleFile = Files.createTempFile(repositoryDir, "example", ".yaml"); + Files.write(ruleFile, IOUtils.toByteArray(getClass().getResourceAsStream("/example.semgrep"))); + // run the scan - SarifSchema210 sarif = - new DefaultSemgrepSarifProvider().getSarif(repositoryDir, "example.semgrep"); + SarifSchema210 sarif = new DefaultSemgrepRunner().run(List.of(ruleFile), repositoryDir); // assert the scan went as we think it should List runs = sarif.getRuns(); diff --git a/languages/java/build.gradle.kts b/languages/java/build.gradle.kts index c32632a2d..8424ba059 100644 --- a/languages/java/build.gradle.kts +++ b/languages/java/build.gradle.kts @@ -70,6 +70,7 @@ dependencies { implementation(libs.progressbar) implementation(libs.slf4j.api) + implementation(project(":languages:codemodder-semgrep-provider")) implementation(project(":languages:codemodder-common")) implementation(project(":languages:codemodder-framework-java")) implementation(project(":languages:codemodder-default-codemods")) diff --git a/languages/java/src/main/java/io/openpixee/java/JavaFixitCliRun.java b/languages/java/src/main/java/io/openpixee/java/JavaFixitCliRun.java index 6ab4187ce..c4182918d 100644 --- a/languages/java/src/main/java/io/openpixee/java/JavaFixitCliRun.java +++ b/languages/java/src/main/java/io/openpixee/java/JavaFixitCliRun.java @@ -10,7 +10,6 @@ import io.codemodder.RuleContext; import io.codemodder.Weave; import io.codemodder.codemods.DefaultCodemods; -import io.codemodder.codemods.SecureRandomCodemod; import io.github.pixee.codetf.CodeTFReport; import java.io.File; import java.io.IOException; @@ -117,8 +116,7 @@ public CodeTFReport run( List> defaultCodemodTypes = DefaultCodemods.asList(); CodemodInvoker codemodInvoker = - new CodemodInvoker( - List.of(SecureRandomCodemod.class), ruleContext, repositoryRoot.toPath()); + new CodemodInvoker(defaultCodemodTypes, ruleContext, repositoryRoot.toPath()); // run the Java code visitors final var javaSourceWeaveResult = javaSourceWeaver.weave( From 7390cb4a6fd651620ad45021c00a301579cdd79a Mon Sep 17 00:00:00 2001 From: Arshan Dabirsiaghi Date: Mon, 13 Mar 2023 21:49:48 -0400 Subject: [PATCH 15/19] fixed bug --- .../providers/sarif/semgrep/DefaultSemgrepRunner.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/languages/codemodder-semgrep-provider/src/main/java/io/codemodder/providers/sarif/semgrep/DefaultSemgrepRunner.java b/languages/codemodder-semgrep-provider/src/main/java/io/codemodder/providers/sarif/semgrep/DefaultSemgrepRunner.java index c99b38ec3..446d2e57d 100644 --- a/languages/codemodder-semgrep-provider/src/main/java/io/codemodder/providers/sarif/semgrep/DefaultSemgrepRunner.java +++ b/languages/codemodder-semgrep-provider/src/main/java/io/codemodder/providers/sarif/semgrep/DefaultSemgrepRunner.java @@ -37,7 +37,7 @@ public SarifSchema210 run(final List ruleYamls, final Path repository) thr args.add(repositoryPath); // backup existing .segmrepignore if it exists - File existingSemgrepFile = new File(repository.toFile(), ".semgrepignore"); + File existingSemgrepFile = new File(".semgrepignore"); Optional backup = Optional.empty(); if (existingSemgrepFile.exists()) { From 51c5fc4a0d772cc2d079c6f0e90d4b8bef698187 Mon Sep 17 00:00:00 2001 From: Arshan Dabirsiaghi Date: Mon, 13 Mar 2023 22:01:18 -0400 Subject: [PATCH 16/19] updated future entrypoint to be documented at least --- .../src/main/java/io/codemodder/codemods/Runner.java | 2 +- .../src/main/java/io/codemodder/CodemodInvoker.java | 9 ++++----- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/languages/codemodder-default-codemods/src/main/java/io/codemodder/codemods/Runner.java b/languages/codemodder-default-codemods/src/main/java/io/codemodder/codemods/Runner.java index 28511ee73..0b1157127 100644 --- a/languages/codemodder-default-codemods/src/main/java/io/codemodder/codemods/Runner.java +++ b/languages/codemodder-default-codemods/src/main/java/io/codemodder/codemods/Runner.java @@ -6,6 +6,6 @@ public final class Runner { public static void main(final String[] args) { - run(SecureRandomCodemod.class); + run(args, SecureRandomCodemod.class); } } diff --git a/languages/codemodder-framework-java/src/main/java/io/codemodder/CodemodInvoker.java b/languages/codemodder-framework-java/src/main/java/io/codemodder/CodemodInvoker.java index 85f2967fd..810da456b 100644 --- a/languages/codemodder-framework-java/src/main/java/io/codemodder/CodemodInvoker.java +++ b/languages/codemodder-framework-java/src/main/java/io/codemodder/CodemodInvoker.java @@ -110,12 +110,11 @@ public void execute(final Path path, final CompilationUnit cu, final FileWeaving /** * This is the entry point custom-built codemods are supposed to go through. Right now, this is - * not useful directly as we're worried about + * not useful directly as we're worried primarily about the legacy entrypoints. */ - public static void run(final Class... codemodTypes) { - new CodemodInvoker(Arrays.asList(codemodTypes), Path.of(".")); - // TODO: loop through the files and invoke the codemods on each file - // codemodInvoker.execute(); + @SuppressWarnings("unused") + public static void run(String[] args, final Class... codemodTypes) { + CodemodInvoker invoker = new CodemodInvoker(Arrays.asList(codemodTypes), Path.of(".")); } private static void validateRequiredFields(final Codemod codemodAnnotation) { From aa13a24ade146585238d703d08affe60488a95fb Mon Sep 17 00:00:00 2001 From: Arshan Dabirsiaghi Date: Tue, 14 Mar 2023 14:10:05 -0400 Subject: [PATCH 17/19] pr feedback --- .../build.gradle.kts | 14 +++-- .../build.gradle.kts | 22 +++----- .../java/io/codemodder/CodeDirectory.java | 4 +- .../src/main/java/io/codemodder/Codemod.java | 2 + .../java/io/codemodder/CodemodInvoker.java | 26 +++++++--- .../io/codemodder/DefaultCodeDirectory.java | 18 ++++--- .../DefaultCodemodInvocationContext.java | 5 -- .../DependencyManagementProvider.java | 9 ---- .../java/io/codemodder/JavaParserInvoker.java | 3 -- .../java/io/codemodder/JavaParserUtils.java | 8 +-- .../main/java/io/codemodder/RuleSarif.java | 3 +- .../build.gradle.kts | 6 --- .../sarif/semgrep/DefaultSemgrepRunner.java | 51 +++++++++---------- .../semgrep/SemgrepJavaParserChanger.java | 7 +-- .../sarif/semgrep/SemgrepModule.java | 19 +++---- .../sarif/semgrep/SemgrepProvider.java | 2 +- ...emgrepSarif.java => SemgrepRuleSarif.java} | 22 +++++--- .../providers/sarif/semgrep/SemgrepScan.java | 20 ++++++++ .../sarif/semgrep/SemgrepRunnerTest.java | 8 ++- 19 files changed, 128 insertions(+), 121 deletions(-) delete mode 100644 languages/codemodder-framework-java/src/main/java/io/codemodder/DependencyManagementProvider.java delete mode 100644 languages/codemodder-framework-java/src/main/java/io/codemodder/JavaParserInvoker.java rename languages/codemodder-semgrep-provider/src/main/java/io/codemodder/providers/sarif/semgrep/{SemgrepSarif.java => SemgrepRuleSarif.java} (75%) diff --git a/languages/codemodder-default-codemods/build.gradle.kts b/languages/codemodder-default-codemods/build.gradle.kts index ce78164c0..1650b3b50 100644 --- a/languages/codemodder-default-codemods/build.gradle.kts +++ b/languages/codemodder-default-codemods/build.gradle.kts @@ -32,14 +32,12 @@ publishing { } dependencies { - implementation(libs.javax.inject) - implementation(libs.contrast.sarif) - implementation(libs.slf4j.api) - implementation(libs.javaparser.core) - implementation(libs.javaparser.symbolsolver.core) - implementation(libs.javaparser.symbolsolver.logic) - implementation(libs.javaparser.symbolsolver.model) - implementation(project(":languages:codemodder-common")) +// implementation(libs.slf4j.api) +// implementation(libs.javaparser.core) +// implementation(libs.javaparser.symbolsolver.core) +// implementation(libs.javaparser.symbolsolver.logic) +// implementation(libs.javaparser.symbolsolver.model) +// implementation(project(":languages:codemodder-common")) implementation(project(":languages:codemodder-framework-java")) implementation(project(":languages:codemodder-semgrep-provider")) diff --git a/languages/codemodder-framework-java/build.gradle.kts b/languages/codemodder-framework-java/build.gradle.kts index 847f99fcb..61093a52d 100644 --- a/languages/codemodder-framework-java/build.gradle.kts +++ b/languages/codemodder-framework-java/build.gradle.kts @@ -3,7 +3,6 @@ plugins { id("io.openpixee.codetl.base") id("io.openpixee.codetl.java-library") id("io.openpixee.codetl.maven-publish") - id("application") alias(libs.plugins.fileversioning) } @@ -13,11 +12,6 @@ java { } } -spotless { - java { - } -} - publishing { publications { register("maven") { @@ -32,16 +26,16 @@ dependencies { api("io.github.pixee:codetf-java:0.0.2") // TODO bring codetf-java into the monorepo - implementation(libs.guice) - implementation(libs.contrast.sarif) - implementation(libs.javaparser.core) - implementation(libs.javaparser.symbolsolver.core) - implementation(libs.javaparser.symbolsolver.logic) - implementation(libs.javaparser.symbolsolver.model) + api(libs.guice) + api(libs.contrast.sarif) + api(libs.javaparser.core) + api(libs.javaparser.symbolsolver.core) + api(libs.javaparser.symbolsolver.logic) + api(libs.javaparser.symbolsolver.model) implementation(libs.logback.classic) implementation(libs.maven.model) - implementation(libs.slf4j.api) - implementation(project(":languages:codemodder-common")) + api(libs.slf4j.api) + api(project(":languages:codemodder-common")) testImplementation(testlibs.bundles.junit.jupiter) testImplementation(testlibs.bundles.hamcrest) diff --git a/languages/codemodder-framework-java/src/main/java/io/codemodder/CodeDirectory.java b/languages/codemodder-framework-java/src/main/java/io/codemodder/CodeDirectory.java index b2f441357..8a1fd9b44 100644 --- a/languages/codemodder-framework-java/src/main/java/io/codemodder/CodeDirectory.java +++ b/languages/codemodder-framework-java/src/main/java/io/codemodder/CodeDirectory.java @@ -1,12 +1,10 @@ package io.codemodder; -import java.io.File; import java.nio.file.Path; /** Holds a code directory (e.g., a repository root). */ public interface CodeDirectory { - File asFile(); - + /** The filesystem directory path we are running against. */ Path asPath(); } diff --git a/languages/codemodder-framework-java/src/main/java/io/codemodder/Codemod.java b/languages/codemodder-framework-java/src/main/java/io/codemodder/Codemod.java index bacde72ef..140ee8d0c 100644 --- a/languages/codemodder-framework-java/src/main/java/io/codemodder/Codemod.java +++ b/languages/codemodder-framework-java/src/main/java/io/codemodder/Codemod.java @@ -1,11 +1,13 @@ package io.codemodder; +import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** Used to mark types providing codemod functionality and provide the necessary metadata. */ +@Documented @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) public @interface Codemod { diff --git a/languages/codemodder-framework-java/src/main/java/io/codemodder/CodemodInvoker.java b/languages/codemodder-framework-java/src/main/java/io/codemodder/CodemodInvoker.java index 810da456b..751a77b9a 100644 --- a/languages/codemodder-framework-java/src/main/java/io/codemodder/CodemodInvoker.java +++ b/languages/codemodder-framework-java/src/main/java/io/codemodder/CodemodInvoker.java @@ -9,10 +9,8 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; -import java.util.HashMap; import java.util.HashSet; import java.util.List; -import java.util.Map; import java.util.Objects; import java.util.Optional; import java.util.ServiceLoader; @@ -33,7 +31,7 @@ public final class CodemodInvoker { private final List codemods; private final Path repositoryDir; - private final Map changerToCodemodIds; + private final List changers; public CodemodInvoker( final List> codemodTypes, final Path repositoryDir) { @@ -62,7 +60,7 @@ public CodemodInvoker( } // record which changers are associated with which codemod ids - Map changerToCodemodIds = new HashMap<>(); + List changers = new ArrayList<>(); // validate and instantiate the codemods Injector injector = Guice.createInjector(allModules); @@ -74,15 +72,25 @@ public CodemodInvoker( String codemodId = codemodAnnotation.id(); if (ruleContext.isRuleAllowed(codemodId)) { codemods.add(changer); - changerToCodemodIds.put(changer, codemodId); + changers.add(new IdentifiedChanger(codemodId, changer)); } } - this.changerToCodemodIds = Collections.unmodifiableMap(changerToCodemodIds); + this.changers = Collections.unmodifiableList(changers); this.codemods = Collections.unmodifiableList(codemods); this.repositoryDir = Objects.requireNonNull(repositoryDir); } + private static final class IdentifiedChanger { + final String id; + final Changer changer; + + private IdentifiedChanger(final String id, final Changer changer) { + this.id = id; + this.changer = changer; + } + } + /** * Run the codemods we've collected on the given file. * @@ -98,7 +106,11 @@ public void execute(final Path path, final CompilationUnit cu, final FileWeaving new DefaultCodemodInvocationContext( new DefaultCodeDirectory(repositoryDir), path, - changerToCodemodIds.get(changer), + changers.stream() + .filter(ic -> ic.changer == changer) + .findFirst() + .orElseThrow() + .id, context)); modifierVisitor.ifPresent( changeContextModifierVisitor -> cu.accept(changeContextModifierVisitor, context)); diff --git a/languages/codemodder-framework-java/src/main/java/io/codemodder/DefaultCodeDirectory.java b/languages/codemodder-framework-java/src/main/java/io/codemodder/DefaultCodeDirectory.java index 3d7a5f904..68aff5634 100644 --- a/languages/codemodder-framework-java/src/main/java/io/codemodder/DefaultCodeDirectory.java +++ b/languages/codemodder-framework-java/src/main/java/io/codemodder/DefaultCodeDirectory.java @@ -1,21 +1,25 @@ package io.codemodder; -import java.io.File; +import java.nio.file.Files; import java.nio.file.Path; import java.util.Objects; -/** {@inheritDoc} */ final class DefaultCodeDirectory implements CodeDirectory { private final Path repositoryDir; DefaultCodeDirectory(final Path repositoryDir) { - this.repositoryDir = Objects.requireNonNull(repositoryDir); - } + if (!Files.exists(repositoryDir)) { + throw new IllegalArgumentException("code directory doesn't exist"); + } + if (!Files.isDirectory(repositoryDir)) { + throw new IllegalArgumentException("code directory isn't a directory"); + } + if (!Files.isReadable(repositoryDir)) { + throw new IllegalArgumentException("code directory isn't readable"); + } - @Override - public File asFile() { - return repositoryDir.toFile(); + this.repositoryDir = Objects.requireNonNull(repositoryDir); } @Override diff --git a/languages/codemodder-framework-java/src/main/java/io/codemodder/DefaultCodemodInvocationContext.java b/languages/codemodder-framework-java/src/main/java/io/codemodder/DefaultCodemodInvocationContext.java index dbc2caee9..34d74ba8d 100644 --- a/languages/codemodder-framework-java/src/main/java/io/codemodder/DefaultCodemodInvocationContext.java +++ b/languages/codemodder-framework-java/src/main/java/io/codemodder/DefaultCodemodInvocationContext.java @@ -3,7 +3,6 @@ import java.nio.file.Path; import java.util.Objects; -/** {@inheritDoc} */ final class DefaultCodemodInvocationContext implements CodemodInvocationContext { private final CodeDirectory codeDirectory; @@ -22,25 +21,21 @@ final class DefaultCodemodInvocationContext implements CodemodInvocationContext this.changeRecorder = Objects.requireNonNull(changeRecorder); } - /** {@inheritDoc} */ @Override public FileWeavingContext changeRecorder() { return changeRecorder; } - /** {@inheritDoc} */ @Override public CodeDirectory codeDirectory() { return codeDirectory; } - /** {@inheritDoc} */ @Override public Path path() { return path; } - /** {@inheritDoc} */ @Override public String codemodId() { return codemodId; diff --git a/languages/codemodder-framework-java/src/main/java/io/codemodder/DependencyManagementProvider.java b/languages/codemodder-framework-java/src/main/java/io/codemodder/DependencyManagementProvider.java deleted file mode 100644 index 66da5cea9..000000000 --- a/languages/codemodder-framework-java/src/main/java/io/codemodder/DependencyManagementProvider.java +++ /dev/null @@ -1,9 +0,0 @@ -package io.codemodder; - -/** A provider that offers developers a way to query and manage dependencies. */ -public interface DependencyManagementProvider { - - boolean hasDependency(String groupId, String artifactId, String minimumVersion); - - void addDependency(String groupId, String artifactId, String minimumVersion); -} diff --git a/languages/codemodder-framework-java/src/main/java/io/codemodder/JavaParserInvoker.java b/languages/codemodder-framework-java/src/main/java/io/codemodder/JavaParserInvoker.java deleted file mode 100644 index 414f09574..000000000 --- a/languages/codemodder-framework-java/src/main/java/io/codemodder/JavaParserInvoker.java +++ /dev/null @@ -1,3 +0,0 @@ -package io.codemodder; - -public class JavaParserInvoker {} diff --git a/languages/codemodder-framework-java/src/main/java/io/codemodder/JavaParserUtils.java b/languages/codemodder-framework-java/src/main/java/io/codemodder/JavaParserUtils.java index f649b0930..2488abedb 100644 --- a/languages/codemodder-framework-java/src/main/java/io/codemodder/JavaParserUtils.java +++ b/languages/codemodder-framework-java/src/main/java/io/codemodder/JavaParserUtils.java @@ -7,8 +7,11 @@ import com.github.javaparser.ast.Node; import com.github.javaparser.ast.NodeList; +/** Holds common AST utilities in JavaParser. */ public final class JavaParserUtils { + private JavaParserUtils() {} + /** * Adds a type to the import list if it's not in there directly, and if it's not implied by a * wildcard. Care will be taken to ensure it's inserted in alphabetical order. @@ -22,11 +25,8 @@ public static void addImportIfMissing(final CompilationUnit cu, final String cla if (imports.contains(newImport)) { return; } - for (int i = 0; i < imports.size(); i++) { - ImportDeclaration existingImport = imports.get(i); - + for (ImportDeclaration existingImport : imports) { if (existingImport.getNameAsString().compareToIgnoreCase(className) > 0) { - imports.addBefore(newImport, existingImport); return; } diff --git a/languages/codemodder-framework-java/src/main/java/io/codemodder/RuleSarif.java b/languages/codemodder-framework-java/src/main/java/io/codemodder/RuleSarif.java index f67f89da7..3cc3c3dce 100644 --- a/languages/codemodder-framework-java/src/main/java/io/codemodder/RuleSarif.java +++ b/languages/codemodder-framework-java/src/main/java/io/codemodder/RuleSarif.java @@ -12,10 +12,9 @@ public interface RuleSarif { * Get all the regions for the SARIF with the matching rule ID * * @param path the file being scanned - * @param ruleId the semgrep rule ID * @return the source code regions where the given rule was found in the given file */ - List getRegionsFromResultsByRule(Path path, String ruleId); + List getRegionsFromResultsByRule(Path path); /** Return the entire SARIF as a model in case more comprehensive inspection is needed. */ SarifSchema210 rawDocument(); diff --git a/languages/codemodder-semgrep-provider/build.gradle.kts b/languages/codemodder-semgrep-provider/build.gradle.kts index 82cc70c37..113afa5cb 100644 --- a/languages/codemodder-semgrep-provider/build.gradle.kts +++ b/languages/codemodder-semgrep-provider/build.gradle.kts @@ -3,7 +3,6 @@ plugins { id("io.openpixee.codetl.base") id("io.openpixee.codetl.java-library") id("io.openpixee.codetl.maven-publish") - id("application") alias(libs.plugins.fileversioning) } @@ -13,11 +12,6 @@ java { } } -spotless { - java { - } -} - publishing { publications { register("maven") { diff --git a/languages/codemodder-semgrep-provider/src/main/java/io/codemodder/providers/sarif/semgrep/DefaultSemgrepRunner.java b/languages/codemodder-semgrep-provider/src/main/java/io/codemodder/providers/sarif/semgrep/DefaultSemgrepRunner.java index 446d2e57d..bbe37a212 100644 --- a/languages/codemodder-semgrep-provider/src/main/java/io/codemodder/providers/sarif/semgrep/DefaultSemgrepRunner.java +++ b/languages/codemodder-semgrep-provider/src/main/java/io/codemodder/providers/sarif/semgrep/DefaultSemgrepRunner.java @@ -2,8 +2,6 @@ import com.contrastsecurity.sarif.SarifSchema210; import com.fasterxml.jackson.databind.ObjectMapper; -import java.io.File; -import java.io.FileReader; import java.io.IOException; import java.nio.charset.StandardCharsets; import java.nio.file.Files; @@ -12,24 +10,25 @@ import java.util.ArrayList; import java.util.List; import java.util.Optional; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -/** {@inheritDoc} */ final class DefaultSemgrepRunner implements SemgrepRunner { - /** {@inheritDoc} */ + private final ObjectMapper objectMapper; + + DefaultSemgrepRunner() { + this.objectMapper = new ObjectMapper(); + } + @Override public SarifSchema210 run(final List ruleYamls, final Path repository) throws IOException { String repositoryPath = repository.toString(); - File sarifFile = File.createTempFile("semgrep", ".sarif"); - sarifFile.deleteOnExit(); + Path sarifFile = Files.createTempFile("semgrep", ".sarif"); List args = new ArrayList<>(); args.add("semgrep"); args.add("--sarif"); args.add("-o"); - args.add(sarifFile.getAbsolutePath()); + args.add(sarifFile.toAbsolutePath().toString()); for (Path ruleYamlPath : ruleYamls) { args.add("--config"); args.add(ruleYamlPath.toString()); @@ -37,21 +36,20 @@ public SarifSchema210 run(final List ruleYamls, final Path repository) thr args.add(repositoryPath); // backup existing .segmrepignore if it exists - File existingSemgrepFile = new File(".semgrepignore"); - Optional backup = Optional.empty(); + Path existingSemgrepFile = Path.of(".semgrepignore").toAbsolutePath(); + Optional backup = Optional.empty(); - if (existingSemgrepFile.exists()) { - File backupFile = File.createTempFile("backup", ".semgrepignore"); - if (backupFile.exists()) { - backupFile.delete(); // i don't know how but this is happening in tests + if (Files.exists(existingSemgrepFile)) { + Path backupFile = Files.createTempFile("backup", ".semgrepignore"); + if (Files.exists(backupFile)) { + Files.delete(backupFile); } - Files.copy(existingSemgrepFile.toPath(), backupFile.toPath()); + Files.copy(existingSemgrepFile, backupFile); backup = Optional.of(backupFile); } - // create an an empty .semgrepignore file - Files.write( - existingSemgrepFile.toPath(), OUR_SEMGREPIGNORE_CONTENTS.getBytes(StandardCharsets.UTF_8)); + // create an empty .semgrepignore file + Files.write(existingSemgrepFile, OUR_SEMGREPIGNORE_CONTENTS.getBytes(StandardCharsets.UTF_8)); Process p = new ProcessBuilder(args).inheritIO().start(); try { @@ -60,20 +58,21 @@ public SarifSchema210 run(final List ruleYamls, final Path repository) thr throw new RuntimeException("error code seen from semgrep execution: " + rc); } } catch (InterruptedException e) { - logger.error("problem waiting for semgrep process execution", e); + throw new RuntimeException("problem waiting for semgrep process execution", e); } // restore existing .semgrepignore if it exists if (backup.isPresent()) { - Files.copy( - backup.get().toPath(), existingSemgrepFile.toPath(), StandardCopyOption.REPLACE_EXISTING); + Files.copy(backup.get(), existingSemgrepFile, StandardCopyOption.REPLACE_EXISTING); } else { - existingSemgrepFile.delete(); + Files.delete(existingSemgrepFile); } - return new ObjectMapper().readValue(new FileReader(sarifFile), SarifSchema210.class); + + SarifSchema210 sarif = + objectMapper.readValue(Files.newInputStream(sarifFile), SarifSchema210.class); + Files.delete(sarifFile); + return sarif; } private static final String OUR_SEMGREPIGNORE_CONTENTS = "# dont ignore anything"; - - private static final Logger logger = LoggerFactory.getLogger(SemgrepRunner.class); } diff --git a/languages/codemodder-semgrep-provider/src/main/java/io/codemodder/providers/sarif/semgrep/SemgrepJavaParserChanger.java b/languages/codemodder-semgrep-provider/src/main/java/io/codemodder/providers/sarif/semgrep/SemgrepJavaParserChanger.java index 84f9ba3b2..3d2817ff9 100644 --- a/languages/codemodder-semgrep-provider/src/main/java/io/codemodder/providers/sarif/semgrep/SemgrepJavaParserChanger.java +++ b/languages/codemodder-semgrep-provider/src/main/java/io/codemodder/providers/sarif/semgrep/SemgrepJavaParserChanger.java @@ -22,11 +22,8 @@ protected SemgrepJavaParserChanger(final RuleSarif semgrepSarif) { @Override public Optional> createModifierVisitor( final CodemodInvocationContext context) { - List regions = sarif.getRegionsFromResultsByRule(context.path(), sarif.getRule()); - if (!regions.isEmpty()) { - return Optional.of(createVisitor(context, regions)); - } - return Optional.empty(); + List regions = sarif.getRegionsFromResultsByRule(context.path()); + return !regions.isEmpty() ? Optional.of(createVisitor(context, regions)) : Optional.empty(); } /** diff --git a/languages/codemodder-semgrep-provider/src/main/java/io/codemodder/providers/sarif/semgrep/SemgrepModule.java b/languages/codemodder-semgrep-provider/src/main/java/io/codemodder/providers/sarif/semgrep/SemgrepModule.java index 3f0ae2896..4d75d3ac7 100644 --- a/languages/codemodder-semgrep-provider/src/main/java/io/codemodder/providers/sarif/semgrep/SemgrepModule.java +++ b/languages/codemodder-semgrep-provider/src/main/java/io/codemodder/providers/sarif/semgrep/SemgrepModule.java @@ -7,6 +7,7 @@ import io.codemodder.RuleSarif; import java.io.IOException; import java.io.InputStream; +import java.io.UncheckedIOException; import java.lang.reflect.Constructor; import java.lang.reflect.Parameter; import java.nio.charset.StandardCharsets; @@ -24,15 +25,10 @@ final class SemgrepModule extends AbstractModule { private final List> codemodTypes; private final Path codeDirectory; - private final SemgrepRunner runner; - SemgrepModule( - final Path codeDirectory, - final List> codemodTypes, - final SemgrepRunner runner) { + SemgrepModule(final Path codeDirectory, final List> codemodTypes) { this.codemodTypes = Objects.requireNonNull(codemodTypes); this.codeDirectory = Objects.requireNonNull(codeDirectory); - this.runner = Objects.requireNonNull(runner); } @Override @@ -88,14 +84,14 @@ protected void configure() { // actually run the SARIF only once SarifSchema210 sarif; try { - sarif = runner.run(yamlRuleFiles, codeDirectory); + sarif = new DefaultSemgrepRunner().run(yamlRuleFiles, codeDirectory); } catch (IOException e) { throw new IllegalArgumentException("Semgrep execution failed", e); } // bind the SARIF results for (SemgrepScan sarifAnnotation : toBind) { - SemgrepSarif semgrepSarif = new SemgrepSarif(sarifAnnotation.ruleId(), sarif); + SemgrepRuleSarif semgrepSarif = new SemgrepRuleSarif(sarifAnnotation.ruleId(), sarif); bind(RuleSarif.class).annotatedWith(sarifAnnotation).toInstance(semgrepSarif); } } @@ -106,16 +102,15 @@ protected void configure() { * exception re-throwing but this is being used from a lambda and this shouldn't fail ever anyway. */ private Path saveClasspathResourceToTemp(final String yamlClasspathResourcePath) { - try { - InputStream ruleInputStream = getClass().getResource(yamlClasspathResourcePath).openStream(); + try (InputStream ruleInputStream = + getClass().getResource(yamlClasspathResourcePath).openStream()) { String ruleYaml = IOUtils.toString(ruleInputStream, StandardCharsets.UTF_8); ruleInputStream.close(); - Path semgrepRuleFile = Files.createTempFile("semgrep", ".yaml"); Files.write(semgrepRuleFile, ruleYaml.getBytes(StandardCharsets.UTF_8)); return semgrepRuleFile; } catch (IOException e) { - throw new RuntimeException("failed to write write yaml to disk", e); + throw new UncheckedIOException("failed to write write yaml to disk", e); } } } diff --git a/languages/codemodder-semgrep-provider/src/main/java/io/codemodder/providers/sarif/semgrep/SemgrepProvider.java b/languages/codemodder-semgrep-provider/src/main/java/io/codemodder/providers/sarif/semgrep/SemgrepProvider.java index 0d049583d..19c55e5c6 100644 --- a/languages/codemodder-semgrep-provider/src/main/java/io/codemodder/providers/sarif/semgrep/SemgrepProvider.java +++ b/languages/codemodder-semgrep-provider/src/main/java/io/codemodder/providers/sarif/semgrep/SemgrepProvider.java @@ -13,6 +13,6 @@ public final class SemgrepProvider implements CodemodProvider { @Override public Set getModules( final Path codeDirectory, final List> codemodTypes) { - return Set.of(new SemgrepModule(codeDirectory, codemodTypes, new DefaultSemgrepRunner())); + return Set.of(new SemgrepModule(codeDirectory, codemodTypes)); } } diff --git a/languages/codemodder-semgrep-provider/src/main/java/io/codemodder/providers/sarif/semgrep/SemgrepSarif.java b/languages/codemodder-semgrep-provider/src/main/java/io/codemodder/providers/sarif/semgrep/SemgrepRuleSarif.java similarity index 75% rename from languages/codemodder-semgrep-provider/src/main/java/io/codemodder/providers/sarif/semgrep/SemgrepSarif.java rename to languages/codemodder-semgrep-provider/src/main/java/io/codemodder/providers/sarif/semgrep/SemgrepRuleSarif.java index 108af4706..4805036fe 100644 --- a/languages/codemodder-semgrep-provider/src/main/java/io/codemodder/providers/sarif/semgrep/SemgrepSarif.java +++ b/languages/codemodder-semgrep-provider/src/main/java/io/codemodder/providers/sarif/semgrep/SemgrepRuleSarif.java @@ -10,33 +10,38 @@ import java.util.List; import java.util.Objects; import java.util.stream.Collectors; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; -/** {@inheritDoc} */ -public final class SemgrepSarif implements RuleSarif { +/** + * {@inheritDoc} + * + *

    Semgrep's SARIF has a relatively simple model but the "ruleId" in the document is weird. It + * places the whole path to the rule in the field. This means our filtering logic is a little weird + * and unexpected, but it works. + */ +public final class SemgrepRuleSarif implements RuleSarif { private final SarifSchema210 sarif; private final String ruleId; - SemgrepSarif(final String ruleId, final SarifSchema210 sarif) { + SemgrepRuleSarif(final String ruleId, final SarifSchema210 sarif) { this.sarif = Objects.requireNonNull(sarif); this.ruleId = Objects.requireNonNull(ruleId); } - /** {@inheritDoc} */ @Override public SarifSchema210 rawDocument() { return sarif; } - /** {@inheritDoc} */ @Override public String getRule() { return ruleId; } - /** {@inheritDoc} */ @Override - public List getRegionsFromResultsByRule(final Path path, final String ruleId) { + public List getRegionsFromResultsByRule(final Path path) { List resultsFilteredByRule = sarif.getRuns().get(0).getResults().stream() .filter(result -> result.getRuleId().endsWith("." + ruleId)) @@ -55,6 +60,7 @@ public List getRegionsFromResultsByRule(final Path path, final String ru try { return Files.isSameFile(path, Path.of(uri)); } catch (IOException e) { // this should never happen + logger.error("Problem inspecting SARIF to find code regions", e); return false; } }) @@ -63,4 +69,6 @@ public List getRegionsFromResultsByRule(final Path path, final String ru .map(result -> result.getLocations().get(0).getPhysicalLocation().getRegion()) .collect(Collectors.toUnmodifiableList()); } + + private static final Logger logger = LoggerFactory.getLogger(SemgrepRuleSarif.class); } diff --git a/languages/codemodder-semgrep-provider/src/main/java/io/codemodder/providers/sarif/semgrep/SemgrepScan.java b/languages/codemodder-semgrep-provider/src/main/java/io/codemodder/providers/sarif/semgrep/SemgrepScan.java index 625a4d923..e29fcca57 100644 --- a/languages/codemodder-semgrep-provider/src/main/java/io/codemodder/providers/sarif/semgrep/SemgrepScan.java +++ b/languages/codemodder-semgrep-provider/src/main/java/io/codemodder/providers/sarif/semgrep/SemgrepScan.java @@ -1,16 +1,36 @@ package io.codemodder.providers.sarif.semgrep; +import io.codemodder.Codemod; +import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import javax.inject.Qualifier; +/** + * This tells the framework to inject the results of a Semgrep scan into the following parameter. + * This can only inject {@link io.codemodder.RuleSarif} types. + */ +@Documented @Qualifier @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.PARAMETER) public @interface SemgrepScan { + + /** + * The classpath resource path of the type. It is assumed the path will be in the same package as + * the {@link Codemod}. + * + *

    So, for instance, if you had a codemod in com.acme.codemods, and a YAML rule + * file in /com/acme/codemods/my-rule.yaml, you would simply specify "my-rule.yaml" for this + * value. + */ String pathToYaml(); + /** + * The Semgrep rule "id" field from the YAML. This is needed to disambiguate Semgrep results as we + * consolidate Semgrep rules into one scan. + */ String ruleId(); } diff --git a/languages/codemodder-semgrep-provider/src/test/java/io/codemodder/providers/sarif/semgrep/SemgrepRunnerTest.java b/languages/codemodder-semgrep-provider/src/test/java/io/codemodder/providers/sarif/semgrep/SemgrepRunnerTest.java index e1bbb2a9d..12c535a75 100644 --- a/languages/codemodder-semgrep-provider/src/test/java/io/codemodder/providers/sarif/semgrep/SemgrepRunnerTest.java +++ b/languages/codemodder-semgrep-provider/src/test/java/io/codemodder/providers/sarif/semgrep/SemgrepRunnerTest.java @@ -7,11 +7,13 @@ import com.contrastsecurity.sarif.Run; import com.contrastsecurity.sarif.SarifSchema210; import java.io.IOException; +import java.io.InputStream; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; +import java.nio.file.StandardCopyOption; import java.util.List; -import org.apache.commons.io.IOUtils; +import java.util.Objects; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; @@ -26,7 +28,9 @@ void it_runs_semgrep_and_produces_expected_sarif(@TempDir Path repositoryDir) th Files.write(javaFile, insecureRandomJavaClass.getBytes(StandardCharsets.UTF_8)); Path ruleFile = Files.createTempFile(repositoryDir, "example", ".yaml"); - Files.write(ruleFile, IOUtils.toByteArray(getClass().getResourceAsStream("/example.semgrep"))); + InputStream resourceAsStream = + Objects.requireNonNull(getClass().getResourceAsStream("/example.semgrep")); + Files.copy(resourceAsStream, ruleFile, StandardCopyOption.REPLACE_EXISTING); // run the scan SarifSchema210 sarif = new DefaultSemgrepRunner().run(List.of(ruleFile), repositoryDir); From ecf7bfaf672b666035a6a2f4d7aa0fa5a5669068 Mon Sep 17 00:00:00 2001 From: Arshan Dabirsiaghi Date: Tue, 14 Mar 2023 17:27:33 -0400 Subject: [PATCH 18/19] all feedback addressed --- languages/codemodder-common/build.gradle.kts | 6 - .../build.gradle.kts | 16 --- .../build.gradle.kts | 5 - .../sarif/semgrep/SemgrepModule.java | 37 ++++-- .../providers/sarif/semgrep/SemgrepScan.java | 2 +- .../semgrep/SemgrepJavaParserChangerTest.java | 94 ++++++++++++++ .../sarif/semgrep/SemgrepModuleTest.java | 118 ++++++++++++++++++ .../sarif/semgrep/implicit-yaml-path.yaml | 7 ++ .../other_dir/explicit-yaml-path.yaml | 7 ++ 9 files changed, 251 insertions(+), 41 deletions(-) create mode 100644 languages/codemodder-semgrep-provider/src/test/java/io/codemodder/providers/sarif/semgrep/SemgrepJavaParserChangerTest.java create mode 100644 languages/codemodder-semgrep-provider/src/test/java/io/codemodder/providers/sarif/semgrep/SemgrepModuleTest.java create mode 100644 languages/codemodder-semgrep-provider/src/test/resources/io/codemodder/providers/sarif/semgrep/implicit-yaml-path.yaml create mode 100644 languages/codemodder-semgrep-provider/src/test/resources/other_dir/explicit-yaml-path.yaml diff --git a/languages/codemodder-common/build.gradle.kts b/languages/codemodder-common/build.gradle.kts index 5f325990c..784055027 100644 --- a/languages/codemodder-common/build.gradle.kts +++ b/languages/codemodder-common/build.gradle.kts @@ -3,7 +3,6 @@ plugins { id("io.openpixee.codetl.base") id("io.openpixee.codetl.java-library") id("io.openpixee.codetl.maven-publish") - id("application") alias(libs.plugins.fileversioning) } @@ -13,11 +12,6 @@ java { } } -spotless { - java { - } -} - publishing { publications { register("maven") { diff --git a/languages/codemodder-default-codemods/build.gradle.kts b/languages/codemodder-default-codemods/build.gradle.kts index 1650b3b50..67aef3ede 100644 --- a/languages/codemodder-default-codemods/build.gradle.kts +++ b/languages/codemodder-default-codemods/build.gradle.kts @@ -3,7 +3,6 @@ plugins { id("io.openpixee.codetl.base") id("io.openpixee.codetl.java-library") id("io.openpixee.codetl.maven-publish") - id("application") alias(libs.plugins.fileversioning) } @@ -13,15 +12,6 @@ java { } } -application { - mainClass.set("io.codemodder.codemods.Runner") -} - -spotless { - java { - } -} - publishing { publications { register("maven") { @@ -32,12 +22,6 @@ publishing { } dependencies { -// implementation(libs.slf4j.api) -// implementation(libs.javaparser.core) -// implementation(libs.javaparser.symbolsolver.core) -// implementation(libs.javaparser.symbolsolver.logic) -// implementation(libs.javaparser.symbolsolver.model) -// implementation(project(":languages:codemodder-common")) implementation(project(":languages:codemodder-framework-java")) implementation(project(":languages:codemodder-semgrep-provider")) diff --git a/languages/codemodder-semgrep-provider/build.gradle.kts b/languages/codemodder-semgrep-provider/build.gradle.kts index 113afa5cb..f08a264ee 100644 --- a/languages/codemodder-semgrep-provider/build.gradle.kts +++ b/languages/codemodder-semgrep-provider/build.gradle.kts @@ -23,11 +23,6 @@ publishing { dependencies { compileOnly(libs.jetbrains.annotations) - implementation(libs.guice) - implementation(libs.contrast.sarif) - implementation(libs.java.security.toolkit) - implementation(libs.slf4j.api) - implementation(libs.javaparser.core) implementation(project(":languages:codemodder-common")) implementation(project(":languages:codemodder-framework-java")) diff --git a/languages/codemodder-semgrep-provider/src/main/java/io/codemodder/providers/sarif/semgrep/SemgrepModule.java b/languages/codemodder-semgrep-provider/src/main/java/io/codemodder/providers/sarif/semgrep/SemgrepModule.java index 4d75d3ac7..e0aef7f06 100644 --- a/languages/codemodder-semgrep-provider/src/main/java/io/codemodder/providers/sarif/semgrep/SemgrepModule.java +++ b/languages/codemodder-semgrep-provider/src/main/java/io/codemodder/providers/sarif/semgrep/SemgrepModule.java @@ -2,7 +2,6 @@ import com.contrastsecurity.sarif.SarifSchema210; import com.google.inject.AbstractModule; -import com.google.inject.Inject; import io.codemodder.Changer; import io.codemodder.RuleSarif; import java.io.IOException; @@ -10,15 +9,16 @@ import java.io.UncheckedIOException; import java.lang.reflect.Constructor; import java.lang.reflect.Parameter; -import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; +import java.nio.file.StandardCopyOption; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Objects; import java.util.stream.Collectors; -import org.apache.commons.io.IOUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** Responsible for binding Semgrep-related things. */ final class SemgrepModule extends AbstractModule { @@ -47,10 +47,7 @@ protected void configure() { Constructor[] constructors = codemodType.getDeclaredConstructors(); List> injectableConstructors = Arrays.stream(constructors) - .filter( - constructor -> - constructor.getAnnotation(Inject.class) != null - || constructor.getAnnotation(javax.inject.Inject.class) != null) + .filter(constructor -> constructor.getAnnotation(javax.inject.Inject.class) != null) .collect(Collectors.toUnmodifiableList()); // find all @SemgrepScan annotations in their parameters and batch them up for running @@ -69,7 +66,20 @@ protected void configure() { + RuleSarif.class.getSimpleName() + " types"); } - yamlClasspathPathsToRun.add(semgrepScanAnnotation.pathToYaml()); + String yamlPath = semgrepScanAnnotation.pathToYaml(); + if ("".equals(yamlPath)) { + yamlPath = + "/" + + codemodType.getPackageName().replace(".", "/") + + "/" + + semgrepScanAnnotation.ruleId() + + ".yaml"; + logger.info( + "Codemod {} didn't provide yaml path, assuming {}", + codemodType.getSimpleName(), + yamlPath); + } + yamlClasspathPathsToRun.add(yamlPath); toBind.add(semgrepScanAnnotation); } }); @@ -102,15 +112,16 @@ protected void configure() { * exception re-throwing but this is being used from a lambda and this shouldn't fail ever anyway. */ private Path saveClasspathResourceToTemp(final String yamlClasspathResourcePath) { - try (InputStream ruleInputStream = - getClass().getResource(yamlClasspathResourcePath).openStream()) { - String ruleYaml = IOUtils.toString(ruleInputStream, StandardCharsets.UTF_8); - ruleInputStream.close(); + try (InputStream ruleInputStream = getClass().getResourceAsStream(yamlClasspathResourcePath)) { Path semgrepRuleFile = Files.createTempFile("semgrep", ".yaml"); - Files.write(semgrepRuleFile, ruleYaml.getBytes(StandardCharsets.UTF_8)); + Objects.requireNonNull(ruleInputStream); + Files.copy(ruleInputStream, semgrepRuleFile, StandardCopyOption.REPLACE_EXISTING); + ruleInputStream.close(); return semgrepRuleFile; } catch (IOException e) { throw new UncheckedIOException("failed to write write yaml to disk", e); } } + + private static final Logger logger = LoggerFactory.getLogger(SemgrepModule.class); } diff --git a/languages/codemodder-semgrep-provider/src/main/java/io/codemodder/providers/sarif/semgrep/SemgrepScan.java b/languages/codemodder-semgrep-provider/src/main/java/io/codemodder/providers/sarif/semgrep/SemgrepScan.java index e29fcca57..30a56fec4 100644 --- a/languages/codemodder-semgrep-provider/src/main/java/io/codemodder/providers/sarif/semgrep/SemgrepScan.java +++ b/languages/codemodder-semgrep-provider/src/main/java/io/codemodder/providers/sarif/semgrep/SemgrepScan.java @@ -26,7 +26,7 @@ * file in /com/acme/codemods/my-rule.yaml, you would simply specify "my-rule.yaml" for this * value. */ - String pathToYaml(); + String pathToYaml() default ""; /** * The Semgrep rule "id" field from the YAML. This is needed to disambiguate Semgrep results as we diff --git a/languages/codemodder-semgrep-provider/src/test/java/io/codemodder/providers/sarif/semgrep/SemgrepJavaParserChangerTest.java b/languages/codemodder-semgrep-provider/src/test/java/io/codemodder/providers/sarif/semgrep/SemgrepJavaParserChangerTest.java new file mode 100644 index 000000000..570377879 --- /dev/null +++ b/languages/codemodder-semgrep-provider/src/test/java/io/codemodder/providers/sarif/semgrep/SemgrepJavaParserChangerTest.java @@ -0,0 +1,94 @@ +package io.codemodder.providers.sarif.semgrep; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.notNullValue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import com.contrastsecurity.sarif.Region; +import com.github.javaparser.ast.visitor.ModifierVisitor; +import com.google.inject.Guice; +import com.google.inject.Injector; +import io.codemodder.CodeDirectory; +import io.codemodder.Codemod; +import io.codemodder.CodemodInvocationContext; +import io.codemodder.FileWeavingContext; +import io.codemodder.ReviewGuidance; +import io.codemodder.RuleSarif; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardOpenOption; +import java.util.List; +import java.util.Optional; +import javax.inject.Inject; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +final class SemgrepJavaParserChangerTest { + + @Codemod( + author = "pixee", + id = "pixee-test:java/finds-stuff", + reviewGuidance = ReviewGuidance.MERGE_AFTER_CURSORY_REVIEW) + static class FindsStuffCodemod extends SemgrepJavaParserChanger { + private final RuleSarif ruleSarif; + + @Inject + FindsStuffCodemod( + @SemgrepScan( + pathToYaml = "/other_dir/explicit-yaml-path.yaml", + ruleId = "explicit-yaml-path") + RuleSarif ruleSarif) { + super(ruleSarif); + this.ruleSarif = ruleSarif; + } + + @Override + public ModifierVisitor createVisitor( + final CodemodInvocationContext context, final List regions) { + return new ModifierVisitor<>(); + } + } + + @Test + void it_gives_visitor_when_findings_present(@TempDir Path tmpDir) throws IOException { + String javaCode = "class Foo { \n\n Object a = new Stuff(); \n }"; + Path javaFile = writeJavaFile(tmpDir, javaCode); + + SemgrepModule module = new SemgrepModule(tmpDir, List.of(FindsStuffCodemod.class)); + Injector injector = Guice.createInjector(module); + FindsStuffCodemod instance = injector.getInstance(FindsStuffCodemod.class); + RuleSarif ruleSarif = instance.ruleSarif; + assertThat(ruleSarif, is(notNullValue())); + assertThat(ruleSarif.getRegionsFromResultsByRule(javaFile).size(), is(1)); + + CodeDirectory directory = mock(CodeDirectory.class); + when(directory.asPath()).thenReturn(tmpDir); + + CodemodInvocationContext context = mock(CodemodInvocationContext.class); + when(context.codemodId()).thenReturn("pixee-test:java/finds-stuff"); + when(context.path()).thenReturn(javaFile); + when(context.codeDirectory()).thenReturn(directory); + when(context.changeRecorder()).thenReturn(mock(FileWeavingContext.class)); + + // we should get a visitor for this file + Optional> modifierVisitor = + instance.createModifierVisitor(context); + assertThat(modifierVisitor.isPresent(), is(true)); + + // now change it so the path is to a file that should not find results for + when(context.path()).thenReturn(javaFile.getParent()); + modifierVisitor = instance.createModifierVisitor(context); + assertThat(modifierVisitor.isEmpty(), is(true)); + } + + private Path writeJavaFile(final Path tmpDir, final String javaCode) throws IOException { + Path javaFile = Files.createTempFile(tmpDir, "HasStuff", ".java"); + Files.write( + javaFile, javaCode.getBytes(StandardCharsets.UTF_8), StandardOpenOption.TRUNCATE_EXISTING); + return javaFile; + } +} diff --git a/languages/codemodder-semgrep-provider/src/test/java/io/codemodder/providers/sarif/semgrep/SemgrepModuleTest.java b/languages/codemodder-semgrep-provider/src/test/java/io/codemodder/providers/sarif/semgrep/SemgrepModuleTest.java new file mode 100644 index 000000000..757ffd7ee --- /dev/null +++ b/languages/codemodder-semgrep-provider/src/test/java/io/codemodder/providers/sarif/semgrep/SemgrepModuleTest.java @@ -0,0 +1,118 @@ +package io.codemodder.providers.sarif.semgrep; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.notNullValue; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import com.contrastsecurity.sarif.Region; +import com.github.javaparser.ast.visitor.ModifierVisitor; +import com.google.inject.Guice; +import com.google.inject.Injector; +import io.codemodder.Changer; +import io.codemodder.Codemod; +import io.codemodder.CodemodInvocationContext; +import io.codemodder.FileWeavingContext; +import io.codemodder.ReviewGuidance; +import io.codemodder.RuleSarif; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardOpenOption; +import java.util.HashMap; +import java.util.List; +import javax.inject.Inject; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +/** Tests binding and running while binding. */ +final class SemgrepModuleTest { + + @Codemod( + author = "pixee", + id = "pixee-test:java/implicit-yaml", + reviewGuidance = ReviewGuidance.MERGE_AFTER_CURSORY_REVIEW) + static class UsesImplicitYamlPath implements Changer { + private final RuleSarif ruleSarif; + + @Inject + UsesImplicitYamlPath(@SemgrepScan(ruleId = "implicit-yaml-path") RuleSarif ruleSarif) { + this.ruleSarif = ruleSarif; + } + } + + @Codemod( + author = "pixee", + id = "pixee-test:java/explicit-yaml-test", + reviewGuidance = ReviewGuidance.MERGE_AFTER_CURSORY_REVIEW) + static class UsesExplicitYamlPath extends SemgrepJavaParserChanger { + private final RuleSarif ruleSarif; + + @Inject + UsesExplicitYamlPath( + @SemgrepScan( + pathToYaml = "/other_dir/explicit-yaml-path.yaml", + ruleId = "explicit-yaml-path") + RuleSarif ruleSarif) { + super(ruleSarif); + this.ruleSarif = ruleSarif; + } + + @Override + public ModifierVisitor createVisitor( + final CodemodInvocationContext context, final List regions) { + return new ModifierVisitor<>(); + } + } + + @Codemod( + author = "pixee", + id = "pixee-test:java/incorrect-binding-type", + reviewGuidance = ReviewGuidance.MERGE_AFTER_CURSORY_REVIEW) + static class BindsToIncorrectObject implements Changer { + @Inject + BindsToIncorrectObject( + @SemgrepScan(ruleId = "incorrect-binding-type") HashMap nonSarifObject) {} + } + + @Test + void it_fails_when_injecting_nonsarif_type(@TempDir Path tmpDir) { + assertThrows( + IllegalArgumentException.class, + () -> { + new SemgrepModule(tmpDir, List.of(BindsToIncorrectObject.class)).configure(); + }); + } + + @Test + void it_works_with_implicit_yaml_path(@TempDir Path tmpDir) throws IOException { + String javaCode = "class Foo { \n Object a = new Thing(); \n }"; + Path javaFile = Files.createTempFile(tmpDir, "HasThing", ".java"); + Files.write( + javaFile, javaCode.getBytes(StandardCharsets.UTF_8), StandardOpenOption.TRUNCATE_EXISTING); + SemgrepModule module = new SemgrepModule(tmpDir, List.of(UsesImplicitYamlPath.class)); + Injector injector = Guice.createInjector(module); + UsesImplicitYamlPath instance = injector.getInstance(UsesImplicitYamlPath.class); + + RuleSarif ruleSarif = instance.ruleSarif; + assertThat(ruleSarif, is(notNullValue())); + List regions = ruleSarif.getRegionsFromResultsByRule(javaFile); + assertThat(regions.size(), is(1)); + } + + @Test + void it_works_with_explicit_yaml_path(@TempDir Path tmpDir) throws IOException { + String javaCode = "class Foo { \n\n Object a = new Stuff(); \n }"; + Path javaFile = Files.createTempFile(tmpDir, "HasStuff", ".java"); + Files.write( + javaFile, javaCode.getBytes(StandardCharsets.UTF_8), StandardOpenOption.TRUNCATE_EXISTING); + + SemgrepModule module = new SemgrepModule(tmpDir, List.of(UsesExplicitYamlPath.class)); + Injector injector = Guice.createInjector(module); + UsesExplicitYamlPath instance = injector.getInstance(UsesExplicitYamlPath.class); + RuleSarif ruleSarif = instance.ruleSarif; + assertThat(ruleSarif, is(notNullValue())); + assertThat(ruleSarif.getRegionsFromResultsByRule(javaFile).size(), is(1)); + } +} diff --git a/languages/codemodder-semgrep-provider/src/test/resources/io/codemodder/providers/sarif/semgrep/implicit-yaml-path.yaml b/languages/codemodder-semgrep-provider/src/test/resources/io/codemodder/providers/sarif/semgrep/implicit-yaml-path.yaml new file mode 100644 index 000000000..4afcca2a9 --- /dev/null +++ b/languages/codemodder-semgrep-provider/src/test/resources/io/codemodder/providers/sarif/semgrep/implicit-yaml-path.yaml @@ -0,0 +1,7 @@ +rules: + - id: implicit-yaml-path + pattern: new Thing() + message: Finds nothing useful + languages: + - java + severity: WARNING diff --git a/languages/codemodder-semgrep-provider/src/test/resources/other_dir/explicit-yaml-path.yaml b/languages/codemodder-semgrep-provider/src/test/resources/other_dir/explicit-yaml-path.yaml new file mode 100644 index 000000000..6671d2229 --- /dev/null +++ b/languages/codemodder-semgrep-provider/src/test/resources/other_dir/explicit-yaml-path.yaml @@ -0,0 +1,7 @@ +rules: + - id: explicit-yaml-path + pattern: new Stuff() + message: Finds nothing useful + languages: + - java + severity: WARNING From dddcb2a0915a7cb69fcf15747064aa9021c25c69 Mon Sep 17 00:00:00 2001 From: Arshan Dabirsiaghi Date: Tue, 14 Mar 2023 17:39:22 -0400 Subject: [PATCH 19/19] did the flatmap thing --- .../sarif/semgrep/SemgrepModule.java | 63 +++++++++---------- 1 file changed, 31 insertions(+), 32 deletions(-) diff --git a/languages/codemodder-semgrep-provider/src/main/java/io/codemodder/providers/sarif/semgrep/SemgrepModule.java b/languages/codemodder-semgrep-provider/src/main/java/io/codemodder/providers/sarif/semgrep/SemgrepModule.java index e0aef7f06..d3385a4e0 100644 --- a/languages/codemodder-semgrep-provider/src/main/java/io/codemodder/providers/sarif/semgrep/SemgrepModule.java +++ b/languages/codemodder-semgrep-provider/src/main/java/io/codemodder/providers/sarif/semgrep/SemgrepModule.java @@ -17,6 +17,7 @@ import java.util.List; import java.util.Objects; import java.util.stream.Collectors; +import java.util.stream.Stream; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -51,38 +52,36 @@ protected void configure() { .collect(Collectors.toUnmodifiableList()); // find all @SemgrepScan annotations in their parameters and batch them up for running - injectableConstructors.forEach( - constructor -> { - Parameter[] parameters = constructor.getParameters(); - Arrays.stream(parameters) - .forEach( - parameter -> { - SemgrepScan semgrepScanAnnotation = - parameter.getAnnotation(SemgrepScan.class); - if (semgrepScanAnnotation != null) { - if (!RuleSarif.class.equals(parameter.getType())) { - throw new IllegalArgumentException( - "Can only inject semgrep results into " - + RuleSarif.class.getSimpleName() - + " types"); - } - String yamlPath = semgrepScanAnnotation.pathToYaml(); - if ("".equals(yamlPath)) { - yamlPath = - "/" - + codemodType.getPackageName().replace(".", "/") - + "/" - + semgrepScanAnnotation.ruleId() - + ".yaml"; - logger.info( - "Codemod {} didn't provide yaml path, assuming {}", - codemodType.getSimpleName(), - yamlPath); - } - yamlClasspathPathsToRun.add(yamlPath); - toBind.add(semgrepScanAnnotation); - } - }); + List parameters = + injectableConstructors.stream() + .flatMap(constructor -> Stream.of(constructor.getParameters())) + .collect(Collectors.toUnmodifiableList()); + parameters.forEach( + parameter -> { + SemgrepScan semgrepScanAnnotation = parameter.getAnnotation(SemgrepScan.class); + if (semgrepScanAnnotation != null) { + if (!RuleSarif.class.equals(parameter.getType())) { + throw new IllegalArgumentException( + "Can only inject semgrep results into " + + RuleSarif.class.getSimpleName() + + " types"); + } + String yamlPath = semgrepScanAnnotation.pathToYaml(); + if ("".equals(yamlPath)) { + yamlPath = + "/" + + codemodType.getPackageName().replace(".", "/") + + "/" + + semgrepScanAnnotation.ruleId() + + ".yaml"; + logger.info( + "Codemod {} didn't provide yaml path, assuming {}", + codemodType.getSimpleName(), + yamlPath); + } + yamlClasspathPathsToRun.add(yamlPath); + toBind.add(semgrepScanAnnotation); + } }); // copy the yaml out of the classpath onto disk so semgrep can use them