1. sonar类型漏洞

1.1 质量阀值介绍

sonar质量阀默认阈值如下

新覆盖率小于80%

Bugs数=0

漏洞数=0

坏味道不做要求

重复代码块重复度低于3%

1.2 Bugs类型修复方案

1.2.1 Remove this call to "equals"; comparisons between unrelated types always return false.

说明:去掉equals判断语句,因为总为false

示例代码:

vpdn-broadband-user-atomservice:BroadbandUserController.java:108

Private Integer pauseReason;

Get()..

Set()..

//不应该使用空字符串再去equals判断

if(null==reqDTO.getPauseReason() || "".equals(reqDTO.getPauseReason()))

1.2.2 A"NullPointerException" could be thrown; "XXX" is nullable here.

说明:当某变量初始为null时,后面所有代码分支均未给变量重新赋值时,针对该变量的所有赋值、取值操作均需判空后进行。

错误代码示例如下

Connection conn = null;
Statement stmt = null;
try{
  conn = DriverManager.getConnection(DB_URL,USER,PASS);
  stmt = conn.createStatement();
  // ...

}catch(Exception e){
  e.printStackTrace();
}finally{
  stmt.close();   // Noncompliant; stmt could be null if an exception was thrown in the try{} block
  conn.close();  // Noncompliant; conn could be null if an exception was thrown
}

修复建议如下:

Connection conn = null;
Statement stmt = null;
try{
  conn = DriverManager.getConnection(DB_URL,USER,PASS);
  stmt = conn.createStatement();
  // ...
}catch(Exception e){
  e.printStackTrace();
}finally{

if(stmt!=null){

stmt.close();   

}

if(conn!=null){
  conn.close();

}

}

1.2.3 Use a “double” or “BigDecimal” instead.

说明:两个float计算会产生不精确的结果

解决方案:将float换为double或者BigDecimal计算

举例:

float a = 16777216.0f;

float b = 1.0f;

float c = a + b; // Noncompliant; yields 1.6777216E7 not 1.6777217E7

double d = a + b; // Noncompliant; addition is still between 2 floats

换为

float a = 16777216.0f;

float b = 1.0f;

BigDecimal c = BigDecimal.valueOf(a).add(BigDecimal.valueOf(b));

double d = (double)a + (double)b;

1.2.4 Cast one of the operands of this multiplication operation to a “long”

说明:int数运算最终再把结果转为long将有可能产生溢出

解决方案:转换为long型预算

举例:

long bigNum = Integer.MAX_VALUE + 2; // Noncompliant. Yields -2147483647

换为

long bigNum = Integer.MAX_VALUE + 2L;

1.2.5 Close this “XXX”.

说明:流没有显示的关闭。

解决方案:在fianlly语句块内关闭。

Remove or correct this useless self-assignment.

说明:缺少this

解决方案:用this.xxx=xxx来替代

举例:

public void setName(String name) {

name = name;

}

换为

public void setName(String name) {

this.name = name;

}

1.2.6 Correct this “&” to “&&”.

说明:错误的使用&和&&

解决方案:根据情况使用&和&&

1.2.7 This branch can not be reached because the condition duplicates a previous condition in the same sequence of “if/else if” statements

说明:if else if 语句判断条件重复

解决方案:去掉多余的判断条件

1.2.8 Make this “XXX” field final.

说明:将某个字段置为final,常见在Exception的参数

解决方案:将字段置为final

1.2.9 Remove this return statement from this finally block.

说明:在finally语句块中有return语句

解决方案:去掉finally语句块的return语句或者放在finally语句块之外

1.2.10 Remove this continue statement from this finally block.

说明:在finally语句块中有continue语句

解决方案:去掉finally语句块中的continue语句或者放在finally语句块之外

1.2.11 Equality tests should not be made with floating point values.

说明:浮点数之间用 == 来比较大小不准确

解决方案:用Number或者BigDecimal来比较

举例:

float myNumber = 3.146;

if ( myNumber == 3.146f ) { //Noncompliant. Because of floating point imprecision, this will be false

// …

}

if ( myNumber != 3.146f ) { //Noncompliant. Because of floating point imprecision, this will be true

// …

}

1.2.12 Add a type test to this method.

说明:强转前未判断类型

解决方案:强转前先判断类型

举例:

ErpVO ev = (ErpVO) obj;

return this.userCode.equals(ev.getUserCode());

换为

if (obj == null) {

return false;

}

if (obj.getClass() != this.getClass()) {

return false;

}

1.2.13 Add an end condition to this loop.

说明:没有退出条件

解决方案:根据情况来决定方法的退出条件

1.2.14 Make “XXX” an instance variable.

说明:有些类不是线程安全的,将变量声明为静态的可能会导致线程安全问题

解决方案:将变量声明为实例的。

1.2.15 Use isEmpty() to check whether the collection is empty or not.

解释:

建议使用list.isEmpty()方法 替代list.size()==0 或者 !list.isEmpty() 替代 list.size() >0

修改前:

if(attachedColumns.size() > 0)

修改后:

if(attachedColumns.isEmpty())

1.2.16 Remove this expression which always evaluates to “true”

解释:

建议移除变量值一直是true,没有变化的值

修改前:

boolean columnExisted = false;

boolean groupExisted = false;

for(String id : columnIds) {

    String[] idTemp = id.split("\\.");

    String groupCode = idTemp[idTemp.length == 3 ? 1 : 0];

    if(targetGroupCode.equals(groupCode)) groupExisted = true;

}

if(!columnExisted && groupExisted)

...

修改后:

boolean columnExisted = false;

boolean groupExisted = false;

for(String id : columnIds) {

    String[] idTemp = id.split("\\.");

    String groupCode = idTemp[idTemp.length == 3 ? 1 : 0];

    if(targetGroupCode.equals(groupCode)) groupExisted = true;

}

if(groupExisted)

...

1.2.17 Replace this “Map.get()” and condition with a call to “MapputeIfAbsent()”.

解释:

建议使用MapputeIfAbsent() 方法替代Map.get() 方法,computeIfAbsent()方法对 hashMap 中指定 key 的值进行重新计算,如果不存在这个 key,则添加到 hashMap 中

修改前:

List<Node> relatedReports = reportIdMappingTable.get(reportId);

if(relatedReports == null) {

    relatedReports = new LinkedList<>();

    reportIdMappingTable.put(reportId,relatedReports);

}

relatedReports.add(new Node(targetNode,report));

...

修改后:

List<Node> relatedReports =reportIdMappingTable

puteIfAbsent(reportId,

(Function<? super String, ? extends List<Node>>) new LinkedList<Node>());

relatedReports.add(new Node(targetNode,report));

...

1.2.18 Iterate over the “entrySet” instead of the “keySet”

解释:

建议使用entrySet 替代 keySet,通过查看源代码发现,调用方法keySetMap.keySet()会生成KeyIterator迭代器,其next方法只返回其key值,而调用entrySetMap.entrySet()方法会生成EntryIterator 迭代器,其next方法返回一个Entry对象的一个实例,其中包含key和value。所以当我们用到key和value时,用entrySet迭代性能更高

修改前:

public void doSomethingWithMap(Map<String,Object> map) {

  for (String key : map.keySet()) {  

    Object value = map.get(key);

  }

}

修改后:

public void doSomethingWithMap(Map<String,Object> map) {

  for (Map.Entry<String,Object> entry : map.entrySet()) {

    String key = entry.getKey();

    Object value = entry.getValue();

  }

}

1.2.19 2 duplicated blocks of code must be removed.

解释::

有2段重复代码块建议删除

1.2.20 Replace this use of System.out or System.err by a logger.

解释:

建议使用logger日志输出替代控制台打印system.out.println();

修改前:

System.out.println("My Message");

修改后:

logger.log("My Message");

1.2.21 Add a nested comment explaining why this method is empty, throw an UnsupportedOperationException or complete the implementation.

解释:

添加一个嵌套注释,解释为什么这个方法是空的,抛出UnsupportedOperationException,或者完成这个实现

修改前:

public void doSomething() {

}

修改后:

public void doSomething() {

  // Do nothing because of X and Y.

}

1.2.22 Remove the parentheses around the “x” parameter

解释:

移除最外成括号

修改前:

(x) -> x * 2

修改后:

x -> x * 2

1.2.23 Define and throw a dedicated exception instead of using a generic one.

解释:

定义并引发专用异常,而不是使用泛型异常

修改前:

public void foo(String bar) throws Throwable {  

  throw new RuntimeException("My Message");     

}

修改后:

public void foo(String bar) {

  throw new MyOwnRuntimeException("My Message");

}

1.2.24 Rename this field “LOG” to match the regular expression “[a-z][a-zA-Z0-9]*$”

解释:

重命名成符合命名规则(’[a-z][a-zA-Z0-9]*$’)的属性名称

修改前:

class MyClass {

   private int my_field;

}

修改后:

class MyClass {

   private int myField;

}

1.2.25 Define a constant instead of duplicating this literal “action1” 3 times.

解释:

建议定义个字符串常量值替代出现引用3次的字符串

修改前:

public void run() {

  prepare("action1");                           

  execute("action1");

  release("action1");

}

修改后:

private static final String ACTION_1 = "action1";  

public void run() {

  prepare(ACTION_1);                             

  execute(ACTION_1);

  release(ACTION_1);

}

1.2.26 Extract this nested ternary operation into an independent statement.

解释:

建议将此嵌套的三元操作提取到独立语句中

修改前:

public String getReadableStatus(Job j) {

  return j.isRunning() ? "Running" : j.hasErrors() ? "Failed" : "Succeeded";

}

修改后:

public String getReadableStatus(Job j) {

  if (j.isRunning()) {

    return "Running";

  }

  return j.hasErrors() ? "Failed" : "Succeeded";

}

1.2.27 Add a private constructor to hide the implicit public one.

解释:

如果一个类的里面的方法都是static修饰的静态方法,那么需要给这个类定义一个非公共构造函数(添加私有构造函数以隐藏隐式公共构造函数)

修改前:

class StringUtils {

  public static String concatenate(String s1, String s2) {

    return s1 + s2;

  }

}

修改后:

class StringUtils {

  private StringUtils() {

    throw new IllegalStateException("Utility class");

  }

  public static String concatenate(String s1, String s2) {

    return s1 + s2;

  }

}

1.3 漏洞类型修复方案

1.4 坏味道类型修复方案

1.4.1 Exception handlers should preserve the original exceptions

  • 错误示例代码:

try {
  /* ... */
} catch (Exception e) {   // Noncompliant - exception is lost
  LOGGER.info("context");
}

try {
  /* ... */
} catch (Exception e) {  // Noncompliant - exception is lost (only message is preserved)
  LOGGER.info(e.getMessage());
}

try {
  /* ... */
} catch (Exception e) {  // Noncompliant - original exception is lost
  throw new RuntimeException("context");
}

  • 修复建议如下:

try {
  /* ... */
} catch (Exception e) {
  LOGGER.info(e);  // exception is logged
}

try {
  /* ... */
} catch (Exception e) {
  throw new RuntimeException(e);   // exception stack trace is propagated
}

1.4.2 Add a private constructor to hide the implicit public one.

  • 错误示例代码如下:

class StringUtils { // Noncompliant
  public static String concatenate(String s1, String s2) {
    return s1 + s2;
  }
}

  • 修复建议如下:

class StringUtils { // Compliant
  private StringUtils() {
    throw new IllegalStateException("Utility class");
  }
  public static String concatenate(String s1, String s2) {
    return s1 + s2;
  }
}

1.4.3 x duplicated blocks of code must be removed.

重复代码块需要将重复部分归纳合并

1.4.4 Merge this if statement with the enclosing one.

  • 错误示例代码如下:

if (file != null) {
  if (file.isFile() || file.isDirectory()) {
    /* ... */
  }
}

  • 修复建议如下:

if (file != null && isFileOrDirectory(file)) {
  /* ... */
}

1.4.5 Rename "tableList" which hides the field declared at line 43

错误代码示例如下

class Foo {
  private List<String> tableList;

  public void doSomething() {
    List<String> tableList = new ArrayList<>();
    ...
  }
}

修复建议如下:

class Foo {
  // private List<String> tableList;

  public void doSomething() {
    List<String> tableList = new ArrayList<>();
    ...
  }
}

1.4.6 This block of commented-out lines of code should be removed.

//        po.setOpenDate(AisDateUtil.dateToString(new Date()));

注释掉的代码要么删除,要么取消注释

1.4.7 Remove this useless assignment to local variable "userName".

初始化且未被使用过的变量,需要直接移除,没有必要保留

错误代码示例如下

i = a + b; // Noncompliant; calculation result not used before value is overwritten
i = compute();

修复建议如下:

i = a + b;
i += compute();

1.4.8 Either log or rethrow this exception.

错误代码示例如下

try {
  /* ... */
} catch (Exception e) {   // Noncompliant - exception is lost
  LOGGER.info("context");
}

try {
  /* ... */
} catch (Exception e) {  // Noncompliant - exception is lost (only message is preserved)
  LOGGER.info(e.getMessage());
}

try {
  /* ... */
} catch (Exception e) {  // Noncompliant - original exception is lost
  throw new RuntimeException("context");
}

修复建议如下:

try {
  /* ... */
} catch (Exception e) {
  LOGGER.info(e);  // exception is logged
}

try {
  /* ... */
} catch (Exception e) {
  throw new RuntimeException(e);   // exception stack trace is propagated
}

try {
  /* ... */
} catch (RuntimeException e) {
  doSomething();
  throw e;  // original exception passed forward
} catch (Exception e) {
  throw new RuntimeException(e);  // Conversion into unchecked exception is also allowed
}

2. Fortify类型漏洞

2.1 质量阈值介绍

Critical漏洞数=0

High漏洞数=0

Medium不做要求

Low不做要求

2.2 Access Specifier Manipulation

问题描述:调用方法 setAccessible() 可更改访问说明符。

错误代码示例如下

//去除private权限,变为可更改

            field.setAccessible(true);

修复建议如下:

ReflectionUtils.makeAccessible(field);

2.3 Often Misused: Authentication

问题描述:许多 DNS 服务器都很容易被攻击者欺骗,所以应考虑到某天软件有可能会在有问题的 DNS 服务器环境下运行。如果允许攻击者进行 DNS 更新(有时称为 DNS 缓存中毒),则他们会通过自己的机器路由您的网络流量,或者让他们的 IP 地址看上去就在您的域中。勿将系统安全寄托在 DNS 名称上。

错误代码示例如下

if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {

            ip = request.getRemoteAddr();

            if (ip.equals("127.0.0.1")) {

                //根据网卡取本机配置的IP

                InetAddress inet = null;

                try {

                    inet = InetAddress.getLocalHost();

                } catch (Exception e) {

                    log.error("get IP address error [{}]", e);

                }

                ip = inet.getHostAddress();

            }

        }

修复建议如下:

1、去掉该代码

2、通过SSL方式来实现传输认证

2.4 Often Misused: Spring Web Service

问题描述:在配置文件中存储明文密码,可能会危及系统安全。

修复建议:参考章节2.13 Password Management: Password in Configuration

2.5 Null Dereference

当违反程序员的一个或多个假设时,通常会出现null 指针异常。如果程序明确将对象设置为 null,但稍后却间接引用该对象,则将出现 dereference-after-store 错误。此错误通常是因为程序员在声明变量时将变量初始化为 null。在这种情况下,在第 443 行间接引用该变量时,变量有可能为 null,从而引起 null 指针异常。 大部分空指针问题只会引起一般的软件可靠性问题,但如果攻击者能够故意触发空指针间接引用,攻击者就有可能利用引发的异常绕过安全逻辑,或致使应用程序泄漏调试信息,这些信息对于规划随后的攻击十分有用。

错误代码示例:

public void test6(){

        String[] arrays = null;

        ArrayList list = new ArrayList();

        if (list != null && list.size() != 0) {

            arrays = new String[list.size()];

        }

        arrays[0] = "str";

    }

Foo foo = null;...

foo.setBar(val);

}

修复建议:

对可能存在 null 的引用进行 null 判断,确保程序能够正常执行。

public void test7(){

        String[] arrays = null;

        ArrayList list = new ArrayList();

        if (list != null && list.size() != 0) {

            arrays = new String[list.size()];

        }

        if(arrays != null){

            arrays[0] = "str";

        }

    }

2.6 Insecure Randomness

标准的伪随机数值生成器不能抵挡各种加密攻击。

错误代码示例:

js代码:

function genReceiptURL (baseURL){

  var randNum = Math.random();

  var receiptURL = baseURL + randNum + ".html";

  return receiptURL;

}

Java代码:

public void randomTest(){

        int i = new Random().nextInt();

    }

修复建议如下:

1、安全要求不高

Math.random()常规函数,无需修改。

2、js解决方案

不使用Math.random(),采用自定义随机函数,代码如下:

function rnd( seed ){

    seed = ( seed * 9301 + 49297 ) % 233280; //这三个数很特别哦

    return seed / ( 233280.0 );

};

//随机数生成函数

function rand(number){

    today = new Date();

    seed = today.getTime();

    return Math.ceil( rnd( seed ) * number );

};

function genReceiptURL (baseURL){

     var randNum = (rand(99999999));

     var receiptURL = baseURL + randNum + ".html";

     return receiptURL;

}

3、java解决方案

pom.xml

<dependency>

            <groupId>clojure-interop</groupId>

            <artifactId>java.security</artifactId>

        </dependency>

public void randomTest(){

        int i = new SecureRandom().nextInt();

    }

2.7 J2EE Bad Practices: Non-Serializable Object Stored in Session

问题描述:

一个 J2EE 应用程序可以利用多个JVM,以提高应用程序的可靠性和性能。为了在最终用户中将多个JVM 显示为单个的应用程序,J2EE 容器可以在多个JVM 之间复制 HttpSession 对象,所以当一个JVM 不可用时,另一个 JVM 可以在不中断应用程序流程的情况下接替步骤的执行。
为了使会话复制能够正常运行,作为应用程序属性存储在会话中的数值必须实现 Serializable 接口。

错误代码示例:
public class DataGlob {
String globName;
String globValue;
public void addToSession(HttpSession session) {
session.setAttribute("glob", this);
}
}

修复建议如下:

public class DataGlob  implements java.io.Serializable{
String globName;
String globValue;
public void addToSession(HttpSession session) {
session.setAttribute("glob", this);
}
}

2.8  File Disclosure:J2EE

问题描述:在以下情况下,会发生文件泄露:

1、数据从一个不可信赖的数据源进入程序。

2、数据用于动态地构造一个路径。

错误代码示例:

public void test(HttpServletRequest request,HttpServletResponse response){

        try{

            String filedownload = request.getParameter("filedownload");//即将下载的文件的相对路径

            String filedisplay = request.getParameter("filedisplay");//下载文件时显示的文件保存名称

            filedisplay = URLEncoder.encode(filedisplay,"utf-8");

            response.addHeader("Content-Disposition","attachment;filename=" + filedisplay);

            RequestDispatcher dis = request.getRequestDispatcher(filedownload);

        }catch (Exception e){

            e.printStackTrace();

        }

    }

修复建议:

使用文件服务地址替换文件路径,即前台只传入服务地址,不直接传递文件路径。

public void test6(HttpServletRequest request,HttpServletResponse response){

        try{

            //下载文件服务路径,非直接的文件地址路径

            String filedownloadUrlPath = "http://localhost:8080/download/";

            //下载文件时显示的文件保存名称

            String filedisplay = request.getParameter("filedisplay");

            filedisplay = URLEncoder.encode(filedisplay,"utf-8");

            response.addHeader("Content-Disposition","attachment;filename=" + filedisplay);

            RequestDispatcher dis = request.getRequestDispatcher(filedownloadUrlPath);

        }catch (Exception e){

            e.printStackTrace();

        }

    }

2.9 Unreleased resource : Streams

资源泄露至少有两种常见的原因:

  • 1、错误状况及其他异常情况。
  • 2、未明确程序的哪一部份负责释放资源。

错误代码示例:

public void test(){

        PrintStream printStream= null;

        try

        {

            printStream = new PrintStream(new FileOutputStream("out.txt"));//

            System.setOut(printStream);//将输出的流指定到ps流

        }

        catch(Exception ex)

        {

            //solve error

        }

    }

修复建议

在 finally 代码段中释放资源。

public void test(){

        PrintStream printStream= null;

        try

        {

            printStream = new PrintStream(new FileOutputStream("out.txt"));

            System.setOut(printStream);//将输出的流指定到ps流

        }

        catch(Exception ex)

        {

            //solve error

        }

        finally {

            if(printStream != null){

                printStreamClose(printStream);

            }

        }

    }

2.10 Log Forging

问题描述:文件中的方法将未验证的用户输入写入日志。攻击者可以利用这一行为来伪造日志条目或将恶意内容注入日志。

错误代码示例

public void test(HttpServletRequest request, HttpServletResponse response){

        String val = request.getParameter("val");

        try {

              int value = Integer.parseInt(val);

        }

        catch (NumberFormatException nfe) {

            LOG.info("Failed to parse val = " + val);

        }

    }

修复建议

1、定义合法日志条目

public void test3(HttpServletRequest request, HttpServletResponse response){

        String errorLog = "Failed to parse val. The input is required to be an integer value.";

        String val = request.getParameter("val");

        try {

              int value = Integer.parseInt(val);

        }

        catch (NumberFormatException nfe) {

            LOG.info(errorLog);

        }

    }

2、白名单过滤日志条目

public void test2(HttpServletRequest request, HttpServletResponse response){

        String val = request.getParameter("val");

        try {

              int value = Integer.parseInt(val);

        }

        catch (NumberFormatException nfe) {

            LOG.info("Failed to parse val = " + StrUtils.pathManipulation(val));

        }

    }

StrUtils.pathManipulation 方法:

    public static String pathManipulation(String path) {
        boolean isValid = true;
        HashMap<String, String> map = new HashMap<String, String>();
        map.put("a", "a");
        map.put("b", "b");
        map.put("c", "c");
        map.put("d", "d");
        map.put("e", "e");
        map.put("f", "f");
        map.put("g", "g");
        map.put("h", "h");
        map.put("i", "i");
        map.put("j", "j");
        map.put("k", "k");
        map.put("l", "l");
        map.put("m", "m");
        map.put("n", "n");
        map.put("o", "o");
        map.put("p", "p");
        map.put("q", "q");
        map.put("r", "r");
        map.put("s", "s");
        map.put("t", "t");
        map.put("u", "u");
        map.put("v", "v");
        map.put("w", "w");
        map.put("x", "x");
        map.put("y", "y");
        map.put("z", "z");
        map.put("A", "A");
        map.put("B", "B");
        map.put("C", "C");
        map.put("D", "D");
        map.put("E", "E");
        map.put("F", "F");
        map.put("G", "G");
        map.put("H", "H");
        map.put("I", "I");
        map.put("J", "J");
        map.put("K", "K");
        map.put("L", "L");
        map.put("M", "M");
        map.put("N", "N");
        map.put("O", "O");
        map.put("P", "P");
        map.put("Q", "Q");
        map.put("R", "R");
        map.put("S", "S");
        map.put("T", "T");
        map.put("U", "U");
        map.put("V", "V");
        map.put("W", "W");
        map.put("X", "X");
        map.put("Y", "Y");
        map.put("Z", "Z");
        //map.put(":", ":");
        //map.put("/", "/");
        //map.put("\\", "\\");
        //map.put(".", ".");
        map.put("-", "-");
        map.put("_", "_");
        map.put("0", "0");
        map.put("1", "1");
        map.put("2", "2");
        map.put("3", "3");
        map.put("4", "4");
        map.put("5", "5");
        map.put("6", "6");
        map.put("7", "7");
        map.put("8", "8");
        map.put("9", "9");
        String temp = "";
        for (int i = 0; i < path.length(); i++) {
            if (map.get(path.charAt(i) + "") != null) {
                temp += map.get(path.charAt(i) + "");
            }
        }
        return temp;
    }

2.11 XML Entity Expansion Injection

问题描述:代码中配置的 XML 解析器无法预防和限制文档类型定义 (DTD) 实体解析。这会使解析器暴露在 XML Entity Expansion injection 之下。

错误代码示例

public static void test() {

        try{

            DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();

            factory.setFeature("http://xml/sax/features/external-general-entities", false);

            factory.setFeature("http://xml/sax/features/external-parameter-entities", false);

            DocumentBuilder builder = factory.newDocumentBuilder();            

            Document doc = builder.parse(new File("file"));

            doc.normalize();

        }catch(Exception e){

            e.printStackTrace();

        }

    }

上面代码 builder.parse(new File(“file”)); 解析 xml 文件时,未进行安全限制,存在安全风险。

此测试可以在内存中将小型 XML 文档扩展到超过 3GB 而使服务器崩溃。

修复建议

为 XML 代理、解析器或读取器设置“secure-processing”属性:

public static void test2() {

        try{

            DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();

            factory.setFeature("http://xml/sax/features/external-general-entities", false);

            factory.setFeature("http://xml/sax/features/external-parameter-entities", false);

            factory.setFeature("http://javax.xml.XMLConstants/feature/secure-processing", true);

            DocumentBuilder builder = factory.newDocumentBuilder();            

            Document doc = builder.parse(new File("file"));

            doc.normalize();

        }catch(Exception e){

            e.printStackTrace();

        }

    }

2.12 SQL Injection: MyBatis Mapper

问题描述:SQL injection 错误在以下情况下发生:

1. 数据从一个不可信赖的数据源进入程序。

2. 数据用于动态地构造一个 SQL 查询。

错误代码示例

VpdnAuthRecordMapper.xml

SELECT * FROM ${tableName}

        <if test="sortName != null and sortName != ''">

            order by ${sortName}

        </if>

        <if test="sortOrder != null and sortOrder != ''">

            ${sortOrder}

        </if>

修复建议

1、穷举所有字段

修改VpdnAuthRecordMapper.xml文件,添加白名单校验,根据表结构字段穷举sortName字段,防止SQL拼接注入,示例如下:

<if test="sortName != null and sortName=='name1'.toString()">

    order by name1

</if>

<if test="sortName != null and sortName=='name2'.toString()">

    order by name2

</if>

<if test="sortName != null and sortName=='name3'.toString()">

    order by name3

</if>

<if test="sortOrder !=null and sortOrder=='desc'.toString()">

    desc

</if>

<if test="sortOrder !=null and sortOrder=='asc'.toString()">

    asc

</if>

2、代码层面添加过滤器,过滤相关字符

参数如果是从外部传入,可通过添加过滤器来过滤相关敏感字符:如:select、update、delete等

${tableName}因业务需求,可以暂时不改,保持原来的逻辑就可以

${sortName}${sortOrder}是外部传参过来的,需要添加过滤器校验参数是否包含敏感字符

2.13 Key Management: Hardcoded Encryption Key

问题描述:Hardcoded password 可能会危及系统安全性,并且无法轻易修正出现的安全问题。

错误代码示例

try{

            DriverManager.getConnection("localhost", "scott", "tiger");

        }catch (Exception e){

            e.printStackTrace();

        }

修复建议

敏感属性信息存储到数据库,并且加密存储,程序通过配置工具类获取属性。

敏感属性信息命名不要使用常用的password、username等一眼就知道意思的命名。

try{

            String url = PropertyTools.getProperty("url");

            String userName = PropertyTools.getProperty("un");

            String password = PropertyTools.getProperty("pw");

            DriverManager.getConnection(url, userName, password);

        }catch (Exception e){

            e.printStackTrace();

        }

2.14 Password Management: Password in Configuration

问题描述:在配置文件中存储明文密码,可能会危及系统安全。

错误代码示例

application.properties文件

//url

tiger.database.url=56923ed16ee1ae39a9cc6f26ad2f42c6

//userName

tiger.database.userName=43b90920409618f188bfc6923f16b9fa

//password

tiger.database.password=43b90920409618f188bfc6923f16b9fa

修复建议

敏感属性信息不能保存在配置中,需要加密保存到数据库或者自定义一套保存机制,通常都是加密保存到数据库。

2.15 Password Management: Empty Password

问题描述:使用空字符串作为密码是不安全的做法。

错误代码示例:

String passwordTypeName="";

修复建议:

密码等敏感信息不能为空。

密码等敏感信息不能保存在配置文件中。

或者更改密码等敏感信息命名。

String passwordTypeName="XXXXXXXX";

2.16 Password Management: Hardcoded Password

问题描述:Hardcoded password 可能会危及系统安全性,并且无法轻易修正出现的安全问题。

错误代码示例

String PASSWORD_INVALID="密码格式不正确";

String PASSWORD_NOT_EMPTY = "密码不能为空";

String PASSWORD_PATTERN = "密码不合法";

String PASSWORD_TYPE_IN_ENUM = "静态密码加密方式不合法";

String PASSWORD_FLAG_IN_ENUM = "动态密码标识不合法";

String TMP_PWD_EXP_DATE_FUTURE = "临时密码失效日期必须大于当前日期";

修复建议

1、如果只是参数命名携带了“PASSWORD”“PWD”“KEY”等关键词,可以不用修复

2、如果是真实的密码字段:

敏感属性信息存储到数据库,并且加密存储,程序通过配置工具类获取属性。

敏感属性信息命名不要使用常用的password、username等一眼就知道意思的命名。

2.17 Path Manipulation

问题描述:攻击者可以控制文件系统路径参数,借此访问或修改其他受保护的文件。

错误代码示例

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

public void pathManipulation(HttpServletRequest request){

        try{

            String rName = request.getParameter("reportName");

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

            rFile.delete();

        }catch (Exception e){

            e.printStackTrace();

        }

    }

修复建议

对需要重定向的 URL 进行校验

public void pathManipulation(HttpServletRequest request){

        try{

            //原始路径

            String rName = request.getParameter("reportName");

            //处理后路径

            String validateName = StrUtils.pathManipulation(rName);

            //不帶路径

            if(validateName.equals(rName)){

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

                rFile.delete();

            }else{

                //throw error

            }

        }catch (Exception e){

            e.printStackTrace();

        }

    }

2.18 Portability Flaw: File Separator

问题描述:代码可能会导致可移植性问题,因为它使用硬编码文件分隔符。不同的操作系统使用不同的字符作为文件分隔符。例如,Microsoft Windows 系统使用“\”,而 UNIX 系统则使用“/”。应用程序需要在不同的平台上运行时,使用硬编码文件分隔符会导致应用程序逻辑执行错误,并有可能导致 denial of service。

错误代码示例

public void fileSeparator(HttpServletRequest request){

        try{

            String directoryName = "directoryName";

            String fileName = "fileName.jpg";

            File file = new File(directoryName + "\\" + fileName);

        }catch (Exception e){

            e.printStackTrace();

        }

    }

修复建议

使用独立于平台的 API(File.separator)来指定文件分隔符。

public void fileSeparator2(HttpServletRequest request){

        try{

            String directoryName = "directoryName";

            String fileName = "fileName.jpg";

            File file = new File(directoryName + File.separator + fileName);

        }catch (Exception e){

            e.printStackTrace();

        }

    }

2.19 Race Condition: Singleton Member Field

问题描述:许多 Servlet 开发人员都不了解 Servlet 为单例模式。Servlet 只有一个实例,并通过使用和重复使用该单个实例来处理需要由不同线程同时处理的多个请求。

这种误解的共同后果是,开发者使用 Servlet 成员字段的这种方式会导致某个用户可能在无意中看到其他用户的数据。换言之,即把用户数据存储在 Servlet 成员字段中会引发数据访问的 race condition。

错误代码示例

//单例属性

    String name;

    protected void doPost (HttpServletRequest req, HttpServletResponse res) {

        name = req.getParameter("name");

        System.out.println(name + ", thanks for visiting!");

    }

当该代码在单一用户环境中正常运行时,如果有两个用户几乎同时访问 Servlet,可能会导致这两个请求以如下方式处理线程的插入:

Plaintext
线程 1    将“Dick”分配给 name
    线程 Jane”分配给 name
    线程 1    printJane, thanks for visiting!”
    线程 2    printJane, thanks for visiting!”

因此会向第一个用户显示第二个用户的用户名。

方案一

不要为任何参数(常量除外)使用 Servlet 成员字段,把变量放到方法里面。

Plaintext
public class GuestBook extends HttpServlet {

    protected void doPost (HttpServletRequest req, HttpServletResponse res) {
        //单例属性
        String name = req.getParameter("name");
        System.out.println(name + ", thanks for visiting!");
    }
}

方案二

新增一个类,来进行变量的操作,确保每个请求都有一个单独的类,这样变量就不会混乱了。

Plaintext
public class ServletDomain {

    private String name;

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

public class GuestBook2 extends HttpServlet {

    protected void doPost (HttpServletRequest req, HttpServletResponse res) {
        ServletDomain servletDomain = new ServletDomain();
        servletDomain.handle(req, res);
    }
}

方案三

使用同步来确保参数不会被别的线程修改,但是经过测试,<span style="color:red;font-size:20px;">不建议该方案</span>,因为同步会影响系统性能,同时同步后,还是会提示 Race Condition: Singleton Member Field 问题。

Plaintext
public class GuestBook extends HttpServlet {

   String name;

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

2.20 System Information Leak: External

问题描述:当系统数据或调试信息通过套接字或网络连接使程序流向远程机器时,就会发生外部信息泄露。外部信息泄露会暴露有关操作系统、完整路径名、现有用户名或配置文件位置的特定数据,从而使攻击者有机可乘,它比内部信息(攻击者更难访问)泄露更严重。

错误代码示例

以下代码泄露了 HTTP 响应中的异常信息:

protected void doPost (HttpServletRequest req, HttpServletResponse res) throws IOException {

        PrintWriter out = res.getWriter();

        try {

            //...业务

        } catch (Exception e) {

            out.println(e.getMessage());

        }

    }
修复建议

对于返回到界面的信息一定要慎重,尽量使用错误码及可控的异常信息,避免抛出直接的异常日志。

Plaintext
protected void doPost (HttpServletRequest req, HttpServletResponse res) throws IOException {
        PrintWriter out = res.getWriter();
        try {
            //...业务
        } catch (Exception e) {
            out.println("{errorCode:003,errorMessage:\"系统异常\"}");
        }
    }

2.21 Access Control: Database

问题描述:揭示系统数据或调试信息有助于攻击者了解系统并制定攻击计划。

错误代码示例

public BasicRespDTO modify(RoleMenuModifyReqDTO roleMenuModifyReqDTO) {

        String sid = roleMenuModifyReqDTO.getSid();

        log.info("[{}],RoleService,modifyRoleMenu response [{}]", sid, roleMenuModifyReqDTO.toString());

        BasicRespDTO respDTO = new BasicRespDTO();

        respDTO.setSid(sid);

        respDTO.setResultCode(ResultCodeEnum.SUCCESS.getCode());

        respDTO.setResultDesc(ResultCodeEnum.SUCCESS.getDesc());

        if (isRoleExist(roleMenuModifyReqDTO.getRoleId())) {

            RoleMenuPO roleMenuPO = new RoleMenuPO();

            AisBeanUtil.copyBean(roleMenuModifyReqDTO, roleMenuPO);

            List<RoleMenuPO> roleMenuDelList = roleMenuDAO.select(roleMenuPO);

            Integer roleId = roleMenuModifyReqDTO.getRoleId();

            String menuList = roleMenuModifyReqDTO.getMenuIdList();

            List<RoleMenuPO> roleMenuAddList = new ArrayList<RoleMenuPO>();

            if (null != menuList && !"".equals(menuList)) {

                // 构造新角色授权列表

                for (String menuId : menuList.split(",")) {

                    RoleMenuPO roleMenu = new RoleMenuPO();

                    roleMenu.setRoleId(roleId);

                    roleMenu.setMenuId(menuId);

                    roleMenuAddList.add(roleMenu);

                }

            }

            if (null != roleMenuDelList && !roleMenuDelList.isEmpty()) {

                for (RoleMenuPO trm : roleMenuDelList) {

                    if (!roleMenuDAO.delete(trm)) {

                        respDTO.setResultCode(ResultCodeEnum.DATAINDB_ERROR.getCode());

                        respDTO.setResultDesc(ResultCodeEnum.DATAINDB_ERROR.getDesc());

                    }

                }

            }

            if (!roleMenuAddList.isEmpty()) {

                for (RoleMenuPO trm : roleMenuAddList) {

                    if (!roleMenuDAO.insert(trm)) {

                        respDTO.setResultCode(ResultCodeEnum.DATAINDB_ERROR.getCode());

                        respDTO.setResultDesc(ResultCodeEnum.DATAINDB_ERROR.getDesc());

                    }

                }

            }

        } else {

            respDTO.setResultCode(ResultCodeEnum.ROLE_NOEXIST.getCode());

            respDTO.setResultDesc(ResultCodeEnum.ROLE_NOEXIST.getDesc());

        }

        log.info("[{}],RoleService,modifyRoleMenu response [{}]", sid, respDTO.toString());

        return respDTO;

    }

修复建议

相关SQL需添加当前用户账号条件进行关联查询

...
userName = ctx.getAuthenticatedUserName();
id = Integer.decode(request.getParameter("invoiceID"));
String query =
"SELECT * FROM invoices WHERE id = ? AND user = ?";
PreparedStatement stmt = conn.prepareStatement(query);
stmt.setInt(1, id);
stmt.setString(2, userName);
ResultSet results = stmt.execute();
...

2.22 Dynamic Code Evaluation: UnsafeDeserialization

问题描述:Java 序列化会将对象图转换为字节流(包含对象本身和必要的元数据),以便通过字节流进行重构。开发人员可以创建自定义代码,以协助 Java 对象反序列化过程,在此期间,他们甚至可以使用其他对象或代理替代反序列化对象。在对象重构过程中,并在对象返回至应用程序并转换为预期的类型之前,会执行自定义反序列化过程。到开发人员尝试强制执行预期的类型时,代码可能已被执行。

在必须存在于运行时类路径中且无法由攻击者注入的可序列化类中,会自定义反序列化例程,所以这些攻击的可利用性取决于应用程序环境中的可用类。令人遗憾的是,常用的第三方类,甚至 JDK 类都可以被滥用,导致 JVM 资源耗尽、部署恶意文件或运行任意代码。

解决方案:如果可能,在没有验证对象流的内容的情况下,请勿对不可信数据进行反序列化。为了验证要进行反序列化的类,应使用前瞻反序列化模式。

对象流首先将包含类描述元数据,然后包含其成员字段的序列化字节。Java 序列化过程允许开发人员读取类描述,并确定是继续进行对象的反序列化还是中止对象的反序列化。为此,需要在应执行类验证和确认的位置,子类化java.io.ObjectInputStream 并提供resolveClass(ObjectStreamClass desc)方法的自定义实现。

已有易于使用的前瞻模式实现方式,例如 Apache Commons IO (org.apachemons.io.serialization.ValidatingObjectInputStream)。始终使用严格的白名单方法,以仅允许对预期类型进行反序列化。不建议使用黑名单方法,因为攻击者可以使用许多可用小工具绕过黑名单。此外,请谨记,尽管用于执行代码的某些类已公开,但是还可能存在其他未知或未公开的类,因此,白名单方法始终都是首选方法。应审计白名单中允许的任何类,以确保对其进行反序列化是安全的。

为避免 Denial of Service,建议您覆盖 resolveObject(Objectobj) 方法,以便计算要进行反序列化的对象数量,并在超过阈值时中止反序列化。


在库或框架(例如,使用 JMX、RMI、JMS、HTTP Invoker 时)中执行反序列化时,上述建议并不适用,因为它超出了开发人员的控制范围。在这些情况下,您可能需要确保这些协议满足以下要求:

- 未公开披露。
- 使用身份验证。
- 使用完整性检查。
- 使用加密。

此外,每当应用程序通过ObjectInputStream 执行反序列化时,HPE Security Fortify Runtime(HPE Security Fortify 运行时)都会提供要强制执行的安全控制,以此同时保护应用程序代码以及库和框架代码,防止遭到此类攻击。

2.23 Insecure Transport: MailTransmission

问题描述:通过未加密网络发送的敏感数据容易被任何可拦截网络通信的攻击者读取/修改。

解决方案:大多数现代邮件服务提供商提供了针对不同端口的加密备选方案,可使用 SSL/TLS 对通过网络发送的所有数据进行加密,或者将现有的未加密连接升级到 SSL/TLS。如果可能,请始终使用这些备选方案

2.24 Privacy Violation: Heap Inspection

问题描述:敏感信息不要一直放在内存中,使用完后删除

错误代码示例

public String decrypt(String outblock) throws CipherException {

        byte[] tmp = this.decrypt(AisHexUtil.hex2Bytes(outblock));

        return (new String(tmp, this.charset)).trim();

    }

修复建议如下

private JPasswordField pf;
...
final char[] password = pf.getPassword();
// use the password
...
// erase when finished
Arrays.fill(password, ' ');

2.25 Privacy Violation

问题描述:Privacy Violation 会在以下情况下发生

1. 用户私人信息进入了程序。

2. 数据被写到了一个外部介质,例如控制台、file system 或网络。

错误代码示例

String val = AisDes3Util.decrypt(key);

   if (val == null) {

     log.error("Api key decrypt fail, forbidden access! api key:{}", val);

   return dto;

}

修复建议

敏感数据不要写入日志文件,可以入库

2.26 Weak Encryption

问题描述:使用了弱加密算法

错误代码示例

public byte[] encrypt(byte[] src) throws CipherException {

        try {

            SecretKey deskey = new SecretKeySpec(this.secretKey, ALGORITHM);

            Cipher cipher = Cipher.getInstance(this.cipherAlgorithm);

            cipher.init(1, deskey);

            byte[] data = src;

            if (this.cipherAlgorithm.equals(CIPHER_NOPADDING)) {

                data = this.fixBytesBlock(src);

            }

            return cipher.doFinal(data);

        } catch (Exception var5) {

            throw new CipherException("DES3 Encrypt Exception , ", var5);

        }

    }

修复建议:

升级加密算法,如AES、BASE64等

未完待续。。。

更多推荐

Sonar&&Fortify漏洞修复