网上有各种防止 xss 和 sql 注入的方案,前端的也有,后端的也有。各种方案太多太杂,看的我头疼。
我的策略是,前端负责非法字符的过滤,转换,显示,让过滤后的安全数据入库,以及能安全的显示给用户看。而后端处理非法字符,就很简单。一旦检测到非法字符就直接返回 404,403,400 等错误码。
那么问题来了,先不考虑富文本,就那种普通的文本框,假设每个用户用浏览器操作,在输入框里输入的内容中,都可能带有 sql 注入,xss 攻击所用的代码的一段文字或文章例,前端如何负责这些带有非法字符的过滤转换?让数据能安全的入库,然后在数据显示时,又能显示用户输入时的样子,又不出问题呢。
1
ipwx 2020-04-24 15:03:41 +08:00 1
首先,sql 注入很好解决。用 prepared statement 就行。而且 prepared statement 比 sql 直接塞值更高效。另外,大部分 ORM 都能有效利用各种机制防止注入。
其次,对于 XSS 。如果你不在后端检查,那么攻击者总能构造出让你能入库并原样返回给前端的内容。除非你前端是在显示内容的最后一步之前进行处理,在提交的时候过滤是无效的。当然,如果你前端在最后一步显示之前处理 XSS 的内容,那么后端可能不需要任何检查,也不需要什么 404 之类的状态吗。结合第一条的解决方案,正常入库出库就行。 但是对于用户体验而言,每次显示前处理 XSS 无疑会占用 CPU 资源。还不如你后端入库进行处理呢。 |
2
lneoi 2020-04-24 15:05:22 +08:00
前后端都得做处理
前端有一个库,刚搜了下,这是官网 https://jsxss.com |
4
MOETAN0 2020-04-24 15:17:03 +08:00 1
后端必需处理。
xss 攻击者可不会老老实实在你的输入框里填脚本,构造一个 post 请求就可以绕开在 js 上的处理。 |
5
est 2020-04-24 15:17:40 +08:00
一劳永逸很简单啊。把所有用户输入过滤成 [a-zA-Z0-9]。一次过滤不成过滤 2 次 。
|
6
dilu 2020-04-24 15:22:28 +08:00
sql 注入就不说了 1l 方案完全没问题
至于 xss php 有内置函数,直接转换所有 html 标签为实体标签,也可以直接去掉全部 html 标签 一句话,php 专为 web 而生 香 |
7
Leon6868 2020-04-24 15:23:06 +08:00 1
前端基本防不住
你要后端处理才行 |
8
tctc4869 OP @MOETAN0 我的后端不是不处理,而是处理比较简单粗暴,在后端,把检测到带有的非法字符的请求返回个 404,403 之类的错误码,把别有用心的构造请求的攻击者挡在外面。
|
9
tctc4869 OP @Leon6868 看清楚啊,我并不是说后端不处理,只是前端已经过滤了,就代表已经是安全的了,没有必要在后端再来一次过滤,而后端处理就比较简单,不用搞过滤等操作,把检测到非法字符的请求返回个如 404 错误或无用的信息给攻击者。
|
10
moonlord 2020-04-24 15:29:03 +08:00 9
SQL 注入的一劳永逸,就是 不硬拼 SQL,只用 prepared statement,参数和语句分离开
XSS 注入的一劳永逸,就是 不操作 html,只操作 txt 认为 用户输入、后端返回 的数据必须是文本,而不是网页的一部分 也就是不用 JS 的 innerHTML, jQuery 的 html() 只用 JS 的 innerText (IE) 和 textContent (Firefox), jQuery 的 text(),毛的注入都不存在了 其余所有什么 检验、转码 都是辣鸡方法,过时的,误导新人的 我就想用<script>当名字,你就给我显示<script>就完了,不要扯别的,瞎折腾 =。= https://segmentfault.com/q/1010000004067521 http://www.moonlord.cn |
12
msg7086 2020-04-24 15:32:36 +08:00
用 ORM 的我已经很多年没有考虑过注入问题了。你随便构造数据,我什么都不过滤,能注入算我输。
|
13
napsterwu 2020-04-24 15:34:27 +08:00 via iPhone
xss 一劳永逸就是加入 csp 报文头,并且拒绝用户使用 ie
|
15
tctc4869 OP @gz911122 我知道,我其实想说的是,后端搞过滤是多此一举,后端不要搞什么过滤,只搞检查同步通不通过就行,过滤转码交给前端就行,直接构造请求的攻击者,肯定不会乖乖用浏览器,这些,直接交给后端用拦截器拦下来返回错误码即可,这是我的策略。
|
20
whoami9894 2020-04-24 15:49:13 +08:00 2
XSS 的话,一般情况下对所有符号(`<>"&#`)进行 HTML encode 基本就没问题了,比如 PHP 的 htmlspecialchars 函数。
不过总有一些特殊场景,还是需要开发者有足够经验,比如标签内的输出点:`<a href="javascript:\u0061\u006c\u0065\u0072\u0074(1);">click</a>` 当然 CSP 也是好办法,但一样有不少绕过方式。国内外 SRC 上经常有大厂被挖出 XSS,想一劳永逸解决?不存在的 |
21
moonlord 2020-04-24 15:50:47 +08:00
@tctc4869
仔细想想,你 ”用拦截器拦下来返回错误码“ 是不是也是多此一举? 我就想用<script>当名字,你就给我前端显示<script>就完了,瞎鸡儿检验转码报错,你要干啥? 你是看了多少辣鸡教程被误导了? |
22
whoami9894 2020-04-24 15:51:54 +08:00
@whoami9894 #20
可以看看我这段回复 V 站是怎么过滤的,因为是标签外输出点,所以直接 HTMLencode 就完了 |
23
moonlord 2020-04-24 15:53:24 +08:00
|
24
whoami9894 2020-04-24 15:55:38 +08:00
@moonlord
众人皆醉你独醒? |
25
moonlord 2020-04-24 15:58:15 +08:00
|
26
neoblackcap 2020-04-24 16:00:23 +08:00
@whoami9894 网站不注意动态内容生成是那样的,要不简单粗暴实现一个留言板,会有 xss 吗?用户内容只输出成文本,一点问题都不会有的
|
27
jiantao 2020-04-24 16:00:38 +08:00
没有
|
28
fancy111 2020-04-24 16:01:07 +08:00 1
@moonlord 就你这技术还开博客,直接给你一段代码打你脸。
你是说只需要用 innerText 就可以防是吧? <html> <body> <p id="test">hello world </p> <script> var content = document.getElementById("test"); content.innerText='</script><script>alert(11);</script>' </script> </body> </html> 看清楚了,别告诉我你看不懂。 |
31
fancy111 2020-04-24 16:03:54 +08:00
@whoami9894 没办法,我出面打他脸。
|
32
moonlord 2020-04-24 16:07:23 +08:00
@fancy111
你都给爷整笑了! 你写了个 HTML,里面就是 <script>alert(11);</script>,完了弹出来了,所以呢? 你在逗我吗? 浏览器都报错了 Uncaught SyntaxError: Invalid or unexpected token 你这根本都不符合 JS 语法,你是来搞笑的吗? 来我对齐给你: <html> <body> <p id="test">hello world </p> <script> var content = document.getElementById("test"); content.innerText=' </script> <script> alert(11); </script>' </script> </body> </html> |
34
whoami9894 2020-04-24 16:09:21 +08:00
@fancy111
虽然我很想挺你,但你这个例子确实不对 浏览器解析 HTML 时会先解析 HTML,然后才是 JS 。也就是说你这段代码里`content.innerText='</script><script>alert(11);</script>'`的第一个`</script>`在 HTML 解析阶段被当做 script 闭合标签了 所以这段代码被交给 JS 引擎解析时是这样的 <script> var content = document.getElementById("test"); content.innerText=' </script> <script> alert(11); </script> |
36
wooyuntest 2020-04-24 16:11:21 +08:00
不能,后端必须参与处理。其实无论是前端还是后端,接收到数据后都需要进行必要的校验才能到下一步流程,包括后端从数据库里取出来的数据,都要校验、过滤、处理后才能进到下一步流程,否则会引起“二次注入”等问题。
总结下来就是:DO NOT Trust, Always Verify. |
37
fancy111 2020-04-24 16:12:00 +08:00
@whoami9894 你真是傻。 不报错的我已经说了,另外玩 XSS 或者其他注入,不用考虑什么报错,报错能运行就是最好的。 一看你们就是小白,搞过黑客吗? 唉。。。。 累
|
38
whoami9894 2020-04-24 16:15:09 +08:00
@fancy111 他的意思是前端 JS 操纵数据时用 innerText,你这种情况是后端直接渲染 HTML,所以实际不能反驳他对于前端操纵数据的说法
@moonlord 使用 innerText 等做法都是在前后端分离,前端 JS 操纵数据的前提下,后端直接渲染 HTML 返回你直接避过不提?没什么好杠的 @neoblackcap 确实。我的意思的是总有一些复杂的业务场景,所以想寻求一劳永逸的解决方式是不太现实的 |
39
moonlord 2020-04-24 16:16:37 +08:00
@wooyuntest @fancy111
一知半解太可怕了 你难道没注意到我写的 <script>alert("菜鸡不要说话");</script> 现在在 v2ex,就是原封不动显示的吗? 还在说什么检验、编码、过滤,我服了。 看看 10 楼,好吗,亲? |
40
whoami9894 2020-04-24 16:17:44 +08:00
|
41
fancy111 2020-04-24 16:18:38 +08:00
@whoami9894 说实话,给你们解释我很累。。。
你数据是 json 的吧? text 是'</script><script>alert(11);</script> ,你把这个用 innerText 显示出来,不是注入了吗? 还他的意思是前端 JS 操作数据。。。没后端处理你能操作啥啊? |
42
moonlord 2020-04-24 16:18:50 +08:00
@whoami9894
9102 年都过了,现在 2020 年了,我这里反正只有前后端分离的项目 |
44
moonlord 2020-04-24 16:20:15 +08:00
@fancy111
你可以试试你说的 “你把这个用 innerText 显示出来,不是注入了吗”,根本不会弹出来 我写的 <script>alert("菜鸡不要说话");</script> 在这里,现在就是 innerText 显示出来的,弹了吗? |
45
fancy111 2020-04-24 16:22:36 +08:00
@moonlord 我已经不想说了,不喜欢 BB 。你连网页基本流程都不懂,我给你扯什么啊。你写的<script>是因为 v2 后端过滤的结果,不是只靠前端懂吗? 不回了
|
46
wget 2020-04-24 16:22:54 +08:00 2
不建议前端过滤,针对正常用户没必要,针对攻击者没有用,顺带还可能会暴露后端的过滤规则
实际可用的拦截方案都很复杂,姓名手机号之类的正则校验即可,但是复杂的富文本中,' " < > ? 之类攻击常用字符都有本身的含义,无脑拦截会造成误判 |
47
skyRival 2020-04-24 16:24:19 +08:00
@moonlord 兄弟,大家都是程序员,说话别那么冲呗。我站你这边,我用的 React,从没有做过 XSS 过滤(因为我坚决抵制了 dangerousSetInnerHTML )。刚刚用 @whoami9894 这位兄弟的代码试过了,没有注入。
|
48
moonlord 2020-04-24 16:25:06 +08:00
@fancy111
后端过滤? Chrome 审查元素会吗?看看是 innerText 还是转码过滤了?球球你了,不信我的,你自己试试好么? |
49
skyRival 2020-04-24 16:26:22 +08:00
如果有道友有意见我都在线测试。
|
50
SilentDepth 2020-04-24 16:31:55 +08:00
不就是个字符转义的事儿,怎么看你们讨论得那么热闹……
|
51
fancy111 2020-04-24 16:32:50 +08:00
@moonlord
@whoami9894 @skyRival 反正我现在没事,我就一起给你们上一课好了。 你们说前端做了过滤,像 V2 一样,你知道 POST 是可以直接绕过 html 端发送的吗? 现在我们的回复根本不需要打字到这里点击回复,我可以直接用其他方式发送,那你的 JS 还有什么用?不靠后端的话,前端就会直接显示我上面代码的内容。 要是还不懂,建议自己搭建网站试试,靠纯前端,我给你们测试。 |
52
murmur 2020-04-24 16:34:19 +08:00
@fancy111
@TangMonk 冷静一下,这兄弟说话有问题,但是道理是可取的,他的思路核心就是将需求缩窄,限制用户的操作,功能越多 bug 越多这是真理 我举一些例子,任天堂的游戏交流只能使用内置的一些,比入你好、你真棒,官方可以对这些话进行本地化,即解决了沟通问题,又解决了监督问题,大家都只能说你好,自然不用做关键字过滤,相比之下 lol 这种祖安文化就是开放的例子 很多国内游戏 id 必须用中文,因为英文奇奇怪怪的词汇太多,不能用英语交流就省去维护英语关键字的成本 如果你让用户写一段签名,那么这段签名要尽可能短,几十个字,就一行,而且是纯文字,而不是像以前的论坛可以用 ubb code,甚至是一些 html 语句进行美化。 dangerousSetInnerHTML,这是真的危险 |
53
moonlord 2020-04-24 16:34:24 +08:00
|
54
skyRival 2020-04-24 16:34:59 +08:00 via Android
@fancy111 你说的我们都知道啊,然后呢?我不管前端发送的什么,只要渲染的时候不触发就算是防御成功了呗。
|
56
skyRival 2020-04-24 16:37:19 +08:00 via Android
@fancy111 我对测试和运维确实不擅长,我这刚好有一个空的项目,你说怎么测试我在线测试,如果有哪个我没防住,OK,我马上给我的后端加 xss 过滤。
|
57
SilentDepth 2020-04-24 16:37:38 +08:00
@moonlord #10 楼主貌似没提到具体需求?禁止 innerHTML 的话,我需要渲染用户自定义 HTML 怎么办?
|
58
fancy111 2020-04-24 16:38:05 +08:00
@skyRival 看我上面回复的,渲染他说只用 innerText 就行,那肯定是不行的。 另外如果你前端渲染时做其他过滤,我也有办法绕过过滤规则,我不信你们写的代码能有多完美。 尽管给我贴代码,少 BB
|
64
fancy111 2020-04-24 16:42:52 +08:00
@skyRival 那你直接写个页面就行,只要你靠前端防的。 三个功能,输入框提交、后端只存数据库、前端你自己做过滤后调用渲染。 接下来我给你演示
|
65
SilentDepth 2020-04-24 16:43:26 +08:00
你们在争什么呢……V2EX 的页面是后端渲染的吧,那不转义字符可咋办
|
66
moonlord 2020-04-24 16:43:53 +08:00
|
67
ooQAQoo 2020-04-24 16:43:57 +08:00
这楼歪的厉害
|
69
cjc2017 2020-04-24 16:44:36 +08:00
<script>alert(1)</script>
|
70
moonlord 2020-04-24 16:44:56 +08:00
|
72
SilentDepth 2020-04-24 16:47:14 +08:00
@moonlord #70 10 楼的内容我看了啊,所以我在问如果有渲染用户 HTML 要怎么办。以及,大概我们对转义逻辑的理解不同,没说到一件事情上。
|
74
lzuntalented 2020-04-24 16:50:59 +08:00
@moonlord 富文本的处理场景你没考虑,不是所有的输出都是当做字符使用的,富文本中图片链接标签等都存在的
|
75
skyRival 2020-04-24 16:51:19 +08:00
@fancy111 你可真逗兄弟,我一摸鱼的哪有功夫去部署,内网穿透我现在也没条件。你就上代码,我这边跑起来就 OK,大家都可以看看。
|
77
skyRival 2020-04-24 16:55:04 +08:00
@SilentDepth @lzuntalented 我之前做的网站没有渲染富文本的需求,这个我不太确定,我的想法是转成 markdown,然后在前端渲染 markdown 。
|
78
SilentDepth 2020-04-24 16:56:06 +08:00
@moonlord
我概括一下你在 #10 的观点:对于用户输入的内容,一律作为文本节点处理,不要创建 HTML 元素。 首先,没错,这样是可以防止 XSS 。如果业务需求上本不需要用户提供 HTML 结构,这么做没问题。但就是有用户直接提供 HTML 的需求场景啊,这个时候你的这个办法是行不通的。最简单的做法就是 tag name 白名单 + 转义字符(或过滤),我不认为这个办法是「错误」的。 另外,对于后端渲染页面的场景(例如 V2EX,如果我没猜错的话),你要怎么把用户内容精确控制到文本节点内?还是得转义啊。 所以你们到底在争什么…… |
79
skyRival 2020-04-24 16:57:45 +08:00
@SilentDepth 我觉得应该是在争需不需要后端过滤来防御 XSS 的问题。
|
80
SilentDepth 2020-04-24 17:00:51 +08:00
@skyRival #77 我这儿 V2EX plus 插件貌似 bug 了,解析不出会话,我就不手动追溯你们的对话了。如果允许的富文本类型是有限的,转成 Markdown 再渲染出来是可行的。如果不是(例如允许用户插入 Gist 组件),要么做复杂的格式转换(高开发成本但更可控),要么直出 HTML 然后走传统的 XSS 处理(低开发成本但难可控)。
|
81
zdhxiong 2020-04-24 17:02:14 +08:00
|
83
moonlord 2020-04-24 17:11:07 +08:00
|
84
ikaros1997 2020-04-24 17:13:09 +08:00
<img onerror="alert(111)"/>
|
85
SilentDepth 2020-04-24 17:15:17 +08:00
@moonlord #83 一劳永逸也得看需求啊。「如何一劳永逸地解决人口太多粮食不够吃的问题」,难道你的方案是别生孩子吗😓。本来我也觉得这个问题不至于讨论成这样……
另外,结合上面那位朋友提供的 API 来看,V2EX 压根儿也没限制你输入什么,谈不上「只允许你输入文本」。 |
86
moonlord 2020-04-24 17:17:18 +08:00
@SilentDepth
要想一劳永逸,那可不就是不生孩子嘛! |
87
SilentDepth 2020-04-24 17:20:05 +08:00
@moonlord #86 大兄弟,你的思想很危险……😏
|
88
u823tg 2020-04-24 17:22:01 +08:00
问哈站长回复为啥不用富文本编辑器。-_-
|
89
SilentDepth 2020-04-24 17:27:32 +08:00
@u823tg #88 赌五毛是因为懒(
|
90
mtrec 2020-04-24 17:31:07 +08:00 via Android
@SilentDepth 就是你输入任何内容都当成是文本 没毛病 那老哥是冲了点 但是对的
|
91
u823tg 2020-04-24 17:32:22 +08:00
绝对的安全没可能,带套了还有那么 1%的可能怀孕,哈哈!!
|
92
Leon6868 2020-04-24 17:32:23 +08:00
@tctc4869 我的意思是,如果出现 xss 或者 sql 注入,那很可能是比较高级的攻击者进行攻击。如果你是用 post 或者 ajax 传入后端,他们可以再构造一个包,绕过你前端的检测。其实后端检测到后报告 404 或 403 就可以了。
10 楼的答案已经很好了,可以参考一下 |
93
Leon6868 2020-04-24 17:35:32 +08:00
@moonlord #21 我想知道你是前端还是后端还是产品经理。。。你去用工作台看看 v2 的"<>",看看是不是被转码了。你再用 postman 发一个没被转码过的包,看看是否会被转码,再回来好吗?。。
|
95
SilentDepth 2020-04-24 17:40:18 +08:00
@mtrec #90 是的,我没有说 #10 的做法是错的,我只是在说那个方案可行的前提是不允许用户输入包含 HTML 内容,但实际业务中不全是这种情况。想要「一劳永逸」就需要解决本质问题,而 XSS 的本质问题就是意外执行了用户提供的 脚本内容,那么把可能会被执行的内容转换成不可执行的普通文本就是最直接的做法。
|
96
moonlord 2020-04-24 17:43:46 +08:00
|
97
Leon6868 2020-04-24 17:44:14 +08:00
@moonlord 确实,innerText 是可以避免的,确实是我的问题,学习了。不过 sql 注入一般是数据库操作,不就要返回服务端吗?但是返回服务端就需要通过请求啊,这些包是可以中途截下来的。(当然你可以用 token 来防止,但是攻击者可以修改你的代码,然后在按照自己的想法把包发去除嘛)
|
98
tctc4869 OP @Leon6868 我的策略是,数据转码,交给前端做。后端只负责"是不是有,有就别想进入";后端做请求过滤,那是多此一举。
|
99
skyRival 2020-04-24 17:52:03 +08:00 via Android
@ikaros1997 防住了。
|