This custom check sample for Linux is designed to analyze your Avantra master server to ensure that the hardening configuration against CVE-20221-44228 has been applied correctly.


This custom can be imported into systems with RUN_JS functionality and require an agent to be present on the master server itself to function. 


Note that the variable at the top avantraInstallationLocation must be updated to match your install location.


// Name: CVE-2021-44228 - server check
// Type: Custom check
// Description: Check that your Avantra server is hardened against CVE-2021-44228 - Log4j 2 Vulnerability
// Author: Avantra Support
// Date: Tuesday Dec 14 2021
// ######### Variable and constant declarations
// ######### MUST BE UPDATED BY YOU - this is the folder where your Avantra installation is and must end with a / e.g. /opt/avantra/
const avantraInstallLocation = "/opt/avantra/";

const kb_article = "https://support.avantra.com/en/support/solutions/articles/44002291388-cve-2021-44228-log4j-2-vulnerability";
const log4j_disable = "-Dlog4j2.formatMsgNoLookups=true";

var server_version_full = null;
var server_version_major = null;
var ui_version_full = null;
var ui_version_major = null;

var number_of_errors = 0;
var number_of_warnings = 0;

var error_message_cve_not_patched = '- Configuration (ALERT): Avantra did not find the recommended hardening flags set to prevent CVE-2021-44228. Please read here for more information: ' + kb_article;
error_message_cve_not_patched = error_message_cve_not_patched + "\n\n- Running Service (UNKNOWN): Unable to check the running service - please fix the configuration first.";

var error_message_service_not_restarted_after_patch = '- Running Service (ALERT): While the configuration is in place to mitigate CVE-2021-44228 it appears to not have been applied to the running server. Please restart your Avantra services to complete the patch.';

const linux = "LINUX";
const windows = "WINDOWS";

const currentOs = os.osName.toUpperCase();

// ######### Functions

/**
 * Sets the text output message for the check result (visible in the Avantra UI)
 * @param  {string} output_message The mesage to be displayed as part of the check result.
 * @returns {string} The formatted notes.
 */
 function log(existing_notes, output_message, appendToStart = false) {

    // If we have no previous messages, we don't need a new line.
    var newline = (existing_notes.length > 0 ? '\n' : '');

    if (appendToStart) {
        existing_notes = output_message + newline + existing_notes;
    } else {
        existing_notes = existing_notes + newline + output_message;
    }
    return existing_notes;
}

/**
 * Sets up the summary output table.
 */
function buildOutputTableHeaders(){
    check.addTableColumn('Service');
    check.addTableColumn('Version');
    check.addTableColumn('Status');
}

/**
 * Add data to the output table.
 * @param {string} service - the name of the service
 * @param {string} version - the version of the service
 * @param {string} status - the status of the service
 */
 function addValuesToOutputTable(service, version, status){
    check.newRow();
    check.addCell(service);
    check.addCell(version);
    switch (status) {
        case OK:
            check.addCell("OK", OK);
            break;
        case WARNING:
            check.addCell("Warning", WARNING);
            break;
        case CRITICAL:
            check.addCell("CRITICAL", CRITICAL);
            break;
        default:
            check.addCell("Please Review", UNKNOWN);
            break;
    }
}

/**
 * Checks if a given version number is on the list of known vulnerable versions.
 * @param  {string} version_string The version string to be checked in the form major.minor.patch i.e. 20.11.2
 * @returns {boolean} Indicates if the given version is vulnerable.
 */
function avantraVersionIsVulnerable(version_string) {

    const vulnerableVersions = [
        "21.11.0",
        "20.11.9", "20.11.8", "20.11.7", "20.11.6", "20.11.5", "20.11.4", "20.11.3", "20.11.2", "20.11.1", "20.11.0",
        "20.5.6", "20.5.5", "20.5.4", "20.5.3", "20.5.2", "20.5.1", "20.5.0",
        "20.2.2", "20.2.1", "20.2.0",
    ];

    for (var j = 0; j < vulnerableVersions.length; j++) {
        if (vulnerableVersions[j].match(version_string)) return true;
    }
    return false;
}

/**
 * Get the current version of the Avantra server.
 * @returns {string} The current version of the Avantra service.
 */
function getAvantraServerVersion() {
    const avantra_status_command = avantraInstallLocation + "master/rc.master status";
    const avantra_version_regex = /(?:master[\s-])(\d+.\d+.\d+)(?:[’\s])/gi;
    var avantraVersion = null;
    try {
        var commandOutput = os.exec(avantra_status_command);
        if (commandOutput.exitCode === 0) {
            var result = avantra_version_regex.exec(commandOutput.out);
            avantraVersion = result[1];
        } else {
            avantraVersion = null;
        }
    } catch (error) {
        // Unable to execute the command.
        avantraVersion = null;
    }
    return avantraVersion;
}

/**
 * Get the current version of the Avantra UI (xangui).
 * @returns {string} The current version of the Avantra UI (xangui).
 */
function getAvantraUIVersion() {
    const avantra_status_command = avantraInstallLocation + "xangui/rc.xangui status";
    const avantra_version_regex = /(?:'xangui-)(\d+.\d+.\d+)(?:'\s)/g;
    var version = null;
    try {
        var commandOutput = os.exec(avantra_status_command);
        if (commandOutput.exitCode === 0) {
            var result = avantra_version_regex.exec(commandOutput.out);
            version = result[1];
        } else {
            version = null;
        }
    } catch (error) {
        // Unable to execute the command.
        version = null;
    }
    return version;
}

/**
 * Process the checks for the Avantra server
 */
function processServerChecks() {

    var componentResult = OK;
    var componentNotes = "";

    componentNotes = log(componentNotes, "================= Action required on Avantra server =====================\n");
    componentNotes = log(componentNotes, "Analysis of your Avantra server (" + server_version_full + ")");

    if (server_version_major === "20.2" || server_version_major === "20.5" || server_version_major === "7.3" || server_version_major === "7.2") {
        // This cannot be fixed with a configuration change and so refer to the KB article on next steps.

        var error_version_too_low = 'Avantra Server Version (ALERT):\n- Your Avantra version (' + server_version_full + ') may be vulnerable to CVE-2021-44228 and we are unable to detect whether you have mitigated this risk using this custom check owing to the older version of software.';
        error_version_too_low = error_version_too_low + "\n- Please read here for more information: " + kb_article;

        componentNotes = log(componentNotes, error_version_too_low);
        componentResult = check.statusIfWorse = CRITICAL;
        number_of_errors++;
    } else {

        const linux_configFileLocation = avantraInstallLocation + "master/" + "cfg/jvm.options";
        const linux_serviceStatusCommand = avantraInstallLocation + "master/" + "rc.master status";

        // Get the contents of the current configuration file
        var currentConfig = os.exec("cat " + linux_configFileLocation);

        // If we have managed to open the file then we can continue - otherwise we error out.
        if (currentConfig.exitCode === 0) {

            // Pull the file contents into a variable.
            var fileContents = currentConfig.out;

            // Search for the log4j2 disabling string
            // This way, if it's already present we can stop execution.
            var stringLocation = fileContents.indexOf(log4j_disable);

            // If the configuration is not already in place, we mark this check as critical.
            if (stringLocation === -1) {
                componentNotes = log(componentNotes, error_message_cve_not_patched);
                number_of_errors++;
                componentResult = check.statusIfWorse = CRITICAL;

                // If the config is already in place then we continue to check if the running service is using that config.
            } else {
                componentNotes = log(componentNotes, "- Configuration (OK)");

                // Now we verify that this configuration is loaded i.e. the service was restarted after applying the config
                var runningConfig = os.exec(linux_serviceStatusCommand);

                if (runningConfig.exitCode === 0) {

                    // Pull the command output into a variable.
                    var commandOutput = runningConfig.out;

                    // Search for the log4j2 disabling string
                    // This way, if it's already present know the running server is protected.
                    var stringLocation = commandOutput.indexOf(log4j_disable);

                    // If the configuration is not already in place, we mark this check as critical.
                    if (stringLocation === -1) {
                        componentNotes = log(componentNotes, error_message_service_not_restarted_after_patch + "\n");
                        number_of_errors++;
                        componentResult = check.statusIfWorse = CRITICAL;

                        // If the config is in place in the running service then everything is ok.
                    } else {
                        componentNotes = log(componentNotes, "- Running Service (OK)");
                        componentResult = check.statusIfWorse = OK;
                    }
                } else {
                    componentNotes = log(componentNotes, "- Running Service (WARNING): We were unable to verify if the running server is already using the required configuration. Please verify that the server was restarted after the configuration was applied." + "\n", true);
                    number_of_warnings++;
                    componentResult = check.statusIfWorse = WARNING;
                }
            }
        }

    }
    if(componentResult != OK){
        print(componentNotes);
    }
        
    addValuesToOutputTable("server", server_version_full, componentResult);
}

function processUIChecks(){

    var componentResult = OK;
    var componentNotes = "";

    componentNotes = log(componentNotes, "\n================= Action required on Avantra UI (xangui) =====================\n");
    componentNotes = log(componentNotes, "Analysis of your Avantra UI (xangui) (" + server_version_full + ")");
    
    if (ui_version_major === "20.2" || ui_version_major === "20.5" || ui_version_major === "7.3" || ui_version_major === "7.2") {
        // This cannot be fixed with a configuration change and so refer to the KB article on next steps.

        var error_version_too_low = 'Avantra UI Version (ALERT):\n- Your Avantra UI (xangui) version (' + ui_version_full + ') may be vulnerable to CVE-2021-44228 and we are unable to detect whether you have mitigated this risk using this custom check owing to the older version of software.';
        error_version_too_low = error_version_too_low + "\n- Please read here for more information: " + kb_article;

        componentNotes = log(componentNotes, error_version_too_low);
        componentResult = check.statusIfWorse = CRITICAL;
        number_of_errors++;
    } else {

        const linux_configFileLocation = avantraInstallLocation + "xangui/" + "cfg/jvm.options";
        const linux_serviceStatusCommand = avantraInstallLocation + "xangui/" + "rc.xangui status";

        // Get the contents of the current configuration file
        var currentConfig = os.exec("cat " + linux_configFileLocation);

        // If we have managed to open the file then we can continue - otherwise we error out.
        if (currentConfig.exitCode === 0) {

            // Pull the file contents into a variable.
            var fileContents = currentConfig.out;

            // Search for the log4j2 disabling string
            // This way, if it's already present we can stop execution.
            var stringLocation = fileContents.indexOf(log4j_disable);

            // If the configuration is not already in place, we mark this check as critical.
            if (stringLocation === -1) {
                componentNotes = log(componentNotes, error_message_cve_not_patched);
                number_of_errors++;
                componentResult = check.statusIfWorse = CRITICAL;

                // If the config is already in place then we continue to check if the running service is using that config.
            } else {
                componentNotes = log(componentNotes, "componentNotes, - Configuration (OK)");

                // Now we verify that this configuration is loaded i.e. the service was restarted after applying the config
                var runningConfig = os.exec(linux_serviceStatusCommand);

                if (runningConfig.exitCode === 0) {

                    // Pull the command output into a variable.
                    var commandOutput = runningConfig.out;

                    // Search for the log4j2 disabling string
                    // This way, if it's already present know the running server is protected.
                    var stringLocation = commandOutput.indexOf(log4j_disable);

                    // If the configuration is not already in place, we mark this check as critical.
                    if (stringLocation === -1) {
                        componentNotes = log(componentNotes, error_message_service_not_restarted_after_patch + "\n");
                        number_of_errors++;
                        componentResult = check.statusIfWorse = CRITICAL;

                        // If the config is in place in the running service then everything is ok.
                    } else {
                        componentNotes = log(componentNotes, "componentNotes, - Running Service (OK)");
                        componentResult = check.statusIfWorse = OK;
                    }
                } else {
                    componentNotes = log(componentNotes, "componentNotes, - Running Service (WARNING): We were unable to verify if the running server is already using the required configuration. Please verify that the server was restarted after the configuration was applied." + "\n", true);
                    number_of_warnings++;
                    componentResult = check.statusIfWorse = WARNING;
                }
            }
        }

    }
    if(componentResult != OK){
        print(componentNotes);
    }
        
    addValuesToOutputTable("ui (xangui)", server_version_full, componentResult);
}

if(currentOs.indexOf(linux) > -1){
    buildOutputTableHeaders();

    server_version_full = getAvantraServerVersion();
    if(server_version_full === null){
        check.message = check.message + "\nAvantra Server:\n- Unable to get information about the installed Avantra server at the given location.\n- Is this directory right and contains the 'master' and 'xangui' folders? " + avantraInstallLocation + "\n";
        number_of_errors++;
        check.statusIfWorse = CRITICAL;
    } else {
        const server_majorVersion_regex = /(\d+.\d+)/g;
        var server_major_version_result = server_majorVersion_regex.exec(server_version_full);
        server_version_major = server_major_version_result[1];
        processServerChecks();
    }

    ui_version_full = getAvantraUIVersion();
    if(ui_version_full === null){
        check.message = check.message + "\nAvantra UI (xangui):\n- Unable to get information about the installed Avantra UI (xangui) at the given location.\n- Is this directory right and contains the 'master' and 'xangui' folders? " + avantraInstallLocation + "\n";
        number_of_errors++;
        check.statusIfWorse = CRITICAL;
    } else {
        const ui_majorVersion_regex = /(\d+.\d+)/g;
        var ui_major_version_result = ui_majorVersion_regex.exec(ui_version_full);
        ui_version_major = ui_major_version_result[1];
        processUIChecks();
    }


    if(number_of_warnings === 0 && number_of_errors === 0){
        check.message = "All OK\n" + check.message;
    } else if(number_of_warnings > 0 && number_of_errors === 0){
        check.message = "Warnings occurred and should be reviewed\n" + check.message;
    } else {
        check.message = "Summary\n- Errors: " + number_of_errors + "\n- Warnings: " + number_of_warnings + "\n" + check.message;
    }
} else if(currentOs.indexOf(windows) > -1){
    check.message = "This check is designed to run only on Linux based operating systems. Please see this KB article for more information:\n" + kb_article;
    check.status = UNKNOWN;
}