PHP代码审计实例
0x01.extract变量覆盖
<?php
$flag='C:\test.txt';
extract($_GET);
if(isset($shiyan))
{
$content=trim(file_get_contents($flag));//当文件不存在时$content为空,传入一个空的$shiyan即可,但是当文件存在时,这要将$flag也传入空。
if($shiyan==$content)
{
echo'ctf{xxx}';
}
else
{
echo'Oh.no';
}
}
?>
payload:http://127.0.0.1/?shiyan=&flag=
0x02.绕过过滤的空白字符
<?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;
首先判断了$_GET是否传入了number,没有则返回hint并且终止程序且输出“have a fun!!”,接下来遍历传入的数组,检测传入的number是否为数字或者数字字符串,如果是则失败(绕过),再判断是否为整数(绕过),再判断变量的字符串反转值和其本事是否相等,最后再调用is_palindrome_number这个函数判断是否为回文数(绕过)返回false则拿到flag。
条件:
1.不为空,且不能是一个数值型数字,包括小数。(由is_numeric函数判断)//%00或者%20绕过
2.不能是一个回文数。(is_palindrome_number判断)// /f(%0c)绕过
3.该数的反转的整数值应该和它本身的整数值相等;
2与3冲突//传入一个最大整数值或者/f绕过2
方法:
一:
1、is_numeric函数对于空字符%00判断为非数值,绕过第一个条件。
函数判断为非数值,但又不影响它值的构造,理所当然想到空格字符%20和空字符%00。
2、2147483647不是一个回文数绕过第二个条件。
3、这里利用了intval函数的溢出问题。
Intval最大的值取决于操作系统。 32 位系统最大带符号的 integer 范围是 -2147483648 到 2147483647。举例,在这样的系统上, intval(‘1000000000000’) 会返回 2147483647。64 位系统上,最大带符号的 integer 值是 9223372036854775807。
至于服务器是什么系统的可以用burp抓包分析出来为32位
$value1 = intval($req["number"]);
$value2 = intval(strrev($req["number"]));
我们构造的2147483647经过strrev反转函数后为7463847412,又经过intval函数值又变为2147483647。
故满足条件3,可以输出flag。
二:
因为要求不能为回文数,但又要满足intval($req["number"])=intval(strrev($req["number"])),
所以我们采用科学计数法构造payload为number=0e-0%00
,这样的话我们就可以绕过。
xey在php中就是x*10的y次方
注意
is_numeric函数对于空字符%00,无论是%00放在前后都可以判断为非数值,而%20空格字符只能放在数值后
三:
is_numeric函数在开始判断前,会先跳过所有空白字符可是题目获取$req[‘number’]的时候明明使用trim过滤了空白字符这时候我们可以引入\f(也就是%0c)在数字前面,来绕过最后那个is_palindrome_number函数,而对于前面的数字判断,因为intval和is_numeric都会忽略这个字符,所以不会影响。
所以我们构造payload=URL?%00%0c191即可绕过上面的条件获得flag
四:
+(%2b)
‘+191’=='191’且intval(‘191+’)==191
payload
%0c0%20
%002147483647
0e-0%00
%00%2b0
…
0x03.多重加密
<?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');
}
?>