contrast security plugin for eclipse

I recently learned that Contrast Security has a free plugin that tests your application against the OWASP Top 10.  We’ve tried to fix these already. You can read about how we fixed Clickjacking, CSRF and XSS in JForum.

Installing

I started out by installing the Contrast plugin from the Eclipse Marketplace. After restarting Eclipse, a Contrast view automatically opens with instructions. It says to right click your server and choose “Start with Contrast.” Easy enough. I usually use Sysdeo so I can start the server in one click, but this is hardly onerous.

A Diversion: Fixing Tomcat Configuration

I got an error on startup. I then tried to start the server using the server view (without Contrast) and got the same NoSuchMethodError:

java.lang.NoSuchMethodError: sun.security.ec.NamedCurve.<init>(Ljava/lang/String;Ljava/lang/String;Ljava/security/spec/EllipticCurve;Ljava/security/spec/ECPoint;Ljava/math/BigInteger;I)V

I fixed this by switching Tomcat 7 to use Java 7 instead of Java 8. (We aren’t using Java 8 yet for CodeRanch’s JForum software so this is fine.)

  • Workspace preference
  • Server
  • Runtime Environments
  • Click Tomcat and edit
  • Choose Java 7 as JRE

This had nothing to do with Contrast. I hadn’t encountered it because I was using Sysdeo to start Tomcat before this.

Actually testing

Now that the server starts up, I stopped it and restarted with Contrast. Then I clicked around the app a bit. (You can use Selenium tests or any other testing tool to automate this part.) The Contrast view starts to populate with its findings. I clicked around until I had about a dozen findings. They were:

Category Issue # Instances Details My analysis
Orange Insecure hash algorithms in XXX 3 Provides an explanation of what the problem is, why it might/might not be a problem along with the stack trace (showing how it is used) and the HTTP request/headers for the request(s) that triggered it. Two of the three findings refer to the exact same line of code. (Which was run on two different screens). The other appears to be in Tomcat itself. My configuration isn’t the same as the real server here. [The other two I need to look into further]
Yellow Anti-Caching Controls Missing in XXXX 6 Provides the HTTP request/headers, suggested remediation It’s annoying to have this reported on every page. Glad there is an :ignore this rule” option. We run a public website and want things to be cached. Client side caching makes the site faster for users and doesn’t leak information since 90% of our information is public to begin with. The only risk is if a moderator access the private forum on a public computer. We are technical users and know to clear data if this happens.
Yellow Forms without autocomplete prevention 3 Provides the HTTP request/headers, suggested remediation Again, we are a public site so not a big deal for browsers to retain information.
Warning CVE(s) in commons-httpclient-3-1.jar 1 Provides links to the two CVEs along with the manifest of the vulnerable library. I knew this from running Sonatype CLM Insight. The two CVEs are in functionality in the library that we don’t use. Still it is sweet to have this information available for free and with almost no effort. (Insight is a commercial project. We saw a one time result from the report.) I was concerned that information about the jars was being sent over the internet so I asked on Twitter. Jeff Williams replied that the CVE information is in a built in database updated via Eclipse Marketplace. Neat!

What to do with the results

When right clicking on any finding, you have four options:

  • Mark Resolved
  • Delete
  • Ignore (this instance) – useful for a false positive
  • Ignore rule – useful for a rule that doesn’t apply

My thoughts on the Contrast plugin

  • I like that the stack trace is included because it is easy to see context. I also like that lines belonging to the app is in blue in the stack trace.
  • It was very easy to use. And free. Which makes using it a no brainer.
  • While there aren’t false positives from unused code, there are false positives from context (which a tool can’t know).
  • Two of the rules triggered on a number of pages. (and would have triggered on a lot if I tested more)
  • While I don’t have a long list of things to follow up, it was a good thought exercise. And the reason I don’t have a long list is because we manually went through the OWASP top 10 in preparation for the “Iron Clad Java” promo recently. (so as not to have embarrassing issues pointed out)

fixing clickjacking and brute force login for jforum

I’ve been blogging about some of the security fixes we’ve made in the CodeRanch fork of JForum such as XSS with quotes and CSRF. Today it is time to write about Clickjacking and preventing brute force logins.

Clickjacking

Clickjacking is an attack where someone includes your site in transparent frames and the attacker intercepts anything typed in/clicked. We had originally decided not to do anything about ClickJacking because we want our site to be available in frames. For example, DZone serves the main page in a frame when asking people to vote on it.

However, that doesn’t mean that we shouldn’t protect anything. We decided to protect the most critical parts of the site – the admin screens and the login form. That leaves the read only pages (which we don’t mind framed) and editing posts/user info (which is displayed publicly anyway).

The implementation turned out to be simple. We added this header to the relevant pages. (the admin screens are always within a frame on the site and the login screen is when an admin’s session times out).

response.setHeader("X-Frame-Options", "SAMEORIGIN");

It turned out the method only needed to be called from two places: UserAction (for login) and the AdminCommand (for the admin console).

While this isn’t complete protection, it is certainly better than the nothing we had before. And it is easy to add more screens in the future if we need to. We also didn’t include the JavaScript framebusting logic for older browsers because Google Analytics says hardly of our users are on such browsers.

Preventing Brute Force Logins

Another security issue that we knew about but didn’t fix because “it wasn’t a problem” was preventing brute force logins. It seemed like high time to do something about that one too. While this was more work than fixing Clickjacking, it wasn’t a huge deal either. The logic is mostly in one (well unit tested) class that gets called from the method with the login check. There is also a property file to externalize the number of attempts/wait. And a job to clear out the Map once a day for attempts where the IP gives up and goes away.

The idea is that an IP gets a number of “free” attempts. After that more login attempts from that IP must wait longer and longer. Once a user logs in successfully from that IP or there are no attempts for a certain amount of time,t he clock starts over and the “free” attempts are back.

This approach doesn’t protect against a distributed attack because the IPs are different. (I choose the IP based approach so someone can’t lock out a user’s account specifically by targeting them and entering invalid passwords. That said, we aren’t running a bank so it seems unlikely for someone to go through that much effort to attack us.

Note: I got a private comment to be careful about IP blocking due to companies using a single public IP for thousands of users. This approach is NOT using IP blocking. Let’s say have 1000 users from InfoSys. (We probably have more, but that’s not the point.) Most of those users will either know their password or use the “remember password” functionality. If a few forget their password and type in a bunch of wrong attempts, the time throttling kicks in.  It seems unlikely for multiple users to having this problem at the same time. And as soon as anyone from the company (IP) has a correct password, the “penalty” count resets.

Code for the curious:

package net.jforum.repository;

import java.util.*;

import net.jforum.util.preferences.*;

/**
 * We prevent brute force login attacks by throttling how often we will check
 * credential after three failed logins from the same IP since the last success.
 * If there are no failures within an hour, the clock will restart on this
 * throttling.
 *
 * @author Jeanne
 * @version $Id: $
 */
public class FailedLoginRepository {
    private static final int NUM_SECONDS_IN_MINUTE = 1000 * 60;
    private static FailedLoginRepository cache = new FailedLoginRepository();
    private Map<String, List<Date>> failedLogins;
    private int maxLoginsBeforeThrottling;
    private int numMinutesForReset;
    private int[] throttlingDelay;

    // ----------------------------------------------------------------
    public FailedLoginRepository() {
        failedLogins = new HashMap<String, List<Date>>();
        numMinutesForReset = SystemGlobals.getIntValue(ConfigKeys.BRUTE_FORCE_PREVENTION_NUM_MINUTES_FOR_RESET);
        String waitTimes = SystemGlobals.getValue(ConfigKeys.BRUTE_FORCE_PREVENTION_WAIT_TIMES);
        setWaitsAsIntArray(waitTimes);
        setMaxLoginsBeforeThrottling();
    }

    private void setMaxLoginsBeforeThrottling() {
        for (int i = 0; i < throttlingDelay.length; i++) {
            if (throttlingDelay[i] == 0) {
                maxLoginsBeforeThrottling++;
            }
        }
    }

    private void setWaitsAsIntArray(String waitTimes) {
        String[] stringArray = waitTimes.split(",");
        throttlingDelay = new int[stringArray.length];
        for (int i = 0; i < throttlingDelay.length; i++) {
            throttlingDelay[i] = Integer.parseInt(stringArray[i].trim());
        }
    }

    // ----------------------------------------------------------------
    public static FailedLoginRepository getInstance() {
        return cache;
    }

    // ----------------------------------------------------------------
    /**
     * After a successful login from the API, remove the blocks. This indicates
     * the user just mistyped and not a hacker.
     *
     * @param ip
     */
    public void successfulAttempt(String ip) {
        failedLogins.remove(ip);
    }

    // ----------------------------------------------------------------
    /**
     * Keep track of time of login failure
     *
     * @param ip
     * @param now
     */
    public void failedAttempt(String ip, Date now) {
        removeOldAttempts(ip);
        List<Date> fails = failedLogins.get(ip);
        if (fails == null) {
            fails = new ArrayList<Date>();
            failedLogins.put(ip, fails);
        }
        fails.add(now);
    }

    public void failedAttempt(String ip) {
        failedAttempt(ip, new Date());
    }

    // ----------------------------------------------------------------
    /**
     * If there haven't been any failed attempts from that IP in the last hour,
     * start over.
     *
     * @param ip
     */
    private void removeOldAttempts(String ip) {
        List<Date> fails = failedLogins.get(ip);
        if (fails != null) {
            Date lastFail = getLastFail(fails);
            Calendar cal = Calendar.getInstance();
            cal.add(Calendar.MINUTE, -numMinutesForReset);
            if (lastFail.before(cal.getTime())) {
                failedLogins.remove(ip);
            }
        }
    }

    private Date getLastFail(List<Date> fails) {
        Date lastFail = fails.get(fails.size() - 1);
        return lastFail;
    }

    public void removeAllOldAttempts() {
        for (String ip : failedLogins.keySet()) {
            removeOldAttempts(ip);
        }
    }

    // ----------------------------------------------------------------
    public boolean shouldCheckPassword(String ip) {
        removeOldAttempts(ip);
        int waitRemaining = numberMinutesUntilNextAllowedLogin(ip, new Date());
        return waitRemaining == 0;
    }

    public boolean shouldCheckPassword(String ip, Date now) {
        removeOldAttempts(ip);
        List<Date> fails = failedLogins.get(ip);
        return fails == null || fails.size() <= maxLoginsBeforeThrottling;
    }

    // ----------------------------------------------------------------
    public int numberMinutesUntilNextAllowedLogin(String ip) {
        return numberMinutesUntilNextAllowedLogin(ip, new Date());
    }

    /**
     * Contains algorithm for delays: see if already waited number of minutes
     * need to wai
     *
     * @param ip
     * @return
     */
    public int numberMinutesUntilNextAllowedLogin(String ip, Date now) {
        List<Date> fails = failedLogins.get(ip);
        if (fails == null) {
            return 0;
        }
        int index = fails.size() - 1;
        if (index >= throttlingDelay.length) {
            index = throttlingDelay.length - 1;
        }
        int delay = throttlingDelay[index];
        Date lastFailure = getLastFail(fails);
        int numMinutesWaited = (int) (now.getTime() - lastFailure.getTime()) / NUM_SECONDS_IN_MINUTE;
        int result = delay - numMinutesWaited;
        // if delay is over, do need to wait longer
        if (result < 0) {
            result = 0;
        }
        return result;
    }
}

 

fixing JForum XSS error in PM module with quotes

A member reported a XSS vulnerability in stock JForum 2.1.9. We confirmed it was a vulnerability/exposure on CodeRanch as well and fixed our fork. Luckily, it was an easy fix unlike the CSRF problems last year.

In addition to saying how to fix the issue in this post, I’m going to outline some of the other techniques JForum uses to defeat XSS.  For the actual (two character) fix, scroll down to “the fix.”

What is XSS?

XSS (Cross site scripting) is a security attack. OWASP describes in well on their XSS page. In brief, XSS injects code into a web page that runs on the target computer. The injected script code can do anything that the web page can do. Which means it can use JavaScript to steal your cookies, mount other attacks, etc. Scary stuff!

  • Reflected XSS – A reflected XSS attack targets specific users but is not stored in the database of the server with the issue. You might see a reflected XSS attack if you click on a link that takes you to the page. Others going to the page normally wouldn’t see the issue.
  • Persistent XSS – A persistent XSS attack gets the attack code stored in the database of the server with the issue. It could still target a specific user (in the case of the private message issue reported here.) Or it could target all users – even non logged in users – if the same attack was made in a post instead of a private message.  I was able to reproduce this problem in posts as well.

Both types of XSS attack are bad and up to the website to prevent. So how does Jforum 2.1.X protect against XSS attacks?

Approach #1 – Use Freemarker HTML escape sequence

JForum uses Freemarker as the view technology. Freemarker allows you to specify that all HTML should be escaped. This means attacks that reply on outputting HTML characters like < (tags) or ” (attributes) will be prevented.  Instead the raw characters of &lt; and &quot; will be output instead. Which the browser will not run. As an example of this technique, the code writes:


${post.subject?default("")?html}

Approach #2 – Escape characters in Java

Approach #1 is very powerful, but it has a limitation. Forum posts typically contain HTML code. For example, you write code in a special format, bold posts, etc. JForum uses Java code to do a search and replace on the special characters in text before adding the HTML formatting. Since the Freemarker view has to be able to render the HTML formatting, it can’t use approach #1. See an example of just one of these transformations:

ViewCommon.replaceAll(text, "<", "&lt;");

This approach is not foolproof because it relies on a blacklist of “not allowed” characters and hackers are creative. But it is really hard to come up with a whitelist of allowed characters in forum posts. And worse, the characters used in attacks are ones that are used in normal writing.

Approach #3 – Limit raw HTML

While JForum does allow HTML in posts, it only allows a limited set of tags and attributes. This one does use a whitelist with code like:


private static Set welcomeTags;

private static Set welcomeAttributes;

Approach #4 – Use BB code instead of HTML

The forum also allows use of BB (bulletin board) codes. This lets you write [b] instead of <b>. If the user isn’t entering HTML, the chance of a problem is lower.

The actual problem here

The XSS vulnerability reported was caused by the interaction between approach #2 and approach #4.

Approach #2 guarantees the quotes are safe with


ViewCommon.replaceAll(tmp, "\"", "&quot;");

Approach #4 contains the following BB mapping code in bb_config.xml


<!-- COLOR -->

<match name="color" removeQuotes="true">

<regex>(?s)(?i)\[color=['"]?(.*?[^'"])['"]?\](.*?)\[/color\]</regex>

<replace>

<![CDATA[

<font color='$1'>$2</font>

]]>

</replace>

</match>

This is a problem because the replace uses single quotes instead of double quotes. The system doesn’t escape single quotes. Allowing all manners of code to be injected in the color attribute.

The fix

Luckily, there is an easy fix. Just change this one line of code in bb_config.xml to:


<font color="$1">$2</font>

I’ve tested and this does in fact solve the problem.

For more learning about XSS

If you want to learn more about XSS, I recommend reading the OWASP cheat sheet.  In particular, notice that you need to escape the code differently depending on whether you are looking at HTML or JavaScript injection. In our case, it was HTML injection because the injection was occurring as a textual HTML attribute. If it was in <script> tag or JavaScript event handler, we’d need to call a JavaScript encoding library. Also, you can learn about DOM based XSS attacks.