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

Updates MySQL modules to now support the new MySQL session type #18759

Conversation

cgranleese-r7
Copy link
Contributor

@cgranleese-r7 cgranleese-r7 commented Jan 29, 2024

This PR is a continuation of #18718, make sure it is merged before landing this.

This PR updates existing MySQL modules to know work with an existing session, as well as the original rhost functionality:

run session=1

modules/auxiliary/scanner/mysql/mysql_authbypass_hashdump.rb

I updated the MySQL modules to make use of self.mysql_conn instead of @mysql_handle. This module was the only other module that wasn't updated as part of this PR that relied on @mysql_handle, I updated it to now use self.mysql_conn and tested it and I believe it works as expected, the module completes but I didn't have an old enough version of MySQL to test it fully.
Hopefully that was the right call. Happy to revert if needed 👍

Verification

Targets I used

vulhub/mysql:5.5.23

docker run -d -p 3306:3306 --name=mysql-server --env="MYSQL_ROOT_PASSWORD=123456" vulhub/mysql:5.5.23

mariadb:latest

docker run -it --rm -e MYSQL_ROOT_PASSWORD='password' -p 3306:3306 mariadb:latest

Windows 10 via MySQL installer

Resource script to test modules

<ruby>

  @windows_session = nil

  # Get the docker session
  # run_single("use scanner/mysql/mysql_login")
  # run_single("run 'mysql://root:password@127.0.0.1' CreateSession=true rport=3306")

  # Get the windows session
  @windows_session = true
  run_single("use scanner/mysql/mysql_login")
  run_single("run 'mysql://foo:password@192.168.175.193' CreateSession=true rport=3306")

  modules = %w[
  auxiliary/admin/mysql/mysql_enum
  auxiliary/admin/mysql/mysql_sql
  auxiliary/scanner/mysql/mysql_file_enum
  auxiliary/scanner/mysql/mysql_hashdump
  auxiliary/scanner/mysql/mysql_schemadump
  auxiliary/scanner/mysql/mysql_version
  auxiliary/scanner/mysql/mysql_writable_dirs
  exploits/multi/mysql/mysql_udf_payload
]

windows_username = 'foo'
windows_ip = 'XXX.XXX.XXX.XXX'

docker_username = 'root'
docker_ip = '127.0.0.1'

windows_modules = %w[
  exploits/windows/mysql/mysql_mof
  exploits/windows/mysql/mysql_start_up
]

modules += windows_modules if @windows_session

modules.each do |mod|
  if @windows_session
    rhost = windows_ip
    username = windows_username
  else
    rhost = docker_ip
    username = docker_username
  end

  print_line
  print_line

  print_status("Running module - #{mod}")
  run_single("use #{mod}")

  print_line

  print_warning("******** RHOST ********")
  if mod.include?("file_enum")
    run_single("run rhosts=#{rhost} username=#{username} password=password rport=3306 file_list=/")
  elsif mod.include?("writable_dirs")
    run_single("run rhosts=#{rhost} username=#{username} password=password rport=3306 dir_list=/")
  else
    run_single("run rhosts=#{rhost} username=#{username} password=password rport=3306")
  end

  print_line
  print_warning("******** SESSION ********")
  if mod.include?("file_enum")
    run_single("run session=1 file_list=/")
  elsif mod.include?("writable_dirs")
    run_single("run session=1 dir_list=/")
  else
    run_single("run session=1")
  end


  print_line
  print_line
end
</ruby>

Testing steps

  • Start msfconsole
  • Turn on the mysql_session_type feature
  • Test each update module against each of the follow run methods:
  • run session=1
  • run rhosts=127.0.0.1 username=root password=password rport=3306
  • run 'mysql://root:password@127.0.0.1'
  • Verify they work as they previously did on master

@adfoster-r7
Copy link
Contributor

Your skipped module list looks good; but I think there's a few more modules we could pull in from it to implement:

  • modules/exploits/multi/mysql/mysql_udf_payload.rb - Should work
  • modules/exploits/windows/mysql/mysql_mof.rb - Should work
  • modules/exploits/windows/mysql/mysql_start_up.rb - Should work
  • modules/auxiliary/scanner/mysql/mysql_version.rb - We could either track the banner on the session object, or call select version(); similar to the postgres module. Should be able to sync up with @sjanusz-r7 if you get stuck here

@cgranleese-r7
Copy link
Contributor Author

Your skipped module list looks good; but I think there's a few more modules we could pull in from it to implement:

  • modules/exploits/multi/mysql/mysql_udf_payload.rb - Should work
  • modules/exploits/windows/mysql/mysql_mof.rb - Should work
  • modules/exploits/windows/mysql/mysql_start_up.rb - Should work
  • modules/auxiliary/scanner/mysql/mysql_version.rb - We could either track the banner on the session object, or call select version(); similar to the postgres module. Should be able to sync up with @sjanusz-r7 if you get stuck here

@adfoster-r7 Thanks for taking a look. I can take look into get those updated as well then 👍

@cgranleese-r7 cgranleese-r7 changed the title Updates MySQL modules to now support the new MySQL session type <DRAFT> Updates MySQL modules to now support the new MySQL session type Jan 31, 2024
Copy link
Contributor

@zgoldman-r7 zgoldman-r7 left a comment

Choose a reason for hiding this comment

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

Generally LGTM from a static pov, just a couple questions. Haven't tested for myself yet since this PR doesn't seem to include the MySQL session type addition

modules/auxiliary/scanner/mysql/mysql_hashdump.rb Outdated Show resolved Hide resolved
@cgranleese-r7 cgranleese-r7 force-pushed the update-mysql-modules-to-support-new-mysql-session-type branch from 6f581c8 to fc3135a Compare February 5, 2024 16:46
@adfoster-r7 adfoster-r7 changed the title <DRAFT> Updates MySQL modules to now support the new MySQL session type Updates MySQL modules to now support the new MySQL session type Feb 6, 2024
@@ -6,6 +6,7 @@
class MetasploitModule < Msf::Auxiliary
include Msf::Auxiliary::Report
include Msf::Exploit::Remote::MYSQL
include Msf::OptionalSession
Copy link
Contributor

Choose a reason for hiding this comment

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

Since we're moving to using optional sessions like this here:

  include Msf::OptionalSession::MySQL

Why wouldn't we just override rport and rhost to be something like:

module Msf
  module OptionalSession
    module MySQL
      include Msf::OptionalSession

      # ...
      def rhost
        return session.client.host if session
        super # Or potentially default to datastore['RHOST'] etc
      end

      def rport
        return session.client.port if session
        super
      end
      # ...

That way none of these modules would need code changes to replace rhost->mysql_conn.host

Copy link
Contributor

Choose a reason for hiding this comment

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

@dwelch-r7 This one might be a question for yourself I think 👀

Copy link
Contributor

Choose a reason for hiding this comment

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

I was thinking directly accessing the host/port via the client makes the most sense here, it feels a bit weird to me trying to use rhost and rport when we require the client in every situation anyway, I'm just not sure that getting a result for rhost when the datastore value is empty is expected either

I'm not super against it though, unless there's a scenario where we have an OptionalSession and want to keep rhost/rport datastore options free but that feels like a bit of a stretch

any references to datastore['RHOST'] or datastore['RPORT'] still need to be changed either way whether it's to rhost/rport or mysql_conn.host

Copy link
Contributor

Choose a reason for hiding this comment

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

Works for me 👍 It also stops mixin madness, for modules that would need to target multiple protocols

# If we have a session make use it
if session
print_status("Using existing session #{session.sid}")
self.mysql_conn = session.client
Copy link
Contributor

Choose a reason for hiding this comment

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

Not a blocker; and maybe one for a future PR. But maybe we should move all of this logic into the new optional session MySQL mixin:

def use_mysql_session
  print_status("Using existing session #{session.sid}")
  self.mysql_conn = session.client
  self.sock = session.client.sock_or_conn_or_whatever_is_required # This might be required
end

Copy link
Contributor

Choose a reason for hiding this comment

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

Probably makes sense to explore this approach after the new mixins are available: #18770

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Copy link
Contributor

Choose a reason for hiding this comment

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

Yup totally agree, thinking a new PR that handles this across the sessions makes the most sense

@cgranleese-r7 cgranleese-r7 force-pushed the update-mysql-modules-to-support-new-mysql-session-type branch from fc3135a to 98f961e Compare February 6, 2024 14:09
@cgranleese-r7 cgranleese-r7 force-pushed the update-mysql-modules-to-support-new-mysql-session-type branch from 98f961e to e80f0ef Compare February 6, 2024 14:11
@adfoster-r7
Copy link
Contributor

It looks like there's a bug here where RPORT isn't being defaulted anymore 👀

msf6 auxiliary(admin/mysql/mysql_sql) > rerun rhost=127.0.0.1 username=root password=123456 rport=3306
[*] Reloading module...
[*] New in Metasploit 6.4 - This module can target a SESSION or an RHOST
[*] Running module against 127.0.0.1

[+] 127.0.0.1:3306 - 127.0.0.1:3306 MySQL - Logged in to '' with 'root':'123456'
[*] 127.0.0.1:3306 - Sending statement: 'select version()'...
[*] 127.0.0.1:3306 - 127.0.0.1:3306 MySQL - querying with 'select version()'
[*] 127.0.0.1:3306 -  | 5.5.23 |
[*] Auxiliary module execution completed
msf6 auxiliary(admin/mysql/mysql_sql) > rerun rhost=127.0.0.1 username=root password=123456
[*] Reloading module...
[*] New in Metasploit 6.4 - This module can target a SESSION or an RHOST
[*] Running module against 127.0.0.1

[-] 127.0.0.1: - Auxiliary failed: Rex::InvalidDestination The destination is invalid: (127.0.0.1:0).
[-] 127.0.0.1: - Call stack:
[-] 127.0.0.1: -   /Users/user/.rvm/gems/ruby-3.0.5@metasploit-framework/gems/rex-socket-0.1.55/lib/rex/socket/comm/local.rb:298:in `rescue in create_by_type'
[-] 127.0.0.1: -   /Users/user/.rvm/gems/ruby-3.0.5@metasploit-framework/gems/rex-socket-0.1.55/lib/rex/socket/comm/local.rb:274:in `create_by_type'
[-] 127.0.0.1: -   /Users/user/.rvm/gems/ruby-3.0.5@metasploit-framework/gems/rex-socket-0.1.55/lib/rex/socket/comm/local.rb:36:in `create'
[-] 127.0.0.1: -   /Users/user/.rvm/gems/ruby-3.0.5@metasploit-framework/gems/rex-socket-0.1.55/lib/rex/socket.rb:51:in `create_param'
[-] 127.0.0.1: -   /Users/user/.rvm/gems/ruby-3.0.5@metasploit-framework/gems/rex-socket-0.1.55/lib/rex/socket/tcp.rb:37:in `create_param'
[-] 127.0.0.1: -   /Users/user/.rvm/gems/ruby-3.0.5@metasploit-framework/gems/rex-socket-0.1.55/lib/rex/socket/tcp.rb:28:in `create'
[-] 127.0.0.1: -   /Users/user/Documents/code/metasploit-framework/lib/msf/core/exploit/remote/tcp.rb:102:in `connect'
[-] 127.0.0.1: -   /Users/user/Documents/code/metasploit-framework/lib/msf/core/exploit/remote/mysql.rb:41:in `mysql_login'
[-] 127.0.0.1: -   /Users/user/Documents/code/metasploit-framework/lib/msf/core/exploit/remote/mysql.rb:70:in `mysql_login_datastore'
[-] 127.0.0.1: -   /Users/user/Documents/code/metasploit-framework/modules/auxiliary/admin/mysql/mysql_sql.rb:44:in `run'
[*] Auxiliary module execution completed

@cgranleese-r7
Copy link
Contributor Author

It looks like there's a bug here where RPORT isn't being defaulted anymore 👀

Yeah good catch. I needed to update the OptionalSession mixin to include the default.

As a heads up, I got caught out by having multiple of the new session types features enabled at once and they were overriding each other and the Postgres options being set last was overriding my RPORT value even when I had the fix added. I double checked if the Postgres optional sessions was set to nil but it looks like it was already updated here.

I believe we have a ticket to move each of these session types out into a separate mixin per session type.

@cgranleese-r7 cgranleese-r7 marked this pull request as ready for review February 7, 2024 16:18
@adfoster-r7
Copy link
Contributor

It looks like from your test resource script:

  print_warning("******** SESSION ********")
  if mod.include?("file_enum")
    run_single("run session=1 file_list=/")
  elsif mod.include?("writable_dirs")
    run_single("run session=1 dir_list=/")
  else
    run_single("run session=1")
  end

That the file_list and dir_list are meant to be a local file with a list of directories/files:

    run_single("run rhost=192.168.123.1 username=root password=123456 lhost=192.168.123.1 DIR_LIST=./dirs.txt FILE_LIST=./files.txt")

After creating the corresponding files:

$ files.txt 
/etc/passwd
/etc/passwd
/etc/passwd
$ cat dirs.txt 
/
/tmp
/etc

The old resource script was error'ing out:

[*] Running mod :: auxiliary/scanner/mysql/mysql_writable_dirs :: Existing session
[!] 192.168.123.1:3306 - For every writable directory found, a file called crKGzRQm with the text test will be written to the directory.
[*] 192.168.123.1:3306 - Using existing session 9
[*] 192.168.123.1:3306 - Error: 192.168.123.1: Errno::EISDIR Is a directory @ io_fread - / <----- Error
[*] 192.168.123.1:3306 - Scanned 1 of 1 hosts (100% complete)
[*] Auxiliary module execution completed

@mysql_handle = ::Mysql.connect(rhost, user, pass, db, rport, io: sock)
self.mysql_conn = ::Mysql.connect(rhost, user, pass, db, rport, io: sock)
# Deprecating this in favor off `mysql_conn`
@mysql_handle = ActiveSupport::Deprecation::DeprecatedInstanceVariableProxy.new(self, :mysql_conn, :@mysql_handle, ActiveSupport::Deprecation.new)
Copy link
Contributor

Choose a reason for hiding this comment

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

Confirmed that this deprecation works, and that old modules would continue to function:

msf6 exploit(multi/mysql/mysql_udf_payload) > git checkout upstream/master  -- modules/auxiliary/scanner/mysql/mysql_file_enum.rb
[*] exec: git checkout upstream/master  -- modules/auxiliary/scanner/mysql/mysql_file_enum.rb

msf6 exploit(multi/mysql/mysql_udf_payload) > use auxiliary/scanner/mysql/mysql_file_enum
msf6 auxiliary(scanner/mysql/mysql_file_enum) > rerun rhost=127.0.0.1 username=root password=123456 file_list=./files.txt
[*] Reloading module...

[*] 127.0.0.1:3306        - Login...
[+] 127.0.0.1:3306        - 127.0.0.1:3306 MySQL - Logged in to '' with 'root':'123456'
DEPRECATION WARNING: @mysql_handle is deprecated! Call mysql_conn.query instead of @mysql_handle.query. Args: ["USE mysql"] (called from mysql_query_no_handle at /Users/user/Documents/code/metasploit-framework/modules/auxiliary/scanner/mysql/mysql_file_enum.rb:40)
... etc ...

@adfoster-r7 adfoster-r7 merged commit 8b71afd into rapid7:master Feb 8, 2024
49 checks passed
@cgranleese-r7 cgranleese-r7 deleted the update-mysql-modules-to-support-new-mysql-session-type branch February 8, 2024 12:40
@adfoster-r7
Copy link
Contributor

Release Notes

Updates the multiple MySQL modules to work with a provided MySQL session instead of opening a new connection. This functionality is behind a feature flag which can be enabled with features set mysql_session_type true

@smcintyre-r7 smcintyre-r7 added library rn-enhancement release notes enhancement labels Feb 16, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

5 participants