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.
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:
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.
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.
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:
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.
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.
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.
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.
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.
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
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:
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.
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â.
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?
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:
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
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:
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.
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?
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
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!