Fortify漏洞一窥

Path Manipulation

解释:

当满足以下两个条件时,就会产生 path manipulation 错误:

  1. 攻击者可以指定某一文件系统操作中所使用的路径。

  2. 攻击者可以通过指定特定资源来获取某种权限,而这种权限在一般情况下是不可能获得的。

例如,在某一程序中,攻击者可以获得特定的权限,以重写指定的文件或是在其控制的配置环境下运行程序。

例 1: 下面的代码使用来自于 HTTP 请求的输入来创建一个文件名。程序员没有考虑到攻击者可能使用像“**../../**tomcat/conf/server.xml”一样的文件名,从而导致应用程序删除它自己的配置文件。

1
2
3
4
5
6
7
String rName = request.getParameter("reportName");

File rFile = new File("/usr/local/apfr/reports/" + rName);

...

rFile.delete()

例 2:下面的代码使用来自于配置文件的输入来决定打开哪个文件,并返回给用户。如果程序在一定的权限下运行,且恶意用户能够篡改配置文件,那么他们可以通过程序读取系统中以 .txt 扩展名结尾的所有文件。

1
2
3
fis = new FileInputStream(cfg.getProperty("sub") + ".txt");
amt = fis.read(arr);
out.println(arr);

建议:

方案一:创建一份合法资源名的列表,并且规定用户只能选择其中的文件名。通过这种方法,用户就不能直接由自己来指定资源的名称了。

但在某些情况下,这种方法并不可行,因为这样一份合法资源名的列表过于庞大、难以跟踪。因此,程序员通常在这种情况下采用方案二,黑名单+白名单 双重过滤路径的办法。

方案二:在输入之前,黑名单会有选择地拒绝或避免潜在的危险字符(例如过滤**..**字符)。同时创建一份白名单,允许其中的字符出现在资源名称中,且只接受完全由这些被认可的字符组成的输入。

Try to normalize the URL before using it

https://docs.oracle.com/javase/7/docs/api/java/net/URI.html#normalize()

1
Path path = Paths.get("/foo/../bar/../baz").normalize();

or use normalize from org.apache.commons.io.FilenameUtils

https://commons.apache.org/proper/commons-io/javadocs/api-1.4/org/apache/commons/io/FilenameUtils.html#normalize(java.lang.String)

1
Stirng path = FilenameUtils.normalize("/foo/../bar/../baz");

For both the result will be \baz

Race Condition: Singleton MemberField

Servlet 成员字段可能允许一个用户查看其他用户的数据。

解释

许多 Servlet 开发人员都不了解 Servlet 为单例模式。 Servlet 只有一个实例,并通过使用和重复使用该单个实例来处理需要由不同线程同时处理的多个请求。 这种误解的共同后果是,开发者使用 Servlet 成员字段的这种方式会导致某个用户可能在无意中看到其他用户的数据。 换言之,即把用户数据存储在 Servlet 成员字段中会引发数据访问的 race condition。
例 1: 以下 Servlet 把请求参数值存储在成员字段中,然后将参数值返回给响应输出流。

1
2
3
4
5
6
7
8
9
public class GuestBook extends HttpServlet {
String name;

protected void doPost (HttpServletRequest req, HttpServletResponse res) {
name = req.getParameter("name");
...
out.println(name + ", thanks for visiting!");
}
}

当该代码在单一用户环境中正常运行时,如果有两个用户几乎同时访问 Servlet,可能会导致这两个请求以如下方式处理线程的插入:
线程 1: assign “Dick” to name
线程 2: assign “Jane” to name
线程 1: print “Jane, thanks for visiting!”
线程 2: print “Jane, thanks for visiting!”
因此会向第一个用户显示第二个用户的用户名。

建议

不要为任何参数(常量除外)使用 Servlet 成员字段。 (例如,确保所有成员字段都是 static final)。当开发者需要把代码内某一部分中的数据传输到另一部分时,他们经常使用 Servlet 成员字段存储用户数据。 如果您也是这么做的,可以考虑声明一个单独的类,并仅使用 Servlet “封装”这个新类。
例 2: 上述例子中的 bug 可以利用以下方式进行修正:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class GuestBook extends HttpServlet {
protected void doPost (HttpServletRequest req, HttpServletResponse res) {
GBRequestHandler handler = new GBRequestHandler();
handler.handle(req, res);
}
}
public class GBRequestHandler {
String name;
public void handle(HttpServletRequest req, HttpServletResponse res) {
name = req.getParameter("name");
...
out.println(name + ", thanks for visiting!");
}
}

此外, Servlet 也可以利用同步代码块来访问 servlet 实例变量。但是,使用同步代码块可能会导致严重的性能问题。

Command Injection

执行不可信赖资源中的命令,或在不可信赖的环境中执行命令,都会导致程序以攻击者的名义执行恶意命令。

解释

Command Injection 漏洞主要表现为以下两种形式:

- 攻击者能够篡改程序执行的命令:攻击者直接控制了所执行的命令。

- 攻击者能够篡改命令的执行环境:攻击者间接地控制了所执行的命令。

在这种情况下,我们着重关注第一种情况,即攻击者有可能控制所执行命令。这种类型的 Command Injection 漏洞会在以下情况下出现:

\1. 数据从不可信赖的数据源进入应用程序。

\2. 数据被用作代表应用程序所执行命令的字符串,或字符串的一部分。

\3. 通过命令的执行,应用程序会授予攻击者一种原本不该拥有的特权或能力。

例 1:下面这段来自系统实用程序的代码根据系统属性 APPHOME 来决定其安装目录,然后根据指定目录的相对路径执行一个初始化脚本。

1
2
3
4
5
...
String home = System.getProperty("APPHOME");
String cmd = home + INITCMD;
java.lang.Runtime.getRuntime().exec(cmd);
...

Example 1 中的代码可以使攻击者通过修改系统属性 APPHOME 以指向包含恶意版本 INITCMD 的其他路径来提高自己在应用程序中的权限,继而随心所欲地执行命令。由于程序不会验证从环境中读取的值,因此如果攻击者能够控制系统属性 APPHOME 的值,他们就能欺骗应用程序去运行恶意代码,从而取得系统控制权。

例 2:下面的代码来自一个管理 Web 应用程序,旨在使用户能够使用一个围绕 rman 实用程序的批处理文件封装器来启动 Oracle 数据库备份,然后运行一个 cleanup.bat 脚本来删除一些临时文件。脚本 rmanDB.bat 接受单个命令行参数,该参数指定了要执行的备份类型。由于访问数据库受限,所以应用程序执行备份需要具有较高权限的用户。

1
2
3
4
5
6
...
String btype = request.getParameter("backuptype");
String cmd = new String("cmd.exe /K
\"c:\\util\\rmanDB.bat "+btype+"&&c:\\util\\cleanup.bat\"")
System.Runtime.getRuntime().exec(cmd);
...

这里的问题是:程序没有对读取自用户的 backuptype参数进行任何验证。通常情况下 Runtime.exec() 函数不会执行多条命令,但在这种情况下,程序会首先运行 cmd.exe shell,从而可以通过调用一次 Runtime.exec() 来执行多条命令。在调用该 shell 之后,它即会允许执行用两个与号分隔的多条命令。如果攻击者传递了一个形式为 "&& del c:\\dbms\\*.*" 的字符串,那么应用程序将随程序指定的其他命令一起执行此命令。由于该应用程序的特性,运行该应用程序需要具备与数据库进行交互所需的权限,这就意味着攻击者注入的任何命令都将通过这些权限得以运行。

示例 3:下面的代码来自一个 Web 应用程序,用户可通过该应用程序提供的界面在系统上更新他们的密码。在某些网络环境中更新密码时,其中的一个步骤就是在 /var/yp 目录中运行 make 命令。

1
2
3
...
System.Runtime.getRuntime().exec("make");
...

这里的问题在于程序没有在它的构造中指定一个绝对路径,并且没能在执行 Runtime.exec() 调用前清除它的环境变量。如果攻击者能够修改 $PATH 变量,把它指向名为 make 恶意二进制代码,程序就会在其指定的环境下执行,然后加载该恶意二进制代码,而非原本期望的代码。由于应用程序自身的特性,运行该应用程序需要具备执行系统操作所需的权限,这意味着攻击者会利用这些权限执行自己的 make,从而可能导致攻击者完全控制系统。

有些人认为在移动世界中,典型的漏洞(如 Command Injection)是无意义的 – 为什么用户要攻击自己?但是,谨记移动平台的本质是从各种来源下载并在相同设备上运行的应用程序。恶意软件在银行应用程序附近运行的可能性很高,它们会强制扩展移动应用程序的攻击面(包括跨进程通信)。

例 4:以下代码可从 Android Intent 中读取要执行的命令。

1
2
3
4
5
6
7
8
9
10
...
String[] cmds = this.getIntent().getStringArrayExtra("commands");
Process p = Runtime.getRuntime().exec("su");
DataOutputStream os = new DataOutputStream(p.getOutputStream());
for (String cmd : cmds) {
os.writeBytes(cmd+"\n");
}
os.writeBytes("exit\n");
os.flush();
...

在经过 root 的设备上,恶意应用程序会强迫受攻击应用程序使用超级用户权限执行任意命令。

建议

应当禁止用户直接控制由程序执行的命令。 在用户的输入会影响命令执行的情况下,应将用户输入限制为从预定的安全命令集合中进行选择。 如果输入中出现了恶意的内容,传递到命令执行函数的值将默认从安全命令集合中选择,或者程序将拒绝执行任何命令。

在需要将用户的输入用作程序命令中的参数时,由于合法的参数集合实在很大,或是难以跟踪,使得这个方法通常都不切实际。 开发者通常的做法是使用黑名单。 在输入之前,黑名单会有选择地拒绝或避免潜在的危险字符。

但是,任何一个定义不安全内容的列表都很可能是不完整的,并且会严重地依赖于执行命令的环境。 较好的方法是创建一份白名单,允许其中的字符出现在输入中,并且只接受完全由这些经认可的字符组成的输入。

攻击者可以通过修改程序运行命令的环境来间接控制这些命令的执行。 我们不应当完全信赖环境,还需采取预防措施,防止攻击者利用某些控制环境的手段进行攻击。 无论何时,只要有可能,都应由应用程序来控制命令,并使用绝对路径执行命令。 如果编译时尚不了解路径(如在跨平台应用程序中),应该在执行过程中利用可信赖的值构建一个绝对路径。 应对照一系列定义有效值的常量,仔细地检查从配置文件或者环境中读取的命令值和路径。

有时还可以执行其他检验,以检查这些来源是否已被恶意篡改。 例如,如果一个配置文件为可写,程序可能会拒绝运行。 如果能够预先得知有关要执行的二进制代码的信息,程序就会进行检测,以检验这个二进制代码的合法性。 如果一个二进制代码始终属于某个特定的用户,或者被指定了一组特定的访问权限,这些属性就会在执行二进制代码前通过程序进行检验。 尽管可能无法完全阻止强大的攻击者为了控制程序执行的命令而对系统进行的攻击,但只要程序执行外部命令,就务必使用最小授权原则: 不给予超过执行该命令所必需的权限。

Cross-Site Scripting: Content Sniffing

向 Web 浏览器发送未经验证的数据可能导致某些浏览器执行恶意代码。

解释

Cross-Site Scripting (XSS) 漏洞会在以下情况下出现:

1.数据通过一个不可信赖的数据源进入 Web 应用程序。对于 Reflected XSS,不可信赖的数据源通常为 Web 请求,而对于 Persisted(也称为 Stored)XSS,该数据源通常为数据库或其他后端数据存储。

2.未经验证但包含在动态内容中的数据将传送给 Web 用户。

传送到 Web 浏览器的恶意内容通常采用 JavaScript 片段的形式,但也可能会包含一些 HTML、Flash 或者其他任意一种可以被浏览器执行的代码。基于 XSS 的攻击手段花样百出,几乎是无穷无尽的,但通常它们都会包含传输给攻击者的私有数据(如 Cookie 或者其他会话信息)。在攻击者的控制下,指引受害者进入恶意的网络内容;或者利用易受攻击的站点,对用户的机器进行其他恶意操作。

为了让浏览器将响应呈现为 HTML 或者可执行脚本的其他文档,必须指定text/htmlMIME 类型。因此,仅当响应使用此 MIME 类型或者使用的任何其他类型同样强制浏览器将响应呈现为 HTML 或可执行 SVG 图像 (image/svg+xml) 和 XML 文档 (application/xml) 等脚本的其他文档时,才有可能使用 XSS。

大多数现代浏览器不会呈现 HTML,也不会在为响应提供application/json等 MIME 类型时执行脚本。但是,Internet Explorer 等某些浏览器可执行称为Content Sniffing的内容。Content Sniffing 涉及到忽略提供的 MIME 类型并尝试根据响应的内容推断正确的 MIME 类型。
但是,值得注意的是,MIME 类型的text/html是可能导致 XSS 漏洞的唯一 MIME 类型。可执行 SVG 图像 (image/svg+xml) 和 XML 文档 (application/xml) 等脚本的其他文档可能导致 XSS 漏洞,无论浏览器是否执行 Content Sniffing 都是如此。

因此,<html><body><script>alert(1)</script></body></html>等响应可能呈现为 HTML,即使其 content-type 标头设置为 application/json 也是如此。

示例 1:以下 JAX-RS 方法反映 application/json响应中的用户数据。

1
2
3
4
5
6
7
8
@Path("/myResource")
@Produces("application/json")
public class SomeResource {
@GET
public String doGetAsJson(@QueryParam("param") String param) {
return "{'name': '" + param + "'}";
}
}

如果攻击者所发送请求的 name参数设置为 <html><body><script>alert(1)</script></body></html>,则服务器将生成以下响应:

1
2
3
4
5
6
HTTP/1.1 200 OK
Content-Length: 88
Content-Type: application/json
Connection: Closed

{'name': '<html><body><script>alert(1)</script></body></html>'}

尽管响应明确声明应该将其视为 JSON 文档,但旧浏览器仍可能尝试将其呈现为 HTML 文档,使其容易受到 Cross-Site Scripting 攻击。

建议

所以根据XSS漏洞产生的原因,对于XSS脚本攻击的漏洞修复,主要解决方案是:

a、对输入源进行校验和过滤;

b、对输出源进行校验和过滤;

例:

提供公共方法,结合实际业务需求,对输入源和输出源调用该方法进行特殊字符的过滤,主要是浏览器脚本可能包含的一些特殊字符。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public static String filterInput(String input) {
List<String> list = new ArrayList<>();
list.add("<");
list.add(">");
list.add("(");
list.add(")");
list.add("&");
list.add("?");
list.add(";");
String encode = Normalizer.normalize(input, Normalizer.Form.NFKC);
for (String s : list) {
encode = encode.replace(s, "");
}
return encode;
}

Often Misused: File Upload

允许用户上传文件可能导致攻击者注入危险内容或恶意代码以便在服务器上运行。

解释

无论编写程序所用的语言是什么,最具破坏性的攻击通常都会涉及执行远程代码,攻击者借此可在程序上下文中成功执行恶意代码。如果允许攻击者向某个可通过 Web 访问的目录上传文件,并能够将这些文件传递给代码解释器(如 JSP/ASPX/PHP),他们就能促使这些文件中包含的恶意代码在服务器上执行。

示例:以下 Spring MVC 控制器类包含可用于处理上传文件的参数。

1
2
3
4
5
6
7
8
@Controller
public class MyFormController {
...
@RequestMapping("/test")
public String uploadFile (org.springframework.web.multipart.MultipartFile file) {
...
} ...
}

即使程序将上传的文件存储在一个无法通过 Web 访问的目录中,攻击者仍然有可能通过向服务器环境引入恶意内容来发动其他攻击。如果程序容易出现 path manipulation、command injection 或危险的 file inclusion 漏洞,那么攻击者就可能上传带恶意内容的文件,并利用另一种漏洞促使程序读取或执行该文件。

建议

对上传文件做好校验:

  1. 文件类型,做好类型白名单,通过文件头来判断类型
  2. 文件大小,不同业务做不同限制
  3. 压缩文件,要提防zip炸弹

Resource Injection

使用用户输入控制资源标识符,借此攻击者可以访问或修改其他受保护的系统资源。

解释

当满足以下两个条件时,就会发生 resource injection:

\1. 攻击者可以指定已使用的标识符来访问系统资源。

例如,攻击者可能可以指定用来连接到网络资源的端口号。

\2. 攻击者可以通过指定特定资源来获取某种权限,而这种权限在一般情况下是不可能获得的。

例如,程序可能会允许攻击者把敏感信息传输到第三方服务器。

资源注入攻击与路径操纵不同,因为资源注入侧重于访问本地文件系统以外的资源,而 路径操纵侧重于访问本地文件系统。

示例 1:下面的代码使用读取自 HTTP 请求的端口号来建立一个套接字。

1
2
3
4
5
String remotePort = request.getParameter("remotePort");
...
ServerSocket srvr = new ServerSocket(remotePort);
Socket skt = srvr.accept();
...

有些人认为在移动世界中,典型的 Web 应用程序漏洞(如 resource injection)是无意义的 – 为什么用户要攻击自己?但是,谨记移动平台的本质是从各种来源下载并在相同设备上运行的应用程序。恶意软件在银行应用程序附近运行的可能性很高,它们会强制扩展移动应用程序的攻击面(包括跨进程通信)。

示例 2:下面的代码使用读取自 Android Intent 的 URL 在 WebView 中加载页面。

1
2
3
4
5
6
...
WebView webview = new WebView(this);
setContentView(webview);
String url = this.getIntent().getExtras().getString("url");
webview.loadUrl(url);
...

这种受用户输入影响的资源表明其中的内容可能存在危险。例如,包含如句点、斜杠和反斜杠等特殊字符的数据在与 file system 相作用的方法中使用时,具有很大风险。类似的,对于创建远程结点的函数来说,包含 URL 和 URI 的数据也具有很大风险。

Formula Injection

攻击者可能会控制写入到电子表格的数据,借此让用户打开某些电子表格处理器上的文件。

解释

常用电子表格处理器(如 Apache OpenOffice Calc 和 Microsoft Office Excel)支持的公式运算非常强大,这可能会使攻击者控制电子表格而在底层系统上运行任意命令或在电子表格上泄漏敏感信息。

例如,攻击者可能会将以下有效负载作为 CSV 字段的一部分注入:=cmd|'/C calc.exe'!Z0。如果打开电子表格的用户信任文档来源,他们可能就会接受电子表格处理器提供的所有安全提示信息,并使此有效负载(此处为打开 Windows 计算器)在其系统上运行。

示例:以下示例展示了使用未经检查的用户控制数据生成 CSV 响应的 Spring 控制器:

1
2
3
4
5
6
7
8
9
10
11
@RequestMapping(value = "/api/service.csv")
public ResponseEntity<String> service(@RequestParam("name") String name) {

HttpHeaders responseHeaders = new HttpHeaders();
responseHeaders.add("Content-Type", "application/csv; charset=utf-8");
responseHeaders.add("Content-Disposition", "attachment;filename=file.csv");

String data = generateCSVFor(name);

return new ResponseEntity<>(data, responseHeaders, HttpStatus.OK);
}

fortify源代码扫描问题分析汇总:https://blog.csdn.net/weixin_39997829/article/details/118970901


Fortify漏洞一窥
https://honosv.github.io/2022/05/04/Fortify漏洞一窥/
作者
Nova
发布于
2022年5月4日
许可协议