Calling function on another gateway?

I have a split frontend/backend gateway architecture with two gateways and I’m trying to set up a page which displays all devices and allow the user (engineers) to enable/disable the connections from there. However because of the split architecture, I can’t just call system.device.* functions as they don’t exist on the frontend gateway… How would I achieve this? And how would I return the return value from the gateway function call e.g. from listDevices()?

system.util.sendRequest takes an optional remoteServer argument you can use to send a request to a remote gateway.

In your case, a message handler on the backend could return a device list when requested.

4 Likes

Cool, thanks! I was sure I’d seen something before but never used it before.

I created a message handler that accepts a string path to the system function to call so I can use it generically for calling any system function on the other gateway:

	"""	
	Arguments:
		payload: A dictionary with the following structure:
		If the function accepts keyword args:
			{'function': 'subLibrary.functionName',
			 'args': {'arg1': 'val1', 'argN': valN'}
			}
		If the function doesn't accept keyword args:
			{'function': 'subLibrary.functionName',
			 'args': ['arg1Val', 'argNVal']
			}
		Note: The function string appended to "system." will be the resulting function call executed.
		      Passing in the full system.subLibrary.functionName will also work but system. is otherwise
		      assumed.
	"""
	import traceback
	functionWhiteList = []
	functionWhiteList.extend(['device.listDevices', 'device.setDeviceEnabled'])
	
	try:
		functionPartialString = payload['function']
	except KeyError:
		return traceback.format_exc()
	
	# remove 'system.' from the start if it exists
	if functionPartialString.startswith('system.'):
		functionPartialString = functionPartialString[len('system.'):]
	
	if not any([functionPartialString == func for func in functionWhiteList]):
		raise KeyError('Function not included in whitelist.')
	
	function = system
	for part in functionPartialString.split('.'):
		function = getattr(function, part)
	
	args = payload.get('args', {})
	if isinstance(args, dict):
		ret = function(**args)
	else:
		ret = function(*args)
	
	return ret

and calling it on the other gateway:

project = 'dummy'
msgType = 'systemFunction'
payload = {'function': 'system.device.listDevices'}
remoteGWName = 'OtherGWName'

print system.util.sendRequest(project, msgType, payload, remoteGWName)

UPDATED: added a whitelist by popular demand / the firing squad made me do it

4 Likes

Oy! That makes me cringe.

1 Like

I knew it would make some people cringe, but hey, we use to have a single gateway and I could run any system function then as well… I did limit it to only running system functions, not user functions

yikes... free real estate for hackers
you should atleast prevent some function like system.util.execute and add/edit user/role... than atleast hackers can only mess with your application and not with the gateway, i think...

Like @victordcq you should probably make a whitelist of what functions you allow to be called. So something like

WHITELISTED_FUNCTIONS = ['listDevices', ...]
...
if payload['function'] in WIHTELISTED_FUNCTIONS:
    #proceed

yeah whitelisting probably is better than blacklisting xd

Oook, i’ll create a whitelist. Call me naive, but I still don’t see how it’s a security risk since:

  • they can only execute the function from a gateway part of the gateway network which is configured with a user with sufficient privileges. A user with these privileges will also have full reign to do anything they want.
  • if they have access to the frontend gateway, they have access to the backend gateway in which case they could just run the system functions from there anyway.

The only thing I can think of is if they could manipulate the function call in the client session - is this possible? And in this case, if you have a single-gateway system, can’t they just do the same thing?

I saw you link this in an other topic (seems i missed it the first time)

Since their is communication between gateways, some sort of message is being sent between them. Which can be mimiced/manipulated with some tools. Strings and json are especially easy to manipulate…

If someone mimics a message but with something like system.util.execute, They can have full access to the gateway to run anything they want since this can run cmd lines or programs…
Or if they can add a user with admin rights they can do that aswell…

Perspective works over internet which makes it a lot more vulnerable if you are not careful and it seems most developers here arent webdevs but come from vision (atleast i think, since there are a lot of basic css questions xD) so most dont know how to be.

The thing I don’t understand though, is how is it any different to running a single gateway where you can just execute those same functions directly? Are messages able to be sent somehow from a browser console or outside of the designed interface?

Because the scripts are ran on the gateway. Not on the browser. When a button is pressed it send a message to the gateway, something like “run script on button_1 on page_1 for session_id”
Text fields and session props could be in here but nothing to run a script itself… (atleast i hope the json of the scirpt isnt in here xd)

It would be possible to mimic this and send other values than those inside the textfields for example, (thats why its important to use system.db.runPrepQuery() instead of runQuery for updates/inserts) but no scripts can be messed with here.
Its possible to mimic a “run script on button_1 on page_1 for session_id” but there is some authentication on this so not as easy.
And they can not run a script that is not scripted inside your code.

You are doing a message between gateways tho, which means this will contain the full payload.
Which you then use to basically run an eval. Your string gets translated into code…
Its like running an eval on a textfield xd Ofc a textfield is a bit easier to manipulate but still xd

Im sure there is some encryption/authentication on this too, since you have to configure the link between gateways beforehand, but evals are dangerous

Yes - the entire reason we 'suddenly' introduced named queries and the client permissions settings in 7.9 was to counter this risk; a sufficiently motivated attacker could basically impersonate a Vision client and send arbitrary DB queries to the gateway, which would faithfully execute them, for instance. The attack surface wasn't totally broad - we never had an arbitrary "run this string as code in an interpreter" endpoint exposed, but the DB & tag vulnerabilities were sufficiently broad (and could theoretically be pivoted into RCE) that we had to change the security model of Vision significantly.

The gateway network is not guaranteed to be safe; while we strongly recommend and default to mutual TLS authentication, it's not strictly required, and if there are poor practices around private keys, even mutual TLS isn't guaranteed (someone with your private key could regenerate or impersonate your certificate).

The common thread between Vision, Perspective, and the gateway network is a common one through most security oriented programming - the only place you can truly trust is your own server*. Any message you're receiving could be faked with a sufficiently motivated actor/sufficient bad luck. Restrict what you can possibly do as much as possible; always verify things locally, etc.

  • For the pedants: Yes, at some level of paranoia you can't even trust your own code, but in that event you've already lost the war, so who cares about the battle
5 Likes