不安全的直接对象引用:你的ASP.NET应用数据是否安全?
作者:网络转载 发布时间:[ 2015/4/16 14:24:43 ] 推荐标签:.NET 对象 引用 应用数据
可以实现不同级别的混淆,每一级别都能提供不同级别的安全性和平衡性.我们将看到第一个选项是一种比较常见的,安全的但有些限制的选项,我喜欢称之为“视野”,该词间接参考地图。
作用域间接引用映射
引用映射与Bit.ly短网址并没有什么不同,你的服务器知道怎样将一个公开的表面值映射到一个内部值来代表敏感数据。作用域代表我们用于限制映射使用而放入的限制条件。这对理论研究已经足够了,我们来看一个例子:
我们认为一个账号编号例如1344573490是一个敏感数据,我们希望隐藏它并只提供可被确认的账号持有者。为了避免暴露账号编号,我们可以提供一个间接引用到账号编号的公开表面值。服务器将会知道怎样把这个间接引用映射回直接引用,这个直接引用指向我们的账号编号。服务器使用的映射存储在一个ASP.NET用户回话中,这是作用域,关于作用域的更多内容,来看看这个实现:
public static class ScopedReferenceMap
{
private const int Buffer=32;
///<summary>
///Extension method to retrieve a public facing indirect value
///</summary>
///<typeparam name="T"></typeparam>
///<param name="value"></param>
///<returns></returns>
public static string GetIndirectReference<T>(this T value)
{
//Get a converter to convert value to string
var converter=TypeDescriptor.GetConverter(typeof(T));
if(!converter.CanConvertTo(typeof(string)))
{
throw new ApplicationException("Can't convert value to string");
}
var directReference=converter.ConvertToString(value);
return CreateOrAddMapping(directReference);
}
///<summary>
///Extension method to retrieve the direct value from the user session
///if it doesn't exists,the session has ended or this is possibly an attack
///</summary>
///<param name="indirectReference"></param>
///<returns></returns>
public static string GetDirectReference(this string indirectReference)
{
var map=HttpContext.Current.Session["RefMap"];
if(map==null)throw new ApplicationException("Can't retrieve direct reference map");
return((Dictionary<string,string>)map)[indirectReference];
}
private static string CreateOrAddMapping(string directReference)
{
var indirectReference=GetUrlSaveValue();
var map=
(Dictionary<string,string>)HttpContext.Current.Session["RefMap"]??
new Dictionary<string,string>();
//If we have it,return it.
if(map.ContainsKey(directReference))return map[directReference];
map.Add(directReference,indirectReference);
map.Add(indirectReference,directReference);
HttpContext.Current.Session["RefMap"]=map;
return indirectReference;
}
private static string GetUrlSaveValue()
{
var csprng=new RNGCryptoServiceProvider();
var buffer=new Byte[Buffer];
//generate the random indirect value
csprng.GetBytes(buffer);
//base64 encode the random indirect value to a URL safe transmittable value
return HttpServerUtility.UrlTokenEncode(buffer);
}
}
这里,我们创建了一个简单的工具类ScopedReferenceMap,可以提供扩展的方法处理一个值例如我们的银行卡号1344573490处理成Xvqw2JEm84w1qqLN1vE5XZUdc7BFqarB0,这是所谓的间接引用。
终,当一个间接引用值被请求时,我们使用一个用户会话来作为保持请求中的间接引用和直接引用之间的映射的一种方法。用户会话成为间接引用的作用域,而且强制在每个用户映射上加上时间限制。只有经过验证和指定的用户会话才具有检索的能力。
你可以利用它在任何你需要的地方创建间接引用,例如:
AccountNumber=accountNumber.GetIndirectReference();//create an indirect reference
现在,在一个使用如下URL的传入请求(请求一个账号的详细信息):
我们可以看出,对accountNumber的间接引用映射通过与我们的访问控制合作重新得到真实值:
[HttpGet]
public ActionResult Details(string accountNumber)
{
//get direct reference
var directRefstr=accountNumber.GetDirectReference();
var accountNum=Convert.ToInt64(directRefstr);
Account account=_accountRepository.Find(accountNum);
//Verify authorization
if(account.UserId!=User.Identity.GetUserId())
{
return new HttpUnauthorizedResult("User is not Authorized.");
}
//…
在我们对获得直接引用的尝试中,如果ASP.NET用户会话没有获得一个映射,那可能是受到了攻击。但是,如果映射存在,仍然得到了直接引用,则可能是值被篡改了。
正如我前面提到的,用户会话创建了一个作用域,用户和时间约束限制了映射回直接引用的能力。这些限制条件以其自身的形式提供额外的安全措施。但是,你或许在使用ASP.NET会话状态时遇到问题,这可能是由于已知的安全弱点,你也可能会问怎样才能让这些限制条件与提供含状态传输(Representational State Transfer)风格的引擎例如超媒体状态应用引擎良好的合作共处?真是个好问题,让我们来检查一些替代选项吧。
HATEOAS Gonna Hate
如果你思考过通过网络服务进行的典型交互方式,这种在你的应用中通过发送一个request和接受一个包含额外超媒体链接(例如URLs)的response来获得额外的资源的方式对web开发者来说是一个可以理解的概念。
这个概念经过高度的精炼已经成为构建REST风格的网络服务的支柱之一:超媒体作为应用程序状态或HATEOAS的引擎。用一句话来解释HATEOAS是网络服务提供对于资源发现操作的能力:它通过在HTTP响应中提供超媒体链接。这不是一篇关于定义REST风格网络服务的论文,所以,如果REST和HATEOAS对你来说是陌生概念,你需要查看关于REST和关于HATEOAS的资料来对他们有一个了解。
因此,提供包含有作用域的间接引用参数的URL的想法与像HATEOAS这样的概念或需要一直提供持久性URL(具有较长生存时间的URL)之间是有很大困难的。如果我们希望提供持久性URL的同时,包含间接引用值,那么我们需要采用一种不同的安全方法,我们应该怎么做呢?
静态间接引用映射
为了提供包含间接引用的持久性URL,我们接下来需要一些方法来在任意给定的时间或者至少是在未来相当长的一段时间内将间接值映射回原始的直接值。如果我们想要持久性,那么像使用一个用户会话来维持一个引用映射这样的限制条件将不再是个可用的选项。让我们来看看可以使用静态间接引用映射方案的场景。
假设你有一个B2B网络应用,它允许商家获得指定给他们的VIP商品的定价。给客户系统发送一个请求,返回一个包含链接到此客户的VIP商品的附加超媒体链接的响应。当点击VIP商品链接时,接收到的响应包含他们指定商家的所有可用VIP商品的超媒体链接。
在我们的例子中,我们决定通过创建一个间接引用,对VIP商品URL中的VIP商品ID加以混淆,到时候我们能很快地重新映射回商品的实际ID。
例子:https://AppCore.com/business/Acme/VIP/Products/99933
针对我们的处境,加密是一个不错的选择,这使得我们能更好的掌控将间接引用映射回实际商品ID的生命周期。
如同我们在域引用例子中做的那样,利用相同的API,来看看它将会成为什么样子,然后我们带着关注和额外的选择,再讨论一下我们做了什么和为什么用这种方法:
public static class StaticReferenceMap
{
public const int KeySize=128;//bits
public const int IvSize=16;//bytes
public const int OutputByteSize=KeySize/8;
private static readonly byte[]Key;
static StaticReferenceMap()
{
Key=//pull 128 bit key in
}
///<summary>
///Generates an encrypted value using symmetric encryption.
///This is utilizing speed over strength due to the limit of security through obscurity
///</summary>
///<typeparam name="T">Primitive types only</typeparam>
///<param name="value">direct value to be encrypted</param>
///<returns>Encrypted value</returns>
public static string GetIndirectReferenceMap<T>(this T value)
{
//Get a converter to convert value to string
var converter=TypeDescriptor.GetConverter(typeof(T));
if(!converter.CanConvertTo(typeof(string)))
{
throw new ApplicationException("Can't convert value to string");
}
//Convert value direct value to string
var directReferenceStr=converter.ConvertToString(value);
//encode using UT8
var directReferenceByteArray=Encoding.UTF8.GetBytes(directReferenceStr);
//Encrypt and return URL safe Token string which is the indirect reference value
var urlSafeToken=EncryptDirectReferenceValue<T>(directReferenceByteArray);
return urlSafeToken;
}
///<summary>
///Give a encrypted indirect value,will decrypt the value and
///return the direct reference value
///</summary>
///<param name="indirectReference">encrypted string</param>
///<returns>direct value</returns>
public static string GetDirectReferenceMap(this string indirectReference)
{
var indirectReferenceByteArray=
HttpServerUtility.UrlTokenDecode(indirectReference);
return DecryptIndirectReferenceValue(indirectReferenceByteArray);
}
private static string EncryptDirectReferenceValue<T>(byte[]directReferenceByteArray)
{
//IV needs to be a 16 byte cryptographic stength random value
var iv=GetRandomValue();
//We will store both the encrypted value and the IV used-IV is not a secret
var indirectReferenceByteArray=new byte[OutputByteSize+IvSize];
using(SymmetricAlgorithm algorithm=GetAlgorithm())
{
var encryptedByteArray=
GetEncrptedByteArray(algorithm,iv,directReferenceByteArray);
Buffer.BlockCopy(
encryptedByteArray,0,indirectReferenceByteArray,0,OutputByteSize);
Buffer.BlockCopy(iv,0,indirectReferenceByteArray,OutputByteSize,IvSize);
}
return HttpServerUtility.UrlTokenEncode(indirectReferenceByteArray);
}
private static string DecryptIndirectReferenceValue(
byte[]indirectReferenceByteArray)
{
byte[]decryptedByteArray;
using(SymmetricAlgorithm algorithm=GetAlgorithm())
{
var encryptedByteArray=new byte[OutputByteSize];
var iv=new byte[IvSize];
//separate off the actual encrypted value and the IV from the byte array
Buffer.BlockCopy(
indirectReferenceByteArray,
0,
encryptedByteArray,
0,
OutputByteSize);
Buffer.BlockCopy(
indirectReferenceByteArray,
encryptedByteArray.Length,
iv,
0,
IvSize);
//decrypt the byte array using the IV that was stored with the value
decryptedByteArray=GetDecryptedByteArray(algorithm,iv,encryptedByteArray);
}
//decode the UTF8 encoded byte array
return Encoding.UTF8.GetString(decryptedByteArray);
}
private static byte[]GetDecryptedByteArray(
SymmetricAlgorithm algorithm,byte[]iv,byte[]valueToBeDecrypted)
{
var decryptor=algorithm.CreateDecryptor(Key,iv);
return decryptor.TransformFinalBlock(
valueToBeDecrypted,0,valueToBeDecrypted.Length);
}
private static byte[]GetEncrptedByteArray(
SymmetricAlgorithm algorithm,byte[]iv,byte[]valueToBeEncrypted)
{
var encryptor=algorithm.CreateEncryptor(Key,iv);
return encryptor.TransformFinalBlock(
valueToBeEncrypted,0,valueToBeEncrypted.Length);
}
private static AesManaged GetAlgorithm()
{
var aesManaged=new AesManaged
{
KeySize=KeySize,
Mode=CipherMode.CBC,
Padding=PaddingMode.PKCS7
};
return aesManaged;
}
private static byte[]GetRandomValue()
{
var csprng=new RNGCryptoServiceProvider();
var buffer=new Byte[16];
//generate the random indirect value
csprng.GetBytes(buffer);
return buffer;
}
}
在这里,我们的API应该看起来像ScopedReferenceMap,只有在发生变化时才会在内部运行,我们借助了.NET中具有128位秘钥的AesManaged对称加密库和一个对初始向量(IV)高度加密的随机值。你们中的一些人可能会意识到,怎样才能做到在速度与强度之间的优化呢?
AesManaged在实例中要比FIPS快约170倍,相当于AesCryptoServiceProvider
128位长度需要执行算法的次数少于4次,这要比更大的256位长度要小
关键点之一是我们为初始向量(IV)生成一个强加密的随机值,这个随机值应用到了所有的加密过程中。秘钥同样是个机密,为了保密,我选择将它留给你,让你来找出你想怎样使用秘钥,好的一方面是我们不必与任何人分享秘钥。终,我们存储带有密码的非机密的初始向量(间接引用),这样我们可以在一个请求中解密间接引用。
要地清楚,这不是一个可替代的访问控制。这只能用或应该用在正确的访问控制连接上。
现在,也还有一个没有那么复杂的方法。一种改进过的方法是包含了上述过程的加密认证(AE),但是这是一个基于哈希消息验证码的过程。认证加密也支持像填充、消息篡改等暴漏的安全攻击。此外,像Stan Drapkin那样的学着会告诉你对称加密必须被认证加密。
然而,这并不是一篇关于加密的文章。所有的出发点是以后一个选项来“照亮”其他的选项,目的是给那些不间接使用作用域的用户会话,如.NET,提供一个敏感数据的模糊环境。
牢记这些
缓解和减少不安全的直接对象引用的可靠的方法是具有适当的访问控制,再多的混淆都不能阻止对数据的未授权访问。
资料是非常重要的,恶意用户会以对他们有利的方式来使用它,当你意识到的时候太晚了。因此,当你认为一项数据是个敏感数据时,你需要应用一定等级的混淆来进行技术上的限制,例如使用用户会话。但是会有一个.NET会话开销,所以要知道你应该怎样利用它。
绝大多数应用并不需要混淆和创建间接引用,但是对于像金融等高度敏感的网站好加上这层额外的安全层。
后一点是:对特定数据值的混淆只是一个模糊的安全。它需要与其它安全措施同时使用,例如正确的访问控制。从这方面来说,不应该单独依赖它。
总结
不安全的直接对象引用主要涉及的内容是,通过合理的访问控制来保护数据不被未经授权的访问。其次,为了防止像直接引用键值那样的敏感数据遭到泄露,要了解如何以及何时该通过间接引用那些键值来添加一层混淆。后,在决定要使用混淆技术时,要意识到利用间接引用映射来弥补漏洞的局限性。
相关推荐
更新发布
功能测试和接口测试的区别
2023/3/23 14:23:39如何写好测试用例文档
2023/3/22 16:17:39常用的选择回归测试的方式有哪些?
2022/6/14 16:14:27测试流程中需要重点把关几个过程?
2021/10/18 15:37:44性能测试的七种方法
2021/9/17 15:19:29全链路压测优化思路
2021/9/14 15:42:25性能测试流程浅谈
2021/5/28 17:25:47常见的APP性能测试指标
2021/5/8 17:01:11