Skip to content
Snippets Groups Projects

SAST Vulnerability Checker

  • Clone with SSH
  • Clone with HTTPS
  • Embed
  • Share
    The snippet can be accessed without any authentication.
    Authored by Zacharia Mansouri

    https://cylab.be/blog/181/secure-your-project-with-the-gitlab-sast-analyzers

    This checker is supposed to be used in a GitLab pipeline to retrieve a JSON report provided by a SAST analyzer job from the previous stage and go through each of the vulnerabilities the analyzer found.

    All the detected vulnerabilities that have been explained with the @SAST-BYPASS[justification_text] tag (written as a commentary) will not trigger the failure of the job running this PHP script. Any presence of unexplained vulnerabilities will trigger it.

    Here is the color system applied to the logs of the job running this script:

    • orange: explained vulnerability
    • green: vulnerability explanation
    • red: unexplained vulnerability

    Usage:

    php sast-vuln-checker.php <JSON REPORT FILENAME>

    Example of a simple .gitlab-ci.yml file using this script in its pipeline:

    stages:
      - test
      - sast-vuln
    
    phpcs-security-audit-sast:
      image: registry.gitlab.com/security-products/sast/phpcs-security-audit:2
      stage: test
      artifacts:
        paths:
          - gl-sast-report.json
      script:
        - /analyzer run
    
    sast-vuln-phpcs-security-audit-sast:
      image: cylab/php74
      stage: sast-vuln
      needs:
        - job: phpcs-security-audit-sast
      script:
        - php sast-vuln-checker.php gl-sast-report.json
    Edited
    sast-vuln-checker.php 3.21 KiB
    <?php
    
    /**
     * sast-vuln-checker.php
     * 
     * Check if the SAST analyzer found vulnerabilities, using the content of its report.
     */
    
    
    if ($argc != 2) {
        echo "Usage: " . $argv[0] . " <path/to/report.json>\n";
        exit(-1);
    }
    
    $toParse = file_get_contents($argv[1]); // @SAST-BYPASS[$argv[1] is defined in .gitlab-ci.yml, this is not a user input]
    if ($toParse === false) {
        echo "Failed to open the report file!\n";
        exit(-1);
    }
    
    $parsed = json_decode($toParse, true);
    echo "Decoding report file: ";
    switch (json_last_error()) {
        case JSON_ERROR_NONE:
            echo "No errors\n";
        break;
        case JSON_ERROR_DEPTH:
            echo "Maximum stack depth exceeded\n";
        break;
        case JSON_ERROR_STATE_MISMATCH:
            echo "Underflow or the modes mismatch\n";
        break;
        case JSON_ERROR_CTRL_CHAR:
            echo "Unexpected control character found\n";
        break;
        case JSON_ERROR_SYNTAX:
            echo "Syntax error, malformed JSON\n";
        break;
        case JSON_ERROR_UTF8:
            echo "Malformed UTF-8 characters, possibly incorrectly encoded\n";
        break;
        default:
            echo "Unknown error\n";
        break;
    }
    if ($parsed === null) {
        echo "Failed to decode the report file to JSON!\n";
        exit(-1);
    }
    
    if (count($parsed['vulnerabilities']) > 0) {
        $nbPassingVulns = 0;
        $k = 0;
        foreach ($parsed['vulnerabilities'] as $vuln) {
            $k++;
            echo "+" . str_repeat("-", 2 + strlen((string)($k))) . "+\n| " . $k . " |\n" . "+" . str_repeat("-", 2 + strlen((string)($k))) . "+\n";
            $file = file($vuln['location']['file']); // @SAST-BYPASS[the vulnerabilities reports are supposed to be trusted]
            $startLine = intval($vuln['location']['start_line']) - 1;
            $endLine = $startLine;
            if (array_key_exists('end_line', $vuln['location'])) {
                $endLine = intval($vuln['location']['end_line']) - 1;
            }
            $isJustified = false;
            for ($i = $startLine; $i <= $endLine; $i++) {
                $line = $file[$i];
                if (strpos($line, '@SAST-BYPASS') !== false) {
                    $pattern = '/@SAST-BYPASS\[(.*?)\]/';
                    $matches = [];
                    preg_match($pattern, $line, $matches);
                    $printedVuln = print_r($vuln, true);
                    echo "\e[38;5;215m" . $printedVuln . "\e[0m";
                    echo "\e[38;5;82mVulnerability ignored because:\n> " . $matches[1] . "\e[0m\n";
                    $nbPassingVulns++;
                    $isJustified = true;
                    break;
                }
            }
            if (! $isJustified) {
                $printedVuln = print_r($vuln, true);
                echo "\e[38;5;196m" . $printedVuln . "\e[0m";
            }
        }
        echo "\n+---------+\n| Summary |\n+---------+\n";
        if ($nbPassingVulns > 0) {
            echo "\e[38;5;215m" . $nbPassingVulns . " found vulnerabilities due to security checker misinterpretation.\e[0m\n";
        }
        $failed = false;
        if (count($parsed['vulnerabilities']) > $nbPassingVulns) {
            echo "\e[38;5;196m" . (count($parsed['vulnerabilities']) - $nbPassingVulns) . " vulnerabilities found in the " . $argv[1] . " report.\e[0m\n";
            $failed = true;
        }
        echo "Download the full report from the pipeline artifacts for more details.\n";
        if ($failed) {
            exit(-1);
        }
    
    }
    0% Loading or .
    You are about to add 0 people to the discussion. Proceed with caution.
    Finish editing this message first!
    Please register or to comment