Recent issue with marketplace proxy

Luke Towers
Posted on Feb 27, 2022.

As part of our goal for transparency with Winter CMS, we wish to report on the recently identified and reported issue with our temporary marketplace proxy server. This post will go over what the issue was, how it was resolved, and how it happened in the first place.

The issue

As reported, the issue resulted in some paid plugins from the October CMS marketplace becoming unintentionally available temporarily through the Winter CMS API gateway server without requiring valid Project IDs in order to download them. October CMS issued a security advisory and updates to the October codebase in order to make the usage of non-October API gateway servers more difficult by default.

The crux of the issue is that paid products from the October CMS marketplace were being unintentionally cached on the Winter API gateway server. When this issue was reported to the Winter CMS maintainers, these packages were immediately identified and removed from the Winter gateway server and we have subsquently modified the server to prevent it from proxying requests for paid packages to the October gateway.

Responding to October's security advisory

There were several sweeping claims made about the Winter gateway server that are simply not true and we wish to dispute here.

"a project fork of October CMS v1.0 is using a compromised gateway to access the October CMS marketplace service"

The Winter gateway server is not "compromised". It's a proxy server that is meant to smooth over the transition between October and Winter.

"the compromised gateway captures the personal/business information of users and authors, including private source code files. It was also disclosed that captured plugin files are freely redistributed to other users without authorization."

No personal or business information is captured or retained by the Winter gateway server, only plugins that are freely available and licensed for public use are cached by the Winter gateway.

It was completely unintentional that some paid products were accessible through the Winter gateway, and once reported, was quickly resolved to the satisfaction of those authors.

We also do not "freely" distribute these captured plugin files, with the implication in this language that we are sanctioning or conducting piracy. We are simply trying to allow people to continue to use the platform that they wish with the extensions that they legitimately purchased.

"The user is instructed to enter their October CMS license key into the administration panel to access the October CMS marketplace. The key is sent to the compromised server while appearing to access the genuine October CMS gateway server."

At no point do we claim to be "the genuine October CMS gateway server". Pointing existing projects at the Winter CMS gateway server requires an explicit step taken by the developer to modify their configuration files to point to the Winter gateway server.

"The compromised gateway server uses a "man in the middle" mechanism that captures information while forwarding the request to the genuine October CMS gateway and relaying the response back to the client."

This is a technically correct, while unnecessarily scary sounding, explanation of how proxying requests work.

"The compromised gateway server stores the license key and other information about the user account including client name, email address and contents of purchased plugins and privately uploaded plugin files."

This is entirely untrue. If it is indeed possible to access information about the October marketplace user accounts from Project IDs, then that is not known to us and we are most certainly not storing that information. The only information we have access to is information already provided through the public October API that was present in v1.0 and v1.1 of October. We do not store any of this information - requests are proxied as needed and then disposed of.

"The stored plugin files are made available to other users of the compromised gateway server."

As stated previously, only publicly available packages are made available through the caching system - the fact that some paid packages were also unintentionally available was a bug and was quickly fixed as soon as it was reported. If you believe that any of the packages currently available throught the Winter gateway server should not be available, please contact us at hello@wintercms.com and we will be more than happy to review and remove any offending content.

Background

In order to understand how this happened, it is important to first look at why the Winter gateway server exists and then the circumstances around how it came to be.

Reason for Existance

The Winter CMS API (https://api.wintercms.com) exists because there is a need for Winter CMS instances to connect to an API gateway in order for the web-based plugin / theme updates / installation system to function. In order to maintain backwards compatibility with the existing update / installation system present in Winter installs and decrease reliance on October's API gateway (which is subject to change at any moment), it was necessary for Winter to provide its own API gateway server for Winter projects to use going forward.

The gateway server provides several pieces of important functionality to end users of the project. It provides plugin & theme discovery, installation, and updates and it is also responsible for providing web-based updates for the CMS itself. Because of the extremely important role the server has to play within the project it was important that it be controlled by the core Winter CMS team in order to provide a seamless experience for our end users in terms of avoiding any future breaking changes introduced by October (of which there have been several over the past year) as well as provide the ability for the core team to list first party packages and themes for consumption through the service.

Development

When the split between October CMS and Winter CMS happened in March of 2021, there was a lot of work that needed to happen in order for Winter to exists as a fully functional fork. We needed to a lot of work to get the fork off the ground and furthermore it needed to get done in a very short period of time:

Contrary to the assumptions made by some people, we have never once had access to any of the codebase used for the October CMS main site, the October CMS marketplace, the API gateway server, or the code used to generate and embed the documentation into the October CMS website. This means that we had to complete each of those tasks designing and building those systems completely from scratch with no references to help speed up the development process.

Gateway Server Logic Error

The Winter gateway works similar to how Winter itself works in relation to how it acts as a layer inbetween your application code and the ever changing Laravel framework code; smoothing over rough edges for you and avoiding requiring you to make any changes in your code as the ecosystem evolves. At a technical level, it functions by proxying requests to the October CMS gateway; reformating the response into a response that the original API handlers in October / Winter v1.0 & v1.1 can handle, and then returning that response to the consumer of the API.

In order to minimize the impact on both October's gateway server and the Winter gateway server (reprocessing package responses is an expensive process computationally) a caching layer was implemented so that if an API consumer requested a resource from the gateway that Winter had already processed it would serve the cached result rather than forwarding the request on to October.

While there was logic in place to detect when the resource being interacted with was one that shouldn't be freely available (see the if block starting with if (str_contains($message, "Authority to provide")) { in the API logic used for retrieving packages below) - this logic failed to take into account what would happen if an API request was made to the gateway that include a Project ID that was authorized to access a package resource that should not be freely available.

Ultimately, the fix was simple - we simply stopped proxying project IDs to the October marketplace server. While this does prevent people with legitimate Product IDs from being able to download their October Marketplace products through the Winter gateway server; it's worth it in order to protect the hard working package authors that make their products available for sale on the October marketplace.

// Don't proxy project IDs
unset($_POST['project']);

Gateway Server Code

In the interest of transparency, please see the below code that we use for interacting with the October API to retrieve a plugin or theme:

/**
 * Returns the requested package file or an error message
 *
 * Route::post('theme/get', function () {});
 *
 * // Send:
 * $stdArgs + ['name' => 'rainlab.mailchimp', 'installation' => 1]
 *
 * // Receive:
 * Invalid Name / No package found
 *     "Package not found"
 * 1.1:
 *     Zip folder containing rainlab/mailchimp/$contents
 * 1.2:
 *     Zip (maybe ARC?) folder containing $contents
 *
 * @return StreamedResponse|string
 */
protected function packageGet()
{
    $name = input('name', '');
    $package = $this->detail();

    if ($package instanceof Package) {
        // Get the latest release attempting to handle it within the API's build system first
        $build = $package->latest_release->getBuild();

        // Fallback to October, processing the provided package file into a release for the current package
        if (!$build) {
            try {
                $octoberResponse = $this->proxyRequest();
                if ($octoberResponse->headers->get('Content-Type') === 'application/zip') {
                    $tempPath = tempnam(sys_get_temp_dir(), 'api.wintercms.com_') . '.zip';
                    file_put_contents($tempPath, $octoberResponse->getContent());

                    // Build the package
                    $builder = new PackageBuilder();
                    $build = $builder->buildPackage($package, $tempPath);
                } else {
                    $message = $octoberResponse->getContent();
                    if (str_contains($message, "Authority to provide")) {
                        Log::error("October refused to provided {$package->code}, archiving package. Message: $message");
                        $package->delete();
                    } else {
                        Log::error("Unexpected response from October Marketplace");
                    }
                }
            } catch (\Exception $e) {
                Log::error("Retrieving $name from the October API failed with the message " . $e->getMessage());
                throw new \Exception("Failed to retrieve $name");
            }
        }

        if ($build && !empty($build->hash) && file_exists($build->getPath())) {
            return Storage::response(str_after($build->getPath(), storage_path('app')), $build->hash . '.zip');
        }
    } else {
        return $package;
    }
}

Conclusion

Hopefully this post was able to provide additional information on the recent issue with the Winter gateway server and the related security advisory that was posted by October CMS. We apologize for any inconvenience that this may have caused anyone and we welcome any reports of issues in the Winter CMS ecosystem at large or the website & gateway server in particular.

Ultimately we hope to have the Winter CMS marketplace fully functional in the next couple of months (after completing the Laravel 9 migration) - at which point we'll be able to fully cut of all ties to the October marketplace and no longer have any form of proxying or caching of requests that hit the October marketplace.

More

Keep informed

Sign up to our newsletter and receive updates on Winter releases, new features in the works, plugin and theme promotions and much more!