CVE-2022-29528 (published 2022-04-20T23:15Z)

An issue was discovered in MISP before 2.4.158. PHAR deserialization can occur.

Severity: HIGH
CVSS v2 base score: 7.5 (AV:N/AC:L/Au:N/C:P/I:P/A:P)
CVSS v3 base score: 9.8, CRITICAL (CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H)

Weakness information:
cwe_id: CWE-502
cwe_name: Deserialization of Untrusted Data
description: The product deserializes untrusted data without sufficiently verifying that the resulting data will be valid.
extended_description: 
            It is often convenient to serialize objects for communication or to save them for later use. However, deserialized data or code can often be modified without using the provided accessor functions if it does not use cryptography to protect itself. Furthermore, any cryptography would still be client-side security -- which is a dangerous security assumption.
            Data that is untrusted can not be trusted to be well-formed.
            When developers place no restrictions on "gadget chains," or series of instances and method invocations that can self-execute during the deserialization process (i.e., before the object is returned to the caller), it is sometimes possible for attackers to leverage them to perform unauthorized actions, like generating a shell.
         
         

Fix information:
repo_url: https://github.com/MISP/MISP
commit_details: repo_url: https://github.com/MISP/MISP
author: iglocska
author_date: 2022-04-17 16:30:14+02:00
msg: fix: [security] unregister phar from stream wrappers globally for all Model code

- blanket protection against phar deserialization vulnerabilities

- as reported by Dawid Czarnecki of Zigrin Security
repository: repo_url: https://github.com/MISP/MISP
repo_name: MISP/MISP
description: MISP (core software) - Open Source Threat Intelligence and Sharing Platform
repo_language: PHP
owner: MISP
file_changes: filename: AppModel.php
change_type: ModificationType.MODIFY
diff: @@ -51,6 +51,9 @@ class AppModel extends Model
     {
         parent::__construct($id, $table, $ds);
         $this->findMethods['column'] = true;
+        if (in_array('phar', stream_get_wrappers())) {
+            stream_wrapper_unregister('phar');
+        }
     }
 
     // deprecated, use $db_changes

programming_language: PHP
repo_url: https://github.com/MISP/MISP
commit_details: repo_url: https://github.com/MISP/MISP
author: iglocska
author_date: 2022-04-17 18:25:51+02:00
msg: fix: [security] Sanitise paths for several file interactions

- remove :// anywhere we don't expect a protocol to be supplied
- remove phar:// in certauth plugin's fetcher

- as reported by Dawid Czarnecki of Zigrin Security
repository: repo_url: https://github.com/MISP/MISP
repo_name: MISP/MISP
description: MISP (core software) - Open Source Threat Intelligence and Sharing Platform
repo_language: PHP
owner: MISP
file_changes: filename: AppModel.php
change_type: ModificationType.MODIFY
diff: @@ -2588,6 +2588,7 @@ class AppModel extends Model
             App::uses('KafkaPubTool', 'Tools');
             $kafkaPubTool = new KafkaPubTool();
             $rdkafkaIni = Configure::read('Plugin.Kafka_rdkafka_config');
+            $rdkafkaIni = mb_ereg_replace("/\:\/\//", '', $rdkafkaIni);
             $kafkaConf = array();
             if (!empty($rdkafkaIni)) {
                 $kafkaConf = parse_ini_file($rdkafkaIni);

programming_language: PHP
filename: Feed.php
change_type: ModificationType.MODIFY
diff: @@ -126,12 +126,13 @@ class Feed extends AppModel
     public function urlOrExistingFilepath($fields)
     {
         if ($this->isFeedLocal($this->data)) {
+            $path = mb_ereg_replace("/\:\/\//", '', $this->data['Feed']['url']);
             if ($this->data['Feed']['source_format'] == 'misp') {
-                if (!is_dir($this->data['Feed']['url'])) {
+                if (!is_dir($path)) {
                     return 'For MISP type local feeds, please specify the containing directory.';
                 }
             } else {
-                if (!file_exists($this->data['Feed']['url'])) {
+                if (!file_exists($path)) {
                     return 'Invalid path or file not found. Make sure that the path points to an existing file that is readable and watch out for typos.';
                 }
             }
@@ -1929,6 +1930,7 @@ class Feed extends AppModel
     private function feedGetUri($feed, $uri, HttpSocket $HttpSocket = null)
     {
         if ($this->isFeedLocal($feed)) {
+            $uri = mb_ereg_replace("/\:\/\//", '', $uri);
             if (file_exists($uri)) {
                 return FileAccessTool::readFromFile($uri);
             } else {

programming_language: PHP
filename: CertificateAuthenticate.php
change_type: ModificationType.MODIFY
diff: @@ -21,274 +21,275 @@ App::uses('BaseAuthenticate', 'Controller/Component/Auth');
 
 class CertificateAuthenticate extends BaseAuthenticate
 {
-	/**
-	 * Holds the certificate issuer information (available at SSL_CLIENT_I_DN)
-	 *
-	 * @var array
-	 */
-	protected static $ca;
+    /**
+     * Holds the certificate issuer information (available at SSL_CLIENT_I_DN)
+     *
+     * @var array
+     */
+    protected static $ca;
 
-	/**
-	 * Holds the certificate user information (available at SSL_CLIENT_S_DN)
-	 *
-	 * @var array
-	 */
-	protected static $client;
+    /**
+     * Holds the certificate user information (available at SSL_CLIENT_S_DN)
+     *
+     * @var array
+     */
+    protected static $client;
 
-	/**
-	 * Holds the user information
-	 *
-	 * @var array
-	 */
-	protected static $user;
+    /**
+     * Holds the user information
+     *
+     * @var array
+     */
+    protected static $user;
 
-	/**
-	 * Class constructor.
-	 *
-	 * This should only be called once per request, so it doesn't need to store values in
-	 * the instance. Simply checks if the certificate is valid (against configured valid issuers)
-	 * and returns the user information encoded.
-	 */
-	public function __construct()
-	{
-		self::$ca = self::$client = false;
+    /**
+     * Class constructor.
+     *
+     * This should only be called once per request, so it doesn't need to store values in
+     * the instance. Simply checks if the certificate is valid (against configured valid issuers)
+     * and returns the user information encoded.
+     */
+    public function __construct()
+    {
+        self::$ca = self::$client = false;
 
-		if (isset($_SERVER['SSL_CLIENT_I_DN'])) {
-			$CA = self::parse($_SERVER['SSL_CLIENT_I_DN'], Configure::read('CertAuth.mapCa'));
-			// only valid CAs, if this was configured
-			if ($ca=Configure::read('CertAuth.ca')) {
-				$k = Configure::read('CertAuth.caId');
-				if (!$k) $k = 'CN';
-				$id = (isset($CA[$k]))?($CA[$k]):(false);
+        if (isset($_SERVER['SSL_CLIENT_I_DN'])) {
+            $CA = self::parse($_SERVER['SSL_CLIENT_I_DN'], Configure::read('CertAuth.mapCa'));
+            // only valid CAs, if this was configured
+            if ($ca=Configure::read('CertAuth.ca')) {
+                $k = Configure::read('CertAuth.caId');
+                if (!$k) $k = 'CN';
+                $id = (isset($CA[$k]))?($CA[$k]):(false);
 
-				if (!$id) {
-					$CA = false;
-				} else if (is_array($ca)) {
-					if (!in_array($id, $ca)) $CA = false;
-				} else if ($ca!=$id) {
-					$CA = false;
-				}
-				unset($id, $k);
-			}
-			self::$ca = $CA;
-			unset($CA, $ca);
-		}
+                if (!$id) {
+                    $CA = false;
+                } else if (is_array($ca)) {
+                    if (!in_array($id, $ca)) $CA = false;
+                } else if ($ca!=$id) {
+                    $CA = false;
+                }
+                unset($id, $k);
+            }
+            self::$ca = $CA;
+            unset($CA, $ca);
+        }
 
-		if (self::$ca) {
-			$map = Configure::read('CertAuth.map');
-			if(isset($_SERVER['SSL_CLIENT_S_DN'])) {
-				self::$client = self::parse($_SERVER['SSL_CLIENT_S_DN'], $map);
-			} else {
-				self::$client = array();
-			}
-			foreach($map as $n=>$d) {
-				if(isset($_SERVER[$n])) {
-					self::$client[$d] = $_SERVER[$n];
-				}
-				unset($map[$n], $n, $d);
-			}
-			unset($map);
-			if(!self::$client) {
-				self::$client = false;
-			}
-		}
-	}
+        if (self::$ca) {
+            $map = Configure::read('CertAuth.map');
+            if(isset($_SERVER['SSL_CLIENT_S_DN'])) {
+                self::$client = self::parse($_SERVER['SSL_CLIENT_S_DN'], $map);
+            } else {
+                self::$client = array();
+            }
+            foreach($map as $n=>$d) {
+                if(isset($_SERVER[$n])) {
+                    self::$client[$d] = $_SERVER[$n];
+                }
+                unset($map[$n], $n, $d);
+            }
+            unset($map);
+            if(!self::$client) {
+                self::$client = false;
+            }
+        }
+    }
 
-	/**
-	 * Parse certificate extensions
-	 *
-	 * @TODO    this should properly address the RFC
-	 * @param   string            $s    text to be parsed
-	 * @param   (optional) array  $map  array of mapping extension to User fields
-	 * @return  array             parsed values
-	 */
-	private static function parse($s, $map=null)
-	{
-		$r=array();
-		if (preg_match_all('#(^/?|\/|\,)([a-zA-Z]+)\=([^\/\,]+)#', $s, $m)) {
-			foreach ($m[2] as $i=>$k) {
-				if ($map) {
-					if (isset($map[$k])) {
-						$k = $map[$k];
-					} else {
-						$k = null;
-					}
-				}
-				if ($k) {
-					$v = $m[3][$i];
-					$r[$k] = $v;
-				}
-				unset($m[0][$i], $m[1][$i], $m[2][$i], $m[3][$i], $k, $v, $i);
-			}
-		}
-		return $r;
-	}
+    /**
+     * Parse certificate extensions
+     *
+     * @TODO    this should properly address the RFC
+     * @param   string            $s    text to be parsed
+     * @param   (optional) array  $map  array of mapping extension to User fields
+     * @return  array             parsed values
+     */
+    private static function parse($s, $map=null)
+    {
+        $r=array();
+        if (preg_match_all('#(^/?|\/|\,)([a-zA-Z]+)\=([^\/\,]+)#', $s, $m)) {
+            foreach ($m[2] as $i=>$k) {
+                if ($map) {
+                    if (isset($map[$k])) {
+                        $k = $map[$k];
+                    } else {
+                        $k = null;
+                    }
+                }
+                if ($k) {
+                    $v = $m[3][$i];
+                    $r[$k] = $v;
+                }
+                unset($m[0][$i], $m[1][$i], $m[2][$i], $m[3][$i], $k, $v, $i);
+            }
+        }
+        return $r;
+    }
 
-	// to enable stateless authentication
-	public function getUser(CakeRequest $request)
-	{
-		if (empty(self::$user)) {
-			if (self::$client) {
-				self::$user = self::$client;
-				// If $sync is true, allow the creation of the user from the certificate
-				$sync = Configure::read('CertAuth.syncUser');
-				$url = Configure::read('CertAuth.restApi.url');
-				if ($sync && $url) {
-					if (!self::getRestUser()) return false;
-				}
+    // to enable stateless authentication
+    public function getUser(CakeRequest $request)
+    {
+        if (empty(self::$user)) {
+            if (self::$client) {
+                self::$user = self::$client;
+                // If $sync is true, allow the creation of the user from the certificate
+                $sync = Configure::read('CertAuth.syncUser');
+                $url = Configure::read('CertAuth.restApi.url');
+                if ($sync && $url) {
+                    if (!self::getRestUser()) return false;
+                }
 
-				// find and fill user with model
-				$userModelKey = empty(Configure::read('CertAuth.userModelKey')) ? 'email' : Configure::read('CertAuth.userModelKey');
-				$userDefaults = Configure::read('CertAuth.userDefaults');
-				$this->User = ClassRegistry::init('User');
-				if (!empty(self::$user[$userModelKey])) {
-					$existingUser = $this->User->find('first', array(
-						'conditions' => array($userModelKey => self::$user[$userModelKey]),
-						'recursive' => false
-					));
-				}
-				if ($existingUser) {
-					if ($sync) {
-						if (!isset(self::$user['org_id']) && isset(self::$user['org'])) {
-							self::$user['org_id'] = $this->User->Organisation->createOrgFromName(self::$user['org'], $existingUser['User']['id'], true);
-							// reset user defaults in case it's a different org_id
-							if (self::$user['org_id'] && $existingUser['User']['org_id'] != self::$user['org_id']) {
-								if ($userDefaults && is_array($userDefaults)) {
-									self::$user = array_merge($userDefaults + self::$user);
-								}
-							}
-							unset(self::$user['org']);
-						}
-						$write = array();
-						foreach (self::$user as $k => $v) {
-							if (isset($existingUser['User'][$k]) && trim($existingUser['User'][$k]) != trim($v)) {
-								$write[] = $k;
-								$existingUser['User'][$k] = trim($v);
-							}
-						}
-						if (!empty($write) && !$this->User->save($existingUser['User'], true, $write)) {
-							CakeLog::write('alert', 'Could not update model at database with RestAPI data.');
-						}
-					}
-					self::$user = $this->User->getAuthUser($existingUser['User']['id']);
-					if (isset(self::$user['gpgkey'])) unset(self::$user['gpgkey']);
-				} else if ($sync && !empty(self::$user)) {
-					$org = isset(self::$client['org']) ? self::$client['org'] : null;
-					if ($org == null) return false;
-					if (!isset(self::$user['org_id']) && isset(self::$user['org'])) {
-						self::$user['org_id'] = $this->User->Organisation->createOrgFromName($org, 0, true);
-						unset(self::$user['org']);
-					}
-					if ($userDefaults && is_array($userDefaults)) {
-						self::$user = array_merge(self::$user, $userDefaults);
-					}
-					$this->User->create();
-					if ($this->User->save(self::$user)) {
-						$id = $this->User->id;
-						self::$user = $this->User->getAuthUser($id);
-						if (isset(self::$user['gpgkey'])) unset(self::$user['gpgkey']);
-					} else {
-						CakeLog::write('alert', 'Could not insert model at database from RestAPI data. Reason: ' . json_encode($this->User->validationErrors));
-					}
-				} else {
-					// No match -- User doesn't exist !!!
-					self::$user = false;
-				}
-			}
-		}
-		return self::$user;
-	}
+                // find and fill user with model
+                $userModelKey = empty(Configure::read('CertAuth.userModelKey')) ? 'email' : Configure::read('CertAuth.userModelKey');
+                $userDefaults = Configure::read('CertAuth.userDefaults');
+                $this->User = ClassRegistry::init('User');
+                if (!empty(self::$user[$userModelKey])) {
+                    $existingUser = $this->User->find('first', array(
+                        'conditions' => array($userModelKey => self::$user[$userModelKey]),
+                        'recursive' => false
+                    ));
+                }
+                if ($existingUser) {
+                    if ($sync) {
+                        if (!isset(self::$user['org_id']) && isset(self::$user['org'])) {
+                            self::$user['org_id'] = $this->User->Organisation->createOrgFromName(self::$user['org'], $existingUser['User']['id'], true);
+                            // reset user defaults in case it's a different org_id
+                            if (self::$user['org_id'] && $existingUser['User']['org_id'] != self::$user['org_id']) {
+                                if ($userDefaults && is_array($userDefaults)) {
+                                    self::$user = array_merge($userDefaults + self::$user);
+                                }
+                            }
+                            unset(self::$user['org']);
+                        }
+                        $write = array();
+                        foreach (self::$user as $k => $v) {
+                            if (isset($existingUser['User'][$k]) && trim($existingUser['User'][$k]) != trim($v)) {
+                                $write[] = $k;
+                                $existingUser['User'][$k] = trim($v);
+                            }
+                        }
+                        if (!empty($write) && !$this->User->save($existingUser['User'], true, $write)) {
+                            CakeLog::write('alert', 'Could not update model at database with RestAPI data.');
+                        }
+                    }
+                    self::$user = $this->User->getAuthUser($existingUser['User']['id']);
+                    if (isset(self::$user['gpgkey'])) unset(self::$user['gpgkey']);
+                } else if ($sync && !empty(self::$user)) {
+                    $org = isset(self::$client['org']) ? self::$client['org'] : null;
+                    if ($org == null) return false;
+                    if (!isset(self::$user['org_id']) && isset(self::$user['org'])) {
+                        self::$user['org_id'] = $this->User->Organisation->createOrgFromName($org, 0, true);
+                        unset(self::$user['org']);
+                    }
+                    if ($userDefaults && is_array($userDefaults)) {
+                        self::$user = array_merge(self::$user, $userDefaults);
+                    }
+                    $this->User->create();
+                    if ($this->User->save(self::$user)) {
+                        $id = $this->User->id;
+                        self::$user = $this->User->getAuthUser($id);
+                        if (isset(self::$user['gpgkey'])) unset(self::$user['gpgkey']);
+                    } else {
+                        CakeLog::write('alert', 'Could not insert model at database from RestAPI data. Reason: ' . json_encode($this->User->validationErrors));
+                    }
+                } else {
+                    // No match -- User doesn't exist !!!
+                    self::$user = false;
+                }
+            }
+        }
+        return self::$user;
+    }
 
-	// to enable stateless authentication
-	public function authenticate(CakeRequest $request, CakeResponse $response)
-	{
-		return self::getUser($request);
-	}
+    // to enable stateless authentication
+    public function authenticate(CakeRequest $request, CakeResponse $response)
+    {
+        return self::getUser($request);
+    }
 
-	/**
-	 * Fetches user information from external REST API
-	 *
-	 * Valid options (should be configured under CertAuth.restApi):
-	 *
-	 * @param   (optional) array  $options  API configuration
-	 *  url     (string) Where to fetch information from
-	 *  headers (array)  list of additional headers to be used, reserved for authentication tokens
-	 *  params  (array)  mapping of additional params to be included at the url, uses $user values
-	 *  map     (array)  mapping of the return values to be added to the self::$user
-	 * @return  array    updated user object
-	 */
-	public function getRestUser($options=null, $user=null)
-	{
-		if (is_null($options)) {
-			$options = Configure::read('CertAuth.restApi');
-		}
-		if (!is_null($user)) {
-			self::$user = $user;
-		}
+    /**
+     * Fetches user information from external REST API
+     *
+     * Valid options (should be configured under CertAuth.restApi):
+     *
+     * @param   (optional) array  $options  API configuration
+     *  url     (string) Where to fetch information from
+     *  headers (array)  list of additional headers to be used, reserved for authentication tokens
+     *  params  (array)  mapping of additional params to be included at the url, uses $user values
+     *  map     (array)  mapping of the return values to be added to the self::$user
+     * @return  array    updated user object
+     */
+    public function getRestUser($options=null, $user=null)
+    {
+        if (is_null($options)) {
+            $options = Configure::read('CertAuth.restApi');
+        }
+        if (!is_null($user)) {
+            self::$user = $user;
+        }
 
-		if (!isset($options['url'])) {
-			return null;
-		}
+        if (!isset($options['url'])) {
+            return null;
+        }
 
-		// Create a stream
-		$req = array(
-			'http'=>array(
-				'method'=>'GET',
-				'header'=>"Accept: application/json\r\n"
-			),
-		);
-		if (isset($options['headers'])) {
-			foreach ($options['headers'] as $k=>$v) {
-				if (is_int($k)) {
-					$req['header'] .= "{$v}\r\n";
-				} else {
-					$req['header'] .= "{$k}: {$v}\r\n";
-				}
-				unset($k, $v);
-			}
-		}
+        // Create a stream
+        $req = array(
+            'http'=>array(
+                'method'=>'GET',
+                'header'=>"Accept: application/json\r\n"
+            ),
+        );
+        if (isset($options['headers'])) {
+            foreach ($options['headers'] as $k=>$v) {
+                if (is_int($k)) {
+                    $req['header'] .= "{$v}\r\n";
+                } else {
+                    $req['header'] .= "{$k}: {$v}\r\n";
+                }
+                unset($k, $v);
+            }
+        }
 
-		$url = $options['url'];
-		if (isset($options['param'])) {
-			foreach ($options['param'] as $k=>$v) {
-				if (isset(self::$user[$v])) {
-					$url .= ((strpos($url, '?'))?('&'):('?'))
-						. $k . '=' . urlencode(self::$user[$v]);
-				}
-				unset($k, $v);
-			}
-		}
-		$ctx = stream_context_create($req);
-		$a   = file_get_contents($url, false, $ctx);
-		if (!$a) return null;
+        $url = $options['url'];
+        if (isset($options['param'])) {
+            foreach ($options['param'] as $k=>$v) {
+                if (isset(self::$user[$v])) {
+                    $url .= ((strpos($url, '?'))?('&'):('?'))
+                        . $k . '=' . urlencode(self::$user[$v]);
+                }
+                unset($k, $v);
+            }
+        }
+        $ctx = stream_context_create($req);
+        $url = mb_ereg_replace("/phar\:\/\//i", '', $url);
+        $a   = file_get_contents($url, false, $ctx);
+        if (!$a) return null;
 
-		$A = json_decode($a, true);
-		if (!isset($A['data'][0])) {
-			self::$user = false;
-		} else if (isset($options['map'])) {
-			foreach ($options['map'] as $k=>$v) {
-				if (isset($A['data'][0][$k])) {
-					self::$user[$v] = $A['data'][0][$k];
-				}
-				unset($k, $v);
-			}
-		}
+        $A = json_decode($a, true);
+        if (!isset($A['data'][0])) {
+            self::$user = false;
+        } else if (isset($options['map'])) {
+            foreach ($options['map'] as $k=>$v) {
+                if (isset($A['data'][0][$k])) {
+                    self::$user[$v] = $A['data'][0][$k];
+                }
+                unset($k, $v);
+            }
+        }
 
-		return self::$user;
-	}
+        return self::$user;
+    }
 
-	protected static $instance;
+    protected static $instance;
 
-	public static function ca()
-	{
-		if (is_null(self::$ca)) new CertificateAuthenticate();
-		return self::$ca;
-	}
+    public static function ca()
+    {
+        if (is_null(self::$ca)) new CertificateAuthenticate();
+        return self::$ca;
+    }
 
-	public static function client()
-	{
-		if (is_null(self::$client)) new CertificateAuthenticate();
-		return self::$client;
-	}
+    public static function client()
+    {
+        if (is_null(self::$client)) new CertificateAuthenticate();
+        return self::$client;
+    }
 
 }

programming_language: PHP