WAF的识别、检测、绕过原理与实战案例
1.WAF简介
1.0.WAF检测原理
WAF通过配置DNS解析地址、软件部署、串联部署、透明部署、网桥部署、反向代理部署、旁路部署等获取攻击流量,基于规则进行攻击特征匹配,或利用其他方式进行攻击检测及阻断。
1.0.1.基于规则匹配
一般都是基于一定的正则语法进行匹配,例如匹配函数 concat(),而不会匹配字符 concat。而 MySQL里面concat函数调用的时候括号是可以被隔开的。例如concat ()
,就可以绕过该正则。
对于规则匹配的WAF,想办法让WAF的解析引擎或者匹配引擎岀错,而对于实际业务后端来说又无影响可以正常执行,就能绕过WAF的拦截。
1.0.2.基于语义分析
将输入的参数模拟为真实语句去运行,然后判断结果是否有问题。这种理念类似于一个 webshell检测引擎,在不考虑性能等情况下,将PHP的Zend引擎下执行命令的函数hook住,如果该函数被调用,会先进入自己的逻辑去判断。如果符合恶意执行的逻辑则判断为webshell执行,从而实现webshell的识别。
对于语义分析的WAF,绕过理念会更加复杂和困难。因为你不再是对正则进行绕过。而是类似于对一个解析引擎进行黑盒测试的绕过,将其绕过后还要保证后端的真实解析引擎不出错。
类似于前面举例 concat的绕过,本质上是利用了前端语言引擎的解析和后端MySQL引擎解析的不一致达成绕过。而在基于语义分析绕过的情况下,再也无法使用等价替换的手段,只能通过WAF引擎与后端服务器引擎不一致来绕过。
1.0.3.RASP技术
例如OPENRASP有个功能是拦截所有 php/jsp 等脚本文件的写入操作,从攻击会产生的行为去进行检测和防御。
http://blog.nsfocus.net/rasp-tech/
1.1.WAF分类
1.1.1.云WAF
云WAF主要利用DNS技术,通过移交域名解析权来实现安全防护,用户的请求首先发送到云端节点进行检测,如存在异常请求则进行拦截否则将请求转发至真实服务器。
常见的云WAF有阿里云盾、腾讯云 T-Sec Web 应用防火墙、百度云应用防火墙 WAF、华为云-云防火墙 CFW、华为云 Web应用防火墙 WAF、安全狗云御WEB应用防护系统、安恒玄武盾云防护、绿盟网站云防护、启明星辰虚拟化WAF、深信服云Web应用防火墙云WAF、知道创宇-创宇盾、F5 分布式云 WAF、奇安信网站卫士、360磐云、网宿Web应用防火墙、奇安信网神WEB应用安全云防护系统(安域)等
1.1.2.硬件WAF
硬件WAF通常部署在Web服务器之前,过滤所有外部访问流量,并对请求包进行解析,通过安全规则库的攻击规则进行匹配,识别异常并进行请求阻断。
常见产品有绿盟Web应用防火墙、安恒明御Web应用防火墙、启明天清Web应用安全网关、长亭雷池(SafeLine) 下一代 Web 应用防火墙、远江盛邦Web应用防护系统(RayWAF)、天融信Web应用安全防护系统(TopWAF)、深信服Web应用防火墙WAF、铱迅Web应用防护系统、F5 Advanced WAF(API 安全 - 新一代 WAF)
1.1.3.软件WAF
软件WAF安装在需要防护的服务器上,通过监听端口或以Web容器扩展方式进行请求检测和阻断。
常见产品有安全狗、云锁、D盾、网防G01、护卫神、智创、UPUPW、宝塔网站防火墙、悬镜、安骑士等
1.1.4.其他WAF
中间件自带的WAF模块、网站内置WAF、开源WAF(Naxsi、OpenRASP、ModSecurity)
1.2.WAF识别
1.2.1.wafw00f
nmap默认有19个指纹,sqlmap默认有94个指纹,wafw00f默认有155个指纹
#!/usr/bin/env python'''Copyright (C) 2020, WAFW00F Developers.See the LICENSE file for copying permission.'''# NOTE: this priority list is used so that each check can be prioritized,# so that the quick checks are done first and ones that require more# requests, are done laterwafdetectionsprio = [ 'ACE XML Gateway (Cisco)', 'aeSecure (aeSecure)', 'AireeCDN (Airee)', 'Airlock (Phion/Ergon)', 'Alert Logic (Alert Logic)', 'AliYunDun (Alibaba Cloud Computing)', 'Anquanbao (Anquanbao)', 'AnYu (AnYu Technologies)', 'Approach (Approach)', 'AppWall (Radware)', 'Armor Defense (Armor)', 'ArvanCloud (ArvanCloud)', 'ASP.NET Generic (Microsoft)', 'ASPA Firewall (ASPA Engineering Co.)', 'Astra (Czar Securities)', 'AWS Elastic Load Balancer (Amazon)', 'AzionCDN (AzionCDN)', 'Azure Front Door (Microsoft)', 'Barikode (Ethic Ninja)', 'Barracuda (Barracuda Networks)', 'Bekchy (Faydata Technologies Inc.)', 'Beluga CDN (Beluga)', 'BIG-IP Local Traffic Manager (F5 Networks)', 'BinarySec (BinarySec)', 'BitNinja (BitNinja)', 'BlockDoS (BlockDoS)', 'Bluedon (Bluedon IST)', 'BulletProof Security Pro (AITpro Security)', 'CacheWall (Varnish)', 'CacheFly CDN (CacheFly)', 'Comodo cWatch (Comodo CyberSecurity)', 'CdnNS Application Gateway (CdnNs/WdidcNet)', 'ChinaCache Load Balancer (ChinaCache)', 'Chuang Yu Shield (Yunaq)', 'Cloudbric (Penta Security)', 'Cloudflare (Cloudflare Inc.)', 'Cloudfloor (Cloudfloor DNS)', 'Cloudfront (Amazon)', 'CrawlProtect (Jean-Denis Brun)', 'DataPower (IBM)', 'Cloud Protector (Rohde & Schwarz CyberSecurity)', 'DenyALL (Rohde & Schwarz CyberSecurity)', 'Distil (Distil Networks)', 'DOSarrest (DOSarrest Internet Security)', 'DDoS-GUARD (DDOS-GUARD CORP.)', 'DotDefender (Applicure Technologies)', 'DynamicWeb Injection Check (DynamicWeb)', 'Edgecast (Verizon Digital Media)', 'Eisoo Cloud Firewall (Eisoo)', 'Expression Engine (EllisLab)', 'BIG-IP AppSec Manager (F5 Networks)', 'BIG-IP AP Manager (F5 Networks)', 'Fastly (Fastly CDN)', 'FirePass (F5 Networks)', 'FortiWeb (Fortinet)', 'GoDaddy Website Protection (GoDaddy)', 'Greywizard (Grey Wizard)', 'Huawei Cloud Firewall (Huawei)', 'HyperGuard (Art of Defense)', 'Imunify360 (CloudLinux)', 'Incapsula (Imperva Inc.)', 'IndusGuard (Indusface)', 'Instart DX (Instart Logic)', 'ISA Server (Microsoft)', 'Janusec Application Gateway (Janusec)', 'Jiasule (Jiasule)', 'Kona SiteDefender (Akamai)', 'KS-WAF (KnownSec)', 'KeyCDN (KeyCDN)', 'LimeLight CDN (LimeLight)', 'LiteSpeed (LiteSpeed Technologies)', 'Open-Resty Lua Nginx (FLOSS)', 'Oracle Cloud (Oracle)', 'Malcare (Inactiv)', 'MaxCDN (MaxCDN)', 'Mission Control Shield (Mission Control)', 'ModSecurity (SpiderLabs)', 'NAXSI (NBS Systems)', 'Nemesida (PentestIt)', 'NevisProxy (AdNovum)', 'NetContinuum (Barracuda Networks)', 'NetScaler AppFirewall (Citrix Systems)', 'Newdefend (NewDefend)', 'NexusGuard Firewall (NexusGuard)', 'NinjaFirewall (NinTechNet)', 'NullDDoS Protection (NullDDoS)', 'NSFocus (NSFocus Global Inc.)', 'OnMessage Shield (BlackBaud)', 'Palo Alto Next Gen Firewall (Palo Alto Networks)', 'PerimeterX (PerimeterX)', 'PentaWAF (Global Network Services)', 'pkSecurity IDS (pkSec)', 'PT Application Firewall (Positive Technologies)', 'PowerCDN (PowerCDN)', 'Profense (ArmorLogic)', 'Puhui (Puhui)', 'Qcloud (Tencent Cloud)', 'Qiniu (Qiniu CDN)', 'Reblaze (Reblaze)', 'RSFirewall (RSJoomla!)', 'RequestValidationMode (Microsoft)', 'Sabre Firewall (Sabre)', 'Safe3 Web Firewall (Safe3)', 'Safedog (SafeDog)', 'Safeline (Chaitin Tech.)', 'SecKing (SecKing)', 'eEye SecureIIS (BeyondTrust)', 'SecuPress WP Security (SecuPress)', 'SecureSphere (Imperva Inc.)', 'Secure Entry (United Security Providers)', 'SEnginx (Neusoft)', 'ServerDefender VP (Port80 Software)', 'Shield Security (One Dollar Plugin)', 'Shadow Daemon (Zecure)', 'SiteGround (SiteGround)', 'SiteGuard (Sakura Inc.)', 'Sitelock (TrueShield)', 'SonicWall (Dell)', 'UTM Web Protection (Sophos)', 'Squarespace (Squarespace)', 'SquidProxy IDS (SquidProxy)', 'StackPath (StackPath)', 'Sucuri CloudProxy (Sucuri Inc.)', 'Tencent Cloud Firewall (Tencent Technologies)', 'Teros (Citrix Systems)', 'Trafficshield (F5 Networks)', 'TransIP Web Firewall (TransIP)', 'URLMaster SecurityCheck (iFinity/DotNetNuke)', 'URLScan (Microsoft)', 'UEWaf (UCloud)', 'Varnish (OWASP)', 'Viettel (Cloudrity)', 'VirusDie (VirusDie LLC)', 'Wallarm (Wallarm Inc.)', 'WatchGuard (WatchGuard Technologies)', 'WebARX (WebARX Security Solutions)', 'WebKnight (AQTRONIX)', 'WebLand (WebLand)', 'wpmudev WAF (Incsub)', 'RayWAF (WebRay Solutions)', 'WebSEAL (IBM)', 'WebTotem (WebTotem)', 'West263 CDN (West263CDN)', 'Wordfence (Defiant)', 'WP Cerber Security (Cerber Tech)', 'WTS-WAF (WTS)', '360WangZhanBao (360 Technologies)', 'XLabs Security WAF (XLabs)', 'Xuanwudun (Xuanwudun)', 'Yundun (Yundun)', 'Yunsuo (Yunsuo)', 'Yunjiasu (Baidu Cloud Computing)', 'YXLink (YxLink Technologies)', 'Zenedge (Zenedge)', 'ZScaler (Accenture)']
举几个例子:
1.2.1.1.阿里云盾
#!/usr/bin/env python'''Copyright (C) 2020, WAFW00F Developers.See the LICENSE file for copying permission.'''NAME = 'AliYunDun (Alibaba Cloud Computing)'def is_waf(self): schemes = [ self.matchContent(r'error(s)?\.aliyun(dun)?\.(com|net)?'), self.matchCookie(r'^aliyungf_tc='), self.matchContent(r'cdn\.aliyun(cs)?\.com'), self.matchStatus(405) ] if all(i for i in schemes): return True return False
1.2.1.2.安全狗
#!/usr/bin/env python'''Copyright (C) 2020, WAFW00F Developers.See the LICENSE file for copying permission.'''NAME = 'Safedog (SafeDog)'def is_waf(self): schemes = [ self.matchCookie(r'^safedog\-flow\-item='), self.matchHeader(('Server', 'Safedog')), self.matchContent(r'safedogsite/broswer_logo\.jpg'), self.matchContent(r'404\.safedog\.cn/sitedog_stat.html'), self.matchContent(r'404\.safedog\.cn/images/safedogsite/head\.png') ] if any(i for i in schemes): return True return False
1.2.1.3.腾讯云
#!/usr/bin/env python'''Copyright (C) 2020, WAFW00F Developers.See the LICENSE file for copying permission.'''NAME = 'Tencent Cloud Firewall (Tencent Technologies)'def is_waf(self): schemes = [ self.matchContent(r'waf\.tencent\-?cloud\.com/') ] if any(i for i in schemes): return True return False
1.2.1.4.云锁
#!/usr/bin/env python'''Copyright (C) 2020, WAFW00F Developers.See the LICENSE file for copying permission.'''NAME = 'Yunsuo (Yunsuo)'def is_waf(self): schemes = [ self.matchCookie(r'^yunsuo_session='), self.matchContent(r'class=\"yunsuologo\"') ] if any(i for i in schemes): return True return False
1.3.WAF拦截页面
收集WAF拦截页面较为麻烦,部分图片引用自潇湘信安微信公众号中3had0w师傅的文章《30几款常见WAF的拦截页整理》,在此感谢下师傅~
1.3.1.云WAF
1.3.1.1.阿里云盾
服务名:Alibaba Security Aegis Detect Service、Alibaba Security Aegis Update Service、AliyunService进程名:AliYunDun.exe、AliYunDunUpdate.exe、aliyun_assist_service.exe
指纹:https://errors.aliyun.com/images/TB1TpamHpXXXXaJXXXXeB7nYVXX-104-162.png
1.3.1.2.腾讯云
进程名:BaradAgent.exe、sgagent.exe、YDService.exe、YDLive.exe、YDEdr.exe
1.3.1.3.百度云
1.3.1.4.华为云
1.3.1.5.安恒玄武盾
1.3.1.6.创宇盾
1.3.1.7.知道创宇 加速乐
1.3.1.8.奇安信网站卫士
1.3.1.9.360磐云(360网站卫士)
服务名:QHWafUpdata进程名:360WebSafe.exe、QHSrv.exe、QHWebshellGuard.exe
1.3.1.10.网宿云
1.3.1.11.奇安信 安域云WAF
1.3.1.12.Cloudflare云WAF
1.3.1.13.安全狗
1.3.2.硬件WAF
1.3.2.1.安恒明御WAF
1.3.2.2.长亭SafeLine
1.3.2.3.盛邦RayWAF
1.3.2.4.铱讯WAF
1.3.2.5.F5 BIG-IP
1.3.3.软件WAF
1.3.3.1.网站安全狗
服务名:SafeDogCloudHelper、Safedog Update Center、SafeDogGuardCenter(服务器安全狗守护中心)进程名:SafeDogSiteApache.exe、SafeDogSiteIIS.exe、SafeDogTray.exe、SafeDogServerUI.exe、SafeDogGuardCenter.exe、CloudHelper.exe、SafeDogUpdateCenter.exe
1.3.3.2.云锁
服务端监听端口:5555服务名:YunSuoAgent/JtAgent(云锁Windows平台代理服务)、YunSuoDaemon/JtDaemon(云锁Windows平台守护服务)进程名:yunsuo_agent_service.exe、yunsuo_agent_daemon.exe、PC.exe
1.3.3.3.D盾
服务名:d_safe进程名:D_Safe_Manage.exe、d_manage.exe
1.3.3.4.网防G01
服务端监听端口:5555服务名:YunSuoAgent、YunSuoDaemon(不知是否忘了替换了!)进程名:gov_defence_service.exe、gov_defence_daemon.exe
1.3.3.5.护卫神
服务名:hws、hwsd、HwsHostEx/HwsHostWebEx(护卫神主机大师服务)进程名:hws.exe、hwsd.exe、hws_ui.exe、HwsPanel.exe、HwsHostPanel.exe、HwsHostMaster.exe(护卫神主机大师)
1.3.3.6.智创防火墙
1.3.3.7.UPUPW安全防护
1.3.3.8.宝塔网站防火墙
1.3.4.其他WAF
1.3.4.1.Naxsi WAF
1.3.4.2.360主机卫士或360webscan
服务名:QHWafUpdata进程名:360WebSafe.exe、QHSrv.exe、QHWebshellGuard.exe
1.3.4.3.西数WTS-WAF
1.3.4.4.腾讯宙斯盾
1.3.4.5.Mod_Security
1.3.4.6.OpenRASP
1.3.4.7.dotDefender
1.3.4.8.西部数码云网盾
1.3.4.9.红网云WAF
2.WAF绕过
2.1.架构层
2.1.1.绕过云WAF寻找源站
2.1.1.1.目标域名历史解析IP
https://site.ip138.com/
https://ipchaxun.com/
DNSDB:https://dnsdb.io/zh-cn/
2.1.1.2.页面/js源码爬取IP
jsfinder:https://github.com/Threezh1/JSFinder
burp敏感信息高亮插件:https://github.com/ScriptKid-Beta/Unexpected_information
配置文件等敏感信息泄露
2.1.1.3.暴露真实ip的边缘业务
例如www.xxx.cn,官网一般都喜欢走CDN,直接查找该业务真实IP不太好找,但是可以根据子域名www.baxx.xxx.cn去定位真实IP,再结合C段扫描定位官网的真实IP及端口。
2.1.1.4.HOSTS碰撞
隐藏资产探测-hosts碰撞:https://www.cnblogs.com/Rain99-/p/13756032.html
2.1.1.5.其他思路
-
SSL证书查询
-
未接入WAF前,真实IP地址是否被搜索引擎等服务收录,例如百度快照。
-
GIHUB源代码泄露是否包含源站IP。
-
拿下子站,利用子站向主站进行攻击,如果子站IP有加白名单就比较舒hu。
2.2.资源限制
2.2.1.填充垃圾字符
尝试往各种位置填充垃圾字符即可,例如uri里、sql语句中、body中、filename值等等
URI:/xxx?data=aaaaaaa...aaaaa&cmd=whaomiSQL:11111111...111111111' and 1=1 --1'/*aaa...aaa*/ and 1=1 --Body:a=aaaaaaa- --&b=bbbb. . .&pay1oad=. . .filename:name="uploaded"; filename222... ="xxxx.. . . ";filename="x.txt"
2.2.1.高并发
爆破模块持续发包,也可结合payload字典进行fuzz绕过
2.3.协议层
2.3.1.常见绕过思路
利用HTTPS协议,若WAF没有配置SSL解密就可以绕过;
利用chunk也就是分块传输进行绕过;
去掉请求头中的请求方法进行绕过等等
2.3.2.HTTP协议标准
思路是死的,人是活的,了解协议标准会帮助我们开发新的绕过思路。可以参考腾讯云上的HTTP协议介绍:https://cloud.tencent.com/developer/doc/1117
2.4.规则层
2.4.1.等价替换
判断字符集是否可以使用,可以正常发包,改字符集,看返回是否正常。
字符编码
好的文章:编码导致的WAF安全性研究
Nginx,uWSGI-Django-Python3
IBM037、IBM500、cp875、IBM1026、IBM273
查询字符串和正文需要编码。查询字符串和正文中的 URL 解码参数。等号和 & 符号也需要编码(无 url 编码)。
Nginx,uWSGI-Django-Python2 | IBM037、IBM500、cp875、IBM1026、utf-16、utf-32、utf-32BE、IBM424 | 查询字符串和正文需要编码。之后查询字符串和正文中的 URL 解码参数。等号和 & 号不应以任何方式编码。 |
Apache-TOMCAT8-JVM1.8-JSP | IBM037、IBM500、IBM870、cp875、IBM1026、IBM01140、IBM01141、IBM01142、IBM01143、IBM01144、IBM01145、IBM01146、IBM01147、IBM01148、IBM01149、utf-16、utf-32、utf-32BE、IBM273、IBM280278 IBM284、IBM285、IBM290、IBM297、IBM420、IBM424、IBM-Thai、IBM871、cp1025 | 原始格式的查询字符串(可以像往常一样进行 url 编码)。可以使用/不使用 url 编码发送正文。等号和 & 号不应以任何方式编码。 |
Apache-TOMCAT7-JVM1.6-JSP | IBM037、IBM500、IBM870、cp875、IBM1026、IBM01140、IBM01141、IBM01142、IBM01143、IBM01144、IBM01145、IBM01146、IBM01147、IBM01148、IBM01149、utf-16、utf-32、utf-32BE、IBM273、IBM280278 IBM284、IBM285、IBM297、IBM420、IBM424、IBM-Thai、IBM871、cp1025 | 原始格式的查询字符串(可以像往常一样进行 url 编码)。可以使用/不使用 url 编码发送正文。等号和 & 号不应以任何方式编码。 |
IIS6、7.5、8、10 -ASPX (v4.x) | IBM037,IBM500,IBM870,cp875,IBM1026,IBM01047,IBM01140,IBM01141,IBM01142,IBM01143,IBM01144,IBM01145,IBM01146,IBM01147,IBM01148,IBM01149,utf-16,unicode-FFFE,BE,utf-32,IBM27732,utf-32,utf-32 IBM278、IBM280、IBM284、IBM285、IBM290、IBM297、IBM420、IBM423、IBM424、x-EBCDIC-KoreanExtended、IBM-Thai、IBM871、IBM880、IBM905、IBM00924、cp1025 | 原始格式的查询字符串(可以像往常一样进行 url 编码)。可以使用/不使用 url 编码发送正文。等号和 & 号不应以任何方式编码。 |
2.4.2.字符干扰
空字符
MySQL
Oracle
MSSQL
注释符 | //、#、/!/、/!50000xx/、--、-- -、--+ | --、/**/ --%0a- | --、/**/、--%0a- |
空白字符 | %09%0A%0B%0C%0D%20 | %00%09%0A%0B%0C%0D%20 | %00-%20 |
2.4.3.特殊符号
mysql
Injection
Allowed symbols
-1 union: | `.,%.0,%" ",%' ',&.0,&\N,-.0,=\N,<0.,>0.,e0,^0., |
select 1: | `+-!,-@,@ |
column from: | `,' '," ",1.,1e1,1.1,%" ",%' ',.1,%\N,*" ",*' ',=.0,<.0,>.0,="",=' ',^" " |
from table: | .%20,%20. |
table limit: | ```` |
mssql
Injection
Allowed symbols
Any place | %00,%01,%02,%03,%04,%05,%06,%07,%08,%09,%0A,%0B,%0C,%0D,%0E,%0F,%10,%11,%12,%13,%14,%15,%16,%17,%18,%19,%1A,%1B,%1C,%1D,%1E,%1F,%20 |
-1 union | `.,e,*0,%1,*,-0,-,^0 |
union select | %C2%85,%C2%A0 |
select null | -,~,/,\*,\ABC,%,\#,\#,\_,%C2%85,%C2%A0,' '#,' '_,%2B |
column from | 'a'=\ |
from table | . |
windows文件名不可包含的特殊字符
\/:*?"<>|
3.WAF绕过实战案例
针对常见的WAF,可以提前做些绕过方式储备,这样打点会比较高效一些。
3.1.脏数据填充绕WAF
Content-Type: multipart/form-data;此处填充12187以上个字符Boundary=---------------------------1750181627257789624103726579Content-Type: multipart/form-data;Boundary此处填充12187以上个字符=---------------------------1750181627257789624103726579
3.2.SQL注入绕WAF
以DVWA+安全狗的环境为例,默认payload会被拦截:
可以利用payload = 1'+/*!33441and*/+1=1%23
绕过安全狗进行检测:
3.3.双写filename绕WAF
这个场景是绕waf传压缩文件,有个坑点是用专业版传压缩文件,传上去之后解压出来webshell无法利用,用社区版burp可以规避这个上传后文件无法利用的情况。
3.4.文件上传绕WAF思路
截断思路,例如将filename前加%00[空格][换行]特殊字符思路,例如将form-data; 修改为~form-data; 或者将form-data 修改为 f+orm-d+ata大小写敏感思路,例如将content-Type 修改为content-Type畸形格式思路,例如将form-data冒号及后面的属性删掉 Content-Disposition: form-data; name= "file"; 或者 调换name和filename属性的顺序空格思路,例如将Content-Type各处后面加入空格Content-Type : multipart/form-data ; boundary =换行思路,例如将filename=后面换行,将文件名每个字符都换行畸形数据包思路,例如将content-Type: image/jpeg删掉特殊协议思路,例如通过chunked编码提交转义符号思路,例如设置boundary="aa\ "bbb"特殊字符扩展名思路,例如fi1ename=file_name: .php,除了:外还有' " \ \' \" ; [空格][换行]重复参数思路,例如多个boundary,或者多个filename等特殊字符编码思路,例如使用ibm037、utf-32等编码
4.参考文档
1、WAF介绍:https://www.cnblogs.com/realjimmy/p/12937247.html
2、WAF攻防研究之四个层次Bypass WAF:https://xz.aliyun.com/t/15/
3、WAF攻防实战笔记v1.0--Bypass
4、细说——WAF:https://blog.csdn.net/weixin_44288604/article/details/120709418
5、隐藏资产探测-https://www.cnblogs.com/Rain99-/p/13756032.html