发布于  更新于 

致远A6 V8.0SP1 代码审计

授权问题

盗版网站的一日授权

可以从这里 http://lic.seeyom.com/index.jspx?locale=zh_CN 生成一个一天的授权,但是其针对版本为V7.0

授权算法分析

授权工具

安装包解压后,运行0.SeeyonInstall\updateDog\updateDog.exe

  • 选择软加密文件

  • 点击查看license信息

  • 选择之前从seeyom生成的授权文件

通过简单分析发现该程序为exe4j生成,在%temp%目录即可找到相关jar包

最终定位到关键逻辑位于mocnoyeeswz.jar文件

算法分析

经过一番分析,发现授权文件由私钥加密,而updateDog.exe内置公钥用于解密

但进一步分析发现,其私钥也在jar中,所以就可以写出注册机

点击查看代码
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
public class test {
static String n = "21989813755621156293273501389493103170478328865382851535119108650514578230710085815320625775002099957978047341520630619620968646687641136648474885196527432975032680128867461330713312410713852757543371126481833686429634246895522221843242400928239808073984384986341957082609978353191102506262999820889287314940454068736561332035836375947373095017281035332790078205824154732301582606749476995280424823282151268108966524365305986387988471004945472743386691892679883533938532545175617206571176967648832197838471513091863241277420160655821470900975715819210425380750123100541143689073853835049178798646417287183721931874617";
static String e = "6333852850525055576973677246890478791356324122126922020056508750717361082458981795562925565019983229423861325142819235036468333056481098257675212595249223374119839217428796977888447722310227787420025584854320071085527346058665684754182931814431280910212601055696432913444749704337831179497484590672857723791526397236422992667259948858815644211642076355254409538306069722345612392012915604981881367144586134958548638622059431200740663401535386954787142318402161407062367658834129067730124799206565784982639002519538994476859475051193413032441802655895182311620992722743521812713033355316618340286163976059545102169473";
static String d = "65537";

public static void main(String[] args) throws Exception {
//查看mac地址,修改mac地址生成授权
StringBuilder sb = new StringBuilder();
sb.append("AL:3,AM:0,AA:gg,AJ:");
sb.append("00-0C-29-50-21-D5,");
sb.append(
"AD:A6V5,AG:9999,AR:9999,AC:9999,AF:A6V5-1,AE:V8.0,AB:2021-01-01,");
sb.append("AI:2222-02-02,AH:2222-02-02,BA:base64Z2c=,news:1,addressbook:1,");
sb.append("v_culture:1,officeOcx:1,doc:1,v_mbo:1,webmail:1,lbs:1,bulletin:1,Mx:<sl><l><key>Mx1</key><value>0</value></l><l><key>Mx2</key><value>5</value></l><l><key>Mx4</key><value>2222-02-02</value></l></sl>");
makelic(sb.toString());
}

public static void makelic(String lic) throws Exception {

java.security.Key privkey = getPrivateKey(e, n);
StringBuilder sb = new StringBuilder();
java.security.KeyPair kp = generateKeyPair(2048);
String dog = Base64Encode(encode(kp.getPrivate(), lic.getBytes()));
java.security.interfaces.RSAPublicKey puk = (java.security.interfaces.RSAPublicKey) kp.getPublic();

sb.append("dogMsg=");
sb.append(dog);
sb.append("\nmodules=");
sb.append(puk.getModulus().toString());
sb.append("0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000");
sb.append("\nproductLine=A6V5\ndogNo=www.seeyom.com");
System.out.println(Base64Encode(encode(privkey, sb.toString().getBytes())));

}
public static String Base64Encode(byte[] data){
return new sun.misc.BASE64Encoder().encodeBuffer(data).replaceAll("\r\n", "");
}
public static java.security.KeyPair generateKeyPair(int keySize) {
try {
java.security.KeyPairGenerator keyPairGen = java.security.KeyPairGenerator.getInstance("RSA");
keyPairGen.initialize(keySize, new java.security.SecureRandom());
return keyPairGen.genKeyPair();
} catch (java.security.NoSuchAlgorithmException nsae) {
return null;
}
}

public static java.security.Key getPrivateKey(java.lang.String privatekeyStr, java.lang.String moduleStr) {
try {
return (java.security.interfaces.RSAPrivateKey) java.security.KeyFactory.getInstance("RSA")
.generatePrivate(new java.security.spec.RSAPrivateKeySpec(new java.math.BigInteger(moduleStr),
new java.math.BigInteger(privatekeyStr)));
} catch (java.lang.Exception e) {
e.printStackTrace();
return null;
}
}
public static byte[] encode(java.security.Key key, byte[] data) throws Exception {
int blocksize = 245;
java.io.ByteArrayOutputStream out = new java.io.ByteArrayOutputStream(data.length);
try {
javax.crypto.Cipher cipher = javax.crypto.Cipher.getInstance("RSA");
cipher.init(1, key);
int position = 0;
int length = data.length;
boolean more = true;
while (more) {
if (position + blocksize <= length) {
out.write(cipher.doFinal(data, position, blocksize));
position += blocksize;
} else {
more = false;
}
}
if (position < length) {
out.write(cipher.doFinal(data, position, length - position));
}
return out.toByteArray();
} catch (java.lang.Exception e) {
throw e;
}
}

}

最终将输出保存在Seeyon\A6\base\license\A6V5.seeyonkey

调试问题

修改启动脚本

Seeyon\A6\ApacheJetspeed\bin\startup.bat 中添加远程调试

第一行插入,并重新启动

SET CATALINA_OPTS=-server -Xdebug -Xnoagent -Djava.compiler=NONE -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=8000

Idea远程调试

  • 打开Seeyon\A6\ApacheJetspeed目录

  • 右键目录webapps\seeyon\WEB-INF\lib,选择Add as Library

  • 添加远程调试Run->Edit Configurations,填写IP和端口

授权分析

总体来说分为两部分,首先由过滤器com.seeyon.ctp.common.web.filter.CTPSecurityFilter针对URL白名单过滤,不在白名单则均需要授权,最后是shiro的部分。

CTPSecurityFilter

在该过滤器中,将url分为6类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
private static boolean isAjax(String uri, HttpServletRequest request) {
return uri.endsWith("ajax.do");
}
private static boolean isV3xAjax(String uri, HttpServletRequest request) {
return uri.endsWith("getAjaxDataServlet");
}
private static boolean isRest(String uri, HttpServletRequest request) {
return uri.startsWith(request.getContextPath() + "/rest/");
}
private static boolean isSOAP(String uri, HttpServletRequest request) {
return uri.startsWith(request.getContextPath() + "/services/");
}
private static boolean isServlet(String uri, HttpServletRequest request) {
return ServletAuthenticator.accept(request);
}
private static boolean isJSP(String uri, HttpServletRequest request) {
return uri.endsWith(".jsp");
}

针对不同的分类,使用不同的授权

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
if (isSpringController(uri, req)) {
result2.setAuthenticator(controllerAuthenticator);
result2.authenticate(req, resp);
if (result2.getResult() && isAjax(uri, req)) {
result2.setAuthenticator(ajaxAuthenticator);
result2.authenticate(req, resp);
}
} else if (isRest(uri, req)) {
result2.setAuthenticator(restAuthenticator);
result2.authenticate(req, resp);
} else if (isV3xAjax(uri, req)) {
result2.setAuthenticator(v3xAjaxAuthenticator);
result2.authenticate(req, resp);
} else if (isSOAP(uri, req)) {
result2.setAuthenticator(soapAuthenticator);
result2.authenticate(req, resp);
} else if (isServlet(uri, req)) {
result2.setAuthenticator(servletAuthenticator);
result2.authenticate(req, resp);
} else if (isJSP(uri, req)) {
result2.setAuthenticator(jspAuthenticator);
result2.authenticate(req, resp);
} else {
result2.setAuthenticator(defaultAuthenticator);
result2.authenticate(req, resp);
}
if (result2.getResult()) {
getHttpSecurityFilterManager().doFrameFilter(req, resp);
}

isJSP

针对jsp文件,有两个限制

  • jsp文件的最后修改时间不能晚于启动时间(防止jsp被修改)

  • 必须在白名单中,否则需要授权

    • colsso.jsp
    • gke.jsp
    • gke2a8.jsp
    • index.jsp
    • lightweightsso.jsp
    • pc.jsp
    • pc2a8.jsp
    • ssoproxy/jsp/ssoproxy.jsp
    • thirdpartysso/listVoucherA8Form.jsp
    • cache_clear_pending.jsp

isSOAP

无任何限制

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<!-- web service -->
<servlet>
<servlet-name>axis2</servlet-name>
<servlet-class>com.seeyon.ctp.common.ws.CtpAxis2Servlet</servlet-class>
<!-- <load-on-startup>1</load-on-startup> -->
</servlet>
<servlet-mapping>
<servlet-name>axis2</servlet-name>
<url-pattern>/axis2/*</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>axis2</servlet-name>
<url-pattern>/services/*</url-pattern>
</servlet-mapping>
<servlet>
<servlet-name>downloadService</servlet-name>
<servlet-class>com.seeyon.ctp.services.FileOutputService</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>downloadService</servlet-name>
<url-pattern>/services/downloadService</url-pattern>
</servlet-mapping>

后经调试CtpAxis2Servlet未注册任何service

isServlet

  • 必须在白名单中,否则需要授权

    • /getAJAXMessageServlet
    • /getAJAXOnlineServlet
    • /htmlofficeservlet
    • /isignaturehtmlH5servlet
    • /isignaturehtmlservlet
    • /login/sso
    • /login/ssologout
    • /m-signature/
    • /ofdServlet
    • /office/cache/
    • /officeservlet
    • /pdfservlet
    • /sursenServlet
    • /verifyCodeImage.jpg
    • /login/sso
    • /verifyCodeImage.jpg
    • /getAJAXOnlineServlet

这些url对应的servlet可以在Seeyon\A6\ApacheJetspeed\webapps\seeyon\WEB-INF\web.xml中找到对应逻辑

isV3xAjax

  • 必须在白名单中,否则需要授权

    • ajaxColManager_colDelLock
    • ajaxEdocSummaryManager_deleteUpdateObj
    • ajaxEdocManager_ajaxCheckNodeHasExchangeType
    • ajaxEdocSummaryManager_deleteUpdateRecieveObj
1
2
3
4
5
6
7
8
9
10
<servlet>
<servlet-name>AJAXDataServlet</servlet-name>
<servlet-class>
com.seeyon.v3x.common.ajax.AJAXDataServlet
</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>AJAXDataServlet</servlet-name>
<url-pattern>/getAjaxDataServlet</url-pattern>
</servlet-mapping>

处理逻辑为AJAXDataServlet

isRest

  • 必须在白名单中,否则需要授权

    • token
    • application.wadl
    • jssdk
    • authentication
    • cap4/form/pluginScripts
    • orgMember/avatar
    • orgMember/groupavatar
    • m3/appManager/getAppList
    • m3/appManager/download
    • m3/message/unreadCount/
    • m3/login/refresh
    • m3/login/verification
    • m3/theme/homeSkin
    • m3/theme/homeSkinDownload
    • m3/common/service/enabled
    • uc/systemConfig
    • product/hasPlugin
    • product/dongle/data
    • password/retrieve
    • m3/appManager/checkEnv
    • m3/security/device/apply
    • meeting/meetingInviteCard
    • microservice
    • media/verify
    • cap4/form/headerJs
    • ocip/forwardTo

可通过搜索@Path对应的类

isSpringController & isAjax

  • 必须在白名单中,否则需要授权

白名单位于needless_check_login.xml

处理逻辑可搜索ApacheJetspeed\webapps\seeyon\WEB-INF\cfgHome\spring目录中xml文件,找到对应的Controller

shiro

访问响应接口需要对应权限由注解指定

对不需要授权的url进行分析

发现没有高危漏洞,仅发现以下接口可用于获取信息

  1. 枚举用户名
    /seeyon/rest/password/retrieve/getEmailByLoginName/username
  2. 申请绑定用户昵称和手机号
    /seeyon/rest/m3/security/device/apply
  3. ocip可能权限绕过
    /seeyon/rest/ocip/forwardTo?tiket=1
    1. 目前报错,可能要开启ocip
    2. tiket为des加密并且base64的OcipTicket对象
    3. OcipTicket对象的tiket最终发给ocip服务器?
  4. 枚举用户名,但用户要设置手机号
    /seeyon/rest/authentication/sms/{loginName}
  5. 枚举手机号,但用户要设置手机号
    /seeyon/rest/authentication/sms2/tel/{loginTel}
  6. 获取seed
    /seeyon/main.do?method=updateLoginSeed
  7. 枚举用户名-可获取其绑定email和手机号(有掩码)
    /seeyon/personalBind.do?method=getBindTypeByLoginName&loginName=system

待续