JavaWeb代码审计

JavaWeb代码审计小抄,持续更新ing...

一、常见漏洞类型

1. SQL注入

📌漏洞成因

  • 直接使用SQL语句拼接,将用户传入的参数通过字符串拼接的方式传入查询语句。
    示例:String sql = "select * from test where id = " + id;
  • 预编译使用有误,没有调用 set 方法将变量与占位符进行对应。
    示例:
String sql = "select * from test where id = ?";
PreparedStatement pstt = conn.prepareStatement(sql);
// pstt.setObject(1,id);
  • 对 % 和 _ 没有进行显示处理, 导致用户可以自行拼接进行模糊查询。
  • 不能参数化的位置,还是有可能存在拼接的情况,如order by后面。
    示例:String sql = "select * from test where id = ? order by '" + time + "' asc";
  • Mybatis中,#{}是预编译处理,${}是字符串替换,使用${}就可能导致SQL注入。
    示例:Select * from news where title like '%#{title}%'

🔍审计策略

  • 重点关注创建查询的函数如 createQuery()createSQLQuery()createNativeQuery()
  • 定位SQL语句上下文,查看是否有参数直接拼接,是否有对模糊查询关键字的过滤。
  • 是否使用预编译技术,预编译是否完整,关键函数定位setObject()setInt()setString()setSQLXML()关联上下文搜索set*开头的函数。
  • Mybatis中搜索${},因为对于like模糊查询、order by排序、范围查询in、动态表名/列名,没法使用预编译,只能拼接,所以还是需要手工防注入,此时可查看相关逻辑是否正确。
  • JPA搜索JpaSort.unsafe(),查看是否用实体之外的字段对查询结果排序,进行了SQL的拼接。以及查看EntityManager的使用,也可能存在拼接SQL的情况。

📝如何修复

正确使用预编译;无论是 SQL/HQL/JPQL,都不进行SQL语句字符串拼接;正确理解占位符、预编译、替换、参数注入等形式的使用。

2. XSS/链接注入/框架注入

📌漏洞成因

对于用户传递参数,没有进行过滤,导致恶意攻击者可以插入一些恶意的js语句、标签、frame等来获取应用或用户的敏感信息。

🔍审计策略

审计过程要点还是定位用户的输入输出,也就是梳理数据交互以及前端展示的过程。找到一条完整的利用链之后,就是结合现有的安全措施(输出编码、过滤器等)进行判断,例如是否存在绕过的可能,或者是没有任何安全防护可直接造成攻击。

下面是一些可以快速定位的关键字:

<%=
${
<c:out
<c:if
<c:forEach
ModelAndView
ModelMap
Model
request.getParameter
request.setAttribute

一般会使用全局的Filter进行xss的过滤,但通常可能存在漏网之鱼,所以也需要审计全局过滤规则是否完善。可以通过关键字 XssFilter 进行搜索。

📝如何修复

自定义全局过滤器,对用户提交输入进行检查和过滤;对用户输入并用来展示的数据进行HTML转义,可用的工具类包括org.springframework.web.util.HtmlUtilsorg.apache.commons.lang3.StringEscapeUtilsESAPI.encoder().encodeForHTML 等。

3. XXE

📌漏洞成因

  • XML文档结构包括XML声明、DTD文档类型定义(可选)、文档元素。文档类型定义(DTD)的作用是定义 XML 文档的合法构建模块。DTD 可以在 XML 文档内声明,也可以外部引用。当允许引用外部实体时,恶意攻击者即可构造恶意内容访问服务器资源。
    示例:
<?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE root [
        <!ENTITY xxe SYSTEM "file:///tmp/aaa">
       ]>
    <root>&xxe;</root>
  • 使用不可信数据来构造XML会导致XML注入漏洞,如果用户被允许输入结构化的XML片段,则他可以在XML的数据域中注入XML标签来改写目标XML文档的结构与
    内容。

Java中的XXE支持以下协议:http,https,file,ftp,mailto,jar,netdoc,可以利用file协议读取文件,利用http协议探测内网,没有回显时可组合利用file协议和ftp协议来读取文件。如果存在报错的情况下还可以尝试使用报错XXE进行敏感信息的获取。甚至是尝试递归调用造成拒绝服务攻击。

🔍审计策略

XML解析涉及的业务功能点: WebServices接口、RESTful接口、Excel文件解析、Soap协议等。

漏洞触发点就在XML解析时,因此重点审计XML解析器是否设置了相关的安全属性,禁用DTDs或者禁止使用外部实体。还有是否使用了不安全的漏洞组件。部分解析器如下:

javax.xml.parsers.DocumentBuilder
javax.xml.parsers.DocumentBuilderFactory
javax.xml.stream.XMLStreamReader
javax.xml.stream.XMLInputFactory
org.jdom.input.SAXBuilder
org.jdom2.input.SAXBuilder
org.jdom.output.XMLOutputter
oracle.xml.parser.v2.XMLParser
javax.xml.parsers.SAXParser
org.dom4j.io.SAXReader 
org.dom4j.DocumentHelper
org.xml.sax.XMLReader
javax.xml.transform.sax.SAXSource 
javax.xml.transform.TransformerFactory 
javax.xml.transform.sax.SAXTransformerFactory 
javax.xml.validation.SchemaFactory
javax.xml.validation.Validator
javax.xml.bind.Unmarshaller
javax.xml.xpath.XPathExpression
java.beans.XMLDecoder

除此之外,关注StreamSourceXMLConstantsStringReader等方法的调用,在项目中搜索. xsd文件。

📝如何修复

使用XML解析器时禁止使用外部实体;使用白名单的方式对用户输入进行处理,避免用户输入改变XML结构或内容。

参考链接:OWASP Cheat Sheet

各种Features:

是否允许使用DTDS解析
    http://apache.org/xml/features/disallow-doctype-decl
是否允许使用通用实体
    http://xml.org/sax/features/external-general-entities
是否允许使用参数实体
    http://xml.org/sax/features/external-parameter-entities
是否允许加载外部DTD实体
    http://apache.org/xml/features/nonvalidating/load-external-dtd
是否启用安全性处理
    http://javax.xml.XMLConstants/feature/secure-processing
是否允许使用外部DTD实体
    http://javax.xml.XMLConstants/property/accessExternalDTD
是否允许使用外部Schema
    http://javax.xml.XMLConstants/property/accessExternalSchema
是否允许使用外部Stylesheet
    http://javax.xml.XMLConstants/property/accessExternalStylesheet

4. 反序列化

📌漏洞成因

  • Java程序使用ObjectInputStream对象的readObject()方法将反序列化数据转换为java对象。如果被反序列化的对象重写了readObject()方法,则会执行该对象的此方法。因此,当输入的反序列化的数据可被用户控制,那么攻击者即可通过构造恶意输入,让反序列化产生非预期的对象,在此过程中执行构造的任意代码。
  • 反序列化一个类时,通常会伴随调用 get/set 方法注入参数,并为这个类创建一个新的实例化对象,因此会执行构造方法及静态代码块,在这种情况下,攻击者也可以挖掘gadget来执行危险的动作。
  • 根据权限最小化原则,一般情况下反序列化的类中的readObject()writeObject()readResolve()writeReplace() 方法必须被声明为 private void。否则如果 Serializable 的类开放 writeObject 函数为 public 的话,给非受信调用者过高权限,潜在有风险。
  • 在 Java 环境中,允许处于不同受信域的组件进行数据通信,从而出现跨受信边界的数据传输。如果反序列化类中存在未加密的敏感数据,将面临泄露或被篡改的风险。
  • 对非静态内部类的序列化依赖编译器,且随着平台的不同而不同,容易产生错误。对内部类的序列化会导致外部类的实例也被序列化。这样有可能泄露敏感数据。

🔍审计策略

反序列化操作的功能位置:导入模版文件、网络通信、数据传输、日志格式化存储、对象数据落磁盘或DB存储等业务场景。

可以通过对网络抓包寻找序列化数据:java序列化的数据一般会以标记(ac ed 00 05)开头,base64编码后的特征为rO0AB。

一些服务的传输可能存在反序列化:多平台HTTP通信、RMI、JMX。

一些反序列化触发点:

ObjectInputStream.readObject
ObjectInputStream.readUnshared
XMLDecoder.readObject
Yaml.load
XStream.fromXML
ObjectMapper.readValue
JSON.parseObject

主要查看这些触发点的参数是否由用户可控。

全局搜索是否具有public权限的一些方法:public * writeObject/readObject/readResolve/writeReplace

查看反序列化类中是否包含敏感数据。

全局查找implements Serializable 的所有内部类。

📝如何修复

  • 对要反序列化的对象设置黑白名单,像 fastJSON、Jackson这种官方会维护一个黑名单,持续更新,但还是建议使用白名单,可通过Hook函数resolveClass()来校验反序列化的类从而实现白名单校验;也可以使用Apache Commons IO Serialization包中的ValidatingObjectInputStream类的accept()方法来实现反序列化类白/黑名单控制。
  • 对于一些敏感的属性,将其声明为 transient,或进行加密处理。
  • 避免内部类的序列化,或把内部类声明为静态,但还是要注意敏感信息的问题。

5. 命令执行

📌漏洞成因

由于业务需求,应用程序可能含有执行系统命令的功能,如果执行的命令任意用户可控,则将产生极大的危害。

🔍审计策略

重点关注能执行命令的一些功能及函数:

Runtime.getRuntime().exec()
Process
UNIXProcess
ProcessImpl
ProcessBuilder.start()
GroovyShell.evaluate()

📝如何修复

避免使用这样的功能,如必须,待执行命令尽量不由用户传入。

6. SSRF

📌漏洞成因

SSRF形成的原因大都是由于代码中提供了从其他服务器应用获取数据的功能但没有对目标地址做过滤与限制。比如从指定URL链接获取图片、下载等。

🔍审计策略

出现SSRF漏洞的主要业务有:

  • 通过URL地址分享网页内容
  • 在线服务
  • 通过URL地址加载或下载图片
  • 加载远端配置

重点关注一些HTTP请求操作函数:

om.alibaba.druid.util.HttpClientUtils
sun.net.www.http.HttpClient
javax.net.ssl.HttpsURLConnection
sun.net.www.protocol.http.HttpURLConnection
java.net.HttpURLConnection
javax.servlet.http.HttpServletRequest
java.net.URI
java.net.URL
java.net.URLConnection
com.bea.uddiexplorer.Search
com.squareup.okhttp.Request
com.squareup.okhttp3.Request
org.apache.commons.httpclient.HttpMethodBase
org.apache.http.client.methods.HttpRequestBase

除了建立HTTP协议连接,还可能直接通过 Socket建立连接,因此应该同样关注Socket相关类:

AsynchronousServerSocketChannel.accept/bind
AsynchronousSocketChannel.write/read/bind/connect
ServerSocketChannel.bind
ServerSocket.accept/bind
Socket.bind/connect
Socket.getInputStream().read
Socket.getOutputStream().write
SocketChannel.bind/read/write/connect

📝如何修复

  • 使用白名单校验HTTP请求url地址,例如通过InetAddress对象的isSiteLocalAddress()方法进行判断,禁止内网地址的网络请求。
  • 避免将请求响应及错误信息返回给用户
  • 禁用不需要的协议及限制请求端口

7. 文件上传

📌漏洞成因

文件上传时,由于校验不全、限制不当,可能导致被上传webshell、拒绝服务、任意文件写入等安全问题。

🔍审计策略

  • 首先关注文件后缀验证,使用白名单或黑名单,建议使用白名单。使用lastIndexOf()方法获取文件后缀,使用IndexOf()可能被绕过。如果是白名单验证时,使用toLowerCase()处理再进行对比,或使用equalsIgnoreCase(),避免被大小写绕过。
  • 是否校验了文件的大小。
  • 是否校验了文件类型getContentType(),这种方式虽然能够被绕过,但还是会增加攻击成本。
  • 对于使用Hutool的FileTypeUtil的getType()ImageIO.read()通过读取文件流中前N个byte值来判断文件类型的,也可以使用类似图片马的方式进行绕过。
  • "%00"截断能否绕过。
  • QP编码特性能否绕过。javax.mail.internet.MimeUtility.encodeWord()方法。
  • 有一些安全校验的顺序有问题,先将文件保存,再进行安全检测,如果不通过检测则进行删除,此时可以在文件保存后触发报错终止流程,导致不删除文件。

重点是文件上传相关类或函数:

FileUpload
FileUploadBase
FileItemIteratorImpl
FileItemStreamImpl
FileUtils
UploadHandleServlet
FileLoadServlet
FileOutputStream
DiskFileItemFactory
MultipartRequestEntity
MultipartFile
com.oreilly.servlet.MultipartRequest

📝如何修复

对上传文件后缀进行白名单验证,验证文件大小,强制重命名后缀,上传文件单独服务器保存。

8. 任意文件读/写/删除/复制/移动/遍历

📌漏洞成因

应用程序由于业务需求,提供了文件操作的一系列功能,但文件名、文件路径等由用户控制,在校验不当的情况下,用户可以绕过限制对服务器上任意文件进行操作。

🔍审计策略

首先关注包含这些功能的类和函数:

sun.nio.ch.FileChannelImpl
java.io.File.list/listFiles
java.io.FileInputStream
java.io.FileOutputStream
java.io.FileSystem/Win32FileSystem/WinNTFileSystem/UnixFileSystem
sun.nio.fs.UnixFileSystemProvider/WindowsFileSystemProvider
java.io.RandomAccessFile
sun.nio.fs.CopyFile
sun.nio.fs.UnixChannelFactory
sun.nio.fs.WindowsChannelFactory
java.nio.channels.AsynchronousFileChannel
FileUtil/IOUtil

在使用这些函数时,对于用户传递来的文件对象/文件名/文件路径,是否进行了正确的处理:

  • 是否限制了可操作文件的路径、文件类型、文件所有者。
  • 是否将敏感文件进行排除。
  • 查找getPath(), getAbsolutePath(),查看是否有错误的路径判断。

再排查程序的安全策略配置文件,全局搜索permissionJava.io.FilePermissiongrant字样。查看是否只为程序的某部分路径赋予读写权限。

📝如何修复

  • 对要操作的文件名进行黑白名单限制。
  • 对用户输入的数据进行过滤,过滤掉"./"、"…/"、"%"、"/"
  • 针对不同的功能,将可操作文件路径限制在某个目录内,禁止攻击者通过 '../'的方式穿越路径,建议使用getCanonicalPath()标准化路径,之后再进行限制和判断。
  • 使用 FilePermission 限制权限。

9. URL重定向

📌漏洞成因

由于Web站点有时需要根据不同的逻辑将用户引向到不同的页面,如典型的登录接口就经常需要在认证成功之后将用户引导到登录之前的页面,整个过程中如果实现不好就可能导致URL重定向问题,攻击者构造恶意跳转的链接,可以向用户发起钓鱼攻击。

🔍审计策略

出现URL重定向主要的功能有:

  • 用户登录、统一身份认证处,认证完了会通过url=的形式跳转到类似操作的页面。
  • 用户分享、收藏内容后跳转。
  • 跨域认证授权后进行跳转。

一些相关函数及关键字:sendRedirectsetHeaderforwardredirect:<c:redirectself.location.hreflocation.hrefwindows.location.href等。
一些常见的参数名称:redirectredirect_doredirect_urlurljumpjump_totargettolinkdomain等。

审计的思路为定位可能存在redirect业务的代码段,审计跳转的URL是否来自于前端参数,是否具有校验和限制。

📝如何修复

对传入的URL做认证处理,保证该URL来自于信任域。例如如下方式:

  • 通过限制Referer保证将要跳转URL的有效性,避免攻击者生成自己的恶意跳转链接。
  • 加入有效性验证Token,保证所有生成的链接都来自于可信域,通过在生成的链接里加入用户不可控的Token对生成的链接进行校验。

关键参数前端不可控。
对跳转的URL进行白名单检查

10. 敏感信息泄露

📌漏洞成因

在开发中,可能使用各种各样的框架、组件,由于配置不当或使用有误,将可能导致泄露服务器的敏感信息。
例如:swagger 接口文档、Hystrix 监控面板、DWR 框架、druid监控平台等等。

🔍审计策略

重点审计系统使用的框架、组件,根据经验查看配置,配置是否有误、是否将调试功能正式上线到生产环境中等。

📝如何修复

在使用敏感功能,或框架、组件提供了管理页面、调试接口时,为此类功能设置访问权限校验。

11. 代码执行

📌漏洞成因

在Java中的代码执行漏洞通常不常见,因为在jdk9之前的Java并不提供eval功能,想要执行任意代码,就要动态编译,或使用反序列化、自定义ClassLoader、结合JNDI服务、SPI机制等功能完成的任意代码执行能力。这种方式一般是安全研究员研究用于区别于传统的命令执行webshell用,平常在开发时很少会遇见此类功能,可参考这里

🔍审计策略

重点审计具有加载类、反序列化类、对类字节码进行操作的功能和代码,关键字如下:

eval
classLoader
$$BCEL$$
ServiceLoader
ToolProvider.getSystemJavaCompiler()
getSystemClassLoader
JavaFileObject
JdbcRowSetImpl
TemplatesImpl
TransformerFactoryImpl
resolveClass
loadClass
javax.el.ELProcessor

📝如何修复

在非必要情况下,避免业务中出现此类功能,或对数据源进行严格校验。

二、业务逻辑漏洞

1. 越权漏洞

📌漏洞成因

  • 在应用程序处理当前用户请求时,没有对用户权限进行校验,或校验不足、失效,导致低权限用户使用高权限功能,或同权限用户操作对方数据。
  • 对用户的身份标识信息从可伪造的参数或headers中获取,而不是从session中获取的话,可能存在越权。
  • 一些跨域的服务导向架构,尤其是一些数据处理的接口,如果缺少类似token的认证机制的话,也会存在类似的越权访问问题。

🔍审计策略

针对系统任意功能都可能存在越权,审计关注的是:

  • 操作是否需要身份标识或其他标识。
  • 此标识是否由用户可控。
  • 是否能够可猜解,不同主体的标识是否具有规律性。
  • 查询信息场景下,信息是否与身份标识进行绑定。
  • 对应处理的函数方法中是否使用注解或其他方式对当前用户进行权限校验。
  • 校验能否被绕过或失效。

📝如何修复

  • 处理请求之前先进行权限校验,可通过Filter、AOP或拦截器进行实现。
  • 用户标识从缓存中取出,尽量不从参数中取。

2. 图形验证码漏洞

📌漏洞成因

  • 当验证码图片的长宽由前端参数控制,并且后端没有进行校验时,攻击者可以并发生成超大的图片来进行DDOS攻击。
  • 当验证码认证了一次后,无论成功还是失败,没有及时清空,就可以被重复使用,导致爆破攻击。
  • 简单的图片验证码可被OCR识别,导致爆破攻击。
  • 在后端逻辑校验过程中并未对前端传递验证码参数为null进行相关的逻辑判断,直接删除验证码参数或置空值即可绕过。

🔍审计策略

在登陆,重要数据的增删改功能处一般会添加图形验证码功能,可以通关关键字搜索:captchacheckCode等等。
重点审计验证码生成逻辑,以及验证码的判断校验逻辑、顺序。

📝如何修复

图形验证码由后端控制大小;验证一次后无论是否成功都立即清空缓存;生成复杂的图形验证码,降低遭到机器识别的风险;对验证码参数是否为空进行判断。

3. 短信服务漏洞

📌漏洞成因

  • 如果应用程序对短信接口没有发送频率限制、或校验逻辑有误,则会导致短信炸弹漏洞。
  • 如果对手机号进行校验或格式化处理逻辑有误,可导致攻击者通过使用空格、分隔符、手机区号、字母等方式绕过对手机号的限制,在进行缓存计数时进行绕过。
  • 如果短信内容的提示有部分通过参数内容获取,则可能导致攻击者篡改消息内容,对部分用户进行定点钓鱼攻击、或垃圾短信等。

🔍审计策略

重点审计发送短信验证码功能的业务逻辑,一般通过关键字sms进行搜索。审计业务逻辑、缓存次数逻辑、非空判断逻辑、短信内容逻辑等等。

📝如何修复

针对手机号的格式化以及号码缓存的业务逻辑设计一定要正确,对短信发送接口及验证接口的业务顺序要正确。

4. Zip文件提取

📌漏洞成因

在业务中可能存在上传压缩文件并提取的功能,利用此类功能可能存在如下安全问题:

  • 如果提取出的文件标准路径落在解压的目标目录之外,攻击者可以利用此功能进行任意文件写入。
  • 提取出的文件消耗过多的系统资源。zip算法的本性就可能会导致zip炸弹(zip bomb)的出现,
    由于极高的压缩率,即使在解压小文件时,比如ZIP、GIF,以及gzip编码的HTTP内容,也
    可能会导致过度的资源消耗。

🔍审计策略

审计重点主要关注应用是否存在ZIP解压缩功能:

FileInputStream
ZipInputStream
getSize()
ZipEntry

如果出现getSize基本上就需要特别注意了。

📝如何修复

针对ZIP解压缩功能,应该在解压每个条目之前对其文件名进行校验。如果校验不通过,就终止整个解压过程,或对其进行忽略。
除了校验文件名,还应该对每个文件大小进行限制,过大的单个文件应该不予处理,或抛出异常。
最后对文件总个数也应该有所限制,避免过多的文件数目导致占用系统资源。

5. 自动绑定漏洞

📌漏洞成因

攻击者可能将非预期的HTTP请求参数绑定到一个对象上,使用这种方法来创建、修改、更新开发人员或者业务本身从未打算变更的参数,而这些新参数反过来又会影响程序代码中不需要的新变量或对象,进而触发一些业务逻辑漏洞。

🔍审计策略

重点审计应用程序接参时是否使用自动绑定注入完整对象,而对象是否又对应了数据层的实体类,在更新数据时是否能够通过添加参数的方式更新非预期的属性值;或这些参数能否影响后续业务逻辑。

📝如何修复

取原始数据由DAO层取;存放于session中的对象,如用户等,在更新信息时注意敏感字段;使用自动绑定以实体类来接参时,确保类属性与实际参数能够一一对应。

三、编码问题

1. 硬编码

📌漏洞成因

如果将敏感信息(包括口令和加密密钥)硬编码在程序中,可能会将敏感信息暴露给攻击者。任何能够访问到class文件的人都可以反编译class文件并发现这些敏感信息。因此,不能将信息硬编码在程序中。

🔍审计策略

审计源代码中是否有硬编码敏感信息。

📝如何修复

动态获取敏感信息,通过配置文件、读取数据库或其他手段。

2. 临时文件删除

📌漏洞成因

开发人员会在全局可写的目录中创建临时文件。这类目录中的文件可能会被定期清理,然而,如果文件未被安全地创建或者用完后还是可访问的,攻击者便可以利用共享目录中的文件操作获取本地文件系统访问权限。

🔍审计策略

重点文件应用程序创建新文件时是否为临时文件,在使用后是否对临时进行及时的删除。
可以搜索关键字FileFileOutputStreamtempFileFileUtils进行查找。

📝如何修复

可以使用NIO中的createTempFile() 方法创建临时文件,这种方法无论是否有异常,都会自动关闭文件,使用DELETE_ON_CLOSE选项,在文件关闭时自动删除。
或在业务逻辑处理完成后显式地进行删除,但要注意条件竞争问题。