# Web Security [TOC] payload 可以參考這個網站 [Web CTF Cheatsheet](https://github.com/w181496/Web-CTF-Cheatsheet) ## What is a website? 一般我們看到的網頁是由 HTML、JavaScript 以及 CSS 組成的 當你到一個網站時,實際上是向一個伺服器發起 HTTP 請求,並以此獲得資源 而瀏覽器則會 render 得到的 HTML 及 CSS 並執行裡面的 JavaScript ### Frontend 在網頁開發中,我們會將使用者可以看到並且操作的部分稱為前端,這部分是由 HTML/CSS/JavaScript 組成的 透過瀏覽器的開發者工具(F12),我們可以看到原始碼 * 主控台(console),可以執行 JavaScript * 來源(source),此頁面所用到的所有 HTML、CSS、JavaScript 或是圖片等 * 網路(network),紀錄此頁面所發起的所有連線 * 應用程式(application),存放此頁面的 Cookie 以及 LocalStorage ### HTML HTML(超文本標記語言) 是用來表示網頁架構的語言,本身雖然不具有任何執行的能力,但是可以在裡面擺放 `<script>` 標籤來讓瀏覽器執行 JavaScript ~~但加上 CSS 可以達成圖靈完備~~ 同時一些如 `<img>` 或是 `<iframe>` 等標籤會讓瀏覽器向伺服器發起 HTTP 請求來取得圖片或是其他資源 瀏覽器會以 [DOM tree](https://developer.mozilla.org/en-US/docs/Web/API/Document_Object_Model/Introduction) 的形式來對應 HTML 的元素,使得 CSS 可以透過標籤、class 或是 id 來對元素附加 style(樣式) 同時也讓 JavaScript 可以來操控 HTML 的元素 ```xml <!DOCTYPE html> <html> <head> <title>My Link</title> <link rel=stylesheet type="text/css" href="index.css"> </head> <body> <img class="bg-img" src="https://myimage.com"> <div class="content"> <div class="box"> hihi </div> </div> </body> <script> console.log(123) </script> <style> .content { width: 300px; justify-content: center; display: flex; align-items: center; } </style> </html> ``` 每一個標籤都可以含有多個屬性,而某些標籤的某些屬性可以註冊事件,使得在觸發某些事件時執行 JavaScript(按下按鈕、送出表單) ```xml <button class="btn" onclick='alert(1)'> <img id="my-img" src='https://imgur.com/123' onload='console.log(1)'> ``` ### JavaScript ![](https://hackmd.io/_uploads/HycdXGyx6.png) JavaScript 是一個~~自由的語言~~被設計用來操縱網頁的語言,可以透過瀏覽器提供的 API 來操縱網頁的 DOM 或是發起 HTTP 請求 他也是一個以 prototype (原型鍊)來實作物件導向的弱型別語言 而以 [Google v8](https://v8.dev/) 來實作的 [Node.js](https://nodejs.org/en) 也使得它可以脫離瀏覽器的環境來運作 [MDN JavaScript](https://developer.mozilla.org/zh-TW/docs/Web/JavaScript) ```javascript // 選取一個 id 為 my-element 的元素,並將他的文字設為 hello const a = document.querySelector('#my-element') a.innerText = 'hello' ``` #### fetch https://developer.mozilla.org/zh-TW/docs/Web/API/Fetch_API 最早的時候,瀏覽器要發送請求只能透過網址或是發送表單,所以每次更動頁面都必須重新載入 後來 Google 開始使用 [AJAX](https://developer.mozilla.org/zh-TW/docs/Web/Guide/AJAX),使得網頁可以透過 JavaScript 來發送請求,並以此來動態的改變網頁 fetch 為現今瀏覽器提供的 API,比起以往的 `XMLHttpRequest` 介面好用許多 ```javascript fetch('https://example.com',{ method:'POST', header:{ 'Content-Type':'application/json' }, body: JSON.stringify(myobject) }) ``` #### cookie cookie 常被用來存放一些資訊,例如 * Session * Token * 狀態 * 追蹤資訊 而前端可以使用 JavaScript 來設置 cookie,或是使用 devtool 的 application 頁面來設置 ```javascript document.cookie = "username=Asuna"; //or //Chrome api cookieStore.set('admin',true) ``` 後端要設置 cookie 則是透過 `Set-Cookie` 標頭 ``` HTTP/1.1 200 OK ... Set-Cookie: session=deadbeef; ... ``` 而瀏覽器發起請求時,便會將這些 cookie 帶入 `Cookie` 標頭裡 ``` GET / HTTP/1.1 ... Cookie: session=deadbeef; ... ``` cookie 同時也有不同的屬性,來決定瀏覽器要怎麼處理 cookie * HttpOnly 前端的 JavaScript 不能操作這個 cookie * Domain cookie 存放的網域 * Expire cookie 什麼時候過期 * Secure 此 cookie 只能在 https 下存放 [MDN Cookies](https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies) ### Backend 負責處理請求以及請求的資料,可以由幾乎所有程式語言來撰寫 現代常用的 HTTP Server 或是框架都有針對不同路徑執行不同函式的功能 * Nginx 現代主流的 Web Server * Apache 老牌的 Web Server * Flask Python 的後端框架 * Express Node.js 的後端框架 * PHP ~~世界上最好的語言~~ ## HTTP HTTP 請求是前後端溝通的手段 一個 HTTP 請求會分成三個部分 * request line 這會在請求的第一行,說明此請求的方法(method)、路徑(path)以及版本 * request header 請求的標頭,可包含許多訊息,包括 Cookie 或是使用者使用的客戶端 * request body 請求攜帶的資料,不一定需要 一個 HTTP 請求的範例長這樣 ``` GET / HTTP/1.1 Host: google.com User-Agent: curl/7.68.0 Accept: */* ``` request line 以及 header 的每一行會用 `\r\n` 來隔開 ### Request Line ``` GET / HTTP/1.1 ^ ^ ^ method path version ``` 一般來說請求方法只會用到 `GET` 跟 `POST` 分別代表獲取以及上傳的意思 其他還有較不常用的 `PUT`、`OPTION`、`HEAD` 等 這部分其實完全可以自定義,只要後端有對應的 route 即可 有些開發者會依照 REST 的設計方法讓每個 API 的 method 都是更為精確的 `PUT` 或是 `OPTION` 之類的 可參考 : https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods 請求路徑則是 URL 的一部分 但是還可以使用 `?` 來提供參數 `/foo/bar?dead=beef&id=123` 若是在請求路徑裡含有特殊字元(如 ascii 0x00~0x19 或是中文) 我們可以使用 url encoding,也就是以 hex 來表達該字元 `/%41%42%43` ### Request Header ``` Host: google.com User-Agent: curl/7.68.0 Accept: */* ``` HTTP 標頭會攜帶封包的資訊,像是我們常聽說的 Cookie 之類的 而因為 HTTP 是無狀態的 (Stateless),所以 header 所攜帶的訊息非常重要 ### HTTP Response ``` HTTP/1.1 200 OK Server: nginx/1.18.0 (Ubuntu) Date: Fri, 15 Sep 2023 02:34:04 GMT Content-Type: text/html; charset=utf-8 Content-Length: 22511 ``` response 跟 request 的格式差不多相同 不同的點在於第一行會改而顯示 HTTP 狀態碼 像是 200 就是 OK,而我們常見的 404 就是無此頁面的意思 同時 request 跟 response 所使用的 header 也不盡相同 [MDN HTTP Header](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers) [MDN HTTP Status](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status) ### Lab 使用 Curl 或是 BurpSuite 來觀察 HTTP 封包 `curl https://www.google.com -v` ## Rookie Vulnerable ### SQLI(SQL Injection) SQL 是非常常使用的資料庫語言,可說是現代資料庫的基石 他可以用來操作關聯式資料庫(RDBMS),也就是用表格型式來儲存資料的資料庫 這些表格被稱為 table 每一個欄位被稱為 column 而每一項資料被稱為 row |id|user |password| |--|------|-------| |1 |Heathcliff|047463c04d2c12909efbbc71fb170efd |2 |Kirito|8699314d319802ef792b7babac9da58a| ```sql select * from users where user = 'Kirito' and password = '8699314d319802ef792b7babac9da58a' -- ^ ^ ^ -- columns table condition ``` 在 condition 的部分我們通常會讓使用者來提供輸入 但若沒檢查用戶輸入會發生非常嚴重的後果 假如使用者在 user 欄位輸入 `Healthcliff' or '1'='1';--` 則查詢的語句就會變成 ```sql select * from users where user = 'Healthcliff' or '1'='1';--' and password = 'something' ``` #### Union base 但是上述語句只有在登入的情況才能使用 我們能否透過其他查詢語句來將整個資料庫 dump 下來? 這時候就可以用到 union 關鍵字 假如我們有下列的 table | id | book | author | rating | | --- | ----------------- |:---------------- | ------ | | 1 | Lord of the Rings | J. R. R. Tolkien | 5 | | 2 | Sword Art Online | Kawahara Reki | 3 | 而有一個 query 長這樣 ```sql select * from books where book = 'Lord of the Rings' ``` union 便可以用來查詢另一個 table,並且跟原本的結果合併 **要注意的是**透過 union 查詢的另一個 select 語句的 column 數必須與原本的一樣 若不足的可以補 null、字串或是數字 有些 DBMS 除了 column 數要相同外,還會要求資料型態相同,可以使用 null 就好 ```sql select * from books where book = 'Lord of the Rings' union select *,null from users; ``` #### Boolean base 更常的一些情況是 query 查詢的結果不會直接顯示在頁面上 但是仍然能透過其他資訊來判斷是否查詢成功,像是登入成功與否之類的 而這時我們便可以使用 SQLI 裡的 if else 並搭配二分搜來將要搜尋的東西一個字一個字的爆出來 ```sql -- i = 1 -- mid = (l+r)/2 -- 用{}包起來不是合法 sql 語句,這樣弄只是避免看起來太長 ... and if((select ascii(substring(password,{i},{i}))<={mid} from users where user='admin'),true,false);-- ``` #### Metadata table 每種 SQL 資料庫都會有專門的 table 去存放所有 table 以及 column 的名稱 若你不知道你要撈的目標表名,則需要先從這些 table 將目標表明撈出來 * sqlite_master SQLite * information_schema MySQL * pg_database pg_tables PostgreSQL #### How to prevent? 最佳解法就是使用 prepare statement 或是使用 ORM,來避免直接串接字串 自己去過濾關鍵字,可能會有漏網之魚,還不如用別人提供的 ```javascript db.get('SELECT * FROM Users WHERE username = ?', username) ``` ### XSS(Cross-Site Scripting) 前端使用 HTML、CSS 以及 JavaScript 來 render 頁面 但若直接把用戶的輸入不加以過濾便插入至頁面上,就會造成 XSS(Cross-Site Scripting) 雖然 XSS 的形式有分很多種,但最常見的成因只有兩個 * 前端的 JS 使用到 innerHTML 來植入用戶提供的內容 * 後端在 render 模板時沒有 escape 用戶提供的內容 而受到 XSS 攻擊的網站會使得攻擊者能獲得其他用戶的 Cookie,造成能獲得其他使用者的敏感資訊,或是成為管理員 而若受到 XSS 攻擊的是使用 Electron 的應用程式,則也有機會能造成 RCE 一些常見的 payload * `<img src=# onerror="alert(1)">` * `<svg/onload="alert(1)">` * `<button onclick="alert(1)">` #### CORS(Cross-Origin Resource Sharing) 瀏覽器的同源政策 (Same-Origin Policy) 會限制網頁只能去存取同個網頁的資源,除了路徑以外,協定(protocol)、網域(domain)以及埠口(port)都必須相同 而如果要去獲取別的網站的資源(api、CSS、JavaScript),瀏覽器提供一個叫做 CORS 的機制 本質上就是只要該網站的回應包含 CORS 標頭就允許存取 `Access-Control-Allow-Origin: *` [MDN CORS](https://developer.mozilla.org/zh-TW/docs/Web/HTTP/CORS) #### CSP #### How to prevent? 通常會將 HTML 轉譯成 [HTML Entity](https://developer.mozilla.org/en-US/docs/Glossary/Entity) 來將 HTML 轉成純字串顯示 不要隨意使用 innerHTML 若是要有多功能的文字編輯(如用戶發文可以附圖片、超連結等) 一定要使用如 [Dompurify](https://github.com/cure53/DOMPurify) 等過濾函式庫來處理用戶的輸入 若是有其他需求,如使用到 iframe 的 srcdoc 或是讓用戶可以上傳並檢視 SVG 等,也需要去使用 [CSP](https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP) 做好防護 ### Miss Configuration #### Information Leak 有些檔案可能會帶有敏感資訊,同時網站開發者沒限制存取 * robots.txt * .git * .htaccess * .well-known * package.json ## Other Vulnerable ### Arbitrary Read/Write 有些網站可能會將使用者的輸入拼接到路徑之上,並且讀取該路徑的檔案 這時候便可以讀取諸如原始碼或是環境變數等檔案 ```py open(f'/tmp/userdir/{file}') # file = ../../etc/passwd open('/tmp/userdir/../../etc/passwd') ``` * /etc/passwd * /proc/self/cmdline * /proc/self/fd/[0-9] * /proc/self/environ * /etc/nginx/nginx.conf * /etc/apache2/httpd.conf * ... #### Local File Inclusion 在一些特殊的語言如 PHP,可以在執行時去動態 include 外部的腳本 這時若能改變所 include 的檔案,則就有辦法達成 RCE * [include](https://www.php.net/manual/en/function.include.php) * [require](https://www.php.net/manual/en/function.require.php) ### Template Injection ### SSRF ### Prototype Pollution ### Unsafe Deserialization # Docker 在打 Web 題的時候,出題者通常會提供 Dockerfile 來讓參賽者可以在自己的電腦把環境架起來 Docker 其實就是一種 KVM 的應用,可以透過設定檔來定義虛擬容器的內容 容器化的技術可以讓服務在更精簡的環境運行,同時擴展性以及開關速度都比以往的虛擬機器還要好 ![Container vs VM](https://www.docker.com/wp-content/uploads/2021/11/docker-containerized-and-vm-transparent-bg.png) Docker 的架構主要有三個 * Dockerfile 決定 Image 如何去構建 * Image 決定 Container 開啟時會長怎樣,可以直接從官方倉庫抓下來別人構建好的 * Container 運作時的實體,每個 container 都互相獨立 ## Install Docker Ubuntu 可以直接輸入 `sudo apt-get install docker` Windows 及 MacOS 則是下載 [Docker Desktop](https://www.docker.com/products/docker-desktop) MacOS 還可以使用 [OrbStack](https://orbstack.dev/) 作為替代方案 ## Dockerfile Docker 可以使用 Dockerfile 來做好一個虛擬容器的配置 包括安裝套件或是複製原始碼 同時他也是一層一層的結構,也就是說每個 Dockerfile 都會是基於另一個 Dockerfile 去做配置 ```dockerfile # 基於 node 20 alpine 的環境 FROM node:20-alpine # 將工作目錄設成容器裡的 /app WORKDIR /app # 將檔案及目錄複製過去 COPY package.json ./ COPY package-lock.json ./ COPY views ./views COPY public ./public COPY app.js ./ # 安裝套件 RUN npm install # 容器啟動時要使用的指令 CMD ["node", "app.js"] ``` 當寫好 Dockerfile 時,我們可以使用 `docker build -t .` 來將對應的 image 建構出來 而 Dockerfile 的內容則會在 build 時執行完畢 接著便能使用 `docker run` 來啟動容器 ```sh $ docker run -d --name web -p 8080:80 image # -d 背景執行 # -p port 轉發 # image 你的 image 的名稱 ``` ## Docker compose 如果每次重新啟動容器都要重打參數會很麻煩,雖然可以自行寫 shell script 來自動化 但是官方提供了 docker compose 來使用 yaml 設定檔一次管理多個容器 docker-compose.yml ```yaml # 版本 version: "3.7" services: minecraft: # 要使用的 image image: itzg/minecraft-server # 容器名稱 container_name: minecraft # 容器裡的環境變數 environment: EULA: "TRUE" TYPE: "FABRIC" ENABLE_COMMAND_BLOCK: "true" ONLINE_MODE: "false" DIFFICULTY: "hard" VERSION: "1.20" VIEW_DISTANCE: 14 TZ: "Asia/Taipei" # 使容器裡的目錄或檔案映射到 host 的目錄或是檔案,讓容器關閉後資料仍然能留存 volumes: - ./minecraft:/data # port 轉發 port: - "25565:25565" ``` 要啟動便在同一個目錄底下輸入 ```sh $ sudo docker compose up -d ``` 如果是舊版則是 ```sh $ sudo docker-compose up -d ```