跳至主要内容

[IS] WebGoat 學習筆記

此篇筆記是照著 來玩 webgoat - 資安補漏洞,越補越大洞 @ iThome 實作而來。

OWASP:包含各種資訊安全描述。

安裝與啟動

# 啟動 webgoat server
# java -jar webgoat-server-<version>.jar [--server.port=8080] [--server.address=localhost]
java -jar webgoat-server-8.0.0.VERSION.jar

# 啟動 webwolf server
java -jar webwolf-8.0.0.VERSION.jar [--server.port=9090] [--server.address=localhost]

接著就可以透過 http://localhost:8080/WebGoat 看到 WebGoat 登入頁面;http://localhost:9090/WebWolf 則可進入 WebWolf 登入頁。

HTTP Proxy

許多時候 Proxy 是用來處理存取被阻擋的內容,使用者可能連上 server A,而 server A 的內容來自 server B,而使用者的網路(network)是無法直接存取到 server B 的內容。HTTP Proxies 是很類似的概念,它會接收從 client 的 request 然後轉發,通常還會紀錄(record)下來。

HTTP Proxies 就像中間人的角色,它介於 client 和 server 之間,它可以:

  • 記錄和分析(甚至修改)這些 request 和 response
  • 更有效率地進行測試或分析網站的安全性
  • CI/CD

SQL Injection

Normal SQL Injection

cheat sheet

-- string injection
' or '1' = '1

-- 利用註解來 injection
admin' --
-- 利用 UNION 搭配「註解」撈出另一個帶有密碼的 user_system_data 表,達到 SQL Inject

' UNION
SELECT userid, user_name, password, 'number', 'type', cookie, 0
FROM user_system_data --

「SQL Injection 就是利用程式的漏洞,利用 SQL 執行的錯誤語法,或是猜測資料庫結構或欄位名稱等,幫原本要執行的 SQL 語法加料,進而造成資料庫執行到被加料的 SQL 語法,並造成非預期的結果,像是資料被修改、外洩及刪除等,也有可能整個資料庫被刪除或多新增幾個帳號等,總之只要是 SQL 語法能做到的事情都有可能因此被執行」[Day 16] 來玩 WebGoat!之 4:SQL Injection @ IThome

利用字串插補(string interpolation)或字串連結(concatenating strings)的方式來撰寫「動態的」 SQL 語法非常容易遭到 SQL Injection:

String and Numeric Injection

String Based
select * from users where name = '" + userName + "';

-- username 帶入 userName = Smith' or '1'='1
-- 即可把所有資料撈出來
select * from users where name = 'Smith' or '1' = '1';
Numeric Based
"select * from users where employee_id = " + userID;

-- userID 帶入 1234567 or 1=1
-- 即可把所有資料撈出來
select * from users where employee_id = 1234567 or 1 = 1

Special characters SQL Injection

利用 SQL 中的特殊字符(例如註解)進行 SQL Injection:

# php
$query = "select * from member where member_id = '".$memberID."' and password = '".$password."';”;
$result = $conn->query($sql);

$memberID 的內容中偷偷塞入註解符號 ,如此會變成:

-- 利用註解達到 SQL Injection
select * from member where member_id = 'admin' -- ' and password = 'pass';

UNION based SQL Injection

使用 [UNION](/Users/pjchen/Projects/Notes/source/_posts/Database/[SQL] 常用指令.md) 只需注意兩個 query 中的欄位「數目」與「資料型別」要相同:

-- 利用 UNION 搭配「註解」撈出另一個帶有密碼的 user_system_data 表,達到 SQL Inject

' UNION SELECT userid, user_name, password, 'number', 'type', cookie, 0 FROM user_system_data --

Blind SQL Injection

一般的 SQL injection 和 blind SQL injection 的差別在於,一般的 SQL injection 會從 database 丟出一定的訊息(可能是錯誤訊息),讓我們可以根據這些資訊來嘗試。但是當沒有任何訊息顯示時,你必須要向資料庫詢問 truefalse 這類的問題(例如,WHERE password = '123'),以此來猜測想要取得的資訊。

Blind SQL injections 有很多種類型,其中包含 content basedtime based 的 SQL injection。

Content Based

舉例來說,當我們輸入網址 https://my-shop.com/?article=4 時,伺服器實際會執行如下的 query:

SELECT * from articles where article_id = 4

這時候若我們將網址改成 https://my-shop.com/?article=4 AND 1=1 則 query 的內容會變成:

-- 得到一樣的資料
SELECT * from articles where article_id = 4 AND 1 = 1

-- 得不到任何資料
SELECT * from articles where article_id = 4 AND 1 = 2

如果這時候可以得到資料內容而不是進到 404 時,表示說我們可以透過 url 來取得一些資料庫相關的資訊,因為這表示 AND 後面的內容有被吃到 SQL 中,例如當我們使用網址 https://my-shop.com?article=4 AND substring(database_version(),1,1) = 2,可以逐一嘗試來猜這資料庫的版本號碼等等。

Time Based

article = 4; sleep(10) --

搭配 ORDER BY

當 password 等於 1 時則會按照 lastname 排序,否則按照 firstname 排序,重複此過程如此來猜出密碼:

select * from users order by (case when (password = '1') then lastname else firstname)

Example: Blind SQL Injection

可以使用這段 URL 來測試出 IP:

http://localhost:8080/WebGoat/SqlInjection/servers?column=(CASE WHEN EXISTS(SELECT id FROM servers WHERE hostname='webgoat-prd' AND substring(ip,1,1)=${item}) THEN id ELSE ip end)

原本的 SQL 可能類似:

SELECT * FROM servers ORDER BY $orderParams

把上面的 url 帶入 SQL 中會變成:

SELECT * FROM servers ORDER BY
(
CASE
WHEN EXISTS(
SELECT id FROM servers
-- 判斷 servers 中是否真的有 webgoat-prd 這個 hostname(可省略)
-- AND substring(ip,1,1) = 0 -> 這句是關鍵
-- 每次取出 ip 欄位中的一個值來嘗試,如果猜對了,則網頁會用 id 排序,否則會用 ip 排序
WHERE hostname='webgoat-prd' AND substring(ip,1,1)=0
)
THEN id
ELSE ip end
)

若要檢查 IP 的第二碼,則 substring 要改成 substring(ip, 2, 1)=0

透過 JavaScript 一次送出 10 個 request:

// 以下程式會檢查 ip 的第一碼多少
Array.from({ length: 10 }, (_, k) => k).forEach((item) => {
fetch(
`http://localhost:8080/WebGoat/SqlInjection/servers?column=(case when exists(select id from servers where hostname='webgoat-prd' and substring(ip,1,1)=${item}) then id else ip end)`,
)
.then((response) => response.json())
.then((result) => console.log(`result - ${item}`, result));
});

防範 SQL Injection

要減少被 SQL Injection 的機會最重要的是不要使用 Dynamic Queries,而是改用 Static QueriesParameterized Queries

Static Query

-- Static Query
select * from products;
select * from users where user = "'" + session.getAttribute("UserID") + "'";

Parameterized Query

String query = "SELECT * FROM users WHERE last_name = ?";
PreparedStatement statement = connection.prepareStatement(query);
statement.setString(1, accountName);
ResultSet results = statement.executeQuery();

減少授權的對象

  • 對於每一個會連結到資料庫的應用程式都應該要擁有不同的驗證訊息(例如,連線字串)
  • 應用程式通常不需要擁有刪除資料庫或資料表的權限
  • 資料庫的帳號應該限制 schema 的存取
  • 根據不同的資料庫帳號設定不同的存取權限,有些只能讀取,有些可以讀寫

注意

  • 即使已經透過 Parameterized Query 的方式來避免 SQL injection,表單驗證仍然一定要做,以此避免其他可能的攻擊,例如,Stored XSS, Information leakage, Logic errors – business rule validation 等。
  • 若你的網站有提供排序的功能(order by),應該要把可以被排序的欄位放入「白名單」中(例如,firstname),而不是讓使用者可以填入任何內容去做排序。

XXE (XML External Entity attack)

XML External Entity 攻擊主要針對會解析 XML 的應用程式。這種攻擊會先定義具惡意內容的外部實體(external entity),然後再 XML 解析時去引用或參照到這個外部實體,如此以達到洩漏機密資料、服務拒絕存取、偽造伺服器端的請求、掃描 port、以及其他對系統可能的影響。

一般來說 XXE 的攻擊可以分區成:

  • Classic:外部的 entity 被包含在本機的 Document Type Definition (DTD)
  • Blind:在回應中沒有錯誤或回傳結果
  • Error:試著從錯誤訊息中找到想要的內容

攻擊者自己定義 XML 外部的 entity,然後寫入 XML 中,如此進行攻擊:

<?xml version="1.0" standalone="yes" ?>
<!-- 定義外部 entity 名為 js -->
<!DOCTYPE author [
<!ELEMENT author (#PCDATA)>
<!ENTITY js "Jo Smith">
]>

<!-- 在 XML 文件中引用外部實體 -->
<author>&js;</author>

XML DTD Tutorial @ W3School

Example

舉例來說,原本使用者填入的資料會出現在 <text></text> 內,透過 XXE 的方式,引入外部定義好的實體,即可取得使用者資料夾的內容:

<?xml version="1.0" encoding='UTF-8'?>
<!DOCTYPE comment [
<!ELEMENT comment (#PCDATA)>
<!ENTITY xxe SYSTEM "file:///">
]>
<comment> <text>&xxe;</text></comment>

防範 XXE

要避免 XXE 攻擊最重要的是去檢驗所有不信任的客戶端傳送過來的資料,在 Java 中可以告訴 parse 完全忽略 DTD。同時適當地於伺服器端驗證 HTTP 標頭中的 Content-typeAccept

驗證漏洞 Authentication Flaws

Authentication Bypassed

  • Hidden Inputs:透過修改表單中的參數名稱、參數值或移除參數來試著找出漏洞
  • Removing Parameters:有時攻擊者如果不清楚參數正確的值,他們會移除所有送出的參數來看會發生什麼。
  • Forced Browsing:如果一個網站的設定不恰當,有機會可以透過猜測或暴力破解的方式來進入特定網頁。

JSON Web Token(JWT)

JWT 的部分可以參考 [JWT 學習筆記](/Users/pjchen/Projects/Notes/source/_posts/WebDevelopment/[Note] JWT 學習筆記.md)。

Password reset

每個網站都有「重設密碼」的功能,但進行的方式可能不同,有些公司甚至還會透過電子郵件傳送明碼,這種網站你應該要非常留意,因為這表示它們將你的密碼使用明碼儲存。

在建立重設密碼的功能時,需要確定:

  • 每一次的連結都是獨特(unique)且帶有隨機的 token 的:確保攻擊者無法使用簡單的 DOS 攻擊
  • 只能被使用一次:避免密碼可以被再次重設
  • 每個連結只能使用一小時:減少被攻擊的可能

透過重設密碼找出 email 是否存在

有些駭客會透過「重設密碼」的功能觀察某個使用者是否已經註冊該網站會員,然後再透過寄送假的驗證信,騙取使用者的帳號密碼:

imgur

Cross-Site Scripting (XSS)

Cross-site scripting (XSS) 是一種透過 <script> 來讓瀏覽器在未經過濾惡意程式碼的情況下將這些惡意程式執行在瀏覽器上。

常見的 XSS 攻擊出現在:

  • 會呈現使用者搜尋的結果的搜尋欄位
  • 會顯示使用者所輸入資料的輸入欄位(input field)
  • 會回傳使用者提供文字的錯誤訊息
  • 隱藏使用者提供的資料的隱藏欄位
  • 任何會顯示使用者所提供的資料的頁面,例如 message boards 或 comments。在 input 欄位內輸入 <script>alert('my javascript here')</script> 看看是否有被執行。
  • HTTP Headers

常見的 XSS 類型可以分成下面幾類。

Reflected / DOM-based

  • 透過使用者所發出的請求在瀏覽器上呈現有害的內容
  • 在伺服器回應後,將有害的內容顯示在頁面上
  • 需要用到社交工程(social engineering)
  • 透過使用者授權瀏覽器的權限來執行一些功能
  • 常見情境像是攻擊者會寄送惡意連結給受害人,受害人點開連結後會進入到惡意的網頁,該網頁中的惡意程式會在受害者的瀏覽器中執行,以此竊取機密資訊等。

imgur

Stored or Persistent

  • 將有害的內容存在伺服器上的資料庫、檔案系統內等,在使用者稍後瀏覽頁面時呈現。
  • 不需要使用到社交工程。

Self XSS or Reflected XSS

Self XSS 通常是透過社交工程的方式,「讓你自己主動」去執行某段程式,例如把某段代碼複製貼上到瀏覽器執行。

Reflected XSS 則是讓使用者點擊某個網址後,透過 HTTP 回應的內容就加以執行攻擊。

防範 XSS

要防範 XSS 最基本的作法對於所有不信任來源的 input 都要以 encode 後的方式呈現在瀏覽器上,而不是直接讓他執行。

  • 將 HTML Body 和 Attribute 內的 HTML Entities 都進行編碼

Access Control Flow

沒有做好資料存取的管理,使得沒有該權限的使用者一樣可以存取到特定資訊。

Insecure Direct Object References (IDOR)

例如說,當你登入後可以使用 https://some.company.tld/app/user/23398 這個網址看到你自己的個人資料,但當你修改網址後的 :user_idhttps://some.company.tld/app/user/12345 時,卻發現即使不用使用他的帳號登入,一樣可以看到他的使用者資料。相似的,你可能也可以透過類似的方式來新增修改其他人的資料。

Missing Function Level Access Control

Insecure Communication

Insecure Login

在把敏感的資料傳送到伺服器前需要先進行加密處理,避免中間人攔截了可以被讀取的封包。除了透過在瀏覽器端自己先加密後再傳送到後端以外,最常見的是透過 HTTPS 將所有從 client 到 server 的內容都進行加密。

Insecure Deserialization

**序列化(Serialization)**指的是將物件轉換成可被儲存的資料格式的「過程」,序列化之後可以將資料儲存在儲存裝置(記憶體、檔案、資料庫),或者在通訊時傳送。**反序列化(deserialization)**則是用來重新建立該物件。當今最常用來序列化資料的檔案格式是 JSON,而在這之前則是 XML。

跨站偽造請求(Cross-site request forgery)

**跨站偽造請求(cross-site request forgery)**也稱為 one-click attacksession riding,通常縮寫為 CSRF(有時發音為 sea-surf) 或 XSRF,這是一種利用伺服器所信任的網站來發送惡意請求的攻擊;和 cross-site scripting (XSS) 不同,XSS 是透過在網站上輸入惡意程式碼的方式來進行攻擊,通常利用的是使用者對該網站的信任;而 CSRF 則是利用網站對該信用者的信任。

CSRF 通常有以下特徵:

  • 該網站需要使用者身份驗證
  • 利用該網站對使用者 ID 的信任
  • 欺騙使用者的瀏覽器來傳送 HTTP 請求到目標網站
  • 包含帶有 side effect 的 HTTP 請求

當使用者登入特定網站後,攻擊者可能會在使用者不知情的情況下透過瀏覽器送出 HTTP 請求,以此做出無預期的行為。透過 CSRF 攻擊有機會修改受害者的帳號密碼,或是進行消費。

現在多數的框架都支援去防範 CSRF 的攻擊,舉例來說,Angular 會去讀取 cookie 中的 XSRF-TOKEN 並將他設定在 HTTP header 的 X-XSRF-TOKEN。因為只有在你的 domain 下才能讀取到該 cookie,因此後端可以確定這個 HTTP 請求是從 client 送來,而不是攻擊者。

為了要這樣做,後端伺服器必須在 cookie 中設定一組 token,在每一個從 Angular client 傳送到後端 server 的請求中,都會帶上 X-XSRF-TOKEN 的 HTTP header。後端伺服器可以比對這兩組 token 是否相匹配,以確保伺服器和請求都在同一個 domain 執行。

Client Side

  • Bypass front-end restriction:直接從瀏覽器端透過修改 HTML 來避開表單驗證。
  • Client side filtering:不要把所有資料都吐到 client,而是只吐 client 有權限存取的資料。
  • HTML tampering:直接從瀏覽器端透過修改 HTML 來送出不同的表單內容。

問題解決

使用 ZAP 時無法監聽到 localhost

解法:透過 ZAP 內「探索您的應用程序」中選擇對應的瀏覽器後按下「啟動瀏覽器」:

Imgur

其他

參考