Fix typos and clarify documentation for jupyter_login

This commit is contained in:
Spencer McIntyre 2020-08-10 09:47:59 -04:00
parent 5e5922a1c4
commit c57391501a
3 changed files with 46 additions and 24 deletions

View File

@ -11,10 +11,10 @@ original IPython Notebook system. This module is compatible with both standard J
### Installation
1. Install the latest version of Jupyter from PyPi using pip: `pip install notebook`. The "notebook" package is the core
application and is the one whose version number is referenced.
1. Start Jupyter using `jupyter notebook`, new installs will randomly generate an authentication token and open the
browser with it
1. As of [version 5.3][2], the user will be prompted to set a password the first time they open the UI
application and is the one whose version number is used as the Jupyter version number referred to in this document.
1. Start Jupyter using `jupyter notebook`
* New installs will randomly generate an authentication token and open the browser with it
* As of [version 5.3][2], the user will be prompted to set a password the first time they open the UI
1. With the password set, the module can be tested
## Verification Steps
@ -25,13 +25,15 @@ original IPython Notebook system. This module is compatible with both standard J
1. Set the `RHOSTS` option
* With no other options set, this will only check if authentication is required
1. Do: `run`
1. You should see login attempts
1. You should the server version
1. If password options (such as `PASS_FILE`) where specified, and the server requires authentication then you should see
login attempts
## Options
## Scenarios
### Jupyte Notebook 4.3.0
### Jupyter Notebook 4.3.0 With No Authentication Requirement
```
msf5 > use auxiliary/scanner/http/jupyter_login
@ -48,5 +50,21 @@ msf5 auxiliary(scanner/http/jupyter_login) > run
msf5 auxiliary(scanner/http/jupyter_login) >
```
### Jupyter Notebook 6.0.2 With A Password Set
```
msf5 > use auxiliary/scanner/http/jupyter_login
msf5 auxiliary(scanner/http/jupyter_login) > set PASS_FILE /tmp/passwords.txt
PASS_FILE => /tmp/passwords.txt
msf5 auxiliary(scanner/http/jupyter_login) > run
[*] 192.168.159.128:8888 - The server responded that it is running Jupyter version: 6.0.2
[-] 192.168.159.128:8888 - LOGIN FAILED: :Password (Incorrect)
[+] 192.168.159.128:8888 - Login Successful: :Password1
[*] Scanned 1 of 1 hosts (100% complete)
[*] Auxiliary module execution completed
msf5 auxiliary(scanner/http/jupyter_login) >
```
[1]: https://jupyter-notebook.readthedocs.io/en/stable/changelog.html#release-4-3
[2]: https://jupyter-notebook.readthedocs.io/en/stable/public_server.html#automatic-password-setup

View File

@ -32,8 +32,9 @@ module Metasploit
begin
res = send_request({'method'=> 'GET', 'uri' => uri})
vars_post = {'password' => credential.private }
# versions < 4.3.1 do not use this field
unless (node = res.get_html_document.xpath('//form//input[@name="_xsrf"]')).empty?
# versions < 4.3.1 do not use this field
vars_post['_xsrf'] = node.first['value']
end
@ -49,7 +50,7 @@ module Metasploit
else
result_opts.merge!(status: Metasploit::Model::Login::Status::INCORRECT, proof: res)
end
rescue ::EOFError, Errno::ETIMEDOUT ,Errno::ECONNRESET, Rex::ConnectionError, OpenSSL::SSL::SSLError, ::Timeout::Error => e
rescue ::EOFError, Errno::ETIMEDOUT, Errno::ECONNRESET, Rex::ConnectionError, OpenSSL::SSL::SSLError, ::Timeout::Error => e
result_opts.merge!(status: Metasploit::Model::Login::Status::UNABLE_TO_CONNECT, proof: e)
end
Result.new(result_opts)

View File

@ -14,21 +14,22 @@ class MetasploitModule < Msf::Auxiliary
def initialize
super(
'Name' => 'Jupyter Login Utility',
'Description' => %q{
'Name' => 'Jupyter Login Utility',
'Description' => %q{
This module checks if authentication is required on a Jupyter Lab or Notebook server. If it is, this module will
bruteforce the password. Jupyter only requires a password to authenticate, usernames are not used. This module
is compatible with versions 4.3.0 (released 2016-12-08) and newer.
},
'Author' => [ 'Spencer McIntyre' ],
'License' => MSF_LICENSE
'Author' => [ 'Spencer McIntyre' ],
'License' => MSF_LICENSE
)
register_options(
[
OptString.new('TARGETURI', [ true, 'The path to the Jupyter application', '/' ]),
OptString.new('TARGETURI', [ true, 'The path to the Jupyter application', '/' ]),
Opt::RPORT(8888)
])
]
)
deregister_options('PASSWORD_SPRAY')
deregister_options('DB_ALL_CREDS', 'DB_ALL_USERS', 'HttpUsername', 'STOP_ON_SUCCESS', 'USERNAME', 'USERPASS_FILE', 'USER_AS_PASS', 'USER_FILE')
@ -36,15 +37,17 @@ class MetasploitModule < Msf::Auxiliary
register_autofilter_ports([ 80, 443, 8888 ])
end
def requires_password?(ip)
def requires_password?(_ip)
res = send_request_cgi({
'method' => 'GET',
'uri' => normalize_uri(target_uri.path, 'tree')
})
return false if res&.code == 200
destination = res.headers['Location'].split('?', 2)[0]
return true if destination.ends_with?(normalize_uri(target_uri.path, 'login'))
return true if destination.end_with?(normalize_uri(target_uri.path, 'login'))
fail_with(Failure::UnexpectedReply, "#{peer} - The server responded with a redirect that did not match a known fingerprint")
end
@ -63,12 +66,12 @@ class MetasploitModule < Msf::Auxiliary
unless requires_password?(ip)
print_good "#{peer} - No password is required."
report_vuln(
:host => ip,
:port => rport,
:proto => 'tcp',
:sname => (ssl ? 'https' : 'http'),
:name => "Unauthenticated Jupyter Access",
:info => "Module #{self.fullname} confirmed access to the Jupyter application with no authentication"
host: ip,
port: rport,
proto: 'tcp',
sname: (ssl ? 'https' : 'http'),
name: 'Unauthenticated Jupyter Access',
info: "Module #{fullname} confirmed unauthenticated access to the Jupyter application"
)
return
end
@ -94,8 +97,8 @@ class MetasploitModule < Msf::Auxiliary
scanner.scan! do |result|
credential_data = result.to_h
credential_data.merge!(
module_fullname: fullname,
workspace_id: myworkspace_id
module_fullname: fullname,
workspace_id: myworkspace_id
)
if result.success?
credential_core = create_credential(credential_data)