开发者社区> 余二五> 正文

用ISAPI Filter设置HttpOnly属性

简介:
+关注继续查看

说到ISAPI很多人会觉得很陌生,因为如果你是做ASP.NET开发的话,ISAPI的方式已经过时,取而代之的是HttpHandler和HttpModule,说到这两个东西很多人估计明白了,ISAPI可以说是早期实现请求拦截和处理的唯一途径,只是随着ASP.NET的流行,渐渐淡出了开发人员的视野。

 

此文的开发场景是这样的,我们公司使用古老的ASP语言,但是ASP的Response.Cookies属性中没有HttpOnly(但.Net的Cookie对象是有HttpOnly属性的),有帖子说可以利用Path属性来设置HttpOnly,可以这么做是因为我们在页面中设置cookie值的动作都会被转换成Set-Cookie头,如下

Set-Cookie: user=t=bfabf0b1c1133a822; path=/
但如果要让cookie变成HttpOnly,就需要用如下格式:
Set-Cookie: user=t=bfabf0b1c1133a822; path=/;HttpOnly

理论上讲设置Path是完全可行的,因为说白了就是在原来Path的基础上增加;HttpOnly,

但经试验表明这行不通,比如我用下面的代码

Response.Cookies(“user”).Path+=”;HttpOnly”;

得到的结果却是

Set-Cookie: user=t=bfabf0b1c1133a822; path=/3B%;HttpOnly

这显然是不行的,所以我们不得不考虑用ISAPI来实现。

 

ISAPI基础

首先,请不要把ISAPI Extension和ISAPI Filter混为一谈,这两个东西虽然只差一个字,但却完全是两样东西,所提供的接口是完全不一样的。ISAPI Extension是一个类似页面的dll,你可以对它做post或get提交,如http://localhost/abc.dll?a=1,从严格意义上讲它没有拦截的功能,和cgi差不多。而ISAPI Filter则是具有过滤功能的,你可以在IIS网站的属性中添加需要加载的ISAPI Filter,例如asp.net的实现也使用了一个ISAPI Filter,叫做aspnet_filter.dll。

ISAPI Filter说到底就是一个DLL,它有两个主要的接口:GetFilterVersion和HttpFilterProc,如下所示:

BOOL WINAPI __stdcall GetFilterVersion(HTTP_FILTER_VERSION *pVer) 
{ 
 /* Specify the types and order of notification */ 
 
 pVer->dwFlags = (SF_NOTIFY_PREPROC_HEADERS | SF_NOTIFY_AUTHENTICATION | 
 SF_NOTIFY_URL_MAP | SF_NOTIFY_SEND_RAW_DATA | SF_NOTIFY_LOG | SF_NOTIFY_END_OF_NET_SESSION ); 
 
 pVer->dwFilterVersion = HTTP_FILTER_REVISION; 
 
 strcpy(pVer->lpszFilterDesc, "Upper case conversion filter, Version 1.0"); 
 
 CFile myFile("c:\\mylist.html", CFile::modeCreate | CFile::modeWrite);
 myFile.SeekToEnd();
 char myText[40];
 strcpy(myText,"<B>GetFilterVersion </B><BR><BR>");
 myFile.Write(myText,strlen(myText));
 myFile.Close();
 
 return TRUE; 
}

GetFilterVersion不仅仅是用来获得Filter版本这么简单,它可以用来过滤需要触发的事件,这些事件的详细信息你可以参考http://msdn.microsoft.com/en-us/library/ms825957.aspx。请注意,这里做的是或操作,而不是与操作,学过数理逻辑的应该明白这个是干嘛用的,就是值的叠加,说的再直接点,EventA|EventB就是我既要Event A也要Event B。

DWORD WINAPI __stdcall HttpFilterProc(HTTP_FILTER_CONTEXT *pfc, DWORD NotificationType, VOID *pvData) 
{ 
 CFile myFile("c:\\mylist.html", CFile::modeWrite);
 myFile.SeekToEnd();
 
 switch (NotificationType) { 
 
 case SF_NOTIFY_ACCESS_DENIED : 
 
 myFile.Write("SF_NOTIFY_ACCESS_DENIED<BR>",strlen("SF_NOTIFY_ACCESS_DENIED<BR>"));
 break;
 
 case SF_NOTIFY_AUTH_COMPLETE :
 
 myFile.Write("SF_NOTIFY_AUTH_COMPLETE<BR>",strlen("SF_NOTIFY_AUTH_COMPLETE<BR>"));
 break;
 
 case SF_NOTIFY_AUTHENTICATION :
 
 myFile.Write("SF_NOTIFY_AUTHENTICATION<BR>",strlen("SF_NOTIFY_AUTHENTICATION<BR>"));
 break;
 
 case SF_NOTIFY_END_OF_NET_SESSION : 
 
 myFile.Write("SF_NOTIFY_END_OF_NET_SESSION<BR>",strlen("SF_NOTIFY_END_OF_NET_SESSION<BR>"));
 break;
 
 case SF_NOTIFY_END_OF_REQUEST : 
 
 myFile.Write("SF_NOTIFY_END_OF_REQUEST<BR>",strlen("SF_NOTIFY_END_OF_REQUEST<BR>"));
 break;
 
 case SF_NOTIFY_LOG : 
 
 myFile.Write("SF_NOTIFY_LOG<BR>",strlen("SF_NOTIFY_LOG<BR>"));
 break;
 
 case SF_NOTIFY_PREPROC_HEADERS : 
 
 myFile.Write("SF_NOTIFY_PREPROC_HEADERS<BR>",strlen("SF_NOTIFY_PREPROC_HEADERS<BR>"));
 break;
 
 case SF_NOTIFY_READ_RAW_DATA : 
 
 myFile.Write("SF_NOTIFY_READ_RAW_DATA<BR>",strlen("SF_NOTIFY_READ_RAW_DATA<BR>"));
 break;
 
 case SF_NOTIFY_SEND_RAW_DATA :
 
 myFile.Write("SF_NOTIFY_SEND_RAW_DATA<BR>",strlen("SF_NOTIFY_SEND_RAW_DATA<BR>"));
 break;
 
 case SF_NOTIFY_SEND_RESPONSE : 
 
 myFile.Write("SF_NOTIFY_SEND_RESPONSE<BR>",strlen("SF_NOTIFY_SEND_RESPONSE<BR>"));
 break;
 
 case SF_NOTIFY_URL_MAP : 
 
 myFile.Write("SF_NOTIFY_URL_MAP<BR>",strlen("SF_NOTIFY_URL_MAP<BR>"));
 break;
 
 case SF_NOTIFY_SECURE_PORT : 
 
 myFile.Write("SF_NOTIFY_SECURE_PORT<BR>",strlen("SF_NOTIFY_SECURE_PORT<BR>"));
 break;
 
 case SF_NOTIFY_NONSECURE_PORT :
 
 myFile.Write("SF_NOTIFY_NONSECURE_PORT<BR>",strlen("SF_NOTIFY_NONSECURE_PORT<BR>"));
 break;
 
 default : 
 break; 
 } 
 
 
 myFile.Close();
 
 return SF_STATUS_REQ_NEXT_NOTIFICATION; 
}

HttpFilterProc是主要入口,相当于Console程序中的main。上面这段代码是在这些事件触发时写入一个日志,这样便于调试。

说到这里我们来了解下通常开发一个ISAPI Filter的流程。

a. 获得一个现有的ISAPI Filter项目,当做模板,这个网上很多,google一下就有了。

b. 修改GetFilterVersion中的dwFlags的值来决定需要哪些事件

c. 修改HttpFilterProc中的case分支,删除不需要的事件

d. 在需要处理的事件中写代码。

 

有一件事必须提醒大家,在写ISAPI时,你千万不要忘了把这两个接口暴露出去,也就是定义DLL的EXPORTS,如下:

LIBRARY "isapi_sample"
EXPORTS
HttpFilterProc
GetFilterVersion

 

事件的执行顺序

在ASP.NET中我们有Page Life Cycle,ISAPI Filter也是如此,这些时间的执行顺序可以在 http://msdn.microsoft.com/en-us/library/ms524855(VS.90).aspx 上找到,下面的事件就是按执行顺序排列的。

SF_NOTIFY_READ_RAW_DATA

SF_NOTIFY_PREPROC_HEADERS

SF_NOTIFY_URL_MAP 

SF_NOTIFY_AUTHENTICATION

SF_NOTIFY_AUTH_COMPLETE

SF_NOTIFY_SEND_RESPONSE

SF_NOTIFY_SEND_RAW_DATA

SF_NOTIFY_END_OF_REQUEST

SF_NOTIFY_LOG

SF_NOTIFY_END_OF_NET_SESSION

通过分析,我们知道要想获得Set-Cookie header必须在ASP把页面处理完之后,因为ASP页面代码有可能会设置Cookie值,所以SF_NOTIFY_PREPROC_HEADERS事件并不合适,因为它是在收到请求后,处理页面前触发的,我们需要的是在页面处理完,发送前触发的事件,所以SF_NOTIFY_SEND_RESPONSE最合适。在下一节我们将讲解如何在该事件中添加处理代码。

 

如何遍历Set-Cookie

HttpFilterProc函数的第三个参数VOID *pvData是对应事件的数据,为了获得header里面的数据,我们会把它转换成PHTTP_FILTER_PREPROC_HEADERS,因为我们先要把Set-cookie的数据读出来,然后才能处理。

代码如下:

 1: case SF_NOTIFY_SEND_RESPONSE : 
 2: pPH = (PHTTP_FILTER_PREPROC_HEADERS)pvData;
 3: pPH->GetHeader(pfc, "Set-Cookie:", szBuffer, &dwSize);
 4:  
 5: cookieNum=sizeof(strtok(szBuffer,","));
 6: if(cookieNum>0)
 7: {
 8: //handle the cookies that are read from header
 9: ...
 10: }

这里的szBuffer就是我们获得的Set-Cookie的字符串,这里要讲一下Set-Cookie到底是啥,因为很多程序员对Set-Cookie的含义和表示形式不是特别了解。

每次我们在页面中设置Cookie值,无论是ASP还是ASP.NET,都会把设置的操作转换为Set-Cookie中的一段字符串,如果你使用Fiddler或者HttpFox跟踪这些请求的话,你会发现头里面有一项就是Set-Cookie项,这项仅在有设置Cookie的操作时才会有。另外,Set-Cookie中的每一个Cookie字符串使用逗号分隔开的,如下

Set-Cookie: test1=a; path=/, test2=b; path=/

这里设置了名为test1和test2的两个cookie值,单个cookie的属性之间使用分号分隔的。也正是因为如此,这段代码中使用strtok来获得字符串中每一段用逗号分隔的cookie字符串,这里的cookieNum表示Set-cookie中cookie字符串的总数(注意,不是字符的总数)。

一旦我们获得了每一个cookie的字符串,我们就可以把;HttpOnly附加到这些字符串的最后,并最终把字符串拼起来组成Set-Cookie字符串,关于如何做字符串拼接本文就不多讲了,这完全是C++实现的问题。

 

如何覆盖Set-Cookie字符串

这里的设置cookie和我们平时在代码里做的可不太一样,因为我们要直接修改请求中的Set-Cookie,之所以是修改而不是增加新的Set-Cookie,是因为Set-Cookie在请求的header中只能有一个,

HTTP_FILTER_SEND_RESPONSE * pResponse=(HTTP_FILTER_SEND_RESPONSE *)pvData;
BOOL fServer = TRUE;
fServer = pResponse->SetHeader(pfc, "Set-Cookie:",szHeader);

上面的代码把pvData转换成HTTP_FILTER_SEND_RESPONSE类型,这样我们就可以对Response进行操作,并通过调用它的SetHeader方法来设置Set-Cookie header。

 

完整代码下载:isapi_sample.zip (VC6项目),最主要的是MyISAPI.cpp和MyISAPI.def文件,其他都是工程文件。










本文转自 瞿杰 51CTO博客,原文链接:http://blog.51cto.com/tonyqus/1305400,如需转载请自行联系原作者

版权声明:本文内容由阿里云实名注册用户自发贡献,版权归原作者所有,阿里云开发者社区不拥有其著作权,亦不承担相应法律责任。具体规则请查看《阿里云开发者社区用户服务协议》和《阿里云开发者社区知识产权保护指引》。如果您发现本社区中有涉嫌抄袭的内容,填写侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。

相关文章
iOS逆向 01:初识汇编
iOS逆向 01:初识汇编
5 0
干货 | 2021 年如何一步一步的学习 Python
干货 | 2021 年如何一步一步的学习 Python
6 0
VIM 简单使用1
Vim是从 vi 发展出来的一个文本编辑器。代码补完、编译及错误跳转等方便编程的功能特别丰富,在程序员中被广泛使用。 简单的来说, vi 是老式的字处理器,不过功能已经很齐全了,但是还是有可以进步的地方。 vim 则可以说是程序开发者的一项很好用的工具。 连 vim 的官方网站 (http://www.vim.org) 自己也说 vim 是一个程序开发工具而不是文字处理软件。
5 0
Go 函数
函数定义 Go 语言函数定义格式如下: func function_name( [parameter list] ) [return_types] { 函数体 } 以下实例为 max() 函数的代码,该函数传入两个整型参数 num1 和 num2,并返回这两个参数的最大值:
7 0
快将你的 React 应用迁移到 Vite 吧,速度太快啦
我们大多数人将使用 Create React App 来创建 React App。 它支持所有开箱即用的配置。 但是,当您的项目代码增长时,您可能会面临更高的构建时间、开发服务器的启动速度变慢并等待 2 到 5 秒以反映您在代码中所做的更改,并且当应用程序大规模增长时,这可能会迅速增加。
10 0
人人都能做游戏系列教程2(视频+图文版)
这是“人人都能做游戏”系列视频教程的第2节。这一节我讲会先带大家了解一下一个小游戏的全貌,然后会介绍一些游戏开发领域的“术语”。最后,会分享一些个人的经验和心得,希望能够对大家有所帮助。
5 0
Swift-进阶 10:可选类型Optional & Equatable+Comparable协议
Swift-进阶 10:可选类型Optional & Equatable+Comparable协议
7 0
SpringMVC面试题大总结
Spring MVC是一个基于Java的实现了MVC设计模式的请求驱动类型的轻量级Web框架,通过把Model,View,Controller分离,将web层进行职责解耦,把复杂的web应用分成逻辑清晰的几部分,简化开发,减少出错,方便组内开发人员之间的配合。
9 0
【技术创作101训练营】Web 前端发展历程
【技术创作101训练营】Web 前端发展历程
4 0
万字长文Python面试题,年后找工作就靠这了(一)
废话不多说,年后找工作,就靠这些啦!
7 0
+关注
20381
文章
0
问答
文章排行榜
最热
最新
相关电子书
更多
OceanBase 入门到实战教程
立即下载
阿里云图数据库GDB,加速开启“图智”未来.ppt
立即下载
实时数仓Hologres技术实战一本通2.0版(下)
立即下载