Question: Automatic launching of a CLI menu upon login (OpenBSD)

Hi all,

I am OpenBSD newbie and currently need to manage some OpenBSD firewalls running pf. The OpenBSD version is 4.8

As the other sys admins are not so familiar with OpenBSD, so I have an idea across in my mind on how to minimize the root account usage and other unnecessary access and make the configuration/change of OpenBSD firewalls easier.

Let say if the IT Admin would like to manage the firewall from either console or ssh and they don't need to su in to do some config:

OpenBSD/i386

login: user1
password: password1

after they inputted the password and click enter, there will be another menu coming out on the screen instead of normal shell prompt ($)

>>>Welcome to the OpenBSD, please choose the option to configure:
1>Configure/Change IP address and subnet mask
2>View ifconfig
3>Configure/Change default route
4>Add/Remove static route
5>View routing table
6>Add/Change Name Server IP address
7>Add/Modify pf rule
8>Check pfstatus
9>Backup OpenBSD pf config
10>Quit

I really have no idea how to do that and the users are not allowed to access ($) or (#) at all to minimize human error(eg: accidentally delete config file etc) My intention is only giving them the necessary access to do the daily job.

Have you guys ever done the task like what I would like to do?
Can you give me the direction and hints on how to do that?

Regards,
Stefan

First thing is to make yourself a test account, and change the login shell to run your script.

Hi, stefan:

You'll have to setup sudo to allow the user your script runs as to use privileged pfctl features (same for other commands to alter the system's routing table, network interfaces, dns configuration, etc), unless you intend to run a suid shell script (the mere mention of it ... *gasp*). :wink:

Regards and welcome to the forum,
Alister

I think setuid shell script is almost impossible, as the dynamic lib locations are not compiled into the shells!

That suid sh script remark was intended as a joke, particularly given the platform in question. You're correct regarding the near impossiblity, but I believe you're reason is mistaken. Most modern UNIX platforms simply do not honor the suid bit for interpreted files.

I'm not certain what your dynamic library location remark means, unless you are referring to the fact that the loader ignores LD_LIBRARY_PATH and LD_PRELOAD for suid binaries. If so, that's an unrelated security issue.

Regards,
Alister

No, that was it, when a ksh script is started without LD_LIBRARY_PATH, it dies, and the same for any other common interpreter. I guess if you did a static link or compiled in a path with -R or whatever, then it might work. You seem to think there is special code in exec() to not allow both interpreter #! files and setuid, but I think the library path was sufficient is stopping the foolish.

What value does it demand for LD_LIBRARY_PATH, and why?

Well, like all good C progs, the path to libc.so or whatever, at least. Now, it might be /lib, but setuid erases even that from LD_LIBRARY_PATH, and ld does not think it is on him to look there unasked.

On HP-UX, a set-uid ksh script gets a message, so I guess they added explicit prevention, but on Solaris I do recall such: Setuid execution not allowed

$ ldd -rsv $(whence ksh)                    
ldd -rsv $(whence ksh)
  find library=/usr/lib/libc.2; required by /usr/bin/ksh
        /usr/lib/libc.2 =>      /usr/lib/libc.2
  find library=/usr/lib/libdld.2; required by /usr/lib/libc.2
        /usr/lib/libdld.2 =>    /usr/lib/libdld.2
  find library=/usr/lib/libc.2; required by implicit load
        /usr/lib/libc.2 =>      /usr/lib/libc.2
  find library=/usr/lib/libxti.2; required by /usr/bin/ksh
        /usr/lib/libxti.2 =>    /usr/lib/libxti.2
  find library=/usr/lib/libnsl.1; required by /usr/bin/ksh
        /usr/lib/libnsl.1 =>    /usr/lib/libnsl.1
  find library=/usr/lib/libxti.2; required by /usr/lib/libnsl.1
    search path=/usr/lib:  (RPATH)
    trying path=/usr/lib/libxti.2
        /usr/lib/libxti.2 =>    /usr/lib/libxti.2
$

On the platforms I use (*BSD, Linux, OSX), LD_LIBRARY_PATH is usually unset and the interpreters work fine. In my experience, LD_LIBRARY_PATH is usually used as a hack in environment-modifying shell script wrappers that launch oddball binaries which need help locating their libraries. Typically, it's not needed as shared libraries are in a system defined location which is searched by the loader regardless of the value of LD_LIBRARY_PATH or whether an executable is SUID.

There is, at least on some systems. For example, testing on Linux (an approximately 5 yr old Debian install running kernel version 2.6.18) shows that the SUID bit of an interpreted file has absolutely no effect. The only way to launch the interpreter under a different effective user id is to set the SUID bit on the interpreter itself.

Let's assume we've just called exec() on a SUID interpreted file.

The exec system call calls prepare_binprm(). prepare_binrpm() always sets the new process' effective [ug]id to the current [ug]id before checking the permissions of the script's inode. If the inode has the SUID bit set, then it modifies the euid for the new process to match the inode's uid.

So far, Linux's exec() conforms to historical behavior: the effective uid is set to the inode owner's uid. But, there's more to come. At this point the kernel does not know that it's dealing with an interpreted file. What has happened so far happens for all exec()s.

Later in the exec syscall, search_binary_handler() is called to walk a list of supported binary formats which point to their respective loaders. In the case of a text-file with a leading she-bang, it's load_script().

Thus begins a recursive loop. load_script() processes the she-bang, determines the interpreter to use, then replays the sequence above, calling prepare_binrpm() and then search_binary_handler(), but now the inode whose permissions are scrutinized is that of the interpreter not the script.

Each time it's called, prepare_binrpm() will clobber the new process' egid and euid (possibly previously modified by the script's SUID bit), resetting them to the current e[ug]id, before examining the interpreter's inode's SUID bit.

In the end, the SUID bit in the inode of the final file in the chain determines the euid of the exec'd process. The SUID bit of the originating script file is irrelevant.

exec.c/prepare_binrpm():

1205 /*
1206  * Fill the binprm structure from the inode.
1207  * Check permissions, then read the first 128 (BINPRM_BUF_SIZE) bytes
1208  *
1209  * This may be called multiple times for binary chains (scripts for example).
1210  */
1211 int prepare_binprm(struct linux_binprm *bprm)
1212 {

...<snip>...

1221         /* clear any previous set[ug]id data from a previous binary */
1222         bprm->cred->euid = current_euid();
1223         bprm->cred->egid = current_egid();
1224
1225         if (!(bprm->file->f_path.mnt->mnt_flags & MNT_NOSUID)) {
1226                 /* Set-uid? */
1227                 if (mode & S_ISUID) {
1228                         bprm->per_clear |= PER_CLEAR_ON_SETID;
1229                         bprm->cred->euid = inode->i_uid;
1230                 }

exec.c/sys_execve():

1379 /*
1380  * sys_execve() executes a new program.
1381  */

...<snip>...

1435         retval = prepare_binprm(bprm);

...<snip>...

1452         retval = search_binary_handler(bprm,regs);
1453         if (retval < 0)
1454                 goto out;
1455
1456         /* execve succeeded */

binfmt_script.c/load_script():

17 static int load_script(struct linux_binprm *bprm,struct pt_regs *regs)
18 {

...<snip>...

87         /*
88          * OK, now restart the process with the interpreter's dentry.
89          */
90         file = open_exec(interp);
91         if (IS_ERR(file))
92                 return PTR_ERR(file);
93
94         bprm->file = file;
95         retval = prepare_binprm(bprm);
96         if (retval < 0)
97                 return retval;
98         return search_binary_handler(bprm,regs);
99 }

git.kernel.org - linux/kernel/git/torvalds/linux-2.6.git/blob - fs/exec.c
git.kernel.org - linux/kernel/git/torvalds/linux-2.6.git/blob - fs/binfmt_script.c

An older OSX Tiger (10.4.11) laptop appears to be even more restrictive: If the first file in the chain is a SUID interpreted file, the euid is never changed (not even when the interpreter and the interpreted file are both SUID).

Back to where this thread started, an old OpenBSD 4.4 system honors the SUID bit of interpreted files (modifying the euid as per historical practice, as per the interpreted file's inode permissions, regardless of the interpreter's permissions).

I really should update my disused machines to more current versions of their respective operating systems. :wink:

Regards,
Alister

---------- Post updated at 07:12 PM ---------- Previous update was at 06:52 PM ----------

I ran my tests using the following "interpreter" in my sh/ksh shebang:

#include <stdio.h>
#include <unistd.h>

int
main (int argc, char **argv) {
    printf("%lu\n", (unsigned long int) geteuid());
    return 0;
}

Regards,
Alister

So, it's implicitly impossible unless you have a setuid binary in the middle. I guess you could write a trivial re-executing program to setuid if you though it was a good idea.