JavaScript for XSS
In web browser, go Inspect Element
, then go to Console
.
Let’s review and try some essential JavaScript functions:
-
Alert: You can use the
alert()
function to display a JavaScript alert in a web browser. Tryalert(1)
oralert('XSS')
(oralert("XSS")
) to display an alert box with the number 1 or the text XSS.alert(document.cookie)
to display cookie. -
Console log: Similarly, you can display a value in the browser’s JavaScript console using
console.log()
. Try the following two examples in the console log:console.log(1)
andconsole.log("test text")
to display a number or a text string. -
Encoding: The JavaScript function
btoa("string")
encodes a string of binary data to create a base64-encoded ASCII string. This is useful to remove white space and special characters or encode other alphabets. The reverse function isatob("base64_string")
.
Types of XSS
- Reflected XSS: This attack relies on the user-controlled input reflected to the user. For instance, if you search for a particular term and the resulting page displays the term you searched for (reflected), the attacker would try to embed a malicious script within the search term.
- Stored XSS: This attack relies on the user input stored in the website’s database. For example, if users can write product reviews that are saved in a database (stored) and being displayed to other users, the attacker would try to insert a malicious script in their review so that it gets executed in the browsers of other users.
- DOM-based XSS: This attack exploits vulnerabilities within the Document Object Model (DOM) to manipulate existing page elements without needing to be reflected or stored on the server. This vulnerability is the least common among the three.
XSS Causes
1. Insufficient Input Validation & Sanitization
- Web applications accept user input (e.g., forms) and dynamically generate HTML pages.
- Without proper sanitization, malicious scripts can be embedded and executed.
2. Lack of Output Encoding
- Special characters like
<
,>
,"
,'
, and&
must be encoded in HTML. - JavaScript-specific escaping is required for
'
,"
, and\
. - Failure to encode output leads to XSS vulnerabilities.
3. Improper Use of Security Headers
- Content Security Policy (CSP) mitigates XSS by restricting script sources.
-
Misconfigured CSP (e.g.,
unsafe-inline
,unsafe-eval
) allows attackers to execute scripts.
4. Framework & Language Vulnerabilities
- Older frameworks lacked built-in XSS protections.
- Modern frameworks auto-escape user input but must be kept updated.
5. Third-Party Libraries
- External libraries can introduce XSS vulnerabilities, even in secure applications.
XSS Implications
1. Session Hijacking
- Attackers can steal session cookies and impersonate users.
2. Phishing & Credential Theft
- XSS enables fake login prompts to steal user credentials.
3. Social Engineering
- Attackers can create fake pop-ups or alerts on trusted websites.
4. Content Manipulation & Defacement
- Attackers can modify web pages, damaging a company’s reputation.
5. Data Exfiltration
- XSS can extract sensitive user data (e.g., financial or personal information).
6. Malware Installation
- Drive-by downloads can install malware through XSS exploits.
By implementing proper input validation, output encoding, security headers, and secure coding practices, developers can reduce the risk of XSS attacks. 🚨
Reflected XSS
- PHP
- JavaScript (Node.js)
- Python (Flask)
- C# (ASP.NET)
PHP
Vulnerable code:
You searched for: $search_query";
?>
Example exploitation:
http://shop.thm/search.php?q=
Fixed code:
You searched for: $escaped_search_query";
?>
The PHP function htmlspecialchars()
converts special characters to HTML entities. The characters <
, >
, &
, "
, '
are replaced by default to prevent scripts in the input from executing. You can read its documentation here.
JavaScript (Node.js)
Vulnerable code:
const express = require('express');
const app = express();
app.get('/search', function(req, res) {
var searchTerm = req.query.q;
res.send('You searched for: ' + searchTerm);
});
app.listen(80);
Example exploitation:
http://shop.thm/search.php?q=
Fixed code:
const express = require('express');
const sanitizeHtml = require('sanitize-html');
const app = express();
app.get('/search', function(req, res) {
const searchTerm = req.query.q;
const sanitizedSearchTerm = sanitizeHtml(searchTerm);
res.send('You searched for: ' + sanitizedSearchTerm);
});
app.listen(80);
Using functions like:
- sanitizeHtml(): removes unsafe elements and attributes. This includes removing script tags, among other elements that could be used for malicious purposes. You can read its documentation here.
-
escapeHtml(): escape characters such as
<
,>
,&
,"
, and'
. You can check its homepage here.
Python (Flask)
Vulnerable code:
from flask import Flask, request
app = Flask(__name__)
@app.route("/search")
def home():
query = request.args.get("q")
return f"You searched for: {query}!"
if __name__ == "__main__":
app.run(debug=True)
Example exploitation:
http://shop.thm/search.php?q=
Fixed code:
from flask import Flask, request
from html import escape
app = Flask(__name__)
@app.route("/search")
def home():
query = request.args.get("q")
escaped_query = escape(query)
return f"You searched for: {escaped_query}!"
if __name__ == "__main__":
app.run(debug=True)
html.escape()
(alias for markupsafe.escape()
) escapes unsafe characters in strings. This function converts characters like <, >, ", ' to HTML escaped entities, disarming any malicious code the user has inserted.
C# (ASP.NET)
Vulnerable code:
public void Page_Load(object sender, EventArgs e)
{
var userInput = Request.QueryString["q"];
Response.Write("User Input: " + userInput);
}
Example exploitation:
http://shop.thm/search.php?q=
Fixed code;
using System.Web;
public void Page_Load(object sender, EventArgs e)
{
var userInput = Request.QueryString["q"];
var encodedInput = HttpUtility.HtmlEncode(userInput);
Response.Write("User Input: " + encodedInput);
}
HttpUtility.HtmlEncode()
method, which converts various characters, such as <
, >
, and &
, into their respective HTML entity encoding.
Stored XSS
Ways to Prevent Stored XSS
1. Validate and Sanitize Input
- Define clear rules and enforce strict validation on all user-supplied data.
- Example: Only alphanumeric characters should be used in usernames, and only integers should be allowed in age fields.
- Prevents attackers from injecting harmful code.
2. Use Output Escaping
- Encode all HTML-specific characters, such as
<
,>
, and&
, before displaying user input. - Ensures scripts are not executed in the browser.
3. Apply Context-Specific Encoding
- Use appropriate encoding for different scenarios:
- JavaScript Encoding: When inserting data within JavaScript code.
-
URL Encoding: Use percent-encoding (e.g.,
%20
for spaces,%3C
for<
) to keep URLs valid and prevent script injection.
4. Practice Defense in Depth
- Don't rely on a single layer of defense.
- Server-side validation is crucial as client-side validation can be bypassed by attackers.
- Combining multiple security measures reduces risk.
PHP
Vulnerable code:
// Storing user comment
$comment = $_POST['comment'];
mysqli_query($conn, "INSERT INTO comments (comment) VALUES ('$comment')");
// Displaying user comment
$result = mysqli_query($conn, "SELECT comment FROM comments");
while ($row = mysqli_fetch_assoc($result)) {
echo $row['comment'];
}
Fixed code:
// Storing user comment
$comment = mysqli_real_escape_string($conn, $_POST['comment']);
mysqli_query($conn, "INSERT INTO comments (comment) VALUES ('$comment')");
// Displaying user comment
$result = mysqli_query($conn, "SELECT comment FROM comments");
while ($row = mysqli_fetch_assoc($result)) {
$sanitizedComment = htmlspecialchars($row['comment']);
echo $sanitizedComment;
}
htmlspecialchars()
function to ensure all special characters are converted to HTML entities.
JavaScript (Node.js)
Vulnerable code:
app.get('/comments', (req, res) => {
let html = '';
for (const comment of comments) {
html += `${comment}`;
}
html += '';
res.send(html);
});
Fixed code:
const sanitizeHtml = require('sanitize-html');
app.get('/comments', (req, res) => {
let html = '';
for (const comment of comments) {
const sanitizedComment = sanitizeHtml(comment);
html += `${sanitizedComment}`;
}
html += '';
res.send(html);
});
sanitizeHTML()
to remove potentially dangerous or unsafe elements such as and
.
Python (Flask)
Vulnerable code:
from flask import Flask, request, render_template_string
from flask_sqlalchemy import SQLAlchemy
app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///site.db'
db = SQLAlchemy(app)
class Comment(db.Model):
id = db.Column(db.Integer, primary_key=True)
content = db.Column(db.String, nullable=False)
@app.route('/comment', methods=['POST'])
def add_comment():
comment_content = request.form['comment']
comment = Comment(content=comment_content)
db.session.add(comment)
db.session.commit()
return 'Comment added!'
@app.route('/comments')
def show_comments():
comments = Comment.query.all()
return render_template_string(''.join(['' + c.content + '' for c in comments]))
Fixed code:
from flask import Flask, request, render_template_string, escape
from flask_sqlalchemy import SQLAlchemy
from markupsafe import escape
app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///site.db'
db = SQLAlchemy(app)
class Comment(db.Model):
id = db.Column(db.Integer, primary_key=True)
content = db.Column(db.String, nullable=False)
@app.route('/comment', methods=['POST'])
def add_comment():
comment_content = request.form['comment']
comment = Comment(content=comment_content)
db.session.add(comment)
db.session.commit()
return 'Comment added!'
@app.route('/comments')
def show_comments():
comments = Comment.query.all()
sanitized_comments = [escape(c.content) for c in comments]
return render_template_string(''.join(['' + comment + '' for comment in sanitized_comments]))
We used the escape()
function to ensure that any special characters in the user-submitted comment are replaced with HTML entities. As you would expect, the characters &
, <
, >
, '
, and "
are converted to HTML entities (&
, <
, >
, '
, and "
). We made two changes:
Although the user-submitted input request.form['comment']
is saved verbatim, the content of each saved comment c
goes through the escape() function before it is sent to the user’s browser to be displayed as HTML.
C# (ASP.NET)
Vulnerable code:
public void SaveComment(string userComment)
{
var command = new SqlCommand("INSERT INTO Comments (Comment) VALUES ('" + userComment + "')", connection);
// Execute the command
}
public void DisplayComments()
{
var reader = new SqlCommand("SELECT Comment FROM Comments", connection).ExecuteReader();
while (reader.Read())
{
Response.Write(reader["Comment"].ToString());
}
// Execute the command
}
Fixed code:
using System.Web;
public void SaveComment(string userComment)
{
var command = new SqlCommand("INSERT INTO Comments (Comment) VALUES (@comment)", connection);
command.Parameters.AddWithValue("@comment", userComment);
}
public void DisplayComments()
{
var reader = new SqlCommand("SELECT Comment FROM Comments", connection).ExecuteReader();
while (reader.Read())
{
var comment = reader["Comment"].ToString();
var sanitizedComment = HttpUtility.HtmlEncode(comment);
Response.Write(sanitizedComment);
}
reader.Close();
}
Stored-XSS is fixed by using the HttpUtility.HtmlEncode()
.
SQL injection vulnerability is fixed by using parametrized SQL queries using the Parameters.AddWithValue()
method in the SqlCommand
objects.
DOM-Based XSS
Quite rare compared to Reflected and Stored XSS due to improved security.
DOM-based XSS is completely browser-based and does not need to go to the server and back to the client.
Vulnerable Web Application:
Vulnerable Page
const name = new URLSearchParams(window.location.search).get('name');
document.write("Hello, " + name);
Enter fullscreen mode
Exit fullscreen mode
The page above expects the user to provide their name after ?name=. In the screenshot below:
The user has entered Web Tester after ?name in the URL.
The greeting worked as expected and displayed “Hello, Web Tester”.
Finally, the DOM structure on the right is left intact; the has three direct children.
The user might try to inject a malicious script. In the screenshot below, we see the following:
The user added alert("XSS") instead of only Web Tester as their name.
The script was executed, and an alert dialogue box was displayed.
Most importantly, we can see how the DOM tree got a new element. has four children now.
This basic example illustrates a couple of things:
The server has no direct role in DOM-based vulnerabilities. In this demonstration, everything took place on the client’s browser without using a back end.
The DOM was insecurely modified using document.write().
Fixed site:
Secure Page
const name = new URLSearchParams(window.location.search).get('name');
// Escape the user input to prevent XSS attacks
const escapedName = encodeURIComponent(name);
document.getElementById("greeting").textContent = "Hello, " + escapedName;
Enter fullscreen mode
Exit fullscreen mode
One way to fix this page is by avoiding adding user input directly with document.write(). Instead, we first escaped the user input using encodeURIComponent() and then added it to textContent.
Context
XSS payloads can be injected into different parts of a web page:
Between HTML tags: Example: alert(document.cookie).
Within HTML attributes: Requires breaking out of the attribute, e.g., ">alert(document.cookie).
Inside JavaScript: Requires breaking out of the script, e.g., ';alert(document.cookie)//.
Understanding the context in which the payload is executed is critical for successful exploitation.
Evasion Techniques
Attackers use various techniques to bypass XSS filters:
Using alternate payloads: Resources like the XSS Payload List provide different payloads.
Bypassing length restrictions: Tiny XSS Payloads help evade payload length filters.
Breaking filter detection: Special characters like tabs, new lines, and carriage returns can obfuscate payloads.
Example: Breaking Up Payloads
Horizontal tab (TAB) is 9 in hexadecimal representation
New line (LF) is A in hexadecimal representation
Carriage return (CR) is D in hexadecimal representation
Standard payload:
SRC="javascript:alert('XSS');">
Enter fullscreen mode
Exit fullscreen mode
Broke up payload:
Enter fullscreen mode
Exit fullscreen mode