2023年12月

wmproxy

wmproxy
已用
Rust
实现
http/https
代理,
socks5
代理, 反向代理, 静态文件服务器,四层TCP/UDP转发,七层负载均衡,内网穿透,后续将实现
websocket
代理等,会将实现过程分享出来,感兴趣的可以一起造个轮子

项目地址

国内: https://gitee.com/tickbh/wmproxy

github: https://github.com/tickbh/wmproxy

nginx中的try_files

  • 语法:
    try_files file … uri;

    try_files file … = code;
  • 作用域:server location
    • 首先:按照指定的顺序检查文件是否存在,并使用第一个找到的文件进行请求处理
    • 其次:处理是在当前上下文中执行的。根据 root 和 alias 指令从 file 参数构造文件路径。
    • 然后:可以通过在名称末尾指定一个斜杠来检查目录的存在,例如
      "$uri/"
    • 最后:如果没有找到任何文件,则进行内部重定向到最后一个参数中指定的 uri。

注:只有最后一个参数可以引起一个内部重定向,之前的参数只设置内部的 URL 的指向。最后一个参数是回退 URL 且必须存在,否则会出现内部 500 错误。命名的 location 也可以使用在最后一个参数中。

应用场景

1、前端路由处理:

location / {
    try_files $uri $uri/ /index.html;
    # $uri指请求的uri路径,$uri/表示请求的uri路径加上一个/,例如访问example.com/path,则会依次尝试访问/path,/path/index.html,/index.html
    # /index.html表示如果仍未匹配到则重定向到index.html
}

这种场景多用于单页应用,例如vue.js等前端框架的路由管理。当用户在浏览器中访问一个非根路径的路径时,由于这些路径都是由前端路由管理的,nginx无法直接返回正确的静态文件,因此需要将请求重定向到统一的路径,这里是/index.html,由前端路由控制页面的展示。
2、图片服务器:

location /images/ {
  root /data/www;
  error_page 404 = /fetch_image.php;
  try_files $uri $uri/ =404;
}
location /fetch_image.php {
  fastcgi_pass 127.0.0.1:9000;
  set $path_info "";
  fastcgi_param PATH_INFO $path_info;
  fastcgi_param SCRIPT_FILENAME /scripts/fetch_image.php;
  include fastcgi_params;
}

这种场景多用于图片服务器,当用户访问图片时,先尝试在本地文件系统中查找是否有该文件,如果找到就返回;如果没有找到则会转发到fetch_image.php进行处理,从远程资源服务器拉取图片并返回给用户。

实现方案

当前nginx方案的实现,是基于文件的重试,也就是所谓的
伪静态
,如果跨目录的服务器就很麻烦了,比如:

location /images/ {
  root /data/upload;
  try_files $uri $uri/ =404;
}
location /images2/ {
  root /data/www;
  try_files $uri $uri/ =404;
}

上面的我们无法同时索引两个目录下的结构。即我假设我访问
/images/logo.png
无法同时查找
/data/upload/logo.png

/data/www/logo.png
是否存在。

当前实现方案从
try_files
变成
try_paths
也就是当碰到该选项时,将当前的几个访问地址重新进入路由

例:

[[http.server.location]]
rate_limit = "4m/s"
rule = "/root/logo.png"
file_server = { browse = true }
proxy_pass = ""
try_paths = "/data/upload/logo.png /data/www/logo.png  /root/README.md"

[[http.server.location]]
rule = "/data/upload"
file_server = { browse = true }

[[http.server.location]]
rule = "/data/www"
file_server = { browse = true }

除非碰到返回100或者200状态码的,否则将执行到最后一个匹配路由。

源码实现

    1. 要能循环遍历路由
    1. 当try_paths时要避免递归死循环
    1. 当try_paths时可能会调用自己本身,需要能重复调用

以下主要源码均在
reverse/http.rs

  • 实现循环
    主要的处理函数为
    deal_match_location
    ,函数的参数为
#[async_recursion]
async fn deal_match_location(
    req: &mut Request<Body>,
    // 缓存客户端请求
    cache: &mut HashMap<
        LocationConfig,
        (Sender<Request<Body>>, Receiver<ProtResult<Response<Body>>>),
    >,
    // 该Server的配置选项
    server: Arc<ServerConfig>,
    // 已处理的匹配路由
    deals: &mut HashSet<usize>,
    // 已处理的TryPath匹配路由
    try_deals: &mut HashSet<usize>,
) -> ProtResult<Response<Body>>

当前在Rust中的异步递归会报如下错误

recursion in an `async fn` requires boxing
a recursive `async fn` must be rewritten to return a boxed `dyn Future`
consider using the `async_recursion` crate: https://crates.io/crates/async_recursion

所以需要添加
#[async_recursion]
或者改成Box返回。

参数其中多定义了两组
HashSet
用来存储已处理的路由及已处理的
TryPath
路由。

将循环获取合适的location,如果未找到直接返回503错误。

let path = req.path().clone();
let mut l = None;
let mut now = usize::MAX;
for idx in 0..server.location.len() {
    if deals.contains(&idx) {
        continue;
    }
    if server.location[idx].is_match_rule(&path, req.method()) {
        l = Some(&server.location[idx]);
        now = idx;
        break;
    }
}
if l.is_none() {
    return Ok(Response::status503()
        .body("unknow location to deal")
        .unwrap()
        .into_type());
}

当该路由存在
try_paths
的情况时:

// 判定该try是否处理过, 防止死循环
if !try_deals.contains(&now) && l.try_paths.is_some() {
    let try_paths = l.try_paths.as_ref().unwrap();
    try_deals.insert(now);
    let ori_path = req.path().clone();
    for val in try_paths.list.iter() {
        deals.clear();
        // 重写path好方便做数据格式化
        req.set_path(ori_path.clone());
        let new_path = Helper::format_req(req, &**val);
        // 重写path好方便后续处理无感
        req.set_path(new_path);
        if let Ok(res) = Self::deal_match_location(
            req,
            cache,
            server.clone(),
            deals,
            try_deals,
        )
        .await
        {
            if !res.status().is_client_error() && !res.status().is_server_error() {
                return Ok(res);
            }
        }
    }
    return Ok(Response::builder()
        .status(try_paths.fail_status)
        .body("not valid to try")
        .unwrap()
        .into_type());
}

其中会将
req
中的
path
进行格式化的重写以方便处理:

// 重写path好方便做数据格式化
req.set_path(ori_path.clone());
let new_path = Helper::format_req(req, &**val);
// 重写path好方便后续处理无感
req.set_path(new_path);

如果不存在
try_paths
将正常的按照路由的处理逻辑,该文件服务器或者反向代理,并标记该路由已处理。

deals.insert(now);
let clone = l.clone_only_hash();
if cache.contains_key(&clone) {
    let mut cache_client = cache.remove(&clone).unwrap();
    if !cache_client.0.is_closed() {
        println!("do req data by cache");
        let _send = cache_client.0.send(req.replace_clone(Body::empty())).await;
        match cache_client.1.recv().await {
            Some(res) => {
                if res.is_ok() {
                    log::trace!("cache client receive response");
                    cache.insert(clone, cache_client);
                }
                return res;
            }
            None => {
                log::trace!("cache client close response");
                return Ok(Response::status503()
                    .body("already lose connection")
                    .unwrap()
                    .into_type());
            }
        }
    }
} else {
    log::trace!("do req data by new");
    let (res, sender, receiver) = l.deal_request(req).await?;
    if sender.is_some() && receiver.is_some() {
        cache.insert(clone, (sender.unwrap(), receiver.unwrap()));
    }
    return Ok(res);
}

小结

try_files
在nginx中提供了更多的可能,也方便了伪静态文件服务器的处理。我们在其中的基础上稍微改造成
try_paths
来适应处理提供多路由映射的可能性。

点击
[关注]

[在看]

[点赞]
是对作者最大的支持

在 iOS 12 中,苹果正式弃用 UIWebView,改成 WKWebView,参考
官方声明

后者在性能、稳定性、功能方面有很大提升,并且与 Safari 具有相同的 JavaScript 引擎(JavaScriptCore)。

从 Android 4.4 开始,增加 Chromium WebView,用来取代 Android WebKit WebView。

从 Android 5.0 开始,Chromium WebView 支持以 Android System WebView App 的形式在应用商店中下载,可独立升级。

Android 可以在 App 中集成其他 WebView 组件,例如如 TBS X5 内核的 WebView,或者集成其他 JavaScript 引擎(默认采用 V8 引擎),例如为 React Native 专门优化的 Hermes。

而 iOS 上就只能使用系统内置的 WebView。由此可知,iOS 和 Android 对于 CSS 和 JavaScript 的支持度各不相同,在研发时需要注意兼容性。

一、调试的困难

在客户端中调试网页,不像在 PC 的浏览器有那么完善的控制台,对于网页的各方面都能一目了然。

1)注入脚本

若要调试此类页面,需要些方法,比较常见的有在页面中加入
vConsole
脚本,生成一个控制台。

PageSpy
是一款适用于远程 Web 项目调试的工具(可在异地调试),需要在页面注入脚本,并且还需要在服务器中部署一套控制台系统。

2)抓包工具

或者借助
Charles

Fiddler
这类抓包工具来查看网络通信、映射本地脚本等。

不过在 Android 6.0 之后,不会信任从抓包工具导出的根证书,这就导致无法在抓包工具中浏览 HTTPS 通信,不过 iOS 没有这方面的限制。

而网页中的某些业务可能需要借助客户端的能力(JSBridge)才能完成,但这块无法在 PC 浏览器中实现。

由此可见,需要有种办法能够调试 iOS 和 Android 两种客户端中的网页。

二、iOS

iOS 的调试比较简单,可以借助 Safari 浏览器实现。

1)显示开发

首先显示 Safari 浏览器的“开发”菜单。

以 macOS 为例,偏好设置 > 高级,然后勾选“在菜单栏中显示开发菜单”。

2)开启调试

然后是开启 iPhone 的 Safari 调试模式,设置 > Safari浏览器 > 高级 > 网页检查器 > 启用。

3)两端联调

现在就可以开始调试了,将手机用数据线与电脑连接。

在访问页面后,选中开发菜单,找到对应的手机,就能看到访问中的网页地址。

4)调试窗口

点击页码地址,进入调试窗口,常规的诸如网络、元素等调试模块都有。

除了能映射客户端 WebView 中的网页之外,Safari 浏览器中的页面也能同步映射到调试窗口。

三、Android

Android 配置调试的过程比较波折,可以借助 Chrome 浏览器实现。

1)显示开发者选项

首先需要在手机中显示开发者选项,默认是隐藏的,各款手机的开启过程可能略有不同。

以小米为例,我的设备 > 全部参数与信息 > MIUI版本,点击 5~8 下,就会有一个开发者模式开启的提示。

2)开启 USB 调试

然后进入更多设置,翻到最后就能看到开发者选项菜单,进入后,开启 USB 调试。

3)Chrome Inspect

接着打开电脑的 Chrome 浏览器,输入 chrome://inspect。

现在可以在客户端中访问网页,下图的 WebView 表示客户端环境,点击 inspect。

应该是弹出控制台,但是却发生了错误,提示 HTTP/1.1 404 Not Found。

这是因为 Android System WebView 的版本低于电脑 Chrome 浏览器的版本,前者是 117,而后者是 120。

如果移动端的 Chrome 浏览器版本较低,那么在调试时,也会出现同样的问题。

若未出现上述异常,可直接跳过下一节的版本升级。

4)版本升级

两者的解决办法都是升级,一个是升级 Android System WebView,另一个是升级 Chrome 浏览器。

而 Android System WebView 需要到 Google Play Store 中更新。

由于图中已经是最新版本,因此没有显示更新按钮。

在更新成功后,以小米为例,在开发者选项中,选中 WebView 实现,就能看到当前版本。

5)调试窗口

重新访问网页,提示 Remote browser is newer than client browser. Try `inspect fallback` if inspection fails。

也就是说,客户端的版本高于电脑浏览器,那么可以点击 inspect fallbak 按钮。

在点击后,就能正式出现完整的调试窗口,左边是网页,右边是控制台。

当有一端移动网页时,另一端也能看到在移动,两者实现了同步。

参考资料:

iOS UIWebView与WKWebView 那些事

Web 浏览器相关的一些概念

小米手机开发者选项在哪?

Chrome远程调试

MIUI 更新 WebView 组件

什么是服务网格?

服务网格是一个软件层,用于处理应用程序中服务之间的所有通信。该层由容器化微服务组成。随着应用程序的扩展和微服务数量的增加,监控服务的性能变得越来越困难。为了管理服务之间的连接,服务网格提供了监控、记录、跟踪和流量控制等新功能。它独立于每项服务的代码,这使它能够跨网络边界和多个服务管理系统工作。

为什么需要服务网格?

在现代应用程序架构中,您可以将应用程序作为可独立部署的小型微服务的集合来构建。不同的团队可以构建单独的微服务并选择其编程语言和工具。但是,微服务必须进行通信,应用程序代码才能正常运行。

应用程序性能取决于服务之间通信的速度和弹性。开发人员必须跨服务监控和优化应用程序,但由于系统的分布性质,他们很难获得可见性。随着应用程序的扩展,管理通信变得更加复杂。

服务级别的可观测性

随着部署的工作负载和服务越来越多,开发人员发现很难理解所有服务是如何协同工作的。例如,服务团队想知道他们的下游和上游依赖关系是什么。他们希望更清楚地了解服务和工作负载在应用程序层的通信方式。

服务级别控制

管理员希望控制哪些服务相互通信,以及它们执行哪些操作。他们希望对微服务架构中服务的行为、策略和交互进行精细的控制和治理。强制执行安全策略对于监管合规至关重要。

服务网格有哪些优势?

服务网格提供了一个集中的专用基础设施层,用于处理分布式应用程序中复杂的服务到服务通信。

服务发现

服务网格提供自动服务发现,可以减少管理服务端点的运维负担。它们使用服务注册表来动态发现和跟踪网格中的所有服务。无论服务位于何处或底层基础设施如何,都可以无缝地相互查找和通信。您可以根据需要部署新服务来快速扩展。

负载均衡

服务网格使用各种算法(例如循环算法、最少连接或加权负载均衡)在多个服务实例之间智能地分配请求。负载均衡可提高资源利用率并确保高可用性和可扩展性。您可以优化性能并防止出现网络通信瓶颈。

流量管理

服务网格提供高级流量管理功能,可对请求路由和流量行为进行精细控制。下面是几个示例。

流量分割

您可以将传入流量划分到不同的服务版本或配置中。网格将一些流量引导到更新后的版本,从而以受控方式逐步推出变更。这样可以实现平稳过渡,并最大限度地降低变更的影响。

请求镜像

您可以将流量复制到测试或监控服务进行分析,而不影响主请求流。镜像请求时,您可以深入了解服务如何在不影响生产流量的情况下处理特定请求。

金丝雀部署

您可以将一小部分用户或流量引导到新的服务版本,而大多数用户则继续使用现有的稳定版本。在有限的接触范围内,您可以在现实环境中试验新版本的行为和性能。

安全性

服务网格提供安全通信功能,例如双向 TLS(mTLS)加密、身份验证和授权。双向 TLS 支持服务间通信中的身份验证。它通过加密流量来帮助确保数据的机密性和完整性。您还可以强制执行授权策略,以控制哪些服务访问特定端点或执行特定操作。

监控

服务网格提供全面的监控和可观测性功能,可深入了解服务的运行状况、性能和行为。监控还支持故障排除和性能优化。以下是您可以使用的监控功能示例:

  • 收集延迟、错误率和资源利用率等指标,以分析整体系统性能

  • 执行分布式跟踪,查看多个服务中请求的完整路径和时间

  • 在日志中捕获服务事件,用于审计、调试和合规目的

服务网格是如何工作的?

服务网格从单个服务中移除控制服务间通信的逻辑,并将通信抽象到自己的基础设施层。它使用多个网络代理来路由和跟踪服务之间的通信。

代理充当组织网络和微服务之间的中间网关。所有进出该服务的流量都通过代理服务器路由。单个代理有时被称为 sidecar,因为它们是分开运行的,但在逻辑上位于每个服务旁边。这些代理一起构成了服务网格层。

image

服务网格架构中有两个主要组成部分:控制面板和数据面板。

数据面板

数据面板是服务网格的数据处理组件。它包括所有 sidecar 代理及其功能。当一个服务想要与其他服务通信时,sidecar 代理会采取以下操作:

  • sidecar 拦截请求

  • 它将请求封装在单独的网络连接中

  • 它在源代理和目标代理之间建立安全的加密通道

sidecar 代理处理服务之间的低级消息传递。它们还会实施断路和请求重试等功能,以增强弹性并防止服务降级。服务网格功能(例如负载均衡、服务发现和流量路由)在数据面板中实施。

控制面板

控制面板充当服务网格的中央管理和配置层。

使用控制面板,管理员可以在网格内定义和配置服务。例如,他们可以指定服务端点、路由规则、负载均衡策略和安全设置等参数。定义配置后,控制面板将必要信息分发到服务网格的数据面板。

代理使用配置信息来决定如何处理传入的请求。它们还可以接收配置更改并动态调整其行为。您可以实时更改服务网格配置,而无需重新启动或中断服务。

服务网格实现通常在控制面板中包括以下功能:

  • 用于跟踪网格内所有服务的服务注册表

  • 自动发现新服务并删除非活动服务

  • 收集和聚合遥测数据,例如指标、日志和分布式跟踪信息

image

什么是 Istio?

Istio 是一个开源服务网格项目,设计为主要与 Kubernetes 配合使用。Kubernetes 是一款开源容器编排平台,用于大规模部署和管理容器化应用程序。

Istio 的控制面板组件本身作为 Kubernetes 工作负载运行。它使用 Kubernetes 容器组(一组共享一个 IP 地址的紧密耦合的容器)作为 sidecar 代理设计的基础。

Istio 的第 7 层代理在与主服务相同的网络环境中作为另一个容器运行。从这个位置,它可以拦截、检查和操作所有通过容器组的网络流量。但是,主容器不需要任何改动,甚至不需要知道这种情况正在发生。

开源服务网格实施面临哪些挑战?

以下是与 Istio、Linkerd 和 Consul 等开源平台相关的一些常见服务网格挑战。

复杂性

服务网格引入了其他基础设施组件、配置要求和部署注意事项。它们的学习曲线很陡峭,这要求开发人员和操作人员获得使用特定服务网格实施方面的专业知识。培训团队需要时间和资源。组织必须确保团队具备必要的知识,以了解服务网格架构的复杂性并对其进行有效配置。

运维管理费用

服务网格会带来部署、管理和监控数据面板代理和控制面板组件的额外开销。例如,您必须执行以下操作:

  • 确保服务网格基础设施的高可用性和可扩展性

  • 监控代理的运行状况和性能

  • 处理升级和兼容性问题

必须仔细设计和配置服务网格,以最大限度地减少对整个系统的性能影响。

集成挑战

服务网格必须与现有基础设施无缝集成,才能执行其所需的功能。这包括容器编排平台、网络解决方案和技术堆栈中的其他工具。

在复杂多样的环境中,要确保与其他组件的兼容性和顺利集成可能具有挑战性。要更改 API、配置格式和依赖关系,需要进行持续的规划和测试。如果您需要在堆栈中的任何位置升级到新版本,也是如此。

今天斗胆来跟大家分享一下作为一位阿里的P9是什么样的体验?

首先还是简单给大家介绍一下阿里的p9是属于一个什么样的层级?阿里p9中文名称叫做资深技术专家,一般大概带的团队有从几十人到一两百号人不等,具体人数根据业务情况而定,但是范围一般都是至少三四十人起步,薪资的范围也一般在250万~500万之间。如果之前拿到了一些股票,就是股票还增值的话,可能范围还会更大,能轻松超过500万。

同时p9一般都有绩效最终决定权、薪资调整权、奖金池分配权等核心权利。不过随着阿里的业务逐渐走下坡路,现在想必大部分p9的薪资也在大幅度缩水,这是后话了。

综合来说,p9可以决定普通打工人的晋升调薪和奖金,已经是相当大的权利了。

p9我们对外的名称一般都称之为技术总监,也就是二级管理,下面一般还有一级主管,在阿里也属于中层管理了,再上去就是副总裁高管级别,摸到天了。

那当然我还是远远够不上阿里p9的,差了好几个数量级,只不过我的主管刚好就是一位优秀的p9,那么我就从一个下属的身份来描述一下我日常的观察,作为一位阿里p9,他的核心定位是什么?他对阿里的价值是什么,他平常到底是怎么样工作的?

首先我们一定要明白不同的等级对于个人能力要求是完全不一样的,即不同层级要求的JobModel是不同的,所以不能用线性眼光去类比p9。

比如对于一个刚刚毕业的工程师,对于他们来说最重要的是执行力,也就是要完成上司给你交代的任务。你只要把这个任务交代的漂亮了,就是合格的,如果你在这个交代的任务之外,你还做了更多出彩的事情,那么你就是优秀的。大部分程序员都在执行层面努力,少数能晋升到下一个级别。

除了一线的执行员工之外,再上一层就是一线的主管。一线的主管和执行员工不同,他就有了实际的管理权利,他能够定绩效,能够提晋升,有汰换权利。从一线员工到主管已经是一个质的飞跃了,一般主管管理的范围可能在几个人到几十号人之间,一般根据情况快则五六年,慢则十来年才能从一线小兵晋升到主管。

对于一个主管来说,主要就是“定目标跟过程拿结果”,定目标主要就是要分拆更上层领导的目标。对于一名一线主管来说,最重要的是能够理解组织的目标,能够分解组织的目标,并且还能够组织下面的团队,能够顺利的完成目标。如果在这个目标能够顺利完成的同时,还能够做一点创新,实现与众不同的功能,那么还有可能拿到更好的结果。

很多时候技术主管本身也要有强大的一个自我的执行能力,才能给团队做榜样。所以到了主管,还是很讲究执行能力,只不过这个执行更偏重于团队的整体执行。

那么对于一个p9同学来说,他直接管的一般就是几名主管,也有直接带一线的同学,看起来他好像管了四五十号人的大团队,但是基本上都是间接管理,所以对于一线执行的细节他基本上是不清楚的,也不排除部分p9事必躬亲。综合来说,这也意味着不应该要求一个p9能够去亲自去一线干活,一个人不睡不休一天也才24小时,也意味着p9不应该过于关注局部细节,而更应该关注全局的核心产出。

那么p9的存在到底是什么价值呢?用我们那句阿里话来说就是“无中生有”。

p9再往上面走可能就到了组织的最高管理层,也就是俗称的“高管”,在最高管理层往往只会定大致的方向,比如逍遥子就指定了“All In无线”,而不会定具体的策略,具体怎么做,就交给p10和p9去解决就行了。

因此p9最重要的价值,就是把高层方向性的战略解读成可以落地的清晰策略,同时通过敏锐嗅查到的市场机会反哺高层。

换一句通俗的话来说就是要在组织规定的大方向上“造概念,谈概念,落概念”。

当然很多人认为在高层里面非常喜欢讲口号,做空想,画大饼,而没有实际落地的功夫和能力。这里面存在着一个很大的误区,对于一个高层的负责人来讲,本身就要具备长远的观察力。试想一下,如果一个高层负责人都是鼠目寸光,毫无远见,怎么可能带好士兵打顺一场战役?想当年就是在全国人民都不信的情况下,阿里做成了支付宝,阿里云。当然不排除很多水货高管混迹其中,这是另外一个话题了。

所以p9一个非常重要的工作,就是要经常和p10或ceo沟通讨论,力争可以拿到最多的资源来完成组织的战略。我们经常可以看到,p9要和公司核心一号位走得很近,进不了核心决策权的p9未来也不可能有好的发展机会,进而整个团队也不会拿到什么好机会。

和我们想象中不一样,p9很难浑水摸鱼,反而是最警小慎微的一个角色。

以钉钉为例,比如公司CEO提出了一个要做“数字化”的方向,那么对于p9来说就要解读和理解什么叫“数字化”,然后怎么样用一个下属能够听懂的方式把它抽象出一个个具体的概念,比如“数据消费新方式”“数据驱动业务”等概念,并且能够把这个概念再分解到各个团队,让不同的主管来执行。

概念确定之后,就是要充分让团队形成共识,就是要通过各个方式去让主管来理解要做的事情,不管是通过PPT,文档还是脑暴会的方式,都要反复输出自己的理念、理解和策略。这就是强化输出,也是一种“说”的能力。让团队所有人相信这件事能做成。

俗称“洗脑”,换个高雅的说法,叫做“共同愿景”,很反差的感觉,但是佛魔本来就是一念之间,就像马云说过的,相信相信的力量。

最后当这个概念的分解清楚的时候,又要保证持续的对下面一级主管进行跟进,确保他们能够让一线的同学能够执行到位。同时在这个过程中也要不断的去纠偏,对焦和调整,能够持续的能够使自己的概念落地。比如我们目前的p9就是采用了主管周会的方式来实现同步我们各个团队的进展。

p9就是通过这样的周会把组织最高目标的更换或者迭代的情况及时同步给我们,同时也可以根据我们的一线执行的信息输入反馈给p9。p9主打一个承上启下,守正出奇。

除了在主管这个层面上的会议之外,作为总监还要构建整个团队的文化价值观和执行力。对于我们大团队来说,我们会以月会的方式把所有的同学都拉到一个会议上,做相关的信息同步,文化分享,主管述职,优秀同学表彰等等。

用这种方式可以使得整个大团队的层面大家能够相互了解,相互协同和相互认可。通过这种比较正式的沟通方式能够了解到别的团队成员在做什么,能够跟我配合什么,能够得到什么样的新的化学反应。

另外,每个同学可以感知到整个大团队鼓励的是什么,不鼓励的是什么,能够使整个团队都了解到组织最新的价值观和文化,力往一处使。

所以对于一名p9来说往往难度是最大的,因为在他这个层面,他个人的主观能动性甚至决定了整个团队的生死和发展。一个有决策的人,才能对一个组织负责,而p9就是大公司里,除了ceo高质量的决策外,p9则是最能够决定组织执行力的人。p9有点像古代出征的大将,兵熊熊一个,将熊熊一窝,这是极为明显的例子。

因此,承接高层战略,使得无中生有,通过强大的落地执行力和大团队管理能力达成组织战略目标,构成了一名p9最核心的竞争力。

更多原创内容,可以关注公众号:ali老蒋

本文分享自华为云社区《
静态分析工具的评估测试
》,作者: Uncle_Tom。

1. 垂直极限

还是先说故事。那是 2014 年参加的一个测试驱动(TDD)的培训,培训是 TDD 推广的志愿者组织的,在一个咖啡馆里搞的,周末两天的免费培训。

培训过程中的一张图和一个视频让我至今记忆尤新。

1.1. 一张图

上面的两个图(原来培训的那个图找不到了,自己随手涂鸦了一下)。

左边是经过完整的系统的测试的软件产品,每个节点都通过测试,这样一层层的搭建起来的系统。看着就坚实可靠。

右边的测试则是随意的,很多地方都缺失了。任何一个风吹草动,一个异常都可能造成整个大厦倾覆。

不用说大家立刻就可以看懂,那个软件产品更可靠,更让人放心。

1.2. 一段影像

视频是电影《垂直极限》中开头的一段。虽然过去 20 多年了,但这部电影仍然被奉做山难电影的经典之作,有兴趣的朋友还是可以找来看一看的。

Vertical_limit

一个风和日丽的日子里,老爸带着儿子、女儿在一个高耸、陡峭的山峰上攀岩。他们已经来到了山峰的一半,在他们的上面个还有另一群攀岩爱好者。

老爸一边整理着自己的安全栓(攀岩过程中打在岩石中,然后利用登山绳固定自己的安全扣),一边对儿子说:“检查下你妹妹的安全栓。”

儿子看向下面的妹妹,妹妹说:“告诉他我们已经不是小孩子了。”

儿子打趣的对老爸说:“爸,她还需要一个安全栓。”

爸爸看向下面的兄妹两人说:“万一出事,那样可撑不住。安妮,我不管你有多老练,聪明的攀岩者都会做好安全措施,两个栓才安全,三个更好。在岩壁上再放个安全栓,我们才继续攀。”

女儿生气的对爸爸说:“爸,你别听哥哥的,他在开玩笑,我放了三个安全栓。”

电影总是这样,一段舒缓的节奏后面,总会有段紧张的让人喘不过气来的场面。不出意外,意外就会发生。

话音未落,先是一个登山包从山上面坠落下来,快速的经过三人。上面的菜鸟在登山的过程中背包意外脱落,帮着的圣山包的登山绳造成一连串的连锁反应,两个登山者也不幸地被连带着坠落下去。老爸、儿子也被着一连串的变故拖累到挂在了半空中,女儿的三个安全栓无法承担三个人的重量,一个、两个先后崩飞,只靠着最后的一个安全栓勉强的维持着女儿,以及挂在半空的老爸和儿子。最后在老爸的要求下,儿子不得不割断了绳索,女儿和儿子看着老爸坠了下去。

看到这里,大家都不禁须臾不已,再看看上面的图,对系统的完整的测试有了更深刻的认识。

1.3. 思考

在软件的开发过程中,每一个节点的测试都是在为系统增加一个安全栓。由这样一层层搭建起来的系统,整个系统才是安全、可靠的。否则任何一个意外都可能将整个系统带飞。做工具和做单点能力验证不同,需要更多的测试节点来保障工具的稳定性和可靠性。这个从学院里出来的大部分朋友还没有意识到这个问题,加之项目的管理的问题,就会把各种坑坑洼洼带到生产中,使后期维护陷入无尽的深渊。

大家都喜欢做 0 到 1 的事情,出彩啊。很少有人愿意做为 0 到 1 填坑的基础工作,但一个工具要生存,能够赢得最终的胜利,需要无数的这些幕后英雄。就像长津湖战役一样,指挥很重要,但更多的是需要后面无数的无名英雄,那些人才是撑起整个胜利的英雄。

那么对于静态分析工具,我们该如何构建一个稳定、可靠的静态分析工具?如何评价一个静态分析工具的检查能力?

自从程序的诞生,程序分析便紧随其后,人们视图通过一个程序来分析编制的程序,保证编制程序运行结果的正确性。尽管后面的莱斯定理给出了这个问题的“不可判定性”,但并不会妨碍程序分析在这方面的卓越表现。

尽管现在大模型能够帮助我们生成程序代码和对已有程序进行问题检查,这似乎可以绕过我们一值以来使用的模式匹配的检查方式,可以简化模式的提炼,和根据模式再编写检查规则。 但如何保障生成代码的安全性和可靠性,以及如何评估大模型的能力,仍然将是人工智能在今后很长一段时间需要解决的问题。

通过下面对三组测试用例的分析,希望能够给静态分析工具的测试和评估给大家一个指导性的启发。

2. Juliet Java、C/CPP 测试用例集

2.1. 创建背景

2005 年美国国家标准与技术研究院(National Institute of Standards and Technology (NIST)),简称NIST,下属的软件质量组,成立了软件保障指标和工具评估项目(Software Assurance Metrics And Tool Evaluation (SAMATE)),简称SAMATE项目。项目组的主要目的是通过开发支持软件工具评估的方法、衡量工具和技术的有效性以及识别工具和方法中的差距来改善软件保障,主要工作包括定义错误类,收集具有已知错误的程序语料库,以及更好地了解工具的有效性。

围绕这个目标,软件保障指标和工具评估项目(SAMATE)建立了两个子项目:

  • 软件保障参考数据集(Software Assurance Reference Dataset (SARD)),简称SARD,用于收集整理C、C++、Java、PHP 和 C#针对软件弱点的测试用例集;

  • 软件工具博览会(Static Analysis Tool Exposition (SATE)),简称SATE,用于工具制造商对用例进行测试和工具研讨。截至 2021 年,已经举办了六场 SATE 活动。

2.2. Juliet 用例集简介

Juliet 测试用例集就是软件保障参考数据集(SARD)下的一个是用于检测 C/C++ 和 Java 程序已知缺陷的集合。

2010 年 12 月 1.0版本。Juliet 测试用例集最早的 1.0 发布于2010 年 12 月,由软件保障指标和工具评估项目(SAMATE)的开发团队完成,名字选取了当时软件保障参考数据集(SARD)的第十个贡献者:国际无线电(International Radiotelephony)的字母表中的第十个单词 “Juliet” 而的得名。

2011 年 1.1 - 1.1.1 版本。Juliet 1.1 版本 是 Juliet 的开发团队根据多个因素为选定的缺陷创建了测试用例,包括团队的经验、缺陷的重要性或严重性以及其出现的频率。测试用例涵盖了 2011 年 CWE/SAN TOP 25 个最危险的编程错误中的 14 个。剩下的 11 个缺陷是设计问题,例如 CWE-862 授权机制缺失 和 CWE-250 带着不必要的权限执行,这些问题不适合用静态分析的方式来检测,所以未包含在测试用例中。

2012 年 1.2 版本。2012 年的 Juliet 1.2 版本。基本形成了现在使用版本。是我们现在使用最多的版本,程序分析、自动修复、深度学习的论文基本上都使用了这里面的用例,用于证明理论、工具的有效性。

2017 年 1.3 版本。只对1.2 版本的个别错误进行了修正。

  • Juliet C/C++ 涵盖 118 个 CWE 问题,1689 个场景,用例数: 181,140, 其中正例:117,041, 反例:64,099。
  • Juliet Java 涵盖 112 个 CWE 问题,933 个场景,用例数: 96,537, 其中正例:67,656, 反例:28,881。

2022 年 1.3.1 版本。增加了SARIF 的支持,试图通过SARIF 精确的给出告警的位置信息,但对数据流的支持的并不好,不能完全依靠现有的SARIF版本。

注:关于 SARIF请参考:

2.3. Juliet 用例构造的特点

2.3.1. 统一明确的命名方式

测试用例使用CWE作为命名和组织的基础。测试用例力求对目标缺陷使用最具体的CWE条目。每个测试用例文件与一个CWE条目相关联。

2.3.1.1. 测试用例文件名命名规范

测试用例名由四个元素的组合唯一标识:

组成部分 描述 是否强制
CWE 字符
CWE ID CWE的编号
_ 分隔,下划线
CWE短描述 CWE英文名称的缩短版本,单词之间用下划线"_"连接
__ 分隔,两个下划线"_"
问题场景 表示用例场景。场景描述单词之间用下划线"_"连接
_ 分隔,下划线,问题场景和案例编号之间进行连接
案例编号 基础案例编号采用“01”
一个案例多文件定义 对同一个案例,如使用多个文件,可采用a,b等进行区别,或采用下划线加不同的作用定义,例如_good或_bad,表示正例或反例

例如:

单文件用例:

CWE190_Integer_Overflow__byte_console_readLine_add_01.java

  • CWE190: CWE 的编号,可以在CWE网站上查询到这个编号对应的问题:
    https://cwe.mitre.org/data/definitions/190.html;
  • Integer_Overflow:CWE190 描述的简写,整型溢出。CWE190 的完整描述是:整型溢出或越界折返;
  • byte_console_readLine_add: 场景描述:通过readLine函数从控制界面读取字节流;
  • 01:代表基础用例。

多文件用例, 下面的一组文件构成了一个用例:

  • CWE190_Integer_Overflow__byte_console_readLine_add_81_bad.java
  • CWE190_Integer_Overflow__byte_console_readLine_add_81_base.java
  • CWE190_Integer_Overflow__byte_console_readLine_add_81_goodB2G.java
  • CWE190_Integer_Overflow__byte_console_readLine_add_81_goodG2B.java
  • CWE190_Integer_Overflow__byte_console_readLine_add_81a.java
    • CWE190: CWE 的编号,可以在CWE网站上查询到这个编号对应的问题:
      https://cwe.mitre.org/data/definitions/190.html;
    • Integer_Overflow:CWE190 描述的简写,整型溢出。CWE190 的完整描述是:整型溢出或越界折返;
    • byte_console_readLine_add: 场景描述:通过readLine函数从控制界面读取字节流;
    • 81: 数据流案例,案例将参数传递给通过引用调用的抽象方法;
    • _bad: 表示这是个反例的文件;
    • _base: 表示这个是用例的主文件;
    • _goodB2G: good 表示这是个正例的文件,同时 B2G 表示污点(Bad)会经过清洗到达(2(to))爆发点,但由于做了清洗,变成了无污染的,不会引起问题(Good);
    • _goodG2B: good 表示这是个正例的文件,同时 G2B 表示污点经过清洗变成无污染(Good)到达(2(to))爆发点(Bad),但由于是无污染的数据,也不会引起问题;
    • a: 表示这是个辅助文件。

2.3.1.2. 测试用例函数名命名规范

有问题(反例)函数,通常以bad命名, 并可使用正则表达式匹配:
^bad$
;

没问题(正例)的函数,通常以good命名, 并可使用正则表达式匹配:
^good$
;

对于用例中存在多个正例的函数,有以下三种命名方式,可使用正则表达式匹配:
^good(\d+|G2B\d*|B2G\d*)$
;

  • 默认或通用的方式,采用例如: good1()、good2()、good3()的命名方式;
  • 当一个好的源将安全数据传递到一个潜在的坏接收器时,采用例如:goodG2B()、goodG2B1()、goodG2B2()、goodG2B3() 的方式命名;
  • 当不良源将不安全或潜在不安全的数据传递给良好源时,采用例如:goodB2G()、goodB2G1()、goodB2G2()、goodB2G3()的方式命名;
  • 对于数据流用例还遵守以下命名方式:有问题的污染源,采用正则表达式:
    ^badSource$;
    有问题的爆发点,采用正则表达式:
    ^badSink$;
    无问题的污染源,采用正则表达式:
    ^good(G2B\d*|B2G\d*)?Source$;
    无问题的爆发点,采用正则表达式:
    ^good(G2B\d*|B2G\d*)?Sink$

2.3.1.3. 命名规则在测试中带来的好处

从文件名就能判断出检测问题、场景、适用的案例类型,有的还可以知道文件是正例、还是反例;

从发生问题的函数名就可以知道,所处的函数是正例,还是反例;

通常检测工具都会给出问题所在的文件和函数,这样在查看问题的时候能够立刻判断出检测结果的有效性;

严格的命名方式,也便于使用程序自动化的检查结果做出快速的判断。

2.3.2. 每个问题给出发生问题的可能场景

Juliet 还为每个问题(CWE)枚举了一些发生场景,一些场景还使用模板结合控制流和数据流进行了枚举,从而测试更加广泛的问题发生的可能性,以提高问题的覆盖率。

Juliet 一共采用了三类模板:

  • 控制流模板(point-flaw)
  • 单一污点源和爆发点模板(source-sink);
  • 多污点源和爆发点模板(sources-sinks);

Juliet Java

  • Juliet Java 一共 122 个 CWE, 覆盖 933 个场景。
  • 用模板生成的场景 823 个,这包括:point-flaw: 92 个场景;sources-sink: 197 个场景;sources-sinks: 534 个场景;
  • 单一场景 110 个。

Juliet C/C++

  • Juliet C/C++ 一共 118 个 CWE, 覆盖 1689 个场景。
  • 用模板生成的场景 1509 个,这包括:
Template C CPP Total
point-flaw 259 18 277
sources-sink 499 233 723
sources-sinks 357 152 509
Total 1106 403 1509

而单一场景 180 个。

2.3.3. 每个场景使用不同的案例进行覆盖

Juliet 用例还真对每个场景结合控制流和数据流进行了枚举,以达到每个场景在不同代码条件下的检测能力的覆盖。

从下表我们看到类型基本上涵盖了编程语言通常所需要的基本语法,例如条件判断(if),条件分支(swith),循环(while,for),函数间和程序间不同层数的调用,以及不同类型的参数传递方式。

案例类型明细

案列编号 案例类型(流类型) 类型描述 C CPP JAVA
01 基线——最简单的缺陷形式 Y Y Y
02 控制流 if(true) 和 if(false) Y Y Y
03 控制流 if(5==5) 和 if(5!=5) Y Y Y
04 控制流 if(PRIVATE_STATIC_FINAL_TRUE) 和 if(PRIVATE_STATIC_FINAL_FALSE) Y Y Y
05 控制流 if(privateTrue) 和 if(privateFalse) Y Y Y
06 控制流 if(PRIVATE_STATIC_FINAL_FIVE==5) 和 if(PRIVATE_STATIC_FINAL_FIVE!=5) Y Y Y
07 控制流 if(privateFive==5) 和 if(privateFive!=5) Y Y Y
08 控制流 if(privateReturnsTrue()) 和 if(privateReturnsFalse()) Y Y Y
09 控制流 if(IO.STATIC_FINAL_TRUE) 和 if(IO.STATIC_FINAL_FALSE) Y Y Y
10 控制流 if(IO.staticTrue) 和 if(IO.staticFalse) Y Y Y
11 控制流 if(IO.staticReturnsTrue()) 和 if(IO.staticReturnsFalse()) Y Y Y
12 控制流 if(IO.staticReturnsTrueOrFalse()) Y Y Y
13 控制流 if(IO.STATIC_FINAL_FIVE==5) 和 if(IO.STATIC_FINAL_FIVE!=5) Y Y Y
14 控制流 if(IO.staticFive==5) 和 if(IO.staticFive!=5) Y Y Y
15 控制流 switch(6) and switch(7) Y Y Y
16 控制流 while(true) Y Y Y
17 控制流 for 循环 Y Y Y
18 控制流 goto 语句 Y Y N
21 控制流 流由私有变量的值控制。所有方法都包含在一个文件中。 Y Y Y
22 控制流 流由公共静态变量的值控制。source和sink在两个不同的文件里。 Y Y Y
31 数据流 在同一方法中使用数据副本流动 Y Y Y
32 数据流 在同一个函数中使用两个指向同一个值的指针的数据流 Y Y N
33 数据流 在同一函数中使用对数据的 C++ 引用 N Y N
34 数据流 使用包含两个访问相同数据的方法的联合(在同一函数内) Y Y N
41 数据流 作为参数从一个方法传递到同一类中的另一个方法 Y Y Y
42 数据流 在同一类中从一个方法返回到另一个方法 Y Y Y
43 数据流 数据使用 C++ 引用从同一源文件中的一个函数流向另一个函数 N Y N
44 数据流 数据作为参数从一个函数传递到通过函数指针调用的同一源文件中的函数 Y Y N
45 数据流 作为私有类成员变量从一个方法传递到同一类中的另一个方法 Y Y Y
51 数据流 在同一个包的不同类中作为参数从一个方法传递到另一个方法 Y Y Y
52 数据流 在同一个包的三个不同类中作为参数从一个方法传递到另一个方法 Y Y Y
53 数据流 在同一个包的四个不同类中作为参数从一个方法传递到另一个方法 Y Y Y
54 数据流 在同一个包的五个不同类中作为参数从一个方法传递到另一个方法 Y Y Y
61 数据流 在同一个包的不同类中通过一个方法的返回值传递到另一个方法 Y Y Y
62 数据流 使用 C++ 引用从不同源文件中的一个函数到另一个函数的数据流 N Y N
63 数据流 指向不同源文件中从一个函数传递到另一个函数的数据的指针 Y Y N
64 数据流 void 指向不同源文件中从一个函数传递到另一个函数的数据的指针 Y Y N
65 数据流 数据作为参数从一个函数传递到通过函数指针调用的不同源文件中的函数 Y Y N
66 数据流 在同一个包中的不同类中通过数组从一个方法传递到另一个方法 Y Y Y
67 数据流 在同一个包中的不同类中通过类从一个方法传递到另一个方法 Y Y Y
68 数据流 在同一个包中的不同类中通过类的成员变量从一个方法传递到另一个方法 Y Y Y
71 数据流 在同一个包中的不同类中通过类的引用从一个方法传递到另一个方法 N N Y
72 数据流 在同一个包中的不同类中将向量从一个方法传递到另一个方法 N Y Y
73 数据流 在同一个包中的不同类中将LinkedList 从一个方法传递到另一个方法 N Y Y
74 数据流 在同一个包中的不同类中将HashMap从一个方法传递到另一个方法 N Y Y
75 数据流 将序列化对象从一个方法传递到同一包中不同类中的另一个方法 N N Y
81 数据流 将参数传递给通过引用调用的抽象方法 N Y Y
82 数据流 将参数传递给通过指针调用的虚函数的数据 N Y N
83 数据流 通过在堆栈上声明类对象将数据传递给类构造函数和析构函数 N Y N
84 数据流 通过在堆上声明类对象并在使用后将其删除来传递给类构造函数和析构函数的数据 N Y N

2.3.4. 缺点

Juliet 可以说是堪称完美的测试用例集,但随着时间的推移,编程语言的迭代,它也显现出一些缺点:

  • 一些用例的写法开始变得老旧,可能不能胜任新的场景;
  • 没有对语言的所有语法特点做覆盖,特别是新的一些语法类型;比如:lamda 表达式;
  • 用例的设计存在大量的重叠,特别是对数据流的检测,每个场景都使用了相同的模板,产生了大量的用例。其实对于数据流的处理,完全可以分成:污染源、污染传递、污染清理、以及爆发四个不同的维度进行分别测试,这样可以大大提升测试效率。

3. OwaspBenchmark 测试用例集

3.1. 用例背景

OWASP(Open Worldwide Application Security Project (OWASP)) 基金会致力于通过其社区主导的开源软件项目、全球数百个分会、数万名成员以及举办当地和全球会议来提高软件的安全性。

3.2. 用例简介

OWASP Benchmark Project 是一个 Java 测试套件,旨在评估自动化软件漏洞检测工具的准确性、覆盖率和速度。

  • 1.0 版本于 2015年4月15日发布,共有 20983 个测试用例。
  • 1.1 版本于 2015年5月23日发布。1.1版本在前一版本的基础上进行了改进,确保每个漏洞区域都有真阳性和假阳性。
  • 1.2 版本于 2016年6月5日首次发布(1.2测试版于2015年8月15日发布)。自那以后,v1.2版本一直在不断调整。1.2 版本将测试用例数量控制在小于3000个测试用例,以便快速得到测试结果。

3.3. 用例设计和组成方式

下面以 1.2 版本为例进行说明。

3.3.1. 用例问题的覆盖

从下面这个表可以看出 Benchmark 更多的注重覆盖 Web 类的问题的检查,同时重点覆盖了 OWASP TOP 10 中的主要能够通过静态检查工具检查的问题。关于 OWASP TOP 10 可以参考:
CWE 4.6 和 OWASP TOP10(2021)

Benchmark 用例集主要以加密问题,以及注入类问题为主,这也巧合的与静态检查技术相互匹配。加密问题多用抽象语法树(AST)的遍历来返现加密函数,并对其做出判断。而注入类问题多用数据流的污点分析技术来追踪外部输入是否会对爆发点形成可达的路径。有关污点分析技术,可以参考:
使用污点分析检查log4j问题

检测问题 CWE TOP 25(2023) OWASP TOP 10(2021) 正确用例 错误用例 用例总数
CWE-22 对路径名的限制不恰当(路径遍历) 8 A01:2021-中断访问控制 135 133 268
CWE-327 使用已被攻破或存在风险的密码学算法 - A02:2021-加密故障 116 130 246
CWE-328 可逆的单向哈希 - A02:2021-加密故障 107 129 236
CWE-330 使用不充分的随机数 - A02:2021-加密故障 275 218 493
CWE-501 违背信任边界 - A04:2021-不安全的设计 43 83 126
CWE-614 HTTPS会话中未设置’Secure’属性的敏感Cookie - A02:2021-加密故障 31 36 67
CWE-643 XPath表达式中数据转义处理不恰当(XPath注入) - A03:2021-注入 20 15 35
CWE-78 OS命令中使用的特殊元素转义处理不恰当(OS命令注入) 5 A03:2021-注入 125 126 251
CWE-79 在Web页面生成时对输入的转义处理不恰当(跨站脚本) 2 A03:2021-注入 209 246 455
CWE-89 SQL命令中使用的特殊元素转义处理不恰当(SQL注入) 3 A03:2021-注入 232 272 504
CWE-90 LDAP查询中使用的特殊元素转义处理不恰当(LDAP注入) - A03:2021-注入 32 27 59

3.3.2. 问题场景的覆盖

Benchmark 用例对每个问题采用: 场景枚举 + 组合的方式完成用例的设计,并通过此方法形成问题的覆盖。

这里以我们熟悉的:CWE89 SQL注入问题来说明这种用例的设计方式。用例集中CWE 89 SQL注入问题一共有 232 个正例,272 个反例,共计504 个用例。

因为 SQL 是注入是通过外部不可信数据,传播到 SQL 脚本执行的位置而导致的安全问题。这个外部数据传播的过程可以分为:

  • 接收数据

用例使用了我们常用的从 http 请求中得到外部数据,然后将数据以不同方法存入不同类型的字段。用例中列举了下面 9 种不同的方法。例如放入:字符串、枚举、数组等。

  • 数据传递

用例接收到数据后,使用不同的传递方式,向程序内传递,并对信息采用不同的操作方式进行加工。用例中列举了下面 10 种不同的方法。例如:通过 创建一个新类然后调用函数传递、if 条件表达式、内部类等。

  • 问题爆发

最终数据会拼装成 SQL 语句,并通过不同的调用方式执行。用例中列举了 3 类,28 种不同的执行方式。

  • 场景组合

得到上面三种基本节点后,通过组合的方式形成用例。下表列举了 CWE89 SQL注入 272个反例组合的场景:

3.3.3. OwaspBenchmark 用例集的缺点

OwaspBenchmark 应为涵盖了Web应用的主要安全问题,使用例集基本上成为了Web 应用安全测试的基本用例集。但它也存在一些缺点。

  • 用例名只采用了简单的编号方式,从用例看不出测试目标:用例反应的问题、场景、正例、反例这些基本信息,而不得不给每个用例加了一个xml文件来说明这些用例的基础信息。
  • 用例缺少场景的描述,工具测试后,无法得到覆盖场景和非覆盖场景的统计信息,只知道覆盖率。具体哪些场景缺失,要一个个用例去自己分析。
  • 缺少检测语言语法级别的场景的覆盖,例如lamda表达式,工具在数据流的分析过程中,任何一个语法的不适配都会导致分析中断。

4. Alipay 测试用例集

4.1. 用例背景

针对xAST领域缺乏有效衡量技术能力标准的业界痛点,蚂蚁安全团队联合蚂蚁程序分析团队、浙江大学网络空间安全学院的20余位专家学者,共同设计了xAST评价体系及其测试样本套件Benchmark,致力于成为应用安全测试工具的“度量衡”。

  • 目标:打造具备行业共识的xAST能力评价体系技术标准

  • 价值:衡量xAST产品技术能力,指引xAST技术发展方向,辅助企业产品选型

4.2. 用例设计和组成方式

用例设计的核心思想是:分层设计,降低评价复杂度。

从底层到上层分成引擎能力、规则能力和产品化能力这三层。对这三层分别设计评价体系和测试样本,既降低了每一层评价的复杂度,又使测试结果可以直接反映问题出在哪一层。

看的出,用例集的设计者试图希望结合Juliet、Owaspbenchmark 的优点,在形成一种分层的评估测试方式。来完善前面两个用例集在语法层面的不足。

目前用例集只推出了一个雏形,还在建设中。

5. 理想的测试用例集

最后再来总结下,理想的测试用例集应该是怎样的。

  • 能够从用例的命名上,清楚的反映:测试问题、场景、正例、反例;或从目标函数上明确知道在这个函数内的告警是正确的告警,还是误报;
  • 能够覆盖业界主要的安全问题,例如:CWE TOP 25、OWASP TOP 10等常见的问题;
  • 能够覆盖检测语言的主要语法和语言的主要使用方式;
  • 能够有一定的场景枚举和组合,以增加测试用例的复杂度,这有点类似 fuzzy 测试了。

写在最后,测试用例集的结果,只能反映一个工具的基础能力,并不能取代通过实际的工程来打磨检查工具。

一般的程序员只需要在问题和实现上建立一条通道就好,但程序分析的程序员却需要考虑各种程序员实现问题的可能性。

6. 参考

点击关注,第一时间了解华为云新鲜技术~