0x00 安全引言
1、傳統Web應用與新興移動應用
(1)傳統Web應用:瀏覽器 HTTP 服務器
(2)新興移動應用:APP HTTP 服務器
從安全角度看,傳統Web應用與新興移動應用沒有本質區別
2、Web應用安全的核心問題是什么?
用戶提交的數據不可信是Web應用程序核心安全問題
用戶可以提交任意輸入
例如:
√ 請求參數->多次提交或者不提交
√ 修改Cookie
√ 修改HTTP信息頭
√ 請求順序->跳過或者打亂
3、Web應用防御
(1)完善的異常處理
(2)監控
(3)日志:記錄重要業務、異常的詳細請求信息
4、對輸入的處理
建議采用:白名單
盡量避免:凈化或黑名單
0x01 SQL注入
1、原理:
(1)合法輸入:
id=1 SELECT * FROM users WHRER id='1';
(2)惡意注入:
id=1' or '1'='1 SELECT * FROM users WHRER id='1' or 'a'='a';
2、Java代碼分析(JDBC)
(1)不合規代碼(SQL參數拼接)
public class SQLInject { public static void main(String[] args)throws Exception{ //正常輸入 select("1"); // 惡意輸入 select("' or 'a'='a"); } public static void select(String id){ //聲明Connection對象 Connection con; //驅動程序名 String driver = "com.mysql.jdbc.Driver"; //URL指向要訪問的數據庫名mydata String url = "jdbc:mysql://localhost:3306/mybatis"; //MySQL配置時的用戶名 String user = "root"; //MySQL配置時的密碼 String password = "budi"; //遍歷查詢結果集 try { //加載驅動程序 Class.forName(driver); //1.getConnection()方法,連接MySQL數據庫!! con = DriverManager.getConnection(url,user,password); if(!con.isClosed()) System.out.println("Succeeded connecting to the Database!"); //2.創建statement類對象,用來執行SQL語句!! Statement statement = con.createStatement(); //要執行的SQL語句 String sql = "select * from users where id='"+id+"'"; //3.ResultSet類,用來存放獲取的結果集!! ResultSet rs = statement.executeQuery(sql); System.out.println("-----------------"); System.out.println("執行結果如下所示:"); System.out.println("-----------------"); String age,name; while(rs.next()){ //獲取stuname這列數據 name = rs.getString("name"); //獲取stuid這列數據 age = rs.getString("age"); //輸出結果 System.out.println(name + "\t" + age); } rs.close(); con.close(); } catch(ClassNotFoundException e) { //數據庫驅動類異常處理 System.out.println("Sorry,can`t find the Driver!"); e.printStackTrace(); } catch(SQLException e) { //數據庫連接失敗異常處理 e.printStackTrace(); }catch (Exception e) { // TODO: handle exception e.printStackTrace(); }finally{ System.out.println("數據庫數據成功獲取!!"); } } }
執行結果:
SQL Paramter:1 ----------------- budi 27 ----------------- SQL Paramter:' or 'a'='a ----------------- budi 27 budisploit 28 -----------------
(2)合規代碼(參數化查詢)

public class SQLFormat { public static void main(String[] args)throws Exception{ select("1"); select("' or 'a'='a"); } public static void select(String id){ //聲明Connection對象 Connection con; //驅動程序名 String driver = "com.mysql.jdbc.Driver"; //URL指向要訪問的數據庫名mydata String url = "jdbc:mysql://localhost:3306/mybatis"; //MySQL配置時的用戶名 String user = "root"; //MySQL配置時的密碼 String password = "budi"; //遍歷查詢結果集 try { //加載驅動程序 Class.forName(driver); //1.getConnection()方法,連接MySQL數據庫!! con = DriverManager.getConnection(url,user,password); if(!con.isClosed()) System.out.println("Succeeded connecting to the Database!"); //2.//要執行的SQL語句 String sql = "select * from users where id=?"; //3.創建statement類對象,ResultSet類,用來存放獲取的結果集!! PreparedStatement stmt = con.prepareStatement(sql); stmt.setString(1, id); ResultSet rs = stmt.executeQuery(); System.out.println("-----------------"); System.out.println("執行結果如下所示:"); System.out.println("-----------------"); String age,name; while(rs.next()){ //獲取stuname這列數據 name = rs.getString("name"); //獲取stuid這列數據 age = rs.getString("age"); //輸出結果 System.out.println(name + "\t" + age); } rs.close(); con.close(); } catch(ClassNotFoundException e) { //數據庫驅動類異常處理 System.out.println("Sorry,can`t find the Driver!"); e.printStackTrace(); } catch(SQLException e) { //數據庫連接失敗異常處理 e.printStackTrace(); }catch (Exception e) { // TODO: handle exception e.printStackTrace(); }finally{ System.out.println("數據庫數據成功獲取!!"); } } }
執行結果:
SQL Paramter:1
-----------------
budi 27
-----------------
SQL Paramter:' or 'a'='a
-----------------
-----------------
3、防范建議:
√ 采用參數查詢即預編譯方式(首選)
√ 字符串過濾
0x02 XML注入
1、原理
(1)合法輸入:
quantity=1 <item> <name>apple</name> <price>500.0</price> <quantity>1</quantity> <item>
(2)惡意輸入:
quantity=1</quantity><price>5.0</price><quantity>1 <item> <name>apple</name> <price>500.0</price> <quantity>1</quantity><price>5.0</price><quantity>1</quantity> <item>
2、Java代碼分析
(1)不合規代碼(未進行安全檢查)
public class XMLInject2 { public static void main(String[] args) { // 正常輸入 ArrayList<Map<String, String>> normalList=(ArrayList<Map<String, String>>) ReadXML("D:\\JavaWorkspace\\TestInput\\src\\cn\\com\\budi\\xml\\inject\\normal.xml","price"); System.out.println(normalList.toString()); // 異常輸入 ArrayList<Map<String, String>> evilList=(ArrayList<Map<String, String>>) ReadXML("D:\\JavaWorkspace\\TestInput\\src\\cn\\com\\budi\\xml\\inject\\evil.xml","price"); System.out.println(evilList.toString()); } private static List<Map<String,String>> ReadXML(String uri,String NodeName){ try { //創建一個解析XML的工廠對象 SAXParserFactory parserFactory=SAXParserFactory.newInstance(); //創建一個解析XML的對象 SAXParser parser=parserFactory.newSAXParser(); //創建一個解析助手類 MyHandler myhandler=new MyHandler(NodeName); parser.parse(uri, myhandler); return myhandler.getList(); } catch (Exception e) { e.printStackTrace(); } return null; } }
運行結果:
正常輸入結果:[{price=500.0}]
惡意輸入結果:[{price=500.0}, {price=5.0}]
(2)合規代碼(利用schema安全檢查)
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"> <xs:element name="item"> <xs:complexType> <xs:sequence> <xs:element name="name" type="xs:string"/> <xs:element name="price" type="xs:decimal"/> <xs:element name="quantity" type="xs:integer"/> </xs:sequence> </xs:complexType> </xs:element>
測試代碼
public class XMLFormat{ public static void main(String[] args) { //測試正常輸入 test("D:\\JavaWorkspace\\TestInput\\src\\cn\\com\\budi\\xml\\inject\\normal.xml"); //測試異常輸入 test("D:\\JavaWorkspace\\TestInput\\src\\cn\\com\\budi\\xml\\inject\\evil.xml"); } private static void test(String file) { SchemaFactory schemaFactory = SchemaFactory .newInstance("XMLConstants.W3C_XML_SCHEMA_NS_URI"); Schema schema; try { schema = schemaFactory.newSchema(new File("D:\\JavaWorkspace\\TestInput\\src\\cn\\com\\budi\\xml\\inject\\schema.xsd")); Validator validator = schema.newValidator(); validator.setErrorHandler(new ErrorHandler() { public void warning(SAXParseException exception) throws SAXException { System.out.println("警告:" + exception); } public void fatalError(SAXParseException exception) throws SAXException { System.out.println("致命:" + exception); } public void error(SAXParseException exception) throws SAXException { System.out.println("錯誤:" + exception); } }); validator.validate(new StreamSource(new File(file))); System.out.println("解析正常");; } catch (SAXException e) { // TODO Auto-generated catch block //e.printStackTrace(); System.out.println("解析異常"); } catch (IOException e) { // TODO Auto-generated catch block //e.printStackTrace(); System.out.println("解析異常"); } } }
運行結果:
正常輸入........
解析正常
惡意輸入........
錯誤:org.xml.sax.SAXParseException; systemId: file:/D:/JavaWorkspace/TestInput/src/cn/com/budi/xml/inject/evil.xml; lineNumber: 7; columnNumber: 10; cvc-complex-type.2.4.d: 發現了以元素 'price' 開頭的無效內容。此處不應含有子元素。
3、防范建議:
√ 文檔類型定義(Document Type Definition,DTD)
√ XML結構化定義文件(XML Schemas Definition)
√ 白名單
0x03 XXE (XML external entity)
1、原理:
(1)合法輸入:
<?xml version="1.0" encoding="utf-8" ?> <!DOCTYPE updateProfile [<!ENTITY lastname "Hello, Budi!"> <!ENTITY file SYSTEM "file:///D:/test.txt">]> <users > <firstname>&file</firstname> <lastname>&lastname;</lastname> </users>
(2)惡意輸入:
<?xml version="1.0" encoding="utf-8" ?> <!DOCTYPE updateProfile [<!ENTITY file SYSTEM "file:///D:/password.txt"> ]> <users > <firstname>&file;</firstname> <lastname>&lastname;</lastname> </users>
2、Java代碼分析
(1)不合規代碼(未安全檢查外部實體)
public class XXEInject { private static void receiveXMLStream(InputStream inStream, MyDefaultHandler defaultHandler) { // 1.獲取基於SAX的解析器的實例 SAXParserFactory factory = SAXParserFactory.newInstance(); // 2.創建一個SAXParser實例 SAXParser saxParser = factory.newSAXParser(); // 3.解析 saxParser.parse(inStream, defaultHandler); } public static void main(String[] args) throws FileNotFoundException, ParserConfigurationException, SAXException, IOException{ //正常輸入 receiveXMLStream(new FileInputStream("D:\\JavaWorkspace\\TestInput\\src\\cn\\com\\budi\\xml\\xxe\\inject\\normal.xml"), new MyDefaultHandler()); //惡意輸入 receiveXMLStream(new FileInputStream("D:\\JavaWorkspace\\TestInput\\src\\cn\\com\\budi\\xml\\xxe\\inject\\evil.xml"), new MyDefaultHandler()); } }
運行結果:
正常輸入,等待解析...... <firstname>XEE TEST !!</firstname> ========================== 惡意輸入,等待解析...... <firstname>OWASP BWA root/owaspbwa Metasploitable msfadmin/msfadmin Kali Liunx root/wangpeng </firstname>
(2)合規代碼(安全檢查外部實體)
public class CustomResolver implements EntityResolver{ public InputSource resolveEntity(String publicId, String systemId) throws SAXException, IOException{ //System.out.println("PUBLIC:"+publicId); //System.out.println("SYSTEM:"+systemId); System.out.println("引用實體檢測...."); String entityPath = "file:///D:/test.txt"; if (systemId.equals(entityPath)){ System.out.println("合法解析:"+systemId); return new InputSource(entityPath); }else{ System.out.println("非法實體:"+systemId); return new InputSource(); } } }
測試代碼
public class XXEFormat { private static void receiveXMLStream(InputStream inStream, MyDefaultHandler defaultHandler) { // 獲取基於SAX的解析器的實例 SAXParserFactory factory = SAXParserFactory.newInstance(); // 創建一個SAXParser實例 SAXParser saxParser; try { saxParser = factory.newSAXParser(); //創建讀取工具 XMLReader reader = saxParser.getXMLReader(); reader.setEntityResolver(new CustomResolver()); reader.setErrorHandler(defaultHandler); InputSource is = new InputSource(inStream); reader.parse(is); System.out.println("\t成功解析完成!"); } catch (ParserConfigurationException e) { // TODO Auto-generated catch block System.out.println("\t非法解析!"); } catch (SAXException e) { // TODO Auto-generated catch block System.out.println("\t非法解析!"); } catch (IOException e) { // TODO Auto-generated catch block System.out.println("\t非法解析!"); } } public static void main(String[] args) throws ParserConfigurationException, SAXException, IOException{ //正常輸入 System.out.println("正常輸入,等待解析......"); receiveXMLStream(new FileInputStream("D:\\JavaWorkspace\\TestInput\\src\\cn\\com\\budi\\xml\\xxe\\inject\\normal.xml"), new MyDefaultHandler()); System.out.println("=========================="); //惡意輸入 System.out.println("惡意輸入,等待解析......"); receiveXMLStream(new FileInputStream("D:\\JavaWorkspace\\TestInput\\src\\cn\\com\\budi\\xml\\xxe\\inject\\evil.xml"), new MyDefaultHandler()); } }
運行結果:
正常輸入,等待解析......
引用實體檢測....
合法解析:file:///D:/test.txt
成功解析完成!
==========================
惡意輸入,等待解析......
引用實體檢測....
非法實體:file:///D:/password.txt
非法解析!
3、防范建議:
√ 白名單
0x04命令注入
1、原理:
(1)正常輸入:
dir
(2)惡意輸入:
dir & ipconfig & net user budi budi /add & net localgroup Administrators admin /add
2、Java代碼分析
(1)非合規Window命令注入
public class OrderWinFault { public static void main(String[] args) throws Exception{ //正常命令 runOrder("dir"); //惡意命令 runOrder("dir & ipconfig & net user budi budi /add & net localgroup Administrators admin /add"); } private static void runOrder(String order) throws IOException, InterruptedException{ Runtime rt = Runtime.getRuntime(); Process proc = rt.exec("cmd.exe /C "+order); int result = proc.waitFor(); if(result !=0){ System.out.println("process error: "+ result); } InputStream in = (result == 0)? proc.getInputStream() : proc.getErrorStream(); BufferedReader reader=new BufferedReader(new InputStreamReader(in)); StringBuffer buffer=new StringBuffer(); String line; while((line = reader.readLine())!=null){ buffer.append(line+"\n"); } System.out.print(buffer.toString()); } }
(2)非合規的Linux注入命令
public class OrderLinuxFault { public static void main(String[] args) throws Exception{ // 正常命令 runOrder("ls"); // 惡意命令 runOrder(" ls & ifconfig"); } private static void runOrder(String order) throws IOException, InterruptedException{ Runtime rt = Runtime.getRuntime(); Process proc = rt.exec(new String [] {"sh", "-c", "ls "+order}); int result = proc.waitFor(); if(result !=0){ System.out.println("process error: "+ result); } InputStream in = (result == 0)? proc.getInputStream() : proc.getErrorStream(); BufferedReader reader=new BufferedReader(new InputStreamReader(in)); StringBuffer buffer=new StringBuffer(); String line; while((line = reader.readLine())!=null){ buffer.append(line+"\n"); } System.out.print(buffer.toString()); } }
(3)合規編碼(對命令安全檢查)
public class OrderFormat { public static void main(String[] args) throws Exception{ runOrder("dir"); runOrder("dir & ipconfig & net user budi budi /add & net localgroup Administrators admin /add"); } private static void runOrder(String order) throws IOException, InterruptedException{ if (!Pattern.matches("[0-9A-Za-z@.]+", order)){ System.out.println("存在非法命令"); return; } Runtime rt = Runtime.getRuntime(); Process proc = rt.exec("cmd.exe /C "+order); int result = proc.waitFor(); if(result !=0){ System.out.println("process error: "+ result); } InputStream in = (result == 0)? proc.getInputStream() : proc.getErrorStream(); BufferedReader reader=new BufferedReader(new InputStreamReader(in)); StringBuffer buffer=new StringBuffer(); String line; while((line = reader.readLine())!=null){ buffer.append(line+"\n"); } System.out.print(buffer.toString()); } }
3、防范建議:
√ 白名單
√ 嚴格權限限制
√ 采用命令標號
0x05 壓縮炸彈(zip bomb)
(1)合法輸入:
普通壓縮比文件normal.zip
(2)惡意輸入:
高壓縮比文件evil.zip
2、Java代碼分析
public class ZipFault { static final int BUFFER = 512; public static void main(String[] args) throws IOException{ System.out.println("正常壓縮文件......."); checkzip("D:\\JavaWorkspace\\TestInput\\src\\cn\\com\\budi\\zip\\normal.zip"); System.out.println("惡意壓縮文件......."); checkzip("D:\\JavaWorkspace\\TestInput\\src\\cn\\com\\budi\\zip\\evil.zip"); } private static void checkzip(String filename) throws IOException{ BufferedOutputStream dest = null; FileInputStream fls = new FileInputStream(filename); ZipInputStream zis = new ZipInputStream(new BufferedInputStream(fls)); ZipEntry entry; long begin = System.currentTimeMillis(); while ((entry = zis.getNextEntry()) != null){ System.out.println("Extracting:" + entry+"\t解壓后大小:"+entry.getSize()); int count; byte data[] = new byte[BUFFER]; FileOutputStream fos = new FileOutputStream("D:/"+entry.getName()); dest = new BufferedOutputStream(fos, BUFFER); while ((count = zis.read(data, 0, BUFFER))!=-1){ dest.write(data,0, count); } dest.flush(); dest.close(); } zis.close(); long end = System.currentTimeMillis(); System.out.println("解壓縮執行耗時:" + (end - begin) + " 豪秒"); } }
運行結果:
正常壓縮文件....... Extracting:normal.txt 解壓后大小:17496386 解壓縮執行耗時:382 豪秒 惡意壓縮文件....... Extracting:evil.txt 解壓后大小:2000000000 解壓縮執行耗時:25911 豪秒
(2)合規代碼
public class ZipFormat { static final int BUFFER = 512; static final int TOOBIG = 0x640000; public static void main(String[] args) throws IOException{ checkzip("D:\\JavaWorkspace\\TestInput\\src\\cn\\com\\budi\\zip\\normal.zip"); checkzip("D:\\JavaWorkspace\\TestInput\\src\\cn\\com\\budi\\zip\\evil.zip"); } private static void checkzip(String filename) throws IOException{ BufferedOutputStream dest = null; FileInputStream fls = new FileInputStream(filename); ZipInputStream zis = new ZipInputStream(new BufferedInputStream(fls)); ZipEntry entry; long begin = System.currentTimeMillis(); while ((entry = zis.getNextEntry()) != null){ System.out.println("Extracting:" + entry+"\t解壓后大小:"+entry.getSize()); if (entry.getSize() > TOOBIG){ System.out.println("壓縮文件過大"); break; } if (entry.getSize() == -1){ System.out.println("文件大小異常"); } int count; byte data[] = new byte[BUFFER]; FileOutputStream fos = new FileOutputStream("D:/"+entry.getName()); dest = new BufferedOutputStream(fos, BUFFER); while ((count = zis.read(data, 0, BUFFER))!=-1){ dest.write(data,0, count); } dest.flush(); dest.close(); } zis.close(); long end = System.currentTimeMillis(); System.out.println("解壓縮執行耗時:" + (end - begin) + " 豪秒"); } }
運行結果:
正常文件......... Extracting:normal.txt 解壓后大小:17496386 解壓縮執行耗時:378 豪秒 =================== 惡意文件......... Extracting:evil.txt 解壓后大小:2000000000 壓縮文件過大 解壓縮執行耗時:0 豪秒
3、防范建議:
√ 解壓前檢查解壓后文件大小
0x06 正則表達式注入
1、原理:
(1)合法輸入
search=error
拼接后
(.*? +public\\[\\d+\\]+.*error.*)
(2)惡意輸入
search=.*)|(.*
拼接后
(.*? +public\\[\\d+\\]+.*.*)|(.*.*)
2、Java代碼分析
(1)非合規代碼(未進行安全檢查)
public class RegexFault { /** * 以行為單位讀取文件,常用於讀面向行的格式化文件 */ public static void readFileByLines(String filename,String search) { File file = new File(filename); BufferedReader reader = null; String regex ="(.*? +public\\[\\d+\\] +.*"+search+".*)"; System.out.println("正則表達式:"+regex); try { reader = new BufferedReader(new FileReader(file)); String tempString = null; int line = 1; System.out.println("查找開始......"); // 一次讀入一行,直到讀入null為文件結束 while ((tempString = reader.readLine()) != null) { //System.out.println("line " + line + ": " + tempString); if(Pattern.matches(regex, tempString)){ // 顯示行號 System.out.println("line " + line + ": " + tempString); } line++; } reader.close(); System.out.println("查找結束...."); } catch (IOException e) { e.printStackTrace(); } finally { if (reader != null) { try { reader.close(); } catch (IOException e1) { } } } } public static void main(String[] args){ //正常輸入 readFileByLines("D:\\JavaWorkspace\\TestInput\\src\\cn\\com\\budi\\regex\\regex.log","error"); //惡意輸入 readFileByLines("D:\\JavaWorkspace\\TestInput\\src\\cn\\com\\budi\\regex\\regex.log",".*)|(.*"); } }
運行結果:
正常輸入...... 正則表達式:(.*? +public\[\d+\] +.*error.*) line 5: 10:48:08 public[48964] Backup failed with error: 19 ============================ 惡意輸入...... 正則表達式:(.*? +public\[\d+\] +.*.*)|(.*.*) line 1: 10:47:03 private[423] Successful logout name: budi ssn: 111223333 line 2: 10:47:04 public[48964] Failed to resolve network service line 3: 10:47:04 public[1] (public.message[49367]) Exited with exit code: 255 line 4: 10:47:43 private[423] Successful login name: budisploit ssn: 444556666 line 5: 10:48:08 public[48964] Backup failed with error: 19
(2)合規代碼(進行安全檢查)
public class RegexFormat { /** * 檢測是否存在非法字符 * @param search */ private static boolean validate(String search){ for (int i = 0; i< search.length(); i++){ char ch = search.charAt(i); if(!(Character.isLetterOrDigit(ch) || ch ==' ' || ch =='\'')){ System.out.println("存在非法字符,查找失敗...."); return false; } } return true; } /** * 以行為單位讀取文件,常用於讀面向行的格式化文件 */ public static void readFileByLines(String filename,String search) { if(!validate(search)){ return; } File file = new File(filename); BufferedReader reader = null; String regex ="(.*? +public\\[\\d+\\] +.*"+search+".*)"; System.out.println("正則表達式:"+regex); try { reader = new BufferedReader(new FileReader(file)); String tempString = null; int line = 1; System.out.println("查找開始......"); // 一次讀入一行,直到讀入null為文件結束 while ((tempString = reader.readLine()) != null) { //System.out.println("line " + line + ": " + tempString); if(Pattern.matches(regex, tempString)){ // 顯示行號 System.out.println("line " + line + ": " + tempString); } line++; } reader.close(); System.out.println("查找結束...."); } catch (IOException e) { e.printStackTrace(); } finally { if (reader != null) { try { reader.close(); } catch (IOException e1) { } } } } public static void main(String[] args){ //正常輸入 System.out.println("正常輸入......"); readFileByLines("D:\\JavaWorkspace\\TestInput\\src\\cn\\com\\budi\\regex\\regex.log","error"); System.out.println("============================"); //惡意輸入 System.out.println("惡意輸入......"); readFileByLines("D:\\JavaWorkspace\\TestInput\\src\\cn\\com\\budi\\regex\\regex.log",".*)|(.*"); } }
運行結果:
============================ 正常輸入...... 正則表達式:(.*? +public\[\d+\] +.*error.*) line 5: 10:48:08 public[48964] Backup failed with error: 19 ============================ 惡意輸入...... 存在非法字符,查找失敗....
3、防范建議:
√ 白名單
0x07 未凈化輸入
(1)日志記錄
正常輸入:
budi
日志記錄:
User Login Successed for: budi
惡意輸入:
budi \nUser Login Successed for: administrator
日志記錄:
User Login Failed for: budi User Login Successed for: administrator
(2)更新用戶名
正常輸入:
username=budi
SQL查詢:
SELECT * FROM users WHRER id='budi';
惡意輸入:
username=budi' or 'a'='a
SQL查詢:
SELECT * FROM users WHRER id='budi' or 'a'='a';
2、Java代碼分析
(1)非合規代碼(未安全檢查)
public class LogFault { private static void writeLog( boolean isLogin,String username){ if(isLogin){ System.out.println("User Login Successed for: "+username); }else{ System.out.println("User Login Failed for: "+username); } } public static void main(String[] args){ String test1= "budi"; System.out.println("正常用戶登錄成功后,記錄日志....."); //正常用戶登錄成功后,記錄日志 writeLog(true, test1); //惡意用戶登錄失敗,記錄日志 String test2 = "budi \nUser Login Successed for: administrator"; System.out.println("惡意用戶登錄失敗,記錄日志....."); writeLog(false, test2); } }
運行結果:
正常用戶登錄成功后,記錄日志..... User Login Successed for: budi 惡意用戶登錄失敗,記錄日志..... User Login Failed for: budi User Login Successed for: administrator
(2)合規代碼(安全檢查)
public class LoginFormat { private static void writeLog( boolean isLogin,String username){ if(!Pattern.matches("[A-Za-z0-9_]+", username)){ System.out.println("User Login Failed for Unknow User"); }else if(isLogin){ System.out.println("User Login Successed for: "+username); }else{ System.out.println("User Login Failed for: "+username); } } public static void main(String[] args){ String test1= "budi"; System.out.println("正常用戶登錄成功后,記錄日志....."); writeLog(true, test1); String test2 = "budi \nUser Login Successed for: administrator"; System.out.println("惡意用戶登錄失敗,記錄日志....."); writeLog(false, test2); } }
運行結果:
正常用戶登錄成功后,記錄日志..... User Login Successed for: budi 惡意用戶登錄失敗,記錄日志..... User Login Failed for Unknow User
3、防范建議:
√ 先檢測用戶輸入,強烈建議直接拒絕帶非法字符的數據
0x08 路徑遍歷
1、原理:
(1)正常輸入:
john.txt
(2)惡意輸入:
../../a.txt"
2、Java代碼分析
(1)非合規代碼(未安全檢查)
public class PathFault { public static void main(String[] args) throws IOException{ System.out.println("合法輸入......."); readFile("john.txt"); System.out.println("\n惡意輸入......."); readFile("../../a.txt"); } private static void readFile(String path) throws IOException{ File f = new File("F://passwords//"+path); String absPath = f.getAbsolutePath(); FileOutputStream fls = new FileOutputStream(f); System.out.print("絕對路徑:"+absPath); if(!isInSecureDir(Paths.get(absPath))){ System.out.println("->非安全路徑"); return; } System.out.print("->安全路徑"); } private static boolean isInSecureDir(Path path){ if(!path.startsWith("F://passwords//")){ return false; }; return true; } }
運行結果:
合法輸入....... 絕對路徑:F:\passwords\john.txt->安全路徑 惡意輸入....... 絕對路徑:F:\passwords\..\..\a.txt->安全路徑
(2)合規代碼(先統一路徑表示)
public class PathFormat { public static void main(String[] args) throws IOException{ System.out.println("合法輸入......."); readFile("john.txt"); System.out.println("/n惡意輸入......."); readFile("../../a.txt"); } private static void readFile(String path) throws IOException{ File f = new File("F://passwords//"+path); String canonicalPath = f.getCanonicalPath(); System.out.println("絕對路徑"+canonicalPath); FileInputStream fls = new FileInputStream(f); if(!isInSecureDir(Paths.get(canonicalPath))){ System.out.print("非安全路徑"); return; } System.out.print("安全路徑"); } private static boolean isInSecureDir(Path path){ if(!path.startsWith("F://passwords//")){ return false; }; return true; } }
運行結果:
合法輸入....... 絕對路徑F:\passwords\john.txt->安全路徑 惡意輸入....... 絕對路徑F:\a.txt->非安全路徑
3、防范建議
√ 嚴格的權限限制->安全管理器
√ getCanonicalPath()在所有平台上對所有別名、快捷方式、符號鏈接采用統一的解析。
0x09 格式化字符串
1、原理:
(1)正常輸入:
11
正常拼接:
(2)惡意輸入:
惡意拼接:
2、Java代碼分析
(1)非合規代碼:
public class DateFault { static Calendar c = new GregorianCalendar(2016, GregorianCalendar.MAY, 23); public static void main(String[] args){ //正常用戶輸入 System.out.println("正常用戶輸入....."); format("11"); System.out.println("非正常輸入獲取月份....."); format("%1$tm"); System.out.println("非正常輸入獲取日....."); format("%1$te"); System.out.println("非正常輸入獲取年份....."); format("%1$tY"); } private static void format(String month){ System.out.printf(month+" did not match! HINT: It was issued on %1$te rd of some month\n", c); } }
運行結果:
11 did not match! HINT: It was issued on 23rd of some month 非正常輸入獲取月份..... 05 did not match! HINT: It was issued on 23rd of some month 非正常輸入獲取日..... 23 did not match! HINT: It was issued on 23rd of some month 非正常輸入獲取年份..... 2016 did not match! HINT: It was issued on 23rd of some month
(2)合規代碼:
public class DateFormat { static Calendar c = new GregorianCalendar(2016, GregorianCalendar.MAY, 23); public static void main(String[] args){ //正常用戶輸入 System.out.println("正常用戶輸入....."); format("11"); System.out.println("非正常輸入獲取月份....."); format("%1$tm"); System.out.println("非正常輸入獲取日....."); format("%1$te"); System.out.println("非正常輸入獲取年份....."); format("%1$tY"); } private static void format(String month){ System.out.printf("%s did not match! HINT: It was issued on %1$te rd of some month\n", month, c); } }
運行結果:
3、防范建議:
√ 對用戶輸入進行安全檢查
√ 在格式字符串中,杜絕使用用戶輸入參數
0x0A 字符串標准化
1、原理:
(1)合法輸入:
username=budi
(2)惡意輸入一:
username=/><script>alert(1)</script> username=/\uFE65\uFE64script\uFE65alert(1) \uFE64/script\uFE65
(3)惡意輸入二:
username=A\uD8AB
username=A?
2、Java代碼分析
(1)非合規代碼(先檢查再統一編碼)
public class EncodeFault { public static void main(String[] args){ System.out.println("未編碼的非法字符"); check("/><script>alert(2)</script>"); System.out.println("Unicode編碼的非法字符"); check("/\uFE65\uFE64script\uFE65alert(1) \uFE64/script\uFE65"); } public static void check(String s){ Pattern pattern = Pattern.compile("[<>]"); Matcher matcher = pattern.matcher(s); if (matcher.find()){ System.out.println(s+"->存在非法字符"); }else{ System.out.println(s+"->合法字符"); } s = Normalizer.normalize(s, Form.NFC); } }
運行結果:
未編碼的非法字符 /><script>alert(2)</script>->存在非法字符 Unicode編碼的非法字符 /﹥﹤script﹥alert(1) ﹤/script﹥->合法字符
(3)合規代碼(先統一編碼再檢查)
public class EncodeFormat { public static void main(String[] args){ System.out.println("未編碼的非法字符"); check("/><script>alert(2)</script>"); System.out.println("Unicode編碼的非法字符"); check("/\uFE65\uFE64script\uFE65alert(1)\uFE64/script\uFE65"); } public static void check(String s){ s = Normalizer.normalize(s, Form.NFC); // 用\uFFFD替代非Unicode編碼字符 s = s.replaceAll("^\\p{ASCII}]", "\uFFFD"); Pattern pattern = Pattern.compile("[<>]"); Matcher matcher = pattern.matcher(s); if (matcher.find()){ System.out.println(s+"->存在非法字符"); }else{ System.out.println(s+"->合法字符"); } } }
運行結果:
未編碼的非法字符 /><script>alert(2)</script>->存在非法字符 Unicode編碼的非法字符 /><script>alert(1)</script>->存在非法字符
3、防范建議:
√ 先按指定編碼方式標准化字符串,再檢查非法輸入
√ 檢測非法字符
0x0B 最后總結
- 從安全角度看,移動應用與傳統Web應用沒有本質區別。
- 安全的Web應用必須處理好兩件事:
- 處理好用戶的輸入(HTTP請求)
- 處理好應用的輸出(HTTP響應)
參考文獻: 《Java安全編碼標准》