文章目录
  1. 专属链接

DDCTF2018-专属链接 writeup

专属链接

服务器根据每一个用户注册的邮箱,用rsa加密成密文。同样被加密的还有一个用户专属的随机flag。这道题目的思路是先下载到邮箱和flag的加密类,将“好朋友”的邮箱当做新的注册邮箱,加密为邮箱的密文。提交到网页的/flag/getflag处,网页会返回给这个用户在注册时加密的flag密文。利用下载到的秘钥库提取出公钥解密密文,得到flag。 下面说具体步骤。

进入网页,对比和官网的区别,有两处不同:

  1. <link href="/image/banner/ZmF2aWNvbi5pY28= rel="shortcut icon">

这道题的入口在第一个不同处,将ZmF2aWNvbi5pY28= 解码就是facion.ico。访问之后不是展示图标而是下载图标,因此这里存在一个任意文件下载。

考虑到这是一道Java Web,访问的内容并不是以实体文件展示,而是class和url映射,所以第一个问题就是找到到底下载什么文件。在经过一系列测试,我终于下载到了第一个文件:web.xml,位置在./../../WEB-INF/web.xml中,将他编码放到href中,就可以下载了。

web.xml:

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
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
<web-app version="2.4"
xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee
http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">
<display-name>Spring MVC Application</display-name>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>WEB-INF/applicationContext.xml</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<!--<listener>-->
<!--<listener-class>com.didichuxing.ctf.listener.InitListener</listener-class>-->
<!--</listener>-->
<context-param>
<param-name>log4jconfigLocation</param-name>
<param-value>WEB-INF/log4j.properties</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.util.Log4jConfigListener</listener-class>
</listener>
<servlet>
<servlet-name>mvc-dispatcher</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>mvc-dispatcher</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>default</servlet-name>
<url-pattern>*.jpg</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>default</servlet-name>
<url-pattern>*.png</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>default</servlet-name>
<url-pattern>*.js</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>default</servlet-name>
<url-pattern>*.css</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>default</servlet-name>
<url-pattern>*.woff</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>default</servlet-name>
<url-pattern>*.woff2</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>default</servlet-name>
<url-pattern>*.map</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>default</servlet-name>
<url-pattern>*.ttf</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>default</servlet-name>
<url-pattern>*.svg</url-pattern>
</servlet-mapping>
<session-config>
<session-timeout>
30
</session-timeout>
</session-config>
<filter>
<filter-name>encodingFilter</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<init-param>
<param-name>encoding</param-name>
<param-value>UTF-8</param-value>
</init-param>
<init-param>
<param-name>forceEncoding</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>encodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<error-page>
<error-code>404</error-code>
<location>/WEB-INF/pages/404.jsp</location>
</error-page>
</web-app>

从web.xml中可以得知框架是Spring MVC,applicationContext.xml的位置(网上搭建Sping MVC的教程默认都是在WEB-INF下)。还可以知道一个监听器的类地址com.didichuxing.ctf.listener.InitListener

接下来下载applicationContext.xml和com.didichuxing.ctf.listener.InitListener

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
68
69
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<context:annotation-config/>
<context:component-scan base-package="com.didichuxing.ctf.*"/>
<!--导入数据连接池配置文件-->
<bean id="propertyConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="location" value="classpath:properties/db.properties"/>
<property name="fileEncoding" value="utf-8"/>
</bean>
<!--数据连接池配置-->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="${driverClassName}"/>
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
<!-- 初始化连接大小 -->
<property name="initialSize" value="${initialSize}"/>
<!-- 连接池最大数量 -->
<property name="maxActive" value="${maxActive}"/>
<!-- 连接池最大空闲 -->
<property name="maxIdle" value="${maxIdle}"/>
<!-- 连接池最小空闲 -->
<property name="minIdle" value="${minIdle}"/>
<!-- 获取连接最大等待时间 -->
<property name="maxWait" value="${maxWait}"/>
</bean>
<!--Spring Mybatis整合-->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource"/>
<!--<property name="mapperLocations" value="classpath:mapper/*.xml"/>-->
<property name="configLocation" value="classpath:mybatis/config.xml"/>
<property name="typeAliasesPackage" value="com.didichuxing.ctf.model"/>
<property name="plugins">
<array>
<bean class="com.github.pagehelper.PageHelper">
<property name="properties">
<value>
reasonable=true
</value>
</property>
</bean>
</array>
</property>
</bean>
<!-- DAO接口所在包名,Spring会自动查找其下的类 -->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.didichuxing.ctf.dao"/>
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
</bean>
<!-- (事务管理)transaction manager, use JtaTransactionManager for global tx -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<!-- (init) -->
<bean id="initListener" class="com.didichuxing.ctf.listener.InitListener">
</bean>
</beans>

然后我们知道了更多的信息,包名com.didichuxing.ctf 使用Mybatis,Dao类com.didichuxing.ctf.da

我在网上照着教程搭建了一遍Spring-MVC,得知还有一个重要的xml,他会根据web.xml中的servlet-name创建一个servlet-name-servlet.xml,因此就是mvc-dispatcher-servlet.xml。

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
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd">
<context:component-scan base-package="com.didichuxing.ctf"/>
<mvc:annotation-driven/>
<bean id="velocityConfigurer"
class="org.springframework.web.servlet.view.velocity.VelocityConfigurer">
<property name="resourceLoaderPath" value="/WEB-INF/pages/"/> //模板存放的位置
<property name="velocityProperties">
<props>
<prop key="input.encoding">utf-8</prop>
<prop key="output.encoding">utf-8</prop>
</props>
</property>
</bean>
<!--配置附加工具,以及将后缀为vm的文件交给下面的Resolver处理-->
<bean id="velocityViewResolver"
class="org.springframework.web.servlet.view.velocity.VelocityViewResolver">
<property name="suffix" value=".vm"/>
<property name="contentType" value="text/html;charset=utf-8"/>
</bean>
<mvc:interceptors>
<mvc:interceptor>
<mvc:mapping path="/image/banner/"/>
<mvc:mapping path="/image/banner/*"/>
<mvc:mapping path="/image/banner/**"/>
<bean class="com.didichuxing.ctf.interceptor.GlobalInterceptor"/>
</mvc:interceptor>
</mvc:interceptors>
</beans>

这里面又给了一个类:com.didichuxing.ctf.interceptor.GlobalInterceptor

先下载InitListener.class:./../../WEB-INF/classes/com/didichuxing/ctf/interceptor/InitListener.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
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//
package com.didichuxing.ctf.listener;
import com.didichuxing.ctf.model.Flag;
import com.didichuxing.ctf.service.FlagService;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.security.InvalidKeyException;
import java.security.Key;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.PublicKey;
import java.security.SecureRandom;
import java.security.UnrecoverableKeyException;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.util.Properties;
import java.util.UUID;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.Mac;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.SecretKeySpec;
import org.apache.commons.io.FileUtils;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.context.WebApplicationContext;
public class InitListener implements ApplicationListener, InitializingBean {
final String k = "sdl welcome you !";
@Autowired
private FlagService flagService;
private Properties properties = new Properties();
private String p;
public InitListener() {
}
public void afterPropertiesSet() throws Exception {
System.out.println("aftrPropertiesSet");
try {
InputStream inputStream = this.getClass().getClassLoader().getResourceAsStream("/properties/conf.properties");
this.properties.load(inputStream);
} catch (Exception var2) {
var2.printStackTrace();
}
this.p = "sdl welcome you !".substring(0, "sdl welcome you !".length() - 1).trim().replace(" ", "");
}
@RequestMapping("/con")
public void onApplicationEvent(ApplicationEvent event) {
System.out.println("start!@");
if (event.getSource() instanceof ApplicationContext) {
WebApplicationContext ctx = (WebApplicationContext)event.getSource();
if (ctx.getParent() == null) {
String regenflag = this.properties.getProperty("regenflag");
if (regenflag != null && "false".equals(regenflag)) {
System.out.println("skip gen flag");
} else {
try {
this.flagService.deleteAll();
int id = 1;
String path = ctx.getServletContext().getRealPath("/WEB-INF/classes/emails.txt");
String ksPath = ctx.getServletContext().getRealPath("/WEB-INF/classes/sdl.ks");
System.out.println(this.p.toCharArray());
String emailsString = FileUtils.readFileToString(new File(path), "utf-8");
String[] emails = emailsString.trim().split("\n");
KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
FileInputStream inputStream = new FileInputStream(ksPath);
keyStore.load(inputStream, this.p.toCharArray());
Key key = keyStore.getKey("www.didichuxing.com", this.p.toCharArray());
Cipher cipher = Cipher.getInstance(key.getAlgorithm());
cipher.init(1, key);
SecretKeySpec signingKey = new SecretKeySpec("sdl welcome you !".getBytes(), "HmacSHA256");
Mac mac = Mac.getInstance("HmacSHA256");
mac.init(signingKey);
SecureRandom sr = new SecureRandom();
String[] var16 = emails;
int var17 = emails.length;
for(int var18 = 0; var18 < var17; ++var18) {
String email = var16[var18];
String flag = "DDCTF{这里是一个随机数,原来的代码弄丢了}";
String uuid = UUID.randomUUID().toString().replace("-", "s");
byte[] data = cipher.doFinal(flag.getBytes());
byte[] e = mac.doFinal(String.valueOf(email.trim()).getBytes());
Flag flago = new Flag();
flago.setId(id);
flago.setFlag(byte2hex(data));
flago.setEmail(byte2hex(e));
flago.setOriginFlag(flag);
flago.setUuid(uuid);
flago.setOriginEmail(email);
this.flagService.save(flago);
System.out.println(email + "同学的入口链接为:http://116.85.48.102:5050/welcom/" + uuid);
++id;
System.out.println(flago);
}
} catch (KeyStoreException var25) {
var25.printStackTrace();
} catch (IOException var26) {
var26.printStackTrace();
} catch (NoSuchAlgorithmException var27) {
var27.printStackTrace();
} catch (CertificateException var28) {
var28.printStackTrace();
} catch (UnrecoverableKeyException var29) {
var29.printStackTrace();
} catch (NoSuchPaddingException var30) {
var30.printStackTrace();
} catch (InvalidKeyException var31) {
var31.printStackTrace();
} catch (IllegalBlockSizeException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (BadPaddingException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
}
public static String byte2hex(byte[] b) {
StringBuilder hs = new StringBuilder();
for(int n = 0; b != null && n < b.length; ++n) {
String stmp = Integer.toHexString(b[n] & 255);
if (stmp.length() == 1) {
hs.append('0');
}
hs.append(stmp);
}
return hs.toString().toUpperCase();
}
}

从代码中,发现了com.didichuxing.ctf.model.Flag,com.didichuxing.ctf.service.FlagService

com.didichuxing.ctf.model.Flag

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
68
69
70
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//
package com.didichuxing.ctf.model;
public class Flag {
private Integer id;
private String uuid;
private String email;
private String originEmail;
private String flag;
private String originFlag;
public Flag() {
}
public String getUuid() {
return this.uuid;
}
public void setUuid(String uuid) {
this.uuid = uuid;
}
public String getOriginEmail() {
return this.originEmail;
}
public void setOriginEmail(String originEmail) {
this.originEmail = originEmail;
}
public Integer getId() {
return this.id;
}
public void setId(Integer id) {
this.id = id;
}
public String getEmail() {
return this.email;
}
public void setEmail(String email) {
this.email = email;
}
public String getFlag() {
return this.flag;
}
public void setFlag(String flag) {
this.flag = flag;
}
public String getOriginFlag() {
return this.originFlag;
}
public void setOriginFlag(String originFlag) {
this.originFlag = originFlag;
}
public String toString() {
return "Flag{id=" + this.id + ", uuid='" + this.uuid + '\'' + ", email='" + this.email + '\'' + ", originEmail='" + this.originEmail + '\'' + ", flag='" + this.flag + '\'' + ", originFlag='" + this.originFlag + '\'' + '}';
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package com.didichuxing.ctf.service;
import com.didichuxing.ctf.model.Flag;
public interface FlagService {
Flag getFlagByEmail(String var1);
Flag getFlagByUUID(String var1);
Flag getFirst(int var1, int var2, String var3, String var4);
void save(Flag var1);
void deleteAll();
int exist(String var1);
}

大致读一下三个文件,主要的流程在InitListerner.class中,代码流程是加载emais.txt和sdl.ks,根据利用getKey得到的私钥加密emailes.txt和flag,并且会返回一个对应的登陆入口,emails.txt有几个行代码就执行几次。生成一个的结果:

1
2
3113936212117314317@didichuxing.com同学的入口链接为:http://116.85.48.102:5050/welcom/9fa3b571s8832s43a9s8257s275e95ff909f
Flag{id=1, uuid='9fa3b571s8832s43a9s8257s275e95ff909f', email='0DFEE0968F44107479B6CF5784641060DB42952C197C7E8560C2B5F58925FAF4', originEmail='3113936212117314317@didichuxing.com', flag='AEF3A61AC9106D35E0439BFF7FD641CE5C7B0F876190BB281301237C9C251351A37233B40481670EEA1453E03E9E669C8C2C173335337C041DA7F8AF723FF03AB956EE3A275EDA16B31E30AE58FBF55B475016F32E219EEC251650C3BA9289597DD1AD0DF993C438D8F3D0BC8F411EB474287C025F85958D144BC31AE2BB4EBC3E85168E6E961E8DE39D10AD2F48DACE5076A7C8D7E73F3FB27EF9D8C8F17590D622F88FFFFE7B10B83AEEC9FD6C65AD5EA425FE11EF1F8771B4E66135D9D04326EF9973C55D97357B6CC7B55ADFF4E116995CCC8BC8A8D2D664617AE0C58976C57E7EBA5ACD9989EF8D95B547D73CA69B137F17C0EE185B1DB79F7285193C18', originFlag='DDCTF{9999999999}'}

做题时鸡冻的将Flag{}和里面的内容当做flag提交了,发现不正确。想起http://116.85.48.102:5050/flag/testflag/ 这个网页,于是将flag提交到这个网页后面,也就是http://116.85.48.102:5050/flag/testflag/Flag{id=1, uuid='9fa3b571s8832s43a9s8257s275e95ff909f', email='0DFEE0968F44107479B6CF5784641060DB42952C197C7E8560C2B5F58925FAF4', originEmail='3113936212117314317@didichuxing.com', flag='AEF3A61AC9106D35E0439BFF7FD641CE5C7B0F876190BB281301237C9C251351A37233B40481670EEA1453E03E9E669C8C2C173335337C041DA7F8AF723FF03AB956EE3A275EDA16B31E30AE58FBF55B475016F32E219EEC251650C3BA9289597DD1AD0DF993C438D8F3D0BC8F411EB474287C025F85958D144BC31AE2BB4EBC3E85168E6E961E8DE39D10AD2F48DACE5076A7C8D7E73F3FB27EF9D8C8F17590D622F88FFFFE7B10B83AEEC9FD6C65AD5EA425FE11EF1F8771B4E66135D9D04326EF9973C55D97357B6CC7B55ADFF4E116995CCC8BC8A8D2D664617AE0C58976C57E7EBA5ACD9989EF8D95B547D73CA69B137F17C0EE185B1DB79F7285193C18', originFlag='DDCTF{9999999999}'} 又测试了直接提交DDCTF{9999999999},也不对。但是在提交DDCTF{}的时候,页面返回的500错误发生了变化

从新的500错误中,发现com.didichuxing.ctf.controller.user.FlagController.submitFlag(FlagController.java:36),下载下来

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
package com.didichuxing.ctf.controller.user;
import com.didichuxing.ctf.model.Flag;
import com.didichuxing.ctf.service.FlagService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping({"flag"})
public class FlagController {
@Autowired
private FlagService flagService;
public FlagController() {
}
@RequestMapping(
value = {"/getflag/{email:[0-9a-zA-Z']+}"},
method = {RequestMethod.POST}
)
public String getFlag(@PathVariable("email") String email, ModelMap model) {
Flag flag = this.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 = this.flagService.exist(flag);
return i > 0 ? "pass!!!" : "failed!!!";
}
private void init() {
System.out.println("test");
}
}

得知有两个函数,真正应该使用的是getFlag函数。提交的参数是email,于是将生成的加密email发送过去。提交的方法是POST方法,参数放在url中。http://116.85.48.102:5050/flag/getflag/0DFEE0968F44107479B6CF5784641060DB42952C197C7E8560C2B5F58925FAF4

网页返回加密的Flag:506920534F89FA62C1125AABE3462F49073AB9F5C2254895534600A9242B8F18D4E420419534118D8CF9C20D07825C4797AF1A169CA83F934EF508F617C300B04242BEEA14AA4BB0F4887494703F6F50E1873708A0FE4C87AC99153DD02EEF7F9906DE120F5895DA7AD134745E032F15D253F1E4DDD6E4BC67CD0CD2314BA32660AB873B3FF067D1F3FF219C21A8B5A67246D9AE5E9437DBDD4E7FAACBA748F58FC059F662D2554AB6377D581F03E4C85BBD8D67AC6626065E2C950B9E7FBE2AEA3071DC0904455375C66A2A3F8FF4691D0C4D76347083A1E596265080FEB30816C522C6BFEA41262240A71CDBA4C02DB4AFD46C7380E2A19B08231397D099FE

那么这段Flag的原文就对应着这个用户专属的Flag了。下一步就是解决怎么解密Flag的问题。

首先需要一些密码学知识,在Cipher cipher = Cipher.getInstance(key.getAlgorithm()); 中,可以打印出key.getAlgorithm(),返回结果是RSA。RSA是一种被广泛使用的非对称加密算法,加密和解密使用一对秘钥。加密使用公钥,解密就使用私钥。反过来就是私钥加密,公钥解密。Flag加密的方法是公钥,需要用私钥解密。

如何获得私钥呢?tring ksPath = ctx.getServletContext().getRealPath("/WEB-INF/classes/sdl.ks"); 这行代码给我启示,sdl.ks被加载之后变量命名是KeyStore。进行了一系列百度,最后使用的方法是利用java自带的KeyTool从sdl.ks中提取出证书,从证书中再提取私钥解密flag。Keytool命令 keytool -export -alias 别名 -keystore 文件名 -file 证书名称 alias是www.didichuxing.com,密码是sdlwelcomeyou。提取证书后,修改一下原来的Java代码,从crt中获得私钥,再利用私钥解密密文。

这里有一个坑点,在解密的时候会出现paddding error的问题,回顾keytool,想起秘钥库类型是JKS,不太懂是什么,但好像是一个不常见的类型,百度了一下,JKS是安卓签名,在Cipher得到实例的时候写Cipher.getInstance("RSA/ECB/NoPadding");

然后再编写一个hex2byte的函数,就可以直接得到flag了。最终代码如下:

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
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//
package com.didichuxing.ctf.listener;
import com.didichuxing.ctf.model.Flag;
import com.didichuxing.ctf.service.FlagService;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.security.InvalidKeyException;
import java.security.Key;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.PublicKey;
import java.security.SecureRandom;
import java.security.UnrecoverableKeyException;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.util.Properties;
import java.util.UUID;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.Mac;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.SecretKeySpec;
import org.apache.commons.io.FileUtils;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.context.WebApplicationContext;
public class InitListener implements ApplicationListener, InitializingBean {
final String k = "sdl welcome you !";
@Autowired
private FlagService flagService;
private Properties properties = new Properties();
private String p;
public InitListener() {
}
public void afterPropertiesSet() throws Exception {
System.out.println("aftrPropertiesSet");
this.p = "sdl welcome you !".substring(0, "sdl welcome you !".length() - 1).trim().replace(" ", "");
}
@RequestMapping("/con")
public void onApplicationEvent(ApplicationEvent event) {
System.out.println("start!@");
if (event.getSource() instanceof ApplicationContext) {
WebApplicationContext ctx = (WebApplicationContext)event.getSource();
if (ctx.getParent() == null) {
String regenflag = this.properties.getProperty("regenflag");
if (regenflag != null && "false".equals(regenflag)) {
System.out.println("skip gen flag");
} else {
try {
int id = 1;
String path = ctx.getServletContext().getRealPath("/WEB-INF/classes/emails.txt");
String ksPath = ctx.getServletContext().getRealPath("/WEB-INF/classes/sdl.ks");
System.out.println(this.p.toCharArray());
String emailsString = FileUtils.readFileToString(new File(path), "utf-8");
String[] emails = emailsString.trim().split("\n");
KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
FileInputStream inputStream = new FileInputStream(ksPath);
keyStore.load(inputStream, this.p.toCharArray());
Key key = keyStore.getKey("www.didichuxing.com", this.p.toCharArray());
Cipher cipher = Cipher.getInstance("RSA/ECB/NoPadding");
cipher.init(1, key);
SecretKeySpec signingKey = new SecretKeySpec("sdl welcome you !".getBytes(), "HmacSHA256");
Mac mac = Mac.getInstance("HmacSHA256");
mac.init(signingKey);
SecureRandom sr = new SecureRandom();
String[] var16 = emails;
int var17 = emails.length;
String crtpath = ctx.getServletContext().getRealPath("/WEB-INF/classes/sdl.crt");
CertificateFactory cf = CertificateFactory.getInstance("X.509");
FileInputStream in = new FileInputStream(crtpath);
Certificate c = cf.generateCertificate(in);
PublicKey publicKey = c.getPublicKey();
cipher.init(Cipher.DECRYPT_MODE, publicKey);
String flag = "506920534F89FA62C1125AABE3462F49073AB9F5C2254895534600A9242B8F18D4E420419534118D8CF9C20D07825C4797AF1A169CA83F934EF508F617C300B04242BEEA14AA4BB0F4887494703F6F50E1873708A0FE4C87AC99153DD02EEF7F9906DE120F5895DA7AD134745E032F15D253F1E4DDD6E4BC67CD0CD2314BA32660AB873B3FF067D1F3FF219C21A8B5A67246D9AE5E9437DBDD4E7FAACBA748F58FC059F662D2554AB6377D581F03E4C85BBD8D67AC6626065E2C950B9E7FBE2AEA3071DC0904455375C66A2A3F8FF4691D0C4D76347083A1E596265080FEB30816C522C6BFEA41262240A71CDBA4C02DB4AFD46C7380E2A19B08231397D099FE";
byte[] res = flag.getBytes();
byte[] result = cipher.doFinal(hex2byte(res));
String aaa = new String(result);
System.out.print(aaa);
System.out.print("complete");
} catch (KeyStoreException var25) {
var25.printStackTrace();
} catch (IOException var26) {
var26.printStackTrace();
} catch (NoSuchAlgorithmException var27) {
var27.printStackTrace();
} catch (CertificateException var28) {
var28.printStackTrace();
} catch (UnrecoverableKeyException var29) {
var29.printStackTrace();
} catch (NoSuchPaddingException var30) {
var30.printStackTrace();
} catch (InvalidKeyException var31) {
var31.printStackTrace();
} catch (IllegalBlockSizeException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (BadPaddingException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
}
private 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;
}
public static String getType(Object o){
return o.getClass().toString();
}
public static String byte2hex(byte[] b) {
StringBuilder hs = new StringBuilder();
for(int n = 0; b != null && n < b.length; ++n) {
String stmp = Integer.toHexString(b[n] & 255);
if (stmp.length() == 1) {
hs.append('0');
}
hs.append(stmp);
}
return hs.toString().toUpperCase();
}
}

运行结果即得到Flag

一道题做了两天,从不会Spring MVC语法,到RSA一遍遍报错,可谓是坎坷至极。很感谢DD给了一个做Java web题的机会,学习了很多。

支持一下
扫一扫,支持forsigner