一、核心知识点概览
- DOM树操作:掌握JS原生获取/修改DOM元素的方法,理解innerHTML与innerText的差异及DOM-XSS风险;
- JS加密编码库:通过第三方库(如md5.js、crypto-js.js)实现MD5、SHA1、HMAC、AES、DES、RSA加密,明确不同加密场景的适用范围;
- 逆向调试:通过"浏览器控制台比对"和"断点调试"分析JS加密逻辑,解决"加密函数未加载到前端"的问题;
- 安全风险:DOM-XSS漏洞原理与SQL注入语句(如'admin' OR 1=1 -- ')的危害。
二、JS原生开发:DOM树操作与安全隐患
DOM(文档对象模型)是浏览器解析HTML后形成的树状结构,JS通过DOM API实现"获取元素→修改属性/内容→响应用户交互"的完整流程。
(一)DOM核心操作:获取与修改元素
- 步骤1:获取DOM对象(元素)
通过document.querySelector()按"标签名、类名、ID"查找元素,是最灵活的DOM查询方法:
| 选择目标 | 语法格式 | 示例代码 | 说明 |
|---|---|---|---|
| 标签名 | document.querySelector('标签') | const h1 = document.querySelector('h1'); | 获取页面中第一个<h1>元素 |
| 类名 | document.querySelector('.类名') | const box = document.querySelector('.box'); | 获取第一个带class="box"的元素 |
| ID | document.querySelector('#ID名') | const btn = document.querySelector('#btn'); | 获取id="btn"的元素(唯一) |
示例:点击标题触发获取ID属性的逻辑
<h1 id="myHeader" onclick="getValue()">这是标题</h1>
<script>
function getValue() {
// 1. 获取h1元素
const h1 = document.querySelector('h1');
// 2. 获取元素的id属性
const h1Id = h1.id;
// 3. 控制台输出属性值
console.log('h1的ID:', h1Id); // 输出:myHeader
}
</script>
- 步骤2:操作元素数据(内容)
通过innerHTML和innerText修改元素内容,二者核心差异是是否解析HTML标签:
| 方法 | 功能 | 安全风险 | 示例代码 |
|---|---|---|---|
| innerHTML | 读取/设置元素的HTML内容(解析标签) | 高(易引发DOM-XSS) | h1.innerHTML = "这是新标题带换行"; |
| innerText | 读取/设置元素的纯文本内容(不解析标签) | 低(自动转义HTML) | h1.innerText = "这是新标题带换行"; |
示例对比:
<!-- innerHTML:解析<br>标签,显示换行 -->
<h1 onclick="updateByInnerHTML()">点击用innerHTML修改</h1>
<!-- innerText:不解析<br>,直接显示文本 -->
<h1 onclick="updateByInnerText()">点击用innerText修改</h1>
<script>
function updateByInnerHTML() {
const h1 = document.querySelector('h1');
h1.innerHTML = "修改后(innerHTML)<br>换行生效";
}
function updateByInnerText() {
const h1 = document.querySelector('h1:nth-child(2)');
h1.innerText = "修改后(innerText)<br>换行不生效";
}
</script>
- 步骤3:操作元素属性(如src、href)
直接通过"元素.属性名"修改标签属性(如图片src、链接href),常用于动态切换资源。
示例:点击切换图片
<img src="iphone.jpg" width="300" height="300" onclick="switchImg()">
<script>
function switchImg() {
// 获取img元素
const img = document.querySelector('img');
// 修改src属性,切换为华为图片
img.src = 'huawei.png';
// 控制台输出新的src
console.log('新图片路径:', img.src);
}
</script>
(二)安全隐患:DOM-XSS漏洞
- 漏洞原理
当用户可控的数据(如URL参数、表单输入)通过innerHTML、document.write等API直接插入页面时,攻击者可注入恶意JS脚本(如<script>alert(document.cookie)</script>),窃取Cookie或伪造操作。
-
漏洞示例
<script> // 假设content来自URL参数(用户可控) const content = getUrlParam('content'); // 自定义函数:从URL提取参数 const div = document.querySelector('#content'); div.innerHTML = content; // 未过滤,直接插入 </script>
- 攻击方式:访问http://xxx.com/?content=\<img src=x οnerrοr="alert(document.cookie)">,页面会执行alert脚本,窃取Cookie;
- 关键风险点:content是用户可控数据,且通过innerHTML解析HTML标签。
- 防御建议
- 优先使用innerText替代innerHTML(自动转义HTML标签);
- 若必须用innerHTML,需过滤用户输入的危险标签(如<script>、<img onerror>)和特殊字符(如<→<);
- 限制用户输入的内容范围(如仅允许字母、数字)。
三、JS导入库开发:加密编码实现
JS原生不支持复杂加密,需通过第三方库实现MD5、SHA1、HMAC、AES、DES、RSA等加密,核心是"引入库→调用API→处理结果"。
(一)常见加密方式及实现
- MD5(哈希加密,不可逆)
-
适用场景:密码存储(非明文)、文件校验;
-
依赖库:md5.js(需下载并引入);
-
示例代码:
<!-- 引入md5库 --> <script src="js/md5.js"></script> <script> const plainText = 'xiaodi jichu No1'; // 明文 const md5Encrypt = md5(plainText); // 调用md5函数加密 console.log('MD5加密结果:', md5Encrypt); // 输出:afe5119ec0ab46b55432fc5e24f1dc62 </script>
- SHA1(哈希加密,不可逆)
-
适用场景:数据完整性校验;
-
依赖库:crypto-js.js(功能更全面,支持多种加密);
-
示例代码:
<script src="js/crypto-js.js"></script> <script> const plainText = 'xiaodisec'; // CryptoJS.SHA1(明文).toString() 转为十六进制字符串 const sha1Encrypt = CryptoJS.SHA1(plainText).toString(); console.log('SHA1加密结果:', sha1Encrypt); // 输出:ce22eaa1c5ebd3dfb3f4474b66f6d3612d4cb3ee </script>
- HMAC(带密钥的哈希加密,不可逆)
-
适用场景:接口签名(防止数据篡改);
-
核心特点:需传入"密钥",同一明文不同密钥加密结果不同;
-
示例代码:
<script src="js/crypto-js.js"></script> <script> const key = 'key123'; // 密钥(前后端需一致) const plainText = 'xiaodisec'; // HMAC-SHA256加密:CryptoJS.HmacSHA256(密钥, 明文) const hmacHash = CryptoJS.HmacSHA256(key, plainText); const hmacEncrypt = CryptoJS.enc.Hex.stringify(hmacHash); // 转为十六进制 console.log('HMAC加密结果:', hmacEncrypt); // 输出:08ac6dc8773bd34dcadeffb2b90a8b8f5be9453a9dce7cf09d4da2fcb363d9e7 </script>
- AES(对称加密,可逆,密钥长度8/16/32位)
-
适用场景:敏感数据传输(如用户信息);
-
核心特点:加密和解密用同一密钥,需指定mode(如ECB)和padding(如Pkcs7);
-
示例代码:
<script src="js/crypto-js.js"></script> <script> const key = '12345678'; // 密钥(长度8位) const plainText = 'xiaodisec'; // 明文 // 1. AES加密 const aesEncrypt = CryptoJS.AES.encrypt( plainText, CryptoJS.enc.Utf8.parse(key), // 密钥转为Utf8格式 { mode: CryptoJS.mode.ECB, padding: CryptoJS.pad.Pkcs7 } ).toString(); // 转为字符串 // 2. AES解密 const aesDecrypt = CryptoJS.AES.decrypt( aesEncrypt, CryptoJS.enc.Utf8.parse(key), { mode: CryptoJS.mode.ECB, padding: CryptoJS.pad.Pkcs7 } ).toString(CryptoJS.enc.Utf8); // 解密后转为Utf8字符串 console.log('AES加密结果:', aesEncrypt); // 输出:g4ohopaiYA34XXLsV92Udw== console.log('AES解密结果:', aesDecrypt); // 输出:xiaodisec </script>
- DES(对称加密,可逆,密钥长度8位)
-
适用场景:简单敏感数据加密;
-
实现逻辑:与AES类似,仅需将CryptoJS.AES改为CryptoJS.DES;
-
示例代码(核心部分):
// DES加密 const desEncrypt = CryptoJS.DES.encrypt( plainText, CryptoJS.enc.Utf8.parse(key), { mode: CryptoJS.mode.ECB, padding: CryptoJS.pad.Pkcs7 } ).toString(); console.log('DES加密结果:', desEncrypt); // 输出:WVSwdlodMcV2n1FH72uXgw==
- RSA(非对称加密,可逆,公钥加密+私钥解密)
-
适用场景:密钥传输(如对称加密的密钥);
-
依赖库:jsencrypt.js;
-
核心特点:公钥公开(用于加密),私钥保密(用于解密);
-
示例代码:
<script src="js/jsencrypt.js"></script> <script> // 1. 定义公钥和私钥(实际场景需通过工具生成) const publicKey = '-----BEGIN PUBLIC KEY-----MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBALyBJ6kZ/VFJYTV3vOC07jqWIqgyvHulv6us/8wzlSBqQ2+eOTX7s5zKfXY40yZWDoCaIGk+tP/sc0D6dQzjaxECAwEAAQ==-----END PUBLIC KEY-----'; const privateKey = '-----BEGIN PRIVATE KEY-----MIIBVQIBADANBgkqhkiG9w0BAQEFAASCAT8wggE7AgEAAkEAvIEnqRn9UUlhNXe84LTuOpYiqDK8e6W/q6z/zDOVIGpDb545NfuznMp9djjTJlYOgJogaT60/+xzQPp1DONrEQIDAQABAkEAu7DFsqQEDDnKJpiwYfUE9ySiIWNTNLJWZDN/Bu2dYIV4DO2A5aHZfMe48rga5BkoWq2LALlY3tqsOFTe3M6yoQIhAOSfSAU3H6jIOnlEiZabUrVGqiFLCb5Ut3Jz9NN+5p59AiEA0xQDMrxWBBJ9BYq6RRY4pXwa/MthX/8Hy+3GnvNw/yUCIG/3Ee578KVYakq5pih8KSVeVjO37C2qj60d3Ok3XPqBAiEAqGPvxTsAuBDz0kcBIPqASGzArumljkrLsoHHkakOfU0CIDuhxKQwHlXFDO79ppYAPcVO3bph672qGD84YUaHF+pQ-----END PRIVATE KEY-----'; // 2. 公钥加密 const encrypt = new JSEncrypt(); encrypt.setPublicKey(publicKey); // 设置公钥 const plainText = 'xiaodisec'; const rsaEncrypt = encrypt.encrypt(plainText); // 加密 console.log('RSA加密结果:', rsaEncrypt); // 输出:Fw1H5KoC6zZnwAzLee8z5ubmQYSqaVqu711VI+NBavYT9bkWpzxUtZHmbSUvLbuCblPO96NdfoQHtPe9TURo6A== // 3. 私钥解密 const decrypt = new JSEncrypt(); decrypt.setPrivateKey(privateKey); // 设置私钥 const rsaDecrypt = decrypt.decrypt(rsaEncrypt); // 解密 console.log('RSA解密结果:', rsaDecrypt); // 输出:xiaodisec </script>
(二)加密方式选型建议
| 加密类型 | 可逆性 | 密钥特点 | 适用场景 |
|---|---|---|---|
| MD5/SHA1 | 不可逆 | 无密钥 | 密码存储、文件校验 |
| HMAC | 不可逆 | 需密钥 | 接口签名(防篡改) |
| AES/DES | 可逆 | 对称密钥(同一) | 敏感数据传输(如用户信息) |
| RSA | 可逆 | 非对称密钥(公/私) | 密钥传输(如AES密钥) |
四、逆向调试:分析JS加密逻辑
当网站未将加密函数完全加载到前端(如encrypt未定义)时,需通过"浏览器控制台比对"或"断点调试"逆向分析加密逻辑。
(一)场景1:浏览器控制台比对(加密函数已加载)
以"小迪渗透吧登录页"为例,分析密码加密方式:
- 步骤1:定位密码输入框
打开登录页→右键"检查"→找到密码输入框的id(如edtPassWord); - 步骤2:搜索加密相关代码
在"检查"的"Sources"面板搜索("#btnPost").click(登录按钮的点击事件),发现密码加密逻辑:("#password").val(MD5(strPassWord));,推测是MD5加密; - 步骤3:控制台验证
在浏览器控制台输入MD5('xiaodi'),得到加密结果bb1be44c4f8e615aeba54e9d233c23b6;
点击登录,抓包查看提交的密码参数,若与控制台结果一致,确认是MD5加密。
(二)场景2:断点调试(加密函数未加载到前端)
以"申通快递会员登录页"为例,解决encrypt is not defined问题:
- 步骤1:定位加密代码
找到密码输入框id(如numPassword),搜索加密逻辑,发现logindata.Password = encodeURI(encrypt.encrypt(numPassword));,但控制台输入encrypt提示"未定义"(函数仅在服务器端执行,未加载到前端); - 步骤2:打断点
在"Sources"面板找到加密代码所在行,点击行号打断点; - 步骤3:触发断点并调试
输入账号密码→点击登录,程序暂停在断点处;此时encrypt已被服务器加载,在控制台输入encodeURI(encrypt.encrypt(numPassword)),即可获取加密后的密码; - 步骤4:篡改数据(测试)
将numPassword改为SQL注入语句(如'admin' OR 1=1 -- '),执行encodeURI(encrypt.encrypt(numPassword)),得到加密后的注入语句,提交后可尝试绕过登录。
五、补充:SQL注入语句解析('admin' OR 1=1 -- ')
1. 语句含义
- 'admin':伪造用户名;
- OR 1=1:逻辑"或",1=1恒为真,使整个条件恒为真;
- -- ':MySQL注释符,将后面的SQL语句(如AND password='xxx')注释掉,避免语法错误。
2. 攻击原理
假设原SQL语句为:
SELECT * FROM users WHERE username = '$username' AND password = '$password'
当username='admin' OR 1=1 -- '时,SQL语句变为:
SELECT * FROM users WHERE username = 'admin' OR 1=1 -- ' AND password = '$password'
- 注释后,AND password=...被忽略,条件username='admin' OR 1=1恒为真,会查询所有用户数据,实现登录绕过。
六、核心总结
- DOM操作安全:避免用innerHTML处理用户输入,优先用innerText,防御DOM-XSS;
- 加密选型:哈希加密(MD5/SHA1)用于非敏感校验,对称加密(AES/DES)用于数据传输,非对称加密(RSA)用于密钥传输;
- 逆向调试技巧:加密函数已加载用"控制台比对",未加载用"断点调试",核心是定位关键加密代码和变量;
- 注入防御:后端需对用户输入的SQL参数做预编译(如PDO),避免拼接SQL语句。