第九章 PHP基础
在第二章介绍了 PHP 环境下的各种黑客技术,相信大家已经体验了它的乐趣。而本章开始我们就来系统性的学习 PHP 的各种知识,并在下一章讲授 PHP 代码中的各种漏洞及利用过程。虽然本章的内容有点枯燥,但是大家如果认真学习了,那么在后面将可以体验到很大成就感哦。如果三心二意的学习态度,那后面可能就看不懂了,所以大家赶快打起精神跟着我走吧,哈哈!
PHP快速入门
什么是PHP
PHP 是一种服务器端脚本语言,它是专门为 Web 而设计的。在一个 HTML 页面中,你可以嵌入 PHP 代码,这些代码在每次页面被访问时被执行。PHP 代码将在 Web 服务器中被解释并且生成 HTML 或访问者看到的其他输出。
PHP 是 1994 年出现的,最初是由 Rasmus Lerdorf 一个人的工作。之后其他一些天才改进了这种语言,PHP 的主页是 http://www.php.net。目前非常多的网站使用 PHP,如图 9-1显示了到 2006 年 11 月为止使用 PHP 的网站。最新的数据大家可以去 http://www.php.net/ usage.php 上查看。
图 9-1 使用 PHP 的网站数目
PHP 是一个开放源代码的产品,这就意味着,你可以访问他的源代码,也可以免费使用、修改并且再次发布。PHP 最初只是 Personal Home Page(个人主页)的缩写,但是后来经过修改,采用了 GNU 命名惯例(GNU=Gnu’Not Unix)。如今它是 PHP 超文本预处理程序的缩写。目前,它的最新版本是 5.2.1,这个版本的 Zend 引擎经过了完全的重写,而且还实现了一些主要的语言改进。
PHP 的性能非常高,我们使用一太单独的廉价的服务器就可以满足每天几百万的点击。
PHP 支持多种数据库,都提供了各种数据库的内置连接,不过用的最多还是 MySQL 数据库, PHP+MySQL 堪称是最好的组合,而且 MySQL 也是开源的。因为 PHP 是专门为 Web 开发而设计的,所以它提供了许多内置函数来执行有用的 Web 任务。如可以生成一个 GIF 图像、连接到
Web 服务和其他网络服务、解析 XML、发送电子邮件以及生成 PDF 文档,所有的这些任务只需要几条代码就可以搞定了。同时 PHP 还具有设计良好的面向对象特性和可移植性,可以在许多不同的操作系统中使用。
在HTML中嵌入PHP
任何服务器端脚本语言最常见的应用之一就是处理 HTML 表单,在前面学习 ASP 的时候大家已经知道了。下面是我写的一个简单的订单表单,从而开始了我们的 PHP 学习了。代码如下:
<html>
<body>
<form action="9-1.php" method="post">
<table border="0">
<tr bgcolor="#cccccc">
<td width="150">商品</td>
<td width="15">数目</td>
</tr>
<tr>
<td>显示器</td>
<td align="center"><input type="text" name="display" size="3" maxlength="3"></td>
</tr>
<tr>
<td>主机</td>
<td align="center"><input type="text" name="mainframe" size="3" maxlength="3"></td>
</tr>
<tr>
<td>键盘</td>
<td align="center"><input type="text" name="keyboard" size="3" maxlength="3"></td>
</tr>
<tr>
<input type="submit" value="提交">
</tr>
</table>
</form>
</body>
</html>
该代码的功能是提交需要购买的商品的数目,这个表单的给设置为能够处理客户订单的 PHP脚本名称,也就是 9-1.php 文件,运行后的结果如图 9-2 所示。
图 9-2 订单表单运行的结果
前面学习了 HTML 就知道,ACTION 属性值就是用户点击提交按钮时需要载入的 URL。我们可以在表单中输入的数据将按照 METHOD 属性中指定的方法发送到这个 URL,这个方法可以是 GET 或 POST。此外还要注意的是,表单域的名称——display、mainframe 和keyboard。而在 9-1.php 脚本中,我们还将使用这个名称。
要处理上面的这个表单,我们就需要创建一个 FROM 标记的 ACTION 属性中指定的脚本,这个脚本为 9-1.php,下面就是该脚本的代码:
<html>
<head>
<title>表单处理文件</title>
</head>
<body>
<h1>销售计算机</h1>
<h2>价格公道</h2>
</body>
</html>
大家应该也看到了,上面的代码都是 HTML。现在,我们可以开始在这些脚本中添加一些简单的 PHP 代码。我们在上面代码中的<h2>标记处,添加如下代码:
<?php
echo '<p>这是 PHP 输出的信息哦!</p>';
?>
保存该文件并打开表单处理文件,填写表单之后,点击“提交”按钮。那么就看到如图 9-3这样的输出结果了。
图 9-3 传递给 PHP echo 语句中的文本显示在浏览器中
和 ASP 一样,在客户端我们也看不到原始的 PHP 语句,这是因为 PHP 解释器已经运行了该脚本,下面就是上面例子中运行 PHP echo 语句后在客户端的代码:
<html>
<head>
<title>表单处理文件</title>
</head>
<body>
<h1>销售计算机</h1>
<h2>价格公道</h2>
<p>这是 PHP 输出的信息哦!</p>
</body>
</html>
上面就是简单给大家介绍了一下 PHP 脚本在Web 服务器上被解释和执行,和我们前面所学的 ASP 是大同小异。学习了前面的知识大家应该要知道,一个 PHP 文件中是由下面五个部分组成的:
HTML
PHP 标记
PHP 语句
空格
注释
其中注释为可选,不一定需要。 (1)、使用 PHP 标记
上面的例子中的 PHP 代码是以“<?php”为开始,“?>”为结束的。这类似于我们所使用的 HTML 标记,因为他们都是以小于号(<)为开始,大于(>)为结束的。这些符号(<?php 和?>)叫做 PHP 标记,他可以告诉 Web 服务器 PHP 代码的开始和结束。这和 ASP 中的<%和%>功能是一样的。在这两个标记之间的任何文本都会被解释成为 PHP。而次标记之外的任何文本都会被认为是常规的 HTML。PHP 标记可以隔离 PHP 代码和 HTML。
(2)、PHP 的标记风格
PHP 标记有四种不同的风格,这里大家要熟悉一下,是分析代码所必须掌握的常识问
题。
XML 风格
<?php echo '需要输出的内容'; ?>
这是 PHP 编程中最为常用的标记风格,它是 PHP 所推荐使用的标记风格。服务器管理员不能禁止这种风格标记,因此可以保证所有的服务器上使用这种风格都没有问题。特别是在编写不同服务器环境的应用程序时,这种风格是非常重要。还有这种风格的标记还可以在 XML(可扩展置标记语言)文档中使用。
简短风格
<? echo '需要输出的内容'; ?>
这种标记风格最为简单, 要使用这种标记风格必须在配置文件(php.ini) 中启用
short_open_tag 选项。虽然这种标记风格默认是启用的,但是因为它会干扰 XML 文档的声明,所以在一些情况下管理员会禁止该风格的使用。
SCRIPT 风格
<SCRIPT LANGUAGE= 'php'> echo '需要输出的内容';</SCRIPT>
前面我们已经学了 Javascript 和VBScript 了,对这种声明方式应该很熟悉了。这种声明方式最长,一般很少用。
ASP 风格
<% echo '需要输出的内容'; %>
这种标记风格和 ASP 的声明是一样的,如果在配置设定中启用了 asp_tags 选项,那么就可以使用它。默认的情况下这种风格是禁止的。
(3)PHP 语句
通过将 PHP 语句放置在 PHP 的开始和结束标记之间,那么它就告诉了 PHP 解释器进行何种操作。在上面的例子中,我们只使用了一种类型的语句:
echo '<p>这是 PHP 输出的信息哦!</p>';
正如大家猜的那样,使用 echo 语句具有一个非常简单的结果。它将引号内的字符串回显到浏览器中。在图 9-3 中可以看见该语句的结果,也就是“这是 PHP 输出的信息哦!”文本出现在浏览器窗口中。需要注意的是,在 echo 语句的结束处出现一个分号。在 PHP 中,分号是用来分隔语句的,就像英语中的逗号用来分隔句子一样。如果以前学过了 C 或 JAVA 的话,那对这种分号的使用应该比较习惯了。
间隔字符,例如换行(回车)、空格和 Tab(制表符),都可以被认为是空格。在前面学习 HTML 的时候就已经告诉了大家浏览器将回忽略 HTML 的空格字符。PHP 引擎同样会忽略这些空格字符。
注释其实相当于代码的解释和说明。在前面我们也已经多次用到了解释,在代码分析中,解释是非常重要的。在 PHP 中如果是多行解释,那么应该以/*为开始,*/为结束;如果是单行注释那么可以使用//和#两种注释符,//在前面已经多次使用了,大家应该非常熟悉了。
访问表单变量
前面我们使用表单的目的就是为了收集客户的订单。在 PHP 中,获得客户输入的具体数据非常的简单,但是具体的方法还依赖于所使用的 PHP 版本,以及 php.ini 文件的设置。
(1)、表单变量
在 PHP 脚本中,可以以 PHP 变量的形式访问每一个表单域,其中 PHP 变量名称必须与表单域的名称一致。大家可以很容易的识别 PHP 的变量名称,因为它们都是以$符号开始的。根据 PHP 版本和设置的不同,通过变量,可以有三种方法来访问表单数据。这三种方法
分别为简短风格、中等风格和冗长风格。在任何情况下,一个页面上提交给 PHP 脚本的每一个表单域在 PHP 脚本中都是可以使用的。比如你可以用以下的方法访问表单域 display 中的内容:
$display // 简 短 风 格
$_POST['display'] // 中 等 风 格
$HTTP_POST_VARS['display'] // 冗 长 风 格
简短风格($display)使用非常方便,但是需要将 register_globals 配置选项设置为on。在默认的情况下,该选项的默认设定值与 PHP 的版本相关。在 4.2.0 版本以后的所有版本中,这个配置默认值为 off。中等风格($_POST['display'])是现在用的最多的是中等风格来引用表单变量,但是它支持 4.1.0 以上版本。变量就放在中括号([])中并用引号引起来,我们称之为数组。冗长风格($HTTP_POST_VARS['display'])是最详细的,但是如今已过时。
当使用简短风格时,脚本中的变量名称应该与 HTML 表单中的表单域名称相同。如果使用这种风格,就可以使用类似于$display 的变量,表单中的 display 域将在表单处理脚本中创建$display 变量。对变量如此方便的访问是非常受欢迎的,但是为什么现在 PHP 开发小组将该选项设置为 off 呢?这是因为这种方法可能会使读者遇到破坏脚本的安全性。由于表单变量会自动转换成全局变量,因此读者所创建的变量与直接来自用户的不可信任变量之间没有明显区别,所以会出现一些安全问题。
中等风格涉及了$_POST、$_GET 或$_REQUEST 这三种数组。$_POST 或$_GET 都可以保存表单变量的细节,使用哪一种取决于提交表单时所使用的方法是 POST 还是 GET。此外,通过 POST 或 GET 方法提交的所有数据都可以通过$_REQUEST 来获得。
如果表单是通过 POST 方法提交的, display 文本输入框中的数据将保存在
$_POST['display']中。如果表单是通过 GET 方法提交的,数据将保存在$_GET['display']中。在任何一种情况下,数据都可以通过$_REQUEST['display']来获得。
$_POST、$_GET 和$_REQUEST 这三个数组被称为超级全局变量。在后面给大家介绍变量的作用域时,会详细介绍超级全局变量。
可以使用赋值操作符将一个变量值复制给另一个变量,在 PHP 中,赋值操作符是等于号 (=)。如下代码创建一个名为$domen 的新变量,并且将$_POST['display']的内容复制给这个新变量:$domen = $_POST['display']。
学习了上面的知识后,我们就可以对 9-1.php 进行改进了。我们可以利用表单变量来访问表单中的数据,通过$_POST 数组就可以获得客户端中输入的数据,下面就是利用$_POST来获得客户端中输入的数据,而且大家可以看到服务端的变量和客户端表单中的变量是一样
的,只是多一个$符号,代码如下:
<?php
//获得客户端表单中的数据
$display = $_POST['display']; //获得显示器的值
$mainframe = $_POST['mainframe']; //获得主机的值
$keyboard = $_POST['keyboard']; //获得键盘的值
这段代码将创建三个新变量:$display、$mainframe 和$keyboard,并且通过 POST 方法从表单中传递过来的数据分别赋值给这三个变量。我们还可以把获得的数据输出,用以下代码就可以完成这个功能了:
echo '<p><h1>下面就是客户端输入的数据</h1></p>'; echo $display.'台显示器<br>';
echo $mainframe.'台主机<br>'; echo $keyboard.'个键盘<br>';
改进后的 9-1.php 的完整代码如下所示:
<html>
<head>
<title>表单处理文件</title>
</head>
<body>
<?php
$display = $_POST['display'];
$mainframe = $_POST['mainframe'];
$keyboard = $_POST['keyboard'];
echo '<p><h1>下面就是客户端输入的数据</h1></p>'; echo $display.'台显示器<br>';
echo $mainframe.'台主机<br>'; echo $keyboard.'个键盘<br>';
?>
</body>
</html>
我打开客户端文件后,在里面分别输入 3、2、1 之后,点击“提交”按钮后,就得到了如图 9-4 所示的结果了。
图 9-4 获得客户端输入并在浏览器输出其输入的数据
(2)、字符串连接
在上面的例子中,我们使用了 echo 语句来输出我们输入的每一个表单域的值,而这些值是跟随在一段说明性文本之后的。如果仔细查看 echo 语句,大家会发现在变量名称和后
面的文本之间存在一个点号(.),例如:echo $display.'台显示器<br>';
这个点号就是字符串的连接符,它可以将几段文本连接成一个字符串。通常,使用 echo命令向浏览器发送输出时,将使用这个连接符。这样可以避免编写多个 echo 命令。
对于任何非数组变量,也可以将变量写入到一个由双引号引用起来的字符串中,如 echo
$display.'台显示器<br>';还可以写成 echo "$display 台显示器<br>";。他们的功能是一样的。在双引号中,变量名称将被变量值所替代;而在单引号中,变量名称或者任何其他的文本都会不经修改而发送给客户端浏览器。
变量
标识符是变量的名称,当然函数和类的名称也是属于标识符。PHP 的特性之一就是它不要求在使用变量之前声明变量,当你第一次给一个变量赋值时,你才真正创建了这个变量。变量都具有类型,叫做变量类型,它是指保存在该变量中的数据类型。PHP 支持Integer(整数)、Float(浮点数)、String(字符串)、Boolean(布尔值)、Array(数组)及 Object(对象)等数据类型。
如果是一个变量,那么它的值我们是可以改变的。而如果声明了一个常量,那么它的值一旦被设定后,在脚本的其他地方就不能更改了。我们可以使用 define 函数定义常量,例如:define('DISPLAY',500);。创建了变量之后我们可以这样输出:echo DISPLAY。为了区别变量和常量的不同一个重要的的特点是引用一个变量的时候,它前面并没有$符号,还有就是常量名都是由大写字母组成。
(1)、变量的作用域
作用域是指在一个脚本中某个变量在哪些地方可以使用或可见。PHP 具有六项基本的作用域:
内置超级全局变量可以在脚本的任何地方使用和可见。
常量,一旦被声明,将可以在全局可见;也就是说,它们可以在函数内外使用。
在一个脚本中声明的全局变量在整个脚本中是可见的,但不是在函数内部。
函数内部使用的变量声明为全局变量时,其名称要与全局变量名称一致。
在函数内部创建并声明为静态的变量无法在函数外部可见,但是可以在函数的多次执行过程中保持该值。
在函数内部创建的变量对函数来说是本地,而当函数终止时,该变量就不存在了。
在 PHP 4.1 以后的版本中,$_POST 和$_GET 数组以及一些其他特殊变量都具有各自的作用域规则。这些被称为超级全局变量,它们可以在任何地方使用和可见,包括内部和外部函数。超级全局变量的完整列表如下所示:
$GLOBALS,所有全局函数变量数组(就像 global 关键字,这将允许在一个函数内部访问全局变量——例如,以$GLOBALS['myvariable']的形式。)
$_SERVER,服务器环境变量数组
$_GET,通过GET 方法传递给该脚本的变量数组
$_POST,通过 POST 方法传递给该脚本的变量数组
$_COOKIE,cookie 变量数组
$_FILES,与文件上传相关的变量数组
$_ENV,环境变量数组
$_REQUEST,所有用户输入的变量数组,包括$_GET、$_POST 和$_COOKIE 所包含的输入内容
$_SESSION,会话变量数组
文件
在上一节中我给大家演示了一个订单的例子。对于商家来说,用户提交了订单了之后,就是应该按照订单的数据进行送货了。而订单中的数据是肯定要被存储起来的,而且这个数据是非常的重要。
在 PHP 中,存储数据有两种基本方法:保存到普通文件,或者保存到数据库中。普通文件可以具有多种格式,但是,通常所指的普通文件是简单的文本文件。而本节的重点就是给大家讲解如果将数据存储到文件中,这部分大家一定要掌握,因为它和我们的黑客技术有紧密的联系。要将数据存储在文件中就必然涉及文件的处理。
9.2.1 文件处理
将数据写入一个文件,有三步操作:
打开这个文件。如果文件不存,需要先创建它
将数据写入这个文件
关闭这个文件
同样,从一个文件中读出数据,也有三步操作:
打开这个文件,如果这个文件不能打开(例如,文件不存在),就应该意识到这一点并且正确地退出
从文件中读出数据
关闭这个文件
当希望从一个文件中读出数据时,可以选择一次从文件中读取多少数据,这些在后面都会给大家讲解。
打开文件
要在 PHP 中打开一个文件,可以使用 fopen()函数。当打开一个文件的时候,还需要确定如何使用它。也就是,文件模式。
(1)、文件模式
服务器上的操作系统必须知道要对打开的文件进行什么操作。操作系统还要了解在打开这个文件后,这个文件是否还允许其他的脚本打开,它还需要了解当前使用者是否具有这样操作的文件的权限。从本质上来说,文件模式可以告诉操作系统一种机制,这种机制可以决定如何处理来自其他人或脚本的访问请求,以及一种用来检查你是否有权访问这个特定文件的方法。当打开一个文件的时候,有三种选择:
打开文件为了只读、只写或者读和写
如果要写一个文件,你可以希望覆盖所有已有的文件内容,或者仅仅将数据追加到文件的末尾。如果该文件已经存在,也可以终止程序的执行而不是覆盖该文件。
如果希望在一个区分了二进制方式和纯文本方法的系统上写一个文件,还必须执行采用的方式。
函数 fopen()支持以上三种方式的组合。 (2)、使用 fopen()打开文件
假设要我们要将数据写入 php.txt 文件中,可以使用如下所示的语句打开这个文件:
$fp = fopen("$DOCUMENT_ROOT/../php/php.txt", 'w');
调用 fopen()的时候,可以传递 2 个、3 个或 4 个参数。通常我们使用 2 个就够了,如上面的代码所示。
第一个参数是要打开的文件,如上面的代码所示,我们可以指定该文件的路径。在这里,php.txt 文件保存在php 目录中。我们使用了 PHP 内置变量$_SERVER['DOCMUENT_ROOT'],不过这里我们这个变量有点长,所以我们用$DOCMUENT_ROOT 来代替它,不过要记得在脚本的开始处加上:$DOCMUENT_ROOT = $_SERVER['DOCMUENT_ROOT']这条代码。这个变量指向了
Web 服务器的根目录,而“..”表示文档的根目录的父目录。
第二个参数是文件模式,它是一个字符串,指定了对文件进行的操作。在这个例子中,我们将“w”传给了 fopen()——这就意味着文件要以写的方式打开这个文件。表 9-1 给出了所有的文件模式和意义。
模 式 模 式 名 称 意 义
r 只读 读模式——打开文件,从文件头开始读 r+ 只读 读写模式——打开文件,从文件头开始读写
只写 写模式——打开文件,从文件头开始读。如果该文件已经存在,将删除所有文件已有的内容。如果该文件不存在,函数将创建这个文件
w+ 只写 写模式——打开文件,从文件头开始读写。如果该文件已经存在,将删除所有文件已有的内容。如果该文件不存在,函数将创建这个文件
谨慎写 写模式打开文件,从文件头开始写。如果文件已经存在,该文件将不会被打开,fopen()函数将返回 flase,而且 PHP 将产生一个警告
x+ 谨慎写 读/写模式打开文件,从文件头开始写。如果文件已经存在,该文件将不会被打开,fopen()函数将返回 flase,而且PHP 将产生一个警告
追加 追加模式——打开文件,如果该文件已有内容,将从文件末尾开始追加(写),如果该文件不存在,函数将创建这个文件
a+ 追加 追加模式——打开文件,如果该文件已有内容,将从文件末尾开始追加(写)或者读,如果该文件不存在,函数将创建这个文件
二进制 二进制模式——用于与其他模式进行连接。如果文件系统能够区分二进制文件和文本文件,那么就可以使用它。Windows 系统可以区分;而
unix 不区分。使用这个选项可以获得最大的移植性。它是默认的模式表 9-1 fopen()函数的文件模式总结
在我们实际的例子和系统中所使用的文件模式取决于如何使用这个系统,在我们分析代码的时候也要特别这里,因为这里也是安全问题出现的比较多的地方。如果 fopen()函数调用失败,函数将返回 false。可以以一种对于用户友好的方式来处理这个错误,在 fopen 函数前面加上一个@符号就可以告诉 PHP 抑制所有由该函数调用所产生的错误,示例代码如下:
$fp = @fopen("$DOCUMENT_ROOT/../php/php.txt", 'a');
写文件
在 PHP 中写文件比较的简单,可以使用 fwrite()或者 fputs()。fputs()是 fwrite()的别名函数。我们可以用这样的方式来调用 fwrite():fwrite($fp,$outputstring);。这个函数告诉 PHP 将保存在变量$outputstring 中的字符串写入到$fp 指向的文件中。
目前,fwrite()函数有了一个替换它的函数,是 file_put_contents(),函数的原型如下所示:
int file_put_contents( string filename,
string data [,int flags
[,resource context]])
这个函数可以在不需要调用 fopen()函数打开要写的文件以前,将包含在 data 中的字符串写入到 filename 所指定的文件中。这个函数的后两个参数是可选的,一般用前面两个就可以了,这个函数是在 PHP5.0 中新引入的。
实际上,fwrite()的的参数有三个,但是第三个参数是可选的。fwrite()的原型如下所示:
int fwrite(resource handle,string string [,int length])
第三个参数 length 是写如的最大字符数。如果给出了这个参数,fwrite()将向 handle 指向的文件写入字符串,一直写到字符串末尾,或者已经写入了 length 字节,满足这两个条件之一就停止写入。handle 参数就是前面我们已经打开了的文件的那个变量。
通过 PHP 的内置函数 strlen()函数就可以获得该字符串的长度了,如下所示: fwrite($fp,$outputstring,strlen($outputstring));
当使用完了文件后,应当将它关闭,我们调用 fclose()函数就可以关闭文件了,调用方式为:fclose($fp);
如果该文件成功关闭了,函数将返回一个 true 值。反之,该函数就返回 false。
读文件
下面给大家讲一下怎么读文件。我们可以使用以只读模式打开文件,仍然是使用
fopen()函数打开文件。在下面的例子中,以只读方式打开这个文件,所以使用了“rb”文件模式:$fp = fopen("$DOCUMENT_ROOT/../php/php.txt", 'rb');
我们还可以每次读取文件的一行数据,可以使用的函数有:fgets()和 fgetss()。使用 fgets() 来读取文件比较简单,比如我们可以这样使用这个函数: $order =
fgets($fp,101);。它的第一个参数就是打开了的文件,而第二个参数就是可以读取一行的最大容量,需要注意的是读取的最大长度为指定的长度减去 1B,所以这里可以读取一行的最大长度为 100B。当使用这个函数之后,它将不断地读入数据,直至读到一个换行字符(\n)、或者文件结束符 EOF,或者是从文件中读取了 100B 就会结束读取。
fgetss()函数是fgets()的一个变体,它的原型如下所示: fgetss(resource fp,int length,string [allowable_tags]);
这个函数功能和 fgets()是一样的,而且前面两个参数和 fgets()是一样的。但是它可以过滤字符串中包含的 PHP 和 HTML 标记。如果要过滤任何特殊的标记,可以将它们包含在
allowable_tags 字符串中。如果要从安全的角度考虑,那就可以使用这个函数。
除了每次读取文件的一行外,还可以一次读取整个文件。PHP 提供了四种不同的方式来读取整个文件。
第一种方式是 readfile(),调用 readfile()这个函数打开这个文件,并且将文件内容输 出 到 浏 览 器 中 。 可 以 这 样 来 读 取 整 个 文 件 的 内 容 :
readfile("$DOCUMENT_ROOT/../php/php.txt");。使用这个函数的时候,参数就是文件的路径。
第二种方式是 fpassthru()。要使用这个函数,必须先使用 fopen()打开文件,然后将文件指针作为参数传递给 fpassthru()。这样就可以把文件的内容发送到浏览器中了。可以使用如下的代码代替上面的代码:
$fp = fopen("$DOCUMENT_ROOT/../php/php.txt", 'rb'); fpassthru($fp);
如果读操作成功,fpassthru()函数将返回 true,否则返回 false。
第三种方式是 file(),这个函数的使用方法和 readfile()是一样的,不过这个函数不仅可以将文件内容发送到浏览器中,还可以将结果发送到一个数组中,可以用以下的方式调用它:$filearray = file("$DOCUMENT_ROOT/../php/php.txt");
这行代码将 php.txt 文件中的所有内容保存在数组$filearray 中。文件中的每一行都将作为一个元素保存在这个数组中。
最后一种方式是 file_get_contents()函数,这个函数的使用方法也和 readfile()一样,但是这个函数将字符串的形式返回文件内容,而不是将文件内容回显到浏览器中。
我们还可以用 fgetc()函数来读取文件中的的一个字符,使用方法非常简单,为 fgetc($fp),参数就是已经打开了的文件。
字符串操作
通过前面的学习,大家已经知道了。漏洞出现的最主要原因是没有对用户输入的字符串进行相应的处理。所以对字符串的处理是防止出现安全问题的一个重要方法,而本节就给大家介绍在 PHP 环境下,对字符串处理的一些常见的方法。通过学习这些方法大家在后面的代码分析中会明白,漏洞出现的原因。所以这一节尤为的重要。
字符串的格式化
整理字符串的第一步就是要清理字符串中多余的空格。虽然这一步不是必须的,但是如果要将字符串存入一个文件或数据库中,就显的非常重要了,目前绝大部分 PHP 系统都会过滤字符串中的空格。和 ASP 中一样,PHP 也是使用 trim()函数来整理用户输入数据中的空格,如下所示:$name=trim($name);
trim()函数可以除去字符串开始位置和结束位置的空格,并将结果字符串返回。默认情况下,除去的字符是换行符和回车符(\n 和\r)、水平和垂直制表符(\t 和\x0B)、字符串结束符(\0)和空格。
对于输入的字符串来说,某些字符肯定是有效的。但是对于我们黑客来说,就是要往里面输入一些具有特殊功能的字符。但是 PHP 为了防止用户输入的数据具有某个功能,提供了转义字符串函数。这些转义字符串函数将字符串转义就是在她们前面加上一个反斜杠。例
如,“(双引号)就边成了\“(反斜杠双引号);\(反斜杠)就变成了\\(反斜杠反斜杠)。这个规则对所有的特殊字符都通用,所以如果在字符串中存在\\字符,就需要用\\\\进进行替换,而实现这个功能的函数就是 addslshes(),它的使用方法如:$name= addslshes($name); 和许多字符串函数一样,addslshes()函数需要一个字符串作为输入参数,经过处理,将返回一个重新格式化后的字符串。也正是因为有了这个函数的出现,PHP 的安全性得到了很大的提高,如果程序中使用了这个函数对用户输入的字符串进行处理了,那么我们几乎是不可能进行入侵了,这个函数可谓是黑客的天敌啊!而还有一个函数 stripslashes()的功能就是去掉这些斜杠的,功能和 addslshes()是相反的。
而在 php.ini 中还有一个叫做 magic_quotes_gpc 配置指令。如果它启用了的话,也就是说magic_quotes_gpc=true 的话那么它就会实现addslshes()和stripslashes()这两个函数的功能。如今,在 PHP 的新版本中的默认情况下,该指令是启用的,这也使得新版的 PHP安全性得到了很大的提高。gpc 表示 GET、POST 和cookie,这就意味着来自这些方法和方式的变量将自动包括在引号内。使用 magic_quotes_gpc()函数可以检查系统上的指令是否已经启用。
字符串的匹配与替换
匹配和替换也是和安全紧密相关的。通常为了在一字符串中查找另一个字符串,最为常用的函数是 strstr(),它可以用于在一个校长的字符串中查找匹配的字符串或字符。 Strstr()函数的原型如下所示:string strstr(string haystack,string needle); Haystack 为一个字符串,needle 也是一个字符串,但是函数的功能就是要在 haystack 中查找 needle 字符串出现的位置。必须向函数传递一个 haystack 参数和一个要查找的 needle参数。如果找到了 needle 的一个精确匹配,函数会从 needle 前面返回 haystack,否则返回值为 false。如果不存在不止一个 needle,返回字符串从出现第一个 needle 的位置开始。
我们知道替换函数在防止注入、跨站攻击上有着非常重要的地位,在 PHP 中也同样提供了这样的函数。进行替换操作最常用的字符串函数是 str_replace(),它的函数原型如下所示:
Mixed str_replace(mixed needle,mixed new_needle,mixed haystack[,int &count]); 这个函数用“new_needle”替换所有 haystack 中的“needle”,并且返回 haystack 替换后的结果。第四个参数 count 为可选,它包含了要执行的替换操作次数。
代码重用
在我们的软件开发过程中,会经常用到一些公共的代码。而为了省略多次编写那些多次使用的代码,提出了一种代码重用的理论。也就是说把一些公共的代码放在一个地方,而要用的时候就引用它。在 ASP 中我已经给大家介绍的 include()就是完成代码重用的函数,同样 PHP 也对代码重用方面做的非常好,本节重点就是讲解那些代码重用的函数。在第二章中,花了大量的篇幅给大家介绍文件包含漏洞,而这个漏洞出现的原因就是在引用的时候不当而造成的。所以本节是文件包含漏洞的基础知识,只有掌握了本节的知识才能对文件包含漏洞有一个全面的理解。大家听了一定很心动吧,那就 follow me 啊!
PHP 提供了两个非常简单却很有用的语句,它们允许重新使用任何类型的代码。使用一条 require()或 include()语句,可以将一个文件载入到 PHP 脚本中,这就实现了我们代码重用的结果了。通常,这个文件可以包含任何希望在一个脚本中输入的内容,其中包括 PHP语句、文本、HTML 标记、PHP 函数或 PHP 类。
下面我们来演示一下 require()的功能,我写了一些代码,把它命名为 require.php,代码如下所示:
<?php
echo '<h1>这是通过 require 函数而输出的内容</h1>';
?>
而我还写了一些代码,把它命名为 main.php,代码如下所示:
<?php
echo '<h1>这是通过echo 命令输出的内容</h1><br>'; require('require.php');
echo '<h1>这是通过 main.php 文件输出的内容</h1><br>';
?>
如果我们运行 require.php 文件后输出“这是通过 echo 命令输出的内容”大家应该不会感到奇怪。而当我们运行 main.php 时,输出的结果如图 9-5 所示。
图 9-5 通过require()函数而调用的输出
当我们需要在一个文件中引用其他文件的内容的时候,我们就可以使用 require()函数来调用。上面的例子中,我们使用的文件是 require.php,当运行该脚本的时候,require()语句:require('require.php');将被请求的文件内容代替,然后再执行脚本。这就意味着,当载入 main.php 文件时,它会像如下所示的代码那样执行:
<?php
echo '<h1>这是通过echo 命令输出的内容</h1><br>'; echo '<h1>这是通过 require 函数而输出的内容</h1>'; echo '<h1>这是通过 main.php 文件输出的内容</h1><br>';
?>
因为require.php 文件是公共代码,所以可以被多个文件所引用。 include()语句和 require()语句的功能几乎是等价的。两者之间的差异在于,当这两
个语句调用失败后,require()语句将给出一个致命的错误,而 Include()只是给出一个警告。
include()和require()都有变体函数,分别是 include_once()和 require_once()。通
过字面意思大家也可以猜到它们的用途是确保一个被包含的文件只能够被使用一次,而使用 include()和 require()函数确可以确保一个被包含的文件可以被多次使用,这就是他们的区别。
函数在前面已经提到了非常的多了,他们是用于分隔那些能够完成独立而明确的任务的代码,而且是允许在每次需要完成同样任务的时候重复使用代码。所以函数也是属于代码重用的一个方面。
在 PHP 中,函数的调用是不区分大小写的。一个函数声明将创建或者声明一个新函数,声明是以关键字 function 开始的,接着,给出函数名称和必要的参数,然后再次给出每次调用这个函数时要执行的代码。例如,下面是一个常见的函数声明:
function my_function()
{
echo '这里是函数要执行的代码'
}
这个函数声明是以 function 开始的,函数名为 my_function()。我们可以使用如下所示的命令调用这个函数:my_function()。在一个函数中,花括号包括了完成所要求任务的代码。在花括号中,可以包含任何在 PHP 脚本的其他地方都合法的代码,其中包括函数调用、新的变量或函数声明、include()和 require()语句类声明及 HTML 代码。还有在函数名的小括号里可以包含一些参数。下面我举个例子,说明 require()语句是如何和函数进行相互调用的。我在文件 function.php 中编写了一个函数,代码如下:
<html>
<head><title>funtion file</title></head>
<body>
<?php
function add($a,$b)
{
int $c;
$c=a+b; return $c;
}
</body>
</html>
而另外一个文件 add.php 的代码如下所示:
<html>
<head><title>add file</title></head>
<body>
<from>
<tr>
<td><h1>请输入 a</h1></td>
<td align="center"><input type="text" name="a" size="5" maxlength="10"></td><br>
</tr>
<tr>
<td><h1>请输入 b</h1></td>
<td align="center"><input type="text" name="b" size="5"
maxlength="10"></td><br>
</tr>
<tr>
<input type="submit" value="提交">
</tr>
</from>
<?php require('function.php')
$a=$_POST['a'];
$b=$_POST['b'];
add($a,$b);
echo '$c 的值是'.$a;
?>
</body>
</html>
在 add.php 中,我们先通过表单获得用户输入的数据,然后调用 function.php 中的 add函数,而这个调用的桥梁就是通过 require() 语句来完成的,通过 require() 语句把 funcion.php 中的代码插入到 add.php 中,最后在调用它。这也是在编程时最为常用的调用方法,这里大家一定要注意,因为我们分析代码的时候就是靠它来顺藤摸瓜的。图 9-6 和
9-7 就是上面代码运行的结果。
图 9-6 输入要相加的数
图 9-7 调用函数后的相加结果
面向对象的PHP简介
面向对象(OOP)是目前软件开发的主流技术,目前的编程语言大部分都已经支持面向对象方法了,我们的 PHP 自然对面向对象有非常好的支持。相信在大家当中一定有人也听过这个名词,而当今的两大主流语言 C++和 JAVA 均是面向对象的编程语言。
在面向对象编程语言中,在面向对象语言中,所有的东西都是对象,包括它自己也是一个对象。一个对象我们可以对其进行分类同时它具有操作和属性等特性。对象的属性是与对象相关的特性或变量,对象的操作则是对象可以执行的、用来改变其自身或对外部产生影响的方法、行为或函数。而我们可以对对象进行分类,所以类是一些具有相同特性或属性以及相同操作组成的对象,同时类还是对象的实例化。
上面这样讲,如果没有接触过面向对象肯定是理解起来有点困难,所以这里我举个例子来说明。假设我们要举例的对象是全世界的“人”。那么我们就可以对全世界的人的这个对象进行分类,比如我们分成“白种人”、“黑种人”、“黄种人”这三个类。而类是具有具有操作和属性的。比如“黄种人”这个类的人,他们都具有黄色皮肤这个特性,也就是有黄色皮肤这个属性,同时“黄种人”还可以完成跑步、吃饭等行为,也就是其操作。而且同一个对象中的不同类也会有一些相同的属性和操作,比如“白种人”、“黑种人”、“黄种人”这三个类的人都是具有两只手的特性,都具有会吃饭的操作等等。而我曾云好是一个黄种人,具有黄种人的各种属性和操作。所以我是属于“黄种人”这个类,同时也是这个类中的一个对象。同时我也把黄种人这个类实例化了,因为我是一个例子啊。上面的解释可能有一点牵强,不过大家一定要知道,类是对对象的实例化,一个对象具有属性和操作。
在PHP中创建类、属性和操作
前面已经给大家提到了 PHP 对面向对象有很好支持,所以下面就来具体给大家介绍一下
PHP 下的各种面向对象的知识。
当我们创建一个 PHP 类的时候,必须使用关键字“class”。一个最小的、最简单的类定义如下所示:
class classname
{
}
为了使上面的类具有实用性,类需要添加一些操作和属性。通过在类的定义中使用关键词
“var”来声明变量,可以创建属性。如下所示的代码创建了一个名为“classname”的类,它具有两个属性$attribute1 和$attribute2:
class classname
{
var $attribute1; var $attribute2;
}
通过在类中声明函数,我们可以创建类的操作。如下所示的代码创建了一个名为“classname”的类,该类中包含了一个进行加法操作的函数:
class classname
{
function add(a,b)
{
var $c;
$c=a+b; return c;
}
}
类的实例化、使用及继承
我们声明了一个类之后,我们需要创建一个对象——一个特定的个体,即类的一个成员并使用这个对象。这个过程我们就叫做创建一个实例或实例化类。我们可以使用关键字
“new”来创建一个对象。创建一个对象很简单,比如前面我们已经创建了一个 classname的类,那么该类的对象就应该这样创建:$a= new classname()。对象的名称就是该类的名称,不过后面还有一个小括号,括号里还可以有参数也可以没有。
在一个类中,可以访问一个特殊的叫做指针的变量——$this。如果当前的类中有一个属性为$attribute,则当在该类中通过一个操作访问这个变量时,可以使用$this->
attribute 来引用。下面的代码就是说明如何在一个类中访问属性: class classname
{
var $attribute; function sdsd($a)
{
$this->attribute=$a; echo $this->attribute;
}
}
我们还可以在类的外部访问类中的属性,比如下面的代码就是类的对象访问类中的属性:
class classname
{
var $attribute;
}
$a = new classname();
$a->attribute= 'value'; echo $a->attribute;
细心的朋友应该看可以看出来,如果任何对象都可以随便类中的任何变量的话,那一定会出现很多安全问题。所以 PHP 中对类中引用了访问修饰符,即给某些属性、变量进行权限的设置。如果程序员在编写代码的时候没有注意这个问题一定会发生漏洞,所以大家在分析代码的时候也要注意这些变量的访问权限,试一下是否可以越权访问。
目前的 PHP5 支持三种访问修饰符:
public:它是默认选项,如果没有为一个属性或方法指定访问修饰符,比如我们前面的例子,那它将是 public。Public 的属性和方法可以在类的内部和外部都进行访问。它
被称为公有的
private:使用这个访问修饰符意味着被标记的属性和方法只能在类的内部进行访问,它被成为私有的。私有的属性和方法将不会被继承(继承稍后给大家讲解)。
protected:使用这个访问修饰符的话那么类中的属性和方法也将只能在类的内部进行访问。它被成为保护的。
下面我给大家演示如何在程序中使用访问修饰符,代码如下: class classname
{
private $attribute; public function add()
{
public $a,$b,$c
$c= $a+$b; return $c;
}
}
$a = new classname();
$a->attribute= 'value'; echo $a->attribute;
在上面,每一个类成员都具有一个访问修饰符,说明它是公有的还是私有的。可以不添加 public 关键字,因为它是默认的访问修饰符。细心的朋友应该可以发现 attribute 前的
var 关键字被删除了,因为它被 public 给代替,所以这里可以不用写。爱动脑筋的朋友应该可以发现上面这个程序在运行的时候肯定会发生错误。还是 attribute 属性,因为在类中我们设置的是private 访问修饰符,只能在类的内部使用,而我们在外面却引用了 attribute这个属性,所以在运行的时候就会出现错误。分析代码的时候也是这样,要从细小的地方发现问题所在。
面向对象还有一个重要的特性就是继承,和我们现实生活差不多,例如,我们都继承了父母的一些特性等。假设有两个类,一个为 A 类另一个为 B 类,而且 B 类继承了 A 类,那么 A 类就是B 类的父类,自然 B 类就是A 类的子类了。如果类是另一个类的子类,那么可以使用关键字“extends”来指明继承关系。下面的就是 B 类继承了 A 类的例子,代码如下: class B extends A
{
var $attribute2; function oper2()
{
}
}
而 A 类具有如下所示的声明: class A
{
var $attribute1; function oper1()
{
}
}
按照上面两个类的声明,下面的访问都是正确的:
$b = new B();
$b->oper1();
$b->attribute1 = 10;
$b->oper2();
$b->attribute2 = 10;
因为 B 类是继承于 A 类,所以可以使用操作 oper1()和属性$attribute1,尽管这些操作和属性是在 A 类里声明的。作为 A 的子类,B 具有和 A 一样的功能和数据,此外,B 还可以声明自己的属性和操作。还有一点要注意的是,继承是单向的,也就是说子类可以从父类那些继承特性,而父类却不可以从子类中继承特性,和我们人的生活中也是差不多。
还有上面的 B 类之所以可以访问 A 类中的属性和方法是因为他们的访问修饰符都是
public,而如果在父类中的属性、方法和变量是 private 的话,那么它将不可以被子类所继承,它只能够父类自己所使用;而如果访问修饰符是 protected 的话,她将在类外部不可见 (就像一个 private 元素),但是可以被继承。
异常处理简介
既然是面向对象那还有一个叫做异常处理的机制,异常为以一种可扩展、可维护和面向对象的方式处理错误提供了统一的机制。这样理解理解比较困难,从字面上来说异常就是指程序发生了异常情况或非正常情况,例如用户输入错误、除数为零、需要处理的文件不存在等,而异常处理自然就是程序中的异常情况进行相应的处理的一个过程。
异常是 PHP5 中的一个新的重要特性。异常处理的基本思想是代码在 try 代码块被调用执行,如下代码就是一个示例:
try
{
//这里可以编写代码,为 try 的代码块
}
如果在 try 代码块中出现了错误,我们可以执行一个叫做抛出异常的操作。在一些面向对象语言中,比如 JAVA,在特定的情况下会自动抛出异常。而在 PHP 中,异常必须手动抛出。我们可以使用如下方式抛出一个异常:
throw new Exception('message',code);
throw 关键字将触发异常处理机制。它是一个语言结构,而不是一个函数,但必须给它传递一个值。它要求接收一个对象。在最简单的情况下,可以实例化一个内置的 Exception类,这个类就是系统内置用来处理异常情况的类, Exception 就是异常处理的类名,像上面的代码一样。这个类需要两个参数:一个消息和一个代码。它们分别表示一个错误消息和错误代码号。不过这两个参数都是可选。
最后,在 try 代码块之后,必须至少给出一个 catch 代码块。catch 代码块的结构如下所示:
catch (异常处理类名,异常处理对象名)
{
//处理异常情况的代码
}
可以将多个 catch 代码块与一个 try 代码块进行关联,这样不同的 catch 代码块就可以捕获不同的异常情况了。例如,捕获 Exception 类的异常情况,catch 代码块可以如下所示: catch (Exception $e)
{
//异常处理代码
}
传递给 catch 代码块的对象就是导致异常并传递给 throw 语句的对象。当产生一个异常时,而 catch 代码块具有多个的时候,PHP 将自动查询一个匹配的 catch 代码块。上面将对于新手来说确实有点太理论话了,下面我就举一个完整的异常处理的例子。
<?php try
{
throw new Exception('出现一了错误',32); //捕获异常
}
catch (Exception $e) // 处 理 异 常
{
Echo ' Exception'.$e->getCode().':'.$e->getMessage().'<br>';
//输出异常情况
}
?>
上面的代码是我们在 try 代码块中编写了一个异常处理,当代码运行的时候就会捕获异常,而 catch 就是用来处理这个异常的。代码运行后的结果如图 9-8 所示。
图 9-8 异常处理的输出结果
上面之所以能够输出异常处理的结构是因为我们使用 Exception 类的内置方法,下面就是该类主要的方法:
getCode()——返回传递给构造函数的代码
getMessage()——返回传递给构造函数的消息
getFile()——返回产生异常代码文件的完整路径
getLine()——返回代码文件中产生异常的代码行号
9.6 使用PHP从Web访问MySQL数据库
在第 5 章已经给大家讲解了 MySQL 的一些特性,相信大家已经掌握了。而对于 PHP 的最经典组合是 PHP+MySQL 了。有了前面的 PHP 和 MySQL 基础之后,本节就给大家讲解 PHP+MySQL 这个经典组合的一些知识,在以后的代码分析中大家会明白,几乎所有的 PHP 系
统的后台数据库都是 MySQL。
在任何用于从 WEB 访问数据库的脚本中,都应该遵循下面的基本步骤:
检查并过滤来自用户的数据。
建立一个到适当数据库的连接。
对数据库进行各种操作,如查询操作。
获取操作数据库的结果。
将结果现实给用户。
而我们分析代码找漏洞最重要的就是第一步,寻找代码中那些数据系统没有过滤或过滤不完全,只要出现了没有过滤或过滤不完全的话,八成就会出现了安全问题,也就找到了一个漏洞了。
前面我已经给大家介绍了 magic_quotes_gpc 指令及 addslshes()和 stripslashes()这两个函数。如果一个安全意识比较高的程序员,他一般都会利用他们来过滤用户输入的数据,常见的过滤代码如下:
if (!magic_quotes_gpc())
{
$id = addslshes($id);
$name = addslshes($name);
}
前面也已经告诉大家了 magic_quotes_gpc()函数就是用来检测 magic_quotes_gpc 指令
是否已经启用了。上面的代码首先是判断 magic_quotes_gpc 指令是否启用,如果没有启用的话就用 addslshes()函数对输入的数据进行转义。如果在一个系统中的所有参数都用这些给转义了,那我们找代码的漏洞几乎是没戏了。
9.6.1 执行SQL命令
(1)、建立与数据库服务器的连接
PHP 为连接 MySQL 提供了新的函数库。这个函数库是 mysqli(i 表示改进)。这个函数库用于 MySQL 版本 4 以及以后版本。在版本 4 中,MySQL 添加了一个新的连接协议,其执行速度更快,而且 mysqli 允许使用它。mysqli 函数库允许使用面向对象或面向过程的语法。在
PHP 版本 4 已经后面的版本,我们可以使用下面的方法来连接 MYSQL 数据库。
①、@db = new mysqli('MySQL 数据库服务器的主机名', 'MySQL 用户名', 'MySQL 密码', '需要访问的数据库');
例如,我们要访问本地主机上的 hack 数据库,其数据库的用户名为 username,密码为
Password,那么连接 MySQL 数据库的语句为:
@db = new mysqli('localhost', 'username', 'password', 'hack');
上面的$db 是一用变量名,随便用什么名称都可以的,而其前面的@符号是为了抑制出错信息,防止由系统返回的出错信息直接显示在 Web 浏览器上。
②、上面的方法是利用 mysqli 函数库的面向对象的方法,我们还可以利用 mysqli 的面向过程来连接,如下所示:
@ $db = mysqli_connect('MySQL 数据库服务器的主机名', 'MySQL 用户名', 'MySQL 密码',
'需要访问的数据库');例如,还是上面的例子:
@ $db = mysqli_connect('localhost', 'username', 'password', 'hack');
利用 mysqli 连接好了数据库之后,就可以利用 mysqli 函数库的函数了。Mysqli 的 大多数函数都有面向对象接口和过程接口。通常,他们二者的差异在于过程版本的函数名称是以 mysqli_开始的,同时要求传入通过 mysqli_connect()函数获得的资源句柄。
而在PHP4 以前的版本中,PHP 连接MySQL 数据库是使用mysql 模块来完成的。通过mysql功能模块去连接 MySQL 服务器的办法是调用 mysql_connect()函数,它需要三个参数:Mysql服务器主机名、Mysql 用户名和密码。如果 MySQL 服务器与 PHP 脚本运行在同一台计算机上,可以使用 localhost 作为它的主机名。
@ $conn = mysqli_connect('localhost', 'username', 'password');
大家如果细心的话,可以看到在上面的连接中,没有连接到数据库名,而只是连接了数据库服务器。在与 MySQL 数据库服务器建立连接之后,就可以使用各种数据库函数和执行一些 SQL 命令了。而要对数据库服务器中某个数据库进行操作最好先使用 mysql_select_db()函数与这个数据库建立一个连接,例如要连接 hack 这个数据库:mysql_select_db('hack');
(2)、执行 SQL 命令
要执行 SQL 命令,在 PHP4 版本以前中,我们需要把 SQL 作为一个字符串传递给
mysql_query()函数来完成,而如果想访问的不是当前数据库,就需要调用 mysql_db_query()函数来添加 SQL 命令并明确地给出那个数据库的名称。例如,下面的查询语句:
$result = mysql_query("select * from hack");
$result = mysql_db_query("crack","select * from hack");
需要注意的是通过调用 mysql_query()函数而执行 SQL 命令不得以分号(;)结束,否则会导致 SQL 语法错误。mysql_query()函数可以用来执行任何一种 SQL 命令,比如 SELECT命令(查询)、INSERT 命令(插入新记录)、UPDATE 命令(修改现有记录)、DELECT 命令(删除现有记录)、CREATE TABLE 命令(创建新数据表)、ALTER TABLE 命令(改变数据表结构)等。而在 PHP 版本 4 以及后面的版本中,要执行 SQL 命令可以使用 mysqli_query()函数。
但是在使用之前,最好建立要执行的 SQL 命令,例如:
$query = "select * from hack where city='长沙' ";
在这个例子中,我们将在指定的字段 city 中查询表 hack 中为“长沙”的黑客信息。对于面向对象,我们可以运行如下操作:$result = $db->query($query);
或者我们也可以使用面向过程来进行操作:$result = mysqli_query($db,$query);
上面都是将要运行的 SQL 命令传递给它,在过程版本中,$db 是数据库连接。同样,上面的操作能够完成各种 SQL 命令。
对于执行 SQL 命令之后返回的结果,我们还可以对其进行相应的处理。例如要获得执行
SQL 命令结果的行数,在使用面向对象方法中,我们这样来访问:
$num_results = $result->num_rows;
当使用一个过程方法时,函数 mysqli_num_rows()给出了结果的行数,我们可以这样使用它:
$num_results = mysqli_num_rows($result);
PHP的高级技术
本章的最后一节,我给大家介绍一些 PHP 的高级技术,当然大部分都是与安全相关的,对大家在分析代码及入侵 PHP 网站是非常有帮助的。
php.ini文件简介
php.ini 文件是 PHP 系统的配制文件,PHP 服务器的各种参数设置都在这个文件里。在前面也多次提到了这个文件。通过 php.ini 文件我们可以对 PHP 进行各种设置。一般来说,在我们搭建好了 PHP 服务器后不需要对 php.ini 文件进行修改了,在默认的情况下,它已经比较好了,特别是在 php5 中,php.ini 文件对安全性的设置就更加全面了,所以 php5 的安全性比起以前的版本来说安全性有了很大的提高。但是对于我们要查找 PHP 系统中代码的漏洞就要对 php.ini 这个文件中常见的选项比较熟悉。
php.ini 配置文件允许用户调整 PHP 多项功能,包括设置文件路径以及目录、改变会话以及数据库参数,以及激活扩展选项等。下面我给大家简单介绍一下 php.ini 配制文件中比较重要的一些选项,而对于更加详细和完整的配制大家可以去参考光盘中自带的 php 手册。
在 php3 中,php 的配制文件为 php3.ini。不过现在的名字是 php.ini。这个文件我们可以用记事本打开。大家如果搭建了 PHP 服务器的话,可以在根目录下找到 php.ini 文件来看看。下面开始给大家介绍 php.ini 文件中的一些重要参数。更多的参数要去参考 PHP 手册。
short_open_tag:这个参数决定是否允许使用 PHP 代码开始标志的缩写形式(<? ?>)。如果要和 XML 结合使用 PHP ,可以禁用此选项以便于嵌入使用 <? xml ? >。否则还可以通过 PHP 来输出,例如:<?php echo '<?xml version="1.0"'; ?>。如果禁用了,必须使用 PHP 代码开始标志的完整形式(<?php ? >)。这个参数有两个结果,on 代表允许,off 表示禁用。 例如 short_open_tag=on 表示允许使用缩写形式。注: 本指令也会影响到缩写形式 <? =,它和 <? echo等价。默认是 on。
asp_tags:这个参数决定是否可以使用 ASP 风格的标志 <% %>。如果 asp_tags=on 则表示可以,而 off 表示禁用。默认是 off。
track_vars:这个参数有两个选项 on 和 off,如果 track_vars=on 的话,则环境变量,GET,
POST,Cookie 和 Server 变量都能够分别在全局关联数组中找到:$_ENV,$_GET,
$_POST,$_COOKIE 和$_SERVER。注意自 PH P 4.0 .3 起,track_vars 总是打开的。
variables_order:设定 EGPCS (Environment,GET,POST,Cookie,Server)变量解析的顺序。默认设定为“EGPCS”,为 variables_order "EGPCS"。
register_globals:该参数只有 on 和 off 两个选项,作用为决定是否将 EGPCS (Environment, GET,POST ,Cookie,Server)变量注册为全局变量。自 PHP 4.2.0 开始以后的版本,本选项默认为 of f。
gpc_order:设定 GET/POST/COOKIE 变量解析的顺序,默认为“GPC”,为 gpc_order "GPC"。举例说,若将其设为“GP”,为 gpc_order "GP"。将会导致 PHP 完全忽略 cookie 变量,并用 GET 方法的变量覆盖 POST 方法的同名变量。
file_uploads:是否允许 HTTP 文件上传。这个参数有 on 和off 两个选项。这个参数从 PHP
4.0 开始起用。
upload_tmp_dir:文件上传时存放文件的临时目录。必须是 PHP 进程所有者用户可写的目录。
magic_quotes_gpc:这个参数大家应该很熟悉,前面已经两次提到了它。其作用是在输入的
GET/POST/Cookie 数据中对数据进行转义。
php.ini 文件中的参数非常的多,我这里就是简单给大家介绍一些比较重要而且与安全相关的给大家认识。如果要获得当前系统的 PHP 配制信息,还可以使用 phpinfo()函数来获得。对于 更多的可 以参考 PHP 中文 手册 ( 在光盘中已 收录 ) 或者访问 http://php.chinaunix.net/manual/zh/index.php。
与文件系统和服务器的交互
(1)、上传
第二章已经向大家演示了上传漏洞,大家应该这个漏洞的危害是非常大的,利用这个漏洞一般是直接可以获得 webshell。而接下来就给教大家一些在 PHP 下上传方面的知识,大家有了这些基础知识后就能够对上传的代码进行漏洞分析了。
在 PHP 中有一个非常有用的功能就是它支持 HTTP 方式的文件上传。不是使用 HTTP 将服务器上的文件传递到客户端的浏览器,而是以相反的方向执行——使用 HTTP 将文件从客户端浏览器传递到服务器。通常是使用 HTML 表单界面来实现对文件的上传。为了实现文件上传的功能,需要用到一些专门用于上传的 HTML 语法,在这里我给大家写了一段简单的代码来实现客户端的功能,代码如下:
<html>
<head>
<title>Administration - upload new files</title>
</head>
<body>
<h1>上传文件</h1>
<form enctype="multipart/form-data" action="upload.php" method=post>
<input type="hidden" name="MAX_FILE_SIZE" value="1000000">上传这个文件: <input name="uploadfile" type="file">
<input type="submit" value="上传">
</form>
</body>
</html>
上面代码 中的表单 使用 POST 方法, 在 <from>标记中, 必须设置 属 性 enctype="multipart/form-data",这样服务器就可以知道上传的文件带有常规的表单信息。还必须有一个可以设置上传文件最大长度的表单域,这是一个隐藏域,如下所示:
<input type="hidden" name="MAX_FILE_SIZE" value="1000000">。而且表单域的名字必须是“MAX_FILE_SIZE”,它的值是允许上传文件的最大长度值(按字节计算),在这里我们设置的是 1000000B(几乎是 1MB)。同时还要指定文件类型,这里的是<input name="userfile"
type="file">。上面的代码运行后的结果如图 9-9 所示,我们点击“浏览..”按钮就可以选择我们要上传的文件,点击“上传”按钮就将文件提交到服务器上的 upload.php 文件进行处理。
图 9-9 客户端上传的界面
客户端的文件传到服务器上去就要对其进行处理,一般要处理文件与 register_globals这个指令有关,好在目前的最新版本默认是开启的这个指令,所以这个已经对我们上传没什么很大的影响了。
在PHP 脚本中,需要处理的数据保存在超级全局数组$_FILES 中,所以我们上传了的文件也同样保存在全局数组$_FILES 中,目前 register_globals 指令是默认开启的,所以我们可以直接访问这个数组。
保存$_FILES 数组中的元素时,将同时保存客户端 HTML 表单中<file>标记的名称。在上面的例子中,<file>标记的名称是 uploadfile,所以我们上传的文件保存在$_FILES 数组中的内容有下面一些:
存储在$_FILES['uploadfile'][ 'tmp_name']变量中的值就是文件在 Web 服务器中临时存储的位置。
存储在$_FILES['uploadfile'][ 'name']变量中的值就是我们上传的文件的文件名。
存储在$_FILES['uploadfile'][ 'size']变量中的值就是文件的字节大小
存储在$_FILES['uploadfile'][ 'type'] 变量中的值就是文件的 MIME 类型,例如:
text/plain 或 image/gif。
存储在$_FILES['uploadfile'][ 'error'] 变量中的值将是任何与文件上传相关的错误代码。
通过上面的五个变量服务器可以得到我们上传的文件的各种信息,通过他们就可以知道上传文件的位置及其名称。接下来就是将上传的文件复制到其他有用的地方。因为脚本在执行结束前会将临时文件删除,所以如果要保留上传的文件,就必须将文件重命名或移动。通过上面我们就可以完成文件的上传了,到这里大家基本上掌握了上传了基础知识,在下一章会教大家如何利用上面的基础知识来查找代码中存在的上传漏洞。
(2)、目录、文件
PHP 提供了一些函数用于浏览目录,例如我们要浏览 windows 操作系统下的 WINDOWS目录的脚本,代码如下所示:
<html>
<head>
<title>浏览WINDOWS 目录</title>
</head>
<body>
<?php
$current_dir = 'C:/WINDOWS';
$dir = opendir($current_dir);
echo "$file"; closedir($dir);
?>
</body>
</html>
上面的脚本使用了三个函数 opendir()、closedir()和 readdir()。
函数 opendir()用于打开所浏览的目录,类似于我们用 fopen()打开所读取的文件。传递给
opendir() 函数的参数不是文件名称而是一个目录名称, 上面的代码是: $dir = opendir($current_dir);
目录打开之后,可以通过调用 readdir($dir)从目录中读取文件。当完成从目录中读取文件的步骤之后,可以通过调用函数 closedir($dir)来关闭文件。
我们还可以通过调用 dirname($path)函数和 basename($path)函数将分别返回路径的目录部分和路径部分的文件名称部分。使用 disk_free_space($path)函数来返回磁盘(在
Windows 操作系统下)或目录所在的文件系统(UNIX 系统下)上的空余空间。
除了可以移动地读取信息和目录外,还可以用 PHP 中的mkdir()和rmdir()函数来创建和删除目录。当然,只能在用户具有访问权限的路径上创建和删除目录。
函数 mkdir()的使用有一点复杂,它带有两个输入参数:目标目录的路径(包括新的目录名)和希望在该目录下拥有的访问权限。例如:mkdir("C:/haha",0777);
0777 就是访问权限的权限码,默认情况下是 0777,即最大权限。而且在 Windows 操作系统下,PHP 版本 4 以后就对权限码忽略了,成为一个可选项了。函数 rmdir()将删除一个目录,例如:rmdir("C:/haha");,不过需要注意的是要删除的目录必须是为空目录。
前面的都是关于目录的函数,接下来给大家介绍一些文件函数。
basename()函数获得的是不带目录的文件名,也可以使用 dirname()获得不带文件名称目录名称。函数 fileatime()和 filemtime()将分别返回该文件最近被访问和最近被修改的时间戳。函数 fileowner()和 filegroup()将分别返回用户标识(uid)和组标识(gid)。函数
chmod(file,permissions)用于修改文件的权限,例如:chmod(‘haha.txt’,0777);,不过需要注意的是该函数不可以在 windows 下运行。
同目录一样,我们也可以创建、删除和移动文件。创建一个文件很简单,使用 touch()函数就可以完成,该函数的原型如下所示:
int touch (string file,[int time[,int atime]])
如果要创建的文件已经存在了,那么它的修改时间将回改成当前时间或者所给出的第二个时间参数。如果文件不存在,将创建此文件。也可以使用 unlink()函数来删除一个文件,可以按照如下方式使用这个函数:unlink($filename);。这个函数在旧版本的 Windows 系统中不能够运行,不过我们可以使用 system("del filename.txt")函数来删除这个文件。
可以使用函数 copy()和rename()来复制和重命名(移动)文件,如下所示: copy($source_path,$destination_path);
rename($oldfile,$newfile);
(3)、程序执行函数
在PHP 中,给我们提供了几个函数用来完成服务器命令的功能,这些函数对我们入侵服务器的时候非常有用。利用这些函数我们可以完成一些操作系统的功能,常见的执行函数有下面三个:
exec()
exec()函数的原型如下所示:
string exec(string command [,array &result[,int &return_value]])
其中 command 就是我们要执行的命令,其他两个参数是可选的。例如要遍历 D 盘中的目录和文件:exec("dir D:");
exec()函数没有直接输出,只是将返回命令结果的最后一行。如果以 result 传递一个变量,次函数将返回字符串组数,这些字符串代表输出的每一行。如果以 return_value 传递一个变量,将获得返回代码。
passthru()
函数 passthru()原型如下所示:
void passthru(string command [,int return_value])
函数 passthru()的功能和exec()是一样的,不过 passthru()将结果直接输出并显示在浏览器上。而且这个函数的参数格式与 exec()也是一样的。
system()
函数 system()原型如下所示:
string system(string command [,int return_value])
这个函数将执行命令的输出结果显示在浏览器上,并且它将每一行的输出向后对齐。这就是与 passthru()函数的不同之处。而且这个函数的参数格式与 exec()也是一样的。
在最后介绍一下PHP 中使用环境变量。PHP 提供了两个函数来使用环境变量:getenv()和 putenv()。其中 getenv()函数能够获得环境变量值,而 putenv()函数能够设置环境变量值。不过要注意的是,这里所说的环境变量值是指运行 PHP 的服务器上的环境变量值。
运行phpinfo()函数可以获得PHP 所有环境变量的列表。不过我们也可以使用getenv()来获得,例如 getenv("HTTP_REFERER");就会返回拥护来到当前页之前的上一页面的 URL。我们也可以调用函数 putenv()函数来设置环境变量,如下所示:
$home= "/home/blank"; putenv("HOME=$home");
会话控制
会话控制在前面将 ASP 的时候就已经提到过了,如 cookie 就是属于其他的一种技术。在 PHP4 及其以后版本中,PHP 自身包含了会话控制函数。PHP 的会话是通过唯一的会话 ID来驱动的,会话 ID 是一个加密的随机数字。它有 PHP 生成,在会话的生命周期中都会保存在客户端。它可以保存在用户机器里的 cookie 中,或者通过 URL 在网络上传递。
在 PHP 中,可以使用 setcookie()函数手动设置 cookie,该函数的原型如下所示: bool setcookie(string name [,string value [,int expire [,string path [,string domain [,int secure]]]]])
setcookie()的参数详解如图 9-10 所示。
图 9-10 setcookie()的参数详解如果按如下方式设置一个 cookie:
setcookie ('mycookie', 'value');
当我们访问该站点的下一页(或者重载当前页)的时候,通过名为$_COOKI['mycookie']或
$HTTP_COOKIE_VARS["mycookie"]来访问这个 cookie。而当 register_globals=on 的时候可以直接通过$mycookie 来访问。
我们可以通过函数 session_get_cookie_params()来查看由会话控制设置的 cookie 内容。它将返回包含元素 lifetime、path、domain 和 secure 的相关数组。也可以使用 session_get_cookie_params($lifetime,$path,$domain [,$secure]);来设置会话 cookie的参数。
在PHP 中要使用会话功能,首先必须开始一个会话,在代码中通常调用 session_start()函数来开始一个会话,我们还可以在 php.ini 文件中将参数 session.auto_start 设置为 on的话,PHP 就会自动启用会话。
自 PHP4.1 版本以后,会话变量保存在超级全局数组$_SESSION 中,所以可以利用
$_SESSION 来创建一个会话变量,只需要在这些数组中设置一个元素,如下所示:
$_SESSION['myvar']=5;
当使用完了一个会话变量之后,可以将其注销。通过注销$_SESSION 数组的适当元素,可以直接注销变量,如:unset($_SESSION['myvar']);
注销完了之后在调用 session_destory()函数来清除会话 ID,如:session_destory();
通过会话控制我们可以用它来进行身份认证,一般是采用 session。在下一章将会向您演示如何绕过他们的验证进行入侵。
eval()函数及一句话木马
第二章中大家已经接触了 PHP 的一句话木马了,大家都知道 PHP 的一句话木马是<?php
eval($_POST[cmd])?>。当这段代码被插入到服务器上后,我们在提交大的木马代码到服务器上的时候通过$_POST 就会接收 cmd 参数过来的数据,而 eval()函数的功能就是执行我们提交的大马的代码,这样我们的大马就在服务器上执行了,成功的获得了 webshell。
而下面就是要给大家详细讲一下 eval()这个函数。这个函数可以在许多不同的情况下
使用。例如,我们可以在数据库里存储一段代码,以便以后可以检索它们并求值。eval()函数最常见的用法就是系统模块化,我们可以从数据库中载入 HTML、PHP 和纯文本混合。模版系统可以对这些内容进行格式化并且通过 eval()函数来执行任何 PHP 代码。这里大家要注意了,是执行任何 PHP 代码,这就是为什么我们用这个函数来编写一句话木马。它能够执行任何获得的 PHP 代码,而这个获得就是通过$_POST,而 cmd 就是接收来自客户端的代码。
到这里,我们的 PHP 基础就讲完了。上面的内容确实比较枯燥,但是这是必须要学的,而在下一章会带领大家如何寻找 PHP 系统中的各种漏洞,而要寻找这些漏洞都是和上面的知识息息相关的,上面的内容都是我精心挑选出来的,大部分都是与黑客相关的基础知识。掌握了他们就可以很快的找出代码中的各种漏洞。还有上面涉及到了比较多的函数,我并没有讲的非常清楚,这是因为在光盘中的 PHP 中文手册已经包含了 PHP 的一切,如果哪个函数没有理解的话可以直接查,里面有最为详细的解释。
声明:本章中部分 PHP 的基础知识参考了由机械工业出版社出版的《PHP 和 MySQL Web
开发》,该书由 Luke welling、Laura Thomson 著。及由人民邮电出版社出版的《MySQL
5 权威指南》,该书由 Michael Kofler 著。