Angular/Javascript - Custom User Scripts (Eval) - javascript

Hi we are building a web app platform where users can make their own smart forms using drag and drop features. We are looking for a way for admin users to create their own custom scripts to run some logic using pre-defined functions for the app. Currently the solution we have come up is using eval().
Knowing Eval is 'evil' we have implemented a function to check if the script is safe before it is executed. Essentially it breaks up the code into tokens and then runs those tokens against a blacklist. Stuff like new, eval, window, require, fetch,browser will show an error. Also the function is executed via a angular service so we try to limit what is injected.
Below is the basic high-level code. We have custom async functions so the solution needs to handle this.
My question is there a better(ie faster) and safer way to run custom scripts?
async runScript(script,callback) {
var updateForm=(id,value)=>{
return this.updateForm(id,value);
}
var getForm=(id)=>{
return this.getForm(id);
}
if (this.checkScriptSafe(script)) {
try {
return eval("(async () => {" + script + "})()");
} catch (e) {
if (e instanceof SyntaxError) {
alert(e.message);
} else {
console.log('Error',e);
alert("Error in script");
}
}
} else {
alert("Script not safe")
}
}
Example script:
"var value = 1 +4; await updateForm("11",value);alert("Success!");"

Function constructor would be a better approach. Function constructor creates a new function that will execute in the global scope. Your eval script (because of the arrow function) will run in the same context as your runScript method. They would access/modify your internals, or override your class methods. They can even override the runScript method itself and remove the checkScriptSafe check.
Using the function constructor is similar to typing in the dev tools console. If your application is not vulnerable to the dev tools console, then you wouldn't have any issues using the function constructor.
Here is an example:
const script = `
var value = 1 +4;\n
await updateForm("11",value);\n
alert("Success!");
`;
// we have to get a handle of the async function constructor
// in order to create an async function
const dummyFunction = async function() {}
const AsyncFunction = dummyFunction.constructor;
// create an async function which will run in the global scope
// the function will have an `updateForm` parameter
const userFunction = new AsyncFunction('updateForm', script);
// now userFunction is equavalent of:
// const userFunction = async function(updateForm) {
// var value = 1 +4;
// await updateForm("11",value);
// alert("Success!");
// }
// bind the current context 'this' to update form and pass it
// to user's function as parameter. The user's function
// will be able to execute it.
userFunction(this.updateForm.bind(this));
I'm not an expert in browser internals. But I assume tokenizing and interpreting the function on your own would be much slower than the Function constructor method. Even if you do everything in the most efficient way, you would still be in the JavaScript domain; v8 (or any other JS engine) would perform the actual interpretation after you. Why not directly give the script to the JS engine then? If the same custom script is going to run frequently, then with the right design, v8 will optimize the custom functions by compiling them into machine code. This wouldn't be the case with eval.

Related

Excel.run, pass and return context as a variable, where does context come from?

I have a big Google Apps Script project working on Google Spreadsheets and I am trying to convert it to an office-js add-in that works on Excel Workbooks. I understand that it is good practice to put everything that will be calling excel specific functions (directly interacting with the workbook) into an Excel.run() function so it does a proper clean-up and no memory leaks occur. I also understand I should do context.sync() as little as possible to optimize performance.
Here are my questions, (I think some of them come from my incomplete grasp of how js works; these things GAS handled without me needing to question them) :
1a) When we put our block of code in
Excel.run(context => {
//code that does stuff with context;
context.sync();
});
where does the context come from? Is this equivalent to
Excel.run(()=> {
let context = new Excel.RequestContext;
//code that does stuff with context;
context.sync();
});
1b) Also, if context gets generated with every new function why would I ever return context.sync() and not just context.sync()?
Is a new, second context generated in this case and what happens to it?
function handle_error(e){//second context generated in case of error
let context=New Excel.RequestContext;
context.workbook.load('name');
await context.sync();
some_logging_function(context.workbook.name, e);
}
function data_func(some_data: Non-Excel-Interface): Other-Non-Excel-Interface{
//manipulate data
//in case of error
handle_error(e);
//continue with data massaging
return altered_data;
}
Excel.run(context=>{ //first context
context.workbook.worksheets.getItem('Sheet1').getUsedRange().load('values');
context.sync();
let values = context.workbook.worksheets.getItem('Sheet1').getUsedRange().values;
let some_data: Non-Excel-Interface = {sheetName: 'Sheet1', data: values};
let new_vals = data_func(some_data);
context.workbook.worksheets.getItem('Sheet1').getUsedRange().values = new_vals.new_data;
context.sync();
});
If I put my main code inside Excel.run, then pass and return context: Excel.RequestContext and range: Excel.Range in other functions do I need Excel.run() in those functions, too? In other words, should the code inside functions a() and b() be inside Excel.run()?
function a(rng: Excel.Range, values:string[][]):Excel.Range{
rng.values = values;
return rng;
}
function b(context: Excel.RequestContext): Excel.RequestContext{
context.workbook.load('name');//load name property, but don't context.sync()
return context;
}
Excel.run(async context=>{
context = b(context);
let rng = context.workbook.worksheets.getItem('Sheet1').getUsedRange();
rng.load('values');
await context.sync();//values property and workbook name property must be available now
rng = a(rng, [['aa', 'bb', 'cc']]);
await context.sync();//new values must be available now
console.log(context.workbook.name, rng.values);//should show the title of the workbook and the newly assigned values of the range
});
Also, what is the advantage of asynchronous functions if I have to explicitly wait every time I need a value? I mean, if I am going to use context.sync() sparingly, that means I use it only when I desperately need it, so it must always come with await. So why not make context.sync() synchronous by default?
I'll try to answer some of these questions and try to get some help for the others. I also recommend the book Building Office Add-ins for an understanding of the Office JavaScript library. See this too, if you haven't already: Application specific API model.
1a. Yes. That's essentially correct. Under the hood, Excel.run creates an Office.RequestContext object and passes it to the batch function parameter. (But your two code blocks are not literally equivalent. You would not call Excel.run AND explicitly create a RequestContext object.)
1b. From skimming the book I linked to, I think that you have to return what the book calls the meta-promise so that the Excel.run can resolve the Promise that it returns. Here's an example from the book:
Excel.run(function (context) {
var selectionRange = context.workbook.getSelectedRange();
selectionRange.format.fill.clear();
selectionRange.load("values");
return context.sync()
.then(function () {
var rowCount = selectionRange.values.length;
var columnCount = selectionRange.values[0].length;
for (var row = 0; row < rowCount; row++) {
for (var column = 0; column < columnCount; column ++) {
if (selectionRange.values[row][column] > 50) {
selectionRange.getCell(row, column)
.format.fill.color = "yellow";
}
}
}
})
.then(context.sync);
}).catch(OfficeHelpers.Utilities.log);
From skimming the book I linked to, I think that the answer is yes; the Excel.run always creates a new context object and passes it to the batch function. There are techniques and an override of Excel.run that enable you to pass an object created in one context to another call of Excel.run, but these are intended for use with independent calls of Excel.run, not nested calls, as in your case.
No. You should not call Excel.run inside a or b.
I think there are scenarios in which you would not need to await context.sync. For example, when all the code in the parent function that comes after the context.sync only affects the UI of a task pane and does not depend on reading any data from the current Office document. The good practice of minimizing calls of context.sync is because it requires a round-trip between the document and JavaScript runtime in which the add-in code is running (on the user's computer). This would be true regardless of whether context.sync is synchronous or not.
For 1a, here's one of the descriptions of how the Run function works which I'm getting from ScriptLab's intellisense:
A function that takes in a RequestContext and returns a promise (typically, just the result of "context.sync()"). The context parameter facilitates requests to the Excel application. Since the Office add-in and the Excel application run in two different processes, the RequestContext is required to get access to the Excel object model from the add-in.
In terms of 1b, I don't think so. RequestContext does not seem to be an object you can instantiate yourself.
EDIT: actually it does look like this is possible. Please see below:
$("#run").click(() => tryCatch(run));
async function run() {
await Excel.run(async () => {
let ctx:Excel.RequestContext = new Excel.RequestContext();
let wb: Excel.Workbook = ctx.workbook
let rang: Excel.Range = wb.getSelectedRange()
rang.load("address")
await ctx.sync()
console.log(rang.address)
});
}
/** Default helper for invoking an action and handling errors. */
async function tryCatch(callback) {
try {
await callback();
} catch (error) {
// Note: In a production add-in, you'd want to notify the user through your add-in's UI.
console.error(error);
}
}
I'd recommend against using this approach. Passing in anonymous functions, like in the original example, is very common in JavaScript. So this would likely be considered a bad practice.

Is it unsafe to invoke a function by it's name as a string using the window object?

Would you please tell me if there is anything unsafe, in the JS of a standard web page, about invoking a function by it's name as a string using window['function_name']()? I remember reading something about it quite some time ago but cannot locate it now.
I thought there was a reason to not use the window object to do so, because to invoke functions between a background script and content script in a web extension, I always declare a different object in each and declare the functions that can be invoked in this manner as properties of the object and pass the function names as strings in the communication port to invoke them; but I cannot remember why.
Thank you.
It depends on which context your running the code,
1. JS Execution Context
Its fine to use string as function name and call the corresponding function residing in an object.
const functionName = "someFunction";
window[functionName]()
But If string is part of a untrusted data or user controllable string then it not safe to use. i.e Reading a string from a url parameter.
Example:
const unTrustedUserInput = window.location.hash
window[unTrustedUserInput]();
2. web Extension BG & CS Context
As per chrome recommendation, you should not trust the message received from content-script. You should always sanitise the input and place necessary validation before executing it.
So, I would recommend not to pass function name as string, instead use a dictionary map with corresponding guid to validate to which function the call is made.
Example:
// cs context
chrome.extension.sendMessage({ id: "<GUID-1>", data: "data" }, (response) => {
// handle response if any
});
// BG context
var CSBGCommunicationMap = {
"<GUID>-1": window.someFunction
}
chrome.runtime.onMessage.addListener(function (message, sender, sendResponse) {
if (sender.id !== "<varlidate id-ur-Extension>" && sender. origin !== "<validate origin>") {
// Early return if it comes from malicious content or unknown sender
return;
}
if (message.id && message.id in CSBGCommunicationMap) {
const functionToBeCalled = CSBGCommunicationMap[message.id];
// functionToBeCalled(message, sendResponse);
}
});
I hope this clarifies your concern.
According to MDN, The Window object is a global object which contains the entire DOM document. So if you call a function foo() (without specifying any object), Javascript will search it in window object.
In other hand,
foo() , window.foo() and window['foo']() are same. But when talk about security, Let's say if user injects some malicious code into the function foo,
doesn't matter you invoke the function foo() or window['foo'](), The injection will effect both.
Avoid using Window object
You don't need to specify the window object to call a global scoped functions or variable unless, it shadowed by your current scope.
function x() {
console.log('hey i am global');
}
function y() {
function x() {
console.log('I have power only inside y()');
}
x(); // I have power only inside y()
window.x() // hey i am global
}
And If you don't handle window object properly, There are lot of chances to get run-time errors and the entire object will be collapsed.

node.js vm and external methods

I have a few problems working on an app that uses vm:
External methods calls inside an infinite while (true) {} loop are not timed out:
var context = {
externalMethod: function(str) {console.log(str)}
};
vm.runInNewContext("while (true) {externalMethod('test')}", context, {}, 100);
Code above runs infinitely, even after 100ms timeout is finished;
vm.runInNewContext("for (;;) {}", {}, {}, 100)
Also this.. Even without running external methods, it isn't timed out.
And one last question: how safe is it to have external method calls inside vm2 that will run untrusted code:
var context = {
method: (str) => {
createMessage("id", str) // some external (non-sandbox) method that sends an authenticated POST request to a chat app...
}
}
So, is it possible to retrieve external global or this, using that method?
Code above runs infinitely, even after 100ms timeout is finished
timeout should be a property of the options object, not a separate argument:
vm.runInNewContext("while (true) {externalMethod('test')}", context, { timeout : 100 })
And one last question: how safe is it to have external method calls inside vm2 that will run untrusted code.
I assume you mean vm (the built-in Node module) and not vm2 here. In that case, when you call code outside of the VM, it can potentially access the globals, locals and this of the "outside" code:
const vm = require('vm');
let SECRET = 'this is a secret';
let context = {
console, // to allow `console.log()` calls inside the sandboxed code
externalMethod() {
console.log('Secret outside:', SECRET)
}
};
vm.runInNewContext(`
console.log('Secret inside: ', typeof SECRET);
externalMethod();
`, context);
You can't access SECRET directly from the 'inside' code, but the external method can access it. So if externalMethod has the potential of running untrusted code, it'll be unsafe.

overriding fullcalendar javascript functions which is in another script

I am newbie in js and I want to override/overwrite some fullcalendar functions from another script (my-fullcalendar.js) to make some changes in it for myself. for example function names are :
formatRange and oldMomentFormat.
formatRange is accessible from this.$.fullCalendar.formatRange but oldMomentFormat is not accessible via this kind of chain. But even when I do something like this in my-fullcalendar.js:
;(function () {
function MyformatRange(date1, date2, formatStr, separator, isRTL) {
console.log( "MyformatRange");
//other parts is exactly the same
// ...
}
this.$.fullCalendar.formatRange=MyformatRange;
console.log(this);
})();
nothing happens because no log is generated and even line by line tracing does not pass from here. but when observing "this" in console log MyformatRange replaced by original formatRange.
another problem is how can I override/overwrite oldMomentFormat function which is not in window hierarchy to access (or I can not find it) ??
OK, let's simplify the problem. In essence, you have this situation:
var makeFunObject = function () {
var doSomething = function (msg) {
console.log(msg);
};
var haveFun = function () {
doSomething( "fun!");
};
return {
doSomething : doSomething,
haveFun : haveFun
};
};
In other words you have a function that is creating a closure. Inside that closure are two "private" functions, one of which calls the other. But both functions seem to be "exposed" in the returned object.
You write some code:
var myFunObject = makeFunObject();
myFunObject.haveFun(); // fun!
Yep, seems to work just fine. Now let's replace the doSomething function in that returned object and call haveFun again:
myFunObject.doSomething = function (msg) {
console.log("My new function: " + msg);
};
myFunObject.haveFun(); // fun! <== wait what?
But wait! The new replacement function is not being called! That's right: the haveFun function was expressly written to call the internal function. It in fact knows nothing about the exposed function in the object at all.
That's because you cannot replace the internal, private function in this way (you cannot replace it at all, in fact, not without altering the original code).
Now draw back to the FullCalendar code: you are replacing the external function in the object, but the internal function is the one that is called by every other function inside FullCalendar.
I realize this is an old question, but I was butting my head against this same problem when I wanted to override the getEventTimeText function.
I was able to accomplish this, from inside my own JS file, like so:
$.fullCalendar.Grid.mixin({
getEventTimeText: function (range, formatStr, displayEnd) {
//custom version of this function
}
});
So, in terms of the function you were trying to override, you should be able to do it with:
$.fullCalendar.View.mixin({
formatRange: function (range, formatStr, separator) {
//custom formatRange function
}
});
Note: Make sure this runs before where you actually create the calendar. Also note that you need to make sure to override the function in the right place. For example, getEventTimeText was in $.fullCalendar.Grid, while formatRange is in $.fullCalendar.View.
Hopefully this helps other people who end up on this question.

HTML5 Call to global function from Worker

I have a global function in Utils.js called "sendAndWaitCommand".
When I'm try to call this function form the Worker (name 'uploadToDevice.js'), the Worker crashes.
Worker Initialization
var worker = new Worker('uploadToDevice.js');
worker.postMessage(SplitedFile);
The Worker (uploadToDevice.js)
self.addEventListener('message', function (e)
{
var SplitedFile = e.data;
sendAndWaitCommand(SplitedFile[0].substring(1));//crash here
}, false);
Utils.js
function sendAndWaitCommand(commandToSend)
{
...//Some heavy stuff to do.
}
Is there any way to call a global function without the worker will crash?
If not, is there a solution how I can call objects from outside the worker?
The JavaScript model isn't, naturally, thread based but event based. There's no facility to lock data and ensure their integrity in a multi-thread context.
That's why multi-threading schemes (among them the webworkers) don't allow data sharing (a "global" function is some data and usually points to data through the closure). You can't call a "global" function from your webworker. You communicate with messages.
Instead of a global function, you set an event listener :
var myWorker = new Worker("my_task.js");
myWorker.onmessage = function (oEvent) {
// the implementation of your "global function", for example :
sendAndWaitCommand(oEvent.data.commandToSend);
};
If you want to use the code in util.js you would have to import it using the importScripts command:
Example from MDF:
importScripts(); /* imports nothing */
importScripts('foo.js'); /* imports just "foo.js" */
importScripts('foo.js', 'bar.js'); /* imports two scripts */
Note that if util.js interacts with the global scope this won't work, since after being imported it will still be in the same scope as your worker. If util.js contains all the code needed to perform the sendAndWaitCommand then that might be a good choice.
If sendAndWaitCommand interacts with your other code, (jquery.ajax, your error handling code, etc.) it would probably be better to setup a message and event system, like so:
var worker = new Worker('uploadToDevice.js');
worker.onMessage = function (message) {
if (// Test here to see if message is result
message && message[0] == "!")
myApp.onDone(message.substr(1)) // Removes the ! and calls a function to handel the worker being done
else // assume the message is a sendAndWaitCommand request
sendAndWaitCommand(message).then(function (a) {
worker.postMessage(a); // Send the worker the result
});
worker.postMessage(SplitedFile); // Start the worker
The second approach will probably involve more refactoring of your code but may be necessary to use workers correctly.

Categories