summaryrefslogtreecommitdiffstats
path: root/scripts/bitcoinrpc-info.nse
blob: 66208a675437205c58d1cdcf209a737f04a15040 (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
local creds = require "creds"
local http = require "http"
local json = require "json"
local nmap = require "nmap"
local shortport = require "shortport"
local stdnse = require "stdnse"
local string = require "string"
local table = require "table"
local tableaux = require "tableaux"

description = [[
Obtains information from a Bitcoin server by calling <code>getinfo</code> on its JSON-RPC interface.
]]

---
-- @usage
-- nmap -p 8332 --script bitcoinrpc-info --script-args creds.global=<user>:<pass> <target>
-- @args creds.global http credentials used for the query (user:pass)
-- @output
-- 8332/tcp open  unknown
-- | bitcoinrpc-info.nse:
-- |   root:
-- |     balance: 0
-- |     blocks: 135041
-- |     connections: 36
-- |     difficulty: 1379223.4296725
-- |     generate: false
-- |     genproclimit: -1
-- |     hashespersec: 0
-- |     keypoololdest: 1309381827
-- |     paytxfee: 0
-- |     testnet: false
-- |_    version: 32100
--
-- @xmloutput
-- <table key="root">
--   <elem key="balance">0</elem>
--   <elem key="blocks">135041</elem>
--   <elem key="connections">36</elem>
--   <elem key="difficulty">1379223.4296725</elem>
--   <elem key="generate">false</elem>
--   <elem key="genproclimit">-1</elem>
--   <elem key="hashespersec">0</elem>
--   <elem key="keypoololdest">1309381827</elem>
--   <elem key="paytxfee">0</elem>
--   <elem key="testnet">false</elem>
--   <elem key="version">32100</elem>
-- </table>

author = "Toni Ruottu"
license = "Same as Nmap--See https://nmap.org/book/man-legal.html"
categories = {"default", "discovery", "safe"}
dependencies = {"http-brute"}


portrule = shortport.portnumber(8332)

-- JSON-RPC helpers

local function request(method, params, id)
  json.make_array(params)
  local req = {method = method, params = params, id = id}
  local serial = json.generate(req)
  return serial
end

local function response(serial)
  local _, response = json.parse(serial)
  local result = response["result"]
  return result
end

local ServiceProxy = {}
function ServiceProxy:new(host, port, path, options)
  local o = {}
  setmetatable(o, self)
  self.host = host
  self.port = port
  self.path = path
  self.options = options
  self.__index = function(_, method)
    return function(...)
      return self:call(method, table.pack(...))
    end
  end
  return o
end

function ServiceProxy:remote(req)
  local httpdata = http.post(self.host, self.port, self.path, self.options, nil, req)
  if httpdata.status == 200 then
    return httpdata.body
  end
end

function ServiceProxy:call(method, args)
  local FIRST = 1
  local req = request(method, args, FIRST)
  local ret = self:remote(req)
  if not ret then
    return
  end
  local result = response(ret)
  return result
end

-- Convert an integer into a broken-down version number.
-- Prior to version 0.3.13, versions are 3-digit numbers as so:
--   200 -> 0.2.0
--   300 -> 0.3.0
--   310 -> 0.3.10
-- In 0.3.13 and later, they are 5-digit numbers as so:
--   31300 -> 0.3.13
--   31900 -> 0.3.19
-- Version 0.3.13 release announcement: https://bitcointalk.org/?topic=1327.0
local function decode_bitcoin_version(n)
  if n < 31300 then
    local minor, micro = n // 100, n % 100
    return string.format("0.%d.%d", minor, micro)
  else
    local minor, micro = n // 10000, (n // 100) % 100
    return string.format("0.%d.%d", minor, micro)
  end
end

local function formatpairs(info)
  local result = stdnse.output_table()
  local keys = tableaux.keys(info)
  table.sort(keys)
  for _, k in ipairs(keys) do
    if info[k] ~= "" then
      result[k] = info[k]
    end
  end
  return result
end

local function getinfo(host, port, user, pass)
  local auth = {username = user, password = pass}
  local bitcoind = ServiceProxy:new(host, port, "/", {auth = auth})
  return bitcoind.getinfo()
end

action = function(host, port)
  local response = stdnse.output_table()
  local c = creds.Credentials:new(creds.ALL_DATA, host, port)
  local states = creds.State.VALID + creds.State.PARAM
  for cred in c:getCredentials(states) do
    local info = getinfo(host, port, cred.user, cred.pass)
    if info then
      local result = formatpairs(info)
      response[cred.user] = result

      port.version.name = "http"
      port.version.product = "Bitcoin JSON-RPC"
      if info.version then
        port.version.version = decode_bitcoin_version(info.version)
      end
      nmap.set_port_version(host, port)
    end
  end

  return response
end