Race condition and git hooks vs Gitea server
Articles,  Blog

Race condition and git hooks vs Gitea server

[INTRO] Hi. My name is Kacper Szurek and in this episode
I’m going to show you the remote code execution on gitea server. Gitea is a simple git server written in Go
language. It’s very simple to install and has many
interesting options. The exploit demonstrated here consists of
several elements that, when connected together, lead to a complete takeover of the server. First, we use the error in the GIT LFS implementation
to get the contents of the app.ini file. Then, from this file we read SECRET that can
be used to sign JWT tokens. Thanks to that, we are able to send a falsified
session file of a user. We create a new repository using our newly
created administrator session. We need an administrator account because only
the administrator can modify git hooks. The `update` hook is going to contain our
malicious code to be executed on the server. Then, all that is needed to run this code
is to just push any source code changes to the repository. That’s enough because git automatically
runs hooks with the privileges of the user who started the server. We already know how in theory the server attack
will look like. Now I will discuss its individual elements
in detail. Our exploit is `Unauthenticated`. That means we don’t need any username or
password to execute it. We just need to find a starting point from
which we begin the takeover of the server. In this case, it’s quite difficult. Why? Because most of the actions require logging
in. I took a closer look at LFS server implementation,
because it’s a relatively new functionality. We can find there a function named `PostHandler`,
which is responsible for creating new LFS objects. At the beginning, the function checks whether
LFS support is enabled, then the repository identifier is retrieved and then it is checked
whether the current user has write permission to the given repository. Seemingly everything looks fine. If the user does not have the proper permissions,
the `requireAuth` function is called and it sets the appropriate `WWW-Authenticate` header
as well as the `401` status. However, when we dig a bit deeper in the source
code, it turns out that a correct usage of this function looks a bit different. The vulnerable code lacks the word “return”,
which would terminate the “PostHandler” function in case of failure. Without this word, the `requireAuth` function
is going to be executed and then the program will proceed to the next actions, in this
case creation of the LFS object. In such way, we bypassed the mechanism that
validates user permissions. We are now able to create any LFS object for
any repository. We choose one that is publicly available to
every user. As `Oid` parameter we set: ` ….custom/conf/app.ini
` Where do these dots come from and why is it
so important to choose a public repository? The `getContentHandler` function responsibility
is to retrieve content of a file from LFS repository based on its `Oid`. Firstly, it checks if the current user has
read access to the repository. Because the one we use is publicly available,
any user (even not logged in) can download any file from it. Then the path to the file is retrieved using
`ContentStore`. There, the `LFS_CONTENT_PATH` directory is
concatenated with `oid` parameter. Function named `transformKey` generates new
path to the file. It is created on the basis of the first two
characters, then backslash, then the next two characters, then backslash again and then
the rest of the identifier. By replacing our `oid` parameter with dots
we get:` giteadatalfs….customconfapp.ini`
On Windows, the `../` means “move up to parent directory”. Thanks to that we are able to read contents
of the `app.ini` file. The configuration file contains `LFS_JWT_SECRET`. Its value is used as a key to sign JWT tokens. These tokens are checked when sending LFS
GIT files to the server. Thanks to the fact that we know its value,
we can send any file to any repository with any `oid`. We create new LFS object using 4 dots trick
again. This time however, we use the path to the
`sessions` directory as `oid`. This directory contains files with information
about currently logged in users. Using `Gitea` we send to the server a cookie
named `i_like_gitea`. The server checks if the file with name from
the cookie exists in this directory. If so, it reads the information about the
current user stored in the session. We are going to send here our own session
file with fake administrator account. Why? When we take a look at the function that allows
saving files on the server, it turns out it works exactly like the function used to download
files. There is only one difference between them. The `.tmp` string is added to the name of
the file being created. For us, attackers, it means that we can send
the file to any place. However, it will always have the `.tmp` extension. If such limitation wouldn’t exist, we could
immediately create a file with the code to be executed in the hooks directory of the
respective repository. Unfortunately, it turns out that we can’t
use the session we send because it’s immediately removed from the server. The keyword `defer` is responsible for this
– it removes the created file as soon as the `Put` function finishes its operation. To bypass this restriction, we are going to
make use of a behaviour called `race condition`. When a POST request is sent to the server,
the `Content-Length` header is passed along with the data you want to send. It tells the server how much data the user
intends to transfer. Thanks to this the server knows at what stage
of data transmission the user is currently. The trick here is to set the header value
to a large number. The data that the server receives from the
user is saved in the file immediately. The function, however, waits for its completion
until its size is equal to the number given in the header. Thanks to that the file is not removed immediately. This gives us few dozens of seconds during
which we can make use of our session. At some point of time the server determines
that the user no longer transmits any data and terminates the function automatically. The next part is very simple. We create a new repository using our fake
administrator account. Next we go to the repository settings. Because we are the administrator, we have
the access to the `Git hooks` option. The hooks are scripts located in `.git/hooks`
directory in every repository. They are executed when actions are performed
on the repository. For example, the `update` script is executed
by Git in response to the `git push` command. For example, the hooks can be used for checking
whether a code that is being committed is formatted properly. In case of a badly formatted code, the commit
won’t be saved on the server. It’s a very useful mechanism but quite dangerous
at the same time. For this reason, during the standard cloning
of repositories using the `git clone` command, these scripts are not copied to the user’s
computer. It guarantees that cloning the repository
from an unknown server doesn’t expose us to the attack using hooks. They must be copied manually by the user to
the appropriate directory. The creators of `Gitea` were aware of the
risks carried by the hooks. For this reason, only administrators can edit
their content. In the body of the `update` hook we enter
our command for execution. Its result is passed to the `objects/info/exploit`
file. Now we just need to add a new file to our
repository and send it to the server using `git push` command. At this moment, the server will execute `update`
hook and write the result of our command in the file named `exploit`. We can display the result of that command
by downloading the object. And that’s all that I wanted to show in
this episode. We managed to execute the code on the remote
server without having a login and password. We achieved this only by exploiting a few
small vulnerabilities. It also proves how dangerous the git hooks
mechanism can be. If you enjoyed this video, I encourage you
to subscribe my channel. You can find more information about my current
projects on my twitter @kacperszurek as well as on my website security.szurek.pl
Thanks for watching and see you in the next video.

Leave a Reply

Your email address will not be published. Required fields are marked *