防范 CSRF 跨站请求伪造

CSRF(Cross-site request forgery,中文为跨站请求伪造)是一种利用网站可信用户的权限去执行未授权的命令的一种恶意攻击。通过伪装可信用户的请求来利用信任该用户的网站,这种攻击方式虽然不是很流行,但是却难以防范,其危害也不比其他安全漏洞小。

本文将简要介绍CSRF产生的原因以及利用方式,然后对如何避免这种攻击方式提供一些可供参考的方案,希望广大程序猿们都能够对这种攻击方式有所了解,避免自己开发的应用被别人利用。

CSRF也称作one-click attack或者session riding,其简写有时候也会使用XSRF

什么是CSRF?

简单点说,CSRF攻击就是 攻击者利用受害者的身份,以受害者的名义发送恶意请求。与XSS(Cross-site scripting,跨站脚本攻击)不同的是,XSS的目的是获取用户的身份信息,攻击者窃取到的是用户的身份(session/cookie),而CSRF则是利用用户当前的身份去做一些未经过授权的操作。

CSRF攻击最早在2001年被发现,由于它的请求是从用户的IP地址发起的,因此在服务器上的web日志中可能无法检测到是否受到了CSRF攻击,正是由于它的这种隐蔽性,很长时间以来都没有被公开的报告出来,直到2007年才真正的被人们所重视。

CSRF有哪些危害

CSRF可以盗用受害者的身份,完成受害者在web浏览器有权限进行的任何操作,想想吧,能做的事情太多了。

  • 以你的名义发送诈骗邮件,消息
  • 用你的账号购买商品
  • 用你的名义完成虚拟货币转账
  • 泄露个人隐私

产生原理以及利用方式

要完成一个CSRF攻击,必须具备以下几个条件:

  • 受害者已经登录到了目标网站(你的网站)并且没有退出
  • 受害者有意或者无意的访问了攻击者发布的页面或者链接地址

(图片来自网络,出处不明,百度来的😂)

整个步骤大致是这个样子的:

  1. 用户小明在你的网站A上面登录了,A返回了一个session ID(使用cookie存储)
  2. 小明的浏览器保持着在A网站的登录状态,事实上几乎所有的网站都是这样做的,一般至少是用户关闭浏览器之前用户的会话是不会结束的
  3. 攻击者小强给小明发送了一个链接地址,小明打开了这个地址,查看了网页的内容
  4. 小明在打开这个地址的时候,这个页面已经自动的对网站A发送了一个请求,这时候因为A网站没有退出,因此只要请求的地址是A的就会携带A的cookie信息,也就是使用A与小明之间的会话
  5. 这时候A网站肯定是不知道这个请求其实是小强伪造的网页上发送的,而是误以为小明就是要这样操作,这样小强就可以随意的更改小明在A上的信息,以小明的身份在A网站上进行操作

利用方式

利用CSRF攻击,主要包含两种方式,一种是基于GET请求方式的利用,另一种是基于POST请求方式的利用。

GET请求利用

使用GET请求方式的利用是最简单的一种利用方式,其隐患的来源主要是由于在开发系统的时候没有按照HTTP动词的正确使用方式来使用造成的。对于GET请求来说,它所发起的请求应该是只读的,不允许对网站的任何内容进行修改

但是事实上并不是如此,很多网站在开发的时候,研发人员错误的认为GET/POST的使用区别仅仅是在于发送请求的数据是在Body中还是在请求地址中,以及请求内容的大小不同。对于一些危险的操作比如删除文章,用户授权等允许使用GET方式发送请求,在请求参数中加上文章或者用户的ID,这样就造成了只要请求地址被调用,数据就会产生修改。

现在假设攻击者(用户ID=121)想将自己的身份添加为网站的管理员,他在网站A上面发了一个帖子,里面包含一张图片,其地址为http://a.com/user/grant_super_user/121

<img src="http://a.com/user/grant_super_user/121"/>

设想管理员看到这个帖子的时候,这个图片肯定会自动加载显示的。于是在管理员不知情的情况下,一个赋予用户管理员权限的操作已经悄悄的以他的身份执行了。这时候攻击者121就获取到了网站的管理员权限。

POST请求利用

相对于GET方式的利用,POST方式的利用更加复杂一些,难度也大了一些。攻击者需要伪造一个能够自动提交的表单来发送POST请求。

//

只要想办法实现用户访问的时候自动提交表单就可以了。

如何防范

防范原理

防范CSRF攻击,其实本质就是要求网站能够识别出哪些请求是非正常用户主动发起的。这就要求我们在请求中嵌入一些额外的授权数据,让网站服务器能够区分出这些未授权的请求,比如说在请求参数中添加一个字段,这个字段的值从登录用户的Cookie或者页面中获取的(这个字段的值必须对每个用户来说是随机的,不能有规律可循)。攻击者伪造请求的时候是无法获取页面中与登录用户有关的一个随机值或者用户当前cookie中的内容的,因此就可以避免这种攻击。

防范技术

Synchronizer token pattern

令牌同步模式(Synchronizer token pattern,简称STP)是在用户请求的页面中的所有表单中嵌入一个token,在服务端验证这个token的技术。token可以是任意的内容,但是一定要保证无法被攻击者猜测到或者查询到。攻击者在请求中无法使用正确的token,因此可以判断出未授权的请求。

Cookie-to-Header Token

对于使用Js作为主要交互技术的网站,将CSRF的token写入到cookie中

Set-Cookie: CSRF-token=i8XNjC4b8KVok4uw5RftR38Wgp2BFwql; expires=Thu, 23-Jul-2015 10:25:33 GMT; Max-Age=31449600; Path=/

然后使用javascript读取token的值,在发送http请求的时候将其作为请求的header

X-CSRF-Token: i8XNjC4b8KVok4uw5RftR38Wgp2BFwql

最后服务器验证请求头中的token是否合法。

验证码

使用验证码可以杜绝CSRF攻击,但是这种方式要求每个请求都输入一个验证码,显然没有哪个网站愿意使用这种粗暴的方式,用户体验太差,用户会疯掉的。

简单实现STP

首先在index.php中,创建一个表单,在表单中,我们将session中存储的token放入到隐藏域,这样,表单提交的时候token会随表单一起提交

<?php
$token = sha1(uniqid(rand(), true));
$_SESSION['token'] = $token;
?>
<form action="buy.php" method="post">
    <input type="hidden" name="token" value="<?=$token; ?>" />
    ... 表单内容
</form>

在服务端校验请求参数的buy.php中,对表单提交过来的token与session中存储的token进行比对,如果一致说明token是有效的

<code>
  <?php if ($_POST['token'] != $_SESSION['token']) {
    // TOKEN无效
    throw new \Exception('Token无效,请求为伪造请求');
}
// TOKEN有效,表单内容处理
</code?>
</code>

对于攻击者来说,在伪造请求的时候是无法获取到用户页面中的这个token值的,因此就可以识别出其创建的伪造请求。

解析Laravel框架中的VerifyCSRFToken中间件

在Laravel框架中,使用了VerifyCSRFToken这个中间件来防范CSRF攻击。

在页面的表单中使用{{ CSRF_field() }}来生成token,该函数会在表单中添加一个名为_token的隐藏域,该隐藏域的值为Laravel生成的token,Laravel使用随机生成的40个字符作为防范CSRF攻击的token。

$this->put('_token', Str::random(40));

如果请求是ajax异步请求,可以在meta标签中添加token

<meta name="CSRF-token" content="{{ CSRF_token() }}"/>

使用jquery作为前端的框架时候,可以通过以下配置将该值添加到所有的异步请求头中

$.ajaxSetup({
    headers: {
        'X-CSRF-TOKEN': $('meta[name="CSRF-token"]').attr('content')
    }
});

在启用session的时候,Laravel会生成一个名为_token的值存储到session中。而使用前面两种方式在页面中加入的token就是使用的这一个值。在用户请求到来时,VerifyCSRFToken中间件会对符合条件的请求进行CSRF检查

if (
  $this->isReading($request) ||
  $this->runningUnitTests() ||
  $this->shouldPassThrough($request) ||
  $this->tokensMatch($request)
) {
  return $this->addCookieToResponse($request, $next($request));
}

throw new TokenMismatchException;

if语句中有四个条件,只要任何一个条件结果为true则任何该请求是合法的,否则就会抛出TokenMismatchException异常,告诉用户请求不合法,存在CSRF攻击。

第一个条件$this->isReading($request)用来检查请求是否会对数据产生修改

protected function isReading($request)
{
    return in_array($request->method(), ['HEAD', 'GET', 'OPTIONS']);
}

这里判断了请求方式,如果是HEADGETOPTIONS这三种请求方式则直接放行。你可能会感到疑惑,为什么GET请求也要放行呢?这是因为Laravel认为这三个请求都是请求查询数据的,如果一个请求是使用GET方式,那无论请求多少次,无论请求参数如何,都不应该最数据做任何修改

第二个条件顾名思义是对单元测试进行放行,第三个是为开发者提供了一个可以对某些请求添加例外的功能,最后一个$this->tokensMatch($request)则是真正起作用的一个,它是Laravel防范CSRF攻击的关键

$sessionToken = $request->session()->token();
$token = $request->input('_token') ?: $request->header('X-CSRF-TOKEN');

if (! $token && $header = $request->header('X-XSRF-TOKEN')) {
  $token = $this->encrypter->decrypt($header);
}

if (! is_string($sessionToken) || ! is_string($token)) {
  return false;
}

return hash_equals($sessionToken, $token);

Laravel会从请求中读取_token参数的的值,这个值就是在前面表单中添加的CSRF_field()函数生成的。如果请求是异步的,那么会读取X-CSRF-TOKEN请求头,从请求头中读取token的值。

最后使用hash_equals函数验证请求参数中提供的token值和session中存储的token值是否一致,如果一致则说明请求是合法的。

你可能注意到,这个检查过程中也会读取一个名为X-XSRF-TOKEN的请求头,这个值是为了提供对一些javascript框架的支持(比如Angular),它们会自动的对异步请求中添加该请求头,而该值是从Cookie中的XSRF-TOKEN中读取的,因此在每个请求结束的时候,Laravel会发送给客户端一个名为XSRF-TOKEN的Cookie值

$response->headers->setCookie(
    new Cookie(
        'XSRF-TOKEN', $request->session()->token(), time() + 60 * $config['lifetime'],
        $config['path'], $config['domain'], $config['secure'], false
    )
);

写在最后

本文只是对CSRF做了一个简单的介绍,主要是侧重于CSRF是什么以及如何应对CSRF攻击。有一个事实是我们无法回避的:没有绝对安全的系统,你有一千种防御对策,攻击者就有一千零一种攻击方式,但不管如何,我们都要尽最大的努力去将攻击者拦截在门外。如果希望深入了解如何发起一个CSRF攻击,可以参考一下这篇文章 从零开始学CSRF。

作为一名web方向的研发人员,无论你是从事业务逻辑开发还是做单纯的技术研究,了解一些安全方面的知识都是很有必要的,多关注一些安全方向的动态,了解常见的攻击方式以及应对策略,必将在你成长为一名大牛的路上为你“推波助澜”。

参考

防范 CSRF 跨站请求伪造,首发于文章 – 伯乐在线

想写无Bug的安全代码?看防御性编程的艺术

为什么开发者不编写安全的代码?我们在这并不是要再一次讨论「整洁代码」。我们要从纯粹的实用观点出发,讨论软件的安全性和保密性。是的,因为不安全的软件不仅无用,而且还可怕。我们来看看什么是不安全的软件。

  • 1996年6月4日,欧洲航天局的 Ariane 5 Flight 501 在起飞后 40 秒被引爆。因为导航软件里的一个 bug,这个价值 10 亿美金的运载火箭不得不自毁。
  • 1991年2月25日,MIM-104 Patriot(爱国者)里的一个软件错误使它的系统每一百小时有三分之一秒的时钟偏移,导致定位拦截入侵导弹失败。结果伊拉克的飞毛腿导弹击中宰赫兰(沙特阿拉伯东北部城市)的一个美军军营,28 人死亡,100 多人受伤。
  • 其他案例,请参见《Bug 引发的 18 次重大事故》。

这应该能够说明编写安全软件的重要性了,尤其在特定的环境中。当然也包括其他用例中,我们也应该意识到我们的软件 bug 会导致什么后果。

防御性编程初窥

为什么我认为在特定种类的工程中,防御性编程是解决这些问题好办法?

抵御那些不可能的事,因为看似不可能的事也会发生。

防御性编程中有很多防御方式,这也取决于你的软件项目所需的「安全」级别和资源级别。

防御性编程是防御式设计的一种形式,用来确保软件在未知的环境中能继续运行。防御性编程的实践往往用于需要高可用性、安全性、保密性的地方。—— 维基百科

我个人相信这种方法适合很多人参与的大型、长期的项目。例如,一个需要大量维护的开源项目。

我们来探索一下我提出的关键点,来完成一个防御性编程的实现。

永远不要相信用户输入

设想你总是获取到你不想要的东西。因为像我们说过的,我们预期的是异常情况的出现,(所以)要时刻防备用户输入以及通常会传入你系统的东西,这是你成为一个防御性程序员的方法。试着做到尽可能的严格,确保输入的值就是你所期望的值。

进攻是最好的防守

设置白名单而不是黑名单。举个例子,当你验证图像扩展名时,不要检查非法的类型,而是检查合法的类型并排除其他类型。在 PHP 有无数的开源校验库可以让你的工作变简单。

进攻是最好的防守。共勉

数据库抽象化

OWASP Top 10 Security Vulnerabilities 排首位的是注入攻击。这意味着有些人(很多人)还没有使用安全的工具来查询数据库。请使用数据库抽象包或库。在 PHP 里你可以使用 PDO确保基本的注入攻击防范

不要重复发明轮子

你不用框架(或微框架)吗?好吧恭喜你,你喜欢毫无理由地做额外的工作。这并不仅跟框架有关,也意味着你可以方便地使用已经存在的、经过测试的、受万千开发者信任的、稳定的新特性,而不是你只为了自己从中受益而制作的东西。你自己创建方法的唯一原因是你需要的东西不存在,或存在但不符合你的需求(性能差、缺失特性等等)。

这就是所谓的智能代码重用。拥抱它吧。

不要相信开发者

防御性编程与防御性驾驶相关联。在防御性驾驶中,我们假设周围的每个人都可能犯错。所以我们要留意别人的行为。相同概念也适用于防御性编程,我们作为开发者不要相信其他开发者的代码。我们同样也不要相信我们的代码。

在很多人参与的大型项目中,我们有许多方式编写并组织代码。这也导致混乱甚至更多的 bug。这也是我们需要加强规范代码风格和代码检查的原因,让生活更轻松。

编写符合 SOLID 原则的代码

这是(防御性)编程最困难的部分——编写不糟糕的代码。这也是很多人知道并一直在讨论的,但没有人真正关心或将注意力和精力放在实现符合 SOLID 原则的代码上。

让我们看一些糟糕的例子

避免:未初始化的属性

<?php

class BankAccount
{
    protected $currency = null;
    public function setCurrency($currency) { ... }
    public function payTo(Account $to, $amount)
    {
        // sorry for this silly example
        $this->transaction->process($to, $amount, $this->currency);
    }
}

// I forgot to call $bankAccount->setCurrency('GBP');
$bankAccount->payTo($joe, 100);

在这个例子中,我们需要牢记签发付款前要先调用 setCurrency。这是很糟糕的事情,一个像这样的改变状态的操作(签发付款)不应该分两步来完成,且使用两个公开的方法。我们还可以用很多方法付款,但我们必须只有一个公开的方法来改变状态(对象不应该存在不一致的状态)。

在这个例子中,我们把它改进,将未初始化的属性封装进 Money 对象。

<?php

class BankAccount
{
    public function payTo(Account $to, Money $money) { ... }
}

$bankAccount->payTo($joe, new Money(100, new Currency('GBP')));

使它万无一失。不要使用未初始化的对象属性。

避免:在类的作用域外泄露状态

<?php

class Message
{
    protected $content;
    public function setContent($content)
    {
        $this->content = $content;
    }
}

class Mailer
{
    protected $message;
    public function __construct(Message $message)
    {
        $this->message = $message;
    }
    public function sendMessage(
    {
        var_dump($this->message);
    }
}

$message = new Message();
$message->setContent("bob message");
$joeMailer = new Mailer($message);

$message->setContent("joe message");
$bobMailer = new Mailer($message);

$joeMailer->sendMessage();
$bobMailer->sendMessage();

在这个例子中,Message 是通过引用传递的,两个实例的输出都是 “joe message”。一个解决方案是复制 Mailer 构造函数中的 message 对象。但是我们应该做的是试着使用(不可变的)值对象,而不是简单可变的 Message 对象。尽可能使用不可变的对象。

<?php

class Message
{
    protected $content;
    public function __construct($content)
    {
        $this->content = $content;
    }
}

class Mailer 
{
    protected $message;
    public function __construct(Message $message)
    {
        $this->message = $message;
    }
    public function sendMessage()
    {
        var_dump($this->message);
    }
}

$joeMailer = new Mailer(new Message("bob message"));
$bobMailer = new Mailer(new Message("joe message"));

$joeMailer->sendMessage();
$bobMailer->sendMessage();

编写测试

这点我们很还需要再说吗?编写单元测试可以帮助你秉承一般的原则,比如高内聚、单一职责、低耦合和正确的对象组合。它不仅帮助你测试小的单元用例,也能测试你组织对象的方式。确实,当测试你的小功能时,你会清晰的看到你需要测试多少情况和需要模拟多少对象,来达到 100% 的覆盖率。

结论

希望你喜欢这篇文章。记住这些仅仅是建议而已,由你决定何时、何处以及是否应用。

感谢阅读!

想写无Bug的安全代码?看防御性编程的艺术,首发于文章 – 伯乐在线

百亿互金平台救火故事

多年前,又是周六客服打电话过来,平台官网不能访问,app完全无法打开,客户在QQ群和微信群中各种反馈,说平台是不是跑路了?客服的多条400热线完全被打爆,电话已经接不过来…

前言

一直以来总是想以什么方式去记录下自己在互金行业的这段经历,趁着自己还记得清楚,还能找到一些资料原型,一方面可以分享出来供大家参考,但是更重要就是多年以后我可以根据这些文章回忆起来自己的那段激情岁月。

想了很久但一直没有实施,后来觉得应该从架构的角度来梳理一篇文章,就写《从零到百亿互联网金融架构发展史》这篇文章;最后认为只有实战出来的东西以及解决问题的过程,才是工作中最宝贵的经验,应该把它分享出来,在梳理的过程中觉得有三起事故比较有代表性就整理出了下面这三篇文章,本篇文章从整体来回忆一下一路走过来所经历过的救火故事。

作为一个互联网金融平台,涉及到用户资金,任何的服务(资金)差错用户都是不可容忍的,用户不懂什么是数据库,不知道什么网络不通,就是一会看不到钱在app里面展示都会觉得不安。在已经有很多P2P公司跑路的前提下,用户个个已经被锻炼成为福尔摩斯侦探,每天打开app查看收益,监控着平台一切,甚至半夜升级断网十分钟,也会被用户察觉,直接就发到群里面,更有甚者直接在QQ群或者微信群中你们的技术行不行!

我们常说的互联网工作经验,一方面是开发经验,但其实更重要的是处理问题的能力。那么处理问题的能力怎么来呢,就是不断的去解决问题,不断的去总结经验,其中处理生产环境中问题的经验更甚,因为在处理生产环境中对个人的压力和临危应变的能力要求最高,你不但需要面临千万个用户反馈,客服不时得催促而且旁边可能就站了N个领导在看着你,一副你行不行的样子要求立刻马上解决问题!这个时候你的操作就非常重要,稍有不慎便会引发二次生产事故。

说了这么多,只是想说明,生产事故对技术综合能力要求颇高,更是锻炼处理问题能力最佳时机!下面给大家介绍我们从零开发到现在百亿交易量所遇到的几次关键事故,有大有小挑出一些比较有代表性的事件来分享。

并发满标

公司系统刚上线的时候,其实没有经历过什么大量用户并发的考验,结果公司做了一个大的推广,涌入了一批用户来抢标,共1000万的标的几乎都在10秒之内搞定,大概会有上万左右的用户会同时去抢标,平均每秒大概有千人左右的并发,满标控制这块没有经过大的并发测试,上来之后就被打垮了,导致得结果是什么呢,1000万的标的,有可能到一千零几万满标,也有可能会九百多万就满标,也就说要不就是多了一些,要不就是少了一些,就满标了。

这就会很尴尬,因为借款用户就借款一千万整,那么多出来的钱既不能给用户退回了,因为用户好不容易才抢上了,无端退了用户也闹;少了也是问题,用户借款一千万,少了几十万也不行,如果短得少了可以想办法找一些有钱的客户直接给买了,多了就必须重新放出来让用户投资,非常影响士气,这个问题困扰了我们有一段时间。

购买标的流程图,不知道大家是否能根据此图发现问题呢?

超募

为何会产生超募?在最早前的版本中没有使用乐观锁来控制,如果在最后购买的用户一单出现并发,就会出现超募,比如最后剩余30000份的购买份额,因为并发量特别大,可能同时会有十几个用户拿到了剩余30000份余额的可购买额度,有的买1000份、有的买上3000份、有的买上20000份都会驱动满标,所以最后导致了超募。

针对这个问题,主要是引入了memcached乐观锁的概念(底层主要是casgets两个命令),在发标的时候存入标的总份额,当用户购买的时候首先去锁定用户购买的份额,因为乐观锁的原因,如果同时有两个用户拿到份额的时候保证只有一个最后可以更新成功(锁定份额),(锁定份额)失败直接返回,这样就保证了在入口的时候就直接屏蔽了部分并发的请求。

少募
为何产生少募?少募是可能1000万的标的突然到980万就给满标了,这是因为在超募情况下我们完善了代码,用户一进来首先就是锁定购买份额,只有锁定购买份额才能进行下面的流程,如果锁定购买份额失败直接返回,这样虽然保证了在1000万份额在购买初期必须每一个用户只能锁定一份,但是在高并发的情况下,因为购买流程中有十几个分支,每一个分支失败就会退回锁定的份额,这样就会导致这样的现象,就是可能是并发一上来,马上就满标了,过了一会进度就回退回来了。

少募主要是因为分支失败回退导致的,一方面我们分析了容易导致回退热点,因为在用户抢标的时候会给用户实时的展示标的进度,在很早的版本中直接就是存入到一个标的进度表里面,并且采用了乐观锁,如果并发一高就频繁的更新失败导致回退,因此优化了标的进度这块,直接去掉了标的进度表,实时根据查询来展示标的进度(可以有延迟,有缓存);另一方面在回退份额的时候在次判断试下memcached的份额和标的的状态,如果份额不为零并且标的状态是满标,马上自动更新状态保证后续用户可以立即购买再次驱动满标。

做了以上的两种优化后,我们还遇到了其它的一些小问题,在不断的优化过程中,终于稳定下来;在后期版本中将考虑使用MQ队列或者redis队列来处理抢标更合理对用户也更公平一些。

黑客攻击

2015年应该是互联网行业受黑客攻击最多的一年吧,各互金公司都深受其害,其中我就记得网贷之家有一段时间被黑客攻击的太厉害,连续几天网站都无法打开。当然了我们也未能幸免,什么DDOS攻击、SQL注入、寻找系统漏洞等都几乎都经历过了,有的黑客还比较好,应该是出于善意或者展示自己,将漏洞放到乌云上面或者漏洞盒子里面让厂商来修复。但更多的是一些黑产完全就是威胁、敲诈想捞一笔钱,先看看下面这位吧:

这个家伙潜伏到我们公司的客户群里面,冒充我们的客户代表将头像和资料替换成一样,然后给群里所有的客服让他们发送我们内部的后台地址,想通过这种方式来寻找突破口,当然了这个算是里面的小菜鸟吧。

DDOS攻击

DDOS攻击我们也是遇到了很多次,确实也没有比较好办法,最后都是通过一些笨办法来尽量的避免,先说说我们的经历吧。有一次我正在敲代码,客服QQ又闪烁了起来,还没来得及打开查看信息,客服的经理电话就直接打了过来,我立刻就有一种不祥的预感,说官网打不开了,后台也登录不了。

挂了电话,我在本机进行了测试果然不行,立刻准备登录VPN查看服务器各项指标,结果登录不上去,马上上楼找运维经理,他也登录不上,刚准备给机房打电话的时候,机房来电话了,说我们的一个IP正经历着1G多的流量访问,问我们是否正在做什么活动,刚话没有说完就说流量已经到5G,不到一分钟之后流量已经到达18G之多。因为我们的机房和集团公用了一个宽带入口,结果陆续的集团上面反馈他们的网站、服务也都出现了问题,机房方面害怕引起更大的冲击,直接把我们官网对外的IP封掉,集团的其它业务也才慢慢都恢复了过来,我们也紧急的更换了外网IP,重新切换了域名解析才恢复。

事后我们根据apache分析了日志,流量来自N多个不同的IP地址根本无法应对,也正式因为这次攻击也才让我们领导重视了起来,将我们公司的机房网络层和公司集团彻底分离,这样的话不管那方受到大流量攻击都不会相互影响,我们也想了一些笨办法,因为上次我们更换了外网IP之后攻击也就停止了,那么我们认为肯定是针对我们外网来攻击的,所有我们就多准备了6个外网IP,当监控到对某一个外网进行攻击的时候马上切换到另一个外网地址,就这样跟他们玩,可以起到非常有限的一点作用,如果黑客真的想跟我们玩,这个办法就像是小孩子捉迷藏。

周年庆的DDOS攻击

还有一次我们正在做周年庆活动,突然有人在QQ群里面给我们客服说了一句,叫你们的技术负责人来找我,然后我们的网站就挂了,我还保留了当时的一个截图如下:

完了之后客服就来找我,然后按照往常的策略处理完之后,我根据客服给我的QQ号码加上了那个人,开口就来吓我,我依稀记当年的对话如下:

黑客:你是平台的技术负责人吗?
我:算是吧
黑客:你信不信我可以让你们官网在5秒之内挂掉?
我:…(沉默,还真害怕又把官网搞挂了)
黑客:你们的官网漏洞很大
我:如果有好的建议请您赐教
黑客:你们的服务器是不是什么防护软件都没有装?
我:…(继续沉默,这会在想不会是那个安全厂商来推广产品的吧,当然我们基础的防护肯定有)
黑客:我们有非常多的肉鸡,想攻击谁,几秒之内肯定搞定
我:…
黑客:我们已经给很多互联网金融行业做了渗透测试,花点钱帮买你们平安,保证以后不会在出事情
我:…
黑客:免费的策略也有很多,比如360、百度云的安全产品可以免费低档10G左右的流量

……(中间省略)

黑客:我说了这多,你们也是不是给包烟钱,表示表示。

……

后来也和领导进行了商议,坚决不能给他们钱,不能助涨这种嚣张气焰,实在不行就报警!

曝光一下当年使用的假QQ号,刚查了下变了个头像和描述,如下:

后来我一直在想为什么DDOS攻击总是喜欢根据外网IP来攻击呢,慢慢好像是理解了如果针对域名来攻击的话,那不就是攻击到域名商的服务器了吗,一般域名商比较强大,黑客不太搞的定,也确实没有必要。当然记的前一段时间,某著名域名服务商被攻击,导致国外twitter等著名的互联网公司访问不断到达半天以上,还是很严重的。但是对于我们这些小公司,倒不至于搞这么大的动作。

到底如何正确的防止DDOS攻击:

  • 第一种方案,隐藏服务器外网地址,服务器前端加CDN中转,免费的有百度云加速、360网站卫士、加速乐、安全宝等,如果资金充裕的话,可以购买高防的盾机,用于隐藏服务器真实IP,域名解析使用CDN的IP,所有解析的子域名都使用CDN的IP地址。此外,服务器上部署的其他域名也不能使用真实IP解析,全部都使用CDN来解析。
  • 第二种方案,买一些安全产品来进行流量清洗,主要是阿里云、腾讯云这种大厂商提供的一种服务。
  • 第三种方案,有很多的防火墙产品声称可以防止Ddos攻击,但是我个人使用感觉效果非常有限。

SQL注入

我们的官网使用的是PHP开发,因为框架比较老旧的原因,存在着一些SQL注入的点,我们发现了一些进行了修补,没想到还是被一些黑客找到了突破点,这块还是比较感谢这些黑客的在漏洞盒子上面提交了bug(如下图),最后我们根据提示进行了紧急修复,后来我们也在WAF防火墙配置了一些拦截SQL注入的策略,起到双保险的作用。

我一直在想为什么PHP一般比较容易出现SQL注入呢,而Java较少的暴漏出来SQL漏洞的情况,我估摸着有两方面的原因:第一,PHP一般会在前端使用的较多,受攻击的机会更多一些,Java一般做为后端服务攻击的可能性会比较少;第二,PHP框架较多而且很多早期的框架并没有特别考虑SQL注入的情况,Java大量普及了mybaits\hibernate这种orm框架,框架本身对常见的SQL注入有防止的功能,但不是说mybaits/hibernate框架就没有被sql注入的可能,大部分场景下是OK的。另外参数化查询可以有效的避免SQL注入。

通过一段时间的学习,我发黑客一般先使用工具对网站做整体的扫描类似Acunetix,在根据扫描出来的漏洞做个大概的分析,但是比较深入的漏洞都需要根据网站的业务在进行调整,比如sql注入会根据页面的查询使用sqlmap等工具来进一步的渗透。当然我对这方面还是外行,描述的可能不够清晰。

其它攻击

其它方面的攻击,主要是在业务方面,比如我们当初有一个很小的失误,有一个程序员在H5的小网页中将发送短信验证码返回了前端,最后被haker发现了,利用这个漏洞可以给任意的用户重置登录密码;短信攻击,现在的网站几乎都有发送短信或者短信验证码的功能,如果前端不做校验,haker会随便写一个for循环来发短信,一般系统的短信会进行全方位的防控,比如:1、前端加验证(字符验证码,有的是拖拽的动画);2、后端根据用户或者IP加限制,比如用户一分钟只可以发送一条短信,忘记密码的短信一天只能发送10条、一个IP地址限制每天只能发送100条短信等。

BUG

重复派息

15年的某一天看到一个新闻说是陆金所的一个用户发现自己银行里面突然多了很多钱,没过多久又被扣走了,然后收到陆金所那边的解释,说是给用户还本派息的时候程序出现了问题导致还本派息两次,当他们程序员发现了此问题后紧急进行了处理,用户当然闹了呀,就上了新闻,当然陆金所通道能力确实比较强可以直接从用户卡里面扣,当大家都兴致勃勃的谈论这个话题的时候,我却有一股淡淡的忧伤,为什么呢?因为这个错误我们也犯过,具体说就是我搞的,大家可不知道当时的心里压力有多大!

事情是这样子的,我们使用的第三方支付的扣款接口不是特别的稳定,于是我们前期就对接了两种不通的扣款接口,平时前端投资的时候走一个接口,后端派息或者还本的时候走另外的一个接口,在初期的时候扣款接口不稳定,因此在给用户跑批的时候经常会有个别用户失败,需要手动给失败的用户二次派息。做为一个有志向的程序员当然觉得这种方式是低效的,于是将程序改造了一下,在后端派息的时候当第一种扣款失败的时候,自动再次调用第二种扣款接口进行扣款,当时想着这种方式挺好的,各个环境测试也没有问题,上线之后监控过一段时间也运行稳定。

当我感觉一切都很美妙的时候,事故就来了,突然有一天客服反馈说有的用户说自己收到的利息感觉不对,好像是多了(真的是太感谢这个用户了),我登录后台看了一下派息的流水复核了一遍,果然利息被重复派了,一股冷水从头而下,把当天所有的用户派息记录和到期记录都进行了检查,影响了70多个用户,导致多派息了6万多元,幸亏只是派息出了问题,如果是到期的话金额会翻N倍,其中70多个人里面有几个进行了提现、几个进行了再次投资,绝大部分用户在我们发现的时候还不知情,金额也没有动。

怎么处理呢,当然不能直接就动用户的钱了,给每个重复派息的用户打电话,说明原因赠送小礼物,请求谅解后我们把重复派过的利息在次调回来。大部分用户进行了核对之后都还是比较配合的,但是肯定有一些用户不干了,当然也不能怪客户,都是我的原因,有的客户需要上门赔礼道歉,有的客户需要公司出具证明材料,我们的老板亲自给客户打了N个电话被客户骂了N遍,我心里压力可想而知,其中有一个客户特别难缠,各种威胁说既然到了我的账户里面肯定是我的,你们的失误不应该让他来承担,折腾了很久,还是不能怪客户。可能会说有的互联网公司经常出现这种问题后就送给客户了,哎,我们是小公司呀!这个噱头玩不起。

到底是什么原因呢,事后进行了复盘也给领导做了汇报,平时都是首先进行派息的定时任务,过一个小时之后进行到期的定时任务,当天的派息标的比较多,跑了一个半小时,就导致了派息和到期的两个定时任务同时进行,转账有了并发,第三方支付的接口不稳定给我们返回的失败,其实有的是成功的,就导致了我们进行了二次的扣款尝试引发了此问题。这个事情给我带来了非常大的教训,对于金融扣款的这种事情一定需要谨慎,哪怕付款引发报警之后在人工处理,也不能盲目重试可能引发雪崩效应。

杂七杂八

还有就是其它一些零碎的问题了,记的有一次对用户的登录过程进行优化,导致有一块判断少了一个括号结果用户在那两个小时内,只要输入账户,任意密码就可以登录了,幸好及时发现这个问题,正是这个问题才导致了我们正式确立了规范的上线流程,为以后的上线制度建定了基础。

还有一次我们在模拟用户投资一种标的时候,留了一个入口通过http就可以调用,测试也没有问题,有一天正好给领导演示呢,就在次用http请求的方式在浏览器执行了一下,前端就会看到自动投标的过程,因为生产的数据有点多,投标的过程有点长,我们为了加快进度,找了好几个人同时来执行这http请求,导致最后出现了问题,最后发现写测试脚本的这个同事根本就没有考虑并发的情况,才导致出现了问题。

也做了很多的活动,记得做一个网贷之家的一个活动的时候,活动上线比较紧张,我们团队曾经连续工作超过30个小时(一天一夜再一天),当天晚上我2点左右写完程序,测试从2两点测试到早上9点,最终确认没有任何问题,才进行投产。半夜公司没有暖气,我们实在冻的不行了,就在办公室跑步,从这头跑到那头,第二天上线之后,又害怕出现问题,监控了一天,确认没有任何问题,才到下午正常下班回家,那时候真是激情满满呀。

说到做活动肯定少了羊毛党,说哪一家互金公司没有遇到过羊毛党那很少见,而且现在的羊毛党规模简直逆天了,我们用户里面就有一个羊毛党在两三天之内邀请了六七千位用户,如果说邀请一个用户送1元,那这个用户就可以搞几千块一次,而且有很多专业的网站、QQ群、微信公共账号都是他们的聚集地,那天那个平台有活动门清,他们写的淘羊毛操作手册有时候比我们官网的帮助文档还清晰,所以做活动的时候要考虑特别周全,各种限制,有封定、有预案、讲诚信,只要是符合我们活动规则的坚决按照流程走。

还有一个有趣的事情,app推送,一次我在公交车上就看到xx盒子app弹出hhhhh的推送,这个事情我们也搞过,因为在调试的时候生产和测试就差了一个参数,有时候开发人员不注意就把生产参数部署到uat环境了,测试一发送就跑到生产了,这方面只能严格流产管理来防止了。

其实还很多问题:mongodb集群和mysql的同步出现的一些状况、后台大量数据查询下的sql优化、golang使用mapreduce碰到的问题… 限于篇幅这里就不一一清晰的描述了。

其实每次的出现问题都是对团队一次非常好的锻炼机会,通过发现问题,定位问题,解决问题,再次回过头来反思这些问题;重新梳理整个环节, 举一反三避免下次再次出现类似的问题。正是因为经历这些种种的困难、考验才让团队变的更强大更稳定,也更体现了流程的重要性,更是避免再次发生类似问题。

总结

古代对将军的要求是,心有万马奔腾而过,而面平静如湖水可照镜,在互联网行业对大牛的要求也同如此,特别是技术的负责人,在面对生产事故的时候,一定首先是安抚同事,静下下心来找到问题本质在去解决,而不是不断去施加压力催促解决,重压之下很多心里承受能力稍弱的队友,更加慌乱而不利于解决问题或者引发二次事故。

在看淘宝双十一视频中,有一段特别受到感触,在双十一初期,虽然技术团队做了很多的准备,但是在零点过后流量瞬间涌入,服务被打垮,部分用户投诉刷新不出网页,紧接着隔壁同事也都反馈网站打不开,在大家都在慌乱中,xx一拍桌子大喊一声,大家都别动,三分钟之后再说,过了几分钟之后服务慢慢部分恢复了正常。后来回忆说,当时虽然服务瘫痪,但是监控还是有部分得业务成功,说明系统并没有被压垮,而此时的任何操作都有可能引发更大的问题,从此之后此人一战成名,成为阿里大将。

互联网平台发展大抵都会经历三个阶段:

  • 1、上线初期,此阶段问题最为繁多,生产事故不断,系统快速迭代优化。有人说为什么不测试到完全没有问题在投产吗?说实话在互联网行业这个很难,第一小公司很难做到生产环境和测试环境一致,成本太高;时间紧迫,一般都是很短的时间内要求上线,上线之后在快速迭代。另外互联网本就是一个快速试错的行业,错过半年时间可能风口早过;
  • 2、发展期,此阶段主要业务模式已经得到验证,系统出现问题的频繁度较少,低级错误减少,但此时是用户量和交易量不断爆发的时候,对系统性能、高并发的要求又上来了,所以此时出现的问题大多都是性能的问题;
  • 3、成熟期,发展期过后系统相对比较平稳,用户量和交易量都已经慢慢稳定下来,生产问题越来越少,出现问题几乎都是细小的bug,这个阶段也是公司最忽略技术得阶段,恰好我们公司就处于这个阶段,在这个阶段就需要静下心里,组织架构升级,补齐在初期和发展起所欠的技术债务,做好公司在升下一个量级的技术准备。

所有的这些问题几乎都集中在14年底到15年初的这个阶段,15年后半年开始到现在平台慢慢稳定了下来,到现在几乎没有再出现过类似的问题,也因为几乎都是两年前的事情,有很多记的不是特别清楚了,写的比较粗糙望见谅。

百亿互金平台救火故事,首发于文章 – 伯乐在线

如何用 OllyDbg 的跟踪功能分析虚拟机保护

虚拟机保护已经是现代保护壳不可缺少的一环,虽然逆向方也发展出各种插件帮助分析,但只针对特定某款,通用性的方法却不多见。我总在想,既然虚拟机的结构是固定的,如果有一款工具能够记录指令流,那么按图索骥,也许能发展出一套通用的分析方法来。其实OD(OllyDbg)就有记录指令流的功能,叫跟踪(trace),也许是效果不好或者操作不便,用的人甚至知道的人不多。先介绍下怎么用。

OD的跟踪功能原理很简单,就是每一步都自动下单步断点,然后记录断下来的指令信息。这项功能涉及到几项设置,第一项是缓存大小,不难想象,跟踪得到的这一些列的指令记录是需要占地方存储的,占多大可以设置,位置在调试选项(Debugging options)->跟踪(Trace),如图1。

file0001

图1

第一项就是缓存的大小,内存允许的话,自然是多多益善,毕竟缓存越大,允许记录的信息越多。第二项是记录的内容,跟踪会自动记录地址模块等信息,此外可以选择是否记录指令、ESP和标志位的信息。设置位置紧接着缓存大小,见图2,可以按需勾选,本文只需要记录指令即可。最后一项是在调试(DEBUG)菜单中打开Trace。

file0002

图2

现在Trace已经设置完毕了,按下Ctrl+F12,查看Trace窗口,应该已经开始记录执行过的指令。否则请检查前述设置和操作是否正确。

那么,虚拟机保护要怎么入手分析呢?前面我提到,虚拟机是有固定结构的,既然要分析,那对应的找到这些结构应该就可以了。传统保护虚拟机的结构其实很简单,大致可以看成一只章鱼,有三个部分,分别是init(头),Dispatch(身)和Handle(触须),如图3:

file0003

图3

Init主要完成虚拟机初始化工作,例如申请内存填写初始值之类,每次进入虚拟机,这个“头部”通常只执行一次。Dispatch是虚拟机的主体,可以看成一个主循环,它是每一条虚拟机指令的开始之处,也是结束之处,负责读取虚拟机指令,进入具体handle解释等工作。Handle就是虚拟机的“指令”了,实际完成各项虚拟机指令的功能。 我曾写过一篇《基于虚拟机的软件保护技术》较为详细的介绍过虚拟机保护技术,对基本结构还不太熟的同学,此文会对上述概念有更详细的说明。

现在,我们就要在具体的软件中找这只“章鱼”了。以一个CrackMe为例,首先清理所有断点,打开Trace,Ctrl+F12跟踪步过运行,看到程序跑起来了,F12暂停,看Trace的窗口如下(图4):

file0004

图4

记录是从下往上看的,可以看出,在程序空间的最后一条支流,是00401534的一个call,调用了DialogBoxInDirectParamA,这是一个调出系统对话框的API,其中有一个参数DlgProc用来指明消息回调函数的位置,我们直接在反汇编窗口查看这个API,发现回调函数是0x401572(图5):

file0005

图5

0x401572处代码不长,有好几条Call,但大部分都是系统Call,只有一处调用了程序空间的函数,这个函数就是虚拟机的入口。到这里,我们对虚拟机的分析的工作才刚刚开始。

首先对虚拟机的入口下断,然后重新运行程序。目的是保证能够正确找到init。现在应该端在虚拟机的入口处,如下图:

file0006

图6

这是个非常简单的虚拟机,有经验的同学也许可以一眼就看出来图6包含了Init和Dispatch分别在哪里。当然也可以用Trace快速找出虚拟机的各个结构。现在去掉断点,打开Trace,Ctrl+F12跟踪步过,这时程序会跑起来,多点击几下按钮,目的是让主要分支得到更充分的执行(即增加获得执行的次数),然后F12暂停。回到Trace窗口,对着任意一行程序空间的指令点击右键,选择模块统计,结果如下图:

file0007

图7

统计是以代码段来划分的,第一栏显示的是这段代码在刚才的跟踪执行中执行的次数,第二栏显示了某个代码段的首地址。我们先找执行了一次的指令首地址。可以找到第5行的地址就是虚拟机的入口地址,点击在反汇编窗口跟随,可以看到这段代码是从0x00401060到0x004010B9,这就是init:

00401060 $ 55 push ebp
00401061 . 8BEC mov ebp, esp
00401063 . 81C4 D0FEFFFF add esp, -0x130
00401069 . C745 E4 00000>mov dword ptr [ebp-0x1C], 0x0
00401070 . C745 E8 00000>mov dword ptr [ebp-0x18], 0x0
00401077 . C745 F1 00000>mov dword ptr [ebp-0xF], 0x0
0040107E . C645 FD 00 mov byte ptr [ebp-0x3], 0x0
00401082 . C645 FE 00 mov byte ptr [ebp-0x2], 0x0
00401086 . C745 F5 00000>mov dword ptr [ebp-0xB], 0x0
0040108D . 8D85 D0FEFFFF lea eax, dword ptr [ebp-0x130]
00401093 . 8945 F1 mov dword ptr [ebp-0xF], eax
00401096 . 8B45 14 mov eax, dword ptr [ebp+0x14]
00401099 . 8945 E0 mov dword ptr [ebp-0x20], eax
0040109C . 8B45 08 mov eax, dword ptr [ebp+0x8]
0040109F . 8945 D0 mov dword ptr [ebp-0x30], eax
004010A2 . 8B45 0C mov eax, dword ptr [ebp+0xC]
004010A5 . 8945 D8 mov dword ptr [ebp-0x28], eax
004010A8 . C745 DC 00000>mov dword ptr [ebp-0x24], 0x0
004010AF . C745 D4 00000>mov dword ptr [ebp-0x2C], 0x0
004010B6 . 8B45 10 mov eax, dword ptr [ebp+0x10]
004010B9 . 8945 EC mov dword ptr [ebp-0x14], eax

接着找Dispatch,刚才说过,它既是虚拟机指令的开始,又是结束,它得到的执行次数一定也最多。可以看到第三行的0x004010B9,这个地址在虚拟机入口地址之后,执行次数最多,同样的办法可以看到这段代码的终止位置是0x004010D9:

004010BC > /FF45 EC inc dword ptr [ebp-0x14]
004010BF . |8B45 EC mov eax, dword ptr [ebp-0x14]
004010C2 . |8A00 mov al, byte ptr [eax]
004010C4 . |8845 F0 mov byte ptr [ebp-0x10], al
004010C7 . |B8 00204000 mov eax, 00402000
004010CC . |0FB65D F0 movzx ebx, byte ptr [ebp-0x10]
004010D0 . |C1E3 02 shl ebx, 0x2
004010D3 . |03C3 add eax, ebx
004010D5 . |FF20 jmp dword ptr [eax]

最后是找这次执行虚拟机用到的handle。这个不难,虚拟机入口地址之后的代码段除了init和dispatch,其它都是handle,所有执行过的handle都会在里面出现。当然了,某条handle的具体作用,以及没有执行过的handle,就只能靠人肉分析了。还有就是,就分析虚拟机保护来说,了解执行了哪些handle,以及哪些handle更常用,这些信息都是十分有用的。

如何用 OllyDbg 的跟踪功能分析虚拟机保护,首发于文章 – 伯乐在线

解决PKIX问题:unable to find valid certification path to requested target

话说前几天在测试服务器上遇到了这么个异常

javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path building failed: 
sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target

就是说找不着安全证书啥的等等烂码七糟的一大堆

接着就拜Google大神,发现一篇文章能被N个人转来转去的,关键文章还不怎么靠谱

后来找到了一个办法,幸运的是在测试环境一弄, 这个问题看上去就被解决了

我们要做的就是将所要访问的URL的安全认证证书导入到客户端

下面是获取安全证书的一种方法

/*
 * Copyright 2006 Sun Microsystems, Inc.  All Rights Reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 *   - Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 *
 *   - Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in the
 *     documentation and/or other materials provided with the distribution.
 *
 *   - Neither the name of Sun Microsystems nor the names of its
 *     contributors may be used to endorse or promote products derived
 *     from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
 * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.security.KeyStore;
import java.security.MessageDigest;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;

import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLException;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import javax.net.ssl.X509TrustManager;

public class InstallCert {
	public static void main(String[] args) throws Exception {
		String host;
		int port;
		char[] passphrase;
		if ((args.length == 1) || (args.length == 2)) {
			String[] c = args[0].split(":");
			host = c[0];
			port = (c.length == 1) ? 443 : Integer.parseInt(c[1]);
			String p = (args.length == 1) ? "changeit" : args[1];
			passphrase = p.toCharArray();
		} else {
			System.out.println("Usage: java InstallCert <host>[:port] [passphrase]");
			return;
		}

		File file = new File("jssecacerts");
		if (file.isFile() == false) {
			char SEP = File.separatorChar;
			File dir = new File(System.getProperty("java.home") + SEP + "lib" + SEP + "security");
			file = new File(dir, "jssecacerts");
			if (file.isFile() == false) {
				file = new File(dir, "cacerts");
			}
		}
		
		System.out.println("Loading KeyStore " + file + "...");
		InputStream in = new FileInputStream(file);
		KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType());
		ks.load(in, passphrase);
		in.close();

		SSLContext context = SSLContext.getInstance("TLS");
		TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
		tmf.init(ks);
		X509TrustManager defaultTrustManager = (X509TrustManager) tmf.getTrustManagers()[0];
		SavingTrustManager tm = new SavingTrustManager(defaultTrustManager);
		context.init(null, new TrustManager[]{tm}, null);
		SSLSocketFactory factory = context.getSocketFactory();

		System.out.println("Opening connection to " + host + ":" + port + "...");
		SSLSocket socket = (SSLSocket) factory.createSocket(host, port);
		socket.setSoTimeout(10000);
		try {
			System.out.println("Starting SSL handshake...");
			socket.startHandshake();
			socket.close();
			System.out.println();
			System.out.println("No errors, certificate is already trusted");
		} catch (SSLException e) {
			System.out.println();
			e.printStackTrace(System.out);
		}

		X509Certificate[] chain = tm.chain;
		if (chain == null) {
			System.out.println("Could not obtain server certificate chain");
			return;
		}

		BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));

		System.out.println();
		System.out.println("Server sent " + chain.length + " certificate(s):");
		System.out.println();
		MessageDigest sha1 = MessageDigest.getInstance("SHA1");
		MessageDigest md5 = MessageDigest.getInstance("MD5");
		for (int i = 0; i < chain.length; i++) {
			X509Certificate cert = chain[i];
			System.out.println(" " + (i + 1) + " Subject " + cert.getSubjectDN());
			System.out.println("   Issuer  " + cert.getIssuerDN());
			sha1.update(cert.getEncoded());
			System.out.println("   sha1    " + toHexString(sha1.digest()));
			md5.update(cert.getEncoded());
			System.out.println("   md5     " + toHexString(md5.digest()));
			System.out.println();
		}

		System.out.println("Enter certificate to add to trusted keystore or 'q' to quit: [1]");
		String line = reader.readLine().trim();
		int k;
		try {
			k = (line.length() == 0) ? 0 : Integer.parseInt(line) - 1;
		} catch (NumberFormatException e) {
			System.out.println("KeyStore not changed");
			return;
		}

		X509Certificate cert = chain[k];
		String alias = host + "-" + (k + 1);
		ks.setCertificateEntry(alias, cert);

		OutputStream out = new FileOutputStream("jssecacerts");
		ks.store(out, passphrase);
		out.close();

		System.out.println();
		System.out.println(cert);
		System.out.println();
		System.out.println("Added certificate to keystore 'jssecacerts' using alias '" + alias + "'");
	}

	
	private static final char[] HEXDIGITS = "0123456789abcdef".toCharArray();

	
	private static String toHexString(byte[] bytes) {
		StringBuilder sb = new StringBuilder(bytes.length * 3);
		for (int b : bytes) {
			b &= 0xff;
			sb.append(HEXDIGITS[b >> 4]);
			sb.append(HEXDIGITS[b & 15]);
			sb.append(' ');
		}
		return sb.toString();
	}

	
	private static class SavingTrustManager implements X509TrustManager {
		private final X509TrustManager tm;
		private X509Certificate[] chain;

		SavingTrustManager(X509TrustManager tm) {
			this.tm = tm;
		}

		public X509Certificate[] getAcceptedIssuers() {
			throw new UnsupportedOperationException();
		}

		public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
			throw new UnsupportedOperationException();
		}

		public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
			this.chain = chain;
			tm.checkServerTrusted(chain, authType);
		}
	}
}

编译InstallCert.java得到两个class文件,并执行InstallCert类

执行方式:java InstallCert hostname     eg:java InstallCert www.cebbank.com

接下来会看到下面的打印信息

java InstallCert www.cebbank.com
Loading KeyStore /usr/java/jdk1.6.0_31/jre/lib/security/cacerts...
Opening connection to www.cebbank.com:443...
Starting SSL handshake...

javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path building failed: 
sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
	at com.sun.net.ssl.internal.ssl.Alerts.getSSLException(Alerts.java:174)
	at com.sun.net.ssl.internal.ssl.SSLSocketImpl.fatal(SSLSocketImpl.java:1731)
	at com.sun.net.ssl.internal.ssl.Handshaker.fatalSE(Handshaker.java:241)
	at com.sun.net.ssl.internal.ssl.Handshaker.fatalSE(Handshaker.java:235)
	at com.sun.net.ssl.internal.ssl.ClientHandshaker.serverCertificate(ClientHandshaker.java:1206)
	at com.sun.net.ssl.internal.ssl.ClientHandshaker.processMessage(ClientHandshaker.java:136)
	at com.sun.net.ssl.internal.ssl.Handshaker.processLoop(Handshaker.java:593)
	at com.sun.net.ssl.internal.ssl.Handshaker.process_record(Handshaker.java:529)
	at com.sun.net.ssl.internal.ssl.SSLSocketImpl.readRecord(SSLSocketImpl.java:925)
	at com.sun.net.ssl.internal.ssl.SSLSocketImpl.performInitialHandshake(SSLSocketImpl.java:1170)
	at com.sun.net.ssl.internal.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:1197)
	at com.sun.net.ssl.internal.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:1181)
	at InstallCert.main(InstallCert.java:102)
Caused by: sun.security.validator.ValidatorException: PKIX path building failed: 
sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
	at sun.security.validator.PKIXValidator.doBuild(PKIXValidator.java:323)
	at sun.security.validator.PKIXValidator.engineValidate(PKIXValidator.java:217)
	at sun.security.validator.Validator.validate(Validator.java:218)
	at com.sun.net.ssl.internal.ssl.X509TrustManagerImpl.validate(X509TrustManagerImpl.java:126)
	at com.sun.net.ssl.internal.ssl.X509TrustManagerImpl.checkServerTrusted(X509TrustManagerImpl.java:209)
	at InstallCert$SavingTrustManager.checkServerTrusted(InstallCert.java:198)
	at com.sun.net.ssl.internal.ssl.ClientHandshaker.serverCertificate(ClientHandshaker.java:1198)
	... 8 more
Caused by: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
	at sun.security.provider.certpath.SunCertPathBuilder.engineBuild(SunCertPathBuilder.java:174)
	at java.security.cert.CertPathBuilder.build(CertPathBuilder.java:238)
	at sun.security.validator.PKIXValidator.doBuild(PKIXValidator.java:318)
	... 14 more

Server sent 1 certificate(s):

 1 Subject CN=www.cebbank.com, OU=Terms of use at www.verisign.com/rpa (c)05, OU=CEB, O="China Everbright Bank Co., Ltd", L=Beijing
   Issuer  CN=VeriSign Class 3 Extended Validation SSL CA, OU=Terms of use at https://www.verisign.com/rpa (c)06, OU=VeriSign Trust Network
   sha1    5b d2 85 6e b3 a4 2b 07 a2 13 47 b3 be 3e 1f c9 d3 ce 46 57 
   md5     05 d8 ae ee f1 d9 51 63 6d 2f 11 e0 ac d0 e7 d7 

Enter certificate to add to trusted keystore or 'q' to quit: [1]

然后输入 1 并回车,会看到类似下面的打印信息

[
[
  Version: V3
  Subject: CN=www.cebbank.com, OU=Terms of use at www.verisign.com/rpa (c)05, OU=CEB, O="China Everbright Bank Co., Ltd", L=Beijing
  Signature Algorithm: SHA1withRSA, OID = 1.2.840.113549.1.1.5

  Key:  Sun RSA public key, 2048 bits
  modulus: 30831246384548809540705228292841393062583732250993909916355780413722161557074568469738254573472093341710481517139910877
  public exponent: 65537
  Validity: [From: Mon Jul 02 08:00:00 CST 2012,
               To: Thu Jul 03 07:59:59 CST 2014]
  Issuer: CN=VeriSign Class 3 Extended Validation SSL CA, OU=Terms of use at https://www.verisign.com/rpa (c)06, OU=VeriSign Trust Network
  SerialNumber: [    5715ab25 6be8fa42 2fa28dd4 601bc732]

Certificate Extensions: 9
[1]: ObjectId: 1.3.6.1.5.5.7.1.1 Criticality=false
AuthorityInfoAccess [
  [
   accessMethod: 1.3.6.1.5.5.7.48.1
   accessLocation: URIName: http://ocsp.verisign.com, 
   accessMethod: 1.3.6.1.5.5.7.48.2
   accessLocation: URIName: http://EVSecure-aia.verisign.com/EVSecure2006.cer]
]

[2]: ObjectId: 2.5.29.17 Criticality=false
SubjectAlternativeName [
  DNSName: www.cebbank.com
]

[3]: ObjectId: 2.5.29.35 Criticality=false
AuthorityKeyIdentifier [
KeyIdentifier [
0000: FC 8A 50 BA 9E B9 25 5A   7B 55 85 4F 95 00 63 8F  ..P...%Z.U.O..c.
0010: E9 58 6B 43                                        .XkC
]

]

[4]: ObjectId: 2.5.29.32 Criticality=false
CertificatePolicies [
  [CertificatePolicyId: [2.16.840.1.113733.1.7.23.6]
[PolicyQualifierInfo: [
  qualifierID: 1.3.6.1.5.5.7.2.1
  qualifier: 0000: 16 1C 68 74 74 70 73 3A   2F 2F 77 77 77 2E 76 65  ..https://www.ve
0010: 72 69 73 69 67 6E 2E 63   6F 6D 2F 63 70 73        risign.com/cps

]]  ]
]

[5]: ObjectId: 2.5.29.19 Criticality=false
BasicConstraints:[
  CA:false
  PathLen: undefined
]

[6]: ObjectId: 1.3.6.1.5.5.7.1.12 Criticality=false
Extension unknown: DER encoded OCTET string =
0000: 04 62 30 60 A1 5E A0 5C   30 5A 30 58 30 56 16 09  .b0`.^.\0Z0X0V..
0010: 69 6D 61 67 65 2F 67 69   66 30 21 30 1F 30 07 06  image/gif0!0.0..
0020: 05 2B 0E 03 02 1A 04 14   4B 6B B9 28 96 06 0C BB  .+......Kk.(....
0030: D0 52 38 9B 29 AC 4B 07   8B 21 05 18 30 26 16 24  .R8.).K..!..0&.$
0040: 68 74 74 70 3A 2F 2F 6C   6F 67 6F 2E 76 65 72 69  http://logo.veri
0050: 73 69 67 6E 2E 63 6F 6D   2F 76 73 6C 6F 67 6F 31  sign.com/vslogo1
0060: 2E 67 69 66                                        .gif


[7]: ObjectId: 2.5.29.37 Criticality=false
ExtendedKeyUsages [
  serverAuth
  clientAuth
]

[8]: ObjectId: 2.5.29.31 Criticality=false
CRLDistributionPoints [
  [DistributionPoint:
     [URIName: http://EVSecure-crl.verisign.com/EVSecure2006.crl]
]]

[9]: ObjectId: 2.5.29.15 Criticality=false
KeyUsage [
  DigitalSignature
  Key_Encipherment
]

]
  Algorithm: [SHA1withRSA]
  Signature:
0000: 42 0A 89 BF 48 08 1E F4   98 F2 E5 DB 0D 83 EF 37  B...H..........7
0010: EC 27 6F 4D 81 69 C6 4A   4C 17 EC 57 F5 48 2A 14  .'oM.i.JL..W.H*.
0020: 3C 54 B2 C5 49 39 42 BA   EC 83 78 02 F9 96 6C 63  <T..I9B...x...lc
0030: 80 BC 60 61 BB 20 D1 AD   C3 D3 76 47 6F 0C 7B AC  ..`a. ....vGo...
0040: 76 B2 C7 2D B1 0A 7A 00   CA 40 38 86 FF 9F 12 F5  v..-..z..@8.....
0050: BE 5A E7 42 97 2F DF DE   0C 19 C5 F6 92 58 17 7A  .Z.B./.......X.z
0060: 9A 1D 2C 2C DA 8B 83 83   2D BE 07 58 56 36 92 E7  ..,,....-..XV6..
0070: B1 F8 A0 B5 00 F4 C3 30   D1 34 37 3D 94 75 28 04  .......0.47=.u(.
0080: A2 D8 C3 FE B1 E1 C2 2E   51 A8 6F D5 09 6D 49 DB  ........Q.o..mI.
0090: 2E 1D 4B F7 A8 06 30 B4   97 E7 C2 33 26 FD 6A DF  ..K...0....3&.j.
00A0: D6 B0 10 A1 F2 73 DD 5A   60 DE 51 5E EA 80 46 86  .....s.Z`.Q^..F.
00B0: 25 0B 53 FC C2 57 80 35   09 2D 31 55 28 35 EE 0F  %.S..W.5.-1U(5..
00C0: 62 50 4B 12 75 0B 02 9F   2F 0B D2 8A 0D 23 E3 C1  bPK.u.../....#..
00D0: 48 28 56 33 E1 DE 31 DD   72 78 15 96 EE 2B A5 1D  H(V3..1.rx...+..
00E0: 37 85 1B E5 88 53 80 88   02 6D 90 F3 E6 4A 74 AC  7....S...m...Jt.
00F0: D2 CA 0E 04 BC 46 A0 57   34 FA CF 9D E5 D7 0E 4B  .....F.W4......K

]

Added certificate to keystore 'jssecacerts' using alias 'www.cebbank.com-1'

同时我们会在当面目录下发现已经生成了一个名为jssecacerts的证书

再将名为jssecacerts的证书拷贝\\%JAVA_HONME%\\jre\\lib\\security\\目录中

最后重启下应用的服务,证书就会生效了。。

补充: 有人说生成证书后不用拷贝,直接代码里加句话就行,结果试了一下发现不管用

System.setProperty("javax.net.ssl.trustStore", "jssecacerts证书路径");

[转]openssl的证书格式转换

证书转换

PKCS 全称是 Public-Key Cryptography Standards ,是由 RSA 实验室与其它安全系统开发商为促进公钥密码的发展而制订的一系列标准,PKCS 目前共发布过 15 个标准。 常用的有:
PKCS#7 Cryptographic Message Syntax Standard
PKCS#10 Certification Request Standard
PKCS#12 Personal Information Exchange Syntax Standard
X.509是常见通用的证书格式。所有的证书都符合为Public Key Infrastructure (PKI) 制定的 ITU-T X509 国际标准。
PKCS#7 常用的后缀是: .P7B .P7C .SPC
PKCS#12 常用的后缀有: .P12 .PFX
X.509 DER 编码(ASCII)的后缀是: .DER .CER .CRT
X.509 PAM 编码(Base64)的后缀是: .PEM .CER .CRT
.cer/.crt是用于存放证书,它是2进制形式存放的,不含私钥。
.pem跟crt/cer的区别是它以Ascii来表示。
pfx/p12用于存放个人证书/私钥,他通常包含保护密码,2进制方式
p10是证书请求
p7r是CA对证书请求的回复,只用于导入
p7b以树状展示证书链(certificate chain),同时也支持单个证书,不含私钥。

1. CA证书

用openssl创建CA证书的RSA密钥(PEM格式):

openssl genrsa -des3 -out ca.key 1024

2. 创建CA证书有效期为一年

用openssl创建CA证书(PEM格式,假如有效期为一年):

openssl req -new -x509 -days 365 -key ca.key -out ca.crt -config openssl.cnf

openssl是可以生成DER格式的CA证书的,最好用IE将PEM格式的CA证书转换成DER格式的CA证书。

3. x509转换为pfx

openssl pkcs12 -export -out server.pfx -inkey server.key -in server.crt

4. PEM格式的ca.key转换为Microsoft可以识别的pvk格式

pvk -in ca.key -out ca.pvk -nocrypt -topvk

5. PKCS#12 到 PEM 的转换

openssl pkcs12 -nocerts -nodes -in cert.p12 -out private.pem  验证   openssl pkcs12 -clcerts -nokeys -in cert.p12 -out cert.pem

6. 从 PFX 格式文件中提取私钥格式文件 (.key)

openssl pkcs12 -in mycert.pfx -nocerts -nodes -out mycert.key

7. 转换 pem 到到 spc

   openssl crl2pkcs7 -nocrl -certfile venus.pem  -outform DER -out venus.spc

用 -outform -inform 指定 DER 还是 PAM 格式。例如:

openssl x509 -in Cert.pem -inform PEM -out cert.der -outform DER

8. PEM 到 PKCS#12 的转换

openssl pkcs12 -export -in Cert.pem -out Cert.p12 -inkey key.pem

IIS 证书

cd c:\openssl            set OPENSSL_CONF=openssl.cnf            openssl pkcs12 -export -out server.pfx -inkey server.key -in server.crt

server.key和server.crt文件是Apache的证书文件,生成的server.pfx用于导入IIS

9. How to Convert PFX Certificate to PEM Format for SOAP

$ openssl pkcs12 -in test.pfx -out client.pem  Enter Import Password:  MAC verified OK  Enter PEM pass phrase:  Verifying - Enter PEM pass phrase:

Docker 笔记二:安装Nginx

docker pull nginx

I’d like to install nginx as a load balance server.

http {
include       mime.types;
default_type  application/octet-stream;
#定义日志格式
#log_format  main  '$remote_addr - $remote_user [$time_local] $request '
#                  '"$status" $body_bytes_sent "$http_referer" '
#                  '"$http_user_agent" "$http_x_forwarded_for"';
#access_log  off;
access_log  logs/access.log;
client_header_timeout  3m;
client_body_timeout    3m;
send_timeout           3m;
client_header_buffer_size    1k;
large_client_header_buffers  4 4k;
sendfile        on;
tcp_nopush      on;
tcp_nodelay     on;
#keepalive_timeout  75 20;
include    gzip.conf;
upstream localhost {
#根据ip计算将请求分配各那个后端tomcat,许多人误认为可以解决session问题,其实并不能。
#同一机器在多网情况下,路由切换,ip可能不同
#ip_hash;
server localhost:18081;
server localhost:18080;
}
server {
listen       80;
server_name  localhost;
location / {
proxy_connect_timeout   3;
proxy_send_timeout      30;
proxy_read_timeout      30;
proxy_pass http://localhost;
}
}


useful link
https://hub.docker.com/_/nginx/

Docker 笔记一:安装Tomcat

 

docker pull tomcat

Run the default Tomcat server (CMD ["catalina.sh", "run"]):

$ docker run -it --rm tomcat:8.0

You can test it by visiting http://container-ip:8080 in a browser or, if you need access outside the host, on port 8888:

$ docker run -it --rm -p 8888:8080 tomcat:8.0

You can then go to http://localhost:8888 or http://host-ip:8888 in a browser.

The default Tomcat environment in the image for versions 7 and 8 is:

CATALINA_BASE:   /usr/local/tomcat
CATALINA_HOME:   /usr/local/tomcat
CATALINA_TMPDIR: /usr/local/tomcat/temp
JRE_HOME:        /usr
CLASSPATH:       /usr/local/tomcat/bin/bootstrap.jar:/usr/local/tomcat/bin/tomcat-juli.jar

The default Tomcat environment in the image for version 6 is:

CATALINA_BASE:   /usr/local/tomcat
CATALINA_HOME:   /usr/local/tomcat
CATALINA_TMPDIR: /usr/local/tomcat/temp
JRE_HOME:        /usr
CLASSPATH:       /usr/local/tomcat/bin/bootstrap.jar

The configuration files are available in /usr/local/tomcat/conf/. By default, no user is included in the “manager-gui” role required to operate the “/manager/html” web application. If you wish to use this app, you must define such a user in tomcat-users.xml.

 

If you want to map local file into the docker image, you can type following command with the parameter -v

docker run -p 8080:8080 -v /home/tomcat/webapps:/usr/local/tomcat/webapps -v /home/tomcat/conf:/usr/local/tomcat/conf -v /home/tomcat/work:/usr/local/tomcat/work tomcat

 

PHP中“==”运算符的安全问题

前言

PHP是一种通用的开源脚本语言,它的语法混合了C,Java,以及Perl等优秀语言的语法。除此之外,它还提供了大量的函数库可供开发人员使用。但是,如果使用不当,PHP也会给应用程序带来非常大的安全风险。

在这篇文章中,我们将会对PHP应用程序中经常会出现的一些问题进行深入地分析,尤其是当我们使用“==”(比较运算符)来进行字符串比较时,可能会出现的一些安全问题。虽然近期有很多文章都围绕着这一话题进行过一些探讨,但我决定从“黑盒测试”的角度出发,讨论一下如何利用这个问题来对目标进行渗透和攻击。首先,我会对引起这个问题的根本原因进行分析,以便我们能够更加深入地理解其工作机制,这样才可以保证我们能够尽可能地避免这种安全问题的发生。

问题的描述


在2011年,PHP官方漏洞追踪系统发现,当字符串与数字在进行比较的时候,程序会出现某些非常奇怪的现象。从安全的角度出发,这个问题实际上并不能算是一个安全问题。比如说,你可以看到下面这段代码:

php > var_dump('0xff' == '255');
bool(true)

实际上,当使用类似“==”这样的比较运算符进行操作时,就会出现这样的情况。上面这个例子中出现的问题不能算是一个漏洞,因为它是PHP所提供的一种名为“类型转换”的功能。从本质上来分析,当我们使用特定的比较运算符(例如== , !=, <>)来进行操作时,PHP首先会尝试去确定参与比较的数据类型。但是这样的一种类型转换机制将有可能导致计算结果与我们预期的结果有较大出入,而且也会带来非常严重的安全问题。安全研究专家在该问题的完整披露报告中写到:这种类型转化机制将有可能导致权限提升,甚至还会使程序的密码验证过程变得不安全。

Gynvael写过一篇关于这一话题的经典文章,PHP等号运算符“==”所涵盖的数据类型非常广泛,我们给大家提供了一个较为完整的比较参考列表,并给出了一些示例,具体内容如下所示:

"1.00000000000000001" == "0.1e1" → bool(true)
"+1"    == "0.1e1" → bool(true)
"1e0"   == "0.1e1" → bool(true)
"-0e10" == "0" → bool(true)
"1000"  == "0x3e8" → bool(true)
"1234"  == "  	1234" → bool(true)

正如你所看到的,当我们使用“==”来比较这些数字字符串时,参与比较的就是字符串中数字的实际大小,从安全的角度出发,这就是一个非常有趣的问题了。在这种情况下,你可以使用科学计数法来表示一个数字,并将其放在一个字符串中,PHP将会自动把它作为一个数字类型来处理。我们之所以会得到这样的输出类型,是因为PHP使用了一种哈希算法(通常使用十六进制数值表示)来进行处理。比如说,如果一个数字为0,那么在进行松散比较的过程中,PHP会自动对其类型进行转换,但其值永远为0。对于一个给定的散列算法而言,密码就有可能会变成可以被替换的了。比如说,当密码的哈希值被转换成使用科学计数法来表示的数字时,将有可能正好与其他的密码哈希相匹配。这样一来,即使是一个完全不同的密码,也有可能可以通过系统的验证。但有趣的是,当某些采用科学计数法表示的数字在进行比较的时候,结果可能会让你意想不到:

"18372e0" == "492372e0000" → bool(false)
"2e6" == "8e2" → bool(false)

从“黑盒测试”的角度出发来考虑这个问题

从静态分析的角度来看,这些安全问题就显得有些普通了。但如果我们从黑盒的角度来看待这些问题,我们能够得到什么样的启发呢?对于应用程序中的任何用户账号而言,如果应用程序使用了当前最为流行的哈希散列算法(例如SHA1和MD5)来对密码进行处理,而你在对密码哈希进行验证的时候使用了PHP的松散比较,那么此时就有可能出现安全问题。我们现在可以考虑进行一次典型的渗透测试,你可以创建一个普通的账号,将密码设置成哈希值类似的其中一个密码,然后使用其他的密码进行登录操作。很明显,系统的安全性完全取决于你所使用的散列算法。所以,我们假设你没有在散列算法中使用“Salt”值,那么你至少得使用两种不同的散列算法来对密码进行处理。

现在,在我们去对这些密码组合进行研究之前,我们还应该考虑到一点——即密码的要求。因为我们在对这些密码和散列算法进行分析之前,首先得确保我们所设置的初始密码复合了密码复杂度的要求,否则我们的分析和研究将会没有任何的意义。因此,我们得确保我们的密码长度至少为八个字符,密码中包含有大小写字母,数字,以及至少一个特殊字符:具体如下所示:

import random
import hashlib
import re
import string
import sys
prof = re.compile("^0+ed*$") # you can also consider: re.compile("^d*e0+$")
prefix = string.lower(sys.argv[1])+'!'+string.upper(sys.argv[1])+"%s"
num=0
while True:
    num+=1
    b = hashlib.sha256(prefix % num).hexdigest()
    if (b[0]=='0' and prof.match(b)):
        print(prefix+str(num),b)

为此,我专门编写了一个Python脚本,虽然我没有竭尽全力去优化这个脚本的性能,但是在PyPy编译器的帮助下,这个精心编写的脚本可以在我的AMD FX8350所有可用的CPU核心中稳定运行。除此之外,我还使用到了hashlib库中的散列函数,而且为了避免遇到Python GIL的进程同步问题,我还生成了独立的进程来对密码数据进行处理。不仅如此,我还使用了非常复杂的技术来为每一个密码生成不同的前缀,正如上面这段代码所示。

分析结果


在经过了一个多小时的分析之后,我得到了四个密码的SHA1值。令我感到惊讶的是,得到四个密码的MD5值所需的时间竟然更短。

密码的计算结果十分相似,具体如下所示:

MD5
word	hash
c!C123449477	0e557632468345060543073989263828
d!D206687225	0e749889617409631915178731435707
e!E160399390	0e680455198929448171766997030242
f!F24413812	0e666889174135968272493873755352

SHA-1
word	hash
aA1537368460!	0e98690883042693380036268365370177656718
aA3539920368!	0e80128521090954700858853090442722395969
cC6593433400!	0e65495612893131014886449602893230063369
fF3560631665!	0e49205137236861153120561516430463247071

你可以随意选取两个密码来进行对比,对比的演示结果如下:

php > var_dump(md5('c!C123449477') == md5('d!D206687225'));
bool(true)
php > var_dump(sha1('aA1537368460!') == sha1('fF3560631665!'));
bool(true)

如果你无法得到如上图所示的计算结果,那么你应该感到幸运。你可以尝试将用户名和密码捆绑在一起,然后使用带“salt”值的散列算法来进行计算。你只需要修改一小部分代码即可实现,点击“这里”获取修改后的脚本。

解决方案


PHP给我们提供了一个解决方案,如果你想要对比哈希值,你应该使用password_verify()或hash_equals()这两个函数。它们会对数据进行严格比较,并排除一些其他的干扰因素。但是请你注意,hash_equals()函数也可以用于字符串的比较。

分析结论


虽然我们的分析步骤执行起来有些过于复杂,但是从黑盒测试的角度出发,我们所描述的方法也许可以给大家提供一些有价值的信息。如果某个应用程序中的密码采用了这样的一种验证机制,那么它所带来的安全问题将会超出PHP数据类型转换本身所存在的问题。

问题远不止于此


这个问题给我们带来的影响远远不止于此。攻击者可以将这些密码添加到字典文件中,然后对应用程序中的所有用户进行暴力破解攻击。而且,如果应用程序的密码恢复机制中存在不安全的因素,攻击者还有可能对目标账号进行不限次数的攻击,直到攻击成功为止。

源代码


点击“这里”获取Python源码。

PHP中“==”运算符的安全问题,首发于文章 – 伯乐在线

NSA 是如何破解大量加密信息的?

近几年有谣言说 NSA 可以解密已加密网络中的大部分数据。在 2012 年,James Bamford 发布了一篇 文章,引用了匿名的 NSA 前成员的说法,他证实“NSA已经取得了计算力的突破性进展,他们有能力破解当前已公开的加密算法”。斯诺登公布的文档也暗指像一些额外的线索和文档显示,NSA已经建立了大量的基础设备来拦截和解密 VPN 流量,它至少能解密一些 NSA 想要知道的 HTTPS 和 SSH 连接。

然而,对于这些超前的工作是如何运作的,技术小组是如何判断后门或逻辑缺陷的等问题,这些文档都没有说明。2015 年 10 月 13 日,在 ACM CCS 会议的安全研究分会场,我们和 12 个同行发布了一篇技术谜题揭秘报告

Diffie-Hellman 秘钥交换算法,是我们推荐使用的防御监听的算法。它是现代密码学的基石,VPN、HTTPS 网站、邮件和许多其他的协议都用在 Diffie-Hellman。我们的报告显示,通过数理理论的交汇和糟糕的协议实现,许多现实世界里的 Diffie-Hellman 用户在面对国家级攻击时都是脆弱的。这个事实稍微有点讽刺。

如果读者里有技术狂人,就会发现一些问题:如果客户端和服务端要使用 Diffie-Hellman,它们首先需要协定一个特殊形式的大素数。为什么不能每个人都用同样的素数?似乎不用问什么原因。实际上,许多应用倾向于使用标准的或者硬编码的素数。但算法从数学家到实现者之间,会丢失一个非常重要的细节:攻击者可以执行一个单点大规模计算来破解大素数,使用该大素数的个人连接很容易丢失。

你要问计算量有多大?大概是整个二战时期破解英格玛的计算规模(相较于那时候的计算量而言)。甚至连估算难度都很棘手,因为这个算法涉及到的内容太复杂,但我们有论文给出了一些保守的估算。对于最常用的 Diffie-Hellman(1024位),花了几亿美元来建造专门的破译机器,这能够每年破解一个 Diffie-Hellman 素数。

对情报机构而言,这值得么?一旦少量的大素数被滥用,那他们可解密的连接,将会很可观。破译一个通用的 1024 位大素数,将让NSA 能解密全球三分之二的 VPN 和四分之一的 SSH 服务器。破解两个1024 位大素数,将能够被动监听上百万个 HTTPS 网站中的20%。总而言之,在大规模计算上的一次投资,使它能够监听数以兆计的加密连接。

NSA 能够负担这些投资。在斯诺登泄露的部分文件里,有一份 2013 年的黑色预算的申请,文件显示 NSA 已经将“发展突破性的密码分析能力以打击敌对方密码系统和利用网络流量”提上了日程。它显示了 NSA 的预算大约一年有100 亿美元,其中超过 10 亿被用于计算网络技术开发和几个子项目中。

基于我们已有的证据来说,我们无法确切证明 NSA 已经完成了。然而,我们提出的 Diffie-Hellman 攻击方式,相较于其他可能性,更能解释在当前已知的技术水平下如何获得大规模破解的能力。例如,斯诺登发布的文档显示了 NSA 的 VPN解密设施 通过拦截加密连接,并使用超级计算机计算已知数据来得到密钥。这套系统的设计不遗余力的收集必要的特殊数据,为攻击 Diffie-Hellman 做准备。但它不适合 AES 或其他对称加密算法。这份文档清晰是显示了 NSA 使用其他类似软硬件“移植”的技术来破解特殊目标的加密算法,但这些并没有解释 NSA 大规模被动监听 VPN 信道的能力。

一旦 Diffie-Hellman 这种不牢靠的使用方式在标准和实现中被滥用,这个问题将会影响到许多年后,甚至给现有的安全推荐和我们新的调查带来影响。与此同时,其他强大的政府也有可能实现类似的攻击,假设他们还没那么做的话。

我们的调查阐明了 NSA 两项任务间的矛盾,收集情报和保卫美国网络安全。假如我们的猜想是正确的,情报机构已经掌控了弱 Diffie-Hellman,帮忙修正这个问题仅是小小的一步而已。在防守方,NSA 推荐大家应该向椭圆曲线密码学过渡,椭圆曲线密码学没有已知的漏洞,但这一推荐在没有明确推论或证明的情况下,就被大多数人忽略了。这个问题太复杂,因为安全通过采取 NSA 推荐,表面上价值不高。看看这这个显而易见的影响:后门密码标准

这种状况让人人的安全都处于危险中。这种规模的漏洞是不加区别地影响着大家的安全,包括美国公民和公司,但我们希望,能有一种更清晰的技术,来了解 ZF 监控背后的密码机制,为大家能有更好的安全。

更多详情,请见我们的研究论文:《Imperfect Forward Secrecy: How Diffie-Hellman Fails in Practice》(更新:我们这篇论文获得了CCS 2015 最佳论文奖!)

J. Alex Halderman 是密歇根大学计算机科学与工程的助理教授,密歇根计算机安全和社会中心的主管。

Nadia Heninger 是宾夕法尼亚大学计算机与信息科学系助理教授。

NSA 是如何破解大量加密信息的?,首发于文章 – 伯乐在线

10 分钟服务器安全设置,Ubuntu安全设置入门

Bryan Kennedy 的《5分钟服务器安全设置》很好地介绍了对多数服务器攻击的防御对策。我们对他的方法做了一些修改,记录下来,作为推广我们的流程和最佳实践的一部分。还增加一些额外的解释,年轻的工程师们应该可以从中受益。

我每天上午检查 logwatch 邮件的时候,看到那些成百上千的登录尝试,几乎没有成功的,完全就像是一种享受。(有很多非常令人无语,比如用 1234 做 root 密码反反复复的登录)。这篇入门文章适用于 Debian/Ubuntu服务器,这是我们最喜欢的服务器发行版,通常我们这些服务器只是作为 docker 容器的宿主机,不过原理仍然适用。下一次我们再深入讲解如何保护专门用作 docker 宿主机的服务器。

在大规模系统上,当然最好是用 AnsibleShipyard 这类工具完全自动化配置。不过偶尔的,你只是搭建一台独立服务器或为一个 Ansible recipe 准备基准设置,这就是本文要涵盖的内容。

声明:本文仅仅是入门和基础,你需要根据自己的需求来扩展。

重要的事先说

我们还没给 root 设密码呢,密码要随机、要复杂。我们用一种密码管理软件的密码生成器,调至最复杂设定。PW 管理软件将密码加密保存,并且由一个很长的主密码保护着。这里提供了多项冗余措施——长、复杂、随机的密码 + 加密保存/另一个长密码保护。不管你是用 PW 管理软件或其他手段,要妥善保管密码并且要加密保存。你只有在忘了 sudo 密码时才会用到这个密码。

# passwd

* 注:在 HNRedit 上有很多关于 root 密码的讨论,值得一读。

下一步就需要更新软件库并升级系统、应用最新的补丁。我们后面有个章节专门介绍如何自动安装安全更新。

apt-get update
apt-get upgrade

添加用户

永远不要以 root 登录服务器。在用户名方面我们跟 Bryan 遵循类似的惯例,但是你可以根据自己的喜好使用任何惯例。在小团队里,大家共用一个用户名不是问题;但是队伍再大一些的话,最好是给不同的用户设定不同的权限级别,只给精心挑选的少数用户赋予 sudo 权限。

useradd deploy
mkdir /home/deploy
mkdir /home/deploy/.ssh
chmod 700 /home/deploy/.ssh

给用户 deploy 配置你喜欢的 shell,这里我们用 bash:

usermod -s /bin/bash deploy

记住, chmod 700 表示“所有者可以读、写、执行”。我们现在还在 root 帐号下,马上就要将此文件夹设为属于 deploy 用户和 deploy组,只有这个用户对  .ssh 文件夹有完全控制权。

使用 ssh key 验证

我们倾向于避免用密码登录服务器。Bryan 当初那份指南发表后,关于这个话题有很多讨论,但我愿意加入这个阵营。下面是一些要点:

  1. ssh keys 比密码更好,因为它包含并要求更多信息;
  2. 密码可以被暴力破解,猜测一个公钥基本上不可能,可以认为是完美的安全措施;
  3. 电脑丢失了怎么办?是的,你的私钥到了别人手里。不过废除 ssh-key 很容易,只需要将公钥从 authorized_keys 里删除。你还应该给你的私钥加上安全的、足够长的密码短语。见下一条;
  4. 以上这些都好用的前提是:必须用安全的、足够长的密码短语来保护你的私钥。重要的事情至少说两遍。

好了,我们把服务器密码验证抛到脑后吧。把你电脑里的 id_rsa.pub 的内容复制到服务器的 authorized keys 文件里。

vim /home/deploy/.ssh/authorized_keys

我们基于 Linux 安全性的“最少特权原则”来设置正确的权限:

chmod 400 /home/deploy/.ssh/authorized_keys
chown deploy:deploy /home/deploy -R

chmod 400 将文件权限设为“仅所有者可读”。第二个命令 chown ,使用户 deploy 和 组 depoly 成为它们家目录的所有者。我们先前提到过这个,记得吗?在设这这个目录权限为“所有者可以读、写、执行”的时候。

我们顺利测试 deploy 用户并设置 sudo 之后,再回来禁止 root 登录和强制实行仅密钥认证。

测试 deploy 用户、设置 sudo

我们来测试一下用 deploy登录,同时不要关闭 root 帐号的 ssh 连接以备万一。如果没有问题,我们就用 root 用户给 deploy 设置密码,因为我们没会禁用密码登录,这个密码是 sudo 的时候用的。还是用一个密码管理器来生成一个复杂随机的密码、加密保存、在团队中共享(同步加密的 pw 文件)。

passwd deploy

设置 sudo 很简单,用这个命令打开 sudo 文件:

visudo

如下,在 root 用户下面添加 %sudo 组(在sudo文件里,用户名没有前缀,组名需要用 %前缀)。确保用 # 注释掉其他任何用户和组,新安装的系统一般不会有,但还是确认一下好。

root    ALL=(ALL) ALL
%sudo   ALL=(ALL:ALL) ALL

然后把 deploy 用户添加到 sudo 组。

usermod -aG sudo deploy

deploy 现在具备了sudo 权限,一般来说你需要退出并重新登录来使用这个权限,不过有个避免这个步骤的小伎俩:

exec su -l deploy

这个命令为 deploy 用户启动了一个新的交互 shell,并带有了 sudo 组的新权限。你需要输入 deploy 的密码,但是感觉上比注销再登录回来能更快些。「译者注:这个感觉我没有 😉 」

编辑注释:
感谢 Reddit 上的 ackackacksyn 指出来,不应该直接把用户加到 sudoer (即sudo file),而是要加入到具备 sudo 权限的组,文章已经按此修改。
感谢 /r/netsec 的 FredFS456 指出来,需要注销并重新登录才能使新加入的组权限生效,文章已经按此修改。

强制 ssh 密钥登录

服务器的 ssh 设置在这里:

vim /etc/ssh/sshd_config

你需要修改或添加下面这几行,我觉得它们相当的简单明了。你可以填上你用来登录的 IP ,我们有个用 OpenVPN 搭建的公司 VPN 服务器,带加密验证的,所以为了连接到服务器,你必须首先连上 VPN 。

PermitRootLogin no
PasswordAuthentication no
AllowUsers deploy@你的VPN或固定IP
AddressFamily inet

重新启动 ssh 服务来让这些规则生效,你很可能需要重新连接(这回就用 deploy 用户吧!)

service ssh restart

* 注:感谢 HN 上的 raimue 和 mwpmaybe 指出来,我们在后面要安装的 fail2ban现在还不支持 IPv6,所以我在 ssh_config 里添加了 AddressFamily inet 这一行,意思是只允许 IPv4。

设置防火墙

这里有两个阵营,一个直接使用 iptables,另一个使用一款叫做 ufw 的简便接口(在 iptables 之上的一个层,目的是简化操作)。对安全来说,简单化通常是更好的,DigitalOcean ufw 真心不错而且绝不只是个基本工具。

Ubuntu 上 ufw 是默认安装的,在 Debian 上只需要执行一下 sudo apt-get install ufw 。

默认情况下 ufw 应该拒绝一切入站连接、允许全部出站连接,但是这行不通(那样的话你怎么连进来呢?)。我们来一步步地显式允许我们视为OK的连接。

首先我们需要保证支持 IPV6 ,只需打开配置文件:

vim /etc/default/ufw

允许 IPv6

IPV6=yes

其它要打开的端口我们用 ufw 工具在命令行添加,这非常方便:

sudo ufw allow from 你的IP to any port 22
sudo ufw allow 80
sudo ufw allow 443
sudo ufw disable
sudo ufw enable

第一个命令是个冗余措施,保证只有从我们的 IP 才能连接 ssh标准端口,第二个和第三个用来打开 http 和 https 。

注:感谢 chrisfosterelli 指出来,如果你要设置第一条规则(你应该),要确保你有个固定 IP 或安全的 VPN,动态 IP 会在某天把你锁到服务器外面。[ 译者注:这是废话,但比较贴心,呵呵 ]

自动应用安全更新

我喜欢这个,安全更新并不完美,但及时安装补丁还是更好些。

apt-get install unattended-upgrades
vim /etc/apt/apt.conf.d/10periodic

照下面的例子编辑文件10periodic :

APT::Periodic::Update-Package-Lists "1";
APT::Periodic::Download-Upgradeable-Packages "1";
APT::Periodic::AutocleanInterval "7";
APT::Periodic::Unattended-Upgrade "1";

我基本同意 Bryan 的说法,禁用普通更新,只启用安全更新。因为你不希望一个应用在你毫无察觉的时候 down 掉,只是因为某个软件包被更新了。相反,安全更新很少在应用程序方面产生噩梦般的依赖问题。

vim /etc/apt/apt.conf.d/50unattended-upgrades

这样修改文件:

Unattended-Upgrade::Allowed-Origins {
    "Ubuntu lucid-security";
    //"Ubuntu lucid-updates";
};

到这里你已经准备就绪了。

fail2ban

1087378

Fail2ban 软件包主动拦阻可疑行为。根据官方 wiki,Fail2ban 扫描日志文件(比如:/var/log/apache/error_log ),通过在 iptables 添加规则来阻止那些有恶意迹象(多次密码错误,探寻漏洞等…)的 IP。

Fail2Ban 自带多种协议过滤器(HTTPS, STMP, SSH等),它还与 ApacheNginx等众多服务集成,提供一定程度的 DDoS 或暴力破解防护。不过使用的时候要小心,因为封堵 DDoS 攻击的源地址,可能也会阻止正常用户一段时间。它带有大量的配置选项,包括集成SendMail,当某个 IP 被封堵时发出邮件通知。你尽可以点击上述这些链接,看看哪些选项对你的胃口。

我们来安装这个软件,作为开始,只用默认配置来保护 ssh:

apt-get install fail2ban

双重认证

构建任何敏感系统时,双重认证(2 Factor Authentication, 2FA)不是额外的要求,而是必须的。理论上,如果你强制启用 2FA (加上以上所有措施),那么为了登录你的服务器(应用程序漏洞除外),攻击者必须:

  1. 获得你的证书来登录VPN
  2. 获得你的电脑、拿到你的私钥
  3. 获得你私钥的密码短语
  4. 获得你的电话以遍完成双重认证

这就需要跨越不少的障碍了,即使都得手了,攻击者还是需要 deploy 用户的密码才能通过 sudo 获得 root 权限,而密码可是用 AES 加密保存的。

安装这个软件包

apt-get install libpam-google-authenticator

运行这个命令来设置,根据提示配置:

su deploy
google-authenticator

2FA非常简单,而且增加了很强的一层安全性。

Logwatch

这不仅仅是个简单的小快乐和监控工具,用来事后查看服务器发生了什么。Logwatch 监控你的日志文件,经过配置后,它可以每天发送格式精美的报告邮件。邮件内容看起来非常有喜感,你会惊讶每天有那么多要进入你服务器的企图。我装这个软件包没有别的原因,就是给团队显示一下好的安全性配置有多么重要。

DigitalOcean 有一篇关于 logwatch 安装配置的详细文档,但是如果时间限制在 10 分钟以内,我们只会完成安装,然后配置 cron 计划任务来每天发送邮件。

apt-get install logwatch

添加 cron 计划任务

vim /etc/cron.daily/00logwatch

把这一行加进去:

/usr/sbin/logwatch --output mail --mailto you@example.com --detail high

完工!

好啦,完成这一切之后,你的主要漏洞关注点将是你的应用程序和服务,这完全是另一盘菜了。

10 分钟服务器安全设置,Ubuntu安全设置入门,首发于文章 – 伯乐在线

用了ZAP,你的软件就安全了吗?

近来几年,很多大型网站频发安全事件,比如2011年众所周知的CSDN密码泄露事件,2014年eBay也因受到攻击造成用户密码和个人数据泄露,Web安全逐渐进入人们的视野,安全测试也逐渐成为了软件测试中非常重要的一部分。

提到安全测试,很多人应该都会想到ZAP,ZAP(Zed Attack Proxy)是OWASP提供的一款免费Web安全漏洞扫描工具,用户可以通过设置浏览器和ZAP的Proxy,在开发过程或测试过程中自动检测Web应用程序是否存在安全漏洞,ZAP还会提供扫描结果的风险等级,修复建议以及一些参考文档,下图就是ZAP扫描后的一个用户界面。

ZAP

除了自动扫描功能,ZAP也支持手动安全测试,通过在数据发送到服务器之前手动修改请求信息来测试Web应用程序是否存在安全漏洞。

很多人会有这样的疑惑,ZAP能否扫描出所有的安全漏洞?ZAP扫描出的安全漏洞和安全等级是否可靠?用了ZAP,软件是不是就安全了?

ZAP局限性

首先虽然ZAP的自动扫描功能非常强大,但对于OWASP Top 10中的某些项或者Top 10以外的一些安全漏洞,想要通过ZAP扫描检测出来是非常困难的,比如Top 10中的A5 “Security Misconfiguration” 就很难通过扫描检测出来,所以ZAP所能扫描到的安全漏洞只是OWASP Top 10的一个子集。

其次,ZAP扫描后的安全报告,还是需要结合实际项目进行分析才能确定其有效性和安全等级,比如我们在项目中曾经用ZAP扫描出了 “Cookie set without HttpOnly flag” 的安全隐患,推荐的解决方案是将所有的Cookie都设置成HttpOnly,但现实的情况是项目中前端AJAX需要携带这个Cookie来给后端发送请求,如果设置了这个flag,那么我们正常的请求也会失败,所以这个漏洞对我们来说就是无效的,或者说我们是不应该修复的。

另外因为Web应用程序往往比较复杂,会有很多组成部分,比如前端、服务器端、数据库等,各层分别使用了不同的框架、语言,而且经常会引入一些第三方的库、框架或者模块,每一个环节都有可能存在安全隐患,所以仅仅依赖ZAP是不够的,比如针对第三方组件的安全测试,就可以借助OWASP提供的另外一款工具Dependency Check。

通过分析实际项目中发现的安全问题,我们发现缺陷大体上如下分布:

Defects分析

安全问题可以归为两大类:

  • 一类是比较有共性的,即可以抛开业务上下文,软件之间共通的一些问题,常见的比较严重的安全隐患,如XSS攻击,CSRF攻击等,ZAP可以帮我们扫描出大多数的问题。
  • 另一类是针对业务需求的,比如非授权的账户是否不能访问/修改他们没有权限的信息等等,对于这一类问题,离开具体的业务上下文,是很难测试的,因为什么样的用户具有什么样的权限往往是业务领域的知识,换句话说,这一类问题的测试重点是看正常的用户能否按照业务需求所期望地正常使用系统,怎么区分evil user并阻止其对系统的使用和破坏,需要很强的业务背景。

举一个简单的例子,比如一个Web系统有两种角色,管理员和普通账户,业务需求是管理员可以修改所有人的所有信息,普通账户只能看到和修改自己的信息,如果普通账户张三可以通过一些非正常手段修改李四的信息,或者非系统的用户(evil user)通过某些方式可以看到系统内账户的个人信息,这些都是严重的安全缺陷,而且这一类的缺陷所占比率比较大,但是都没有办法通过ZAP扫描出来,也没有办法脱离对业务知识的了解来进行测试。

安全内建

ZAP扫描,针对业务上下文的用户权限测试(不能局限于界面,还要通过其他一些方式比如修改请求)以及evil user的用户场景测试,可以覆盖绝大多数的Web安全缺陷,但是正如我们没有办法将质量注入一个已经成型的产品一样,安全也是同样的道理。

如果我们在软件已经编码完成之后再引入安全检查和测试,那么软件的安全质量已经确定,后期的修复只能解决已经发现的安全漏洞,不能让软件更加安全,而且对于这些安全缺陷,发现得越晚,修复的成本就会越高。

所以为了开发安全质量较高的产品,除了选择好的工具以及增加业务背景下的安全测试,还非常有必要将安全检查和测试提前,在软件开发各个过程中引入安全实践。

微软提出的SDL(Security Development Lifecycle)就是这样的理念,SDL提出了很多软件开发过程中非常好的的安全活动,比如需求分析阶段的“最小权限原则”,开发阶段“弃用不安全的函数”,以及测试阶段可以使用“模糊测试”等等,其核心理念就是将软件安全的考虑集成在软件开发的每一个阶段,从需求,设计,编码,测试,到最后的发布整个过程中,下图是一个简化版的SDL流程图,完整的SDL还包括前期的培训,发布后的响应等:

SDL

ThoughtWorks提出的BSI(Build Security In),即安全内建,强调的也是安全提前的理念,结合敏捷软件开发实践,将安全融入到用户故事的生命周期中,引入安全验收条件, 不同角色(业务人员、开发人员,测试人员以及客户)在用户故事的每个阶段对安全验收条件进行沟通,检查,验证,确保在用户故事交付之前满足了定义的安全验收条件,下图就是用户故事的生命周期图以及在各个阶段哪些角色会参与哪些安全活动的一个简单介绍:

QQ20160405-0@2x

在保证用户故事满足了安全需求的基础上,基于迭代/发布还会有一些整体的功能级别的验证,做到真正的安全内建。

以上两种理念强调的都是把安全作为跟功能一样重要的考虑因素,在软件开发的各个阶段进行沟通和反复验证,很多实践经验也表明,将安全活动作为软件开发过程的一部分来执行,其安全效益要大于临时的或者零散的安全活动。

结论

所以从安全测试的选择来看,我们不能单一依赖某种工具,比如ZAP扫描,而应该多加入一些基于业务需求的安全测试,多从攻击者的角度思考问题,更重要的是,可靠的安全测试只能避免安全漏洞造成更大的危害和影响,并不能打造安全的产品,真正的安全产品必定需要整个软件开发过程中每个环节的保障,需要从业务分析阶段就将安全作为重要的考虑因素,将安全实践融入到整个软件开发过程中,所有角色共同参与,这样才能做到真正的安全内建。

用了ZAP,你的软件就安全了吗?,首发于文章 – 伯乐在线

内建安全的软件开发

1. 传统安全实践面临着严峻的挑战

随着互联网应用、移动应用爆发式的增长,伴随而来的黑客攻击事件也是层出不穷。仅在过去的2015年里,被公开报道的数据泄露安全事件就有约3930起,将近7.36亿条数据被泄漏。显而易见的是,如果某家企业被爆出安全问题,对企业造成的影响不仅仅只是名誉、财务上的损失,还会遭受法律诉讼,陷入竞争不利的局面。安全已经是企业不可忽视的问题。

近年来,黑客攻击的趋势已经发生了显著的改变,由最初的针对网络、操作系统以及服务器的攻击,已经转变为针对应用程序的攻击。根据Garnter的调研报告显示,有将近80%的安全漏洞发生在应用层。为了应对新的安全威胁,企业也做出了各种尝试,例如为员工提供安全培训、设立安全规范文档、部署Web应用防火墙、建立安全监控中心、利用安全渗透测试寻找安全漏洞等等。经过一些列的努力,取得的成果也是比较显著的,例如老生常谈的SQL注入攻击,其全球范围内的漏洞数量呈现出了非常显著的下降趋势,如图1-1所示。

image_0

图 1-1 SQL 注入漏洞数量趋势

然而并不是所有发生在应用程序中的安全漏洞的数量都得到了有效抑制,例如跨站脚本攻击漏洞的数量似乎并不受安全措施数量的影响,如图1-2所示。

image_1

图 1-2 XSS 漏洞数量趋势

一个现象是,企业为应对应用安全问题采取了各种措施,虽然有一定成效但是也有或多或少的问题,如图1-3所示,随着安全措施数量的增加,应用中的安全漏洞的数量不再显著减少。似乎无论如何努力,开发出来的应用中始终有安全漏洞。

image_2

图 1-3 安全措施和安全漏洞数量的关系

除此之外,企业还面临着更多的困境:

  • 安全漏洞总是很晚才被发现出来,造成的结果是安全漏洞的修复成本高昂。
  • 未能检查出隐藏在应用中的安全漏洞,含有安全缺陷的应用被发布到生产环境中,这将增加企业面临的安全风险。
  • 某些安全措施容易被误解为是团队的负担
  • 开发团队人员安全技能有所缺失,应用的安全性过度依赖于应用安全团队

2. 为何安全漏洞如此难以消除?

为消除安全漏洞,企业采取了不少措施,然而一个不争的事实是,企业主要依赖于Web应用防火墙(Web Application Firewall,下文简称WAF)和渗透测试(或安全审计)。然而问题在于,这两种措施固然有效,但是也存在着明显的不足。

2.1 WAF是把双刃剑

WAF主要的职责是阻挡攻击,为开发团队争取修复漏洞的时间。换言之,只要应用中存在安全问题的代码没有被修复,漏洞就会一直存在,一旦WAF规则被绕过,或者配置不当,反而会将应用置于更高的安全风险当中。此外,误报和漏报始终是WAF无法彻底解决的问题,与此同时,随着应用规模的扩大,业务逻辑复杂度的增加,对于WAF的管理维护难度也在不断增加。

2.2 渗透测试让企业面临两难的境地

渗透测试确实能发现安全漏洞,但是也存在着明显的不足。首先是太晚才能得到安全问题的反馈,因为通常而言,渗透测试会被安排在产品上线发布之前进行,此时如果检查出安全漏洞,企业反而会面临两难的境地:修复安全漏洞之后再发布产品,或者强行发布包含安全漏洞的产品。同修复业务缺陷一样,修复安全漏洞也是越晚修复成本越高,开发团队需要投入更多的时间和人力,而这可能导致产品推迟发布,错失市场机会;如果强行发布含有安全漏洞的产品,又将增加被黑客攻击利用的风险。

2.3 抛开现象看本质,问题的根源在于缺乏高效的安全反馈机制

应用中的安全漏洞是在开发过程当中由于各种原因被引入的,然而回顾现有的安全措施就会发现,其中大量的措施发生在应用程序开发过程之外,例如WAF、安全监控发生在产品上线之后,渗透测试需要等待开发完毕后才能进行,而安全培训、安全规范只能起到预防作用。尽管各种安全措施都能为开发团队提供不同程度的安全反馈信息,然而问题在于,大量的安全措施都发生在开发过程之外,距离安全问题被引入的时间点之间有很长的距离,因此安全问题不能及时的反馈给开发团队。

此外,由于不少安全措施的成本高、耗时长,因此难以持续性的为开发团队提供安全反馈。例如聘请第三方安全公司对应用进行安全渗透测试的价格较高,且往往需要几天甚至更多时间才能收到安全报告。

3. 更好的解决安全问题

安全漏洞本质上是软件质量缺陷,安全性是软件质量的重要组成部分,而现如今应用安全面临的问题和多年以前软件测试面临的问题极其相似。当时人们在开发软件的时候,采取的做法往往是在软件开发临近结束之时集中性的进行测试,修复发现的质量缺陷,结果是当时的软件质量普遍不高,不少项目因此失败。

为了提高软件质量,开发团队采取了一些措施,例如将测试介入的时间点提前,尽早进行测试,甚至是先写测试后写实现代码,与此同时也大量采用自动化测试来加快测试执行速度,采用构建流水线持续关注软件质量,并且每位团队成员都对软件质量负责,而不再认为只是测试人员的职责。这些措施之所以能够显著提高软件质量,关键在于它们能够更加高效的为开发团队提供软件质量相关的反馈信息,使得开发团队能够基于反馈结果迅速进行改进。

同理,要更好的解决安全问题,开发团队应当建立起一个高效的安全反馈机制,在开发过程中引入一些安全活动,尽早开始收集安全反馈信息,加快获取安全反馈的速度,在整个开发过程中持续性的关注应用的安全性,与此同时团队成员共同来承担安全职责。我们将这些在实践中总结出来的经验命名为内建安全的软件开发方式(Build Security In DnA,下文简称BSI),从源头上尽早、尽快、持续性的,以团队共同协作的方式发现并解决安全问题。

image_3

图 3-1 内建安全的软件开发方式 (Build Security In)

3.1 尽早获取安全反馈

越早获取到安全反馈信息,越有利于开发团队以更低的成本将其修复。一个反面例子是,安全问题在早期就已经引入了,但是只能通过后期的渗透测试才能暴露出来,安全反馈信息经历了很长一段时间才能反馈给开发团队。与其依赖于后期的渗透测试,不如在开发过程当中引入一些适当的安全实践,比如在分析业务需求的同时主动分析安全需求,将其作为质量验收标准在团队内明确出来,再比如,针对每次代码提交都对其进行安全评审、测试人员在测试业务功能的同时还对安全性进行验证。通过这种方式,使得团队能够尽快得知应用的安全状况,而不必依赖于很晚才能提供安全反馈的渗透测试。

image_4

图 3-2 在开发过程当中引入安全实践

3.2 通过自动化加速获取安全反馈的速度

从安全角度对代码进行的评审,以及测试人员主动进行安全测试等等手段都能更早的产出安全性的反馈信息,但是传统的安全测试、渗透测试主要是以手动的方式进行,造成的结果是需要耗费许多时间才能收集到安全反馈信息。更好的做法是,对于常规的安全测试可以借助工具以自动化的方式进行,不仅能够以更快的速度得到安全性反馈,还能在一定程度上弥补由于人员安全技能不足所造成的安全测试不够全面的问题。自动化带来的另外的好处是,它可以集成入CI服务器,从而让开发团队可以在CI流水线完成之后第一时间发现应用的常规安全问题,而不必等到上线前由安全专家来发现安全问题。

例如,应用通常依赖于各种第三方组件,这些组件可能含有已知的安全漏洞,手工对每个组件进行安全检查是行不通的,更好的做法是利用自动化的检查工具如OWASP Dependency Check,以更快的速度对组件进行安全检查并且生成结果报告。这一过程是完全自动化的,开发团队只需定期检查报告即可,使得开发团队获取反馈的速度得到极大的提升。

3.3 在开发过程中持续性的关注安全

收集安全反馈不是一次性的活动,高效的安全反馈机制和持续集成秉持相同的理念:除了尽早集成、尽快集成之外,很重要的一点就是让集成活动能够持续性的发生。类似的,对于建立高效的安全反馈机制而言,在能够尽早、快速的获取安全反馈的同时,还需要让这个反馈循机制在应用开发过程中持续的运行下去。

自动化使得开发团队能够更容易的做到对安全的持续关注。例如,将自动化的代码安全扫描和持续集成服务器结合起来,这样在每次代码提交后都能得到一份安全报告,从而可以立刻着手进行相应的修复。此外,对于那些不适合自动化运行的安全实践而言,开发团队依然有必要持续性的去做。例如,每当应用的功能发生变更之时都应该进行一次威胁建模以分析安全威胁,明确安全需求,每当新的功能被开发出来,测试人员都要对其安全性进行验证。

image_5

图 3-3 对安全保持持续性的关注

3.4 团队成员共同承担安全职责

大多数人认为团队中的测试人员应该对产品的安全负责,理由是他们要进行各种测试,安全测试理应包含在内。如果有专门的安全团队,则更多的人会认为安全团队应该主导产品的安全性,毕竟,在他们眼里这是安全团队天生的职责。

但这是一种误解,只要人们认为安全应该由团队中的某个角色或者某个独立的团队负责,就容易形成消极的氛围:既然有专门的人在负责产品安全,那就说明安全不是自己的职责,等发现安全问题了再说。这种氛围会带来很多负面影响,例如:安全问题难以及早发现并解决,人员安全技能得不到锻炼,安全改进在团队内难以推动,等等。

和传统的高度依赖于某个角色或者安全团队来保证产品安全的做法不同,BSI则是把安全职责拆分到团队中的每个角色身上,让所有人都参与进来,共同协作解决安全问题。每个人都肩负着维护产品安全性的责任,主动为此付出努力。

业务分析人员在分析业务需求的同时把产品的安全性纳入考虑的范围之内,如果他们没有相关的安全技能,则可以和技术人员合作共同来完成这一任务,与此同时,开发人员在编码的过程中有意识的去避开安全风险,并协助测试人员对产品进行安全测试。在产品交给安全团队进行安全审查之前,一些安全漏洞已经被发现并修复了,甚至在需求分析之时就已经被规避了。此时的产品已经具备了一定的安全性,也为安全团队争取到了更多的时间,使得他们可以把更多的精力放在检测深层次安全漏洞上面。

通过这种共同分担职责的方式,开发团队在应对安全问题上的内部凝聚力会得到增强,团队成员会更有意愿和动力去提升自己的安全能力。更重要的是,这种做法可以使得团队成员以及安全团队能更好的进行协作,从而逐渐形成良性循环。

image_6

图 3-4 共同承担安全职责

4. 总结

安全,是每个企业都需要做到的,在互联网时代更是重中之重。企业正面临着全新的安全挑战,然而现有的一些传统安全措施却并不能高效的解决这些问题,主要的原因在于过度依赖WAF、渗透测试等被动防御手段,往往是在比较晚的时候才获取的安全反馈,导致安全问题修复成本高昂,甚至使企业陷入进退两难的局面。

为了提高应用的安全性,并且弥补传统安全措施的不足,Build Security In应运而生:在软件开发过程中引入合适的安全实践以尽早发现安全问题,利用自动化的优势缩短安全问题的反馈周期,持续的、以共同协作的方式主动预知并化解安全问题。在减轻开发团队和安全团队负担的同时,提高发现、解决安全问题的效率。

安全不是绝对的,所以BSI对于安全的保证也是相对的,但是对于业务越来越复杂,规模越来越庞大的软件系统,BSI这种内建安全的软件开发方式必将从源头上减少安全风险,降低被黑客攻击的可能,从而显著减少系统在上线使用后因安全问题造成的损失,使其在互联网的大潮中生存下来。

内建安全的软件开发,首发于文章 – 伯乐在线

SQL 注入,永不过时的黑客技术

TalkTalk的信息泄漏事件导致约15万人的敏感信息被暴露,涉嫌造成这一事件的其中一名黑客使用的并不是很新的技术。事实上,该技术的「年纪」比这名15岁黑客还要大两岁。

[译注:TalkTalk是英国电话和宽带供应商,这件信息安全事故发生在2015年10月份,当时这件事情还挺轰动的,上了新闻头条,其中一名黑客年仅15岁。]

这项技术就是大名鼎鼎的SQL注入,所谓SQL注入就是黑客在网站的提交表单数据中输入恶意命令从而获取数据的方法。它曾经被用来窃取世界卫生组织员工的信息,偷过华尔街日报数据,甚至还攻击过美国联邦政府机构。

“这是最简单的黑客技术”,w0rm 告诉 Motherboard[译者注:Motherboard就是这篇文章原文所在网站],他是一名匿名黑客,声称对华尔街日报的攻击负责。整个攻击过程只用了几个小时。

但是,正因为它是比较简单的技术,同时经常被用来从公司或者政府机构窃取数字信息,所以SQL注入攻击应该相对来说比较容易防范才对。

但为何到了2015年,SQL注入攻击仍然能导致最大规模的信息泄漏事件呢?

有迹可循的最早SQL注入攻击的记录大概是Jeff Forristal在黑客杂志Phrack上发表的文章。Forristal当时是rain.forest.puppy的掌舵人[译者注:rain.forest.puppy是一个安全咨询组织,或者说,是个黑客团队?:)],他目前在网络安全公司Bluebox,负责移动安全方面的CTO。

“微软声称,你看到的都不是问题,所以不用费心去处理它”

[译者注:这是有人给微软报告SQL server这个漏洞时,微软的回复]

SQL, 全称为结构化查询语言,是一种用来管理数据库的编程语言。本质上说,他就是被网站用来从数据库中提取一些数据来处理或者展示给用户的。

但是Forristal发现,当输入特定的命令时,会导致服务器泄漏信息。“用户可能捎带一些自己的SQL语句”,他写道。

在Phrack的1998年12月份期刊上,Forristal发表了微软的SQL server的一系列SQL注入的问题。当Forristal的同事们向微软反馈此问题时,“他们的回答是:好了,别闹了”,他写道,“他们说,你看到的都不是问题,所依别费心去处理了”

SQL注入发展到今天,已经过去了15年,在OWASP组织每三年发表一次的OWASP的Top 10问题上,SQL注入攻击经常坐上榜首的位置。OWSAP全称是开放式Web应用程序安全项目(Open Web Application Security Project),它是一个监控网站面临哪些安全威胁的非盈利性组织。

Phrack现在的logo

“SQL注入经常是第一威胁,主要反映在相关攻击事件的数量上,同时还有一些其他因素导致它如此频发,” Troy Hunt在Motherboard的一次电话采访中如是说道,Troy Hunt是攻击检测网站haveibeenpwned.com的创始人[译者注:感觉像是一个社工库]

“当你访问一个网页时,你发出一个请求,然后渲染服务器返回的数据”, Hunt介绍,“举个例子,你看一篇新闻,在地址栏上使用id=‘1’来发出请求时,服务器返回编号为1的文章,把id改成2的时候,服务器返回编号为2的文章”

但是,“使用SQL注入攻击,攻击者可能会将ID字段改成一个其他什么东西,导致服务器做一些意料之外的事情”,Hunt继续说到,比如返回一些隐私数据。

一次攻击也许只返回一条或者一段信息,于是攻击者就“不断重复攻击,一次又一次,想干几次干几次,直到他们获取到所有数据为止”,Hunt说。

显然,这需要一段比较长的时间,所以,黑客一般会使用自动化工具。其中比较具有代表性的事Havij,“这是最流行的脚本小工具,因为它支持Windows操作系统,而且还有个图形界面”, Mustafa AI-Bassam通过在线聊天工具和Motherboard如此描述,他是一名安全研究员,同时还是一名LulzSec黑客组织的前成员。

另一个经常被用到的工具是 sqlmap 。“它可以爬取网站的页面,就像搜索引擎的爬虫,寻找网站中所有输入表单,然后提交一些可能导致MySQL语法错误的数据”, AI-Bassam继续介绍。

当攻击者找到一个攻击点,接下来就很容易自动化开搞了。

sqlmap的界面

“他们会使用Google来搜索那些典型的容易受到SQL注入脚本攻击的URL”, AI-Bassam说。“他们还经常使用脚本来检查所有URL,用自动化的方法来试探是否存在漏洞”

“你甚至可以教一个4岁的孩子来做这种事情[译者注:汗~~我还不如个4岁的孩子]”, AI-Bassam补充道,强调这整个过程简单到令人发指的地步。确实,Hunt就曾经上传过一个视频,视频上显示他是怎么教他3岁的儿子用Havij来实施SQL注入攻击的[译者注:这是真爱啊,我坚决不会让我女儿走上计算机这条不归路]

“你把要攻击的URL输入进来,然后所有数据就出来了”,Hunt向Motherboard介绍道。在YouTube上有太多教程来介绍如何实施SQL注入攻击了。

事实上,已经有很多解决方案被网站开发人员用来防止SQL注入攻击了和避免客户信息或者公司信息泄漏了。而且那些解决方案一经存在好几年了。

所有这些解决方案的核心都是采用“prepared statments” (预编译声明?): 当SQL语句在数据库中执行是,绝对不能直接执行用户的输入。

如果这些解决方案直接有效,那为何SQL注入攻击还是这么猖獗呢?

“使用prepared staement的好处是它们可以从语义层面上杜绝任何在开发者意料之外的输入,这些输入可以通过构造一些SQL语法,让数据库查询语句从任意一张数据表中获取一条额外的数据”,Mike Schema在给Motherboard的一封email中如此说导,他是雅虎的一名高级经理、软件工程师。

另外一种做法是“使用SQL库对用户输入做一些净化操作”,AI-Bassam建议。简而言之,就是将输入中任何潜在的恶意部分去除掉。

那么,如果SQL注入如此简单,以至于小孩子都能用,而且解决方案又这么直接有效,为什么SQL注入攻击还这么猖獗呢?

“任何一名严谨的程序员都应该知道SQL注入攻击,但是仍然有大量的程序员缺乏经验,所以公司在雇佣某人的是偶,他可能没有受到过任何关于如何消除风险的训练”, AI-Bassam指出。除此之外,“他们的经理们往往只要求开发出能用的软件,而不是安全的软件”。

来自雅虎的Schema也认同这一点,并且补充“有些时候,一些小应用功能有限,要求很快就把东西做出来”,导致开发者经常忽略一些应对各种攻击的工作,尽管他们实施起来也不是很困难。

Hunt不是那么宽容,他也不认同所有的过错都来自于高层管理的压力。相反,他认为现在有网上有这么多教程详细的向开发人员讲解如何避免SQL注入攻击,对,是详细的教程,不仅仅是一些好的建议。“这些年,我看到很多有关如何避免明显的SQL注入风险的教程”,他说到。

随着互联网黑客们不停的在YouTube上分享他们的SQL注入攻击经验,网站的开发者们也没有闲着。“我们有能力的人站出来,分享他们的专业知识,而不仅仅是把自己的事情做好就行了”,Hunt说。

最后,这些网站的安全和他们的数据安全归根到底还是网站开发者自己负责的。这意味着SQL注入攻击和漏洞还会存在,至少在未来一段时间内不会消失。[译者注:估计原作者的意思是安全还是由开发者自己负责,不在于黑客不去攻击,自己开发的时候不注意,还是没办法避免被攻破的~~]

SQL 注入,永不过时的黑客技术,首发于博客 – 伯乐在线

如何伪造 TCP 握手?

据我们所知,这种攻击是新发现的。询问一下周围的人,几乎都认为TCP握手会对两侧的IP地址进行验证。这种攻击表明实际上并非如此。

在一个方提斯大学的合作项目中,我和Raoul Houkes研究不同的TCP攻击方式,要不就在协议层面,要不就针对具体的实现。我们发现了一种协议方面的攻击,它可以影响到所有正确的协议实现。

客户端A要与B建立连接,TCP握手的过程是这样的:

A:你好,B。我是A。发送序列号(SYN)是5。
B:你好,A。我是B,确认序列号(ACK)是5,发送序列号(SYN)是3。
A:你好,B。我是A,确认序列号(ACK)是3,发送序列号(SYN)是6。我要请求example.net。
B:你好,A。我是B,确认序列号(ACK)是6.发送序列号(SYN)是4。下面是你请求的结果:……

然后,A和B之间就可以相互通信了。对A或者B而言,每发送一个byte,序列号都会相应地增加。这样就可以跟踪是否所有的数据都被对方接收,保证可靠的传输。

在1981年,这种通讯方式被设计出来的时候,安全并没有那么重要。ARPANET适应单一网络,并且他们需要这样一个发送数据的协议:能够进行出错重传,错误校验,并且可以保持包的有序性等等。TCP解决了所有这些问题。

目前,这两个数字域,分别叫做发送序列号(SYN)和确认序列号(ACK),用来保证安全可靠的传输。但是,同时也存在两个问题:

  1. 这两个数字域都只有32字节,无法记录更大的数值。
  2. 由于它们的双重目的,序号不正确的报文会被丢弃,而不破坏连接。换言之,你可以发送一个ACK不正确的报文,然后再发送一个ACK正确的报文,正确的报文可以被接收。

A向B发送数据包,我们利用上面两个特性来进行攻击,大致过程如下:

A:你好B。我是C。发送序列号(SYN)是5。
B:你好C。我是B。确认序列号(ACK)是5。发送序列号(SYN)是3。
A:你好,B。我是C,确认序列号(ACK)是 1,发送序列号(SYN)是6。我要请求example.net。
B:你好C。我是B,你的报文不正确。请关闭连接。
A:你好,B。我是C,确认序列号(ACK)是2,发送序列号(SYN)是6。我请求example.net。
B:你好C,我是B。这是不正确的。请关闭连接。
A:你好B,我是C,确认序列号(ACK)是3。发送序列号(SYN)是6。我想请求example.net。
B:你好C,我是B,确认序列号(ACK)是6,发送序列号(SYN)是4,以下是你请求的结果……

在上面的案例中,主机A没有收到B的任何消息,B也不知道响应的是一个假的IP地址。主机A伪装成了C的IP地址。

这中攻击的先决条件之一是真正的C没有发送后续报文(或者RST报文),但是这非常容易实现,使用一个并不存在的C(比如0.0.0.0)或者利用防火墙(客户端一般都处于全状态防火墙的后面,或者是NAT, 也或者两者都有)。

B等待C(或者其他客户端)确认消息的时间是有限的。我在Linux4.2内核上做过实验,等待时间是20秒。20秒之后,你需要重新开始(发送下一个SYN),但是这没有什么意义,因为选择的序列号完全是随机的。

这种攻击的成本如何呢?平均而言,平均而言,需要耗费120GB的网络流量(60byte的因特网头,IP头和TCP头的组合)来创建一个虚假连接。也许你很不幸要花费200GB的流量,也可能很幸运只要72GB就够了。

简单调查表明很多VPS系统中1 gbps的带宽花不了多少钱。如果你能充分利用可用带宽,平均而言这种攻击17分钟11秒就够了。

通常你想注入有效载荷,例如,发送一个命令。这个命令需要拼接到已经存在的数据上,使得攻击耗费的流量更大。例如,发送“GET/HTTP/1.0nn”平均下来要花费了152GB或者20分钟。这可以在访问日志中展体现出来,虽然看上去是一个完美的正常连接。

这种攻击的其他案例还包括绕过黑名单或者白名单,比如在某些系统的管理接口上。这从90年代就非常流行,而且现在仍然广泛存在,很多新应用设备还是才用这种方式运行。

概念证明(Proof of concept,简写作PoC)

一个研究项目没有Poc像什么话呢?下面是一些Wireshark的截图,一个dump包,以及用到的代码。

(点击查看大图)

我筛选出了一些192.168.36.17捕获的相关包。第一个包是初始化包,192.168.36.11发送的,伪装的IP是192.168.36.18.。我们的目标主机向虚假IP 192.168.36.18发送响应报文。192.168.36.17开始猜测正确的ACK号。注意,时间从0.x秒增加到8.x秒,这里我把一些尝试过滤掉了。在某一时刻数字从2的32次方(4十多亿)到0,这时因为Wireshark给了我们一个相对的数字。这也意味着我们已经发现了正确的序号。我们向SSH服务器发送确认序列号(ACK)1,服务器收到这个序号后,开始响应。SSH服务器假定拥有的连接都是有效TCP连接。

以下是一些更详细的会话内容:

(点击查看大图)

Server选取的的随机数字是0x0006943f (或者 431167)。

(点击查看大图)

在某一时刻,我们脚本猜测出了0×00069440 (或者 431168),这是正确的数字,因为我们需要发送我们收到的数字加1。

(查看大图)

SSH服务器对此作出了响应,发送了一个标志有效连接开始的标题报文。

原始dump包只有15秒,因为我只在捕获了日志交替之前15秒的事件。感觉15秒时间并不长,但是这有5 544 384(550万)个包,几乎占领十亿字节的一半。如果你想看一下实际效果,可以运行下面提供的攻击代码攻击。

上面的看到的dump包可以点击下面的链接下载:

spoofed-tcp-connection.pcap

最后,进行攻击的代码也可以点击下面的链接下载:

attack-tcp.py

作为一个真正的PoC,以上代码是为了这个目的而写的,没有考虑可维护性。

结论

这种攻击很难避免,因为这是TCP协议的本身的特性造成的。只能漫无目的的猜测可能被注入的无效ACK,然后将它作为一个关闭连接的理由,但即便如此还是很容易受到攻击。

为了验证一次连接的双方,需要用到附加的安全验证方式例如TLS。即使证书没有被授权,一些加密的TLS会话还是要进行,因为有些附加的数据需要传送到客户端。这使得证明变得不可行。

经验总结:不要使用基于IP地址的授权,不要相信IP地址白名单,当需要安全验证(或者不可可抵赖)的时候考虑使用安全协议。
作为一个真正的PoC,以上代码是为了这个目的而写的,没有考虑可维护性。

如何伪造 TCP 握手?,首发于博客 – 伯乐在线