forked from I2P_Developers/i2p.i2p
Console: Validate host header (thx Kevin Froman)
This commit is contained in:
@ -0,0 +1,127 @@
|
|||||||
|
package net.i2p.router.web;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import javax.servlet.ServletException;
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
|
||||||
|
import net.i2p.I2PAppContext;
|
||||||
|
import net.i2p.data.DataHelper;
|
||||||
|
import net.i2p.util.Log;
|
||||||
|
|
||||||
|
import org.apache.http.conn.util.InetAddressUtils;
|
||||||
|
|
||||||
|
import org.eclipse.jetty.server.Request;
|
||||||
|
import org.eclipse.jetty.server.handler.HandlerWrapper;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Block certain Host headers to prevent DNS rebinding attacks.
|
||||||
|
*
|
||||||
|
* This Handler wraps the ContextHandlerCollection, which handles
|
||||||
|
* all the webapps (not just routerconsole).
|
||||||
|
* Therefore, this protects all the webapps.
|
||||||
|
*
|
||||||
|
* @since 0.9.32
|
||||||
|
*/
|
||||||
|
public class HostCheckHandler extends HandlerWrapper
|
||||||
|
{
|
||||||
|
private final I2PAppContext _context;
|
||||||
|
private final Set<String> _listenHosts;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* MUST call setListenHosts() afterwards.
|
||||||
|
*/
|
||||||
|
public HostCheckHandler(I2PAppContext ctx) {
|
||||||
|
super();
|
||||||
|
_context = ctx;
|
||||||
|
_listenHosts = new HashSet<String>(8);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the legal hosts.
|
||||||
|
* Not synched. Call this BEFORE starting.
|
||||||
|
* If empty, all are allowed.
|
||||||
|
*
|
||||||
|
* @param hosts contains hostnames or IPs. But we allow all IPs anyway.
|
||||||
|
*/
|
||||||
|
public void setListenHosts(Set<String> hosts) {
|
||||||
|
_listenHosts.clear();
|
||||||
|
_listenHosts.addAll(hosts);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Block by Host header, pass everything else to the delegate.
|
||||||
|
*/
|
||||||
|
public void handle(String pathInContext,
|
||||||
|
Request baseRequest,
|
||||||
|
HttpServletRequest httpRequest,
|
||||||
|
HttpServletResponse httpResponse)
|
||||||
|
throws IOException, ServletException
|
||||||
|
{
|
||||||
|
|
||||||
|
String host = httpRequest.getHeader("Host");
|
||||||
|
if (!allowHost(host)) {
|
||||||
|
Log log = _context.logManager().getLog(HostCheckHandler.class);
|
||||||
|
host = DataHelper.stripHTML(getHost(host));
|
||||||
|
String s = "Console request denied.\n" +
|
||||||
|
" To allow access using the hostname \"" + host + "\", add the line \"" +
|
||||||
|
RouterConsoleRunner.PROP_ALLOWED_HOSTS + '=' + host +
|
||||||
|
"\" to advanced configuration and restart.";
|
||||||
|
log.logAlways(Log.WARN, s);
|
||||||
|
httpResponse.sendError(403, s);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
super.handle(pathInContext, baseRequest, httpRequest, httpResponse);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Should we allow a request with this Host header?
|
||||||
|
*
|
||||||
|
* ref: https://en.wikipedia.org/wiki/DNS_rebinding
|
||||||
|
*
|
||||||
|
* @param host the HTTP Host header, null ok
|
||||||
|
* @return true if OK
|
||||||
|
*/
|
||||||
|
private boolean allowHost(String host) {
|
||||||
|
if (host == null)
|
||||||
|
return true;
|
||||||
|
// common cases
|
||||||
|
if (host.equals("127.0.0.1:7657") ||
|
||||||
|
host.equals("localhost:7657"))
|
||||||
|
return true;
|
||||||
|
// all allowed?
|
||||||
|
if (_listenHosts.isEmpty())
|
||||||
|
return true;
|
||||||
|
host = getHost(host);
|
||||||
|
if (_listenHosts.contains(host))
|
||||||
|
return true;
|
||||||
|
// allow all IP addresses
|
||||||
|
if (InetAddressUtils.isIPv4Address(host) || InetAddressUtils.isIPv6Address(host))
|
||||||
|
return true;
|
||||||
|
//System.out.println(host + " not found in " + s);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Strip [] and port from a host header
|
||||||
|
*
|
||||||
|
* @param host the HTTP Host header non-null
|
||||||
|
*/
|
||||||
|
private static String getHost(String host) {
|
||||||
|
if (host.startsWith("[")) {
|
||||||
|
host = host.substring(1);
|
||||||
|
int brack = host.indexOf(']');
|
||||||
|
if (brack >= 0)
|
||||||
|
host = host.substring(0, brack);
|
||||||
|
} else {
|
||||||
|
int colon = host.indexOf(':');
|
||||||
|
if (colon >= 0)
|
||||||
|
host = host.substring(0, colon);
|
||||||
|
}
|
||||||
|
return host;
|
||||||
|
}
|
||||||
|
}
|
@ -14,6 +14,7 @@ import java.util.ArrayList;
|
|||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Properties;
|
import java.util.Properties;
|
||||||
@ -140,6 +141,7 @@ public class RouterConsoleRunner implements RouterApp {
|
|||||||
private static final int MAX_IDLE_TIME = 90*1000;
|
private static final int MAX_IDLE_TIME = 90*1000;
|
||||||
private static final String THREAD_NAME = "RouterConsole Jetty";
|
private static final String THREAD_NAME = "RouterConsole Jetty";
|
||||||
public static final String PROP_DTG_ENABLED = "desktopgui.enabled";
|
public static final String PROP_DTG_ENABLED = "desktopgui.enabled";
|
||||||
|
static final String PROP_ALLOWED_HOSTS = "routerconsole.allowedHosts";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* <pre>
|
* <pre>
|
||||||
@ -360,6 +362,7 @@ public class RouterConsoleRunner implements RouterApp {
|
|||||||
*<pre>
|
*<pre>
|
||||||
* Server
|
* Server
|
||||||
* HandlerCollection
|
* HandlerCollection
|
||||||
|
* HostCheckHandler
|
||||||
* ContextHandlerCollection
|
* ContextHandlerCollection
|
||||||
* WebAppContext (i.e. ContextHandler)
|
* WebAppContext (i.e. ContextHandler)
|
||||||
* SessionHandler
|
* SessionHandler
|
||||||
@ -442,10 +445,12 @@ public class RouterConsoleRunner implements RouterApp {
|
|||||||
|
|
||||||
HandlerCollection hColl = new HandlerCollection();
|
HandlerCollection hColl = new HandlerCollection();
|
||||||
ContextHandlerCollection chColl = new ContextHandlerCollection();
|
ContextHandlerCollection chColl = new ContextHandlerCollection();
|
||||||
|
HostCheckHandler chCollWrapper = new HostCheckHandler(_context);
|
||||||
|
chCollWrapper.setHandler(chColl);
|
||||||
// gone in Jetty 7
|
// gone in Jetty 7
|
||||||
//_server.addHandler(hColl);
|
//_server.addHandler(hColl);
|
||||||
_server.setHandler(hColl);
|
_server.setHandler(hColl);
|
||||||
hColl.addHandler(chColl);
|
hColl.addHandler(chCollWrapper);
|
||||||
hColl.addHandler(new DefaultHandler());
|
hColl.addHandler(new DefaultHandler());
|
||||||
|
|
||||||
String log = _context.getProperty("routerconsole.log");
|
String log = _context.getProperty("routerconsole.log");
|
||||||
@ -480,6 +485,7 @@ public class RouterConsoleRunner implements RouterApp {
|
|||||||
if (!_webAppsDir.endsWith("/"))
|
if (!_webAppsDir.endsWith("/"))
|
||||||
_webAppsDir += '/';
|
_webAppsDir += '/';
|
||||||
|
|
||||||
|
Set<String> listenHosts = new HashSet<String>(8);
|
||||||
HandlerWrapper rootWebApp = null;
|
HandlerWrapper rootWebApp = null;
|
||||||
ServletHandler rootServletHandler = null;
|
ServletHandler rootServletHandler = null;
|
||||||
List<Connector> connectors = new ArrayList<Connector>(4);
|
List<Connector> connectors = new ArrayList<Connector>(4);
|
||||||
@ -549,6 +555,7 @@ public class RouterConsoleRunner implements RouterApp {
|
|||||||
Collections.sort(hosts, new HostComparator());
|
Collections.sort(hosts, new HostComparator());
|
||||||
_context.portMapper().register(PortMapper.SVC_CONSOLE, hosts.get(0), lport);
|
_context.portMapper().register(PortMapper.SVC_CONSOLE, hosts.get(0), lport);
|
||||||
// note that we could still fail in connector.start() below
|
// note that we could still fail in connector.start() below
|
||||||
|
listenHosts.addAll(hosts);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -627,6 +634,7 @@ public class RouterConsoleRunner implements RouterApp {
|
|||||||
Collections.sort(hosts, new HostComparator());
|
Collections.sort(hosts, new HostComparator());
|
||||||
_context.portMapper().register(PortMapper.SVC_HTTPS_CONSOLE, hosts.get(0), sslPort);
|
_context.portMapper().register(PortMapper.SVC_HTTPS_CONSOLE, hosts.get(0), sslPort);
|
||||||
// note that we could still fail in connector.start() below
|
// note that we could still fail in connector.start() below
|
||||||
|
listenHosts.addAll(hosts);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
System.err.println("Unable to create or access keystore for SSL: " + keyStore.getAbsolutePath());
|
System.err.println("Unable to create or access keystore for SSL: " + keyStore.getAbsolutePath());
|
||||||
@ -670,6 +678,27 @@ public class RouterConsoleRunner implements RouterApp {
|
|||||||
ioe.printStackTrace();
|
ioe.printStackTrace();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// fix up the allowed hosts set (see HostCheckHandler)
|
||||||
|
if (listenHosts.contains("0.0.0.0") ||
|
||||||
|
listenHosts.contains("::") ||
|
||||||
|
listenHosts.contains("0:0:0:0:0:0:0:0")) {
|
||||||
|
// empty set says all are valid
|
||||||
|
listenHosts.clear();
|
||||||
|
} else {
|
||||||
|
listenHosts.add("localhost");
|
||||||
|
listenHosts.add("127.0.0.1");
|
||||||
|
listenHosts.add("::1");
|
||||||
|
listenHosts.add("0:0:0:0:0:0:0:1");
|
||||||
|
String allowed = _context.getProperty(PROP_ALLOWED_HOSTS);
|
||||||
|
if (allowed != null) {
|
||||||
|
StringTokenizer tok = new StringTokenizer(allowed, " ,");
|
||||||
|
while (tok.hasMoreTokens()) {
|
||||||
|
listenHosts.add(tok.nextToken());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
chCollWrapper.setListenHosts(listenHosts);
|
||||||
|
|
||||||
// https://bugs.eclipse.org/bugs/show_bug.cgi?id=364936
|
// https://bugs.eclipse.org/bugs/show_bug.cgi?id=364936
|
||||||
// WARN:oejw.WebAppContext:Failed startup of context o.e.j.w.WebAppContext{/,jar:file:/.../webapps/routerconsole.war!/},/.../webapps/routerconsole.war
|
// WARN:oejw.WebAppContext:Failed startup of context o.e.j.w.WebAppContext{/,jar:file:/.../webapps/routerconsole.war!/},/.../webapps/routerconsole.war
|
||||||
// java.lang.IllegalStateException: zip file closed
|
// java.lang.IllegalStateException: zip file closed
|
||||||
|
@ -1,3 +1,8 @@
|
|||||||
|
2017-10-11 zzz
|
||||||
|
* Console: Validate host header (thx Kevin Froman)
|
||||||
|
* Router: Honor IPv6 setting when converting configured hostnames
|
||||||
|
to IP addresses (proposal #141)
|
||||||
|
|
||||||
2017-10-04 zzz
|
2017-10-04 zzz
|
||||||
* Router: Convert configured hostnames to IP addresses
|
* Router: Convert configured hostnames to IP addresses
|
||||||
before publishing (proposal #141)
|
before publishing (proposal #141)
|
||||||
|
@ -18,7 +18,7 @@ public class RouterVersion {
|
|||||||
/** deprecated */
|
/** deprecated */
|
||||||
public final static String ID = "Monotone";
|
public final static String ID = "Monotone";
|
||||||
public final static String VERSION = CoreVersion.VERSION;
|
public final static String VERSION = CoreVersion.VERSION;
|
||||||
public final static long BUILD = 4;
|
public final static long BUILD = 5;
|
||||||
|
|
||||||
/** for example "-test" */
|
/** for example "-test" */
|
||||||
public final static String EXTRA = "";
|
public final static String EXTRA = "";
|
||||||
|
Reference in New Issue
Block a user