You've already forked Arcturus-Morningstar-Extended
mirror of
https://github.com/duckietm/Arcturus-Morningstar-Extended.git
synced 2026-06-19 15:06:19 +00:00
feat: LIKE-wildcard escaping (security) + recycle/craft reward rollback (stability)
Security / speed: - New util SqlLikeEscaper: escapes %, _ and \ in user search input. Applied to the user-facing LIKE searches (messenger user search, marketplace search, furni-editor search, housekeeping room search, guild member search) so a query like "%" can no longer match everything or trigger a needless full scan, and usernames containing "_" are matched literally. Stability (item-loss fixes): - RecycleEvent: compute the recycler reward BEFORE consuming the 8 inputs. The inputs were deleted from the DB first, so a null reward (misconfig) destroyed them permanently with nothing back. Now the inputs are only removed once the reward is confirmed. - CraftingCraftItemEvent: restore the pulled ingredients to the inventory if the recipe can't be completed (not enough ingredients mid-pull, or reward creation returns null) — previously they silently vanished from the inventory.
This commit is contained in:
@@ -171,8 +171,9 @@ public class MarketPlace {
|
||||
statement.setInt(paramIndex++, maxPrice);
|
||||
}
|
||||
if (!search.isEmpty()) {
|
||||
statement.setString(paramIndex++, "%" + search + "%");
|
||||
statement.setString(paramIndex++, "%" + search + "%");
|
||||
String likeSearch = "%" + com.eu.habbo.util.SqlLikeEscaper.escape(search) + "%";
|
||||
statement.setString(paramIndex++, likeSearch);
|
||||
statement.setString(paramIndex++, likeSearch);
|
||||
}
|
||||
|
||||
try (ResultSet set = statement.executeQuery()) {
|
||||
|
||||
@@ -421,7 +421,7 @@ public class GuildManager {
|
||||
|
||||
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection(); PreparedStatement statement = connection.prepareStatement("SELECT users.username, users.look, guilds_members.* FROM guilds_members INNER JOIN users ON guilds_members.user_id = users.id WHERE guilds_members.guild_id = ? " + (rankQuery(levelId)) + " AND users.username LIKE ? ORDER BY level_id, member_since ASC LIMIT ?, ?")) {
|
||||
statement.setInt(1, guild.getId());
|
||||
statement.setString(2, "%" + query + "%");
|
||||
statement.setString(2, "%" + com.eu.habbo.util.SqlLikeEscaper.escape(query) + "%");
|
||||
statement.setInt(3, page * 14);
|
||||
statement.setInt(4, 14);
|
||||
|
||||
|
||||
@@ -53,7 +53,7 @@ public class Messenger {
|
||||
public static THashSet<MessengerBuddy> searchUsers(String username) {
|
||||
THashSet<MessengerBuddy> users = new THashSet<>();
|
||||
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection(); PreparedStatement statement = connection.prepareStatement("SELECT * FROM users WHERE username LIKE ? ORDER BY username ASC LIMIT " + Emulator.getConfig().getInt("hotel.messenger.search.maxresults"))) {
|
||||
statement.setString(1, username + "%");
|
||||
statement.setString(1, com.eu.habbo.util.SqlLikeEscaper.escape(username) + "%");
|
||||
try (ResultSet set = statement.executeQuery()) {
|
||||
while (set.next()) {
|
||||
users.add(new MessengerBuddy(set, false));
|
||||
|
||||
+10
-7
@@ -40,23 +40,26 @@ public class RecycleEvent extends MessageHandler {
|
||||
}
|
||||
}
|
||||
|
||||
if (items.size() == count) {
|
||||
for (HabboItem item : items) {
|
||||
this.client.getHabbo().getInventory().getItemsComponent().removeHabboItem(item);
|
||||
this.client.sendResponse(new RemoveHabboItemComposer(item.getGiftAdjustedId()));
|
||||
Emulator.getThreading().run(new QueryDeleteHabboItem(item.getId()));
|
||||
}
|
||||
} else {
|
||||
if (items.size() != count) {
|
||||
this.client.sendResponse(new AlertPurchaseFailedComposer(AlertPurchaseFailedComposer.SERVER_ERROR));
|
||||
return;
|
||||
}
|
||||
|
||||
// Compute the reward BEFORE consuming the inputs. Previously the
|
||||
// inputs were deleted first, so a null reward (misconfiguration)
|
||||
// permanently destroyed the 8 furni with nothing in return.
|
||||
HabboItem reward = Emulator.getGameEnvironment().getItemManager().handleRecycle(this.client.getHabbo(), Emulator.getGameEnvironment().getCatalogManager().getRandomRecyclerPrize().getId() + "");
|
||||
if (reward == null) {
|
||||
this.client.sendResponse(new AlertPurchaseFailedComposer(AlertPurchaseFailedComposer.SERVER_ERROR));
|
||||
return;
|
||||
}
|
||||
|
||||
for (HabboItem item : items) {
|
||||
this.client.getHabbo().getInventory().getItemsComponent().removeHabboItem(item);
|
||||
this.client.sendResponse(new RemoveHabboItemComposer(item.getGiftAdjustedId()));
|
||||
Emulator.getThreading().run(new QueryDeleteHabboItem(item.getId()));
|
||||
}
|
||||
|
||||
this.client.sendResponse(new AddHabboItemComposer(reward));
|
||||
this.client.getHabbo().getInventory().getItemsComponent().addItem(reward);
|
||||
this.client.sendResponse(new RecyclerCompleteComposer(RecyclerCompleteComposer.RECYCLING_COMPLETE));
|
||||
|
||||
+17
@@ -37,6 +37,8 @@ public class CraftingCraftItemEvent extends MessageHandler {
|
||||
HabboItem habboItem = this.client.getHabbo().getInventory().getItemsComponent().getAndRemoveHabboItem(set.getKey());
|
||||
|
||||
if (habboItem == null) {
|
||||
// Not enough ingredients — give back whatever we already pulled.
|
||||
this.restoreItems(toRemove);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -70,8 +72,23 @@ public class CraftingCraftItemEvent extends MessageHandler {
|
||||
return;
|
||||
}
|
||||
|
||||
// Reward creation failed after we already pulled the ingredients —
|
||||
// restore them so the craft isn't a silent item sink.
|
||||
this.restoreItems(toRemove);
|
||||
}
|
||||
|
||||
this.client.sendResponse(new CraftingResultComposer(null));
|
||||
}
|
||||
|
||||
private void restoreItems(TIntObjectHashMap<HabboItem> items) {
|
||||
if (items.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
items.forEachValue(item -> {
|
||||
this.client.getHabbo().getInventory().getItemsComponent().addItem(item);
|
||||
this.client.sendResponse(new AddHabboItemComposer(item));
|
||||
return true;
|
||||
});
|
||||
this.client.sendResponse(new InventoryRefreshComposer());
|
||||
}
|
||||
}
|
||||
|
||||
+6
-4
@@ -49,15 +49,17 @@ public class FurniEditorSearchEvent extends MessageHandler {
|
||||
try {
|
||||
int numericQuery = Integer.parseInt(query);
|
||||
isNumeric = true;
|
||||
String likeQuery = "%" + com.eu.habbo.util.SqlLikeEscaper.escape(query) + "%";
|
||||
whereClause.append(" AND (id = ? OR sprite_id = ? OR item_name LIKE ? OR public_name LIKE ?)");
|
||||
params.add(numericQuery);
|
||||
params.add(numericQuery);
|
||||
params.add("%" + query + "%");
|
||||
params.add("%" + query + "%");
|
||||
params.add(likeQuery);
|
||||
params.add(likeQuery);
|
||||
} catch (NumberFormatException e) {
|
||||
String likeQuery = "%" + com.eu.habbo.util.SqlLikeEscaper.escape(query) + "%";
|
||||
whereClause.append(" AND (item_name LIKE ? OR public_name LIKE ?)");
|
||||
params.add("%" + query + "%");
|
||||
params.add("%" + query + "%");
|
||||
params.add(likeQuery);
|
||||
params.add(likeQuery);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -56,7 +56,7 @@ public class HousekeepingSearchRoomsEvent extends MessageHandler {
|
||||
|
||||
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection();
|
||||
PreparedStatement statement = connection.prepareStatement(sql)) {
|
||||
statement.setString(1, exactMatch ? query : query + "%");
|
||||
statement.setString(1, exactMatch ? query : com.eu.habbo.util.SqlLikeEscaper.escape(query) + "%");
|
||||
statement.setInt(2, limit);
|
||||
|
||||
try (ResultSet set = statement.executeQuery()) {
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
package com.eu.habbo.util;
|
||||
|
||||
/**
|
||||
* Escapes the LIKE wildcards {@code %} and {@code _} (and the escape char itself)
|
||||
* in user-supplied search input, so they are matched literally instead of acting
|
||||
* as wildcards. Prevents wildcard-driven over-broad matches and the expensive
|
||||
* full-scans an attacker could trigger with a query like {@code "%"}. Uses
|
||||
* MariaDB's default escape character {@code \}.
|
||||
*/
|
||||
public final class SqlLikeEscaper {
|
||||
|
||||
private SqlLikeEscaper() {
|
||||
}
|
||||
|
||||
public static String escape(String input) {
|
||||
if (input == null) {
|
||||
return "";
|
||||
}
|
||||
return input
|
||||
.replace("\\", "\\\\")
|
||||
.replace("%", "\\%")
|
||||
.replace("_", "\\_");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user