代码审计之不安全的Java代码

mt0u   ·   发表于 2023-09-13 09:40:21   ·   代码审计

不安全的Java代码

​ 开发工程师与渗透工程师之间是很微妙的关系,在打靶场的同时,需要想一下如果你是开发人员你会怎样去防御这种漏洞,而作为攻击方你又怎么去绕过开发人员的防御。

环境搭建

https://github.com/j3ers3/Hello-Java-Sec

SQL注入

​ SQLI(SQL Injection), SQL注入是因为程序未能正确对用户的输入进行检查,将用户的输入以拼接的方式带入SQL语句,导致了SQL注入的产生。攻击者可通过SQL注入直接获取数据库信息,造成信息泄漏。

SQL注入之JDBC注入

​ JDBC有两个方法执行SQL语句,分别是PrepareStatement和Statement。

漏洞代码:

  1. // 采用原始的Statement拼接语句,导致漏洞产生
  2. public String jdbcVul(String id) {
  3. StringBuilder result = new StringBuilder();
  4. try {
  5. Class.forName("com.mysql.cj.jdbc.Driver");
  6. Connection conn = DriverManager.getConnection(db_url, db_user, db_pass);
  7. Statement stmt = conn.createStatement();
  8. // 拼接语句产生SQL注入
  9. String sql = "select * from users where id = '" + id + "'";
  10. ResultSet rs = stmt.executeQuery(sql);
  11. while (rs.next()) {
  12. String res_name = rs.getString("user");
  13. String res_pass = rs.getString("pass");
  14. String info = String.format("查询结果 %s: %s", res_name, res_pass);
  15. result.append(info);
  16. }

漏洞代码二:

  1. // PrepareStatement会对SQL语句进行预编译,但有时开发者为了便利,直接采取拼接的方式构造SQL,此时进行预编译也无用。
  2. Connection conn = DriverManager.getConnection(db_url, db_user, db_pass);
  3. String sql = "select * from users where id = " + id;
  4. PreparedStatement st = conn.prepareStatement(sql);
  5. System.out.println("[*] 执行SQL语句:" + st);
  6. ResultSet rs = st.executeQuery();

SQL注入之MyBatis

​ MyBatis框架底层已经实现了对SQL注入的防御,但存在使用不当的情况下,仍然存在SQL注入的风险。

漏洞代码:

  1. // 此漏洞出现频率较高并且很严重!
  2. // 为何产生:由于使用 #{} 会将对象转成字符串,形成 order by "user" desc 造成错误,因此很多研发会采用${}来解决,从而造成SQL注入
  3. // 点击运行可通过报错语句获取数据库user
  4. @RequestMapping("/mybatis/vul/order")
  5. public List<User> orderBy(String field, String sort) {
  6. return userMapper.orderBy(field, sort);
  7. }
  8. // mapper.xml语句
  9. <select id="orderBy" resultType="com.best.hello.entity.User">
  10. select * from users order by ${field} ${sort}
  11. </select>

漏洞代码二:

  1. // 模糊搜索时,直接使用'%#{q}%' 会报错,部分研发图方便直接改成'%${q}%'从而造成注入
  2. @Select("select * from users where user like '%${q}%'")
  3. List<User> search(String q);
  4. // 安全代码,采用concat
  5. @Select("select * from users where user like concat('%',#{q},'%')")
  6. List<User> search(String q);

文件上传

​ 文件上传漏洞,是指用户上传了一个可执行的脚本文件(如jsp\php\asp),并通过此脚本文件获得了执行服务器端命令的能力。常见场景是web服务器允许用户上传图片或者普通文本文件保存,这种漏洞属于低成本高杀伤力

漏洞代码:

  1. // 允许上传任意文件导致的安全风险
  2. public String singleFileUpload(@RequestParam("file") MultipartFile file, RedirectAttributes redirectAttributes) {
  3. try {
  4. byte[] bytes = file.getBytes();
  5. Path dir = Paths.get(UPLOADED_FOLDER);
  6. Path path = Paths.get(UPLOADED_FOLDER + file.getOriginalFilename());
  7. Files.write(path, bytes);
  8. redirectAttributes.addFlashAttribute("message","上传文件成功:" + path + "");
  9. } catch (Exception e) {
  10. return e.toString();
  11. }
  12. return "redirect:upload_status";
  13. }

目录遍历

​ 目录遍历, 应用系统在处理下载文件时未对文件进行过滤,系统后台程序程序中如果不能正确地过滤客户端提交的../和./之类的目录跳转符,攻击者可以通过输入../进行目录跳转,从而下载、删除任意文件。

  1. // 文件路径没做限制,通过../递归下载任意文件
  2. // PoC:/Traversal/download?filename=../../../../../../../etc/passwd
  3. @GetMapping("/download")
  4. public String download(String filename, HttpServletRequest request, HttpServletResponse response) {
  5. String filePath = System.getProperty("user.dir") + "/logs/" + filename;
  6. try {
  7. File file = new File(filePath);
  8. InputStream is = new BufferedInputStream(new FileInputStream(file));
  9. byte[] buffer = new byte[is.available()];
  10. fis.read(buffer);
  11. fis.close();
  12. response.reset();
  13. response.addHeader("Content-Disposition", "attachment;filename=" + filename);
  14. response.addHeader("Content-Length", "" + file.length());
  15. OutputStream toClient = new BufferedOutputStream(response.getOutputStream());
  16. response.setContentType("application/octet-stream");
  17. toClient.write(buffer);
  18. toClient.flush();
  19. toClient.close();
  20. return "下载文件成功:" + filePath;

XSS

​ XSS(Cross Site Scripting) 跨站脚本攻击,攻击者插入恶意Script代码,当用户浏览该页之时,嵌入其中Web里面的Script代码会被执行,从而达到恶意攻击用户的目的。

漏洞代码:

  1. // 简单的反射型XSS,没对输出做处理。当攻击者输入恶意js语句时可触发
  2. @GetMapping("/reflect")
  3. public static String input(String content) {
  4. return content;
  5. }

SSRF

​ SSRF(Server-Side Request Forgery) 服务器端请求伪造,是一种由攻击者构造形成由服务端发起请求的一个安全漏洞。

漏洞代码:

  1. // url参数没做限制,可调用URLConnection发起任意请求,比如请求内网,或使用file等协议读取文件
  2. public static String URLConnection(String url) {
  3. try {
  4. URL u = new URL(url);
  5. URLConnection conn = u.openConnection();
  6. BufferedReader reader = new BufferedReader(new InputStreamReader(conn.getInputStream()));
  7. String content;
  8. StringBuffer html = new StringBuffer();
  9. while ((content = reader.readLine()) != null) {
  10. html.append(content);
  11. }
  12. reader.close();
  13. return html.toString();
  14. } catch (Exception e) {
  15. return e.getMessage();
  16. }
  17. }

漏洞代码二:

  1. // SSRF修复经常碰到的问题,虽然过滤了内网地址,但通过短链接跳转的方式可以绕过
  2. public String URLConnectionSafe(String url) {
  3. if (!Security.is_http(url)){
  4. return "不允许非http/https协议!!!";
  5. }else if (Security.isIntranet(url)) {
  6. return "不允许访问内网!!!";
  7. }else{
  8. return Http.URLConnection(url);
  9. }
  10. }

远程代码执行

​ RCE (Remote Code Execution), 远程代码执行漏洞,这里包含两种类型漏洞:

  • 命令注入(Command Injection),在某种开发需求中,需要引入对系统本地命令的支持来完成特定功能,当未对输入做过滤时,则会产生命令注入
  • 代码注入(Code Injection),在正常的java程序中注入一段java代码并执行,即用户输入的数据当作java代码进行执行。

漏洞代码:

  1. // 功能是利用ProcessBuilder执行ls命令查看文件,但攻击者通过拼接; & |等连接符来执行自己的命令。
  2. @RequestMapping("/ProcessBuilder")
  3. public static String cmd(String filepath) {
  4. String[] cmdList = {"sh", "-c", "ls -l " + filepath};
  5. StringBuilder sb = new StringBuilder();
  6. ProcessBuilder pb = new ProcessBuilder(cmdList);
  7. pb.redirectErrorStream(true);
  8. ...

漏洞代码二:

  1. // getRuntime()常用于执行本地命令,使用频率较高。
  2. @RequestMapping("/runtime")
  3. public static String cmd2(String cmd) {
  4. StringBuilder sb = new StringBuilder();
  5. try {
  6. Process proc = Runtime.getRuntime().exec(cmd);
  7. InputStream fis = proc.getInputStream();
  8. InputStreamReader isr = new InputStreamReader(fis);
  9. BufferedReader br = new BufferedReader(isr);
  10. ...

漏洞代码三:

  1. // 通过加载远程js文件来执行代码,如果加载了恶意js则会造成任意命令执行
  2. // 远程恶意js: var a = mainOutput(); function mainOutput() { var x=java.lang.Runtime.getRuntime().exec("open -a Calculator");}
  3. // ⚠️ 在Java 8之后移除了ScriptEngineManager的eval
  4. public void jsEngine(String url) throws Exception {
  5. ScriptEngine engine = new ScriptEngineManager().getEngineByName("JavaScript");
  6. Bindings bindings = engine.getBindings(ScriptContext.ENGINE_SCOPE);
  7. String payload = String.format("load('%s')", url);
  8. engine.eval(payload, bindings);
  9. }

漏洞代码四:

  1. // 不安全的使用Groovy调用命令
  2. import groovy.lang.GroovyShell;
  3. @GetMapping("/groovy")
  4. public void groovy(String cmd) {
  5. GroovyShell shell = new GroovyShell();
  6. shell.evaluate(cmd);
  7. }

漏洞代码五:

  1. // ProcessImpl是更为底层的实现,Runtime和ProcessBuilder执行命令实际上也是调用了ProcessImpl这个类
  2. Class clazz = Class.forName("java.lang.ProcessImpl");
  3. Method method = clazz.getDeclaredMethod("start", String[].class, Map.class, String.class, ProcessBuilder.Redirect[].class, boolean.class);
  4. method.setAccessible(true);
  5. method.invoke(null, new String[]{cmd}, null, null, null, false);

不安全的反序列化

​ 反序列化漏洞,当输入的反序列化的数据可被用户控制,那么攻击者即可通过构造恶意输入,让反序列化产生非预期的对象,在此过程中执行构造的任意代码(多见于第三方组件产生的漏洞)

漏洞代码:

  1. // readObject,读取输入流,并转换对象。ObjectInputStream.readObject() 方法的作用正是从一个源输入流中读取字节序列,再把它们反序列化为一个对象。
  2. // payload:java -jar ysoserial-0.0.6-SNAPSHOT-BETA-all.jar CommonsCollections5 "open -a Calculator" | base64
  3. public String cc(String base64) {
  4. try {
  5. BASE64Decoder decoder = new BASE64Decoder();
  6. base64 = base64.replace(" ", "+");
  7. byte[] bytes = decoder.decodeBuffer(base64);
  8. ByteArrayInputStream stream = new ByteArrayInputStream(bytes);
  9. // 反序列化流,将序列化的原始数据恢复为对象
  10. ObjectInputStream in = new ObjectInputStream(stream);
  11. in.readObject();
  12. in.close();
  13. return "反序列化漏洞";
  14. } catch (Exception e) {
  15. return e.toString();
  16. }
  17. }

漏洞代码二:

  1. // 远程服务器支持用户可以输入yaml格式的内容并且进行数据解析,没有做沙箱,黑名单之类的防控
  2. public void yaml(String content) {
  3. Yaml y = new Yaml();
  4. y.load(content);
  5. }

漏洞代码三:

  1. // Java RMI Registry 反序列化漏洞,受jdk版本影响,< = jdk8u111
  2. public String rmi() {
  3. try {
  4. Registry registry = LocateRegistry.createRegistry(9999);
  5. } catch (Exception e) {
  6. e.printStackTrace();
  7. }
  8. return "开启RMI监听,端口:9999";
  9. }

失效的身份认证

​ 失效的身份认证,通过错误使用应用程序的身份认证和会话管理功能,攻击者能够破译密码、密钥或会话令牌,或者利用其它开发缺陷来暂时性或永久性冒充其他用户的身份。

表达式注入

​ SpEL(Spring Expression Language)表达式注入, 是一种功能强大的表达式语言、用于在运行时查询和操作对象图,由于未对参数做过滤可造成任意命令执行。

漏洞代码:

  1. // PoC: T(java.lang.Runtime).getRuntime().exec(%22open%20-a%20Calculator%22)
  2. @GetMapping("/vul")
  3. public String spelVul(String ex) {
  4. ExpressionParser parser = new SpelExpressionParser();
  5. String result = parser.parseExpression(ex).getValue().toString();
  6. System.out.println(result);
  7. return result;
  8. }

XML外部实体注

​ XXE (XML External Entity Injection), XML外部实体注入,当开发人员配置其XML解析功能允许外部实体引用时,攻击者可利用这一可引发安全问题的配置方式,实施任意文件读取、内网端口探测、命令执行、拒绝服务等攻击。

漏洞代码:

  1. @RequestMapping(value = "/XMLReader")
  2. public String XMLReader(@RequestBody String content) {
  3. try {
  4. XMLReader xmlReader = XMLReaderFactory.createXMLReader();
  5. // 修复:禁用外部实体
  6. // xmlReader.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
  7. xmlReader.parse(new InputSource(new StringReader(content)));
  8. return "XMLReader XXE";
  9. } catch (Exception e) {
  10. return e.toString();
  11. }
  12. }

漏洞代码二:

  1. // SAXReader
  2. SAXReader sax = new SAXReader();
  3. // 修复:禁用外部实体
  4. // sax.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
  5. sax.read(new InputSource(new StringReader(content)));

漏洞代码三:

  1. // SAXBuilder
  2. @RequestMapping(value = "/SAXBuilder")
  3. public String SAXBuilder(@RequestBody String content) {
  4. try {
  5. SAXBuilder saxbuilder = new SAXBuilder();
  6. // 修复: saxbuilder.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
  7. saxbuilder.build(new InputSource(new StringReader(content)));
  8. return "SAXBuilder XXE";
  9. } catch (Exception e) {
  10. return e.toString();
  11. }
  12. }

越权访问

​ 失效的访问控制(Broken Access Control),应用在检查授权时存在纰漏,使得攻击者在获得低权限用户账户后,利用一些方式绕过权限检查,访问或者操作其他用户或者更高权限。越权漏洞的成因主要是因为开发人员在对数据进行增、删、改、查询时对客户端请求的数据过分相信而遗漏了权限的判定,一旦权限验证不充分,就易致越权漏洞。

漏洞代码:

  1. // 未做权限控制,通过遍历name参数可查询任意用户信息
  2. @GetMapping("/vul/info")
  3. public List<User> vul(String name) {
  4. return userMapper.queryByUser(name);
  5. }

接口未授权访问

​ 接口未授权访问(Unauthorized Access),在不进行请求授权的情况下,能够直接对相应的业务逻辑功能进行访问、操作等。通常是由于认证页面存在缺陷或者无认证、安全配置不当等导致的。

漏洞代码:

  1. // 对部分接口未做鉴权拦截,导致可未授权访问
  2. @Override
  3. public void addInterceptors(InterceptorRegistry registry) {
  4. registry.addInterceptor(new LoginHandlerInterceptor())
  5. .addPathPatterns("/**")
  6. .excludePathPatterns("/Unauth/**", "/css/**", "/js/**", "/img/**");
  7. }

SSTI模板注入

​ SSTI(Server Side Template Injection) 服务器模板注入, 服务端接收了用户的输入,将其作为 Web 应用模板内容的一部分,在进行目标编译渲染的过程中,执行了用户插入的恶意内容。

漏洞代码:

  1. /**
  2. * 将请求的url作为视图名称,调用模板引擎去解析
  3. * 在这种情况下,我们只要可以控制请求的controller的参数,一样可以造成RCE漏洞
  4. * payload: __${T(java.lang.Runtime).getRuntime().exec("open -a Calculator")}__::.x
  5. */
  6. @GetMapping("/doc/{document}")
  7. public void getDocument(@PathVariable String document) {
  8. System.out.println(document);
  9. }

漏洞组件

组件中存在的漏洞

XStream反序列化

​ XStream是一个简单的基于Java库,Java对象序列化到XML,历史上存在多个反序列化漏洞。

漏洞代码:

  1. public String vul(@RequestBody String content) {
  2. XStream xs = new XStream();
  3. xs.fromXML(content);
  4. return "XStream Vul";
  5. }

Fastjson反序列化

​ fastjson是阿里巴巴的开源JSON解析库,它可以解析JSON格式的字符串,支持将Java Bean序列化为JSON字符串,也可以从JSON字符串反序列化到JavaBean,历史上存在多个反序列化漏洞。

  1. // 使用了低版本,存在漏洞
  2. // poc: {"@type":"java.net.Inet4Address","val":"8d5tv8.dnslog.cn"}
  3. <dependency>
  4. <groupId>com.alibaba</groupId>
  5. <artifactId>fastjson</artifactId>
  6. <version>1.2.24</version>
  7. </dependency>

Jackson反序列化

​ Jackson是一套开源的java序列化与反序列化工具框架,可将java对象序列化为xml和json格式的字符串并提供对应的反序列化过程。由于其解析效率较高,Jackson目前是Spring MVC中内置使用的解析方式。

漏洞代码:

  1. public void vul() {
  2. try {
  3. String payload = "[\"com.nqadmin.rowset.JdbcRowSetImpl\",{\"dataSourceName\":\"ldap://127.0.0.1:1389/Exploit\",\"autoCommit\":\"true\"}]";
  4. ObjectMapper mapper = new ObjectMapper();
  5. mapper.enableDefaultTyping();
  6. Object o = mapper.readValue(payload, Object.class);
  7. mapper.writeValueAsString(o);
  8. } catch (Exception e) {
  9. e.printStackTrace();
  10. }
  11. }

Log4j2反序列化

​ Apache Log4j2是一款优秀的Java日志框架。此次漏洞是由 Log4j2 提供的lookup功能造成的,该功能允许开发者通过一些协议去读取相应环境中的配置。但在处理数据时,并未对输入(如${jndi)进行严格的判断,从而造成JNDI注入

漏洞代码:

  1. // log4j-core < 2.15.0-rc1
  2. public String vul(String content) {
  3. logger.error(content);
  4. return "Log4j2 RCE";
  5. }

shiro反序列化

其他漏洞

其他的一些漏洞

开放重定向

​ 开放重定向漏洞,是指后台服务器在告知浏览器跳转时,未对客户端传入的重定向地址进行合法性校验,导致用户浏览器跳转到钓鱼页面的一种漏洞
出现场景:用户登录、统一身份认证等需要跳转的地方

漏洞代码:

  1. // 满足参数url可控,且未做限制
  2. public String vul(String url) {
  3. return "redirect:" + url;
  4. }

Actuator未授权访问

​ Actuator, 是Spring Boot提供的服务监控和管理中间件,默认配置会出现接口未授权访问,部分接口会泄露网站流量信息和内存信息等,使用Jolokia库特性甚至可以远程执行任意代码,获取服务器权限。

漏洞代码:

  1. # 不安全的配置:Actuator设置全部暴露
  2. management.endpoints.web.exposure.include=*

IP地址伪造

​ X-Forwarded-For地址伪造,很多Web应用需要获取用户的IP,通过IP伪造可以绕过一些安全限制。

漏洞代码:

  1. public static String vul(HttpServletRequest request) {
  2. String ip2 = request.getHeader("X-Forwarded-For");
  3. if(!Objects.equals(ip2, "127.0.0.1")) {
  4. return "禁止访问,只允许本地IP!";
  5. } else {
  6. return "success,你的IP:" + ip2;
  7. }
  8. }

Swagger未授权访问

​ Swagger未开启页面访问限制,Swagger未开启严格的Authorize认证。

CORS

​ CORS(Cross-origin resource sharing),即跨域资源共享,用于绕过SOP(同源策略)来实现跨域资源访问的一种技术。 CORS漏洞则是利用CORS技术窃取用户敏感数据,CORS漏洞的成因是服务端配置的规则不当所导致的,服务器端没有配置Access-Control-Allow-Origin等字段

漏洞代码:

  1. public String corsVul(HttpServletRequest request, HttpServletResponse response) {
  2. String origin = request.getHeader("origin");
  3. response.setHeader("Access-Control-Allow-Origin", origin);
  4. response.setHeader("Access-Control-Allow-Credentials", "true");
  5. response.setHeader("Access-Control-Allow-Methods", "GET,POST,PUT,DELETE,OPTIONS");
  6. return "cors vul";
  7. }

JNDI注入

​ Java命名和目录接口(JNDI)是一种Java API,类似于一个索引中心,它允许客户端通过name发现和查找数据和对象。JNDI注入就是当上文代码中jndiName这个变量可控时,引发的漏洞,它将导致远程class文件加载,从而导致远程代码执行。

漏洞代码:

  1. // lookup是通过名字检索执行的对象,当lookup()方法的参数可控时,攻击者便能提供一个恶意的url地址来加载恶意类。
  2. Context ctx = new InitialContext();
  3. ctx.lookup(url);

DoS漏洞

​ DoS是Denial of Service的简称,即拒绝服务,造成DoS的攻击行为被称为DoS攻击,其目的是使计算机或网络无法提供正常的服务。

漏洞代码:

  1. // Pattern.matches造成的ReDoS
  2. // PoC: vul?contnet=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaab
  3. public String vul(String content) {
  4. boolean match = Pattern.matches("(a|aa)+", content);
  5. return String.format("正则匹配:%s,正则表达式拒绝服务攻击", match);
  6. }

验证码复用

​ 验证码反复利用,可以直接进行暴力破解。(这是一类常见的安全问题)
一般来说,验证码是与Session绑定的,Session生成时,也伴随着验证码的生成和绑定,在访问页面时,接口的请求和验证码的生成通常是异步进行的,这使得两个功能变得相对独立。也就意味着我们如果仅请求接口,而不触发验证码的生成,那么验证码就不会变化。 并且在考虑安全时,开发人员的关注点往往在 验证码校验 是否通过,通过则进入业务流程,不通过则重新填写,而忽视了这个用户是否按照既定的业务流程在走(接口访问与验证码生成是否同时进行),验证码是否被多次使用了。

漏洞代码:

  1. // 未清除session中的验证码,导致可复用
  2. if (!CaptchaUtil.ver(captcha, request)) {
  3. model.addAttribute("msg", "验证码不正确");
  4. return "login";
  5. }

打赏我,让我更有动力~

1 条回复   |  直到 2023-9-13 | 684 次浏览

Track-魔方
发表于 2023-9-13

文章非原创首发,无金币奖励

评论列表

  • 加载数据中...

编写评论内容
登录后才可发表内容
返回顶部 投诉反馈

© 2016 - 2024 掌控者 All Rights Reserved.