掌握XSS与CSFR,我也可以是个黑客!

  • 2019 年 10 月 6 日
  • 筆記

今天呀,我想当一名黑客,去黑别人的网站!我有两三技能,独乐不如众乐乐,今天我也把这个几个攻击手段教给你,咱们一起搞事情去。

首先我们来了解一下攻击手段,也是比较常见的两种攻击手段了:

  • CSRF
  • XSS

CSRF

全称:Cross-site request forgery,跨站请求伪造。原理是:通过伪装成受信任用户的请求来攻击受信任的网站。

如何伪装?如何才算攻击?

生活中其实我们不缺这种例子,比如说我们经常接收到一些来历不明的垃圾短信,短信内容里面有个url链接,有些人手贱点开了链接,然后就发现钱不见了!!

我们从技术角度来复原一下这个过程,首先设定一些基础:

  • 垃圾短信里的链接(垃圾网):http://www.lajiwang.com/pianqian
  • 存了钱的网站(存钱网):http://www.cunqianwang.com/

然后用户动作是:点开了垃圾网的链接,但是存钱网里账户的钱不见了。既然是自己账户的钱不见了,所以这里其实有个前提:用户已经登录了存钱网!所以准确来说用户的动作是这样的:点开了垃圾网的链接,但是之前登录过的存钱网里账户的钱不见了!

两个网站毫无关联,为啥会造成这个让人意想不到的后果呢?

其实呀,垃圾网的人为了达到攻击的目的,偷偷在网页上嵌入了存钱网的链接,所以打开垃圾网时候顺便也触发了存钱网的转账的链接,整体逻辑如下:

  • 1、用户登录成功存钱网,于是浏览器中产生了网站cookie
  • 2、用户在没有退出存钱网的情况下,访问了垃圾网
  • 3、垃圾网要求访问存钱网的转账url,转账url带上存钱网的cookie去访问服务器
  • 4、存钱网服务器验证转账url确认是用户在转账,转账成功!

说到这里,你发现漏洞在哪里没有?大家都知道cookie代表用户身份,每次发起请求,请求头里都会附上用户的cookie信息,既然cookie是存在浏览器的,我偷不到你的cookie,那么我就让你在不知道到的情况下让你自己去操作。

举个例子:假如一家银行转账操作的URL地址如下:

http://www.cunqianwang.com/zhuanzhang?account=A&for=B&amount=500

那么,一个垃圾网中可以放置如下代码

<img src="http://www.cunqianwang.com/zhuanzhang?account=A&for=B&amount=500">

好了,原理和攻击手段我们都懂了,那么我们来说说几种常见的预防手段:

1、检查referer字段

HTTP头中有一个Referer字段,这个字段是用来标明请求来源于哪一个网址。当网站A去访问网站B的资源时候,链接上的请求头上就会有Referer字段。注意是在不同域名下才有。

我随意打开hao123.com的首页,一些图片不是放在hao123.com域名下的,所以会在header中带上Referer字段表示请求源是hao123.com。

那么服务器可以通过判断Referer字段来判断请求的来源。所以在垃圾网站里访问存钱网,Referer的值就是垃圾网的域名,就能判断是不是合法的操作啦。

java代码里获取Referer字段值代码是:

String referer = request.getHeader("Referer");

这种方法简单易行,但也有其局限性。http协议无法保证来访的浏览器的具体实现,可以通过篡改Referer字段的方式来进行攻击,所以就要看你用的浏览器高级不高级了,如果你用的浏览刚好是骗子开发的浏览器,嘿嘿~~

2、Token 验证

既然我们要判定用户行为的合法性,那么我就给用户颁发一个合法token,除了带上cookie,还得带上token才行,token在前一个步骤中获取。

逻辑如下:

  • 服务器发送给客户端一个token;
  • 客户端提交的表单中带着这个token。
  • 如果这个 token 不合法,那么服务器拒绝这个请求。

3、添加图片验证码、短信验证等

重要步骤添加验证码认证后才能操作。脑补,略略略略~

学会攻击

好了,作为一名出色的黑客,必须要知道自己攻击手段的漏洞在哪,怎么防御,绝不做无用功!既然预防手段我知道了,那么接下来就是我展现真正技术的时候了。

嘿嘿,很多公司在一开始的时候为了节约成本,选择用开源项目作为基础,然后再二次开发。虽说开发快,但其实未必安全,一些开源项目如果没有做csrf的预防,那么漏洞就一直存在。

经过我多天的研究,终于发现了某个商城用的是开源项目二次开发的,没有csrf预防。商城的积分可以直接赠送给别人,我立马搞了个网页,嵌入网站赠送积分的链接。

于是有了我和我朋友的对白。

  • 我:小明呀,你的A商城还有多少积分呀?
  • 小明:2000多吧?
  • 我:这么多?我不信!你登录让我看看!
  • 小明去登录A网站给我看积分,果然2000多。
  • 我:小明呀,我开发了个网站,我发给你看看能不能打开
  • 小明打开网站,小明的积分到我账户了~

当黑客感觉真好,小明,你是个好人~

XSS

全程:Cross Site Scripting,中文:跨域脚本攻击。原理:不需要你做任何的登录认证,通过合法的操作(比如在url中输入、在评论框中输入),向你的页面注入脚本(可能是js、hmtl代码块等),类似于SQL注入。

通俗点讲就是:恶意攻击者往Web页面里插入恶意html代码,当用户浏览该页之时,嵌入其中Web里面的html代码会被执行,从而达到恶意用户的特殊目的。

讲再细点其实就是:利用输入内容来闭合对应的html标签,从而执行输入内容的脚本。

攻击形态

xss有两种形态(网友总结):

  • 1、反射型

发出请求时,XSS代码出现在url中,作为输入提交到服务器端,服务器端解析后响应,XSS代码随响应内容一起传回给浏览器,最后浏览器解析执行XSS代码。这个过程像一次反射,所以叫反射型XSS。

  • 2、存储型

存储型XSS和反射型XSS的差别在于,提交的代码会存储在服务器端(数据库、内存、文件系统等),下次请求时目标页面时不用再提交XSS代码。

攻击手段

不管是什么类型,你get到关键点没有?关键点以及技术难点其实在于如何往页面中嵌入恶意的代码。

下面我们来写个例子模拟一下:首先我页面写了个form表单:

  • index.ftl
<form action="/submit" method="post">      名称:<input name="name" value="${name}">      <input type="submit">  </form>

controller中有个基本跳转,还有form表单的提交:

  • com.example.IndexController#index
@GetMapping("")  public String index(HttpServletRequest request) {      request.setAttribute("name", "公众号:java思维导图");      return "index";  }    @PostMapping("/submit")  public String submit(HttpServletRequest request) {      String name = request.getParameter("name");      System.out.println("name---------->" + name);        // 假装只有名字为“求关注”才能通过      if(!name.equals("求关注")) {          request.setAttribute("name", name);      }      return "index";  }

初始效果如下:

ok,基本逻辑也写好了,一个简单的表单提交,提交之后如果数据不对,或格式不对就会返回表单页面,同时回显表单数据。

加入我想嵌入脚本如下:

<script>alert(1);</script>

那么我该怎么样才能往这个页面上嵌入代码呢?我打开F12,研究一下

要是这个这个脚本能提到input的外面,value能提前结束就好了。嘿嘿,突然想到,既然我改不了原来的,那么我就创造一个。

于是我改了一下输入的值成:

"><script>alert(1);</script>

这">不就跑到前面了嘛,哈哈哈,天才,我赶紧试试。谷歌浏览器测试结果如下:

脚本的确跑到外面了,但是alert(1)怎么不见了呀,我赶紧调试一下:

不是后端在搞事情,那么真相就只有一个,谷歌浏览器在搞事情,谷歌果然强大,还能辨别我的脚本并和谐掉。

我换个Edge浏览器再试试:

哇,果然Edge你最帅,我想要的你都给我~ F12看下:

没毛病,原声原味的alert(1);

好了上面我们已经弄懂了xss的嵌入脚本的方式,我们输入是合法的,只是内容有点取巧,这就是xss的攻击手段。

除了这个input标签,其实还有很多标签比较常用,比如title、a、img、script等。

上面这个一般都是反射性的xss攻击,我们再来看看一个存储类型的title的例子。

在很多博客中,我们都可以发布文章,我们需要写文章标题,文章内容等,文章标题一般我们还会放在我们的head的title中,用于标签展示当前浏览文章标题。

加入说,我们的页面是这样展示的:

  • title.ftl
<!DOCTYPE html>  <html>  <head>      <title>${title}</title>  </head>  <body>      这是内容 - ${content}  </body>  </html>

而controller中传过来的内容如下:

  • IndexController
@GetMapping("/title")  public String title(HttpServletRequest request) {      request.setAttribute("title", "</title><script>alert('公众号java思维导图');</script>");      request.setAttribute("content", "内容是关注公众号:java思维导图");      return "title";  }

最后我们的得到的页面展示这样子:加载时候先执行弹窗:alert("公众号java思维导图");然后再加载内容。

因为一般我们文章标题内容都是保存到数据库的,所以每次渲染都会执行脚本,所以是个存储型xss攻击。

解决方法

好了,看了我们的例子项目,我们已经意识到了xss攻击的可怕性,一单发布文章都可以写脚本,那么所有的用户打开这篇文章都会被执行脚本,影响可就大了。那么有什么好的解决方法吗?

这里给大家介绍几个解决方法。我们先来看renren-fast项目是怎么解决这个问题的:

  • renren-fast
#识别攻击脚本、并删掉对应可执行脚本的标签  HTMLFilter  #全局过滤器,包装request  XssFilter  #包装request,重写request的几个重要方法,比如getParameter等  XssHttpServletRequestWrapper

所以renren-fast项目的设计逻辑是加入一个全局过滤器,然后通过包装请求的request,重写request的getParameter、getHeader、getInputStream等方法,在这些方法里面都进行一遍过滤,从而去掉所有的攻击脚本。看看重要代码:

  • io.renren.common.xss.XssFilter
public class XssFilter implements Filter {       public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)              throws IOException, ServletException {        XssHttpServletRequestWrapper xssRequest = new XssHttpServletRequestWrapper(              (HttpServletRequest) request);        chain.doFilter(xssRequest, response);     }       ...  }
  • io.renren.common.xss.XssHttpServletRequestWrapper
public class XssHttpServletRequestWrapper extends HttpServletRequestWrapper {        @Override      public String getParameter(String name) {          String value = super.getParameter(xssEncode(name));          if (StringUtils.isNotBlank(value)) {              value = xssEncode(value);          }          return value;      }      ...  }

可以看到上面的xssEncode就是进行过滤脚本的方法;xssEncode方法代码如下:

private String xssEncode(String input) {      return htmlFilter.filter(input);  }

ok,相信你已经弄明白了。

我们来看看另一个博客项目mblog的解决方法:

  • mblog
#通用控制器  BaseController    #自定义编辑器  StringEscapeEditor

mblog项目其实是通过注册所有controller的自定义编辑器,在提交表单时候对所有字段都进行一层get和set,在set的过程中对输入内容进行一番检查,如果有脚本就进行替换等操作。

详细代码如下:

  • BaseController
@InitBinder  public void initBinder(ServletRequestDataBinder binder) {        /**       * 防止XSS攻击       */      binder.registerCustomEditor(String.class, new StringEscapeEditor(true, false));        ...  }

@InitBinder用于表单到方法的数据绑定的,这里绑定了一个自定义编辑器StringEscapeEditor。

  • StringEscapeEditor

可以看到setAsText中就是对脚本进行过滤等操作的。

这两种方法都学会了吗?其实逻辑都是对脚本进行过滤替换删除等操作。

学会攻击

好了,又到了黑客show time,某个知名博客平台没防御xss攻击,这时候我发布了一篇文章,title中包含了脚本

<script>alert("刘亦菲,我爱你");</script>

于是,全世界都知道我的表白,我们最后开心得在一起了。

真是浪漫有爱,又爱做白日梦的黑客,鼓掌,鲜花~

结束语

文中涉及到的项目:

  • renren-fast:https://gitee.com/renrenio/renren-fast.git
  • mblog:https://gitee.com/mtons/mblog.git

好啦,今天的文章先到这里了。

如果你喜欢我的文章,欢迎关注我的公众号:java思维导图,给我点个在看或者转发一下,万分感谢哈!