CVE-2018-12457: Incorrect Permission Assignment for Critical Resource (CWE-732)
value: expressCart before 1.1.6 allows remote attackers to create an admin user via a /admin/setup Referer header.
CWE description: The product specifies permissions for a security-critical resource in a way that allows that resource to be read or modified by unintended actors.
CVSS v3 base score: 8.8
Published: 2018-06-15T14:29Z
Repository: https://github.com/mrvautin/expressCart
Language: JavaScript
Files changed: routes/user.js
Commit date: 2018-05-30 18:35:17 +0930
Commit message: Fix issue with ability to create admin user by setting referrer

Diff: diff --git a/routes/user.js b/routes/user.js
index 9c8815b..2089266 100644
--- a/routes/user.js
+++ b/routes/user.js
@@ -144,60 +144,65 @@ router.post('/admin/user/insert', common.restrict, (req, res) => {
     const db = req.app.db;
 
     // set the account to admin if using the setup form. Eg: First user account
     let urlParts = url.parse(req.header('Referer'));
 
-    let isAdmin = false;
-    if(urlParts.path === '/admin/setup'){
-        isAdmin = true;
-    }
+    // Check number of users
+    db.users.count({}, (err, userCount) => {
+        let isAdmin = false;
 
-    let doc = {
-        usersName: req.body.usersName,
-        userEmail: req.body.userEmail,
-        userPassword: bcrypt.hashSync(req.body.userPassword, 10),
-        isAdmin: isAdmin
-    };
-
-    // check for existing user
-    db.users.findOne({'userEmail': req.body.userEmail}, (err, user) => {
-        if(user){
-            // user already exists with that email address
-            console.error(colors.red('Failed to insert user, possibly already exists: ' + err));
-            req.session.message = 'A user with that email address already exists';
-            req.session.messageType = 'danger';
-            res.redirect('/admin/user/new');
-            return;
+        // if no users, setup user as admin
+        if(userCount === 0){
+            isAdmin = true;
         }
-        // email is ok to be used.
-        db.users.insert(doc, (err, doc) => {
-            // show the view
-            if(err){
-                if(doc){
-                    console.error(colors.red('Failed to insert user: ' + err));
-                    req.session.message = 'User exists';
-                    req.session.messageType = 'danger';
-                    res.redirect('/admin/user/edit/' + doc._id);
-                    return;
-                }
-                console.error(colors.red('Failed to insert user: ' + err));
-                req.session.message = 'New user creation failed';
+
+        let doc = {
+            usersName: req.body.usersName,
+            userEmail: req.body.userEmail,
+            userPassword: bcrypt.hashSync(req.body.userPassword, 10),
+            isAdmin: isAdmin
+        };
+
+        // check for existing user
+        db.users.findOne({'userEmail': req.body.userEmail}, (err, user) => {
+            if(user){
+                // user already exists with that email address
+                console.error(colors.red('Failed to insert user, possibly already exists: ' + err));
+                req.session.message = 'A user with that email address already exists';
                 req.session.messageType = 'danger';
                 res.redirect('/admin/user/new');
                 return;
             }
-            req.session.message = 'User account inserted';
-            req.session.messageType = 'success';
-
-            // if from setup we add user to session and redirect to login.
-            // Otherwise we show users screen
-            if(urlParts.path === '/admin/setup'){
-                req.session.user = req.body.userEmail;
-                res.redirect('/admin/login');
-                return;
-            }
-            res.redirect('/admin/users');
+            // email is ok to be used.
+            db.users.insert(doc, (err, doc) => {
+                // show the view
+                if(err){
+                    if(doc){
+                        console.error(colors.red('Failed to insert user: ' + err));
+                        req.session.message = 'User exists';
+                        req.session.messageType = 'danger';
+                        res.redirect('/admin/user/edit/' + doc._id);
+                        return;
+                    }
+                    console.error(colors.red('Failed to insert user: ' + err));
+                    req.session.message = 'New user creation failed';
+                    req.session.messageType = 'danger';
+                    res.redirect('/admin/user/new');
+                    return;
+                }
+                req.session.message = 'User account inserted';
+                req.session.messageType = 'success';
+
+                // if from setup we add user to session and redirect to login.
+                // Otherwise we show users screen
+                if(urlParts.path === '/admin/setup'){
+                    req.session.user = req.body.userEmail;
+                    res.redirect('/admin/login');
+                    return;
+                }
+                res.redirect('/admin/users');
+            });
         });
     });
 });
 
 module.exports = router;

Vulnerable code:     let isAdmin = false;
    if(urlParts.path === '/admin/setup'){
        isAdmin = true;
    }
    let doc = {
        usersName: req.body.usersName,
        userEmail: req.body.userEmail,
        userPassword: bcrypt.hashSync(req.body.userPassword, 10),
        isAdmin: isAdmin
    };

    // check for existing user
    db.users.findOne({'userEmail': req.body.userEmail}, (err, user) => {
        if(user){
            // user already exists with that email address
            console.error(colors.red('Failed to insert user, possibly already exists: ' + err));
            req.session.message = 'A user with that email address already exists';
            req.session.messageType = 'danger';
            res.redirect('/admin/user/new');
            return;
        // email is ok to be used.
        db.users.insert(doc, (err, doc) => {
            // show the view
            if(err){
                if(doc){
                    console.error(colors.red('Failed to insert user: ' + err));
                    req.session.message = 'User exists';
                    req.session.messageType = 'danger';
                    res.redirect('/admin/user/edit/' + doc._id);
                    return;
                }
                console.error(colors.red('Failed to insert user: ' + err));
                req.session.message = 'New user creation failed';
            req.session.message = 'User account inserted';
            req.session.messageType = 'success';

            // if from setup we add user to session and redirect to login.
            // Otherwise we show users screen
            if(urlParts.path === '/admin/setup'){
                req.session.user = req.body.userEmail;
                res.redirect('/admin/login');
                return;
            }
            res.redirect('/admin/users');
Fixed code:     // Check number of users
    db.users.count({}, (err, userCount) => {
        let isAdmin = false;
        // if no users, setup user as admin
        if(userCount === 0){
            isAdmin = true;

        let doc = {
            usersName: req.body.usersName,
            userEmail: req.body.userEmail,
            userPassword: bcrypt.hashSync(req.body.userPassword, 10),
            isAdmin: isAdmin
        };

        // check for existing user
        db.users.findOne({'userEmail': req.body.userEmail}, (err, user) => {
            if(user){
                // user already exists with that email address
                console.error(colors.red('Failed to insert user, possibly already exists: ' + err));
                req.session.message = 'A user with that email address already exists';
            // email is ok to be used.
            db.users.insert(doc, (err, doc) => {
                // show the view
                if(err){
                    if(doc){
                        console.error(colors.red('Failed to insert user: ' + err));
                        req.session.message = 'User exists';
                        req.session.messageType = 'danger';
                        res.redirect('/admin/user/edit/' + doc._id);
                        return;
                    }
                    console.error(colors.red('Failed to insert user: ' + err));
                    req.session.message = 'New user creation failed';
                    req.session.messageType = 'danger';
                    res.redirect('/admin/user/new');
                    return;
                }
                req.session.message = 'User account inserted';
                req.session.messageType = 'success';

                // if from setup we add user to session and redirect to login.
                // Otherwise we show users screen
                if(urlParts.path === '/admin/setup'){
                    req.session.user = req.body.userEmail;
                    res.redirect('/admin/login');
                    return;
                }
                res.redirect('/admin/users');
            });