execute external program in lua without userinput as arguments in lua - security

I want to execute an external program in lua. Usually this can be done with
os.execute("run '"..arg0.."' 'arg1' arg2")
The problem with this approach is if I want to pass user input as string to an external program, user input could be '; evil 'h4ck teh system' ' and the script from above would execute like this:
/bin/bash -c "run ''; evil 'h4ck teh system' '' 'arg1' arg2"
Another problem occurs when I have '$var' as argument and the shell replaces this with its environment variable. In my particular case I have something like [[program 'set title "$My Title$"']] – so nested strings – and program parses "$My Title$" (with escape sequences) differently than '$My Title$' (as it is). Because I want to set the title as it, the best way is to have arguments like this: 'My Title'. But now the command have to be:
os.execute([[run "set title '$My Title$'"]])
But now – as I said – $My will be replaced with an empty string, because the environment does not know any variable named $My and because, I never wanted it to be replaced.
So I am looking for the usual approach with
execv("run", {"set title '"..arg0.."'", arg1, arg2})

local safe_unquoted = "^[-~_/.%w%%+,:#^]*$"
local function q(text, expand) -- quoting under *nix shells
-- "expand"
-- false/nil: $var and `cmd` must NOT be expanded (use single quotes)
-- true: $var and `cmd` must be expanded (use double quotes)
if text == "" then
text = '""'
elseif not text:match(safe_unquoted) then
if expand then
text = '"'..text:gsub('["\\]', '\\%0')..'"'
else
local new_text = {}
for s in (text.."'"):gmatch"(.-)'" do
new_text[#new_text + 1] = s:match(safe_unquoted) or "'"..s.."'"
end
text = table.concat(new_text, "\\'")
end
end
return text
end
function execute_commands(...)
local all_commands = {}
for k, command in ipairs{...} do
for j = 1, #command do
if not command[j]:match"^[-~_%w/%.]+$" then
command[j] = q(command[j], command.expand)
end
end
all_commands[k] = table.concat(command, " ") -- space is arguments delimiter
end
all_commands = table.concat(all_commands, ";") -- semicolon is commands delimiter
return os.execute("/bin/bash -c "..q(all_commands))
end
Usage examples:
-- Usage example #1:
execute_commands(
{"your/program", "arg 1", "$arg2", "arg-3", "~/arg4.txt"},
{expand=true, "echo", "Your program finished with exit code $?"},
{"ls", "-l"}
)
-- The following command will be executed:
-- /bin/bash -c 'your/program '\''arg 1'\'' '\''$arg2'\'' arg-3 ~/arg4.txt;echo "Your program finished with exit code $?";ls -l'
$arg2 will NOT be expanded into value because of single quotes around it, as you required.
Unfortunately, "Your program finished with exit code $?" will NOT be expanded too (unless you explicitly set expand=true).
-- Usage example #2:
execute_commands{"run", "set title '$My Title$'", "arg1", "arg2"}
-- the generated command is not trivial, but it does exactly what you need :-)
-- /bin/bash -c 'run '\''set title '\''\'\'\''$My Title$'\''\'\'' arg1 arg2'

Related

How do I assign a File into a variable to work with that variable? [duplicate]

I have this script called test.sh:
#!/bin/bash
STR = "Hello World"
echo $STR
when I run sh test.sh I get this:
test.sh: line 2: STR: command not found
What am I doing wrong? I look at extremely basic/beginners bash scripting tutorials online and this is how they say to declare variables... So I'm not sure what I'm doing wrong.
I'm on Ubuntu Server 9.10. And yes, bash is located at /bin/bash.
You cannot have spaces around the = sign.
When you write:
STR = "foo"
bash tries to run a command named STR with 2 arguments (the strings = and foo)
When you write:
STR =foo
bash tries to run a command named STR with 1 argument (the string =foo)
When you write:
STR= foo
bash tries to run the command foo with STR set to the empty string in its environment.
I'm not sure if this helps to clarify or if it is mere obfuscation, but note that:
the first command is exactly equivalent to: STR "=" "foo",
the second is the same as STR "=foo",
and the last is equivalent to STR="" foo.
The relevant section of the sh language spec, section 2.9.1 states:
A "simple command" is a sequence of optional variable assignments and redirections, in any sequence, optionally followed by words and redirections, terminated by a control operator.
In that context, a word is the command that bash is going to run. Any string containing = (in any position other than at the beginning of the string) which is not a redirection and in which the portion of the string before the = is a valid variable name is a variable assignment, while any string that is not a redirection or a variable assignment is a command. In STR = "foo", STR is not a variable assignment.
Drop the spaces around the = sign:
#!/bin/bash
STR="Hello World"
echo $STR
In the interactive mode everything looks fine:
$ str="Hello World"
$ echo $str
Hello World
Obviously(!) as Johannes said, no space around =. In case there is any space around = then in the interactive mode it gives errors as
No command 'str' found
I know this has been answered with a very high-quality answer. But, in short, you cant have spaces.
#!/bin/bash
STR = "Hello World"
echo $STR
Didn't work because of the spaces around the equal sign. If you were to run...
#!/bin/bash
STR="Hello World"
echo $STR
It would work
When you define any variable then you do not have to put in any extra spaces.
E.g.
name = "Stack Overflow"
// it is not valid, you will get an error saying- "Command not found"
So remove spaces:
name="Stack Overflow"
and it will work fine.

Assigning variable to a variable inside if statement

I am trying to assign a variable from a prompt input choice with no luck. If the user inputs 1, I want target_db_name = "database2".
My code:
while true; do
read -p "What is the table name?" table_name
table_name=${table_name,,}
if hdfs dfs -test -e /foo/$table_name ;
then read -p "What is the target database you want to copy the
“foo.${table_name}” table to?
Your three options are:
1) database1
2) database2
3) database3
Type 1, 2, or 3: " target_db;
(((Here is where I want to state if $target_db = "1" then target_db_name
= "database1", if $target_db = "2" then target_db_name = "database2" etc...)))
read -p "Would you like to begin the HDFS copy with the following configuration:
Target Database: ${target_db_name}
Table Name: ${table_name}
Continue (Y/N):"
else echo "Please provide a valid table name.
Exiting this script" ; exit ; fi
done
I have tried creating another if statement with no luck.
"....Type 1, 2, or 3: " target_db;
else if $target_db = "1" then target_db_name = "edw_qa_history"; fi
if $target_db = "1" then won't work, because what follows if must be a command, not a test expression. Now, the most common command used in if statements is [ (yes, that's actually a command name; it's synonymous with the test command), which takes a test expression (and a close bracket) as its arguments and succeeds or fails depending on whether the expression is true or not. So the correct syntax would be something like:
if [ "$target_db" = "1" ]; then
Note that there are two other differences from what you had: I put double-quotes around the variable reference (almost always a good idea, to avoid may parsing oddities), and added a semicolon before then (needed to indicate where the arguments to [ end and shell syntax resumes). I also notice you have semicolons at the end of many lines of your script; this isn't necessary, the end-of-line is enough to indicate the end of a command. It's only if you have another command (or something like then) on the same line that you need a semicolon as a delimiter.
HOWEVER, as #Barmar pointed out in a comment, case would probably be better than a list of if and elif statements here. case is intended specifically for comparing a string against a list of other strings (or patterns), and executing different things depending on which one it matches. It looks something like this:
case "$target_db" in
1) target_db_name="database1" ;;
2) target_db_name="database2" ;;
3) target_db_name="database3" ;;
*) "Please provide a valid table name. Exiting this script" ; exit ;;
esac
Here, the double-semicolon is needed, even at the end of a line, to indicate the end of each case. Also, note that the * pattern (the last case) matches anything, so it functions like an else would in an if ... elif ... sequence.
Final note: use shellcheck.net to sanity-check your code.
You don't need an if statement to map the number to an array; you just need an array.
db_names=(
"datebase 1"
"database 2"
"database 3"
)
# ...
target_db_name=${db_names[$target_db - 1]}
if [[ -z $target_db_name ]]; then
exit
fi

escape a whole array of arguments for use in sh-like shells with a standard tool

I'm looking for a standard tool capable of taking all of its arguments and turning it into a single string suitable for use as multiple arguments in an automatically generated bash/sh/zsh script. Such a command is extremely useful in various disciplines of script-fu. An example of its usage:
% shsafe 'A big \nasty string '\'' $HOME $PATH' 'another string \\'
'A big \nasty string '\'' $HOME $PATH' 'another string \\'
Using it in another script:
% sshc host rm 'file/with spaces and $special chars'
where sshc contains
#!/bin/bash
# usage: sshc host command [arg ...]
# Escapes its arguments so that the command may contain special
# characters. Assumes the remote shell is sh-like.
host=$1
shift
exec ssh "$host" "$(shsafe "$#")"
Another example:
#!/bin/bash
# Run multiple commands in a single sudo session. The arguments of
# this script are passed as arguments to the first command. Useful if
# you don't want to have to type the password for both commands and
# the first one takes a while to run.
sudo bash -c "pacman -Syu $(shsafe "$#") && find /etc -name '*.pacnew'"
I couldn't find a suitable solution to this problem in the pre-existing commands, so I made up my own, called shsafe. It uses the fact that single quotes, '', turn off absolutely all shell expansion, except for ' itself.
shsafe:
#!/usr/bin/env python
from sys import *
n = len(argv)
if n == 1:
exit(0)
i = 1
while True:
stdout.write("'" + argv[i].replace("'", "'\\''") + "'")
i += 1
if i == n:
break
stdout.write(' ')
stdout.write('\n')
Is there any standard tool capable of doing this to its arguments?
Note that the printf command with a format string consisting of just the %q formatter is not good enough for this, because it won't keep multiple arguments separated:
% printf %q arg1 arg2
arg1arg2
I did eventually figure out a decent way of doing this:
% printf "$'%q' " 'crazy string \ $HOME' 'another\ string'
$'crazy\ string\ \\\ \$HOME' $'another\\\ string'
It's a little error prone what with the quotes everywhere, so it's not ideal, IMO, but it's a solid solution that should work anywhere. If it's being used a lot, you could always turn it into a shell function:
shsafe () {
printf "$'%q' " "$#"
}

How to use set to set complex arguments of a shell functions

I want to execute a function with complex arguments
I want to input them in this way (if it is possible?)
set [-options here] a111 "a222" "a333 a333" "a444"
set [-options here] b111 "b222" "b333 b333" "b444"
set [-options here] c111 "c222" "c333 c333" "c444"
myfunc "$#"
myfunc should see
a111 "a222" "a333 a333" "a444" ==> as the first argument with keeping ""
b111 "b222" "b333 b333" "b444" ==> as the second argument with keeping ""
c111 "c222" "c333 c333" "c444" ==> as the third argument with keeping ""
Is it possible to do that with set or with other way? and how to do it?
By the way I do not want to use \"
Also I can't use ' (like '"a333 a333"') since it does not evaluate vars
If you want the value to have the quotes (which is an odd thing to want in general) you need to escape them from the shell.
$ set -- a111 '"a222"' '"a333 a333"' "\"a444\""
$ c() {
printf argc:%s\\n "$#"
printf argv:%s\\n "$#"
}
$ c "$#"
argc:4
argv:a111
argv:"a222"
argv:"a333 a333"
argv:"a444"

shell script function return a string

I am new to shell scripts, I am trying to create a simple function which will return the concatenated two strings that are passed as parameters. I tried with below code
function getConcatenatedString() {
echo "String1 $1"
echo "String2 $2"
str=$1/$2
echo "Concatenated String ${str}"
echo "${str}"
}
//I am calling the above function
constr=$(getConcatenatedString "hello" "world")
echo "printing result"
echo "${constr}"
echo "exit"
I see the below output when running the script with above code,
printing result
String1 hello
String2 world
Concatenated String hello/world
hello/world
exit
If you look at the code I am first calling the function and then I am echoing "printing result" statement, but the result is first comes the "printing result" and echos the statement inside the function. Is the below statement calling the function
constr=$(getConcatenatedString "hello" "world")
or
echo ${constr}
is calling the function ?
Because if I comment out #echo ${constr} then nothing is getting echoed !!! Please clarify me.
The first is calling the function and storing all of the output (four echo statements) into $constr.
Then, after return, you echo the preamble printing result, $constr (consisting of four lines) and the exit message.
That's how $() works, it captures the entire standard output from the enclosed command.
It sounds like you want to see some of the echo statements on the console rather than capturing them with the $(). I think you should just be able to send them to standard error for that:
echo "String1 $1" >&2
paxdiablo's solution is correct. You cannot return a string from a function, but you can capture the output of the function or return an integer value that can be retrieved by the caller from $?. However, since all shell variables are global, you can simply do:
getConcatenatedString() { str="$1/$2"; }
getConcatenatedString hello world
echo "Concatenated String ${str}"
Note that the function keyword is redundant with (), but function is less portable.
A more flexible, but slightly harder to understand approach is to pass a variable name, and use eval so that the variable becomes set in the caller's context (either a global or a function local). In bash:
function mylist()
{
local _varname=$1 _p _t
shift
for _p in "$#"; do
_t=$_t[$_p]
done
eval "$_varname=\$_t"
}
mylist tmpvar a b c
echo "result: $tmpvar"
On my Linux desktop (bash-3.2) it's approx 3-5x faster (10,000 iterations) than using ``, since the latter has process creation overheads.
If you have bash-4.2, its declare -g allows a function to set a global variable, so you can replace the unpretty eval with:
declare -g $_varname="$_t"
The eval method is similar to TCL's upvar 1, and declare -g is similar to upvar #0.
Some shell builtins support something similar, like bash's printf with "-v", again saving process creation by assigning directly to a variable instead of capturing output (~20-25x faster for me).

Resources