Skip to content

Commit d2676e7

Browse files
authored
Fixes issue where multiple mixed type injections were only fixed partially (#434)
1 parent 42f1f73 commit d2676e7

File tree

9 files changed

+172
-22
lines changed

9 files changed

+172
-22
lines changed

core-codemods/src/test/java/io/codemodder/codemods/SonarSQLInjectionCodemodTest.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,4 +33,13 @@ class SupportedTest implements CodemodTestMixin {}
3333
expectingFixesAtLines = {19, 25, 33, 40},
3434
dependencies = {})
3535
class SupportedTableInjectionTest implements CodemodTestMixin {}
36+
37+
@Nested
38+
@Metadata(
39+
codemodType = SonarSQLInjectionCodemod.class,
40+
testResourceDir = "sonar-sql-injection-s2077/supportedMixedInjections",
41+
renameTestFile = "core-codemods/src/main/java/io/codemodder/codemods/SQLTestMixed.java",
42+
expectingFixesAtLines = {21},
43+
dependencies = {})
44+
class SupportedMixedInjectionTest implements CodemodTestMixin {}
3645
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
package io.codemodder.codemods;
2+
3+
import java.sql.Connection;
4+
import java.sql.ResultSet;
5+
import java.sql.SQLException;
6+
import java.sql.Statement;
7+
import java.sql.PreparedStatement;
8+
import java.util.Scanner;
9+
import java.util.regex.Pattern;
10+
11+
public final class SQLTestMixed {
12+
13+
private Connection conn;
14+
15+
public ResultSet simpleIndirect() throws SQLException {
16+
Scanner scanner = new Scanner(System.in);
17+
String input = scanner.nextLine();
18+
String sql = "SELECT * FROM " + validateTableName(input + "") + " where name=?" ;
19+
PreparedStatement stmt = conn.prepareStatement(sql);
20+
stmt.setString(1, scanner.nextLine());
21+
return stmt.execute();
22+
}
23+
24+
String validateTableName(final String tablename) {
25+
Pattern regex = Pattern.compile("[a-zA-Z0-9_]+(.[a-zA-Z0-9_]+)?");
26+
if (!regex.matcher(tablename).matches()) {
27+
throw new SecurityException("Supplied table name contains non-alphanumeric characters");
28+
}
29+
return tablename;
30+
}
31+
32+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package io.codemodder.codemods;
2+
3+
import java.sql.Connection;
4+
import java.sql.ResultSet;
5+
import java.sql.SQLException;
6+
import java.sql.Statement;
7+
import java.sql.PreparedStatement;
8+
import java.util.Scanner;
9+
import java.util.regex.Pattern;
10+
11+
public final class SQLTestMixed {
12+
13+
private Connection conn;
14+
15+
public ResultSet simpleIndirect() throws SQLException {
16+
Scanner scanner = new Scanner(System.in);
17+
String input = scanner.nextLine();
18+
String input2 = scanner.nextLine();
19+
String sql = "SELECT * FROM " + input + " where name='" + input2 + "'" ;
20+
Statement stmt = conn.createStatement();
21+
return stmt.executeQuery(sql);
22+
}
23+
24+
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
{
2+
"paging": {
3+
"pageIndex": 1,
4+
"pageSize": 100,
5+
"total": 1
6+
},
7+
"hotspots": [
8+
{
9+
"key": "AZEIpASKc7kK4RXktkeh",
10+
"component": "pixee_codemodder-java:core-codemods/src/main/java/io/codemodder/codemods/SQLTestMixed.java",
11+
"project": "pixee_codemodder-java",
12+
"securityCategory": "sql-injection",
13+
"vulnerabilityProbability": "HIGH",
14+
"status": "TO_REVIEW",
15+
"line": 21,
16+
"message": "Make sure using a dynamically formatted SQL query is safe here.",
17+
"creationDate": "2024-07-31T13:53:37+0200",
18+
"updateDate": "2024-07-31T13:53:37+0200",
19+
"textRange": {
20+
"startLine": 21,
21+
"endLine": 21,
22+
"startOffset": 33,
23+
"endOffset": 36
24+
},
25+
"flows": [],
26+
"ruleKey": "java:S2077"
27+
}
28+
],
29+
"components": [
30+
{
31+
"organization": "pixee",
32+
"key": "pixee_codemodder-java",
33+
"qualifier": "TRK",
34+
"name": "codemodder-java",
35+
"longName": "codemodder-java",
36+
"pullRequest": "434"
37+
},
38+
{
39+
"organization": "pixee",
40+
"key": "pixee_codemodder-java:core-codemods/src/main/java/io/codemodder/codemods/SQLTestMixed.java",
41+
"qualifier": "FIL",
42+
"name": "SQLTestMixed.java",
43+
"longName": "core-codemods/src/main/java/io/codemodder/codemods/SQLTestMixed.java",
44+
"path": "core-codemods/src/main/java/io/codemodder/codemods/SQLTestMixed.java",
45+
"pullRequest": "434"
46+
}
47+
]
48+
}

framework/codemodder-base/src/main/java/io/codemodder/ast/LinearizedStringExpression.java

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,13 @@
66
import com.github.javaparser.ast.expr.Expression;
77
import com.github.javaparser.ast.expr.NameExpr;
88
import com.github.javaparser.resolution.types.ResolvedType;
9-
import java.util.*;
9+
import java.util.ArrayDeque;
10+
import java.util.Deque;
11+
import java.util.IdentityHashMap;
12+
import java.util.List;
13+
import java.util.Map;
14+
import java.util.Objects;
15+
import java.util.Optional;
1016
import java.util.stream.Collectors;
1117
import java.util.stream.Stream;
1218

@@ -20,7 +26,7 @@ public final class LinearizedStringExpression {
2026
private final Deque<Expression> linearized;
2127

2228
public LinearizedStringExpression(final Expression expression) {
23-
this.resolvedExpressions = new HashMap<>();
29+
this.resolvedExpressions = new IdentityHashMap<>();
2430
this.root = expression;
2531
this.linearized = linearize(expression).collect(Collectors.toCollection(ArrayDeque::new));
2632
}

framework/codemodder-base/src/main/java/io/codemodder/remediation/sqlinjection/DefaultJavaParserSQLInjectionRemediatorStrategy.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
final class DefaultJavaParserSQLInjectionRemediatorStrategy
2626
implements JavaParserSQLInjectionRemediatorStrategy {
2727

28-
private Map<Predicate<MethodCallExpr>, Predicate<MethodCallExpr>> remediationStrategies;
28+
private final Map<Predicate<MethodCallExpr>, Predicate<MethodCallExpr>> remediationStrategies;
2929

3030
/**
3131
* Builds a strategy from a matcher-fixer pair. A matcher is a predicate that matches the call,

framework/codemodder-base/src/main/java/io/codemodder/remediation/sqlinjection/JavaParserSQLInjectionRemediatorStrategy.java

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -35,9 +35,5 @@ <T> CodemodFileScanningResult remediateAll(
3535
/** A default implementation that should be used in all non-test scenarios. */
3636
JavaParserSQLInjectionRemediatorStrategy DEFAULT =
3737
new DefaultJavaParserSQLInjectionRemediatorStrategy(
38-
Map.of(
39-
SQLParameterizer::isSupportedJdbcMethodCall,
40-
SQLParameterizerWithCleanup::checkAndFix,
41-
SQLTableInjectionFilterTransform::matchCall,
42-
SQLTableInjectionFilterTransform::fix));
38+
Map.of(SQLInjectionFixComposer::match, SQLInjectionFixComposer::checkAndFix));
4339
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
package io.codemodder.remediation.sqlinjection;
2+
3+
import com.github.javaparser.ast.expr.MethodCallExpr;
4+
5+
/** Composes several transformations related to SQL injections. */
6+
public final class SQLInjectionFixComposer {
7+
8+
private SQLInjectionFixComposer() {}
9+
10+
/**
11+
* Given a {@link MethodCallExpr} related to executing JDBC API SQL queries (i.e.
12+
* prepareStatement(), executeQuery(), etc.), parameterize data injections or add a validation
13+
* step for structural injections.
14+
*/
15+
public static boolean checkAndFix(final MethodCallExpr methodCallExpr) {
16+
// First, check if any data injection fixes apply
17+
var maybeFixed = new SQLParameterizer(methodCallExpr).checkAndFix();
18+
if (maybeFixed.isPresent()) {
19+
// If yes, execute cleanup steps and check if any table injection remains.
20+
SQLParameterizerWithCleanup.cleanup(maybeFixed.get());
21+
SQLTableInjectionFilterTransform.findAndFix(maybeFixed.get());
22+
return true;
23+
// If not, try the table injection only
24+
} else {
25+
return SQLTableInjectionFilterTransform.findAndFix(methodCallExpr);
26+
}
27+
}
28+
29+
/**
30+
* Check if the {@link MethodCallExpr} is a JDBC API query method that is a target of a SQL
31+
* injection transformation.
32+
*/
33+
public static boolean match(final MethodCallExpr methodCallExpr) {
34+
return SQLParameterizer.isSupportedJdbcMethodCall(methodCallExpr)
35+
|| SQLTableInjectionFilterTransform.matchCall(methodCallExpr);
36+
}
37+
}

framework/codemodder-base/src/main/java/io/codemodder/remediation/sqlinjection/SQLParameterizerWithCleanup.java

Lines changed: 12 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -10,21 +10,19 @@ private SQLParameterizerWithCleanup() {}
1010

1111
public static boolean checkAndFix(final MethodCallExpr methodCallExpr) {
1212
var maybeFixed = new SQLParameterizer(methodCallExpr).checkAndFix();
13-
if (maybeFixed.isPresent()) {
14-
// Cleanup
15-
var maybeMethodDecl = methodCallExpr.findAncestor(CallableDeclaration.class);
13+
maybeFixed.ifPresent(call -> cleanup(call));
14+
return maybeFixed.isPresent();
15+
}
16+
17+
public static void cleanup(final MethodCallExpr pstmtCall) {
18+
var maybeMethodDecl = pstmtCall.findAncestor(CallableDeclaration.class);
1619

17-
// Remove concatenation with empty strings e.g "first" + "" -> "first";
18-
maybeMethodDecl.ifPresent(ASTTransforms::removeEmptyStringConcatenation);
19-
// Remove potential unused variables left after transform
20-
maybeMethodDecl.ifPresent(md -> ASTTransforms.removeUnusedLocalVariables(md));
20+
// Remove concatenation with empty strings e.g "first" + "" -> "first";
21+
maybeMethodDecl.ifPresent(ASTTransforms::removeEmptyStringConcatenation);
22+
// Remove potential unused variables left after transform
23+
maybeMethodDecl.ifPresent(md -> ASTTransforms.removeUnusedLocalVariables(md));
2124

22-
// Merge concatenated literals, e.g. "first" + " and second" -> "first and second"
23-
maybeFixed
24-
.flatMap(mce -> mce.getArguments().getFirst())
25-
.ifPresent(ASTTransforms::mergeConcatenatedLiterals);
26-
return true;
27-
}
28-
return false;
25+
// Merge concatenated literals, e.g. "first" + " and second" -> "first and second"
26+
pstmtCall.getArguments().getFirst().ifPresent(ASTTransforms::mergeConcatenatedLiterals);
2927
}
3028
}

0 commit comments

Comments
 (0)