[inBuilder x UBML]关于开发规范,一些可能要说的点(三)

1.3 JAVA安全编码规范

1.3.1 背景、目标及适用范围

在计算机技术和网络技术飞速发展的今天,软件系统的安全所面临的挑战日益严峻。黑客攻击技术越来越成熟、简洁和大众化,针对软件系统的攻击和破坏不断增长,使得软件系统的安全风险达到了前所未有的高度。随着国家和企业对信息安全的越来越重视,信息系统的安全验收和安全监测都逐步被越来越多的客户所采用。目前,软件系统的开发人员在应用程序开发的安全方面缺乏统一的规范指导要求,因此特制订本规范,提供一套完善的、系统化的、实用的软件安全开发方法供产品研发人员使用,以提高大家的安全编码意识、并按照统一的安全要求进行软件开发,以达到提高公司软件产品安全性的目标。

保证软件开发过程中采用统一的安全指导策略:包括客户端程序开发、Web开发、应用服务器程序开发、数据库设计开发等;并提供了常见的提高安全性的技术实现。且保证iGIX产品的整体安全性和健壮性。

本规范的读者及使用对象主要为公司软件产品研发相关的需求分析人员、设计人员、开发人员、测试人员等。

本规范与具体编程语言关联性不大。但作为公司级规范,其主要是基于公司目前的技术体系,包括Web、移动等终端,主要基于Java、HTML、JavaScript语言进行描述。

1.3.2 信息系统安全的目标和架构

1.3.2.1 信息系统安全的目标

信息系统安全的目标是要维护信息资源的保密性、完整性和可用性,简称CIA三要素:

保密性(Confidentiality):确保信息在存储、使用、传输过程中不会泄漏给非授权用户或实体。

完整性(Integrity):确保信息在存储、使用、传输过程中不会被非授权用户篡改,同时还要防止授权用户对系统及信息进行不恰当的篡改,保持信息内、外部表示的一致性。

可用性(Availability):确保授权用户或实体对信息及资源的正常使用不会被异常拒绝,允许其可靠而及时地访问信息及资源。

该目标需要通过实施一定的安全控制来实现。本规范重点描述如何从软件编码时进行技术控制,以减少常见安全问题的发生。

软件开发团队根据功能需求文档和用例设计一个应用程序,以执行特定的任务。而另一方面,攻击者基于“没有具体说明应拒绝的行为,则被认为是可行的”原则,对于应用程序可以做什么更感兴趣。为了解决这种视角差异带来的问题,我们在软件开发生命周期的每个阶段都应该考虑软件的安全性、熟悉安全开发规范,并将其贯穿到软件开发的整个生命周期中。

1.3.2.2 信息系统安全的技术框架

浪潮ERP-iGIX是采用基于互联网的多层架构,按部署架构分为客户端、应用服务器、数据库服务器三层。下图是一个典型的部署方案:

对于如上的基于互联网的多层架构来说,各个区域及传输层都存在很多典型的安全技术点,这些安全技术点,贯穿整个应用设计开发过程,当任何一点存在薄弱环节时,都容易导致一系列的安全漏洞。下图显示了一个典型的应用安全技术框架和安全技术点:

由于HTTP协议的开放性,Web应用程序必须能够通过某种形式的身份验证来识别用户,并确保身份验证过程是安全的,同样必须很好地保护用于跟踪己验证用户的会话处理机制。

为了防止一些恶意输入,需要对输入的数据和参数进行校验。另外还要考虑应用系统的配置安全,敏感信息的保护和用户的权限管理,以及所有操作的安全审计。当然还要考虑代码安全,以及其他方面的威胁。

按照行业经验,在软件产品中,涉及安全问题较多的地方主要集中在以下几个方面:

常见安全类别开发指导原则涉及的软件部署位置
敏感信息保护敏感信息不能明文出现,要注意全程加密客户端、传输层、Web服务器、应用服务器、数据库服务器
安全审计 审计信息要记录完整、并不可修改应用服务器
远程访问服务端更要注意鉴权、输入校验等传输层、Web服务器、应用服务器
输入校验不要相信用户的任何输入客户端、Web服务器、应用服务器
SQL处理 XML处理OS命令调用输入校验、参数化执行客户端、传输层、Web服务器、应用服务器、数据库服务器
文件操作尽量以白名单方式校验客户端、传输层、Web服务器、应用服务器
异常处理禁止向客户端暴露不必要的信息客户端、传输层、Web服务器、应用服务器

因此,为提升信息系统的安全性,信息系统开发人员在开发过程中要对以上几个方面进行安全问题规避,本规范也将针对如何规避上述几个安全问题对安全编码规范进行阐述。

1.3.3 安全开发规范

1.3.3.1 敏感信息保护
1.3.3.1.1 敏感信息定义

所谓敏感信息是指其丢失、不当使用、未经授权被人接触或修改等都会不利于数据所属主体利益或企业计划的实行或不利于个人依法享有的个人隐私权的所有信息,泄露的危害性越大,则敏感性越高。

我们常见的敏感信息包括但不限于:密码、密钥、证书、会话标识、License、隐私数据(如短消息的内容)、授权凭据、个人数据(如姓名、住址、电话等)、数据库或其他系统连接信息等,以及各业务模块识别的业务数据信息。

在程序文件、配置文件、日志文件、备份文件、数据库、通讯过程中都有可能包含敏感信息。因此,在存储和传输过程中都应该对敏感信息采取相应的保护措施。

1.3.3.1.2 敏感信息存储

规则1. 禁止在代码中存储敏感信息。

说明:禁止在代码中明文硬编码如数据库连接字符串、密码、密钥之类的敏感信息,这样容易导致泄密。用于加密密钥的密钥可以在代码中硬编码,但秘钥本身不应该在代码中明文硬编码。

实施指导:下述示例将加密的密钥直接硬编码到代码中,是错误的。

        // <summary>
        // 将字符串进行加密
        // </summary>
        // <returns>加密后的密码</returns>
        public String encrypt(String password)
        {
            if (StringUtils.isEmpty(password))
                return “”;
            SymmCryptoEx crypt = new SymmCryptoEx(SymmProvEnum.Rijndael);
            //此处在代码中明文写死了秘钥,是错误的
            return crypt.encrypting(password, "key");
        }

规则2. 要尽量缩短内存中明文敏感信息的存在时间,善于使用SecurityString。

说明:SecurityString类专门用来存放敏感信息,比如密码、秘钥等。一般是将敏感信息存储在SecurityString中,只有真正需要明文的短暂时间内才转换成明文String进行使用,并在使用完毕后立即销毁明文String。比如:数据库连接密码。

实施指导:对上述明文写死秘钥的情况进行改造,并顺便讲解SecurityString使用方法。

        // <summary>
        // 将字符串进行加密
        // </summary>
        // <returns>加密后的密文</returns>
        public String encrypt(String plainText)
        {
            if (StringUtils.isEmpty(plainText))
                return “”;
            SecureKey secretKey = new SecureKey();
            SymmCryptoEx crypt = new SymmCryptoEx(SymmProvEnum.Rijndael);
            return crypt.encrypting(plainText, secretKey.secureStringToString());
        }
        // <summary>
        // 安全key,使用SecureString
        // </summary>
        class SecureKey
        {
            private int[] array1 = { 67, 108, 105, 101, 110, 116 };
            private int[] array2 = { 68, 97, 116, 97 };
            private int[] array3 = { 67, 97, 99, 104, 101 };
            private SecureString KeyStr;
            // <summary>
            // 构造函数
            // </summary>
            void secureKey()
            {
                this.KeyStr = new SecureString();
                for(int item : array1)
                {
                    this.KeyStr.appendChar(Convert.toChar(item));
                }
                for(int item : array2)
                {
                    this.KeyStr.appendChar(Convert.toChar(item));
                }
                for (int item : array3)
                {
                    this.KeyStr.appendChar(Convert.toChar(item));
                }
            }
            // <summary>
            // 将SecureString转换成对应的字符串
            // </summary>
            // <returns>SecureString对应的字符串</returns>
            String secureStringToString()
            {
                //分配非托管内存用来存储SecurityString中的字符串
                IntPtr bstr = Marshal.secureStringToBSTR(this.KeyStr);
                try
                {
                    //将非托管内存中的字符串拷贝到托管内存中
                    return Marshal.ptrToStringBSTR(bstr);
                }
                finally
                {
                    //清理掉非托管代码中对应的内存的值
                    Marshal.freeBSTR(bstr);
                }
            }
        }

规则3. 禁止密钥或帐号的密码以明文形式存储在数据库或者文件中。

说明:密钥或帐号的密码必须经过加密存储。

规则4. 禁止用自己开发的加密算法,必须使用公开、安全的标准加密算法。能用不可逆加密算法的就用不可逆加密算法,并要进行加盐处理。

说明:公开、安全的标准加密算法是经过了充分设计和大量验证的安全加密方式,破解难度较大,这是自己开发的加密算法很难达到的。

对称加密算法中,加密和解密用的都是同样的密钥,因而其安全性依赖于所使用密钥的安全性;非对称加密算法中:加密所用的密钥和解密所用的密钥是不相同的,加密双方各自持有公钥和私钥,一般都是用公钥加密,私钥解密,由于每个人都持有各自独立的与公钥对应的私钥,所以该加密方式具有抗抵赖性;加盐处理,就是在加密一份数据之前,先将这份数据的某个位置加入其他已知且互不相同的数据,然后再行加密,这样即使两份数据完全一样,加密后得到的密文也不一样,这就大大增强了破解难度。

实施指导:

场景1:后台服务端保存的用户登录密码应采用不可逆算法

用户密码的设置和使用流程基本是:用户在客户端设置个人密码,系统加密传输到服务端进行持久化,当用户输入密码登陆时,系统在将用户输入的密码加密传输到服务端进行验证密码是否和数据库中保存的密文一致,然后返回验证的结果。此时,在后台服务端,用户密码不需要还原,因此用户密码的加密方式应该是:

(1) 使用不可逆的加密算法

(2) 采用加盐处理:在用户的密码字符串之后,拼接上一个随机字符串后,再进行不可逆加密处理。

常见的不可逆算法有MD5、SHA1、SHA2(包括SHA224、SHA256、SHA384、SHA512),推荐使用SHA-256、SHA1算法,安全性要求低时,也可采用MD5算法。平台已经提供了这些加密算法的调用接口,业务开发时直接调用即可。

// 用户密码加密方式示例:
 private static String encryptingWithSalt(String passWordref String salt)
        {
            //首先生成随机加密用的盐值
            RandomNumberGenerator saltNumber = new RNGCryptoServiceProvider();
            byte[] s = new byte[256];
            saltNumber.getBytes(s);
            salt = Convert.toBase64String(s); //将盐值转化为字符串
            //将盐值和密码一起通过hash加密函数,生成密文
            SymmCryptoEx crypto = new SymmCryptoEx(SymmProvEnum.DES);
            return crypto.encrypting(password + salt, new SecretKey().secureStringToString());
        }

场景2:必须在后台服务端保存数据库的登录密码

后台服务器登录数据库需要使用登录数据库的明文密码,此时后台服务器加密保存该密码后,下次登录时需要还原成明文,因此,在这种情况下,不可用不可逆的加密算法,而需要使用对称加密算法或者非对称加密(一般不建议采用非对称加密)。

常用的对称加密算法主要有DES、3DES(TripleDES)和AES(Rijndael)。推荐使用AES(Rijndael),包括AES128、AES192、AES256。

常见的非对称加密算法有:RSA、DSA(数字签名用)。

平台已经提供了这些加密算法的调用接口,业务开发时直接调用即可。

规则5. 禁止在日志中记录明文的敏感信息。

说明:禁止在日志中记录明文的敏感信息,如:密码、会话标识sessionid、数据库连接信息等,防止敏感信息泄漏。

规则6. 禁止在cookie中以明文形式存储敏感信息。

说明:cookie信息容易被窃取,尽量不要在cookie中存储敏感信息:如果条件限制必须使用cookie存储敏感信息时,必须先对敏感信息加密再存储到cookie。

规则7. 禁止在HTML隐藏域中存放明文形式的敏感信息。

规则8. 禁止带有敏感信息的Web页面缓存。

说明:带有敏感信息的Web页面都应该禁止缓存,以防止敏感信息泄漏或通过代理服务器上网的用户数据互窜问题。

实施指导:在HTML页面的标签内加入如下代码:

<HEAD>
    <METAHTTP-EQUIV="Expires"CONTENT="0">
    <METAHTTP-EQUIV="Pragma"CONTENT="no-cache">
    <METAHTTP-EQUIV="Cache-control"CONTENT="no-cache">
    <METAHTTP-EQUIV="Cache"CONTENT="no-cache">
</HEAD>

注意:以上代码对于采用强制缓存策略的代理服务器不生效(代理服务器默认是不缓存的),要防止代理服务器缓存页面,可以在链接后加入一个随机数pageid,这样每次请求此页面时,随机数都不同,IE始终认为此为一个新请求,并重新解析,生成新的响应页面。

1.3.3.1.3 敏感信息传输

规则9. 在客户端和服务器间传递敏感信息时,必须进行加密。在可能的情况下,尽量使用非可逆加密。

说明:因为传输经过了网络层,所以必须要进行加密处理。如果服务端不需要解密,则尽量使用非可逆加密算法进行加密,如果服务端需要解密,则优先使用非对称加密,其次是对称加密。

在基于Web技术的B/S模式下,因为客户端逻辑都是JS代码,用户可以直接查看JS源代码,所以加密算法和逻辑相当于是公开的,因此建议的处理方式有:

(1) 使用公开的加密算法,比如Cryptico.js

(2) 优先使用用户证书进行非对称加密;如果加密数据较多较频繁,性能消耗较大

(3) 如果不使用证书加密时,加密秘钥尽量从服务端动态获取

(4) JS代码可以按照一定的算法进行压缩处理,使得用户无法直接阅读JS源代码

其中:对于第二条,因为JS代码无法访问到浏览器或者USBKEY中的证书信息,所以,一般都是以OCX控件的方式进行实现。

规则10. 带有敏感信息的表单必须使用HTTP-POST方法提交。

说明:禁止使用HTTP-GET方法提交带有敏感信息的表单(form),因为该方法使用查询字符串传递表单数据,易被查看、篡改。

实施指导:对于ASP.net页面,将表单的属性method赋值为”post”的方式如第一行,对于JSP页面,将表单的属性method赋值为”post”的方式如第二行:

<form  id="forml" method="post"  runat="server">
<form name="forml" method="post"  action="switch.jsp">

规则11. 禁止在URL中携带会话标识(如sessionId)。

说明:由于浏览器会保存URL历史记录,如果URL中携带会话标识,则在多人共用的PC上会话标识容易被其他人看到,一旦该会话标识还在其生命有效期,则恶意用户可以冒充受害用户访问Web应用系统。

规则12. 禁止将对用户保密的敏感信息传送到客户端。

说明:这些信息一旦传送到客户端,那么用户也就可以获取到了。

实施指导:

场景1:修改密码、校验密码时,只需要将用户输入的密码加密传输到服务端后直接进行验证即可,不需要解密,也不需要将服务端密码取到客户端后再校验。

场景2:用于密码掩码展示时,只需要在客户端构造固定长度的常量即可,该显示与真实密码无关,也不需要将服务端密码取到客户端展示。

1.3.3.1.4 敏感信息修改

规则13. 用掩码展示的敏感信息输入和展示控件,禁止直接绑定到对应的敏感信息真实数据上。

说明:在登陆界面、设置密码界面、数据库连接信息界面等用于输入或展示密码的控件中,安全上有如下要求:

(1) 必须用掩码形式展示

(2) 该控件的值不能直接使用后台的密码明文绑定

(3) 如果是输入控件,用户输入信息必须注意保密存储SecurityString,用于掩码显的控件绑定值也必须更换成其他数据进行展示,以保护用户输入的真实密码

(4) 用户敏感信息,只需要采用默认长度的默认字符串作为控件绑定的值进行显示即可,绝对不能将敏感信息传送到客户端。

(5) 只用于展示时,掩码的长度不应该暴露用户密码的真实长度。

实施指导:

场景1:登录中密码输入控件。

场景2:查看数据库登录信息页面中的密码展示控件。

规则14. 在查看和处理敏感信息时,必须要进行实时(二次)的身份验证。

说明:

(1) 在资金等敏感信息处理时,必须进行身份的二次认证,最好是进行Ukey认证。

(2) 在修改密码时,必须正确输入原密码后才能进行下一步的修改。

(3) 当忘记原始密码时,必须通过用户原始安全邮箱或者手机等进行二次验证,且验证链接和验证码必须设置较短的有效期,且在有效期内只能一次有效,验证出错时必须再次生成和更换验证码。

(4) 在密码验证时,可以加入动态验证码,且必须先验证验证码正确性再校验密码正确性,错误提示信息也不能暴露账号正确性、密码正确性等片面提示信息,只能提示“用户名或密码错误!”等笼统信息,以防止人为试错和暴力破解。

(5) 必要时要使用动态的手机验证码进行身份的双重识别。

1.3.3.2 安全审计

业务系统必须对每个用户的安全事件及重要操作事件进行审计,包括但不限于:用户登录、退出、进入功能、退出功能、改变访问控制策略、增加/删除用户、改变用户权限、增加/删除/查询敏感信息等。审计记录的内容至少应包括事件的日期、时间、发起者信息、类型、描述和结果等。

规则15. 必须对安全事件及重要操作事件进行审计日志记录。

说明:安全事件包括登录、注销、添加、删除、修改用户、授权、取消权限、鉴权、修改用户密码等;重要操作事件包括对业务系统配置参数的修改、对重要业务数据的创建、删除、修改、查询等。

对于上述事件的结果,不管是成功还是失败,都需要记录审计日志。

所谓重要业务数据,是指在特定业务领域下比较重要的业务数据,其增加、删除、修改、查询等都要严格进行限制,数据一旦被不正当使用和篡改,将会带来比较严重的后果。重要业务数据列表,后继会根据各组识别结果进行补充。

规则16. 审计日志必须包括但不限于如下内容:事件发生的时间、事件类型、客户端IP、客户端机器名、当前用户的标识、受影响的数据、成功或失败标识以及对该事件的详细描述。

规则17. 严格限制对安全审计日志的访问。

说明:只有系统的安全审计人员才能查询数据库表形式或文件形式的安全审计日志。要确保安全审计日志的安全,限制对安全审计日志的访问,以加大攻击者篡改安全审计日志文件以掩饰其攻击行为的难度。

规则18. 要确保安全审计日志不被修改和删除

说明:作为安全事件和重要操作的记录,系统要确保任何人都不能对安全审计日志进行修改和删除。

1.3.3.3 远程传输

规则19. 对远程服务的调用必须进行认证。

说明:认证就是确定谁在调用服务端发布的RESTful、WebService等远程服务,并且证实调用者身份。

供内部访问的接口:iGIX平台自身可以保证通过Session机制进行身份验证,业务开发者不需要专门验证。

供外部访问的接口:开发者必须自行进行身份认证,并在验证通过后,设置Session信息。

规则20. 不要相信客户端权限校验,必须在服务端对远程服务的调用进行鉴权。

说明:鉴权就是判断调用者是否有权限调用该远程服务,包括操作权限校验和数据权限校验。

客户端权限校验只是为了减少因为用户可以看到无权限的功能但无权访问而造成的易用性问题和空调用导致的性能问题,所以大部分客户端校验只是用来进行界面菜单展现或按钮可用性判断相关的。

规则21. 通过远程服务传递敏感信息时,必须保障其机密性。

实施指导:少量数据加密时只需要对该部分少量数据单独加密即可;大量加密要求时,可以采用基于SSL加密的HTTPS连接。

规则22. 必须对远程调用提交的输入参数进行合法性校验。

说明:具体输入校验部分在下面一小节

1.3.3.4 输入校验

不要相信用户的任何输入!

必须假定所有用户产生的输入都是不可信的,并对它们进行合法性校验,一旦数据不合法,应该告知用户输入非法并且建议用户纠正输入。

包括客户端用户交互界面中的输入和服务端远程调用中的参数值

规则23. 用户在客户端输入的值,必须进行合法性校验。

说明:必须假定所有用户产生的输入都是不可信的,并对它们进行合法性校验。合法性校验需要使用最小化校验原则。

规则23.1. 尽量使用白名单进行校验。

说明:如果输入只允许包含某些特定的字符或字符的组合,则使用白名单进行输入校验。

对于一些有规则可循的输入,如Email地址、日期、小数等,应该使用正则表达式进行白名单校验,这样比使用黑名单进行校验更有效。

实施指导:Email地址校验的正则表达式为:

 ^([a-z0-9A-Z]+[_-]?) +[a-z0-9A-Z]@(([a-z0-9A-Z]+[_-]?) +(-[a-zO-9A-Z]+)?\\.) +[a-zA-Z]{2,4}$";

规则23.2. 如果输入为数字参数则必须进行数字类型判断。

说明:这里的数字参数指的是完全由数字组成的数据。

实施指导:

 String mobileno = request.getParameter("mobileno");
        Regex regex = new Regex(@"\d+$");    //正则表达式表示是否全为数字
        if(!regex.isMatch(mobileno))
        System.out.println("lnvalidlnput");

规则23.3. 如果输入为字符串参数则必须进行字符型合法性判断。

说明:可定义一个合法字符集。

实施指导:比如全部字符串校验的正则表达式为:

  "^[A-Za-z]*$"//开发者可根据具体的参数限制要求,自行定义字符规则

规则23.4. 校验输入数据的长度。

说明:如果输入数据是字符串,必须校验字符串的长度是否符合要求,长度校验会加大攻击者实施攻击的难度,并有效防止长度溢出导致的内存溢出问题。

规则23.5. 校验输入数据的范围。

说明:如果输入数据是数值,必须校验数值的范围是否正确,如年龄应该为0~150之间的正整数。

规则24. 不能依赖于客户端校验,必须在服务端对输入数据进行最终校验。

说明:客户端的校验只能作为辅助手段,只是为了减少客户端和服务端的信息交互次数。客户端传递到服务端的参数,经过了网络传输层,存在被纂改的可能,所以服务端不应该相信客户端传递过来的参数,必须进行更为严格的有效性校验。

规则25. 对于在客户端已经做了输入校验,在服务器端再次以相同的规则进行校验时,一旦数据不合法,必须使会话失效,并记录告警日志。

说明:此时可以判断肯定存在攻击行为,攻击者绕过了客户端的输入校验,因此必须使会话失效,并记入告警日志。

规则26. 禁止将远程传输的任何未加密信息作为安全决策依据。

说明:比如HTTP标题头中的任何未加密信息,因为攻击者要操作这一标题头是很容易的。例如,标题头中的referer字段包含来自请求源端的Web页面的URL。不要根据referer字段的值做出任何安全决策,因为该字段很容易被伪造。

1.3.3.5 SQL处理

SQL处理的指导原则是:我们要求必须使用参数化方式组装SQL语句,在使用参数化时,输入校验是可选的,但我们还是强烈建议客户端进行适当的校验,以防止无谓的服务端交互。当特殊情况下,不能使用参数化方式时,则必须对用户输入进行严格的合法性校验。

规则27. 在客户端,不要相信用户的任何输入,对参与SQL语句的用户输入数据必须进行有效性校验。

说明:该条目是SQL处理时用到的输入校验的总结,针对SQL语句相关的常用校验方式有:

(1) 最小化许可校验:如目标值只包含数字、只包含字母、电话号码、邮箱时,应该进行相应的正则表达式校验

(2) 特殊字符过滤,采用黑名单机制:如不能包含:分号、等号、破折号、括号、SQL关键字等。

(3) 需要过滤的SQL关键字:

A. 特殊字符:单引号,分号,逗号,冒号,百分号、注释符号……
B. SQL关键字:Select、 Insert、delete from、drop table、update、truncate、from、alert、Union、backup……
C. 命令注入:exec、 master、 xp_cmdshell……
D. 关键字转码:count、Asc、char、mid……
E. 注意变种:要注意大小写混排、转码、中间加注释等的情况
(4) 限制输入长度:一般注入语句的长度都比期望值长很多,所以应该进行输入长度校验,以减少SQL注入攻击的可能性。

实施指导:SQL校验常用正则表达式:

//SQL关键字校验
        private String StrKeyWord = @".*(select|insert|delete|from|
                count(|drop table|update|truncate|asc(|mid(|char(|xp_cmdshell|
                exec master|netlocalgroup administrators|:|net user|""|or|and).*";
        //特殊符号黑名单
        private String StrRegex = @"[-|;|,|/|(|)|[|]|}|{|%|@|*|!|']";
        // <summary>
        // 获取SQL处理校验的正则表达式
        // </summary>
        // <returns>SQL校验时所需的正则表达式</returns>
        private static String getRegexString()
        {
            //构造SQL的注入关键字符
            String[] strBadChar =
            {
              "and"
              ,"exec"
              ,"insert"
              ,"select"
               ,"delete"
               ,"update"
               ,"count"
               ,"from"
               ,"drop"
               ,"asc"
               ,"char"
               ,"or"
               ,"%"
               ,";"
               ,":"
               ,"\'"
               ,"\""
               ,"-"
               ,"chr"
               ,"mid"
               ,"master"
               ,"truncate"
               ,"char"
               ,"declare"
               ,"SiteName"
               ,"net user"
               ,"xp_cmdshell"
               ,"/add"
               ,"exec master.dbo.xp_cmdshell"
               ,"net localgroup administrators"
            };
            //构造正则表达式
            String str_Regex = ".*(";
            for (int i = 0; i < strBadChar.length - 1; i++)
            {
                str_Regex += strBadChar[i] + "|";
            }
            str_Regex += strBadChar[strBadChar.length - 1] + ").*";
            return str_Regex;
        }

判断输入字符串中是否有SQL关键字的方法:

// <summary>
        //判断字符串中是否有SQL攻击关键字
        // </summary>
        // <param name="inputString">待判定的字符串</param>
        // <returns> True:安全;False:有危险urns>
        public boolean processSqlStr(String inputString)
        {
            String SqlStr = @"and|or|exec|execute|insert|select|delete|
                   update|alter|create|drop|count|\*|chr|char|asc|mid|
                   subString|master|truncate|declare|xp_cmdshell|restore|backup|
                   net +user|net +localgroup +administrators";
            try
            {
                if (StringUtils.isNotEmpty(inputString))
                {
                    String str_Regex = @"\b(" + SqlStr + @")\b";
                    Regex regex = new Regex(str_Regex, RegexOptions.IgnoreCase);
                    if (true == regex.isMatch(inputString))
                        return false;
                    //下面是字符串转换相关代码
                    //MatchCollection matches = Regex.matches(inputString);
                    //For (int i = 0; i < matches.count; i++)
                    //  inputString = inputString.replace(matches[i].value,
                    //      "["+matches[i].value+"]");
                }
            }
            catch
            {
                return false;
            }
            return true;
        }

规则28. 源自用户输入的值,禁止通过字符串串联的方式直接拼装到SQL语句中,必须使用参数化的方式

说明:当用户的输入作为SQL语句一部分时,必须使用参数化方式。

实施指导:如下述通过字符串串联的方式直接拼装SQL的方式,很容易被攻击:

String selectSql =“select* from Users where username='” + userName + “' and password = '” + pwd + “'”;
        DataSet ds = dataBase.execute Query(selectSql);

此时应该使用预编译语句SqlCommand,参数化模式的输入将检查输入的类型,确保输入值在数据库中当作字符串、数字、日期或boolean等值而不是可执行代码进行处理,从而防止SQL注入攻击。而且,由于SqlCommand对象己预编译过,所以其执行速度较快。因此,多次执行的SQL语句经常创建为SqlCommand对象,还可以提高效率。

正确的做法:使用参数化方式编码为:

String selectSql = "select* from Users where username =? and password =? ";
        PreparedStatement preparedStatement = connection. prepareStatement(selectSql);
        preparedStatement.setString(1, "userName");
        preparedStatement.setString(2, "password");
        ResultSet ds = preparedStatement.executeQuery();

规则29. 将用户输入的单引号替换成双引号

说明:用户输入的字符串类型数据,一般都要对其单引号进行转义:我们可以将单引号替换为双引号,即可避免字符串类型的注入。

规则30. 客户端不能传输完整SQL语句到服务端执行。无论任何时候,服务端都不能提供能够执行完整SQL语句的远程访问接口。

说明:在网络传输过程中,完整的SQL语句、SQL主干、条件片段,都会导致数据库信息泄露。供客户端调用的服务端方法,都不能提供可执行完整SQL语句的万能接口,包括RESTful、WebService等。服务端应该提供的是针对具体业务的专有方法,不应该支持传递SQL语句,只能传输由零到多个独立条件值组成的参数列表。服务端的方法都应该是专有的,不应该试图提供万能接口

实施指导:远程服务不应该提供可执行SQL的万能方法,如下服务方法是错误的:

// <summary>
        //根据传入的SQL语句,返回执行结果的DataSet
        // </summary>
        // <param name="psSql">待执行的SQL语句</param>
        // <returns>执行结果DataSet</returns>
        public static List<Object> executeSQL(String psSql);
        //客户端调用示例
        var objs = userClient.executeSQL(“select id, name, age from Users where id = ‘0101001’”);

规则31. 客户端不能传输SQL主干到服务端执行。

说明:SQL主干指的是带有SQL关键字、数据库表名、取数字段列表等敏感信息构成的SQL主体。

实施指导:远程服务不应该提供可传递SQL主干的方法,如下服务方法是错误的:

// <summary>
        //根据传入的SQL主干和时间点,返回该时间点之后修改数据的DataSet
        // </summary>
        // <param name="mainSql">待执行的SQL语句主干,用来表示取数的表名和字段列表</param>
        // <param name="lastChangeTime ">最后修改时间点,查询结果将返回最后修改时间晚于该时间点的数据</param>
        // <returns>执行结果DataSet</returns>
        public static List<Object> executeSQL(String mainSql, String lastChangeTime);
        //客户端调用示例
        var objs = userClient.executeSQL(“select id, name, age from Users”, time);

规则32. 客户端不能传输数据库表名到服务端执行。

说明:数据库表名是后台数据结构中比较重要的敏感信息,不能对外泄露。

规则33. 客户端不能传输带有数据库字段和查询值组成的条件字符串到服务端执行。

说明:数据库字段也是后台数据结构中比较重要的敏感信息,不能对外泄露。字段的查询值,如果是用户输入,也必须进行参数化方式加入到待执行的SQL语句中。比如Name=’userName’ and password=’pwd’。

实施指导1:字段固定的,可以直接在服务端方法中以参数列表的形式传递。对于字段不固定的情况,可以使用GSPFilter作为参数进行传递。GSPFilter支持将多个字段及其对应的值独立传输到服务端,传输到服务端后,可以进行校验后再组装成具体的参数化的SQL条件。

实施指导2:对于固定字段的查询,可以直接在服务端接口中按照字段顺序设置方法参数即可。

        //服务端方法定义
        public List<User> getUsers(String orgId, String userType);

综合查询页面,用户可以动态组织查询所依赖的字段,此时查询条件的传递就可以基于GSPFilter进行传递。

        //服务端方法定义
        public List<User> getUsers(GSPFilter filter);
        //客户端调用示例
        userClient.getUsers(filter);
1.3.3.6 XML处理

要和防止SQL注入一样防止XML注入,因此在XML操作时,也要和SQL处理一样,进行用户输入数据的有效性校验、XPATH语句拼接校验、输出编码等多种预防途径。

规则34. 用于写入到XML中的用户输入,都要进行有效性校验和转码。

说明:用户输入中如果包含有XML结构相关的的字符串,则有可能会导致XML文件结构混乱、插入用户非法数据等。

XML中需要校验的特殊字符包括:< > & “ ’,转义方式如下:

特殊字符转义序列说明
<<XML标记符号
>>XML标记符号
&&转义开始字符
&qout;属性值的开始结束符号
'属性值的开始结束符号

实施指导:比如让用户输入用户名并进行存储的XML文件格式如下:

<USER  role = "guest" > UserName </ USER >

则用户可能输入下面内容:(User1 </ USER >  < USER  role="admin">User2)     

<USER  role = "guest" > User1 </ USER >      < USER  role="admin">User2</USER>

如果应用程序读取这个文件,并且决定给每个用户分配何种访问权限的时候,User2将获得管理员权限。

规则35. 禁止动态构建XPath语句。

说明:和动态构建SQL一样,动态构建XPath语句也会导致注入漏洞(XPath注入)。动态构建XPath语句的例子:

xpathEpression="//users/user[" + loginID + "]/firstname";

实施指导:对于如下XPath查询语句:

 xpathEpression="//users/user[loginID/text()='" + UserID+ "'and  password/text()='" +  password+ "'] ";

用户可以采用如下的方法绕过身份验证:比如用户传入类似 ‘ or ‘1’=’1 的值,XPath 查询语句变成:

   xpathEpression="//users/user[loginID/text()=' ' or '1'='1' and  password/text()=' ' or '1'='1']";

这个XPath会在逻辑上使查询一直返回 true。

1.3.3.7 OS命令

规则36. 在服务端,尽量不要调用操作系统命令。

说明:调用外部程序时,一般都需要输入相关参数,如果该参数中包含客户输入数据,则可能会造成OS注入攻击。

规则37. 如果在服务端执行操作系统命令,禁止从客户端获取命令。

说明:如果服务端代码中使用exec(cmd)或其他命令执行操作系统命令,那么禁止从客户端获取命令。

规则38. 如果在服务端执行操作系统命令时,参数中必须包含用户输入数据,则必须将参数中用户输入相关数据进行数据有效性校验。

说明:如果必须从客户获取命令的参数,那么必须采用正则表达式对命令参数进行严格的校验,以防止命令注入(因为,一旦从客户端获取命令或参数,非常容易构造命令注入,危害系统)。

OS命令中需要校验的特殊字符包括:; & | < >,分别描述如下:

特殊字符转义序列说明
<输入重定向输入重定向命令,比如:从文件中读入命令输入,而不是从键盘中读入
>输出重定向将运行的结果传递到后面的范围,比如输出到文件中
**\**管道(将第一条命令的结果作为第二条命令的参数)
&组合命令两个命令依次执行
;分号当命令相同时,可以将不同目标用“;”来隔离,一次执行多个
%0A换行符的十六进制编码运行一个新命令

规则39. 如果服务端执行操作系统命令,则必须设置用户的权限为最小权限。

说明:执行外部程序所使用的OS用户,要使用最小权限原则,仅授予其为完成所承担任务所需的最小权限。

规则40. 如果需要在OS上给用户赋权限,则必须设置用户的权限为最小权限。

说明:很多时候,业务系统需要一定的操作系统权限才能运行,比如文件夹权限读、写权限等,此时需要使用最小权限原则,仅授予其为完成所承担任务所需的最小权限,而不是按照默认最大权限进行设置。

实施说明:业务系统在服务端进行日志、缓存操作是,对所在文件夹都需要读写权限,此时只需要赋予IIS所使用用户的读写权限即可,没必要赋予Everyone读写权限,这样有权限扩大的问题。

1.3.3.8 输出编码

规则41. 对于不可信的数据,输出到客户端前必须先进行HTML编码。

说明:不可信的数据(也就是其他业务系统生成的未经本应用程序验证的表数据或文件数据),通过对输出到客户端的数据进行编码,可以防止浏览器将HTML视为可执行脚本,从而防止跨站脚本攻击。

实施指导:ASP.NET语言可以通过HtmlEncode方法对HTML的输出进行编码:

 HttpUtility.HtmlEncode(htmlInputTxt.Text)

PHP语言可以通过htmlentities或htmlspecialchars方法对HTML输出进行编码。

JSP语言可以通过替换输出数据的特殊字符【&<>’”()%+】为其他表示形式后再输

出给客户端,例如:

        <%
          OutStr="<script>alert('XSS)</script>";
          OutStr=OutStr.replaceAll("&","&amp;");
          OutStr=OutStr.replaceAll("<","&It;");
          OutStr=OutStr.replaceAIl(">","&gt;");
          OutStr=OutStr.replaceAll("\","&gt;");
          OutStr=OutStr.replaceAll("\'","&#39;");
          OutStr=OutStr.replaceAIl("\\(","&#40;");
          OutStr=OutStr.replaceAll("\\)","#41;");
          System.out.println(OutStr);
        %>
1.3.3.9 上传下载

规则42. 必须在服务器端采用白名单方式对上传或下载的文件类型、大小进行严格的限制。

规则43. 禁止以用户提交的数据作为读/写/上传/下载文件的路径或文件名。

说明:建议对写/上传文件的路径或文件名采用随机方式生成,或将写/上传文件放置在有适当访问许可的专门目录。对读/下载文件采用映射表(例如,用户提交的读文件参数为1,则读取filel,参数为2,则读取file2)。防止恶意用户构造路径和文件名,实施目录跨越和不安全直接对象引用攻击。下载相关的安全问题主要用户下载了许可下载目录以外的文件,比如在文件路径中加入”../”字符,如果服务端未做校验,且直接使用了用户输入的路径作为文件查找路径,则会使得用户可以下载非下载目录的文件。

1.3.3.10 异常处理

规则44. 应用程序出现异常时,禁止向客户端暴露不必要的信息,尤其是安全敏感信息,只能向客户端返回一般性的错误提示消息。

说明:具体的异常信息中,往往包含着很多底层细节,比如表名、字段名、数据库类型等等,所以应用程序出现异常时,禁止将数据库版本、数据库结构、操作系统版本、堆找跟踪、文件名和路径信息、SQL查询字符串等对攻击者有用的信息返回给客户端。建议重定向到一个统一、默认的错误提示页面,进行信息过滤。

实施指导:数据执行时,经常会直接提示如下格式的信息:

Invalid  Object “GSPUser”, Invalid Column “UserId”

正确的做法是,封装新的业务异常,并将数据库异常作为InnerException进行关联。如:

throw new GSPException("用户保存失败!",  innerException);
1.3.3.11 Web页面代码注释

规则45. 在注释信息中禁止包含物理路径信息。

规则46. 在注释信息中禁止包含数据库连接信息。

规则47. 在注释信息中禁止包含SQL语句信息。

规则48. 对于静态页面,在注释信息中禁止包含源代码信息。

规则49. 对于动态页面不能使用普通注释,只能使用隐藏注释。

说明:动态页面包括ASP、PHP、JSP、CGI等由动态语言生成的页面。通过浏览器查看源码的功能,能够查看动态页面中的普通注释信息,但看不到隐藏注释(隐藏注释不会发送给客户端)。因此,为了减少信息泄漏,建议只使用隐藏注释。

// 实施指导:
 <formaction=h.jsp>
        <!-- HTML 注释,查看源代码时能够看到 -->
        <%-- 隐藏注释1,查看源代码时看不到 --%>
        <textareaname=alength=200></textarea>
        <inputtype=submitvalue=test>
        </form>
        <%
            //隐藏注释2,查看源代码时看不到
            String str=(String)request.getParameter("a");
            /*隐藏注释3,查看源代码时看不到 */
            str=str.replaceAll("<",&lt;");
            out.println(str);
        %>
1.3.3.12 其他

规则50. 对客户端提交的表单请求进行合法性校验,防止跨站请求伪造攻击。

说明:跨站请求伪造(CSRF)是一种挟制终端用户在当前已登录的Web应用程序上执行非本意的操作的攻击方法。攻击者可以迫使用户去执行攻击者预先设置的操作,例如,如果用户登录网络银行去查看其存款余额,他没有退出网络银行系统就去了自己喜欢的论坛去灌水,如果攻击者在论坛中精心构造了一个恶意的链接并诱使该用户点击了该链接,那么该用户在网络银行帐户中的资金就有可能被转移到攻击者指定的帐户中。当CSRF针对普通用户发动攻击时,将对终端用户的数据和操作指令构成严重的威胁;当受攻击的终端用户具有管理员帐户的时候,CSRF攻击将危及整个Web应用程序。

实施指导:方法一:为每个session创建唯一的随机字符串,并在受理请求时验证

<form action = "/transfer.do" method="post">
        <input type = "hidden" name="randomStr" value=<%=request.getSession().getAttribute("randomStr")%>>
        </form>
        //判断客户端提交的随机字符串是否正确
        String randomStr = (String)request.getParameter("randomStr");
        if(randomStr == null) randomStr="";
        if(randomStr.equals(request.getSession().getAttribute("randomStr")))
        {//处理请求}
        else{
        //跨站请求攻击,注销会话
        }

方法二:受理重要操作请求时,在相应的表单页面增加图片验证码,用户提交操作请求的同时提交验证码,在服务器端先判断用户提交的验证码是否正确,验证码正确再受理操作请求。

规则51. 禁止使用eval()函数来处理用户提交的字符串。

说明:JavaScript中的eval()函数存在安全隐患,该函数可以把输入的字符串当作JavaScript表达式执行,容易被恶意用户利用。

规则52. 关闭登录窗体表单中的自动填充功能,以防止浏览器记录用户名和口令。

说明:浏览器都具有自动保存用户输入数据和自动填充数据的能力。为了保障用户名和口令的安全,必须关闭自动填充选项,指示浏览器不要存储登录窗口中用户名、口令等敏感信息。

实施指导:

在form表单头中增加选项(autocomplete=”off”),例如:

<form  action = "Login.jsp" name=login method = post autocomplete="off">

规则53. 防止网页被框架盗链或者点击劫持。

说明:框架盗链(Cross Frame Script, 简称CFS)和点击劫持(ClickJacking)都利用到框架技术,防范措施就是防止网页被框架。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值