google-site-verification: google61fe8ba583a51912.html
CodeQL for Java: Comprehensive Static Analysis and Security Scanning

CodeQL is a semantic code analysis engine that lets you query code as data. It treats code like a database and allows you to find vulnerabilities, bugs, and other issues through logical queries.


Setup and Installation

1. Prerequisites and Dependencies
<!-- Maven Dependencies for Analysis --> <properties> <codeql.version>2.13.5</codeql.version> <spotbugs.version>4.7.3</spotbugs.version> </properties> <dependencies> <!-- For security analysis --> <dependency> <groupId>com.github.spotbugs</groupId> <artifactId>spotbugs-annotations</artifactId> <version>${spotbugs.version}</version> <scope>provided</scope> </dependency> </dependencies> <build> <plugins> <!-- Maven Compiler Plugin --> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.11.0</version> <configuration> <source>17</source> <target>17</target> <encoding>UTF-8</encoding> </configuration> </plugin> <!-- SpotBugs for complementary analysis --> <plugin> <groupId>com.github.spotbugs</groupId> <artifactId>spotbugs-maven-plugin</artifactId> <version>${spotbugs.version}</version> </plugin> </plugins> </build>
2. GitHub Actions Workflow
# .github/workflows/codeql-analysis.yml name: "CodeQL Analysis" on: push: branches: [ main, develop ] pull_request: branches: [ main ] schedule: - cron: '0 0 * * 0' # Weekly scan jobs: analyze: name: Analyze with CodeQL runs-on: ubuntu-latest permissions: actions: read contents: read security-events: write strategy: fail-fast: false matrix: language: [ 'java' ] steps: - name: Checkout repository uses: actions/checkout@v4 - name: Initialize CodeQL uses: github/codeql-action/init@v2 with: languages: ${{ matrix.language }} queries: security-and-quality build-mode: autobuild - name: Autobuild uses: github/codeql-action/autobuild@v2 - name: Perform CodeQL Analysis uses: github/codeql-action/analyze@v2 with: category: "/language:java" output: codeql-results.sarif - name: Upload SARIF results uses: github/codeql-action/upload-sarif@v2 with: sarif_file: codeql-results.sarif
3. Local CodeQL Setup
#!/bin/bash # setup-codeql.sh # Download CodeQL CODEQL_VERSION="2.13.5" CODEQL_URL="https://github.com/github/codeql-cli-binaries/releases/download/v${CODEQL_VERSION}/codeql-linux64.zip" echo "Setting up CodeQL..." wget $CODEQL_URL -O codeql.zip unzip codeql.zip -d ~/codeql-home export PATH="$HOME/codeql-home/codeql:$PATH" # Clone CodeQL repositories mkdir -p ~/codeql-home/repos cd ~/codeql-home/repos git clone https://github.com/github/codeql.git git clone https://github.com/github/codeql-go.git echo "CodeQL setup complete!"

Basic CodeQL Queries

1. Simple Security Query
// Security/CWE-089-SqlInjection.ql import java import semmle.code.java.dataflow.DataFlow import semmle.code.java.dataflow.TaintTracking class SqlInjectionQuery extends TaintTracking::Configuration { SqlInjectionQuery() { this = "SqlInjection" } override predicate isSource(DataFlow::Node source) { source instanceof RemoteFlowSource } override predicate isSink(DataFlow::Node sink) { exists(MethodAccess ma | ma.getMethod().hasName("executeQuery") and ma.getMethod().getDeclaringType().hasQualifiedName("java.sql", "Statement") and sink.asExpr() = ma.getArgument(0) ) } } from SqlInjectionQuery config, DataFlow::PathNode source, DataFlow::PathNode sink where config.hasFlowPath(source, sink) select sink, "Potential SQL injection vulnerability: user input reaches $@.", source, "user input"
2. Common Vulnerability Patterns
// Security/CommandInjection.ql import java import semmle.code.java.dataflow.DataFlow import semmle.code.java.dataflow.TaintTracking class CommandInjectionConfig extends TaintTracking::Configuration { CommandInjectionConfig() { this = "CommandInjection" } override predicate isSource(DataFlow::Node source) { source instanceof RemoteFlowSource } override predicate isSink(DataFlow::Node sink) { exists(MethodAccess ma | ma.getMethod().hasName("exec") and ma.getMethod().getDeclaringType().hasQualifiedName("java.lang", "Runtime") and sink.asExpr() = ma.getArgument(0) ) } } from CommandInjectionConfig config, DataFlow::PathNode source, DataFlow::PathNode sink where config.hasFlowPath(source, sink) select sink, "Potential command injection vulnerability"
3. Code Quality Queries
// CodeQuality/EmptyCatchBlock.ql import java from BlockStmt catchBlock, CatchClause catchClause where catchBlock = catchClause.getBlock() and catchBlock.getNumStmt() = 0 select catchClause, "Empty catch block: exceptions are silently ignored"

Custom CodeQL Queries for Java

1. Custom Security Query - Hardcoded Credentials
// Custom/HardcodedCredentials.ql import java class HardcodedCredential extends string { HardcodedCredential() { this.regexpMatch("(?i).*(password|pwd|secret|key|token|credential).*") and this.length() > 8 } } from Variable v, StringLiteral literal where literal.getString() instanceof HardcodedCredential and v.getInitializer() = literal and v.getType().hasName("String") select v, "Potential hardcoded credential: " + literal.getString()
2. Custom Query - Unsafe Deserialization
// Custom/UnsafeDeserialization.ql import java import semmle.code.java.dataflow.DataFlow import semmle.code.java.dataflow.TaintTracking class UnsafeDeserializationConfig extends TaintTracking::Configuration { UnsafeDeserializationConfig() { this = "UnsafeDeserialization" } override predicate isSource(DataFlow::Node source) { source instanceof RemoteFlowSource } override predicate isSink(DataFlow::Node sink) { exists(MethodAccess ma | // ObjectInputStream.readObject() (ma.getMethod().hasName("readObject") and ma.getMethod().getDeclaringType().hasQualifiedName("java.io", "ObjectInputStream")) or // XMLDecoder.readObject() (ma.getMethod().hasName("readObject") and ma.getMethod().getDeclaringType().hasQualifiedName("java.beans", "XMLDecoder")) or // YAML.load() - SnakeYAML (ma.getMethod().hasName("load") and ma.getMethod().getDeclaringType().hasQualifiedName("org.yaml.snakeyaml", "YAML")) ) and sink.asExpr() = ma.getArgument(0) } } from UnsafeDeserializationConfig config, DataFlow::PathNode source, DataFlow::PathNode sink where config.hasFlowPath(source, sink) select sink, "Unsafe deserialization of user-controlled data"
3. Custom Query - Log Injection
// Custom/LogInjection.ql import java import semmle.code.java.dataflow.DataFlow import semmle.code.java.dataflow.TaintTracking class LogInjectionConfig extends TaintTracking::Configuration { LogInjectionConfig() { this = "LogInjection" } override predicate isSource(DataFlow::Node source) { source instanceof RemoteFlowSource } override predicate isSink(DataFlow::Node sink) { exists(MethodAccess ma | // Logger methods (ma.getMethod().getDeclaringType().getASupertype*().hasQualifiedName("org.slf4j", "Logger") or ma.getMethod().getDeclaringType().getASupertype*().hasQualifiedName("org.apache.logging.log4j", "Logger")) and (ma.getMethod().hasName("info") or ma.getMethod().hasName("warn") or ma.getMethod().hasName("error") or ma.getMethod().hasName("debug")) and sink.asExpr() = ma.getArgument(0) ) } } from LogInjectionConfig config, DataFlow::PathNode source, DataFlow::PathNode sink where config.hasFlowPath(source, sink) select sink, "Potential log injection vulnerability"
4. Custom Query - Resource Leak
// Custom/ResourceLeak.ql import java class ResourceType extends RefType { ResourceType() { this.getASupertype*().hasQualifiedName("java.lang", "AutoCloseable") or this.hasQualifiedName("java.sql", "Connection") or this.hasQualifiedName("java.sql", "Statement") or this.hasQualifiedName("java.sql", "ResultSet") or this.hasQualifiedName("java.io", "InputStream") or this.hasQualifiedName("java.io", "OutputStream") } } from Variable v, Method m where v.getType() instanceof ResourceType and m = v.getEnclosingCallable() and not exists(v.getAnAccess()) and v.getInitializer() != null select v, "Potential resource leak: " + v.getName() + " is never used"

Java Code Examples for Analysis

1. Vulnerable Code Examples
package com.example.vulnerable; import java.sql.*; import java.io.*; // SQL Injection vulnerable class public class UserRepository { private Connection connection; // VULNERABLE: SQL Injection public User findUserByName(String name) throws SQLException { String sql = "SELECT * FROM users WHERE name = '" + name + "'"; Statement stmt = connection.createStatement(); ResultSet rs = stmt.executeQuery(sql); // CodeQL will flag this return mapToUser(rs); } // SECURE: Using PreparedStatement public User findUserByNameSecure(String name) throws SQLException { String sql = "SELECT * FROM users WHERE name = ?"; PreparedStatement stmt = connection.prepareStatement(sql); stmt.setString(1, name); ResultSet rs = stmt.executeQuery(); return mapToUser(rs); } // VULNERABLE: Command Injection public void executeSystemCommand(String input) throws IOException { Runtime.getRuntime().exec("ping " + input); // CodeQL will flag this } // VULNERABLE: Unsafe Deserialization public Object deserializeData(byte[] data) throws Exception { ByteArrayInputStream bis = new ByteArrayInputStream(data); ObjectInputStream ois = new ObjectInputStream(bis); return ois.readObject(); // CodeQL will flag this } // VULNERABLE: Hardcoded Credentials private static final String DB_PASSWORD = "supersecret123"; // CodeQL will flag this // VULNERABLE: Log Injection public void logUserInput(String userInput) { logger.info("User input: " + userInput); // CodeQL will flag this } // VULNERABLE: Resource Leak public void readFile(String filename) throws IOException { FileInputStream fis = new FileInputStream(filename); // CodeQL might flag if not closed // Missing fis.close() } private User mapToUser(ResultSet rs) throws SQLException { // Implementation omitted return null; } }
2. Secure Code Examples
package com.example.secure; import java.sql.*; import java.io.*; import java.util.logging.Logger; import org.slf4j.LoggerFactory; // Secure implementation public class SecureUserService { private static final Logger logger = Logger.getLogger(SecureUserService.class.getName()); private Connection connection; // SECURE: Parameterized query public User authenticateUser(String username, String password) throws SQLException { String sql = "SELECT * FROM users WHERE username = ? AND password_hash = ?"; try (PreparedStatement stmt = connection.prepareStatement(sql)) { stmt.setString(1, username); stmt.setString(2, hashPassword(password)); try (ResultSet rs = stmt.executeQuery()) { return rs.next() ? mapToUser(rs) : null; } } } // SECURE: Validated command execution public void safePing(String host) throws IOException { if (!isValidHostname(host)) { throw new IllegalArgumentException("Invalid hostname"); } String[] command = {"ping", "-c", "1", host}; Process process = Runtime.getRuntime().exec(command); // Process output handling... } // SECURE: Safe logging public void safeLogUserAction(String userId, String action) { // Sanitize user input before logging String sanitizedUserId = userId.replaceAll("[\\r\\n]", ""); String sanitizedAction = action.replaceAll("[\\r\\n]", ""); logger.info(String.format("User %s performed action: %s", sanitizedUserId, sanitizedAction)); } // SECURE: Resource management with try-with-resources public String readFileSecurely(String filename) throws IOException { try (FileInputStream fis = new FileInputStream(filename); InputStreamReader isr = new InputStreamReader(fis); BufferedReader br = new BufferedReader(isr)) { StringBuilder content = new StringBuilder(); String line; while ((line = br.readLine()) != null) { content.append(line).append("\n"); } return content.toString(); } } // SECURE: Configuration from environment private String getDatabasePassword() { return System.getenv("DB_PASSWORD"); // Not hardcoded } private boolean isValidHostname(String hostname) { return hostname.matches("^[a-zA-Z0-9.-]+$"); } private String hashPassword(String password) { // Implementation for password hashing return "hashed_" + password; // Simplified } private User mapToUser(ResultSet rs) throws SQLException { User user = new User(); user.setId(rs.getInt("id")); user.setUsername(rs.getString("username")); user.setEmail(rs.getString("email")); return user; } }
3. Security Configuration Classes
package com.example.security; import java.io.*; import java.util.*; // Security configuration that CodeQL can analyze public class SecurityConfig { // VULNERABLE: Weak random number generator public String generateWeakToken() { Random random = new Random(); // CodeQL: Use SecureRandom instead return String.valueOf(random.nextInt()); } // SECURE: Strong random number generator public String generateStrongToken() { SecureRandom random = new SecureRandom(); byte[] bytes = new byte[32]; random.nextBytes(bytes); return Base64.getEncoder().encodeToString(bytes); } // VULNERABLE: Weak encryption public String weakEncrypt(String data) { // This is a weak encryption example return Base64.getEncoder().encodeToString(data.getBytes()); // Not actual encryption } // VULNERABLE: Insecure SSL configuration public void configureInsecureSSL() { // This would disable SSL verification in a real scenario System.setProperty("https.protocols", "TLSv1"); // Weak protocol } // SECURE: Input validation public boolean isValidEmail(String email) { if (email == null || email.length() > 254) { return false; } String emailRegex = "^[A-Za-z0-9+_.-]+@(.+)$"; return email.matches(emailRegex); } // SECURE: Output encoding for XSS prevention public String encodeForHTML(String input) { if (input == null) return ""; return input.replace("&", "&amp;") .replace("<", "&lt;") .replace(">", "&gt;") .replace("\"", "&quot;") .replace("'", "&#x27;"); } }

Advanced CodeQL Queries

1. Complex Data Flow Analysis
// Advanced/SpringRequestMappingInjection.ql import java import semmle.code.java.dataflow.DataFlow import semmle.code.java.dataflow.TaintTracking import semmle.code.java.frameworks.Spring class SpringRequestMappingConfig extends TaintTracking::Configuration { SpringRequestMappingConfig() { this = "SpringRequestMappingInjection" } override predicate isSource(DataFlow::Node source) { // Source is request mapping parameter exists(SpringRequestMappingMethod method, Parameter p | method = p.getCallable() and p.getType().hasQualifiedName("org.springframework.web.context.request", "WebRequest") and source.asParameter() = p ) } override predicate isSink(DataFlow::Node sink) { // Sink is SQL execution exists(MethodAccess ma | ma.getMethod().hasName("executeQuery") and ma.getMethod().getDeclaringType().hasQualifiedName("java.sql", "Statement") and sink.asExpr() = ma.getArgument(0) ) } } from SpringRequestMappingConfig config, DataFlow::PathNode source, DataFlow::PathNode sink where config.hasFlowPath(source, sink) select sink, "Spring request parameter flows to SQL execution"
2. Custom Security Rule - JWT Validation
// Security/JWTValidationMissing.ql import java class JWTValidationMissing extends Method { JWTValidationMissing() { this.getDeclaringType().hasQualifiedName("io.jsonwebtoken", "Jwts") and this.hasName("parser") and not exists(MethodAccess ma | ma.getMethod().hasName("verifyWith") or ma.getMethod().hasName("setSigningKey") and ma.getQualifier() = this.getACall() ) } } from JWTValidationMissing method, MethodAccess call where call.getMethod() = method select call, "JWT parser created without signature verification"
3. Performance Query
// Performance/StringConcatenationInLoop.ql import java from Loop loop, Expr concat where concat.getParent*() = loop and concat instanceof AddExpr and exists(AddExpr add | add = concat | add.getLeftOperand().getType().hasName("String")) and exists(AddExpr add | add = concat | add.getRightOperand().getType().hasName("String")) select concat, "String concatenation in loop may cause performance issues"

Integration with Build Tools

1. Maven Plugin Configuration
<!-- pom.xml - CodeQL Analysis Integration --> <build> <plugins> <!-- CodeQL Maven Plugin (Custom) --> <plugin> <groupId>org.codehaus.mojo</groupId> <artifactId>exec-maven-plugin</artifactId> <version>3.1.0</version> <executions> <execution> <id>codeql-analysis</id> <phase>verify</phase> <goals> <goal>exec</goal> </goals> <configuration> <executable>codeql</executable> <arguments> <argument>database</argument> <argument>create</argument> <argument>codeql-db</argument> <argument>--language=java</argument> <argument>--source-root=.</argument> <argument>--command=mvn compile</argument> </arguments> </configuration> </execution> </executions> </plugin> <!-- SpotBugs for complementary analysis --> <plugin> <groupId>com.github.spotbugs</groupId> <artifactId>spotbugs-maven-plugin</artifactId> <version>4.7.3</version> <configuration> <effort>Max</effort> <threshold>Low</threshold> <failOnError>true</failOnError> <includeFilterFile>spotbugs-include.xml</includeFilterFile> <excludeFilterFile>spotbugs-exclude.xml</excludeFilterFile> </configuration> <executions> <execution> <phase>verify</phase> <goals> <goal>check</goal> </goals> </execution> </executions> </plugin> </plugins> </xml>
2. SpotBugs Configuration
<!-- spotbugs-include.xml --> <FindBugsFilter> <Match> <Bug category="SECURITY" /> </Match> <Match> <Bug category="MALICIOUS_CODE" /> </Match> <Match> <Bug category="BAD_PRACTICE" /> </Match> </FindBugsFilter> <!-- spotbugs-exclude.xml --> <FindBugsFilter> <Match> <Bug pattern="DLS_DEAD_LOCAL_STORE" /> </Match> </FindBugsFilter>
3. Gradle Configuration
// build.gradle plugins { id 'java' id 'com.github.spotbugs' version '5.0.13' } spotbugs { toolVersion = '4.7.3' effort = 'max' reportLevel = 'low' includeFilter = file('config/spotbugs-include.xml') excludeFilter = file('config/spotbugs-exclude.xml') } tasks.named('spotbugsMain') { reports { html { enabled = true destination = file("$buildDir/reports/spotbugs/main.html") } xml { enabled = false } } } // Custom task for CodeQL task codeqlDatabase(type: Exec) { commandLine 'codeql', 'database', 'create', 'codeql-db', '--language=java', '--source-root=.', '--command=./gradlew compileJava' }

Continuous Monitoring

1. Pre-commit Hook
#!/bin/bash # .git/hooks/pre-commit echo "Running CodeQL analysis..." # Create CodeQL database codeql database create codeql-temp --language=java --source-root=. --command="mvn compile -q" # Run security queries codeql database analyze codeql-temp \ ~/codeql-home/repos/codeql/java/ql/src/Security \ --format=sarif-latest \ --output=codeql-results.sarif # Check if there are critical findings if grep -q '"level":"error"' codeql-results.sarif; then echo "❌ Critical security issues found. Commit blocked." cat codeql-results.sarif | jq '.runs[0].results[] | select(.level == "error") | .message.text' exit 1 fi echo "✅ CodeQL analysis passed" exit 0
2. Custom Query Pack
# codeql-pack.yml name: company-security-queries version: 1.0.0 library: false dependencies: codeql/java-all: "*" queries: - include: - Security/CWE-089-SqlInjection.ql - Security/CWE-078-CommandInjection.ql - Custom/HardcodedCredentials.ql - Custom/UnsafeDeserialization.ql suites: security: description: "Company Security Standards" queries: - Security/CWE-089-SqlInjection.ql - Security/CWE-078-CommandInjection.ql

Best Practices

  1. Query Design:
  • Start with existing queries and customize
  • Use precise source and sink definitions
  • Test queries on known vulnerable code
  1. Integration:
  • Run CodeQL in CI/CD pipeline
  • Combine with other SAST tools
  • Review findings regularly
  1. Performance:
  • Use specific queries rather than broad patterns
  • Limit data flow steps when possible
  • Use database caching
  1. Maintenance:
  • Update CodeQL regularly
  • Review and update custom queries
  • Train developers on fixing identified issues
# Update CodeQL databases codeql database upgrade codeql-db codeql pack update

Conclusion

CodeQL for Java provides:

  • Advanced static analysis through semantic code understanding
  • Custom security rules tailored to your codebase
  • Data flow analysis for vulnerability detection
  • Integration with CI/CD pipelines
  • Comprehensive reporting in multiple formats

By implementing the patterns and queries shown above, you can significantly improve your Java application's security posture, catch vulnerabilities early in the development process, and maintain code quality across your codebase. The combination of pre-built security queries and custom rules tailored to your specific needs makes CodeQL a powerful tool for any Java development team.

Leave a Reply

Your email address will not be published. Required fields are marked *


Macro Nepal Helper