diff --git a/README.md b/README.md index 38f6c9ebf..1f0e4298d 100644 --- a/README.md +++ b/README.md @@ -1,120 +1,3 @@ # Damn Vulnerable NodeJS Application (DVNA) -![dvna-logo](docs/resources/dvna.png) - -Damn Vulnerable NodeJS Application (DVNA) is a simple NodeJS application to demonstrate [**OWASP Top 10 Vulnerabilities**](https://www.owasp.org/index.php/Category:OWASP_Top_Ten_Project#OWASP_Top_10_for_2013) and guide on fixing and avoiding these vulnerabilities. The [fixes](https://github.com/appsecco/dvna/tree/fixes) branch will contain fixes for the vulnerabilities. - -The application is powered by commonly used libraries such as [express](https://www.npmjs.com/package/express), [passport](https://www.npmjs.com/package/passport), [sequelize](https://www.npmjs.com/package/sequelize), etc. - -The application comes with a developer friendly comprehensive guide which can be used to learn, avoid and fix the vulnerabilities. The guide available at https://appsecco.com/books/dvna-developers-security-guide/ covers the following - -1. Instructions for setting up DVNA -2. Instructions on exploiting the vulnerabilities -3. Vulnerable code snippets and instructions on fixing vulnerabilities -4. Recommendations for avoid such vulnerabilities -5. References for learning more - -The blog post for this release is at https://blog.appsecco.com/damn-vulnerable-nodejs-application-dvna-by-appsecco-7d782d36dc1e - -## Getting Started - -DVNA can be deployed in three ways - -1. For Developers, using docker-compose with auto-reload on code updates -2. For Security Testers, using the Official image from Docker Hub -3. For Advanced Users, using a fully manual setup - -Detailed instructions on setup and requirements are given in the Guide Gitbook - -### Development Setup - -Clone this repository -```bash -git clone https://github.com/appsecco/dvna; cd dvna -``` - -Create a `vars.env` with the desired database configuration -``` -MYSQL_USER=dvna -MYSQL_DATABASE=dvna -MYSQL_PASSWORD=passw0rd -MYSQL_RANDOM_ROOT_PASSWORD=yes -``` - -Start the application and database using docker-compose -```bash -docker-compose up -``` - -Access the application at http://127.0.0.1:9090/ - -The application will automatically reload on code changes, so feel free to patch and play around with the application. - -### Using Official Docker Image - -Create a file named `vars.env` with the following configuration -``` -MYSQL_USER=dvna -MYSQL_DATABASE=dvna -MYSQL_PASSWORD=passw0rd -MYSQL_RANDOM_ROOT_PASSWORD=yes -MYSQL_HOST=mysql-db -MYSQL_PORT=3306 -``` - -Start a MySQL container -```bash -docker run --name dvna-mysql --env-file vars.env -d mysql:5.7 -``` - -Start the application using the official image -```bash -docker run --name dvna-app --env-file vars.env --link dvna-mysql:mysql-db -p 9090:9090 appsecco/dvna -``` - -Access the application at http://127.0.0.1:9090/ and start testing! - -### Manual Setup - -Clone the repository -```bash -git clone https://github.com/appsecco/dvna; cd dvna -``` - -Configure the environment variables with your database information -```bash -export MYSQL_USER=dvna -export MYSQL_DATABASE=dvna -export MYSQL_PASSWORD=passw0rd -export MYSQL_HOST=127.0.0.1 -export MYSQL_PORT=3306 -``` - -Install Dependencies -```bash -npm install -``` - -Start the application -```bash -npm start -``` - -Access the application at http://localhost:9090 - -## TODO - -- [ ] Link commits to fixes in documentation -- [ ] Add new vulnerabilities from OWASP Top 10 2017 -- [ ] Improve application features, documentation - -## Contributing - -In case of bugs in the application, please create an issue on github. Pull requests are highly welcome! - -## Thanks -[Abhisek Datta - abhisek](https://github.com/abhisek) for application architecture and front-end code - -## License - -MIT +This branch is for all the fixes for the vulnerabilities from OWASP Top 10 2017 \ No newline at end of file diff --git a/core/appHandler.js b/core/appHandler.js index 95e2befdd..90da1a187 100644 --- a/core/appHandler.js +++ b/core/appHandler.js @@ -215,7 +215,7 @@ module.exports.listUsersAPI = function (req, res) { module.exports.bulkProductsLegacy = function (req,res){ // TODO: Deprecate this soon if(req.files.products){ - var products = serialize.unserialize(req.files.products.data.toString('utf8')) + var products = JSON.parse(req.files.products.data.toString('utf8')) products.forEach( function (product) { var newProduct = new db.Product() newProduct.name = product.name @@ -232,7 +232,7 @@ module.exports.bulkProductsLegacy = function (req,res){ module.exports.bulkProducts = function(req, res) { if (req.files.products && req.files.products.mimetype=='text/xml'){ - var products = libxmljs.parseXmlString(req.files.products.data.toString('utf8'), {noent:true,noblanks:true}) + var products = libxmljs.parseXmlString(req.files.products.data.toString('utf8'), {noent:false,noblanks:true}) products.root().childNodes().forEach( product => { var newProduct = new db.Product() newProduct.name = product.childNodes()[0].text() diff --git a/core/authHandler.js b/core/authHandler.js index f0e67ab5a..0d0d25e18 100644 --- a/core/authHandler.js +++ b/core/authHandler.js @@ -1,6 +1,7 @@ var db = require('../models') var bCrypt = require('bcrypt') var md5 = require('md5') +var winston = require('winston') module.exports.isAuthenticated = function (req, res, next) { if (req.isAuthenticated()) { @@ -78,6 +79,8 @@ module.exports.resetPwSubmit = function (req, res) { if (req.body.token == md5(req.body.login)) { user.password = bCrypt.hashSync(req.body.password, bCrypt.genSaltSync(10), null) user.save().then(function () { + let username = req.body.login + winston.log({level:'info',message:'Password reset', username}) req.flash('success', "Passowrd successfully reset") res.redirect('/login') }) diff --git a/core/passport.js b/core/passport.js index 65d57f0de..47a584bab 100644 --- a/core/passport.js +++ b/core/passport.js @@ -1,7 +1,7 @@ var db = require('../models') var LocalStrategy = require('passport-local').Strategy var bCrypt = require('bcrypt') - +var winston = require('winston') module.exports = function (passport) { @@ -37,8 +37,10 @@ module.exports = function (passport) { return done(null, false, req.flash('danger', 'Invalid Credentials')) } if (!isValidPassword(user, password)) { + winston.log({level:'warn',message:'Failed login', username}) return done(null, false, req.flash('danger', 'Invalid Credentials')) } + winston.log({level:'info',message:'Successful login', username}) return done(null, user); }); })) @@ -68,6 +70,7 @@ module.exports = function (passport) { name: req.body.name, login: username }).then(function (user) { + winston.log({level:'info',message:'New User', username}) return done(null, user) }) } else { diff --git a/docs/README.md b/docs/README.md deleted file mode 100644 index 02ca55ee4..000000000 --- a/docs/README.md +++ /dev/null @@ -1 +0,0 @@ -![dvna](resources/cover.png) \ No newline at end of file diff --git a/docs/SUMMARY.md b/docs/SUMMARY.md deleted file mode 100644 index 66174ac67..000000000 --- a/docs/SUMMARY.md +++ /dev/null @@ -1,29 +0,0 @@ -# Summary - -* [Cover](README.md) - -## Introduction - -* [Introduction](intro.md) -* [Setup](setup.md) - -## Solution - -* [A1 Injection](solution/a1-injection.md) -* [A2 Broken Authentication and Session Management](solution/a2-broken-auth.md) -* [A3 Sensitive Data Exposure](solution/a3-sensitive-data-exposure.md) -* [A4 XML External Entities](solution/a4-xxe.md) -* [A5 Broken Access Control](solution/a5-broken-access-control.md) -* [A6 Security Misconfiguration](solution/a6-securty-misconfig.md) -* [A7 Cross-site Scripting](solution/a7-xss.md) -* [A8 Insecure Deseriliaztion](solution/a8-insecure-deserialization.md) -* [A9 Using Components with Known Vulnerability](solution/a9-using-components-with-known-vulnerability.md) -* [A10 Insufficient Logging and Monitoring](solution/a10-insufficient-logging.md) - -## Top 10 2013 -* [A8:2013 Cross-site Request Forgery](solution/ax-csrf.md) -* [A10:2013 Unvalidated Redirects and Forwards](solution/ax-unvalidated-redirects-and-forwards.md) - -## About - -* [Appsecco](appsecco.md) diff --git a/docs/appsecco.md b/docs/appsecco.md deleted file mode 100644 index 691fcfe73..000000000 --- a/docs/appsecco.md +++ /dev/null @@ -1,23 +0,0 @@ -# About Appsecco - -![appsecco-slide](resources/appsecco.png) - -Appsecco is a specialist application security company, founded in 2015, with physical presence in London, Bangalore, Doha and Boston, providing industry leading security advice that is firmly grounded in commercial reality. - -Our services cover the entire software development lifecycle from advising on how build and foster a culture of security within development teams and organisations to reviewing and advising on the security of applications and associated infrastructure under development to providing rapid response and advice in the event of a security breach or incident. - -As a team, we are highly qualified and have many years of extensive experience working with clients across multiple counties and in a wide range of industries and sectors; from financial services to software development, manufacturing to governmental organisations and consumer brands to ecommerce. - -The solutions, advice and insight we deliver to our clients always follows three core principles: - -1. It must be pragmatic; taking into account the specific commercial, organisational and operational realities of each client individually - -2. It must genuinely add value; the advice or solutions we provide must addresses the specific problem a client seeks to solve and have actionable insight to enable them to achieve this - -3. Never be purely automated; whenever we are testing for security our reports and output always have significant, expert, human input to give the greatest possible value for our clients - -In addition to their client-facing work our technical team are actively involved in researching and developing new and better ways to stay secure and can regularly be found presenting their findings at industry conferences and events ranging from nullcon in India, DevSecCon in London and Singapore, to DEF CON, the world’s largest security conference held annually in the USA. - -Appsecco - -[https://appsecco.com](https://appsecco.com) \ No newline at end of file diff --git a/docs/intro.md b/docs/intro.md deleted file mode 100644 index 8ff1f7b43..000000000 --- a/docs/intro.md +++ /dev/null @@ -1,21 +0,0 @@ -# Damn Vulnerable NodeJS Application - -![dvna-logo](resources/dvna.png) - -[Damn Vulneable NodeJS Application (DVNA)](https://github.com/appsecco/dvna) is a simple NodeJS application to demonstrate [**OWASP Top 10 Vulnerabilities**](https://www.owasp.org/index.php/Category:OWASP_Top_Ten_Project#OWASP_Top_10_for_2013) and guide on fixing and avoiding these vulnerabilities. - -The application is powered by commonly used libraries such as [express](https://www.npmjs.com/package/express), [passport](https://www.npmjs.com/package/passport), [sequelize](https://www.npmjs.com/package/sequelize), etc. - -It is aimed to be useful for developers with limited development expereience in NodeJS, and the fixes for the vulnerabilities will be available in the [fixes](https://github.com/appsecco/dvna/tree/fixes) branch in the repository. - -This guide contains the following - -1. Instructions for setting up DVNA -2. Instructions on exploiting the vulnerabilities -3. Vulnerable code snippets and instructions on fixing vulnerabilities -4. Recommendations for avoid such vulnerabilities -5. References for learning more - -#### Available on Github [https://github.com/appsecco/dvna](https://github.com/appsecco/dvna) - -This gitbook was generated from https://github.com/appsecco/dvna/tree/master/docs \ No newline at end of file diff --git a/docs/resources/appsecco.png b/docs/resources/appsecco.png deleted file mode 100644 index d0add214d..000000000 Binary files a/docs/resources/appsecco.png and /dev/null differ diff --git a/docs/resources/ci1.png b/docs/resources/ci1.png deleted file mode 100644 index 246c732bd..000000000 Binary files a/docs/resources/ci1.png and /dev/null differ diff --git a/docs/resources/cover.png b/docs/resources/cover.png deleted file mode 100644 index c692090ee..000000000 Binary files a/docs/resources/cover.png and /dev/null differ diff --git a/docs/resources/dvna.png b/docs/resources/dvna.png deleted file mode 100644 index bc5bed9cd..000000000 Binary files a/docs/resources/dvna.png and /dev/null differ diff --git a/docs/resources/idor1.png b/docs/resources/idor1.png deleted file mode 100644 index 823fe88c5..000000000 Binary files a/docs/resources/idor1.png and /dev/null differ diff --git a/docs/resources/info-dis.png b/docs/resources/info-dis.png deleted file mode 100644 index e7a247d75..000000000 Binary files a/docs/resources/info-dis.png and /dev/null differ diff --git a/docs/resources/info-dis2.png b/docs/resources/info-dis2.png deleted file mode 100644 index eb749b1bf..000000000 Binary files a/docs/resources/info-dis2.png and /dev/null differ diff --git a/docs/resources/jse1.png b/docs/resources/jse1.png deleted file mode 100644 index 59fcb1764..000000000 Binary files a/docs/resources/jse1.png and /dev/null differ diff --git a/docs/resources/jse2.png b/docs/resources/jse2.png deleted file mode 100644 index 6a7ac2692..000000000 Binary files a/docs/resources/jse2.png and /dev/null differ diff --git a/docs/resources/missing-fn-access.png b/docs/resources/missing-fn-access.png deleted file mode 100644 index 9da3199fe..000000000 Binary files a/docs/resources/missing-fn-access.png and /dev/null differ diff --git a/docs/resources/powered-by.png b/docs/resources/powered-by.png deleted file mode 100644 index f14e51e27..000000000 Binary files a/docs/resources/powered-by.png and /dev/null differ diff --git a/docs/resources/rce.png b/docs/resources/rce.png deleted file mode 100644 index fc9263774..000000000 Binary files a/docs/resources/rce.png and /dev/null differ diff --git a/docs/resources/secmis1.png b/docs/resources/secmis1.png deleted file mode 100644 index 777da8e7c..000000000 Binary files a/docs/resources/secmis1.png and /dev/null differ diff --git a/docs/resources/sqli1.png b/docs/resources/sqli1.png deleted file mode 100644 index 2ca302578..000000000 Binary files a/docs/resources/sqli1.png and /dev/null differ diff --git a/docs/resources/sqli2.png b/docs/resources/sqli2.png deleted file mode 100644 index 8c5a5f9d9..000000000 Binary files a/docs/resources/sqli2.png and /dev/null differ diff --git a/docs/resources/xss1.png b/docs/resources/xss1.png deleted file mode 100644 index 988ef518f..000000000 Binary files a/docs/resources/xss1.png and /dev/null differ diff --git a/docs/resources/xxe1.png b/docs/resources/xxe1.png deleted file mode 100644 index 82d9994ad..000000000 Binary files a/docs/resources/xxe1.png and /dev/null differ diff --git a/docs/resources/xxe2.png b/docs/resources/xxe2.png deleted file mode 100644 index 44800e2ed..000000000 Binary files a/docs/resources/xxe2.png and /dev/null differ diff --git a/docs/setup.md b/docs/setup.md deleted file mode 100644 index 918d784a5..000000000 --- a/docs/setup.md +++ /dev/null @@ -1,107 +0,0 @@ -# Setup - -DVNA can be deployed in three ways - -1. For Developers, using docker-compose with auto-reload on code updates -2. For Security Testers, using the Official image from Docker Hub -3. For Advanced Users, using a fully manual setup - -## Requirements - -Setup with Docker: - -- Docker (Tested working on v1.13.1) -- Docker Compose (Tested working on v1.17.1) - -Setup Without Docker: - -- NodeJS (Developed using NodeJS v6.11.4) -- MySQL Server (Developed using MySQL 5.7) - -### Development Setup - -If you do not have Docker and Docker Compose setup, then - -Install [Docker](https://docs.docker.com/engine/installation/) first and then -[Docker Compose](https://docs.docker.com/compose/install/) on your system. Then follow the instructions below - -Clone the repository -```bash -git clone https://github.com/appsecco/dvna; cd dvna -``` - -Create a file with name `vars.env` in the application's folder with the desired configuration like the example below -``` -MYSQL_USER=dvna -MYSQL_DATABASE=dvna -MYSQL_PASSWORD=passw0rd -MYSQL_RANDOM_ROOT_PASSWORD=yes -``` - -Start the application using Docker Compose -```bash -docker-compose up -``` - -Access the application at http://localhost:9090 and start practicing! - -The application will automatically reload on code changes, so feel free to patch and play around. - -### Using Official Docker Image - -Create a file named `vars.env` with the following configuration -``` -MYSQL_USER=dvna -MYSQL_DATABASE=dvna -MYSQL_PASSWORD=passw0rd -MYSQL_RANDOM_ROOT_PASSWORD=yes -MYSQL_HOST=mysql-db -MYSQL_PORT=3306 -``` - -Start a MySQL container, unless you want to use your own, in which case configure in the env file above -```bash -docker run --name dvna-mysql --env-file vars.env -d mysql:5.7 -``` - -Start the application using the official image -```bash -docker run --name dvna-app --env-file vars.env --link dvna-mysql:mysql-db -p 9090:9090 appsecco/dvna -``` - -Access the application at http://127.0.0.1:9090/ and start testing! - -### Manual Setup - -This is an advanced setup which requires you to have NodeJS setup on your system and access to a MySQL Database. Unless your requirements demands it, its recommended to go with the Dockerized Setup above. - -If you do not have NodeJS on your system, then -Install [NodeJS](https://nodejs.org/en/download/package-manager/) first. - -If you do not have access to an existing MySQL server and would like to setup your own MySQL instance, then refer to this [Getting Started](https://dev.mysql.com/doc/mysql-getting-started/en/) guide. Once the MySQL Sever is setup, create a new database and user for DVNA. You will need to configure these in the environment variables before starting the application. - -Clone the repository -```bash -git clone https://github.com/appsecco/dvna; cd dvna -``` - -Configure the environment variables with your database information. For Windows system, refer to the guide on [Setting up environment variables](http://www.dowdandassociates.com/blog/content/howto-set-an-environment-variable-in-windows-command-line-and-registry/) -```bash -export MYSQL_USER=dvna -export MYSQL_DATABASE=dvna -export MYSQL_PASSWORD=passw0rd -export MYSQL_HOST=127.0.0.1 -export MYSQL_PORT=3306 -``` - -Install Dependencies -```bash -npm install -``` - -Start the application -```bash -npm start -``` - -Access the application at http://localhost:9090 and start practicing! \ No newline at end of file diff --git a/docs/solution/a1-injection.md b/docs/solution/a1-injection.md deleted file mode 100644 index c671f7de0..000000000 --- a/docs/solution/a1-injection.md +++ /dev/null @@ -1,103 +0,0 @@ -# Injection - -## SQL Injection - -There is a SQL Injection in `User Search` feature at the following URL - -http://127.0.0.1:9090/app/usersearch - -By injecting a single quote `'`, we see an error has occurred. -![sqli1](/resources/sqli1.png "SQLi Trigger") - -An attacker can exploit this further and obtain potentially sensitive information from the database -![sqli2](/resources/sqli2.png "Exploiting SQLi") - -**Vulnerable Code snippet** - -*core/appHandler.js* -``` -... -var query = "SELECT name FROM Users WHERE login='" + req.body.login + "'"; -db.sequelize.query(query,{ model: db.User }).then(user => { - if(user.length){ -... -``` -**Solution** - -You may use model's **find** function and rely on in-built input sanitization of sequelize - -*core/appHandler.js* -``` -... -if (vh.vCode(req.body.login)){ - db.User.find({where:{'login':req.body.login}}).then(user => { - if (user) { -... -``` - -But it is recommended to explicitly validate/sanitize inputs - -**Fixes** - -Implemented in the following files - -- *core/appHandler.js* - -**Recommendation** - -- Validate Input before processing -- Sanitize Input before storing - -## Command Injection - -There is a Command Injection in `Connectivity Test` feature at the following URL - -http://127.0.0.1:9090/app/ping - - -By injecting `x ; id`, we are able to see that the `id` command has been executed. -![ci1](/resources/ci1.png "Command injection") - -**Vulnerable Code snippet** - -*core/appHandler.js* -``` -const exec = require('child_process').exec; -... -exec('ping -c 2 '+ req.body.address, function(err,stdout,stderr){ - console.log(err) - output = stdout + stderr -... -``` -**Solution** - -You may use `exec_file` or `spawn` method under child_process which will prevent arbitrary command execution. - -*core/appHandler.js* -``` -const execFile = require('child_process').execFile; -... -if (vh.vIP(req.body.address)){ - execFile('ping', ['-c', '2', req.body.address] , function(err,stdout,stderr){ - output = stdout + stderr -... -``` - -**Fixes** - -Implemented in the following files - -- *core/appHandler.js* - -**Recommendation** - -- Use exec_file or spawn method instead -- Always Validate/Sanitize Input before processing. Look at [validator](https://www.npmjs.com/package/validator) -- Run commands in a sandbox/ isolated environment if possible -- Use a restricted user for running the process - -**Reference** - -- -- -- \ No newline at end of file diff --git a/docs/solution/a10-insufficient-logging.md b/docs/solution/a10-insufficient-logging.md deleted file mode 100644 index 1ca34d6d3..000000000 --- a/docs/solution/a10-insufficient-logging.md +++ /dev/null @@ -1,60 +0,0 @@ -# Insufficient Logging and Monitoring - -Exploitation of insufficient logging and monitoring is the bedrock of nearly every major incident. - -- Auditable events, such as logins, failed logins, and high-value transactions are not logged. -- Warnings and errors generate no, inadequate, or unclear log messages. -- Logs of applications and APIs are not monitored for suspicious activity. -- Logs are only stored locally. -- Appropriate alerting thresholds and response escalation processes are not in place or effective. -- Penetration testing and scans by DAST tools (such as OWASP ZAP) do not trigger alerts. -- The application is unable to detect, escalate, or alert for active attacks in real time or near real time. - -**Solution** - -All critical functionalities of the application must be logged. We use winston, a logging library to handle our logging. - -Define a default logger - -*server.js* -```js -var winston = require('winston') -... -winston.configure({ - format: winston.format.json(), - transports: [ - new winston.transports.File({ filename: 'combined.log' }) - ] -}); -... -``` - -Log from anywhere - -*core/passport.js* -```js -var winston = requir('winston') -... -if (!isValidPassword(user, password)) { - logger.log({level:'warn',message:'Failed login attempt for ', username}) - return done(null, false, req.flash('danger', 'Invalid Credentials')) -} -... -``` - -**Fixes** - -Implemented in the following files - -- *server.js* -- *core/passport.js* -- *core/authHandler.js* - -**Recommendation** - -- Log all sensitive operations by default -- Ensure that the logs are stored and processed securely - -**Reference** - -- \ No newline at end of file diff --git a/docs/solution/a2-broken-auth.md b/docs/solution/a2-broken-auth.md deleted file mode 100644 index a56c0bcab..000000000 --- a/docs/solution/a2-broken-auth.md +++ /dev/null @@ -1,73 +0,0 @@ -# Broken Authentication and Session Management - -## Insecure Reset Password - -The `Reset password` functionality can be triggered by visiting an URL such as below - -http://127.0.0.1:9090/resetpw?login=user&token=ee11cbb19052e40b07aac0ca060c23ee - -The trust establishment in reset password is inherently weak because the _login_ name and _token_ parameter required to execute the password reset is user supplied. Additionally the apparently random key is the MD5 hash of _login_ name which can be easily computed by an attacker. - -This issue can be exploited by an attacker to reset any user's password by using an URL such as below - -``` -http://127.0.0.1:9090/resetpw?login=&token= -``` - -**Solution** - -Store the password reset request along with a randomly generated token string and expiry - -Email a reset link containing that token and username to the user - -Validate the reset token for the user before password reset - -**Fixes** - -Implemented in the following files - -- *core/authHandler.js* -- *models/passreset.js* - -## Insecure Session Secret - -The session secret is used is insecure and is used in the example snippets across the web - -**Vulnerable Code snippet** - -*server.js* -``` -... -app.use(session({ - secret: 'keyboard cat', - resave: false, -... -``` - -This allows an attacker to -1. Decrypt a user's session -2. Potentially forge the session cookie and bypass authentication - -**Solution** - -Always use unique, long, secure random generated for secrets - -**Fixes** - -Implemented in the following files - -- *server.js* -- *config/server.js* - -**Recommendation** - -- Do not copy paste code without understanding what it does -- Rotate session secrets -- Store secrets in environment variables or config files -- Consider using a secret management solution if your scale demands it - -**References** - -- -- -- \ No newline at end of file diff --git a/docs/solution/a3-sensitive-data-exposure.md b/docs/solution/a3-sensitive-data-exposure.md deleted file mode 100644 index 8058b605e..000000000 --- a/docs/solution/a3-sensitive-data-exposure.md +++ /dev/null @@ -1,74 +0,0 @@ -# Sensitive Data Disclosure - -## Hashed Passwords Disclosed - -![info-dis](/resources/info-dis.png "Password Hash Disclosed") - -The Admin API endpoint at http://127.0.0.1:9090/app/admin/api/users sends the entire user object to the front end. Even if the application/page rendering this may not display the password, it's critical that only necessary information is sent instead of the entire object - -**Vulnerable Code snippet** - -*core/apphandler.js* -``` -... -db.User.findAll({}).then(users => { - res.status(200).json({ - success: true, - users: users - }) -... -``` - - -**Solution** - -This particular error can be fixed by selecting for only the required attributes from the database - -``` -db.User.findAll({attributes: [ 'id' ,'name', 'email']},).then(users => { - res.status(200).json({ - success: true, - users: users - }) -``` - -**Fixes** - -Implemented in the following files - -- *core/appHandler.js* - -## Logging of sensitive information - -![info-dis](/resources/info-dis2.png "Password Hash Disclosed") - -By default, Sequelize logs every query using `console.log`, this could be a serious issue if these logs are stored to disk or worse, sent elsewhere for analytics or other purposes - -**Solution** - -To fix this issue, disable logging in Sequelize - -``` -// Sequelize connection -var sequelize = new Sequelize(config.database, config.username, config.password, { - host: config.host, - dialect: config.dialect, - logging: false -}); -``` - -**Fixes** - -Implemented in the following files - -- *models/index.js* - -**Recommendation** - -- Always be wary of where all your data resides or is transmitted to -- Send only the minimum information which is essential, even if may not be used - -**Reference** - -- -- \ No newline at end of file diff --git a/docs/solution/a4-xxe.md b/docs/solution/a4-xxe.md deleted file mode 100644 index f0b968736..000000000 --- a/docs/solution/a4-xxe.md +++ /dev/null @@ -1,63 +0,0 @@ -# XML External Entities - -The `Bulk Import` feature at http://127.0.0.1:9090/app/bulkproducts is vulnerable to XML External Entity attack. - -![xxe1](/resources/xxe1.png) - -This can be easily exploited by supplying an input like the one below - -```xml - -]> - - - Playstation 4 - 274 - gaming console - &bar; - - -``` - -The resulting product's description will have the contents of `/etc/passwd` - -![xxe2](/resources/xxe2.png) - -**Vulnerable Code snippet** - -*core/appHandler.js* -``` -... -module.exports.bulkProducts = function(req, res) { - if (req.files.products && req.files.products.mimetype=='text/xml'){ - var products = libxmljs.parseXmlString(req.files.products.data.toString('utf8'), {noent:true,noblanks:true}) -... -``` - -**Solution** - -The XML parsing library used is `libxmljs` which allows for parsing external entities. We can disable parsing of external entities by modifying the flag value `noent` to `false`. - -*core/appHandler.js* -``` -... -module.exports.bulkProducts = function(req, res) { - if (req.files.products && req.files.products.mimetype=='text/xml'){ - var products = libxmljs.parseXmlString(req.files.products.data.toString('utf8'), {noent:false,noblanks:true}) -... -``` - -**Fixes** - -Implemented in the following file - -- *core/appHandler.js* - -**Recommendation** - -- Ensure that External entity parsing is disabled -- If parsing is absoutely required, then validate the data before parsing - -**Reference** - -- \ No newline at end of file diff --git a/docs/solution/a5-broken-access-control.md b/docs/solution/a5-broken-access-control.md deleted file mode 100644 index e940262ca..000000000 --- a/docs/solution/a5-broken-access-control.md +++ /dev/null @@ -1,112 +0,0 @@ -# Missing Function Level Access Control - - -## Unauthorized Access to Users API - -The issue lies in `List Users` API implementation where the code does not correctly establish identity and capability for the calling user before fulfilling the request. - -**Vulnerable Code snippet** - -*routes/app.js* -``` -... -router.get('/admin',authHandler.isAuthenticated,function(req,res){ - res.render('app/admin',{admin: (req.user.role=='admin')}) -}) - -router.get('/admin/api/users',authHandler.isAuthenticated, appHandler.listUsersAPI) -... -``` - -*views/app/admin.ejs* -``` -... -var isAdmin = false; -if(!isAdmin){ - var div = document.getElementById('admin-body'); - div.style.display = "none"; -}else{ - var div = document.getElementById('non-admin-body'); - div.style.display = "none"; -} -... -``` - -By checking the page source, we are able to see the `List Users API` that isn't visible in the Dashboard. - -![missing-fn-access](/resources/missing-fn-access.png "API Hidden in Front End") - -The API endpoint doesn't check whether the requesting user is an admin. Assuming that an attacker will not be able to access your endpoints because they are hidden is a very bad practice. - -**Solution** - -The `List Users API` route must check the requesting user's privilege before serving the request. - -``` -function adminCheck(req,res,next){ - if(req.user.role=='admin') - next() - else - res.status(401).send('Unauthorized') -} - -router.get('/admin/api/users',authHandler.isAuthenticated, adminCheck, appHandler.listUsersAPI) -``` - -**Fixes** - -Implemented in the following files - -- *core/authHandler.js* -- *routes/app.js* -- *views/app/admin.ejs* - -## Missing Authorization check in Edit User - -The `userEditSubmit` method fails to validate `id` parameter to ensure that the calling user has appropriate access to the object. This issue can be exploited to reset information for any user identified by id. - -http://127.0.0.1:9090/app/useredit - -**Vulnerable Code snippet** - -*core/apphandler.js* -``` -... -module.exports.userEditSubmit = function(req,res){ - if(req.body.password==req.body.cpassword){ - db.User.find({where:{'id':req.body.id}}).then(user=>{ - if(user){ - user.password = bCrypt.hashSync(req.body.password, bCrypt.genSaltSync(10), null) - user.save().then(function(){ -... -``` - -Simply changing the user id in the page can lead to exploitation.

-![idor1](/resources/idor1.png "IDOR") - -**Solution** - -A simple check can solve this issue -``` -if (req.user.id == req.body.id) - //do -else - //dont -``` - -In our case we can use passports user object at `req.user` for modifying user information - -**Fixes** - -Implemented in the following files - -- *core/appHandler.js* - -**Recommendation** - -- Try to restrict your functions to maximum extent, White listing is always better than blacklisting -- Consider any user supplied information as untrusted and always validate user access by sessions - -**Reference** - -- diff --git a/docs/solution/a6-securty-misconfig.md b/docs/solution/a6-securty-misconfig.md deleted file mode 100644 index d03d03cc1..000000000 --- a/docs/solution/a6-securty-misconfig.md +++ /dev/null @@ -1,61 +0,0 @@ -# Security Misconfiguration - -## Application sends Stack Trace - -![secmis1](/resources/secmis1.png "Security Misconfiguration") -An invalid input `XD` triggers a stack trace in the calculator endpoint at - -http://127.0.0.1:9090/app/calc - -The application was running in `DEVELOPMENT` mode and due to lack of error handling, it sent the stack trace with internal file locations and other potentially sensitive information. - -*/core/apphandler.js* -``` -... -if(req.body.eqn){ - req.flash('result',mathjs.eval(req.body.eqn)) - res.render('app/calc') -... -``` - -Additionally the issue occurs due to insecure NodeJS configuration that shows stack staces. - -**Solution** - -This particular issue can be solved by using a try catch exception handling -``` -try{ - result = mathjs.eval(req.body.eqn) -}catch (err){ - result = 'Invalid Equation' -} -``` - -But a bigger issue is the application running in development mode. Set **NODE_ENV** environment variable to `production`, this improves performance too! - -**Fixes** - -Implemented in the following files - -- *core/appHandler.js* - -## X-Powered-By header - -![powered-by](/resources/powered-by.png "X-Powered-By") - -The `X-Powered-By : Express` header is sent by default in every response and disabling this is a good way to prevent attackers from fingerprinting your application. - -**Solution** - -Disable it using `app.disable('x-powered-by')` in express - -**Fixes** - -Implemented in the following files - -- *server.js* - -**Reference** -- -- -- \ No newline at end of file diff --git a/docs/solution/a7-xss.md b/docs/solution/a7-xss.md deleted file mode 100644 index 0f48270ef..000000000 --- a/docs/solution/a7-xss.md +++ /dev/null @@ -1,92 +0,0 @@ -# Cross-site Scripting - -![xss1](/resources/xss1.png "XSS") -with input *Inconspicuous Pizza<script>alert('Cookie:'+document.cookie)</script>* - -## Reflected XSS in Search Query - -**Note: Chrome XSS Auditor may block XSS Attacks** - -A Cross-site scripting vulnerability exists in the following URL - -http://127.0.0.1:9090/app/products - -**Vulnerable Code snippet** - -*/views/app/products.ejs* -``` -... -<% if (output&&output.searchTerm) { %> -

- Listing products with search query: - <%- output.searchTerm %> -... -``` - -User supplied input is directly rendered as part of HTML response. This issue can be exploited to inject arbitrary scripting code to perform a Cross-site Scripting attack. - -**Solution** - -Ensure user supplied or any other untrusted data is not rendered as part of HTTP response without appropriate encoding. EJS escape output tag can be used to render this securely with appropriate encoding such as below - -``` -<%= output.searchTerm %> -``` -Notice the `=` symbol instead of `-`, which escapes the output. Note that this only prevents xss when the target for escaped output is in a html context. - -**Fixes** - -Implemented in the following files - -- *server.js* -- *views/app/products.ejs* - -## Stored XSS in Product Listing - -Another XSS vulnerability exists in the same page, however at a different location - -**Vulnerable Code snippet** - -*/views/app/products.ejs* -``` -... -<%- output.products[i].id %> -<%- output.products[i].name %> -<%- output.products[i].code %> -<%- output.products[i].tags %> -... -``` - -**Solution** - -Enable output string encoding -``` -... -<%= output.products[i].id %> -<%= output.products[i].name %> -<%= output.products[i].code %> -<%= output.products[i].tags %> -... -``` -Notice the `=` symbol instead of `-`, which escapes the output. Note that this only prevents xss when the target for escaped output is in a html context. - -**Fixes** - -Implemented in the following files - -- *server.js* -- *views/app/products.ejs* - -**Recommendation** - -- Use Security header `X-XSS-Protection` to prevent reflected XSS attacks -- Limit raw rendering to internal trusted data only. Do not disable output encoding for untrusted data coming from external sources -- Always validate user input and escape them wherever necessary -- Use cookies securely `httpOnly`, `secure` are enabled in `express-cookies`. Refer to [this](https://expressjs.com/en/advanced/best-practice-security.html) -- Use a Content Security policy for your application using a library like [helmet](https://www.npmjs.com/package/helmet) - -**Reference** - -- -- -- diff --git a/docs/solution/a8-insecure-deserialization.md b/docs/solution/a8-insecure-deserialization.md deleted file mode 100644 index 4782e6bd5..000000000 --- a/docs/solution/a8-insecure-deserialization.md +++ /dev/null @@ -1,55 +0,0 @@ -# Insecure Deserialization - -The `Legacy Bulk Import` feature at http://127.0.0.1:9090/app/bulkproducts?legacy=true does not securely deserialize the data thus allowing remote code execution. - -![jse1](/resources/jse1.png) - -The following input will trigger the vulnerability - -``` -{"rce":"_$$ND_FUNC$$_function (){require('child_process').exec('id;cat /etc/passwd', function(error, stdout, stderr) { console.log(stdout) });}()"} -``` - -![jse2](/resources/jse2.png) - -**Vulnerable Code snippet** - -*core/appHandler.js* -``` -... -module.exports.bulkProductsLegacy = function (req,res){ - // TODO: Deprecate this soon - if(req.files.products){ - var products = serialize.unserialize(req.files.products.data.toString('utf8')) -... -``` - -**Solution** - -Since the required feature is to essentially parse a JSON, it can be parsed securely using `JSON.parse` instead. - -*core/appHandler.js* -``` -... -module.exports.bulkProductsLegacy = function (req,res){ - // TODO: Deprecate this soon - if(req.files.products){ - var products = JSON.parse(req.files.products.data.toString('utf8')) -... -``` - -**Fixes** - -Implemented in the following files - -- *core/appHandler.js* - -**Recommendation** - -- Use secure and recommended ways to implement application features -- Ensure that potentially vulnerable legacy features are't accessible - -**Reference** - -- -- \ No newline at end of file diff --git a/docs/solution/a9-using-components-with-known-vulnerability.md b/docs/solution/a9-using-components-with-known-vulnerability.md deleted file mode 100644 index 6745ef1a1..000000000 --- a/docs/solution/a9-using-components-with-known-vulnerability.md +++ /dev/null @@ -1,50 +0,0 @@ -# Using Components with Known Vulnerabilities - -## mathjs Remote Code Execution - -The version of mathjs(https://www.npmjs.com/package/mathjs) library used in the application has a remote code execution vulnerability that allows an attacker to run arbitrary code on the server. - -To understand how the exploit works, look at [this](https://capacitorset.github.io/mathjs/) - -The calculator implementation uses `mathjs.eval` to evaluate user input at - -http://127.0.0.1:9090/app/calc - -There is no input validation either, probably because it is going to be a maths equation which will contain symbols - -Malicious input that triggers command execution -``` -cos.constructor("spawn_sync = process.binding('spawn_sync'); normalizeSpawnArguments = function(c,b,a){if(Array.isArray(b)?b=b.slice(0):(a=b,b=[]),a===undefined&&(a={}),a=Object.assign({},a),a.shell){const g=[c].concat(b).join(' ');typeof a.shell==='string'?c=a.shell:c='/bin/sh',b=['-c',g];}typeof a.argv0==='string'?b.unshift(a.argv0):b.unshift(c);var d=a.env||process.env;var e=[];for(var f in d)e.push(f+'='+d[f]);return{file:c,args:b,options:a,envPairs:e};};spawnSync = function(){var d=normalizeSpawnArguments.apply(null,arguments);var a=d.options;var c;if(a.file=d.file,a.args=d.args,a.envPairs=d.envPairs,a.stdio=[{type:'pipe',readable:!0,writable:!1},{type:'pipe',readable:!1,writable:!0},{type:'pipe',readable:!1,writable:!0}],a.input){var g=a.stdio[0]=util._extend({},a.stdio[0]);g.input=a.input;}for(c=0;c -- diff --git a/docs/solution/ax-csrf.md b/docs/solution/ax-csrf.md deleted file mode 100644 index cccb521c2..000000000 --- a/docs/solution/ax-csrf.md +++ /dev/null @@ -1,45 +0,0 @@ -# Cross-site Request Forgery - -## CSRF in Add/Edit product, User Edit - -The application is vulnerable to a Cross-sites-Request-Forgery in `Product Management` feature. The application fails to implement anti-CSRF token to prevent forced browsing. - -http://127.0.0.1:9090/app/modifyproduct - -http://127.0.0.1:9090/app/useredit - -This issue can be exploited by an attacker by hosting a malicious page like the one below and tricking the victim onto visiting it. The below example will add a new product on behalf of the victim on visiting a crafted URL like `http://youtube.com.xyz.nxd/watch/v=cute-kitten` by the attacker - -*Attacker's Webpage* -```html - - -

- - - - -
- - -``` - -**Solution** - -CSRF vulnerabilities can be fixed by ensuring anti-CSRF tokens are needed for successful form submission. - -This can be done by using a module like [csurf](https://www.npmjs.com/package/csurf), and can save time needed to correctly implement your own logic. - -**Fixes** - -Implemented in the following files - -- *routes/app.js* -- *core/appHandler.js* -- *views/app/modifyproducts.ejs* -- *views/app/useredit.ejs* - -**Reference** - -- https://www.owasp.org/index.php/Cross-Site_Request_Forgery_(CSRF) -- https://github.com/expressjs/csurf \ No newline at end of file diff --git a/docs/solution/ax-unvalidated-redirects-and-forwards.md b/docs/solution/ax-unvalidated-redirects-and-forwards.md deleted file mode 100644 index f0eacff21..000000000 --- a/docs/solution/ax-unvalidated-redirects-and-forwards.md +++ /dev/null @@ -1,40 +0,0 @@ -# Unvalidated Redirects and Forwards - -The application fails to perform any validation before redirecting user to external URL based on untrusted user supplied data in the `redirect` function accessible at - -http://127.0.0.1:9090/app/redirect?url= - -**Vulnerable Code snippet** - -*/core/apphandler.js* -``` -... -module.exports.redirect = function(req,res){ - if(req.query.url){ - res.redirect(req.query.url) - }else{ - res.send('invalid redirect url') - } -} -... -``` - -An attacker can exploit this vulnerability using an URL such as below: - -``` -http://127.0.0.1:9090/redirect.action?url=http://www.attacker.nxd/phising_page -``` - -**Solution** - -Use an interceptor page which requires user approval before external redirection - -**Fixes** - -Implemented in the following files - -- *views/app/redirect.ejs* -- *core/appHandler.js* - -**Reference** -- https://www.owasp.org/index.php/Unvalidated_Redirects_and_Forwards_Cheat_Sheet \ No newline at end of file diff --git a/routes/main.js b/routes/main.js index b56db4040..3facf712b 100644 --- a/routes/main.js +++ b/routes/main.js @@ -1,6 +1,7 @@ var router = require('express').Router() var vulnDict = require('../config/vulns') var authHandler = require('../core/authHandler') +var winston = require('winston') module.exports = function (passport) { router.get('/', authHandler.isAuthenticated, function (req, res) { @@ -38,6 +39,10 @@ module.exports = function (passport) { }) router.get('/logout', function (req, res) { + if(req.user){ + let username = req.user.login + winston.log({level:'info',message:'Logout', username}) + } req.logout(); res.redirect('/'); }) diff --git a/server.js b/server.js index 18c0e18db..d3257be25 100644 --- a/server.js +++ b/server.js @@ -6,6 +6,7 @@ var ejs = require('ejs') var morgan = require('morgan') const fileUpload = require('express-fileupload'); var config = require('./config/server') +var winston = require('winston') //Initialize Express var app = express() @@ -16,6 +17,15 @@ app.use(morgan('tiny')) app.use(bodyParser.urlencoded({ extended: false })) app.use(fileUpload()); +// Configure winston logger +winston.configure({ + level:'info', + format: winston.format.json(), + transports: [ + new winston.transports.File({ filename: 'combined.log' }) + ] +}); + // Enable for Reverse proxy support // app.set('trust proxy', 1)