watobo 0.9.21 → 0.9.23

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (283) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +46 -1
  3. data/bin/nfq_server.rb +0 -9
  4. data/bin/watobo_gui.rb +3 -13
  5. data/custom-views/prettify-json.rb +9 -18
  6. data/icons/watobo.ico +0 -0
  7. data/icons/watobo.ico.old +0 -0
  8. data/lib/watobo.rb +10 -19
  9. data/lib/watobo/adapters.rb +5 -14
  10. data/lib/watobo/adapters/data_store.rb +50 -59
  11. data/lib/watobo/adapters/file/file_store.rb +287 -296
  12. data/lib/watobo/adapters/file/marshal_store.rb +293 -296
  13. data/lib/watobo/adapters/session_store.rb +5 -14
  14. data/lib/watobo/ca.rb +1 -10
  15. data/lib/watobo/config.rb +197 -206
  16. data/lib/watobo/constants.rb +0 -9
  17. data/lib/watobo/core.rb +3 -12
  18. data/lib/watobo/core/active_check.rb +72 -135
  19. data/lib/watobo/core/active_checks.rb +49 -58
  20. data/lib/watobo/core/ca.rb +369 -389
  21. data/lib/watobo/core/cert_store.rb +34 -43
  22. data/lib/watobo/core/chat.rb +92 -101
  23. data/lib/watobo/core/chats.rb +271 -280
  24. data/lib/watobo/core/client_cert_store.rb +106 -35
  25. data/lib/watobo/core/conversation.rb +48 -57
  26. data/lib/watobo/core/cookie.rb +23 -32
  27. data/lib/watobo/core/egress_handlers.rb +98 -0
  28. data/lib/watobo/core/finding.rb +66 -75
  29. data/lib/watobo/core/findings.rb +107 -114
  30. data/lib/watobo/core/forwarding_proxy.rb +13 -22
  31. data/lib/watobo/core/fuzz_gen.rb +0 -9
  32. data/lib/watobo/core/intercept_carver.rb +166 -177
  33. data/lib/watobo/core/intercept_filter.rb +235 -244
  34. data/lib/watobo/core/interceptor.rb +98 -107
  35. data/lib/watobo/core/min_class.rb +4 -13
  36. data/lib/watobo/core/netfilter_queue.rb +170 -179
  37. data/lib/watobo/core/ott_cache.rb +132 -141
  38. data/lib/watobo/core/parameter.rb +43 -52
  39. data/lib/watobo/core/passive_check.rb +103 -102
  40. data/lib/watobo/core/passive_checks.rb +48 -57
  41. data/lib/watobo/core/passive_scanner.rb +54 -55
  42. data/lib/watobo/core/plugin.rb +11 -20
  43. data/lib/watobo/core/project.rb +3 -9
  44. data/lib/watobo/core/proxy.rb +43 -52
  45. data/lib/watobo/core/request.rb +125 -123
  46. data/lib/watobo/core/response.rb +44 -53
  47. data/lib/watobo/core/scanner.rb +0 -9
  48. data/lib/watobo/core/scanner3.rb +405 -414
  49. data/lib/watobo/core/scope.rb +83 -92
  50. data/lib/watobo/core/session.rb +1043 -1026
  51. data/lib/watobo/core/sid_cache.rb +98 -107
  52. data/lib/watobo/core/subscriber.rb +25 -34
  53. data/lib/watobo/defaults.rb +21 -30
  54. data/lib/watobo/external/diff/lcs.rb +0 -9
  55. data/lib/watobo/external/diff/lcs/array.rb +0 -9
  56. data/lib/watobo/external/diff/lcs/block.rb +0 -9
  57. data/lib/watobo/external/diff/lcs/callbacks.rb +0 -9
  58. data/lib/watobo/external/diff/lcs/change.rb +0 -9
  59. data/lib/watobo/external/diff/lcs/hunk.rb +0 -9
  60. data/lib/watobo/external/diff/lcs/ldiff.rb +0 -9
  61. data/lib/watobo/external/diff/lcs/string.rb +0 -9
  62. data/lib/watobo/externals.rb +6 -15
  63. data/lib/watobo/framework.rb +4 -13
  64. data/lib/watobo/framework/create_project.rb +60 -69
  65. data/lib/watobo/framework/init.rb +0 -9
  66. data/lib/watobo/framework/init_modules.rb +0 -9
  67. data/lib/watobo/framework/license_text.rb +28 -37
  68. data/lib/watobo/framework/load_chat.rb +13 -22
  69. data/lib/watobo/gui.rb +132 -123
  70. data/lib/watobo/gui/about_watobo.rb +0 -9
  71. data/lib/watobo/gui/browser_preview.rb +0 -9
  72. data/lib/watobo/gui/certificate_dialog.rb +0 -9
  73. data/lib/watobo/gui/chat_diff.rb +0 -9
  74. data/lib/watobo/gui/chatviewer_frame.rb +73 -72
  75. data/lib/watobo/gui/checkboxtree.rb +0 -9
  76. data/lib/watobo/gui/checks_policy_frame.rb +0 -9
  77. data/lib/watobo/gui/client_cert_dialog.rb +96 -87
  78. data/lib/watobo/gui/confirm_scan_dialog.rb +0 -9
  79. data/lib/watobo/gui/conversation_table.rb +158 -164
  80. data/lib/watobo/gui/conversation_table_ctrl.rb +207 -216
  81. data/lib/watobo/gui/conversation_table_ctrl2.rb +373 -382
  82. data/lib/watobo/gui/csrf_token_dialog.rb +0 -9
  83. data/lib/watobo/gui/custom_viewer.rb +374 -383
  84. data/lib/watobo/gui/dashboard.rb +296 -303
  85. data/lib/watobo/gui/define_scope_frame.rb +0 -9
  86. data/lib/watobo/gui/differ_frame.rb +215 -224
  87. data/lib/watobo/gui/edit_comment.rb +0 -9
  88. data/lib/watobo/gui/edit_scope_dialog.rb +0 -9
  89. data/lib/watobo/gui/export_dialog.rb +104 -113
  90. data/lib/watobo/gui/finding_info.rb +0 -9
  91. data/lib/watobo/gui/findings_tree.rb +210 -217
  92. data/lib/watobo/gui/full_scan_dialog.rb +0 -9
  93. data/lib/watobo/gui/fuzzer_gui.rb +1295 -1313
  94. data/lib/watobo/gui/fxsave_thread.rb +14 -0
  95. data/lib/watobo/gui/goto_url_dialog.rb +70 -79
  96. data/lib/watobo/gui/hex_viewer.rb +0 -9
  97. data/lib/watobo/gui/html_viewer.rb +287 -296
  98. data/lib/watobo/gui/intercept_filter_dialog.rb +188 -197
  99. data/lib/watobo/gui/interceptor_gui.rb +1041 -1051
  100. data/lib/watobo/gui/interceptor_settings_dialog.rb +0 -9
  101. data/lib/watobo/gui/json_viewer.rb +287 -0
  102. data/lib/watobo/gui/list_box.rb +101 -110
  103. data/lib/watobo/gui/log_file_viewer.rb +32 -41
  104. data/lib/watobo/gui/log_viewer.rb +83 -88
  105. data/lib/watobo/gui/login_wizzard.rb +0 -9
  106. data/lib/watobo/gui/main_window.rb +587 -618
  107. data/lib/watobo/gui/manual_request_editor.rb +620 -565
  108. data/lib/watobo/gui/master_pw_dialog.rb +0 -9
  109. data/lib/watobo/gui/mixins/gui_settings.rb +29 -38
  110. data/lib/watobo/gui/page_tree.rb +217 -226
  111. data/lib/watobo/gui/password_policy_dialog.rb +0 -9
  112. data/lib/watobo/gui/plugin_board.rb +0 -9
  113. data/lib/watobo/gui/preferences_dialog.rb +0 -9
  114. data/lib/watobo/gui/progress_window.rb +17 -27
  115. data/lib/watobo/gui/project_wizzard.rb +0 -9
  116. data/lib/watobo/gui/proxy_dialog.rb +1 -10
  117. data/lib/watobo/gui/quick_scan_dialog.rb +0 -9
  118. data/lib/watobo/gui/request_builder_frame.rb +102 -111
  119. data/lib/watobo/gui/request_editor.rb +181 -137
  120. data/lib/watobo/gui/rewrite_filters_dialog.rb +394 -403
  121. data/lib/watobo/gui/rewrite_rules_dialog.rb +372 -381
  122. data/lib/watobo/gui/save_chat_dialog.rb +140 -149
  123. data/lib/watobo/gui/scanner_settings_dialog.rb +0 -9
  124. data/lib/watobo/gui/select_chat_dialog.rb +0 -9
  125. data/lib/watobo/gui/session_management_dialog.rb +0 -9
  126. data/lib/watobo/gui/sites_tree.rb +0 -9
  127. data/lib/watobo/gui/status_bar.rb +0 -9
  128. data/lib/watobo/gui/table_editor.rb +0 -9
  129. data/lib/watobo/gui/tagless_viewer.rb +0 -9
  130. data/lib/watobo/gui/templates/plugin.rb +0 -9
  131. data/lib/watobo/gui/templates/plugin2.rb +92 -100
  132. data/lib/watobo/gui/templates/plugin_base.rb +144 -153
  133. data/lib/watobo/gui/text_viewer.rb +0 -9
  134. data/lib/watobo/gui/transcoder_window.rb +0 -9
  135. data/lib/watobo/gui/utils/gui_utils.rb +0 -9
  136. data/lib/watobo/gui/utils/init_icons.rb +86 -95
  137. data/lib/watobo/gui/utils/load_icons.rb +33 -42
  138. data/lib/watobo/gui/utils/load_plugins.rb +116 -119
  139. data/lib/watobo/gui/utils/master_password.rb +68 -77
  140. data/lib/watobo/gui/utils/save_default_settings.rb +113 -122
  141. data/lib/watobo/gui/utils/save_project_settings.rb +0 -9
  142. data/lib/watobo/gui/utils/save_proxy_settings.rb +41 -50
  143. data/lib/watobo/gui/utils/save_scanner_settings.rb +18 -27
  144. data/lib/watobo/gui/utils/session_history.rb +112 -121
  145. data/lib/watobo/gui/workspace_dialog.rb +0 -9
  146. data/lib/watobo/gui/www_auth_dialog.rb +0 -9
  147. data/lib/watobo/gui/xml_viewer_frame.rb +0 -9
  148. data/lib/watobo/http.rb +4 -13
  149. data/lib/watobo/http/cookies/cookies.rb +26 -35
  150. data/lib/watobo/http/data/data.rb +45 -54
  151. data/lib/watobo/http/data/json.rb +47 -55
  152. data/lib/watobo/http/url/url.rb +38 -47
  153. data/lib/watobo/http/xml/xml.rb +124 -130
  154. data/lib/watobo/interceptor.rb +3 -12
  155. data/lib/watobo/interceptor/proxy.rb +742 -739
  156. data/lib/watobo/interceptor/transparent.rb +22 -24
  157. data/lib/watobo/mixins.rb +10 -19
  158. data/lib/watobo/mixins/check_info.rb +27 -36
  159. data/lib/watobo/mixins/httpparser.rb +613 -637
  160. data/lib/watobo/mixins/request_parser.rb +88 -97
  161. data/lib/watobo/mixins/shapers.rb +515 -529
  162. data/lib/watobo/mixins/transcoders.rb +3 -11
  163. data/lib/watobo/parser.rb +1 -10
  164. data/lib/watobo/parser/html.rb +83 -92
  165. data/lib/watobo/patch_fxruby_setfocus.rb +26 -0
  166. data/lib/watobo/sockets.rb +3 -12
  167. data/lib/watobo/sockets/agent.rb +828 -837
  168. data/lib/watobo/sockets/client_socket.rb +308 -312
  169. data/lib/watobo/sockets/connection.rb +401 -410
  170. data/lib/watobo/sockets/http_socket.rb +11 -13
  171. data/lib/watobo/sockets/ntlm_auth.rb +129 -138
  172. data/lib/watobo/utils.rb +10 -19
  173. data/lib/watobo/utils/check_regex.rb +0 -9
  174. data/lib/watobo/utils/copy_object.rb +0 -9
  175. data/lib/watobo/utils/crypto.rb +0 -9
  176. data/lib/watobo/utils/expand_range.rb +23 -32
  177. data/lib/watobo/utils/export_xml.rb +97 -106
  178. data/lib/watobo/utils/file_management.rb +9 -11
  179. data/lib/watobo/utils/hexprint.rb +9 -18
  180. data/lib/watobo/utils/load_chat.rb +0 -9
  181. data/lib/watobo/utils/load_icon.rb +0 -9
  182. data/lib/watobo/utils/ntlm.rb +866 -875
  183. data/lib/watobo/utils/print_debug.rb +12 -21
  184. data/lib/watobo/utils/response_builder.rb +90 -99
  185. data/lib/watobo/utils/response_hash.rb +0 -9
  186. data/lib/watobo/utils/secure_eval.rb +0 -9
  187. data/lib/watobo/utils/strings.rb +10 -19
  188. data/lib/watobo/utils/text2request.rb +0 -9
  189. data/lib/watobo/utils/url.rb +23 -32
  190. data/lib/watobo/utils/utf16.rb +11 -20
  191. data/modules/active/Apache/mod_status.rb +0 -9
  192. data/modules/active/Apache/multiview.rb +151 -160
  193. data/modules/active/Flash/crossdomain.rb +0 -9
  194. data/modules/active/JWT/jwt_oauth2_none.rb +111 -0
  195. data/modules/active/cq5/cq5_default_selectors.rb +106 -115
  196. data/modules/active/cq5/cqp_user_enumeration.rb +125 -134
  197. data/modules/active/directories/dirwalker.rb +0 -9
  198. data/modules/active/discovery/fileextensions.rb +0 -9
  199. data/modules/active/discovery/http_methods.rb +0 -9
  200. data/modules/active/discovery/jsmapfiles.rb +79 -0
  201. data/modules/active/domino/domino_db.rb +68 -76
  202. data/modules/active/dotNET/custom_errors.rb +102 -111
  203. data/modules/active/dotNET/dotnet_files.rb +90 -99
  204. data/modules/active/fileinclusion/lfi_simple.rb +0 -9
  205. data/modules/active/jboss/jboss_basic.rb +0 -9
  206. data/modules/active/sap/business_objects.rb +51 -60
  207. data/modules/active/sap/its_commands.rb +0 -9
  208. data/modules/active/sap/its_service_parameter.rb +0 -9
  209. data/modules/active/sap/its_services.rb +0 -9
  210. data/modules/active/sap/its_xss.rb +0 -9
  211. data/modules/active/shell_shock/shell_shock.rb +139 -148
  212. data/modules/active/siebel/siebel_apps.rb +160 -169
  213. data/modules/active/sqlinjection/sql_boolean.rb +0 -9
  214. data/modules/active/sqlinjection/sql_numerical.rb +198 -0
  215. data/modules/active/sqlinjection/sqli_error.rb +0 -9
  216. data/modules/active/sqlinjection/sqli_timing.rb +220 -229
  217. data/modules/active/struts2/default_handler_ognl.rb +106 -115
  218. data/modules/active/struts2/include_params_ognl.rb +105 -114
  219. data/modules/active/xml/xml_xxe.rb +112 -123
  220. data/modules/active/xss/xss_ng.rb +214 -223
  221. data/modules/active/xss/xss_simple.rb +0 -9
  222. data/modules/passive/ajax.rb +68 -77
  223. data/modules/passive/autocomplete.rb +56 -65
  224. data/modules/passive/cookie_options.rb +0 -9
  225. data/modules/passive/cookie_xss.rb +0 -9
  226. data/modules/passive/detect_code.rb +0 -9
  227. data/modules/passive/detect_fileupload.rb +0 -9
  228. data/modules/passive/detect_infrastructure.rb +0 -9
  229. data/modules/passive/detect_one_time_tokens.rb +0 -9
  230. data/modules/passive/dirindexing.rb +0 -9
  231. data/modules/passive/disclosure_domino.rb +55 -64
  232. data/modules/passive/disclosure_emails.rb +0 -9
  233. data/modules/passive/disclosure_ipaddr.rb +55 -53
  234. data/modules/passive/filename_as_parameter.rb +0 -9
  235. data/modules/passive/form_spotter.rb +0 -9
  236. data/modules/passive/hidden_fields.rb +50 -59
  237. data/modules/passive/hotspots.rb +0 -9
  238. data/modules/passive/in_script_parameter.rb +0 -9
  239. data/modules/passive/json_web_token.rb +93 -0
  240. data/modules/passive/multiple_server_headers.rb +0 -9
  241. data/modules/passive/possible_login.rb +0 -9
  242. data/modules/passive/redirect_url.rb +0 -9
  243. data/modules/passive/redirectionz.rb +0 -9
  244. data/modules/passive/sap-headers.rb +56 -65
  245. data/modules/passive/xss_dom.rb +0 -9
  246. data/plugins/aem/aem.rb +11 -20
  247. data/plugins/aem/gui/main.rb +118 -127
  248. data/plugins/aem/gui/tree_view.rb +171 -180
  249. data/plugins/aem/lib/agent.rb +130 -138
  250. data/plugins/aem/lib/dispatcher.rb +45 -51
  251. data/plugins/aem/lib/engine.rb +177 -186
  252. data/plugins/catalog/catalog.rb +345 -355
  253. data/plugins/crawler/crawler.rb +4 -13
  254. data/plugins/crawler/gui.rb +5 -14
  255. data/plugins/crawler/gui/auth_frame.rb +270 -279
  256. data/plugins/crawler/gui/crawler_gui.rb +271 -276
  257. data/plugins/crawler/gui/general_settings_frame.rb +96 -105
  258. data/plugins/crawler/gui/hooks_frame.rb +80 -89
  259. data/plugins/crawler/gui/scope_frame.rb +50 -59
  260. data/plugins/crawler/gui/settings_tabbook.rb +38 -47
  261. data/plugins/crawler/gui/status_frame.rb +59 -68
  262. data/plugins/crawler/lib/bags.rb +18 -27
  263. data/plugins/crawler/lib/constants.rb +11 -20
  264. data/plugins/crawler/lib/engine.rb +488 -497
  265. data/plugins/crawler/lib/grabber.rb +68 -77
  266. data/plugins/crawler/lib/status.rb +71 -80
  267. data/plugins/crawler/lib/uri_mp.rb +12 -21
  268. data/plugins/filefinder/filefinder.rb +326 -333
  269. data/plugins/sqlmap/bin/test.rb +78 -87
  270. data/plugins/sqlmap/gui.rb +4 -13
  271. data/plugins/sqlmap/gui/main.rb +218 -227
  272. data/plugins/sqlmap/gui/options_frame.rb +97 -106
  273. data/plugins/sqlmap/lib/sqlmap_ctrl.rb +90 -100
  274. data/plugins/sqlmap/sqlmap.rb +2 -11
  275. data/plugins/sslchecker/cli/sslchecker_cli.rb +0 -9
  276. data/plugins/sslchecker/gui/cipher_table.rb +246 -254
  277. data/plugins/sslchecker/gui/gui.rb +258 -264
  278. data/plugins/sslchecker/gui/sslchecker.rb +4 -13
  279. data/plugins/sslchecker/lib/check.rb +127 -133
  280. data/plugins/wshell/gui/main.rb +119 -117
  281. data/plugins/wshell/lib/core.rb +38 -88
  282. data/plugins/wshell/wshell.rb +11 -20
  283. metadata +170 -164
@@ -1,108 +1,99 @@
1
- #.
2
- # export_xml.rb
3
- #.
4
- # Copyright 2014 by siberas, http://www.siberas.de
5
- # This file is part of WATOBO (Web Application Tool Box) http://watobo.sourceforge.com
6
- # WATOBO is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation version 2 of the License.
7
- # WATOBO is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
8
- # You should have received a copy of the GNU General Public License along with WATOBO; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
1
+ module Watobo
2
+ module Utils
3
+ def self.finding2xml(finding,xml)
4
+ fnode = Nokogiri::XML::Node.new("Finding", xml)
9
5
 
10
- module Watobo
11
- module Utils
12
- def self.finding2xml(finding,xml)
13
- fnode = Nokogiri::XML::Node.new("Finding", xml)
14
-
15
- dnode = Nokogiri::XML::Node.new("Request", xml)
16
- dnode.content = Base64.strict_encode64( finding.request.join )
17
- fnode << dnode
18
-
19
- dnode = Nokogiri::XML::Node.new("Response", xml)
20
- dnode.content = Base64.strict_encode64 finding.response.join
21
- fnode << dnode
22
-
23
- dnode = Nokogiri::XML::Node.new("Details", xml)
24
- finding.details.each do |k,v|
25
- d = Nokogiri::XML::Node.new(k.to_s, xml)
26
- d.content = v
27
- dnode << d
28
- end
29
- fnode << dnode
30
-
31
- fnode
32
- end
33
-
34
- def self.chat2xml(chat,xml)
35
- fnode = Nokogiri::XML::Node.new("Chat", xml)
36
-
37
- dnode = Nokogiri::XML::Node.new("Request", xml)
38
- dnode.content = Base64.strict_encode64( chat.request.join )
39
- fnode << dnode
40
-
41
- dnode = Nokogiri::XML::Node.new("Response", xml)
42
- dnode.content = Base64.strict_encode64 chat.response.join
43
- fnode << dnode
44
-
45
- dnode = Nokogiri::XML::Node.new("Details", xml)
46
- chat.settings.each do |k,v|
47
- d = Nokogiri::XML::Node.new(k.to_s, xml)
48
- d.content = v
49
- dnode << d
50
- end
51
- fnode << dnode
52
-
53
- fnode
54
- end
55
-
56
- def self.exportXML(*prefs)
57
- # prefs ||= []
58
- xml = Nokogiri::XML("")
59
- env = Nokogiri::XML::Node.new("WatoboExportv1", xml)
60
- xml << env
61
-
62
- if prefs.include? :export_findings
63
-
64
- findings = Nokogiri::XML::Node.new("Findings", xml)
65
- env << findings
66
-
67
- Watobo::Findings.each do |fid, finding|
68
- if prefs.include? :scope_only
69
- if Watobo::Scope.match_site?(finding.request.site)
70
- if prefs.include? :ignore_fps
71
- unless finding.false_positive?
72
- findings << finding2xml(finding, xml)
73
- end
74
- else
75
- findings << finding2xml(finding, xml)
76
- end
77
- end
78
- else
79
- if prefs.include? :ignore_fps
80
- unless finding.false_positive?
81
- findings << finding2xml(finding, xml)
82
- end
83
- else
84
- findings << finding2xml(finding, xml)
85
- end
86
- end
87
- end
88
- end
89
-
90
- chats = Nokogiri::XML::Node.new("Chats", xml)
91
- env << chats
92
-
93
- if prefs.include? :export_chats
94
- Watobo::Chats.each do |chat|
95
- if prefs.include? :scope_only
96
- if Watobo::Scope.match_site?(chat.request.site)
97
- chats << chat2xml(chat, xml)
98
- end
99
- else
100
- chats << chat2xml(chat, xml)
101
- end
102
- end
103
- end
104
-
105
- xml
106
- end
107
- end
6
+ dnode = Nokogiri::XML::Node.new("Request", xml)
7
+ dnode.content = Base64.strict_encode64( finding.request.join )
8
+ fnode << dnode
9
+
10
+ dnode = Nokogiri::XML::Node.new("Response", xml)
11
+ dnode.content = Base64.strict_encode64 finding.response.join
12
+ fnode << dnode
13
+
14
+ dnode = Nokogiri::XML::Node.new("Details", xml)
15
+ finding.details.each do |k,v|
16
+ d = Nokogiri::XML::Node.new(k.to_s, xml)
17
+ d.content = v
18
+ dnode << d
19
+ end
20
+ fnode << dnode
21
+
22
+ fnode
23
+ end
24
+
25
+ def self.chat2xml(chat,xml)
26
+ fnode = Nokogiri::XML::Node.new("Chat", xml)
27
+
28
+ dnode = Nokogiri::XML::Node.new("Request", xml)
29
+ dnode.content = Base64.strict_encode64( chat.request.join )
30
+ fnode << dnode
31
+
32
+ dnode = Nokogiri::XML::Node.new("Response", xml)
33
+ dnode.content = Base64.strict_encode64 chat.response.join
34
+ fnode << dnode
35
+
36
+ dnode = Nokogiri::XML::Node.new("Details", xml)
37
+ chat.settings.each do |k,v|
38
+ d = Nokogiri::XML::Node.new(k.to_s, xml)
39
+ d.content = v
40
+ dnode << d
41
+ end
42
+ fnode << dnode
43
+
44
+ fnode
45
+ end
46
+
47
+ def self.exportXML(*prefs)
48
+ # prefs ||= []
49
+ xml = Nokogiri::XML("")
50
+ env = Nokogiri::XML::Node.new("WatoboExportv1", xml)
51
+ xml << env
52
+
53
+ if prefs.include? :export_findings
54
+
55
+ findings = Nokogiri::XML::Node.new("Findings", xml)
56
+ env << findings
57
+
58
+ Watobo::Findings.each do |fid, finding|
59
+ if prefs.include? :scope_only
60
+ if Watobo::Scope.match_site?(finding.request.site)
61
+ if prefs.include? :ignore_fps
62
+ unless finding.false_positive?
63
+ findings << finding2xml(finding, xml)
64
+ end
65
+ else
66
+ findings << finding2xml(finding, xml)
67
+ end
68
+ end
69
+ else
70
+ if prefs.include? :ignore_fps
71
+ unless finding.false_positive?
72
+ findings << finding2xml(finding, xml)
73
+ end
74
+ else
75
+ findings << finding2xml(finding, xml)
76
+ end
77
+ end
78
+ end
79
+ end
80
+
81
+ chats = Nokogiri::XML::Node.new("Chats", xml)
82
+ env << chats
83
+
84
+ if prefs.include? :export_chats
85
+ Watobo::Chats.each do |chat|
86
+ if prefs.include? :scope_only
87
+ if Watobo::Scope.match_site?(chat.request.site)
88
+ chats << chat2xml(chat, xml)
89
+ end
90
+ else
91
+ chats << chat2xml(chat, xml)
92
+ end
93
+ end
94
+ end
95
+
96
+ xml
97
+ end
98
+ end
108
99
  end
@@ -1,12 +1,3 @@
1
- #.
2
- # file_management.rb
3
- #.
4
- # Copyright 2014 by siberas, http://www.siberas.de
5
- # This file is part of WATOBO (Web Application Tool Box) http://watobo.sourceforge.com
6
- # WATOBO is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation version 2 of the License.
7
- # WATOBO is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
8
- # You should have received a copy of the GNU General Public License along with WATOBO; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
9
-
10
1
  # @private
11
2
  module Watobo#:nodoc: all
12
3
  module Utils
@@ -35,11 +26,18 @@ module Watobo#:nodoc: all
35
26
  settings = YAML.load_file(file)
36
27
  end
37
28
  return settings
38
- end
39
-
29
+ end
30
+
40
31
  def Utils.saveChat(chat, filename)
41
32
  return false if filename.nil?
42
33
  return false if chat.nil?
34
+
35
+ Watobo::DataStore.save_chat(filename, chat)
36
+ end
37
+
38
+ def Utils.saveChat_UNUSED(chat, filename)
39
+ return false if filename.nil?
40
+ return false if chat.nil?
43
41
  chat_data = {
44
42
  :request => chat.request.map{|x| x.inspect},
45
43
  :response => chat.response.map{|x| x.inspect},
@@ -1,20 +1,11 @@
1
- #.
2
- # hexprint.rb
3
- #.
4
- # Copyright 2014 by siberas, http://www.siberas.de
5
- # This file is part of WATOBO (Web Application Tool Box) http://watobo.sourceforge.com
6
- # WATOBO is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation version 2 of the License.
7
- # WATOBO is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
8
- # You should have received a copy of the GNU General Public License along with WATOBO; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
9
-
10
1
  # @private
11
- module Watobo#:nodoc: all
12
- module Utils
13
- def self.hexprint(data)
14
- data.length.times do |i|
15
- print "%02X" % data[i].ord
16
- puts if data[i] == "\n"
17
- end
18
- end
19
- end
2
+ module Watobo#:nodoc: all
3
+ module Utils
4
+ def self.hexprint(data)
5
+ data.length.times do |i|
6
+ print "%02X" % data[i].ord
7
+ puts if data[i] == "\n"
8
+ end
9
+ end
10
+ end
20
11
  end
@@ -1,12 +1,3 @@
1
- #.
2
- # load_chat.rb
3
- #.
4
- # Copyright 2014 by siberas, http://www.siberas.de
5
- # This file is part of WATOBO (Web Application Tool Box) http://watobo.sourceforge.com
6
- # WATOBO is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation version 2 of the License.
7
- # WATOBO is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
8
- # You should have received a copy of the GNU General Public License along with WATOBO; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
9
-
10
1
  # @private
11
2
  module Watobo#:nodoc: all
12
3
  module Utils
@@ -1,12 +1,3 @@
1
- #.
2
- # load_icon.rb
3
- #.
4
- # Copyright 2014 by siberas, http://www.siberas.de
5
- # This file is part of WATOBO (Web Application Tool Box) http://watobo.sourceforge.com
6
- # WATOBO is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation version 2 of the License.
7
- # WATOBO is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
8
- # You should have received a copy of the GNU General Public License along with WATOBO; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
9
-
10
1
  # @private
11
2
  module Watobo#:nodoc: all
12
3
  module Utils
@@ -1,875 +1,866 @@
1
- #.
2
- # ntlm.rb
3
- #.
4
- # Copyright 2014 by siberas, http://www.siberas.de
5
- # This file is part of WATOBO (Web Application Tool Box) http://watobo.sourceforge.com
6
- # WATOBO is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation version 2 of the License.
7
- # WATOBO is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
8
- # You should have received a copy of the GNU General Public License along with WATOBO; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
9
-
10
- # encoding: UTF-8
11
- #
12
- # = net/ntlm.rb
13
- #
14
- # An NTLM Authentication Library for Ruby
15
- #
16
- # This code is a derivative of "dbf2.rb" written by yrock
17
- # and Minero Aoki. You can find original code here:
18
- # http://jp.rubyist.net/magazine/?0013-CodeReview
19
- # -------------------------------------------------------------
20
- # Copyright (c) 2005,2006 yrock
21
- #
22
- # This program is free software.
23
- # You can distribute/modify this program under the terms of the
24
- # Ruby License.
25
- #
26
- # 2006-02-11 refactored by Minero Aoki
27
- # -------------------------------------------------------------
28
- #
29
- # All protocol information used to write this code stems from
30
- # "The NTLM Authentication Protocol" by Eric Glass. The author
31
- # would thank to him for this tremendous work and making it
32
- # available on the net.
33
- # http://davenport.sourceforge.net/ntlm.html
34
- # -------------------------------------------------------------
35
- # Copyright (c) 2003 Eric Glass
36
- #
37
- # Permission to use, copy, modify, and distribute this document
38
- # for any purpose and without any fee is hereby granted,
39
- # provided that the above copyright notice and this list of
40
- # conditions appear in all copies.
41
- # -------------------------------------------------------------
42
- #
43
- # The author also looked Mozilla-Firefox-1.0.7 source code,
44
- # namely, security/manager/ssl/src/nsNTLMAuthModule.cpp and
45
- # Jonathan Bastien-Filiatrault's libntlm-ruby.
46
- # "http://x2a.org/websvn/filedetails.php?
47
- # repname=libntlm-ruby&path=%2Ftrunk%2Fntlm.rb&sc=1"
48
- # The latter has a minor bug in its separate_keys function.
49
- # The third key has to begin from the 14th character of the
50
- # input string instead of 13th:)
51
- #--
52
- # $Id: ntlm.rb,v 1.1 2006/10/05 01:36:52 koheik Exp $
53
- #++
54
-
55
-
56
-
57
- module Watobo
58
- module NTLM
59
- # @private
60
- module VERSION
61
- MAJOR = 0
62
- MINOR = 3
63
- TINY = 4
64
- STRING = [MAJOR, MINOR, TINY].join('.')
65
- end
66
-
67
- SSP_SIGN = "NTLMSSP\0"
68
- BLOB_SIGN = 0x00000101
69
- LM_MAGIC = "KGS!@\#$%"
70
- TIME_OFFSET = 11644473600
71
- MAX64 = 0xffffffffffffffff
72
-
73
- FLAGS = {
74
- :UNICODE => 0x00000001,
75
- :OEM => 0x00000002,
76
- :REQUEST_TARGET => 0x00000004,
77
- :MBZ9 => 0x00000008,
78
- :SIGN => 0x00000010,
79
- :SEAL => 0x00000020,
80
- :NEG_DATAGRAM => 0x00000040,
81
- :NETWARE => 0x00000100,
82
- :NTLM => 0x00000200,
83
- :NEG_NT_ONLY => 0x00000400,
84
- :MBZ7 => 0x00000800,
85
- :DOMAIN_SUPPLIED => 0x00001000,
86
- :WORKSTATION_SUPPLIED => 0x00002000,
87
- :LOCAL_CALL => 0x00004000,
88
- :ALWAYS_SIGN => 0x00008000,
89
- :TARGET_TYPE_DOMAIN => 0x00010000,
90
- :TARGET_INFO => 0x00800000,
91
- :NTLM2_KEY => 0x00080000,
92
- :KEY128 => 0x20000000,
93
- :KEY56 => 0x80000000
94
- }.freeze
95
-
96
- FLAG_KEYS = FLAGS.keys.sort{|a, b| FLAGS[a] <=> FLAGS[b] }
97
-
98
- DEFAULT_FLAGS = {
99
- :TYPE1 => FLAGS[:UNICODE] | FLAGS[:OEM] | FLAGS[:REQUEST_TARGET] | FLAGS[:NTLM] | FLAGS[:ALWAYS_SIGN] | FLAGS[:NTLM2_KEY],
100
- :TYPE2 => FLAGS[:UNICODE],
101
- :TYPE3 => FLAGS[:UNICODE] | FLAGS[:REQUEST_TARGET] | FLAGS[:NTLM] | FLAGS[:ALWAYS_SIGN] | FLAGS[:NTLM2_KEY]
102
- }
103
-
104
- class EncodeUtil
105
- if RUBY_VERSION == "1.8.7"
106
- require "kconv"
107
-
108
- # Decode a UTF16 string to a ASCII string
109
- # @param [String] str The string to convert
110
- def self.decode_utf16le(str)
111
- Kconv.kconv(swap16(str), Kconv::ASCII, Kconv::UTF16)
112
- end
113
-
114
- # Encodes a ASCII string to a UTF16 string
115
- # @param [String] str The string to convert
116
- def self.encode_utf16le(str)
117
- swap16(Kconv.kconv(str, Kconv::UTF16, Kconv::ASCII))
118
- end
119
-
120
- # Taggle the strings endianness between big/little and little/big
121
- # @param [String] str The string to swap the endianness on
122
- def self.swap16(str)
123
- str.unpack("v*").pack("n*")
124
- end
125
- else # Use native 1.9 string encoding functions
126
-
127
- # Decode a UTF16 string to a ASCII string
128
- # @param [String] str The string to convert
129
- def self.decode_utf16le(str)
130
- str.encode(Encoding::UTF_8, Encoding::UTF_16LE).force_encoding('UTF-8')
131
- end
132
-
133
- # Encodes a ASCII string to a UTF16 string
134
- # @param [String] str The string to convert
135
- # @note This implementation may seem stupid but the problem is that UTF16-LE and UTF-8 are incompatiable
136
- # encodings. This library uses string contatination to build the packet bytes. The end result is that
137
- # you can either marshal the encodings elsewhere of simply know that each time you call encode_utf16le
138
- # the function will convert the string bytes to UTF-16LE and note the encoding as UTF-8 so that byte
139
- # concatination works seamlessly.
140
- def self.encode_utf16le(str)
141
- str = str.force_encoding('UTF-8') if [::Encoding::ASCII_8BIT,::Encoding::US_ASCII].include?(str.encoding)
142
- str.dup.force_encoding('UTF-8').encode(Encoding::UTF_16LE, Encoding::UTF_8).force_encoding('UTF-8')
143
- end
144
- end
145
- end
146
-
147
- class << self
148
-
149
- # Conver the value to a 64-Bit Little Endian Int
150
- # @param [String] val The string to convert
151
- def pack_int64le(val)
152
- [val & 0x00000000ffffffff, val >> 32].pack("V2")
153
- end
154
-
155
- # Builds an array of strings that are 7 characters long
156
- # @param [String] str The string to split
157
- # @api private
158
- def split7(str)
159
- s = str.dup
160
- until s.empty?
161
- (ret ||= []).push s.slice!(0, 7)
162
- end
163
- ret
164
- end
165
-
166
- # Not sure what this is doing
167
- # @param [String] str String to generate keys for
168
- # @api private
169
- def gen_keys(str)
170
- split7(str).map{ |str7|
171
- bits = split7(str7.unpack("B*")[0]).inject('')\
172
- {|ret, tkn| ret += tkn + (tkn.gsub('1', '').size % 2).to_s }
173
- [bits].pack("B*")
174
- }
175
- end
176
-
177
- def apply_des(plain, keys)
178
- dec = OpenSSL::Cipher::DES.new
179
- keys.map {|k|
180
- dec.key = k
181
- dec.encrypt.update(plain)
182
- }
183
- end
184
-
185
- # Generates a Lan Manager Hash
186
- # @param [String] password The password to base the hash on
187
- def lm_hash(password)
188
- keys = gen_keys password.upcase.ljust(14, "\0")
189
- apply_des(LM_MAGIC, keys).join
190
- end
191
-
192
- # Generate a NTLM Hash
193
- # @param [String] password The password to base the hash on
194
- # @option opt :unicode (false) Unicode encode the password
195
- def ntlm_hash(password, opt = {})
196
- pwd = password.dup
197
- unless opt[:unicode]
198
- pwd = EncodeUtil.encode_utf16le(pwd)
199
- end
200
- OpenSSL::Digest::MD4.digest pwd
201
- end
202
-
203
- # Generate a NTLMv2 Hash
204
- # @param [String] user The username
205
- # @param [String] password The password
206
- # @param [String] target The domain or workstaiton to authenticate to
207
- # @option opt :unicode (false) Unicode encode the domain
208
- def ntlmv2_hash(user, password, target, opt={})
209
- ntlmhash = ntlm_hash(password, opt)
210
- userdomain = (user + target).upcase
211
- unless opt[:unicode]
212
- userdomain = EncodeUtil.encode_utf16le(userdomain)
213
- end
214
- OpenSSL::HMAC.digest(OpenSSL::Digest::MD5.new, ntlmhash, userdomain)
215
- end
216
-
217
- def lm_response(arg)
218
- begin
219
- hash = arg[:lm_hash]
220
- chal = arg[:challenge]
221
- rescue
222
- raise ArgumentError
223
- end
224
- chal = NTLM::pack_int64le(chal) if chal.is_a?(Integer)
225
- keys = gen_keys hash.ljust(21, "\0")
226
- apply_des(chal, keys).join
227
- end
228
-
229
- def ntlm_response(arg)
230
- hash = arg[:ntlm_hash]
231
- chal = arg[:challenge]
232
- chal = NTLM::pack_int64le(chal) if chal.is_a?(Integer)
233
- keys = gen_keys hash.ljust(21, "\0")
234
- apply_des(chal, keys).join
235
- end
236
-
237
- def ntlmv2_response(arg, opt = {})
238
- begin
239
- key = arg[:ntlmv2_hash]
240
- chal = arg[:challenge]
241
- ti = arg[:target_info]
242
- rescue
243
- raise ArgumentError
244
- end
245
- chal = NTLM::pack_int64le(chal) if chal.is_a?(Integer)
246
-
247
- if opt[:client_challenge]
248
- cc = opt[:client_challenge]
249
- else
250
- cc = rand(MAX64)
251
- end
252
- cc = NTLM::pack_int64le(cc) if cc.is_a?(Integer)
253
-
254
- if opt[:timestamp]
255
- ts = opt[:timestamp]
256
- else
257
- ts = Time.now.to_i
258
- end
259
- # epoch -> milsec from Jan 1, 1601
260
- ts = 10000000 * (ts + TIME_OFFSET)
261
-
262
- blob = Blob.new
263
- blob.timestamp = ts
264
- blob.challenge = cc
265
- blob.target_info = ti
266
-
267
- bb = blob.serialize
268
- OpenSSL::HMAC.digest(OpenSSL::Digest::MD5.new, key, chal + bb) + bb
269
- end
270
-
271
- def lmv2_response(arg, opt = {})
272
- key = arg[:ntlmv2_hash]
273
- chal = arg[:challenge]
274
-
275
- chal = NTLM::pack_int64le(chal) if chal.is_a?(Integer)
276
-
277
- if opt[:client_challenge]
278
- cc = opt[:client_challenge]
279
- else
280
- cc = rand(MAX64)
281
- end
282
- cc = NTLM::pack_int64le(cc) if cc.is_a?(Integer)
283
-
284
- OpenSSL::HMAC.digest(OpenSSL::Digest::MD5.new, key, chal + cc) + cc
285
- end
286
-
287
- def ntlm2_session(arg, opt = {})
288
- begin
289
- passwd_hash = arg[:ntlm_hash]
290
- chal = arg[:challenge]
291
- rescue
292
- raise ArgumentError
293
- end
294
-
295
- if opt[:client_challenge]
296
- cc = opt[:client_challenge]
297
- else
298
- cc = rand(MAX64)
299
- end
300
- cc = NTLM::pack_int64le(cc) if cc.is_a?(Integer)
301
-
302
- keys = gen_keys passwd_hash.ljust(21, "\0")
303
- session_hash = OpenSSL::Digest::MD5.digest(chal + cc).slice(0, 8)
304
- response = apply_des(session_hash, keys).join
305
- [cc.ljust(24, "\0"), response]
306
- end
307
- end
308
-
309
-
310
- # base classes for primitives
311
- # @private
312
- class Field
313
- attr_accessor :active, :value
314
-
315
- def initialize(opts)
316
- @value = opts[:value]
317
- @active = opts[:active].nil? ? true : opts[:active]
318
- end
319
-
320
- def size
321
- @active ? @size : 0
322
- end
323
- end
324
-
325
- class String < Field
326
- def initialize(opts)
327
- super(opts)
328
- @size = opts[:size]
329
- end
330
-
331
- def parse(str, offset=0)
332
- if @active and str.size >= offset + @size
333
- @value = str[offset, @size]
334
- @size
335
- else
336
- 0
337
- end
338
- end
339
-
340
- def serialize
341
- if @active
342
- @value
343
- else
344
- ""
345
- end
346
- end
347
-
348
- def value=(val)
349
- @value = val
350
- @size = @value.nil? ? 0 : @value.size
351
- @active = (@size > 0)
352
- end
353
- end
354
-
355
- class Int16LE < Field
356
- def initialize(opt)
357
- super(opt)
358
- @size = 2
359
- end
360
- def parse(str, offset=0)
361
- if @active and str.size >= offset + @size
362
- @value = str[offset, @size].unpack("v")[0]
363
- @size
364
- else
365
- 0
366
- end
367
- end
368
-
369
- def serialize
370
- [@value].pack("v")
371
- end
372
- end
373
-
374
- class Int32LE < Field
375
- def initialize(opt)
376
- super(opt)
377
- @size = 4
378
- end
379
-
380
- def parse(str, offset=0)
381
- if @active and str.size >= offset + @size
382
- @value = str.slice(offset, @size).unpack("V")[0]
383
- @size
384
- else
385
- 0
386
- end
387
- end
388
-
389
- def serialize
390
- [@value].pack("V") if @active
391
- end
392
- end
393
-
394
- class Int64LE < Field
395
- def initialize(opt)
396
- super(opt)
397
- @size = 8
398
- end
399
-
400
- def parse(str, offset=0)
401
- if @active and str.size >= offset + @size
402
- d, u = str.slice(offset, @size).unpack("V2")
403
- @value = (u * 0x100000000 + d)
404
- @size
405
- else
406
- 0
407
- end
408
- end
409
-
410
- def serialize
411
- [@value & 0x00000000ffffffff, @value >> 32].pack("V2") if @active
412
- end
413
- end
414
-
415
- # base class of data structure
416
- class FieldSet
417
- class << FieldSet
418
-
419
-
420
- # @macro string_security_buffer
421
- # @method $1
422
- # @method $1=
423
- # @return [String]
424
- def string(name, opts)
425
- add_field(name, String, opts)
426
- end
427
-
428
- # @macro int16le_security_buffer
429
- # @method $1
430
- # @method $1=
431
- # @return [Int16LE]
432
- def int16LE(name, opts)
433
- add_field(name, Int16LE, opts)
434
- end
435
-
436
- # @macro int32le_security_buffer
437
- # @method $1
438
- # @method $1=
439
- # @return [Int32LE]
440
- def int32LE(name, opts)
441
- add_field(name, Int32LE, opts)
442
- end
443
-
444
- # @macro int64le_security_buffer
445
- # @method $1
446
- # @method $1=
447
- # @return [Int64]
448
- def int64LE(name, opts)
449
- add_field(name, Int64LE, opts)
450
- end
451
-
452
- # @macro security_buffer
453
- # @method $1
454
- # @method $1=
455
- # @return [SecurityBuffer]
456
- def security_buffer(name, opts)
457
- add_field(name, SecurityBuffer, opts)
458
- end
459
-
460
- def prototypes
461
- @proto
462
- end
463
-
464
- def names
465
- @proto.map{|n, t, o| n}
466
- end
467
-
468
- def types
469
- @proto.map{|n, t, o| t}
470
- end
471
-
472
- def opts
473
- @proto.map{|n, t, o| o}
474
- end
475
-
476
- private
477
-
478
- def add_field(name, type, opts)
479
- (@proto ||= []).push [name, type, opts]
480
- define_accessor name
481
- end
482
-
483
- def define_accessor(name)
484
- module_eval(<<-End, __FILE__, __LINE__ + 1)
485
- def #{name}
486
- self['#{name}'].value
487
- end
488
-
489
- def #{name}=(val)
490
- self['#{name}'].value = val
491
- end
492
- End
493
- end
494
- end
495
-
496
- def initialize
497
- @alist = self.class.prototypes.map{ |n, t, o| [n, t.new(o)] }
498
- end
499
-
500
- def serialize
501
- @alist.map{|n, f| f.serialize }.join
502
- end
503
-
504
- def parse(str, offset=0)
505
- @alist.inject(offset){|cur, a| cur += a[1].parse(str, cur)}
506
- end
507
-
508
- def size
509
- @alist.inject(0){|sum, a| sum += a[1].size}
510
- end
511
-
512
- def [](name)
513
- a = @alist.assoc(name.to_s.intern)
514
- raise ArgumentError, "no such field: #{name}" unless a
515
- a[1]
516
- end
517
-
518
- def []=(name, val)
519
- a = @alist.assoc(name.to_s.intern)
520
- raise ArgumentError, "no such field: #{name}" unless a
521
- a[1] = val
522
- end
523
-
524
- def enable(name)
525
- self[name].active = true
526
- end
527
-
528
- def disable(name)
529
- self[name].active = false
530
- end
531
- end
532
-
533
- class Blob < FieldSet
534
- int32LE :blob_signature, {:value => BLOB_SIGN}
535
- int32LE :reserved, {:value => 0}
536
- int64LE :timestamp, {:value => 0}
537
- string :challenge, {:value => "", :size => 8}
538
- int32LE :unknown1, {:value => 0}
539
- string :target_info, {:value => "", :size => 0}
540
- int32LE :unknown2, {:value => 0}
541
- end
542
-
543
- class SecurityBuffer < FieldSet
544
-
545
- int16LE :length, {:value => 0}
546
- int16LE :allocated, {:value => 0}
547
- int32LE :offset, {:value => 0}
548
-
549
- attr_accessor :active
550
- def initialize(opts)
551
- super()
552
- @value = opts[:value]
553
- @active = opts[:active].nil? ? true : opts[:active]
554
- @size = 8
555
- end
556
-
557
- def parse(str, offset=0)
558
- if @active and str.size >= offset + @size
559
- super(str, offset)
560
- @value = str[self.offset, self.length]
561
- @size
562
- else
563
- 0
564
- end
565
- end
566
-
567
- def serialize
568
- super if @active
569
- end
570
-
571
- def value
572
- @value
573
- end
574
-
575
- def value=(val)
576
- @value = val
577
- self.length = self.allocated = val.size
578
- end
579
-
580
- def data_size
581
- @active ? @value.size : 0
582
- end
583
- end
584
-
585
- # @private false
586
- class Message < FieldSet
587
- class << Message
588
- def parse(str)
589
- m = Type0.new
590
- m.parse(str)
591
- case m.type
592
- when 1
593
- t = Type1.parse(str)
594
- when 2
595
- t = Type2.parse(str)
596
- when 3
597
- t = Type3.parse(str)
598
- else
599
- raise ArgumentError, "unknown type: #{m.type}"
600
- end
601
- t
602
- end
603
-
604
- def decode64(str)
605
- parse(Base64.decode64(str))
606
- end
607
- end
608
-
609
- def has_flag?(flag)
610
- (self[:flag].value & FLAGS[flag]) == FLAGS[flag]
611
- end
612
-
613
- def set_flag(flag)
614
- self[:flag].value |= FLAGS[flag]
615
- end
616
-
617
- def dump_flags
618
- FLAG_KEYS.each{ |k| print(k, "=", flag?(k), "\n") }
619
- end
620
-
621
- def serialize
622
- deflag
623
- super + security_buffers.map{|n, f| f.value}.join
624
- end
625
-
626
- def encode64
627
- Base64.encode64(serialize).gsub(/\n/, '')
628
- end
629
-
630
- def decode64(str)
631
- parse(Base64.decode64(str))
632
- end
633
-
634
- alias head_size size
635
-
636
- def data_size
637
- security_buffers.inject(0){|sum, a| sum += a[1].data_size}
638
- end
639
-
640
- def size
641
- head_size + data_size
642
- end
643
-
644
-
645
- def security_buffers
646
- @alist.find_all{|n, f| f.instance_of?(SecurityBuffer)}
647
- end
648
-
649
- def deflag
650
- security_buffers.inject(head_size){|cur, a|
651
- a[1].offset = cur
652
- cur += a[1].data_size
653
- }
654
- end
655
-
656
- def data_edge
657
- security_buffers.map{ |n, f| f.active ? f.offset : size}.min
658
- end
659
-
660
- # sub class definitions
661
- class Type0 < Message
662
- string :sign, {:size => 8, :value => SSP_SIGN}
663
- int32LE :type, {:value => 0}
664
- end
665
-
666
- # @private false
667
- class Type1 < Message
668
-
669
- string :sign, {:size => 8, :value => SSP_SIGN}
670
- int32LE :type, {:value => 1}
671
- int32LE :flag, {:value => DEFAULT_FLAGS[:TYPE1] }
672
- security_buffer :domain, {:value => ""}
673
- security_buffer :workstation, {:value => Socket.gethostname }
674
- string :padding, {:size => 0, :value => "", :active => false }
675
-
676
- class << Type1
677
- # Parses a Type 1 Message
678
- # @param [String] str A string containing Type 1 data
679
- # @return [Type1] The parsed Type 1 message
680
- def parse(str)
681
- t = new
682
- t.parse(str)
683
- t
684
- end
685
- end
686
-
687
- # @!visibility private
688
- def parse(str)
689
- super(str)
690
- enable(:domain) if has_flag?(:DOMAIN_SUPPLIED)
691
- enable(:workstation) if has_flag?(:WORKSTATION_SUPPLIED)
692
- super(str)
693
- if ( (len = data_edge - head_size) > 0)
694
- self.padding = "\0" * len
695
- super(str)
696
- end
697
- end
698
- end
699
-
700
-
701
- # @private false
702
- class Type2 < Message
703
-
704
- string :sign, {:size => 8, :value => SSP_SIGN}
705
- int32LE :type, {:value => 2}
706
- security_buffer :target_name, {:size => 0, :value => ""}
707
- int32LE :flag, {:value => DEFAULT_FLAGS[:TYPE2]}
708
- int64LE :challenge, {:value => 0}
709
- int64LE :context, {:value => 0, :active => false}
710
- security_buffer :target_info, {:value => "", :active => false}
711
- string :padding, {:size => 0, :value => "", :active => false }
712
-
713
- class << Type2
714
- # Parse a Type 2 packet
715
- # @param [String] str A string containing Type 2 data
716
- # @return [Type2]
717
- def parse(str)
718
- t = new
719
- t.parse(str)
720
- t
721
- end
722
- end
723
-
724
- # @!visibility private
725
- def parse(str)
726
- super(str)
727
- if has_flag?(:TARGET_INFO)
728
- enable(:context)
729
- enable(:target_info)
730
- super(str)
731
- end
732
- if ( (len = data_edge - head_size) > 0)
733
- self.padding = "\0" * len
734
- super(str)
735
- end
736
- end
737
-
738
- # Generates a Type 3 response based on the Type 2 Information
739
- # @return [Type3]
740
- # @option arg [String] :username The username to authenticate with
741
- # @option arg [String] :password The user's password
742
- # @option arg [String] :domain ('') The domain to authenticate to
743
- # @option opt [String] :workstation (Socket.gethostname) The name of the calling workstation
744
- # @option opt [Boolean] :use_default_target (False) Use the domain supplied by the server in the Type 2 packet
745
- # @note An empty :domain option authenticates to the local machine.
746
- # @note The :use_default_target has presidence over the :domain option
747
- def response(arg, opt = {})
748
- usr = arg[:user]
749
- pwd = arg[:password]
750
- domain = arg[:domain] ? arg[:domain] : ""
751
- if usr.nil? or pwd.nil?
752
- raise ArgumentError, "user and password have to be supplied"
753
- end
754
-
755
- if opt[:workstation]
756
- ws = opt[:workstation]
757
- else
758
- ws = Socket.gethostname
759
- end
760
-
761
- if opt[:client_challenge]
762
- cc = opt[:client_challenge]
763
- else
764
- cc = rand(MAX64)
765
- end
766
- cc = NTLM::pack_int64le(cc) if cc.is_a?(Integer)
767
- opt[:client_challenge] = cc
768
-
769
- if has_flag?(:OEM) and opt[:unicode]
770
- usr = NTLM::EncodeUtil.decode_utf16le(usr)
771
- pwd = NTLM::EncodeUtil.decode_utf16le(pwd)
772
- ws = NTLM::EncodeUtil.decode_utf16le(ws)
773
- domain = NTLM::EncodeUtil.decode_utf16le(domain)
774
- opt[:unicode] = false
775
- end
776
-
777
- if has_flag?(:UNICODE) and !opt[:unicode]
778
- usr = NTLM::EncodeUtil.encode_utf16le(usr)
779
- pwd = NTLM::EncodeUtil.encode_utf16le(pwd)
780
- ws = NTLM::EncodeUtil.encode_utf16le(ws)
781
- domain = NTLM::EncodeUtil.encode_utf16le(domain)
782
- opt[:unicode] = true
783
- end
784
-
785
- if opt[:use_default_target]
786
- domain = self.target_name
787
- end
788
-
789
- ti = self.target_info
790
-
791
- chal = self[:challenge].serialize
792
-
793
- if opt[:ntlmv2]
794
- ar = {:ntlmv2_hash => NTLM::ntlmv2_hash(usr, pwd, domain, opt), :challenge => chal, :target_info => ti}
795
- lm_res = NTLM::lmv2_response(ar, opt)
796
- ntlm_res = NTLM::ntlmv2_response(ar, opt)
797
- elsif has_flag?(:NTLM2_KEY)
798
- ar = {:ntlm_hash => NTLM::ntlm_hash(pwd, opt), :challenge => chal}
799
- lm_res, ntlm_res = NTLM::ntlm2_session(ar, opt)
800
- else
801
- lm_res = NTLM::lm_response(pwd, chal)
802
- ntlm_res = NTLM::ntlm_response(pwd, chal)
803
- end
804
-
805
- Type3.create({
806
- :lm_response => lm_res,
807
- :ntlm_response => ntlm_res,
808
- :domain => domain,
809
- :user => usr,
810
- :workstation => ws,
811
- :flag => self.flag
812
- })
813
- end
814
- end
815
-
816
- # @private false
817
- class Type3 < Message
818
-
819
- string :sign, {:size => 8, :value => SSP_SIGN}
820
- int32LE :type, {:value => 3}
821
- security_buffer :lm_response, {:value => ""}
822
- security_buffer :ntlm_response, {:value => ""}
823
- security_buffer :domain, {:value => ""}
824
- security_buffer :user, {:value => ""}
825
- security_buffer :workstation, {:value => ""}
826
- security_buffer :session_key, {:value => "", :active => false }
827
- int64LE :flag, {:value => 0, :active => false }
828
-
829
- class << Type3
830
- # Parse a Type 3 packet
831
- # @param [String] str A string containing Type 3 data
832
- # @return [Type2]
833
- def parse(str)
834
- t = new
835
- t.parse(str)
836
- t
837
- end
838
-
839
- # Builds a Type 3 packet
840
- # @note All options must be properly encoded with either unicode or oem encoding
841
- # @return [Type3]
842
- # @option arg [String] :lm_response The LM hash
843
- # @option arg [String] :ntlm_response The NTLM hash
844
- # @option arg [String] :domain The domain to authenticate to
845
- # @option arg [String] :workstation The name of the calling workstation
846
- # @option arg [String] :session_key The session key
847
- # @option arg [Integer] :flag Flags for the packet
848
- def create(arg, opt ={})
849
- t = new
850
- t.lm_response = arg[:lm_response]
851
- t.ntlm_response = arg[:ntlm_response]
852
- t.domain = arg[:domain]
853
- t.user = arg[:user]
854
-
855
- if arg[:workstation]
856
- t.workstation = arg[:workstation]
857
- end
858
-
859
- if arg[:session_key]
860
- t.enable(:session_key)
861
- t.session_key = arg[session_key]
862
- end
863
-
864
- if arg[:flag]
865
- t.enable(:session_key)
866
- t.enable(:flag)
867
- t.flag = arg[:flag]
868
- end
869
- t
870
- end
871
- end
872
- end
873
- end
874
- end
875
- end
1
+ # encoding: UTF-8
2
+ #
3
+ # = net/ntlm.rb
4
+ #
5
+ # An NTLM Authentication Library for Ruby
6
+ #
7
+ # This code is a derivative of "dbf2.rb" written by yrock
8
+ # and Minero Aoki. You can find original code here:
9
+ # http://jp.rubyist.net/magazine/?0013-CodeReview
10
+ # -------------------------------------------------------------
11
+ # Copyright (c) 2005,2006 yrock
12
+ #
13
+ # This program is free software.
14
+ # You can distribute/modify this program under the terms of the
15
+ # Ruby License.
16
+ #
17
+ # 2006-02-11 refactored by Minero Aoki
18
+ # -------------------------------------------------------------
19
+ #
20
+ # All protocol information used to write this code stems from
21
+ # "The NTLM Authentication Protocol" by Eric Glass. The author
22
+ # would thank to him for this tremendous work and making it
23
+ # available on the net.
24
+ # http://davenport.sourceforge.net/ntlm.html
25
+ # -------------------------------------------------------------
26
+ # Copyright (c) 2003 Eric Glass
27
+ #
28
+ # Permission to use, copy, modify, and distribute this document
29
+ # for any purpose and without any fee is hereby granted,
30
+ # provided that the above copyright notice and this list of
31
+ # conditions appear in all copies.
32
+ # -------------------------------------------------------------
33
+ #
34
+ # The author also looked Mozilla-Firefox-1.0.7 source code,
35
+ # namely, security/manager/ssl/src/nsNTLMAuthModule.cpp and
36
+ # Jonathan Bastien-Filiatrault's libntlm-ruby.
37
+ # "http://x2a.org/websvn/filedetails.php?
38
+ # repname=libntlm-ruby&path=%2Ftrunk%2Fntlm.rb&sc=1"
39
+ # The latter has a minor bug in its separate_keys function.
40
+ # The third key has to begin from the 14th character of the
41
+ # input string instead of 13th:)
42
+ #--
43
+ # $Id: ntlm.rb,v 1.1 2006/10/05 01:36:52 koheik Exp $
44
+ #++
45
+
46
+
47
+
48
+ module Watobo
49
+ module NTLM
50
+ # @private
51
+ module VERSION
52
+ MAJOR = 0
53
+ MINOR = 3
54
+ TINY = 4
55
+ STRING = [MAJOR, MINOR, TINY].join('.')
56
+ end
57
+
58
+ SSP_SIGN = "NTLMSSP\0"
59
+ BLOB_SIGN = 0x00000101
60
+ LM_MAGIC = "KGS!@\#$%"
61
+ TIME_OFFSET = 11644473600
62
+ MAX64 = 0xffffffffffffffff
63
+
64
+ FLAGS = {
65
+ :UNICODE => 0x00000001,
66
+ :OEM => 0x00000002,
67
+ :REQUEST_TARGET => 0x00000004,
68
+ :MBZ9 => 0x00000008,
69
+ :SIGN => 0x00000010,
70
+ :SEAL => 0x00000020,
71
+ :NEG_DATAGRAM => 0x00000040,
72
+ :NETWARE => 0x00000100,
73
+ :NTLM => 0x00000200,
74
+ :NEG_NT_ONLY => 0x00000400,
75
+ :MBZ7 => 0x00000800,
76
+ :DOMAIN_SUPPLIED => 0x00001000,
77
+ :WORKSTATION_SUPPLIED => 0x00002000,
78
+ :LOCAL_CALL => 0x00004000,
79
+ :ALWAYS_SIGN => 0x00008000,
80
+ :TARGET_TYPE_DOMAIN => 0x00010000,
81
+ :TARGET_INFO => 0x00800000,
82
+ :NTLM2_KEY => 0x00080000,
83
+ :KEY128 => 0x20000000,
84
+ :KEY56 => 0x80000000
85
+ }.freeze
86
+
87
+ FLAG_KEYS = FLAGS.keys.sort{|a, b| FLAGS[a] <=> FLAGS[b] }
88
+
89
+ DEFAULT_FLAGS = {
90
+ :TYPE1 => FLAGS[:UNICODE] | FLAGS[:OEM] | FLAGS[:REQUEST_TARGET] | FLAGS[:NTLM] | FLAGS[:ALWAYS_SIGN] | FLAGS[:NTLM2_KEY],
91
+ :TYPE2 => FLAGS[:UNICODE],
92
+ :TYPE3 => FLAGS[:UNICODE] | FLAGS[:REQUEST_TARGET] | FLAGS[:NTLM] | FLAGS[:ALWAYS_SIGN] | FLAGS[:NTLM2_KEY]
93
+ }
94
+
95
+ class EncodeUtil
96
+ if RUBY_VERSION == "1.8.7"
97
+ require "kconv"
98
+
99
+ # Decode a UTF16 string to a ASCII string
100
+ # @param [String] str The string to convert
101
+ def self.decode_utf16le(str)
102
+ Kconv.kconv(swap16(str), Kconv::ASCII, Kconv::UTF16)
103
+ end
104
+
105
+ # Encodes a ASCII string to a UTF16 string
106
+ # @param [String] str The string to convert
107
+ def self.encode_utf16le(str)
108
+ swap16(Kconv.kconv(str, Kconv::UTF16, Kconv::ASCII))
109
+ end
110
+
111
+ # Taggle the strings endianness between big/little and little/big
112
+ # @param [String] str The string to swap the endianness on
113
+ def self.swap16(str)
114
+ str.unpack("v*").pack("n*")
115
+ end
116
+ else # Use native 1.9 string encoding functions
117
+
118
+ # Decode a UTF16 string to a ASCII string
119
+ # @param [String] str The string to convert
120
+ def self.decode_utf16le(str)
121
+ str.encode(Encoding::UTF_8, Encoding::UTF_16LE).force_encoding('UTF-8')
122
+ end
123
+
124
+ # Encodes a ASCII string to a UTF16 string
125
+ # @param [String] str The string to convert
126
+ # @note This implementation may seem stupid but the problem is that UTF16-LE and UTF-8 are incompatiable
127
+ # encodings. This library uses string contatination to build the packet bytes. The end result is that
128
+ # you can either marshal the encodings elsewhere of simply know that each time you call encode_utf16le
129
+ # the function will convert the string bytes to UTF-16LE and note the encoding as UTF-8 so that byte
130
+ # concatination works seamlessly.
131
+ def self.encode_utf16le(str)
132
+ str = str.force_encoding('UTF-8') if [::Encoding::ASCII_8BIT,::Encoding::US_ASCII].include?(str.encoding)
133
+ str.dup.force_encoding('UTF-8').encode(Encoding::UTF_16LE, Encoding::UTF_8).force_encoding('UTF-8')
134
+ end
135
+ end
136
+ end
137
+
138
+ class << self
139
+
140
+ # Conver the value to a 64-Bit Little Endian Int
141
+ # @param [String] val The string to convert
142
+ def pack_int64le(val)
143
+ [val & 0x00000000ffffffff, val >> 32].pack("V2")
144
+ end
145
+
146
+ # Builds an array of strings that are 7 characters long
147
+ # @param [String] str The string to split
148
+ # @api private
149
+ def split7(str)
150
+ s = str.dup
151
+ until s.empty?
152
+ (ret ||= []).push s.slice!(0, 7)
153
+ end
154
+ ret
155
+ end
156
+
157
+ # Not sure what this is doing
158
+ # @param [String] str String to generate keys for
159
+ # @api private
160
+ def gen_keys(str)
161
+ split7(str).map{ |str7|
162
+ bits = split7(str7.unpack("B*")[0]).inject('')\
163
+ {|ret, tkn| ret += tkn + (tkn.gsub('1', '').size % 2).to_s }
164
+ [bits].pack("B*")
165
+ }
166
+ end
167
+
168
+ def apply_des(plain, keys)
169
+ dec = OpenSSL::Cipher::DES.new
170
+ keys.map {|k|
171
+ dec.key = k
172
+ dec.encrypt.update(plain)
173
+ }
174
+ end
175
+
176
+ # Generates a Lan Manager Hash
177
+ # @param [String] password The password to base the hash on
178
+ def lm_hash(password)
179
+ keys = gen_keys password.upcase.ljust(14, "\0")
180
+ apply_des(LM_MAGIC, keys).join
181
+ end
182
+
183
+ # Generate a NTLM Hash
184
+ # @param [String] password The password to base the hash on
185
+ # @option opt :unicode (false) Unicode encode the password
186
+ def ntlm_hash(password, opt = {})
187
+ pwd = password.dup
188
+ unless opt[:unicode]
189
+ pwd = EncodeUtil.encode_utf16le(pwd)
190
+ end
191
+ OpenSSL::Digest::MD4.digest pwd
192
+ end
193
+
194
+ # Generate a NTLMv2 Hash
195
+ # @param [String] user The username
196
+ # @param [String] password The password
197
+ # @param [String] target The domain or workstaiton to authenticate to
198
+ # @option opt :unicode (false) Unicode encode the domain
199
+ def ntlmv2_hash(user, password, target, opt={})
200
+ ntlmhash = ntlm_hash(password, opt)
201
+ userdomain = (user + target).upcase
202
+ unless opt[:unicode]
203
+ userdomain = EncodeUtil.encode_utf16le(userdomain)
204
+ end
205
+ OpenSSL::HMAC.digest(OpenSSL::Digest::MD5.new, ntlmhash, userdomain)
206
+ end
207
+
208
+ def lm_response(arg)
209
+ begin
210
+ hash = arg[:lm_hash]
211
+ chal = arg[:challenge]
212
+ rescue
213
+ raise ArgumentError
214
+ end
215
+ chal = NTLM::pack_int64le(chal) if chal.is_a?(Integer)
216
+ keys = gen_keys hash.ljust(21, "\0")
217
+ apply_des(chal, keys).join
218
+ end
219
+
220
+ def ntlm_response(arg)
221
+ hash = arg[:ntlm_hash]
222
+ chal = arg[:challenge]
223
+ chal = NTLM::pack_int64le(chal) if chal.is_a?(Integer)
224
+ keys = gen_keys hash.ljust(21, "\0")
225
+ apply_des(chal, keys).join
226
+ end
227
+
228
+ def ntlmv2_response(arg, opt = {})
229
+ begin
230
+ key = arg[:ntlmv2_hash]
231
+ chal = arg[:challenge]
232
+ ti = arg[:target_info]
233
+ rescue
234
+ raise ArgumentError
235
+ end
236
+ chal = NTLM::pack_int64le(chal) if chal.is_a?(Integer)
237
+
238
+ if opt[:client_challenge]
239
+ cc = opt[:client_challenge]
240
+ else
241
+ cc = rand(MAX64)
242
+ end
243
+ cc = NTLM::pack_int64le(cc) if cc.is_a?(Integer)
244
+
245
+ if opt[:timestamp]
246
+ ts = opt[:timestamp]
247
+ else
248
+ ts = Time.now.to_i
249
+ end
250
+ # epoch -> milsec from Jan 1, 1601
251
+ ts = 10000000 * (ts + TIME_OFFSET)
252
+
253
+ blob = Blob.new
254
+ blob.timestamp = ts
255
+ blob.challenge = cc
256
+ blob.target_info = ti
257
+
258
+ bb = blob.serialize
259
+ OpenSSL::HMAC.digest(OpenSSL::Digest::MD5.new, key, chal + bb) + bb
260
+ end
261
+
262
+ def lmv2_response(arg, opt = {})
263
+ key = arg[:ntlmv2_hash]
264
+ chal = arg[:challenge]
265
+
266
+ chal = NTLM::pack_int64le(chal) if chal.is_a?(Integer)
267
+
268
+ if opt[:client_challenge]
269
+ cc = opt[:client_challenge]
270
+ else
271
+ cc = rand(MAX64)
272
+ end
273
+ cc = NTLM::pack_int64le(cc) if cc.is_a?(Integer)
274
+
275
+ OpenSSL::HMAC.digest(OpenSSL::Digest::MD5.new, key, chal + cc) + cc
276
+ end
277
+
278
+ def ntlm2_session(arg, opt = {})
279
+ begin
280
+ passwd_hash = arg[:ntlm_hash]
281
+ chal = arg[:challenge]
282
+ rescue
283
+ raise ArgumentError
284
+ end
285
+
286
+ if opt[:client_challenge]
287
+ cc = opt[:client_challenge]
288
+ else
289
+ cc = rand(MAX64)
290
+ end
291
+ cc = NTLM::pack_int64le(cc) if cc.is_a?(Integer)
292
+
293
+ keys = gen_keys passwd_hash.ljust(21, "\0")
294
+ session_hash = OpenSSL::Digest::MD5.digest(chal + cc).slice(0, 8)
295
+ response = apply_des(session_hash, keys).join
296
+ [cc.ljust(24, "\0"), response]
297
+ end
298
+ end
299
+
300
+
301
+ # base classes for primitives
302
+ # @private
303
+ class Field
304
+ attr_accessor :active, :value
305
+
306
+ def initialize(opts)
307
+ @value = opts[:value]
308
+ @active = opts[:active].nil? ? true : opts[:active]
309
+ end
310
+
311
+ def size
312
+ @active ? @size : 0
313
+ end
314
+ end
315
+
316
+ class String < Field
317
+ def initialize(opts)
318
+ super(opts)
319
+ @size = opts[:size]
320
+ end
321
+
322
+ def parse(str, offset=0)
323
+ if @active and str.size >= offset + @size
324
+ @value = str[offset, @size]
325
+ @size
326
+ else
327
+ 0
328
+ end
329
+ end
330
+
331
+ def serialize
332
+ if @active
333
+ @value
334
+ else
335
+ ""
336
+ end
337
+ end
338
+
339
+ def value=(val)
340
+ @value = val
341
+ @size = @value.nil? ? 0 : @value.size
342
+ @active = (@size > 0)
343
+ end
344
+ end
345
+
346
+ class Int16LE < Field
347
+ def initialize(opt)
348
+ super(opt)
349
+ @size = 2
350
+ end
351
+ def parse(str, offset=0)
352
+ if @active and str.size >= offset + @size
353
+ @value = str[offset, @size].unpack("v")[0]
354
+ @size
355
+ else
356
+ 0
357
+ end
358
+ end
359
+
360
+ def serialize
361
+ [@value].pack("v")
362
+ end
363
+ end
364
+
365
+ class Int32LE < Field
366
+ def initialize(opt)
367
+ super(opt)
368
+ @size = 4
369
+ end
370
+
371
+ def parse(str, offset=0)
372
+ if @active and str.size >= offset + @size
373
+ @value = str.slice(offset, @size).unpack("V")[0]
374
+ @size
375
+ else
376
+ 0
377
+ end
378
+ end
379
+
380
+ def serialize
381
+ [@value].pack("V") if @active
382
+ end
383
+ end
384
+
385
+ class Int64LE < Field
386
+ def initialize(opt)
387
+ super(opt)
388
+ @size = 8
389
+ end
390
+
391
+ def parse(str, offset=0)
392
+ if @active and str.size >= offset + @size
393
+ d, u = str.slice(offset, @size).unpack("V2")
394
+ @value = (u * 0x100000000 + d)
395
+ @size
396
+ else
397
+ 0
398
+ end
399
+ end
400
+
401
+ def serialize
402
+ [@value & 0x00000000ffffffff, @value >> 32].pack("V2") if @active
403
+ end
404
+ end
405
+
406
+ # base class of data structure
407
+ class FieldSet
408
+ class << FieldSet
409
+
410
+
411
+ # @macro string_security_buffer
412
+ # @method $1
413
+ # @method $1=
414
+ # @return [String]
415
+ def string(name, opts)
416
+ add_field(name, String, opts)
417
+ end
418
+
419
+ # @macro int16le_security_buffer
420
+ # @method $1
421
+ # @method $1=
422
+ # @return [Int16LE]
423
+ def int16LE(name, opts)
424
+ add_field(name, Int16LE, opts)
425
+ end
426
+
427
+ # @macro int32le_security_buffer
428
+ # @method $1
429
+ # @method $1=
430
+ # @return [Int32LE]
431
+ def int32LE(name, opts)
432
+ add_field(name, Int32LE, opts)
433
+ end
434
+
435
+ # @macro int64le_security_buffer
436
+ # @method $1
437
+ # @method $1=
438
+ # @return [Int64]
439
+ def int64LE(name, opts)
440
+ add_field(name, Int64LE, opts)
441
+ end
442
+
443
+ # @macro security_buffer
444
+ # @method $1
445
+ # @method $1=
446
+ # @return [SecurityBuffer]
447
+ def security_buffer(name, opts)
448
+ add_field(name, SecurityBuffer, opts)
449
+ end
450
+
451
+ def prototypes
452
+ @proto
453
+ end
454
+
455
+ def names
456
+ @proto.map{|n, t, o| n}
457
+ end
458
+
459
+ def types
460
+ @proto.map{|n, t, o| t}
461
+ end
462
+
463
+ def opts
464
+ @proto.map{|n, t, o| o}
465
+ end
466
+
467
+ private
468
+
469
+ def add_field(name, type, opts)
470
+ (@proto ||= []).push [name, type, opts]
471
+ define_accessor name
472
+ end
473
+
474
+ def define_accessor(name)
475
+ module_eval(<<-End, __FILE__, __LINE__ + 1)
476
+ def #{name}
477
+ self['#{name}'].value
478
+ end
479
+
480
+ def #{name}=(val)
481
+ self['#{name}'].value = val
482
+ end
483
+ End
484
+ end
485
+ end
486
+
487
+ def initialize
488
+ @alist = self.class.prototypes.map{ |n, t, o| [n, t.new(o)] }
489
+ end
490
+
491
+ def serialize
492
+ @alist.map{|n, f| f.serialize }.join
493
+ end
494
+
495
+ def parse(str, offset=0)
496
+ @alist.inject(offset){|cur, a| cur += a[1].parse(str, cur)}
497
+ end
498
+
499
+ def size
500
+ @alist.inject(0){|sum, a| sum += a[1].size}
501
+ end
502
+
503
+ def [](name)
504
+ a = @alist.assoc(name.to_s.intern)
505
+ raise ArgumentError, "no such field: #{name}" unless a
506
+ a[1]
507
+ end
508
+
509
+ def []=(name, val)
510
+ a = @alist.assoc(name.to_s.intern)
511
+ raise ArgumentError, "no such field: #{name}" unless a
512
+ a[1] = val
513
+ end
514
+
515
+ def enable(name)
516
+ self[name].active = true
517
+ end
518
+
519
+ def disable(name)
520
+ self[name].active = false
521
+ end
522
+ end
523
+
524
+ class Blob < FieldSet
525
+ int32LE :blob_signature, {:value => BLOB_SIGN}
526
+ int32LE :reserved, {:value => 0}
527
+ int64LE :timestamp, {:value => 0}
528
+ string :challenge, {:value => "", :size => 8}
529
+ int32LE :unknown1, {:value => 0}
530
+ string :target_info, {:value => "", :size => 0}
531
+ int32LE :unknown2, {:value => 0}
532
+ end
533
+
534
+ class SecurityBuffer < FieldSet
535
+
536
+ int16LE :length, {:value => 0}
537
+ int16LE :allocated, {:value => 0}
538
+ int32LE :offset, {:value => 0}
539
+
540
+ attr_accessor :active
541
+ def initialize(opts)
542
+ super()
543
+ @value = opts[:value]
544
+ @active = opts[:active].nil? ? true : opts[:active]
545
+ @size = 8
546
+ end
547
+
548
+ def parse(str, offset=0)
549
+ if @active and str.size >= offset + @size
550
+ super(str, offset)
551
+ @value = str[self.offset, self.length]
552
+ @size
553
+ else
554
+ 0
555
+ end
556
+ end
557
+
558
+ def serialize
559
+ super if @active
560
+ end
561
+
562
+ def value
563
+ @value
564
+ end
565
+
566
+ def value=(val)
567
+ @value = val
568
+ self.length = self.allocated = val.size
569
+ end
570
+
571
+ def data_size
572
+ @active ? @value.size : 0
573
+ end
574
+ end
575
+
576
+ # @private false
577
+ class Message < FieldSet
578
+ class << Message
579
+ def parse(str)
580
+ m = Type0.new
581
+ m.parse(str)
582
+ case m.type
583
+ when 1
584
+ t = Type1.parse(str)
585
+ when 2
586
+ t = Type2.parse(str)
587
+ when 3
588
+ t = Type3.parse(str)
589
+ else
590
+ raise ArgumentError, "unknown type: #{m.type}"
591
+ end
592
+ t
593
+ end
594
+
595
+ def decode64(str)
596
+ parse(Base64.decode64(str))
597
+ end
598
+ end
599
+
600
+ def has_flag?(flag)
601
+ (self[:flag].value & FLAGS[flag]) == FLAGS[flag]
602
+ end
603
+
604
+ def set_flag(flag)
605
+ self[:flag].value |= FLAGS[flag]
606
+ end
607
+
608
+ def dump_flags
609
+ FLAG_KEYS.each{ |k| print(k, "=", flag?(k), "\n") }
610
+ end
611
+
612
+ def serialize
613
+ deflag
614
+ super + security_buffers.map{|n, f| f.value}.join
615
+ end
616
+
617
+ def encode64
618
+ Base64.encode64(serialize).gsub(/\n/, '')
619
+ end
620
+
621
+ def decode64(str)
622
+ parse(Base64.decode64(str))
623
+ end
624
+
625
+ alias head_size size
626
+
627
+ def data_size
628
+ security_buffers.inject(0){|sum, a| sum += a[1].data_size}
629
+ end
630
+
631
+ def size
632
+ head_size + data_size
633
+ end
634
+
635
+
636
+ def security_buffers
637
+ @alist.find_all{|n, f| f.instance_of?(SecurityBuffer)}
638
+ end
639
+
640
+ def deflag
641
+ security_buffers.inject(head_size){|cur, a|
642
+ a[1].offset = cur
643
+ cur += a[1].data_size
644
+ }
645
+ end
646
+
647
+ def data_edge
648
+ security_buffers.map{ |n, f| f.active ? f.offset : size}.min
649
+ end
650
+
651
+ # sub class definitions
652
+ class Type0 < Message
653
+ string :sign, {:size => 8, :value => SSP_SIGN}
654
+ int32LE :type, {:value => 0}
655
+ end
656
+
657
+ # @private false
658
+ class Type1 < Message
659
+
660
+ string :sign, {:size => 8, :value => SSP_SIGN}
661
+ int32LE :type, {:value => 1}
662
+ int32LE :flag, {:value => DEFAULT_FLAGS[:TYPE1] }
663
+ security_buffer :domain, {:value => ""}
664
+ security_buffer :workstation, {:value => Socket.gethostname }
665
+ string :padding, {:size => 0, :value => "", :active => false }
666
+
667
+ class << Type1
668
+ # Parses a Type 1 Message
669
+ # @param [String] str A string containing Type 1 data
670
+ # @return [Type1] The parsed Type 1 message
671
+ def parse(str)
672
+ t = new
673
+ t.parse(str)
674
+ t
675
+ end
676
+ end
677
+
678
+ # @!visibility private
679
+ def parse(str)
680
+ super(str)
681
+ enable(:domain) if has_flag?(:DOMAIN_SUPPLIED)
682
+ enable(:workstation) if has_flag?(:WORKSTATION_SUPPLIED)
683
+ super(str)
684
+ if ( (len = data_edge - head_size) > 0)
685
+ self.padding = "\0" * len
686
+ super(str)
687
+ end
688
+ end
689
+ end
690
+
691
+
692
+ # @private false
693
+ class Type2 < Message
694
+
695
+ string :sign, {:size => 8, :value => SSP_SIGN}
696
+ int32LE :type, {:value => 2}
697
+ security_buffer :target_name, {:size => 0, :value => ""}
698
+ int32LE :flag, {:value => DEFAULT_FLAGS[:TYPE2]}
699
+ int64LE :challenge, {:value => 0}
700
+ int64LE :context, {:value => 0, :active => false}
701
+ security_buffer :target_info, {:value => "", :active => false}
702
+ string :padding, {:size => 0, :value => "", :active => false }
703
+
704
+ class << Type2
705
+ # Parse a Type 2 packet
706
+ # @param [String] str A string containing Type 2 data
707
+ # @return [Type2]
708
+ def parse(str)
709
+ t = new
710
+ t.parse(str)
711
+ t
712
+ end
713
+ end
714
+
715
+ # @!visibility private
716
+ def parse(str)
717
+ super(str)
718
+ if has_flag?(:TARGET_INFO)
719
+ enable(:context)
720
+ enable(:target_info)
721
+ super(str)
722
+ end
723
+ if ( (len = data_edge - head_size) > 0)
724
+ self.padding = "\0" * len
725
+ super(str)
726
+ end
727
+ end
728
+
729
+ # Generates a Type 3 response based on the Type 2 Information
730
+ # @return [Type3]
731
+ # @option arg [String] :username The username to authenticate with
732
+ # @option arg [String] :password The user's password
733
+ # @option arg [String] :domain ('') The domain to authenticate to
734
+ # @option opt [String] :workstation (Socket.gethostname) The name of the calling workstation
735
+ # @option opt [Boolean] :use_default_target (False) Use the domain supplied by the server in the Type 2 packet
736
+ # @note An empty :domain option authenticates to the local machine.
737
+ # @note The :use_default_target has presidence over the :domain option
738
+ def response(arg, opt = {})
739
+ usr = arg[:user]
740
+ pwd = arg[:password]
741
+ domain = arg[:domain] ? arg[:domain] : ""
742
+ if usr.nil? or pwd.nil?
743
+ raise ArgumentError, "user and password have to be supplied"
744
+ end
745
+
746
+ if opt[:workstation]
747
+ ws = opt[:workstation]
748
+ else
749
+ ws = Socket.gethostname
750
+ end
751
+
752
+ if opt[:client_challenge]
753
+ cc = opt[:client_challenge]
754
+ else
755
+ cc = rand(MAX64)
756
+ end
757
+ cc = NTLM::pack_int64le(cc) if cc.is_a?(Integer)
758
+ opt[:client_challenge] = cc
759
+
760
+ if has_flag?(:OEM) and opt[:unicode]
761
+ usr = NTLM::EncodeUtil.decode_utf16le(usr)
762
+ pwd = NTLM::EncodeUtil.decode_utf16le(pwd)
763
+ ws = NTLM::EncodeUtil.decode_utf16le(ws)
764
+ domain = NTLM::EncodeUtil.decode_utf16le(domain)
765
+ opt[:unicode] = false
766
+ end
767
+
768
+ if has_flag?(:UNICODE) and !opt[:unicode]
769
+ usr = NTLM::EncodeUtil.encode_utf16le(usr)
770
+ pwd = NTLM::EncodeUtil.encode_utf16le(pwd)
771
+ ws = NTLM::EncodeUtil.encode_utf16le(ws)
772
+ domain = NTLM::EncodeUtil.encode_utf16le(domain)
773
+ opt[:unicode] = true
774
+ end
775
+
776
+ if opt[:use_default_target]
777
+ domain = self.target_name
778
+ end
779
+
780
+ ti = self.target_info
781
+
782
+ chal = self[:challenge].serialize
783
+
784
+ if opt[:ntlmv2]
785
+ ar = {:ntlmv2_hash => NTLM::ntlmv2_hash(usr, pwd, domain, opt), :challenge => chal, :target_info => ti}
786
+ lm_res = NTLM::lmv2_response(ar, opt)
787
+ ntlm_res = NTLM::ntlmv2_response(ar, opt)
788
+ elsif has_flag?(:NTLM2_KEY)
789
+ ar = {:ntlm_hash => NTLM::ntlm_hash(pwd, opt), :challenge => chal}
790
+ lm_res, ntlm_res = NTLM::ntlm2_session(ar, opt)
791
+ else
792
+ lm_res = NTLM::lm_response(pwd, chal)
793
+ ntlm_res = NTLM::ntlm_response(pwd, chal)
794
+ end
795
+
796
+ Type3.create({
797
+ :lm_response => lm_res,
798
+ :ntlm_response => ntlm_res,
799
+ :domain => domain,
800
+ :user => usr,
801
+ :workstation => ws,
802
+ :flag => self.flag
803
+ })
804
+ end
805
+ end
806
+
807
+ # @private false
808
+ class Type3 < Message
809
+
810
+ string :sign, {:size => 8, :value => SSP_SIGN}
811
+ int32LE :type, {:value => 3}
812
+ security_buffer :lm_response, {:value => ""}
813
+ security_buffer :ntlm_response, {:value => ""}
814
+ security_buffer :domain, {:value => ""}
815
+ security_buffer :user, {:value => ""}
816
+ security_buffer :workstation, {:value => ""}
817
+ security_buffer :session_key, {:value => "", :active => false }
818
+ int64LE :flag, {:value => 0, :active => false }
819
+
820
+ class << Type3
821
+ # Parse a Type 3 packet
822
+ # @param [String] str A string containing Type 3 data
823
+ # @return [Type2]
824
+ def parse(str)
825
+ t = new
826
+ t.parse(str)
827
+ t
828
+ end
829
+
830
+ # Builds a Type 3 packet
831
+ # @note All options must be properly encoded with either unicode or oem encoding
832
+ # @return [Type3]
833
+ # @option arg [String] :lm_response The LM hash
834
+ # @option arg [String] :ntlm_response The NTLM hash
835
+ # @option arg [String] :domain The domain to authenticate to
836
+ # @option arg [String] :workstation The name of the calling workstation
837
+ # @option arg [String] :session_key The session key
838
+ # @option arg [Integer] :flag Flags for the packet
839
+ def create(arg, opt ={})
840
+ t = new
841
+ t.lm_response = arg[:lm_response]
842
+ t.ntlm_response = arg[:ntlm_response]
843
+ t.domain = arg[:domain]
844
+ t.user = arg[:user]
845
+
846
+ if arg[:workstation]
847
+ t.workstation = arg[:workstation]
848
+ end
849
+
850
+ if arg[:session_key]
851
+ t.enable(:session_key)
852
+ t.session_key = arg[session_key]
853
+ end
854
+
855
+ if arg[:flag]
856
+ t.enable(:session_key)
857
+ t.enable(:flag)
858
+ t.flag = arg[:flag]
859
+ end
860
+ t
861
+ end
862
+ end
863
+ end
864
+ end
865
+ end
866
+ end