sploit for arena-battle
This commit is contained in:
@@ -21,6 +21,33 @@
|
||||
#include <signal.h>
|
||||
#include <arpa/inet.h>
|
||||
#include <sys/time.h>
|
||||
#include <cstdint>
|
||||
#include <random>
|
||||
|
||||
// Auth tokens must be unguessable: NOTE_GET authenticates on the token alone.
|
||||
// Keeps the original "token_<id>_<digits>" shape so clients see no change.
|
||||
std::ifstream urandom_source("/dev/urandom", std::ios::binary);
|
||||
std::mutex urandom_mutex;
|
||||
|
||||
std::string gen_auth_token(int id) {
|
||||
uint64_t v = 0;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(urandom_mutex);
|
||||
if (urandom_source.is_open()) {
|
||||
urandom_source.read(reinterpret_cast<char*>(&v), sizeof(v));
|
||||
if (!urandom_source) {
|
||||
urandom_source.clear();
|
||||
v = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (v == 0) {
|
||||
std::random_device rd;
|
||||
v = (static_cast<uint64_t>(rd()) << 32) ^ static_cast<uint64_t>(rd());
|
||||
}
|
||||
v |= (1ULL << 63);
|
||||
return "token_" + std::to_string(id) + "_" + std::to_string(v);
|
||||
}
|
||||
|
||||
struct Item {
|
||||
int id;
|
||||
@@ -151,7 +178,7 @@ int create_fighter(const std::string& name, const std::string& fighter_class) {
|
||||
f.last_activity = now();
|
||||
f.active = true;
|
||||
|
||||
f.auth_token = "token_" + std::to_string(f.id) + "_" + std::to_string(rand());
|
||||
f.auth_token = gen_auth_token(f.id);
|
||||
|
||||
if (fighter_class == "warrior") {
|
||||
f.strength = 15;
|
||||
@@ -227,20 +254,18 @@ std::string cmd_buy(int client_fd, const std::vector<std::string>& args) {
|
||||
Fighter& f = fighters[fid];
|
||||
Item& item = items[item_id];
|
||||
|
||||
int total_price = item.price * quantity;
|
||||
int64_t total_price = static_cast<int64_t>(item.price) * static_cast<int64_t>(quantity);
|
||||
|
||||
if (total_price < 0) {
|
||||
f.gold += abs(total_price);
|
||||
f.last_activity = now();
|
||||
return "OK: Overflow exploited! Gold:" + std::to_string(f.gold);
|
||||
if (total_price > 1000000000LL) {
|
||||
return "ERROR: Invalid quantity";
|
||||
}
|
||||
|
||||
|
||||
if (f.gold < total_price) {
|
||||
return "ERROR: Not enough gold. Need:" + std::to_string(total_price) +
|
||||
" Have:" + std::to_string(f.gold);
|
||||
}
|
||||
|
||||
f.gold -= total_price;
|
||||
f.gold -= static_cast<int>(total_price);
|
||||
for (int i = 0; i < quantity; i++) {
|
||||
f.inventory.push_back(item_id);
|
||||
}
|
||||
@@ -389,6 +414,7 @@ std::string cmd_attack(int client_fd, const std::vector<std::string>&) {
|
||||
|
||||
Fighter& attacker = fighters[fid];
|
||||
int defender_id = (b.fighter1_id == fid) ? b.fighter2_id : b.fighter1_id;
|
||||
if (!fighters.count(defender_id)) return "ERROR: Opponent not found";
|
||||
Fighter& defender = fighters[defender_id];
|
||||
|
||||
// Calculate damage
|
||||
@@ -487,6 +513,7 @@ std::string cmd_status(int client_fd) {
|
||||
Battle& b = battles[bid];
|
||||
Fighter& f = fighters[fid];
|
||||
int opponent_id = (b.fighter1_id == fid) ? b.fighter2_id : b.fighter1_id;
|
||||
if (!fighters.count(opponent_id)) return "ERROR: Opponent not found";
|
||||
Fighter& opponent = fighters[opponent_id];
|
||||
|
||||
std::stringstream ss;
|
||||
@@ -561,8 +588,11 @@ std::string cmd_note_get(const std::vector<std::string>& args) {
|
||||
return "ERROR: Note not found";
|
||||
}
|
||||
|
||||
static const int MAX_BAD_TOKENS = 16;
|
||||
|
||||
void handle_client(int client_fd) {
|
||||
char buffer[1024];
|
||||
int bad_tokens = 0;
|
||||
|
||||
struct sockaddr_in client_addr;
|
||||
socklen_t client_len = sizeof(client_addr);
|
||||
@@ -593,6 +623,9 @@ void handle_client(int client_fd) {
|
||||
|
||||
std::string response;
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(state_mutex);
|
||||
|
||||
if (cmd == "REGISTER") {
|
||||
response = cmd_register(client_fd, args);
|
||||
} else if (cmd == "FIGHTER") {
|
||||
@@ -615,20 +648,37 @@ void handle_client(int client_fd) {
|
||||
response = cmd_note_create(client_fd, args);
|
||||
} else if (cmd == "NOTE_GET") {
|
||||
response = cmd_note_get(args);
|
||||
if (response.rfind("ERROR: Invalid auth token", 0) == 0) {
|
||||
bad_tokens++;
|
||||
}
|
||||
} else if (cmd == "LOGOUT") {
|
||||
response = cmd_logout(client_fd);
|
||||
} else if (cmd == "QUIT" || cmd == "EXIT") {
|
||||
response = "BYE";
|
||||
write(client_fd, response.c_str(), response.length());
|
||||
break;
|
||||
} else {
|
||||
response = "ERROR: Unknown command: " + cmd;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// QUIT/EXIT keeps its original reply: "BYE" with no trailing newline.
|
||||
if (cmd == "QUIT" || cmd == "EXIT") {
|
||||
write(client_fd, response.c_str(), response.length());
|
||||
break;
|
||||
}
|
||||
|
||||
response += "\n";
|
||||
write(client_fd, response.c_str(), response.length());
|
||||
|
||||
// Throttle token guessing: a legitimate client never needs this many.
|
||||
if (bad_tokens >= MAX_BAD_TOKENS) {
|
||||
LOG(client_str + " disconnected: too many invalid auth tokens");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
std::lock_guard<std::mutex> lock(state_mutex);
|
||||
|
||||
// Cleanup
|
||||
if (client_fighter.count(client_fd)) {
|
||||
int fid = client_fighter[client_fd];
|
||||
@@ -667,7 +717,8 @@ void cleanup_thread() {
|
||||
|
||||
for (auto cf = client_fighter.begin(); cf != client_fighter.end(); ) {
|
||||
if (cf->second == fid) {
|
||||
close(cf->first);
|
||||
// Do not close(): the handler thread still owns this fd
|
||||
// and the number would be recycled onto another client.
|
||||
cf = client_fighter.erase(cf);
|
||||
} else {
|
||||
++cf;
|
||||
@@ -733,7 +784,8 @@ int main(int argc, char* argv[]) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (listen(server_fd, 10) < 0) {
|
||||
// Backlog of 10 let a burst of connections lock the checker out.
|
||||
if (listen(server_fd, 256) < 0) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user