DEV Community

BeanBean
BeanBean

Posted on • Originally published at nextfuture.io.vn

Cursor Sửa IDOR Bỏ Sót Route: Lỗi AI Coding Hay Gặp

Originally published on NextFuture

Bạn nhờ Cursor sửa một lỗi IDOR (insecure direct object reference) trên route GET /api/invoices/:id, và nó sửa đúng ngay lập tức — thêm điều kiện kiểm tra chủ sở hữu vào query. Nhưng cuộn xuống 20 dòng, route DELETE trên cùng resource vẫn gọi findByIdAndDelete(req.params.id) không kiểm tra gì, còn route GET /api/invoices (list) thì trả về toàn bộ dữ liệu của mọi user. Đây không phải lỗi hiếm — đó là pattern lặp lại mỗi khi AI coding agent sửa IDOR, và bài này chỉ ra vì sao, kèm cách chặn triệt để.

Miếng vá đúng, nhưng phạm vi chỉ bằng một route

Theo một kỹ sư chia sẻ chi tiết trên Dev.to, miếng vá Cursor viết cho route GET là chính xác: điều kiện sở hữu được đưa vào query (findOne({_id, userId: req.user.id})) thay vì kiểm tra sau khi lấy dữ liệu, và khi không tìm thấy thì trả 404 thay vì 403 — cách làm đúng vì không để lộ rằng invoice của người khác tồn tại. Nếu chỉ chấm điểm riêng route này, nó pass tuyệt đối.

Vấn đề là ba route còn lại trên cùng file — PATCH, DELETE, và GET (list) — vẫn dùng primary-key lookup thuần túy, không hề biết đến điều kiện sở hữu vừa được thêm vào route kia.

Vì sao route list mới là phần nguy hiểm nhất

IDOR trên route ghi (PATCH/DELETE) thường đòi hỏi kẻ tấn công phải đoán được ID hợp lệ trước — chi phí này phụ thuộc vào bạn dùng ID tuần tự hay UUID ngẫu nhiên. Nhưng khi route list không lọc theo chủ sở hữu, nó tự trả về toàn bộ ID hợp lệ, biến bước "dò ID" thành một request đơn giản, được xác thực và trông giống hệt một lượt đọc bình thường trong log truy cập — không có gì bất thường để phát hiện.

Không chỉ AI mới mắc lỗi này

Bài viết dẫn CVE-2026-47418 (CVSS 8.1, tháng 6/2026) trên praisonai-platform làm ví dụ: các route project đã có kiểm tra require_workspace_member(workspace_id), nhưng sau đó lại resolve object qua ProjectService.get(project_id) — một primary-key lookup không hề có điều kiện workspace. Update và delete gọi lại đúng hàm get() đó nên thừa hưởng luôn lỗ hổng. Advisory ghi nhận cùng một lỗi cấu trúc lặp lại ba lần trong cùng codebase — trên project, issue (CVE-2026-47415) và agent (CVE-2026-47419) — bởi một team rõ ràng đã hiểu khái niệm membership check, vì họ từng viết một cái.

Framework cũng không tự động che phần thiếu

Với Django REST Framework, tài liệu chính thức nêu rõ: has_object_permission() chỉ chạy khi get_object() được gọi — tức bao phủ retrieve/update/destroy nhưng KHÔNG bao phủ list và create. Ngược lại get_queryset() bao phủ list/retrieve/update/destroy nhưng cũng không chạm vào create. Khi bạn dán một detail view và nói "fix IDOR", AI agent gần như luôn chọn viết permission class (cơ chế nghe "bảo mật" hơn) — đúng cách nhưng vẫn để lộ route list.

Pattern chặn triệt để: một accessor duy nhất, bắt buộc đi qua

Cách xử lý bền vững không nằm ở dòng code sửa lỗi, mà ở việc không còn chỗ để quên nó. Với DRF, scope ngay trong get_queryset() (vì đây là cơ chế duy nhất phủ được list) và xử lý create riêng bằng perform_create() để gán owner từ session, không bao giờ từ request body. Với Express/Mongoose, dùng một helper scope chung như owned(req) => ({ userId: req.user.id }) và bắt mọi handler — get, list, patch, delete — đều phải spread qua nó. Đây là ví dụ minh họa pattern, cần điều chỉnh theo schema thực tế của bạn.

Việc cần làm ngay

Nếu tuần trước bạn từng nhờ AI coding agent sửa một IDOR, hãy mở lại đúng file đó: kiểm tra route list và các route ghi (PATCH/DELETE) trên cùng resource có đi qua cùng điều kiện sở hữu với route vừa sửa hay không. Đừng tin vào commit message "fixed IDOR" — nó chỉ đúng cho đúng một route mà bạn đã dán vào prompt.


This article was originally published on NextFuture. Follow us for more fullstack & AI engineering content.

Top comments (0)