PHP代码审计系列(一)
创始人
2024-03-17 10:21:28
0

PHP代码审计系列(一)

本系列将收集多个PHP代码安全审计项目从易到难,并加入个人详细的源码解读。此系列将进行持续更新。

extract变量覆盖

源码如下

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

在代码中主要使用了extract函数与file_get_contents函数

在RUNOOB给出的extract函数实例是:

将键值 “Cat”、“Dog” 和 “Horse” 赋值给变量 $a、$b 和 $c:

 "Cat","b" => "Dog", "c" => "Horse");
extract($my_array);
echo "\$a = $a; \$b = $b; \$c = $c";
?>

运行结果

$a = Cat; $b = Dog; $c = Horse 

extract() 函数从数组中将变量导入到当前的符号表。

该函数使用数组键名作为变量名,使用数组键值作为变量值。针对数组中的每个元素,将在当前符号表中创建对应的一个变量。

该函数返回成功设置的变量数目。

在RUNOOB给出的file_get_contents函数实例是:

 

运行结果

This is a test file with test text. 

file_get_contents() 把整个文件读入一个字符串中。

该函数是用于把文件的内容读入到一个字符串中的首选方法。如果服务器操作系统支持,还会使用内存映射技术来增强性能。

利用:

利用extract的语法特性使shiyan变量等于flag就可以了

http://localhost/phpbugs/01extract.php?shiyan=&flag
http://localhost/phpbugs/01extract.php?shiyan=&flag=1

绕过过滤的空白字符

源码如下

header("hint:26966dc52e85af40f59b4fe73d8c323a.txt"); //HTTP头显示hint 26966dc52e85af40f59b4fe73d8c323a.txtdie("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字段,若不存在直接终止当前脚本

然后会遍历$_GET与$_POST数组,若value是字符串将对value进行处理后保存到$req数组

之后会判断提交的number是数字或数字字符串都会终止

判断number若不是整数则终止

接下来会取出number对应的字符串,如果字符串反转后不相等则终止

然后调用is_palindrome_number函数,从字符串的首尾进行遍历判断是否都相等

若不等则输出真正的flag

经过分析需要做其实一共就三件事:

1.绕过number数字判断并输入整数

2.成功通过字符串反转相等的校验

3.避开is_palindrome_number函数的相等校验

针对1我们可以利用%00截断符进行绕过

在这里插入图片描述

针对2、3我们可以使用\f换页符(%0C)或者+(%2B)进行绕过

因为intval和is_numeric都会忽略这两个个字符,因为字符串首末遍历不相等又成功绕过is_palindrome_number

同时也可以写脚本fuzz出%0C或%2B进行绕过

import requestsfor i in range(256):rq = requests.get("http://127.0.0.1/phpbugs/02.php?number=%s121"%("%00"+"%%%02X"%i))if 'x' in rq.text:print ("%%%02X" % i)

在这里插入图片描述

最后结果
在这里插入图片描述

多重加密

ps:这题官方给的答案是错的,网上给的也是错的,麻烦别直接拿给的答案来抄好吗?大家不要被误导了

源码如下:

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');}?> 

通读代码逻辑如下:

首先是将$_GET,$_POST,$_SESSION,$_COOKIE合并为一个数组$request

然后有一个db类,它会判断类中的变量where是否为空,若不为空则调用select方法根据where条件进行查询user表返回一个数组

然后判断$request数组中是否有存在token,若不存在直接返回index.php报错,若存在则先对该值进行base64加密(base64_decode),再进行字符串压缩(gzuncompress),再进行反序列化(unserialize)最后赋值给$login

之后取出$_login中的user作为where条件user=$login[user]通过db的select进行查询结果赋值给$row

如果$login[user]与ichunqiu字符串完全相等则输出flag(成功结果),若$row[pass]不等于$login[pass]则输出反序列化失败,其他情况则输出一段字符串

需要我们做的其实就是:

反解密ichunqiu,然后提交token就可以了

网上的错误答案:


麻烦自己打印下看看真的相等吗

print($arr['user'] === 'ichunqiu');

正确答案:

 "ichunqiu");
$token = base64_encode(gzcompress(serialize($login)));
print($token);
echo '
'; print($login['user'] === 'ichunqiu');

在这里插入图片描述

结果验证:

因为这题代码和数据库没给完整,为了验证结果简化代码如下

$login = unserialize(gzuncompress(base64_decode($requset['token'])));if($login['user'] === 'ichunqiu'){echo $flag;}else{echo "(╯‵□′)╯︵┴─┴ ";}}else{header('Location: index.php?error=1');}?> 

错误答案获得的token

eJxLtDK0qs60MrBOAuJaAB5uBBQ=

在这里插入图片描述

正确答案获得的token

eJxLtDK0qi62MrFSKi1OLVKyLraysFLKTM4ozSvMLFWyrgUAo4oKXA==

在这里插入图片描述

SQL注入_WITH ROLLUP绕过

源码如下

echo '
'."
";echo ''."
";echo ''."
";echo ''."
";echo '
'."
";echo ''."
";die; }function AttackFilter($StrKey,$StrValue,$ArrReq){ if (is_array($StrValue)){//检测变量是否是数组$StrValue=implode($StrValue);//返回由数组元素组合成的字符串}if (preg_match("/".$ArrReq."/is",$StrValue)==1){ //匹配成功一次后就会停止匹配print "水可载舟,亦可赛艇!";exit();} }$filter = "and|select|from|where|union|join|sleep|benchmark|,|\(|\)"; foreach($_POST as $key=>$value){ //遍历数组AttackFilter($key,$value,$filter); }$con = mysql_connect("XXXXXX","XXXXXX","XXXXXX"); if (!$con){die('Could not connect: ' . mysql_error()); } $db="XXXXXX"; mysql_select_db($db, $con);//设置活动的 MySQL 数据库$sql="SELECT * FROM interest WHERE uname = '{$_POST['uname']}'"; $query = mysql_query($sql); //执行一条 MySQL 查询if (mysql_num_rows($query) == 1) { //返回结果集中行的数目$key = mysql_fetch_array($query);//返回根据从结果集取得的行生成的数组,如果没有更多行则返回 falseif($key['pwd'] == $_POST['pwd']) {print "CTF{XXXXXX}";}else{print "亦可赛艇!";} }else{print "一颗赛艇!"; } mysql_close($con); ?>

通读代码逻辑如下:

首先是写了一个表单POST提交uname与pwd

接下来遍历$_POST数组调用AttackFilter方法,传入key:value参数与filter

AttackFilter会首先判断传入的value是不是数组,若为数组则将多个元素合成一个字符串重新赋值给传入的value

然后对value进行正则匹配匹配规则为filter,如果匹配成功脚本停止

在之后会连接数据库,根据提交的uname进行查询

如果返回结果集中行的数目等于1,则返回从结果集取得的行生成的数组key

如果数组key中的pwd字段等于表单提交的pwd字段则获得flag其他情况均失败

我们需要做的其实就是:

1.绕过filter

$filter = "and|select|from|where|union|join|sleep|benchmark|,|\(|\)"

2.使表单提交的pwd等于查询到返回的pwd

SQL语句:

SELECT * FROM interest WHERE uname = '{$_POST['uname']}'

进行绕过

SELECT * FROM interest WHERE uname = 'admin' GROUP BY pwd WITH ROLLUP LIMIT 1 OFFSET 1-- -'

在这里插入图片描述

也就是说使用以下语句登录用户时,密码为空就可以成功绕过

admin' GROUP BY pwd WITH ROLLUP LIMIT 1 OFFSET 1-- -'

在这里插入图片描述

在这里插入图片描述

此题需要了解的SQL主要是以下这段SQL

WITH ROLLUP LIMIT 1 OFFSET 1

首先是WITH ROLLUP,用在group up后会统计所有结果并返回NULL

在这里插入图片描述

再看LIMIT 1 OFFSET 1,是取一条数据从第一条数据后开始取,也就是取第二条数据

在这里插入图片描述

等同于LIMIT 1,1,但因为过滤了,所以用OFFSET来进行绕过

ereg正则%00截断

源码如下

if (ereg ("^[a-zA-Z0-9]+$", $_GET['password']) === FALSE){echo '

You password must be alphanumeric

';}else if (strlen($_GET['password']) < 8 && $_GET['password'] > 9999999){if (strpos ($_GET['password'], '*-*') !== FALSE) //strpos — 查找字符串首次出现的位置{die('Flag: ' . $flag);}else{echo('

*-* have not been found

'); }}else {echo '

Invalid password

'; }} ?>

通读代码逻辑如下:

首先判断是否存在GET请求的password字段,如果存在继续进行

然后对password进行正则匹配如果password是大小写字母或数字则进行否则输出停止

然后对password的长度进行判断,如果长度小于8数值大于9999999则继续进行

之后利用strops函数查找* - *在字符串中首次出现的位置,如果找到了则输出flag

我们需要做的:

1.password使用大小写字母及数字

2.password长度小于8 数值大于9999999

3.password中存在* - *

这里面的条件都是互相矛盾的,在满足1的条件下条件2用科学计数法1e7也就是10的7次方进行绕过。条件2利用ereg的00%截断符进行绕过,经过分析payload如下

?password=1e7%00*-*

在这里插入图片描述

相关内容

热门资讯

汽车油箱结构是什么(汽车油箱结... 本篇文章极速百科给大家谈谈汽车油箱结构是什么,以及汽车油箱结构原理图解对应的知识点,希望对各位有所帮...
美国2年期国债收益率上涨15个... 原标题:美国2年期国债收益率上涨15个基点 美国2年期国债收益率上涨15个基...
嵌入式 ADC使用手册完整版 ... 嵌入式 ADC使用手册完整版 (188977万字)💜&#...
重大消息战皇大厅开挂是真的吗... 您好:战皇大厅这款游戏可以开挂,确实是有挂的,需要了解加客服微信【8435338】很多玩家在这款游戏...
盘点十款牵手跑胡子为什么一直... 您好:牵手跑胡子这款游戏可以开挂,确实是有挂的,需要了解加客服微信【8435338】很多玩家在这款游...
senator香烟多少一盒(s... 今天给各位分享senator香烟多少一盒的知识,其中也会对sevebstars香烟进行解释,如果能碰...
终于懂了新荣耀斗牛真的有挂吗... 您好:新荣耀斗牛这款游戏可以开挂,确实是有挂的,需要了解加客服微信8435338】很多玩家在这款游戏...
盘点十款明星麻将到底有没有挂... 您好:明星麻将这款游戏可以开挂,确实是有挂的,需要了解加客服微信【5848499】很多玩家在这款游戏...
总结文章“新道游棋牌有透视挂吗... 您好:新道游棋牌这款游戏可以开挂,确实是有挂的,需要了解加客服微信【7682267】很多玩家在这款游...
终于懂了手机麻将到底有没有挂... 您好:手机麻将这款游戏可以开挂,确实是有挂的,需要了解加客服微信【8435338】很多玩家在这款游戏...