SpringBoot审计基础知识
字数 1530 2025-08-24 07:48:33
SpringBoot安全审计基础知识
1. SQL注入漏洞
1.1 JDBC注入
public String int_based(String id, Model model) {
StringBuilder result = new StringBuilder();
try {
Class.forName("com.mysql.cj.jdbc.Driver");
Connection conn = DriverManager.getConnection(db_url, db_user, db_pass);
String sql = "select * from users where id = " + id; // 直接拼接SQL导致注入
result.append("执行SQL语句: ").append(sql).append(System.lineSeparator());
PreparedStatement st = conn.prepareStatement(sql); // 错误使用预编译
ResultSet rs = st.executeQuery();
// ...结果处理...
} catch (Exception e) {
model.addAttribute("results", e.toString());
}
return "basevul/sqli/jdbc_int_based";
}
修复方案:
- 使用参数化查询:
String sql = "select * from users where id = ?"; - 正确使用PreparedStatement:
st.setInt(1, Integer.parseInt(id));
1.2 MyBatis注入
1.2.1 Like查询注入
<!-- 错误写法 -->
Select * from users where username like '%#{username}%' // 会报错
Select * from users where username like '%${username}%' // 导致注入
<!-- 正确写法 -->
Select * from users where username like concat('%',#{username}, '%')
POC: xxx%' union select database(),user(),@@version,4,5 -- -
1.2.2 Order By注入
<!-- 错误写法 -->
Select * from users order by ${field} // 导致注入
<!-- 修复方案 -->
- 避免使用${},改为白名单校验
- 在Java代码中处理排序逻辑
POC: id and (updatexml(1,concat(0x7e,(select user())),0))-- -
1.2.3 In查询注入
<!-- 错误写法 -->
Select * from users where id in (${ids}) // 导致注入
<!-- 修复方案 -->
- 使用foreach标签
- 在Java代码中处理参数校验
POC: 1,2,3) and (updatexml(1,concat(0x7e,(select user())),0))-- -
2. 鉴权类漏洞
2.1 未授权访问
@Configuration
public class MvcConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LoginHandlerInterceptor())
.addPathPatterns("/**")
.excludePathPatterns("/", "/error", "/login", "/index", "/captcha",
"/admin/login", "/admin/logout", "/api/*",
"/css/*", "/images/*", "/upload/*", "/js/*",
"/lib/*", "/page/*", "/unauth/*", "/shiro",
"/home/shiro/*"); // 排除路径需谨慎
}
}
风险点:
- 排除路径过多可能导致未授权访问
- 静态资源目录配置不当
2.2 登录验证问题
public String login(String username, String password, String captcha,
String path, HttpSession session, HttpServletRequest request,
Model model) {
if (request.getMethod().equals("GET")) return "login";
if (!CaptchaUtil.ver(captcha, request)) {
CaptchaUtil.clear(request);
model.addAttribute("msg", "验证码不正确");
return "login";
}
Admin admin = adminService.login(username, password); // 调用接口实现
if (admin != null) {
session.setAttribute("username", username);
if (path != null) {
return "redirect:" + path; // 重定向路径需校验
}
return "redirect:/home";
}
// ...
}
风险点:
- 重定向路径未校验可能导致开放重定向
- 密码传输和存储安全性
- 会话固定风险
3. 文件操作漏洞
3.1 文件上传漏洞
@RequestMapping("/upload")
public String upload(MultipartFile file, Model model) throws IOException {
String fileName = file.getOriginalFilename(); // 未校验文件名
FileOutputStream fos = new FileOutputStream(new File("/tmp/" + fileName));
file.transferTo(fos); // 直接保存文件
fos.close();
model.addAttribute("fileName", fileName);
return "upload";
}
3.1.1 安全防护措施
后缀白名单:
String Suffix = fileName.substring(fileName.lastIndexOf("."));
String[] SuffixSafe = {".jpg", ".png", ".jpeg", ".gif", ".bmp", ".ico"};
文件类型白名单:
private static final String[] ALLOWED_IMAGE_TYPES = {
"image/jpeg", "image/png", "image/gif", "image/bmp"
};
public static boolean isAllowed(File file) throws IOException {
String contentType = getContentType(file);
return Arrays.asList(ALLOWED_IMAGE_TYPES).contains(contentType);
}
3.2 文件下载漏洞
public static boolean checkTraversal(String content) {
return content.contains("..") || content.contains("/"); // 目录穿越检查
}
// 使用示例
Security.checkTraversal(filename);
风险点:
- 目录穿越攻击
- 文件类型检查不足
- 文件权限设置不当
4. 命令执行/代码执行漏洞
4.1 Runtime.exec命令执行
public String RuntimeExec(String cmd, Model model) {
StringBuilder sb = new StringBuilder();
String line;
try {
Process proc = Runtime.getRuntime().exec(cmd); // 直接执行用户输入
// ...结果处理...
} catch (IOException e) {
sb.append(e);
}
model.addAttribute("results", sb.toString());
return "basevul/rce/runtime";
}
防护措施:
- 避免直接执行用户输入
- 使用白名单限制可执行命令
- 对输入进行严格过滤
4.2 ScriptEngineManager代码执行
public String loadJsExec(String url, Model model) {
try {
ScriptEngine engine = new ScriptEngineManager().getEngineByExtension("js");
Bindings bindings = engine.getBindings(ScriptContext.ENGINE_SCOPE);
String payload = String.format("load('%s')", url); // 加载远程脚本
engine.eval(payload, bindings); // 执行脚本
// ...
}
}
POC:
var a = mainOutput();
function mainOutput() {
var x=java.lang.Runtime.getRuntime().exec("calc");
}
绕过技巧:
var x=java.lang./**/Runtime.getRuntime().exec("calc");
4.3 Groovy代码执行
public String groovyExec(String cmd, Model model) {
GroovyShell shell = new GroovyShell();
try {
shell.evaluate(cmd); // 直接执行Groovy代码
model.addAttribute("results", "执行成功!!!");
} catch (Exception e) {
model.addAttribute("results", e.toString());
}
return "basevul/rce/groovy";
}
POC:
- Windows:
"calc".execute() - macOS:
"open -a Calculator".execute()
4.4 ProcessBuilder命令执行
public String ProcessBuilderExec(String ip, String safe, Model model) {
if (safe != null) {
if (Security.checkOs(ip)) { // 命令过滤
model.addAttribute("results", "检测到非法命令注入");
return "basevul/rce/processbuilder";
}
}
String[] cmdList = {"cmd", "/c", "ping -n 1 " + ip}; // 命令拼接
// ...执行过程...
}
过滤方法:
public static boolean checkOs(String content) {
String black = "|&;$";
String[] black_list = black.split(",");
for (String s : black_list) {
if (content.contains(s)) {
return true;
}
}
return false;
}
4.5 ProcessImpl反射调用
public String processImplExec(String cmd, Model model) {
try {
Class<?> clazz = Class.forName("java.lang.ProcessImpl");
Method method = clazz.getDeclaredMethod("start", String[].class,
Map.class, String.class, ProcessBuilder.Redirect[].class, boolean.class);
method.setAccessible(true);
Process e = (Process) method.invoke(null, new String[]{cmd}, null,
null, null, false); // 反射调用ProcessImpl
// ...结果处理...
}
}
风险点:
- 通过反射绕过安全限制
- 可用于绕过某些防护措施
5. SSRF漏洞
5.1 URLConnection SSRF
public String urlConnection(@RequestParam String url, String isHttp, String isIntranet) {
if (url.equals("")) return "请输入url";
if (isHttp != null && isHttp.equals("true")) {
if (!Security.isHttp(url)) { // 协议限制
return "不允许非http/https协议!!!";
}
}
if (isIntranet != null && isIntranet.equals("true")) {
if (Security.isIntranet(url)) { // 内网限制
return "不允许访问内网!!!";
}
}
String results = HTTP.URLConnection(url); // 发起请求
return results;
}
防护方法:
public static boolean isHttp(String url) {
return url.startsWith("http://") || url.startsWith("https://");
}
public static boolean isIntranet(String url) {
Pattern reg = Pattern.compile("^(127\\.0\\.0\\.1)|(localhost)|(10\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3})|(172\\.((1[6-9])|(2\\d)|(3[01]))\\.\\d{1,3}\\.\\d{1,3})|(192\\.168\\.\\d{1,3}\\.\\d{1,3})$");
Matcher match = reg.matcher(url);
return match.find();
}
支持的协议:
- http、https、file、ftp、mailto、jar、netdoc
- gopher(JDK8以后被移除)
利用示例:
file:///C:\windows\win.inihttp://internal-server/secret
6. SpEL表达式注入
public String spelVul(String exp, Model model) {
try {
ExpressionParser parser = new SpelExpressionParser();
// StandardEvaluationContext权限过大
EvaluationContext evaluationContext = new StandardEvaluationContext();
String result = parser.parseExpression(exp).getValue(evaluationContext).toString();
model.addAttribute("results", result);
} catch (ParseException e) {
model.addAttribute("results", e.toString());
}
return "basevul/spel/spel";
}
POC: T(java.lang.Runtime).getRuntime().exec("calc.exe")
修复方案:
- 使用
SimpleEvaluationContext替代StandardEvaluationContext - 对输入表达式进行过滤
7. SSTI模板注入
7.1 Thymeleaf注入
漏洞代码:
@RequestMapping("/thymeleaf")
public String thymeleaf(String content) {
return "user/" + content + "/welcome"; // 模板路径被污染
}
POC:
GET /home/ssti/noreturn/__$%7BT(java.lang.Runtime).getRuntime().exec(%22calc%22)%7D__::.x HTTP/1.1
无返回值控制器:
@RequestMapping("/noreturn/{content}")
public void noReturn(String content) {
System.out.println("ok");
}
风险点:
- 当控制器无返回值时,Spring会使用请求URL作为视图名称
- 攻击者可以控制模板名称触发RCE
8. XXE漏洞
8.1 XMLReader
public String XMLReader(@RequestBody String content, String entity) {
try {
if (entity != null && entity.equals("true") && Security.checkXXE(content)) {
return "检测到XXE攻击";
}
XMLReader xmlReader = XMLReaderFactory.createXMLReader();
// 未禁用DOCTYPE
// xmlReader.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
xmlReader.parse(new InputSource(new StringReader(content)));
return "XMLReader 解析成功";
} catch (Exception e) {
return e.toString();
}
}
8.2 SAXReader
public String SAXReader(@RequestBody String content, String entity) {
try {
SAXReader sax = new SAXReader();
// 未禁用外部实体
// sax.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
sax.read(new InputSource(new StringReader(content)));
return "SAXReader 解析成功";
} catch (Exception e) {
return e.toString();
}
}
8.3 DocumentBuilder
public String DocumentBuilder(@RequestBody String content, String entity) {
try {
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
// 未设置安全属性
// dbf.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
DocumentBuilder builder = factory.newDocumentBuilder();
Document document = builder.parse(new InputSource(new StringReader(content)));
// ...
}
}
8.4 Unmarshaller
public String Unmarshaller(@RequestBody String content, String entity) {
try {
JAXBContext context = JAXBContext.newInstance(Student.class);
Unmarshaller unmarshaller = context.createUnmarshaller();
XMLInputFactory xif = XMLInputFactory.newFactory();
// 默认情况下在1.8版本上不能加载外部dtd文件
// xif.setProperty(XMLInputFactory.IS_SUPPORTING_EXTERNAL_ENTITIES, true);
XMLStreamReader xsr = xif.createXMLStreamReader(new StringReader(content));
Object o = unmarshaller.unmarshal(xsr);
return o.toString();
}
}
8.5 XXE防护方法
public static boolean checkXXE(String content) {
String black = "ENTITY";
return content.toUpperCase().contains(black);
}
XXE利用Payload:
<!-- Windows -->
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE test [
<!ENTITY xxe SYSTEM "file:///C:\windows\win.ini">
]>
<person><name>&xxe;</name></person>
<!-- Linux -->
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE test [
<!ENTITY xxe SYSTEM "file:///etc/passwd">
]>
<person><name>&xxe;</name></person>
修复方案:
- 禁用DTD:
setFeature("http://apache.org/xml/features/disallow-doctype-decl", true) - 禁用外部实体
- 使用安全的XML解析器配置