Source Code Auditing

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 = '&lt;pre>{}&lt;/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

&lt;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.