A08: Software and Data Integrity Failures
Learn about software and data integrity failures, including insecure CI/CD pipelines, unsigned updates, and untrusted deserialization. Understand how to verify the integrity of code and data.
Software and Data Integrity Failures relate to code and infrastructure that does not protect against integrity violations. This is a new category in the 2021 OWASP Top 10, combining insecure deserialization with supply chain security concerns.
What Are Integrity Failures?
Integrity failures occur when:
- Applications rely on plugins, libraries, or modules from untrusted sources
- CI/CD pipelines don't verify the integrity of code before deployment
- Auto-update functionality downloads updates without signature verification
- Objects or data are deserialized from untrusted sources
When integrity is compromised, attackers can inject malicious code into your software supply chain.
Common Vulnerability Patterns
1. Insecure Deserialization
The Problem:
Deserializing untrusted data can lead to remote code execution.
// VULNERABLE: Deserializing user-controlled data
app.post('/api/import', (req, res) => {
// User sends serialized JavaScript object
const data = eval('(' + req.body.data + ')'); // DANGEROUS!
// ...
});
// VULNERABLE: Deserializing YAML without restrictions
const yaml = require('js-yaml');
app.post('/config', (req, res) => {
const config = yaml.load(req.body.yaml); // Can execute code!
});
YAML parsers with full feature support can instantiate objects and call constructors, leading to code execution.
Secure Implementation:
// SECURE: Use JSON for data interchange
app.post('/api/import', (req, res) => {
try {
const data = JSON.parse(req.body.data); // Safe
// Validate the structure
if (!validateSchema(data)) {
return res.status(400).json({ error: 'Invalid data format' });
}
} catch (e) {
return res.status(400).json({ error: 'Invalid JSON' });
}
});
// SECURE: Use safe YAML loading
const yaml = require('js-yaml');
app.post('/config', (req, res) => {
try {
// safeLoad only parses basic types, no code execution
const config = yaml.load(req.body.yaml, { schema: yaml.SAFE_SCHEMA });
} catch (e) {
return res.status(400).json({ error: 'Invalid YAML' });
}
});
2. Untrusted Dependencies
The Problem:
Installing packages from public registries without verification.
# VULNERABLE: Installing without verification
npm install some-random-package
# VULNERABLE: Using unverified CDN scripts
<script src="https://cdn.example.com/library.js"></script>
Secure Practices:
# Use lock files with integrity hashes
npm ci # Installs from lock file, verifies integrity
# Verify package signatures
npm audit signatures
For CDN scripts, use Subresource Integrity (SRI):
<!-- SECURE: Verify script integrity -->
<script
src="https://cdn.example.com/library.js"
integrity="sha384-oqVuAfXRKap7fdgcCY5uykM6+R9GqQ8K/uxy9rx7HNQlGYl1kPzQho1wx4JwY8wC"
crossorigin="anonymous">
</script>
3. Insecure CI/CD Pipelines
The Problem:
CI/CD pipelines that don't verify code integrity can deploy malicious code.
# VULNERABLE: No code signing or verification
name: Deploy
on: push
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: npm install
- run: npm run build
- run: ./deploy.sh # Deploys whatever was built
Secure Pipeline:
# SECURE: Verify commits and artifacts
name: Deploy
on:
push:
branches: [main]
jobs:
verify:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
# Verify GPG signature on commits
- name: Verify commit signature
run: |
git verify-commit HEAD || {
echo "Commit is not signed!"
exit 1
}
# Lock file verification
- name: Install dependencies
run: npm ci # Strict installation from lock file
# Security scan before build
- name: Security audit
run: npm audit --audit-level=high
build:
needs: verify
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: npm ci
- run: npm run build
# Sign the artifact
- name: Sign artifact
run: |
cosign sign-blob --key cosign.key dist/app.js
- uses: actions/upload-artifact@v4
with:
name: build
path: dist/
deploy:
needs: build
runs-on: ubuntu-latest
environment: production
steps:
- uses: actions/download-artifact@v4
# Verify artifact signature before deploy
- name: Verify artifact
run: |
cosign verify-blob --key cosign.pub dist/app.js
- run: ./deploy.sh
4. Unsigned Auto-Updates
The Problem:
Auto-update mechanisms that don't verify signatures can install malicious updates.
// VULNERABLE: Download and execute without verification
async function checkForUpdates() {
const response = await fetch('https://updates.example.com/latest');
const update = await response.json();
if (update.version > currentVersion) {
const binary = await fetch(update.downloadUrl);
// Install without verification!
await installUpdate(binary);
}
}
Secure Implementation:
const crypto = require('crypto');
// Public key for update verification
const UPDATE_PUBLIC_KEY = process.env.UPDATE_PUBLIC_KEY;
async function checkForUpdates() {
const response = await fetch('https://updates.example.com/latest');
const update = await response.json();
if (update.version > currentVersion) {
// Download the update
const binaryResponse = await fetch(update.downloadUrl);
const binary = await binaryResponse.buffer();
// Download the signature
const sigResponse = await fetch(update.signatureUrl);
const signature = await sigResponse.text();
// Verify signature
const verify = crypto.createVerify('SHA256');
verify.update(binary);
if (!verify.verify(UPDATE_PUBLIC_KEY, signature, 'base64')) {
console.error('Update signature verification failed!');
return;
}
// Verify checksum
const hash = crypto.createHash('sha256').update(binary).digest('hex');
if (hash !== update.checksum) {
console.error('Update checksum mismatch!');
return;
}
// Now safe to install
await installUpdate(binary);
}
}
Supply Chain Security
Software Bill of Materials (SBOM)
Generate an SBOM to track all components in your software:
# Generate SBOM using Syft
syft packages dir:. -o spdx-json > sbom.json
# Generate SBOM for container
syft packages docker:myapp:latest -o cyclonedx-json > sbom.json
Container Image Signing
Sign container images to ensure integrity:
# Sign with Cosign
cosign sign --key cosign.key myregistry/myapp:v1.0.0
# Verify before deploying
cosign verify --key cosign.pub myregistry/myapp:v1.0.0
Enforce signature verification in Kubernetes:
# Kyverno policy to require signed images
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: verify-image-signature
spec:
validationFailureAction: enforce
rules:
- name: verify-signature
match:
resources:
kinds:
- Pod
verifyImages:
- imageReferences:
- "myregistry/*"
attestors:
- entries:
- keys:
publicKeys: |
-----BEGIN PUBLIC KEY-----
...
-----END PUBLIC KEY-----
Key Takeaways
- Never deserialize untrusted data - use JSON, avoid eval
- Use lock files and verify package integrity
- Sign code and artifacts in your CI/CD pipeline
- Verify signatures before deploying or installing updates
- Generate SBOMs to track your software supply chain
- Use Subresource Integrity for CDN-hosted scripts
- Sign container images and enforce verification
- Require signed commits for sensitive repositories
Software supply chain attacks are increasing in sophistication. Building integrity verification into your development and deployment processes is essential for defense.
Found an issue?