Fork me on GitHub
Mvc中常见错误简单总结

Mvc中常见错误简单总结

一、修改实体类报错

存储区更新、插入或删除语句影响到了意外的行数(0)。实体在加载后可能被修改或删除。刷新 ObjectStateManager 项。
 
对于这个错误其实是由于表的主键没有赋值引起的,所以出现了“存储区更新、插入或删除语句影响到了意外的行数(0)”这句话。 如果是强类型view时如果不希望主键显示出来,建议可以这样做: @Html.HiddenFor(model => model.DispatchID)  就是设置一个隐藏控件来存储主键。如果不是强类型的view,则可以手动给你创建的实体对象添加主键的值。
 
二、一个实体对象不能由多个 IEntityChangeTracker 实例引用
在添加或者修改的时候不小心的话往往容易出现这个错误,那么这个错误是怎么产生的呢?
先给出解决方案,然后给出原理,仅攻大家参考:
 
解决方案:
你在对每个对象进行删除或者修改时都是先进行附加到上下文,然后修改状态,如下:
public virtual T Add(T model)
{
db.Set<T>().Add(model);
db.SaveChanges();
db.Entry<T>(model).State = EntityState.Detached;  //增加这句可以解决问题
return model;
}

原理:
在EF中有一个叫做对象管理容器(ObjectStateManager)的东东,如果你希望对某些对象进行增加、删除、修改,查询的操作,那么你首先需要把这些实体对象添加到这个对象管理容器中,
(查询出来的实体默认已经添加进去,不用手动添加了)。但是假如你把好几个对象都添加到了EF的对象管理容器中(ObjectStateManage)那么提交的时候,系统(EF)怎么知道你是增加、删除还是修改呢?
这时就需要另外一个东东出场了,它叫做DbEntityEntry(一个伪包装类)。这个伪包装类会自动把你放到对象管理容器中的对象包裹起来,这时你放进去的实体默认都放到一个自动产生的伪包装类中,
这个类还有一个很重要的属性就是对象的状态,是一个枚举类型的值。有Add、Deleted、Detached、Modified、Unchanged这样几个值,这几个值得意思就不用我讲解了吧,相信大家一看都懂。
这是db.savechanges()的时候EF就会根据实体和实体状体生成相应的sql语句,对数据库进行增删改查操作。

附加下图:


 
 
分类: Asp.NetMvc

EntityFramework用法探索(七)线程安全实践

前文中,我们通过Unity来注册各种类型和WiringUp。

复制代码
 1       IUnityContainer container = new UnityContainer()
 2         .RegisterType(typeof(IRepository<>), typeof(Repository<>), new ContainerControlledLifetimeManager())
 3         .RegisterType<IUnitOfWork, UnitOfWork>(new ContainerControlledLifetimeManager())
 4         .RegisterType<DbContext, RETAILContext>(new ContainerControlledLifetimeManager())
 5         .RegisterType<DbContextAdapter>(new ContainerControlledLifetimeManager())
 6         .RegisterType<IObjectSetFactory, DbContextAdapter>(new ContainerControlledLifetimeManager())
 7         .RegisterType<IObjectContext, DbContextAdapter>(new ContainerControlledLifetimeManager())
 8         .RegisterType<ICustomerRepository, CustomerRepository>(new ContainerControlledLifetimeManager());
 9 
10       UnityServiceLocator locator = new UnityServiceLocator(container);
11       ServiceLocator.SetLocatorProvider(() => locator);
12 
13       ICustomerRepository customerRepository = container.Resolve<ICustomerRepository>();
复制代码

但选择使用了ContainerControlledLifetimeManager对象生命周期管理器,其将每个对象存储为Singleton。这导致在多线程环境下会产生异常。

例如我们尝试在多线程条件下更新Customer表:

复制代码
 1       List<Task> tasks = new List<Task>();
 2       for (int i = 0; i < 16; i++)
 3       {
 4         DomainModels.Customer modifiedCustomer = Mapper.Map<DomainModels.Customer, DomainModels.Customer>(customer1);
 5         modifiedCustomer.Name = modifiedCustomer.Name + i;
 6 
 7         Task t = Task.Factory.StartNew(() =>
 8         {
 9           try
10           {
11             customerRepository.UpdateCustomer(modifiedCustomer);
12           }
13           catch (Exception ex)
14           {
15             Console.WriteLine("Exception occurred in thread " + Thread.CurrentThread.ManagedThreadId);
16             Console.WriteLine(ex.Message);
17           }
18         });
19         tasks.Add(t);
20       }
21       Task.WaitAll(tasks.ToArray());
复制代码

但由于我们仍然需要EntityFramework的Local功能,即在当前线程环境下始终使用当前上下文中的对象。我们可能还无法选择其他Unity对象生命期管理模型

此时,我们考虑一种新的方法,引入线程Scope功能,即在给定线程中,使用同一个UnityContainer来维护对象,这样间接利用的EntityFramework的上下文功能。

原理很简单,就是为每个线程生成一个单独的ChildUnityContainer。

复制代码
 1   public class UnityContainerScope : IDisposable
 2   {
 3     private static ConcurrentDictionary<int, bool> scopeMapping
 4       = new ConcurrentDictionary<int, bool>();
 5 
 6     protected UnityContainerScope()
 7     {
 8       ScopeId = Thread.CurrentThread.ManagedThreadId;
 9       scopeMapping.Add(ScopeId, true);
10     }
11 
12     public int ScopeId { get; private set; }
13     public static int ScopeCount { get { return scopeMapping.Count; } }
14 
15     public static UnityContainerScope NewScope()
16     {
17       return new UnityContainerScope();
18     }
19 
20     public static bool InScope(int scopeId)
21     {
22       return scopeMapping.ContainsKey(scopeId);
23     }
24 
25     public void Dispose()
26     {
27       UnityContainerDispatcher.DisposeContainer();
28       scopeMapping.Remove(ScopeId);
29     }
30   }
复制代码

这里同时需要一个UnityContainerDispatcher来负责为线程生成Container容器。

复制代码
 1   public static class UnityContainerDispatcher
 2   {
 3     private static IUnityContainer parentContainer = null;
 4     private static ConcurrentDictionary<int, IUnityContainer> containerMapping
 5       = new ConcurrentDictionary<int, IUnityContainer>();
 6 
 7     public static void InjectParentContainer(IUnityContainer unity)
 8     {
 9       if (unity == null)
10         throw new ArgumentNullException("unity");
11 
12       parentContainer = unity;
13     }
14 
15     public static IUnityContainer GetContainer()
16     {
17       int key = Thread.CurrentThread.ManagedThreadId;
18 
19       if (!UnityContainerScope.InScope(key))
20       {
21         throw new UnityContainerNotInScopeException(
22           string.Format(CultureInfo.InvariantCulture,
23           "The specified scope id [{0}] is not in scope.", key));
24       }
25 
26       if (!containerMapping.ContainsKey(key))
27       {
28         BuildUpContainer(key);
29       }
30 
31       return containerMapping.Get(key);
32     }
33 
34     public static void DisposeContainer()
35     {
36       int key = Thread.CurrentThread.ManagedThreadId;
37       IUnityContainer container = containerMapping.Remove(key);
38       if (container != null)
39       {
40         container.Dispose();
41       }
42     }
43 
44     private static void BuildUpContainer(int key)
45     {
46       if (parentContainer == null)
47         throw new InvalidProgramException("The parent container cannot be null.");
48 
49       IUnityContainer childContainer = parentContainer.CreateChildContainer();
50       containerMapping.Add(key, childContainer);
51     }
52   }
复制代码

在注入的根UnityContainer中,我们通过使用CreateChildContainer方法来获取一个新的Container,同时继承所有根容器的注册配置信息。这要求使用HierarchicalLifetimeManager生命期管理器

此时,我们的代码修改为,

复制代码
 1       IUnityContainer container = new UnityContainer()
 2         .RegisterType(typeof(IRepository<>), typeof(Repository<>), new HierarchicalLifetimeManager())
 3         .RegisterType<IUnitOfWork, UnitOfWork>(new HierarchicalLifetimeManager())
 4         .RegisterType<DbContext, RETAILContext>(new HierarchicalLifetimeManager())
 5         .RegisterType<DbContextAdapter>(new HierarchicalLifetimeManager())
 6         .RegisterType<IObjectSetFactory, DbContextAdapter>(new HierarchicalLifetimeManager())
 7         .RegisterType<IObjectContext, DbContextAdapter>(new HierarchicalLifetimeManager())
 8         .RegisterType<ICustomerRepository, CustomerRepository>(new HierarchicalLifetimeManager());
 9 
10       UnityContainerDispatcher.InjectParentContainer(container);
11 
12       ICustomerRepository customerRepository = container.Resolve<ICustomerRepository>();
复制代码

创建多线程测试代码,

复制代码
 1       List<Task> tasks = new List<Task>();
 2       for (int i = 0; i < 16; i++)
 3       {
 4         DomainModels.Customer modifiedCustomer = Mapper.Map<DomainModels.Customer, DomainModels.Customer>(customer1);
 5         modifiedCustomer.Name = modifiedCustomer.Name + i;
 6 
 7         Task t = Task.Factory.StartNew(() =>
 8         {
 9           try
10           {
11             using (UnityContainerScope scope = UnityContainerScope.NewScope())
12             {
13               customerRepository.UpdateCustomer(modifiedCustomer);
14               Console.WriteLine("Modified " + modifiedCustomer.Name + " in thread " + Thread.CurrentThread.ManagedThreadId);
15             }
16           }
17           catch (Exception ex)
18           {
19             Console.WriteLine("Exception occurred in thread " + Thread.CurrentThread.ManagedThreadId);
20             Console.WriteLine(ex.Message);
21           }
22         });
23         tasks.Add(t);
24       }
25       Task.WaitAll(tasks.ToArray());
复制代码

测试结果表明已经可以安全的在多线程条件下工作了。

完整代码和索引

EntityFramework用法探索系列

完整代码下载

 
 
标签: C#.NETIoCUnityThreadingDatabaseEntityFrameworkCode FirstUnitOfWorkweb文件操作常见安全漏洞(目录、文件名检测漏洞)

做web开发,我们经常会做代码走查,很多时候,我们都会抽查一些核心功能,或者常会出现漏洞的逻辑。随着技术团队的壮大,组员技术日益成熟。 常见傻瓜型SQL注入漏洞、以及XSS漏洞。会越来越少,但是我们也会发现一些新兴的隐蔽性漏洞偶尔会出现。这些漏洞更多来自开发人员,对一个函数、常见模块功能设计不足,遗留下的问题。以前我们能够完成一些功能模块,现在要求是要安全正确方法完成模块才行。 接下来,我会分享一些常见功能模块,由于设计原因导致漏洞出现。下面,我们先看下,读取文件型功能漏洞。

  我们先看下下面一段代码,通过用户输入不同目录,包含不同文件

<?php
///读取模块名称
$mod = isset($_GET['m'])?trim($_GET['m']):'index';

///过滤目录名称不让跳转到上级目录
$mod = str_replace("..",".",$mod);

///得到文件
$file = "/home/www/blog/".$mod.".php";

///包含文件
@include($file);


  这段代码,可能在很多朋友做的程序里面有遇到过,对于新人来说,也是很容易出现这样问题,记得走查遇到该代码时候,我问到,你这个代码安全方面能做到那些?

答:1. 对”..”目录有做替换,因此用户传入模块名里面有有..目录都会被替换掉了。

         2.构造拼接file名称,有前面目录限制,有后面扩展名限制,包含文件就会限制在该目录了

  • 这段代码真的做到了目录安全检测吗?

我们来测试下,如果$mod传入这个值将会是什么样的结果。

image

$mod 通过构造输?mod=…%2F…%2F…%2F…%2Fetc%2Fpasswd%00 ,我们看结果将是:

image

居然include(“/etc/passwd”)文件了。

  • 怎么逃脱了我参数限制呢?

 

  首先:做参数过滤类型去限制用户输入本来就不是一个好方法,一般规则是:能够做检测的,不要做替换 只要是检测不通过的,直接pass 掉!这是我们的一个原则。过滤失败情况,举不胜举,我们来看看,实际过程。

1、输入”…/…/…/” 通过把”..” 替换为”.”后

2、结果是”../../../” 就变成了这个了

有朋友就会说,如果我直接替换为空格是不是就好了?在这个里面确实可以替换掉。但是不代表以后你都替换为空格就好了。再举例子下。如:有人将字符串里面javascript替换掉。代码如下:

……

$msg = str_replace(“javascript”,””,$msg);

看似不会出现了javascript了,但是,如果输入:jjavascriptavascript 替换,会替换掉中间一个变为空后。前面的”j” 跟后面的会组成一个新的javascript了。

  其次:我们看看,怎么逃脱了,后面的.php 限制呢。用户输入的参数有:”etc/passwd\0” ,\0字符非常特殊,一段连接后,文件名称变成了”……etc/passwd\0.php”,你打印出该变量时候,还是正确的。但是,一段放入到文件读写操作方法里面,\0后面会自动截断。操作系统,只会读取……etc/passwd文件了。 “\0”会出现在所有文件系统读写文件变量中。都会同样处理。这根c语言\0作为字符串完整标记有关系。

通过上面分析,大家发现做文件类型操作时候,一不注意将产生大的漏洞。而且该漏洞就可能引发一系列安全问题。

  • 该怎么做文件类操作呢?

  到这里,估计有人就会思考这个,做文件读写操作时候,如果路径里面有变量时候,我该怎么样做呢?有人会说,替换可以吗? “可以”,但是这个方法替换不严格,将会出现很多问题。而且,对于初写朋友,也很难杜绝。 做正确的事情,选择了正确的方法,会从本身杜绝问题出现可能了。 这里,我建议:对于变量做白名单限制。

  1. 什么是白名单限制

    举例来说:

    $mod = isset($_GET['m'])?trim($_GET['m']):’index’; ///读取模块名称后

    mod变量值范围如果是枚举类型那么:

    if(!in_array($mod,array(‘user’,’index’,’add’,’edit’))) exit(‘err!!!’);

    完全限定了$mod,只能在这个数组中,够狠!!!!

     

  2. 怎么做白名单限制

通过刚才例子,我们知道如果是枚举类型,直接将值放到list中即可,但是,有些时候,这样不够方面。我们还有另外一个白名单限制方法。就是限制字符范围

举例来说:

$mod = isset($_GET['m'])?trim($_GET['m']):’index’; ///读取模块名称后

我限制知道$mod是个目录名称,对于一般站点来说,就是字母加数字下划线之类。

if(!preg_match(“/^\w+$/”,$mod)) exit(‘err!!!’);

字符只能是:[A-Za-z0-9_] 这些了。够狠!!!

 

总结:是不是发现,白名单限制方法,做起来其实很简单,你知道那个地方要什么,就对输入检测必须是那些。而且,检测自己已知的,比替换那些未知的字符,是不是简单多了。 好了,先到这里,正确的解决问题方法,会让文件简单,而且更安全!!欢迎交流!

作者:chengmo  QQ:8292669 
本文版权归作者所有,欢迎转载,请务必添加原文链接。

 
 
分类: php
标签: phpweb安全
posted on 2013-06-06 23:14  HackerVirus  阅读(492)  评论(0编辑  收藏  举报