R师傅在群里发DDCTF比赛地址的时候已经结束了 Orz,但是赛题仍然可以看,放假找时间看了看
PS: 粥师傅搞了点奇怪得东西
首页是个登录,没看到什么东西,看源码的时候发现 LOGIN FORM
里面有个注释
1
| <!-- YWRtaW46IGFkbWluX3Bhc3N3b3JkXzIzMzNfY2FpY2Fpa2Fu -->
|
解开base64是: admin: admin_password_2333_caicaikan
用这个账号密码可以直接登录进去,只有四个查看详情,全是下载文件 test啥的。后来发现这个下载链接看起来很厉害 http://116.85.48.104:5036/gd5Jq3XoKvGKqu5tIH2p/rest/user/getInfomation?filename=informations/readme.txt
像是一个任意文件下载,于是构造了一下发现能下载一些文件,但是这个目录结构不太清楚,不知道代码放在哪儿的。注意到版权里面写着 Quick4j By Eliteams.
,看起来不像是DD自己的东西,于是搜了一下发现是一个开源项目 https://github.com/Eliteams/quick4j
很尴尬,不太懂java,翻了翻资料,发现字节码都是放在 WEB-INF/classes
目录下,通过查看github上clone下来的quick4j发现结构为 com/eliteams/quick4j/web/controller/xxx.java
,下载下来的源码中有一个 UserController.java 是其中唯一一个写了业务逻辑的控制器,于是构造将服务器上的 UserController.class 下载回来,然后就可以用 jd-gui 看看代码了,注意到其中有一个
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
| @RequestMapping(value={"/nicaicaikan_url_23333_secret"}, produces={"text/html;charset=UTF-8"})
@ResponseBody
@RequiresRoles({"super_admin"})
public String xmlView(String xmlData)
{
if (xmlData.length() >= 1000) {
return "Too long~~";
}
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
factory.setExpandEntityReferences(true);
try
{
DocumentBuilder builder = factory.newDocumentBuilder();
InputStream xmlInputStream = new ByteArrayInputStream(xmlData.getBytes());
Document localDocument = builder.parse(xmlInputStream);
}
catch (ParserConfigurationException e)
{
e.printStackTrace();
return "ParserConfigurationException";
}
catch (SAXException e)
{
e.printStackTrace();
return "SAXException";
}
catch (IOException e)
{
e.printStackTrace();
return "IOException";
}
return "ok~ try to read /flag/hint.txt";
}
|
这里很明显是存在一个XXE的,但是需要 super_admin
的权限,通过查看下载回来的quick4j项目可以发现权限判断在文件 security/SecurityRealm.java
文件中
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
| protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token)
throws AuthenticationException
{
String username = String.valueOf(token.getPrincipal());
String password = new String((char[])token.getCredentials());
User authentication = this.userService.authentication(new User(username, password));
if ((username.equals("superadmin_hahaha_2333")) && (password.hashCode() == 0))
{
String wonderful = "you are wonderful,boy~";
System.err.println(wonderful);
}
else if (authentication == null)
{
throw new AuthenticationException("用户名或密码错误.");
}
SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(username, password, getName());
return authenticationInfo;
}
|
用户名已经限定为 superadmin_hahaha_2333
,但是密码的 hashCode 为 0 才可以通过,测试后发现空字符串的hashCode()是可以为零的,然而登录的时候已经判断过非空
1
2
3
4
| if ((user.getUsername().isEmpty()) || (user.getUsername() == null) ||
(user.getPassword().isEmpty()) || (user.getPassword() == null)) {
return "login";
}
|
于是google了一下 spring hashcode() not empty == 0
,在 stackoverflow 上找到 https://stackoverflow.com/questions/18746394/can-a-non-empty-string-have-a-hashcode-of-zero?utm_medium=organic&utm_source=google_rich_qa&utm_campaign=google_rich_qa
使用 superadmin_hahaha_2333 : f5a5a608
成功登录,在 UserController
中定义了一个 hintFile , public static final String hintFile = "/flag/hint.txt";
,因为没有回显,所以利用oob读内容
然后构造访问 tomcat_2:8080 后发现返回 "GET /try%20to%20visit%20hello.action. HTTP/1.1"
,应该就是攻击内网里的struts2了,这时才反应过来题目一开始给的hint: 第二层关卡应用版本号为2.3.1
再次访问 hello.action 后得到 "GET /This%20is%20Struts2%20Demo%20APP,%20try%20to%20read%20/flag/flag.txt. HTTP/1.1"
网上找到的payload都是命令执行的,这里打不到,得把payload改成直接读取flag文件才行