Geoplugin.net Insecure Deserialization

Plenty of applications require GeoIP capabilities and there's a ton of services out there that offer APIs for this purpose. I was actually researching libraries for an upcoming security package I'm developing and I came across a promising looking Laravel GeoIP library, stevebauman/location.

A lot of developers pull in libraries from GitHub or their language's package manager without reading any of the source code. I've been burned in the past, so I try to read as much of the code as possible to form an educated opinion of the quality and security of the library.

Vulnerability in stevebauman/location

Looking at the Geoplugin provider, I noticed that it was calling unserialize on the result of an API to call to the service. You should never pass data that you don't control to unserialize, and if you absolutely have to use it, you should set the allowed_classes flag to false to prevent anything except basic types from being deserialized.

// Bad - Unsafe, and shouldn't be used on anything
// except input controlled and validated by you.
unserialize($serializedData);
// Better, only allows deserialization of basic types,
// preventing object based RCE.
unserialize($serializedData, ['allowed_classes' => false]);

Here's the code from the library (with irrelevant bits removed). As you can see, we're passing the input from an insecure request directly to the unserialize. If geoplugin.net was to be compromised, any application implementing the php endpoint would be vulnerable to RCE.

In addition, since the call is insecure (over HTTP) it's possible for it to be intercepted by anyone between you and Geoplugin. This includes your ISP, your datacenter/hosting provider or potentially anyone with access to your applications internal network (through ARP Spoofing or some other kind of network compromise).

class GeoPlugin extends Driver
{
    protected function url($ip)
    {
        return "http://www.geoplugin.net/php.gp?ip=$ip";
    }
    
    protected function process($ip)
    {
        try {
            $response = unserialize($this->getUrlContent($this->url($ip)));

            return new Fluent($response);
        } catch (Exception $e) {
            return false;
        }
    }
}

The trail leads back to Geoplugin

Once I'd found the issue in the aforementioned package, I checked the documentation on geoplugin.com and was surprised to see that all the website examples use the insecure HTTP endpoint, and there's no mention of allow_classes.

In addition, using https requires a payment, resulting in most using the http endpoints. Their reasoning behind the payment is certificate costs, but it's 2021 and certificates are either free or very low cost.

So, how can you avoid insecure deserialization? Well, they also offer a json endpoint that returns the exact same data in JSON format. This removes the possibility of injection, and is supported in basically every language out of the box.

Other vulnerable libraries

I used GitHub's code search to find other projects using the php geoplugin API. There were about 700 unique repositories using the endpoint, including 22 with 5 or more github stars.

Interestingly, a large portion of the results seem to be web shells (back doors hackers place on a PHP server to maintain persistence) or signatures for malware detection. This indicates it's been used by hackers, probably to determine the country/location of their infected host.

I also reached out to other popular libraries using the insecure endpoint, informing them on how to switch to the JSON endpoint.

Wrapping Up

This investigation was a good reminder to me to read the code of libraries I use. Just because a library is popular, doesn't mean it's secure or well designed. Consider your dependencies carefully, and ideally lock them to a specific version to avoid unknowingly pulling in code you haven't reviewed.