i2psnark standalone: Add DNS rebinding protection

Add context config file to turn it off
Console: Mark request handled when rejecting in HostCheckHandler
XSSFilter: Catch cascaded ISE
This commit is contained in:
zzz
2018-02-24 16:43:15 +00:00
parent 9d989c6a67
commit 5b0680b29e
8 changed files with 172 additions and 4 deletions

View File

@ -0,0 +1,4 @@
#
# This is for app context configuration of standalone i2psnark.
# Almost all configuration settings are in i2psnark.config.d/i2psnark.config
#

View File

@ -50,10 +50,11 @@
<pathelement location="../../jetty/jettylib/javax.servlet.jar" />
<!-- jsp-api.jar only present for debian builds -->
<pathelement location="../../jetty/jettylib/jsp-api.jar" />
<!-- jetty-i2p.jar only for RunStandalone -->
<!-- following jars only for standalone builds -->
<pathelement location="../../jetty/jettylib/jetty-i2p.jar" />
<!-- systray.jar only for RunStandalone -->
<pathelement location="../../systray/java/build/systray.jar" />
<pathelement location="../../jetty/jettylib/org.mortbay.jetty.jar" />
<pathelement location="../../jetty/jettylib/jetty-util.jar" />
</classpath>
</javac>
</target>
@ -319,6 +320,7 @@
<mkdir dir="./dist/webapps" />
<copy file="../i2psnark.war" tofile="./dist/webapps/i2psnark.war" />
<copy file="../jetty-i2psnark.xml" tofile="./dist/jetty-i2psnark.xml" />
<copy file="../i2psnark-appctx.config" tofile="./dist/i2psnark-appctx.config" />
<copy file="./build/i2psnark-standalone.jar" tofile="./dist/i2psnark.jar" />
<copy file="../readme-standalone.txt" tofile="./dist/readme.txt" />
<!-- temp so announces work -->

View File

@ -0,0 +1,136 @@
package org.klomp.snark.standalone;
import java.io.IOException;
import java.util.HashSet;
import java.util.Set;
import java.util.StringTokenizer;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import net.i2p.I2PAppContext;
import net.i2p.util.Log;
import org.apache.http.conn.util.InetAddressUtils;
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.handler.AbstractHandler;
/**
* Block certain Host headers to prevent DNS rebinding attacks.
*
* Unlike in the console, this is an AbstractHandler, not a HandlerWrapper.
*
* @since 0.9.34 adapted from router console
*/
public class HostCheckHandler extends AbstractHandler {
private final I2PAppContext _context;
private final Set<String> _listenHosts;
private static final String PROP_ALLOWED_HOSTS = "i2psnark.allowedHosts";
public HostCheckHandler() {
this(I2PAppContext.getGlobalContext());
}
public HostCheckHandler(I2PAppContext ctx) {
super();
_context = ctx;
_listenHosts = new HashSet<String>(8);
_listenHosts.add("127.0.0.1");
_listenHosts.add("::1");
_listenHosts.add("localhost");
String allowed = _context.getProperty(PROP_ALLOWED_HOSTS);
if (allowed != null) {
StringTokenizer tok = new StringTokenizer(allowed, " ,");
while (tok.hasMoreTokens()) {
_listenHosts.add(tok.nextToken());
}
}
}
/**
* Unused, we can't get here from RunStandalone
*
* @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,
* redirect HTTP to HTTPS,
* 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 = getHost(host);
String s = "Console request denied.\n" +
" To allow access using the hostname \"" + host + "\", add the line \"" +
PROP_ALLOWED_HOSTS + '=' + host +
"\" in the file " + RunStandalone.APP_CONFIG_FILE.getAbsolutePath() + " and restart.";
log.logAlways(Log.WARN, s);
httpResponse.sendError(403, s);
baseRequest.setHandled(true);
return;
}
}
/**
* 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:8002") ||
host.equals("localhost:8002") ||
host.equals("[::1]:8002"))
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;
}
}

View File

@ -1,9 +1,12 @@
package org.klomp.snark.standalone;
import java.io.File;
import java.io.IOException;
import java.util.Properties;
import net.i2p.I2PAppContext;
import net.i2p.apps.systray.UrlLauncher;
import net.i2p.data.DataHelper;
import net.i2p.jetty.JettyStart;
/**
@ -16,9 +19,16 @@ public class RunStandalone {
private int _port = 8002;
private String _host = "127.0.0.1";
private static RunStandalone _instance;
static final File APP_CONFIG_FILE = new File("i2psnark-appctx.config");
private RunStandalone(String args[]) throws Exception {
_context = I2PAppContext.getGlobalContext();
Properties p = new Properties();
if (APP_CONFIG_FILE.exists()) {
try {
DataHelper.loadProps(p, APP_CONFIG_FILE);
} catch (IOException ioe) {}
}
_context = new I2PAppContext(p);
File base = _context.getBaseDir();
File xml = new File(base, "jetty-i2psnark.xml");
_jettyStart = new JettyStart(_context, null, new String[] { xml.getAbsolutePath() } );

View File

@ -0,0 +1,7 @@
<html>
<body>
<p>
Classes only used for, and bundled with, the standalone installation. Since 0.9.27.
</p>
</body>
</html>

View File

@ -55,6 +55,9 @@
<New id="Handlers" class="org.eclipse.jetty.server.handler.HandlerCollection">
<Set name="handlers">
<Array type="org.eclipse.jetty.server.Handler">
<Item>
<New id="HostChecker" class="org.klomp.snark.standalone.HostCheckHandler"/>
</Item>
<Item>
<New id="Contexts" class="org.eclipse.jetty.server.handler.ContextHandlerCollection"/>
</Item>

View File

@ -33,7 +33,11 @@ public class XSSFilter implements Filter {
// We need to send the error quickly, if we just throw a ServletException,
// the data keeps coming and the connection gets reset.
// This way we at least get the error to the browser.
((HttpServletResponse)response).sendError(413, ise.getMessage());
try {
((HttpServletResponse)response).sendError(413, ise.getMessage());
} catch (IllegalStateException ise2) {
// Committed, probably wasn't a multipart form error after all
}
}
}
}

View File

@ -78,6 +78,7 @@ public class HostCheckHandler extends HandlerWrapper
"\" to advanced configuration and restart.";
log.logAlways(Log.WARN, s);
httpResponse.sendError(403, s);
baseRequest.setHandled(true);
return;
}
@ -92,6 +93,7 @@ public class HostCheckHandler extends HandlerWrapper
if (Boolean.valueOf(redir) ||
(redir == null && "1".equals(httpRequest.getHeader("Upgrade-Insecure-Requests")))) {
sendRedirect(httpsPort, httpRequest, httpResponse);
baseRequest.setHandled(true);
return;
}
}