FileFlows SQL Injection by Decompiling .NET Code

Sometimes the good stuff isn’t on the surface. We turned to decompiling .NET code to find a hidden SQL injection flaw.

FileFlows SQL Injection by Decompiling .NET Code

Our project of choice this time round is an application called FileFlows.

FileFlows is a file processing application that can execute actions against a file in a tree flow structure.

The FileFlows application supports a master/slave architecture. The slave nodes' limited attack surface doesn't make it a good research target. The master node is partially open source and makes for a better target.

Exploring the Attack Surface

When analysing any application, a good place to start is by looking at the total attack surface. The most impactful vulnerabilities affect functionality that's available unauthenticated or accessible to lower privileged users.

In .NET applications, a quick trick is to search for controller decorators like [Route blah] or [HttpPost] to quickly find API endpoints.

└─$ grep -rnE '^\s*\[Http' FileFlows/ | wc -l
290

That's a decent sized application.

Unfortunately, nothing particularly interesting turned up by inspecting these.

But one thing did catch my attention. The LibraryFileController.cs uses a LibraryFileService that wasn’t defined anywhere in the code published in Github.

Also importantly, any authenticated user can call the /api/library-file/search endpoint.

// Routes defined in this class start with "/api/library-file"
[Route("/api/library-file")]

// Only users with the "UserRole.File" role can call these endpoints
[FileFlowsAuthorize(UserRole.Files)]
public class LibraryFileController : Controller
{
    ...
    // Defines the "POST /api/library-file/search" endpoint.
    [HttpPost("search")]

    // Indicates any logged in user can call this endpoint, even if they don't 
    // have the "UserRole.File" role.
    [FileFlowsAuthorize]
    public Task<List<LibraryFile>> Search([FromBody] LibraryFileSearchModel filter)
        => ServiceLoader.Load<LibraryFileService>().Search(filter);
    ...

Snippet from LibraryFileController.cs

So how can we get our hands on the full source code? Maybe it's worth digging a little deeper.

Digging Deeper - Decompilation with ILSpy

Fortunately for us, File Flows is written in C# which is fairly easy to decompile if can get our hands on the compiled application.

Downloading the FileFlows Docker container in this case was the easiest way for us the get the compiled server code.

Downloading the compiled application from the FileFlows Docker container.

By opening these with ILSpy we can recover most of the original source code.

The missing "LibraryFileService" class.

Striking Silver

Looking into the implementation of the POST /api/library/search endpoint, we get to the following code block inside of DbLibraryFileManager.

internal class DbLibraryFileManager : BaseManager
{
   ...
   // The implementation of the "POST /api/library/search" endpoint. 
   // The attacker controls the "filter" object.
   public async Task<List<LibraryFile>> Search(LibraryFileSearchModel filter)
   {
       ...
       if (!string.IsNullOrWhiteSpace(filter.LibraryName))
           // The attacker's input is used to generate a SQL query.
           wheres.Add("lower(" + Wrap("LibraryName") + ") like lower('%" + filter.LibraryName.Replace("'", "''").Replace(" ", "%") + "%')");
       }
       ...
       // The SQL query is generated.
       string sql = "select * from " + Wrap("LibraryFile") + " where " + string.Join(" and ", wheres);
       ...
       try
       {
           using DatabaseConnection db = await DbConnector.GetDb();
           // The attacker controlled "sql" query is sent to the database.
           return await db.Db.FetchAsync<LibraryFile>(sql, Array.Empty<object>());

String concatenation!

There is some good news and some bad news.

  • The good news: we have a textbook case of SQL injection.
  • The bad news: The .Replace("'", "''") call will prevent SQL injection in some databases. If we can use a \' to escape single quotes, we should be able to exploit this.

FileFlows supports four databases and after some analysis, this SQL injection flaw is only exploitable in MySQL.

  • SQLite. This is the default database. Unfortunately we can't escape the single quotes with a \ because "C-style escapes using the backslash character are not supported because they are not standard SQL." (ref).
  • Microsoft SQL Server. Microsoft SQL Server is just like SQLite. So we won't have any luck here either.
  • PostgreSQL. We probably cannot exploit PostgreSQL databases. \ can be used to escape single quotes if an escape string is used (e.g. E'...') or standard_conforming_strings is set to off (the default has been on since 2011).
  • MySQL. "MySQL uses C escape syntax in strings …" (ref). Therefore \' can be used to escape out of the query string.

Exploitation

With that in mind, let's validate the vulnerability and build a POC!

The first step? Getting the feature to work as intended. I'll skip the boring details but after a bit of finagling here's what the API looks like being used normally.

Calling the "POST /api/library-file/search" endpoint.

There's a couple of hurdles to keep in mind. Using the MySQL server we can add \ to escape out of the string but we need to remember that the .Replace(" ", "%") function will replace any spaces with %.

Fortunately for us, we can use MySQL comments (/**/) instead of spaces.

SQLi POC with FileFlows configured to use the MySQL database.

By default MySQL servers support multiple queries separated by a ;. Therefore, we can add our own commands after the query.

Let's extend this to build a POC to grant our user admin permissions. To avoid any potential encoding issues, strings are hex encoded (0x...).

Using MySQL's multiple queries syntax to perform a update the database to grant a user admin privileges.

Disclosure Timeline

  • 02/05/25- Research started on version "25.04.9.5355".
  • 09/05/25 - SQL injection vulnerability disclosed.
  • 11/05/25 - Verified SQL injection vulnerability fix.
  • 05/06/25 - CVE Requested

Conclusion

This vulnerability would’ve been nearly impossible to find or exploit without access to the source code.

There were no obvious indicators in the application's behaviour and the controls in place would've made building an exploit blind extremely difficult. Some vulnerabilities hide deep beneath the surface, and source code analysis can be the key to finding them.