su another user through ssh with a local script












1















I have the following snippet:



ssh root@"${DO_DROPLET_IP}" -i "${SSH_PRIVKEY_PATH}" su www -c sh -c -- 
"./server-user-setup.sh" "${BB_USER}" "${APP_USER}"


which is inspired by the following (working) snippet:



ssh root@"${DO_DROPLET_IP}" -i "${SSH_PRIVKEY_PATH}" sh -s -- < 
"./server-root-setup.sh" "${BB_USER}" "${APP_USER}"


where both files are local files.



However, I can't find a way to quote or escape the top command so that it successfully works. The above throws ${value of BB_USER}": ./server-user-setup.sh: Permission denied. I have tried many variations:





Can anybody explain to me how to quote/escape this command properly?





Update: getting promisingly close - this fires the script, but doesn't pass the arguments!



ssh root@"${DO_DROPLET_IP}" -i "${SSH_PRIVKEY_PATH}" sh -s -- su www -c < 
"./server-user-setup.sh" "${BB_USER}" "${APP_USER}"


returns



server-user-setup.sh: Wrong number of arguments. # Part of the script - no args passed
su www -c ${value of BB_USER} ${value of APP_USER}









share|improve this question

























  • Apologies! Local

    – Nick Bull
    Jan 31 at 17:08











  • Lol yes sorry. End of the day tiredness making me type things incorrectly. I'm sshing as root so no sudo

    – Nick Bull
    Jan 31 at 17:42








  • 1





    I'm not sure if this is what you want (so only a comment); and I'm not sure it will work; and keep in mind that running random code from the internet is dangerous (especially as root). This madness: ssh root@"${DO_DROPLET_IP}" -i "${SSH_PRIVKEY_PATH}" 'su www -c "exec sh -s -- '"'${BB_USER}' '${APP_USER}'"'"' <"./server-user-setup.sh"

    – Kamil Maciorowski
    Jan 31 at 17:58











  • @KamilMaciorowski That's... wow. I feel a lot better about not getting it. Post it as an answer for others! I'll accept as soon as you do :)

    – Nick Bull
    Jan 31 at 18:52











  • @KamilMaciorowski It works correctly! I'll leave it as an edit in the question for somebody, with the answer being rewarded for the proper explanation :)

    – Nick Bull
    Jan 31 at 20:54
















1















I have the following snippet:



ssh root@"${DO_DROPLET_IP}" -i "${SSH_PRIVKEY_PATH}" su www -c sh -c -- 
"./server-user-setup.sh" "${BB_USER}" "${APP_USER}"


which is inspired by the following (working) snippet:



ssh root@"${DO_DROPLET_IP}" -i "${SSH_PRIVKEY_PATH}" sh -s -- < 
"./server-root-setup.sh" "${BB_USER}" "${APP_USER}"


where both files are local files.



However, I can't find a way to quote or escape the top command so that it successfully works. The above throws ${value of BB_USER}": ./server-user-setup.sh: Permission denied. I have tried many variations:





Can anybody explain to me how to quote/escape this command properly?





Update: getting promisingly close - this fires the script, but doesn't pass the arguments!



ssh root@"${DO_DROPLET_IP}" -i "${SSH_PRIVKEY_PATH}" sh -s -- su www -c < 
"./server-user-setup.sh" "${BB_USER}" "${APP_USER}"


returns



server-user-setup.sh: Wrong number of arguments. # Part of the script - no args passed
su www -c ${value of BB_USER} ${value of APP_USER}









share|improve this question

























  • Apologies! Local

    – Nick Bull
    Jan 31 at 17:08











  • Lol yes sorry. End of the day tiredness making me type things incorrectly. I'm sshing as root so no sudo

    – Nick Bull
    Jan 31 at 17:42








  • 1





    I'm not sure if this is what you want (so only a comment); and I'm not sure it will work; and keep in mind that running random code from the internet is dangerous (especially as root). This madness: ssh root@"${DO_DROPLET_IP}" -i "${SSH_PRIVKEY_PATH}" 'su www -c "exec sh -s -- '"'${BB_USER}' '${APP_USER}'"'"' <"./server-user-setup.sh"

    – Kamil Maciorowski
    Jan 31 at 17:58











  • @KamilMaciorowski That's... wow. I feel a lot better about not getting it. Post it as an answer for others! I'll accept as soon as you do :)

    – Nick Bull
    Jan 31 at 18:52











  • @KamilMaciorowski It works correctly! I'll leave it as an edit in the question for somebody, with the answer being rewarded for the proper explanation :)

    – Nick Bull
    Jan 31 at 20:54














1












1








1








I have the following snippet:



ssh root@"${DO_DROPLET_IP}" -i "${SSH_PRIVKEY_PATH}" su www -c sh -c -- 
"./server-user-setup.sh" "${BB_USER}" "${APP_USER}"


which is inspired by the following (working) snippet:



ssh root@"${DO_DROPLET_IP}" -i "${SSH_PRIVKEY_PATH}" sh -s -- < 
"./server-root-setup.sh" "${BB_USER}" "${APP_USER}"


where both files are local files.



However, I can't find a way to quote or escape the top command so that it successfully works. The above throws ${value of BB_USER}": ./server-user-setup.sh: Permission denied. I have tried many variations:





Can anybody explain to me how to quote/escape this command properly?





Update: getting promisingly close - this fires the script, but doesn't pass the arguments!



ssh root@"${DO_DROPLET_IP}" -i "${SSH_PRIVKEY_PATH}" sh -s -- su www -c < 
"./server-user-setup.sh" "${BB_USER}" "${APP_USER}"


returns



server-user-setup.sh: Wrong number of arguments. # Part of the script - no args passed
su www -c ${value of BB_USER} ${value of APP_USER}









share|improve this question
















I have the following snippet:



ssh root@"${DO_DROPLET_IP}" -i "${SSH_PRIVKEY_PATH}" su www -c sh -c -- 
"./server-user-setup.sh" "${BB_USER}" "${APP_USER}"


which is inspired by the following (working) snippet:



ssh root@"${DO_DROPLET_IP}" -i "${SSH_PRIVKEY_PATH}" sh -s -- < 
"./server-root-setup.sh" "${BB_USER}" "${APP_USER}"


where both files are local files.



However, I can't find a way to quote or escape the top command so that it successfully works. The above throws ${value of BB_USER}": ./server-user-setup.sh: Permission denied. I have tried many variations:





Can anybody explain to me how to quote/escape this command properly?





Update: getting promisingly close - this fires the script, but doesn't pass the arguments!



ssh root@"${DO_DROPLET_IP}" -i "${SSH_PRIVKEY_PATH}" sh -s -- su www -c < 
"./server-user-setup.sh" "${BB_USER}" "${APP_USER}"


returns



server-user-setup.sh: Wrong number of arguments. # Part of the script - no args passed
su www -c ${value of BB_USER} ${value of APP_USER}






linux ssh shell su






share|improve this question















share|improve this question













share|improve this question




share|improve this question








edited Jan 31 at 20:57









Kamil Maciorowski

28.7k156187




28.7k156187










asked Jan 31 at 16:46









Nick BullNick Bull

1084




1084













  • Apologies! Local

    – Nick Bull
    Jan 31 at 17:08











  • Lol yes sorry. End of the day tiredness making me type things incorrectly. I'm sshing as root so no sudo

    – Nick Bull
    Jan 31 at 17:42








  • 1





    I'm not sure if this is what you want (so only a comment); and I'm not sure it will work; and keep in mind that running random code from the internet is dangerous (especially as root). This madness: ssh root@"${DO_DROPLET_IP}" -i "${SSH_PRIVKEY_PATH}" 'su www -c "exec sh -s -- '"'${BB_USER}' '${APP_USER}'"'"' <"./server-user-setup.sh"

    – Kamil Maciorowski
    Jan 31 at 17:58











  • @KamilMaciorowski That's... wow. I feel a lot better about not getting it. Post it as an answer for others! I'll accept as soon as you do :)

    – Nick Bull
    Jan 31 at 18:52











  • @KamilMaciorowski It works correctly! I'll leave it as an edit in the question for somebody, with the answer being rewarded for the proper explanation :)

    – Nick Bull
    Jan 31 at 20:54



















  • Apologies! Local

    – Nick Bull
    Jan 31 at 17:08











  • Lol yes sorry. End of the day tiredness making me type things incorrectly. I'm sshing as root so no sudo

    – Nick Bull
    Jan 31 at 17:42








  • 1





    I'm not sure if this is what you want (so only a comment); and I'm not sure it will work; and keep in mind that running random code from the internet is dangerous (especially as root). This madness: ssh root@"${DO_DROPLET_IP}" -i "${SSH_PRIVKEY_PATH}" 'su www -c "exec sh -s -- '"'${BB_USER}' '${APP_USER}'"'"' <"./server-user-setup.sh"

    – Kamil Maciorowski
    Jan 31 at 17:58











  • @KamilMaciorowski That's... wow. I feel a lot better about not getting it. Post it as an answer for others! I'll accept as soon as you do :)

    – Nick Bull
    Jan 31 at 18:52











  • @KamilMaciorowski It works correctly! I'll leave it as an edit in the question for somebody, with the answer being rewarded for the proper explanation :)

    – Nick Bull
    Jan 31 at 20:54

















Apologies! Local

– Nick Bull
Jan 31 at 17:08





Apologies! Local

– Nick Bull
Jan 31 at 17:08













Lol yes sorry. End of the day tiredness making me type things incorrectly. I'm sshing as root so no sudo

– Nick Bull
Jan 31 at 17:42







Lol yes sorry. End of the day tiredness making me type things incorrectly. I'm sshing as root so no sudo

– Nick Bull
Jan 31 at 17:42






1




1





I'm not sure if this is what you want (so only a comment); and I'm not sure it will work; and keep in mind that running random code from the internet is dangerous (especially as root). This madness: ssh root@"${DO_DROPLET_IP}" -i "${SSH_PRIVKEY_PATH}" 'su www -c "exec sh -s -- '"'${BB_USER}' '${APP_USER}'"'"' <"./server-user-setup.sh"

– Kamil Maciorowski
Jan 31 at 17:58





I'm not sure if this is what you want (so only a comment); and I'm not sure it will work; and keep in mind that running random code from the internet is dangerous (especially as root). This madness: ssh root@"${DO_DROPLET_IP}" -i "${SSH_PRIVKEY_PATH}" 'su www -c "exec sh -s -- '"'${BB_USER}' '${APP_USER}'"'"' <"./server-user-setup.sh"

– Kamil Maciorowski
Jan 31 at 17:58













@KamilMaciorowski That's... wow. I feel a lot better about not getting it. Post it as an answer for others! I'll accept as soon as you do :)

– Nick Bull
Jan 31 at 18:52





@KamilMaciorowski That's... wow. I feel a lot better about not getting it. Post it as an answer for others! I'll accept as soon as you do :)

– Nick Bull
Jan 31 at 18:52













@KamilMaciorowski It works correctly! I'll leave it as an edit in the question for somebody, with the answer being rewarded for the proper explanation :)

– Nick Bull
Jan 31 at 20:54





@KamilMaciorowski It works correctly! I'll leave it as an edit in the question for somebody, with the answer being rewarded for the proper explanation :)

– Nick Bull
Jan 31 at 20:54










1 Answer
1






active

oldest

votes


















1














tl;dr



ssh root@"${DO_DROPLET_IP}" -i "${SSH_PRIVKEY_PATH}" 
'su www -c "exec sh -s -- '"'${BB_USER}' '${APP_USER}'"'"' <"./server-user-setup.sh"


Code injection possible via BB_USER and APP_USER variables.





Long answer



Let's analyze what happens. It's quite complex. For clarity let's assume:



DO_DROPLET_IP=ip
SSH_PRIVKEY_PATH=key
BB_USER=b-user
APP_USER=a-user




Original (working) snippet




ssh root@"${DO_DROPLET_IP}" -i "${SSH_PRIVKEY_PATH}" sh -s -- < 
"./server-root-setup.sh" "${BB_USER}" "${APP_USER}"



All variables are expanded locally. The redirection is local and it doesn't matter it's in the middle. With our assumed variable values this is the equivalent command:



<"./server-root-setup.sh" ssh root@"ip" -i "key" sh -s -- "b-user" "a-user"


ssh gets these arguments: root@ip, -i, key, sh, -s, --, b-user, a-user. Since root@ip looks like user@hostname, the first following argument not recognized as option (like -i) or option-argument (like key being an option-argument to -i) is considered to start an array of operands to build the remote command from. The said argument is sh and this is the command ssh tries to run on the remote side:



sh -s -- b-user a-user


This starts sh which expects commands from its stdin (due to -s). -- is a POSIX convention that tells the tool anything that follows is not an option (see this, guideline 10). This way even if our ${BB_USER} expanded to -f or so, sh wouldn't consider it an option. The following operands are set as the positional parameters of the shell ($1, $2). Stdin is provided by ssh, the local file ./server-root-setup.sh is streamed through.



If the file and the relevant two variables were available on the remote side, you could get (almost) the same result with the following command there:



sh "./server-root-setup.sh" ${BB_USER} ${APP_USER}


Or if there's a proper shebang in the script:



./server-root-setup.sh ${BB_USER} ${APP_USER}


So the snippet is indeed a way to run a local script on the remote side. But note I deliberately didn't quote ${BB_USER} or ${APP_USER}. In the original snipped they are quoted on the local side, but it doesn't matter because ssh builds and passes the command as a string to be parsed on the remote side. If any of the two variables contained inner spaces, the remote sh would get more arguments than you expected; leading or trailing spaces would be lost. Characters like $, ", ' or would cause trouble, unless the variable values were deliberately designed to be parsed (but then they couldn't be used locally to produce an equivalent result, that's why I wrote "almost").



Worse things will happen if e.g. ${APP_USER} expands to a-user& rogue_command. On the remote side you get



sh -s -- b-user a-user& rogue_command


Note such code injection cannot happen when you run things locally (with or without quotes)



./server-root-setup.sh ${BB_USER} ${APP_USER}


because separators like & and ; are recognized before variables are expanded, not after. But if you expand variables locally and then the resulting string is parsed again on the remote side, this is a whole different story. It's like remote eval.



I guess ${BB_USER} and ${APP_USER} are usernames, quite safe strings, so the whole thing works despite possible general issues. But if these variables are not fully controlled by you, the approach is not safe.





Your (failed) attempt




ssh root@"${DO_DROPLET_IP}" -i "${SSH_PRIVKEY_PATH}" su www -c sh -c -- 
"./server-user-setup.sh" "${BB_USER}" "${APP_USER}"



No redirection this time. Again the variables are expanded locally and the equivalent command looks like this:



ssh root@"ip" -i "key" su www -c sh -c -- "./server-user-setup.sh" "b-user" "a-user"


ssh tries to run this on the remote side:



su www -c sh -c -- ./server-user-setup.sh b-user a-user


Note all arguments are now arguments to su. At this moment sh is just an option-argument to -c option, similarly -- is an option-argument to another -c option (so it doesn't indicate end of options here). My tests show that from multiple -c options to su only the last one takes effect. This means -c sh is discarded, su uses whatever shell is specified in the (remote) /etc/passwd for the www user. This may or may not be sh. Let's say it's some-shell. This is what's run next (as www user):



some-shell -c --   # but wait, it's not all


with remaining operands of su, so



some-shell -c -- ./server-user-setup.sh b-user a-user


If some-shell is a common shell like sh or bash, it behaves alike. Here -c doesn't take an option-argument (see the specification), so -- does indicate end of options. In this case no following argument can be taken as an option, so we can omit --:



some-shell -c ./server-user-setup.sh b-user a-user


This is like




sh -c [-abCefhimnuvx] [-o option]... [+abCefhimnuvx] [+o option]... command_string [command_name [argument...]]



or (discarding everything we don't use)



some-shell -c command_string command_name argument


So ./server-user-setup.sh is command_string, b-user is command_name and a-user is argument.




-c

Read commands from the command_string operand. Set the value of special parameter 0 […] from the value of the command_name operand and the positional parameters ($1, $2, and so on) in sequence from the remaining argument operands. […]




In effect the remote some-shell tries to read (source) ./server-user-setup.sh, with the string a-user available as $1. Special parameter 0 (now with the value b-user) is what the shell considers to be its own name. The name is used in error messages like this one:




b-user: ./server-user-setup.sh: Permission denied



Apparently there is ./server-user-setup.sh on the remote side but www user cannot read it.





My approach (still unsafe):



ssh root@"${DO_DROPLET_IP}" -i "${SSH_PRIVKEY_PATH}" 
'su www -c "exec sh -s -- '"'${BB_USER}' '${APP_USER}'"'"' <"./server-user-setup.sh"
# 1 12 23 3


The last line (a comment) is just to enumerate quotes in the previous line. To understand how the command works we need to "decrypt" this quoting frenzy. The important things are:




  • Everything inside 1 and 3 single quotes should be taken literally.


  • ${BB_USER} and ${APP_USER} are in double quotes 2; "inner" single quotes belong to the quoted string, they don't prevent the variables from expanding.

  • Wherever a closing quote immediately precedes an opening quote (e.g. the quotes above 12), the quoted strings will be concatenated.


Knowing this we can tell the command with expanded variables looks somewhat like this:



<"./server-user-setup.sh" ssh root@"ip" -i "key" 'su www -c "exec sh -s -- Xb-userX Xa-userX"'


where X denotes (only for us, here and now) a single quote character that belongs to the string that starts with su and ends with the last ". Sorry, I couldn't express this in a clearer way. Hopefully it will be less cryptic when we see what ssh gets:



root@ip, -i, key, su www -c "exec sh -s -- 'b-user' 'a-user'"



The last argument contains double quotes and single quotes. This is exactly the string ssh will run on the remote side. Note I took care to pass the whole string as one argument to ssh, so the tool doesn't build a string from multiple arguments and spaces; this way I'm in full control over the string. In general this would be important e.g. if ${BB_USER} expanded to something with a trailing space (the original snippet would fail in this case).



The command that runs on the remote side:



su www -c "exec sh -s -- 'b-user' 'a-user'"


The behavior of su has already been discussed. Note the whole quoted string is an option-argument to -c. If not the quote, -s would be an option to su. This is what runs as www user:



some-shell -c "exec sh -s -- 'b-user' 'a-user'"


Note the quotes in su invocation also cause there is no command_name and no argument(s) (compare some-shell -c command_string command_name argument somewhere above). Then some-shell just runs:



exec sh -s -- 'b-user' 'a-user'


This would run without exec, but since we don't need some-shell anymore, exec may be a good idea. It makes sh replace some-shell, so instead of some-shell and sh as its child, only sh remains, as if when we run some-shell we had



sh -s -- 'b-user' 'a-user'


Now this is very similar to what the original snippet runs (sh -s -- b-user a-user). Thanks to additional quotes, possible spaces or few other troublesome characters from ${BB_USER} or ${APP_USER} may not break anything. However ' or " will. Code injection is still possible. Consider ${APP_USER} expanding to "& rogue_command;". The first thing to run on the remote side will be:



su www -c "exec sh -s -- 'b-user' '"& rogue_command;"'"


It doesn't matter some-shell will throw an error, rogue_command will run anyway. Make sure your variables expand to safe strings.






share|improve this answer

























    Your Answer








    StackExchange.ready(function() {
    var channelOptions = {
    tags: "".split(" "),
    id: "3"
    };
    initTagRenderer("".split(" "), "".split(" "), channelOptions);

    StackExchange.using("externalEditor", function() {
    // Have to fire editor after snippets, if snippets enabled
    if (StackExchange.settings.snippets.snippetsEnabled) {
    StackExchange.using("snippets", function() {
    createEditor();
    });
    }
    else {
    createEditor();
    }
    });

    function createEditor() {
    StackExchange.prepareEditor({
    heartbeatType: 'answer',
    autoActivateHeartbeat: false,
    convertImagesToLinks: true,
    noModals: true,
    showLowRepImageUploadWarning: true,
    reputationToPostImages: 10,
    bindNavPrevention: true,
    postfix: "",
    imageUploader: {
    brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
    contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
    allowUrls: true
    },
    onDemand: true,
    discardSelector: ".discard-answer"
    ,immediatelyShowMarkdownHelp:true
    });


    }
    });














    draft saved

    draft discarded


















    StackExchange.ready(
    function () {
    StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fsuperuser.com%2fquestions%2f1400663%2fsu-another-user-through-ssh-with-a-local-script%23new-answer', 'question_page');
    }
    );

    Post as a guest















    Required, but never shown

























    1 Answer
    1






    active

    oldest

    votes








    1 Answer
    1






    active

    oldest

    votes









    active

    oldest

    votes






    active

    oldest

    votes









    1














    tl;dr



    ssh root@"${DO_DROPLET_IP}" -i "${SSH_PRIVKEY_PATH}" 
    'su www -c "exec sh -s -- '"'${BB_USER}' '${APP_USER}'"'"' <"./server-user-setup.sh"


    Code injection possible via BB_USER and APP_USER variables.





    Long answer



    Let's analyze what happens. It's quite complex. For clarity let's assume:



    DO_DROPLET_IP=ip
    SSH_PRIVKEY_PATH=key
    BB_USER=b-user
    APP_USER=a-user




    Original (working) snippet




    ssh root@"${DO_DROPLET_IP}" -i "${SSH_PRIVKEY_PATH}" sh -s -- < 
    "./server-root-setup.sh" "${BB_USER}" "${APP_USER}"



    All variables are expanded locally. The redirection is local and it doesn't matter it's in the middle. With our assumed variable values this is the equivalent command:



    <"./server-root-setup.sh" ssh root@"ip" -i "key" sh -s -- "b-user" "a-user"


    ssh gets these arguments: root@ip, -i, key, sh, -s, --, b-user, a-user. Since root@ip looks like user@hostname, the first following argument not recognized as option (like -i) or option-argument (like key being an option-argument to -i) is considered to start an array of operands to build the remote command from. The said argument is sh and this is the command ssh tries to run on the remote side:



    sh -s -- b-user a-user


    This starts sh which expects commands from its stdin (due to -s). -- is a POSIX convention that tells the tool anything that follows is not an option (see this, guideline 10). This way even if our ${BB_USER} expanded to -f or so, sh wouldn't consider it an option. The following operands are set as the positional parameters of the shell ($1, $2). Stdin is provided by ssh, the local file ./server-root-setup.sh is streamed through.



    If the file and the relevant two variables were available on the remote side, you could get (almost) the same result with the following command there:



    sh "./server-root-setup.sh" ${BB_USER} ${APP_USER}


    Or if there's a proper shebang in the script:



    ./server-root-setup.sh ${BB_USER} ${APP_USER}


    So the snippet is indeed a way to run a local script on the remote side. But note I deliberately didn't quote ${BB_USER} or ${APP_USER}. In the original snipped they are quoted on the local side, but it doesn't matter because ssh builds and passes the command as a string to be parsed on the remote side. If any of the two variables contained inner spaces, the remote sh would get more arguments than you expected; leading or trailing spaces would be lost. Characters like $, ", ' or would cause trouble, unless the variable values were deliberately designed to be parsed (but then they couldn't be used locally to produce an equivalent result, that's why I wrote "almost").



    Worse things will happen if e.g. ${APP_USER} expands to a-user& rogue_command. On the remote side you get



    sh -s -- b-user a-user& rogue_command


    Note such code injection cannot happen when you run things locally (with or without quotes)



    ./server-root-setup.sh ${BB_USER} ${APP_USER}


    because separators like & and ; are recognized before variables are expanded, not after. But if you expand variables locally and then the resulting string is parsed again on the remote side, this is a whole different story. It's like remote eval.



    I guess ${BB_USER} and ${APP_USER} are usernames, quite safe strings, so the whole thing works despite possible general issues. But if these variables are not fully controlled by you, the approach is not safe.





    Your (failed) attempt




    ssh root@"${DO_DROPLET_IP}" -i "${SSH_PRIVKEY_PATH}" su www -c sh -c -- 
    "./server-user-setup.sh" "${BB_USER}" "${APP_USER}"



    No redirection this time. Again the variables are expanded locally and the equivalent command looks like this:



    ssh root@"ip" -i "key" su www -c sh -c -- "./server-user-setup.sh" "b-user" "a-user"


    ssh tries to run this on the remote side:



    su www -c sh -c -- ./server-user-setup.sh b-user a-user


    Note all arguments are now arguments to su. At this moment sh is just an option-argument to -c option, similarly -- is an option-argument to another -c option (so it doesn't indicate end of options here). My tests show that from multiple -c options to su only the last one takes effect. This means -c sh is discarded, su uses whatever shell is specified in the (remote) /etc/passwd for the www user. This may or may not be sh. Let's say it's some-shell. This is what's run next (as www user):



    some-shell -c --   # but wait, it's not all


    with remaining operands of su, so



    some-shell -c -- ./server-user-setup.sh b-user a-user


    If some-shell is a common shell like sh or bash, it behaves alike. Here -c doesn't take an option-argument (see the specification), so -- does indicate end of options. In this case no following argument can be taken as an option, so we can omit --:



    some-shell -c ./server-user-setup.sh b-user a-user


    This is like




    sh -c [-abCefhimnuvx] [-o option]... [+abCefhimnuvx] [+o option]... command_string [command_name [argument...]]



    or (discarding everything we don't use)



    some-shell -c command_string command_name argument


    So ./server-user-setup.sh is command_string, b-user is command_name and a-user is argument.




    -c

    Read commands from the command_string operand. Set the value of special parameter 0 […] from the value of the command_name operand and the positional parameters ($1, $2, and so on) in sequence from the remaining argument operands. […]




    In effect the remote some-shell tries to read (source) ./server-user-setup.sh, with the string a-user available as $1. Special parameter 0 (now with the value b-user) is what the shell considers to be its own name. The name is used in error messages like this one:




    b-user: ./server-user-setup.sh: Permission denied



    Apparently there is ./server-user-setup.sh on the remote side but www user cannot read it.





    My approach (still unsafe):



    ssh root@"${DO_DROPLET_IP}" -i "${SSH_PRIVKEY_PATH}" 
    'su www -c "exec sh -s -- '"'${BB_USER}' '${APP_USER}'"'"' <"./server-user-setup.sh"
    # 1 12 23 3


    The last line (a comment) is just to enumerate quotes in the previous line. To understand how the command works we need to "decrypt" this quoting frenzy. The important things are:




    • Everything inside 1 and 3 single quotes should be taken literally.


    • ${BB_USER} and ${APP_USER} are in double quotes 2; "inner" single quotes belong to the quoted string, they don't prevent the variables from expanding.

    • Wherever a closing quote immediately precedes an opening quote (e.g. the quotes above 12), the quoted strings will be concatenated.


    Knowing this we can tell the command with expanded variables looks somewhat like this:



    <"./server-user-setup.sh" ssh root@"ip" -i "key" 'su www -c "exec sh -s -- Xb-userX Xa-userX"'


    where X denotes (only for us, here and now) a single quote character that belongs to the string that starts with su and ends with the last ". Sorry, I couldn't express this in a clearer way. Hopefully it will be less cryptic when we see what ssh gets:



    root@ip, -i, key, su www -c "exec sh -s -- 'b-user' 'a-user'"



    The last argument contains double quotes and single quotes. This is exactly the string ssh will run on the remote side. Note I took care to pass the whole string as one argument to ssh, so the tool doesn't build a string from multiple arguments and spaces; this way I'm in full control over the string. In general this would be important e.g. if ${BB_USER} expanded to something with a trailing space (the original snippet would fail in this case).



    The command that runs on the remote side:



    su www -c "exec sh -s -- 'b-user' 'a-user'"


    The behavior of su has already been discussed. Note the whole quoted string is an option-argument to -c. If not the quote, -s would be an option to su. This is what runs as www user:



    some-shell -c "exec sh -s -- 'b-user' 'a-user'"


    Note the quotes in su invocation also cause there is no command_name and no argument(s) (compare some-shell -c command_string command_name argument somewhere above). Then some-shell just runs:



    exec sh -s -- 'b-user' 'a-user'


    This would run without exec, but since we don't need some-shell anymore, exec may be a good idea. It makes sh replace some-shell, so instead of some-shell and sh as its child, only sh remains, as if when we run some-shell we had



    sh -s -- 'b-user' 'a-user'


    Now this is very similar to what the original snippet runs (sh -s -- b-user a-user). Thanks to additional quotes, possible spaces or few other troublesome characters from ${BB_USER} or ${APP_USER} may not break anything. However ' or " will. Code injection is still possible. Consider ${APP_USER} expanding to "& rogue_command;". The first thing to run on the remote side will be:



    su www -c "exec sh -s -- 'b-user' '"& rogue_command;"'"


    It doesn't matter some-shell will throw an error, rogue_command will run anyway. Make sure your variables expand to safe strings.






    share|improve this answer






























      1














      tl;dr



      ssh root@"${DO_DROPLET_IP}" -i "${SSH_PRIVKEY_PATH}" 
      'su www -c "exec sh -s -- '"'${BB_USER}' '${APP_USER}'"'"' <"./server-user-setup.sh"


      Code injection possible via BB_USER and APP_USER variables.





      Long answer



      Let's analyze what happens. It's quite complex. For clarity let's assume:



      DO_DROPLET_IP=ip
      SSH_PRIVKEY_PATH=key
      BB_USER=b-user
      APP_USER=a-user




      Original (working) snippet




      ssh root@"${DO_DROPLET_IP}" -i "${SSH_PRIVKEY_PATH}" sh -s -- < 
      "./server-root-setup.sh" "${BB_USER}" "${APP_USER}"



      All variables are expanded locally. The redirection is local and it doesn't matter it's in the middle. With our assumed variable values this is the equivalent command:



      <"./server-root-setup.sh" ssh root@"ip" -i "key" sh -s -- "b-user" "a-user"


      ssh gets these arguments: root@ip, -i, key, sh, -s, --, b-user, a-user. Since root@ip looks like user@hostname, the first following argument not recognized as option (like -i) or option-argument (like key being an option-argument to -i) is considered to start an array of operands to build the remote command from. The said argument is sh and this is the command ssh tries to run on the remote side:



      sh -s -- b-user a-user


      This starts sh which expects commands from its stdin (due to -s). -- is a POSIX convention that tells the tool anything that follows is not an option (see this, guideline 10). This way even if our ${BB_USER} expanded to -f or so, sh wouldn't consider it an option. The following operands are set as the positional parameters of the shell ($1, $2). Stdin is provided by ssh, the local file ./server-root-setup.sh is streamed through.



      If the file and the relevant two variables were available on the remote side, you could get (almost) the same result with the following command there:



      sh "./server-root-setup.sh" ${BB_USER} ${APP_USER}


      Or if there's a proper shebang in the script:



      ./server-root-setup.sh ${BB_USER} ${APP_USER}


      So the snippet is indeed a way to run a local script on the remote side. But note I deliberately didn't quote ${BB_USER} or ${APP_USER}. In the original snipped they are quoted on the local side, but it doesn't matter because ssh builds and passes the command as a string to be parsed on the remote side. If any of the two variables contained inner spaces, the remote sh would get more arguments than you expected; leading or trailing spaces would be lost. Characters like $, ", ' or would cause trouble, unless the variable values were deliberately designed to be parsed (but then they couldn't be used locally to produce an equivalent result, that's why I wrote "almost").



      Worse things will happen if e.g. ${APP_USER} expands to a-user& rogue_command. On the remote side you get



      sh -s -- b-user a-user& rogue_command


      Note such code injection cannot happen when you run things locally (with or without quotes)



      ./server-root-setup.sh ${BB_USER} ${APP_USER}


      because separators like & and ; are recognized before variables are expanded, not after. But if you expand variables locally and then the resulting string is parsed again on the remote side, this is a whole different story. It's like remote eval.



      I guess ${BB_USER} and ${APP_USER} are usernames, quite safe strings, so the whole thing works despite possible general issues. But if these variables are not fully controlled by you, the approach is not safe.





      Your (failed) attempt




      ssh root@"${DO_DROPLET_IP}" -i "${SSH_PRIVKEY_PATH}" su www -c sh -c -- 
      "./server-user-setup.sh" "${BB_USER}" "${APP_USER}"



      No redirection this time. Again the variables are expanded locally and the equivalent command looks like this:



      ssh root@"ip" -i "key" su www -c sh -c -- "./server-user-setup.sh" "b-user" "a-user"


      ssh tries to run this on the remote side:



      su www -c sh -c -- ./server-user-setup.sh b-user a-user


      Note all arguments are now arguments to su. At this moment sh is just an option-argument to -c option, similarly -- is an option-argument to another -c option (so it doesn't indicate end of options here). My tests show that from multiple -c options to su only the last one takes effect. This means -c sh is discarded, su uses whatever shell is specified in the (remote) /etc/passwd for the www user. This may or may not be sh. Let's say it's some-shell. This is what's run next (as www user):



      some-shell -c --   # but wait, it's not all


      with remaining operands of su, so



      some-shell -c -- ./server-user-setup.sh b-user a-user


      If some-shell is a common shell like sh or bash, it behaves alike. Here -c doesn't take an option-argument (see the specification), so -- does indicate end of options. In this case no following argument can be taken as an option, so we can omit --:



      some-shell -c ./server-user-setup.sh b-user a-user


      This is like




      sh -c [-abCefhimnuvx] [-o option]... [+abCefhimnuvx] [+o option]... command_string [command_name [argument...]]



      or (discarding everything we don't use)



      some-shell -c command_string command_name argument


      So ./server-user-setup.sh is command_string, b-user is command_name and a-user is argument.




      -c

      Read commands from the command_string operand. Set the value of special parameter 0 […] from the value of the command_name operand and the positional parameters ($1, $2, and so on) in sequence from the remaining argument operands. […]




      In effect the remote some-shell tries to read (source) ./server-user-setup.sh, with the string a-user available as $1. Special parameter 0 (now with the value b-user) is what the shell considers to be its own name. The name is used in error messages like this one:




      b-user: ./server-user-setup.sh: Permission denied



      Apparently there is ./server-user-setup.sh on the remote side but www user cannot read it.





      My approach (still unsafe):



      ssh root@"${DO_DROPLET_IP}" -i "${SSH_PRIVKEY_PATH}" 
      'su www -c "exec sh -s -- '"'${BB_USER}' '${APP_USER}'"'"' <"./server-user-setup.sh"
      # 1 12 23 3


      The last line (a comment) is just to enumerate quotes in the previous line. To understand how the command works we need to "decrypt" this quoting frenzy. The important things are:




      • Everything inside 1 and 3 single quotes should be taken literally.


      • ${BB_USER} and ${APP_USER} are in double quotes 2; "inner" single quotes belong to the quoted string, they don't prevent the variables from expanding.

      • Wherever a closing quote immediately precedes an opening quote (e.g. the quotes above 12), the quoted strings will be concatenated.


      Knowing this we can tell the command with expanded variables looks somewhat like this:



      <"./server-user-setup.sh" ssh root@"ip" -i "key" 'su www -c "exec sh -s -- Xb-userX Xa-userX"'


      where X denotes (only for us, here and now) a single quote character that belongs to the string that starts with su and ends with the last ". Sorry, I couldn't express this in a clearer way. Hopefully it will be less cryptic when we see what ssh gets:



      root@ip, -i, key, su www -c "exec sh -s -- 'b-user' 'a-user'"



      The last argument contains double quotes and single quotes. This is exactly the string ssh will run on the remote side. Note I took care to pass the whole string as one argument to ssh, so the tool doesn't build a string from multiple arguments and spaces; this way I'm in full control over the string. In general this would be important e.g. if ${BB_USER} expanded to something with a trailing space (the original snippet would fail in this case).



      The command that runs on the remote side:



      su www -c "exec sh -s -- 'b-user' 'a-user'"


      The behavior of su has already been discussed. Note the whole quoted string is an option-argument to -c. If not the quote, -s would be an option to su. This is what runs as www user:



      some-shell -c "exec sh -s -- 'b-user' 'a-user'"


      Note the quotes in su invocation also cause there is no command_name and no argument(s) (compare some-shell -c command_string command_name argument somewhere above). Then some-shell just runs:



      exec sh -s -- 'b-user' 'a-user'


      This would run without exec, but since we don't need some-shell anymore, exec may be a good idea. It makes sh replace some-shell, so instead of some-shell and sh as its child, only sh remains, as if when we run some-shell we had



      sh -s -- 'b-user' 'a-user'


      Now this is very similar to what the original snippet runs (sh -s -- b-user a-user). Thanks to additional quotes, possible spaces or few other troublesome characters from ${BB_USER} or ${APP_USER} may not break anything. However ' or " will. Code injection is still possible. Consider ${APP_USER} expanding to "& rogue_command;". The first thing to run on the remote side will be:



      su www -c "exec sh -s -- 'b-user' '"& rogue_command;"'"


      It doesn't matter some-shell will throw an error, rogue_command will run anyway. Make sure your variables expand to safe strings.






      share|improve this answer




























        1












        1








        1







        tl;dr



        ssh root@"${DO_DROPLET_IP}" -i "${SSH_PRIVKEY_PATH}" 
        'su www -c "exec sh -s -- '"'${BB_USER}' '${APP_USER}'"'"' <"./server-user-setup.sh"


        Code injection possible via BB_USER and APP_USER variables.





        Long answer



        Let's analyze what happens. It's quite complex. For clarity let's assume:



        DO_DROPLET_IP=ip
        SSH_PRIVKEY_PATH=key
        BB_USER=b-user
        APP_USER=a-user




        Original (working) snippet




        ssh root@"${DO_DROPLET_IP}" -i "${SSH_PRIVKEY_PATH}" sh -s -- < 
        "./server-root-setup.sh" "${BB_USER}" "${APP_USER}"



        All variables are expanded locally. The redirection is local and it doesn't matter it's in the middle. With our assumed variable values this is the equivalent command:



        <"./server-root-setup.sh" ssh root@"ip" -i "key" sh -s -- "b-user" "a-user"


        ssh gets these arguments: root@ip, -i, key, sh, -s, --, b-user, a-user. Since root@ip looks like user@hostname, the first following argument not recognized as option (like -i) or option-argument (like key being an option-argument to -i) is considered to start an array of operands to build the remote command from. The said argument is sh and this is the command ssh tries to run on the remote side:



        sh -s -- b-user a-user


        This starts sh which expects commands from its stdin (due to -s). -- is a POSIX convention that tells the tool anything that follows is not an option (see this, guideline 10). This way even if our ${BB_USER} expanded to -f or so, sh wouldn't consider it an option. The following operands are set as the positional parameters of the shell ($1, $2). Stdin is provided by ssh, the local file ./server-root-setup.sh is streamed through.



        If the file and the relevant two variables were available on the remote side, you could get (almost) the same result with the following command there:



        sh "./server-root-setup.sh" ${BB_USER} ${APP_USER}


        Or if there's a proper shebang in the script:



        ./server-root-setup.sh ${BB_USER} ${APP_USER}


        So the snippet is indeed a way to run a local script on the remote side. But note I deliberately didn't quote ${BB_USER} or ${APP_USER}. In the original snipped they are quoted on the local side, but it doesn't matter because ssh builds and passes the command as a string to be parsed on the remote side. If any of the two variables contained inner spaces, the remote sh would get more arguments than you expected; leading or trailing spaces would be lost. Characters like $, ", ' or would cause trouble, unless the variable values were deliberately designed to be parsed (but then they couldn't be used locally to produce an equivalent result, that's why I wrote "almost").



        Worse things will happen if e.g. ${APP_USER} expands to a-user& rogue_command. On the remote side you get



        sh -s -- b-user a-user& rogue_command


        Note such code injection cannot happen when you run things locally (with or without quotes)



        ./server-root-setup.sh ${BB_USER} ${APP_USER}


        because separators like & and ; are recognized before variables are expanded, not after. But if you expand variables locally and then the resulting string is parsed again on the remote side, this is a whole different story. It's like remote eval.



        I guess ${BB_USER} and ${APP_USER} are usernames, quite safe strings, so the whole thing works despite possible general issues. But if these variables are not fully controlled by you, the approach is not safe.





        Your (failed) attempt




        ssh root@"${DO_DROPLET_IP}" -i "${SSH_PRIVKEY_PATH}" su www -c sh -c -- 
        "./server-user-setup.sh" "${BB_USER}" "${APP_USER}"



        No redirection this time. Again the variables are expanded locally and the equivalent command looks like this:



        ssh root@"ip" -i "key" su www -c sh -c -- "./server-user-setup.sh" "b-user" "a-user"


        ssh tries to run this on the remote side:



        su www -c sh -c -- ./server-user-setup.sh b-user a-user


        Note all arguments are now arguments to su. At this moment sh is just an option-argument to -c option, similarly -- is an option-argument to another -c option (so it doesn't indicate end of options here). My tests show that from multiple -c options to su only the last one takes effect. This means -c sh is discarded, su uses whatever shell is specified in the (remote) /etc/passwd for the www user. This may or may not be sh. Let's say it's some-shell. This is what's run next (as www user):



        some-shell -c --   # but wait, it's not all


        with remaining operands of su, so



        some-shell -c -- ./server-user-setup.sh b-user a-user


        If some-shell is a common shell like sh or bash, it behaves alike. Here -c doesn't take an option-argument (see the specification), so -- does indicate end of options. In this case no following argument can be taken as an option, so we can omit --:



        some-shell -c ./server-user-setup.sh b-user a-user


        This is like




        sh -c [-abCefhimnuvx] [-o option]... [+abCefhimnuvx] [+o option]... command_string [command_name [argument...]]



        or (discarding everything we don't use)



        some-shell -c command_string command_name argument


        So ./server-user-setup.sh is command_string, b-user is command_name and a-user is argument.




        -c

        Read commands from the command_string operand. Set the value of special parameter 0 […] from the value of the command_name operand and the positional parameters ($1, $2, and so on) in sequence from the remaining argument operands. […]




        In effect the remote some-shell tries to read (source) ./server-user-setup.sh, with the string a-user available as $1. Special parameter 0 (now with the value b-user) is what the shell considers to be its own name. The name is used in error messages like this one:




        b-user: ./server-user-setup.sh: Permission denied



        Apparently there is ./server-user-setup.sh on the remote side but www user cannot read it.





        My approach (still unsafe):



        ssh root@"${DO_DROPLET_IP}" -i "${SSH_PRIVKEY_PATH}" 
        'su www -c "exec sh -s -- '"'${BB_USER}' '${APP_USER}'"'"' <"./server-user-setup.sh"
        # 1 12 23 3


        The last line (a comment) is just to enumerate quotes in the previous line. To understand how the command works we need to "decrypt" this quoting frenzy. The important things are:




        • Everything inside 1 and 3 single quotes should be taken literally.


        • ${BB_USER} and ${APP_USER} are in double quotes 2; "inner" single quotes belong to the quoted string, they don't prevent the variables from expanding.

        • Wherever a closing quote immediately precedes an opening quote (e.g. the quotes above 12), the quoted strings will be concatenated.


        Knowing this we can tell the command with expanded variables looks somewhat like this:



        <"./server-user-setup.sh" ssh root@"ip" -i "key" 'su www -c "exec sh -s -- Xb-userX Xa-userX"'


        where X denotes (only for us, here and now) a single quote character that belongs to the string that starts with su and ends with the last ". Sorry, I couldn't express this in a clearer way. Hopefully it will be less cryptic when we see what ssh gets:



        root@ip, -i, key, su www -c "exec sh -s -- 'b-user' 'a-user'"



        The last argument contains double quotes and single quotes. This is exactly the string ssh will run on the remote side. Note I took care to pass the whole string as one argument to ssh, so the tool doesn't build a string from multiple arguments and spaces; this way I'm in full control over the string. In general this would be important e.g. if ${BB_USER} expanded to something with a trailing space (the original snippet would fail in this case).



        The command that runs on the remote side:



        su www -c "exec sh -s -- 'b-user' 'a-user'"


        The behavior of su has already been discussed. Note the whole quoted string is an option-argument to -c. If not the quote, -s would be an option to su. This is what runs as www user:



        some-shell -c "exec sh -s -- 'b-user' 'a-user'"


        Note the quotes in su invocation also cause there is no command_name and no argument(s) (compare some-shell -c command_string command_name argument somewhere above). Then some-shell just runs:



        exec sh -s -- 'b-user' 'a-user'


        This would run without exec, but since we don't need some-shell anymore, exec may be a good idea. It makes sh replace some-shell, so instead of some-shell and sh as its child, only sh remains, as if when we run some-shell we had



        sh -s -- 'b-user' 'a-user'


        Now this is very similar to what the original snippet runs (sh -s -- b-user a-user). Thanks to additional quotes, possible spaces or few other troublesome characters from ${BB_USER} or ${APP_USER} may not break anything. However ' or " will. Code injection is still possible. Consider ${APP_USER} expanding to "& rogue_command;". The first thing to run on the remote side will be:



        su www -c "exec sh -s -- 'b-user' '"& rogue_command;"'"


        It doesn't matter some-shell will throw an error, rogue_command will run anyway. Make sure your variables expand to safe strings.






        share|improve this answer















        tl;dr



        ssh root@"${DO_DROPLET_IP}" -i "${SSH_PRIVKEY_PATH}" 
        'su www -c "exec sh -s -- '"'${BB_USER}' '${APP_USER}'"'"' <"./server-user-setup.sh"


        Code injection possible via BB_USER and APP_USER variables.





        Long answer



        Let's analyze what happens. It's quite complex. For clarity let's assume:



        DO_DROPLET_IP=ip
        SSH_PRIVKEY_PATH=key
        BB_USER=b-user
        APP_USER=a-user




        Original (working) snippet




        ssh root@"${DO_DROPLET_IP}" -i "${SSH_PRIVKEY_PATH}" sh -s -- < 
        "./server-root-setup.sh" "${BB_USER}" "${APP_USER}"



        All variables are expanded locally. The redirection is local and it doesn't matter it's in the middle. With our assumed variable values this is the equivalent command:



        <"./server-root-setup.sh" ssh root@"ip" -i "key" sh -s -- "b-user" "a-user"


        ssh gets these arguments: root@ip, -i, key, sh, -s, --, b-user, a-user. Since root@ip looks like user@hostname, the first following argument not recognized as option (like -i) or option-argument (like key being an option-argument to -i) is considered to start an array of operands to build the remote command from. The said argument is sh and this is the command ssh tries to run on the remote side:



        sh -s -- b-user a-user


        This starts sh which expects commands from its stdin (due to -s). -- is a POSIX convention that tells the tool anything that follows is not an option (see this, guideline 10). This way even if our ${BB_USER} expanded to -f or so, sh wouldn't consider it an option. The following operands are set as the positional parameters of the shell ($1, $2). Stdin is provided by ssh, the local file ./server-root-setup.sh is streamed through.



        If the file and the relevant two variables were available on the remote side, you could get (almost) the same result with the following command there:



        sh "./server-root-setup.sh" ${BB_USER} ${APP_USER}


        Or if there's a proper shebang in the script:



        ./server-root-setup.sh ${BB_USER} ${APP_USER}


        So the snippet is indeed a way to run a local script on the remote side. But note I deliberately didn't quote ${BB_USER} or ${APP_USER}. In the original snipped they are quoted on the local side, but it doesn't matter because ssh builds and passes the command as a string to be parsed on the remote side. If any of the two variables contained inner spaces, the remote sh would get more arguments than you expected; leading or trailing spaces would be lost. Characters like $, ", ' or would cause trouble, unless the variable values were deliberately designed to be parsed (but then they couldn't be used locally to produce an equivalent result, that's why I wrote "almost").



        Worse things will happen if e.g. ${APP_USER} expands to a-user& rogue_command. On the remote side you get



        sh -s -- b-user a-user& rogue_command


        Note such code injection cannot happen when you run things locally (with or without quotes)



        ./server-root-setup.sh ${BB_USER} ${APP_USER}


        because separators like & and ; are recognized before variables are expanded, not after. But if you expand variables locally and then the resulting string is parsed again on the remote side, this is a whole different story. It's like remote eval.



        I guess ${BB_USER} and ${APP_USER} are usernames, quite safe strings, so the whole thing works despite possible general issues. But if these variables are not fully controlled by you, the approach is not safe.





        Your (failed) attempt




        ssh root@"${DO_DROPLET_IP}" -i "${SSH_PRIVKEY_PATH}" su www -c sh -c -- 
        "./server-user-setup.sh" "${BB_USER}" "${APP_USER}"



        No redirection this time. Again the variables are expanded locally and the equivalent command looks like this:



        ssh root@"ip" -i "key" su www -c sh -c -- "./server-user-setup.sh" "b-user" "a-user"


        ssh tries to run this on the remote side:



        su www -c sh -c -- ./server-user-setup.sh b-user a-user


        Note all arguments are now arguments to su. At this moment sh is just an option-argument to -c option, similarly -- is an option-argument to another -c option (so it doesn't indicate end of options here). My tests show that from multiple -c options to su only the last one takes effect. This means -c sh is discarded, su uses whatever shell is specified in the (remote) /etc/passwd for the www user. This may or may not be sh. Let's say it's some-shell. This is what's run next (as www user):



        some-shell -c --   # but wait, it's not all


        with remaining operands of su, so



        some-shell -c -- ./server-user-setup.sh b-user a-user


        If some-shell is a common shell like sh or bash, it behaves alike. Here -c doesn't take an option-argument (see the specification), so -- does indicate end of options. In this case no following argument can be taken as an option, so we can omit --:



        some-shell -c ./server-user-setup.sh b-user a-user


        This is like




        sh -c [-abCefhimnuvx] [-o option]... [+abCefhimnuvx] [+o option]... command_string [command_name [argument...]]



        or (discarding everything we don't use)



        some-shell -c command_string command_name argument


        So ./server-user-setup.sh is command_string, b-user is command_name and a-user is argument.




        -c

        Read commands from the command_string operand. Set the value of special parameter 0 […] from the value of the command_name operand and the positional parameters ($1, $2, and so on) in sequence from the remaining argument operands. […]




        In effect the remote some-shell tries to read (source) ./server-user-setup.sh, with the string a-user available as $1. Special parameter 0 (now with the value b-user) is what the shell considers to be its own name. The name is used in error messages like this one:




        b-user: ./server-user-setup.sh: Permission denied



        Apparently there is ./server-user-setup.sh on the remote side but www user cannot read it.





        My approach (still unsafe):



        ssh root@"${DO_DROPLET_IP}" -i "${SSH_PRIVKEY_PATH}" 
        'su www -c "exec sh -s -- '"'${BB_USER}' '${APP_USER}'"'"' <"./server-user-setup.sh"
        # 1 12 23 3


        The last line (a comment) is just to enumerate quotes in the previous line. To understand how the command works we need to "decrypt" this quoting frenzy. The important things are:




        • Everything inside 1 and 3 single quotes should be taken literally.


        • ${BB_USER} and ${APP_USER} are in double quotes 2; "inner" single quotes belong to the quoted string, they don't prevent the variables from expanding.

        • Wherever a closing quote immediately precedes an opening quote (e.g. the quotes above 12), the quoted strings will be concatenated.


        Knowing this we can tell the command with expanded variables looks somewhat like this:



        <"./server-user-setup.sh" ssh root@"ip" -i "key" 'su www -c "exec sh -s -- Xb-userX Xa-userX"'


        where X denotes (only for us, here and now) a single quote character that belongs to the string that starts with su and ends with the last ". Sorry, I couldn't express this in a clearer way. Hopefully it will be less cryptic when we see what ssh gets:



        root@ip, -i, key, su www -c "exec sh -s -- 'b-user' 'a-user'"



        The last argument contains double quotes and single quotes. This is exactly the string ssh will run on the remote side. Note I took care to pass the whole string as one argument to ssh, so the tool doesn't build a string from multiple arguments and spaces; this way I'm in full control over the string. In general this would be important e.g. if ${BB_USER} expanded to something with a trailing space (the original snippet would fail in this case).



        The command that runs on the remote side:



        su www -c "exec sh -s -- 'b-user' 'a-user'"


        The behavior of su has already been discussed. Note the whole quoted string is an option-argument to -c. If not the quote, -s would be an option to su. This is what runs as www user:



        some-shell -c "exec sh -s -- 'b-user' 'a-user'"


        Note the quotes in su invocation also cause there is no command_name and no argument(s) (compare some-shell -c command_string command_name argument somewhere above). Then some-shell just runs:



        exec sh -s -- 'b-user' 'a-user'


        This would run without exec, but since we don't need some-shell anymore, exec may be a good idea. It makes sh replace some-shell, so instead of some-shell and sh as its child, only sh remains, as if when we run some-shell we had



        sh -s -- 'b-user' 'a-user'


        Now this is very similar to what the original snippet runs (sh -s -- b-user a-user). Thanks to additional quotes, possible spaces or few other troublesome characters from ${BB_USER} or ${APP_USER} may not break anything. However ' or " will. Code injection is still possible. Consider ${APP_USER} expanding to "& rogue_command;". The first thing to run on the remote side will be:



        su www -c "exec sh -s -- 'b-user' '"& rogue_command;"'"


        It doesn't matter some-shell will throw an error, rogue_command will run anyway. Make sure your variables expand to safe strings.







        share|improve this answer














        share|improve this answer



        share|improve this answer








        edited Jan 31 at 23:43

























        answered Jan 31 at 23:30









        Kamil MaciorowskiKamil Maciorowski

        28.7k156187




        28.7k156187






























            draft saved

            draft discarded




















































            Thanks for contributing an answer to Super User!


            • Please be sure to answer the question. Provide details and share your research!

            But avoid



            • Asking for help, clarification, or responding to other answers.

            • Making statements based on opinion; back them up with references or personal experience.


            To learn more, see our tips on writing great answers.




            draft saved


            draft discarded














            StackExchange.ready(
            function () {
            StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fsuperuser.com%2fquestions%2f1400663%2fsu-another-user-through-ssh-with-a-local-script%23new-answer', 'question_page');
            }
            );

            Post as a guest















            Required, but never shown





















































            Required, but never shown














            Required, but never shown












            Required, but never shown







            Required, but never shown

































            Required, but never shown














            Required, but never shown












            Required, but never shown







            Required, but never shown







            Popular posts from this blog

            Сан-Квентин

            8-я гвардейская общевойсковая армия

            Алькесар