我们今天来谈谈一下安全测试,特别是Web应用程序的安全测试。
网络应用程序安全测试的目的是确保应用程序具有安全性,能够保护敏感数据和系统免受未经授权的访问或攻击。这是开发过程中的一个重要步骤,因为它有助于在攻击者利用这些安全漏洞之前识别并修复它们。
它可以有多种形式,这取决于应用程序的需求和目标,比如漏洞扫描、静态代码分析、渗透测试或应用程序的动态测试等。
你可以想象,关于Web应用安全测试的话题非常广泛。本文重点介绍一款工具,它可以帮助开发人员和安全专业人员识别并修复Web应用中的漏洞。我们将看看如何将该工具与Selenium集成,最后我们将讨论自动化Web应用安全测试的优缺点。
ZAP(Zed Attack Proxy)
我们刚才提到的工具叫做ZAP。它是一个由Open Web Application Security Project (OWASP)维护的开源Web应用程序安全扫描器。
OWASP是一个全球性的非营利组织,致力于提升Web应用程序的安全性。为了实现这一目标,OWASP维护着多个项目和倡议,比如OWASP Top Ten、OWASP安全知识框架和OWASP Web安全测试指南。若想了解更多关于OWASP的信息,请访问他们的官方网站。
回到ZAP。它是一个用于Web应用安全测试的工具,通过代理客户端和Web应用之间的流量工作。当我们使用OWASP ZAP测试Web应用时,它会作为我们Web浏览器和应用之间的代理人,允许我们拦截和修改两者之间交换的请求和响应。它包含许多工具和功能,使我们能够分析和测试Web应用的安全性,包括Web代理、爬虫、扫描器以及多种自定义选项。
ZAP安装与启动
安装过程很简单。只需访问官方下载网站,下载适合你操作系统的 ZAP。下载完成后,打开安装程序并完成所有步骤。
https://www.zaproxy.org/download/
因为本文将使用ZAP API,所以我们对它的用户界面并不太感兴趣。因此,我们将不使用GUI启动应用程序,而是在终端(或命令提示符)中以无头模式启动它。
在Windows上,我们需要将自己定位到与`zap.bat`文件在同一文件夹中,并执行以下命令:
zap.bat -daemon -port 8090
ZAP将在本地主机上监听端口8090,但不会打开GUI界面。
在命令提示符中,我们会看到与此相似的一条消息:
[ZAP-daemon] INFO org.zaproxy.addon.network.ExtensionNetwork - ZAP is now listening on localhost:8090
如果我们在浏览器中打开“localhost:8090”,会看到如下页面:
定义目标
我们将使用ZAP工具中的一个名为“Active Scan”的工具,它会对网站执行已知的攻击,并通过这种方式来寻找潜在的漏洞。由于它攻击的是指定的网站,因此如果我们不是网站的所有者,就不应该这样做。
这里有几个可以帮助我们的替代方案。其中一个是OWASP Juice Shop。这是一个用JavaScript编写的不安全的Web应用程序,旨在教授初学者Web应用程序安全。这是一个“故意设计为不安全”的Web应用程序,允许用户练习识别和利用常见的Web漏洞,也就是说,非常适合我们自学。
要使用它,我们可以打开 GitHub,并遵循设置指南,也可以直接使用部署在 Heroku 上的实例。
https://github.com/juice-shop/juice-shop
ZAP工具操作
我们已经设定了目标,并且安装了 ZAP。接下来我们要做的是编写一个类,利用 ZAP 的一些核心功能。对于本文而言,我们感兴趣的功能是“爬虫”和“主动扫描”。
爬虫是一种工具,它可以自动爬取网页应用程序,以发现新的URL和内容。它通过从特定的URL开始,然后沿着同一域名下的其他页面的链接进行爬取。在爬取网站时,它向服务器发送HTTP请求并记录响应。它可以用于发现隐藏的内容或识别网页应用程序中的漏洞,例如跨站脚本(XSS)或SQL注入漏洞。
另一方面,主动扫描是一种分析Web应用程序以识别潜在漏洞的过程。在进行主动扫描时,ZAP会向目标应用程序发送一系列请求,并分析响应以识别可能的安全问题。
使用主动扫描的一个好处是它可以识别仅通过查看应用程序的源代码或手动测试应用程序可能无法发现的漏洞。然而,主动扫描也可能资源消耗量大,可能会导致目标应用程序出现性能问题,因此应谨慎使用。
这里需要说明的是,如果我们在爬虫之前运行了主动扫描,它将不会执行任何操作。为了理解其中的原因,我们需要解释一下所谓的“站点树”的概念。
ZAP中的站点树是网站应用程序结构的表示形式。它显示了由ZAP爬虫或手动测试发现的所有URL,并根据它们之间的相互关系以树形结构组织它们。它可以作为理解网站应用程序布局和组织结构的有用工具,并可以标识可能对测试或分析特别有用的应用程序区域。
在ZAP中,探索和攻击应用程序的概念是分离的。这意味着如果我们不运行“爬虫”程序,网站树将是空的,而“活动扫描”将无法理解Web应用程序及其内容,因此无法对其进行任何攻击。
编写扫描器类
首先,我们需要导入所需的依赖项。我们将创建一个Maven项目,并在pom.xml文件中导入ZAP客户端API、Selenium Java和TestNG:
<dependencies>
<!-- https://mvnrepository.com/artifact/org.zaproxy/zap-clientapi -->
<dependency>
<groupId>org.zaproxy</groupId>
<artifactId>zap-clientapi</artifactId>
<version>1.11.0</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.seleniumhq.selenium/selenium-java -->
<dependency>
<groupId>org.seleniumhq.selenium</groupId>
<artifactId>selenium-java</artifactId>
<version>4.7.2</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.testng/testng -->
<dependency>
<groupId>org.testng</groupId>
<artifactId>testng</artifactId>
<version>7.7.1</version>
<scope>test</scope>
</dependency>
</dependencies>
我们将把这个类命名为“SecurityScanner”,并初始化以下变量和对象:
import org.zaproxy.clientapi.core.ClientApi;
public class SecurityScanner {
static final String ZAP_ADDRESS = "localhost";
static final int ZAP_PORT = 8090;
private static final String ZAP_API_KEY = ""; //insert your API key
static ClientApi api = new ClientApi(ZAP_ADDRESS, ZAP_PORT, ZAP_API_KEY);
}
因为我们是在本地运行 ZAP,所以地址将是 `localhost`。我们将使用与通过命令行运行 ZAP 相同的端口。要获取 API 密钥,有两种方法:
打开ZAP GUI,导航至“工具”>“选项”>“API”。从 在那里,我们可以复制或生成一个新的API密钥。
或者我们可以在通过命令行启动ZAP时使用的相同命令中定义API密钥。我们只需要将以下内容附加到初始命令中: -config api.key=change-me-9203935709
让我们编写爬虫程序。使用ZAP客户端API实现它很简单。我们只需要从爬虫类中调用`scan`方法即可启动爬虫扫描。从`scan`方法提供的响应中,我们可以获取当前的扫描ID。我们将使用该扫描ID轮询爬虫的当前状态并以百分比形式打印出进度。一旦完成,我们将调用`results`方法并打印出所有已找到的URL。
我们的方法只使用一个参数,那就是我们目标的URL。
public static void executeSpiderScan(String targetUrl) {
System.out.println("Spidering target : " + targetUrl);
String scanId;
try {
int progress;
ApiResponse response = api.spider.scan(targetUrl, null, null, null, null);
scanId = ((ApiResponseElement) response).getValue();
do {
Thread.sleep(1000);
progress = Integer.parseInt(((ApiResponseElement) api.spider.status(scanId)).getValue());
System.out.println("Spider progress : " + progress + "%");
} while (progress < 100);
System.out.println("Spider completed!");
List<ApiResponse> spiderResults = ((ApiResponseList) api.spider.results(scanId)).getItems();
System.out.println("Following resources have been found:");
spiderResults.forEach(System.out::println);
} catch (Exception e) {
System.out.println("Exception caught: " + e.getMessage());
e.printStackTrace();
}
}
我们的主动扫描方法的第一部分与我们的爬虫扫描方法非常相似。区别在于我们现在从ascan类中调用方法:
public static void executeActiveScan(String targetUrl, String reportName) {
System.out.println("Active scanning target : " + targetUrl);
String scanId;
try {
int progress;
ApiResponse response = api.ascan.scan(targetUrl, "True", "False", null, null, null);
scanId = ((ApiResponseElement) response).getValue();
do {
Thread.sleep(5000);
progress = Integer.parseInt(((ApiResponseElement) api.ascan.status(scanId)).getValue());
System.out.println("Active scan progress : " + progress + "%");
} while (progress < 100);
System.out.println("Active scan completed!");
} catch (Exception e) {
System.out.println("Exception caught: " + e.getMessage());
e.printStackTrace();
}
}
与之前的方法相比,对于这个方法,我们希望生成一个HTML报告,因为其响应包含有关在测试的Web应用程序中发现的潜在漏洞的大量信息。
为了做到这一点,我们将在主动扫描完成后由 ZAP 生成的 URL 上编写一个 URL。我们将请求该 URL 的内容并将其写入存储在我们的报告目录中的一个文件中:
URL url = new URL("http://" + ZAP_ADDRESS + ":" + ZAP_PORT + "/OTHER/core/other/htmlreport/?apikey=" + ZAP_API_KEY);
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("GET");
int responseCode = connection.getResponseCode();
if(responseCode == 200) {
BufferedReader in = new BufferedReader(new InputStreamReader(connection.getInputStream()));
BufferedWriter out = new BufferedWriter(new FileWriter("src/reports/" + reportName + ".html"));
String line;
while ((line = in.readLine()) != null) {
out.write(line);
out.newLine();
}
in.close();
out.close();
} else {
throw new MalformedURLException("Url could not be accessed! Response code was " + responseCode);
}
好了,让我们编写测试类吧。
编写测试类
测试类将会很简单。我们需要扫描的网站的URL。这取决于您如何设置Juice Shop,对于您来说可能有所不同。除了URL之外,我们还需要一个电子邮件地址和一个登录应用程序的密码:
public class SecurityTest {
private WebDriver driver;
private static final String TARGET_URL = "https://juice-shop.herokuapp.com/#/";
private static final String EMAIL = "test@fakemail.com";
private static final String PASSWORD = "Juice123!";
}
在我们的BeforeMethod中,在我们初始化ChromeDriver之前,我们将为webdriver实例设置代理。我们还将忽略证书错误,因为Chrome会将网站标记为不安全。
@BeforeMethod
public void setup(){
String proxyServerUrl = SecurityScanner.ZAP_ADDRESS + ":" + SecurityScanner.ZAP_PORT;
Proxy proxy = new Proxy();
proxy.setHttpProxy(proxyServerUrl);
proxy.setSslProxy(proxyServerUrl);
ChromeOptions chromeOptions = new ChromeOptions();
chromeOptions.addArguments("--start-maximized");
chromeOptions.addArguments("--ignore-ssl-errors=yes");
chromeOptions.addArguments("--ignore-certificate-errors");
chromeOptions.setProxy(proxy);
driver = new ChromeDriver(chromeOptions);
}
测试本身将遵循以下流程:
我们进入网站后,开始进行爬虫扫描。
爬虫扫描完成后,开始进行主动扫描。
一旦主动扫描完成后,请转到注册页面。
注册一个新的账户
使用新账户登录
一旦登录成功,就执行另一次主动扫描。
幸运的是,大多数果汁店标签都有ID。我们还将添加一些对`sleep`方法的调用,以确保在最后一次活跃扫描之前测试不会中断:
public void juiceShopSecurityAssessment() throws InterruptedException {
driver.get(TARGET_URL);
SecurityScanner.executeSpiderScan(TARGET_URL);
SecurityScanner.executeActiveScan(TARGET_URL, "beforeLoginReport");
Thread.sleep(2000);
driver.findElement(By.className("close-dialog")).click();
Thread.sleep(2000);
driver.findElement(By.id("navbarAccount")).click();
Thread.sleep(2000);
driver.findElement(By.id("navbarLoginButton")).click();
driver.findElement(By.id("newCustomerLink")).click();
driver.findElement(By.id("emailControl")).sendKeys(EMAIL);
driver.findElement(By.id("passwordControl")).sendKeys(PASSWORD);
driver.findElement(By.id("repeatPasswordControl")).sendKeys(PASSWORD);
Thread.sleep(2000);
driver.findElement(By.id("mat-select-value-3")).click();
Thread.sleep(2000);
driver.findElement(By.id("mat-option-3")).click();
driver.findElement(By.id("securityAnswerControl")).sendKeys("something");
driver.findElement(By.id("registerButton")).click();
Thread.sleep(2000);
assertTrue(driver.findElement(By.xpath("//h1[text()='Login']")).isDisplayed());
driver.findElement(By.id("email")).sendKeys(EMAIL);
driver.findElement(By.id("password")).sendKeys(PASSWORD);
driver.findElement(By.id("loginButton")).click();
Thread.sleep(2000);
if(driver.findElement(By.xpath("//button[@aria-label='Show the shopping cart']")).isDisplayed()) {
SecurityScanner.executeActiveScan(TARGET_URL, "afterLoginReport");
}
}
在AfterMethod中,我们将仅退出驱动程序:
public void tearDown() {
driver.quit();
}
执行测试类
当我们运行测试类时,爬虫扫描程序将首先列出所有已发现的资源。输出结果将类似于以下内容:
爬虫完成!
在此之后,活动扫描程序将运行,并在用户登录之前生成一份报告。
主动扫描进度:84%
用户登录后,将再次执行主动扫描,最终将生成两份报告。
分析结果
如果我们查看一下我们的报告,它们看起来会像下面这个报告一样:
报告将列出扫描中发现的所有漏洞,并详细说明每个漏洞的情况,例如风险级别、漏洞发现的URL以及问题的描述。
我们可以利用这些信息来确定首先需要修复的漏洞以及如何进行修复。此外,查看报告以确保扫描没有产生任何假阳性(即应用程序中不存在的漏洞)也是一个好主意。
你可能会问:“为什么我们两次执行了主动扫描(一次针对未登录用户,一次针对已登录用户)?”
某些功能或页面可能仅对已登录用户可用,因此当用户未登录时,扫描程序无法测试这些区域的漏洞。另一个原因是,应用程序的安全控制措施可能针对登录用户和非登录用户有所不同。例如,应用程序可能为登录用户实施更严格的输入验证或更完善的安全检查,这可能导致登录用户时发现的漏洞更少。
通过查看我们的报告,我们可以看到当用户登录时,发现的警报事件有所增加。
优点和缺点
将自动化安全测试的优点与自动化其他测试类型的优点进行比较,其中大多数优点是相似的:
速度:自动化测试的速度通常比手动测试快得多,因此在测试大型或复杂的Web应用程序时,可以节省大量时间。
可扩展性:自动化测试可以针对应用程序的多个版本或在不同的环境中运行,而手动完成这些工作可能比较困难。
可重复性:自动化测试可以使用相同的输入多次运行,这有助于确保结果一致,并确保发现的任何问题实际上都是漏洞。
减少人为错误:自动化测试可以减少人为错误的风险,因为它们不会像人类那样容易犯错。
另一方面,缺点可能是:
误报:自动化测试并不总能确定某个漏洞是否为真正的漏洞还是误报。可能需要进行一些额外的手动测试来验证漏洞。
覆盖范围有限:自动化测试可能无法覆盖所有可能的场景,尤其是在处理复杂或动态的Web应用程序时。
初期成本和维护成本:建立自动化测试并随着应用程序的变化对其进行维护可能需要耗费大量的时间和资源。
OWASP ZAP是一款开源的Web应用程序安全扫描器,旨在自动查找Web应用程序中的安全漏洞并帮助开发人员修复它们。
ZAP提供的一些主要工具包括爬虫(Spider),它可以自动爬取Web应用程序,发现新的URL和内容,以及活动扫描(Active scan),它会向目标应用程序发送一系列请求,并分析响应以识别可能的安全问题。
ZAP还可以生成报告,提供许多好处,例如漏洞跟踪、优先级排序、了解问题、合规性和教育等。
通常来说,采用自动化测试和手动测试相结合的方式来确保网站应用程序的安全性是一个不错的主意。
ZAP拥有许多我们没有在这里讨论的其他功能,比如被动扫描、上下文和认证方法等。若想了解更多相关信息,请访问OWASP ZAP官方文档。
往期系列文章
下方扫码关注 软件质量保障,与质量君一起学习成长、共同进步,做一个职场最贵Tester!
往期推荐