CVE-2026-28370
Description
In the query parser in OpenStack Vitrage before 12.0.1, 13.0.0, 14.0.0, and 15.0.0, a user allowed to access the Vitrage API may trigger code execution on the Vitrage service host as the user the Vitrage service runs under. This may result in unauthorized access to the host and further compromise of the Vitrage service. All deployments exposing the Vitrage API are affected. This occurs in _create_query_function in vitrage/graph/query.py.
AI Insight
LLM-synthesized narrative grounded in this CVE's description and references.
OpenStack Vitrage query parser before 12.0.1, 13.0.0, 14.0.0, 15.0.0 allows remote code execution via crafted API queries due to unsafe eval() usage.
Root
Cause The vulnerability resides in the query parser of OpenStack Vitrage, specifically in the _create_query_function method within vitrage/graph/query.py. The code used eval() to dynamically evaluate query expressions, as seen in the original implementation [4]. This allowed an attacker to inject arbitrary Python code through the query string, leading to remote code execution on the Vitrage service host [1][4].
Exploitation
An attacker with access to the Vitrage API can craft a malicious query containing Python code. The eval() call in the create_predicate function would execute the payload as if it were part of the application. The vulnerability affects all deployments exposing the Vitrage API, and no additional authentication beyond API access is required [1][3].
Impact
Successful exploitation allows the attacker to execute arbitrary commands under the same user account as the Vitrage service. This can lead to unauthorized access to the host, data exfiltration, and further compromise of the Vitrage service and potentially other services on the same host [1][3].
Mitigation
The fix replaces the dangerous eval() with safe function matching using a dictionary mapping operators to functions (e.g., operator.lt), ensuring only predefined operations are allowed [2]. Patched versions are 12.0.1 and later (the commit was merged for 2023.1 and subsequent releases). Users should upgrade immediately or restrict API access to trusted users only [2][3].
AI Insight generated on May 18, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
vitragePyPI | >= 15.0.0.0rc1, < 15.0.1 | 15.0.1 |
vitragePyPI | >= 14.0.0.0rc1, < 14.0.1 | 14.0.1 |
vitragePyPI | >= 13.0.0.0rc1, < 13.0.1 | 13.0.1 |
vitragePyPI | < 12.0.1 | 12.0.1 |
Affected products
2- OpenStack/Vitragev5Range: 0
Patches
189df4bd2ffdaReplace eval with function matching
2 files changed · +42 −35
releasenotes/notes/grap_query_eval_fixup-9232ce40ad85993e.yaml+5 −0 added@@ -0,0 +1,5 @@ +--- +security: + - | + A security issue in the entity graph querying mechanism has been fixed. + This change hardens the query parser against malicious input.
vitrage/graph/query.py+37 −35 modified@@ -12,6 +12,7 @@ # License for the specific language governing permissions and limitations # under the License. +import operator from oslo_log import log as logging from vitrage.common.exception import VitrageError @@ -21,13 +22,21 @@ operators = [ '<', '<=', - # '=', '==', '!=', '>=', '>', ] +ops = { + '<': operator.lt, + '<=': operator.le, + '==': operator.eq, + '!=': operator.ne, + '>=': operator.ge, + '>': operator.gt, +} + logical_operations = [ 'and', 'or' @@ -64,57 +73,50 @@ def create_predicate(query_dict): :return: a predicate "match(item)" """ try: - expression = _create_query_expression(query=query_dict) - LOG.debug('create_predicate::%s', expression) - expression = 'lambda item: ' + expression - return eval(expression) + return _create_query_function(query=query_dict) except Exception as e: LOG.error('invalid query format %s. Exception: %s', query_dict, e) raise VitrageError('invalid query format %s. Exception: %s', query_dict, e) -def _create_query_expression(query, parent_operator=None): - expressions = [] +def _create_query_function(query, parent_operator=None): # First element or element under logical operation if not parent_operator and isinstance(query, dict): (key, value) = query.copy().popitem() - return _create_query_expression(value, key) + return _create_query_function(value, key) # Continue recursion on logical (and/or) operation elif parent_operator in logical_operations and isinstance(query, list): - for val in query: - expressions.append(_create_query_expression(val)) - return _join_logical_operator(parent_operator, expressions) + predicates = [_create_query_function(val) for val in query] + + if not predicates: + return lambda item: False + + if parent_operator == 'and': + return lambda item: all(p(item) for p in predicates) + elif parent_operator == 'or': + return lambda item: any(p(item) for p in predicates) # Recursion evaluate leaf (stop condition) elif parent_operator in operators: - for key, val in query.items(): - expressions.append('item.get(' + _evaluable_str(key) + ')' + - parent_operator + ' ' + _evaluable_str(val)) - return _join_logical_operator('and', expressions) + predicates = [] + op_func = ops[parent_operator] + for field, value in query.items(): + predicates.append( + lambda item, f=field, v=value: op_func(item.get(f), v) + ) + + # Multiple conditions under a comparison operator are implicitly 'and' + if len(predicates) > 1: + return lambda item: all(p(item) for p in predicates) + elif predicates: + return predicates[0] + else: + return lambda item: False + else: raise VitrageError('invalid partial query format', parent_operator, query) - - -def _evaluable_str(value): - """wrap string/unicode with back tick""" - if isinstance(value, str): - return '\'' + value + '\'' - else: - return str(value) - - -def _join_logical_operator(op, expressions): - """Create an expressions string - - Example input: - op='AND' - expressions=['a == b', 'c < d'] - Example output: (a == b AND c < d) - """ - separator = ' ' + op + ' ' - return '(' + separator.join(expressions) + ')'
Vulnerability mechanics
Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
6- github.com/advisories/GHSA-8xwf-cr4r-856rghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2026-28370ghsaADVISORY
- www.openwall.com/lists/oss-security/2026/03/03/6ghsaWEB
- github.com/openstack/vitrage/blob/a1f86950e1314b0c740f9cd9b7e9dbab7d02af51/vitrage/graph/query.pyghsaWEB
- github.com/openstack/vitrage/commit/89df4bd2ffda1a5ddea66cd828438a6a171a3b11ghsaWEB
- storyboard.openstack.orgghsaWEB
News mentions
0No linked articles in our index yet.