util.xml: Do not allow doctypes, comments or processing instructions
Yes. This is as bad as it sounds. CVE pending.
In Prosody itself, this only affects mod_websocket, which uses util.xml
to parse the <open/> frame, thus allowing unauthenticated remote DoS
using Billion Laughs. However, third-party modules using util.xml may
also be affected by this.
This commit installs handlers which disallow the use of doctype
declarations and processing instructions without any escape hatch. It,
by default, also introduces such a handler for comments, however, there
is a way to enable comments nontheless.
This is because util.xml is used to parse human-facing data, where
comments are generally a desirable feature, and also because comments
are generally harmless.
#!/usr/bin/env lua
-- Prosody IM
-- Copyright (C) 2008-2009 Waqas Hussain
--
-- This project is MIT/X11 licensed. Please see the
-- COPYING file in the source package for more information.
--
package.path = package.path..";../?.lua";
package.cpath = package.cpath..";../?.so"; -- needed for util.pposix used in datamanager
local my_name = arg[0];
if my_name:match("[/\\]") then
package.path = package.path..";"..my_name:gsub("[^/\\]+$", "../?.lua");
package.cpath = package.cpath..";"..my_name:gsub("[^/\\]+$", "../?.so");
end
-- ugly workaround for getting datamanager to work outside of prosody :(
prosody = { };
prosody.platform = "unknown";
if os.getenv("WINDIR") then
prosody.platform = "windows";
elseif package.config:sub(1,1) == "/" then
prosody.platform = "posix";
end
local parse_xml = require "util.xml".parse;
-----------------------------------------------------------------------
package.loaded["util.logger"] = {init = function() return function() end; end}
local dm = require "util.datamanager"
dm.set_data_path("data");
local arg = ...;
local help = "/? -? ? /h -h /help -help --help";
if not arg or help:find(arg, 1, true) then
print([[Openfire importer for Prosody
Usage: openfire2prosody.lua filename.xml hostname
]]);
os.exit(1);
end
local host = select(2, ...) or "localhost";
local file = assert(io.open(arg));
local data = assert(file:read("*a"));
file:close();
local xml = assert(parse_xml(data));
assert(xml.name == "Openfire", "The input file is not an Openfire XML export");
local substatus_mapping = { ["0"] = "none", ["1"] = "to", ["2"] = "from", ["3"] = "both" };
for _,tag in ipairs(xml.tags) do
if tag.name == "User" then
local username, password, roster;
for _,tag in ipairs(tag.tags) do
if tag.name == "Username" then
username = tag:get_text();
elseif tag.name == "Password" then
password = tag:get_text();
elseif tag.name == "Roster" then
roster = {};
local pending = {};
for _,tag in ipairs(tag.tags) do
if tag.name == "Item" then
local jid = assert(tag.attr.jid, "Roster item has no JID");
if tag.attr.substatus ~= "-1" then
local item = {};
item.name = tag.attr.name;
item.subscription = assert(substatus_mapping[tag.attr.substatus], "invalid substatus");
item.ask = tag.attr.askstatus == "0" and "subscribe" or nil;
local groups = {};
for _,tag in ipairs(tag) do
if tag.name == "Group" then
groups[tag:get_text()] = true;
end
end
item.groups = groups;
roster[jid] = item;
end
if tag.attr.recvstatus == "1" then pending[jid] = true; end
end
end
if next(pending) then
roster[false] = { pending = pending };
end
end
end
assert(username and password, "No username or password");
local ret, err = dm.store(username, host, "accounts", {password = password});
print("["..(err or "success").."] stored account: "..username.."@"..host.." = "..password);
if roster then
local ret, err = dm.store(username, host, "roster", roster);
print("["..(err or "success").."] stored roster: "..username.."@"..host.." = "..password);
end
end
end