SQL injection: NostrFilter.to_sql_components interpolates client filter values into SQL #8
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
relay/filter.pybuilds the events query by f-string-interpolating client-controlled filter values, wrapped in single quotes, with no escaping and no parameter binding:relay/filter.py:108-109—ids = ",".join([f"'{_id}'" for _id in self.ids])/where.append(f"id IN ({ids})")relay/filter.py:112-113— same forauthors→pubkey IN (...)relay/filter.py:81,89,97— same for#e,#p,#dtag valuesids,authors,e,p,darelist[str]with no hex validation. Onlysince/untilare bound (:120-126);relay_idis bound but the injected text sits in the query string, so binding does not protect it.Reachable without authentication:
require_auth_filterdefaults to false, soREQ→client_connection._handle_message(:138-151) →_handle_request(:324) →crud.get_events(crud.py:111) →to_sql_components. A frame like["REQ","s",{"ids":["x') UNION SELECT ... --"]}]defeats therelay_id = :relay_idtenant scope (cross-relay reads of every relay's events). The same function feedscrud.mark_events_deleted(crud.py:184) andcrud.delete_events(crud.py:195), so the sink is also reached from DELETE/UPDATE paths:relay/client_connection.py:196-206— the incoming event's owndtag goes into#d;a-tag deletion,relay/client_connection.py:268-286—_address_filtercheckskindandpubkey == authorbut passesd_tagthrough unvalidated (:273,:284).Those two routes need a signed event, but any key works, so they are effectively unauthenticated too.
Impact: cross-tenant read of all events on a multi-relay deployment; driver-dependent over-deletion via the delete paths.
Fix direction: bind every list element as a named parameter (
:ids_0, :ids_1, ...) and put the values in thevaluesdict; do the same forkindsfor consistency.(inner_joins, where, values)contract stays, so the three callers incrud.pydo not change. Hex validation onids/authors/#e/#p(64 lowercase hex) as defence in depth. The sandbox fix PR (sandbox nostrrelay#13,bind_inhelper +tests/test_filter_sqli.py) applies to ourrelay/filter.pywith a four-line offset. Land before real #2 (generic single-letter tag filters) rewrites the same function.Found during reforge run #1 (sandbox nostrrelay#2, nostrrelay#11).