对于想了解CNAAC应用安全标识“年终特惠”活动介绍的读者,本文将是一篇不可错过的文章,并且为您提供关于4.(基础)tornado应用安全与认证、AAA-ActFramework的安全框架II-应用、
对于想了解CNAAC应用安全标识“年终特惠”活动介绍的读者,本文将是一篇不可错过的文章,并且为您提供关于4.(基础)tornado应用安全与认证、AAA - ActFramework的安全框架II - 应用、ARMS 应用安全-你的应用运行时的隐形安全卫士、ASP.NET标识 – 强制使用安全戳重新登录的有价值信息。
本文目录一览:- CNAAC应用安全标识“年终特惠”活动介绍
- 4.(基础)tornado应用安全与认证
- AAA - ActFramework的安全框架II - 应用
- ARMS 应用安全-你的应用运行时的隐形安全卫士
- ASP.NET标识 – 强制使用安全戳重新登录
CNAAC应用安全标识“年终特惠”活动介绍
在十九大报告中,习总书记八次提到互联网,特别强调“加强互联网内容建设,建立网络综合治理体系,营造清朗的网络空间,建设网络强国。” 伴随着冷空气席卷全国,12月悄然来到,2017年已经接近尾声,但是十九大的热情仍在持续。为了营造清朗明净的移动互联网应用安全环境,我中心在此之际温情推出应用安全标识“年终特惠”活动,活动期间申请可减免200元,享受68元特别优惠。标识在手,安全无忧。活动时间
自2017年12月4日至2018年2月28日
参与本活动的条款和条件
1.提交有效应用文件到本中心申请检测。
2.应用类别在国家法律允许发行之列。
3.应用不含明显违法违规信息。
特别提示
1.请勿重复提交同一个应用。
2.请在提交前自行检查您的应用不含明显的违法违规信息。
3.免费额满后,本中心将在显著位置发出通知。
4.此优惠活动的最终解释权归本网站所有。
应用安全标识介绍
本中心在公安部的指导下深入开展全国APP安全监测工作,对恶意应用进行通报和下架处理。为了做好应用上架前的安全管理工作,避免因违规而被下架,本中心面向广大应用开发者开启安全标识认证服务,对应用进行安全检测,对通过检测的应用授予官方安全标识,作为全国统一标准的移动应用安全证明。
为何要申请安全标识?
安全标识可帮助开发者加快应用的上架速度,有效规避应用被下架的风险,保护正版应用,打击盗版和恶意反编译行为,提高应用安全形象和下载率。
4.(基础)tornado应用安全与认证
这一节我们介绍应用安全与认证,其实中间省略了一个数据库。对于tornado来说,读取数据库的数据,性能的瓶颈还是在数据库上面。关于数据库,我在<
1.cookie
ookie是储存在客户端的键值对,保存了用户的信息。我们都知道http协议时无状态的,只知道有人链接就行进行交互,但是却不知道是谁。于是这个时候cookie就出现了,当我们第一次访问的时候,服务端就会创建一个cookie然后返回给我们,当我们下次再访问的时候就带着之前的cookie过去就行了,所以我们明明没有输入用户名和密码,却自动登录了,就是因为cookie。当我们换一个浏览器,或者清理垃圾,把cookie删除了,那么就又需要重新登录了。既然提到cookie就要说到session,session是我们人工引入的一个抽象概念,session的实现需要依赖于cookie。当我们的身份信息比较重要时,那么将信息存储在cookie中会不安全,所以session就出现了,服务端把不把敏感信息返回了,而是返回一个随机密串,这个随机密串是由当前的session经过序列化再加密得到的,也就是session id,将这个session id作为cookie值返回给客户端。当客户端下次再访问的时候,只需要带着session id过来就行,将session id解密反序列化,判断用户是否登录。所以session是我们人工引入的一个抽象概念,session的实现依赖于cookie。 那么在tornado中如何设置cookie呢?首先在tornado中,可以设置两种cookie,一个是普通cookie,另一种是加密cookie 设置普通cookie
设置普通cookie
self.set_cookie(name,value,domain=None,expires=None,path="/",expires_days=None,**kwargs) 参数: name:cookies名 value:cookie值 domain:提交cookie时匹配的域名 path:提交cookie时匹配的路径 expires:cookie的有效期,可以是时间戳整数,时间元素,datetime类型。为UTC时间 expires_days:cookie的有效期天数,优先级低于expires
import tornado.web class SatoriHandler(tornado.web.RequestHandler): def get(self,*args,**kwargs): self.set_cookie("satori","love") # 实际上set_cookie本质上是通过set_header来实现的 self.set_header("Set-Cookie","koishi=love") self.write("6666"
获取cookie
import tornado.web class SatoriHandler(tornado.web.RequestHandler): def get(self,"love") cookie = self.get_cookie("satori") self.write(cookie)
清除cookie
self.clear_cookie(name,domain=None) # 删除名为name,同时匹配path和domain的cookie self.clear_all_cookie(path="/",domain=None) # 删除同时匹配path和domain的所有cookie
注意:执行清除cookie的操作时,并不是tornado去删除浏览器上的cookie,而是将cookie的值设为空,并将有效时间改为失效。真正删除cookie是由浏览器进行操作的
此外再介绍一下HTTP-only和SSL-Cookies
Tornado的cookie功能依附于Python内建的Cookie模块。因此,我们可以利用它所提供的一些安全功能。这些安全属性是HTTP cookie规范的一部分,并在它可能是如何暴露其值给它连接的服务器和它运行的脚本方面给予浏览器指导。比如,我们可以通过只允许SSL连接的方式减少cookie值在网络中被截获的可能性。我们也可以让浏览器对JavaScript隐藏cookie值。 为cookie设置secure属性来指示浏览器只通过SSL连接传递cookie。(这可能会产生一些困扰,但这不是Tornado的安全cookies,更精确的说那种方法应该被称为签名cookies。)从Python 2.6版本开始,Cookie对象还提供了一个httponly属性。包括这个属性指示浏览器对于JavaScript不可访问cookie,这可以防范来自读取cookie值的跨站脚本攻击。 为了开启这些功能,你可以向set_cookie和set_secure_cookie方法传递关键字参数。比如,一个安全的HTTP-only cookie(不是Tornado的签名cookie)可以调用self.set_cookie(''foo'',''bar'',httponly=True,secure=True)发送。
设置安全cookie
首先需要设置一个随机密串用来给cookie进行混淆加密,然后写在配置文件的settings里,"cookie_secret": "xxxx"
import os import uuid import base64 cookie_secret = str(base64.b64encode(uuid.uuid4().bytes + uuid.uuid4().bytes),encoding="utf-8") '''''' cRi40+RmRy6iVoLoIeK03KMsjfu1T0hzonLWSEtDb2o= '''''' options = {"port": 7777} BASE_DIR = os.path.dirname(os.path.abspath(__file__)) settings = {"static_path": os.path.join(BASE_DIR,"static"),"template_path": os.path.join(BASE_DIR,"templates"),"static_url_prefix": "/static/","compiled_static_cache": True,"compiled_template_cache": True,"server_traceback": True,"cookie_secret": cookie_secret # 要在settings中注册cookie_secret }
然后使用self.set_secure_cookie(设置安全cookie)进行设置,可以防止cookie被伪造,参数和设置普通cookie是相同的。
import tornado.web class SatoriHandler(tornado.web.RequestHandler): def get(self,**kwargs): self.set_secure_cookie("satori","love") cookie = self.get_cookie("satori") self.write(cookie)
可以看到浏览器中显示的不再是satori = love,而是"2|1:0|10:1535867275|6:satori|8:bG92ZQ==|e8ec0f270440d95b2d004e54633cf7730d9ab4ca3ea8ed5a6fffdbc7ea61b952"; expires=Tue,02 Oct 2018 05:47:55 GMT; Path=/ 说明:安全cookie使用的版本,默认使用版本2;默认为0;时间戳;cookie名;base64编码的cookie值;签名值,不带长度说明
获取安全cookie
self.get_secure_cookies(name,value=None,max_age_days=31,min_version=None),当然self.get_cookie也是可以的。 name:cookie名称 value:如果获取不到返回None max_age_days:不同于expires_days,expires_days表示设置浏览器中cookie的有效时间。而max_age_days是过滤安全cookie的时间戳。 但是安全cookie也不是绝对安全的,只是在一定程度增加了破解的难度,还是不要在cookie中存储敏感信息为好
2.xsrf
CSRF(Cross-site request forgery)跨站请求伪造,也被称为“One Click Attack”或者Session Riding,通常缩写为CSRF或者XSRF,是一种对网站的恶意利用。尽管听起来像跨站脚本(XSS),但它与XSS非常不同,XSS利用站点内的信任用户,而CSRF则通过伪装来自受信任用户的请求来利用受信任的网站。与XSS攻击相比,CSRF攻击往往不大流行(因此对其进行防范的资源也相当稀少)和难以防范,所以被认为比XSS更具危险性。 举个栗子:古明地恋这个时候正在浏览银行账户,古明地觉在古明地恋的站点上编写了一个取款的form表单提交的链接,并将此链接作为图片src,如果银行保存的古明地恋的cookie还没有过期,那么在点击图片的时候就会带上cookie将form表单提交,这样在古明地恋不知情的情况下便完成了取款
那么如何防范请求伪造呢?
有很多预防措施可以防止这种类型的攻击。首先你在开发应用时需要深谋远虑。任何会产生副作用的HTTP请求,比如点击购买按钮、编辑账户设置、改变密码或删除文档,都应该使用HTTP POST方法。无论如何,这是良好的RESTful做法,但它也有额外的优势用于防范像我们刚才看到的恶意图像那样琐碎的XSRF攻击。但是,这并不足够:一个恶意站点可能会通过其他手段,如HTML表单或XMLHTTPRequest API来向你的应用发送POST请求。保护POST请求需要额外的策略。 为了防范伪造POST请求,我们会要求每个请求包括一个参数值作为令牌来匹配存储在cookie中的对应值。我们的应用将通过一个cookie头和一个隐藏的HTML表单元素向页面提供令牌。当一个合法页面的表单被提交时,它将包括表单值和已存储的cookie。如果两者匹配,我们的应用认定请求有效。 由于第三方站点没有访问cookie数据的权限,他们将不能在请求中包含令牌cookie。这有效地防止了不可信网站发送未授权的请求。正如我们看到的,Tornado同样会让这个实现变得简单。
xsrf保护:同源策源
开启xsrf保护,可以在配置中添加
模板中应用
可以form表单中添加{%module xsrf_form_html()%},当然也要在配置文件settings中注册"xsrf_cookies": True
作用: 1.为浏览器设置了名为_xsrf的cookie,这个cookie在关闭浏览器之后会失效。 2.为模板表单添加了一个隐藏的域,名为_xsrf,值为_xsrf的cookie值
非模板中应用
手动创建一个input,并将name的属性值设置为_xsrf,value值为_xsrf的cookie值
<!DOCTYPE html> <html lang="en"> <head> <Meta charset="UTF-8"> <title>Title</title> </head> <body> <form> <input type="hidden" id="hi" name="_xsrf" value=""> name:<input type="text" name="username"> passwd:<input type="password" name="passwd"> <input type="submit" value="submit"> </form> </body> </html>
发起ajax请求
<!DOCTYPE html> <html lang="en"> <head> <Meta charset="UTF-8"> <title>Title</title> </head> <body> <form> name:<input type="text" name="username"> passwd:<input type="password" name="passwd"> <button onclick="login()">login</button> </form> </body> </html>
<!DOCTYPE html> <html lang="en"> <head> <Meta charset="UTF-8"> <title>Title</title> </head> <body> <form> name:<input type="text" name="username"> passwd:<input type="password" name="passwd"> <button onclick="login()">login</button> </form> </body> </html>
需要手动添加_xsrf的cookie,需要在进入主页时就自动设置_xsrf的cookie,因此可以使用tornado.web下的staticfilehandler。但是我们无法直接在staticfilehandler中添加逻辑,因此我们想到可以自定义一个类,然后继承staticfilehandler。
import tornado.web class staticfilehandler(tornado.web.staticfilehandler): def __init__(self,**kwargs): super().__init__(*args,**kwargs) self.xsrf_token
3.用户认证
作用:指在受到用户请求后,进行预先判断的用户状态(是否登录),若验证通过则正常处理,否则返回到登录页面 方法:tornado.web.authenticated装饰器,tornado将确保所装饰函数的主题只有合法的用户才能使用 get_current_user(),验证用户的逻辑应该写在该方法中,如果返回结果为True,验证成功,否则验证失败。当验证失败会将用户返回到指定的路由。这个指定的路由需要再配置文件的settings中进行设置,"login_url": "your_url"
import os import uuid import base64 cookie_secret = str(base64.b64encode(uuid.uuid4().bytes + uuid.uuid4().bytes),"cookie_secret": cookie_secret,# 要在settings中注册cookie_secret "xsrf_cookies": True,"login_url": "/satori" }
application.py
import tornado.web from views import view import config import os class Application(tornado.web.Application): def __init__(self): handlers = [ (r"/satori",view.SatoriHandler),(r"/koishi",view.KoishiHandler),(r"/mashiro",view.MashiroHandler),(r"/(.*)$",view.staticfilehandler,{"path": os.path.join(config.BASE_DIR,"static/html"),"default_filename": "index.html"}) ] super(Application,self).__init__(handlers=handlers,**config.settings)
view.py
import tornado.web class staticfilehandler(tornado.web.staticfilehandler): def __init__(self,**kwargs) self.xsrf_token class SatoriHandler(tornado.web.RequestHandler): def get(self,**kwargs): self.write("<h1>satori</h1>") class KoishiHandler(tornado.web.RequestHandler): def get_current_user(self): return None @tornado.web.authenticated def get(self,**kwargs): self.write("<h1>koishi</h1>") class MashiroHandler(tornado.web.RequestHandler): def get_current_user(self): return True @tornado.web.authenticated def get(self): self.write("<h1>mashiro</h1>")
我访问/koishi和/mashiro,会触发get请求,但是get请求被认证装饰器装饰,所以会执行get_current_user(),如果返回为True,那么执行get,如果返回为False,那么会重定向到配置文件中settings的"login_url"所对应的url
访问localhost:7777/koishi
当我访问/koishi, 由于验证不通过,所以跳转到了satori。而且/satori后面还跟着?next=%2Fkoishi,也说明是由/koishi跳转过来的
但我访问/mashiro,由于验证通过,所以执行对应的get请求,显示mashiro
关于应用安全与认证就介绍到这里,下一节将介绍tornado高性能的第二个杀手锏(第一个是基于epoll的IO多路复用),异步
AAA - ActFramework的安全框架II - 应用
在上一篇博客中我们介绍了 AAA 安全框架的概念. 下面我们开始讲述实际项目中 (本篇博客将实现一个多用户的 Todo 列表系统) 如何使用 AAA 安全框架. 在本博客中我们将假设应用使用 MongoDB 来存储数据. 不过关于安全框架应用的部分代码和具体数据库无关.
1 引入项目依赖
在你的 pom.xml
文件中添加 act-aaa 插件依赖:
<dependency>
<groupId>org.actframework</groupId>
<artifactId>act-aaa</artifactId>
<version>${act-aaa.version}</version>
</dependency>
其中 ${act-aaa.version}
是 act-aaa 插件的版本. 截止本文到落笔时的最新版本是 1.0.2
.
**注意: ** 当你的项目加入依赖之后发生的最直接的变化是所有的控制器响应方法都需要身份认证了. 如果你没有进一步做任何工作, 所有的访问都会被返回 401 Unauthorised
响应.
2 处理不需要身份认证的控制器方法
act-aaa 插件默认所有的 HTTP 访问都需要身份认证. 但很明显任何 web 应用都有不需要身份认证的访问结构, 最简单的例子是登陆和注册.
如果某个响应方法不需要身份认证, 可以在该方法上添加 @org.osgl.aaa.NoAuthentication
注解:
@NoAuthentication
public void loginForm() {}
如果某个控制器所有的响应方法都不需要身份认证, 可以在控制器类上添加 @org.osgl.aaa.NoAuthentication
注解:
@NoAuthentication
public class PublicAccessController {
...
}
3 Model 类
3.1 创建一个 UserLinked 接口
UserLinked 接口将被用于检查具体某个被保护的资源是否属于当前正在访问的用户:
package demo.security;
public interface UserLinked {
/**
* Return the user ID
*/
public String userId();
}
3.2 创建 User 类
每个需要身份认证的应用都需要一个 Model 类来对正在和系统交互的用户建模. 下面是一个基本的 User 类代码. 具体应用可以增加自己需要的字段和逻辑
package demo.model;
@Entity("user")
public class User extends MorphiaAdaptiveRecord<User> implements UserLinked {
public String email;
private String password;
@Override
public String userId() {
return this.email;
}
public void setPassword(String password) {
this.password = Act.crypto().passwordHash(password);
}
public boolean verifyPassword(char[] password) {
return Act.crypto().verifyPassword(password, this.password);
}
public static class Dao extends MorphiaDao<User> {
public User findByEmail(String email) {
return findOneBy("email", email);
}
}
}
注意 我们在 User 类中使用了 ActFramework 提供的工具来对密码明文进行哈希运算以及校验操作. 关于这方面更多的知识可以参见此篇博客.
3.3 创建 TODO 类
我们在本篇博客中实现一个多用户的 TODO 列表管理. 下面是一个简单的 TODO Model 类:
package demo.model;
@Entity("todo")
public class Todo extends MorphiaAdaptiveRecord<Todo> implements UserLinked {
/**
* Store the owner''s email
*/
public String owner;
@Override
public String userId() {
return this.owner;
}
public static class Dao extends MorphiaDao<Todo> {
public Iterable<Todo> findByOwner(User owner) {
return findBy("owner", owner.email);
}
}
}
4. 配置安全层
4.1 定义权限
AAA 的 API 支持使用两种权限, 第一传入某个实现了 org.osgl.aaa.Permission
的类实例; 第二传入某个字符串. 在此篇博客中我们将使用第二种简单形式. 下面定义在这个 TODO 应用中涉及到的权限:
package demo.security;
public final class TodoPermission {
private TodoPermission() {}
public static final String PERM_CREATE_TODO_ITEM = "create-todo-item";
public static final String PERM_UPDATE_TODO_ITEM = "update-todo-item";
public static final String PERM_VIEW_TODO_ITEM = "view-todo-item";
public static final String PERM_DELETE_TODO_ITEM = "delete-todo-item";
}
上面的 TodoPermission
类主要是为了在应用中方便地使用权限常量. 下面我们将定义一个配置文件来告诉 act-aaa 应用中使用的权限及其特性. 在 src/main/resources/
目录下添加一个名为 acl.yaml
的文件, 内容如下:
create-todo-item:
type: permission
dynamic: false
update-todo-item:
type: permission
dynamic: true
view-todo-item:
type: permission
dynamic: true
delete-todo-item:
type: permission
dynamic: true
注意 在上面的定义中除了 create-todo-item
权限, 其他所有权限的 dynamic
属性均为 true
, 这是因为只有当需要校验 create-todo-item
的时候我们不需要校验数据 (i.e. Todo 实例, 因为还没有创建), 而其他权限都需要检查被访问的数据 (i.e Todo 实例) 是否属于当前用户.
4.2 配置应用安全框架
此步骤将使用一个类来设置 act-aaa 的各种配置:
package demo.security;
/**
* Here we use the generic parameter to tell act-aaa about the user model class
*/
public class TodoSecurity extends ActAAAService.Base<demo.model.User> {
/**
* In this simple Todo app every signed up user get granted
* all of the following permissions
*/
private static final Set<String> DEFAULT_PERMS = C.set(
PERM_CREATE_TODO_ITEM,
PERM_DELETE_TODO_ITEM,
PERM_UPDATE_TODO_ITEM,
PERM_VIEW_TODO_ITEM
);
/**
* We tell act-aaa `email` is the key to extract the user from database
*/
@Override
protected String userKey() {
return "email";
}
/**
* Just return the default permission set
*/
@Override
protected Set<String> permissionsOf(User user) {
return DEFAULT_PERMS;
}
/**
* inject the logic of password verification into act-aaa
*/
@Override
protected boolean verifyPassword(User user, char[] password) {
return user.verifyPassword(password);
}
/**
* This will help to check a protected resource against the current logged in user
* if the permission been authorised is dynamic
*/
public static class DynamicPermissionChecker extends DynamicPermissionCheckHelperBase<UserLinked> {
@Override
public boolean isAssociated(UserLinked userLinked, Principal principal) {
return S.eq(userLinked.userId(), principal.getName());
}
}
}
5. 控制器
5.1 登陆注册控制器
package demo.controller;
public class LoginController {
@Inject
private User.Dao userDao;
@GetAction("/login")
public void loginForm() {
}
@PostAction("/login")
public void login(String email, String password, H.Flash flash, ActionContext context) {
User user = userDao.authenticate(email, password);
if (null == user) {
flash.error("cannot find user by email and password combination");
redirect("/login");
}
context.login(email);
redirect("/");
}
@GetAction("/sign_up")
public void signUpForm() {
}
@PostAction("/sign_up")
public void signUp(User user, ActionContext context, @Provided PostOffice postOffice) {
if (userDao.exists(user.email)) {
context.flash().error("User already exists");
redirect("/sign_up");
}
user.activated = false;
userDao.save(user);
postOffice.sendWelcomeLetter(user);
redirect("/sign_up_ok");
}
@GetAction("/sign_up_ok")
public void signUpConfirm() {
}
@GetAction("/activate")
public void activate(String tk, ActionContext context) {
Token token = Act.crypto().parseToken(tk);
notFoundIfNot(token.isValid());
User user = userDao.findByEmail(token.id());
notFoundIfNull(user);
context.session("tk", tk);
render(user);
}
@PostAction("/activate")
public void completeActivation(String password, ActionContext context) {
String tk = context.session("tk");
notFoundIfNull(tk);
Token token = Act.crypto().parseToken(tk);
notFoundIfNot(token.isValid());
User user = userDao.findByEmail(token.id());
token.consume();
user.setPassword(password);
user.activated = true;
userDao.save(user);
context.login(user.email);
redirect("/");
}
}
该控制器主要实现了下列功能:
- 登陆
- 注册并发送激活邮件
- 响应激活链接请求
- 处理激化请求(初始化密码)
5.2 Todo控制器
@Controller("/todo")
public class TodoController extends AuthenticatedController {
@Inject
private TodoItem.Dao dao;
@GetAction
public Iterable<TodoItem> myItems() {
AAA.requirePermission(me, PERM_VIEW_TODO_ITEM);
return dao.findBy("owner", me.email);
}
@PostAction
public TodoItem add(String subject) {
AAA.requirePermission(me, PERM_CREATE_TODO_ITEM);
TodoItem todoItem = new TodoItem(subject);
todoItem.owner = me.email;
return dao.save(todoItem);
}
@PutAction("{id}")
public TodoItem update(@DbBind("id") TodoItem todo, String subject) {
notFoundIfNull(todo);
AAA.requirePermission(todo, PERM_UPDATE_TODO_ITEM);
todo.subject = subject;
return dao.save(todo);
}
@DeleteAction("{id}")
public void delete(@DbBind("id") TodoItem todo) {
notFoundIfNull(todo);
AAA.requirePermission(todo, PERM_DELETE_TODO_ITEM);
dao.delete(todo);
}
}
该控制器提供操作 TODO 项的 RESTful 服务包括:
- 取当前用户所有的 TODO 项
- 创建新的 TODO 项目
- 修改已有的 TODO 项目
- 删除 TODO 项目
所有的请求均经过授权方予以执行
5.3 AuthenticatedController
public abstract class AuthenticatedController {
@LoginUser
protected User me;
}
提供该控制器是一个推荐用法, 所有需要用户登陆的控制器都可以继承该控制器, 并自动获取当前登陆用户的实例: this.me
. 这是使用了 act-aaa 提供的 @LoginUser
注解, 并由 ActFramework 进行依赖注入的.
总结
本博客讲述了如何在应用中使用 act-aaa 插件, 包括:
- 引入依赖
- 创建应用的 User 类和其他 User 关联类
- 配置应用的 AAA 层
- 处理用户注册登陆以及激活帐号
- 在资源控制器方法上进行授权
本博客的项目代码保存在码云上: https://git.oschina.net/greenlaw110/yatl
参考
- ActFramework 官网
- ActFramework视频
- AAA - ActFramework的安全框架I - 概念及简介
- simple-bbs - AAA 演示项目1
- Yet Another Todo List - AAA 演示项目2
ARMS 应用安全-你的应用运行时的隐形安全卫士
随着互联网成为日常生活的基础设施,企业的应用与服务遭受安全威胁和侵害的风险不断增加。这里面的风险不仅仅是网络层面的安全问题,更有应用本身的安全问题。在传统网络架构下,应用位于 IDC 内,有清晰的边界,应用威胁主要来自外部,边界内风险忽略不计,防护只需要关注边界入口处的南北向流量,传统上一般采用 WAF(即 web 应用防火墙)技术来拦截危险流量。
但随着微服务及容器化等技术发展,应用可以在 IDC/容器/公有云/私有云等多环境下部署,针对应用的威胁除了南北向还有来自东西向的横向威胁。网络环境日益复杂及漏洞攻击手段多样化,都造成产研团队牵扯更多精力修复问题,从而浪费公司大量研发成本。
现阶段典型的安全痛点如下:
- 审计不完的代码(数据统计显示平均 2 万行代码有 1 处漏洞,按照行业经验在交付的软件代码中 1000 行代码就有 1-25 处错误);
- 升级不完的三方组件,例如 fastjson,jackson,shiro,tomcat,struts2 等爆出多个 0day 漏洞需要紧急修复;
- 相同问题一犯再犯(例如注入,xss,csrf,broken auth 等依然在头部安全问题列表中反复出现);
- 总是被遗漏掉的应用(一些边缘历史应用长期无人治理以及存在大量未知应用悄悄上线,形同定时炸弹不可控);
- 网络架构约束下端到端加密、多重编码限制导致检测困难,以及被检测的流量需经过检测设备,否则可能会漏掉攻击);
- 远不止于 HTTP 协议攻击(大部分WAF 往往只能防御 HTTP 协议攻击,还存在 dubbo/thrift/rmi 等非 HTTP 协议以及各种私有协议也有被利用进行漏洞攻击);
- 代理攻击的无力(表现为一旦有一台内网机器被拿下,那么攻击者以这台主机为跳板,WAF 也无能为力);
- 漏洞扫描不友好(主要表现为黑盒的扫描器在进行漏洞扫描时候会对测试环境造成大量脏数据以及影响功能卡顿)。
如果我们的应用本身是否可以直接被加固,无需代码安全专家,无需额外代码修改,也无需复杂配置?对此,阿里云应用实时监控服务 ARMS 推出基于 RASP 的"应用安全"功能,让应用具备高水位的运行时安全防护,实现内生安全。
RASP 是什么?
2014 年,Gartner 将“Runtime Application Self-Protection”(简称 RASP)列为当年的应用安全关键趋势。作为在运行时检测应用程序攻击并进行自我保护的安全产品,RASP 将安全程序像疫苗一样注入到应用程序中,和应用程序融为一体,实时检测和阻断安全攻击,使应用程序具备自我保护能力。当应用程序遇到特定漏洞和攻击时,不需要人工干预就可以进行自动重新配置应对新的攻击。
以人体病毒防护举例,我们通过口罩来隔绝外部病毒进入人体,防火墙通过分析识别阻隔外部危险流量来保护系统。而 RASP 技术类似于人体疫苗抗体无需关心病毒进入的途径,部署在在服务端点上伴随应用启动,可以直接识别并拦截攻击。RASP 旨在缩小应用安全安全测试和网络边界控制所留下的空白地带,深入应用运行环境内部,实时了解数据和事件流,以监测或者阻止开发过程中无法预见的新威胁。
一般来说 WAF 在入口只能看到流量内容,即用户的原始请求,并不能感知应用最终会怎样执行这条请求。所以往往会产生一些误报漏报,例如用户在在线文档中上传一段 SQL 语句(误报)、利用PHP漏洞的报文打到了 JAVA 环境(虚报)、应用本身做过预编译,不存在 SQL 注入点(虚报)、ls 等短命令执行,极容易跟正常输入混淆(误报)。
而相比之下,RASP 只上报能利用成功的有效攻击。从原理上来说,RASP 关注运行时行为,不关心流量来源及形式,无需修改代码,以 Agent 方式随着应用启动,Hook 应用的底层关键函数以及参数内容,结合请求上下文,语义分析,基线等手段,判断当前的应用行为是否存在风险。从策略上来说,由于应用行为相对于千变万化的攻击特征来说是可穷举的,因此 RASP 针对 0day 以及各种变形攻击手法的防御效果要远好于 WAF 等以"特征"为检测对象的手段。另外,加密马等手段本质上也是对输入内容做变形以绕过基于特征的检测,RASP 同理也会无视,形成降维打击。
RASP 与 WAF 的差异
我们可以看到 RASP 与 WAF 一样都能检测流量和内容,也能够终止用户会话,但两者之间的差别在哪里呢?
接下来我们从场景,误漏报,维护性上等多个角度来对比 RASP 与 WAF 的差异特点。从上面的表格可以看到,例如 SQL 注入,XSS(跨站脚本),代码执行,webshell 等通用的 WEB 漏洞都具备一定的防御能力。由于 WAF 作为入口层比较擅长拒绝服务攻击,爬虫攻击,扫描器以及访问控制场景,而 RASP 与业务进程运行在一起,更擅长于 0day,复杂编码/加密流量,内存/加密码,非 HTTP 协议等场景,并具备以下特点:
- RASP 无视流量特征更关注应用行为,从误漏报上来说概率极低,只上报有效的攻击。
- RASP 部署在本地会消耗一部分服务器性能,而 WAF 仅消耗自身性能,对应用和源站无影响
- RASP 基本不依赖规则,无需联网,极少更新,反之 WAF 依赖规则需要联网更新或者进行云端运算
- 漏洞修复方面,RASP 通过虚拟补丁进行漏洞修复,同时能定位到漏洞利用的代码行,更胜一筹
总结来说,RASP 更接近云原生,可作为“分布式防火墙”,打破 WAF 边界防护局限性,符合零信任安全概念。而 WAF 基于边界安全,保护面广,RASP 针对 Nginx,Apache 等前置服务器的防护无法支持。所以 RASP 跟 WAF 有各自擅长的场景,结合自身场景,通过互补形成多层纵深防御,才能达到最好的效果。
接下来,我们来了解下 ARMS 应用安全基于阿里云 RASP 能力提供了哪些安全防护手段以及通过一个反序列化漏洞来感受下功能。
ARMS 应用安全
随着业务容器技术、无服务器技术的发展,业务的研发周期和功能迭代频率越来越快,而安全能力的滞后性越来越明显。例如,存在漏洞的老旧中间件升级困难,引入三方组件评估不充分可能引入高危漏洞,代码存在安全漏洞需要快速溯源定位修复等。DevSecOps 作为 DevOps 的延伸,安全应该成为每个人的关注点,优秀的 DevSecOps 工具应该最大程度上实现安全的自动化能力,减小对业务侵入性。而 ARMS 应用安全关注应用发布上线以后阶段的安全,对 DevSecOps 友好,提供完整的运行时安全整体方案,提升业务 7x24 小时安全的同时不引入额外负担影响业务发展,一定程度上改变业务与安全的对立的局面。
ARMS 应用监控探针集成了阿里云安全 RASP 技术,通过深度埋点技术,为您的应用注入"安全疫苗",只需要一键开启接入应用安全,就能够轻松识别黑客的危险攻击行为进行拦截告警,而不影响正常的客户请求,就像隐形的安全卫士一样时刻守护着您的应用运行安全。接下来我们详细看下攻击防护统计以及危险组件检测功能。
1、攻击统计
在很多场景下,我们在流量层面收到安全告警后,但很难与实际漏洞代码位置产生关联,而 ARMS 安全在应用监控探针中集成了阿里云安全 RASP 技术,用户开启安全防护后,一旦发生安全攻击,就可以收到攻击告警。目前支持命令执行漏洞,文件访问漏洞,网络访问漏洞、内存马绕过、JN 注入、恶意反射调用等多达 15 种类型攻击场景检测。
ARMS 应用安全在攻击统计页面,提供了应用正常行为统计以及攻击行为统计数据,方便您了解近期是否存在攻击事件。如果被检测到攻击,在该页面还可以分页查看攻击发生时间、攻击类型、请求 URL、上下文数据以及本次攻击的处理结果(监控还是拦截取决于您的防护设置);点击详情可以查看攻击来源 IP、请求详细参数、机器等信息,通过分析代码堆栈进一步定位到代码漏洞位置进行修复。
2、危险组件检测
在 JAVA 应用中,往往会依赖大量的三方开源组件以及各种中间件框架,往往这些组件存在未知漏洞难以发现和盘点,应用不清楚自身是否存在高危漏洞暴雷的可能。ARMS 应用安全提供的危险组件检测功能则可以帮助解除这方面的担忧,应用一旦集成探针并接入应用安全后,ARMS 会检测应用的三方组件依赖,自动分析关联权威的 CVE 漏洞库,并区分不同的漏洞危险等级。客户可以根据详情中的漏洞修复建议,优先升级组件解决高危漏洞。
针对尚未纳入 CVE 的漏洞,当新的漏洞报出来时,客户还可以第一时间通过"全量组件自查"功能,利用关键词搜索快速检查自身依赖的组件版本是否涉及到该安全漏洞,做到心中有数,确定是否需要处理。
漏洞防护示例
接下来,我们通过 fastjson 反序列化漏洞的攻击场景来演示 ARMS 应用安全的实际防护效果,fastjson 的反序列化过程主要如下:
User类:
package com.test;public class User { private String name; public void setName(String Name) { this.name = Name; } public String getName() { return name; }}
恶意类:
package com.test;import java.io.IOException;public class Evil { //恶意Setter public void setCommand(String command) throws IOException { Runtime.getRuntime().exec(command); }}
反序列化内容:
{
"rand": {
"@type": "com.test.Evil",
"command": "open /System/Applications/Calculator.app/"
}
}
反序列化漏洞代码:
public static void main(String[] args) {
//指定User为所需要的类型,但成功反序列化了Evil类型,并执行了命令
User user = JSON.parseObject("{\"rand\":{\"@type\":\"com.test.Evil\",\"command\":\"open /System/Applications/Calculator.app/\"}}", User.class);
}
当执行上述代码时,在反序列时设置 User 属性时会触发命令执行,本文会打开某个内部应用程序,在真实攻击场景下可能会获取系统配置以及访问敏感信息等造成的危害更大。
在开启了 ARMS 应用安全时,当发生上述反序列漏洞时,ARMS 应用安全会识别到序列化漏洞产生的命令执行行为,可以直接阻断该反序列化动作并发送告警通知,在攻击详情可以进一步分析攻击源以及漏洞代码位置。
fastjson 反序列化的利用核心,还是在于寻找可以造成代码执行的类,实际上在 java 庞大的 jdk 类库以及第三方类库中存在着大量的可利用类,传统流量产品如 WAF,往往需要在防御时去穷举这些可能被利用的类(经常会出现遗漏),而 ARMS 应用安全基于 RASP 的上下文分析能力与基于行为的攻击发现方式,将会捕获掉所有由 fastjson 造成的代码执行、恶意文件上传、敏感文件读写等行为,而无需去针对特定代码实现做精准匹配,所以完全不会存在被绕过的可能性。
结合以上案例,我们可以看到 ARMS 应用安全的整体产品优势,ARMS应用安全全面支持 0day 防御,攻击告警以及溯源,支持危险组件检测,并提供漏洞修复建议。一键开启接入,无需修改代码,支持软熔断逃生避免影响应用以及观察模式体验实际效果。通过压测来看,开启应用安全防护模式后 CPU 平均开销增加低于 1%,内存占用低于 30M,平均 RT 耗时毫秒级,对系统性能影响极低。通过防护设置您可以选择观察/拦截模式、自定义勾选攻击检测类型、自定义检测超时时间,进一步降低性能影响。
通过集成 ARMS 应用安全运行时自我防护能力,为您的应用注入"疫苗",实现应用默认安全,抵御日常 OWASP 攻击,默认防御 fastjson 等新漏洞(0day)以及内存马,加密马等。还可以自动发现三方组件高危漏洞,溯源攻击行为精准定位修复问题。一键开关,稳定低消耗运行,运维无负担,为您的业务安全保驾护航!
目前 ARMS 应用安全正在公测中,欢迎钉钉搜索加入钉群(34833427)或关注 Apache RocketMQ 官方公众号,了解更多产品详情。
ASP.NET标识 – 强制使用安全戳重新登录
在我的MVC应用程序中,管理员可以归档用户.当拱形时,它们应该立即被注销并被迫再次登录(这将因为它们被存档而拒绝它们).
我怎样才能做到这一点?我知道安全标记是关键.默认设置如下所示:
app.UseCookieAuthentication(new CookieAuthenticationoptions { AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,LoginPath = new PathString("/Account/Login"),Provider = new CookieAuthenticationProvider { // Enables the application to validate the security stamp when the user logs in. // This is a security feature which is used when you change a password or add an external login to your account. OnValidateIdentity = SecurityStampValidator.OnValidateIdentity<ApplicationUserManager,ApplicationUser>( validateInterval: TimeSpan.FromMinutes(30),regenerateIdentity: (manager,user) => user.GenerateUserIdentityAsync(manager)) } });
通过实验,如果我将validateInterval设置为1分钟,然后在数据库中手动破解用户安全标记,那么它们将被强制重新登录,但仅在该时间段过去之后.
有没有办法让这个瞬间,或者只是将间隔设置为一个低时间段并等待(或实现我自己的OnValidateIdentity检查每个请求)
谢谢
解决方法
这是一个类似的问题:Propagate role changes immediately
今天关于CNAAC应用安全标识“年终特惠”活动介绍的讲解已经结束,谢谢您的阅读,如果想了解更多关于4.(基础)tornado应用安全与认证、AAA - ActFramework的安全框架II - 应用、ARMS 应用安全-你的应用运行时的隐形安全卫士、ASP.NET标识 – 强制使用安全戳重新登录的相关知识,请在本站搜索。
本文标签: