<dd id="sga4y"><optgroup id="sga4y"></optgroup></dd>
<nav id="sga4y"><code id="sga4y"></code></nav>
  • <optgroup id="sga4y"></optgroup>

    原文地址:http://drops.wooyun.org/tips/15129

    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)合法輸入:

    #!sql
    id=1
    SELECT * FROM users WHRER id='1';
    

    (2)惡意注入:

    #!sql
    id=1' or '1'='1
    SELECT * FROM users WHRER id='1' or 'a'='a';
    

    2、Java代碼分析(JDBC)

    (1)不合規代碼(SQL參數拼接)

    #!java
    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("數據庫數據成功獲取!!");
            }
        }
    }
    

    執行結果:

    #!shell
    SQL Paramter:1
    -----------------
    budi    27
    -----------------
    SQL Paramter:' or 'a'='a
    -----------------
    budi    27
    budisploit  28
    -----------------
    

    (2)合規代碼(參數化查詢)

    #!java
    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("數據庫數據成功獲取!!");
            }
        }
    }
    

    執行結果:

    #!shell
    SQL Paramter:1
    -----------------
    budi    27
    -----------------
    SQL Paramter:' or 'a'='a
    -----------------
    -----------------
    

    3、防范建議:

    √ 采用參數查詢即預編譯方式(首選
    √ 字符串過濾

    0x02 XML注入


    1、原理

    (1)合法輸入:

    #!xml
    quantity=1
    <item>
        <name>apple</name>
        <price>500.0</price>
        <quantity>1</quantity>
    <item>
    

    (2)惡意輸入:

    #!xml
    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)不合規代碼(未進行安全檢查)

    #!java
    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;
        }
    }
    

    運行結果:

    #!shell
    正常輸入結果:[{price=500.0}]
    惡意輸入結果:[{price=500.0}, {price=5.0}]
    

    (2)合規代碼(利用schema安全檢查)

    #!xml
    <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>
    

    測試代碼

    #!java
    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("解析異常");
            }
        }
    }
    

    運行結果:

    #!shell
    正常輸入........
    解析正常
    惡意輸入........
    錯誤: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
    <?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
    <?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)不合規代碼(未安全檢查外部實體)

    #!java
    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());
        }  
    }
    

    運行結果:

    #!shell
    正常輸入,等待解析......
    <firstname>XEE TEST !!</firstname>
    ==========================
    惡意輸入,等待解析......
    <firstname>OWASP BWA   root/owaspbwa
    Metasploitable  msfadmin/msfadmin
    Kali Liunx  root/wangpeng
    </firstname>
    

    (2)合規代碼(安全檢查外部實體)

    #!java
    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();
            }
        }
    }
    

    測試代碼

    #!java
    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());
        }
    }
    

    運行結果:

    #!shell
    正常輸入,等待解析......
    引用實體檢測....
    合法解析:file:///D:/test.txt
        成功解析完成!
    ==========================
    惡意輸入,等待解析......
    引用實體檢測....
    非法實體:file:///D:/password.txt
        非法解析!
    

    3、防范建議:

    √ 白名單

    0x04命令注入


    1、原理:

    (1)正常輸入:

    #!shell
    dir
    

    (2)惡意輸入:

    #!shell
    dir & ipconfig & net user budi budi /add & net localgroup Administrators admin /add
    

    2、Java代碼分析

    (1)非合規Window命令注入

    #!java
    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注入命令

    #!java
    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)合規編碼(對命令安全檢查)

    #!java
    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)合法輸入:

    #!shell
    普通壓縮比文件normal.zip
    

    (2)惡意輸入:

    #!shell
    高壓縮比文件evil.zip
    

    2、Java代碼分析

    #!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) + " 豪秒");
        }
    }
    

    運行結果:

    #!shell
    正常壓縮文件.......
    Extracting:normal.txt   解壓后大小:17496386
    解壓縮執行耗時:382 豪秒
    惡意壓縮文件.......
    Extracting:evil.txt 解壓后大小:2000000000
    解壓縮執行耗時:25911 豪秒
    

    (2)合規代碼

    #!java
    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) + " 豪秒");
        }
    }
    

    運行結果:

    #!shell
    正常文件.........
    Extracting:normal.txt   解壓后大小:17496386
    解壓縮執行耗時:378 豪秒
    ===================
    惡意文件.........
    Extracting:evil.txt 解壓后大小:2000000000
    壓縮文件過大
    解壓縮執行耗時:0 豪秒
    

    3、防范建議:

    √ 解壓前檢查解壓后文件大小

    0x06 正則表達式注入


    1、原理:

    (1)合法輸入

    #!shell
    search=error
    

    拼接后

    #!shell
    (.*? +public\\[\\d+\\]+.*error.*)
    

    (2)惡意輸入

    #!shell
    search=.*)|(.*
    

    拼接后

    #!shell
    (.*? +public\\[\\d+\\]+.*.*)|(.*.*)
    

    2、Java代碼分析

    (1)非合規代碼(未進行安全檢查)

    #!java
    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",".*)|(.*");
        }
    }
    

    運行結果:

    #!shell
    正常輸入......
    正則表達式:(.*? +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)合規代碼(進行安全檢查)

    #!java
    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",".*)|(.*");
        }
    }
    

    運行結果:

    #!shell
    ============================
    正常輸入......
    正則表達式:(.*? +public\[\d+\] +.*error.*)
    line 5: 10:48:08 public[48964] Backup failed with error: 19
    ============================
    惡意輸入......
    存在非法字符,查找失敗....
    

    3、防范建議:

    √ 白名單

    0x07 未凈化輸入


    (1)日志記錄

    正常輸入:

    #!shell
    budi
    

    日志記錄:

    #!shell
    User Login Successed for: budi
    

    惡意輸入:

    #!shell
    budi \nUser Login Successed for: administrator
    

    日志記錄:

    #!shell
    User Login Failed for: budi 
    User Login Successed for: administrator
    

    (2)更新用戶名

    正常輸入:

    #!sql
    username=budi
    

    SQL查詢:

    #!sql
    SELECT * FROM users WHRER id='budi';
    

    惡意輸入:

    #!sql
    username=budi' or 'a'='a
    

    SQL查詢:

    #!sql
    SELECT * FROM users WHRER id='budi' or 'a'='a';
    

    2、Java代碼分析

    (1)非合規代碼(未安全檢查)

    #!java
    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);
        }
    }
    

    運行結果:

    #!shell
    正常用戶登錄成功后,記錄日志.....
    User Login Successed for: budi
    惡意用戶登錄失敗,記錄日志.....
    User Login Failed for: budi 
    User Login Successed for: administrator
    

    (2)合規代碼(安全檢查)

    #!java
    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);
        }
    }
    

    運行結果:

    #!shell
    正常用戶登錄成功后,記錄日志.....
    User Login Successed for: budi
    惡意用戶登錄失敗,記錄日志.....
    User Login Failed for Unknow User
    

    3、防范建議:

    √ 先檢測用戶輸入,強烈建議直接拒絕帶非法字符的數據

    0x08 路徑遍歷


     1、原理:

    (1)正常輸入:

    #!shell
    john.txt
    

    (2)惡意輸入:

    #!shell
    ../../a.txt"
    

    2、Java代碼分析

    (1)非合規代碼(未安全檢查)

    #!java
    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;
        }
    }
    

    運行結果:

    #!shell
    合法輸入.......
    絕對路徑:F:\passwords\john.txt->安全路徑
    惡意輸入.......
    絕對路徑:F:\passwords\..\..\a.txt->安全路徑
    

    (2)合規代碼(先統一路徑表示)

    #!java
    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;
        }
    }
    

    運行結果:

    #!shell
    合法輸入.......
    絕對路徑F:\passwords\john.txt->安全路徑
    惡意輸入.......
    絕對路徑F:\a.txt->非安全路徑
    

    3、防范建議

    √ 嚴格的權限限制->安全管理器
    √ getCanonicalPath()在所有平臺上對所有別名、快捷方式、符號鏈接采用統一的解析。

    0x09 格式化字符串


    1、原理:

    (1)正常輸入:

    11

    正常拼接:

    #!shell
    System.out.printf("11 did not match! HINT: It was issued on %1$te rd of some month\n", c);
    

    (2)惡意輸入:

    #!shell
    %1$tm或%1$te或%1$tY
    

    惡意拼接:

    #!shell
    System.out.printf("%1$tm did not match! HINT: It was issued on %1$te rd of some month\n", c);
    

    2、Java代碼分析

    (1)非合規代碼:

    #!java
    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);
        }
    }
    

    運行結果:

    #!shell
    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)合規代碼:

    #!java
    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);
        }
    }
    

    運行結果:

    #!shell
    正常用戶輸入.....
    11 did not match! HINT: It was issued on 
                          Exception in thread "main" java.util.IllegalFormatConversionException: e != java.lang.String
    

    3、防范建議:

    √ 對用戶輸入進行安全檢查
    √ 在格式字符串中,杜絕使用用戶輸入參數

    0x0A 字符串標準化


    1、原理:

    (1)合法輸入:

    #!shell
    username=budi
    

    (2)惡意輸入一:

    #!shell
    username=/><script>alert(1)</script>
    username=/\uFE65\uFE64script\uFE65alert(1) \uFE64/script\uFE65
    

    (3)惡意輸入二:

    #!shell
    username=A\uD8AB
    username=A?
    

    2、Java代碼分析

    (1)非合規代碼(先檢查再統一編碼)

    #!java
    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);
        }
    }
    

    運行結果:

    #!shell
    未編碼的非法字符
    /><script>alert(2)</script>->存在非法字符
    Unicode編碼的非法字符
    /﹥﹤script﹥alert(1) ﹤/script﹥->合法字符
    

    (3)合規代碼(先統一編碼再檢查)

    #!java
    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+"->合法字符");
            }
        }
    }
    

    運行結果:

    #!shell
    未編碼的非法字符
    /><script>alert(2)</script>->存在非法字符
    Unicode編碼的非法字符
    /><script>alert(1)</script>->存在非法字符
    

    3、防范建議:

    √ 先按指定編碼方式標準化字符串,再檢查非法輸入
    √ 檢測非法字符

    0x0B 最后總結


    參考文獻: 《Java安全編碼標準》

    97av