This article explores the use of static source code analysis tools to detect security vulnerabilities. While the primary focus will be on Python and Java applications, many of the tools discussed support multiple programming languages.
General Tips
The following guidance applies when trying to perform time limited source code assessments.
Understand the threat model
Begin by understanding the purpose of the application and the data it processes. Identifying potential threats helps determine whether the existing security functionality meets the necessary requirements. For example, an online banking application is a prime target for financial fraud, making two-factor authentication (2FA) a reasonable security control to implement.
Understand the applications architecture at a high level
Before analysing the code, gain a high-level understanding of the application’s architecture. This typically involves reviewing design documentation, consulting developers, and performing dynamic analysis. Key aspects to consider include:
- Programming languages used – Different languages have varying security risks.
- Application components – Is it built using an n-tier architecture or is it monolithic?
- Third-party dependencies – Identify external libraries that may introduce vulnerabilities.
Use static analysis tools
Manually reviewing every line of code is rarely practical. Use static analysis tools with built-in rule sets to detect common security flaws. However, these tools may produce false positives, so it’s important to validate their findings.
Identify security enforcing functionality
- Locate sources (points where un-trusted input enters the system, such as network sockets).
- Identify sinks (places where un-trusted input is used in potentially risky operations, such as writing to a fixed-length buffer).
- Focus on reviewing critical security mechanisms, such as access control in multi-user applications.
Use dynamic analysis to guide static analysis
If you can compile and run the application, enable debug logging and perform fuzz testing. This can help uncover runtime errors that may lead to security vulnerabilities, allowing you to trace issues back to the source code.
Static Analysis Tools
Static analysis tools examine source code, bytecode, or binaries without executing the program to identify potential issues such as security vulnerabilities and code quality problems.
You can use a number of tools to perform static analysis. We’re going to be looking at the following:
Vulnerable Applications
To test that the static analysers are working, we can use some deliberately vulnerable applications.
Use the following commands to download a vulnerable Java application (vulnado).
git clone https://github.com/ScaleSec/vulnado
cd vulnado
./mvnw compile
Most Java analysis tools require the class files for analysis (not just the .java files), which is why the Java code needs compiling.
We will also be using the following deliberately vulnerable Python application.
git clone https://github.com/sgabe/DSVPWA
SonarQube
SonarQube is an open-source platform for continuous code quality and security analysis. The free version does have some limitations around the types of security checks it will perform, although it can still be useful.
The easiest way to get a running instance of SonarQube is using the Docker image. You can start it using the following command.
docker run --name sonarqube-custom -p 9000:9000 sonarqube:community
Visit the web interface on port 9000 and login using admin/admin. Select Projects > Create Local Project.

Name the project anything you like, and set analysis method to locally. It will generate a token that will be needed for authentication. Be sure to save this.

With the server configured, we next need to download the sonar-scanner binary.
wget https://binaries.sonarsource.com/Distribution/sonar-scanner-cli/sonar-scanner-cli-7.0.1.4817-linux-x64.zip
unzip sonar-scanner-cli-7.0.1.4817-linux-x64.zip
mv sonar-scanner-7.0.1.4817-linux-x64 sonar-scanner
Modify sonar-scanner/conf/sonar-scanner.properties to include the following details. Make sure sonar.sources points to the source code you want to analyse, and that your API key has been filled in. Java applications will also require the location of the class files using the sonar.java.binaries parameter.
sonar.projectKey=Audit
sonar.sources=/home/kali/Tools/CodeReview/vulnado
sonar.java.binaries=/home/kali/Tools/CodeReview/vulnado/target/classes
sonar.host.url=http://127.0.0.1:9000
sonar.token=<API_KEY_GOES_HERE>
Run sonar-scanner whilst you are in the working directory of the code, and the results should appear on the web interface.

Spotbugs
SpotBugs is a static analysis tool for Java programs that looks for potential bugs in the code. It is the successor of FindBugs, which was widely used for detecting bugs, security vulnerabilities, and other quality issues in Java code.
Download the latest release from the projects GitHub page: https://github.com/spotbugs/spotbugs/releases
Open the application, and select File > New Project. Set the Classpath to the top level directory of the project. The source code directory needs to be the parent directory of the package. For instance, our package name is com.scalesec.vulnado, which resides in the java directory of the file system. As such, the source code directory is configured like the below screenshot.

Clicking analyse will bring us to a window showing identified issues. We can see it has detected a potential SQL injection issue.

PMD
PMD is another open source static code analyser. The following languages are supported:
Java, JavaScript, Salesforce.com Apex and Visualforce, Kotlin, Swift, Modelica, PLSQL, Apache Velocity, JSP, WSDL, Maven POM, HTML, XML and XSL.
Download the latest release from here: https://github.com/pmd/pmd/releases. Run the executable, specifying an appropriate rules file.
./bin/pmd check -R category/java/bestpractices.xml,category/java/security.xml -d vulnado
Picked up _JAVA_OPTIONS: -Dawt.useSystemAAFontSettings=on -Dswing.aatext=true
[WARN] Progressbar rendering conflicts with reporting to STDOUT. No progressbar will be shown. Try running with argument -r <file> to output the report to a file instead.
[WARN] This analysis could be faster, please consider using Incremental Analysis: https://docs.pmd-code.org/pmd-doc-7.10.0/pmd_userdocs_incremental_analysis.html
vulnado/src/main/java/com/scalesec/vulnado/Comment.java:11: OneDeclarationPerLine: Use one line for each declaration, it enhances code readability.
vulnado/src/main/java/com/scalesec/vulnado/Comment.java:32: PreserveStackTrace: Thrown exception does not preserve the stack trace of exception 'e' on all code paths
vulnado/src/main/java/com/scalesec/vulnado/Comment.java:37: UnusedAssignment: The initializer for variable 'stmt' is never used (overwritten on line 41)
vulnado/src/main/java/com/scalesec/vulnado/Comment.java:55: AvoidPrintStackTrace: Avoid printStackTrace(); use a logger call instead.
vulnado/src/main/java/com/scalesec/vulnado/Comment.java:56: SystemPrintln: Usage of System.out/err
vulnado/src/main/java/com/scalesec/vulnado/Comment.java:70: AvoidPrintStackTrace: Avoid printStackTrace(); use a logger call instead.
vulnado/src/main/java/com/scalesec/vulnado/Cowsay.java:10: SystemPrintln: Usage of System.out/err
vulnado/src/main/java/com/scalesec/vulnado/Cowsay.java:24: AvoidPrintStackTrace: Avoid printStackTrace(); use a logger call instead.
vulnado/src/main/java/com/scalesec/vulnado/LinkLister.java:17: LooseCoupling: Avoid using implementation types like 'Elements'; use the interface instead
vulnado/src/main/java/com/scalesec/vulnado/LinkLister.java:28: SystemPrintln: Usage of System.out/err
vulnado/src/main/java/com/scalesec/vulnado/LinkLister.java:35: PreserveStackTrace: Thrown exception does not preserve the stack trace of exception 'e' on all code paths
vulnado/src/main/java/com/scalesec/vulnado/Postgres.java:25: AvoidPrintStackTrace: Avoid printStackTrace(); use a logger call instead.
vulnado/src/main/java/com/scalesec/vulnado/Postgres.java:26: SystemPrintln: Usage of System.out/err
vulnado/src/main/java/com/scalesec/vulnado/Postgres.java:33: SystemPrintln: Usage of System.out/err
vulnado/src/main/java/com/scalesec/vulnado/Postgres.java:56: SystemPrintln: Usage of System.out/err
vulnado/src/main/java/com/scalesec/vulnado/Postgres.java:92: UnusedAssignment: The initializer for variable 'pStatement' is never used (overwritten on line 94)
vulnado/src/main/java/com/scalesec/vulnado/Postgres.java:100: AvoidPrintStackTrace: Avoid printStackTrace(); use a logger call instead.
vulnado/src/main/java/com/scalesec/vulnado/Postgres.java:106: UnusedAssignment: The initializer for variable 'pStatement' is never used (overwritten on line 108)
vulnado/src/main/java/com/scalesec/vulnado/Postgres.java:114: AvoidPrintStackTrace: Avoid printStackTrace(); use a logger call instead.
vulnado/src/main/java/com/scalesec/vulnado/User.java:13: OneDeclarationPerLine: Use one line for each declaration, it enhances code readability.
vulnado/src/main/java/com/scalesec/vulnado/User.java:34: AvoidPrintStackTrace: Avoid printStackTrace(); use a logger call instead.
vulnado/src/main/java/com/scalesec/vulnado/User.java:35: PreserveStackTrace: Thrown exception does not preserve the stack trace of exception 'e' on all code paths
vulnado/src/main/java/com/scalesec/vulnado/User.java:40: UnusedAssignment: The initializer for variable 'stmt' is never used (overwritten on line 44)
vulnado/src/main/java/com/scalesec/vulnado/User.java:45: SystemPrintln: Usage of System.out/err
vulnado/src/main/java/com/scalesec/vulnado/User.java:48: SystemPrintln: Usage of System.out/err
vulnado/src/main/java/com/scalesec/vulnado/User.java:58: AvoidPrintStackTrace: Avoid printStackTrace(); use a logger call instead.
vulnado/src/main/java/com/scalesec/vulnado/User.java:59: SystemPrintln: Usage of System.out/err
vulnado/src/test/java/com/scalesec/vulnado/VulnadoApplicationTests.java:13: UnitTestShouldIncludeAssert: This unit test should include assert()
graudit
graudit is essentially a list of strings that will used to search source code for strings that may indicate vulnerabilities. Whilst it’s not very intelligent, it can be useful to help guide manual analysis. Install with;
apt install graudit
Using the -l flag will show the list of rules databases.
graudit -B -l
/usr/share/graudit/actionscript.db
/usr/share/graudit/android.db
/usr/share/graudit/asp.db
/usr/share/graudit/c.db
/usr/share/graudit/cobol.db
/usr/share/graudit/default.db
/usr/share/graudit/dotnet.db
/usr/share/graudit/eiffel.db
/usr/share/graudit/exec.db
/usr/share/graudit/fruit.db
/usr/share/graudit/go.db
/usr/share/graudit/ios.db
/usr/share/graudit/java.db
/usr/share/graudit/js.db
/usr/share/graudit/kotlin.db
/usr/share/graudit/nim.db
/usr/share/graudit/perl.db
/usr/share/graudit/php.db
/usr/share/graudit/python.db
/usr/share/graudit/ruby.db
/usr/share/graudit/scala.db
/usr/share/graudit/secrets-b64.db
/usr/share/graudit/secrets.db
/usr/share/graudit/spsqli.db
/usr/share/graudit/sql.db
/usr/share/graudit/strings.db
/usr/share/graudit/typescript.db
/usr/share/graudit/xss.db
Using graudit, we can search our vulnerable applications source code for strings related to SQL queries. We can see it’s identified string concatenation related to an SQL statement. This would warrant manual investigation.
┌──(kali㉿kali)-[~/…/java/com/scalesec/vulnado]
└─$ graudit -B -d /usr/share/graudit/sql.db *
Comment.java-43- String query = "select * from comments;";
Comment.java:44: ResultSet rs = stmt.executeQuery(query);
Comment.java-45- while (rs.next()) {
##############################################
CommentsController.java-31- Boolean deleteComment(@RequestHeader(value="x-auth-token") String token, @PathVariable("id") String id) {
CommentsController.java:32: return Comment.delete(id);
CommentsController.java-33- }
##############################################
LinkLister.java-15- List<String> result = new ArrayList<String>();
LinkLister.java:16: Document doc = Jsoup.connect(url).get();
LinkLister.java-17- Elements links = doc.select("a");
##############################################
User.java-46-
User.java:47: String query = "select * from users where username = '" + un + "' limit 1";
User.java-48- System.out.println(query);
User.java:49: ResultSet rs = stmt.executeQuery(query);
User.java-50- if (rs.next()) {
Bandit
Bandit is a tool to identify security vulnerabilities in Python code. It can be installed in Kali Linux using;
sudo apt install python3-bandit
Just run bandit with the Python source code directory as it’s parameter. It should automatically find any .py files within the directory, and report on identified issues.
bandit -r DSVPWA
[main] INFO profile include tests: None
[main] INFO profile exclude tests: None
[main] INFO cli include tests: None
[main] INFO cli exclude tests: None
[main] INFO running on Python 3.12.9
Run started:2025-02-19 16:53:15.592436
Test results:
>> Issue: [B608:hardcoded_sql_expressions] Possible SQL injection vector through string-based query construction.
Severity: Medium Confidence: Medium
CWE: CWE-89 (https://cwe.mitre.org/data/definitions/89.html)
More Info: https://bandit.readthedocs.io/en/1.7.10/plugins/b608_hardcoded_sql_expressions.html
Location: DSVPWA/dsvpwa/attacks.py:39:27
38 try:
39 cursor.execute("SELECT id, username, firstname, lastname, email, session FROM users WHERE id=" + id)
40 except sqlite3.OperationalError as e:
--------------------------------------------------
>> Issue: [B602:subprocess_popen_with_shell_equals_true] subprocess call with shell=True identified, security issue.
Severity: High Confidence: High
CWE: CWE-78 (https://cwe.mitre.org/data/definitions/78.html)
More Info: https://bandit.readthedocs.io/en/1.7.10/plugins/b602_subprocess_popen_with_shell_equals_true.html
Location: DSVPWA/dsvpwa/attacks.py:167:25
166 ' '.join([command, domain]),
167 shell=True,
168 stderr=subprocess.STDOUT,
169 stdin=subprocess.PIPE
170 )
171 content = '<pre>{}</pre>'.format(output.decode())
172
173 return content
--------------------------------------------------
>> Issue: [B301:blacklist] Pickle and modules that wrap it can be unsafe when used to deserialize untrusted data, possible security issue.
Severity: Medium Confidence: High
CWE: CWE-502 (https://cwe.mitre.org/data/definitions/502.html)
More Info: https://bandit.readthedocs.io/en/1.7.10/blacklists/blacklist_calls.html#b301-pickle
Location: DSVPWA/dsvpwa/attacks.py:199:26
198 object = params.get('object', '')[0]
199 content = str(pickle.loads(base64.urlsafe_b64decode(object)))
200
--------------------------------------------------
>> Issue: [B310:blacklist] Audit url open for permitted schemes. Allowing use of file:/ or custom schemes is often unexpected.
Severity: Medium Confidence: High
CWE: CWE-22 (https://cwe.mitre.org/data/definitions/22.html)
More Info: https://bandit.readthedocs.io/en/1.7.10/blacklists/blacklist_calls.html#b310-urllib-urlopen
Location: DSVPWA/dsvpwa/attacks.py:213:23
212 else:
213 file = urllib.request.urlopen(path)
214
<SNIP>
Code scanned:
Total lines of code: 607
Total lines skipped (#nosec): 0
Run metrics:
Total issues (by severity):
Undefined: 0
Low: 6
Medium: 7
High: 2
Total issues (by confidence):
Undefined: 0
Low: 0
Medium: 3
High: 12
Files skipped (0):
In Conclusion
Running a combination of the tools discussed in this article should at least provide some findings that can be manually investigated.