从程序员视角看代码安全问题探因

技术机制 + 攻击战术 + 代码细节问题 = 可利用的漏洞


引语

作为安全小白,在学习了一点安全姿势之后,自然产生了一个疑问: 安全问题如此千姿百态,究竟因出何处?又该如何避免? 显然,不可能兵来将挡水来土掩,有一个解一个,不然就会处于被动的位置。

作为程序员,自然更加关注代码安全问题。如何去理解和避免这些代码安全问题呢?

在安全问题探因时,会发现同一问题也可以用多种视角来思考。

基本逻辑

  • 本性:攻方 --- 巨大的利益铤而走险,守方 --- 趋利避害
  • 符号 + 逻辑 + 构思 = 编程

安全思想

安全领域就像一座森林,有数不尽的林木。 新手有两条路:

  • 找准一个点,深入挖掘;
  • 带一张地图,发现、分析并归类。

我选择了第二条路。

基本的安全思想武器有:

  • 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 等,如果缺乏严格的参数检查,必然会给恶意代码留出藏匿空间。

参数检查的基本准则是白名单检查和精确检查。根据黑名单检查,黑名单不完整会导致漏网之鱼(漏杀)。根据白名单检查,白名单不完整会导致正常请求无法处理(误杀)。

精确检查往往不容易出安全问题,而宽泛检查则往往有漏网之鱼。 精确检查典型的是枚举检查, 宽泛检查典型的是正则匹配检查。

此外,参数的安全检查通常要过滤敏感特殊字符。

以下是已知案例:

作为服务于广大群众的新生代农民工,我们最起码要做好的安全措施是:做好缜密的参数检查。

便利机制隐藏安全问题

很多技术机制便利了日常使用,但同时也带来了安全问题。这样的例子数不胜数:

默认端口和开放服务

免密登录机制

文件系统的相对路径与点号

远程代码执行

动态加载模块

代码执行引擎

  • 系统命令执行
  • 数据库执行系统命令
  • 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 的攻击环节是密切相关的。

漏洞特征:

  • 一个或多个关联点结合;
  • 注入恶意请求、恶意代码(可以执行系统命令、下载远程脚本)、导致系统命令执行的恶意文件。

有些攻击设计非常精巧,令人叹为观止!

具体的代码漏洞:

复现漏洞:

  • 搭建漏洞环境
  • 发送 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)等

  • 其它:从已知漏洞入手、“此地无银”的代码注释、求助信号、鱼叉式模糊测试

管理松散隐藏安全问题

典型的是,默认配置不当。大量的软件、配置,如果缺乏自动化运维工具,很容易出现疏漏之处。

当然也有理念的问题。任何有“自然信任”区域的地方,比如“内网安全”,也必然会遭到外来访客的快速攻击。因此,现在崇尚零信任,但零信任也会增大日常操作成本。因此便利和安全往往难以兼得。

人性有难以消除的漏洞

轻信、疏忽、贪利,是大多数人难以避免的人性漏洞,也是社会工程学家擅长攻击之处。

入侵到底是什么

入侵可以看成是一系列综合技术手段的运用,旨在破坏/绑架基础设施或获取有价值的数据和信息,用于进一步勒索或其它任何不明用途。 典型入侵的第一步,通常就是找管理松散之处,找到突破口,然后再利用代码漏洞获取主机最高权限,接着根据直觉去判断和感知网络环境,确定下一步目标,循环往复。

也有可能是拿到主机权限之后,下载所需要的脚本,执行并藏匿。

为什么有入侵? 入侵的本质是人类世界信息化和数据化导致的结果。实体资产通常只能搞破坏,因为很难运走。以前搞抢劫的,至少人得在,没法通过瞬移技术去搬运东西吧。但是信息化和数据化之后,黑客根本无需在场,就可以把有价值的资产搬走,而且能做到毫无痕迹。

本质上来说,人类通过信息化和数据化获得了便利,而这种便利同样带来了更多更复杂的安全问题。 正验证了那一句: 凡便利之处,必藏安全问题。

小结

做安全很顾忌的一点是影响客户业务的正常运行。安全问题层出不穷(至少新入行的程序员必定会写出有安全问题的代码),看上去似乎无法消除安全问题,那么我们做安全的更贴近现实的目标是什么呢?

封堵大多数安全问题?但也许那些不为人所注意的安全问题才是最需要关注的,因为那些必定会导致血雨腥风。

参考资料

posted @ 2023-05-21 06:11  琴水玉  阅读(105)  评论(0编辑  收藏  举报