Web安全之静态内容防爬虫

Web安全之静态内容防爬虫

随着互联网的快速发展,网络安全问题日益突出。对于静态内容网站来说,防止恶意爬取网站内容成为了一项重要的任务。恶意爬取不仅会导致网站内容被盗用,还可能引发一系列安全问题,如数据泄露、恶意攻击等。因此,本文将探讨静态内容网站如何防范恶意爬取,提高网络安全。

静态内容网站的特点

静态内容网站主要是指那些网页内容固定不变或变化较少的网站。这类网站通常以 HTMLCSSJavaScript 等静态文件形式存在,不依赖后端数据库或复杂的应用程序。由于其结构简单、加载速度快,静态网站在新闻发布、产品展示、个人博客等领域得到广泛应用。

恶意爬取的威胁

恶意爬取是指未经授权,通过自动化手段(如爬虫程序)大规模抓取网站内容的行为。这种行为通常具有以下特点:

  1. 大规模:恶意爬取往往涉及大量的请求,给服务器带来沉重负担。
  2. 未经授权:爬取行为未经网站所有者许可,违反了版权和隐私政策。
  3. 恶意目的:恶意爬取可能导致内容被盗用、数据泄露、恶意攻击等后果。

防恶意爬取策略

为了防范恶意爬取,静态内容网站可以采取以下措施:

设置robots.txt文件

robots.txt 文件是一个用于告知搜索引擎爬虫哪些页面可以爬取、哪些页面不能爬取的文本文件。通过设置 robots.txt 文件,可以限制恶意爬虫的访问。

下面是一个 robots.txt 文件的例子,展示了如何设置一些基本的规则。

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
User-agent: *
Disallow: /

# 禁止所有爬虫访问网站的所有页面

User-agent: Googlebot
Disallow:

# 允许 Googlebot 访问网站的所有页面

User-agent: Bingbot
Disallow: /private/

# 禁止 Bingbot 访问 /private/ 目录下的所有页面

User-agent: Yahoo! Slurp
Disallow: /admin/

# 禁止 Yahoo! Slurp 访问 /admin/ 目录下的所有页面

User-agent: *
Disallow: /cgi-bin/

# 禁止所有其他爬虫访问 /cgi-bin/ 目录下的所有页面

# 允许特定爬虫访问特定页面
User-agent: SpecificBot
Allow: /special-page/
Disallow: /

# 对于名为 SpecificBot 的爬虫,只允许访问 /special-page/ 页面,其他页面都不允许访问

在上面的例子中:

  • User-agent: * 表示该规则适用于所有爬虫。
  • Disallow: / 表示禁止爬虫访问网站的根目录及其所有子目录和文件。
  • Allow: /special-page/ 表示允许特定爬虫访问 /special-page/ 页面。Allow 指令必须在 Disallow 指令之前,否则将无效。
  • User-agent: SpecificBot 表示该规则仅适用于名为 SpecificBot 的爬虫。

请注意, robots.txt 文件必须放置在网站的根目录下(通常是 http://www.example.com/robots.txt),以便爬虫能够找到它。同时,虽然大多数负责任的爬虫都会遵守 robots.txt 的规则,但并非所有爬虫都会遵守,因此它不能作为一种安全机制来防止数据被爬取。

使用验证码技术

对于关键页面或敏感内容,可以引入验证码技术。用户在访问这些页面时需要输入正确的验证码才能继续浏览,从而有效阻止恶意爬虫的访问。

验证码技术的案例有很多,以下列举几个常见的案例:

  1. 网站注册和登录验证码:这是最常见的验证码技术案例。用户在注册或登录网站时,系统会显示一组随机生成的字符或图片,并要求用户输入或选择正确的字符或图片来完成验证。这种技术可以有效防止自动化程序恶意攻击网站,如进行暴力破解密码、刷票等行为。
  2. 图片验证码:图片验证码是一种将随机生成的字符或数字嵌入到图片中,并要求用户识别并输入正确字符或数字的验证码技术。这种技术可以有效防止自动化程序识别并输入验证码,提高网站的安全性。
  3. 滑动验证码:滑动验证码是一种要求用户通过滑动解锁来完成验证的技术。用户需要按照指定的方向或轨迹滑动滑块,才能完成验证。这种技术可以有效防止自动化程序模拟用户操作,提高网站的安全性。
  4. 音频验证码:音频验证码是一种将随机生成的字符或数字转换为语音,并要求用户听取并输入正确字符或数字的验证码技术。这种技术适用于视觉障碍用户或无法通过图片验证码验证的情况。
  5. 逻辑验证码:逻辑验证码是一种要求用户解决一个简单数学问题或逻辑问题来完成验证的技术。例如,系统可能会显示一个加法或减法问题,并要求用户输入正确答案。这种技术可以有效防止自动化程序识别并输入验证码,提高网站的安全性。

限制访问频率

通过设置合理的访问频率限制,可以防止恶意爬虫大量请求服务器资源。例如,可以设置每个IP地址在单位时间内的最大请求次数。

限制访问频率的 Node.js 例子可以使用 Redis 来实现。下面是一个简单的示例代码:

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
const Redis = require('ioredis');
const redis = new Redis({
port: 6379, // Redis 端口
host: 'localhost', // Redis 主机地址
password: 'your_redis_password', // Redis 密码
});

const ACCESS_FREQUENCY = 5; // 设定访问频率限制,例如每分钟最多访问 5 次
const EXPIRE_TIME = 60; // 设定过期时间,例如每分钟过期

app.get('/protected-route', async (req, res) => {
const ip = req.ip; // 获取请求 IP
const key = `access-frequency:${ip}`; // 构建 Redis 键名

try {
const count = await redis.get(key); // 获取当前 IP 的访问次数
if (count && parseInt(count) >= ACCESS_FREQUENCY) {
return res.status(429).send('Too Many Requests'); // 超过访问频率限制,返回 429 状态码
}

const newCount = count ? parseInt(count) + 1 : 1; // 更新访问次数
await redis.set(key, newCount, 'EX', EXPIRE_TIME); // 设置新的访问次数,并设置过期时间

// 处理正常请求逻辑...
res.send('Success');
} catch (error) {
console.error('Error:', error);
res.status(500).send('Internal Server Error'); // 发生错误时返回 500 状态码
}
});

在这个例子中,我们使用 Redis 来存储每个 IP 的访问次数,并设定了一个访问频率限制(例如每分钟最多访问 5 次)。当一个请求到达时,我们首先获取当前 IP 的访问次数,如果超过了限制,则返回 429 状态码表示请求过多。否则,我们更新访问次数,并设置过期时间,以便在下一分钟内重置访问次数。最后,我们处理正常的请求逻辑并返回成功响应。

请注意,这只是一个简单的示例代码,实际应用中可能需要更多的逻辑和安全性考虑,例如使用分布式锁来防止并发访问问题,以及使用更复杂的算法来计算访问频率等。

数据混淆

数据混淆是指通过改变数据的表示方式或结构,使得爬虫无法直接解析出真实数据的方法。

假设你有一个包含敏感信息的API接口,返回的数据是JSON格式的。为了防止爬虫直接获取到这些数据,你可以对返回的数据进行混淆处理。

原始数据:

1
2
3
4
5
{
"name": "张三",
"age": 30,
"email": "zhangsan@example.com"
}

混淆后的数据:

1
2
3
4
5
{
"n1": "Z3Nj",
"a2": "MzAi",
"e3": "emFuc2FuQGV4YW1wbGUuY29t"
}

在这个例子中,你可以使用一种简单的混淆算法(如Base64编码)对原始数据进行编码,然后在前端使用相应的解码算法进行解码,以显示真实的数据。这样,爬虫获取到的只是混淆后的数据,无法直接解析出真实的信息。

加密传输

使用HTTPS协议对网站内容进行加密传输,可以防止恶意爬虫在传输过程中窃取数据。此外,HTTPS协议还能提高网站的安全性,保护用户隐私。

使用反爬虫技术

反爬虫技术是一种主动防御手段,通过识别并阻止恶意爬虫的访问。例如,可以通过分析请求头、请求频率、用户代理等信息来判断是否为恶意爬虫,并采取相应的防御措施。

下面是一个简单的 express 示例代码:

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
const express = require('express');
const app = express();
const rateLimit = require('express-rate-limit');

// 设置访问频率限制
const limiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15分钟
max: 100, // 在时间窗口内的最大请求数
message: 'Too many requests from this IP, please try again later.'
});

app.use('/protected-route', limiter);

app.get('/protected-route', (req, res) => {
const userAgent = req.headers['user-agent'];

// 检查User-Agent是否像是浏览器的
if (!userAgent || !userAgent.includes('Mozilla')) {
return res.status(403).send('Forbidden');
}

// 假设这是从数据库或API获取敏感数据的函数
fetchSensitiveData().then(data => {
res.send(data);
}).catch(err => {
console.error(err);
res.status(500).send('Internal Server Error');
});
});

app.listen(3000, () => {
console.log('Server is running on port 3000');
});

function fetchSensitiveData() {
// 这里模拟从数据库或API获取数据的过程
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve({ sensitiveInformation: 'This is sensitive data' });
}, 1000);
});
}

在这个例子中,我们使用了 express-rate-limit 中间件来限制来自同一 IP 的请求频率,并检查了 User-Agent 头来识别非浏览器请求。

定期更新内容

定期更新网站内容可以降低恶意爬虫的兴趣。同时,通过不断更新网站结构和内容,可以增加恶意爬虫爬取的难度。

建立安全监测机制

建立安全监测机制,及时发现并应对恶意爬取行为。通过监控网站访问日志、流量异常等信息,可以及时发现恶意爬虫并采取相应的措施。

总结

网络安全是互联网发展的基石,而静态内容网站的防爬虫工作是其中的重要一环。防范恶意爬取对于静态内容网站来说至关重要。通过了解恶意爬取的特点和采取相应的防范措施,可以有效提高网站的安全性,维护网站所有者的权益,同时提升用户体验。同时,网站所有者还应持续关注网络安全动态,不断更新和完善防范措施,确保网站内容的安全与稳定。

Web安全之 SQL 注入

Web安全之 SQL 注入

随着互联网的快速发展,网络安全问题日益受到人们的关注。 SQL 注入是一种常见的网络安全攻击方式,严重威胁着数据库的安全。本文将从 SQL 注入的概念、原理、危害以及防范措施等方面,对网络安全中的 SQL 注入进行深入探讨。

概念

SQL 注入(SQL Injection)是指攻击者在用户输入的数据中注入恶意的 SQL 语句,从而在未经授权的情况下实现对数据库的非法操作。当应用程序没有对用户输入的数据进行有效的验证和过滤时,攻击者可以利用这一漏洞,篡改原始的 SQL 语句,进而窃取、篡改或删除数据库中的数据。

原理

SQL 注入的原理主要基于应用程序对用户输入数据的处理不当。攻击者通过在用户输入的数据中插入恶意的 SQL 语句,使得应用程序在构建 SQL 查询时,将恶意语句作为查询的一部分发送给数据库执行。这样,攻击者就可以绕过应用程序的验证,直接对数据库进行操作。

危害

  1. 数据泄露:攻击者可以通过 SQL 注入获取数据库中的敏感信息,如用户密码、身份信息、银行账户等,从而导致数据泄露和用户隐私泄露。
  2. 数据篡改:攻击者可以利用 SQL 注入修改数据库中的数据,破坏数据的完整性和准确性。
  3. 数据删除:攻击者可以通过 SQL 注入删除数据库中的数据,导致数据丢失和业务受损。
  4. 服务器被控制:攻击者还可以通过 SQL 注入获取数据库的完全控制权限,进一步对服务器进行攻击和控制。

案例

下面是一个简单的 Node.jsExpress 框架中的 SQL 注入案例:

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
const express = require('express');
const mysql = require('mysql');
const app = express();

// 创建数据库连接
const connection = mysql.createConnection({
host: 'localhost',
user: 'root',
password: 'password',
database: 'test'
});

connection.connect();

// 定义一个简单的路由处理函数,存在 SQL 注入漏洞
app.get('/users', (req, res) => {
const username = req.query.username; // 从查询字符串获取用户名

// 直接将用户名插入 SQL 查询中,没有进行任何过滤或转义
const query = `SELECT * FROM users WHERE username = '${username}'`;

connection.query(query, (error, results, fields) => {
if (error) throw error;
res.send(results);
});
});

app.listen(3000, () => {
console.log('App listening on port 3000');
});

在上述代码中,我们定义了一个简单的 Express 应用,它监听 /users 路由,并从查询字符串中获取 username 参数。然后,我们直接将 username 插入 SQL 查询语句中。由于没有对 username 进行任何形式的过滤或转义,攻击者可以通过在查询字符串中插入恶意的 SQL 代码来操纵查询结果,甚至执行任意的 SQL 语句。

例如,攻击者可以通过以下 URL 尝试注入攻击:

1
http://localhost:3000/users?username='; DROP TABLE users; --

这个 URL 会导致 SQL 查询变成:

1
SELECT * FROM users WHERE username = ''; DROP TABLE users; --'

如果应用执行了这条查询,它会删除 users 表中的所有数据。

防范措施

为了防止 SQL 注入攻击,你应该:

使用参数化查询

参数化查询可以确保用户输入被当作数据而不是 SQL 代码来处理。在 Node.js 中,可以使用像 mysql 包中的 query 方法的参数化版本,或者使用 ORM(对象关系映射)库如 Sequelize 或者 TypeORM

在上述的 Node.js 例子中,你可以使用 mysql 包来执行参数化查询。以下是一个使用 mysql 包和参数化查询来预防 SQL 注入的示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
...

app.get('/users', (req, res) => {
const username = req.query.username; // 从查询字符串获取用户名

// 构建 SQL 语句
const query = `SELECT * FROM users WHERE username = '?'`;

connection.query(query, [username], (error, results, fields) => {
if (error) throw error;
res.send(results);
});
});

...

在这个例子中,? 是一个参数占位符,代表我们要查询的 username。我们将 username 作为数组 [username] 的元素传递给 query 方法的第二个参数。这样,MySQL 驱动程序就会知道 ? 应该被 username 的值替换。

由于参数化查询确保了用户输入被当作数据处理,而不是作为 SQL 代码的一部分,因此即使 username 包含恶意的 SQL 代码,它也不会被执行。这是预防 SQL 注入攻击的一种有效方式。

使用预编译语句

预编译语句是一种将 SQL 语句和参数分开处理的方式,数据库管理系统会对 SQL 语句进行解析、优化和编译,然后存储起来。每次执行时,只需传递参数,避免了 SQL 注入的风险。

在上述的 Node.js 例子中,你可以使用 mysql 包来执行预编译语句。以下是一个使用 mysql 包和预编译语句来预防 SQL 注入的示例:

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
...

app.get('/users', (req, res) => {
const username = req.query.username; // 从查询字符串获取用户名

// 准备预编译语句
const prepareQuery = `PREPARE stmt FROM "SELECT * FROM users WHERE username = '?'"`;
connection.query(prepareQuery, (error, results, fields) => {
if (error) throw error;

// 设置参数并执行预编译语句
const executeQuery = 'EXECUTE stmt USING ?';
connection.query(executeQuery, [username], (error, results, fields) => {
if (error) throw error;
console.log(results);

// 释放预编译语句
connection.query('DEALLOCATE PREPARE stmt', (error) => {
if (error) throw error;
connection.end();
});
});
});
});

...

在这个预编译语句的例子中,我们首先准备一个预编译语句,其中包含一个占位符 ?。然后,我们使用 PREPARE 语句将其发送到数据库进行编译。之后,我们使用 EXECUTE 语句并传递参数 [username] 来执行预编译的语句。最后,我们使用 DEALLOCATE PREPARE 语句释放预编译语句的资源。

使用预编译语句的好处是,即使参数中包含恶意的 SQL 代码,它也不会被数据库执行。这是因为预编译语句将参数和 SQL 语句分开处理,数据库只会执行预编译的 SQL 语句,并将参数作为数据来处理。

转义用户输入

转义用户输入是另一种防止 SQL 注入攻击的方法。转义用户输入意味着将用户提供的值中的特殊字符(如单引号、双引号等)替换为它们的转义版本,这样在 SQL 语句中它们就会被当作普通文本处理,而不是 SQL 代码的一部分。

虽然参数化查询是首选的方法,因为它提供了更高级别的安全性,但在某些情况下,你可能需要手动转义用户输入。在 Node.js 中,你可以使用 MySQL 驱动程序的 escape 方法来转义用户输入。

以下是一个使用 Node.jsmysql 驱动程序手动转义用户输入的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
...

app.get('/users', (req, res) => {
// 从查询字符串获取用户名
const username = req.query.username;

// 使用 escape 方法转义用户输入
const safeUsername = mysql.escape(username);

// 构建 SQL 语句
const query = `SELECT * FROM users WHERE username = '${safeUsername}'`;

connection.query(query, (error, results, fields) => {
if (error) throw error;
res.send(results);
});
});

...

在这个例子中,我们使用了 mysql.escape 方法来转义 username 中的特殊字符。这样,即使 username 包含特殊字符,它也不会被误解为 SQL 语句的一部分。

然而,尽管手动转义用户输入可以提供一定程度的保护,但它仍然不是最安全的做法。原因之一是它可能会导致程序逻辑变得复杂且容易出错,尤其是在构建复杂的 SQL 语句时。因此,推荐使用参数化查询或预编译语句,因为它们提供了更强的安全性保证,并且更容易正确使用。

最小权限原则

确保数据库账户只有执行必要操作的最小权限。例如,如果应用程序只需要从数据库中读取数据,那么就不应该赋予它写入权限。

MySQL 中设置只读权限,你需要登录到MySQL数据库,然后为用户创建一个新的角色并授予只读权限。你可以使用GRANT语句来实现这一点。具体如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
-- 登录到MySQL
mysql -u root -p

-- 创建新角色
CREATE ROLE 'readonlyuser'@'localhost';

-- 授予只读权限
GRANT SELECT ON your_database.* TO 'readonlyuser'@'localhost';

-- 刷新权限
FLUSH PRIVILEGES;

-- 退出MySQL
EXIT;

在上面的例子中,your_database 是你想要让用户具有只读权限的数据库名。'readonlyuser'@'localhost' 表示用户名为 readonlyuser ,并且该用户只能从本地主机连接到数据库。如果你希望用户可以从任何主机连接,你可以将 localhost 替换为 '%'

错误处理

不要将详细的数据库错误信息直接返回给用户。这可以防止攻击者利用这些信息进行更复杂的攻击。

安全审计和测试

使用工具如 SQLMapOWASP Zap 等进行安全审计和测试,确保应用程序不会受到 SQL 注入攻击。

总结

总之, SQL 注入是一种常见的网络安全攻击方式,对数据库的安全构成严重威胁。为了防范 SQL 注入攻击,我们需要加强用户输入验证、使用参数化查询、存储过程等安全措施,并定期更新和修补数据库管理系统和应用程序。只有这样,我们才能确保数据库的安全,保护用户的数据隐私和业务安全。

Web安全之使用 X-Content-Type-Options 首部字段

Web安全之使用 X-Content-Type-Options 首部字段

简介

X-Content-Type-Options 是一个 HTTP 响应头,用于防止浏览器对响应内容的 MIME 类型进行嗅探或猜测。当设置为 nosniff 时,它告诉浏览器应该严格按照响应头中 Content-Type 字段所指定的类型来处理资源,而不应该根据文件扩展名、文件内容或其他因素来尝试重新确定资源的类型。

作用

使用 X-Content-Type-Options 可以提高网站的安全性,因为它可以防止某些类型的攻击,例如 MIME 类型混淆攻击。这种攻击通常涉及将恶意内容伪装成合法的资源类型(如 JavaScript 文件),然后利用浏览器对 MIME 类型的错误处理来执行恶意代码,能够有效的预防 XSS 攻击。

案例

要在你的 web 服务器上设置 X-Content-Type-Options 响应头,你需要根据你的服务器软件(如 Apache、Nginx、IIS 等)进行配置。下面是一些常见的服务器配置示例:

Apache

在 Apache 中,你可以使用 mod_headers 模块来设置响应头。在你的网站配置文件中添加以下行:

1
Header set X-Content-Type-Options "nosniff"

Nginx

在 Nginx 中,你可以在 http、server 或 location 块中添加 add_header 指令来设置响应头:

1
add_header X-Content-Type-Options "nosniff";

IIS

在 IIS 中,你可以使用 web.config 文件来设置响应头。在 <system.webServer> 部分添加以下配置:

1
2
3
4
5
<httpProtocol>
<customHeaders>
<add name="X-Content-Type-Options" value="nosniff" />
</customHeaders>
</httpProtocol>

Node.js (使用 Express)

如果你使用 Node.jsExpress 框架,你可以在应用程序中添加中间件来设置响应头:

1
2
3
4
app.use((req, res, next) => {
res.setHeader('X-Content-Type-Options', 'nosniff');
next();
});

请确保你的服务器配置正确,并且 X-Content-Type-Options 头部已正确设置。这样,浏览器在接收响应时就会遵守该头部的指示,从而提高应用程序的安全性。

Web安全之 CSRF 攻击

Web安全之 CSRF 攻击

简介

CSRF是跨站请求伪造(Cross-Site Request Forgery)的缩写。跨站请求伪造是一种网络攻击方式,攻击者利用已登录的用户在浏览器中存储的合法会话信息,伪造用户发出的请求,以达到欺骗服务器的目的。这种攻击方式并不涉及窃取用户的会话信息,而是利用会话信息。攻击者通常会在用户毫不知情的情况下,在用户的浏览器上执行恶意操作,如更改密码、发布信息、购买商品等。

攻击原理

CSRF的攻击原理基于Web的隐式身份验证机制。在用户访问Web应用时,浏览器会自动携带用户的会话凭证(如Cookie),以便服务器能够识别用户的身份。攻击者通过构造一个恶意网站或链接,诱使用户访问该网站或点击链接,从而触发一个针对目标网站的伪造请求。由于浏览器会自动携带用户的会话凭证,目标网站无法区分正常请求和伪造请求,因此会执行伪造请求中的操作。

攻击目标

CSRF的攻击目标主要是需要用户身份认证的操作,如修改用户信息、更改密码、发表评论、进行转账等。攻击者希望利用用户的身份执行这些操作,以达到窃取数据、篡改内容、转移资金等目的。

案例

  1. 受害者登录 a.com,并保留了登录凭证(Cookie)
  2. 攻击者引诱受害者访问了b.com
  3. b.com 向 a.com 发送了一个请求:a.com/act=xx浏览器会默认携带a.com的Cookie
  4. a.com接收到请求后,对请求进行验证,并确认是受害者的凭证,误以为是受害者自己发送的请求
  5. a.com以受害者的名义执行了act=xx
  6. 攻击完成,攻击者在受害者不知情的情况下,冒充受害者,让a.com执行了自己定义的操作

例如: 攻击者构造一个伪造的银行转账请求,诱导受害者点击后,该请求会携带受害者的cookie信息,并向正常站点发起请求,从而完成非法转账。或者攻击者设置了一个自动转发邮件的规则,使得受害者的所有邮件都被自动转发到攻击者的邮箱中。

预防方案

CSRF通常从第三方网站发起,被攻击的网站无法防止攻击发生,只能通过增强自己网站针对CSRF的防护能力来提升安全性。

  1. 同源检测:通过 Header 中的 Origin HeaderReferrer Header 确定,但不同浏览器可能会有不一样的实现,不能完全保证

  2. CSRF Token 校验:将 CSRF Token输出到页面中(通常保存在 Session 中),页面提交的请求携带这个 Token ,服务器验证 Token 是否正确

  3. 双重cookie验证

    • 流程:
      • 步骤1:在用户访问网站页面时,向请求域名注入一个Cookie,内容为随机字符串(例如csrfcookie=v8g9e4ksfhw
      • 步骤2:在前端向后端发起请求时,取出Cookie,并添加到URL的参数中(接上例POST https://www.a.com/comment?csrfcookie=v8g9e4ksfhw
      • 步骤3:后端接口验证Cookie中的字段与URL参数中的字段是否一致,不一致则拒绝。
    • 优点:
      • 无需使用Session,适用面更广,易于实施。
      • Token储存于客户端中,不会给服务器带来压力。
      • 相对于Token,实施成本更低,可以在前后端统一拦截校验,而不需要一个个接口和页面添加。
    • 缺点:
      • Cookie中增加了额外的字段。
      • 如果有其他漏洞(例如XSS),攻击者可以注入Cookie,那么该防御方式失效。
      • 难以做到子域名的隔离。
      • 为了确保Cookie传输安全,采用这种防御方式的最好确保用整站HTTPS的方式,如果还没切HTTPS的使用这种方式也会有风险。
  4. SameSite Cookie属性CookieSameSite属性是一种安全策略,用于限制第三方网站获取Cookie的能力,从而增强网站的安全性。SameSite属性可以设置三个值,分别是:

    • Strict:完全禁止第三方获取Cookie,只有在当前网页的URL与请求目标一致时,才会带上Cookie。这种设置可能会对用户体验造成一定的影响,例如,在跳转到其他网站时,可能会因为未携带Cookie而导致登录状态丢失。
    • Lax:比Strict宽松一些,允许在跨站使用GET请求时携带Cookie。例如,在通过链接进行页面跳转时,会带上Cookie,但在通过表单提交数据时,不会带上Cookie
    • None:允许在任何情况下都携带Cookie。但是,为了安全起见,当设置为None时,必须同时设置Secure属性,即Cookie只能通过HTTPS协议发送,否则设置将无效。
  5. 验证码:在关键操作或敏感操作前,要求用户输入验证码。这样即使攻击者能够构造伪造请求,也无法自动执行操作,因为需要用户手动输入验证码。

总结

CSRF是一种严重的网络威胁,对于需要用户身份认证的操作尤其危险。为了保障用户数据的安全性和隐私保护,开发人员在设计和开发网络应用时,应该充分考虑CSRF攻击的防御措施,并采取多种手段提高系统的安全性。同时,用户也应该保持警惕,避免点击不明来源的链接或访问可疑网站,以防止CSRF攻击的发生。

Web安全之 XSS 攻击

Web安全之 XSS 攻击

简介

XSS,全称跨站脚本攻击(Cross-Site Scripting),是一种网络安全漏洞攻击,指攻击者在网页中嵌入恶意脚本,当其他用户浏览该网页时,恶意脚本就会在其浏览器上执行,从而达到攻击者窃取用户信息、破坏数据、篡改网页内容、在用户浏览器上执行非法任务等目的。

分类

XSS攻击分为三种类型:

  • 反射型XSS(Reflected XSS):攻击者将恶意脚本嵌入到URL地址中,当其他用户访问这个URL时,恶意脚本就会在其浏览器中执行。这种攻击方式需要用户主动点击含有恶意脚本的链接才会触发。

  • 存储型XSS(Stored XSS):攻击者将恶意脚本存储到被攻击的网站数据库中,当其他用户访问网站时,恶意脚本会从数据库中取出并在用户浏览器中执行。这种攻击方式不需要用户主动点击链接,只要用户浏览被攻击的网站就可能被攻击。

  • DOM型XSS(DOM-based XSS):攻击者通过修改页面的DOM结构,注入恶意脚本,当其他用户浏览该页面时,恶意脚本会在用户浏览器中执行。这种攻击方式也不需要用户主动点击链接,只需要用户浏览被修改的页面就可能被攻击。

案例

反射型XSS

Node.js中,一个反射型XSS攻击的案例可能涉及一个web应用,该应用没有正确地处理或转义用户输入的数据,并将其直接插入到HTML响应中。攻击者可以构造一个包含恶意脚本的URL,当其他用户访问这个URL时,恶意脚本会在用户的浏览器中执行。

以下是一个简单的Node.js反射型XSS攻击的案例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
const express = require('express');
const app = express();
app.use(express.static('public'));

app.get('/profile', (req, res) => {
// 假设用户可以通过URL参数传递他们的名字
const username = req.query.username;

// 没有对用户输入进行任何处理或转义
const html = `<html>
<body>
<h1>Welcome, ${username}!</h1>
</body>
</html>`;

res.send(html);
});

const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
console.log(`Server is running on port ${PORT}`);
});

在这个例子中,我们创建了一个简单的Express应用,其中有一个/profile路由,该路由从URL的查询参数中获取用户名(username)。然后,我们将这个用户名嵌入到一个HTML字符串中,并将其作为响应发送回客户端。

攻击者可以构造一个包含恶意脚本的URL,如:

1
http://example.com/profile?username=<script>alert('XSS');</script>

当用户访问这个URL时,浏览器会接收到包含恶意脚本的HTML响应,并执行该脚本,从而触发XSS攻击。

为了防止反射型XSS攻击,开发者应该对用户输入进行适当的过滤和转义。在这个案例中,可以使用如escape-html这样的库来转义HTML特殊字符:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const escapeHtml = require('escape-html');

app.get('/profile', (req, res) => {
const username = req.query.username;

// 转义用户输入中的HTML特殊字符
const escapedUsername = escapeHtml(username);

const html = `<html>
<body>
<h1>Welcome, ${escapedUsername}!</h1>
</body>
</html>`;

res.send(html);
});

通过转义用户输入,恶意脚本将不会被浏览器执行,从而防止了XSS攻击。此外,开发者还应该使用内容安全策略(CSP)等额外安全措施来增强应用的安全性。

Read More

常用的CSS技巧

  1. 网站平滑滚动

<html>元素中添加scroll-behavior: smooth,以实现整个页面的平滑滚动。

1
2
3
html {
scroll-behavior: smooth;
}
  1. 链接的属性选择器

此选择器的目标是具有以“https”开头的 href 属性的链接。

1
2
3
a[href^="https"] {
color: blue;
}
  1. 〜合并兄弟姐妹

选择 <h2> 后面的所有兄弟元素 <p> 元素。

1
2
3
h2 ~ p {
color: blue;
}
  1. :not() 伪类

该选择器将样式应用于不具有“特殊”类的列表项。

1
2
3
li:not(.special) {
font-stlye: italic;
}
  1. 用于响应式排版的视口单位

使用视口单位(vw、vh、vmin、vmax)可以使字体大小响应视口大小。

1
2
3
h1 {
font-size: 5vw;
}
  1. :empty 表示空元素

此选择器定位空的 <p> 元素并隐藏它们。

1
2
3
p:empty {
display: none;
}
  1. 自定义属性(变量)

可以定义和使用自定义属性,以更轻松地设置主题和维护。

1
2
3
4
5
6
7
:root {
--main-color: #3498db;
}

h1 {
color: var(--main-color);
}
  1. 图像控制的Object-fit属性

object-fit 控制如何调整替换元素(如 <img>)的内容大小。

1
2
3
4
5
img {
width: 100px;
height: 100px;
object-fit: cover;
}

Read More

Service Worker:提升网页性能与用户体验的关键技术

Service Worker:提升网页性能与用户体验的关键技术

随着互联网的快速发展,网页应用的复杂性不断提高,对性能和用户体验的要求也日益增强。为了应对这些挑战,浏览器技术也在不断进步,其中 Service Worker 作为一种在浏览器后台独立运行的脚本,成为了优化网页性能、提升用户体验的关键技术。

基本概念

Service Worker 是一种运行在浏览器后台的脚本,它独立于网页内容,不会阻塞页面的渲染。它允许开发人员在浏览器和网络之间设置一个代理服务器,拦截和处理网络请求,实现缓存、推送通知、消息传递等功能。Service Worker 使得网页能够在离线状态下运行,提高页面加载速度,优化用户体验。

caniuse_service_worker

生命周期

Service Worker 的生命周期完全独立于网页。

Service Worker 被注册成功后,它将开始它的生命周期,我们对 Service Worker 的操作一般都是在其生命周期里面进行的。Service Worker 的生命周期分为下面几个状态:installing -> installed -> activating -> activated -> redundant。

f54bdb55c775be0498c6efcd55a73020

  1. installing(安装):这个状态发生在 Service Worker 注册之后,表示开始安装,这个状态会触发 install 事件,一般会在 install 事件的回调里面进行静态资源的离线缓存, 如果这些静态资源缓存失败了,那 Service Worker 安装就会失败,生命周期终止。

  2. installed(安装后):当成功捕获缓存到的资源时,Service Worker 会变为这个状态,当此时没有其他的 Service Worker 线程在工作时,会立即进入激活状态,如果此时有正在工作的 Service Worker 工作线程,则会等待其他的 Service Worker 线程被关闭后才会被激活。可以使用 self.skipWaiting() 方法强制正在等待的 Service Worker 工作线程进入激活状态。

  3. activating(激活):在这个状态下会触发 activate 事件,在 activate 事件的回调中去清理旧版缓存。

  4. activated(激活后):在这个状态下,Service Worker 会取得对整个页面的控制

  5. redundant(废弃状态):这个状态表示一个 Service Worker 的生命周期结束。新版本的 Service Worker 替换了旧版本的 Service Worker 会出现这个状态

常用接口

  • Cache
    表示用于 Request / Response 对象对的存储,作为 Service Worker 生命周期的一部分被缓存。

  • CacheStorage
    表示 Cache 对象的存储。提供一个所有命名缓存的主目录,Service Worker 可以访问并维护名字字符串到 Cache 对象的映射。

  • Client
    表示 service worker client 的作用域。一个 service worker client 可以是浏览器上下文的一个文档,也可以是一个由 active worker 控制的 Shared Worker

  • Clients
    表示一个 Client 对象容器;是访问当前源的活动的 service worker client 的主要途径。

  • ExtendableEvent
    扩展被分发到 ServiceWorkerGlobalScopeinstallactivate 事件时序,作为 service worker 生命周期的一部分。这会确保任何功能型事件(如 FetchEvent)不被分发到 Service Worker,直到它更新了数据库架构、删除过期缓存项等等以后。

  • ExtendableMessageEvent
    Service Worker 触发的 message 事件的时间对象(当 ServiceWorkerGlobalScope 从另一个上下文收到通道消息),延长了此类事件的生命周期。

  • FetchEvent
    传递给 ServiceWorkerGlobalScope.onfetch 处理函数的参数,FetchEvent 代表一个在 Service WorkerServiceWorkerGlobalScope 中分发的请求动作。它包含关于请求和响应的结果信息,并且提供 FetchEvent.respondWith() 方法,这个方法允许我们提供任意的响应返回到控制页面。

  • NavigationPreloadManager
    提供与 Service Worker 一起管理资源预加载的方法。

  • Navigator.serviceWorker
    返回一个 ServiceWorkerContainer 对象,该对象提供对相关 document 的注册、删除、更新以及与 Service Worker 对象通信的访问。

  • NotificationEvent
    传递给 onnotificationclick 处理函数的参数,NotificationEvent 接口代表在 Service WorkerServiceWorkerGlobalScope 中分发的单击事件通知。

  • ServiceWorker
    表示一个 Service Worker。多个浏览的上下文 (例如 pageworker 等等) 都能通过相同的 Service Worker 对象相关联。

  • ServiceWorkerContainer
    提供一个在网络生态中把 service worker 作为一个整体的对象,包括辅助注册,反注册以及更新 service worker,并且访问 service worker 的状态以及他们的注册信息。

  • ServiceWorkerGlobalScope
    表示 service worker 的全局执行上下文。

  • MessageEvent
    表示发送到 ServiceWorkerGlobalScope 的信息。

  • ServiceWorkerRegistration
    表示 service worker 的注册。

  • WindowClient
    表示在浏览器上下文中记录的 service worker 客户端的作用域,被活动的工作者控制。是 Client 对象的特殊类型,包含一些附加的方法和可用的属性。

使用场景

缓存管理

Service Worker 可以拦截和处理网络请求,将资源缓存在本地,实现离线访问和快速加载。开发人员可以根据需求自定义缓存策略,提高网页的加载速度和性能。

下面是一个简单的 Service Worker 缓存管理的例子,用于缓存和提供静态资源。

首先,你需要注册一个 Service Worker:

1
2
3
4
5
6
// 在你的主 JavaScript 文件中,例如 main.js
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('/service-worker.js')
.then(() => console.log('Service Worker 已注册'))
.catch(err => console.error('Service Worker 注册失败:', err));
}

然后,创建一个 service-worker.js 文件,用于定义缓存策略:

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
// service-worker.js

self.addEventListener('install', (event) => {
// Perform install steps
event.waitUntil(
caches.open('my-cache').then((cache) => {
// 打开一个名为 'my-cache' 的缓存
return cache.addAll([
'/',
'/styles/main.css',
'/script/main.js'
// 你可以添加更多需要缓存的资源
]);
})
);
});

self.addEventListener('fetch', (event) => {
event.respondWith(
caches.match(event.request)
.then((response) => {
// Cache hit - return response
if (response) {
return response;
}
return fetch(event.request).then(
(res) => {
// Check if we received a valid response
if (!res || res.status !== 200 || res.type !== 'basic') {
return response;
}
// Important: Clone the response. A response is a stream and
// can only be consumed once.
const responseToCache = res.clone();
caches.open('my-cache').then((cache) => {
// Take the response from the network,
// and put a copy in the cache.
cache.put(event.request, responseToCache);
});
return res;
}
);
})
);
});

这个例子中,当 Service Worker 安装时,它会打开一个名为 'my-cache' 的缓存,并添加一些资源到缓存中。当浏览器请求这些资源时,Service Worker 会首先检查缓存中是否有这些资源。如果有,它会直接从缓存中提供这些资源,而不是从网络上获取。如果缓存中没有这些资源,Service Worker 会从网络上获取它们,然后将它们添加到缓存中,以便下次可以直接从缓存中提供。

注意,为了简化示例,这里并没有处理所有可能的错误情况,也没有考虑缓存的更新和失效策略。在实际应用中,你可能需要根据你的具体需求来定制你的缓存策略。

推送通知

Service Worker 可以接收来自服务器的推送消息,即使网页未打开或处于休眠状态,也能向用户发送通知。这一功能使得开发者能够及时向用户传达重要信息,提高应用的活跃度和用户粘性。

下面是一个使用 Service WorkerPush API 发送推送通知的基本例子。

首先,你需要注册 Service Worker 并订阅推送消息:

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
// main.js

if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('/service-worker.js')
.then(function(registration) {
console.log('Service Worker 注册成功:', registration.scope);

// 检查浏览器是否支持显示通知
if (!('Notification' in window)) {
alert('此浏览器不支持桌面通知');
}

// 检查是否已授予权限
else if (Notification.permission === 'granted') {
// 如果用户已授予权限,我们可以立即注册推送
subscribeUser();
}

// 否则,我们需要询问用户
else if (Notification.permission !== 'denied') {
Notification.requestPermission().then(function (permission) {
if (permission === 'granted') {
subscribeUser();
}
});
}

// 订阅推送通知
function subscribeUser() {
return registration.pushManager.subscribe({
userVisibleOnly: true
})
.then(function(subscription) {
console.log('用户已订阅推送通知:', subscription);

// 将订阅信息发送到服务器
sendSubscriptionToServer(subscription);
})
.catch(function(err) {
console.error('无法订阅推送通知:', err);
});
}

// 将订阅信息发送到服务器
function sendSubscriptionToServer(subscription) {
// TODO: 将 subscription 发送到你的服务器
console.log('将订阅信息发送到服务器:', subscription);
}
})
.catch(function(err) {
console.error('Service Worker 注册失败:', err);
});
}

然后,你需要一个 Service Worker 脚本 (service-worker.js) 来接收和显示推送通知:

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
// service-worker.js

self.addEventListener('push', function(event) {
console.log('收到推送消息:', event);

// 你可能需要从服务器获取一些数据来显示通知
// 这里只是一个简单的示例
var title = '推送通知';
var body = '你收到了一条新消息!';
var icon = '/images/icon.png';

event.waitUntil(
self.registration.showNotification(title, {
body: body,
icon: icon
})
);
});

self.addEventListener('notificationclick', function(event) {
console.log('用户点击了通知:', event);

// 用户点击通知后,你可以执行一些操作,例如打开一个新的页面
event.notification.close();

// 例如,打开一个特定的 URL
event.waitUntil(
clients.openWindow('https://example.com/notification-clicked')
);
});

请注意,为了实际使用推送通知,你还需要一个服务器端的组件来发送推送消息。这通常涉及到使用 Web Push Protocol (Web Push) 与用户的浏览器进行通信。此外,你可能需要配置你的服务器以接收订阅信息,并在需要时发送推送消息。

此外,由于推送通知和 Service Worker 的复杂性,这里提供的代码只是一个起点。在生产环境中,你可能需要处理更多细节,如错误处理、用户权限管理、通知内容的动态生成等。

消息传递

Service Worker 可以与其他浏览器环境(如页面、扩展程序等)进行双向通信,实现消息传递和共享数据。这使得开发者能够在不同环境之间传递信息,实现更复杂的功能和更好的用户体验。

下面是一个简单的 Service Worker 消息传递的例子。

首先,注册并启动 Service Worker

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// main.js

if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('/service-worker.js')
.then(registration => {
console.log('Service Worker 注册成功:', registration.scope);

// 向 Service Worker 发送消息
registration.active.postMessage({ action: 'hello' });

// 监听来自 Service Worker 的消息
navigator.serviceWorker.addEventListener('message', event => {
console.log('收到来自 Service Worker 的消息:', event.data);
if (event.data.action === 'response') {
alert('Service Worker 回应了: ' + event.data.message);
}
});
})
.catch(err => {
console.error('Service Worker 注册失败:', err);
});
}

然后,在 service-worker.js 中处理接收到的消息,并发送响应:

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
// service-worker.js

self.addEventListener('install', (event) => {
event.waitUntil(
caches.open('my-cache').then((cache) => {
// 缓存逻辑
})
);
});

self.addEventListener('activate', (event) => {
// 激活逻辑
});

self.addEventListener('fetch', (event) => {
// 拦截请求和响应的逻辑
});

self.addEventListener('message', (event) => {
console.log('收到来自页面的消息:', event.data);
if (event.data.action === 'hello') {
// 向页面发送回应
event.ports[0].postMessage({ action: 'response', message: '你好,页面!' });
}
});

在这个例子中,当页面加载并成功注册 Service Worker 后,它会向 Service Worker 发送一个包含 action: 'hello' 的消息。Service Worker 监听到这个消息后,会打印出收到的消息内容,并通过 event.ports[0].postMessage 向页面发送一个回应消息,其中包含了 action: 'response' 和一条消息字符串。

页面通过监听 message 事件来接收来自 Service Worker 的回应,并在控制台中打印出消息内容。如果回应消息中的 action'response',页面会弹出一个警告框显示收到的消息。

请注意,postMessage API 允许在 Service Worker 和页面之间传递结构化数据,如对象、数组、字符串等。但是,出于安全考虑,不能直接传递函数、DOM 对象或其他某些类型的数据。此外,event.ports 提供了一个双向通信的通道,允许 Service Worker 和页面之间进行更复杂的交互。

优缺点

优点:

  1. 提高网页性能:通过缓存管理,Service Worker 可以减少网络请求,降低带宽消耗,提高网页加载速度。
  2. 提升用户体验:Service Worker 可以实现离线访问、推送通知等功能,提高用户的满意度和粘性。
  3. 拓展性强:Service Worker 可以与其他浏览器环境进行通信,为开发者提供更多可能性。

缺点:

  1. 兼容性问题:虽然主流浏览器都支持 Service Worker ,但在一些旧版本或非主流浏览器中可能无法使用。
  2. 学习成本高:使用 Service Worker 需要一定的技术储备和学习成本,对于初学者来说可能有一定的难度。

总结

Service Worker 作为一种在浏览器后台运行的脚本技术,为优化网页性能和提升用户体验提供了有力支持。通过缓存管理、推送通知和消息传递等功能,Service Worker 使得网页能够在离线状态下运行,提高加载速度,及时向用户传达重要信息,实现更复杂的功能和更好的用户体验。虽然存在一些兼容性和学习成本的问题,但随着技术的不断发展和普及,相信 Service Worker 将在未来的网页开发中发挥越来越重要的作用。

H5 移动端调试工具汇总

H5 移动端调试工具汇总

一、概要

因为移动端操作系统分为 iOSAndroid 两派,所以本文的调试技巧也会按照不同的系统来区分。寻找最合适高效的方式,才能让你事半功倍。

文章会列举目前适合移动端调试的多种方案,快来选择你的最佳实践吧!

二、iOS 设备

Safari:iphone 调试利器,查错改样式首选,需要我们做如下设置:

  • 浏览器设置:Safari - 偏好设置 - 高级 - 勾选「在菜单栏中显示开发」菜单
  • iphone 设置:设置 - Safari - 高级 - 打开 Web 检查器

大功告成,这时候通过手机的 Safari 来打开 H5 页面,我们通过浏览器开发选项可以看到:

image

iOS 模拟器:不需要真机,适合调试 Webview 和 H5 有频繁交互的功能页面。

首先下载 Xcode ,运行项目,选择模拟器 iphonex,编译后就会打开模拟器,如下:

image

可以看到 H5 已经在「壳子」中运行起来了,下来就可以尝试调用 Webview 的方法,和「壳子」交互了。

具体的调试功能还是依赖浏览器的开发选项,与上无异,就不赘述了。

Read More

使用 git 克隆 github 上的项目失败

现象

今天在使用 git clone nextjs demo project 源代码的时, git clone https://github.com/Weibozzz/next-blog.git 下载速度很慢,然后下载一段时间后,总是提示下面的错误信息

1
2
3
4
5
6
7
8
nCloning into 'next-blog'...
remote: Enumerating objects: 111, done.
remote: Counting objects: 100% (111/111), done.
remote: Compressing objects: 100% (83/83), done.
error: RPC failed; curl 18 transfer closed with outstanding read data remaining
fatal: The remote end hung up unexpectedly
fatal: early EOF
fatal: index-pack failed

原因

由于Http协议错误,当 pull 或者 clone 的时候,或者是 github 某个CDN被伟大的墙屏蔽所致。

解决办法

协议错误

  1. 先执行下列命令

    1
    git config --global http.postBuffer 524288000
  2. 再执行git pull 或者 git clone命令

墙屏蔽

  1. 访问 http://github.global.ssl.fastly.net.ipaddress.com/#ipinfo
    获取cdn域名以及IP地址

  2. 访问 http://github.com.ipaddress.com/#ipinfo 获取cdn域名以及IP地址 cdn域名以及IP地址

  3. 将上述获取的IP地址添加到/etc/hosts

    1
    sudo vim /etc/hosts

    添加IP地址到hosts

  4. 刷新dns缓存

    1
    2
    sudo killall -HUP mDNSResponder
    sudo dscacheutil -flushcache

结果

再执行 git clone 操作的时候,速度飕飕飕的上去了,一下子达到几百Kb啦~

使用Weinre调试移动端Web开发

一、使用Npm全局安装weinre

1
npm -g install weinre

二、引入js文件

1
<script src="http://192.168.225.198:8081/target/target-script-min.js#anonymous"></script>

三、启动 Weinre Debug 服务端

1
weinre --httpPort 8081 --boundHost -all-