影响Angular和React应用的常见六大漏洞

本文将和您讨论影响Angular和React应用的六种最常见的漏洞,以及如何发现和预防它们。
首页 新闻资讯 行业资讯 影响Angular和React应用的常见六大漏洞

目前大多数应用程序都会包含:服务器端逻辑、客户端逻辑、数据存储、数据传输、以及API等多个组件。与此同时,每种语言、框架、以及环境的使用,都会让应用程序暴露于一组独特的漏洞之中。为了保证这些组件的安全,第一时间发现应用的漏洞,进而构建出一个安全态势较高的应用,往往需要我们付出不懈的努力。

6282c21438bf731589c761bc964232b976c9ee.jpg

值得庆幸的是,大多应用程序的漏洞都有着相似、甚至相同的底层原因。研究这些常见的漏洞类型、以及背后的原因,将有助于我们对应用程序进行恰当的防护。

下面,我将和您一起讨论影响Angular和React应用的如下六种最常见的漏洞,以及如何发现和预防它们:

  • 身份验证绕过

  • 访问控制不当

  • 开放式重定向

  • 跨站请求伪造 (CSRF)

  • 模板注入

  • 跨站点脚本包含 (XSSI)

身份验证绕过

身份验证是指在执行敏感操作、或访问敏感数据之前,先验明身份的合法性。如果在应用程序上未能正确地实施身份验证,那么攻击者将可以利用此类错误配置,来访问他们本不该能够访问到的服务与功能。例如,Angular通常使用AppRoutingModule来进行路由。在将用户引导至应用程序中的敏感路由之前,您应该检查用户是否已通过了身份验证,并被授权了访问该资源。请参考如下代码段:

复制

@NgModule({imports: [RouterModule.forRoot([    // These paths are available to all users.{ path: '', component: HomeComponent },
        { path: 'features', component: FeaturesComponent },
        { path: 'login', component: LoginComponent },    // These routes are only available to users after logging in.{ path: 'feed', component: FeedComponent, canActivate: [ AuthGuard ]},
        { path: 'profile', component: ProfileComponent, canActivate: [ AuthGuard ]},    // This is the fall-through component when the route is not recognized.{ path: '**', component: PageNotFoundComponent}
    
 ])],        exports: [RouterModule]
        
 })    
 export class AppRoutingModule {}
  • 1.

  • 2.

  • 3.

  • 4.

  • 5.

  • 6.

  • 7.

  • 8.

  • 9.

  • 10.

  • 11.

  • 12.

  • 13.

  • 14.

  • 15.

  • 16.

  • 17.

  • 18.

  • 19.

  • 20.

  • 21.

  • 22.

访问控制不当

攻击者会想方设法绕过那些访问权限控制实施不当的应用程序。访问控制不仅仅只包括身份验证。也就是说,我们除了需要证明用户的身份(即“你是谁?”),还要通过应用程序授予相应的权限(即“你可以做什么?”)。只有通过两者双管齐下,才能共同确保用户不会访问到超出其权限的服务与功能。

目前,我们有多种方式为用户配置授权,其中包括:基于角色的访问控制、基于所有权的访问控制、以及访问控制列表等。而开发人员常犯的一种错误是在客户端执行授权检查。由于攻击者可以操控和覆盖客户端的检查,因此这是不安全的。可见,此类授权检查必须使用服务器端的代码来执行。请参考如下代码段:

复制

export class AdministratorGuard implements CanActivate {  constructor(private authService: AuthService, private router: Router) {}  canActivate(next: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<true | UrlTree> {// Check whether this user is an administratoor.  return this.authService.isAdmin().pipe(      map(isAdmin => {if (!isAdmin) {          return this.router.parseUrl('/')
        }return isAdmin  })
    )
  }
}export class AuthService {  constructor(private http: HttpClient) {}  // Whether the user is currently logged in.
  loggedIn: boolean | null = null
  // The user object object encompassing the user's name and role. Will be set
  user: User | null = null
  // Check whether the current user is an administrator.
  isAdmin(): Observable<boolean> {return this.getCurrentUser().pipe(map(user => {      return user != null && user.role === 'admin'}))
  }  // Get the user definition from local state, or the server (if this is the first time we are checking).
  getCurrentUser(): Observable<User | null> {if (this.loggedIn !== null) {      return of(this.user)
    }return this.http.get<User>('/api/auth', {      responseType: 'json'}).pipe(      tap({next: user => {          // If we get a user definition from the server it indicates this user is logged in.    this.user     = user  this.loggedIn = true},error: error => {          // A 401 response from the server indicates this user is not logged in.    this.user     = null  this.loggedIn = false}
      }),      catchError(() => {return of(null)
      })
    )
  }
}export interface User {username: stringrole:     string}
  • 1.

  • 2.

  • 3.

  • 4.

  • 5.

  • 6.

  • 7.

  • 8.

  • 9.

  • 10.

  • 11.

  • 12.

  • 13.

  • 14.

  • 15.

  • 16.

  • 17.

  • 18.

  • 19.

  • 20.

  • 21.

  • 22.

  • 23.

  • 24.

  • 25.

  • 26.

  • 27.

  • 28.

  • 29.

  • 30.

  • 31.

  • 32.

  • 33.

  • 34.

  • 35.

  • 36.

  • 37.

  • 38.

  • 39.

  • 40.

  • 41.

  • 42.

  • 43.

  • 44.

  • 45.

  • 46.

  • 47.

  • 48.

  • 49.

  • 50.

  • 51.

  • 52.

  • 53.

  • 54.

  • 55.

  • 56.

开放式重定向

例如,当未经身份验证的用户尝试着访问需要登录后才能看到的页面时,网站就需要将该用户自动重定向到登录页面,并在他们通过了身份验证之后,再让其返回到原来的位置。

在开放式重定向攻击发生时,攻击者通过向用户提供来自合法站点的URL,以欺骗用户访问某个外部站点。也就是说,该URL会将其重定到其他站点。而该站点一面设法让用户相信他们仍然在原始网站上,一面帮助攻击者构建出更加看似可信的网络钓鱼活动。

为了防止开放式重定向,您需要确保应用程序不会轻易将用户重定向到那些恶意站点的位置。例如,您可以通过验证重定向URL,来完全禁止离站重定向行为。请参考如下代码段:

复制

export class LoginComponent {    
  // The username and password entered by the user in the login form.
  username = '';  password = '';    
  // The destination URL to redirect the user to once they log in successfully.
  destinationURL = '/feed'
  constructor(private authService : AuthService,              private route       : ActivatedRoute,              private router      : Router) { }    
  ngOnInit() {this.destinationURL = this.route.snapshot.queryParams['destination'] || '/feed';
  }    
  onSubmit() {this.authService.login(this.username, this.password)
      .subscribe(
        () => {          // After the user has lgged in, redirect them to their desired destination.  let url = this.destinationURL
          // Confirm that the URL is a relative path - i.e. starting with a single '/' characters.  if (!url.match(/^\/[^\/\\]/)) {url = '/feed'  }    
          this.router.navigate([ url ])
        })
  }
}
  • 1.

  • 2.

  • 3.

  • 4.

  • 5.

  • 6.

  • 7.

  • 8.

  • 9.

  • 10.

  • 11.

  • 12.

  • 13.

  • 14.

  • 15.

  • 16.

  • 17.

  • 18.

  • 19.

  • 20.

  • 21.

  • 22.

  • 23.

  • 24.

  • 25.

  • 26.

  • 27.

  • 28.

  • 29.

  • 30.

  • 31.

  • 32.

  • 33.

当然,我们还有许多其他方法可以防止开放式重定向的发生。例如:对请求引用方予以检查、或使用页面索引进行重定向。不过,正因为验证URL相对比较困难,因此开放式重定向仍然是当代Web应用普遍存在的问题。

跨站请求伪造

跨站点请求伪造(Cross-Site Request Forgery,CSRF)是一种客户端技术,可用于攻击Web应用的其他用户。使用CSRF,攻击者可以发送虚假的、来自受害者的HTTP请求,去执行攻击者的危害性操作。例如,攻击者会在未经许可的情况下,更改受害者的密码、或从其银行账户里转账。

与开放式重定向不同,我们目前已有一种行之有效的对抗CSRF的方法,即:使用CSRF令牌和SameSite Cookie的组合,以避免使用GET请求进行各项状态更改的操作。例如,Angular允许您使用HttpClientXsrfModule模块,向HTTP请求添加防伪的令牌。请参考如下代码段:

复制

@NgModule({  declarations: [],  imports: [BrowserModule,HttpClientModule,HttpClientXsrfModule.withOptions({      cookieName: 'XSRF-TOKEN',      headerName: 'X-CSRF-TOKEN'}),
  ],  providers: [],  bootstrap: [AppComponent]
})export class AppModule {
}
  • 1.

  • 2.

  • 3.

  • 4.

  • 5.

  • 6.

  • 7.

  • 8.

  • 9.

  • 10.

  • 11.

  • 12.

  • 13.

  • 14.

  • 15.

模板注入

类似于HTML文件的Web模板,为开发人员提供了一种通过将应用数据与静态模板相结合,以指定如何呈现页面的方法。此功能允许开发人员将从数据库或HTTP请求中检索到的动态内容,插入到网页中。

顾名思义,模板注入需要注入到网页的模板中。根据受感染应用的权限,攻击者可以通过使用模板注入的漏洞,来读取敏感文件、执行恶意代码、或提升他们在系统上的各种权限。下面展示了Angular模板的不安全用法。它允许攻击者通过URL的哈希值,来恶意注入代码:

复制

@Component({  selector: 'app-header',  template: '<h1>' + (window.location.hash || 'Home') + '</h1>'})export class HeaderComponent {}
  • 1.

  • 2.

  • 3.

  • 4.

  • 5.

注意:请千万不要直接将用户提供的输入连接到模板中,而应该使用模板引擎的内置替换机制,来安全地嵌入动态输入。请参考如下代码段:

复制

@Component({  selector: 'app-header',  template: '<h1>{{ title }}</h1>'})export class HeaderComponent {  title = ''  
        
  ngOnInit() {this.title = window.location.hash || 'Home';
  }
}
  • 1.

  • 2.

  • 3.

  • 4.

  • 5.

  • 6.

  • 7.

  • 8.

  • 9.

  • 10.

  • 11.

跨站点脚本

跨站点脚本包含的攻击也称为XSSI(Cross-Site Script Inclusion)。此类攻击发生在当恶意站点包含了来自受害者站点的Javascript,并通过脚本提取其敏感信息时。

同源策略(same-origin policy,SOP)通常可以起到控制数据跨源(cross-origins)访问的作用。不过,SOP并不能限制JavaScript代码,而且HTML<script>标签会允许从任何来源加载JavaScript代码。从技术角度来说,该功能方便了允许跨域重用的JavaScript文件,但是会带来新的安全风险:攻击者可以通过加载受害者的JS文件,来窃取写入JavaScript文件的数据。

例如,某网站通过Javascript文件,为登录用户存储和传输敏感数据。如果用户在同一浏览器中访问了恶意站点,那么恶意站点可以导入该JavaScript文件,并访问与该用户会话相关的敏感信息。而这一切都归因于存储在浏览器中的用户Cookie。

因此,为避免XSSI攻击,请勿在JavaScript文件中传输敏感数据。下面是如何使用JSON文件(会受到SOP的限制)在Angular中安全地加载API令牌的示例:

复制

// The configuration information we will retrieve from the server.export interface Config {  username     : string
  accessToken  : string
  role         : string}    
@Injectable()export class ConfigService {  constructor(private http: HttpClient) {}    
  // Retrieve configuration information from the server.
  getConfig() {return this.http.get<Config>('api/config')
      .pipe(catchError(this.handleError) 
      )
  }    
  private handleError(error: HttpErrorResponse) {if (error.status === 0) {      // A client-side or network error occurred. Handle it accordingly.  log.error('An error occurred:', error.error)
    } else {      // The server returned an unsuccessful response code.  log.error(`Backend returned code ${error.status}, body was: `, error.error)
    }        return throwError('An unexpected error occurred loading configuration.')
  }
}
  • 1.

  • 2.

  • 3.

  • 4.

  • 5.

  • 6.

  • 7.

  • 8.

  • 9.

  • 10.

  • 11.

  • 12.

  • 13.

  • 14.

  • 15.

  • 16.

  • 17.

  • 18.

  • 19.

  • 20.

  • 21.

  • 22.

  • 23.

  • 24.

  • 25.

  • 26.

  • 27.

  • 28.

  • 29.

  • 30.

  • 31.

译者介绍

陈峻 (Julian Chen),51CTO社区编辑,具有十多年的IT项目实施经验,善于对内外部资源与风险实施管控,专注传播网络与信息安全知识与经验;持续以博文、专题和译文等形式,分享前沿技术与新知;经常以线上、线下等方式,开展信息安全类培训与授课。

原文标题:Angular + React: Vulnerability Cheatsheet,作者: Vickie Li

25    2022-05-27 08:00:00    漏洞 Angular React