9

Java安全编码之用户输入 | WooYun知识库

 6 years ago
source link:
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

Java安全编码之用户输入

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 最后总结


  • 从安全角度看,移动应用与传统Web应用没有本质区别。
  • 安全的Web应用必须处理好两件事:

    1. 处理好用户的输入(HTTP请求)
    2. 处理好应用的输出(HTTP响应)

参考文献: 《Java安全编码标准》


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK