Deeps-2005/java-ssl-scanner
0
1import com.github.javaparser.StaticJavaParser;
2import com.github.javaparser.ast.CompilationUnit;
3import com.github.javaparser.ast.body.*;
4import com.github.javaparser.ast.expr.*;
5import com.github.javaparser.ast.stmt.*;
6import com.github.javaparser.ast.visitor.ModifierVisitor;
7import com.github.javaparser.ast.NodeList;
8import com.github.javaparser.ast.type.ClassOrInterfaceType;
9import com.github.javaparser.ast.type.Type;
10import com.github.javaparser.ast.comments.LineComment;
11import com.github.javaparser.ast.comments.BlockComment;
12import com.github.javaparser.ast.Node;
13
14import java.io.File;
15import java.io.FileInputStream;
16import java.io.IOException;
17import java.util.Arrays;
18import java.util.List;
19import java.util.Set;
20import java.util.HashSet;
21import java.util.ArrayList;
22import java.util.Comparator;
23import java.util.Map;
24import java.util.LinkedHashMap;
25
26public class AutoPatcher {
27
28 // Lists for quick lookup, shared across methods to reduce object creation
29 private static final List<String> INSECURE_PROTOCOLS = Arrays.asList("sslv2", "sslv3", "tlsv1", "tlsv1.0", "tlsv1.1");
30 private static final List<String> WEAK_CIPHER_KEYWORDS = Arrays.asList("null", "anon", "export", "rc4", "des", "md5");
31 private static final List<String> RECOMMENDED_PROTOCOLS = Arrays.asList("TLSv1.2", "TLSv1.3");
32 private static final List<String> RECOMMENDED_CIPHER_SUITES = Arrays.asList(
33 "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384",
34 "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384",
35 "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256",
36 "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256"
37 );
38
39 // NEW: Cryptography and XML related constants
40 private static final List<String> WEAK_HASHING_ALGORITHMS = Arrays.asList("md5", "sha-1");
41 private static final Set<String> XML_FACTORIES = new HashSet<>(Arrays.asList(
42 "DocumentBuilderFactory", "SAXParserFactory", "XMLInputFactory"
43 ));
44 // Pattern for hardcoded sensitive strings (password, key, secret, salt, token, auth)
45 private static final String SENSITIVE_STRING_PATTERN = ".*(password|pass|secret|key|pwd|salt|token|auth).*";
46
47
48 // Using LinkedHashMap to maintain insertion order for more readable logs
49 private static Map<Integer, String> patchLogs = new LinkedHashMap<>(); // Store logs here
50
51 // Consolidated strong cipher/protocol string literals for parsing
52 private static final String STRONG_CIPHERS_ARRAY_EXPR =
53 "new String[]{\"TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256\", " +
54 "\"TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384\"}";
55
56 private static final String STRONG_PROTOCOLS_ARRAY_EXPR =
57 "new String[]{\"TLSv1.2\", \"TLSv1.3\"}";
58
59
60 public static void main(String[] args) throws Exception {
61 if (args.length == 0) {
62 System.err.println("Usage: java AutoPatcher <JavaFile>");
63 System.err.println("This tool attempts to automatically patch known SSL/JSSE vulnerabilities.");
64 System.err.println("The patched code will be printed to standard output.");
65 return;
66 }
67
68 String filePath = args[0];
69 CompilationUnit cu;
70 try (FileInputStream in = new FileInputStream(filePath)) {
71 cu = StaticJavaParser.parse(in);
72 } catch (IOException e) {
73 System.err.println("Error reading file: " + filePath + " - " + e.getMessage());
74 return;
75 }
76
77 System.err.println("Attempting to patch: " + filePath);
78
79 // Apply patches using the visitor
80 cu.accept(new SecurityPatchVisitor(), null);
81
82 // Print ONLY the patched code to standard output (stdout)
83 System.out.println(cu.toString());
84
85 // Print patch logs to standard error (stderr), wrapped in markers
86 System.err.println("--- PATCH LOG START ---");
87 // Sort logs by line number before printing for consistent output
88 List<Map.Entry<Integer, String>> sortedLogs = new ArrayList<>(patchLogs.entrySet());
89 sortedLogs.sort(Comparator.comparingInt(Map.Entry::getKey));
90
91 for (Map.Entry<Integer, String> entry : sortedLogs) {
92 System.err.println("Line " + entry.getKey() + ": " + entry.getValue());
93 }
94 System.err.println("--- PATCH LOG END ---");
95 }
96
97 private static class SecurityPatchVisitor extends ModifierVisitor<Void> {
98
99 /**
100 * Helper method to record patch logs.
101 * @param line The line number where the patch was applied.
102 * @param message A description of the patch.
103 */
104 private void logPatch(int line, String message) {
105 patchLogs.put(line, message);
106 }
107
108 /**
109 * Visits MethodCallExpr nodes to apply patches related to method calls.
110 * This includes system property settings, protocol enabling, hostname verification,
111 * keystore password loading, weak hashing algorithms, and XML factory instantiation.
112 *
113 * @param mce The MethodCallExpr node being visited.
114 * @param arg A generic argument (not used here).
115 * @return The modified MethodCallExpr (or null if the node is removed).
116 */
117 @Override
118 public MethodCallExpr visit(MethodCallExpr mce, Void arg) {
119 super.visit(mce, arg); // Call super to ensure full traversal and allow nested modifications
120
121 int line = mce.getBegin().map(p -> p.line).orElse(-1);
122
123 // 1. Patch: Remove debug logging and TLS renegotiation system properties
124 if (mce.getNameAsString().equals("setProperty") &&
125 mce.getArguments().size() == 2) {
126 Expression arg0 = mce.getArgument(0);
127 if (arg0.isStringLiteralExpr()) {
128 String key = arg0.asStringLiteralExpr().getValue();
129 if (key.equals("javax.net.debug") || key.equals("com.ibm.jsse2.renegotiate") || key.equals("jdk.tls.rejectClientInitiatedRenegotiation")) {
130 logPatch(line, "Removed System.setProperty(\"" + key + "\", ...) for security.");
131 return null; // Removing the method call expression
132 }
133 }
134 }
135
136 // 2. Patch: Insecure HostnameVerifier (lambda or anonymous class always returns true)
137 if (mce.getNameAsString().equals("setDefaultHostnameVerifier") &&
138 mce.getArguments().size() == 1) {
139 Expression argExpr = mce.getArgument(0);
140 boolean patched = false;
141
142 if (argExpr.isLambdaExpr()) {
143 LambdaExpr lambda = argExpr.asLambdaExpr();
144 if ((lambda.getBody().isExpressionStmt() && lambda.getBody().asExpressionStmt().getExpression().isBooleanLiteralExpr() &&
145 lambda.getBody().asExpressionStmt().getExpression().asBooleanLiteralExpr().getValue()) ||
146 (lambda.getBody().isBlockStmt() && lambda.getBody().asBlockStmt().getStatements().stream()
147 .filter(stmt -> stmt instanceof ReturnStmt)
148 .map(stmt -> (ReturnStmt) stmt)
149 .anyMatch(returnStmt -> returnStmt.getExpression().isPresent() && returnStmt.getExpression().get().isBooleanLiteralExpr() &&
150 returnStmt.getExpression().get().asBooleanLiteralExpr().getValue()))) {
151 // Replace with a safer placeholder that requires manual config
152 mce.setArgument(0, StaticJavaParser.parseExpression("new javax.net.ssl.HostnameVerifier() {\n" +
153 " @Override\n" +
154 " public boolean verify(String hostname, javax.net.ssl.SSLSession session) {\n" +
155 " // AUTO-PATCH: Manual review required. Implement strict hostname verification here.\n" +
156 " // Example: return hostname.equals(\"your.secure.domain.com\");\n" +
157 " return false; // Default to false for security until reviewed\n" +
158 " }\n" +
159 "}"));
160 patched = true;
161 }
162 } else if (argExpr.isObjectCreationExpr()) {
163 ObjectCreationExpr oce = argExpr.asObjectCreationExpr();
164 if (oce.getType().getNameAsString().equals("HostnameVerifier") && oce.getAnonymousClassBody().isPresent()) {
165 if (oce.getAnonymousClassBody().get().stream()
166 .filter(bodyDecl -> bodyDecl instanceof MethodDeclaration)
167 .map(bodyDecl -> (MethodDeclaration) bodyDecl)
168 .filter(md -> md.getNameAsString().equals("verify") && md.getBody().isPresent())
169 .anyMatch(md -> md.getBody().get().getStatements().isEmpty() ||
170 md.getBody().get().getStatements().stream()
171 .filter(stmt -> stmt instanceof ReturnStmt)
172 .map(stmt -> (ReturnStmt) stmt)
173 .anyMatch(returnStmt -> returnStmt.getExpression().isPresent() && returnStmt.getExpression().get().isBooleanLiteralExpr() &&
174 returnStmt.getExpression().get().asBooleanLiteralExpr().getValue()))) {
175 // Replace with a safer placeholder that requires manual config
176 mce.setArgument(0, StaticJavaParser.parseExpression("new javax.net.ssl.HostnameVerifier() {\n" +
177 " @Override\n" +
178 " public boolean verify(String hostname, javax.net.ssl.SSLSession session) {\n" +
179 " // AUTO-PATCH: Manual review required. Implement strict hostname verification here.\n" +
180 " // Example: return hostname.equals(\"your.secure.domain.com\");\n" +
181 " return false; // Default to false for security until reviewed\n" +
182 " }\n" +
183 "}"));
184 patched = true;
185 }
186 }
187 }
188 if (patched) {
189 logPatch(line, "Insecure HostnameVerifier replaced with secure placeholder requiring manual review.");
190 }
191 }
192
193 // 3. Patch: Hardcoded password passed to keystore load()
194 if (mce.getNameAsString().equals("load") &&
195 mce.getScope().isPresent() &&
196 mce.getScope().get().toString().contains("KeyStore") &&
197 mce.getArguments().size() == 2 &&
198 (mce.getArgument(1).isCharLiteralExpr() || mce.getArgument(1).isStringLiteralExpr())) {
199 logPatch(line, "Hardcoded password in KeyStore.load() replaced with environment variable lookup.");
200 mce.setArgument(1, StaticJavaParser.parseExpression("System.getenv(\"KEYSTORE_PASSWORD\").toCharArray()"));
201 }
202
203 // 4. Patch: Use of outdated/weak SSL/TLS protocols in SSLContext.getInstance()
204 if (mce.getNameAsString().equals("getInstance") &&
205 mce.getScope().isPresent() &&
206 mce.getScope().get().toString().contains("SSLContext") &&
207 mce.getArguments().size() >= 1 &&
208 mce.getArgument(0).isStringLiteralExpr()) {
209 String protocol = mce.getArgument(0).asStringLiteralExpr().getValue().toLowerCase();
210 if (INSECURE_PROTOCOLS.contains(protocol)) {
211 logPatch(line, "Insecure SSLContext protocol '" + protocol.toUpperCase() + "' changed to 'TLSv1.2'.");
212 mce.setArgument(0, StaticJavaParser.parseExpression("\"TLSv1.2\""));
213 }
214 }
215
216 // 5. Patch: Enabling weak/outdated protocols via setEnabledProtocols()
217 if (mce.getNameAsString().equals("setEnabledProtocols") &&
218 mce.getScope().isPresent() &&
219 (mce.getScope().get().toString().contains("SSLSocket") || mce.getScope().get().toString().contains("SSLEngine"))) {
220
221 Expression protocolsArg = mce.getArgument(0);
222 boolean shouldPatch = false;
223
224 if (protocolsArg.isArrayInitializerExpr()) {
225 ArrayInitializerExpr init = protocolsArg.asArrayInitializerExpr();
226 for (Expression expr : init.getValues()) {
227 if (expr.isStringLiteralExpr()) {
228 String protocol = expr.asStringLiteralExpr().getValue().toLowerCase();
229 if (INSECURE_PROTOCOLS.contains(protocol)) {
230 shouldPatch = true;
231 break;
232 }
233 }
234 }
235 } else if (protocolsArg.isStringLiteralExpr()) {
236 String protocol = protocolsArg.asStringLiteralExpr().getValue().toLowerCase();
237 if (INSECURE_PROTOCOLS.contains(protocol)) {
238 shouldPatch = true;
239 }
240 }
241
242 if (shouldPatch) {
243 logPatch(line, "setEnabledProtocols() to use only 'TLSv1.2' and 'TLSv1.3'.");
244 mce.setArgument(0, StaticJavaParser.parseExpression(STRONG_PROTOCOLS_ARRAY_EXPR));
245 }
246 }
247
248 // 6. Patch: Enabling weak CIPHER SUITES via setEnabledCipherSuites()
249 if (mce.getNameAsString().equals("setEnabledCipherSuites") &&
250 mce.getScope().isPresent() &&
251 (mce.getScope().get().toString().contains("SSLSocket") || mce.getScope().get().toString().contains("SSLEngine"))) {
252
253 Expression ciphersArg = mce.getArgument(0);
254 boolean shouldPatch = false;
255
256 if (ciphersArg.isArrayInitializerExpr()) {
257 ArrayInitializerExpr init = ciphersArg.asArrayInitializerExpr();
258 for (Expression expr : init.getValues()) {
259 if (expr.isStringLiteralExpr()) {
260 String cipherSuite = expr.asStringLiteralExpr().getValue().toLowerCase();
261 if (WEAK_CIPHER_KEYWORDS.stream().anyMatch(cipherSuite::contains)) {
262 shouldPatch = true;
263 break;
264 }
265 }
266 }
267 } else if (ciphersArg.isStringLiteralExpr()) {
268 String cipherSuite = ciphersArg.asStringLiteralExpr().getValue().toLowerCase();
269 if (WEAK_CIPHER_KEYWORDS.stream().anyMatch(cipherSuite::contains)) {
270 shouldPatch = true;
271 }
272 }
273
274 if (shouldPatch) {
275 logPatch(line, "setEnabledCipherSuites() to use strong default cipher suites.");
276 mce.setArgument(0, StaticJavaParser.parseExpression(STRONG_CIPHERS_ARRAY_EXPR));
277 }
278 }
279
280 // NEW PATCH: Weak Hashing Algorithms (MD5, SHA-1)
281 if (mce.getNameAsString().equals("getInstance") &&
282 mce.getScope().isPresent() &&
283 mce.getScope().get().toString().contains("MessageDigest") &&
284 mce.getArguments().size() >= 1) {
285 Expression arg0 = mce.getArgument(0);
286 if (arg0.isStringLiteralExpr()) {
287 String algorithm = arg0.asStringLiteralExpr().getValue().toLowerCase();
288 if (WEAK_HASHING_ALGORITHMS.contains(algorithm)) {
289 logPatch(line, "Weak hashing algorithm '" + algorithm.toUpperCase() + "' updated to 'SHA-256'.");
290 mce.setArgument(0, StaticJavaParser.parseExpression("\"SHA-256\""));
291 }
292 }
293 }
294
295 // NEW PATCH: XML External Entity (XXE) Vulnerabilities - Add hardening features
296 if (mce.getNameAsString().equals("newInstance") &&
297 mce.getScope().isPresent() &&
298 XML_FACTORIES.contains(mce.getScope().get().toString())) {
299
300 // Find the variable declaration for this factory
301 mce.findAncestor(VariableDeclarator.class).ifPresent(vd -> {
302 String factoryName = vd.getNameAsString();
303 // Append hardening calls after the factory creation
304 BlockStmt parentBlock = mce.findAncestor(BlockStmt.class).orElse(null);
305 if (parentBlock != null) {
306 int insertIndex = parentBlock.getStatements().indexOf(mce.findAncestor(ExpressionStmt.class).orElse(null));
307 if (insertIndex != -1) {
308 NodeList<Statement> newStatements = new NodeList<>();
309 newStatements.add(StaticJavaParser.parseStatement(factoryName + ".setFeature(\"http://apache.org/xml/features/disallow-doctype-decl\", true);"));
310 newStatements.add(StaticJavaParser.parseStatement(factoryName + ".setFeature(\"http://xml.org/sax/features/external-general-entities\", false);"));
311 newStatements.add(StaticJavaParser.parseStatement(factoryName + ".setFeature(\"http://xml.org/sax/features/external-parameter-entities\", false);"));
312 newStatements.add(StaticJavaParser.parseStatement(factoryName + ".setFeature(\"http://apache.org/xml/features/nonvalidating/load-external-dtd\", false);"));
313 newStatements.add(StaticJavaParser.parseStatement(factoryName + ".setXIncludeAware(false);"));
314 newStatements.add(StaticJavaParser.parseStatement(factoryName + ".setExpandEntityReferences(false);"));
315
316 for (int i = 0; i < newStatements.size(); i++) {
317 parentBlock.addStatement(insertIndex + 1 + i, newStatements.get(i));
318 }
319 logPatch(line, "Added XXE hardening features for XML factory: " + factoryName);
320 }
321 }
322 });
323 }
324
325 // NEW PATCH: Insecure HTTP URL Usage - Change to HTTPS
326 if (mce.getNameAsString().equals("URL") && mce.getArguments().size() == 1) {
327 Expression arg0 = mce.getArgument(0);
328 if (arg0.isStringLiteralExpr()) {
329 String urlString = arg0.asStringLiteralExpr().getValue();
330 if (urlString.startsWith("http://") && !urlString.contains("localhost") && !urlString.contains("127.0.0.1")) {
331 String httpsUrlString = urlString.replaceFirst("http://", "https://");
332 mce.setArgument(0, StaticJavaParser.parseExpression("\"" + httpsUrlString + "\""));
333 logPatch(line, "Changed insecure 'http://' URL to 'https://': " + httpsUrlString);
334 }
335 }
336 }
337
338
339 return mce;
340 }
341
342 /**
343 * Visits ObjectCreationExpr nodes to apply patches during object instantiation.
344 * This includes insecure TrustManager implementations, unseeded SecureRandom,
345 * hardcoded cryptographic keys, and deserialization of untrusted data.
346 *
347 * @param oce The ObjectCreationExpr node being visited.
348 * @param arg A generic argument (not used here).
349 * @return The modified ObjectCreationExpr.
350 */
351 @Override
352 public ObjectCreationExpr visit(ObjectCreationExpr oce, Void arg) {
353 super.visit(oce, arg); // Call super to ensure full traversal
354
355 int line = oce.getBegin().map(p -> p.line).orElse(-1);
356
357 // Patch: Insecure TrustManager (anonymous class with empty methods or return true)
358 if (oce.getAnonymousClassBody().isPresent() &&
359 (oce.getType().getNameAsString().equals("X509TrustManager") ||
360 oce.getType().getNameAsString().equals("TrustManager"))) {
361
362 boolean foundInsecurePattern = false;
363 for (BodyDeclaration bodyDecl : oce.getAnonymousClassBody().get()) {
364 if (bodyDecl instanceof MethodDeclaration) {
365 MethodDeclaration md = (MethodDeclaration) bodyDecl;
366 String methodName = md.getNameAsString();
367
368 if (("checkClientTrusted".equals(methodName) || "checkServerTrusted".equals(methodName)) && md.getBody().isPresent()) {
369 BlockStmt methodBody = md.getBody().get();
370
371 // Check for empty body or body with 'return true'
372 if (methodBody.getStatements().isEmpty() ||
373 methodBody.getStatements().stream()
374 .filter(stmt -> stmt instanceof ReturnStmt)
375 .map(stmt -> (ReturnStmt) stmt)
376 .anyMatch(returnStmt -> returnStmt.getExpression().isPresent() && returnStmt.getExpression().get().isBooleanLiteralExpr() &&
377 returnStmt.getExpression().get().asBooleanLiteralExpr().getValue())) {
378 foundInsecurePattern = true;
379 }
380 // Check for swallowing exceptions
381 else if (methodBody.getStatements().stream()
382 .filter(stmt -> stmt instanceof TryStmt)
383 .map(stmt -> (TryStmt) stmt)
384 .anyMatch(ts -> ts.getCatchClauses().stream()
385 .anyMatch(catchClause -> {
386 Type caughtType = catchClause.getParameter().getType();
387 if (caughtType instanceof ClassOrInterfaceType) {
388 String typeName = ((ClassOrInterfaceType) caughtType).getNameAsString();
389 if (typeName.equals("Exception") || typeName.equals("Throwable") ||
390 typeName.equals("CertificateException") || typeName.equals("NoSuchAlgorithmException")) {
391 return catchClause.getBody().getStatements().isEmpty() ||
392 (catchClause.getBody().getStatements().size() == 1 &&
393 catchClause.getBody().getStatement(0).isExpressionStmt() &&
394 catchClause.getBody().getStatement(0).asExpressionStmt().getExpression().isMethodCallExpr() &&
395 catchClause.getBody().getStatement(0).asExpressionStmt().getExpression().asMethodCallExpr().getNameAsString().equals("printStackTrace"));
396 }
397 }
398 return false;
399 })
400 )) {
401 foundInsecurePattern = true; // Also consider this an insecure pattern for patching
402 }
403 }
404 }
405 }
406
407 if (foundInsecurePattern) {
408 logPatch(line, "Insecure TrustManager replaced with secure placeholder requiring manual review.");
409 // Replace the entire anonymous class with a secure placeholder
410 oce.replace(StaticJavaParser.parseExpression("new javax.net.ssl.X509TrustManager() {\n" +
411 " @Override\n" +
412 " public java.security.cert.X509Certificate[] getAcceptedIssuers() { return new java.security.cert.X509Certificate[0]; }\n" +
413 " @Override\n" +
414 " public void checkClientTrusted(java.security.cert.X509Certificate[] certs, String authType) throws java.security.cert.CertificateException {\n" +
415 " // AUTO-PATCH: Manual review required. Implement strict certificate validation here.\n" +
416 " throw new java.security.cert.CertificateException(\"Insecure TrustManager automatically patched: Manual review required.\");\n" +
417 " }\n" +
418 " @Override\n" +
419 " public void checkServerTrusted(java.security.cert.X509Certificate[] certs, String authType) throws java.security.cert.CertificateException {\n" +
420 " // AUTO-PATCH: Manual review required. Implement strict certificate validation here.\n" +
421 " throw new java.security.cert.CertificateException(\"Insecure TrustManager automatically patched: Manual review required.\");\n" +
422 " }\n" +
423 "}"));
424 }
425 }
426
427 // NEW PATCH: Hardcoded Cryptographic Keys/Salts/IVs - Add Warning Comment
428 if (oce.getType().getNameAsString().equals("SecretKeySpec") ||
429 oce.getType().getNameAsString().equals("IvParameterSpec")) {
430 boolean hasLiteralArgument = false;
431 for (Expression argExpr : oce.getArguments()) {
432 if (argExpr.isStringLiteralExpr()) {
433 String value = argExpr.asStringLiteralExpr().getValue();
434 if (value.length() > 5 && value.toLowerCase().matches(SENSITIVE_STRING_PATTERN)) {
435 hasLiteralArgument = true;
436 break;
437 }
438 } else if (argExpr.isMethodCallExpr() && argExpr.asMethodCallExpr().getScope().isPresent() && argExpr.asMethodCallExpr().getScope().get().isStringLiteralExpr()) {
439 String value = argExpr.asMethodCallExpr().getScope().get().asStringLiteralExpr().getValue();
440 if (value.length() > 5 && value.toLowerCase().matches(SENSITIVE_STRING_PATTERN)) {
441 hasLiteralArgument = true;
442 break;
443 }
444 } else if (argExpr.isArrayCreationExpr()) {
445 ArrayCreationExpr ace = argExpr.asArrayCreationExpr();
446 if (ace.getInitializer().isPresent() && ace.getInitializer().get().getValues().isNonEmpty()) {
447 hasLiteralArgument = true; // Assume any direct array literal initialization is suspicious
448 break;
449 }
450 }
451 }
452 if (hasLiteralArgument) {
453 oce.getParentNode().ifPresent(parent -> {
454 if (parent instanceof ExpressionStmt || parent instanceof VariableDeclarator) {
455 String comment = "/* AUTO-PATCH: WARNING! This cryptographic key/salt/IV may be hardcoded.\n" +
456 " * Storing sensitive keys directly in code is a severe security risk.\n" +
457 " * Externalize this key/salt to a secure location (e.g., environment variable, KeyVault).\n" +
458 " */";
459 parent.setComment(new BlockComment(comment));
460 logPatch(line, "Added warning comment for potentially hardcoded cryptographic key/salt/IV.");
461 }
462 });
463 }
464 }
465
466 // NEW PATCH: Deserialization of Untrusted Data - Add Warning Comment
467 if (oce.getType().getNameAsString().equals("ObjectInputStream")) {
468 oce.getParentNode().ifPresent(parent -> {
469 if (parent instanceof ExpressionStmt) {
470 ExpressionStmt stmt = (ExpressionStmt) parent;
471 String comment = "/* AUTO-PATCH: WARNING! ObjectInputStream is used here.\n" +
472 " * Deserializing untrusted data from an ObjectInputStream is a MAJOR security vulnerability (RCE).\n" +
473 " * Avoid using ObjectInputStream with untrusted sources. Consider safer formats like JSON/XML (with XXE protection).\n" +
474 " * If unavoidable, implement a robust deserialization filter (Java 9+).\n" +
475 " */";
476 stmt.setComment(new BlockComment(comment));
477 logPatch(line, "Added warning comment for ObjectInputStream (deserialization vulnerability).");
478 }
479 });
480 }
481
482 return oce;
483 }
484
485 /**
486 * Visits VariableDeclarator nodes to apply patches related to variable declarations.
487 * This includes hardcoded sensitive data and unseeded SecureRandom instances.
488 *
489 * @param vd The VariableDeclarator node being visited.
490 * @param arg A generic argument (not used here).
491 * @return The modified VariableDeclarator.
492 */
493 @Override
494 public VariableDeclarator visit(VariableDeclarator vd, Void arg) {
495 super.visit(vd, arg); // Call super to ensure full traversal
496
497 int line = vd.getBegin().map(p -> p.line).orElse(-1);
498
499 // Patch: Unseeded SecureRandom instance during variable declaration
500 if (vd.getType() instanceof ClassOrInterfaceType) {
501 ClassOrInterfaceType classType = (ClassOrInterfaceType) vd.getType();
502 if (classType.getNameAsString().equals("SecureRandom") &&
503 vd.getInitializer().isPresent() &&
504 vd.getInitializer().get().isObjectCreationExpr()) {
505 ObjectCreationExpr oce = vd.getInitializer().get().asObjectCreationExpr();
506 if (oce.getType().getNameAsString().equals("SecureRandom") && oce.getArguments().isEmpty()) {
507 logPatch(line, "Unseeded SecureRandom replaced with SecureRandom.getInstanceStrong() for variable: " + vd.getNameAsString());
508 vd.setInitializer(StaticJavaParser.parseExpression("SecureRandom.getInstanceStrong()"));
509 }
510 }
511 }
512
513 // Patch: Hardcoded password/sensitive string assigned to variable
514 if (vd.getInitializer().isPresent() && vd.getInitializer().get().isStringLiteralExpr()) {
515 String val = vd.getInitializer().get().asStringLiteralExpr().getValue();
516 // Using regex for more flexible pattern matching, and a minimum length to avoid false positives
517 if (val.matches(SENSITIVE_STRING_PATTERN) && val.length() > 3) { // Check against new pattern
518 logPatch(line, "Hardcoded sensitive string assigned to variable '" + vd.getNameAsString() + "' replaced with environment lookup.");
519 // Replace with environment variable lookup. Use StandardCharsets for robustness.
520 vd.setInitializer(StaticJavaParser.parseExpression("System.getenv(\"" + vd.getNameAsString().toUpperCase() + "_SECRET\")"));
521 }
522 }
523
524 // Patch: Weak cipher suites in array initialization (already present)
525 if (vd.getType().isArrayType() &&
526 vd.getType().asArrayType().getComponentType().toString().equals("String") &&
527 vd.getInitializer().isPresent() &&
528 vd.getInitializer().get() instanceof ArrayInitializerExpr) {
529
530 ArrayInitializerExpr init = (ArrayInitializerExpr) vd.getInitializer().get();
531
532 boolean weak = init.getValues().stream()
533 .filter(Expression::isStringLiteralExpr)
534 .map(expr -> expr.asStringLiteralExpr().getValue().toLowerCase())
535 .anyMatch(val -> WEAK_CIPHER_KEYWORDS.stream().anyMatch(val::contains));
536
537 if (weak) {
538 logPatch(line, "Weak cipher suites array replaced with strong defaults.");
539 vd.setInitializer(StaticJavaParser.parseExpression(STRONG_CIPHERS_ARRAY_EXPR));
540 }
541 }
542
543 return vd;
544 }
545
546 /**
547 * Visits WhileStmt nodes to add a warning for potential infinite loops.
548 * Direct patching is too risky without deeper semantic analysis.
549 *
550 * @param ws The WhileStmt node being visited.
551 * @param arg A generic argument (not used here).
552 * @return The modified WhileStmt.
553 */
554 @Override
555 public WhileStmt visit(WhileStmt ws, Void arg) {
556 super.visit(ws, arg);
557 int line = ws.getBegin().map(p -> p.line).orElse(-1);
558
559 if (ws.getCondition().isBooleanLiteralExpr() &&
560 ws.getCondition().asBooleanLiteralExpr().getValue()) {
561 ws.setComment(new BlockComment("/* AUTO-PATCH: WARNING! Infinite loop (while(true)) detected.\n" +
562 " * This could be a Denial-of-Service vulnerability if it ties up resources.\n" +
563 " * Review the loop condition to ensure it terminates properly or has safeguards.\n" +
564 " * Manual review is required to determine if this is intentional or a vulnerability.\n" +
565 " */"));
566 logPatch(line, "Added warning comment for potential infinite loop (while(true)).");
567 }
568 return ws;
569 }
570
571 /**
572 * Visits TryStmt nodes to add a warning for overly broad exception catching.
573 * Direct patching of catch blocks is complex and risky.
574 *
575 * @param ts The TryStmt node being visited.
576 * @param arg A generic argument (not used here).
577 * @return The modified TryStmt.
578 */
579 @Override
580 public TryStmt visit(TryStmt ts, Void arg) {
581 super.visit(ts, arg);
582 int line = ts.getBegin().map(p -> p.line).orElse(-1);
583
584 ts.getCatchClauses().forEach(catchClause -> {
585 Type caughtType = catchClause.getParameter().getType();
586 if (caughtType instanceof ClassOrInterfaceType) {
587 String typeName = ((ClassOrInterfaceType) caughtType).getNameAsString();
588 if (typeName.equals("Exception") || typeName.equals("Throwable")) {
589 if (catchClause.getBody().getStatements().isEmpty() ||
590 (catchClause.getBody().getStatements().size() == 1 &&
591 catchClause.getBody().getStatement(0).isExpressionStmt() &&
592 catchClause.getBody().getStatement(0).asExpressionStmt().getExpression().isMethodCallExpr() &&
593 catchClause.getBody().getStatement(0).asExpressionStmt().getExpression().asMethodCallExpr().getNameAsString().equals("printStackTrace"))) {
594 String comment = "/* AUTO-PATCH: WARNING! Overly broad catch for '" + typeName + "' with minimal error handling.\n" +
595 " * This may hide critical exceptions, including security-related ones. Catch more specific exceptions.\n" +
596 " * Manual review is required to refine exception handling.\n" +
597 " */";
598 catchClause.setComment(new BlockComment(comment));
599 logPatch(line, "Added warning comment for overly broad catch block (" + typeName + ").");
600 }
601 }
602 }
603 });
604 return ts;
605 }
606 }
607}
608 