Aller au contenu

Photo

Securing Your Server Without Master Server Authentication


  • Veuillez vous connecter pour répondre
100 réponses à ce sujet

#1
FunkySwerve

FunkySwerve
  • Members
  • 1 308 messages
Since the MS has been taken down at least temporarily, there are a lot of people having problems with players logging in to other player's accounts. This is a scriptset designed to protect your server from these account thefts by linking playernames to cds like the master server does. I decided to move these out of the sticky thread on the MS being taken down, since it was getting a bit cluttered. Again, these are reposts. I'll start with the native bioware database version:

PLEASE NOTE: Windows servers treat playernames as case-insensitive (FunkySwerve is the same as funkyswerve). Linux servers treat them as case-sensitive. Because of this, I've added code to convert all playernames to lowercase. I'm putting the function calls in red (assuming the bioboard coloring systems don't revolt on me), because they're entirely optional and possibly even undesirable for Linux users.

=============================

Here's a system to link cd keys to playernames using the native bioware database.

When a character logs in, it will automatically check their cd key from oncliententer, and compare it to a Campaign variable stored in the database. If that variable is not set, this is the first time that playername has logged in (at least since this system was installed). The key will be linked to their account by setting that variable, and they can proceed with play as normal, unaware that anything has happened. If the variable is already set, however, and it does not match they key they are using, they are booted. This setup allows you to accumulate CD Key info as you go, and assumes that the first login of an account is ligit - an assumption that held true on our server. It's technically possible someone else could get to it first, but the chances are small, since most need to see the account name in use first in order to steal it (unless it's known already). Generally, I think this is far preferable to server passwording, as it's less of an impediment to players, an the chances of a ]addressed as soon as the legitimate logger posts on the forums of your server reporting the account lockout.

This system is a little more complex than that, though. It also allows addition of multiple keys, up to 7, for a playername - you'd be amazed at how many players use more than one. On our server, it's done via a conversation fired from a item they get when they enter the docks. The conditional checks to make sure they don't already have 7 keys added (this one returns FALSE if they do).


int StartingConditional()
{
    object oPC = GetPCSpeaker();
    string sPName = GetStringLowerCase(GetPCPlayerName(oPC));
    string sStoredKey = GetCampaignString("PlayernameKey", sPName );
    if (sStoredKey != "") {
        int nLength =  GetStringLength(sStoredKey);
        if (nLength > 65) /* allow 7 keys max SET-key-key-key-key-key-key-key   SET/ADD + 7 spacers + 7x8 keys = 66 */
            return FALSE;
    }
    return TRUE;
}


If they do not have the maximum allowed already, their account is marked as ready to accept a new key, and they are asked to logout, swap to the new key, and log in again. This is the action taken script for that line:


void main()
{
    object oPC = GetPCSpeaker();
    string sPName = GetStringLowerCase(GetPCPlayerName(oPC));
    string sStoredKey = GetCampaignString("PlayernameKey", sPName);
    string sKeys = "ADD" + GetStringRight(sStoredKey, GetStringLength(sStoredKey) - 3);//mark as adding
    SetCampaignString("PlayernameKey", sPName, sKeys);
}


Here is the oncliententer code I mentioned at the outset, which should make more sense now that you know the procedure for adding keys:



int VerifyPlayernameAgainstCDKey(object oPlayer) {
    int nBoot = FALSE;
    string sPName = GetStringLowerCase(GetPCPlayerName(oPlayer));
    string sKey = GetPCPublicCDKey(oPlayer);

    string sNewKey, sAddingKey, sStoredKey = GetCampaignString("PlayernameKey", sPName);

    /* there's at least one key stored already */

    if (sStoredKey != "") {
        sAddingKey = GetStringLeft(sStoredKey, 3);
        sStoredKey = GetStringRight(sStoredKey, GetStringLength(sStoredKey) - 3);

        /* they indicated that they wanted to add a key this login */

        if (sAddingKey == "ADD") {

            /* their current key is not in the key string, add it unless at 7 keys already */
            if (FindSubString(sStoredKey, sKey) == -1) {
                int nKeyLength = GetStringLength(sStoredKey);

                /* allow 7 keys max SET-key-key-key-key-key-key-key   SET/ADD + 7 spacers + 7x8 keys = 66 */
                if (nKeyLength > 65) {
                    nBoot = TRUE;

                    /* must mark as no longer adding */
                    SetCampaignString("PlayernameKey", sPName, "SET" + sStoredKey);

                    /* add the key to the string */
                } else {
                    sNewKey = "SET" + sStoredKey  + "-" + sKey;
                    SetCampaignString("PlayernameKey", sPName, sNewKey);
                    DelayCommand(25.0, FloatingTextStringOnCreature("New CD Key Successfully Added!", oPlayer, FALSE));
                }

                /* let them know they already had this key in their string */
            } else {
                DelayCommand(25.0,
                    FloatingTextStringOnCreature("CD Key Addition Failed! This key already listed for this account!", oPlayer,
                        FALSE));

                /* must mark as no longer adding */
                SetCampaignString("PlayernameKey", sPName, "SET" + sStoredKey);
            }


            /* they are not adding, and the cd key doesnt match those listed - boot and log */
        } else if (FindSubString(sStoredKey, sKey) == -1) {
            string sReport = "INCORRECT CD KEY DETECTED! ID: " + sPName + "; Name: " +
                GetName(oPlayer) + "; CD Key: " + sKey + "; IP: " + GetPCIPAddress(oPlayer);

            WriteTimestampedLogEntry(sReport);
            SendMessageToAllDMs(sReport);

            nBoot = TRUE;
        }


        /* new account, add the key */
    } else {
        SetCampaignString("PlayernameKey", sPName, "SET-" + sKey);
    }

    return nBoot;
}
void main() {

    object oPC = GetEnteringObject();

    /* verify CD keys and double logins to stop hackers */
    if (VerifyPlayernameAgainstCDKey(oPC)) {
        if (GetIsObjectValid(oPC))
            BootPC(oPC);
        return;
    }
}





Here's an onplayerchat event script I wrote for DMs to use ingame to wipe cdkey-playername associations - inevitably, players lose their keys, and this allows you to reset the playername to accept a new key, by entering their playername after 'dm_wipekeys'. It is meant for native bioware database users - mysql and sqlite users should have command-line access that makes such a command unnecessary, but I can code one up if someone needs it.

Example command line spoken ingame:
dm_wipekeys Funky

would wipe the key listings for playername Funky ingame, allowing that player to log in using whatever cd key they have. This is important because players DO lose keys on occasion. It will also allow you to wipe a false association by an account thief, should one manage to log in before the true owner, as discussed above.


void main() {
    string sMessage = GetPCChatMessage();
    object oPC = GetPCChatSpeaker();
    if (GetStringLeft(sMessage, 12) == "dm_wipekeys ") {
        if (!GetIsDM(oPC))
            FloatingTextStringOnCreature("Only DMs may use this command!", oPC, FALSE);
        else {
            string sPlayerName = GetStringRight(sMessage, GetStringLength(sMessage)-12);
            sPlayerName = GetStringLowerCase(sPlayerName);
            string sStoredKey = GetCampaignString("PlayernameKey", sPlayerName);
            if (sStoredKey != "") {
                DeleteCampaignVariable("PlayernameKey", sPlayerName);
                FloatingTextStringOnCreature("CD Key bindings for Playername: '" + sPlayerName + "' erased.", oPC, FALSE);
            } else {
                FloatingTextStringOnCreature("No CD Key bindings for Playername: '" + sPlayerName +
                    "' were found! Please check to make sure you entered the right name.", oPC, FALSE);
            }
        }
    }
}

Modifié par FunkySwerve, 14 décembre 2012 - 09:03 .

  • hexmendacious, Kato - et RustyNail aiment ceci

#2
FunkySwerve

FunkySwerve
  • Members
  • 1 308 messages
Here is the code for those using NWNX with MySQL or SQLite and the default NWNX pwdata database table.
=========================

This is a modified repost from the Tales of the Silver Marchers Server Admin forum, where TSMDude has set up a forum for about 20 server admins to talk security, once it's been verified that they are in fact admins - posting some code can reveal vulnerabilities. In this case, however, there's little a determined troublemaker can glean from this code.

PLEASE NOTE: Windows servers treat playernames as case-insensitive (FunkySwerve is the same as funkyswerve). Linux servers treat them as case-sensitive. Because of this, I've added code to convert all playernames to lowercase. I'm putting the function calls in red (assuming the bioboard coloring systems don't revolt on me), because they're entirely optional and possibly even undesirable for Linux users.

========Begin Repost=========
Here's a system to link cd keys to playernames. This system assumes you are using NWNX with MySQL or SQLite and the default pwdata database. It should be easy enough to tweak to another setup, including the default Bioware database - I can help if you like.

When a character logs in, it will automatically check their cd key from oncliententer, and compare it to a persistent variable ]time that playername has logged in (at least since this system was installed). The key will be linked to their account by setting that variable, and they can proceed with play as normal, unaware that anything has happened. If the variable is already set, however, and it does not match they key they are using, they are booted. This setup allows you to accumulate CD Key info as you go, and assumes that the first login of an account is ligit - an assumption that held true on our server. It's technically possible someone else could get to it first, but the chances are small, since most need to see the account name in use first in order to steal it (unless it's known already). Generally, I think this is far preferable to server passwording, as it's less of an impediment to players, an the chances of a ]first logger are quite small, and can be addressed as soon as the legitimate logger posts on the forums of your server reporting the account lockout.

This system is a little more complex than that, though. It also allows addition of multiple keys, up to 7, for a playername - you'd be amazed at how many players use more than one. On our server, it's done via a conversation fired from a item they get when they enter the docks. The conditional checks to make sure they don't already have 7 keys added (this one returns FALSE if they do).

#include "aps_include"
int StartingConditional()
{
    object oPC = GetPCSpeaker();
    string sPlayer = GetStringLowerCase(GetPCPlayerName(oPC));
    sPlayer = SQLEncodeSpecialChars(sPlayer);
    string sSQL = "SELECT val FROM pwdata WHERE name='PlayernameKey_" + sPlayer + "'";
    SQLExecDirect(sSQL);
    if (SQLFetch() == SQL_SUCCESS) /* there's at least one key stored already */
    {
        string sStoredKey = SQLGetData(1);
        int nLength =  GetStringLength(sStoredKey);
        if (nLength > 61) /* allow 7 keys max key-key-key-key-key-key-key    6 spacers + 7x8 keys = 62 */
        {
            return FALSE;
        }
        else return TRUE;
    }

    return TRUE; /* this should never be reached if your database is running, since the first key add is automatic oncliententer */
}


If they do not have the maximum allowed already, their account is marked as ready to accept a new key, and they are asked to logout, swap to the new key, and log in again. This is the action taken script for that line:


#include "aps_include"
void main()
{
    object oPC = GetPCSpeaker();
    string sPlayer = GetStringLowerCase(GetPCPlayerName(oPC));
    sPlayer = SQLEncodeSpecialChars(sPlayer);
    string sSQL = "UPDATE pwdata SET tag='Adding' WHERE name='PlayernameKey_"+ sPlayer + "'"; //must mark as adding
    SQLExecDirect(sSQL);
}


Here is the oncliententer code I mentioned at the outset, which should make more sense now that you know the procedure for adding keys:


#include "aps_include"

int VerifyPlayernameAgainstCDKey(object oPlayer) {
    int nBoot = FALSE;
    string sUnencoded = GetStringLowerCase(GetPCPlayerName(oPlayer));
    string sPlayer = SQLEncodeSpecialChars(sUnencoded);
    string sKey = GetPCPublicCDKey(oPlayer);
    string sStoredKey, sAddingKey;
    string sSQL = "SELECT val, tag FROM pwdata WHERE name='PlayernameKey_" + sPlayer + "'";

    SQLExecDirect(sSQL);

    /* there's at least one key stored already */

    if (SQLFetch() == SQL_SUCCESS) {
        sStoredKey = SQLGetData(1);
        sAddingKey = SQLGetData(2);

        /* they indicated that they wanted to add a key this login */

        if (sAddingKey == "Adding") {

            /* their current key is not in the key string, add it unless at 7 keys already */
            if (FindSubString(sStoredKey, sKey) == -1) {
                int nKeyLength = GetStringLength(sStoredKey);

                /* allow 7 keys max key-key-key-key-key-key-key    6 spacers + 7x8 keys = 62 */
                if (nKeyLength > 61) {
                    nBoot = TRUE;

                    /* must mark as no longer adding */
                    sSQL = "UPDATE pwdata SET tag='Set' WHERE name='PlayernameKey_" + sPlayer + "'";
                    SQLExecDirect(sSQL);

                    /* add the key to the string */
                } else {
                    sSQL =
                        "UPDATE pwdata SET tag='Set',val='" + sStoredKey + "-" + sKey + "' WHERE name='PlayernameKey_" + sPlayer +
                        "'";
                    SQLExecDirect(sSQL);
                    DelayCommand(25.0, FloatingTextStringOnCreature("New CD Key Successfully Added!", oPlayer, FALSE));
                }


                /* let them know they already had this key in their string */
            } else {
                DelayCommand(25.0,
                    FloatingTextStringOnCreature("CD Key Addition Failed! This key already listed for this account!", oPlayer,
                        FALSE));

                /* must mark as no longer adding */
                sSQL = "UPDATE pwdata SET tag='Set' WHERE name='PlayernameKey_" + sPlayer + "'";
                SQLExecDirect(sSQL);
            }


            /* they are not adding, and the cd key doesnt match those listed - boot and log */
        } else if (FindSubString(sStoredKey, sKey) == -1) {
            string sReport = "INCORRECT CD KEY DETECTED! ID: " + sUnencoded + "; Name: " +
                GetName(oPlayer) + "; CD Key: " + sKey + "; IP: " + GetPCIPAddress(oPlayer) ;

            WriteTimestampedLogEntry(sReport);
            SendMessageToAllDMs(sReport);

            nBoot = TRUE;
        }


        /* new account, add the key */
    } else {
        sSQL = "INSERT INTO pwdata (val,name) VALUES" + "('" + sKey + "','PlayernameKey_" + sPlayer + "')";
        SQLExecDirect(sSQL);
    }

    return nBoot;
}
void main() {

    object oPC = GetEnteringObject();

    /* verify CD keys and double logins to stop hackers */
    if (VerifyPlayernameAgainstCDKey(oPC)) {
        if (GetIsObjectValid(oPC))
            BootPC(oPC);
        return;
    }
}


Here is a sample of 25 playername keys in pwdata. I find looking at database entries helps me to understand what is going on. I have heavily edited the playernames and cd keys to protect their owners (the keys bear almost no relation to their original characters, and might as well be invented from whole cloth). I selected from the very beginning of our table, because it has a few longstanding 'Adding' vars that never got added (only a dozen or so in 5-6 years of this system's operation, though, not bad, including two recent pending ones). That should help you to understand all the steps of the system's code.


+--------+--------+-------------------------------------+---------------------------------------+--------+---------------------+
| player | tag    | name                                | val                                   | expire | last                |
+--------+--------+-------------------------------------+---------------------------------------+--------+---------------------+
| ~      | Adding | PlayernameKey_.toov48fe.S8olen      | A7U1XGY3                              |      0 | 2009-03-08 23:47:35 |
| ~      | Adding | PlayernameKey_234fg3c7b             | BTM2NLH6                              |      0 | 2007-09-06 04:54:53 |
| ~      | Adding | PlayernameKey_Hae3v0                | CPQ3KDRH                              |      0 | 2010-05-09 17:44:13 |
| ~      | Adding | PlayernameKey_Monoridalblad3sz      | DEV64QN7-DTGM5U3G-DGME6VGQ-DLP37JT3   |      0 | 2011-06-26 19:21:37 |
| ~      | Adding | PlayernameKey_isnku6                | EVRM5DDR                              |      0 | 2009-09-10 00:53:52 |
| ~      | Adding | PlayernameKey_Lafigan4              | FNWX6MMG                              |      0 | 2008-03-03 17:18:59 |
| ~      | Adding | PlayernameKey_Kohhn5er              | GEJQ79DE                              |      0 | 2008-12-09 04:40:57 |
| ~      | Adding | PlayernameKey_Yero8en               | HFU8QFMF                              |      0 | 2007-02-06 01:25:42 |
| ~      | Adding | PlayernameKey_lh8ipd02              | IGM9S9G4                              |      0 | 2010-01-16 06:17:32 |
| ~      | Adding | PlayernameKey_opsadl0               | JVC60X6Y                              |      0 | 2011-06-26 23:28:57 |
| ~      | Adding | PlayernameKey_ter3jik1              | KYU1W9L                               |      0 | 2007-09-07 20:53:52 |
| ~      | Adding | PlayernameKey_Ohewfcpw_o0e          | LT62XQR7-LORM8R3Y                     |      0 | 2008-09-02 19:55:11 |
| ~      | Adding | PlayernameKey_tymorsg3huycna6i3s    | MCR3KK3K                              |      0 | 2009-09-24 16:03:39 |
| ~      | Adding | PlayernameKey_I6lmanh               | NG64R3QY-LPDX3XQ7                     |      0 | 2008-09-02 19:53:27 |
| ~      | Set    | PlayernameKey_---Huir---            | OQ45HVTC-ODX27Q7M-OW4Z5PF             |      0 | 2008-06-03 18:47:52 |
| ~      | Set    | PlayernameKey_--k10m--              | P6JG5EEQ-PQC347MT                     |      0 | 2007-02-21 07:41:40 |
| ~      | Set    | PlayernameKey_-Titafi-              | QV47LKGA-QQC73GP-QTEV35VX             |      0 | 2010-01-19 17:16:59 |
| ~      | Set    | PlayernameKey_-Wh1afn-              | R7H68YEU-R2GVFC4P                     |      0 | 2010-01-13 15:58:43 |
| ~      | Set    | PlayernameKey_-Xa7kjo Furrl9oatoon- | SCU96DY6-S0SDK7RA                     |      0 | 2009-04-21 20:11:41 |
| ~      | Set    | PlayernameKey_-Vef-                 | TZVX0T7L-TJD72MR6-TVDEW4PF            |      0 | 2008-03-27 10:10:15 |
| ~      | Set    | PlayernameKey_-Uohy-                | U7Y19IT7-UQYUOADQ                     |      0 | 2009-06-29 10:40:20 |
| ~      | Set    | PlayernameKey_-NejG-                | V772WQHF-VQYIUXYA                     |      0 | 2009-04-09 01:52:12 |
| ~      | Set    | PlayernameKey_.Ga4o                 | W773B4HJ-WA9X2PRC                     |      0 | 2008-04-23 04:33:52 |
| ~      | Set    | PlayernameKey_00Wau611              | XCQJ4U6D-X7QLFE1F-X75NXDKH            |      0 | 2009-02-19 18:22:39 |
| ~      | Set    | PlayernameKey_1Shagi                | YCR5FVVE-YCG2QRW6                     |      0 | 2008-03-17 16:29:51 |
+--------+--------+-------------------------------------+-------------------------------------+--------+---------------------+


Here's the pwdata table info, should you want it. If you have the skill, you're probably better off making your own table with columns labeled to your liking - the player column, for example, is totally unnecessary if you're using custom database calls instead of aps_include. Really, all you need is 3 columns: status (adding or set), playername, keys.


mysql> describe pwdata;
+--------+-------------+------+-----+-------------------+-----------------------------+
| Field  | Type        | Null | Key | Default           | Extra                       |
+--------+-------------+------+-----+-------------------+-----------------------------+
| player | varchar(64) | NO   | PRI | ~                 |                             |
| tag    | varchar(64) | NO   | PRI | ~                 |                             |
| name   | varchar(64) | NO   | PRI | ~                 |                             |
| val    | text        | YES  |     | NULL              |                             |
| expire | int(11)     | NO   |     | 0                 |                             |
| last   | timestamp   | NO   |     | CURRENT_TIMESTAMP | on update CURRENT_TIMESTAMP |
+--------+-------------+------+-----+-------------------+-----------------------------+
6 rows in set (0.00 sec)

Modifié par FunkySwerve, 14 décembre 2012 - 09:29 .

  • hexmendacious, Kato - et RustyNail aiment ceci

#3
WebShaman

WebShaman
  • Members
  • 913 messages
Really great stuff, Funky! Thank you very much for giving the Community this.

#4
ffbj

ffbj
  • Members
  • 593 messages
Thanks Funky.

#5
OldTimeRadio

OldTimeRadio
  • Members
  • 1 400 messages
Thank you very much for putting this together and sharing it with us!

#6
henesua

henesua
  • Members
  • 3 863 messages
Thanks for doing this, Funky. Useful scripts.

#7
IronRook

IronRook
  • Members
  • 23 messages
Excellent work FunkySwerve.

I have implemented it to my server.

I have set it so you talk to an NPC in the OOC to add your keys.

I have not tried the DM erase keys in the onplayerchat event yet.I am using DMFI 1.07 and I must have a bug because it doesnt like to work when I put scripts in the onplayerchat event.I'm sure I probably screwed a script up somewhere lol.  

I will just add that Bioware has made an excellent game and has an EXCELLNENT COMMUNITY  full of members that make it possible for other people learning  mod building..(scripting ) to make their own module.

:wizard:

#8
FunkySwerve

FunkySwerve
  • Members
  • 1 308 messages
I suspect that you will have difficulty using a chat script with DMFI - you definitely can't with SIMTools, because it uses its own chat event which conflicts with the Bioware one. You can either have an item spawn a listener instead, or merge it with the DMFI scripts, depending on how they're set up. The chat event is simply the easiest way to capture text, if you don't have a conflicting setup, because it only requires a single script (that's why I picked it).

Funky

#9
IronRook

IronRook
  • Members
  • 23 messages
Yeah that sounds good.I can spawn a listner in a DM only map.How would I do that?

#10
FunkySwerve

FunkySwerve
  • Members
  • 1 308 messages
Here's an erf. It contains 2 scripts, a listener creature utc, and an item uti. Both the creature and item will appear in Special/Custom 1 in their respective palettes. When used, the rod will create the creature and prompt the user to speak the playername they wish to delete.

The item script uses tag-based. scripting. It should be relatively easy to convert to an old-style onactivate if you don't use tag-based scripting.

Key Deleter Item .ERF file (Native Bioware Database only)

Funky

Modifié par FunkySwerve, 14 juillet 2011 - 07:55 .


#11
zunath

zunath
  • Members
  • 83 messages
Thanks a lot, Funky.

#12
tltlvilus

tltlvilus
  • Members
  • 2 messages
ty Funky

#13
The Amethyst Dragon

The Amethyst Dragon
  • Members
  • 1 877 messages
Thanks for posting this here, FunkySwerve. Very helpful.

#14
FunkySwerve

FunkySwerve
  • Members
  • 1 308 messages
Still getting questions about this - could you all request a sticky if you haven't already?

Thanks,
Funky

#15
zunath

zunath
  • Members
  • 83 messages
This line in the OnClientEnter script for the NWNX version needs to be changed from
    string sSQL = "SELECT val, tag FROM pwdata WHERE name='PlayernameKey " + sPlayer + "'";

to

    string sSQL = "SELECT val, tag FROM pwdata WHERE name='PlayernameKey_" + sPlayer + "'";

It has a space instead of an underscore.

#16
FunkySwerve

FunkySwerve
  • Members
  • 1 308 messages
Thanks for spotting that. The _ was an addition from our version, to make the var easier to read. Ours has no divider.

Funky

#17
zunath

zunath
  • Members
  • 83 messages
No prob. It works great with that fix :)

#18
WebShaman

WebShaman
  • Members
  • 913 messages
Sticky request sent!

A huge thank you to Mr. Priestly for the quick sticky!!!! :o

Modifié par WebShaman, 09 août 2011 - 04:31 .


#19
mysticjester

mysticjester
  • Members
  • 5 messages
nice work. :-)
s.

#20
OldTimeRadio

OldTimeRadio
  • Members
  • 1 400 messages
Nice!

#21
Alphamojo

Alphamojo
  • Members
  • 19 messages
Awesome as always Funky!

#22
BlackJello

BlackJello
  • Members
  • 1 messages
Thank you so much for sharing your work, Funky. This was very easy to add to my module. I appreciate you sharing it with the community.

#23
dragonvain

dragonvain
  • Members
  • 4 messages
question how do tell the scripts to only take one cd key only

#24
Melkior_King

Melkior_King
  • Members
  • 135 messages
If I understand correctly, the above scripts automatically take only one CD key unless you deliberately allow players to add another CD key by providing them with an item to activate the script which "unlocks" the account again.

#25
ultima03

ultima03
  • Members
  • 38 messages
Non sql data storage is not reliable. (more like horrible)