Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update deprecated report_auth_info in various modules #18398

Merged
merged 9 commits into from
Jan 16, 2024

Conversation

errorxyz
Copy link
Contributor

Updates the deprecated report_auth_info in various modules to use the new credential API instead.
Updates the spec file for lotus_domino_password_hashes to comply with the new API

Related to #10314

Verification

List the steps needed to make sure this thing works

  • Start msfconsole
  • use <module>
  • ...
  • Verify the credentials are stored using the proper API

@errorxyz
Copy link
Contributor Author

Any reviews or suggestions? This is my first code PR.

@errorxyz errorxyz closed this Oct 9, 2023
@errorxyz errorxyz deleted the dev branch October 9, 2023 09:21
@errorxyz errorxyz restored the dev branch October 9, 2023 09:24
@errorxyz errorxyz reopened this Oct 9, 2023
@adfoster-r7
Copy link
Contributor

Thanks for the PR! Since this PR is touching so many protocol types/modules it will require some close review on our side to ensure there's no regressions/bugs introduced, as a result it might take a little longer than expected to verify and merge this work 👍

@errorxyz
Copy link
Contributor Author

Any updates? @adfoster-r7

report_auth_info(s.merge({:active => false}))
report_cred(
:ip => s[:host],
:port => 21,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we need to hard code 21 here, is it not just s[:port] ? 👀

Copy link
Contributor Author

@errorxyz errorxyz Dec 14, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

def parse(pkt)
    # We want to return immediatly if we do not have a packet which is handled by us
    return unless pkt.is_tcp?
    return if (pkt.tcp_sport != 21 and pkt.tcp_dport != 21)
    s = find_session((pkt.tcp_sport == 21) ? get_session_src(pkt) : get_session_dst(pkt))
    ...

The function only analyses packets where the target's port 21 is involved(hard coded by the writer?). So s[:port] is always gonna be 21. But yes, cleaner to use s[:port] instead. I'll have to do the same for the other modules too.

@@ -49,7 +57,15 @@ def parse(pkt)

when :login_pass
if(s[:user] and s[:pass])
report_auth_info(s)
report_cred(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems to crash for me, were these changes working for you locally when you were testing? 👀

msf6 auxiliary(sniffer/psnuffle) > 
[-] Auxiliary failed: NameError undefined local variable or method `myworkspace_id' for #<#<Module:0x00007f6f1d99d0c0>::SnifferFTP:0x00007f6f1de354e8 @framework=#<Framework (0 sessions, 1 jobs, 0 plugins)>, @module=#<Module:auxiliary/sniffer/psnuffle datastore=[#<Msf::ModuleDataStoreWithFallbacks:0x00007f6f1d2f54c0 @options={"WORKSPACE"=>#<Msf::OptString:0x00007f6f2603a858 @name="WORKSPACE", @advanced=true, @evasion=false, @aliases=[], @max_length=nil, @conditions=[], @fallbacks=[], @required=false, @desc="Specify the workspace for this module", @default=nil, @enums=[], @owner=Msf::Module>, "VERBOSE"=>#<Msf::OptBool:0x00007f6f2603a650 @name="VERBOSE", @advanced=true, @evasion=false, @aliases=[], @max_length=nil, @conditions=[], @fallbacks=[], @required=false, @desc="Enable detailed status messages", @default=false, @enums=[], @owner=Msf::Module>, "PCAPFILE"=>#<Msf::OptPath:0x00007f6f26038058 @name="PCAPFILE", @advanced=false, @evasion=false, @aliases=[], @max_length=nil, @conditions=[], @fallbacks=[], @required=false, @desc="The name of the PCAP capture file to process", @default=nil, @enums=[], @owner=Msf::Exploit::Capture>, "INTERFACE"=>#<Msf::OptString:0x00007f6f26033e68 @name="INTERFACE", @advanced=false, @evasion=false, @aliases=[], @max_length=nil, @conditions=[], @fallbacks=[], @required=false, @desc="The name of the interface", @default=nil, @enums=[], @owner=Msf::Exploit::Capture>, "FILTER"=>#<Msf::OptString:0x00007f6f26033c88 @name="FILTER", @advanced=false, @evasion=false, @aliases=[], @max_length=nil, @conditions=[], @fallbacks=[], @required=false, @desc="The filter string for capturing traffic", @default=nil, @enums=[], @owner=Msf::Exploit::Capture>, "SNAPLEN"=>#<Msf::OptInt:0x00007f6f26033aa8 @name="SNAPLEN", @advanced=false, @evasion=false, @aliases=[], @max_length=nil, @conditions=[], @fallbacks=[], @required=true, @desc="The number of bytes to capture", @default=65535, @enums=[], @owner=Msf::Exploit::Capture>, "TIMEOUT"=>#<Msf::OptInt:0x00007f6f260338f0 @name="TIMEOUT", @advanced=false, @evasion=false, @aliases=[], @max_length=nil, @conditions=[], @fallbacks=[], @required=true, @desc="The number of seconds to wait for new data", @default=500, @enums=[], @owner=Msf::Exploit::Capture>, "SECRET"=>#<Msf::OptInt:0x00007f6f260324a0 @name="SECRET", @advanced=true, @evasion=false, @aliases=[], @max_length=nil, @conditions=[], @fallbacks=[], @required=true, @desc="A 32-bit cookie for probe requests.", @default=1297303073, @enums=[], @owner=Msf::Exploit::Capture>, "GATEWAY_PROBE_HOST"=>#<Msf::OptAddress:0x00007f6f26032298 @name="GATEWAY_PROBE_HOST", @advanced=true, @evasion=false, @aliases=[], @max_length=nil, @conditions=[], @fallbacks=[], @required=true, @desc="Send a TTL=1 random UDP datagram to this host to discover the default gateway's MAC", @default="8.8.8.8", @enums=[], @owner=Msf::Exploit::Capture>, "GATEWAY_PROBE_PORT"=>#<Msf::OptPort:0x00007f6f260320b8 @name="GATEWAY_PROBE_PORT", @advanced=true, @evasion=false, @aliases=[], @max_length=nil, @conditions=[], @fallbacks=[], @required=false, @desc="The port on GATEWAY_PROBE_HOST to send a random UDP probe to (random if 0 or unset)", @default=nil, @enums=[], @owner=Msf::Exploit::Capture>, "PROTOCOLS"=>#<Msf::OptString:0x00007f6f26030768 @name="PROTOCOLS", @advanced=false, @evasion=false, @aliases=[], @max_length=nil, @conditions=[], @fallbacks=[], @required=true, @desc="A comma-delimited list of protocols to sniff or \"all\".", @default="all", @enums=[], @owner=Msf::Modules::Auxiliary__Sniffer__Psnuffle::MetasploitModule>, "ProtocolBase"=>#<Msf::OptPath:0x00007f6f2602bb50 @name="ProtocolBase", @advanced=true, @evasion=false, @aliases=[], @max_length=nil, @conditions=[], @fallbacks=[], @required=true, @desc="The base directory containing the protocol decoders", @default="/home/kali/metasploit-framework/data/exploits/psnuffle", @enums=[], @owner=Msf::Modules::Auxiliary__Sniffer__Psnuffle::MetasploitModule>}, @aliases={}, @defaults={}, @user_defined={"VERBOSE"=>false, "SNAPLEN"=>65535, "TIMEOUT"=>500, "SECRET"=>1297303073, "GATEWAY_PROBE_HOST"=>"8.8.8.8", "PROTOCOLS"=>"all", "ProtocolBase"=>"/home/kali/metasploit-framework/data/exploits/psnuffle"}, @_module=#<Module:auxiliary/sniffer/psnuffle datastore=[#<Msf::ModuleDataStoreWithFallbacks:0x00007f6f2603ab78 @options={"WORKSPACE"=>#<Msf::OptString:0x00007f6f2603a858 @name="WORKSPACE", @advanced=true, @evasion=false, @aliases=[], @max_length=nil, @conditions=[], @fallbacks=[], @required=false, @desc="Specify the workspace for this module", @default=nil, @enums=[], @owner=Msf::Module>, "VERBOSE"=>#<Msf::OptBool:0x00007f6f2603a650 @name="VERBOSE", @advanced=true, @evasion=false, @aliases=[], @max_length=nil, @conditions=[], @fallbacks=[], @required=false, @desc="Enable detailed status messages", @default=false, @enums=[], @owner=Msf::Module>, "PCAPFILE"=>#<Msf::OptPath:0x00007f6f26038058 @name="PCAPFILE", @advanced=false, @evasion=false, @aliases=[], @max_length=nil, @conditions=[], @fallbacks=[], @required=false, @desc="The name of the PCAP capture file to process", @default=nil, @enums=[], @owner=Msf::Exploit::Capture>, "INTERFACE"=>#<Msf::OptString:0x00007f6f26033e68 @name="INTERFACE", @advanced=false, @evasion=false, @aliases=[], @max_length=nil, @conditions=[], @fallbacks=[], @required=false, @desc="The name of the interface", @default=nil, @enums=[], @owner=Msf::Exploit::Capture>, "FILTER"=>#<Msf::OptString:0x00007f6f26033c88 @name="FILTER", @advanced=false, @evasion=false, @aliases=[], @max_length=nil, @conditions=[], @fallbacks=[], @required=false, @desc="The filter string for capturing traffic", @default=nil, @enums=[], @owner=Msf::Exploit::Capture>, "SNAPLEN"=>#<Msf::OptInt:0x00007f6f26033aa8 @name="SNAPLEN", @advanced=false, @evasion=false, @aliases=[], @max_length=nil, @conditions=[], @fallbacks=[], @required=true, @desc="The number of bytes to capture", @default=65535, @enums=[], @owner=Msf::Exploit::Capture>, "TIMEOUT"=>#<Msf::OptInt:0x00007f6f260338f0 @name="TIMEOUT", @advanced=false, @evasion=false, @aliases=[], @max_length=nil, @conditions=[], @fallbacks=[], @required=true, @desc="The number of seconds to wait for new data", @default=500, @enums=[], @owner=Msf::Exploit::Capture>, "SECRET"=>#<Msf::OptInt:0x00007f6f260324a0 @name="SECRET", @advanced=true, @evasion=false, @aliases=[], @max_length=nil, @conditions=[], @fallbacks=[], @required=true, @desc="A 32-bit cookie for probe requests.", @default=1297303073, @enums=[], @owner=Msf::Exploit::Capture>, "GATEWAY_PROBE_HOST"=>#<Msf::OptAddress:0x00007f6f26032298 @name="GATEWAY_PROBE_HOST", @advanced=true, @evasion=false, @aliases=[], @max_length=nil, @conditions=[], @fallbacks=[], @required=true, @desc="Send a TTL=1 random UDP datagram to this host to discover the default gateway's MAC", @default="8.8.8.8", @enums=[], @owner=Msf::Exploit::Capture>, "GATEWAY_PROBE_PORT"=>#<Msf::OptPort:0x00007f6f260320b8 @name="GATEWAY_PROBE_PORT", @advanced=true, @evasion=false, @aliases=[], @max_length=nil, @conditions=[], @fallbacks=[], @required=false, @desc="The port on GATEWAY_PROBE_HOST to send a random UDP probe to (random if 0 or unset)", @default=nil, @enums=[], @owner=Msf::Exploit::Capture>, "PROTOCOLS"=>#<Msf::OptString:0x00007f6f26030768 @name="PROTOCOLS", @advanced=false, @evasion=false, @aliases=[], @max_length=nil, @conditions=[], @fallbacks=[], @required=true, @desc="A comma-delimited list of protocols to sniff or \"all\".", @default="all", @enums=[], @owner=Msf::Modules::Auxiliary__Sniffer__Psnuffle::MetasploitModule>, "ProtocolBase"=>#<Msf::OptPath:0x00007f6f2602bb50 @name="ProtocolBase", @advanced=true, @evasion=false, @aliases=[], @max_length=nil, @conditions=[], @fallbacks=[], @required=true, @desc="The base directory containing the protocol decoders", @default="/home/kali/metasploit-framework/data/exploits/psnuffle", @enums=[], @owner=Msf::Modules::Auxiliary__Sniffer__Psnuffle::MetasploitModule>}, @aliases={}, @defaults={}, @user_defined={}, @_module=#<Module:auxiliary/sniffer/psnuffle datastore=[#<Msf::ModuleDataStoreWithFallbacks:0x00007f6f2603ab78 ...>]>>]>>]>, @sessions={"192.168.123.136:49782-192.168.123.1:21"=>{:client_host=>"192.168.123.136", :client_port=>"49782", :host=>"192.168.123.1", :port=>"21", :session=>"192.168.123.136:49782-192.168.123.1:21", :ctime=>2023-12-14 09:28:18.068211206 -0500, :mtime=>2023-12-14 09:28:21.138930833 -0500, :sname=>"ftp", :info=>"220---------- Welcome to Pure-FTPd [privsep] [TLS] ----------", :user=>"ftpuser", :pass=>"ftpuser"}}, @dport=0, @sigs={:banner=>/^(220\s*[^\r\n]+)/i, :user=>/^USER\s+([^\s]+)/i, :pass=>/^PASS\s+([^\s]+)/i, :login_pass=>/^(230\s*[^\n]+)/i, :login_fail=>/^(5\d\d\s*[^\n]+)/i, :bye=>/^221/}>
[-] Call stack:
[-]   (eval):100:in `report_cred'
[-]   (eval):60:in `block in parse'
[-]   (eval):29:in `each_key'
[-]   (eval):29:in `parse'
[-]   /home/kali/metasploit-framework/modules/auxiliary/sniffer/psnuffle.rb:92:in `block (2 levels) in run'
[-]   /home/kali/metasploit-framework/modules/auxiliary/sniffer/psnuffle.rb:91:in `each_key'
[-]   /home/kali/metasploit-framework/modules/auxiliary/sniffer/psnuffle.rb:91:in `block in run'
[-]   /home/kali/metasploit-framework/lib/msf/core/exploit/capture.rb:171:in `block in each_packet'
[-]   /home/kali/metasploit-framework/lib/msf/core/exploit/capture.rb:170:in `each'
[-]   /home/kali/metasploit-framework/lib/msf/core/exploit/capture.rb:170:in `each_packet'
[-]   /home/kali/metasploit-framework/modules/auxiliary/sniffer/psnuffle.rb:87:in `run'

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For context, I was running an FTP server on my host machine:

docker run -it -e FTP_USER_NAME=ftpuser -e FTP_USER_PASS=ftpuser -e FTP_USER_HOME=/home/bob -p 30000-30009:30000-30009 -p 21:21 -v $(pwd)/testing:/home/bob --rm stilliard/pure-ftpd

And logging in via FTP from the metasploit host machine:

┌──(kali㉿kali)-[~]
└─$ ftp 192.168.123.1
Connected to 192.168.123.1.
220---------- Welcome to Pure-FTPd [privsep] [TLS] ----------
220-You are user number 1 of 5 allowed.
220-Local time is now 14:28. Server port: 21.
220-This is a private system - No anonymous login
220-IPv6 connections are also welcome on this server.
220 You will be disconnected after 15 minutes of inactivity.
Name (192.168.123.1:kali): ftpuser
331 User ftpuser OK. Password required
Password: 
230 OK. Current directory is /
Remote system type is UNIX.
Using binary mode to transfer files.
ftp> 

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes it crashes for me too. I think I missed testing this thinking it was too simple(I guess overconfidence does hit back :P). Other than this, I think there a few other fixes to be made in the other modules too. I'll request a review once that is done. Also, as a best practice should I open a different PR for each module?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think in this case a single PR is good 👍

@errorxyz
Copy link
Contributor Author

errorxyz commented Dec 16, 2023

Modicon password recovery

Since I couldn't get the target, I copied the function call used in the module to the start of the run function and ran the module with dummy values:

ip = "1.1.1.1"
rport = 80
httpuser = "test"
httppass = "password"
proof = "testproof"

report_cred(
    ip: ip,
    port: rport,
    service_name: 'http',
    user: httpuser,
    password: httppass,
    proof: proof
)

creds output:

msf6 auxiliary(admin/scada/modicon_password_recovery) > creds
Credentials
===========

host     origin   service        public  private   realm  private_type  JtR Format
----     ------   -------        ------  -------   -----  ------------  ----------
1.1.1.1  1.1.1.1  80/tcp (http)  test    password         Password

Lotus Domino Hashes

Similar to previous one, I copied the function call to the start of the run function and used dummy values:

short_name = "test modicon"
pass_hash = "dominopasswordhash"
user_mail = "[email protected]"
vhost = "1.1.1.1"

report_cred(
    user: short_name,
    password: pass_hash,
    proof: "WEBAPP=\"Lotus Domino\", USER_MAIL=#{user_mail}, HASH=#{pass_hash}, VHOST=#{vhost}"
)

Output of creds command:

msf6 auxiliary(scanner/lotus/lotus_domino_hashes) > creds
Credentials
===========

host         origin       service        public        private             realm  private_type        JtR Format
----         ------       -------        ------        -------             -----  ------------        ----------
192.168.1.9  192.168.1.9  80/tcp (http)  test modicon  dominopasswordhash         Nonreplayable hash  dominosec

Psnuffle's FTP sniffer

  1. Setup environment using docker: docker run -it -e FTP_USER_NAME=ftpuser -e FTP_USER_PASS=ftpuser -e FTP_USER_HOME=/home/bob -p 30000-30009:30000-30009 -p 21:21 -v $(pwd)/testing:/home/bob --rm stilliard/pure-ftpd
  2. run the module
  3. Log in to FTP server

output:

msf6 auxiliary(sniffer/psnuffle) > run
[*] Auxiliary module running as background job 0.
msf6 auxiliary(sniffer/psnuffle) > 
[*] Loaded protocol FTP from /home/errorxyz/Documents/git/metasploit-framework/data/exploits/psnuffle/ftp.rb...
[*] Sniffing traffic.....
[*] Successful FTP Login: 192.168.1.9:54076-172.17.0.2:21 >> ftpuser / ftpuser
creds
Credentials
===========

host        origin      service       public   private  realm  private_type  JtR Format
----        ------      -------       ------   -------  -----  ------------  ----------
172.17.0.2  172.17.0.2  21/tcp (ftp)  ftpuser  ftpuser         Password

Psnuffle's POP3 sniffer

While verifying the working of this module, I encountered a bug in the code- seems the module doesn't really extract the s[:banner] and tries to do s[:banner].split while printing information resulting in a error. However the report_cred function does work as intended without raising errors since the error is raised after the creds have been reported

pop3.rb

...
report_cred(
    :ip  => s[:host],
    :port => s[:port],
    :service_name => s[:proto],
    :user => s[:user],
    :password => s[:pass],
    :type => :password,
    :proof => s[:extra],
    :status => Metasploit::Model::Login::Status::INCORRECT
)
print_status("Invalid POP3 Login: #{s[:session]} >> #{s[:user]} / #{s[:pass]} (#{s[:banner].strip})")
...
  1. run server using docker: docker run -d -it -p 1110:110 esminis/mail-server-postfix-vm-pop3d
  2. run the module
  3. Login to pop3 using telnet
  4. USER test
  5. PASS test

output:

msf6 auxiliary(sniffer/psnuffle) > run
[*] Auxiliary module running as background job 0.
msf6 auxiliary(sniffer/psnuffle) > 
[*] Loaded protocol POP3 from /home/errorxyz/Documents/git/metasploit-framework/data/exploits/psnuffle/pop3.rb...
[*] Sniffing traffic.....
[-] Auxiliary failed: NoMethodError undefined method `strip' for nil:NilClass
[-] Call stack:
[-]   (eval):94:in `block in parse'
[-]   (eval):25:in `each_key'
[-]   (eval):25:in `parse'
[-]   /home/errorxyz/Documents/git/metasploit-framework/modules/auxiliary/sniffer/psnuffle.rb:92:in `block (2 levels) in run'
[-]   /home/errorxyz/Documents/git/metasploit-framework/modules/auxiliary/sniffer/psnuffle.rb:91:in `each_key'
[-]   /home/errorxyz/Documents/git/metasploit-framework/modules/auxiliary/sniffer/psnuffle.rb:91:in `block in run'
[-]   /home/errorxyz/Documents/git/metasploit-framework/lib/msf/core/exploit/capture.rb:171:in `block in each_packet'
[-]   /home/errorxyz/Documents/git/metasploit-framework/lib/msf/core/exploit/capture.rb:170:in `each'
[-]   /home/errorxyz/Documents/git/metasploit-framework/lib/msf/core/exploit/capture.rb:170:in `each_packet'
[-]   /home/errorxyz/Documents/git/metasploit-framework/modules/auxiliary/sniffer/psnuffle.rb:87:in `run'

msf6 auxiliary(sniffer/psnuffle) > creds
Credentials
===========

host        origin      service         public  private  realm  private_type  JtR Format
----        ------      -------         ------  -------  -----  ------------  ----------
172.17.0.2  172.17.0.2  110/tcp (pop3)  test    test            Password      

Psnuffle

Other sniffer modules have been verified by copying the report_cred function call to the start of the parse function in their respective files, setting it up with dummy values in s, setting protocols option to all in metasploit and running the module along with a ftp server and logging into it to trigger the parse function for every protocol sniffer.

smb.rb
dst_ip = '1.1.1.1'
smb_db_type_hash = 'hashtype'
s = {
    port: 445,
    user: 'user',
    domain: 'domain',
    lmhash: 'lmhash',
    ntlmhash: 'ntlmhash',
    challenge: 'challenge',
    peer_os: 'peer os'
}
report_cred(
    :ip  => dst_ip,
    :port => s[:port],
    :service_name => 'smb',
    :user => s[:user],
    :password => s[:domain] + ":" + s[:lmhash] + ":" + s[:ntlmhash] + ":" + s[:challenge],
    :type => :nonreplayable_hash,
    :jtr_format => smb_db_type_hash,
    :proof => "DOMAIN=#{s[:domain]} OS=#{s[:peer_os]}",
    :status => Metasploit::Model::Login::Status::SUCCESSFUL 
)
url.rb
s = {
    host: '0.0.0.0',
    port: 80,
    user: 'urluser',
    pass: 'urlpass',
    session: 'httpsession',
    basic_auth: 'basic auth'
}

report_cred(
    :ip  => s[:host],
    :port => s[:port],
    :service_name => 'http',
    :user => s[:user],
    :password => s[:pass],
    :type => :password,
    :proof => "Session: #{s[:session]} Basic Auth: #{s[:basic_auth]}",
    :status => Metasploit::Model::Login::Status::UNTRIED
)
imap.rb
s = {
    host: '0.0.0.0',
    port: 143,
    sname: 'imap4',
    user: 'imapuser',
    pass: 'imappass'
}

report_cred(
    :ip  => s[:host],
    :port => s[:port],
    :service_name => s[:sname],
    :user => s[:user],
    :password => s[:pass],
    :type => :password,
    :proof => "Capability OK reponse from server",
    :status => Metasploit::Model::Login::Status::SUCCESSFUL
)

output:

Credentials
===========

host        origin      service          public    private                           realm  private_type        JtR Format
----        ------      -------          ------    -------                           -----  ------------        ----------
0.0.0.0     0.0.0.0     143/tcp (imap4)  imapuser  imappass                                 Password
0.0.0.0     0.0.0.0     80/tcp (http)    urluser   urlpass                                  Password
1.1.1.1     1.1.1.1     445/tcp (smb)    user      domain:lmhash:ntlmhash:challenge         Nonreplayable hash  hashtype
172.17.0.2  172.17.0.2  21/tcp (ftp)     ftpuser   ftpuser                                  Password

@errorxyz errorxyz requested a review from adfoster-r7 December 16, 2023 19:12
@errorxyz
Copy link
Contributor Author

Hi, its been a month without any activity, are we still missing something?

@adfoster-r7
Copy link
Contributor

adfoster-r7 commented Jan 15, 2024

Running through this now 👍

Modicon password recovery 🟢

I ran a FTP server with some of the files hard coded as I also don't have access to the real system

msf6 auxiliary(admin/scada/modicon_password_recovery) > notes -dInterrupt: use the 'exit' command to quit
msf6 auxiliary(admin/scada/modicon_password_recovery) > rerun rhost=127.0.0.1 rport=21 username=ftpuser password=ftpuser
[*] Reloading module...
[*] Running module against 127.0.0.1

[*] 127.0.0.1:21 - 127.0.0.1:21 - FTP - Checking fingerprint
[*] 127.0.0.1:21 - Connecting to FTP server 127.0.0.1:21...
[*] 127.0.0.1:21 - Connected to target FTP server.
[*] 127.0.0.1:21 - 127.0.0.1:21 - FTP - Matches Modicon fingerprint
[*] 127.0.0.1:21 - Service detected.
[*] 127.0.0.1:21 - 127.0.0.1:21 - FTP - Connecting
[*] 127.0.0.1:21 - Connecting to FTP server 127.0.0.1:21...
[*] 127.0.0.1:21 - Connected to target FTP server.
[*] 127.0.0.1:21 - Authenticating as ftpuser with password ftpuser...
[*] 127.0.0.1:21 - Sending password...
[+] 127.0.0.1:21 - 127.0.0.1:21 - FTP - Login succeeded
[*] 127.0.0.1:21 - 127.0.0.1:21 - FTP - Opening PASV data socket to download "/FLASH0/userlist.dat"
[*] 127.0.0.1:21 - 127.0.0.1:21 - FTP - HTTP password retrieval: success
[*] 127.0.0.1:21 - 127.0.0.1:21 - FTP - Opening PASV data socket to download "/FLASH0/ftp/ftp.ini"
[*] 127.0.0.1:21 - 127.0.0.1:21 - FTP - password retrieval: success
[*] 127.0.0.1:21 - 127.0.0.1:21 - FTP - Opening PASV data socket to download "/FLASH0/rdt/password.rde"
[*] 127.0.0.1:21 - 127.0.0.1:21 - FTP - Write password retrieval: success
[*] 127.0.0.1:21 - 127.0.0.1:21 - FTP - Storing HTTP credentials
[*] 127.0.0.1:21 - 127.0.0.1:21 - FTP - Storing hashed FTP credentials
Schneider Modicon Quantum services, usernames, and passwords
============================================================

 Service      User Name           Password
 -------      ---------           --------
 VxWorks      ftp_ini_first_line  ftp_ini_second_line
 http         foobar1             foobar2
 scada-write                      password_from_password.rde

[*] Auxiliary module execution completed
msf6 auxiliary(admin/scada/modicon_password_recovery) > creds
Credentials
===========

host       origin     service       public   private  realm  private_type  JtR Format
----       ------     -------       ------   -------  -----  ------------  ----------
127.0.0.1  127.0.0.1  21/tcp (ftp)  foobar1  foobar2         Password      
127.0.0.1  127.0.0.1  21/tcp (ftp)  ftpuser  ftpuser         Password      

msf6 auxiliary(admin/scada/modicon_password_recovery) > notes

Notes
=====

 Time                     Host       Service  Port  Protocol  Type                          Data
 ----                     ----       -------  ----  --------  ----                          ----
 2024-01-15 16:37:48 UTC  127.0.0.1  http     80    tcp       scada.modicon.write-password  "password_from_password.rde\r\n"
 2024-01-15 16:37:48 UTC  127.0.0.1  ftp      21    tcp       scada.modicon.ftp-password    "User:ftp_ini_first_line VXWorks_Password:ftp_ini_second_line"

psnuffle

ftp 🟢

target

docker run -it -e FTP_USER_NAME=ftpuser -e FTP_USER_PASS=ftpuser -e FTP_USER_HOME=/home/bob -p 30000-30009:30000-30009 -p 21:21 -v $(pwd)/testing:/home/bob --rm stilliard/pure-ftpd
Creating user...
Password: 
Enter it again: 
Setting default port range to: 30000:30009
Setting default max clients to: 5
Setting default max connections per ip to: 5
Starting Pure-FTPd:
  pure-ftpd  -l puredb:/etc/pure-ftpd/pureftpd.pdb -E -j -R -P localhost   -p 30000:30009 -c 5 -C 5

Running module which causes auth capture:

msf6 auxiliary(scanner/ftp/ftp_login) > run rhost=192.168.123.1 username=ftpuser password=ftpuser

[*] 192.168.123.1:21      - 192.168.123.1:21 - Starting FTP login sweep
[+] 192.168.123.1:21      - 192.168.123.1:21 - Login Successful: ftpuser:ftpuser
[*] 192.168.123.1:21      - Scanned 1 of 1 hosts (100% complete)
[*] Auxiliary module execution completed
msf6 auxiliary(scanner/ftp/ftp_login) > 
[*] Successful FTP Login: 192.168.123.136:39899-192.168.123.1:21 >> ftpuser / ftpuser

msf6 auxiliary(scanner/ftp/ftp_login) > creds
Credentials
===========

host           origin         service       public   private  realm  private_type  JtR Format
----           ------         -------       ------   -------  -----  ------------  ----------
192.168.123.1  192.168.123.1  21/tcp (ftp)  ftpuser  ftpuser         Password      

http 🟠

target - only works on port 80

sudo -E python3 -m http.server 80

Trigger auth capture:

curl http://basic_auth:[email protected]
[*] HTTP GET: 192.168.123.136:54618-192.168.123.1:80 http://192.168.123.1/
msf6 auxiliary(sniffer/psnuffle) > creds
Credentials
===========

host  origin  service  public  private  realm  private_type  JtR Format
----  ------  -------  ------  -------  -----  ------------  ----------

Looks like there's a bug in the signature order potentially? Introduced in #10315

[11] pry(#<#<Module:0x00007f335a232780>::SnifferURL>)> s
=> {:client_host=>"192.168.123.136",
 :client_port=>"51174",
 :host=>"192.168.123.1",
 :port=>"80",
 :session=>"192.168.123.136:51174-192.168.123.1:80",
 :ctime=>2024-01-15 19:08:51.976307333 -0500,
 :mtime=>2024-01-15 19:08:51.976308702 -0500}

Raw netcat request shows that the headers are probably in a different order to when the URL command was written:

$ sudo -E ncat -lnvp 80
Ncat: Version 7.94 ( https://nmap.org/ncat )
Ncat: Listening on [::]:80
Ncat: Listening on 0.0.0.0:80
Ncat: Connection from 192.168.123.136:54618.
GET / HTTP/1.1
Host: 192.168.123.1
Authorization: Basic YmFzaWNfYXV0aDpwYXNzd29yZDEyMw==
User-Agent: curl/7.87.0
Accept: */*

i.e. if the Host and Authorization headers were swapped around, the module would work - but would obviously break the original test data that bcoles added support for. It'll need restructured either way. Unrelated to this PR's changes

smb 🟠

not a blocker: Doesn't work with smb 3 negotiation - but also doesn't work against my target when the smb_status value is checked; Unrelated to this PR's changes

Running:

$ bundle exec ruby ./msfconsole -q -x 'use psnuffle; run; creds -d; use scanner/smb/smb_login; run rhost=192.168.123.147 username=vagrant password=vagrant SMB::ProtocolVersion=1 SMB::AlwaysEncrypt=false'
...
[*] 192.168.123.147:445   - 192.168.123.147:445 - Starting SMB login bruteforce
[+] 192.168.123.147:445   - 192.168.123.147:445 - Success: '.\vagrant:vagrant' Administrator
[*] 192.168.123.147:445   - Scanned 1 of 1 hosts (100% complete)
[*] Auxiliary module execution completed

Logging into a windows box almost works, but fails this smb_status check:

https://github.com/rapid7/metasploit-framework/blob/e9ff2e55dca46637a534182b1e81d8f360771380/data/exploits/psnuffle/smb.rb#L152C1-L153

Value:

[1] pry(#<#<Module:0x00007fd989271130>::SnifferSMB>)> smb_status
=> 256

For now I've manually hard-coded that it will treat 256 as successful, but looks to be a bug

[*] NTLMv2 Response Captured in SMBv2 session : 192.168.123.136:45393-192.168.123.147:445 
USER:vagrant DOMAIN:. OS: LM:
SERVER CHALLENGE:fe5773c21b061a90 
LMHASH:a36a6eeb95f1ba3e558ed0b17a7d99c306fbb38af129693e 
NTHASH:28856884ea28d52ebd0cc1cbd71d02d50101000000000000802a98911648da0106fbb38af129693e0000000002000c00570049004e0044004500560001000c00570049004e0044004500560004000c00570069006e0044006500760003000c00570069006e00440065007600070008007d87be911648da010000000000000000

msf6 auxiliary(scanner/smb/smb_login) > creds
Credentials
===========

host             origin           service        public   private                                                                                              realm  private_type        JtR Format
----             ------           -------        ------   -------                                                                                              -----  ------------        ----------
192.168.123.147  192.168.123.147  445/tcp (smb)  vagrant  vagrant                                                                                                     Password            
192.168.123.147  192.168.123.147  445/tcp (smb)  vagrant  .:a36a6eeb95f1ba3e558ed0b17a7d99c306fbb38af129693e:28856884ea28d52ebd0cc1cbd71d02d50101 (TRUNCATED)         Nonreplayable hash  netntlmv2

msf6 auxiliary(scanner/smb/smb_login) > notes

Notes
=====

 Time                  Host             Service  Port  Protocol  Type                         Data
 ----                  ----             -------  ----  --------  ----                         ----
 2024-01-16 00:22:53   192.168.123.147                           host.os.session_fingerprint  {:name=>"WINDEV", :os=>"Windows 2016+ (10.0 Build 14393).",
 UTC                                                                                          :arch=>"x64"}
 2024-01-16 00:54:33   192.168.123.136                           smb_domain                   "."
 UTC


imap

Target

msf6 auxiliary(server/capture/imap) > [!] No active DB -- Credential data will not be saved!
[+] IMAP LOGIN 192.168.123.136:48094 user / password123

Connecting

┌──(kali㉿kali)-[~/metasploit-framework]
└─$ nc 192.168.123.1 143 
* OK IMAP4
A0 CAPABILITY
* CAPABILITY IMAP4 IMAP4rev1 IDLE LOGIN-REFERRALS MAILBOX-REFERRALS NAMESPACE LITERAL+ UIDPLUS CHILDREN UNSELECT QUOTA XLIST XYZZY LOGIN-REFERRALS AUTH=XYMCOOKIE AUTH=XYMCOOKIEB64 AUTH=XYMPKI AUTH=XYMECOOKIE ID
A0 OK CAPABILITY completed.
A0 NO LOGIN FAILURE
A1 login user password123

This doesn't trigger logging

I think this isn't working due to to the test server not responding on auth success/failure correctly

https://github.com/rapid7/metasploit-framework/blame/e9ff2e55dca46637a534182b1e81d8f360771380/data/exploits/psnuffle/imap.rb#L12-L20

Spec example:

# https://datatracker.ietf.org/doc/html/rfc3501#section-6.2.3
 Example:    C: a001 LOGIN SMITH SESAME
               S: a001 OK LOGIN completed

Using a different client also doesn't work with the current regex matches.

docker-compose file:

services:
  mailserver:
    image: ghcr.io/docker-mailserver/docker-mailserver:latest
    container_name: mailserver
    # Provide the FQDN of your mail server here (Your DNS MX record should point to this value)
    hostname: mail.example.com
    ports:
      - "25:25"
      - "465:465"
      - "587:587"
      - "993:993"
      - "143:143"
    restart: always

Running and creating a user:

docker-compose up
docker-compose exec mailserver /bin/bash /usr/local/bin/setup email add [email protected] password

Logging in:

└─$ printf "A1 CAPABILITY\r\nA2 LOGIN [email protected] password\r\n" | nc 192.168.123.1 143
* OK [CAPABILITY IMAP4rev1 SASL-IR LOGIN-REFERRALS ID ENABLE IDLE LITERAL+ AUTH=PLAIN AUTH=LOGIN] Dovecot ready.
* CAPABILITY IMAP4rev1 SASL-IR LOGIN-REFERRALS ID ENABLE IDLE LITERAL+ AUTH=PLAIN AUTH=LOGIN
A1 OK Pre-login capabilities listed, post-login capabilities have more.
* CAPABILITY IMAP4rev1 SASL-IR LOGIN-REFERRALS ID ENABLE IDLE SORT SORT=DISPLAY THREAD=REFERENCES THREAD=REFS THREAD=ORDEREDSUBJECT MULTIAPPEND URL-PARTIAL CATENATE UNSELECT CHILDREN NAMESPACE UIDPLUS LIST-EXTENDED I18NLEVEL=1 CONDSTORE QRESYNC ESEARCH ESORT SEARCHRES WITHIN CONTEXT=SEARCH LIST-STATUS BINARY MOVE SNIPPET=FUZZY PREVIEW=FUZZY PREVIEW STATUS=SIZE SAVEDATE LITERAL+ NOTIFY SPECIAL-USE QUOTA
A2 OK Logged in

Failed matching string

"* CAPABILITY IMAP4rev1 SASL-IR LOGIN-REFERRALS ID ENABLE IDLE SORT SORT=DISPLAY THREAD=REFERENCES THREAD=REFS THREAD=ORDEREDSUBJECT MULTIAPPEND URL-PARTIAL CATENATE UNSELECT CHILDREN NAMESPACE UIDPLUS LIST-EXTENDED I18NLEVEL=1 CONDSTORE QRESYNC ESEARCH ESORT SEARCHRES WITHIN CONTEXT=SEAR
CH LIST-STATUS BINARY MOVE SNIPPET=FUZZY PREVIEW=FUZZY PREVIEW STATUS=SIZE SAVEDATE LITERAL+ NOTIFY SPECIAL-USE QUOTA\r\nA2 OK Logged in\r\n

From the specification it looks like the server can respond with the login status and then an arbitrary human readable code:

7.1.1. OK Response

Contents: OPTIONAL response code
human-readable text

 The OK response indicates an information message from the server.
 When tagged, it indicates successful completion of the associated
 command.  The human-readable text MAY be presented to the user as
 an information message.

So it looks like this module needs updated to work in more scenarios, unrelated to this PR

Pop3 🟠

Using the same docker target above for imap;

Connecting with the following:

└─$ printf "USER [email protected]\r\nPASS password\r\n" | nc 192.168.123.1 110
+OK Dovecot ready.
+OK
+OK Logged in.

Leads to a crash in msfconsole:

msf6 auxiliary(sniffer/psnuffle) > 
[-] Auxiliary failed: NoMethodError undefined method `strip' for nil:NilClass
[-] Call stack:
[-]   (eval):65:in `block in parse'
[-]   (eval):25:in `each_key'
[-]   (eval):25:in `parse'
[-]   /home/kali/metasploit-framework/modules/auxiliary/sniffer/psnuffle.rb:92:in `block (2 levels) in run'
[-]   /home/kali/metasploit-framework/modules/auxiliary/sniffer/psnuffle.rb:91:in `each_key'
[-]   /home/kali/metasploit-framework/modules/auxiliary/sniffer/psnuffle.rb:91:in `block in run'
[-]   /home/kali/metasploit-framework/lib/msf/core/exploit/capture.rb:171:in `block in each_packet'
[-]   /home/kali/metasploit-framework/lib/msf/core/exploit/capture.rb:170:in `each'
[-]   /home/kali/metasploit-framework/lib/msf/core/exploit/capture.rb:170:in `each_packet'
[-]   /home/kali/metasploit-framework/modules/auxiliary/sniffer/psnuffle.rb:87:in `run'

I think this will be fixed by #18625 being resolved

For now I just patched the banner dump:

              print_status("Successful POP3 Login: #{s[:session]} >> #{s[:user]} / #{s[:pass]} (#{(s[:banner] || '').strip})")

Working as expected:

msf6 auxiliary(sniffer/psnuffle) > creds
Credentials
===========

host           origin         service         public           private   realm  private_type  JtR Format
----           ------         -------         ------           -------   -----  ------------  ----------
192.168.123.1  192.168.123.1  110/tcp (pop3)  [email protected]  password         Password      

@adfoster-r7 adfoster-r7 merged commit 1ba704b into rapid7:master Jan 16, 2024
34 checks passed
@adfoster-r7
Copy link
Contributor

Release Notes

Fixes deprecation warnings when running the auxiliary/admin/scada/modicon_password_recovery, auxiliary/scanner/lotus/lotus_domino_hashes, auxiliary/sniffer/psnuffle, exploits/unix/webapp/vbulletin_vote_sqli_exec exploit modules with a database connected

@adfoster-r7
Copy link
Contributor

Thanks for the pull request and sorry for the delay 💯

I spotted a bunch of issues with the existing modules when testing things out here, but I don't think that's a blocker to fixing the deprecation warnings

It looks like the HTTP/POP3/IMAP parsers have some edgecases in them when running against targets other than whatever were previously tested against

@errorxyz
Copy link
Contributor Author

Thanks for merging my work!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants