본문 바로가기
소프트웨어(SW) 보안약점 진단원/구현단계 보안약점 제거 기준

구현단계 보안약점 기준 - Null Pointer 역참조

by 브루노W 2025. 6. 4.
유형 코드오류
보안약점 Null Pointer 역참조
개요
널 포인터(Null Pointer) 역참조는 '일반적으로 그 객체가 널(Null)이 될 수 없다'라고 하는 가정을 위반했을 때 발생한다.
 
공격자가 의도적으로 널 포인터 역참조를 발생시키는 경우, 그 결과 발생하는 예외 상황을 이용하여 추후의 공격을 계획하는 데 사용될 수 있다.
보안대책
널이 될 수 있는 레퍼런스(Reference)는 참조하기 전에 널 값인지를 검사하여 안전한 경우에만 사용한다.
진단방법
표현된 객체가 널(Null) 값이 될 수 있는지 확인한다.
만약 널(Null) 값이 될 수 있는지 체크하여 예외처리를 한 경우 안전으로 판단하고 널(Null) 체크를 하지 않은 경우 취약한 것으로 판단한다.
연관된 설계단계 기준 -

 

코드예제

 

● 안전하지 않은 코드 예 (Java)

public static int cardinality (Object obj, final Collection col) {
    int count = 0;
    if (col == null) {
    	return count;
    }
    Iterator it = col.iterator();
    while (it.hasNext()) {
        Object elt = it.next();
        // obj가 null이고 elt가 null이 아닐 경우, Null.equals 가 되어 널(Null) 포인터 역참조가 발생한다.
        if ((null == obj && null == elt) || obj.equals(elt)) {
        	count++;
        }
    }
    return count;
}

 

● 안전한 코드 예 (Java)

public static int cardinality (Object obj, final Collection col) {
    int count = 0;
    if (col == null) {
    	return count;
    }
    Iterator it = col.iterator();
    while (it.hasNext()) {
        Object elt = it.next();
        // obj가 null이 아닌 경우에만 obj.equal를 실행한다.
        if ((null == obj && null == elt) || (null != obj && obj.equals(elt))) {
            count++;
        }
    }
    return count;
}

 

 

● 안전하지 않은 코드 예 (Java)

String url = reuqest.getParamter("url");
// url 에 null이 들어오면 널(Null) 포인터 역참조가 발생한다.
if (url.equals(""))

 

● 안전한 코드 예 (Java)

String url = reuqest.getParamter("url");
// null값을 가지는 참조 변수를 사용할 경우, null 검사를 수행하고 사용한다.
if ( url != null || url.equals("") )

 

 

● 안전하지 않은 코드 예 (C#)

protected void Page_Load(object sender, EventArgs e) {
    // url 파라미터에 name이 없으면 username은 null 값을 가지게 된다.
    string username = Request.QueryString["name"];
    // null 값을 가지는 username을 참조하여 널(Null) 포인터 역참조가 발생한다.
    if (username.Length > 20) {
    	// length error
    }
}

 

● 안전한 코드 예 (C#)

protected void Page_Load(object sender, EventArgs e) {
    // url 파라미터에 name 이 없으면 username은 null 값을 가지게 된다.
    string username = Request.QueryString["name"];
    // null 값을 가지는 username을 참조하기 전에 null 검사를 수행하므로 안전하다.
    if ( username != null && username > 20) {
    	// length error
    }
}

 

 

● 안전하지 않은 코드 예 (C)

void NullPointerDereference(int count) {
    // IntegerAddressReturn()이 0을 return 하면 p는 null 값을 가지게 된다.
    int *p = IntegerAddressReturn();
    // null 값을 가지는 p 값을 참조하여 널(Null) 포인터 역참조가 발생한다.
    *p = count;
}

 

● 안전한 코드 예 (C)

void NullPointerDereference(int count) {
    // IntegerAddressReturn()이 0을 return 하면 p는 null 값을 가지게 된다.
    int *p = IntegerAddressReturn();
    // 참조하기전에 null 검사를 수행하므로 안전하다.
    If(p != 0) *p = count;
}

 

 

진단방법
 
표현된 객체가 널(Null) 값이 될 수 있는지 확인한다. 만약 널(Null) 값이 될 수 있는지 체크하여 예외처리를 한 경우 안전으로 판단하고 널(Null) 체크를 하지 않은 경우 취약한 것으로 판단한다. (①)

 

 

● 일반적인 진단의 예

…
public void f() {
    String cmd = System.getProperty("cmd");
    // cmd가 널(null)인지 체크하지 않았다.
    cmd = cmd.trim();
    System.out.println(cmd); ···························①
    …
}

 

 

※ (참고) 널(Null) 값이 될 가능성은 다음의 기준으로 확인

- 초기값이 널(Null)로 할당된 값은 널(Null)이 될 수 있음

- 선언이 된 후 객체가 생성되지 않은 값은 널(Null)이 될 수 있음

- 널(Null)인 객체로 필드 접근을 하거나 널(Null)인 객체에 메소드 호출을 할 경우 결과 값은 널(Null)이 될 수 있음

- 문자열 등 '널(Null)이 될 수 있는(Nullable)' 객체들의 연산 결과 값은 널(Null)이 될 수 있음

 

 

● 정탐코드

public Hashtable deleteAll(Connection con, GenericModel model) throws Exception {
    Hashtable<String, Object> m = new Hashtable<String, Object>();
    VMResultSet vmrs = null;
    int rValue = 0;
    try {
        ArrayList grid =null ;
        ….
        con.commit();
        con.setAutoCommit(true);
        if(rValue > 0) {
            vmrs = dao.listGoodKnowQuestion(con, model);
            vmrs.setMessage(UtilMsg.getInstance().getMsg("SUC003"));
        } else {
        	vmrs.setMessage(UtilMsg.getInstance().getMsg("ERR000"));
        }
        m.put("result", vmrs);
    } catch (Exception e) {
    	….
    } finally {
    	….
    }
    return m;
}

널(Null)로 초기화 한 이후에 널(Null)값인 vmrs 값을 참조하고 있다. 이 경우는 조건문 등에 의해 분기가 이루어지면서 널(Null)로 초기화된 변수가 초기화되지 않는 경우이다.

 

 

● 정탐코드

private static void makeList() {
    Connection con = null;
    StringBuffer sql = new StringBuffer();
    PreparedStatement statement = null;
    ResultSet rs = null;
    list.clear();
    try {
        con = GenericDAO.getDataSource().getConnection();
        ……..
        statement = con.prepareStatement(sql.toString());
        rs = statement.executeQuery();
        while (rs.next()) {
            String authGrp = rs.getString("AUTHORITY_GROUP");
            ……
        }
    } catch (Exception e) {
        if (verbose) {
        	………
        }
    } finally {
        try {
        	rs.close();
        } catch (Exception e1) {}
            try {
            	statement.close();
            } catch (Exception e1) {}
            	if (con != null) {
            		try {
            			con.close();
            		} catch (SQLException e) {
            			e.printStackTrace();
            		}
            	}
        	}
    	}
    }

Statement를 널(Null)로 초기화하고 있고 예외가 발생하면 Statement의 값을 대입하지 못하고 finally 문으로 분기한 후 실행되면서 널(Null) 값을 참조하게 된다.

 

 

● 정탐코드

[program_view.jsp]
…
String strContent = UTIL.enter2br((String) hsROW.get("content"));
String linkPage= "";
if (!strContent.equals("")) {
	linkPage = strContent.replaceAll("상세보기", "<img src='' ">);
	…

[Util.java]
public static String enter2br(String data) {
    if(data == null)
        return null;
    StringTokenizer st = new StringTokenizer(data, "\n");
    …
}

enter2br()에서 data가 널(Null)일 경우 널(Null)을 리턴하기 때문에 strContent가 널(Null) 값을 가질 가능성이 존재한다. 따라서 널(Null) Pointer 역참조가 발생할 수 있기 때문에 취약하다고 판정한다.

 

 

● 정탐코드

if ( str.length() == 0 || str == null )

널(Null) 검사를 수행하였으나, 조건문에서 널(Null) 객체를 참조하는 구문을 먼저 비교하여 널(Null) Pointer 역참조가 발생하여 취약하다고 판정한다.

 

 

Interface Iteration은 널(Null)을 리턴하지 않는데 널(Null) 포인터 역참조를 탐지해내는 경우나, 함수의 설계상 널(Null)을 리턴하지 않도록 정의되어 있는 함수의 리턴 값을 참조할 경우에는 널(Null) 포인터 역참조가 발생하지 않는다.

이를 확인하기 위해서는 함수의 Java document를 유의 깊게 살펴보아야 한다. 함수의 리턴 값에 대한 기술되어 있는 Javadoc을 참조해서 코드를 검토하여야 한다.

 

 

● 오탐코드

String Tokenizer st = new String Tokenizer(info_url,"?");
int sti=0;
while (st.hasMoreTokens()) {
    if(sti==0)
    	jsonUrl=st.nextToken();
    if(sti==1)
    	jsparam=st.nextToken();
    sti++;
}
String [] jsonParams = jsparam.split("&");
jsparam="";
for (int i=0;i<jsonParams.length;i++) {
    jsparam=jsparam+jsonParams[i].split("=")[0]+": '"+jsonParams[i].split("=")[1]+"'";
    if(i!=jsonParams.length-1) jsparam=jsparam+",";
}

 

 

● 오탐코드

public static void testFunction(String dateStr1) throws Exception {
    SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd", Locale.getDefault());
    Date date1 = null;
    try {
        date1 = sdf.parse(dateStr1);
    } catch (ParseException e) {
        System.err.println("Inner Function");
        e.printStackTrace();
        throw new Exception(e);
    }
    System.out.println("After Throw 1");
    int days1 = (int)((date1.getTime()/100000)/24);
    System.out.println("After Throw 2");
}

try ~ catch 문에서 throw를 하는 경우 catch 절 이후는 실행되지 않음으로 널(Null) 포인터 역참조가 발생하지 않는다. System.out.println("After Throw 1");은 실행되지 않는다.

 

 

● 오탐코드

SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd",Locale.getDefault());
Date date1 = null;
try {
	date1 = sdf.parse(dateStr1);
} catch (ParseException e) {
	System.err.println("Inner Function");
	e.printStackTrace();
}
System.out.println("After Throw 1");
intdays1 = (int)((date1.getTime()/100000)/24);
intdays2 = (int)((date1.getTime()/100000)/24);
System.out.println("After Throw 2");

intdays1 = (int)((date1.getTime()/100000)/24);에서 date1이 널(Null)일 경우, 널 (Null) 포인터 역참조가 발생하고 다음 단계로 진행되지 않게 된다. 이러한 이유로 intdays2 = (int) ((date1.getTime()/100000)/24); 문은 실행되지 않으며 널(Null)을 참조하지 않게 된다.

 

 

● 오탐코드

if (a == null || a.length() == 0) {
	// 처리한다.
}

앞의 널(Null) 체크 조건이 참이면 뒤의 OR 조건은 판단하지 않는다. 그러므로 뒤의 문장에서 널(Null) 포이터 역참조는 발생하지 않는다.

 

 

● 오탐코드

FileOutputStream f = null;
try {
    f = new FileOutputStream("toctou");
    f.write();
} catch (FileNotFoundException e) {
    // TODO Auto-generated catch block
    e.printStackTrace();
}

catch 절 안에서 return 하고 있으므로 예외가 발생하여 f에 값을 넣지 못하더라도 f.write(); 는 실행되지 않는다.

 

 

● 오탐코드

<td align="center" colspan='2'><input type='button' value='다시로그인' onclick="location.href='${pageContext.request.contextPath}/utl/sec/certLogin.do'"></td>

JSP 내장 객체를 참조하고 있다. 이 경우에는 널(Null) 포인터 역참조가 발생하지 않는다.

 

 

● 오탐코드

Integer.parseInt(stringValue);

Integer 클래스의 static 함수 호출은 Integer가 객체가 아니므로 널(Null)값을 참조하지 않는다.

 

 

● 오탐코드

…
public SmsConnection sendRequsest(SmsConnection smsConn) throws Exception {
    // SMS 전송 요청
    SmsSender sender = null;
    SmsConnection result = null;
    try {
        sender = new SmsSender(smeConfigPath);
        sender.open();
        result = sendRequsest(smsConn, sender);
    } finally {
    	if (sender != null) {
    		sender.close();
    	}
    }

    smsConn.setResult(result.getResult());
    smsConn.setResultMessage(result.getResultMessage());
    return smsConn;
}

Data Flow 상이 아닌 단일 함수에서 파라미터가 널(Null)일 경우는 취약하지 않은 것으로 판단한다. 해당 함수를 호출할 때 널(Null) 체크를 하도록 해야 한다.

 

 

● 오탐코드

if(infoList == null) infoList = new ArrayList();
for(int i=0; containers != null && i < containers.length; i++) {
    hm = new HashMap();
    for(int j=0; j < paramRecvData.length; j++) {
    	hm.put(paramRecvData[j], containers[i].getField(paramRecvData[j]).getValueAsString());
    }
    infoList.add(hm);
}

JRE 기본 패키지 클래스의 생성자의 경우, 널(Null) 을 반환할 수 있다고 명시되어 있지 않는한 널(Null)을 리턴한다고 판단하지 않는다.