If your site is custom-built rather than a packaged application, these are the issues that account for most compromises we see.
SQL INJECTION
Never build a query by concatenating user input into a string. Use prepared statements with bound parameters, available in PDO and MySQLi. This single change eliminates the entire class of attack.
CROSS-SITE SCRIPTING
Escape output before it reaches the page. Any value that came from a user, a URL parameter or the database must be escaped for the context it is printed into.
CROSS-SITE REQUEST FORGERY
Every form that changes data must carry a token the server generates and verifies. Without it, another site can make a logged-in visitor perform actions on yours.
CREDENTIALS IN CODE
Keep database passwords and API keys in a configuration file outside public_html, not hardcoded into a script inside it, and never in a public Git repository.
ERROR DISPLAY
Set display_errors to Off on production. Error messages showing file paths and query fragments are a gift to an attacker. Log errors instead.
DEPENDENCIES
Libraries pulled in via Composer or npm need updating like anything else. Run an audit periodically.