Re: "su -" and "su" - what is the real difference?
Florent Rougon <f.rougon@free.fr> wrote:
> Is it possible for a malicious su wrapper to:
>
>   1. record root's password (of course, yes);
>
>   2. *and then* feed this password to the real "su".
>
> I suspect the real "su" empties the stdin buffer (or something like
> that) to avoid such attacks, but would be glad to hear a confirmation
> from people who know better.
OK, answering my own question. su has the following code:
    if (isatty (0) && (cp = ttyname (0))) {
    [...]
    } else {
            if (!amroot) {
                    fprintf (stderr,
                             _("%s: must be run from a terminal\n"), Prog);
                    exit (1);
            }
            tty = "???";
    }
with the result that the attached program fails this way:
  % ./autosu.py
  su: must be run from a terminal
  Child exit status: 1
  %
#! /usr/bin/env python
# autosu.py --- Try to su to root, with the password given by the program, not
#               the user.
# Copyright (c) 2006 Florent Rougon
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; version 2 dated June, 1991.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# General Public License for more details.
# 
# You should have received a copy of the GNU General Public License
# along with this program; see the file COPYING. If not, write to the
# Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
# Boston, MA  02110-1301 USA.
import sys, os, time
class Bug(Exception):
    pass
def main():
    (rfd, wfd) = os.pipe()
    child_pid = os.fork()
    if child_pid == 0:
        # We are in the child process. We MUST NOT raise any exception.
        try:
            os.dup2(rfd, 0)
            os.execvp("su", ["su", "root", "-c", "id"])
        except:
            os._exit(127)
        # Should not happen unless there is a bug in Python
        os._exit(126)
    # We are in the father process.
    time.sleep(2)
    f = os.fdopen(wfd, "wb")
    f.write("v3ry s3kr3t p455w0rd\n")
    f.flush()
    f.close()
    exit_info = os.waitpid(child_pid, 0)[1]
    if os.WIFEXITED(exit_info):
        exit_code = os.WEXITSTATUS(exit_info)
    elif os.WIFSIGNALED(exit_info):
        sys.exit("Child terminated by signal %u" % os.WTERMSIG(exit_info))
    else:
        raise Bug()
    print "Child exit status: %u" % exit_code
    sys.exit(0)
if __name__ == "__main__": main()
-- 
Florent
Reply to: