Description
Code injection occurs when external input is interpreted as executable program structure instead of data. Passing an untrusted string to Roslyn scripting, CS-Script or a dynamic-compilation API can give the resulting code access to files, networks, environment variables and processes with the application's privileges when it is evaluated or executed.
APIs such as EvaluateAsync, RunAsync, Eval, LoadCode and LoadMethod evaluate or load code. CSharpScript.Create, CompileCode and CompileAssemblyFromSource might not execute it immediately, but produce attacker-controlled scripts or assemblies that can later be loaded or invoked. Both cases require a trust boundary.
Potential impact
- Arbitrary code or commands can run with the application's privileges.
- The confidentiality, integrity and availability of files and databases can be compromised.
- Internal network access, credential theft, privilege escalation or denial of service may follow.
Remediation
Avoid interpreting untrusted input as C# code.
- Accept an operation identifier and map approved values such as
addandsubtractto fixed application functions. - If expressions are necessary, parse a purpose-built grammar with explicit allowed syntax and operations instead of trying to restrict the full C# language. Limit input length, syntax depth, execution time and memory.
- If an existing integration must select a script, choose a complete script from a developer-controlled immutable catalog. Do not concatenate user input into code fragments.
- If executing untrusted C# is unavoidable, run it outside the application process. Use a separate process, container or virtual machine under a restricted account, with operating-system limits on filesystem access, networking, process privileges, CPU time and memory.
Reducing references or imports in ScriptOptions, or prohibiting unsafe, does not establish a security boundary. The C# and .NET API surface is difficult to restrict completely, and reference restrictions do not prevent infinite loops or memory exhaustion.
CodeDomProvider.CompileAssemblyFromSource is a legacy .NET Framework API. Microsoft documents that it always throws PlatformNotSupportedException on .NET Core and .NET 5 or later; do not use it as a modern .NET replacement or a new implementation example.
Examples
Before
The HTTP request body is evaluated directly as a C# script.
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.CodeAnalysis.CSharp.Scripting;
[ApiController]
[Route("api/scripts")]
public class ScriptController : ControllerBase
{
[HttpPost]
public Task<object> Run([FromBody] string code)
{
return CSharpScript.EvaluateAsync(code);
}
}
After
The request selects a fixed operation instead of providing executable code.
using Microsoft.AspNetCore.Mvc;
public record CalculationRequest(string Operation, decimal Left, decimal Right);
[ApiController]
[Route("api/calculations")]
public class CalculationController : ControllerBase
{
[HttpPost]
public IActionResult Calculate([FromBody] CalculationRequest request)
{
return request.Operation switch
{
"add" => Ok(request.Left + request.Right),
"subtract" => Ok(request.Left - request.Right),
_ => BadRequest("Unsupported operation")
};
}
}
References
- CWE-94: Improper Control of Generation of Code
- OWASP Top 10:2025 A05 - Injection
- OWASP Top 10:2021 A03 - Injection
- Roslyn Scripting API Samples
- Roslyn: Is running untrusted compiled code safe?
- Microsoft Learn: CodeDomProvider.CompileAssemblyFromSource
- CS-Script IEvaluator API
- CS-Script Script Syntax
- KISA Software Security Weakness Assessment Guide 2021