Improved security

This commit is contained in:
Carlos 2020-12-05 13:26:04 +01:00
parent d2696df7bc
commit dd9003190a
9 changed files with 181 additions and 28 deletions

View File

@ -24,7 +24,7 @@ function openServer(serverName){
//Change server name and related info //Change server name and related info
$("#serverTitle").text(serverName); $("#serverTitle").text(serverName);
$("#consoleTextArea").text("Connecting..."); $("#consoleTextArea").text("");
$("#commandInput").prop("disabled", false); $("#commandInput").prop("disabled", false);
$("#sendCommandButton").prop("disabled", false); $("#sendCommandButton").prop("disabled", false);

View File

@ -9,6 +9,7 @@ class WebConsoleConnector {
constructor(serverName, serverURI) { constructor(serverName, serverURI) {
this.serverName = serverName; this.serverName = serverName;
this.serverURI = serverURI; this.serverURI = serverURI;
this.token;
this.subscribers = []; //List of functions called when a new message arrive this.subscribers = []; //List of functions called when a new message arrive
this.messages = []; //All messages retrieved since connection start this.messages = []; //All messages retrieved since connection start
this.commands = []; //EXEC Commands sent by user to this server this.commands = []; //EXEC Commands sent by user to this server
@ -32,7 +33,7 @@ class WebConsoleConnector {
* Internal function * Internal function
*/ */
onOpen(evt){ onOpen(evt){
//TODO Check que la version es correcta, y que es un WebSocket del plugin y no de otra cosa //TODO Check version is correct, and this websocket server is a WebConsole WebSocket
} }
/** /**
@ -48,6 +49,11 @@ class WebConsoleConnector {
*/ */
onMessage(evt){ onMessage(evt){
var obj = JSON.parse(evt.data); var obj = JSON.parse(evt.data);
if(obj.status === 200) //If is a LoggedIn response, save our token
this.token = obj.token;
this.notify(obj); //Notify all subscribers this.notify(obj); //Notify all subscribers
this.messages.push(obj); this.messages.push(obj);
} }
@ -63,7 +69,7 @@ class WebConsoleConnector {
* Sends a WebSocket command to Server * Sends a WebSocket command to Server
*/ */
sendToServer(message){ sendToServer(message){
this.websocket.send(message); this.websocket.send(JSON.stringify(message));
} }
/** /**

View File

@ -60,14 +60,22 @@ class WebConsoleManager {
* Send password to server * Send password to server
*/ */
sendPassword(pwd){ sendPassword(pwd){
this.activeConnection.sendToServer("LOGIN " + pwd); this.activeConnection.sendToServer({
command: "LOGIN",
params: pwd
});
} }
/** /**
* Send console command to server * Send console command to server
*/ */
sendConsoleCmd(cmd){ sendConsoleCmd(cmd){
this.activeConnection.sendToServer("EXEC " + cmd); this.activeConnection.sendToServer({
command: "EXEC",
token: this.activeConnection.token,
params: cmd
});
this.activeConnection.commands.push(cmd); this.activeConnection.commands.push(cmd);
} }
@ -75,16 +83,30 @@ class WebConsoleManager {
* Asks server for CPU, RAM and players info * Asks server for CPU, RAM and players info
*/ */
askForInfo(){ askForInfo(){
this.activeConnection.sendToServer("PLAYERS"); this.activeConnection.sendToServer({
this.activeConnection.sendToServer("CPUUSAGE"); command: "PLAYERS",
this.activeConnection.sendToServer("RAMUSAGE"); token: this.activeConnection.token,
});
this.activeConnection.sendToServer({
command: "CPUUSAGE",
token: this.activeConnection.token,
});
this.activeConnection.sendToServer({
command: "RAMUSAGE",
token: this.activeConnection.token,
});
} }
/** /**
* Asks server for full latest.log * Asks server for full latest.log
*/ */
askForLogs(){ askForLogs(){
this.activeConnection.sendToServer("READLOGFILE"); this.activeConnection.sendToServer({
command: "READLOGFILE",
token: this.activeConnection.token,
});
} }
} }

View File

@ -8,20 +8,26 @@ import es.mesacarlos.webconsole.util.Internationalization;
public class ConnectedUser { public class ConnectedUser {
private String username; private String username;
private InetSocketAddress socketAddress; private InetSocketAddress socketAddress;
private String token;
private UserType userType; private UserType userType;
public ConnectedUser(InetSocketAddress socketAddress, String username, UserType userType) { public ConnectedUser(InetSocketAddress socketAddress, String username, String token, UserType userType) {
this.socketAddress = socketAddress; this.socketAddress = socketAddress;
this.username = username; this.username = username;
this.token = token;
this.userType = userType; this.userType = userType;
} }
public String getUsername() {
return username;
}
public InetSocketAddress getSocketAddress() { public InetSocketAddress getSocketAddress() {
return socketAddress; return socketAddress;
} }
public String getUsername() { public String getToken() {
return username; return token;
} }
public UserType getUserType() { public UserType getUserType() {

View File

@ -46,11 +46,23 @@ public class LoginManager {
} }
/** /**
* Check if user is logged in * Check if user is logged in. It checks that both the socket adress and the user token corresponds to a logged in user.
* @param address User to check * @param address User to check
* @return true if user is logged in, false otherwise * @return true if user is logged in, false otherwise
*/ */
public boolean isLoggedIn(InetSocketAddress address) { public boolean isLoggedIn(InetSocketAddress address, String token) {
for(ConnectedUser user : loggedInUsers)
if(user.getSocketAddress().equals(address) && user.getToken().equals(token))
return true;
return false;
}
/**
* Check if an user is logged in from a given socket address
* @param address User to check
* @return true if user is logged in, false otherwise
*/
public boolean isSocketConnected(InetSocketAddress address) {
for(ConnectedUser user : loggedInUsers) for(ConnectedUser user : loggedInUsers)
if(user.getSocketAddress().equals(address)) if(user.getSocketAddress().equals(address))
return true; return true;

View File

@ -0,0 +1,93 @@
package es.mesacarlos.webconsole.util;
import com.google.gson.Gson;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
public class JsonUtils {
/*{
"command": "LOGIN",
"token": "aosduhasiudgasuidgasdgaspid",
"params": ""
}*/
public final static String COMMAND_PROPERTY = "command";
public final static String TOKEN_PROPERTY = "token";
public final static String PARAMS_PROPERTY = "params";
/**
* Check that a given String is a valid JSON
* @param Json JSON to check
* @return true if it is a valid JSON, false otherwise
*/
public static boolean isValidJson(String Json) {
Gson gson = new Gson();
try {
gson.fromJson(Json, Object.class);
Object jsonObjType = gson.fromJson(Json, Object.class).getClass();
if(jsonObjType.equals(String.class)){
return false;
}
return true;
} catch (com.google.gson.JsonSyntaxException ex) {
return false;
}
}
/**
* Check that a given JSON contains some property
* @param JsonString JSON to check
* @param property property to check
* @return true if the JSON string contains that property, false otherwise
*/
public static boolean containsProperty(String JsonString, String property) {
if(!isValidJson(JsonString))
return false;
JsonParser parser = new JsonParser();
JsonObject obj = parser.parse(JsonString).getAsJsonObject();
JsonElement elem = obj.get(property);
if(elem == null)
return false;
return true;
}
/**
* Check that a given JSON contains some property, and its type is a String
* @param JsonString JSON to check
* @param property property to check
* @returntrue if the JSON string contains that property and it is a String, false otherwise
*/
public static boolean containsStringProperty(String JsonString, String property) {
if(!isValidJson(JsonString))
return false;
JsonParser parser = new JsonParser();
JsonObject obj = parser.parse(JsonString).getAsJsonObject();
JsonElement elem = obj.get(property);
if(elem == null)
return false;
try {
elem.getAsString();
return true;
}catch(Exception e) {
return false;
}
}
/**
* Get a String property from a JSON
* @param JsonString JSON to check
* @param property property to extract from JSON string
* @return the value for that property. If the property is not set, an empty string will be returned
*/
public static String getStringProperty(String JsonString, String property) {
JsonParser parser = new JsonParser();
JsonObject obj = parser.parse(JsonString).getAsJsonObject();
JsonElement result = obj.get(property);
if(result != null)
return result.getAsString();
return "";
}
}

View File

@ -13,6 +13,7 @@ import org.java_websocket.server.WebSocketServer;
import es.mesacarlos.webconsole.auth.LoginManager; import es.mesacarlos.webconsole.auth.LoginManager;
import es.mesacarlos.webconsole.util.DateTimeUtils; import es.mesacarlos.webconsole.util.DateTimeUtils;
import es.mesacarlos.webconsole.util.Internationalization; import es.mesacarlos.webconsole.util.Internationalization;
import es.mesacarlos.webconsole.util.JsonUtils;
import es.mesacarlos.webconsole.websocket.command.WSCommandFactory; import es.mesacarlos.webconsole.websocket.command.WSCommandFactory;
import es.mesacarlos.webconsole.websocket.command.WSCommand; import es.mesacarlos.webconsole.websocket.command.WSCommand;
import es.mesacarlos.webconsole.websocket.response.ConsoleOutput; import es.mesacarlos.webconsole.websocket.response.ConsoleOutput;
@ -30,7 +31,7 @@ public class WSServer extends WebSocketServer {
@Override @Override
public void onOpen(WebSocket conn, ClientHandshake handshake) { public void onOpen(WebSocket conn, ClientHandshake handshake) {
if (LoginManager.getInstance().isLoggedIn(conn.getRemoteSocketAddress())) { if (LoginManager.getInstance().isSocketConnected(conn.getRemoteSocketAddress())) {
sendToClient(conn, new LoggedIn(Internationalization.getPhrase("connection-resumed-message"))); sendToClient(conn, new LoggedIn(Internationalization.getPhrase("connection-resumed-message")));
Bukkit.getLogger().info(Internationalization.getPhrase("connection-resumed-console", conn.getRemoteSocketAddress())); Bukkit.getLogger().info(Internationalization.getPhrase("connection-resumed-console", conn.getRemoteSocketAddress()));
} else { } else {
@ -41,11 +42,15 @@ public class WSServer extends WebSocketServer {
@Override @Override
public void onMessage(WebSocket conn, String message) { public void onMessage(WebSocket conn, String message) {
if(!JsonUtils.containsStringProperty(message, "command") //Contains a command
|| ( !JsonUtils.containsStringProperty(message, "token") && !JsonUtils.getStringProperty(message, JsonUtils.COMMAND_PROPERTY).equals("LOGIN")) //Contains a token or it is a login command
)
return;
// Get command and params // Get command and params
String wsCommand = message.split(" ")[0]; String wsCommand = JsonUtils.getStringProperty(message, JsonUtils.COMMAND_PROPERTY);
String wsCommandParams = ""; String wsToken = JsonUtils.getStringProperty(message, JsonUtils.TOKEN_PROPERTY);
if (message.contains(" ")) String wsCommandParams = JsonUtils.getStringProperty(message, JsonUtils.PARAMS_PROPERTY);
wsCommandParams = message.split(" ", 2)[1];
// Run command // Run command
WSCommand cmd = commands.get(wsCommand); WSCommand cmd = commands.get(wsCommand);
@ -54,8 +59,8 @@ public class WSServer extends WebSocketServer {
// Command does not exist // Command does not exist
sendToClient(conn, new UnknownCommand(Internationalization.getPhrase("unknown-command-message"), message)); sendToClient(conn, new UnknownCommand(Internationalization.getPhrase("unknown-command-message"), message));
Bukkit.getLogger().info(Internationalization.getPhrase("unknown-command-console", message)); Bukkit.getLogger().info(Internationalization.getPhrase("unknown-command-console", message));
} else if (!LoginManager.getInstance().isLoggedIn(conn.getRemoteSocketAddress()) } else if (!wsCommand.equals("LOGIN")
&& !wsCommand.equals("LOGIN")) { && !LoginManager.getInstance().isLoggedIn(conn.getRemoteSocketAddress(), wsToken)) {
// User is not authorised. DO NOTHING, IMPORTANT! // User is not authorised. DO NOTHING, IMPORTANT!
sendToClient(conn, new LoginRequired(Internationalization.getPhrase("forbidden-message"))); sendToClient(conn, new LoginRequired(Internationalization.getPhrase("forbidden-message")));
Bukkit.getLogger().warning(Internationalization.getPhrase("forbidden-console", conn.getRemoteSocketAddress(), message)); Bukkit.getLogger().warning(Internationalization.getPhrase("forbidden-console", conn.getRemoteSocketAddress(), message));
@ -86,7 +91,7 @@ public class WSServer extends WebSocketServer {
public void onNewConsoleLinePrinted(String line) { public void onNewConsoleLinePrinted(String line) {
Collection<WebSocket> connections = getConnections(); Collection<WebSocket> connections = getConnections();
for (WebSocket connection : connections) { for (WebSocket connection : connections) {
if (LoginManager.getInstance().isLoggedIn(connection.getRemoteSocketAddress())) if (LoginManager.getInstance().isSocketConnected(connection.getRemoteSocketAddress()))
sendToClient(connection, new ConsoleOutput(line, DateTimeUtils.getTimeAsString())); sendToClient(connection, new ConsoleOutput(line, DateTimeUtils.getTimeAsString()));
} }
} }

View File

@ -1,5 +1,7 @@
package es.mesacarlos.webconsole.websocket.command; package es.mesacarlos.webconsole.websocket.command;
import java.util.UUID;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.java_websocket.WebSocket; import org.java_websocket.WebSocket;
@ -17,16 +19,16 @@ public class LogInCommand implements WSCommand {
@Override @Override
public void execute(WSServer wsServer, WebSocket conn, String password) { public void execute(WSServer wsServer, WebSocket conn, String password) {
// If user is logged in, then return. // If user is logged in, then return.
if (LoginManager.getInstance().isLoggedIn(conn.getRemoteSocketAddress())) if (LoginManager.getInstance().isSocketConnected(conn.getRemoteSocketAddress()))
return; return;
//Check if user exists //Check if user exists
for(UserData ud : ConfigManager.getInstance().getAllUsers()) { for(UserData ud : ConfigManager.getInstance().getAllUsers()) {
if(ud.getPassword().equals(password)) { if(ud.getPassword().equals(password)) {
ConnectedUser user = new ConnectedUser(conn.getRemoteSocketAddress(), ud.getUsername(), ud.getUserType()); ConnectedUser user = new ConnectedUser(conn.getRemoteSocketAddress(), ud.getUsername(), UUID.randomUUID().toString(), ud.getUserType());
LoginManager.getInstance().logIn(user); LoginManager.getInstance().logIn(user);
wsServer.sendToClient(conn, new LoggedIn(Internationalization.getPhrase("login-sucessful-message"), "LOGIN ********", user.getUsername(), user.getUserType())); wsServer.sendToClient(conn, new LoggedIn(Internationalization.getPhrase("login-sucessful-message"), "LOGIN ********", user.getUsername(), user.getUserType(), user.getToken()));
Bukkit.getLogger().info(Internationalization.getPhrase("login-sucessful-console", user.toString())); Bukkit.getLogger().info(Internationalization.getPhrase("login-sucessful-console", user.toString()));
return; return;
} }

View File

@ -9,16 +9,18 @@ public class LoggedIn implements JSONOutput{
private String respondsTo; private String respondsTo;
private String username; private String username;
private UserType as; private UserType as;
private String token;
public LoggedIn(String message) { public LoggedIn(String message) {
this.message = message; this.message = message;
} }
public LoggedIn(String message, String respondsTo, String username, UserType as) { public LoggedIn(String message, String respondsTo, String username, UserType as, String token) {
this.message = message; this.message = message;
this.respondsTo = respondsTo; this.respondsTo = respondsTo;
this.username = username; this.username = username;
this.as = as; this.as = as;
this.token = token;
} }
@Override @Override
@ -52,7 +54,11 @@ public class LoggedIn implements JSONOutput{
return "VIEWER"; //This is not a security hole bc its just informative... return "VIEWER"; //This is not a security hole bc its just informative...
} }
} }
private String getToken() {
return token;
}
@Override @Override
public String toJSON() { public String toJSON() {
JsonObject object = new JsonObject(); JsonObject object = new JsonObject();
@ -61,6 +67,7 @@ public class LoggedIn implements JSONOutput{
object.addProperty("respondsTo", getRespondsTo()); object.addProperty("respondsTo", getRespondsTo());
object.addProperty("username", getUsername()); object.addProperty("username", getUsername());
object.addProperty("as", getAs()); object.addProperty("as", getAs());
object.addProperty("token", getToken());
object.addProperty("message", getMessage()); object.addProperty("message", getMessage());
return object.toString(); return object.toString();
} }