Skip to content
Open
52 changes: 52 additions & 0 deletions .github/workflows/shiftleft.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@

---
# This workflow integrates ShiftLeft NG SAST with GitHub
# Visit https://docs.shiftleft.io for help
name: ShiftLeft

on:
pull_request:
workflow_dispatch:

jobs:
NextGen-Static-Analysis:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Download ShiftLeft CLI
run: |
curl https://cdn.shiftleft.io/download/sl > ${GITHUB_WORKSPACE}/sl && chmod a+rx ${GITHUB_WORKSPACE}/sl
# ShiftLeft requires Java 1.8. Post the package step override the version
- name: Setup Java JDK
uses: actions/setup-java@v3
with:
distribution: zulu
java-version: 8
- name: Extract branch name
shell: bash
run: echo "##[set-output name=branch;]$(echo ${GITHUB_REF#refs/heads/})"
id: extract_branch
- name: NextGen Static Analysis
run: ${GITHUB_WORKSPACE}/sl analyze --strict --wait --app shiftleft-js-demo --container 18fgsa/s3-resource --tag branch=${{ github.head_ref || steps.extract_branch.outputs.branch }} --js --cpg .
env:
SHIFTLEFT_ACCESS_TOKEN: ${{ secrets.SHIFTLEFT_ACCESS_TOKEN }}

Build-Rules:
runs-on: ubuntu-latest
needs: NextGen-Static-Analysis
steps:
- uses: actions/checkout@v3
- name: Download ShiftLeft CLI
run: |
curl https://cdn.shiftleft.io/download/sl > ${GITHUB_WORKSPACE}/sl && chmod a+rx ${GITHUB_WORKSPACE}/sl
- name: Validate Build Rules
run: |
${GITHUB_WORKSPACE}/sl check-analysis --app shiftleft-js-demo \
--github-pr-number=${{github.event.number}} \
--github-pr-user=${{ github.repository_owner }} \
--github-pr-repo=${{ github.event.repository.name }} \
--github-token=${{ secrets.GITHUB_TOKEN }}
env:
SHIFTLEFT_ACCESS_TOKEN: ${{ secrets.SHIFTLEFT_ACCESS_TOKEN }}


106 changes: 106 additions & 0 deletions shiftleft.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
build_rules:
- id: "No critical or high SAST findings"
finding_types:
- vuln
- secret
cvss_31_severity_ratings:
- critical
- high
threshold: 0
options:
num_findings: 10 # Return 10 sast findings
- id: "No reachable SCA findings"
finding_types:
- oss_vuln
cvss_31_severity_ratings:
- critical
- high
threshold: 0
options:
reachable: true
num_findings: 10 # Return 10 reachable sca findings
- id: "No critical or high container findings"
finding_types:
- container
cvss_31_severity_ratings:
- critical
- high
threshold: 0
options:
num_findings: 10 # Return 10 container findings
# The above rule is perhaps the most common in that it
# is designed to be used with Pull Request and to block
# new vulns from being introduced that aren't already on
# the 'main' branch
#
# Below is enchalada with all the options shown
#
# ID is the name that will be reflected in the PR comments
# - id: build-rule-enchalada
# - vuln
#. - oss_vuln
# - secret
# - insight
# - container
# Do you want to block ALL types by severity?
# cvss_31_severity_ratings:
# - critical
# - high
# - medium
#. - low
# Do you want to focus on just one or more types?
# type:
# - Weak Random
# - Sensitive Data Leak
# - Deserialization
# - Directory Traversal
# - Sensitive Data Exposure
# - Remote Code Execution
# - Command Injection
# - Security Best Practices
# - Unsafe Reflection
# - Regex Injection
# - SQL Injection
# - XML External Entities
# - Template Injection
# - Cross-Site Scripting
# - JSON Injection
# - Potential SQL Injection
# - Potential Regex Injection
# - Header Injection
# - Security Misconfiguration
# - Deprecated Function Use
# - Mail Injection
# - Race Condition
# - Sensitive Data Usage
# - Open Redirect
# - Error Handling
# - HTTP to Database
# - HTTP to Model
# - LDAP Injection
# - Denial of Service
# - CRLF Injection
# - NoSQL Injection
# - Weak Hash
# - Session Injection
# - Server-Side Request Forgery
# - Prototype Pollution
# - Log Forging
# - XPath Injection
# - Insecure Authentication
# - Intent Redirection
# - Authentication Bypass
# - Weak Cipher
# - Crypto
# Focus by OWASP Category?
# owasp_category:
# - a01-2021-broken-access-control
# - a02-2021-cryptographic-failures
# - a03-2021-injection
# - a04-2021-insecure-design
# - a05-2021-security-misconfiguration
# - a06-2021-vulnerable-and-outdated-components
# - a07-2021-identification-and-authentication-failures
# - a08-2021-software-and-data-integrity-failures
# - a09-2021-security-logging-and-monitoring-failures
# - a10-2021-server-side-request-forgery-(ssrf)
34 changes: 28 additions & 6 deletions src/Controllers/Login.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,36 @@ class Login {
res.redirect('/login');
}

encryptData(secretText) {
encryptData(secretText) {
const crypto = require('crypto');

// Weak encryption
const desCipher = crypto.createCipheriv(
'des',
"This is a simple password, don't guess it"
);
// Generate a random 256-bit key (32 bytes) for AES-256
// In production, this should be securely generated once and stored in environment variables
const key = crypto.randomBytes(32);

// Generate a random 16-byte initialization vector
const iv = crypto.randomBytes(16);

// Use AES-256-GCM for strong encryption with authentication
const cipher = crypto.createCipheriv('aes-256-gcm', key, iv);

// Encrypt the data
let encrypted = cipher.update(secretText, 'utf8', 'hex');
encrypted += cipher.final('hex');

// Get the authentication tag for integrity verification
const authTag = cipher.getAuthTag();

// Return encrypted data along with IV and auth tag (all needed for decryption)
// In production, store key securely (e.g., environment variables, key management service)
return {
encrypted: encrypted,
iv: iv.toString('hex'),
authTag: authTag.toString('hex'),
key: key.toString('hex') // Store this securely, not in the return object in production
};
}

return desCipher.write(secretText, 'utf8', 'hex'); // BAD: weak encryption
}

Expand Down
68 changes: 60 additions & 8 deletions src/Controllers/Order.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,68 @@ class Order {
// Hash Key
return key;
}
encryptData(secretText) {
// Weak encryption
const desCipher = crypto.createCipheriv('des', encryptionKey);
return desCipher.update(secretText, 'utf8', 'hex');
}
encryptData(secretText) {
// Use AES-256-GCM for strong encryption (FIPS 140-2 compliant)
// Generate a random 32-byte key (256 bits) for AES-256
// Note: encryptionKey should be a 32-byte buffer, typically derived from a secure key management system

// Generate a random initialization vector (IV) for each encryption operation
const iv = crypto.randomBytes(16); // 16 bytes (128 bits) for AES

// Create cipher using AES-256-GCM (Galois/Counter Mode for authenticated encryption)
const cipher = crypto.createCipheriv('aes-256-gcm', encryptionKey, iv);

// Encrypt the data
let encrypted = cipher.update(secretText, 'utf8', 'hex');
encrypted += cipher.final('hex');

// Get the authentication tag for integrity verification
const authTag = cipher.getAuthTag();

// Return encrypted data with IV and auth tag (needed for decryption)
// Format: iv:authTag:encryptedData
return iv.toString('hex') + ':' + authTag.toString('hex') + ':' + encrypted;
}


decryptData(encryptedText) {
const desCipher = crypto.createDecipheriv('des', encryptionKey);
return desCipher.update(encryptedText);
decryptData(encryptedText) {
// Use AES-256-GCM instead of DES for strong encryption
// AES-256-GCM is recommended by NIST and OWASP for symmetric encryption

// Ensure encryptionKey is 32 bytes for AES-256
// The key should be stored securely (e.g., environment variables, key management service)
const algorithm = 'aes-256-gcm';

// Parse the encrypted data which should contain IV, auth tag, and encrypted content
// Expected format: iv:authTag:encryptedData (all in hex)
const parts = encryptedText.split(':');

if (parts.length !== 3) {
throw new Error('Invalid encrypted data format');
}

const iv = Buffer.from(parts[0], 'hex');
const authTag = Buffer.from(parts[1], 'hex');
const encryptedData = Buffer.from(parts[2], 'hex');

// Verify IV length (should be 12 bytes for GCM mode)
if (iv.length !== 12) {
throw new Error('Invalid IV length');
}

// Create decipher with AES-256-GCM
const decipher = crypto.createDecipheriv(algorithm, encryptionKey, iv);

// Set the authentication tag for integrity verification
decipher.setAuthTag(authTag);

// Decrypt the data
let decrypted = decipher.update(encryptedData);
decrypted = Buffer.concat([decrypted, decipher.final()]);

return decrypted.toString('utf8');
}

addToOrder(req, res) {
const order = req.body;
console.log(req.body);
Expand Down
64 changes: 49 additions & 15 deletions src/views.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,22 +9,56 @@ module.exports = app => {
app.get(`/login`, (req, res) => res.render('Login'));

app.get(`/user-input`, (req, res) => {
/*
User input vulnerability,
if the user passes vulnerable javascipt code, its executed in user's browser
ex: alert('hi')
*/
let result = '';
try {
result = require('util').inspect(eval(req.query.userInput));
} catch (ex) {
console.error(ex);
}
res.render('UserInput', {
userInput: req.query.userInput,
result,
date: new Date().toUTCString()
[
// Input validation middleware
query('userInput')
.optional()
.isString()
.trim()
.isLength({ max: 500 })
.withMessage('Input must be a string with maximum 500 characters')
],
(req, res) => {
// Validate input
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).render('UserInput', {
userInput: '',
result: 'Invalid input provided',
date: new Date().toUTCString(),
error: errors.array()[0].msg
});
}

// Sanitize user input to prevent XSS
const sanitizedInput = DOMPurify.sanitize(req.query.userInput || '', {
ALLOWED_TAGS: [],
ALLOWED_ATTR: []
});

// Safe result processing - NEVER use eval()
// Instead, display the sanitized input as-is or process it safely
let result = '';

if (sanitizedInput) {
// If you need to perform calculations or operations, use a safe parser
// For demonstration, we're just echoing the sanitized input
result = `Received input: ${sanitizedInput}`;

// Alternative: Use a safe expression evaluator library like 'expr-eval'
// or implement specific, controlled operations based on business logic
} else {
result = 'No input provided';
}

// Render with sanitized data
res.render('UserInput', {
userInput: sanitizedInput,
result: result,
date: new Date().toUTCString()
});
}

});

app.get(`/`, secured.get);
Expand Down
Loading