Proof/CVEs & advisories/GHSA-5jx8-p6q2-455g
A cast to object changed the security answer.
DynamicExpresso blocks reflection-sensitive members unless the host calls EnableReflection(). Casting a System.Type to object moved the same access onto a dynamic call site — where that policy was never applied.
LateBindObjectThis is an expression-evaluator primitive, not a built-in remote endpoint. Remote reachability depends on a consuming application accepting attacker-controlled expression text.
The published path assumes a consuming application that:
- accepts less-trusted DynamicExpresso expression text;
- enables
InterpreterOptions.LateBindObject; - retains the default primitive/type registrations;
- provides a writable
objectparameter or an equivalent path into late binding; - permits assignment operators; and
- has not explicitly enabled reflection.
The expected security property is that LateBindObject changes member-resolution convenience — not the evaluator’s reflection security policy.DynamicExpresso 2.19.3 installs DisableReflectionVisitor unless reflection is explicitly enabled. That visitor rejects ordinary expression-tree members involving System.Type or System.Reflection.MemberInfo. A direct expression is therefore blocked, and makes a clean negative control:
typeof(string).Assembly // rejected by DisableReflectionVisitorThe behaviour changes when the same runtime value is first cast to object:
((object)typeof(string)).Assembly // resolved by the late binderParser.cs recognizes the statically typed object receiver and emits Expression.Dynamic nodes using late-binding call-site binders — LateGetMemberCallSiteBinder for member access, LateInvokeMethodCallSiteBinder for invocation. In vulnerable 2.19.3, the implementations in Resolution/LateBinders.cs pass the runtime object to Microsoft’s C# runtime binder without applying the restriction that DisableReflectionVisitor enforces.
The consequence is subtle but fundamental: casting does not change the runtime object’s authority. The value is still a System.Type. Only its expression-tree representation changed.
Reflection is disabled by configuration
The host never calls EnableReflection(); the documented policy says reflection-sensitive members are unavailable.
A default-registered type is cast to object
Static typing changes; runtime authority does not.
Member access becomes a dynamic call site
The static visitor no longer sees a sensitive member expression.
The late binder resolves assembly/type facilities
Capabilities that should have been gated behind an explicit opt-in are now reachable.
System.Diagnostics.Process is instantiated
A benign marker command starts in the host process context.
The important result is not the specific command string. It is that an evaluator configuration intended to have reflection disabled reached a host capability the documented policy says requires an explicit reflection opt-in.
Direct reflection
Blocked when reflection is disabled — the policy is demonstrably active.
Late-bound equivalent
The same runtime value reaches the marker through the 2.19.3 dynamic dispatch path.
2.19.5 package
Benign late binding still works; explicit reflection still works when the host opts in; the bypass does not.
DynamicExpresso had two dispatch paths with different security semantics. Statically typed member and method nodes passed through the reflection-denial visitor; dynamic call-site binders created for LateBindObject did not enforce the same restriction.
The reflection policy therefore depended on the representation of the operation rather than the runtime capability being accessed. This is a common policy-bypass pattern in interpreters: the main AST path is guarded, but an alternate dynamic dispatch mechanism reaches the same sink outside the checker.
The issue was never that System.Type became safe after a cast. It was that the security policy stopped following the value when dispatch moved to a different implementation path.Ask what else can reach the sink.
A reflection-denial visitor is a strong control on the path it inspects. The productive question was which other dispatch mechanism reaches the same runtime capability without passing through it.
Source mapping
Read the reflection-denial visitor and enumerate the dispatch paths that bypass expression-tree inspection.
Representation hypothesis
Test whether a type cast that preserves runtime authority changes the security decision.
Runtime construction
Reach a host capability through the late-bound path with reflection still disabled.
Skeptic gate
Prove the direct form is blocked, so the late-bound result is a policy bypass and not a missing control.
Promotion and disclosure
Verify package/tag mapping and prerequisites before publishing severity.
| Scope | Value | Basis |
|---|---|---|
| Public affected version | 2.19.3 | advisory scope |
| Patched version | 2.19.4 | advisory scope · verified fixed source |
| First fixed package executed | 2.19.5 | 2.19.4 NuGet artifact unavailable at verification time |
| Fixing commit | 23f7c3ee9e348a46c969190379597092033a8bd5 | v2.19.4 and v2.19.5 tags resolve to it |
The project states that 2.19.5 republishes the same code because of a NuGet issue, and the repository metadata confirms both tags map to one commit.
The LateBindObject feature itself is older — public history traces it to 4aabda0330d7f003c4e893008b560abaf7c305ea in v2.8.0. That is implementation lineage, not an expanded affected-version claim; earlier packages were not independently runtime-assessed.
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:HWhen the prerequisites are present, attacker-controlled expression text can escape the reflection restriction and reach command execution with the privileges of the hosting .NET process — files, credentials, environment data, network access and other application resources available to it. DynamicExpresso itself exposes no network listener; the real remote surface is determined by the integrating application.
Upgrade to DynamicExpresso 2.19.4 or later. For NuGet consumers, use a currently available fixed package such as 2.19.5 or newer.
The essential fix is to enforce reflection policy inside late-binding resolution rather than assuming the static expression visitor sees every sensitive access. Any future alternate dispatch mechanism should apply the same capability checks.
Regression coverage should combine:
- reflection disabled + direct
System.Typeaccess — must fail; - reflection disabled +
objectcast + late binding — must also fail; - ordinary non-sensitive late binding — should continue to work;
- reflection explicitly enabled — documented behaviour should continue to work;
- dynamic method calls as well as dynamic property/member reads.
- GHSA-5jx8-p6q2-455g — DynamicExpresso advisory and public PoC
- DynamicExpresso repository and release history
- DynamicExpresso.Core package listing
Credit: Discovered and reported by Charles Vosburgh. Research was AI-assisted through source mapping, hypothesis generation, release comparison and evidence organization; final validation, disclosure coordination, severity calibration and publication review remained human.