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" />
|
<pathelement location="../../jetty/jettylib/javax.servlet.jar" />
|
||||||
<!-- jsp-api.jar only present for debian builds -->
|
<!-- jsp-api.jar only present for debian builds -->
|
||||||
<pathelement location="../../jetty/jettylib/jsp-api.jar" />
|
<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" />
|
<pathelement location="../../jetty/jettylib/jetty-i2p.jar" />
|
||||||
<!-- systray.jar only for RunStandalone -->
|
|
||||||
<pathelement location="../../systray/java/build/systray.jar" />
|
<pathelement location="../../systray/java/build/systray.jar" />
|
||||||
|
<pathelement location="../../jetty/jettylib/org.mortbay.jetty.jar" />
|
||||||
|
<pathelement location="../../jetty/jettylib/jetty-util.jar" />
|
||||||
</classpath>
|
</classpath>
|
||||||
</javac>
|
</javac>
|
||||||
</target>
|
</target>
|
||||||
@ -319,6 +320,7 @@
|
|||||||
<mkdir dir="./dist/webapps" />
|
<mkdir dir="./dist/webapps" />
|
||||||
<copy file="../i2psnark.war" tofile="./dist/webapps/i2psnark.war" />
|
<copy file="../i2psnark.war" tofile="./dist/webapps/i2psnark.war" />
|
||||||
<copy file="../jetty-i2psnark.xml" tofile="./dist/jetty-i2psnark.xml" />
|
<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="./build/i2psnark-standalone.jar" tofile="./dist/i2psnark.jar" />
|
||||||
<copy file="../readme-standalone.txt" tofile="./dist/readme.txt" />
|
<copy file="../readme-standalone.txt" tofile="./dist/readme.txt" />
|
||||||
<!-- temp so announces work -->
|
<!-- 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;
|
package org.klomp.snark.standalone;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.Properties;
|
||||||
|
|
||||||
import net.i2p.I2PAppContext;
|
import net.i2p.I2PAppContext;
|
||||||
import net.i2p.apps.systray.UrlLauncher;
|
import net.i2p.apps.systray.UrlLauncher;
|
||||||
|
import net.i2p.data.DataHelper;
|
||||||
import net.i2p.jetty.JettyStart;
|
import net.i2p.jetty.JettyStart;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -16,9 +19,16 @@ public class RunStandalone {
|
|||||||
private int _port = 8002;
|
private int _port = 8002;
|
||||||
private String _host = "127.0.0.1";
|
private String _host = "127.0.0.1";
|
||||||
private static RunStandalone _instance;
|
private static RunStandalone _instance;
|
||||||
|
static final File APP_CONFIG_FILE = new File("i2psnark-appctx.config");
|
||||||
|
|
||||||
private RunStandalone(String args[]) throws Exception {
|
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 base = _context.getBaseDir();
|
||||||
File xml = new File(base, "jetty-i2psnark.xml");
|
File xml = new File(base, "jetty-i2psnark.xml");
|
||||||
_jettyStart = new JettyStart(_context, null, new String[] { xml.getAbsolutePath() } );
|
_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">
|
<New id="Handlers" class="org.eclipse.jetty.server.handler.HandlerCollection">
|
||||||
<Set name="handlers">
|
<Set name="handlers">
|
||||||
<Array type="org.eclipse.jetty.server.Handler">
|
<Array type="org.eclipse.jetty.server.Handler">
|
||||||
|
<Item>
|
||||||
|
<New id="HostChecker" class="org.klomp.snark.standalone.HostCheckHandler"/>
|
||||||
|
</Item>
|
||||||
<Item>
|
<Item>
|
||||||
<New id="Contexts" class="org.eclipse.jetty.server.handler.ContextHandlerCollection"/>
|
<New id="Contexts" class="org.eclipse.jetty.server.handler.ContextHandlerCollection"/>
|
||||||
</Item>
|
</Item>
|
||||||
|
@ -33,7 +33,11 @@ public class XSSFilter implements Filter {
|
|||||||
// We need to send the error quickly, if we just throw a ServletException,
|
// We need to send the error quickly, if we just throw a ServletException,
|
||||||
// the data keeps coming and the connection gets reset.
|
// the data keeps coming and the connection gets reset.
|
||||||
// This way we at least get the error to the browser.
|
// 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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -78,6 +78,7 @@ public class HostCheckHandler extends HandlerWrapper
|
|||||||
"\" to advanced configuration and restart.";
|
"\" to advanced configuration and restart.";
|
||||||
log.logAlways(Log.WARN, s);
|
log.logAlways(Log.WARN, s);
|
||||||
httpResponse.sendError(403, s);
|
httpResponse.sendError(403, s);
|
||||||
|
baseRequest.setHandled(true);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -92,6 +93,7 @@ public class HostCheckHandler extends HandlerWrapper
|
|||||||
if (Boolean.valueOf(redir) ||
|
if (Boolean.valueOf(redir) ||
|
||||||
(redir == null && "1".equals(httpRequest.getHeader("Upgrade-Insecure-Requests")))) {
|
(redir == null && "1".equals(httpRequest.getHeader("Upgrade-Insecure-Requests")))) {
|
||||||
sendRedirect(httpsPort, httpRequest, httpResponse);
|
sendRedirect(httpsPort, httpRequest, httpResponse);
|
||||||
|
baseRequest.setHandled(true);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user