Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Security vulnerability caused by unicode escapes in decoupled JSON service?

Have a look at this simple JSON system composed of a user facing service that talks to a back end:

import java.util.*;
import com.json.parsers.*;
import org.json.simple.*;

class Main{static {
    String s = new java.util.Scanner(System.in).useDelimiter("\\A").next();
    UserFacingService.handleMessage(s);
}}

class UserFacingService {
    static void handleMessage(String s) {
        JSONParser parser = JsonParserFactory.getInstance().newJsonParser();
        Map js = parser.parseJson(s);
        if (commandAllowed(js))
            Backend.handleMessage(s);
        else
            System.out.println("unauthorized!");
    }
    static boolean commandAllowed(Map js) {
        if (js.get("cmd").equals("bad stuff"))
            return false;
        return true;
    }
}

class Backend {
    static void handleMessage(String s) {
        JSONObject js = (JSONObject) JSONValue.parse(s);
        String cmd = (String)js.get("cmd");
        if (cmd.equals("bad stuff")) {
            doBadStuff();
        } else if (cmd.equals("ping")) {
            doPong();
        }
    }
    static void doBadStuff() { System.out.println("doing bad stuff"); }
    static void doPong() { System.out.println("doing pong"); }
}

The user facing service (using quick-json) prevents the user from making the backend (using json-simple) do bad stuff. To simulate the user, I'm using stdin from Main. In a real scenario, Backend would be on another server, or some old legacy / binary code, so we can't simply change it to accept Java objects. The design is that authorization is done separately from Backend. This could be elaborated by making another class called AdminFacingService that only the admin has access to, or by putting if (userIsAdmin) return true; at the top of UserFacingService.commandAllowed.

As you can see, it works as intended:

$ CP=.:json-simple-1.1.1.jar:quick-json-1.0.2.3.jar
$ javac -cp $CP A.java && echo '{"cmd": "ping"}' | java -cp $CP Main
doing pong
Exception in thread "main" java.lang.NoSuchMethodError: main

Sending {"cmd": "bad stuff"} should cause the user facing service to reject the command:

$ CP=.:json-simple-1.1.1.jar:quick-json-1.0.2.3.jar
$ javac -cp $CP A.java && echo '{"cmd": "bad stuff"}' | java -cp $CP Main
unauthorized!
Exception in thread "main" java.lang.NoSuchMethodError: main

That works as well. But then if the user sends {"cmd": "bad\u0020stuff"}, bad stuff happens:

$ CP=.:json-simple-1.1.1.jar:quick-json-1.0.2.3.jar
$ javac -cp $CP A.java && echo '{"cmd": "bad\u0020stuff"}' | java -cp $CP Main
doing bad stuff
Exception in thread "main" java.lang.NoSuchMethodError: main

But how? Why isn't the user facing service catching this?

like image 224
Dog Avatar asked Oct 02 '22 13:10

Dog


2 Answers

With bad\u0020stuff as an input, the first parser returns an object js where js.get("cmd") returns the string bad\u0020stuff, so commandAllowed returns true. The second parser returns an object js where js.get("cmd") returns bad stuff (it converts \u0020 to a space). Thus the bad stuff command is executed.

The first parser is not following RFC4627, because it's not interpreting unicode escapes.

like image 179
Harold R. Eason Avatar answered Oct 05 '22 03:10

Harold R. Eason


Right, I see. You're using two different parsers. Originally I though these were lenient in different ways. "Postel's Law" is great for UIs but dangerous for programming interfaces. In particular, hairy parsing is great for creating vulnerabilities.

Looking (again!) at RFC4627, \u notation is legal in JSON. One of your parsers appears to be broken and interprets the data incorrectly.

Generally only parse once - quite a few bugs come from multiple parsing. If you need the data in JSON again, extract and re-encode using a formatter that is clearly within spec and any buggy implementations you have to deal with.

It's kind of interesting, that even though is supposed to be really simple, text formats are susceptible to this sort of thing. So, prefer simple (not ASN.1) binary formats and use quality libraries.


Original answer, in which I wrongly assumed you were starting a new process every time with user data on the command line:

This is nothing to do with parsing JSON and everything to do with what you put on the command line. It's a bad idea to put untrusted data on the command line. This is in the "injection" class of vulnerabilities, along with SQL injection, XSS, LDAP injection, HTTP request/response splitting and many others. Most of these vulnerabilities can be solved by a tight formatting library. Command lines are all over the place, so it's best to either avoid them completely or go for something like Base64 encoding.

OWASP have a page on Command Injection.

The "Secure Coding Guidelines for the Java Programming Language, Version 4.0" has Guideline 3-4: Avoid any untrusted data on the command line.

(You're also very easily leaving yourself open to a DoS attack if you start a new Java process for every request.)

like image 28
Tom Hawtin - tackline Avatar answered Oct 05 '22 03:10

Tom Hawtin - tackline