WebGoat-JWT
JWT Tokens 01
概念
本課程將介紹如何使用JSON Web Token(JWT)進行身份驗證,以及在使用JWT時需要注意的常見陷阱。
目標
教授如何安全地實現令牌的使用和這些令牌的驗證。
介紹
許多應用程序使用JSON Web令牌(JWT)來允許客戶機指示身份,以便在身份驗證后進行進一步交換
JWT Tokens 02
JWT TOKEN的結構
讓我們看看JWT令牌的結構。
令牌是base64-url編碼的,由header.claims.signature三部分組成。
基於該算法,將簽名添加到令牌中。通過這種方式,您可以驗證某人沒有修改令牌(對令牌的一次更改將使簽名無效)。
您可以使用下面的表單作為編碼和解碼JWT令牌的簡單方法。或者你可以在jwt.io上使用更廣泛的選項
JWT Tokens 03
身份驗證和獲得JWT令牌
獲取令牌的基本順序如下:
在此流程中,您可以看到在服務器返回的成功身份驗證中,用戶使用用戶名和密碼登錄。服務器創建一個新令牌並將其返回給客戶端。當客戶端對服務器進行連續調用時,它將新令牌附加到“Authorization”頭中。服務器讀取令牌,並在成功驗證后首先驗證簽名,服務器使用令牌中的信息來識別用戶。
Claims
令牌包含標識用戶的聲明和服務器滿足請求所需的所有其他信息。請注意不要將敏感信息存儲在令牌中,並始終通過安全通道發送它們。
JWT Tokens 04
JWT簽署
在將每個JWT令牌發送到客戶機之前,至少應該對其進行簽名,如果沒有對令牌進行簽名,客戶機應用程序將能夠更改令牌的內容。簽名規范在這里定義,你可以使用的具體算法在這里描述。基本上,你使用“HMAC with SHA-2 Functions”或“Digital Signature with rassa - pkcs1 -v1_5/ECDSA/ rassa - pss”函數來簽名令牌。
檢查簽名
一個重要的步驟是在執行任何其他操作之前驗證簽名,讓我們看看在驗證令牌之前需要注意的一些事情。
任務
嘗試更改您收到的令牌,並通過更改令牌成為管理員用戶,一旦您是管理員,重置投票
查看投票部分的源碼 /JWT/votings
直接判斷token中的admin對應值是否為true,沒有驗證簽名。
隨筆選一個用戶,將jwt進行解碼:
這里我們直接將admin改為true,算法改為none,然后把簽名部分刪除掉,再提交即可
JWT Tokens 05
JWT 爆破
使用帶有SHA-2函數的HMAC,您可以使用一個秘密密鑰來簽名和驗證令牌。一旦我們弄清楚了這個密鑰,我們就可以創建一個新令牌並對它進行簽名。因此,關鍵字要足夠強,這樣暴力破解或字典攻擊就不可行了。一旦有了令牌,就可以啟動離線暴力攻擊或字典攻擊。
任務
假設我們有以下令牌,嘗試找出密鑰並提交一個新密鑰,將用戶名更改為WebGoat。
eyJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJXZWJHb2F0IFRva2VuIEJ1aWxkZXIiLCJhdWQiOiJ3ZWJnb2F0Lm9yZyIsImlhdCI6MTYyOTg1ODIzMywiZXhwIjoxNjI5ODU4MjkzLCJzdWIiOiJ0b21Ad2ViZ29hdC5vcmciLCJ1c2VybmFtZSI6IlRvbSIsIkVtYWlsIjoidG9tQHdlYmdvYXQub3JnIiwiUm9sZSI6WyJNYW5hZ2VyIiwiUHJvamVjdCBBZG1pbmlzdHJhdG9yIl19.YRlFG7xUEjsdLoOiwu6LbocK7u0h8G-r13FvwweJdNI
查看源碼,發現secret就是隨機選擇了字典里的一個
把這幾個字符串放入字典,編寫腳本爆破
JWT Tokens 06
刷新令牌
介紹
在本節中,我們將討論刷新訪問令牌。
令牌的類型
通常有兩種類型的令牌:訪問令牌和刷新令牌。訪問令牌用於對服務器進行API調用。訪問令牌的生命周期有限,因此需要使用刷新令牌。一旦訪問令牌不再有效,就可以向服務器發出請求,通過呈現刷新令牌來獲得新的訪問令牌。刷新令牌可能會過期,但它們的生命周期要長得多。這解決了用戶必須再次使用其憑證進行身份驗證的問題。是否應該使用刷新令牌和訪問令牌取決於情況,下面是在選擇使用哪個令牌時需要記住的幾點。
一個正常的流程可以是這樣的:
服務器返回:
如你所見,刷新令牌是一個隨機字符串,服務器可以跟蹤(在內存中或存儲在數據庫中),以便將刷新令牌與被授予刷新令牌的用戶匹配。因此,在這種情況下,只要訪問令牌仍然有效,我們就可以稱之為“無狀態”會話,服務器端沒有設置用戶會話的負擔,令牌是自包含的。當訪問令牌不再有效時,服務器需要查詢存儲的刷新令牌,以確保令牌不會以任何方式被阻塞。
每當攻擊者獲得訪問令牌時,它只在一定的時間內有效(比如10分鍾)。然后,攻擊者需要刷新令牌來獲得新的訪問令牌。這就是刷新令牌需要更好保護的原因。也可以使刷新令牌無狀態,但這意味着要查看用戶是否撤銷令牌將變得更加困難。在服務器完成所有驗證之后,它必須向客戶機返回一個新的刷新令牌和一個新的訪問令牌。客戶端可以使用新的訪問令牌進行API調用。
你應該檢查什么?
無論選擇何種解決方案,都應該在服務器端存儲足夠的信息,以驗證用戶是否仍然受信任。你能想到的很多事情,就像存儲的ip地址,跟蹤刷新令牌使用的次數(在訪問令牌有效的時間窗口內,使用多次刷新令牌可能表示奇怪的行為,你可以撤銷所有的token,讓用戶再次進行身份驗證)。也要跟蹤訪問令牌屬於哪個刷新令牌,否則攻擊者可以通過自己的刷新令牌得到不同用戶的新的訪問令牌(詳細見https://emtunc.org/blog/11/2017/jwt-refresh-token-manipulation/ )。同時,檢查用戶的ip地址或地理位置也是有用的。如果您需要給出一個新的令牌,首先檢查位置是否仍然相同,如果不是,撤銷所有令牌,並讓用戶再次進行身份驗證。
Refresh Tokens的必要性
在現代單頁應用程序(SPA)中使用刷新令牌有意義嗎?正如我們在關於存儲令牌的章節中所看到的,有兩種選擇:web存儲或cookie,這意味着刷新令牌就在訪問令牌旁邊,所以如果訪問令牌泄漏,刷新令牌也有可能被泄露。當然,大多數時候是有區別的。訪問令牌在您進行API調用時發送,刷新令牌僅在應該獲得新訪問令牌時發送,而在大多數情況下,新訪問令牌是不同的終端。如果您最終在同一台服務器上,您可以選擇只使用訪問令牌。
如上所述,使用訪問令牌和單獨的刷新令牌可以讓服務器避免反復檢查訪問令牌。只在用戶需要新的訪問令牌時執行檢查。當然可以只使用訪問令牌。在服務器上存儲與為刷新令牌存儲的信息完全相同的信息,請參見前面的段落。通過這種方式,您需要每次檢查令牌,但這可能是合適的,取決於應用程序。在存儲刷新令牌用於驗證的情況下,保護這些令牌也很重要(至少使用散列函數將它們存儲在數據庫中)。
JWT是個好主意嗎?
有很多文章質疑使用JWT令牌進行客戶端到服務器的cookie身份驗證的用例。使用JWT令牌的最佳位置是服務器與服務器之間的通信。在一個普通的web應用程序中,你最好使用普通的舊cookie。
JWT Tokens 07
刷新令牌
實現刷新訪問令牌的良好策略非常重要。這個任務是基於Bugcrowd上一個私人漏洞懸賞程序發現的漏洞,你可以在這里閱讀完整的Write Up
任務
查看日志文件,找到讓Tom為這些書買單的方法
token是jwt形式的,進行解密
發現這是tom的token,但是是18年的,已經過期。
查看源代碼webgoat-lessons/jwt/src/main/java/org/owasp/webgoat/jwt/JWTRefreshEndpoint.java:
refresh token是通過RandomStringUtils.randomAlphabetic(20)獲取的隨機值,用於刷新過期的access token,但是沒有綁定用戶信息
下面的校驗也是僅校驗是否存在user和refreshToken,未校驗兩者對應關系,存在漏洞
所以可以用來刷新任何用戶的過期token。
那么,怎么來獲得一個refresh token呢
查看源代碼,我們發現編碼了一個靜態的password,並且存在一個登錄頁面/JWT/refresh/login,如果用戶為Jerry,密碼為bm5nhSkxCXZkKRy4的話就會登錄成功,然后新的token。
根據responsebody,我們構造請求包,這里知道是json格式:
通過checkout的請求包,將conten-type改為json,然后構造參數,即可獲得refresh token:
現在通過Jerry的refresh token來得到tom的新的access_token。
接口也在源碼中:
在/JWT/refresh/newToken接口,首先接收Authorization的值,進行jwt解碼,得到user,然后再從json中得到refresh_token,如果refresh_token有效,則刷新access_token:
現在就可以拿着刷新后的access_token 結賬了:
eyJhbGciOiJIUzUxMiJ9.eyJhZG1pbiI6ImZhbHNlIiwidXNlciI6IlRvbSJ9.a4yUoDOuv6L7ICs-HsE6craLHG_u6YDTkmXiGHjF7GdJZVZWCTurWBBunW9ujab8f4vNG31XAEvWYUEmAt0SGg
JWT Tokens 08
最后的挑戰
下面你會看到兩個賬戶,一個是Jerry,一個是Tome。Jerry想從Twitter上刪除Tom的賬戶,但是他的token只能刪除他自己的賬戶。你能幫他刪除Tom的賬號嗎?
點擊delete tom
我們是Jerry的token,沒有辦法刪除Tom
jwt解密后,發現header頭的kid參數,kid是jwt header中的一個可選參數,全稱是key ID,它用於指定加密算法的密鑰,通過修改它的值,我們可能可以進行任意文件讀取、SQL注入、命令執行等操作。
現在看一下源碼:
webgoat/jwt/JWTFinalEndpoint.java
/JWT/final/delete
首先得到請求參數里的token,如果token不為空的話,進行解析:
從Header中獲取“kid”直接插入sql查詢語句中,存在SQL注入,將返回結果進行了base64解碼作為KEY,然后進行解析。
然后解析后 ,username參數等於”Tom”,則刪除
PS:
主要是利用注入,關注點有以下幾個:
第一, select的值要是編碼之后的,因為源碼里取得值之后,有進行了base64解碼。
第二, username改為Tom
第三, exp過期時間一定要注意,我前面很快就改好了,但是提示“Not a valid JWT token, please try again",最后發現到過過期時間了,修改之后就可以了。
WebGoat-XXE
XXE-01
Concept:
This lesson teaches how to perform a XML External Entity attack is and how it can be abused and protected against.
Goals:
The user should have basic knowledge of XML
The user will understand how XML parsers work
The user will learn to perform a XXE attack and how to protected against it.
XXE-02
什么是XML實體:
XML Entity允許定義標記,這些標記將在解析XML文檔時被內容替換。 通常有三種類型的實體: 內部實體、外部實體、參數實體。
實體必須在文檔類型定義(DTD)中創建,例如:
正如可以看到的,一旦XML文檔被解析器處理,它將用已定義的常量“Jo Smith”替換已定義的實體js。這有很多好處,因為你可以在一個地方更改js,例如“John Smith”。
在Java應用程序中,XML可以用來從客戶端獲取數據到服務器,我們都熟悉JSON api,我們也可以使用XML來獲取信息。 大多數情況下,框架會基於xml結構自動填充Java對象,例如:
什么是XXE注入?
XML外部實體攻擊是針對解析XML輸入的應用程序的一種攻擊。當包含對外部實體引用的XML輸入由配置較弱的XML解析器處理時,就會發生這種攻擊。這種攻擊可能導致泄露機密數據、拒絕服務、偽造服務器端請求、從解析器所在機器的角度掃描端口,以及其他系統影響。
攻擊者可以使用file: 協議和系統標識符中的相對路徑,來包含本地文件,這些文件可能包含密碼或私人用戶數據等敏感數據。由於攻擊是相對於處理XML文檔的應用程序發生的,攻擊者可能使用這個受信任的應用程序來轉移到其他內部系統,可能通過http請求公開其他內部內容,或者對任何不受保護的內部服務發起CSRF攻擊。在某些情況下,容易出現客戶端內存損壞問題的XML處理器庫可能會被解除對惡意URI的引用所利用,可能允許在應用程序帳戶下執行任意代碼。其他攻擊可以訪問本地資源,這些資源可能不會停止返回數據,如果太多的線程或進程沒有被釋放,可能會影響應用程序的可用性。
一般來說,我們分為以下幾種XXE攻擊:
經典:在這種情況下,外部實體包含在本地DTD中;
盲:響應中沒有輸出或錯誤顯示;
錯誤:嘗試在錯誤消息中獲取資源的內容
XXE-03
XXE例子
讓我們看一個XXE注入的例子,在前一節中,我們看到XML實體可以如下使用:
外部DTD聲明:
定義這些實體還可以在外部文件中定義另一個DTD,例如:
email.dtd可被定義如下:
XXE
如果XML解析器被配置為允許外部DTD或實體,我們可以用下面的代碼修改以下XML片段:
現在發生了什么?我們從本地文件系統定義了一個include, XML解析器將加載該文件,並在引用實體的地方添加內容。讓我們假設XML消息被返回給用戶,消息將是:
額外的文檔類型定義(DOCTYPE)總是可以添加到xml文檔中,如果啟用了解析器設置以允許處理外部實體,那么就為尋找XXE注入提供了一個良好的開端。
XXE-04
在這個作業中,您將向照片添加評論,當提交表單時,嘗試執行帶有XXE注入的評論。嘗試列出文件系統的根目錄。
解題:
源碼分析:
webgoat/xxe/SimpleXXE.java
createNewComment中,接收POST請求正文中的內容賦值給commentStr這個字符串對象。
調用 Comment 的parseXml(commentStr, secure)方法進行xml解析。
查看源碼文件Comments.java:
如下代碼,描述了parseXml如何處理commentStr。
可以通過設置XMLConstants的兩個屬性來禁用外部實體解析,默認的空字符串就是禁用,但是這里secure值為true,所以沒有禁用(SimpleXXE.java)
最后創建一個Unmarshaller對象。返回的值是xml經過unmarshal方法處理的值。由於unmarshal在執行過程中解析了XML,導致XXE注入
XXE-05
參考解決方案
本練習的目標是列出文件系統的根目錄。如果我們首先嘗試一個正常的帖子,我們看到以下請求:
這個網頁發出一個xhr請求來發布一個xml消息,之后評論就會顯示在評論部分。現在,讓我們試着像前一節中所示的那樣稍微更改一下請求:
因此,我們使用file:///來引用文件系統的根,而不是包含一個特定的文件。如果我們只是將其復制並粘貼到注釋文本框中,您將在響應正文中得到一個錯誤
這是由於JavaScript正在接收輸入。
創建以下請求:
第7行包含要使用注釋表單時在文本框中輸入的輸入。
要解決這個問題,您必須攔截完整的傳出請求,並用解決方案替換完整的正文
XXE-06
通過代碼審計找到XXE漏洞
現在我們知道了注入是如何工作的,讓我們看看為什么會發生這種情況。在Java應用程序中,默認情況下XML庫配置是不安全的,您必須更改設置。假設在代碼審計期間發現以下代碼片段:
問題:解析器易受攻擊嗎?
這段代碼定義了一個新的XmlMapper (ObjectMapper),它是一個用於讀寫xml和json的流行框架。如果我們再深入一層,我們會發現:
① 這是我們從上面的代碼(1)中調用的'構造函數'。
② 調用另一個“構造函數”並初始化XmlFactory的新實例
讓我們看一下XMLFactory的源代碼:
① 這是3中創建的新實例的“構造函數”定義
② 調用3中定義的另一個“構造函數”
在4這里,我們知道if (xmlIn==null)將不為真,因為如果我們查看頂部的聲明,我們創建了自己的實例XMLInputFactory.newInstance(),它不為空。這意味着我們有一個XML解析器,它在默認情況下不受XXE注入的保護。5和6中有趣的部分是嵌套在if語句中的額外保護,它們將起不到作用。
如果我們看看Spring Boot框架,例如它們是如何初始化相同的解析器的:
① 調用了一個可以安全的初始化解析器的方法
正如我們看到的,通過私有方法XMLInputFactory()顯式地定義了XMLInputFactory,該方法實際上為解析器設置了與最上面代碼中所示相同的屬性。
正如你所看到的,要確定解析器對注入是否安全並不容易,您必須深入研究代碼和庫,以確定解析器設置是什么。
XXE-07
Modern REST framework
在現代REST框架中,服務器可能能夠接受您作為開發人員沒有考慮過的數據格式。 因此,這可能會導致JSON端點容易受到XXE攻擊。
同樣的練習,但嘗試執行與第一次賦值中相同的XML注入。
解題:
改為xml格式
源碼分析:
webgoat/xxe/ContentTypeAssignment.java
代碼根據contentType判斷數據格式,之后xml的解析和XXE-04一樣,所以同樣存在XXE
XXE-08
參考解決方案
這個賦值背后的想法是,雖然應用程序看起來只接受JSON,但如果我們將消息體更改為XML,框架可能會處理它。 當你嘗試輸入評論時,請求體將是:
這是一個普通的json消息,讓我們試着改變請求的內容類型
這將導致以下異常:
根據XML解析器的不同,您可能會得到更詳細的錯誤消息,在這種情況下,消息有點神秘,這意味着我們沒有發送有效的XML。 例如,Jackson庫給出以下信息:
這個錯誤消息出現是因為我們請求體的內容仍然是json格式,所以如果我們攔截並更改json消息為xml消息:
返回錯誤消息:
解析器抱怨消息不是有效的xml消息,需要嵌入到comment標簽中:
現在不再報錯,如果在WebGoat中刷新頁面,發布的評論就會出現。
為了攻擊工作,我們需要發布:
在一些公司的網絡中,如果通過HTTP發送,一些網絡設備可能會完全丟棄這個payload。 在這種情況下,POST不會返回響應,並且終端永遠不會接收請求。 然而,這種保護的作用是有限的,因為相同的請求將在HTTPS設置中成功通過,而負載將被加密。
XXE-09
XXE DOS attack
使用同樣的XXE攻擊,我們可以對服務器執行DOS服務攻擊。 這種攻擊的一個例子是:
十億笑臉DOS攻擊
當XML解析器加載該文檔時,它看到它包含一個根元素“lolz”,其中包含文本“&lol9;”。然而,“&lol9;”是一個定義的實體,它擴展為一個包含十個“&lol8;”字符串。 每個“&lol8;”字符串都是一個被定義的實體,擴展為10個“&lol7;”字符串,以此類推。 在處理完所有實體擴展之后,這個小的(< 1 KB) XML塊實際上將占用幾乎3g的內存。
XXE-10
Blind XXE
在某些情況下,您將看不到輸出,因為盡管您的攻擊可能有效,但該字段不會反映在頁面的輸出中。 或者您試圖讀取的資源包含導致解析器失敗的非法XML字符。讓我們從一個例子開始,在這個例子中,我們引用一個外部DTD,我們在自己的服務器上控制它。
作為一個攻擊者,你可以控制WebWolf(這可以是你控制的任何服務器),例如,你可以使用這個服務器使用http://127.0.0.1:9090/landing來ping它
我們如何使用這個終端來驗證是否可以執行XXE?
我們可以再次使用WebWolf來創建一個名為attack.dtd的文件,包含以下內容:
現在提交表單,將xml改為:
現在在WebWolf瀏覽到“傳入請求”,你會看到:
所以有了XXE,我們可以ping我們自己的服務器,這意味着XXE注入是可能的。 因此,通過使用XXE注入,我們基本上能夠達到與一開始使用curl命令時相同的效果。
XXE-11
Blind XXE assignment
在前面的頁面中,我們展示了如何用XXE攻擊ping服務器,在這個任務中,嘗試創建一個DTD,它將從WebGoat服務器上傳文件secret.txt的內容到我們的WebWolf服務器。 您可以使用WebWolf來提供DTD。txt文件位於WebGoat服務器的這個位置,所以你不需要掃描所有的目錄和文件:
嘗試使用WebWolf登陸頁面上傳這個文件,例如:http://127.0.0.1:9090/landing?text=contents_file(注意:這個終端在你的完全控制之下)。一旦你獲得了文件的內容,將其作為一個新的評論發布在頁面上,你將解決這一任務。
源碼分析:
webgoat/xxe/BlindSendFileAssignment.java
源碼層面還是一樣的,只不過不返回我們的信息了。
這里有個if判斷不讓我們直接使用file協議進行直接讀取,所以要讀取只能借助dtd
解題:
test.dtd
遇到的問題:
第一次寫的時候是這種:
一直覺得沒啥問題,但是一直不成功,報錯說實體名詞必須緊跟在%后面
但是看了幾遍都沒啥問題,卡了挺長時間。
最后,看之前的筆記,說”實體的值中不能有 %, 所以將其轉成html實體編碼 % “
然后就可以成功了。
XXE-12
XXE 防御
為了防止XXE攻擊,您需要確保驗證從不受信任的客戶機接收到的輸入。在Java世界中,您還可以指示您的解析器完全忽略DTD,例如:
如果您不能完全關閉DTD支持,您也可以指示XML解析器忽略外部實體,例如:
驗證
為Content-type和Accept報頭實現正確的驗證,不要簡單地依賴框架來處理傳入請求。如果客戶端指定了一個合適的accept報頭,返回一個' 406/Not Acceptable。
各語言中推薦的禁用外部實體的方法: