前端安全 XSS 跨站脚本攻击(全称Cross Site Scripting,为和CSS(层叠样式表)区分,简称为XSS)是指恶意攻击者在Web页面中插入恶意javascript代码(也可能包含html代码),当用户浏览网页之时,嵌入其中Web里面的javascript代码会被执行,从而达到恶意攻击用户的目的。XSS是攻击客户端,最终受害者是用户,当然,网站管理员也是用户之一。
XSS漏洞通常是通过php的输出函数(echo)将javascript代码输出到html页面中,通过用户本地浏览器执行的,所以xss漏洞关键就是寻找参数未过滤的输出函数。
XSS有三类:反射型XSS(非持久型)、存储型XSS(持久型)和DOM XSS
防御:
XSS 来源于用户提供的内容,只要过滤掉其中的输入的恶意代码即可。
不信任用户的输入;编码;
CSRF 跨站请求伪造
1)原理是: 浏览器机制,用户访问一个url就会带上对应域名的cookie,这就方便了CSRF;此时cookie有效;
2)get和post攻击:
1、get攻击:若是服务器接受get请求
用户登录银行网站后,用户没有退出网站,就点击恶意网站图片或隐藏的iframe,然后src为https://bank.com/zhuanzhang?to=111&fee=222,这样恶意网站伪造的请求就携带银行cookie请求成功了;
2、post攻击
构造一个点击按钮,触发js提交post;例如隐藏一个iframe,target指向隐藏iframe,填入input内容,js触发submit,诱导用户点击按钮,提交请求,例如关闭广告按钮等等
3)防御 :
使用完后退出正常网站登录;
refer校验,对请求来源网址校验;
增加手机验证码;
token防御:每次生成随机字符串,设置隐藏,提交的时候会带上token,服务端校验token是否正确,伪造的网站是不会拿到token的
加密
RSA非对称加密、md5加密
DDoS 介绍 DDoS的全称是“Distributed Denial of Service”,即“分布式拒绝服务”。
其目标是通过同时向目标服务器发送大量请求,以超过其处理能力,导致服务器无法正常工作或被拒绝服务
DDoS攻击通常不是由单个攻击者控制的,而是由多个被感染的计算机(也称为“僵尸”)组成的僵尸网络协同发起。这些僵尸计算机可以是恶意软件感染的受害者,攻击者通过操控它们来进行攻击。
DDoS攻击可分为几种类型,包括以下几种常见形式:
SYN Flood:攻击者发送大量的TCP连接请求(SYN包)到目标服务器,但不响应服务器的确认请求(ACK包),耗尽服务器的连接资源。 UDP Flood:攻击者发送大量的UDP数据包到目标服务器的随机端口,以消耗服务器的带宽和处理能力。 ICMP Flood:攻击者发送大量的ICMP Echo请求(ping)到目标服务器,用于消耗服务器的带宽和处理能力。 HTTP Flood:攻击者发送大量的HTTP请求到目标服务器,使其无法处理正常用户的请求。 DNS Amplification:攻击者利用被感染的DNS服务器向目标服务器发送大量的DNS响应数据包,占用目标服务器的带宽和资源。 防御措施 使用防火墙和负载均衡器:配置防火墙和负载均衡器来过滤和分散流量,以减轻服务器的负担。这可以帮助识别和阻止来自恶意源的流量。 使用反向代理:通过使用反向代理服务器,可以将请求转发到后端服务器,并且可以根据各种标准检测和过滤恶意流量。一些反向代理服务器提供了强大的防护功能,例如 Nginx 和 Cloudflare。 限制连接和请求速率:通过设置连接和请求速率限制,可以减轻服务器的负载并抵御大规模连接和请求。这可以通过使用服务器软件、中间件或专门的防护设备来实现。rateLimit、加签、黑名单。 使用 CDN(内容分发网络):将内容部署到 CDN,并启用其防护功能。CDN 可以帮助缓存和分发内容,同时过滤和拦截恶意流量。它可以在源服务器之前分担流量负载,并通过全球分布来防止攻击直接冲击服务器。 使用验证码或人机验证:在网站或应用程序的关键交互点(如登录、注册、表单提交等)增加验证码或人机验证。这可以有效识别和防止自动化攻击脚本。 使用 IP 黑名单和白名单:根据恶意 IP 地址的历史记录或通过实时监测,构建 IP 黑名单,并将其阻止访问服务器。另一方面,可以使用 IP 白名单来限制只有信任的 IP 地址可以访问服务器。 使用专业的 DDoS 防护服务:对于高度敏感的网络应用程序,可以考虑使用专业的 DDoS 防护服务,这些服务拥有强大的硬件和软件设备来检测和过滤 DDoS 攻击流量。 请注意,没有单一的解决方案可以完全防止 DDoS 攻击,因为攻击者的技术和手段不断演进。最好的做法是采取多层防御策略,结合不同的措施来减轻攻击带来的影响,以保护网络和服务器的安全
AES对称加密 对称加密:如AES,DES,3DES
含义:加密和解密使用的是同一把钥匙。密钥不能在网络中传输,避免被拦截。如果要传输,必须要对密钥进行非对称加密再加密一次。
优点:算法简单,加密解密容易,效率高,执行快。
缺点:相对来说不算特别安全,只有一把钥匙,密文如果被拦截,且密钥也被劫持,那么,信息很容易被破译。
非对称加密:如RSA DSA RCC
含义:有两个钥匙,及公钥(Public Key)和私钥(Private Key)。公钥和私钥是成对的存在,如果对原文使用公钥加密,则只能使用对应的私钥才能解密。通过私钥经过一系列算法是可以推导出公钥的,但是无法通过公钥反向推倒出私钥,这个过程的单向的。
优点:安全,即使密文被拦截、公钥被获取,但是无法获取到私钥,也就无法破译密文。作为接收方,务必要保管好自己的密钥。
缺点:加密算法及其复杂,安全性依赖算法与密钥,而且加密和解密效率很低。
AES:AES(高级加密标准)是一种对称加密算法,即加密和解密使用相同的密钥。它可以加密长度为128、192和256位的数据块,并使用128位的密钥进行加密。AES算法使用了固定的块长度和密钥长度,并且被广泛应用于许多安全协议和标准中,例如SSL/TLS、SSH、IPSec等。
https://cryptojs.gitbook.io/docs/#ciphers
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 import CryptoJS from 'crypto-js' function getAesString (data, key, iv ) { let keys = CryptoJS .enc .Utf8 .parse (key) let vis = CryptoJS .enc .Utf8 .parse (iv) let encrypt = CryptoJS .AES .encrypt (data, keys, { iv : vis, mode : CryptoJS .mode .CBC , padding : CryptoJS .pad .Pkcs7 }); return encrypt.toString (); } function getDAesString (encrypted, key, iv ) { var key = CryptoJS .enc .Utf8 .parse (key); var iv = CryptoJS .enc .Utf8 .parse (iv); var decrypted =CryptoJS .AES .decrypt (encrypted,key,{ iv :iv, mode :CryptoJS .mode .CBC , padding :CryptoJS .pad .Pkcs7 }); return decrypted.toString (CryptoJS .enc .Utf8 ); } const aes = { en : (data, key ) => getAesString (data, key.key , key.iv ), de : (data, key ) => getDAesString (data, key.key , key.iv ) }; const base64 = { en : (data ) => CryptoJS .enc .Base64 .stringify (CryptoJS .enc .Utf8 .parse (data)), de : (data ) => CryptoJS .enc .Base64 .parse (data).toString (CryptoJS .enc .Utf8 ) }; const sha256 = (data ) => { return CryptoJS .SHA256 (data).toString (); }; const md5 = (data ) => { return CryptoJS .MD5 (data).toString (); }; export { aes, md5, sha256, base64 };
Encoders
CryptoJS can convert from encoding formats such as Base64, Latin1 or Hex to WordArray objects and vice-versa.
1 2 var words = CryptoJS .enc .Base64 .parse ("SGVsbG8sIFdvcmxkIQ==" );var base64 = CryptoJS .enc .Base64 .stringify (words);
避免前端请求明文传输 使用HTTPS协议:通过使用HTTPS协议,所有的数据都会在传输过程中进行加密。这样可以确保请求和响应的内容在传输过程中不会被窃听或篡改。 使用POST方法传递敏感数据:POST方法将请求的数据放在请求体中,而不是放在URL中,这样可以避免敏感数据在URL中暴露。 使用请求头中的Authorization字段传递凭证:当需要传递用户凭证或身份认证信息时,可以将这些敏感信息放在请求头的Authorization字段中,而不是作为URL或请求体中的参数。 对数据进行加密:如果敏感数据必须在请求中传递,可以在前端对数据进行加密,然后在后端进行解密。这样可以确保即使在数据传输过程中被窃听,也无法轻易获取到敏感信息。 避免在URL参数中传递敏感数据:URL参数通常会被浏览器缓存、服务器日志等保存下来,因此避免将敏感数据放在URL参数中传递。 使用HTTP响应头中的Content-Security-Policy字段:Content-Security-Policy(CSP)是一项安全政策,它可以配置浏览器只能加载指定来源或类型的资源。通过配置CSP,可以防止恶意的脚本或资源被加载,从而提高安全性。 对敏感数据进行一次性使用或使用token验证:对于一些特别敏感的数据,可以采取一次性使用或使用token机制,在每次请求时生成一个临时的、只能使用一次或有时效性的凭证,来确保受限数据的安全传输。 在前端进行数据验证和过滤:前端应该对用户输入的数据进行验证和过滤,在提交请求之前进行必要的数据清理以防止恶意数据或攻击载荷被发送到后端。 综上所述,通过使用HTTPS协议、POST方法传递数据、加密敏感信息、避免在URL参数中传递敏感数据等措施,可以有效避免前端请求明文传输的安全隐患,保护用户的敏感信息。
签名 双方约定一种加密校验方式,以保证数据的完整性和安全
请求签名验证是一种验证请求完整性和身份验证的方法,通常用于确保请求在传输过程中没有被篡改,并且请求来自于合法的发送方。
请求签名验证的一般步骤:
签名生成: 发送请求的客户端在发送请求之前,会根据事先约定好的签名算法(如HMAC、RSA等)以及密钥对请求参数进行签名处理。签名处理的结果会作为请求的一部分发送到服务器。请求发送: 客户端发送带有签名的请求到服务器。签名可以作为请求头、请求参数或请求体的一部分发送到服务器。验证签名: 服务器接收到请求后,会根据事先约定好的签名算法以及密钥对请求参数进行签名验证。服务器会重新计算请求参数的签名,然后将计算得到的签名和请求中的签名进行比较。比较签名: 服务器会将计算得到的签名和请求中的签名进行比较。如果两者一致,则说明请求参数没有被篡改,且请求来自于合法的发送方;否则,说明请求可能被篡改或来自于非法发送方,服务器可以拒绝该请求或采取其他适当的处理措施。响应处理(可选): 如果请求签名验证通过,服务器会处理请求,并生成相应的响应返回给客户端。如果请求签名验证不通过,服务器可以返回相应的错误信息或拒绝请求。通过请求签名验证,可以确保请求在传输过程中的完整性和可靠性,防止数据被篡改或伪造请求。这种方法经常用于对 API 请求进行验证,保护 API 服务的安全和稳定。
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 import md5 from 'js-md5' ;export default { random (max, min ) { return Math .round (Math .random () * (max - min) + min); }, randomStr (len ) { let str = '' ; for (let i = 0 ; i < len; i++) { str += String .fromCharCode (this .random (97 , 122 )); } return str; }, getToken (api, signKey ) { let key = '?' ; if (api.indexOf ('?' ) > -1 ) { key = '&' ; } let e = this .getUtcTime (); let s = this .randomStr (10 ); let token = `${api} ${key} e=${e} &s=${s} &pk=${signKey} ` ; token = md5 (token); return { token, e, s }; }, getUtcTime ( ) { let len = (new Date ()).getTime (); let utcTime = parseInt ((len) / 1000 ) + 1800 ; return utcTime; }, signUrl (api, SIGNATURE_KEY ) { let tokens = this .getToken (api, SIGNATURE_KEY ); let { e, token, s } = tokens; let key = '?' ; if (api.indexOf ('?' ) > -1 ) { key = '&' ; } const res = api + `${key} e=${e} &s=${s} &tk=${token} ` ; return res; }, md5 (str ) { return md5 (str); } }
服务端生成临时签名 服务端生成
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 const express = require ("express" );const { Buffer } = require ("buffer" );const OSS = require ("ali-oss" );const app = express ();const path = require ("path" );const config = { accessKeyId : process.env .ALIBABA_CLOUD_ACCESS_KEY_ID , accessKeySecret : process.env .ALIBABA_CLOUD_ACCESS_KEY_SECRET , bucket : "<YOUR-BUCKET>" , dir : "prefix/" , }; app.use (express.static (path.join (__dirname, "templates" ))); app.get ("/get_post_signature_for_oss_upload" , async (req, res) => { const client = new OSS (config); const date = new Date (); date.setSeconds (date.getSeconds () + 3600 ); const policy = { expiration : date.toISOString (), conditions : [ ["content-length-range" , 0 , 1048576000 ], { bucket : client.options .bucket }, ], }; const formData = await client.calculatePostSignature (policy); const host = `http://${config.bucket} .${ (await client.getBucketLocation()).location } .aliyuncs.com` .toString (); const params = { policy : formData.policy , signature : formData.Signature , ossAccessKeyId : formData.OSSAccessKeyId , host, dir : config.dir , }; res.json (params); }); app.get (/^(.+)*\.(html|js)$/i , async (req, res) => { res.sendFile (path.join (__dirname, "./templates" , req.originalUrl )); }); app.listen (8000 , () => { console .log ("http://127.0.0.1:8000" ); });
客户端使用
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 <!DOCTYPE html > <html lang ="zh-CN" > <head > <meta charset ="UTF-8" > <meta name ="viewport" content ="width=device-width, initial-scale=1.0" > <title > 上传文件到OSS</title > </head > <body > <div class ="container" > <form > <div class ="mb-3" > <label for ="file" class ="form-label" > 选择文件</label > <input type ="file" class ="form-control" id ="file" name ="file" required > </div > <button type ="submit" class ="btn btn-primary" > 上传</button > </form > </div > <script type ="text/javascript" > const form = document .querySelector ('form' ); const fileInput = document .querySelector ('#file' ); form.addEventListener ('submit' , (event ) => { event.preventDefault (); let file = fileInput.files [0 ]; let filename = fileInput.files [0 ].name ; fetch ('/get_post_signature_for_oss_upload' , { method : 'GET' }) .then (response => response.json ()) .then (data => { const formData = new FormData (); formData.append ('name' ,filename); formData.append ('policy' , data.policy ); formData.append ('OSSAccessKeyId' , data.ossAccessKeyId ); formData.append ('success_action_status' , '200' ); formData.append ('signature' , data.signature ); formData.append ('key' , data.dir + filename); formData.append ('file' , file); fetch (data.host , { method : 'POST' , body : formData},).then ((res ) => { console .log (res); alert ('文件已上传' ); }); }) .catch (error => { console .log ('Error occurred while getting OSS upload parameters:' , error); }); }); </script > </body > </html >