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.