# Research: Client Side Request Forgery (CSRF) ## I. CSRF là gì? ![image](https://hackmd.io/_uploads/By0bNKAhJg.png) `Client Side Request Forgery` hay `CSRF` được biết đến là mỗ lỗ hỏng ở phía `Client` khi mà attacker có thể giả mạo là request của victim để từ dó thực hiện các hành động có hại cho user. ## II. Cách nhận biết Để hiểu rõ hơn ta cần hiểu một cách tường tận về lỗ hỏng. Để một cuộc tấn công CSRF có thể xảy ra, cần thỏa mãn ba điều kiện chính: **Có một hành động liên quan** * Ứng dụng có một chức năng mà kẻ tấn công muốn lợi dụng, chẳng hạn như thay đổi quyền truy cập của người dùng hoặc thay đổi thông tin cá nhân. **Ứng dụng sử dụng cookie để quản lý phiên đăng nhập** * Việc thực hiện hành động yêu cầu gửi một hoặc nhiều yêu cầu HTTP. * Ứng dụng chỉ dựa vào cookie phiên để xác định người dùng, mà không có cơ chế bảo vệ bổ sung như CSRF token. **Không có tham số yêu cầu ngẫu nhiên hoặc khó đoán** * Nếu yêu cầu cần thông tin mà kẻ tấn công có thể đoán được hoặc kiểm soát, thì CSRF có thể xảy ra. * Ví dụ, nếu ứng dụng yêu cầu mật khẩu cũ khi thay đổi mật khẩu mới, thì tấn công CSRF sẽ thất bại. Ví dụ: Giả sử một ứng dụng có chức năng thay đổi địa chỉ email của người dùng, với yêu cầu sau: ```te POST /email/change HTTP/1.1 Host: vulnerable-website.com Content-Type: application/x-www-form-urlencoded Content-Length: 30 Cookie: session=yvthwsztyeQkAPzeQ5gHgTvlyxHfsAfE email=wiener@normal-user.com ``` Yêu cầu này đáp ứng đủ điều kiện để bị tấn công CSRF: * Hành động có giá trị đối với kẻ tấn công: Nếu email bị thay đổi, kẻ tấn công có thể thực hiện khôi phục mật khẩu để chiếm tài khoản. * Dựa vào cookie để xác thực phiên: Ứng dụng không yêu cầu xác minh khác ngoài session cookie. * Tham số dễ đoán: Kẻ tấn công có thể xác định giá trị của tham số email. ## III. Khai thác ### 1. Bypass CSRF Token #### CSRF token là gì? `CSRF token` là một mã duy nhất được server tạo ra và gán cho từng phiên hoặc từng `request` để chống lại Cross-Site Request Forgery (CSRF). Khi một client gửi `request` đến server (ví dụ: gửi form, thực hiện hành động quan trọng như thay đổi mật khẩu), token này phải được gửi kèm theo. Server sẽ kiểm tra token để xác định `request` đó có thực sự đến từ người dùng hợp lệ hay không. Nếu `request` không có token hợp lệ (hoặc không có token), server sẽ từ chối xử lý `request` đó. Ví dụ: Nếu web có một trường để ta update email như thế này ![image](https://hackmd.io/_uploads/rJcpsEUayx.png) Thì khi ta gửi request để thực hiện update thì đồng thời mã `CSRF token` cũng sẽ được gửi kèm theo request `POST` của ta, dẫn đến request của ta sẽ có dạng ```http POST /my-account/change-email HTTP/1.1 Host: normal-website.com Content-Length: 70 Content-Type: application/x-www-form-urlencoded csrf=50FaWgdOhi9M9wyna8taR1k3ODOR8d6u&email=example@normal-website.com ``` #### a. Bypass Validation of CSRF token depends on request method Một vài các máy chủ khi được web dev mặc dù có sử dụng `CSRF token` để chống lại việc khai thác `CSRF` nhưng lại chỉ được áp dụng cho 1 method cố định. Ví dụ khi web cho người dùng thực hiện việc thay đổi email bằng cách gửi một `POST request` đến server như sau ```http POST /email/change HTTP/1.1 Host: vulnerable-website.com Cookie: session=2yQIDcpia41WrATfjPqvm9tOkDvkMvLm email=pwned@evil-user.net&csrf_token=TOKEN ``` Nhưng vấn đề ở đây là cơ chế validate chỉ áp dụng mỗi `POST request` nên attacker có thể lợi dụng điều này và đổi thành `GET request` dẫn đến request trở thành ```http! GET /email/change?email=pwned@evil-user.net HTTP/1.1 Host: vulnerable-website.com Cookie: session=2yQIDcpia41WrATfjPqvm9tOkDvkMvLm ``` Từ đó có thể bypass được cơ chế xác thực này ##### LAB: CSRF where token validation depends on request method Đến với lab, ta khi đăng nhập với credentials và thực hiện việc đổi email thì ta sẽ gửi request như này ![image](https://hackmd.io/_uploads/Sy71NwUaJx.png) Phân tích kỹ hơn ![image](https://hackmd.io/_uploads/rJUgEvITkl.png) Ta thấy rằng khi thay đổi email, một `POST request` sẽ được gửi đến server và gửi kèm theo cả `CSRF_Token`, giờ như đã nói ta sẽ thay đổi thành `GET request` ![image](https://hackmd.io/_uploads/rkoP4v8akg.png) Khi thay đổi và send request mới ta nhận thấy rằng vẫn hoàn toàn có thể thực việc thay đổi email của ta như ở `POST`. Giờ để khai thác `CSRF` ta có thể tự mình dựng một server attack để phising được victim, nhưng ở đây để nhanh hơn thì `Portswigger` cho phép ta dùng exploit server để thực hiện, việc cần làm bây giờ là ta cần tạo một payload để tấn công, ở đây mình sẽ dùng `Engagement tools` của `Burp` ![image](https://hackmd.io/_uploads/r1rySv86yx.png) Thực hiện chọn `Generate CSRF PoC`, ta được một payload và thực hiện việc gửi đến exploit server ![image](https://hackmd.io/_uploads/HJHISwU61e.png) #### b. Bypass Validation of CSRF token depends on token being present Phần này khá là hài hước khi ở một số server sẽ thực hiện việc validate `CSRF_Token` nếu trong request có gửi kèm `CSRF_Token` nếu không thì hàm validate sẽ không hề thực hiện việc kiểm tra dẫn đến lỗ hỏng nghiêm trọng Phần này không có gì để nói thêm ngoài việc thay vì ta thay đổi method như lab truớc thì ở đây ta sẽ xóa luôn cả phần `CRSF_Token`(bao gồm phần param và value) khỏi request trước khi gửi đến server #### c. Bypass CSRF token is not tied to the user session Một số ứng dụng không kiểm tra xem `CSRF` token có thuộc về session của người dùng đang gửi request hay không. Thay vì gắn token với từng phiên đăng nhập (session) riêng biệt, ứng dụng chỉ giữ một danh sách các token hợp lệ và chấp nhận bất kỳ token nào có trong danh sách này. ##### Lab: CSRF where token is not tied to user session Ta nhận được 2 credentials ![image](https://hackmd.io/_uploads/B1oR3vLpyl.png) Khi đăng nhập với `wiener:peter` thực hiện việc đổi email thì ta gửi request kèm với `CSRF_Token` ![image](https://hackmd.io/_uploads/SyLb6DL6yg.png) Giờ ta sẽ thực hiện việc kiểm tra liệu `CSRF_Token` này có được `tied` với user session của `wiener` hay không bằng cách đăng nhập vào với credential thứ 2 là `carlos:montoya` ![image](https://hackmd.io/_uploads/B19Upw86ke.png) Ở request đổi email ở phía `carlos` ta sẽ lấy phần `CSRF_Token` hợp lệ của `wiener` và gán vào phần request của `carlos` và send nhớ là phải bật intercept để chặn request lại vì lúc này ta mới có thẻ lấy được `CSRF_Token` không bị hết hạn và sau đó là drop request đi ![image](https://hackmd.io/_uploads/Sygmulu8ayl.png) Ta thấy ta đã gửi được thành công và bypass giờ chỉ cần gen payload và gửi lên exploit server thôi #### d. Bypass CSRF token is tied to a non-session cookie Lỗ hỏng xảy ra khi ứng dụng sử dụng `CSRF_Token` nhưng lại gắn nó với một cookie khác với cookie dùng để quản lý phiên đăng nhập (session). Điều này có thể xảy ra khi ứng dụng sử dụng hai framework khác nhau cho quản lý phiên và bảo vệ CSRF mà không tích hợp chặt chẽ với nhau. ```http! POST /email/change HTTP/1.1 Host: vulnerable-website.com Content-Type: application/x-www-form-urlencoded Content-Length: 68 Cookie: session=pSJYSScWKpmC60LpFOAHKixuFuM4uXWF; csrfKey=rZHCnSzEp8dbI6atzagGoSYyqJqTz5dv csrf=RhV7yQDO0xcq9gLEah2WVbmuFqyOq7tY&email=wiener@normal-user.com ``` Như ta thấy ngoài `csrf` ta còn có 1 `csrfkey` được binding với session thay vì là `csrf` để hiểu rõ hơn ta sẽ thực hành lab ### 2. Bypassing SameSite cookie restrictions #### Samesite Cookie? Trước khi tìm hiểu `Samesite cookie` là gì ta cần biết đến khái niệm `Site` và `Origin` trong `URL` ##### Site Site trong ngữ cảnh của `Samsite Cookie` sẽ là phần `schemes + TLD+1`, `schemes` sẽ là các giao thức (http, https, ws,...) và phần `TLD+1` sẽ là `Top level domain (.com, .net,...)` còn `+1` sẽ là phần domain name của ta ![image](https://hackmd.io/_uploads/Hk2xY3LaJl.png) Như trên hình ta thấy `site` sẽ gồm 3 phần tử trên ( ngoại trừ sub-domain là app) ##### Origin Còn `Origin` sẽ nôm na là nguồn gốc, chi tiết hơn `site`, nó bao gồm luôn cả phần `sub-domain` và các `port` ở phía sau mà server đang được chạy ![image](https://hackmd.io/_uploads/B1sUFnLpke.png) Origin khác nhau thì không thể chia sẻ tài nguyên trừ khi có chính sách CORS (Cross-Origin Resource Sharing). ![image](https://hackmd.io/_uploads/SJuN92L61x.png) Ta có các trường hợp dùng để so sánh xem liệu hai `URL` có cùng site hay không ví dụ ở trường hợp thứ 4, mặc dù cả `schemes, domain-name` đều giống nhau nhưng ở phần `TLD` ta thấy được là khác nhau nên dẫn đến việc `Same-site` là NO Vậy giờ ta sẽ đến với câu hỏi `Same-Site cookie` là gì? `SameSite` là một thuộc tính của cookie được sử dụng để kiểm soát cách trình duyệt gửi cookie trong các yêu cầu HTTP, đặc biệt khi có yêu cầu từ các trang web khác. Nó giúp bảo vệ chống lại Cross-Site Request Forgery (CSRF) và cải thiện bảo mật. Các giá trị của SameSite Thuộc tính SameSite có thể có ba giá trị: **SameSite=Strict** * Cookie chỉ được gửi khi người dùng truy cập trang web từ cùng một nguồn (same-site). * Không gửi cookie khi điều hướng từ trang web khác hoặc khi thực hiện yêu cầu từ bên thứ ba. * Bảo mật cao nhất, nhưng có thể gây bất tiện cho người dùng. **SameSite=Lax (Mặc định trong nhiều trình duyệt hiện nay)** * Cookie được gửi khi điều hướng từ trang web khác nếu đó là một yêu cầu điều hướng GET thông thường (ví dụ: bấm vào link). * Không gửi cookie cho các yêu cầu không điều hướng, như fetch(), XMLHttpRequest, hoặc yêu cầu được thực hiện bởi form POST. * Cân bằng giữa bảo mật và tiện lợi. **SameSite=None; Secure** * `Cookie` được gửi trong tất cả các yêu cầu, kể cả yêu cầu từ trang web khác (cross-site request). * Bắt buộc phải có Secure, nghĩa là chỉ được gửi qua `HTTPS`. * Dùng khi cần chia sẻ cookie giữa nhiều trang web, nhưng cần cẩn thận với các rủi ro bảo mật. #### a. Bypassing SameSite Lax restrictions using GET requests Đã có cách để tạo ra thì cũng có cách để phá hủy, ta sẽ tìm hiểu cách đầu tiên để bypass được cơ chế `Samesite` này Trường hợp này sẽ là đối với server dùng `SameSite = Lax`, như ta biết `Lax` sẽ cho phép cookie được gán vào request nếu đó là method `GET` nên từ đây ta sẽ có cách để bypass Một số framework (như Symfony, Ruby on Rails) hỗ trợ tham số `_method`, cho phép ghi đè phương thức HTTP. Điều này có thể bị lợi dụng trong CSRF như sau: ```html <form action="https://vulnerable-website.com/account/transfer-payment" method="POST"> <input type="hidden" name="_method" value="GET"> <input type="hidden" name="recipient" value="hacker"> <input type="hidden" name="amount" value="1000000"> </form> ``` * Nếu server chấp nhận tham số `_method`, nó có thể xử lý yêu cầu như một `GET` request ngay cả khi form sử dụng `POST`. * Điều này giúp kẻ tấn công vượt qua các biện pháp bảo vệ hạn chế GET. * Khi nạn nhân tải trang chứa form này, trình duyệt sẽ tự động gửi yêu cầu nếu có `JavaScript` kích hoạt. ##### Lab: SameSite Lax bypass via method override Thực hiện đăng nhập với `wiener`, giờ ta có thể khai thác như các lap truớc, mình dùng engagement tools để gen payload ![image](https://hackmd.io/_uploads/HkDk8a86ye.png) ta sẽ thêm thẻ input với `name=_method` và `value=GET` #### b. Bypassing SameSite restrictions using on-site gadgets Một gadget ở đây là bất kỳ tính năng nào của trang web có thể tạo ra một yêu cầu `HTTP` thứ cấp (secondary request) mà vẫn giữ nguyên context của người dùng. Một ví dụ phổ biến là client-side redirect: * Nếu trang web có một chức năng redirect mà kẻ tấn công có thể kiểm soát, họ có thể lợi dụng nó để tạo một yêu cầu hợp lệ mà trình duyệt sẽ coi là `same-site request`. * Khi đó, trình duyệt sẽ gửi cookie `SameSite=Strict`, bỏ qua giới hạn cross-site ban đầu. Lý do bypass được `SameSite=Strict`: * Trình duyệt chỉ chặn yêu cầu đầu tiên nếu nó đến từ một trang khác. * Nhưng nếu yêu cầu đầu tiên dẫn đến một redirect trong cùng trang web, yêu cầu thứ hai vẫn được coi là `same-site` và cookie sẽ được gửi. ##### Lab: SameSite Strict bypass via client-side redirect ![image](https://hackmd.io/_uploads/Sk3YjTL6Jg.png) Quan sát thấy ở request `POST login` được gửi kèm với `SameSite=Strict` giờ ta sẽ đến với các bài đăng, ![image](https://hackmd.io/_uploads/B118hTUp1g.png) Thấy rằng khi ta comment và post lên thì ta sẽ được redirect về bài post trước đó được thực hiện bởi request sau đây ![image](https://hackmd.io/_uploads/SJX_2TU6Je.png) Nó sẽ thực hiện lấy element `postId` và gán vào `blogPath` ![image](https://hackmd.io/_uploads/SJljnpL6kl.png) Ở đây ta có request `GET` như này có số `postId=6` tức là bài post lúc ta thực hiện việc đăng comment, do hàm `Javascript` lúc nãy không hề validate input nên ta có thể inject ở đây, mình thử inject path traversal ![image](https://hackmd.io/_uploads/SJTze0Iayx.png) ![image](https://hackmd.io/_uploads/B1W7g08ayl.png) Ta thấy rằng đã được redirect về trang home, tức là ta có thể inject được, vậy nghĩ đến viễn cảnh ta sẽ thực hiện redirect đến `/my-account/change-email` thì sao? Do đây là redirect thực hiến trực tiếp từ phía server không phải là request của user nên ta có thể dễ dàng bypass được mặc dù là `SameSite=Strict` Ta có payload `https://0a16004d039e5d70806a3ac900d2001a.web-security-academy.net/my-account/change-email?email=trietnguyen1892%40gmail.com%26submit=1` ![image](https://hackmd.io/_uploads/ry30WC8aJg.png) Ta được redirect thẳng đến trang `change-email` và thay đổi email thành công, từ giờ ta có thể gen payload và đưa lên server để khai thác ### 3. Bypassing Referer-based CSRF defenses Trước tiên, ta cần biết được `Referer` là gì? `Referer` xuất phát từ việc viết sai chính tả của từ `Referrer` trong tiếng Anh, là một `HTTP header` mà trình duyệt tự động gửi kèm trong mỗi yêu cầu HTTP. Nó cho biết trang web trước đó (trang gốc) đã dẫn người dùng đến trang hiện tại là gì. ![image](https://hackmd.io/_uploads/HkS7O590Jl.png) Từ ảnh trên ta có thể thấy trang hiện tại ta đang `GET` đến là `/index.php/2019/12/` và trang gốc chứa nó chính là ở phần `Referer: http://192.168.78.157/`. Trình duyệt có thể không gửi header này vì lý do bảo mật hoặc quyền riêng tư và chúng dễ bị giả mạo hoặc bị loại bỏ. Kẻ tấn công có thể lợi dụng các lỗ hổng hoặc cấu hình trình duyệt để loại bỏ hoặc thay đổi giá trị của header Referer. #### a. Validation of Referer depends on header being present Nếu ứng dụng chỉ kiểm tra Referer khi header này có mặt, kẻ tấn công có thể tạo ra các yêu cầu mà trình duyệt không gửi kèm header Referer. Một phương pháp phổ biến là sử dụng thẻ meta trong trang HTML độc hại:​ ```html <meta name="referrer" content="never"> ``` Thẻ này yêu cầu trình duyệt không gửi header Referer trong các yêu cầu xuất phát từ trang đó. Nếu ứng dụng không xử lý trường hợp thiếu Referer, yêu cầu giả mạo có thể được chấp nhận. Đến với lab, ta sẽ thực hiện việc thay đổi email như các lab trước, sau đó xóa đi phần `Referer` ở req và sau đó thêm thẻ meta ở trên vào ![image](https://hackmd.io/_uploads/ryJER99Akl.png) #### b.Validation of Referer can be circumvented Một số ứng dụng cố gắng ngăn chặn tấn công CSRF bằng cách kiểm tra tiêu đề Referer, cụ thể là kiểm tra xem tên miền có khớp với domain của chính nó không. Tuy nhiên, nếu kiểm tra không cẩn thận (kiểu "starts with" hoặc "contains") thì dễ bị bypass. Nếu ứng dụng kiểm tra rằng Referer "bắt đầu bằng" tên miền vulnerable-website.com, thì attacker có thể tạo subdomain trông giống như domain thật: ```url Referer: http://vulnerable-website.com.attacker-website.com/csrf-attack ``` → Kẻ tấn công sở hữu domain attacker-website.com, nhưng tạo subdomain vulnerable-website.com.attacker-website.com.