Merge branch '2.4' of github.com:MISP/MISP into stix2

pull/3707/head
chrisr3d 2018-08-05 23:07:09 +02:00
commit 9c9e2960a9
7 changed files with 312 additions and 453 deletions

View File

@ -1415,6 +1415,10 @@ class ServersController extends AppController
if ($result['status'] == 1) {
$version = json_decode($result['message'], true);
if (isset($version['version']) && preg_match('/^[0-9]+\.+[0-9]+\.[0-9]+$/', $version['version'])) {
$perm_sync = false;
if (isset($version['perm_sync'])) {
$perm_sync = $version['perm_sync'];
}
App::uses('Folder', 'Utility');
$file = new File(ROOT . DS . 'VERSION.json', true);
$local_version = json_decode($file->read(), true);
@ -1438,16 +1442,9 @@ class ServersController extends AppController
}
}
}
if (!isset($version['perm_sync'])) {
if (!$this->Server->checkLegacyServerSyncPrivilege($id)) {
$result['status'] = 7;
return new CakeResponse(array('body'=> json_encode($result), 'type' => 'json'));
}
} else {
if (!$version['perm_sync']) {
$result['status'] = 7;
return new CakeResponse(array('body'=> json_encode($result), 'type' => 'json'));
}
if (!$perm_sync) {
$result['status'] = 7;
return new CakeResponse(array('body'=> json_encode($result), 'type' => 'json'));
}
return new CakeResponse(
array(

View File

@ -1193,6 +1193,16 @@ class AppModel extends Model
return $version_array;
}
public function validateAuthkey($value) {
if (empty($value['authkey'])) {
return 'Empty authkey found. Make sure you set the 40 character long authkey.';
}
if (!preg_match('/[a-z0-9]{40}/i', $value['authkey'])) {
return 'The authkey has to be exactly 40 characters long and consist of alphanumeric characters.';
}
return true;
}
// alternative to the build in notempty/notblank validation functions, compatible with cakephp <= 2.6 and cakephp and cakephp >= 2.7
public function valueNotEmpty($value)
{

View File

@ -1242,8 +1242,8 @@ class Event extends AppModel
public function downloadProposalsFromServer($uuidList, $server, $HttpSocket = null)
{
$url = $server['Server']['url'];
$HttpSocket = $this->__setupHttpSocket($server, $HttpSocket);
$request = $this->__setupPushRequest($server);
$HttpSocket = $this->setupHttpSocket($server, $HttpSocket);
$request = $this->setupSyncRequest($server);
$uri = $url . '/shadow_attributes/getProposalsByUuidList';
$response = $HttpSocket->post($uri, json_encode($uuidList), $request);
if ($response->isOk()) {

View File

@ -47,14 +47,7 @@ class Server extends AppModel
)
),
'authkey' => array(
'minlength' => array(
'rule' => array('minlength', 40),
'message' => 'A authkey of a minimum length of 40 is required.',
'required' => true,
),
'valueNotEmpty' => array(
'rule' => array('valueNotEmpty'),
),
'rule' => array('validateAuthkey')
),
'org_id' => array(
'numeric' => array(
@ -1741,6 +1734,42 @@ class Server extends AppModel
return true;
}
private function __getEventIdListBasedOnPullTechnique($technique, $server) {
if ("full" === $technique) {
// get a list of the event_ids on the server
$eventIds = $this->getEventIdsFromServer($server);
if ($eventIds === 403) {
return array('error' => array(1, null));
} elseif (is_string($eventIds)) {
return array('error' => array(2, $eventIds));
}
// reverse array of events, to first get the old ones, and then the new ones
if (!empty($eventIds)) {
$eventIds = array_reverse($eventIds);
}
} elseif ("update" === $technique) {
$eventIds = $this->getEventIdsFromServer($server, false, null, true, true);
if ($eventIds === 403) {
return array('error' => array(1, null));
} elseif (is_string($eventIds)) {
return array('error' => array(2, $eventIds));
}
$local_event_ids = $eventModel->find('list', array(
'fields' => array('uuid'),
'recursive' => -1,
));
$eventIds = array_intersect($eventIds, $local_event_ids);
} elseif (is_numeric($technique)) {
$eventIds[] = intval($technique);
// if we are downloading a single event, don't fetch all proposals
$conditions = array('Event.id' => $technique);
} else {
return array('error' => array(4, null));
}
return $eventIds;
}
public function pull($user, $id = null, $technique=false, $server, $jobId = false, $percent = 100, $current = 0)
{
if ($jobId) {
@ -1754,39 +1783,7 @@ class Server extends AppModel
App::uses('HttpSocket', 'Network/Http');
$eventIds = array();
$conditions = array();
if ("full" === $technique) {
// get a list of the event_ids on the server
$eventIds = $this->getEventIdsFromServer($server);
// FIXME this is not clean at all ! needs to be refactored with try catch error handling/communication
if ($eventIds === 403) {
return array(1, null);
} elseif (is_string($eventIds)) {
return array(2, $eventIds);
}
// reverse array of events, to first get the old ones, and then the new ones
if (!empty($eventIds)) {
$eventIds = array_reverse($eventIds);
}
} elseif ("update" === $technique) {
$eventIds = $this->getEventIdsFromServer($server, false, null, true, true);
if ($eventIds === 403) {
return array(1, null);
} elseif (is_string($eventIds)) {
return array(2, $eventIds);
}
$local_event_ids = $eventModel->find('list', array(
'fields' => array('uuid'),
'recursive' => -1,
));
$eventIds = array_intersect($eventIds, $local_event_ids);
} elseif (is_numeric($technique)) {
$eventIds[] = intval($technique);
// if we are downloading a single event, don't fetch all proposals
$conditions = array('Event.id' => $technique);
} else {
return array(4, null);
}
$eventIds = $this->__getEventIdListBasedOnPullTechnique($technique, $server);
$successes = array();
$fails = array();
$pulledProposals = array();
@ -2374,15 +2371,8 @@ class Server extends AppModel
return $serverSettings;
}
public function serverSettingsRead($unsorted = false)
private function __serverSettingsRead($serverSettings, $currentSettings)
{
$this->Module = ClassRegistry::init('Module');
$serverSettings = $this->getCurrentServerSettings();
$currentSettings = Configure::read();
if (Configure::read('Plugin.Enrichment_services_enable')) {
$this->readModuleSettings($serverSettings, array('Enrichment'));
}
$finalSettingsUnsorted = array();
foreach ($serverSettings as $branchKey => &$branchValue) {
if (isset($branchValue['branch'])) {
foreach ($branchValue as $leafKey => &$leafValue) {
@ -2417,14 +2407,11 @@ class Server extends AppModel
$finalSettingsUnsorted[$branchKey] = $branchValue;
}
}
foreach ($finalSettingsUnsorted as $key => $temp) {
if (in_array($temp['tab'], array_keys($this->__settingTabMergeRules))) {
$finalSettingsUnsorted[$key]['tab'] = $this->__settingTabMergeRules[$temp['tab']];
}
}
if ($unsorted) {
return $finalSettingsUnsorted;
}
return $finalSettingsUnsorted;
}
private function __sortFinalSettings($finalSettingsUnsorted)
{
$finalSettings = array();
for ($i = 0; $i < 4; $i++) {
foreach ($finalSettingsUnsorted as $k => $s) {
@ -2437,6 +2424,26 @@ class Server extends AppModel
return $finalSettings;
}
public function serverSettingsRead($unsorted = false)
{
$this->Module = ClassRegistry::init('Module');
$serverSettings = $this->getCurrentServerSettings();
$currentSettings = Configure::read();
if (Configure::read('Plugin.Enrichment_services_enable')) {
$this->readModuleSettings($serverSettings, array('Enrichment'));
}
$finalSettingsUnsorted = $this->__serverSettingsRead($serverSettings, $currentSettings);
foreach ($finalSettingsUnsorted as $key => $temp) {
if (in_array($temp['tab'], array_keys($this->__settingTabMergeRules))) {
$finalSettingsUnsorted[$key]['tab'] = $this->__settingTabMergeRules[$temp['tab']];
}
}
if ($unsorted) {
return $finalSettingsUnsorted;
}
return $this->__sortFinalSettings($finalSettingsUnsorted);
}
public function serverSettingReadSingle($settingObject, $settingName, $leafKey)
{
// invalidate config.php from php opcode cache
@ -2910,8 +2917,6 @@ class Server extends AppModel
$k = $this->Attribute->generateCorrelation();
}
} else {
$job = ClassRegistry::init('Job');
$job->create();
if ($value == true) {
$jobType = 'jobPurgeCorrelation';
$jobTypeText = 'purge correlations';
@ -2919,6 +2924,8 @@ class Server extends AppModel
$jobType = 'jobGenerateCorrelation';
$jobTypeText = 'generate correlation';
}
$job = ClassRegistry::init('Job');
$job->create();
$data = array(
'worker' => 'default',
'job_type' => $jobTypeText,
@ -3727,156 +3734,6 @@ class Server extends AppModel
));
}
public function upgrade2324($user_id, $jobId = false)
{
$this->cleanCacheFiles();
if (Configure::read('MISP.background_jobs') && $jobId) {
$this->Job = ClassRegistry::init('Job');
$this->Job->id = $jobId;
}
$this->Log = ClassRegistry::init('Log');
$this->Organisation = ClassRegistry::init('Organisation');
$this->Attribute = ClassRegistry::init('Attribute');
$this->Log->create();
$this->Log->save(array(
'org' => 'SYSTEM',
'model' => 'Server',
'model_id' => 0,
'email' => 'SYSTEM',
'action' => 'upgrade_24',
'user_id' => 0,
'title' => 'Upgrade initiated',
'change' => 'Starting the migration of the database to 2.4',
));
if (Configure::read('MISP.background_jobs') && $jobId) {
$this->Job->saveField('progress', 10);
$this->Job->saveField('message', 'Starting the migration of the database to 2.4');
}
$this->query('UPDATE roles SET perm_template = 1 WHERE perm_site_admin = 1 OR perm_admin = 1');
$this->query('UPDATE roles SET perm_sharing_group = 1 WHERE perm_site_admin = 1 OR perm_sync = 1');
$orgs = array('local' => array(), 'external' => array());
$captureRules = array(
'events_org' => array('table' => 'events', 'old' => 'org', 'new' => 'org_id'),
'events_orgc' => array('table' => 'events', 'old' => 'orgc', 'new' => 'orgc_id'),
'jobs_org' => array('table' => 'jobs', 'old' => 'org', 'new' => 'org_id'),
'servers_org' => array('table' => 'servers', 'old' => 'org', 'new' => 'org_id'),
'servers_organization' => array('table' => 'servers', 'old' => 'organization', 'new' => 'remote_org_id'),
'shadow_attributes_org' => array('table' => 'shadow_attributes', 'old' => 'org', 'new' => 'org_id'),
'shadow_attributes_event_org' => array('table' => 'shadow_attributes', 'old' => 'event_org', 'new' => 'event_org_id'),
'threads_org' => array('table' => 'threads', 'old' => 'org', 'new' => 'org_id'),
'users_org' => array('table' => 'users', 'old' => 'org', 'new' => 'org_id'),
);
$rules = array(
'local' => array(
$captureRules['users_org'],
),
'external' => array(
$captureRules['events_org'],
$captureRules['events_orgc'],
$captureRules['shadow_attributes_event_org'],
$captureRules['shadow_attributes_org'],
$captureRules['servers_organization'],
$captureRules['threads_org'],
$captureRules['jobs_org'],
$captureRules['servers_org'],
)
);
foreach ($rules as $k => $type) {
foreach ($type as $rule) {
$temp = ($this->query('SELECT DISTINCT(`' . $rule['old'] . '`) from `' . $rule['table'] . '` WHERE ' . $rule['new'] . '= "";'));
foreach ($temp as $t) {
// in case we have something in the db with a missing org, let's hop over that
if ($t[$rule['table']][$rule['old']] !== '') {
if ($k == 'local' && !in_array($t[$rule['table']][$rule['old']], $orgs[$k])) {
$orgs[$k][] = $t[$rule['table']][$rule['old']];
} elseif ($k == 'external' && !in_array($t[$rule['table']][$rule['old']], $orgs['local']) && !in_array($t[$rule['table']][$rule['old']], $orgs[$k])) {
$orgs[$k][] = $t[$rule['table']][$rule['old']];
}
} else {
$this->Log->create();
$this->Log->save(array(
'org' => 'SYSTEM',
'model' => 'Server',
'model_id' => 0,
'email' => 'SYSTEM',
'action' => 'upgrade_24',
'user_id' => 0,
'title' => '[ERROR] - Detected empty string organisation identifier during the upgrade',
'change' => 'Detected entries in table `' . $rule['table'] . '` where `' . $rule['old'] . '` was blank. This has to be resolved manually!',
));
}
}
}
}
$this->Log->create();
$this->Log->save(array(
'org' => 'SYSTEM',
'model' => 'Server',
'model_id' => 0,
'email' => 'SYSTEM',
'action' => 'upgrade_24',
'user_id' => 0,
'title' => 'Organisation creation',
'change' => 'Detected ' . count($orgs['local']) . ' local organisations and ' . count($orgs['external']) . ' external organisations. Starting organisation creation.',
));
if (Configure::read('MISP.background_jobs') && $jobId) {
$this->Job->saveField('progress', 20);
$this->Job->saveField('message', 'Starting organisation creation');
}
$orgMapping = array();
foreach ($orgs as $k => $orgArray) {
foreach ($orgArray as $org) {
$orgMapping[$org] = $this->Organisation->createOrgFromName($org, $user_id, $k == 'local' ? true : false);
}
}
$this->Log->create();
$this->Log->save(array(
'org' => 'SYSTEM',
'model' => 'Server',
'model_id' => 0,
'email' => 'SYSTEM',
'action' => 'upgrade_24',
'user_id' => 0,
'title' => 'Organisations created and / or mapped',
'change' => 'Captured all missing organisations and created a mapping between the old organisation tag and the organisation IDs. ',
));
if (Configure::read('MISP.background_jobs') && $jobId) {
$this->Job->saveField('progress', 30);
$this->Job->saveField('message', 'Updating all current entries');
}
foreach ($orgMapping as $old => $new) {
foreach ($captureRules as $rule) {
$this->query('UPDATE `' . $rule['table'] . '` SET `' . $rule['new'] . '`="' . $new . '" WHERE (`' . $rule['old'] . '`="' . $old . '" AND `' . $rule['new'] . '`="");');
}
}
if (Configure::read('MISP.background_jobs') && $jobId) {
$this->Job->saveField('progress', 40);
$this->Job->saveField('message', 'Rebuilding all correlations.');
}
//$this->Attribute->generateCorrelation($jobId, 40);
// upgrade correlations. No need to recorrelate, we can be a bit trickier here
// Private = 0 attributes become distribution 1 for both the event and attribute.
// For all intents and purposes, this oversimplification works fine when upgrading from 2.3
// Even though the distribution values stored in the correlation won't be correct, they will provide the exact same realeasability
// Event1 = distribution 0 and Attribute1 distribution 3 would lead to private = 1, so setting distribution = 0 and a_distribution = 0
// will result in the same visibility, etc. Once events / attributes get put into a sharing group this will get recorrelated anyway
// Also by unsetting the org field after the move the changes we ensure that these correlations won't get hit again by the script if we rerun it
// and that we don't accidentally "upgrade" a 2.4 correlation
$this->query('UPDATE correlations SET distribution = 1, a_distribution = 1 WHERE org != "" AND private = 0');
foreach ($orgMapping as $old => $new) {
$this->query('UPDATE correlations SET org_id = "' . $new . '", org = "" WHERE org = "' . $old . '";');
}
if (Configure::read('MISP.background_jobs') && $jobId) {
$this->Job->saveField('progress', 60);
$this->Job->saveField('message', 'Correlations rebuilt. Indexing all tables.');
}
$this->updateDatabase('indexTables');
if (Configure::read('MISP.background_jobs') && $jobId) {
$this->Job->saveField('progress', 100);
$this->Job->saveField('message', 'Upgrade complete.');
}
}
/* returns the version string of a connected instance
* error codes:
* 1: received non json response

View File

@ -7,7 +7,7 @@ class User extends AppModel
{
public $displayField = 'email';
public $orgField = array('Organisation', 'name'); // TODO Audit, LogableBehaviour + org
public $orgField = array('Organisation', 'name');
public $validate = array(
'role_id' => array(
@ -206,7 +206,7 @@ class User extends AppModel
);
public $actsAs = array(
'SysLogLogable.SysLogLogable' => array( // TODO Audit, logable
'SysLogLogable.SysLogLogable' => array(
'userModel' => 'User',
'userKey' => 'user_id',
'change' => 'full',
@ -303,20 +303,17 @@ class User extends AppModel
}
// Checks if the GnuPG key is a valid key, but also import it in the keychain.
// TODO: this will NOT fail on keys that can only be used for signing but not encryption!
// this will NOT fail on keys that can only be used for signing but not encryption!
// the method in verifyUsers will fail in that case.
public function validateGpgkey($check)
{
// LATER first remove the old gpgkey from the keychain
// empty value
if (empty($check['gpgkey'])) {
return true;
}
// we have a clean, hopefully public, key here
// key is entered
try {
require_once 'Crypt/GPG.php';
$gpg = new Crypt_GPG(array('homedir' => Configure::read('GnuPG.homedir'), 'gpgconf' => Configure::read('GnuPG.gpgconf'), 'binary' => (Configure::read('GnuPG.binary') ? Configure::read('GnuPG.binary') : '/usr/bin/gpg')));
@ -331,12 +328,12 @@ class User extends AppModel
}
} catch (Exception $e) {
$this->log($e->getMessage());
return true; // TODO was false
return true;
}
}
// Checks if the certificate is a valid x509 certificate, but also import it in the keychain.
// TODO: this will NOT fail on keys that can only be used for signing but not encryption!
// this will NOT fail on keys that can only be used for signing but not encryption!
// the method in verifyUsers will fail in that case.
public function validateCertificate($check)
{
@ -351,48 +348,7 @@ class User extends AppModel
// Check if $check is a x509 certificate
if (openssl_x509_read($check['certif_public'])) {
try {
App::uses('Folder', 'Utility');
App::uses('FileAccessTool', 'Tools');
$fileAccessTool = new FileAccessTool();
$dir = APP . 'tmp' . DS . 'SMIME';
if (!file_exists($dir)) {
if (!mkdir($dir, 0750, true)) {
throw new MethodNotAllowedException('The SMIME temp directory is not writeable (app/tmp/SMIME).');
}
}
$tempFile = $fileAccessTool->createTempFile($dir, 'SMIME');
$msg_test = $fileAccessTool->writeToFile($tempFile, 'test');
$msg_test_encrypted = $fileAccessTool->createTempFile($dir, 'SMIME');
// encrypt it
if (openssl_pkcs7_encrypt($msg_test, $msg_test_encrypted, $check['certif_public'], null, 0, OPENSSL_CIPHER_AES_256_CBC)) {
unlink($msg_test);
unlink($msg_test_encrypted);
$parse = openssl_x509_parse($check['certif_public']);
// Valid certificate ?
$now = new DateTime("now");
$validTo_time_t_epoch = $parse['validTo_time_t'];
$validTo_time_t = new DateTime("@$validTo_time_t_epoch");
if ($validTo_time_t > $now) {
// purposes smimeencrypt ?
if (($parse['purposes'][5][0] == 1) and ($parse['purposes'][5][2] == 'smimeencrypt')) {
return true;
} else {
return 'This certificate cannot be used to encrypt email';
}
} else {
return 'This certificate is expired';
}
} else {
unlink($msg_test);
unlink($msg_test_encrypted);
return false;
}
} catch (Exception $e) {
unlink($msg_test);
unlink($msg_test_encrypted);
$this->log($e->getMessage());
}
return $this->testSmimeCertificate($check['certif_public']);
} else {
return false;
}
@ -572,58 +528,65 @@ class User extends AppModel
return $results;
}
private function testSmimeCertificate($certif_public) {
$result = array();
try {
App::uses('Folder', 'Utility');
App::uses('FileAccessTool', 'Tools');
$fileAccessTool = new FileAccessTool();
$dir = APP . 'tmp' . DS . 'SMIME';
if (!file_exists($dir)) {
if (!mkdir($dir, 0750, true)) {
throw new MethodNotAllowedException('The SMIME temp directory is not writeable (app/tmp/SMIME).');
}
}
$tempFile = $fileAccessTool->createTempFile($dir, 'SMIME');
$msg_test = $fileAccessTool->writeToFile($tempFile, 'test');
$msg_test_encrypted = $fileAccessTool->createTempFile($dir, 'SMIME');
// encrypt it
if (openssl_pkcs7_encrypt($msg_test, $msg_test_encrypted, $certif_public, null, 0, OPENSSL_CIPHER_AES_256_CBC)) {
$parse = openssl_x509_parse($certif_public);
// Valid certificate ?
$now = new DateTime("now");
$validTo_time_t_epoch = $parse['validTo_time_t'];
$validTo_time_t = new DateTime("@$validTo_time_t_epoch");
if ($validTo_time_t > $now) {
// purposes smimeencrypt ?
if (($parse['purposes'][5][0] == 1) && ($parse['purposes'][5][2] == 'smimeencrypt')) {
$result = true;
} else {
// openssl_pkcs7_encrypt good -- Model/User purposes is NOT GOOD'
$result = 'This certificate cannot be used to encrypt email';
}
} else {
// openssl_pkcs7_encrypt good -- Model/User expired;
$result = 'This certificate is expired';
}
} else {
// openssl_pkcs7_encrypt NOT good -- Model/User
$result = 'This certificate cannot be used to encrypt email';
}
} catch (Exception $e) {
$this->log($e->getMessage());
}
unlink($msg_test);
unlink($msg_test_encrypted);
return $result;
}
public function verifyCertificate()
{
$this->Behaviors->detach('Trim');
$results = array();
$users = $this->find('all', array(
'conditions' => array('not' => array('certif_public' => '')),
//'fields' => array('id', 'email', 'gpgkey'),
'recursive' => -1,
));
foreach ($users as $k => $user) {
$certif_public = $user['User']['certif_public'];
try {
App::uses('Folder', 'Utility');
App::uses('FileAccessTool', 'Tools');
$fileAccessTool = new FileAccessTool();
$dir = APP . 'tmp' . DS . 'SMIME';
if (!file_exists($dir)) {
if (!mkdir($dir, 0750, true)) {
throw new MethodNotAllowedException('The SMIME temp directory is not writeable (app/tmp/SMIME).');
}
}
$tempFile = $fileAccessTool->createTempFile($dir, 'SMIME');
$msg_test = $fileAccessTool->writeToFile($tempFile, 'test');
$msg_test_encrypted = $fileAccessTool->createTempFile($dir, 'SMIME');
// encrypt it
if (openssl_pkcs7_encrypt($msg_test, $msg_test_encrypted, $certif_public, null, 0, OPENSSL_CIPHER_AES_256_CBC)) {
$parse = openssl_x509_parse($certif_public);
// Valid certificate ?
$now = new DateTime("now");
$validTo_time_t_epoch = $parse['validTo_time_t'];
$validTo_time_t = new DateTime("@$validTo_time_t_epoch");
if ($validTo_time_t > $now) {
// purposes smimeencrypt ?
if (($parse['purposes'][5][0] == 1) && ($parse['purposes'][5][2] == 'smimeencrypt')) {
} else {
// openssl_pkcs7_encrypt good -- Model/User purposes is NOT GOOD'
$results[$user['User']['id']][0] = true;
}
} else {
// openssl_pkcs7_encrypt good -- Model/User expired;
$results[$user['User']['id']][0] = true;
}
} else {
// openssl_pkcs7_encrypt NOT good -- Model/User
$results[$user['User']['id']][0] = true;
}
$results[$user['User']['id']][1] = $user['User']['email'];
} catch (Exception $e) {
$this->log($e->getMessage());
$result = $this->testSmimeCertificate($user['User']['certif_public']);
if ($result !== true) {
$results[$user['User']['id']] = array(0 => true, 1 => $user['User']['email']);
}
unlink($msg_test);
unlink($msg_test_encrypted);
}
return $results;
}
@ -782,20 +745,14 @@ class User extends AppModel
));
return true;
}
if (isset($user['User']['disabled']) && $user['User']['disabled']) {
if (!empty($user['User']['disabled'])) {
return true;
}
$failed = false;
$failureReason = "";
// check if the e-mail can be encrypted
$canEncryptGPG = false;
if (isset($user['User']['gpgkey']) && !empty($user['User']['gpgkey'])) {
$canEncryptGPG = true;
}
$canEncryptSMIME = false;
if (isset($user['User']['certif_public']) && !empty($user['User']['certif_public']) && Configure::read('SMIME.enabled')) {
$canEncryptSMIME = true;
}
$canEncryptGPG = isset($user['User']['gpgkey']) && !empty($user['User']['gpgkey']);
$canEncryptSMIME = isset($user['User']['certif_public']) && !empty($user['User']['certif_public']) && Configure::read('SMIME.enabled');
// If bodyonlyencrypted is enabled and the user has no encryption key, use the alternate body (if it exists)
if (Configure::read('GnuPG.bodyonlyencrypted') && !$canEncryptSMIME && !$canEncryptGPG && $bodyNoEnc) {
@ -803,21 +760,6 @@ class User extends AppModel
}
$body = str_replace('\n', PHP_EOL, $body);
if ($canEncryptGPG) {
// Sign the body
require_once 'Crypt/GPG.php';
try {
$gpg = new Crypt_GPG(array('homedir' => Configure::read('GnuPG.homedir'), 'gpgconf' => Configure::read('GnuPG.gpgconf'), 'binary' => (Configure::read('GnuPG.binary') ? Configure::read('GnuPG.binary') : '/usr/bin/gpg'), 'debug')); // , 'debug' => true
if (Configure::read('GnuPG.sign')) {
$gpg->addSignKey(Configure::read('GnuPG.email'), Configure::read('GnuPG.password'));
$body = $gpg->sign($body, Crypt_GPG::SIGN_MODE_CLEAR);
}
} catch (Exception $e) {
$failureReason = " the message could not be signed. The following error message was returned by gpg: " . $e->getMessage();
$this->log($e->getMessage());
$failed = true;
}
}
$Email = new CakeEmail();
// If we cannot encrypt the mail and the server settings restricts sending unencrypted messages, return false
if (!$failed && Configure::read('GnuPG.onlyencrypted') && !$canEncryptGPG && !$canEncryptSMIME) {
@ -826,6 +768,98 @@ class User extends AppModel
}
// Let's encrypt the message if we can
if (!$failed && $canEncryptGPG) {
$encryptionResult = $this->__encryptUsingGPG($Email, $body, $subject, $user);
if (isset($encryptionResult['failed'])) {
$failed = true;
}
if (isset($encryptionResult['failureReason'])) {
$failureReason = $encryptionResult['failureReason'];
}
}
// SMIME if not GPG key
if (!$failed && !$canEncryptGPG && $canEncryptSMIME) {
$encryptionResult = $this->__encryptUsingSmime($Email, $body, $subject);
if (isset($encryptionResult['failed'])) {
$failed = true;
}
if (isset($encryptionResult['failureReason'])) {
$failureReason = $encryptionResult['failureReason'];
}
}
$replyToLog = '';
if (!$failed) {
$result = $this->__finaliseAndSendEmail($replyToUser, $Email, $replyToLog, $user, $subject, $body);
}
$this->Log = ClassRegistry::init('Log');
$this->Log->create();
if (!$failed && $result) {
$this->Log->save(array(
'org' => 'SYSTEM',
'model' => 'User',
'model_id' => $user['User']['id'],
'email' => $user['User']['email'],
'action' => 'email',
'title' => 'Email ' . $replyToLog . ' to ' . $user['User']['email'] . ' sent, titled "' . $subject . '".',
'change' => null,
));
return true;
} else {
if (empty($failureReason)) {
$failureReason = " there was an error sending the e-mail.";
}
$this->Log->save(array(
'org' => 'SYSTEM',
'model' => 'User',
'model_id' => $user['User']['id'],
'email' => $user['User']['email'],
'action' => 'email',
'title' => 'Email ' . $replyToLog . ' to ' . $user['User']['email'] . ', titled "' . $subject . '" failed. Reason: ' . $failureReason,
'change' => null,
));
}
return false;
}
private function __finaliseAndSendEmail($replyToUser, &$Email, &$replyToLog, $user, $subject, $body)
{
// If the e-mail is sent on behalf of a user, then we want the target user to be able to respond to the sender
// For this reason we should also attach the public key of the sender along with the message (if applicable)
if ($replyToUser != false) {
$Email->replyTo($replyToUser['User']['email']);
if (!empty($replyToUser['User']['gpgkey'])) {
$Email->attachments(array('gpgkey.asc' => array('data' => $replyToUser['User']['gpgkey'])));
} elseif (!empty($replyToUser['User']['certif_public'])) {
$Email->attachments(array($replyToUser['User']['email'] . '.pem' => array('data' => $replyToUser['User']['certif_public'])));
}
$replyToLog = 'from ' . $replyToUser['User']['email'];
}
$Email->from(Configure::read('MISP.email'));
$Email->returnPath(Configure::read('MISP.email'));
$Email->to($user['User']['email']);
$Email->subject($subject);
$Email->emailFormat('text');
$result = $Email->send($body);
$Email->reset();
return $result;
}
private function __encryptUsingGPG(&$Email, &$body, $subject, $user)
{
$failed = false;
// Sign the body
require_once 'Crypt/GPG.php';
try {
$gpg = new Crypt_GPG(array('homedir' => Configure::read('GnuPG.homedir'), 'gpgconf' => Configure::read('GnuPG.gpgconf'), 'binary' => (Configure::read('GnuPG.binary') ? Configure::read('GnuPG.binary') : '/usr/bin/gpg'), 'debug')); // , 'debug' => true
if (Configure::read('GnuPG.sign')) {
$gpg->addSignKey(Configure::read('GnuPG.email'), Configure::read('GnuPG.password'));
$body = $gpg->sign($body, Crypt_GPG::SIGN_MODE_CLEAR);
}
} catch (Exception $e) {
$failureReason = " the message could not be signed. The following error message was returned by gpg: " . $e->getMessage();
$this->log($e->getMessage());
$failed = true;
}
if (!$failed) {
$keyImportOutput = $gpg->importKey($user['User']['gpgkey']);
try {
$key = $gpg->getKeys($keyImportOutput['fingerprint']);
@ -852,119 +886,75 @@ class User extends AppModel
$failed = true;
}
}
// SMIME if not GPG key
if (!$failed && !$canEncryptGPG && $canEncryptSMIME) {
try {
$prependedBody = 'Content-Transfer-Encoding: 7bit' . PHP_EOL . 'Content-Type: text/plain;' . PHP_EOL . ' charset=us-ascii' . PHP_EOL . PHP_EOL . $body;
App::uses('Folder', 'Utility');
App::uses('FileAccessTool', 'Tools');
$fileAccessTool = new FileAccessTool();
$dir = APP . 'tmp' . DS . 'SMIME';
if (!file_exists($dir)) {
if (!mkdir($dir, 0750, true)) {
throw new MethodNotAllowedException('The SMIME temp directory is not writeable (app/tmp/SMIME).');
}
if (!empty($failed)) {
return array('failed' => $failed, 'failureReason' => $failureReason);
}
return true;
}
private function __encryptUsingSmime(&$Email, &$body, $subject)
{
try {
$prependedBody = 'Content-Transfer-Encoding: 7bit' . PHP_EOL . 'Content-Type: text/plain;' . PHP_EOL . ' charset=us-ascii' . PHP_EOL . PHP_EOL . $body;
App::uses('Folder', 'Utility');
App::uses('FileAccessTool', 'Tools');
$fileAccessTool = new FileAccessTool();
$dir = APP . 'tmp' . DS . 'SMIME';
if (!file_exists($dir)) {
if (!mkdir($dir, 0750, true)) {
throw new MethodNotAllowedException('The SMIME temp directory is not writeable (app/tmp/SMIME).');
}
}
// save message to file
$tempFile = $fileAccessTool->createTempFile($dir, 'SMIME');
$msg = $fileAccessTool->writeToFile($tempFile, $prependedBody);
$headers_smime = array("To" => $user['User']['email'], "From" => Configure::read('MISP.email'), "Subject" => $subject);
$canSign = true;
if (
!empty(Configure::read('SMIME.cert_public_sign')) &&
is_readable(Configure::read('SMIME.cert_public_sign')) &&
!empty(Configure::read('SMIME.key_sign')) &&
is_readable(Configure::read('SMIME.key_sign'))
) {
$signed = $fileAccessTool->createTempFile($dir, 'SMIME');
if (openssl_pkcs7_sign($msg, $signed, 'file://'.Configure::read('SMIME.cert_public_sign'), array('file://'.Configure::read('SMIME.key_sign'), Configure::read('SMIME.password')), array(), PKCS7_TEXT)) {
$bodySigned = $fileAccessTool->readFromFile($signed);
unlink($msg);
unlink($signed);
} else {
unlink($msg);
unlink($signed);
throw new Exception('Failed while attempting to sign the SMIME message.');
}
// save message to file
$tempFile = $fileAccessTool->createTempFile($dir, 'SMIME');
$msg = $fileAccessTool->writeToFile($tempFile, $prependedBody);
$headers_smime = array("To" => $user['User']['email'], "From" => Configure::read('MISP.email'), "Subject" => $subject);
$canSign = true;
if (empty(Configure::read('SMIME.cert_public_sign')) || !is_readable(Configure::read('SMIME.cert_public_sign'))) {
$canSign = false;
}
if (empty(Configure::read('SMIME.key_sign')) || !is_readable(Configure::read('SMIME.key_sign'))) {
$canSign = false;
}
if ($canSign) {
$signed = $fileAccessTool->createTempFile($dir, 'SMIME');
if (openssl_pkcs7_sign($msg, $signed, 'file://'.Configure::read('SMIME.cert_public_sign'), array('file://'.Configure::read('SMIME.key_sign'), Configure::read('SMIME.password')), array(), PKCS7_TEXT)) {
$bodySigned = $fileAccessTool->readFromFile($signed);
unlink($msg);
unlink($signed);
} else {
unlink($msg);
unlink($signed);
throw new Exception('Failed while attempting to sign the SMIME message.');
}
// save message to file
$tempFile = $fileAccessTool->createTempFile($dir, 'SMIME');
$msg_signed = $fileAccessTool->writeToFile($tempFile, $bodySigned);
} else {
$msg_signed = $msg;
}
$msg_signed_encrypted = $fileAccessTool->createTempFile($dir, 'SMIME');
// encrypt it
if (openssl_pkcs7_encrypt($msg_signed, $msg_signed_encrypted, $user['User']['certif_public'], $headers_smime, 0, OPENSSL_CIPHER_AES_256_CBC)) {
$bodyEncSig = $fileAccessTool->readFromFile($msg_signed_encrypted);
unlink($msg_signed);
unlink($msg_signed_encrypted);
$parts = explode("\n\n", $bodyEncSig);
$bodyEncSig = $parts[1];
// SMIME transport (hardcoded headers
$Email = $Email->transport('Smime');
$body = $bodyEncSig;
} else {
unlink($msg_signed);
unlink($msg_signed_encrypted);
throw new Exception('Could not encrypt the SMIME message.');
}
} catch (Exception $e) {
// despite the user having a certificate. This must mean that there is an issue with the user's certificate.
$failureReason = " the message could not be encrypted because there was an issue with the user's public certificate. The following error message was returned by openssl: " . $e->getMessage();
$this->log($e->getMessage());
$failed = true;
$msg_signed = $fileAccessTool->writeToFile($tempFile, $bodySigned);
} else {
$msg_signed = $msg;
}
}
$replyToLog = '';
if (!$failed) {
// If the e-mail is sent on behalf of a user, then we want the target user to be able to respond to the sender
// For this reason we should also attach the public key of the sender along with the message (if applicable)
if ($replyToUser != false) {
$Email->replyTo($replyToUser['User']['email']);
if (!empty($replyToUser['User']['gpgkey'])) {
$Email->attachments(array('gpgkey.asc' => array('data' => $replyToUser['User']['gpgkey'])));
} elseif (!empty($replyToUser['User']['certif_public'])) {
$Email->attachments(array($replyToUser['User']['email'] . '.pem' => array('data' => $replyToUser['User']['certif_public'])));
}
$replyToLog = 'from ' . $replyToUser['User']['email'];
$msg_signed_encrypted = $fileAccessTool->createTempFile($dir, 'SMIME');
// encrypt it
if (openssl_pkcs7_encrypt($msg_signed, $msg_signed_encrypted, $user['User']['certif_public'], $headers_smime, 0, OPENSSL_CIPHER_AES_256_CBC)) {
$bodyEncSig = $fileAccessTool->readFromFile($msg_signed_encrypted);
unlink($msg_signed);
unlink($msg_signed_encrypted);
$parts = explode("\n\n", $bodyEncSig);
$bodyEncSig = $parts[1];
// SMIME transport (hardcoded headers
$Email = $Email->transport('Smime');
$body = $bodyEncSig;
} else {
unlink($msg_signed);
unlink($msg_signed_encrypted);
throw new Exception('Could not encrypt the SMIME message.');
}
$Email->from(Configure::read('MISP.email'));
$Email->returnPath(Configure::read('MISP.email'));
$Email->to($user['User']['email']);
$Email->subject($subject);
$Email->emailFormat('text');
$result = $Email->send($body);
$Email->reset();
} catch (Exception $e) {
// despite the user having a certificate. This must mean that there is an issue with the user's certificate.
$result['failureReason'] = " the message could not be encrypted because there was an issue with the user's public certificate. The following error message was returned by openssl: " . $e->getMessage();
$this->log($e->getMessage());
$result['failed'] = true;
}
$this->Log = ClassRegistry::init('Log');
$this->Log->create();
if (!$failed && $result) {
$this->Log->save(array(
'org' => 'SYSTEM',
'model' => 'User',
'model_id' => $user['User']['id'],
'email' => $user['User']['email'],
'action' => 'email',
'title' => 'Email ' . $replyToLog . ' to ' . $user['User']['email'] . ' sent, titled "' . $subject . '".',
'change' => null,
));
return true;
} else {
if (isset($result) && !$result) {
$failureReason = " there was an error sending the e-mail.";
}
$this->Log->save(array(
'org' => 'SYSTEM',
'model' => 'User',
'model_id' => $user['User']['id'],
'email' => $user['User']['email'],
'action' => 'email',
'title' => 'Email ' . $replyToLog . ' to ' . $user['User']['email'] . ', titled "' . $subject . '" failed. Reason: ' . $failureReason,
'change' => null,
));
}
return false;
return $result;
}
public function adminMessageResolve($message)
@ -1033,12 +1023,12 @@ class User extends AppModel
{
$fields = array();
$fields = array_merge($fields, array_keys($this->getColumnTypes()));
if (($key = array_search('gpgkey', $fields)) !== false) {
unset($fields[$key]);
}
if (($key = array_search('certif_public', $fields)) !== false) {
unset($fields[$key]);
foreach ($fields as $k => $field) {
if (in_array($field, array('gpgkey', 'certif_public'))) {
unset($fields[$k]);
}
}
$fields = array_values($fields);
$relatedModels = array_keys($this->belongsTo);
foreach ($relatedModels as $relatedModel) {
$fields[] = $relatedModel . '.*';

View File

@ -461,7 +461,7 @@ class StixBuilder():
killchain = self.create_killchain(category)
labels = self.create_labels(attribute)
attribute_value = attribute.value if attribute_type != "AS" else self.define_attribute_value(attribute.value, attribute.comment)
pattern = mispTypesMapping[attribute_type]['pattern'](attribute_type, attribute_value, b64encode(attribute.data.getbuffer()).decode()[1:-1]) if 'data' in attribute else self.define_pattern(attribute_type, attribute_value)
pattern = mispTypesMapping[attribute_type]['pattern'](attribute_type, attribute_value, b64encode(attribute.data.getbuffer()).decode()[1:-1]) if ('data' in attribute and attribute.data) else self.define_pattern(attribute_type, attribute_value)
indicator_args = {'id': indicator_id, 'type': 'indicator', 'labels': labels, 'kill_chain_phases': killchain,
'valid_from': attribute.timestamp, 'created_by_ref': self.identity_id, 'pattern': pattern}
if hasattr(attribute, 'comment') and attribute.comment:
@ -644,9 +644,11 @@ class StixBuilder():
@staticmethod
def create_labels(attribute):
return ['misp:type="{}"'.format(attribute.type),
'misp:category="{}"'.format(attribute.category),
'misp:to_ids="{}"'.format(attribute.to_ids)]
labels = ['misp:type="{}"'.format(attribute.type),
'misp:category="{}"'.format(attribute.category),
'misp:to_ids="{}"'.format(attribute.to_ids)]
labels += [tag.name for tag in attribute.Tag]
return labels
@staticmethod
def create_object_labels(name, category, to_ids):

View File

@ -278,6 +278,9 @@ class StixParser():
attribute_type = self.get_misp_type(labels)
attribute_category = self.get_misp_category(labels)
attribute = {'type': attribute_type, 'category': attribute_category}
tags = [{'name': label} for label in labels[3:]]
if tags:
attribute['Tag'] = tags
stix_type = o._type
if stix_type == 'vulnerability':
value = o.get('name')