[Tutorial] Securing RCON
#1

Securing RCON
There's some tools designed for cracking the RCON password by using many failed attempts. These failed attempts can also cause the server_log.txt to get spammed and also, it will be causing lags on the server when it's getting abused. In this tutorial, I'm showing the ways of keeping the RCON access safe. This tutorial has also been divided into two parts, part 1 is important. Part 2 is also efficient and helps to keep the RCON much safe, however it's optional.

Part : #1

Disabling remote access
Many of you might disagree on disabling the remote access AKA allowing RCON login only in game. Disabling remote access avoids RCON getting cracked externally. It disables the wrong RCON logins which are done by users without entering the game. The callback called "OnRconLoginAttempt" gets called only if the RCON attempt is from the game and it don't support for remote ones. Another good feature of disabling remote access is that it doesn't spam the server_log in case if many false attempts are done.

• Steps to disable remote access
Disabling this is quite easy. All you've to do is to open server.cfg and add a line called "rcon 0". Here's an example:
Код:
echo Executing Server Config...
lanmode 0
rcon_password suchpassmuchsafetywow
rcon 0
maxplayers 50
port 7777
hostname SA-MP Server!
gamemode0 grandlarc 1
filterscripts 
announce 0
query 1
chatlogging 0
weburl Coming Soon
onfoot_rate 40
incar_rate 40
weapon_rate 40
stream_distance 300.0
stream_rate 1000
maxnpc 0
logtimeformat [%H:%M:%S]
Setting your RCON password
Most of them are just setting the RCON password to simple ones, but I'm suggesting to use passwords which contains upper to lower case character and numerals. You can also use other type of characters and make sure that the password is long. When it is long, it would be always a bit hard for the crackers to get it cracked.

Suppose, "mypass" is the RCON password. By spending some time, this can get cracked. There's some methods in cracking the RCON and here's one among the way which is used commonly. They assume the length of the password and changes the character on each loop and on every loop, a RCON login attempt is done.
Код:
mypass is your RCON.

Cracking procedure:
aaaaaa
aaaaab
aaaaac
aaaaad
aaaaae
....
aaaaab
aaaabb
aaaaac

...
mabcde
mypabc
mypabd
...
And so, after a long time there's a possibility in getting "mypass" as the result. Well, of course it might take sometime with many RCON attempts which could spam the server log. That's why it's said to disable the remote RCON password.

Setting up complex passwords

Set up complex password which contains upper and lower cases, numbers and other characters. Here's an example of a password used as RCON password.
Код:
sucHpASSMuchSafetYwow798725
It would bring some sort of "WTF" feeling. But to keep your RCON safe, that's one of the best method! This was just my idea, you can implement your own too.
Using OnRconLoginAttempt
OnRconLoginAttempt is a callback which is called when a player does RCON login attempt in game. It got 3 parameters :

ip[] - The IP address of the person who tried a login attempt.
password[] - The password which had been used for RCON login attempt.
success - If success has come to 1, it means the right RCON login has been done. If 0, it means a login has been failed!

You can do whatever to the IP address using this callback according to the login states. Here's an example of kicking whole players who are using that IP address and in case if a wrong RCON login is done!
pawn Код:
public OnRconLoginAttempt(ip[], password[], success)
{
    if(!success) //! means not. So it generally means if not success or if success is 0.
    { //Opening the brackets to execute the function if a wrong RCON attempt is done.

        new
            IPAddr[16];
        /*Creating an array of 16 cells, which will be holding the IP address of
        the players we loop on the next steps. */


        //Doing a loop through all players :
        for(new i, j = GetMaxPlayers(); i< j; i++)
        {
            /*A question may arise why "GetMaxPlayers()" is being used instead of "MAX_PLAYERS"
            Answer : It's because, MAX_PLAYERS are generally set to 500. And GetMaxPlayers()
            returns the number of player slots a server has. So instead of simply looping
            through 500 where a server might have only 200, its better to loop through 200.

            EDIT : GetMaxPlayers are necessary in cases only if the player slots
                   are low compared to 500. MAX_PLAYERS being a constant define,
                   it returns faster than GetMaxPlayers does. For this, you could
                   store the maximum player slots on a variable when gamemode/filterscript
                   gets loaded.
            */

           
            if(!IsPlayerConnected(i)) continue;
            /*If the player isn't connected, we don't need to execute functions on them.
            So, simply skip that looping index using 'continue'.
            */


            GetPlayerIp(i, IPAddr, sizeof(IPAddr));
            /*Getting the player's IP who had been called on the loop and now
            storing it into the array called "IPAddr".
            */


            /*It's time to compare and see whether the IP address of this player
            and the IP address called for the wrong RCON login is same or not
            using strcmp. Strcmp means to compare two strings. */


            if(!strcmp(ip, IPAddr, false)) //If both of the IP addresses match!
            {
                Kick(i); //Then the player gets kicked for wrong login!
            }
            /*The same step gets repeated to every players online and checks if
            the IP address of that player matches to the one who did a false login.*/

        }
    }
    return 1;
}
Code without explanation:
pawn Код:
new
    g_MaxPlayerSlots = 0;

public OnFilterScriptInit()
{
    //An example of storing data for looping.
    g_MaxPlayerSlots = GetMaxPlayers();
    return 1;
}

public OnRconLoginAttempt(ip[], password[], success)
{
    if(!success)
    {
        new
            IPAddr[16];
        for(new i; i< g_MaxPlayerSlots; i++)
        {
            if(!IsPlayerConnected(i)) continue;
            GetPlayerIp(i, IPAddr, sizeof(IPAddr));
            if(!strcmp(ip, IPAddr, false))
            {
                Kick(i);
            }
        }
    }
    return 1;
}
But there's a negative side for the above - In case if there's multiple players connected through the same IP, it can kick all the users connected with the same IP address.


Part : #2 (Optional)

Using OnPlayerRconLogin
OnPlayerRconLogin is a custom callback created by me. It's a callback which gets called when a player logs in as RCON. Generally IP is being traced and it may result in multiple users. - that's what most of them does. OnPlayerRconLogin results the exact player and you can use it according to it.

OnPlayerRconLogin contains only 1 parameter and that's "playerid". Unlike OnRconLoginAttempt, this don't provide ip address of the person who logs in. Neither the password used. Because this gets called only when a player logs in SUCCESSFULLY as a RCON admin. So, GetPlayerIp can be used to retrieve the IP address and password would be the exact RCON password! Following are some methods by using OnPlayerRconLogin.

WhiteListed IP Addresses

The purpose of this method is to see if a player who logs in as RCON got the IP address which is white-listed. If not, it will be kicking the player.
pawn Код:
#include <a_samp>
#include <OPRL3>

new
    WhiteListedIPAddr[][] = { //Creating a 2D array to store IP addresses which will be used below.
    {"127.0.0.1"},
    {"192.0.0.1"}}; //You can add more just like these are done, but don't add a ',' on the last one.

public OnPlayerRconLogin(playerid)
{
    if(!IsPlayerConnected(playerid)) return 0; //Using OPRL2, it does a 1 second delay to get the callback called.
    //So, in between if the player quits, you can just return 0 on the callback and the other stuffs dont get called.

    //Now, a player has been logged in as RCON. Determining whether the player got the IP which is stored on array.
    new
        pIPAddr[16]; //Creating an array with 16 cells to store the player's IP.
    GetPlayerIp(playerid, pIPAddr, sizeof(pIPAddr)); //Now the IP of the player has been stored on "pIPAddr"

    /*Just like we used strcmp on the part 1, let's compare the IP address with the white list.
    But this time, a loop is necessary as the "WhiteListedIPAddr" is a 2D array with multiple IP addresses.
    */

    new bool:IP_Matches = false; /*Create a boolean variable and set it to false. We can set it to true if
                                    players IP matches with any one among the IP addresses specified on the
                                    whitelist array. */

    for(new i; i< sizeof(WhiteListedIPAddr); i++) //Does looping on the size of this array. It will be returning 2 as 2 IP is only stored.
    {
        if(!strcmp(pIPAddr, WhiteListedIPAddr[i], false))
        {
            /*
            If the IP address of player is equal to any index of the whitelisted IPs:*/

            IP_Matches = true;
            break; //Breaks the loop as it's no longer needed.
        }
    }
    if(!IP_Matches)
    {
        /*If the 'IP_Matches' bool is still 0, which means the IPs don't match. If so:*/
        Kick(playerid); //Kicks the player!
    }
    return 1;
}
Code without explanation :
pawn Код:
public OnPlayerRconLogin(playerid)
{
    if(!IsPlayerConnected(playerid)) return 0;
    new
        pIPAddr[16];
    GetPlayerIp(playerid, pIPAddr, sizeof(pIPAddr));
    new
        bool:IP_Matches = false;
    for(new i; i< sizeof(WhiteListedIPAddr); i++)
    {
        if(!strcmp(pIPAddr, WhiteListedIPAddr[i], false))
        {
            IP_Matches = true;
            break;
        }
    }
    if(!IP_Matches)
    {
        Kick(playerid);
    }
    return 1;
}
This same method can be also used for "WhiteListed - nick names" of RCON logins.
Second RCON password
Second RCON system is a method which is done via script. It's an external RCON system which can be useful to keep RCON logins safe. Most of the second RCON systems done is getting called when a IP address does, not a player. That's not safe because in case if a cheater is also at second RCON, it cannot be safe if any sort of data is being sent when a second RCON login is also done.

To ensure it, OnPlayerRconLogin can also be used here so that it sends to the one who logged in the 1st RCON. Second RCON password system is always safe using dialogs and I'll be mentioning some secure tips too about the Second RCON.

pawn Код:
#include <a_samp> //Including a_samp
#include <OPRL3> //Including OnPlayerRconLogin

new
    bool:RCON2LoggedIn[MAX_PLAYERS], //This boolean array is to check whether the player is logged in as RCON or not.
    RCON2WrongLogins[MAX_PLAYERS]; //And this one is to check how many wrong second RCON logins has been done.

#define SECOND_RCON_DIALOG  1126 //Dialog ID of the second RCON.
#define SECOND_RCON_PASS    "testing" //Second RCON password, you can change it but keep it in quotes.
#define MAX_FALSE_2RCON_ATTEMPTS    3 //Maximum number of second RCON fails.
#define MAX_HOLD_SECONDS    120 //Maximum seconds in which a player could stay as RCON without confirming second RCON.

/*
When a player connects, it's necessary to clear the arrays so that there
won't be any collission occurring.*/

public OnPlayerConnect(playerid)
{
    /*
        First, before resetting it directly a simple verification is being done.
        In case if "OnPlayerConnect" is called directly via script, and if
        player was logged in as RCON, we don't need to reset the variable.
        So, checking if player has logged in as RCON before, if so, setting the
        second RCON login as true.
    */

    if(IsPlayerAdmin(playerid))
    {
        RCON2LoggedIn[playerid] = true;
    }
    /* Else if not, then second rcon array of player will remain false. */
    else
    {
        RCON2LoggedIn[playerid] = false;
    }
    /*Resetting the number of wrong second RCON login counts to 0 */
    RCON2WrongLogins[playerid] = 0;
    return 1;
}

/*
Sometimes, if using it as a filterscript, there's a chance of reloading it. So
at those times, resetting variables of players is necessary.
*/

public OnFilterScriptInit()
{
    /*Running a loop around maximum player slots of a server*/
    for(new i, j = GetMaxPlayers(); i< j; i++)
    {
        /*From those, choosing the connected ones only.*/
        OnPlayerConnect(i); //Calling out THIS script's OnPlayerConnect function so that second rcon's reset occurs.
    }
    /*
        This isn't necessary, the below one. I'm just explaining a
        filterscript I created.
    */

    printf("-------------------------------------------------");
    printf("    OnPlayerRconLogin - Second RCON system");
    printf("                    Loaded!");
    printf("-------------------------------------------------");
    return 1;
}

/*Now, time to show RCON login request to the player who logs in as RCON.
For that, using "OnPlayerRconLogin" : */

public OnPlayerRconLogin(playerid)
{
    /*We can first determine if the player isn't logged in as second RCON already.
    This is done to ensure that there won't be any collission in regaring it. */

    if(RCON2LoggedIn[playerid] == false)
    {
        /*If not logged in, we're notifying the player that this server uses a second
        Rcon system and to get logged in completely, second rcon pass should also be executed. */

        SendClientMessage(playerid, -1, "{FF0000}Server : {880088}Before playing as RCON Administrator, please login to the second RCON option too.");
        ShowPlayerDialog(playerid, SECOND_RCON_DIALOG, DIALOG_STYLE_INPUT, "Second RCON Login", "Hello,\nBefore accessing RCON, please login to the second RCON.", "Login", "");
        /*
            Why a timer?
            Sometimes, the player might remian in the dialog and do nothing. That would avoid the second RCON, so to avoid that,
            a timer will be done. And if the player hasn't still logged in when the timer passes, we can take actions.
        */

        SetTimerEx("DetectSecondRconLogin", 1000*MAX_HOLD_SECONDS, false, "d", playerid);
    }
    return 1;
}

/*
As second RCON uses dialog system, it will have the response when the player clicks any one among the button.
And the handling can be done through here: */

public OnDialogResponse(playerid, dialogid, response, listitem, inputtext[])
{
    /*Checking if the dialogid is the second RCON's dialog which you have defined earlier. */
    if(dialogid == SECOND_RCON_DIALOG)
    {
        new
            string[128]; //Creating an array of 128 cells for storing a string. This would be useful and will be followed below:

        //If that's the dialog, we're looking for the response. If response means if the 1st button is selected.
        if(response)
        {
            /*If it's the first button, let's look the string length of the password typed.
            '!' means not. So !strlen means if 0 length. If so :*/

            if(!strlen(inputtext))
            {
                RCON2WrongLogins[playerid]++; //Assuming it as a failure on second Rcon login and counting the warns.

                /*While the count has been done, we can check if the wrong second rcon has been done more than
                or equal to the maximum number of fail logins we allow. If its reached to the max: */

                if(RCON2WrongLogins[playerid] >= MAX_FALSE_2RCON_ATTEMPTS)
                {
                    new
                        Lname[MAX_PLAYER_NAME]; //Another array called "Lname" with the size of MAX_PLAYER_NAME (default : 24) to store name of the player.
                    GetPlayerName(playerid, Lname, sizeof(Lname)); //We've stored player's name on Lname array.
                    //Formatting the string with names and Ids, as we're kicking the player with a message to all.
                    format(string, sizeof(string), "%s (ID:%d) has been automatically kicked from the server! (Reason : %d/%d)", Lname, playerid, RCON2WrongLogins[playerid], MAX_FALSE_2RCON_ATTEMPTS);
                    //We've formatted the message with the reason that the player has done much fails on second RCON.
                    SendClientMessageToAll(0xFF0000FF, string); //Sends that message to all!
                    /*Returning this callback with a timer and another question may arise:
                    Again a timer? Y u no kick directly?
                    It's because since the launch of SA-MP 0.3x, kick/ban functions are fast and the messages
                    might not get displayed to the player. So to avoid that, a delayed kick is done. 150ms isn't that
                    long, it will go faster. */

                    return SetTimerEx("KickPlayer", 150, false, "d", playerid);
                   
                    /*
                    So the part if player has done too much fails has been over as we're returning it directly.
                    Now, for other steps: */

                }
                /*
                    On the previous part, we checked if player has crossed the limit, now we can assume
                    that player has done wrong rcon logins lesser than the maximum and we're issuing a warning to player.
                */

               
                //Formatting the string with a warning message. Rather than creating another array, use the same which has been declared.
                format(string, sizeof(string), "ERROR! The previous second RCON given was incorrect! (Warnings : %d/%d)", RCON2WrongLogins[playerid], MAX_FALSE_2RCON_ATTEMPTS);
                SendClientMessage(playerid, 0xFF0000FF, string); //Sends the message to the player in regarding it.
               
                //Now returning this part by showing the second RCON login page again otherwise the system could be easily evaded!
                return ShowPlayerDialog(playerid, SECOND_RCON_DIALOG, DIALOG_STYLE_INPUT, "Second RCON Login", "Hello,\nBefore accessing RCON, please login to the second RCON.", "Login", "");
               
                //The part of inputtext being invalid has been over!
            }
           
            //Now for the right and wrong RCON passwords given.
           
            /*
                Let's compare for the password using strcmp. If it's
                matching each other :*/

            if(!strcmp(inputtext, SECOND_RCON_PASS, false))
            {
                //If the typed text matches the define "SECOND_RCON_PASS", it means the player has logged in as second RCON too.
                GameTextForPlayer(playerid, "~G~ACCESS GRANTED!", 2000, 3);
                //Notifying the player with a sound and text that the access has been granted!
                PlayerPlaySound(playerid, 1057, 0.0, 0.0, 0.0);
                //Setting the second RCON array of this player to true.
                RCON2LoggedIn[playerid] = true;
                return 1; //Returning the callback as further works aren't needed.
            }
           
            /*
                But what if the text typed isn't the password!
            */

            else if(strcmp(inputtext, SECOND_RCON_PASS, false))
            {
                /*
                    If so, we're doing the above part again in increasing the
                    warnings and checking if the limits has been crossed!
                */

                RCON2WrongLogins[playerid]++;
                if(RCON2WrongLogins[playerid] >= MAX_FALSE_2RCON_ATTEMPTS)
                {
                    new
                        Lname[MAX_PLAYER_NAME];
                    GetPlayerName(playerid, Lname, sizeof(Lname));
                    format(string, sizeof(string), "%s (ID:%d) has been automatically kicked from the server! (Reason : Wrong RCON logins | %d/%d)", Lname, playerid, RCON2WrongLogins[playerid], MAX_FALSE_2RCON_ATTEMPTS);
                    SendClientMessageToAll(0xFF0000FF, string);
                    return SetTimerEx("KickPlayer", 150, false, "d", playerid);
                }
                format(string, sizeof(string), "ERROR! The previous second RCON given was incorrect! (Warnings : %d/%d)", RCON2WrongLogins[playerid], MAX_FALSE_2RCON_ATTEMPTS);
                SendClientMessage(playerid, 0xFF0000FF, string);
                return ShowPlayerDialog(playerid, SECOND_RCON_DIALOG, DIALOG_STYLE_INPUT, "Second RCON Login", "Hello,\nBefore accessing RCON, please login to the second RCON.", "Login", "");
            }
        /*The work under pressing the first button has been over! */
        }
       
        /*Now the response which is called if a player presses ESC button or second dialog button */
        if(!response)
        {
            //We're assuming that as a 'KICK' for player. And the system kicks the player.

            new
                Lname[MAX_PLAYER_NAME]; //Creating an array to store the player's name.
            GetPlayerName(playerid, Lname, sizeof(Lname)); //Stores the player's name at "Lname"
            format(string, sizeof(string), "%s (ID:%d) has been automatically kicked from the server! (Reason : Wrong RCON login)", Lname, playerid);
            //Formatting the text in player getting kicked for quitting the RCON login and sends the message to all!
            SendClientMessageToAll(0xFF000FF, string);
            //Same as above, it returns the callback with a timer to kick.
            return SetTimerEx("KickPlayer", 150, false, "d", playerid);
        }
    }
    return 1;
}

//The callbacks used on timers are required to be public and so must be forwarded!
forward KickPlayer(playerid); //The kick function to get executed.
forward DetectSecondRconLogin(playerid); //To detect if player hasn't logged in as second rcon even after logging in as RCON.


public DetectSecondRconLogin(playerid)
{
    //When this is called, we're checking if the player has logged in as second RCON.
    //Due to disconnections and quick connections, the timer may go on other players. That's why we're looking if this player is RCON admin.
    if(IsPlayerAdmin(playerid))
    {
        //If so, checking whether second RCON hasn't been logged in:
        if(RCON2LoggedIn[playerid] == false)
        {
            //If so, we're again kicking the player by declaring "Lname" to store name and "string" as message.
            new
                Lname[MAX_PLAYER_NAME],
                string[128];
            GetPlayerName(playerid, Lname, sizeof(Lname));
            format(string, sizeof(string), "%s (ID:%d) has been automatically kicked from the server! (Reason : Delayed RCON confirmation)", Lname, playerid);
            SendClientMessageToAll(0xFF0000FF, string);
            //Here also, the callback is returned by Kicking the player.
            return SetTimerEx("KickPlayer", 150, false, "d", playerid);
        }
    }
    return 1;
}

//What KickPlayer should do is that it must directly kick the player. And so, the callback returns with "Kick" function.
public KickPlayer(playerid) return Kick(playerid);
Code without explanation:
https://raw.github.com/Lordzy/OPRL/m...SecondRCON.pwn


Tips in using second-RCON
There's some programs which can avoid dialogs and can enter the chat box. So, even second RCON boxes can be ignored. This can be ensured by:

• Creating a boolen array for second RCON. Set it to "true" if player has logged in the second RCON successfully, else false.
• Under "OnPlayerText" and "OnPlayerCommandText", check if player is logged in as RCON using IsPlayerAdmin. If so, make sure that player is also logged in as second RCON. If not, it means that the player has evaded the second RCON dialog. You can kick/ban then to make things clear.
• ZCMD users can use "OnPlayerCommandReceived" and "OnPlayerCommandPerformed" callback using the same above step.
• When a second RCON box is shown, better freeze the player. And check if the velocity is changing under OnPlayerUpdate. Use warn counts and then kick/ban in case if that overrides. It means, the player has evaded second RCON dialog.

Example of these tips based on the second RCON script I've posted:
pawn Код:
//Under that script:

public OnPlayerCommandText(playerid, cmdtext[])
{
    /*If player is RCON admin, but not logged in as second RCON.
    ! means not. So !RCON2LoggedIn means if RCON2LoggedIn is 0 or false.*/

    if(IsPlayerAdmin(playerid) && !RCON2LoggedIn[playerid]) return Kick(playerid);
    return 0;
}

public OnPlayerText(playerid, text[])
{
    /*If player is RCON admin, but not logged in as second RCON.
    ! means not. So !RCON2LoggedIn means if RCON2LoggedIn is 0 or false.*/

   
    /*A ! is being used before Kick is to ensure that it returns 0 after kicking.
    So that the chat won't be displayed. */

    if(IsPlayerAdmin(playerid) && !RCON2LoggedIn[playerid]) return !Kick(playerid);
    return 1;
}
And for cmds which requires "IsPlayerAdmin", you could also check if second RCON array is true. Here's a small function in regarding it.
pawn Код:
stock bool:IsPlayerAdminEx(playerid)
{
    if(IsPlayerAdmin(playerid) && RCON2LoggedIn[playerid]) return true;
    else return false;
}
• OnPlayerRconLogin : https://sampforum.blast.hk/showthread.php?tid=491835
• Questions, Suggestions, Feed backs or whatever in relating to the tutorial are always welcome.
• This topic will be getting updated.

-Lordz
Reply


Messages In This Thread
Securing RCON - by Lordzy - 08.03.2014, 10:46
Re: Securing RCON - by Lordzy - 08.03.2014, 10:49
Re: Securing RCON - by Yashas - 08.03.2014, 10:56
Re: Securing RCON - by Lordzy - 08.03.2014, 10:59
Re: Securing RCON - by Misiur - 08.03.2014, 11:48
Re: Securing RCON - by kristo - 08.03.2014, 14:04
Re: Securing RCON - by Lordzy - 08.03.2014, 15:01
Re: Securing RCON - by Vince - 08.03.2014, 16:04
Re: Securing RCON - by Lordzy - 08.03.2014, 16:10
Re: Securing RCON - by Lordzy - 09.03.2014, 05:10

Forum Jump:


Users browsing this thread: 1 Guest(s)