forked from brynary/testjour
/
authprogs
executable file
·231 lines (183 loc) · 6.36 KB
/
authprogs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
#!/usr/bin/perl
#
# authprogs, Copyright 2003, Brian Hatch.
#
# Released under the GPL. See the file
# COPYING for more information.
#
# This program is intended to be called from an authorized_keys
# file, i.e. triggered by use of specific SSH identities.
#
# It will check the original command (saved in $SSH_ORIGINAL_COMMAND
# environment variable by sshd) and see if it is on the 'approved'
# list.
#
# Allowed commands are stored in ~/.ssh/authprogs.conf
# The format of this file is as follows:
#
# [ ALL ]
# command0 arg arg arg
#
# [ ip.ad.dr.01 ip.ad.dr.02 ]
# command1 arg arg arg
#
# [ ip.ad.dr.03 ]
# command2 arg arg arg
# command3 arg arg
#
# There is no regexp or shell metacharacter support. If
# you want to allow 'ls /dir1' and 'ls /dir2' you need to
# explicitly create those two rules. Putting "ls /dir[12]"
# in the authprogs.conf file will *not* work.
#
# NOTE: Some versions of Bash do not export the (already exported)
# SSH_CLIENT environment variable. You can get around this by adding
# export SSH_CLIENT=${SSH_CLIENT}
# or something similar in your ~/.bashrc, /etc/profile, etc.
# http://mail.gnu.org/archive/html/bug-bash/2002-01/msg00096.html
#
# Changes:
# 2003-10-27: fixed exit status, noted by Brad Fritz.
# 2003-10-27: added blank SSH_ORIGINAL_COMMAND debug log message
use strict;
use subs qw(bail log);
use POSIX qw(strftime);
use File::Basename;
use FileHandle;
# DEBUGLEVEL values:
# 0 - log nothing
# 1 - log errors
# 2 - log failed commands
# 3 - log successful commands
# 4 - log debugging info
my $DEBUGLEVEL = 4;
# Salt to taste. /dev/null might be a likely
# place if you don't want any logging.
my $LOGFILE = "$ENV{HOME}/.ssh/authprogs.log";
# Configfile - location of the host/commands allowed.
my $CONFIGFILE = "$ENV{HOME}/.ssh/authprogs.conf";
# $CLIENT_COMMAND is the string the client sends us.
#
# Unfortunately, the actual spacing is lost. IE
# ("some string" and "some" "string" are not differentiable.)
my ($CLIENT_COMMAND) = $ENV{SSH_ORIGINAL_COMMAND};
# strip quotes - we'll explain later on.
$CLIENT_COMMAND =~ s/['"]//g;
# Set CLIENT_IP to just the ip addr, sans port numbers.
my ($CLIENT_IP) = $ENV{SSH_CLIENT} =~ /^(\S+)/;
# Open log in append mode. Note that the use of '>>'
# means you better be doing it somewhere that is only
# writeable by you, lest you have a symlink/etc attack.
# Since we default to ~/.ssh, this should not be a problem.
if ( $DEBUGLEVEL ) {
open LOG, ">>$LOGFILE" or bail "Can't open $LOGFILE\n";
LOG->autoflush(1);
}
if ( ! $ENV{SSH_ORIGINAL_COMMAND} ) {
log(4, "SSH_ORIGINAL_COMMAND not set - either the client ".
"didn't send one, or your shell is removing it from ".
"the environment.");
}
# Ok, let's scan the authprogs.conf file
open CONFIGFILE, $CONFIGFILE or bail "Config '$CONFIGFILE' not readable!";
# Note: we do not verify that the configuration file is owned by
# this user. Some might argue that we should. (A quick stat
# compared to $< would do the trick.) However some installations
# relax the requirement that the .ssh dir is owned by the user
# s.t. it can be owned by root and only modifyable in that way to
# keep even the user from making changes. We should trust the
# administrator's SSH setup (StrictModes) and not bother checking
# the ownership/perms of configfile.
my $VALID_COMMAND=0; # flag: is this command appopriate for this host?
READ_CONF: while (<CONFIGFILE>) {
chomp;
# Skip blanks and comments.
if ( /^\s*#/ ) { next }
if ( /^\s*$/ ) { next }
# Are we the beginning of a new set of
# clients?
if ( /^\[/ ) {
# Snag the IP address(es) in question.
/^ \[ ( [^\]]+ ) \] /x;
$_ = $1;
if ( /^\s*ALL\s*$/ ) { # If wildcard selected
$_ = $CLIENT_IP;
}
my @clients = split;
log 4, "Found new clients line for @clients\n";
# This would be a great place to add
# ip <=> name mapping so we can have it work
# on hostnames rather than just IP addresses.
# If so, better make sure that forward and
# reverse lookups match -- an attacker in
# control of his network can easily set a PTR
# record, so don't rely on it alone.
unless ( grep /^$CLIENT_IP$/, @clients ) {
log 4, "Client IP does not match this list.\n";
$VALID_COMMAND=0;
# Nope, not relevant - go to next
# host definition list.
while (<CONFIGFILE>) {
last if /^\[/;
}
# Never found another host definition. Bail.
redo READ_CONF;
}
$VALID_COMMAND=1;
log 4, "Client matches this list.\n";
next;
}
# We must be a potential command
if ( ! $VALID_COMMAND ) {
bail "Parsing error at line $. of $CONFIGFILE\n";
}
my $allowed_command = $_;
$allowed_command =~ s/\s+$//; # strip trailing slashes
$allowed_command =~ s/^\s+//; # strip leading slashes
# We've now got the command as we'd run it through 'system'.
#
# Problem: SSH sticks the command in $SSH_ORIGINAL_COMMAND
# but doesn't retain the argument breaks.
#
# Solution: Let's guess by stripping double and single quotes
# from both the client and the config file. If those
# versions match, we'll assume the client was right.
my $allowed_command_sans_quotes = $allowed_command;
$allowed_command_sans_quotes =~ s/["']//g;
log 4, "Comparing allowed command and client's command:\n";
log 4, " Allowed: $allowed_command_sans_quotes\n";
log 4, " Client: $CLIENT_COMMAND\n";
if ( $allowed_command_sans_quotes eq $CLIENT_COMMAND ) {
log 3, "Running [$allowed_command] from $ENV{SSH_CLIENT}\n";
# System is a bad thing to use on untrusted input.
# But $allowed_command comes from the user on the SSH
# server, from his authprogs.conf file. So we can trust
# it as much as we trust him, since it's running as that
# user.
system $allowed_command;
exit $? >> 8;
}
}
# The remote end wants to run something they're not allowed to run.
# Log it, and chastize them.
log 2, "Denying request '$ENV{SSH_ORIGINAL_COMMAND}' from $ENV{SSH_CLIENT}\n";
print STDERR "You're not allowed to run '$ENV{SSH_ORIGINAL_COMMAND}'\n";
exit 1;
sub bail {
# print log message (w/ guarenteed newline)
if (@_) {
$_ = join '', @_;
chomp $_;
log 1, "$_\n";
}
close LOG if $DEBUGLEVEL;
exit 1
}
sub log {
my ($level,@list) = @_;
return if $DEBUGLEVEL < $level;
my $timestamp = strftime "%Y/%m/%d %H:%M:%S", localtime;
my $progname = basename $0;
grep { s/^/$timestamp $progname\[$$\]: / } @list;
print LOG @list;
}