Current situation: PHP2Shell. Two critical PHP vulnerabilities (CVSS 9.2) allow malicious code without login. Patches since 21 October 2026.What to do now →

Checking the Database for Injected Code

If you only check files, you miss half. Here is how to search content, settings, triggers and accounts of the database for injected code, read-only and on a copy.

Last updated 6 min readDetection and forensics

In short: To check the database for malicious code, search an export or a copy with read-only queries for injected scripts, foreign addresses and obfuscated code in content and settings. Also check triggers, scheduled events, stored procedures and the database users. Clean up only after assessment and never with blind search and replace, because serialized data would otherwise break.

The blind spot of many cleanups

After a break-in, most operators check the files. They replace the CMS core, delete suspicious PHP files and reinstall extensions. The website then looks clean, and a week later it redirects to foreign sites again. The reason is often the database, which was taken over unchanged during the cleanup.

For attackers, the database is an attractive hiding place. A script in a widget is delivered on every page. A changed address setting diverts all visitors. An extra admin account opens the door for later. None of this shows up in a file scan.

After PHP2Shell, the vulnerabilities CVE-2026-109618 and CVE-2026-109619 with code execution without login, the database therefore belongs in every check. Anyone who can execute code on the server can, as a rule, read the database credentials from the configuration and write there at will. How the application talks to the database in the first place is explained in PHP and databases: PDO and mysqli.

Where malicious code hides in the database

Location Typical content Effect
Content (posts, pages, products) <script> blocks, invisible links, <iframe> malicious code or spam for visitors
Settings and options changed site address, foreign scripts in widgets or theme options redirects, scripts on all pages
User tables additional administrators permanent access
Triggers code that changes content on every save malicious code returns after cleanup
Scheduled events recurring tasks in the database server changes without web access
Database users additional accounts with far-reaching rights access bypassing the CMS

The last three rows are overlooked especially often because they are not in the CMS tables but in the database server itself.

For online shops, one location deserves special attention: settings through which additional scripts are included in checkout and order completion. A foreign script at this point can read customers' input before it is sent encrypted to the payment service. So check all fields for header and footer code, tracking includes and design settings that act on checkout pages.

Also think of other databases on the same server. Test and staging copies, old project states or databases of other websites often sit next to it and are reachable with the same credentials. What is found in the live database should also be checked there before a copy later serves as a basis again.

Checking the database for malicious code: content and settings

Work on an export. It serves as evidence preservation and as a search basis at the same time. The export can be searched as a text file without putting load on the running database:

# Create a consistent export (credentials will be prompted)
mysqldump --single-transaction --routines --triggers --events -u BENUTZER -p DATENBANK > export.sql
sha256sum export.sql > export.sql.sha256

# Count typical patterns in the export
grep -oiE '<script[^>]{0,80}' export.sql | sort | uniq -c | sort -rn | head -30
grep -oiE '(eval\(|base64_decode|fromCharCode|document\.write|atob\()' export.sql | sort | uniq -c
grep -oiE 'https?://[a-z0-9.-]+' export.sql | sort | uniq -c | sort -rn | head -50

The last query lists all domains that occur in the database, by frequency. Foreign domains that nobody on the team knows are the most important candidates. For targeted queries in the database itself, SELECT statements are suitable. The example uses WordPress tables; the prefix and table names of other systems are in their documentation:

-- Settings containing scripts or suspicious functions
SELECT option_name, LENGTH(option_value) AS laenge
FROM wp_options
WHERE option_value LIKE '%<script%'
   OR option_value LIKE '%eval(%'
   OR option_value LIKE '%base64_decode%'
ORDER BY laenge DESC;

Entries with very large content deserve special attention. Background on WordPress-specific hiding places is in the guide Finding backdoors in WordPress.

Triggers, events and database accounts

Triggers and scheduled events are not intended at all in many web applications. If they exist, someone should be able to explain why. These queries only read from the system tables of MySQL and MariaDB:

-- Triggers in all databases
SELECT TRIGGER_SCHEMA, TRIGGER_NAME, EVENT_OBJECT_TABLE, CREATED
FROM information_schema.TRIGGERS;

-- Scheduled events
SELECT EVENT_SCHEMA, EVENT_NAME, STATUS, CREATED, LAST_EXECUTED
FROM information_schema.EVENTS;

-- Stored procedures and functions
SELECT ROUTINE_SCHEMA, ROUTINE_NAME, ROUTINE_TYPE, CREATED
FROM information_schema.ROUTINES
WHERE ROUTINE_SCHEMA NOT IN ('mysql', 'sys', 'information_schema', 'performance_schema');

-- Database users
SELECT user, host FROM mysql.user ORDER BY user;

If your application uses PostgreSQL or another database system, system tables and commands are named differently; the principle stays the same. For the analysis, a separate database user that may only read is recommended. That way even a typo in a query cannot change anything, and the check leaves no traces in the data.

A trigger that appends code when a post is saved explains why malicious code returns after every cleanup. Unknown database users belong in the account review just like admin accounts in the CMS; more on this under Finding foreign admin accounts in CMS and server.

Cleaning up properly without destroying data

The cleanup is the most delicate part. This order has proven itself:

  1. Assess findings: Every hit is classified as known, harmlessly explainable or foreign. Tracking and map codes are often legitimate.
  2. Close the entry point: Bring PHP to a fixed version (8.5.12, 8.4.27, 8.3.36, 8.2.35 or a confirmed backport), restart all workers, update the application.
  3. Remove specifically: Clean up foreign content through the application or with tools that handle serialized data correctly. Delete foreign triggers, events and accounts.
  4. Change credentials: New database password, enter it in the application's configuration, limit the application user's rights to its own database.
  5. Recheck: Repeat the search queries after a few days.
Important

Do not replace anything with search and replace in the SQL export. Serialized PHP data stores the length of each string. If the length changes, the setting becomes unreadable and the website can fail.

Redirects that occur despite a clean database have their cause in another layer. The article The website redirects to foreign sites helps narrow it down.

How does PHP SOS help?

  • We back up the database with a checksum and search the export for scripts, foreign domains and obfuscated code.
  • We check triggers, events, procedures and database users that lie outside the CMS.
  • We clean up findings as part of malware removal without damaging serialized data.
  • We limit the rights of the application user and repeat the check after the cleanup.

Frequently asked questions

Can malicious code hide in the database?

Yes. Attackers write JavaScript into posts, product descriptions or widgets, change address settings for redirects or create admin accounts. In MySQL and MariaDB, triggers and scheduled events can also modify data. A file scan finds none of this.

How do I search the database without breaking anything?

Work on an export or a copy and use only SELECT queries. An export can also be searched with grep as a text file. Changes are made only once every finding has been assessed and a backup exists.

Why shouldn't I clean up findings with search and replace in the export?

Many CMS store settings as serialized PHP data, in which the length of each string is stored as well. A replacement changes the length but not the stored value, and the setting becomes unreadable. Clean up through the application or with tools that understand serialized data.

Is every script in the database suspicious?

No. Tracking codes, maps, videos or chat widgets are also stored as scripts. What matters is whether someone on the team knows the entry and the target address.

Sources

Immediate contact

Every hour counts. Write to us now.

Briefly describe your system, PHP version and observation. We will get back to you, clarify the situation and work remotely until the incident is closed and documented.

Immediate help