从程序员视角看代码安全问题探因
技术机制 + 攻击战术 + 代码细节问题 = 可利用的漏洞
引语
作为安全小白,在学习了一点安全姿势之后,自然产生了一个疑问: 安全问题如此千姿百态,究竟因出何处?又该如何避免? 显然,不可能兵来将挡水来土掩,有一个解一个,不然就会处于被动的位置。
作为程序员,自然更加关注代码安全问题。如何去理解和避免这些代码安全问题呢?
在安全问题探因时,会发现同一问题也可以用多种视角来思考。
基本逻辑
- 本性:攻方 --- 巨大的利益铤而走险,守方 --- 趋利避害
- 符号 + 逻辑 + 构思 = 编程
安全思想
安全领域就像一座森林,有数不尽的林木。 新手有两条路:
- 找准一个点,深入挖掘;
- 带一张地图,发现、分析并归类。
我选择了第二条路。
基本的安全思想武器有:
- Cyber Kill Chain
- ATT&CK
- 威胁建模
- 安全左移 & 零信任 & 东西向隔离
CyberKillChain
Cyber Kill Chain 对一次典型攻击活动进行建模。
- Reconnaissance: 侦查,熟悉目标环境
- Weaponization: 武器研制,比如攻击脚本
- Delivery: 将武器传送至目标环境,受害者机器;
- Exploitation:漏洞挖掘与利用;
- Installation: 安装恶意软件;
- Command & Control: 控制受害者机器的通道;
- Actions on Objectives: 在受害者机器上完成攻击目标。
ATTCK
ATTCK 对于攻击活动中所使用的战术和技术进行分类。
实质与形式
实质是不变的,形式是多样的。
实质:登录、拿到root权限、维持root权限、隐藏和防御规避、执行命令。
形式:
- 语言:入侵者可以用多种语言实现相同的目标。比如用 go , perl, python 等来实现。
- 手段:登录可以使用爆破、免密机制。
- 系统机制利用:比如免密机制、授权认证机制缺陷、钩子方法、相对路径、动态模块加载、系统可信程序利用、点号开头的文件会隐藏、软链接。可以为开发运维提供便利,同时也给入侵者可乘之机。
- 多样化的命令执行机制:既可以用 shell 命令行执行,也可以进入特定的命令解释器来执行;不同语言和平台通常也提供了命令执行的功能。
- 程序漏洞:比如反序列化漏洞、远程代码执行、参数检查缺失或不足、缺乏边界检查(缓冲区溢出)等。
- 程序后门:未公开的程序后门接口等。
- 社会工程: 利用人容易轻信、不愿声张的弱点和漏洞。
这里要求我们能够尽可能复用本质的检测能力,而对形式和场景具备扩展能力。
安全问题
参数安全检查缺失或不足
典型的安全问题是 SQL 注入。 在 SQL 里添加一些附加条件(比如 OR 1 = 1),根据 SQL 执行结果就能推断一些内部细节,甚至能拿到敏感数据。SQL 注入问题的原因是参数安全检查缺失。规避措施是:检查参数类型、过滤特殊符号、绑定变量、预编译语句、PreparedStatement 参数化 SQL。
参数安全检查缺失,可以导致各种问题。 任何能把字符串当做代码来执行的地方,比如脚本执行引擎、代码执行引擎、RCE 等,如果缺乏严格的参数检查,必然会给恶意代码留出藏匿空间。
参数检查的基本准则是白名单检查和精确检查。根据黑名单检查,黑名单不完整会导致漏网之鱼(漏杀)。根据白名单检查,白名单不完整会导致正常请求无法处理(误杀)。
精确检查往往不容易出安全问题,而宽泛检查则往往有漏网之鱼。 精确检查典型的是枚举检查, 宽泛检查典型的是正则匹配检查。
此外,参数的安全检查通常要过滤敏感特殊字符。
以下是已知案例:
- “JBOSS反序列化漏洞【CVE-2017-12149】 ”: ReadOnlyAccessFilter 在缺乏安全检查的情况下无条件反序列化客户端的二级制流。如果二进制流中含有恶意代码,那么恶意代码将会被执行。
- “Harbor用户名枚举漏洞【CVE-2019-3990】(https://github.com/goharbor/harbor/security/advisories/GHSA-6qj9-33j4-rvhg)”: /api/users 是仅供管理员使用的 API, 但 /api/users/search 接口缺乏管理员权限的校验,普通用户能够直接通过该接口查询其它用户信息(越权)
- “Apache DolphinScheduler 反序列化漏洞【CVE-2021-27644】(https://github.com/advisories/GHSA-93g4-3phc-g4xw)”:1.3.6 版本之前的 DolphinScheduler,授权用户可以针对 MySql 数据源使用 SQL 注入。Mysql 配置布尔参数不仅可以为 TRUE/FALSE 还可以为 YES/NO 绕过
作为服务于广大群众的新生代农民工,我们最起码要做好的安全措施是:做好缜密的参数检查。
便利机制隐藏安全问题
很多技术机制便利了日常使用,但同时也带来了安全问题。这样的例子数不胜数:
默认端口和开放服务
- Redis 6379: “Centos7 下 redis 入侵实战 - root提权”
免密登录机制
- SSH keys:“免密登录原理”
文件系统的相对路径与点号
-
“nodejs 目录穿越漏洞【CVE-2017-14849】” :normalize 操作有缺陷,导致 ../../../foo/../../../../etc/passwd => /etc/passwd
-
“Pulse Secure 任意文件读取漏洞【CVE-2019-11510】”: 通过 SSL VPN 可以读取任意文件,包括敏感文件 /etc/passwd
远程代码执行
- “Tomcat 远程代码执行漏洞【CVE-2017-12615】”:normalize 操作可以将 /test.jsp%20, /test.jsp/ (windows 文件名可允许) 转成 /test.jsp 文件,结合 tomcat put 请求(readonly=false)可以写入 jsp 文件,绕过检查。
动态加载模块
- “MySQL远程代码执行和权限提升漏洞【CVE-2016-6662】” :malloc_lib 配置项可加载任意 so ,且以 root 执行。需要服务器重启才能加载配置文件。
代码执行引擎
- 系统命令执行
- 数据库执行系统命令
- Java 执行系统命令 Runtime.exec, ProcessBuilder
- dockerfile / k8s 命令配置
特权模式
- 特权模式容器(容器逃逸)
引用机制
- linux ln 命令(可用于藏匿)
弱口令/弱密码
- 你的银行卡密码是怎样的
一旦引入了可能性,就无法阻止可能性。后面的事情就变成了“道高一尺魔高一丈”的攻防战。
可推理之处隐藏安全问题
典型的就是根据 ID 值来拉取资源。 如果 ID 值是连续的,是个有点编程基础的小白都懂得用爬虫和 for 循环搞一搞。比如利用id连续值拉取B站视频:
#!/bin/zsh
if [ "$#" -eq 0 ] || [ "$#" -gt 3 ]; then
echo 'Usage: bili [category_id]'
echo ' bili [category_id] page_start'
echo ' bili [category_id] page_start page_end'
echo 'category_id must be provided.'
exit 1
fi
cate_id=$1
if [ "$#" -eq 1 ]; then
page_start=1
page_end=1
fi
if [ "$#" -eq 2 ]; then
page_start=$2
page_end=$2
fi
if [ "$#" -eq 3 ]; then
page_start=$2
page_end=$3
fi
echo "$0 $cate_id $page_start $page_end"
source ~/.zshrc
for ((i=$page_start; i<=$page_end; i++))
do
curl -s "https://api.bilibili.com/x/v3/fav/resource/list?media_id=${cate_id}&pn=$i&ps=20&keyword=&order=mtime&type=0&tid=0&platform=web&jsonp=jsonp" > ~/videolinks.txt && jp ~/videolinks.txt | grep 'video' | xargs -I {} you-get {}
sleep 3
done
当然弄成随机串,也不一定能阻止,只是增大了难度。
同样也可以根据 IP 行为来推断和封堵,比如 超过正常次数阈值、有规律性的拉取。 不过这些都有对策。 对于有规律拉取,稍微做点改造就能变成随机拉取;对于超过正常次数阈值,则必须很谨慎,避免影响大客户的正常行为。加白名单可能是一种方案,不过颇为繁琐。需要精细化管理。 采用黑名单机制,总有漏网之鱼。
过于灵活的非透明之处隐藏安全问题
典型的是恶意镜像。可以动态叠加各种软件来制造新的镜像,但这些新的镜像的安全性,很难通过统一的机制去检测出来。需要有专门的安全公司去做。 见“青藤云安全-蜂巢系统的镜像扫描功能”。
缺乏限制隐藏安全问题
比如服务器对请求来者不拒。服务拒绝攻击会使得缺乏限制的服务器瘫痪,从而无法服务正常用户。
规避措施:增加更多的安全保护机制,例如限流,详细的日志记录,适当的限制资源使用等。
代码漏洞
代码漏洞属于比较精细的层面,往往只有特别精通计算机技术和原理、且非常细心的人才能发现代码漏洞。这种有个专门称谓 0Day 漏洞。我也是才到安全行业才知道这么个专业词汇。
由于很多国内程序员的编程年限比较短,很多开发工程师的代码安全意识比较薄弱(即使有三至五年的高级工程师也不例外),一不小心就能创造个代码漏洞。
此外,反射、泛型、动态字节码等技术,增强了灵活性,同时也会隐藏安全问题。
主要的代码漏洞集中在:
- 认证漏洞
- 提权漏洞
- 反序列化漏洞
- 远程代码/命令执行漏洞
- 权限/身份验证绕过漏
- 文件访问漏洞
- 溢出漏洞
注意到:这些代码漏洞与 Cyber Kill Chain 的攻击环节是密切相关的。
漏洞特征:
- 一个或多个关联点结合;
- 注入恶意请求、恶意代码(可以执行系统命令、下载远程脚本)、导致系统命令执行的恶意文件。
有些攻击设计非常精巧,令人叹为观止!
具体的代码漏洞:
-
“Jackson框架远程代码执行漏洞【CVE-2017-17485】” :通过构造恶意 JSON,下载恶意文件配置,结合 Spring 环境,初始化 bean,这个 bean 可以执行系统命令
-
“SpringSecurityOAuth-RCE【CVE-2016-4977】” : spEL 表达式递归解析
复现漏洞:
- 搭建漏洞环境
- 发送 payload 请求
- 断点调试
避免方式:
- 常用 URL 检测,比如 raw.githubusercontent.com;
- 敏感字符检测(尤其涉及系统命令执行),比如 java.lang.ProcessBuilder, java.lang.Runtime, exec;
- 代码沙箱
寻找漏洞:
-
对 bad words 敏感:比如 raw ; eval|exec|run ; process|system|popen|exec|spawn ; privilege|permission|capability|role|rbac|policy|authorization|claims ; reflect|klass|constantize|forName ; pickle|yaml|serialize|marshal|objectinput ; parse|open|request ; unsafe|insecure|dangerous ; todo|fixme|xxx ; merge|clone ; alloc|free ; AES|RSA|DSA|DES|CBC|ECB|HMAC|GCM ; JWT|JKS|JWK|JKU……; password|provate|token|secret|key|Authorization ; validate|verify ; XML|xerces|SAX|etree|xpath|DocumentBuilder
-
后门(参数/接口等):“ [CVE-2021-1675] Windows Print Spooler权限提升/远程代码执行漏洞”
-
可执行动态语言
-
四种分析模型:用例分析、通信分析(程序接口及交互)、实现分析(含环境配置)、代码及二进制分析
-
漏洞挖掘技术: 手工测试技术(manual testing)、Fuzzing技术、比对和二进制比对技术(Diff and BinDiff)、静态分析技术(static analysis)、动态分析技术(runtime analysis)等
-
其它:从已知漏洞入手、“此地无银”的代码注释、求助信号、鱼叉式模糊测试
管理松散隐藏安全问题
典型的是,默认配置不当。大量的软件、配置,如果缺乏自动化运维工具,很容易出现疏漏之处。
当然也有理念的问题。任何有“自然信任”区域的地方,比如“内网安全”,也必然会遭到外来访客的快速攻击。因此,现在崇尚零信任,但零信任也会增大日常操作成本。因此便利和安全往往难以兼得。
人性有难以消除的漏洞
轻信、疏忽、贪利,是大多数人难以避免的人性漏洞,也是社会工程学家擅长攻击之处。
入侵到底是什么
入侵可以看成是一系列综合技术手段的运用,旨在破坏/绑架基础设施或获取有价值的数据和信息,用于进一步勒索或其它任何不明用途。 典型入侵的第一步,通常就是找管理松散之处,找到突破口,然后再利用代码漏洞获取主机最高权限,接着根据直觉去判断和感知网络环境,确定下一步目标,循环往复。
也有可能是拿到主机权限之后,下载所需要的脚本,执行并藏匿。
为什么有入侵? 入侵的本质是人类世界信息化和数据化导致的结果。实体资产通常只能搞破坏,因为很难运走。以前搞抢劫的,至少人得在,没法通过瞬移技术去搬运东西吧。但是信息化和数据化之后,黑客根本无需在场,就可以把有价值的资产搬走,而且能做到毫无痕迹。
本质上来说,人类通过信息化和数据化获得了便利,而这种便利同样带来了更多更复杂的安全问题。 正验证了那一句: 凡便利之处,必藏安全问题。
小结
做安全很顾忌的一点是影响客户业务的正常运行。安全问题层出不穷(至少新入行的程序员必定会写出有安全问题的代码),看上去似乎无法消除安全问题,那么我们做安全的更贴近现实的目标是什么呢?
封堵大多数安全问题?但也许那些不为人所注意的安全问题才是最需要关注的,因为那些必定会导致血雨腥风。