關於我在 Glints 找到的高風險漏洞

2022-02-10 更新:修正標題為「關於我在 Glints 找到的高風險漏洞」,原標題是「我怎麼從 Glints 手中偷走你的履歷」

Glints 是一間總部位於新加坡的求職平台,2021 年年初的時候才剛拿到 6.5 億新台幣的投資,在台灣也有團隊負責徵才相關事項。

在 2021 年 7 月時我發現 Glints 有一個 bug bounty program,於是花了一些時間研究一下 Glints 的服務,最後找到了四個漏洞並且回報給了 Glints,這些漏洞可以造成的影響包括:

  1. 查看平台上每一個職缺的所有應徵者資料,包括應徵者的姓名、電話、生日、履歷以及 email
  2. 查看平台上所有 recruiter 的個人資料,包括姓名、職稱、所在的團隊以及 email

簡單來說,若是被有心人士利用這些漏洞,那基本上所有應徵者的個人資訊都會外洩。

接著,就讓我們來看看這些漏洞。

附註:文章所提及的漏洞在回報給 Glints 之後都已經修復。

# 1. 瀏覽職缺申請的權限控管不當,導致使用者資訊洩露

Glints 這個平台的使用者基本上分成兩種:雇主跟求職者,以目前來說任何人都可以直接在上面開一個雇主的帳號,只是職缺發布時還是會需要經由系統或人工審核,才會正式公開。

雇主有個後台可以管理職缺跟來應徵的求職者:

portal

而查看應徵者資訊的 API URL 是這樣:/api/recruiterats/jobApplications?where={"JobId":"55e137a1-f96e-4720-9b08-7eb2749e1557"}

API 返回的部分資訊長這樣:

{
"candidate": {
"id": "44007523-f7a8-411d-b2c4-57c68a976534",
"profilePic": "6f14ffc62f3f53d8dcb22a4bfc1da6c8.png",
"firstName": "Peter",
"lastName": "劉",
"email": "abc123@yopmail.com",
"phone": "+886-999999999",
"resume": "cc042b7400c444619adedf79a9c1daf3.pdf",
"salaryExpectation": null,
"currencyCode": null,
"recentJob": {
"title": "工程師"
},
"lastSeen": "2021-07-22T01:58:14.859Z",
"country": "Taiwan",
"city": "Taipei"
}
}

可以看到裡面有應徵者的姓名、電子郵件、電話跟履歷檔案名稱。

這時候我做了一件大家都會做的事情,就是把 JobId 換成別間公司的職缺,而 API 照常返回了資料:

idor

那我們要怎麼拿到其他職缺的 JobId 呢?很簡單,因為這都是公開的。只要去到給求職者的網頁,就可以直接在 API 或是網址上看到職缺的 ID,像這樣:https://glints.com/tw/opportunities/jobs/consultant/55e137a1-f96e-4720-9b08-7eb2749e1557

假設我是攻擊者,就可以寫個腳本透過自動化的方式把 Glints 上所有職缺的 ID 都抓下來,然後再利用上面這個漏洞,就能抓到 Glints 上所有開放的職缺的應徵者資訊。

# 修補方式

後來 Glints 修正了這個 API,實作了 JobId 的檢查,確認使用者對於 JobId 所屬的公司有存取權限才回傳資料。

# 2. 應徵者資訊 RSS 的權限控管不當,導致使用者資訊洩露

Glints 提供了一個職缺應徵者的 RSS feed,方便雇主串接 Slack 或其他服務,就能看到某個職缺最新的應徵者資訊:

rss

回傳的資訊有應徵者的姓名、電子郵件跟履歷,內容大概是這樣:

rss content

而 RSS 的網址長這樣:https://employers.glints.com/api/feed/jobs/{RSS_ID}/approved-candidates?UserId={companyOwnerId}

需要這個職缺專屬的 RSS ID 跟所屬公司的管理員 ID 才能正確打開 RSS 的連結,而管理員 ID 很容易可以取得,因為公司資訊都是公開的,所以從 Glints 的公司頁面中可以直接看到公司管理員的 User ID,但是 RSS ID 要怎麼取得呢?

我發現有個拿公司底下職缺的 API 網址如下:https://employers.glints.tw/api/companies/03638b7f-2da0-4b68-9e92-1be9350600ba/jobs?where={"status":"open"}&include=jobSalaries,Groups,City,Country

從 API response、傳進去的參數跟欄位的命名這三點中,我推測背後用的應該是 Sequelize 這一套 Node.js 的 ORM,為什麼我會知道呢?因為我也用過這一套,像是 where 的規則跟 include 這個名稱,都是 Sequelize 的操作方式。

於是,我猜測後端有可能是直接把 query string 整個 object 丟進去 Sequelize 做處理,不過試了一下之後發現好像不是,還是有一些限制,但至少我新加的一個參數:attributes 有效!

attributes 這個參數在 Sequelize 裡面是決定回傳值要有哪些欄位,例如說 attributes=id,那回傳的資料裡面就只會有 ID,因此我在 attributes 裡面放上了 rssId,結果 response 裡面就真的出現這個欄位了:

rss-id

因此,我們可以透過這個方式取得任意職缺的 RSS ID,那既然有了 RSS ID 跟公司管理員的 ID,跟上一個漏洞一樣,我們可以自動化去跑,就可以透過 RSS feed 來取得所有職缺的應徵者資訊。

# 修補方式

因為這個功能使用率極低,所以後來 Glints 直接把這功能拔掉了。

# 3. 使用者資訊洩露

前兩個漏洞都是從雇主那端才能利用的漏洞,而這一個則是任何人都可以利用。

當你在 Glints 求職平台註冊一個帳號以後,你會有一個 User ID 以及公開的個人頁面,像是這樣:https://glints.com/tw/profile/public/44007523-f7a8-411d-b2c4-57c68a976534

public page

網址列上的那一串 44007523-f7a8-411d-b2c4-57c68a976534 就是我的 User ID。

而有另外一個 API 也可以拿到使用者的公開資訊:https://glints.com/api/publicProfiles/44007523-f7a8-411d-b2c4-57c68a976534

profile api

可以看出在 API 的回傳資訊中,已經有特別過濾掉具有敏感資訊的欄位,像是手機以及 email 等等,但卻有一個欄位忘記過濾掉,叫做 resume。這個欄位存的會是一個檔案名稱,像是這樣:badf34128adefqcxsq.pdf,而 Glints 上的履歷都放在同一個地方,網址的規則是: https://glints-dashboard.s3.ap-southeast-1.amazonaws.com/resume/xxxxx.pdf

換句話說,只要知道履歷的檔名,就可以拿到履歷的內容。因此,我只要知道某個使用者的 ID,我就可以透過上面的 API 拿到他的履歷檔名,再透過固定規則拿到履歷的檔案,而通常履歷上都會有 email 以及電話等等的資訊(有些甚至還有地址)。

可是,我們要怎麼知道使用者的 ID 呢?

有幾個方式,第一個是 Glints 在有些國家有論壇的功能,在論壇就可以透過 API 直接看到那些發文跟回文的人的 ID,第二個方式則是 Google。

因為個人頁面的網址規則都是一樣的,所以只要 google:inurl:profile/public site:glints.com,就能找到一大串的搜尋結果,就可以從網址中取得 User ID,並且透過 ID 拿到他們的履歷。

google hacking

# 修補方式

Glints 修改了 API 的回傳值,隱藏了 resume 欄位

# 4. 招募人員資料洩漏

上面幾個漏洞都是跟一般使用者有關的,我們來看看一個不一樣的。

除了 Glints 的主站以外,我也針對了 Glints 去做子網域的掃描,找到了一個頁面:https://superpowered.glints.com/

website

這頁面需要特定的 Google 帳號才能登入,看起來沒什麼機會,但我們可以從 JS 檔案裡面去尋找一些線索!通常這些檔案都是 minified 過的所以不好看懂,但我們可以直接用關鍵字搜尋,去找找看有沒有我們感興趣的資訊,我下的關鍵字是:query

search query

從關鍵字搜尋中,可以找到一個 GrapgQL 的 query 叫做 findRecruiters,而參數的部分也可以從程式碼中得知,或是也可以利用 error message 去慢慢試出來,整個 API 長這樣:

query {
findRecruiters(input:{}) {
id,
email,
role,
displayName,
fullName,
jobTitle,
jobStatus

team {
labels
}
}
}

從 JS 中找到了 API URL 跟 query 的格式,打了這個 API 之後,發現並沒有鎖權限。

從 response 中我們可以得到每一個招募人員的姓名、職稱、所屬的團隊以及 email:

response

# 修補方式

Glints 增加了權限控制,確保訪客無法取得相關資訊。

# 結語

這次在 Glints 發現的漏洞皆為權限控管相關的問題,當權限沒有管理好的時候,就很容易可以取得其他人的資訊。特別是對 Glints 這種求職平台來說,使用者的個人資訊價值比較高,因為履歷上通常都會有電話、姓名、email 或甚至是地址,有許多的個資都在上面。

也因為如此,這次發現的四個漏洞的嚴重程度都被評為「高」,一個的獎金是 400 SGD,總共是 1600 SGD,折合台幣約 32000 元。

最後,附上這次回報的時間軸:

Tag

Recommendation

  1. PWN 入門 - rop, gadget 是什麼?
  2. 基於 JS 原型鏈的攻擊手法:Prototype Pollution
  3. 從 cdnjs 的漏洞來看前端的供應鏈攻擊與防禦
  4. 關於 email security 的大小事 — 原理篇
  5. Reverse Engineering 101 — Part 1

Discussion(login required)