Skip to main content

Securing File Uploads Part 4: Malware Scanning with Windows AMSI

Welcome to the final post in our file upload security series. We've covered content type validation, file size validation, and file signature validation—each providing a crucial layer of defense. Today, we're implementing the final and most sophisticated protection: malware scanning using Windows Antimalware Scan Interface (AMSI).

The last line of defense

Even after all our previous validation steps, a determined attacker could still upload malicious content:

  • A legitimate PDF with embedded JavaScript exploits
  • A valid Office document containing malicious macros
  • An actual image file with embedded steganographic payloads
  • A genuine archive containing malware
  • Zero-day exploits targeting file processing libraries

These files pass all our previous validations because they are legitimate file formats—they're just weaponized. This is where malware scanning becomes essential.

Why AMSI?

Windows Antimalware Scan Interface (AMSI) is a powerful, often overlooked Windows feature that allows applications to integrate with the system's antivirus engine. Here's why it's an excellent choice:

Advantages:

  • Leverages existing antivirus: Uses Windows Defender or any AMSI-compatible antivirus already installed
  • Always up-to-date: Detection signatures are updated automatically by Windows Update
  • No additional licensing: Free to use on Windows systems
  • Lightweight integration: Simple API with minimal overhead
  • Industry-standard detection: Benefits from Microsoft's extensive threat intelligence
  • Content-based scanning: Analyzes actual file content, not just signatures

Limitations:

  • Windows-only: AMSI is exclusive to Windows (for cross-platform needs, consider ClamAV)
  • Requires Windows Defender or compatible AV: Must have an AMSI-compatible antivirus engine installed
  • Not foolproof: Like all antivirus solutions, it can't detect unknown zero-day threats

For applications running on Windows Server, AMSI provides enterprise-grade malware detection without additional infrastructure or licensing costs.

Our implementation

Here's our malware scanning validation step:

Integrating AMSI

The AMSI integration code is based on Gérald Barré's excellent article. Here's the core implementation you'll need:

AMSI scans are typically fast (100-500ms for most files), but they can take longer for:

  • Very large files
  • Archives with many nested files
  • Files with complex structure (Office documents)
  • First scan after Windows Defender updates

The complete validation pipeline

Here's how all four validation steps work together for one last time:

Testing Strategy

Testing malware scanning is more complex than our previous validation steps. We used the EICAR test file approach.

The EICAR test file is a standard anti-malware test file that's detected by all major antivirus engines as malware, but is completely harmless:

Conclusion: Defense in depth works

Over this four-part series, we've built a comprehensive file upload security pipeline:

  • Part 1: Content type validation stopped files with wrong MIME types
  • Part 2: Size validation prevented resource exhaustion attacks
  • Part 3: Signature validation caught files masquerading as other types
  • Part 4: Malware scanning detected weaponized legitimate files

No single validation step is sufficient on its own. But together, they create overlapping layers of defense that have proven remarkably effective.

File upload security is not a "set it and forget it" concern. It requires ongoing vigilance, monitoring, and adaptation as new threats emerge. But with the foundation we've built, you're well-equipped to protect your applications from the vast majority of file upload attacks.

More information

Antimalware Scan Interface (AMSI) - Win32 apps | Microsoft Learn

Using Windows Antimalware Scan Interface in .NET - Meziantou's blog

EICAR test file - Wikipedia

Popular posts from this blog

Azure DevOps/ GitHub emoji

I’m really bad at remembering emoji’s. So here is cheat sheet with all emoji’s that can be used in tools that support the github emoji markdown markup: All credits go to rcaviers who created this list.

Podman– Command execution failed with exit code 125

After updating WSL on one of the developer machines, Podman failed to work. When we took a look through Podman Desktop, we noticed that Podman had stopped running and returned the following error message: Error: Command execution failed with exit code 125 Here are the steps we tried to fix the issue: We started by running podman info to get some extra details on what could be wrong: >podman info OS: windows/amd64 provider: wsl version: 5.3.1 Cannot connect to Podman. Please verify your connection to the Linux system using `podman system connection list`, or try `podman machine init` and `podman machine start` to manage a new Linux VM Error: unable to connect to Podman socket: failed to connect: dial tcp 127.0.0.1:2655: connectex: No connection could be made because the target machine actively refused it. That makes sense as the podman VM was not running. Let’s check the VM: >podman machine list NAME         ...

Cleaner switch expressions with pattern matching in C#

Ever find yourself mapping multiple string values to the same result? Being a C# developer for a long time, I sometimes forget that the C# has evolved so I still dare to chain case labels or reach for a dictionary. Of course with pattern matching this is no longer necessary. With pattern matching, you can express things inline, declaratively, and with zero repetition. A small example I was working on a small script that should invoke different actions depending on the environment. As our developers were using different variations for the same environment e.g.  "tst" alongside "test" , "prd" alongside "prod" .  We asked to streamline this a long time ago, but as these things happen, we still see variations in the wild. This brought me to the following code that is a perfect example for pattern matching: The or keyword here is a logical pattern combinator , not a boolean operator. It matches if either of the specified pattern...