65.9K
CodeProject 正在变化。 阅读更多。
Home

使用 OWASP ZAP 对 Microsoft 堆栈进行自动化渗透测试

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.84/5 (22投票s)

2014 年 1 月 16 日

CPOL

11分钟阅读

viewsIcon

95371

downloadIcon

1322

使用渗透测试工具 ZAP 和

引言

本文将介绍如何使用 OWASP ZAP 结合 Team Foundation Server (TFS) 和 C#,在 Microsoft 技术栈中进行自动化渗透测试。最终,我们将实现 TFS 构建能够针对我们选择的网站运行渗透测试。


背景

我参与的一个项目提出一个想法,即创建一个夜间构建,对一系列网站运行渗透测试。我开始调查渗透测试工具,发现有许多免费和商业的工具。你可以在 这个页面 了解一些可用的工具。

我决定使用 ZAP,因为它开源、正在积极维护,能够发现我们可能遇到的绝大多数问题,并且它有一个可以接入的 API。这肯定比我们最初一无所有的情况要好得多。

 

10,000 英尺视图

总的来说,我们的想法是创建一个命令行工具,该工具与 ZAP 交互,并将交互结果报告给构建服务器。主要步骤如下:

  • TFS 构建服务器调用命令行工具,提供要扫描的 URL
  • 命令行工具连接到 ZAP API 并启动扫描
  • 命令行工具查询扫描过程的状态,直到完成
  • 命令行工具查询扫描结果,进行聚合,并将结果返回给构建,以便构建可以输出它们。
 

设置

需要做几件事情来启动它:

  1. ZAP。您可以从 官方页面 下载。
  2. 命令行工具。可以从本文下载,也可以从 GitHub 获取。
  3. TFS 服务器,我们假设已经就绪。如果还没有,网上有很多关于如何安装和配置 TFS 的信息。安装并运行后,我们需要自定义构建定义,使其能够调用命令行工具。
  

ZAP

“OWASP Zed Attack Proxy (ZAP) 是一个易于使用的集成渗透测试工具,用于查找 Web 应用程序中的漏洞。

它旨在供具有广泛安全经验的人使用,因此非常适合渗透测试新手开发人员和功能测试人员,同时也为有经验的渗透测试人员提供了有用的补充。”(来源

您可以从 官方页面 下载。花一些时间熟悉该工具。该网站包含有用的信息,足以 让您入门。在阅读并理解 ZAP 的工作原理后,您就可以进行此项目所需的唯一配置更改了。

首先要做的是安装 ZAP。我将其安装在构建服务器上,但也可以单独安装。

在 ZAP 中,转到“工具”->“选项”->“本地代理”,然后设置您希望代理配置的地址和端口。在我的例子中,我将其配置为在地址 *localhost* 上运行(因为我希望 ZAP 和 TFS 运行在同一台服务器上,否则我将指定 *myServerName*),并在端口 *8090* 上运行。

 

完成这些操作后,您应该可以通过浏览器访问 ZAP 代理,访问 https://:8090。如果代理正在运行,您应该会看到一个类似于以下内容的页面。

 

      

就这样。ZAP 正在运行,其 API 可以被我们的命令行工具访问。

 

命令行工具

您应该将此工具部署到构建服务器上的某个位置,以便 TFS 可以访问它。您可以从本文附加的 ZAPPenTester.zip 文件中获取源代码,其中还包括编译后的文件,您可以将其复制到 TFS 服务器上的一个目录中。

在运行工具之前,您应该编辑 ZAPPenTester.exe.config 文件。该文件包含运行应用程序所需的配置设置。

请特别注意以下设置:

  • ZapStartFile:此项应指向 ZAP 的 exe 文件。命令行工具将使用此文件路径在连接到 API 之前打开 ZAP。
  • ZapWorkingDirectory:此项应指向 ZAP exe 文件所在的文件夹。
  • ZapProxy:此项应指向您在上一步中配置的代理 URL。命令行工具将在发出请求到您要扫描的网站时使用它。
其余设置可以保持不变。它们指定了 API 运行的端点。

在命令行工具部署并调整设置后,您应该能够运行它并获得第一个结果。令人兴奋!打开一个命令行窗口,然后传入要扫描的 URL 作为参数来运行该工具。例如:“*c:\<SomeFolderPath>\ZAPPenTester.exe http://www.mysitetotest.com*”。

 

   

正如您所见,该工具会报告扫描的进度。它首先启动 ZAP 爬虫,爬虫尝试查找网站的所有页面,在完成之后,它会开始扫描爬虫找到的页面。扫描完成后,它会显示发现问题的摘要。

如上图所示,该工具的输出将显示在 TFS 构建日志中。

接下来,我们将 TFS 构建连接到此工具。

 

TFS 服务器 

在我的场景中,我只想让构建对特定站点运行渗透测试,仅此而已。我不想它进行代码编译,也不想它进行部署,因为我们已经有进行编译和部署的持续集成构建了。然后,我们将新的渗透测试构建安排为每晚运行。

假设您已经有一个 TFS 服务器,您只需要创建一个新的构建(也可以重用现有的构建),并为其分配一个运行命令行工具的构建定义。

我首先创建了一个现有 Build Definition 的副本,作为我的新定义的起点。之后,我使用 Visual Studio 2013 编辑了新创建的构建定义。由于我不想编译代码、运行测试、部署等,我删除了构建模板的所有内容,只保留了主要的*Sequence*活动。

 

暂时忽略内部活动,添加它们将是我们的下一步行动。

在一个空的构建中,我们要添加一个调用命令行工具的活动。为此,转到 Visual Studio 工具箱,并将一个 *InvokeProcess* 活动拖到主 *Sequence* 活动中。您还应该将一个 *WriteBuildMessage* 活动拖到 *InvokeProcess* 活动的 *Handle Standard Output* 部分,并将一个 *WriteBuildError* 活动拖到 *Handle Error Output* 部分。您的定义应该与下图类似。

 

接下来,我们要创建一个方法,将要扫描的 URL 从构建传递到命令行工具。这样,我们可以为要测试的每个站点创建一个构建,并且我们的命令行工具是通用的,可以扫描任何被指示扫描的站点。

在构建定义的底部,您可以看到选项卡:“*Variables*”、“*Arguments*”和“*Imports*”。我们将创建一个名为 *UrlToScan* 的新参数,方向为 *In*,类型为 *String*。这将使我们能够与所有渗透测试构建共享此模板,让构建本身提供此参数的值。我们稍后会回到这一步。现在,只需创建一个新参数。

现在我们必须告诉 *InvokeProcess* 活动做什么。右键单击该活动,然后转到属性。应设置以下属性:

  • Arguments:[UrlToScan],这是我们上面创建的自定义参数的名称。
  • Display Name:您可以为 InvokeProcess 活动设置一个更友好的名称。
  • Filename:“FilePath\ZAPPenTester.exe”,即命令行工具在您的 TFS 服务器上的文件系统路径。




为了完成对构建定义的修改,我们需要更改 WriteBuildMessage 活动和 WriteBuildError 的属性。打开 WriteBuildMessage 的属性,并将 Message 属性设置为值 *stdOutput*。打开 WriteBuildError 的属性,并将 Message 属性设置为值 *errOutput*。

这样,我们的构建定义就完成了!只需将其保存并签入 TFS。这很重要,如果您不签入构建定义,构建服务器将无法拾取它。

为了完成构建,还需要进行两个小步骤。首先,确保您的构建使用的是新的构建定义。最后,确保在上面创建的 UrlToScan 参数中传递要扫描的网站 URL。

 

 

此时,构建应该已经准备就绪,我们可以运行它了。结果是我们得到了一个很好的日志,记录了工具在扫描期间所做的操作。

 

 

 

就是这样。我们成功创建了一个 TFS 构建,该构建报告了在网站中发现的安全问题。

 

命令行工具的实现

源代码可以从本文下载,也托管在 GitHub 上。

用于连接 ZAP API 的命令行工具是使用 Visual Studio 2013 开发的 C# 应用程序。其目标是连接到 ZAP API,指示 ZAP 运行扫描并报告检测到的警报。

当 ZAP 启动时,它也会启动 API。然后,我们可以执行诸如启动爬虫、检查爬虫状态、启动扫描器、检查扫描器状态、获取报告的警报列表等操作。我们可以选择以三种不同的格式从 API 获取响应:Json(这是工具使用的格式)、Xml 和 Html。您可以通过访问 http://zap 来查看完整的 API,前提是 ZAP 正在运行并已配置。您可以在 官方网站 上获取有关 API 的更多信息。

Visual Studio 2013 解决方案包含两个项目:一个是工具本身,另一个是一组单元测试。

 

 

有几个对象构成了应用程序的核心。下图显示了系统的组成以及每个组件的预期输入/输出。

 

  

 

主应用程序功能

应用程序分 5 个阶段运行:

  1. 打开 ZAP;
  2. 指示 ZAP 扫描目标网站。这在内部是一个两阶段的过程,首先爬取网站,然后扫描在爬虫阶段找到的页面;
  3. 请求扫描期间发现的警报;
  4. 关闭 ZAP;
  5. 聚合警报数据并输出。
 
var zap = ZapFactory.Create();
 
zap.Open();
zap.Scan(urlToScan);
Report report = zap.GetReport(urlToScan);
zap.Close();
 
new CommandLineReportPrinter().Print(report); 
       

 

打开 ZAP

打开 ZAP 意味着启动 zap.exe 应用程序。当应用程序启动时,代理也会启动,我们的工具就可以开始发出请求了。

public void Open()
{
    logger.Log("Opening ZAP");
    process = new Process
    {
        StartInfo =
        {
            FileName = settings.ZapStartFile,
            WorkingDirectory = settings.ZapWorkingDirectory,
            Arguments = "-daemon"
        }
    };
 
    process.Start();
    process.WaitForExit();
            
    Thread.Sleep(sleepTimeBetweenRequests);
} 
         

这里没有什么特别的。我们只是启动一个新进程来运行 ZAP,并给予它一些时间让 ZAP 打开,这通常需要 5-10 秒。

但有两点要注意:

  • 设置 WorkingDirectory 属性很重要,否则 ZAP 无法运行。我将其设置为 zap .exe 所在的路径;
  • 参数 -daemon 指示 ZAP 在没有 UI 的情况下运行。如果我们查看任务管理器,当 ZAP 运行时,我们会看到一个名为 *javaw* 的进程。
 

向 API 发出请求

控制台应用程序向 API 的 Json 端点发出请求。

public string MakeHttpRequest(string url, HttpResponseType requestResponseType)
{
    HttpClient client = PrepareHttpClient(requestResponseType);
    HttpResponseMessage response = client.GetAsync(url).Result;
 
    if (response.IsSuccessStatusCode)
    {
        return response.Content.ReadAsStringAsync().Result;
    }
 
    return null;
}
 
private HttpClient PrepareHttpClient(HttpResponseType httpResponseType)
{
    var httpClientHandler = new HttpClientHandler
    {
        Proxy = new WebProxy(settings.ZapProxy, false, new string[] { }),
        UseProxy = true
    };
 
    var client = new HttpClient(httpClientHandler);
    client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/" + httpResponseType.ToString()));
            
    return client;
} 
        

我们首先准备一个 HttpClient 对象,该对象配置为使用 ZAP 的代理并期望特定类型的响应,在此实现中是 Json。然后,我们使用该对象发出请求并返回结果。

 

扫描

如上所述,扫描是一个两阶段的过程。首先,我们必须启动 ZAP 的爬虫,在爬虫完成后,我们再启动扫描器。

public void Scan(string urlToScan)
{
    if (!spider.Start(urlToScan))
    {
        return;
    }
 
    do
    {
        Thread.Sleep(sleepTimeBetweenRequests);
    }
    while (!spider.HasCompleted());
 
    if (!scanner.Scan(urlToScan))
    {
        return;
    }
 
    do
    {
        Thread.Sleep(sleepTimeBetweenRequests);
    }
    while (!scanner.HasCompleted());
 
    logger.Log("Scan complete");
} 
      

我们启动爬虫,并在它未完成之前,持续检查其状态。当爬虫完成时,我们启动扫描器,并等待它完成。当此操作完成后,ZAP 应该已经完成扫描,并且警报列表可供检索。

报告

扫描完成后,我们可以从 ZAP 请求警报列表。

public Report GetReport(string url)
{
    logger.Log("Preparing report");
 
    string response = httpClientHelper.MakeHttpRequest(settings.ZapReportUrl, HttpResponseType.xml);
 
    if (response != null)
    {
        XDocument document = XDocument.Parse(response);
 
        IEnumerable<XElement> elements = document.Element("OWASPZAPReport").Elements("site").Where(e => e.Attribute("host").Value == new Uri(url).Host);
 
        var issues = new List<Issue>();
        foreach(XElement element in elements)
        {
            issues.AddRange(
                from e in element.Descendants("alertitem")
                select new Issue
                {
                    IssueDescription = e.Element("alert").Value,
                    RiskDescription = e.Element("riskdesc").Value,
                    TargetUrl = e.Element("uri").Value,
                    OriginalSiteUrl = element.Attribute("name").Value
                });
        }
 
        return new Report { Issues = issues };
    }
 
    return null;
}  

此方法看起来有点复杂,但其职责很简单。它向 API 发出请求,以 XML 格式获取警报列表(我没有找到 Json 端点),并将这些警报转换为 C# 对象。当方法完成时,我们就拥有一个将被后续聚合的错误列表。

public IEnumerable<IssueGrouped> AggregateReport(Report report)
{
    if (report != null)
    {
        return from issue in report.Issues
                group issue by new { issue.IssueDescription, issue.RiskDescription } into groupedIssue
                select new IssueGrouped
                {
                    IssueDescription = groupedIssue.Key.IssueDescription,
                    RiskDescription = groupedIssue.Key.RiskDescription,
                    IssueCount = groupedIssue.Count()
                };
    }
 
    return null;
}   

此方法将同一问题的所有出现次数聚合到一个项中,并记录问题发生的次数。

此时,警报数据就可以打印到控制台了。

 

未来增强功能

目前,控制台应用程序无法爬取启用了表单身份验证的网站区域。ZAP 支持此功能,API 也支持此功能,因此应该可以自动化身份验证,让爬虫查找受保护区域内的页面。有关此主题的更多信息可以在 官方网站 上找到。

 

结论 

ZAP 的学习曲线不高,通过阅读网站上的文档可以轻松掌握。

API 也非常简单,并且提供了对 ZAP 功能的大部分访问。对于本文的目的,API 没有任何缺失的功能。

我们使用 TFS 对我们的网站运行渗透测试的目标已经实现。

我希望 ZAP 继续发展,并期待为这个工具添加更多功能。

 

致谢

我要感谢 Patrick Kalkman(Code Project 个人资料Twitter)进行了代码审查,并帮助我克服了一些障碍。

我还要感谢 Simon Bennetts(Twitter)花费时间帮助我解决问题,以及他对开源社区的贡献,特别是他领导了 ZAP 项目。

 

© . All rights reserved.