Create New User
+ ++
{{ responseMessage }}
+diff --git a/README.md b/README.md
index c0e993c..8c05f67 100644
--- a/README.md
+++ b/README.md
@@ -32,6 +32,12 @@ This vulnerable application contains the following API/Web Service vulnerabiliti
* GraphQL Introspection Enabled
* GraphQL Arbitrary File Write
* GraphQL Batching Brute Force
+* API Endpoint Brute Forcing
+* CRLF Injection
+* XML Injection
+* XML Bomb Denial-of-Service
+* SOAP Injection
+* Cross-Site Request Forgery (CSRF)
* Client Side Template Injection
## Set Up Instructions
@@ -69,7 +75,7 @@ Change directory to DVWS
cd dvws-node
```
-npm install all dependencies (build from source is needed for `libxmljs`, you might also need install libxml depending on your OS: `sudo apt-get install -y libxml2 libxml2-dev`)
+npm install all dependencies (build from source is needed for `libxmljs`, you might also need to install libxml depending on your OS: `sudo apt-get install -y libxml2 libxml2-dev`)
```
@@ -126,16 +132,11 @@ If the DVWS web service doesn't start because of delayed MongoDB or MySQL setup,
## To Do
-* Cross-Site Request Forgery (CSRF)
-* XML Bomb Denial-of-Service
-* API Endpoint Brute Forcing
+
* Web Socket Security
* Type Confusion
* LDAP Injection
-* SOAP Injection
-* XML Injection
* GRAPHQL Denial Of Service
-* CRLF Injection
* GraphQL Injection
* Webhook security
diff --git a/app.js b/app.js
index 5bed01b..969b97f 100644
--- a/app.js
+++ b/app.js
@@ -71,9 +71,12 @@ swaggerGen().then(() => {
app.use('/api-docs', swaggerUI.serve, swaggerUI.setup(swaggerOutput));
- app.listen(process.env.EXPRESS_JS_PORT, '0.0.0.0', () => {
+ const serverInstance = app.listen(process.env.EXPRESS_JS_PORT, '0.0.0.0', () => {
console.log(`🚀 API listening at http://dvws.local${process.env.EXPRESS_JS_PORT == 80 ? "" : ":" + process.env.EXPRESS_JS_PORT } (127.0.0.1)`);
});
+}).catch(err => {
+ console.error("Unable to generate Swagger documentation", err);
+ process.exit(1);
});
diff --git a/controllers/notebook.js b/controllers/notebook.js
index 64a7bbb..092385f 100644
--- a/controllers/notebook.js
+++ b/controllers/notebook.js
@@ -3,6 +3,7 @@ const jwt = require('jsonwebtoken')
const { exec } = require('child_process');
var xpath = require('xpath');
const xml2js = require('xml2js');
+const libxml = require('libxmljs');
const fs = require('fs');
dom = require('@xmldom/xmldom').DOMParser
const parser = new xml2js.Parser({ attrkey: "ATTR" });
@@ -200,6 +201,61 @@ module.exports = {
} finally {
await client.close();
}
+ },
+
+ // Vulnerability: XML Bomb / XXE (Import Notes)
+ import_notes_xml: async (req, res) => {
+ res = set_cors(req, res);
+
+ const xmlData = req.body.xml;
+ if (!xmlData) {
+ return res.status(400).send({ error: "XML data required" });
+ }
+
+ // Verify token
+ let result = {};
+ try {
+ const token = req.headers.authorization.split(' ')[1];
+ result = jwt.verify(token, process.env.JWT_SECRET, options);
+ } catch (e) {
+ return res.status(401).send({ error: "Unauthorized" });
+ }
+
+ const optionsXml = {
+ noent: true, // VULNERABLE: Enables entity substitution
+ dtdload: true,
+ huge: true // VULNERABLE: Bypasses parser limits (e.g. max node depth) to facilitate DoS
+ };
+
+ try {
+ const doc = libxml.parseXml(xmlData, optionsXml);
+
+ // Parse and save notes
+ const notes = doc.find('//note');
+ let count = 0;
+
+ for (const node of notes) {
+ const name = node.get('name') ? node.get('name').text() : ("Imported " + Date.now());
+ const body = node.get('body') ? node.get('body').text() : "";
+ const type = node.get('type') ? node.get('type').text() : "public";
+
+ const newNote = new Note({
+ name: name,
+ body: body,
+ type: type,
+ user: result.user
+ });
+ await newNote.save();
+ count++;
+ }
+
+ res.send({
+ success: true,
+ message: `Successfully imported ${count} notes.`,
+ parsedRoot: doc.root().name()
+ });
+ } catch (e) {
+ res.status(500).send(e);
+ }
}
-
}
diff --git a/controllers/passphrase.js b/controllers/passphrase.js
index aaec1c5..9eea507 100644
--- a/controllers/passphrase.js
+++ b/controllers/passphrase.js
@@ -4,6 +4,8 @@ const jwt = require('jsonwebtoken');
var serialize = require("node-serialize")
const PDFDocument = require('pdfkit');
const fs = require('fs');
+const bcrypt = require('bcrypt');
+const User = require('../models/users');
const sequelize = require('../models/passphrase');
@@ -71,6 +73,23 @@ const options = {
let result = {};
const token = req.headers.authorization.split(' ')[1];
result = jwt.verify(token, process.env.JWT_SECRET, options);
+
+ // Verify credentials before export (Vulnerable: No Rate Limiting + User enumeration)
+ const { password, username } = req.body;
+ if (!password || !username) {
+ return res.status(400).send("Username and Password required");
+ }
+
+ try {
+ // Vulnerability: Uses username from body allowing brute force of any user
+ const user = await User.findOne({ username: username });
+ if (!user || !(await bcrypt.compare(password, user.password))) {
+ return res.status(401).send("Incorrect credentials");
+ }
+ } catch (err) {
+ return res.status(500).send(err.message);
+ }
+
const payload = Buffer.from(req.body.data, 'base64');
const data = serialize.unserialize(payload.toString());
diff --git a/controllers/users.js b/controllers/users.js
index 12086c3..f29f7af 100644
--- a/controllers/users.js
+++ b/controllers/users.js
@@ -1,10 +1,21 @@
const mongoose = require('mongoose');
const bcrypt = require('bcrypt');
const jwt = require('jsonwebtoken');
+const xml2js = require('xml2js');
const connUri = process.env.MONGO_LOCAL_CONN_URL;
const User = require('../models/users');
+const options = {
+ expiresIn: '2d',
+ issuer: 'https://github.com/snoopysecurity',
+ algorithms: ["HS256", "none"],
+ ignoreExpiration: true
+};
+
+// In-memory log store for login attempts (Vulnerable to Log Pollution)
+const loginLogs = [];
+
function set_cors(req,res) {
if (req.get('origin')) {
res.header('Access-Control-Allow-Origin', req.get('origin'))
@@ -97,6 +108,12 @@ module.exports = {
let result = {};
let status = 200;
+ // Vulnerability: Log Pollution via CRLF Injection
+ // We log the username directly without sanitization.
+ // If username contains \n, it creates a fake log entry on a new line.
+ const ip = req.headers['x-forwarded-for'] || req.connection.remoteAddress || "unknown";
+ loginLogs.push(`[${new Date().toISOString()}] Login attempt from IP:${ip} User:${username}`);
+
try {
const user = await User.findOne({username});
if (user) {
@@ -140,7 +157,8 @@ module.exports = {
result.error = `Authentication error`;
}
res.setHeader('Authorization', 'Bearer '+ result.token);
- //res.cookie("SESSIONID", result.token, {httpOnly:true, secure:true});
+ // Set cookie for CSRF demonstration
+ res.setHeader('Set-Cookie', `auth_token=${result.token}; Path=/; HttpOnly`);
res.status(status).send(result);
} catch (err) {
status = 500;
@@ -178,5 +196,184 @@ module.exports = {
result.error = err;
}
res.status(status).send(result);
+ },
+
+ getLoginLogs: (req, res) => {
+ // Returns raw logs. Vulnerable to Log Pollution/Forgery if displayed line-by-line.
+ res.set('Content-Type', 'text/plain');
+ res.send(loginLogs.join('\n'));
+ },
+
+
+ // Vulnerability: XML Injection (Profile Export)
+ exportProfileXml: async (req, res) => {
+ // Scenario: User exports their profile to XML.
+ // Vulnerability: The 'bio' and 'username' fields are user-controlled and concatenated directly.
+ const username = req.body.username || "guest";
+ const bio = req.body.bio || "No bio";
+
+ // Construct XML manually (Vulnerable)
+ const xml = `
+ Functionality only available to Admin Users User Information: {{ ResponseMessage }} Search DVWS Users: System Information (OS): {{ SysMessage }} System Information: {{ SysMessage }}
+ Check User Status (Legacy SOAP)Damn Vulnerable Web Services
Admin Area
+ Back to Home
Damn Vulnerable Web Services
Admin AreaDVWS User Data
-
+
-
-
- Admin Tools
+
+ Create User
+ View Login Activity Logs
+ DVWS User Data
}
-
-
- $scope.SendData1 = function () {
- var post = $http({
- method: "POST",
- url: "/dvwsuserservice",
- dataType: 'xml',
- data: `DVWS User Data
Create new users (Admin Only)
+{{ responseMessage }}
+View recent login activity.
+ +{{ logs }}
+ Check user status and role (Admin Only)
+Enter Username to check status via Legacy SOAP Service.
+ +Username: {{ userStatus.username }}
+Role: {{ userStatus.role }}
+Status: {{ userStatus.status }}
+{{ error }}
+All Accessible Areas can be found here
- + +Username: {{ profile.username }}
+Bio: {{ profile.bio || 'No bio set.' }}
+Role: {{ profile.admin ? 'Admin' : 'User' }}
+Loading profile...
+- \ No newline at end of file + diff --git a/public/admin_create_user.html b/public/admin_create_user.html new file mode 100644 index 0000000..a44bccd --- /dev/null +++ b/public/admin_create_user.html @@ -0,0 +1,57 @@ +
+ + + + + + +
diff --git a/public/admin_logs.html b/public/admin_logs.html new file mode 100644 index 0000000..97cb973 --- /dev/null +++ b/public/admin_logs.html @@ -0,0 +1,52 @@ +
+ + + + + + +
diff --git a/public/admin_user_status.html b/public/admin_user_status.html new file mode 100644 index 0000000..2cbeadb --- /dev/null +++ b/public/admin_user_status.html @@ -0,0 +1,98 @@ +
+ + + + + + +
diff --git a/public/export_profile.html b/public/export_profile.html new file mode 100644 index 0000000..e6177b1 --- /dev/null +++ b/public/export_profile.html @@ -0,0 +1,84 @@ + + + + + + +
Export and Import your profile data via XML.
++
Enter your bio to include in the export.
+{{ XmlResponse }}
+
+ Update your profile by importing XML.
+{{ ImportResponse }}
+ + + diff --git a/public/home.html b/public/home.html index b585910..38a1eb9 100644 --- a/public/home.html +++ b/public/home.html @@ -6,18 +6,31 @@ +
-