State Transition Manipulation and Workflow Jumping
Applications that fail to validate proper state transitions in business workflows allow attackers to skip approval steps, jump directly to final states, or manipulate workflow status parameters to bypass required authorization checkpoints.
Preview example – JAVASCRIPT
// VULNERABLE: Document approval workflow with state manipulation flaws
const express = require('express');
const app = express();
// PROBLEM: Workflow states defined as simple strings without validation
const WORKFLOW_STATES = {
DRAFT: 'draft',
PENDING_REVIEW: 'pending_review',
UNDER_REVIEW: 'under_review',
APPROVED: 'approved',
REJECTED: 'rejected',
PUBLISHED: 'published'
};
// VULNERABLE: Document status update without proper validation
app.put('/api/documents/:id/status', async (req, res) => {
const { id } = req.params;
const { status, comment } = req.body;
try {
const document = await Document.findById(id);
if (!document) {
return res.status(404).json({ error: 'Document not found' });
}
// PROBLEM 1: No validation of state transition logic
// Attacker can jump from 'draft' directly to 'published'
if (Object.values(WORKFLOW_STATES).includes(status)) {
document.status = status; // DANGEROUS: Any status allowed
document.lastModified = new Date();
if (comment) {
document.comments.push({
user: req.user.id,
comment,
timestamp: new Date()
});
}
await document.save();
res.json({ success: true, document });
} else {
res.status(400).json({ error: 'Invalid status' });
}
} catch (error) {
res.status(500).json({ error: 'Failed to update document' });
}
});
// PROBLEM 2: Approval endpoint without proper authorization
app.post('/api/documents/:id/approve', async (req, res) => {
const { id } = req.params;
const { decision, notes } = req.body; // 'approve' or 'reject'
try {
const document = await Document.findById(id);
// INSUFFICIENT: Only checking if user exists, not role/permissions
if (!req.user) {
return res.status(401).json({ error: 'Authentication required' });
}
// PROBLEM: No validation of approver authority
// PROBLEM: No check if document is in correct state for approval
if (decision === 'approve') {
document.status = WORKFLOW_STATES.APPROVED;
document.approvedBy = req.user.id;
document.approvedAt = new Date();
} else {
document.status = WORKFLOW_STATES.REJECTED;
document.rejectedBy = req.user.id;
document.rejectedAt = new Date();
}
if (notes) {
document.approvalNotes = notes;
}
await document.save();
res.json({ success: true, document });
} catch (error) {
res.status(500).json({ error: 'Approval failed' });
}
});
// PROBLEM 3: Bulk operations without individual validation
app.post('/api/documents/bulk-approve', async (req, res) => {
const { documentIds, decision } = req.body;
try {
// VULNERABLE: Bulk approval without checking individual permissions
const result = await Document.updateMany(
{ _id: { $in: documentIds } },
{
status: decision === 'approve' ? WORKFLOW_STATES.APPROVED : WORKFLOW_STATES.REJECTED,
approvedBy: req.user.id,
approvedAt: new Date()
}
);
res.json({ success: true, modifiedCount: result.modifiedCount });
} catch (error) {
res.status(500).json({ error: 'Bulk approval failed' });
}
});
// Attack scenarios:
// 1. POST /api/documents/123/status {"status": "published"} - Skip all approvals
// 2. Approve documents user doesn't have authority over
// 3. Bulk approve sensitive documents
// 4. Change status of already-approved documents
// 5. Manipulate document state during concurrent reviews