New Plugin for Feedback: File Integrity Scanner to Secure OJS

Hello everyone,

We are excited to release the File Integrity Scanner, a new security plugin for OJS 3.x.

This plugin protects your journal by scanning your OJS core and plugin files for any unauthorized modifications, additions, or deletions. It works by comparing your local files against official cryptographic hashes to immediately detect changes that could signal a security breach.

We are actively seeking feedback from the community to help us improve the plugin and make it as robust as possible. We would greatly appreciate any thoughts, suggestions, or bug reports you might have.

Key Features

  • Automatic Daily Scans: Runs a full integrity scan once every 24 hours.

  • Email Alerts: Instantly notifies the site administrator if any modified, added, or deleted files are found.

  • Core & Plugin Validation: Checks the integrity of both the main OJS application files and all installed plugins.

  • On-Demand Actions: Manually trigger a scan or clear the hash cache directly from the plugin settings.

Download

Releases · ashvisualtheme/file-integrity-scanner · GitHub

1 Like

Hi @ashvisualtheme,

Thanks, this looks like a thoughtful plugin!

I have one fundamental concern, though – assuming this plugin is written intending to address remote code injections, wouldn’t it be possible for the malicious code to simply modify, disable or remove this plugin before continuing with its goals?

Regards,
Alec Smecher
Public Knowledge Project Team

That’s a very sharp and insightful observation, and you are absolutely correct.

This highlights a fundamental challenge of any application-level security monitor: if an attacker gains sufficient privilege to modify files, they inherently have the privilege to modify or disable the monitor itself.

This gets to the core of the plugin’s purpose: it is a detection tool, not a prevention tool.

Let’s break this down into the two likely scenarios you identified:

  1. Automated ‘Hit-and-Run’ Attacks: This is the most common case (e.g., SEO spammers, Gambling, Porn). An automated script scans thousands of sites for a known vulnerability (perhaps in an outdated plugin or theme). When found, it automatically uploads its payload (like webshells or spam files) and immediately moves on to the next victim. It does not stop to analyze the server’s specific plugins. In this scenario, the scanner is highly effective. It will run on its schedule, detect the ‘Added’ or ‘Modified’ files, and alert the administrator immediately.

  2. Targeted, Manual Attacks: If your site is specifically targeted, and an attacker gains high-level privileges (like an admin account or RCE), your concern is 100% valid. A skilled attacker will attempt to cover their tracks, and their first steps would be to find and disable logging or monitoring tools, including this one.

To be clear, this plugin itself is not an attack vector. It has no front-end components, requires Site Admin privileges and CSRF validation for all its manual actions (like running a scan or clearing the cache), and applies security hardening (like cache tampering verification). An attacker cannot exploit this plugin to gain entry.

So, how do we address this? Your concern is solved by layered security.

This plugin provides the first line of defense at the application level. It cannot (and is not designed to) defend itself from a compromised administrator or a server-level breach. The ultimate solution, as you correctly identified, is to add a security layer above the application, at the server-level.

A server-level tool like AIDE (Advanced Intrusion Detection Environment) or another File Integrity Monitoring (FIM) system runs with root privileges. These tools monitor all files, including the OJS application files and this plugin’s files.

In this model:

  • If an attacker uploads a spam file, our OJS plugin will likely detect it first.

  • If the attacker tries to disable our plugin to avoid detection, the server-level AIDE scan will detect that that change (the modification to the plugin’s files or database status) and alert the server administrator.

Hi @ashvisualtheme

This discussion is interesting.

What if I don’t have AIDE or something similar, how can I ensure this plugin is working as intended?

Hi @afifsh @asmecher

That’s an excellent follow up and gets to the heart of practical, “real-world” monitoring. You’re right, not everyone has a server-level FIM (File Integrity Monitor) running.

We anticipated this, so we built two specific features into the plugin precisely to address your question: one is a “dead man’s switch” (a feature that alerts the administrator when it stops working), and the other is a self checking mechanism.

The following is the explanation:

In a scenario where your website is under a targeted attack, an attacker typically has four ways to bypass this plugin:

  1. Disable or Delete the Plugin: The administrator will know almost immediately. The plugin is designed to send a daily email notification even if no issues are found (an “All Clear” report). If the administrator stops** receiving** this daily email, it serves as a “dead man’s switch” alerting them that something is wrong.

  2. Modify the Hash Cache: This is nearly impossible. The plugin has Built-in Cache Tampering Protection. Every cache file is signed with an HMAC hash using unique salt. If an attacker modifies a cache file, the signature will fail verification. The plugin will then discard the corrupt cache and download fresh baselines, ensuring the attacker’s files are still found.

  3. Add New Files (e.g., Webshells): This is the most common attack. The scanner hashes all files in the installation and compares them to the official core and plugin baselines. Any file that exists locally but is not in a baseline is immediately flagged and reported as “Added” in the email alert.

  4. Modify the Plugin’s Code: This is possible but this is the most complex scenario, and you’ve hit on a key point. Let’s assume the attacker tries to modify the plugin’s code to bypass its own detection (e.g., force it to report a ‘good’ hash for itself). This is extremely difficult for several reasons:

    • High Technical Barrier: This is not an automated attack. The attacker must manually read and understand the plugin’s PHP code. They must then inject their own bypass logic flawlessly without creating a PHP error. If they make a single mistake, the plugin breaks, the daily “All Clear” email (Point 1) stops, and the administrator is alerted.

    • External Source of Truth: The attacker cannot change the official baseline hashes because they are stored externally on a remote GitHub repository. They can only try to “blind” the local scanner.

    • Incomplete Cover Up: Even if the attacker successfully modifies the scanner to hide itself, they did this for a reason—usually to add a webshell or modify an OJS core file. The (now compromised) scanner will still run. It will scan those other malicious files, compare them to the external baseline, and report them as “Added” or “Modified”.

    • To be Truly Successful: the attacker must add new bypass logic for every single file they touch. They would have to maintain a complex, patched version of the scanner just to hide, which is far more difficult than simply deleting the plugin (which alerts the admin) or adding a file (which gets detected).

In short, the only way for an attacker to avoid detection is to completely disable / delete the plugin (point 1), and the absence of the daily “All Clear” email is the built-in alert system for that exact scenario.

Regard,
Rama Arya
AshVisualTheme Dev. Team

Interesting product and thread. However, this caught my suspicion: The plugin is calling home somehow? Can you please comment on that?

1 Like

Hi @mpbraendle

That’s a very valid and important question. We’re happy to clarify this, as transparency is key.

You are correct: the plugin does make an external connection, but it’s not calling home in the sense of sending data from your server. It’s the exact opposite: it’s a one-way download from a read-only, public source.

This is what we mean by an External Source of Truth. For the plugin to know if your local files have been modified, it must compare them against an official “master list” of what those files should look like.

This master list (a set of JSON files containing cryptographic hashes) is stored in a dedicated public GitHub repository, which is also mentioned in the plugin’s README file.

When the scan runs, the plugin simply downloads the relevant official hash file (e.g., ojs-3.4.0.9.json or a hash for a specific plugin) from that URL. It then compares those official hashes against the hashes of the local files in your installation.

Most importantly, the plugin does not send data from your installation back to this repository. It does not transmit your file list, your configuration, your user data, or anything at all. It is purely a download process to get the comparison baseline, similar to how a package manager (like apt or npm) checks a central repository for the latest package list.

This is a fundamental security design choice. By storing the “master list” on an independent, external server, an attacker who gains access to your OJS server cannot also modify the hash baseline to cover their tracks.

Thank you for the question!

Regard,
Rama Arya
AshVisualTheme Dev. Team

Hi @ashvisualtheme and @mpbraendle,

This is a fundamental security design choice. By storing the “master list” on an independent, external server, an attacker who gains access to your OJS server cannot also modify the hash baseline to cover their tracks.

I still worry about the issue I raised above. Having the file hashes stored on a 3rd party server doesn’t offer any guarantees as long as the code that fetches it could be subject to intervention.

Thanks,
Alec Smecher
Public Knowledge Project Team

1 Like

Hi @asmecher

You are absolutely correct. If an attacker could intercept the connection and redirect the original link to their malicious server, they could deceive the plugin. This is the Man-in-the-Middle (MITM) attack scenario.

This is indeed the primary challenge when using an external server for the baseline hash, and we understand that. Let’s discuss it here to perfect this plugin.

1. JSON (Baseline Hash) from External Server (Current Method)

This is the current method in the plugin. Here is the process:

After the plugin is activated, the administrator runs the first scan via the “Run Manual Scan” button:

  • The plugin checks all current versions of the OJS core and all installed plugins.
  • The plugin will then download the JSON from GitHub (external server) once and save it to the cache directory with a unique files name.
  • Next, the plugin scans the files in your installation and compares their hashes against the cached baseline.
  • The baseline hash is only downloaded once at the beginning. It is not re-downloaded during subsequent daily scheduled scans.
  • The baseline hash is only re-downloaded if: the administrator clicks the “Clear Hash Cache” button, forces a manual scan (which sets forceRefresh=true), a version upgrade is detected (which orphans the old cache file), or if the cache file is corrupted or unreadable.

The point is that the baseline hash is not re-downloaded on every scan, which itself minimizes the window for an MITM attack.

Regarding the MITM concern, the plugin currently applies two layers of defense:

  1. Hardcoded URL: The URL is a PHP constant, so the only way for an attacker to change it is to modify the plugin file itself.

    • Implication: An attacker cannot “provide a fake link” via settings or the database. They must modify plugin file on your server.
  2. Strict SSL/TLS Verification (e.g., against DNS poisoning): We explicitly force PHP to perform strict SSL/TLS certificate verification. This was a feature added specifically to “protect against potential man-in-the-middle attacks”.

    'verify_peer' => true,
    'verify_peer_name' => true,
    'allow_self_signed' => false,
    

    This brings us to the weakness you identified: This verification is only at the domain level. It does not protect against a path-level bypass where an attacker proxies a valid domain but serves content from a different path (e.g., github.com/attacker/repo instead of github.com/ashvisualtheme/repo). This is what we plan to develop next by adding GPG signature verification, which would force PHP to validate that the file truly originated from us, regardless of the transport path.

2. JSON (Baseline Hash) from Local File

The method: The plugin would create a baseline from the current installation to be used as the baseline hash and save it to the cache.

With this method, the concern of an MITM attack would never happen. However, in our opinion, the weakness here is that the administrator must be absolutely certain that their OJS and plugin files are 100% clean at the moment they create the baseline.


Proposal & Question

What are your thoughts on this? What if we built the plugin to support both methods by adding a setting for the baseline hash source (“Online” or “Local”), defaulting to “Online”? This would give the administrator the choice to switch to “Local” if they are confident their installation is clean.

Or is there another approach you or the PKP team would recommend for perfecting this plugin?

Regard,
Rama Arya
AshVisualTheme Dev. Team

Sorry, I missed something in my previous comment:

We will also be refactoring the plugin to use Guzzle for external requests instead of file_get_contents, leveraging the Guzzle library that is part of the OJS core dependencies. This is purely to improve reliability and error handling, as file_get_contents can be unreliable or disabled on some hosts.

This is a separate improvement from the GPG signature verification we discussed, which remains our definitive plan to solve the core security weakness (the path-level bypass), as I mentioned here:

Hi @ashvisualtheme,

Consider the following pseudocode:

validator.php:

$hashes = get_hash_list('https://github.com/.../hash_list.csv');
$errors = check_all_files($hashes);
email_errors_to_admin($errors);

You can make this code as secure as you want, but as long as something can modify validator.php and do something like


$hashes = get_hash_list('https://github.com/.../hash_list.csv');
$errors = check_all_files($hashes);

// Maliciously set the error list to empty so it looks like nothing is wrong
$errors = [];

email_errors_to_admin($errors);


it doesn’t matter how careful you are with where the hashes are stored etc.; the checks can be trivially nullified.

This fundamental problem is why we haven’t tried to build tools like this into OJS. They can be categorically defeated as long as the code can be locally modified.

Regards,
Alec Smecher
Public Knowledge Project Team

Hi, @asmecher

Yes, you are 100% correct. This is the fundamental weakness of this plugin.

If an attacker has already gained file-write access to the server, this plugin can be defeated. We acknowledge that this weakness is theoretically unsolvable within a plugin architecture.

I actually alluded to this specific risk in my previous message:

Is there a way to completely solve this? The answer is yes, but it requires changing the approach entirely:

  1. External Monitoring (SaaS Architecture): As you implied, true security requires the check to be performed from a secure external server (monitor server) that verifies the OJS installation remotely. This shifts the trust anchor away from the potentially compromised environment.
  2. Immutable Files: System administrators could enforce file immutability (e.g., chattr +i on Linux) on the plugin files. This would ensure that even if the attacker has web-user write access, they cannot modify the plugin’s code to bypass the check.

However, implementing these solutions makes the tool significantly more complex to deploy (requiring external services or root-level sysadmin intervention).

Because of this, I believe the plugin still serves a critical purpose as an accessible “tripwire” for the majority of common, automated, or non-targeted attacks.

Given this, do you agree that the plugin still provides significant value as this “first-line-of-defense,” even while acknowledging its limitation against a deeply entrenched, plugin-aware attacker?

Regard,
Rama Arya
AshVisualTheme Dev. Team

Hi @ashvisualtheme,

I agree that it may well catch some kinds of problems – but would shy away from e.g. regular reporting that might give a false sense of security.

Some of the attacks we’ve seen on community installations are tailored to the software, e.g. importing XML documents that lead to privilege escalations. (These have since been fixed in recent releases but a lot of folks are running older versions). That attackers are formulating application-specific XML documents means that at least some effort is going into targeting OJS specifically, so it’s not too big a stretch to imagine they would also look at attacking preventative measures.

For that reason, we have opted not to integrate this kind of mechanism into OJS. We can’t guarantee that it won’t be subverted.

Regards,
Alec Smecher
Public Knowledge Project Team

Hi @asmecher

Thank you for that explanation. It gives me complete clarity on why PKP has opted not to integrate this type of mechanism natively.

I fully agree with your point regarding the “false sense of security”. This discussion has been incredibly valuable in defining the proper scope and messaging for this plugin.

I will maintain this as a third-party tool for the community members who, understanding these limitations, still value an automated “sanity check” for their file system. I will also update the documentation to explicitly state that this tool is a monitoring aid for general hygiene, not a replacement for keeping OJS updated or server-level security measures.

Thanks again for taking the time to provide such detailed feedback. It is much appreciated!

Regards,
Rama Arya
AshVisualTheme Dev. Team

This topic was automatically closed after 12 days. New replies are no longer allowed.