summaryrefslogtreecommitdiffstats
path: root/scripts/ftp-bounce.nse
blob: cb5476f98cc2f5c6741adc5c37780840a4988bf4 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
local nmap = require "nmap"
local shortport = require "shortport"
local stdnse = require "stdnse"
local string = require "string"
local ftp = require "ftp"

description=[[
Checks to see if an FTP server allows port scanning using the FTP bounce method.
]]
author = "Marek Majkowski"
license = "Same as Nmap--See https://nmap.org/book/man-legal.html"

---
-- @args ftp-bounce.username Username to log in with. Default
-- <code>anonymous</code>.
-- @args ftp-bounce.password Password to log in with. Default
-- <code>IEUser@</code>.
-- @args ftp-bounce.checkhost Host to try connecting to with the PORT command.
--                            Default: scanme.nmap.org
--
-- @output
-- PORT   STATE SERVICE
-- 21/tcp open  ftp
-- |_ftp-bounce: bounce working!
--
-- PORT   STATE SERVICE
-- 21/tcp open  ftp
-- |_ftp-bounce: server forbids bouncing to low ports <1025

categories = {"default", "safe"}

portrule = shortport.port_or_service({21, 990}, {"ftp", "ftps"})

local function get_portfmt()
  local arghost = stdnse.get_script_args(SCRIPT_NAME .. ".checkhost") or "scanme.nmap.org"
  local reg = nmap.registry[SCRIPT_NAME] or {}
  local addr = reg[arghost]
  if not addr then
    local status, addrs = nmap.resolve(arghost, "inet")
    if not status or #addrs < 1 then
      stdnse.verbose1("Couldn't resolve %s, scanning 10.0.0.1 instead.", arghost)
      addr = "10.0.0.1"
    else
      addr = addrs[1]
    end
    reg[arghost] = addr
  end
  nmap.registry[SCRIPT_NAME] = reg
  return string.format("PORT %s,%%s\r\n", (string.gsub(addr, "%.", ",")))
end

action = function(host, port)
  local user = stdnse.get_script_args(SCRIPT_NAME .. ".username") or "anonymous"
  local pass = stdnse.get_script_args(SCRIPT_NAME .. ".password") or "IEUser@"

  -- BANNER
  local socket, code, message, buffer = ftp.connect(host, port, {request_timeout=10000})
  if not socket then
    return nil
  end
  if code < 200 or code > 299 then
    socket:close()
    return nil
  end

  socket:set_timeout(5000)
  -- USER
  local status, code, message = ftp.auth(socket, buffer, user, pass)
  if not status then
    stdnse.debug1("Authentication rejected: %s %s", code or "socket", message)
    ftp.close(socket)
    return nil
  end

  -- PORT highport
  local portfmt = get_portfmt()
  -- This is actually port 256*80 + 80 = 20560
  if not socket:send(string.format(portfmt, "80,80")) then
    stdnse.debug1("Can't send PORT")
    return nil
  end
  code, message = ftp.read_reply(buffer)
  if not code then
    stdnse.debug1("Error after PORT: %s", message)
    return nil
  end
  if code < 200 or code > 299 then
    stdnse.verbose1("PORT response: %d %s", code, message)
    ftp.close(socket)
    -- return "server forbids bouncing"
    return nil
  end

  -- PORT lowport
  if not socket:send(string.format(portfmt, "0,80")) then
    stdnse.debug1("Can't send PORT")
    return nil
  end
  code, message = ftp.read_reply(buffer)
  if not code then
    stdnse.debug1("Error after PORT: %s", message)
    return nil
  end
  if code < 200 or code > 299 then
    stdnse.verbose1("PORT (low port) response: %d %s", code, message)
    ftp.close(socket)
    return "server forbids bouncing to low ports <1025"
  end

  ftp.close(socket)
  return "bounce working!"
end