# GraphQL API Vulnerability PortSwigger Challenge ### Overview về GraphQL GraphQL là một ngôn ngữ truy vấn cho API (Query Language for APIs) và cũng là một môi trường thực thi phía máy chủ (server-side runtime) để thực hiện các truy vấn đó. Để dễ hiểu hãy hình dung như sau: - Với `API truyền thống` (như REST API): Bạn phải gọi nhiều `"endpoint"`(đường dẫn) khác nhau để lấy các loại dữ liệu khác nhau. Ví dụ: để lấy thông tin người dùng và các bài viết của họ, bạn có thể phải gọi /users/1 để lấy thông tin người dùng, sau đó gọi `/users/1/posts` để lấy danh sách bài viết. Bạn thường nhận về toàn bộ dữ liệu mà endpoint đó cung cấp, dù bạn có cần hết hay không. - Với `GraphQL`: Bạn chỉ cần `một endpoint duy nhất`. Phía `client` (ứng dụng của bạn) sẽ gửi một truy vấn (query) duy nhất mô tả chính xác những dữ liệu nào nó cần. Ví dụ: Bạn có thể gửi một truy vấn duy nhất yêu cầu "lấy tên và email của người dùng có ID là 1, và chỉ lấy tiêu đề của 3 bài viết gần nhất của người đó". ```graphql query { user(id: "1") { name email posts(last: 3) { title } } } ``` Các đặc điểm chính của GraphQL: - Lấy chính xác những gì bạn cần: Giúp tránh tình trạng "over-fetching" (lấy quá nhiều dữ liệu không cần thiết) và "under-fetching" (phải gọi thêm nhiều API khác để lấy đủ dữ liệu). Điều này rất hữu ích cho các ứng dụng di động có băng thông hạn chế. - Một yêu cầu, nhiều tài nguyên: Có thể lấy nhiều tài nguyên liên quan (như người dùng, bài viết, bình luận) chỉ trong một lần gọi API duy nhất. - Hệ thống kiểu dữ liệu mạnh mẽ (Strongly Typed): Toàn bộ API được định nghĩa bằng một "schema", trong đó mô tả rõ ràng tất cả các loại dữ liệu và các truy vấn có thể thực hiện. Điều này giúp client và server có chung một "ngôn ngữ", dễ dàng phát triển và kiểm tra. - Tự động tạo tài liệu (Self-documenting): Nhờ có schema, tài liệu API luôn được cập nhật và có thể được khám phá tự động (thông qua công cụ như GraphiQL hoặc GraphQL Playground). - Không phụ thuộc vào cơ sở dữ liệu: GraphQL chỉ là một lớp nằm giữa client và các dịch vụ backend của bạn. Nó có thể lấy dữ liệu từ bất kỳ đâu: cơ sở dữ liệu SQL, NoSQL, các REST API khác, hoặc thậm chí là một file tĩnh. ### Lab exploit and POC #### Lab: Accessing private GraphQL posts ```text The blog page for this lab contains a hidden blog post that has a secret password. To solve the lab, find the hidden blog post and enter the password. ``` Đây là một trang web blog sử dụng graphQL API bây giờ ta sẽ sử dụng burpsuite để bắt các request. ![image](https://hackmd.io/_uploads/BJFxrbjg-g.png) Ở đây sau khi nó load xong trang thì ta thấy một POST request với API là `/graphql/v1` đây chính là request gọi đến `graphql` ta có thế thấy ở bên dưới là câu query của nó gọi đến `allBlogPosts` ta có thể thấy rằng response trả về là data của trang blog. Vậy với nội dung query xuất hiện ở bên trong request như này liệu ta có thao túng được nó để có thể thay đổi nội dung mà mình cần không? ![image](https://hackmd.io/_uploads/rkKF8bolZl.png) Tại đây sau khi thử inject vào : ```graphql { "query":"{__typename}" } ``` Ta thấy rằng response trả về kết quả 200 cùng với kết quả câu query. ![image](https://hackmd.io/_uploads/HJSgPWoeWg.png) Với tiêu đề của bài ta đã đọc ở trên thì sẽ có 1 secret password nằm trong bài blog nào đó, với lab này có vẻ như không có filter nào hết và ta hoàn toàn có thể thao túng query vì vậy ta có thể thao túng đến các blog và lấy thử password được giấu trong blog đó. ![image](https://hackmd.io/_uploads/SJtOvWixbe.png) Sau khi bấm đại đại vào một blog nào đó ta có thể thấy được biến id nằm trong request của API bây giờ ta sẽ thử thêm cả một biến nữa đó là biến `postPassword` xem thử nó sẽ ra kết quả như nào, ![image](https://hackmd.io/_uploads/SJJe6Zix-l.png) Ở đây với postid=4 thì có vẻ như không có password trong này nên ta sẽ bắt request của trang chính khi mà nó hiển thị tất cả blog cà inject giá trị password vào để kiểm tra hết. ![image](https://hackmd.io/_uploads/HyH56Wol-g.png) Gọi hết thì thấy thiếu đi id=3 chắc nó giấu rồi nên trở lại với request gọi từng bài và thay giá trị id từ 4 về 3 sau đó gửi thử. ![image](https://hackmd.io/_uploads/rJ_66-sebe.png) Thành công lấy được postPassword và solve lab. #### Lab: Accidental exposure of private GraphQL fields ```text The user management functions for this lab are powered by a GraphQL endpoint. The lab contains an access control vulnerability whereby you can induce the API to reveal user credential fields. To solve the lab, sign in as the administrator and delete the username carlos. ``` Đến với lab này thì yêu cầu đề đã thay đổi bây giờ nó bắt mình khai thác lỗ hổng access control bằng graphql sau đó dùng quyền admin để xoá đi username carlos, bây giờ ta sẽ tiếp tục bắt request xem thử kết quả trả về ra sao. ![image](https://hackmd.io/_uploads/HkdyBmogWl.png) Như cũ ta vẫn sẽ bắt được cái API gọi graphql ở đây nó query đến hết nội dung của trang bây giờ ta sẽ tiến hành login. ![image](https://hackmd.io/_uploads/B18Fm02gbg.png) Tại đây ta bắt được một request gọi đến graphql login ở đây ta thấy rằng response ta đã login thành công với user wiener. Sau đó ta sẽ sử dụng một extension đã tải từ Bapp của burpsuite có tên là InQL Scanner để tiến hành phân tích request graphql vừa rồi. ![image](https://hackmd.io/_uploads/SJBDdKTe-x.png) Tại đây ta tìm được một query nhạy cảm đó là `getUser` có khả năng trả về username và password và nó nhận biến id để truy xuất user theo số id vậy bây giờ ta sẽ copy đoạn query này và đưa nó vào request để xem ta có lợi dụng nó thay đổi biến id để lấy được username và password của user admin không. ![image](https://hackmd.io/_uploads/Sk5tFK6lbl.png) Nó trả lỗi về dòng `"Unknown operation named 'login'."` ta sẽ đổi getUser thành login vì đây là đoạn mình sẽ phải query đến chức năng login. ![image](https://hackmd.io/_uploads/BJ0pYtpgbl.png) Thành công leak ra được username password của user peter, bây giờ ta tiếp tục thay đổi id đến khi tìm được admin. ![image](https://hackmd.io/_uploads/rJ2W5Yag-g.png) Thành công lấy được username và password của administrator bây giờ đăng nhập vào admin và xoá user carlos thôi. ![image](https://hackmd.io/_uploads/HJdO9YTebg.png) #### Lab: Finding a hidden GraphQL endpoint ```text The user management functions for this lab are powered by a hidden GraphQL endpoint. You won't be able to find this endpoint by simply clicking pages in the site. The endpoint also has some defenses against introspection. To solve the lab, find the hidden endpoint and delete carlos ``` ![image](https://hackmd.io/_uploads/B15iSqaeWg.png) Sau khi vào page và tiến hành thử đăng nhập thì ở đây đúng như description của bài đã nói rằng bây giờ cái graphql đã hidden nên ta sẽ không tìm được endpoint `/graphql` như mấy lab trước. Bây giờ ta sẽ tiến hành brute-force thử để tìm các endpoint graphql ở đây tôi có tạo một cái list nhỏ. ```text /graphql /api /graphql/v1 /api/graphql /v1 /v1/graphql /graphql/graphql /api /api/graphql ``` Bây giờ gửi request vào intruder và tiến hành brute force xem thử response trả về ra sao. ![image](https://hackmd.io/_uploads/Hy7PcfRgbg.png) Ở đây ta thấy hầu như nó sẽ đều trả về status code là 404 là not found nhưng riêng với `/api` thì status code trả về kết quả là 405 có nghĩa là method not allowed vậy thì có nghĩa endpoint này tồn tại bây giờ đem nó ra repeater xem thử. ![image](https://hackmd.io/_uploads/Hy3xsM0e-e.png) Ta thấy rằng nó trả về method not allowed bây giờ ta sẽ thử đổi method thành GET xem kết quả trả về ra sao. ![image](https://hackmd.io/_uploads/SytmjfRlbl.png) Nó trả về dòng `"Query not present"` do là ở đây ta chưa có viết query của graphql vào nên nó không có gì thì nó báo bây giờ ta sẽ thử một query đơn giản xem sao. ![image](https://hackmd.io/_uploads/H1dRhGRgWl.png) Tại đây mình inject vào : ```graphql { "query":"query{__typename}" } ``` Ở đây nó vẫn trả về kết qủa của `__typename` đây đơn giản là một meta-field luôn có sẵn trong GraphQL. Nó đơn giản trả về tên của type hiện tại trong kết quả. Vậy bây giờ ta sẽ thử với Introspection thì sao bây giờ vào inql để chỉnh lại cái typename thành schema thử xem vì introspection thường ám chỉ việc truy vấn schema bằng các field đặc biệt như `__schema` hoặc `__type`. Những field này cho phép attacker khám phá toàn bộ cấu trúc API. ![image](https://hackmd.io/_uploads/HkmS0GCgZg.png) Sau khi inject sửa thành : ```graphql query{__schema{queryType{name}}} ``` Thì đã bị ăn một cái error là `"GraphQL introspection is not allowed, but the query contained __schema or __type"` vậy là ở đây introspection bị chặn rồi bây giờ ta phải tìm cách để bypass được bước này. Ở đây sau khi tìm kiếm vài nguồn thì có cái trick áp dụng thành công đó là xuống dòng, cái trick này nó không hẳn là cách để bypass introspection mà cía này nó liên quan đến cách mà server parse GraphQl vì ở đây thì theo standard thì graphql cho phép ta xuống dòng nhưng ở đây filter có vẻ như đã dùng cơ chế lọc string hoặc có thể là xài regex để bắt các keyword như `__schema` và có vẻ nó chưa được filter chặt ở các đoạn xuống hàng hoặc khoảng trắng thì trong trường hợp này nó đã không làm tốt đoạn payload ta có thể xuống hàng vì nó sẽ chỉ check graphql nằm trên cùng 1 hàng. ![image](https://hackmd.io/_uploads/SJBFeX0xZx.png) Ở đây schema có thể hoạt động có nghĩa là introspection có họat động bây giờ ta sẽ tìm cách truy vấn schema đến các object ta cần. Tham khảo thêm ở : <https://graphql.org/learn/introspection/> Tại đây sử dụng câu truy vấn kiếm được từ nguồn để leak trường bên trong. ![image](https://hackmd.io/_uploads/B1RHGm0l-g.png) Ở đây ta sẽ để ý đến là có User và có cả chức năng `DeleteOrganizationUser` đây là một mảnh ghép thông tin rất lớn, bây giờ ta sẽ tìm cách dùng schema leak user. ![image](https://hackmd.io/_uploads/SknxXmAgWg.png) Không thấy dữ liệu nào trở về nên ta sẽ kiếm thêm coi có payload introspection nào có thể leak ra dữ liệu không. Sau khi mò trong <https://github.com/swisskyrepo/PayloadsAllTheThings/tree/master/GraphQL%20Injection> thì mình có payload để có thể test vào. ![image](https://hackmd.io/_uploads/ByoKmmCxbx.png) Vì payload test rất rộng nên response trả về rất nhiều dữ liệu nên ta sẽ phải nhờ đến extension inql để lấy query ra nhìn cho dễ. ![image](https://hackmd.io/_uploads/rJr_L7AlWg.png) Tại đây ở query ta thấy có `getUser` và ở Mutation có `deleteOrganizationUser` bây giờ ta sẽ test với query `getUser` trước xem sao. ![image](https://hackmd.io/_uploads/r1O1w7Ae-l.png) Vừa mới nhét cái id=1 thì nó đã nổ ra luôn username là administrator luôn bây giờ ta sẽ tìm id của user carlos xem thử. ![image](https://hackmd.io/_uploads/S1kmDmReWe.png) Tìm được user carlos có id là 3 bây giờ ta sẽ lợi dụng `deleteOrganiztionUser` để xóa đi user carlos graphql sẽ thực hiện truy vấn và xóa đi user carlos. ![image](https://hackmd.io/_uploads/BkFeOm0ebg.png) Thành công xóa user carlos ở đây có cái message `Exception while fetching data (/deleteOrganizationUser) : User does not exist` vì lỡ click 2 lần nó xóa rồi xóa lần nữa thì còn cái nịt mà xóa. ![image](https://hackmd.io/_uploads/SJYmum0e-x.png) Thành công solve lab. #### Lab: Bypassing GraphQL brute force protections ```text The user login mechanism for this lab is powered by a GraphQL API. The API endpoint has a rate limiter that returns an error if it receives too many requests from the same origin in a short space of time. To solve the lab, brute force the login mechanism to sign in as carlos. Use the list of authentication lab passwords as your password source. ``` Đọc description thì ta cũng đã hiểu phần nào về bài rằng bây giờ ở chức năng login sẽ sử dụng graphql nhưng vẫn đề là API endpoint đã bị rate limit nếu request quá nhiều nó sẽ bị sleep trong một khoảng thời gian vì thế sẽ khó để có thể brute force như là ở lab trước. ![image](https://hackmd.io/_uploads/rkTSMdWZZg.png) Sau khi tôi thử đăng nhập vài lần liên tục thì đã dính rate limit và nó sẽ sleep trong vòng 1 phút thì tương tự đó nếu thực hiện các thao tác khác nhiều lần nó cũng sẽ bị rate limit. ![image](https://hackmd.io/_uploads/r1Qb7u-bWg.png) Bây giờ chuyển sang tab đọc graphql thì ta thấy một cái mutation login. - Mutation là một loại operation trong GraphQL dùng để thay đổi dữ liệu trên server. - Nếu query chỉ để đọc dữ liệu (giống như SELECT trong SQL), thì mutation giống như các thao tác ghi/chỉnh sửa (INSERT, UPDATE, DELETE). Vậy ở đây ta có một cái mutation dùng để login vậy có cách nào để ta lợi dụng nó để có thể brute force là lấy được credentials của user carlos không, tại đây ta sẽ phải tìm hướng bypass được cái rate limit và tìm đường brute force. Sau khi tìm kiếm ở document thì có một cách đó là sử dụng alias. ![image](https://hackmd.io/_uploads/BybmVdZZ-g.png) - GraphQL cho phép dùng alias để gọi nhiều mutation trong cùng một request. - Rate limiter chỉ tính số lượng request, không tính số lượng alias bên trong. - Nghĩa là ta có thể gửi một request duy nhất nhưng chứa hàng chục (hoặc hàng trăm) lần thử mật khẩu khác nhau. Vậy có nghĩa kịch bản tấn công là ta sẽ tạo một mutation có nhiều alias đăng nhập để có thể đăng nhập nhiều lần chỉ trong 1 request có nghĩa ta sẽ test được nhiều password của user carlos trong 1 lần và đó là cách bypass để brute force được cơ chế rate limit ở đây. ![image](https://hackmd.io/_uploads/BkHT8OWW-l.png) Sau khi viết lại mutation để có thể brute thì ở đây ta thành công gửi nhiều request cùng lúc nhưng ở đây kết quả false hết nên ta sẽ phải tìm thêm cái wordlist để brute cho chuẩn. ![image](https://hackmd.io/_uploads/rywOt_Wbbx.png) Sau khi tạo hết request để test password trong wordlist thì ta đã tìm ra được password của user carlos khi mà request brute thứ 65 đã trả cho về giá trị là true. ![image](https://hackmd.io/_uploads/SJk0KubZ-l.png) Thành công có được password bây giờ tôi sẽ thử đăng nhập xem có truy cập được không. ![image](https://hackmd.io/_uploads/HylUb5u-W-g.png) Thành công đăng nhập vào user carlos và solve lab. #### Lab: Performing CSRF exploits over GraphQL ```text The user management functions for this lab are powered by a GraphQL endpoint. The endpoint accepts requests with a content-type of x-www-form-urlencoded and is therefore vulnerable to cross-site request forgery (CSRF) attacks. To solve the lab, craft some HTML that uses a CSRF attack to change the viewer's email address, then upload it to your exploit server. You can log in to your own account using the following credentials: wiener:peter. ``` Đến với bài lab cuối này thì ngay ở tiêu đề thì ta đã biết rằng bây giờ sẽ phải tìm cách lợi dụng graphql để có thể thực hiện CSRF, nó còn nói rằng nếu như có endpoint accept loại content-type là `x-www-form-urlencoded` thì nó có thể bị CSRF không nói nhiều vào bài luôn. ![image](https://hackmd.io/_uploads/S1ifoHzZZe.png) Sau khi đăng nhập bằng user wiener thì có thể thấy một chức năng update email y hệt như các lab CSRF trước đây bây giờ mình sẽ thử update mail lên và bắt request về. ![image](https://hackmd.io/_uploads/S1ijiSfbbe.png) Ở đây ta sẽ thấy câu query graphql để mà thực hiện update email lên rồi bên response trả về thực hiện câu query thành công bây giờ email đã bị đổi. Ở đây nếu như muốn thực hiện CSRF qua câu query của graphql thì ta sẽ thử đổi method header 2 lần từ POST thành GET và về lại POST để thay đổi content type hoặc là ta sẽ tự thay đổi luôn content type cho nhanh thành `Content-Type: application/x-www-form-urlencoded` với content type này thì có lý do để nó phải thành như vậy đó là: Cơ chế CSRF hoạt động dựa trên trình duyệt: Khi nạn nhân truy cập một trang HTML độc hại, trình duyệt sẽ tự động gửi request kèm cookie session của họ đến server. - Trình duyệt chỉ tự động gửi form với một số loại content-type mặc định: - application/x-www-form-urlencoded - multipart/form-data - text/plain Đây là các loại mà HTML form hỗ trợ trực tiếp. GraphQL endpoint mặc định thường nhận JSON (application/json): - Nếu request yêu cầu application/json, thì attacker không thể tạo một form HTML đơn giản để gửi dữ liệu, vì form không hỗ trợ gửi JSON. - Muốn gửi JSON thì phải dùng JavaScript (fetch, XMLHttpRequest), nhưng khi đó lại bị chặn bởi Same-Origin Policy (SOP), nên không thể thực hiện CSRF. Khi server chấp nhận application/x-www-form-urlencoded: - Attacker có thể tạo một form HTML bình thường với method="POST" và enctype="application/x-www-form-urlencoded". - Trình duyệt sẽ tự động gửi request kèm cookie của nạn nhân. - Như vậy, attacker có thể ép nạn nhân thực hiện hành động (ví dụ: đổi email) mà không cần vượt qua SOP. Còn kịch bản ở đây là sẽ cố gắng lợi dụng CSRF và graphql để thực hiện graphql query nhằm thay đổi được email của victim mà mình gửi payload tới. ![image](https://hackmd.io/_uploads/r1aipBzW-x.png) Sau khi mình gửi request thay đổi content-type thì nó trả về dòng `"Query not present"` nó là bình thường tại ở đây là đổi sang Content-Type: application/x-www-form-urlencoded, server không còn nhận dữ liệu ở dạng JSON nữa. Nhưng với x-www-form-urlencoded, dữ liệu phải được encode theo dạng form. ![image](https://hackmd.io/_uploads/r17FCrfbZx.png) Tại đây sau khi sửa và thêm url encoded thì ta đã thành công trong việc gửi đi được query đúng chuẩn bây giờ kịch bản tấn công sẽ lợi dụng việc query này ta sẽ tạo payload CSRF sau đó gửi tới victim khi mà click vào thì nó sẽ thực hiện html nhằm thay đổi được email của victim. ![image](https://hackmd.io/_uploads/HJpWyIGZWl.png) Tại đây mình sẽ dùng chức năng gen ra CSRF POC luôn cho nhanh để thực hiện gửi exploit tới exploit server hay là victim mà ta vừa nhắc tới à nhớ đổi email nữa. ![image](https://hackmd.io/_uploads/BkEKyLfWZe.png) Thành công exploit lab này khai thác được CSRF từ graphql và cũng kết thúc chuỗi lỗ hổng GraphQL.