城的灯

开发人员应该知道的安全知识

最近几年层出不穷的安全漏洞,再一次要求大家提高安全意识。安全是很大的一个话题,下面是我平时工作总结出来的一些安全方面的经验。


关闭错误显示

很多网站不知道是运维人员的业余还是疏忽,网站的错误显示没有关闭。攻击者只需要通过调整参数,就可以获得错误的堆栈信息,从而推断出网站使用的相关技术栈,从而为后面的攻击大开方便之门。


避免SQL注入经验

关于SQL注入,只要是稍微有点经验的程序员都应该明白是怎么回事。此处不谈SQL注入的方法和原理,只谈我们在开发过程中怎样编程是较安全的方式,从而将注入的风险降到最低。

  1. 前面已经说过了关闭错误显示,只要我们关闭了错误显示,我们就可以将盲注的难度提升。
  2. 参数校验和过滤特殊符号,这对喜欢拼SQL的人来说是一个可行的方案,不过成本很高。
  3. 绑定变量,使用预编译语句。例如:PreparedStatement(需要驱动支持),框架层面如Mybatis中要尽量使用#符号,少用和不用$,如果非要$使用也要用方案2来进行补偿。

SQL注入已经经历10几个年头了,它的攻防都有非常成熟的案例,我个人觉得只要我们在开发的时候稍微留点心,就可以防住绝大多数的攻击。


用户账号体系安全

任何一家互联网公司或者软件公司都会遇到用户账号体系设计的需求,尤为重要的就是用户密码的安全。很多知名的互联网公司用户库的泄露更是给我们敲响了警钟,所以我们需要设计一套即使账号体系发生泄露,也不会造成较大的危害,即泄露也不能登录用户账号。关于账号体系的设计,网上的方案有很多,我觉得主要需要做好如下几点。

  • 千万不要自己去写hash函数,如果你自信到能够写出SHA256SHA512那就另当别论,建议使用SHA256、SHA512、RipeMD、WHIRLPOOL、SHA3等经过验证的hash函数,至于MD5SHA1这些毕竟有点老了建议不使用。
  • 为了防止查表(包含彩虹表)破解、暴力破解,所以一定需要加盐。首先盐不能复用,每个用户一个盐,密码发生变动盐也要同步修改;其次盐不能被预测,所以建议使用伪随机数生成器(CSPRNG),各个平台都有相应的技术解决方案。至于盐放到密码后面还是前面,这个都可以,只要保持风格一致就行。
  • hash一定要在服务端做,即使前端JS做了hash,前端hash依赖于JS,如果前端禁用JS,后端还需要模拟前端的hash。前端的盐千万别向后端请求去获取用户的盐,前端的盐可以用用户名+特定的字符串的规则来实现。
  • 尽可能使用慢速hash函数,让破解的代价高昂到不可以接收,所以我们可以使用key扩展的hash函数,可以使用PBKDF2或者bcrypt,虽然慢速hash可能导致DDoS攻击,但是我们可以让迭代次数少一点或者在JS端来运行hash函数,如Stanford JavaScript Crypto Library
  • 密码重置的时候,随机token一定要跟账户绑定并设置时效,因为SMTP是明文传输协议,防止SMTP导致安全问题。
  • 客户端hash并不能代替TLS,建议为了防止中间人攻击,认证等敏感信息接口还是走TLS。

我平时更多的是使用Spring-security中提供的BCryptPasswordEncoder,它是OpenBSD-style Blowfish的实现,盐都不用保存了,它直接帮我搞定非常方便,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
package info.yangguo.demo.security;

import org.junit.Test;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;


/**
* @author:杨果
* @date:15/4/19 上午10:04
*
* Description:
*
*/
public class BcryptTest {
private static String password = "yangguo";

@Test
public void testEncode() {
BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder();
for (int i = 0; i < 10; i++) {
String encodePassword=bCryptPasswordEncoder.encode(password);
System.out.println(encodePassword);
System.out.println(bCryptPasswordEncoder.matches(password,encodePassword));
}
}
}

CSPRNG方案

platform CSPRNG
PHP PHP mcrypt_create_iv,openssl_random_pseudo_bytes
Java java.security.SecureRandom
Dot NET (C#, VB) System.Security.Cryptography.RNGCryptoServiceProvider
Ruby SecureRandom
Python os.urandom
Perl Math::Random::Secure
C/C++ (Windows API) CryptGenRandom
Any language on GNU/Linux or Unix Read from /dev/random or /dev/urandom

PHP PBKDF2 密码hash代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140

<?php
/*
* Password Hashing With PBKDF2 (http://crackstation.net/hashing-security.htm).
* Copyright (c) 2013, Taylor Hornby
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. 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.
*
* 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 HOLDER 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.
*/

// These constants may be changed without breaking existing hashes.
define("PBKDF2_HASH_ALGORITHM", "sha256");
define("PBKDF2_ITERATIONS", 1000);
define("PBKDF2_SALT_BYTE_SIZE", 24);
define("PBKDF2_HASH_BYTE_SIZE", 24);

define("HASH_SECTIONS", 4);
define("HASH_ALGORITHM_INDEX", 0);
define("HASH_ITERATION_INDEX", 1);
define("HASH_SALT_INDEX", 2);
define("HASH_PBKDF2_INDEX", 3);

function create_hash($password)
{
// format: algorithm:iterations:salt:hash
$salt = base64_encode(mcrypt_create_iv(PBKDF2_SALT_BYTE_SIZE, MCRYPT_DEV_URANDOM));
return PBKDF2_HASH_ALGORITHM . ":" . PBKDF2_ITERATIONS . ":" . $salt . ":" .
base64_encode(pbkdf2(
PBKDF2_HASH_ALGORITHM,
$password,
$salt,
PBKDF2_ITERATIONS,
PBKDF2_HASH_BYTE_SIZE,
true
));
}

function validate_password($password, $correct_hash)
{
$params = explode(":", $correct_hash);
if(count($params) < HASH_SECTIONS)
return false;
$pbkdf2 = base64_decode($params[HASH_PBKDF2_INDEX]);
return slow_equals(
$pbkdf2,
pbkdf2(
$params[HASH_ALGORITHM_INDEX],
$password,
$params[HASH_SALT_INDEX],
(int)$params[HASH_ITERATION_INDEX],
strlen($pbkdf2),
true
)
);
}

// Compares two strings $a and $b in length-constant time.
function slow_equals($a, $b)
{
$diff = strlen($a) ^ strlen($b);
for($i = 0; $i < strlen($a) && $i < strlen($b); $i++)
{
$diff |= ord($a[$i]) ^ ord($b[$i]);
}
return $diff === 0;
}

/*
* PBKDF2 key derivation function as defined by RSA's PKCS #5: https://www.ietf.org/rfc/rfc2898.txt
* $algorithm - The hash algorithm to use. Recommended: SHA256
* $password - The password.
* $salt - A salt that is unique to the password.
* $count - Iteration count. Higher is better, but slower. Recommended: At least 1000.
* $key_length - The length of the derived key in bytes.
* $raw_output - If true, the key is returned in raw binary format. Hex encoded otherwise.
* Returns: A $key_length-byte key derived from the password and salt.
*
* Test vectors can be found here: https://www.ietf.org/rfc/rfc6070.txt
*
* This implementation of PBKDF2 was originally created by https://defuse.ca
* With improvements by http://www.variations-of-shadow.com
*/
function pbkdf2($algorithm, $password, $salt, $count, $key_length, $raw_output = false)
{
$algorithm = strtolower($algorithm);
if(!in_array($algorithm, hash_algos(), true))
trigger_error('PBKDF2 ERROR: Invalid hash algorithm.', E_USER_ERROR);
if($count <= 0 || $key_length <= 0)
trigger_error('PBKDF2 ERROR: Invalid parameters.', E_USER_ERROR);

if (function_exists("hash_pbkdf2")) {
// The output length is in NIBBLES (4-bits) if $raw_output is false!
if (!$raw_output) {
$key_length = $key_length * 2;
}
return hash_pbkdf2($algorithm, $password, $salt, $count, $key_length, $raw_output);
}

$hash_length = strlen(hash($algorithm, "", true));
$block_count = ceil($key_length / $hash_length);

$output = "";
for($i = 1; $i <= $block_count; $i++) {
// $i encoded as 4 bytes, big endian.
$last = $salt . pack("N", $i);
// first iteration
$last = $xorsum = hash_hmac($algorithm, $last, $password, true);
// perform the other $count - 1 iterations
for ($j = 1; $j < $count; $j++) {
$xorsum ^= ($last = hash_hmac($algorithm, $last, $password, true));
}
$output .= $xorsum;
}

if($raw_output)
return substr($output, 0, $key_length);
else
return bin2hex(substr($output, 0, $key_length));
}
?>

java PBKDF2 密码hash代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
/*
* Password Hashing With PBKDF2 (http://crackstation.net/hashing-security.htm).
* Copyright (c) 2013, Taylor Hornby
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. 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.
*
* 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 HOLDER 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.security.SecureRandom;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.SecretKeyFactory;
import java.math.BigInteger;
import java.security.NoSuchAlgorithmException;
import java.security.spec.InvalidKeySpecException;

/*
* PBKDF2 salted password hashing.
* Author: havoc AT defuse.ca
* www: http://crackstation.net/hashing-security.htm
*/
public class PasswordHash
{
public static final String PBKDF2_ALGORITHM = "PBKDF2WithHmacSHA1";

// The following constants may be changed without breaking existing hashes.
public static final int SALT_BYTE_SIZE = 24;
public static final int HASH_BYTE_SIZE = 24;
public static final int PBKDF2_ITERATIONS = 1000;

public static final int ITERATION_INDEX = 0;
public static final int SALT_INDEX = 1;
public static final int PBKDF2_INDEX = 2;

/**
* Returns a salted PBKDF2 hash of the password.
*
* @param password the password to hash
* @return a salted PBKDF2 hash of the password
*/
public static String createHash(String password)
throws NoSuchAlgorithmException, InvalidKeySpecException
{
return createHash(password.toCharArray());
}

/**
* Returns a salted PBKDF2 hash of the password.
*
* @param password the password to hash
* @return a salted PBKDF2 hash of the password
*/
public static String createHash(char[] password)
throws NoSuchAlgorithmException, InvalidKeySpecException
{
// Generate a random salt
SecureRandom random = new SecureRandom();
byte[] salt = new byte[SALT_BYTE_SIZE];
random.nextBytes(salt);

// Hash the password
byte[] hash = pbkdf2(password, salt, PBKDF2_ITERATIONS, HASH_BYTE_SIZE);
// format iterations:salt:hash
return PBKDF2_ITERATIONS + ":" + toHex(salt) + ":" + toHex(hash);
}

/**
* Validates a password using a hash.
*
* @param password the password to check
* @param correctHash the hash of the valid password
* @return true if the password is correct, false if not
*/
public static boolean validatePassword(String password, String correctHash)
throws NoSuchAlgorithmException, InvalidKeySpecException
{
return validatePassword(password.toCharArray(), correctHash);
}

/**
* Validates a password using a hash.
*
* @param password the password to check
* @param correctHash the hash of the valid password
* @return true if the password is correct, false if not
*/
public static boolean validatePassword(char[] password, String correctHash)
throws NoSuchAlgorithmException, InvalidKeySpecException
{
// Decode the hash into its parameters
String[] params = correctHash.split(":");
int iterations = Integer.parseInt(params[ITERATION_INDEX]);
byte[] salt = fromHex(params[SALT_INDEX]);
byte[] hash = fromHex(params[PBKDF2_INDEX]);
// Compute the hash of the provided password, using the same salt,
// iteration count, and hash length
byte[] testHash = pbkdf2(password, salt, iterations, hash.length);
// Compare the hashes in constant time. The password is correct if
// both hashes match.
return slowEquals(hash, testHash);
}

/**
* Compares two byte arrays in length-constant time. This comparison method
* is used so that password hashes cannot be extracted from an on-line
* system using a timing attack and then attacked off-line.
*
* @param a the first byte array
* @param b the second byte array
* @return true if both byte arrays are the same, false if not
*/
private static boolean slowEquals(byte[] a, byte[] b)
{
int diff = a.length ^ b.length;
for(int i = 0; i < a.length && i < b.length; i++)
diff |= a[i] ^ b[i];
return diff == 0;
}

/**
* Computes the PBKDF2 hash of a password.
*
* @param password the password to hash.
* @param salt the salt
* @param iterations the iteration count (slowness factor)
* @param bytes the length of the hash to compute in bytes
* @return the PBDKF2 hash of the password
*/
private static byte[] pbkdf2(char[] password, byte[] salt, int iterations, int bytes)
throws NoSuchAlgorithmException, InvalidKeySpecException
{
PBEKeySpec spec = new PBEKeySpec(password, salt, iterations, bytes * 8);
SecretKeyFactory skf = SecretKeyFactory.getInstance(PBKDF2_ALGORITHM);
return skf.generateSecret(spec).getEncoded();
}

/**
* Converts a string of hexadecimal characters into a byte array.
*
* @param hex the hex string
* @return the hex string decoded into a byte array
*/
private static byte[] fromHex(String hex)
{
byte[] binary = new byte[hex.length() / 2];
for(int i = 0; i < binary.length; i++)
{
binary[i] = (byte)Integer.parseInt(hex.substring(2*i, 2*i+2), 16);
}
return binary;
}

/**
* Converts a byte array into a hexadecimal string.
*
* @param array the byte array to convert
* @return a length*2 character string encoding the byte array
*/
private static String toHex(byte[] array)
{
BigInteger bi = new BigInteger(1, array);
String hex = bi.toString(16);
int paddingLength = (array.length * 2) - hex.length();
if(paddingLength > 0)
return String.format("%0" + paddingLength + "d", 0) + hex;
else
return hex;
}

/**
* Tests the basic functionality of the PasswordHash class
*
* @param args ignored
*/
public static void main(String[] args)
{
try
{
// Print out 10 hashes
for(int i = 0; i < 10; i++)
System.out.println(PasswordHash.createHash("p\r\nassw0Rd!"));

// Test password validation
boolean failure = false;
System.out.println("Running tests...");
for(int i = 0; i < 100; i++)
{
String password = ""+i;
String hash = createHash(password);
String secondHash = createHash(password);
if(hash.equals(secondHash)) {
System.out.println("FAILURE: TWO HASHES ARE EQUAL!");
failure = true;
}
String wrongPassword = ""+(i+1);
if(validatePassword(wrongPassword, hash)) {
System.out.println("FAILURE: WRONG PASSWORD ACCEPTED!");
failure = true;
}
if(!validatePassword(password, hash)) {
System.out.println("FAILURE: GOOD PASSWORD NOT ACCEPTED!");
failure = true;
}
}
if(failure)
System.out.println("TESTS FAILED!");
else
System.out.println("TESTS PASSED!");
}
catch(Exception ex)
{
System.out.println("ERROR: " + ex);
}
}

}

ASP.NET (C#)密码hash代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118

# Password Hashing With PBKDF2 (http://crackstation.net/hashing-security.htm).
# Copyright (c) 2013, Taylor Hornby
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# 1. Redistributions of source code must retain the above copyright notice,
# this list of conditions and the following disclaimer.
#
# 2. 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.
#
# 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 HOLDER 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.

require 'securerandom'
require 'openssl'
require 'base64'

# Salted password hashing with PBKDF2-SHA1.
# Authors: @RedragonX (dicesoft.net), havoc AT defuse.ca
# www: http://crackstation.net/hashing-security.htm
module PasswordHash

# The following constants can be changed without breaking existing hashes.
PBKDF2_ITERATIONS = 1000
SALT_BYTE_SIZE = 24
HASH_BYTE_SIZE = 24

HASH_SECTIONS = 4
SECTION_DELIMITER = ':'
ITERATIONS_INDEX = 1
SALT_INDEX = 2
HASH_INDEX = 3

# Returns a salted PBKDF2 hash of the password.
def self.createHash( password )
salt = SecureRandom.base64( SALT_BYTE_SIZE )
pbkdf2 = OpenSSL::PKCS5::pbkdf2_hmac_sha1(
password,
salt,
PBKDF2_ITERATIONS,
HASH_BYTE_SIZE
)
return ["sha1", PBKDF2_ITERATIONS, salt, Base64.encode64( pbkdf2 )].join( SECTION_DELIMITER )
end

# Checks if a password is correct given a hash of the correct one.
# correctHash must be a hash string generated with createHash.
def self.validatePassword( password, correctHash )
params = correctHash.split( SECTION_DELIMITER )
return false if params.length != HASH_SECTIONS

pbkdf2 = Base64.decode64( params[HASH_INDEX] )
testHash = OpenSSL::PKCS5::pbkdf2_hmac_sha1(
password,
params[SALT_INDEX],
params[ITERATIONS_INDEX].to_i,
pbkdf2.length
)

return pbkdf2 == testHash
end

# Run tests to ensure the module is functioning properly.
# Returns true if all tests succeed, false if not.
def self.runSelfTests
puts "Sample hashes:"
3.times { puts createHash("password") }

puts "\nRunning self tests..."
@@allPass = true

correctPassword = 'aaaaaaaaaa'
wrongPassword = 'aaaaaaaaab'
hash = createHash(correctPassword)

assert( validatePassword( correctPassword, hash ) == true, "correct password" )
assert( validatePassword( wrongPassword, hash ) == false, "wrong password" )

h1 = hash.split( SECTION_DELIMITER )
h2 = createHash( correctPassword ).split( SECTION_DELIMITER )
assert( h1[HASH_INDEX] != h2[HASH_INDEX], "different hashes" )
assert( h1[SALT_INDEX] != h2[SALT_INDEX], "different salt" )

if @@allPass
puts "*** ALL TESTS PASS ***"
else
puts "*** FAILURES ***"
end

return @@allPass
end

def self.assert( truth, msg )
if truth
puts "PASS [#{msg}]"
else
puts "FAIL [#{msg}]"
@@allPass = false
end
end

end

PasswordHash.runSelfTests

XSS防御

XSS漏洞千奇百怪,我们要防止XSS的攻击,可以遵循下面的一些原则:

  • 尽量不要页面中插入不可信的数据,如果迫不得已要插入,我们需要进行编码,编码使用OWASP提供的ESAPI的函数来实现,最好别自己实现。
  • 如果需要将不可信任的数据插入到HTML标签之间,需要对这些数据进行HTML Entity编码,ESAPI使用如下:
1
String encodedContent = ESAPI.encoder().encodeForHTML(request.getParameter(“input”));
  • 将不可信的数据插入到HTML属性里的时候,需要进行HTML属性编码,ESAPI使用如下:
1
String encodedContent = ESAPI.encoder().encodeForHTMLAttribute(request.getParameter(“input”));
  • 在将不可信数据插入到SCRIPT里时,需要对这些数据进行SCRIPT编码,ESAPI使用如下:
1
String encodedContent = ESAPI.encoder().encodeForJavaScript(request.getParameter(“input”));
  • 在将不可信数据插入到Style属性里时,对这些数据进行CSS编码,ESAPI使用如下:
1
String encodedContent = ESAPI.encoder().encodeForCSS(request.getParameter(“input”));
  • 在将不可信数据插入到HTML URL里时,对这些数据进行URL编码,我们可以进行URL编码和URL可信度的检查,ESAPI使用如下:
1
String encodedContent = ESAPI.encoder().encodeForURL(request.getParameter(“input”));
1
2
3
4
String userProvidedURL = request.getParameter(“userProvidedURL”);boolean isValidURL = ESAPI.validator().isValidInput(“URLContext”, userProvidedURL, “URL”, 255, false);
if (isValidURL) {
<a href=”<%= encoder.encodeForHTMLAttribute(userProvidedURL) %>”></a>
}
  • 使用富文本时,使用XSS规则引擎进行编码过滤,推荐使用OWASP AntiSamp或者Java HTML Sanitizer。

  • 尽可能将Cookie设置为HttpOnly。

  • 不论cookie还是localStorage都别存储敏感信息。


CSRF防御

跨站请求伪造,就是利用你的身份,以你的名义来发送而已请求,例如:发送邮件,购买商品,转账。CSRF攻击步骤分为以下两步:

  1. 用户登录被攻击的网站A,并且A网站将cookie保存在用户浏览器。
  2. 在没有登出A的情况下,访问了恶意网站B。

防御手段如下:

  1. 良好的API设计,最好遵循Restful风格。GET操作只是获取资源,不能操作资源,它具备幂等性。如果需要对资源进行操作,请使用POST请求,当然你如果想使用PUT和DELETE,可以使用POST来模拟。
  2. Cookie Hashing,由于同源策略,攻击者获取不到被攻击网站的cookie(理论上),因此就不能正确构造表单数据,所以攻击就失败了。但是如果攻击者先通过XSS获取到cookie,然后在结合CSRF来攻击,就有可能成功。这种方式是最简单可行的方案,基本可以抵挡绝大部分的攻击。
  3. 验证码,可以完全解决CSRF,但是体验会非常差。
  4. One Time Token,其实是方案2的升级,但是得注意用户开多个页来浏览站点的情况,可以通过为一个用户授权多个token来解决,但是得设置生命周期。
  5. 验证HTTP头中的refer。

Cookie使用注意事项

  1. 尽可能将cookie设置为Http Only,这样可以降低XSS攻击概率。
  2. 如果cookie是由HTTPS设置的,那么将cookie设置为secure,防止HTTP 301重定向导致cookie泄露。

防点击劫持和拖放的欺骗劫持

1.X-Frame-Options
2.if (top !==window) top.location = window.location.href;(攻击者可以使用204转向或者禁用Javascript的方式来绕过(例如iframe沙箱))。


参考文章:hashing-security