Insecure Access Control
Fixing Comparison that is Vulnerable to Timing Attackโ
By their nature, string comparisons (or any comparison of
contiguous memory) short-circuit. This means that the
comparison will return false
as soon as 2 different
characters are detected. This helps improve a programming
language's performance but can allow timing attacks to
occur.
For example, say that passwords were compared using ===
a
server. An attacker could then enumerate every single character
that could be used at the start of a password and time how
long each request took. The request that would take the
longest would then be likely to contain the first character
of the password, as the string comparison would
short-circuit later in the comparison process.
Rule-specific references:
Option A: Compare Hashesโ
This method requires the developer to compare hashes instead of comparing plain text. This option also encourages developers to store password hashes instead of plain-text passwords - this is best practice to avoid passwords being leaked in the event of a data breach.
The easiest way of implementing this is to use the bcrypt
npm package, as it
packs information such as the salt, the algorithm, and the
cost into a single string.
Another option would be to use something like
crypto.scrypt
,
however, you would need to store information such as the
cost and the salt manually.
Locate the vulnerable pattern (example below):
const fastify = require('fastify')()
// ...
const authenticate = { realm: "TestRealm" }
fastify.register(require('@fastify/basic-auth'), { validate, authenticate })
function validate(user, pass, req, reply, done) {
if(user in users && pass === users[user].pass) {
done()
} else done(new Error("Authentication isn't valid"));
}Replace it with one of the following patterns (examples below):
Using
bcrypt
const fastify = require('fastify')()
const bcrypt = require('bcrypt')
// ...
const authenticate = { realm: "TestRealm" }
fastify.register(require('@fastify/basic-auth'), { validate, authenticate })
async function validate(user, pass, req, reply, done) {
if(user in users) {
const match = await bcrypt.compare(pass, users[user].passHash)
done()
} else done(new Error("Authentication isn't valid"));
}
// Somewhere else in the code ...
async function newUser(username, pass) {
users[user] = {
username,
passHash: await bcrypt.hash(pass, 20),
}
return users[user];
}Test it
Ship it ๐ข and relax ๐ด
Option B: Perform Constant Time String Comparisonโ
This method entails using a time-agnostic string comparison
algorithm. You can implement these yourself, but it is
recommended that you use a package that implements this for
you such as tsscmp
Locate the vulnerable pattern (example below):
const express = require('express')
const bodyParser = require('body-parser')
const app = express();
app.use(bodyParser.json()); // to support JSON-encoded bodies
app.use(bodyParser.urlencoded({ // to support URL-encoded bodies
extended: true
}));
// ...
const API_KEY = "abcdefghijklmnopqrstuvwxyz"
// ...
app.use('/api/v1', (req, resp, next) => {
if(req.body.apiKey !== API_KEY) {
resp.status(403).send('API key is invalid');
return;
}
next()
})Replace it with one of the following patterns (examples below):
Using
tsscmp
const express = require('express')
const bodyParser = require('body-parser')
const timingSafeCompare = require('tsscmp')
const app = express();
app.use(bodyParser.json()); // to support JSON-encoded bodies
app.use(bodyParser.urlencoded({ // to support URL-encoded bodies
extended: true
}));
// ...
const API_KEY = "abcdefghijklmnopqrstuvwxyz"
// ...
app.use('/api/v1', (req, resp, next) => {
if(!timingSafeCompare(req.body.apiKey, API_KEY)) {
resp.status(403).send('API key is invalid');
return;
}
next()
})Using a Custom XOR String Comparison Algorithm
const express = require('express')
const bodyParser = require('body-parser')
const app = express();
app.use(bodyParser.json()); // to support JSON-encoded bodies
app.use(bodyParser.urlencoded({ // to support URL-encoded bodies
extended: true
}));
// ...
const API_KEY = "abcdefghijklmnopqrstuvwxyz"
// ...
app.use('/api/v1', (req, resp, next) => {
let diff = 0;
for(let i = 0; i < req.body.apiKey; i++) {
let actualApiChar = API_KEY[i] || (req.body.apiKey[i] + 1)
diff |= actualApiChar ^ req.body.apiKey[i]
}
// highlught-next-line
if(diff !== 0) {
resp.status(403).send('API key is invalid');
return;
}
next()
})Test it
Ship it ๐ข and relax ๐ด
Fixing Cross-Site Request Forgeryโ
About Cross-Site Request Forgery (CSRF)
What is Cross-Site Request Forgery?โ
Cross-site request forgery (CSRF or XSRF) is a type of web application vulnerability that allows an attacker to exploit the trust relationship between a user's web browser and a web application. The vulnerability occurs when a web application doesn't adequately verify that a request comes from an authorized user, and it can allow an attacker to force an unsuspecting user to perform actions on the application without their knowledge or consent.
In a CSRF attack, an attacker will create a malicious website or email that contains a link or form that automatically sends a request to the vulnerable web application. If the victim clicks on the link or submits the form, the request will be sent to the web application, and the application will assume that it was initiated by the legitimate user.
Check out this video for a high-level explanation:
What is the impact of Cross-Site Request Forgery?โ
Some potential impacts of CSRF attacks are:
- Unauthorized actions: An attacker can use a CSRF attack to force a user to perform unauthorized actions on the vulnerable web application, such as changing their password, making unauthorized purchases, or deleting or modifying data.
- Data breaches: An attacker can use a CSRF attack to override sensitive information from the vulnerable web application, such as login credentials.
- Financial loss: An attacker can use a CSRF attack to force a user to make unauthorized financial transactions, resulting in financial loss for the victim.
- Reputation damage: A successful attack exploiting a CSRF vulnerability can lead to loss of customer trust and reputational damage for the organization.
- Regulatory violations: A successful attack exploiting a CSRF vulnerability can lead to violations of regulatory requirements, such as data protection laws.
How to prevent Cross-Site Request Forgery?โ
To prevent cross-site request forgery (CSRF) attacks, web applications can implement the following measures:
- Implement CSRF tokens: Use CSRF tokens to ensure that requests are made by legitimate users and not by attackers. The token is a unique value that is generated for each user session, and it is included in each form or link request. The server-side application checks that the token matches the user's session, and if not, it rejects the request.
- Use HTTP Referer header validation: Validate the HTTP Referer header to ensure that requests are coming from the expected source. This can help prevent attackers from spoofing the source of a request.
- Use anti-CSRF frameworks: Use anti-CSRF frameworks or libraries that provide built-in protection against CSRF attacks, such as Django's CSRF protection, Spring Security's CSRF protection, or the OWASP CSRFGuard library.
- Use SameSite cookies: Use SameSite cookies to prevent CSRF attacks that leverage session hijacking through cross-site scripting (XSS) vulnerabilities.
- Limit the use of HTTP methods: Limit the use of sensitive HTTP methods (e.g. PUT, DELETE) to authenticated and authorized users only.
Referencesโ
Taxonomiesโ
Explanation & Preventionโ
- OWASP: Cross-Site Request Forgery (CSRF)
- OWASP: Cross-Site Request Forgery (CSRF) Prevention Cheat Sheet
- OWASP: Testing for Cross-Site Request Forgery (CSRF)
- OWASP: CSRFGuard
- Portswigger - CSRF
Related CVEsโ
Trainingโ
Insecure use of CSRF middlewareโ
The method-override
middleware allows the method of a request to be overridden either by a X-HTTP-Method-Override
header, or a _method
parameter in a form.
However, by default, CSRF middleware doesn't check GET/HEAD/OPTIONS
requests, and is not aware of the method-override
middleware - it won't check for X-HTTP-Method-Override
headers or _method
form data.
This means that a POST
request can masquerade as a GET
request, and bypass any CSRF checks. This is important as GET/HEAD/OPTIONS
requests are (usually) not supposed to modify any important data.
Note that GET/HEAD/OPTIONS
requests that modify important/user data are a potential security issue due to the fact most CSRF middleware isn't configured to check GET/HEAD/OPTIONS
requests.
Using CSRF middleware before method-override
middleware may cause CSRF vulnerabilities.
Rule-specific references:
Option A: Use the correct order of the CSRF middlewareโ
This option is fairly self-explanatory: you simply put the method-override
middleware before the CSRF middleware. This means the method-override
middleware will apply the overridden HTTP method before the request reaches the CSRF middleware.
Locate the vulnerable pattern (example below):
const express = require('express');
const csurf = require('csurf');
const methodoverride = require('method-override');
...
let app = express();
app.use(csurf());
app.use(methodoverride('x-http-method-override'));
...Replace it with one of the following patterns (examples below):
const express = require('express');
const csurf = require('csurf');
const methodOverride = require('method-override');
...
let app = express();
app.use(methodOverride('X-HTTP-Method-Override'));
app.use(csurf());
...Test it
Ship it ๐ข and relax ๐ด
Fixing Open Redirectsโ
About Open Redirects
What are open redirects?โ
Open redirect is a web application vulnerability that occurs when an application uses unvalidated user input as part of a redirect URL.
An attacker can exploit this vulnerability by manipulating the input to redirect a victim to a malicious site. The impact of this vulnerability can range from phishing attacks to theft of sensitive information.
Check out this video for a high-level explanation:
What is the impact of open redirects?โ
An open redirect vulnerability can allow an attacker to:
- Conduct phishing attacks: An attacker can trick a victim into clicking a link that appears to be legitimate but actually redirects them to a malicious website where their sensitive information can be compromised.
- Steal sensitive information: An attacker can craft a URL that appears to be legitimate but redirects to a site that steals the victim's sensitive information, such as login credentials or credit card information.
- Distribute malware: An attacker can use an open redirect to send victims to a site that distributes malware, allowing them to gain control of the victim's device.
How to prevent open redirects?โ
To prevent improper open redirect vulnerabilities, it is important to implement secure coding practices, including:
- Validate redirect URLs: Ensure that all redirect URLs are validated and verified before being used in the application. This can include ensuring that the redirect URL is within the same domain as the current page or using an allow list of permitted domains.
- Avoid using untrusted input: Avoid using untrusted user input as part of a redirect URL. Use a standard URL-building function instead, which allows you to control the parameters used in the redirect URL.
- Implement security headers: Implement security headers such as Content-Security-Policy (CSP) and X-Frame-Options to help prevent open redirect attacks.
- Use secure coding practices: Use secure coding practices to prevent open redirect vulnerabilities. This includes input validation, output encoding, and regular security audits.
Referencesโ
Taxonomiesโ
Explanation & Preventionโ
Related CVEsโ
Trainingโ
Open Redirect (Frontend)โ
Passing untrusted input into window.location.href
can facilitate phishing attacks from legitimate domains.
Option A: Whitelist certain redirect locationsโ
The safest way to fix this kind of vulnerability is to whitelist certain URLs.
Locate the vulnerable pattern (example below):
function vulnerable() {
let req = new XMLHttpRequest();
req.onload = function() {
let redirect_loc = JSON.parse(req.responseText).new_location;
window.location.href = redirect_loc;
};
req.open("https://example.com/get_redirect");
req.send();
}Replace it with one of the following patterns (example below):
Example #1
function vulnerable() {
let req = new XMLHttpRequest();
req.onload = function() {
let redirect_loc = JSON.parse(req.responseText).new_location;
let url = new URL(redirect_loc);
if(url.hostname == window.location.hostname) {
window.location.href = redirect_loc;
}
};
req.open("https://example.com/get_redirect");
req.send();
}Test it
Ship it ๐ข and relax ๐ด
Option B: Assign unique IDs to each valid URLโ
This can be done by having a list of valid URLs, and each ID can be an array index. Other more complex solutions exist (i.e. have a database of authorized URLs) - but the idea is the same.
Locate the vulnerable pattern (example below):
function vulnerable() {
let req = new XMLHttpRequest();
req.onload = function() {
let redirect_loc = JSON.parse(req.responseText).new_location;
window.location.href = redirect_loc;
};Replace it with one of the following patterns (example below):
const REDIRECTS = [
"#header-1",
"#header-2",
"/login",
"/home"
];
function safe() {
let req = new XMLHttpRequest();
req.onload = function() {
let redirectID = JSON.parse(req.responseText).id;
if(redirectID < REDIRECTS.length) {
window.location.href = redirects[redirectID];
}
};
req.open("https://example.com/get_redirect_id");
req.send();
}Test it
Ship it ๐ข and relax ๐ด