安全問題其實是很多程式設計師想了解又容易忽略的問題,但需要我們重視起來,提高應用程式的安全性。常出現的安全問題包括,程式接受資料可能來源於未經驗證的使用者,網路連線和其他不受信任的來源,如果未對程式接受資料進行校驗,則可能會引發安全問題等等,具體也可以分成以下幾方面:
- 資料校驗
- 敏感資訊
- 加密演算法
- 序列化與反序列化
- I/O操作
- 多執行緒安全
- 框架和元件
資料校驗
資料校驗-校驗策略
1. 白名單策略 -接受已知好的資料( 任何時候,儘可能使用“白名單”的策略 )
下面的示例程式碼確保 name引數只包含字母、以及下劃線
if (Pattern.matches("^[0 -9A -Za -z_]+$", name)){
throw new IllegalArgumentException("Invalid name");
}
複製程式碼
2. 黑名單策略 -拒絕已知好的資料
public String removeJavascript(String input){
Pattern p = Pattern.compile("javascript", Pattern.CASE_INSENSITIVE );
Matcher m = p.matcher(input);
return (! m.matches()) ? input : "";
}
複製程式碼
3. 白名單淨化
對資料中任何不屬於某個已驗證的、合法字元列表進行刪除編碼或者替換,然後再使用這些淨化的資料
4. 黑名單淨化: 剔除或者轉換某些字元(例如,刪除引號、轉換成HTML實體)
public static String quoteApostrophe(String input){
if (input != null){
return input.replaceAll(" \'","’");
} else{
return null;
}
}
複製程式碼
資料校驗 -輸入輸出
規則1.1 校驗跨信任邊界傳遞的不可資料**
程式接受的不可信資料來源跨越任邊界傳遞必須經過內校驗,包括輸入和出校驗。 不可信資料:使用者、網路連線等源 不可信資料:使用者、網路連線等源 資料入口:
- 終端計算機
- 網際網路出入口
- 廣域網出入口
- 公司對外發布服務的 DMZ伺服器
- VPN和類似遠端連線裝置。 信任邊界:根據威脅建模劃分的信任邊 如 web 應用的服務端;
規則 1.2:禁止直接使用不可信資料來拼SQL語句
SQL 注入是指原始SQL查詢被動態更改成一個與程式預期完全不同的查詢。執行這樣後可能導致資訊洩露或者資料被篡改。防止 SQL隱碼攻擊的方式主要可以分為兩類:
-
使用引數化查詢 (推薦使用)
-
對不可信資料進行校驗
-
預編譯處理
Statement stmt= null; ResultSet rs= null; try{ String userName= ctx.getAuthenticatedUserName(); //this is a constant String sqlString= "SELECT * FROM t_item WHERE owner='" + userName+ "' AND itemName='" + request.getParameter("itemName") + "'"; stmt= connection.createStatement(); rs= stmt.executeQuery(sqlString);// ... result set handling } 新增 name' OR 'a' = 'a SELECT * FROM t_item WHERE owner = 'wiley' AND itemname= 'name' OR 'a'='a'; 複製程式碼
預編譯處理:
PreparedStatement stmt= null
ResultSet rs=null
try
{
String userName= ctx.getAuthenticatedUserName(); //this is a constant
String itemName= request.getParameter("");
// ...Ensure that the length of userName and itemNameis legitimate
// ...
String sqlString= "SELECT * FROM t_item WHERE owner=? AND itemName=?";
stmt= connection.prepareStatement(sqlString);
stmt.setString(1, userName);
stmt.setString(2, itemName);
rs=stmt.executeQuery();
// ... result set handling
}catch(SQLExceptions e)
{
// ... logging and error handling
}
複製程式碼
在儲存過程中,通拼接引數值來構建查詢字串和應用序程式碼一樣同是有SQL隱碼攻擊風險 反例:
CallableStatement= null
ResultSet results = null;
try{
String userName= ctx.getAuthenticatedUserName(); //this is a constant
String itemName= request.getParameter("itemName");
cs= connection.prepareCall("{call sp_queryItem(?,?)}");
cs.setString(1, userName);
cs.setString(2, itemName);
results = cs.executeQuery();
// ... result set handling
}catch(SQLException se){
// ... logging and error handling
}
複製程式碼
對應的SQL Server儲存過程:
CREATE PROCEDURE sp_queryItem
@userNamevarchar(50),
@itemNamevarchar(50)
AS
BEGIN
DECLARE @sql nvarchar(500);
SET @sql= 'SELECT * FROM t_item
WHERE owner = ''' + @userName+ '''
AND itemName= ''' + @itemName+ '''';
EXEC(@sql);
END
GO
複製程式碼
正例:
** 在儲存過程中動態構建sql,採用預編譯的方式防禦sql注入,**
CallableStatement= null
ResultSet results = null;
try{
String userName= ctx.getAuthenticatedUserName(); //this is a constant
String itemName=request.getParameter("itemName");
// ... Ensure that the length of userName and itemName is legitimate
// ...
cs= ("{call sp_queryItem(?,?)}");
cs.setString(1, userName);
cs.setString(2, itemName);
results = cs.executeQuery();
// ... result set handling
}catch(SQLException se){
// ... logging and error handling
}
複製程式碼
對應的SQL Server儲存過程:
CREATE PROCEDURE sp_queryItem
@userName varchar(50),
@itemName varchar(50)
AS
BEGIN
SELECT * FROM t_item
WHERE userName= @userName
AND itemName= @itemName;
END
複製程式碼
使用Hibernate,如果在動態構建SQL/HQL查詢時包含了不可信輸入,同樣也會面臨SQL/HQL注入的問題。
反例:
//原生sql查詢
String userName= ctx.getAuthenticatedUserName();
//this is a constant
String itemName= request.getParameter("itemName");
Query sqlQuery= session.createSQLQuery("select * from where owner = '"
+ userName+ "' and itemName= '" + itemName+ “’”);
List<Item> rs= (List<Item>) sqlQuery.list();
//HQL查詢
String userName= ctx.getAuthenticatedUserName();
//this is a constant
String itemName=request.getParameter("itemName");
Query hqlQuery= session.createQuery("from Item as item where item.owner= '"
+ userName+ "' and = '" + itemName+ "'");
List<Item> hrs= (List<Item>) hqlQuery.list();
複製程式碼
正例:
//HQL中基於位置的引數化查詢:
String userName= ctx.getAuthenticatedUserName();
String itemName=request.getParameter("itemName");
Query hqlQuery= session.createQuery("from Item as item where item.owner= ? and item.itemName= ?");
hqlQuery.setString(1, userName);
hqlQuery.setString(2, itemName);
List<Item> rs= (List<Item>) hqlQuery.list();
//HQL中基於名稱的引數化查詢:
String userName= ctx.getAuthenticatedUserName();
String itemName= ("itemName");
Query hqlQuery= session.createQuery("from Item as item where item.owner= :owner and = :itemName");
hqlQuery.setString("owner", userName);
hqlQuery.setString("itemName", itemName);
List<Item> rs= (List<Item>) hqlQuery.list();
//原生引數化查詢:
String userName=ctx.getAuthenticatedUserName(); //this is a constant
String itemName= request.getParameter("itemName");
Query sqlQuery= session.createSQLQuery("select * from t_itemwhere owner = ? and itemName= ?");
sqlQuery.setString(0, owner);
sqlQuery.setString(1, itemName);
List<Item> rs= (List<Item>) sqlQuery.list();
複製程式碼
Mybaits和ibaits的#和$
Mybaits:
<select id="getItems" parameterClass="MyClass" resultClass="Item">
SELECT * FROM t_item
WHERE owner = #userName# AND itemName= #itemName#
</select>
String sqlString= "SELECT * FROM t_itemWHERE owner=? AND itemName=?";
PreparedStatement stmt= connection.prepareStatement(sqlString);
stmt.setString(1, myClassObj.getUserName());
stmt.setString(2, myClassObj.getItemName());
ResultSet rs= stmt.executeQuery();
// ... convert results set to Item objects
複製程式碼
ibaits:
<select id="getItems" parameterClass="MyClass"="items">
SELECT * FROM t_item
WHERE owner = #userName# AND itemName= '$itemName$'
</select>
String sqlString= "SELECT * FROM t_itemWHERE owner=? AND itemName='" +myClassObj.getItemName() + "'";
PreparedStatementstmt=connection.prepareStatement(sqlString);
stmt.setString(1, myClassObj.getUserName());
ResultSetrs= stmt.executeQuery();
複製程式碼
輸入驗證,針對無法引數化查詢的場景
public List<Book> queryBooks(queryCondition){
try{
StringBuilder sb= StringBuilder("select * from t_bookwhere ");
Codec oe= new OracleCodec();
if(queryCondition!= null&& !queryCondition.isEmpty()){
for(Expression e : queryCondition){
String exprString=e.getColumn() + e.getOperator() + e.getValue();
String safeExpr= ESAPI.encoder().encodeForSQL(oe, exprString);
sb.append(safeExpr).append(" and ");
}
sb.append("1=1");
Statement stat = connection.createStatement();
ResultSet rs= stat.executeQuery(sb.toString());
//other omitted code
}
}
}
複製程式碼
規則1.3 禁止直接使用不可信資料來拼接XML
一個使用者,如果他被允許輸入結構化的XML片段,則他可以在XML的資料域中注入XML標籤來改寫目標XML文件的結構與內容。XML解析器會對注入的標籤進行識別和解釋。
private void createXMLStream(BufferedOutputStreamoutStream, 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();}}
複製程式碼
新增joeadministratorjoe
<user>
<role>operator</role>
<id>joe</id>
<role>administrator</role>
<id>joe</id>
<description>I want to be an administrator</description>
</user>
複製程式碼
XML Schema或者DTD校驗,反例:
private void createXMLStream(BufferedOutputStreamoutStream, User user)throwsIOException{
String xmlString;
xmlString= "<user><id>" + user.getUserId()+ "</id><role>operator</role><description>"+ user.getDescription() + "</description></user>";
StreamSource xmlStream= new StreamSource(new StringReader(xmlString));
// Build a validating SAX parser using the schema
SchemaFactory sf = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
StreamSource ss= new StreamSource(newFile("schema.xsd"));
try{
Schema schema= sf.newSchema(ss);
Validator validator= schema.newValidator();
validator.validate(xmlStream);
}catch(SAXException x){
throw new IOException("Invalid userId", x);
}
// the XML is valid, proceed
outStream.write(xmlString.getBytes());
outStream.flush();
}
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:element name="user">
<xs:complexType>
<xs:sequence>
<xs:elementname="id" type="xs:string"/>
<xs:element name="role"type="xs:string"/>
<xs:element name="description" type="xs:string"/>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:schema>
複製程式碼
某個惡意使用者可能會使用下面的字串作為使用者ID:
"joe</id>Administrator</role>
<!—"並使用如下字串作為描述欄位:
"-><description>I want to be an administrator"
<user>
<id>joe</id>
<role>Administrator</role><!--</id>
<role>operator</role> <description> -->
<description>I want to be an administrator</description>
</user>
複製程式碼
安全做法:白名單+安全的xml庫
private void createXMLStream(BufferedOutputStreamoutStream, User user) throws IOException{
// Write XML string if userID contains alphanumeric and underscore characters only
if (!Pattern.matches("[_a-bA-B0-9]+", user.getUserId())){
// Handle format violation
}
if (!Pattern.matches("[_a-bA-B0-9]+", user.getDescription())){
// Handle format violation
}
String xmlString= "<user><id>"+ user.getUserId()+ "</id><role>operator</role><description>"+ user.getDescription() + "</description></user>";
outStream.write(xmlString.getBytes());
outStream.flush();
}
public static void buidlXML(FileWriterwriter, User user) throwsIOException{
Document userDoc= DocumentHelper.createDocumen();
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();
}
}
複製程式碼
Xml注入淨化之後的資料
<user>
<id>joe</id><role>Administrator</role><!—</id>
<role>operator</role>
<description>-->lt;description>Iwant to be an administrator</description>
</user>
複製程式碼
規則1.4:禁止直接使用不可信資料來記錄日誌
如果在記錄的日誌中包含未經校驗的不可信資料,則可能導致日誌注入漏洞。惡意使用者會插入偽造的日誌資料,從而讓系統
管理員誤以為這些日誌資料是由系統記錄的。例如,一個使用者可能通過輸入一個回車符和一個換行符(CRLF)序列來將一
條合法日誌拆分成兩條日誌,其中每一條都可能會令人誤解。
將未經淨化的使用者輸入寫入日誌還可能會導致向信任邊界之外洩露敏感資料,或者導致違反當地法律法規,在日誌中寫入和儲存了某些型別的敏感資料。
if(loginSuccessful){
logger.severe("User login succeeded for: "+ username);
}else{
logger.severe("User login failed for: "+ username);
}
複製程式碼
生成log:
david May 15, 2011 2:25:52 PM java.util.logging.LogManager$RootLogger.log
SEVERE: User login succeeded for: administrator
May 15, 2011 2:19:10 PM java.util.logging.LogManager$RootLogger log
SEVERE: User login failed for: david
May 15, 2011 2:25:52 PM java.util.logging.LogManager log
SEVERE: User login succeeded for: administrator
Username=David(生成標準日誌)
May 15, 2011 2:19:10 PM java.util.logging.LogManager$RootLogger log
SEVERE: User login failed for: david
複製程式碼
登入之前會對使用者名稱輸入進行淨化,從而防止注入攻擊
if(!Pattern.("[A-Za-z0-9_]+", username)){
// Unsanitized username
logger.severe("User login failed for unauthorized user");
}else if(loginSuccessful){
logger.severe("User login succeeded for: "+ username);
}else{
logger.severe("User login failed for: "+ username);
}
複製程式碼
規則1.5:禁止向Runtime.exec() 方法傳遞不可信、未淨化的資料
在執行任意系統命令或者外部程式時使用了未經校驗的不可信輸入,就會導致產生命令和引數注入漏洞。
class DirList{
public static void main(String[] args){
if(args.length== 0){
System.out.println("No arguments");
System.exit(1);
}
try{
Runtime rt= Runtime.getRuntime();
Process proc = rt.exec("cmd.exe /c dir" + args[0]);
// ...
}catch(Exception e){
// Handle errors
}
}
}
複製程式碼
java DirList"dummy & echo bad"
dirdummy echo bad
安全建議:
-
避免直接使用Runtime.exec(),採用標準的API替代執行系統命令來完成任務
-
白名單資料校驗和資料淨化
class DirList{ public static void main(String[] args){ if(args.length== 0){ System.out.println("No arguments"); System.exit(1); } try{ File dir= newFile(args[0]); // the dir need to be validated if (!validate(dir)) { System.out.println("An illegal directory"); }else{ for (String file : dir.list()){ System.out.println(file); } } } } } 複製程式碼
型別 | 舉例 | 常見注入模式和結果 |
---|---|---|
管道 | | | | shell_command -執行命令並返回命令輸出資訊 |
內聯 | ; & |
; shell_command -執行命令並返回命令輸出資訊 & shell_command -執行命令並返回命令輸出資訊 |
邏輯運算子 | $ && || |
$(shell_command) -執行命令 && shell_command -執行命令並返回命令輸出資訊 || shell_command -執行命令並返回命令輸出資訊 |
重定向運算子 | > >> < |
> target_file -使用前面命令的輸出資訊寫入目標檔案 >> target_file -將前面命令的輸出資訊附加到目標檔案 < target_file-將目標檔案的內容傳送到前面的命令 |
規則1.6:驗證路徑之前應該先將其標準化
絕對路徑名或者相對路徑名中可能會包含檔案連結,對檔名標準化可以使得驗證檔案路徑更加容易,同時可以防禦目錄遍歷引發的安全漏洞。
public static void main(String[] args){
File f = newFile(System.getProperty("user.home")
+ System.getProperty("file.separator") + args[0]);
String absPath= f.getAbsolutePath();
if(!isInSecureDir(Paths.get(absPath))){
// Refer to Rule 3.5 for the details of isInSecureDir()
throw new IllegalArgumentException();
}
if(!validate(absPath)){
// Validation
throw new IllegalArgumentException();
}
/* … */
}
public static void main(String[] args) throwsIOException{
File f = newFile(System.getProperty("user.home")
+ System.getProperty("file.separator") + args[0]);
String canonicalPath= f.getCanonicalPath();
if(!isInSecureDir(Paths.get(absPath))){
// Refer to Rule3.5 for the details of isInSecureDir()
throw new IllegalArgumentException();
}
if(!validate(absPath)){
// Validation
throw new IllegalArgumentException();
}
/* ... */
}
複製程式碼
規則1.7:安全地從ZipInputStream提取檔案
-
提取出的檔案標準路徑落在解壓的目標目錄之外-跨目錄解壓攻擊,
-
是提取出的檔案消耗過多的系統資源-zip壓縮炸彈。
static final int BUFFER= 512; // ... public final void unzip(String fileName) throws java.io.IOException{ FileInputStream fis= new FileInputStream(fileName); ZipInputStream zis= new ZipInputStream(newBufferedInputStream(fis)); ZipEntry entry; while((entry = zis.getNextEntry()) != null){ System.out.println("Extracting: "+ entry); int count; byte data[] = newbyte[BUFFER]; // Write the files to the disk FileOutputStreamfos= new FileOutputStream(entry.getName()); BufferedOutputStreamdest= 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(); } 複製程式碼
未對解壓的檔名做驗證,直接將檔名傳遞給FileOutputStream構造器。它也未檢查解壓檔案的資源消耗情況,它允許程式執行到操作完成或者本地資源被耗盡
示例
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= newFileInputStream(filename);
ZipInputStreamzis= newZipInputStream(newBufferedInputStream(fis));
ZipEntry entry;
try{
while((entry = zis.getNextEntry()) != null){
System.out.println("Extracting: "+ entry);
int count;
byte data[] = new byte[BUFFER];
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.");
}
FileOutputStreamfos= newFileOutputStream(entry.getName());
BufferedOutputStreamdest= new BufferedOutputStream(fos,BUFFER);
while((count = zis.read(data, 0, BUFFER)) != -1){
dest.write(data, 0, count);
}
dest.flush();
dest.close();
zis.closeEntry();
}
}
}
複製程式碼
ZipEntry.getSize()方法在解壓提取一個條目之前判斷其大小,以試圖解決之前的問題。攻擊者可以偽造ZIP檔案中用來描述解壓條目大小的欄位,因此,getSize()可靠的,本地資源實際仍可能被過度消耗
static final int BUFFER= 512;
static final int TOOBIG= 0x6400000; // max size of unzipped data, 100MB
static final int TOOMANY = 1024; // max number of files
// ...
private String sanitzeFileName(String entryName, String intendedDir) throws IOException{
File f = newFile(intendedDir, entryName);
String canonicalPath= f.getCanonicalPath();
File iD= newFile(intendedDir);
String canonicalID= iD.getCanonicalPath();
if(canonicalPath.startsWith(canonicalID)){
return canonicalPath;
}else{
throw new IllegalStateException("File is outside extraction target directory.");
}
}
public final void unzip(String fileName) throws java.io.IOException{
FileInputStream fis= new FileInputStream(fileName);
ZipInputStream zis= newZipInputStream(newBufferedInputStream(fis));
ZipEntryentry;
int entries = 0;
int total = 0;
byte[] data = newbyte[BUFFER];
try{
while((entry = zis.getNextEntry()) != null){
System.out.println("Extracting: "+ entry);
int count;
String name = sanitzeFileName(entry.getName(), ".");
FileOutputStream fos= newFileOutputStream(name);
BufferedOutputStream dest= new BufferedOutputStream(fos, BUFFER);
while (total + BUFFER<= && (count = zis.read(data, 0, BUFFER)) != -1){
dest.write(data, 0, count);
total += count;
}
dest.flush();
dest.close();
zis.closeEntry();
entries++;
if(entries > TOOMANY){
throw new IllegalStateException("Too many files to unzip.");
}
if(total > TOOBIG){
throw new IllegalStateException("File being unzipped is too big.");
}
}
}
}
複製程式碼
規則1.8:禁止未經驗證的使用者輸入直接輸出到html介面
使用者輸入未經過驗證直接輸出到html介面容易導致xss注入攻擊,該攻擊方式可以盜取使用者cookie資訊,嚴重的可以形成xss蠕蟲攻擊漏洞,也可以結合其他的安全漏洞進一步進行攻擊和破壞系統
反例:
String eid=request.getParameter("eid");
eid=StringEscapeUtils.escapeHtml(eid);//insufficient validation
...
ServletOutputStream out=response.getOutputStream();
out.print("Employee ID:"+eid);
...
out.close();
...
複製程式碼
正例:
...
Statement stmt=conn.creatStatement();
ResultSet rs=stmt.executeQuery("select * from emp where id ="+eid);
if(rs != null){
rs.next();
String name=StringEscapeUtils.escapeHtml(rs.getString("name"));//insufficient validation
}
ServletOutputStream out =response.getOutputStream();
...
out.close();
...
複製程式碼
資料型別 | 上下文 | 示例程式碼 | 防禦措施 |
---|---|---|---|
string | HTML Body | <span>UNTRUSTED DATA</span> | HTML Entity編碼 |
String | 安全HTML變數 | <input type="text" name="fname" value="UNTRUSTED DATA"> | 1. HTML Attribute編碼 2. 只把不可信資料放在安全白名單內的變數上(白名單在下文列出) 3. 嚴格地校驗不安全變數,如background、id和name |
String | GET引數 | <a href="/site/search?value=UNTRUSTED DATA">clickme | URL編碼 |
String | 使用在src或href變數上的不可信URLs | <a href="UNTRUSTED URL">clickme</a><iframe src="UNTRUSTED URL" / | 1. 對輸入進行規範化; 2. URL校驗; 3. URL安全性認證 4. 只允許使用http和https協議(避免使用JavaScript協議去開啟一個新視窗) 5. HTML Attribute編碼 |
String | CSS值 | <div style="width: UNTRUSTED DATA;">Selection</div> | 1. 使用CSS編碼; 2. 使用CSS Hex編碼; 3. 良好的CSS設計 |
String | JavaScript變數 | <script> var currentValue='UNTRUSTED DATA';</script> <script>someFunction('UNTRUSTED DATA');</script> |
1. 確保所有變數值都被引號括起來; 2. 使用JavaScript Hex編碼 3. 使用JavaScript Unicode編碼; 4. 避免使用“反斜槓轉譯”(\"、\'或者\) |
HTML | HTML Body | <div>UNTRUSTED HTML</div> | [HTML校驗(JSoup, AntiSamy, HTML Sanitizer)] |
String | DOM XSS | <script> document.write("UNTRUSTED INPUT: " + document.location.hash);<script/> | 基於DOM操作的XSS漏洞防禦措施 |
1、輸入過濾:客戶端求情引數:包括使用者輸入,url引數、post引數。
- 在產品形態上,針對不同輸入型別,對輸入做變數型別限制。
- 字串型別的資料,需要針對<、>、/、’、”、&五個字元進行實體化轉義 2、輸出編碼:瀏覽器解析中html和js編碼不一樣,以及上下文場景多樣,所以對於後臺輸出的變數,不同的上下文中渲染後端變數,轉碼不一樣。
特殊字元 | 實體編碼 |
---|---|
& | & |
< | < |
> | > |
“ | " |
/ | / |
‘ | ' |
規則1.9:禁止直接解析未驗證的xml實體
當允許引用外部實體時,若程式針對輸入xml實體未驗證,攻擊者通過構造惡意內容,進行xxe注入攻擊,可導致讀取任意檔案、執行系統命令、探測內網埠、攻擊內網網站等危害 反例:
public void transform(InputStream xmlStream,OutputStream output)throws Exception{
Transformer trans=null;
TransformerFactory transFactory=TransformerFactory.newInstance();
if(this.style!=null){
trans=transFactory.newTranformer(this.style);
}else{
trans=transFactory.newTranformer();
}
/*********UTF-8***/
trans.setOutputProperty(OutputKeys.ENCOOING,"UTF-8");
Source source=new SAXSource(new InputSource(xmlStream));
Result result=new StreamResult(output);
trans.transform(source,result);
}
複製程式碼
正例:
public void transform(InputStream xmlStream,OutputStream output)throws Exception{
Transformer trans=null;
TransformerFactory transFactory=TransformerFactory.newInstance();
if(this.style!=null){
//trans=transFactory.newTranformer(this.style);
TransformerFactory trans=TransformerFactory.newInstance();
trfactory.setFeature(XMLConstans.FEATURE_SECURE_PROCESSING,true);
trfactory.setAttribute((XMLConstans.ACCESS_EXTERNAL_DTD,"");
trfactory.setAttribute((XMLConstans.ACCESS_EXTERNAL_STYLESHEET,"");
}else{
//trans=transFactory.newTranformer();
TransformerFactory trans=TransformerFactory.newInstance();
trfactory.setFeature(XMLConstans.FEATURE_SECURE_PROCESSING,true);
trfactory.setAttribute((XMLConstans.ACCESS_EXTERNAL_DTD,"");
trfactory.setAttribute((XMLConstans.ACCESS_EXTERNAL_STYLESHEET,"");
}
/*********UTF-8***/
trans.setOutputProperty(OutputKeys.ENCOOING,"UTF-8");
Source source=new SAXSource(new InputSource(xmlStream));
Result result=new StreamResult(output);
trans.transform(source,result);
}
複製程式碼
不同xml解析器防禦xxe注入的方法:
XMLReader
To protect a java org.xml.sax.XMLReader from XXE,do this:
XMLReader reader=XMLReaderFactory.createXMLReader();
reader.setFeatrue("http://apache.org/xml/features/disallow-doctype-decl",true);
reader.setFeatrue("http://apache.org/xml/features/disallow-doctype-decl",true);
//stictly required as DTDs should not be allowed at all ,per previous
reader.setFeatrue("http://xml.org/sax/features/external-general-entilies",false);
reader.setFeatrue("http://xml.org/sax/features/external-parameter-entilies",false);
複製程式碼
SAXReader To protect a java org.dom4j.io.SAXReader from XXE,do this:
saxReader.setFeatrue("http://apache.org/xml/features/disallow-doctype-decl",true);
saxReader.setFeatrue("http://xml.org/sax/features/external-general-entilies",false);
saxReader.setFeatrue("http://xml.org/sax/features/external-parameter-entilies",false);
複製程式碼
後續會把涉及的其他安全問題全部寫出來,可關注本人的下篇文章。
最後可關注公眾號,一起學習,每天會分享乾貨,還有學習視訊領取!