Have you ever trusted a plugin to keep your site secure? We see it weekly: excellent developers who ship functional code but treat security as an afterthought. Then comes the breach – a defacement, leaked customer data, or worse. At Meteora Web, we manage dozens of web projects, and the picture is consistent: the most common vulnerabilities are also the easiest to prevent. The OWASP Top 10 2025 lists exactly these flaws. In this guide we tackle them one by one with real code and actionable steps.
Why OWASP Top 10 Is Your Best Friend
The Open Web Application Security Project periodically updates the list of the ten most critical web application vulnerabilities. It's not a certificate to hang on the wall – it's a mirror of what goes wrong every day on servers, databases, and forms. We use it as an audit checklist. Because a site that ignores these ten things is a site that will eventually be hacked. And when we talk about security in Italian SMEs, the situation is dire: backups never configured, plain-text credentials, unprotected forms. That's why we start right here.
The 10 Vulnerabilities Explained with Code and Fixes
For each OWASP Top 10 2025 entry, we show a practical vulnerable code example and the immediate solution. You don't need to become a security expert – just stop repeating the same mistakes.
1. Broken Access Control
A normal user can see another user's data simply by changing an ID in the URL? That's broken access control. Classic PHP example:
// VULNERABLE
$id = $_GET['id'];
$sql = "SELECT * FROM orders WHERE id = $id";
// FIXED
session_start();
$id = intval($_GET['id']);
$user_id = $_SESSION['user_id'];
$sql = "SELECT * FROM orders WHERE id = ? AND user_id = ?";
$stmt = $pdo->prepare($sql);
$stmt->execute([$id, $user_id]);
Action now: Check every endpoint to verify the user has permission to access the resource. Never trust input.
2. Cryptographic Failures
Passwords stored in plain text? Sensitive data transmitted over HTTP? It's 2026, this is no longer acceptable. Use password_hash() in PHP or bcrypt on the backend. For data in transit, force HTTPS and HSTS headers.
// VULNERABLE
$password = $_POST['password'];
$sql = "INSERT INTO users (password) VALUES ('$password')";
// FIXED
$hash = password_hash($_POST['password'], PASSWORD_BCRYPT);
$stmt = $pdo->prepare("INSERT INTO users (password) VALUES (?)");
$stmt->execute([$hash]);
Action now: Enable HTTPS across your entire domain. Use Let's Encrypt for free certificates and automate renewal.
3. Injection
SQL, NoSQL, OS command injection – the classic: malicious input alters your query. The solution is always the same: prepared statements.
// VULNERABLE
$username = $_POST['username'];
$sql = "SELECT * FROM users WHERE username = '$username'";
// FIXED
$stmt = $pdo->prepare("SELECT * FROM users WHERE username = ?");
$stmt->execute([$_POST['username']]);
Action now: Search your codebase for all queries built with string concatenation and replace them with prepared statements. Do the same for eval(), shell_exec() and similar functions.
4. Insecure Design
This isn't just code – it's flawed architecture. For example, allowing a user to change their email without verifying the current password. The fix is in design: security by design.
Action now: Every change to sensitive data (email, password, address) must require password re-confirmation or an OTP. Never leave a backdoor in your flow.
5. Security Misconfiguration
Common mistakes: directory listing enabled, default credentials, debug errors visible in production. We've seen servers with display_errors = On in production. One error can expose your absolute path and database name.
// In php.ini for production
display_errors = Off
log_errors = On
error_log = /var/log/php_errors.log
Action now: Run a scan using SecLists or an automated tool (Nikto, Wapiti). Disable everything you don't need.
6. Vulnerable and Outdated Components
WordPress, plugins, JavaScript libraries. If you don't update, you're exposed. We've seen clients running jQuery 1.x with known vulnerabilities.
Action now: Set up automatic dependency updates (Dependabot, Renovate). Check your libraries against CVE at least once a month.
7. Identification and Authentication Failures
Sessions not invalidated, weak passwords, no 2FA. If an attacker can impersonate a user, the party is over.
// VULNERABLE: logout without destroying the session
session_start();
unset($_SESSION['user_id']);
// FIXED
session_start();
session_destroy();
setcookie(session_name(), '', time()-3600, '/');
Action now: Implement 2FA (TOTP or SMS). Use session_regenerate_id() after login. Set an inactivity timeout.
8. Software and Data Integrity Failures
Unsigned updates, unverified CDN dependencies, missing checksums. An attacker can inject malicious code into a library you load from an insecure server.
Action now: Use Subresource Integrity (SRI) for all CDN resources. Sign your updates with GPG. Always verify package hashes before installation.
9. Security Logging and Monitoring Failures
If you don't track access, you can't know if you've been attacked. Many SMEs have no access logs, and when they do, they don't analyze them.
// Minimal logging example
$log = date('Y-m-d H:i:s') . " - " . $_SERVER['REMOTE_ADDR'] . " - " . $event . PHP_EOL;
file_put_contents('/var/log/app.log', $log, FILE_APPEND);
Action now: Enable access logs on your web server (Apache/Nginx). Configure alerts for failed login attempts. Use tools like ELK Stack for centralized logging.
10. Server-Side Request Forgery (SSRF)
If your application makes HTTP requests to user-supplied URLs, an attacker can force it to connect to internal servers (e.g., 127.0.0.1, cloud metadata).
// VULNERABLE
$url = $_GET['url'];
$content = file_get_contents($url);
// FIXED
$allowed_hosts = ['api.myservice.com', 'cdn.trusted.com'];
$parsed_url = parse_url($url);
if (!in_array($parsed_url['host'], $allowed_hosts)) {
die('URL not allowed');
}
Action now: Whitelist allowed hosts. Block private IPs and cloud metadata IPs (169.254.169.254, 100.100.100.200).
How to Integrate These Checks Into Your Daily Workflow
You don't need to become paranoid. Just inject automated tools into your development pipeline. We use:
- Static Analysis: PHPStan / Psalm for PHP, ESLint with security plugin for JS.
- DAST: OWASP ZAP or Nikto for periodic scans on staging.
- CI/CD: GitHub Actions that run security tests on every push.
If you use WordPress, check plugins with WPScan. For Laravel applications, PHP Insights already tells you a lot.
In Summary — What to Do Now
- Audit your application using the OWASP Top 10 as a checklist.
- Fix immediately Injection, Broken Access Control, and Cryptographic Failures – they are the easiest to exploit.
- Automate checks: if you don't have time to read every line, use SAST/DAST tools.
- Update all dependencies and configure automatic updates.
- Train yourself and your team: dedicate one day per month to security. It's an investment, not a cost.
At Meteora Web, we have seen companies lose months of work due to a single flaw. Don't let it happen to you. If you need a quick security check for your application, get in touch.
Sponsored Protocol