Quote from Crow on April 24, 2022, 4:30 pm- Open Discussion | Suggestions / Existing implemtation of ways to counteract Bot Flooding, Bruteforcing & Hacking for any RSPS revision -
Contributions are welcome & credited, please keep to topic (Even you Tyluur)Over the years, you may have experienced more malicious activity ramping up around the community, possibly due to servers turning into businesses and only caring about their income/growth.
I myself had created a simple Headless Client back in 2011 for educational purposes to see how many of these servers lacked just simple security measures.Still, in 2021 majority of the servers that are online (even some of the top 10 servers on the toplists at present time of this thread) have absolutely zero security to prevent this.
I know that some of these servers are created for a quick buck, but even implementing any of the following suggestions in this thread would make your staff teams life even easier.This thread is heavily focused on the server-side, as only making changes on the client doesn't resolve the underlining issue.
Contributed code will also be included within this thread, I do not have the time to write-up a system for every-source base but feel free to comment.Expectations of these attacks:
- Headless Client that will include your login stream
- Residential Proxy IPs to attempt to get past any Proxy API detection
- Bypass publicly used Captcha services, by using third-party services such as 2Captcha
- Bypass client-sided security. Such as; hashing, unique packets, RSA etc[Bot Flooding]
- Randomly named accounts that will automatically complete any tutorial and begin flooding PM/CC/IGC/Other Packets
- 1-2000 concurrent logins in attempts to either take-down the server or advertise another[Bruteforcing / Hacking]
- Login attempts ranging from 1-2000 attempts per second (Depending on the networking within the source / current safe-guards)
- 1-100 accounts being attacked at the same time, dumped from highscores or in-game sources
- Login attempts with passwords from leaked RSPS / Gaming databases
- Bruteforce 2FA code (Once password obtained)My current implementation of discouraging Bruteforcing attacks (Subject to change after 16/04/2021):
- Detection to see if the same IP is attempting to login to various accounts, as the same proxy may be used
- Throttling logins per IP - 5 second wait time per attempt, lockout IP for 5 minutes after multiple failed attempts
- Throttling logins per account (Except from an IP that has already successfully logged into the account previously)
- Monitoring system that will alert you if login attempts exceed 10/s (Useful for inspecting the type of attack, when it's happening)
- Warning users before they login that their password is in a compromised database, highly recommending using another password or risk being compromisedMy current implementation of discouraging Bot Flooding attacks (Subject to change after 16/04/2021):
(Detection if 10+ accounts are created within a short-space, choosing to inact the following):
- Choice to stop proxies/vpn's from logging in for X minutes
- Choice to make it so only IPs that have successfully logged into the server from XX-XX-XXXX - XX-XX-XXXX date/time for X minutes
- Unique "tutorial" that is enabled when a Bot Flood attack is detected, confusing the attacker and possibly dissuading them
- Hiding the account from players (within PlayerUpdate), until tutorial is complete [Credit to @Simplex for implementing this in their server]
- Deleting /or Moving accounts to another database that have less than 3 minutes of total play-time (Check every 2-3 months)
- Do not allow saving of an account that haven't completed the "tutorial"My current implementation of discouraging Hacking (Subject to change after 16/04/2021):
- Discourage users from setting passwords that are used in RSPS / Gaming database breaches (RSPS Breached-DB Checker)
- Encourage users to use a secondary password/pin that is not related to the existing password. Limit this to 3-5 attempts per X minutes. Ths secondary password prompt will lock ALL non vital packets and only allow the Input Packet for the prompt
- Monitor account logins from new IPs, announce/flag to staff in-game & in-discord
- Encourge users to use 2FA on their accountHonestly, what I personally would not recommend as a solution:
- Rate limit logins for all connections (In-General)
- Attempting to resolve the issue client-sided onlyExtra Bits:
- I have seen some servers use a Proxy API to detect if the sign-in IP is from a Proxy. This is great but please be careful with the way you implement this, as it may open-up yourself up to a DOS attack
- Also, I have seen some servers implement 2FA in a way which allows an attacker to Bruteforce it & even crash the server. Please add the 2FA check before the password check, and throttle this.
- Client Obfuscation does make it harder for an attacker to extract your login packets, but not impossible. I would highly recommend a mixture of obfuscation & re-locating fields/voids (for login) around your other class files to discourage an attackerIf you would like to test your current implementation, I am happy to run my headless client on your test/active server (as long as you provide your login packets, I'd rather not see another obfuscated client again).
I am not able to provide my Headless Client as this breaches Rune-Server's terms, as this could be used maliciously. However, I can provide a snippet of a login packet for an example.
Spoiler for #Login Stream/Packet Example:Code:@Override public void initSocketRequest() throws IOException { long l = Misc.longForName(getUsername()); int i = (int)(l >> 16L & 0x1FL); /** Init Login Connection **/ getStream().currentOffset = 0; getStream().writeByte(14); getStream().writeByte(i); getRSSocket().queueBytes(2, getStream().buffer); for (int j = 0; j < 8; j++) getRSSocket().read(); int responseCode = getRSSocket().read(); /** RESPONSE MUST RETURN 0 **/ if (responseCode == 0) { getRSSocket().flushInputStream(getInStream().buffer, 8); getInStream().currentOffset = 0; long aLong1215 = getInStream().readQWord(); int[] ai = new int[4]; ai[0] = (int)(Math.random() * 9.9999999E7D); ai[1] = (int)(Math.random() * 9.9999999E7D); ai[2] = (int)(aLong1215 >> 32L); ai[3] = (int)aLong1215; getStream().currentOffset = 0; getStream().writeByte(10); getStream().writeInt(ai[0]); getStream().writeInt(ai[1]); getStream().writeInt(ai[2]); getStream().writeInt(ai[3]); getStream().writeInt(350); //MAC ADDRESS 08-60-6E-7C-33-69 //This server does not check if MAC is valid, generating random numbers for faster operation Random r = new Random(); int low = 10; int high = 99; String macAddress = (r.nextInt(high-low) + low) + "-" + (r.nextInt(high-low) + low) + "-" + (r.nextInt(high-low) + low) + "-" + (r.nextInt(high-low) + low) + "-" + (r.nextInt(high-low) + low) + "-" + (r.nextInt(high-low) + low); //Another means of protection that the server is expecting to see StringBuilder sb = new StringBuilder(12); for(int len = 0; len < 12; len++) sb.append(AB.charAt(rnd.nextInt(AB.length()))); StringBuilder stringBuilder = new StringBuilder(); for(int c = 0; c < 12; c++) stringBuilder.append(r.nextInt(254) + 'a'); getStream().writeString(stringBuilder.toString()); getStream().writeString(getPassword()); getStream().writeString(macAddress); getStream().writeString(" "); getStream().writeShort(222); getStream().writeByte(0); getStream().doKeys(RSA_MODULUS, RSA_EXPONENT); getInitialStream().currentOffset = 0; getInitialStream().writeByte(isFlag() ? 18 : 16); String currentPin = ""; getInitialStream().writeByte(getStream().currentOffset + 36 + 1 + 1 + 2 + (currentPin.length()) + 1); getInitialStream().writeByte(255); getInitialStream().writeShort(14); //int attack = Store.currentKey += 1; getInitialStream().writeByte(0); //Client is using Reflection to generate identifier for current client version, based on class files & cache files //Grabbed string from client memory, converting to bytes and sending to the server byte[] bytes = "d41d8cd98f00b204e9800998ecf8427e".getBytes(); getInitialStream().writeByte(bytes.length); getInitialStream().writeBytes(bytes, bytes.length, 0); //EXTRA - This server supports 2FA and is required within login stream /*Random rnd = new Random(); int number = rnd.nextInt(999999); String twoFactorPin = String.format("%06d", number);*/ String twoFactorPin = ""; setTwoFactor(twoFactorPin); getInitialStream().writeShort(0); getInitialStream().writeString(currentPin); getInitialStream().writeString(twoFactorPin); for (int i2 = 0; i2 < 9; i2++) getInitialStream().writeInt(0); getInitialStream().writeBytes(getStream().buffer, getStream().currentOffset, 0); getStream().encryption = new ISAACRandomGen(ai); for (int j2 = 0; j2 < 4; j2++) ai[j2] = ai[j2] + 50; getRSSocket().queueBytes(getInitialStream().currentOffset, getInitialStream().buffer); setResponseCode(getRSSocket().read()); if(getResponseCode() != -1 && getResponseCode() != 30) { // TextEditor.writePasswordToFile(String.valueOf(attack), String.valueOf(attack)); } } getRSSocket().close(); }
Discussion / Suggestions:
POW - (Bruteforcing/Hacking/Bot Flooding):
[@Omar]:
- Adding POW in general will slow down the ability to bruteforce and you can increase the difficult of the work for specific accounts. >> Original reply
[@JayArrowz]:
- Where the client requires some compute resource to complete a proof sent by server. This way bot flooding/bruteforcing will cost a lot of resource to do quickly. It should stagger logins for bots quite nicely.
The server can verify this proof much easier without expending the same resources the client did. >> Original replyForum/SQL - (Bruteforcing/Hacking):
[@Corey]:
- Applying similar throttling/measures to your website too. Cloudflare Rate Limiting to certain login endpoints proven to be effected. Also, making login attempts much stricter within the Forum software it-self. >> Original replyJS5 Flooding:
[@Kris]:
- The one thing that haunts a lot of OSRS-based servers nowadays is the JS5. It is all too easy to make mistakes with it. I'll leave it up to someone else to figure out what sort of protection they could do against JS5 flooding. Note: Throwing more hardware/servers at it isn't a real solution. >> Original reply
- Limit connections per IP, limit requests per file group. Block off invalid cache indexes(as of revision 178, there's only one - index 16, previous world map). The latter is a huge worrying point - you don't really want to release your server with a cache from before that. A single group in index 16 is over ten megabytes. That is such a weak point that anyone could take the JS5 down relatively easily by targeting that specific group. All it requires is for you to send a request thats a couple bytes, and the server is forced to reply to that with the aforementioned ten megabytes. With enough proxies, you can always take it down, even if the dedi has 10gbps network.
As long as index 16 isn't in the picture anymore, everything becomes a lot more clear. The biggest group then is one of the models, with just tens of kilobytes if I recall correctly. That is significantly harder to abuse to bring the whole network down.>> Original reply [More Info]Best Practices:
[@Kris]:
- Something else people should do is worry over the logic in their login handling. Ideally you wanna discard any logins that reach the game which don't get processed in the 30 seconds time-window that the client actually awaits for(so if you do by some miracle get flooded hard enough, worst case, it'll clear out as soon as 30 seconds are up at the end of the flooding). Besides that, you want to process logins in a logical order, checking things that require little to no processing power to begin with, moving on to more and more expensive stuff further down the line. Something as complex as a hashed password comparison should be done as the very last thing, as it usually requires hundred(s) of milliseconds to process just once. >> Original reply
TLDR;
It's not possible to completely prevent these attacks but serious mitigation may prevent an attack from happening in the first place.— Props to servers that have implemented security measures to prevent this, you know who you are - you may have even seen me in your console logs
Contributions are welcome & credited, please keep to topic (Even you Tyluur)
Over the years, you may have experienced more malicious activity ramping up around the community, possibly due to servers turning into businesses and only caring about their income/growth.
I myself had created a simple Headless Client back in 2011 for educational purposes to see how many of these servers lacked just simple security measures.
Still, in 2021 majority of the servers that are online (even some of the top 10 servers on the toplists at present time of this thread) have absolutely zero security to prevent this.
I know that some of these servers are created for a quick buck, but even implementing any of the following suggestions in this thread would make your staff teams life even easier.
This thread is heavily focused on the server-side, as only making changes on the client doesn't resolve the underlining issue.
Contributed code will also be included within this thread, I do not have the time to write-up a system for every-source base but feel free to comment.
Expectations of these attacks:
- Headless Client that will include your login stream
- Residential Proxy IPs to attempt to get past any Proxy API detection
- Bypass publicly used Captcha services, by using third-party services such as 2Captcha
- Bypass client-sided security. Such as; hashing, unique packets, RSA etc
[Bot Flooding]
- Randomly named accounts that will automatically complete any tutorial and begin flooding PM/CC/IGC/Other Packets
- 1-2000 concurrent logins in attempts to either take-down the server or advertise another
[Bruteforcing / Hacking]
- Login attempts ranging from 1-2000 attempts per second (Depending on the networking within the source / current safe-guards)
- 1-100 accounts being attacked at the same time, dumped from highscores or in-game sources
- Login attempts with passwords from leaked RSPS / Gaming databases
- Bruteforce 2FA code (Once password obtained)
My current implementation of discouraging Bruteforcing attacks (Subject to change after 16/04/2021):
- Detection to see if the same IP is attempting to login to various accounts, as the same proxy may be used
- Throttling logins per IP - 5 second wait time per attempt, lockout IP for 5 minutes after multiple failed attempts
- Throttling logins per account (Except from an IP that has already successfully logged into the account previously)
- Monitoring system that will alert you if login attempts exceed 10/s (Useful for inspecting the type of attack, when it's happening)
- Warning users before they login that their password is in a compromised database, highly recommending using another password or risk being compromised
My current implementation of discouraging Bot Flooding attacks (Subject to change after 16/04/2021):
(Detection if 10+ accounts are created within a short-space, choosing to inact the following):
- Choice to stop proxies/vpn's from logging in for X minutes
- Choice to make it so only IPs that have successfully logged into the server from XX-XX-XXXX - XX-XX-XXXX date/time for X minutes
- Unique "tutorial" that is enabled when a Bot Flood attack is detected, confusing the attacker and possibly dissuading them
- Hiding the account from players (within PlayerUpdate), until tutorial is complete [Credit to @Simplex for implementing this in their server]
- Deleting /or Moving accounts to another database that have less than 3 minutes of total play-time (Check every 2-3 months)
- Do not allow saving of an account that haven't completed the "tutorial"
My current implementation of discouraging Hacking (Subject to change after 16/04/2021):
- Discourage users from setting passwords that are used in RSPS / Gaming database breaches (RSPS Breached-DB Checker)
- Encourage users to use a secondary password/pin that is not related to the existing password. Limit this to 3-5 attempts per X minutes. Ths secondary password prompt will lock ALL non vital packets and only allow the Input Packet for the prompt
- Monitor account logins from new IPs, announce/flag to staff in-game & in-discord
- Encourge users to use 2FA on their account
Honestly, what I personally would not recommend as a solution:
- Rate limit logins for all connections (In-General)
- Attempting to resolve the issue client-sided only
Extra Bits:
- I have seen some servers use a Proxy API to detect if the sign-in IP is from a Proxy. This is great but please be careful with the way you implement this, as it may open-up yourself up to a DOS attack
- Also, I have seen some servers implement 2FA in a way which allows an attacker to Bruteforce it & even crash the server. Please add the 2FA check before the password check, and throttle this.
- Client Obfuscation does make it harder for an attacker to extract your login packets, but not impossible. I would highly recommend a mixture of obfuscation & re-locating fields/voids (for login) around your other class files to discourage an attacker
If you would like to test your current implementation, I am happy to run my headless client on your test/active server (as long as you provide your login packets, I'd rather not see another obfuscated client again).
I am not able to provide my Headless Client as this breaches Rune-Server's terms, as this could be used maliciously. However, I can provide a snippet of a login packet for an example.
@Override
public void initSocketRequest() throws IOException {
long l = Misc.longForName(getUsername());
int i = (int)(l >> 16L & 0x1FL);
/** Init Login Connection **/
getStream().currentOffset = 0;
getStream().writeByte(14);
getStream().writeByte(i);
getRSSocket().queueBytes(2, getStream().buffer);
for (int j = 0; j < 8; j++)
getRSSocket().read();
int responseCode = getRSSocket().read();
/** RESPONSE MUST RETURN 0 **/
if (responseCode == 0) {
getRSSocket().flushInputStream(getInStream().buffer, 8);
getInStream().currentOffset = 0;
long aLong1215 = getInStream().readQWord();
int[] ai = new int[4];
ai[0] = (int)(Math.random() * 9.9999999E7D);
ai[1] = (int)(Math.random() * 9.9999999E7D);
ai[2] = (int)(aLong1215 >> 32L);
ai[3] = (int)aLong1215;
getStream().currentOffset = 0;
getStream().writeByte(10);
getStream().writeInt(ai[0]);
getStream().writeInt(ai[1]);
getStream().writeInt(ai[2]);
getStream().writeInt(ai[3]);
getStream().writeInt(350);
//MAC ADDRESS 08-60-6E-7C-33-69
//This server does not check if MAC is valid, generating random numbers for faster operation
Random r = new Random();
int low = 10; int high = 99;
String macAddress = (r.nextInt(high-low) + low) + "-" +
(r.nextInt(high-low) + low) + "-" +
(r.nextInt(high-low) + low) + "-" +
(r.nextInt(high-low) + low) + "-" +
(r.nextInt(high-low) + low) + "-" +
(r.nextInt(high-low) + low);
//Another means of protection that the server is expecting to see
StringBuilder sb = new StringBuilder(12);
for(int len = 0; len < 12; len++)
sb.append(AB.charAt(rnd.nextInt(AB.length())));
StringBuilder stringBuilder = new StringBuilder();
for(int c = 0; c < 12; c++)
stringBuilder.append(r.nextInt(254) + 'a');
getStream().writeString(stringBuilder.toString());
getStream().writeString(getPassword());
getStream().writeString(macAddress);
getStream().writeString(" ");
getStream().writeShort(222);
getStream().writeByte(0);
getStream().doKeys(RSA_MODULUS, RSA_EXPONENT);
getInitialStream().currentOffset = 0;
getInitialStream().writeByte(isFlag() ? 18 : 16);
String currentPin = "";
getInitialStream().writeByte(getStream().currentOffset + 36 + 1 + 1 + 2 + (currentPin.length()) + 1);
getInitialStream().writeByte(255);
getInitialStream().writeShort(14);
//int attack = Store.currentKey += 1;
getInitialStream().writeByte(0);
//Client is using Reflection to generate identifier for current client version, based on class files & cache files
//Grabbed string from client memory, converting to bytes and sending to the server
byte[] bytes = "d41d8cd98f00b204e9800998ecf8427e".getBytes();
getInitialStream().writeByte(bytes.length);
getInitialStream().writeBytes(bytes, bytes.length, 0);
//EXTRA - This server supports 2FA and is required within login stream
/*Random rnd = new Random();
int number = rnd.nextInt(999999);
String twoFactorPin = String.format("%06d", number);*/
String twoFactorPin = "";
setTwoFactor(twoFactorPin);
getInitialStream().writeShort(0);
getInitialStream().writeString(currentPin);
getInitialStream().writeString(twoFactorPin);
for (int i2 = 0; i2 < 9; i2++)
getInitialStream().writeInt(0);
getInitialStream().writeBytes(getStream().buffer, getStream().currentOffset, 0);
getStream().encryption = new ISAACRandomGen(ai);
for (int j2 = 0; j2 < 4; j2++)
ai[j2] = ai[j2] + 50;
getRSSocket().queueBytes(getInitialStream().currentOffset, getInitialStream().buffer);
setResponseCode(getRSSocket().read());
if(getResponseCode() != -1 && getResponseCode() != 30) {
// TextEditor.writePasswordToFile(String.valueOf(attack), String.valueOf(attack));
}
}
getRSSocket().close();
}
Discussion / Suggestions:
POW - (Bruteforcing/Hacking/Bot Flooding):
[@Omar]:
- Adding POW in general will slow down the ability to bruteforce and you can increase the difficult of the work for specific accounts. >> Original reply
[@JayArrowz]:
- Where the client requires some compute resource to complete a proof sent by server. This way bot flooding/bruteforcing will cost a lot of resource to do quickly. It should stagger logins for bots quite nicely.
The server can verify this proof much easier without expending the same resources the client did. >> Original reply
Forum/SQL - (Bruteforcing/Hacking):
[@Corey]:
- Applying similar throttling/measures to your website too. Cloudflare Rate Limiting to certain login endpoints proven to be effected. Also, making login attempts much stricter within the Forum software it-self. >> Original reply
JS5 Flooding:
[@Kris]:
- The one thing that haunts a lot of OSRS-based servers nowadays is the JS5. It is all too easy to make mistakes with it. I'll leave it up to someone else to figure out what sort of protection they could do against JS5 flooding. Note: Throwing more hardware/servers at it isn't a real solution. >> Original reply
- Limit connections per IP, limit requests per file group. Block off invalid cache indexes(as of revision 178, there's only one - index 16, previous world map). The latter is a huge worrying point - you don't really want to release your server with a cache from before that. A single group in index 16 is over ten megabytes. That is such a weak point that anyone could take the JS5 down relatively easily by targeting that specific group. All it requires is for you to send a request thats a couple bytes, and the server is forced to reply to that with the aforementioned ten megabytes. With enough proxies, you can always take it down, even if the dedi has 10gbps network.
As long as index 16 isn't in the picture anymore, everything becomes a lot more clear. The biggest group then is one of the models, with just tens of kilobytes if I recall correctly. That is significantly harder to abuse to bring the whole network down.>> Original reply [More Info]
Best Practices:
[@Kris]:
- Something else people should do is worry over the logic in their login handling. Ideally you wanna discard any logins that reach the game which don't get processed in the 30 seconds time-window that the client actually awaits for(so if you do by some miracle get flooded hard enough, worst case, it'll clear out as soon as 30 seconds are up at the end of the flooding). Besides that, you want to process logins in a logical order, checking things that require little to no processing power to begin with, moving on to more and more expensive stuff further down the line. Something as complex as a hashed password comparison should be done as the very last thing, as it usually requires hundred(s) of milliseconds to process just once. >> Original reply
TLDR;
It's not possible to completely prevent these attacks but serious mitigation may prevent an attack from happening in the first place.
— Props to servers that have implemented security measures to prevent this, you know who you are - you may have even seen me in your console logs