7. dial_ppp

To get PPP working, you have to customise your own chat script. Whilst in deep hack mode, you might like create a wrapper to call it properly. Here's mine ...

dial_ppp is a local command that calls pppd with the correct chat script, the correct options, and correct UID permission.

It allows a 'plain user' to bring a ppp link up or down without knowing the root password. It does this using a suid wrapper. If you don't like that, simply adjust it to your requirements and call it when you are root (or get sudo to do so).

Some people know about and use the /usr/sbin/ppp-on and ppp-off commands. If other apps try to use them, provide them as wrappers to dial_ppp.

Remember that it's the dial_ppp.sh script that is important, the suid wrapper and the group is just interesting. If you first test it as root, you can forget the wrapper until later (eg if you are in a hurry).

7.1 /etc/group
7.2 /etc/ppp/dial_ppp.c
7.3 chgrp dial_ppp /etc/ppp/dial_ppp
7.4 chown root /etc/ppp/dial_ppp
7.5 chmod 6750 /etc/ppp/dial_ppp
7.6 /etc/ppp/dial_ppp.sh
7.7 /etc/ppp/chat_tdc
7.8 /etc/ppp/ip-up
7.9 /etc/ppp/options
7.10 persist
7.11 Anything else ?
7.12 ANNOUCE: dial_ppp.tgz
7.13 Now What?

7.1

/etc/group

Add a new group, called dial_ppp. Members of the group are allowed to start and stop pre-configured ppp links. Add those users to the /etc/group line:

dial_ppp::19:gps,fred,kt

You don't have to, but now is a good time to add a new group, called "phone". Members are allowed to use the modem for other things. This presumes they are permitted to use it at any time of the day for sesson type calls (eg minicom) and to send faxes.

The dial_ppp and phone groups have nothing to do with each other, except they are both related to the modem. You need write permission to the device to make a fax (any user-id), you need root permission for the kernel to setup a PPP link.

/etc/ppp/dial_ppp stops non-members of the dial_ppp group group from running the binary, by having the correct permission set on the file. It has to be the group permission, because the user permission (root) is needed for the SUID. If you are not listed in the /etc/group line, you can't even start the binary, so you won't get the suid-root benefit.

The suid-bit and root ownership of the binary, is how a plain user gets 'promoted' to root, though the binary itself has to request the facility (see the code below). You can't simply set the suid-bit on any binary, unless that binary expects it.

That gives root uid and then exec's a "semi-safe" script passing argv through untouched. You do have to be careful when calling shell scripts, to prevent any security holes appearing, but if you are careful, they should be safe. The main risk, is that you are running /bin/sh, a large complex program, with possible flaws. The second risk is the contents of the script, and the general configuration.

After editing /etc/group, those users will have to logout/login, for it to take effect.

7.2

/etc/ppp/dial_ppp.c

Here is a program. Type it in. Compile it.

/* /etc/ppp/dial_ppp.c */

#include 
#include 
#include 

main( int argc, char ** argv, char ** envp )
{
	if( setgid(getegid()) ) perror( "setgid" );
	if( setuid(geteuid()) ) perror( "setuid" );
	envp = 0; /* blocks IFS attack on non-bash shells */
	execve( "/etc/ppp/dial_ppp.sh", argv, envp );
	perror( argv[0] );
	return errno;
}

You don't need the setgid() line, but as a generic, educational wrapper, there is is. If you don't run an unsafe program, you don't need to flatten envp.

How it works

If you're not in the dial_ppp group you can't run the program (see below). If you are, the kernel runs the elf binary (this also works on a.out systems).

The SUID bit on the binary file tells the kernel to provide a special permission to the running process: the euid number or effective user id (Normally euid == uid ). The kernel only does that for binaries, not for scripts!

The code can make use of the euid, by asking the kernel to set it's active uid to that value, using the setuid(2) system call.

Actually a root uid process can switch to any uid, a non-root user can only switch to the euid (or the uid!). The euid isn't used if the code doesn't use it, and the option is lost on the next exec. That prevents a hacker putting SUID option on one binary, and using it in another, If the euid is used, and converted to the uid, the child process gets the full access.

By calling the getuid(2) sys-call, the code can be reused in a different program and different file-owner, to get a different uid. The getuid call simply tells the program what uid it can request (the root uid is always zero).

Now that the uid is root (or whatever was set on the file, the above is a generic example), you can make any system calls you like, or call a program that does so. So the program exec's the shell script.

The program passes the environment (or 0 for none!) and also the argv vector of command line argument words. Here we pass on the ones we received, in a more complex program, you would filter out what you think is acceptable.

The kernel can't run a shell script directly, but the initial #!/bin/sh in /etc/ppp/dial_ppp.sh tells the kernel to run a shell to run the script. That script does what it does.

The call to the exec() system call never returns, except when the kernel can't run the program, eg bad permissions, can't find /bin/sh, etc. In which case it does return and tells us.

NOTE: the exit code from this program will be errno (if the exec failed) or it will be the exit code from the exec'd binary (which runs in the same process-id). With PPPD the exit code is rarely interesting, because it forks off it's own child process which might fail long after the parent has exited.

perror prints any error message. Note the above code "ploughs on", even after an error was detected. In this case it's fairly harmless, and you get the benefit of a later error message (pppd failed - ENOPERM).

Action - Security hole

If someone somehow hijacked my user-id, eg through a Netscape browser plugin, or simply by tempting me to download and run their code, they would be able to run dial_ppp

However, they would not be able to establish arbitrary PPP configurations, just make or break the ones that I have configured.

The list of permitted actions is in the shell script.

IFS - Security hole

Setting envp to 0 means that no environment variables are exported what so ever. This blocks the IFS attack, where the cracker defines "/" to be treated as a space changing the meaning of some well formed line. However this also drops all other environment variables. I leave the envp mechanism for you to see, particularly for non suid programs, though you should also look at the man pages for fork(2) and wait(2).

You could write a bit more code and check the array of strings held in envp, exiting if you don't like anything.

Note that with bash-1.14.5(1), and maybe others, the IFS attack is blocked: the binary does what the specifiaction should have been, and resets IFS, however older SVR4 and other unix's do the "standard" thing which is both stupid and wrong.

Setting up such an attach does require a local shell, and so is a greatly reduced risk.

PATH - Security hole

If you allow the user to provide the PATH, and your script calls an innocent command like sync, a cracker could create a sync shell script and set the PATH to point to it first. To avoid that, you can use absolute pathnames, or prefix the PATH with the system directories first.

PATH="${PATH:-/bin}"	# else null PATH adds .
PATH="/usr/bin:$PATH"	# third
PATH="/bin:$PATH"	# second
PATH="/sbin:$PATH"	# first

In particular beware of any command that you have mis-typed and says "command not found", a helpful hacker might correct that error ... Also check that commands you do run are not in a writable directory or a writable file. /usr/local/bin is often a writable directory for some users. (I used to have it group writable by myself, but that opens up problems with a hijacked session).

ARGV characters - Security hole

If you pass ARGV to programs that pass it to shell, the shell might interpret them in ways you didn't expect. For example back-quotes, run the command in them. $(cmd) is another form of backquotes!

With older shells, you should also put everything in "quotes" and if a command allows "--" to end all options, use it. Similarly, distinguish between "$@" and $* RTFM: bash(1)

If you design your application to use names which the script then goes and fetches the values for, you can simply reject any line or word that isn't a simple alphanumeric string.

The dial_ppp script doesn't have this problem, but it does show you how to use names, to select actions. Another reason for using names at the top level, is where a hacker uses excessively long parameter strings to overflow a buffer somewhere.

And then there's filenames. Firstly: do you need to allow ".." in a pathname? If not reject it. Secondly do you need absolute or full filenames (again linking back to names). The script runs as root (or as lp or ...), which is a different user than the person who invoked it. If you accept STDIN/STDOUT you at least know that the user had permission to open the file themselves, you are not granting that facility.

Not all shell script security problems involve SUID-wrappers. Plain scripts that cron regularly runs as root, or as a specific user, may also leave you exposed. "Command not found" ...

7.3

chgrp dial_ppp /etc/ppp/dial_ppp

This is where the group permission is set and used. Along with chmod, it grants users in the dial_ppp group permission to run this binary.

If you leave the binary in /etc/ppp, you may also need group access to the directory, though some (non-Linux) kernels allow access if you know the exact filename.

7.4

chown root /etc/ppp/dial_ppp

The owner of the file will be the process UID when it is run, provided the SUID bit is set.

7.5

chmod 6750 /etc/ppp/dial_ppp

It isn't 6755, because we only want members of the group to be able to run this command, and that zero stops others.

It can be 4750 or 6750, it makes no difference here.

The suid bit is fragile! If you chgrp or chown the file, you will have to chmod it again. Similarly recompiling needs a new chmod. If there is a Makefile, eg dial_ppp.mk, it should do that for you.

make -f dial_ppp.mk

You might wish to allow the command to appear in /bin, /usr/bin, or /usr/sbin so that users can run it by accident, when browsing for gif converters. If you leave it in /etc/ppp/ you will need to allow dial_ppp group members access to the directory. If you move the binary where they can reach it, you will have to remember to move it's replacement when you recompile, or create a hard link as follows:

ln /etc/ppp/dial_ppp /bin

7.6

/etc/ppp/dial_ppp.sh

This is the command that gets run. It is a suid shell script so be careful.

Edit the parts that have "tdc", that's the name of the isp. Put your details there. The version in the .tgz file actually allows you to simply create /etc/ppp/chat_NAME, and call dial_ppp NAME.

The big thing to look out for is the "$loc:$rem" parameter to pppd. If you set loc="" (ditto likewise for rem), that tells PPPD to allow the remote to choose the IP address. When dircon changed all our IP addresses, I picked up the new address automatically. Even though it didn't appear in any file on may machine. A big win!

The downside of not setting rem=addr, is that a non-trusted ISP could try to become an IP address that I place too much trust in. If you know who you are calling, setting the rem address in the script will stop them.

Not specifying an address, makes PPPD offer a suggestion to the remote, typically the IP_ADDR of the ethernet interface, or the first hostname in /etc/hosts. That (mild) suggestion is easily and quickly overridden by the ISP, who knows what IP address they want you to be, but it causes a bit more PPP dialog, and maybe an entry in their log files.

Users of dynamic IP addresses, will almost certainly want to allow the ISP to set the IP address at connetc time.

#!/bin/sh
# /etc/ppp/dial_ppp.sh
#  echo "Running: $0 $* (\$#=$#)"
loc="tdc_me"
rem=""
speed=38400
modem=/dev/modem
OPTIONS="debug lock crtscts modem defaultroute persist"
PATH="/usr/sbin:$PATH"
case "$1"
in
	help) 	echo "$0" "$@" "# see /etc/ppp/."
		exit 1

;;	id)	id

;;	cut)	killall pppd

;;	dip_tdc)
		exec /etc/ppp/dip_tdc 

;;	tdc)	
		rem="" 
		chat_script=/etc/ppp/chat_tdc
		# rem="tdc_gw" # allow remote to supply ip_addr
		
		set -x
		pppd connect "chat -v -f $chat_script" \
			"$modem" $speed $OPTIONS \
			"$loc:$rem"

;;	list)	echo "tdc # PPP link to dircon" # used by menu

;;	*)	echo "BAD USAGE: $0 $*"
		exit 1
esac

7.7

/etc/ppp/chat_tdc

ABORT BUSY
ABORT 'NO CARRIER'
ABORT 'NO DIAL TONE'
REPORT CONNECT
'' ATZ
TIMEOUT 5
OK 'AT Q0 V1 E1 X4 L1 W1 S95=47 M1 I4'
OK 'ATDT 0181 265 2211'
TIMEOUT 45
CONNECT ''
TIMEOUT 10
login: trix
Password: password
Protocol: ppp
chat does the actual modem initialisation and dialing. This script is alternating: on prompt send keystrokes along with a few ABORT and TIMEOUT rules. most people put the prompt and mkeystrokes pairs on the same line.

I wish chat had a spectial line-string character such as @ that didn't echo the password into the trace files! You may need chmod 640 /var/log/messages to keep it's contents private.

7.8

/etc/ppp/ip-up

This is another shell script that also gets run as root, as does /etc/ppp/ip-down I don't have to fetch email, but running finger @mailhost wakes the remote sendmail up, and tells me when it's finished. That's local. You may need POP3 to actively fetch your email.

#!/bin/sh
mailq
sleep 2
sendmail -q
finger @felix # seems to leave one in queue
finger @felix # you might use popclient - faster
echo "$0 $* exit $?" >> /var/log/messages

7.9

/etc/ppp/options

Mine is blank, but you can put the modem baud rate and options in there. Much tidier if you do.

Your ISP's PPP setup may be different than Dircon's ("The Direct Connection"). You might have to negociate your secret key challenge, (PPP has a choice of configurations, and security options). Your ISP should tell you what settings you need.

7.10

persist

This option tells pppd, to redial if the connecton drops.

Note that if the chat script fails (eg BUSY), it won't redial again, if that is a problem, look at diald.

7.11

Anything else ?

If you specify them as names to pppd, you must have "tdc_me" and tdc_gw in /etc/hosts. These are alias names for the IP addresses of the two ends of the ppp link.

If you don't specify them, you can't complain about what the other end thinks it's called, or request it to become what you want.

You need a menu to call your new command. I used tcl+tk. The lines you have to call are:

dial_ppp tdc	- to connect to the tdc isp
dial_ppp cut	- to cut the ppp link

I'm not sure what is the correct way to bring a pppd link down, as I only have one, so I kill all of them. There is probably a better way of recording the pid and naming it later, or recording the interface and ifconfig ppp0 down

If you flit between different ISP's, you will need to change your selected nameserver to avoid timeouts. The ppp_script would also switch over the necessary /etc/resolv.conf file.

You probably want ppp-2.2.0f or better, if you are running a 1.3 or 2.0 kernel. If you want to use dip instead (for a SLIP link), you may need to edit the scripts so that it isn't called "dial_ppp"

7.12

ANNOUCE: dial_ppp.tgz

dial_ppp is a nifty utility, I use it every day!

To make dial_ppp avaiable to everybody, without requiring them to purchase Raven Issue-3, I am created a .tgz file, and placed it on sunsite.unc.edu as Free software. system/Network/Serial

Noone has send any feedback, so I guess it needs no update. Please feel free to generalise it.

7.13

Now What?

Hopefully you now have a working TCP/IP link with the internet, you now have to make a few apps work to use it. Most distributions put a working ftp client on your disk, or you could use lynx (a text mode web browser) so try this:

lynx http://sunsite.unc.edu/pub/Linux/docs/Raven/

Or if you have mc installed, and running, try

cd ftp://sunsite.unc.edu/pub/Linux/docs