7 notashell - A non-interactive shell lacking of any shell syntax
11 notashell -c I<COMMANDLINE>
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.
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.
61 setupIORedirects("date", "-R", ">", "/tmp/date.txt")
62 # returns: ("date", "-R")
63 # and have STDOUT redirected to the file.
65 Recognized representation:
71 write (B<< > >>) and append (B<< > >>B<< > >>)
73 =item an integer before the operator;
75 optional, defaults are the same as in sh(1)
79 just right after the operator or in the next argument;
80 strings only matching to B<[a-zA-Z0-9_,./-]+> are considered filenames.
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;
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).
107 $s =~ s/^([''""])(.*)\g1$/$2/;
115 my $filename_chars = 'a-zA-Z0-9_,./-';
119 my $arg = shift @cmdargs;
120 my $arg_consumed = 0;
121 if(my ($fd, $redirop, $target) = $arg =~ /^(\d*)(>|>>)([$filename_chars]*)$/)
125 my $nextarg = shift @cmdargs;
126 if($nextarg =~ /^[$filename_chars]+$/)
132 unshift @cmdargs, $nextarg;
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";
147 push @return, $arg unless $arg_consumed;
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;
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";