I ask this question with a bit of sheepishness because I should know the answer. Could someone be kind and explain if and how injection could occur in the following code?
doesn't CF already "magically" do this in CF query tag when you wrap evaluated variables in single quotes?
Yep, it'll convert ' to '' for you.
Now guess what SQL you get from this code:
select * from tableName
where fieldName = '#value#'
The cfqueryparam tag works; using query params solves SQL injection.
Any custom written attempts at validating, sanitizing, or escaping (all separate things, btw) are, at best, only as good as the developer's knowledge of the database system the code is running against.
If the developer is unaware of other escape methods, or if the values are modified between validation/escaping and them being rendered into SQL, or even if the codebase is ported to another database system and seems to be fine, there's a chance of custom code breaking down.
When it comes to security, you don't want chances like that. So use cfqueryparam.