add_Sources), * Changed save_Target to save the short_sw_string as the os_string instead * - Apr 10, 2017 - More db_helper conversions and bug fixes * - May 13, 2017 - Always lots of changes here * Added get_Checklist_By_Filename method * Converted get_Category_Checklists function to use db_helper class * Converted save_SV_Rule method to use db_helper class * Converted over a few other methods to use db_helper class * - May 19, 2017 - Converted over more methods to use db_helper class * Fixed typo, added deleting category interview data when deleting category * - May 22, 2017 - Simplified get_ScanData function to not retrieve all data for target host lists (took too much time) * Converted save_IA_Controls method to use db_helper class * - May 25, 2017 - Added 'start' flag to db_helper::flags method to start at a different record (supporting paging in DataTables library) * Converted over a couple methods to use db_helper class * - Jun 27, 2017 - Set default target classification if not present, fixed bug #264 * - Jul 13, 2017 - Revised several method to use db_helper class and removed db::add_PDI method and moved to just using save_PDI method * - Jul 21, 2017 - Cleaned up port saving in save_Interface method * - Jul 24, 2017 - Added save_EMASS_CCIs() and get_EMASS_CCIs() to create and retrieve the rmf.emass_cci table * - Aug 28, 2017 - Fixed typo w/cve_reference table #276 * - Aug 28, 2017 - Added delete_Target_Software, fixed error when deleting all software or checklists (wouldn't save correctly), and cleaned up delete_Target method * - Oct 26, 2017 - Formatting and bug fix for #326 * - Oct 27, 2017 - Change filtering for get_ports, added delete_Interface method * - Dec 27, 2017 - Migrate methods to use db_helper and syntax updates * - Jan 1, 2018 - Added get_Software_Id method that gets all the software IDs from a list of CPEs * - Jan 10, 2018 - Added a couple functions and formatting * - Jan 15, 2018 - Fixed bug in get_Category_Findings * - Jan 16, 2018 - Added include for host_list.inc, updated to use host_list class, fixed bug in delete_Scan method Moved scan deletion here * - Jan 20, 2018 - Fixed typo in save_STE method * - May 24, 2018 - Added defaulting where clause operator to '=' * - May 26, 2018 - Updated autocategorization to removed any extranious spaces before or after the string * - May 31, 2018 - Changes to support renaming sagacity.pdi_catalog.check_content field and scan error detection * - Jun 2, 2018 - Formatting and added set_Setting_Array method * - Jun 5, 2018 - Changed set_Setting_Array method to use SQL update instead of replace * - Sep 5, 2018 - Fix for #8 * - Nov 3, 2018 - Fix for fix #62, commented out last INSERT in post_Processing, jao * - Nov 8, 2018 - Added a couple methods to support changes for #25 */ include_once 'base.inc'; include_once 'software.inc'; include_once 'checklist.inc'; include_once 'ste.inc'; include_once 'target.inc'; include_once 'scan.inc'; include_once 'pdi_catalog.inc'; include_once 'port.inc'; include_once 'sites.inc'; include_once 'sources.inc'; include_once 'ste_cat.inc'; include_once 'system.inc'; include_once 'interfaces.inc'; include_once 'script.inc'; include_once 'stigs.inc'; include_once 'golddisk.inc'; include_once 'finding.inc'; include_once 'ia_control.inc'; include_once 'retina.inc'; include_once 'sv_rule.inc'; include_once 'nessus.inc'; include_once 'echecklist.inc'; include_once 'cve.inc'; include_once 'advisories.inc'; include_once 'iavm.inc'; include_once 'oval.inc'; include_once 'cce.inc'; include_once 'proc_ia_controls.inc'; include_once 'error.inc'; include_once 'question.inc'; include_once 'cci.inc'; include_once 'rmf_control.inc'; include_once 'cpe.inc'; include_once 'nasl.inc'; include_once 'uuid.inc'; include_once 'host_list.inc'; // @TODO - Make sure all save functions accept a class object or array of that class that is being saved. Otherwise, only primative types will be passed in. /** * Constant to decide if the database queries will run automatically after creating them * * @var boolean */ define('AUTORUN', false); /** * Global to represent an IN statement (e.g. * WHERE field IN (1,2)) * * @var int */ define('IN', 1); /** * Global to represent a NOT IN statement (e.g. * WHERE field NOT IN (1,2)) * * @var int */ define('NOT_IN', 64); /** * Global to represent a BETWEEN statement (e.g. * WHERE field BETWEEN 1 and 2) * * @var int */ define('BETWEEN', 2); /** * Global to represent a LIKE statement (e.g. * WHERE field LIKE '%value%') * * @var int */ define('LIKE', 4); /** * Global to represent an IS NOT statement (e.g. * WHERE field IS NOT NULL) * * @var int */ define('IS_NOT', 8); /** * Global to represent an IS statement (e.g. * WHERE field IS NULL) * * @var int */ define('IS', 16); /** * Global to represent an NOT LIKE statement (e.g. * WHERE field NOT LIKE '%value%' * * @var int */ define('NOT_LIKE', 32); /** * Class to help the database * * @author Ryan Prather */ class db_helper { const SELECT = 1; const SELECT_COUNT = 2; const CREATE_TABLE = 3; const DROP = 4; const DELETE = 5; const INSERT = 6; const REPLACE = 7; const UPDATE = 8; const EXTENDED_INSERT = 9; const EXTENDED_REPLACE = 10; const EXTENDED_UPDATE = 11; const ALTER_TABLE = 12; const TRUNCATE = 13; /** * The mysqli connection * * @var mysqli */ public $c; /** * To store the SQL statement * * @var string */ public $sql = null; /** * A string to store the type of query that is being run * * @var string */ public $query_type = null; /** * The result of the query * * @var mixed */ private $result = null; /** * Constructor * * @param mysqli $dbh * [by ref] * mysqli object to perform queries. */ public function __construct(&$dbh) { if (! is_null($dbh) && is_a($dbh, "mysqli")) { $this->c = $dbh; } else { throw (new Exception("Could not create database helper class", E_ERROR)); } $this->c->real_query("SET time_zone='+00:00'"); $this->c->real_query("SET sql_mode=''"); } /** * Function to execute the statement * * @param mixed $return * [optional] * MYSQLI constant to control what is returned from the mysqli_result object * @param string $sql * [optional] * Optional SQL query * * @return mixed */ public function execute($return = MYSQLI_ASSOC, $sql = null) { if (! is_null($sql)) { $this->sql = $sql; } if (is_a($this->c, 'mysqli')) { if (! $this->c->ping()) { $this->c = null; $this->c = new mysqli(DB_SERVER, 'web', db::decrypt_pwd(), 'sagacity'); } } else { throw (new Exception('Database was not connected', E_ERROR)); } try { if (in_array($this->query_type, [ self::SELECT, self::SELECT_COUNT ])) { $this->result = $this->c->query($this->sql); if ($this->c->error) { $this->debug(E_ERROR); } } elseif ($this->query_type == self::DELETE) { $this->c->real_query($this->sql); if ($this->c->error) { return 0; } } else { $this->c->real_query($this->sql); if ($this->c->error) { $this->debug(E_ERROR, $this->c->error); } } $this->result = $this->check_results($return); } catch (Exception $e) { die($e->getTraceAsString()); } return $this->result; } /** * Function to check the results and return what is expected * * @param mixed $return_type * [optional] * Optional return mysqli_result return type * * @return mixed */ function check_results($return_type = MYSQLI_ASSOC) { $res = null; if ($this->c->error) { $this->debug(E_ERROR); } elseif (LOG_LEVEL == E_DEBUG) { $this->debug(E_DEBUG); } switch ($this->query_type) { case self::SELECT_COUNT: if (! is_a($this->result, 'mysqli_result')) { $this->debug(E_ERROR); } if ($this->result->num_rows == 1) { $res = $this->result->fetch_assoc()['count']; } elseif ($this->result->num_rows > 1) { $res = $this->result->num_rows; } mysqli_free_result($this->result); return $res; case self::SELECT: if (! is_a($this->result, 'mysqli_result')) { $this->debug(E_ERROR); } if ($this->result->num_rows == 1) { $res = $this->result->fetch_array($return_type); } elseif ($this->result->num_rows > 1) { $res = $this->fetch_all($return_type); } mysqli_free_result($this->result); return $res; case self::INSERT: if ($this->c->error) { $this->debug(E_ERROR); return 0; } if ($this->c->insert_id) { return $this->c->insert_id; } elseif ($this->c->affected_rows) { return $this->c->affected_rows; } return 1; case self::EXTENDED_INSERT: case self::EXTENDED_REPLACE: case self::EXTENDED_UPDATE: case self::REPLACE: case self::UPDATE: case self::DELETE: case self::ALTER_TABLE: if ($this->c->error && $this->c->errno == 1060) { return ($this->c->affected_rows ? $this->c->affected_rows : true); } elseif ($this->c->error) { $this->debug(E_ERROR); return false; } elseif ($this->c->affected_rows) { return $this->c->affected_rows; } else { return true; } break; case self::CREATE_TABLE: case self::DROP: case self::TRUNCATE: return true; } } /** * Function to pass through calling the query function (used for backwards compatibility and for more complex queries that aren't currently supported) * * @param string $sql * [optional] * Optional query to pass in and execute * * @return mysqli_result */ public function query($sql = null) { if (is_null($sql)) { return $this->c->query($this->sql); } else { return $this->c->query($sql); } } /** * A function to build a select query * * @param string $table_name * The table to query * @param array $fields * [optional] * Optional array of fields to return (defaults to '*') * @param array $where * [optional] * Optional 2-dimensional array to build where clause from * @param array $flags * [optional] * Optional 2-dimensional array to allow other flags * * @see db_helper::where() * @see db_helper::flags() * * @return mixed */ public function select($table_name, $fields = null, $where = null, $flags = null) { $this->sql = null; $this->query_type = self::SELECT; if (! is_null($table_name) && is_string($table_name)) { $this->sql = "SELECT " . $this->fields($fields) . " FROM $table_name"; } else { return null; } if (isset($flags['table_joins']) && is_array($flags['table_joins'])) { $this->sql .= " " . implode(" ", $flags['table_joins']); } if (! is_null($where) && is_array($where) && count($where)) { $this->sql .= $this->where($where); } if (! is_null($flags) && is_array($flags) && count($flags)) { $this->sql .= $this->flags($flags); } if (AUTORUN) { return $this->execute(MYSQLI_BOTH); } return $this->sql; } /** * Function to build a query to check the number of rows in a table * * @param string $table_name * The table to query * @param array $where * [optional] * Optional 2-dimensional array to build where clause * @param array $flags * [optional] * Optional 2-dimensional array to add flags * * @see db_helper::where() * @see db_helper::flags() * * @return string|NULL */ public function select_count($table_name, $where = null, $flags = null) { $this->sql = null; $this->query_type = self::SELECT_COUNT; if (! is_null($table_name) && is_string($table_name)) { $this->sql = "SELECT COUNT(1) AS 'count' FROM $table_name"; } else { return null; } if (isset($flags['table_joins']) && is_array($flags['table_joins'])) { $this->sql .= " " . implode(" ", $flags['table_joins']); } if (! is_null($where) && is_array($where) && count($where)) { $this->sql .= $this->where($where); } if (! is_null($flags) && is_array($flags) && count($flags)) { $this->sql .= $this->flags($flags); } if (AUTORUN) { return $this->execute(MYSQLI_BOTH); } return $this->sql; } /** * Function to build an insert query statement * * @param string $table_name * Table name to query * @param array $params * Name/value pair to insert into the table * @param boolean $to_ignore * [optional] * Optional boolean to decide if the "IGNORE" will be added * * @return string|NULL */ public function insert($table_name, $params, $to_ignore = false) { $this->sql = null; $this->query_type = self::INSERT; if (! is_null($table_name) && is_string($table_name)) { $this->sql = "INSERT " . ($to_ignore ? "IGNORE " : "") . "INTO $table_name " . "(`" . implode("`,`", array_keys($params)) . "`)"; } $this->sql .= " VALUES (" . implode(",", array_map([ $this, '_escape' ], array_values($params))) . ")"; if (AUTORUN) { return $this->execute(MYSQLI_BOTH); } return $this->sql; } /** * Function to create an extended insert query statement * * @param string $table_name * The table name that the data is going to be inserted on * @param array $fields * An array of field names that each value represents * @param array $params * An array of array of values * @param boolean $to_ignore * [optional] * Boolean to decide if we need to use the INSERT IGNORE INTO syntax * * @return NULL|string Returns the SQL if AUTORUN is set to false, else it returns the output from running. */ public function extended_insert($table_name, $fields, $params, $to_ignore = false) { $this->sql = null; $this->query_type = self::EXTENDED_INSERT; if (! is_null($table_name) && is_string($table_name)) { $this->sql = "INSERT " . ($to_ignore ? "IGNORE " : "") . "INTO $table_name " . "(`" . implode("`,`", $fields) . "`)"; } else { throw (new Exception("Missing table name in extended_insert", E_ERROR)); } if (is_array($params) && count($params)) { $this->sql .= " VALUES "; if (isset($params[0]) && is_array($params[0])) { foreach ($params as $p) { $this->sql .= "(" . implode(",", array_map([ $this, '_escape' ], array_values($p))) . "),"; } } else { if (count($params) != count($fields)) { throw (new Exception("Inconsistent number of fields in fields and values")); } $this->sql .= "(" . implode("),(", array_map([ $this, '_escape' ], array_values($params))) . "),"; } } else { throw new \InvalidArgumentException("Expected array parameters"); } $this->sql = substr($this->sql, 0, - 1); if (AUTORUN) { return $this->execute(MYSQLI_BOTH); } return $this->sql; } /** * Build a statement to update a table * * @param string $table_name * The table name to update * @param array $params * Name/value pairs of the field name and value * @param array $where * [optional] * Two-dimensional array to create where clause * @param array $flags * [optional] * Two-dimensional array to create other flag options (table_joins, order, and group) * * @see db_helper::where() * @see db_helper::flags() * * @return NULL|string */ public function update($table_name, $params, $where = null, $flags = null) { $this->sql = "UPDATE "; $this->query_type = self::UPDATE; if (! is_null($table_name) && is_string($table_name)) { $this->sql .= $table_name; if (isset($flags['table_joins'])) { $this->sql .= " " . implode(" ", $flags['table_joins']); unset($flags['table_joins']); } $this->sql .= " SET "; } foreach ($params as $f => $p) { if ((strpos($f, "`") === false) && (strpos($f, ".") === false) && (strpos($f, "*") === false) && (stripos($f, " as ") === false)) { $f = "`{$f}`"; } if (! is_null($p)) { $this->sql .= "$f={$this->_escape($p)},"; } else { $this->sql .= "$f=NULL,"; } } $this->sql = substr($this->sql, 0, - 1); if (! is_null($where) && is_array($where) && count($where)) { $this->sql .= $this->where($where); } if (! is_null($flags) && is_array($flags) && count($flags)) { $this->sql .= $this->flags($flags); } if (AUTORUN) { return $this->execute(MYSQLI_BOTH); } return $this->sql; } /** * Function to offer an extended updated functionality by using two different tables. * * @param string $to_be_updated * The table that you want to update (alias 'tbu' is automatically added) * @param string $original * The table with the data you want to overwrite to_be_updated table (alias 'o' is automatically added) * @param string $using * The common index value between them that will join the fields * @param array|string $params * If string only a single field is updated (tbu.$params = o.$params) * If array each element in the array is a field to be updated (tbu.$param = o.$param) * * @return mixed */ public function extended_update($to_be_updated, $original, $using, $params) { $this->sql = "UPDATE "; $this->query_type = self::EXTENDED_UPDATE; if (! is_null($to_be_updated) && ! is_null($original) && ! is_null($using)) { $this->sql .= "$to_be_updated tbu INNER JOIN $original o USING ($using) SET "; } if (is_array($params) && count($params)) { foreach ($params as $param) { $this->sql .= "tbu.$param = o.$param,"; } $this->sql = substr($this->sql, 0, - 1); } elseif (is_string($params)) { $this->sql .= "tbu.$params = o.$params"; } else { throw (new Exception("Do not understand datatype of \$params", E_ERROR)); } if (AUTORUN) { return $this->execute(MYSQL_BOTH); } return $this->sql; } /** * Function to build a replace query * * @param string $table_name * The table to update * @param array $params * Name/value pair to insert * * @return NULL|string */ public function replace($table_name, $params) { $this->sql = null; $this->query_type = self::REPLACE; if (! is_null($table_name) && is_string($table_name)) { $this->sql = "REPLACE INTO $table_name " . "(`" . implode("`,`", array_keys($params)) . "`)"; } $this->sql .= " VALUES (" . implode(",", array_map(array( $this, '_escape' ), array_values($params))) . ")"; if (AUTORUN) { return $this->execute(MYSQLI_BOTH); } return $this->sql; } /** * Function to build an extended replace statement * * @param string $table_name * Table name to update * @param array $fields * Array of fields * @param array $params * Two-dimensional array of values * * @return NULL|string */ public function extended_replace($table_name, $fields, $params) { $this->sql = null; $this->query_type = self::EXTENDED_REPLACE; if (! is_null($table_name) && is_string($table_name)) { $this->sql = "REPLACE INTO $table_name " . "(`" . implode("`,`", $fields) . "`)"; } else { return null; } if (is_array($params) && count($params)) { $this->sql .= " VALUES "; foreach ($params as $p) { $this->sql .= "(" . implode(",", array_map(array( $this, '_escape' ), array_values($p))) . "),"; } } $this->sql = substr($this->sql, 0, - 1); if (AUTORUN) { return $this->execute(MYSQLI_BOTH); } return $this->sql; } /** * Function to build a delete statement * * @param string $table_name * Table name to act on * @param array $fields * [optional] * Optional list of fields to delete (used when including multiple tables) * @param array $where * [optional] * Optional 2-dimensional array to build where clause from * @param array $table_joins * [optional] * Optional 2-dimensional array to add other flags * * @see db_helper::where() * @see db_helper::flags() * * @return string|NULL */ public function delete($table_name, $fields = null, $where = null, $table_joins = null) { $this->sql = "DELETE"; $this->query_type = self::DELETE; if (! is_null($fields) && is_array($fields)) { $this->sql .= " " . implode(",", $fields); } if (! is_null($table_name) && is_string($table_name)) { $this->sql .= " FROM $table_name"; } else { throw (new Exception("Failed to create delete query, no table name")); } if (! is_null($table_joins) && is_array($table_joins) && count($table_joins)) { $this->sql .= " " . implode(" ", $table_joins); } if (! is_null($where) && is_array($where) && count($where)) { $this->sql .= $this->where($where); } if (AUTORUN) { return $this->execute(MYSQLI_BOTH); } return $this->sql; } /** * Function to build a drop table statement (automatically executes) * * @param string $schema * Schema the table resides in * @param string $table_name * Table to drop * @param boolean $is_tmp * [optional] * Optional boolean if this is a temporary table * * @return string|NULL */ public function drop($schema, $table_name, $is_tmp = false) { $this->sql = null; $this->query_type = self::DROP; if (! is_null($table_name) && is_string($table_name)) { $this->sql = "DROP " . ($is_tmp ? "TEMPORARY " : "") . "TABLE IF EXISTS `$schema`.`$table_name`"; } return $this->execute(MYSQLI_BOTH); } /** * Function to build a truncate table statement (automatically executes) * * @param string $table_name * Table to truncate * * @return string|NULL */ public function truncate($table_name) { $this->sql = null; $this->query_type = self::TRUNCATE; if (! is_null($table_name) && is_string($table_name)) { $this->sql = "TRUNCATE TABLE $table_name"; } return $this->execute(MYSQLI_BOTH); } /** * Function to build a create temporary table statement * * @param string $table_name * Name to give the table when creating * @param boolean $is_tmp * [optional] * Optional boolean to make the table a temporary table * @param mixed $select * [optional] * Optional parameter if null uses last built statement * If string, will be made the SQL statement executed to create the table * If array, 2-dimensional array with "field", "datatype" values to build table fields * * @return NULL|string */ public function create_table($table_name, $is_tmp = false, $select = null) { $this->query_type = self::CREATE_TABLE; if (is_null($select) && ! is_null($this->sql) && substr($this->sql, 0, 6) == 'SELECT') { $this->sql = "CREATE " . ($is_tmp ? "TEMPORARY" : "") . " TABLE IF NOT EXISTS $table_name AS ($this->sql)"; } if (! is_null($table_name) && is_string($table_name) && is_string($select)) { $this->sql = "CREATE " . ($is_tmp ? "TEMPORARY" : "") . " TABLE IF NOT EXISTS $table_name AS ($select)"; } elseif (! is_null($table_name) && is_string($table_name) && is_array($select)) { $this->sql = "CREATE " . ($is_tmp ? "TEMPORARY" : "") . " TABLE IF NOT EXISTS $table_name ("; foreach ($select as $field) { $this->sql .= "{$field['field']} {$field['datatype']}" . (isset($field['default']) ? " {$field['default']}" : '') . (isset($field['option']) ? " {$field['option']}" : '') . ","; } $this->sql = substr($this->sql, 0, - 1) . ")"; } if (AUTORUN) { return $this->execute(); } return $this->sql; } /** * Function to create a table using a stdClass object derived from JSON * * @param stdClass $json */ public function create_table_json($json) { $this->query_type = self::CREATE_TABLE; $this->c->select_db($json->schema); $this->sql = "CREATE TABLE IF NOT EXISTS `{$json->name}` ("; foreach ($json->fields as $field) { $this->sql .= "`{$field->name}` {$field->dataType}"; if ($field->dataType == 'enum') { $this->sql .= "('" . implode("','", $field->values) . "')"; } if ($field->ai) { $this->sql .= " AUTO_INCREMENT"; } if ($field->nn) { $this->sql .= " NOT NULL"; } else { if ($field->default === null) { $this->sql .= " DEFAULT NULL"; } elseif (strlen($field->default)) { $this->sql .= " DEFAULT '{$field->default}'"; } } if ($field != end($json->fields)) { $this->sql .= ","; } } if (isset($json->index) && is_array($json->index) && count($json->index)) { foreach ($json->index as $ind) { $this->sql .= ", " . strtoupper($ind->type) . " `{$ind->id}` (`{$ind->ref}`)"; } } if (isset($json->constraints) && is_array($json->constraints) && count($json->constraints)) { foreach ($json->constraints as $con) { $this->sql .= ", CONSTRAINT `{$con->id}` " . "FOREIGN KEY (`{$con->local}`) " . "REFERENCES `{$con->schema}`.`{$con->table}` (`{$con->field}`) " . "ON DELETE " . (!isset($con->delete) || is_null($con->delete) ? "NO ACTION" : strtoupper($con->delete)) . " " . "ON UPDATE " . (!isset($con->update) || is_null($con->update) ? "NO ACTION" : strtoupper($con->update)); } } if (isset($json->unique) && is_array($json->unique) && count($json->unique)) { $this->sql .= ", UNIQUE(`" . implode("`,`", $json->unique) . "`)"; } if (isset($json->primary_key) && is_array($json->primary_key) && count($json->primary_key)) { $this->sql .= ", PRIMARY KEY(`" . implode("`,`", $json->primary_key) . "`))"; } else { $this->sql = substr($this->sql, 0, - 1) . ")"; } $this->execute(); } /** * Function to alter a existing table * * @param string $table_name * Table to alter * @param string $action * What action should be taken ('add-column', 'drop-column', 'modify-column') * @param array $params * [optional] * Optional 2-dimensional array of parameters to act on. $action will dictate what parameters need to be present * * @return mixed */ public function alter_table($table_name, $action, $params) { $this->query_type = self::ALTER_TABLE; $this->sql = "ALTER TABLE $table_name "; if ($action == 'add-column') { $nn = ($params->nn ? " NOT NULL" : ""); $default = null; if ($params->default === null) { $default = " DEFAULT NULL"; } elseif (strlen($params->default)) { $default = " DEFAULT '{$params->default}'"; } $this->sql .= "ADD COLUMN {$params->name} {$params->dataType}" . $nn . $default; } elseif ($action == 'drop-column') { $this->sql .= "DROP COLUMN "; foreach ($params as $col) { $this->sql .= "{$col['name']},"; } $this->sql = substr($this->sql, 0, - 1); } elseif ($action == 'modify-column') {} $this->debug(E_DEBUG); return $this->execute(); } /** * Check to see if a field in a table exists * * @param string $schema * Schema that contains tables * @param string $table_name * Table to check * @param string $field_name * Field name to find * * @return boolean Returns TRUE if field is found in that schema and table, otherwise FALSE */ public function field_exists($schema, $table_name, $field_name) { $this->c->select_db($schema); $fdata = $this->field_data($schema, $table_name); foreach ($fdata as $field) { if ($field->name == $field_name) { return true; } } return false; } /** * Function to get the column data (datatype, flags, defaults, etc) * * @param string $schema * Schema to search for table in * @param string $table_name * Table to query * @param mixed $field * [optional] * Optional field to retrieve data (if null, returns data from all fields) * * @return array */ public function field_data($schema, $table_name, $field = null) { $this->c->select_db($schema); if (is_null($field)) { $res = $this->c->query("SELECT * FROM $table_name LIMIT 1"); } elseif (is_array($field)) { $res = $this->c->query("SELECT `" . implode("`,`", $field) . "` FROM $table_name LIMIT 1"); } elseif (is_string($field)) { $res = $this->c->query("SELECT $field FROM $table_name LIMIT 1"); } else { return null; } $fields = null; if (is_a($res, 'mysqli_result')) { $fields = $res->fetch_fields(); foreach ($fields as $i => $f) { $fields["{$f->name}"] = $f; unset($fields[$i]); } } return $fields; } /** * Function to check that all field parameters are set correctly * * @param object $field_data * @param object $check * @param object $pks * @param object $index * * @return array */ public function field_check($field_data, $check, $pks, $index) { $default = null; $ret = null; $nn = ($check->nn ? " NOT NULL" : null); if ($check->default === null) { $default = " DEFAULT NULL"; } elseif (strlen($check->default)) { $default = " DEFAULT '{$check->default}'"; } if ($field_data->type != $check->type && $check->type != MYSQLI_TYPE_ENUM) { $this->debug("{$field_data->name} wrong datatype, changing to {$check->dataType}"); $ret = " CHANGE COLUMN `{$field_data->name}` `{$check->name}` {$check->dataType}" . "{$nn}{$default}"; } elseif (! is_null($check->length) && $field_data->length != $check->length) { $this->debug("{$field_data->name} incorrect size ({$field_data->length} != {$check->length})"); $ret = " CHANGE COLUMN `{$field_data->name}` `{$check->name}` {$check->dataType}" . "{$nn}{$default}"; } elseif ($check->type == MYSQLI_TYPE_ENUM && ! ($field_data->flags & MYSQLI_ENUM_FLAG)) { $ret = " CHANGE COLUMN `{$field_data->name}` `{$check->name}` {$check->dataType}('" . implode("','", $check->values) . "')" . "{$nn}{$default}"; } if (! is_null($index) && is_array($index) && count($index)) { foreach ($index as $ind) { if ($check->name == $ind->ref && ! ($field_data->flags & MYSQLI_MULTIPLE_KEY_FLAG)) { $this->debug("{$field_data->name} is not an index"); $ret .= ($ret ? "," : "") . " ADD INDEX `{$ind->id}` (`{$ind->ref}` ASC)"; } } } if (in_array($check->name, $pks) && ! ($field_data->flags & MYSQLI_PRI_KEY_FLAG)) { $ret .= ($ret ? "," : "") . " DROP PRIMARY KEY, ADD PRIMARY KEY(`" . implode("`,`", $pks) . "`)"; } return $ret; } /** * Function to check for the existence of a table within a schema * * @param string $schema * Schema to search for table * @param string $table_name * Table to search for * * @return boolean Returns TRUE if table is found in that schema, otherwise FALSE */ public function table_exists($schema, $table_name) { $this->c->select_db($schema); $res = $this->c->query("SHOW TABLES LIKE '$table_name'"); if ($res->num_rows > 0) { return true; } return false; } /** * Function to detect if string is a JSON object or not * * @param string $val * * @return boolean */ public function isJson($val) { json_decode($val); return (json_last_error() == JSON_ERROR_NONE); } /** * Function to escape SQL characters to prevent SQL injection * * @param mixed $val * Value to escape * * @return string Escaped value */ public function _escape($val) { if (is_null($val)) { return 'NULL'; } elseif (is_numeric($val) || is_string($val)) { if ($this->isJson($val)) { return "'{$this->c->real_escape_string($val)}'"; } elseif (strtolower($val) == 'now()') { return $val; } elseif (preg_match("/\.`\w+`/", $val)) { return $val; } return "'{$this->c->real_escape_string($val)}'"; } elseif (is_a($val, 'DateTime')) { return "'{$val->format(MYSQL_DT_FORMAT)}'"; } elseif (is_bool($val)) { return $val ? "'1'" : "'0'"; } elseif (gettype($val) == 'object') { $this->debug(E_ERROR, "Unknown object to escape " . get_class($val) . " in SQL string {$this->sql}"); } else { $this->debug(E_ERROR, "Unknown datatype to escape in SQL string {$this->sql} " . gettype($val)); } throw (new Exception("Unknown datatype to escape in SQL string {$this->sql} " . gettype($val), E_ERROR)); } /** * Function to retrieve all results * * @param string $resulttype * * @return mixed */ public function fetch_all($resulttype = MYSQLI_ASSOC) { $res = []; if (method_exists('mysqli_result', 'fetch_all')) { // Compatibility layer with PHP < 5.3 $res = $this->result->fetch_all($resulttype); } else { while ($tmp = $this->result->fetch_array($resulttype)) { $res[] = $tmp; } } return $res; } /** * Function to debug the class * * @param int $errno * @param string $errmsg */ public function debug($errno = E_NOTICE, $errmsg = null) { check_path(LOG_PATH . "/db.log"); check_path(LOG_PATH . "/db.err"); check_path(LOG_PATH . "/db.debug"); $err_lvl = 'NOTICE'; switch ($errno) { case E_DEBUG: $err_lvl = 'DEBUG'; break; case E_WARNING: $err_lvl = 'WARNING'; break; case E_ERROR: $err_lvl = 'FATAL ERROR'; break; } $dt = new DateTime(); if (is_null($errmsg)) { $errmsg = $this->sql; } file_put_contents(realpath(LOG_PATH . '/db.log'), "{$dt->format(DATE_ISO8601)}\t" . "$err_lvl\t" . "Executing: $this->query_type\t" . "SQL: {$errmsg}" . PHP_EOL, FILE_APPEND); if ($errno == E_DEBUG && $this->result && LOG_LEVEL == E_DEBUG && is_a($this->result, 'mysqli_result')) { file_put_contents(realpath(LOG_PATH . '/db.debug'), print_r($this->result, true), FILE_APPEND); } elseif ($errno == E_ERROR && $this->c->error) { file_put_contents(realpath(LOG_PATH . '/db.err'), "{$dt->format(DATE_ISO8601)}\t" . "{$this->c->error}" . PHP_EOL, FILE_APPEND); error_log($this->c->error); die($this->c->error); } } /** * Function to populate the fields for the SQL * * @param array $fields * [optional] * Optional array of fields to string together to create a field list * * @return string */ public function fields($fields = null) { $str_fields = null; if (is_array($fields) && count($fields)) { foreach ($fields as $field) { if ((strpos($field, '`') === false) && (strpos($field, '.') === false) && (strpos($field, '*') === false) && (stripos($field, ' as ') === false)) { $str_fields .= "`$field`,"; } else { $str_fields .= "$field,"; } } $str_fields = substr($str_fields, 0, - 1); } elseif (is_null($fields)) { $str_fields = "*"; } return $str_fields; } /** * Function to create the where statement for the SQL * * @param array $where * Two-dimensional array to use to build the where clause * * * array(
*   array(
*     'field' => 'field_name',
*     'op' => '=', // (common operations or IN, BETWEEN, LIKE, NOT_LIKE, IS, & IS_NOT constants)
*     'value' => 'field_value',
*     'sql_op' => 'AND', // NOT required for first element (common SQL operators AND, OR, NOR)
*     'open-paren' => true, // optional to add a paren '(' BEFORE clause
*     'close-paren' => true, // optional to add a paren ')' AFTER clause
*     'low' => '1', // LOW value only used in BETWEEN clause
*     'high' => '100', // HIGH value only used in BETWEEN clause
*     'case_insensitive' => true // optional boolean to set the parameters to LOWER to do case insenstive comparison *   ),
*   array(
*     ...
*   ),
*   ...
* ) *
* * @return string */ public function where($where) { $ret = " WHERE"; foreach ($where as $x => $w) { if (! isset($w['field']) && isset($w['close-paren']) && $w['close-paren']) { $ret .= ")"; continue; } elseif (! isset($w['field']) || ($x > 0 && ! isset($w['sql_op']))) { continue; } if ($x > 0) { $ret .= " {$w['sql_op']}"; } if (isset($w['open-paren']) && $w['open-paren']) { $ret .= " ("; } if ((strpos($w['field'], '`') === false) && (strpos($w['field'], '.') === false) && (strpos($w['field'], '*') === false) && (stripos($w['field'], ' as ') === false)) { $field = "`{$w['field']}`"; } else { $field = $w['field']; } $not = null; if (isset($w['op']) && in_array($w['op'], array( IS_NOT, NOT_LIKE, NOT_IN ))) { $not = ' NOT'; } if (isset($w['op']) && ($w['op'] == LIKE || $w['op'] == NOT_LIKE)) { $ret .= " {$field}{$not} LIKE {$w['value']}"; } elseif (isset($w['op']) && ($w['op'] == IN || $w['op'] == NOT_IN) && is_string($w['value'])) { $ret .= " {$field}{$not} IN " . (strpos($w['value'], '(') !== false ? $w['value'] : "({$w['value']})"); } elseif (isset($w['op']) && ($w['op'] == IN || $w['op'] == NOT_IN) && is_array($w['value'])) { $ret .= " {$field}{$not} IN (" . implode(",", array_map(array( $this, '_escape' ), $w['value'])) . ")"; } elseif (isset($w['op']) && $w['op'] == BETWEEN) { if (! isset($w['low']) && ! isset($w['high'])) { continue; } $ret .= " {$field} BETWEEN {$this->_escape($w['low'])} AND {$this->_escape($w['high'])}"; } elseif (isset($w['op']) && ($w['op'] == IS || $w['op'] == IS_NOT)) { $ret .= " {$field} IS{$not} {$this->_escape($w['value'])}"; } else { $op = "="; if (isset($w['op'])) { $op = $w['op']; } if (isset($w['case_insensitive']) && $w['case_insensitive']) { $ret .= " LOWER({$field}) {$op} LOWER({$this->_escape($w['value'])})"; } elseif (preg_match("/\(SELECT/", $w['value'])) { $ret .= " {$field} {$op} {$w['value']}"; } else { $ret .= " {$field} {$op} {$this->_escape($w['value'])}"; } } if (isset($w['close-paren']) && $w['close-paren']) { $ret .= ")"; } } if (strlen($ret) == 7) { $ret = ''; } return $ret; } /** * Function to parse the flags * * @param array $flags * Two-dimensional array to added flags * * * array( *   'table_joins' => array( *     "JOIN table2 t2 ON t2.id=t1.id" *   ), *   'group' => 'field', *   'having' => 'field', *   'order' => 'field', *   'start' => 0, *   'limit' => 0 * ) * * * @see db_helper::groups() * @see db_helper::having() * @see db_helper::order() * * @return string */ public function flags($flags) { $ret = ''; if (isset($flags['group'])) { $ret .= $this->groups($flags['group']); } if (isset($flags['having']) && is_array($flags['having'])) { $ret .= $this->having($flags['having']); } if (isset($flags['order'])) { $ret .= $this->order($flags['order']); } if (isset($flags['limit']) && (is_string($flags['limit']) || is_numeric($flags['limit']))) { $ret .= " LIMIT "; if (isset($flags['start']) && (is_string($flags['start']) || is_numeric($flags['start']))) { $ret .= "{$flags['start']},"; } $ret .= "{$flags['limit']}"; } return $ret; } /** * Function to parse SQL GROUP BY statements * * @param mixed $groups * * @return string */ private function groups($groups) { $ret = ''; if (is_array($groups)) { $ret .= " GROUP BY"; foreach ($groups as $grp) { $ret .= " $grp"; } } elseif (is_string($groups)) { $ret .= " GROUP BY {$groups}"; } return $ret; } /** * Function to parse SQL HAVING statements * * @param mixed $having * return string */ private function having($having) { $ret = " HAVING"; $x = 0; foreach ($having as $h) { if (! isset($h['field']) || ($x > 0 && ! isset($h['sql_op']))) { continue; } if ($x > 0) { $ret .= " {$h['sql_op']} "; } if ($h['op'] == LIKE) { $ret .= " {$h['field']} LIKE {$h['value']}"; } elseif ($h['op'] == IN && is_string($h['value'])) { $ret .= " {$h['field']} IN {$h['value']}"; } elseif ($h['op'] == IN && is_array($h['value'])) { $ret .= " {$h['field']} IN ('" . implode("', '", $h['value']) . "')"; } elseif ($h['op'] == BETWEEN) { $ret .= " {$h['field']} BETWEEN {$this->_escape($h['low'])} AND {$this->_escape($h['high'])}"; } elseif ($h['op'] == IS) { $ret .= " {$h['field']} IS {$this->_escape($h['value'])}"; } elseif ($h['op'] == IS_NOT) { $ret .= " {$h['field']} IS NOT {$this->_escape($h['value'])}"; } else { $ret .= " {$h['field']} {$h['op']} {$this->_escape($h['value'])}"; } $x ++; } return $ret; } /** * Function to parse SQL ORDER BY statements * * @param mixed $order * * @return string */ private function order($order) { $ret = ''; if (is_array($order)) { $ret .= " ORDER BY"; foreach ($order as $ord) { $ret .= " {$ord['field']} {$ord['sort']},"; } $ret = substr($ret, 0, - 1); } elseif (is_string($order)) { $ret .= " ORDER BY {$order}"; } return $ret; } /** * * @return boolean */ public static function run() { $args = func_get_args(); $conn = null; if (is_array($args) && count($args) < 2) { return; } if (! is_a($args[0], "mysqli")) { return; } else { $conn = $args[0]; } if (! is_string($args[1])) { return; } else { $sql = $args[1]; } if (strpos($sql, "?") !== false) { $x = 2; while (strpos($sql, "?") !== false) { $sql = str_replace_first("?", "'" . $conn->real_escape_string($args[$x]) . "'", $sql); } } if (! $stmt = $conn->prepare($sql)) { print $conn->error . PHP_EOL; return; } if (! $stmt->execute()) { print "Execution of prepared statement failed: (" . $stmt->errno . ") " . $stmt->error . PHP_EOL; return false; } return true; } /** * Function * * @return void|mixed */ public static function selectrow_array() { $args = func_get_args(); $conn = null; $ret = []; if (is_array($args) && count($args) < 2) { return; } if (! is_a($args[0], "mysqli")) { return; } else { $conn = $args[0]; } if (! is_string($args[1])) { return; } else { $sql = $args[1]; } if (strpos($sql, "?") !== false) { $x = 2; while (strpos($sql, "?") !== false) { $sql = str_replace_first("?", "'" . $conn->real_escape_string($args[$x]) . "'", $sql); } } if (! $stmt = $conn->prepare($sql)) { print $conn->error . PHP_EOL; return; } if (! $stmt->execute()) { print "Execution of prepared statement failed: (" . $stmt->errno . ") " . $stmt->error . PHP_EOL; return; } $meta = $stmt->result_metadata(); $fields = $fieldNames = []; while ($field = $meta->fetch_field()) { $fieldNames[] = $var = $field->name; $$var = null; $fields[$var] = &$$var; } $fieldCount = (is_array($fieldNames) ? count($fieldNames) : 0); call_user_func_array(array( $stmt, "bind_result" ), $fields); $i = 0; while ($stmt->fetch()) { for ($r = 0; $r < $fieldCount; $r ++) { $ret[$i][$fieldNames[$r]] = $fields[$fieldNames[$r]]; } } if (is_array($ret) && count($ret) == 1) { return $ret[0]; } else { return $ret; } } /** * * @return string */ public static function mysql_escape_string() { $args = func_get_args(); $conn = null; $sql = ''; if (is_array($args) && count($args) < 3) { return; } if (! is_a($args[0], "mysqli")) { return; } else { $conn = $args[0]; } if (! is_string($args[1])) { return; } else { $sql = $args[1]; } if (strpos($sql, "?") !== false) { $x = 2; while (strpos($sql, "?") !== false) { $sql = str_replace_first("?", "'" . $conn->real_escape_string($args[$x]) . "'", $sql); } } return $sql; } public function is_constraint($con_id) { $res = $this->c->query("SELECT * FROM information_schema.TABLE_CONSTRAINTS WHERE CONSTRAINT_NAME = '$con_id'"); if ($res->num_rows) { return true; } return false; } } /** * This file will contain all the variables and functions to operate a database * and for the ST&E Manager to interact with * * @author Ryan Prather */ class db { /** * Array of words to be removed */ private $DISALLOWED = array( 'the', 'be', 'to', 'of', 'and', 'a', 'in', 'that', 'have', 'I', 'it', 'for', 'not', 'on', 'with', 'he', 'as', 'you', 'do', 'at', 'this', 'but', 'his', 'by', 'from', 'they', 'we', 'say', 'her', 'she', 'or', 'an', 'will', 'my', 'one', 'all', 'would', 'there', 'their', 'what', 'so', 'up', 'out', 'if', 'about', 'who', 'get', 'which', 'go', 'me', 'when', 'make', 'can', 'like', 'time', 'no', 'just', 'him', 'know', 'take', 'people', 'into', 'year', 'your', 'good', 'some', 'could', 'them', 'see', 'other', 'than', 'then', 'now', 'look', 'only', 'come', 'its', 'over', 'think', 'also', 'back', 'after', 'use', 'two', 'how', 'our', 'work', 'first', 'well', 'way', 'even', 'new', 'want', 'because', 'any', 'these', 'give', 'day', 'most', 'us' ); /** * The database connection * * @var mysqli * @access protected */ protected $conn = null; /** * A private database helper * * @var db_helper * @access public */ public $help = null; /** * Constant for the first column where target finding statuses start * * @var integer */ const FIRST_ECHECKLIST_HOST_COL = 5; /** * Constructor function to instantiate a new DB object and connection * * @param bool $persistent * [optional] */ public function __construct($persistent = false) { // attempt to create a new database connection $host = ($persistent ? "p:" : "") . DB_SERVER; if (class_exists('mysqli')) { $pwd = self::decrypt_pwd(); $this->conn = new mysqli($host, 'web', $pwd, 'sagacity'); } else { die("Could not find the mysqli class"); } // if there is a problem output that if ($this->conn->connect_errno && $this->conn->connect_errno == 1045) { die("Invalid database username and/or password"); } elseif ($this->conn->connect_errno) { error_log("Error connecting to " . DB_SERVER . " " . $this->conn->connect_error); die("Error connecting to " . DB_SERVER); } // set the character set and default database $this->conn->set_charset("utf8"); $this->conn->real_query("SET sql_mode=''"); $this->help = new db_helper($this->conn); } /** * Function to decrypt the password * * @return string */ public static function decrypt_pwd() { if (! file_exists(DOC_ROOT . "/" . PWD_FILE)) { die("Cannot connect to the database because the password file does not exist"); } $enc_pwd = file_get_contents(DOC_ROOT . "/" . PWD_FILE); $pwd = my_decrypt($enc_pwd); return $pwd; } /** * Get the ID of the last command that was executed * * @return integer The integer of the last primary key id inserted into whatever table */ public function get_Last_Insert_ID() { return $this->conn->insert_id; } // {{{ ADVISORY CLASS FUNCTIONS /** * Function to get an advisory from the database * * @param string $advisory_id * [optional] * String with advisory ID to specifically find * * @return array:advisory|NULL Returns array of advisory objects or NULL if nothing is found in the database */ public function get_Advisory($advisory_id = null) { $ret = []; if (! is_null($advisory_id)) { $this->help->select("sagacity.advisories", null, array( array( 'field' => 'advisory_id', 'op' => '=', 'value' => $advisory_id ) )); } else { $this->help->select("sagacity.advisories", null, []); } if ($ref = $this->help->execute()) { foreach ($ref as $row) { $ret[] = new advisory($row['pdi_id'], $row['advisory_id'], $row['reference'], $row['type'], $row['url']); } return $ret; } else { Sagacity_Error::sql_handler($this->help->sql); $this->help->debug(E_ERROR); } return null; } /** * Update or insert an advisory * * @param array:advisory $advisories * Array of advisory class objects to save/update to database * * @return boolean Returns TRUE if successful, otherwise FALSE */ public function save_Advisory($advisories) { $values = []; $fields = [ 'pdi_id', 'advisory_id', 'type', 'reference', 'url', 'title', 'impact' ]; foreach ($advisories as $adv) { $values[] = [ $adv->get_PDI_ID(), $adv->get_Advisory_ID(), $adv->get_Type(), $adv->get_Ref_Text(), $adv->get_URL(), $adv->get_Title(), $adv->get_Impact() ]; } $this->help->extended_replace("sagacity.advisories", $fields, $values); if (! $this->help->execute()) { Sagacity_Error::sql_handler($this->help->sql); $this->help->debug(E_ERROR); return false; } return true; } // }}} // {{{ CATEGORY CLASS FUNCTIONS /** * Get ST&E category data * * @param integer $int_Cat_ID * [optional] * Grab specific ste_cat from database (default NULL) * * @return array:ste_cat|NULL Returns an array of categories that are applicable to the specific ST&E or a specifically requested category */ public function get_Category($int_Cat_ID = null) { $where = []; $ret = []; if ($int_Cat_ID != null) { $where[] = [ 'field' => 'id', 'op' => '=', 'value' => $int_Cat_ID ]; } $this->help->select("ste_cat", null, $where); $cats = $this->help->execute(); if (is_array($cats) && count($cats) && isset($cats['id'])) { $cats = [ 0 => $cats ]; } if (is_array($cats) && count($cats)) { foreach ($cats as $cat) { $tmp = new ste_cat($cat['id'], $cat['ste_id'], $cat['name'], $cat['analysts']); $this->help->select("ste_cat_sources", [ 'src_id' ], [ [ 'field' => 'cat_id', 'op' => '=', 'value' => $cat['id'] ] ]); $srcs = $this->help->execute(); if (is_array($srcs) && count($srcs) && isset($srcs['src_id'])) { $srcs = [ 0 => $srcs ]; } if (is_array($srcs) && count($srcs)) { foreach ($srcs as $src) { $tmp->add_Source($this->get_Sources($src['src_id'])); } } $this->get_Cat_Count($tmp); $ret[] = $tmp; } } return $ret; } /** * Function to automatically put targets in categories by operating systems
* Skips generic OS's and targets that already assigned * * @param int $ste_id */ public function auto_Catorgize_Targets($ste_id) { $this->help->select("sagacity.target t", [ 't.id', 't.os_string' ], [ [ 'field' => 't.ste_id', 'value' => $ste_id ], [ 'field' => 't.cat_id', 'op' => IS, 'value' => null, 'sql_op' => 'AND' ], [ 'field' => 's.cpe', 'op' => '!=', 'value' => 'cpe:/o:generic:generic:-', 'sql_op' => 'AND' ] ], [ 'table_joins' => [ 'JOIN sagacity.software s ON t.os_id=s.id' ] ]); $rows = $this->help->execute(); if (is_array($rows) && count($rows) && isset($rows['id'])) { $rows = [ 0 => $rows ]; } if (is_array($rows) && count($rows) && isset($rows[0])) { foreach ($rows as $row) { $id = 0; $this->help->select("sagacity.ste_cat", [ 'id' ], [ [ 'field' => 'ste_id', 'value' => $ste_id ], [ 'field' => 'name', 'value' => trim($row['os_string']), 'sql_op' => 'AND' ] ]); $tmp = $this->help->execute(); if (is_array($tmp) && count($tmp) && isset($tmp['id'])) { $id = $tmp['id']; } else { $this->help->insert("sagacity.ste_cat", [ 'ste_id' => $ste_id, 'name' => trim($row['os_string']) ], true); $id = $this->help->execute(); } if ($id) { $this->help->update("sagacity.target", [ 'cat_id' => $id ], [ [ 'field' => 'id', 'value' => $row['id'] ] ]); $this->help->execute(); } } } } /** * Function to save categories * * @param ste_cat $ste_cat_in * * @return mixed Returns FALSE if failed, otherwise the ID of the newly inserted category */ public function save_Category($ste_cat_in) { if (is_null($ste_cat_in->get_ID())) { $this->help->insert("sagacity.ste_cat", array( 'ste_id' => $ste_cat_in->get_STE_ID(), 'name' => $ste_cat_in->get_Name(), 'analysts' => $ste_cat_in->get_Analyst() )); if (! ($cat_id = $this->help->execute())) { $this->help->debug(E_ERROR); return false; } $ste_cat_in->set_ID($cat_id); } else { $this->help->update("sagacity.ste_cat", array( 'name' => $ste_cat_in->get_Name(), 'analysts' => $ste_cat_in->get_Analyst() ), array( array( 'field' => 'id', 'op' => '=', 'value' => $ste_cat_in->get_ID() ) )); if (! $this->help->execute()) { $this->help->debug(E_ERROR); return false; } } if (is_array($ste_cat_in->get_Sources()) && count($ste_cat_in->get_Sources())) { $this->help->delete("ste_cat_sources", null, [ [ 'field' => 'cat_id', 'op' => '=', 'value' => $ste_cat_in->get_ID() ] ]); $this->help->execute(); $srcs = []; foreach ($ste_cat_in->get_Sources() as $src) { $srcs[] = [ $ste_cat_in->get_ID(), $src->get_ID() ]; } $this->help->extended_insert("ste_cat_sources", [ 'cat_id', 'src_id' ], $srcs); $this->help->execute(); } return $ste_cat_in->get_ID(); } /** * This function renames a category * * @param integer $intOldCat * Category ID of the category to rename * @param string $strNewCatName * New name for the category * * @return boolean Return TRUE if successful, otherwise FALSE */ public function rename_Cat($intOldCat, $strNewCatName) { $this->help->update("sagacity.ste_cat", array( 'name' => $strNewCatName ), array( array( 'field' => 'id', 'op' => '=', 'value' => $intOldCat ) )); if (! $this->help->execute()) { $this->help->debug(E_ERROR); return false; } return true; } /** * This function deletes a category and assigns the targets to "Unassigned" * * @param integer $intCat * ID of the category to delete * * @return boolean Return TRUE if successful, otherwise FALSE */ public function delete_Cat($intCat) { $this->help->update("sagacity.target", array( 'cat_id' => null ), array( array( 'field' => 'cat_id', 'op' => '=', 'value' => $intCat ) )); if (! $this->help->execute()) { $this->help->debug(E_ERROR); return false; } $this->help->delete("sagacity.ste_cat_sources", null, array( array( 'field' => 'cat_id', 'op' => '=', 'value' => $intCat ) )); if (! $this->help->execute()) { $this->help->debug(E_ERROR); return false; } $this->help->delete("sagacity.category_interview", null, array( array( 'field' => 'cat_id', 'op' => '=', 'value' => $intCat ) )); if (! $this->help->execute()) { $this->help->debug(E_ERROR); return false; } $this->help->delete("sagacity.ste_cat", null, array( array( 'field' => 'id', 'op' => '=', 'value' => $intCat ) )); if (! $this->help->execute()) { $this->help->debug(E_ERROR); return false; } return true; } /** * This function sets the analyst that is in charge of this category * * @param integer $intCat * Category ID to update * @param string $strAnalyst * Name of the analyst * * @return boolean Return TRUE if successful, otherwise FALSE */ public function assign_Analyst_To_Category($intCat, $strAnalyst) { $analysts = strtolower($strAnalyst) == 'none' ? null : $strAnalyst; $this->help->update("sagacity.ste_cat", array( 'analysts' => $analysts ), array( array( 'field' => 'id', 'op' => '=', 'value' => $intCat ) )); if (! $this->help->execute()) { $this->help->debug(E_ERROR); return false; } return true; } /** * This function does the move of a tgt to a new category * * @param array:integer $arrTgts * Array of integer ID for each target to move * @param integer $intCat * Category ID to reassign them to * * @return boolean Return TRUE if successful, otherwise FALSE */ public function move_Tgt_To_Cat($arrTgts, $intCat) { $this->help->update("sagacity.target", array( 'cat_id' => $intCat ), array( array( 'field' => 'id', 'op' => IN, 'value' => $arrTgts ) )); if (! $this->help->execute()) { $this->help->debug(E_ERROR); return false; } return true; } // }}} // {{{ CCE CLASS FUNCTIONS /** * Getter function for CCE * * @param string $cce_id * CCE ID to query for * * @return array:cce */ public function get_CCE($cce_id = null) { $ret = []; $where = []; if (! is_null($cce_id)) { $where[] = array( 'field' => 'cce_id', 'op' => '=', 'value' => $cce_id ); } $this->help->select("sagacity.cce", array( 'pdi_id', 'cce_id' ), $where); $cces = $this->help->execute(); if (is_array($cces) && count($cces) && isset($cces['pdi_id'])) { $cces = array( 0 => $cces ); } if (is_array($cces) && count($cces) && isset($cces[0])) { foreach ($cces as $cce) { $ret[] = new cce($cce['pdi_id'], $cce['cce_id']); } } return $ret; } /** * Function to save CCE's to database * * @param array:cce|cce $cces * An array of CCE's that need to be saved * * @return boolean Returns TRUE if save was successful, otherwise FALSE */ public function save_CCE($cces) { $ret = true; $fields = array( 'pdi_id', 'cce_id' ); $params = []; if (is_array($cces)) { foreach ($cces as $cce) { $params[] = [ $cce->get_PDI_ID(), $cce->get_CCE_ID() ]; } $this->help->extended_replace("sagacity.cce", $fields, $params); if (! $this->help->execute()) { $this->help->debug(E_ERROR); $ret = false; } } else { $this->help->replace("sagacity.cce", array( 'pdi_id' => $cces->get_PDI_ID(), 'cce_id' => $cces->get_CCE_ID() )); if (! $this->help->execute()) { $this->help->debug(E_ERROR); $ret = false; } } return $ret; } // }}} // {{{ CCI CLASS FUNCTIONS public function get_CCI($cci_id = null) { $ret = []; $this->help->select("sagacity.cci"); if (! is_null($cci_id)) { $this->help->select("sagacity.cci", null, array( array( 'field' => 'cci_id', 'op' => '=', 'value' => $cci_id ) )); } $ccis = $this->help->execute(); if (is_array($ccis) && count($ccis)) { foreach ($ccis as $cci_data) { $cci = new cci(); $cci->cci_id = $cci_data['cci_id']; $cci->definition = $cci_data['definition']; $cci->type = $cci_data['type']; $cci->param = $cci_data['param']; $cci->note = $cci_data['note']; $this->help->select("sagacity.cci_refs", null, array( array( 'field' => 'cci_id', 'op' => '=', 'value' => $cci_data['cci_id'] ) )); $refs = $this->help->execute(); if (is_array($refs) && count($refs)) { foreach ($refs as $ref_data) { $ref = new cci_reference(); $ref->index = $ref_data['index']; $ref->url = $ref_data['url']; $ref->title = $ref_data['title']; $ref->ver = $ref_data['ver']; $cci->refs[] = $ref; } } $ret[] = $cci; } } else { $this->help->debug(E_ERROR); } return $ret; } /** * Get eMASS CCI Map * * @author Matt Shuter * * @return array Array of CCI-eMASS control mappings */ public function get_EMASS_CCIs() { $this->help->select("rmf.emass_cci"); $ret = $this->help->execute(); return $ret; } /** * Function to save a CCI * * @param array:cci|cci $cci_in * * @return boolean */ public function save_CCI($cci_in) { if (is_array($cci_in)) { $ccis = []; foreach ($cci_in as $cci) { $cci_id = preg_replace("/CCI\-[0]+/", "CCI-", $cci->cci_id); $ccis[] = [ $cci_id, $cci->control_id, $cci->enh_id, $cci->definition, $cci->guidance, $cci->procedure ]; } $this->help->extended_insert('rmf.cci', array( 'id', 'control_id', 'enh_id', 'def', 'guidance', 'procedure' ), $ccis, true); } else { $cci_id = preg_replace("/CCI\-[0]+/", "CCI-", $cci_in->cci_id); $this->help->insert('rmf.cci', array( 'cci_id' => $cci_id, 'control_id' => $cci_in->control_id, 'enh_id' => $cci_in->enh_id, 'def' => $cci_in->definition, 'guidance' => $cci_in->guidance, 'procedure' => $cci_in->procedure ), true); } if (! $this->help->execute()) { $this->help->debug(E_ERROR); return false; } return true; } /** * Function to save an array of eMASS-CCI mappings * * @param array:cci $cci_in * * @return boolean */ public function save_EMASS_CCIs($ccis_in) { $ret = false; $columns = array( 'id', 'control' ); $this->help->extended_insert('rmf.emass_cci', $columns, $ccis_in, true); if ($this->help->execute()) { $ret = true; } else { $this->help->debug(E_ERROR); } return $ret; } // }}} // {{{ CHECKLIST CLASS FUNCTIONS /** * Get a checklist * * @param mixed $Checklist_ID * [optional] * Checklist ID to query for (default NULL) * @param boolean $ord_desc * [optional] * Decide if you want to order to return from newest release * * @return array:checklist Returns an array of checklists, or an empty array if none found */ public function get_Checklist($Checklist_ID = null, $ord_desc = false) { $ret = []; if ($ord_desc) { $this->help->select("sagacity.checklist", [ 'id', 'checklist_id', 'name', 'description', 'date', 'file_name', 'ver', "MAX(LPAD(`release`, 0, 2)) AS 'release'", 'type', 'icon' ]); } else { $this->help->select("sagacity.checklist", null); } $where = []; if (! is_null($Checklist_ID)) { if (is_numeric($Checklist_ID)) { $where[] = [ 'field' => 'id', 'op' => '=', 'value' => $Checklist_ID ]; } elseif (is_array($Checklist_ID)) { if (isset($Checklist_ID['checklist_id'])) { $where[] = [ 'field' => 'checklist_id', 'op' => '=', 'value' => $Checklist_ID['checklist_id'] ]; } if (isset($Checklist_ID['type'])) { $where[] = [ 'field' => 'type', 'op' => '=', 'value' => $Checklist_ID['type'], 'sql_op' => 'AND' ]; } if (isset($Checklist_ID['version'])) { $where[] = [ 'field' => 'ver', 'op' => '=', 'value' => $Checklist_ID['version'], 'sql_op' => 'AND' ]; } if (isset($Checklist_ID['release'])) { $where[] = [ 'field' => 'release', 'op' => '=', 'value' => $Checklist_ID['release'], 'sql_op' => 'AND' ]; } } else { $where[] = [ 'field' => 'checklist_id', 'op' => '=', 'value' => $Checklist_ID ]; } } if (is_array($where) && count($where)) { $this->help->sql .= $this->help->where($where); } $flags = []; if ($ord_desc) { $flags = [ 'group' => 'type', 'order' => [ [ 'field' => 'name', 'sort' => 'asc' ], [ 'field' => 'type', 'sort' => 'asc' ], [ 'field' => 'ver', 'sort' => 'desc' ], [ 'field' => 'LPAD(`release`,2,0)', 'sort' => 'desc' ] ] ]; } else { $flags = [ 'order' => [ [ 'field' => 'name', 'sort' => 'asc' ], [ 'field' => 'type', 'sort' => 'asc' ], [ 'field' => 'ver', 'sort' => 'asc' ], [ 'field' => 'LPAD(`release`,2,0)', 'sort' => 'asc' ] ] ]; } if (is_array($flags) && count($flags)) { $this->help->sql .= $this->help->flags($flags); } $rows = $this->help->execute(); if (isset($rows['id'])) { $rows = array( 0 => $rows ); } if (is_array($rows) && count($rows)) { foreach ($rows as $row) { $chk = new checklist($row['id'], $row['checklist_id'], $row['name'], $row['description'], $row['date'], $row['file_name'], $row['ver'], $row['release'], $row['type'], $row['icon']); /* * $this->help->select("sagacity.checklist_software_lookup", array('sw_id'), array( * array( * 'field' => 'chk_id', * 'op' => '=', * 'value' => $row['id'] * ) * )); * $sw_rows = $this->help->execute(); * if (count($sw_rows)) { * if (isset($sw_rows['sw_id'])) { * $sw_rows = array(0 => $sw_rows); * } * * foreach ($sw_rows as $row2) { * $chk->add_SW($this->get_Software($row2['sw_id'])); * } * } */ $ret[] = $chk; } } return $ret; } /** * Function to get the checklists by the checklist name in an eChecklist file * * @param array:string $chk_arr * * @return array:checklist */ public function get_Checklist_By_Name($chk_arr) { $ret = []; $this->help->select("checklist", null, [ [ 'field' => "CONCAT(`name`, ' V', `ver`, 'R', `release`, ' (', `type`, ')')", 'op' => IN, 'value' => $chk_arr, 'backticks' => false ] ]); $rows = $this->help->execute(); if(is_array($rows) && count($rows) && isset($rows[0])) { foreach($rows as $row) { $ret[] = new checklist($row['id'], $row['checklist_id'], $row['name'], $row['description'], new DateTime($row['date']), $row['file_name'], $row['ver'], $row['release'], $row['type'], $row['icon']); } } elseif(is_array($rows) && count($rows) && isset($rows['id'])) { $ret = new checklist($rows['id'], $rows['checklist_id'], $rows['name'], $rows['description'], new DateTime($rows['date']), $rows['file_name'], $rows['ver'], $rows['release'], $rows['type'], $rows['icon']); } return $ret; } /** * Function to get the checklist based on the checklist filename * * @param string $fname * * @return mixed */ public function get_Checklist_By_File($fname) { $ret = []; $this->help->select("sagacity.checklist", null, [ [ 'field' => 'file_name', 'value' => $fname ] ]); $rows = $this->help->execute(); if (isset($rows['id'])) { $rows = [ 0 => $rows ]; } if (is_array($rows) && count($rows) && isset($rows[0])) { foreach ($rows as $row) { $chk = new checklist($row['id'], $row['checklist_id'], $row['name'], $row['description'], $row['date'], $row['file_name'], $row['ver'], $row['release'], $row['type'], $row['icon']); /* */ $this->help->select("sagacity.checklist_software_lookup", [ 'sw_id' ], [ [ 'field' => 'chk_id', 'value' => $row['id'] ] ]); $sw_rows = $this->help->execute(); if (is_array($sw_rows) && count($sw_rows)) { if (isset($sw_rows['sw_id'])) { $sw_rows = [ 0 => $sw_rows ]; } foreach ($sw_rows as $row2) { $chk->add_SW($this->get_Software($row2['sw_id'])); } } $ret[] = $chk; } } return $ret; } /** * Function to retrieve the most current checklist for a given software package * * @param software $software * Software of which to look for checklists * * @return array:checklist Returns an array of checklists that this software ties to. Otherwise, an empty array */ public function get_Latest_Checklist_By_Software($software) { $ret = []; $this->help->create_table("c", true, $this->help->select("sagacity.checklist", null, [], array( 'order' => '`ver` DESC, CONVERT(`release`, DECIMAL(4,2)) DESC' ))); if (! $this->help->execute()) { return $ret; } $this->help->select("c", array( 'c.id' ), array( array( 'field' => 'csl.sw_id', 'op' => '=', 'value' => $software->get_ID() ) ), array( 'table_joins' => array( "JOIN sagacity.checklist_software_lookup csl ON csl.chk_id=c.id" ), 'group' => 'c.name,c.type', 'order' => 'c.name' )); $rows = $this->help->execute(); if (is_array($rows) && count($rows) && isset($rows['id'])) { $rows = array( 0 => $rows ); } if (is_array($rows) && count($rows) && isset($rows[0])) { foreach ($rows as $row) { $chk = $this->get_Checklist($row['id']); if (is_array($chk) && count($chk) && isset($chk[0])) { $ret[] = $chk[0]; } } } return $ret; } /** * Get a summary of checklist stats * * @param integer $cat_id * Integer category ID to get the summary on * * @return NULL|array:targets,checklist,string Returns an associative array of target (id & name), checklists, and a summary that joins the two */ public function get_Checklist_Summary($cat_id) { $where = array( array( 'field' => 't.cat_id', 'op' => (is_null($cat_id) ? IS : '='), 'value' => $cat_id ) ); $tgts = []; $chklsts = []; $summary = []; $this->help->select("sagacity.target_checklist tc", null, $where, array( 'table_joins' => array( "LEFT JOIN sagacity.target t ON t.id=tc.tgt_id" ), 'group' => 't.id' )); $tgt_rows = $this->help->execute(); if (is_array($tgt_rows) && count($tgt_rows) && isset($tgt_rows['name'])) { $tgt_rows = array( 0 => $tgt_rows ); } if (is_array($tgt_rows) && count($tgt_rows) && isset($tgt_rows[0])) { foreach ($tgt_rows as $row) { $tgts[$row['id']] = $row['name']; } } else { $this->help->debug(E_ERROR); } $this->help->select("sagacity.target_checklist tc", array( 'c.id', 'c.name', 'c.type', 'c.ver', 'c.`release`' ), $where, array( 'table_joins' => array( "LEFT JOIN sagacity.checklist c ON tc.chk_id=c.id", "LEFT JOIN sagacity.target t ON tc.tgt_id=t.id" ), 'group' => 'c.id', 'order' => 'c.name' )); $chk_rows = $this->help->execute(); if (is_array($chk_rows) && count($chk_rows) && isset($chk_rows['id'])) { $chk_rows = array( 0 => $chk_rows ); } if (is_array($chk_rows) && count($chk_rows) && isset($chk_rows[0])) { foreach ($chk_rows as $row) { $chklsts[$row['id']] = "{$row['name']} V{$row['ver']}R{$row['release']} (" . ($row['type'] == 'iavm' ? 'IAVM' : ucfirst($row['type'])) . ")"; } } else { $this->help->debug(E_ERROR); } $this->help->select("sagacity.findings f", array( "COUNT(1) as 'cnt'", 'c.id', 'c.name', 'c.ver', 'c.`release`' ), array( array( 'field' => 't.cat_id', 'op' => (is_null($cat_id) ? IS : '='), 'value' => $cat_id ), array( 'field' => 'c.name', 'op' => '=', 'value' => 'Orphan', 'sql_op' => 'AND' ) ), array( 'table_joins' => array( "LEFT JOIN sagacity.target t ON t.id=f.tgt_id", "LEFT JOIN sagacity.pdi_checklist_lookup pcl ON pcl.pdi_id=f.pdi_id", "LEFT JOIN sagacity.checklist c ON pcl.checklist_id=c.id" ) )); $chk_rows2 = $this->help->execute(); if (is_array($chk_rows2) && count($chk_rows2) && isset($chk_rows2['cnt'])) { if ($chk_rows2['cnt'] > 0) { $chklsts[$chk_rows2['id']] = "{$chk_rows2['name']} V{$chk_rows2['ver']}R{$chk_rows2['release']}"; } } foreach ($chklsts as $chk_key => $chk) { foreach ($tgts as $host_key => $host) { if ($chk != 'Orphan') { $this->help->select_count("sagacity.target_checklist tc", [ [ 'field' => 'tc.tgt_id', 'op' => '=', 'value' => $host_key ], [ 'field' => 'tc.chk_id', 'op' => '=', 'value' => $chk_key, 'sql_op' => 'AND' ] ]); } else { $this->help->select("sagacity.findings f", [ "IF(COUNT(1) > 0, '1', '0')" ], [ [ 'field' => 'f.tgt_id', 'op' => '=', 'value' => $host_key ], [ 'field' => 'c.name', 'op' => '=', 'value' => 'Orphan', 'sql_op' => 'AND' ] ], [ 'table_joins' => [ "LEFT JOIN sagacity.pdi_checklist_lookup pcl ON pcl.pdi_id=f.pdi_id", "LEFT JOIN sagacity.checklist c ON c.id=pcl.checklist_id" ] ]); } $summary[$chk_key][$host_key] = $this->help->execute(); } } return [ 'tgts' => $tgts, 'checklists' => $chklsts, 'summary' => $summary ]; } /** * Get all checklist & targets in a category * * @param integer $cat_id * Category ID to pull the checklists from * * @return NULL|array:string checklist */ public function get_Category_Checklists($cat_id) { $chklsts = []; $this->help->select("sagacity.target_checklist tc", array( 'tc.tgt_id', 'tc.chk_id' ), array( array( 'field' => 't.cat_id', 'op' => '=', 'value' => $cat_id ) ), array( 'table_joins' => array( "LEFT JOIN sagacity.target t ON tc.tgt_id = t.id", "LEFT JOIN sagacity.checklist c ON tc.chk_id = c.id" ), 'order' => 'c.name' )); $rows = $this->help->execute(); if (is_array($rows) && count($rows) && isset($rows['tgt_id'])) { $rows = array( 0 => $rows ); } if (is_array($rows) && count($rows) && isset($rows[0])) { foreach ($rows as $row) { $chk = $this->get_Checklist($row['chk_id']); if (is_array($chk) && count($chk) && isset($chk[0]) && is_a($chk[0], 'checklist')) { $chk = $chk[0]; } else { continue; } $tgts = isset($chklsts[$chk->get_ID()]['tgts']) ? $chklsts[$chk->get_ID()]['tgts'] : null; $chklsts[$chk->get_ID()] = array( 'tgts' => $tgts . $row['tgt_id'] . ",", 'checklist' => $chk ); } } $this->help->select_count("sagacity.target t", array( array( 'field' => 't.cat_id', 'op' => '=', 'value' => $cat_id ), array( 'field' => 'pcl.checklist_id', 'op' => '=', 'value' => "(SELECT c.id FROM sagacity.checklist c WHERE c.name='Orphan')", 'sql_op' => 'AND' ) ), array( 'table_joins' => array( "LEFT JOIN sagacity.findings f ON t.id=f.tgt_id", "LEFT JOIN sagacity.pdi_checklist_lookup pcl ON pcl.pdi_id=f.pdi_id" ) )); $count = $this->help->execute(); if ($count) { $this->help->select("sagacity.target t", array( "t.id AS 'tgt_id'", "pcl.checklist_id AS 'chk_id'" ), array( array( 'field' => 't.cat_id', 'op' => '=', 'value' => $cat_id ), array( 'field' => 'pcl.checklist_id', 'op' => '=', 'value' => "(SELECT c.id FROM sagacity.checklist c WHERE c.name='Orphan')", 'sql_op' => 'AND' ) ), array( 'table_joins' => array( "LEFT JOIN sagacity.findings f ON t.id=f.tgt_id", "LEFT JOIN sagacity.pdi_checklist_lookup pcl ON pcl.pdi_id=f.pdi_id" ), 'group' => 't.id,pcl.checklist_id' )); $rows2 = $this->help->execute(); if (is_array($rows2) && count($rows2) && isset($rows2['tgt_id'])) { $rows2 = array( 0 => $rows2 ); } if (is_array($rows2) && count($rows2) && isset($rows2[0])) { foreach ($rows2 as $row2) { $chk = $this->get_Checklist($row2['chk_id']); if (is_array($chk) && count($chk) && isset($chk[0]) && is_a($chk[0], 'checklist')) { $chk = $chk[0]; } else { continue; } $tgts = isset($chklsts[$chk->get_ID()]['tgts']) ? $chklsts[$chk->get_ID()]['tgts'] : ""; $chklsts[$chk->get_ID()] = array( 'tgts' => $tgts . $row2['tgt_id'] . ",", 'checklist' => $chk ); } } } return $chklsts; } /** * Get array of checklists for a target * * @param integer $tgt_id * The target ID of the target we want checklists from * * @return array:checklist |NULL * Returns an array of checklists that are assigned to the requested target */ public function get_Target_Checklists($tgt_id) { $this->help->select("sagacity.target_checklist tc", [ 'c.id', 'tc.class' ], [ [ 'field' => 'tc.tgt_id', 'op' => '=', 'value' => $tgt_id ] ], [ 'table_joins' => [ "LEFT JOIN sagacity.checklist c ON c.id=tc.chk_id" ], 'order' => 'c.name' ]); $chk = []; $chks = $this->help->execute(); if (isset($chks['id'])) { $chks = [ 0 => $chks ]; } if (is_array($chks) && count($chks) && isset($chks[0])) { foreach ($chks as $row) { /** @var checklist $checklist */ $checklist = $this->get_Checklist($row['id'])[0]; $checklist->set_Classification($row['class']); $chk[$checklist->get_ID()] = $checklist; } } // get the orphan checklist ID $this->help->select("sagacity.checklist", [ 'id' ], [ [ 'field' => 'name', 'op' => '=', 'value' => 'Orphan' ] ]); $orphan = $this->help->execute(); // check to see if this target has findings from the Orphan checklist $this->help->select_count("sagacity.pdi_checklist_lookup pcl", [ [ 'field' => 'pcl.checklist_id', 'op' => '=', 'value' => $orphan['id'] ], [ 'field' => 'f.tgt_id', 'op' => '=', 'value' => $tgt_id, 'sql_op' => 'AND' ] ], [ 'table_joins' => [ "RIGHT JOIN sagacity.findings f ON pcl.pdi_id=f.pdi_id" ] ]); $cnt = $this->help->execute(); // add the orphan checklist if findings exist if ($cnt) { $chk[] = $this->get_Checklist($orphan['id'])[0]; } return $chk; } /** * Function for getting eChecklist data to export * * @param int $cat_id * @param array $chk_host_list * @param string $status * @param int $category * * @return array */ public function get_Category_Findings($cat_id, $chk_host_list = [], $status = null, $category = null) { $ret = []; $stigs = []; $tgt_ids = []; $where = [ [ 'field' => 'gcf.cat_id', 'op' => (is_null($cat_id) ? IS : '='), 'value' => $cat_id ] ]; if (! is_null($status)) { $where[] = [ 'field' => 'gcf.status', 'op' => '=', 'value' => $status, 'sql_op' => 'AND', 'open-paren' => true ]; if ($status == 'Not Reviewed') { $where[] = [ 'field' => 'gcf.status', 'op' => IS, 'value' => null, 'sql_op' => 'OR', 'close-paren' => true ]; } else { unset($where[1]['open-paren']); } } if (! is_null($category)) { $where[] = [ 'field' => 'gcf.cat', 'op' => '=', 'value' => $category, 'sql_op' => 'AND' ]; } $this->help->select("sagacity.get_cat_findings gcf", null, $where); $rows = $this->help->execute(); if (is_array($rows) && count($rows) && isset($rows['tgt_id'])) { $rows = [ 0 => $rows ]; } if (is_array($rows) && count($rows) && isset($rows[0])) { foreach ($rows as $row) { if (is_null($row['chk_icon']) || $row['chk_icon'] == '') { $worksheet_name = '(Unknown)'; } else { $worksheet_name = substr($row['chk_icon'], 0, - 4); } if (! isset($ret[$worksheet_name])) { $ret[$worksheet_name] = [ 'target_list' => [], 'checklists' => [], 'stigs' => [] ]; $where2 = [ [ 'field' => 't.cat_id', 'op' => '=', 'value' => $cat_id ], [ 'field' => 'c.icon', 'op' => LIKE, 'value' => "'$worksheet_name%'", 'sql_op' => 'AND' ] ]; if (is_array($tgt_ids) && count($tgt_ids)) { $where2[] = [ 'field' => 't.id', 'op' => IN, 'value' => $tgt_ids, 'sql_op' => 'AND' ]; } $this->help->select("target t", [ 't.class' ], $where2, [ 'table_joins' => [ "LEFT JOIN target_checklist tc ON tc.tgt_id = t.id", "LEFT JOIN checklist c ON c.id=tc.chk_id" ], 'group' => 't.class', 'order' => "FIELD(t.class, 'S', 'FOUO', 'U')" ]); $rows2 = $this->help->execute(); if (is_array($rows2) && count($rows2) && isset($rows2['class'])) { $ret[$worksheet_name]['highest_class'] = $rows2['class']; } } if (! in_array($row['chk_id'], $ret[$worksheet_name]['checklists'])) { $ret[$worksheet_name]['checklists'][] = $row['chk_id']; } if (! isset($ret[$worksheet_name]['target_list'][$row['tgt_name']])) { $ret[$worksheet_name]['target_list']["{$row['tgt_name']}"] = count($ret[$worksheet_name]['target_list']) + 6; } if (! isset($ret[$worksheet_name]['stigs'][$row['stig_id']])) { if (! empty($row['finding_ia_controls'])) { $ia = explode(' ', $row['finding_ia_controls']); } else { $ia = explode(' ', $row['ia_controls']); } $echk = new echecklist($row['stig_id'], $row['vms_id'], (empty($row['finding_cat']) ? $row['cat'] : $row['finding_cat']), $ia, $row['short_title'], null, $row['notes'], $row['check_contents'], null); $echk->set_PDI_ID($row['pdi_id']); $ret[$worksheet_name]['stigs'][$row['stig_id']] = [ 'echecklist' => $echk, "{$row['tgt_name']}" => $row['finding_status'], 'chk_id' => $row['chk_id'] ]; if (! in_array($row['stig_id'], $stigs)) { $stigs[] = $row['stig_id']; } } else { $ret[$worksheet_name]['stigs'][$row['stig_id']][$row['tgt_name']] = $row['finding_status']; if(stripos($ret[$worksheet_name]['stigs'][$row['stig_id']]['echecklist']->get_Notes(), $row['notes']) === false) { $ret[$worksheet_name]['stigs'][$row['stig_id']]['echecklist']->append_Notes($row['notes'] . PHP_EOL); } } if ($row['chk_type'] == 'manual') { $ret[$worksheet_name]['stigs'][$row['stig_id']]['chk_id'] = $row['chk_id']; } } } /* $where = [ [ 'field' => 'gof.cat_id', 'op' => (is_null($cat_id) ? IS : '='), 'value' => $cat_id ] ]; if (is_array($stigs) && count($stigs) && isset($stigs[0]) && is_a($stigs[0], 'stig')) { $where[] = [ 'field' => 'gof.stig_id', 'op' => NOT_IN, 'value' => $stigs, 'sql_op' => 'AND' ]; } if (! is_null($status)) { $where[] = [ 'field' => 'gof.status', 'op' => '=', 'value' => $status, 'sql_op' => 'AND', 'open-paren' => true ]; if ($status == 'Not Reviewed') { $where[] = [ 'field' => 'gof.status', 'op' => IS, 'value' => null, 'sql_op' => 'OR', 'close-paren' => true ]; } else { unset($where[2]['open-paren']); } } if (! is_null($category)) { $where[] = [ 'field' => 'gof.cat', 'op' => '=', 'value' => $category, 'sql_op' => 'AND' ]; } $this->help->select("sagacity.get_orphan_findings gof", null, $where); $rows = $this->help->execute(); if (is_array($rows) && count($rows) && isset($rows['tgt_id'])) { $rows = [ 0 => $rows ]; } if (is_array($rows) && count($rows) && isset($rows[0])) { $worksheet_name = "Orphan"; $class = [ 'U' => 1, 'FOUO' => 2, 'S' => 3 ]; foreach ($rows as $row) { if (! isset($ret[$worksheet_name])) { $ret[$worksheet_name] = [ 'target_list' => [], 'checklists' => [], 'stigs' => [], 'highest_class' => 'U' ]; } if (! in_array($row['chk_id'], $ret[$worksheet_name]['checklists'])) { $ret[$worksheet_name]['checklists'][] = $row['chk_id']; } if (! isset($ret[$worksheet_name]['target_list'][$row['tgt_name']])) { $ret[$worksheet_name]['target_list'][$row['tgt_name']] = (is_array($ret[$worksheet_name]['target_list']) ? count($ret[$worksheet_name]['target_list']) + 6 : 7); $sql2 = "SELECT t.`class` " . "FROM `sagacity`.`target` t " . "WHERE t.`name` = '" . $this->conn->real_escape_string($row['tgt_name']) . "'"; if ($res2 = $this->conn->query($sql2)) { $row2 = $res2->fetch_assoc(); if (isset($class[$row2['class']]) && isset($class[$ret[$worksheet_name]['highest_class']])) { if ($class[$row2['class']] > $class[$ret[$worksheet_name]['highest_class']]) { $ret[$worksheet_name]['highest_class'] = $row2['class']; } } } } if (! isset($ret[$worksheet_name]['stigs'][$row['stig_id']])) { if (! empty($row['finding_ia_controls'])) { $ia = explode(" ", $row['finding_ia_controls']); } else { $ia = explode(" ", $row['ia_controls']); } $echk = new echecklist($row['stig_id'], $row['vms_id'], (empty($row['finding_cat']) ? $row['cat'] : $row['finding_cat']), $ia, $row['short_title'], null, $row['notes'], $row['check_contents'], null); $echk->set_PDI_ID($row['pdi_id']); $ret[$worksheet_name]['stigs'][$row['stig_id']] = [ 'echecklist' => $echk, $row['tgt_name'] => $row['finding_status'], 'chk_id' => $row['chk_id'] ]; } else { $ret[$worksheet_name]['stigs'][$row['stig_id']][$row['tgt_name']] = $row['finding_status']; $ret[$worksheet_name]['stigs'][$row['stig_id']]['echecklist']->append_Notes($row['notes'] . "\r"); } } } */ return $ret; } /** * Add a checklist to database * * @param checklist $checklist_in * The checklist that we want to add to the database * * @return integer Returns the id of the checklist inserted, or 0 if failed */ public function save_Checklist($checklist_in) { if (empty($checklist_in->id)) { $this->help->insert("sagacity.checklist", array( 'checklist_id' => $checklist_in->checklist_id, 'name' => $checklist_in->name, 'description' => $checklist_in->description, 'date' => $checklist_in->date, 'file_name' => $checklist_in->file_name, 'release' => $checklist_in->release, 'ver' => $checklist_in->ver, 'type' => $checklist_in->type, 'icon' => $checklist_in->icon ), true); if (! $this->help->execute()) { Sagacity_Error::sql_handler($this->help->sql); $this->help->debug(E_ERROR); } else { $chk_id = $this->conn->insert_id; } if (is_array($checklist_in->sw) && count($checklist_in->sw)) { $fields = [ 'chk_id', 'sw_id' ]; $params = []; foreach ($checklist_in->sw as $sw) { if (is_a($sw, 'software') && $sw->get_ID()) { $params[] = [ $chk_id, $sw->get_ID() ]; } } if (count($params)) { $this->help->extended_insert('checklist_software_lookup', $fields, $params, true); if (! $this->help->execute()) { Sagacity_Error::sql_handler($this->help->sql); $this->help->debug(E_ERROR); } } } } else { $this->help->update('checklist', [ 'checklist_id' => $checklist_in->checklist_id, 'name' => $checklist_in->name, 'description' => $checklist_in->description, 'date' => $checklist_in->date, 'file_name' => $checklist_in->file_name, 'release' => $checklist_in->release, 'ver' => $checklist_in->ver, 'type' => $checklist_in->type, 'icon' => $checklist_in->icon ], [ [ 'field' => 'id', 'op' => '=', 'value' => $checklist_in->id ] ]); $chk_id = $checklist_in->id; if (! $this->help->execute()) { Sagacity_Error::sql_handler($this->help->sql); $this->help->debug(E_ERROR); } if (is_array($checklist_in->sw) && count($checklist_in->sw)) { $this->help->delete("checklist_software_lookup", [ [ 'field' => 'chk_id', 'op' => '=', 'value' => $checklist_in->id ] ]); $this->help->execute(); $field = [ 'chk_id', 'sw_id' ]; $params = []; foreach ($checklist_in->sw as $sw) { if ($sw->get_ID()) { $params[] = [ $chk_id, $sw->get_ID() ]; } } if (is_array($params) && count($params)) { $this->help->extended_insert("checklist_software_lookup", $field, $params, true); if (! $this->help->execute()) { Sagacity_Error::sql_handler($this->help->sql); $this->help->debug(E_ERROR); } } } } return $chk_id; } // }}} // {{{ CVE CLASS FUNCTIONS /** * Function to retrieve CVE object * * @param string $cve_id * CVE to query from the database * * @return cve|NULL Returns CVE and associated references or null is nothing found */ public function get_CVE($cve_id) { $cve = null; $this->help->select("sagacity.cve_db", array( "cve_db.cve_id", "cve.pdi_id", "cve_db.seq", "cve_db.status", "cve_db.phase", "cve_db.phase_date", "cve_db.desc" ), array( array( 'field' => 'cve_db.cve_id', 'op' => '=', 'value' => $cve_id ) ), array( 'table_joins' => array( "LEFT JOIN sagacity.cve ON cve.cve_id=cve_db.cve_id" ) )); $row = $this->help->execute(); if (is_array($row) && count($row) && isset($row['cve_id'])) { $cve_id = $row['cve_id']; $cve = new cve($row['pdi_id'], $row['cve_id']); $cve->set_Sequence($row['seq']); $cve->set_Status($row['status']); $cve->set_Phase($row['phase']); $cve->set_Phase_Date($row['phase_date']); $cve->set_Description($row['desc']); $this->help->select("sagacity.iavm_to_cve itc", array( "itc.noticeId" ), array( array( 'field' => "itc.cve_id", 'op' => '=', 'value' => $cve_id ) )); $iavm_rows = $this->help->execute(); if (is_array($iavm_rows) && count($iavm_rows) && isset($iavm_rows['noticeId'])) { $iavm_rows = array( 0 => $iavm_rows ); } if (is_array($iavm_rows) && count($iavm_rows) && isset($iavm_rows[0])) { foreach ($iavm_rows as $iavm) { $cve->add_IAVM($iavm['noticeId']); } } $this->help->select("sagacity.cve_references", array( 'id', 'source', 'url', 'val' ), array( array( 'field' => 'cve_seq', 'op' => '=', 'value' => $cve_id ) )); $ref_rows = $this->help->execute(); if (is_array($ref_rows) && count($ref_rows) && isset($ref_rows['id'])) { $ref_rows = array( 0 => $ref_rows ); } if (is_array($ref_rows) && count($ref_rows) && isset($ref_rows[0])) { foreach ($ref_rows as $ref) { $cve->add_Reference(new cve_reference($ref['id'], $ref['source'], $ref['url'], $ref['val'])); } } } return $cve; } /** * Getter function to retrieve CVE's by their link to a PDI * * @param integer $pdi_id * PDI ID that we want to find CVE's for * * @return NULL|array:cve Returns an array of CVEs for each one found that links to a PDI or NULL if none found */ public function get_CVEs_By_PDI($pdi_id) { $ret = []; $sql = "SELECT " . "cve_db.`cve_id`,cve.`pdi_id`,cve_db.`seq`,cve_db.`status`," . "cve_db.`phase`,cve_db.`phase_date`,cve_db.`desc` " . "FROM `sagacity`.`cve_db` " . "LEFT JOIN `sagacity`.`cve` ON cve.`cve_id` = cve_db.`cve_id` " . "WHERE cve.`pdi_id` = " . $this->conn->real_escape_string($pdi_id); if ($res = $this->conn->query($sql)) { if (! $res->num_rows) { return null; } while ($row = $res->fetch_assoc()) { $cve_id = $row['cve_id']; $cve = new cve($row['pdi_id'], $row['cve_id']); $cve->set_Sequence($row['seq']); $cve->set_Status($row['status']); $cve->set_Phase($row['phase']); $cve->set_Phase_Date($row['phase_date']); $cve->set_Description($row['desc']); $sql = "SELECT itc.`noticeId` " . "FROM `sagacity`.`iavm_to_cve` itc " . "WHERE itc.`cve_id`='" . $this->conn->real_escape_string($cve_id) . "'"; if ($res2 = $this->conn->query($sql)) { if ($res2->num_rows) { while ($row2 = $res2->fetch_assoc()) { $cve->add_IAVM($row2['noticeId']); } } } $sql = "SELECT `id`,`source`,`url`,`val` " . "FROM `sagacity`.`cve_references` " . "WHERE `cve_seq`='" . $this->conn->real_escape_string($cve_id) . "'"; if ($res2 = $this->conn->query($sql)) { while ($row2 = $res2->fetch_assoc()) { $cve->add_Reference(new cve_reference($row2['id'], $row2['source'], $row2['url'], $row2['val'])); } $ret[] = $cve; } else { Sagacity_Error::sql_handler($sql); error_log($this->conn->error); } } return $ret; } else { Sagacity_Error::sql_handler($sql); error_log($this->conn->error); } return null; } /** * Get a CVE from a external reference * * @param string $ext * String of the external reference we are looking for * * @return cve|NULL Returns the CVE that references that external data point or NULL if none found */ public function get_CVE_From_External($ext) { $sql = "SELECT `cve_seq` " . "FROM `sagacity`.`cve_references` " . "WHERE `url` LIKE '%" . $this->conn->real_escape_string($ext) . "%' OR " . "`val` LIKE '%" . $this->conn->real_escape_string($ext) . "%' " . "GROUP BY `cve_seq` " . "ORDER BY `cve_seq` DESC"; if ($res = $this->conn->query($sql)) { if ($res->num_rows) { $row = $res->fetch_assoc(); $cve = $this->get_CVE($row['cve_seq']); return $cve; } } return null; } /** * Update or insert a CVE * * @param array:cve $cves * Array of CVEs to save to database * * @return boolean Returns TRUE if successful, otherwise FALSE */ public function save_CVE($cves) { if (is_array($cves) && isset($cves[0]) && is_a($cves[0], 'cve')) { foreach ($cves as $cve) { $db_cve = $this->get_CVE($cve->get_CVE()); if (! is_null($db_cve) && is_a($db_cve, 'cve')) { $this->help->update("sagacity.cve_db", array( 'status' => $cve->get_Status(), 'phase' => $cve->get_Phase(), 'phase_date' => $cve->get_Phase_Date(), 'desc' => $cve->get_Description() ), array( array( 'field' => 'cve_id', 'op' => '=', 'value' => $cve->get_CVE() ) )); if (! $this->help->execute()) { $this->help->debug(E_WARNING); return false; } if (! $db_cve->get_PDI_ID() && $cve->get_PDI_ID()) { $this->help->insert("sagacity.cve", array( 'pdi_id' => $cve->get_PDI_ID(), 'cve_id' => $cve->get_CVE() )); if (! $this->help->execute()) { $this->help->debug(E_WARNING); return false; } } $vals = []; foreach ($cve->get_References() as $ref) { if (! $db_cve->ref_Exists($ref->get_Value())) { $vals[] = [ $cve->get_CVE(), $ref->get_Source(), $ref->get_URL(), $ref->get_Value() ]; } } if (is_array($vals) && count($vals)) { $this->help->extended_insert("cve_references", [ 'cve_seq', 'source', 'url', 'val' ], $vals, true); if (! $this->help->execute()) { $this->help->debug(E_WARNING); return false; } } } else { $this->help->insert("cve_db", [ 'cve_id' => $cve->get_CVE(), 'seq' => $cve->get_Sequence(), 'status' => $cve->get_Status(), 'phase' => $cve->get_Phase(), 'phase_date' => $cve->get_Phase_Date(), 'desc' => $cve->get_Description() ], true); if (! $this->help->execute()) { $this->help->debug(E_WARNING); return false; } if ($cve->get_PDI_ID()) { $this->help->insert("sagacity.cve", [ 'pdi_id' => $cve->get_PDI_ID(), 'cve_id' => $cve->get_CVE() ], true); if (! $this->help->execute()) { $this->help->debug(E_WARNING); return false; } } if (is_array($cve->get_References()) && count($cve->get_References())) { $ref_vals = []; foreach ($cve->get_References() as $ref) { $ref_vals[] = [ $cve->get_CVE(), $ref->get_Source(), $ref->get_URL(), $ref->get_Value() ]; } if (is_array($ref_vals) && count($ref_vals)) { $this->help->extended_insert("cve_references", [ 'cve_seq', 'source', 'url', 'val' ], $ref_vals, true); if (! $this->help->execute()) { $this->help->debug(E_WARNING); return false; } } } if ($cve->get_XML()) { $this->help->insert("cve_web", [ 'cve_id' => $cve->get_CVE(), 'xml' => $cve->get_XML() ], true); $this->help->execute(); } } } } return true; } // }}} // {{{ ECHECKLIST CLASS FUNCTIONS /** * Get an eChecklist for a checklist and list of targets * * @param mixed $ref * The reference to search for (can consist of any data that is referenced in an eChecklist line * @param integer $chk_id * * @return NULL|echecklist Returns eChecklist for associated checklists and reference */ public function get_eChecklist($ref, $chk_id) { $ret = null; $where = []; if (is_a($ref, "stig")) { $where[] = [ 'field' => 's.stig_id', 'op' => '=', 'value' => $ref->get_ID() ]; } elseif (is_a($ref, "golddisk")) { $where[] = [ 'field' => 'v.vms_id', 'op' => '=', 'value' => $ref->get_ID() ]; } elseif (is_a($ref, "pdi")) { $where[] = [ 'field' => 'pdi.id', 'op' => '=', 'value' => $ref->get_ID() ]; } else { error_log("No reference to search for"); return $ret; } $this->help->select("pdi_catalog pdi", [ "pdi.id AS 'pdi_id'", "s.stig_id", "v.vms_id", "pdi.short_title", "IF(pdi.cat=1,'I',IF(pdi.cat=2,'II',IF(pdi.cat=3,'III',''))) as 'cat'" ], $where, [ 'table_joins' => [ "LEFT JOIN stigs s ON s.pdi_id = pdi.id", "LEFT JOIN golddisk v ON v.pdi_id = pdi.id" ], 'group' => 's.stig_id' ]); $rows = $this->help->execute(); if (is_array($rows) && count($rows) && isset($rows['pdi_id'])) { $rows = [ 0 => $rows ]; } if (is_array($rows) && count($rows) && isset($rows[0])) { foreach ($rows as $row) { $ret = new echecklist($row['stig_id'], $row['vms_id'], $row['cat'], null, $row['short_title'], null, null, null, null); $ret->set_PDI_ID($row['pdi_id']); $this->help->select("pdi_checklist_lookup pcl", [ 'pcl.check_contents' ], [ [ 'field' => 'pcl.checklist_id', 'op' => IN, 'value' => (is_array($chk_id) ? implode(",", $chk_id) : $chk_id) ], [ 'field' => 'pcl.pdi_id', 'op' => '=', 'value' => $row['pdi_id'], 'sql_op' => 'AND' ] ], [ 'table_joins' => [ "JOIN sagacity.checklist c ON c.id = pcl.checklist_id" ], 'order' => "FIELD(c.`type`, 'manual', 'iavm', 'policy', 'benchmark')" ]); $row2 = $this->help->execute(); if (is_array($row2) && count($row2) && isset($row2['check_contents'])) { $ret->set_Check_Contents($row2['check_contents']); } } } return $ret; } // }}} // {{{ FILTER CLASS FUNCTIONS /** * Getter function for search filters * * @param string $type * * @return array:string */ public function get_Filters($type, $name = null) { $ret = []; $sql = "SELECT `type`, `name`, `criteria` " . "FROM `sagacity`.`search_filters` " . "WHERE `type` = '" . $this->conn->real_escape_string($type) . "'"; if (! is_null($name)) { $sql .= " AND `name` = '" . $this->conn->real_escape_string($name) . "'"; } if ($res = $this->conn->query($sql)) { while ($row = $res->fetch_assoc()) { $ret[] = array( 'type' => $row['type'], 'name' => $row['name'], 'criteria' => $row['criteria'] ); } return $ret; } else { error_log($this->conn->error); Sagacity_Error::sql_handler($sql); } return []; } /** * Save function for a search filter * * @param string $type * @param string $name * @param string $criteria * * @return string */ public function save_Filter($type, $name, $criteria) { $this->help->insert("sagacity.search_filters", [ 'name' => $name, 'type' => $type, 'criteria' => $criteria ]); if (! $this->help->execute()) { $this->help->debug(E_WARNING); return false; } return true; } // }}} // {{{ FINDING CLASS FUNCTIONS /** * Get finding(s) for a specific target from the database * * @param target $tgt * The target that we want findings for * @param stig|golddisk|iavm|nessus $ref * [optional] * Get a finding associated with a specific PDI (default null) * @param scan $scan * [optional] * Get findings associated with a specific scan (default null) * @param boolean $orphan_only * [optional] * Only retrieve orphaned findings (default false) * @param string $status * [optional] * Limit the retrieval to findings with this status (default null) * * @return array:finding|NULL Returns array of findings */ public function get_Finding($tgt, $ref = null, $scan = null, $orphan_only = false, $status = null) { $ret = null; $where = [ [ 'field' => 'tgt_id', 'op' => '=', 'value' => $tgt->get_ID() ] ]; if (! is_null($scan)) { $where[] = [ 'field' => 'scan_id', 'op' => '=', 'value' => $scan->get_ID(), 'sql_op' => 'AND' ]; } if (! is_null($ref) && method_exists($ref, 'get_PDI_ID')) { $where[] = [ 'field' => 'pdi_id', 'op' => '=', 'value' => $ref->get_PDI_ID(), 'sql_op' => 'AND' ]; } $this->help->select("sagacity.findings", null, $where); if (! is_null($status)) { $this->help->sql = "SELECT " . "f.`id`, {$tgt->get_ID()} as 'tgt_id', pdi.`id` as 'pdi_id', f.`scan_id`, " . "f.`notes`, f.`change_id`, f.`orig_src`, f.`finding_itr`, f.`cat`, " . "IF(f.`findings_status_id` IS NOT NULL, " . "f.`findings_status_id`, " . "(SELECT fs.`id` " . "FROM `sagacity`.`findings_status` fs " . "WHERE fs.`status` = '{$this->conn->real_escape_string($status)}')" . ") as 'findings_status' " . "FROM `sagacity`.`pdi_catalog` pdi " . "LEFT JOIN `sagacity`.`pdi_checklist_lookup` lookup ON lookup.`pdi_id` = pdi.`id` " . "LEFT JOIN `sagacity`.`target_checklist` tc ON tc.`chk_id` = lookup.`checklist_id` " . "LEFT JOIN `sagacity`.`findings` f ON f.`pdi_id` = pdi.`id` AND f.`tgt_id` = {$tgt->get_ID()} " . "WHERE tc.`tgt_id` = {$tgt->get_ID()} AND " . "(f.`findings_status_id` = (" . "SELECT fs.`id` " . "FROM `sagacity`.`findings_status` fs " . "WHERE fs.`status` = '{$this->conn->real_escape_string($status)}'" . ") OR " . "f.`findings_status_id` IS NULL) " . "GROUP BY pdi.id"; } if ($orphan_only) { $this->help->select("sagacity.findings f", [ 'f.*' ], [ [ 'field' => 'f.tgt_id', 'op' => '=', 'value' => $tgt->get_ID() ], [ 'field' => 'c.name', 'op' => '=', 'value' => 'Orphan', 'sql_op' => 'AND' ] ], [ 'table_joins' => [ "LEFT JOIN pdi_checklist_lookup pcl ON f.pdi_id=pcl.pdi_id", "LEFT JOIN target_checklist tc ON tc.chk_id=pcl.checklist_id", "LEFT JOIN checklist c ON pcl.checklist_id=c.id" ] ]); } $rows = $this->help->execute(); if (is_array($rows) && count($rows) && isset($rows['pdi_id'])) { $rows = [ 0 => $rows ]; } if (is_array($rows) && count($rows) && isset($rows[0])) { foreach ($rows as $row) { $find = new finding($row['tgt_id'], $row['pdi_id'], $row['scan_id'], $row['findings_status_id'], $row['notes'], $row['change_id'], $row['orig_src'], $row['finding_itr']); $find->set_Category($row['cat']); $this->get_Finding_Notes($find); $this->help->select("finding_controls", [ 'ia_control' ], [ [ 'field' => 'tgt_id', 'op' => '=', 'value' => $row['tgt_id'] ], [ 'field' => 'pdi_id', 'op' => '=', 'value' => $row['pdi_id'], 'sql_op' => 'AND' ] ]); $rows2 = $this->help->execute(); if (is_array($rows2) && count($rows2) && isset($rows2['ia_control'])) { $rows2 = [ 0 => $rows2 ]; } if (is_array($rows2) && count($rows2) && isset($rows2[0])) { foreach ($rows2 as $row2) { $find->add_IA_Control($row2['ia_control']); } } else { $this->help->select("ia_controls", [ "CONCAT(`type`, '-', `type_id`) AS 'ia_control'" ], [ [ 'field' => 'pdi_id', 'op' => '=', 'value' => $row['pdi_id'] ] ]); $rows2 = $this->help->execute(); if (is_array($rows2) && count($rows2) && isset($rows2['ia_control'])) { $rows2 = [ 0 => $rows2 ]; } if (is_array($rows2) && count($rows2) && isset($rows2[0])) { foreach ($rows2 as $row2) { $find->add_IA_Control($row2['ia_control']); } } } $ret[$find->get_PDI_ID()] = $find; } } return $ret; } /** * Function to get the findings that are assigned to specific controls * * @param ste $ste * @param proc_ia_controls $ia_ctrl * @param string $status * @return array:finding |NULL */ public function get_Findings_by_Control($ste, $ia_ctrl, $status = null) { if (! is_null($status)) { if ($status == "Open") { $status = " AND (fs.`status` = 'Open' OR fs.`status` = 'Exception')"; } else { $status = " AND fs.`status` = '" . $this->conn->real_escape_string($status) . "'"; } } $sql = <<get_ID()} AND fc.`ia_control` = '{$this->conn->real_escape_string($ia_ctrl->get_Control_ID())}' $status GROUP BY f.`pdi_id` ORDER BY f.`cat`, s.`stig_id` EOQ; if ($res = $this->conn->query($sql)) { $ret = []; while ($row = $res->fetch_assoc()) { $find = new finding($row['tgt_id'], $row['pdi_id'], $row['scan_id'], $row['findings_status'], $row['notes'], $row['change_id'], $row['orig_src'], $row['finding_itr']); $find->set_Category($row['cat']); $sql2 = "SELECT `ia_control` FROM `sagacity`.`finding_controls` WHERE `tgt_id` = {$row['tgt_id']} AND `pdi_id` = {$row['pdi_id']}"; if ($res2 = $this->conn->query($sql2)) { if ($res2->num_rows) { while ($row2 = $res2->fetch_assoc()) { $find->add_IA_Control($row2['ia_control']); } } else { $sql2 = "SELECT CONCAT(`type`, '-', `type_id`) AS 'ia_control' FROM `sagacity`.`ia_controls` WHERE `pdi_id` = " . $row['pdi_id']; if ($res2 = $this->conn->query($sql2)) { while ($row2 = $res2->fetch_assoc()) { $find->add_IA_Control($row2['ia_control']); } } } } $ret[] = $find; } return $ret; } else { Sagacity_Error::sql_handler($sql); error_log($this->conn->error); } return null; } /** * Function to return the host name of the targets that have this finding * * @param ste $ste * @param pdi $pdi * * @return string */ public function get_Affected_Hosts_by_PDI($ste, $pdi) { $sql = <<get_ID()} AND f.`pdi_id` = {$pdi->get_ID()} EOQ; if ($res = $this->conn->query($sql)) { return $res->fetch_assoc()['name']; } return ''; } /** * Function to return stigs that are not in the systems MAC and Classification * * @param ste $ste * * @return array:stig */ public function get_Findings_Not_in_System($ste) { $ret = []; $this->help->create_table("unaccounted_for_findings", [ [ 'field' => 'pdi_id', 'datatype' => 'int(11)', 'option' => 'UNIQUE NOT NULL' ] ]); $this->help->execute(); $sql = "INSERT IGNORE INTO `unaccounted_for_findings` (`pdi_id`) SELECT DISTINCT(f.`pdi_id`) " . "FROM `findings` f JOIN `target` t ON t.`id` = f.`tgt_id` " . "WHERE t.`ste_id` = {$ste->get_ID()}"; $this->conn->real_query($sql); $class = 'cl'; if ($ste->get_System()->get_Classification() == 'Public') { $class = 'pub'; } elseif ($ste->get_System()->get_Classification() == 'Sensitive') { $class = 'sen'; } $sql = "DELETE FROM `unaccounted_for_findings` WHERE `pdi_id` IN (SELECT ia.`pdi_id` " . "FROM `proc_level_type` plt " . "JOIN `ia_controls` ia ON CONCAT(ia.`type`, '-', ia.`type_id`) = plt.`proc_control` " . "WHERE " . "plt.`level` = {$ste->get_System()->get_MAC()} AND " . "plt.`class` = '$class')"; $this->conn->real_query($sql); $sql = "SELECT s.`stig_id` FROM `unaccounted_for_findings` uaf JOIN `stigs` s ON s.`pdi_id` = uaf.`pdi_id`"; if ($res = $this->conn->query($sql)) { while ($row = $res->fetch_assoc()) { $stig = $this->get_Stig($row['stig_id']); if (is_array($stig) && count($stig) && isset($stig[0]) && is_a($stig[0], 'stig')) { $stig = $stig[0]; } if (! preg_match("/^\d{5}$/", $stig->get_ID())) { $ret[] = $stig; } } } return $ret; } /** * Get count of all findings with the status passed in * * @param integer $cat_id * The category we are searching * @param string $status * The status to look for * @param integer $cat * [optional] * The CAT/severity level * @param proc_ia_controls $ctrl * [optional] * A IA control to filter for * * @return integer Returns the number of findings in the category that have the passed in status, severity, and control */ public function get_Finding_Count_By_Status($cat_id, $status, $cat = null, $ctrl = null) { $joins = [ "JOIN target_checklist tc ON t.id = tc.tgt_id", "JOIN pdi_checklist_lookup pcl ON pcl.checklist_id = tc.chk_id", "LEFT JOIN findings f ON f.pdi_id = pcl.pdi_id AND t.id = f.tgt_id", "LEFT JOIN findings_status fs ON fs.id = f.findings_status_id" ]; if (! is_null($ctrl)) { $joins[] = "JOIN finding_controls fc ON fc.tgt_id = f.tgt_id AND fc.pdi_id = f.pdi_id"; } $where = [ [ 'field' => 't.cat_id', 'value' => $cat_id ], [ 'field' => 'fs.status', 'value' => $status, 'sql_op' => 'AND', 'open-paren' => true ] ]; if ($status == 'Not Reviewed') { $where[] = [ 'field' => 'fs.status', 'op' => IS, 'value' => null, 'sql_op' => 'OR', 'close-paren' => true ]; } else { $where[] = [ 'close-paren' => true ]; } if (! is_null($cat) && is_numeric($cat)) { $where[] = [ 'field' => 'f.cat', 'value' => $cat, 'sql_op' => 'AND' ]; } if (! is_null($ctrl) && is_a($ctrl, 'proc_ia_controls')) { $where[] = [ 'field' => 'fc.ia_control', 'value' => $ctrl->get_Control_ID(), 'sql_op' => 'AND' ]; } $field = ($status == 'Not Reviewed' ? "COUNT(DISTINCT(pcl.pdi_id)) AS 'count'" : "COUNT(DISTINCT(f.id)) AS 'count'"); $this->help->select_count("target t", $where, [ 'table_joins' => $joins ]); $this->help->sql = str_replace("COUNT(1) AS 'count'", $field, $this->help->sql); $cnt = $this->help->execute(); $joins = [ "JOIN pdi_checklist_lookup pcl ON pcl.checklist_id = c.id", "JOIN findings f ON f.pdi_id = pcl.pdi_id", "LEFT JOIN findings_status fs ON f.findings_status_id = fs.id", "JOIN target t ON t.id = f.tgt_id" ]; if (! is_null($ctrl) && is_a($ctrl, 'proc_ia_controls')) { $joins[] = "JOIN finding_controls fc ON fc.tgt_id = f.tgt_id AND fc.pdi_id = f.pdi_id"; } $where = [ [ 'field' => 't.cat_id', 'value' => $cat_id ], [ 'field' => 'c.name', 'value' => 'Orphan', 'sql_op' => 'AND' ], [ 'field' => 'fs.status', 'value' => $status, 'sql_op' => 'AND', 'open-paren' => true ] ]; if ($status == 'Not Reviewed') { $where[] = [ 'field' => 'fs.status', 'op' => IS, 'value' => null, 'sql_op' => 'OR', 'close-paren' => true ]; } else { $where[] = [ 'close-paren' => true ]; } if (! is_null($cat) && is_numeric($cat)) { $where[] = [ 'field' => 'f.cat', 'value' => $cat, 'sql_op' => 'AND' ]; } if (! is_null($ctrl) && is_a($ctrl, 'proc_ia_controls')) { $where[] = [ 'field' => 'fc.ia_control', 'value' => $ctrl->get_Control_ID(), 'sql_op' => 'AND' ]; } $this->help->select_count("checklist c", $where, array( 'table_joins' => $joins )); $this->help->sql = str_replace("COUNT(1) AS 'count'", $field, $this->help->sql); $cnt += $this->help->execute(); return $cnt; } /** * Get count of all findings with the status passed in * * @param ste $ste * The category we are searching * @param string $status * The status to look for * @param integer $cat * The CAT/severity level * @param proc_ia_controls $ctrl * A IA control to filter for * * @return integer Returns the number of findings with status */ public function get_STE_Finding_Count_By_Status($ste, $status, $cat = null, $ctrl = null) { $field = ($status == 'Not Reviewed' ? "SELECT COUNT(DISTINCT(pcl.`pdi_id`))" : "SELECT COUNT(DISTINCT(f.`id`))"); $ctrl_join = (! is_null($ctrl) ? "JOIN `sagacity`.`finding_controls` fc ON fc.`tgt_id` = f.`tgt_id` AND fc.`pdi_id` = f.`pdi_id` " : ""); $status_clause = ($status == 'Not Reviewed' ? " OR fs.`status` IS NULL" : ""); $cat_clause = (! is_null($cat) ? "AND f.`cat` = $cat " : ""); $ctrl_clause = (! is_null($ctrl) ? "AND fc.`ia_control` = '{$ctrl->get_Control_ID()}' " : ""); $sql = <<conn->real_escape_string($ste->get_ID())} AND ( fs.`status` = '{$this->conn->real_escape_string($status)}' $status_clause ) $cat_clause $ctrl_clause ) + ( $field FROM `sagacity`.`checklist` c LEFT JOIN `sagacity`.`pdi_checklist_lookup` pcl ON pcl.`checklist_id` = c.`id` LEFT JOIN `sagacity`.`findings` f ON f.`pdi_id` = pcl.`pdi_id` LEFT JOIN `sagacity`.`findings_status` fs ON f.`findings_status_id` = fs.`id` JOIN `sagacity`.`target` t ON t.`id` = f.`tgt_id` $ctrl_join WHERE t.`ste_id` = {$this->conn->real_escape_string($ste->get_ID())} AND c.`name` = 'Orphan' AND ( fs.`status` = '{$this->conn->real_escape_string($status)}' $status_clause ) $cat_clause $ctrl_clause ) AS 'sum_count' EOQ; if ($res = $this->conn->query($sql)) { return $res->fetch_assoc()['sum_count']; } else { return 0; } } /** * Get count of all findings with the status passed in * * @param target $tgt * The target we are searching * @param string $status * The status to look for * @param integer $cat * [optional] * The CAT/severity level * @param proc_ia_controls $ctrl * [optional] * A IA control to filter for * @param array $chk_ids * [optional] * @param boolean $is_orphan * [optional] * * @return integer Returns the number of findings with status 'False Positives' */ public function get_Host_Finding_Count_By_Status($tgt, $status, $cat = null, $ctrl = null, $chk_ids = null, $is_orphan = false) { if (! $is_orphan) { $sql = "SELECT (SELECT COUNT(DISTINCT(pcl.`pdi_id`)) " . "FROM `sagacity`.`target` t " . "LEFT JOIN `sagacity`.`target_checklist` tc ON t.`id` = tc.`tgt_id` " . "LEFT JOIN `sagacity`.`pdi_checklist_lookup` pcl ON pcl.`checklist_id` = tc.`chk_id` " . "LEFT JOIN `sagacity`.`findings` f ON f.`pdi_id` = pcl.`pdi_id` AND t.`id` = f.`tgt_id` " . "LEFT JOIN `sagacity`.`findings_status` fs ON fs.`id` = f.`findings_status_id` " . (! is_null($ctrl) ? "JOIN `sagacity`.`finding_controls` fc ON fc.`tgt_id` = f.`tgt_id` AND fc.`pdi_id` = f.`pdi_id` " : "") . "WHERE t.`id` = {$this->conn->real_escape_string($tgt->get_ID())} AND " . "(fs.`status` = '{$this->conn->real_escape_string($status)}' " . ($status == 'Not Reviewed' ? " OR fs.`status` IS NULL" : "") . ") " . (! is_null($cat) ? "AND f.`cat` = $cat " : "") . (! is_null($ctrl) ? "AND fc.`ia_control` = '{$ctrl->get_Control_ID()}' " : "") . (! is_null($chk_ids) ? "AND pcl.`checklist_id` IN (" . implode(", ", $chk_ids) . ") " : "") . ")"; } else { $sql = "SELECT (SELECT COUNT(DISTINCT(pcl.`pdi_id`)) " . "FROM `sagacity`.`checklist` c " . "LEFT JOIN `sagacity`.`pdi_checklist_lookup` pcl ON pcl.`checklist_id` = c.`id` " . "LEFT JOIN `sagacity`.`findings` f ON f.`pdi_id` = pcl.`pdi_id` " . "LEFT JOIN `sagacity`.`findings_status` fs ON f.`findings_status_id` = fs.`id` " . "JOIN `sagacity`.`target` t ON t.`id` = f.`tgt_id` " . (! is_null($ctrl) ? "JOIN `sagacity`.`finding_controls` fc ON fc.`tgt_id` = f.`tgt_id` AND fc.`pdi_id` = f.`pdi_id` " : "") . "WHERE t.`id` = {$this->conn->real_escape_string($tgt->get_ID())} AND " . "c.`name` = 'Orphan' AND " . "(fs.`status` = '{$this->conn->real_escape_string($status)}' " . ($status == 'Not Reviewed' ? " OR fs.`status` IS NULL" : "") . ") " . (! is_null($cat) ? "AND f.`cat` = $cat " : "") . (! is_null($ctrl) ? "AND fc.`ia_control` = '{$ctrl->get_Control_ID()}' " : "") . ")"; } $sql .= " AS 'sum_count'"; if ($res = $this->conn->query($sql)) { return $res->fetch_assoc()['sum_count']; } else { return 0; } } /** * Function for getting number of targets that have a finding in this control * * @param ia_control $ctrl * @param ste $ste * @param string $status * * @return int */ public function get_Control_Finding_Count($ctrl, $ste, $status, $cat = null) { $sql = "SELECT " . "IFNULL((SELECT COUNT(1) " . "FROM `target` t " . "LEFT JOIN `target_checklist` tc ON t.`id` = tc.`tgt_id` " . "LEFT JOIN `pdi_checklist_lookup` pcl ON pcl.`checklist_id` = tc.`chk_id` " . "LEFT JOIN `findings` f ON f.`pdi_id` = pcl.`pdi_id` AND t.`id` = f.`tgt_id` " . "LEFT JOIN `findings_status` fs ON fs.`id` = f.`findings_status_id` " . "LEFT JOIN `finding_controls` fc ON fc.`tgt_id` = f.`tgt_id` AND fc.`pdi_id` = f.`pdi_id` " . "WHERE " . "(fs.`status` = '$status' " . ($status == 'Open' ? " OR fs.`status` = 'Exception'" : "") . ($status == 'Not a Finding' ? " OR fs.`status` = 'Not Applicable'" : "") . ($status == 'Not Reviewed' ? " OR fs.`status` IS NULL" : "") . ") AND " . (! is_null($cat) ? "f.`cat` = $cat AND " : "") . (! is_null($ctrl) ? "fc.`ia_control` = '{$ctrl->get_Control_ID()}' AND " : "") . "t.`ste_id` = $ste " . "GROUP BY f.`pdi_id`" . "), 0)" . " + " . "IFNULL((SELECT COUNT(1) " . "FROM `checklist` c " . "LEFT JOIN `pdi_checklist_lookup` pcl ON pcl.`checklist_id` = c.`id` " . "LEFT JOIN `findings` f ON f.`pdi_id` = pcl.`pdi_id` " . "LEFT JOIN `findings_status` fs ON f.`findings_status_id` = fs.`id` " . "LEFT JOIN `target` t ON t.`id` = f.`tgt_id` " . "LEFT JOIN `finding_controls` fc ON fc.`tgt_id` = f.`tgt_id` AND fc.`pdi_id` = f.`pdi_id` " . "WHERE " . "c.`name` = 'Orphan' AND " . "(fs.`status` = '$status' " . ($status == 'Open' ? " OR fs.`status` = 'Exception'" : "") . ($status == 'Not a Finding' ? " OR fs.`status` = 'Not Applicable'" : "") . ($status == 'Not Reviewed' ? " OR fs.`status` IS NULL" : "") . ") AND " . (! is_null($cat) ? "f.`cat` = $cat AND " : "") . (! is_null($ctrl) ? "fc.`ia_control` = '{$ctrl->get_Control_ID()}' AND " : "") . "t.`ste_id` = $ste " . "GROUP BY f.`pdi_id`" . "), 0) AS 'sum_count'"; if ($res = $this->conn->query($sql)) { return $res->fetch_assoc()['sum_count']; } else { return 0; } } /** * Getter method to get finding notes * * @param finding $find */ public function get_Finding_Notes(finding &$find) { $this->help->select("analyst_notes", ['note'], [ [ 'field' => 'tgt_id', 'op' => '=', 'value' => $find->get_Tgt_ID() ], [ 'field' => 'pdi_id', 'op' => '=', 'value' => $find->get_PDI_ID(), 'sql_op' => 'AND' ] ]); $row = $this->help->execute(); if(isset($row['note'])) { $find->set_Analyst_Notes($row['note']); } $this->help->select("scan_notes sn", ['sn.note', 'src.name'], [ [ 'field' => 'tgt_id', 'op' => '=', 'value' => $find->get_Tgt_ID() ], [ 'field' => 'pdi_id', 'op' => '=', 'value' => $find->get_PDI_ID(), 'sql_op' => 'AND' ], [ 'field' => 'scan_id', 'op' => '=', 'value' => $find->get_Scan_ID(), 'sql_op' => 'AND' ] ], [ 'table_joins' => [ "JOIN scans s ON sn.scan_id = s.id", "JOIN sources src ON src.id = s.src_id" ] ]); $rows = $this->help->execute(); if(is_array($rows) && count($rows) && isset($rows[0])) { $notes = $find->get_Scanner_Notes(); foreach($rows as $row) { $notes .= $row['name'] . ":\r" . $row['note']; } $find->set_Scanner_Notes($notes); } elseif(isset($rows['note'])) { $notes = $find->get_Scanner_Notes(); $notes .= $rows['name'] . ":\r" . $rows['note']; $find->set_Scanner_Notes($notes); } } /** * Function to determine how pervasive a finding is across all targets * * @todo - FINISH * * @param ste $ste * @param proc_ia_controls $ia_ctrl * @param string $status */ public function get_Finding_Pervasivity_by_Control($ste, $ia_ctrl, $status = null) {} /** * Function to return all the possible finding statuses * * @return array:finding_status */ public function get_Finding_Statuses() { $this->help->select("sagacity.findings_status", null); $rows = $this->help->execute(); $ret = []; if(is_array($rows) && count($rows) && isset($rows[0])) { foreach($rows as $row) { $s = new finding_status(); $s->id = $row['id']; $s->status = $row['status']; $ret[] = $s; } } elseif(is_array($rows) && count($rows) && isset($rows['id'])) { $s = new finding_status(); $s->id = $rows['id']; $s->status = $rows['status']; $ret[] = $s; } return $ret; } /** * Function to compare the findings from two different targets * * @param target $left_tgt * @param target $right_tgt * * @return array */ public function get_Finding_Comparrison($left_tgt, $right_tgt) { $ret = []; $left_sql = "SELECT " . "s.`stig_id`, pcl.`check_contents`, " . "tgt.`id` AS 'tgt_id', tgt.`name` AS 'tgt_name', " . "IF(f.`cat` IS NULL, pdi.`cat`, f.`cat`) AS 'cat', f.`notes`, " . "IF(f.`findings_status_id` IS NULL, 'Not Reviewed', fs.`status`) AS 'finding_status', " . "(SELECT GROUP_CONCAT(fc.`ia_control` SEPARATOR ' ') " . "FROM `sagacity`.`finding_controls` fc " . "WHERE fc.`tgt_id` = f.`tgt_id` AND fc.`pdi_id` = f.`pdi_id`) AS 'finding_ia_controls', " . "(SELECT GROUP_CONCAT(DISTINCT CONCAT(ia.`type`, '-', ia.`type_id`) SEPARATOR ' ') " . "FROM `sagacity`.`ia_controls` ia " . "WHERE ia.`pdi_id` = pcl.`pdi_id`) AS 'ia_controls' " . "FROM `sagacity`.`checklist` chk " . "JOIN `sagacity`.`target_checklist` tc ON tc.`chk_id` = chk.`id` " . "JOIN `sagacity`.`target` tgt ON tgt.`id` = tc.`tgt_id` " . "LEFT JOIN `sagacity`.`pdi_checklist_lookup` pcl ON pcl.`checklist_id` = chk.`id` " . "LEFT JOIN `sagacity`.`findings` f ON f.`pdi_id` = pcl.`pdi_id` AND f.`tgt_id` = tgt.`id` " . "LEFT JOIN `sagacity`.`findings_status` fs ON fs.`id` = f.`findings_status_id` " . "LEFT JOIN `sagacity`.`stigs` s ON s.`pdi_id` = pcl.`pdi_id` " . "LEFT JOIN `sagacity`.`pdi_catalog` pdi ON pdi.`id` = pcl.`pdi_id` " . "WHERE tgt.`id` = {$left_tgt->get_ID()} " . "GROUP BY s.`stig_id`, tgt.`name` " . "ORDER BY s.`stig_id`, FIELD(chk.`type`, 'manual', 'iavm', 'policy', 'benchmark')"; $right_sql = "SELECT " . "s.`stig_id`, pcl.`check_contents`, " . "tgt.`id` AS 'tgt_id', tgt.`name` AS 'tgt_name', " . "IF(f.`cat` IS NULL, pdi.`cat`, f.`cat`) AS 'cat', f.`notes`, " . "IF(f.`findings_status_id` IS NULL, 'Not Reviewed', fs.`status`) AS 'finding_status', " . "(SELECT GROUP_CONCAT(fc.`ia_control` SEPARATOR ' ') " . "FROM `sagacity`.`finding_controls` fc " . "WHERE fc.`tgt_id` = f.`tgt_id` AND fc.`pdi_id` = f.`pdi_id`) AS 'finding_ia_controls', " . "(SELECT GROUP_CONCAT(DISTINCT CONCAT(ia.`type`, '-', ia.`type_id`) SEPARATOR ' ') " . "FROM `sagacity`.`ia_controls` ia " . "WHERE ia.`pdi_id` = pcl.`pdi_id`) AS 'ia_controls' " . "FROM `sagacity`.`checklist` chk " . "JOIN `sagacity`.`target_checklist` tc ON tc.`chk_id` = chk.`id` " . "JOIN `sagacity`.`target` tgt ON tgt.`id` = tc.`tgt_id` " . "LEFT JOIN `sagacity`.`pdi_checklist_lookup` pcl ON pcl.`checklist_id` = chk.`id` " . "LEFT JOIN `sagacity`.`findings` f ON f.`pdi_id` = pcl.`pdi_id` AND f.`tgt_id` = tgt.`id` " . "LEFT JOIN `sagacity`.`findings_status` fs ON fs.`id` = f.`findings_status_id` " . "LEFT JOIN `sagacity`.`stigs` s ON s.`pdi_id` = pcl.`pdi_id` " . "LEFT JOIN `sagacity`.`pdi_catalog` pdi ON pdi.`id` = pcl.`pdi_id` " . "WHERE tgt.`id` = {$right_tgt->get_ID()} " . "GROUP BY s.`stig_id`, tgt.`name` " . "ORDER BY s.`stig_id`, FIELD(chk.`type`, 'manual', 'iavm', 'policy', 'benchmark')"; if ($res = $this->conn->query($left_sql)) { while ($row = $res->fetch_assoc()) { $ret['left'][$row['stig_id']] = array( 'stig_id' => $row['stig_id'], 'cat' => $row['cat'], 'ia_controls' => (! empty($row['finding_ia_controls']) ? $row['finding_ia_controls'] : $row['ia_controls']), 'status' => $row['finding_status'], 'notes' => $row['notes'] ); } } if ($res = $this->conn->query($right_sql)) { while ($row = $res->fetch_assoc()) { $ret['right'][$row['stig_id']] = array( 'stig_id' => $row['stig_id'], 'cat' => $row['cat'], 'ia_controls' => (! empty($row['finding_ia_controls']) ? $row['finding_ia_controls'] : $row['ia_controls']), 'status' => $row['finding_status'], 'notes' => $row['notes'] ); if (! isset($ret['left'][$row['stig_id']])) { $ret['left'][$row['stig_id']] = null; } } } return $ret; } // @TODO - modify to accept finding and array:finding /** * Add a finding * * @param scan $scan * Scan that found this item * @param array:target|target $tgts * Array of targets or a single target that have this finding * @param array|finding $finding_data * Array of data associated with the finding
* [0] => 'stig id'
* [1] => 'vms id'
* [2] => 'category level (I, II, III)'
* [3] => 'ia controls (space delimited)'
* [4] => 'short title'
* [5...n] => 'target status'
* [n+1] => 'notes'
* [n+2] => 'check contents'
* [n+3] => 'missing pdi' */ public function add_Finding($scan, $tgts, $finding_data) { global $cmd; set_time_limit(0); $host_count = 0; $ref = null; if (is_array($tgts)) { $host_count = count($tgts); } else { $host_count ++; } if (preg_match('/\d\.\d+/', $finding_data[0])) { $finding_data[0] = str_pad($finding_data[0], 5, "0"); } $stig_id = $finding_data[0]; $vms_id = preg_replace("/V0+/i", "V-", $finding_data[1]); $cat_lvl = substr_count($finding_data[2], 'I'); $ia_controls = $finding_data[3]; $short_title = $finding_data[4]; $notes = $finding_data[self::FIRST_ECHECKLIST_HOST_COL + $host_count]; if (preg_match('/SV\-.*_rule/', $stig_id)) { $ref = $this->get_SV_Rule(null, $stig_id); } elseif (preg_match('/CVE\-\d{4}\-\d+/', $stig_id)) { $ref = [ 0 => $this->get_CVE($stig_id) ]; } elseif (preg_match('/\d{4}\-[ABT]\-\d+/', $stig_id)) { $ref = [ 0 => $this->get_IAVM($stig_id) ]; } if (is_null($ref) && $stig_id != 'No Reference') { $ref = $this->get_Stig($stig_id); } if (is_null($ref) && $vms_id != 'No Reference') { $ref = $this->get_GoldDisk($vms_id); } if (is_array($ref) && count($ref) && isset($ref[0])) { $ref = $ref[0]; } else { // add a new checklist entry $pdi = new pdi(null, $cat_lvl, 'NOW', $short_title, $short_title); $pdi_id = $this->save_PDI($pdi); $stig = new stig($pdi_id, $stig_id, $short_title); $ref = $stig; $this->add_Stig($stig); $golddisk = new golddisk($pdi_id, $vms_id, $short_title); if ($vms_id != 'No Reference') { $this->save_GoldDisk($golddisk); } } if (is_array($tgts)) { $updated_finding = []; $new_finding = []; $x = 0; foreach ($tgts as $tgt) { switch (strtolower(str_replace('_', ' ', $finding_data[self::FIRST_ECHECKLIST_HOST_COL + $x]))) { case 'not reviewed': case 'not a finding': case 'open': case 'not applicable': case 'no data': case 'exception': case 'false positive': $status = $finding_data[self::FIRST_ECHECKLIST_HOST_COL + $x]; break; default: $status = 'Not Reviewed'; } $current_finding = $this->get_Finding($tgt, $ref); if (is_array($current_finding) && count($current_finding) > 0) { $current_finding = $current_finding[0]; } $current_status = ''; if ($current_finding != null) { $current_status = $current_finding->get_Finding_Status_String(); // $current_source = $current_finding->get(); if ($current_status != $status) { $current_notes = $current_finding->get_Notes(); if (! $current_notes) { $current_finding->set_Notes($notes); } else { if ($notes && stristr($current_notes, $notes) === false) { $current_finding->prepend_Notes($current_notes); } } if (($current_status == 'Open' || $status == 'Open') && ($current_status == 'Not Applicable' || $current_status == 'Not a Finding' || $status == 'Not Applicable' || $status == 'Not a Finding')) { $current_finding->set_Notes("OPEN CONFLICT: $current_status/$status\n$notes"); $current_finding->set_Change_ID(finding::TO_OPEN); } elseif (($current_status == 'Not a Finding' || $current_status == 'Not Applicable') && ($status == 'Not a Finding' || $status == 'Not Applicable')) { $current_finding->set_Notes("NF/NA CONFLICT: $current_status/$status\n$notes"); $current_finding->set_Change_ID(finding::TO_NF); } else { $current_finding->set_Change_ID(finding::NC); } $new_status = $current_finding->get_Deconflicted_Status($status); $new_status_id = $current_finding->get_Finding_Status_ID($new_status); $current_finding->set_Finding_Status($new_status_id); $current_finding->set_Original_Source($scan->get_Source() ->get_Name()); $current_finding->set_Finding_Iteration($current_finding->get_Finding_Iteration() + 1); $current_finding->set_Scan_ID($scan->get_ID()); $current_finding->set_Category($cat_lvl); $current_finding->set_IA_Controls($ia_controls); $updated_finding[] = $current_finding; } else { $current_notes = $current_finding->get_Notes(); if (! $current_notes) { $current_finding->set_Notes($notes); } else { if ($notes && stristr($current_notes, $notes) === false) { $current_finding->set_Notes($current_notes . PHP_EOL . $notes); } } $current_finding->set_Change_ID(finding::NC); $current_finding->set_Original_Source($scan->get_Source() ->get_Name()); $current_finding->set_Finding_Iteration($current_finding->get_Finding_Iteration() + 1); $current_finding->set_Scan_ID($scan->get_ID()); $current_finding->set_Category($cat_lvl); $current_finding->set_IA_Controls($ia_controls); $updated_finding[] = $current_finding; } } else { $new = new finding($tgt->get_ID(), $ref->get_PDI_ID(), $scan->get_ID(), $status, $notes, 0, null, 1); $new->set_Category($cat_lvl); $new->set_IA_Controls($ia_controls); $new_finding[] = $new; } if ($status == 'False Positive') { $match = []; if (preg_match("/\(FP\-([a-zA-Z \-]+)\)/i", $notes, $match)) { $src = $match[1]; // $src = str_replace("_", " ", $match[1]); $sql = "REPLACE INTO `false_positives` (`pdi_id`, `src_id`, `notes`) VALUES (" . $this->conn->real_escape_string($ref->get_PDI_ID()) . ", " . "(SELECT `id` FROM `sources` WHERE `name` = '" . $this->conn->real_escape_string($src) . "'), " . "'Common FP for $src')"; if (! $this->conn->real_query($sql)) { error_log($this->conn->error); Sagacity_Error::sql_handler($sql); } if (isset($cmd['debug'])) { Sagacity_Error::err_handler("Added " . $ref->get_PDI_ID() . " to FP list for $src"); } } } if ($status == 'Exception') { $ste = $this->get_STE($tgt->get_STE_ID())[0]; $sql = "REPLACE INTO `exceptions` (`pdi_id`, `sys_id`, `notes`) VALUES (" . $this->conn->real_escape_string($ref->get_PDI_ID()) . ", " . $this->conn->real_escape_string($ste->get_System() ->get_ID()) . ", " . "'')"; if (! $this->conn->real_query($sql)) { error_log($this->conn->error); Sagacity_Error::sql_handler($sql); } if (isset($cmd['debug'])) { Sagacity_Error::err_handler("Added exception " . $ref->get_PDI_ID()); } } $x ++; } $notes = (isset($current_finding) && is_array($current_finding) && count($current_finding) ? $current_finding->get_Notes() . " " . $notes : $notes); if (isset($updated_finding) && is_array($updated_finding) && count($updated_finding) > 0) { /** @var finding $finding */ foreach ($updated_finding as $finding) { $this->help->update('findings', [ 'scan_id' => $finding->get_Scan_ID(), 'findings_status_id' => $finding->get_Finding_Status(), 'notes' => $finding->get_Notes(), 'change_id' => $finding->get_Change_ID(), 'orig_src' => $finding->get_Original_Source(), 'finding_itr' => $finding->get_Finding_Iteration(), 'cat' => $finding->get_Category() ], [ [ 'field' => 'tgt_id', 'op' => '=', 'value' => $finding->get_Tgt_ID() ], [ 'field' => 'pdi_id', 'op' => '=', 'value' => $finding->get_PDI_ID(), 'sql_op' => 'AND' ] ]); if (! $this->help->execute()) { $this->help->debug(E_ERROR); return false; } $this->help->delete("finding_controls", null, [ [ 'field' => 'tgt_id', 'op' => '=', 'value' => $finding->get_Tgt_ID() ], [ 'field' => 'pdi_id', 'op' => '=', 'value' => $finding->get_PDI_ID(), 'sql_op' => 'AND' ] ]); $this->help->execute(); $params = []; foreach ($finding->get_IA_Controls() as $ia) { $params[] = [ $finding->get_Tgt_ID(), $finding->get_PDI_ID(), $ia ]; } $this->help->extended_insert('finding_controls', ['tgt_id', 'pdi_id', 'ia_control'], $params, true); $this->help->execute(); } } if (isset($new_finding) && count($new_finding) > 0) { $finding_params = []; $finding_control_params = []; foreach ($new_finding as $finding) { /** @var finding $finding */ $finding_params[] = [ $finding->get_Tgt_ID(), $finding->get_PDI_ID(), $finding->get_Scan_ID(), $finding->get_Finding_Status(), $finding->get_Category(), $finding->get_Notes() ]; foreach ($finding->get_IA_Controls() as $ia) { $finding_control_params[] = [ $finding->get_Tgt_ID(), $finding->get_PDI_ID(), $ia ]; } } if(is_array($finding_params) && count($finding_params)) { $this->extended_insert('findings', ['tgt_id', 'pdi_id', 'scan_id', 'findings_status_id', 'cat', 'notes'], $finding_params, true); if($this->help->execute()) { $this->help->extended_insert('finding_controls', ['tgt_id', 'pdi_id', 'ia_control'], $finding_control_params, true); $this->help->execute(); } } } return true; } else { $updated_finding = null; $new_finding = null; switch (strtolower(str_replace('_', ' ', $finding_data[self::FIRST_ECHECKLIST_HOST_COL]))) { case 'not reviewed': case 'not a finding': case 'open': case 'not applicable': case 'no data': case 'exception': case 'false positive': $status = str_replace('_', ' ', $finding_data[self::FIRST_ECHECKLIST_HOST_COL]); break; default: $status = 'Not Reviewed'; } $current_finding = $this->get_Finding($tgts, $ref); if (is_array($current_finding) && count($current_finding) > 0) { $current_finding = current($current_finding); } $current_status = ''; if (is_a($current_finding, 'finding')) { $current_status = $current_finding->get_Finding_Status_String(); if ($current_status != $status) { $current_notes = $current_finding->get_Notes(); if (! $current_notes) { $current_finding->set_Notes($notes); } else { if ($notes && stristr($current_notes, $notes) === false) { $current_finding->set_Notes($current_notes . PHP_EOL . $notes); } } if (($current_status == 'Open' || $status == 'Open') && ($current_status == 'Not Applicable' || $current_status == 'Not a Finding' || $status == 'Not Applicable' || $status == 'Not a Finding')) { $current_finding->set_Notes("OPEN CONFLICT: $current_status/$status\n$notes"); $current_finding->set_Change_ID(finding::TO_OPEN); } elseif (($current_status == 'Not a Finding' || $current_status == 'Not Applicable') && ($status == 'Not a Finding' || $status == 'Not Applicable')) { $current_finding->set_Notes("NF/NA CONFLICT: $current_status/$status\n$notes"); $current_finding->set_Change_ID(finding::TO_NF); } else { $current_finding->set_Change_ID(finding::NC); } $new_status = $current_finding->get_Deconflicted_Status($status); $new_status_id = $current_finding->get_Finding_Status_ID($new_status); $current_finding->set_Finding_Status($new_status_id); $current_finding->set_Original_Source($scan->get_Source() ->get_Name()); $current_finding->set_Finding_Iteration($current_finding->get_Finding_Iteration() + 1); $current_finding->set_Scan_ID($scan->get_ID()); $updated_finding = $current_finding; } else { $current_notes = $current_finding->get_Notes(); if (! $current_notes) { $current_finding->set_Notes($notes); } else { if ($notes && stristr($current_notes, $notes) === false) { $current_finding->set_Notes($current_notes . PHP_EOL . $notes); } } $current_finding->set_Change_ID(finding::NC); $current_finding->set_Original_Source($scan->get_Source() ->get_Name()); $current_finding->set_Finding_Iteration($current_finding->get_Finding_Iteration() + 1); $current_finding->set_Scan_ID($scan->get_ID()); $updated_finding = $current_finding; } } else { $new_finding = new finding($tgts->get_ID(), $ref->get_PDI_ID(), $scan->get_ID(), $status, $notes, 0, null, 1); $new_finding->set_Category($cat_lvl); $new_finding->set_IA_Controls($ia_controls); } $notes = (isset($current_finding) && is_array($current_finding) && count($current_finding) ? $current_finding->get_Notes() . " " . $notes : $notes); if (isset($updated_finding) && ! is_null($updated_finding)) { $this->help->update("findings", [ 'scan_id' => $updated_finding->get_Scan_ID(), 'findings_status_id' => $updated_finding->get_Finding_status(), 'notes' => $updated_finding->get_Notes(), 'change_id' => $updated_finding->get_Change_ID(), 'orig_src' => $updated_finding->get_Original_Source(), 'finding_itr' => $updated_finding->get_Finding_Iteration(), 'cat' => $updated_finding->get_Category() ], [ [ 'field' => 'tgt_id', 'op' => '=', 'value' => $updated_finding->get_Tgt_ID() ], [ 'field' => 'pdi_id', 'op' => '=', 'value' => $updated_finding->get_PDI_ID(), 'sql_op' => 'AND' ] ]); if (! $this->help->execute()) { $this->help->debug(E_ERROR); return false; } $this->help->delete("finding_controls", null, [ [ 'field' => 'tgt_id', 'op' => '=', 'value' => $updated_finding->get_Tgt_ID() ], [ 'field' => 'pdi_id', 'op' => '=', 'value' => $updated_finding->get_PDI_ID(), 'sql_op' => 'AND' ] ]); $this->help->execute(); $params = []; foreach ($updated_finding->get_IA_Controls() as $ia) { $params[] = [ $updated_finding->get_Tgt_ID(), $updated_finding->get_PDI_ID(), $ia ]; } $this->help->extended_insert("finding_controls", ['tgt_id', 'pdi_id', 'ia_control'], $params, true); $this->help->execute(); } if (isset($new_finding) && ! is_null($new_finding)) { $this->help->insert("findings", [ 'tgt_id' => $new_finding->get_Tgt_ID(), 'pdi_id' => $new_finding->get_PDI_ID(), 'scan_id' => $new_finding->get_Scan_ID(), 'findings_status_id' => $new_finding->get_Finding_Status(), 'notes' => $new_finding->get_Notes(), 'cat' => $new_finding->get_Category() ], true); if(!$this->help->execute()) { $this->help->debug(E_ERROR); return false; } $params = []; foreach ($new_finding->get_IA_Controls() as $ia) { $params[] = [ $new_finding->get_Tgt_ID(), $new_finding->get_PDI_ID(), $ia ]; } $this->help->extended_insert('finding_controls', ['tgt_id', 'pdi_id', 'ia_control'], $params, true); $this->help->execute(); } return true; } } /** * Function to add findings to the database * * @param array:finding $updated_findings * Array of findings to update * @param array:finding $added_findings * Array of findings to add to database * * @return boolean Returns TRUE if successful, otherwise FALSE */ public function add_Findings_By_Target($updated_findings, $added_findings) { $fields = [ 'pdi_id', 'tgt_id', 'scan_id', 'findings_status_id', 'notes', 'cat' ]; $ins_arr = []; if (is_array($added_findings) && count($added_findings) && is_a(current($added_findings), 'finding')) { $scan_id = current($added_findings)->get_Scan_ID(); foreach ($added_findings as $finding) { /** @var finding $finding */ $ins_arr[] = [ $finding->get_PDI_ID(), $finding->get_Tgt_ID(), $finding->get_Scan_ID(), $finding->get_Finding_Status(), $finding->get_Notes(), $finding->get_Category() ]; } if (is_array($ins_arr) && count($ins_arr)) { $this->help->extended_insert('findings', $fields, $ins_arr, true); if (! $this->help->execute()) { $this->help->debug(E_ERROR); } } $this->help->sql = "INSERT IGNORE INTO `finding_controls` (`tgt_id`, `pdi_id`, `ia_control`) " . "(SELECT f.`tgt_id`, f.`pdi_id`, " . "(SELECT CONCAT(ia.`type`, '-', ia.`type_id`) " . "FROM `ia_controls` ia " . "WHERE ia.`pdi_id` = f.`pdi_id`) " . "FROM `findings` f " . "WHERE f.`scan_id` = $scan_id)"; $this->help->query_type = db_helper::INSERT; if (! $this->help->execute()) { $this->help->debug(E_WARNING); } $this->help->delete("finding_controls", null, [ [ 'field' => 'ia_control', 'op' => '=', 'value' => '' ], [ 'field' => 'ia_control', 'op' => '=', 'value' => '-', 'sql_op' => 'OR' ], [ 'field' => 'ia_control', 'op' => IS, 'value' => null, 'sql_op' => 'OR' ] ]); $this->help->execute(); } if (is_array($updated_findings) && count($updated_findings) && is_a(current($updated_findings), 'finding')) { $this->help->create_table("tmp_findings", true, [ [ 'field' => 'tgt_id', 'datatype' => 'int(11)' ], [ 'field' => 'pdi_id', 'datatype' => 'int(11)' ], [ 'field' => 'scan_id', 'datatype' => 'int(11)' ], [ 'field' => 'findings_status_id', 'datatype' => 'int(11)' ], [ 'field' => 'change_id', 'datatype' => 'int(11)' ], [ 'field' => 'finding_itr', 'datatype' => 'int(5)' ], [ 'field' => 'cat', 'datatype' => 'int(1)' ], [ 'field' => 'notes', 'datatype' => 'text' ], [ 'field' => 'orig_src', 'datatype' => 'varchar(10)' ] ]); $this->help->execute(); $upd_arr = []; $update_fields = [ 'tgt_id', 'pdi_id', 'scan_id', 'findings_status_id', 'change_id', 'finding_itr', 'cat', 'notes', 'orig_src' ]; foreach ($updated_findings as $finding) { /** @var finding $finding */ $upd_arr[] = [ $finding->get_Tgt_ID(), $finding->get_PDI_ID(), $finding->get_Scan_ID(), $finding->get_Finding_Status(), $finding->get_Change_ID(), $finding->get_Finding_Iteration(), $finding->get_Category(), $finding->get_Notes(), $finding->get_Original_Source() ]; } if (is_array($upd_arr) && count($upd_arr)) { $this->help->extended_insert("tmp_findings", $update_fields, $upd_arr, true); $this->help->execute(); $this->help->extended_update('findings', 'tmp_findings', '`tgt_id`,`pdi_id`', $update_fields); if (! $this->help->execute()) { $this->help->debug(E_ERROR); return false; } } } return true; } /** * Function to update a finding status and notes * * @param finding $find * The finding to update * * @return boolean Returns TRUE if successful, otherwise FALSE */ public function update_Finding($find) { $this->help->replace("sagacity.findings", [ 'tgt_id' => $find->get_Tgt_ID(), 'pdi_id' => $find->get_PDI_ID(), 'scan_id' => $find->get_Scan_ID(), 'findings_status_id' => $find->get_Finding_Status(), 'notes' => $find->get_Notes(), 'cat' => $find->get_Category() ]); if ($this->help->execute()) { $this->help->delete("sagacity.finding_controls", null, [ [ 'field' => 'tgt_id', 'op' => '=', 'value' => $find->get_Tgt_ID() ], [ 'field' => 'pdi_id', 'op' => '=', 'value' => $find->get_PDI_ID(), 'sql_op' => 'AND' ] ]); $ia_arr = []; foreach ($find->get_IA_Controls() as $ia) { $ia_arr[] = [ $find->get_Tgt_ID(), $find->get_PDI_ID(), $ia ]; } if (is_array($ia_arr) && count($ia_arr) && isset($ia_arr[0])) { $this->help->extended_insert("sagacity.finding_controls", [ 'tgt_id', 'pdi_id', 'control_id' ], $ia_arr, true); if (! $this->help->execute()) { $this->help->debug(E_ERROR); return false; } } } else { $this->help->debug(E_ERROR); return false; } return true; } /** * Get count of open category I findings for a target * * @param integer $checklist_id * Checklist ID to query for quantity * @param target $tgt * Target to query * * @return integer Returns the number of findings that are Cat I and with a status of 'Open' for a specific host */ public function get_Host_Open_Cat_1($checklist_id, $tgt) { $this->help->select_count("sagacity.pdi_catalog pdi", array( array( 'field' => 'lu.checklist_id', 'op' => '=', 'value' => $checklist_id ), array( 'field' => 'f.tgt_id', 'op' => '=', 'value' => $tgt->get_ID(), 'sql_op' => 'AND' ), array( 'field' => 'fs.status', 'op' => '=', 'value' => 'Open', 'sql_op' => 'AND' ), array( 'field' => 'pdi.cat', 'op' => '=', 'value' => 1, 'sql_op' => 'AND' ) ), array( 'table_joins' => array( "JOIN sagacity.pdi_checklist_lookup lu ON lu.pdi_id=pdi.id", "JOIN sagacity.findings f ON f.pdi_id=pdi.id", "LEFT JOIN sagacity.stigs s ON s.pdi_id=pdi.id", "LEFT JOIN sagacity.findings_status fs ON fs.id=f.findings_status_id" ) )); return $this->help->execute(); } /** * Get count of not reviewed findings for a target * * @param integer $checklist_id * Checklist ID to query for quantity * @param target $tgt * Target to query * * @return integer Returns the number of findings with a status of 'Not Reviewed' for a specific host */ public function get_Host_Not_Reviewed($checklist_id, $tgt) { $this->help->select_count("sagacity.pdi_catalog pdi", array( array( 'field' => 'lu.checklist_id', 'op' => '=', 'value' => $checklist_id ), array( 'field' => 'f.tgt_id', 'op' => '=', 'value' => $tgt->get_ID(), 'sql_op' => 'AND' ), array( 'field' => 'fs.status', 'op' => '=', 'value' => 'Not Reviewed', 'sql_op' => 'AND' ) ), array( 'table_joins' => array( "JOIN sagacity.pdi_checklist_lookup lu ON lu.pdi_id=pdi.id", "JOIN sagacity.findings f ON f.pdi_id=pdi.id", "LEFT JOIN sagacity.stigs s ON s.pdi_id=pdi.id", "LEFT JOIN sagacity.findings_status fs ON fs.id=f.findings_status_id" ) )); return $this->help->execute(); } // }}} // {{{ GOLDDISK CLASS FUNCTIONS /** * Get GoldDisk data * * @param string $str_VMS_ID * [optional] * The VMS id of the golddisk object (default null) * * @return array:golddisk |NULL * Returns an array of golddisk objects, or null if none found */ public function get_GoldDisk($str_VMS_ID = null) { $ret = []; $where = []; if ($str_VMS_ID != null) { $where[] = array( 'field' => 'vms_id', 'op' => '=', 'value' => $str_VMS_ID ); } $this->help->select("sagacity.golddisk", null, $where); $rows = $this->help->execute(); if (is_array($rows) && count($rows) && isset($rows['pdi_id'])) { $rows = array( 0 => $rows ); } if (is_array($rows) && count($rows) && isset($rows[0])) { foreach ($rows as $row) { $ret[] = new golddisk($row['pdi_id'], $row['vms_id'], $row['short_title']); } } return $ret; } /** * Function for retrieving a VMS using the PDI * * @param integer $pdi_id * The PDI ID of the golddisk to grab * * @return array:golddisk |NULL * Returns an array of golddisk, or null if none found */ public function get_GoldDisk_By_PDI($pdi_id) { $ret = []; $this->help->select("golddisk", null, [ [ 'field' => 'pdi_id', 'op' => '=', 'value' => $pdi_id ] ]); $rows = $this->help->execute(); if (is_array($rows) && count($rows) && isset($rows['pdi_id'])) { $rows = [ 0 => $rows ]; } if (is_array($rows) && count($rows) && isset($rows[0])) { foreach ($rows as $row) { $ret[] = new golddisk($row['pdi_id'], $row['vms_id'], $row['short_title']); } } else { $this->help->debug(E_ERROR); } return $ret; } /** * Function to add GoldDisk to database * * @param golddisk $new_GoldDisk * The golddisk object to add to database * * @return boolean Returns TRUE if successful, otherwise false */ public function save_GoldDisk($new_GoldDisk) { $this->help->insert("sagacity.golddisk", array( 'pdi_id' => $new_GoldDisk->get_PDI_ID(), 'vms_id' => $new_GoldDisk->get_ID(), 'short_title' => $new_GoldDisk->get_Short_Title() ), true); if (! $this->help->execute()) { $this->help->debug(E_ERROR); return false; } return true; } // }}} // {{{ IA_CONTROL CLASS FUNCTIONS /** * Function to get IA control from DB * * @param ia_control $ia * IA Control to retrieve from the database * * @return ia_control|NULL Returns IA_Control object, or null if none found */ public function get_IA_Controls($ia) { $sql = "SELECT `pdi_id`, `type`, `type_id` " . "FROM `sagacity`.`ia_controls` " . "WHERE " . "`pdi_id` = " . $this->conn->real_escape_string($ia->get_PDI_ID()) . " AND " . "`type` = '" . $this->conn->real_escape_string($ia->get_Type()) . "' AND " . "`type_id` = " . $this->conn->real_escape_string($ia->get_Type_ID()); $res = $this->conn->query($sql); if ($res->num_rows > 0) { $row = $res->fetch_assoc(); return new ia_control($row['pdi_id'], $row['type'], $row['type_id']); } return null; } /** * Function for retrieving all the IA controls by mac and classification * * @param system $sys * * @return array:ia_control */ public function get_IA_Controls_By_Mac_Class($sys) { $class = 'cl'; if ($sys->get_Classification() == 'Public') { $class = 'pub'; } elseif ($sys->get_Classification() == 'Sensitive') { $class = 'sen'; } $ret = []; $sql = "SELECT `proc_control` " . "FROM `sagacity`.`proc_level_type` " . "WHERE " . "`level` = " . $sys->get_MAC() . " AND " . "`class` = '$class'"; if ($res = $this->conn->query($sql)) { while ($row = $res->fetch_assoc()) { $ret[] = new ia_control(null, explode('-', $row['proc_control'])[0], explode('-', $row['proc_control'])[1]); } } else { error_log($this->conn->error); Sagacity_Error::sql_handler($sql); } return $ret; } /** * Function for retrieving IA Controls by PDI * * @param integer $pdi_id * PDI ID used to query * * @return array:ia_control |NULL * Returns array of ia_controls associated with a specific PDI, or null if none found */ public function get_IA_Controls_By_PDI($pdi_id) { $sql = "SELECT " . "`pdi_id`, `type`, `type_id` " . "FROM `sagacity`.`ia_controls` " . "WHERE `pdi_id` = " . $this->conn->real_escape_string($pdi_id); if ($res = $this->conn->query($sql)) { $ret = []; while ($row = $res->fetch_assoc()) { $ret[] = new ia_control($row['pdi_id'], $row['type'], $row['type_id']); } return $ret; } else { Sagacity_Error::sql_handler($sql); error_log($this->conn->error); } return null; } /** * Function to get the icon that represents the IA control status * * @param ste $ste * @param proc_ia_controls $ctrl * * @return string */ public function get_IA_Control_Icon($ste, $ctrl) { $cats = $this->get_STE_Cat_List($ste->get_ID()); $total = 0; foreach ($cats as $cat) { $total += $this->get_Finding_Count_By_Status($cat->get_ID(), "Open", null, $ctrl); $total += $this->get_Finding_Count_By_Status($cat->get_ID(), "Exception", null, $ctrl); } if ($total) { return "error.png"; } if (false) { $ctrl = new proc_ia_controls(); } if (empty($ctrl->finding->vul_desc)) { return "exclamation.png"; } elseif (empty($ctrl->finding->mitigations)) { return "exclamation.png"; } return "Under_construction.svg"; } /** * Update an IA control * * @param ia_control|array:ia_control $ia_Controls * Array of IA Controls to update * * @return boolean Returns TRUE if successful, otherwise FALSE */ public function save_IA_Control($ia_Controls) { $params = []; if (is_array($ia_Controls) && count($ia_Controls) && isset($ia_Controls[0]) && is_a($ia_Controls[0], 'ia_control')) { foreach ($ia_Controls as $ia) { $params[] = array( $ia->get_PDI_ID(), $ia->get_Type(), $ia->get_Type_ID() ); } } elseif (is_a($ia_Controls, 'ia_control')) { $params[] = array( $ia_Controls->get_PDI_ID(), $ia_Controls->get_Type(), $ia_Controls->get_Type_ID() ); } else { return false; } $this->help->extended_replace("sagacity.ia_controls", array( 'pdi_id', 'type', 'type_id' ), $params); if (! $this->help->execute()) { $this->help->debug(E_ERROR); return false; } return true; } // }}} // {{{ IAVM CLASS FUNCTIONS /** * Function for retrieving an IAVM * * @param integer|string $iavm_ID * The IAVM ID to look for * * @return iavm|NULL Returns IAVM object, otherwise null if none found */ public function get_IAVM($iavm_ID) { $sql = "SELECT " . "iavm.`noticeId`, iavm.`pdi_id`, iavm.`xmlUrl`, iavm.`htmlUrl`, iavm.`iavmNoticeNumber`, iavm.`title`, " . "iavm.`type`, iavm.`state`, iavm.`lastUpdated`, iavm.`releaseDate`, iavm.`supersedes`, " . "iavm.`executiveSummary`, iavm.`fixAction`, iavm.`note`, iavm.`vulnAppsSysAndCntrmsrs`, " . "iavm.`stigFindingSeverity`, iavm.`knownExploits`, iavm.`file_name` " . "FROM `sagacity`.`iavm_notices` iavm"; if (is_numeric($iavm_ID)) { $sql .= " WHERE iavm.`noticeId` = " . $this->conn->real_escape_string($iavm_ID); } else { $sql .= " WHERE iavm.`iavmNoticeNumber` = '" . $this->conn->real_escape_string($iavm_ID) . "'"; } if ($res = $this->conn->query($sql)) { if (! $res->num_rows) { return null; } $notice_row = $res->fetch_assoc(); $noticeId = $notice_row['noticeId']; $iavm = new iavm($notice_row['noticeId'], $notice_row['pdi_id'], $notice_row['xmlUrl'], $notice_row['htmlUrl'], $notice_row['iavmNoticeNumber'], $notice_row['title'], $notice_row['type'], $notice_row['state'], $notice_row['lastUpdated'], $notice_row['releaseDate'], $notice_row['supersedes'], $notice_row['executiveSummary'], $notice_row['fixAction'], $notice_row['note'], $notice_row['vulnAppsSysAndCntrmsrs'], $notice_row['stigFindingSeverity'], $notice_row['knownExploits']); $sql = "SELECT `cve_id` FROM `sagacity`.`iavm_to_cve` WHERE `noticeId` = " . $notice_row['noticeId']; if ($res2 = $this->conn->query($sql)) { if ($res2->num_rows) { while ($row2 = $res2->fetch_assoc()) { $iavm->add_CVE($row2['cve_id']); } } } $sql = "SELECT `id`, `title`, `url` FROM `sagacity`.`iavm_references` " . "WHERE `iavm_notice_id` = " . $this->conn->real_escape_string($noticeId); if ($res2 = $this->conn->query($sql)) { if ($res2->num_rows) { while ($ref_row = $res2->fetch_assoc()) { $iavm->add_Reference(new iavm_reference($ref_row['id'], $ref_row['title'], $ref_row['url'])); } } } else { Sagacity_Error::sql_handler($sql); error_log($this->conn->error); } $sql = "SELECT `id`, `details` FROM `sagacity`.`iavm_tech_overview` " . "WHERE `iavm_notice_id` = " . $this->conn->real_escape_string($noticeId); if ($res2 = $this->conn->query($sql)) { if ($res2->num_rows) { $to_row = $res2->fetch_assoc(); $to = new iavm_tech_overview($to_row['id'], $to_row['details']); $iavm->set_Tech_Overview($to); } } else { Sagacity_Error::sql_handler($sql); error_log($this->conn->error); } $sql = "SELECT `id`, `type`, `title`, `url` FROM sagacity.iavm_patches " . "WHERE `iavm_notice_id` = " . $this->conn->real_escape_string($noticeId); if ($res2 = $this->conn->query($sql)) { if ($res2->num_rows) { while ($patch_row = $res2->fetch_assoc()) { $iavm->add_Patch(new iavm_patch($patch_row['id'], $patch_row['type'], $patch_row['title'], $patch_row['url'])); } } } else { Sagacity_Error::sql_handler($sql); error_log($this->conn->error); } $sql = "SELECT `header`, `body` FROM `sagacity`.`iavm_mitigations` " . "WHERE `iavm_notice_id` = " . $this->conn->real_escape_string($noticeId); if ($res2 = $this->conn->query($sql)) { if ($res2->num_rows) { $mit_row = $res2->fetch_assoc(); $iavm->set_Mitigation(new iavm_mitigation($mit_row['header'], $mit_row['body'])); } } else { Sagacity_Error::sql_handler($sql); error_log($this->conn->error); } $sql = "SELECT `bid` FROM `sagacity`.`iavm_bids` " . "WHERE `iavm_notice_id` = " . $this->conn->real_escape_string($noticeId); if ($res2 = $this->conn->query($sql)) { if ($res2->num_rows) { while ($bid_row = $res2->fetch_assoc()) { $iavm->add_Bid(new iavm_bid($bid_row['bid'])); } } } return $iavm; } else { Sagacity_Error::sql_handler($sql); error_log($this->conn->error); } return null; } /** * Get IAVM from external data (reference or patch) * * @param string $ext * The external data to search for * * @return iavm|NULL Returns an iavm object if any are found, otherwise NULL */ public function get_IAVM_From_External($ext) { $sql = "SELECT `iavm_notice_id` FROM `sagacity`.`iavm_references` " . "WHERE `title` LIKE '%" . $this->conn->real_escape_string($ext) . "%' OR " . "`url` LIKE '%" . $this->conn->real_escape_string($ext) . "%' " . "GROUP BY `iavm_notice_id` " . "ORDER BY `iavm_notice_id` DESC"; if ($res = $this->conn->query($sql)) { if ($res->num_rows) { $row = $res->fetch_assoc(); $iavm = $this->get_IAVM($row['iavm_notice_id']); return $iavm; } } else { Sagacity_Error::sql_handler($sql); } $sql = "SELECT `iavm_notice_id` FROM `sagacity`.`iavm_patches` " . "WHERE `title` LIKE '%" . $this->conn->real_escape_string($ext) . "%' OR " . "`url` LIKE '%" . $this->conn->real_escape_string($ext) . "%' " . "GROUP BY `iavm_notice_id` " . "ORDER BY `iavm_notice_id` DESC"; if ($res = $this->conn->query($sql)) { if ($res->num_rows) { $row = $res->fetch_assoc(); $iavm = $this->get_IAVM($row['iavm_notice_id']); return $iavm; } } else { Sagacity_Error::sql_handler($sql); } return null; } /** * Method to save IAVM BIDs * * @param iavm $iavm */ public function save_Iavm_Bids($iavm) { $params = []; if (is_array($iavm->get_Bids()) && count($iavm->get_Bids())) { foreach ($iavm->get_Bids() as $bid) { $params[] = [ $iavm->get_Notice_ID(), $bid ]; } } if (count($params)) { $this->help->extended_replace('iavm_bids', [ 'iavm_notice_id', 'bid' ], $params); $this->help->execute(); } } /** * Method to save IAVM mitigations * * @param iavm $iavm */ public function save_Iavm_Mitigation($iavm) { if ($iavm->get_Mitigation()) { $this->help->replace("iavm_mitiagations", [ 'iavm_notice_id' => $iavm->get_Notice_ID(), 'header' => $iavm->get_Mitigation() ->get_Header(), 'body' => $iavm->get_Mitigation() ->get_Text() ]); $this->help->execute(); } } /** * Method to save IAVM patches * * @param iavm $iavm */ public function save_Iavm_Patches($iavm) { $params = []; if (is_array($iavm->get_Patches()) && count($iavm->get_Patches())) { foreach ($iavm->get_Patches() as $patch) { $params[] = [ $iavm->get_Notice_ID(), $patch->get_Type(), $patch->get_Title(), $patch->get_URL() ]; } } if (count($params)) { $this->help->extended_replace("iavm_patches", [ 'iavm_notice_id', 'type', 'title', 'url' ], $params); $this->help->execute(); } } /** * Method to save IAVM references * * @param iavm $iavm */ public function save_Iavm_References($iavm) { $params = []; if (is_array($iavm->get_References()) && count($iavm->get_References())) { foreach ($iavm->get_References() as $ref) { $params[] = [ $iavm->get_Notice_ID(), $ref->get_Title(), $ref->get_URL() ]; } } if (count($params)) { $this->help->extended_replace("iavm_references", [ 'iavm_notice_id', 'title', 'url' ], $params); $this->help->execute(); } } /** * Method to save IAVM tech overview data * * @param iavm $iavm */ public function save_Iavm_Tech_Overview($iavm) { if ($iavm->get_Tech_Overview()) { $this->help->replace("iavm_tech_overview", [ 'iavm_notice_id' => $iavm->get_Notice_ID(), 'details' => $iavm->get_Tech_Overview() ->get_Details() ]); $this->help->execute(); } } /** * Method to save IAVM-to-CVE references * * @param iavm $iavm */ public function save_Iavm_Cves($iavm) { $params = []; if (is_array($iavm->get_CVE()) && count($iavm->get_CVE())) { foreach ($iavm->get_CVE() as $cve) { $params[] = [ $iavm->get_Notice_ID(), $cve ]; } } if (count($params)) { $this->help->extended_replace("iavm_to_cve", [ 'noticeId', 'cve_id' ], $params); $this->help->execute(); } } /** * Function to save IAVMs * * @param iavm $iavm_in * The IAVM to save * * @return boolean Returns TRUE if successful, otherwise FALSE */ public function save_IAVM($iavm_in) { // check to see if the IAVM already exists $db_iavm = $this->get_IAVM($iavm_in->get_Notice_ID()); if (is_null($db_iavm)) { $this->help->insert('iavm_notices', [ 'noticeId' => $iavm_in->get_Notice_ID(), 'pdi_id' => $iavm_in->get_PDI_ID(), 'xmlUrl' => $iavm_in->get_XML_URL(), 'htmlUrl' => $iavm_in->get_HTML_URL(), 'iavmNoticeNumber' => $iavm_in->get_Notice_Number(), 'title' => $iavm_in->get_Title(), 'type' => $iavm_in->get_Type(), 'state' => $iavm_in->get_State(), 'lastUpdate' => $iavm_in->get_Last_Updated_Date(), 'releaseDate' => $iavm_in->get_Release_Date_Date(), 'supersedes' => $iavm_in->get_Supersedes(), 'executiveSummary' => $iavm_in->get_Executive_Summary(), 'fixAction' => $iavm_in->get_Fix_Action(), 'note' => $iavm_in->get_Notes(), 'vulnAppsSysAndCntrmsrs' => $iavm_in->get_Vuln_Apps(), 'stigFindingSeverity' => $iavm_in->get_Stig_Severity(), 'knownExploits' => $iavm_in->get_Known_Exploits() ]); } else { $this->help->update("iavm_notices", [ 'type' => $iavm_in->get_Type(), 'state' => $iavm_in->get_State(), 'lastUpdated' => $iavm_in->get_Last_Updated_Date(), 'supersedes' => $iavm_in->get_Supersedes(), 'executiveSummary' => $iavm_in->get_Executive_Summary(), 'fixAction' => $iavm_in->get_Fix_Action(), 'knownExploits' => $iavm_in->get_Known_Exploits(), 'note' => $iavm_in->get_Notes(), 'vulnAppsSysAndCntrmsrs' => $iavm_in->get_Vuln_Apps() ], [ [ 'field' => 'noticeId', 'op' => '=', 'value' => $iavm_in->get_Notice_ID() ] ]); } if ($this->help->execute()) { $this->save_Iavm_Bids($iavm_in); $this->save_Iavm_Mitigation($iavm_in); $this->save_Iavm_Patches($iavm_in); $this->save_Iavm_References($iavm_in); $this->save_Iavm_Tech_Overview($iavm_in); $this->save_Iavm_Cves($iavm_in); } } // }}} // {{{ INTERFACES CLASS FUNCTIONS /** * Get all interfaces for a target * * @param integer $tgtID * Target ID to get interface information for * * @return array:interfaces|NULL Returns array of interfaces (with ports), or NULL if none found */ public function get_Interfaces($tgtID) { $ret = []; if (! $tgtID) { return []; } $this->help->select("sagacity.interfaces", null, [ [ 'field' => 'tgt_id', 'op' => '=', 'value' => $tgtID ], [ 'field' => 'ipv4', 'op' => '!=', 'value' => '', 'sql_op' => 'AND' ], [ 'field' => 'ipv4', 'op' => IS_NOT, 'value' => null, 'sql_op' => 'AND' ] ]); $rows = $this->help->execute(); if (is_array($rows) && count($rows) && isset($rows['id'])) { $rows = [ 0 => $rows ]; } if (is_array($rows) && count($rows) && isset($rows[0])) { foreach ($rows as $row) { $int = new interfaces($row['id'], $row['tgt_id'], $row['name'], $row['ipv4'], $row['ipv6'], $row['hostname'], $row['fqdn'], $row['description']); $int->set_MAC($row['mac']); $this->help->select("sagacity.get_ports", null, [ [ 'field' => 'int_id', 'op' => '=', 'value' => $row['id'] ] ]); $rows2 = $this->help->execute(); if (is_array($rows2) && count($rows2) && isset($rows2['id'])) { $rows2 = [ 0 => $rows2 ]; } if (is_array($rows2) && count($rows2) && isset($rows2[0])) { foreach ($rows2 as $p) { if ($p['proto'] == 'tcp') { $port = new tcp_ports($p['id'], $p['port'], $p['name'], $p['banner'], $p['notes']); $int->add_TCP_Ports($port); } else { $port = new udp_ports($p['id'], $p['port'], $p['name'], $p['banner'], $p['notes']); $int->add_UDP_Ports($port); } } } if ($row['ipv6']) { $ret[$row['ipv6']] = $int; } else { $ret[$row['ipv4']] = $int; } } } return $ret; } /** * Function to get an interface object using the IP address * * @param integer $tgt_id * @param string $ip * * @return NULL|interfaces */ public function get_Interface_By_IP($tgt_id, $ip) { $this->help->select("sagacity.interfaces", null, array( array( 'field' => 'tgt_id', 'op' => '=', 'value' => $tgt_id ), array( 'field' => 'ipv4', 'op' => '=', 'value' => $ip, 'sql_op' => 'AND', 'open-paren' => true ), array( 'field' => 'ipv6', 'op' => '=', 'value' => $ip, 'sql_op' => 'OR', 'close-paren' => true ) )); $row = $this->help->execute(); if (is_null($row)) { return null; } $int = new interfaces($row['id'], $row['tgt_id'], $row['name'], $row['ipv4'], $row['ipv6'], $row['hostname'], $row['fqdn'], $row['description']); $this->help->select("sagacity.ports_proto_services pps", array( 'pps.id', 'pps.port', 'pps.proto', "IF(ppsl.name != pps.IANA_Name, ppsl.name, pps.IANA_Name) AS 'name'", "IF(ppsl.banner != pps.banner, ppsl.banner, pps.banner) AS 'banner'", "IF(ppsl.notes != pps.notes, ppsl.notes, pps.notes) AS 'notes'" ), array( array( 'field' => 'ppsl.int_id', 'op' => '=', 'value' => $row['id'] ), array( 'field' => 'pps.id', 'op' => IN, 'value' => "(SELECT pps_id FROM sagacity.pps_list WHERE int_id={$row['id']})", 'sql_op' => 'AND' ) ), array( 'table_joins' => array( "LEFT JOIN sagacity.pps_list ppsl ON ppsl.pps_id=pps.id" ) )); $rows2 = $this->help->execute(); if (is_array($rows2) && count($rows2) && isset($rows2['id'])) { $rows2 = array( 0 => $rows2 ); } if (is_array($rows2) && count($rows2) && isset($rows2[0])) { foreach ($rows2 as $port) { $class = "{$port['proto']}_ports"; $method = "add_" . strtoupper($port['proto']) . "_Ports"; $port = new $class($port['id'], $port['port'], $port['name'], $port['banner'], $port['notes']); $int->$method($port); } } return $int; } /** * Return the last ID of the last interface in the database * * @return integer Returns the ID of the last interface that was inserted */ public function get_Last_Interface_ID() { $this->help->select("sagacity.interfaces", array( 'id' ), [], array( 'order' => 'id DESC', 'limit' => 1 )); $row = $this->help->execute(); if (isset($row['id']) && $row['id']) return $row['id']; else return 0; } /** * Save an interface * * @param array|interfaces $req * Associative array of data to insert into database * @param string $action * [optional] * String representing the action to be taken ('insert','update', defaulted to 'insert') * @param integer $tgt_id * [optional] * Integer that the interface info is going to be save to (defaulted to 0) * * @return boolean Returns TRUE if successful, otherwise FALSE */ public function save_Interface($req, $action = 'insert') { if ($action == 'insert') { if (is_array($req)) { $first = array_shift($req); if (! is_a($first, 'interfaces')) { return false; } $req[$first->get_IPv4()] = $first; foreach ($req as $int) { $this->help->insert("sagacity.interfaces", array( 'tgt_id' => $int->get_TGT_ID(), 'ipv4' => $int->get_IPv4(), 'ipv6' => $int->get_IPv6(), 'hostname' => $int->get_Hostname(), 'fqdn' => $int->get_FQDN(), 'description' => $int->get_Description(), 'mac' => $int->get_MAC() ), true); if (! ($int_id = $this->help->execute())) { $this->help->debug(E_ERROR); return false; } $int->set_ID($int_id); $ports = []; if (is_array($int->get_TCP_Ports()) && count($int->get_TCP_Ports())) { foreach ($int->get_TCP_Ports() as $tcp) { $ports[] = array( $int->get_ID(), $tcp->get_ID(), $tcp->get_Banner(), $tcp->get_Notes() ); } } if (is_array($int->get_UDP_Ports()) && count($int->get_UDP_Ports())) { foreach ($int->get_UDP_Ports() as $udp) { $ports[] = array( $int->get_ID(), $udp->get_ID(), $udp->get_Banner(), $udp->get_Notes() ); } } if (count($ports)) { $this->help->extended_insert("pps_list", array( 'int_id', 'pps_id', 'banner', 'notes' ), $ports, true); if (! $this->help->execute()) { $this->help->debug(E_WARNING); } } } } elseif (is_a($req, 'interfaces')) { $this->help->insert("interfaces", array( 'tgt_id' => $req->get_TGT_ID(), 'ipv4' => $req->get_IPv4(), 'ipv6' => $req->get_IPv6(), 'hostname' => $req->get_Hostname(), 'fqdn' => $req->get_FQDN(), 'description' => $req->get_Description(), 'mac' => $req->get_MAC() ), true); if (! ($int_id = $this->help->execute())) { $this->help->debug(E_ERROR); return false; } $req->set_ID($int_id); $ports = []; if (is_array($req->get_TCP_Ports()) && count($req->get_TCP_Ports())) { foreach ($req->get_TCP_Ports() as $tcp) { $ports[] = array( $int->get_ID(), $tcp->get_ID(), $tcp->get_Banner(), $tcp->get_Notes() ); } } if (is_array($req->get_UDP_Ports()) && count($req->get_UDP_Ports())) { foreach ($req->get_UDP_Ports() as $udp) { $ports[] = array( $int->get_ID(), $udp->get_ID(), $udp->get_Banner(), $udp->get_Notes() ); } } if (count($ports)) { $this->help->extended_insert("sagacity.pps_list", array( 'int_id', 'pps_id', 'banner', 'notes' ), $ports, true); if (! $this->help->execute()) { $this->help->debug(E_WARNING); } } } else { $this->help->insert("interfaces", array( 'tgt_id' => $req['tgt_id'], 'ipv4' => $req['ipv4'], 'hostname' => (isset($req['hostname']) ? $req['hostname'] : $req['ipv4']), 'fadn' => (isset($req['fqdn']) ? $req['fqdn'] : $req['fqdn']) ), true); if (! $this->help->execute()) { $this->help->debug(E_ERROR); return false; } } } else { if (isset($req['ip']) && $req['ip'] != null) { foreach ($req['ip'] as $int_id => $val) { if (isset($req['new'][$int_id])) { $this->help->insert("sagacity.interfaces", [ 'tgt_id' => $req['tgt'], 'ipv4' => $req['ip'][$int_id], 'hostname' => $req['hostname'][$int_id], 'fqdn' => $req['fqdn'][$int_id], 'name' => $req['name'][$int_id], 'description' => $req['description'][$int_id] ], true); if (! $this->help->execute()) { $this->help->debug(E_ERROR); return false; } } elseif ($val != 'DELETE') { $this->help->update("sagacity.interfaces", [ 'name' => $req['name'][$int_id], 'ipv4' => $val, 'hostname' => $req['hostname'][$int_id], 'fqdn' => $req['fqdn'][$int_id], 'description' => $req['description'][$int_id] ], [ [ 'field' => 'id', 'op' => '=', 'value' => $int_id ] ]); if ($this->help->execute()) { $tcp_ports = isset($req['tcp_port'][$int_id]) ? $req['tcp_port'][$int_id] : []; $udp_ports = isset($req['udp_port'][$int_id]) ? $req['udp_port'][$int_id] : []; $ports = []; if (is_array($tcp_ports) && count($tcp_ports) > 0) { foreach ($tcp_ports as $port_id => $val2) { $ports[] = [ $int_id, $port_id, $req['iana_name'][$int_id][$port_id], $req['banner'][$int_id][$port_id], $req['notes'][$int_id][$port_id] ]; } } if (is_array($udp_ports) && count($udp_ports) > 0) { foreach ($udp_ports as $int_id => $val2) { $ports[] = [ $int_id, $port_id, $req['iana_name'][$int_id][$port_id], $req['banner'][$int_id][$port_id], $req['notes'][$int_id][$port_id] ]; } } if (count($ports)) { $this->help->extended_insert("pps_list", [ 'int_id', 'pps_id', 'name', 'banner', 'notes' ], $ports, true); if (! $this->help->execute()) { $this->help->debug(E_WARNING); } } } else { $this->help->debug(E_ERROR); return false; } } else { $this->help->delete("sagacity.pps_list", null, array( array( 'field' => 'int_id', 'op' => '=', 'value' => $int_id ) )); $this->help->execute(); $this->help->delete("sagacity.interfaces", null, array( array( 'field' => 'id', 'op' => '=', 'value' => $int_id ) )); $this->help->execute(); } } } } return true; } /** * Function to delete an target interface from the database * * @param int $id * The ID of the interface to be deleted * * @return boolean Returns TRUE if interface successfully deleted, otherwise FALSE */ public function delete_Interface($id) { // delete all associated ports $this->help->delete("sagacity.pps_list", null, [ [ 'field' => 'int_id', 'op' => '=', 'value' => $id ] ]); if (! $this->help->execute()) { $this->help->debug(E_WARNING); return false; } // delete the interface itself $this->help->delete("sagacity.interfaces", null, [ [ 'field' => 'id', 'op' => '=', 'value' => $id ] ]); if (! $this->help->execute()) { $this->help->debug(E_WARNING); return false; } return true; } // {{{ Ports /** * Save the port to the database * * @param interfaces $int * Interface to tie the ports to * @param array:tcp_ports|array:udp_ports $ports * Array of tcp and udp ports that are to be saved * @param string $action * [optional] * Whether or not the ports are to be updated or inserted (defaulted 'insert') * * @return boolean Returns TRUE if successful, otherwise FALSE */ public function save_Ports($int, $ports, $action = 'insert') { $ret = true; $ins_sql = 'REPLACE INTO `sagacity`.`pps_list` (`int_id`,`pps_id`,`name`,`banner`,`notes`) VALUES '; if ($action == 'insert') { foreach ($ports as $port) { $ins_sql .= "(" . $int->get_ID() . ", " . "(SELECT `id` FROM `sagacity`.`ports_proto_services` WHERE `port` = '" . $port->get_Port() . "'" . " AND `proto` = '" . (is_a($port, 'tcp_ports') ? 'tcp' : 'udp') . "' " . " AND `notes` NOT LIKE '%historic%' LIMIT 1), " . "'" . $this->conn->real_escape_string($port->get_IANA_Name()) . "', " . "'" . $this->conn->real_escape_string($port->get_Banner()) . "', " . "'" . $this->conn->real_escape_string($port->get_Notes()) . "'), "; } $ins_sql = substr($ins_sql, 0, - 1); if (strlen($ins_sql) > 84) { if (! $this->conn->real_query($ins_sql)) { Sagacity_Error::sql_handler($ins_sql); error_log($this->conn->error); $ret = false; } } } else {} return $ret; } // }}} // {{{ TCP_PORTS CLASS FUNCTIONS /** * Get TCP port data * * @param integer $port_number * [optional] * Port number to retrieve from database * * @return array:tcp_ports|NULL Returns array of tcp ports, or null if none found */ public function get_TCP_Ports($port_number = null) { $ret = []; $where = [ [ 'field' => 'proto', 'op' => '=', 'value' => 'tcp' ] ]; if (! is_null($port_number)) { $where[] = [ 'field' => 'port', 'op' => '=', 'value' => $port_number, 'sql_op' => 'AND' ]; } $this->help->select("ports_proto_services", [ 'id', 'port', 'iana_Name', 'banner', 'notes' ], $where); $rows = $this->help->execute(); if (isset($rows['id'])) { $rows = [ 0 => $rows ]; } if (is_array($rows) && count($rows) && isset($rows[0])) { foreach ($rows as $row) { $ret[] = new tcp_ports($row['id'], $row['port'], $row['iana_Name'], $row['banner'], $row['notes']); } return $ret; } return null; } // }}} // {{{ UDP_PORTS CLASS FUNCTIONS /** * Get UDP port data * * @param integer $port_number * Port number to retrieve from database * * @return array:udp_ports|NULL Returns array of udp ports, or null if none found */ public function get_UDP_Ports($port_number = null) { $ret = []; $where = [ [ 'field' => 'proto', 'op' => '=', 'value' => 'udp' ] ]; if (! is_null($port_number)) { $where[] = [ 'field' => 'port', 'op' => '=', 'value' => $port_number, 'sql_op' => 'AND' ]; } $this->help->select("ports_proto_services", [ 'id', 'port', 'iana_Name', 'banner', 'notes' ], $where); $rows = $this->help->execute(); if (is_array($rows) && count($rows) && isset($rows['id'])) { $rows = [ 0 => $rows ]; } if (is_array($rows) && count($rows) && isset($rows[0])) { foreach ($rows as $row) { $ret[] = new udp_ports($row['id'], $row['port'], $row['iana_Name'], $row['banner'], $row['notes']); } return $ret; } return null; } // }}} // {{{ NESSUS CLASS FUNCTIONS /** * Function to retrieve a nessus object * * @param string $nessus_id * Nessus ID of the object you want * * @return nessus|NULL Returns nessus object and associated references, or null if none found */ public function get_Nessus($nessus_id) { $this->help->select("nessus_plugins np", null, [ [ 'field' => 'np.plugin_id', 'op' => '=', 'value' => $nessus_id ] ], [ 'table_joins' => [ "LEFT JOIN sagacity.nessus n ON n.nessus_id = np.plugin_id" ] ]); $row = $this->help->execute(); if (is_array($row) && count($row) && isset($row['plugin_id'])) { $nessus = new nessus($row['pdi_id'], $row['plugin_id']); $nessus->set_Name($row['name']); $nessus->set_Copyright($row['copyright']); $nessus->set_Version($row['version']); $nessus->set_FileDate($row['file_date']); $nessus->set_FileName($row['file_name']); $this->help->select("sagacity.nessus_meta", null, [ [ 'field' => 'plugin_id', 'op' => '=', 'value' => $row['plugin_id'] ] ]); if ($rows = $this->help->execute()) { if (is_array($rows) && count($rows)) { foreach ($rows as $row) { $nessus->add_Reference($row['type'], $row['val']); } } } return $nessus; } return null; } /** * Update Nessus data * * @param array:nessus|nessus $nessus * Nessus object to update * * @return boolean Returns TRUE if successful, otherwise FALSE */ public function save_Nessus($nessus) { $nessus_arr = []; $meta_arr = []; $plugins_arr = []; $update_arr = []; $nessus_fields = array( 'pdi_id', 'nessus_id' ); $meta_fields = array( 'plugin_id', 'type', 'val' ); $plugins_fields = array( 'plugin_id', 'name', 'copyright', 'version', 'file_name', 'file_date' ); $this->help->create_table("tmp_nessus", true, array( array( 'field' => 'plugin_id', 'datatype' => 'int(11)', 'options' => 'primary key' ), array( 'field' => 'name', 'datatype' => 'varchar(255)' ), array( 'field' => 'copyright', 'datatype' => 'varchar(255)' ), array( 'field' => 'version', 'datatype' => 'varchar(45)' ), array( 'field' => 'file_name', 'datatype' => 'varchar(100)' ), array( 'field' => 'file_date', 'datatype' => 'int(11)' ) )); $this->help->execute(); if (is_a($nessus, 'nessus')) { $nessus = array( 0 => $nessus ); } if (is_array($nessus)) { $refs = []; foreach ($nessus as $plug) { $db_nessus = $this->get_Nessus($plug->get_Nessus_ID()); if (is_null($db_nessus)) { if (! $plug->get_PDI_ID()) { $pdi = new pdi(null, $plug->get_Category(), $plug->get_FileDate_Date()); $pdi->set_Short_Title($plug->get_Name()); $pdi->set_Group_Title($plug->get_Name()); $pdi->set_Description($plug->get_Description()); $pdi->set_ID($this->save_PDI($pdi)); $plug->set_PDI_ID($pdi->get_ID()); $stig = new stig($plug->get_PDI_ID(), $plug->get_Nessus_ID(), $plug->get_Name()); $this->add_Stig($stig); } $plugins_arr[] = [ $plug->get_Nessus_ID(), $plug->get_Name(), $plug->get_Copyright(), $plug->get_Version(), $plug->get_FileName(), $plug->get_FileDate() ]; $refs = $plug->get_Reference(); } else { $update_arr[] = [ $plug->get_Nessus_ID(), $plug->get_Name(), $plug->get_Copyright(), $plug->get_Version(), $plug->get_FileName(), $plug->get_FileDate() ]; $refs = $plug->compare_References($db_nessus); } $nessus_arr[] = [ $plug->get_PDI_ID(), $plug->get_Nessus_ID() ]; if (is_array($refs) && count($refs)) { foreach ($refs as $type => $ref) { foreach ($ref as $val) { $meta_arr[] = array( $plug->get_Nessus_ID(), $type, $val ); } } } } if (is_array($plugins_arr) && count($plugins_arr)) { $this->help->extended_insert("nessus_plugins", $plugins_fields, $plugins_arr, true); if (! $this->help->execute()) { $this->help->debug(E_ERROR); } } if (is_array($update_arr) && count($update_arr)) { $this->help->extended_insert("tmp_nessus", $plugins_fields, $update_arr, true); if (! $this->help->execute()) { $this->help->debug(E_ERROR); } $this->help->extended_update("nessus_plugins", "tmp_nessus", "plugin_id", $plugins_fields); if (! $this->help->execute()) { $this->help->debug(E_ERROR); } } if (is_array($nessus_arr) && count($nessus_arr)) { $this->help->extended_insert("nessus", $nessus_fields, $nessus_arr, true); if (! $this->help->execute()) { $this->help->debug(E_ERROR); } } if (is_array($meta_arr) && count($meta_arr)) { $this->help->extended_insert("nessus_meta", $meta_fields, $meta_arr, true); if (! $this->help->execute()) { $this->help->debug(E_ERROR); } } } else { return false; } return true; } // }}} // {{{ OVAL CLASS FUNCTIONS /** * Getter function for oval * * @param string $oval_id * Oval ID to retrieve from database * * @return oval|NULL Returns oval object, or null if none found */ public function get_Oval($oval_id) { $oval = null; $sql = "SELECT " . "`pdi_id`, `oval_id`, `title`, `desc`, `platform`, `ext_def`, `ext_def_op` " . "FROM sagacity.oval " . "WHERE `oval_id` = '" . $this->conn->real_escape_string($oval_id) . "'"; if ($res = $this->conn->query($sql)) { $row = $res->fetch_assoc(); $oval = new oval($row['pdi_id'], $row['oval_id'], $row['title'], $row['desc'], $row['platform'], $row['ext_def'], $row['ext_def_op']); $sql = "SELECT" . "`oval_id`, `source`, `url`, `ref_id` " . "FROM sagacity.oval_ref " . "WHERE `oval_id` = '" . $this->conn->real_escape_string($row['oval_id']) . "'"; if ($res2 = $this->conn->query($sql)) { while ($row2 = $res2->fetch_assoc()) { $ref = new oval_ref($row2['oval_id'], $row2['source'], $row2['url'], $row2['ref_id']); $oval->add_Reference($ref); } } else { Sagacity_Error::sql_handler($sql); error_log($this->conn->error); } return $oval; } else { Sagacity_Error::sql_handler($sql); error_log($this->conn->error); } return null; } /** * Function to create a OVAL xml file to import into SCC * * @param string $os * Operating system version to query * * @return string Returns string representing XML */ public function get_OS_Oval($os) { $xmlns = "xmlns = 'http://oval.mitre.org/XMLSchema/oval-definitions-5#windows'"; // ------------------------------ Start ----------------------------- // create temporary db table to combine all OVAL checks marked 'M' and not 'M' $tmp_sql = "CREATE TEMPORARY TABLE `tmp_oval` SELECT " . "pdi.`id`, o.`oval_id`, s.`stig_id`, vms.`vms_id`, pdi.`check_contents`, pdi.`short_title` " . "FROM `pdi_catalog` AS pdi " . "LEFT JOIN `oval` AS o ON pdi.`id` = o.`pdi_id` " . "LEFT JOIN `stigs` AS s ON pdi.`id` = s.`pdi_id` " . "LEFT JOIN `golddisk` AS vms ON pdi.`id` = vms.`pdi_id` " . "LEFT JOIN `pdi_checklist_lookup` AS lookup ON pdi.`id` = lookup.`pdi_id` " . "LEFT JOIN `checklist` AS c ON lookup.`checklist_id` = c.`id` " . "LEFT JOIN `software` AS sft ON sft.`id` = c.`sw_id` " . "WHERE " . "o.`oval_id` = 'M' AND " . "pdi.`check_contents` LIKE '%Registry Hive%' AND " . "sft.`man` = 'MS' AND " . "sft.`name` = 'Windows' AND " . "sft.`ver` = '$os' " . "GROUP BY `stig_id`"; $this->conn->real_query($tmp_sql); // delete rows in temporary table from other checklist that cannot designated as manual $del_sql = "DELETE FROM tmp_oval " . "WHERE `id` IN (" . "SELECT pdi.`id` " . "FROM `pdi_catalog` AS pdi " . "LEFT JOIN `oval` AS o ON pdi.`id` = o.`pdi_id` " . "LEFT JOIN `stigs` AS s ON pdi.`id` = s.`pdi_id` " . "LEFT JOIN `golddisk` AS vms ON pdi.`id` = vms.`pdi_id` " . "LEFT JOIN `pdi_checklist_lookup` AS lookup ON pdi.`id` = lookup.`pdi_id` " . "LEFT JOIN `checklist` AS c ON lookup.`checklist_id` = c.`id` " . "LEFT JOIN `software` AS sft ON sft.`id` = c.`sw_id` " . "WHERE " . "o.`oval_id` != 'M' AND " . "pdi.`check_contents` REGEXP 'Registry Hive' AND " . "sft.`man` = 'MS' AND " . "sft.`name` = 'Windows' AND " . "sft.`ver` = '$os' " . "GROUP BY pdi.`id`)"; $this->conn->real_query($del_sql); $sql = "SELECT " . "`id`, `oval_id`, `stig_id`, `vms_id`, `check_contents`, `short_title` " . "FROM `tmp_oval`"; if ($sth = $this->conn->prepare($sql)) { if ($sth->execute()) { $pdi_id = 0; $oval_id = ''; $stig_id = ''; $vms_id = ''; $check_contents = ''; $short_title = ''; $x = 0; $sth->bind_result($pdi_id, $oval_id, $stig_id, $vms_id, $check_contents, $short_title); // oval_file xml validation check $root = ''; // declaring string variables and setting values to empty $def = ''; $tst = ''; $obj = ''; $ste = ''; // generator node in xml format $date = new DateTime(); // insert date and time when file completed $gen = "DISA FSO5.3" . $date->format(DATE_W3C) . ""; while ($sth->fetch()) { $x ++; $match = []; preg_match('/Registry Hive: +(\S*)/', $check_contents, $match); $hive = $match[1]; preg_match('/(Subkey|Path|Registry Path): +(\\\)?(.*)/', $check_contents, $match); $path = is_array($match) && count($match) > 3 ? $match[3] : "STIG ID: $stig_id" . PHP_EOL; preg_match('/Value Name: +(\S*)/', $check_contents, $match); $name = is_array($match) && count($match) > 1 ? $match[1] : ''; if (is_array($match) && count($match) == 2) { $c_operator = 'AND'; $c_count = 1; } preg_match('/Type: +(\S*)/', $check_contents, $match); $type = is_array($match) && count($match) > 0 ? $match[1] : "PDI ID: $pdi_id" . PHP_EOL; preg_match('/Value: +(\S*)/', $check_contents, $match); $value = is_array($match) && count($match) > 0 ? $match[1] : "PDI ID: $pdi_id" . PHP_EOL; if (strpos($type, "PDI ID: " . $pdi_id) !== false) { // print "$pdi_id, $vms_id this VMS item cannot be automated".PHP_EOL.PHP_EOL; continue; } // variables set for various xml nodes $def_id = 'oval:smc.gpea.windows:def:' . $pdi_id; $tst_id = 'oval:smc.gpea.windows:tst:' . $pdi_id . "00"; $ste_id = 'oval:smc.gpea.windows:ste:' . $pdi_id . "00"; $obj_id = 'oval:smc.gpea.windows:obj:' . $pdi_id . "00"; $var_id = 'oval:smc.gpea.windows:var:' . $pdi_id . "00"; $def_class = 'compliance'; $m_family = 'windows'; $aft_platform = 'Microsoft Windows ' . $os; $tst_chk_existence = ($c_count == 1 ? "all_exist" : ''); // definitions node in xml format $def .= "" . "" . "$short_title" . "" . "$aft_platform" . "" . "" . "$short_title" . "" . ""; if ($c_count == 1) { $def .= "" . PHP_EOL; } $def .= ""; $tst .= "" . "" . "" . ""; if (substr($path, - 1) != "\\") { $path .= "\\"; } $obj .= "" . "" . strtoupper($hive) . "" . "$path" . "$name" . ""; $ste .= "" . "" . strtolower($type) . "" . "$value" . ""; } $sth->close(); } } // ------------------------------ End ----------------------------- // ------------------------------ Start ----------------------------- $tmp_sql = "CREATE TEMPORARY TABLE `tmp_oval` SELECT " . "pdi.`id`,o.`oval_id`,s.`stig_id`,vms.`vms_id`,pdi.`check_contents`,pdi.`short_title` " . "FROM `sagacity`.`pdi_catalog` AS pdi " . "LEFT JOIN `sagacity`.`oval` AS o ON pdi.`id`=o.`pdi_id` " . "LEFT JOIN `sagacity`.`stigs` AS s ON pdi.`id`=s.`pdi_id` " . "LEFT JOIN `sagacity`.`golddisk` AS vms ON pdi.`id`=vms.`pdi_id` " . "LEFT JOIN `sagacity`.`pdi_checklist_lookup` AS lookup ON pdi.`id`=lookup.`pdi_id` " . "LEFT JOIN `sagacity`.`checklist` AS c ON lookup.`checklist_id`=c.`id` " . "LEFT JOIN `sagacity`.`software` AS sft ON sft.`id`=c.`sw_id` " . "WHERE " . "o.`oval_id`='M' AND " . "pdi.`check_contents` LIKE '%AuditPol%' AND " . "sft.`man`='MS' AND " . "sft.`name`='Windows' AND " . "sft.`ver`='$os' " . "GROUP BY `stig_id`"; $this->conn->real_query($tmp_sql); $del_sql = "DELETE FROM tmp_oval " . "WHERE `id` IN (" . "SELECT pdi.`id` " . "FROM `sagacity`.`pdi_catalog` AS pdi " . "LEFT JOIN `sagacity`.`oval` AS o ON pdi.`id`=o.`pdi_id` " . "LEFT JOIN `sagacity`.`stigs` AS s ON pdi.`id`=s.`pdi_id` " . "LEFT JOIN `sagacity`.`golddisk` AS vms ON pdi.`id`=vms.`pdi_id` " . "LEFT JOIN `sagacity`.`pdi_checklist_lookup` AS lookup ON pdi.`id`=lookup.`pdi_id` " . "LEFT JOIN `sagacity`.`checklist` AS c ON lookup.`checklist_id`=c.`id` " . "LEFT JOIN `sagacity`.`software` AS sft ON sft.`id`=c.`sw_id` " . "WHERE " . "o.`oval_id`!='M' AND " . "pdi.`check_contents` REGEXP 'AuditPol' AND " . "sft.`man`='MS' AND " . "sft.`name`='Windows' AND " . "sft.`ver`='$os' " . "GROUP BY pdi.`id`)"; $this->conn->real_query($del_sql); $sql = "SELECT " . "`id`,`oval_id`,`stig_id`,`vms_id`,`check_contents`,`short_title` " . "FROM `tmp_oval`"; if ($sth = $this->conn->prepare($sql)) { if ($sth->execute()) { $pdi_id = 0; $oval_id = ''; $stig_id = ''; $vms_id = ''; $check_contents = ''; $short_title = ''; $x = 0; $sth->bind_result($pdi_id, $oval_id, $stig_id, $vms_id, $check_contents, $short_title); $sth->store_result(); if ($sth->num_rows > 0) { $obj .= ""; } while ($sth->fetch()) { $tst_id = "oval:smc.gpea.windows:tst:" . $pdi_id . "00"; $ste_id = "oval:smc.gpea.windows:ste:" . $pdi_id . "00"; $arrow_idx = strpos($check_contents, '->') + 3; $dash_idx = strpos($check_contents, ' - '); $subcat = substr($check_contents, $arrow_idx, $dash_idx - $arrow_idx); $tag = str_replace(' ', '_', strtolower($subcat)); $audit = substr($check_contents, $dash_idx + 3); $ste .= "" . "<$tag datatype='string'>" . ($audit == 'Failure' ? 'AUDIT_FAILURE' : 'AUDIT_SUCCESS') . "" . ""; $tst .= "" . "" . "" . ""; } } else { error_log($sth->error); } } else { error_log($this->conn->error); } // ------------------------------ End ----------------------------- // ------------------------------ Start ----------------------------- // ------------------------------ End ----------------------------- $xml_string = $root . "$gen$def$tst$obj$ste"; return $xml_string; } /** * Function to get oval constant data from database * * @param string $oval_id * Oval ID to get constant data for * * @return array Returns array of constant ID and value */ public function get_Oval_Const($oval_id) { $sql = "SELECT `const_id`,`value` FROM `sagacity`.`ov_convert` WHERE `var_id`=" . $oval_id; if ($res = $this->conn->query($sql)) { $vals = []; while ($row = $res->fetch_assoc()) { $vals[] = $row['value']; } return array( 'const_id' => $row['const_id'], 'values' => $vals ); } else { Sagacity_Error::sql_handler($sql); error_log($this->conn->error); return null; } } /** * Function to add an Oval * * @param oval $oval * Oval to add to database * * @return boolean Returns TRUE if successful, otherwise FALSE */ public function add_Oval($oval) { $this->help->insert("sagacity.oval", array( 'pdi_id' => $oval->get_PDI_ID(), 'oval_id' => $oval->get_Oval_ID(), 'title' => $oval->get_Title(), 'desc' => $oval->get_Description(), 'platform' => $oval->get_Platform(), 'ext_def' => $oval->get_External_Definition(), 'ext_def_op' => $oval->get_External_Definition_Operator() ), true); if (! $this->help->execute()) { $this->help->debug(E_ERROR); return false; } return true; } /** * Function to save oval data * * @param oval $oval_in * Oval to update database * * @return boolean Returns TRUE if successful, otherwise FALSE */ public function save_Oval($oval_in) { $this->help->replace("sagacity.oval", array( 'pdi_id' => $oval_in->get_PDI_ID(), 'oval_id' => $oval_in->get_Oval_ID(), 'title' => $oval_in->get_Title(), 'desc' => $oval_in->get_Description(), 'platform' => $oval_in->get_Platform(), 'ext_def' => $oval_in->get_External_Definition(), 'ext_def_op' => $oval_in->get_External_Definition_Operator() )); if (! $this->help->execute()) { $this->help->debug(E_ERROR); return false; } return true; } // }}} // {{{ PDI_CATALOG CLASS FUNCTIONS /** * Function to retrieve a PDI from the database * * @param integer $pdi_id * PDI ID to get from database * @param integer $chk_id * Checklist ID to filter on * * @return pdi|NULL Returns PDI object, or null if none found */ public function get_PDI($pdi_id, $chk_id = null) { $pdi = null; $this->help->select("sagacity.pdi_catalog p", null, [ [ 'field' => 'p.id', 'op' => '=', 'value' => $pdi_id ] ]); $row = $this->help->execute(); if (is_array($row) && count($row) && isset($row['id'])) { $pdi = new pdi($row['id'], $row['cat'], $row['update']); $pdi->set_Short_Title($row['short_title']); $pdi->set_Check_Contents($row['check_contents']); if (! is_null($chk_id)) { $this->help->select("sagacity.pdi_checklist_lookup", null, [ [ 'field' => 'pdi_id', 'op' => '=', 'value' => $pdi_id ], [ 'field' => 'checklist_id', 'op' => '=', 'value' => $chk_id, 'sql_op' => 'AND' ] ]); $row = $this->help->execute(); if (is_array($row) && count($row) && isset($row['pdi_id'])) { $pdi->set_Short_Title($row['short_title']); $pdi->set_Group_Title($row['group_title']); $pdi->set_Check_Contents($row['check_contents']); $pdi->set_Fix_Text($row['fix_text']); } } } return $pdi; } /** * Function to get pdi catalog item from database * * @param integer $pdi_id * Get PDI Catalog entry from database using this ID * * @return array|NULL Returns associative array with record, or null if none found */ public function get_PDI_Catalog($pdi_id) { $this->help->select("sagacity.pdi", null, array( array( 'field' => 'pdi_id', 'op' => '=', 'value' => $pdi_id ) )); return $this->help->execute(); } /** * Function to attempt to match text * * @param pdi $pdi * PDI to match in database * @param nessus $nessus * Nessus to match in database * @param cve $cve * CVE to match in database * @param iavm $iavm * IAVM to match in database * * @return array|NULL Returns array of possible matches, or null if none found */ public function get_Matching_PDIs($pdi, $nessus, $cve, $iavm) { /* * $string = ''; * * if (!is_null($nessus)) { * $string = $nessus->get_Name() . ' ' . $nessus->get_Description() . ' ' . $nessus->get_Summary(); * } * elseif (!is_null($cve)) { * $string = $cve->get_Description(); * } * elseif (!is_null($iavm)) { * $string = $iavm->get_Title() . ' ' . $iavm->get_Executive_Summary(); * } * * foreach ($this->DISALLOWED as $word) { * $string = preg_replace("/\s" . $word . "\s/i", " ", $string); * } * * $sql = "SELECT " . * "MATCH(pdi.`short_title`,pdi.`description`,pdi.`check_content`) " . * "AGAINST('" . $this->conn->real_escape_string($string) . "' IN NATURAL LANGUAGE MODE) AS 'score'," . * "pdi.`id`,pdi.`short_title`,pdi.`description`,pdi.`check_content` " . * "FROM `sagacity`.`pdi_catalog` pdi " . * "GROUP BY pdi.`id`,`score` " . * "HAVING `score` > 10 " . * "ORDER BY `score` DESC " . * "LIMIT 0, 5"; * * $ret = []; * * if ($res = $this->conn->query($sql)) { * while ($row = $res->fetch_assoc()) { * $ret[] = array( * 'score' => number_format($row['score'], 3), * 'pdi_id' => $row['id'], * 'title' => $row['short_title'], * 'check_content' => $row['check_content'], * 'desc' => $row['description'] * ); * } * * return $ret; * } * else { * Sagacity_Error::sql_handler($sql); * error_log($this->conn->error); * } */ return null; } /** * Function to try and find a PDI * * @param array $data_in * An array of a type and value to search for. This will primarily be intended for types that don't have a readily available link to a PDI (nessus, retina, CVE, IAVM, etc). * * @return integer Returns the PDI id of the matching entry, or 0 if none found */ public function find_PDI($data_in) { if ($data_in['type'] == 'nessus') { $nessus = $this->get_Nessus($data_in['value']); if (is_null($nessus)) { return 0; } if ($nessus->get_PDI_ID()) { return $nessus->get_PDI_ID(); } else { $cves = $nessus->get_Reference_By_Type('cve'); foreach ($cves as $cve_num) { $cve = $this->get_CVE($cve_num); if ($cve->get_PDI_ID()) { return $cve->get_PDI_ID(); } $sql = "SELECT `noticeId` FROM `sagacity`.`iavm_to_cve` WHERE `cve_id`='" . $this->conn->real_escape_string($cve_num) . "'"; if ($res = $this->conn->query($sql)) { while ($row = $res->fetch_assoc()) { $iavm = $this->get_IAVM($row['noticeId']); if (! is_null($iavm)) { return $iavm->get_PDI_ID(); } } } else { Sagacity_Error::sql_handler($sql); error_log($this->conn->error); } } $bids = $nessus->get_Reference_By_Type('bid'); foreach ($bids as $bid_num) { $sql = "SELECT iavm.`pdi_id` " . "FROM `sagacity`.`nessus_refs` nr " . "JOIN `sagacity`.`iavm_bids` ib ON ib.`bid`=nr.`val` " . "JOIN `sagacity`.`iavm_notices` iavm ON iavm.`noticeId`=ib.`iavm_notice_id` " . "WHERE " . "nr.`type`='bid' AND " . "nr.`val`=" . $this->conn->real_escape_string($bid_num) . " AND " . "nr.`plugin_id`=" . $this->conn->real_escape_string($nessus->get_Nessus_ID()); if ($res = $this->conn->query($sql)) { while ($row = $res->fetch_assoc()) { return $row['pdi_id']; } } } } } return 0; } /** * Function to save an existing PDI * * @param pdi $pdi_in * The PDI to save or update * @param checklist $checklist * [optional] * The checklist to link new PDIs to (if null links to Orphan checklist) * * @return boolean|int Returns ID of PDI or FALSE if failed to save. */ public function save_PDI($pdi_in, $checklist = null) { $pdi_id = null; if ($pdi_in->get_ID()) { $this->help->update('sagacity.pdi_catalog', [ 'cat' => $pdi_in->get_Category_Level(), 'update' => $pdi_in->get_Last_Update(), 'short_title' => $pdi_in->get_Short_Title(), 'check_contents' => $pdi_in->get_Check_Contents() ], [ [ 'field' => 'id', 'op' => '=', 'value' => $pdi_in->get_ID() ] ]); if (! $this->help->execute()) { $this->help->debug(E_ERROR); return false; } $pdi_id = $pdi_in->get_ID(); } else { $this->help->insert("sagacity.pdi_catalog", [ "cat" => $pdi_in->get_Category_Level(), 'update' => $pdi_in->get_Last_Update(), 'short_title' => $pdi_in->get_Short_Title(), 'check_contents' => $pdi_in->get_Check_Contents() ]); if (! ($pdi_id = $this->help->execute())) { $this->help->debug(E_ERROR); return false; } $pdi_in->set_ID($pdi_id); } if (is_null($checklist)) { $checklist = $this->get_Checklist('Orphan'); } if (is_array($checklist) && isset($checklist[0]) && is_a($checklist[0], 'checklist')) { $this->help->insert('sagacity.pdi_checklist_lookup', [ 'pdi_id' => $pdi_id, 'checklist_id' => $checklist[0]->get_ID(), 'check_contents' => $pdi_in->get_Check_Contents(), 'group_title' => $pdi_in->get_Group_Title(), 'short_title' => $pdi_in->get_Short_Title(), 'fix_text' => $pdi_in->get_Fix_Text() ], true); if (! $this->help->execute()) { $this->help->debug(E_ERROR); return false; } } elseif (is_a($checklist, 'checklist')) { $this->help->insert("sagacity.pdi_checklist_lookup", array( 'pdi_id' => $pdi_id, 'checklist_id' => $checklist->get_ID(), 'check_contents' => $pdi_in->get_Check_Contents(), 'group_title' => $pdi_in->get_Group_Title(), 'short_title' => $pdi_in->get_Short_Title(), 'fix_text' => $pdi_in->get_Fix_Text() ), true); if (! $this->help->execute()) { $this->help->debug(E_ERROR); return false; } } else { Sagacity_Error::err_handler("Cannon link PDI ID $pdi_id with a checklist", E_WARNING); } return $pdi_id; } /** * Function to save the check contents to a specific PDI and checklist * * @param pdi $pdi_in * The PDI (containing the check contents) * @param checklist $checklist_in * The checklist * @param string $check_contents_in * [optional] * The check contents to save (will use check contents in $pdi_in if this is null) * @param string $fix_text_in * [optional] * The fix text to save * * @return boolean Returns TRUE if successful, otherwise FALSE */ public function save_Check_Contents($pdi_in, $checklist_in, $check_contents_in = null, $fix_text_in = null) { $this->help->replace("sagacity.pdi_checklist_lookup", array( 'pdi_id' => $pdi_in->get_ID(), 'checklist_id' => $checklist_in->get_ID(), 'group_title' => $pdi_in->get_Group_Title(), 'short_title' => $pdi_in->get_Short_Title(), 'check_contents' => (! is_null($check_contents_in) ? $check_contents_in : $pdi_in->get_Check_Contents()), 'fix_text' => (! is_null($fix_text_in) ? $fix_text_in : $pdi_in->get_Fix_Text()) )); if (! $this->help->execute()) { return false; } return true; } // }}} // {{{ PROC_IA_CONTROLS CLASS FUNCTIONS /** * Function to get all procedural IA controls for specified system * * @param ste $ste_in * ST&E to query the database for * @param string $control_id * [optional] * Control ID to query (default null) * * @return array:proc_ia_controls Return array of proc_ia_controls and associated sub controls, or empty array if none found */ public function get_Proc_IA_Controls($ste_in, $control_id = null) { $ret = []; $sys = $this->get_System($ste_in->get_System() ->get_ID())[0]; switch ($sys->get_Classification()) { case 'Public': $class = 'pub'; break; case 'Sensitive': $class = 'sen'; break; case 'Classified': $class = 'cl'; break; default: $class = ''; } $sql = "SELECT " . "pia.`control_id`,pia.`name`,pia.`subject_area`,pia.`description`," . "pia.`threat_vul_cm`,pia.`gen_imp_guide`,pia.`guide_resource`,pia.`impact` " . "FROM `sagacity`.`proc_ia_controls` pia " . "LEFT JOIN `sagacity`.`proc_level_type` plt ON plt.`proc_control`=pia.`control_id` " . "WHERE plt.`type`='diacap' AND " . "plt.`level`=" . $sys->get_MAC() . " AND " . "plt.`class`='$class'"; if (! is_null($control_id)) { $sql .= " AND pia.`control_id`='" . $this->conn->real_escape_string($control_id) . "'"; } if ($res = $this->conn->query($sql)) { while ($row = $res->fetch_assoc()) { $ia = new proc_ia_controls($row['control_id'], $row['name'], $row['subject_area'], $row['description'], $row['threat_vul_cm'], $row['gen_imp_guide'], $row['guide_resource'], $row['impact']); $sql2 = "SELECT `ste_id`,`control_id`,`vul_desc`,`mitigations`,`references`,`risk_analysis`,`notes`,`done` " . "FROM `sagacity`.`control_findings` " . "WHERE `ste_id`=" . $ste_in->get_ID() . " AND " . "`control_id`='" . $row['control_id'] . "'"; if ($res2 = $this->conn->query($sql2)) { if ($res2->num_rows > 0) { $row2 = $res2->fetch_assoc(); $ia->finding->control_id = $row2['control_id']; $ia->finding->ste_id = $row2['ste_id']; $ia->finding->vul_desc = $row2['vul_desc']; $ia->finding->mitigations = $row2['mitigations']; $ia->finding->reference = $row2['references']; $ia->finding->notes = $row2['notes']; $ia->finding->risk_analysis = $row2['risk_analysis']; $ia->finding->done = $row2['done']; } } $sql2 = "SELECT " . "`sub_control_id`,`name`,`objective`," . "`prep`,`script`,`exp_result` " . "FROM `sagacity`.`proc_ia_sub_controls` " . "WHERE `parent_control_id`='" . $row['control_id'] . "'"; if ($res2 = $this->conn->query($sql2)) { while ($row2 = $res2->fetch_assoc()) { $ia_sub = new proc_sub_ia_controls($row2['sub_control_id'], $row2['name'], $row2['objective'], $row2['prep'], $row2['script'], $row2['exp_result']); $sql3 = "SELECT " . "`ste_id`,`proc_id`,`status`,`test_results`," . "`mitigations`,`milestones`,`ref`,`notes` " . "FROM `sagacity`.`proc_findings` " . "WHERE `ste_id`=" . $ste_in->get_ID() . " AND " . "`proc_id`='" . $row2['sub_control_id'] . "'"; if ($res3 = $this->conn->query($sql3)) { if ($res3->num_rows > 0) { $row3 = $res3->fetch_assoc(); $ia_sub->finding->control_id = $row3['proc_id']; $ia_sub->finding->ste_id = $row3['ste_id']; $ia_sub->finding->test_result = $row3['test_results']; $ia_sub->finding->mitigation = $row3['mitigations']; $ia_sub->finding->milestone = $row3['milestones']; $ia_sub->finding->reference = $row3['ref']; $ia_sub->finding->notes = $row3['notes']; $ia_sub->finding->status = $row3['status']; } else { $ia_sub->finding->status = 'Not Reviewed'; } } $ia->add_Sub($ia_sub); } } $ret[] = $ia; } } else { Sagacity_Error::sql_handler($sql); error_log($this->conn->error); } return $ret; } // }}} // {{{ INTERVIEW QUESTION CLASS FUNCTIONS /** * Function to return the categories * * @return array:string */ public function get_Question_Categories() { $ret = []; $this->help->select("interview_questions", [ 'cat' ], [], [ 'group' => 'cat' ]); $rows = $this->help->execute(); if (is_array($rows) && count($rows) && isset($rows[0])) { foreach ($rows as $row) { $ret[] = $row['cat']; } } return $ret; } /** * Function to get the questions * * @param integer $cat_in * @param string $type_in */ public function get_Questions($cat_in, $type_in = null) { $ret = []; $sql = "SELECT " . "iq.`id`,iq.`key`,iq.`cat`,iq.`question`," . "(SELECT ci.`answer` " . "FROM `category_interview` ci " . "WHERE ci.`ques_id`=iq.`id` AND ci.`cat_id`=" . $this->conn->real_escape_string($cat_in) . ") AS 'answer' " . "FROM `interview_questions` iq " . "WHERE iq.`cat`='" . $this->conn->real_escape_string($type_in) . "'"; if ($res = $this->conn->query($sql)) { while ($row = $res->fetch_assoc()) { $ques = new question(); $ques->id = $row['id']; $ques->cat = $row['cat']; $ques->key = $row['key']; $ques->question = $row['question']; $ques->answer = $row['answer']; $ret[] = $ques; } } else { print $sql . "
"; print $this->conn->error; Sagacity_Error::sql_handler($sql); error_log($this->conn->error); } return $ret; } /** * Function to return the interview questions with their answers * * @param int $cat_id_in * * @return array:question */ public function get_Interview_Answers($cat_id_in) { $ret = []; $this->help->select("interview_questions iq", [ 'iq.id', 'iq.key', 'iq.question', 'ci.answer' ], [ [ 'field' => 'ci.cat_id', 'op' => '=', 'value' => $cat_id_in ] ], [ 'table_joins' => "LEFT JOIN category_interview ci ON iq.id = ci.ques_id" ]); $rows = $this->help->execute(); if (is_array($rows) && count($rows) && isset($rows['id'])) { $rows = [ 0 => $rows ]; } if (is_array($rows) && count($rows) && isset($rows[0])) { foreach ($rows as $row) { $ques = new question(); $ques->id = $row['id']; $ques->key = $row['key']; $ques->question = $row['question']; $ques->answer = ($row['answer'] ? true : false); $ret[] = $ques; } } return $ret; } /** * Function to reset the interview answers * * @param string $type_in * @param int $cat_in */ public function set_Questions($type_in, $cat_in) { $this->help->delete("category_interview", null, [ [ 'field' => 'cat_id', 'op' => '=', 'value' => $cat_in ] ]); $this->help->execute(); $this->help->sql = "INSERT IGNORE INTO `category_interview` (`cat_id`,`ques_id`)" . " SELECT " . $this->conn->real_escape_string($cat_in) . ",`id`" . " FROM `interview_questions`" . " WHERE `cat`='" . $this->conn->real_escape_string($type_in) . "'"; $this->help->query_type = db_helper::INSERT; if (! $this->help->execute()) { $this->help->debug(E_ERROR); return false; } return true; } /** * Function to set the answer for an interview question * * @param int $cat_id_in * @param question $question * @return boolean */ public function set_QA($cat_id_in, $question) { $this->help->update("category_interview", [ 'answer' => ($question->answer) ], [ [ 'field' => 'ques_id', 'op' => '=', 'value' => $question->id ], [ 'field' => 'cat_id', 'op' => '=', 'value' => $cat_id_in, 'sql_op' => 'AND' ] ]); if (! $this->help->execute()) { $this->help->debug(E_ERROR); return false; } return true; } // }}} // {{{ RETINA CLASS FUNCTIONS /** * Update retina data * * @param retina $retina_In * Retina object to save to database * * @return boolean Returns TRUE if successful, otherwise FALSE */ public function save_Retina($retina_In) { $sql = "REPLACE INTO `sagacity`.`retina` (`pdi_id`,`retina_id`) VALUES (" . $this->conn->real_escape_string($retina_In->get_PDI_ID()) . "," . $this->conn->real_escape_string($retina_In->get_Retina_ID()) . ")"; if (! $this->conn->real_query($sql)) { Sagacity_Error::sql_handler($sql); error_log($this->conn->error); return false; } return true; } // }}} // {{{ RMF_CONTROL CLASS FUNCTIONS /** * Function to get all the RMF controls that apply to a certain baseline impact
* Used for tailoring later * * @param string $baseline * * @return array:rmf_control */ public function get_RMF_Control_By_Baseline($baseline) { $ret = []; if (! in_array($baseline, array( "low", "moderate", "high" ))) { return []; } $sql = "SELECT " . "f.`abbr`,f.`name` AS 'family_name' " . "c.`control_id`,c.`name` AS 'control_name',c.`pri`,c.`statement`,c.`guidance` " . "cb.`impact_level` " . "FROM `rmf`.`controls` c " . "JOIN `rmf`.`control_baseline` cb ON cb.`control_id`=c.`control_id` " . "JOIN `rmf`.`family` f ON f.`abbr`=c.`family_id` " . "WHERE cb.`impact_level`='" . $this->conn->real_escape_string($baseline) . "'"; if ($res = $this->conn->query($sql)) { while ($row = $res->fetch_assoc()) { $family = new rmf_family(); $family->set_Abbr($row['abbr']); $family->set_Name($row['family_name']); $rmf = new rmf_control(); $rmf->family = $family; $rmf->set_Control_ID($row['control_id']); $rmf->set_Name($row['control_name']); $rmf->set_Priority($row['pri']); $rmf->set_Statement($row['statement']); $rmf->set_Guidance($row['guidance']); $rmf->set_Baseline($baseline, true); $this->get_RMF_Related_Controls($rmf); $this->get_RMF_Enhanced_Controls($rmf, $baseline); $ret[] = $rmf; } } else { Sagacity_Error::sql_handler($sql); error_log($this->conn->error); } return $ret; } /** * Function to get all the related controls * * @param rmf_control $rmf */ public function get_RMF_Related_Controls(rmf_control &$rmf) { $sql = "SELECT rc.`related_control_id` " . "FROM `rmf`.`related_controls rc " . "WHERE rc.`control_id`='" . $this->conn->real_escape_string($rmf->get_Control_ID()) . "'"; if ($res = $this->conn->query($sql)) { while ($row = $res->fetch_assoc()) { $rmf->add_Related_Control($row['related_control_id']); } } else { Sagacity_Error::sql_handler($sql); error_log($this->conn->error); } } /** * Function to get all the enhanced controls * * @param rmf_control $rmf * @param string $baseline */ public function get_RMF_Enhanced_Controls(rmf_control &$rmf, $baseline = null) { $sql = "SELECT " . "ce.`enh_id`,ce.`name`,ce.`desc`,ce.`guidance` " . "FROM `rmf`.`control_enh` ce " . "JOIN `rmf`.`enhancement_baseline eb ON eb.`control_id`=ce.`control_id` AND " . "eb.`enh_id`=ce.`enh_id` " . "WHERE ce.`control_id`='" . $this->conn->real_escape_string($rmf->get_Control_ID()) . "' AND " . "eb.`impact`='" . (is_null($baseline) ? $rmf->get_Worst_Baseline() : $baseline) . "'"; if ($res = $this->conn->query($sql)) { while ($row = $res->fetch_assoc()) { $enh = new rmf_control_enhancements(); $enh->set_Enhanced_ID($row['enh_id']); $enh->set_Guidance($row['guidance']); $enh->set_Name($row['name']); $enh->set_Statement($row['desc']); $rmf->add_Enhanced_Control($enh); } } else { Sagacity_Error::sql_handler($sql); error_log($this->conn->error); } } // }}} // {{{ SCAN CLASS FUNCTIONS /** * Get ScanData for Results page * * @param integer $intSTE * ST&E ID to grab scans for * @param integer|string $Scan_ID * [optional] * Scan ID or file name to grab (defaulted null) * * @return array:scan|NULL Returns array of scans associated with the ST&E, or null if none found */ public function get_ScanData($intSTE, $Scan_ID = null, $status_in = null, $type_in = null) { $ret = []; $where = [ [ 'field' => 's.ste_id', 'value' => $intSTE ] ]; if (! is_null($Scan_ID)) { if (is_numeric($Scan_ID)) { $where[] = [ 'field' => 's.id', 'value' => $Scan_ID, 'sql_op' => 'AND' ]; } else { $where[] = [ 'field' => 's.file_name', 'value' => $Scan_ID, 'sql_op' => 'AND' ]; } } if (! is_null($status_in)) { $where[] = [ 'field' => 's.status', 'value' => $status_in, 'sql_op' => 'AND' ]; } if (! is_null($type_in)) { $where[] = [ 'field' => 'src.name', 'value' => $type_in, 'sql_op' => 'AND' ]; } $this->help->select("scans s", [ 's.*' ], $where, [ 'table_joins' => [ "JOIN sources src ON src.id=s.src_id" ], 'order' => 's.file_name' ]); $scan_rows = $this->help->execute(); if (isset($scan_rows['id'])) { $scan_rows = [ 0 => $scan_rows ]; } if (is_array($scan_rows) && count($scan_rows)) { foreach ($scan_rows as $row) { $src = $this->get_Sources($row['src_id']); if (is_array($src) && count($src) && isset($src[0]) && is_a($src[0], 'source')) { $src = $src[0]; } else { continue; } $ste = $this->get_STE($intSTE); if (is_array($ste) && count($ste) && isset($ste[0]) && is_a($ste[0], 'ste')) { $ste = $ste[0]; } else { continue; } $scan = new scan($row['id'], $src, $ste, $row['itr'], $row['file_name'], $row['file_date']); $scan->set_Notes($row['notes']); $scan->set_PID($row['pid']); $scan->set_Start_Time($row['start_time']); $scan->set_Last_Update($row['last_update']); $scan->set_Status($row['status']); $scan->set_Percentage_Complete($row['perc_comp']); $scan->set_Last_Host($row['last_host']); $scan->set_Total_Host_Count($row['host_count']); $this->help->select("host_list hl", [ 'hl.tgt_id', 't.name', 'hl.finding_count', 'hl.scanner_error', 'hl.notes' ], [ [ 'field' => 'hl.scan_id', 'value' => $row['id'] ] ], [ 'table_joins' => [ "LEFT JOIN target t ON t.id=hl.tgt_id" ] ]); $hl_rows = $this->help->execute(); if (is_array($hl_rows) && count($hl_rows) && isset($hl_rows['tgt_id'])) { $hl_rows = [ 0 => $hl_rows ]; } if (is_array($hl_rows) && count($hl_rows) && isset($hl_rows[0])) { foreach ($hl_rows as $row) { $tgt = new target($row['name']); $tgt->set_ID($row['tgt_id']); $tgt->set_STE_ID($intSTE); $tgt->interfaces = $this->get_Interfaces($tgt->get_ID()); if ((bool) $row['scanner_error']) { $scan->setScanError((bool) $row['scanner_error']); } $hl = new host_list(); $hl->setTargetId($tgt->get_ID()); $hl->setTargetName($tgt->get_Name()); $hl->setTargetIp($tgt->getIP()); $hl->setFindingCount($row['finding_count']); $hl->setScanError((bool) $row['scanner_error']); $hl->setScanNotes($row['notes']); $scan->add_Target_to_Host_List($hl); } } $ret[] = $scan; } } return $ret; } /** * Save scan data * * @param scan $new_Scan * New scan to save to database * * @return integer Returns ID of new scan, or 0 if fail */ public function save_Scan($new_Scan) { if (! is_a($new_Scan, "scan")) { return; } if (! is_a($new_Scan->get_Source(), 'source')) { throw (new Exception("Wrong source type " . print_r($new_Scan->get_Source(), true))); } if ($new_Scan->get_ID()) { $this->help->update("scans", [ 'src_id' => $new_Scan->get_Source()->get_ID(), 'itr' => $new_Scan->get_Itr(), 'file_date' => $new_Scan->get_File_DateTime(), 'pid' => $new_Scan->get_PID(), 'start_time' => $new_Scan->get_Start_Time(), 'last_update' => $new_Scan->get_Last_Update(), 'status' => $new_Scan->get_Status(), 'perc_comp' => $new_Scan->get_Percentage_Complete(), 'last_host' => $new_Scan->get_Last_Host(), 'host_count' => $new_Scan->get_Total_Host_Count(), 'notes' => $new_Scan->get_Notes() ], [ [ 'field' => 'id', 'value' => $new_Scan->get_ID() ] ]); if (! $this->help->execute()) { $this->help->debug(E_ERROR); } } else { $this->help->insert("scans", [ 'src_id' => $new_Scan->get_Source()->get_ID(), 'ste_id' => $new_Scan->get_STE()->get_ID(), 'itr' => $new_Scan->get_Itr(), 'file_name' => $new_Scan->get_File_Name(), 'file_date' => $new_Scan->get_File_DateTime(), 'pid' => $new_Scan->get_PID(), 'start_time' => $new_Scan->get_Start_Time(), 'last_update' => $new_Scan->get_Last_Update(), 'status' => $new_Scan->get_Status(), 'perc_comp' => $new_Scan->get_Percentage_Complete(), 'last_host' => $new_Scan->get_Last_Host(), 'host_count' => $new_Scan->get_Total_Host_Count(), 'notes' => $new_Scan->get_Notes() ]); if (! $this->help->execute()) { $this->help->debug(E_ERROR); return 0; } $new_Scan->set_ID($this->conn->insert_id); } $this->update_Scan_Host_List($new_Scan, $new_Scan->get_Host_List()); return $new_Scan->get_ID(); } /** * Delete a scan (associated finding data and optionally targets) * * @param integer $ste_id * ST&E ID where the scan exists * @param integer $scan_id * Scan to delete from database * @param boolean $del_tgts * Boolean to decide if we are deleting targets as well * * @return boolean Returns TRUE if successful, otherwise FALSE */ public function delete_Scan($ste_id, $scan_id, $del_tgts = false) { $scan = $this->get_ScanData($ste_id, $scan_id); if (is_array($scan) && count($scan) && isset($scan[0]) && is_a($scan[0], 'scan')) { $scan = $scan[0]; } elseif (! is_a($scan, 'scan')) { Sagacity_Error::err_handler("Failed to find Scan ($scan_id)", E_ERROR); return false; } $this->help->delete("finding_controls fc", [ 'fc.*' ], [ [ 'field' => 'f.scan_id', 'op' => '=', 'value' => $scan_id ] ], [ "JOIN findings f ON f.tgt_id = fc.tgt_id AND f.pdi_id = fc.pdi_id" ]); if (! $this->help->execute()) { $this->help->debug(E_ERROR); return false; } $this->help->delete("findings", null, [ [ 'field' => 'scan_id', 'op' => '=', 'value' => $scan_id ] ]); if (! $this->help->execute()) { $this->help->debug(E_ERROR); return false; } $this->help->delete("host_list", null, [ [ 'field' => 'scan_id', 'op' => '=', 'value' => $scan_id ] ]); if (! $this->help->execute()) { $this->help->debug(E_ERROR); return false; } $this->help->delete("scans", null, [ [ 'field' => 'id', 'op' => '=', 'value' => $scan_id ] ]); if (! $this->help->execute()) { $this->help->debug(E_ERROR); return false; } if ($del_tgts) { /** * * @var host_list $host */ foreach ($scan->get_Host_List() as $host) { $this->delete_Target($host->getTargetId()); } } return true; } /** * Updates the host_list field for a particular scan * * @param scan $scan * Scan to update * @param array $host_list * [optional] * Formatted host list to update (default null) * * @return boolean Returns TRUE if successful, otherwise FALSEs */ public function update_Scan_Host_List($scan, $host_list = null) { $this->help->delete("host_list", null, [ [ 'field' => 'scan_id', 'value' => $scan->get_ID() ] ]); if (! $this->help->execute()) { $this->help->debug(E_ERROR); return false; } $params = []; if (is_null($host_list)) { foreach ($scan->get_Host_List() as $host) { $params[] = [ $scan->get_ID(), $host->getTargetId(), $host->getFindingCount(), $host->getScanError(), $host->getScanNotes() ]; $this->update_Target_Counts($host->getTargetId()); } } else { foreach ($host_list as $host) { if (! is_a($host, 'host_list')) { break; } $params[] = [ $scan->get_ID(), $host->getTargetId(), $host->getFindingCount(), $host->getScanError(), $host->getScanNotes() ]; $this->update_Target_Counts($host->getTargetId()); } } if (count($params)) { $this->help->extended_insert("host_list", [ 'scan_id', 'tgt_id', 'finding_count', 'scanner_error', 'notes' ], $params); if (! $this->help->execute()) { $this->help->debug(E_WARNING); } } return true; } /** * Get the scan source data * * @param integer|string $srcID * Source ID or name to grab from database * * @return source|NULL Returns source, or null if none found */ public function get_Sources($srcID = null) { $where = []; $ret = null; if (! is_null($srcID)) { if (is_numeric($srcID)) { $where[] = [ 'field' => 'id', 'op' => '=', 'value' => $srcID ]; } else { $where[] = [ 'field' => 'name', 'op' => '=', 'value' => $srcID, 'case_insensitive' => true ]; } } $this->help->select("sagacity.sources", null, $where, [ 'order' => 'name' ]); $src_rows = $this->help->execute(); if (is_array($src_rows) && isset($src_rows['id'])) { $src_rows = [ 0 => $src_rows ]; } if (is_array($src_rows) && count($src_rows) && isset($src_rows[0])) { foreach ($src_rows as $row) { $src = new source($row['id'], $row['name']); $src->set_Icon($row['icon']); $ret[] = $src; } } return $ret; } /** * Function to get the expected sources for a category * * @param ste_cat $cat * * @return array:source|array */ public function get_Expected_Category_Sources($cat) { if (is_array($cat) && count($cat)) { $cat = $cat[0]; } if (! is_a($cat, "ste_cat")) { return []; } $ret = []; $this->help->select("sagacity.sources s", [ 's.id', 's.name', 's.icon' ], [ [ 'field' => 'cat.id', 'op' => '=', 'value' => $cat->get_ID() ] ], [ 'table_joins' => [ "JOIN sagacity.ste_cat_sources src ON s.id=src.src_id", "JOIN sagacity.ste_cat cat ON cat.id=src.cat_id" ] ]); $src_arr = $this->help->execute(); if (is_array($src_arr) && count($src_arr) && isset($src_arr['id'])) { $src_arr = [ 0 => $src_arr ]; } if (is_array($src_arr) && count($src_arr) && isset($src_arr[0])) { foreach ($src_arr as $row) { $src = new source($row['id'], $row['name']); $icon = null; $missing_fname = str_replace(" ", "-", substr($row['icon'], 0, - 4)) . "-missing.png"; if ($row['icon'] && file_exists(DOC_ROOT . "/img/scan_types/{$missing_fname}")) { $icon = $missing_fname; } else { $icon = $row['icon']; } $src->set_Icon($icon); $ret[$src->get_ID()]['src'] = $src; } } return $ret; } /** * Find the sources that have contained this target * * @param target $tgt * @param array $exp_scan_srcs * * @return array:sources */ public function get_Target_Scan_Sources($tgt, &$exp_scan_srcs = null) { $ret = (!is_null($exp_scan_srcs) ? $exp_scan_srcs : []); $this->help->select("sources src", [ "src.id", "src.name", "src.icon", "hl.scanner_error", "hl.notes", "hl.finding_count", "s.file_name" ], [ [ 'field' => 'hl.tgt_id', 'value' => $tgt->get_ID() ] ], [ 'table_joins' => [ "LEFT JOIN scans s ON s.src_id=src.id", "LEFT JOIN host_list hl ON hl.scan_id=s.id" ] ]); $rows = $this->help->execute(); if (is_array($rows) && count($rows) && isset($rows['id'])) { $rows = [ 0 => $rows ]; } if (is_array($rows) && count($rows) && isset($rows[0])) { foreach($rows as $row) { $src = new source($row['id'], $row['name']); $src->set_Icon($row['icon']); $ret[$src->get_ID()]['src'] = $src; $ret[$src->get_ID()]['count'] = $row['finding_count']; $ret[$src->get_ID()]['notes'] = $row['notes']; $ret[$src->get_ID()]['scan_error'] = $row['scanner_error']; $ret[$src->get_ID()]['file_name'] = $row['file_name']; } } return $ret; } // }}} // {{{ SCRIPT FUNCTIONS /** * Function to get a catalog script * * @param string $file_name_in * [optional] * Look for a specific catalog/STIG file that is processing * * @return array:catalog_script|NULL */ public function get_Catalog_Script($file_name_in = null) { $ret = []; $where = []; if (! is_null($file_name_in)) { $where[] = [ 'field' => 'file_name', 'op' => '=', 'value' => $file_name_in ]; } $this->help->select("sagacity.catalog_scripts cs", ['c.id', 'cs.*'], $where, [ 'table_joins' => [ "LEFT JOIN sagacity.checklist c ON c.file_name = cs.file_name" ], 'order' => "FIELD(`status`, 'ERROR','RUNNING','IN QUEUE','COMPLETE'),`file_name`" ]); $rows = $this->help->execute(); if (is_array($rows) && count($rows) && isset($rows['file_name'])) { $rows = [ 0 => $rows ]; } if (is_array($rows) && count($rows)) { foreach ($rows as $row) { $script = new catalog_script(); $script->{'id'} = $row['id']; $script->file_name = $row['file_name']; $script->pid = $row['pid']; $script->start_time = new DateTime($row['start_time']); $script->last_update = new DateTime($row['last_update']); $script->status = $row['status']; $script->perc_comp = $row['perc_comp']; $script->stig_count = $row['stig_count']; $ret[] = $script; } } return $ret; } /** * Function to get script count * * @param string $status * [optional] * Return only the count for a script that is in a certain status (defaulted null) * * @return integer Returns the number of script that are in the database or count in a specific status */ public function get_Catalog_Script_Count($status = null) { $where = []; if (! is_null($status)) { $where[] = [ 'field' => 'status', 'op' => '=', 'value' => $status ]; if ($status == 'RUNNING') { $where[] = [ 'field' => 'perc_comp', 'op' => '<', 'value' => 100, 'sql_op' => 'AND', 'open-paren' => true ]; $where[] = [ 'field' => 'perc_comp', 'op' => IS, 'value' => null, 'sql_op' => 'OR', 'close-paren' => true ]; } } $this->help->select_count("sagacity.catalog_scripts", $where); if ($count = $this->help->execute()) { return $count; } return 0; } /** * Function to add new catalog parsing script * * @param string $file_name_in * The catalog/STIG file that is processing * * @return boolean Returns TRUE if successful, otherwise FALSE */ public function add_Catalog_Script($file_name_in) { $this->help->insert("sagacity.catalog_scripts", [ 'file_name' => $file_name_in ], true); if (! $this->help->execute()) { $this->help->debug(E_ERROR); return false; } return true; } /** * Function to update catalog script execution * * @param string $file * Script to update * @param array $field * Array with the name and value of the column to update * 'name' => 'pid', * 'value' => 1234 * * @return boolean Returns TRUE if successful, otherwise FALSE */ public function update_Catalog_Script($file, $field) { $where = array( array( 'field' => 'file_name', 'op' => '=', 'value' => $file ) ); if ($field['name'] == 'perc_comp' && $field['value'] == 100 && isset($field['complete'])) { $this->help->update("sagacity.catalog_scripts", array( $field['name'] => $field['value'], 'status' => 'COMPLETE', 'last_update' => 'NOW()' ), $where); } elseif ($field['name'] == 'pid') { $this->help->update("sagacity.catalog_scripts", array( $field['name'] => $field['value'], 'status' => 'RUNNING', 'start_time' => 'NOW()', 'last_update' => 'NOW()' ), $where); } else { $this->help->update('sagacity.catalog_scripts', array( $field['name'] => $field['value'], 'last_update' => 'NOW()' ), $where); } if (! $this->help->execute()) { return false; } return true; } /** * Function to get the number of scripts that are currently running * * @param integer $ste * ST&E to evaluate * * @return integer Returns the count of scripts that are running */ public function get_Running_Script_Count($ste) { $this->help->select_count("scans", [ [ 'field' => 'status', 'op' => '=', 'value' => 'RUNNING' ], [ 'field' => 'ste_id', 'op' => '=', 'value' => $ste, 'sql_op' => 'AND' ] ]); return $this->help->execute(); } /** * Getter function to retrieve the status of a result script * * @param int $ste_id * @param string $file * * @return string */ public function get_Running_Script_Status($ste_id, $file) { $this->help->select("sagacity.scans", [ 'status', 'perc_comp' ], [ [ 'field' => 'ste_id', 'op' => '=', 'value' => $ste_id ], [ 'field' => 'file_name', 'op' => '=', 'value' => $file, 'sql_op' => 'AND' ] ]); return $this->help->execute(); } /** * Add a new script to the database * * @param string $file * Result file name * @param integer $ste_id * The STE ID that the script is being added to * @param string $type * The result type * * @return boolean Return TRUE if successful, otherwise FALSE */ public function add_Running_Script($file, $ste_id, $type, $location) { $existing_scan = $this->get_ScanData($ste_id, $file); if (is_array($existing_scan) && count($existing_scan) && isset($existing_scan[0]) && is_a($existing_scan[0], 'scan')) { $scan = $existing_scan[0]; $this->help->update("scans", [ 'start_time' => 'NOW()', 'last_update' => 'NOW()', 'perc_comp' => 0.0 ], [ [ 'field' => 'id', 'op' => '=', 'value' => $scan->get_ID() ] ]); } else { $type = str_replace("_", " ", $type); $src = $this->get_Sources($type); if (is_array($src) && count($src) && is_a($src[0], 'source')) { $src = $src[0]; } else { return false; } $fd = date("Y-m-d", filemtime(TMP . "/" . $file)); $this->help->insert("sagacity.scans", [ 'ste_id' => $ste_id, 'src_id' => $src->get_ID(), 'file_name' => $file, 'file_date' => $fd, 'start_time' => 'NOW()', 'last_update' => 'NOW()', 'status' => 'IN QUEUE', 'perc_comp' => 0.0, 'location' => $location ], true); } if (! $this->help->execute()) { $this->help->debug(E_ERROR); return false; } return true; } /** * Function to update a running script entry to add the process ID * * @param string $file * The result file to update * @param array $field * Associative array (name,value) to know what field to update * * @return boolean Returns TRUE if successful, otherwise FALSE */ public function update_Running_Scan($file, $field) { $where = [ [ 'field' => 'file_name', 'op' => '=', 'value' => $file ] ]; if ($field['name'] == 'perc_comp' && $field['value'] == 100 && isset($field['complete'])) { $this->help->update("sagacity.scans", [ $field['name'] => $field['value'], 'status' => 'COMPLETE', 'last_update' => 'NOW()' ], $where); } elseif ($field['name'] == 'pid') { $this->help->update("sagacity.scans", [ $field['name'] => $field['value'], 'status' => 'RUNNING', 'start_time' => 'NOW()', 'last_update' => 'NOW()', 'host_count' => 0 ], $where); } elseif ($field['name'] == 'last_host') { $this->help->update("sagacity.scans s", [ "s.{$field['name']}" => $field['value'], 's.last_update' => 'NOW()', 's.hosts_comp' => "s.`hosts_comp`+1" ], $where); } else { $this->help->update("sagacity.scans", [ $field['name'] => $field['value'], 'last_update' => 'NOW()' ], $where); } if (! $this->help->execute()) { $this->help->debug(E_WARNING); return false; } return true; } // }}} // {{{ SITE CLASS FUNCTIONS /** * Get site data * * @param integer $siteID * [optional] * Site ID to get from database * * @return array:site Returns array of sites, or empty array if none found */ public function get_Site($siteID = null) { $where = []; $sites = []; if (! is_null($siteID)) { if (is_numeric($siteID)) { $where[] = [ 'field' => 'id', 'op' => '=', 'value' => $siteID ]; } else { $where = [ 'field' => 'name', 'op' => '=', 'value' => $siteID ]; } } $this->help->select("sites", null, $where, [ 'order' => 'name' ]); $rows = $this->help->execute(); if (is_array($rows) && count($rows) && isset($rows['id'])) { $rows = [ 0 => $rows ]; } if (is_array($rows) && count($rows) && isset($rows[0])) { foreach ($rows as $row) { $sites[] = new site($row['id'], $row['name'], $row['address'], $row['city'], $row['state'], $row['zip'], $row['country'], $row['poc_name'], $row['poc_email'], $row['poc_phone']); } } return $sites; } /** * Get a site for an ST&E * * @param integer $intSTE * ID of the STE to isolate * * @return site|NULL Returns array of sites associated with a specific ST&E, or null if none found */ public function get_Site_By_STE_ID($intSTE) { $this->help->select("sites s", [ 's.*' ], [ [ 'field' => 'ste.id', 'op' => '=', 'value' => $intSTE ] ], [ 'table_joins' => [ "LEFT JOIN ste ON ste.site_id = s.id" ] ]); $row = $this->help->execute(); if (is_array($row) && count($row) && isset($row['id'])) { $site = new site($row['id'], $row['name'], $row['address'], $row['city'], $row['state'], $row['zip'], $row['country'], $row['poc_name'], $row['poc_email'], $row['poc_phone']); return $site; } return null; } /** * Update or insert a site * * @param site $site_In * Site to save to the database * * @return boolean|NULL Returns TRUE if successful, otherwise FALSE */ public function save_Site(site $site_In) { if ($site_In->get_Id()) { $this->help->update("sites", [ 'name' => $site_In->get_Name(), 'address' => $site_In->get_Address(), 'city' => $site_In->get_City(), 'state' => $site_In->get_State(), 'zip' => $site_In->get_Zip(), 'country' => $site_In->get_Country(), 'poc_name' => $site_In->get_POC_Name(), 'poc_email' => $site_In->get_POC_Email(), 'poc_phone' => $site_In->get_POC_Phone() ], [ [ 'field' => 'id', 'op' => '=', 'value' => $site_In->get_Id() ] ]); if (! $this->help->execute()) { $this->help->debug(E_ERROR); return false; } return $site_In->get_Id(); } else { $this->help->insert("sites", [ 'name' => $site_In->get_Name(), 'address' => $site_In->get_Address(), 'city' => $site_In->get_City(), 'state' => $site_In->get_State(), 'zip' => $site_In->get_Zip(), 'country' => $site_In->get_Country(), 'poc_name' => $site_In->get_POC_Name(), 'poc_email' => $site_In->get_POC_Email(), 'poc_phone' => $site_In->get_POC_Phone() ], true); if (! ($site_id = $this->help->execute())) { $this->help->debug(E_ERROR); return false; } return $site_id; } return true; } // }}} // {{{ SOFTWARE CLASS FUNCTIONS /** * Get software data * * @param integer|string|software $software_In * Specific ID, array of software objects, or associative array to use (default null) * @param boolean $exact_match * [optional] * Perform an exact match on a CPE (default false) * * @return array:software Returns array of matching software, or empty array if none found */ public function get_Software($software_In, $exact_match = false) { $ret = []; $cpe = null; $sw = null; $query = false; if (is_array($software_In)) { if (isset($software_In[0]) && is_a($software_In[0], 'software')) { $cpe = $software_In[0]->get_CPE(); } elseif (isset($software_In[0]) && isset($software_In[0]['man'])) { $software_In = $software_In[0]; $type = (isset($software_In['type']) && $software_In['type'] ? "o" : "a"); $ver = (isset($software_In['ver']) && $software_In['ver'] ? $software_In['ver'] : "-"); $cpe = strtolower(str_replace(array( " ", "(", ")" ), array( "_", "%28", "%29" ), "cpe:/{$type}:{$software_In['man']}:{$software_In['name']}:{$ver}")); } if ($cpe) { $this->help->select("sagacity.software", null, array( array( 'field' => 'cpe', 'op' => LIKE, 'value' => "'%$cpe%'" ) )); $query = true; } } elseif (is_numeric($software_In)) { $this->help->select("sagacity.software", null, array( array( 'field' => 'id', 'op' => '=', 'value' => $software_In ) )); $query = true; } elseif (is_string($software_In)) { $op = $exact_match ? '=' : LIKE; $field = 'cpe'; if (strpos($software_In, "cpe:2.3") !== false) { $field = 'cpe23'; } $exclude_r2 = null; if (preg_match("/windows_server_20[\d]+/", $software_In)) { if (! preg_match("/r2/", $software_In)) { $exclude_r2 = array( 'field' => $field, 'op' => NOT_LIKE, 'value' => "'%r2%'", 'sql_op' => 'AND' ); } } $this->help->select("software", null, [ [ 'field' => $field, 'op' => $op, 'value' => ($op == LIKE ? "'$software_In%'" : $software_In) ], $exclude_r2 ], [ 'order' => 'cpe' ]); $query = true; } elseif (is_a($software_In, 'software')) { $os = ($software_In->is_OS() ? "/o" : "/a"); $man = str_replace(" ", "_", strtolower($software_In->get_Man())); $name = str_replace(" ", "_", strtolower($software_In->get_Name())); $ver = str_replace(" ", "_", strtolower($software_In->get_Version())); $value = "'cpe:{$os}:{$man}:{$name}:{$ver}:%'"; $field = 'cpe'; if (! is_null($software_In->get_CPE23())) { $os = substr($os, 1); $value = "'cpe:2.3:{$os}:{$man}:{$name}:{$ver}:%'"; $field = 'cpe23'; } $this->help->select("software", null, [ [ 'field' => $field, 'op' => LIKE, 'value' => $value ] ], [ 'order' => 'cpe' ]); $query = true; } if ($query) { $rows = $this->help->execute(); if (isset($rows['cpe'])) { $rows = [ 0 => $rows ]; } if (is_array($rows) && count($rows) && isset($rows[0])) { foreach ($rows as $row) { $sw = new software($row['cpe'], $row['cpe23']); $sw->set_ID($row['id']); $sw->set_SW_String($row['sw_string']); $sw->set_Shortened_SW_String($row['short_sw_string']); $ret[] = $sw; } } } return $ret; } /** * Function to retrieve a software item by using the software string * * @param string $sw_string * * @return software|NULL */ public function get_Software_By_String($sw_string) { $ret = null; $this->help->select("software", null, [ [ 'field' => 'sw_string', 'op' => '=', 'value' => $sw_string ] ], [ 'limit' => 1 ]); $row = $this->help->execute(); if(is_array($row) && count($row) && isset($row['id'])) { $ret = new software($row['cpe'], $row['cpe23']); $ret->set_ID($row['id']); $ret->set_SW_String($row['sw_string']); $ret->set_Shortened_SW_String($row['short_sw_string']); } return $ret; } /** * Function to retrieve a software item by using the CPE or CPE v2.3 * * @param string $cpe_in * CPE to search for * * @return software|NULL Returns software object if found, otherwise null */ public function get_Software_By_CPE($cpe_in) { $field = "cpe"; if (strpos($cpe_in, "cpe:2.3") !== false) { $field = "cpe23"; } $this->help->select("software", null, [ [ 'field' => $field, 'op' => '=', 'value' => $cpe_in ] ]); $rows = $this->help->execute(); if (is_array($rows) && count($rows) && isset($rows['id'])) { $rows = [ 0 => $rows ]; } if (is_array($rows) && count($rows) && isset($rows[0])) { foreach ($rows as $row) { $sw = new software($row['cpe'], $row['cpe23']); $sw->set_ID($row['id']); $sw->set_SW_String($row['sw_string']); return $sw; } } return null; } /** * Get the IDs of the CPEs passed in * * @param array:string $cpes */ public function get_Software_Ids(array $cpes = []) { $ret = []; $this->help->select("software", [ 'id' ], [ [ 'field' => 'cpe', 'op' => IN, 'value' => $cpes ] ]); $rows = $this->help->execute(); if (is_array($rows) && count($rows) && isset($rows['id'])) { $rows = [ 0 => $rows ]; } if (is_array($rows) && count($rows) && isset($rows[0])) { foreach ($rows as $row) { $ret[] = $row['id']; } } return $ret; } // @TODO - Finish /** * Get a list of all software items * * @param boolean $isOS * Boolean to isolate the operating systems * @param integer $os_ID * ID of a specific software, used to select an element in the drop-down * * @return string Returns a string with the drop-down option tags */ public function get_Software_List($isOS, $os_ID = null) { $ret = ''; $sql = "SELECT `id`,`cpe`,`cpe23`,`sw_string` " . "FROM `sagacity`.`software`"; if (! is_null($os_ID)) { $sql .= " WHERE `id`=" . $os_ID; } elseif ($isOS) { $sql .= " WHERE `cpe23` LIKE '%:o:%'"; } elseif (! $isOS) { $sql .= " WHERE `cpe23` LIKE '%:a:%'"; } // set up query to split cpe string then group by man and name // $sql .= " GROUP BY "; if ($res = $this->conn->query($sql)) { while ($row = $res->fetch_assoc()) { $sw = new software($row['cpe'], $row['cpe23']); $ret .= ""; } } else { Sagacity_Error::sql_handler($sql); error_log($this->conn->error); } return $ret; } /** * Get array of software that a target has installed * * @param integer $tgt_id * Target ID to query for * * @return array:software|NULL Returns array of software that are assigned to associated target, or null if none found */ public function get_Target_Software($tgt_id) { $this->help->select("software s", [ 's.*' ], [ [ 'field' => 'ts.tgt_id', 'op' => '=', 'value' => $tgt_id ] ], [ 'table_joins' => [ "LEFT JOIN sagacity.target_software ts ON ts.sft_id=s.id" ] ]); $sw_arr = $this->help->execute(); $sft = []; if (is_array($sw_arr) && count($sw_arr)) { if (isset($sw_arr['cpe'])) { $sw_arr = [ 0 => $sw_arr ]; } foreach ($sw_arr as $row) { $sw = new software($row['cpe'], $row['cpe23']); $sw->set_ID($row['id']); $sw->set_SW_String($row['sw_string']); $sw->set_Shortened_SW_String($row['short_sw_string']); $sft[] = $sw; } } return $sft; } /** * Update existing software or add new * * @param software $sw_in * The software to save * * @return integer Returns the ID of the software that was just inserted or updated if successful, otherwise it returns 0 */ public function save_Software($sw_in) { if (! is_null($sw_in->get_ID())) { $this->help->update("sagacity.software", array( 'cpe' => $sw_in->get_CPE(), 'cpe23' => $sw_in->get_CPE23(), 'sw_string' => $sw_in->get_SW_String(), 'short_sw_string' => $sw_in->get_Shortened_SW_String() ), array( array( 'field' => 'id', 'op' => '=', 'value' => $sw_in->get_ID() ) )); if (! $this->help->execute()) { $this->help->debug(E_WARNING); return 0; } return $sw_in->get_ID(); } else { $this->help->insert("sagacity.software", array( 'cpe' => $sw_in->get_CPE(), 'cpe23' => $sw_in->get_CPE23(), 'sw_string' => $sw_in->get_SW_String(), 'short_sw_string' => $sw_in->get_Shortened_SW_String() ), true); if (! ($sw_id = $this->help->execute())) { $this->help->debug(E_WARNING); return 0; } return $sw_id; } return 0; } /** * Function to retrieve an array of all the software detection regex's * * @param string $type * * @return array */ public function get_Regex_Array($type) { $ret = []; $where = []; if ($type != 'os') { $where[] = [ 'field' => 'type', 'op' => '=', 'value' => $type ]; $where[] = [ 'field' => 'type', 'op' => '=', 'value' => 'multiple', 'sql_op' => 'OR' ]; } else { $where[] = [ 'field' => 'type', 'op' => LIKE, 'value' => "'%os'" ]; } $this->help->select("sagacity.sw_man_match", null, $where); $rows = $this->help->execute(); if (is_array($rows) && count($rows) && isset($rows['id'])) { $rows = [ 0 => $rows ]; } if (is_array($rows) && count($rows) && isset($rows[0])) { foreach ($rows as $row) { $tmp = [ 'id' => $row['id'], 'man' => $row['man'], 'rgx' => $row['rgx'], 'name' => [] ]; $this->help->select("sagacity.sw_name_match", null, [ [ 'field' => 'man_id', 'op' => '=', 'value' => $row['id'] ] ]); $name_rows = $this->help->execute(); if (is_array($name_rows) && count($name_rows) && isset($name_rows['id'])) { $name_rows = [ 0 => $name_rows ]; } if (is_array($name_rows) && count($name_rows) && isset($name_rows[0])) { foreach ($name_rows as $row2) { $tmp['name'][$row2['id']] = array( 'name' => $row2['name'], 'man_override' => $row2['man_override'], 'rgx' => $row2['rgx'], 'name_match' => $row2['name_match'], 'ver_match' => $row2['ver_match'], 'ver' => $row2['ver'], 'update_match' => $row2['update_match'], 'is_os' => ($row2['is_os'] ? true : false), 'multiple' => ($row2['multiple'] ? true : false) ); } } $ret[] = $tmp; } } return $ret; } // }}} // {{{ STE CLASS FUNCTIONS /** * Get ST&E data * * @param integer $steID * ST&E ID to isolate * * @return array:ste|NULL Returns array of ste objects, or null if none found */ public function get_STE($steID = null) { $where = []; $ret = null; if ($steID != null) { $where[] = [ 'field' => 'id', 'op' => '=', 'value' => $steID ]; } else { $where[] = [ 'field' => 'primary', 'op' => '=', 'value' => 0 ]; } $this->help->select("ste", null, $where, [ 'order' => 'eval_start DESC' ]); $ste_rows = $this->help->execute(); if (isset($ste_rows['id'])) { $ste_rows = [ 0 => $ste_rows ]; } if (is_array($ste_rows) && count($ste_rows) && isset($ste_rows[0])) { foreach ($ste_rows as $row) { $sys = $this->get_System($row['system_id']); if (is_array($sys) && count($sys) && isset($sys[0]) && is_a($sys[0], 'system')) { $sys = $sys[0]; } else { Sagacity_Error::err_handler("Unable to find system for ST&E ID {$row['id']}", E_ERROR); } $site = $this->get_Site($row['site_id']); if (is_array($site) && count($site) && isset($site[0]) && is_a($site[0], 'site')) { $site = $site[0]; } else { Sagacity_Error: err_handler("Unable to find site for ST&E ID {$row['id']}", E_ERROR); } $ste = new ste($row['id'], $sys, $site, $row['eval_start'], $row['eval_end'], $row['multiple'], $row['primary']); $ste->set_Assumptions($row['assumptions']); $ste->set_Conclusions($row['conclusion']); $ste->set_Constraints($row['constraints']); $ste->set_Deviations($row['deviations']); $ste->set_Recommendations($row['recommendations']); $ste->set_Residual_Risk($row['residual_risk']); $ste->set_Scope($row['scope']); $ste->set_Status($row['risk_status']); $ste->set_AO($row['ao']); $this->help->select("people p", [ 'st.pos', 'p.*' ], [ [ 'field' => 'st.ste_id', 'op' => '=', 'value' => $ste->get_ID() ] ], [ 'table_joins' => [ "JOIN ste_team st ON st.people_id=p.id" ] ]); $people_rows = $this->help->execute(); if (is_array($people_rows) && isset($people_rows['id'])) { $people_rows = [ 0 => $people_rows ]; } if (is_array($people_rows) && count($people_rows) && isset($people_rows[0])) { foreach ($people_rows as $row2) { $people = new people(); $people->id = $row2['id']; $people->org = $row2['org']; $people->name = $row2['name']; $people->phone = $row2['phone']; $people->position = $row2['pos']; $ste->add_STE_Team_Member($people); } } $ret[] = $ste; } } return $ret; } /** * Get the subsystems for a particular site * * @param ste $ste_in * ST&E to get subsystems for * * @return array:ste Returns the subsystem ST&E, or empty array if none found */ public function get_Subsystems($ste_in) { $this->help->select("sagacity.ste", null, array( array( 'field' => 'primary', 'op' => '=', 'value' => $ste_in->get_ID() ) )); $ret = []; $rows = $this->help->execute(); if (is_array($rows) && count($rows) && isset($rows['id'])) { $rows = array( 0 => $rows ); } if (is_array($rows) && count($rows) && isset($rows[0])) { foreach ($rows as $row) { $ret[] = new ste($row['id'], $row['system_id'], $row['site_id'], $row['eval_start'], $row['eval_end'], $row['multiple'], $row['primary']); } } return $ret; } /** * This function returns ST&E list and creates options for a select box * Will organize into optgroup tags if subsystems are found * * @param boolean $select_first * [optional] * Force the selection of the first element in the drop-down * * @return string|NULL Returns a string of option tag elements, or null if none found */ public function get_STE_List($select_first = false) { $ret = ''; $stes = $this->get_STE(); if (is_array($stes) && count($stes) && isset($stes['id'])) { $stes = [ 0 => $stes ]; } if (is_array($stes) && count($stes) && isset($stes[0])) { foreach ($stes as $ste) { $start_str = $ste->get_Eval_Start_Date()->format('d M Y'); $subs = $this->get_Subsystems($ste); if (is_array($subs) && count($subs) > 0) { $ret .= "" . ""; foreach ($subs as $sub) { $ret .= ""; } $ret .= ""; } else { $ret .= ""; } } } // return the string of