Article
Fortify Static Code Analyzer (SCA) is a powerful static application security testing (SAST) tool that identifies security vulnerabilities in source code. For Java applications, it provides deep security analysis across the entire codebase, identifying issues like injection flaws, cryptographic weaknesses, and access control problems.
This guide covers everything from basic setup to advanced integration and remediation strategies.
Fortify SCA Architecture Overview
- Source Analyzer: Parses code and builds intermediate representation
- Scan Engine: Applies security rules to identify vulnerabilities
- Audit Workbench: GUI for reviewing and prioritizing issues
- Software Security Center: Centralized management and reporting
- Custom Rules: Extend analysis with organization-specific rules
1. Installation and Setup
System Requirements:
- Windows/Linux/macOS
- Java 8 or higher
- 4GB+ RAM (8GB+ recommended)
- 10GB+ disk space
Installation Steps:
- Download Fortify SCA from Micro Focus website
- Run installer with administrative privileges
- Configure license server connection
- Set up environment variables
Environment Configuration:
# Add to .bashrc or equivalent export FORTIFY_HOME=/opt/Fortify/Fortify_SCA_and_Apps_23.1.0 export PATH=$FORTIFY_HOME/bin:$PATH export JAVA_HOME=/usr/lib/jvm/java-11-openjdk export PATH=$JAVA_HOME/bin:$PATH # Fortify specific variables export FORTIFY_SCA_HOME=$FORTIFY_HOME export SCA_VM_OPTS="-Xmx4G -Xms2G"
2. Basic Scanning Workflow
Command Line Interface Basics:
# Basic scan sourceanalyzer -b myapp build.sh sourceanalyzer -b myapp -scan -f myapp.fpr # Maven project scan sourceanalyzer -b myapp mvn compile sourceanalyzer -b myapp -scan -f myapp.fpr # Gradle project scan sourceanalyzer -b myapp gradle build sourceanalyzer -b myapp -scan -f myapp.fpr
Maven Integration:
<!-- Fortify Maven Plugin -->
<plugin>
<groupId>com.fortify</groupId>
<artifactId>fortify-maven-plugin</artifactId>
<version>1.0</version>
<configuration>
<scaHome>/opt/Fortify/Fortify_SCA_and_Apps_23.1.0</scaHome>
<buildId>${project.artifactId}</buildId>
<maxHeap>4G</maxHeap>
</configuration>
</plugin>
# Maven commands mvn fortify:clean mvn fortify:translate mvn fortify:scan
3. Comprehensive Build Integration
Maven Full Configuration:
<profile>
<id>fortify-scan</id>
<build>
<plugins>
<plugin>
<groupId>com.fortify</groupId>
<artifactId>fortify-maven-plugin</artifactId>
<version>1.0</version>
<executions>
<execution>
<id>fortify-scan</id>
<phase>verify</phase>
<goals>
<goal>clean</goal>
<goal>translate</goal>
<goal>scan</goal>
</goals>
</execution>
</executions>
<configuration>
<scaHome>${env.FORTIFY_HOME}</scaHome>
<buildId>${project.artifactId}-${project.version}</buildId>
<maxHeap>8G</maxHeap>
<jvmOptions>
<jvmOption>-Xmx8G</jvmOption>
<jvmOption>-Xms2G</jvmOption>
</jvmOptions>
<source>1.8</source>
<failOnError>false</failOnError>
<verbose>true</verbose>
<debug>false</debug>
<excludeList>fortify-excludes.txt</excludeList>
<filterFile>fortify-filters.fdl</filterFile>
<rules>custom-rules.xml</rules>
</configuration>
</plugin>
</plugins>
</build>
</profile>
Gradle Integration:
// build.gradle
plugins {
id 'com.fortify.fortify-gradle-plugin' version '1.0'
}
fortify {
scaHome = System.getenv('FORTIFY_HOME')
buildId = "${project.name}-${project.version}"
maxHeap = '8G'
source = '1.8'
exclude = file('fortify-excludes.txt')
filterFile = file('fortify-filters.fdl')
}
task fortifyScan(dependsOn: 'fortifyScan') {
doLast {
println "Fortify scan completed for ${project.name}"
}
}
4. Advanced Scanning Configuration
Custom Build Configuration:
#!/bin/bash
# fortify-scan.sh
PROJECT_NAME="my-application"
SCAN_DIR="./src"
OUTPUT_FILE="${PROJECT_NAME}.fpr"
EXCLUDE_PATTERNS="**/test/**,**/generated/**,**/target/**"
MAX_HEAP="8G"
echo "Starting Fortify scan for ${PROJECT_NAME}"
# Clean previous scan
sourceanalyzer -b ${PROJECT_NAME} -clean
# Translate source code
sourceanalyzer -b ${PROJECT_NAME} \
-source 1.8 \
-cp "./target/classes:./target/dependency/*" \
-exclude "${EXCLUDE_PATTERNS}" \
${SCAN_DIR}
# Perform scan
sourceanalyzer -b ${PROJECT_NAME} \
-scan \
-f ${OUTPUT_FILE} \
-maxheap ${MAX_HEAP} \
-rules custom-rules.xml \
-filter fortify-filters.fdl \
-format fpr
echo "Scan completed: ${OUTPUT_FILE}"
Exclusion Configuration (fortify-excludes.txt):
# Exclude test directories **/test/** **/test/**/* **/test*/** **/*Test.java **/*Tests.java # Exclude generated code **/generated/** **/target/generated-sources/** # Exclude third-party libraries **/lib/** **/dependency/** # Exclude specific packages **/com/example/legacy/** **/com/example/experimental/** # File patterns to exclude *.min.js *.css *.properties *.xml
5. Custom Rule Development
Custom Rule Template:
<!-- custom-rules.xml --> <RulePack xmlns="xmlns://www.fortify.com/schema/rules"> <RulePackID>B5C0D3F0-9E2A-4D8C-9C7A-3F6E8B2A1C7D</RulePackID> <Name>MyCompany Custom Rules</Name> <Version>1.0</Version> <Description>Custom security rules for MyCompany applications</Description> <Rules> <Rule Definition="D8D7916A-4B6C-4A7D-9C3A-2E8F4B1A6D9C" Language="Java"> <Group>Custom Security</Group> <Category>Custom Vulnerability</Category> <Folder>MyCompany Rules</Folder> <Kingdom>Code Quality</Kingdom> <DefaultSeverity>3.0</DefaultSeverity> <Abstract>Custom sensitive data exposure</Abstract> <Description> Detects exposure of sensitive company-specific data patterns. </Description> <RuleBody> <Unified> <Context>method</Context> <Condition> <Or> <And> <Match>method-name:.*[Ss]ensitive.*</Match> <Match>code-pattern:.*[Pp]assword.*|[Ss]ecret.*|[Kk]ey.*</Match> </And> <Match>code-pattern:log\.(debug|info|warn|error)\(.*[Pp]assword.*\)</Match> </Or> </Condition> <Vulnerability>Custom.Sensitive.Data.Exposure</Vulnerability> <Recommendation> Avoid logging sensitive information. Use masking or avoid exposure. </Recommendation> <Tips> <Tip>Review logging statements for sensitive data exposure</Tip> </Tips> </Unified> </RuleBody> </Rule> <Rule Definition="A1B2C3D4-E5F6-4A7B-8C9D-0E1F2A3B4C5D" Language="Java"> <Group>API Security</Group> <Category>Insecure Deserialization</Category> <Folder>MyCompany Rules</Folder> <Kingdom>Input Validation and Representation</Kingdom> <DefaultSeverity>4.0</DefaultSeverity> <Abstract>Insecure Jackson deserialization</Abstract> <Description> Detects unsafe Jackson deserialization configurations. </Description> <RuleBody> <Unified> <Context>class</Context> <Condition> <And> <Match>type-name:.*[Dd]eserializer</Match> <Match>code-pattern:enableDefaultTyping|@JsonTypeInfo.*USE_CLASS</Match> </And> </Condition> <Vulnerability>API.Insecure.Deserialization</Vulnerability> <Recommendation> Avoid using enableDefaultTyping() in Jackson ObjectMapper. Use @JsonTypeInfo with JsonTypeInfo.Id.NAME instead. </Recommendation> <References> <Reference>https://cowtowncoder.medium.com/on-jackson-cves-dont-panic-here-is-what-you-need-to-know-54cd0d6e8062</Reference> </References> </Unified> </RuleBody> </Rule> </Rules> </RulePack>
Java-Specific Custom Rules:
<!-- java-custom-rules.xml --> <Rule Definition="CUSTOM_SQL_INJECTION" Language="Java"> <Group>Data Validation</Group> <Category>SQL Injection</Category> <Folder>Custom SQL Rules</Folder> <Kingdom>Input Validation and Representation</Kingdom> <DefaultSeverity>4.5</DefaultSeverity> <Abstract>Custom SQL injection detection</Abstract> <Description> Detects potential SQL injection with custom query builders. </Description> <RuleBody> <Unified> <Context>method-call</Context> <Condition> <And> <Match>function:java.sql.Statement.executeQuery</Match> <Or> <Match>code-pattern:".*" \+.*[Uu]ser.*[Ii]nput.*</Match> <Match>code-pattern:String\.format\(.*[Ss]elect.*,%s\)</Match> </Or> </And> </Condition> <Vulnerability>Custom.SQL.Injection</Vulnerability> <Recommendation> Use PreparedStatement with parameterized queries instead of string concatenation. </Recommendation> </Unified> </RuleBody> </Rule>
6. Filter Configuration
Filter File (fortify-filters.fdl):
# Filter out low priority issues Filter "MyCompany Security Filter" Issue_Type "*" Kingdom "Environment" Folder "Best Practice" Confidence "Low" Analysis "Not an Issue" EndFilter # Filter specific false positives Filter "Spring Framework False Positives" Issue_Type "Cross-Site Scripting: Persistent" Folder "Spring Framework" File_Path "*SpringFramework*" Analysis "Not an Issue" EndFilter # Filter test code issues Filter "Test Code Filter" Issue_Type "*" File_Path "*/test/*" Analysis "Not an Issue" EndFilter # Filter third-party library issues Filter "Third-Party Libraries" Issue_Type "*" File_Path "*/lib/*" Analysis "Not an Issue" EndFilter # Custom filter for business logic false positives Filter "Business Logic Exceptions" Issue_Type "Privacy Violation" File_Path "*UserService*" Analysis "Not an Issue" Comment "Business requirement to process user data" EndFilter
7. CI/CD Integration
Jenkins Pipeline:
// Jenkinsfile
pipeline {
agent any
environment {
FORTIFY_HOME = '/opt/Fortify/Fortify_SCA_and_Apps_23.1.0'
SSC_URL = 'https://ssc.company.com'
SSC_APP_ID = '12345'
SSC_APP_VERSION_ID = '67890'
}
stages {
stage('Build') {
steps {
sh 'mvn clean compile -DskipTests'
}
}
stage('Fortify Scan') {
steps {
script {
// Translate source code
sh """
${FORTIFY_HOME}/bin/sourceanalyzer \
-b ${env.JOB_BASE_NAME} \
-clean
${FORTIFY_HOME}/bin/sourceanalyzer \
-b ${env.JOB_BASE_NAME} \
-source 1.8 \
-cp "target/classes:target/dependency/*" \
-exclude "**/test/**,**/target/generated-sources/**" \
"src/main/java"
"""
// Perform scan
sh """
${FORTIFY_HOME}/bin/sourceanalyzer \
-b ${env.JOB_BASE_NAME} \
-scan \
-f ${env.JOB_BASE_NAME}.fpr \
-maxheap 8G \
-rules custom-rules.xml
"""
// Upload to Software Security Center
sh """
${FORTIFY_HOME}/bin/fortifyclient \
uploadFPR \
-file ${env.JOB_BASE_NAME}.fpr \
-url ${SSC_URL} \
-applicationId ${SSC_APP_ID} \
-applicationVersionId ${SSC_APP_VERSION_ID} \
-authtoken ${SSC_AUTH_TOKEN}
"""
}
}
}
stage('Security Gate') {
steps {
script {
// Check if scan passed security gates
sh """
${FORTIFY_HOME}/bin/fortifyclient \
getIssueCounts \
-url ${SSC_URL} \
-applicationVersionId ${SSC_APP_VERSION_ID} \
-authtoken ${SSC_AUTH_TOKEN} \
-output issue-counts.json
"""
// Parse results and fail if critical issues found
def issueCounts = readJSON file: 'issue-counts.json'
if (issueCounts.Critical > 0) {
error "Build failed: ${issueCounts.Critical} critical security issues found"
}
}
}
}
}
post {
always {
// Archive FPR file
archiveArtifacts artifacts: '*.fpr', fingerprint: true
// Publish Fortify results
fortifyPublisher failBuildOnSeverity: 'Critical'
}
}
}
GitHub Actions Workflow:
# .github/workflows/fortify-scan.yml
name: Fortify Security Scan
on:
push:
branches: [ main, develop ]
pull_request:
branches: [ main ]
jobs:
fortify-scan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up Java
uses: actions/setup-java@v3
with:
java-version: '11'
distribution: 'temurin'
- name: Cache Fortify dependencies
uses: actions/cache@v3
with:
path: ~/.fortify
key: ${{ runner.os }}-fortify-${{ hashFiles('**/pom.xml') }}
restore-keys: |
${{ runner.os }}-fortify-
- name: Download Fortify SCA
run: |
# Download Fortify SCA (requires enterprise setup)
curl -L -o fortify-scanner.zip "${{ secrets.FORTIFY_DOWNLOAD_URL }}"
unzip fortify-scanner.zip -d /opt/fortify
- name: Build project
run: mvn -B compile -DskipTests
- name: Run Fortify Scan
env:
FORTIFY_HOME: /opt/fortify
SSC_TOKEN: ${{ secrets.SSC_TOKEN }}
run: |
$FORTIFY_HOME/bin/sourceanalyzer -b ${{ github.event.repository.name }} -clean
$FORTIFY_HOME/bin/sourceanalyzer -b ${{ github.event.repository.name }} \
mvn compile -DskipTests
$FORTIFY_HOME/bin/sourceanalyzer -b ${{ github.event.repository.name }} \
-scan -f scan-results.fpr
# Upload to Software Security Center
$FORTIFY_HOME/bin/fortifyclient uploadFPR \
-file scan-results.fpr \
-url https://ssc.company.com \
-authtoken $SSC_TOKEN \
-applicationVersionId ${{ secrets.SSC_APP_VERSION_ID }}
- name: Check Security Gate
run: |
# Custom script to check security compliance
python scripts/check-security-gate.py scan-results.fpr
8. Remediation Guidance
Common Java Vulnerabilities and Fixes:
SQL Injection:
// VULNERABLE
@GetMapping("/users")
public List<User> getUsers(@RequestParam String name) {
String sql = "SELECT * FROM users WHERE name = '" + name + "'";
return jdbcTemplate.query(sql, userMapper);
}
// SECURE - Use PreparedStatement
@GetMapping("/users")
public List<User> getUsers(@RequestParam String name) {
String sql = "SELECT * FROM users WHERE name = ?";
return jdbcTemplate.query(sql, new Object[]{name}, userMapper);
}
// SECURE - Use JPA
@GetMapping("/users")
public List<User> getUsers(@RequestParam String name) {
return userRepository.findByName(name);
}
Cross-Site Scripting (XSS):
// VULNERABLE
@Controller
public class WelcomeController {
@GetMapping("/welcome")
public String welcome(@RequestParam String name, Model model) {
model.addAttribute("name", name); // Unsafe!
return "welcome";
}
}
// SECURE - Use HTML escaping
@Controller
public class WelcomeController {
@GetMapping("/welcome")
public String welcome(@RequestParam String name, Model model) {
model.addAttribute("name", StringEscapeUtils.escapeHtml4(name));
return "welcome";
}
}
// SECURE - Use Thymeleaf (auto-escaped)
@Controller
public class WelcomeController {
@GetMapping("/welcome")
public String welcome(@RequestParam String name, Model model) {
model.addAttribute("name", name); // Thymeleaf auto-escapes
return "welcome";
}
}
Insecure Deserialization:
// VULNERABLE
public Object deserialize(byte[] data) throws Exception {
ByteArrayInputStream bis = new ByteArrayInputStream(data);
ObjectInputStream ois = new ObjectInputStream(bis);
return ois.readObject(); // Dangerous!
}
// SECURE - Use safe deserialization
public Object deserializeSafe(byte[] data) throws Exception {
// Use validation and filtering
if (!isSafeToDeserialize(data)) {
throw new SecurityException("Untrusted data");
}
// Use look-ahead deserialization
try (ObjectInputStream ois = new SafeObjectInputStream(
new ByteArrayInputStream(data))) {
return ois.readObject();
}
}
// Jackson safe configuration
@Bean
public ObjectMapper objectMapper() {
return JsonMapper.builder()
.disable(JsonParser.Feature.AUTO_CLOSE_SOURCE)
.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)
.enable(DeserializationFeature.FAIL_ON_NULL_FOR_PRIMITIVES)
.build();
}
9. Reporting and Metrics
Custom Report Generation:
#!/bin/bash # generate-reports.sh FPR_FILE=$1 REPORT_DIR="./reports" # Generate HTML report ReportGenerator -format HTML -f "$REPORT_DIR/vulnerabilities.html" -source "$FPR_FILE" # Generate PDF report ReportGenerator -format PDF -f "$REPORT_DIR/vulnerabilities.pdf" -source "$FPR_FILE" # Generate CSV for further analysis ReportGenerator -format CSV -f "$REPORT_DIR/vulnerabilities.csv" -source "$FPR_FILE" # Generate BIRT report ReportGenerator -format BIRT -f "$REPORT_DIR/vulnerabilities.rptdesign" -source "$FPR_FILE" echo "Reports generated in $REPORT_DIR"
Metrics Collection Script:
#!/usr/bin/env python3
# fortify-metrics.py
import xml.etree.ElementTree as ET
import json
import csv
from datetime import datetime
def parse_fpr_metrics(fpr_file):
"""Extract metrics from FPR file"""
# Extract audit.xml from FPR (FPR is a zip file)
import zipfile
with zipfile.ZipFile(fpr_file, 'r') as zip_ref:
audit_data = zip_ref.read('audit.xml')
root = ET.fromstring(audit_data)
metrics = {
'timestamp': datetime.now().isoformat(),
'total_issues': 0,
'by_severity': {},
'by_category': {},
'by_kingdom': {},
'critical_issues': 0,
'high_issues': 0,
'medium_issues': 0,
'low_issues': 0
}
# Parse issues
for issue in root.findall('.//Issue'):
severity = issue.get('severity')
category = issue.get('category')
kingdom = issue.get('kingdom')
# Update counts
metrics['total_issues'] += 1
metrics['by_severity'][severity] = metrics['by_severity'].get(severity, 0) + 1
metrics['by_category'][category] = metrics['by_category'].get(category, 0) + 1
metrics['by_kingdom'][kingdom] = metrics['by_kingdom'].get(kingdom, 0) + 1
# Severity counts
if severity == '5.0' or severity == 'Critical':
metrics['critical_issues'] += 1
elif severity == '4.0' or severity == 'High':
metrics['high_issues'] += 1
elif severity == '3.0' or severity == 'Medium':
metrics['medium_issues'] += 1
else:
metrics['low_issues'] += 1
return metrics
def generate_metrics_report(metrics, output_file):
"""Generate metrics report in JSON format"""
with open(output_file, 'w') as f:
json.dump(metrics, f, indent=2)
if __name__ == "__main__":
fpr_file = "scan-results.fpr"
metrics = parse_fpr_metrics(fpr_file)
generate_metrics_report(metrics, "fortify-metrics.json")
10. Best Practices and Optimization
Performance Optimization:
# Optimized scan command sourceanalyzer -b myapp \ -Xmx8G \ -Xms4G \ -XX:+UseG1GC \ -Dcom.fortify.sca.Phase0HigherOrderFuncs=true \ -Dcom.fortify.sca.EnableParallelAnalysis=true \ -Dcom.fortify.sca.ThreadCount=4 \ -scan -f optimized-scan.fpr
Incremental Scanning:
#!/bin/bash # incremental-scan.sh PROJECT="myapp" LAST_SCAN="last_scan.timestamp" # Check if we need a full scan (if source changed significantly) if [ -f "$LAST_SCAN" ] && [ $(find src/ -name "*.java" -newer "$LAST_SCAN" | wc -l) -lt 50 ]; then echo "Performing incremental scan..." sourceanalyzer -b $PROJECT -incremental -scan -f incremental.fpr else echo "Performing full scan..." sourceanalyzer -b $PROJECT -clean sourceanalyzer -b $PROJECT -scan -f full.fpr fi # Update timestamp touch "$LAST_SCAN"
Conclusion
Fortify SCA provides comprehensive security analysis for Java applications:
- Deep Code Analysis: Identifies complex security vulnerabilities
- Customizable Rules: Extend with organization-specific security requirements
- CI/CD Integration: Automated security testing in development pipelines
- Comprehensive Reporting: Detailed vulnerability analysis and tracking
- Remediation Guidance: Actionable fixes for identified issues
Key Success Factors:
- Regular scanning throughout development lifecycle
- Custom rules for organization-specific requirements
- Developer education on secure coding practices
- Integration with issue tracking systems
- Continuous monitoring of security metrics
By implementing Fortify SCA effectively, organizations can significantly improve their application security posture and catch vulnerabilities early in the development process.