標題:CTF 中的PHP 代碼審計

taibeihacker

Moderator

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​

20190116101600.png-water_print

Payload:url?param1=QNKCDZOparam2=aabg7XSs

1.2.2.2 DEMO 2​

20190116101741.png-water_print

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

1.2.2.3 DEMO 3​

20190116101835.png-water_print

經過String 強制類型轉換後,當傳入數組時,返回值為Array fail,無法繞過,因此,採用MD5 碰撞的形式。
工具:fastcoll
Payload:
20190116110131.png-water_print

1.2.2.4 DEMO 4​

20190116101915.png-water_print

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

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

20190116103644.png-water_print

Payload:url?password=ffifdyop
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 函数​

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 函数​

20190116143759.png-water_print

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 浮点数精度​

20190116150154.png-water_print

3.3 is_numeric 函数​

20190116150258.png-water_print

當傳入的字符串中含有空格、\t \r \n \v \f 等特殊符號,返回結果仍為true

3.3 trim 函数​

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 函数​

20190116153537.png-water_print

如果我們自己指定範圍,如果過小是很容易被爆破出來的,因此大多數實際應用中都是不指定範圍,mt_rand() 函數默認範圍是0 到mt_getrandmax() 之間的偽隨機數
20190116153802.png-water_print

相同的種子生成的隨機數是相同的,所以可以通過逆推mt_rand 的種子來獲得同頁面的另一個rand 的值
 
返回
上方