# php-malware-scanner: pattern matching for webshells, with a whitelist you have to maintain

> scr34m/php-malware-scanner walks a directory of PHP files and tests them against text and regular expression rules derived from known webshells, with MD5 whitelisting, WordPress checksum support, and a base64 mode. The author is candid that the rules are easy to bypass, so the output is a triage list rather than a verdict.

**scr34m/php-malware-scanner** — Scans PHP files for malwares and known threats

- Repository: https://github.com/scr34m/php-malware-scanner
- Stars: 606 · Forks: 102
- Language: PHP
- License: GPL-3.0
- Published: 2026-10-22 · Updated: 2026-10-22 · Language: en
- Canonical page: https://hysenlabs.com/projects/scr34m-php-malware-scanner

## A scanner that finds leads, and says so

php-malware-scanner scans PHP files for malwares and known threats. The mechanism is straightforward text matching against a directory tree. The README states the goal plainly: to find infected files, and to fight against people who find rules too easy to bypass. That last clause is the most useful sentence in the document for anyone evaluating the tool, because it sets the expectation correctly. This is a pattern matcher, and pattern matchers have a losing record against adversaries who know the patterns exist. What it does well is speed. Scanning a few thousand files for known webshell signatures takes seconds, and a clean tree gives you a clear signal. The output is a list of files to look at, and the tool's job ends there. The project description calls it a command line tool for malware scanning in PHP, and the code is published under the GPL-3.0 licence with the terms in the LICENSE.txt file at the repository root.

## Three pattern files, one line each, comments allowed

The rules live in three main pattern files that cover different types of matching, with one pattern per line. Any line whose very first character is a `#` is treated as a comment and not used as a pattern, and whitespace in the pattern files is not used. The three files are `patterns_raw.txt` for raw string matching, `patterns-iraw.txt` for case insensitive raw string matching, and `patterns-re.txt` for regular expression matching. The rules were built from self gathered samples and from publicly available malwares and webshells. The comment convention is not decoration, because the `--comment` flag displays the last comment appearing in the pattern file before the matched pattern, which is how a hit explains itself. The README is explicit that documenting the pattern files is therefore important, since that comment is the only explanation a triaging engineer gets.

## The CLI is a directory, an extension, and a list of output filters

Running the scanner means pointing it at a directory:

```bash
Usage: php scan.php -d <directory>
    -d <directory>       --directory          Directory for searching
    -b                   --base64             Scan for base64 encoded PHP keywords
    -w                   --hide-whitelist     Hide results with 'WL' status
```

The extension argument defaults to `.php` and can be given more than once, and the ignore argument can also be used multiple times and accepts glob style matching, with examples such as `cache*`, `??-cache.php`, or `/cache`. The output flags are what make the tool usable in a pipeline. `-k` hides results with an OK status, `-r` hides results with an ER status, and `-w` hides results with a WL status, so a first pass can show only the interesting files. `-m` shows the MD5 hash of a file, `-c` shows the comment for a matched pattern, `-p` shows the pattern next to the file name, `-L` gives the matching line number, and `-t` shows the time of the last file change. There is also `-s` to keep scanning a file after the first hit instead of stopping, and `-n` to disable the ANSI colouring that the default output uses.

## Output is a format string with seven variables

The output layer is more configurable than most scanners of this kind. The full default format is `%S %T %M # {%F} %C %P # %L`, and the variables are the matching indicator with possible values OK, ER or WL, the file change time, the file MD5 hash value, the file with path, the pattern, the pattern comment, and the matching pattern line number. The `-o` option takes a custom output format, so a shell script or a log pipeline can consume exactly the fields it needs rather than parsing human output. The three indicator values deserve attention because they are the whole output model. OK means the file matched no pattern. ER means it matched and is therefore an error condition. WL means it matched but its hash is on the whitelist, so the match is known to be a legitimate file. The combination of ER and WL is where the practical work of a scan happens, since a file can match a webshell signature and still be an unmodified copy of a shipped application.

## The whitelist is MD5 based, and WordPress checksums fill it automatically

False positives are the reason the whitelist exists, and the implementation is deliberately simple. The predefined whitelist is a file of MD5 hashes where only the first 32 characters are used and the rest of the line is ignored, so you can leave a comment after a hash. The scanner checks the hash of the file against that list. The WordPress route is the interesting one. You can add checksums from WordPress core files automatically by specifying a version to the `--wordpress-version` option, for example `scan -d . -j 4.9.2`, and the tool fetches the checksums from the WordPress API and adds them to the whitelist. The README explains how to find your version, in the `wp-includes/version.php` file of your WordPress install. There is also a combined whitelist, a pre-generated database for open source projects, which the scanner validates before downloading and only fetches when it differs from what it has and when the argument is used. The `--custom-whitelist` option loads a whitelist from a specified file and merges it with the existing one.

## Base64 mode trades speed and accuracy for coverage

The `--base64` flag switches to an alternative scan mode that ignores the main pattern files and instead uses a large list of PHP keywords and functions converted to base64. The README describes the trade-off directly: it is slower and prone to false positives, but it gives additional base64 scanning coverage, and the pattern files live in `base64_patterns` and were derived from PHP 7 keywords and functions. Not many PHP extensions are included. The matching is inherently approximate because of how base64 alignment works, and the repository includes a tool that makes this explicit. `tools/text2base64.py` takes a plaintext string and returns three base64 equivalents, and the example in the README shows the three forms for `base64_decode`. The important property is one-directional. The README states it is guaranteed that if the subject string was present in the original code, then one of the three output strings will be present in the base64 version, while the presence of one of them does not guarantee the string was in the original code, because a variant with missing edge characters can produce the same output.

## It can be used as a library, with a caveat about the entry point

The scanner is not only a command line tool. The `scan.php` file performs a check for whether it was called from the command line, and to use it as a library you have to use a different directory than the one scan.php itself sits in. The usage is a class with a small fluent surface:

```php
<?php

require_once '../scan.php';

$scan = new MalwareScanner();
$scan->setFlagHideWhitelist(true);
$scan->setFlagHideOk(true);
$scan->run('../samples/test');
```

The flags mirror the command line options, so `setFlagHideWhitelist` and `setFlagHideOk` are the library equivalents of `-w` and `-k`, and `run` takes the directory to scan. This is the shape you want if you are building a CI check or a deploy pipeline that refuses to publish when an ER result appears. The repository also has a `Dockerfile` for that case, based on `php:8.2-cli` with the zip extension installed, which copies the source into `/scanner`, makes the `scan` script executable, and sets it as the entrypoint. The `definitions/` directory, `base64_patterns/` directory, `ast_visitor.php`, and `whitelist.txt` at the root complete the picture of a small, self-contained tool.

## Conclusion

php-malware-scanner fits a sysadmin or developer who suspects a shared host or a pulled-in dependency has been tampered with, and who wants a fast pass over a tree of PHP files that produces a shortlist to investigate by hand. It is the wrong tool for a hosting platform that needs a verdict rather than a lead, because the pattern rules are trivially evaded and the whitelist needs constant care, and it is the wrong tool if your files are obfuscated or packed. Before you rely on the output, run it against a directory you know is clean to see the false positive rate, and remember that the WordPress checksum whitelist only covers core files, since plugins and themes are outside it.

## FAQ

### How do I install and run php-malware-scanner?

Clone the repository, or install it globally with `composer global require scr34m/php-malware-scanner`, then point the scanner at a directory with `php scan.php -d <directory>`. The extension argument defaults to `.php` and can be given more than once.

### What do the OK, ER, and WL statuses mean in php-malware-scanner output?

OK means the file matched no pattern, ER means it matched and is treated as an error, and WL means it matched but its MD5 hash is on the whitelist, so the file is a known legitimate one. The `-k`, `-r`, and `-w` flags hide results with those statuses respectively.

### How does the php-malware-scanner whitelist work?

It is a list of MD5 hashes where only the first 32 characters of each line are used, so comments can follow a hash. WordPress core checksums can be added automatically by passing a version to `--wordpress-version` or `-j`, which fetches the checksums from the WordPress API. A combined whitelist of open source project hashes is also available.

### What does the --base64 flag do in php-malware-scanner?

It is an alternative scan mode that ignores the main pattern files and uses a large list of PHP keywords and functions converted to base64, derived from PHP 7 keywords and functions. The README notes it is slower and prone to false positives, and that the presence of a base64 string does not guarantee the original string was in the code.

### Can php-malware-scanner be used as a library?

Yes. Require scan.php, instantiate MalwareScanner, set the flags you want, and call run with the directory to scan. Because scan.php checks whether it was invoked from the command line, you need to use a different directory than the one scan.php itself lives in.

## Sources

- [Issues](https://github.com/scr34m/php-malware-scanner/issues)
- [License: GPL-3.0](https://github.com/scr34m/php-malware-scanner/blob/master/LICENSE)
- [README](https://github.com/scr34m/php-malware-scanner/blob/master/README.md)
- [scr34m/php-malware-scanner on GitHub](https://github.com/scr34m/php-malware-scanner)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/scr34m-php-malware-scanner
