授权问题
盗版网站的一日授权
可以从这里 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 { 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文件,有两个限制
isSOAP
无任何限制
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| <servlet> <servlet-name>axis2</servlet-name> <servlet-class>com.seeyon.ctp.common.ws.CtpAxis2Servlet</servlet-class> </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进行分析
发现没有高危漏洞,仅发现以下接口可用于获取信息
- 枚举用户名
/seeyon/rest/password/retrieve/getEmailByLoginName/username
- 申请绑定用户昵称和手机号
/seeyon/rest/m3/security/device/apply
- ocip可能权限绕过
/seeyon/rest/ocip/forwardTo?tiket=1
- 目前报错,可能要开启ocip
- tiket为des加密并且base64的OcipTicket对象
- OcipTicket对象的tiket最终发给ocip服务器?
- 枚举用户名,但用户要设置手机号
/seeyon/rest/authentication/sms/{loginName}
- 枚举手机号,但用户要设置手机号
/seeyon/rest/authentication/sms2/tel/{loginTel}
- 获取seed
/seeyon/main.do?method=updateLoginSeed
- 枚举用户名-可获取其绑定email和手机号(有掩码)
/seeyon/personalBind.do?method=getBindTypeByLoginName&loginName=system
待续