CSP Bypass in DVWA

1 Introduction
In this post, the Content Security Policy (CSP) Bypass vulnerability in the Damn Vulnerable Web Application (DVWA) is described. The objective for attacks on all levels is to bypass the CSP and execute JavaScript in the vulnerable web page.
The creators of DVWA describe the CSP bypass module in the following way:
Content Security Policy (CSP) is used to define where scripts and other resources can be loaded or executed from. This module will walk you through ways to bypass the policy based on common mistakes made by developers.
None of the vulnerabilities are actual vulnerabilities in CSP, they are vulnerabilities in the way it has been implemented.
2 Lab environment
The penetration test is performed using virtual machines set up on Oracle VirtualBox. The VM specifications can be viewed in table 1 below. Unlike most other attacks in this blog, the CSP bypass vulnerability on the low security level requires internet access because the payload is hosted on an external domain allowed by the CSP policy. For that reason, the attacker device here uses NAT network. If you only want to do the medium and high security levels, you do not need internet access and can therefore disable the Network Adapter 2 on both VMs.
.
Table 1
Lab environment setup.
| Vulnerable DVWA client | Attacker device | |
|---|---|---|
| IP address | 192.168.56.105 | 192.168.56.101 |
| Operating system | Kali GNU/Linux Rolling | Kali GNU/Linux Rolling |
| Network Adapter 1 | Host-only Adapter | Host-only Adapter |
| Network Adapter 2 | Not enabled | NAT |
3 Vulnerability description
For the low security level, the page vulnerable to CSP bypass consists of a URL input field and an include button, see figure 1. The source code reveals that the CSP allows JavaScript from the same domain, and various trusted external domains, and that the input is inserted directly into a script tag; <script src='" . $_POST['include'] . "'></script>.
.
Figure 1
The CSP bypass page in DVWA on the low security level.
.
vulnerabilities/csp/source/low.php:
<?php
$headerCSP = "Content-Security-Policy: script-src 'self' https://pastebin.com hastebin.com www.toptal.com example.com code.jquery.com https://ssl.google-analytics.com unpkg.com cdn.jsdelivr.net digi.ninja ;"; // allows js from various trusted locations
header($headerCSP);
# These might work if you can't create your own for some reason
# https://cdn.jsdelivr.net/gh/digininja/csp_bypass/alert.js
# https://unpkg.com/@digininja/csp_bypass@1.0.0/index.js
?>
<?php
if (isset ($_POST['include'])) {
$page[ 'body' ] .= "
<script src='" . $_POST['include'] . "'></script>
";
}
$page[ 'body' ] .= '
<form name="csp" method="POST">
<p>You can include scripts from external sources, examine the Content Security Policy and enter a URL to include here:</p>
<input size="50" type="text" name="include" value="" id="include" />
<input type="submit" value="Include" />
</form>
<p>
You will probably need to do some reading up on what some of the domains allowed by the CSP do and how they can be used.
</p>
';
.
For the medium security level, the page vulnerable to CSP bypass consists of an input field and an include button, see figure 2. The source code reveals that anything submitted in the input field is dropped directly into the page with no sanitization and that scripts are allowed from the same origin and from 'unsafe-inline'. The CSP also uses a nonce, but as it is static, and therefore predictable, it is possible to include it in an injected script.
.
Figure 2
The CSP bypass page in DVWA on the medium security level.
.
vulnerabilities/csp/source/medium.php:
<?php
$headerCSP = "Content-Security-Policy: script-src 'self' 'unsafe-inline' 'nonce-TmV2ZXIgZ29pbmcgdG8gZ2l2ZSB5b3UgdXA=';";
header($headerCSP);
// Disable XSS protections so that inline alert boxes will work
header ("X-XSS-Protection: 0");
# <script nonce="TmV2ZXIgZ29pbmcgdG8gZ2l2ZSB5b3UgdXA=">alert(1)</script>
?>
<?php
if (isset ($_POST['include'])) {
$page[ 'body' ] .= "
" . $_POST['include'] . "
";
}
$page[ 'body' ] .= '
<form name="csp" method="POST">
<p>Whatever you enter here gets dropped directly into the page, see if you can get an alert box to pop up.</p>
<input size="50" type="text" name="include" value="" id="include" />
<input type="submit" value="Include" />
</form>
';
.
For the high security level, the page vulnerable to CSP bypass consists of a button to "solve the sum" of a mathematical expression. The source code reveals that the CSP only allows scripts from the same origin and that when the button is clicked, the function clickButton() runs. The function creates a new <script> tag and loads source/jsonp.php?callback=solveSum. Another function, solveSum(obj), then inserts the answer into the page.
.
Figure 3
The CSP bypass page in DVWA on the high security level.
.
vulnerabilities/csp/source/high.php:
<?php
$headerCSP = "Content-Security-Policy: script-src 'self';";
header($headerCSP);
?>
<?php
if (isset ($_POST['include'])) {
$page[ 'body' ] .= "
" . $_POST['include'] . "
";
}
$page[ 'body' ] .= '
<form name="csp" method="POST">
<p>The page makes a call to ' . DVWA_WEB_PAGE_TO_ROOT . '/vulnerabilities/csp/source/jsonp.php to load some code. Modify that page to run your own code.</p>
<p>1+2+3+4+5=<span id="answer"></span></p>
<input type="button" id="solve" value="Solve the sum" />
</form>
<script src="source/high.js"></script>
';
.
vulnerabilities/csp/source/high.js:
function clickButton() {
var s = document.createElement("script");
s.src = "source/jsonp.php?callback=solveSum";
document.body.appendChild(s);
}
function solveSum(obj) {
if ("answer" in obj) {
document.getElementById("answer").innerHTML = obj['answer'];
}
}
var solve_button = document.getElementById ("solve");
if (solve_button) {
solve_button.addEventListener("click", function() {
clickButton();
});
}
4 Attack steps
In this section, the attack steps are mapped to the Cyber Kill Chain. The screencast of the attack can be viewed here:
.
4.1 Reconnaissance
Using the network tab in the browser developer tools on the low security level, it is revealed that the CSP allows scripts from the same domain, as well as the following trusted external domains:
https://pastebin.comhastebin.comwww.toptal.comexample.comcode.jquery.comhttps://ssl.google-analytics.comunpkg.comcdn.jsdelivr.netdigi.ninja
Researching the domains reveals that some of them primarily host trusted libraries and do not allow arbitrary user-generated JavaScript, but others do allow user-generated content. It is stated in the instructions that the module was originally created to work with Pastebin, then later Hastebin and Toptal, but all these stopped working as they set various headers that prevent the browser executing the JavaScript once it has downloaded it. Using those domains to deliver the JavaScript would therefore not work. UNPKG, a proxy for NPM packages, and jsDelivr, a proxy for GitHub files, should work though. They are both designed to allow raw access to any files and do not set any headers that will stop injection.
For the medium security level, using the network tab in the browser developer tools reveals that the CSP allows scripts from the same origin and as inline scripts. For this level it requires a nonce though. Submitting random values and inspecting the POST request reveals that the nonce is static and that the same nonce is used for all requests.
For the high security level, using the network tab in the browser developer tools reveals that the CSP allows scripts from the same origin only. Clicking the button on the page results in a GET-request being sent to http://192.168.56.105/DVWA/vulnerabilities/csp/source/jsonp.php?callback=solveSum. As jsonp.php has the same origin, a manipulation of that file or the request to that file should bypass the CSP. Considering that manipulating the file itself would typically be more difficult for an attacker, manipulating the request is the more likely strategy a real attacker would choose.
.
4.2 Weaponization
Using reconnaissance findings, a malicious payload is created for each security level. Note that the payload for the high security level has been encoded with an online URL encoder tool, which is needed because the payload contains spaces and because it in the next step will be delivered inside a URL query parameter.
.
The payloads are available below in this post, but they are also available on GitHub. I have noticed that there are sometimes discrepancies in the code in the final post compared to the draft, which might have something to do with the use of markdown on Hashnode, although I am not entirely sure. So, if the code in this post does not work, please try the code on GitHub instead. The GitHub repository can be found here:
https://github.com/wikblo-0/CSP_Bypass_in_DVWA
.
For the low security level:
alert("If you can read this, CSP has been bypassed on the low security level");
.
For the medium security level:
<script nonce="TmV2ZXIgZ29pbmcgdG8gZ2l2ZSB5b3UgdXA=">
alert("If you can read this, CSP has been bypassed on the medium security level")
</script>
.
For the high security level:
alert("If%20you%20can%20read%20this%2C%20CSP%20has%20been%20bypassed%20on%20the%20high%20security%20level")//
.
4.3 Delivery
For the low security level, jsDelivr is selected as the domain for delivery. As it is a proxy for GitHub files, it requires the creation of a GitHub repository with a file containing the script created during the weaponization.
Create a repository in GitHub and give it a name, e.g. "CSP_Bypass_in_DVWA".
Create a file in the repository and give it a name, e.g. "payload.js".
Paste the script created during the weaponization into the file.
Commit the file to the repository.
Check that it works by pasting the following into the browser, where username, repository name and filename are replace with the actual names:
https://cdn.jsdelivr.net/gh/Username/Repositoryname/FilenameIf you do not wish to create your own script via your own GitHub, you can use mine:
https://cdn.jsdelivr.net/gh/wikblo-0/CSP_Bypass_in_DVWA/low_security_level.jsOnce confirmed that it works, submit the link in the input field on the page in DVWA.
.
For the medium security level, the script is delivered by submitting it directly into the input field.
.
For the high security level, Burp Suite can be used to intercept the GET-request to http://192.168.56.105/DVWA/vulnerabilities/csp/source/jsonp.php?callback=solveSum, after the button has been clicked. The request can then be altered, so that the payload created during weaponization is inserted after callback=, instead of solveSum. The // at the end of the payload causes the original contents of jsonp.php to be commented out and ignored.
.
4.4 Exploitation
For the low security level, CSP is bypassed by exploiting an external domain that allows user-generated content. For the medium security level, the reused static nonce and allowance of scripts from the inline are exploited. For the high security level, the GET-request to jsonp.php is intercepted and exploited. Successful exploitation bypasses the CSP restrictions and results in execution of attacker-controlled JavaScript, effectively enabling a Cross-Site Scripting (XSS) attack.
.
4.5 Installation
In this attack, persistence is not achieved. In a real attack, however, the script could install a web shell, steal session cookies, or inject a persistent backdoor.
.
4.6 Command and Control
Remote control is not achieved in this attack. In a real attack, however, the malicious script might open a WebSocket to the attacker, continuously send stolen data, or receive commands.
.
4.7 Actions on Objectives
The attack objective is to execute JavaScript in the vulnerable web page. The outcome is a message displayed on the screen via a Cross-Site Scripting attack.
5 Evidence of attack success
Evidence of attack success can be viewed in figure 4-7.
.
Figure 4
CSP bypass on the low security level.
.
Figure 5
CSP bypass on the medium security level.
.
Figure 6
CSP bypass on the high security level, part 1.
.
Figure 7
CSP bypass on the high security level, part 2.
6 Impact analysis (CIA)
The successful bypass of the Content Security Policy (CSP) results in the execution of attacker-controlled JavaScript within the victim's browser. This effectively enables a Cross-Site Scripting (XSS) attack, which can affect the confidentiality, integrity and availability of the web application.
A CSP bypass combined with XSS can allow an attacker to access sensitive information available in the user's browser session, which impacts the confidentiality. Examples of potential confidentiality breaches include session cookie theft, where an attacker extracts authentication cookies and impersonate the victim user, and sensitive data exfiltration, where JavaScript is used to read data from the DOM and send it to an attacker-controlled server. Other examples include credential harvesting, where the attacker inject fake login forms to capture user credentials, and access to private application data, where a malicious script retrieve sensitive information displayed on the page of an application where the victim is authenticated. Because the malicious code executes in the context of the trusted website, the browser treats it as legitimate code.
An attacker could also manipulate the contents and behaviour of the web page, compromising data integrity. Examples include modification of page content, where the attacker alter displayed information, injection of malicious scripts or forms, where additional malicious elements are added to trick users into performing unintended actions, and execution of unauthorised actions, where the attacker use the victim's session to perform actions such as changing account settings or submitting transactions. Because the malicious code runs with the user's privileges, the attacker may perform actions as the victim user.
Although XSS attacks typically focus on confidentiality and integrity, they may also affect availability. Examples include client-side denial-of-service, where malicious scripts overload the browser or create infinite loops that freeze the page, service disruption, where injected scripts interfere with application functionality, preventing users from interacting with the system normally, and malware delivery, where attackers redirect users to malicious sites or trigger unwanted downloads. Availability impacts are generally less severe than confidentiality or integrity impacts, but may still affect user experience.
It is worth noting that while the CSP bypass allowed malicious JavaScript to be executed, which enabled XSS, it could also enable additional attacks, such as session hijacking, CSRF automation, phishing, data exfiltration, and malware distribution. These attacks may in turn have significant impact and consequences.
7 Severity assessment (CVSS v4.0)
The vulnerability scores 6.3 (Medium) using the Common Vulnerability Scoring System (CVSS), version 4.0. For this calculation, the online FIRST CVSS v4.0 calculator was used (https://www.first.org/cvss/calculator/4.0#).
Vector: CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:A/VC:N/VI:N/VA:N/SC:H/SI:H/SA:L
.
7.1 Exploitability metrics
The attack vector is network, as the vulnerability can be exploited remotely through a web browser. The attack complexity is low, as the attack requires no special conditions beyond submitting malicious input or manipulating a request. The attack requirements and required privileges are none as no additional requirements or authentication are needed. The needed user interaction is active, as a user must interact with the page. An overview of this can be found in table 2.
.
Table 2
Exploitability metrics.
| Metric | Result |
|---|---|
| Attack Vector (AV) | Network (N) |
| Attack Complexity (AC) | Low (L) |
| Attack Requirements (AT) | None (N) |
| Privileges Required (PR) | None (N) |
| User Interaction (UI) | Active (A) |
.
7.2 Vulnerable system impact metrics
The vulnerable confidentiality, integrity and availability impact are none, as the application server itself is not directly compromised, the server data is not directly modified, and the server remains operational. An overview of this can be found in table 3.
.
Table 3
Vulnerable system impact metrics.
| Metric | Result |
|---|---|
| Confidentiality (VC) | None (N) |
| Integrity (VI) | None (N) |
| Availability (VA) | None (N) |
.
7.3 Subsequent system impact metrics
The subsequent confidentiality impact is high as the malicious script might access session cookies, authentication tokens, and sensitive page data. The subsequent integrity impact is high as the attacker could modify the DOM, perform actions as the user, and submit unauthorised requests. The subsequent availability impact is low, as the attack could degrade the user's browser experience through malicious scripts. An overview of this can be found in table 4.
.
Table 4
Subsequent system impact metrics.
| Metric | Result |
|---|---|
| Confidentiality (SC) | High (H) |
| Integrity (SI) | High (H) |
| Availability (SA) | Low (L) |
8 Mitigation strategies
Possible mitigation strategies include following security best practices for CSP implementation, which could significantly reduce the risk of XSS attacks. This includes avoiding 'unsafe-inline', using dynamic nonces that change for every request, avoid mixing nonce and unsafe-inline, restrict script sources to trusted domains only, and avoid allowing domains that host user-generated content.
Proper input validation and output encoding should also be implemented, as user input should never be inserted directly into HTML or JavaScript without validation or sanitization. Techniques include validating all user inputs using whitelisting, encoding output depending on context, and rejecting or sanitizing potentially dangerous input. This could prevent malicious code from being injected into the page.
JSONP endpoints should be avoided, as JSONP introduces security risks because it executes arbitrary JavaScript. Instead, alternative options include using CORS with JSON APIs, returning pure JSON data instead of executable JavaScript, and validating callback parameters if JSONP must be used. Removing JSONP eliminates a common injection vector.
Secure development practices should be implemented, and developers should follow secure coding guidelines, such as using modern frameworks that automatically escape output, perform regular security code reviews, and conduct penetration testing and vulnerability scanning. These practices reduce the likelihood of introducing injection vulnerabilities.
Additional browser security controls should also be used to reduce the impact of client-side attacks. Examples include HttpOnly cookies to prevent JavaScript from accessing session cookies, secure and SameSite cookie flags, and Subresource Integrity (SRI) for external scripts. These controls help limit what an attacker can do even if an XSS vulnerability exists.
.
8.1 The impossible security level in DVWA
For the impossible security level in DVWA, the page vulnerable to CSP bypass consists of a button to "solve the sum" of a mathematical expression, similar to the high security level. The CSP only allows JavaScript execution from the same origin and when the button is clicked, the browser loads source/jsonp_impossible.php. Because it is loaded without a callback parameter and is hardcoded on the server, it is not possible to modify the callback, the response or the script source.
.
vulnerabilities/csp/source/impossible.php:
<?php
$headerCSP = "Content-Security-Policy: script-src 'self';";
header($headerCSP);
?>
<?php
if (isset ($_POST['include'])) {
$page[ 'body' ] .= "
" . $_POST['include'] . "
";
}
$page[ 'body' ] .= '
<form name="csp" method="POST">
<p>Unlike the high level, this does a JSONP call but does not use a callback, instead it hardcodes the function to call.</p><p>The CSP settings only allow external JavaScript on the local server and no inline code.</p>
<p>1+2+3+4+5=<span id="answer"></span></p>
<input type="button" id="solve" value="Solve the sum" />
</form>
<script src="source/impossible.js"></script>
';
.
vulnerabilities/csp/source/impossible.js:
function clickButton() {
var s = document.createElement("script");
s.src = "source/jsonp_impossible.php";
document.body.appendChild(s);
}
function solveSum(obj) {
if ("answer" in obj) {
document.getElementById("answer").innerHTML = obj['answer'];
}
}
var solve_button = document.getElementById ("solve");
if (solve_button) {
solve_button.addEventListener("click", function() {
clickButton();
});
}






