找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

3692

积分

0

好友

507

主题
发表于 17 小时前 | 查看: 1| 回复: 0

Web容器是承载Web应用运行的核心环境,其自身的安全性和应用程序的代码安全共同构成了Web安全防御的重要战线。本文将深入解析Java Web的演进与核心架构,并结合实战案例剖析常见安全漏洞的审计方法与Apache服务器经典配置漏洞。

Java Web发展历程与技术演进

初代MVC架构

MVC(Model-View-Controller)是一种经典的设计模式,它将应用程序分为三个核心部分:

  • V(View):代表视图,主要负责页面的渲染。
  • C(Controller):代表控制层,处理页面跳转、业务逻辑,以及访问数据库等操作。
  • M(Model):代表模型,通常是一个类,对应从数据库取出或将要存入数据库的数据。

Java Web MVC 架构图

除了示意图中的Applet,其他技术至今仍广泛应用于互联网,并持续面临着各自的安全威胁。

关键技术的发展节点

Java Web技术栈经历了一系列关键演进:

Java Web 技术栈演进图

① Applet 与 Servlet

  • Applet:一种运行在客户端浏览器中的Java程序,可视为一种支持Java的网页插件。
  • Servlet:用Java编写的服务器端程序,用于动态生成Web内容。

两者的核心区别在于,Applet拥有用户界面类,而Servlet没有界面,它负责接收并处理客户端的请求。

② 从 Servlet 到 JSP
直接使用Servlet输出复杂的HTML页面代码非常繁琐。于是,人们思考:能否直接在HTML中编写Java代码?基于这个想法,JSP(Java Server Pages)诞生了。

JSP本质上也是一种Servlet。当用户访问JSP页面时,它会被编译成由Java代码编写的Servlet,再生成响应返回给客户端。

③ 从 JSP 到 Struts1
在JSP中,页面代码和业务逻辑代码混合在一起,导致业务变动时需要修改大量JSP页面,维护困难。因此,Struts1框架应运而生。

它将JSP作为视图(View)进行界面展示,实现了与业务逻辑的分离,并明确提出了MVC(Model-View-Controller)架构,在当时非常流行,约在2004年达到使用巅峰。但Struts1也有明显缺点:代码严重依赖其自身API,属于侵入式框架,且视图层与控制层耦合较紧密。

④ 从 Struts1 到 Spring MVC & Spring Boot
随后,Spring开源社区推出了Spring MVC,它迅速成为最优秀且最受欢迎的框架。但其问题在于配置非常复杂,需要编写大量配置文件。

为了简化配置,Spring Boot被提出,它遵循“约定大于配置”的理念,极大地简化了项目搭建和配置过程。

⑤ 从单体架构到微服务
然而,即使是基于Spring Boot,在大型项目中,所有代码通常部署在一起(单体架构)。这导致更新某个模块或修复某个Bug时,需要停用整个项目。为了减少停机时间、实现快速更新,目前的主流方向转向了Spring Cloud Microservice(微服务)架构。

Java Web应用与Spring MVC工作流程

Servlet示例

一个典型的Servlet类结构如下,它扩展了HttpServlet,并通过doGetdoPost方法分别处理GET和POST请求。

Java Servlet 代码示例

当收到GET请求时,会进入doGet方法处理;收到POST请求时,则进入doPost方法处理。其中,请求报文被封装在HttpServletRequest对象中,而返回报文则封装在HttpServletResponse对象里。

配置文件映射

web.xml配置文件负责建立URL到具体Servlet的映射关系,主要包含<servlet><servlet-mapping>两部分。

web.xml 配置与目录结构

  1. 首先,将Servlet名称(<servlet-name>)与实际处理请求的Java类(<servlet-class>)绑定。
  2. 然后,将URL访问路径(<url-pattern>)与Servlet名称(<servlet-name>)绑定。

通过这两层绑定,就实现了从浏览器访问的URL到具体后端处理类代码的映射。

Tomcat 默认目录结构
配置之后,就可以在浏览器中通过URL直接访问到对应的Servlet功能。

Tomcat 安装成功页面

Spring MVC工作流程

Spring MVC框架处理请求的过程更为精细,其核心是DispatcherServlet

Spring MVC 请求处理流程图

  1. 浏览器向后端发起HTTP请求。
  2. 请求首先进入DispatcherServlet,它继承自HttpServlet
  3. DispatcherServlet会通过HandlerMapping寻找并确定需要调用哪个控制器(Controller)。
  4. 找到对应的Controller后,控制器会调用模型(Model)来处理具体的业务逻辑。
  5. 业务处理完成后,返回一个ModelAndView对象给DispatcherServlet
  6. DispatcherServlet再通过ViewResolver(视图解析器)来处理视图映射。
  7. 映射关系确定后,DispatcherServlet将模型数据(Model)传递给视图(View)进行渲染。
  8. 最后,生成HTML页面返回给浏览器。

这就是一个完整的Spring MVC请求处理流程。

Servlet 类继承关系图

从继承关系图中可以看到,Spring MVC的类层次结构较为复杂,并且传统配置需要大量XML文件。因此,当前的主流趋势是采用基于Spring Boot的微服务架构。

微服务架构示意图

实际上,每个Spring微服务内部都是一个Spring Boot应用。而Spring Boot正是基于Spring MVC标准演化而来的一种架构,它主要采用“约定大于配置”的方式,极大地减少了配置文件的编写工作量。

源代码审计实战方法

软件代码审计是对编程项目中的源代码进行的全面分析,主要目的是发现错误、安全漏洞或违反编程约定的情况。它是防御性编程的组成部分,旨在软件发布前尽可能减少错误。

审计方式与要点

审计之前,最好创建一个核心问题清单,包括安全漏洞、身份验证问题、授权问题、内存泄漏和不良设计习惯之类的问题。其中,跟踪用户输入数据和敏感函数参数回溯,是最为常用的审计方法。

在审计源码之前,首先要对项目建立宏观概念,包括其核心功能、支撑的业务以及内部的源码结构。

Spring Boot 架构流程

宏观上,可以观察项目的包结构来理解其组织方式。

典型 Java 项目包结构

例如:

  • common包通常存放公共类。
  • config包主要存放配置类。
  • controller包对应控制层。
  • resources目录则用于存放静态文件、配置文件等资源。

不同项目的包结构可能略有差异,但整体思想一致。建立宏观概念后,就可以针对具体的漏洞类别进行指向性分析。

常见漏洞类型及审计思路

① 密码硬编码与明文存储
这类问题常出现在属性文件、Java源码或XML等配置文件中。例如,在数据库连接配置中直接硬编码用户名和密码。

<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/test"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
</bean>
@Bean
public DataSource getDataSource() {
    DataSource dataSource = new DriverManagerDataSource();
    dataSource.setDriverClassName("com.mysql.jdbc.Driver");
    dataSource.setUrl("jdbc:mysql://localhost:3306/contactdb");
    dataSource.setUsername("root");
    dataSource.setPassword("Password");
    return dataSource;
}

审计时,应全局搜索相关关键词(如 passwordusernamejdbc 等),检查敏感信息是否以明文形式暴露。

② 命令注入
关注代码中执行系统命令的位置。常见的关键词和类包括 Runtime.exec()ProcessBuildergetRuntime() 以及 shell 等。

public static void method1(String cmd) {
    try {
        Process process = Runtime.getRuntime().exec(cmd);
        BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
        String s;
        while((s=reader.readLine())!=null) {
            System.out.println(s);
        }
    } catch (IOException e) {
        e.printStackTrace();
    }
}
public static void method2(String cmd) {
    try {
        ProcessBuilder processBuilder = new ProcessBuilder(cmd);
        processBuilder.redirectErrorStream(true);
        Process p = processBuilder.start();
        BufferedReader reader = new BufferedReader(new InputStreamReader(p.getInputStream()));
        String s=null;
        while((s=reader.readLine())!=null) {
            System.out.println(s);
        }
    } catch (IOException e) {
        e.printStackTrace();
    }
}

若发现这些调用,需重点审计执行参数是否用户可控,以及是否存在绕过可能。

③ 文件上传漏洞
首先定位项目中所有潜在的文件上传点。审计重点在于验证逻辑,尤其是对用户输入的白名单过滤机制是否可被绕过。

@PostMapping("/upload")
def upload_file(request):
    if request.method == "POST":
        file = request.FILES.get("file")
        if not file:
            return JsonResponse({"code": 400, "msg": "文件未上传"})

        # 获取文件名
        filename = file.name

        # 设置文件保存路径
        file_directory = os.path.join(settings.MEDIA_ROOT, "uploads")
        if not os.path.exists(file_directory):
            os.makedirs(file_directory)

        # 构建文件保存路径
        file_path = os.path.join(file_directory, filename)

        # 写入文件
        with open(file_path, "wb+") as destination:
            for chunk in file.chunks():
                destination.write(chunk)

        # 返回成功响应
        return JsonResponse({"code": 200, "msg": "文件上传成功", "file_url": settings.MEDIA_URL + "uploads/" + filename})
    else:
        return JsonResponse({"code": 405, "msg": "不支持的请求方法"})

相关关键词包括 uploadmultipartfilefileNamefilePath 等。

④ 越权与URL跳转
寻找进行重定向或转发的函数,如 sendRedirect()setHeader()forward() 等。

public class AdminLoginInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        String requestPath = request.getRequestURI();
        if (requestPath.startsWith("/admin") && !request.getSession().getAttribute("loginUser") != null) {
            response.sendRedirect("/admin/login");
            return false;
        }
        return true;
    }
}

检查这些操作涉及的路径或参数是否用户可控,从而可能导致未授权访问或恶意跳转。

⑤ 其他常见漏洞关键词

  • SQL注入Select, Dao, from, delete, update, insert, createStatement
  • 反序列化readObject, readUnshared, JSON.parseObject
  • XSSgetParamter, param
  • XML注入XMLStreamReader, SAXReader, XMLReader

审计工具

除了手工审计,还可以借助自动化工具提高效率。例如 Fortify SCA,它是一款静态应用程序安全性测试(SAST)产品,可用于分析源代码,检测安全漏洞。

Apache服务器安全配置漏洞

从安全角度审计Apache服务器,有两个主要方向:Apache自身配置的安全性问题,以及其本身存在的已知漏洞(Nday)。

多后缀解析漏洞

Apache有一个特性:通过使用AddHandler指令(例如 AddHandler application/x-httpd-php .php),可以为特定文件扩展名指定处理程序。关键在于,Apache在识别文件扩展名时,会从前向后进行匹配。当遇到无法识别的扩展名时,它会继续向后查找,直到遇到第一个可识别的扩展名,并以此作为该文件的最终类型。

文件多后缀示意图

案例解析
假设上传一个名为 test.php.xyz.jpg 的文件,其内容为 <?php phpinfo(); ?>。首次访问时,它可能被当作图片处理。

文件后缀示例

根据Apache的多后缀解析特性:对于文件名test.php.xyz.jpg,最后一个扩展名.jpg可能是Web应用允许上传的类型,而第一个扩展名.php是Apache可识别的脚本类型。因此,只要Apache配置允许多后缀解析,该文件就可能被当作PHP脚本执行。

配置方式有两种:

  1. 修改Apache主配置文件(httpd.conf等)并重启服务。

    Apache 主配置文件中的 AddHandler 指令

  2. 使用更灵活的.htaccess文件,对当前目录下的文件应用修改。

    通过 .htaccess 文件添加 AddHandler 指令

假设采用第二种方式配置后,再次访问test.php.xyz.jpg文件,该文件已被成功解析并执行其中的PHP代码。

成功解析 PHP 文件后的 phpinfo 页面

Apache Nday漏洞:CVE-2017-15715解析漏洞

Nday漏洞是指已经公开N天的漏洞,与之相对的是尚未公开的0day漏洞。CVE-2017-15715是一个影响Apache 2.4.10-2.4.29版本的换行解析漏洞。

CVE-2017-15715 漏洞影响信息

漏洞复现
假设有一个简单的文件上传页面。

文件上传表单

后端采用黑名单过滤,拦截扩展名为 .php.php5.phtml.pht 的文件。

if(in_array($ext, ['php', 'php3', 'php4', 'php5', 'phtml', 'pht'])) {
    exit('bad file');
}

黑名单过滤代码

直接上传1.php文件会被拦截。

上传 1.php 被拦截的请求与响应

服务器返回 bad file 错误

绕过尝试
在Burp Suite中修改上传请求,将文件名 1.php 末尾(在filename字段的值中)插入一个换行符(十六进制0A)。

在十六进制视图中修改文件名,插入 0A 换行符

这次上传成功,没有收到“bad file”错误。访问上传后的文件,发现它已经被Apache成功解析执行。

成功访问并解析 1.php%0A 文件

漏洞原理
我们实际上传的文件后缀变为了.php%0A%0A是换行符的URL编码)。黑名单禁止的后缀是.php.php5.phtml.pht,因此.php%0A成功绕过了黑名单检查。

文件后缀对比示意图

Apache开发者在编写用于匹配文件类型的正则表达式时,使用了类似\.php$的模式(即以.php结尾)。这里的$符号在正则表达式中匹配“行尾”。而换行符0A正好被解释为行尾。因此,Apache在进行匹配时,会将1.php%0A识别为以.php结尾的文件,从而调用PHP解析器来处理该文件。

Apache 解析漏洞原理流程图

这个漏洞的本质是黑名单过滤逻辑与Apache解析器对文件名解释方式的不一致,从而被绕过。理解和掌握这类底层原理,是进行有效安全代码审计和渗透测试的关键。

希望这篇结合了架构解析与实战案例的文章,能帮助你在云栈社区以及其他技术场景中,更深入地理解Web容器层面的安全风险与防御要点。




上一篇:深度解析 Spring @Value 注入:5大高级用法与避坑指南
下一篇:从美伊局势看数据中心供电与光纤投资机会:技术选择与市场逻辑
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2026-3-4 20:16 , Processed in 0.387230 second(s), 43 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2026 云栈社区.

快速回复 返回顶部 返回列表