資訊處理安全確保支援士(情報処理安全確保支援士) 2024年春季(令和6年) 下午問1解說 ── JWT的alg=none與API授權、WAF的暫定對策

· · 資訊處理安全確保支援士, 登錄資訊處理安全確保支援士, API, API安全, JWT, 認證, 授權, WAF, Log4Shell, 資訊安全, 漏洞, IPA

「因為驗證了JWT的簽章,所以使用者ID值得信任」

這種說法只對了一半。

資訊處理安全確保支援士考試 令和6年度(2024年度)春季 下午 問1,以智慧型手機呼叫的API為題材1。認證成功後會發出JWT,之後呼叫端會附上這個JWT,呼叫使用者資訊的取得・更新API。乍看之下,是很常見的架構。

然而,在診斷中發現了以下四項問題。

  1. 將JWT標頭的alg改成none,未簽章的JWT就能通過
  2. 保持使用正確的JWT,只把mid改成其他使用者ID,就能取得・更新他人的資訊
  3. 加上規格外的status=paid,免費使用者就會變成付費使用者
  4. 對電子郵件送達的4位數認證碼,可以不受限制地暴力破解

這四項看起來都是「認證周邊的漏洞」,但成因並不相同。權杖的完整性、物件層級授權、屬性層級授權、嘗試次數限制——這幾個各自獨立的邊界都被突破了。

後半還會加入另一個論點。一套廣泛使用的開放原始碼函式庫,被公開了一項可利用JNDI Lookup從外部執行程式碼的重大漏洞。當時既沒有修正版,也還沒有完整的WAF規則。在這段空窗期間,題目要問的是:該如何確認影響範圍、WAF該檢查哪裡、以及為什麼一開始要選「偵測」而不是「阻斷」。

本文將以官方公布的解答範例2與評分講評3為基礎,不只整理各設問的答案,還會進一步整理為什麼答案會是這樣,以及在實務上該把設計做到多嚴謹

問題全貌顯示認證碼、JWT、API授權、函式庫漏洞各階段被突破的信任邊界無嘗試次數限制允許alg=none信任midstatus=paidJNDI/LDAP/HTTP使用者應用程式4位數認證碼發出JWT使用者API記錄日誌有漏洞的函式庫外部程式碼執行取得/更新他人資料變更付費狀態

圖1:問題全貌。各階段各自被突破了不同的信任邊界。

1. 先講結論

  • RESTful API不具工作階段的特性稱為無狀態。但這不代表伺服器完全不保存資料庫或使用者狀態
  • 4位數認證碼共有1萬種。若每秒可嘗試10次,平均500秒就能成功。由於比有效期間10分鐘還短,光靠失效時間無法防範
  • alg=none的最低限度對策,是確認JWT標頭的alg不是NONE。不過在實務上,應在伺服器端固定允許使用的演算法
  • 即使JWT正確,也不能信任請求中的mid。應核對JWT內的使用者ID與mid是否一致,或是更安全地不接收mid、改由JWT決定目標對象
  • 新增status=paid,是把規格外的屬性也綁定到內部物件上的Mass Assignment問題。應把更新用DTO做成許可清單,不讓使用者變更付費狀態
  • 暴力破解對策的解答是,連續失敗次數超過門檻值就鎖定帳號的處理。實務上還應搭配階段性延遲與依來源做的控制
  • 在確認新的重大漏洞影響範圍時,不使用破壞性命令,而是靠記錄是否有人存取測試伺服器的index.html,來確認外部程式碼執行是否成立
  • 由於攻擊字串放在HTTP標頭中,WAF的檢查對象是Header。因應大小寫互換的正規表示式,例如可用\W[jJ][nN][dD][iI]\W
  • 一開始把WAF設為「偵測」的優點是,能避免因誤判而中斷業務通訊。收到警示後查明是否為攻擊,調整完成後再轉為阻斷
  • WAF是暫定對策,根本對策則是把受影響的函式庫更新到修正版

2. 題材與設問對應

題目的背景是正準備新推出健康照護服務的G公司。使用者透過智慧型手機應用程式輸入飲食、體重等資料,取得健康風險判定與飲食菜單建議。系統建置在雲端上,結合了API閘道、事件驅動處理、代管資料庫。

題目原文中的產品名稱與服務名稱都經過抽象化處理。本文同樣不轉載IPA的圖表與文字,只改寫理解設問所需的結構。

設問 主題 本文對應章節
設問1 RESTful API的性質 第4章
設問2(1) 4位數認證碼的暴力破解時間 第5章
設問2(2) JWT的alg=none 第6章
設問2(3) 利用mid存取他人資料 第7章
設問2(4) 接受規格外status的缺失 第8章
設問2(5) 暴力破解對策 第9章
設問3(1) 以安全的方式確認漏洞是否存在 第11章
設問3(2)(3) WAF的檢查位置與正規表示式 第12章
設問3(4) 偵測模式的優點與運用 第13章

評分講評指出,整體的正確率算是中等水準。另一方面,設問2(2)的JWT竄改對策,以及設問3(1)驗證用伺服器所需機制,則被指出正確率偏低。這兩題光是知道專有名詞並不能作答,必須追蹤攻擊者變更了哪個值、這個值流向了哪個處理、又在何處被錯誤地信任了

3. 這道題目不只是「認證的問題」

把整個問題依信任邊界排列,會呈現如下的結果。

[使用者ID・密碼]
          |
          v
[確認4位數認證碼] ---- 無嘗試次數限制 ----> 暴力破解
          |
          v
[發出JWT]
          |
          v
[JWT函式庫] ------- 允許alg=none ------> 竄改使用者ID
          |
          v
[使用者API]
    |             |
    |             +-- 整包傳遞status ----> 屬性層級授權缺失
    |
    +-- 信任mid ------------------------> 物件層級授權缺失

[將外部輸入記錄到日誌]
          |
          v
[有漏洞的函式庫] ---- JNDI/LDAP/HTTP ------> 外部程式碼執行

這裡最重要的是以下的區別。

確認項目 要問的問題 本題中被突破的例子
認證 你是誰 4位數認證碼的暴力破解
權杖驗證 該身分資訊是否遭到竄改 alg=none
物件層級授權 是否可以存取該使用者的資料 替換mid
屬性層級授權 是否可以變更該項目 status=paid
輸入到執行的邊界 外部輸入是否會被解讀成命令 JNDI Lookup

前一項確認成功,不代表可以省略下一項確認。 就算是持有正確JWT的使用者,也不代表可以讀取他人的資料。就算是能更新自己資料的使用者,也不代表可以連付費狀態都一併變更。

只要能做出這樣的分層,各設問的答案就不再只是死記硬背。

認證與授權的差異認證用來確認主體身分,授權用來確認該主體被允許執行的操作認證是誰授權可以做什麼

圖2:認證與授權的差異。認證在先,授權則是另一項獨立的確認。

4. 設問1 ── 什麼是無狀態(Stateless)

設問1問的是RESTful API設計原則之一,也就是不進行工作階段管理的特性。

解答是 無狀態(Stateless)

所謂無狀態,是指伺服器即使不記得上一個請求的對話狀態,每個請求本身也具備處理所需的全部資訊。在本題中,智慧型手機應用程式會在每個請求的Authorization標頭中附上JWT。伺服器驗證這個JWT,藉此識別該請求的使用者。

容易產生的誤解,是把無狀態理解成「伺服器完全不保存任何狀態」。實際上,以下這些狀態通常都會保存。

  • 儲存使用者資訊與健康資料的資料庫
  • 付費狀態
  • 認證碼的值、有效期限、失敗次數
  • JWT的簽章金鑰
  • 若採用失效清單的設計,則包含該失效資訊
  • 日誌與稽核記錄

不保存的是,不把「僅為了延續對話而存在的伺服器端工作階段狀態」當作每次API呼叫的前提

此外,無狀態並不會自動提升安全性。每次都傳送JWT確實有助於水平擴展,但只要JWT的驗證出錯,錯誤也會平均擴散到所有節點。架構上的特性,與安全性上的正確與否,是兩回事。

5. 設問2(1) ── 4位數認證碼平均500秒就會被猜中

認證API只要使用者ID與密碼相符,就會將4位數的數字寄送到電子郵件。之後,只要使用者ID與4位數認證碼一致,就會發出JWT。認證碼從產生起10分鐘內有效。

在診斷中,每秒可以嘗試10次。題目要求的是平均需要幾秒才能被突破。

計算方式是「候選數的一半」

4位數的數字,含開頭為0的情況在內,共有以下1萬種。

0000, 0001, 0002, ... , 9999

若正確答案是均勻隨機選出,依序不重複嘗試的攻擊者,平均要花候選數的一半次數才能命中正確答案。

平均嘗試次數 = 10,000 ÷ 2 = 5,000次
平均所需時間 = 5,000 ÷ 10次/秒 = 500秒

因此,空格b的答案是 500

最壞情況下需要1,000秒,但題目問的是平均值。而認證碼的有效期間是600秒,比平均突破時間500秒還長,這就是被判定為「很有可能被突破」的原因。

4位數認證碼的時間感受對1萬種候選以每秒10次的速度嘗試,平均5,000次、500秒就會命中,短於有效期間600秒平均 10,000 / 2 = 5,000次500秒 小於 600秒候選數 10,000種平均突破時間 500秒有效期間 600秒有效期間內可被突破

圖9:4位數認證碼的時間感受。平均只需嘗試候選數的一半,就會在有效期間內命中。

光縮短失效時間,若候選數量太少仍然沒有意義

認證碼的強度,並非只由位數或只由有效期間決定。

有效期間內可嘗試的次數
= 每秒可嘗試次數 × 有效期間
= 10 × 600
= 6,000次

若依序嘗試不重複的數值,就能在有效期間內驗證1萬種組合中的60%。就算設有失效時間,只要沒有限制嘗試次數,防護就不夠充分。

現行的NIST SP 800-63B要求頻外驗證(out-of-band authentication)所使用的短期密碼至少要有6位數,若熵值低於64位元則必須有嘗試次數限制。此外也要求不要用電子郵件做為頻外驗證的管道4。考試是在題目給定的4位數・郵件寄送這項規格前提下作答,但在實務上進行新設計時,連這個前提本身都應該重新檢討。

6. 設問2(2) ── alg=none是「讓攻擊者自行選擇驗證方式」的問題

題目中的JWT由標頭、酬載、簽章三個部分組成。

base64url(header).base64url(payload).base64url(signature)

標頭中記錄了簽章所使用的演算法RS256。酬載中則放有使用者ID、發行時間、有效期限等資訊。

診斷人員變更了以下兩處。

  1. 把標頭的algRS256改成NONE
  2. 把酬載中的使用者ID改成另一名使用者

送出這個JWT後,驗證竟然成功,因而能冒充他人。

JWT alg=none 攻擊流程從正確的JWT把alg改成none,並改寫使用者ID後成功通過驗證的流程把標頭的alg改成none略過簽章驗證正確的JWTalg=RS256user=user01遭竄改的JWTalg=noneuser=user02伺服器接受為user02

圖3:JWT alg=none 攻擊流程。攻擊者自行選擇了驗證演算法。

none並不是拼字錯誤

RFC 7519中定義了algnone、既無簽章也無加密的「Unsecured JWT」5。因此,none這個值在規格上並非完全不存在。

問題在於,原本應該只接受有簽章JWT的API,卻接受了攻擊者指定的none

若用概念性的方式描述這段有漏洞的處理,會是下面這樣。

1. 讀取JWT標頭
2. 依標頭中記載的alg選擇驗證方式
3. 若alg為none,就不進行簽章驗證
4. 信任酬載中的使用者ID

這等於是讓攻擊者能透過自己能控制的輸入,來選擇安全強度本身。

考試的解答

設問要求分別以20字以內回答,修正後的函式庫Q「針對哪個資料、進行什麼樣的驗證」。

解答範例如下。

項目 解答要點
驗證對象的資料 JWT標頭中alg所指定的值
驗證內容 驗證其值不是NONE

就題目原文中漏洞的直接修正而言,這樣的答案是正確的。

實務上不能只做到「不是NONE就好」

這裡必須把考試的解答與實務上建議的做法區分開來。

RFC 8725要求JWT函式庫要讓呼叫端指定一組允許使用的演算法集合,並且不得使用集合以外的演算法6。也就是以下這種思維方式。

不好的思維方式:
  只要 token.header.alg != "none" 就接受

好的思維方式:
  只有包含在 serverConfig.allowedAlgorithms 中時才接受
  例如: allowedAlgorithms = ["RS256"]

就算只拒絕none,仍可能殘留其他較弱的演算法,或是把公開金鑰方式與共用金鑰方式搞混的演算法混淆問題。原則應該是不要一直用否定條件去增加拒絕清單,而是把允許的條件收窄並固定下來

JWT的驗證不只是演算法,還應視用途至少確認以下項目。

項目 確認內容
簽章 是否能用預期的金鑰與演算法完成驗證
iss 是否為信任的發行者
aud 是否為針對這個API所發出的權杖
exp 是否在有效期限內
nbf 是否還沒到可使用的起始時間
sub或使用者ID 是否為應用程式上有效的主體
權杖種類 是否把ID權杖與存取權杖等搞混

本題中酬載的鍵值名稱是user,但實務上應使用標準的sub,或明確定義自訂宣告(claim)的意義。

安全的JWT驗證與危險的JWT驗證危險的驗證方式依賴alg,安全的驗證方式則使用伺服器端的許可清單安全的驗證伺服器設定的許可演算法例如 RS256確認JWT標頭的alg是否包含在許可清單中驗證簽章・iss・aud・exp危險的驗證讀取JWT標頭的algalg為none就接受

圖4:安全的驗證與危險的驗證。實務上應把允許的演算法收窄並固定下來。

Base64url不是加密

還有一個JWT常見的誤解。標頭與酬載雖然是以base64url編碼表示,但這並不是加密,任何人都可以解碼並讀取內容。

簽章所保證的,僅限於「在能正確驗證通過的前提下,內容自發行後未遭竄改」。這並不代表可以把想要保密的個人資訊放進有簽章的JWT酬載中。

7. 設問2(3) ── 即使JWT正確,只要改變mid就能讀到他人資料

接下來是一種不竄改JWT本身的攻擊。

使用者API會透過GET或PUT接收名為mid的使用者ID。共用模組P會從資料庫中取得・更新與該mid綁定的使用者資訊。

攻擊的架構很單純。

JWT的使用者ID: user01    ← 簽章正確的JWT
請求中的mid: user02      ← 攻擊者變更後的值

由於JWT的簽章正確,認證會成功。然而,API卻直接信任mid=user02,把user02的資訊回傳出去。

這正是OWASP API Security Top 10 2023中所說的 Broken Object Level Authorization(BOLA,物件層級授權缺失) 的典型案例。當使用者以自行指定的物件ID來存取資料時,每一次都必須確認是否具備該物件的授權7

BOLA攻擊使用正確的JWT,同時把請求中的mid改成另一個使用者IDJWT: user01mid: user02信任mid攻擊者使用者API從資料庫回傳user02的資訊

圖5:BOLA攻擊。認證通過了,卻沒有確認授權。

設問的解答

表5中的底線②,要求以40字以內回答應在共用模組P的呼叫處理中新增的處理內容。

解答範例如下。

驗證JWT中所含使用者ID是否與mid的值一致的處理

在共用模組P中進行驗證的好處是,GET與PUT兩者、以及日後其他呼叫P的API,都能輕鬆套用相同的授權機制。若把相同的比對處理複製到各個畫面或端點,總會有某處遺漏。

更安全的設計是不接收mid

如果API只需要取得・更新使用者自己的資訊,其實不需要從用戶端接收使用者ID。

GET /users/me
Authorization: Bearer <JWT>

在伺服器端,則從驗證過的JWT中取出主體。

principal = validateJwt(request.authorization)
userId = principal.subject
return repository.getUser(userId)

更新處理也是一樣。

principal = validateJwt(request.authorization)
input = validateProfileUpdate(request.body)
repository.updateProfile(
    userId = principal.subject,
    name = input.name,
    age = input.age
)

比對處理只要寫了就能防護。但如果採用不從外部接收目標ID的設計,就能從根本上減少「忘了寫比對處理」這類臭蟲本身的發生機率

若管理者需要操作他人的使用者資訊,則應如下方式區分開來。

PUT /users/me                  一般使用者用
PUT /admin/users/{userId}      管理者用

管理者用的端點應要求不同的權限、稽核日誌,必要時還要求重新認證。相較於「在一般使用者API上,只為管理者情境加一段例外處理」,這種做法能讓授權原則的邊界更清楚可見。

BOLA的防範方式不使用請求中的mid,改由JWT的主體決定或核對目標對象GET /users/me + JWT取得JWT的sub一致不一致沒有mid使用者API若有mid是否與sub一致回傳自己的資料拒絕並回傳403以JWT的sub查詢資料庫

圖6:BOLA的防範方式。不接收mid,或是與JWT的主體核對。

用一句話區分認證與授權

無論是考試還是實務,以下的說法都很有幫助。

  • 認證: 是誰
  • 授權: 這個人可以做什麼

JWT的簽章驗證成功,只代表「可以信任這個權杖所代表的主體」而已。「這個主體是否可以讀取user02」,必須另外確認。

8. 設問2(4) ── status=paid是屬性層級的授權缺失

使用者API的規格中,定義了以下這些做為更新用的參數。

mid   使用者ID
name  姓名
age   年齡

然而,診斷人員新增了規格中沒有的以下數值。

status=paid

結果,免費使用者的狀態就變成了付費使用者。

依題目原文所述,服務L並未驗證收到的參數,而是把全部參數都直接傳給共用模組P。P的設計是可以直接以此更新資料庫。

空格c的答案是 共用模組P

Mass Assignment新增了規格外的status=paid,並整個反映到內部物件上攻擊者新增status=paid自動綁定寫入資料庫API規格mid / name / age請求主體共用模組P付費狀態變更為paid

圖7:Mass Assignment。規格外的屬性被整個反映到內部物件上。

與BOLA的差異

前一章的mid替換,與這次的status新增看似相似,但要守護的粒度不同。

漏洞 攻擊者變更的項目 原本應確認的事項
替換mid 目標物件 這名使用者是否可以存取這筆使用者紀錄
新增status 物件內的屬性 這名使用者是否可以變更這個項目

在OWASP API Security Top 10 2023中,後者被歸類為 Broken Object Property Level Authorization(物件屬性層級授權缺失),先前的Mass Assignment也被納入這項分類中8

「把JSON原封不動放進實體」很危險

有漏洞的實作,概念上大致如下。

entity = repository.find(body.mid)
bindAllProperties(entity, body)
repository.save(entity)

就算畫面上只有nameage的輸入欄位,攻擊者仍然可以直接自行組出HTTP請求。UI上沒有的項目,並不是安全邊界。

安全的實作,會明確標示出可更新的項目。

input = parseExactSchema(body, fields = ["name", "age"])

entity = repository.find(authenticatedUserId)
entity.name = input.name
entity.age  = input.age
repository.save(entity)

這裡有兩個重點。

  1. 更新用的輸入型別中,只放入使用者可以變更的項目
  2. 對於規格外的未知項目,不要選擇忽略,盡量以錯誤方式予以拒絕

若默默忽略未知項目,雖然能掩蓋攻擊失敗的痕跡,卻也會漏看用戶端的實作錯誤或攻擊的徵兆。若沒有相容性上的理由,採用嚴格結構(schema)予以拒絕,反而更方便日後調查。

屬性層級授權更新用DTO只保留許可清單,未知的屬性一律拒絕結構驗證專用路徑更新用DTO(許可清單)nameage請求主體是否僅包含許可的項目更新實體的name/age回傳錯誤支付服務驗證過的通知更新status=paid

圖8:屬性層級授權。以許可清單限制可更新的項目,付費狀態則透過另一條路徑變更。

status只能由支付結果來變更

status=paid並不是使用者個人資料的一部分,而是從「伺服器端已確認支付成功」這項事實所導出的狀態。

使用者的個人資料更新
  -> 只能變更name / age

來自支付服務的驗證過通知
  -> 核對paymentId
  -> 防止重複處理
  -> 把status變更為paid

就算儲存在同一個資料庫欄位中,能變更它的權限與路徑仍然是分開的。若把內部實體直接拿來當作外部API的輸入型別,這條邊界就會消失。

9. 設問2(5) ── 暴力破解對策要把失敗次數當成狀態來保存

針對4位數認證碼的暴力破解,要以30字以內回答表5空格d應填入的處理。門檻值為10。

解答範例如下。

連續失敗次數超過門檻值就鎖定帳號的處理

這一點與設問1的無狀態並不矛盾。「不把API呼叫的對話狀態當成伺服器工作階段來保存」,與「把安全性判斷所需的失敗次數持久化保存」是兩回事。

有無嘗試次數限制的差異沒有限制的話平均500秒就會被突破,加上失敗次數限制可以壓低攻擊速度有限制攻擊速度大幅下降帳號遭鎖定失敗10次就鎖定階段性延遲無限制約500秒認證成功每秒嘗試10次

圖10:有無次數限制。失敗次數限制能在實務上有效阻止暴力破解。

實務上不能只靠「永久鎖定」

帳號單位的次數限制雖然必要,但如果攻擊者知道他人的使用者ID,就能故意連續失敗10次,把正常使用者鎖在外面。因此,實務上應結合以下做法。

控制項目 作用
帳號單位的失敗次數 阻止對單一帳號的暴力破解
階段性等待時間 容忍正常使用者的輸入失誤,同時拖慢攻擊速度
來源IP・裝置・ASN等控制 抑制對大量帳號各嘗試少數幾次的攻擊
風險基礎判斷 對異於平常的地區・裝置・速度加強限制
通知使用者 讓使用者能察覺攻擊或誤操作
安全的復原程序 避免解鎖管道本身成為攻擊路徑

此外,重新寄送認證碼時,不應把失敗次數歸零。因為攻擊者只要每次呼叫重寄API,就能讓嘗試額度重新恢復。現行的NIST SP 800-63B也要求,即使產生了新的認證密碼,也不應重設失敗次數4

讓認證碼只能使用一次

題目原文以有效期限為主軸,但實務上還需要以下這些做法。

  • 驗證成功的認證碼要立即失效
  • 拒絕重複使用同一組認證碼
  • 不把認證碼本身留在日誌中
  • 讓回應內容無法從認證碼核對的成敗推斷出使用者是否存在
  • 認證碼寄送API也要設有次數限制

既然使用的是位數較短的密碼,就不能只靠隨機產生來確保安全性。

認證碼對策除了位數與失效時間之外,再加上嘗試次數限制、拒絕重複使用、通知等機制加以防護認證碼增加位數縮短有效期限嘗試次數限制成功後立即失效重寄不重設失敗次數不把認證碼留在日誌中依來源做控制

圖11:認證碼對策。除了位數與失效時間,還要結合嘗試控制與營運機制。

10. 用一張表區分設問2的四個問題

以下依攻擊者所控制的值,整理設問2中容易混淆的論點。

攻擊 攻擊者變更的值 不該信任的地方 根本對策
JWT竄改 JWT標頭的alg、酬載的使用者ID 權杖自行申報的驗證演算法 在伺服器端固定允許使用的演算法
取得他人資訊 請求中的mid 用戶端指定的目標ID 與JWT的主體核對,或由JWT決定目標ID
變更為付費使用者 規格外的status 自動綁定的所有屬性 把可更新的屬性做成許可清單
4位數認證碼被突破 otp的候選值 無限制的認證嘗試 加入失敗次數限制、延遲、風險判斷

重要的是,不要用「驗證輸入值」一句話把這些全部混為一談。

  • alg關係到加密處理的政策
  • mid關係到物件層級授權
  • status關係到屬性層級授權
  • otp關係到對線上猜測的抵抗力

即使同樣出現在一個HTTP請求中,要守護的理由並不相同。

11. 設問3(1) ── 在不造成破壞的前提下確認外部程式碼執行

服務上線後,一套廣泛使用的開放原始碼函式庫H被公開了一項重大漏洞V。題目原文中的流程如下。

  1. 攻擊者把含有JNDI Lookup的字串放進HTTP標頭中送出
  2. 目標伺服器把這個值輸出到日誌
  3. 有漏洞的函式庫解析JNDI Lookup,向攻擊用的LDAP伺服器發出查詢
  4. LDAP的回應中回傳攻擊用HTTP伺服器的URL
  5. 目標伺服器取得類別檔案,並執行命令

這可以視為隱去了具體產品名稱的 Log4Shell(CVE-2021-44228)型攻擊。Apache的官方說明中也提到,若攻擊者能控制日誌訊息或參數,就能利用這項漏洞執行從LDAP伺服器載入的任意程式碼9

Log4Shell型漏洞確認流程以無害的回呼確認從JNDI到外部程式碼執行的連鎖是否成立在x-api-version中注入jndi:ldap://...JNDI Lookup回應HTTP URL記錄GET請求確認到達攻擊者有漏洞的伺服器日誌處理惡意LDAP伺服器惡意HTTP伺服器index.html測試伺服器存取日誌確認漏洞

圖12:Log4Shell型漏洞確認流程。不使用破壞性命令,而是靠HTTP存取記錄來確認是否到達。

驗證程式碼只會產生無害的HTTP存取

G公司執行不會對系統造成影響的驗證程式碼,確認是否能從外部利用漏洞V。驗證程式碼所執行的命令,僅僅是取得測試伺服器的index.html而已。

設問3(1)問的是,要在測試伺服器上實作什麼,才能確認命令已經被執行。

解答範例如下。

記錄並確認是否有人存取測試伺服器index.html的機制

只要Web伺服器的存取日誌中留下來自目標伺服器的GET紀錄,至少就能確認以下這一連串連鎖成立了。

外部HTTP請求
  -> 日誌處理
  -> JNDI Lookup
  -> LDAP回應
  -> 取得類別檔案
  -> 執行驗證命令
  -> 存取測試伺服器

為什麼「在畫面上顯示文字」還不夠

攻擊目標是伺服器。使用者瀏覽器畫面不一定會出現變化。此外,即使漏洞確實存在,途中的對外連線也可能被防火牆擋下。

只要在測試伺服器端記錄存取情形,就能成為「目標伺服器已對外連線到達」這項可觀測的證據。

在實務上進行同類驗證時,務必遵守以下事項。

  • 取得目標系統擁有者的明確同意
  • 採用不會影響正式環境、或影響程度可接受的驗證方式
  • 不使用寫入・刪除・變更設定等破壞性命令
  • 驗證用的網域與伺服器由自己公司管理
  • 記錄驗證時間、來源、目標、預期的回呼
  • 驗證結束後撤除臨時建立的LDAP・HTTP伺服器與憑證

「確認任意程式碼執行(是否可行)」與「實際執行任意危險的程式碼」並不相同。應以能達成目的的最小副作用來進行。

12. 設問3(2)(3) ── WAF要檢查的是HTTP標頭

服務N的WAF,可以從GET、POST、PUT、ANY、Header、COOKIE、Multipart之中選擇檢查對象。

攻擊程式碼放在名為x-api-version的HTTP標頭值中。因此,表6的空格e與f,答案都是 Header

直接把題目原文中寫明的位置,對應到WAF的檢查對象

這一題與其說是考一般知識,不如說是考能否讀懂題目原文中的資料流程。

攻擊字串放置的位置:
  x-api-version標頭
          |
          v
WAF的檢查對象:
  Header

既不是GET參數,也不是POST主體。不要看著WAF的功能清單,憑「感覺像攻擊所以選ANY」來作答,而要回答題目原文中攻擊者放入數值的實際位置

因應大小寫互換

最初的方案,概念上是以下這樣的規則。

Header  \Wjndi\W  阻斷
Header  \Wldap\W  阻斷

但是,像jNdI這樣調換大小寫,就能規避只比對小寫的簡單模式。

設問3(3)的解答範例,是以下其中一種。

\W[jJ][nN][dD][iI]\W
\W(j|J)(n|N)(d|D)(i|I)\W

在題目冊子中,反斜線在日文環境的字型下有時會顯示成日圓符號,但就正規表示式而言指的是\W\W比對的是除了英數字與底線以外的字元。在JNDI Lookup的語法中,jndi的前後會出現${:等非單詞字元,所以連同這些一併納入比對範圍。

用同樣的思路,ldap那一側也可以做成忽略大小寫的形式。

\W[lL][dD][aA][pP]\W

不要把這條正規表示式當成「完整的Log4Shell對策」

考試問的是,針對題目原文中出現的規避手法,寫出對應的正規表示式。但在實際攻擊中,可能存在字串分割、其他Lookup、編碼、其他通訊協定等變形手法,光靠特徵碼很難完全涵蓋。

因此,在實務上的定位如下。

  1. 用WAF暫時擋下目前已知的攻擊模式
  2. 調查是否真的含有受影響的函式庫
  3. 限制對外的LDAP・RMI・不必要的HTTP通訊
  4. 更新到修正版
  5. 更新後仍要確認日誌,調查是否已遭入侵

WAF是在修正版推出之前,用來爭取時間的一層防護。

WAF的定位WAF是暫定緩解的層級,根本對策是更新到函式庫的修正版WAF規則偵測/阻斷限制對外通訊公開重大漏洞確認影響暫定緩解暫時阻止攻擊模式封鎖濫用路徑更新到修正版函式庫事後確認與再發防止

圖13:WAF的定位。WAF是在修正版推出前爭取時間的手段,根本對策仍是更新。

13. 設問3(4) ── 一開始使用「偵測」的理由

關於變更後的WAF規則,身為登錄資訊處理安全確保支援士的Z先生建議,在正式上線運作後的一段期間內,動作模式應設為「偵測」而不是「阻斷」。

設問要求分別以25字以內回答,選擇偵測模式的優點,以及為了將損害降到最低所應執行的內容。

解答範例如下。

項目 解答要點
優點 可以避免因誤判而遭到阻斷
應執行的內容 收到警示後,查明是否真的是攻擊

偵測模式不是「什麼都不做的模式」

在偵測模式下,符合規則的通訊仍會放行,但會記錄到日誌並發出警示。即使正常的API呼叫中偶然出現jndildap這樣的字串,也不會立刻中斷業務。

相對地,營運方需要做到以下事項。

收到警示
   |
   v
確認目標請求
   |
   +-- 正常通訊 -> 收窄規則、檢討例外條件
   |
   +-- 攻擊     -> 隔離目標、保全日誌、調查影響、轉為阻斷

如果沒有人查看警示,偵測模式就不會有任何防禦效果。偵測必須與觀察後再判斷的營運方式搭配成一組才有意義。

從偵測轉為阻斷的流程

一般的導入步驟如下。

  1. 以偵測模式套用到實際流量上
  2. 把誤判與正確偵測分類
  3. 調整目標標頭、路徑、API、字元邊界等
  4. 確認對正常通訊的影響在可接受範圍內
  5. 轉為阻斷模式
  6. 監控阻斷件數與業務影響

不過,這是平時的原則。若漏洞十分嚴重、正遭到實際濫用,又沒有替代手段時,也可能判斷入侵造成的損害會大於誤判導致的停擺,因而從一開始就選擇阻斷。在考試所設定的情境中,則是為了確認服務能否維持以往的可用性,才先選擇偵測模式。

WAF從偵測轉為阻斷在偵測模式下觀察警示,調整誤判之後再轉移到阻斷模式實際流量誤判攻擊偵測模式產生警示是攻擊還是誤判調整規則轉為阻斷模式監控阻斷件數與業務影響

圖14:從偵測轉為阻斷。先觀察・調整,確認影響在可接受範圍後再轉為阻斷。

14. WAF是暫定對策,更新才是根本對策

依題目原文所述,函式庫H的官方網站當時既沒有修正版,也沒有暫定對策,雲端業者要提供涵蓋範圍完整的WAF規則,最長也要72小時。因此G公司自行確認影響範圍,先針對已知的攻擊模式做暫時性的防護。

這樣的順序,正是事故應變的基本型態。

階段 目的 本題中的對應做法
確認影響 判斷自家公司是否真的處於危險之中 用無害的回呼確認是否能從外部濫用
暫定緩解 為修正爭取時間 WAF規則、偵測・阻斷、限制對外通訊
根本修正 消除有漏洞的原因 更新到修正版函式庫
事後確認 調查是否已經遭到濫用 調查WAF・應用程式・DNS・代理伺服器等日誌
再發防止 加快下一次的判斷速度 盤點相依關係、SBOM、更新程序、聯絡管道

「不知道自己有沒有用到」是最大的延遲來源

依題目原文所述,即使G公司向F公司詢問是否有使用函式庫H,由於需要詳細的組態分析,回覆也要花上不少時間。

在實務上,若是等到重大漏洞公布之後才開始尋找JAR檔案,應變就會來不及。至少應在平時就準備好以下事項。

  • 直接相依與遞移相依的清單
  • 發布物中實際包含的元件與版本
  • 部署到哪些服務・容器・裝置上
  • 更新相依函式庫並重新建置・重新發布的程序
  • 核准緊急變更的聯絡管道
  • 對外通訊的允許目的地,以及停用時的影響
  • 日誌的保存位置與搜尋方法

SBOM本身不是目的,而是用來在短時間內回答「這個漏洞會影響哪些正在執行的系統」的索引

不能只做完更新就結束調查

在漏洞公布前後,系統有可能早已遭受攻擊。就算更新到修正版、阻止了今後的濫用,已經外洩的憑證或已被植入的後門並不會因此消失。

若是Log4Shell型漏洞,至少應調查以下這些面向。

  • 包含JNDI或LDAP等可疑字串的HTTP請求
  • 從應用程式伺服器發往外部LDAP・RMI・HTTP的通訊
  • 異於平常的子處理程序啟動
  • 建立可疑的JAR、class、指令碼、執行檔
  • 存取雲端憑證或環境變數
  • 更新前後的認證・權限變更・對外傳送

重要的是,不能只憑WAF日誌就斷定「沒有遭受攻擊」。因為還存在不經過WAF的內部路徑,以及過去未曾保存下來的日誌。

15. 有助於在考試中拿分的閱讀方式

這道題目與其說是知識題,不如說是考驗能否讀出規格與實作之間差異的題目。

15.1 把表中的「規格」與「實作」分開來看

status的問題中,API規格中沒有的值,卻在實作中被放行了。

規格:
  mid / name / age

實作:
  把收到的參數全部送進P

只要能看出這個落差,就能明白空格c的答案是共用模組P。

15.2 在攻擊者變更的值上劃線標記

各項攻擊中被變更的值如下。

  • JWT標頭的alg
  • JWT酬載的使用者ID
  • API參數的mid
  • 規格外的status
  • 認證API的otp
  • HTTP標頭的x-api-version

幾乎每一道設問,問的都是「這個值應該在哪裡被驗證」。

15.3 把解答還原成題目原文的用語

在實務上,可以直接稱之為「BOLA」「Mass Assignment」「rate limiting」。但設問要求的,是配合題目原文架構所寫出的具體處理內容。

不好的例子:

妥善進行授權。

好的例子:

驗證JWT中所含的使用者ID是否與mid的值一致。

不好的例子:

做好暴力破解對策。

好的例子:

連續失敗次數超過門檻值就鎖定帳號。

光是知道抽象名稱,並不能寫出符合字數限制、可供評分的答案。

15.4 WAF要追蹤的是「攻擊字串放進了哪裡」

WAF的檢查對象,不是從攻擊種類去猜測,而是要依攻擊字串存放的位置來決定。

放進了x-api-version標頭
        ↓
檢查對象是Header

評分講評中提到設問3(1)正確率偏低,原因也在於很多答案不符合圖6的攻擊流程。只要試著把攻擊步驟用箭頭重新畫出來,就能看清該觀察的重點是什麼。

16. 實務API審查中可用的檢查清單

以下是能把這道題目應用到實際設計・程式碼審查中的檢查清單。

JWT驗證

  • 允許使用的簽章演算法已在伺服器設定中固定下來
  • 拒絕none或非預期的演算法
  • 視用途驗證簽章、issaudexpnbf
  • 不會把ID權杖、存取權杖、更新權杖搞混
  • 有金鑰輪替與失效時的處理程序
  • 沒有把應保密的資訊放進JWT酬載

物件層級授權

  • 就算變更請求中的ID,也無法到達他人的資料
  • 清單、詳細資訊、更新、刪除、下載,每一項都有做授權檢查
  • 授權是在存取資料的共用層執行,而不是在畫面層
  • 對於本人專用的API,已考慮是否能從權杖導出目標ID
  • 管理者用的操作與一般使用者用的API,授權原則已分開

屬性層級授權

  • 外部輸入型別與資料庫實體已經分開
  • 可更新的項目已用許可清單列舉出來
  • 規格外的屬性會被拒絕或被記錄稽核
  • 權限、付費、核准、擁有者等狀態,無法透過使用者輸入來變更
  • 回應內容中也沒有包含不必要的機密屬性

認證嘗試

  • 有帳號單位的失敗次數限制
  • 有階段性延遲或依來源做的控制
  • 重新發出認證碼時不會重設失敗次數
  • 認證碼只能使用一次
  • 不會把認證碼或密碼留在日誌中
  • 解鎖・復原程序不會變成另一條較弱的認證路徑

重大的相依函式庫漏洞

  • 能將正在執行的服務與相依版本互相對應
  • 有以無害方式驗證影響範圍的程序
  • 能套用WAF或限制對外通訊等暫定對策
  • 有負責人確認偵測警示的營運機制
  • 有能緊急發布修正版更新的路徑
  • 會在更新前透過日誌調查是否已遭濫用

17. 從這道題目看見共用元件的兩面性

這道題目中出現了JWT管理函式庫Q與共用模組P這兩個共用元件。

共用元件有很大的優點。

  • 只要修正一個地方,就能把修正套用到所有使用它的API上
  • 不必在各個功能中重複實作授權或驗證邏輯
  • 能夠集中測試對象
  • 能統一日誌與稽核的格式

但另一方面,錯誤也會擴散到整體。

  • 只要函式庫Q接受alg=none,所有使用JWT的API都會陷入危險
  • 只要共用模組P接受任意的midstatus,GET與PUT兩者都會陷入危險
  • 只要基礎架構中使用了有漏洞的函式庫H,所有把HTTP標頭輸出到日誌的路徑都會變成攻擊面

因此,應該共用化的並不只是單純的資料存取。必須把安全性不變條件也共用化,並對這個共用元件單獨進行嚴格驗證

例如,可以把P的介面約定設計成以下這樣。

P.getOwnUser(authenticatedSubject)
P.updateOwnProfile(authenticatedSubject, ProfileUpdate{name, age})

像下面這種低階API,不要直接公開給一般呼叫端使用,會比較安全。

P.getUser(arbitraryMid)
P.updateUser(arbitraryMap)

真正需要用到後者的,只有管理處理等有限的路徑。若把這種低階的自由度分配給所有API,就等於是一種「每次都寄望呼叫端能正確使用」的設計。

18. 總結

令和6年度春季下午問1,是一道要求把API安全的各項論點一一分離來解讀的題目。

使用JWT,不代表就是安全的認證。只要讓攻擊者選擇簽章演算法,使用者ID就可能被竄改。

JWT的簽章正確,不代表授權也是正確的。只要信任請求中的mid,正常使用者就能存取他人的資訊。

能更新自己的物件,不代表所有屬性都可以任意變更。只要像status這樣的內部狀態被自動綁定,權限或付費狀態就可能被竄改。

認證碼有有效期限,不代表能抵禦暴力破解。必須計算候選數量與嘗試速度,並限制失敗次數。

在WAF中加入規則,不代表漏洞已經修正。應透過偵測與阻斷爭取時間、確認影響範圍,最終仍要更新函式庫。

漏洞與對策對應表把各項漏洞對應到相應的信任邊界與對策權杖驗證物件層級授權屬性層級授權認證嘗試控制輸入到執行JWT竄改固定允許的演算法替換mid核對JWT主體/mid非必要status=paid把更新DTO做成許可清單4位數認證碼暴力破解失敗次數限制・延遲Log4Shell型漏洞更新函式庫/WAF

圖15:漏洞與對策對應表。依被突破的邊界分別列出對策。

貫穿這道題目的原則只有一個。

不要因為前一項驗證成功,就把它當成省略下一道信任邊界的理由。

本系列的前兩篇文章,解說了令和5年秋 下午問1的儲存型XSS,以及令和5年秋 下午問2從訪客用Wi-Fi外洩資料的案例。若想確認整個網站的檢查觀點,也請參考把IPA《安全的網站製作方式》當作檢查清單來用

最終總結顯示即使前一項驗證成功,也不能省略下一道信任邊界認證成功JWT簽章驗證物件層級授權屬性層級授權嘗試次數限制輸入到執行的邊界WAF/函式庫更新

圖16:最終總結。信任邊界應逐階段確認,不可省略任何一個。

參考連結

</content>

  1. IPA, 令和6年度春季 資訊處理安全確保支援士考試 下午 試題冊。本文所討論的即為此份題目。 

  2. IPA, 令和6年度春季 資訊處理安全確保支援士考試 下午 解答範例。各設問的官方解答範例。 

  3. IPA, 令和6年度春季 資訊處理安全確保支援士考試 下午 評分講評。說明正確率與常見錯誤答案的傾向。 

  4. NIST, SP 800-63B: Authentication and Authenticator Management。說明了短期密碼的位數、嘗試次數限制、重新發行時的失敗次數、不將電子郵件用於頻外驗證等規範。  2

  5. RFC Editor, RFC 7519: JSON Web Token (JWT)。定義JWT規格,其中包含Unsecured JWT與alg=none。 

  6. RFC Editor, RFC 8725: JSON Web Token Best Current Practices。規範了固定允許演算法、驗證發行者・主體・audience等做法的最佳實務(BCP)文件。 

  7. OWASP, API1:2023 Broken Object Level Authorization。說明了必須針對使用者指定的每一個物件ID分別確認授權的必要性。 

  8. OWASP, API3:2023 Broken Object Property Level Authorization。說明了包含Mass Assignment在內的屬性層級授權缺失與對策。 

  9. Apache Logging Services, Security。說明了CVE-2021-44228的影響範圍、透過JNDI與LDAP執行程式碼的方式,以及修正版本。 

共用相同標籤的最新文章。能以相近的主題延伸理解。

資訊處理安全確保支援士(情報処理安全確保支援士) 2023年秋季(令和5年)午後問1解說 ── 16則評論卻只顯示2則的儲存型XSS

以資訊處理安全確保支援士考試2023年秋季(令和5年)午後問1為題材,解說儲存型XSS的攻擊流程。整理輸入字數限制被分段投稿突破的原因、工作階段 ID 在未經外部通訊的情況下以圖片形式被取出的路徑,以及當中真正發揮作用的因應對策。

與本文相近的主題頁面。以本文為起點,可進一步連到相關服務與其他文章。

本文連結到以下服務頁面,歡迎從最接近的入口查看。

常見問題

整理諮詢這個主題時常見的問題。

為什麼JWT的簽章驗證明明成功,卻還能冒充他人?
在本題中,JWT管理函式庫會依攻擊者指定的方式接受JWT標頭中的alg,把alg=none的JWT當成未簽章卻仍屬正確的權杖來處理。因此,即使改寫酬載中的使用者ID,驗證依然會成功。考試的解答是,將驗證對象設為JWT標頭中的alg,並確認其值不是NONE。不過在實務上,只拒絕NONE並不夠。應在伺服器端的設定中,把允許使用的演算法固定為RS256等,不讓權杖自行申報的演算法直接用於選擇。此外還要視用途驗證issuer、audience、有效期限、subject等項目。
只要核對JWT內的使用者ID與請求中的mid,作為授權對策就足夠了嗎?
作為本題的解答已經足夠。在共用模組P中驗證JWT內含的使用者ID與mid是否一致,就能擋下指定他人mid的攻擊。不過,如果API只處理使用者本人的資訊,實務上更安全的做法是不從用戶端接收mid,而是依驗證過的JWT的subject來決定使用者ID。例如設計成GET /users/me或PUT /users/me,就能從根本上降低漏寫比對處理這類實作疏失發生的機率。管理者操作他人資料的API,則應拆分成不同的端點與授權原則。
新增status的攻擊,為什麼光靠一般的輸入值驗證無法防範?
因為即使驗證了name的長度或age的範圍,只要原本就不該接收的status被自動綁定並傳給內部物件,就無法防範這種攻擊。問題不在於值的格式,而在於該屬性是否允許由使用者變更,這是屬性層級的授權問題。更新用的輸入型別應只定義name與age,並拒絕未知的屬性。付費狀態則必須只能透過伺服器可信任的事件(例如支付服務回傳的成功結果)來變更。
4位數的認證碼10分鐘就會失效,為什麼還是很危險?
因為從0000到9999的候選只有1萬種,若每秒能嘗試10次,平均5,000次、也就是500秒就會命中。有效期間10分鐘等於600秒,如果依序嘗試不重複的候選,就能在有效期間內驗證6,000種組合。只靠失效時間無法阻止暴力破解,必須把候選數量、嘗試速度、嘗試次數上限一併納入設計。
設問的對策是鎖定帳號,但實務上只做即時鎖定就夠了嗎?
不夠。題目空格中應填入的是「連續失敗次數超過門檻就鎖定帳號的處理」,但如果只做固定式的永久鎖定,攻擊者就能刻意鎖定他人帳號,造成服務妨害。實務上應結合帳號單位的失敗次數、階段性等待時間、對來源與裝置的風險判斷、通知、復原程序。即使發出新的驗證碼,也不能把失敗次數歸零,這一點同樣重要。
把WAF設為偵測而非阻斷,意義何在?
意義在於,即使把正常字串誤判為攻擊,也不會因此中斷業務通訊。題目的解答中,好處是可以避免因誤判而遭到阻斷,應執行的內容則是收到警示後,查明是否真的是攻擊。偵測模式並不是放著不管的設定,而應把它當成一段觀察期間:確認日誌、排除誤判、調整規則,再轉為阻斷。若已知的重大漏洞正遭到實際濫用而情況緊急,也可能權衡可用性風險後,判斷應優先阻斷。
本題中的函式庫H是Log4j嗎?
題目原文隱去了具體產品名稱,不過JNDI Lookup、LDAP伺服器、從HTTP伺服器取得類別檔案、嵌入HTTP標頭的字串、CVSS v3.1偏高的基本分數——這一整套攻擊流程,很自然地可以視為對外界熟知的Log4Shell(CVE-2021-44228)所做的抽象化描述。本文會說明這層對應關係,但考試中不需要回答具體的產品名稱,僅憑題目給出的攻擊步驟與WAF規格就能作答。
從這道題目應該帶回實務的重點是什麼?
重點在於,認證成功、JWT未遭竄改、可以存取目標物件、可以變更目標屬性,這些全都是各自獨立的確認。此外,短位數的認證碼必須搭配嘗試次數限制,而重大的函式庫漏洞則需要同時推進影響確認、暫定防禦與根本修正。把授權集中到共用元件、把輸入結構做成許可清單、在伺服器端固定JWT的驗證條件、掌握相依函式庫並隨時保持可更新的狀態,是實務上的核心所在。

作者檔案

本文作者的個人檔案頁面。

Go Komura

小村軟體有限公司 代表

以 Windows 軟體開發、技術諮詢與故障調查為中心,在難以重現的故障調查與既有資產仍在運作的專案上具有優勢。

回到部落格一覽