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?