ref:http://www.xwood.net/_site_domain_/_root/5870/5874/t_c268166.html

标签:安全,漏洞,健壮,java,SQL注入,SS及CSRF,命令注入,线程安全     发布时间:2017-09-16   

一、前言

这边通过工作整理一些常见安全漏洞及解决方法,主要涉及有:框架低版本漏洞、空指针的引用、整数溢出、命令注入、SQL注入、XSS及CSRF、跳转漏洞、HTTP Response Splitting漏洞、路径可控制及代码注入、资源泄露及对象相等、序列化、线程安全等

二、主要内容

1.框架低版本漏洞

漏洞说明 在使用低版本struts/spring/hibernate开发的时候,低版本漏洞通常存在任意代码执行、XSS漏洞、信息泄露等其他漏洞,可能会因为低版本漏洞问题导致业务安全问题。
漏洞示例及安全方法 使用稳定的高版本框架进行开发,并密切关注该版本的漏洞问题、漏洞修复规避方法。避免使用存在已知问题的低版本。

2.空指针的引用

漏洞说明

空指针不是指空的指针,而是空的对象(java),即对象为null值。如图1模型,当一个空的对象被引用时将使程序立即崩溃或强制退出。空指针引用在Java程序中经常出现,尤其在函数间通过对象参数传递的情况,因此,对于对象的引用,应该添加必要的判断或异常捕获以确定其引用没有问题

1111111111副本.png

漏洞示例及安全方法

示例1:

1
2
String cmd = System.getProperty(“cmd”);
cmd = cmd.trim();

以上代码段假设系统总会定义”cmd”属性,然而若攻击者能够控制程序的环境使得”cmd”没定义,则cmd.trim()将引发空指针的引用。

示例2:

1
2
3
4
5
6
String getValueName ( IValue valueObject ){
   if( valueObject.getType() == VALTYPE.IMME ) {
       return valueObject.getName();
    }
    return null;
}

以上是某个类的成员函数定义,获取值对象的串值,供外部函数调用。有以下两点需要注意,否则可能会导致空指针引用。

1) 在函数内,没有考虑到传入的参数valueObject存在等于null值的可能性,导致在valueObject.getType()时存在空指针引用问题;

2) 函数不负责任地以null值作为返回值,将导致在调用getValueName()后,需判断返回值为空的可能性。

备注 Crash / exit / restart在较小的概率下,可导致任意代码执行。

3.整数溢出

漏洞说明

整数溢出(被称为silent killer),包括上溢和下溢,上溢时整数会被转化为负数,下溢时整数会变成正数。整数溢出能否被利用视溢出后续操作有没改写额外的内存。

当溢出后的整数值作为数组索引(访问非法地址)或缓冲区大小(在C/C++较常见)时,将导致不确定的行为,如程序崩溃、死循环等。在某些情况下,整数溢出还可能引发缓冲区溢出(如通过本地方法调用C/C++程序)。

整数溢出通常源于以下三种情况:

1)进行数值间的运算(+,-,×,/ 等)时,比如最大值加1;

2)在数值扩展或截断等类型转换后进行数值运算时;

3)符号扩展,特别是一个负数进行符号扩展到一个更大的数值类型时

通常造成以下危害:

异常的结果/访问非法地址/死循环;

DOS(资源耗尽);

引发缓冲区溢出 (JNI本地方法调用)

漏洞示例及安全方法

示例1:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public void update(byte[] b, int off, int len) {
    if (b == null) {
        throw new NullPointerException();
    }
    if (off < 0 || len < 0 || off + len > b.length) {
        throw new ArrayIndexOutOfBoundsException();
}
    crc = updateBytes(crc, b, off, len);
}
public void update(byte[] b, int off, int len) {
    if (b == null) {
        throw new NullPointerException();
    }
    if (off < 0 || len < 0 || off > b.length - len) {
        throw new ArrayIndexOutOfBoundsException();
    }
    crc = updateBytes(crc, b, off, len);
}

 以上示例中,判断语句offset + len > b.length可能导致offset + len过大而溢出,从而不满足该条件,继续往下正常执行,可能导致访问非法内存。将其改为off > b.length – len有效避免了整数溢出问题。

4.命令注入

漏洞说明

命令注入允许攻击者将任意的系统命令注入到应用程序中,被执行命令拥有与Java应用具有相同的权限等级。命令注入利用用户的输入来执行shell命令,如cmd.exe,/bin/sh等。程序被控制,可任意执行命令

漏洞示例及安全方法

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import java.io.*;
public class Example2 {
       public static void main(String[] args)
       throws IOException {
              if(args.length != 1) {
                     system.out.println("No arguments");
                     system.exit(1);
              }
              Runtime runtime = Runtime.getRuntime();
              String[] cmd = new String[3];
              cmd[0] = "cmd.exe" ;
                cmd[1] = "/C";
                cmd[2] = "dir " + args[0];
              Process proc = runtime.exec(cmd);
              
              InputStream is = proc.getInputStream();
              InputStreamReader isr = new InputStreamReader(is);
              BufferedReader br = new BufferedReader(isr);
              
              String line;
              while ((line = br.readLine()) != null) {
                     System.out.println(line);
              }
       }
}

以上代码调用系统的shell执行含有用户输入参数的命令,攻击者可以将多个命令合并在一起。比如,输入 “. & echo hello” 首先由dir命令罗列出当前目录的内容,再用echo打印出友好的信息。

备注

Java程序中用来执行命令的API有:

1
2
3
4
5
6
7
Runtime.  public Process exec(String command)
public Process exec(String command, String[] envp)
public Process exec(String command,  String[] envp,  File dir)
public Process exec(String[] cmdarray)
public Process exec(String[] cmdarray, String[] envp)
public Process exec(String[] cmdarray, String[] envp, File dir)
System.exec(String str)

对于以上参数,可通过写死命令,只允许输入预设命令、白名单的方式控制问题。

5.SQL注入

漏洞说明

SQL注入是Web应用程序中最常见的一类安全漏洞。SQL注入通常是由于直接将未可信的用户输入作为数据库查询的参数,用于数据库查询操作,从而引发数据库信息的泄露。SQL注入可导致未授权的访问敏感数据,更新或删除数据库信息等恶意操作。引发信息泄露/任意代码执行

漏洞。

漏洞示例及安全方法

示例:

1
2
3
4
5
HttpServletRequest request = ...;
String userName = request.getParameter("name");
Connection con = ...
String query = "SELECT * FROM Users   WHERE name = ’" + userName + "’";
con.execute (query);

以上代码段将用户输入的用户名作为SQL查询语句的条件参数,在正常的用户名输入下,该查询将正确返回结果。但当恶意用户能够完全控制userName的值,如其可设置userName的值为’ OR 1 = 1; --,则查询语句变成 SELECT * FROM Users WHERE name = ‘’ OR 1=1; --‘,将允许其访问数据库中的所有用户信息记录。

SQL语句应使用语句参数化编程,或使用存储过程、安全框架的方式。

注意:使用ibatis框架,应使用”#”的写法,#写法会采用预编译的方式,将转义交给数据库,不会出现注入问题。Like语句:1、oracle 可以通过’%’||’#param#’||’%’避免。2、MySQL可以通过CONCAT(‘%’,#param#,’#’)避免。3、MSSQL通过’%’+#param#+’%’避免。

备注

存储过程

Java程序中常用的SQL执行语句执行函数如以下所示,在使用的时候应注意:

1
2
3
4
5
6
Statement  boolean execute(String sql)
ResultSet executeQuery()
ResultSet executeQuery(String sql)
int executeUpdate()
int executeUpdate(String sql)
Connection  prepareStatement(string sql)

6.XSS

漏洞说明

跨站点脚本发生于在动态生成的Web页面直接显示未可信(未严格验证)的外部输入,恶意用户可以利用该缺陷往动态页面中注入恶意脚本,当用户打开页面时,该恶意脚本将自动执行。注入的恶意脚本可以获取用户的帐号密码、改变用户设置、窃取cookies或往页面中插入一些恶意的内容。可导致恶意内容显示/任意脚本(代码)执行问题。

漏洞示例及安全方法

在应用程序层面上,将用户的输入打印到由浏览器执行的动态页面上将引发XSS漏洞。如下例子:

1
2
3
4
5
if (cookies != null) {
    for (int i = 0; i < cookies.length; i++) {
        out.println(cookies[i].getName() + "=[" + cookies[i].getValue() + "]");
    }
}

若恶意攻击者能够重写用户的cookie,则将可以让用户执行恶意的javaScript脚本,一个简单实例如下: http://www.javanuke.org/user.jsp?op=userinfo&uname=&ltscript&gtalert(document.cookie);</script>

Java程序中常用的XSS页面输出方式有:

1
2
3
4
5
6
out.println
              <%=  %>
HttpServletResponse.sendError()
ServletOutputStream.print(…);
                             .println(…);
.write(…);

XSS常见的有两类,一类是反应式(Reflected)的XSS,直接将攻击者的恶意脚本回显到动态页面上,以诱使其他用户上当;另一类是存储式(Stored)的XSS,恶意数据通过数据库媒介,并将从数据库读出的数据回显到动态页面上,影响更多的用户。因此,数据库中获取的数据也是不可信的。

解决XSS漏洞,需要对参数的传入进行校验和转义,并对输出进行编码,避免参数被恶意篡改,出现类似漏洞。

在公司内部,可以使用金丝甲的方式避免该类问题,开发库直接调用:

<dependency org="com.xwood.pms" name="esapi" rev="1.1.0-beta-20170925143711"/>

备注

在使用以下函数的时候比较容易出现XSS漏洞,需要注意对传入参数的转义:

1
2
3
4
5
6
7
PrintWriter.print(…)
PrintWriter.println(…)
PrintWriter.write(…)
HttpServletResponse.sendError(…)
ServletOutputStream.print(…);
ServletOutputStream.println(…);
OutputStreamWriter.write(…);

7.CSRF

漏洞说明

用户以当前身份浏览到flash或者第三方网站时,JS/flash可以迫使用户浏览器向任意CGI发起请求,此请求包含用户身份标识,CGI如无限制则会以用户身份进行操作

漏洞示例及安全方法
1
2
3
4
5
6
可使用以下任意办法防御CSRF攻击
1、  部署anti csrf token。
2、  限制访问的referer。
3、  不使用get方式提交数据。
4、  重要操作进行二次验证(验证码)等请求referrer验证
5、  关键请求使用验证码

8.跳转漏洞

漏洞说明

服务端未对传入的URL变量进行控制,可以恶意构造一个恶意地址,让用户跳转到恶意网站。跳转漏洞一般用于钓鱼攻击,通过转到恶意网站欺骗用户输入用户名和密码盗取用户信息,或欺骗用户进行金钱交易;可能引发的XSS漏洞。

漏洞示例及安全方法

通过修改参数中的合法URL为非法URL可检测该漏洞。

修复思路如下:

1.需要经过验证的页面,可跟踪上一个页面的文件名,只有从上一页面转进来的会话才能读取这个页面。

2.仅传递一个回调url作为参数是不安全的,增加一个参数签名,保证回调url不被修改

在控制页面转向的地方校验传入的URL是否为公司域名(或者其他可信域名)

如以下是一段校验是否公司域名的JS函数:

1
2
3
4
function VaildURL(sUrl)
{
 return (/^(https?:\/\/)?[\w\-.]+\.(qq|paipai|soso|taotao)\.com($|\/|\\)/i).test(sUrl)||(/^[\w][\w\/\.\-_%]+$/i).test(sUrl)||(/^[\/\\][^\/\\]/i).test(sUrl) ? true false;
}

跳转检测中也加入了CRLF头部注入漏洞的检测逻辑,具体用例就是在请求参数中加入了%0d%0a这种测试代码,来实现攻击。

修复方案:在判断到一个参数中包含 %00 --- %1f 的控制字符时都是不合法的,可以对其进行删除

9.HTTP Response Splitting漏洞

漏洞说明

HTTP响应头注入漏洞(又叫做HTTP响应分割,HTTP Response Splitting,也有叫做CrLf Injection)。

该漏洞是指HTTP消息头可以被控制注入一些恶意的换行,而可以注入一些会话cookies或者HTML代码。

漏洞示例及安全方法

 通过过滤\r、\n之类的换行符,避免输入数据污染到其他http头。

10.路径可控制

漏洞说明

利用用户的未可信的输入来控制访问服务器上的哪个文件,允许用户控制或访问不在用户访问路径上的文件。所访问文件的文件名(含路径)来源于外部输入(或部分受控制),通过控制用户访问路径上的“../”或“//”等特殊字符来访问其他目录下的文件(或添加新文件),可造成以下风险:

读敏感数据文件

删除重要文件

DOS攻击

漏洞示例及安全方法

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
public class FileUploadServlet extends HttpServlet {
...
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
           
response.setContentType("text/html");
PrintWriter out = response.getWriter();
String contentType = request.getContentType();
           
// the starting position of the boundary header
int ind = contentType.indexOf("boundary=");
String boundary = contentType.substring(ind+9);
           
String pLine = new String();
String uploadLocation = new String(UPLOAD_DIRECTORY_STRING); //Constant value
           
// verify that content type is multipart form data
if (contentType != null && contentType.indexOf("multipart/form-data") != -1) {
// extract the filename from the Http header
BufferedReader br = new BufferedReader(new InputStreamReader(request.getInputStream()));
...
pLine = br.readLine(); 
String filename = pLine.substring(pLine.lastIndexOf("\\"), pLine.lastIndexOf("\""));
...
// output the file to the local upload directory
try {
BufferedWriter bw=new BufferedWriter(new FileWriter(uploadLocation+filename, true));
for (String line; (line=br.readLine())!=null; ) {
if (line.indexOf(boundary) == -1) {
bw.write(line);
bw.newLine();
bw.flush();
}
//end of for loop
bw.close();
               
catch (IOException ex) {...}
// output successful upload response HTML page
}
// output unsuccessful upload response HTML page
else
{...}
}
...
}

以上示例未对上传的文件进行检查,允许攻击者上传任意可执行文件或者含有恶意代码的其他文件。此外,该例子由BufferedWriter对象指定所写的文件,攻击者可能能够对任意文件进行写操作,尤其是一些关键的服务器配置文件。又如下简单程序。

1
2
3
4
5
6
String path = getInputPath();
if (path.startsWith("/safe_dir/"))
{
File f = new File(path);
f.delete()
}

由于有“/safe_path/”的限定,程序假定path就是可信的。但当恶意用户输入/safe_dir/../important.dat,则导致程序误删父目录下的文件important.dat。

危害说明

可以采取的解决方案有:

写死路径、白名单路径,随机化文件名(如../),

要检测../,对../ 进行替换转义

使用文件服务器

限制文件及目录权限

11.代码注入

漏洞说明

代码注入通过将恶意代码注入到应用程序,在应用程序的上下文中被执行恶意代码。在Java应用程序中主要有两种方式可以将Java代码注入到应用程序中,脚本API和动态JSP文件包含(include)。如果攻击者能够知道哪个脚本文件将被载入(load),则恶意代码将能够被执行造成任意代码执行漏洞。

漏洞示例及安全方法

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import javax.script.*;
  
public class Example1 {
       public static void main(String[] args) {
              try {
                     ScriptEngineManager manager = new ScriptEngineManager();
                     ScriptEngine engine = manager.getEngineByName("JavaScript");
                     System.out.println(args[0]);
                     engine.eval("print('"+ args[0] + "')");
              catch(Exception e) {
                     e.printStackTrace();
              }
       }
}

以上代码允许用户注入任意的代码,若其中args[0]参数设置如下,则将允许恶意用户创建任意文件。

1
2
3
hallo');
var fImport = new JavaImporter(java.io.File);
with(fImport) { var f = new File('new'); f.createNewFile(); }
备注

ScriptEngine.eval(string str)

写死需要执行的命令,或通过白名单、预设命令的方式,限制可以执行的命令。

12.Xpath注入

漏洞说明

XPath注入是一种类似SQL注入的攻击,XPath用于查询XML文件,通过XPath查询XML文件中特定节点的信息。漏洞会造成以下风险:

信息泄露/任意代码执行

漏洞示例及安全方法

漏洞示例:

如果程序没有验证用户查询输入,就会发生Xpath注入,攻击者可以提交恶意的请求修改查询语句,导致:事物逻辑和认证绕过,获取后端XML数据库内容。

不像传统的关系型数据库,可以对数据库、表、行或者列执行细粒度的访问控制,XML没有用户或者权限的概念。这意味着整个数据库都可以被用户读取,在应用中属于很严重的安全漏洞。

利用Xpath

绕过认证

如果一个认证过程是如下形式

/*[1]/user[username=”jtothep”and password=”password123!”]

攻击者可以提交以下输入

username: jtohep"or "1" ="1

password: anything

Xpath的查询会变成

/*[1]/user[username=”jtothep"or "1"="1” and

password=”anything”]

攻击者可以以jtohep的用户登录并且绕过实际的密码认证,这是因为XPath中的OR语法查询导致条件一直为真,类似SQL的and语法,Xpath的查询语法为:

username="jtothep" or [TRUE AND False]

结果就是

username="jtothep" or FALSE

如果jtothep这个用户被验证存在,攻击者就可以使用这个用户的身份登录,在通常的实践中使用加密形式的密码保存在用户表中,用户输入的密码也要经过加密计算再与用户表中的哈希密码进行匹配。因此使用加密形式的密码会较少的存在漏洞,使用加密查询的语法如下:

'/*[1]/user[username=”'.$username. '” and password=”'

.md5(password). '”]'

如果攻击者不知道一个正确的用户名,他仍可以绕过认证

/*[1]/user[username=”non_existing"or "1"="1” or "1" ="1"and

password=”5f4dcc3b5aa765d61d8327deb882cf99”]

执行会显示成如下的形式:

username="non_existing" or TRUE or [True AND False]

结果是:

username="non_existing" or TRUE or FALSE.

结果会以第一个节点的身份登录系统。

1
2
3
4
5
6
7
8
9
10
11
String name = request.getPararneter( name");
String passwd=request.getPararneter( passwd');
DocumentBuilderFactory dornFactory=DocumentBuilderFactory.newInstance();
dornFactory.setNarnespaceAware(true);
DocumentBuilder builder = domFactory.newEbcumentBuilder();
Document doc = builder.parse( bank-users.xml );
XPathFactory factory= XPathFactory.newlnstance();
XPath xpath=factory.newXPath();
String sss = //bank— user[name=”+name;
sss += and passwd- +passwd+ ]/*/text();
XPathExpression expr= xpath.compile(sss);
备注

净化用户输入,fn:doc(),fn:collection(), xdmp:eval() and xdmp:value()这些函数需要特别注意

使用参数化的查询,如Java的Xpath表达式

/root/element[@id=$ID]

限制doc()功能。

13.资源泄露

漏洞说明

当函数含有多处返回,导致分支混乱,而忽略在某些分支释放,即存在一条执行路径,使得打开与关闭不配对,常见的有内存泄漏 malloc/free(C/C++)、文件句柄泄漏fopen/fclose、SQL查询句柄泄漏等。

漏洞示例及安全方法

示例:

以上代码创建一个数据库Statement对象stmt,执行数据库语句CXN_SQL并处理查询结果后,在try分支及时将释放stmt。然而当查询执行或结果处理出现异常时,将导致程序进入catch分支,从而没有执行stmt.close()。当以上代码被执行足够多次,则数据库将耗尽所有的游标,而无法执行任何其他的查询。

可通过配置统一错误页面的方式解决程序崩溃的问题。

备注

资源泄露在较短时间内不会对程序造成破坏性影响,但当系统资源耗尽时,如内存不足或句柄耗尽等,程序将直接崩溃

14.对象相等、序列化、线程安全等

14.1 对象相等问题

在Java程序中,自定义对象的相等(equal)需要同时重载equals方法和hashcode方法。在实现中,经常会由于未重载或仅重载实现equals方法而导致对象的相等与意向的不一致。尤其在容器(放自定义对象)的查询和插入操作时,对象相等的不完备实现将导致容器管理混乱。

因此,对于自定义对象的判等,需要确保该对象是否已重载实现了equals方法和hashcode方法,否则将导致错误的程序逻辑。

14.2 线程安全问题

在多线程环境下,线程不安全主要来自于类变量(静态变量)和实例变量,前者位于方法区中,后者位于堆中,都是共享区域。线程不安全将导致访问不同步或读写出错。因此,在对类变量和实例变量的访问时,要注意加上适当的synchronized 关键字来防止线程不安全。

14.3 迭代器使用问题

使用迭代器时,不能修改容器的结构,否则将造成线程不安全。一个线程在 Collection 上进行迭代时,通常不允许另一个线性修改该 Collection。在这些情况下,迭代的结果是不确定的。如下简单例程,在迭代遍历容器时,删除容器中的元素,导致线程不同步的异常。使用迭代器遍历的时候,不删除容器元素,找个例子-孙亚东。

1
2
3
4
5
6
7
for (Map.Entry<String, WordInfo> entry : featureMap.entrySet())
 {
         String key = entry.getKey();
         WordInfo value = entry.getValue();
         //其他操作
         featureMap.remove(key);//抛出异常
}

14.4 不恰当的作用域

Java程序拥有严格的作用域保护机制,private, protected , public等级别作用于数据字段(类成员变量)和类方法,不恰当的作用域可能导致攻击者能够利用受信的保护域来提升权限,甚至可以通过本地方法访问系统的内存。找例子。外部访问敏感接口,覆盖权限。

通常情况下,java类的数据字段不可设置为public,public方法不可返回任意private或protected的内部数据的引用。

14.5 危险的数字格式转换

若输入串不是数字串,以下的数值转换函数都将产生异常:

1
2
3
Integer.parseInt(string str)
Long.parseLong(string str)
Double.parseDouble(string str)

捕获异常可解决。

 

关于exec注入的一点说明:一般不能实现注入,除非含有特

1.        Windows下的

cmd.exe /K 参数可以批量执行命令 

  1. String btype = request.getParameter("inputparam");
  2. String cmds[] = {"cmd.exe","/K","\"C: &&del C:\\r2.txt”+btype+” &&del C:\\r1.txt \""};
  3. System.Runtime.getRuntime().exec(cmds);

虽然此时传入的参数是用于cmd.exe的,但因为cmd.exe 使用了/K参数,将后续传入的参数用于命令执行,如果传入&&del *,最后变成

cmd.exe /K “del C:\\test1.txt && del *&&del C:\\test2.txt”

2.        Unix 下的

  • /bin/bash 参数,输入参数直接作为命令执行
  1. String btype = request.getParameter("test");
  2. String cmds[] = {"/bin/bash ","test.sh"+btype};
  3. System.Runtime.getRuntime().exec(cmds);

传入的参数作为bash直接执行的,此时如果作为参数传入;rm *,最后等效于

/bin/bash test.sh;rm *

  • /bin/bash –c 参数,后续输入参数为执行命令
  1.  String btype = request.getParameter("inputparam");
  2.  String cmds[] = {"/bin/bash ","-c","sh script.sh"+btype};
  3.  System.Runtime.getRuntime().exec(cmds);

此时传入的参数是用于bash执行脚本文件的,但因为使用了-c参数,使后续传入的参数用于命令执行,如果传入;rm *,最后等效于

/bin/bash –c “sh script.sh; rm *”

上述的例子提示只要在实际应用过程中,传入的参数(例如上例的sh script.sh;)是用于运行程序内部(例如sh -c,cmd.exe/k)执行命令的,execve,CreateProcessW函数就无法防护,此时用法等效于system(“sh script.sh;rm *”)函数

posted on 2018-07-11 11:47  studyskill  阅读(1558)  评论(0编辑  收藏  举报