7408fe775baeImproper Neutralization of Special Elements used in an OS Command ('OS Command Injection')
src/main/java/com/scalesec/vulnado/Cowsay.java
com.scalesec.vulnado.Cowsay.runprocessBuilder.command("bash", "-c", cmd);
5: public static String run(String input) {
6: ProcessBuilder processBuilder = new ProcessBuilder();
7: String cmd = "/usr/games/cowsay '" + input + "'";
8: System.out.println(cmd);
9: processBuilder.command("bash", "-c", cmd);
10:
11: StringBuilder output = new StringBuilder();
12:
13: try {
14: Process process = processBuilder.start();
15: BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
An attacker can execute arbitrary system commands on the server by injecting shell metacharacters into the input parameter. The single quotes around the input can be escaped using techniques like '; malicious_command; # or ' && malicious_command && '. This allows complete system compromise including data exfiltration, privilege escalation, lateral movement, and denial of service attacks.
input → cmdString cowsay(@RequestParam(defaultValue = "I love Linux!") String input) { → input
return Cowsay.run(input); → input
String cmd = "/usr/games/cowsay '" + input + "'";; → cmd
processBuilder.command("bash", "-c", cmd); → cmd
1. Use ProcessBuilder with separate arguments instead of shell execution 2. Implement strict input validation and sanitization 3. Use allowlist validation for acceptable characters 4. Consider using safer alternatives that don't involve system command execution
public static String run(String input) {
// Input validation - only allow alphanumeric, spaces, and basic punctuation
if (!input.matches("^[a-zA-Z0-9\\s.,!?-]+$")) {
throw new IllegalArgumentException("Invalid input characters");
}
// Use ProcessBuilder with separate arguments (no shell)
ProcessBuilder processBuilder = new ProcessBuilder("/usr/games/cowsay", input);
StringBuilder output = new StringBuilder();
try {
Process process = processBuilder.start();
BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
String line;
while ((line = reader.readLine()) != null) {
output.append(line).append("\n");
}
} catch (Exception e) {
e.printStackTrace();
}
return output.toString();
}
06b096966a82Server-Side Request Forgery (SSRF)
src/main/java/com/scalesec/vulnado/LinkLister.java
com.scalesec.vulnado.LinkLister.getLinksDocument doc = Jsoup.connect(url).get();
11: public static List<String> getLinks(String url) throws IOException {
12: List<String> result = new ArrayList<String>();
13: Document doc = Jsoup.connect(url).get();
14: Elements links = doc.select("a");
15: for (Element link : links) {
16: result.add(link.absUrl("href"));
17: }
18: return result;
19: }
20:
21: public static List<String> getLinksV2(String url) throws BadRequest {
An attacker can force the server to make HTTP requests to arbitrary URLs, including internal services, localhost, and private network resources. This can lead to: 1) Access to internal services and APIs not exposed to the internet, 2) Port scanning of internal networks, 3) Reading local files via file:// protocol, 4) Potential remote code execution if internal services have vulnerabilities, 5) Bypassing firewalls and network segmentation, 6) Information disclosure from internal systems.
urlList<String> links(@RequestParam String url) throws IOException{ → url
return LinkLister.getLinks(url); → url
Document doc = Jsoup.connect(url).get(); → url
1) Implement URL validation with an allowlist of permitted domains/protocols, 2) Block private IP ranges (10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16, 127.0.0.0/8), 3) Only allow HTTP/HTTPS protocols, 4) Use DNS resolution validation to prevent DNS rebinding attacks, 5) Implement request timeouts and size limits, 6) Consider using a proxy service for external requests.
public static List<String> getLinks(String url) throws IOException, BadRequest {
// Validate URL format
URL validatedUrl;
try {
validatedUrl = new URL(url);
} catch (MalformedURLException e) {
throw new BadRequest("Invalid URL format");
}
// Only allow HTTP/HTTPS
String protocol = validatedUrl.getProtocol().toLowerCase();
if (!"http".equals(protocol) && !"https".equals(protocol)) {
throw new BadRequest("Only HTTP/HTTPS protocols allowed");
}
// Block private IP ranges
String host = validatedUrl.getHost();
if (isPrivateIP(host)) {
throw new BadRequest("Access to private IP ranges not allowed");
}
// Allowlist of permitted domains
List<String> allowedDomains = Arrays.asList("example.com", "trusted-site.com");
if (!allowedDomains.contains(host)) {
throw new BadRequest("Domain not in allowlist");
}
List<String> result = new ArrayList<String>();
Document doc = Jsoup.connect(url)
.timeout(5000)
.maxBodySize(1024 * 1024) // 1MB limit
.get();
Elements links = doc.select("a");
for (Element link : links) {
result.add(link.absUrl("href"));
}
return result;
}
private static boolean isPrivateIP(String host) {
return host.startsWith("127.") || host.startsWith("10.") ||
host.startsWith("192.168.") || host.startsWith("172.") ||
"localhost".equals(host);
}
1bc131b18696Improper Neutralization of Special Elements used in an SQL Command ('SQL Injection')
src/main/java/com/scalesec/vulnado/User.java
com.scalesec.vulnado.User.fetchString query = "select * from users where username = '" + un + "' limit 1";
37: Connection cxn = Postgres.connection();
38: stmt = cxn.createStatement();
39: System.out.println("Opened database successfully");
40:
41: String query = "select * from users where username = '" + un + "' limit 1";
42: System.out.println(query);
43: ResultSet rs = stmt.executeQuery(query);
44: if (rs.next()) {
45: String user_id = rs.getString("user_id");
46: String username = rs.getString("username");
47: String password = rs.getString("password");
An attacker can inject malicious SQL code through the username parameter in login requests. This allows complete database compromise including: extracting all user credentials, bypassing authentication, modifying/deleting data, and potentially executing system commands depending on database permissions. The vulnerability enables authentication bypass and complete application takeover.
input.username → un → queryUser user = User.fetch(input.username); → input.username
public static User fetch(String un) { → un
String query = "select * from users where username = '" + un + "' limit 1"; → un
Replace string concatenation with parameterized queries using PreparedStatement to prevent SQL injection attacks.
public static User fetch(String un) {
PreparedStatement pstmt = null;
User user = null;
try {
Connection cxn = Postgres.connection();
String query = "select * from users where username = ? limit 1";
pstmt = cxn.prepareStatement(query);
pstmt.setString(1, un);
ResultSet rs = pstmt.executeQuery();
if (rs.next()) {
String user_id = rs.getString("user_id");
String username = rs.getString("username");
String password = rs.getString("password");
user = new User(user_id, username, password);
}
cxn.close();
} catch (Exception e) {
e.printStackTrace();
} finally {
return user;
}
}
0e1a3818fcb5Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting')
client/js/index.js
fetchComments$("#comments-container").append(template(comment));
27: function fetchComments() {
28: $.get("http://localhost:8080/comments", function(data){
29: $('#comments-container').html('')
30: data.forEach(function(comment){
31: if (comment.body.indexOf("<script>") < 0) {
32: $("#comments-container").append(template(comment));
33: }
34: });
35: setupDeleteCommentHandler();
36: });
37: }
An attacker can inject malicious JavaScript code through comment data that bypasses the weak XSS filter. The filter only checks for '<script>' tags but Handlebars templates can execute JavaScript through various other vectors like {{constructor.constructor('alert(1)')()}} or event handlers in HTML attributes. This allows for session hijacking, credential theft, defacement, and arbitrary actions on behalf of users.
data → comment$.get("http://localhost:8080/comments", function(data){ → data
data.forEach(function(comment){ → comment
$("#comments-container").append(template(comment)); → comment
1. Use Handlebars' built-in HTML escaping by default (triple braces {{{ }}} should be avoided). 2. Implement proper server-side input validation and output encoding. 3. Use Content Security Policy (CSP) headers. 4. Replace the weak indexOf filter with a proper HTML sanitization library like DOMPurify.
// Use DOMPurify for proper sanitization
function fetchComments() {
$.get("http://localhost:8080/comments", function(data){
$('#comments-container').html('')
data.forEach(function(comment){
// Sanitize the comment body before templating
comment.body = DOMPurify.sanitize(comment.body);
$("#comments-container").append(template(comment));
});
setupDeleteCommentHandler();
});
}
08ed39ec7a32Server-Side Request Forgery (SSRF)
src/main/java/com/scalesec/vulnado/LinkLister.java
com.scalesec.vulnado.LinkLister.getLinksV2Document doc = Jsoup.connect(url).get();
21: public static List<String> getLinksV2(String url) throws BadRequest {
22: try {
23: URL aUrl= new URL(url);
24: String host = aUrl.getHost();
25: System.out.println(host);
26: if (host.startsWith("172.") || host.startsWith("192.168") || host.startsWith("10.")){
27: throw new BadRequest("Use of Private IP");
28: } else {
29: return getLinks(url);
30: }
31: } catch(Exception e) {
32: throw new BadRequest(e.getMessage());
33: }
The private IP validation can be bypassed through multiple attack vectors: 1) Localhost access via 127.0.0.1, ::1, or 'localhost', 2) Link-local addresses (169.254.x.x), 3) DNS rebinding attacks using malicious domains that resolve to private IPs, 4) URL encoding or alternative IP representations (hex, octal, integer format), 5) IPv6 private ranges, 6) Non-HTTP protocols like file://, ftp://, gopher://. Attackers can still access internal services and perform SSRF attacks.
url → hostList<String> linksV2(@RequestParam String url) throws BadRequest{ → url
return LinkLister.getLinksV2(url); → url
return getLinks(url); → url
Document doc = Jsoup.connect(url).get(); → url
1) Implement comprehensive IP range validation including localhost (127.0.0.0/8), link-local (169.254.0.0/16), and IPv6 private ranges, 2) Resolve hostnames to IP addresses and validate the resolved IPs, 3) Block non-HTTP/HTTPS protocols, 4) Use proper IP address parsing libraries instead of string matching, 5) Implement DNS resolution validation to prevent rebinding attacks, 6) Consider using a dedicated service or proxy for external requests.
public static List<String> getLinksV2(String url) throws BadRequest {
try {
URL aUrl = new URL(url);
String protocol = aUrl.getProtocol().toLowerCase();
// Only allow HTTP/HTTPS
if (!"http".equals(protocol) && !"https".equals(protocol)) {
throw new BadRequest("Only HTTP/HTTPS protocols allowed");
}
String host = aUrl.getHost();
// Resolve hostname to IP and validate
InetAddress[] addresses = InetAddress.getAllByName(host);
for (InetAddress addr : addresses) {
if (isPrivateOrLocalAddress(addr)) {
throw new BadRequest("Access to private/local IP ranges not allowed");
}
}
return getLinks(url);
} catch (UnknownHostException e) {
throw new BadRequest("Unable to resolve hostname");
} catch (Exception e) {
throw new BadRequest("Invalid URL: " + e.getMessage());
}
}
private static boolean isPrivateOrLocalAddress(InetAddress addr) {
return addr.isLoopbackAddress() ||
addr.isLinkLocalAddress() ||
addr.isSiteLocalAddress() ||
addr.isAnyLocalAddress();
}
e83edd1d4eeaUse of Unmaintained Third Party Components
pom.xml
N/A<version>1.8.3</version>
33: <dependency> 34: <groupId>org.jsoup</groupId> 35: <artifactId>jsoup</artifactId> 36: <version>1.8.3</version> 37: </dependency>
The application uses JSoup version 1.8.3, which is significantly outdated (released in 2015) and contains multiple known security vulnerabilities including CVE-2021-37714 (XSS bypass), CVE-2022-36033 (XSS bypass), and other parsing vulnerabilities. These vulnerabilities can allow attackers to bypass HTML sanitization, leading to Cross-Site Scripting attacks when processing untrusted HTML content.
jsoup<version>1.8.3</version> → jsoup.version
<version>1.8.3</version> → jsoup.version
Update JSoup to the latest stable version (1.15.3 or newer) to address known security vulnerabilities. Review all usage of JSoup in the codebase to ensure proper sanitization practices.
<dependency>
<groupId>org.jsoup</groupId>
<artifactId>jsoup</artifactId>
<version>1.15.3</version>
</dependency>
6d2808fe076eUse of Hard-coded Credentials
src/main/resources/application.properties
N/Aapp.secret=edf10572-880c-4dd9-aaf0-6ec402f678db
1: app.secret=edf10572-880c-4dd9-aaf0-6ec402f678db
The hardcoded secret 'edf10572-880c-4dd9-aaf0-6ec402f678db' is exposed in the application configuration file. This secret appears to be a UUID that could be used for JWT signing, encryption keys, or other security-critical operations. Attackers with access to the source code, configuration files, or deployed application can extract this secret and potentially forge tokens, decrypt sensitive data, or bypass authentication mechanisms.
app.secretapp.secret=edf10572-880c-4dd9-aaf0-6ec402f678db → app.secret
app.secret=edf10572-880c-4dd9-aaf0-6ec402f678db → app.secret
Remove the hardcoded secret from the configuration file and use environment variables or a secure secret management system. Configure the application to read secrets from environment variables at runtime.
# Remove hardcoded secret
# app.secret=edf10572-880c-4dd9-aaf0-6ec402f678db
# Use environment variable instead
app.secret=${APP_SECRET:}
# Or use Spring Boot's encrypted properties
# app.secret={cipher}AQA...
efa741d11f8eImproper Neutralization of Input During Web Page Generation ('Cross-site Scripting')
client/index.html
handlebars.template.render<p class="card-text">{{{body}}}</p>
62: <div class="card-header">
63: <strong>{{username}}</strong> commented at {{created_on}}
64: <span class="delete-comment">
65: <img src="images/trash.png" height=15px/>
66: </span>
67: </div>
68: <div class="card-body">
69: <p class="card-text">{{{body}}}</p>
70: </div>
71: </div>
72: </div><!-- /col-sm-12 -->
Attackers can inject arbitrary JavaScript code into comment bodies that will execute in other users' browsers when they view the comments. This enables session hijacking, credential theft, defacement, malware distribution, and complete compromise of user accounts. The triple-brace syntax {{{body}}} in Handlebars bypasses HTML escaping, making this a stored XSS vulnerability affecting all users who view the malicious comment.
body → comment_body<input type="text" id="new-comment" class="form-control" placeholder="Comment"> → comment_body
comment submission via AJAX → comment_body
<p class="card-text">{{{body}}}</p> → body
Replace triple-brace {{{body}}} with double-brace {{body}} to enable HTML escaping, implement server-side input validation and sanitization, use Content Security Policy headers, and validate/sanitize data on both client and server sides.
<!-- Use double braces for HTML escaping -->
<p class="card-text">{{body}}</p>
<!-- Add CSP header -->
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self' 'unsafe-inline' https://code.jquery.com https://cdnjs.cloudflare.com https://maxcdn.bootstrapcdn.com; style-src 'self' 'unsafe-inline' https://maxcdn.bootstrapcdn.com;">
a6f0d41d5cbcUse of Hard-coded Credentials
docker-compose.yml
environment.variable- PGPASSWORD=vulnado
5: ports: 6: - 8080:8080 7: links: 8: - db 9: - internal_site 10: environment: 11: - PGPASSWORD=vulnado 12: - PGDATABASE=vulnado 13: - PGHOST=db:5432 14: - PGUSER=postgres 15: depends_on:
Hardcoded database credentials in configuration files expose the database password to anyone with access to the source code repository. This enables unauthorized database access, data theft, data manipulation, and potential complete system compromise. The credentials are visible in version control history and deployment artifacts.
PGPASSWORD- PGPASSWORD=vulnado → PGPASSWORD
- PGPASSWORD=vulnado → PGPASSWORD
Use environment variables, Docker secrets, or external secret management systems. Never commit credentials to version control. Use .env files that are excluded from git, or runtime secret injection.
# Use external environment variables or secrets
environment:
- PGPASSWORD=${DB_PASSWORD} # Set externally
- PGDATABASE=${DB_NAME}
- PGHOST=db:5432
- PGUSER=${DB_USER}
# Or use Docker secrets
secrets:
- db_password
# In .env file (not committed to git):
# DB_PASSWORD=secure_random_password
# DB_NAME=vulnado
# DB_USER=postgres
622fc0d92299Use of Hard-coded Credentials
docker-compose.yml
environment.variable- POSTGRES_PASSWORD=vulnado
20: db: 21: image: postgres 22: environment: 23: - POSTGRES_PASSWORD=vulnado 24: - POSTGRES_DB=vulnado 25: 26: internal_site: 27: build: internal_site
Hardcoded PostgreSQL root password exposes the database to unauthorized access. Anyone with access to the source code can connect to the database with full administrative privileges, enabling data theft, data corruption, privilege escalation, and complete database compromise.
POSTGRES_PASSWORD- POSTGRES_PASSWORD=vulnado → POSTGRES_PASSWORD
- POSTGRES_PASSWORD=vulnado → POSTGRES_PASSWORD
Use environment variables, Docker secrets, or external secret management. Generate strong random passwords and inject them at runtime rather than hardcoding in configuration files.
# Use external environment variables
db:
image: postgres
environment:
- POSTGRES_PASSWORD=${POSTGRES_ROOT_PASSWORD}
- POSTGRES_DB=${POSTGRES_DB_NAME}
secrets:
- postgres_password
# Define secrets
secrets:
postgres_password:
external: true
dc9a39a8f8efMissing Authorization
src/main/java/com/scalesec/vulnado/CommentsController.java
com.scalesec.vulnado.CommentsController.createCommentComment createComment(@RequestHeader(value="x-auth-token") String token, @RequestBody CommentRequest input) {
17: @CrossOrigin(origins = "*")
18: @RequestMapping(value = "/comments", method = RequestMethod.POST, produces = "application/json", consumes = "application/json")
19: Comment createComment(@RequestHeader(value="x-auth-token") String token, @RequestBody CommentRequest input) {
20: return Comment.create(input.username, input.body);
21: }
22:
The createComment endpoint accepts an authentication token but never validates it, allowing unauthenticated users to create comments with arbitrary usernames. Attackers can impersonate any user, spam the system with fake comments, and potentially conduct social engineering attacks by posting malicious content under trusted usernames.
token → inputComment createComment(@RequestHeader(value="x-auth-token") String token, @RequestBody CommentRequest input) { → token
return Comment.create(input.username, input.body); → input
Add authentication validation by calling User.assertAuth() before processing the comment creation request.
@CrossOrigin(origins = "*")
@RequestMapping(value = "/comments", method = RequestMethod.POST, produces = "application/json", consumes = "application/json")
Comment createComment(@RequestHeader(value="x-auth-token") String token, @RequestBody CommentRequest input) {
User.assertAuth(secret, token);
return Comment.create(input.username, input.body);
}
b3e22c4f3e78Missing Authorization
src/main/java/com/scalesec/vulnado/CommentsController.java
com.scalesec.vulnado.CommentsController.deleteCommentBoolean deleteComment(@RequestHeader(value="x-auth-token") String token, @PathVariable("id") String id) {
23: @CrossOrigin(origins = "*")
24: @RequestMapping(value = "/comments/{id}", method = RequestMethod.DELETE, produces = "application/json")
25: Boolean deleteComment(@RequestHeader(value="x-auth-token") String token, @PathVariable("id") String id) {
26: return Comment.delete(id);
27: }
28: }
The deleteComment endpoint accepts an authentication token but never validates it, allowing unauthenticated users to delete any comment by providing its ID. Attackers can perform denial of service attacks by deleting legitimate comments, conduct censorship attacks, and disrupt normal application functionality without any authentication.
token → idBoolean deleteComment(@RequestHeader(value="x-auth-token") String token, @PathVariable("id") String id) { → token
return Comment.delete(id); → id
Add authentication validation by calling User.assertAuth() before processing the comment deletion request.
@CrossOrigin(origins = "*")
@RequestMapping(value = "/comments/{id}", method = RequestMethod.DELETE, produces = "application/json")
Boolean deleteComment(@RequestHeader(value="x-auth-token") String token, @PathVariable("id") String id) {
User.assertAuth(secret, token);
return Comment.delete(id);
}
42a406251df4Use of a Broken or Risky Cryptographic Algorithm
src/main/java/com/scalesec/vulnado/Postgres.java
com.scalesec.vulnado.Postgres.md5MessageDigest md = MessageDigest.getInstance("MD5");
42: public static String md5(String input)
43: {
44: try {
45:
46: // Static getInstance method is called with hashing MD5
47: MessageDigest md = MessageDigest.getInstance("MD5");
48:
49: // digest() method is called to calculate message digest
50: // of an input digest() return array of byte
51: byte[] messageDigest = md.digest(input.getBytes());
52:
MD5 is cryptographically broken and vulnerable to collision attacks and rainbow table attacks. Attackers can easily crack MD5 hashes using precomputed tables or brute force attacks, especially for common passwords. This compromises all user passwords stored in the database and enables account takeover attacks.
input.password → inputif (Postgres.md5(input.password).equals(user.hashedPassword)) { → input.password
public static String md5(String input) → input
MessageDigest md = MessageDigest.getInstance("MD5"); → input
Replace MD5 with a strong password hashing algorithm like bcrypt, scrypt, or Argon2 that includes salt and is designed to be computationally expensive.
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
public static String hashPassword(String password) {
BCryptPasswordEncoder encoder = new BCryptPasswordEncoder(12);
return encoder.encode(password);
}
public static boolean verifyPassword(String password, String hashedPassword) {
BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
return encoder.matches(password, hashedPassword);
}
c9b659e2efb5Authorization Bypass Through User-Controlled Key
client/js/index.js
setupDeleteCommentHandlerurl: "http://localhost:8080/comments/" + id
12: $('.delete-comment').click(function(){
13: var parent = this.closest(".row");
14: var id = $(parent).data("comment_id");
15:
16: $.ajax({
17: type: "DELETE",
18: url: "http://localhost:8080/comments/" + id
19: }).done(function(){
20: $(parent).remove();
21: });
22: });
An attacker can manipulate the comment_id data attribute in the DOM to delete any comment by changing the ID value. This allows unauthorized deletion of other users' comments, potentially leading to data loss and violation of data integrity. The client-side code trusts the DOM data without server-side authorization checks.
idvar id = $(parent).data("comment_id"); → id
url: "http://localhost:8080/comments/" + id → id
1. Implement proper server-side authorization to verify the user owns the comment before deletion. 2. Use session-based authentication to validate user permissions. 3. Add CSRF protection. 4. Log deletion attempts for audit purposes.
// Client-side: Add CSRF token
$('.delete-comment').click(function(){
var parent = this.closest(".row");
var id = $(parent).data("comment_id");
if (!confirm('Are you sure you want to delete this comment?')) {
return;
}
$.ajax({
type: "DELETE",
url: "http://localhost:8080/comments/" + id,
headers: {
'X-CSRF-Token': $('meta[name="csrf-token"]').attr('content')
}
}).done(function(){
$(parent).remove();
}).fail(function(xhr) {
alert('Failed to delete comment: ' + xhr.responseText);
});
});
42a37c9ee904Insecure Storage of Sensitive Information
client/js/index.js
$.ajaxSetupxhr.setRequestHeader('x-auth-token', localStorage.jwt);
2: var source = $("#comment-template").html();
3: var template = Handlebars.compile(source);
4:
5: // Add JWT to every request
6: $.ajaxSetup({ beforeSend: function(xhr) {
7: xhr.setRequestHeader('x-auth-token', localStorage.jwt);
8: }});
9:
10: // Helper Functions
11: function setupDeleteCommentHandler() {
12: // NOTE: This needs to come first since comments aren't loaded yet.
JWT tokens and usernames stored in localStorage are accessible to any JavaScript code running on the same origin, including malicious scripts injected via XSS attacks. This data persists across browser sessions and can be accessed by browser extensions or local malware. An attacker gaining access to these tokens can impersonate users and perform unauthorized actions.
localStorage.jwt → localStorage.usernamexhr.setRequestHeader('x-auth-token', localStorage.jwt); → localStorage.jwt
xhr.setRequestHeader('x-auth-token', localStorage.jwt); → localStorage.jwt
1. Store JWT tokens in httpOnly, secure cookies instead of localStorage. 2. Implement proper session management with shorter token lifetimes. 3. Use secure, httpOnly cookies for authentication. 4. Implement token refresh mechanisms. 5. Clear sensitive data on logout.
// Remove localStorage usage for sensitive data
// Server should set httpOnly cookies instead
$.ajaxSetup({
beforeSend: function(xhr) {
// JWT will be sent automatically via httpOnly cookie
// No need to manually set headers
},
xhrFields: {
withCredentials: true // Include cookies in requests
}
});
// Clear any existing localStorage data
$('#signout').click(function(){
alert("Goodbye!");
// Clear localStorage completely
localStorage.clear();
// Server should invalidate the session cookie
$.post('/logout').always(function() {
window.location.replace("login.html");
});
});
3e2ec88f35f0Cleartext Transmission of Sensitive Information
client/index.html
script.load<script src="http://builds.handlebarsjs.com.s3.amazonaws.com/handlebars.min-v4.1.0.js"></script>
49: <!-- Optional JavaScript -->
50: <script src="https://code.jquery.com/jquery-3.2.1.min.js"></script>
51: <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.12.9/umd/popper.min.js" integrity="sha384-ApNbgh9B+Y1QKtv3Rn7W3mgPxhU9K/ScQsAP7hUibX39j7fakFPskvXusvfa0b4Q" crossorigin="anonymous"></script>
52: <script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/js/bootstrap.min.js" integrity="sha384-JZR6Spejh4U02d8jOt6vLEHfe/JQGiRRSQQxSfFWpi1MquVdAyjUar5+76PVCmYl" crossorigin="anonymous"></script>
53: <script src="http://builds.handlebarsjs.com.s3.amazonaws.com/handlebars.min-v4.1.0.js"></script>
54: <script src="js/index.js"></script>
55: <script id="comment-template" type="text/x-handlebars-template">
56: <div class="row" data-comment_id="{{id}}">
57: <div class="col-sm-12" style="padding:5px">
58: <div class="card">
59: <div class="card-header">
Loading JavaScript libraries over HTTP creates multiple security risks: Man-in-the-middle attacks can inject malicious code, mixed content warnings in browsers, potential for script tampering during transmission, and violation of security best practices. When served over HTTPS, this creates mixed content that browsers may block or warn about.
handlebars_script<script src="http://builds.handlebarsjs.com.s3.amazonaws.com/handlebars.min-v4.1.0.js"></script> → handlebars_script
<script src="http://builds.handlebarsjs.com.s3.amazonaws.com/handlebars.min-v4.1.0.js"></script> → handlebars_script
Change HTTP URLs to HTTPS, add Subresource Integrity (SRI) hashes to verify script authenticity, and consider hosting critical libraries locally to reduce external dependencies.
<!-- Use HTTPS and add SRI hash --> <script src="https://cdn.jsdelivr.net/npm/handlebars@4.1.0/dist/handlebars.min.js" integrity="sha384-[SRI-HASH]" crossorigin="anonymous"></script>
c50f9344e640Insecure Storage of Sensitive Information
client/js/login.js
localStorage.setItemlocalStorage.jwt = data.token;
12: })
13: .fail(function(data){
14: alert("Whoops, try again");
15: })
16: .done(function(data){
17: localStorage.jwt = data.token;
18: var username = JSON.parse(atob(data.token.split('.')[1]))['sub'];
19: localStorage.username = username;
20: window.location.replace("index.html");
21: })
22: })
Storing JWT tokens in localStorage makes them accessible to any JavaScript code running on the page, including malicious scripts injected via XSS attacks. Unlike httpOnly cookies, localStorage data persists across browser sessions and can be easily accessed by attackers, leading to session hijacking and unauthorized access to user accounts.
data.token → localStorage.jwtlocalStorage.jwt = data.token; → data.token
localStorage.jwt = data.token; → localStorage.jwt
Store JWT tokens in httpOnly, secure cookies instead of localStorage. Implement proper CSRF protection when using cookies, and consider using short-lived tokens with refresh token rotation.
// Server should set httpOnly cookie instead
// Remove localStorage usage
// .done(function(data){
// // Token will be automatically stored in httpOnly cookie by server
// var username = JSON.parse(atob(data.token.split('.')[1]))['sub'];
// sessionStorage.username = username; // Only store non-sensitive data
// window.location.replace("index.html");
// })
9046310329d6Cleartext Transmission of Sensitive Information
client/login.html
script.load<script src="http://builds.handlebarsjs.com.s3.amazonaws.com/handlebars.min-v4.1.0.js"></script>
34: </body> 35: <script src="https://code.jquery.com/jquery-3.2.1.min.js"></script> 36: <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.12.9/umd/popper.min.js" integrity="sha384-ApNbgh9B+Y1QKtv3Rn7W3mgPxhU9K/ScQsAP7hUibX39j7fakFPskvXusvfa0b4Q" crossorigin="anonymous"></script> 37: <script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/js/bootstrap.min.js" integrity="sha384-JZR6Spejh4U02d8jOt6vLEHfe/JQGiRRSQQxSfFWpi1MquVdAyjUar5+76PVCmYl" crossorigin="anonymous"></script> 38: <script src="http://builds.handlebarsjs.com.s3.amazonaws.com/handlebars.min-v4.1.0.js"></script> 39: <script src="js/login.js"></script> 40: </html>
Loading JavaScript libraries over HTTP on the login page creates critical security risks: Man-in-the-middle attacks can inject credential-stealing code, compromise the authentication process, and potentially capture user login credentials. This is especially dangerous on a login page where sensitive authentication data is processed.
handlebars_script<script src="http://builds.handlebarsjs.com.s3.amazonaws.com/handlebars.min-v4.1.0.js"></script> → handlebars_script
<script src="http://builds.handlebarsjs.com.s3.amazonaws.com/handlebars.min-v4.1.0.js"></script> → handlebars_script
Change HTTP URLs to HTTPS, add Subresource Integrity (SRI) hashes, and consider removing unused libraries from the login page to minimize attack surface.
<!-- Use HTTPS and add SRI hash, or remove if not needed --> <script src="https://cdn.jsdelivr.net/npm/handlebars@4.1.0/dist/handlebars.min.js" integrity="sha384-[SRI-HASH]" crossorigin="anonymous"></script>
ab46dd7f7cdfPermissive Cross-domain Policy with Untrusted Domains
src/main/java/com/scalesec/vulnado/CommentsController.java
com.scalesec.vulnado.CommentsController.comments@CrossOrigin(origins = "*")
10: private String secret;
11:
12: @CrossOrigin(origins = "*")
13: @RequestMapping(value = "/comments", method = RequestMethod.GET, produces = "application/json")
14: List<Comment> comments(@RequestHeader(value="x-auth-token") String token) {
15: User.assertAuth(secret, token);
16: return Comment.fetch_all();
The wildcard CORS policy allows any domain to make authenticated requests to the API endpoints. This enables malicious websites to perform cross-origin requests using victims' authentication tokens, potentially leading to data theft, unauthorized actions, and CSRF-like attacks from any external domain.
origins → token@CrossOrigin(origins = "*") → origins
List<Comment> comments(@RequestHeader(value="x-auth-token") String token) { → token
Replace wildcard CORS policy with specific trusted domains or implement proper CORS configuration with credentials handling.
@CrossOrigin(origins = {"https://trusted-domain.com", "https://app.example.com"}, allowCredentials = "true")
@RequestMapping(value = "/comments", method = RequestMethod.GET, produces = "application/json")
List<Comment> comments(@RequestHeader(value="x-auth-token") String token) {
User.assertAuth(secret, token);
return Comment.fetch_all();
}
fb2710063a16Insertion of Sensitive Information into Log File
src/main/java/com/scalesec/vulnado/LinkLister.java
com.scalesec.vulnado.LinkLister.getLinksV2System.out.println(host);
21: public static List<String> getLinksV2(String url) throws BadRequest {
22: try {
23: URL aUrl= new URL(url);
24: String host = aUrl.getHost();
25: System.out.println(host);
26: if (host.startsWith("172.") || host.startsWith("192.168") || host.startsWith("10.")){
27: throw new BadRequest("Use of Private IP");
28: } else {
29: return getLinks(url);
30: }
31: } catch(Exception e) {
User-controlled input (hostnames from URLs) is being logged to system output without sanitization. This can lead to: 1) Log injection attacks where malicious hostnames containing control characters corrupt log files, 2) Information disclosure if logs are accessible to unauthorized users, 3) Log forging where attackers inject fake log entries, 4) Potential system information leakage about internal network topology through hostname resolution attempts.
url → hostList<String> linksV2(@RequestParam String url) throws BadRequest{ → url
URL aUrl= new URL(url); → url
String host = aUrl.getHost(); → host
System.out.println(host); → host
1) Remove debug System.out.println statements from production code, 2) Use proper logging framework (SLF4J, Logback) with appropriate log levels, 3) Sanitize user input before logging, 4) Implement log rotation and access controls, 5) Consider logging only necessary information for debugging purposes in development environments only.
// Remove the System.out.println(host); line entirely
// Or replace with proper logging:
private static final Logger logger = LoggerFactory.getLogger(LinkLister.class);
public static List<String> getLinksV2(String url) throws BadRequest {
try {
URL aUrl = new URL(url);
String host = aUrl.getHost();
// Use proper logging with sanitization if needed for debugging
if (logger.isDebugEnabled()) {
logger.debug("Processing request for host: {}", sanitizeForLogging(host));
}
if (host.startsWith("172.") || host.startsWith("192.168") || host.startsWith("10.")) {
throw new BadRequest("Use of Private IP");
} else {
return getLinks(url);
}
} catch (Exception e) {
throw new BadRequest(e.getMessage());
}
}
private static String sanitizeForLogging(String input) {
return input.replaceAll("[\r\n\t]", "_");
}