分析源码框架

Spring MVC/Boot

Struts2

科蓝PE 常见于网银

JSP/Servlet

刨析项目结构

SPringBoot

简化结构

[用户]
   ⇅
[服务器]
   ⇅
┌───────────────────────────┐
│        Web 中间件         │
│   ┌───────────────────┐   │
│   │ Filter / Interceptor │ │
│   └───────────────────┘   │
│         ⇅                 │
│      Controller           │
│         ⇅                 │
│       Service             │
│         ⇅                 │
│        Mapper             │
└───────────────────────────┘
   ⇅
[数据库]

登录鉴权审计

首先关注 拦截器(Interceptor)、过滤器(Filter)或安全框架如Shiro/Spring Security

关注其中 JWT 、Shiro 等硬编码造成的 认证伪造

若鉴权绕过初步判断不存在,则鉴别权限分配越权(普通用户/管理员用户) 水平越权/垂直越权

SQL注入

第一步先分析 pom.xml (如果是 maven 项目) ,依赖包。

分析SQL采用框架 : JDBC mybatis mybatis-plus Hibernate

Mybatis

Mybatis -> Mapper 层搜索 ${ 重点关注排序注入

例如:

JDBC

搜索拼接关键字定位

关键字: statement prepareStatement

定位关键字 statement

定位关键字 prepareStatement (错误用法等价 statement)

定位关键字 SQLException (异常处理处周围潜在SQL 函数执行)

单独的 SQL 处理方法

方法名

适用场景

返回值类型

用途说明

executeQuery()

仅用于 SELECT 查询语句

ResultSet

返回结果集

executeUpdate()

用于 INSERT/UPDATE/DELETE/DDL

int(影响的行数)

返回修改行数

execute()

适用于 任意 SQL(查询或修改)

boolean

true: 有结果集;false: 没有结果集(比如插入、更新)

如果采用 Statement 则默认非预编译 , 采用 PreparedStatement 则采用预编译 参数绑定

下面采用 Statement 潜在 SQL 注入

/*执勤Sql前截取最后面的空格和英文逗号,并加上‘;’*/
exeSql = exeSql.substring(0, exeSql.length() - 2) + ";";
int status= statement.executeUpdate(exeSql);
statement.close();
connection.close();
// 错误:在预编译语句外部拼接字符串! 未使用 ? 占位符
String sql = "SELECT * FROM users WHERE username = '" + userInput + "'";
PreparedStatement pstmt = connection.prepareStatement(sql); // 等同于 Statement,仍有注入风险

常见拼接方式

+
append()
concat()
join()    

不能被预编译的结构

在预编译中, 字面量(literals)能通过 ? 占位符绑定,所有“结构”层面的东西都必须在 SQL 字符串里写死或由程序拼接预编译处理不了。

数据库对象 & 标识符

表名、视图名、模式(schema)名

// 不可通过 ? 绑定
String table = getFromUser();
String sql = "SELECT * FROM " + table + " WHERE id = ?";

列名、别名

// 列名也不能用 ?
String col = getFromUser();
String sql = "SELECT " + col + " FROM users WHERE status = ?";

索引名、触发器名、存储过程名

数据库名、链接名

SQL 关键字 & 语法结构

排序子句 ORDER BY <column> [ASC|DESC]

分组子句 GROUP BY <column_list>

过滤子句 HAVING <agg_expr> > ? 中的聚合表达式本身不能参数化

分页子句

  • MySQL: LIMIT offset, count

  • PostgreSQL: LIMIT count OFFSET offset

  • SQL Server: OFFSET x ROWS FETCH NEXT y ROWS ONLY

JOIN 类型INNER / LEFT / RIGHT / FULL / CROSS

UNION / INTERSECT / EXCEPT

子查询体,如动态决定要 union 几个子查询

// 这些都属于 SQL “结构”或“控制流”,只能拼接,不能 ? 绑定

操作符 & 函数名

算术/逻辑操作符+ - * / %ANDORNOTLIKEIN(关键字)

比较操作符=<>!=<><=>=

字符串操作符||(串接)

函数名NOW()COUNT()SUM()SUBSTR()CONCAT()

窗口函数ROW_NUMBER()RANK()OVER 子句

动态 SQL 片段

片段拼接:如动态加 WHERE、AND、OR 的子句序列

开关型语句:是否加 FOR UPDATEWITH (NOLOCK)OPTION (RECOMPILE) 等提示

文件操作类

文件上传

// 也要时刻注意是否可目录遍历

上传 html 造成 XSS

危害较小,但是可以造成存储型 XSS

JSP 马

上传可用的 JSP马 有条件限制: 1: 需要能上传 jsp 后缀的文件(或绕过过滤)

2:需要目标服务器采用 Tomcat 搭建,且存在 jsp 解析环境

SpringBoot

SpringBoot 默认是不对 jsp 进行解析的。

想要在 SpringBoot 中使用 jsp ,就需要引入相关依赖,或者自建 WEB-INF, web.xml 等操作,然后在 pom.xml 导入解析依赖。

<!-- 用于编译jsp -->
<dependency>
    <groupId>org.apache.tomcat.embed</groupId>
    <artifactId>tomcat-embed-jasper</artifactId>
    <scope>provided</scope>
</dependency>

存在过滤时的绕过方法

后缀

#探测
#对同一个已知存在的 .jsp 页面改后缀(如 index.jspx、index.jsw)查看是否仍然返回 200/500。

jsp 被限制时,我们也可以考虑采用的后缀有下不等:

jsp  jspa  jspx  jsw  jsv  jspf  jhtml  jSp  jSpx  jSpa  jSw  jSv  jSpf  jHtml #大小写转换   

其中容器支持(默认映射到 JspServlet 的后缀): Apache Tomcat / Jetty / GlassFish / Payara

.jsp  .jspx

IBM WebSphere / Open Liberty

.jsp  .jspx  .jsw  .jsv

Oracle/BEA WebLogic

.jsp  .jspx(JSP Document) .jhtml(需把 PageCompileServlet 或 JSPServlet 手动映射到 .jhtml)

WildFly / JBoss AS

同 Tomcat(继承自 Undertow/Servlet 实现)
.jsp  .jspx

截断

::$DATA    %00

::$DATA 详细参阅: https://labs.portcullis.co.uk/blog/ntfs-alternate-data-streams-for-pentesters-part-1/

NTFS 支持 "一文件多数据流":格式是 文件名:流名:流类型。 (NTFS文件系统,ADS语法)

  • 省略 流名 就是“默认流”;流类型 通常固定写 $DATA

  • 因此 foo.jsp::$DATAfoo.jsp:foo.jsp 三者对真正读写文件时效果完全一样

Windows API 在解析路径时,只把 冒号之前 的部分当文件名。后面的 :$不会出现在硬盘目录里,但路径仍被认为合法。

关键字

// 也要时刻注意是否可目录遍历

───────────────────── Commons FileUpload ─────────────────────
ServletFileUpload
FileItem
FileItemHeaders
FileItemHeadersImpl
FileItemFactory
DefaultFileItem
DefaultFileItemFactory
ProgressListener
​
──────────────────────── Servlet 3.0+ ─────────────────────────
Part
javax.servlet.http.Part
jakarta.servlet.http.Part
@MultipartConfig
MultipartConfigElement
HttpServletRequest#getPart
HttpServletRequest#getParts
​
───────────────────────── Spring MVC ─────────────────────────
MultipartResolver
CommonsMultipartResolver
StandardServletMultipartResolver
MultipartHttpServletRequest
CommonsMultipartFile
StandardMultipartFile
MultipartException
MultipartParsingResult
MultipartFilter
​
──────────────────────── Spring WebFlux ───────────────────────
FilePart
FormFieldPart
PartEvent
PartHttpMessageReader
​
───────────────────────── Struts2 / OGNL ──────────────────────
FileUploadInterceptor
com.opensymphony.xwork2.FileManager
org.apache.struts2.dispatch.multipart.MultiPartRequest
struts.multipart.maxSize
struts.multipart.saveDir
​
────────────────────── Jersey / JAX-RS ────────────────────────
FormDataParam
FormDataContentDisposition
MultiPartFeature
InputStreamDataSource
​
──────────────────── Apache HttpClient / OkHttp ───────────────
MultipartEntityBuilder
MultipartEntity
MultipartBody
MultipartBody.Builder
FormBodyPart
InputStreamEntity
​
──────────────────────── Netty / Reactor ─────────────────────
HttpPostRequestDecoder
InterfaceHttpData
FileUploadData
DiskFileUpload
MixedFileUpload
​
───────────────────── 其他常见工具/类 ─────────────────────────
IOUtils
FilenameUtils
Files.copy
Files.write
Paths.get
Path.toFile
FileInputStream
FileOutputStream
BufferedOutputStream
RandomAccessFile
FileChannel
DiskFileItemFactory
TemporaryFile
tempFilePrefix
uploadTempDir
​

功能点

富文本编辑器图片  -- 高发
模板 / 主题 / 皮肤包上传  -- 解压运行导致的 rce
后台文件管理 / 在线文件操作  -- 易造成文件覆盖形成可利用rce
产品详情图片
商品缩略图
品牌 LOGO
文档附件上传
合同 / 证照上传
身份证照片上传
银行卡照片上传
报销凭证上传
发票图片
二维码生成图
活动海报
CMS 资源文件
静态资源替换
系统配置导入
后台配置文件上传
数据导入 Excel
CSV 数据批量导入
日志备份上传
数据库备份还原
补丁 / 升级文件上传
插件 / 扩展包上传
APK / IPA 上传
微服务 JAR 上传
脚本文件上传
…

额外注意点

现在的 Spring 的 MultipartFile.getOriginalFilename() 实现一般存在 3 种

CommonsMultipartFile.getOriginalFilename()

StandardMultipartFile.getOriginalFilename()

MockMultipartFile.getOriginalFilename()

CommonsMultipartFile 中
    
// 在获取原始文件名时,会默认做一部分的截断清洗,导致目录遍历失效
int unixSep = filename.lastIndexOf(47);
int winSep = filename.lastIndexOf(92);
int pos = Math.max(winSep, unixSep);
return pos != -1 ? filename.substring(pos + 1) : filename;
​
// 而 StandardMultipartFile 与 MockMultipartFile 均默认返回传入的 filename 

SSRF

函数: isSiteLocalAddress

#只能拦截:
10/8、172.16/12、192.168/16 以及 IPv6 fec0::/10

利用方式

绕过原理

样例 Payload¹

IPv6 回环

::1 不是 site-local,也不在黑名单

http://[::1]:8080/health

Link-Local

169.254.*.*isLinkLocalAddress() 才会挡

http://169.254.169.254/latest/meta-data/

0.0.0.0 / 广播

同上

http://0.0.0.0:6379

CG-NAT / 保留段

100.64/10、192.0.2/24 等未检测

http://100.64.0.10:2375/version

数字 / 十六进制写法

黑名单按字符串精确匹配

http://2130706433/ (= 127.0.0.1)http://0x7f000001/

子域→回环

filters 不含子域名

http://127.0.0.1.nip.io:8080/

HTTP 重定向

目标第一次合法,302 到内网

http://attacker.com/redirect → 302 Location: http://127.0.0.1:9000/

DNS 重绑定

首次解析外网 IP → 校验通过;第二次访问同域已换成内网 IP

需两步请求,可配合上面的 302

支付处理

提取 Restful API

Spring Boot 2.x 特性提供支持

配置依赖 访问端点 /actuator/mappings

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

反射扫描

后续待完善

国家一级保护废物