2012-01-12 15:20:46 -05:00
##
2017-07-24 06:26:21 -07:00
# This module requires Metasploit: https://metasploit.com/download
2013-10-15 13:50:46 -05:00
# Current source: https://github.com/rapid7/metasploit-framework
2012-01-12 15:20:46 -05:00
##
2016-03-08 14:02:44 +01:00
class MetasploitModule < Msf :: Auxiliary
2012-01-12 15:20:46 -05:00
include Msf :: Exploit :: Remote :: Postgres
include Msf :: Auxiliary :: Report
include Msf :: Auxiliary :: Scanner
2024-01-30 17:15:00 +00:00
include Msf :: OptionalSession :: PostgreSQL
2012-01-12 15:20:46 -05:00
def initialize
super (
2021-07-29 18:51:58 +01:00
'Name' = > 'Postgres Schema Dump' ,
'Description' = > %(
2012-01-18 15:01:00 -06:00
This module extracts the schema information from a
2012-01-12 15:20:46 -05:00
Postgres server.
2021-07-29 18:51:58 +01:00
) ,
'Author' = > [ 'theLightCosine' ] ,
2024-01-15 15:21:13 +00:00
'License' = > MSF_LICENSE ,
2012-01-12 15:20:46 -05:00
)
register_options ( [
2021-07-29 18:51:58 +01:00
OptBool . new ( 'DISPLAY_RESULTS' , [ true , 'Display the Results to the Screen' , true ] ) ,
OptString . new ( 'IGNORED_DATABASES' , [ true , 'Comma separated list of databases to ignore during the schema dump' , 'template1,template0' ] )
] )
2012-01-12 15:20:46 -05:00
deregister_options ( 'SQL' , 'RETURN_ROWSET' , 'VERBOSE' )
end
2024-02-19 10:57:53 +00:00
def rhost
self . postgres_conn . peerhost
end
def rport
self . postgres_conn . peerport
end
2021-07-29 18:51:58 +01:00
def run_host ( _ip )
2024-01-24 13:47:22 +00:00
if session
print_status 'When targeting a session, only the current database can be dumped.'
self . postgres_conn = session . client
end
2012-01-12 15:20:46 -05:00
pg_schema = get_schema
pg_schema . each do | db |
report_note (
2024-01-15 15:21:13 +00:00
host : rhost ,
2021-07-29 18:51:58 +01:00
type : 'postgres.db.schema' ,
2025-05-21 10:45:08 +01:00
data : { :database = > db } ,
2024-01-15 15:21:13 +00:00
port : rport ,
2021-07-29 18:51:58 +01:00
proto : 'tcp' ,
update : :unique_data
2012-01-12 15:20:46 -05:00
)
end
2024-01-15 15:21:13 +00:00
output = " Postgres SQL Server Schema \n Host: #{ rhost } \n Port: #{ rport } \n ==================== \n \n "
2012-01-12 15:20:46 -05:00
output << YAML . dump ( pg_schema )
this_service = report_service (
2024-01-15 15:21:13 +00:00
host : rhost ,
port : rport ,
2021-07-29 18:51:58 +01:00
name : 'postgres' ,
proto : 'tcp'
)
2024-01-15 15:21:13 +00:00
store_loot ( 'postgres_schema' , 'text/plain' , rhost , output , " #{ rhost } _postgres_schema.txt " , 'Postgres SQL Schema' , this_service )
2012-05-17 22:29:38 -05:00
print_good output if datastore [ 'DISPLAY_RESULTS' ]
2012-01-12 15:20:46 -05:00
end
def get_schema
2021-07-29 18:51:58 +01:00
ignored_databases = datastore [ 'IGNORED_DATABASES' ] . split ( ',' ) . map ( & :strip )
2012-01-12 15:20:46 -05:00
pg_schema = [ ]
2024-01-15 15:21:13 +00:00
database_names = session ? [ session . client . params [ 'database' ] ] : smart_query ( 'SELECT datname FROM pg_database' ) . to_a . flatten
2021-07-29 18:51:58 +01:00
if database_names . empty?
print_status ( " #{ rhost } : #{ rport } - No databases found " )
return pg_schema
end
status_message = " #{ rhost } : #{ rport } - Found databases: #{ database_names . join ( ', ' ) } . "
excluded_databases = ( database_names & ignored_databases )
status_message += " Ignoring #{ excluded_databases . join ( ', ' ) } . " if excluded_databases . any?
print_status ( status_message )
extractable_database_names = database_names - ignored_databases
extractable_database_names . each do | database_name |
next if ignored_databases . include? database_name
2025-06-20 13:20:44 +01:00
2021-07-29 18:51:58 +01:00
tmp_db = { }
tmp_db [ 'DBName' ] = database_name
tmp_db [ 'Tables' ] = [ ]
2024-01-24 13:47:22 +00:00
postgres_login ( { database : database_name } ) unless session
2021-07-29 18:51:58 +01:00
tmp_tblnames = smart_query ( " SELECT c.relname, n.nspname FROM pg_catalog.pg_class c LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace WHERE n.nspname NOT IN ('pg_catalog','pg_toast') AND pg_catalog.pg_table_is_visible(c.oid); " )
if tmp_tblnames && ! tmp_tblnames . empty?
tmp_tblnames . each do | tbl_row |
tmp_tbl = { }
tmp_tbl [ 'TableName' ] = tbl_row [ 0 ]
tmp_tbl [ 'Columns' ] = [ ]
tmp_column_names = smart_query ( " SELECT A.attname, T.typname, A.attlen FROM pg_class C, pg_namespace N, pg_attribute A, pg_type T WHERE (N.oid=C.relnamespace) AND (A.attrelid=C.oid) AND (A.atttypid=T.oid) AND (A.attnum>0) AND (NOT A.attisdropped) AND (N.nspname ILIKE 'public') AND (c.relname=' #{ tbl_row [ 0 ] } '); " )
if tmp_column_names && ! tmp_column_names . empty?
tmp_column_names . each do | column_row |
tmp_column = { }
tmp_column [ 'ColumnName' ] = column_row [ 0 ]
tmp_column [ 'ColumnType' ] = column_row [ 1 ]
tmp_column [ 'ColumnLength' ] = column_row [ 2 ]
tmp_tbl [ 'Columns' ] << tmp_column
2012-01-12 15:20:46 -05:00
end
end
2021-07-29 18:51:58 +01:00
tmp_db [ 'Tables' ] << tmp_tbl
2012-01-12 15:20:46 -05:00
end
end
2021-07-29 18:51:58 +01:00
pg_schema << tmp_db
2012-01-12 15:20:46 -05:00
end
2021-07-29 18:51:58 +01:00
pg_schema
end
2012-01-12 15:20:46 -05:00
def smart_query ( query_string )
2021-07-29 18:51:58 +01:00
res = postgres_query ( query_string , false )
2015-04-03 16:12:23 +05:00
# Error handling routine here, borrowed heavily from todb
2012-01-12 15:20:46 -05:00
case res . keys [ 0 ]
when :conn_error
2021-07-29 18:51:58 +01:00
print_error ( 'A Connection Error Occurred' )
2012-01-12 15:20:46 -05:00
return
when :sql_error
case res [ :sql_error ]
when / ^C42501 /
2024-01-15 15:21:13 +00:00
print_error " #{ rhost } : #{ rport } Postgres - Insufficient permissions. "
2012-01-12 15:20:46 -05:00
else
2024-01-15 15:21:13 +00:00
print_error " #{ rhost } : #{ rport } Postgres - #{ res [ :sql_error ] } "
2012-01-12 15:20:46 -05:00
end
2021-07-29 18:51:58 +01:00
return nil
2012-01-12 15:20:46 -05:00
when :complete
return res [ :complete ] . rows
end
end
end