From 1be9ce864970f270d90ad4fe014b46fef55cd7f4 Mon Sep 17 00:00:00 2001 From: James Lee Date: Thu, 7 Jun 2012 22:24:59 -0600 Subject: [PATCH] Fixes command parsing in Post::Common The meterpreter API wants arguments in a seperate string (not an array, mind you) just so it can concatenate them on the server side. Originally, I worked around that by using Shellwords.shellwords to pull out the first token. But! Shellwords.shellwords inexplicably and inexcusably removes backslashes in ways that make it impossible to quote things on Windows. This commit works around both of those things. --- data/meterpreter/ext_server_stdapi.jar | Bin 38747 -> 38719 bytes .../stdapi/stdapi_sys_process_execute.java | 19 +++--- lib/msf/core/post/common.rb | 64 +++++++++++++++--- 3 files changed, 65 insertions(+), 18 deletions(-) diff --git a/data/meterpreter/ext_server_stdapi.jar b/data/meterpreter/ext_server_stdapi.jar index bfa2a9d4b5d0f63dfacf05757abbe5966da82487..fa4c7814311c2c25ffd87e51ca2a6c9492530903 100755 GIT binary patch delta 1326 zcmV+}1=0H3t^&WV0%s`|LUAo^x*gdi?8eu%I75hA;|f%eW#UiK`;s#d{okpC8vmq&RsUGC$@8qy=OIC=A_G)02_t%uFhJ zB^tRj8%>SJM=qoy7e^9_Xgqc`I>x|a42ORsnp)OnRiBgd6~zMJ5QC+d%P@?l^K!Y| zlhz771x1(3#k`i&IjWS3C4Tml^^9E1)j_JfR!$X5T3RWWQ_8B6uILm-dwfY=mV2{{m}dzV&1)5o=nn%Rkq;Z#E2w0Y|f>jA?+|x^VmBH7diuF1~7G+gc zYM=g4!T^E{Lu573uo^H~4H!K%@c*qOA%w6*6X}=m5pD|jSi&u=3iw3Ar?@TPGYOw# zP{J3uE#XUiCE#lT-$?it-%0qMM?KUcYU9JfR;_%rQMt+aB_*vh_*-}+Q9*xJGr@=f z8ICpVO;>1eb6PMqY2vo+x_cUl1+A2j3rdrp*~OB!BG2cED`pjag5s64M09w4d)tno zbH8S!sp~n&P%s>!PZ(CUWx@T$_We$;H`coAcQjq(GFD$cH2vg{CeJPxiwdt;;0eQH zZs2g!1sM`@l~>fPzF1o?P!n~^-&;Yo-Hlj z_B|er;>Ofsg;!XS3k=7%?~wOKLl8E%j9K57&TD1Ga8#TAZ9y9`CM&v0^@Pc_Swg-$ z_Tx82JY7-sT%q1^J49zw$JN>7&&tOKr|Dtq1%sC!&y(=MPk0}HYY=~+Cz58Ga0g!! z#D5`2!s+}O?T>l2_ztj{MZK9XS0T=%RD{U>{?=1+)4LCy9fZJJx7_tT2 zF1J+0;gCJxK2k;7dBJ1#*s3^s*<-Jw{U^AHw%2Ilw*Ls5^`75jt-@W!v5=!vu-&?Y zYg_0@_IVr|=nOd@pzD86w7HLO;zaTxy4GPQPBJ9^{s129^@s5EyH65Y#Z&$6rwzm- z{;>{M$VJfOderZJ#zVoKBEdSz992A9MK_11>+nn!&spwKZZ~lUcPX#C__+pw^Ym|} z++U%SHgP@wE`t2&7B|t;SH;R8oCE*>Dw9K$fdVpblR-5rlYMFv zlLVDD0_1R$K{YFreQFGoQ*jfM>5&kVm6aL+I+L)KaRM`Uld+XRldE?NlWvwO0{?rH kK{X_kzLrA*?tGI$H6)Y1mJ^d*eG8LCmk delta 1371 zcmV-h1*H1Ft^(Vx0l;9C9;*YZ!e zs{3RDgo|0^;p~0(`Sw2h?AzqeXTLmw3tjk5#s$17;w=&VGA<${lXyUeA7K}m^i0Me zE)jp{VG=I$O+>`oG8B$c@Q#Qmr$a8(c)*WiG8!-};|xYbyeq?pQ4ek662`dj3g3(q zTKb2h-y^POffk&VaTOCXuHm|j_wfPeZtx``W0G$*Oz|ZtA|*l>G0kv%WOO(bj>i+> ztKraI$Xo0k5 z&ICf-UN3_uzHHjFx@{yGYWob+urJV^{-IiTZ4?LO@&_^lUfr?r7pj9wan~pIY+k(Wb5@6{B z&d^pRvgOc(W;N5))7+6|1p&Oo&_`O`TUy-}THQ`vb^m{PDdPwD@&gsfXd0^4Rp!(YP zVD1qO)W%&Fod8A5Y=K8f*D?&v8+qXQ-8$&4OnW8XolIMK-7!>Z>Wza25)2pY3h0pv z)0HP=yJ;`^HjhUs=tiboHJcmXwyG(M$)3Pl04L~!OXCesDn1cXKf$MpK1rD5po8(d5?lHA z?@n)H5#An26^c0YBiw(&y>^xCHw8UD$$jew+*pTiqC@qp;&4!Yh~_`g;61Vib>b14 zS5QlmiD2Dt578n_K0-^E_b9PNJlExY-eJ_b-&e>-g{Z1L?eZQ|$>$g7U zZ|o!qqD&h%xC8(I9g`=NfdWo%lR-5rlYMFvlZ 0) { + cmdbuf.append(argsString); + } + + if (cmd.length() == 0) return ERROR_FAILURE; - Process proc = execute(cmdarray); + Process proc = execute(cmdbuf.toString()); if ((flags & PROCESS_EXECUTE_FLAG_CHANNELIZED) != 0) { ProcessChannel channel = new ProcessChannel(meterpreter, proc); @@ -47,8 +48,8 @@ public class stdapi_sys_process_execute implements Command { return ERROR_SUCCESS; } - protected Process execute(String[] cmdarray) throws IOException { - Process proc = Runtime.getRuntime().exec(cmdarray); + protected Process execute(String cmdstr) throws IOException { + Process proc = Runtime.getRuntime().exec(cmdstr); return proc; } } diff --git a/lib/msf/core/post/common.rb b/lib/msf/core/post/common.rb index a7a7a8c7cd..d7ea9635de 100644 --- a/lib/msf/core/post/common.rb +++ b/lib/msf/core/post/common.rb @@ -3,18 +3,64 @@ class Post module Common - # Execute given command as hidden and channelize, output of command given as a multiline string. - # For certain versions of Meterpreter options can not be included in the cmd var - def cmd_exec(cmd, opts=nil, time_out=15) + # + # Executes +cmd+ on the remote system + # + # On Windows meterpreter, this will go through CreateProcess as the + # "commandLine" parameter. This means it will follow the same rules as + # Windows' path disambiguation. For example, if you were to call this method + # thusly: + # + # cmd_exec("c:\\program files\\sub dir\\program name") + # + # Windows would look for these executables, in this order, passing the rest + # of the line as arguments: + # + # c:\program.exe + # c:\program files\sub.exe + # c:\program files\sub dir\program.exe + # c:\program files\sub dir\program name.exe + # + # On POSIX meterpreter, if +args+ is set or if +cmd+ contains shell + # metacharacters, the server will run the whole thing in /bin/sh. Otherwise, + # (cmd is a single path and there are no arguments), it will execve the given + # executable. + # + # On Java, it is passed through Runtime.getRuntime().exec(String) and PHP + # uses proc_open() both of which have similar semantics to POSIX. + # + # On shell sessions, this passes +cmd+ directly the session's + # +shell_command_token+ method. + # + # Returns a (possibly multi-line) String. + # + def cmd_exec(cmd, args=nil, time_out=15) case session.type when /meterpreter/ - if opts.nil? and cmd =~ /\s*/ - opts = Shellwords.shellwords(cmd) - cmd = opts.shift - opts = opts.join(" ") + # + # The meterpreter API requires arguments to come seperately from the + # executable path. This has no effect on Windows where the two are just + # blithely concatenated and passed to CreateProcess or its brethren. On + # POSIX, this allows the server to execve just the executable when a + # shell is not needed. Determining when a shell is not needed is not + # always easy, so it assumes anything with arguments needs to go through + # /bin/sh. + # + # This problem was originally solved by using Shellwords.shellwords but + # unfortunately, it is retarded. When a backslash occurs inside double + # quotes (as is often the case with Windows commands) it inexplicably + # removes them. So. Shellwords is out. + # + # By setting +args+ to an empty string, we can get POSIX to send it + # through /bin/sh, solving all the pesky parsing troubles, without + # affecting Windows. + # + if args.nil? and cmd =~ /[^a-zA-Z0-9\/._-]/ + args = "" end + session.response_timeout = time_out - process = session.sys.process.execute(cmd, opts, {'Hidden' => true, 'Channelized' => true}) + process = session.sys.process.execute(cmd, args, {'Hidden' => true, 'Channelized' => true}) o = "" while (d = process.channel.read) break if d == "" @@ -23,7 +69,7 @@ module Common process.channel.close process.close when /shell/ - o = session.shell_command_token("#{cmd} #{opts}", time_out) + o = session.shell_command_token("#{cmd} #{args}", time_out) o.chomp! if o end return "" if o.nil?