你的.git在網站上裸奔嗎 版本庫目錄成資安大漏洞

本文將介紹.git目錄的結構內容以及對於Git在運作上的重要性,並探討.git目錄暴露的技術原理與可能造成的風險。另外,也會用常見的.git目錄外洩方式來說明其中的安全風險,並提出預防措施與外洩後的補救方式,最後透過git-dumper和模擬情境演練做說明。

目前Git是現代軟體開發中最廣泛使用的版本控制系統之一,幾乎所有軟體開發都會透過Git進行版本控制與部署,這大大提升了開發者或團隊在製作一個專案時的效率。

然而,若是開發端在使用Git做控管時沒有將資訊提交管理好,或網站伺服器沒有正確設定等疏失,則可能造成Git洩漏等資安問題。

瑞士國家網路安全中心(NCSC)發布報告指出,網路上「未受保護的.git目錄」可能讓外部使用者獲取完整原始碼與認證資訊。

調查發現許多網站的.git目錄未受保護,導致任何人都能下載包含完整原始碼、版本歷史與機密設定檔,例如API KEY等資料。

背景知識介紹

以下介紹Git的背景知識,說明何謂Git以及.git目錄。

何謂Git

Git是一種分散式版本控制系統,主要功能是協助開發者追蹤每個開發版本和程式碼的變更,讓開發團隊能同時在一個專案中進行開發、修改和維護。Git會在每一次的紀錄提交後記錄一個版本,紀錄內容包括修改的內容、作者及時間等等,讓整個開發過程變得更加明確。

與集中式版本控制系統相比,Git的分散式設計讓每位開發者的本機都擁有完整的repository(Git倉庫),包括所有的歷史紀錄與分支。這代表即使在沒有網路的情況下,開發者仍能進行版本切換、提交或回溯等操作。這不僅提升了開發效率,也讓專案在伺服器發生問題時仍能持續進行,不會因單一伺服器失效而導致整個團隊停擺。

什麼是.git目錄

在Git系統中之所以能夠記錄使用者的行為,有一個隱藏資料夾扮演很重要的角色,它是「.git目錄」。在每一個專案資料夾中,在使用Git初始化專案後,會自動建立一個「.git」的隱藏目錄,如圖1為在Windows系統中的.git目錄。這個目錄中存放著所有版本控制所需的資料,是專案能夠回溯、切換與合併的關鍵檔案。也因此,只要保留.git目錄,即使刪除了其他檔案,也能完整還原整個專案的歷史版本。接著,介紹.git目錄內的檔案和目錄有哪些,整理如表1所示。

圖1 Git專案中的.git目錄。表1 .git目錄內的目錄和檔案列表hooks info目錄
圖1 Git專案中的.git目錄。表1 .git目錄內的目錄和檔案列表hooks info目錄

接著,針對表1中的內容進行說明。

hooks

.git/hooks/目錄裡面含有許多範例腳本,例如pre-commit、post-commit等,如圖2所示。這些腳本就像自動化幫手,可以在執行commit、merge、push等操作前或後,自動做檢查、格式化程式碼,或者發送通知。例如,可以寫一個pre-commit腳本,禁止提交含有TODO註解的程式碼,或是利用post-commit腳本,在提交新的版本時自動發送Slack通知告訴團隊有人提交新版本,這對於團隊合作和維護程式碼品質非常有幫助。

圖2 hooks檔案內含有的腳本。
圖2 hooks檔案內含有的腳本。

info

.git/info/主要用來存放一些額外資訊,例如exclude文件,如圖3所示。它的功能與.gitignore類似,但只針對本機有效,不會被推送到遠端,因為這個機制,exclude文件通常都用來存放一些臨時檔案或個人設定檔,例如編譯產生的中間檔,確保它們不會被加入版本控制。值得注意的是,.gitignore主要影響git status、git add、git rm和git clean,而一些特殊情況則需要透過exclude來設定。

圖3 info示意圖。
圖3 info示意圖。

logs

亦即歷史紀錄。在進行專案時,開發者會根據不同情況的要求,來切換各種分支logs objects refs COMMIT_EDITMSG FETCH_HEAD HEAD ORIG_HEAD檔案config description index packed-refs做各種不同工作的切換,以利進行版本控制。因此.git/logs/便顯得格外重要保存,因為它會保存在這個專案分支的歷史紀錄和各個各種HEAD。因為它能記錄每一次的commit、分支切換、merge或reset的操作,如圖4所示,對追蹤錯誤操作也非常有幫助。此外,與git log會相提並論的是git reflog,兩者差異整理如表2所示。

圖4為commit hash a82a7開頭的紀錄。
圖4為commit hash a82a7開頭的紀錄。
表2 git log與git reflog的區別
指令 全名 功能
git log commit log 查看commit的歷史紀錄 (專案發展歷程)
git reflog reference log 查看HEAD或分支指標的變 化歷史(包含重設、回滾、 刪除等動作)

Objects

.git/objects/是Git的核心資料庫,存放所有提交的內容和歷史。每個檔案都會被拆成不同的物件來管理,包括blob、tree、commit和tag。

圖5 物件的名稱都是用SHA-1。
圖5 物件的名稱都是用SHA-1。

‧blob檔案內容:blob負責儲存檔案的內容,但不包含檔名,每次執行git add時都會生成新的blob。

‧tree目錄結構:tree負責記錄目錄結構,它會指向底下的blob或其他tree,tree就相當於一個資料夾,可以告知這個目錄結構底下的所有子資料夾和物件內容。

圖6 各個版本指向的分支和指標。
圖6 各個版本指向的分支和指標。

‧commit提交訊息:commit記錄所有提交過的訊息、作者和時間,以及對應的tree。

‧tag版本標記:tag則標記重要版本,當想要對一個物件進行標記,做更明顯的區隔時,就可以利用tag的功能。

Git在實體儲存內容時會使用SHA-1雜湊值作為物件名稱,例如file02.a檔案對應的SHA-1雜湊值是e69de29bb2d1d6434b8b29a e775ad8c2e48c5391,如圖5所示。

refs

refs是References的縮寫,refs是用來存放「名稱和commit的對應關係」,也就是Git的指標清單,包含分支和標籤。這讓Git可以快速知道某個分支或標籤指向哪個版本,方便使用者去做切換或追蹤,如圖6所示。

COMMIT_EDITMSG

COMMIT_EDITMSG是最近一次commit的訊息檔案,如圖7所示。通常在進行commit時都會在後面加入-m來標注新的版本訊息。但若沒有用-m指定訊息或在進行merge/rebase的過程中被要求編輯commit訊息時,Git會開啟編輯器讓使用者輸入內容,編輯器就會儲存那段文字,把這段訊息永久寫進版本歷史,之後的.git/COMMIT_EDITMSG則通常會被下一次的commit覆寫。

圖7 COMMIT_EDITMSG示意圖。
圖7 COMMIT_EDITMSG示意圖。

FETCH_HEAD

FETCH_HEAD是Git在執行git fetch後所產生的暫存紀錄檔,主要用來儲存fetch回來的最新分支資訊,如圖8所示。這個檔案主要是讓Git知道哪些分支、commit ID是來自哪個遠端來源,以便後續進行merge/rebase時使用。當輸入git fetch origin時,Git並不會立即修改本地分支,而是從遠端伺服器抓下最新的commit資料,接著把這些資料的摘要寫進.git/FETCH_HEAD檔案。

圖8 FETCH_HEAD示意圖。
圖8 FETCH_HEAD示意圖。
圖9 當前分支最新的版本。
圖9 當前分支最新的版本。

HEAD

HEAD是Git的指標,通常指向目前所在的分支,例如main,如圖9所示。當對分支進行commit時,HEAD會跟著移動,去指向最新的commit。可以把HEAD想像成目前工作所在的「位置標誌」,它告訴Git現在的操作基準點在哪裡。如果想切換分支或回到某個歷史版本,Git都會根據HEAD的位置去決定操作的對象。

ORIG_HEAD

ORIG_HEAD是Git在執行可能改變歷史的操作時所建立的備份指標,例如git reset、git merge或git rebase這些比較重要的改動,如圖10所示。它會記錄操作前HEAD所指向的commit,方便在進行這些重要操作發生失誤時能夠快速回到原本狀態。舉例來說,假如不小心merge合併錯分支,可以用git reset –hard ORIG_HEAD回到merge前的版本,避免資料丟失。

圖10 顯示危險操作前的原始commit。
圖10 顯示危險操作前的原始commit。
圖11 Config示意圖。
圖11 Config示意圖。

Config

.git/config是Git專案裡最重要的設定檔,它負責記錄這個專案的所有設定,例如遠端倉庫的網址、這個專案要用的使用者名稱與Email等,如圖11所示。這些設定只會影響當前專案,不會影響其他資料夾的Git,也不會因為clone或push而跑到遠端。可以把它想像成專案專屬的Git設定中心,每次Git在處理commit、push或pull時,都會先來這裡確認該怎麼操作。內容是純文字格式,隨時可以用編輯器打開修改;也可以利用git config指令讓Git自己更新它。

圖12 description為「My project description」。
圖12 description為「My project description」。
圖13 各個index的資訊。
圖13 各個index的資訊。
圖14 refs很多都被打包在packed-refs檔案裡。
圖14 refs很多都被打包在packed-refs檔案裡。

description

.git/description是Git專案的一個純文字描述檔案,如圖12所示。它的存在是為了伺服器端或Git Web介面(例如Gitweb、GitLab)在顯示這個repository時,有一個簡短的專案說明可用。

index

又稱索引或暫存區檔案,主要負責暫存已經被git add的檔案,並記錄檔案路徑、模式、blob hash以及修改時間等資訊,如圖13所示。當使用git commit時,Git會依據index的內容建立新的tree物件,因此index可視為提交的準備區。index最重要的功能包括能讓Git快速比對working tree跟index所定義的tree之間的差異,以判斷哪些檔案被修改過,不需要每次都讀整個檔案內容來比較;若是在操作merge合併而產生衝突時,操作者也可以從index裡取得相關資料協助解決衝突。

packed-refs

packed-refs是Git為了節省空間、加快讀取速度而產生的一個索引檔案。平常Git會把每個分支、標籤等資訊分別放在.git/refs/裡的各種小檔案中,然而當專案規模變大、變多時,這些分散的小檔案會讓存取效率下降。為了解決這個問題,Git會在需要時把這些零散的refs打包成單一的packed-refs檔案,讓所有指標集中存放在同一個地方,這麼一來,Git只需要讀取一個檔案就能取得所有refs資訊,大幅加快查詢速度,packed-refs使用範例如圖14所示。

Git的安全風險

.git目錄安全性極為重要,其本質上可以被視為一套完整Git專案的「核心資料庫」。這個目錄中除了包含所有檔案的最新版本外,還記錄了每一次的commits、所有objects、分支與標籤的refs、設定檔等等。這些資料甚至會包括開發過程中曾經存在,但後來被移除的各種檔案與內容。也就是說,只要攻擊者取得這份.git目錄,基本上就能還原出專案從第一天到最近所有的檔案狀態與改動細節,等同持有專案最核心的設計藍圖。

當網站誤將.git目錄公開在網站伺服器上時,攻擊者不僅能下載原始碼現有檔案,還能用Git的版本回溯能力追蹤專案歷史。即使開發者後來趕緊刪除了密碼、API KEY、資料庫連線資訊等重要機密資訊,只要曾經加進Git的紀錄,就能被具備技術能力的人從舊版commit還原出來。

近期有研究報告指出,僅GitHub平台每日就可偵測到數千個新憑證、API KEY外洩事件,而且近半數受害專案在公開後數周才發現問題,進而導致攻擊者能從公開.git目錄批次掃描歷史commit,挖掘出資料庫密碼、雲端存取憑證等。這類敏感資料一旦外洩,會導致各種資安漏洞,例如攻擊者取得資料庫、雲端服務的控制權,甚至影響公司客戶或下游系統的安全等。以下整理.git目錄暴露在網路上的幾種常見的原因。

.git目錄常見外洩原因

.git目錄之所以會被公開於網路上,原因通常並非Git系統本身的設計有缺陷,多半源自於部署流程或伺服器設定上的人為疏失與管理不周。以下列逐項常見的幾種外洩情況:

網頁伺服器和權限設定錯誤

許多開發者在架設網站或製作網頁的時候,會直接將整個專案資料夾放到網頁的根目錄裡,但如果沒有特別排除.git目錄且網頁伺服器啟用了目錄清單功能的話,就有可能導致整個版本庫原封不動地暴露在網路上。不論是使用Apache、Nginx或其他伺服器,只要沒有明確限制存取.git目錄,存取者只須連線到儲存庫的URL即可存取該目錄,直接列出伺服器資料夾中的檔案內容。因此,在最初部署或架設伺服器的時候,開發者就應該主動封鎖.git目錄的存取,如圖15以Nginx的nginx.conf檔案為例所示。

圖15 設定拒絕存取.git目錄。
圖15 設定拒絕存取.git目錄。

備份檔、壓縮檔意外暴露

另一個常見的洩漏方式是發生在專案備份或要把整個專案打包起來的時候,有些開發者習慣使用「zip -r」等等指令來打包專案,但若是沒有在指令中輸入正確參數以限制.git目錄不會被一起打包,例如「-x '*.git*'」,則打包或備份後的專案就可能包含.git目錄,而一旦這些檔案不小心外流或備份位置本身是公開網址,攻擊者只要下載這份檔案就能完整還原整個repository。

為了避免這類打包疏失導致的.git目錄外洩,除了使用指令確實排除.git之外,也可以使用「git archive」指令來匯出不包含.git目錄的檔案,因為「git archive」在打包時本身就不會包含.git目錄,可有效避免意外將整個Git repository外洩出去。

為了防止.git洩漏在網路中,開發者在部署時最重要的事情之一就是確保.git不會被一起上傳到正式環境,因此在上線前一定要先檢查資料夾內容,確認專案裡沒有多帶.git或其他不該出現的東西。若真的不小心把.git放上去,最重要的就是先把.git整個移除,必要時可以暫時把網站關掉,以防被人持續下載,接著把可能外洩的密碼、API KEY等敏感資訊全部重新更換一次,確保不會被人利用。

最後,也要記得看伺服器的存取紀錄,確認是否有人已經下載過.git。為了避免日後又發生同樣的問題,可以建立一套固定的部署流程,例如上線前固定檢查、部署後快速掃描一次,並在團隊內訂好「哪些資料夾不能上傳」的規範,讓每次部署都照著流程走,減少人為疏忽。

如何知道.git目錄是否洩漏

若想知道.git目錄是否洩漏,可採用以下的方法。

直接查詢

最常見的方式是直接上網查詢特定網站的.git目錄,例如「https://fangjoker.site/.git/」,如果沒有洩漏的情況,網站會回傳「403 Forbidden」,這個情況不是代表此檔案不存在,而是沒有權限開啟,或是這管理已經將其關閉。但假如管理者可能有之前的人為疏失等,裡面的內容將允許所有人來做存取。

圖16 Google Dorking搜尋結果。
圖16 Google Dorking搜尋結果。
圖17 安裝git-dumper。
圖17 安裝git-dumper。

利用Google Dorking

可以使用Google Dorking來發現暴露的.git目錄。在Google中使用指令「inurl:.git」,如圖16所示。

git-dumper工具介紹

git-dumper是一個開源的滲透測試與資安分析工具,設計目的在於自動化從對外可存取的網站.git/目錄下載並還原出完整Git repository。它能取得並重組repository的資料與objects,最後在本地重建出可用的.git目錄,讓使用者能用標準git指令檢視提交歷史與還原過去版本。

核心原理運作機制和安裝

git-dumper會先檢查目標網站的.git目錄是否開啟了目錄列舉功能。如果有,工具就會直接把所有檔案大量下載。如果網站未開放目錄列表,git-dumper則會進一步操作,它會先試圖下載最關鍵的檔案,如.git/HEAD、.git/index、.git/config等,並分析內容取得所有branch、refs、objects等資訊。接著,根據這些檔案的內容遞迴搜尋與下載剩下的檔案與物件,拼湊出能完整重建的Git repository。

當所有必要檔案下載完成後,git-dumper會自動還原出原本的專案原始碼,讓攻擊者或研究人員可以直接用git指令操作。例如可以用git log檢視完整的提交歷史,甚至checkout各種分支,還原過去曾經出現過但後來被刪除的檔案。這種方法可以確認網站有無誤將完整開發歷史、密碼、API KEY或憑證等敏感資料外洩,使用python pip直接安裝git-dumper工具,指令如圖17所示,接著,使用指令「pip install -r requirements.txt」進行套件安裝。

git-dumper的主要指令

git-dumper主要指令整理如表3所示,其餘指令可以參考git-dumper的官方文件(https://github.com/arthaud/git-dumper)。例如,使用指令「git-dumper https://fangjoker.site/.git C:UsersoliveOneDriveDesktop3」還原網站的.git目錄,如圖18所示。

情境演練

阿範是一名資深資訊安全研究員,在九百元軟體股份有限公司工作。平時,他總是默默地關注業界各家技術動態,尤其對競爭對手粉騰公司的新系統格外留意。最近,粉騰公司推出一套嵌入式報表系統,裡面不僅有複雜的商業邏輯,還藏著專屬演算法。阿範看到這個系統後職業本能直接被激起,心想一旦掌握原始碼,就能逆轉這兩家公司在市場的價值和地位,於是阿範開始著手找尋有哪些方法能夠最有效地擊潰粉騰公司。

圖18 執行下載目標網址.git目錄。
圖18 執行下載目標網址.git目錄。
表3 git-dumper主要指令
指令1 下載Git repository git-dumper <URL> <DIR>
git-dumper https://fangjoker.site /.git/ "C:Users oliveOneDriveDesktop3"
指令2 設定失敗後重試次數 git-dumper –retry <次數> <URL> <DIR>
git-dumper –retry 5 https://fangjoker.site /.git/ "C: UsersoliveOneDriveDesktop3"
指令3 設定放棄前等待時間 git-dumper –timeout <秒數> <URL> <DIR>
git-dumper — timeout 25 https://fangjoker.site /.git/ "C:UsersoliveOneDriveDesktop3"

黑亨是粉騰公司內部的系統主要管理者,寫程式的能力非常出眾,但他有一個非常不好的個性,他的性格急躁且非常沒有耐心,一旦工作壓力大,便會導致他整個心思會完全不在專案上,暴躁的脾氣就導致黑亨常常在許多重要的專案中出包。

某日黑亨一如既往地來到了粉騰公司,但由於連續加班到很晚導致睡眠不足,隔天上班又因為塞車而遲到,一連串的不順讓黑亨的火氣快衝破了他的極限,這使黑亨整天的思緒完全不在工作上,當黑亨在部署新開發的嵌入式報表系統過程中,完全沒發現他犯了一個作為一位工程師最不能犯的一個錯誤:不小心將整個網站根目錄下的.git版本控制資料夾設定成公開,變得從外部可以直接訪問.git目錄這一行為成為資安漏洞的根源。

某日,阿範在例行針對網路目標進行掃描時,他無意間點開了一個路徑,看到一堆版本控制相關的檔案列在頁面上,驚訝地發現粉騰公司網站http://fangjoker.site/.git/路徑能正常被瀏覽器存取,顯示出一堆版本控制相關檔案列表。這意味著網站可能未限制.git目錄訪問權限,讓攻擊者可以下載所有版本控制資料。阿範判斷,作為一個能夠重重打擊粉騰公司的絕佳好機會絕對不能放過,於是阿範立即採取攻擊,透過git-dumper工具快速還原出整個專案歷史與最新原始碼,並好好來解析。

阿範先在桌面建立一個資料夾用來存放等一下抓到的資料,利用指令「pip install git-dumper」安裝好git-dumper工具和相關套件,接著輸入「git-dumper https://fangjoker.site/.git/ "C:UsersoliveOneDriveDesktop3 "」。

阿範成功將粉騰公司不小心外洩的整個儲存庫下載到預設的資料夾裡,而這裡面就包含最重要的.git目錄。抓到資料後,但阿範覺得只有抓到這個專案檔案還不夠,於是他做進階的資料掃描打算把這個專案裡有的祕密全部找出來,正好阿範不久之前在GitHub上面找到一個可以掃描.git repository的強大工具「Gitleaks」(Gitleaks安裝網址為https://github.com/gitleaks/gitleaks),於是他決定利用這個工具來做進階的資料掃描,把這個專案裡有的敏感資訊全部找出來,成功掃描到不少資料。

阿範在這個專案中發現大量敏感資料,包括疑似為API KEY的檔案。然而,他很快意識到,若將這些資料公開或做利用,不僅可能觸犯妨害電腦使用罪,還可能涉及商業秘密侵害及個人資料保護法等民刑事責任,這使得阿範必須謹慎思考接下來的行動。在權衡風險後,阿範決定採取負責任的資訊安全行動。他將下載的儲存庫和掃描結果安全地保存在本地,並整理了發現的概要、風險描述以及改善建議,用安全管道通知粉騰公司的資安團隊。他明白,任何貪圖短期利益的行為都可能觸法,對自己與公司都不利。

粉騰公司收到消息後迅速採取行動。內部IT團隊立刻關閉.git目錄的公開存取,並全面審查專案資料是否存在個人資料或商業機密。黑亨也意識到他自己犯了大錯,並下定決心要好好的改進自己的問題。

在這之後,黑亨在粉騰公司內部的例行檢討會上把這次疏失作為案例,提醒同事們「部署前的最後檢查」有多重要;阿範則把這件事列入自己的案例集,向年輕研究員分享如何以職業倫理與負責任態度處理漏洞。這起事件讓雙方都學到寶貴一課:「即便技術再強,唯有把安全與責任放在首位,才能真正保護公司與用戶」。

結語

本文透過案例模擬和工具演示,展示了攻擊者如何利用git-dumper等工具還原完整原始碼、提交歷史,甚至進一步使用Gitleaks等工具掃描出敏感資訊如API KEY、資料庫密碼等祕密,藉此明白Git洩漏的嚴重性,並在使用版本控制工具的同時提升自身的資安意識。