forked from I2P_Developers/i2p.i2p
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:
4
apps/i2psnark/i2psnark-appctx.config
Normal file
4
apps/i2psnark/i2psnark-appctx.config
Normal 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
|
||||
#
|
@ -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 -->
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
@ -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() } );
|
||||
|
@ -0,0 +1,7 @@
|
||||
<html>
|
||||
<body>
|
||||
<p>
|
||||
Classes only used for, and bundled with, the standalone installation. Since 0.9.27.
|
||||
</p>
|
||||
</body>
|
||||
</html>
|
@ -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>
|
||||
|
@ -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.
|
||||
try {
|
||||
((HttpServletResponse)response).sendError(413, ise.getMessage());
|
||||
} catch (IllegalStateException ise2) {
|
||||
// Committed, probably wasn't a multipart form error after all
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user