usd-2025-13 | ArcGIS Arcade (at least until v1.28) - Language Escape in JavaScript Runtime

Product: ArcGIS Arcade
Affected Version: at least until v1.28
Vulnerability Type: Sandbox / Language Escape
Security Risk: Low
Vendor: ArcGIS
Vendor URL: https://www.arcgis.com/index.html
Vendor acknowledged vulnerability: Yes
Vendor Status: Not fixed
CVE Number: N/A
CVE Link: Not requested, Vendor is CNA
Advisory ID: usd-2025-13

Description

Arcade is a scripting Language used in various ArcGIS Products.
When executed in a JavaScript Runtime (either ArcGIS Maps SDK for JavaScript or ArcGIS Online) Arcade scripts can execute arbitrary JavaScript.
This is possible, even though there are measures in place to prevent a language escape.

Proof of Concept

The following snippet leads to an alert on Arcade Playground.

var obj = {};
var func = {"__proto__": obj, "attributes": obj.constructor};
var target = {"__proto__": obj, "attributes": obj};
var wrapper = {"__proto__": obj, "attributes": target};
wrapper.hasField = func.constructor;
var payload = HasKey(target, "alert(1)");
wrapper.castToText = payload;Text(target)

The main vulnerability lies in the way __proto__/ constructor are handled as properties in Arcade dictionaries.
Internally an Arcade dictionary is a class with an attributes field that stores the actual dictionary.
For instance the dictionary {"test":1} would be stored as follows:

{
"declaredClass": "esri.arcade.Dictionary",
"plain": false,
"immutable": false,
"attributes": {
"test": 1
}
}

When creating a dictonary with a __proto__ key, this structure is destroyed. For example {"__proto__":{}, "attributes":1}
will lead to the following JavaScript object:

{
"declaredClass": "esri.arcade.Dictionary",
"plain": false,
"immutable": false,
"attributes": 1
}

Now accessing the toString property of the Arcade dictionary will resolve to 1.toString.
This way, properties can also be overwritten and hence internal fields manipulated.
Even without manipulation of the internal structure the constructor property allows access to the constructor of the attributes field (usually an Object).

The Proof of Concept uses these two primitives in the following way:

1. Create a func object, that has an attributes field containing the Object constructor internally.Accessing func.constructor hence resolves to Object.constructor, which is the Function constructor.

2. Create a target object and a wrapper object that has target as its attributes field.

3. Accessing wrapper.hasField accesses the hasField property of target.This method is called internally, when using the HasKey function. Hence overwriting it with the Function constructor leads to the ability of creating arbitrary JavaScript functions.

4. Using a similar trick, a created function can be executed. This time the castToText method is overwritten.

Fix

Instead of using a primitive JavaScript object to store the attributes of a dictionary, a Map should be used instead.
Also property accesses should be checked using the Map.has method before evaluation.
The square bracket property access should never be done with user input.

As a general precaution the compiled Arcade script could also be evaluated in an HTML iFrame with a dedicated origin.
This way, even if a language escape is possible, no critical information (e.g., Cookies / localStorage) can be accessed by a malicious script.

References

Timeline

Credits

This security vulnerability was identified by Yannick Sommer of usd AG.