S1xHcL's Blog.

php_bugs

Word count: 1.7kReading time: 8 min
2021/03/06 Share

PHP代码审计学习 项目地址

0x01 extract 变量覆盖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?php

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

?>

extract() : 从数组中将变量导入到当前的符号表。该函数使用数组键名作为变量名,使用数组键值作为变量值。针对数组中的每个元素,将在当前符号表中创建对应的一个变量。该函数返回成功设置的变量数目
file_get_contents() : 将整个文件读入一个字符串
trim() : 去除字符串首尾处的空白字符(或者其他字符)

trim ( string $str , string $character_mask = “ \t\n\r\0\x0B” ) : string

  • “ “ (ASCII 32 (0x20)),普通空格符
  • “\t” (ASCII 9 (0x09)),制表符
  • “\n” (ASCII 10 (0x0A)),换行符
  • “\r” (ASCII 13 (0x0D)),回车符
  • “\0” (ASCII 0 (0x00)),空字节符
  • “\x0B” (ASCII 11 (0x0B)),垂直制表符

payload: http://127.0.0.1/php_bugs/01.php?shiyan=&flag=

最开始$flag的值为xxx,通过函数extract()$flag$shiyan的值覆盖为空,则在if判断中为true打印flag值

1.png

0x02 绕过过滤的空白字符

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
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
<?php

$info = "";
$req = [];
$flag="xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";

ini_set("display_error", false); //为一个配置选项设置值
error_reporting(0); //关闭所有PHP错误报告

if(!isset($_GET['number'])){
header("hint:26966dc52e85af40f59b4fe73d8c323a.txt"); //HTTP头显示hint 26966dc52e85af40f59b4fe73d8c323a.txt

die("have a fun!!"); //die — 等同于 exit()

}

foreach([$_GET, $_POST] as $global_var) { //foreach 语法结构提供了遍历数组的简单方式
foreach($global_var as $key => $value) {
$value = trim($value); //trim — 去除字符串首尾处的空白字符(或者其他字符)
is_string($value) && $req[$key] = addslashes($value); // is_string — 检测变量是否是字符串,addslashes — 使用反斜线引用字符串
}
}


function is_palindrome_number($number) {
$number = strval($number); //strval — 获取变量的字符串值
$i = 0;
$j = strlen($number) - 1; //strlen — 获取字符串长度
while($i < $j) {
if($number[$i] !== $number[$j]) {
return false;
}
$i++;
$j--;
}
return true;
}


if(is_numeric($_REQUEST['number'])) //is_numeric — 检测变量是否为数字或数字字符串
{

$info="sorry, you cann't input a number!";

}
elseif($req['number']!=strval(intval($req['number']))) //intval — 获取变量的整数值
{

$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;

is_string() : 检测变量是否是字符串
addslashes() : 使用反斜线引用字符串
strval() : 获取变量的字符串值
intval() : 获取变量的整数值
is_numeric() : 检测变量是否为数字或数字字符串

payload: http://127.0.0.1/php_bugs/02.php?number=%00%0c1

is_numeric()可以通过\0(%00)绕过,从而试得is_numeric($_REQUEST['number'])false

2.png

由于trim()函数没有过滤\f(%0c),而intval()函数跳过\f(%0c),导致$value1$value2都为相等,进入到is_palindrome_number函数成功通过$number[$i] !== $number[$j]检测返回false,最终进入到获取$flag最后的else里,输出flag

3.png

0x03 多重加密

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
<?php
include 'common.php';
$requset = array_merge($_GET, $_POST, $_SESSION, $_COOKIE);
//把一个或多个数组合并为一个数组
class db
{
public $where;
function __wakeup()
{
if(!empty($this->where))
{
$this->select($this->where);
}
}
function select($where)
{
$sql = mysql_query('select * from user where '.$where);
//函数执行一条 MySQL 查询。
return @mysql_fetch_array($sql);
//从结果集中取得一行作为关联数组,或数字数组,或二者兼有返回根据从结果集取得的行生成的数组,如果没有更多行则返回 false
}
}

if(isset($requset['token']))
//测试变量是否已经配置。若变量已存在则返回 true 值。其它情形返回 false 值。
{
$login = unserialize(gzuncompress(base64_decode($requset['token'])));
//gzuncompress:解压缩一个压缩的字符串
//unserialize: 将已序列化的字符串还原回 PHP 的值

$db = new db();
$row = $db->select('user=\''.mysql_real_escape_string($login['user']).'\'');
//mysql_real_escape_string() 函数转义 SQL 语句中使用的字符串中的特殊字符。

if($login['user'] === 'ichunqiu')
{
echo $flag;
}else if($row['pass'] !== $login['pass']){
echo 'unserialize injection!!';
}else{
echo "(╯‵□′)╯︵┴─┴ ";
}
}else{
header('Location: index.php?error=1');
}

?>

array_merge() : 合并一个或多个数组
base64_decode() : 对base64编码的数据进行解码
base64_encode() : 使用base64对数据进行编码
gzuncompress() : 解压缩一个压缩的字符串
gzcompress() : 压缩一个字符串
unserialize() : 对单一的已序列化的变量进行操作,将其转换回 PHP 的值
serialize() : 对一个 PHP 的值进行序列

payload: http://127.0.0.1/php_bugs/03.php?token=eJxLtDK0qi62MrFSKi1OLVKyLraysFLKTM4ozSvMLFWyrgUAo4oKXA==

1
2
3
4
5
6
7
<?php
$arr = array("user" => "ichunqiu");
$token = base64_encode(gzcompress(serialize($arr)));
print_r($token);

// eJxLtDK0qi62MrFSKi1OLVKyLraysFLKTM4ozSvMLFWyrgUAo4oKXA==
?>

题目环境不全,描述解题方法

获取flag的值需要满足$login['user'] === 'ichunqiu', $login的值来自

1
$login = unserialize(gzuncompress(base64_decode($requset['token'])));

token的值先经过了base64_decode解码,然后通过gzuncompress解压缩字符串,字符串经过unserialize反序列号赋值

$requset['token']的值来源于$requset = array_merge($_GET, $_POST, $_SESSION, $_COOKIE);

也就是说,我们将user的值通过数组形式进行赋值

1
$arr = array("user" => "ichunqiu");

然后通过上述的编码方式编码回去,依次进行序列化,字符串压缩和base64编码

1
$token = base64_encode(gzcompress(serialize($arr)));

0x04 SQL注入_WITH ROLLUP绕过

0x05 ereg正则%00截断

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
<?php 

$flag = "flag";

if (isset ($_GET['password']))
{
if (ereg ("^[a-zA-Z0-9]+$", $_GET['password']) === FALSE)
{
echo '<p>You password must be alphanumeric</p>';
}
else if (strlen($_GET['password']) < 8 && $_GET['password'] > 9999999)
{
if (strpos ($_GET['password'], '*-*') !== FALSE) //strpos — 查找字符串首次出现的位置
{
die('Flag: ' . $flag);
}
else
{
echo('<p>*-* have not been found</p>');
}
}
else
{
echo '<p>Invalid password</p>';
}
}
?>

ereg() : 用指定的模式搜索一个字符串中指定的字符串,如果匹配成功返回true,否则,则返回false。搜索字母的字符是大小写敏感的
strpos() : 函数查找字符串在另一字符串中第一次出现的位置

解法一:

ereg()只能处理字符串,提交password为数组形式可绕过,下文中的strpos()判断不能是数组,所以返回为null,而null不等于false成立,输出flag

payload: ?password[]=

解法二:

CATALOG
  1. 1. 0x01 extract 变量覆盖
  2. 2. 0x02 绕过过滤的空白字符
  3. 3. 0x03 多重加密
  4. 4. 0x04 SQL注入_WITH ROLLUP绕过
  5. 5. 0x05 ereg正则%00截断