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