打造双剑合璧的 XSS 前端防火墙

2015/09/30 · HTML5 ·
XSS

原文出处: 林子杰(@Zack__lin)   

图片 1

双剑合璧

前言

深入接触 xss 注入是从排查业务的广告注入开始,以前对 xss
注入片面认为是页面输入的安全校验漏洞导致一系列的问题,通过对 zjcqoo
的《XSS 前端防火墙》系列文章,认识到自己其实对 XSS
注入的认识还真是半桶水。

文 ∕ 郭子民

2016-10-1 16:39

上一篇介绍的系统,虽然能防御简单的内联XSS
代码,但想绕过还是很容易的。

关于XSS漏洞怎样形成、如何注入、能做什么、如何防范,前人已有无数的探讨,这里就不再累述了。本文介绍的则是另一种预防思路。

捣蛋的运营商

由于 xss 注入的范围太广,本文仅对网关劫持这一方面的 XSS 注入进行讨论。
这里读者有个小小的疑问,为什么我要选网关劫持进行讨论?因为网关劫持可以大面积范围进行有效控制。

曾经,有这样一道风靡前端的面试题(当然我也现场笔试过):当你在浏览器地址栏输入一个URL后回车,将会发生的事情?其实本文不关心请求发到服务端的具体过程,但是我关心的时,服务端响应输出的文档,可能会在哪些环节被注入广告?手机、路由器网关、网络代理,还有一级运营商网关等等。所以,无论如何,任何网页都得经过运营商网关,而且最调(zui)皮(da)捣(e)蛋(ji)的,就是通过运营商网关。

另外,
也提醒大家,如果手机安装了一些上网加速软件、网络代理软件或设置网络代理
IP,会有安全风险,也包括公共场所/商家的免费 WIFI。

常绕口令,不得痴呆!100首,您能念出几条?

由于是在前端防护,策略配置都能在源代码里找到,因此很快就能试出破解方案。并且攻击者可以屏蔽日志接口,在自己电脑上永不发出报警信息,保证测试时不会被发现。

几乎每篇谈论 XSS
的文章,结尾多少都会提到如何防止,然而大多万变不离其宗。要转义什么,要过滤什么,不要忘了什么之类的。尽管都是众所周知的道理,但
XSS
漏洞十几年来几乎从未中断过,不乏一些大网站也时常爆出,小网站更是家常便饭。

前端防火墙的实践

经过近一段时间通过对 zjcqoo 的《XSS
前端防火墙》六板斧的反复琢磨理解,基本上防御措施可以归为两大类:一种是从协议上屏蔽,一种是从前端代码层面进行拦截移除。通过
zjcqoo
提出的几种注入防御方式,进行几个月的实践观察,对广告注入方式大概可以归为两种:完全静态注入、先静态注入后动态修改(创建)。

  1. 完全静态注入
    完全内联 js、css、和 dom,不管是 body
    内外,甚是恶心,而且如果是在监控脚本前面注入的,还可以抢先执行,造成防御不起作用。注入的
    DOM 也无法清除。
  2. 先静态注入后动态修改
    这种可以分为几种:一种是异步请求接口数据再生成 DOM 注入,一种是修改
    iframe 源地址进行引入,另外一种是修改 script 源地址,请求执行 js
    再异步获取数据或生成 DOM。

我本写诗人,来从金庸游

2016-07-02 中老年时报

昨天提到最简单并且最常见的XSS代码,就是加载站外的一个脚本文件。对于这种情况,关键字扫描就无能为力了,因为代码可以混淆的千变万化,我们看不出任何异常,只能将其放行。

预警系统

监控数据观察分析

对 zjcqoo
提出的几种防御方式的实践,前一个月主要是花在优化检测脚本和增加白名单过滤脏数据方面,因为这块事情只能利用业余时间来搞,所以拖的时间有点久。白名单这块的确是比较繁琐,很多人以为分析下已知的域名就
ok 了,其实不然,云龙在这篇 iframe
黑魔法就提到移动端 Native 与 web
的通信机制,所以在各种 APP 上,会有各种 iframe
的注入,而且是各种五花八门的协议地址,也包括 chrome。

监控拿到的数据很多,但是,由于对整个广告注入黑产行业的不熟悉,所以,有必要借助
google
进行查找研究,发现,运营商大大地狡猾,他们自己只会注入自己业务的广告,如
4G
免费换卡/送流量/送话费,但是商业广告这块蛋糕他们会拱手让人?答案是不可能,他们会勾结其他广告代理公司,利用他们的广告分发平台(运营商被美名为广告系统平台提供商)进行广告投放然后分成…

对于用户投诉,他们一般都是认错,然后对这个用户加白名单,但是他们对其他用户还是继续作恶。对于企业方面的投诉,如果影响到他们的域名,如果你没有确凿的证据,他们就会用各种借口摆脱自己的责任,如用户手机中毒等等,如果你有确凿的证据,还得是他们运营商自己的域名或者
IP,否则他们也无法处理。他们还是一样的借口,用户手机中毒等等。

除非你把运营商的域名或 IP
监控数据列给他看,他才转变态度认错,但是这仅仅也是之前我们提到的流量话费广告,对于第三方广告代理商的广告,还是没法解决,这些第三方广告代理商有广告家、花生米、XX
传媒等等中小型广告商,当然也不排除,有的是“个体户广告商”。

从另一方面来看,由于使用的是古老的 http 协议,这种明文传输的协议,html
内容可以被运营商一清二楚地记录下来,页面关键字、访问时间、地域等用户标签都可以进行采集,说到这,你可能已经明白了一个事(隐私侵犯已经见怪不怪了)——大数据分析+个性化推荐,在
google 一查,运营商还真有部署类似于 iPush
网络广告定向直投这样的系统,而且广告点击率也出奇的高,不排除会定向推送一些偏黄色的图片或游戏。

另外,数据分析中发现一些百度统计的接口请求,也在一些 js
样本中发现百度统计地址,猜测很有可能是这种广告平台利用百度统计系统做数据分析,如定向投放用户
PV 统计,广告效果统计等等。
监控数据分析也扯这么多了,我们还是回来看怎么做防御措施吧!

01、 完美的武功

1.捉兔——一位爷爷他姓顾,上街打醋又买布。买了布,打了醋,回头看见鹰抓兔。放下布,搁下醋,上前去追鹰和兔,飞了鹰,跑了兔。打翻醋,醋湿布。

因此,我们还需增加一套可疑模块跟踪系统。

事实上,至今仍未有一劳永逸的解决方案,要避免它依旧使用最古老的土办法,逐个的过滤。然而人总有疏忽的时候,每当产品迭代更新时,难免会遗漏一些新字段,导致漏洞被引入。

防御措施介绍

《神雕》里最让人激动的一幕,就是一对缠缠绵绵的小情侣,双剑合璧,打败大魔王。

2.小猪——小猪扛锄头,吭哧吭哧走。小鸟唱枝头,小猪扭头瞅,锄头撞石头,石头砸猪头。小猪怨锄头,锄头怨猪头。

被动扫描

即使圣人千虑也有一失,程序出 BUG
完全可以理解,及时修复就行。但令人费解的是,问题出现到被发现,却要经过相当长的时间。例如不久前贴吧
XSS
蠕虫脚本,直到大规模爆发后经用户举报,最终才得知。其他网站大多也类似,直到白帽子们挖掘出漏洞,提交到安全平台上,最终厂商才被告知。若遇到黑客私下留着这些漏洞慢慢利用,那只能听天由命了。

全站 HTTPS + HSTS

开启 HTTPS,可以加强数据保密性、完整性、和身份校验,而 HSTS (全称 HTTP
Strict Transport Security)可以保证浏览器在很长时间里都会只用 HTTPS
访问站点,这是该防御方式的优点。但是,缺点和缺陷也不可忽略。

互联网全站HTTPS的时代已经到来 一文已有详细的分析,加密解密的性能损耗在服务端的损耗和网络交互的损耗,但是移动端浏览器和
webview 的兼容性支持却是个问题,比如 Android webview
需要固件4.4以上才支持,iOS safari 8 以上也才支持,而 UC
浏览器目前还不支持。

而目前推动团队所有业务支持 HTTPS 难度也是相当高,部分 302
重定向也有可能存在 SSLStrip,更何况 UC
浏览器还不支持这个协议,很容易通过 SSLStrip
进行劫持利用,虽然运营商大部分情况下不会这么干,但是我还是坚定怀疑他们的节操。由于我国宽带网络的基本国情,短时间指望速度提升基本上不可能的,就算总理一句话,但哪个运营商不想赚钱?所以,业务性能的下降和业务安全,需要进行权衡利弊。

金轮法王一败涂地,凭自己五绝级的武功见识,要找出破绽,克制的办法,最后得出的结论是:没有破绽,完美的武功。

3.白石塔——白石白又滑,搬来白石搭白塔。白石塔,白石塔,白石搭石塔,白塔白石搭。搭好白石塔,白塔白又滑。

和之前说的一样,最简单的办法仍是遍历扫描。我们可以定时分析页面里的脚本元素,发现有站外地址的脚本就发送预警日志。

因此,要是能有一套实时的预警系统,那就更好了。即使无法阻止漏洞的发生,但能在漏洞触发的第一时间里,通知开发人员,即可在最短的时间里修复,将损失降到最低。各式各样的应用层防火墙,也由此产生。

Content Security Policy(简称 CSP)

CSP
内容安全策略,属于一种浏览器安全策略,以可信白名单作机制,来限制网站中是否可以包含某来源内容。兼容性支持同样是个问题,比如
Android webview 需要固件4.4以上才支持,iOS safari 6 以上支持,幸运的是
UC 浏览器目前支持 1.0
策略版本,具体可以到 CANIUSE 了解。目前对
CSP 的使用仅有不到两周的经验而已,下面简单说说其优缺点。

缺点:

  1. CSP
    规范也比较累赘,每种类型需要重新配置一份,默认配置不能继承,只能替换,这样会导致整个
    header 内容会大大增加。
  2. 如果业务中有爬虫是抓取了外部图片的话,那么 img
    配置要么需要枚举各种域名,要么就信任所有域名。
    1. 移动端 web app 页面,如果有存在 Native 与 web 的通信,那么 iframe
      配置只能信任所有域名和协议了。
    1. 一些业务场景导致无法排除内联 script 的情况,所以只能开启
      unsafe-inline
    1. 一些库仍在使用 eval,所以避免误伤,也只能开启 unsafe-eval
    1. 由于 iframe 信任所有域名和协议,而 unsafe-inline
      开启,使得整个防御效果大大降低

优点:

  1. 通过 connect/script 配置,我们可以控制哪些
    外部域名异步请求可以发出,这无疑是大大的福音,即使内联 script
    被注入,异步请求仍然发不出,这样一来,除非攻击者把所有的 js
    都内联进来,否则注入的功能也运行不了,也无法统计效果如何。
  2. 通过 reportUri 可以统计到攻击类型和
    PV,只不过这个接口的设计不能自定义,上报的内容大部分都是鸡肋。
  3. object/media
    配置可以屏蔽一些外部多媒体的加载,不过这对于视频播放类的业务,也会误伤到。
  4. 目前 UC 浏览器 Android 版本的客户端和 web 端通信机制都是采用标准的
    addJavascriptInterface 注入方式,而 iPhone 版本已将 iframe
    通信方式改成 ajax 方式(与页面同域,10.5
    全部改造完成),如果是只依赖 UC
    浏览器的业务,可以大胆放心使用,如果是需要依赖于第三方平台,建议先开启
    reportOnly,将一些本地协议加入白名单,再完全开启防御。

总的来说吧,单靠 CSP
单打独斗显然是不行,即使完全开启所有策略,也不能完成消除注入攻击,但是作为纵深防御体系中的一道封锁防线,价值也是相当有用的。

之后,见了“双剑合璧”,就腿软。

4.花鸭与彩霞——水中映着彩霞,水面游着花鸭。霞是五彩霞,鸭是麻花鸭。麻花鸭游进五彩霞,五彩霞网住麻花鸭。乐坏了鸭,拍碎了霞,分不清是鸭还是霞。

如果昨天说的内联事件使用定时扫描,或许还能在触发前拦截一部分,但对于脚本则完全不可能了。脚本元素一旦被挂载到主节点之下,就立即加载并执行了。除非定时器开的特别短,能在脚本加载的过程中将其销毁,或许还能拦截,否则一不留神就错过了。

不过,和传统的系统漏洞不同,XSS
最终是在用户页面中触发的。因此,我们不妨尝试使用前端的思路,进行在线防御。

前端防火墙拦截

前端防火墙显然适合作为第一道防线进行设计,可以预先对一些注入的内联 js
代码、script/iframe 源引用进行移除,同时对 script/iframe
源地址修改做监控移除。
基本设计逻辑大概如下:

图片 2

详细的实现逻辑,参考zjcqoo 的《XSS 前端防火墙》系列文章。

缺点:

  1. 如果是在监控脚本执行前,注入的脚本已经执行,显然后知后觉无法起防御作用了。
  2. 一些 DOM 的注入显然无能为力。

优点:

  1. 可以针对 iframe 做一些自定义的过滤规则,防止对本地通信误伤。
  2. 可以收集到一些注入行为数据进行分析。

待到小龙女学会“左右互搏”,左手全真,右手玉女,不光金轮法王怂了,最高战绩,还要加上潇湘子、尹克西、尼摩星、全真五子,九大高手围攻(心不齐),武功真是高到没边。

5.四和十——四和十,十和四,十四和四十,四十和十四。说好四和十得靠舌头和牙齿:谁说四十是“细席”,他的舌头没用力;谁说十四是“适时”,他的舌头没伸直。认真学,常练习,十四、四十、四十四。

我们得寻找更高端的浏览器接口,能在元素创建或添加时,进行分析和拦截。

DOM 储存型 XSS

双剑合璧

即使是单纯的 DOM
注入,显然无法满足更高级功能的使用,也会使运营商的广告分发平台效果大打折扣。如果单独其中一种方式进行使用,也只是发挥了一招一式的半成功力,如果是双手互搏,那也可以发挥成倍的功力。

而前端防火墙再加上 CSP
安全策略,双剑合璧,则可以大大降低广告注入带来的负面效果,重则造成广告代码严重瘫痪无法运行:在监控脚本后注入广告脚本,基本上可以被前端防火墙封杀殆尽,即使有漏网之鱼,也会被
CSP 进行追杀,不死也残。

即使在监控脚本运行前注入,通过 CSP content-src
策略,可以拦截白名单域名列表外的接口请求,使得广告代码的异步请求能力被封杀,script-src
策略,也可以封杀脚本外链的一些外部请求,进一步封杀异步脚本引用,frame-src
策略无论先后创建的 iframe,一律照杀。

侥幸者躲过了初一,却躲不过十五,前端防火墙拍马赶到,照样封杀无误,唯一的路径只有注入
DOM 这一方式,别忘了,只要开启 img-src
策略配置,广告代码只剩下文字链。虽然是一个文字链广告,但点击率又能高到哪去呢?

如果你是 node
派系,小弟附上《辟邪剑谱》 helmet 一本,如果你的业务有涉及到
UCBrowser,更有《辟邪剑谱之 UC
版》helmet-csp-uc 。

所谓道高一尺魔高一丈,既然我们有高效的防御措施,相信他们不久也会探索出反防御方式,如此,我们也需要和这帮人斗智斗勇,一直等到
HTTP/2 规范的正式落地。

1 赞 3 收藏
评论

图片 3

这还不是“双剑合璧”的全部威力,它还有两个神奇的地方。

6.鹅过河——哥哥弟弟坡前坐,坡上卧着一只鹅,坡下流着一条河,哥哥说:宽宽的河,弟弟说:白白的鹅。鹅要过河,河要渡鹅。不知是鹅过河,还是河渡鹅。

主动防御

先来假设一个有 BUG 的后台,没有很好处理用户输入的数据,导致 XSS
能被注入到页面:

第一个,“双剑合璧”学会了,就无敌。

7.颠倒歌——咬牛奶,喝面包,夹着火车上皮包。东西街,南北走,出门看见人咬狗。拿起狗来打砖头,又怕砖头咬我手。

在无所不能的 HTML5 里,这当然是能办到的,它就是
MutationEvent。与其相关的有两个玩意:一个叫 DOMNodeInserted
的事件,另一个则是 MutationObserver 类。

<img src="{路径}" /> <img src="{路径" onload="alert(/xss/)}" /> 

我们知道在射雕,神雕的主角,郭靖、杨过并不是学了无敌的武功,马上就天下无敌的。

8.兜装豆——兜里装豆,豆装满兜,兜破漏豆。倒出豆,补破兜,补好兜,又装豆,装满兜,不漏豆。

前者虽然是个事件,但即使阻止冒泡它,或调用 preventDefault
这些方法,仍然无法阻止元素被添加;而后者就不用说了,看名字就是一个观察器,显然优先级会更低。

只转义尖括号,却忘了引号,是 XSS
里最为常见的。攻击者们可以提前关闭属性,并添加一个极易触发的内联事件,跨站脚本就这样被轻易执行了。

比如郭靖学了《降龙十八掌》,《九阴真经》,还是打不过欧阳锋。

9.狗与猴——树上卧只猴,树下蹲条狗。猴跳下来撞了狗,狗翻起来咬住猴,不知是猴咬狗,还是狗咬猴。

MutationEvent 试探

那么,我们能否使用前端脚本来捕获,甚至拦截呢?

杨过学了《蛤蟆功》,《打狗棒法》,《弹指神通》,《九阴真经》
,还是打不过金轮法王。

10.河里有只船——河里有只船,船上挂白帆,风吹帆张船向前,无风帆落停下船。

但不管能否实现我们的目标,既然有这么个东西,就先测试看看究竟能有多大的本领。

被动扫描

有了无敌的武功还要进行漫长的磨练,才能无敌。

11.汤烫塔——老唐端蛋汤,踏凳登宝塔,只因凳太滑,汤洒汤烫塔。

<script>     var observer = new MutationObserver(function(mutations) {          console.log('MutationObserver:', mutations);      });      observer.observe(document, {          subtree: true,          childList: true      });       document.addEventListener('DOMNodeInserted', function(e) {          console.log('DOMNodeInserted:', e);      }, true);  </script>  <script>console.warn('site-in xss 1');</script> <script src="http://www.etherdream.com/xss/out.js"></script> <script>console.warn('site-in xss 2');</script>  <button id="btn">创建脚本</button> <script>     btn.onclick = function() {          var el = document.createElement('script');          el.src = 'http://www.etherdream.com/xss/out.js?dynamic';          document.body.appendChild(el);      };  </script> 

最简单的办法,就是把页面里所有元素都扫描一遍,检测那些 on
开头的内联属性,看看是不是存在异常:

只有“双剑合璧”开挂了,杨过小龙女学会了,立马无敌,小龙女一个人可以玩“合璧”了,也立马无敌。

12.蚕和蝉——这是蚕,那是蝉,蚕常在叶里藏,蝉常在林里唱。

Run

例如字符数非常多,正常情况下这是很少出现的,但XSS
为了躲避转义有时会编码的很长;例如出现一些 XSS
经常使用的关键字,但在实际产品里几乎不会用到的。这些都可以作为漏洞出现的征兆,通知给开发人员。

第二,“双剑合璧”是唯一没有破绽,无解的武功。

13.六十六头牛——六十六岁的陆老头,盖了六十六间楼,买了六十六篓油,养了六十六头牛,栽了六十六棵垂杨柳。六十六篓油,堆在六十六间楼;六十六头牛,扣在六十六棵垂杨柳。忽然一阵狂风起,吹倒了六十六间楼,翻倒了六十六篓油,折断了六十六棵垂杨柳,砸死了六十六头牛,急煞了六十六岁的陆老头。

出乎意料的是,MutationObserver
居然能逐一捕捉到页面加载时产生的静态元素,这在过去只能通过定时器才能勉强实现。同时为了更高效的记录,MutationObserver
并非发现新元素就立即回调,而是将一个时间片段里出现的所有元素,一起传过来。这对性能来说是件好事,但显然会损失一些优先级。

不过,土办法终究存在很大的局限性。在如今清一色的 AJAX
时代,页面元素从来都不是固定的。伴随着用户各种交互,新内容随时都可能动态添加进来。即使换成定期扫描一次,XSS
也可能在定时器的间隔中触发,并销毁自己,那样永远都无法跟踪到了。况且,频繁的扫描对性能影响也是巨大的。

金庸里武功多数可以破解。

14.任命、人名——任命是任命,人名是人名,任命人名不能错,错了人名错任命。

再看DOMNodeInserted,它虽然无法捕获到静态元素,但在动态创建元素时,它比
MutationObserver 更早触发,拥有更高的优先级。

如同早期的安全软件一样,每隔几秒扫描一次注册表启动项,不仅费性能,而且对恶意软件几乎不起作用;但之后的主动防御系统就不同了,只有在真正调用
API 时才进行分析,不通过则直接拦截,完全避免了定时器的间隔遗漏。

比如,闻名天下的“打狗棒法”,在华山之巅,就让欧阳锋破了个干干净净。

15.枪和糠——墙上一个窗,窗上一支枪,窗下一箩糠。枪落进了糠,糠埋住了枪。窗要糠让枪,糠要枪上墙,墙要枪上窗。互相不退让,糠赶不走枪,枪也上不了窗和墙。

静态脚本拦截

因此,我们需要这种类似的延时策略 —— 仅在 XSS
即将触发时对其分析,对不符合策略的元素,进行拦截或者放行,同时发送报警到后台日志。

欧阳锋的《蛤蟆功》让南帝的《一阳指》克制。

16.白果树——我从伯伯门前过,看见伯爹伯妈门前种着白果树,白果树上站着百十百个白斑鸠,我就拣了百十百块白石头,打那百十百个白斑鸠。

接着再来尝试,能否利用这两个事件,销毁可疑的脚本元素,以达到主动拦截的效果。

主动防御

《一阳指》,让瑛姑的金针克制。

17.好孩子——张家有个小英子,王家有个小柱子。张家的小英子,自己穿衣洗袜子,天天扫地擦桌子,王家的小柱子,捡到一只皮夹子,还给后院大婶子。小英子,小柱子,他们都是好孩子。

<script>     var observer = new MutationObserver(function(mutations) {          mutations.forEach(function(mutation) {               var nodes = mutation.addedNodes;              for (var i = 0; i < nodes.length; i++) {                  var node = nodes[i];                   if (/xss/.test(node.src) || /xss/.test(node.innerHTML)) {                      node.parentNode.removeChild(node);                      console.log('拦截可疑模块:', node);                  }              }          });      });       observer.observe(document, {          subtree: true,          childList: true      });  </script>  <script>console.warn('site-in xss 1');</script> <script src="http://www.etherdream.com/xss/out.js"></script> <script>console.warn('site-in xss 2');</script>  <button id="btn">创建脚本</button> <script>     btn.onclick = function() {          var el = document.createElement('script');          el.src = 'http://www.etherdream.com/x\ss/out.js?dynamic';          document.body.appendChild(el);      };  </script> 

『主动防御』,这概念放在前端脚本里似乎有些玄乎。但不难发现,这仅仅是执行优先级的事而已
——
只要防御程序能运行在其他程序之前,我们就有了可进可退的主动权。对于无比强大的
HTML5 和灵活多变的 JavaScript,这些概念都可以被玩转出来。

完美武功,只有“双剑合璧”的“玉女素心剑法”(“独孤九剑”没出现)。

18.送花——华华有两朵红花,红红有两朵黄花,华华想要黄花,红红想要红花,华华送给红红一朵红花,红红送给华华一朵黄花。

Run

继续回到刚才讨论的内联事件 XSS
上来。浏览器虽然没提供可操控内联事件的接口,但内联事件的本质仍是一个事件,无论怎样变化都离不开
DOM 事件模型。

为啥“双剑合璧”辣么厉害。

19.皮鞋、蒲鞋——一只皮鞋,一只蒲鞋,皮鞋补蒲鞋,蒲鞋补皮鞋,皮鞋、蒲鞋,蒲鞋、皮鞋……

又是一个出人意料的结果,所有静态脚本被成功拦截了!

扯到模型上面,一切即将迎刃而解。模型是解决问题的最靠谱的办法,尤其是像
DOM-3-Event 这种早已制定的模型,其稳定性毋庸置疑。

金庸书里说是,全真剑法,玉女剑法,相互配合呼应,所有破绽都全为旁边一人补去。

20.猫鼻子——白猫黑鼻子,黑猫白鼻子;黑猫的白鼻子,碰破了白猫黑鼻子,白猫的黑鼻子破了,剥了秕谷壳儿补鼻子;黑猫的白鼻子不破,不剥秕谷壳儿补鼻子。

图片 4

即便没仔细阅读官方文档,但凡做过网页的都知道,有个 addEventListener
的接口,并取代了曾经一个古老的叫 attachEvent
的东西。尽管只是新增了一个参数而已,但正是这个差别成了人们津津乐道的话题。每当面试谈到事件时,总少不了考察下这个新参数的用途。尽管在日常开发中很少用到它。

创造这套剑法的林朝英,和王重阳争风吃醋,从自己武功中提炼出“玉女心经”,克制全真教武功,全真教走堂皇正道,她就从旁门左道抢上风。

21.羊和狼——东边来了一只小山羊,西边来了一只大灰狼,一起走到小桥上,小山羊不让大灰狼,大灰狼不让小山羊,小山羊叫大灰狼让小山羊,大灰狼叫小山羊让大灰狼,羊不让狼,狼不让羊,扑通一起掉到河中央。

图片 5

图片 6

结果她自成一派的“玉女心经”,把全真教外功、内功、掌法、剑法,克制的死死的。

22.盆和瓶——桌上放个盆,盆里有个瓶,砰砰啪啪,啪啪砰砰,不知是瓶碰盆,还是盆碰瓶。

然而这并非标准。FireFox 虽然拦截到脚本,但仍然执行代码了。

关于事件捕获和冒泡的细节,就不多讨论了。下面的这段代码,或许能激发你对『主动防御』的遐想。

这时候比武,王重阳哪怕学了《九阴真经》,我也选林朝英会赢。

23.荷花和蛤蟆——一朵粉红大荷花,趴着一只活蛤蟆,八朵粉红大荷花,趴着八只活蛤蟆。

图片 7

<button onclick="console.log('target')">CLICK ME</button> <script>     document.addEventListener('click', function(e) {          console.log('bubble');      });       document.addEventListener('click', function(e) {          console.log('capture');          //e.stopImmediatePropagation();      }, true);  </script> 

原因是,一正一奇,一正一反的对立见识,可以统一互补,那见识才阔大,才高明,才完美。

24.画狮子——有个好孩子,拿张图画纸,来到石院子,学画石狮子。一天来画一次石狮子,十天来画十次石狮子。次次画石狮子,天天画石狮子,死狮子画成了“活狮子”。

不过对于预警系统来说,能够发现问题也足够了,可以拦截风险那就再好不过。

Run

好了,我的本职不是读金庸,是写诗,什么道理都喜欢往写诗上靠,下面就聊聊。

25.小花猫——小花猫爱画画,先画一朵腊梅花,又画一个小喇叭,带着腊梅花,吹着小喇叭,回家去见妈妈,妈妈见了笑哈哈。

动态脚本拦截

尽管按钮上直接绑了一个内联的事件,但事件模型并不买账,仍然得按标准的流程走一遍。capture,target,bubble,模型就是那样固执。

02、 诗词的“双剑合璧”

26.造房子——捡颗小石子,在地上画个方格子,画好了格子造房子,画个大方格子造个大房子,画个小方格子造个小房子,楼上的房子分给鸽子,楼下的房子分给小兔子。

刚刚测试了静态脚本的拦截,取得了不错的成绩。但在动态创建的元素上,和我们先前猜测的一样,MutationObserver
因优先级过低而无法拦截。

不过,把那行注释的代码恢复,结果就只剩 capture
了。这个简单的道理大家都明白,也没什么好解释的。

“双剑合璧”,在诗理层面,就是要在见到一个道理,不着急赞同、反对,而是看看它相反的观点,优胜劣汰,去粗取精,得出真知。

27.毛毛和猫猫——毛毛有一顶红帽,猫猫有一身灰毛。毛毛要猫猫的灰毛,猫猫要毛毛的红帽,毛毛把红帽交给猫猫,猫猫给毛毛几根灰毛。

那就让 DOMNodeInserted 来试试:

但仔细揣摩下,这不就是『主动防御』的概念吗?捕获程序运行在内联事件触发之前,并且完全有能力拦截之后的调用。

“双剑合璧”,在写诗层面,就是要经得起“毁”、“誉”。

28.斗放豆——黑豆放在黑斗里,黑斗里边放黑豆,黑豆放黑斗,黑斗放黑豆,不知黑豆放黑斗,还是黑斗放黑豆。

<script>     document.addEventListener('DOMNodeInserted', function(e) {          var node = e.target;           if (/xss/.test(node.src) || /xss/.test(node.innerHTML)) {              node.parentNode.removeChild(node);              console.log('拦截可疑模块:', node);          }      }, true);  </script>  <button id="btn">创建脚本</button> <script>     btn.onclick = function() {          var el = document.createElement('script');          el.src = 'http://www.etherdream.com/xss/out.js?dynamic';          document.body.appendChild(el);      };  </script> 

上面的 Demo
只是不假思索拦截了所有的事件。如果我们再加一些策略判断,或许就更明朗了:

人们的“誉”,无所谓,只要脸皮厚一点。

29.瘸子——北边来了一个瘸子,背着一捆橛子。南边来了一个瘸子,背着一筐茄子。背橛子的瘸子打了背茄子的瘸子一橛子。背茄子的瘸子打了背橛子的瘸子一茄子。

Run

<button onclick="console.log('xss')">CLICK ME</button> <script>     document.addEventListener('click', function(e) {          console.log('bubble');      });       document.addEventListener('click', function(e) {          var eelement = e.target;          var code = element.getAttribute('onclick');           if (/xss/.test(code)) {              e.stopImmediatePropagation();              console.log('拦截可疑事件:', code);          }      }, true);  </script> 

关键是“毁”,人人都喜欢被肯定,不喜欢被否定,而自己对了,也许还有诋毁,何况自己不完美呢?

30.天上七颗星——天上七颗星,地上七块冰,台上七盏灯,树上七只莺,墙上七枚钉。吭唷吭唷拔脱七枚钉。喔嘘喔嘘赶走七只莺。乒乒乓乓踏坏七块冰。一阵风来吹来七盏灯。一片乌云遮掉七颗星。

遗憾的是,DOMNodeInserted 也没能拦截动态脚本的执行 ——
尽管能检测到。经过一番尝试,所有浏览器都宣告失败。

Run

所以,就自己来“毁誉”。

31.花和瓜——瓜藤开花像喇叭,娃娃爱花不去掐。瓜藤开花瓜花结花,没花就没瓜。吃瓜要爱花,娃娃爱花也爱瓜。

当然,能实时预警已满足我们的需求了。但若能拦截动态脚本,整套系统防御力就更高了。

我们先在捕获阶段扫描内联事件字符,若是出现了『xss』这个关键字,后续的事件就被拦截了;换成其他字符,仍然继续执行。同理,我们还可以判断字符长度是否过多,以及更详细的黑白名单正则。

自己来“誉”。

32.妈妈骂马——妈妈种麻,我去放马,马吃了麻,妈妈骂马。

既然无法通过监控节点挂载来拦截,我们不妨换一条路。问题总有解决的方案,就看简单与否。

怎么样,一个主动防御的原型诞生了吧。

即是说,写诗要达到自己的审美境界(不是所谓的能力极限),有多美的审美,就有多美的诗,这样的诗,绝对不差。

33.白家伯伯——北贫坡上白家有个伯伯,家里养着一百八十八只白鹅,门口种着一百八十八棵白果,树上住着一百八十八只八哥。八哥在白果树上吃白果,白鹅气得直叫:我饿!我饿!

属性拦截

不过,上面的片段还有个小问题,就是把事件的冒泡过程也给屏蔽了,而我们仅仅想拦截内联事件而已。解决办法也很简单,把
e.stopImmediatePropagation() 换成 element.onclick = null 就可以了。

自己来“毁”。

34.比锤——炉东有个锤快锤,炉西有个锤锤快,两人炉前来比赛,不知是锤快锤比锤锤快锤得快?还是锤锤快比锤快锤锤得快?

仔细分析动态脚本创建的所有步骤:

当然,目前这只能防护
onclick,而现实中有太多的内联事件。鼠标、键盘、触屏、网络状态等等,不同浏览器支持的事件也不一样,甚至还有私有事件,难道都要事先逐一列出并且都捕获吗?是的,可以都捕获,但不必事先都列出来。

即是说,不幻想诗里的破绽,无人诋毁,幻想别人诋毁的破绽,自己在无数次毁誉重生中,已经弥补了。

35.窝和锅——树上一个窝,树下一口锅,窝掉下来打着锅,窝和锅都破,锅要窝赔锅,窝要锅赔窝,闹了半天,不知该锅赔窝,还是窝赔锅。

var el = document.createElement('script');  el.src = 'http://www.etherdream.com/xss/out.js?dynamic';  document.body.appendChild(el); 

因为我们监听的是 document 对象,浏览器所有内联事件都对应着
document.onxxx 的属性,因此只需运行时遍历一下 document
对象,即可获得所有的事件名。

即是说,不幻想写诗无数,能有一首诗成功,幻想写诗一首,无数次重来的努力,能成功一次。

36.青龙洞——青龙洞中龙做梦,青龙做梦出龙洞,做了千年万载梦,龙洞困龙在深洞。自从来了新愚公,愚公捅开青龙洞,青龙洞中涌出龙,龙去农田做农工。

是哪一步触发了挂载事件?显然是最后行。要获得比它更高的优先级,我们只能往前寻找。

<img src="*" onerror="console.log('xss')" /> <script>     function hookEvent(onevent) {          document.addEventListener(onevent.substr(2), function(e) {              var eelement = e.target;              if (element.nodeType != Node.ELEMENT_NODE) {                  return;              }              var code = element.getAttribute(onevent);              if (code && /xss/.test(code)) {                  element[onevent] = null;                  console.log('拦截可疑事件:', code);              }          }, true);      }       console.time('耗时');      for (var k in document) {          if (/^on/.test(k)) {              //console.log('监控:', k);              hookEvent(k);          }      }      console.timeEnd('耗时');  </script> 

如果,成功一次,就有第二次。

37.分果果——多多和哥哥,坐下分果果。哥哥让多多,多多让哥哥。都说要小个,外婆乐呵呵。

既然是动态创建脚本,赋予它 src 属性必不可少。如果创建脚本只为赋值
innerHTML 的话,还不如直接 eval 代码更简单。

Run

这样,写诗,考虑问题,只要考虑两个地方:

38.小毛与花猫——小毛抱着花猫,花猫用爪抓小毛,小毛用手拍花猫,花猫抓破了小毛,小毛打疼了花猫,小毛哭,花猫叫,小毛松开了花猫,花猫跑离了小毛。

如果能在属性赋值时进行拦截,那么我们即可阻止赋予可疑的 src 属性。

现在,无论页面中哪个元素触发哪个内联事件,都能预先被我们捕获,并根据策略可进可退了。

一是自己的诗词,有没有达到自己的审美境界;

39.种冬瓜——东门童家门东董家,童、董两家,同种冬瓜,童家知道董家冬瓜大,来到董家学种冬瓜。门东董家懂种冬瓜,来教东门童家种冬瓜。童家、董家都懂得种冬瓜,童、董两家的冬瓜比桶大。

类似 IE 有个 onpropertychange 事件,HTML5
里面也是有属性监听接口的,并且就是刚刚我们使用的那个:MutationEvent。甚至还是那两套方案:DOMAttrModified
和 MutationObserver。

性能优化

二是自己的审美,有没有问题。

40.六叔和六舅——好六叔和好六舅,借给六斗六升绿绿豆。打罢秋,接住豆,再还六叔六舅六斗六升绿绿豆。

在根节点上监听属性变化,肯定会大幅影响页面的性能,但我们还是先来看看是否可行。

或许有些事件没有必要捕获,例如视频播放、音量调节等,但就算全都捕捉也耗不了多少时间,基本都在
1ms 左右。

通常只有第一个地方,会出问题。

41.高高山上一条藤——高高山上一条藤,藤条头上挂铜铃。风吹藤动铜铃动,风停藤停铜铃停。

先尝试 MutationObserver:

当然,注册事件本来就花不了多少时间,真正的耗费都算在回调上了。尽管大多数事件触发都不频繁,额外的扫描可以忽律不计。但和鼠标移动相关的事件那就不容忽视了,因此得考虑性能优化。

03、最高境界

42.小牛赔油——小牛放学去打球,踢倒老刘一瓶油,小牛回家取来油,向老刘道歉又赔油,老刘不要小牛还油,小牛硬要把油还给老刘,老刘夸小牛,小直摇头,你猜老刘让小牛还油,还是不让小牛不油。

var observer = new MutationObserver(function(mutations) {      console.log(mutations);  });  observer.observe(document, {      subtree: true,      attributes: true  });   var el = document.createElement('script');  el.src = 'http://www.etherdream.com/xss/out.js?dynamic';  document.body.appendChild(el); 

显然,内联事件代码在运行过程中几乎不可能发生变化。使用内联事件大多为了简单,如果还要在运行时
setAttribute
去改变内联代码,完全就是不可理喻的。因此,我们只需对某个元素的特定事件,扫描一次就可以了。之后根据标志,即可直接跳过。

相信李白杜甫王维,他们对别人过分的毁誉,如清风拂面,不在乎。

43.植树——老顾大顾和小顾,扛锄植树走出屋。漫天大雾罩峡谷,雾像灰布满路铺,大顾关注喊小顾。老顾扛锄又提树,雾里植树尽义务。

站外脚本执行了,但奇怪的是,回调却没有触发。原来,我们监控的是 document
下的元素,而脚本赋值时还处于离屏状态,显然无法将事件冒泡上来。

<div style="width:100%; height:100%; position:absolute" onmouseover="console.log('xss')"></div> <script>     function hookEvent(onevent) {          document.addEventListener(onevent.substr(2), function(e) {              var eelement = e.target;               // 跳过已扫描的事件              var flags = element['_flag'];              if (!flags) {                  flags = element['_flag'] = {};              }              if (typeof flags[onevent] != 'undefined') {                  return;              }              flags[onevent] = true;               if (element.nodeType != Node.ELEMENT_NODE) {                  return;              }              var code = element.getAttribute(onevent);              if (code && /xss/.test(code)) {                  element[onevent] = null;                  console.log('拦截可疑代码:', code);              }          }, true);      }       for (var k in document) {          if (/^on/.test(k)) {              hookEvent(k);          }      }  </script> 

因为他们知道,自己的诗在自己的审美境界,是好诗,标准不是60分,90分,而是满分的100分。

44.扁担和板凳——板凳宽,扁担长,板凳比扁担宽,扁担比板凳长,扁担要绑在板凳上,板凳不让扁担绑在板凳上,扁担偏要板凳让扁担绑在板凳上。

如果我们先 appendChild 再赋值 src
属性,倒是可以捕获到。但现实中调用顺序完全不是我们说了算的。

Run

这就是,自我毁誉“双剑合璧”的,最高境界。

45.南南有个篮——南南有个篮篮,篮篮装着盘盘,盘盘放着碗碗,碗碗盛着饭饭。南南翻了篮篮,篮篮扣了盘盘,盘盘打了碗碗,碗碗撒了饭饭。

同样的,DOMAttrModified 也有这问题。

这样,之后的扫描仅仅是判断一下目标对象中的标记而已。即使疯狂晃动鼠标,CPU
使用率也都忽略不计了。

46.鸟和猫——树上一只鸟,地上一只猫。地上的猫想咬树上的鸟,树上的鸟想啄猫的毛。

看来,事件这条路的局限性太大,我们得另辟蹊径。

到此,在 XSS 内联事件这块,我们已实现主动防御。

47.蒋、墙、杨、羊——蒋家砌了一垛墙,杨家养了一只羊,杨家羊,撞塌了蒋家的墙,蒋家的墙,压死了杨家的羊,蒋家要杨家赔墙,杨家要蒋家赔羊。

API 拦截

对于有着大量字符,或者出现类似 String.fromCharCode,$.getScript 这类典型
XSS 代码的,完全可以将其拦截;发现有 alert(/xss/),alert(123)
这些测试代码,可以暂时放行,并将日志发送到后台,确定是否能够复现。

48.葵花、蓖麻——胖娃小筐手中拿,来到园中收葵花;小华小篮身上挎,一同进园收蓖麻。胖娃种的葵花花盘大,小华种的蓖麻密麻麻。小华去帮胖娃摘葵花,胖娃去帮小华收蓖麻。小华和胖娃,收了葵花、蓖麻献国家。

监控属性赋值的方式肯定不会错,只是我们不能再用事件那套机制了。

如果复现,说明已有人发现 XSS
并成功注入了,但还没大规模开始利用。程序猿们赶紧第一时间修 BUG
吧,让黑客忙活一阵子后发现漏洞已经修复了:)

49.四老伯——郭老伯、骆老伯,毕老伯、柏老伯,郭骆毕柏四老伯,约着城北买菱角,买得菱角阁上剥,菱角壳戳了四老伯的脚。

想在修改属性时触发函数调用,除了事件外,另一个在传统语言里经常用到的、并且主流
JavaScript 也支持的,那就是Setter 访问器。

字符策略的缺陷

50.我有一条狗——我有一条狗,狗尾拖个斗。走起路来狗关直发抖,不知是狗拖斗,还是斗拖狗。

当我们设置脚本元素 src 属性时,理论上说 HTMLScriptElement.prototype.src
这个访问器将被调用。如果我们重写这个访问器,即可在设置脚本路径时将其拦截。

但是,光靠代码字符串来判断,还是会有疏漏的。尤其是黑客们知道有这么个玩意存在,会更加小心了。把代码转义用以躲避关键字,并将字符存储在其他地方,以躲过长度检测,即可完全绕过我们的监控了:

51.乌鸦说猪黑——乌鸦站在黑猪脊上说黑猪黑,黑猪说乌鸦比黑猪还要黑。乌鸦说它身比黑猪黑,嘴不黑,黑猪听罢笑得嘿嘿嘿。

<script>     HTMLScriptElement.prototype.__defineSetter__('src', function(url) {          console.log('设置路径:', url);      });  </script>  <button id="btn">创建脚本</button> <script>     btn.onclick = function() {          var el = document.createElement('script');          el.src = 'http://www.etherdream.com/xss/out.js?dynamic';          document.body.appendChild(el);      };  </script> 
<img src="*" onerror="window['ev'+'al'](this.align)" align="alert('a mass of code...')"> 

52.妞妞和牛牛——牛牛要吃河边柳,妞妞护柳要赶牛,牛牛扭头瞅妞妞,妞妞扭牛牛更牛,牛牛要顶妞妞,妞妞捡起小石头,吓得牛牛扭头溜。

Run

因此,我们不仅需要分析关键字。在回调执行时,还需监控
eval、setTimeout(‘…’) 等这类能解析代码的函数被调用。

53.灰猫追花鸟——灰猫跳,花鸟叫,灰猫听花鸟叫,花鸟瞧灰猫跳。灰猫跳起抓花鸟,花鸟怕灰猫,拔腿就逃掉。

如果这套方案可行的话,一切都将迎刃而解。而且我们只监听脚本元素的 src
赋值,其他元素和属性则完全不受影响,因此性能得到极大提升。

不过,通常不会注入太多的代码,而是直接引入一个外部脚本,既简单又靠谱,并且能实时修改攻击内容:

54.雕与猫——树上有只雕,地上有只猫,地上的猫想叼走树上的雕,树上的雕啄猫身上的毛,雕吓走了猫,猫赶飞了雕。

经测试,FireFox 和 IE 浏览器完全可行。我们事先保存原始的 setter
变量,然后根据策略,决定是否向上调用。

<img src="*" onerror="$['get'+'Script'](...)"> 

55.两只鹅——河边两只鹅,一同过了河;白鹅去拾草,黑鹅来搭窝。冬天北风刮,草窝真暖和,住在草窝里,哦哦唱支歌。

<script>     var raw_setter = HTMLScriptElement.prototype.__lookupSetter__('src');       HTMLScriptElement.prototype.__defineSetter__('src', function(url) {          if (/xss/.test(url)) {              if (confirm('试图加载可疑模块:\n\n' + url + '\n\n是否拦截?')) {                  return;              }                         }          raw_setter.call(this, url);      });  </script>  <button id="btn">创建脚本</button> <script>     btn.onclick = function() {          var el = document.createElement('script');          el.src = 'http://www.etherdream.com/xss/out.js?dynamic';          document.body.appendChild(el);      };  </script> 

下一篇将讨论,如何拦截可疑的外部模块。

56.胡子骑驴子——胡子骑驴子,驼子挑螺蛳,胡子撞翻了驼子的螺蛳,驼子拖住胡子的驴子,胡子去打挑螺蛳的驼子,驼子来打骑驴子的胡子,胡子打驼子,驼子打胡子。

Run

原文链接:]

57.上果市——四个孩子上果市,拿着四个小篮子。花了硬币四毛四,买了十个小柿子。四个孩子出果市,拾了四十小石子。到了家里吃柿子,吃完柿子玩石子。

图片 8

【编辑推荐】

58.养鱼——大渠养大鱼不养小鱼,小渠养小鱼不养大鱼。一天天下雨,下了一天雨。大渠水流进小渠,小渠水流进大渠。大渠里有了小鱼不见大鱼,小渠里有了大鱼不见小鱼。

效果非常漂亮,然而现实却令人遗憾 —— 我们的主流浏览器 Chrome
并不支持。由于无法操作原生访问器,即使在原型链上重写了
setter,实际赋值时仍不会调用我们的监控程序。

59.白猫与黑猫——庙里有只白猫,庙外有只黑猫。庙里白猫骂庙外黑猫是馋猫,庙外黑猫骂庙里白猫是懒猫。

先不急,若是抛弃原型链,直接在元素实例上定义访问器又会如何?

60.倒吊鸟——梁上两对倒吊鸟,泥里两对鸟倒吊。可怜梁上的两对倒吊鸟,惦着泥里的两对鸟倒吊,可怜泥里的两对鸟倒吊,也惦着梁上的两对倒吊鸟。

<button id="btn">创建脚本</button> <script>     btn.onclick = function() {          var el = document.createElement('script');           el.__defineSetter__('src', function(url) {              console.log('设置路径:', url);          });           el.src = 'http://www.etherdream.com/xss/out.js?dynamic';          document.body.appendChild(el);      };  </script> 

61.吃桔子——吃桔子,剥桔子,桔皮丢在垃圾箱里,不吃桔子,不剥桔子,不把桔皮丢在垃圾箱里。

run

62.看豆豆——小妞妞围个圆兜兜,牛头沟边看豆豆,忽听沟前喊抓牛,妞妞怕牛牛踩豆豆,紧紧抓住牛牛不松手。

这一回,Chrome 终于可以了。

63.小道上——小王的姜撞翻老杨的缸,老杨的缸碰倒小王的姜。小王放下姜去扶老杨的缸,老杨放下缸去帮小王装姜。

图片 9

64.是灯还是星——天上满天星,地上满山灯,满天星亮满天庭,满山灯接满天星。星映灯,灯映星,分不清是灯还是星。

然而,这仅仅是测试。现实中哪有这样的机会,供我们装上访问器呢。

65.丽丽和弟弟——丽丽家来了小弟弟,弟弟叫丽丽出来做游戏,丽丽和弟弟拿不定主意,是做游戏还是玩玩具。

因此,我们只能把主动防御的时机再往前推,在元素创建时就调用我们的防御代码。我们得重写
createElement 这些能创建元素
API,只有这样,才能第一时间里,给实例装上我们的钩子程序,为 Chrome
实现动态模块的防御:

66.北京和天津——天津和北京,津京两个音。一是前鼻音,一是后鼻音。如果分不清,请你认真听。

<script>     // for chrome      var raw_fn = Document.prototype.createElement;       Document.prototype.createElement = function() {           // 调用原生函数          var element = raw_fn.apply(this, arguments);           // 为脚本元素安装属性钩子          if (element.tagName == 'SCRIPT') {              element.__defineSetter__('src', function(url) {                  console.log('设置路径:', url);              });          }           // 返回元素实例          return element;      };  </script>  <button id="btn">创建脚本</button> <script>     btn.onclick = function() {          var el = document.createElement('script');          el.src = 'http://www.etherdream.com/xss/out.js?dynamic';          document.body.appendChild(el);      };  </script> 

67.肥猪、肥兔——小苏要喂猪,老鲁要养兔。老鲁会喂猪,小苏会养兔。老鲁教小苏喂猪,小苏教老鲁养兔。兔肥如猪,满院肥猪、肥兔关不住。

Run

68.四个头——天上有日头,地下有石头,嘴里有舌头,瓶口有塞头。天上是日头不是石头,地下是石头不是日头,嘴里是舌头不是塞头,瓶中是塞头不是舌头。

这样,当元素创建时,就已带有我们的属性扫描程序了,Chrome
不支持的问题也迎刃而解。

69.狗、猴过桥——桥东走来一条狗,桥西走来一只猴。行到桥心碰了头,彼此匆匆跑回头。猴回头来望望狗,狗回头来望望猴,究竟是猴怕狗,还是狗怕猴。

事实上,除了重写 property 访问器,我们还得考虑通过 setAttribute 赋值 src
的情况。因此需整理出一套完善的浏览器钩子程序。

70.人造神?神造人?宁宁和成成,去看观音神。宁宁说是“人造神”,成成说是“神造人”,宁宁说不服成成,成成说不服宁宁。一道去请教高婶婶,到底是神造人,还是人造神?

重写原生 API
看似很简单,但如何才能打造出一个无懈可击的钩子系统呢?明天继续讲解。

71.梳胡子——苏州有个苏胡子,湖州有个胡梳子。胡梳子买把斧子做梳子,苏胡子买把梳子梳胡子。

【编辑推荐】

72.涩柿子与石狮子——树上结了四十四个涩柿子,树下蹲着四十四头石狮子;树下四十四头石狮子,要吃树上四十四个涩柿子;涩柿子不让石狮子吃涩柿子;石狮子偏要吃涩柿子。

73.白庙、白猫——山上有座白庙,地上有只白猫,白发老公公掉了一顶白帽,白猫叼着白帽跑进一白庙。

74.捉壁虎——李虎捉壁虎,本是虎捉虎,李虎满屋转,壁虎不敢咬李虎,李虎也捉不住壁虎。

75.梨和犁——耕地要用犁,口渴要吃梨。梨子掉下地,沾了一身泥。不要扔了梨,只需洗掉泥。

76.学捏梨——盘里放着一个梨,桌上放块橡皮泥。小丽用泥学捏梨,眼看着梨手捏泥,比比,真梨、假梨差不离。

77.三娘找山羊——三娘在山上放三只山羊,三只山羊翻过山梁,三娘翻过山梁去找三只山羊。三只山羊躲在杉树旁,三娘找到三只山羊。

78.东南西北四个阳——东有荥阳西有咸阳,南有衡阳北有汾阳,荥阳、咸阳,衡阳、汾阳,东南西北四个阳,发音不能走了样。

79.补裤——一块土粗布,一条粗布裤,哥哥屋里补布裤,飞针走线自己做。粗布裤上补粗布,土粗布补粗布裤,哥哥穿上粗布裤,艰苦朴素牢记住。

80.买菜——小艾和小戴,一起去买菜。小艾把一斤菜给小戴,小戴有比小艾多一倍的菜;小戴把一斤菜给小艾,小艾、小戴就有一般多的菜。

81.一个人——这边一个人,挑了一挑瓶。那边一个人,担了一挑盆。瓶碰烂了盆,盆碰烂了瓶。卖瓶买盆来赔盆,卖盆买瓶来赔瓶。瓶不能赔盆,盆不能赔瓶。

82.分清巾金睛景——小金到北京看风景,小京到天津买纱巾,看风景,用眼睛,还带一个望远镜。买纱巾,带现金,到了天津把商店进,买纱巾,用现金,看风景,用眼睛,巾、金、睛、景要分清。

83.花青蛙——花青蛙,叫呱呱,西瓜地里看西瓜,西瓜夸青蛙背背花,青蛙夸西瓜长得大。

84.麻字谣——麻家爷爷挑着一对麻叉口,走到麻家婆婆的家门口。麻家婆婆的一对麻花狗,咬破了麻家爷爷的麻叉口。麻家婆婆拿来麻针、麻线,来补麻家爷爷的麻叉口。

85.月亮走——月亮走,我也走,我给月亮提竹篓,竹篓里面装豆豆,送给月亮上的小猴猴,小猴吃了豆豆长肉肉。

86.画葫芦——胡图用笔画葫芦,葫芦画得真糊涂,糊涂不能算葫芦,要画葫芦不糊涂,胡图决心不糊涂,画出一只大葫芦。

87.吃荸荠——荸荠有皮,皮上有泥。洗掉荸荠皮上的泥,削去荸荠外面的皮,小丽、小艺和小奇,欢欢喜喜吃荸荠。

88.登山——小三去登山;上山又下山,下山又上山;登了三次山,跑了三里三,出了一身汗,湿了三件衫,小三山上大声喊:离天只有三尺三!

89.死狮子——我说四个石狮子,你说十个纸狮子。纸狮子是死狮子,石狮子不能撕,要想说清这几个字,读准四十石死撕。

90.数数——山上一只虎,林中一只鹿,路边一头猪,草里一只兔,还有一只鼠。数一数,一二三四五,虎鹿猪兔鼠。

91.秃丫头——从南来了个秃丫头,胳膊上挎着个破笆斗,里头有堆羊骨头,伸手拿骨头,送在口里啃骨头。地下有块破砖头,绊倒了秃丫头,撒了羊骨头。

92.油缸碰豆筐——豆混油,缸混筐,豆要油赔豆,油要豆赔油;缸要筐赔缸,筐要缸赔筐。墙上油一缸,墙下豆一筐,乒乓一声响,油缸碰豆筐。

93.白果果——我走白家门前过,白家门前一棵白果树,树上结了白果果,白果树上歇了只白八哥。

94.小花鼓——一面小花鼓,鼓上画老虎。妈妈用布来补。到底是布补鼓,还是布补虎。

95.换斑竹——斑竹林里头有干斑竹,包谷林里头有干包谷。潘家三虎走进包谷林,掰了一担干包谷,回家路过斑竹林,换了三根干斑竹。

  1. 这是蚕,那是蝉,蚕常在叶里藏,蝉常在林里唱。

97.老齐欲想去卖鱼,巧遇老吕去牵驴,老齐要用老吕的驴去驮鱼,老吕说老齐要用我老吕的驴驮鱼就得给我鱼,要不给我鱼就别用我老吕的驴去驮鱼,二人争来争去都误了去赶集。

98.张伯伯,李伯伯,饽饽铺里买饽饽,张伯伯买了个饽饽大,李伯伯买了个大饽饽。拿回家里喂婆婆,婆婆又去比饽饽也不知是张伯伯买的饽饽大还是李伯伯买的大饽饽。

99.打南边来了个哑巴,腰里别了个喇叭;打北边来了个喇嘛,手里提了个獭犸。提着獭犸的喇嘛要拿獭犸换别着喇叭的哑巴的喇叭;别着喇叭的哑巴不愿拿喇叭换提着獭犸的喇嘛的獭犸。伽小説抠:玖贰壹壹陆泗壹泗泗,不知
是别着喇叭的哑巴打了提着獭犸的喇嘛一喇叭;还是提着獭犸的喇嘛打了别着喇叭的哑巴一獭犸。喇嘛回家炖獭犸,哑巴嘀嘀哒哒吹喇叭

100.字纸里裹着细银丝,细银丝上趴着四千四百四十四个似死似不死的

赞 评论转发

发表评论

电子邮件地址不会被公开。 必填项已用*标注

网站地图xml地图