1local stdnse = require "stdnse"
2local shortport = require "shortport"
3local stringaux = require "stringaux"
4local table = require "table"
5local libssh2_util = require "libssh2-utility"
6
7description = [[
8Runs remote command on ssh server and returns command output.
9]]
10
11---
12-- @usage nmap -p 22 --script=ssh-run \
13-- --script-args="ssh-run.cmd=ls -l /, ssh-run.username=myusername, ssh-run.password=mypassword" <target>
14--
15-- @output
16-- 22/tcp open  ssh
17-- | ssh-run:
18-- |   output:
19-- |     total 91
20-- |     drwxr-xr-x   2 root root  4096 Jun  5 11:56 bin
21-- |     drwxr-xr-x   4 root root  3072 Jun  5 12:42 boot
22-- |     drwxrwxr-x   2 root root  4096 Jun 22  2017 cdrom
23-- |     drwxr-xr-x  20 root root  4060 Jun 23 10:26 dev
24-- |     drwxr-xr-x 127 root root 12288 Jun  5 11:56 etc
25-- |     drwxr-xr-x   3 root root  4096 Jun 22  2017 home
26-- ....
27-- |_    drwxr-xr-x  13 root root  4096 Jul 20  2016 var
28--
29-- @xmloutput
30-- <elem key="output">total 91\x0D&#xa;drwxr-xr-x   2 root root  4096 Jun  5 11:56 bin\x0D&#xa;drwxr-xr-x   4 root root  3072 Jun  5 12:42 boot\x0D&#xa;drwxrwxr-x   2 root root  4096 Jun 22  2017 cdrom\x0D&#xa;drwxr-xr-x  20 root root  4060 Jun 23 10:26 dev\x0D&#xa;drwxr-xr-x 127 root root 12288 Jun  5 11:56 etc\x0D&#xa;drwxr-xr-x   3 root root  4096 Jun 22  2017 home\x0D&#xa;....\x0D&#xa;drwxr-xr-x  13 root root  4096 Jul 20  2016 var\x0D&#xa;</elem>
31--
32-- @args ssh-run.username    Username to authenticate as
33-- @args ssh-run.password    Password to use if using password authentication
34-- @args ssh-run.privatekey    Privatekeyfile to use if using publickey authentication
35-- @args ssh-run.passphrase    Passphrase for privatekey if using publickey authentication
36-- @args ssh-run.cmd   Command to run on remote server
37
38
39author = "Devin Bjelland"
40license = "Same as Nmap--See https://nmap.org/book/man-legal.html"
41
42categories = {
43  'intrusive',
44}
45
46portrule = shortport.ssh
47
48local username = stdnse.get_script_args 'ssh-run.username'
49local cmd = stdnse.get_script_args 'ssh-run.cmd'
50local password = stdnse.get_script_args 'ssh-run.password'
51local privatekey = stdnse.get_script_args 'ssh-run.privatekey'
52local passphrase = stdnse.get_script_args 'ssh-run.passphrase'
53
54local function remove_tabs (str, tabsize)
55tabsize = tabsize or 8
56local out = str:gsub("(.-)\t", function (s)
57                                 return s .. (" "):rep(tabsize - #s % tabsize)
58                               end)
59  return out
60end
61
62function action (host, port)
63  local conn = libssh2_util.SSHConnection:new()
64  if not conn:connect(host, port) then
65    return "Failed to connect to ssh server"
66  end
67  if username and password and cmd then
68    if not conn:password_auth(username, password) then
69      conn:disconnect()
70      stdnse.verbose "Failed to authenticate"
71      return "Authentication Failed"
72    else
73      stdnse.verbose "Authenticated"
74    end
75  elseif username and privatekey and cmd then
76    if not conn:publickey_auth(username, privatekey, passphrase) then
77      conn:disconnect()
78      stdnse.verbose "Failed to authenticate"
79      return "Authentication Failed"
80    else
81      stdnse.verbose "Authenticated"
82    end
83
84  else
85    stdnse.verbose "Failed to specify credentials and command to run."
86    return "Failed to specify credentials and command to run."
87  end
88  stdnse.verbose("Running command: " .. cmd)
89  local output, err_output = conn:run_remote(cmd)
90  stdnse.verbose("Output of command: " .. output)
91
92  local out = stdnse.output_table()
93  out.output = output
94
95  local txtout = {}
96  for _, line in ipairs(stringaux.strsplit("\r?\n", output:gsub("\r?\n$", ""))) do
97    local str = line:gsub("[^\t\x20-\x7f]", "")
98    table.insert(txtout, remove_tabs(str))
99  end
100  txtout.name = "output:"
101
102  return out, stdnse.format_output(true, {txtout})
103end
104