NestJS小技巧22-最大限度地提高NestJS应用程序中的代码安全性(part1)
by 雪隐 from https://juejin.cn/user/1433418895994094
本文欢迎分享与聚合,全文转载就不必了,尊重版权,圈子就这么大,若急用可联系授权
NestJS开发人员的顶级安全代码最佳实践
作为一名开发人员,我们都知道代码安全的重要性。Optus和Medibank最近发生的数据泄露事件再次凸显了代码安全的重要意义。因此,问题是:我们如何编写安全的代码来防止网络应用程序中的各种类型的攻击?遵循最佳实践编写安全代码对于保护我们的应用程序免受漏洞和威胁至关重要。
在我们深入探讨如何预防安全风险之前?让我们首先研究一下最常见的安全风险类型。这将使我们更好地了解保护我们的应用程序所面临的挑战。
OWASP Top 10是一个被广泛接受的web应用程序最关键安全风险列表,由行业专家一致确定。以下是2017年和2021年十大风险列表。
前10名中的许多对网络应用程序的安全性至关重要。
代码安全准备写2篇,这是第一篇,在这篇文章中,我会聊一聊一些风险,以及防范这些风险的最佳实践。
他们包括
- 越权访问 Broken access control
- 服务器端请求伪造Server-side request forgery (SSRF)
- 自动绑定漏洞Mass assignment
- 敏感信息暴露Sensitive information exposure
越权访问
越权访问是最常见的风险之一。当攻击者可以访问未经授权的功能或资源时,就会发生这种情况。现实世界中的一个例子是2014年1月的Snapchat事件。
重要的是要遵循最低特权原则来防止这种风险。这意味着在默认情况下,访问应该始终被拒绝,并且只有在需要时才应该授予特权。
我们可以使用访问控制机制,如基于角色的访问控制(RBAC)或访问控制列表(ACL),根据用户的角色或权限限制对功能或资源的访问。
以下是RBAC在NestJS应用程序中使用Guards的示例:
import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common';
@Injectable()
export class AdminRoleGuard implements CanActivate {
canActivate(context: ExecutionContext): boolean {
const request = context.switchToHttp().getRequest();
const user = request.user;
return user.role === 'admin';
}
}
@Controller('cats')
export class CatsController {
@UseGuards(AdminRoleGuard)
@Get()
async findAll(): Promise<Cat[]> {
return this.catsService.findAll();
}
}
在上面的代码片段中,我创建了AdminRoleGuard
并实现了NestJS
提供的CanActive
接口。他用来验证当前用户,当用户为admin时,返回true
。然后,在findAll
方法上使用@UseGuards
装饰器,进而调用AdminRoleGuard
,只有用户为admin
的情况下,才能访问此端点。
访问控制机制应使用经过验证的框架中的集中式功能来应用,以确保其安全且易于维护。
也非常推荐使用单元测试,来测试controller
是否漏用了必要Guards
守卫。这样即使不小心把守卫给移除了,单元测试也能测试出来。
服务器端请求伪造(SSRF)
SSRF是一种网络攻击,攻击者诱使服务器代表他们发出意外请求。这些请求可用于访问来自内部网络的受限资源。
为了防止SSRF攻击,验证用户输入的参数非常重要。以下是易受SSRF风险影响的端点示例。
import { Controller, Get, Res, HttpStatus, Query } from '@nestjs/common';
@Controller()
export class CatsController {
@Get()
async getData(@Query('url') url: string, @Res() res) {
const response = await fetch(url);
return await response.json();
}
}
在上面的例子中,应用程序发起URL请求,来获取URL的资源,并把内容返回给客户端。显然,它很容易受到SSRF攻击,因为攻击者可以使用从内部网络访问受限资源的恶意URL向服务器发送请求。
为了防止这个风险,我们应该验证这个URL参数:
import { Controller, Get, Res, HttpStatus, Query } from '@nestjs/common';
import { isURL } from 'validator';
@Controller()
export class CatsController {
@Get()
async getData(@Query('url') url: string, @Res() res) {
if (!isURL(url)) {
return res.status(HttpStatus.BAD_REQUEST).send('Invalid URL');
}
const response = await fetch(url);
return await response.json();
}
}
为了进一步提高安全性,我们不应该允许用户在查询参数中直接传入URL。作为代替,我们应该使用一个存在的服务从我们信任的API来取回数据。
@Controller()
export class CatsController {
@Get()
async getData(@Query('name') dataName: string, @Res() res) {
const response = await dataService.GetDataByName(dataName);
return await response.json();
}
}
还有其他的方法来防止SSRF攻击:
- 仅向可信来源(即已知的API或服务)发出请求
- 实现安全标头(即类似“X-Frame-Options”的标头),以防止点击劫持攻击和其他类型的恶意请求。
- 使用内容安全策略(CSP)指定允许哪些源代表您的应用程序发出请求。
在NestJS中,您可以使用helmet来简单的设置安全标头和内容安全策略。
自动绑定漏洞
自动绑定漏洞是一个漏洞,因为攻击者可以通过向您的应用程序发送恶意请求来修改多个对象属性。
在下面的示例中,将根据来自请求主体的数据创建一个新用户。它很容易受到大规模分配攻击,因为攻击者可以发送带有恶意数据的请求,这些数据会覆盖客户端对象中的敏感字段(即角色或密码)。
import { Controller, Post, Body } from '@nestjs/common';
@Controller('client')
export class ClientController {
@Post()
create(@Body() body) {
const client = new Client(body);
return await client.save();
}
}
为了防止自动绑定漏洞,我们可以在每个对象中定一个允许穿参的白名单。在下面的示例中,我们实现了一个属性白名单,以防止覆盖敏感字段。
import { Entity, Column, PrimaryGeneratedColumn } from 'typeorm';
@Entity()
export class Client {
@PrimaryGeneratedColumn()
id: number;
@Column()
name: string;
@Column()
role: string;
@Column()
password: string;
@Column()
email: string;
}
@Controller('client')
export class ClientController {
constructor(private clientService: ClientService) {}
@Post()
async create(@Body() client: Pick<User, 'name' | 'email'>) {
return await this.clientService.create(client);
}
}
在这里,我们使用TypeScript Pick
类型来定义User
实体的属性白名单。然后使用@Body
装饰器将请求正文绑定到User
参数,该参数将只包括允许的属性。这样可以防止攻击者通过批量分配修改用户实体的其他属性。
防止自动绑定漏洞的其他方法包括:
- 使用减少的DTO,而不是一般的DTO。例如,创建
InsertClientEntity
和UpdateClientEntity
。这些DTO仅包含插入和更新操作中允许的属性。 - 避免直接绑定到来自客户端的对象。
敏感信息暴露
敏感信息包括密码,APIkeys,以及其他保密数据。任何包括个人信息或者支付相关的信息都是敏感的。
在设计web API时,经常会向客户端返回过多的数据。
import { Controller, Get, Param } from '@nestjs/common';
import { Client } from './client/client.entity';
@Controller()
export class ClientController {
@Get('clients/:id')
async getClient(@Param('id') id: string): Promise<Client> {
// 返回所有的项目给客户端
return await Client.findById(id);
}
}
在这个例子中,getClient
方法返回了所有的项目给客户端,其中包括敏感信息比如role
或者password
。尽管客户端并不消费或者展示这些数据,他们仍然能够被攻击者拦截并且导出。
为了防止敏感个人数据暴露,我们应该只返回必要的数据,在这个例子中是name
和email
项目。简而言之,我们应该返回最小数量的项目。
import { Controller, Get, Param, UseGuards } from '@nestjs/common';
import { Client } from './client/client.entity';
@Controller()
export class ClientController {
@Get('clients/:id')
async getClient(@Param('id') id: string): Promise<Client> {
// Only return the name and email
return await Client.findById(id).map((c) => {
c.name, c.email;
});
}
}
以下内容是防止敏感信息暴露的别的指导:
- 不要将敏感信息存储到版本控制中。此信息包括环境变量或配置文件
- 识别系统中的敏感信息(GDPR、PCI和PII数据),并通过加密保护它们。
- 请确保您的应用程序在客户端和服务器之间使用HTTPS。这将防止敏感数据在传输过程中被截获。
总结
在本文中,我们介绍了NestJS环境中的4种常见风险和预防这些风险的最佳实践。
通过遵循这些最佳实践,您可以编写安全的代码,以确保您的NestJS应用程序尽可能安全。
在本文的第2部分中,我们继续讨论OWASP的其他主要风险。
转载自:https://juejin.cn/post/7248655627928535095