Community
0 56
HostiServer
2025-12-17 09:00:00

SQL Injection Protection in 2026: Complete Guide by Hostiserver

⏱️ Reading time: ~10 minutes | 📅 Updated: December 17, 2025

SQL Injection in 2026: Why It's Still a Problem

SQL Injection has remained in the OWASP Top 10 for over 20 years. In November 2025, OWASP published an updated ranking where Injection holds 5th place (A05:2025). The FBI and CISA have officially labeled SQLi an "unforgivable defect" — a vulnerability that shouldn't exist in modern software.

But it does exist. At Hostiserver, we see this regularly: clients come to us after breaches, with damaged databases and leaked user data. In most cases, the cause is missing prepared statements or legacy code without validation.

This guide isn't a theoretical overview. We've compiled configurations and approaches we actually use on Hostiserver managed servers: from ModSecurity setup to MySQL hardening. Everything tested in practice.

⚠️ Important: If your site accepts any user input (forms, search, filters, URL parameters) — it's potentially vulnerable. Even a "simple blog" on WordPress can become a victim through a vulnerable plugin.

How SQL Injection Works

SQL Injection is an attack technique where an attacker inserts malicious SQL code into input fields. If the application doesn't validate input, this code executes on the database server.

SQL Injection

Vulnerable Code Example

// ❌ UNSAFE — never do this!
$username = $_POST['username'];
$query = "SELECT * FROM users WHERE username = '$username'";
$result = mysqli_query($conn, $query);

If an attacker enters in the username field:

' OR '1'='1' --

The query becomes:

SELECT * FROM users WHERE username = '' OR '1'='1' --'

Result: the attacker gains access to all records in the table.

Attack Types

Type Mechanism Detection Difficulty
Classic (In-band) Result visible on page Low
Union-based UNION extracts data from other tables Low
Error-based Data through error messages Medium
Blind SQLi No visible result, "guessing" High
Time-based Blind SLEEP() determines condition truth High
Out-of-band Data sent to external server Very High

Recent CVEs: SQL Injection in 2024-2025

SQLi isn't an archaic problem. Critical vulnerabilities are found even in modern enterprise software. Here are examples we've been tracking:

CVE-2025-25257: Fortinet FortiWeb

Ironic case: SQL Injection in a WAF itself. CVSS 9.6 Critical. The vulnerability allowed unauthenticated attackers to execute SQL commands via HTTP requests. (Source).

CVE-2025-1094: PostgreSQL

Critical vulnerability in PostgreSQL escaping functions. CVSS 8.1 High. Bypass of prepared statements through incorrect multibyte character handling. Affected all versions up to 17.3. (Source).

CVE-2024-42327: Zabbix

CVSS 9.9 Critical. Any user with API access could exploit SQLi and escalate privileges. Zabbix is used by thousands of companies for infrastructure monitoring. (Source).

MOVEit Transfer (CVE-2023-34362)

This case is still referenced as an example of scale. SQLi led to compromise of over 2,500 organizations. (Source).

What an Attacker Can Do

  • Steal data — passwords, emails, payment details
  • Modify data — forge transactions, change prices
  • Gain shell access — via INTO OUTFILE
  • Destroy database — DROP TABLE, TRUNCATE
  • Escalate privileges — gain admin access

Prepared Statements: The Only Right Approach

Prepared Statements (parameterized queries) are the most effective protection against SQL Injection. They separate SQL code from data. We recommend this as baseline standard for all projects.

PHP PDO (Recommended)

// ✅ SAFE — PDO with prepared statements
$pdo = new PDO('mysql:host=localhost;dbname=app_db;charset=utf8mb4', $user, $pass);
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, false); // Important!
$stmt = $pdo->prepare('SELECT * FROM users WHERE username = :username AND status = :status');
$stmt->execute([
    ':username' => $username,
    ':status' => 'active'
]);
$user = $stmt->fetch(PDO::FETCH_ASSOC);

PHP MySQLi

// ✅ SAFE — MySQLi with prepared statements
$mysqli = new mysqli('localhost', $user, $pass, 'app_db');
$mysqli->set_charset('utf8mb4');
$stmt = $mysqli->prepare('SELECT * FROM users WHERE username = ? AND status = ?');
$stmt->bind_param('ss', $username, $status);
$stmt->execute();
$result = $stmt->get_result();
$user = $result->fetch_assoc();

Why This Works

When using prepared statements:

  1. SQL query compiles separately from data
  2. Parameters pass as values, not code
  3. Special characters auto-escaped
  4. Even ' OR '1'='1 becomes just a string

✅ Our recommendation: ALWAYS use prepared statements for SQL queries with user input. No exceptions.

Important: CVE-2025-1094

In February 2025, it was discovered that even prepared statements can be bypassed through incorrect multibyte character handling in PostgreSQL. Solution is simple: keep software updated (PostgreSQL 17.3+, 16.7+, 15.11+).

ORM and Frameworks: Built-in Protection

Modern frameworks have SQL Injection protection out of the box. On Hostiserver servers we support all popular frameworks — Laravel, Django, Node.js stack.

Laravel Eloquent (PHP)

// ✅ SAFE — Eloquent auto-parameterizes
$users = User::where('username', $username)
             ->where('status', 'active')
             ->get();
// ✅ SAFE — Query Builder
$users = DB::table('users')
           ->where('username', $username)
           ->get();
// ❌ UNSAFE — raw queries without bindings
// DB::select("SELECT * FROM users WHERE username = '$username'");

Django ORM (Python)

# ✅ SAFE — Django ORM
users = User.objects.filter(username=username, status='active')
# ✅ SAFE — raw query with parameters
users = User.objects.raw('SELECT * FROM users WHERE username = %s', [username])
# ❌ UNSAFE — string formatting
# User.objects.raw(f"SELECT * FROM users WHERE username = '{username}'")

Node.js (Sequelize / Prisma)

// ✅ SAFE — Sequelize
const users = await User.findAll({
  where: { username: username, status: 'active' }
});
// ✅ SAFE — Prisma
const users = await prisma.user.findMany({
  where: { username: username, status: 'active' }
});

⚠️ Warning: ORM protects only when used correctly. Raw SQL inside ORM can still be vulnerable. We often see this when auditing client projects.

Input Validation: Additional Layer

Validation is additional protection, not a replacement for prepared statements. We recommend applying both approaches simultaneously.

Validation Types

Type Description Example
Whitelist Allow only expected values Sorting: only 'asc' or 'desc'
Type casting Force type conversion $id = (int) $_GET['id'];
Format validation Check data format Email, date, UUID
Length limits Restrict length Username: max 50 chars

PHP Validation Examples

// ✅ Whitelist for sorting (ORDER BY can't be parameterized)
$allowed_columns = ['created_at', 'username', 'email'];
$sort_column = in_array($_GET['sort'], $allowed_columns) ? $_GET['sort'] : 'created_at';
$allowed_directions = ['ASC', 'DESC'];
$sort_dir = in_array(strtoupper($_GET['dir']), $allowed_directions) ? strtoupper($_GET['dir']) : 'DESC';
// ✅ Type casting for ID
$user_id = filter_var($_GET['id'], FILTER_VALIDATE_INT);
if ($user_id === false) {
    throw new InvalidArgumentException('Invalid user ID');
}
// ✅ Regex for specific formats
if (!preg_match('/^[a-zA-Z0-9_]{3,20}$/', $username)) {
    throw new InvalidArgumentException('Invalid username format');
}

💡 From our experience: Whitelist is always better than Blacklist. Instead of blocking dangerous characters — allow only expected ones.

WAF: How We Configure on Hostiserver Servers

WAF analyzes HTTP requests and blocks suspicious patterns before they reach the application. Especially important for legacy code that's hard to rewrite.

What We Use

Solution Level Use
ModSecurity Server (Apache/Nginx) Deep request inspection
Cloudflare WAF DNS proxy Edge protection, DDoS

ModSecurity Rules (Our Configuration)

Here's an example of rules we configure for clients:

# Rule 1: Detect SQLi patterns
SecRule ARGS|REQUEST_BODY \
    "@rx (?i)(union\s+select|sleep\(|benchmark\(|or\s+1=1)" \
    "id:1001002,phase:2,pass,log,tag:'attack-sqli',setvar:'tx.inbound_anomaly_score=+5',msg:'SQLi pattern detected'"
# Rule 2: Block when anomaly score exceeded
SecRule TX:INBOUND_ANOMALY_SCORE "@ge 5" \
    "id:1001099,phase:2,deny,status:403,log,msg:'Inbound anomaly score exceeded'"

What These Rules Block

  • UNION SELECT — extracting data from other tables
  • SLEEP() — time-based blind SQLi
  • BENCHMARK() — alternative time-based attack
  • OR 1=1 — classic boolean injection

OWASP Core Rule Set (CRS)

For full protection we install OWASP CRS — ruleset covering SQLi, XSS, LFI and other attacks:

# Installing OWASP CRS for Apache
sudo apt install libapache2-mod-security2
sudo mv /etc/modsecurity/modsecurity.conf-recommended /etc/modsecurity/modsecurity.conf
# Download CRS
cd /etc/modsecurity
sudo git clone https://github.com/coreruleset/coreruleset.git
sudo cp coreruleset/crs-setup.conf.example coreruleset/crs-setup.conf

✅ Our approach: We combine ModSecurity on server with Cloudflare WAF. Cloudflare blocks bulk of attacks at edge, ModSecurity catches what gets through.

MySQL Hardening: Our Standard Settings

Even if attacker finds SQLi, properly configured database minimizes damage. Here's what we do on Hostiserver managed servers.

Network Isolation

# /etc/mysql/mysql.conf.d/mysqld.cnf
# MySQL listens only on localhost — CRITICAL!
bind-address = 127.0.0.1
# Port 3306 NOT open to public internet
# Access allowed only from localhost or specific IPs via firewall

Principle of Least Privilege

Each application gets separate user with minimal permissions:

-- ✅ Separate user for application
CREATE USER 'app_user'@'localhost' IDENTIFIED BY 'STRONG_RANDOM_PASSWORD';
-- Only needed permissions on specific database
GRANT SELECT, INSERT, UPDATE, DELETE ON app_db.* TO 'app_user'@'localhost';
-- ❌ DON'T grant: GRANT ALL ON *.*
-- ❌ DON'T grant: SUPER, FILE, PROCESS, SHUTDOWN
FLUSH PRIVILEGES;

Our Standard Policy

What We Do How
Remove anonymous users DELETE FROM mysql.user WHERE User='';
Root local only Prohibit root@'%'
Strong passwords Password policy, min 16 chars
Database isolation User sees only their DB
TLS for remote REQUIRE SSL

Limits and Timeouts

# /etc/mysql/mysql.conf.d/mysqld.cnf
max_connections = 150
max_user_connections = 50
# Timeouts — close idle connections
wait_timeout = 300
interactive_timeout = 300

⚠️ Why this matters: If attacker finds SQLi, they're limited to app_user permissions. Without FILE — can't write files. Without SUPER — can't change server config.

Monitoring: How We Detect Attacks

Preventive protection is important, but you also need to see what's happening in real-time.

MySQL Logging

# /etc/mysql/mysql.conf.d/mysqld.cnf
# Error log — always enabled
log_error = /var/log/mysql/error.log
# Slow query log — detect suspicious queries
slow_query_log = 1
slow_query_log_file = /var/log/mysql/slow.log
long_query_time = 2

Audit Logging

For compliance and forensics we use:

  • MariaDB Audit Plugin — free for MariaDB
  • MySQL Enterprise Audit — for MySQL Enterprise
# MariaDB Audit Plugin
INSTALL SONAME 'server_audit';
SET GLOBAL server_audit_logging = ON;
SET GLOBAL server_audit_events = 'CONNECT,QUERY,TABLE';

What We Configure Alerts For

  • Abnormally high request count from one IP
  • Queries with suspicious patterns (UNION, SLEEP)
  • Authentication failures
  • Database structure changes

SIEM for Enterprise

For enterprise clients we offer integration with Elastic Security (ELK SIEM) — centralized log collection, event correlation, automatic threat detection.

💡 Tip: Slow query log isn't just about performance. Abnormally slow queries can indicate time-based SQLi (SLEEP, BENCHMARK).

Recommended Versions for 2026

PHP/MySQL Version Updates

Outdated PHP and MySQL versions have known vulnerabilities. Here's what we recommend:

PHP

Version Status Our Recommendation
PHP 8.4 ✅ Active Support Best Choice
PHP 8.3 ✅ Active Support Recommended
PHP 8.2 ⚠️ Security Only Minimum Version
PHP 8.1 and below ❌ End of Life Update urgently!

MySQL / MariaDB

Version Status Our Recommendation
MySQL 8.4 LTS ✅ Long Term Support Best Choice
MySQL 8.0.3x+ ✅ Active Support Recommended
MariaDB 10.11 LTS ✅ Long Term Support Recommended
MySQL 5.7 ❌ End of Life Critical risk!

PostgreSQL (after CVE-2025-1094)

If using PostgreSQL — update to versions 17.3+, 16.7+, 15.11+, 14.16+, or 13.19+.

🔴 Critical: MySQL 5.7 and PHP 7.x no longer receive security updates. If on these versions — contact us, we'll help with migration.

Checklist: Check Your Project

🔐 Application Code

  • ☐ All SQL queries use prepared statements
  • ☐ PDO: ATTR_EMULATE_PREPARES = false
  • ☐ ORM/Query Builder instead of raw SQL
  • ☐ Whitelist validation for ORDER BY, table names
  • ☐ Type casting for numeric parameters
  • ☐ Error messages don't expose SQL queries

🛡️ Server Infrastructure

  • ☐ WAF (ModSecurity / Cloudflare) active
  • ☐ MySQL bind-address = 127.0.0.1
  • ☐ Port 3306 closed externally
  • ☐ Separate database user per application
  • ☐ Minimal privileges (SELECT, INSERT, UPDATE, DELETE)
  • ☐ SUPER, FILE, PROCESS prohibited

📊 Monitoring

  • ☐ Error log enabled
  • ☐ Slow query log enabled
  • ☐ Alerts for suspicious activity

🔄 Maintenance

  • ☐ PHP 8.2+ (recommended 8.3/8.4)
  • ☐ MySQL 8.0+ / MariaDB 10.11+
  • ☐ PostgreSQL 17.3+ (if used)
  • ☐ Regular backups

🛡️ Need Help with Security?

We can audit your project, configure WAF, database hardening and monitoring.

What's Included in Hostiserver Managed Servers:

  • ModSecurity WAF with SQLi rules
  • Cloudflare integration
  • MySQL hardening per our standards
  • Separate database users
  • Monitoring and alerts
  • Regular backups

For Enterprise:

  • Elastic Security (ELK SIEM)
  • MySQL Audit Plugin
  • TLS encryption
  • 24/7 DevOps support

FAQ: Frequently Asked Questions

Does WordPress protect against SQL Injection?

WordPress core uses $wpdb->prepare() and is well protected. But plugins and themes are different story. From our experience, most WordPress breaches happen through vulnerable plugins.

We recommend: Use verified plugins, update regularly, install WAF.

Do prepared statements protect 100%?

Prepared statements protect against value injection. But some elements can't be parameterized: table names, ORDER BY, LIMIT. For those — whitelist validation.

Also consider CVE-2025-1094: PostgreSQL had vulnerability in escaping functions. Keep software updated.

Is WAF alone enough?

No. WAF is important layer but not silver bullet. CVE-2025-25257 showed even Fortinet FortiWeb (a WAF!) had critical SQLi vulnerability.

Right approach: prepared statements + validation + WAF + database hardening.

PDO or MySQLi?

Both safe when used correctly:

  • PDO — supports 12+ databases, named placeholders, recommended for new projects
  • MySQLi — MySQL/MariaDB only, slightly faster

Important for PDO: ATTR_EMULATE_PREPARES = false.

How to test site for SQL Injection?

Tools:

Important: Only test your own sites. Testing others without permission is crime.

What to do if already breached?
  1. Isolate server from network
  2. Make backup for forensics
  3. Check logs for entry point
  4. Restore from clean backup
  5. Fix vulnerability
  6. Change all passwords
  7. Notify users about breach

If need help with incident response — contact our support.

Is separate database user needed per site?

Yes. Isolation is critical. If one site breached — other databases stay protected. We configure this by default on all managed servers.

Contents

MANAGED VPS STARTING AT

$19 95 / mo

NEW INTEL XEON BASED SERVERS

$80 / mo

CDN STARTING AT

$0 / mo

 

By using this website you consent to the use of cookies in accordance with our privacy and cookie policy.