Summary by 0xJ0cKkY
The order parameter of these public JSON API endpoints is placed into the SQL ORDER BY clause without being bound as a parameter or checked against an allow-list of permitted columns. The site's own front-end already sends raw SQL fragments in this parameter
(order=publish_date desc,created_at desc), so the value is used as SQL text by design.
Two independent facts establish that the value is parsed as SQL rather than matched against a list of allowed tokens:
PostgreSQL parses SQL comments supplied in the parameter.
id/**/descandid/*x*/descboth produce exactly theid descordering - a comment, including one containing text, is correctly treated as whitespace.A malformed comment produces a database error.
id/*desc(unterminated) andid*/desc(stray close) both fail with HTTP 403. A filter that merely stripped or rejected comments could not produce this asymmetry: a balanced comment is honoured, an unbalanced one errors. Only a SQL parser behaves this way.
This gives a reliable oracle - HTTP 200 = valid SQL referencing a real column; HTTP 403 = the query failed at the database