Saturday 30 April 2011

A Secure PPP Dial-In Centos machine with SELinux

I did this a while ago but I thought this was worth sharing. I wanted a backup connection to the office when all other links are down. So I created a dial-up PPP modem machine. I was however concerned about security. After all to make this machine useful it will need to be on the internal network, but also have a modem connected to a process running as root (a getty and then a pppd). Finding a flaw in these could allow you to obtain a root shell on this internal machine. Obviously not very likely if the machine is up to date, but lets try and help with this. So I decided to look at SELinux to help with my concern.

There are two getty's shipped with RHEL/Centos , the mingetty and mgetty. The "mgetty" is more suitable for use with modems, whereas the "mingetty" is better for the virtual consoles.

Sadly for me in the standard targeted SELinux policy the mgetty and mingetty share a policy, which is fine for most applications. But I'd like mingetty to work normally (with logins on virtual consoles etc) and mgetty to be more limited. All I need my mgetty to do is spawn a pppd, and this will handle my authentication and then talk PPP for me, but not be able to spawn a login or shell.

I started with a machine with a very cut down Centos 5 install. I installed mgetty and selinux-policy-devel. I also installed  selinux-policy srpm so I could take getty.fc, getty.if and getty.te from here.
I put these files in a directory /root/mgettyse (not sure if this is the best practice way of doing this) and renamed them with a prefix of lcl. I edited these until I had removed everything not needed for my application (launching pppd), I hope. I also changed all the policy names in the files to lclgetty so as not to collide with the existing policy. I have put these files at the end.

Before anything else I'll set up my dial up config. I add to /etc/initab:


# Run mgetty for the modem
7:2345:respawn:/sbin/mgetty /dev/ttyS0

Now edit the config files under /etc/mgetty+sendfax. My dialin.config file is fully commented out. My login.config has only two lines uncommented:

/AutoPPP/ -   a_ppp /usr/sbin/pppd  
* - - /bin/false @

Basically here I want the mgetty to autospawn pppd if it hears a ppp on the other end it. If not it will spawn /bin/false and hang up (well it probably won't even do that with my SELinux policy as it won't spawn false either, but mgetty will get bored and hang up eventually). My mgetty.config has in it the only uncommented lines:

debug 4
fax-id 49 115 xxxxxxxx
speed 115200
port ttyS0
speed 115200
init-chat "" "\d\d\d+++\d\d\dATH" OK "AT&F" OK "ATM0" OK "AT&B1" OK 
data-only yes
login-prompt
issue-file /etc/issue.ppp

These will all likely depend on your modem esp the init-chat (basically I force a hang up (after an escape sequence), a reset to factory defaults, speaker off and AT&B1 tells the modem not to match port speed to connect speed). My issue.ppp basically has a message you want to appear if someone connects in a text session.

I also have my "emerconnect" user in /etc/ppp/chap-secrets:



# Secrets for authentication using CHAP
# client        server  secret                  IP addresses
#
emerconnect     *       "12345678"      10.1.32.20

(obviously you want to change this passord)

Then a few pppd options in  /etc/ppp/options:
-detach
lock
auth
crtscts
chap-max-challenge 5
ms-dns  10.1.10.10
ms-dns  10.1.10.11
asyncmap 0
idle 1800
10.1.32.1:
require-mschap-v2
debug 

Obviously change for your requirements (for example your internal DNS servers).

You need to turn on IP forwarding to allow this to route:
sysctl -w net.ipv4.ip_forward=1

(and probably easiest to add this to the bottom of /etc/rc.local so it's persistent across reboots, I didn't add to /etc/sysctl.conf as I only wanted to turn this on after everything else was up, not sure if this is justified or not paranoia).


I then make the SELinux policy:

# cd /root/mgetty

# make -f /usr/share/selinux/devel/Makefile                                                                                     
Compiling targeted lclgetty module
/usr/bin/checkmodule:  loading policy configuration from tmp/lclgetty.tmp
/usr/bin/checkmodule:  policy configuration loaded
/usr/bin/checkmodule:  writing binary representation (version 6) to tmp/lclgetty.mod
Creating targeted lclgetty.pp policy package
rm tmp/lclgetty.mod.fc tmp/lclgetty.mod


If is all builds install the module:

/usr/sbin/semodule -i lclgetty.pp

(you will probably get some warnings about colliding with exiting policies i.e. the built in getty policy, but it seems happy enough with itself). But if happy you can set the file SELinux contexts:

/sbin/restorecon -rvrvvF /etc/mgetty+sendfax /etc/issue.ppp /sbin/mgetty /var/log/mgetty.log.*

And verify that they set with ls -alZ e.g


-bash-3.2$ ls -alZ /sbin/mgetty 
-rwx------  root root system_u:object_r:lclgetty_exec_t /sbin/mgetty


(but look at the other files too)

If all happy run "init q" to reload the initab file. Then check that mgetty is running in the new context:

-bash-3.2$ ps -efZ | grep -i mgetty                                                                                                                   
system_u:system_r:lclgetty_t    root      4903     1  0 Apr15 ?        00:00:00 /sbin/mgetty /dev/ttyS0


(so now running in the lclgetty context).

Obviously test the normal operation of this by connecting from another machine as a PPP session (say with kppp) to this machine. Then ensure that everything works and you can communicate with your network). But to test SELinux security try replacing the /bin/false line in login.config with:


* - - /bin/login @


With SELinux off, by using "setenforce 0", try dialling in using minicom from another machine. This should  bring up a login prompt (it will fail the autoppp test). With SELinux on by using "setenforce 1" it shouldn't. You may want to try more elaborate tests say trying to launch a shell (/bin/bash) directly (failing with SELinux on), but you'll probably want to ensure a safe testing environment (no open phone line).

After testing return the /bin/login back to /bin/false in login.config and ensure SELinux is enforcing (hopefully 2 layers of protection). 

Hopefully everything here is correct I'm working from memory to some extent. You should hopefully now have a secure PPP dial-in server. Below are the SELinux policy files mentioned above.

::::::::::::::
lclgetty.fc
::::::::::::::
/etc/mgetty+sendfax/(/.*)? gen_context(system_u:object_r:lclgetty_etc_t,s0)
/etc/issue.ppp -- gen_context(system_u:object_r:lclgetty_etc_t,s0)
/sbin/mgetty -- gen_context(system_u:object_r:lclgetty_exec_t,s0)
/var/log/mgetty.log.ttyS0 -- gen_context(system_u:object_r:lclgetty_log_t,s0)
/var/log/mgetty.log.ttyS0.1 -- gen_context(system_u:object_r:lclgetty_log_t,s0)
/var/log/mgetty.log.ttyS0.2 -- gen_context(system_u:object_r:lclgetty_log_t,s0)
/var/log/mgetty.log.ttyS0.3 -- gen_context(system_u:object_r:lclgetty_log_t,s0)
/var/log/mgetty.log.ttyS0.4 -- gen_context(system_u:object_r:lclgetty_log_t,s0)
/var/log/mgetty.log.ttyS0.5 -- gen_context(system_u:object_r:lclgetty_log_t,s0)
/var/log/mgetty.log.ttyS0.6 -- gen_context(system_u:object_r:lclgetty_log_t,s0)
/var/log/mgetty.log.ttyS0.7 -- gen_context(system_u:object_r:lclgetty_log_t,s0)
/var/run/mgetty.pid.ttyS0 -- gen_context(system_u:object_r:lclgetty_var_run_t,s0)
::::::::::::::
lclgetty.if
::::::::::::::
## <summary>Policy for getty.</summary>

########################################
## <summary>
## Execute gettys in the getty domain.
## </summary>
## <param name="domain">
## <summary>
## Domain allowed access.
## </summary>
## </param>
#
interface(`lclgetty_domtrans',`
gen_require(`
type lclgetty_t, lclgetty_exec_t;
')

corecmd_search_sbin($1)
domain_auto_trans($1,lclgetty_exec_t,lclgetty_t)

allow $1 lclgetty_t:fd use;
allow lclgetty_t $1:fd use;
allow lclgetty_t $1:fifo_file rw_file_perms;
allow getty_t $1:process sigchld;
')

########################################
## <summary>
## Inherit and use getty file descriptors.
## </summary>
## <param name="domain">
## <summary>
## Domain allowed access.
## </summary>
## </param>
#
interface(`lclgetty_use_fds',`
gen_require(`
type lclgetty_t;
')

allow $1 lclgetty_t:fd use;
')

########################################
## <summary>
## Allow process to read getty log file.
## </summary>
## <param name="domain">
## <summary>
## Domain allowed access.
## </summary>
## </param>
## <rolecap/>
#
interface(`lclgetty_read_log',`
gen_require(`
type lclgetty_log_t;
')

logging_search_logs($1)
allow $1 lclgetty_log_t:file { getattr read };
')

########################################
## <summary>
## Allow process to read getty config file.
## </summary>
## <param name="domain">
## <summary>
## Domain allowed access.
## </summary>
## </param>
## <rolecap/>
#
interface(`lclgetty_read_config',`
gen_require(`
type lclgetty_etc_t;
')

files_search_etc($1)
allow $1 lclgetty_etc_t:file { getattr read };
')

########################################
## <summary>
## Allow process to edit getty config file.
## </summary>
## <param name="domain">
## <summary>
## Domain allowed access.
## </summary>
## </param>
## <rolecap/>
#
interface(`lclgetty_rw_config',`
gen_require(`
type lclgetty_etc_t;
')

files_search_etc($1)
allow $1 lclgetty_etc_t:file rw_file_perms;
')
::::::::::::::
lclgetty.te
::::::::::::::

policy_module(lclgetty,1.2.0)

########################################
#
# Declarations
#

type lclgetty_t;
type lclgetty_exec_t;
init_domain(lclgetty_t,lclgetty_exec_t)
domain_interactive_fd(lclgetty_t)

type lclgetty_etc_t;
typealias lclgetty_etc_t alias etc_lclgetty_t;
files_config_file(lclgetty_etc_t)

type lclgetty_lock_t;
files_lock_file(lclgetty_lock_t)

type lclgetty_log_t;
logging_log_file(lclgetty_log_t)

type lclgetty_tmp_t;
files_tmp_file(lclgetty_tmp_t)

type lclgetty_var_run_t;
files_pid_file(lclgetty_var_run_t)

########################################
#
# Getty local policy
#

# Use capabilities.
#allow lclgetty_t self:capability { dac_override sys_resource sys_tty_config fowner fsetid };
dontaudit lclgetty_t self:capability chown;
#dontaudit lclgetty_t self:capability sys_tty_config;
#allow lclgetty_t self:process { getpgid getsession signal_perms };
#allow lclgetty_t self:capability { sys_tty_config };

allow lclgetty_t lclgetty_etc_t:dir r_dir_perms;
allow lclgetty_t lclgetty_etc_t:file r_file_perms;
allow lclgetty_t lclgetty_etc_t:lnk_file { getattr read };
files_etc_filetrans(lclgetty_t,lclgetty_etc_t,{ file dir })

allow lclgetty_t lclgetty_lock_t:file create_file_perms;
files_lock_filetrans(lclgetty_t,lclgetty_lock_t,file)

allow lclgetty_t lclgetty_log_t:file create_file_perms;
logging_log_filetrans(lclgetty_t,lclgetty_log_t,file)

#allow lclgetty_t lclgetty_tmp_t:file create_file_perms;
#allow lclgetty_t lclgetty_tmp_t:dir create_dir_perms;
#files_tmp_filetrans(lclgetty_t,lclgetty_tmp_t,{ file dir })

allow lclgetty_t lclgetty_var_run_t:file create_file_perms;
allow lclgetty_t lclgetty_var_run_t:dir rw_dir_perms;
files_pid_filetrans(lclgetty_t,lclgetty_var_run_t,file)

#kernel_list_proc(lclgetty_t)
#kernel_read_proc_symlinks(lclgetty_t)

#dev_read_sysfs(lclgetty_t)

#fs_search_auto_mountpoints(lclgetty_t)
# for error condition handling
#fs_getattr_xattr_fs(getty_t)

#mcs_process_set_categories(getty_t)

#mls_file_read_up(getty_t)
#mls_file_write_down(getty_t)

# Chown, chmod, read and write ttys.
term_use_all_user_ttys(lclgetty_t)
#term_use_unallocated_ttys(getty_t)
term_setattr_all_user_ttys(lclgetty_t)
#term_setattr_unallocated_ttys(getty_t)
#term_setattr_console(getty_t)
#term_dontaudit_use_console(getty_t)

auth_rw_login_records(lclgetty_t)

#corecmd_search_bin(lclgetty_t)
#corecmd_search_sbin(lclgetty_t)

#files_rw_generic_pids(lclgetty_t)
###
files_read_etc_runtime_files(lclgetty_t)
files_read_etc_files(lclgetty_t)
#files_search_spool(getty_t)

init_rw_utmp(lclgetty_t)
#init_use_script_ptys(getty_t)
#init_dontaudit_use_script_ptys(getty_t)

libs_use_ld_so(lclgetty_t)
libs_use_shared_libs(lclgetty_t)

###locallogin_domtrans(getty_t)

logging_send_syslog_msg(lclgetty_t)

miscfiles_read_localization(lclgetty_t)

###ifdef(`targeted_policy',`
### term_dontaudit_use_unallocated_ttys(getty_t)
### term_dontaudit_use_generic_ptys(getty_t)
###')

###optional_policy(`
### mta_send_mail(getty_t)
###')

# Keeps nscd quiet in logs but not running so no problem
optional_policy(`
nscd_socket_use(lclgetty_t)
')

optional_policy(`
ppp_domtrans(lclgetty_t)
')

###optional_policy(`
### udev_read_db(lclgetty_t)
###')



No comments:

Post a Comment