Prepared statements close the hole; privileges limit the damage of one you missed. The web account gets rows in one database and nothing else, from localhost only (an app on another host gets 'shop_app'@'10.0.0.%' with REQUIRE SSL, never '%'):
GRANT SELECT, INSERT, UPDATE, DELETE ON shop.* TO 'shop_app'@'localhost';
ALTER USER 'shop_app'@'localhost' WITH MAX_USER_CONNECTIONS 50;
SHOW GRANTS FOR 'shop_app'@'localhost';Output
+----------------------------------------------------------------------------+ | Grants for shop_app@localhost | +----------------------------------------------------------------------------+ | GRANT USAGE ON *.* TO `shop_app`@`localhost` | | GRANT SELECT, INSERT, UPDATE, DELETE ON `shop`.* TO `shop_app`@`localhost` | +----------------------------------------------------------------------------+
Run as shop_app, the attacks of Subsection 3.16.7 stop at the edge of its grants:
app() { mysql -ushop_app -p'App#Shop-2027' shop -e "$1" 2>&1 | grep -v Warning; }
app "SELECT id, name FROM customers WHERE email = 'x'
UNION SELECT user, plugin FROM mysql.user -- '"
app "DROP TABLE reviews"
app "SELECT * FROM customers INTO OUTFILE '/var/lib/mysql-files/c.csv'"
app "CREATE USER 'evil'@'%' IDENTIFIED BY 'Evil#Pass-2026'"Output
ERROR 1142 (42000) at line 1: SELECT command denied to user 'shop_app'@'localhost' for table 'user' ERROR 1142 (42000) at line 1: DROP command denied to user 'shop_app'@'localhost' for table 'reviews' ERROR 1227 (42000) at line 1: Access denied; you need (at least one of) the FILE privilege(s) for this operation ERROR 1227 (42000) at line 1: Access denied; you need (at least one of) the CREATE USER privilege(s) for this operation
Shop data is still exposed to a hole, but accounts, schema, files and other databases are not. Give other jobs their own accounts:
| Account | Privileges | Used by |
|---|---|---|
| shop_app | DML on shop.* | PHP, every request |
| report | Role shop_read | Dashboards, exports |
| shop_migrate | DML plus CREATE, ALTER, DROP, INDEX | Deployments only |
| root via socket | Everything | A person at the console |
Where a page needs more, such as placing an order, grant EXECUTE on one SQL SECURITY DEFINER procedure (Stored Procedures) rather than widening shop_app, which Databases with PDO uses from PHP.