PHP代码审计基础(三)

文件读取(下载)

1.file_get_contents()

函数功能是将整个文件读入一个字符串

file_get_contents(path,include_path,context,start,max_length)

参数

  • filename:要读取文件的名称。
  • include_path:可选。如果也想在 include_path 中搜索文件,可以设置为1。
  • context:可选。规定句柄的位置。
  • start:可选。规定文件中开始读取的位置。
  • max_length:可选。规定读取的字节数。

代码示例

<?php
echo file_get_contents("test.txt");
?>
执行结果——>
This is  a test text

2.fopen()

此函数将打开一个文件或URL,如果 fopen() 失败,它将返回 FALSE 并附带错误信息。我们可以通过在函数名前面添加一个 @ 来隐藏错误输出。

fopen(filename,mode,include_path,context)

参数

  • filename:必需。要打开的文件或URL
  • mode:必需。规定访问类型(例如只读,只写,读写方式等,方式的规定和其他语言的规定方式一致)
    i- nclude_path:可选。就是你可以指定搜索的路径位置,如果要指定的话,那么该参数要指定为1
  • context:可选。规定句柄的环境。

代码示例:

?php
	$file = fopen("test.txt","rb");
	$content = fread($file,1024);
	echo $content;
	fclose($file);
?>
    
执行结果——>
This is  a test text

这段代码中还包含了fread的用法。因为fopen仅仅只是打开一个文件,要想读取还得需要用到fread来读取文件内容。

3.fread()

这个函数刚才在上个函数中基本已经演示过了,就是读取文件内容。这里代码就不再演示了,简单介绍一下参数和用法。

tring fread ( resource $handle , int $length )

参数

  • handle:文件系统指针,是典型地由 fopen创建的resource。
  • length:必需。你要读取的最大字节数。

4.fgets()

从打开的文件中读取一行

fgets(file,length) 

参数

扫描二维码关注公众号,回复: 13771433 查看本文章
  • file:必需。规定要读取的文件。
  • length:可选。规定要读取的字节数。默认是1024字节。
    可以看出这个函数和之前的fread区别不是很大,只不过这个读取的是一行。

5.fgetss()

这个函数跟上个没什么差别,也是从打开的文件中读取出一行,只不过过滤掉了 HTML 和 PHP 标签。

fgetss(file,length,tags)

参数

  • file:必需。要检查的文件。
  • length:可选。规定要读取的字节数,默认1024字节。
  • tags:可选。哪些标记不去掉。

代码示例:

<?php
	$file = fopen("demo.html","r");
	echo fgetss($file);
	fclose($file);
?>

demo.html代码
<h1>I am a demo</h1>
    
执行结果——>
I am a demo

6.readfile()

这个函数从名称基本就知道它是干啥的了,读文件用的。此函数将读取一个文件,并写入到输出缓冲中。如果成功,该函数返回从文件中读入的字节数。如果失败,该函数返回 FALSE 并附带错误信息。

readfile(filename,include_path,context)

参数

  • filename:必需。要读取的文件。
  • include_path:可选。规定要搜索的路径。
  • context:可选。规定文件句柄环境。

代码示例:

<?php
	echo "<br>" . readfile("demo.txt");
?>
    
执行结果——>
I am a demo:) I am a demo:(
28

我们看到不仅输出了所有内容,而且还输出了总共长度。但是没有输出换行。

7. file()

把文件读入到一个数组中,数组中每一个元素对应的是文件中的一行,包括换行符。

file(path,include_path,context)

参数

  • path:必需。要读取的文件。
  • include_path:可选。可指定搜索路径。
  • context:可选。设置句柄环境。

代码示例:

<?php
print_r(file("demo.txt"));
?>
    
执行结果——>
Array 
( 
[0] => I am the first line! 
[1] => I am the second line! 
)

8.parse_ini_file()

从名称可以看出,这个函数不是读取一个简单的文件。它的功能是解析一个配置文件(ini文件),并以数组的形式返回其中的位置。

parse_ini_file(file,process_sections)

参数

  • file:必需。要读取的ini文件
  • process_sections:可选。若为TRUE,则返回一个多维数组,包括了详细信息

代码示例:

<?php
	print_r(parse_ini_file("demo.ini"));
?>

demo.ini内容:
[names]
me = Robert
you = Peter

[urls]
first = "http://www.example.com"
second = "https://www.runoob.com"

执行结果——>
Array 
( 
[me] => Robert 
[you] => Peter 
[first] => http://www.example1.com 
[second] => https://www.example2.com 
)

9.show_source()/highlight_file()

作用就是让php代码显示在页面上。这两个没有任何区别,show_source其实就是highlight_file的别名。

10.总结

文件读取这块内容没什么好说的,不难,大多只是基本的应用。重点文件读取如果没有设置权限和过滤参数,那就问题大了,我们就可以任意文件读取了。

补充:什么是句柄?

一段代码

$file = fopen("demo.txt","rb");

在这段代码中$file就是一个句柄。句柄关键点在“柄”,后面的fopen是一个资源,好比一口锅,而前面的$file就好比这个锅的把手。那么以后我们在操作的时候操作把手就行了。通过这个把手我们可以间接操作比较大的资源。其实也类似C语言中的指针,只是一个标识


文件删除

1.unlink()

此函数用来删除文件。成功返回 TURE ,失败返回 FALSE。

unlink(filename,context)

参数

  • filename:必需。要删除的文件。
  • context:可选。句柄环境。

一些网站是有删除功能的。比如常见的论坛网站,是有删除评论或者文章功能的。倘若网站没有对删除处做限制,那么就可能会导致任意文件删除(甚至删除网站源码)。

代码示例:

<?php
    $file = "demo.txt";
    if(unlink($file)){
    
    
        echo("$file have been deleted");
    }
	else{
    
    
        echo("$file not exist?")
    }
php>

2. session_destroy()

在了解这个函数之前,我们需要先了解 PHP session。 PHP session 变量用于存储关于用户会话的信息。

session_destroy()函数用来销毁一个会话中的全部数据,但并不会重置当前会话所关联的全局变量,同时也不会重置会话 cookie

代码示例:

<?php
// 初始化会话。
// 如果要使用会话,别忘了现在就调用:
session_start();

// 重置会话中的所有变量
$_SESSION = array();

// 如果要清理的更彻底,那么同时删除会话 cookie
// 注意:这样不但销毁了会话中的数据,还同时销毁了会话本身
if (ini_get("session.use_cookies")) {
    
    
    $params = session_get_cookie_params();
    setcookie(session_name(), '', time() - 42000,
        $params["path"], $params["domain"],
        $params["secure"], $params["httponly"]
    );
}

// 最后,销毁会话
session_destroy();
?> 

变量覆盖

1.extract()

此函数从数组中将变量导入到当前的符号表。其实作用就是给变量重新赋值,从而达到变量覆盖的作用。

extract(array,extract_rules,prefix)

参数

  • array:必需。规定要使用的数组。

  • extract_rules:可选。extract函数将检查每个键名是否为合法的变量名,同时也检查和符号中已经存在的变量名是否冲突,对不合法或者冲突的键名将会根据此参数的设定的规则来决定。

      EXTR_OVERWRITE - 默认。如果有冲突,则覆盖已有的变量。
      EXTR_SKIP - 如果有冲突,不覆盖已有的变量。
      EXTR_PREFIX_SAME - 如果有冲突,在变量名前加上前缀 prefix。
      EXTR_PREFIX_ALL - 给所有变量名加上前缀 prefix。
      EXTR_PREFIX_INVALID - 仅在不合法或数字变量名前加上前缀 prefix。
      EXTR_IF_EXISTS - 仅在当前符号表中已有同名变量时,覆盖它们的值。其它的都不处理。
      EXTR_PREFIX_IF_EXISTS - 仅在当前符号表中已有同名变量时,建立附加了前缀的变量名,其它都不处理。
      EXTR_REFS - 将变量作为引用提取。导入的变量仍然引用了数组参数的值。
    
  • prefix:可选。如果 extract_rules 参数的值是 EXTR_PREFIX_SAME、EXTR_PREFIX_ALL、
    EXTR_PREFIX_INVALID 或 EXTR_PREFIX_IF_EXISTS,则 prefix 是必需的。

代码示例:

<?php
    $color = "blue";
    $one_array = array("color" => "red",
        "size"  => "medium",
        "name" => "dog");
    extract($one_array);
    echo "$color, $size, $name";
?>
    
执行结果——>
red, medium, dog

在上述代码中,我们看到,本来我们定义的color是blue,输出的时候变成了red,本来我们没有定义size和name,可是却能输出这两个变量。

还有一些在CTF比赛中出现过的用法,比如直接让你POST传参来改变某个变量的值。

代码示例:

<?php
    $name = 'cat';
    extract($_POST);
    echo $name;
?>

参时如果我们POST传入name=dog,那么页面将会回显dog,说明这个函数的使用让我们实现了变量的覆盖,改变了变量的值。

2.parse_str()

此函数把查询到的字符串解析到变量中。

parse_str(string,array)

参数

  • string:必需。规定要解析的字符串。
  • array:可选。规定存储变量的数组名称。该参数只是变量存储到数组中。

代码示例:

<?php
    parse_str("name=Ameng&sex=boy",$a);
    print_r($a);
?>
    
执行结果——>
Array
(
    [name] => Ameng
    [sex] => boy
)

上述代码是有array情况下的使用情况,那么如何实现变量的覆盖呢?如果没有array 参数,则由该函数设置的变量将覆盖已存在的同名变量。

代码示例:

<?php
	$name = 'who';
    $age = '20';
    parse_str("name=Ameng&age=21");
    echo "$name, $age";
?>
执行结果——>
Ameng, 21

通过上述代码,我们可以发现,变量name和age都发生了变化,被新的值覆盖了。这里我用的是 PHP 7.4.3 版本。发现这个函数的这个作用还是存在的,且没有任何危险提示。

3.import_request_variables()

此函数将GET/POST/Cookie变量导入到全局作用域中。从而能够达到变量覆盖的作用。

版本要求:PHP 4 >= 4.1.0, PHP 5 < 5.4.0

bool import_request_variables ( string $types [, string $prefix ] )

参数

  • types:指定需要导入的变量,可以用字母 G、P 和 C 分别表示 GET、POST 和 Cookie,这些字母不区分大小写,所以你可以使用 g 、 p 和 c 的任何组合。POST 包含了通过 POST 方法上传的文件信息。注意这些字母的顺序,当使用 gp 时,POST 变量将使用相同的名字覆盖 GET 变量。
  • prefix:变量名的前缀,置于所有被导入到全局作用域的变量之前。所以如果你有个名为 userid 的 GET 变量,同时提供了 pref_ 作为前缀,那么你将获得一个名为 $pref_userid 的全局变量。虽然 prefix 参数是可选的,但如果不指定前缀,或者指定一个空字符串作为前缀,你将获得一个 E_NOTICE 级别的错误。

代码示例:

<?php
    $name = 'who';
	import_request_variables('bob');
	if($name == 'Alice'){
    
    
		echo $name;
	}
	else{
    
    
		echo 'You are not Alice';
	}
?>

如果什么变量也不传,那么页面将回显You are not Alice如果通过GET或者POST传入name=Alice那么页面就会回显Alice

可以见到此函数还是很危险的,没有修复方法,不使用就是最好的方法。所以在新版本的 PHP 中已经废弃了这个函数。

4. foreach()

foreach 语法结构提供了遍历数组的简单方式。foreach 仅能够应用于数组和对象,如果尝试应用于其他数据类型的变量,或者未初始化的变量将发出错误信息。有两种语法:

foreach (array_expression as $value)
    statement
foreach (array_expression as $key => $value)
    statement

第一种格式遍历给定的 array_expression 数组。每次循环中,当前单元的值被赋给 $value 并且数组内部的指针向前移一步(因此下一次循环中将会得到下一个单元)。

第二种格式做同样的事,只是除了当前单元的键名也会在每次循环中被赋给变量 $key。

那么这个函数如何实现变量的覆盖呢?我们来看个案例.

代码示例:

<?php
    $name = 'who';
    foreach($_GET as $key => $value)	{
    
      
            $$key = $value;  
    }  
    if($name == "Alice"){
    
    
        echo 'You are right!';
    }
	else{
    
    
        echo 'You are flase!';
    }
?>

那么执行结果是怎样的呢?当我们直接打开页面的时候它会输出You are false!,而当我们通过GET传参name=Alice的时候,它会回显You are right!。那么这是为什么呢?我们来分析一下

关键点就在于$$这种写法。这种写法称为可变变量。一个变量能够获取一个普通变量的值作为这个可变变量的变量名。当使用foreach来遍历数组中的值,然后再将获取到的数组键名作为变量,数组中的键值作为变量的值。这样就产生了变量覆盖漏洞,如上代码示例。其执行过程为$$key=$name,最后赋值为$value,从而实现了变量覆盖

猜你喜欢

转载自blog.csdn.net/pggril/article/details/123825148