前言
滴滴的web题感觉质量还是挺高的,因为每一道题自己做得都比较吃力(菜鸟自白)。。。但更重要的是自己也从每道题中学习到了新知识,这种感觉甚至胜过拿到flag的喜悦…..
数据库的秘密
打开网站后进行简单的探索,可以发现有一个隐藏的input
表单项,还有前端使用了sha1
对传递的数据进行签名和加上时间戳。
当然最大的障碍是防火墙。
经过一天的不断尝试和搜索后终于得到了
POC
,利用方式有两点:
- 1、使用enctype=”multipart/form-data”方式提交表单防火墙不拦截
- 2、隐藏的author表项不会对’(单引号)进行转义
为了让python跑起来我们需要了解它的签名过程,通过
js调试
后我们可以得到它的签名原理。
所以我们的python脚本就可以这样写:
1 | # -*- coding: utf-8 -*- |
flag:DDCTF{IKIDLHNZMKFUDEQE}
。
专属链接
题目描述:1
2
3
4
5
6
7现在,你拿到了滴滴平台为你同学生成的专属登录链接,但是你能进一步拿到专属他的秘密flag么
提示1:虽然原网站跟本次CTF没有关系,原网站是www.xiaojukeji.com
注:题目采用springmvc+mybatis编写,链接至其他域名的链接与本次CTF无关,请不要攻击
http://116.85.48.102:5050/welcom/3fca5965sd7b7s4a71s88c7se658165a791e
打开链接后发现并没有什么业务,都是些从原网站挪过来的模板,这里也纠结了好一会,经过一番比对后,可以发现用于标签页的favicon.ico
有人为涂抹的痕迹。在源代码中打开后发现就直接下载了。
用
winhex
打开后,果然发现了信息。
通过这个信息,我们可以猜测是源码泄露(文件下载)。而因为网站框架是基于java的
spring
,它相关的源码泄露可以参考:
/WEB-INF/web.xml:Web应用程序配置文件,描述了 servlet 和其他的应用组件配置及命名规则。
/WEB-INF/classes/:含了站点所有用的 class 文件,包括 servlet class 和非servlet class,他们不能包含在 .jar文件
/WEB-INF/lib/:存放web应用需要的各种JAR文件,放置仅在这个应用中要求使用的jar文件,如数据库驱动jar文件
/WEB-INF/src/:源码目录,按照包名结构放置各个java文件。
/WEB-INF/database.properties:数据库配置文件
但是发现如果直接拼接WEB-INF/web.xml
却没有该地址,后来再回去看看下载favicon.ico
的链接,发现最后是base64
编码过的。
所以我们猜测需要后面拼接
base64
编码的字符,我们把../../WEB-INF/web.xml
(注意../
)进行编码后拼接访问,发现确实可以造成文件下载。下载后就开始了顺藤摸瓜
的操作了。
我们根据得到的
applicationContext.xml
的位置,继续下载。然后可以从这个文件中得到mybatis/config.xml
的地址。
关于
.xml
文件的下载就介绍到这,其余的文件按照同样的操作就能找到,注意的地方就是路径
。
接下来就是下载.class
文件,从applicationContext.xml
中可以发现程序开始运行的接口类(应该可以这样说吧,,,java web不熟)。
一开始由于没有接触过
java web
的框架,更不熟悉java的类包管理方法,所以碰了挺久壁的。如果你按../../WEB-INF/classes/InitListener.class
下载是不行的,正确的方法是classes
后面按照该类的导入方式依次拼接路径,比如这道题的正确路径就是:../../WEB-INF/classes/com/didichuxing/ctf/listener/InitListener.class
。
需要注意一定要找到
类
,而不能下载包
。
下载的.class
文件需要进行反编译成.java
文件,这个很多在线网站都能做。
其余文件按照上面所述的方法都可以找到并下载,完了以后能拿到的文件如下:
下面就是代码审计的时候了。
首先需要关注的就是FlagController.java
文件,它起到路由的作用。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@RequestMapping({"flag"}) // 定义根路径
public class FlagController
{
@Autowired
private FlagService flagService;
public FlagController() {}
// 接收类似 /flag/getflag/1111路径 用post请求
@RequestMapping(value={"/getflag/{email:[0-9a-zA-Z']+}"}, method={org.springframework.web.bind.annotation.RequestMethod.POST})
public String getFlag(@PathVariable("email") String email, ModelMap model)
{
Flag flag = flagService.getFlagByEmail(email);
return "Encrypted flag : " + flag.getFlag();
}
@RequestMapping({"/testflag/{flag}"})
public String submitFlag(@PathVariable("flag") String flag, ModelMap model)
{
String[] fs = flag.split("[{}]");
Long longFlag = Long.valueOf(fs[1]);
int i = flagService.exist(flag);
if (i > 0) {
return "pass!!!";
}
return "failed!!!";
}
}
这里的email
是我们进入时系统分配的,在首页的右上角能找到。
接着我们来拿到我们的
加密
后的flag
。
按照我们的第一感觉你会发现根本不按套路走,我的第一感觉是会不会真是系统错误,但跟客服姐姐交流后发现这是正常的,为了解决这个问题,只能将全部源码审计一边,最终找到了原因。
在InitListener.java
里我们可以看到关于email、flag
的定义。下面是截取的部分代码:1
2
3
4
5
6
7
8
9
10
11
12SecretKeySpec signingKey = new SecretKeySpec("sdl welcome you !".getBytes(), "HmacSHA256");
Mac mac = Mac.getInstance("HmacSHA256");
mac.init(signingKey);
byte[] e = mac.doFinal(String.valueOf(email.trim()).getBytes());
Flag flago = new Flag();
flago.setId(Integer.valueOf(id));
flago.setFlag(byte2hex(data));
flago.setEmail(byte2hex(e)); # 设置email的值
flago.setOriginFlag(flag);
flago.setUuid(uuid);
flago.setOriginEmail(email);
所以我们可以看到实际的email
是经过加密的,而不是表面的。所以写个同样的java代码得到实际的email
。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19import java.io.*;
import javax.crypto.spec.SecretKeySpec;
import javax.crypto.Mac;
import java.security.Key;
class test
{
public static void main (String[] args) throws java.lang.Exception
{
System.out.println("hi");
SecretKeySpec signingKey = new SecretKeySpec("sdl welcome you !".getBytes(), "HmacSHA256");
Mac mac = Mac.getInstance("HmacSHA256");
mac.init(signingKey);
String email = "3113936212117314317@didichuxing.com"; // 首页得到的email
byte[] e = mac.doFinal(String.valueOf(email.trim()).getBytes());
System.out.println(e);
String tmp1 = byte2hex(e);
}
}
我们将得到的值提交。
现在我们就得到了属于自己的加密后的flag。接下来就是分析flag的加密方法,然后进行相应的解密。下面是相应的加密代码(截取部分):
1 | String p = "sdl welcome you !".substring(0, "sdl welcome you !".length() - 1).trim().replace(" ", ""); |
分析了flag
的加密算法后,我们就来写下解密算法。密钥文件sdl.ks
的下载这里就不赘述了。并且这道题是属于:私钥加密,公钥解密
的一个类型。
由于对java的加解密库的函数不了解,也怪自己搜索的关键字不好,,这里卡了好一会。。。终于在某天早上起床后,,脑瓜子灵光一闪,想到了一些关键字,然后在google结果的第二条就找到了解决方法。下面是解密脚本: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
43import java.io.*;
import java.security.PublicKey;
import java.security.KeyStore;
import java.security.cert.Certificate;
import javax.crypto.Cipher;
class test
{
public static void main (String[] args) throws java.lang.Exception
{
String ksPath = "e:/WEB-INF_classes_sdl.ks";
String p = "sdl welcome you !".substring(0, "sdl welcome you !".length() - 1).trim().replace(" ", "");
System.out.println(KeyStore.getDefaultType()); // jks
KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
FileInputStream inputStream = new FileInputStream(ksPath);
keyStore.load(inputStream, p.toCharArray());
Certificate publicCertificate = keyStore.getCertificate("www.didichuxing.com");
PublicKey publicKey = publicCertificate.getPublicKey();
System.out.println(publicKey.getAlgorithm()); // rsa
System.out.println(publicKey.getEncoded());
String miwen = "506920534F89FA62C1125AABE3462F49073AB9F5C2254895534600A9242B8F18D4E420419534118D8CF9C20D07825C4797AF1A169CA83F934EF508F617C300B04242BEEA14AA4BB0F4887494703F6F50E1873708A0FE4C87AC99153DD02EEF7F9906DE120F5895DA7AD134745E032F15D253F1E4DDD6E4BC67CD0CD2314BA32660AB873B3FF067D1F3FF219C21A8B5A67246D9AE5E9437DBDD4E7FAACBA748F58FC059F662D2554AB6377D581F03E4C85BBD8D67AC6626065E2C950B9E7FBE2AEA3071DC0904455375C66A2A3F8FF4691D0C4D76347083A1E596265080FEB30816C522C6BFEA41262240A71CDBA4C02DB4AFD46C7380E2A19B08231397D099FE";
Cipher cipher =Cipher.getInstance("RSA");
cipher.init(2, publicKey);
byte[] result = cipher.doFinal(hex2byte(miwen.getBytes()));
System.out.println(new String(result));
}
public static byte[] hex2byte(byte[] b) {
if ((b.length % 2) != 0)
throw new IllegalArgumentException("长度不是偶数");
byte[] b2 = new byte[b.length / 2];
for (int n = 0; n < b.length; n += 2) {
String item = new String(b, n, 2);
b2[n / 2] = (byte) Integer.parseInt(item, 16);
}
return b2;
}
}
最后flag:DDCTF{1797193649441981961}
。
考虑篇幅的原因,下题writeup重新开篇。