安全开发规范:开发人员必须了解开发安全规范(一)(涉及安全问题,以及解决方法和代码实现)

简介: 安全问题其实是很多程序员想了解又容易忽略的问题,但需要我们重视起来,提高应用程序的安全性。常出现的安全问题包括,程序接受数据可能来源于未经验证的用户,网络连接和其他不受信任的来源,如果未对程序接受数据进行校验,则可能会引发安全问题等等

安全问题其实是很多程序员想了解又容易忽略的问题,但需要我们重视起来,提高应用程序的安全性。常出现的安全问题包括,程序接受数据可能来源于未经验证的用户,网络连接和其他不受信任的来源,如果未对程序接受数据进行校验,则可能会引发安全问题等等,具体也可以分成以下几方面:


  • 数据校验
  • 敏感信息
  • 加密算法
  • 序列化与反序列化
  • I/O操作
  • 多线程安全
  • 框架和组件


29.png


数据校验


数据校验-校验策略


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 校验跨信任边界传递的不可数据**


程序接受的不可信数据源跨越任边界传递必须经过内校验,包括输入和出校验。 不可信数据:用户、网络连接等源 不可信数据:用户、网络连接等源 数据入口:


  1. 终端计算机
  2. 互联网出入口
  3. 广域网出入口
  4. 公司对外发布服务的 DMZ服务器
  5. VPN和类似远程连接设备。 信任边界:根据威胁建模划分的信任边 如 web 应用的服务端;


规则 1.2:禁止直接使用不可信数据来拼SQL语句


SQL 注入是指原始SQL查询被动态更改成一个与程序预期完全不同的查询。执行这样后可能导致信息泄露或者数据被篡改。防止 SQL注入的方式主要可以分为两类:


  1. 使用参数化查询 (推荐使用)
  2. 对不可信数据进行校验
  3. 预编译处理


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&lt;/id&gt;&lt;role&gt;Administrator&lt;/role&gt;&lt;!—</id>
  <role>operator</role>
  <description>--&gtlt;description&gt;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


安全建议:


  1. 避免直接使用Runtime.exec(),采用标准的API替代运行系统命令来完成任务
  2. 白名单数据校验和数据净化


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提取文件


  1. 提取出的文件标准路径落在解压的目标目录之外-跨目录解压攻击,
  2. 是提取出的文件消耗过多的系统资源-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编码不一样,以及上下文场景多样,所以对于后台输出的变量,不同的上下文中渲染后端变量,转码不一样。


特殊字符 实体编码
& &amp
< &lt
> &gt
&quot
/ &#x2F
&#x27


规则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);
复制代码


后续会把涉及的其他安全问题全部写出来,可关注本人的下篇文章。



相关实践学习
日志服务之使用Nginx模式采集日志
本文介绍如何通过日志服务控制台创建Nginx模式的Logtail配置快速采集Nginx日志并进行多维度分析。
目录
相关文章
|
1月前
|
新零售 供应链 小程序
复购见单模式小程序开发系统|细节方案
人们之所以喜欢在网上购物,最主要的是价格便宜,而且还能够在短时间内就拿到手
|
3月前
|
安全 前端开发 测试技术
【测开方法论】当老功能代码命名不规范的时候...如何安全增加新功能
【测开方法论】当老功能代码命名不规范的时候...如何安全增加新功能
|
7月前
SAP 实施项目中涉及到编程方式操作 Excel 的几种场景介绍
SAP 实施项目中涉及到编程方式操作 Excel 的几种场景介绍
71 0
|
11月前
|
存储 安全 搜索推荐
详解软件开发的标准过程(生命周期):跟着标准搞,设计没烦恼
详解软件开发的标准过程(生命周期):跟着标准搞,设计没烦恼
|
测试技术
测试思想-测试流程 需求开发与管理简述
测试思想-测试流程 需求开发与管理简述
69 0
测试思想-测试流程 需求开发与管理简述
|
SQL 存储 XML
面向开发人员的代码安全指南
面向开发人员的代码安全指南
297 0
|
测试技术
软件测试面试题:依赖第三方数据的接口如何进行测试?
软件测试面试题:依赖第三方数据的接口如何进行测试?
427 0
|
设计模式 Java 程序员
怎样才能写出规范的好代码?
最近发现一件事情,自己写的代码和公司里工作5到10年的前辈写的代码虽然功能一样,但是他们的代码更规范,更优雅。比如有时候我会给一个需求写一个方法,但是有些人就可以好几个需求通过同一个方法实现。因此有了今天这个疑问,怎样才能写出规范的好代码?
|
关系型数据库 数据库
重构老系统遗留代码的一些方法学习笔记
重构老系统遗留代码的一些方法学习笔记
111 0
重构老系统遗留代码的一些方法学习笔记
|
自然语言处理 程序员
为什么谷歌要执行严格的代码编写规范
我们在谷歌所做事情中另外一个让我感到异常有效、有用的制度是严格的编码规范。 在到Google工作之前,我一直认为编码规范没有什么用处。我坚信这些规范都是官僚制度下产生的浪费大家的编程时间、影响人们开发效率的东西。
186 0
 为什么谷歌要执行严格的代码编写规范