output asked headers in the order they were asked; avoid header name spoofing by...
[hband-tools.git] / user-tools / notashell
blobeed6cf1cfa9825224b114c806a11365bf5764351
1 #!/usr/bin/env perl
3 =pod
5 =head1 NAME
7 notashell - A non-interactive shell lacking of any shell syntax
9 =head1 SYNOPSIS
11 notashell -c I<COMMANDLINE>
13 =head1 DESCRIPTION
15 notashell(1) is a program with non-interactive shell interface (ie. C<sh -c commandLine>),
16 and intentionally does not understand any shell syntax or meta character,
17 rather takes the first word of I<COMMANDLINE> and executes it as a single command
18 with all of the rest of I<COMMANDLINE> as its arguments.
20 This is useful when you have a program which normally calls some other commands via shell (eg. system(3)),
21 notably with user-controlled parts in it, ie. data from an untrusted source.
22 This potentially makes the call vulnerable to shell-injection.
23 Like incrond(8) since 2015, which triggered the author to make this defense tool.
25 These kind of programs usually try to guard by escaping user input,
26 but it often turns out that the re-implemented shell-escape mechanism was bad or incomplete.
28 Using notashell(1) enables you to fully evade this type of shell-injection attacks.
29 Since if you control at least the first word of I<COMMANDLINE>,
30 you can trustworthly call a program (wrapper script) in which the supplied I<COMMANDLINE>
31 can be re-examined, accepted, rejected, rewritten, etc.
32 and pass the execution forward now with verified user input.
34 No need to think on "is it safe to run by shell?" or quotation-mark/escape-backslash forests ever again.
36 =head1 FILES
38 Customize how I<COMMANDLINE> is parsed by F</etc/notashell/custom.pl>.
39 If this file exists, notashell(1) executes it inside its main context,
40 so in custom.pl you can build in custom logic.
41 There are some perl variables accessible:
42 B<$CommandString>, B<@CommandArgs>, and B<$ExecName>.
44 B<$CommandString> is just the I<COMMANDLINE> and recommended that only read it in custom.pl,
45 because changing it does not affect what will be executed.
46 B<@CommandArgs> is I<COMMANDLINE> split into parts by spaces.
47 You may change or redefine it to control what will be the arguments of the executed command at the end.
48 B<$ExecName> is the command's name or path (C<$CommandArgs[0]> by default) what will be executed at the end.
49 You may change this one too, and it's does not need to be aligned with C<$CommandArgs[0]>.
51 You are also given some utility functions to use in custom.pl at your dispense:
52 B<stripQuotes()>, B<setupIORedirects()>.
53 B<stripQuotes()> currently just return the supplied string without surrounding single and double quotes.
55 B<setupIORedirects()> scans the supplied list for common shell IO redirection syntax,
56 setup these redirections on the current process,
57 and return the input list except those elements which are found to be part of the redirection.
59 Example:
61 setupIORedirects("date", "-R", ">", "/tmp/date.txt")
62 # returns: ("date", "-R")
63 # and have STDOUT redirected to the file.
65 Recognized representation:
67 =over 4
69 =item operators:
71 write (B<< > >>) and append (B<< > >>B<< > >>)
73 =item an integer before the operator;
75 optional, defaults are the same as in sh(1)
77 =item filename
79 just right after the operator or in the next argument;
80 strings only matching to B<[a-zA-Z0-9_,./-]+> are considered filenames.
82 =back
84 Don't forget to exit from custom.pl with a true value.
86 B<Typical custom.pl script>:
88 @CommandArgs = setupIORedirects(@CommandArgs);
89 @CommandArgs = map {stripQuotes($_)} @CommandArgs;
92 =head1 SETUP
94 You probably need a tool to force the neglegent program (which is the attack vector to shell-injection)
95 to run notashell(1) in place of normal shell (sh(1), bash(1)).
96 See for example B<noshellinject> tool to accomplish this (in F<../root-tools> directory in notashell's source git repo).
98 =cut
101 use POSIX qw/dup2/;
104 sub stripQuotes
106 my $s = shift;
107 $s =~ s/^([''""])(.*)\g1$/$2/;
108 return $s;
111 sub setupIORedirects
113 my @cmdargs = @_;
114 my @return = ();
115 my $filename_chars = 'a-zA-Z0-9_,./-';
117 while(@cmdargs)
119 my $arg = shift @cmdargs;
120 my $arg_consumed = 0;
121 if(my ($fd, $redirop, $target) = $arg =~ /^(\d*)(>|>>)([$filename_chars]*)$/)
123 if($target eq '')
125 my $nextarg = shift @cmdargs;
126 if($nextarg =~ /^[$filename_chars]+$/)
128 $target = $nextarg;
130 else
132 unshift @cmdargs, $nextarg;
135 if($target ne '')
137 if($fd eq '')
139 $fd = 0 if $redirop =~ /^</;
140 $fd = 1 if $redirop =~ /^>/;
142 open my $fh, $redirop, $target or die "$0: $target: open: $!\n";
143 dup2(fileno $fh, $fd) or die "$0: $target: dup2: $!\n";
144 $arg_consumed = 1;
147 push @return, $arg unless $arg_consumed;
149 return @return;
153 $ParserHookPath = "/etc/notashell/custom.pl";
155 if(scalar(@ARGV) == 2 and $ARGV[0] eq '-c')
157 $sh_c_invocation = 1;
160 if($ENV{'NOTASHELL_INTERCEPT'} eq "0" or not $sh_c_invocation)
162 my $basename = $0 =~ s/.*\/([^\/]+)$/$1/r;
163 $progname = $basename;
164 exec {"/var/lib/notashell/real-" . $basename} $0, @ARGV;
166 else
168 $CommandString = $ARGV[1];
169 @CommandArgs = split / /, $CommandString;
170 $ExecName = $CommandArgs[0];
172 if(-e $ParserHookPath)
174 my $hook_result = do $ParserHookPath;
175 if(not defined $hook_result)
177 die "$ParserHookPath: $@: $!\n";
181 $progname = $ExecName;
182 $ENV{'NOTASHELL_INTERCEPT'} = 0;
183 $ENV{'NOTASHELL_ORIGINAL_COMMAND'} = $CommandString;
185 exec {$ExecName} @CommandArgs;
188 ($errno, $errstr) = (int $!, $!);
189 warn "$progname: $errstr\n";
190 exit 125+$errno;