JavaScript已经成为现代Web浏览器开发中最普遍的技术之一。之前在《PTES-信息收集》中讲到源代码审计也可能获取到一些敏感信息,但有的测试人员只审计html源代码,而忽略审计js文件里的源代码,今天我们就来讲一讲审计js文件都能获取到哪些信息。
通过审计js文件源代码能获取到的信息,我个人将其分为以下四类:敏感信息、业务逻辑、加密算法、Ajax请求。
敏感信息
审计js文件最直观的就是很有可能获取到账号密码、配置等敏感信息。
HTML定义页面结构,CSS定义装修样式,而JavaScript定义前端工作内容,主要是监听事件执行动作,比如逻辑判断,亦或是向服务器发送请求等等。而要正确执行这些动作,通常都会进行一些简单的配置。
最常见的配置就是设置网站根路径,不过也经常遇到直接在js文件里配置账号密码的情况;更有甚者,有的开发人员直接在js文件里定义SQL语句,然后由前端发送到服务器执行。这不仅泄露了数据库信息,也提高了SQL注入漏洞攻击的风险。
以上是获取敏感信息的一种情况,还有一种情况就是注释。
为了更好的维护代码、调试代码,我们经常在源码中见到各种注释。比如为了方便调试,直接把账号密码写在Javascript代码中,调试完后,只是把这条语句注释掉而非删掉;有时候系统更迭,推出一些新的接口,开发人员把老接口注释掉使用新的接口,但服务器端并没有把老接口关掉等等。
以上情况不仅让我们搜集到了更多目标信息,也增加了系统攻击面,只能说:这些开发人员真他娘的是个人才。
业务逻辑
除了能获取到敏感信息,审计js文件还可以获取到Web系统的一些业务逻辑。
说到业务逻辑,就要牵扯到前端校验。有些系统只做了前端校验,只要响应包满足条件,就会执行下一步操作,服务器端不会再进一步检验接下来的请求是否有问题,我们只要修改响应包就可以绕过检测。
比如输入错误的账号密码,响应false,要求重新输入账号密码;我们把响应包拦截,把false改成true,前端检测到true,认为账号密码正确,发送跳转到系统内部的请求,而服务器没有再校验请求是否有问题,将系统内部页面响应给前端。
要绕过前端校验,像0/1、true/false这种很容易就能猜到该怎么修改,但有些系统可能有十多个响应码,或者响应码有多位,如果我们耐着性子一个一个去试,时间成本会很大。
比如0是用户名错误,1是密码错误,2是验证码错误,9999是登陆成功,如果我们从0开始,一个一个去试,说真的还不一定能试出来。除非先输入正确的账号密码得到正确的响应码,然后输入错误的账号密码,再用之前得到的正确响应码替换错误的响应码,尝试看是否存在前端校验的问题。
问题是:如果是安全服务,客户还有可能提供账号密码,但攻防演练、挖洞一般都是靠自己白手起家,哪里有什么账号密码给你。而前端业务逻辑通常都由js实现,基本都可以通过代码审计来确定如何构造响应包来尝试绕过前端校验。我只想说:信息收集真的很重要。
加密算法
审计代码能获取到的第三类信息就是加密算法。在当今的大环境下,企业越来越重视安全,运用了各种防护手段来提高系统的安全系数,提高攻击者的攻击成本,其中就有加密机制。
可能很多测试人员在测试的时候,抓包发现参数值都是加密的,就这么放过了。但这种前端加密都是可以在js中找到加密算法的,要么在html文件的js中,要么包含在js文件中。
当然我们也可以不需要知道加密算法到底是怎么样的,直接输入测试的明文Payload,系统自然会利用加密算法输出对应的加密数据。这个Payload不行,我们再输入下一个明文Payload去测试。
以上的方式时间成本会增加很多,而且有时候系统会先检测输入的数据是否含有非法字符,没有非法字符才会加密发送到服务器端。而只要我们找到了对应的加密算法,就可以手工构造或者编写脚本批量生成我们想要的加密数据,进而去测试系统是否存在漏洞。
据目前工作经历来看,涉及到加密的参数,存在漏洞的几率要高很多。因为前端加密后,后端必须解密才能使用。但很多时候,系统的安全检测只在请求传过来的时候,加密时没有检测到非法字符就绕过了检测,后端解密之后就可以肆意横行。
Ajax请求
审计js文件能获取到的信息还有Ajax请求,也是漏洞重灾区。而Ajax请求的调用通常很隐蔽,且Ajax请求的触发条件无规律,很容易被遗漏或忽略。
Ajax请求通常是被各个事件触发调用,而这些事件的触发往往需要满足一定条件。但很多时候,即便事件触发条件未满足,我们去构造Ajax请求的数据包发送给服务器端,依然可以得到服务器的响应。
服务器访问控制不当,并没有去检测用户是否是通过正常业务逻辑发送Ajax请求,也没有去检测用户是否有操作权限。服务器认为客户端发过来的请求都是正确的,这又要提一下安全的本质:一切输入都是不安全的。而且每一个Ajax请求都对应一个接口,这又增加了攻击面,每个请求都可能形成独立的攻击过程。
举个真实案例:修改密码,先要通过短信验证码校验才能修改新密码。只有管理员才能修改密码,修改密码的Ajax请求由修改密码功能调用,普通用户没有这个功能。(某些原因,没有截图,意淫一下吧)
开发人员认为所有的用户都会按照系统业务逻辑一步一步触发请求,先点击发送验证码,然后填写验证码进行校验,通过校验后跳转到修改密码的页面,输入新密码,最后修改成功。而且只有管理员有这个功能页面。
但通过代码审计,直接在js文件中找到了修改密码的Ajax请求,请求参数只有新密码。现在我们已经找到了修改密码的请求,那么我们可以跳过前面一系列的校验和请求,只需要构造出修改密码请求,就可以修改密码。
而且这个Ajax请求是写在js文件里的,系统并不会因为用户权限的不同而调用不同的js文件,所以普通用户也能够查到到对应的js代码,不存在管理员一说。
本文简单讲述了审计js文件源代码能获取到的四类信息:敏感信息、业务逻辑、加密算法、Ajax请求。
当然,这也仅仅是我个人的归类,审计代码能获取到的信息远不止这些,需要大家去深入挖掘,我纯碎起一个抛砖引玉的作用。
忙里偷闲,差不多半年才写了一篇文章。这波行动差不多要结束了,接下来就是国庆重保。国庆重保后不出意外的话应该,可能,也许会稍微轻松一点,争取一个月能写篇文章吧。感谢大家的关注!
在之前的一篇文章《前端开发过程中的HTML规范,来学习一下吧》中,我们有讲过前端开发过程中需要注意到的HTML规范问题,今天这篇文章我们继续来看下关于Javascript的规范问题。
Javascript
我们总是会在Javascript文件中定义变量,但是一不留神就会将其定义成全局变量,如果引用的外部JS文件较多,很容易出现全局变量污染的情况。
我们不推荐总是在全局空间定义变量的行为,因为所有在全局空间定义的变量都是挂在window对象上,很容易出现变量污染,如下所示。
不推荐-全局变量
IIFE就可以防止出现全局变量污染的情况,IIFE是立即执行的函数表达式,在IIFE内部会创建一个封闭的作用域,内部定义的变量不会影响外部的执行环境,而且可以通过参数传递的形式引用外部变量,最重要的一点是在函数执行完后会立即释放占用的内存。
我们推荐使用下面这种写法。
推荐写法-IIFE
为了避免全局变量的干扰,我们建议所有脚本文件都从IIFE开始。
我们都知道之所以叫立即执行的函数表达式,是因为在函数表达式后面会多一个执行的括号。这个执行的括号可以出现在两个地方,不管是在内部还是外部,都是有效的。但是为了让整个函数表达式看起来像一个整体,我们推荐将括号写在里面。
因此我们不推荐以下写法。
不推荐写法
而推荐以下写法。
推荐写法
同样,我们可以通过参数传递的形式引用外部变量。
引用外部变量
我们都知道在ES5中是没有块级作用域概念的,只有函数级作用域,而且由于变量提升的存在,在函数内部声明的变量都会提升至函数顶部,这就会造成一些难以预料的问题。
首先我们来看看变量提升是什么样的情况?看看下面一段代码。
变量提升
上面这段代码返回的结果是undefined,并不是'Hello Shenzhen',这是因为变量v会在函数内部被提升至函数顶部,实际执行的其实是下面这段代码。
实际执行
为了降低变量提升所带来的编码风险,我们应该手动声明定义的变量和方法,并把其放在函数顶部。
我们不推荐以下写法。
不推荐写法
我们推荐以下写法。
推荐写法
在编写判断相等类型的条件语句时,总是使用严格相等(===),这样可以避免Javascript在执行类型转换时带来的问题。
我们看下面一个例子,定义一个函数,传入一个数字,如果等于5,则将这个数加5返回。如果不使用严格等于,在传入一个字符串'5'后,会返回'55'。
没有使用严格相等
因此,我们推荐在使用相等判断时都采用严格相等(===)。
强烈建议在所有结束语句后面加上分号,如果不加上分号会引起一些很难发现的问题。我们看看下面一段代码。
代码
在上面这段代码执行后,我们发现即使resultOperation()函数返回-1,与-1相等,后面的method方法仍然被调用。
这是因为在上面定义的数组末尾没有加上分号,这个数组会与下面一行的-1当做表达式执行,任何非空数组-1都会返回NaN,NaN与resultOperation的返回结果-1不相等,因此后面的method方法会被执行。
省略分号不写,不只是会出现上述的问题,还有很多,这里不一一列举。
因此,建议在每个结尾的语句后加上分号,养成一个好的习惯。
闭包作为前端面试题中必不可少的知识点是应该要掌握的,而且在前端开发中经常会涉及到,关于闭包的问题,在我写的一篇文章《前端面试中不可逃避的闭包问题,你真的了解吗?》中有详细介绍,大家可以好好看下。
今天这篇文章详细的介绍了在前端开发过程中涉及到的Javascript规范问题,可能还不够全面,大家可以自行补充。
前端项目开发的时候,系统支持文件下载是前端开发中常用到的功能之一,当用户访问我们的网站时发现有自己需要的资源时可以将资源下载下来。文件下载主要有两种形式,一种是通过文件地址下载,该文件可以存放在前端或者后端。另一种则是通过文件流下载,前端通过发送请求给后端并获取后端文件流进行下载。
download属性:是HTML5中的a标签的新特性,用来规定被下载的超链接目标。在a标签中如果没有申明download属性的时a标签的默会链接跳转进行预览(如txt , jpg , pdf ),当前浏览器不支持预览的文件时则出现下载。当申明了download属性之后浏览器会对href属性链接的文件进行下载。download属性与不支持H5的低版本浏览器不兼容且仅限于同源文件,如果是非同源download属性会失效。比如引用第三方的网站内容、引用前后端分离的服务器内容、甚至本地测试引用的本地文件,download都会不起作用。如果你想测试该功能可以在本地开一个服务,将文件放同一服务中测试就可以了。
直接使用a标签时只要在a标签中添加download属性,如果是非a标签的话可以在出发事件的时候通过JavaScript来创建一个隐藏a标签下载,当我们点击时触发隐藏的a标签下载事件。这里使用appendChild和removeChild的处理是为了兼容Firefox浏览器。
需要了解XMLHttpRequest可以参考文章:JavaScript实战001:XMLHttpRequest使用入门,这里我利用XMLHttpRequest对象来发送请求,用blob类型来接受后台发过来的二进制类型文件。然后模拟a标签创建隐藏的下载链接,通过URL.createObjectURL()方法创建一个指向blob对象的URL地址。
iframe是HTML标签元素,可以用来创建内联框架。iframe提供了src属性用来规定在 iframe 中显示的文档的 URL,我们可以直接将文件地址抛给iframe,也可以赋值文件流地址给iframe。功能实现跟a标签有点相似,创建一个隐藏的iframe标签来实现文件的下载功能。使用文件地址下载需要注意的是浏览器支持预览的文件类型无法下载(比如图片、PDF文档、text文本等会直接打开文件预览),文件流下载只需将请求接口赋给src属性,iframe会自动去请求该文件实现下载。
window.open()方法用于打开一个新的浏览器窗口或查找一个已命名的窗口,也可以用它来实现文件下载功能。而且这个比iframe更简单,直接将文件地址或者请求接口赋给window.open(url)方法即可实现文件下载功能。但是有个缺点就是每次下载都会打开一个新的窗口来实现下载(想不跳转可以尝试window.location.assign()方法,用于加载一个新的文档),而且如果使用文件地址下载的是浏览器支持预览的文件类型无法下载(比如图片、PDF文档、text文本等会直接打开文件预览)。
form表单是个比较常用的html表签,用户提交用户信息,比如常见的登录、注册界面大部分都是通过form表单进行数据提交的。form表单所有要提交的数据都必须放在form标签中,method属性定义提交的方法(有get和post两种提交方法),action属性定义请求的地址。form标签中支持input、menus、textarea、fieldset、legend 和 label 等元素,通过submit向服务器提交数据。这里我创建了form表单和input框,input用于输入请求的参数,form用于提交数据请求。
这里提供下Django的后台文件请求接口,以上文件请求都是基于该接口实现的。接收请求方法为GET,请求参数为id(数据库存储的文件id),采用FileResponse方式返回的文件流信息(具体实现功能可以参考文章:Django实战013:各种文件下载功能实现详解)。
下载的方式方法有很多,以上只是JavaScript中常见的几种下载方式。其实用ajax或者axios也可以实现下载,但是万变不离其中,会JavaScript下载害怕不会别的么。以上下载方式个人觉得还是iframe比较简单方便,请求最好还是通过文件流来实现,相对文件地址下载会更安全些。
更多前端技巧可以参考专栏:Vue实战技巧
*请认真填写需求信息,我们会在24小时内与您取得联系。