Некоторое время назад я решил освежить и расширить свои знания в области тестирования на проникновение. Для этого я начал проходить курсы на Hack The Box. Всё шло гладко, пока не дошло до задачи по перечислению SMTP-пользователей. Но эта история не столько о самом перечислении, сколько о том, как ChatGPT помогает нам исправлять инструменты, с которыми мы работаем в нашей непростой профессии.
Введение
Задача казалась простой: у меня был список имён пользователей, и нужно было определить, какие из них существуют на SMTP-сервере. Стандартный скрипт Nmap smtp-enum-users идеально подходил для этой задачи. Вот команда, которую я изначально использовал, и которая теоретически должна была всё решить:
$sudo nmap -Pn -n -p25 -sC --script smtp-enum-users.nse --script-args "smtp-enum-users.methods={VRFY},userdb=./userlist" 10.129.197.249
Starting Nmap 7.94SVN ( https://nmap.org ) at 2025-03-18 02:23 UTC
Nmap scan report for 10.129.197.249
Host is up (0.090s latency).
PORT STATE SERVICE
25/tcp open smtp
| smtp-enum-users:
|_ Couldn't find any accounts
Nmap done: 1 IP address (1 host up) scanned in 6.36 seconds
Проблема заключалась в том, что я точно знал — хотя бы один пользователь на сервере существует. Тем не менее, Nmap упорно сообщал, что ни одной учётной записи не найдено. Сначала я подумал, что дело в аргументах команды, перепробовал разные варианты, но ничего не помогло.
ChatGPT приходит на помощь
Тогда я решил спросить ChatGPT, и он предложил отличную идею: использовать опцию --packet-trace, чтобы точно увидеть, какие данные Nmap отправляет серверу. И вот тут стало интересно:
$sudo nmap -Pn -n -p25 -sC --script '/home/user/repo/htb/labs/nmap/footprinting/smtp-enum-users.nse' --script-args "smtp-enum-users.methods={VRFY},userdb=./userlist" 10.129.131.114 --packet-trace
Starting Nmap 7.94SVN ( https://nmap.org ) at 2025-03-19 03:06 UTC
SENT (0.0634s) TCP 10.10.15.178:54712 > 10.129.131.114:25 S ttl=48 id=9347 iplen=44 seq=2493288949 win=1024 <mss 1460>
RCVD (0.1543s) TCP 10.129.131.114:25 > 10.10.15.178:54712 SA ttl=63 id=0 iplen=44 seq=902777686 win=64240 <mss 1360>
NSOCK INFO [0.0440s] nsock_iod_new2(): nsock_iod_new (IOD #1)
NSOCK INFO [0.1990s] nsock_connect_tcp(): TCP connection requested to 10.129.131.114:25 (IOD #1) EID 8
NSOCK INFO [0.2860s] nsock_trace_handler_callback(): Callback: CONNECT SUCCESS for EID 8 [10.129.131.114:25]
NSE: TCP 10.10.15.178:59690 > 10.129.131.114:25 | CONNECT
NSOCK INFO [0.2860s] nsock_read(): Read request from IOD #1 [10.129.131.114:25] (timeout: 20000ms) EID 18
NSOCK INFO [6.1890s] nsock_trace_handler_callback(): Callback: READ SUCCESS for EID 18 [10.129.131.114:25] (27 bytes): 220 InFreight ESMTP v2.11..
NSE: TCP 10.10.15.178:59690 < 10.129.131.114:25 | 00000000: 32 32 30 20 49 6e 46 72 65 69 67 68 74 20 45 53 220 InFreight ES
00000010: 4d 54 50 20 76 32 2e 31 31 0d 0a MTP v2.11
NSE: TCP 10.10.15.178:59690 > 10.129.131.114:25 | 00000000: 45 48 4c 4f 20 6e 6d 61 70 2e 73 63 61 6e 6d 65 EHLO nmap.scanme
00000010: 2e 6f 72 67 0d 0a .org
NSOCK INFO [6.1900s] nsock_write(): Write request for 22 bytes to IOD #1 EID 27 [10.129.131.114:25]
NSOCK INFO [6.1900s] nsock_trace_handler_callback(): Callback: WRITE SUCCESS for EID 27 [10.129.131.114:25]
NSE: TCP 10.10.15.178:59690 > 10.129.131.114:25 | SEND
NSOCK INFO [6.1920s] nsock_readlines(): Read request for 1 lines from IOD #1 [10.129.131.114:25] EID 34
NSOCK INFO [6.2790s] nsock_trace_handler_callback(): Callback: READ SUCCESS for EID 34 [10.129.131.114:25] (156 bytes)
NSE: TCP 10.10.15.178:59690 < 10.129.131.114:25 | 00000000: 32 35 30 2d 6d 61 69 6c 31 0d 0a 32 35 30 2d 50 250-mail1 250-P
00000010: 49 50 45 4c 49 4e 49 4e 47 0d 0a 32 35 30 2d 53 IPELINING 250-S
00000020: 49 5a 45 20 31 30 32 34 30 30 30 30 0d 0a 32 35 IZE 10240000 25
00000030: 30 2d 56 52 46 59 0d 0a 32 35 30 2d 45 54 52 4e 0-VRFY 250-ETRN
00000040: 0d 0a 32 35 30 2d 53 54 41 52 54 54 4c 53 0d 0a 250-STARTTLS
00000050: 32 35 30 2d 45 4e 48 41 4e 43 45 44 53 54 41 54 250-ENHANCEDSTAT
00000060: 55 53 43 4f 44 45 53 0d 0a 32 35 30 2d 38 42 49 USCODES 250-8BI
00000070: 54 4d 49 4d 45 0d 0a 32 35 30 2d 44 53 4e 0d 0a TMIME 250-DSN
00000080: 32 35 30 2d 53 4d 54 50 55 54 46 38 0d 0a 32 35 250-SMTPUTF8 25
00000090: 30 20 43 48 55 4e 4b 49 4e 47 0d 0a 0 CHUNKING
NSE: TCP 10.10.15.178:59690 > 10.129.131.114:25 | 00000000: 56 52 46 59 20 67 72 65 67 6f 72 79 0d 0a VRFY gregory
NSOCK INFO [6.2790s] nsock_write(): Write request for 14 bytes to IOD #1 EID 43 [10.129.131.114:25]
NSOCK INFO [6.2790s] nsock_trace_handler_callback(): Callback: WRITE SUCCESS for EID 43 [10.129.131.114:25]
NSE: TCP 10.10.15.178:59690 > 10.129.131.114:25 | SEND
NSOCK INFO [6.2800s] nsock_readlines(): Read request for 1 lines from IOD #1 [10.129.131.114:25] EID 50
NSOCK INFO [6.3680s] nsock_trace_handler_callback(): Callback: READ SUCCESS for EID 50 [10.129.131.114:25] (88 bytes)
NSE: TCP 10.10.15.178:59690 < 10.129.131.114:25 | 550 5.1.1 <gregory>: Recipient address rejected: User unknown in local recipient table
NSE: TCP 10.10.15.178:59690 > 10.129.131.114:25 | 00000000: 51 55 49 54 0d 0a QUIT
NSOCK INFO [6.3690s] nsock_write(): Write request for 6 bytes to IOD #1 EID 59 [10.129.131.114:25]
NSOCK INFO [6.3690s] nsock_trace_handler_callback(): Callback: WRITE SUCCESS for EID 59 [10.129.131.114:25]
NSE: TCP 10.10.15.178:59690 > 10.129.131.114:25 | SEND
NSE: TCP 10.10.15.178:59690 > 10.129.131.114:25 | CLOSE
NSOCK INFO [6.3690s] nsock_iod_delete(): nsock_iod_delete (IOD #1)
Nmap scan report for 10.129.131.114
Host is up (0.091s latency).
PORT STATE SERVICE
25/tcp open smtp
| smtp-enum-users:
|_ Couldn't find any accounts
Nmap done: 1 IP address (1 host up) scanned in 6.37 seconds
Из трассировки стало ясно, что скрипт проверял только первого пользователя из файла, а затем сразу останавливался, считая дальнейшие проверки бессмысленными. Стало очевидно: в скрипте либо ошибка, либо это такая “особенность”.
Я не силён в Lua и не хотел глубоко копаться в скрипте, поэтому снова обратился к ChatGPT, и он сразу указал на проблемный участок кода:
if status == STATUS_CODES.NOTPERMITTED then
-- Method not permitted, stop checking further users
break
end
Скрипт, увидев ошибку 550, ошибочно считал, что метод VRFY вообще не поддерживается сервером, хотя на деле это означало лишь то, что конкретный пользователь не существует.
Исправление “особенности”
Я решил проблему просто: удалил оператор break, чтобы скрипт не завершался преждевременно и продолжал проверять других пользователей:
if status == STATUS_CODES.NOTPERMITTED then
-- Method not permitted for a specific user; skip and continue
stdnse.print_debug(1, "Method not permitted for user %s", user)
continue
end
Новая проблема — новый вызов
Проблема вроде бы решена, но тут появилась новая: после нескольких попыток SMTP-сервер начал отвечать ошибкой 421, указывая на слишком большое число неверных запросов и требуя новое подключение. Это выглядело так:
NSOCK INFO [29.9070s] nsock_write(): Write request for 13 bytes to IOD #1 EID 363 [10.129.131.114:25]
NSOCK INFO [29.9070s] nsock_trace_handler_callback(): Callback: WRITE SUCCESS for EID 363 [10.129.131.114:25]
NSE: TCP 10.10.15.178:41264 > 10.129.131.114:25 | SEND
NSOCK INFO [29.9070s] nsock_readlines(): Read request for 1 lines from IOD #1 [10.129.131.114:25] EID 370
NSOCK INFO [31.0340s] nsock_trace_handler_callback(): Callback: READ SUCCESS for EID 370 [10.129.131.114:25] (87 bytes)
NSE: TCP 10.10.15.178:41264 < 10.129.131.114:25 | 550 5.1.1 <rachel>: Recipient address rejected: User unknown in local recipient table
NSE: TCP 10.10.15.178:41264 > 10.129.131.114:25 | 00000000: 56 52 46 59 20 61 64 61 6d 0d 0a VRFY adam
NSOCK INFO [31.0350s] nsock_write(): Write request for 11 bytes to IOD #1 EID 379 [10.129.131.114:25]
NSOCK INFO [31.0350s] nsock_trace_handler_callback(): Callback: WRITE SUCCESS for EID 379 [10.129.131.114:25]
NSE: TCP 10.10.15.178:41264 > 10.129.131.114:25 | SEND
NSOCK INFO [31.0360s] nsock_readlines(): Read request for 1 lines from IOD #1 [10.129.131.114:25] EID 386
NSOCK INFO [32.0590s] nsock_trace_handler_callback(): Callback: READ SUCCESS for EID 386 [10.129.131.114:25] (40 bytes): 421 4.7.0 mail1 Error: too many errors..
NSE: TCP 10.10.15.178:41264 < 10.129.131.114:25 | 421 4.7.0 mail1 Error: too many errors
NSE: TCP 10.10.15.178:41264 > 10.129.131.114:25 | 00000000: 56 52 46 59 20 61 64 61 6d 40 6e 6d 61 70 2e 73 VRFY adam@nmap.s
00000010: 63 61 6e 6d 65 2e 6f 72 67 0d 0a canme.org
NSOCK INFO [32.0610s] nsock_write(): Write request for 27 bytes to IOD #1 EID 395 [10.129.131.114:25]
NSOCK INFO [32.0610s] nsock_trace_handler_callback(): Callback: WRITE ERROR [Connection reset by peer (104)] for EID 395 [10.129.131.114:25]
NSE: TCP 10.10.15.178:41264 > 10.129.131.114:25 | SEND
NSE: TCP 10.10.15.178:41264 > 10.129.131.114:25 | CLOSE
NSOCK INFO [32.0610s] nsock_iod_delete(): nsock_iod_delete (IOD #1)
Nmap scan report for 10.129.131.114
Host is up (1.5s latency).
PORT STATE SERVICE
25/tcp open smtp
| smtp-enum-users_fix1:
| Method VRFY is not permitted for user gregory.
| Method VRFY is not permitted for user sharon.
| Method VRFY is not permitted for user larry.
| Method VRFY is not permitted for user angela.
Сервер явно сам завершал соединение:
WRITE ERROR [Connection reset by peer (104)] for EID 395 [10.129.131.114:25]
Чтобы обойти это, я доработал скрипт: теперь при получении ошибки 421 он автоматически переподключался и продолжал проверку, ограничивая число попыток, чтобы избежать бесконечных циклов. Не буду описывать всю отладку, но скажу одно — ChatGPT очень помог в понимании логики скрипта. Финальный результат можно посмотреть здесь.
Рабочая версия выглядит так:
$sudo nmap -Pn -n -p25 -sC --script '/home/user/repo/htb/labs/nmap/footprinting/smtp-enum-users-updated.nse' --script-args "smtp-enum-users.methods={VRFY},userdb=./userlist" 10.129.131.114
Starting Nmap 7.94SVN ( https://nmap.org ) at 2025-03-19 02:53 UTC
Nmap scan report for 10.129.131.114
Host is up (0.086s latency).
PORT STATE SERVICE
25/tcp open smtp
| smtp-enum-users-updated:
| Method VRFY is not permitted for user someuser.
| Method VRFY is not permitted for user some-other-user.
| ...
| Method VRFY returned 252 2.0.0 some_other_user for user some_other_user.
| Method VRFY is not permitted for user some-other-user2.
| ...
Nmap done: 1 IP address (1 host up) scanned in 33.03 seconds
Теперь видно, что для некоторых пользователей мы получаем код 550 (не существует), а для других — 252, что означает, что пользователь, скорее всего, существует.
Заключение
С помощью ChatGPT мы не только точно определили причину сбоя скрипта, но и успешно его исправили. Теперь утилита корректно проверяет всех пользователей из списка, обрабатывает ошибки и достигает ожидаемого результата.
Так что если что-то работает не так, как ожидалось — помните, ChatGPT всегда рядом и готов помочь. Иногда достаточно просто взглянуть на проблему под новым углом.