JAVA安全編碼與代碼審計

lbkf2108 7年前發布 | 42K 次閱讀 Java 安全漏洞 XML Java開發

JAVA安全編碼與代碼審計

概述

本文重點介紹JAVA安全編碼與代碼審計基礎知識,會以漏洞及安全編碼示例的方式介紹JAVA代碼中常見Web漏洞的形成及相應的修復方案,同時對一些常見的漏洞函數進行例舉。

XXE

介紹

XML文檔結構包括XML聲明、DTD文檔類型定義(可選)、文檔元素。文檔類型定義(DTD)的作用是定義 XML 文檔的合法構建模塊。DTD 可以在 XML 文檔內聲明,也可以外部引用。

  • 內部聲明DTD:
  • 引用外部DTD:

當允許引用外部實體時,惡意攻擊者即可構造惡意內容訪問服務器資源,如讀取passwd文件:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE replace [
<!ENTITY test SYSTEM "file:///ect/passwd">]>
<msg>&test;</msg>

漏洞示例

此處以org.dom4j.io.SAXReader為例,僅展示部分代碼片段:

String xmldata = request.getParameter("data");
SAXReader sax=new SAXReader();//創建一個SAXReader對象
Document document=sax.read(new ByteArrayInputStream(xmldata.getBytes()));//獲取document對象,如果文檔無節點,則會拋出Exception提前結束
Element root=document.getRootElement();//獲取根節點
List rowList = root.selectNodes("http://msg");
Iterator<?> iter1 = rowList.iterator();
if (iter1.hasNext()) {
    Element beanNode = (Element) iter1.next();
    modelMap.put("success",true);
    modelMap.put("resp",beanNode.getTextTrim());
}
...

審計函數

XML解析一般在導入配置、數據傳輸接口等場景可能會用到,涉及到XML文件處理的場景可留意下XML解析器是否禁用外部實體,從而判斷是否存在XXE。部分XML解析接口如下:

javax.xml.parsers.DocumentBuilder
javax.xml.stream.XMLStreamReader
org.jdom.input.SAXBuilder
org.jdom2.input.SAXBuilder
javax.xml.parsers.SAXParser
org.dom4j.io.SAXReader 
org.xml.sax.XMLReader
javax.xml.transform.sax.SAXSource 
javax.xml.transform.TransformerFactory 
javax.xml.transform.sax.SAXTransformerFactory 
javax.xml.validation.SchemaFactory
javax.xml.bind.Unmarshaller
javax.xml.xpath.XPathExpression
...

修復方案

使用XML解析器時需要設置其屬性,禁止使用外部實體,以上例中SAXReader為例,安全的使用方式如下:

sax.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
sax.setFeature("http://xml.org/sax/features/external-general-entities", false);
sax.setFeature("http://xml.org/sax/features/external-parameter-entities", false);

其它XML解析器的安全使用可參考 OWASP XML External Entity (XXE) Prevention Cheat Sheet

反序列化漏洞

介紹

序列化是讓 Java 對象脫離 Java 運行環境的一種手段,可以有效的實現多平臺之間的通信、對象持久化存儲。

Java程序使用ObjectInputStream對象的readObject方法將反序列化數據轉換為java對象。但當輸入的反序列化的數據可被用戶控制,那么攻擊者即可通過構造惡意輸入,讓反序列化產生非預期的對象,在此過程中執行構造的任意代碼。

漏洞示例

漏洞代碼示例如下:

......
//讀取輸入流,并轉換對象
InputStream in=request.getInputStream();
ObjectInputStream ois = new ObjectInputStream(in);
//恢復對象
ois.readObject();
ois.close();

上述代碼中,程序讀取輸入流并將其反序列化為對象。此時可查看項目工程中是否引入可利用的commons-collections 3.1、commons-fileupload 1.3.1等第三方庫,即可構造特定反序列化對象實現任意代碼執行。相關三方庫及利用工具可參考ysoserial、marshalsec。

審計函數

反序列化操作一般在導入模版文件、網絡通信、數據傳輸、日志格式化存儲、對象數據落磁盤或DB存儲等業務場景,在代碼審計時可重點關注一些反序列化操作函數并判斷輸入是否可控,如下:

ObjectInputStream.readObject
ObjectInputStream.readUnshared
XMLDecoder.readObject
Yaml.load
XStream.fromXML
ObjectMapper.readValue
JSON.parseObject
...

修復方案

如果可以明確反序列化對象類的則可在反序列化時設置白名單,對于一些只提供接口的庫則可使用黑名單設置不允許被反序列化類或者提供設置白名單的接口,可通過Hook函數resolveClass來校驗反序列化的類從而實現白名單校驗,示例如下:

public class AntObjectInputStream extends ObjectInputStream{
    public AntObjectInputStream(InputStream inputStream)
            throws IOException {
        super(inputStream);
    }

/**
 * 只允許反序列化SerialObject class
 */
@Override
protected Class<?> resolveClass(ObjectStreamClass desc) throws IOException,
        ClassNotFoundException {
    if (!desc.getName().equals(SerialObject.class.getName())) {
        throw new InvalidClassException(
                "Unauthorized deserialization attempt",
                desc.getName());
    }
    return super.resolveClass(desc);
}

}</code></pre>

也可以使用Apache Commons IO Serialization包中的ValidatingObjectInputStream類的accept方法來實現反序列化類白/黑名單控制,如果使用的是第三方庫則升級到最新版本。更多修復方案可參考 淺談Java反序列化漏洞修復方案

SSRF

介紹

SSRF形成的原因大都是由于代碼中提供了從其他服務器應用獲取數據的功能但沒有對目標地址做過濾與限制。比如從指定URL鏈接獲取圖片、下載等。

漏洞示例

此處以HttpURLConnection為例,示例代碼片段如下:

String url = request.getParameter("picurl");
    StringBuffer response = new StringBuffer();

URL pic = new URL(url);
HttpURLConnection con = (HttpURLConnection) pic.openConnection();
con.setRequestMethod("GET");
con.setRequestProperty("User-Agent", "Mozilla/5.0");
BufferedReader in = new BufferedReader(new InputStreamReader(con.getInputStream()));
String inputLine;
while ((inputLine = in.readLine()) != null) {
     response.append(inputLine);
}
in.close();
modelMap.put("resp",response.toString());
return "getimg.htm";</code></pre> 

審計函數

程序中發起HTTP請求操作一般在獲取遠程圖片、頁面分享收藏等業務場景,在代碼審計時可重點關注一些HTTP請求操作函數,如下:

HttpClient.execute
HttpClient.executeMethod
HttpURLConnection.connect
HttpURLConnection.getInputStream
URL.openStream
...

修復方案

  • 使用白名單校驗HTTP請求url地址
  • 避免將請求響應及錯誤信息返回給用戶
  • 禁用不需要的協議及限制請求端口,僅僅允許http和https請求等

SQLi

介紹

注入攻擊的本質,是程序把用戶輸入的數據當做代碼執行。這里有兩個關鍵條件,第一是用戶能夠控制輸入;第二是用戶輸入的數據被拼接到要執行的代碼中從而被執行。sql注入漏洞則是程序將用戶輸入數據拼接到了sql語句中,從而攻擊者即可構造、改變sql語義從而進行攻擊。

漏洞示例

此處以Mybatis框架為例,示例sql片段如下:

select * from books where id= ${id}

對于Mybatis框架下SQL注入漏洞的審計可參考 Mybatis框架下SQL注入漏洞面面觀

修復方案

Mybatis框架SQL語句安全寫法應使用#{},避免使用動態拼接形式${},ibatis則使用#變量#。安全寫法如下:

select * from books where id= #{id}

文件上傳漏洞

介紹

文件上傳過程中,通常因為未校驗上傳文件后綴類型,導致用戶可上傳jsp等一些webshell文件。代碼審計時可重點關注對上傳文件類型是否有足夠安全的校驗,以及是否限制文件大小等。

漏洞示例

此處以MultipartFile為例,示例代碼片段如下:

public String handleFileUpload(MultipartFile file){
        String fileName = file.getOriginalFilename();
        if (fileName==null) {
            return "file is error";
        }
        String filePath = "/static/images/uploads/"+fileName;
        if (!file.isEmpty()) {
            try {
                byte[] bytes = file.getBytes();
                BufferedOutputStream stream =
                        new BufferedOutputStream(new FileOutputStream(new File(filePath)));
                stream.write(bytes);
                stream.close();
                return "OK";
            } catch (Exception e) {
                return e.getMessage();
            }
        } else {
            return "You failed to upload " + file.getOriginalFilename() + " because the file was empty.";
        }
    }

審計函數

java程序中涉及到文件上傳的函數,比如:

MultipartFile
...

修復方案

  • 使用白名單校驗上傳文件類型、大小限制

Autobinding

介紹

Autobinding-自動綁定漏洞,根據不同語言/框架,該漏洞有幾個不同的叫法,如下:

  • Mass Assignment: Ruby on Rails, NodeJS
  • Autobinding: Spring MVC, ASP.NET MVC
  • Object injection: PHP(對象注入、反序列化漏洞)

軟件框架有時允許開發人員自動將HTTP請求參數綁定到程序代碼變量或對象中,從而使開發人員更容易地使用該框架。這里攻擊者就可以利用這種方法通過構造http請求,將請求參數綁定到對象上,當代碼邏輯使用該對象參數時就可能產生一些不可預料的結果。

漏洞示例

示例代碼以 ZeroNights-HackQuest-2016 的demo為例,把示例中的justiceleague程序運行起來,可以看到這個應用菜單欄有about,reg,Sign up,Forgot password這4個頁面組成。我們關注的點是密碼找回功能,即怎么樣繞過安全問題驗證并找回密碼。

1)首先看reset方法,把不影響代碼邏輯的刪掉。這樣更簡潔易懂:

@Controller
@SessionAttributes("user")
public class ResetPasswordController {

private UserService userService;
...
@RequestMapping(value = "/reset", method = RequestMethod.POST)
public String resetHandler(@RequestParam String username, Model model) {
        User user = userService.findByName(username);
        if (user == null) {
            return "reset";
        }
        model.addAttribute("user", user);
        return "redirect: resetQuestion";
    }

這里從參數獲取username并檢查有沒有這個用戶,如果有則把這個user對象放到Model中。因為這個Controller使用了@SessionAttributes("user"),所以同時也會自動把user對象放到session中。然后跳轉到resetQuestion密碼找回安全問題校驗頁面。

2)resetQuestion密碼找回安全問題校驗頁面有resetViewQuestionHandler這個方法展現

@RequestMapping(value = "/resetQuestion", method = RequestMethod.GET)
    public String resetViewQuestionHandler(@ModelAttribute User user) {
        logger.info("Welcome resetQuestion ! " + user);
        return "resetQuestion";
    }

這里使用了@ModelAttribute User user,實際上這里是從session中獲取user對象。但存在問題是如果在請求中添加user對象的成員變量時則會更改user對象對應成員的值。 所以當我們給resetQuestionHandler發送GET請求的時候可以添加“answer=hehe”參數,這樣就可以給session中的對象賦值,將原本密碼找回的安全問題答案修改成“hehe”。這樣在最后一步校驗安全問題時即可驗證成功并找回密碼

審計函數

這種漏洞一般在比較多步驟的流程中出現,比如轉賬、找密等場景,也可重點留意幾個注解如下:

@SessionAttributes
@ModelAttribute
...

更多信息可參考 Spring MVC Autobinding漏洞實例初窺

修復方案

Spring MVC中可以使用@InitBinder注解,通過WebDataBinder的方法setAllowedFields、setDisallowedFields設置允許或不允許綁定的參數。

URL重定向

介紹

由于Web站點有時需要根據不同的邏輯將用戶引向到不同的頁面,如典型的登錄接口就經常需要在認證成功之后將用戶引導到登錄之前的頁面,整個過程中如果實現不好就可能導致URL重定向問題,攻擊者構造惡意跳轉的鏈接,可以向用戶發起釣魚攻擊。

漏洞示例

此處以Servlet的redirect 方式為例,示例代碼片段如下:

String site = request.getParameter("url");
    if(!site.isEmpty()){
        response.sendRedirect(site);
    }

審計函數

java程序中URL重定向的方法均可留意是否對跳轉地址進行校驗、重定向函數如下:

sendRedirect
setHeader
forward
...

修復方案

  • 使用白名單校驗重定向的url地址
  • 給用戶展示安全風險提示,并由用戶再次確認是否跳轉

CSRF

介紹

跨站請求偽造(Cross-Site Request Forgery,CSRF)是一種使已登錄用戶在不知情的情況下執行某種動作的攻擊。因為攻擊者看不到偽造請求的響應結果,所以CSRF攻擊主要用來執行動作,而非竊取用戶數據。當受害者是一個普通用戶時,CSRF可以實現在其不知情的情況下轉移用戶資金、發送郵件等操作;但是如果受害者是一個具有管理員權限的用戶時CSRF則可能威脅到整個Web系統的安全。

漏洞示例

由于開發人員對CSRF的了解不足,錯把“經過認證的瀏覽器發起的請求”當成“經過認證的用戶發起的請求”,當已認證的用戶點擊攻擊者構造的惡意鏈接后就“被”執行了相應的操作。例如,一個博客刪除文章是通過如下方式實現的:

GET http://blog.com/article/delete.jsp?id=102

當攻擊者誘導用戶點擊下面的鏈接時,如果該用戶登錄博客網站的憑證尚未過期,那么他便在不知情的情況下刪除了id為102的文章,簡單的身份驗證只能保證請求發自某個用戶的瀏覽器,卻不能保證請求本身是用戶自愿發出的。

漏洞審計

此類漏洞一般都會在框架中解決修復,所以在審計csrf漏洞時。首先要熟悉框架對CSRF的防護方案,一般審計時可查看增刪改請求重是否有token、formtoken等關鍵字以及是否有對請求的Referer有進行校驗。手動測試時,如果有token等關鍵則替換token值為自定義值并重放請求,如果沒有則替換請求Referer頭為自定義鏈接或置空。重放請求看是否可以成功返回數據從而判斷是否存在CSRF漏洞。

修復方案

  • Referer校驗,對HTTP請求的Referer校驗,如果請求Referer的地址不在允許的列表中,則攔截請求。
  • Token校驗,服務端生成隨機token,并保存在本次會話cookie中,用戶發起請求時附帶token參數,服務端對該隨機數進行校驗。如果不正確則認為該請求為偽造請求拒絕該請求。
  • Formtoken校驗,Formtoken校驗本身也是Token校驗,只是在本次表單請求有效。
  • 對于高安全性操作則可使用驗證碼、短信、密碼等二次校驗措施
  • 增刪改請求使用POST請求

命令執行

介紹

由于業務需求,程序有可能要執行系統命令的功能,但如果執行的命令用戶可控,業務上有沒有做好限制,就可能出現命令執行漏洞。

漏洞示例

此處以getRuntime為例,示例代碼片段如下:

String cmd = request.getParameter("cmd");
    Runtime.getRuntime().exec(cmd);

審計函數

這種漏洞原理上很簡單,重點是找到執行系統命令的函數,看命令是否可控。在一些特殊的業務場景是能判斷出是否存在此類功能,這里舉個典型的實例場景,有的程序功能需求提供網頁截圖功能,筆者見過多數是使用phantomjs實現,那勢必是需要調用系統命令執行phantomjs并傳參實現截圖。而參數大多數情況下應該是當前url或其中獲取相關參數,此時很有可能存在命令執行漏洞,還有一些其它比較特別的場景可自行腦洞。

java程序中執行系統命令的函數如下:

Runtime.exec
ProcessBuilder.start
GroovyShell.evaluate
...

修復方案

  • 避免命令用戶可控
  • 如需用戶輸入參數,則對用戶輸入做嚴格校驗,如&&、|、;等

權限控制

介紹

越權漏洞可以分為水平、垂直越權兩種,程序在處理用戶請求時未對用戶的權限進行校驗,使的用戶可訪問、操作其他相同角色用戶的數據,這種情況是水平越權;如果低權限用戶可訪問、操作高權限用戶則的數據,這種情況為垂直越權。

漏洞示例

@RequestMapping(value="/getUserInfo",method = RequestMethod.GET)
    public String getUserInfo(Model model, HttpServletRequest request) throws IOException {
        String userid = request.getParameter("userid");
        if(!userid.isEmpty()){
            String info=userModel.getuserInfoByid(userid);
            return info;
        }
        return "";
    }

審計函數

水平、垂直越權不需關注特定函數,只要在處理用戶操作請求時查看是否有對當前登陸用戶權限做校驗從而確定是否存在漏洞

修復方案

獲取當前登陸用戶并校驗該用戶是否具有當前操作權限,并校驗請求操作數據是否屬于當前登陸用戶,當前登陸用戶標識不能從用戶可控的請求參數中獲取。

批量請求

介紹

業務中經常會有使用到發送短信校驗碼、短信通知、郵件通知等一些功能,這類請求如果不做任何限制,惡意攻擊者可能進行批量惡意請求轟炸,大量短信、郵件等通知對正常用戶造成困擾,同時也是對公司的資源造成損耗。

除了短信、郵件轟炸等,還有一種情況也需要注意,程序中可能存在很多接口,用來查詢賬號是否存在、賬號名與手機或郵箱、姓名等的匹配關系,這類請求如不做限制也會被惡意用戶批量利用,從而獲取用戶數據關系相關數據。對這類請求在代碼審計時可關注是否有對請求做鑒權、和限制即可大致判斷是否存在風險。

漏洞示例

@RequestMapping(value="/ifUserExit",method = RequestMethod.GET)
    public String ifUserExit(Model model, HttpServletRequest request) throws IOException {
        String phone = request.getParameter("phone");
        if(! phone.isEmpty()){
            boolean ifex=userModel.ifuserExitByPhone(phone);
            if (!ifex)
                return "用戶不存在";
        }
        return "用戶已被注冊";
    }

修復方案

  • 對同一個用戶發起這類請求的頻率、每小時及每天發送量在服務端做限制,不可在前端實現限制

第三方組件安全

介紹

這個比較好理解,諸如Struts2、不安全的編輯控件、XML解析器以及可被其它漏洞利用的如commons-collections:3.1等第三方組件,這個可以在程序pom文件中查看是否有引入依賴。即便在代碼中沒有應用到或很難直接利用,也不應該使用不安全的版本,一個產品的周期很長,很難保證后面不會引入可被利用的漏洞點。

修復方案

  • 使用最新或安全版本的第三方組件

待續...

總結

除了上述相關的漏洞,在代碼審計的時候有時會遇到一些特別的漏洞,比如開發為了測試方便關閉掉了一些安全校驗函數、甚至未徹底清除的一些預留后門及測試管理接口等。除此,框架本身的安全問題也是可以深挖。一些安全校驗、安全解決方案也未必就毫無破綻的,即便存在一些安全解決,但開發人員有沒有使用以及是否正確使用安全方案都是可能存在問題的點。大公司都有成熟的框架,一些基本的安全問題并不是太多,但設計層面上的安全及流程相關的問題卻基本依賴開發的經驗。流程相關的漏洞則有必要先熟悉應用本身的設計和邏輯,這塊也是潛在的風險點。

不要指望給開發說一句“一切輸入都是不可信的”,他就能編寫出安全的代碼。總之,Talk is cheap. Show me the code~

 

來自:https://github.com/Cryin/JavaID/blob/master/JAVA安全編碼與代碼審計.md

 

 本文由用戶 lbkf2108 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!