CVE-2022-37298 (published 2022-10-20T11:15Z)

Shinken Solutions Shinken Monitoring Version 2.4.3 affected is vulnerable to Incorrect Access Control. The SafeUnpickler class found in shinken/safepickle.py implements a weak authentication scheme when unserializing objects passed from monitoring nodes to the Shinken monitoring server.

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-287
cwe_name: Improper Authentication
description: When an actor claims to have a given identity, the product does not prove or insufficiently proves that the claim is correct.
extended_description: 
            
			
         
         

Fix information:
repo_url: https://github.com/naparuba/shinken
commit_details: repo_url: https://github.com/naparuba/shinken
author: Jean Gabès
author_date: 2022-02-03 11:42:24+01:00
msg: Fix: Dailymotion team (Nicolas Perraud) found a way to bypass pickle.loads protection and execute code from daemon if you can exchange with it's internal port. Now we whitelist allowed class only :)

Bonus: update the changelog, Thanks & setup.py so we can release soon :)
repository: repo_url: https://github.com/naparuba/shinken
repo_name: shinken-solutions/shinken
description: Flexible and scalable monitoring framework
repo_language: Python
owner: naparuba
file_changes: filename: Changelog
change_type: ModificationType.MODIFY
diff: @@ -3,14 +3,60 @@ Shinken ChangeLog
 ########################
 
 
-3.0.03 - X/X/2017
+2.4.4 - XX/02/2022
 -----------------
         CORE ENHANCEMENT
         Enh: (backport from Enterprise, internal ref #SEF-202) Big boost for the broker daemons, especially for very large setups and slow modules (yes Graphite, I'm looking at you).
         Enh: (backport from Enterprise ref:#SEF-103) Slight boost of startup time by remiving useless hash computation.
+        Enh: (Christophe Simon) Force problem/impact evaluation
+        Enh: (Christophe Simon) added strict object name conflict policy
+        Enh: (Christophe Simon) better objects balancing between schedulers (#1999)
+        Enh: (Christophe Simon) Maintenance checks (#1929)
+        Enh: (Nicolas DUPEUX) Add an option to dump configuration as build by shinken to a json file (#1954)
+        Enh: (Lionel Sausin) Add instructions to use --install-scripts in pip (#1890)
+        Enh: (Christophe Simon) extend duplicate_foreach to host/hostgroups (#1905)
+        Enh: (Christophe Simon) Made memory free an opt-in option
+        Enh: (Christophe Simon) Explicitly frees memory when receiving new conf
+        Enh: (Christophe Simon) Harmonized graceful restart
+        Enh: (David Durieux) Add support of proxy socks5 for shinken cli (#1583)
+        Enh: (Mateusz Korniak) Add info about exit error code when check finished with error/was signalled. (#1766)
 
         CORE FIXES
         Fix: (backport from Enterprise ref:#SEF-76) In some case, we can have "negative" value for downtime depth, and then the element will never exit from downtime.
+        Fix: Dailymotion team (Nicolas Perraud) found a way to bypass pickle.loads protection and execute code from daemon if you can exchange with it's internal port. Now we whitelist allowed class only :)
+        Fix: (Christophe Simon) Enforced downtime state after retention load
+        Fix: (V. D'AGOSTINO) Update about.rst to fix a typo
+        Fix: (Flavien Peyre) multiple notification way when using a contact template (#1867)
+        Fix: (Dani Rodríguez) Update typographic errors and explain things in the help messages (#1968)
+        Fix: (Dani Rodríguez) brok queues not producing broks (#1971)
+        Fix: (David Gil) Pin CherryPy dependpency < 9.0.0 (#1983)
+        Fix: (Vladimir Kazarin) Update dmz-monitoring.rst (#1980)
+        Fix: (Muhammad Zeeshan Qazi) #1961 : Replace #!/usr/bin/python to #!/usr/bin/python2 (#1979)
+        Fix: (Dani Rodríguez) Fixes calls to cProfile in shinken binaries (#1967)
+        Fix: (wilfriedroset) shinken typo (#1974)
+        Fix: (efficks) CherryPy >= 3 required (#1943)
+        Fix: (David Gil) osmatch in nmap discovery process (#1944)
+        Fix: (Konstantin Shalygin) modules_manager: ignore '.git' dir when load modules. (#1883)
+        Fix: (Christophe Simon) http_client exceptions management
+        Fix: (Christophe Simon) OSX (darwin) platform detection
+        Fix: (Christophe Simon) unicode string parisng in shinken cmdline
+        Fix: (Olivier Hanesse) missing ini file for shinken.io
+        Fix: (Olivier Hanesse) #1857 Set default value for proxy/proxy_socks5 for shinken cli
+        Fix: (Olivier Hanesse) #1845 Add missing paramter for statsd
+        Fix: (Nicolas Le Manchet) Improve systemd units reliability
+        Fix: (Frédéric MOHIER) Iterate correctly in the Services object (#1842)
+
+
+        MISC FIXES
+        Fix: (Yann 'Ze' Richard) missing email header to prevent "Out Of Office" messages (#2014)
+        Fix: (se4598*) #1951: Email notification in text mode (#1986)
+        Enh: (Arthur Lutz) starttls and username/password for SMTP
+        Fix: (George Shuklin) trace on sening smtp messages
+        Doc: (rdmo) than -> as
+        Doc: (dodofox) specify host inheritance for contact/contactgroup (#1872)
+        Doc: (Elliott Peay) Fix default reactionner port in docs
+        Fix: (Konstantin Shalygin) Encode attachment. (#1817)
+        Fix: (Marc Remy) Typo: Update some doc files. (#1821)
 
 
 2.4.3 - 10/03/2016

programming_language: Markdown
filename: THANKS
change_type: ModificationType.MODIFY
diff: @@ -111,3 +111,4 @@ To Mathieu Parent for his patch about Thruk doc
 To Aina Rakotoson for his bug report about reactionner_tag logic fail, and his patch to fix about it
 To jylenhofgfi for his bug report about shinken cli problems and | grep
 To Alexandre Viau for his patch about passive unmanaged host broks generation
+To Nicolas Perraud for his warn about a RCE in the daemon exchange

programming_language: Markdown
filename: setup.py
change_type: ModificationType.MODIFY
diff: @@ -411,7 +411,7 @@ for o in not_allowed_options:
 required_pkgs = ['pycurl']
 setup(
     name="Shinken",
-    version="2.4.3",
+    version="2.4.4",
     packages=find_packages(),
     package_data={'': package_data},
     description="Shinken is a monitoring framework compatible with Nagios configuration and plugins",

programming_language: Python
filename: safepickle.py
change_type: ModificationType.MODIFY
diff: @@ -26,29 +26,96 @@ import cPickle
 from cStringIO import StringIO
 
 
-
-# Unpickle but strip and remove all __reduce__ things
-# so we don't allow external code to be executed
+# Unpickle but strip and remove all __reduce__ things so we don't allow external code to be executed
 # Code from Graphite::carbon project
 class SafeUnpickler(object):
     PICKLE_SAFE = {
-        'copy_reg': set(['_reconstructor']),
-        '__builtin__': set(['object', 'set']),
+        'copy_reg'                                : set(['_reconstructor']),
+        '__builtin__'                             : set(['object', 'set']),
+        
+        # Be sure to white list all we need to pickle.loads so user cannot exploit other modules (like bottle ^^)
+        'shinken.acknowledge'                     : set(['Acknowledge']),
+        'shinken.basemodule'                      : set(['BaseModule']),
+        'shinken.borg'                            : set(['Borg']),
+        'shinken.check'                           : set(['Check']),
+        'shinken.brok'                            : set(['Brok']),
+        'shinken.commandcall'                     : set(['CommandCall']),
+        'shinken.comment'                         : set(['Comment']),
+        'shinken.complexexpression'               : set(['ComplexExpressionNode']),
+        'shinken.contactdowntime'                 : set(['ContactDowntime']),
+        'shinken.daterange'                       : set(['Timerange',
+                                                         'Daterange',
+                                                         'CalendarDaterange',
+                                                         'StandardDaterange',
+                                                         'MonthWeekDayDaterange',
+                                                         'MonthDateDaterange',
+                                                         'WeekDayDaterange',
+                                                         'MonthDayDaterange',
+                                                         ]),
+        'shinken.dependencynode'                  : set(['DependencyNode']),
+        'shinken.downtime'                        : set(['Downtime']),
+        'shinken.discovery.discoverymanager'      : set(['DiscoveredHost']),
+        'shinken.eventhandler'                    : set(['EventHandler']),
+        'shinken.external_command'                : set(['ExternalCommand']),
+        'shinken.graph'                           : set(['Graph']),
+        'shinken.message'                         : set(['Message']),
+        'shinken.modulesctx'                      : set(['ModulesContext']),
+        'shinken.modulesmanager'                  : set(['ModulesManager']),
+        'shinken.notification'                    : set(['Notification']),
+        'shinken.objects.command'                 : set(['DummyCommand', 'Command', 'Commands']),
+        'shinken.objects.arbiterlink'             : set(['ArbiterLink', 'ArbiterLinks']),
+        'shinken.objects.businessimpactmodulation': set(['Businessimpactmodulation', 'Businessimpactmodulations']),
+        'shinken.objects.brokerlink'              : set(['BrokerLink', 'BrokerLinks']),
+        'shinken.objects.checkmodulation'         : set(['CheckModulation', 'CheckModulations']),
+        'shinken.objects.config'                  : set(['Config']),
+        'shinken.objects.contact'                 : set(['Contact', 'Contacts']),
+        'shinken.objects.contactgroup'            : set(['Contactgroup', 'Contactgroups']),
+        'shinken.objects.discoveryrule'           : set(['Discoveryrule', 'Discoveryrules']),
+        'shinken.objects.discoveryrun'            : set(['Discoveryrun', 'Discoveryruns']),
+        'shinken.objects.escalation'              : set(['Escalation', 'Escalations']),
+        'shinken.objects.hostdependency'          : set(['Hostdependency', 'Hostdependencies']),
+        'shinken.objects.host'                    : set(['Host', 'Hosts']),
+        'shinken.objects.hostescalation'          : set(['Hostescalation', 'Hostescalations']),
+        'shinken.objects.itemgroup'               : set(['Itemgroup', 'Itemgroups']),
+        'shinken.objects.hostgroup'               : set(['Hostgroup', 'Hostgroups']),
+        'shinken.objects.hostextinfo'             : set(['HostExtInfo', 'HostsExtInfo']),
+        'shinken.objects.item'                    : set(['Item', 'Items']),
+        'shinken.objects.macromodulation'         : set(['MacroModulation', 'MacroModulations']),
+        'shinken.objects.matchingitem'            : set(['MatchingItem']),
+        'shinken.objects.pack'                    : set(['Pack', 'Packs']),
+        'shinken.objects.notificationway'         : set(['NotificationWay', 'NotificationWays']),
+        'shinken.objects.module'                  : set(['Module', 'Modules']),
+        'shinken.objects.pollerlink'              : set(['PollerLink', 'PollerLinks']),
+        'shinken.objects.reactionnerlink'         : set(['ReactionnerLink', 'ReactionnerLinks']),
+        'shinken.objects.realm'                   : set(['Realm', 'Realms']),
+        'shinken.objects.receiverlink'            : set(['ReceiverLink', 'ReceiverLinks']),
+        'shinken.objects.resultmodulation'        : set(['Resultmodulation', 'Resultmodulations']),
+        'shinken.objects.satellitelink'           : set(['SatelliteLink', 'SatelliteLinks']),
+        'shinken.objects.schedulingitem'          : set(['SchedulingItem']),
+        'shinken.objects.schedulerlink'           : set(['SchedulerLink', 'SchedulerLinks']),
+        'shinken.objects.service'                 : set(['Service', 'Services']),
+        'shinken.objects.servicedependency'       : set(['Servicedependency', 'Servicedependencies']),
+        'shinken.objects.serviceescalation'       : set(['Serviceescalation', 'Serviceescalations']),
+        'shinken.objects.serviceextinfo'          : set(['ServiceExtInfo', 'ServicesExtInfo']),
+        'shinken.objects.servicegroup'            : set(['Servicegroup', 'Servicegroups']),
+        'shinken.objects.timeperiod'              : set(['Timeperiod', 'Timeperiods']),
+        'shinken.objects.trigger'                 : set(['Trigger', 'Triggers']),
+        
     }
-
-
+    
+    
     @classmethod
     def find_class(cls, module, name):
-        if module not in cls.PICKLE_SAFE and not module.startswith('shinken.'):
+        if module not in cls.PICKLE_SAFE:
             raise ValueError('Attempting to unpickle unsafe module %s' % module)
         __import__(module)
         mod = sys.modules[module]
-        if not module.startswith('shinken.') and name not in cls.PICKLE_SAFE[module]:
+        if name not in cls.PICKLE_SAFE[module]:
             raise ValueError('Attempting to unpickle unsafe class %s/%s' %
                              (module, name))
         return getattr(mod, name)
-
-
+    
+    
     @classmethod
     def loads(cls, pickle_string):
         pickle_obj = cPickle.Unpickler(StringIO(pickle_string))

programming_language: Python
filename: test_safe_pickle.py
change_type: ModificationType.MODIFY
diff: @@ -24,12 +24,15 @@
 
 
 import cPickle as pickle
+import sys
+
 from shinken_test import *
 
 from shinken.safepickle import SafeUnpickler
 
-
 should_not_change = False
+
+
 def fff(b):
     global should_not_change
     should_not_change = b
@@ -41,37 +44,98 @@ class SadPanda(object):
 
 
 class TestSafePickle(ShinkenTest):
-
+    
     def setUp(self):
         pass
-
-
+    
+    
     def launch_safe_pickle(self, buf):
         SafeUnpickler.loads(buf)
-        
-        
+    
+    
     def test_safe_pickle(self):
         global should_not_change
-
-        print "Creating payload"
+        
+        print("Creating payload")
         buf = pickle.dumps(SadPanda(), 0)
         should_not_change = False
-        print "Payload", buf
-        #self.assertEqual('HARD', host.state_type)
-        print "Now loading payload"
+        print("Payload", buf)
+        # self.assertEqual('HARD', host.state_type)
+        print("Now loading payload")
         pickle.loads(buf)
         print should_not_change
         self.assertTrue(should_not_change)
         
         # reset and try our fix
         should_not_change = False
-        #SafeUnpickler.loads(buf)
+        
+        
+        # SafeUnpickler.loads(buf)
         def launch_safe_pickle():
             SafeUnpickler.loads(buf)
+        
+        
         self.assertRaises(ValueError, launch_safe_pickle)
-        print should_not_change
+        print (should_not_change)
         self.assertFalse(should_not_change)
+    
+    
+    # Thanks to security team @Dailymotion, we did have a RCE that ook like a return into libc
+    # exploit: they are using the bottle._load code (that blindly __import__) so the injected
+    # code will finally be executed. And as it's shinken.webui.bottle, it's ok with the
+    # safe_pickle filter. Smart ^^
+    def test_safe_pickle_exploit_rce(self):
+        ###### Phase 1: can be exploited
+        # Arrange
+        rce_path = '/rce_exploited'
+        if rce_path in sys.path:
+            sys.path.remove(rce_path)
+        
+        payload = b"""cshinken.webui.bottlewebui
+_load
+(S'sys:path.append("%s")'
+tR.""" % rce_path
+        
+        # Act
+        print("Now loading payload")
+        pickle.loads(payload)
+        
+        # Assert
+        self.assertTrue(rce_path in sys.path)
+        
+        ##### Phase 2: no more exploitable by calling the good one
+        # Arrange
+        sys.path.remove(rce_path)
+        
+        
+        # SafeUnpickler.loads(buf)
+        def launch_safe_pickle():
+            SafeUnpickler.loads(payload)
         
         
+        # Act
+        self.assertRaises(ValueError, launch_safe_pickle)
+        
+        # Assert
+        self.assertTrue(rce_path not in sys.path)
+    
+    
+    # Thanks to security team @Dailymotion, we did have a RCE that ook like a return into libc
+    # exploit: they are using the bottle._load code (that blindly __import__) so the injected
+    # code will finally be executed. And as it's shinken.webui.bottle, it's ok with the
+    # safe_pickle filter. Smart ^^
+    def test_safe_pickle_exploit_rce_can_load(self):
+        ###### Phase 1: can be exploited
+        # Arrange
+        
+        payload = pickle.dumps(Brok('void', {}))
+        
+        # Act
+        b = SafeUnpickler.loads(payload)
+        
+        # Assert
+        self.assertTrue(isinstance(b, Brok))
+
+
 if __name__ == '__main__':
     unittest.main()

programming_language: Python