还有两个月Xctf就开始了,也是第一次参加Xctf这样高校中难度级别最高的比赛。最近做了几道历届的Xctf题。
1.
根据提示在URL处尝试hint=1,然后页面给出了一段代码。
<?php error_reporting(0); include_once("flag.php"); $cookie = $_COOKIE['ISecer']; if(isset($_GET['hint'])){ show_source(__FILE__); } elseif (unserialize($cookie) === "$KEY") { echo "$flag"; } else { ?> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>Login</title> <link rel="stylesheet" href="admin.css" type="text/css"> </head> <body> <br> <div class="container" align="center"> <form method="POST" action="#"> <p><input name="user" type="text" placeholder="Username"></p> <p><input name="password" type="password" placeholder="Password"></p> <p><input value="Login" type="button"/></p> </form> </div> </body> </html> <?php } $KEY='ISecer:www.isecer.com'; ?>
代码的意思很明确,cookie反序列化后得到的字符串等于key,就得到答案。
开始以为key的值就是最下面的那个isecer但后来看整段代码,发现其实key的值还没有被定义,因此key的值应该是NULL
序列化后的结果s:0:"",于是构造cookie:ISser = s:0:""
但是直接把这个cookie上传到服务器上,服务器不会解析,原因是;(分号)需要被转义,最终的cookie应该是ISser = s:0:""%3B
这种题在Xctf上出现也不算罕见,提示的信息很重要,例如这道题,给出的hint其实是一个重要的参数,我在做这道题的时候这个切入点也是蒙出来的。
2.php_encrypt(密码学)
题目中给出这样一段字符串
fR4aHWwuFCYYVydFRxMqHhhCKBseH1dbFygrRxIWJ1UYFhotFjA=
还有一段代码
<?php function encrypt($data,$key) { $key = md5('ISCC'); $x = 0; $len = strlen($data); $klen = strlen($key); for ($i=0; $i < $len; $i++) { if ($x == $klen) { $x = 0; } $char .= $key[$x]; $x+=1; } for ($i=0; $i < $len; $i++) { $str .= chr((ord($data[$i]) + ord($char[$i])) % 128); } return base64_encode($str); } ?>
flag为一段字符串,将flag传入给data,然后运行这个函数,最终运行结果是提示中的字符串,因此需要逆向找出这个flag。
<?php function decrypt($str) { $mkey = "729623334f0aa2784a1599fd374c120d"; $klen = strlen($mkey); $tmp = $str; $tmp = base64_decode($tmp); // 对 base64 后的字符串 decode $md_len = strlen($tmp); //获取字符串长度 $x = 0; $char = ""; for($i=0;$i < $md_len;$i++) { // 取二次加密用 key; if ($x == $klen) // 数据长度是否超过 key 长度检测 $x = 0; $char .= $mkey[$x]; // 从 key 中取二次加密用 key $x+=1; } $md_data = array(); for($i=0;$i<$md_len;$i++) { // 取偏移后密文数据 array_push($md_data, ord($tmp[$i])); } $md_data_source = array(); $data1 = ""; $data2 = ""; foreach ($md_data as $key => $value) { // 对偏移后的密文数据进行还原 $i = $key; if($i >= strlen($mkey)) {$i = $i - strlen($mkey);} $dd = $value; $od = ord($mkey[$i]); array_push($md_data_source,$dd); $data1 .= chr(($dd+128)-$od); // 第一种可能, 余数+128-key 为回归数 $data2 .= chr($dd-$od); // 第二种可能, 余数直接-key 为回归数 } print "data1 => ".$data1."<br>\n"; print "data2 => ".$data2."<br>\n"; } $str = "fR4aHWwuFCYYVydFRxMqHhhCKBseH1dbFygrRxIWJ1UYFhotFjA="; decrypt($str); ?>
Flag:{asdqwdfasfdawfefqwdqwdadwqadawd}
代码审计:
- extract覆盖变量
<?php $flag='xxx'; extract($_GET); if(isset($shiyan)) { $content=trim(file_get_contents($flag)); if($shiyan==$content) { echo'flag{xxx}'; } else { echo'Oh.no'; } } ?>
存在一个名称为shiyan的字符串,将flag变量的值赋值给content变量。如果shiyan=content,输出flag,extract的作用是可以替换变量,也就是无论传入时的变量名是什么,被extract后都会变成flag。
因此可以构造?shiyan=&flag,这样的写法是因为shiyan这个变量被extract之后是一个空值,flag也是空值,二者相等就可以执行输出flag的语句。
flag{bugku-dmsj-p2sm3N}
-
strcmp
用法:stramp(a,b) a大于b返回1,等于返回0,小于返回-1。
<?php $flag = "flag{xxxxx}"; if (isset($_GET['a'])) { if (strcmp($_GET['a'], $flag) == 0) //如果 str1 小于 str2 返回 < 0; 如果 str1大于 str2返回 > 0;如果两者相等,返回 0。 //比较两个字符串(区分大小写) die('Flag: '.$flag); else print 'No'; } ?>
这里直接传进去一个a[]=1 a是一个数组类型就可以。
-
urldecode二次编码绕过
<?php if(eregi("hackerDJ",$_GET[id])) { echo(" not allowed! "); exit(); } $_GET[id] = urldecode($_GET[id]); if($_GET[id] == "hackerDJ") { echo " Access granted! "; echo " flag "; } ?>
eregi()函数在一个字符串搜索指定的模式的字符串。搜索不区分大小写
因此对于传入的id第一个要求不能为"hackerDJ"
第二个地方id 需要两次urldecode 因为在传入id时已经经过一次编码。
id=hackerD%254A
-
md5()函数
<?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'; } ?>
直接以数组的形式进行不同的赋值。username[]=1&password[]=2
-
数组返回NULL绕过
<?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'; } ?>
strops:查找 "php" 在字符串中第一次出现的位置,用空数组可以进行绕过。
erges:字符截断,%00
http://120.24.86.145:9009/19.php?password[]=%00
-
弱类型整数大小比较绕过
$temp = $_GET['password']; is_numeric($temp)?die("no numeric"):NULL; if($temp>1336){ echo $flag
弱类型包括数字+字符,数组,这里用的是数组;
password[]=1
-
sha()函数比较绕过
<?php $flag = "flag"; if (isset($_GET['name']) and isset($_GET['password'])) { var_dump($_GET['name']); echo " "; var_dump($_GET['password']); var_dump(sha1($_GET['name'])); var_dump(sha1($_GET['password'])); if ($_GET['name'] == $_GET['password']) echo ' Your password can not be your name! '; else if (sha1($_GET['name']) === sha1($_GET['password'])) die('Flag: '.$flag); else echo ' Invalid password. ';
sha和MD5都是加密方式,都无法处理数组,因此和MD5处理方式相同。name[]=1&password[]=2