【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