Install this skill
npx skills add mukul975/Anthropic-Cybersecurity-SkillsFramework mappings
MITRE ATT&CK
Overview
Web application vulnerability triage is the process of reviewing findings from DAST (Dynamic Application Security Testing) and SAST (Static Application Security Testing) tools to validate true positives, dismiss false positives, assign risk ratings using the OWASP Risk Rating Methodology, and prioritize remediation. Effective triage reduces alert fatigue and focuses development teams on the vulnerabilities that matter most.
When to Use
- When conducting security assessments that involve performing web application vulnerability triage
- When following incident response procedures for related security events
- When performing scheduled security testing or auditing activities
- When validating security controls through hands-on testing
Prerequisites
- DAST scan results (OWASP ZAP, Burp Suite, Acunetix)
- SAST scan results (Semgrep, SonarQube, Checkmarx, Snyk Code)
- Python 3.9+ with
requests,beautifulsoup4 - Burp Suite Professional or OWASP ZAP for manual validation
- DefectDojo or similar for finding management
OWASP Risk Rating Methodology
Risk Calculation
Risk = Likelihood x ImpactLikelihood Factors (0-9 scale)
| Factor Group | Factor | Description |
|---|---|---|
| Threat Agent | Skill Level | How technically skilled is the attacker? |
| Threat Agent | Motive | How motivated is the attacker? |
| Threat Agent | Opportunity | What resources/access are needed? |
| Threat Agent | Size | How large is the potential threat agent group? |
| Vulnerability | Ease of Discovery | How easy is it to find the vulnerability? |
| Vulnerability | Ease of Exploit | How easy is it to exploit? |
| Vulnerability | Awareness | How well known is the vulnerability? |
| Vulnerability | Intrusion Detection | How likely is exploitation to be detected? |
Impact Factors (0-9 scale)
| Factor Group | Factor | Description |
|---|---|---|
| Technical | Confidentiality | How much data could be disclosed? |
| Technical | Integrity | How much data could be corrupted? |
| Technical | Availability | How much service could be lost? |
| Technical | Accountability | Can actions be traced to attacker? |
| Business | Financial Damage | Revenue loss, regulatory fines |
| Business | Reputation Damage | Brand trust erosion |
| Business | Non-compliance | Regulatory violation exposure |
| Business | Privacy Violation | PII/PHI exposure volume |
Risk Matrix
| Low Impact (0-3) | Medium Impact (3-6) | High Impact (6-9) | |
|---|---|---|---|
| High Likelihood (6-9) | Medium | High | Critical |
| Medium Likelihood (3-6) | Low | Medium | High |
| Low Likelihood (0-3) | Note | Low | Medium |
Triage Process
Step 1: Categorize by OWASP Top 10
OWASP_TOP_10_2021 = {
"A01": "Broken Access Control",
"A02": "Cryptographic Failures",
"A03": "Injection",
"A04": "Insecure Design",
"A05": "Security Misconfiguration",
"A06": "Vulnerable and Outdated Components",
"A07": "Identification and Authentication Failures",
"A08": "Software and Data Integrity Failures",
"A09": "Security Logging and Monitoring Failures",
"A10": "Server-Side Request Forgery",
}
CWE_TO_OWASP = {
"CWE-79": "A03", # XSS -> Injection
"CWE-89": "A03", # SQL Injection
"CWE-78": "A03", # OS Command Injection
"CWE-352": "A01", # CSRF -> Access Control
"CWE-22": "A01", # Path Traversal
"CWE-200": "A02", # Information Exposure
"CWE-327": "A02", # Weak Cryptography
"CWE-287": "A07", # Authentication Issues
"CWE-918": "A10", # SSRF
"CWE-502": "A08", # Deserialization
"CWE-611": "A05", # XXE -> Misconfiguration
}Step 2: Validate True vs False Positives
def triage_finding(finding):
"""Classify finding as true_positive, false_positive, or needs_review."""
fp_indicators = [
"Content-Security-Policy header not set", # Often informational
"X-Content-Type-Options header missing", # Low severity header
"Cookie without SameSite attribute", # Context dependent
]
for indicator in fp_indicators:
if indicator.lower() in finding.get("title", "").lower():
if finding.get("severity", "").lower() in ("info", "low"):
return "false_positive", "Common informational finding"
# Check for confirmed exploitation evidence
if finding.get("evidence") and finding.get("confidence", "").lower() == "certain":
return "true_positive", "Scanner confirmed exploitation"
# SAST findings need manual code review
if finding.get("source") == "sast":
if finding.get("cwe") in ["CWE-89", "CWE-78", "CWE-79"]:
return "needs_review", "Injection finding requires manual code review"
return "needs_review", "Requires manual validation"Step 3: Risk Score Calculation
def calculate_risk_score(finding, app_context):
"""Calculate OWASP risk rating for a web application finding."""
# Likelihood factors
likelihood = {
"skill_level": 6 if finding["cwe"] in ["CWE-89", "CWE-79"] else 4,
"motive": 7, # Financial gain
"opportunity": 7 if finding.get("authenticated") == False else 4,
"size": 9 if finding.get("internet_facing") else 4,
"ease_of_discovery": 8 if finding.get("scanner_detected") else 5,
"ease_of_exploit": 7 if finding.get("exploit_available") else 4,
"awareness": 6,
"intrusion_detection": 3 if app_context.get("waf_enabled") else 8,
}
# Impact factors
impact = {
"confidentiality": 9 if "data_exposure" in finding.get("tags", []) else 5,
"integrity": 9 if finding["cwe"] in ["CWE-89", "CWE-78"] else 4,
"availability": 7 if "dos" in finding.get("tags", []) else 2,
"accountability": 3 if app_context.get("logging_enabled") else 7,
"financial": 7 if app_context.get("processes_payments") else 3,
"reputation": 6 if app_context.get("customer_facing") else 2,
"compliance": 8 if app_context.get("pci_scope") else 3,
"privacy": 9 if app_context.get("handles_pii") else 2,
}
likelihood_score = sum(likelihood.values()) / len(likelihood)
impact_score = sum(impact.values()) / len(impact)
risk_score = likelihood_score * impact_score
if risk_score >= 42:
risk_level = "Critical"
elif risk_score >= 24:
risk_level = "High"
elif risk_score >= 12:
risk_level = "Medium"
elif risk_score >= 3:
risk_level = "Low"
else:
risk_level = "Note"
return {
"likelihood_score": round(likelihood_score, 1),
"impact_score": round(impact_score, 1),
"risk_score": round(risk_score, 1),
"risk_level": risk_level,
}Step 4: Generate Triage Report
# Process DAST/SAST results through triage pipeline
python3 scripts/process.py \
--input zap_results.json \
--format zap \
--app-context app_config.json \
--output triage_report.jsonManual Validation Techniques
SQL Injection Validation
# Test parameter with single quote
GET /search?q=test' HTTP/1.1
# Test with boolean-based payload
GET /search?q=test' AND 1=1-- HTTP/1.1
GET /search?q=test' AND 1=2-- HTTP/1.1
# Time-based verification
GET /search?q=test'; WAITFOR DELAY '0:0:5'-- HTTP/1.1XSS Validation
# Reflected XSS test
GET /search?q=<script>alert(document.domain)</script> HTTP/1.1
# Check if output is encoded
GET /search?q="><img src=x onerror=alert(1)> HTTP/1.1
# DOM-based XSS
GET /page#<img src=x onerror=alert(1)> HTTP/1.1References
Source materials
References and resources
Everything below is rendered for inspection. Script files are read-only and never run.
References 3
api-reference.md1.5 KB
API Reference: Web Application Vulnerability Triage
SLA Remediation Timelines
| Severity | CVSS Range | SLA (Days) |
|---|---|---|
| Critical | 9.0-10.0 | 7 |
| High | 7.0-8.9 | 30 |
| Medium | 4.0-6.9 | 90 |
| Low | 0.1-3.9 | 180 |
| Info | 0.0 | 365 |
Scanner JSON Formats
OWASP ZAP
| Field | Description |
|---|---|
alerts[].name |
Finding title |
alerts[].risk |
Severity (High, Medium, Low, Informational) |
alerts[].cweid |
CWE identifier |
alerts[].uri |
Affected URL |
Burp Suite
| Field | Description |
|---|---|
issues[].name |
Issue name |
issues[].severity |
high, medium, low, information |
issues[].url |
Affected endpoint |
issues[].parameter |
Vulnerable parameter |
Nikto JSON
| Field | Description |
|---|---|
vulnerabilities[].id |
Nikto ID |
vulnerabilities[].OSVDB |
OSVDB reference |
vulnerabilities[].url |
Affected path |
Priority Scoring Formula
score = cvss * 10
+ 5 if parameter identified
+ 10 if injection-type vulnerability
+ 8 if authentication-relatedPython Libraries
| Library | Version | Purpose |
|---|---|---|
json |
stdlib | Ingest scanner output |
datetime |
stdlib | SLA deadline calculation |
collections |
stdlib | Severity distribution |
References
- CVSS v3.1: https://www.first.org/cvss/specification-document
- OWASP Risk Rating: https://owasp.org/www-community/OWASP_Risk_Rating_Methodology
- CWE Database: https://cwe.mitre.org/
standards.md1.3 KB
Standards - Web Application Vulnerability Triage
Primary Standards
OWASP Risk Rating Methodology
- URL: https://owasp.org/www-community/OWASP_Risk_Rating_Methodology
- Purpose: Structured approach to evaluating likelihood and impact of web vulnerabilities
OWASP Top 10 (2021)
- URL: https://owasp.org/www-project-top-ten/
- Categories: A01 through A10 covering the most critical web application security risks
OWASP Web Security Testing Guide v4.2
- URL: https://owasp.org/www-project-web-security-testing-guide/
- Relevance: Manual validation techniques for scanner findings
CWE/SANS Top 25 Most Dangerous Software Weaknesses
- URL: https://cwe.mitre.org/top25/
- Relevance: Maps findings to common weakness enumeration for categorization
CVSS v3.1 / v4.0
- URL: https://www.first.org/cvss/
- Relevance: Industry standard vulnerability scoring complementing OWASP risk rating
Scanner References
| Tool | Type | Documentation |
|---|---|---|
| OWASP ZAP | DAST | https://www.zaproxy.org/docs/ |
| Burp Suite | DAST | https://portswigger.net/burp/documentation |
| Semgrep | SAST | https://semgrep.dev/docs/ |
| SonarQube | SAST | https://docs.sonarqube.org/ |
| Snyk Code | SAST | https://docs.snyk.io/scan-with-snyk/snyk-code |
workflows.md1.3 KB
Workflows - Web Application Vulnerability Triage
Workflow 1: DAST Finding Triage
- Import DAST scan results (ZAP XML/JSON, Burp XML)
- Auto-classify findings by OWASP Top 10 category via CWE mapping
- Filter out known false positive patterns (missing headers on non-sensitive pages, etc.)
- Flag confirmed exploitation findings as true positives
- Queue remaining findings for manual validation
- Security analyst validates with manual testing in Burp/ZAP
- Assign OWASP risk rating to validated findings
- Push validated findings to DefectDojo/Jira
Workflow 2: SAST Finding Triage
- Import SAST scan results (Semgrep JSON, SonarQube)
- Filter out findings in test files, example code, and dead code
- Cross-reference against data flow analysis for injection findings
- Review code context to validate exploitability
- Assign severity based on data sensitivity and exposure
- Create development tickets for validated findings
Workflow 3: Combined Triage and Deduplication
- Import both DAST and SAST findings for same application
- Correlate SAST code findings with DAST runtime findings
- Findings confirmed by both DAST and SAST get elevated priority
- Deduplicate findings pointing to same root cause
- Generate unified triage report with remediation priority
Scripts 1
agent.py3.9 KB
Display-only source. This catalog never executes bundled scripts.
#!/usr/bin/env python3
"""Agent for web application vulnerability triage.
Ingests scan results from multiple scanners (Nikto, ZAP, Burp),
deduplicates findings, prioritizes by CVSS and exploitability,
assigns SLA deadlines, and generates a triage report.
"""
import json
import sys
from datetime import datetime, timedelta
from collections import defaultdict
SLA_DAYS = {"Critical": 7, "High": 30, "Medium": 90, "Low": 180, "Info": 365}
CVSS_SEVERITY = {
(9.0, 10.0): "Critical", (7.0, 8.9): "High",
(4.0, 6.9): "Medium", (0.1, 3.9): "Low", (0.0, 0.0): "Info",
}
class VulnTriageAgent:
"""Triages web application vulnerability scan results."""
def __init__(self):
self.findings = []
self.triaged = []
def ingest_json_report(self, filepath, scanner_name="unknown"):
"""Load findings from a JSON scan report."""
with open(filepath) as f:
data = json.load(f)
items = data if isinstance(data, list) else data.get("findings", data.get("alerts", []))
for item in items:
self.findings.append({
"title": item.get("title", item.get("name", item.get("description", "")[:80])),
"severity": item.get("severity", item.get("risk", "Medium")),
"cvss": item.get("cvss", item.get("cvss_score", 0)),
"url": item.get("url", item.get("uri", "")),
"parameter": item.get("parameter", item.get("param", "")),
"description": item.get("description", "")[:500],
"cwe": item.get("cwe", item.get("cweid", "")),
"scanner": scanner_name,
})
return len(items)
def deduplicate(self):
"""Remove duplicate findings based on title + URL + parameter."""
seen = set()
unique = []
for f in self.findings:
key = (f["title"].lower(), f["url"], f["parameter"])
if key not in seen:
seen.add(key)
unique.append(f)
self.findings = unique
return len(unique)
def classify_severity(self, cvss_score):
for (low, high), severity in CVSS_SEVERITY.items():
if low <= cvss_score <= high:
return severity
return "Medium"
def prioritize(self):
"""Score and prioritize findings for remediation."""
now = datetime.utcnow()
for f in self.findings:
severity = f.get("severity", "Medium")
if severity not in SLA_DAYS:
severity = self.classify_severity(float(f.get("cvss", 0)))
f["severity"] = severity
sla_days = SLA_DAYS.get(severity, 90)
f["sla_deadline"] = (now + timedelta(days=sla_days)).isoformat()
f["sla_days"] = sla_days
priority_score = float(f.get("cvss", 0)) * 10
if f.get("parameter"):
priority_score += 5
if "injection" in f.get("title", "").lower():
priority_score += 10
if "authentication" in f.get("title", "").lower():
priority_score += 8
f["priority_score"] = round(priority_score, 1)
self.triaged = sorted(self.findings, key=lambda x: x["priority_score"], reverse=True)
return self.triaged
def generate_report(self):
self.deduplicate()
self.prioritize()
severity_dist = defaultdict(int)
for f in self.triaged:
severity_dist[f["severity"]] += 1
report = {
"report_date": datetime.utcnow().isoformat(),
"total_findings": len(self.triaged),
"severity_distribution": dict(severity_dist),
"top_priority": self.triaged[:20],
}
print(json.dumps(report, indent=2, default=str))
return report
def main():
agent = VulnTriageAgent()
for filepath in sys.argv[1:]:
agent.ingest_json_report(filepath, scanner_name=filepath)
agent.generate_report()
if __name__ == "__main__":
main()
Keep exploring