java編譯篇
java編譯過程:
Java源代碼 ——(編譯)——> Java字節碼 ——(解釋器)——> 機器碼
Java源代碼 ——(編譯器 )——> jvm可執行的Java字節碼 ——(jvm解釋器) ——> 機器可執行的二進制機器碼 ——>程序運行
采用字節碼的好處:高效、可移植性高
以下示例為.java文件:
以下是.class文件:
反編譯工具篇
- fernflower
-
jad
-
jd-gui
-
idea自帶插件
jar包本質上是將所有class文件、資源文件壓縮打成一個包。
Servlet與jsp篇
Servlet:
-
類似小程序,處理較復雜的服務端業務邏輯
-
含有HttpServlet類,可進行重寫
- servlet3.0后使用注解方式描述servlet,使用doGet和doPost為默認命名
- servlet3.0版本之前必須在web.xml中配置
jsp:
會被編譯成一個java類文件
,如index.jsp在Tomcat中Jasper編譯后會生成index_jsp.java
和index_jsp.class
兩個文件。是特殊的servlet。
全局控制器篇
使用idea,全局搜索command+shift+f(或者a)
find ~/cms/ -type f -name "*.class" |xargs grep -E "Controller|@RestController|RepositoryRestController"
find ~/cms/ -type f -name "*.class" |xargs grep -E "RequestMapping|GetMapping|PostMapping|PutMapping|DeleteMapping|PatchMapping|RepositoryRestResource"
全局過濾器篇
審計時,得先看是否含有全局過濾器。切勿看到Servlet
、JSP
中的漏洞點就妄下定論,Servlet
前面很有可能存在一個全局安全過濾的Filter
。當然每種框架的寫法也有差別。個人認為Filter主要是用在
-
web.xml全局過濾
<filter> <filter-name>YytSecurityUrlFilter</filter-name> <filter-class>com.yytcloud.core.spring.pub.filter.YytSecurityUrlFilter</filter-class> <async-supported>true</async-supported> <init-param> <param-name>sqlInjIgnoreUrls</param-name> <param-value>.*/itf/.*</param-value> </init-param> <init-param> <param-name>ignoreXSSUrls</param-name> <param-value>.*/itf/.*</param-value> </init-param> </filter> <filter-mapping> <filter-name>YytSecurityUrlFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>
-
jar包
首先添加一個 jar 包:commons-lang-2.5.jar ,然后在后台調用這些函數: StringEscapeUtils.escapeHtml(string); StringEscapeUtils.escapeJavaScript(string); StringEscapeUtils.escapeSql(string);
-
轉義
String string = HtmlUtils.htmlEscape(userinput); //轉義 String s2 = HtmlUtils.htmlUnescape(string); //轉成原來的
常見漏洞篇
分為業務安全
問題、代碼實現
和服務架構
安全問題
代碼實現,查看對應代碼與全局過濾器:
- 任意
文件讀寫
(文件上傳、文件下載)、文件遍歷
、文件刪除
、文件重命名
等漏洞 - SQL注入漏洞
- XXE(XML實體注入攻擊)
- 表達式執行(SpEL、OGNL、MVEL2、EL等)
- 系統命令執行漏洞(ProcessBuilder)
- 反序列化攻擊(ObjectInputStream、JSON、XML等)
- Java反射攻擊
- SSRF攻擊
- XSS
業務安全,主要理解該系統的邏輯:
- 用戶登陸、用戶注冊、找回密碼等功能中密碼信息未采用加密算法。
- 用戶登陸、用戶注冊、找回密碼等功能中
未采用驗證碼
或驗證碼未做安全刷新
(未刷新Session中驗證碼的值)導致的撞庫、密碼爆破漏洞。 - 找回密碼邏輯問題(如:可直接跳過驗證邏輯直接發包修改)。
- 手機、郵箱驗證、找回密碼等涉及到動態驗證碼
未限制驗證碼失敗次數
、驗證碼有效期
、驗證碼長度過短
導致的驗證碼爆破問題。 - 充值、付款等功能調用了第三方支付系統未正確校驗接口(與第三方的交互、與客戶的交互,主要查看邏輯問題)。
- 后端采用了
ORM框架
更新操作時因處理不當導致可以更新用戶表任意字段(如:用戶注冊、用戶個人資料修改時可以直接創建管理員賬號
或其他越權修改操作)。 - 后端采用了
ORM框架
查詢數據時因處理不當導致可以接收任何參數導致的越權查詢、敏感信息查詢等安全問題。 - 用戶中心轉賬、修改個人資料、密碼、退出登陸等功能未采用驗證碼或
Token機制
導致存在CSRF漏洞
。 - 后端服務過於信任前端,重要的參數和業務邏輯只做了前端驗證(如:文件上傳功能的文件類型只在JS中驗證、后端不從Session中獲取用戶ID、用戶名而是直接接收客戶端請求的參數導致的
越權問題
)。 - 用戶身份信息認證邏輯問題(如:后台系統自動登陸時直接讀取Cookie中的用戶名、用戶權限不做驗證)。
- 重要接口采用
ID自增、ID可預測並且雲端未驗證參數有效性
導致的越權訪問、信息泄漏問題(如:任意用戶訂單越權訪問)。 條件競爭問題
,某些關鍵業務(如:用戶轉賬)不支持並發、分布式部署時不支持鎖的操作等。- 重要接口
未限制請求頻率
,導致短信、郵件、電話、私信等信息轟炸。 - 敏感信息未保護,如
Cookie中直接存儲用戶密碼等重要信息
,跟蹤cookie中的變量最終到了哪。 - 弱加密算法、弱密鑰,如勿把Base64當成數據加密方式、重要算法密鑰采用弱口令如
123456
。 - 后端無異常處理機制、未自定義50X錯誤頁面,服務器異常導致敏感信息泄漏(如:數據庫信息、網站絕對路徑等)。
- 使用
DWR框架
開發時前后端不分漏洞(如:DWR直接調用數據庫信息把用戶登陸邏輯直接放到了前端來做)。
SQL注入篇
-
直接拼接,未進行過濾
將
request.getParameter("")
直接放在SQL語句。全局搜索查看:
String sql
等。 -
預編譯使用有誤
-
在使用占位符后未進行setObject或者setInt或者setString。
-
有些會使用SQLparameter函數,參數化查詢SQL,能有效避免SQL注入。
-
使用setProperties函數。
-
占位符
這種在滲透中出現的情況是:當輸入1' or '1'='1,不會有什么回顯。
因為這個引號已經無法起到閉合作用了,只相當於是一個字符,由於對特殊符號的轉義。
如圖所知,在setString那個函數那里對引號等一些特殊符號做了轉義。
// 執行查詢
System.out.println(" 實例化Statement對象..."); PreparedStatement st=conn.prepareStatement("select * from " + "springmysql1 where name=?"); st.setString(1,request.getParameter("name")); ResultSet rs=st.executeQuery();
-
%和_
沒有手動過濾%。預編譯是不能處理這個符號的, 所以需要手動過濾,否則會造成慢查詢,造成 dos。
-
Order by、from等無法預編譯
如以下示例,需要手動過濾,否則存在sql注入。
String sql = "Select * from news where title =?" + "order by '" + time + "' asc"
-
Mybatis 框架
使用注解或者xml將java對象與數據庫sql操作對應。
在注解中或者 Mybatis 相關的配置文件中搜索 $ 。然后查看相關 sql 語句上下文環境。
mybatis簡單示例
- mybatis的maven配置
<dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.5.2</version> </dependency>
-
目錄結構
java文件
配置文件
-
各文件功能(左下角是我的水印哈哈哈)
-
config.xml
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd"> <configuration> <environments default="dev"> <environment id="dev"> <transactionManager type="JDBC"/> <dataSource type="POOLED"> <property name="driver" value="com.mysql.cj.jdbc.Driver"/> <property name="url" value="jdbc:mysql://localhost:3306/mybatistest"/> <property name="username" value="root"/> <property name="password" value="root"/> </dataSource> </environment> </environments> <mappers> <mapper resource="UserMapper.xml"/> </mappers> </configuration>
-
UserMapper.xml
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="org.UserMapper"> <select id="getUser" resultType="org.User1"> select * from user where name=#{name} </select> </mapper>
-
UserMapper.java
package org; public interface UserMapper{ public User1 getUser(String name); }
-
MybatisUtil.java
package org; import org.apache.ibatis.io.Resources; import org.apache.ibatis.session.SqlSession; import org.apache.ibatis.session.SqlSessionFactory; import org.apache.ibatis.session.SqlSessionFactoryBuilder; import java.io.Reader; public class MybatisUtil{ public static SqlSessionFactory sessionFactory; static{ try{ Reader reader = Resources.getResourceAsReader("config.xml"); sessionFactory = new SqlSessionFactoryBuilder().build(reader); } catch (Exception e){ System.out.println(e); } } public static SqlSession getSession(){ return sessionFactory.openSession(); } }
-
User1.java
package org; import lombok.Data; @Data public class User1 { String name; int age; }
-
test.java
package org; import org.apache.ibatis.session.SqlSession; import org.apache.ibatis.session.SqlSessionFactory; import org.apache.ibatis.session.SqlSessionFactoryBuilder; import java.io.InputStream; import org.junit.Test; public class test { @Test public void test1() { SqlSession session=MybatisUtil.getSession(); UserMapper userMapper=session.getMapper(UserMapper.class); User1 user1=userMapper.getUser("wy"); System.out.println(user1.getAge()); } }
-
在UserMapper.xml使用
#{}
的結果 -
在UserMapper.xml使用
${}
的結果使用該符號需要手動寫上引號拼接,不然會報錯,
User1 user1=userMapper.getUser("'"+"wy' or '1'='1"+"'");
-
容易觸發sql注入的條件與修復
- 模糊查詢,需要加入特殊符號,不單單加入引號的那種。如
like '%${xxx}%'
,修復自然是將xxx拎出來,比如使用concat函數。 - 無需加引號處。比如
in(${xxx})
或者order by ${xxx}
。修復是用戶自行過濾。
- 模糊查詢,需要加入特殊符號,不單單加入引號的那種。如
-
:=
和和此處的${ids}
可防止SQL注入@Arguments("id") @Sql("select count(1) from cgform_head where physice_id=:id ") public int getByphysiceId(String id); @Arguments("ids") @Sql("select count(1) as hasPeizhi,physice_id id from cgform_head where 1=1 and physice_id in (${ids}) group by physice_id") public List<Map<String, Object>> getPeizhiCountByIds(String ids);
像字符型SQL語句的滲透利用在現實中無非三種,可能還需試一下時間盲注等等,視情況而定:
- 1') or 1=1 or ('1(括號那里可能會有1至多個)
- 1%' or '%'='
- 1' or '1'='1
SPel注入篇
簡單描述:
使用el表達式且el表達式可控。如CVE-2018-1260就是spring-security-oauth2的一個SPel注入導致的RCE。
示例:
String el="T(java.lang.Runtime).getRuntime().exec(\"open /tmp\")"; ExpressionParser PARSER = new SpelExpressionParser(); Expression exp = PARSER.parseExpression(el); System.out.println(exp.getValue());
在getValue那里執行命令,調用棧如下
審計:
查看使用SpelExpressionParser的地方有沒有用戶可控的。
XSS篇
示例
@RequestMapping("/xss") public ModelAndView xss(HttpServletRequest request,HttpServletResponse response) throws ServletException,IOException{ String name = request.getParameter("name"); ModelAndView mav = new ModelAndView("mmc"); mav.getModel().put("uname", name); return mav; }
如果想要返回json格式,將mmc
替換為new MappingJackson2JsonView()
。
SSRF篇
代碼中提供了從其他服務器應用獲取數據的功能但沒有對目標地址做過濾與限制。比如從指定URL鏈接獲取圖片、下載等。主要可能存在於在線文檔編輯器之類。
示例
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(); return response.toString();
審計
支持的協議
- file
- ftp
- http
- https
- jar
- mailto
- netdoc
常用的函數
HttpClient.execute HttpClient.executeMethod HttpURLConnection.connect HttpURLConnection.getInputStream URL.openStream
修復
- 使用白名單校驗HTTP請求url地址
- 避免將請求響應及錯誤信息返回給用戶
- 禁用不需要的協議及限制請求端口,僅僅允許http和https請求等(這點待研究)
CSRF篇
簡單描述:
跨站請求偽造是一種使已登錄用戶在不知情的情況下執行某種動作的攻擊。因為攻擊者看不到偽造請求的響應結果,所以CSRF攻擊主要用來執行動作
,而非竊取用戶數據。當受害者是一個普通用戶時,CSRF可以實現在其不知情的情況下轉移用戶資金、發送郵件
等操作;但是如果受害者是一個具有管理員權限的用戶
時CSRF則可能威脅到整個Web系統的安全。
審計:
一些增刪改查方法,是否進行Referer頭檢驗
、token檢驗
無法構造的隨機數參數
、驗證碼密碼
。
搜索session["token"]
修護:
Referer頭檢驗、token檢驗。
XXE篇
簡單描述:
當允許引用外部實體且存在輸入點
時,惡意攻擊者即可構造惡意內容訪問服務器資源,如讀取 passwd 文件
https://www.cnblogs.com/r00tuser/p/7255939.html
示例:
@RequestMapping("/xxetest") public String xxetest(HttpServletRequest request) throws DocumentException { String xmldata = request.getParameter("data"); SAXReader sax=new SAXReader(); Document document=sax.read(new ByteArrayInputStream(xmldata.getBytes())); Element root= ((org.dom4j.Document) document).getRootElement(); List rowList = root.selectNodes("//msg"); Iterator<?> iter1 = rowList.iterator(); if (iter1.hasNext()) { Element beanNode = (Element) iter1.next(); return beanNode.getTextTrim(); } return "error"; }
root.selectNodes("//msg")
獲取根目錄下的所有<msg>標簽</msg>
利用:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE replace [ <!ENTITY test SYSTEM "file:///tmp/flag">]> <msg>&test;</msg>
滲透的話可以結合burpsuite的插件:collaborator https://blog.csdn.net/fageweiketang/article/details/89073662
審計:
- 判斷使用哪種XML解析器
- 搜索是否有禁用外部實體配置(修護部分有具體代碼)
- 是否有外部輸入點進行解析
修護:
- saxReader
saxReader.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true); saxReader.setFeature("http://xml.org/sax/features/external-general-entities", false); saxReader.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
- saxBuilder
SAXBuilder builder = new SAXBuilder(); builder.setFeature("http://apache.org/xml/features/disallow-doctype-decl",true); builder.setFeature("http://xml.org/sax/features/external-general-entities", false); builder.setFeature("http://xml.org/sax/features/external-parameter-entities", false); Document doc = builder.build(new File(fileName));
- saxTransformerFactory
SAXTransformerFactory sf = SAXTransformerFactory.newInstance(); sf.setAttribute(XMLConstants.ACCESS_EXTERNAL_DTD, ""); sf.setAttribute(XMLConstants.ACCESS_EXTERNAL_STYLESHEET, ""); sf.newXMLFilter(Source); Note: Use of the following XMLConstants requires JAXP 1.5, which was added to Java in 7u40 and Java 8: javax.xml.XMLConstants.ACCESS_EXTERNAL_DTD javax.xml.XMLConstants.ACCESS_EXTERNAL_SCHEMA javax.xml.XMLConstants.ACCESS_EXTERNAL_STYLESHEET
- schemaFactory
SchemaFactory factory = SchemaFactory.newInstance("http://www.w3.org/2001/XMLSchema"); factory.setProperty(XMLConstants.ACCESS_EXTERNAL_DTD, ""); factory.setProperty(XMLConstants.ACCESS_EXTERNAL_SCHEMA, ""); Schema schema = factory.newSchema(Source);
- xmlInputFactory
xmlInputFactory.setProperty(XMLInputFactory.SUPPORT_DTD, false); // This disables DTDs entirely for that factory xmlInputFactory.setProperty("javax.xml.stream.isSupportingExternalEntities", false); // disable external entities
- xmlReader
XMLReader reader = XMLReaderFactory.createXMLReader(); reader.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true); reader.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false); // This may not be strictly required as DTDs shouldn't be allowed at all, per previous line. reader.setFeature("http://xml.org/sax/features/external-general-entities", false); reader.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
- XPathExpression
DocumentBuilderFactory df = DocumentBuilderFactory.newInstance(); df.setAttribute(XMLConstants.ACCESS_EXTERNAL_DTD, ""); df.setAttribute(XMLConstants.ACCESS_EXTERNAL_SCHEMA, ""); DocumentBuilder builder = df.newDocumentBuilder(); String result = new XPathExpression().evaluate( builder.parse(new ByteArrayInputStream(xml.getBytes())) );
- transformerFactory
TransformerFactory tf = TransformerFactory.newInstance(); tf.setAttribute(XMLConstants.ACCESS_EXTERNAL_DTD, ""); tf.setAttribute(XMLConstants.ACCESS_EXTERNAL_STYLESHEET, "");
- Validator
SchemaFactory factory = SchemaFactory.newInstance("http://www.w3.org/2001/XMLSchema"); Schema schema = factory.newSchema(); Validator validator = schema.newValidator(); validator.setProperty(XMLConstants.ACCESS_EXTERNAL_DTD, ""); validator.setProperty(XMLConstants.ACCESS_EXTERNAL_SCHEMA, "");
- Unmarshaller
SAXParserFactory spf = SAXParserFactory.newInstance(); spf.setFeature("http://xml.org/sax/features/external-general-entities", false); spf.setFeature("http://xml.org/sax/features/external-parameter-entities", false); spf.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false); Source xmlSource = new SAXSource(spf.newSAXParser().getXMLReader(), new InputSource(new StringReader(xml))); JAXBContext jc = JAXBContext.newInstance(Object.class); Unmarshaller um = jc.createUnmarshaller(); um.unmarshal(xmlSource);
XML篇
簡單描述:
一個用戶,如果他被允許輸入結構化的XML片段,則他可以在 XML 的數據域中注入 XML 標簽
來改寫目標 XML 文檔的結構與內容。
示例:
private void createXMLStream(BufferedOutputStream outStream, User user) throws IOException { String xmlString; xmlString = "<user><role>operator</role><id>" + user.getUserId() +"</id><description>" + user.getDescription() + "</description></user>"; outStream.write(xmlString.getBytes()); outStream.flush(); }
輸入以下惡意代碼
hhh</id><role>administrator</role><id>hhh
由於 SAX 解析器(org.xml.sax and javax.xml.parsers.SAXParser
)在解釋 XML 文檔時會將第二個role 域的值覆蓋前一個 role 域的值
,因此導致此用戶角色由操作員提升為了管理員。
審計方法:
全局搜索如下字符串
- xml
- StreamSource
- XMLConstants
- StringReader
- xmlString
在項目中搜索. Xsd 文件
修護:
- 白名單。只能包含
字母、數字、下划線
。
private void createXMLStream(BufferedOutputStream outStream, User user) throws IOException { if (!Pattern.matches("[_a-bA-B0-9]+", user.getUserId())) { ... } if (!Pattern.matches("[_a-bA-B0-9]+", user.getDescription())) { ... } String xmlString = "<user><id>" + user.getUserId() \+ "</id><role>operator</role><description>" \+ user.getDescription() + "</description></user>"; outStream.write(xmlString.getBytes()); outStream.flush(); }
-
使用
dom4j
來構建 XML。dom4j 是一個良好定義的、開源的 XML 工具庫。Dom4j將會
對文本數據域進行 XML 編碼
,從而使得 XML 的原始結構和格式免受破壞。
public static void buidlXML(FileWriter writer, User user) throws IOException { Document userDoc = DocumentHelper.createDocument(); Element userElem = userDoc.addElement("user"); Element idElem = userElem.addElement("id"); idElem.setText(user.getUserId()); Element roleElem = userElem.addElement("role"); roleElem.setText("operator"); Element descrElem = userElem.addElement("description"); descrElem.setText(user.getDescription()); XMLWriter output = null; try{ OutputFormat format = OutputFormat.createPrettyPrint(); format.setEncoding("UTF-8"); output = new XMLWriter(writer, format); output.write(userDoc); output.flush(); } finally{ try{ output.close(); } catch (Exception e){} } }
越權篇
水平越權和垂直越權。
審計:
在每個request.getParameter("userid");
之后查看是否有檢驗當前用戶與要進行增刪改查的用戶。
修護:
獲取當前登陸用戶並校驗該用戶是否具有當前操作權限,並校驗請求操作數據是否屬於當前登陸用戶,當前登陸用戶標識不能從用戶可控的請求參數中獲取。
批量請求篇
簡單描述:
在部分接口,沒有進行驗證碼等防護,導致可以無限制重發接口,結果是浪費了系統資源的才算。比如一直發短信驗證碼,但是可以不斷查詢就不算批量請求漏洞。批量請求與csrf的修護建議類似,但由於使用場景不同,因此漏洞不同。
修護:
- 驗證碼
- token
- 對同一個用戶發起這類請求的頻率、每小時及每天發送量在服務端做限制,不可在前端實現限制
- 對參數使用不可預測的隨機數
命令執行篇
簡單描述:
執行的命令用戶可控。
示例:
String cmd=request.getParameter("cmd"); Runtime.getRuntime.exec(cmd);
審計:
查找是否有使用如下方法,且其中的內容用戶可控。
Runtime.exec ProcessBuilder.start GroovyShell.evaluate
反序列化-代碼執行篇
簡單描述:
Java 程序使用 ObjectInputStream 對象的readObject
方法將反序列化數據轉換為 java 對象。但當輸入的反序列化的數據可被用戶控制
,那么攻擊者即可通過構造惡意輸入,讓反序列化產生非預期的對象,在此過程中執行構造的任意代碼
。
示例:
//讀取輸入流,並轉換對象
InputStream in=request.getInputStream(); ObjectInputStream ois = new ObjectInputStream(in); //恢復對象 ois.readObject(); ois.close();
審計:
java 序列化的數據一般會以標記(ac ed 00 05
)開頭,base64 編碼后的特征為rO0AB
。
找出反序列化函數調用點:
- ObjectInputStream.readObject
- ObjectInputStream.readUnshared
- XMLDecoder.readObject
- Yaml.load
- XStream.fromXML
- ObjectMapper.readValue
- JSON.parseObject
RMI:是 Java 的一組擁護開發分布式應用程序的 API,實現了不同操作系統之間程序的方法調用。RMI 的傳輸 100%基於反序列化,Java RMI 的默認端口是 1099 端口。
修護:
-
白名單。只允許某些類被反序列化。
以下例子通過重寫ObjectInputSream中的resolveClass方法,讀取需要反序列化的類名與SerialObject.class對比,判斷是否合法。SerialKiller就是利用這種原理而寫的jar包。
-
/**只允許反序列化 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); } }
SerialKiller簡單用法
ObjectInputStream ois = new SerialKiller(is, "/etc/serialkiller.conf"); String msg = (String) ois.readObject();
-
Apache Commons IO Serialization 包中的
ValidatingObjectInputStream
類的accept
方法來實現反序列化類白/黑名單控制Object obj; ByteArrayInputStream bais = new ByteArrayInputStream(buffer); // Use ValidatingObjectInputStream instead of InputStream ValidatingObjectInputStream ois = new ValidatingObjectInputStream(bais); //只允許反序列化SerialObject class ois.accept(SerialObject.class); obj = ois.readObject();
反序列化-權限過高篇
簡單描述:
沒懂所以不想寫
審計:
手工搜索以下文本
- public * writeObject
- public * readObject
- public readResolve public writeReplace
修護:
- private void writeObject
- private void readObject
- protected Object readResolve
- protected Object writeReplace
敏感數據序列化篇
簡單描述:
將敏感數據連着實例方法一起序列化,導致敏感數據泄漏。
示例:
假設x和y
是敏感數據,序列化后面臨坐標泄漏危險
public class GPSLocation implements Serializable { private double x; // sensitive field private double y; // sensitive field private String id;// non-sensitive field // other content } public class Coordinates { public static void main(String[] args) { FileOutputStream fout = null; try{ GPSLocation p = new GPSLocation(5, 2, "northeast"); fout = new FileOutputStream("location.ser"); ObjectOutputStream oout = new ObjectOutputStream(fout); oout.writeObject(p); oout.close(); } catch (Throwable t){ // Forward to handler } finally{ if (fout != null){ try{ fout.close(); } catch (IOException x){ // handle error } } } } }
審計:
對於已經被確定為敏感的數據搜索示例一中相關的關鍵字。
或者查看進行序列化的類,是否含有敏感數據。
修護:
- 將敏感數據加上transient
private transient double x; // transient field will not be serialized private transient double y; // transient field will not be serialized
- 將能序列化的加入
serialPersistentFields
,那么其余將不會被序列化
public class GPSLocation implements Serializable { private double x; private double y; private String id; // sensitive fields x and y are not content in serialPersistentFields private static final ObjectStreamField[] serialPersistentFields = {new ObjectStreamField("id", String.class)};// other content }
靜態內部類的序列化篇
簡單描述:
沒懂
審計:
人工查找 implements Serializable 的所有內部類
修護:
class \${InnerSer} {}
去除內部類的序列化。
static class \${InnerSer} implements Serializable {}把內部類聲明為靜態從而被序列化。但是要注意遵循示例三中的敏感信息問題
路徑安全篇
簡單描述:
攻擊者利用../
可以上傳至任意指定目錄。
服務端使用getAbsolutePath()
的話無法檢測出攻擊者真正上傳的文件路徑,因此即使做了過濾也將可被繞過。
示例:
當前目錄E:\workspace\myTestPathPrj(windows系統)
public static void testPath() throws Exception{ File file = new File("..\\src\\ testPath.txt"); System.out.println(file.getAbsolutePath()); System.out.println(file.getCanonicalPath()); }
file.getAbsolutePath()
打印出E:\workspace\myTestPathPrj\..\src\testpath.txt
file.getCanonicalPath()
打印出E:\workspace\src\testPath.txt
審計:
-
查找
permission Java.io.FilePermission
字樣和grant
字樣,看是否已經做出防御。 -
查找
getAbsolutePath()
和getPath()
,找到后看有沒有用戶輸入的。
ZIP文件提取篇
簡單描述:
兩個危害:一個是提取出的文件標准路徑落在解壓的目標目錄之外,另一個是提取出的文件消耗過多的系統資源。
示例:
-
解壓后的文件名未作過濾(直接
entry.getName()
);未對上傳的壓縮包大小作限制(
zis.read
后直接dest.write
)
static final int BUFFER = 512; // ... public final void unzip(String fileName) throws java.io.IOException { FileInputStream fis = new FileInputStream(fileName); ZipInputStream zis = new ZipInputStream(new BufferedInputStream(fis)); ZipEntry entry; while ((entry = zis.getNextEntry()) != null) { System.out.println("Extracting: " + entry); int count; byte data[] = new byte[BUFFER]; // Write the files to the disk FileOutputStream fos = new FileOutputStream(entry.getName()); BufferedOutputStream dest = new BufferedOutputStream(fos, BUFFER); while ((count = zis.read(data, 0, BUFFER)) != -1) { dest.write(data, 0, count); } dest.flush(); dest.close(); zis.closeEntry(); } zis.close(); }
-
解壓后的文件名未作過濾;
使用
getSize()函數不能准確判斷壓縮包大小
,攻擊者可以修改壓縮包的16進制編碼進行繞過。惡意攻擊者可以偽造 ZIP 文件中用來描述解壓條目大小的字段,因此,getSize()方法的返回值是不可靠的。
public static final int BUFFER = 512; public static final int TOOBIG = 0x6400000; // 100MB // ... public final void unzip(String filename) throws java.io.IOException { FileInputStream fis = new FileInputStream(filename); ZipInputStream zis = new ZipInputStream(new BufferedInputStream(fis)); ZipEntry entry; try{ while ((entry = zis.getNextEntry()) != null) { System.out.println("Extracting: " + entry); int count; byte data[] = new byte[BUFFER]; // Write the files to the disk, but only if the file is not insanely if (entry.getSize() > TOOBIG) { throw new IllegalStateException("File to be unzipped is huge."); } if (entry.getSize() == -1) { throw new IllegalStateException("File to be unzipped might be huge."); } FileOutputStream fos = new FileOutputStream(entry.getName()); BufferedOutputStream dest = new BufferedOutputStream(fos,BUFFER); while ((count = zis.read(data, 0, BUFFER)) != -1) { dest.write(data, 0, count); } dest.flush(); dest.close(); zis.closeEntry(); } } finally{ zis.close(); } }
審計:
搜索以下函數,看是否有使用到:
- FileInputStream
- ZipInputStream
- getSize()
- ZipEntry
如果出現 getSize 基本上就需要特別注意了。
修護:
- 防止解壓至任何目錄,使用
getCanonicalPath()
,過濾。
File f = new File(intendedDir, entryName); String canonicalPath = f.getCanonicalPath(); File iD = new File(intendedDir); String canonicalID = iD.getCanonicalPath(); if (canonicalPath.startsWith(canonicalID)) { return canonicalPath; } else { ... }
- 防止過大
BufferedOutputStream dest = new BufferedOutputStream(fos, BUFFER); while (total + BUFFER <= TOOBIG && (count = zis.read(data, 0, BUFFER)) != -1)
文件上傳篇
JDK<1.7.40的版本存在空字節問題。
文件讀取篇
簡單描述:
可讀取用戶輸入的文件路徑並回顯在響應中。
審計:
快速發現這類漏洞的方式其實也是非常簡單的,在IDEA中的項目中重點搜下如下文件讀取的類。
-
JDK原始的java.io.FileInputStream類
-
JDK原始的java.io.RandomAccessFile類
-
Apache Commons IO提供的org.apache.commons.io.FileUtils類
-
JDK1.7新增的基於NIO非阻塞異步讀取文件的
java.nio.channels.AsynchronousFileChannel
類 -
JDK1.7新增的基於NIO讀取文件的
java.nio.file.Files
類常用方法如:
Files.readAllBytes
、Files.readAllLines
如果仍沒有什么發現可以搜索一下FileUtil
,很有可能用戶會封裝文件操作的工具類。(參考)
URL重定向篇
簡單描述:
接口從host頭或者參數中取值,直接跳轉到用戶自定義的url,導致url重定向。
示例:
訪問不存在的資源,將host改成自定義url,頁面302跳轉,跳轉地址為host頭中的自定義url。
@RequestMapping("/urltest") @ResponseBody public String urltest(HttpServletRequest request,HttpServletResponse response) throws ServletException, IOException { String site = request.getParameter("url"); if(!site.isEmpty()){ response.sendRedirect(site); } return response.toString(); }
審計:
查找sendRedirect
,跳轉的url是否用戶可控,如果可控是否有進行過濾判斷。
特別是在刪掉某個資源的斜杠,有可能就進行了302跳轉,該處時常出現url重定向漏洞。
Autobinding篇
簡單描述:
將HTTP請求參數綁定到程序代碼變量或對象中。邏輯型漏洞。
-
@ModelAttribute注解
- 運用在參數上,會將
客戶端傳遞過來的參數
按名稱注入到指定對象
中,並且會將這個對象自動加入ModelMap
中,便於View層使用 - 運用在方法上,會在每一個@RequestMapping標注的方法前執行,如果有返回值,則自動將該返回值加入到ModelMap中
@RequestMapping(value = "/home", method = RequestMethod.GET) public String home(@ModelAttribute User user, Model model) { if (showSecret){ model.addAttribute("firstSecret", firstSecret); } return "home"; }
前端jsp中可使用
${user.name}
訪問對象user中的name成員。注意這時候這個User類一定要有沒有參數的構造函數。 - 運用在參數上,會將
-
@SessionAttributes注解
- 將ModelMap 中的屬性轉存到 session 中
- 只要不去調用SessionStatus的
setComplete()
方法,這個對象就會一直保留在 Session 中
示例:
在/resetQuestion
接口,從客戶端傳入user的成員answer=hhd
,因為代碼@ModelAttribute User user
,answer將會注入到user對象,並自動加入ModelMap中。
在/reset
接口,因為代碼@SessionAttributes("user")
,將user對象從ModelMap中讀出並放入session中,因此user中的answer=hhd
也加入了session中。那么只需要輸入問題的答案為hhd
則與session中的hhd
匹配,因此可繞過。
@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"; }
@RequestMapping(value = "/resetQuestion", method = RequestMethod.GET) public String resetViewQuestionHandler(@ModelAttribute User user) { logger.info("Welcome resetQuestion ! " + user); return "resetQuestion"; }
修護:
Spring MVC中可以使用@InitBinder注解,通過WebDataBinder的方法setAllowedFields、setDisallowedFields設置允許或不允許綁定的參數。
Webservice篇
Web Service
是一種基於SOAP協議
實現的跨語言Web服務調用。配置web.xml,配置server-config.wsdd
文件注冊Web Service
服務類和方法。
訪問Web Service
的FileService
服務加上?wsdl
參數可以看到FileService
提供的服務方法和具體的參數信息。
一般掃描目錄時可掃出,后帶?wsdl的是接口總的說明文檔
此類漏洞可使用burpsuite的wsdl插件,直接進行解析生成不同接口的request,再發送到repeater
接口可能是查詢,可能是添加等等操作
根據報錯信息,可能存在注入點
- sql注入
- 信息泄漏
- 通過信息泄漏引起的組件
第三方組件安全篇
比如struts2、不安全的編輯控件、fastjson等等。