2011年11月26日

解决DESCryptoServiceProvider加解密时弱密钥异常

DES算法的密钥是8字节,由于其算法的特性所致,有一些密钥是不安全的,比如0xFF0xFF0xFF0xFF0xFF0xFF0xFF0xFF,
用该密钥对数据进行加密与解密运算,得到的结果是一样的,这样的密钥被称为弱密钥。在.NET中我们一般用DESCryptoServiceProvider
来进行DES运算,但是该类会去检测传入的密钥是否为弱密钥,如果是,就直接抛出异常。先看下面的代码:

 1         public static string DESEncode(byte[] str, byte[] key)
2 {
3 DESCryptoServiceProvider des = new DESCryptoServiceProvider();
4 des.Mode = CipherMode.ECB;
5 des.Padding = PaddingMode.None;
6 des.Key = key;
7 ICryptoTransform desCrypt = des.CreateEncryptor();
8 byte[] result = desCrypt.TransformFinalBlock(str, 0, str.Length);
9 return BitConverter.ToString(result).Replace("-", "");
10 }

上面的代码网上到处能找到,但是如果你传入0xFF0xFF0xFF0xFF0xFF0xFF0xFF0xFF这样的密钥,在des.Key = key;这一行就会抛出
“不能为弱密钥”的异常。这在正常情况下是没问题的,微软为我们考虑的很周到,强制我们不要用安全性低的弱密钥。但是有时需求是万变
的,比如你操作的是一个硬件设备,往它里面写入一个密钥,但是厂家规定了第一次写入时必须用8个0xFF密钥进行加密,后面就用原密钥加密后
写入(银行的金融POS机几乎都是这种模式的),那岂不是就没法用C#来操作了吗?
我之前也是这么认为的,甚至都决定用C写个DES算法(这个网上有现成的),编译成DLL后用C#去调用。后来想到了反射,去绕过弱密钥的检测。
于是我先反编译了DESCryptoServiceProvider.Key这个属性的set方法,看看里面做了什么操作:

 1 public override byte[] Key
2 {
3 set
4 {
5 if (value == null)
6 {
7 throw new ArgumentNullException("value");
8 }
9 if (!base.ValidKeySize(value.Length * 8))
10 {
11 throw new ArgumentException(Environment.GetResourceString("Cryptography_InvalidKeySize"));
12 }
13 if (IsWeakKey(value))
14 {
15 throw new CryptographicException(Environment.GetResourceString("Cryptography_InvalidKey_Weak"), "DES");
16 }
17 if (IsSemiWeakKey(value))
18 {
19 throw new CryptographicException(Environment.GetResourceString("Cryptography_InvalidKey_SemiWeak"), "DES");
20 }
21 base.KeyValue = (byte[]) value.Clone();
22 base.KeySizeValue = value.Length * 8;
23 }
24 }

Key这个属性是DESCryptoServiceProvider的父类DES类的属性。果然,我们在里面发现了检测弱密钥的代码if (IsWeakKey(value))和
if (IsSemiWeakKey(value)),后者是检测半弱密钥的。那么抛除前面的检测语句,真正有作用的只有这两句

1 base.KeyValue = (byte[]) value.Clone();
2 base.KeySizeValue = value.Length * 8;

那么,下面的事情就明朗了,用反射去对这两个属性进行赋值

 1         public static string DESEncode(byte[] str, byte[] key)
2 {
3 DESCryptoServiceProvider des = new DESCryptoServiceProvider();
4 des.Mode = CipherMode.ECB;
5 des.Padding = PaddingMode.None;
6
7 FieldInfo fi = des.GetType().GetField("KeyValue", BindingFlags.Instance | BindingFlags.NonPublic);
8 fi.SetValue(des, key);
9 fi = des.GetType().GetField("KeySizeValue", BindingFlags.Instance | BindingFlags.NonPublic);
10 fi.SetValue(des, key.Length * 8);
11
12 ICryptoTransform desCrypt = des.CreateEncryptor();
13 byte[] result = desCrypt.TransformFinalBlock(str, 0, str.Length);
14 return BitConverter.ToString(result).Replace("-", "");
15 }

这时发现des.CreateEncryptor()这句还是抛出弱密钥异常,继续反编译

1 public virtual ICryptoTransform CreateEncryptor()
2 {
3 return this.CreateEncryptor(this.Key, this.IV);
4 }
 1 public override ICryptoTransform CreateEncryptor(byte[] rgbKey, byte[] rgbIV)
2 {
3 if (DES.IsWeakKey(rgbKey))
4 {
5 throw new CryptographicException(Environment.GetResourceString("Cryptography_InvalidKey_Weak"), "DES");
6 }
7 if (DES.IsSemiWeakKey(rgbKey))
8 {
9 throw new CryptographicException(Environment.GetResourceString("Cryptography_InvalidKey_SemiWeak"), "DES");
10 }
11 return this._NewEncryptor(rgbKey, base.ModeValue, rgbIV, base.FeedbackSizeValue, CryptoAPITransformMode.Encrypt);
12 }

它里面果然又进行了一次弱密钥判断,最终调用的是_NewEncryptor这个私有方法,于是就要反射去调用它。注意参数里的rgbIV是向量,
feedbackSize不清楚是干嘛的,但是这两个参数在ECB加解密模式下是不需要的,可以随便传。CryptoAPITransformMode这个枚举是internal的,
一样要用反射去得到。最终的代码如下:

 1         public static string DESEncode(byte[] str, byte[] key)
2 {
3 DESCryptoServiceProvider des = new DESCryptoServiceProvider();
4 des.Padding = PaddingMode.None;
5
6 Type t = Type.GetType("System.Security.Cryptography.CryptoAPITransformMode");
7 object obj = t.GetField("Encrypt", BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.DeclaredOnly).GetValue(t);
8
9 MethodInfo mi = des.GetType().GetMethod("_NewEncryptor", BindingFlags.Instance | BindingFlags.NonPublic);
10 ICryptoTransform desCrypt = (ICryptoTransform)mi.Invoke(des, new object[] { key, CipherMode.ECB, null, 0, obj });
11
12 byte[] result = desCrypt.TransformFinalBlock(str, 0, str.Length);
13 return BitConverter.ToString(result).Replace("-", "");
14 }

以上是DES的加密代码,DES的解密、3DES的加解密与之类似,就不一一写了。







 

 

posted @ 2011-11-26 16:00 小小娟 阅读(1232) 评论(1) 编辑

2011年9月23日

SQL模糊查询时特殊字符的处理方式总结

当编写WHERE语句中有LIKE条件时,如果参数中需要匹配 % 和_ 等特殊字符时,必须进行处理,否则系统会将其当成通配符处理。
 
SqlServer:
有两种方案
一:将参数中的 [ 替换成 [[],  % 替换成 [%], _ 替换成 [_];(推荐用这种方案处理)
二:先将参数中的 \ 替换成 \\, [替换成\[ , % 替换成 \%, _ 替换成 \_;
然后在每个需要like查询的字段后加上escape '\'。
注:sqlserver2005测试通过
 
Oracle只能既改参数,又改SQL语句。
先将参数中的 \ 替换成 \\,  % 替换成 \%, _ 替换成 \_;
然后在每个需要like查询的字段后加上escape   chr(92   USING   NCHAR_CS),组成的SQL语句形如:
select * from A where name like '%\%%' escape chr(92 USING NCHAR_CS) or addr like '%\_%' escape chr(92 USING NCHAR_CS);
在ORACLE 9I中必须写escape chr(92 USING NCHAR_CS)这么长一段,ORACLE 10G中可以简写成escape '\'
注:oracle9i、10g测试通过
 
Mysql的情况与ORACLE类似,也需要用escape语句。但是经测试不能用escape '\',所以用escape '/'
先将参数中 / 替换成 //,% 替换成 /%,_ 替换成 /_,\ 替换成 /\;
然后在每个需要like查询的字段后加上escape '/'。
注:Mysql 5.5测试通过
 
Access不能用escape,但与SQLSERVER类似,只需将参数中的 [ 替换成 [[],  % 替换成 [%], _ 替换成 [_]即可。
注:Access2003测试通过

posted @ 2011-09-23 20:24 小小娟 阅读(315) 评论(1) 编辑

2011年8月28日

浙江电信网上营业厅的一个BUG(有更新)

  前几天看到浙江电信网上营业厅在搞签到得米粒的活动,每天可签到一次,每次获得一个米粒,首次签到可以获得21个米粒。
我玩了几天,发现了它的一个BUG。我们先看看页面,地址是http://zj.ct10000.com/qiandao
  点击后就变灰了,当天就不能再次点了,如下图所示:

先来看看页面代码吧,用开发人员工具很容易就找到下面这个JS方法

注意红框的代码,它是根据前台传过去的参数来判断是否首次登陆。那么如果我们每次都传“登陆首次积分”这个字符串过去呢?
测试一下,先要登陆网上营业厅,然后在签到页面执行如下JS代码

$j.post('/zjpr/score/qiandao/checkin.html', {'type': 'WT_SIGNIN', 'name': encodeURI('登录首次积分')}, function(data){
	alert(data);	
});

接着查询一下米粒数量看看:

果然是多了21个,说明它后台没做验证。这时如果再次执行就没效果了,因为后台做了同一天只能签到一次的验证。
那么如果在还没签到的时候,同时执行多次上面的JS代码,会怎么样呢?试验一下,执行如下代码:

for(var i=0;i<10;i++){
$j.post('/zjpr/score/qiandao/checkin.html', {'type': 'WT_SIGNIN', 'name': encodeURI('登录首次积分')}, function(data){
		
	});
}

同时提交10个请求,看看后台是如何处理并发的。查询一下

存在了多条记录,米粒数从165涨到了249。说明后台没对代码加锁(C#中的lock,JAVA中的synchronized),导致存在并发问题。

结论:首先还是那句话,前台验证看需要,后台验证不可少;其次是建议对有特殊限制的操作的相应代码加锁,可以对session中的对
象加锁,这样既限制了同一用户的并发操作,又不影响其它用户。

续:刚才又找到一个漏洞,可以直接无限刷米粒。原理是这样,在签到页面有分享链接,可以分享到人人、开心等网站,分享一个
获得一个米粒。但是后台没有对分享的名称做过滤,于是我传了一个随机数过去,也分享成功,获得了一个米粒。JS代码如下:

$j.post(basePath+'zjpr/score/qiandao/checkin.html', {"type": "WT_SHARE", "channel": Math.random(), "name": encodeURI("分享渠道积分")}, function(data) {

});

然后写个循环,你们懂得。我现在已经有1500多个米粒了

我已经把BUG报给在线客服了,能不能快速处理掉就看电信的办事效率了。

posted @ 2011-08-28 20:42 小小娟 阅读(4331) 评论(27) 编辑

2011年7月20日

某投注网站的BUG

  前两天一个博彩投注网站的站长,说是要找网站的漏洞,看不到源代码,只能通过前台测试。由于之前找过团购网站
的漏洞(请看这里),因此便谈好一个漏洞500元。可惜天下老板一般黑啊,就是不给,现在QQ也隐身了。之前找出漏
洞的团购网站(VC团)也一样,说好给报酬的也没下文了,就这点器量这点诚信,网站还能做大?手底下人能留的住?那
VC团当时和我联系的开发人员,没几个月也离职了,可见一斑哪。

  博彩投注网站一般不能随意注册,得找一个代理给你注册,当时那个站长给了一个测试账号。

  以下是投注的页面

  

  我们用IE8开发人员工具就能发现很多个hidden标签,连注释都有!该网站将需要的金额数通过javascript计算出来后放在hidden标签中,
提交给后台,但是后台却没有验证金额。这里我们将金额数修改为0.01,然后提交,投注成功!经测试,中奖后奖金确实会加到账户中!(这里
如果修改期号,还能购买前面已经开过奖的期号,但是系统不会判断你中奖)

  

  我们再进入已购买的订单的详情页面。

  这里有个撤单按钮,先看看form表单的内容吧。

  看到没?这里有个单据编号,我们将它修改成其他单据号,点击撤单按钮,提示撤单成功,之前花费的0.01元也回来的,
但是该详情页面进去,发现还能继续撤单,金额依旧会加上去,于是这样就能无限刷钱了!

  还有个BUG是关于代理充值的。每个代理可以给下线充值,将自己账户上的钱转给下线。该页面地址形如addmoney.aspx?id=15234。
将那个id换成别的id,就可以替别的代理为他的下线充值了!该网站的开发人员应该是意识到了一点安全性,整个网站是用iframe
框架页做的,禁掉了鼠标右键、选择文字等功能,主框架里的页面如果直接打开就跳转到其他页面,等等一系列处理。可惜
手握IE8开发人员工具足矣,直接修改iframe的src属性,便可以了!

  最后一个BUG是,如果禁掉了javascript以未登录状态进入该网站,便不会跳转到登录页面(因为是用JS去跳转的),
这时你的权限是代理,可以随意添加下线用户(不过账户金额都为0)。

  说了那么多,还是那句话,客户端的东西靠不住!另外附上一句:遇到没诚信的老板的程序员,你们伤不起啊!!

  附上该网站地址http://dx1.txtxzz.com/trade/

posted @ 2011-07-20 20:03 小小娟 阅读(3031) 评论(25) 编辑

2011年4月18日

ASP.NET自定义控件开发示例(二)

接着上次的内容。接下来我们让它的时间能不停走动。新建一个类TipTime3,新建脚本文件TipTime3.js,将TipTime2代码都复制过来。
为了能将C#的DateTime对象转换为JS的Date,需要将该时间距离1970-1-1午夜的毫秒数传给JS。

剩下的工作就是利用setInterval每隔1秒改变span标签的文本,略过。

下一步要实现利用AJAX获取服务端的时间。先要让控件实现ICallbackEventHandler接口。

该接口包含2个方法。 RaiseCallbackEvent用于处理客户端传递过来的参数,这里不需要,所以空着。 GetCallbackResult用于处理返回给客户端的数据,
这里为了使效果明显,返回了一个随机时间。
注意要在OnLoad方法中需要加上这一行:
Page.ClientScript.GetCallbackEventReference(this, "", "", "");
这样做是为了保证页面上生成了WebForm_DoCallback方法。
剩下的工作就是客户端去调用了。WebForm_DoCallback方法的参数说明依次为:
需要回调的控件的UniqueID
回调的参数
回调结束后客户端的响应事件
回调的上下文
回调出错后客户端的响应事件
是否异步

接着给按钮添加onclick事件,调用WebForm_DoCallback方法,在回调结束后的响应事件中将结果显示在span标签上就行了。
这时我们发现如果页面回传了,控件的位置就会回到原来的位置,因此需要在每次拖动结束后将位置存储在<input type=“hidden”/>标签中。
新建一个类TipTime4,新建脚本文件TipTime4.js,将TipTime3代码都复制过来。
新建两个属性X,Y用来存储控件在客户端的坐标,并在RenderContents方法中输出一个Input标签,保证客户端的坐标信息在回传时还存在。

注意这里对属性X,Y没有用ViewState存储,是因为这2两个值是存在于表单数据中,这个与TextBox控件的Text属性类似。
对上面的input标签一定要设置name属性,不然是不会随着表单提交的。
为了能让在回传后获取input标签的值,需要让控件实现IPostBackDataHandler接口

LoadPostData方法用来检查提交给服务器的数据,根据控件状态数据和回发数据是否发生更改而判断是否调用RaisePostDataChangedEvent方法,
如果返回true,则.NET Framework会自动调用RaisePostDataChangedEvent方法,在此方法中可以引发自己定义的事件。
这里只将回发数据存储到X,Y属性中。
接下来要在OnLoad事件中加上
Page.RegisterRequiresPostBack(this);
这是为了保证在回传时触发IPostBackDataHandler接口。
然后在AddAttributesToRender方法中设置控件的left、right属性:
 

最后在JS中拖动结束时将控件的坐标存入input标签中即可。

最后要加的功能是到了一个时间,自动回传触发自定义事件。新建一个类TipTime,新建脚本文件TipTime.js
将TipTime4代码都复制过来。先加一个属性,用来存储配置的定时时间:

 

接着定义自定义事件。这上面的写法都是固定的写法。这样在设计视图就能看到该事件了

 然后实现IPostBackEventHandler接口

在条件成立时,就执行自定义事件。
下一步在JS中判断是否到时间了,如果是则调用__doPostBack方法来实现回传,并将控件的UniqueID作为参数传递过去,
这样服务端才能知道是哪个控件触发的回传事件。
 

需要注意的是必须在OnLoad方法中加上:
Page.ClientScript.GetPostBackEventReference(new PostBackOptions(this));
这样页面上才会有__doPostBack方法,并且才会触发IPostBackEventHandler接口。
至此,所有功能都实现了。

示例代码下载
ASP.NET自定义控件开发示例(一)

posted @ 2011-04-18 15:13 小小娟 阅读(1732) 评论(0) 编辑

2011年4月15日

ASP.NET自定义控件开发示例(一)

摘要: 本文通过实现一个服务端控件来讲解一下控件开发,该控件的功能如下:1.显示服务端时间,并不停更新 2.通过手动点击刷新按钮以AJAX获取服务端最新时间 3.能拖动 4.能记住在页面上的位置,页面回传后位置不变 5.能配置一个定时时间,一到这个时间,自动回传触发用户自定义的事件首先新建一个类库项目HampWebControl,再新建一个类叫TipTime1,继承WebControl类。如果不是从已有控件中继承,一般就继承WebControl类,它是所有ASP.NET服务端控件的基类。我们编译这个项目,再新建一个网站项目,引用HampWebControl项目,新建页面,在工具箱中拖一个TipTime阅读全文

posted @ 2011-04-15 17:31 小小娟 阅读(2316) 评论(7) 编辑

2011年2月18日

Silverlight与Flash在FF中的一些注意点

摘要: 在HTML中,Silverlight、Flash以object标签的形式显示,如以下代码:View Code 但是必须同时指定该object元素的height、width的值,否则在FF下不能显示。同时如果height的值为百分比,则它的外围元素必须有高度,否则也不显示(width的值为百分比的情况也一样)如下代码在FF下不能显示:View Code 如下代码在FF下可以显示(因为div默认宽度就是整个屏幕的宽度):View Code 另一个注意点也是在FF下,当把object元素用style.display=”none”的形式隐藏掉,再用style.display=&阅读全文

posted @ 2011-02-18 17:25 小小娟 阅读(1272) 评论(2) 编辑

2011年2月16日

基于silverlight实现批量上传控件(一)

摘要: 这是最近做的一个基于silverlight的ASP.NET批量上传控件,目前在写开发文档,同步将文档内容发在博客里。开发目的HTML的file控件只能选择单个文件,不能批量选择,而且没有进度提示。原理用silverlight实现多选批量上传,利用与javascript的交互来控制显示,并将其封装成ASP.NET服务端控件。总体结构首先用户进入页面,执行批量上传控件的服务端代码,生成HTML代码(包括silverlight的html代码,这样就完成了对silverlight的初始化)。用户在silverlight上选择完文件,开始上传,这时用silverlight去访问当前页面,在地址上加上标示阅读全文

posted @ 2011-02-16 18:17 小小娟 阅读(2055) 评论(5) 编辑

Response.End导致“正在中止线程”异常的问题

摘要: 这是最近发现的一个问题,我用AJAX请求P页面,然后根据返回值来确定下一步的逻辑,结果发现Response.End语句居然引发了“正在中止线程”异常,导致影响了判断返回结果的逻辑。P页面代码类似如下结构:[代码]根据一些业务逻辑返回相应的状态字符串,如果出现异常做返回“error”,我预期它返回“状态1”,结果测试时发现AJAX回调的结果是“状态1error”,它居然抛出异常了!google后得知:Response.End 方法终止页的执行,并将此执行切换到应用程序的事件管线中的 Applicati阅读全文

posted @ 2011-02-16 15:50 小小娟 阅读(2199) 评论(13) 编辑

2011年2月12日

URL地址中的#符号

摘要: 一般我们想让一个a标签点击后执行javascript代码,有以下几种写法:方式一:<a href="#" onclick="alert(1);">点击一</a>这种方式的缺点就是点击后会在地址栏的URL后面加#号,同时把页面移动到顶部,如下图所示:方式二:<a href="javascript:void(0);" onclick="alert(1);">点击一</a>这种方式避免了方式一的缺点,点击后对页面没有任何影响。但是有一个致命的缺点,就是在IE6下不能执行form对象的submit()方法,也不能执行跳转语句,比如<a href="阅读全文

posted @ 2011-02-12 16:08 小小娟 阅读(2705) 评论(17) 编辑