Reputation: 109
I'm making a game in Electron and I want it to support mods. Sometimes those mods will need to use custom logic, which leaves me with the issue of dynamically loading their code and I'm finding it hard to come up with a way to do that securely.
Ideally, I'd like to execute the mod scripts while passing just the few safe game objects they need as parameters, but that seems to be impossible (no matter the solution, that code can still access the global scope).
Most importantly, I have to prevent the untrusted code from accessing the preload
global scope (with Node APIs), so require
or anything else done in the preload
is out the window.
Therefore that code has to be executed in the renderer
.
I can either read the files in preload
using fs
or directly in the renderer
using fetch
. I have nodeIntegration
set to false
and contextIsolation
set to true
, and trusted code loaded by the preload
script is selectively passed to the renderer
through a contextBridge
. The code which accesses Node APIs is properly encapsulated.
Unfortunately, that still leaves me with having to execute the unsafe code somehow, and I don't think there's any other way than to use eval
or Function
. Even though malicious code could not access Node APIs, it would still have full access to the renderer
global scope, leaving the application vulnerable to, for example, a prototype pollution attack.
To sum up:
eval
or Function
renderer
global scope vulnerable to attacks which I can try to mitigate but can never make it completely safeMy first question: are these assumptions true, or is there a better way to do it?
So the potentially malicious code has access to the renderer
global scope. What's the risk?
Well, any sensitive user data will be safely stored in the preload
, the same goes for access to the user's computer with Node APIs. The attacker can break the game (as in, the current 'session'), but I can catch any errors caused by that and reload the game with the malicious mod turned off. The global scope will only hold the necessary constructors and no actual instances of the game's classes. It seems somewhat safe, the worst thing that could happen is a reload of the game.
My second question: am I missing anything here regarding the risks?
My third question: are there any risks of using eval
or Function
that I'm not thinking of? I've sort of been bombarded with "eval bad" ever since I've started getting into JS and now I feel really dirty for even considering using it. To be exact, I'd probably be using new Function
instead.
Thank you for reading this long thing!
Upvotes: 3
Views: 467
Reputation: 40842
There is no general solution for this, as this heavily depends on the structure of the project itself.
What you could try is to use espree to parse the unsafe code, and only execute it if there is no access to any global variable.
But that most likely will not prevent all attacks, because you might not think certain other attacks that might be possible due to the way the program is structured, require
(or any other way to include/load other scripts) in that unsafe code could also open side channels allowing certain attacks.
eval
and new Function
are not bad in general, at least not as bad as loading/including unsafe code in any different way. Many libraries use code evaluation for generated code, and that's the purpose of those functions. But it is often misused in a situation in which there no need for that and that is something that should not be done.
The safest way is most likely to run the code in a WebWorker and define an API for the Mods to communicate between the mod and the application. But that requires to serialize and deserialize the data, when passing it form the app to the mod and the other way round, this can be expensive (but this is what is done e.g. with WebAssmebly). So I would read a bit how communication is solved with WebAssembly.
Upvotes: 2