(1) SQL injection: an f-string inside text().
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | # ====================================================================== # Q: Find the security problems in this code. # # (1) SQL injection: an f-string inside text(). For values I use bind parameters # (:x). For column names, which cannot be bound, I use an allowlist. (2) SSRF: # the user can pass http://169.254.169.254/ and get AWS credentials from the # metadata service. It needs a domain allowlist, blocking of private IP ranges # after DNS resolution, a timeout and a size limit. There is also no timeout at # all. (3) The token ends up in the logs. (4) There is no auth and no tenant # filter: it returns all reports of all customers, and SELECT * may expose # internal fields. # ====================================================================== @app.get('/v1/reports') def reports(sort: str, url: str, session=Depends(get_session)): rows = session.execute(text(f'SELECT * FROM reports ORDER BY {sort}')) # (1) preview = requests.get(url).text # (2) logger.info('user token %s', request.headers['Authorization']) # (3) return {'rows': [dict(r._mapping) for r in rows], 'preview': preview} # (4) SORTABLE = {'created': Report.created_at, 'name': Report.name} @app.get('/v1/reports', response_model=list[ReportOut]) def reports(sort: Literal['created', 'name'] = 'created', user: User = Depends(require_permission('reports:read')), session: Session = Depends(get_session)): stmt = (select(Report).where(Report.tenant_id == user.tenant_id) .order_by(SORTABLE[sort].desc()).limit(100)) return session.scalars(stmt).all() |