日常我们在工作时做安全测试或者日常的漏洞挖掘中,往往会遇到请求加密,参数加密的情况,而且绝大部分都是前端加密的情况,那么我们就可以手动调试跟踪去解决这个加密问题,对我们自己要发送的数据进行编码来帮助我们进行测试
一般定位都会采用关键函数跟踪的方式,流程如下:
选择需要定位的标签,一般都是提交、登录、保存等按钮,通过这些按钮再去查找其中对应的事件函数,然后到调试器栏ctrl+shift+f全局搜索函数名定位,定位到关键函数后,对其打个断点,然后跟一跟基本就能确定走了什么加密流程。
随后就需要手工在控制台对函数进行调用对自己要测试的数据进行编码,当然也可以把这些js文件保存下来,通过python脚本调用js函数。
burp也有一个jsEncrypter插件,详情可以去github主页瞅瞅,是把js保存下来然后使用phantomjs调用加密函数对数据进行加密。
登录时抓包查看到数据包如下,可以看到passwd字段进行了明显的加密
一般密码加密会在登录事件进行加密处理,那么我们从这里开始跟踪,选中登录按钮右键检查元素定位。
可以看有一个checkusernew()函数,转到调试器,直接ctrl+sgift+f全局搜索这个函数。
然后转到函数定义处,直接点击右侧加个断点
然后重新走登录流程,自动运行到断点处,然后F10 不过、F11 步入、shift+F11 步出。
对关键函数如hex_md5和Encrypt需要跟进去看下做了什么。
跟进encrypt函数
我们需要去跟进获取加密的key和iv么,当然是不需要的,我们只需要在控制台调用执行即可
但是如何批量呢?有两种方案,一个直接在控制台写js代码或者拉取相应JS,调用python的PyExecJS模块。
我更倾向于直接在控制台调用
let arr = ['a', 'b', 'c', 'd', 'e'] //定义一个列表
var a = "" //定义一个空字符串
for (let i in arr) {a = a + Encrypt(hex_md5(arr[i])) + "\n"} //循环跑
最后输出字符a即可
在测试过程遇到的情况往往可能要复杂得多,跟了半天都没有跟到关键函数也有可能发生。再加上web前端项目打包导致函数更加不好找。
这个时候就要果断放弃,打开下一个。
Web标准构成
主要包括:结构、表现和行为三个方面
<!DOCTYPE>文档类型声明,作用就是告诉浏览器使用哪种HTML版本来显示网页;
lang用来定义当前文档显示的语言;
字符集(cahracter set)是多个字符的集合,以便计算机能够识别和存储各种文字;
charset常用的值有:GB2312,BIG5,GBK和UTF-8,其中UTF-8也被称为万国码,基本包含了全世界所有国家需要用到的字符集。
HTML常用标签
标题标签:<h1></h1>,<h2></h2>,<h3></h3>,<h4></h4>,<h5></h5>,<h6></h6>(字体大小依次来变小)
段落标签:<p></p>
换行标签:<br/>
加粗标签:<strong></strong>或<b></b>(推荐使用strong标签,因为atrong效果更好)
倾斜标签:<em></em>;<i></i>(推荐使用em标签,因为em效果更好)
删除线标签: <del></del>或<s></s>(推荐使用del标签,因为del效果更好)
下划线标签:<ins></ins>或<u></u>(推荐使用ins标签,因为ins效果更好)
div和span标签:是一个盒子,用来装内容,div是块级标签,span是行内标签
图像标签:<img src="图像 URL"/>
水平线标记:<hr/>
图像路径
相对路径:以引用文件所在位置为参考基础,而建立出的目录路径
绝对路径:是指目录下的绝对位置,直接到达目标位置,通常是从盘符开始的路径
超链接标签:<a href="跳转地址" target="目标窗口的弹出方式"> </a>作用从一个页面跳转到另外一个页面
描点连接:配合id选择器使用
注释:<!-- --!>
/**/
//
特殊字符: (空格)等
表格标签:<table></table> 作用于展示数据
<th></th>----表头单元格
<tr></tr> ----行
<td></td>----单元格,列
row="行数" cols="每行的字数"
<thead></thead>标签用于表格的头部区域;<tbody></tbody>标签用于表格的主题区域
无序列表:<ul><li></li></ul>
有序列表:<ol><li></li></ol>
自定义列表:<dl><dt><dd></dd></dt></dl>-----------dt是用于标题,dd是内容
表单的组成:
在HTML中,表单通常由表单域、表单控件和表单信息
from用于定义表单域,以实现用户信息的收集和传递
表单域标签:<from action="url地址" method="提交方式" name="表单域名称"></form>
<input type="属性值"/>
其中name属性是表单的名字,相同的名字可以实现多选一
<lable>标签:用于绑定一个表单元素,当点击《lable》标签内的文本时,浏览器就会自动将焦点(光标)转到或者选择对应的表单元素上,用来增加用户体验
<select>表单元素:下拉表单元素,
<select>
<option></option>
</select>
select中至少包含一对option
textarea表单元素:<textarea></textarea>-----文本域标签
做登录界面可以用表格来做,能直接对齐
后续会更新后面的知识
自己是一名从事了多年开发的web前端老程序员,目前辞职在做自己的web前端私人定制课程,今年年初我花了一个月整理了一份最适合2019年学习的web前端学习干货,各种框架都有整理,送给每一位前端小伙伴,想要获取的可以关注我的头条号并在后台私信我:前端,即可免费获取。
函数是什么
函数是完成某个特定功能的一组语句。如没有函数,完成任务可能需要五行、十行、甚至更多的代码。这时我们就可以把完成特定功能的代码块放到一个函数里,直接调用这个函数,就省重复输入大量代码的麻烦。
函数可以概括为:一次封装,四处使用。
函数的定义
函数的定义方式通常有三种:函数声明方式、函数表达式、 使用Function构造函数 。
函数声明方式
语法:
function 函数名(参数1,参数2,...){ //要执行的语句 }
例:
// 声明 function sum(num1, num2) { return num1 + num2; } // 调用 sum(1, 2) // 3
函数表达式
语法:
var fn = function(参数1,参数2,...){ //要执行的语句 };
例:
// 声明 var sum = function(num1,num2){ return num1+num2; }; // 调用 sum(1, 2) // 3
使用Function构造函数
Function构造函数可以接收任意数量的参数,最后一个参数为函数体,其他的参数则枚举出新函数的参数。其语法为:
new Function("参数1","参数2",...,"参数n","函数体");
例:
// 声明 var sum = new Function("num1","num2","return num1+num2"); // 调用 sum(1, 2) // 3
三种定义方式的区别
三种方式的区别,可以从作用域、效率以及加载顺序来区分。
从作用域上来说,函数声明式和函数表达式使用的是局部变量,而 Function()构造函数却是全局变量,如下所示:
var name = '我是全局变量 name'; // 声明式 function a () { var name = '我是函数a中的name'; return name; } console.log(a()); // 打印: "我是函数a中的name" // 表达式 var b = function() { var name = '我是函数b中的name'; return name; // 打印: "我是函数b中的name" } console.log(b()) // Function构造函数 function c() { var name = '我是函数c中的name'; return new Function('return name') } console.log(c()()) // 打印:"我是全局变量 name",因为Function()返回的是全局变量 name,而不是函数体内的局部变量。
从执行效率上来说,Function()构造函数的效率要低于其它两种方式,尤其是在循环体中,因为构造函数每执行一次都要重新编译,并且生成新的函数对象。
来个例子:
var start = new Date().getTime() for(var i = 0; i < 10000000; i++) { var fn = new Function('a', 'b', 'return a + b') fn(i, i+1) } var end = new Date().getTime(); console.log(`使用Function构造函数方式所需要的时间为:${(end - start)/1000}s`) // 使用Function构造函数方式所需要的时间为:8.646s start = new Date().getTime(); var fn = function(a, b) { return a + b; } for(var i = 0; i < 10000000; i++) { fn(i, i+1) } end = new Date().getTime(); console.log(`使用表达式的时间为:${(end - start)/1000}s`) // 使用表达式的时间为:0.012s
由此可见,在循环体中,使用表达式的执行效率比使用 Function()构造函数快了很多很多。所以在 Web 开发中,为了加快网页加载速度,提高用户体验,我们不建议选择 Function ()构造函数方式来定义函数。
最后是加载顺序,function 方式(即函数声明式)是在 JavaScript 编译的时候就加载到作用域中,而其他两种方式则是在代码执行的时候加载,如果在定义之前调用它,则会返回 undefined:
console.log(typeof f) // function console.log(typeof c) // undefined console.log(typeof d) // undefined function f () { return 'JS 深入浅出' } var c = function () { return 'JS 深入浅出' } console.log(typeof c) // function var d = new Function('return "JS 深入浅出"') console.log(typeof d) // function
函数的参数和返回值
函数的参数-arguments
JavaScript 中的函数定义并未指定函数形参的类型,函数调用也未对传入的实参值做任何类型检查。实际上,JavaScript 函数调用甚至不检查传入形参的个数。
function sum(a) { return a + 1; } console.log(sum(1)); // 2 console.log(sum('1')); // 11 console.log(add()); // NaN console.log(add(1, 2)); // 2
当实参比形参个数要多时,剩下的实参没有办法直接获得,需要使用即将提到的arguments对象。
JavaScript中的参数在内部用一个数组表示。函数接收到的始终都是这个数组,而不关心数组中包含哪些参数。在函数体内可以通过arguments对象来访问这个参数数组,从而获取传递给函数的每一个参数。arguments对象并不是Array的实例,它是一个类数组对象,可以使用方括号语法访问它的每一个元素。
function sum (x) { console.log(arguments[0], arguments[1], arguments[2]); // 1 2 3 } sum(1, 2, 3)
arguments对象的length属性显示实参的个数,函数的length属性显示形参的个数。
function sum(x, y) { console.log(arguments.length); // 3 return x + 1; } sum(1, 2, 3) console.log(sum.length) // 2
函数的参数-arguments
JavaScript 中的函数定义并未指定函数形参的类型,函数调用也未对传入的实参值做任何类型检查。实际上,JavaScript 函数调用甚至不检查传入形参的个数。
function sum(a) { return a + 1; } console.log(sum(1)); // 2 console.log(sum('1')); // 11 console.log(add()); // NaN console.log(add(1, 2)); // 2
函数的参数-同名参数
在非严格模式下,函数中可以出现同名形参,且只能访问最后出现的该名称的形参。
function sum(x, x, x) { return x; } console.log(sum(1, 2, 3)) // 3
而在严格模式下,出现同名形参会抛出语法错误。
function sum(x, x, x) { 'use strict'; return x; } console.log(sum(1, 2, 3)) // SyntaxError: Duplicate parameter name not allowed in this context
函数的参数-参数个数
当实参比函数声明指定的形参个数要少,剩下的形参都将设置为undefined值。
function sum(x, y) { console.log(x, y); } sum(1); // 1 undefined
函数的返回值
所有函数都有返回值,没有return语句时,默认返回内容为undefined。
function sum1 (x, y) { var total = x + y } console.log(sum1()) // undefined function sum2 (x, y) { return x + y } console.log(sum2(1, 2)) // 3
如果函数调用时在前面加上了new前缀,且返回值不是一个对象,则返回this(该新对象)。
function Book () { this.bookName = 'JS 深入浅出' } var book = new Book(); console.log(book); // Book { bookName: 'JS 深入浅出' } console.log(book.constructor); // [Function: Book]
如果返回值是一个对象,则返回该对象。
function Book () { return {bookName: JS 深入浅出} } var book = new Book(); console.log(book); // { bookName: 'JS 深入浅出' } console.log(book.constructor); // [Function: Book]
函数的调用方式
JS 一共有4种调用模式:函数调用、方法调用、构造器调用和间接调用。
函数调用
当一个函数并非一个对象的属性时,那么它就是被当做一个函数来调用的。对于普通的函数调用来说,函数的返回值就是调用表达式的值
function sum (x, y) { return x + y; } var total = sum(1, 2); console.log(total); // 3
使用函数调用模式调用函数时,非严格模式下,this被绑定到全局对象;在严格模式下,this是undefined
// 非严格模式 function whatIsThis1() { console.log(this); } whatIsThis1(); // window // 严格模式 function whatIsThis2() { 'use strict'; console.log(this); } whatIsThis2(); // undefined
方法调用
当一个函数被保存为对象的一个属性时,称为方法,当一个方法被调用时,this被绑定到该对象。
function printValue(){ console.log(this.value); } var value=1; var myObject = {value:2}; myObject.m = printValue; //作为函数调用 printValue(); //作为方法调用 myObject.m();
咱们注意到,当调用printValue时,this绑定的是全局对象(window),打印全局变量value值1。但是当调用myObject.m()时,this绑定的是方法m所属的对象Object,所以打印的值为Object.value,即2。
构造函数调用
如果函数或者方法调用之前带有关键字new,它就构成构造函数调用。
function fn(){ this.a = 1; }; var obj = new fn(); console.log(obj.a);//1
参数处理:一般情况构造器参数处理和函数调用模式一致。但如果构造函数没用形参,JavaScript构造函数调用语法是允许省略实参列表和圆括号的。
如:下面两行代码是等价的。
var o = new Object(); var o = new Object;
函数的调用上下文为新创建的对象。
function Book(bookName){ this.bookName = bookName; } var bookName = 'JS 深入浅出'; var book = new Book('ES6 深入浅出'); console.log(bookName);// JS 深入浅出 console.log(book.bookName);// ES6 深入浅出 Book('新版JS 深入浅出'); console.log(bookName); // 新版JS 深入浅出 console.log(book.bookName);// ES6 深入浅出
1.第一次调用Book()函数是作为构造函数调用的,此时调用上下文this被绑定到新创建的对象,即 book。所以全局变量bookName值不变,而book新增一个属性bookName,值为'ES6 深入浅出';
2.第二次调用Book()函数是作为普通函数调用的,此时调用上下为this被绑定到全局对象,在浏览器中为window。所以全局对象的bookNam值改变为' 新版JS 深入浅出',而book的属性值不变。
间接调用
JS 中函数也是对象,函数对象也可以包含方法,call()和apply()方法可以用来间接地调用函数。
这两个方法都允许显式指定调用所需的this值,也就是说,任何函数可以作为任何对象的方法来调用,哪怕这个函数不是那个对象的方法。两个方法都可以指定调用的实参。call()方法使用它自有的实参列表作为函数的实参,apply()方法则要求以数组的形式传入参数。
var obj = {}; function sum(x,y){ return x+y; } console.log(sum.call(obj,1,2));//3 console.log(sum.apply(obj,[1,2]));//3
词法(静态)作用域与动态作用域
作用域
通常来说,一段程序代码中所用到的名字并不总是有效/可用的,而限定这个名字的可用性的代码范围就是这个名字的作用域。
词法作用域
词法作用域,也叫静态作用域,它的作用域是指在词法分析阶段就确定了,不会改变。而与词法作用域相对的是动态作用域,函数的作用域是在函数调用的时候才决定的。
来个例子,如下代码所示:
var blobal1 = 1; function fn1 (param1) { var local1 = 'local1'; var local2 = 'local2'; function fn2(param2) { var local2 = 'inner local2'; console.log(local1) console.log(local2) } function fn3() { var local2 = 'fn3 local2'; fn2(local2) } fn3() } fn1()
当浏览器看到这样的代码,不会马上去执行,它会先生成一个抽象语法树。上述代码生成的抽象语法树大概是这样的:
执行fn1函数,fn1中调用 fn3(),从fn3函数内部查找是否有局部变量 local1,如果没有,就根据抽象树,查找上面一层的代码,也就是 local1 等于 'local1' ,所以结果会打印 'local1'。
同样的方法查找是否有局部变量 local2,发现当前作用域内有local2变量,所以结果会打印 'inner local2。
思考
有如下的代码:
var a = 1; function fn() { console.log(a) }
两个问题:
对于第一个问题:
分析一个语法,就能确定函数 fn里面的 a 就是外面的 a。
对于第二个问题:
函数 fn 里面的变量 a的值, 不一定是外面的变量 a的值,假设咱们这样做:
var a = 1; function fn() { console.log(a) } a = 2 fn()
这时候当咱们执行 fn() 的时候,打印 a 的值为 2。所以如果没有看到最后,一开始咱们是不知道打印的 a 值到底是什么。
所以词法作用域只能确定变量所在位置,并不能确定变量的值。
调用栈(Call Stack)
什么是执行上下文
执行上下文就是当前JavaScript代码被解析和执行是所在环境的抽象概念,JavaScript中运行任何的代码都是在执行上下文中运行。
执行上下文的类型,主要有两类:
调用栈
调用栈,具有LIFO(Last in, First out 后进先出)结构,用于存储在代码执行期间创建的所有执行上下文。
当JavaScript引擎首次读取脚本时,会创建一个全局执行上下文并将其push到当前执行栈中。每当发生函数调用时,引擎都会为该函数创建一个新的执行上下文并push到当前执行栈的栈顶。
引擎会运行执行上下文在执行栈栈顶的函数,根据LIFO规则,当此函数运行完成后,其对应的执行上下文将会从执行栈中pop出,上下文控制权将转到当前执行栈的下一个执行上下文。
看看下面的代码:
var myOtherVar = 10; function a() { console.log('myVar', myVar); b(); } function b() { console.log('myOtherVar', myOtherVar); c(); } function c() { console.log('Hello world!'); } a(); var myVar = 5;
有几个点需要注意:
当它被执行时你期望发生什么?是否发生错误,因为b在a之后声明或者一切正常?console.log打印的变量又是怎么样?
以下是打印结果:
"myVar" undefined "myOtherVar" 10 "Hello world!"
1. 变量和函数声明(创建阶段)
第一步是在内存中为所有变量和函数分配空间。但请注意,除了undefined之外,尚未为变量分配值。因此,myVar在被打印时的值是undefined,因为JS引擎从顶部开始逐行执行代码。
函数与变量不一样,函数可以一次声明和初始化,这意味着它们可以在任何地方被调用。
所以以上代码在创建阶段时,看起来像这样子:
var myOtherVar = undefined var myVar = undefined function a() {...} function b() {...} function c() {...}
这些都存在于JS创建的全局上下文中,因为它位于全局作用域中。
在全局上下文中,JS还添加了:
2. 执行
接下来,JS 引擎会逐行执行代码。
myOtherVar = 10在全局上下文中,myOtherVar被赋值为10
已经创建了所有函数,下一步是执行函数 a()
每次调用函数时,都会为该函数创建一个新的上下文(重复步骤1),并将其放入调用堆栈。
function a() { console.log('myVar', myVar) b() }
如下步骤:
下面调用堆栈的执行示意图:
创建全局上下文,全局变量和函数。
来源:https://mp.weixin.qq.com/s/4JmQTcvXBew8Eiz8hkYsWQ
作者:前端小智
*请认真填写需求信息,我们会在24小时内与您取得联系。